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>
This commit is contained in:
367
.claude/commands/wiki-compile.md
Normal file
367
.claude/commands/wiki-compile.md
Normal file
@@ -0,0 +1,367 @@
|
|||||||
|
# /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 1–2 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`.
|
||||||
@@ -89,6 +89,86 @@ Read the `## Compilation Queue` section in `wiki/index.md`. For each entry:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 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
|
||||||
|
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 "[SYNCRO] No API key for user '$USER_ID' — skipping Step 6" ; exit 0 ;;
|
||||||
|
esac
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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
|
## Output Format
|
||||||
|
|
||||||
Emit a clean lint report:
|
Emit a clean lint report:
|
||||||
@@ -116,11 +196,17 @@ Emit a clean lint report:
|
|||||||
[QUEUE_STALE] client:birthbiologic — wiki/clients/birth-biologic.md exists; remove from queue
|
[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
|
### Summary
|
||||||
- N missing articles → run /wiki-compile for each
|
- N missing articles → run /wiki-compile for each
|
||||||
- N stale articles → run /wiki-compile to refresh
|
- N stale articles → run /wiki-compile to refresh
|
||||||
- N broken links → fix manually or after recompile
|
- N broken links → fix manually or after recompile
|
||||||
- N index gaps → update wiki/index.md
|
- 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?"
|
After the report, ask: "Run /wiki-compile for any of the missing articles now?"
|
||||||
|
|||||||
Reference in New Issue
Block a user