@antoneeo/agentic-sdlc-skill 1.19.0 → 1.20.3

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/init.js CHANGED
@@ -1,156 +1,217 @@
1
- #!/usr/bin/env node
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const { execSync } = require('child_process');
6
- const { SKILL_SOURCE, CLIENTS, clientDetected, loadTemplates, templateFor } = require('./lib');
7
-
8
- const cwd = process.cwd();
9
-
10
- // 1. Directory layout (canonical ai_docs structure, including reference/)
11
- const directories = [
12
- 'ai_docs',
13
- 'ai_docs/vision',
14
- 'ai_docs/vision/features',
15
- 'ai_docs/reference',
16
- 'ai_docs/strategic',
17
- 'ai_docs/audit',
18
- 'ai_docs/solutions',
19
- ].map((d) => path.join(cwd, d));
20
-
21
- // 2. Project protocol (thin pointer — the operating contract is the skill).
22
- // Deliberately short: duplicating the skill's rules here made them drift.
23
- const protocolContent = `# Agentic SDLC — Project Protocol (pointer)
24
-
25
- This project follows the Agentic SDLC Documentation-First process. The full
26
- operating contract is the \`agentic-sdlc\` skill (installed in your agent's
27
- skills directory); this file is only the minimal always-on pointer.
28
-
29
- ## Rule Zero — Triage every request
30
- - L1 Trivial: ~10 lines, 1-2 files, no API/dependency/behavior change. Implement + run existing tests; no docs.
31
- - L2 Small: clear root cause, at most 3 files, low risk. Mini-analysis in the reply; tests mandatory.
32
- - L3 Significant: >3 files, APIs/contracts, new dependency, user-visible behavior, security-sensitive area, or architectural change. Full workflow via the skill: Vision Gate -> ANALYSIS -> plan -> implement -> test -> closure.
33
- - Spike: time-boxed exploration; outcome in \`ai_docs/solutions/SPIKE_[topic].md\`; reclassify for production.
34
- - Security-sensitive areas (external input parsing, authN/authZ, crypto, network, personal data, filesystem) are never L1.
35
- - When in doubt, pick the higher level. Declare the chosen level when starting.
36
-
37
- ## Where things live
38
- - Vision (gate for L3): \`ai_docs/vision/\` — \`Status: DRAFT\` informs, \`Status: APPROVED\` binds.
39
- - Feature analyses: \`ai_docs/solutions/ANALYSIS_[feature].md\` (frontmatter = feature state).
40
- - Must-reads: \`ai_docs/README.md\`; full generated manifest: \`ai_docs/INDEX.md\`.
41
- - If devPNT is available for this project, its M-VISION / plans / governed artifacts take over (Hybrid mode — see the skill).
42
-
43
- ## Closure gate
44
- Docs travel in the same commit/PR as the code they describe. If the project
45
- adopts the validator, \`python <skill_dir>/scripts/sdlc_check.py check\` must be
46
- CLEAN before declaring work done.
47
-
48
- If the agentic-sdlc skill is not available in this client, ask the user to install it:
49
- \`npm i -g @antoneeo/agentic-sdlc-skill && agentic-sdlc-install-skill\`
50
- `;
51
-
52
- console.log('🚀 Initializing Agentic SDLC workflow...');
53
-
54
- // 3. Load templates from the single source (skill's templates.md)
55
- let sections;
56
- try {
57
- sections = loadTemplates();
58
- } catch (err) {
59
- console.error(`❌ Cannot load templates: ${err.message}`);
60
- process.exit(1);
61
- }
62
-
63
- // audit_plan: the template block carries illustrative rows; a fresh project
64
- // starts from a single root PENDING row instead.
65
- function initialAuditPlan() {
66
- const tpl = templateFor(sections, 'audit_plan.md');
67
- const lines = tpl.split('\n');
68
- const sepIdx = lines.findIndex((l) => /^\|[-\s|:]+\|$/.test(l.trim()));
69
- if (sepIdx === -1) return tpl; // unexpected shape: keep the template as-is
70
- // '.' (the project root), never '/': an absolute row makes `stale` walk the
71
- // whole drive once the area is marked ANALYZED.
72
- return lines.slice(0, sepIdx + 1).join('\n') + '\n| . | PENDING | - | Initial analysis |\n';
73
- }
74
-
75
- let seedFiles;
76
- try {
77
- seedFiles = [
78
- ['ai_docs/README.md', templateFor(sections, 'ai_docs/README.md')],
79
- ['ai_docs/vision/project_vision.md', templateFor(sections, 'project_vision.md')],
80
- ['ai_docs/vision/roadmap.md', templateFor(sections, 'vision/roadmap.md')],
81
- ['ai_docs/vision/principles.md', templateFor(sections, 'principles.md')],
82
- ['ai_docs/strategic/architecture.md', templateFor(sections, 'architecture.md and existing_features.md', 0)],
83
- ['ai_docs/strategic/existing_features.md', templateFor(sections, 'architecture.md and existing_features.md', 1)],
84
- ['ai_docs/audit/audit_plan.md', initialAuditPlan()],
85
- // NOTE: features_history.md and INDEX.md are NOT seeded — they are
86
- // generated by `sdlc_check.py index` and would immediately fail validate.
87
- ];
88
- } catch (err) {
89
- console.error(`❌ ${err.message}`);
90
- process.exit(1);
91
- }
92
-
93
- // 4. Create directories
94
- directories.forEach((dir) => {
95
- if (!fs.existsSync(dir)) {
96
- fs.mkdirSync(dir, { recursive: true });
97
- console.log(`📁 Created directory: ${path.relative(cwd, dir)}`);
98
- }
99
- });
100
-
101
- // 5. Write seed files (never overwrite)
102
- const writeIfNotExists = (relPath, content, description) => {
103
- const filePath = path.join(cwd, relPath);
104
- if (!fs.existsSync(filePath)) {
105
- fs.writeFileSync(filePath, content, 'utf8');
106
- console.log(`📄 Created file: ${relPath}${description ? ` (${description})` : ''}`);
107
- return true;
108
- }
109
- console.log(`⏭️ Skipped: ${relPath} already exists.`);
110
- return false;
111
- };
112
-
113
- seedFiles.forEach(([relPath, content]) => writeIfNotExists(relPath, content));
114
-
115
- // 6. Client discovery and protocol pointers
116
- console.log('\n--- Environment Analysis ---');
117
-
118
- const protocolFiles = {
119
- claude: 'CLAUDE.md',
120
- gemini: 'GEMINI.md',
121
- codex: 'AGENTS.md',
122
- antigravity: 'AGENTS.md', // Antigravity CLI reads AGENTS.md; single protocolContent reused.
123
- };
124
-
125
- for (const client of CLIENTS) {
126
- if (clientDetected(client)) {
127
- console.log(`✅ ${client.label} detected.`);
128
- writeIfNotExists(protocolFiles[client.key], protocolContent, `${client.label} protocol pointer`);
129
- }
130
- }
131
-
132
- // Cursor/Windsurf (always recommended)
133
- writeIfNotExists('.cursorrules', protocolContent, 'Cursor/Windsurf rules');
134
-
135
- // 7. Generate ai_docs/INDEX.md so the very first `validate` is already clean.
136
- // The manifest is generated, never seeded: delegate to the validator if Python is available.
137
- const validator = path.join(SKILL_SOURCE, 'scripts', 'sdlc_check.py');
138
- let indexed = false;
139
- for (const py of ['python', 'python3', 'py']) {
140
- try {
141
- execSync(`${py} "${validator}" index --root "${cwd}"`, { stdio: 'ignore' });
142
- console.log('📇 Generated ai_docs/INDEX.md (document manifest).');
143
- indexed = true;
144
- break;
145
- } catch (e) { /* try the next interpreter */ }
146
- }
147
- if (!indexed) {
148
- console.log('ℹ️ Python not found: generate the manifest later with '
149
- + '"python <skill_dir>/scripts/sdlc_check.py index" (validate reports it until then).');
150
- }
151
-
152
- console.log('\n✅ Setup completed successfully!');
153
- console.log('💡 Next steps:');
154
- console.log(' 1. Make sure the agentic-sdlc skill is installed (agentic-sdlc-install-skill).');
155
- console.log(' 2. Restart/open the project in your AI client so it reads the protocol pointer.');
156
- console.log(' 3. Start with an audit following ai_docs/audit/audit_plan.md.');
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { execSync } = require('child_process');
6
+ const { SKILL_SOURCE, INSTALLED_SKILL_NAME, SELF_LENS, SIBLING_LENSES, CLIENTS, clientDetected, skillTarget, loadTemplates, templateFor } = require('./lib');
7
+
8
+ const cwd = process.cwd();
9
+
10
+ // 1. Directory layout (canonical ai_docs structure, including reference/)
11
+ const directories = [
12
+ 'ai_docs',
13
+ 'ai_docs/vision',
14
+ 'ai_docs/vision/features',
15
+ 'ai_docs/reference',
16
+ 'ai_docs/strategic',
17
+ 'ai_docs/audit',
18
+ 'ai_docs/solutions',
19
+ ].map((d) => path.join(cwd, d));
20
+
21
+ // 2. Project protocol (thin pointer — the operating contract is the skill).
22
+ // Deliberately short: duplicating the skill's rules here made them drift.
23
+ const protocolContent = `# Agentic SDLC — Project Protocol (pointer)
24
+
25
+ This project follows the Agentic SDLC Documentation-First process. The full
26
+ operating contract is the \`agentic-sdlc\` skill (installed in your agent's
27
+ skills directory); this file is only the minimal always-on pointer.
28
+
29
+ ## Rule Zero — Triage every request
30
+ - L1 Trivial: ~10 lines, 1-2 files, no API/dependency/behavior change. Implement + run existing tests; no docs.
31
+ - L2 Small: clear root cause, at most 3 files, low risk. Mini-analysis in the reply; tests mandatory.
32
+ - L3 Significant: >3 files, APIs/contracts, new dependency, user-visible behavior, security-sensitive area, or architectural change. Full workflow via the skill: Vision Gate -> ANALYSIS -> plan -> implement -> test -> closure.
33
+ - Spike: time-boxed exploration; outcome in \`ai_docs/solutions/SPIKE_[topic].md\`; reclassify for production.
34
+ - Security-sensitive areas (external input parsing, authN/authZ, crypto, network, personal data, filesystem) are never L1.
35
+ - When in doubt, pick the higher level. Declare the chosen level when starting.
36
+
37
+ ## Where things live
38
+ - Vision (gate for L3): \`ai_docs/vision/\` — \`Status: DRAFT\` informs, \`Status: APPROVED\` binds.
39
+ - Feature analyses: \`ai_docs/solutions/ANALYSIS_[feature].md\` (frontmatter = feature state).
40
+ - Must-reads: \`ai_docs/README.md\`; full generated manifest: \`ai_docs/INDEX.md\`.
41
+ - If devPNT is available for this project, its M-VISION / plans / governed artifacts take over (Hybrid mode — see the skill).
42
+
43
+ ## Closure gate
44
+ Docs travel in the same commit/PR as the code they describe. If the project
45
+ adopts the validator, \`python <skill_dir>/scripts/sdlc_check.py check\` must be
46
+ CLEAN before declaring work done.
47
+
48
+ If the agentic-sdlc skill is not available in this client, ask the user to install it:
49
+ \`npm i -g @antoneeo/agentic-sdlc-skill && agentic-sdlc-install-skill\`
50
+ `;
51
+
52
+ console.log('🚀 Initializing Agentic SDLC workflow...');
53
+
54
+ // 3. Load templates from the single source (skill's templates.md)
55
+ let sections;
56
+ try {
57
+ sections = loadTemplates();
58
+ } catch (err) {
59
+ console.error(`❌ Cannot load templates: ${err.message}`);
60
+ process.exit(1);
61
+ }
62
+
63
+ // audit_plan: the template block carries illustrative rows; a fresh project
64
+ // starts from a single root PENDING row instead.
65
+ function initialAuditPlan() {
66
+ const tpl = templateFor(sections, 'audit_plan.md');
67
+ const lines = tpl.split('\n');
68
+ const sepIdx = lines.findIndex((l) => /^\|[-\s|:]+\|$/.test(l.trim()));
69
+ if (sepIdx === -1) return tpl; // unexpected shape: keep the template as-is
70
+ // '.' (the project root), never '/': an absolute row makes `stale` walk the
71
+ // whole drive once the area is marked ANALYZED.
72
+ return lines.slice(0, sepIdx + 1).join('\n') + '\n| . | PENDING | - | Initial analysis |\n';
73
+ }
74
+
75
+ let seedFiles;
76
+ try {
77
+ seedFiles = [
78
+ ['ai_docs/README.md', templateFor(sections, 'ai_docs/README.md')],
79
+ ['ai_docs/vision/project_vision.md', templateFor(sections, 'project_vision.md')],
80
+ ['ai_docs/vision/roadmap.md', templateFor(sections, 'vision/roadmap.md')],
81
+ ['ai_docs/vision/principles.md', templateFor(sections, 'principles.md')],
82
+ ['ai_docs/strategic/architecture.md', templateFor(sections, 'architecture.md and existing_features.md', 0)],
83
+ ['ai_docs/strategic/existing_features.md', templateFor(sections, 'architecture.md and existing_features.md', 1)],
84
+ ['ai_docs/audit/audit_plan.md', initialAuditPlan()],
85
+ // NOTE: features_history.md and INDEX.md are NOT seeded — they are
86
+ // generated by `sdlc_check.py index` and would immediately fail validate.
87
+ ];
88
+ } catch (err) {
89
+ console.error(`❌ ${err.message}`);
90
+ process.exit(1);
91
+ }
92
+
93
+ // 4. Create directories
94
+ directories.forEach((dir) => {
95
+ if (!fs.existsSync(dir)) {
96
+ fs.mkdirSync(dir, { recursive: true });
97
+ console.log(`📁 Created directory: ${path.relative(cwd, dir)}`);
98
+ }
99
+ });
100
+
101
+ // 5. Write seed files (never overwrite)
102
+ const writeIfNotExists = (relPath, content, description) => {
103
+ const filePath = path.join(cwd, relPath);
104
+ if (!fs.existsSync(filePath)) {
105
+ fs.writeFileSync(filePath, content, 'utf8');
106
+ console.log(`📄 Created file: ${relPath}${description ? ` (${description})` : ''}`);
107
+ return true;
108
+ }
109
+ console.log(`⏭️ Skipped: ${relPath} already exists.`);
110
+ return false;
111
+ };
112
+
113
+ seedFiles.forEach(([relPath, content]) => writeIfNotExists(relPath, content));
114
+
115
+ // 6. Client discovery and protocol pointers
116
+ console.log('\n--- Environment Analysis ---');
117
+
118
+ const protocolFiles = {
119
+ claude: 'CLAUDE.md',
120
+ gemini: 'GEMINI.md',
121
+ codex: 'AGENTS.md',
122
+ antigravity: 'AGENTS.md', // Antigravity CLI reads AGENTS.md; single protocolContent reused.
123
+ };
124
+
125
+ // A protocol pointer that ALREADY existed was written by someone else — typically a
126
+ // sibling lens's init, which seeded the project with its own triage ladder. This init
127
+ // must not overwrite it (create-only is the T1 guarantee), so the second ladder is
128
+ // written aside and merged by hand.
129
+ let protocolPreexisting = false;
130
+
131
+ for (const client of CLIENTS) {
132
+ if (clientDetected(client)) {
133
+ console.log(`✅ ${client.label} detected.`);
134
+ const created = writeIfNotExists(protocolFiles[client.key], protocolContent, `${client.label} protocol pointer`);
135
+ if (!created) protocolPreexisting = true;
136
+ }
137
+ }
138
+
139
+ // Cursor/Windsurf (always recommended)
140
+ writeIfNotExists('.cursorrules', protocolContent, 'Cursor/Windsurf rules');
141
+
142
+ // 6b. Sibling lenses: additive only. Never edits a user-authored root file.
143
+ function installedSiblingLenses() {
144
+ const found = new Map();
145
+ for (const client of CLIENTS) {
146
+ if (!clientDetected(client)) continue;
147
+ const skillsDir = path.dirname(skillTarget(client));
148
+ for (const [dirName, lens] of Object.entries(SIBLING_LENSES)) {
149
+ if (fs.existsSync(path.join(skillsDir, dirName))) found.set(dirName, lens);
150
+ }
151
+ }
152
+ return found;
153
+ }
154
+
155
+ const siblings = installedSiblingLenses();
156
+ if (siblings.size > 0) {
157
+ const list = [...siblings].map(([dir, lens]) => `- \`${dir}\` — the **${lens}** lens`).join('\n');
158
+ console.log(`\n🔀 Sibling lens detected: ${[...siblings.keys()].join(', ')}.`);
159
+ const wrote = writeIfNotExists('AGENTIC_MULTI_LENS.md', `# Multi-lens project — routing note (additive)
160
+
161
+ This project has more than one lens of the Agentic SDLC family installed:
162
+
163
+ ${list}
164
+ - \`${INSTALLED_SKILL_NAME}\` — the **${SELF_LENS}** lens
165
+
166
+ One \`ai_docs/\` tree, one project default (\`default_domain:\` in \`ai_docs/README.md\`),
167
+ one lens per unit of work. Before acting on any L2, L3 or Spike, run the domain router
168
+ in the skill's \`routing.md\`: it decides which lens's method and validation rules govern
169
+ that unit. L1 never reaches it. Never refer to a document whose meaning differs by lens
170
+ ("threat model", "vision", \`principles.md\`, \`handoff.md\`) by its bare name.
171
+
172
+ ${protocolPreexisting ? `**Merge step owed.** The always-on protocol pointer of this project (\`CLAUDE.md\` /
173
+ \`GEMINI.md\` / \`AGENTS.md\` / \`.cursorrules\`) was written by another lens's init and
174
+ carries ITS triage ladder. This init did not overwrite it. Add the code lens's ladder
175
+ to that file by hand — the pointer below — so both are always loaded.
176
+
177
+ ## Rule Zero — Triage every request (code lens)
178
+ - L1 Trivial: ~10 lines, 1-2 files, no API/dependency/behavior change.
179
+ - L2 Small: clear root cause, at most 3 files, low risk. Tests mandatory.
180
+ - L3 Significant: >3 files, APIs/contracts, new dependency, user-visible behavior,
181
+ security-sensitive area, or architectural change. Full workflow via the skill.
182
+ - Spike: time-boxed exploration; outcome in \`ai_docs/solutions/SPIKE_[topic].md\`.
183
+ ` : `The always-on protocol pointer for the code lens was created by this init. When you
184
+ install another lens over this project, its init will leave its own ladder here for you
185
+ to merge.
186
+ `}
187
+ This file is NOT auto-loaded by any client: it is a note for you, deliberately additive.
188
+ Delete it once the merge is done.
189
+ `, 'multi-lens routing note');
190
+ if (wrote && protocolPreexisting) {
191
+ console.log(' ⚠️ A protocol pointer already existed and was NOT overwritten.');
192
+ console.log(` Merge the ${SELF_LENS}-lens ladder from AGENTIC_MULTI_LENS.md into it by hand.`);
193
+ }
194
+ }
195
+
196
+ // 7. Generate ai_docs/INDEX.md so the very first `validate` is already clean.
197
+ // The manifest is generated, never seeded: delegate to the validator if Python is available.
198
+ const validator = path.join(SKILL_SOURCE, 'scripts', 'sdlc_check.py');
199
+ let indexed = false;
200
+ for (const py of ['python', 'python3', 'py']) {
201
+ try {
202
+ execSync(`${py} "${validator}" index --root "${cwd}"`, { stdio: 'ignore' });
203
+ console.log('📇 Generated ai_docs/INDEX.md (document manifest).');
204
+ indexed = true;
205
+ break;
206
+ } catch (e) { /* try the next interpreter */ }
207
+ }
208
+ if (!indexed) {
209
+ console.log('ℹ️ Python not found: generate the manifest later with '
210
+ + '"python <skill_dir>/scripts/sdlc_check.py index" (validate reports it until then).');
211
+ }
212
+
213
+ console.log('\n✅ Setup completed successfully!');
214
+ console.log('💡 Next steps:');
215
+ console.log(' 1. Make sure the agentic-sdlc skill is installed (agentic-sdlc-install-skill).');
216
+ console.log(' 2. Restart/open the project in your AI client so it reads the protocol pointer.');
217
+ console.log(' 3. Start with an audit following ai_docs/audit/audit_plan.md.');
package/scripts/lib.js CHANGED
@@ -10,6 +10,59 @@ const { execSync } = require('child_process');
10
10
  const PACKAGE_ROOT = path.resolve(__dirname, '..');
11
11
  const SKILL_SOURCE = path.join(PACKAGE_ROOT, 'skills', 'agentic-sdlc-skill');
12
12
  const TEMPLATES_PATH = path.join(SKILL_SOURCE, 'templates.md');
13
+ // The directory name each client loads the skill from. Derived from the manifest,
14
+ // never hard-coded by a consumer: three distributions share these scripts, and a
15
+ // literal here is how a copy-fork starts installing under its sibling's name.
16
+ const INSTALLED_SKILL_NAME = (() => {
17
+ const m = fs.readFileSync(path.join(SKILL_SOURCE, 'SKILL.md'), 'utf8').match(/^name:\s*(\S+)/m);
18
+ if (!m) throw new Error(`SKILL.md carries no 'name:' field: ${SKILL_SOURCE}`);
19
+ return m[1];
20
+ })();
21
+
22
+ // The family's lens table, read from the shared `routing.md` rather than restated
23
+ // here. Same reason as INSTALLED_SKILL_NAME above, and the same failure it prevents:
24
+ // BOTH the row for this lens and the rows for its siblings used to be literals copied
25
+ // between distributions, so kb and mkt wrote a multi-lens note announcing themselves
26
+ // as the code lens. routing.md is one of the byte-identical shared files, so this
27
+ // lookup cannot drift between distributions.
28
+ // Lazy on purpose: preuninstall.js needs INSTALLED_SKILL_NAME and nothing else, and
29
+ // must keep working on an installation damaged badly enough to have lost routing.md.
30
+ function lensTable() {
31
+ const routing = path.join(SKILL_SOURCE, 'routing.md');
32
+ if (!fs.existsSync(routing)) throw new Error(`routing.md missing from the skill: ${SKILL_SOURCE}`);
33
+ // Parsed as a table, not matched with a regex: a mis-escaped pattern matches the
34
+ // empty string and yields undefined instead of throwing, which is how this lookup
35
+ // failed the first time it was written.
36
+ const table = new Map();
37
+ for (const line of fs.readFileSync(routing, 'utf8').split(/\r?\n/)) {
38
+ const cells = line.split('|').map((cell) => cell.trim());
39
+ if (cells.length < 4) continue;
40
+ const [, lens, skill] = cells;
41
+ if (!/^[a-z]+$/.test(lens) || !/^`[a-z0-9-]+`$/.test(skill)) continue;
42
+ const name = skill.slice(1, -1);
43
+ // A duplicate row would otherwise be won silently by whichever came first, and a
44
+ // wrong routing.md propagates byte-identically to every distribution.
45
+ if (table.has(name)) throw new Error(`routing.md lists '${name}' more than once`);
46
+ table.set(name, lens);
47
+ }
48
+ if (!table.size) throw new Error(`routing.md carries no lens table: ${routing}`);
49
+ return table;
50
+ }
51
+
52
+ function selfLens() {
53
+ const lens = lensTable().get(INSTALLED_SKILL_NAME);
54
+ if (!lens) throw new Error(`routing.md has no lens row for '${INSTALLED_SKILL_NAME}'`);
55
+ return lens;
56
+ }
57
+
58
+ // Sibling lenses of the same family: one shared core, one docs tree, a different
59
+ // fidelity discipline each. Keyed by the installed skill directory name.
60
+ function siblingLenses() {
61
+ const table = lensTable();
62
+ selfLens(); // this lens must be in the table too
63
+ table.delete(INSTALLED_SKILL_NAME);
64
+ return Object.fromEntries(table);
65
+ }
13
66
 
14
67
  // One entry per supported AI client. `home` may be overridden by an env var
15
68
  // (Claude Desktop / portable installs); presence of the home dir counts as
@@ -87,7 +140,7 @@ function skillTarget(client) {
87
140
  // (split on '/' to keep cross-platform path.join correctness). Entries
88
141
  // without it resolve to <home>/skills/agentic-sdlc exactly as before.
89
142
  const subdir = client.skillsSubdir ? client.skillsSubdir.split('/') : ['skills'];
90
- return path.join(client.home, ...subdir, 'agentic-sdlc');
143
+ return path.join(client.home, ...subdir, INSTALLED_SKILL_NAME);
91
144
  }
92
145
 
93
146
  function copyRecursive(src, dest) {
@@ -157,6 +210,7 @@ function templateFor(sections, needle, index = 0) {
157
210
  module.exports = {
158
211
  PACKAGE_ROOT,
159
212
  SKILL_SOURCE,
213
+ INSTALLED_SKILL_NAME,
160
214
  TEMPLATES_PATH,
161
215
  CLIENTS,
162
216
  commandExists,
@@ -166,3 +220,8 @@ module.exports = {
166
220
  loadTemplates,
167
221
  templateFor,
168
222
  };
223
+
224
+ // Lazy: reading routing.md is deferred to the consumer that actually asks.
225
+ Object.defineProperty(module.exports, 'SELF_LENS', { enumerable: true, get: selfLens });
226
+ Object.defineProperty(module.exports, 'SIBLING_LENSES', { enumerable: true, get: siblingLenses });
227
+
@@ -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