shraga 0.0.3 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/README.md +82 -27
  2. package/defaults/agents/summarizer.md +16 -0
  3. package/defaults/agents/trace-extractor.md +84 -0
  4. package/defaults/bin/claude +45 -0
  5. package/defaults/bin/claude-revive +17 -0
  6. package/defaults/extensions/README.md +70 -0
  7. package/defaults/extensions/selftest.ext.ts +43 -0
  8. package/defaults/extensions/stripe-webhook.ext.ts +58 -0
  9. package/defaults/gmail-triage-prompt.md +42 -0
  10. package/defaults/scripts/README +4 -0
  11. package/defaults/scripts/agent-once.ts +67 -0
  12. package/defaults/scripts/backfill-slack-usernames.ts +82 -0
  13. package/defaults/scripts/notifier-throttle.ts +44 -0
  14. package/defaults/scripts/summarize-conversations.ts +5 -0
  15. package/defaults/shraga.config.ts +29 -0
  16. package/defaults/skills/add-skill.md +14 -0
  17. package/defaults/skills/artifacts.md +116 -0
  18. package/defaults/skills/code-review.md +26 -0
  19. package/defaults/skills/communications.md +54 -0
  20. package/defaults/skills/context-audit.md +87 -0
  21. package/defaults/skills/debug.md +10 -0
  22. package/defaults/skills/garden.md +179 -0
  23. package/defaults/skills/github-contributor.md +35 -0
  24. package/defaults/skills/identity.md +30 -0
  25. package/defaults/skills/mcp-server.md +62 -0
  26. package/defaults/skills/mcps-sync.md +105 -0
  27. package/defaults/skills/plan.md +9 -0
  28. package/defaults/skills/platform.md +177 -0
  29. package/defaults/skills/reconcile.md +239 -0
  30. package/defaults/skills/scheduler.md +192 -0
  31. package/defaults/skills/self-aware.md +136 -0
  32. package/defaults/skills/shraga-know.md +333 -0
  33. package/defaults/skills/stripe.md +55 -0
  34. package/defaults/skills/write-tests.md +10 -0
  35. package/defaults/skills-defaults.json +1 -0
  36. package/defaults/system-prompt.md +46 -0
  37. package/defaults/workspace/context.md +28 -0
  38. package/defaults/workspace.md +50 -0
  39. package/defaults/zdotdir/.gitignore +8 -0
  40. package/defaults/zdotdir/.zlogin +3 -0
  41. package/defaults/zdotdir/.zprofile +1 -0
  42. package/defaults/zdotdir/.zshenv +4 -0
  43. package/defaults/zdotdir/.zshrc +3 -0
  44. package/dist/client/assets/index-BoHttkMt.js +1940 -0
  45. package/dist/client/assets/index-DdibEb2O.css +10 -0
  46. package/dist/client/index.html +22 -0
  47. package/package.json +59 -14
  48. package/src/cli.ts +71 -46
  49. package/src/client/App.tsx +510 -0
  50. package/src/client/components/ArtifactCard.tsx +26 -0
  51. package/src/client/components/ArtifactPanel.tsx +138 -0
  52. package/src/client/components/AuthedImage.tsx +85 -0
  53. package/src/client/components/AutocompleteTextarea.tsx +149 -0
  54. package/src/client/components/ChatView.tsx +866 -0
  55. package/src/client/components/CliAuthConsent.tsx +98 -0
  56. package/src/client/components/ConfigPanel.tsx +328 -0
  57. package/src/client/components/ConversationHeader.tsx +156 -0
  58. package/src/client/components/ConversationPane.tsx +277 -0
  59. package/src/client/components/LoginPage.tsx +81 -0
  60. package/src/client/components/MachineStats.tsx +77 -0
  61. package/src/client/components/McpManager.tsx +209 -0
  62. package/src/client/components/MessageInput.tsx +263 -0
  63. package/src/client/components/OAuthConsent.tsx +103 -0
  64. package/src/client/components/SchedulesManager.tsx +99 -0
  65. package/src/client/components/Sidebar.tsx +235 -0
  66. package/src/client/components/SkillsManager.tsx +280 -0
  67. package/src/client/components/SmartChart.tsx +167 -0
  68. package/src/client/components/Toast.tsx +54 -0
  69. package/src/client/components/WorkspaceTree.tsx +313 -0
  70. package/src/client/components/ZoomableImage.tsx +123 -0
  71. package/src/client/components/artifact-presets.ts +10 -0
  72. package/src/client/components/schedules/ScheduleEditor.tsx +264 -0
  73. package/src/client/components/schedules/ScheduleList.tsx +271 -0
  74. package/src/client/components/ui/accordion.tsx +50 -0
  75. package/src/client/components/ui/button.tsx +43 -0
  76. package/src/client/components/ui/dialog.tsx +82 -0
  77. package/src/client/components/ui/input.tsx +19 -0
  78. package/src/client/components/ui/scroll-area.tsx +39 -0
  79. package/src/client/components/ui/textarea.tsx +18 -0
  80. package/src/client/globals.css +51 -0
  81. package/src/client/hooks/useAgentSocket.ts +79 -0
  82. package/src/client/hooks/useArtifacts.ts +89 -0
  83. package/src/client/hooks/useAuth.ts +127 -0
  84. package/src/client/hooks/useConversation.ts +412 -0
  85. package/src/client/hooks/useDarkMode.ts +57 -0
  86. package/src/client/hooks/useIsMobile.ts +23 -0
  87. package/src/client/hooks/usePush.ts +127 -0
  88. package/src/client/hooks/useSchedules.ts +73 -0
  89. package/src/client/hooks/useUnread.ts +238 -0
  90. package/src/client/lib/desktopAttention.ts +75 -0
  91. package/src/client/lib/firebase.ts +32 -0
  92. package/src/client/lib/googleAuthNative.ts +94 -0
  93. package/src/client/lib/native.ts +43 -0
  94. package/src/client/lib/schedule-types.ts +34 -0
  95. package/src/client/lib/sessionApi.ts +58 -0
  96. package/src/client/lib/slots.tsx +79 -0
  97. package/src/client/lib/storage.ts +39 -0
  98. package/src/client/lib/utils.ts +26 -0
  99. package/src/client/lib/workspaceContext.tsx +54 -0
  100. package/src/client/lib/ws.ts +203 -0
  101. package/src/client/main.tsx +14 -0
  102. package/src/mcp-stdio-bridge.ts +70 -0
  103. package/src/scripts/summarize-conversations.ts +5 -0
  104. package/src/scripts/typecheck.ts +43 -0
  105. package/src/server/agents.ts +54 -0
  106. package/src/server/api-keys.ts +63 -0
  107. package/src/server/artifacts/artifacts.export.ts +85 -0
  108. package/src/server/artifacts/artifacts.handler.ts +93 -0
  109. package/src/server/artifacts/artifacts.routes.ts +43 -0
  110. package/src/server/artifacts/artifacts.service.ts +100 -0
  111. package/src/server/artifacts/artifacts.types.ts +31 -0
  112. package/src/server/auth.ts +262 -0
  113. package/src/server/claude.ts +394 -0
  114. package/src/server/commands.ts +21 -0
  115. package/src/server/contacts.ts +177 -0
  116. package/src/server/conversation-summarizer.ts +204 -0
  117. package/src/server/data-sync.ts +664 -0
  118. package/src/server/directives.ts +91 -0
  119. package/src/server/engine/claude-code.ts +514 -0
  120. package/src/server/engine/index.ts +41 -0
  121. package/src/server/engine/registry.ts +21 -0
  122. package/src/server/engine/shared.ts +47 -0
  123. package/src/server/engine/types.ts +48 -0
  124. package/src/server/env-resolve.ts +71 -0
  125. package/src/server/env-sanitize.ts +9 -0
  126. package/src/server/events/bus.ts +29 -0
  127. package/src/server/events/dispatcher.ts +48 -0
  128. package/src/server/events/routes.ts +19 -0
  129. package/src/server/events/types.ts +9 -0
  130. package/src/server/extensions.ts +101 -0
  131. package/src/server/features.ts +109 -0
  132. package/src/server/file-inject.ts +45 -0
  133. package/src/server/hooks.ts +142 -0
  134. package/src/server/idempotency.ts +25 -0
  135. package/src/server/index.ts +1715 -0
  136. package/src/server/integrity-audit.ts +132 -0
  137. package/src/server/mcp-catalog.ts +70 -0
  138. package/src/server/mcp-oauth.ts +198 -0
  139. package/src/server/mcp-progress.ts +45 -0
  140. package/src/server/mcp-server.ts +456 -0
  141. package/src/server/mcp-sidecar.ts +87 -0
  142. package/src/server/mcp.ts +291 -0
  143. package/src/server/model-aliases.ts +76 -0
  144. package/src/server/paths.ts +24 -0
  145. package/src/server/polls.ts +175 -0
  146. package/src/server/push/apns.ts +113 -0
  147. package/src/server/push/fcm.ts +108 -0
  148. package/src/server/push/push.ts +66 -0
  149. package/src/server/push/store.ts +84 -0
  150. package/src/server/push/triggers.ts +99 -0
  151. package/src/server/scheduler/builtins.ts +157 -0
  152. package/src/server/scheduler/engine.ts +432 -0
  153. package/src/server/scheduler/index.ts +4 -0
  154. package/src/server/scheduler/runner.ts +334 -0
  155. package/src/server/scheduler/storage.ts +98 -0
  156. package/src/server/scheduler/timing.ts +70 -0
  157. package/src/server/scheduler/types.ts +62 -0
  158. package/src/server/sdk-utils.ts +45 -0
  159. package/src/server/seed.ts +174 -0
  160. package/src/server/session-bus.ts +18 -0
  161. package/src/server/sessions.ts +559 -0
  162. package/src/server/shraga-config.ts +167 -0
  163. package/src/server/skills.ts +372 -0
  164. package/src/server/slack/api.ts +37 -0
  165. package/src/server/slack/bot.ts +391 -0
  166. package/src/server/slack/context-cache.ts +42 -0
  167. package/src/server/slack/feature.ts +59 -0
  168. package/src/server/slack/mention-rewrite.ts +59 -0
  169. package/src/server/slack/oauth.ts +102 -0
  170. package/src/server/slack/questions.ts +112 -0
  171. package/src/server/slack/sessions.ts +139 -0
  172. package/src/server/stats.ts +106 -0
  173. package/src/server/summarize.ts +11 -0
  174. package/src/server/turn-context.ts +61 -0
  175. package/src/server/unclaw-config.ts +19 -0
  176. package/src/server/unread.ts +79 -0
  177. package/src/server/user-context.ts +33 -0
  178. package/src/server/vendor-sync.ts +52 -0
  179. package/src/server/voice-provider.ts +74 -0
  180. package/src/server/workspace.ts +249 -0
