@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/CHANGELOG.md +431 -332
- package/README.md +122 -95
- package/gemini-extension.json +1 -1
- package/package.json +50 -48
- package/scripts/init.js +224 -156
- package/scripts/lib.js +177 -168
- package/skills/agentic-sdlc-skill/ENFORCEMENT.md +10 -6
- package/skills/agentic-sdlc-skill/SKILL.md +4 -1
- package/skills/agentic-sdlc-skill/elicitation.md +123 -14
- package/skills/agentic-sdlc-skill/review.md +13 -3
- package/skills/agentic-sdlc-skill/routing.md +100 -0
- package/skills/agentic-sdlc-skill/scripts/sdlc_check.py +52 -1470
- package/skills/agentic-sdlc-skill/scripts/sdlc_core.py +1996 -0
- package/skills/agentic-sdlc-skill/templates.md +12 -0
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
|
-
//
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
{
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
{
|
|
27
|
-
key: '
|
|
28
|
-
label: '
|
|
29
|
-
cmd: '
|
|
30
|
-
home: process.env.
|
|
31
|
-
envVar: '
|
|
32
|
-
reload: '
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
key: '
|
|
36
|
-
label: '
|
|
37
|
-
cmd: '
|
|
38
|
-
home: process.env.
|
|
39
|
-
envVar: '
|
|
40
|
-
reload: '
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
home
|
|
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
|
-
function
|
|
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
|
-
|
|
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`
|
|
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
|
|
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
|
|
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
|
|
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
|
-
- `
|
|
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
|
-
|
|
4
|
-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
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 (
|
|
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
|
|
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
|