Files
claudetools/.claude/commands/wiki-compile.md
Mike Swanson d9ab515463 feat: add /wiki-compile skill + Syncro live-check in /wiki-lint
/wiki-compile: new skill that seeds or refreshes wiki client articles
from session logs and live Syncro PSA data.

- Three modes: seed (new article), refresh (surgical update), full (--full flag)
- Syncro enrichment for client targets: customer profile, contacts,
  open tickets, recent invoices, asset count
- Ambiguous customer search: pause and ask user to pick
- Customer not found: graceful warn + continue with session logs only
- Syncro is authoritative for all billing fields (hours, rate, contract type)
- Refresh mode: surgical edits only (hours, active tickets, frontmatter)
- Seed/full: Ollama qwen3:14b synthesis; Claude-direct fallback
- Asset count in Profile only — no asset detail tables in wiki
- Commits and pushes after write

/wiki-lint: add Step 6 — Syncro Live-Check
- Pulls live prepay_hours for every client article with a Syncro customer ID
- Auto-fixes stale hours in place; commits fixes in one batch
- Flags articles with open tickets and stale compiled date for review
- Adds Syncro section to lint report output

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 11:45:44 -07:00

368 lines
13 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 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`.