@ctxr/skill-llm-wiki 1.0.1 → 1.1.0

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 (50) hide show
  1. package/CHANGELOG.md +118 -0
  2. package/README.md +2 -2
  3. package/SKILL.md +7 -0
  4. package/guide/cli.md +6 -4
  5. package/guide/consumers/index.md +106 -0
  6. package/guide/consumers/quickstart.md +96 -0
  7. package/guide/consumers/recipes/ci-gate.md +125 -0
  8. package/guide/consumers/recipes/dated-wiki.md +131 -0
  9. package/guide/consumers/recipes/format-gate.md +126 -0
  10. package/guide/consumers/recipes/post-write-heal.md +125 -0
  11. package/guide/consumers/recipes/skill-absent.md +111 -0
  12. package/guide/consumers/recipes/subject-wiki.md +110 -0
  13. package/guide/consumers/recipes/testing.md +149 -0
  14. package/guide/index.md +9 -0
  15. package/guide/substrate/operators.md +1 -1
  16. package/guide/substrate/tiered-ai.md +6 -5
  17. package/guide/ux/user-intent.md +6 -5
  18. package/package.json +9 -3
  19. package/scripts/cli.mjs +565 -15
  20. package/scripts/lib/balance.mjs +579 -0
  21. package/scripts/lib/cluster-detect.mjs +482 -4
  22. package/scripts/lib/contract.mjs +257 -0
  23. package/scripts/lib/decision-log.mjs +121 -15
  24. package/scripts/lib/heal.mjs +167 -0
  25. package/scripts/lib/init.mjs +210 -0
  26. package/scripts/lib/intent.mjs +370 -4
  27. package/scripts/lib/join-constants.mjs +22 -0
  28. package/scripts/lib/join.mjs +917 -0
  29. package/scripts/lib/json-envelope.mjs +190 -0
  30. package/scripts/lib/nest-applier.mjs +395 -32
  31. package/scripts/lib/operators.mjs +472 -38
  32. package/scripts/lib/orchestrator.mjs +419 -12
  33. package/scripts/lib/root-containment.mjs +351 -0
  34. package/scripts/lib/similarity-cache.mjs +115 -20
  35. package/scripts/lib/similarity.mjs +11 -0
  36. package/scripts/lib/soft-dag.mjs +726 -0
  37. package/scripts/lib/templates.mjs +78 -0
  38. package/scripts/lib/tiered.mjs +42 -18
  39. package/scripts/lib/validate.mjs +22 -0
  40. package/scripts/lib/where.mjs +71 -0
  41. package/scripts/testkit/assert-frontmatter.mjs +171 -0
  42. package/scripts/testkit/cli-run.mjs +95 -0
  43. package/scripts/testkit/make-wiki-fixture.mjs +301 -0
  44. package/scripts/testkit/stub-skill.mjs +107 -0
  45. package/templates/adrs.llmwiki.layout.yaml +33 -0
  46. package/templates/plans.llmwiki.layout.yaml +34 -0
  47. package/templates/regressions.llmwiki.layout.yaml +34 -0
  48. package/templates/reports.llmwiki.layout.yaml +33 -0
  49. package/templates/runbooks.llmwiki.layout.yaml +33 -0
  50. package/templates/sessions.llmwiki.layout.yaml +34 -0
