Files
claudetools/.claude/commands/wiki-compile.md
Mike Swanson d4eb8358ce wiki: add capability synthesis to wiki-compile; recompile GuruRMM
Skill + template:
- wiki-compile Phase 2P: type-aware authoritative-artifact discovery for
  projects (migrations, API routes, agent modules, roadmap-done, commit log),
  with a stale-submodule guard that reads origin/main when the pinned
  submodule lags. Changelogs treated as incomplete, not authoritative.
- project template: add a Capabilities / Feature Set section.

GuruRMM recompile (from live main artifacts, not session logs):
- Added Capabilities / Feature Set section covering monitoring, remote
  execution (incl. system vs user_session contexts), inventory/discovery,
  update mgmt, policy, alerting/watchdog, backup, tunnel, identity/security.
- Fixed the misleading "runs as LocalSystem" command-fields line (the gap
  that started this) and the stale BUG-001 temperature claim (now shipped).
- Qualified Entra-only SSO; noted safe-rollout is unwired scaffolding.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 18:16:03 -07:00

410 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# /wiki-compile — Compile session logs and Syncro data into wiki articles
Seed new wiki articles or refresh existing ones from session logs, client documents, and live Syncro PSA data.
---
## Usage
```
/wiki-compile client:<slug> Seed or refresh a client wiki article
/wiki-compile client:<slug> --full Force full Ollama recompile of existing article
/wiki-compile project:<slug> Compile a project wiki article (no Syncro)
/wiki-compile system:<slug> Compile a system wiki article (no Syncro)
/wiki-compile all Process all missing + stale articles
```
**Mode auto-detection:**
- If `wiki/clients/<slug>.md` **does not exist****Seed mode** (full Ollama synthesis)
- If `wiki/clients/<slug>.md` **exists** and no `--full` flag → **Refresh mode** (surgical update of dynamic fields only)
- `--full` flag → **Full recompile** (Ollama synthesis, preserves existing Patterns/History)
---
## Phase 0 — Setup
```bash
CLAUDETOOLS_ROOT="D:/claudetools"
VAULT="$CLAUDETOOLS_ROOT/.claude/scripts/vault.sh"
# Syncro auth (read-only operations only — GET requests ONLY in this skill)
BASE="https://computerguru.syncromsp.com/api/v1"
USER_ID=$(jq -r '.user // empty' "$CLAUDETOOLS_ROOT/.claude/identity.json")
case "$USER_ID" in
mike) API_KEY="T259810e5c9917386b-52c2aeea7cdb5ff41c6685a73cebbeb3" ;;
howard) API_KEY="Tde5174a6e9e312d14-02fd5bfe0f0ee40c87d027507c680e18" ;;
*) echo "[WARNING] Unknown user — Syncro enrichment skipped" ; API_KEY="" ;;
esac
# Ollama endpoint
MACHINE=$(jq -r '.machine // empty' "$CLAUDETOOLS_ROOT/.claude/identity.json")
case "$MACHINE" in
DESKTOP-0O8A1RL|GURU-BEAST-ROG) OLLAMA="http://localhost:11434" ;;
*) OLLAMA="http://100.101.122.4:11434" ;;
esac
if ! curl -s -m 3 "$OLLAMA/api/tags" >/dev/null 2>&1; then
echo "[INFO] Ollama unreachable — synthesis will be Claude-direct"
OLLAMA=""
fi
```
---
## Phase 1 — Argument Parsing
Parse the target and flags:
```bash
# Extract type and slug from argument like "client:cascades-tucson" or "client:cascades-tucson --full"
TARGET_RAW="$1" # e.g. "client:cascades-tucson"
FULL_FLAG="${2:-}" # "--full" or empty
TARGET_TYPE="${TARGET_RAW%%:*}" # client | project | system | all
SLUG="${TARGET_RAW#*:}" # cascades-tucson
# Determine article path
case "$TARGET_TYPE" in
client) ARTICLE_PATH="wiki/clients/${SLUG}.md" ;;
project) ARTICLE_PATH="wiki/projects/${SLUG}.md" ;;
system) ARTICLE_PATH="wiki/systems/${SLUG}.md" ;;
all) # handled separately — see "all" mode below
esac
# Mode detection
if [ ! -f "$CLAUDETOOLS_ROOT/$ARTICLE_PATH" ]; then
MODE="seed"
elif [ "$FULL_FLAG" = "--full" ]; then
MODE="full"
else
MODE="refresh"
fi
echo "[INFO] Mode: $MODE | Target: $TARGET_TYPE:$SLUG"
```
---
## Phase 2 — Syncro Enrichment (clients only, skip for project/system)
**Skip this phase entirely if `TARGET_TYPE != client` or `API_KEY` is empty.**
### 2a — Customer Search
Convert slug to a Syncro search query:
```bash
# Replace hyphens with spaces for the search query
SEARCH_QUERY=$(echo "$SLUG" | sed 's/-/ /g')
CUST_RESULTS=$(curl -s "$BASE/customers?name=$(python3 -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))" "$SEARCH_QUERY")&per_page=5&api_key=$API_KEY")
CUST_COUNT=$(echo "$CUST_RESULTS" | jq '.customers | length')
```
**If 0 results:**
```
[SYNCRO] No customer found matching '${SEARCH_QUERY}' — skipping Syncro enrichment.
Proceeding with session logs only.
```
Set `SYNCRO_DATA=""` and continue to Phase 3.
**If 2+ results:** Show the list and PAUSE execution:
```
[SYNCRO] Multiple customers match '${SEARCH_QUERY}':
1. <id> <business_name> (<city, state>)
2. <id> <business_name> (<city, state>)
...
Which customer should be used for this wiki article? (Enter number, or 'skip' to skip Syncro)
```
Wait for user input before continuing. If user says `skip`, treat as 0 results.
**If exactly 1 result:** Proceed immediately.
### 2b — Customer Profile Pull
```bash
CUST_ID=$(echo "$CUST_RESULTS" | jq -r '.customers[0].id')
# Full customer record
CUST=$(curl -s "$BASE/customers/${CUST_ID}?api_key=$API_KEY" | jq '.customer')
DISPLAY_NAME=$(echo "$CUST" | jq -r '.business_name // .firstname + " " + .lastname')
PREPAY_HOURS=$(echo "$CUST" | jq -r '.prepay_hours // "0"')
NOTES=$(echo "$CUST" | jq -r '.notes // ""')
# Contacts: name, title, email, phone
CONTACTS=$(echo "$CUST" | jq -r '
.contacts[]? |
"\(.firstname) \(.lastname)" +
(if .title != "" and .title != null then " (\(.title))" else "" end) +
(if .email != "" and .email != null then " — \(.email)" else "" end) +
(if .mobile != "" and .mobile != null then ", \(.mobile)" elif .phone != "" and .phone != null then ", \(.phone)" else "" end)
')
```
### 2c — Open Tickets
```bash
OPEN_TICKETS=$(curl -s "$BASE/tickets?customer_id=${CUST_ID}&status=New,In+Progress,Scheduled,Waiting+on+Customer&per_page=10&api_key=$API_KEY" | jq '.tickets[]? | {id, number, subject, status, created_at, user_id}')
TICKET_COUNT=$(echo "$OPEN_TICKETS" | jq -s 'length')
```
### 2d — Recent Invoices (last 12)
```bash
RECENT_INVOICES=$(curl -s "$BASE/invoices?customer_id=${CUST_ID}&per_page=12&api_key=$API_KEY" | jq '[.invoices[]? | {id, number, date, total, status}]')
```
Used only to infer billing pattern (break-fix vs prepaid, rate hints). Do not expose raw invoice data in the wiki.
### 2e — Asset Count
```bash
ASSET_COUNT=$(curl -s "$BASE/customer_assets?customer_id=${CUST_ID}&per_page=200&api_key=$API_KEY" | jq '[.assets[]?] | length')
```
Only the count is used — individual asset details go in session logs and client docs, not the wiki.
---
## Phase 2P — Authoritative Artifact Discovery (projects only)
**Applies when `TARGET_TYPE == project`.** Skip for clients/systems.
Session logs narrate *work done and why* — they are a structurally incomplete record of *what a product can do*. For a code-bearing project, the authoritative capability record is the **code, migrations, API routes, and roadmap**, not the logs. Compiling a project from logs alone WILL miss shipped features (this is exactly how the GuruRMM `user_session` command context was missed). So for projects, dig into the artifacts directly.
### 2P-a — Locate the repo and guard against a stale submodule
Many projects are tracked as a **pinned git submodule** whose commit deliberately lags the live repo. Reading the working tree alone gives stale artifacts. Always check:
```bash
REPO="$CLAUDETOOLS_ROOT/projects/<path-to-submodule-or-repo>"
cd "$REPO"
git fetch origin main 2>/dev/null
PINNED=$(git rev-parse --short HEAD)
LIVE=$(git rev-parse --short origin/main)
echo "pinned=$PINNED live=$LIVE"
# If they differ, read artifacts from origin/main, NOT the working tree.
REF="origin/main" # use this ref for all artifact reads below; falls back to HEAD if no remote
```
Read live artifacts without disturbing the pinned pointer, either via `git show $REF:<path>` / `git ls-tree -r --name-only $REF -- <dir>`, or by creating a throwaway worktree: `git worktree add /tmp/<proj>-live $REF` (remove with `git worktree remove` when done).
### 2P-b — Gather authoritative artifacts (priority order)
1. **DB migrations**`git ls-tree -r --name-only $REF -- <migrations-dir>`. Each migration is a feature/schema checkpoint; the filenames alone are a capability timeline (e.g. `041_add_command_context`). This is the most reliable signal and is usually current even when changelogs are not.
2. **API routes** — the real server surface. Read the route-registration file(s) (e.g. `server/src/api/mod.rs`) and the per-resource handler modules. Enumerate endpoints + notable request options (auth modes, contexts, scopes).
3. **Agent / client capabilities** — module tree of the agent/client (e.g. `agent/src/`): metrics, checks, command execution, updater, tunnel, watchdog, inventory, registry ops. Note **per-platform coverage**.
4. **Completed roadmap items**`docs/FEATURE_ROADMAP.md` checked/done items.
5. **Specs**`docs/specs/` (shipped vs proposed).
6. **Commit log**`git log --oneline $REF` filtered for `feat`/`perf` since the article's `last_compiled`. Fuller and more current than changelogs.
7. **Changelogs** — read if present, but treat as **incomplete** (they are frequently stale — verified on GuruRMM: committed changelogs stopped at v0.6.22 while the fleet ran 0.6.39+). Never rely on them as the sole capability source.
8. **README / DESIGN / ARCHITECTURE docs** — for framing and locked decisions.
### 2P-c — Synthesize the Capabilities / Feature Set section
From the artifacts above, produce the **Capabilities / Feature Set** section (see project template). Organize by surface (monitoring, remote execution, management, integrations, security). Explicitly capture **execution modes and important options** — e.g. command contexts (`system` vs `user_session`), auth modes, policy scopes, platform coverage. Cross-check the existing article (full recompile) and **correct any capability statement that is now incomplete or wrong** (e.g. "runs as LocalSystem" without the user-session context).
For large repos, delegate the artifact read + synthesis to an agent (general-purpose) pointed at the live ref/worktree, and integrate the returned section after review — don't flood the main context with the full code/migration dump.
---
## Phase 3 — Session Log Discovery
Find all session logs that mention this client:
```bash
cd "$CLAUDETOOLS_ROOT"
# 1. Client-specific session logs (all)
CLIENT_LOGS=$(find "clients/${SLUG}/session-logs/" -name "*.md" 2>/dev/null | sort)
# 2. Client-specific docs (README, CONTEXT.md, overview.md, etc.)
CLIENT_DOCS=$(find "clients/${SLUG}/" -name "*.md" -not -path "*/session-logs/*" 2>/dev/null | sort)
# 3. Root session logs mentioning this client (case-insensitive grep on slug and display name)
ROOT_LOGS=$(grep -ril "$SLUG\|$(echo "$DISPLAY_NAME" | sed 's/ /\\|/g')" session-logs/*.md 2>/dev/null | sort)
# 4. Memory files referencing this client
MEMORY_FILES=$(grep -ril "$SLUG" .claude/memory/*.md 2>/dev/null | sort)
# Deduplicate and collect all source paths
ALL_SOURCES=$(echo "$CLIENT_LOGS $CLIENT_DOCS $ROOT_LOGS $MEMORY_FILES" | tr ' ' '\n' | sort -u | grep -v '^$')
SOURCE_COUNT=$(echo "$ALL_SOURCES" | grep -c '^' || echo 0)
echo "[INFO] Found $SOURCE_COUNT source files"
```
If `SOURCE_COUNT == 0` and no Syncro data: warn and stop.
```
[ERROR] No session logs and no Syncro data found for '${SLUG}'. Cannot compile.
Create at least one session log in clients/${SLUG}/session-logs/ first.
```
---
## Phase 4 — Article Generation
### Refresh Mode (existing article, no --full)
Perform surgical updates only. No Ollama call. Three edits:
**Edit 1 — Update hours remaining in Profile section:**
Find the `Hours remaining` line and replace with live Syncro value and today's date. Only run if `PREPAY_HOURS` is non-null and non-zero OR if the article currently shows a non-zero balance.
```
- **Hours remaining (if prepaid):** ${PREPAY_HOURS} hrs as of $(date +%Y-%m-%d)
```
**Edit 2 — Update Active Work ticket list:**
Replace the content of `## Active Work` with the Syncro open tickets formatted as:
```markdown
## Active Work
*As of $(date +%Y-%m-%d) — Syncro shows ${TICKET_COUNT} open ticket(s):*
| Ticket | Subject | Status | Opened |
|---|---|---|---|
| #<number> (ID: <id>) | <subject> | <status> | <created_at date> |
```
If `TICKET_COUNT == 0`:
```markdown
## Active Work
*No open tickets in Syncro as of $(date +%Y-%m-%d). See session logs for recent work.*
```
**Edit 3 — Update frontmatter:**
- `last_compiled`: today's date
- `compiled_by`: `<MACHINE>/claude-main`
- Append new source files to `sources:` list (deduplicate)
After edits, emit:
```
[OK] Refresh complete for wiki/clients/<slug>.md
- Hours: updated to ${PREPAY_HOURS} hrs
- Active tickets: ${TICKET_COUNT} open
- Sources: ${SOURCE_COUNT} files tracked
```
### Seed Mode / Full Recompile — Ollama Synthesis
Prepare the synthesis context by reading the most relevant source files. For sessions logs, read the full content of client-specific logs and the first 200 lines of root session logs (to avoid overwhelming the prompt). For full recompile, also read the existing article.
**Ollama prompt:**
```
You are compiling a wiki article for an MSP (managed service provider) client.
Produce a structured Markdown article using the template and source data provided.
Be concise, factual, and technical. No filler phrases. No emojis. Past tense for history.
Mark unknown fields as (verify).
---
CLIENT SLUG: <slug>
DISPLAY NAME: <display_name>
COMPILE MODE: <seed|full>
SYNCRO LIVE DATA:
Customer ID: <CUST_ID>
Prepaid Hours: <PREPAY_HOURS>
Asset Count: <ASSET_COUNT>
Open Tickets (<TICKET_COUNT>):
<formatted ticket list>
Contacts:
<formatted contact list>
Billing pattern from invoices: <inferred: prepaid block | break-fix | project-based>
SESSION LOG EXCERPTS:
<content of client session logs and relevant root log sections>
<if MODE=full:>
EXISTING ARTICLE (preserve Patterns and History, update everything else):
<existing article content>
</if>
---
TEMPLATE STRUCTURE TO FOLLOW:
<paste of wiki/_templates/client.md>
---
RULES:
1. BILLING — Syncro is the authoritative source for ALL billing-related fields. Never use session log values for these:
- Hours remaining: use live `prepay_hours` from Syncro customer record
- Contract type: if `prepay_hours > 0` → "Prepaid hour block"; if recent invoices show per-ticket billing → "Break-fix"; if large flat invoices → "Project"
- Billing rate: use the `price_retail` from the most recent non-zero labor line item in recent invoices; if no invoices yet, write "(verify — check Syncro invoices)"
- Customer ID: use Syncro `id` exactly as returned
- Managed device count: use Syncro asset count
2. Infrastructure: derive from session logs; keep Syncro asset count in Profile, not in infrastructure tables
3. Patterns & Known Issues: synthesize from session logs; for full recompile preserve existing patterns verbatim unless session logs show they are resolved
4. Active Work: use Syncro open ticket list as the primary source
5. History Highlights: chronological, from session logs only, one-line entries with dates
6. Access: vault paths and IPs from session logs; never invent vault paths
7. For fields with no source data: write "(verify)" not placeholder text
8. Backlinks: list any wiki article slugs (clients/projects/systems) that this client is cross-referenced with
```
If Ollama is unreachable: Claude writes the article directly using the same rules and all collected data.
---
## Phase 5 — Write Article + Update Index
**Write the article:**
- Seed: write `wiki/clients/<slug>.md` from generated content
- Full: overwrite `wiki/clients/<slug>.md`
- Refresh: edits already applied in Phase 4
**Update `wiki/index.md`:**
- Check if `wiki/clients/<slug>.md` is listed in the Clients table
- If **not listed**: insert a new row in the Clients table:
```
| [<display_name>](clients/<slug>.md) | <one-line summary from article intro> | <today's date> |
```
- If **listed**: update the `Last Compiled` date and summary
- Update the `Last updated` header date
---
## Phase 6 — Commit
```bash
cd "$CLAUDETOOLS_ROOT"
git add "wiki/clients/${SLUG}.md" wiki/index.md
git commit -m "wiki: compile ${SLUG} (${MODE})"
git push origin main
```
Emit:
```
[SUCCESS] wiki/clients/<slug>.md committed and pushed
Mode: <seed|refresh|full>
Sources: <N> session logs + Syncro (customer <CUST_ID>)
Article: wiki/clients/<slug>.md
Index: wiki/index.md updated
```
---
## "all" Mode
When invoked as `/wiki-compile all`:
1. Run the same checks as `/wiki-lint` Steps 12 to find missing and stale articles
2. For each missing article: run seed mode
3. For stale articles (compiled > 90 days ago with newer logs): run refresh mode
4. For each: pause after Syncro ambiguity check if needed — do not bulk-skip
5. After all articles processed: single commit with message `wiki: bulk compile (N articles)`
---
## Hard Rules
- **This skill is read-only against Syncro.** No POST, PUT, PATCH, or DELETE. GET requests only.
- **Syncro is authoritative for all billing fields.** Hours remaining, billing rate, contract type, customer ID, and asset count always come from Syncro live data — never from session logs. Session logs may be stale; Syncro is not.
- **Never invent vault paths.** If a credential is not mentioned in session logs, write "(verify)" in the Access section.
- **Never populate Infrastructure tables with placeholder rows.** Only include servers/services that appear in session logs or Syncro assets.
- **Syncro contacts are ground truth for the Profile section.** Do not override with session log guesses if the contact name differs.
- **Refresh mode never touches Patterns or History.** Those sections require human review or `--full`.