Add memory-dream skill + additive cross-machine memory sync

memory-dream: read-only memory lint/consolidation analyzer (index, backlinks,
stale refs, dup clusters, profile drift); additive-only --apply-safe, all
merges/deletes are proposals. sync-memory.sh: additive repo<->harness-profile
union (no delete/overwrite, conflicts surfaced), wired to a SessionStart hook.
Migrates the useful profile-only memories into the synced repo store.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-01 15:21:56 -07:00
parent a00069a020
commit 2a1ccfac73
24 changed files with 1875 additions and 0 deletions

View File

@@ -29,6 +29,7 @@
- [Howard Enos](user_howard.md) — Mike's brother, technician, full access. Machines: ACG-TECH03L, Howard-Home (authoritative in users.json).
## Feedback
- [Scheduling = coord todo, not schedulers](feedback_scheduling_via_coord_todo.md) — Defer future work as a coord todo (POST /api/coord/todos; needs text + created_by_user + created_by_machine) for a later session to pick up. NOT /schedule remote CCR agents (no vault/creds there) or local scheduled tasks.
- [Identify RMM agent by IP](feedback_rmm_identify_by_ip.md) — When the target machine is known by external IP, match the IP to find the agent; don't recon every candidate. (GuruRMM doesn't store agent IPs yet — todo 7459428e.)
- [Attribution is read, never inferred](feedback_attribution_from_identity.md) — Who-did-what (user+machine) comes ONLY from identity.json + users.json + git authorship. Never infer from hostname patterns, the userEmail hint, or memory. The "5070" box is Mike's. sync.sh reconciles git config to identity.json; /save renders the User block via whoami-block.sh.
- [GuruRMM agent parity rule](feedback_gururmm_agent_parity.md) — "Add feature X to the agent" = Windows + Linux + macOS in the same change, no exceptions. Stub + TODO if real impl not feasible.
@@ -79,6 +80,7 @@
- [Mac gururmm setup pending](project_mac_gururmm_setup_pending.md) — ACTION REQUIRED: run `bash scripts/install-hooks.sh` in gururmm repo on Mikes-MacBook-Air before any RMM work
## Project
- [Automate memory consolidation/lint (phased)](project_memory_consolidation_automation.md) — Eventually auto-run /memory-dream; lint+additive fixes can automate early, merges/deletes stay human-approved. Engine: .claude/skills/memory-dream/ + .claude/scripts/sync-memory.sh.
- [RMM webhook docs-only build guard](project_rmm_webhook_docs_guard.md) — RMM build webhook skips docs-only pushes (host guard in /opt/gururmm/webhook-handler.py, SPEC-020 Phase 0); repo copy is stale, don't redeploy it
- [GuruConnect v2 direction](project_guruconnect_v2_direction.md) — v2 re-architecture (SPEC-002, 2026-05-29): greenfield-salvage-cores, NATIVE-first (full key fidelity Win+R/Ctrl+Alt+Del + bidirectional file cut/paste/drag are Mike's headline must-haves; WebRTC fallback only), standalone-first + RMM contract, hardened single-tenant but tenancy-ready schema. Willing to scrap v1 entirely.
- [Apple MDM + Developer certs (GuruRMM mobile)](project_apple_mdm_certs.md) — ACG holds both Apple Developer+signing and Apple MDM Push certs (acquired 2026-05-29) for SPEC-017 mobile support. MDM push cert RENEWS ANNUALLY on the same Apple ID or all enrolled iOS devices break. Capture Apple ID + expiry.

View File

@@ -0,0 +1,16 @@
---
name: Client communication tone
description: How to write client-facing Syncro comments — expert partner, not intake questionnaire
type: feedback
originSessionId: 4ccedc24-2f39-497e-9a89-ca09aba03982
---
Write client comments from the position of a senior MSP that has managed the client for years. State findings, state what we did or are doing, ask only for the one specific thing we genuinely don't know.
**Why:** ACG has managed clients like GlazTech for 10-15 years. We know their locations, key staff, infrastructure, and service accounts. Comments that ask "can you tell us about your setup?" or list basic discovery questions make us look like we just walked in the door.
**How to apply:**
- Lead with what we found and what we already know
- Frame questions as targeted confirmations, not open-ended discovery ("Is FaxFinder authenticating via SMTP basic auth, or has that been migrated to OAuth?" — not "What does the FaxFinder account do?")
- Never ask the client to explain their own infrastructure to us unless Mike explicitly says we don't have context
- Steve Eastman (seastman@glaztech.com) is GlazTech's internal IT person — desktop-level tech, guides technical direction, ~200 users across 9 locations. We implement what he directs. Treat him as a peer, not an end user.
- If we're missing context (IPs, staff roles, auth methods), check session logs and vault first. Ask Mike privately before asking the client.

View File

@@ -0,0 +1,20 @@
---
name: Add Mike as owner on all Entra apps
description: Apps created via management SP have no user owner — must add Mike manually or publisher verification fails
type: feedback
originSessionId: 045c6ef2-5711-4aca-b86f-55506c9b6ada
---
After creating any Entra app registration via the ComputerGuru-Management service principal, always add Mike (f34ebe40-9565-4135-af4c-2e808df57a25) as an owner immediately.
**Why:** Apps created via client credentials have no user owner. Microsoft requires a user owner to perform publisher verification (MPN badge). Without this step, the portal shows "A verified publisher cannot be added to this application."
**How to apply:** After every `POST /v1.0/applications` call, immediately run:
```bash
curl -sk -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
"https://graph.microsoft.com/v1.0/applications/{APP_OBJ_ID}/owners/\$ref" \
-d '{"@odata.id":"https://graph.microsoft.com/v1.0/directoryObjects/f34ebe40-9565-4135-af4c-2e808df57a25"}'
```
Mike's user object ID: `f34ebe40-9565-4135-af4c-2e808df57a25`

View File

@@ -0,0 +1,14 @@
---
name: feedback-gururmm-builds
description: "GuruRMM builds must go through the Gitea webhook pipeline, never run manually via SSH"
metadata:
node_type: memory
type: feedback
originSessionId: 541d4004-8c45-4290-89f5-0ba9ee4e64a9
---
Never run `build-agents.sh` directly via SSH. All builds go through the normal Gitea webhook pipeline (push to main triggers the build automatically).
**Why:** Manual runs execute as the SSH user (`guru`) instead of root, breaking log writes, artifact cleanup, and service restarts. The pipeline exists precisely to handle this correctly.
**How to apply:** To trigger a build, push a commit to the gururmm main branch on Gitea. If a test build is needed without a real change, use an empty commit: `git commit --allow-empty -m "chore: trigger build"`.

View File

@@ -0,0 +1,16 @@
---
name: No TOML/config file approach for endpoints
description: User explicitly prohibits TOML or config-file-based endpoint configuration — this will never be approved
type: feedback
originSessionId: 50d853e9-1d2f-4094-9b7b-f509fb95891f
---
Never propose storing endpoint URLs, server addresses, API targets, or connection parameters in TOML files, config files, INI files, or any file-based config approach when it comes to deployed agents or endpoints.
**Why:** User stated directly: "I cannot stand the toml/config file approach to anything when it comes to endpoints" and "that approach will never be approved by me/the user."
**How to apply:** When designing agent deployment, enrollment, or configuration:
- Embed endpoint/server data directly in the binary (compile-time constants, build flags, or baked into the installer)
- Use registry keys (Windows) for anything that must be configurable post-install
- Use MSI properties for install-time configuration
- Never write agent.toml, config.toml, settings.ini, or equivalent files containing server URLs or connection endpoints
- This applies to GuruRMM agent and any future agent/endpoint projects

View File

@@ -0,0 +1,11 @@
---
name: Python on Windows — use py launcher
description: Windows Store python/python3 aliases disabled; always use py or jq on DESKTOP-0O8A1RL
type: feedback
originSessionId: bdd13bc7-44b1-4e16-aba8-a3332b0c8b8e
---
Always use `py` (Windows launcher) for Python on this machine, never `python3` or bare `python`.
**Why:** Windows Store app execution aliases for python.exe and python3.exe were disabled (2026-04-20). `python3` now fails cleanly (command not found). `py` at `C:\Windows\py.exe` is the correct entry point and reliably finds Python 3.14 at `C:\Program Files\Python314\python.exe`.
**How to apply:** In any script, doc, or inline command that needs Python: use `py`. For simple JSON extraction from curl output, prefer `jq` (available at `C:\Users\guru\AppData\Local\Microsoft\WinGet\Links\jq.exe`) — no Python needed at all.

View File

@@ -0,0 +1,12 @@
---
name: feedback_scheduling_via_coord_todo
description: Defer/schedule future work as a coord todo for a later session to pick up - NOT remote CCR routines or local scheduled tasks
metadata:
type: feedback
---
When something needs to happen later ("check this tomorrow", "verify in 24-48h", "follow up next week"), create a **coord todo** (`POST /api/coord/todos`) assigned to the right user/project. A future ClaudeTools session picks it up at session-start. Do NOT use the `/schedule` skill (remote anthropic_cloud CCR agents) or local OS scheduled tasks.
**Why:** Mike's directive 2026-06-01. Remote cloud routines have NO access to the local SOPS vault, the age key, B2/Discord/API creds, or identity.json - so any credentialed task fails there. Local scheduled tasks need the box powered on and run blind. The coord API is the team's shared source of truth and every session already reads pending todos on startup, so a todo is the reliable, credential-available, multi-machine handoff.
**How to apply:** `POST http://172.16.3.30:8001/api/coord/todos`. REQUIRED fields the CLAUDE.md doc omits: `text` (the todo body - NOT `title`/`description`), `created_by_user`, `created_by_machine` (read user+machine from `.claude/identity.json`). Optional: `project_key`, `assigned_to_user`, `auto_created`, `source_context`, `parent_id`. Put enough context in `text` (commands, exact targets, the when) that a cold session can act without re-deriving. See [[reference_coord_messages_api_shape]].

View File

@@ -0,0 +1,16 @@
---
name: Syncro - preview all comments before posting
description: Every Syncro comment must be previewed and confirmed before posting, no exceptions
type: feedback
originSessionId: 4ccedc24-2f39-497e-9a89-ca09aba03982
---
**Rule:** ALWAYS show the full comment text to Mike and wait for explicit confirmation before posting ANY comment to a Syncro ticket. No exceptions — not for billing comments, not for resolution notes, not for client-facing messages, not for internal notes.
**Why:** Mike has called this out multiple times. Comments posted without preview have had wrong tone, missing context, or incorrect content. Once posted they can't be deleted via API and require manual GUI cleanup.
**How to apply:**
- Draft the comment, show it in chat as a formatted block
- Say "Good to post?" or similar and wait for a yes
- Only then call POST /tickets/{id}/comment
- This applies to every single comment regardless of how routine it seems
- Also always ask for minutes + labor type before logging any time entry — never assume a default

View File

@@ -0,0 +1,20 @@
---
name: Syncro duplicate prevention — tickets AND comments
description: Never retry ANY Syncro POST (ticket create or comment) without first GETting to confirm the action didn't already succeed — Syncro has no idempotency on any endpoint
type: feedback
originSessionId: 7034be43-1464-4085-b765-dc1226b1f8e0
---
Never retry a POST /comment to Syncro without first doing GET /tickets/{id} to confirm the comment did not already post. The server has no idempotency — one POST always creates one comment, regardless of whether the client saw an error.
**ALSO: Always show the full comment draft to the user and wait for explicit confirmation before posting ANY comment — including internal/hidden notes.** This rule has been violated twice. There are no exceptions.
**ALSO: This applies to ticket CREATION too — not just comments.** When a POST /tickets response looks wrong (null fields, jq error, etc.), do GET /customers/{id}/tickets BEFORE retrying. The response wrapper is `{"ticket": {...}}` — always use `.ticket.id` not `.id`. Duplicate tickets were created twice by retrying a succeeded POST. Violated 2026-04-22.
**Why:** A comment was duplicated on ticket #32185 because the first POST succeeded but jq threw a parse error on the response (em-dash in subject caused shell interpolation issue), making the request look failed. A retry posted a second copy. Comments cannot be deleted via API — duplicates require manual GUI removal.
**How to apply:**
- Always write comment payloads to a temp file (`/tmp/syncro_comment.json`) before posting — avoids shell quoting/encoding failures that produce misleading errors
- If any POST /comment tool call returns an error or ambiguous result, immediately GET /tickets/{id} and check `.ticket.comments` for the subject/timestamp before retrying
- A jq parse error, curl error, or timeout on the response does NOT mean the POST failed — verify first
- **CRITICAL — jq path:** POST /comment response is `{"comment": {...}}` — ALWAYS use `.comment.id`, `.comment.created_at` etc. Using `.id` returns null and looks like failure even when the comment landed. This caused a duplicate on 2026-04-23 (#32142). When GETting to verify, check ALL comments not just `[-3:]` — the new comment may not be the most recent if other activity occurred.
- When GETting to verify after an ambiguous POST, search by subject: `.ticket.comments[] | select(.subject == "...")`

View File

@@ -0,0 +1,17 @@
---
name: Syncro comment HTML formatting
description: Use <br> for line breaks in Syncro comments, not <ul>/<li> — list tags don't render
type: feedback
originSessionId: b39e319c-ac3e-49f5-afb6-755e08f1fd82
---
Use `<br>` for line breaks in Syncro comment bodies. Do NOT use `<ul>`, `<li>`, or other block-level list tags — Syncro's renderer collapses them into a single line with no spacing.
**Why:** Posted a comment with `<ul><li>` items and they all ran together on one line in the ticket view. Had to post a corrected duplicate.
**How to apply:** For any bulleted list in a Syncro comment, use:
```
- Item one<br>
- Item two<br>
- Item three
```
wrapped in a `<p>` tag. Never use `<ul>/<li>`.

View File

@@ -0,0 +1,14 @@
---
name: feedback-syncro-labor-tax
description: Labor is never taxable in Arizona — always set taxable=false on labor line items in Syncro
metadata:
node_type: memory
type: feedback
originSessionId: d91f202e-ddd5-46d7-b674-f848eb78aa8e
---
Always pass `"taxable": false` explicitly on labor line items via `add_line_item`.
**Why:** Labor products are configured with `taxable: false` in Syncro, but the `add_line_item` API endpoint does not inherit the product's taxable setting — it posts the line item as `taxable: true` regardless of the product config.
**How to apply:** Include `"taxable": false` in every `add_line_item` payload for labor products (remote, onsite, in-shop, emergency, prepaid). The product itself is correct; the API just doesn't carry it through.

View File

@@ -0,0 +1,24 @@
---
name: feedback_syncro_line_items
description: Correct Syncro API endpoint for adding labor/product line items to tickets
metadata:
node_type: memory
type: feedback
originSessionId: 282e0176-1bdb-49b7-8c15-faf152774d7e
---
Use `POST /api/v1/tickets/{internal_ticket_id}/add_line_item` to add line items to tickets. Both `name` and `description` fields are required (422 if either missing). Never use timers.
**Why:** `/line_item`, `/line_items`, and PUT `line_items_attributes` all 404. The correct endpoint was found via Syncro Swagger spec at api-docs.syncromsp.com. Mike has explicitly said never use timers.
**How to apply:**
- Path uses internal ticket ID (e.g., 111387456), not ticket number (32339)
- Required fields: `name`, `description`, `quantity`, `price`, `taxable` (and `product_id` if catalog item)
- Response is a flat object — parse `.id` directly (not `.line_item.id`)
- For testing/practice, use internal ACG account only (customer ID 15353550)
Example:
```
POST /api/v1/tickets/111387456/add_line_item
{"product_id":1049360,"name":"Labor- Warranty work","description":"...","quantity":1,"price":0.0,"taxable":false}
```

View File

@@ -0,0 +1,18 @@
---
name: feedback-syncro-live-rates
description: Always fetch Syncro labor rates live from the API — never use hardcoded rate table
metadata:
node_type: memory
type: feedback
originSessionId: d91f202e-ddd5-46d7-b674-f848eb78aa8e
---
Always fetch `price_retail` live from `GET /products/<id>``.product.price_retail` before billing any Syncro line item. Never use the rate table in the skill as a source of truth for dollar amounts.
**Why:** The hardcoded rate table was proven wrong on 2026-05-20 (ticket #32304, Cascades) when Labor - Remote Business was listed at $150/hr but the correct rate was $175/hr. Rates vary by contract and change over time.
**How to apply:** In any billing workflow, fetch the rate immediately after selecting the product_id:
```bash
RATE=$(curl -s "${BASE}/products/${PRODUCT_ID}?api_key=${API_KEY}" | jq -r '.product.price_retail')
```
Use this `$RATE` value for the Ollama draft prompt, the preview shown to the user, and the `price_retail` field in all payloads. The product ID table in the skill is still valid — just not the rate column.

View File

@@ -0,0 +1,11 @@
---
name: ACG Website Hosting
description: azcomputerguru.com is hosted on IX Web Hosting via cPanel
type: project
originSessionId: 045c6ef2-5711-4aca-b86f-55506c9b6ada
---
azcomputerguru.com is hosted on **IX Web Hosting** (ixwebhosting.com), managed via **cPanel**.
**Why:** Core ACG company website. Files deploy to `/public_html/` via cPanel File Manager or FTP.
**How to apply:** When uploading files to azcomputerguru.com, use cPanel on IX. Login via ixwebhosting.com → My Hosting → cPanel. Credentials should be vaulted at `clients/azcomputerguru/cpanel.sops.yaml` (pending).

View File

@@ -0,0 +1,14 @@
---
name: project-cascades-billing
description: "Cascades of Tucson Syncro billing — prepaid block customer, rate TBD"
metadata:
node_type: memory
type: project
originSessionId: d91f202e-ddd5-46d7-b674-f848eb78aa8e
---
Cascades of Tucson (Syncro customer_id: 20149445) is a prepaid block customer. As of 2026-05-20 the block had ~37.5 hrs remaining (38.5 minus 1hr for ticket #32304).
**Block rate:** Not yet confirmed — $175/hr is the standard non-block remote rate, NOT the Cascades block rate. Ask Mike before billing future Cascades tickets.
**How to apply:** Always check prepay_hours before billing. Invoices post at $0.00 with hours deducted by quantity. Confirm block rate with Mike before setting price_retail.

View File

@@ -0,0 +1,13 @@
---
name: Dataforth email infrastructure
description: Dataforth uses M365 for email; the Exchange server on 172.16.x.x / neptune.acghosting.com is NOT Dataforth's — it belongs to ACG's own infrastructure
type: project
originSessionId: 7034be43-1464-4085-b765-dc1226b1f8e0
---
Dataforth's email runs on Microsoft 365 (sysadmin@dataforth.com, tenant in vault at `clients/dataforth/m365.sops.yaml`).
The Exchange server at `neptune.acghosting.com` / `67.206.163.124` listed in the vault under `clients/dataforth/neptune-exchange.sops.yaml` is **not** part of Dataforth's infrastructure — do not use it for Dataforth email workflows.
**Why:** Mike corrected this during pipeline notification work (2026-04-22). The Exchange entry is an ACG-side server, not Dataforth's.
**How to apply:** For any Dataforth email sending, SMTP basic auth is disabled on the tenant. Must use OAuth2 — either XOAUTH2 over SMTP or (preferred) Microsoft Graph API `POST /v1.0/users/sysadmin@dataforth.com/sendMail` with a client_credentials token. Entra app is in vault at `clients/dataforth/m365.sops.yaml` under `credentials.entra-app`. Verify `Mail.Send` application permission is granted before use.

View File

@@ -0,0 +1,14 @@
---
name: project_memory_consolidation_automation
description: Goal - automate the memory lint/consolidation (/memory-dream) once the additive proposals prove trustworthy; phased rollout
metadata:
type: project
---
Mike wants the memory consolidation/lint process automated eventually (stated 2026-06-01). Phased so we never auto-wipe useful data:
1. **Now:** `/memory-dream` runs report-only by default; `--apply-safe` does additive-only fixes (append missing index lines, migrate profile-only memories in). Merges/dedups/deletions are PROPOSED only, human-approved. Build trust in the proposals first.
2. **Next - automate the read-only half:** the lint pass + additive `--apply-safe` are non-destructive, so safe to run unattended via a hook (e.g. Stop/SessionStart runs the lint and, on findings, drops a coord todo - NOT a cron/remote scheduler, per [[feedback_scheduling_via_coord_todo]]).
3. **Later - automate consolidation with guardrails:** auto-apply only high-confidence merges; low-confidence stays a proposal; deletion stays gated regardless. Earn the automation.
Engine already exists: skill `.claude/skills/memory-dream/` (+ `scripts/memory_dream.py`). Cross-machine sync of the repo memory store is handled by `.claude/scripts/sync-memory.sh` (additive union repo<->harness-profile, SessionStart hook). See also [[feedback_memory_repo_not_profile]] if/when written re: the two-store model (repo `.claude/memory/` syncs via Gitea; harness `~/.claude/projects/<slug>/memory/` is machine-local and auto-injected).

View File

@@ -0,0 +1,18 @@
---
name: project-pluto-build-server
description: "Pluto Windows build server — location, role, and access details"
metadata:
node_type: memory
type: project
originSessionId: 541d4004-8c45-4290-89f5-0ba9ee4e64a9
---
Pluto (`PLUTO`, 172.16.3.36) is a Windows Server 2019 VM hosted on Jupiter (Unraid primary).
**Why:** It is the primary Windows build server for GuruRMM — builds all Windows agent variants (amd64, x86, legacy, debug), runs WiX 4 MSI builds, and signs binaries via Azure Trusted Signing.
**Credentials:** Administrator / `Paper123!@#` (set 2026-05-15). SSH key: `guru@gururmm-build` (ed25519, `Q+ivqd/...`) must be in `C:\ProgramData\ssh\administrators_authorized_keys` with icacls `/inheritance:r` and ASCII encoding (not UTF-16).
**How to apply:** When Pluto is unreachable or SSH auth fails, check Jupiter's VM console first (not physical machine). SSH key file must be ASCII-encoded — PowerShell `>` writes UTF-16 and breaks auth silently. Use `[System.IO.File]::WriteAllText(..., [System.Text.Encoding]::ASCII)` to write the key.
**GuruRMM agent:** Installed but historically runs old versions (was on 0.6.3 as of 2026-05-15). Update it after any Pluto maintenance.

View File

@@ -0,0 +1,32 @@
---
name: Gitea Internal API Access
description: git.azcomputerguru.com is NOT behind Cloudflare — it's the office Cox IP NAT'd to NPM (openresty) on Jupiter. Prefer internal 172.16.3.20:3000 for reliability (bypasses NPM SSL-renewal reload blips)
type: reference
originSessionId: 511840e9-1aba-40e6-a81e-4905bac958ec
---
**CORRECTED 2026-05-27** (prior note claimed "behind Cloudflare / curl gets a JS challenge" — that is WRONG/outdated).
`git.azcomputerguru.com` resolves to a **direct public A record `72.194.62.10`** (an ACG-office Cox static IP, adjacent to ix at .5 — `wsip-72-194-62-10.ph.ph.cox.net`). NOT Cloudflare-proxied (same answer from 1.1.1.1; no CF edge IP). Path: `.10` → office firewall NAT → **NPM (Nginx Proxy Manager = openresty) on Jupiter `172.16.3.20`** → Gitea container `:3000`. The NPM proxy host is `/data/nginx/proxy_host/4.conf`. `curl`/HTTPS works fine and returns `200` (Server: openresty) — there is no challenge page.
**Why prefer the internal address for API/git on-network:** the external path goes through NPM, which periodically renews its SSL certs and reloads openresty — that briefly drops external `:443` (observed 2026-05-27: ~7-9 min TCP-timeout window, self-recovered when renewal completed). The internal address bypasses NPM, so it's faster and immune to those renewal blips. It is NOT about Cloudflare.
Use the internal LAN/Tailscale address:
```
http://172.16.3.20:3000/api/v1/...
```
Works when on LAN or when Tailscale is connected. Requires the API token from vault:
```bash
bash D:/vault/scripts/vault.sh get-field services/gitea.sops.yaml credentials.api.api-token
# 9b1da4b79a38ef782268341d25a4b6880572063f
```
Example issue creation:
```bash
TOKEN="9b1da4b79a38ef782268341d25a4b6880572063f"
curl -s -X POST "http://172.16.3.20:3000/api/v1/repos/azcomputerguru/gururmm/issues" \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"title": "...", "body": "..."}'
```