Two defects found while running /wiki-compile, both from tooling assuming
a path instead of resolving it.
1. Plaintext Syncro API keys. Mike's and Howard's live PSA keys were
copy-pasted into three command files, a script, and two catalog docs --
despite both already being vaulted at msp-tools/syncro and
msp-tools/syncro-howard. Replaced with reads from the SOPS vault via a
new sourced helper, .claude/scripts/syncro-env.sh. Write paths fail
closed; read-only paths degrade to skipped enrichment rather than a
wrong key. Per-user mapping is unchanged, so Syncro attribution is too.
2. Hardcoded repo root. wiki-compile/wiki-lint/inject-standards and
gen_b64.py hardcoded D:/claudetools; this machine is C:/claudetools.
syncro.md also read ~/.claude/identity.json before the repo copy -- the
same bug that made remediation-tool's consent-audit report a fully
consented tenant as RED. Root now resolves from the script's own
location, with identity.json claudetools_root as the override.
get-identity.sh had a chicken-and-egg bug: it read ${CLAUDETOOLS_ROOT:-.}
but never set it, so every caller had to already know the root. It now
self-resolves and exports CLAUDETOOLS_ROOT + VAULT_ROOT.
Verified: all three command setup blocks authenticate against live Syncro;
get-identity.sh works from any cwd and honors a pre-set root; gps-rmm
autoenroll resolves its key from the vault. security-review: no findings.
NOTE: both keys remain valid in git history. Rotation in the Syncro portal
is the required follow-up -- this commit does not resolve that exposure.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
218 lines
7.5 KiB
Markdown
218 lines
7.5 KiB
Markdown
Health-check the wiki for missing articles, stale content, broken backlinks, and cross-reference gaps.
|
|
|
|
Run this after any session where new session logs were created, or when starting a new session and the wiki may be out of date. Also run before any `/wiki-compile all` pass.
|
|
|
|
---
|
|
|
|
## Step 1 — Missing Articles (Primary Check)
|
|
|
|
Scan for clients and projects that have session logs but no wiki article.
|
|
|
|
```bash
|
|
# List all client slugs that have session-logs but no wiki article
|
|
# Repo root is machine-specific (C:/ on some, D:/ on others) — never hardcode it.
|
|
cd "$(git rev-parse --show-toplevel)"
|
|
for dir in clients/*/session-logs; do
|
|
slug=$(echo "$dir" | sed 's|clients/||;s|/session-logs||')
|
|
wiki="wiki/clients/$slug.md"
|
|
if [ ! -f "$wiki" ]; then
|
|
count=$(ls "$dir"/*.md 2>/dev/null | wc -l)
|
|
echo "MISSING: $wiki ($count session logs)"
|
|
fi
|
|
done
|
|
```
|
|
|
|
```bash
|
|
# List all project slugs that have session-logs but no wiki article
|
|
for dir in projects/*/session-logs; do
|
|
slug=$(echo "$dir" | sed 's|projects/||;s|/session-logs||')
|
|
wiki="wiki/projects/$slug.md"
|
|
if [ ! -f "$wiki" ]; then
|
|
count=$(ls "$dir"/*.md 2>/dev/null | wc -l)
|
|
echo "MISSING: $wiki ($count session logs)"
|
|
fi
|
|
done
|
|
```
|
|
|
|
Report each missing article as:
|
|
```
|
|
[MISSING] wiki/clients/<slug>.md — <N> session logs, oldest: <YYYY-MM-DD>
|
|
```
|
|
|
|
Suggest: `Run /wiki-compile client:<slug> to seed.`
|
|
|
|
---
|
|
|
|
## Step 2 — Stale Articles
|
|
|
|
Check `last_compiled` date in all wiki article frontmatter. Flag any article where:
|
|
- `last_compiled` is more than 90 days ago AND there are session logs newer than `last_compiled`
|
|
- Report as `[STALE]` with days since compile and count of new logs
|
|
|
|
Use `grep -r "last_compiled:" wiki/` to collect dates, then compare against session log mtimes.
|
|
|
|
---
|
|
|
|
## Step 3 — Broken Backlinks
|
|
|
|
Scan all `[[link]]` references in wiki articles. For each `[[slug]]`, check:
|
|
- Does `wiki/clients/<slug>.md` exist? Or `wiki/projects/<slug>.md`? Or `wiki/systems/<slug>.md`?
|
|
- If none match → flag as `[BROKEN_LINK]`
|
|
|
|
```bash
|
|
grep -rh '\[\[' wiki/ | grep -o '\[\[[^]]*\]\]' | sed 's/\[\[//;s/\]\]//' | sort -u
|
|
```
|
|
|
|
For each slug found, normalize before checking:
|
|
1. Strip leading `wiki/` prefix if present → flag as `[BAD_FORMAT]` (seeding agents sometimes write wrong format)
|
|
2. Strip trailing `.md` extension if present → also flag as `[BAD_FORMAT]`
|
|
3. Check existence: `wiki/<slug>.md`, `wiki/clients/<slug>.md`, `wiki/projects/<slug>.md`, `wiki/systems/<slug>.md`, `wiki/patterns/<slug>.md`
|
|
4. If no file found after normalization → flag as `[BROKEN_LINK]`
|
|
|
|
Note: `[[systems/neptune]]` will always show as broken until neptune is seeded — that's expected.
|
|
|
|
---
|
|
|
|
## Step 4 — Index Gaps
|
|
|
|
Read `wiki/index.md`. For every `.md` file in `wiki/clients/`, `wiki/projects/`, `wiki/systems/`:
|
|
- Is it listed in `wiki/index.md`? If not → flag as `[NOT_INDEXED]`
|
|
|
|
Conversely, for every row in `wiki/index.md`:
|
|
- Does the linked file actually exist? If not → flag as `[DEAD_INDEX_ENTRY]`
|
|
|
|
---
|
|
|
|
## Step 5 — Compilation Queue Cleanup
|
|
|
|
Read the `## Compilation Queue` section in `wiki/index.md`. For each entry:
|
|
- Does the corresponding `wiki/<type>/<slug>.md` file now exist? If yes → flag the queue entry as stale, suggest removing it.
|
|
|
|
---
|
|
|
|
## Step 6 — Syncro Live-Check (Client Articles, Auto-Fix)
|
|
|
|
For every client wiki article that contains a `Syncro customer ID` line, pull live billing data from Syncro and auto-fix stale values in place. This step runs silently — no user confirmation needed. Fixes are committed at the end.
|
|
|
|
### Setup
|
|
|
|
```bash
|
|
# Resolves CLAUDETOOLS_ROOT + VAULT_ROOT from identity.json and reads the caller's
|
|
# per-user Syncro key from the SOPS vault. Never hardcode a repo path or an API key.
|
|
source "$(git rev-parse --show-toplevel)/.claude/scripts/syncro-env.sh" || true
|
|
|
|
BASE="$SYNCRO_BASE"
|
|
API_KEY="$SYNCRO_API_KEY"
|
|
|
|
if [ -z "$API_KEY" ]; then
|
|
echo "[SYNCRO] No API key for user '${SYNCRO_USER:-unknown}' — skipping Step 6"
|
|
exit 0
|
|
fi
|
|
```
|
|
|
|
### For Each Client Article
|
|
|
|
```bash
|
|
for ARTICLE in wiki/clients/*.md; do
|
|
SLUG=$(basename "$ARTICLE" .md)
|
|
|
|
# Extract Syncro customer ID — skip if not documented
|
|
CUST_ID=$(grep -oP '(?<=\*\*Syncro customer ID:\*\* )\d+' "$ARTICLE" 2>/dev/null)
|
|
[ -z "$CUST_ID" ] && continue
|
|
|
|
# Pull live customer data
|
|
CUST=$(curl -s "$BASE/customers/${CUST_ID}?api_key=$API_KEY" | jq '.customer')
|
|
[ -z "$CUST" ] && echo "[SYNCRO][WARNING] $SLUG — API returned empty for customer $CUST_ID" && continue
|
|
|
|
LIVE_HOURS=$(echo "$CUST" | jq -r '.prepay_hours // "0"')
|
|
TODAY=$(date +%Y-%m-%d)
|
|
|
|
# --- Fix 1: Hours remaining ---
|
|
# Extract current wiki value (match pattern: "X.X hrs as of")
|
|
WIKI_HOURS=$(grep -oP '[\d.]+ hrs as of' "$ARTICLE" | grep -oP '[\d.]+' | head -1)
|
|
|
|
if [ -n "$WIKI_HOURS" ] && [ "$WIKI_HOURS" != "$LIVE_HOURS" ]; then
|
|
# Replace the Hours remaining line with live value
|
|
sed -i "s/\*\*Hours remaining[^:]*:\*\*[^\n]*/\*\*Hours remaining (if prepaid):\*\* ${LIVE_HOURS} hrs as of ${TODAY} (Syncro live)/" "$ARTICLE"
|
|
echo "[SYNCRO][FIXED] $SLUG — hours: wiki=${WIKI_HOURS} → Syncro=${LIVE_HOURS}"
|
|
SYNCRO_FIXES+=("$SLUG: hours ${WIKI_HOURS} → ${LIVE_HOURS}")
|
|
fi
|
|
|
|
# --- Check 2: Open ticket count (flag only, no auto-fix) ---
|
|
OPEN_COUNT=$(curl -s "$BASE/tickets?customer_id=${CUST_ID}&status=New,In+Progress,Scheduled,Waiting+on+Customer&per_page=1&api_key=$API_KEY" | jq '.meta.total_count // (.tickets | length)')
|
|
# Flag if article was compiled > 7 days ago and open ticket count changed
|
|
COMPILED=$(grep -oP '(?<=last_compiled: )[\d-]+' "$ARTICLE")
|
|
COMPILED_AGE=$(( ( $(date +%s) - $(date -d "$COMPILED" +%s 2>/dev/null || echo 0) ) / 86400 ))
|
|
if [ "$COMPILED_AGE" -gt 7 ] && [ "$OPEN_COUNT" -gt 0 ]; then
|
|
SYNCRO_TICKET_FLAGS+=("[TICKETS_CHECK] $SLUG — $OPEN_COUNT open ticket(s) in Syncro; article compiled ${COMPILED_AGE}d ago")
|
|
fi
|
|
done
|
|
```
|
|
|
|
### Commit Fixes (if any)
|
|
|
|
```bash
|
|
if [ ${#SYNCRO_FIXES[@]} -gt 0 ]; then
|
|
cd "$CLAUDETOOLS_ROOT"
|
|
git add wiki/clients/*.md
|
|
git commit -m "wiki-lint: Syncro live-check auto-fix (${#SYNCRO_FIXES[@]} article(s))"
|
|
git push origin main
|
|
fi
|
|
```
|
|
|
|
### Add to Lint Report
|
|
|
|
Append to the report output:
|
|
|
|
```
|
|
### Syncro Live-Check (N auto-fixed, M flagged)
|
|
[SYNCRO][FIXED] cascades-tucson — hours: 37.5 → 31.0 (Syncro live as of YYYY-MM-DD)
|
|
[TICKETS_CHECK] valleywide — 2 open ticket(s) in Syncro; article compiled 14d ago
|
|
```
|
|
|
|
If API key is missing or Syncro is unreachable, emit `[SYNCRO] Skipped (API unavailable)` and continue.
|
|
|
|
---
|
|
|
|
## Output Format
|
|
|
|
Emit a clean lint report:
|
|
|
|
```
|
|
## Wiki Lint Report — YYYY-MM-DD
|
|
|
|
### Missing Articles (N)
|
|
[MISSING] wiki/clients/evs.md — 1 session log (2026-04-17)
|
|
...
|
|
|
|
### Stale Articles (N)
|
|
[STALE] wiki/clients/cascades-tucson.md — compiled 2026-05-24, 3 new logs since
|
|
...
|
|
|
|
### Broken Backlinks (N)
|
|
[BROKEN_LINK] wiki/clients/kittle.md → [[gururmm]] (no file found)
|
|
...
|
|
|
|
### Index Gaps (N)
|
|
[NOT_INDEXED] wiki/clients/new-client.md — not listed in index.md
|
|
...
|
|
|
|
### Compilation Queue — Stale Entries (N)
|
|
[QUEUE_STALE] client:birthbiologic — wiki/clients/birth-biologic.md exists; remove from queue
|
|
...
|
|
|
|
### Syncro Live-Check (N auto-fixed, M flagged)
|
|
[SYNCRO][FIXED] cascades-tucson — hours: 37.5 → 31.0 (Syncro live)
|
|
[TICKETS_CHECK] valleywide — 2 open ticket(s); article compiled 14d ago
|
|
...
|
|
|
|
### Summary
|
|
- N missing articles → run /wiki-compile for each
|
|
- N stale articles → run /wiki-compile to refresh
|
|
- N broken links → fix manually or after recompile
|
|
- N index gaps → update wiki/index.md
|
|
- N Syncro hours auto-fixed, M ticket flags for review
|
|
```
|
|
|
|
After the report, ask: "Run /wiki-compile for any of the missing articles now?"
|