@ctxr/skill-llm-wiki 1.0.1 → 1.0.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.
- package/SKILL.md +7 -0
- package/guide/cli.md +3 -2
- package/guide/consumers/index.md +106 -0
- package/guide/consumers/quickstart.md +96 -0
- package/guide/consumers/recipes/ci-gate.md +125 -0
- package/guide/consumers/recipes/dated-wiki.md +131 -0
- package/guide/consumers/recipes/format-gate.md +126 -0
- package/guide/consumers/recipes/post-write-heal.md +125 -0
- package/guide/consumers/recipes/skill-absent.md +111 -0
- package/guide/consumers/recipes/subject-wiki.md +110 -0
- package/guide/consumers/recipes/testing.md +149 -0
- package/guide/index.md +9 -0
- package/guide/ux/user-intent.md +5 -4
- package/package.json +6 -2
- package/scripts/cli.mjs +473 -13
- package/scripts/lib/contract.mjs +229 -0
- package/scripts/lib/heal.mjs +162 -0
- package/scripts/lib/init.mjs +210 -0
- package/scripts/lib/json-envelope.mjs +190 -0
- package/scripts/lib/templates.mjs +78 -0
- package/scripts/lib/where.mjs +71 -0
- package/scripts/testkit/assert-frontmatter.mjs +171 -0
- package/scripts/testkit/cli-run.mjs +95 -0
- package/scripts/testkit/make-wiki-fixture.mjs +301 -0
- package/scripts/testkit/stub-skill.mjs +107 -0
- package/templates/adrs.llmwiki.layout.yaml +33 -0
- package/templates/plans.llmwiki.layout.yaml +34 -0
- package/templates/regressions.llmwiki.layout.yaml +34 -0
- package/templates/reports.llmwiki.layout.yaml +33 -0
- package/templates/runbooks.llmwiki.layout.yaml +33 -0
- package/templates/sessions.llmwiki.layout.yaml +34 -0
|
@@ -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.
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: recipe-testing
|
|
3
|
+
type: primary
|
|
4
|
+
depth_role: leaf
|
|
5
|
+
focus: "Use the shipped testkit in consumer test suites"
|
|
6
|
+
parents:
|
|
7
|
+
- ../index.md
|
|
8
|
+
tags:
|
|
9
|
+
- testing
|
|
10
|
+
- testkit
|
|
11
|
+
- fixtures
|
|
12
|
+
- consumers
|
|
13
|
+
activation:
|
|
14
|
+
keyword_matches:
|
|
15
|
+
- test harness
|
|
16
|
+
- consumer tests
|
|
17
|
+
- testkit
|
|
18
|
+
- stub skill
|
|
19
|
+
- wiki fixture
|
|
20
|
+
tag_matches:
|
|
21
|
+
- testing
|
|
22
|
+
- testkit
|
|
23
|
+
|
|
24
|
+
generator: "skill-llm-wiki/v1"
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
# Recipe: testing with the shipped testkit
|
|
28
|
+
|
|
29
|
+
## Trigger
|
|
30
|
+
|
|
31
|
+
Your consumer has tests that interact with `skill-llm-wiki` (e.g. your installer refuses without the skill present, your wiki-write workflow post-heal classifies verdicts). Use the shipped testkit instead of hand-rolling fixtures.
|
|
32
|
+
|
|
33
|
+
## Commands to locate the testkit
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
skill-llm-wiki where --json | jq -r '.testkit_dir'
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The returned absolute path contains:
|
|
40
|
+
|
|
41
|
+
- `stub-skill.mjs` — seed a presence-only skill under `.claude/skills/` or `.agents/skills/`.
|
|
42
|
+
- `make-wiki-fixture.mjs` — build a minimal hosted-mode wiki at a temp path using the shipped templates.
|
|
43
|
+
- `assert-frontmatter.mjs` — parse a leaf's frontmatter and assert expected fields.
|
|
44
|
+
- `cli-run.mjs` — spawn the CLI, capture stdout/stderr/status, auto-parse the envelope.
|
|
45
|
+
|
|
46
|
+
> **About `<testkit_dir>` in the code below:** the literal string
|
|
47
|
+
> `<testkit_dir>` is a placeholder. Resolve it at test-load time via
|
|
48
|
+
> the `where` probe, and convert the filesystem path to a `file://`
|
|
49
|
+
> URL before passing it to dynamic `import()` — Node ESM requires
|
|
50
|
+
> this on Windows (where `TESTKIT` looks like `C:\...`) and it is
|
|
51
|
+
> harmless on POSIX. Example:
|
|
52
|
+
>
|
|
53
|
+
> ```js
|
|
54
|
+
> import { spawnSync } from "node:child_process";
|
|
55
|
+
> import { pathToFileURL } from "node:url";
|
|
56
|
+
> const WHERE = JSON.parse(
|
|
57
|
+
> spawnSync("skill-llm-wiki", ["where", "--json"], { encoding: "utf8" }).stdout,
|
|
58
|
+
> );
|
|
59
|
+
> const TESTKIT = WHERE.testkit_dir;
|
|
60
|
+
> const { stubSkill } = await import(
|
|
61
|
+
> pathToFileURL(`${TESTKIT}/stub-skill.mjs`).href,
|
|
62
|
+
> );
|
|
63
|
+
> ```
|
|
64
|
+
>
|
|
65
|
+
> Do not copy the `<testkit_dir>/stub-skill.mjs` string verbatim
|
|
66
|
+
> into your imports — it will not resolve.
|
|
67
|
+
|
|
68
|
+
## Consumer test code
|
|
69
|
+
|
|
70
|
+
### Presence stub (replaces hand-rolled `wikiSkillStub`)
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
import { test } from "node:test";
|
|
74
|
+
import { mkdtempSync } from "node:fs";
|
|
75
|
+
import { tmpdir } from "node:os";
|
|
76
|
+
import { join } from "node:path";
|
|
77
|
+
import { stubSkill } from "<testkit_dir>/stub-skill.mjs";
|
|
78
|
+
|
|
79
|
+
test("installer refuses when skill is absent", async () => {
|
|
80
|
+
const home = mkdtempSync(join(tmpdir(), "test-"));
|
|
81
|
+
// Do NOT call stubSkill — simulate the absent case.
|
|
82
|
+
const r = await runInstaller({ env: { HOME: home } });
|
|
83
|
+
assert.equal(r.status, 1);
|
|
84
|
+
assert.match(r.stderr, /skill-llm-wiki is not installed/);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("installer proceeds when stub is present", async () => {
|
|
88
|
+
const home = mkdtempSync(join(tmpdir(), "test-"));
|
|
89
|
+
await stubSkill({ home });
|
|
90
|
+
const r = await runInstaller({ env: { HOME: home } });
|
|
91
|
+
assert.equal(r.status, 0);
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Fixture wiki (for exercising write workflows)
|
|
96
|
+
|
|
97
|
+
```js
|
|
98
|
+
import { makeWikiFixture } from "<testkit_dir>/make-wiki-fixture.mjs";
|
|
99
|
+
|
|
100
|
+
test("my consumer writes a report leaf in the right shape", async () => {
|
|
101
|
+
const wiki = await makeWikiFixture({
|
|
102
|
+
path: join(tmpdir(), `reports-${Date.now()}`),
|
|
103
|
+
kind: "dated",
|
|
104
|
+
template: "reports",
|
|
105
|
+
seedLeaves: ["2026/04/18/example.md"],
|
|
106
|
+
});
|
|
107
|
+
// Now drive your consumer write code against `wiki.path`.
|
|
108
|
+
await writeMyLeaf(wiki.path, "2026/04/18/new-report.md", "content");
|
|
109
|
+
assert.ok(existsSync(join(wiki.path, "2026/04/18/new-report.md")));
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### CLI run (for end-to-end verdict-handling tests)
|
|
114
|
+
|
|
115
|
+
```js
|
|
116
|
+
import { runCli } from "<testkit_dir>/cli-run.mjs";
|
|
117
|
+
|
|
118
|
+
test("heal on a fresh wiki returns verdict=ok", async () => {
|
|
119
|
+
const wiki = await makeWikiFixture({ path: tmpWiki(), kind: "dated" });
|
|
120
|
+
const r = runCli(["heal", wiki.path, "--json"]);
|
|
121
|
+
assert.equal(r.envelope.verdict, "ok");
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Frontmatter assertions
|
|
126
|
+
|
|
127
|
+
```js
|
|
128
|
+
import { assertFrontmatterShape } from "<testkit_dir>/assert-frontmatter.mjs";
|
|
129
|
+
|
|
130
|
+
test("my consumer writes the expected frontmatter", async () => {
|
|
131
|
+
await writeMyLeaf(wiki.path, "2026/04/18/leaf.md", "content");
|
|
132
|
+
assertFrontmatterShape(join(wiki.path, "2026/04/18/leaf.md"), {
|
|
133
|
+
type: "primary",
|
|
134
|
+
depth_role: "leaf",
|
|
135
|
+
focus: "leaf",
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Failure modes
|
|
141
|
+
|
|
142
|
+
- `testkit_dir` is `null`: you are running against an old skill version without the testkit. Gate on `format_version >= 1` in CI.
|
|
143
|
+
- `stubSkill` fails with "unknown layout": pass one of `"claude-skills"` or `"agents-skills"`.
|
|
144
|
+
- `makeWikiFixture` fails on the template lookup: pass a name matching one of the shipped templates (see [dated-wiki.md](dated-wiki.md) / [subject-wiki.md](subject-wiki.md)).
|
|
145
|
+
|
|
146
|
+
## Do not
|
|
147
|
+
|
|
148
|
+
- Import from `scripts/lib/` in your tests. Those are internal; only `scripts/testkit/` is part of the consumer contract.
|
|
149
|
+
- Hand-roll parallel stub helpers once this testkit exists. Drift between your stub and the skill's canonical shape is a real bug source.
|
package/guide/index.md
CHANGED
|
@@ -74,6 +74,13 @@ entries:
|
|
|
74
74
|
file: "ux/index.md"
|
|
75
75
|
type: index
|
|
76
76
|
focus: User-facing intent resolution and preflight failure messaging.
|
|
77
|
+
- id: consumers
|
|
78
|
+
file: "consumers/index.md"
|
|
79
|
+
type: index
|
|
80
|
+
focus: "Integrating another skill or agent as a consumer of skill-llm-wiki."
|
|
81
|
+
tags:
|
|
82
|
+
- consumers
|
|
83
|
+
- integration
|
|
77
84
|
children:
|
|
78
85
|
- "basics/index.md"
|
|
79
86
|
- "correctness/index.md"
|
|
@@ -83,6 +90,7 @@ children:
|
|
|
83
90
|
- "operations/index.md"
|
|
84
91
|
- "substrate/index.md"
|
|
85
92
|
- "ux/index.md"
|
|
93
|
+
- "consumers/index.md"
|
|
86
94
|
---
|
|
87
95
|
<!-- BEGIN AUTO-GENERATED NAVIGATION -->
|
|
88
96
|
|
|
@@ -110,6 +118,7 @@ children:
|
|
|
110
118
|
| [operations/index.md](operations/index.md) | 📁 index | per-operation phase pipelines for Build, Extend, Validate, Rebuild, Fix, and Join |
|
|
111
119
|
| [substrate/index.md](substrate/index.md) | 📁 index | Decision machinery — rewrite operators and the tiered AI ladder driving them. |
|
|
112
120
|
| [ux/index.md](ux/index.md) | 📁 index | User-facing intent resolution and preflight failure messaging. |
|
|
121
|
+
| [consumers/index.md](consumers/index.md) | 📁 index | Integrating another skill or agent as a consumer of skill-llm-wiki. |
|
|
113
122
|
|
|
114
123
|
<!-- END AUTO-GENERATED NAVIGATION -->
|
|
115
124
|
|
package/guide/ux/user-intent.md
CHANGED
|
@@ -9,7 +9,7 @@ covers:
|
|
|
9
9
|
- the CLI refuses every ambiguous invocation with a structured INT-NN code
|
|
10
10
|
- Claude MUST ask the user before running the skill when intent is unclear
|
|
11
11
|
- ambiguity scenarios each have a fixed resolving flag the user can pick
|
|
12
|
-
- "--json-errors makes the ambiguity body machine-parseable for Claude to read"
|
|
12
|
+
- "--json (canonical) or --json-errors (legacy alias) makes the ambiguity body machine-parseable for Claude to read"
|
|
13
13
|
- "--no-prompt / LLM_WIKI_NO_PROMPT=1 disables interactive fallback; failures become hard errors"
|
|
14
14
|
- never silently default — the cost of a wrong guess is always higher than a clarifying question
|
|
15
15
|
tags:
|
|
@@ -82,11 +82,12 @@ in the wrong place) is always higher than a one-sentence clarifying question.
|
|
|
82
82
|
| INT-12 | Prompt required in non-interactive mode | supply the flag the prompt was asking for, or re-run in a TTY |
|
|
83
83
|
| INT-13 | Unknown `--quality-mode` value | use `tiered-fast` / `claude-first` / `tier0-only` |
|
|
84
84
|
|
|
85
|
-
## `--json
|
|
85
|
+
## `--json` for programmatic consumption
|
|
86
86
|
|
|
87
87
|
When the skill is called from a script or from another Claude session, pass
|
|
88
|
-
`--json
|
|
89
|
-
object on
|
|
88
|
+
`--json` (canonical) on every invocation; `--json-errors` is the legacy alias
|
|
89
|
+
and continues to work. The ambiguity body becomes a single JSON object on
|
|
90
|
+
stderr:
|
|
90
91
|
|
|
91
92
|
```json
|
|
92
93
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ctxr/skill-llm-wiki",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Claude Code skill — build, extend, validate, rebuild, fix, and join LLM wikis from any knowledge corpus. Token-efficient retrieval via hierarchical indices, DAG parents, and deterministic rewrite operators.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,12 +31,16 @@
|
|
|
31
31
|
"LICENSE",
|
|
32
32
|
"CHANGELOG.md",
|
|
33
33
|
"scripts/",
|
|
34
|
-
"guide/"
|
|
34
|
+
"guide/",
|
|
35
|
+
"templates/"
|
|
35
36
|
],
|
|
36
37
|
"ctxr": {
|
|
37
38
|
"type": "skill",
|
|
38
39
|
"target": "folder"
|
|
39
40
|
},
|
|
41
|
+
"bin": {
|
|
42
|
+
"skill-llm-wiki": "scripts/cli.mjs"
|
|
43
|
+
},
|
|
40
44
|
"scripts": {
|
|
41
45
|
"test": "node --test",
|
|
42
46
|
"validate": "node scripts/cli.mjs --version",
|