Files
claudetools/.claude/commands/wiki-compile.md
Mike Swanson 59b5f1f5f2 wiki: update PST (deletion-report location) + add fast wiki-compile 'update' mode
- peaceful-spirit: record the standing 'Mara audit log' (daily PST Deletion Report task,
  SACL 4660/4663 on G:\Shares\Scanned) and its new output location under the legal/
  partner-review folder (moved 2026-07-02). Surgical update, no full recompile.
- wiki-compile: add an incremental UPDATE mode (now the no-flag default) that folds only
  session logs newer than last_compiled via targeted section edits — no Sonnet subagent,
  no full-article regeneration. --full is now the explicit REBUILD; --syncro is the
  instant Syncro-only refresh. Addresses the slow-rebuild complaint.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 17:27:23 -07:00

26 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>          UPDATE an existing article (fast, incremental) or seed a new one
/wiki-compile client:<slug> --full   REBUILD: full re-synthesis from ALL sources (Sonnet, slow)
/wiki-compile client:<slug> --syncro Syncro dynamic fields only (hours/tickets) — instant, no LLM
/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 synthesis, Sonnet subagent).
  • If it exists and no flag → Update mode (fast, incremental — the default; see below).
  • --fullRebuild (full Sonnet re-synthesis from ALL sources; preserves Patterns/History).
  • --syncroSyncro-only refresh (dynamic fields only; instant, no LLM).

Update vs Rebuild — why update is fast (this is the point). A Rebuild (--full) reads every session log for the client and regenerates the entire article with a Sonnet subagent — correct, but slow and expensive, and wasteful when only one thing changed. Update reads ONLY the session logs dated after the article's last_compiled (usually 13 files, often zero) plus the current article, and applies a few surgical section edits — new History rows, and targeted edits to any section a new log actually changes — leaving the rest of the article byte-for-byte untouched. Small input + small output + no full-article Sonnet pass = typically many times faster. Reach for --full only when the article structure has drifted, sections are stale/wrong, or you want a periodic clean rebuild. For "I just did some work, capture it," use plain update.


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

Synthesis engine: seed/full article drafting is done by a Sonnet subagent (Agent tool, model: "sonnet"), not Ollama. The main agent gathers sources + Syncro data, delegates the draft, then reviews it before writing. No Ollama dependency.


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"          # rebuild — full Sonnet re-synthesis
elif [ "$FULL_FLAG" = "--syncro" ]; then
  MODE="syncro"        # Syncro dynamic fields only
else
  MODE="update"        # default: fast incremental knowledge merge
fi

echo "[INFO] Mode: $MODE | Target: $TARGET_TYPE:$SLUG"

# For update mode, read the article's last_compiled so Phase 3 can select only newer logs.
LAST_COMPILED=""
if [ "$MODE" = "update" ]; then
  LAST_COMPILED=$(sed -n 's/^last_compiled:[[:space:]]*//p' "$CLAUDETOOLS_ROOT/$ARTICLE_PATH" | head -1)
  echo "[INFO] Update since last_compiled=${LAST_COMPILED:-unknown}"
fi

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')
URLQ() { python -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))" "$1"; }

# Use the FUZZY `query=` param, not `name=`. `name=` is near-exact and misses
# singular/plural and word-order mismatches between the slug and the Syncro
# business name (e.g. slug `gonzvar-tax-services` vs Syncro "Gonzvar Tax Service").
CUST_RESULTS=$(curl -s "$BASE/customers?query=$(URLQ "$SEARCH_QUERY")&per_page=5&api_key=$API_KEY")
CUST_COUNT=$(echo "$CUST_RESULTS" | jq '.customers | length')

