@antoneeo/agentic-sdlc-skill 1.17.0 → 1.20.2

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/scripts/lib.js CHANGED
@@ -1,168 +1,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
-
14
- // One entry per supported AI client. `home` may be overridden by an env var
15
- // (Claude Desktop / portable installs); presence of the home dir counts as
16
- // detection even when the CLI is not on PATH.
17
- const CLIENTS = [
18
- {
19
- key: 'claude',
20
- label: 'Claude Code',
21
- cmd: 'claude',
22
- home: process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude'),
23
- envVar: 'CLAUDE_CONFIG_DIR',
24
- reload: 'Restart Claude Code to load it. Invoke via Skill tool as "agentic-sdlc".',
25
- },
26
- {
27
- key: 'gemini',
28
- label: 'Gemini CLI',
29
- cmd: 'gemini',
30
- home: process.env.GEMINI_HOME || path.join(os.homedir(), '.gemini'),
31
- envVar: 'GEMINI_HOME',
32
- reload: 'Run "gemini skills reload" or restart Gemini CLI to load it.',
33
- },
34
- {
35
- key: 'codex',
36
- label: 'Codex AI',
37
- cmd: 'codex',
38
- home: process.env.CODEX_HOME || path.join(os.homedir(), '.codex'),
39
- envVar: 'CODEX_HOME',
40
- reload: 'Restart Codex to load it. Invoke it as "$agentic-sdlc" or by asking for Agentic SDLC.',
41
- },
42
- {
43
- // Google Antigravity 2.0 discovers global agent skills under
44
- // ~/.gemini/config/skills/ -- the SAME home the legacy Gemini CLI claims.
45
- // To avoid a shared-home double-install (P-TM T1), this entry sets:
46
- // - skillsSubdir 'config/skills': distinct target from gemini's ~/.gemini/skills
47
- // - homeMarker on ~/.gemini/config/skills: detection never fires on bare
48
- // ~/.gemini (which every Antigravity user has); only the Antigravity skills
49
- // dir, the `agy` CLI, or ANTIGRAVITY_HOME count as "Antigravity installed".
50
- key: 'antigravity',
51
- label: 'Google Antigravity',
52
- cmd: 'agy',
53
- home: process.env.ANTIGRAVITY_HOME || path.join(os.homedir(), '.gemini'),
54
- envVar: 'ANTIGRAVITY_HOME',
55
- skillsSubdir: 'config/skills',
56
- homeMarker: path.join(
57
- process.env.ANTIGRAVITY_HOME || path.join(os.homedir(), '.gemini'),
58
- 'config',
59
- 'skills',
60
- ),
61
- reload: 'Restart Antigravity, or run "agy skills reload", to load it. Invoke by asking for Agentic SDLC.',
62
- },
63
- ];
64
-
65
- function commandExists(cmd) {
66
- try {
67
- execSync(`${cmd} --version`, { stdio: 'ignore' });
68
- return true;
69
- } catch (e) {
70
- return false;
71
- }
72
- }
73
-
74
- function clientDetected(client) {
75
- // An entry may override the fs-existence probe with a `homeMarker` (a more
76
- // specific path than the bare home) so two clients sharing a home dir do not
77
- // both fire on its mere existence. Entries without a marker check `home`
78
- // exactly as before (backward-compatible).
79
- const homePathToCheck = client.homeMarker || client.home;
80
- return commandExists(client.cmd)
81
- || Boolean(process.env[client.envVar])
82
- || fs.existsSync(homePathToCheck);
83
- }
84
-
85
- function skillTarget(client) {
86
- // An entry may override the default `skills` sub-path with `skillsSubdir`
87
- // (split on '/' to keep cross-platform path.join correctness). Entries
88
- // without it resolve to <home>/skills/agentic-sdlc exactly as before.
89
- const subdir = client.skillsSubdir ? client.skillsSubdir.split('/') : ['skills'];
90
- return path.join(client.home, ...subdir, 'agentic-sdlc');
91
- }
92
-
93
- function copyRecursive(src, dest) {
94
- if (typeof fs.cpSync === 'function') {
95
- fs.cpSync(src, dest, { recursive: true, force: true });
96
- return;
97
- }
98
- // Fallback for Node < 16.7
99
- if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
100
- for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
101
- const s = path.join(src, entry.name);
102
- const d = path.join(dest, entry.name);
103
- if (entry.isDirectory()) copyRecursive(s, d);
104
- else fs.copyFileSync(s, d);
105
- }
106
- }
107
-
108
- /**
109
- * Parse templates.md into { headingText: [fencedBlock, ...] }.
110
- * Templates are single-sourced there: the init script must extract them
111
- * instead of carrying its own inline copies (which historically drifted).
112
- */
113
- function loadTemplates() {
114
- const text = fs.readFileSync(TEMPLATES_PATH, 'utf8');
115
- const lines = text.split(/\r?\n/);
116
- const sections = {};
117
- let heading = null;
118
- let block = null;
119
- for (const line of lines) {
120
- const h = line.match(/^##\s+(.*)$/);
121
- if (h && block === null) {
122
- heading = h[1].trim();
123
- sections[heading] = sections[heading] || [];
124
- continue;
125
- }
126
- if (/^```/.test(line)) {
127
- if (block === null) {
128
- block = [];
129
- } else {
130
- if (heading) sections[heading].push(block.join('\n') + '\n');
131
- block = null;
132
- }
133
- continue;
134
- }
135
- if (block !== null) block.push(line);
136
- }
137
- return sections;
138
- }
139
-
140
- /**
141
- * Return the Nth fenced block of the section whose heading contains `needle`.
142
- * Throws with a clear message when missing: writing a wrong or empty
143
- * boilerplate silently would be worse than failing the init.
144
- */
145
- function templateFor(sections, needle, index = 0) {
146
- const heading = Object.keys(sections).find((h) => h.includes(needle));
147
- const blocks = heading ? sections[heading] : undefined;
148
- if (!blocks || !blocks[index]) {
149
- throw new Error(
150
- `Template section containing "${needle}" (block ${index}) not found in ${TEMPLATES_PATH}. ` +
151
- 'The package is corrupted or templates.md was restructured: fix templates.md, do not improvise content.'
152
- );
153
- }
154
- return blocks[index];
155
- }
156
-
157
- module.exports = {
158
- PACKAGE_ROOT,
159
- SKILL_SOURCE,
160
- TEMPLATES_PATH,
161
- CLIENTS,
162
- commandExists,
163
- clientDetected,
164
- skillTarget,
165
- copyRecursive,
166
- loadTemplates,
167
- templateFor,
168
- };
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
+ // One entry per supported AI client. `home` may be overridden by an env var
23
+ // (Claude Desktop / portable installs); presence of the home dir counts as
24
+ // detection even when the CLI is not on PATH.
25
+ const CLIENTS = [
26
+ {
27
+ key: 'claude',
28
+ label: 'Claude Code',
29
+ cmd: 'claude',
30
+ home: process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude'),
31
+ envVar: 'CLAUDE_CONFIG_DIR',
32
+ reload: 'Restart Claude Code to load it. Invoke via Skill tool as "agentic-sdlc".',
33
+ },
34
+ {
35
+ key: 'gemini',
36
+ label: 'Gemini CLI',
37
+ cmd: 'gemini',
38
+ home: process.env.GEMINI_HOME || path.join(os.homedir(), '.gemini'),
39
+ envVar: 'GEMINI_HOME',
40
+ reload: 'Run "gemini skills reload" or restart Gemini CLI to load it.',
41
+ },
42
+ {
43
+ key: 'codex',
44
+ label: 'Codex AI',
45
+ cmd: 'codex',
46
+ home: process.env.CODEX_HOME || path.join(os.homedir(), '.codex'),
47
+ envVar: 'CODEX_HOME',
48
+ reload: 'Restart Codex to load it. Invoke it as "$agentic-sdlc" or by asking for Agentic SDLC.',
49
+ },
50
+ {
51
+ // Google Antigravity 2.0 discovers global agent skills under
52
+ // ~/.gemini/config/skills/ -- the SAME home the legacy Gemini CLI claims.
53
+ // To avoid a shared-home double-install (P-TM T1), this entry sets:
54
+ // - skillsSubdir 'config/skills': distinct target from gemini's ~/.gemini/skills
55
+ // - homeMarker on ~/.gemini/config/skills: detection never fires on bare
56
+ // ~/.gemini (which every Antigravity user has); only the Antigravity skills
57
+ // dir, the `agy` CLI, or ANTIGRAVITY_HOME count as "Antigravity installed".
58
+ key: 'antigravity',
59
+ label: 'Google Antigravity',
60
+ cmd: 'agy',
61
+ home: process.env.ANTIGRAVITY_HOME || path.join(os.homedir(), '.gemini'),
62
+ envVar: 'ANTIGRAVITY_HOME',
63
+ skillsSubdir: 'config/skills',
64
+ homeMarker: path.join(
65
+ process.env.ANTIGRAVITY_HOME || path.join(os.homedir(), '.gemini'),
66
+ 'config',
67
+ 'skills',
68
+ ),
69
+ reload: 'Restart Antigravity, or run "agy skills reload", to load it. Invoke by asking for Agentic SDLC.',
70
+ },
71
+ ];
72
+
73
+ function commandExists(cmd) {
74
+ try {
75
+ execSync(`${cmd} --version`, { stdio: 'ignore' });
76
+ return true;
77
+ } catch (e) {
78
+ return false;
79
+ }
80
+ }
81
+
82
+ function clientDetected(client) {
83
+ // An entry may override the fs-existence probe with a `homeMarker` (a more
84
+ // specific path than the bare home) so two clients sharing a home dir do not
85
+ // both fire on its mere existence. Entries without a marker check `home`
86
+ // exactly as before (backward-compatible).
87
+ const homePathToCheck = client.homeMarker || client.home;
88
+ return commandExists(client.cmd)
89
+ || Boolean(process.env[client.envVar])
90
+ || fs.existsSync(homePathToCheck);
91
+ }
92
+
93
+ function skillTarget(client) {
94
+ // An entry may override the default `skills` sub-path with `skillsSubdir`
95
+ // (split on '/' to keep cross-platform path.join correctness). Entries
96
+ // without it resolve to <home>/skills/agentic-sdlc exactly as before.
97
+ const subdir = client.skillsSubdir ? client.skillsSubdir.split('/') : ['skills'];
98
+ return path.join(client.home, ...subdir, INSTALLED_SKILL_NAME);
99
+ }
100
+
101
+ function copyRecursive(src, dest) {
102
+ if (typeof fs.cpSync === 'function') {
103
+ fs.cpSync(src, dest, { recursive: true, force: true });
104
+ return;
105
+ }
106
+ // Fallback for Node < 16.7
107
+ if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
108
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
109
+ const s = path.join(src, entry.name);
110
+ const d = path.join(dest, entry.name);
111
+ if (entry.isDirectory()) copyRecursive(s, d);
112
+ else fs.copyFileSync(s, d);
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Parse templates.md into { headingText: [fencedBlock, ...] }.
118
+ * Templates are single-sourced there: the init script must extract them
119
+ * instead of carrying its own inline copies (which historically drifted).
120
+ */
121
+ function loadTemplates() {
122
+ const text = fs.readFileSync(TEMPLATES_PATH, 'utf8');
123
+ const lines = text.split(/\r?\n/);
124
+ const sections = {};
125
+ let heading = null;
126
+ let block = null;
127
+ for (const line of lines) {
128
+ const h = line.match(/^##\s+(.*)$/);
129
+ if (h && block === null) {
130
+ heading = h[1].trim();
131
+ sections[heading] = sections[heading] || [];
132
+ continue;
133
+ }
134
+ if (/^```/.test(line)) {
135
+ if (block === null) {
136
+ block = [];
137
+ } else {
138
+ if (heading) sections[heading].push(block.join('\n') + '\n');
139
+ block = null;
140
+ }
141
+ continue;
142
+ }
143
+ if (block !== null) block.push(line);
144
+ }
145
+ return sections;
146
+ }
147
+
148
+ /**
149
+ * Return the Nth fenced block of the section whose heading contains `needle`.
150
+ * Throws with a clear message when missing: writing a wrong or empty
151
+ * boilerplate silently would be worse than failing the init.
152
+ */
153
+ function templateFor(sections, needle, index = 0) {
154
+ const heading = Object.keys(sections).find((h) => h.includes(needle));
155
+ const blocks = heading ? sections[heading] : undefined;
156
+ if (!blocks || !blocks[index]) {
157
+ throw new Error(
158
+ `Template section containing "${needle}" (block ${index}) not found in ${TEMPLATES_PATH}. ` +
159
+ 'The package is corrupted or templates.md was restructured: fix templates.md, do not improvise content.'
160
+ );
161
+ }
162
+ return blocks[index];
163
+ }
164
+
165
+ module.exports = {
166
+ PACKAGE_ROOT,
167
+ SKILL_SOURCE,
168
+ INSTALLED_SKILL_NAME,
169
+ TEMPLATES_PATH,
170
+ CLIENTS,
171
+ commandExists,
172
+ clientDetected,
173
+ skillTarget,
174
+ copyRecursive,
175
+ loadTemplates,
176
+ templateFor,
177
+ };
@@ -14,15 +14,19 @@ python "<skill_dir>/scripts/sdlc_check.py" check
14
14
 
15
15
  ## 2. Check in CI (recommended for teams)
16
16
 
17
- Copy `scripts/sdlc_check.py` into the repository (e.g. `tools/sdlc_check.py`) and add to the pipeline:
17
+ Copy **both** validator files into the repository — `scripts/sdlc_check.py` (the entry point) **and** `scripts/sdlc_core.py` (the shared core it imports) — keeping them side by side, e.g. `tools/sdlc_check.py` + `tools/sdlc_core.py`. 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 two files: the core carries the behaviour and is identical in every distribution of the family, the entry point names the domain. Copying only `sdlc_check.py` fails immediately with a message saying so — loudly, never as a silently green pipeline. (Copying `sdlc_core.py` alone also works: `python tools/sdlc_core.py validate --strict` behaves identically, defaulting to the code domain.)
24
+
23
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).
24
26
 