@@ -0,0 +1,192 @@
1
+ ---
2
+ description: Create/manage scheduled jobs (cron/interval/once) and event-triggered automations via the internal API — incl. the event bus, built-in event sources (schedule.finished), per-trigger throttling, and the failure-notifier builtin.
3
+ triggers:
4
+ - schedule a job
5
+ - cron job
6
+ - recurring task
7
+ - event trigger
8
+ - react to an event
9
+ - event-based job
10
+ - emit an event
11
+ - event bus
12
+ - schedule.finished
13
+ - notify on failure
14
+ - failure notifier
15
+ - throttle
16
+ - how do i schedule
17
+ ---
18
+
19
+ You can manage scheduled jobs and event-triggered automations via the internal REST API. Requests are authenticated as the current user (your uid/email are injected into env automatically).
20
+
21
+ ## Auth Header
22
+
23
+ All requests need: `-H "x-internal-token: $INTERNAL_API_TOKEN"`
24
+
25
+ This authenticates you as the current session user — you'll have the same permissions they have (can only edit/delete your own schedules, system schedules are read-only).
26
+
27
+ Base URL: `http://localhost:$PORT`
28
+
29
+ ## Endpoints
30
+
31
+ ### List all schedules
32
+ ```bash
33
+ curl -s -H "x-internal-token: $INTERNAL_API_TOKEN" http://localhost:$PORT/api/schedules | jq .
34
+ ```
35
+ Returns `{ schedules: [...], runningIds: [...] }`.
36
+
37
+ ### Get one schedule
38
+ ```bash
39
+ curl -s -H "x-internal-token: $INTERNAL_API_TOKEN" http://localhost:$PORT/api/schedules/{id} | jq .
40
+ ```
41
+
42
+ ### Create a schedule
43
+ ```bash
44
+ curl -s -X POST -H "Content-Type: application/json" -H "x-internal-token: $INTERNAL_API_TOKEN" \
45
+ http://localhost:$PORT/api/schedules \
46
+ -d '{
47
+ "name": "My task",
48
+ "enabled": true,
49
+ "trigger": { "kind": "cron", "expr": "0 9 * * 1-5", "tz": "Asia/Jerusalem" },
50
+ "task": { "kind": "prompt", "prompt": "Do the thing" }
51
+ }' | jq .
52
+ ```
53
+
54
+ ### Create an event-triggered schedule
55
+
56
+ Instead of a time, an `event` trigger fires when a matching external event arrives (a webhook, a signal from another system, an internal watcher). The event payload is injected into the run.
57
+
58
+ ```bash
59
+ curl -s -X POST -H "Content-Type: application/json" -H "x-internal-token: $INTERNAL_API_TOKEN" \
60
+ http://localhost:$PORT/api/schedules \
61
+ -d '{
62
+ "name": "Handle paid invoice",
63
+ "enabled": true,
64
+ "trigger": { "kind": "event", "source": "stripe", "match": { "type": "invoice.paid" } },
65
+ "task": { "kind": "prompt", "prompt": "An invoice was paid — thank the customer and log it." }
66
+ }' | jq .
67
+ ```
68
+
69
+ - `source` (required) routes the event. `match` (optional) is an AND-filter of payload **dot-paths** → expected values (case-insensitive, e.g. `{"data.amount": "42"}`). No `match` → fires on any event with that source.
70
+ - The event reaches the agent: for `prompt` tasks it's appended as a framed `Event data` JSON block; for `job` tasks it's passed as the `SHRAGA_EVENT` env var (never into the command string). `bash` tasks run their command as-is (no payload injected) — use a `prompt` or `job` task when the run needs the event data.
71
+ - "Run now" works on event schedules too (fires without event data — handy for testing).
72
+
73
+ ### Create a one-time future schedule
74
+ ```bash
75
+ curl -s -X POST -H "Content-Type: application/json" -H "x-internal-token: $INTERNAL_API_TOKEN" \
76
+ http://localhost:$PORT/api/schedules \
77
+ -d '{
78
+ "name": "Send weekly report",
79
+ "enabled": true,
80
+ "trigger": { "kind": "once", "at": 1748000000000 },
81
+ "task": { "kind": "prompt", "prompt": "Compile and send the weekly report" }
82
+ }' | jq .
83
+ ```
84
+ Compute `at` as epoch milliseconds (e.g. `Date.now() + 3600000` for 1 hour from now). Must be in the future. Once-schedules auto-delete after completing.
85
+
86
+ ### Update a schedule
87
+ ```bash
88
+ curl -s -X PUT -H "Content-Type: application/json" -H "x-internal-token: $INTERNAL_API_TOKEN" \
89
+ http://localhost:$PORT/api/schedules/{id} \
90
+ -d '{ "name": "New name", "trigger": { "kind": "cron", "expr": "0 8 * * *", "tz": "UTC" } }' | jq .
91
+ ```
92
+ Partial update — only include fields to change. System schedules cannot be edited.
93
+
94
+ ### Delete a schedule
95
+ ```bash
96
+ curl -s -X DELETE -H "x-internal-token: $INTERNAL_API_TOKEN" http://localhost:$PORT/api/schedules/{id} | jq .
97
+ ```
98
+ System schedules cannot be deleted.
99
+
100
+ ### Enable/disable a schedule
101
+ ```bash
102
+ curl -s -X POST -H "Content-Type: application/json" -H "x-internal-token: $INTERNAL_API_TOKEN" \
103
+ http://localhost:$PORT/api/schedules/{id}/toggle \
104
+ -d '{ "enabled": true }' | jq .
105
+ ```
106
+
107
+ ### Run a schedule now
108
+ ```bash
109
+ curl -s -X POST -H "Content-Type: application/json" -H "x-internal-token: $INTERNAL_API_TOKEN" \
110
+ http://localhost:$PORT/api/schedules/{id}/run \
111
+ -d '{ "override": "optional override prompt" }' | jq .
112
+ ```
113
+
114
+ ### Cancel a running schedule
115
+ ```bash
116
+ curl -s -X POST -H "x-internal-token: $INTERNAL_API_TOKEN" http://localhost:$PORT/api/schedules/{id}/cancel | jq .
117
+ ```
118
+
119
+ ### Get run history for a schedule
120
+ ```bash
121
+ curl -s -H "x-internal-token: $INTERNAL_API_TOKEN" http://localhost:$PORT/api/schedules/{id}/runs | jq .
122
+ ```
123
+
124
+ ## Schedule Shape
125
+
126
+ ```typescript
127
+ Trigger:
128
+ { kind: "once", at: <epoch_ms> }
129
+ { kind: "interval", everyMs: <ms> } // min 1000
130
+ { kind: "cron", expr: "<cron>", tz: "<IANA_tz>" }
131
+ { kind: "event", source: "<name>", match?: { "<dot.path>": "<value>" } }
132
+
133
+ Task:
134
+ { kind: "prompt", prompt: "<text>" }
135
+ { kind: "bash", command: "<cmd>" }
136
+
137
+ Schedule: { id, name, enabled, trigger, task, scope, createdBy, nextRun?, lastRun?, runCount }
138
+ ```
139
+
140
+ ## Emitting events (to fire event-triggered schedules)
141
+
142
+ Any caller that can present shraga auth can push an event onto the bus:
143
+
144
+ ```bash
145
+ curl -s -X POST -H "Content-Type: application/json" -H "x-internal-token: $INTERNAL_API_TOKEN" \
146
+ -H "X-Event-Id: optional-dedupe-key" \
147
+ http://localhost:$PORT/api/events/stripe \
148
+ -d '{ "type": "invoice.paid", "data": { "amount": 42 } }' | jq .
149
+ ```
150
+
151
+ The `:source` path segment is the event source; the JSON body is the payload. `X-Event-Id` (optional) dedupes retried deliveries for ~5 min.
152
+
153
+ For **vendor webhooks** that can't send shraga auth (Stripe, GitHub, …), add a data-side extension (`data/extensions/<name>.ext.ts`) that verifies the vendor's own signature, then calls `ctx.emitEvent(source, payload, { id })`. See `defaults/extensions/README.md` and the `stripe-webhook.ext.ts` example.
154
+
155
+ ## Built-in event sources
156
+
157
+ The system emits these onto the bus automatically — use them as the `source` of an event trigger to react to the agent's own lifecycle:
158
+
159
+ - **`schedule.finished`** — fired when any time/manual schedule run completes. Payload: `{ scheduleId, name, status, sessionId, error? }`. `status` is `ok` | `error` | `aborted`. Chain automations off it, e.g.:
160
+ ```json
161
+ { "trigger": { "kind": "event", "source": "schedule.finished", "match": { "status": "error" } },
162
+ "task": { "kind": "prompt", "prompt": "A scheduled run failed — investigate and post a summary." } }
163
+ ```
164
+ Runs that were *themselves* event-triggered do NOT emit `schedule.finished` — this prevents feedback loops, so you can't chain `schedule.finished` → event run → `schedule.finished` infinitely.
165
+
166
+ ## Throttling event triggers
167
+
168
+ Event triggers accept an optional `throttle` that suppresses duplicate fires **before** a run is spawned:
169
+
170
+ ```json
171
+ { "trigger": { "kind": "event", "source": "schedule.finished", "match": { "status": "error" },
172
+ "throttle": { "byFields": ["name", "error"], "windowSec": 21600 } } }
173
+ ```
174
+
175
+ The dedup key is built from the named payload fields (dot-paths), string-normalized (lowercased, digits→`#`, whitespace-collapsed) so values differing only by timestamps/ids collapse together. A fire is dropped if an identical key fired within `windowSec`. State lives in `data/state/trigger-throttle.json` and self-prunes. Empty `byFields` throttles on the source alone. For throttling inside a `job`/`bash` task (no trigger), use the `data/scripts/notifier-throttle.ts` helper instead.
176
+
177
+ ## Failure notification (built-in)
178
+
179
+ `builtin-failure-notifier` is a shipped, **disabled-by-default** schedule that reacts to `schedule.finished` / `status:error`, triages the error (credential-expiry / rate-limit / data-issue / generic), and DMs the deployment owner — throttled to one alert per job+error per 6h. To use it:
180
+
181
+ 1. Enable it (toggle the schedule).
182
+ 2. Optionally set `SHRAGA_ALERT_SLACK_EMAIL` (the legacy `UNCLAW_ALERT_SLACK_EMAIL` is still honoured) (else it falls back to the first `data/whitelist.json` entry).
183
+ 3. Optionally edit its `task.prompt` to add deployment specifics (recipients, runbook links, severity rules, base URL) — your edits to a builtin's prompt and `enabled` flag survive upgrades; only `name`/`scope`/`createdBy` reconcile from code.
184
+
185
+ ## Notes
186
+
187
+ - Event triggers never fire on a timer — they have no `nextRun` and wait for the bus. Per-trigger fires serialize (queue cap 5).
188
+ - System schedules (`scope: "system"`) are read-only — you can toggle them but not edit/delete.
189
+ - User schedules are scoped to their creator.
190
+ - Schedules only execute on the instance where `DATA_SYNC_SCHEDULER_ACTIVE=true`.
191
+ - `once` schedules auto-delete after completing — they don't linger in the list.
192
+ - Always list schedules first to show the user what exists before making changes.
@@ -0,0 +1,136 @@
1
+ You ARE shraga — a multi-user Claude Code web UI. You can read, modify, and manage your own source code and other repos.
2
+
3
+ ## Identity
4
+
5
+ - **Project**: `shraga` (npm package name)
6
+ - **GitHub**: `<org>/shraga` (set via `GITHUB_REPO_URL` env)
7
+ - **GitHub identity**: bot account via `GITHUB_TOKEN`
8
+ - **Prod host**: from `DEPLOY_HOST` / `DEPLOY_DOMAIN` env
9
+ - **Prod path**: from `APP_DIR` env (default `/opt/shraga`)
10
+ - **Prod user**: from `DEPLOY_USER` env
11
+ - **Service**: from `APP_NAME` env (systemd, runs `bun run src/server/index.ts`)
12
+ - **Deploy**: operator-provided (the deployment supplies its own release tooling — not shipped here)
13
+
14
+ ## Git workspaces (`data/git/`)
15
+
16
+ The base clone at `data/git/<org>/<repo>/` is a **shared object store** — concurrent sessions reuse it, so editing or committing in it directly leaks one session's work into another's PR (the contamination this fixes). **The one hard rule: never edit or commit in the shared base tree.** The default that buys that for free is a per-session worktree keyed by `$SHRAGA_SESSION_ID` — isolation tracks concurrency, not dogma, so wherever sessions can overlap it's effectively always-on.
17
+
18
+ ### Clone or pull a repo (base object store)
19
+ ```bash
20
+ REPO_DIR="$(pwd)/data/git/<org>/<repo>"
21
+ if [ -d "$REPO_DIR/.git" ]; then
22
+ git -C "$REPO_DIR" fetch --prune
23
+ else
24
+ mkdir -p "$(dirname "$REPO_DIR")"
25
+ git clone "https://x-access-token:$GITHUB_TOKEN@github.com/<org>/<repo>.git" "$REPO_DIR"
26
+ fi
27
+ ```
28
+ Only ever `fetch` into the base — never `checkout`/`commit`/edit there.
29
+
30
+ ### DEFAULT git flow — fetch fresh, worktree, isolate, PR back to base
31
+ The base branch is per-repo and not always `main` — confirm the repo's actual base branch before branching (a deployment may document per-repo conventions in its own skill). The whole flow is four commands:
32
+ ```bash
33
+ BASE_BRANCH=main # confirm the repo's real base branch first — not every repo uses main
34
+ REPO_DIR="$(pwd)/data/git/<org>/<repo>"
35
+ git -C "$REPO_DIR" fetch origin "$BASE_BRANCH" # always branch off the freshest base
36
+ WT="$REPO_DIR/.wt/$SHRAGA_SESSION_ID" # session-keyed worktree under the base (never commit it)
37
+ git -C "$REPO_DIR" worktree add "$WT" -b <feature-branch> "origin/$BASE_BRANCH" \
38
+ || (sleep 1 && git -C "$REPO_DIR" worktree add "$WT" -b <feature-branch> "origin/$BASE_BRANCH") # retry once on ref-lock race
39
+ # ALL reads/edits/commits/push happen inside $WT — never in $REPO_DIR
40
+ git -C "$WT" worktree remove . --force # when done
41
+ ```
42
+ Then read/edit/test/commit/push inside `$WT`, and open the PR via `mcp-github` **targeting the same base branch**.
43
+
44
+ **Continuing an existing branch** (iterate on an open PR, follow-up commits) — check it out into the worktree instead of `-b`: `git -C "$REPO_DIR" fetch origin <branch> && git -C "$REPO_DIR" worktree add "$WT" <branch>`. Same isolation; you're just resuming a branch rather than cutting a new one.
45
+
46
+ Per-session path means concurrent sessions never share a working tree; worktrees share the base object DB so disk cost is near-zero (`git worktree prune` clears any orphan). The retry covers the only shared-state risk: two `worktree add` racing on a ref lock.
47
+
48
+ ### Self-patching (your own code)
49
+ Your own source is `<org>/shraga` in the git workspace (resolve `<org>` from `GITHUB_REPO_URL`). Use the **same isolated worktree flow as above** — never edit the base clone `$REPO_DIR` directly, and never edit `$APP_DIR` (the live instance).
50
+ 1. Fetch `<org>/shraga` base + `worktree add` into `$WT` (the DEFAULT flow above)
51
+ 2. Make changes inside `$WT`, run `bun run build` there to verify
52
+ 3. Push branch + create PR via `mcp-github` (audit trail / human review)
53
+ 4. **Apply to live** — checkpoint, copy, rebuild, restart. `$APP_DIR` is NOT a clone of origin;
54
+ it's a local-only git repo used purely as a rollback layer. You deliver code by copying the
55
+ built tree in (rsync), exactly like the deployment's own release tooling does — not by `git checkout`:
56
+ ```bash
57
+ SRC="$(pwd)/data/git/<org>/shraga/.wt/$SHRAGA_SESSION_ID" # the worktree you built in, NOT the base clone
58
+ bash "$APP_DIR/tools/checkpoint.sh" pre-apply # local rollback point (snapshots .env/.tmp too)
59
+ rsync -a --delete \
60
+ --exclude .git --exclude node_modules --exclude data --exclude vendor \
61
+ --exclude dist --exclude .tmp --exclude .env --exclude '.env.*' \
62
+ --exclude secrets --exclude .gitignore --exclude .github \
63
+ "$SRC/" "$APP_DIR/"
64
+ cd "$APP_DIR" && bun install && bun run build
65
+ sudo systemctl restart "$APP_NAME"
66
+ ```
67
+ The restart will end the current session — deploy-restart-recovery handles reconnection.
68
+ The PR stays open for human review.
69
+
70
+ > **Never hand-restart yourself.** Use ONLY the single service-manager restart above — `sudo systemctl restart "$APP_NAME"`, or on launchd hosts `launchctl kickstart -k gui/$(id -u)/<label>`. NEVER `kill` the server PID and never start a second `bun run` instance from within your turn. SIGTERM triggers a graceful drain that waits up to 90s for active streams to finish — but *your own in-flight turn IS an active stream*, so the drain blocks on itself; combined with manual PID-killing, the old process wedges (socket closed, never rebinds) and the service manager won't auto-recover it (the wrapper still looks "running"). That self-downs the whole instance. If a mid-turn restart is unavoidable, prefer `tools/flip-restart.ts` (zero-downtime self-apply) or hand it to the user. A `tools/health-watchdog.sh` (where deployed) force-bounces a wedged port within ~2 min as a backstop — don't rely on it to mask a bad restart.
71
+
72
+ **Revert if the apply broke something:**
73
+ ```bash
74
+ cd "$APP_DIR"
75
+ git reset --hard HEAD~1 # back to the pre-apply checkpoint (restores src + .env)
76
+ bun install && bun run build && sudo systemctl restart "$APP_NAME"
77
+ ```
78
+ List checkpoints with `git -C "$APP_DIR" log --oneline`; reset to any of them.
79
+
80
+ ## Key paths (in source)
81
+
82
+ - `src/server/` — Express + WebSocket server, Claude agent SDK integration
83
+ - `src/client/` — React 19 + Vite frontend
84
+ - `src/server/claude.ts` — agent SDK session management
85
+ - `src/server/mcp.ts` — MCP server injection and config
86
+ - `src/server/skills.ts` — skill loading and injection
87
+ - `defaults/` — canonical source for runtime-seeded dirs (see Architecture skill § "Defaults → Runtime Seeding Pattern")
88
+ - `vendor/` — vendored MCP servers (mcp-firebase, mcp-github, mcp-slack, etc.)
89
+ - `data/` — runtime data, git-tracked behavioral config via data-sync (skills, mcps, workspace, schedules, contacts, agent-config, whitelist)
90
+
91
+ ## Where one-off / custom code goes (NOT main `src/`)
92
+
93
+ `src/` is the shared, generic shraga codebase — it ships to every deployment and the public repo. Ephemeral or deployment-specific code must NOT land there. Tell-tale signs you're about to pollute `src/`: a hardcoded deployment URL, a route not gated like its siblings, or code that solves a one-time setup need rather than a product capability.
94
+
95
+ - **Runnable tool / one-off / reusable** → a Bun script under `scripts/` (seeded from `defaults/scripts/`). It can import the full server stack (`streamChat`, `getMcpConfig`, vendor MCP clients) — see `scripts/agent-once.ts`. **Reusable across deployments → `defaults/scripts/` (shared source). Specific to this deployment → the data subgit's `scripts/`** (e.g. `data/scripts/`), never `defaults/`. CLI scripts must end with `main().then(() => process.exit(0))`.
96
+ - **Needs a public route on THIS instance's own URL** (an OAuth/manifest callback, a webhook receiver, or a public share/redirect page that must resolve on your deploy domain — a separate `Bun.serve` listener wouldn't be reachable behind the single tunneled port) → a **server extension**: drop a `*.ext.ts` exporting `default register(app, ctx)` into `data/extensions/`. The loader (`src/server/extensions.ts`) mounts it at boot **before** the SPA catch-all, and **hot-loads new files with no restart**. It lives in the data subgit (durable, synced) with zero `src/` edits. `ctx` provides `{ dataPath, requireAuth, log, app, emitEvent }` (`emitEvent` publishes onto the event bus — the way a verified vendor webhook becomes an agent run; see "Automation" below). See `data/extensions/README.md`, the shipped `selftest.ext.ts`, and the `stripe-webhook.ext.ts` event-bridge example. Still prefer a stored non-interactive key (fine-grained PAT, API key) over an OAuth dance whenever one exists.
97
+ > **Two gotchas — each silently returns the SPA:** (1) a route registered after `app.get('*')` is swallowed by the catch-all → HTML; the loader mounts extensions before it, so never add routes to `index.ts` after the catch-all. (2) a browser-navigated / externally-redirected route can't carry an `Authorization: Bearer` header, so `requireAuth` 401s → SPA fallthrough; make those routes public + a one-time `state`/signed nonce, and reserve `requireAuth` for routes the SPA calls via `fetch`.
98
+ - **Other one-off HTTP needs** (a throwaway listener, or a tool surface) → a standalone `Bun.serve` script, or a proper MCP server (`/create-mcp`). Do NOT wire a route directly into `src/server/index.ts`.
99
+ - **Visual artifact** (creative, card, mockup) → an HTML file with an `<!-- artifact: … -->` comment (see the `artifacts` skill).
100
+ - **True throwaway** → `.tmp/artifacts/` (gitignored) on the machine.
101
+
102
+ ## Data Sync (`src/server/data-sync.ts`)
103
+
104
+ Your behavioral config (skills, MCPs, schedules, contacts, workspace, agent-config, whitelist) is git-tracked in a dedicated repo (configured via `DATA_SYNC_REPO` env). This keeps all environments aligned.
105
+
106
+ - **Auto-sync**: On startup, `dataSync.init()` pulls latest from the remote repo
107
+ - **Write tracking**: Any file you modify in `data/` (skills, config, contacts) is auto-committed and pushed within 2s with an LLM-generated descriptive commit message
108
+ - **Conflict resolution**: If remote changed the same file, Claude resolves conflicts holistically via API
109
+ - **Pull on demand**: `POST /api/data-sync/webhook` triggers a pull (GitHub webhook fires on every push)
110
+ - **Change history**: `data/git-log.json` (gitignored, reconstructed) contains recent commit log — read it to understand what changed and when. Also available via `GET /api/data-sync/log`
111
+ - **Recovery**: If data seems stale or missing, trigger a pull by restarting the service or calling the webhook
112
+ - **Integrity audit**: `bun run src/server/integrity-audit.ts [git-ref]` compares current data/ against a baseline commit. Detects missing files, truncated content, degraded JSON (fewer entries in schedules/contacts/skills-defaults/api-keys), and invalid JSON. Runs automatically after every data-sync init — check logs for `[data-sync] ⚠️ DATA INTEGRITY`. Use manually when investigating suspected data loss.
113
+
114
+ ### Scheduler gating
115
+ Schedules sync across all envs but only **execute** where `DATA_SYNC_SCHEDULER_ACTIVE=true` (prod). Inactive envs load and serve schedules with their real `enabled` flags — they just never fire them (no timers armed). Manual "run now" still works anywhere. Event triggers respect the same gate (`fireEvent` no-ops unless active) so a webhook can't double-fire across blue-green.
116
+
117
+ ## Automation: how you get triggered
118
+
119
+ Two layers cause you to run — know both, and which to reach for:
120
+
121
+ **Schedules (`schedules.json`)** — a schedule is `trigger` + `task` (`prompt`/`bash`/`job`). One execution path, two trigger families:
122
+ - **Time**: `cron` / `interval` / `once`.
123
+ - **Event** (`{ kind:'event', source, match? }`) — fires when a matching event hits the event bus. `match` is an AND-filter of payload dot-paths → values. The event is injected into the run: a framed JSON block for `prompt` tasks, the `SHRAGA_EVENT` env var for `job` tasks.
124
+ - Events arrive via `POST /api/events/:source` (auth-gated) or `ctx.emitEvent(source, payload, {id})` from a data extension — the latter is how a **vendor webhook** (verify its signature in the extension first) becomes an agent run. Bus + dispatcher: `src/server/events/`; fire path: `scheduler/engine.ts` `fireEvent()`. Full how-to (create / match / emit): the **scheduler** skill.
125
+ - **Built-in lifecycle source**: the system auto-emits `schedule.finished` (`{ scheduleId, name, status, sessionId, error? }`) when a time/manual run completes — react to your own runs (e.g. `match: { status: "error" }` → notify). Event-triggered runs don't emit it (loop guard). More internal sources can be added with one `emitEvent()` at the milestone.
126
+
127
+ **SDK hooks (`src/server/hooks.ts`)** — Claude Code's own hook mechanism, wired into your `query()`. These are *synchronous, in-turn interceptors* (currently a `PreToolUse` hook that forces long scripts to background) that can deny/modify a tool call before it runs. Different layer from the event bus: a hook guards/modifies a tool call *during your turn*; an event trigger reacts *after something happened*, in a (possibly new) session. Add a hook for a fast in-turn guard; add an event trigger for a cross-session reaction.
128
+
129
+ ## Rules
130
+
131
+ - Always develop in `data/git/`, never edit live source directly — apply via checkpoint + rsync + rebuild (see Self-patching)
132
+ - Prefer creating PRs over pushing directly to `main` — let the human review
133
+ - After editing `defaults/skills/`, the change takes effect on next server restart (seed sync)
134
+ - Keep files under 250 lines; split into modules
135
+ - Use existing patterns — check similar code before inventing new approaches
136
+ - CLI scripts (`data/scripts/*.ts`) must use `main().then(() => process.exit(0)).catch(...)` — Bun's fetch keep-alive pool holds the event loop open indefinitely without explicit exit
@@ -0,0 +1,333 @@
1
+ ---
2
+ description: "Audit shraga agent knowledge health. Default: session-scoped (check what this session touched). Pass 'full' or 'all' for exhaustive audit across all knowledge layers."
3
+ argument-hint: "[full|all|freshness|skills|users|reconcile]"
4
+ ---
5
+
6
+ # Shraga Knowledge Audit
7
+
8
+ Diagnose the health of the shraga agent's runtime knowledge layer.
9
+
10
+ **Scope: agent runtime only.** Audit `data/` (workspace, skills, contacts, config) and `defaults/` (agents, skills, system prompt). NEVER inspect `.claude/`, `CLAUDE.md`, `memory/`, or any dev-level Claude Code config — those belong to the developer, not the agent.
11
+
12
+ ## Modes
13
+
14
+ - **Default (no args / "this session")**: Session-scoped audit. Check only what was touched in the current conversation — edited files, synced skills, new/changed endpoints, docs. Fast and focused.
15
+ - **`full` or `all`**: Exhaustive audit across all knowledge layers (sections 1-8 below).
16
+ - **Named section** (e.g. `freshness`, `skills`, `users`, `reconcile`): Run only that section from the full audit.
17
+
18
+ ## Session-Scoped Audit (default)
19
+
20
+ Review your conversation history to identify files you created, edited, or synced. Then check:
21
+
22
+ ### A. Defaults→Data Sync
23
+
24
+ For every file in `defaults/skills/` or `defaults/agents/` that was edited this session, verify the `data/` copy matches:
25
+
26
+ ```bash
27
+ # For each edited defaults/ file, check its data/ counterpart
28
+ for f in {list of edited defaults/skills/*.md files}; do
29
+ name=$(basename "$f")
30
+ data="data/skills/$name"
31
+ if [ -f "$data" ]; then
32
+ if ! diff -q "$f" "$data" > /dev/null 2>&1; then
33
+ echo "DRIFT: $name"
34
+ fi
35
+ else
36
+ echo "MISSING in data/: $name"
37
+ fi
38
+ done
39
+ ```
40
+
41
+ If drifted, sync immediately: `cp defaults/skills/X.md data/skills/X.md`
42
+
43
+ ### B. Docs Consistency
44
+
45
+ For every behavioral change (new params, changed defaults, new endpoints):
46
+ - Check that `defaults/skills/platform.md` (or relevant skill) documents the change
47
+ - Check that `CLAUDE.md` key endpoints section is still accurate if endpoints changed
48
+ - Check that any skill referencing the changed code is updated
49
+
50
+ ### C. Related Skills Impact
51
+
52
+ If the session changed agent-facing behavior:
53
+ - Grep `defaults/skills/` and `data/skills/` for references to the changed function/endpoint/param
54
+ - Flag any skill that references old behavior
55
+
56
+ ```bash
57
+ # Example: check for references to changed endpoint behavior
58
+ grep -rn "api/chat" defaults/skills/ data/skills/ 2>/dev/null
59
+ ```
60
+
61
+ ### D. Scope Placement
62
+
63
+ For every knowledge file written or edited this session, verify it landed in the right layer. Apply the test from `workspace.md`: *"would this be true/useful for a different user?"*
64
+
65
+ - **User-scope content** (`users/{id}/user-context.md`, `users/{id}/...`) that is actually a universal convention, shared project fact, or team-wide rule → flag: should **lift to team** (`context.md` / `knowledge/`).
66
+ - **Team-scope content** (`context.md`, `knowledge/*.md`) that is about one specific person (their preferences, their personal projects, a correction only they gave) → flag: should **drop to user** (`users/{id}/`).
67
+
68
+ A single lesson can legitimately live in both (e.g. a correction you gave that is also a universal rule). The flag is for content sitting in the *wrong* or *only* layer. When in doubt, surface it rather than silently pass.
69
+
70
+ ### E. Quick Health Checks
71
+
72
+ Run only on files touched this session:
73
+
74
+ ```bash
75
+ # Check edited skills have valid frontmatter
76
+ for f in {list of edited skill files}; do
77
+ head -5 "$f" | grep -q "description:" || echo "MISSING description: $f"
78
+ done
79
+ ```
80
+
81
+ ### Output Format (session)
82
+
83
+ ```
84
+ ## Session Audit — {date}
85
+
86
+ ### Files Changed
87
+ - {list from conversation history}
88
+
89
+ ### Sync Status
90
+ - {file}: {in sync | DRIFTED → fixed | not applicable}
91
+
92
+ ### Docs
93
+ - {endpoint/behavior}: {documented | MISSING docs | STALE docs}
94
+
95
+ ### Scope Placement
96
+ - {file/lesson}: {correct layer | should LIFT to team | should DROP to user}
97
+
98
+ ### Action Items
99
+ 1. {fix}
100
+ ```
101
+
102
+ ---
103
+
104
+ ## Full Audit (pass `full` or `all`)
105
+
106
+ Run each check section below. Report findings as a table per section. Skip sections not relevant to the focus argument.
107
+
108
+ ### 1. Freshness Check
109
+
110
+ Check last-modified dates on critical files. Flag anything older than the threshold.
111
+
112
+ ```bash
113
+ # Core workspace files (threshold: 3 days)
114
+ stat -f "%Sm %N" -t "%Y-%m-%d" data/workspace/context.md data/workspace/open-questions.md data/workspace/tasks/tasks.md 2>/dev/null
115
+
116
+ # Knowledge files (threshold: 7 days)
117
+ find data/workspace/knowledge -name "*.md" -exec stat -f "%Sm %N" -t "%Y-%m-%d" {} \; 2>/dev/null | sort
118
+
119
+ # User contexts (threshold: 7 days)
120
+ find data/workspace/users -name "user-context.md" -exec stat -f "%Sm %N" -t "%Y-%m-%d" {} \; 2>/dev/null | sort
121
+ ```
122
+
123
+ Report: file, last modified, days stale, status (fresh/stale/critical).
124
+
125
+ ### 2. Knowledge Index Completeness
126
+
127
+ Every file in `data/workspace/knowledge/*.md` should be referenced in `data/workspace/context.md`'s knowledge table. Check for:
128
+ - **Unindexed files**: exist in knowledge/ but not mentioned in context.md
129
+ - **Dead references**: mentioned in context.md but file doesn't exist
130
+ - **Status markers**: check if any files still say "DRAFT" or "not yet implemented" in their first 5 lines
131
+
132
+ ```bash
133
+ # List all knowledge files
134
+ ls data/workspace/knowledge/*.md 2>/dev/null | xargs -I{} basename {}
135
+
136
+ # Check context.md references
137
+ grep -o 'knowledge/[a-z0-9_-]*\.md' data/workspace/context.md 2>/dev/null
138
+ ```
139
+
140
+ ### 3. User Context Completeness
141
+
142
+ For each user in `data/contacts.json`, check their context file exists and has substance.
143
+
144
+ ```bash
145
+ # Map contacts to user dirs
146
+ bun -e "
147
+ const c = JSON.parse(require('fs').readFileSync('data/contacts.json','utf-8'));
148
+ const { statSync, existsSync, readFileSync } = require('fs');
149
+ for (const u of c) {
150
+ const p = 'data/workspace/users/' + u.id + '/user-context.md';
151
+ const exists = existsSync(p);
152
+ const lines = exists ? readFileSync(p,'utf-8').split('\n').length : 0;
153
+ const size = exists ? statSync(p).size : 0;
154
+ const mod = exists ? new Date(statSync(p).mtimeMs).toISOString().slice(0,10) : '-';
155
+ console.log([u.name || u.id, exists?'yes':'NO', lines+'L', size+'B', mod].join(' | '));
156
+ }
157
+ "
158
+ ```
159
+
160
+ Flag: missing files, files <10 lines (sparse), files >7 days stale.
161
+
162
+ ### 4. Reconcile Sync Gap
163
+
164
+ Compare the latest reconcile report against source files to find proposed-but-unapplied changes.
165
+
166
+ ```bash
167
+ # Find latest reconcile report
168
+ ls -t data/workspace/reconcile-reports/*.md 2>/dev/null | head -1
169
+ ```
170
+
171
+ Read the latest report. For each proposed change:
172
+ - Check if the target file was modified AFTER the report date
173
+ - If not modified → flag as "pending sync"
174
+ - Count: applied vs pending vs unknown
175
+
176
+ ### 5. Skills Health
177
+
178
+ ```bash
179
+ # All skills with line counts and frontmatter presence
180
+ for f in data/skills/*.md; do
181
+ name=$(basename "$f" .md)
182
+ lines=$(wc -l < "$f")
183
+ has_desc=$(head -10 "$f" | grep -c "description:")
184
+ has_triggers=$(head -20 "$f" | grep -c "triggers")
185
+ echo "$name | ${lines}L | desc:$has_desc | trig:$has_triggers"
186
+ done
187
+ ```
188
+
189
+ Check:
190
+ - **Large skills (>200 lines) without triggers**: should be trigger-loaded, not always-injected
191
+ - **Missing descriptions**: limits discoverability in skill index
192
+ - **Defaults.json sanity**: check for duplicates, missing files
193
+
194
+ ```bash
195
+ # Check defaults for duplicates and missing files
196
+ bun -e "
197
+ const d = JSON.parse(require('fs').readFileSync('data/skills-defaults.json','utf-8'));
198
+ const names = d.map(e => typeof e === 'string' ? e : e.name);
199
+ const dupes = names.filter((n,i) => names.indexOf(n) !== i);
200
+ if (dupes.length) console.log('DUPLICATES:', dupes);
201
+ const { existsSync } = require('fs');
202
+ for (const n of [...new Set(names)]) {
203
+ if (!existsSync('data/skills/' + n + '.md')) console.log('MISSING:', n);
204
+ }
205
+ console.log('Total defaults:', names.length, '| Unique:', new Set(names).size);
206
+ "
207
+ ```
208
+
209
+ ### 6. Defaults→Data Sync
210
+
211
+ Check that canonical `defaults/skills/` and `defaults/agents/` are in sync with their `data/` copies.
212
+
213
+ ```bash
214
+ # Skills: compare defaults that also exist in data/
215
+ for f in defaults/skills/*.md; do
216
+ name=$(basename "$f")
217
+ data="data/skills/$name"
218
+ if [ -f "$data" ]; then
219
+ if ! diff -q "$f" "$data" > /dev/null 2>&1; then
220
+ echo "DRIFT: $name (defaults differs from data)"
221
+ fi
222
+ fi
223
+ done
224
+
225
+ # Agents: verify all defaults/agents/ are loadable
226
+ for f in defaults/agents/*.md; do
227
+ name=$(basename "$f" .md)
228
+ echo "agent: $name"
229
+ done
230
+ ```
231
+
232
+ Flag any drifted skills — usually means `defaults/` was edited but not synced to `data/`.
233
+
234
+ ### 7. Scope Placement Audit
235
+
236
+ Scan knowledge content for layer misplacement. Apply the test: *"would this be true/useful for a different user?"* — yes → team, only-about-this-person → user.
237
+
238
+ - **Team files** (`context.md`, `knowledge/*.md`): scan for person-specific content — one user's preferences, personal projects, or a correction only they gave. Flag → should drop to that user's area.
239
+ - **User files** (`users/{id}/user-context.md`): scan for universal conventions, shared project facts, or team-wide rules masquerading as personal notes. Flag → should lift to team scope.
240
+
241
+ ```bash
242
+ # Heuristic: flag named-person references in team-scope knowledge
243
+ grep -rniE "(my |i prefer|personal project|don't |never )" data/workspace/knowledge/*.md data/workspace/context.md 2>/dev/null | head -20
244
+ ```
245
+
246
+ Treat hits as candidates, not verdicts — judge each by the test above. A lesson may legitimately live in both layers (universal rule + personal correction).
247
+
248
+ ### 8. Hot Context Size Estimate
249
+
250
+ Estimate the total bytes injected into every conversation (hot context).
251
+
252
+ ```bash
253
+ bun -e "
254
+ const { readFileSync, existsSync } = require('fs');
255
+ const defaults = JSON.parse(readFileSync('data/skills-defaults.json','utf-8'));
256
+ let total = 0;
257
+ const items = [];
258
+ for (const entry of defaults) {
259
+ const name = typeof entry === 'string' ? entry : entry.name;
260
+ const capped = typeof entry === 'object' && entry.capped;
261
+ const cap = typeof entry === 'object' && entry.cap || 600;
262
+ const p = 'data/skills/' + name + '.md';
263
+ if (!existsSync(p)) { items.push([name, 'MISSING', 0]); continue; }
264
+ const size = readFileSync(p,'utf-8').length;
265
+ const effective = capped ? Math.min(size, cap) : size;
266
+ total += effective;
267
+ items.push([name, capped ? 'capped@'+cap : 'full', effective]);
268
+ }
269
+ for (const [n,mode,s] of items) console.log(n + ' | ' + mode + ' | ' + s + ' chars');
270
+ console.log('---');
271
+ console.log('Total hot skills: ~' + (total/1024).toFixed(1) + ' KB');
272
+ // Add system prompt estimate
273
+ const sp = existsSync('defaults/system-prompt.md') ? readFileSync('defaults/system-prompt.md','utf-8').length : 0;
274
+ console.log('System prompt: ~' + (sp/1024).toFixed(1) + ' KB');
275
+ console.log('Estimated hot total: ~' + ((total+sp+2000)/1024).toFixed(1) + ' KB (skills+prompt+roster+index)');
276
+ "
277
+ ```
278
+
279
+ Warn if hot total exceeds 15 KB.
280
+
281
+ ### Output Format (full)
282
+
283
+ ```
284
+ ## Knowledge Audit — {date}
285
+
286
+ ### Freshness
287
+ | File | Modified | Days | Status |
288
+ | ... | ... | ... | ... |
289
+
290
+ ### Index Coverage
291
+ - Indexed: {N}/{total} knowledge files
292
+ - Unindexed: {list}
293
+ - Dead refs: {list}
294
+ - Draft files: {list}
295
+
296
+ ### User Contexts
297
+ | User | Exists | Lines | Size | Modified | Status |
298
+ | ... | ... | ... | ... | ... | ... |
299
+
300
+ ### Reconcile Sync
301
+ - Latest report: {date}
302
+ - Proposed: {N} changes
303
+ - Applied: {N} | Pending: {N}
304
+
305
+ ### Skills Health
306
+ - Total: {N} | With description: {N} | With triggers: {N}
307
+ - Large without triggers: {list}
308
+ - Defaults duplicates: {list}
309
+
310
+ ### Defaults→Data Sync
311
+ - Drifted: {list or "all in sync"}
312
+ - Agents: {list}
313
+
314
+ ### Scope Placement
315
+ - Misplaced (team→user): {list or "none"}
316
+ - Misplaced (user→team): {list or "none"}
317
+
318
+ ### Hot Context
319
+ - Total injected: ~{N} KB
320
+ - Status: {OK | WARNING: exceeds 15KB}
321
+
322
+ ### Action Items
323
+ 1. {prioritized fix}
324
+ 2. {prioritized fix}
325
+ ...
326
+ ```
327
+
328
+ ## Pitfalls
329
+
330
+ - `stat` flags differ on macOS vs Linux — the commands above use macOS format (`-f "%Sm"`). On Linux use `stat -c "%y %n"`.
331
+ - Don't read full knowledge files for freshness — `stat` is enough.
332
+ - Reconcile sync detection is heuristic (file mtime vs report date) — not a guaranteed "applied" check.
333
+ - Some skills intentionally have no triggers (manual-only like `/reconcile`). Don't flag those.