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

17 KiB
Raw Blame History

/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 existSeed 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

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:

# 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.

Convert slug to a Syncro search query:

# 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

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

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)

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

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:

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 migrationsgit 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 itemsdocs/FEATURE_ROADMAP.md checked/done items.
  5. Specsdocs/specs/ (shipped vs proposed).
  6. Commit loggit 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:

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:

## 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:

## 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

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.