@aitne-sh/aitne 0.1.4 → 0.1.6

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 (87) hide show
  1. package/README.md +16 -0
  2. package/agent-assets/agent-profiles/_safety.md +29 -0
  3. package/agent-assets/agent-profiles/routine-fetch-window.md +75 -40
  4. package/agent-assets/agent-profiles/wiki-agent.md +19 -0
  5. package/agent-assets/docs/features/messaging/bang-commands.md +161 -0
  6. package/agent-assets/docs/features/messaging/overview.md +3 -0
  7. package/agent-assets/docs/features/wiki/commands.md +222 -0
  8. package/agent-assets/docs/features/wiki/overview.md +145 -0
  9. package/agent-assets/docs/getting-started/03-what-can-this-do.md +18 -0
  10. package/agent-assets/docs/glossary.md +34 -0
  11. package/agent-assets/docs/guides/budget-and-cost-for-wiki.md +123 -0
  12. package/agent-assets/docs/guides/build-your-wiki.md +99 -0
  13. package/agent-assets/docs/guides/explore-with-trace-and-connect.md +169 -0
  14. package/agent-assets/docs/guides/maintain-wiki-health.md +168 -0
  15. package/agent-assets/docs/guides/multiple-wikis-for-multiple-domains.md +192 -0
  16. package/agent-assets/docs/guides/pause-the-agent.md +10 -3
  17. package/agent-assets/docs/guides/use-an-existing-obsidian-vault.md +156 -0
  18. package/agent-assets/docs/reference/cli-commands.md +24 -1
  19. package/agent-assets/docs/troubleshooting/wiki-ingest-full-blocked.md +96 -0
  20. package/agent-assets/docs/troubleshooting/wiki-write-failed.md +82 -0
  21. package/agent-assets/skills/context/SKILL.md +288 -17
  22. package/agent-assets/skills/external-services/SKILL.delegated.claude.md +2 -2
  23. package/agent-assets/skills/external-services/SKILL.delegated.codex.md +3 -3
  24. package/agent-assets/skills/external-services/SKILL.delegated.gemini.md +6 -6
  25. package/agent-assets/skills/external-services/SKILL.md +5 -3
  26. package/agent-assets/skills/external-services/SKILL.native.claude.md +49 -58
  27. package/agent-assets/skills/external-services/SKILL.native.codex.md +50 -58
  28. package/agent-assets/skills/external-services/SKILL.native.gemini.md +53 -56
  29. package/agent-assets/skills/mail/SKILL.md +5 -5
  30. package/agent-assets/skills/mail/SKILL.native.claude.md +57 -65
  31. package/agent-assets/skills/mail/SKILL.native.codex.md +73 -75
  32. package/agent-assets/skills/mail/SKILL.native.gemini.md +80 -75
  33. package/agent-assets/skills/management-task-register/SKILL.md +3 -3
  34. package/agent-assets/skills/notion/SKILL.native.claude.md +78 -82
  35. package/agent-assets/skills/notion/SKILL.native.codex.md +78 -80
  36. package/agent-assets/skills/notion/SKILL.native.gemini.md +91 -90
  37. package/agent-assets/skills/observations/SKILL.md +123 -15
  38. package/agent-assets/skills/roadmap/SKILL.md +31 -4
  39. package/agent-assets/skills/schedule/SKILL.md +44 -3
  40. package/agent-assets/skills/today/SKILL.md +50 -11
  41. package/agent-assets/skills/travel-time/SKILL.md +9 -0
  42. package/agent-assets/skills/wiki/wiki-ask/SKILL.md +32 -0
  43. package/agent-assets/skills/wiki/wiki-compile/SKILL.md +126 -0
  44. package/agent-assets/skills/wiki/wiki-connect/SKILL.md +75 -0
  45. package/agent-assets/skills/wiki/wiki-graduate/SKILL.md +45 -0
  46. package/agent-assets/skills/wiki/wiki-ingest/SKILL.md +182 -0
  47. package/agent-assets/skills/wiki/wiki-lint/SKILL.md +90 -0
  48. package/agent-assets/skills/wiki/wiki-trace/SKILL.md +72 -0
  49. package/agent-assets/skills/wiki/wiki-vault-rules/SKILL.md +145 -0
  50. package/agent-assets/task-flows/_partials/calendar-acquire.google_calendar.md +28 -9
  51. package/agent-assets/task-flows/_partials/calendar-acquire.outlook_calendar.md +26 -9
  52. package/agent-assets/task-flows/_partials/mail-acquire.gmail.md +51 -24
  53. package/agent-assets/task-flows/_partials/mail-acquire.outlook_mail.md +46 -16
  54. package/agent-assets/task-flows/_partials/notion-acquire.notion.md +29 -9
  55. package/agent-assets/task-flows/message.received.dm.md +35 -2
  56. package/agent-assets/task-flows/message.received.dm.native.claude.md +25 -26
  57. package/agent-assets/task-flows/message.received.dm.native.codex.md +30 -24
  58. package/agent-assets/task-flows/message.received.dm.native.gemini.md +36 -36
  59. package/agent-assets/task-flows/message.received.dm_first.md +43 -4
  60. package/agent-assets/task-flows/message.received.dm_first.native.claude.md +20 -20
  61. package/agent-assets/task-flows/message.received.dm_first.native.codex.md +22 -19
  62. package/agent-assets/task-flows/message.received.dm_first.native.gemini.md +28 -24
  63. package/agent-assets/task-flows/routine.fetch_window.md +51 -36
  64. package/agent-assets/task-flows/routine.morning_routine.md +12 -3
  65. package/agent-assets/task-flows/routine.morning_routine_initial.md +22 -1
  66. package/agent-assets/task-flows/routine.roadmap_refresh.md +7 -3
  67. package/agent-assets/task-flows/scheduled.dm.md +477 -0
  68. package/agent-assets/task-flows/setup.initial.md +50 -23
  69. package/agent-assets/task-flows/wiki.ask.md +11 -0
  70. package/agent-assets/task-flows/wiki.compile.md +28 -0
  71. package/agent-assets/task-flows/wiki.connect.md +12 -0
  72. package/agent-assets/task-flows/wiki.ingest_url.md +35 -0
  73. package/agent-assets/task-flows/wiki.lint.md +13 -0
  74. package/agent-assets/task-flows/wiki.trace.md +13 -0
  75. package/agent-assets/wiki-seeds/schemas/output.md +12 -0
  76. package/agent-assets/wiki-seeds/schemas/raw.md +13 -0
  77. package/agent-assets/wiki-seeds/schemas/wiki.md +12 -0
  78. package/agent-assets/wiki-seeds/taxonomy.md +13 -0
  79. package/package.json +10 -6
  80. package/scripts/check-redaction-coverage.mjs +0 -109
  81. package/scripts/commands.md +0 -0
  82. package/scripts/message-discipline-digest.mjs +0 -535
  83. package/scripts/poc/google-connector-inheritance/REPORT.md +0 -197
  84. package/scripts/poc/google-connector-inheritance/claude-sdk-probe.mjs +0 -79
  85. package/scripts/regen-skill-fixtures.mjs +0 -39
  86. package/scripts/remint-roadmap-ids.mjs +0 -257
  87. package/scripts/smoke-obsidian-api.mjs +0 -166
