@antoneeo/agentic-sdlc-skill 1.19.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,11 +93,11 @@ 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
 
96
- **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`; §1–§4 are theirs.)
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.)
97
101
 
98
102
  The skill self-tests its own doctrine invariants. Two layers over one scenario corpus:
99
103
 
@@ -103,10 +107,10 @@ The skill self-tests its own doctrine invariants. Two layers over one scenario c
103
107
  python -m unittest discover -s skills/agentic-sdlc-skill/scripts -p "test_*.py"
104
108
  ```
105
109
 
106
- 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.
107
111
 
108
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).
109
113
 
110
114
  **Optional CI** (same shape as §2, not mandatory): add a `run:` step invoking the `unittest discover` command above.
111
115
 
112
- **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.
@@ -16,7 +16,8 @@ Support files in the skill directory:
16
16
  - `architect.md`: the architect pass — do the components and services this feature needs already exist? Run at L3 before drafting the Impact.
17
17
  - `guides.md`: pipeline for distilling user-provided indications into `ai_docs/reference/GUIDE_[topic].md`.
18
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.
19
- - `scripts/sdlc_check.py`: mechanical validator for `ai_docs/` (`check`, `validate`, `index`, `stale`, `mark`, `gate`, `plan`, `orient`).
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.
20
21
  - `ENFORCEMENT.md`: optional setup for CI and hooks.
21
22
 
22
23
  Read these files only when needed. `SKILL.md` is the operating contract; the support files are progressive resources.
@@ -47,9 +48,11 @@ Always classify the request before choosing the process. Declare the chosen leve
47
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. |
48
49
 
49
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.
50
52
  - Parsing of external input, authN/authZ, cryptography, networking, personal data and filesystem access are security-sensitive: never L1.
51
53
  - If a bigger impact emerges during L1/L2 work, stop, reclassify and declare it.
52
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.
53
56
  - The full audit does not start for L1/L2 unless explicitly requested.
54
57
 
55
58
  ## Write Triggers
@@ -1,22 +1,130 @@
1
1
  # Spec Elicitation
2
2
 
3
- Applies when an L3 request enters phase 3 (Request Analysis), BEFORE
4
- drafting the ANALYSIS document (Standalone) or the D-UC/E-ISP (Hybrid).
3
+ `## The question discipline` below governs EVERY question to the user — any
4
+ phase, any level, inside or outside the round. The rest of the file is the spec
5
+ elicitation round: it applies when an L3 request enters phase 3 (Request
6
+ Analysis), BEFORE drafting the ANALYSIS document (Standalone) or the D-UC/E-ISP
7
+ (Hybrid).
5
8
 
6
- Skip path: if the spec is already complete — an approved Vision or explicit
7
- user requirements already answer goal, scope, and constraints skip the
8
- round and add a one-line note in the analysis stating why it was skipped.
9
- Do not run the round as a formality when the answers are already on record.
9
+ Skip path: if the spec is already complete — an approved Vision or explicit user
10
+ requirements already answer goal, scope, and constraints, or the remainder is
11
+ derivable from the repo, `ai_docs/` and the conversation skip the round and add
12
+ a one-line note in the analysis stating why it was skipped and naming the
13
+ sources. Do not run the round as a formality when the answers are already on
14
+ record.
10
15
 