25
- Note: the copy in the repo is the authoritative one for CI; update it when you update the skill.
27
+ Note: the copy in the repo is the authoritative one for CI; update it when you update the skill — both files, together.
28
+
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.
26
30
 
27
31
  ## 3. PreToolUse hook (gate on writes)
28
32
 
@@ -89,10 +93,12 @@ Gemini CLI — wire the same command into its startup-hook mechanism if present;
89
93
  **Usage notes:**
90
94
  - The hook assumes the working directory is the project root (standard Claude Code hook behavior); it also accepts `--root <path>`.
91
95
  - **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.
92
- - Like the CI gate (§2), if you copied `sdlc_check.py` into the repo, the hook references that copy — keep it current when you update the skill.
96
+ - 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.
93
97
 
94
98
  ## 5. Skill eval battery (release gate)
95
99
 
100
+ **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.)
101
+
96
102
  The skill self-tests its own doctrine invariants. Two layers over one scenario corpus:
97
103
 
98
104
  **Static battery — the deterministic release gate.** Run before any publish:
@@ -101,10 +107,10 @@ The skill self-tests its own doctrine invariants. Two layers over one scenario c
101
107
  python -m unittest discover -s skills/agentic-sdlc-skill/scripts -p "test_*.py"
102
108
  ```
103
109
 
104
- It aggregates the three test files (`test_plan.py` + `test_session_start.py` + `test_skill_invariants.py`) and asserts the skill's invariants: the M4 triggers/hook/worktree doctrine is present and wired, support-file pointers resolve, and the generated indexes are idempotent. A non-zero exit **blocks the release** — a failing eval is always a real regression, never flakiness: the battery is stdlib-only, makes no model/network/subprocess call (deterministic by construction). If `test_indexes_idempotent` fails, run `sdlc_check.py index` and re-run.
110
+ It aggregates the test files (`test_plan.py` + `test_session_start.py` + `test_skill_invariants.py` + `test_domain_rules.py` + `test_golden_regression.py`) and asserts the skill's invariants: the M4 triggers/hook/worktree doctrine is present and wired, support-file pointers resolve, and the generated indexes are idempotent. A non-zero exit **blocks the release** — a failing eval is always a real regression, never flakiness: the battery is stdlib-only and makes no model or network call. One test does spawn a subprocess — `test_golden_regression.py` runs the shipped validator over a frozen corpus, which is the only way to compare what a user actually sees; it is local, offline and deterministic. If `test_indexes_idempotent` fails, run `sdlc_check.py index` and re-run. If `test_golden_regression` fails, the validator's behaviour on an unchanged project changed: treat that as a regression until someone declares it intended.
105
111
 
106
112
  **Behavioral corpus — opt-in, non-CI.** `evals/scenarios/*.md` (declarative, model-neutral) + `evals/run_behavioral.py` seed a fixture and print a prompt + pass criteria for a human/agent to run and self-assess. Because live adherence is nondeterministic, this layer **never gates** — it is the reproducible way to check that, e.g., the consult trigger actually fires on a seeded repo (the "demonstrably" artifact).
107
113
 
108
114
  **Optional CI** (same shape as §2, not mandatory): add a `run:` step invoking the `unittest discover` command above.
109
115
 
110
- **T10 note:** if you copied `sdlc_check.py` and the `test_*.py` battery into the repo for CI, that copy is authoritative — keep it current when you update the skill.
116
+ **T10 note:** if you copied the validator (both files) and the `test_*.py` battery into the repo for CI, that copy is authoritative — keep it current when you update the skill.
@@ -13,9 +13,11 @@ This skill guides software development with a Documentation-First process propor
13
13
 
14
14
  Support files in the skill directory:
15
15
  - `templates.md`: templates for Vision, ANALYSIS, Spike, audit plan and handoff.
16
+ - `architect.md`: the architect pass — do the components and services this feature needs already exist? Run at L3 before drafting the Impact.
16
17
  - `guides.md`: pipeline for distilling user-provided indications into `ai_docs/reference/GUIDE_[topic].md`.
17
18
  - `vision.md`: how to write a Vision a cold reviewer can actually apply — the properties that make a rule hold, the minimum operable sections, and the blind check run before promoting one to APPROVED.
18
- - `scripts/sdlc_check.py`: mechanical validator for `ai_docs/` (`check`, `validate`, `index`, `stale`, `mark`, `gate`).
19
+ - `routing.md`: which lens owns this unit of work. Read ONLY when a sibling lens skill is installed alongside this one; a single-lens install never reads it.
20
+ - `scripts/sdlc_check.py` + `scripts/sdlc_core.py`: the mechanical validator for the docs root (`check`, `validate`, `index`, `stale`, `mark`, `gate`, `plan`, `orient`, `migrate`). Two files: the core is the family's shared spine, the entry point names this domain. Copy both, or neither.
19
21
  - `ENFORCEMENT.md`: optional setup for CI and hooks.
20
22
 
21
23
  Read these files only when needed. `SKILL.md` is the operating contract; the support files are progressive resources.
@@ -46,9 +48,11 @@ Always classify the request before choosing the process. Declare the chosen leve
46
48
  | **Spike** | Time-boxed exploration to reduce uncertainty | Code not mergeable into main. Outcome in `ai_docs/solutions/SPIKE_[topic].md`. For production, reclassify as L2 or L3. |
47
49
 
48
50
  Cross-cutting rules:
51
+ - **Domain routing (multi-lens installs only).** After the level is set, and only when a sibling lens skill of this family is installed (`kb-agentic`, `mkt-agentic-sdlc`), run the router in `routing.md` for every L2, L3 and Spike: it decides which lens's method and validation rules govern this unit of work. L1 never reaches it, and a single-lens install never reads the file — detection fails open. In such a project, never refer to a document whose meaning differs by lens ("threat model", "vision", `principles.md`, `handoff.md`) by its bare name: qualify it with its domain, or name its path.
49
52
  - Parsing of external input, authN/authZ, cryptography, networking, personal data and filesystem access are security-sensitive: never L1.
50
53
  - If a bigger impact emerges during L1/L2 work, stop, reclassify and declare it.
51
54
  - When in doubt, pick the higher level.
55
+ - **Before asking the user anything — any phase, any level — the question must pass the legality test: search first and name the search with its result; name the decision or fact blocked without the answer.** Blocking the work is the exception, not the default. `elicitation.md` §The question discipline owns the rule and is the only place it is stated — read it before you ask, and do not work from a summary of it.
52
56
  - The full audit does not start for L1/L2 unless explicitly requested.
53
57
 
54
58
  ## Write Triggers
@@ -57,7 +61,7 @@ Triage decides IF documentation is due; this table decides WHICH document each e
57
61
 
58
62
  | Document | Write trigger | Phase |
59
63
  |---|---|---|
60
- | `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. | 3 |
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 |
61
65
  | `solutions/SPIKE_[topic].md` | Closing any Spike — including a failed one (a negative outcome is still an outcome). | — |
62
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 |
63
67
  | `audit/handoff.md` (workstream registry) | One row per OPEN workstream (feature, level, branch, status, since, next step, pointer to its Diary/HANDOFF file) — parallel-safe: closing one milestone touches one row, never another's resume point. Refresh the row at every L3 closure (remove it) and at session end when that ANALYSIS is IN_PROGRESS; discretionary after an L2. ≤ 20 lines. | 5 / session end |
@@ -66,8 +70,10 @@ Triage decides IF documentation is due; this table decides WHICH document each e
66
70
  | `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 |
67
71
  | `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 |
68
72
  | `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 |
73
+ | `audit/reviews/REVIEW_LOG.md` | Every completed review — when and what to write is `review.md` §When a review is due; schema and column meanings are `templates.md`. | 3 / 5 |
69
74
  | ADR — `architecture/` (Standalone) or devPNT DB (Hybrid) | An architectural decision was taken (new pattern, layer or contract change, structural dependency): record it at closure, before DONE. No decision, no ADR. | 5 |
70
75
  | `strategic/architecture.md`, `strategic/existing_features.md` | Bootstrap; update at closure when the stack or the feature catalog actually changed. | 1 / 5 |
76
+ | `strategic/architecture.md` — `## Component Map` | A component was BORN, its contract changed, **or the pass DISCOVERED an existing one while searching an unmapped area** — the same closure adds or corrects its row (`architect.md`). Keyed on the component, not on the stack: a new component is not a stack change, and a discovered one is how an area gets marked ANALYZED while the map stays silent about what lives there — after which the next feature may lawfully rule it MISSING and build it twice. | 5 |
71
77
  | `vision/project_vision.md`, `roadmap.md`, `principles.md` | Bootstrap, as `Status: DRAFT`; promoted to APPROVED only by explicit user confirmation, and only after the blind check (`vision.md` §6) — which also gates any amendment of an APPROVED Vision. Write it against `vision.md` §1–§4 from the first draft. | 1 / 2 |
72
78
  | `INDEX.md`, `reference/INDEX.md`, `strategic/features_history.md` | Never by hand: regenerated by `sdlc_check.py index` at closure when canonical docs or guides changed (prose discipline where the validator is not adopted). | 5 |
73
79
 
@@ -123,6 +129,9 @@ process; it never replaces it.
123
129
  | Feature state | ANALYSIS frontmatter `status` | Action Plan node status | mapping table below; at closure both must move together |
124
130
  | ADR | `architecture/` (canonical dir) | devPNT DB (`adr_YYYY-MM-DD_slug`) | optional filesystem shadow `SHADOW_adr_*` exported at closure for grep-ability |
125
131
  | Audit / freshness | `audit/audit_plan.md` + `stale`/`mark` | devPNT KL coverage + summary status | run `check --hybrid` (skips audit-plan staleness) |
132
+ | Design review (pre-implementation) | `review.md` moment 1, on the ANALYSIS | devPNT §4.5 gate on `E-ISP`/`E-TDD` | same slot, richer backend — run ONE of them, never both |
133
+ | *(mode is per unit of change, not per project)* | a Hybrid-capable project may work one feature Standalone: the slot follows the ARTIFACT the design lives in, and the mode is declared in that artifact. `validate --hybrid` suppresses the Standalone design-review backstop, since devPNT owns the slot there | | |
134
+ | Review log | `audit/reviews/REVIEW_LOG.md` | devPNT `REVIEW_LOG.md` (same path) | always filesystem |
126
135
  | Operative guides | `ai_docs/reference/` | `ai_docs/reference/` — **filesystem-first even in Hybrid** | devPNT bootstrap may point at their index; it never copies their content |
127
136
  | Handoff | `audit/handoff.md` | `audit/handoff.md` | always filesystem |
128
137
 
@@ -180,9 +189,10 @@ gate) instead of requiring an IN_PROGRESS ANALYSIS.
180
189
  ### 1. Audit and Alignment
181
190
 
182
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` (volatile resume logistics) AND its ANALYSIS Diary (durable narrative) — the registry row points at both. A **narrative handoff from a pre-1.17 project** reads as a single-row registry; convert it when you next write it, not now (Write Triggers).
183
- - 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. `solutions/` and `audit/` are not indexed per file: search them with glob/grep.
192
+ - 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.
184
193
  - 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).
185
194
  - 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).
195
+ - **Arriving in a project that was never curated** (the usual case — you arrive with a task, not with a bootstrap): write `audit/audit_plan.md` FIRST. It is the scope ledger the rest is built on — one row per area, all PENDING, `SKIPPED` for what genuinely does not merit reading (vendored, generated). Then the other bootstrap documents describe what you have actually analyzed, and the `## Component Map` in `strategic/architecture.md` starts at whatever the first task made you understand. **No full-codebase sweep is required before the first feature**: the map grows feature by feature, each one marking the areas it covered (`sdlc_check.py mark`). What is NOT deferred is comprehension of what the change touches or depends on — that is understood now, at full standard, mapped or not. The licence is about writing the inventory, never about designing on a guess (`architect.md` §2: unmapped is *unread*, not *empty*, and can never ground a MISSING verdict).
186
196
  - In Standalone use `ai_docs/audit/audit_plan.md` for mapping and state.
187
197
  - In Hybrid prefer the devPNT/KL mapping when available; do not duplicate plan governance.
188
198
  - For detailed templates use `templates.md`.
@@ -206,15 +216,19 @@ Hybrid:
206
216
 
207
217
  For any L3, run the spec elicitation round in `elicitation.md` BEFORE drafting the analysis (skip path inside — one-line note when the spec is already complete).
208
218
 
219
+ **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.
220
+
209
221
  **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.
210
222
 
211
223
  Standalone L3:
212
224
  - 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.
213
225
  - Create or update `ai_docs/solutions/ANALYSIS_[feature].md`.
214
- - Minimum sections: Objective, Feature Vision (or Vision Alignment), Impact, Security and Threat Model, Action Plan, Test Strategy, Diary/Current State.
226
+ - Minimum sections: Objective, Feature Vision (or Vision Alignment), Use Cases / User Needs, Capability Ledger, Impact, Security and Threat Model, Action Plan, Test Strategy, Diary/Current State. (`review.md` makes an uncovered use-case a finding, so an ANALYSIS without that section fails its own closure review.)
215
227
  - Build the Impact/solution **on** the Vision, the use-cases/user-needs and the Security & Threat Model — read and trace to them first, and state the trace (which actor / use-case / threat / benefit each part serves) so the closure review (`review.md`) can verify conformance. Do not draft the Impact in isolation.
216
228
  - For a feature known to span multiple milestones, also create `ai_docs/vision/features/VISION_[feature].md`; the retroactive case (extract it when the SECOND `ANALYSIS_*` on a theme appears) is in Write Triggers.
217
229
 
230
+ **Design review gate — the design is reviewed BEFORE any code (L3).** Close Phase 3 by handing the finished ANALYSIS to an *independent* reviewer: the procedure, the independence ladder, the round cap and the log row are `review.md` §When a review is due, moment 1 — follow it there, it is not restated here. Why the moment exists: the closure review can prove the code matches the design, never that the design was right, and the author is structurally blind to what their own design omitted. In Hybrid this slot belongs to devPNT's §4.5 gate on the `E-ISP`/`E-TDD` — run one, never both.
231
+
218
232
  Hybrid L3:
219
233
  - Restore the Master Plan, Action Plan and linked documents.
220
234
  - Use devPNT for plans and governed artifacts.
@@ -222,7 +236,7 @@ Hybrid L3:
222
236
 
223
237
  ### 4. Development and Testing
224
238
 
225
- - Implement only after the documentation gate required by the level.
239
+ - Implement only after the documentation gate required by the level. **Flip the ANALYSIS frontmatter `PLANNED` → `IN_PROGRESS` when implementation starts** — that flip is what `gate` and the handoff registry key on, and nothing else performs it.
226
240
  - 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.
227
241
  - Modify surgically, consistently with the plan.
228
242
  - Implementation work follows the TDD discipline in `tdd.md` (RED/GREEN/REFACTOR — the L2/L3 default; record the reason when it does not apply).