# Fallback ladder if 0: retry with progressively shorter fuzzy queries
# (first word, then the distinctive surname/token) before declaring "not found".
if [ "$CUST_COUNT" = "0" ]; then
  for Q in "$(echo "$SEARCH_QUERY" | awk '{print $1}')" "$(echo "$SEARCH_QUERY" | awk '{print $1}' | sed 's/s$//')"; do
    [ -z "$Q" ] && continue
    CUST_RESULTS=$(curl -s "$BASE/customers?query=$(URLQ "$Q")&per_page=10&api_key=$API_KEY")
    CUST_COUNT=$(echo "$CUST_RESULTS" | jq '.customers | length')
    [ "$CUST_COUNT" != "0" ] && echo "[SYNCRO] matched on fuzzy fallback '$Q'" && break
  done
fi

If the fuzzy/fallback search returns several, fall through to the 2+ disambiguation below; if still 0, only THEN treat as "not in Syncro". Do not conclude "not found" from the exact name= search alone — that was the Gonzvar miss.

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"

Update mode — narrow to NEW sources only (this is the speedup). In update mode, do NOT read the full source set. Select only the logs the article has not yet incorporated: a source is "new" if its filename date is after LAST_COMPILED, or it is not already listed in the article's frontmatter sources:. Read only those.

if [ "$MODE" = "update" ]; then
  # existing sources already folded into the article
  EXISTING_SRC=$(awk '/^sources:/{f=1;next} /^[^ -]/{f=0} f&&/^[[:space:]]*-/{sub(/^[[:space:]]*-[[:space:]]*/,"");print}' "$CLAUDETOOLS_ROOT/$ARTICLE_PATH")
  NEW_SOURCES=$(echo "$ALL_SOURCES" | while read -r f; do
    [ -z "$f" ] && continue
    # (a) not yet in the article's sources list?
    if ! grep -qxF "$f" <<<"$EXISTING_SRC"; then echo "$f"; continue; fi
    # (b) filename carries a YYYY-MM-DD newer than last_compiled?
    d=$(echo "$f" | grep -oE '[0-9]{4}-[0-9]{2}-[0-9]{2}' | head -1)
    if [ -n "$d" ] && [ -n "$LAST_COMPILED" ] && [ "$d" \> "$LAST_COMPILED" ]; then echo "$f"; fi
  done | sort -u | grep -v '^$')
  NEW_COUNT=$(echo "$NEW_SOURCES" | grep -c '^' || echo 0)
  echo "[INFO] Update: $NEW_COUNT new source(s) since ${LAST_COMPILED:-unknown}"
fi

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

Update Mode (existing article, default) — fast incremental

Fold only what changed since the last compile. Do NOT re-synthesize the whole article and do NOT spawn a Sonnet subagent. Two parts:

Part A — Syncro dynamic fields (the three surgical edits documented under Syncro-only Refresh below): hours remaining, Active Work ticket list, and frontmatter (last_compiled, compiled_by).

Part B — Incremental knowledge merge — run ONLY if NEW_COUNT > 0 (new logs from Phase 3):

  1. Read the full text of the NEW_SOURCES logs and the current article. Do NOT read the full historical log set — that is what makes this fast.
  2. Apply targeted edits for what those new logs actually establish:
    • Always: add one dated row per material change to History Highlights (chronological).
    • Infrastructure — add/adjust a row for any new or removed host, IP, service, or key path.
    • Access — add any new vault path or access route (vault path only, never the secret).
    • Patterns & Known Issues — add a genuinely new recurring issue, or mark an existing one resolved if a new log shows it fixed.
    • Touch only the sections a new log changes; leave every other byte of the article intact.
  3. The delta is small, so the main agent applies these edits directly (Edit tool), or delegates only the prose wording to Ollama Tier-0 / haiku and reviews it. Follow the same Hard Rules (Syncro authoritative for billing; never inline secrets; never invent vault paths).
  4. Append the NEW_SOURCES paths to frontmatter sources: (dedup).

If NEW_COUNT == 0: there is nothing new to fold — Part A (Syncro refresh) is the whole update.

Emit:

[OK] Update complete for wiki/clients/<slug>.md
     - New logs folded: <NEW_COUNT> (since <LAST_COMPILED>)
     - Sections touched: History[, Infrastructure, Access, Patterns] | none (Syncro-only)
     - Syncro: hours <PREPAY_HOURS>, tickets <TICKET_COUNT>