11
- Unattended path: when the user is not reachable (a scheduled or autonomous run,
12
- and a bootstrap Vision is `DRAFT` by mandate, so the skip path above cannot
13
- apply on a project's first L3), do not stall and do not invent consensus. Write
14
- the six answers as **declared assumptions** in `## Objective`, mark the ANALYSIS
15
- `BLOCKED on the user`, and stop before implementation. An assumption on the
16
- record is reviewable; a guess folded silently into a design is not.
16
+ Unattended path: when the user is not reachable (a scheduled or autonomous run)
17
+ and the skip path does not answer all six, do not stall and do not invent
18
+ consensus. Write the missing answers as **declared assumptions** in
19
+ `## Objective`, mark the ANALYSIS `BLOCKED on the user`, and stop before
20
+ implementation. An assumption on the record is reviewable; a guess folded
21
+ silently into a design is not.
22
+
23
+ ## The question discipline
24
+
25
+ A question to the user spends their attention and stalls the work; the round
26
+ below is the only place the process *plans* that cost. Everywhere, a question is
27
+ legal only when BOTH hold:
28
+
29
+ 1. **Searched first, and the search is named — with its result.** The answer is
30
+ not on record and not derivable from the repo, `ai_docs/`, the Vision or the
31
+ conversation — and the question states the terms, tools and areas you searched
32
+ **and what they returned**. Same standard as a router verdict or an
33
+ architect-pass MISSING, and it carries their floors, not only their vocabulary
34
+ (`architect.md`, `guides.md`): a search whose scope does not cover the question
35
+ is not a search; a hit you did not open does not narrow anything; and **never
36
+ fake the search** — "I looked and found nothing" that names no terms and no
37
+ areas is a search not run. A question the repo can answer is a search
38
+ outsourced to the user.
39
+ 2. **It names what is blocked.** The question states the specific decision or
40
+ fact that cannot be resolved without the reply — what you will do differently
41
+ depending on the answer. If nothing downstream changes with the answer, there
42
+ is no question.
43
+
44
+ Never legal:
45
+
46
+ - **Generic confirmation** — "shall I proceed?", "is this OK?". The process
47
+ authorizes proceeding; if a real risk motivates the ask, name the risk and the
48
+ fork — that is a blocking question and carries the form below.
49
+ - **Preference-fishing** — asking the user to pick among options that are
50
+ equivalent **in their effect on the benefit** and already decided by the
51
+ project's conventions. Cheapness to undo is NOT the test: nearly everything is
52
+ reversible under version control, and "it is reversible" as a licence to stop
53
+ asking is the silence-side evasion this clause must not fund.
54
+ - **Re-asking the record** — goal, scope or constraints that an APPROVED Vision,
55
+ an earlier reply, or the request itself already states.
56
+
57
+ What questions are FOR — what the user uniquely owns: the benefit, priorities
58
+ between conflicting goods, non-goals, acceptance, and the approvals doctrine
59
+ reserves to them (Vision promotion and amendment, scope changes, proposal
60
+ acceptance, merge decisions). Facts about intent come from the user; facts about
61
+ the system come from search. (The marketing sibling states the same rule as "ask
62
+ only what the user uniquely owns"; this is its code-domain form.)
63
+
64
+ **Precedence, because both halves can fire at once:** this paragraph wins over
65
+ the "never legal" list above it. A choice the user uniquely owns is never
66
+ preference-fishing, however cheap it is to undo; the list reaches choices that
67
+ are *not* theirs.
68
+
69
+ **Default non-blocking.** An unknown on which no fork of the work depends: write
70
+ it as a **declared assumption** in the artifact it touches — the same mechanism
71
+ the unattended path uses — proceed, and present the open points **batched**, with
72
+ the round for spec questions or with the deliverable otherwise, answered by
73
+ exception.
74
+
75
+ This is the path most work takes, so it carries the SAME evidence duty as a
76
+ question, not a lighter one — otherwise "assume it" becomes the way to skip the
77
+ standard. Each declared assumption states **what it is taken from** ("I take X
78
+ from Y" — the same shape the round uses) and **the alternative it excludes**, and
79
+ **every declared assumption reaches the batch**: an assumption nobody is shown is
80
+ not an open point, it is a silent decision. That pairing is what the kb family's
81
+ escalation rule actually does — keep BOTH sides with their sources and surface
82
+ them, never silently pick one — and this branch, not the blocking one, is where
83
+ it structurally belongs. An assumption recorded with its source and its rejected
84
+ alternative is reviewable; one recorded alone is a decision wearing an
85
+ assumption's clothes, and a session stalled on a question that could have been
86
+ an assumption is the waste this section exists to prevent.
87
+
88
+ **Blocking is reserved** for three cases: proceeding under ANY assumption would
89
+ waste the work (the forks diverge at once, and the wrong branch is rework of the
90
+ whole unit); the doctrine reserves the decision to the user (the approvals
91
+ above); or the doctrine itself mandates the stop — and a mandated stop is legal
92
+ by mandate, never re-argued here.
93
+
94
+ **Exactly two mandating files prescribe their own hand-over, and there the form
95
+ below does not apply** (two forms over one moment is the duplicate `review.md`
96
+ §Reviewing forbids): `debugging.md`'s circuit breaker — the minimal reproduction,
97
+ what was ruled out, the current best hypothesis; and `review.md`'s round cap —
98
+ the artifact plus the open findings. That list is closed. **Every other mandated
99
+ stop carries the form**, including one that names only its options and not its
100
+ evidence (`SKILL.md`'s Vision-Gate conflict names the two choices — the evidence
101
+ is still owed, and for a Vision conflict the quoted line IS the substance) and
102
+ one that prescribes nothing (`guides.md`'s guide proposal and its ingestion
103
+ bound). "The file mentions the moment" is not a prescription; only a stated
104
+ hand-over is.
105
+
106
+ A blocking question carries a mandatory form — surface both sides with their
107
+ evidence, never silently pick one:
108
+
109
+ - the fork: the concrete options and what each implies for the work;
110
+ - the evidence: what you searched, read or tried, and what it leaves undecided;
111
+ - why no assumption survives — what work is discarded if you assume and are
112
+ wrong. This is what makes case (a) falsifiable: without it, "this is a fork"
113
+ is an agent's assertion about its own convenience, and case (a) becomes the
114
+ licence for exactly the question this section forbids. Cases (b) and (c) answer
115
+ it by citing the approval or the mandate instead;
116
+ - why it is the user's call — what makes the remainder intent, priority or
117
+ approval rather than a derivable fact;
118
+ - what stays blocked until answered.
17
119
 
18
120
  ## The round
19
121
 
122
+ **Derive before asking.** Answer each of the six from the record first — the
123
+ Vision, `ai_docs/`, the conversation, the code. Ask only the residue, and carry
124
+ the derived answers into the round as declared assumptions corrected by
125
+ exception ("I take X from Y; the questions below are what no source answers"),
126
+ not re-confirmed one by one.
127
+
20
128
  Ask ONE structured set of questions, not a drip of follow-ups. Keep each
21
129
  question short and numbered; offer concrete options where a real choice
22
130
  exists (this narrows the reply and speeds up the round). Cover:
@@ -53,8 +161,9 @@ run a second round to double-check answers that were already clear.
53
161
 
54
162
  - **Interrogation**: an endless list of questions, or drip-feeding one
55
163
  question at a time across many turns instead of one structured round.
56
- - **Asking what the approved vision already answers**: re-asking goal or
57
- non-goals that a `Status: APPROVED` Vision or M-VISION already states.
58
164
  - **Collecting answers without folding them in**: getting replies in chat
59
165
  and proceeding to design without writing them into the analysis document —
60
166
  the next reader has no record of why the scope is what it is.
167
+
168
+ (The illegal question forms — generic confirmation, preference-fishing,
169
+ re-asking the record — are defined once, in `## The question discipline`.)
@@ -125,20 +125,30 @@ When you are the reviewer:
125
125
  statement is the proof the check ran; an unfalsifiable "I checked" is the review
126
126
  theater this discipline exists to prevent (the reviewer-side twin of §Receiving's
127
127
  silent-drop rule). Plain code reviews stay findings-only.
128
+ - **Restated facts (cite, never copy).** Every governance slot has ONE owning document
129
+ per project. A fact restated in the artifact under review when another document owns
130
+ it is a **finding**: the fix is a citation naming the owner, not a better copy. This
131
+ binds the conformance statement too — where a constraint is satisfied by another
132
+ document, name that document as the evidence instead of repeating what it says. Two
133
+ copies of one fact diverge at the first edit, and the reader then has no way to tell
134
+ which one is current. The rule bites hardest across domains, where the same slot
135
+ ("threat model", "vision", "handoff") carries a different meaning under each lens and
136
+ a copy looks like an independent second source.
128
137
  - **Capability Ledger (same reviews).** **An L3 impact/solution analysis or design
129
- that carries NO Capability Ledger is itself a finding** — the architect pass left
138
+ that carries NO Capability Ledger is itself a finding** — the lens's capability
139
+ pass (`architect.md` in the code lens, `taxonomy.md` in the knowledge lens) left
130
140
  no record, and "the artifact does not have one" is what a skipped pass looks like,
131
141
  not a reason to skip the check. (This half is load-bearing in Hybrid, where the
132
142
  validator backstop reads Standalone ANALYSIS files only and this clause is the
133
143
  sole check that the pass ran.) When the ledger is there, map each
134
144
  ledger row to where the design or diff realizes it. Three findings live here and
135
- nowhere else (`architect.md`): a capability ruled MISSING but implemented inside
145
+ nowhere else (the capability-pass file named above): a capability ruled MISSING but implemented inside
136
146
  the feature's code path, with no component owning it; a component whose contract
137
147
  names the feature (a second consumer would force it open); and a capability ruled
138
148
  MISSING, not built, and absorbed by quietly reshaping the feature — that is a
139
149
  scope change owed to the user, not a design detail. An EXISTS row with no named
140
150
  path or symbol is itself a finding. A capability built in this change and absent
141
- from the `## Component Map` (`strategic/architecture.md`) is a finding too — and
151
+ from the `## Component Map` (`strategic/architecture.md`, where the lens keeps one) is a finding too — and
142
152
  so is **a component the pass merely DISCOVERED and did not write**, especially
143
153
  when the change marks that area ANALYZED: the area now looks read, the map is
144
154
  still silent, and the next feature may lawfully rule the capability MISSING and