@antoneeo/agentic-sdlc-skill 1.43.1 → 1.48.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 +59 -0
- package/README.md +3 -3
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/scripts/lib.js +109 -46
- package/scripts/postinstall.js +11 -1
- package/scripts/preuninstall.js +17 -1
- package/skills/agentic-sdlc-skill/ENFORCEMENT.md +59 -8
- package/skills/agentic-sdlc-skill/SKILL.md +9 -7
- package/skills/agentic-sdlc-skill/docs_site/app.js +9 -3
- package/skills/agentic-sdlc-skill/guides.md +7 -1
- package/skills/agentic-sdlc-skill/memory.md +1 -1
- package/skills/agentic-sdlc-skill/review.md +28 -3
- package/skills/agentic-sdlc-skill/routing.md +4 -0
- package/skills/agentic-sdlc-skill/scripts/docs_site.py +94 -8
- package/skills/agentic-sdlc-skill/scripts/knowledge.py +519 -17
- package/skills/agentic-sdlc-skill/scripts/sdlc_core.py +581 -70
- package/skills/agentic-sdlc-skill/templates.md +13 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,65 @@
|
|
|
2
2
|
|
|
3
3
|
Tutte le modifiche significative a questa skill saranno documentate in questo file.
|
|
4
4
|
|
|
5
|
+
## [1.48.0] - 2026-10-23
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- `guides.md`: a document snapshot is verbatim except for credentials and personal or confidential data the guide does not need, which become `[REDACTED: <kind>]` before the snapshot is written; the guide lists the kinds in `redacted_at_intake:`.
|
|
9
|
+
- The session-start orientation (`orient`) now fits under Claude Code's 10,000-character hook limit, even in a project with many documents. Above that limit Claude Code saves the output to a file and shows the agent only the first 2,000 characters, so the guide router never reached the agent. The output is now at most 9,500 characters, including the knowledge layer's lines, and in this order: the triage line, the guide router (one line per guide), the workstream registry (one line per workstream, then the project-wide notes), then the reading guide (README). The manifest (INDEX) and the full files are listed as pointers. A section that does not fit is cut at a line, with a pointer to its file.
|
|
10
|
+
- The per-turn `remind` line also tells the agent, above L1 and when sibling lenses are installed, to choose the lens with `routing.md` and declare it. The line's limit rises from 500 to 600 characters (`vision/rulings.md` r32).
|
|
11
|
+
- `docs_site.py` adds `.docs-site/` to the project's `.gitignore` on its first run in a git repository, so the site's copy of the documents is not committed. It used to print a note only. An entry already in the file is never changed; when git still does not ignore the folder, the note is printed as before.
|
|
12
|
+
- `SKILL.md` names the docs site in the words users use ("docs site", "minisito", "documentation site", "project site"), so a request for it runs `docs_site.py` instead of building a page by hand.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- `check` refuses, in every lens, a line shaped like a credential (a password, token or key with a real value, a private-key header, a password in a URL, a bearer token) in a `corpus/given/` file or in the snapshot of a `source_kind: document` guide. The message names the file, the lines and the kinds, never the value. A human clears a false positive with `credential_scan: reviewed — <reason>` (in the sidecar, or in the guide). A project whose stored sources already hold such a line fails `check` until each is redacted or reviewed (`vision/rulings.md` r35a).
|
|
16
|
+
- `adopt`: a dry run listing the document guides that rest on `.sources/` snapshots and what moving each into `corpus/given/` involves. It writes nothing.
|
|
17
|
+
- `check` warns when the same text is stored both as a guide snapshot and as a corpus artifact.
|
|
18
|
+
- `check` also warns on a `corpus/given/` file with no sidecar, accepts a withdrawn artifact's tombstone sidecar (no bytes, no digest), and, when an `original_path:` is a URI, names `original_ref:` for non-local identifiers. A password inside a URL in that warning is masked.
|
|
19
|
+
- Shared claim checks (`check`): a claim's `about` cell may hold several relations separated by `; `, and one reserved predicate, `not-yet-honored-by -> ANALYSIS_<name>`, marks a specification claim the code does not yet honor. `check` refuses the marker when that analysis is missing from `solutions/` or cites neither the claim id nor the id of a row the claim succeeds. `export` drops the markers and says so in one line. Nothing lists or counts claims by the marker (`vision/rulings.md` r33). `memory.md`: a marked claim is cited as not yet true of the code, naming its analysis.
|
|
20
|
+
- Shared claim checks (`check`): the `not-yet-honored-by` marker now works in Hybrid projects (devPNT present). It may name `SHADOW_<doc_key>`; `check` reads the newest `solutions/SHADOW_<doc_key>_vX.Y.md` (versions compared number by number), which must exist and cite the claim id or a predecessor's. A missing shadow is an error that says to export it, and names a file of that key whose name lacks the `_vX.Y` suffix. `check` behaves the same with or without `--hybrid`; export drops both target forms (`vision/rulings.md` r36). New shared helper `sdlc_core.newest_shadow`. `memory.md`: a marked claim is cited naming its target. `SKILL.md` Write Triggers: the marker row no longer says "(Standalone)"; in Hybrid the claim id goes in the E-ISP/E-TDD and its exported shadow.
|
|
21
|
+
- Write Triggers: a Standalone L3 that defers fixing code which contradicts a `topics/` claim marks the claim and cites its id in its ANALYSIS. The L2/L3 change that makes the code honor the claim removes the marker; L1 is exempt.
|
|
22
|
+
- `verdict --hook`, wired by default on Claude Code: an Edit, Write, MultiEdit or NotebookEdit is refused, with one line saying how to unblock, when the session declared no guide-router verdict (or `Level: L1`), or never read a guide its verdict names. Silent in projects without guides, for subagents and whenever it cannot tell. Remove the hook entry to opt out (`ENFORCEMENT.md` §3, `vision/rulings.md` r34).
|
|
23
|
+
- `orient` adds a note when one client runs it from two settings entries, which means each session gets the orientation twice. Claude Code and Codex are counted separately, an identical command counts once, and an entry whose validator is gone is not counted.
|
|
24
|
+
- `orient` adds a line naming the sibling lenses installed beside the skill or in the user's Claude skills directory, and says to choose the lens with `routing.md`. The family list is read from `routing.md` §0, as the installer does.
|
|
25
|
+
- `routing.md` §2 gains worked verdicts for four mixed requests: an interface spec written for another team, the requirements another team must build, a guide whose source spec changed, and code that diverges from a supplied spec.
|
|
26
|
+
- The L3 ANALYSIS template gains a `## Guide check` section: two lines answering the Phase-3 question, whether a CURRENT guide covers what the change touches, and whether understanding it forced a model of a complex component that no guide covers. `validate` adds an advisory when an L3 analysis started on or after 2026-10-24 lacks the section or leaves it unfilled; older analyses are not checked.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
- Uninstalling now also removes the per-turn `remind` hook entry. Left behind, it pointed at a deleted file and Python exited 2, which blocks the prompt.
|
|
30
|
+
|
|
31
|
+
## [1.47.0] - 2026-10-19
|
|
32
|
+
|
|
33
|
+
Includes everything listed under [1.46.0], which was tagged but never published to npm.
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
- `mark <paths> --note "<what was done>"` writes a dated entry `**YYYY-MM-DD — <what was done>.**` at the top of `audit/project_notes.md`. A release or work outside an open unit used to leave no dated event, because the date had to be copied there by hand. Without `--note`, and with no open unit, `mark` says so.
|
|
37
|
+
- `validate`: an advisory for a dated entry in `project_notes.md` whose bold does not pair; the chronology then showed the label running into the body.
|
|
38
|
+
- Docs site chronology: a Diary entry's label carries its unit id ("F-074 — closed"), so it reads on its own.
|
|
39
|
+
- `stale` no longer counts files inside the docs root (`ai_docs/`, `mkt_docs/`) as drift of any area, and `mark` refuses to record it: `validate` already checks the docs root, and every document edit used to leave `check` NOT CLEAN until a redundant re-mark.
|
|
40
|
+
|
|
41
|
+
## [1.46.0] - 2026-10-19
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
- Docs site: `docs_site.py --zip <file>` also writes the site as one .zip to share; unzipped, `index.html` opens on any system. A zip made by hand with Windows PowerShell 5.1 `Compress-Archive` stores `data\core.js`, and on Linux or macOS the page then finds no data; `--zip` always writes `/` names. It refuses a target inside the docs root or the site folder. The `SKILL.md` line names it.
|
|
45
|
+
|
|
46
|
+
## [1.45.0] - 2026-10-19
|
|
47
|
+
|
|
48
|
+
Includes everything listed under [1.44.0], which was tagged but never published to npm.
|
|
49
|
+
|
|
50
|
+
### Added
|
|
51
|
+
- Docs site: each card in Open work and each feature in Features links its merge review and its end-to-end test script, when they exist. They were reachable only inside a feature's chain page; who merges looks in Open work while the work is open, who tests looks in Features after it closed.
|
|
52
|
+
|
|
53
|
+
### Changed
|
|
54
|
+
- Docs site: Features are listed newest first by their declared start date; those without a start date come last.
|
|
55
|
+
|
|
56
|
+
## [1.44.0] - 2026-10-18
|
|
57
|
+
|
|
58
|
+
### Changed
|
|
59
|
+
- F-071: a review weighs effort against what the owner asked for. In a design review, each outcome the owner named (the opening request, any entry widening scope, an approved north star) is a row of the conformance statement, saying where the design serves it and what share of the design goes to it; an outcome no section serves, or one served by a minor share while a secondary concern takes the bulk, is a finding. `review.md` §Reviewing.
|
|
60
|
+
- F-071: a review runs where something is left to judge. At level 2 the closure review is due only on a security-sensitive area the lens's Rule Zero names, or at the owner's request; otherwise it does not run and logs no row. After a design or closure PASS, a delta made only of version and release metadata, regenerated indexes and audit-mark records, tool-verified copies of a shared file, and CHANGELOG lines adding no claim is not reviewed: its own check runs and a `NOT DUE` row records it. README or overview prose, an E2E script, a rulings row, doctrine, behaviour, tests and any correction of a review finding are still reviewed. `review.md` §When a review is due.
|
|
61
|
+
- `sdlc_check.py benefit` counts `NOT DUE` rows apart, in no review or findings figure.
|
|
62
|
+
- F-071 (this lens): when the owner's words for an outcome name a comparative effect (an agent understands better, a task gets faster), the probe that would show it is built before code, asks the question the outcome names, and is shown red on the baseline; how the author frames the unit does not exempt it. `SKILL.md` Execute-Before-Specify.
|
|
63
|
+
|
|
5
64
|
## [1.43.1] - 2026-10-18
|
|
6
65
|
|
|
7
66
|
### Fixed
|
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ Vendoring requires all three files: the entry point, `sdlc_core.py`, `knowledge.
|
|
|
35
35
|
- **Independent review, twice**: the design is reviewed before it is implemented and the diff before the work is declared done — by somebody other than its author, who receives the whole review mandate in a fixed block copied verbatim, never a paraphrase of it. Three rungs of independence (fresh-context subagent > one-shot run > a declared self-pass, legal only when no higher rung is usable: absent, or permission-gated and declined — a gated rung is asked about, never silently skipped, and the log says which), capped at 3 rounds, one log line per review, and a PASS is invalid on "found nothing" — it must state where each constraint is satisfied.
|
|
36
36
|
- **Question discipline**: a real doubt is asked when it emerges, before its answer is written into any document — grouped, with the pros and cons of each option. A choice whose pros clearly win is taken without asking, and the weighing is written next to it; a fact is settled by evidence or asked, never weighed. A question is legal only when the agent searched first, names the search with its result, and names the decision it unblocks — never a stream of "shall I proceed?".
|
|
37
37
|
- **Installed support files**: Claude, Codex, Gemini, and Google Antigravity receive the full skill folder, including `templates.md`, `architect.md`, `guides.md`, `vision.md`, `tdd.md`, `debugging.md`, `elicitation.md`, `review.md`, `dispatch.md`, `routing.md`, `hybrid.md`, `ENFORCEMENT.md`, `memory.md`, and the validator's three files, `scripts/sdlc_check.py` + `scripts/sdlc_core.py` + `scripts/knowledge.py` — plus the docs site generator `scripts/docs_site.py` and its page `docs_site/`, and the `LICENSE` and `NOTICE` it ships under.
|
|
38
|
-
- **Docs site** (`scripts/docs_site.py --root <project> [--open]`): a static, offline, read-only website of what the agent reads in the project — reading guide, Vision, rules, documents, open work with each next step, analyses and, with a knowledge base, the topic map, each fact with its exact source passage, and the source documents. Written outside `ai_docs/`; nothing is ranked or recommended.
|
|
38
|
+
- **Docs site** (`scripts/docs_site.py --root <project> [--open] [--zip <file>]`): a static, offline, read-only website of what the agent reads in the project — reading guide, Vision, rules, documents, open work with each next step, analyses and, with a knowledge base, the topic map, each fact with its exact source passage, and the source documents. Written to `.docs-site/` (added to `.gitignore` on the first run), outside `ai_docs/`; nothing is ranked or recommended.
|
|
39
39
|
- **Mechanical checks**: optional validator for document structure, generated feature history, stale audit areas, and protected-path gates — `check`, `validate`, `index`, `stale`, `mark`, `benefit`, `gate`, `plan`, `orient`, `migrate`, `review-map` (code lens: classes the files of a change for the merge review brief). `benefit` reports what the review gates have already caught, beside what the doctrine costs to read — it always exits 0 and carries no verdict, because a measurement that can fail a build becomes a target.
|
|
40
40
|
|
|
41
41
|
## Installation
|
|
@@ -74,8 +74,8 @@ agentic-sdlc-init
|
|
|
74
74
|
Run it inside a project to create `ai_docs/`, Vision stubs awaiting your interview, strategic docs, audit plan, and agent protocol files (`AGENTS.md` — also the Antigravity CLI surface, `CLAUDE.md`, `GEMINI.md`, `.cursorrules`).
|
|
75
75
|
|
|
76
76
|
It also **wires the SessionStart orientation hook** into `.claude/`, so an agent opening
|
|
77
|
-
the project is handed the
|
|
78
|
-
|
|
77
|
+
the project is handed the triage line, the guide router, the workstream registry and the
|
|
78
|
+
reading guide (with pointers to the manifest) before it does anything — instead of only when it remembers to look. That
|
|
79
79
|
wiring used to be a manual step documented in `ENFORCEMENT.md`; being manual, it was
|
|
80
80
|
skipped, and a project could run fully governed with an agent that never met the process.
|
|
81
81
|
|
package/gemini-extension.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antoneeo/agentic-sdlc-skill",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.48.0",
|
|
4
4
|
"description": "Documentation-First SDLC protocol for Claude Code, Gemini CLI, Google Antigravity and Codex with risk triage, Vision governance, installed support files and optional devPNT integration.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
package/scripts/lib.js
CHANGED
|
@@ -587,10 +587,23 @@ function wireGlobalOrientHook(options) {
|
|
|
587
587
|
// already removed the hook, the line stays and the opt-out survives uninstall,
|
|
588
588
|
// reinstall, and sibling-lens uninstalls.
|
|
589
589
|
function removeGlobalOrientHooks(removedRoots) {
|
|
590
|
+
return removeGlobalHookEntries(removedRoots, {
|
|
591
|
+
event: 'SessionStart',
|
|
592
|
+
re: / orient(\s|$)/,
|
|
593
|
+
markerTargets: orientMarkerTargets,
|
|
594
|
+
markerRemove: orientMarkerRemove,
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// The pruning loop shared by every hook this installer wires (F-076): only the
|
|
599
|
+
// `spec.event` entries whose command matches `spec.re` AND whose validator
|
|
600
|
+
// token resolves INTO a removed root are ours; the marker line is cleared only
|
|
601
|
+
// when an entry was actually removed. Returns the targets touched.
|
|
602
|
+
function removeGlobalHookEntries(removedRoots, spec) {
|
|
590
603
|
const targets = [];
|
|
591
604
|
const claude = CLIENTS.find((c) => c.key === 'claude');
|
|
592
605
|
if (claude) targets.push(path.join(claude.home, 'settings.json'));
|
|
593
|
-
for (const t of
|
|
606
|
+
for (const t of spec.markerTargets()) {
|
|
594
607
|
if (!targets.some((x) => pathsEqual(x, t))) targets.push(t);
|
|
595
608
|
}
|
|
596
609
|
const removedFrom = [];
|
|
@@ -599,17 +612,17 @@ function removeGlobalOrientHooks(removedRoots) {
|
|
|
599
612
|
const state = orientSettingsState(target);
|
|
600
613
|
if (!state.ok || !fs.existsSync(target)) continue;
|
|
601
614
|
const hooks = state.settings.hooks;
|
|
602
|
-
if (!hooks || !Array.isArray(hooks.
|
|
615
|
+
if (!hooks || !Array.isArray(hooks[spec.event])) continue;
|
|
603
616
|
let removedHere = false;
|
|
604
617
|
const ours = (h) => {
|
|
605
618
|
const cmd = h && typeof h.command === 'string' ? h.command : '';
|
|
606
|
-
if (!(ORIENT_ENTRY_POINTS.some((n) => cmd.includes(n)) &&
|
|
619
|
+
if (!(ORIENT_ENTRY_POINTS.some((n) => cmd.includes(n)) && spec.re.test(cmd))) return false;
|
|
607
620
|
const tok = orientHookValidator(cmd);
|
|
608
621
|
if (tok === null || !path.isAbsolute(tok)) return false; // cannot attribute: keep
|
|
609
622
|
return removedRoots.some((root) => pathInside(tok, root));
|
|
610
623
|
};
|
|
611
624
|
const kept = [];
|
|
612
|
-
for (const group of hooks.
|
|
625
|
+
for (const group of hooks[spec.event]) {
|
|
613
626
|
if (!group || typeof group !== 'object') { kept.push(group); continue; }
|
|
614
627
|
if (Array.isArray(group.hooks)) {
|
|
615
628
|
const inner = group.hooks.filter((h) => {
|
|
@@ -626,13 +639,13 @@ function removeGlobalOrientHooks(removedRoots) {
|
|
|
626
639
|
}
|
|
627
640
|
}
|
|
628
641
|
if (!removedHere) continue;
|
|
629
|
-
state.settings.hooks.
|
|
630
|
-
if (!kept.length) delete state.settings.hooks.
|
|
642
|
+
state.settings.hooks[spec.event] = kept;
|
|
643
|
+
if (!kept.length) delete state.settings.hooks[spec.event];
|
|
631
644
|
if (state.settings.hooks && !Object.keys(state.settings.hooks).length) {
|
|
632
645
|
delete state.settings.hooks;
|
|
633
646
|
}
|
|
634
647
|
fs.writeFileSync(target, JSON.stringify(state.settings, null, 2) + String.fromCharCode(10), 'utf8');
|
|
635
|
-
|
|
648
|
+
spec.markerRemove(target);
|
|
636
649
|
removedFrom.push(target);
|
|
637
650
|
} catch (e) { /* fail-open per target: never abort an uninstall */ }
|
|
638
651
|
}
|
|
@@ -647,94 +660,142 @@ function removeGlobalOrientHooks(removedRoots) {
|
|
|
647
660
|
// already there -- which is the state of every existing installation.
|
|
648
661
|
// Owner-accepted per-turn cost: `ai_docs/vision/rulings.md` r19.
|
|
649
662
|
|
|
650
|
-
|
|
663
|
+
// F-076 generalized the pair below to every constant-command hook this
|
|
664
|
+
// installer wires: one spec per hook, the remind names kept as wrappers.
|
|
665
|
+
const REMIND_HOOK = {
|
|
666
|
+
event: 'UserPromptSubmit', sub: 'remind', re: / remind(\s|$)/,
|
|
667
|
+
marker: 'remind-hook-wired', timeout: 5,
|
|
668
|
+
};
|
|
669
|
+
// F-076: the guide-verdict check. `; exit 0` because a validator that cannot
|
|
670
|
+
// run `verdict` -- missing (npm 7+ may never run the uninstaller) or older than
|
|
671
|
+
// it (a downgrade) -- exits 2, and exit 2 on PreToolUse blocks every edit.
|
|
672
|
+
// Probed in Git Bash and PowerShell 5.1. Owner-accepted: rulings.md r34.
|
|
673
|
+
const VERDICT_HOOK = {
|
|
674
|
+
event: 'PreToolUse', sub: 'verdict --hook', suffix: '; exit 0', re: / verdict(\s|$)/,
|
|
675
|
+
marker: 'verdict-hook-wired', timeout: 10, matcher: 'Edit|Write|MultiEdit|NotebookEdit',
|
|
676
|
+
};
|
|
651
677
|
|
|
678
|
+
function commandHookCommand(spec, python, validator) {
|
|
679
|
+
return python + ' "' + validator + '" ' + spec.sub + (spec.suffix || '');
|
|
680
|
+
}
|
|
652
681
|
function remindHookCommand(python, validator) {
|
|
653
|
-
return python
|
|
682
|
+
return commandHookCommand(REMIND_HOOK, python, validator);
|
|
654
683
|
}
|
|
655
684
|
|
|
656
|
-
function
|
|
657
|
-
const groups = settings && settings.hooks && settings.hooks.
|
|
685
|
+
function findCommandHook(settings, spec) {
|
|
686
|
+
const groups = settings && settings.hooks && settings.hooks[spec.event];
|
|
658
687
|
if (!Array.isArray(groups)) return null;
|
|
659
688
|
for (const group of groups) {
|
|
660
689
|
if (!group || typeof group !== 'object') continue;
|
|
661
690
|
const inner = Array.isArray(group.hooks) ? group.hooks : [group];
|
|
662
691
|
for (const h of inner) {
|
|
663
692
|
const cmd = h && typeof h.command === 'string' ? h.command : '';
|
|
664
|
-
if (ORIENT_ENTRY_POINTS.some((n) => cmd.includes(n)) &&
|
|
693
|
+
if (ORIENT_ENTRY_POINTS.some((n) => cmd.includes(n)) && spec.re.test(cmd)) {
|
|
665
694
|
return cmd;
|
|
666
695
|
}
|
|
667
696
|
}
|
|
668
697
|
}
|
|
669
698
|
return null;
|
|
670
699
|
}
|
|
700
|
+
function findRemindHook(settings) {
|
|
701
|
+
return findCommandHook(settings, REMIND_HOOK);
|
|
702
|
+
}
|
|
671
703
|
|
|
672
|
-
//
|
|
673
|
-
|
|
674
|
-
// not die on someone's settings file.
|
|
675
|
-
function remindMarkerPath() {
|
|
704
|
+
// One marker file per hook: removal is a standing opt-out for THAT hook only.
|
|
705
|
+
function markerPathOf(spec) {
|
|
676
706
|
const root = process.env.AGENTIC_SDLC_KB_ROOT
|
|
677
707
|
|| path.join(os.homedir(), '.agentic-sdlc');
|
|
678
|
-
return path.join(root,
|
|
708
|
+
return path.join(root, spec.marker);
|
|
679
709
|
}
|
|
680
|
-
function
|
|
710
|
+
function markerTargetsOf(spec) {
|
|
681
711
|
try {
|
|
682
|
-
return fs.readFileSync(
|
|
712
|
+
return fs.readFileSync(markerPathOf(spec), 'utf8')
|
|
683
713
|
.split(/\r?\n/).map((x) => x.trim()).filter(Boolean);
|
|
684
714
|
} catch (e) {
|
|
685
715
|
return [];
|
|
686
716
|
}
|
|
687
717
|
}
|
|
688
|
-
function
|
|
689
|
-
return
|
|
718
|
+
function markerHasOf(spec, target) {
|
|
719
|
+
return markerTargetsOf(spec).some((t) => pathsEqual(t, target));
|
|
720
|
+
}
|
|
721
|
+
function markerAddOf(spec, target) {
|
|
722
|
+
try {
|
|
723
|
+
if (markerHasOf(spec, target)) return;
|
|
724
|
+
fs.mkdirSync(path.dirname(markerPathOf(spec)), { recursive: true });
|
|
725
|
+
fs.appendFileSync(markerPathOf(spec), target + String.fromCharCode(10), 'utf8');
|
|
726
|
+
} catch (e) { /* fail-open */ }
|
|
690
727
|
}
|
|
691
|
-
function
|
|
728
|
+
function markerRemoveOf(spec, target) {
|
|
692
729
|
try {
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
730
|
+
const rest = markerTargetsOf(spec).filter((t) => !pathsEqual(t, target));
|
|
731
|
+
if (rest.length) {
|
|
732
|
+
fs.writeFileSync(markerPathOf(spec), rest.join(String.fromCharCode(10)) + String.fromCharCode(10), 'utf8');
|
|
733
|
+
} else {
|
|
734
|
+
fs.rmSync(markerPathOf(spec), { force: true });
|
|
735
|
+
}
|
|
696
736
|
} catch (e) { /* fail-open */ }
|
|
697
737
|
}
|
|
738
|
+
function remindMarkerHas(target) { return markerHasOf(REMIND_HOOK, target); }
|
|
739
|
+
function remindMarkerAdd(target) { markerAddOf(REMIND_HOOK, target); }
|
|
698
740
|
|
|
699
|
-
|
|
741
|
+
// Writes the hook into `target` (an already-chosen settings file) unless it is
|
|
742
|
+
// there or the user opted out. Returns a code, never throws: an installer must
|
|
743
|
+
// not die on someone's settings file. Order matters: the hook being PRESENT is
|
|
744
|
+
// `already`, and only its ABSENCE plus our marker means the user removed it --
|
|
745
|
+
// a standing opt-out. Marker-first would refuse to re-check a hook someone
|
|
746
|
+
// still has.
|
|
747
|
+
function wireCommandHookInto(spec, target, python, validator) {
|
|
700
748
|
if (!python) return { code: 'no-python' };
|
|
701
749
|
if (!validator || ORIENT_UNSAFE_IN_PATH.test(validator)) return { code: 'unsafe-path', validator };
|
|
702
750
|
const state = orientSettingsState(target);
|
|
703
751
|
if (!state.ok) return { code: 'malformed', target, why: state.why };
|
|
704
|
-
const
|
|
705
|
-
|
|
706
|
-
// PRESENT is `already`, and only its ABSENCE plus our marker means the user
|
|
707
|
-
// removed it -- a standing opt-out. Marker-first would refuse to re-check a
|
|
708
|
-
// hook someone still has.
|
|
709
|
-
const present = findRemindHook(settings0);
|
|
752
|
+
const settings = state.settings;
|
|
753
|
+
const present = findCommandHook(settings, spec);
|
|
710
754
|
if (present) return { code: 'already', target, command: present };
|
|
711
|
-
if (
|
|
712
|
-
const
|
|
713
|
-
const existing = null;
|
|
714
|
-
if (existing) return { code: 'already', target, command: existing };
|
|
715
|
-
const command = remindHookCommand(python, validator);
|
|
755
|
+
if (markerHasOf(spec, target)) return { code: 'opted-out', target };
|
|
756
|
+
const command = commandHookCommand(spec, python, validator);
|
|
716
757
|
if (!settings.hooks) settings.hooks = {};
|
|
717
|
-
if (!Array.isArray(settings.hooks.
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
758
|
+
if (!Array.isArray(settings.hooks[spec.event])) settings.hooks[spec.event] = [];
|
|
759
|
+
const group = { hooks: [{ type: 'command', command, timeout: spec.timeout }] };
|
|
760
|
+
if (spec.matcher) group.matcher = spec.matcher;
|
|
761
|
+
settings.hooks[spec.event].push(group);
|
|
721
762
|
try {
|
|
722
763
|
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
723
764
|
fs.writeFileSync(target, JSON.stringify(settings, null, 2) + String.fromCharCode(10), 'utf8');
|
|
724
765
|
} catch (e) {
|
|
725
766
|
return { code: 'write-failed', target, command, error: e.message };
|
|
726
767
|
}
|
|
727
|
-
|
|
768
|
+
markerAddOf(spec, target);
|
|
728
769
|
return { code: 'wired', target, command };
|
|
729
770
|
}
|
|
771
|
+
function wireRemindHookInto(target, python, validator) {
|
|
772
|
+
return wireCommandHookInto(REMIND_HOOK, target, python, validator);
|
|
773
|
+
}
|
|
730
774
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
function wireGlobalRemindHook(options) {
|
|
775
|
+
// The machine-global twins of wireGlobalOrientHook, same target and validator.
|
|
776
|
+
function wireGlobalCommandHook(spec, options) {
|
|
734
777
|
const client = options.client;
|
|
735
778
|
const validator = path.join(skillTarget(client), 'scripts', ORIENT_ENTRY_POINT);
|
|
736
779
|
if (!fs.existsSync(validator)) return { code: 'no-validator', validator };
|
|
737
|
-
return
|
|
780
|
+
return wireCommandHookInto(spec, path.join(client.home, 'settings.json'), options.python, validator);
|
|
781
|
+
}
|
|
782
|
+
function wireGlobalRemindHook(options) { return wireGlobalCommandHook(REMIND_HOOK, options); }
|
|
783
|
+
function wireGlobalVerdictHook(options) { return wireGlobalCommandHook(VERDICT_HOOK, options); }
|
|
784
|
+
|
|
785
|
+
// Uninstall twin for the constant-command hooks: one {target, event} per
|
|
786
|
+
// removal, so the uninstaller can say what it removed.
|
|
787
|
+
function removeGlobalCommandHooks(removedRoots) {
|
|
788
|
+
const removed = [];
|
|
789
|
+
for (const spec of [REMIND_HOOK, VERDICT_HOOK]) {
|
|
790
|
+
const targets = removeGlobalHookEntries(removedRoots, {
|
|
791
|
+
event: spec.event,
|
|
792
|
+
re: spec.re,
|
|
793
|
+
markerTargets: () => markerTargetsOf(spec),
|
|
794
|
+
markerRemove: (target) => markerRemoveOf(spec, target),
|
|
795
|
+
});
|
|
796
|
+
for (const target of targets) removed.push({ target, event: spec.event });
|
|
797
|
+
}
|
|
798
|
+
return removed;
|
|
738
799
|
}
|
|
739
800
|
|
|
740
801
|
|
|
@@ -750,6 +811,8 @@ module.exports = {
|
|
|
750
811
|
wireOrientHook,
|
|
751
812
|
wireRemindHookInto,
|
|
752
813
|
wireGlobalRemindHook,
|
|
814
|
+
wireGlobalVerdictHook,
|
|
815
|
+
removeGlobalCommandHooks,
|
|
753
816
|
remindMarkerHas,
|
|
754
817
|
remindMarkerAdd,
|
|
755
818
|
remindHookCommand,
|
package/scripts/postinstall.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const {
|
|
5
5
|
SKILL_SOURCE, CLIENTS, clientDetected, skillTarget, copyRecursive,
|
|
6
|
-
wireGlobalOrientHook, wireGlobalRemindHook, detectPython,
|
|
6
|
+
wireGlobalOrientHook, wireGlobalRemindHook, wireGlobalVerdictHook, detectPython,
|
|
7
7
|
} = require('./lib');
|
|
8
8
|
|
|
9
9
|
function installSkill(client) {
|
|
@@ -51,6 +51,16 @@ for (const client of CLIENTS) {
|
|
|
51
51
|
console.log(` (${rr.target}). One constant line per prompt; remove that hook`);
|
|
52
52
|
console.log(' entry to opt out — it will not be re-added.');
|
|
53
53
|
}
|
|
54
|
+
// F-076: the guide-verdict check -- refuses an edit when the session
|
|
55
|
+
// declared no guide-router verdict, or never read a guide it names.
|
|
56
|
+
// Silent on projects without guides; owner-accepted per-edit cost:
|
|
57
|
+
// ai_docs/vision/rulings.md r34.
|
|
58
|
+
const rv = wireGlobalVerdictHook({ client, python: detectPython() });
|
|
59
|
+
if (rv.code === 'wired') {
|
|
60
|
+
console.log('🔗 Guide-verdict check wired for every project on this machine');
|
|
61
|
+
console.log(` (${rv.target}). Edits wait for the Rule-Zero router verdict in projects`);
|
|
62
|
+
console.log(' with guides; remove that hook entry to opt out — it will not be re-added.');
|
|
63
|
+
}
|
|
54
64
|
const r = wireGlobalOrientHook({ client, python: detectPython() });
|
|
55
65
|
if (r.code === 'wired') {
|
|
56
66
|
console.log('🔗 Session orientation wired for every project on this machine');
|
package/scripts/preuninstall.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
|
-
const {
|
|
4
|
+
const {
|
|
5
|
+
CLIENTS, skillTarget, removeGlobalOrientHooks, removeGlobalCommandHooks,
|
|
6
|
+
} = require('./lib');
|
|
5
7
|
|
|
6
8
|
function removeSkill(client) {
|
|
7
9
|
const target = skillTarget(client);
|
|
@@ -27,4 +29,18 @@ try {
|
|
|
27
29
|
}
|
|
28
30
|
} catch (e) { /* never abort an uninstall for a convenience */ }
|
|
29
31
|
|
|
32
|
+
// F-076: the per-turn reminder and the guide-verdict check name the same skill
|
|
33
|
+
// dirs; left behind they exit 2 (Python cannot open the file), which blocks the
|
|
34
|
+
// prompt or the edit. The verdict entry is also wired `; exit 0`, because npm 7+
|
|
35
|
+
// may not run this script at all.
|
|
36
|
+
const COMMAND_HOOK_NAMES = {
|
|
37
|
+
UserPromptSubmit: 'per-turn reminder',
|
|
38
|
+
PreToolUse: 'guide-verdict check',
|
|
39
|
+
};
|
|
40
|
+
try {
|
|
41
|
+
for (const { target, event } of removeGlobalCommandHooks(CLIENTS.map(skillTarget))) {
|
|
42
|
+
console.log(`🧹 Removed the ${COMMAND_HOOK_NAMES[event] || event} hook entry (${target}).`);
|
|
43
|
+
}
|
|
44
|
+
} catch (e) { /* never abort an uninstall for a convenience */ }
|
|
45
|
+
|
|
30
46
|
CLIENTS.forEach(removeSkill);
|
|
@@ -14,17 +14,17 @@ python "<skill_dir>/scripts/sdlc_check.py" check
|
|
|
14
14
|
|
|
15
15
|
## 2. Check in CI (recommended for teams)
|
|
16
16
|
|
|
17
|
-
Copy all three validator files into the repository: `scripts/sdlc_check.py`, `scripts/sdlc_core.py`, and `scripts/knowledge.py`, keeping them side by side. Then add to the pipeline:
|
|
17
|
+
Copy all three validator files into the repository: `scripts/sdlc_check.py`, `scripts/sdlc_core.py`, and `scripts/knowledge.py`, keeping them side by side. Then add to the pipeline:
|
|
18
18
|
|
|
19
19
|
```
|
|
20
20
|
python tools/sdlc_check.py validate --strict
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
The validator ships as **three files**: the domain entry point, `sdlc_core.py` and `knowledge.py`. Keep all three together. The core alone still runs but omits shared-memory/KB checks and domain-specific overlays; it is not an equivalent CI gate. `check` includes the knowledge surface when present.
|
|
23
|
+
The validator ships as **three files**: the domain entry point, `sdlc_core.py` and `knowledge.py`. Keep all three together. The core alone still runs but omits shared-memory/KB checks and domain-specific overlays; it is not an equivalent CI gate. `check` includes the knowledge surface when present.
|
|
24
24
|
|
|
25
25
|
Effect: an unregenerated index, invalid frontmatter, a missing security section or incoherent states **block the pipeline** instead of relying on the agent's memory. `--strict` also fails on warnings and on a missing `ai_docs/`, so a wrong working directory cannot produce a green pipeline. This works because documents travel in the same PR as the code (Phase 5 rule).
|
|
26
26
|
|
|
27
|
-
Note: the copy in the repo is the authoritative one for CI; update it when you update the skill — all three files, together.
|
|
27
|
+
Note: the copy in the repo is the authoritative one for CI; update it when you update the skill — all three files, together.
|
|
28
28
|
|
|
29
29
|
**Projects whose docs root is not `ai_docs/`.** Pass `--docs-dir <name>` (e.g. `--docs-dir mkt_docs`) on any subcommand, or set `AGENTIC_SDLC_DOCS_DIR`. Without either, the validator walks up from the working directory and takes the nearest root it recognizes. If it finds two side by side — the shape of a half-finished migration — it refuses and names both rather than validating half a project and printing a verdict. `ai_docs/` remains the default and the recommended root: the parameter exists so a legacy tree can be read and migrated, not so a second one can be kept.
|
|
30
30
|
|
|
@@ -59,9 +59,60 @@ Semantics: exit code 2 + message on stderr ⇒ the write is blocked and the mess
|
|
|
59
59
|
- The hook assumes the working directory is the project root (standard behavior of Claude Code hooks).
|
|
60
60
|
- **Hybrid/devPNT projects**: add `--hybrid` to the gate command. Governed designs live in the devPNT DB, so the gate also unlocks when an approved E-TDD shadow (`ai_docs/solutions/SHADOW_*tdd*.md`, exported before implementation — see `hybrid.md` §Shadow discipline) is present. Without the flag the gate would block legitimate governed work. The flag is deliberately explicit: never auto-detected.
|
|
61
61
|
|
|
62
|
+
### Verdict check: `verdict` (PreToolUse -- wired by default)
|
|
63
|
+
|
|
64
|
+
Rule Zero makes the agent declare the level with the guide-router verdict and read the
|
|
65
|
+
guide it names before acting. A field agent edited without doing either and said nothing
|
|
66
|
+
had stopped it (F-076). `sdlc_check.py verdict --hook` reads the session transcript
|
|
67
|
+
Claude Code hands every hook and refuses an Edit, Write, MultiEdit or NotebookEdit when:
|
|
68
|
+
|
|
69
|
+
- the session shows no line of the agent's own text with `router:` (the verdict) and no
|
|
70
|
+
`Level: L1` (or another lens's level 1), or
|
|
71
|
+
- a guide the verdict names, present in `reference/`, never appeared in the input of a
|
|
72
|
+
tool call other than an edit (Read, Bash `cat`, Grep, a subagent brief all count).
|
|
73
|
+
|
|
74
|
+
A verdict whose text after `router:` begins with `no match` or `absent` names no guide.
|
|
75
|
+
The refusal is one line, shown to the agent, saying how to unblock. It is silent — the
|
|
76
|
+
edit proceeds — in a project without guides, for a file outside the project, for a
|
|
77
|
+
subagent's edit (one the client marks with `agent_id`, or one whose transcript is the
|
|
78
|
+
subagent's own), and whenever it cannot tell (no or oversized transcript, unknown format,
|
|
79
|
+
ambiguous docs root, any error). It never approves an edit: compliance leaves the user's
|
|
80
|
+
normal permission flow in place.
|
|
81
|
+
|
|
82
|
+
Cost (owner-accepted, `ai_docs/vision/rulings.md` r34, the "no ceremony ratchet"
|
|
83
|
+
Non-Goal on r19's precedent): ~0.2–0.5 s per edit and zero tokens when compliant; one
|
|
84
|
+
refusal of at most 250 characters and one retry when not. L1 pays nothing beyond writing
|
|
85
|
+
`Level: L1`, which Rule Zero already asks for. Residuals: edits through a subagent and
|
|
86
|
+
writes made with Bash are not checked (a subagent's edit the client does not mark is judged
|
|
87
|
+
on the orchestrator's verdict); a verdict anywhere earlier in the session counts.
|
|
88
|
+
|
|
89
|
+
Installing the package wires it machine-wide on Claude Code; **removing that hook entry
|
|
90
|
+
is a standing opt-out.** The command ends in `; exit 0`: a validator that cannot run
|
|
91
|
+
`verdict` — gone after an uninstall, or older after a downgrade — would exit 2, and exit
|
|
92
|
+
2 on PreToolUse blocks every edit. To wire it by hand:
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"hooks": {
|
|
97
|
+
"PreToolUse": [
|
|
98
|
+
{
|
|
99
|
+
"matcher": "Edit|Write|MultiEdit|NotebookEdit",
|
|
100
|
+
"hooks": [
|
|
101
|
+
{
|
|
102
|
+
"type": "command",
|
|
103
|
+
"command": "python \"C:\\Users\\<user>\\.claude\\skills\\agentic-sdlc\\scripts\\sdlc_check.py\" verdict --hook; exit 0",
|
|
104
|
+
"timeout": 10
|
|
105
|
+
}
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
]
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
62
113
|
## 4. SessionStart hook (orientation, recommended default)
|
|
63
114
|
|
|
64
|
-
Emits the `ai_docs/` orientation
|
|
115
|
+
Emits the `ai_docs/` orientation to stdout at session start, in the order an arriving agent needs it: the Rule-Zero triage line, the lens choice when sibling lenses are installed, the guide router (`reference/INDEX.md`, one line per guide), the workstream registry (`audit/handoff.md`, one line per workstream, then the project-wide notes), the reading guide (`README.md`), then pointers to the manifest (`INDEX.md`) and to the full files — so the agent begins already oriented instead of reading them only if it remembers to. The whole output stays under 9,500 characters: Claude Code injects a hook's output up to 10,000 and, above that, only a 2,000-character preview, which is how the router once never reached a session (F-075). A section that does not fit is cut at a line with a pointer to its file, and a note says when one client runs the hook from two settings entries. It is **fail-open**: a missing or unreadable doc is skipped, and it always exits 0 — a broken or empty `ai_docs/` never blocks the session. It is **zero-execution** (it reads and prints, never runs anything).
|
|
65
116
|
|
|
66
117
|
**Wire it on every project that has `ai_docs/` and a Python interpreter.** It was opt-in until v1.16.0 and the field result was the defect this level exists to prevent: the guide router stayed unread unless the user asked for it by hand, so guides were written and never consulted. Prompt-level placement (Rule Zero declares the router verdict; Phase 1 reads the router) carries the process on its own — this hook is the backstop that survives long contexts, compaction and a session that never enters Phase 1 explicitly. Skip it only where Python is unavailable, and know what you are trading.
|
|
67
118
|
|
|
@@ -155,7 +206,7 @@ Gemini CLI — wire the same command into its startup-hook mechanism if present;
|
|
|
155
206
|
**Usage notes:**
|
|
156
207
|
- The hook assumes the working directory is the project root (standard Claude Code hook behavior); it also accepts `--root <path>`.
|
|
157
208
|
- **Hybrid/devPNT projects**: add `--hybrid` — the hook then appends a one-line pointer to run `devpnt_mcp_get_bootstrap` for the Master Plan / Knowledge Layer and does not replicate them; the filesystem orientation (router + handoff + README) still emits.
|
|
158
|
-
- Like the CI gate (§2), if you copied the validator into the repo, the hook references that copy — keep all three files current when you update the skill.
|
|
209
|
+
- Like the CI gate (§2), if you copied the validator into the repo, the hook references that copy — keep all three files current when you update the skill.
|
|
159
210
|
|
|
160
211
|
### Per-turn reminder: `remind` (UserPromptSubmit -- wired by default)
|
|
161
212
|
|
|
@@ -166,13 +217,13 @@ a release ran against a `GUIDE_release.md` the session never consulted, because
|
|
|
166
217
|
ONE constant line at every prompt, and its first duty is that one: decide whether the
|
|
167
218
|
skill governs the work in front of you and, if it does, load it once.
|
|
168
219
|
|
|
169
|
-
Cost:
|
|
220
|
+
Cost: 566 characters, ~95 tokens per prompt, paid on every turn including
|
|
170
221
|
trivial ones. That is admissible only because the owner accepted it explicitly
|
|
171
222
|
(`ai_docs/vision/rulings.md` r19, the "no ceremony ratchet" Non-Goal's second door) --
|
|
172
223
|
and the acceptance was for a FLAT cost. Hence the contract, guarded by
|
|
173
224
|
`scripts/test_remind.py`: the line is **constant and reads nothing** (so nothing repo-
|
|
174
225
|
or session-controlled rides into the agent's context through it, and the cost cannot
|
|
175
|
-
grow with the session), it is **one line under
|
|
226
|
+
grow with the session), it is **one line under 600 characters** (r32 raised r19's 500 to carry the lens choice), and it **can never
|
|
176
227
|
break a prompt** (argv ignored whole, always exit 0). A trivial turn pays a yes/no
|
|
177
228
|
judgement, never a load.
|
|
178
229
|
|
|
@@ -215,7 +266,7 @@ It aggregates the test files (`test_plan.py` + `test_session_start.py` + `test_s
|
|
|
215
266
|
|
|
216
267
|
**Optional CI** (same shape as §2, not mandatory): add a `run:` step invoking the `unittest discover` command above.
|
|
217
268
|
|
|
218
|
-
**T10 note:** if you copied the validator (all three files) and the `test_*.py` battery into the repo for CI, that copy is authoritative — keep it current when you update the skill.
|
|
269
|
+
**T10 note:** if you copied the validator (all three files) and the `test_*.py` battery into the repo for CI, that copy is authoritative — keep it current when you update the skill.
|
|
219
270
|
|
|
220
271
|
## `benefit` is a report, never a CI gate
|
|
221
272
|
|