@antoneeo/agentic-sdlc-skill 1.30.0 → 1.32.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/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/scripts/lib.js +71 -0
- package/scripts/postinstall.js +13 -1
- package/skills/agentic-sdlc-skill/ENFORCEMENT.md +40 -0
- package/skills/agentic-sdlc-skill/SKILL.md +6 -4
- package/skills/agentic-sdlc-skill/guides.md +16 -12
- package/skills/agentic-sdlc-skill/review.md +6 -4
- package/skills/agentic-sdlc-skill/scripts/sdlc_check.py +9 -0
- package/skills/agentic-sdlc-skill/scripts/sdlc_core.py +54 -3
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.32.0] - 2026-09-06
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **F-046: the protocol reaches every turn (`remind`, UserPromptSubmit, wired by
|
|
9
|
+
default).** `orient` fires once at session start and decays; a session that
|
|
10
|
+
never loads the skill never had the protocol at all. Measured 2026-09-06: a
|
|
11
|
+
release ran against a `GUIDE_release.md` the session never consulted, because
|
|
12
|
+
`SKILL.md` -- where the duty is written -- had not been read. `remind` prints
|
|
13
|
+
ONE constant line at every prompt, and its first duty is that one: decide
|
|
14
|
+
whether the skill governs the work in front of you and, if it does, LOAD it
|
|
15
|
+
once. The carrier is kb's F-041 item B, LIFTED into the shared spine rather
|
|
16
|
+
than duplicated, so all three lenses share one machine; each lens declares its
|
|
17
|
+
own line in `set_profile(remind_line=...)`. Installing the package wires it
|
|
18
|
+
machine-wide on Claude Code alongside the orientation hook, never instead of
|
|
19
|
+
it; removing the hook entry is a standing opt-out. Per-turn cost disclosed and
|
|
20
|
+
accepted by the owner (`vision/rulings.md` r19, the "no ceremony ratchet"
|
|
21
|
+
Non-Goal's second door), and bounded to stay flat by a contract the new shared
|
|
22
|
+
`scripts/test_remind.py` guards: constant text, zero reads, one line under 500
|
|
23
|
+
characters, argv ignored whole, always exit 0.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- **`guides.md` said something false about code guides.** It claimed `stale`
|
|
27
|
+
works unchanged for `source_kind: code` because "the code-excerpt snapshot
|
|
28
|
+
drifts when the code changes". A snapshot is a frozen copy and never drifts:
|
|
29
|
+
freshness is NOT mechanized there, and the duty is the author's twice -- when
|
|
30
|
+
you MODIFY code a comprehension guide describes, and when you merely READ one
|
|
31
|
+
before trusting it. Measured 2026-09-06: the guided module had gained 388
|
|
32
|
+
lines and lost 71 across four commits with `stale` silent.
|
|
33
|
+
|
|
34
|
+
- **The guide question moves to the analysis phase (SKILL.md).** Two lines in the
|
|
35
|
+
ANALYSIS -- does a CURRENT guide cover what I am about to touch, and did
|
|
36
|
+
understanding this force a model of a high-complexity component none covers.
|
|
37
|
+
The Phase-5 Comprehension checkpoint stays the backstop for a signal that only
|
|
38
|
+
became visible later; it is no longer the moment of first asking.
|
|
39
|
+
|
|
40
|
+
## [1.31.0] - 2026-09-05
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
- **Execute-Before-Specify now covers text-about-text (SKILL.md, Phase 3).** A
|
|
44
|
+
deliverable that is itself text -- doctrine, a schema, a prompt, a reviewer
|
|
45
|
+
definition -- is probed the same way as code: replay it against the recorded
|
|
46
|
+
case that motivated it (the incident artifact, the tool-usage transcript, the
|
|
47
|
+
review that missed the defect), red under the old text, green under the new.
|
|
48
|
+
"No probe is possible" is shown, never declared. The exemption an earlier
|
|
49
|
+
proposal would have granted was rejected on evidence: the unit cited as
|
|
50
|
+
unprobeable text was a code unit that modified six production modules and
|
|
51
|
+
shipped four assertion harnesses, while doctrine and prompt changes have been
|
|
52
|
+
measured here by replay (three headless probes of a prompt change; an ad-hoc
|
|
53
|
+
checklist that caught in one round what ten rounds missed). Paid for per this
|
|
54
|
+
skill's own `vision.md` cumulative-ratchet rule: 67 words of illustration and
|
|
55
|
+
rationale deleted from the same paragraph, net -2 words.
|
|
56
|
+
- **Form budget belongs to the first draft (shared spine `review.md`,
|
|
57
|
+
byte-identical x3).** The lean operative form is the FIRST draft's form, not
|
|
58
|
+
the round-3 rescue: the review request states the artifact's line count
|
|
59
|
+
against a budget the author fixed before drafting, and an artifact over budget
|
|
60
|
+
is the reviewer's first finding, before content. The ~1,250 -> ~500 line
|
|
61
|
+
measurement already in the file is its anchor. Spine delta is synced in the
|
|
62
|
+
repo; kb and mkt ship it at their next release.
|
|
63
|
+
|
|
5
64
|
## [1.30.0] - 2026-09-01
|
|
6
65
|
|
|
7
66
|
### Added
|
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.32.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
|
@@ -639,6 +639,73 @@ function removeGlobalOrientHooks(removedRoots) {
|
|
|
639
639
|
return removedFrom;
|
|
640
640
|
}
|
|
641
641
|
|
|
642
|
+
// --- Per-turn reminder hook (F-046) ----------------------------------------
|
|
643
|
+
// `orient` fires once and decays; a session that never loads the skill never
|
|
644
|
+
// had the protocol at all. This hook re-arms one constant line every prompt.
|
|
645
|
+
// It is wired ALONGSIDE the orient hook and never instead of it, and it is its
|
|
646
|
+
// own function because `wireOrientHook` returns early when the session hook is
|
|
647
|
+
// already there -- which is the state of every existing installation.
|
|
648
|
+
// Owner-accepted per-turn cost: `ai_docs/vision/rulings.md` r19.
|
|
649
|
+
|
|
650
|
+
const REMIND_HOOK_TIMEOUT = 5;
|
|
651
|
+
|
|
652
|
+
function remindHookCommand(python, validator) {
|
|
653
|
+
return python + ' "' + validator + '" remind';
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
function findRemindHook(settings) {
|
|
657
|
+
const groups = settings && settings.hooks && settings.hooks.UserPromptSubmit;
|
|
658
|
+
if (!Array.isArray(groups)) return null;
|
|
659
|
+
for (const group of groups) {
|
|
660
|
+
if (!group || typeof group !== 'object') continue;
|
|
661
|
+
const inner = Array.isArray(group.hooks) ? group.hooks : [group];
|
|
662
|
+
for (const h of inner) {
|
|
663
|
+
const cmd = h && typeof h.command === 'string' ? h.command : '';
|
|
664
|
+
if (ORIENT_ENTRY_POINTS.some((n) => cmd.includes(n)) && / remind(\s|$)/.test(cmd)) {
|
|
665
|
+
return cmd;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
return null;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Writes the hook into `target` (an already-chosen settings file) unless it is
|
|
673
|
+
// there or the user opted out. Returns a code, never throws: an installer must
|
|
674
|
+
// not die on someone's settings file.
|
|
675
|
+
function wireRemindHookInto(target, python, validator) {
|
|
676
|
+
if (!python) return { code: 'no-python' };
|
|
677
|
+
if (!validator || ORIENT_UNSAFE_IN_PATH.test(validator)) return { code: 'unsafe-path', validator };
|
|
678
|
+
const state = orientSettingsState(target);
|
|
679
|
+
if (!state.ok) return { code: 'malformed', target, why: state.why };
|
|
680
|
+
if (orientMarkerHas(target)) return { code: 'opted-out', target };
|
|
681
|
+
const settings = state.settings;
|
|
682
|
+
const existing = findRemindHook(settings);
|
|
683
|
+
if (existing) return { code: 'already', target, command: existing };
|
|
684
|
+
const command = remindHookCommand(python, validator);
|
|
685
|
+
if (!settings.hooks) settings.hooks = {};
|
|
686
|
+
if (!Array.isArray(settings.hooks.UserPromptSubmit)) settings.hooks.UserPromptSubmit = [];
|
|
687
|
+
settings.hooks.UserPromptSubmit.push({
|
|
688
|
+
hooks: [{ type: 'command', command, timeout: REMIND_HOOK_TIMEOUT }],
|
|
689
|
+
});
|
|
690
|
+
try {
|
|
691
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
692
|
+
fs.writeFileSync(target, JSON.stringify(settings, null, 2) + String.fromCharCode(10), 'utf8');
|
|
693
|
+
} catch (e) {
|
|
694
|
+
return { code: 'write-failed', target, command, error: e.message };
|
|
695
|
+
}
|
|
696
|
+
return { code: 'wired', target, command };
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
// The machine-global twin of wireGlobalOrientHook, same target and validator.
|
|
701
|
+
function wireGlobalRemindHook(options) {
|
|
702
|
+
const client = options.client;
|
|
703
|
+
const validator = path.join(skillTarget(client), 'scripts', ORIENT_ENTRY_POINT);
|
|
704
|
+
if (!fs.existsSync(validator)) return { code: 'no-validator', validator };
|
|
705
|
+
return wireRemindHookInto(path.join(client.home, 'settings.json'), options.python, validator);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
|
|
642
709
|
module.exports = {
|
|
643
710
|
PACKAGE_ROOT,
|
|
644
711
|
SKILL_SOURCE,
|
|
@@ -649,6 +716,10 @@ module.exports = {
|
|
|
649
716
|
clientDetected,
|
|
650
717
|
skillTarget,
|
|
651
718
|
wireOrientHook,
|
|
719
|
+
wireRemindHookInto,
|
|
720
|
+
wireGlobalRemindHook,
|
|
721
|
+
remindHookCommand,
|
|
722
|
+
findRemindHook,
|
|
652
723
|
wireGlobalOrientHook,
|
|
653
724
|
removeGlobalOrientHooks,
|
|
654
725
|
detectPython,
|
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, detectPython,
|
|
6
|
+
wireGlobalOrientHook, wireGlobalRemindHook, detectPython,
|
|
7
7
|
} = require('./lib');
|
|
8
8
|
|
|
9
9
|
function installSkill(client) {
|
|
@@ -39,6 +39,18 @@ for (const client of CLIENTS) {
|
|
|
39
39
|
// marker remembers. A convenience must never fail an npm install: wrapped.
|
|
40
40
|
if (client.key === 'claude' && installed) {
|
|
41
41
|
try {
|
|
42
|
+
// F-046: the per-turn reminder, wired alongside and never instead of
|
|
43
|
+
// the session hook. `orient` decays; this one re-arms one constant line
|
|
44
|
+
// every prompt, and its first duty is the one that was missing -- decide
|
|
45
|
+
// whether the skill governs the work and, if so, load it once, because
|
|
46
|
+
// an unread protocol cannot be applied. Owner-accepted per-turn cost:
|
|
47
|
+
// ai_docs/vision/rulings.md r19.
|
|
48
|
+
const rr = wireGlobalRemindHook({ client, python: detectPython() });
|
|
49
|
+
if (rr.code === 'wired') {
|
|
50
|
+
console.log('🔗 Per-turn protocol reminder wired for every project on this machine');
|
|
51
|
+
console.log(` (${rr.target}). One constant line per prompt; remove that hook`);
|
|
52
|
+
console.log(' entry to opt out — it will not be re-added.');
|
|
53
|
+
}
|
|
42
54
|
const r = wireGlobalOrientHook({ client, python: detectPython() });
|
|
43
55
|
if (r.code === 'wired') {
|
|
44
56
|
console.log('🔗 Session orientation wired for every project on this machine');
|
|
@@ -157,6 +157,46 @@ Gemini CLI — wire the same command into its startup-hook mechanism if present;
|
|
|
157
157
|
- **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
158
|
- Like the CI gate (§2), if you copied the validator into the repo, the hook references that copy — keep both files current when you update the skill.
|
|
159
159
|
|
|
160
|
+
### Per-turn reminder: `remind` (UserPromptSubmit -- wired by default)
|
|
161
|
+
|
|
162
|
+
`orient` fires once, at session start; a long session drifts after it, and a session
|
|
163
|
+
that never loads the skill never had the protocol at all. Measured (F-046, 2026-09-06):
|
|
164
|
+
a release ran against a `GUIDE_release.md` the session never consulted, because
|
|
165
|
+
`SKILL.md` -- where the duty is written -- had not been read. `sdlc_check.py remind` prints
|
|
166
|
+
ONE constant line at every prompt, and its first duty is that one: decide whether the
|
|
167
|
+
skill governs the work in front of you and, if it does, load it once.
|
|
168
|
+
|
|
169
|
+
Cost: 478 characters, ~80 tokens per prompt, paid on every turn including
|
|
170
|
+
trivial ones. That is admissible only because the owner accepted it explicitly
|
|
171
|
+
(`ai_docs/vision/rulings.md` r19, the "no ceremony ratchet" Non-Goal's second door) --
|
|
172
|
+
and the acceptance was for a FLAT cost. Hence the contract, guarded by
|
|
173
|
+
`scripts/test_remind.py`: the line is **constant and reads nothing** (so nothing repo-
|
|
174
|
+
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 500 characters**, and it **can never
|
|
176
|
+
break a prompt** (argv ignored whole, always exit 0). A trivial turn pays a yes/no
|
|
177
|
+
judgement, never a load.
|
|
178
|
+
|
|
179
|
+
Installing the package wires it machine-wide on Claude Code, alongside the orientation
|
|
180
|
+
hook and never instead of it; **removing that hook entry is a standing opt-out.** To
|
|
181
|
+
wire it by hand:
|
|
182
|
+
|
|
183
|
+
```json
|
|
184
|
+
{
|
|
185
|
+
"hooks": {
|
|
186
|
+
"UserPromptSubmit": [
|
|
187
|
+
{
|
|
188
|
+
"hooks": [
|
|
189
|
+
{
|
|
190
|
+
"type": "command",
|
|
191
|
+
"command": "python \"C:\Users\<user>\.claude\skills\agentic-sdlc\scripts\sdlc_check.py\" remind"
|
|
192
|
+
}
|
|
193
|
+
]
|
|
194
|
+
}
|
|
195
|
+
]
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
160
200
|
## 5. Skill eval battery (release gate)
|
|
161
201
|
|
|
162
202
|
**Skill development only.** `test_*.py` and `evals/` are deliberately absent from the npm `files` allowlist — they never reach an installed consumer, so this section applies to whoever builds the skill, not to a project that uses it. (Consumers get `sdlc_check.py` + `sdlc_core.py`; §1–§4 are theirs.)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: agentic-sdlc
|
|
3
|
-
version: 1.
|
|
3
|
+
version: 1.32.0
|
|
4
4
|
description: Documentation-First SDLC protocol with risk-proportional triage, Vision as a guide, a complete Standalone mode and optional symbiosis with devPNT. Use for features, significant bugs, refactors, audits and documented maintenance.
|
|
5
5
|
author: Antonio Pinto (https://github.com/Antoneeo)
|
|
6
6
|
copyright: (c) 2026 Antonio Pinto
|
|
@@ -238,9 +238,11 @@ For any L3, run the spec elicitation round in `elicitation.md` BEFORE drafting t
|
|
|
238
238
|
|
|
239
239
|
**Architect before you list files.** Once the spec is known and BEFORE drafting the Impact, run the architect pass in `architect.md`: state what the feature requires the system to be able to DO (capabilities — verbs over domain nouns, no files), rule each one against the platform — **EXISTS** (name the component and where it lives) / **INADEQUATE** (name the gap) / **MISSING** (say what you searched) — and design what is missing as a component with **its own contract, in its own vocabulary**, of which this feature is one consumer. The output goes where that mode keeps designs — Standalone: the ANALYSIS `## Capability Ledger`; Hybrid: the `E-ISP`, above its Impacted Components map (`architect.md`; never a second copy in `ai_docs/`) — and it feeds the Impact. Note the coverage asymmetry: the validator backstop reads Standalone ANALYSIS files only, so in Hybrid the sole check that the pass ran is `review.md`'s ledger clause — which is why that clause fires on a MISSING ledger and not only on the rows of one that is present. Why it is its own step: a feature is not a unit of construction, and an agent that skips it builds the missing capability inside the feature's code path, where no component owns it and the next feature rebuilds it differently. The pass is a question, not a form — when every capability plainly exists, one line answers it.
|
|
240
240
|
|
|
241
|
+
**Would a guide be worth writing here?** Ask it while you analyze — this is when you are paying to build the model, and the answer decides whether the next session inherits it or re-derives it. Two questions, one line each in the ANALYSIS: does a CURRENT guide already cover what I am about to touch, and did understanding this force a model of a high-complexity component that none covers (then writing it is a duty, `guides.md` §1)?
|
|
242
|
+
|
|
241
243
|
**Blast-radius enumeration is an authoring duty, not a review finding.** Before writing the Impact (the list of what changes), for every symbol whose signature you change, thread a new field through, or that has more than one caller: mechanically enumerate EVERY consumer with the best symbol-graph tool your toolchain offers — an LSP/IDE call hierarchy or a find-usages / call-graph capability — with `grep` only as a last-resort fallback, and list the full set in the Impact. Anchor to symbol identity, not line numbers (they rot). This is deterministic and cheap: doing it up-front collapses the review into one pass, instead of the reviewer returning "you missed a consumer" one round at a time. Leaving completeness to the closure review is the myopia failure this whole workflow exists to prevent.
|
|
242
244
|
|
|
243
|
-
**Execute-Before-Specify — behavioural claims about existing code are probed, not read.** Every claim in an L3 design artifact of the form "the code today does X"
|
|
245
|
+
**Execute-Before-Specify — behavioural claims about existing code are probed, not read.** Every claim in an L3 design artifact of the form "the code today does X" MUST derive from an executed probe (a small script or assertion harness run against the real system in its real states) before the prose is written, never from reading the source alone. The duty is proportional: it covers the claims the design RESTS on — where a wrong answer changes the design — never every mention of existing code; structural facts (a file exists, a symbol's signature, who calls it) belong to the blast-radius enumeration above, not to a probe. A deliverable that is itself text — doctrine, a schema, a prompt, a reviewer definition — is probed the same way: replay it against the recorded case that motivated it (the incident artifact, the tool-usage transcript, the review that missed the defect), red under the old text, green under the new. "No probe is possible" is shown, never declared. And a probe must be able to FAIL: show it red first — negate the condition or the state it asserts — then green against the real system (the RED gesture of `tdd.md`, applied to the experiment); a probe you cannot make fail establishes nothing. Keep the probes as an assertion harness that ships WITH the artifact, in the repo (e.g. `ai_docs/solutions/harness_[feature]/` — a scratchpad dies with the session): the reviewer re-runs assertions instead of re-deriving claims, and every later revision re-verifies them for free. Field measurement (2026-08): eight consecutive design-review FAILs, every finding the class "the design describes intent, not behaviour", converged to a PASS in one round once each behavioural claim was executed first — probed claims held in every subsequent round; read claims failed in every one.
|
|
244
246
|
|
|
245
247
|
Standalone L3:
|
|
246
248
|
- Before creating a new `ANALYSIS_[feature].md`, search `ai_docs/solutions/` with glob/grep for an existing analysis on the same topic: if there is one, update it instead of duplicating it.
|
|
@@ -264,7 +266,7 @@ Hybrid L3:
|
|
|
264
266
|
- Isolate the work: run an L3 change on its own branch. In Hybrid, prefer a git worktree from the start — a running devPNT server locks `.devpnt/*.db` and blocks in-place branch switches/merges in the primary worktree.
|
|
265
267
|
- Modify surgically, consistently with the plan.
|
|
266
268
|
- Implementation work follows the TDD discipline in `tdd.md` (RED/GREEN/REFACTOR — the L2/L3 default; record the reason when it does not apply).
|
|
267
|
-
- Before implementing (L2/L3; L1 exempt), **consult the guide router**
|
|
269
|
+
- Before implementing (L2/L3; L1 exempt), **consult the guide router** — the verdict already declared with the triage level (`## Operative Guides`). Re-run the lookup here only if the work has moved to a topic the first one did not cover, and say so if the verdict changes.
|
|
268
270
|
- If the environment does not allow automated tests, declare the alternative verification and the reason.
|
|
269
271
|
- For bugs (L2/L3), follow the systematic debugging method in `debugging.md`.
|
|
270
272
|
- Circuit breaker: after 3 consecutive runs without progress on the tests, stop, switch to the systematic method in `debugging.md`, and ask for instructions if still stuck. `debugging.md` also covers **chronic fragility** — a component that breaks repeatedly across sessions is a comprehension + complexity signal (write the `source_kind: code` guide AND escalate a refactor), not a fourth patch.
|
|
@@ -278,7 +280,7 @@ Hybrid L3:
|
|
|
278
280
|
- For the review itself follow `review.md` (requesting and receiving findings) — the single definition, intended for reuse by the Hybrid review gates (devPNT-side wiring out of this unit's scope).
|
|
279
281
|
- Verify alignment with the local Vision or the devPNT M-VISION.
|
|
280
282
|
- If the work was governed by user-provided indications and is reusable, **PROPOSE distilling a guide** (proactive trigger, `guides.md` §1) — a proposal for the user, never a silent write, never from model knowledge.
|
|
281
|
-
- **Comprehension checkpoint
|
|
283
|
+
- **Comprehension checkpoint** — the backstop for the Phase-3 question, when the signal only became visible while implementing: did this session force a model of a high-complexity component no CURRENT guide covers? If yes, WRITE the `source_kind: code` guide now (a duty, not a proposal) and say so in the closure. One closure later that model is gone.
|
|
282
284
|
- Update only the documents actually impacted.
|
|
283
285
|
- **Update the workstream registry (`audit/handoff.md`)** — mandatory at every L3 closure: DELETE the closed workstream's `audit/HANDOFF_[feature].md` (history lives in git and in the Diary) and re-run `sdlc_check.py index`. Deleting the file *is* removing the row, and no other workstream's file is touched — that is the parallel-safety the registry exists for. Never edit the generated file: `validate` errors when it disagrees with its sources. The session-end rule and the L2 case: Write Triggers.
|
|
284
286
|
- **Aligned indexes (Poka-Yoke)**: if you created, moved or removed canonical documents (`vision/`, `reference/`, `architecture/`, `functional/`, `strategic/`):
|
|
@@ -60,6 +60,10 @@ already cover how to do this well?*
|
|
|
60
60
|
match was not targeted (T7). Two guides are legitimate only when they cover
|
|
61
61
|
distinct concerns — typically an operative guide plus the comprehension map of
|
|
62
62
|
the component being touched; three is a smell, not a thorough lookup.
|
|
63
|
+
- **Check a `code` guide against the code before you trust it.** No check can do
|
|
64
|
+
this for you (§6): the reader is the only one who sees that the map and the
|
|
65
|
+
component have parted. A divergence you observe is yours to close in the same
|
|
66
|
+
unit of work.
|
|
63
67
|
- **Under subagent dispatch** the consult happens at plan-authoring time (the
|
|
64
68
|
orchestrator populates each task's `guides` field); a context-free subagent
|
|
65
69
|
reads the pointers it was handed and does not run its own router lookup. See
|
|
@@ -271,17 +275,17 @@ state it explicitly when handing off a newly created guide.
|
|
|
271
275
|
|
|
272
276
|
## 6. Maintenance
|
|
273
277
|
|
|
274
|
-
- **Source changed**:
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
and
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
278
|
+
- **Source changed**: new snapshot, regenerate the guide from it (new
|
|
279
|
+
`source_hash`); a guide REPLACING another is `status: SUPERSEDED` on the old
|
|
280
|
+
one. `sdlc_check.py stale` (also under `--hybrid`) reports `[stale]` when a
|
|
281
|
+
guide's recorded hash and its snapshot file diverge — the signal to regenerate.
|
|
282
|
+
- **`source_kind: code` freshness is NOT mechanized.** `stale` compares the guide
|
|
283
|
+
to its snapshot, and a code snapshot is a frozen copy — so a map whose component
|
|
284
|
+
was rewritten reports FRESH forever (measured 2026-09-06: +388/-71 lines in the
|
|
285
|
+
guided module, `stale` silent). The duty is therefore the author's, twice: when
|
|
286
|
+
you MODIFY code a comprehension guide describes, refresh the guide in the same
|
|
287
|
+
closure (docs travel with the code); when you merely READ one, check it against
|
|
288
|
+
the code before trusting it. Too large to absorb → mark it `SUPERSEDED` rather
|
|
289
|
+
than leave a confident-wrong map badged CURRENT, which is worse than none.
|
|
286
290
|
- **Agent-global KB guides** use the same pipeline and validator via
|
|
287
291
|
`--root ~/.agentic-sdlc`; freshness via the same `stale` engine.
|
|
@@ -98,10 +98,12 @@ unfalsifiable, the same defect as an unnamed EXISTS or a faked router verdict.
|
|
|
98
98
|
own findings and can make the cap unreachable honestly. (1) *Patch-on-patch*:
|
|
99
99
|
each round's fixes are grafted onto the previous text until stale counts,
|
|
100
100
|
duplicated sections and internal contradictions are findings the revisions
|
|
101
|
-
themselves created.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
101
|
+
themselves created. The lean operative form is the FIRST draft's form, not the
|
|
102
|
+
round-3 rescue: the review request states the artifact's line count against a
|
|
103
|
+
budget the author fixed before drafting (default: the measured figure below), and
|
|
104
|
+
an artifact over budget is the reviewer's first finding, before content. When a
|
|
105
|
+
round's findings are of that artifact-consistency class, rewrite lean instead of
|
|
106
|
+
patching again. (2) *Revision narrative in the
|
|
105
107
|
artifact*: the artifact under review carries its own review archaeology
|
|
106
108
|
("v1.2 fixed…", "as the round-2 review noted…"). An artifact states what IS,
|
|
107
109
|
once; how it got there lives in the REVIEW_LOG row's notes, never in the
|
|
@@ -60,6 +60,15 @@ sdlc_core.set_profile(
|
|
|
60
60
|
"subagent_dispatch", "legacy_narrative_handoff", "question_discipline",
|
|
61
61
|
),
|
|
62
62
|
design_gate_between=("### 3. Request Analysis", "### 4. Development and Testing"),
|
|
63
|
+
remind_line=(
|
|
64
|
+
"first decide if this skill governs the work; if yes, LOAD it once -- an "
|
|
65
|
+
"unread protocol cannot be applied. Declare the level (L1/L2/L3/Spike) "
|
|
66
|
+
"and the guide-router verdict before acting; an imperative is a request "
|
|
67
|
+
"to triage. Name the mechanism before fixing the first defect you can "
|
|
68
|
+
"measure -- symptom patches recur and stack. Replay governing text -- "
|
|
69
|
+
"doctrine, a prompt, a schema -- against the case that motivated it: red "
|
|
70
|
+
"under the old text, green under the new."
|
|
71
|
+
),
|
|
63
72
|
)
|
|
64
73
|
|
|
65
74
|
|
|
@@ -268,16 +268,18 @@ _PROFILE = {
|
|
|
268
268
|
"support_files": (),
|
|
269
269
|
"capabilities": frozenset(),
|
|
270
270
|
"design_gate_between": (),
|
|
271
|
+
"remind_line": "",
|
|
271
272
|
}
|
|
272
273
|
|
|
273
274
|
|
|
274
275
|
def set_profile(skill_name, support_files=(), capabilities=(), unit_noun="feature",
|
|
275
|
-
design_gate_between=()):
|
|
276
|
+
design_gate_between=(), remind_line=""):
|
|
276
277
|
_PROFILE.update(skill_name=skill_name,
|
|
277
278
|
unit_noun=unit_noun,
|
|
278
279
|
support_files=tuple(support_files),
|
|
279
280
|
capabilities=frozenset(capabilities),
|
|
280
|
-
design_gate_between=tuple(design_gate_between)
|
|
281
|
+
design_gate_between=tuple(design_gate_between),
|
|
282
|
+
remind_line=remind_line)
|
|
281
283
|
|
|
282
284
|
|
|
283
285
|
def profile():
|
|
@@ -1796,6 +1798,43 @@ def cmd_check(root, strict=False, hybrid=False):
|
|
|
1796
1798
|
|
|
1797
1799
|
# --------------------------------------------------------------------- gate
|
|
1798
1800
|
|
|
1801
|
+
# --- UserPromptSubmit reminder hook (F-046) --------------------------------
|
|
1802
|
+
# `orient` fires once per session and decays; a session that never invokes the
|
|
1803
|
+
# skill never had the protocol at all. Measured 2026-09-06: a release ran in
|
|
1804
|
+
# this repo against a `GUIDE_release.md` the session never consulted, because
|
|
1805
|
+
# SKILL.md -- where the duty is written -- had not been read.
|
|
1806
|
+
#
|
|
1807
|
+
# The carrier is kb's (F-041 item B), lifted here so all three lenses share one
|
|
1808
|
+
# machine instead of two that do the same thing. Its contract is kept: the line
|
|
1809
|
+
# is CONSTANT and reads NOTHING -- nothing repo- or session-controlled may ride
|
|
1810
|
+
# text injected every turn, and zero reads is what keeps the per-turn cost flat.
|
|
1811
|
+
# What changed is the payload's first duty and the wiring. First duty: decide
|
|
1812
|
+
# whether the skill governs this work and, if it does, read it once -- an
|
|
1813
|
+
# unread protocol cannot be applied, which is the failure above. Wiring: a
|
|
1814
|
+
# default instead of opt-in, on the owner's explicit acceptance of the per-turn
|
|
1815
|
+
# cost (`vision/rulings.md` r19; the "no ceremony ratchet" Non-Goal admits a
|
|
1816
|
+
# never-zero cost by that acceptance, and a trivial turn pays a yes/no
|
|
1817
|
+
# judgement, never a load).
|
|
1818
|
+
|
|
1819
|
+
REMIND_FALLBACK = (
|
|
1820
|
+
"decide whether the {skill} skill governs this work; if it does, load it "
|
|
1821
|
+
"once before acting -- an unread protocol cannot be applied."
|
|
1822
|
+
)
|
|
1823
|
+
|
|
1824
|
+
|
|
1825
|
+
def cmd_remind(args):
|
|
1826
|
+
"""UserPromptSubmit hook: print the lens's constant per-turn line. Always 0.
|
|
1827
|
+
|
|
1828
|
+
argv is ignored whole and nothing is read: a hook surface must not fail, and
|
|
1829
|
+
a flag typo must not cost a turn's context."""
|
|
1830
|
+
try:
|
|
1831
|
+
prof = profile()
|
|
1832
|
+
line = prof.get("remind_line") or REMIND_FALLBACK.format(skill=prof["skill_name"])
|
|
1833
|
+
print(f"{prof['skill_name']}: {line}")
|
|
1834
|
+
except Exception:
|
|
1835
|
+
pass
|
|
1836
|
+
return 0
|
|
1837
|
+
|
|
1799
1838
|
def cmd_gate(args):
|
|
1800
1839
|
file_path = args.file or ""
|
|
1801
1840
|
if args.hook:
|
|
@@ -2229,6 +2268,12 @@ def cmd_migrate(root, args):
|
|
|
2229
2268
|
# --------------------------------------------------------------------- main
|
|
2230
2269
|
|
|
2231
2270
|
def main(argv=None):
|
|
2271
|
+
# `remind` is intercepted BEFORE argparse (kb's F-041 rule, kept): it is a
|
|
2272
|
+
# hook surface fired on every prompt, so a stray flag must cost a usage
|
|
2273
|
+
# message and a non-zero exit on a turn, not the turn itself.
|
|
2274
|
+
if (argv if argv is not None else sys.argv[1:])[:1] == ["remind"]:
|
|
2275
|
+
return cmd_remind(None)
|
|
2276
|
+
|
|
2232
2277
|
common = argparse.ArgumentParser(add_help=False)
|
|
2233
2278
|
common.add_argument("--root", help="project root (default: walk up until a docs root is found)")
|
|
2234
2279
|
common.add_argument("--docs-dir", dest="docs_dir",
|
|
@@ -2273,6 +2318,10 @@ def main(argv=None):
|
|
|
2273
2318
|
sub.add_parser("orient", parents=[common, hybrid_opt],
|
|
2274
2319
|
help="SessionStart hook: emit docs-root orientation to stdout (fail-open, zero-execution)")
|
|
2275
2320
|
|
|
2321
|
+
sub.add_parser("remind", parents=[common],
|
|
2322
|
+
help="UserPromptSubmit hook: print the constant per-turn line "
|
|
2323
|
+
"(fail-open, zero-read, zero-execution)")
|
|
2324
|
+
|
|
2276
2325
|
pp = sub.add_parser("plan", parents=[common],
|
|
2277
2326
|
help="Subagent Execution: validate/brief a PLAN_[feature].md (zero-execution)")
|
|
2278
2327
|
pp_sub = pp.add_subparsers(dest="plan_cmd", required=True)
|
|
@@ -2296,7 +2345,7 @@ def main(argv=None):
|
|
|
2296
2345
|
# nothing is being guessed. Refusing here would make the guard block the
|
|
2297
2346
|
# one command that ends the ambiguity.
|
|
2298
2347
|
discovered, name = None, args.from_dir
|
|
2299
|
-
elif args.cmd
|
|
2348
|
+
elif args.cmd in ("orient", "remind"):
|
|
2300
2349
|
# The SessionStart hook is fail-open by contract: it must never block a
|
|
2301
2350
|
# session, not even on a half-migrated tree. It orients on the default
|
|
2302
2351
|
# and says so rather than exiting non-zero.
|
|
@@ -2311,6 +2360,8 @@ def main(argv=None):
|
|
|
2311
2360
|
return cmd_gate(args)
|
|
2312
2361
|
if args.cmd == "orient":
|
|
2313
2362
|
return cmd_orient(args)
|
|
2363
|
+
if args.cmd == "remind":
|
|
2364
|
+
return cmd_remind(args)
|
|
2314
2365
|
|
|
2315
2366
|
root = Path(args.root).resolve() if args.root else (discovered or find_project_root())
|
|
2316
2367
|
if args.cmd == "check":
|