@antoneeo/kb-agentic-skill 1.4.7 → 1.5.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 +140 -0
- package/README.md +1 -1
- package/gemini-extension.json +6 -6
- package/package.json +51 -51
- package/scripts/init.js +123 -6
- package/scripts/lib.js +409 -202
- package/skills/kb-agentic-skill/ENFORCEMENT.md +33 -2
- package/skills/kb-agentic-skill/SKILL.md +148 -148
- package/skills/kb-agentic-skill/distillation.md +39 -7
- package/skills/kb-agentic-skill/scripts/sdlc_check.py +189 -15
- package/skills/kb-agentic-skill/templates.md +28 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,146 @@
|
|
|
2
2
|
|
|
3
3
|
Every significant change to this skill is recorded here.
|
|
4
4
|
|
|
5
|
+
## [1.5.0] - 2026-08-25
|
|
6
|
+
|
|
7
|
+
F-036 — the orientation hook installs itself.
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- **`ENFORCEMENT.md` 4 said "wire it on every project" and nothing ever did.** The
|
|
11
|
+
SessionStart orientation hook was real, tested and fail-open, but installing it was a
|
|
12
|
+
manual step, so it was skipped — and a session that never enters Phase 1 explicitly
|
|
13
|
+
then never meets the guide router at all. Field result: an agent worked a governed
|
|
14
|
+
project without invoking the process. `init` now wires the hook itself.
|
|
15
|
+
- **The invariant that looked like it guarded this guarded only prose** — it asserted
|
|
16
|
+
the documentation section exists (`assertIn("## 4. SessionStart hook", t)`) and that
|
|
17
|
+
`orient` runs. It gains a companion asserting the shipped installer actually CALLS the
|
|
18
|
+
writer. Mutation-tested: the first draft asserted the bare symbol and passed on a file
|
|
19
|
+
that imports the writer without invoking it, which is exactly the disabled-installer
|
|
20
|
+
case it exists to catch.
|
|
21
|
+
|
|
22
|
+
### Fixed (also)
|
|
23
|
+
- **ENFORCEMENT 4/2's own worked hook examples named a skill directory that two of
|
|
24
|
+
the three lenses do not install.** kb and mkt both printed
|
|
25
|
+
`.claude/skills/agentic-sdlc/scripts/...`; kb installs under `kb-agentic` and mkt
|
|
26
|
+
under `mkt-agentic-sdlc`, so anyone who copied the snippet got a hook that runs,
|
|
27
|
+
prints `can't open file`, and emits nothing — the wired-and-dead defect this release
|
|
28
|
+
detects, sitting in the instructions that produce it. Four paths corrected, and a new
|
|
29
|
+
invariant derives the expected directory and validator from what the distribution
|
|
30
|
+
actually ships and fails if an example names anything else. Mutation-tested.
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
- **A portability rule the doctrine did not have.** The hook command names a validator,
|
|
34
|
+
and where that validator lives decides which settings file may carry it: a repo that
|
|
35
|
+
vendors the validator gets a repo-relative command in the shared, committed
|
|
36
|
+
`.claude/settings.json`; a normal project gets an absolute path, which goes to the
|
|
37
|
+
git-ignored `.claude/settings.local.json` instead — committing it would hand every
|
|
38
|
+
teammate a hook naming a directory they do not have. `init` picks the file and adds
|
|
39
|
+
the local one to `.gitignore`. ENFORCEMENT 4's own worked example showed an absolute
|
|
40
|
+
path and did not mention the distinction; it now does.
|
|
41
|
+
- **A wired-but-dead hook is detected and reported instead of counted as done.** Found
|
|
42
|
+
in the field: a repository whose hook named a sibling lens's path, so it ran every
|
|
43
|
+
session, printed `can't open file`, and emitted nothing. A bare "is a hook present?"
|
|
44
|
+
check answers "already wired" to that and makes the silence permanent, so the existing
|
|
45
|
+
command's validator path is checked on disk. Never rewritten — it may be hand-tuned —
|
|
46
|
+
but never passed off as working either.
|
|
47
|
+
|
|
48
|
+
### Notes
|
|
49
|
+
- `init` declines rather than guessing: no Python, skill not installed, a settings file
|
|
50
|
+
that is not valid JSON (never rewritten — a merge would discard what is in it), or a
|
|
51
|
+
skill path containing a double quote. Each case prints the snippet to paste.
|
|
52
|
+
- Only Claude Code's hook shape is wired. Codex and Gemini keep the manual snippet: this
|
|
53
|
+
repository has no fixture pinning their schema, and writing a hook file in a shape
|
|
54
|
+
nobody has verified is how the wired-but-dead defect above was born.
|
|
55
|
+
- A blocking `PreToolUse` gate would be real enforcement rather than a nudge, and it is
|
|
56
|
+
refused by the Vision's no-ceremony-ratchet Non-Goal. Recorded in the analysis so the
|
|
57
|
+
next person to have the idea finds the ruling instead of re-deriving it.
|
|
58
|
+
|
|
59
|
+
## [1.4.8] - 2026-08-25
|
|
60
|
+
|
|
61
|
+
F-035 — second field report from the F-029 practitioner. Three defects in the corpus
|
|
62
|
+
letter's own enforcement: the promise is that every provenance is a real file, and two
|
|
63
|
+
of the three mechanisms that should hold it did not run.
|
|
64
|
+
|
|
65
|
+
### Fixed
|
|
66
|
+
- **`prov:` below `GIVEN` was structurally impossible for any claim citing
|
|
67
|
+
`corpus/given/`.** `_note_frontmatter` read frontmatter from the cited file itself. A
|
|
68
|
+
corpus artifact is bytes and carries none — but it *is* a file, so the helper returned
|
|
69
|
+
`{}` rather than `None`, which the caller cannot tell apart from "resolved, field
|
|
70
|
+
absent", and the required-field error fired unconditionally. `DERIVED`, `RULING` and
|
|
71
|
+
`IMPORTED` were unreachable on any `given/` artifact, so every such claim was forced
|
|
72
|
+
to `GIVEN` whatever its real extraction chain. The helper now reads the artifact's
|
|
73
|
+
`.meta.md` **sidecar** when one exists and the cited file otherwise — sidecar-first,
|
|
74
|
+
not "non-`.md`", so a verbatim `.md` source stored in `given/` resolves the same way
|
|
75
|
+
as a `.txt` extraction while a `corpus/notes/*.md` note still resolves to its own
|
|
76
|
+
frontmatter. It also returns the path it actually read, and the three findings now
|
|
77
|
+
name that file instead of saying "note" about a sidecar.
|
|
78
|
+
- **`original_path:` was never verified.** Zero reads of it in either script: a folder
|
|
79
|
+
of originals could be moved and sixteen sidecars go dangling behind a green run. It is
|
|
80
|
+
now checked for resolution — a **warning**, never an error, because a bundle carries
|
|
81
|
+
artifacts and sidecars and never the originals, so after an import it dangles
|
|
82
|
+
legitimately. Absolute paths are tested as written; a relative one is tried against
|
|
83
|
+
the docs root's parent and against the docs root, and warns only if neither resolves.
|
|
84
|
+
`original_sha256` stays unverified for the reason already stated — we do not hold the
|
|
85
|
+
bytes — and `distillation.md` now states the two limits separately, because they were
|
|
86
|
+
never in the same position.
|
|
87
|
+
- **The duplicate-id error named the wrong defect.** "uniqueness is global across
|
|
88
|
+
topics/" describes a copied row; the reporter's two rows were hand-written and
|
|
89
|
+
different, and had collided in `kb_claim_id`, which hashes `path#locator#qty` and
|
|
90
|
+
excludes the text on purpose. The check now branches on the **claim text** — same text
|
|
91
|
+
is a copied row, different text is the collision — and the collision message explains
|
|
92
|
+
why the id cannot separate them and prescribes the two legitimate repairs (widen a
|
|
93
|
+
locator, or merge the rows), explicitly ruling out editing the qty to break the tie.
|
|
94
|
+
The id function itself is unchanged and its two constants are now pinned by tests:
|
|
95
|
+
`portability.md`'s cross-project de-duplication rests on them
|
|
96
|
+
(`ADR_2026-08-25_claim_id_collision.md`).
|
|
97
|
+
|
|
98
|
+
### Added
|
|
99
|
+
- **A `GIVEN` row whose artifact declares a weaker chain now warns.** `provenance:` was
|
|
100
|
+
written into every sidecar and read by nothing; this is its first consumer. A
|
|
101
|
+
conformant corpus (`provenance: GIVEN`, or no field) is silent. **Scope, stated so it
|
|
102
|
+
is not mistaken for more:** it reads the row's first source and it reads the *field* —
|
|
103
|
+
a sidecar declaring `provenance: GIVEN` while its prose says "transcribed from a
|
|
104
|
+
photograph" stays silent, because prose is not machine-readable. The pair's larger
|
|
105
|
+
half is the fix above, which is what makes the honest declaration possible at all.
|
|
106
|
+
|
|
107
|
+
### Fixed (second review round)
|
|
108
|
+
- **`original_path` classification was wrong on Windows, and the documented example was
|
|
109
|
+
the trigger.** `Path.is_absolute()` is False for a rooted-but-driveless path — exactly
|
|
110
|
+
the `/vault/manuals/xyz.pdf` form these templates print — so it was joined under the
|
|
111
|
+
docs root and silently rewritten onto the docs root's *drive*. That produced a warning
|
|
112
|
+
quoting a path nobody wrote (twice, since both candidates collapsed to it), could hide
|
|
113
|
+
a genuinely dangling pointer behind whatever happened to sit there, and warned falsely
|
|
114
|
+
whenever the corpus and the original lived on different drives. Rootedness is now
|
|
115
|
+
decided by `ntpath`/`posixpath`, the candidate list is de-duplicated, and the
|
|
116
|
+
backslash normalisation is an *additional* candidate rather than a rewrite (a
|
|
117
|
+
backslash is a legal character in a POSIX filename).
|
|
118
|
+
- **The pointer probe could crash the run.** `Path.exists()` re-raises `PermissionError`
|
|
119
|
+
and `ENAMETOOLONG`; `original_path` points outside the docs root by design, so the
|
|
120
|
+
validator must survive whatever is out there. It is now `is_file()` (a directory is not
|
|
121
|
+
a document) inside an `OSError` guard.
|
|
122
|
+
- **`_note_frontmatter` could read one file outside the docs root.** A source cell of
|
|
123
|
+
the form `#L1-2` — no path before the locator — made `confine_under` return the docs
|
|
124
|
+
root itself, and the sidecar name was then built from `base.parent`, i.e.
|
|
125
|
+
`<docs-root>.meta.md`. Both that case and a sidecar orphaned by a deleted artifact now
|
|
126
|
+
return "unresolved", which the source loop already reports.
|
|
127
|
+
- **The collision message asserted something it had not checked.** It claimed the two
|
|
128
|
+
rows "cite the same span with the same qty" — true only for a *computed* id. Two rows
|
|
129
|
+
sharing a hand-typed or stale id while citing different spans now get their own
|
|
130
|
+
message, naming the real repair (`claim-id --fill`).
|
|
131
|
+
- Frontmatter resolution is memoized per cited path: it now runs for every row, `GIVEN`
|
|
132
|
+
included, and a ledger citing one artifact from eighty rows was re-reading its sidecar
|
|
133
|
+
eighty times.
|
|
134
|
+
- The worked sidecar example carried trailing `# comment`s. The frontmatter reader is a
|
|
135
|
+
line regex and does not strip them, so the comment landed inside the value — harmless
|
|
136
|
+
while nothing read `original_path`, and a guaranteed false warning once something did.
|
|
137
|
+
|
|
138
|
+
### Known limits, stated
|
|
139
|
+
- `ELICITED` is accepted with **no required field at all** — it is named in the
|
|
140
|
+
provenance branch but has no check below it. Found while fixing the above, deliberately
|
|
141
|
+
not repaired here: adding one is a new gate, not a repair.
|
|
142
|
+
- `corpus` and `graph` still have no `--errors-only`; on a corpus with many in-progress
|
|
143
|
+
artifacts the coverage warnings bury a lone error. Ranked last by the reporter.
|
|
144
|
+
|
|
5
145
|
## [1.4.7] - 2026-08-06
|
|
6
146
|
|
|
7
147
|
### Fixed
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ The sibling of [`@antoneeo/agentic-sdlc-skill`](https://www.npmjs.com/package/@a
|
|
|
8
8
|
|
|
9
9
|
Two axes are kept apart on purpose — **abstraction** (a topic made of topics: the graph edges) and **certainty** (how strongly the corpus supports a statement: the claim rows).
|
|
10
10
|
|
|
11
|
-
1. **Intake — the source becomes untouchable.** Every document enters verbatim under `corpus/given/`, content-addressed by a raw-byte digest. A new version is appended with `supersedes:`, never overwritten. Non-text files get a **stored canonical extraction** (`.txt`, registered extractor) — those are the bytes locators point at, so an id stays valid across re-ingestion. On a large binary corpus the extraction **is** the artifact: the digest moves onto it (immutability still enforced, on the bytes that actually matter) and the original stays where it lives, recorded as `original_path:` + `original_sha256:`. 233 MB of manuals do not enter your repository to protect bytes no locator addresses — and the two recorded fields are honest about their
|
|
11
|
+
1. **Intake — the source becomes untouchable.** Every document enters verbatim under `corpus/given/`, content-addressed by a raw-byte digest. A new version is appended with `supersedes:`, never overwritten. Non-text files get a **stored canonical extraction** (`.txt`, registered extractor) — those are the bytes locators point at, so an id stays valid across re-ingestion. On a large binary corpus the extraction **is** the artifact: the digest moves onto it (immutability still enforced, on the bytes that actually matter) and the original stays where it lives, recorded as `original_path:` + `original_sha256:`. 233 MB of manuals do not enter your repository to protect bytes no locator addresses — and the two recorded fields are honest about their limits, which differ: `original_sha256` checks nothing on its own (we do not hold the bytes), while `original_path` is verified to still resolve, and warns when it does not — a folder you reorganised should not leave sixteen sidecars pointing at nothing behind a green run. Spoken input is a note with `origin: elicited`; a synthesis carries `derived_from:`; your decision carries `basis:`. A note with none of the three is model knowledge dressed as a source, and the validator rejects it.
|
|
12
12
|
|
|
13
13
|
2. **Extraction — the unit is the claim.** Rows of `id | claim | valid | qty | about | source | prov | state`. The **id hashes the location and the quantity, never the text**, so an LLM rephrasing mints no new identity. The locator (`p=17@412-509`) is verified: the validator opens the extraction and checks the span exists — and `anchor <path> <phrase>` produces it for you, matching spaces as `\s+` because a PDF extraction breaks phrases mid-line. **Gates are extracted alongside powers**: for every row saying what something can do, the source is asked what must hold first — default-off, licence tier, version floor, dependency — because "yes, supported" without the gate is a plan that fails on site. The rule is *ask*, never *produce*: a source that states no gate yields no row. Validity scopes are half-open ("until March" and "from March" do not conflict). Quantities are typed — mixed kinds or currencies **refuse to sum**. What the source does not assert becomes a `gaps:` line, never a claim.
|
|
14
14
|
|
package/gemini-extension.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "kb-agentic-skill",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Knowledge-Base & Document-First protocol with risk triage, Vision governance, signal distillation and optional devPNT integration.",
|
|
5
|
-
"author": "Antonio Pinto (https://github.com/Antoneeo)"
|
|
6
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "kb-agentic-skill",
|
|
3
|
+
"version": "1.5.0",
|
|
4
|
+
"description": "Knowledge-Base & Document-First protocol with risk triage, Vision governance, signal distillation and optional devPNT integration.",
|
|
5
|
+
"author": "Antonio Pinto (https://github.com/Antoneeo)"
|
|
6
|
+
}
|
package/package.json
CHANGED
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@antoneeo/kb-agentic-skill",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Knowledge-Base & Document-First protocol for Claude Code, Gemini CLI, Google Antigravity and Codex with risk triage, Vision governance, signal distillation and optional devPNT integration.",
|
|
5
|
-
"keywords": [
|
|
6
|
-
"claude-code",
|
|
7
|
-
"claude-skill",
|
|
8
|
-
"gemini-cli",
|
|
9
|
-
"skill",
|
|
10
|
-
"knowledge-base",
|
|
11
|
-
"documentation",
|
|
12
|
-
"ai-agent"
|
|
13
|
-
],
|
|
14
|
-
"author": "Antonio Pinto (https://github.com/Antoneeo)",
|
|
15
|
-
"license": "MIT",
|
|
16
|
-
"publishConfig": {
|
|
17
|
-
"access": "public"
|
|
18
|
-
},
|
|
19
|
-
"bin": {
|
|
20
|
-
"kb-agentic-init": "scripts/init.js",
|
|
21
|
-
"kb-agentic-install-skill": "scripts/postinstall.js"
|
|
22
|
-
},
|
|
23
|
-
"scripts": {
|
|
24
|
-
"postinstall": "node scripts/postinstall.js",
|
|
25
|
-
"preuninstall": "node scripts/preuninstall.js"
|
|
26
|
-
},
|
|
27
|
-
"files": [
|
|
28
|
-
"skills/kb-agentic-skill/SKILL.md",
|
|
29
|
-
"skills/kb-agentic-skill/templates.md",
|
|
30
|
-
"skills/kb-agentic-skill/taxonomy.md",
|
|
31
|
-
"skills/kb-agentic-skill/guides.md",
|
|
32
|
-
"skills/kb-agentic-skill/vision.md",
|
|
33
|
-
"skills/kb-agentic-skill/distillation.md",
|
|
34
|
-
"skills/kb-agentic-skill/reconciliation.md",
|
|
35
|
-
"skills/kb-agentic-skill/elicitation.md",
|
|
36
|
-
"skills/kb-agentic-skill/review.md",
|
|
37
|
-
"skills/kb-agentic-skill/dispatch.md",
|
|
38
|
-
"skills/kb-agentic-skill/routing.md",
|
|
39
|
-
"skills/kb-agentic-skill/portability.md",
|
|
40
|
-
"skills/kb-agentic-skill/ENFORCEMENT.md",
|
|
41
|
-
"skills/kb-agentic-skill/scripts/sdlc_check.py",
|
|
42
|
-
"skills/kb-agentic-skill/scripts/sdlc_core.py",
|
|
43
|
-
"gemini-extension.json",
|
|
44
|
-
"README.md",
|
|
45
|
-
"CHANGELOG.md",
|
|
46
|
-
"scripts/lib.js",
|
|
47
|
-
"scripts/init.js",
|
|
48
|
-
"scripts/postinstall.js",
|
|
49
|
-
"scripts/preuninstall.js"
|
|
50
|
-
]
|
|
51
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@antoneeo/kb-agentic-skill",
|
|
3
|
+
"version": "1.5.0",
|
|
4
|
+
"description": "Knowledge-Base & Document-First protocol for Claude Code, Gemini CLI, Google Antigravity and Codex with risk triage, Vision governance, signal distillation and optional devPNT integration.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"claude-code",
|
|
7
|
+
"claude-skill",
|
|
8
|
+
"gemini-cli",
|
|
9
|
+
"skill",
|
|
10
|
+
"knowledge-base",
|
|
11
|
+
"documentation",
|
|
12
|
+
"ai-agent"
|
|
13
|
+
],
|
|
14
|
+
"author": "Antonio Pinto (https://github.com/Antoneeo)",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"bin": {
|
|
20
|
+
"kb-agentic-init": "scripts/init.js",
|
|
21
|
+
"kb-agentic-install-skill": "scripts/postinstall.js"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"postinstall": "node scripts/postinstall.js",
|
|
25
|
+
"preuninstall": "node scripts/preuninstall.js"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"skills/kb-agentic-skill/SKILL.md",
|
|
29
|
+
"skills/kb-agentic-skill/templates.md",
|
|
30
|
+
"skills/kb-agentic-skill/taxonomy.md",
|
|
31
|
+
"skills/kb-agentic-skill/guides.md",
|
|
32
|
+
"skills/kb-agentic-skill/vision.md",
|
|
33
|
+
"skills/kb-agentic-skill/distillation.md",
|
|
34
|
+
"skills/kb-agentic-skill/reconciliation.md",
|
|
35
|
+
"skills/kb-agentic-skill/elicitation.md",
|
|
36
|
+
"skills/kb-agentic-skill/review.md",
|
|
37
|
+
"skills/kb-agentic-skill/dispatch.md",
|
|
38
|
+
"skills/kb-agentic-skill/routing.md",
|
|
39
|
+
"skills/kb-agentic-skill/portability.md",
|
|
40
|
+
"skills/kb-agentic-skill/ENFORCEMENT.md",
|
|
41
|
+
"skills/kb-agentic-skill/scripts/sdlc_check.py",
|
|
42
|
+
"skills/kb-agentic-skill/scripts/sdlc_core.py",
|
|
43
|
+
"gemini-extension.json",
|
|
44
|
+
"README.md",
|
|
45
|
+
"CHANGELOG.md",
|
|
46
|
+
"scripts/lib.js",
|
|
47
|
+
"scripts/init.js",
|
|
48
|
+
"scripts/postinstall.js",
|
|
49
|
+
"scripts/preuninstall.js"
|
|
50
|
+
]
|
|
51
|
+
}
|
package/scripts/init.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { execSync } = require('child_process');
|
|
6
|
-
const { SKILL_SOURCE, INSTALLED_SKILL_NAME, SELF_LENS, SIBLING_LENSES, CLIENTS, clientDetected, skillTarget, loadTemplates, templateFor } = require('./lib');
|
|
6
|
+
const { SKILL_SOURCE, INSTALLED_SKILL_NAME, SELF_LENS, SIBLING_LENSES, CLIENTS, clientDetected, skillTarget, wireOrientHook, loadTemplates, templateFor } = require('./lib');
|
|
7
7
|
|
|
8
8
|
const cwd = process.cwd();
|
|
9
9
|
|
|
@@ -149,8 +149,22 @@ const protocolFiles = {
|
|
|
149
149
|
// written aside and merged by hand.
|
|
150
150
|
let protocolPreexisting = false;
|
|
151
151
|
|
|
152
|
-
|
|
153
|
-
|
|
152
|
+
// Probed ONCE and reused by section 6c: re-filtering CLIENTS there would
|
|
153
|
+
// run every client's detection a second time.
|
|
154
|
+
const detectedClients = CLIENTS.filter(clientDetected);
|
|
155
|
+
const PYTHON_CANDIDATES = ['python', 'python3', 'py'];
|
|
156
|
+
const detectedPython = (() => {
|
|
157
|
+
for (const py of PYTHON_CANDIDATES) {
|
|
158
|
+
try {
|
|
159
|
+
execSync(`${py} --version`, { stdio: 'ignore' });
|
|
160
|
+
return py;
|
|
161
|
+
} catch (e) { /* try the next interpreter */ }
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
})();
|
|
165
|
+
|
|
166
|
+
for (const client of detectedClients) {
|
|
167
|
+
{
|
|
154
168
|
console.log(`✅ ${client.label} detected.`);
|
|
155
169
|
const created = writeIfNotExists(protocolFiles[client.key], protocolContent, `${client.label} protocol pointer`);
|
|
156
170
|
if (!created) protocolPreexisting = true;
|
|
@@ -214,15 +228,118 @@ Delete it once the merge is done.
|
|
|
214
228
|
}
|
|
215
229
|
}
|
|
216
230
|
|
|
231
|
+
|
|
232
|
+
// 6c. SessionStart orientation hook (F-036).
|
|
233
|
+
// ENFORCEMENT.md §4 asks for this on every project with a docs root and Python.
|
|
234
|
+
// It was a manual step until now, so it was skipped -- and a session that never
|
|
235
|
+
// enters Phase 1 explicitly then never meets the guide router at all.
|
|
236
|
+
|
|
237
|
+
// devPNT projects want `--hybrid`: without it the hook reports audit-plan
|
|
238
|
+
// staleness that devPNT/KL owns, and noise at every session start is how a
|
|
239
|
+
// session-start message stops being read.
|
|
240
|
+
const hybridProject = fs.existsSync(path.join(cwd, '.devpnt'));
|
|
241
|
+
|
|
242
|
+
// Only touch .gitignore when the wiring landed in the machine-specific file:
|
|
243
|
+
// if that file is not ignored, the absolute path reaches teammates anyway and
|
|
244
|
+
// the whole reason for choosing it is undone. Append-only and marker-guarded,
|
|
245
|
+
// exactly like gitattributes() above. Wrapped, because the installer has already
|
|
246
|
+
// written the seed files and must not die here.
|
|
247
|
+
const ignoreLocalSettings = () => {
|
|
248
|
+
const marker = '.claude/settings.local.json';
|
|
249
|
+
if (!fs.existsSync(path.join(cwd, '.git'))) return;
|
|
250
|
+
const filePath = path.join(cwd, '.gitignore');
|
|
251
|
+
try {
|
|
252
|
+
const current = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : '';
|
|
253
|
+
// Line-wise, not substring: a `!.claude/settings.local.json` un-ignore would
|
|
254
|
+
// satisfy `includes()` and leave the machine-specific path committed.
|
|
255
|
+
if (current.split(/\r?\n/).some((l) => l.trim() === marker)) return;
|
|
256
|
+
fs.writeFileSync(filePath, current + [
|
|
257
|
+
'',
|
|
258
|
+
'# agentic-sdlc: the orientation hook here names a path on THIS machine.',
|
|
259
|
+
marker,
|
|
260
|
+
'',
|
|
261
|
+
].join(EOL), 'utf8');
|
|
262
|
+
console.log(`OK .gitignore: ${marker} ignored (it names a machine-specific path).`);
|
|
263
|
+
console.log(' If it is already tracked, run: git rm --cached ' + marker);
|
|
264
|
+
} catch (e) {
|
|
265
|
+
console.log(`⚠️ Could not update .gitignore (${e.message}). Add "${marker}" by hand.`);
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const claudeClient = detectedClients.find((c) => c.key === 'claude');
|
|
270
|
+
const otherClients = detectedClients.filter((c) => c.key !== 'claude');
|
|
271
|
+
if (!claudeClient) {
|
|
272
|
+
console.log('ℹ️ Claude Code not detected: SessionStart orientation hook not wired.');
|
|
273
|
+
} else {
|
|
274
|
+
const r = wireOrientHook({
|
|
275
|
+
cwd, client: claudeClient, python: detectedPython,
|
|
276
|
+
hybrid: hybridProject, docsLabel: 'ai_docs',
|
|
277
|
+
});
|
|
278
|
+
switch (r.code) {
|
|
279
|
+
case 'wired':
|
|
280
|
+
console.log(`🪝 Wired the SessionStart orientation hook in .claude/${r.file}.`);
|
|
281
|
+
if (r.local) {
|
|
282
|
+
console.log(' It names a path on THIS machine, so it went to the git-ignored');
|
|
283
|
+
console.log(' file: each teammate runs init once to get their own.');
|
|
284
|
+
ignoreLocalSettings();
|
|
285
|
+
}
|
|
286
|
+
break;
|
|
287
|
+
case 'already':
|
|
288
|
+
console.log(`⏭️ SessionStart orientation hook already wired (.claude/${r.file}).`);
|
|
289
|
+
if (r.file === 'settings.local.json') ignoreLocalSettings();
|
|
290
|
+
break;
|
|
291
|
+
case 'broken':
|
|
292
|
+
console.log(`⚠️ The SessionStart orientation hook in .claude/${r.file} is BROKEN —`);
|
|
293
|
+
console.log(' its validator does not resolve, so it has been emitting nothing:');
|
|
294
|
+
console.log(` ${r.existing}`);
|
|
295
|
+
console.log(' Not overwritten (it may be hand-tuned). Correct it to:');
|
|
296
|
+
console.log(` ${r.command}`);
|
|
297
|
+
break;
|
|
298
|
+
case 'unverifiable':
|
|
299
|
+
console.log(`ℹ️ A SessionStart orientation hook exists in .claude/${r.file} but names`);
|
|
300
|
+
console.log(' no validator this installer recognises, so it was left alone:');
|
|
301
|
+
console.log(` ${r.existing}`);
|
|
302
|
+
break;
|
|
303
|
+
case 'malformed':
|
|
304
|
+
console.log(`⚠️ .claude/${r.file} has a shape this installer will not rewrite`);
|
|
305
|
+
console.log(` (${r.why}): left untouched. Add the hook by hand — command:`);
|
|
306
|
+
console.log(` ${JSON.stringify(r.command)}`);
|
|
307
|
+
break;
|
|
308
|
+
case 'write-failed':
|
|
309
|
+
console.log(`⚠️ Could not write .claude/${r.file} (${r.error}). Add by hand:`);
|
|
310
|
+
console.log(` ${JSON.stringify(r.command)}`);
|
|
311
|
+
break;
|
|
312
|
+
case 'no-python':
|
|
313
|
+
console.log('ℹ️ Python not found: SessionStart orientation hook not wired.');
|
|
314
|
+
break;
|
|
315
|
+
case 'no-validator':
|
|
316
|
+
console.log('ℹ️ Skill not installed yet: orientation hook not wired. Install it');
|
|
317
|
+
console.log(' (below), then re-run init.');
|
|
318
|
+
break;
|
|
319
|
+
case 'unsafe-path':
|
|
320
|
+
console.log('⚠️ The validator path contains a character that cannot be placed in a');
|
|
321
|
+
console.log(` hook command safely: ${r.validator}`);
|
|
322
|
+
console.log(' Refusing to build one. Wire it by hand (ENFORCEMENT.md §4).');
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
for (const c of otherClients) {
|
|
327
|
+
// Said out loud rather than skipped in silence: ENFORCEMENT.md §4's manual
|
|
328
|
+
// snippet is these clients' only route, and a silent skip is what let the
|
|
329
|
+
// "documented default nobody installs" defect live in the first place.
|
|
330
|
+
console.log(`ℹ️ ${c.label}: hook not wired (only Claude Code's shape is verified) —`);
|
|
331
|
+
console.log(' wire it by hand from ENFORCEMENT.md §4 if that client supports it.');
|
|
332
|
+
}
|
|
333
|
+
|
|
217
334
|
// 7. Generate ai_docs/INDEX.md
|
|
218
335
|
const validator = path.join(SKILL_SOURCE, 'scripts', 'sdlc_check.py');
|
|
219
336
|
let indexed = false;
|
|
220
|
-
|
|
337
|
+
// detectedPython is probed once, above: one answer for the hook and for this.
|
|
338
|
+
if (detectedPython) {
|
|
221
339
|
try {
|
|
222
|
-
execSync(`${
|
|
340
|
+
execSync(`${detectedPython} "${validator}" index --root "${cwd}"`, { stdio: 'ignore' });
|
|
223
341
|
console.log('📇 Generated ai_docs/INDEX.md (document manifest).');
|
|
224
342
|
indexed = true;
|
|
225
|
-
break;
|
|
226
343
|
} catch (e) { /* try next */ }
|
|
227
344
|
}
|
|
228
345
|
if (!indexed) {
|