@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.
- package/CHANGELOG.md +118 -0
- package/README.md +2 -2
- package/SKILL.md +7 -0
- package/guide/cli.md +6 -4
- 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/substrate/operators.md +1 -1
- package/guide/substrate/tiered-ai.md +6 -5
- package/guide/ux/user-intent.md +6 -5
- package/package.json +9 -3
- package/scripts/cli.mjs +565 -15
- package/scripts/lib/balance.mjs +579 -0
- package/scripts/lib/cluster-detect.mjs +482 -4
- package/scripts/lib/contract.mjs +257 -0
- package/scripts/lib/decision-log.mjs +121 -15
- package/scripts/lib/heal.mjs +167 -0
- package/scripts/lib/init.mjs +210 -0
- package/scripts/lib/intent.mjs +370 -4
- package/scripts/lib/join-constants.mjs +22 -0
- package/scripts/lib/join.mjs +917 -0
- package/scripts/lib/json-envelope.mjs +190 -0
- package/scripts/lib/nest-applier.mjs +395 -32
- package/scripts/lib/operators.mjs +472 -38
- package/scripts/lib/orchestrator.mjs +419 -12
- package/scripts/lib/root-containment.mjs +351 -0
- package/scripts/lib/similarity-cache.mjs +115 -20
- package/scripts/lib/similarity.mjs +11 -0
- package/scripts/lib/soft-dag.mjs +726 -0
- package/scripts/lib/templates.mjs +78 -0
- package/scripts/lib/tiered.mjs +42 -18
- package/scripts/lib/validate.mjs +22 -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,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.
|