@@ -0,0 +1,32 @@
1
+ ---
2
+ name: wiki-ask
3
+ description: Load for wiki.ask. Answers a question from the wiki and records the answer under 30_outputs.
4
+ allowed-tools:
5
+ - Bash(curl *)
6
+ - Bash(jq *)
7
+ ---
8
+
9
+ # Wiki Ask
10
+
11
+ You run under process key `wiki.ask`.
12
+
13
+ Read `<wiki_command>` for the user's `question`. Search and read relevant `20_wiki/` notes first; read `10_raw/` only when the wiki notes need source verification.
14
+
15
+ Write the answer to:
16
+
17
+ ```
18
+ POST /api/wiki/{{workspace_name}}/files/30_outputs/<YYYY-MM-DD>-<slug>.md
19
+ x-process-key: wiki.ask
20
+ ```
21
+
22
+ The output must include the question, short answer, evidence, source links, and follow-up gaps. If the wiki does not contain enough evidence, say that directly and list what is missing.
23
+
24
+ ### Completion message (mandatory)
25
+
26
+ End the turn with a short final assistant message that the daemon forwards back to the channel the bang command came from. This is what the user actually reads on their phone — make it the answer in plain prose:
27
+
28
+ - Lead with the answer in one short paragraph (3–5 lines max).
29
+ - Cite the wiki pages you drew from inline (`see [[<slug>]]`).
30
+ - End with `Full answer: 30_outputs/<YYYY-MM-DD>-<slug>.md` so the user can dig deeper.
31
+ - On insufficient evidence: `Wiki has no entry for <topic>. Missing: <one-line gap>.` and skip the file pointer.
32
+
@@ -0,0 +1,126 @@
1
+ ---
2
+ name: wiki-compile
3
+ description: Load for wiki.compile. Synthesizes 10_raw notes into 20_wiki notes via the daemon Wiki API and updates the wiki index.
4
+ allowed-tools:
5
+ - Bash(curl *)
6
+ - Bash(jq *)
7
+ ---
8
+
9
+ # Wiki Compile
10
+
11
+ You run under process key `wiki.compile` in workspace `{{workspace_name}}` (vault `{{vault_path}}`). The event payload's `data.mode` is either `"incremental"` (compile only raws touched since the last compile) or `"full"` (compile every raw). Read `<wiki_command>` for the mode and any caller-supplied filters.
12
+
13
+ Your job: read `10_raw/` source notes through the daemon Wiki API and emit synthesized `20_wiki/<slug>.md` articles. Never invent sources, never edit existing raws, never collapse multiple conflicting sources into a single uncited paragraph.
14
+
15
+ ## The success contract (read this first)
16
+
17
+ A run is "successful" only when the daemon's Wiki API has returned `{"ok":true,"path":"20_wiki/<slug>.md"}` (or the corresponding `_index.md` / `log.md` path) to each `curl POST` / `PATCH`. There is no other definition of success — the vault is on disk, you cannot write it any other way.
18
+
19
+ Therefore:
20
+
21
+ - Every wiki note you claim to have created MUST be the `path` field of a `200 {"ok":true,...}` response you observed. Never fabricate a path.
22
+ - After all writes, the final tool call before your completion DM SHOULD be a successful PATCH to `log.md` summarising what was compiled.
23
+ - If any planned write fails (4xx/5xx, missing `ok` field, no body, `PA_API_ERROR` on stderr), classify the overall run as **partial** or **failure** in the completion DM — never as success.
24
+
25
+ ## Allowed endpoints (the entire surface for this skill)
26
+
27
+ | Method | Path | Purpose |
28
+ |---|---|---|
29
+ | `GET` | `/api/wiki/{{workspace_name}}/index` | List every file in the workspace with `path`/`mtime`/`sizeBytes`. Primary enumeration tool. |
30
+ | `GET` | `/api/wiki/{{workspace_name}}/search?q=<query>` | Body-substring search for duplicate detection. |
31
+ | `GET` | `/api/wiki/{{workspace_name}}/files/<path>` | Read raw notes, existing wiki notes, taxonomy, log. |
32
+ | `POST` | `/api/wiki/{{workspace_name}}/files/20_wiki/<slug>.md` | Create a new wiki note. |
33
+ | `PATCH` | `/api/wiki/{{workspace_name}}/files/20_wiki/<slug>.md` | Rewrite an existing wiki note (`mode: "replace"`). |
34
+ | `PATCH` | `/api/wiki/{{workspace_name}}/files/20_wiki/_index.md` | Append a new wiki note's entry (`mode: "append"`). |
35
+ | `PATCH` | `/api/wiki/{{workspace_name}}/files/log.md` | Append the per-run summary (`mode: "append"`). |
36
+
37
+ Every request must include `-H 'x-process-key: wiki.compile'`. The `wiki-vault-rules` skill carries the body-quoting cheat-sheet, the `PA_API_ERROR` shape, and the full set of error codes.
38
+
39
+ ## Procedure
40
+
41
+ 1. **Determine the baseline** (incremental mode only).
42
+ - Read `20_wiki/_index.md` (or `log.md`'s last `wiki.compile` entry) to recover the prior `compiled_at` ISO timestamp.
43
+ - If the workspace has never been compiled, fall back to "compile every raw" (i.e. treat the baseline as the empty string — the `jq` filter then admits every `10_raw/*` row).
44
+ 2. **Enumerate raw notes** to compile.
45
+ - `GET /api/wiki/{{workspace_name}}/index` returns `{ files: [{ path, mtime, sizeBytes }, ...] }`.
46
+ - Filter with `jq` for `path` under `10_raw/` and (incremental) `mtime > <baseline>`. Do NOT walk `{{vault_path}}` from disk; `Bash(find ...)`, `Bash(ls ...)`, etc. are silently denied.
47
+ 3. **Read existing wiki + taxonomy** so synthesis can de-duplicate and use canonical slugs.
48
+ - `GET /api/wiki/{{workspace_name}}/files/20_wiki/_index.md`
49
+ - `GET /api/wiki/{{workspace_name}}/files/90_meta/taxonomy.md`
50
+ 4. **Read each candidate raw note** via `GET /api/wiki/{{workspace_name}}/files/10_raw/<slug>.md`.
51
+ 5. **Synthesize root-level `20_wiki/<slug>.md`** notes:
52
+ - One note per coherent topic — merge multiple raws when they cover the same thing; split a single raw across multiple notes only when the raw genuinely spans unrelated topics.
53
+ - Use `90_meta/taxonomy.md` for canonical slugs.
54
+ - Preserve source URLs and quoted passages verbatim. Mark conflicts and unknowns explicitly.
55
+ - Each note's frontmatter MUST list every source raw under a `sources:` array and the synthesis date under `compiled_at:`.
56
+ - Write via `POST` (new slug) or `PATCH mode: "replace"` (existing slug). Read-before-write any slug you `PATCH`-replace so you preserve material that the new raws didn't cover.
57
+ 6. **Append `20_wiki/_index.md`** with one bullet per newly created or modified wiki note (`PATCH mode: "append"`). Skip notes that were unchanged.
58
+ 7. **Append `log.md`** with one operational line summarising the run: `[<ISO>] wiki.compile (<mode>): compiled <N> notes from <M> raws — added <A>, updated <B>, unchanged <C>`.
59
+
60
+ ## Canonical curl shapes
61
+
62
+ The `wiki-vault-rules` skill has the full reference. The shapes you'll use most:
63
+
64
+ ```bash
65
+ # 1) Enumerate (and incremental-filter) raw notes since the last compile
66
+ curl http://localhost:8321/api/wiki/{{workspace_name}}/index \
67
+ -H 'x-process-key: wiki.compile' \
68
+ | jq -r --arg since '<YYYY-MM-DDTHH:MM:SSZ>' \
69
+ '.files[] | select(.path | startswith("10_raw/")) | select(.mtime > $since) | .path'
70
+
71
+ # 2) Read one file (raw, wiki, taxonomy, index, log — same shape)
72
+ curl http://localhost:8321/api/wiki/{{workspace_name}}/files/<path> \
73
+ -H 'x-process-key: wiki.compile'
74
+
75
+ # 3) Write a wiki note — heredoc for multi-KB bodies (recommended)
76
+ curl http://localhost:8321/api/wiki/{{workspace_name}}/files/20_wiki/<slug>.md \
77
+ -X POST \
78
+ -H 'content-type: application/json' \
79
+ -H 'x-process-key: wiki.compile' \
80
+ -d @- <<'JSON'
81
+ {"content":"---\ntitle: <Title>\nsources:\n - 10_raw/<slug-a>.md\n - 10_raw/<slug-b>.md\ncompiled_at: 2026-05-13\nprocess: wiki.compile\n---\n\n# <Title>\n\n## Summary\n\n...\n\n## Source extracts\n\n...\n"}
82
+ JSON
83
+
84
+ # 4) Append an entry to the index / log
85
+ curl http://localhost:8321/api/wiki/{{workspace_name}}/files/20_wiki/_index.md \
86
+ -X PATCH \
87
+ -H 'content-type: application/json' \
88
+ -H 'x-process-key: wiki.compile' \
89
+ -d '{"mode":"append","content":"- [[<slug>]] — <one-line summary>\n"}'
90
+ ```
91
+
92
+ Inside `<<'JSON'` (single-quoted marker) the body is verbatim — only JSON's own escapes (`\"`, `\\`, `\n`) apply.
93
+
94
+ ## Anti-patterns (silent denials — never claim success after these)
95
+
96
+ - `Write` / `Edit` tools — **stripped from the session allow-list** for every `wiki.*` process key. The SDK denies them silently under `dontAsk` ("Permission to use Write has been denied …"). There is no path-rewrite that makes them work; use the Wiki API via curl. The agent that hit "Write denied → reported failure" is the canonical failure mode this section exists to prevent.
97
+ - `Bash(find ...)`, `Bash(ls ...)`, `Bash(cat ...)`, `Bash(grep ...)`, `Bash(wc ...)`, and every other shell utility — silently denied. Only `Bash(curl *)` and `Bash(jq *)` are on the allow-list. Enumerate via `GET /api/wiki/<ws>/index`, not from disk.
98
+ - `echo '{...}' | curl ...`, `cat <<JSON | curl ... -d @- JSON`, `bash -c "curl ..."`, `( curl ... )`, `var=... curl ...`, `curl ... ; curl ...` — Bash command does not start with `curl`; silently denied. The reverse — `curl ... -d @- <<'JSON' … JSON` on the same line — DOES start with `curl` and IS allowed (the heredoc redirects into curl's stdin).
99
+ - `curl ... -d @/some/path` — `@<filepath>` is blocked by the security hook and the shim. The only acceptable `@…` value is the literal stdin marker `@-`.
100
+ - `curl http://example.com/...` (non-loopback) — only `http://localhost:8321/api/*` is permitted.
101
+ - POST to a non-existent path (`/api/send-message`, `/api/notify-user`, `/api/dm`, etc.) — not daemon routes; calls return 401/404 and DO NOT notify anyone. Your completion DM (final assistant text) is what the daemon forwards.
102
+ - Re-POSTing to `/api/wiki/.../20_wiki/<slug>.md` to update — POST is create-only; the second call returns 409. Use `PATCH mode: "replace"` to overwrite, or pick a different slug.
103
+
104
+ ## Common error codes
105
+
106
+ | Status | Body code | Cause | Recovery |
107
+ |---|---|---|---|
108
+ | 400 | `invalid_body` | JSON body did not parse / `content` not a string | Switch to the heredoc shape if you were inline-escaping a multi-KB body. |
109
+ | 400 | `invalid_path` / `invalid_layer` | Slug shape or layer prefix rejected | Path must be exactly `20_wiki/<slug>.md`. Slug matches `^[a-z0-9][a-z0-9-]*$`. |
110
+ | 403 | `missing_process_key` | Header missing | Add `-H 'x-process-key: wiki.compile'`. |
111
+ | 403 | `wiki_write_denied` | Trying to write outside `20_wiki/` (or the `_index.md` / `log.md` exceptions) under `wiki.compile` | Fix the target path. Use `wiki-graduate` for inbox-to-wiki promotion (same process key, different source layer). |
112
+ | 409 | `append_only` | POST to an existing slug | Use `PATCH mode: "replace"` or pick a different slug. |
113
+ | 413 | — | Body exceeds 512 KB | Split the note or trim verbatim extracts. |
114
+ | 5xx | — | Daemon error | Append a failure line to `log.md`, classify the run as partial, do not retry inside the same turn. |
115
+
116
+ If a Bash call returns with no stdout body and no `PA_API_ERROR` on stderr, your command was silently denied — rewrite it as a flat, single-line curl invocation.
117
+
118
+ ## Completion message (mandatory final assistant text)
119
+
120
+ End the turn with one short line — the daemon forwards it verbatim to the channel the bang command came from. Keep it scannable on mobile:
121
+
122
+ - Success: `Compiled <N> wiki page(s) from <M> raw note(s) — added <A>, updated <B>, unchanged <C>.`
123
+ - Partial: `Compiled with warnings — <one-sentence diagnostic>. See log.md for details.`
124
+ - Failure: `Compile failed — <one-sentence reason>.`
125
+
126
+ Do not paste the file list, narrative, or follow-up suggestions into the completion message — the dashboard timeline + `log.md` carry the detail. Do not call `/api/send-message` or any other "send" endpoint; the final assistant text is the delivery channel.
@@ -0,0 +1,75 @@
1
+ ---
2
+ name: wiki-connect
3
+ description: Load for wiki.connect. Bridges two domains by surfacing shared terms, references, and structural analogies; writes a cited connection report to 30_outputs/.
4
+ allowed-tools:
5
+ - Bash(curl *)
6
+ - Bash(jq *)
7
+ ---
8
+
9
+ # Wiki Connect
10
+
11
+ You run under process key `wiki.connect`.
12
+
13
+ Read `<wiki_command>` for the two topics — fields `topic_a` and `topic_b`. Your job is to find genuine bridges between the two domains using only what the wiki contains. Speculative analogies are out of scope; if there is no real overlap, say so plainly.
14
+
15
+ ## Method
16
+
17
+ 1. Search each topic independently:
18
+ ```
19
+ GET /api/wiki/{{workspace_name}}/search?q=<topic_a>
20
+ GET /api/wiki/{{workspace_name}}/search?q=<topic_b>
21
+ ```
22
+ Read enough matches to characterise each domain.
23
+ 2. Surface four types of bridges:
24
+ - **Shared terminology** — terms that appear in both domains. Use `90_meta/taxonomy.md` to disambiguate true synonyms from accidental string matches.
25
+ - **Common references** — the same source URL, author, or wiki note linked from both domains.
26
+ - **Structural analogies** — patterns of reasoning, tradeoffs, or recurring shapes that show up in both, even when the surface vocabulary differs.
27
+ - **Bridging concept candidates** — a new wiki note that would naturally sit between the two (proposal only — you must not create it from `wiki.connect`).
28
+ 3. Cite every bridge with at least one path from each side (`<wiki_a path>` ↔ `<wiki_b path>`). A bridge that can only cite one side is a candidate, not a bridge — file it under "Bridging concept candidates".
29
+
30
+ ## Output
31
+
32
+ Write exactly one report:
33
+
34
+ ```
35
+ POST /api/wiki/{{workspace_name}}/files/30_outputs/<YYYY-MM-DD>-connect-<slug>.md
36
+ x-process-key: wiki.connect
37
+ ```
38
+
39
+ - `<YYYY-MM-DD>` is today's date.
40
+ - `<slug>` is `<slug-a>--<slug-b>` using the canonical slugs from `90_meta/taxonomy.md` when available, kebab-cased and joined by a double hyphen.
41
+
42
+ Report shape:
43
+
44
+ ```
45
+ # Connect — <topic_a> ↔ <topic_b> (<YYYY-MM-DD>)
46
+
47
+ ## Summary
48
+ - one paragraph: is there real connective tissue, weak overlap, or essentially none?
49
+
50
+ ## Shared terminology
51
+ - `term` — meaning in <topic_a> vs <topic_b>, cited
52
+
53
+ ## Common references
54
+ - `<source>` — used by `<wiki_a path>` and `<wiki_b path>`
55
+
56
+ ## Structural analogies
57
+ - short paragraph each, both sides cited
58
+
59
+ ## Bridging concept candidates
60
+ - proposed wiki slug, one-sentence rationale, suggested anchor notes
61
+ ```
62
+
63
+ If no genuine bridges exist, write the report anyway with empty sections marked `_(none)_` and a `## Summary` that names that clearly — a "no connection" finding is still useful to the owner. Append a one-line `log.md` entry referencing the output filename and both topics.
64
+
65
+ ### Completion message (mandatory)
66
+
67
+ End the turn with one short final assistant message that the daemon forwards back to the channel the bang command came from:
68
+
69
+ - `Connect complete — <topic_a> ↔ <topic_b>: <strong | weak | none>. Report: 30_outputs/<YYYY-MM-DD>-connect-<slug>.md.`
70
+ - `strong` = at least one genuine bridge in any of the four buckets.
71
+ - `weak` = only shared terminology or only candidates, no concrete cross-cited overlap.
72
+ - `none` = the summary said so explicitly.
73
+ - On failure: `Connect failed — <one-sentence reason>.`
74
+
75
+ Do not paste bridge content into the completion message.
@@ -0,0 +1,45 @@
1
+ ---
2
+ name: wiki-graduate
3
+ description: Load alongside wiki-compile. Promotes ready inbox material into a real wiki note with the canonical schema; never invents content beyond the source.
4
+ allowed-tools:
5
+ - Bash(curl *)
6
+ - Bash(jq *)
7
+ ---
8
+
9
+ # Wiki Graduate
10
+
11
+ You are loaded alongside `wiki-compile` so a compile pass can promote a single inbox note or output artifact into a real `20_wiki/<slug>.md` article when the source has matured enough.
12
+
13
+ `00_inbox/` is **read-only** to agents (`wiki-vault-rules` §Layers). You may read inbox material, but the only legal write surface for the graduated article is `20_wiki/` via the Wiki API. Never write back into `00_inbox/`.
14
+
15
+ ## When to apply
16
+
17
+ A source qualifies for graduation only if all of these are true:
18
+
19
+ 1. The user explicitly referenced it as a candidate (e.g. via a graduate task body in `<wiki_command>`), OR the compile pass identified the same idea across ≥3 raw notes that converge on a stable framing.
20
+ 2. The synthesis would not duplicate an existing `20_wiki/<slug>.md`.
21
+ 3. The source contains enough substantive content (>200 words of original facts/argument) to warrant a wiki note — short links and bookmarks do not graduate.
22
+
23
+ If any of these fail, skip the graduation and leave a note in `log.md`: "wiki.graduate skipped: <slug> — <reason>".
24
+
25
+ ## Method
26
+
27
+ 1. Read the source path. Inbox paths look like `00_inbox/<anything>.md`; output paths look like `30_outputs/<YYYY-MM-DD>-<slug>.md`.
28
+ 2. Extract the substantive ideas. Preserve quoted source text verbatim; paraphrase only the user's own commentary.
29
+ 3. Apply the wiki schema (`90_meta/schemas/wiki.md`): summary → key facts → source links → related notes. Include frontmatter that names the source path under `derived_from:` and dates the promotion under `graduated_at:`.
30
+ 4. Link to existing related wiki notes using `[[slug]]` syntax. Use the canonical slug from `90_meta/taxonomy.md` when available.
31
+ 5. Update `20_wiki/_index.md` so the new article is discoverable.
32
+
33
+ ## Write path
34
+
35
+ ```
36
+ POST /api/wiki/{{workspace_name}}/files/20_wiki/<slug>.md
37
+ PATCH /api/wiki/{{workspace_name}}/files/20_wiki/_index.md (mode: "append")
38
+ x-process-key: wiki.compile
39
+ ```
40
+
41
+ Use `wiki.compile` as the process key — graduation is a sub-action of the compile pass, not a separate process. If the slug collides with an existing wiki note, append a year disambiguator (`<slug>-<YYYY>`) only as a last resort; prefer extending the existing note via `wiki-compile` itself.
42
+
43
+ Append a `log.md` entry: "wiki.graduate: promoted `<source path>` → `20_wiki/<slug>.md`".
44
+
45
+ End with a short internal summary only.
@@ -0,0 +1,182 @@
1
+ ---
2
+ name: wiki-ingest
3
+ description: Load for wiki.ingest_url. Captures one URL into 10_raw/<slug>.md by POSTing through the daemon Wiki API. Carries the canonical curl invocation, the path / response contract, and the strict success-verification rule.
4
+ allowed-tools:
5
+ - WebFetch
6
+ - Bash(curl *)
7
+ - Bash(jq *)
8
+ ---
9
+
10
+ # Wiki URL Ingestion
11
+
12
+ You run under process key `wiki.ingest_url` in workspace `{{workspace_name}}` (vault `{{vault_path}}`). Read `<wiki_command>` for the target `url` and optional `batch_id`. Your job: capture the page faithfully into ONE new file via the daemon Wiki API. Source fidelity beats summarization.
13
+
14
+ ## The success contract (read this first)
15
+
16
+ A run is "successful" **only** when the daemon's Wiki API has returned `{"ok":true,"path":"10_raw/<slug>.md"}` to your `curl POST`. There is no other definition of success. The vault is on disk; you cannot write it any other way.
17
+
18
+ Therefore:
19
+
20
+ - The final tool call before your completion DM MUST be a successful `curl ... /api/wiki/{{workspace_name}}/files/10_raw/<slug>.md`.
21
+ - You MUST inspect the curl response body. If you do not see the JSON `{"ok":true,"path":"<the exact path you POSTed>"}`, the write did not happen — emit the FAILURE DM, not the success DM.
22
+ - You MUST NOT fabricate a path, claim success after writing to a local file, claim success after a 4xx/5xx response, or claim success after calling any other endpoint.
23
+
24
+ ## Allowed endpoints (the entire surface for this skill)
25
+
26
+ The only daemon routes this skill writes to are:
27
+
28
+ | Method | Path | Purpose |
29
+ |---|---|---|
30
+ | `POST` | `/api/wiki/{{workspace_name}}/files/10_raw/<slug>.md` | Create the raw note (this is the success path) |
31
+ | `PATCH` | `/api/wiki/{{workspace_name}}/files/log.md` | Append a one-line failure entry on fetch failure |
32
+ | `GET` | `/api/wiki/{{workspace_name}}/files/10_raw/<slug>.md` | Verification read after POST (optional) |
33
+ | `GET` | `/api/wiki/{{workspace_name}}/search?q=<q>` | Optional — check whether the URL was already ingested |
34
+
35
+ Do NOT call any other daemon endpoint. The daemon has **no** `/api/send-message`, `/api/whatsapp/send`, `/api/notify-user`, `/api/dm`, `/api/messages/send`, or anything similar — those names look plausible but do not exist; calls there return 401/404 and your "delivery" never happens. The completion DM you emit as your final assistant text is forwarded by the daemon automatically.
36
+
37
+ ## Procedure
38
+
39
+ 1. **Fetch the source page** (one of these, depending on backend):
40
+ - Claude: `WebFetch <url>`.
41
+ - Codex: `curl <url>` against the real internet (the workspace-write sandbox has outbound network).
42
+ - Gemini: `web_fetch <url>`.
43
+ Preserve facts verbatim; mark uncertainty.
44
+ 2. **Derive a slug**: lowercase kebab-case from the URL host + path tail, matching `^[a-z0-9][a-z0-9-]*$`. Examples: `news-webike-31883`, `nytimes-headline-2026-05-12`. ~60 chars max.
45
+ 3. **Build the raw note body** as Markdown:
46
+ - Frontmatter (`source`, `retrieved`, `title`, `process: wiki.ingest_url`).
47
+ - `# <title>` headline.
48
+ - `## Source extracts` — verbatim or close-paraphrase quotes.
49
+ - `## Open questions` — anything you could not verify from the page.
50
+ 4. **POST the note** via the canonical curl shape (next section). Inspect the response body.
51
+ 5. **Verify**: the response body must be exactly `{"ok":true,"path":"10_raw/<slug>.md"}`. If anything else (4xx/5xx, missing `ok`, wrong path, no body, `PA_API_ERROR …` on stderr), treat it as failure. Follow the error table below or PATCH `log.md` and emit the failure DM.
52
+ 6. **Emit the completion DM** (last section). Success or failure, exactly one line, no extra prose.
53
+
54
+ ## Path shape — exact, no nesting
55
+
56
+ The path component after `/files/` must be **exactly** `10_raw/<slug>.md` — a single segment under `10_raw/`. No date prefix folder, no category folder, no sub-categorisation:
57
+
58
+ | Wrong | Why | Correct |
59
+ |---|---|---|
60
+ | `10_raw/articles/2026-05-13-ninja-h2-sx-owner-reviews.md` | nested folder `articles/` — `invalid_layer` 400 | `10_raw/news-webike-31883.md` |
61
+ | `10_raw/2026-05-13/ninja.md` | date folder — `invalid_layer` 400 | `10_raw/news-webike-31883.md` |
62
+ | `10_raw/news.md/file.md` | path has `.md` mid-segment | `10_raw/news-webike-31883.md` |
63
+ | `10_RAW/news.md` | uppercase root | `10_raw/news-webike-31883.md` |
64
+ | `10_raw/News-Webike-31883.md` | slug not lowercase | `10_raw/news-webike-31883.md` |
65
+
66
+ The only acceptable nested form is `10_raw/images/<slug>/<file>` for source images — text notes are always a single segment.
67
+
68
+ ## Canonical curl invocation
69
+
70
+ The Bash command MUST start with the literal token `curl`. The session's `Bash(curl *)` allow-rule is prefix-matched; commands wrapped in `echo … |`, `cat <<EOF |`, `bash -c`, `( … )`, command substitution, or any other shell construct are silently denied under `dontAsk` mode and you receive no error. If a Bash call returns with no stdout, no stderr, and no `PA_API_ERROR`, your command was silently denied — rewrite it to start with literal `curl`.
71
+
72
+ ### POST the raw note — heredoc shape (recommended for article bodies)
73
+
74
+ Article bodies are typically several KB and benefit from heredoc quoting (no shell escapes for `"`, real newlines instead of `\n`). The command still starts with `curl`; the heredoc redirects into curl's stdin, and the shim reads it via `-d @-`:
75
+
76
+ ```bash
77
+ curl http://localhost:8321/api/wiki/{{workspace_name}}/files/10_raw/<slug>.md \
78
+ -X POST \
79
+ -H 'content-type: application/json' \
80
+ -H 'x-process-key: wiki.ingest_url' \
81
+ -d @- <<'JSON'
82
+ {"content":"---\nsource: https://news.example.com/path/123\nretrieved: 2026-05-13T05:08:00Z\ntitle: Sample article\nprocess: wiki.ingest_url\n---\n\n# Sample article\n\n## Source extracts\n\n- First quoted paragraph from the page.\n\n## Open questions\n\n- Anything not verifiable from the page."}
83
+ JSON
84
+ ```
85
+
86
+ Inside `<<'JSON'` (single-quoted marker) the body is verbatim — shell does NOT expand `$`, backticks, or quotes. Only JSON's own escapes apply: `\"`, `\\`, `\n`.
87
+
88
+ ### POST inline (for short bodies only)
89
+
90
+ ```bash
91
+ curl http://localhost:8321/api/wiki/{{workspace_name}}/files/10_raw/<slug>.md \
92
+ -X POST \
93
+ -H 'content-type: application/json' \
94
+ -H 'x-process-key: wiki.ingest_url' \
95
+ -d '{"content":"---\nsource: ...\n---\n\n# Title\n\n## Source extracts\n\n- Short body."}'
96
+ ```
97
+
98
+ Expected response (anything else means failure):
99
+
100
+ ```
101
+ {"ok":true,"path":"10_raw/<slug>.md"}
102
+ ```
103
+
104
+ ### (Optional) Verify the file actually exists
105
+
106
+ ```bash
107
+ curl http://localhost:8321/api/wiki/{{workspace_name}}/files/10_raw/<slug>.md \
108
+ -H 'x-process-key: wiki.ingest_url'
109
+ ```
110
+
111
+ Expected response: `{"path":"10_raw/<slug>.md","content":"...","mtime":"...","sizeBytes":N}`. A 404 means the POST silently failed even if you thought you saw `ok:true`.
112
+
113
+ ### Append a failure entry to log.md (use ONLY when the raw note will NOT be created)
114
+
115
+ ```bash
116
+ curl http://localhost:8321/api/wiki/{{workspace_name}}/files/log.md \
117
+ -X PATCH \
118
+ -H 'content-type: application/json' \
119
+ -H 'x-process-key: wiki.ingest_url' \
120
+ -d '{"mode":"append","content":"[2026-05-13T05:08:00Z] wiki.ingest_url failed https://news.example.com/path/123 — fetch returned 403\n"}'
121
+ ```
122
+
123
+ ## Body-quoting rules
124
+
125
+ **Heredoc body (recommended for article bodies):** wrap with a single-quoted marker (`<<'JSON' … JSON`). The body is verbatim — shell does NOT expand `$`, backticks, or quotes. Only JSON's own escapes apply: `\"`, `\\`, `\n`. The line `JSON` must appear at column 0 with no trailing characters.
126
+
127
+ **Inline body (short bodies only):** wrap the JSON in outer single quotes; inside, use JSON escapes:
128
+
129
+ | Need in `content` | Write in shell-arg |
130
+ |---|---|
131
+ | `"` | `\"` |
132
+ | `\` | `\\` |
133
+ | newline | `\n` |
134
+ | `'` | `'\''` (close-escape-reopen), or substitute `’` (U+2019) |
135
+ | `$`, backticks | leave as-is (single quotes suppress expansion) |
136
+
137
+ Keep the note compact — well under 512 KB. Curate 5-20 KB of the most informative extracts rather than dumping the whole page.
138
+
139
+ ## Anti-patterns (silent denials / 4xx — never claim success after these)
140
+
141
+ - `WebFetch http://localhost:8321/...` — Claude WebFetch refuses loopback. Use curl.
142
+ - `echo '{...}' | curl ...`, `cat <<JSON | curl ... -d @- JSON ; …`, `bash -c "curl ..."` — Bash command does not start with `curl`; denied silently under `dontAsk`. (Heredoc redirected directly into curl on the same line — `curl ... -d @- <<'JSON' … JSON` — IS allowed because the command still starts with `curl`.)
143
+ - `curl ... -d @/tmp/body.json` — `@<path>` form is blocked by the security hook and by the shim.
144
+ - `curl http://example.com/...` (non-loopback) — security hook denies; only `http://localhost:8321/api/*` is permitted.
145
+ - POST to a non-existent path like `/api/send-message`, `/api/whatsapp/send`, `/api/notify-user`, `/api/dm`. These are NOT daemon routes; calls return 401/404 and DO NOT notify anyone.
146
+ - POST/PATCH to `/api/wiki/.../10_raw/<slug>.md` a second time — raw is create-only; the second call returns 409 and **does NOT** modify the file.
147
+ - `Write` / `Edit` tools — stripped from the session allow-list for every `wiki.*` process key. The SDK denies them silently under `dontAsk` (you'll see "Permission to use Write has been denied …"). There is no path-rewrite that makes them work; use the Wiki API via curl. `Bash(find ...)`, `Bash(ls ...)`, `Bash(cat ...)` and other shell utilities are also denied — only `Bash(curl *)` and `Bash(jq *)` are on the allow-list.
148
+
149
+ ## Troubleshooting
150
+
151
+ Every non-2xx response causes the curl shim to write one line to stderr:
152
+
153
+ ```
154
+ PA_API_ERROR {"method":"POST","path":"/api/wiki/...","status":<n>,"bodyPreview":"<json error>"}
155
+ ```
156
+
157
+ Read `status` + `bodyPreview` and react:
158
+
159
+ | Status | Body code | Cause | Recovery |
160
+ |---|---|---|---|
161
+ | 200 | (response is `{"ok":true,"path":...}`) | Success | Emit the success DM. |
162
+ | 200 | (response missing `ok` or `path`) | Should not happen — but if it does, treat as failure | Emit failure DM. |
163
+ | 400 | `invalid_body` | JSON body did not parse / `content` not a string | Re-emit the body. For inline `-d` check your single-quote / `\n` / `\"` escapes. If the body was `@-` literally, the heredoc was missed — switch to the heredoc shape (`-d @- <<'JSON' … JSON` on the same line as curl). |
164
+ | 400 | `invalid_path` / `invalid_layer` | Slug or layer rejected | Path must be **exactly** `10_raw/<slug>.md`, slug matching `^[a-z0-9][a-z0-9-]*$`. No nested folders. |
165
+ | 403 | `missing_process_key` | Header missing | Add `-H 'x-process-key: wiki.ingest_url'` and retry. |
166
+ | 403 | `raw_write_denied` | Process key isn't `wiki.ingest_url` | Configuration error; emit failure DM, do not retry. |
167
+ | 409 | `append_only` | Slug already exists in `10_raw/` | Suffix the slug (`<slug>-2`), retry the POST **once**. If `-2` also 409, PATCH log.md and emit failure DM — do not loop further. |
168
+ | 413 | (body too large) | Article > 512 KB | Trim verbatim extracts; keep essentials. |
169
+ | 5xx | — | Daemon error | PATCH log.md and emit failure DM. Do not retry — the daemon will not heal mid-turn. |
170
+
171
+ If the Bash call returns to the prompt with **no stdout body and no `PA_API_ERROR`**, your command did not start with literal `curl` and was silently denied. Rewrite it as a flat, single-line curl invocation following the canonical shape above.
172
+
173
+ ## Completion message (mandatory final assistant text)
174
+
175
+ Emit exactly one line as your final assistant message. The daemon forwards this verbatim to the channel the bang came from — no other delivery mechanism is needed and you must NOT try to "send" it through another endpoint.
176
+
177
+ - Success (only after seeing `{"ok":true,"path":"10_raw/<slug>.md"}`): `Ingested <url> → 10_raw/<slug>.md`
178
+ - Failure (any other outcome): `Failed <url> — <one-sentence reason>`
179
+
180
+ The path you put in the success DM must be **byte-identical** to the `path` field returned by the daemon. Inventing or paraphrasing it is the bug this section exists to prevent.
181
+
182
+ No follow-up offers, no explanation paragraphs — just the single line.
@@ -0,0 +1,90 @@
1
+ ---
2
+ name: wiki-lint
3
+ description: Load for wiki.lint. Audits the wiki for orphans, broken links, schema drift, taxonomy candidates, and stale notes; writes a dated health report.
4
+ allowed-tools:
5
+ - Bash(curl *)
6
+ - Bash(jq *)
7
+ ---
8
+
9
+ # Wiki Lint
10
+
11
+ You run under process key `wiki.lint`.
12
+
13
+ Use the Wiki API to inventory the workspace and produce one health report. Never write outside the Wiki API and never modify content layers (`10_raw/`, `20_wiki/`, `30_outputs/`); the only meta surface you may write is the health report and the taxonomy candidates section.
14
+
15
+ ## Inputs
16
+
17
+ Read the full index and recent operational history:
18
+
19
+ ```
20
+ GET /api/wiki/{{workspace_name}}/index
21
+ GET /api/wiki/{{workspace_name}}/files/log.md
22
+ GET /api/wiki/{{workspace_name}}/files/90_meta/taxonomy.md
23
+ GET /api/wiki/{{workspace_name}}/files/90_meta/schemas/raw.md
24
+ GET /api/wiki/{{workspace_name}}/files/90_meta/schemas/wiki.md
25
+ GET /api/wiki/{{workspace_name}}/files/90_meta/schemas/output.md
26
+ ```
27
+
28
+ Sample (do not exhaustively read) `10_raw/` and `20_wiki/` notes that look anomalous from the index alone — large size deltas, unusually old `mtime`, slugs that don't appear anywhere in `_index.md`.
29
+
30
+ ## Checks
31
+
32
+ 1. **Orphans** — wiki notes (`20_wiki/<slug>.md`) that no other wiki note links to.
33
+ 2. **Broken wikilinks** — `[[slug]]` references whose target file does not exist.
34
+ 3. **Missing frontmatter** — notes that violate the schema in `90_meta/schemas/`.
35
+ 4. **Stale content** — wiki notes whose newest source link is older than 90 days, or whose body has not changed in 180 days while the raw set behind it has grown.
36
+ 5. **Term inconsistencies** — slug/title variants that look like the same concept (canonicalise via `90_meta/taxonomy.md`).
37
+ 6. **Taxonomy candidates** — recurring concepts (≥3 raw notes or ≥2 wiki notes) that are not yet listed in `90_meta/taxonomy.md`.
38
+ 7. **Index drift** — `20_wiki/_index.md` listing entries that do not exist on disk, or wiki notes missing from the index.
39
+
40
+ ## Outputs
41
+
42
+ Write exactly one health report:
43
+
44
+ ```
45
+ POST /api/wiki/{{workspace_name}}/files/90_meta/health/<YYYY-MM-DD>.md
46
+ x-process-key: wiki.lint
47
+ ```
48
+
49
+ Use today's date in `{{language}}`-neutral ISO form. The report must have these sections in order:
50
+
51
+ ```
52
+ # Wiki Health — <YYYY-MM-DD>
53
+
54
+ ## Summary
55
+ - one-line tally per check (e.g. "3 orphans, 1 broken link, 0 stale notes")
56
+
57
+ ## Action items
58
+ - bullet list, each item names the affected file and the proposed fix
59
+
60
+ ## Orphans
61
+ ## Broken wikilinks
62
+ ## Missing frontmatter
63
+ ## Stale content
64
+ ## Term inconsistencies
65
+ ## Taxonomy candidates
66
+ ## Index drift
67
+ ```
68
+
69
+ Empty sections must still appear with `_(none)_` as the body so a downstream diff can detect the absence.
70
+
71
+ If — and only if — there are taxonomy candidates, append (PATCH `mode: "append"`) a `# Candidates` section to `90_meta/taxonomy.md`:
72
+
73
+ ```
74
+ PATCH /api/wiki/{{workspace_name}}/files/90_meta/taxonomy.md
75
+ x-process-key: wiki.lint
76
+ ```
77
+
78
+ Each candidate line: ` - <canonical-slug> — <one-sentence rationale, references to N raw / M wiki>`. The owner reviews this section before any promotion happens; you must not move candidates into the main `## Topics` section yourself.
79
+
80
+ Append a concise `log.md` entry summarising the report ("wiki.lint: 3 orphans, 1 broken link, 2 taxonomy candidates"). If a check could not run (e.g. unreadable file), call it out explicitly in the report's `## Summary` section rather than silently dropping it.
81
+
82
+ ### Completion message (mandatory)
83
+
84
+ End the turn with one short final assistant message that the daemon forwards back to the channel the bang command came from:
85
+
86
+ - `Lint complete — <tally>. Report: 90_meta/health/<YYYY-MM-DD>.md.`
87
+ - `<tally>` = the same one-line totals from the report's `## Summary`, e.g. "3 orphans, 1 broken link, 2 taxonomy candidates".
88
+ - On hard failure (no report written): `Lint failed — <one-sentence reason>.`
89
+
90
+ Do not paste the full report into the completion message. The user opens the file or the dashboard for detail.
@@ -0,0 +1,72 @@
1
+ ---
2
+ name: wiki-trace
3
+ description: Load for wiki.trace. Reconstructs the chronological evolution of an idea across raw, wiki, and outputs; writes a cited timeline to 30_outputs/.
4
+ allowed-tools:
5
+ - Bash(curl *)
6
+ - Bash(jq *)
7
+ ---
8
+
9
+ # Wiki Trace
10
+
11
+ You run under process key `wiki.trace`.
12
+
13
+ Read `<wiki_command>` for the user's `topic` (free-form). Your job is to reconstruct **how thinking about that topic evolved over time** using only what the wiki actually contains. Never invent sources.
14
+
15
+ ## Method
16
+
17
+ 1. Search across all layers:
18
+ ```
19
+ GET /api/wiki/{{workspace_name}}/search?q=<topic>
20
+ ```
21
+ Then drill into matches by reading their bodies. Cover `10_raw/`, `20_wiki/`, and `30_outputs/`.
22
+ 2. Order findings chronologically. Prefer dates that the content asserts (raw `retrieved` timestamps, source publish dates, output filenames `<YYYY-MM-DD>-…`). When only file `mtime` is available, mark the entry as "discovered on" rather than "happened on".
23
+ 3. Group into **phases** — a phase is a span where the dominant framing, vocabulary, or open question stays roughly stable. Two to five phases is the usual shape; one phase is fine for a topic the wiki only mentions briefly.
24
+ 4. For each phase, surface:
25
+ - The dominant question or claim.
26
+ - The new evidence introduced.
27
+ - What changed compared to the previous phase.
28
+ 5. Cite every claim with a wiki path (e.g. `10_raw/quantum-computing-2024-overview.md`). Do not collapse multiple sources into a single uncited paragraph.
29
+
30
+ ## Output
31
+
32
+ Write exactly one timeline report to:
33
+
34
+ ```
35
+ POST /api/wiki/{{workspace_name}}/files/30_outputs/<YYYY-MM-DD>-trace-<slug>.md
36
+ x-process-key: wiki.trace
37
+ ```
38
+
39
+ - `<YYYY-MM-DD>` is today's date.
40
+ - `<slug>` is a kebab-case slug derived from the topic (e.g. `quantum-computing`). Use the canonical slug from `90_meta/taxonomy.md` when one exists.
41
+
42
+ Report shape:
43
+
44
+ ```
45
+ # Trace — <topic> (<YYYY-MM-DD>)
46
+
47
+ ## Summary
48
+ - one-paragraph synthesis of the arc
49
+
50
+ ## Phase 1 — <YYYY-MM> through <YYYY-MM>: <short label>
51
+ - key question / claim
52
+ - evidence: `<wiki path>`, `<wiki path>`
53
+ - shift since last phase: _(first phase, no prior)_
54
+
55
+ ## Phase 2 — …
56
+
57
+
58
+ ## Gaps
59
+ - bullet list of questions the wiki cannot yet answer
60
+ ```
61
+
62
+ If the wiki has fewer than two distinct sources on the topic, say so directly in `## Summary` and keep the report short — do not pad it with speculation. Append a one-line `log.md` entry referencing the output filename and the topic.
63
+
64
+ ### Completion message (mandatory)
65
+
66
+ End the turn with one short final assistant message that the daemon forwards back to the channel the bang command came from:
67
+
68
+ - `Trace complete — <topic>: <N> phase(s) across <M> source(s). Timeline: 30_outputs/<YYYY-MM-DD>-trace-<slug>.md.`
69
+ - Sparse-vault case: `Trace complete — wiki has <M> source(s) on <topic>; too few to show a clear arc. Report: 30_outputs/<YYYY-MM-DD>-trace-<slug>.md.`
70
+ - On failure (no report written): `Trace failed — <one-sentence reason>.`
71
+
72
+ Do not summarise the timeline content in the completion message — let the file carry it.