@@ -0,0 +1,131 @@
1
+ ---
2
+ id: recipe-dated-wiki
3
+ type: primary
4
+ depth_role: leaf
5
+ focus: "Init a dated topic wiki (reports, sessions, regressions) with one command"
6
+ parents:
7
+ - ../index.md
8
+ tags:
9
+ - dated
10
+ - init
11
+ - hosted
12
+ - consumers
13
+ activation:
14
+ keyword_matches:
15
+ - dated wiki
16
+ - reports wiki
17
+ - sessions wiki
18
+ - regressions wiki
19
+ - yyyy/mm/dd
20
+ tag_matches:
21
+ - dated
22
+ - init
23
+
24
+ generator: "skill-llm-wiki/v1"
25
+ ---
26
+
27
+ # Recipe: dated wiki
28
+
29
+ ## Trigger
30
+
31
+ Your consumer wants to manage a topic whose entries accrete by date (reports, sessions, regression notes, anything with a timestamp). A flat list of date-prefixed siblings will break past a few dozen entries; a nested `{yyyy}/{mm}/{dd}` structure scales indefinitely.
32
+
33
+ ## Commands
34
+
35
+ ```bash
36
+ # Seed the layout contract from a shipped starter template and
37
+ # emit the envelope:
38
+ skill-llm-wiki init .development/shared/reports \
39
+ --kind dated --template reports --json
40
+
41
+ # The envelope names the next command to run. Typically:
42
+ skill-llm-wiki build .development/shared/reports \
43
+ --layout-mode hosted --target .development/shared/reports --json
44
+ ```
45
+
46
+ Available dated templates (from `skill-llm-wiki contract --json` → `layout_tokens`, and `scripts/lib/templates.mjs`):
47
+
48
+ | Template | Default path template | Best for |
49
+ |---|---|---|
50
+ | `reports` | `{yyyy}/{mm}/{dd}` | review reports, investigations |
51
+ | `sessions` | `{yyyy}/{mm}/{dd}` | daily session logs |
52
+ | `regressions` | `{yyyy}/{mm}` | bug triage, post-mortems |
53
+ | `plans` | `{yyyy}/{mm}/{dd}` | implementation plans + subject families |
54
+
55
+ If you omit `--template`, `--kind dated` falls back to `reports`.
56
+
57
+ ## Envelope fields
58
+
59
+ After `init` succeeds, the envelope is:
60
+
61
+ ```json
62
+ {
63
+ "schema": "skill-llm-wiki/v1",
64
+ "command": "init",
65
+ "target": "/abs/path/to/.development/shared/reports",
66
+ "verdict": "initialised",
67
+ "exit": 0,
68
+ "diagnostics": [
69
+ {
70
+ "code": "NEXT-01",
71
+ "severity": "info",
72
+ "path": "/abs/path/to/.development/shared/reports",
73
+ "message": "contract seeded; next step: skill-llm-wiki build ..."
74
+ }
75
+ ],
76
+ "artifacts": {
77
+ "created": ["/abs/.../.llmwiki.layout.yaml"],
78
+ "modified": [],
79
+ "deleted": []
80
+ },
81
+ "next": {
82
+ "command": "skill-llm-wiki",
83
+ "args": ["build", "/abs/.../reports", "--layout-mode", "hosted", "--target", "/abs/.../reports", "--json"]
84
+ },
85
+ "timing_ms": 12
86
+ }
87
+ ```
88
+
89
+ Consumers read the structured `next` field to get the exact build command: `env.next.command` + `env.next.args` go directly into `spawnSync`. The `NEXT-01` diagnostic carries the same hint as human-readable prose for operators tailing stdout, but it is not the canonical machine form.
90
+
91
+ ## Minimum consumer code
92
+
93
+ ```js
94
+ import { spawnSync } from "node:child_process";
95
+
96
+ function initDated(topicPath, template = "reports") {
97
+ const r = spawnSync(
98
+ "skill-llm-wiki",
99
+ ["init", topicPath, "--kind", "dated", "--template", template, "--json"],
100
+ { encoding: "utf8" },
101
+ );
102
+ if (r.status !== 0) throw new Error(`init failed: ${r.stderr}`);
103
+ const env = JSON.parse(r.stdout);
104
+ if (env.verdict !== "initialised") throw new Error(env.diagnostics?.[0]?.message ?? "unexpected");
105
+ // Prefer the structured `next` field over parsing NEXT-01.
106
+ return {
107
+ contractPath: env.artifacts.created[0],
108
+ next: env.next, // { command: "skill-llm-wiki", args: ["build", ...] }
109
+ };
110
+ }
111
+
112
+ // Consumers that want to run the build immediately:
113
+ function initAndBuild(topicPath, template = "reports") {
114
+ const { next } = initDated(topicPath, template);
115
+ if (!next) throw new Error("init envelope missing `next` field");
116
+ const r2 = spawnSync(next.command, next.args, { encoding: "utf8", stdio: "inherit" });
117
+ if (r2.status !== 0) throw new Error(`build exited ${r2.status}`);
118
+ }
119
+ ```
120
+
121
+ ## Failure modes
122
+
123
+ - `verdict: "ambiguous"` with `INIT-02`: you passed neither `--kind` nor `--template`.
124
+ - `verdict: "ambiguous"` with `INIT-05`: `--kind` and `--template` disagree (e.g. `--kind dated --template runbooks`).
125
+ - `verdict: "ambiguous"` with `INIT-07`: a `.llmwiki.layout.yaml` already exists. Pass `--force` only if you are sure the existing contract is wrong; otherwise `skill-llm-wiki rebuild` against the existing contract instead.
126
+ - `verdict: "ambiguous"` with `INIT-08`: the topic path (or the contract path inside it) exists as a symbolic link. `init` refuses to follow symlinks into unknown targets for security. Resolve the symlink explicitly (`realpath <topic>`) and pass the resolved path, or remove the symlink.
127
+
128
+ ## Do not
129
+
130
+ - Copy the template file by hand. The skill's `init` command reads the same file; there is no reason to duplicate it in your own repo.
131
+ - Pick `{yyyy}-{mm}-{dd}` as a filename prefix. Flat date-prefixed siblings are refused by `validate`.
@@ -0,0 +1,126 @@
1
+ ---
2
+ id: recipe-format-gate
3
+ type: primary
4
+ depth_role: leaf
5
+ focus: "Gate consumer CI on skill-llm-wiki contract --json format_version"
6
+ parents:
7
+ - ../index.md
8
+ tags:
9
+ - contract
10
+ - format-version
11
+ - compatibility
12
+ - consumers
13
+ activation:
14
+ keyword_matches:
15
+ - format version
16
+ - contract gate
17
+ - compatibility check
18
+ - version gate
19
+ - drift detection
20
+ tag_matches:
21
+ - contract
22
+ - format-version
23
+
24
+ generator: "skill-llm-wiki/v1"
25
+ ---
26
+
27
+ # Recipe: format_version gate
28
+
29
+ ## Trigger
30
+
31
+ Your consumer's tests used to drift-test against SKILL.md prose. Replace that with a single integer comparison against `format_version`.
32
+
33
+ ## Commands
34
+
35
+ ```bash
36
+ skill-llm-wiki contract --json
37
+ ```
38
+
39
+ Parse the contract JSON; assert `format_version >= <your required>`. Note that `contract --json` emits the contract shape (schema `skill-llm-wiki/contract/v1`), NOT the operational envelope (`skill-llm-wiki/v1`). It carries no `verdict` or `diagnostics` fields.
40
+
41
+ ## Contract JSON fields
42
+
43
+ ```json
44
+ {
45
+ "schema": "skill-llm-wiki/contract/v1",
46
+ "format_version": 1,
47
+ "min_consumer_format_version": 1,
48
+ "package_version": "1.0.1",
49
+ "frontmatter_schema": { "leaf": { "required": [...], "fields": {...} } },
50
+ "layout_tokens": [{ "token": "{yyyy}", "description": "..." }, ...],
51
+ "subcommands": { "build": { "positionals": ["source"], "flags": [...] }, ... },
52
+ "envelope_schema": { "schema": "skill-llm-wiki/v1", "fields": {...} },
53
+ "exit_codes": { "0": "ok", "1": "usage error", ... }
54
+ }
55
+ ```
56
+
57
+ Fields consumers care about:
58
+
59
+ - `format_version`: integer. Bumps on breaking changes.
60
+ - `min_consumer_format_version`: integer. The oldest consumer format_version the current skill still speaks to. A consumer whose required version is below this refuses to run; between this and `format_version` runs unchanged.
61
+ - `package_version`: semver string. For display only; gate on `format_version`, not on semver.
62
+
63
+ ## Minimum consumer code
64
+
65
+ ```js
66
+ import { spawnSync } from "node:child_process";
67
+
68
+ export const REQUIRED_WIKI_FORMAT_VERSION = 1;
69
+
70
+ export function enforceContract(required = REQUIRED_WIKI_FORMAT_VERSION) {
71
+ const r = spawnSync("skill-llm-wiki", ["contract", "--json"], {
72
+ encoding: "utf8",
73
+ });
74
+ if (r.status !== 0) {
75
+ throw new Error(
76
+ `skill-llm-wiki contract failed (${r.status}): ${r.stderr}`,
77
+ );
78
+ }
79
+ const env = JSON.parse(r.stdout);
80
+ if (env.format_version < required) {
81
+ throw new Error(
82
+ `skill-llm-wiki format_version=${env.format_version} is below the required ${required}. ` +
83
+ `Upgrade: npm i -g @ctxr/skill-llm-wiki@latest`,
84
+ );
85
+ }
86
+ if (env.min_consumer_format_version > required) {
87
+ throw new Error(
88
+ `skill-llm-wiki dropped support for format_version=${required}; ` +
89
+ `current min_consumer_format_version=${env.min_consumer_format_version}. ` +
90
+ `Your consumer must upgrade its integration to format_version >= ${env.min_consumer_format_version}.`,
91
+ );
92
+ }
93
+ return env;
94
+ }
95
+ ```
96
+
97
+ ## CI one-liner
98
+
99
+ ```bash
100
+ # Fail CI if the installed skill is below the required format_version.
101
+ FV=$(skill-llm-wiki contract --json | jq -r '.format_version')
102
+ if [ "$FV" -lt 1 ]; then echo "skill format_version=$FV below required 1" >&2; exit 1; fi
103
+ ```
104
+
105
+ ## When to bump your required version
106
+
107
+ Bump `REQUIRED_WIKI_FORMAT_VERSION` when:
108
+
109
+ - The skill bumps `format_version` and your consumer depends on the new behaviour.
110
+ - A leaf frontmatter field your consumer reads is added or removed.
111
+ - A CLI flag your consumer passes becomes mandatory or is removed.
112
+ - The operational envelope shape (schema `skill-llm-wiki/v1`) changes a field your consumer parses from validate/init/heal/rollback output.
113
+
114
+ Do NOT bump when the skill ships a `package_version` patch or minor release that doesn't touch the contract.
115
+
116
+ ## Failure modes
117
+
118
+ - `format_version` missing from the contract JSON: the skill is too old to declare a format version (pre-1). Treat as `< 1` and fail with an upgrade message.
119
+ - Contract JSON is unparseable: the skill's `contract` subcommand is broken. Open an issue upstream.
120
+ - `min_consumer_format_version` exceeds your required version: the skill intentionally dropped support for your version. You must upgrade your consumer.
121
+
122
+ ## Do not
123
+
124
+ - Drift-test against SKILL.md prose. The contract subcommand exists specifically so consumers do not need to read SKILL.md programmatically.
125
+ - Gate on `package_version` semver. A patch release can ship bug fixes without bumping `format_version`; a `format_version` bump is the only breaking-change signal.
126
+ - Cache the contract across PR runs. It's a ~100ms probe; run it every time.
@@ -0,0 +1,125 @@
1
+ ---
2
+ id: recipe-post-write-heal
3
+ type: primary
4
+ depth_role: leaf
5
+ focus: "The canonical after-every-leaf-write heal invocation"
6
+ parents:
7
+ - ../index.md
8
+ tags:
9
+ - heal
10
+ - envelope
11
+ - post-write
12
+ - consumers
13
+ activation:
14
+ keyword_matches:
15
+ - post write
16
+ - after write
17
+ - heal
18
+ - validate after write
19
+ - next command
20
+ tag_matches:
21
+ - heal
22
+ - post-write
23
+
24
+ generator: "skill-llm-wiki/v1"
25
+ ---
26
+
27
+ # Recipe: post-write heal
28
+
29
+ ## Trigger
30
+
31
+ Your consumer just wrote a leaf into a hosted wiki. Before moving on, call `heal` to classify the wiki's state and let it name the next command.
32
+
33
+ ## Commands
34
+
35
+ ```bash
36
+ skill-llm-wiki heal .development/shared/reports --json
37
+ ```
38
+
39
+ That is the entire step. Do not invoke `validate`, `fix`, or `rebuild` directly; `heal` is the router.
40
+
41
+ ## Envelope fields
42
+
43
+ ```json
44
+ {
45
+ "schema": "skill-llm-wiki/v1",
46
+ "command": "heal",
47
+ "target": "/abs/.../reports",
48
+ "verdict": "ok" | "fixable" | "needs-rebuild" | "broken" | "ambiguous",
49
+ "exit": 0,
50
+ "diagnostics": [
51
+ { "code": "IDX-01", "severity": "warning", "path": "...", "message": "..." },
52
+ { "code": "NEXT-01", "severity": "info", "path": "...", "message": "next: skill-llm-wiki fix ... --json" }
53
+ ],
54
+ "next": { "command": "skill-llm-wiki", "args": ["fix", "/abs/.../reports", "--json"] },
55
+ "timing_ms": 23
56
+ }
57
+ ```
58
+
59
+ > The `next` field is the canonical machine-readable form. The `NEXT-01`
60
+ > info diagnostic carries the same hint as a human-readable string for
61
+ > operators tailing stdout; consumers should prefer `env.next` and only
62
+ > fall back to parsing the diagnostic when running against a pre-v1
63
+ > skill that does not emit it.
64
+
65
+ | Verdict | What the consumer does |
66
+ |---|---|
67
+ | `ok` | Nothing. Move on. |
68
+ | `fixable` | Invoke `env.next.command` with `env.next.args` (typically `skill-llm-wiki fix <wiki> --json`). |
69
+ | `needs-rebuild` | Invoke `env.next.command` with `env.next.args` (typically `skill-llm-wiki rebuild <wiki> --json`). |
70
+ | `broken` | Surface every `severity: "error"` diagnostic to the user. Do not auto-mutate. |
71
+ | `ambiguous` | `validate` itself failed; inspect `HEAL-00` diagnostic for the error. |
72
+
73
+ ## Minimum consumer code
74
+
75
+ ```js
76
+ import { spawnSync } from "node:child_process";
77
+
78
+ function healAfterWrite(wikiPath) {
79
+ const r = spawnSync(
80
+ "skill-llm-wiki",
81
+ ["heal", wikiPath, "--json"],
82
+ { encoding: "utf8" },
83
+ );
84
+ const env = JSON.parse(r.stdout);
85
+ switch (env.verdict) {
86
+ case "ok":
87
+ return;
88
+ case "fixable":
89
+ case "needs-rebuild": {
90
+ if (!env.next) throw new Error("heal returned fixable/needs-rebuild without a `next` field");
91
+ const result = spawnSync(env.next.command, env.next.args, { stdio: "inherit" });
92
+ if (result.status !== 0) {
93
+ throw new Error(`follow-up ${env.next.command} exited ${result.status}`);
94
+ }
95
+ return;
96
+ }
97
+ case "broken":
98
+ reportDiagnosticsToUser(env.diagnostics);
99
+ throw new Error(`heal: wiki is broken at ${wikiPath}`);
100
+ default:
101
+ throw new Error(`heal: unexpected verdict ${env.verdict}`);
102
+ }
103
+ }
104
+ ```
105
+
106
+ ## Failure modes
107
+
108
+ - `verdict: "ambiguous"` with a `HEAL-00` diagnostic: validate threw. The wiki substrate itself may be broken (e.g. missing `.llmwiki/git/`).
109
+ - A `fixable` run whose follow-up `fix` invocation fails: re-run `heal`; it should now report `needs-rebuild` or `broken`.
110
+ - `heal` on a path that does not exist or is not a wiki returns `verdict: "broken"` with `WIKI-01` in diagnostics.
111
+
112
+ ## Why not `fix` / `rebuild` / `validate` directly?
113
+
114
+ Consumers that pick one of the three without checking the validate output end up:
115
+
116
+ - Running `fix` on a structurally-broken wiki that only `rebuild` can resolve.
117
+ - Running `rebuild` on a wiki with trivial index drift, triggering unnecessary Tier 2 sub-agent work.
118
+ - Running `validate` and then duplicating the action table consumers always get wrong.
119
+
120
+ `heal` centralises the classification so every consumer uses the same rules.
121
+
122
+ ## Do not
123
+
124
+ - Call `heal` on hot paths (per-keystroke). It runs full validate; budget for one invocation per logical leaf-write batch.
125
+ - Ignore `broken` verdicts silently. The wiki is corrupt and further writes will compound the problem.
@@ -0,0 +1,111 @@
1
+ ---
2
+ id: recipe-skill-absent
3
+ type: primary
4
+ depth_role: leaf
5
+ focus: "Detect the skill is missing and surface an upgrade path without silently degrading"
6
+ parents:
7
+ - ../index.md
8
+ tags:
9
+ - skill-absent
10
+ - preflight
11
+ - install-hint
12
+ - consumers
13
+ activation:
14
+ keyword_matches:
15
+ - skill missing
16
+ - skill not installed
17
+ - install hint
18
+ - preflight skill
19
+ - hard dependency
20
+ tag_matches:
21
+ - skill-absent
22
+ - preflight
23
+
24
+ generator: "skill-llm-wiki/v1"
25
+ ---
26
+
27
+ # Recipe: skill-absent detection
28
+
29
+ ## Trigger
30
+
31
+ Your consumer treats `@ctxr/skill-llm-wiki` as a hard dependency and must refuse to run when the skill is missing, rather than silently degrading to raw-markdown writes.
32
+
33
+ ## Commands
34
+
35
+ ```bash
36
+ skill-llm-wiki contract --json
37
+ ```
38
+
39
+ This is the canonical probe. It is exempt from the runtime-dep preflight inside the skill itself, so even a partially-broken install still answers the probe as long as the binary is on PATH.
40
+
41
+ ## Decision tree
42
+
43
+ `skill-llm-wiki contract --json` emits the **contract JSON** (schema `skill-llm-wiki/contract/v1`), not the operational envelope (`skill-llm-wiki/v1`). It has no `verdict` or `diagnostics` fields; gate on `format_version` directly.
44
+
45
+ 1. `skill-llm-wiki contract --json` exits `0` with a parseable contract JSON containing `format_version >= <your required>`: proceed.
46
+ 2. `skill-llm-wiki contract --json` exits `0` with `format_version` below your required value: the skill is present but too old. Surface an upgrade message.
47
+ 3. `skill-llm-wiki contract --json` exits non-zero: the skill is installed but broken (e.g. runtime deps corrupted). Surface a reinstall message.
48
+ 4. The binary is not on PATH (spawnSync returns ENOENT or no output): the skill is not installed. Surface an install message.
49
+
50
+ ## Minimum consumer code
51
+
52
+ ```js
53
+ import { spawnSync } from "node:child_process";
54
+
55
+ const REQUIRED_FORMAT_VERSION = 1;
56
+
57
+ export function probeSkill() {
58
+ const r = spawnSync("skill-llm-wiki", ["contract", "--json"], {
59
+ encoding: "utf8",
60
+ });
61
+ if (r.error && r.error.code === "ENOENT") {
62
+ return { ok: false, state: "absent" };
63
+ }
64
+ if (r.status !== 0) {
65
+ return { ok: false, state: "broken", stderr: r.stderr };
66
+ }
67
+ try {
68
+ const env = JSON.parse(r.stdout);
69
+ if ((env.format_version ?? 0) < REQUIRED_FORMAT_VERSION) {
70
+ return { ok: false, state: "too-old", current: env.format_version };
71
+ }
72
+ return { ok: true, ...env };
73
+ } catch {
74
+ return { ok: false, state: "unparseable" };
75
+ }
76
+ }
77
+
78
+ export function enforceSkillPresent() {
79
+ const p = probeSkill();
80
+ if (p.ok) return p;
81
+ const hint =
82
+ p.state === "absent"
83
+ ? "@ctxr/skill-llm-wiki is not installed.\n" +
84
+ "Install with: npx @ctxr/kit install @ctxr/skill-llm-wiki"
85
+ : p.state === "too-old"
86
+ ? `@ctxr/skill-llm-wiki format_version ${p.current} is below the required ${REQUIRED_FORMAT_VERSION}.\n` +
87
+ "Upgrade with: npm i -g @ctxr/skill-llm-wiki@latest"
88
+ : `@ctxr/skill-llm-wiki is installed but not answering the contract probe.\n${p.stderr ?? ""}`;
89
+ throw new Error(hint);
90
+ }
91
+ ```
92
+
93
+ ## Do not
94
+
95
+ - Silently fall back to raw markdown writes when the skill is missing. Every downstream tool that expects the wiki format will break.
96
+ - Inline the install command guess. Read the contract JSON's `package_version` and compare to your own requirement; publish your install hint alongside your agent.
97
+ - Cache the probe result across sessions. A user can uninstall the skill at any time; the probe is cheap (~100ms) and should run once per invocation.
98
+
99
+ ## Recovery
100
+
101
+ Once the consumer reports the skill is missing, recovery for the user is:
102
+
103
+ ```bash
104
+ # Via @ctxr/kit (preferred, manages SKILL.md path + update flow):
105
+ npx @ctxr/kit install @ctxr/skill-llm-wiki
106
+
107
+ # Or plain npm for CI / scripted environments:
108
+ npm i -g @ctxr/skill-llm-wiki
109
+ ```
110
+
111
+ The consumer's preflight should re-run `probeSkill()` after the install.
@@ -0,0 +1,110 @@
1
+ ---
2
+ id: recipe-subject-wiki
3
+ type: primary
4
+ depth_role: leaf
5
+ focus: "Init a subject topic wiki (runbooks, adrs) with nested categories"
6
+ parents:
7
+ - ../index.md
8
+ tags:
9
+ - subject
10
+ - init
11
+ - hosted
12
+ - consumers
13
+ activation:
14
+ keyword_matches:
15
+ - subject wiki
16
+ - runbooks wiki
17
+ - adr wiki
18
+ - nested categories
19
+ tag_matches:
20
+ - subject
21
+ - init
22
+
23
+ generator: "skill-llm-wiki/v1"
24
+ ---
25
+
26
+ # Recipe: subject wiki
27
+
28
+ ## Trigger
29
+
30
+ Your consumer wants to manage a topic whose entries group by subject (runbooks, architecture decisions, playbooks). The wiki grows by adding subcategories, not dates.
31
+
32
+ ## Commands
33
+
34
+ ```bash
35
+ skill-llm-wiki init .development/shared/runbooks \
36
+ --kind subject --template runbooks --json
37
+
38
+ # After init, build (one-time):
39
+ skill-llm-wiki build .development/shared/runbooks \
40
+ --layout-mode hosted --target .development/shared/runbooks --json
41
+ ```
42
+
43
+ Available subject templates:
44
+
45
+ | Template | Best for |
46
+ |---|---|
47
+ | `runbooks` | operational runbooks, playbooks, incident response |
48
+ | `adrs` | architecture decision records (zero-padded sequential prefix) |
49
+
50
+ If you omit `--template`, `--kind subject` falls back to `runbooks`.
51
+
52
+ ## Category promotion rule
53
+
54
+ Both shipped templates encode the same invariant in their `global_invariants`: create a subject subfolder on the first write, and grow the hierarchy whenever two or more leaves share a defensible grouping. Never pile leaves at the topic root after a threshold — do it on write.
55
+
56
+ Your consumer should:
57
+
58
+ 1. Pick the subject subfolder before writing the leaf.
59
+ 2. Write the leaf at the deepest valid category path.
60
+ 3. Run `heal` (see [post-write-heal.md](post-write-heal.md)) so `validate` catches drift from the invariant.
61
+
62
+ ## Envelope fields
63
+
64
+ Same `init` envelope as the dated recipe:
65
+
66
+ ```json
67
+ {
68
+ "schema": "skill-llm-wiki/v1",
69
+ "command": "init",
70
+ "verdict": "initialised",
71
+ "target": "/abs/.../runbooks",
72
+ "artifacts": { "created": ["/abs/.../.llmwiki.layout.yaml"] },
73
+ "diagnostics": [{ "code": "NEXT-01", "severity": "info", "message": "..." }],
74
+ "next": {
75
+ "command": "skill-llm-wiki",
76
+ "args": ["build", "/abs/.../runbooks", "--layout-mode", "hosted", "--target", "/abs/.../runbooks", "--json"]
77
+ }
78
+ }
79
+ ```
80
+
81
+ Consumers read `env.next.command` + `env.next.args` to get the build invocation; the `NEXT-01` diagnostic carries the same hint as prose for humans tailing stdout.
82
+
83
+ ## Minimum consumer code
84
+
85
+ ```js
86
+ import { spawnSync } from "node:child_process";
87
+
88
+ function initSubject(topicPath, template = "runbooks") {
89
+ const r = spawnSync(
90
+ "skill-llm-wiki",
91
+ ["init", topicPath, "--kind", "subject", "--template", template, "--json"],
92
+ { encoding: "utf8" },
93
+ );
94
+ if (r.status !== 0) throw new Error(`init failed: ${r.stderr}`);
95
+ const env = JSON.parse(r.stdout);
96
+ // Prefer the structured `next` field over parsing NEXT-01.
97
+ return { contractPath: env.artifacts.created[0], next: env.next };
98
+ }
99
+ ```
100
+
101
+ ## Failure modes
102
+
103
+ - `verdict: "ambiguous"` with `INIT-05`: `--kind subject` does not match a dated template (`reports`, `sessions`, `regressions`, `plans`).
104
+ - `verdict: "ambiguous"` with `INIT-07`: contract already exists; use `--force` or `rebuild`.
105
+ - `verdict: "ambiguous"` with `INIT-08`: the topic path (or the contract path inside it) is a symbolic link. `init` refuses to follow symlinks for security. Resolve it explicitly or remove it before retrying.
106
+
107
+ ## Do not
108
+
109
+ - Put runbooks in a flat list at the topic root. After three siblings, every new leaf should land under a named subcategory.
110
+ - Use a date prefix or date subfolder. Subject wikis are explicitly not dated; that is why they use this template.