Syncro-only Refresh (--syncro) — instant, no LLM

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 / Rebuild (--full) — Claude Synthesis (Sonnet subagent)

Prepare the synthesis context by reading the most relevant source files. For session 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.

Delegate the draft to a Sonnet subagent via the Agent tool (model: "sonnet"), passing the brief below. The subagent returns the article markdown; the main agent reviews it (billing/IPs/vault-paths accurate, Patterns/History preserved on full recompile) before writing in Phase 5.

Subagent brief:

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
6b. NEVER inline raw secrets (passwords, PSKs, RADIUS/shared secrets, API keys, PFX passwords) into the article, even when a session log exposes them. The wiki references the vault path only — e.g. `sysadmin (password: vault)` or `secret in vault (clients/<slug>/server.sops.yaml)`. Raw secrets live in session logs and the SOPS vault, never in the wiki knowledge layer. (Exception: a value the EXISTING article already discloses may be carried over to match its disclosure level — do not ADD new ones.)
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 the subagent is unavailable, the main agent writes the article directly using the same rules and all collected data.


Phase 5 — Serialize, Stage, Review, Apply (Task 2)

Wiki writes are SERIALIZED + STAGED so two machines never recompile the same article into a conflict, and no synthesis lands in the live article without a review.

5.0 Claim a per-article coord lock (via the coord skill): lock claim claudetools wiki/<type>/<slug> "wiki-compile <slug>" --ttl 1.

  • Capture the returned lock ID — the claim prints [coord] lock id=<uuid> .... Save it (LOCK_ID=<uuid>); the release in Phase 6 takes the lock ID, not the resource path.
  • The TTL auto-evicts a dead session's lock (no permanent stranding).
  • If the lock is already held → emit [SKIP] wiki/<type>/<slug> is being compiled on another machine; try again shortly and exit cleanly.
  • If coord is unreachable → emit [WARN] coord down — proceeding without lock and continue.
  • RELEASE the lock in 5.3 — and on ANY error/abort before then.

5.1 Write the synthesized article to STAGING, not the live tree:

  • Staging path: .claude/wiki_staging/<type>-<slug>.md (mkdir -p .claude/wiki_staging). Write the generated/recompiled article THERE. Do NOT touch wiki/... yet.

5.2 Review the staged diff (NO blind merge):

  • diff -u "<live wiki path>" ".claude/wiki_staging/<type>-<slug>.md" | head -120 (or (new article) if none). The main agent reviews: Patterns/History preserved on full recompile, IPs/paths/vault-paths accurate, billing Syncro-authoritative, NO structural corruption or duplicated headers. If the diff looks wrong → STOP, fix the staged file or abort (release the lock); do not apply.

5.3 Apply the staged article to the live tree (then index + commit in Phase 6):

  • cp .claude/wiki_staging/<type>-<slug>.md <live wiki path> (seed/full); refresh edits already applied in Phase 4 still go via this staging review.

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 fetch origin && git rebase origin/main   # serialized, but rebase defensively
git push origin main
# Release the per-article lock and clear staging (ALWAYS — even on an earlier abort):
# NOTE: `lock release` takes the LOCK ID (captured at claim in 5.0), NOT the resource path.
$PY "$CLAUDETOOLS_ROOT/.claude/skills/coord/scripts/coord.py" lock release "$LOCK_ID" 2>/dev/null || true
rm -f "$CLAUDETOOLS_ROOT/.claude/wiki_staging/${TYPE}-${SLUG}.md"

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.
  • Syncro-only refresh (--syncro) never touches Patterns or History. It edits dynamic fields only.
  • Update mode may ADD to History (always) and may add/adjust Infrastructure, Access, and Patterns strictly from the NEW logs — it never rewrites or removes existing prose. Wholesale re-synthesis (rewriting existing sections, reconciling contradictions across the full history) is --full only.