@antoneeo/agentic-sdlc-skill 1.20.2 → 1.21.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 +253 -0
- package/README.md +1 -0
- package/gemini-extension.json +1 -1
- package/package.json +50 -50
- package/scripts/init.js +31 -10
- package/scripts/lib.js +227 -177
- package/skills/agentic-sdlc-skill/SKILL.md +6 -5
- package/skills/agentic-sdlc-skill/review.md +12 -0
- package/skills/agentic-sdlc-skill/scripts/sdlc_core.py +199 -3
- package/skills/agentic-sdlc-skill/templates.md +86 -33
package/scripts/lib.js
CHANGED
|
@@ -1,177 +1,227 @@
|
|
|
1
|
-
// Shared helpers for the Agentic SDLC npm scripts (init / postinstall / preuninstall).
|
|
2
|
-
// Single source for client detection and skill-target paths: init and postinstall
|
|
3
|
-
// must never disagree on what "Claude Code is installed" means.
|
|
4
|
-
|
|
5
|
-
const fs = require('fs');
|
|
6
|
-
const path = require('path');
|
|
7
|
-
const os = require('os');
|
|
8
|
-
const { execSync } = require('child_process');
|
|
9
|
-
|
|
10
|
-
const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
11
|
-
const SKILL_SOURCE = path.join(PACKAGE_ROOT, 'skills', 'agentic-sdlc-skill');
|
|
12
|
-
const TEMPLATES_PATH = path.join(SKILL_SOURCE, 'templates.md');
|
|
13
|
-
// The directory name each client loads the skill from. Derived from the manifest,
|
|
14
|
-
// never hard-coded by a consumer: three distributions share these scripts, and a
|
|
15
|
-
// literal here is how a copy-fork starts installing under its sibling's name.
|
|
16
|
-
const INSTALLED_SKILL_NAME = (() => {
|
|
17
|
-
const m = fs.readFileSync(path.join(SKILL_SOURCE, 'SKILL.md'), 'utf8').match(/^name:\s*(\S+)/m);
|
|
18
|
-
if (!m) throw new Error(`SKILL.md carries no 'name:' field: ${SKILL_SOURCE}`);
|
|
19
|
-
return m[1];
|
|
20
|
-
})();
|
|
21
|
-
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
1
|
+
// Shared helpers for the Agentic SDLC npm scripts (init / postinstall / preuninstall).
|
|
2
|
+
// Single source for client detection and skill-target paths: init and postinstall
|
|
3
|
+
// must never disagree on what "Claude Code is installed" means.
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
const { execSync } = require('child_process');
|
|
9
|
+
|
|
10
|
+
const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
11
|
+
const SKILL_SOURCE = path.join(PACKAGE_ROOT, 'skills', 'agentic-sdlc-skill');
|
|
12
|
+
const TEMPLATES_PATH = path.join(SKILL_SOURCE, 'templates.md');
|
|
13
|
+
// The directory name each client loads the skill from. Derived from the manifest,
|
|
14
|
+
// never hard-coded by a consumer: three distributions share these scripts, and a
|
|
15
|
+
// literal here is how a copy-fork starts installing under its sibling's name.
|
|
16
|
+
const INSTALLED_SKILL_NAME = (() => {
|
|
17
|
+
const m = fs.readFileSync(path.join(SKILL_SOURCE, 'SKILL.md'), 'utf8').match(/^name:\s*(\S+)/m);
|
|
18
|
+
if (!m) throw new Error(`SKILL.md carries no 'name:' field: ${SKILL_SOURCE}`);
|
|
19
|
+
return m[1];
|
|
20
|
+
})();
|
|
21
|
+
|
|
22
|
+
// The family's lens table, read from the shared `routing.md` rather than restated
|
|
23
|
+
// here. Same reason as INSTALLED_SKILL_NAME above, and the same failure it prevents:
|
|
24
|
+
// BOTH the row for this lens and the rows for its siblings used to be literals copied
|
|
25
|
+
// between distributions, so kb and mkt wrote a multi-lens note announcing themselves
|
|
26
|
+
// as the code lens. routing.md is one of the byte-identical shared files, so this
|
|
27
|
+
// lookup cannot drift between distributions.
|
|
28
|
+
// Lazy on purpose: preuninstall.js needs INSTALLED_SKILL_NAME and nothing else, and
|
|
29
|
+
// must keep working on an installation damaged badly enough to have lost routing.md.
|
|
30
|
+
function lensTable() {
|
|
31
|
+
const routing = path.join(SKILL_SOURCE, 'routing.md');
|
|
32
|
+
if (!fs.existsSync(routing)) throw new Error(`routing.md missing from the skill: ${SKILL_SOURCE}`);
|
|
33
|
+
// Parsed as a table, not matched with a regex: a mis-escaped pattern matches the
|
|
34
|
+
// empty string and yields undefined instead of throwing, which is how this lookup
|
|
35
|
+
// failed the first time it was written.
|
|
36
|
+
const table = new Map();
|
|
37
|
+
for (const line of fs.readFileSync(routing, 'utf8').split(/\r?\n/)) {
|
|
38
|
+
const cells = line.split('|').map((cell) => cell.trim());
|
|
39
|
+
if (cells.length < 4) continue;
|
|
40
|
+
const [, lens, skill] = cells;
|
|
41
|
+
if (!/^[a-z]+$/.test(lens) || !/^`[a-z0-9-]+`$/.test(skill)) continue;
|
|
42
|
+
const name = skill.slice(1, -1);
|
|
43
|
+
// A duplicate row would otherwise be won silently by whichever came first, and a
|
|
44
|
+
// wrong routing.md propagates byte-identically to every distribution.
|
|
45
|
+
if (table.has(name)) throw new Error(`routing.md lists '${name}' more than once`);
|
|
46
|
+
table.set(name, lens);
|
|
47
|
+
}
|
|
48
|
+
if (!table.size) throw new Error(`routing.md carries no lens table: ${routing}`);
|
|
49
|
+
return table;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function selfLens() {
|
|
53
|
+
const lens = lensTable().get(INSTALLED_SKILL_NAME);
|
|
54
|
+
if (!lens) throw new Error(`routing.md has no lens row for '${INSTALLED_SKILL_NAME}'`);
|
|
55
|
+
return lens;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Sibling lenses of the same family: one shared core, one docs tree, a different
|
|
59
|
+
// fidelity discipline each. Keyed by the installed skill directory name.
|
|
60
|
+
function siblingLenses() {
|
|
61
|
+
const table = lensTable();
|
|
62
|
+
selfLens(); // this lens must be in the table too
|
|
63
|
+
table.delete(INSTALLED_SKILL_NAME);
|
|
64
|
+
return Object.fromEntries(table);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// One entry per supported AI client. `home` may be overridden by an env var
|
|
68
|
+
// (Claude Desktop / portable installs); presence of the home dir counts as
|
|
69
|
+
// detection even when the CLI is not on PATH.
|
|
70
|
+
const CLIENTS = [
|
|
71
|
+
{
|
|
72
|
+
key: 'claude',
|
|
73
|
+
label: 'Claude Code',
|
|
74
|
+
cmd: 'claude',
|
|
75
|
+
home: process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude'),
|
|
76
|
+
envVar: 'CLAUDE_CONFIG_DIR',
|
|
77
|
+
reload: 'Restart Claude Code to load it. Invoke via Skill tool as "agentic-sdlc".',
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
key: 'gemini',
|
|
81
|
+
label: 'Gemini CLI',
|
|
82
|
+
cmd: 'gemini',
|
|
83
|
+
home: process.env.GEMINI_HOME || path.join(os.homedir(), '.gemini'),
|
|
84
|
+
envVar: 'GEMINI_HOME',
|
|
85
|
+
reload: 'Run "gemini skills reload" or restart Gemini CLI to load it.',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
key: 'codex',
|
|
89
|
+
label: 'Codex AI',
|
|
90
|
+
cmd: 'codex',
|
|
91
|
+
home: process.env.CODEX_HOME || path.join(os.homedir(), '.codex'),
|
|
92
|
+
envVar: 'CODEX_HOME',
|
|
93
|
+
reload: 'Restart Codex to load it. Invoke it as "$agentic-sdlc" or by asking for Agentic SDLC.',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
// Google Antigravity 2.0 discovers global agent skills under
|
|
97
|
+
// ~/.gemini/config/skills/ -- the SAME home the legacy Gemini CLI claims.
|
|
98
|
+
// To avoid a shared-home double-install (P-TM T1), this entry sets:
|
|
99
|
+
// - skillsSubdir 'config/skills': distinct target from gemini's ~/.gemini/skills
|
|
100
|
+
// - homeMarker on ~/.gemini/config/skills: detection never fires on bare
|
|
101
|
+
// ~/.gemini (which every Antigravity user has); only the Antigravity skills
|
|
102
|
+
// dir, the `agy` CLI, or ANTIGRAVITY_HOME count as "Antigravity installed".
|
|
103
|
+
key: 'antigravity',
|
|
104
|
+
label: 'Google Antigravity',
|
|
105
|
+
cmd: 'agy',
|
|
106
|
+
home: process.env.ANTIGRAVITY_HOME || path.join(os.homedir(), '.gemini'),
|
|
107
|
+
envVar: 'ANTIGRAVITY_HOME',
|
|
108
|
+
skillsSubdir: 'config/skills',
|
|
109
|
+
homeMarker: path.join(
|
|
110
|
+
process.env.ANTIGRAVITY_HOME || path.join(os.homedir(), '.gemini'),
|
|
111
|
+
'config',
|
|
112
|
+
'skills',
|
|
113
|
+
),
|
|
114
|
+
reload: 'Restart Antigravity, or run "agy skills reload", to load it. Invoke by asking for Agentic SDLC.',
|
|
115
|
+
},
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
function commandExists(cmd) {
|
|
119
|
+
try {
|
|
120
|
+
execSync(`${cmd} --version`, { stdio: 'ignore' });
|
|
121
|
+
return true;
|
|
122
|
+
} catch (e) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function clientDetected(client) {
|
|
128
|
+
// An entry may override the fs-existence probe with a `homeMarker` (a more
|
|
129
|
+
// specific path than the bare home) so two clients sharing a home dir do not
|
|
130
|
+
// both fire on its mere existence. Entries without a marker check `home`
|
|
131
|
+
// exactly as before (backward-compatible).
|
|
132
|
+
const homePathToCheck = client.homeMarker || client.home;
|
|
133
|
+
return commandExists(client.cmd)
|
|
134
|
+
|| Boolean(process.env[client.envVar])
|
|
135
|
+
|| fs.existsSync(homePathToCheck);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function skillTarget(client) {
|
|
139
|
+
// An entry may override the default `skills` sub-path with `skillsSubdir`
|
|
140
|
+
// (split on '/' to keep cross-platform path.join correctness). Entries
|
|
141
|
+
// without it resolve to <home>/skills/agentic-sdlc exactly as before.
|
|
142
|
+
const subdir = client.skillsSubdir ? client.skillsSubdir.split('/') : ['skills'];
|
|
143
|
+
return path.join(client.home, ...subdir, INSTALLED_SKILL_NAME);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function copyRecursive(src, dest) {
|
|
147
|
+
if (typeof fs.cpSync === 'function') {
|
|
148
|
+
fs.cpSync(src, dest, { recursive: true, force: true });
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
// Fallback for Node < 16.7
|
|
152
|
+
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
|
|
153
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
154
|
+
const s = path.join(src, entry.name);
|
|
155
|
+
const d = path.join(dest, entry.name);
|
|
156
|
+
if (entry.isDirectory()) copyRecursive(s, d);
|
|
157
|
+
else fs.copyFileSync(s, d);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Parse templates.md into { headingText: [fencedBlock, ...] }.
|
|
163
|
+
* Templates are single-sourced there: the init script must extract them
|
|
164
|
+
* instead of carrying its own inline copies (which historically drifted).
|
|
165
|
+
*/
|
|
166
|
+
function loadTemplates() {
|
|
167
|
+
const text = fs.readFileSync(TEMPLATES_PATH, 'utf8');
|
|
168
|
+
const lines = text.split(/\r?\n/);
|
|
169
|
+
const sections = {};
|
|
170
|
+
let heading = null;
|
|
171
|
+
let block = null;
|
|
172
|
+
for (const line of lines) {
|
|
173
|
+
const h = line.match(/^##\s+(.*)$/);
|
|
174
|
+
if (h && block === null) {
|
|
175
|
+
heading = h[1].trim();
|
|
176
|
+
sections[heading] = sections[heading] || [];
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (/^```/.test(line)) {
|
|
180
|
+
if (block === null) {
|
|
181
|
+
block = [];
|
|
182
|
+
} else {
|
|
183
|
+
if (heading) sections[heading].push(block.join('\n') + '\n');
|
|
184
|
+
block = null;
|
|
185
|
+
}
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (block !== null) block.push(line);
|
|
189
|
+
}
|
|
190
|
+
return sections;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Return the Nth fenced block of the section whose heading contains `needle`.
|
|
195
|
+
* Throws with a clear message when missing: writing a wrong or empty
|
|
196
|
+
* boilerplate silently would be worse than failing the init.
|
|
197
|
+
*/
|
|
198
|
+
function templateFor(sections, needle, index = 0) {
|
|
199
|
+
const heading = Object.keys(sections).find((h) => h.includes(needle));
|
|
200
|
+
const blocks = heading ? sections[heading] : undefined;
|
|
201
|
+
if (!blocks || !blocks[index]) {
|
|
202
|
+
throw new Error(
|
|
203
|
+
`Template section containing "${needle}" (block ${index}) not found in ${TEMPLATES_PATH}. ` +
|
|
204
|
+
'The package is corrupted or templates.md was restructured: fix templates.md, do not improvise content.'
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
return blocks[index];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
module.exports = {
|
|
211
|
+
PACKAGE_ROOT,
|
|
212
|
+
SKILL_SOURCE,
|
|
213
|
+
INSTALLED_SKILL_NAME,
|
|
214
|
+
TEMPLATES_PATH,
|
|
215
|
+
CLIENTS,
|
|
216
|
+
commandExists,
|
|
217
|
+
clientDetected,
|
|
218
|
+
skillTarget,
|
|
219
|
+
copyRecursive,
|
|
220
|
+
loadTemplates,
|
|
221
|
+
templateFor,
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
// Lazy: reading routing.md is deferred to the consumer that actually asks.
|
|
225
|
+
Object.defineProperty(module.exports, 'SELF_LENS', { enumerable: true, get: selfLens });
|
|
226
|
+
Object.defineProperty(module.exports, 'SIBLING_LENSES', { enumerable: true, get: siblingLenses });
|
|
227
|
+
|
|
@@ -64,9 +64,10 @@ Triage decides IF documentation is due; this table decides WHICH document each e
|
|
|
64
64
|
| `solutions/ANALYSIS_[feature].md` | Every L3, after elicitation and before any code. On topic match with an existing analysis, update that one instead of a new file. A capability the architect pass splits out as its own unit of change (`architect.md` §4) gets its own ANALYSIS, and the two documents name each other. | 3 |
|
|
65
65
|
| `solutions/SPIKE_[topic].md` | Closing any Spike — including a failed one (a negative outcome is still an outcome). | — |
|
|
66
66
|
| `vision/features/VISION_[feature].md` | Feature known multi-milestone at analysis time, OR the retroactive trigger: you are about to create the SECOND `ANALYSIS_*` on the same theme — extract the shared feature vision first, then let both analyses reference it. | 3 |
|
|
67
|
-
| `audit/handoff.md` (workstream registry) |
|
|
68
|
-
| `audit/
|
|
69
|
-
| `audit/
|
|
67
|
+
| `audit/handoff.md` (workstream registry) | **Never by hand — generated by `sdlc_check.py index` from the `HANDOFF_*.md` sources**, and `validate` errors when the two disagree. Regenerate at every L3 closure and at session end; the `Date:` header is derived, so no writer touches it. It is an inventory for lookup, not a work board: no assignment, no due dates, no ordering, no holder. | 5 / session end |
|
|
68
|
+
| `audit/HANDOFF_[feature].md` | **One per OPEN workstream, with or without volatile state** — it is the authored home of that workstream's registry row (frontmatter `workstream`/`level`/`branch`/`status`/`since`/`next`/`details`/`updated`), so no file means no row. Also carries the resume logistics; **the ANALYSIS Diary keeps the durable narrative (DRY)**, and this file is DELETED at closure — deleting it *is* removing the row. | 4 / 5 / session end |
|
|
69
|
+
| `audit/project_notes.md` | A note true for the whole project rather than for one workstream (release pending, environment quirk). Appended verbatim to the generated registry; it exists so regenerating cannot destroy notes that belong to no workstream. | 5 / session end |
|
|
70
|
+
| `audit/handoff.md` — converting an existing project (hand-written or pre-1.17 narrative) | **Lazily, at the first write — and then ALL AT ONCE.** Converting one row at a time is the state that loses the others, so `index` refuses to write while anything in the file is unaccounted for and names it. Until the first source file exists nothing generates, nothing errors, and the file is read verbatim exactly as today. | 5 / session end |
|
|
70
71
|
| `audit/audit_plan.md` (Standalone) | Bootstrap, and whenever a mapped area changes state (`sdlc_check.py mark` records the reference — git hash, else UTC timestamp). | 1 |
|
|
71
72
|
| `reference/GUIDE_[topic].md` (`source_kind: document`) | Origin+purpose test (`guides.md`), or a proactive proposal the user accepted. Propose, never a silent write, never from model knowledge. | 4 / 5 |
|
|
72
73
|
| `reference/GUIDE_[topic].md` (`source_kind: code`) | Recognized high-complexity component/feature/layer with no CURRENT guide — including one that breaks repeatedly across sessions → **duty to write autonomously** (no proposal; additive, code-anchored, reversible). Fidelity floor: every claim traces to a code excerpt. Signals + guard-rails: `guides.md` §1. Write it as soon as you recognize the signal; the Phase-5 Comprehension checkpoint is the backstop that asks the question, never the only moment it may fire. | 4 / 5 |
|
|
@@ -188,7 +189,7 @@ gate) instead of requiring an IN_PROGRESS ANALYSIS.
|
|
|
188
189
|
|
|
189
190
|
### 1. Audit and Alignment
|
|
190
191
|
|
|
191
|
-
- Read `ai_docs/audit/handoff.md` if it exists — the **workstream registry**: one row per open workstream, so you see at a glance what is in PROGRESS, on which branch, since when, before touching anything. If a row's Date/Branch are inconsistent with reality, treat that row as history. When resuming a specific workstream, read its `audit/HANDOFF_[feature].md` (
|
|
192
|
+
- Read `ai_docs/audit/handoff.md` if it exists — the **workstream registry**: one row per open workstream, so you see at a glance what is in PROGRESS, on which branch, since when, before touching anything. It is generated from the `HANDOFF_*.md` files, so it is read here and never edited here. If a row's Date/Branch are inconsistent with reality, treat that row as history. When resuming a specific workstream, read its `audit/HANDOFF_[feature].md` (its row plus the resume logistics) AND its ANALYSIS Diary (the durable narrative) — the registry row points at both. A hand-written or pre-1.17 narrative handoff still works and is converted when you next write it, not now (Write Triggers).
|
|
192
193
|
- Read `ai_docs/README.md` (curated must-reads), `ai_docs/INDEX.md` (generated manifest of all canonical docs) and `ai_docs/reference/INDEX.md` (the guide router) to know what exists before exploring the code. The router is a mandatory read, not an optional one: it is the only orientation step that tells you a guide already governs the work you are about to do. On a project with no guides yet it exists as an empty stub (`sdlc_check.py index` writes it precisely so the mandatory read has something to read) — the honest verdict there is `router: no match`, and if the file is genuinely absent, say `router: absent (no router file)` and regenerate it rather than inventing a match. `solutions/` and `audit/` are not indexed per file: search them with glob/grep.
|
|
193
194
|
- Recommended default: a SessionStart hook (`ENFORCEMENT.md` §4) emits this orientation automatically at session start (README + INDEX + guide router + handoff + triage reminder), so the router reaches the context even in a session that never opens Phase 1 explicitly. Wire it wherever Python is available; when it is not wired, do these reads manually as above — the process never depends on it, and it fails open (a missing/empty `ai_docs/` never blocks the session).
|
|
194
195
|
- If `ai_docs/` is missing or incomplete, create the structure and the **bootstrap set** by analyzing the project in batches: `README.md`, the three `vision/` docs (`Status: DRAFT`), `strategic/architecture.md`, `strategic/existing_features.md` and — Standalone — `audit/audit_plan.md`; then regenerate `INDEX.md`. Nothing else is mandatory at bootstrap (per-document triggers: Write Triggers).
|
|
@@ -255,7 +256,7 @@ Hybrid L3:
|
|
|
255
256
|
- 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.
|
|
256
257
|
- **Comprehension checkpoint**: ask explicitly — *did this session force me to build a model of a high-complexity component that no CURRENT guide covers?* If yes, WRITE the `source_kind: code` guide now (a duty, not a proposal — `guides.md` §1) and say so in the closure. The knowledge you paid to build is at its most complete right here; one closure later it is gone, and the next session re-derives it or breaks the component from partial understanding.
|
|
257
258
|
- Update only the documents actually impacted.
|
|
258
|
-
- **Update the workstream registry (`audit/handoff.md`)** — mandatory at every L3 closure:
|
|
259
|
+
- **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.
|
|
259
260
|
- **Aligned indexes (Poka-Yoke)**: if you created, moved or removed canonical documents (`vision/`, `reference/`, `architecture/`, `functional/`, `strategic/`):
|
|
260
261
|
- regenerate the manifest with `sdlc_check.py index` (writes `ai_docs/INDEX.md`) — never write it by hand;
|
|
261
262
|
- if the document is a must-read, add/update its line in the curated `README.md`;
|
|
@@ -85,6 +85,14 @@ When you hand work to a reviewer (human or agent), give them:
|
|
|
85
85
|
should have been. The design reviewer checks that every threat surface the change
|
|
86
86
|
touches has a matching security requirement.
|
|
87
87
|
|
|
88
|
+
**The verdict travels back as the reviewer's own final output** — the text it
|
|
89
|
+
returns when it finishes, nothing else. A reviewer that tries to message the
|
|
90
|
+
requester mid-run depends on a delivery channel it cannot verify (a subagent
|
|
91
|
+
addressed by agent TYPE rather than by session gets no such channel, and the
|
|
92
|
+
attempt fails silently); a requester that waits for such a message stalls
|
|
93
|
+
holding a verdict that already exists. State the return form when you request
|
|
94
|
+
the review, and read the verdict where it actually arrives.
|
|
95
|
+
|
|
88
96
|
Never ask a reviewer to "review my session" or "review what I just did"
|
|
89
97
|
without the artifacts above — that forces them to reconstruct scope from
|
|
90
98
|
conversation instead of reviewing the change itself. Say which finding
|
|
@@ -109,6 +117,10 @@ When you are the reviewer:
|
|
|
109
117
|
|
|
110
118
|
- Verify claims against the real source, not against the diff's own
|
|
111
119
|
description of itself.
|
|
120
|
+
- **Your verdict is your final output.** Deliver findings and verdict as the text
|
|
121
|
+
you return when you finish — never only through a message to the requester, a
|
|
122
|
+
channel you cannot verify and which fails silently when it is not there
|
|
123
|
+
(see `## Requesting`).
|
|
112
124
|
- Cite evidence as `file:line` for every finding — a finding without a
|
|
113
125
|
location is not actionable.
|
|
114
126
|
- Keep severity honest: do not inflate a style preference to a blocker, and
|