@antoneeo/agentic-sdlc-skill 1.20.2 → 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/CHANGELOG.md CHANGED
@@ -2,6 +2,42 @@
2
2
 
3
3
  Tutte le modifiche significative a questa skill saranno documentate in questo file.
4
4
 
5
+ ## [1.20.3] - 2026-08-02
6
+
7
+ Three defects found by a field test in which cold agents operated the three published
8
+ skills on real fixtures — a brownfield API, three infrastructure documents, a business
9
+ brief. None of them had been found by any static pass.
10
+
11
+ ### Fixed
12
+ - **The multi-lens routing note named the wrong lens** (shipped in kb 1.0.0 and mkt
13
+ 0.3.0; invisible in the code package, where the literal happened to be right). The row
14
+ for the CURRENT lens was hardcoded as `` `agentic-sdlc` — the **code** lens `` and
15
+ copied verbatim into every distribution, so a kb project listed the code lens twice
16
+ and never named kb. Both the self row AND the sibling table are now derived from the
17
+ `routing.md` lens table — the same authoritative, drift-guarded file the domain router
18
+ reads — so no lens word is restated by hand anywhere. The lookup is lazy: `preuninstall`
19
+ keeps working on an installation that lost `routing.md`, a duplicate row is refused
20
+ instead of silently winning, and a missing row throws.
21
+ - **`FACT` was the way past the URL rule** (marketing lens). `BENCHMARK` owes a source
22
+ URL and `ASSUMPTION` a range, but `FACT` — "user input or primary data" — owed nothing,
23
+ so a researched observation classed FACT with source "see research/VOC.md" entered the
24
+ ledger unsourced. `mkt_check.py` now errors on a FACT with no source, or one pointing at
25
+ a document of the engagement, while exempting a cell that names the client as origin
26
+ (their own primary data may well be a file they handed over). `research.md` states the
27
+ two legal origins.
28
+ - **A review verdict could be delivered nowhere.** `review.md` (all three copies) now says
29
+ the verdict travels as the reviewer's final output, and says it on both sides — the
30
+ requester's and the reviewer's. A reviewer that messages the requester mid-run depends
31
+ on a channel it cannot verify and which fails silently; a requester waiting for that
32
+ message stalls holding a verdict that already exists.
33
+
34
+ ### Changed
35
+ - The guarding test for the routing note asserted that it named the detected SIBLING,
36
+ never itself — which is why the bug shipped. It now asserts this lens is named, with
37
+ its own lens word and no duplicates, and is mutation-tested against the original
38
+ defect. `A_SIBLING` is derived from the lens table rather than parsed out of the file
39
+ under test.
40
+
5
41
  ## [1.20.2] - 2026-08-01
6
42
 
7
43
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-sdlc-skill",
3
- "version": "1.20.2",
3
+ "version": "1.20.3",
4
4
  "description": "Documentation-First SDLC protocol with triage, Vision governance and optional devPNT integration.",
5
5
  "author": "Antonio Pinto (https://github.com/Antoneeo)"
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@antoneeo/agentic-sdlc-skill",
3
- "version": "1.20.2",
3
+ "version": "1.20.3",
4
4
  "description": "Documentation-First SDLC protocol for Claude Code, Gemini CLI, Google Antigravity and Codex with risk triage, Vision governance, installed support files and optional devPNT integration.",
5
5
  "keywords": [
6
6
  "claude-code",
package/scripts/init.js CHANGED
@@ -3,14 +3,7 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
  const { execSync } = require('child_process');
6
- const { SKILL_SOURCE, CLIENTS, clientDetected, skillTarget, loadTemplates, templateFor } = require('./lib');
7
-
8
- // Sibling lenses of the same family: one shared core, one `ai_docs/` tree, a different
9
- // fidelity discipline each. Keyed by the installed skill directory name.
10
- const SIBLING_LENSES = {
11
- 'kb-agentic': 'knowledge',
12
- 'mkt-agentic-sdlc': 'marketing',
13
- };
6
+ const { SKILL_SOURCE, INSTALLED_SKILL_NAME, SELF_LENS, SIBLING_LENSES, CLIENTS, clientDetected, skillTarget, loadTemplates, templateFor } = require('./lib');
14
7
 
15
8
  const cwd = process.cwd();
16
9
 
@@ -168,7 +161,7 @@ if (siblings.size > 0) {
168
161
  This project has more than one lens of the Agentic SDLC family installed:
169
162
 
170
163
  ${list}
171
- - \`agentic-sdlc\` — the **code** lens
164
+ - \`${INSTALLED_SKILL_NAME}\` — the **${SELF_LENS}** lens
172
165
 
173
166
  One \`ai_docs/\` tree, one project default (\`default_domain:\` in \`ai_docs/README.md\`),
174
167
  one lens per unit of work. Before acting on any L2, L3 or Spike, run the domain router
@@ -196,7 +189,7 @@ Delete it once the merge is done.
196
189
  `, 'multi-lens routing note');
197
190
  if (wrote && protocolPreexisting) {
198
191
  console.log(' ⚠️ A protocol pointer already existed and was NOT overwritten.');
199
- console.log(' Merge the code-lens ladder from AGENTIC_MULTI_LENS.md into it by hand.');
192
+ console.log(` Merge the ${SELF_LENS}-lens ladder from AGENTIC_MULTI_LENS.md into it by hand.`);
200
193
  }
201
194
  }
202
195
 
package/scripts/lib.js CHANGED
@@ -1,177 +1,227 @@
1
- // Shared helpers for the Agentic SDLC npm scripts (init / postinstall / preuninstall).
2
- // Single source for client detection and skill-target paths: init and postinstall
3
- // must never disagree on what "Claude Code is installed" means.
4
-
5
- const fs = require('fs');
6
- const path = require('path');
7
- const os = require('os');
8
- const { execSync } = require('child_process');
9
-
10
- const PACKAGE_ROOT = path.resolve(__dirname, '..');
11
- const SKILL_SOURCE = path.join(PACKAGE_ROOT, 'skills', 'agentic-sdlc-skill');
12
- const TEMPLATES_PATH = path.join(SKILL_SOURCE, 'templates.md');
13
- // The directory name each client loads the skill from. Derived from the manifest,
14
- // never hard-coded by a consumer: three distributions share these scripts, and a
15
- // literal here is how a copy-fork starts installing under its sibling's name.
16
- const INSTALLED_SKILL_NAME = (() => {
17
- const m = fs.readFileSync(path.join(SKILL_SOURCE, 'SKILL.md'), 'utf8').match(/^name:\s*(\S+)/m);
18
- if (!m) throw new Error(`SKILL.md carries no 'name:' field: ${SKILL_SOURCE}`);
19
- return m[1];
20
- })();
21
-
22
- // 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
- };
1
+ // Shared helpers for the Agentic SDLC npm scripts (init / postinstall / preuninstall).
2
+ // Single source for client detection and skill-target paths: init and postinstall
3
+ // must never disagree on what "Claude Code is installed" means.
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const os = require('os');
8
+ const { execSync } = require('child_process');
9
+
10
+ const PACKAGE_ROOT = path.resolve(__dirname, '..');
11
+ const SKILL_SOURCE = path.join(PACKAGE_ROOT, 'skills', 'agentic-sdlc-skill');
12
+ const TEMPLATES_PATH = path.join(SKILL_SOURCE, 'templates.md');
13
+ // The directory name each client loads the skill from. Derived from the manifest,
14
+ // never hard-coded by a consumer: three distributions share these scripts, and a
15
+ // literal here is how a copy-fork starts installing under its sibling's name.
16
+ const INSTALLED_SKILL_NAME = (() => {
17
+ const m = fs.readFileSync(path.join(SKILL_SOURCE, 'SKILL.md'), 'utf8').match(/^name:\s*(\S+)/m);
18
+ if (!m) throw new Error(`SKILL.md carries no 'name:' field: ${SKILL_SOURCE}`);
19
+ return m[1];
20
+ })();
21
+
22
+ // The family's lens table, read from the shared `routing.md` rather than restated
23
+ // here. Same reason as INSTALLED_SKILL_NAME above, and the same failure it prevents:
24
+ // BOTH the row for this lens and the rows for its siblings used to be literals copied
25
+ // between distributions, so kb and mkt wrote a multi-lens note announcing themselves
26
+ // as the code lens. routing.md is one of the byte-identical shared files, so this
27
+ // lookup cannot drift between distributions.
28
+ // Lazy on purpose: preuninstall.js needs INSTALLED_SKILL_NAME and nothing else, and
29
+ // must keep working on an installation damaged badly enough to have lost routing.md.
30
+ function lensTable() {
31
+ const routing = path.join(SKILL_SOURCE, 'routing.md');
32
+ if (!fs.existsSync(routing)) throw new Error(`routing.md missing from the skill: ${SKILL_SOURCE}`);
33
+ // Parsed as a table, not matched with a regex: a mis-escaped pattern matches the
34
+ // empty string and yields undefined instead of throwing, which is how this lookup
35
+ // failed the first time it was written.
36
+ const table = new Map();
37
+ for (const line of fs.readFileSync(routing, 'utf8').split(/\r?\n/)) {
38
+ const cells = line.split('|').map((cell) => cell.trim());
39
+ if (cells.length < 4) continue;
40
+ const [, lens, skill] = cells;
41
+ if (!/^[a-z]+$/.test(lens) || !/^`[a-z0-9-]+`$/.test(skill)) continue;
42
+ const name = skill.slice(1, -1);
43
+ // A duplicate row would otherwise be won silently by whichever came first, and a
44
+ // wrong routing.md propagates byte-identically to every distribution.
45
+ if (table.has(name)) throw new Error(`routing.md lists '${name}' more than once`);
46
+ table.set(name, lens);
47
+ }
48
+ if (!table.size) throw new Error(`routing.md carries no lens table: ${routing}`);
49
+ return table;
50
+ }
51
+
52
+ function selfLens() {
53
+ const lens = lensTable().get(INSTALLED_SKILL_NAME);
54
+ if (!lens) throw new Error(`routing.md has no lens row for '${INSTALLED_SKILL_NAME}'`);
55
+ return lens;
56
+ }
57
+
58
+ // Sibling lenses of the same family: one shared core, one docs tree, a different
59
+ // fidelity discipline each. Keyed by the installed skill directory name.
60
+ function siblingLenses() {
61
+ const table = lensTable();
62
+ selfLens(); // this lens must be in the table too
63
+ table.delete(INSTALLED_SKILL_NAME);
64
+ return Object.fromEntries(table);
65
+ }
66
+
67
+ // One entry per supported AI client. `home` may be overridden by an env var
68
+ // (Claude Desktop / portable installs); presence of the home dir counts as
69
+ // detection even when the CLI is not on PATH.
70
+ const CLIENTS = [
71
+ {
72
+ key: 'claude',
73
+ label: 'Claude Code',
74
+ cmd: 'claude',
75
+ home: process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude'),
76
+ envVar: 'CLAUDE_CONFIG_DIR',
77
+ reload: 'Restart Claude Code to load it. Invoke via Skill tool as "agentic-sdlc".',
78
+ },
79
+ {
80
+ key: 'gemini',
81
+ label: 'Gemini CLI',
82
+ cmd: 'gemini',
83
+ home: process.env.GEMINI_HOME || path.join(os.homedir(), '.gemini'),
84
+ envVar: 'GEMINI_HOME',
85
+ reload: 'Run "gemini skills reload" or restart Gemini CLI to load it.',
86
+ },
87
+ {
88
+ key: 'codex',
89
+ label: 'Codex AI',
90
+ cmd: 'codex',
91
+ home: process.env.CODEX_HOME || path.join(os.homedir(), '.codex'),
92
+ envVar: 'CODEX_HOME',
93
+ reload: 'Restart Codex to load it. Invoke it as "$agentic-sdlc" or by asking for Agentic SDLC.',
94
+ },
95
+ {
96
+ // Google Antigravity 2.0 discovers global agent skills under
97
+ // ~/.gemini/config/skills/ -- the SAME home the legacy Gemini CLI claims.
98
+ // To avoid a shared-home double-install (P-TM T1), this entry sets:
99
+ // - skillsSubdir 'config/skills': distinct target from gemini's ~/.gemini/skills
100
+ // - homeMarker on ~/.gemini/config/skills: detection never fires on bare
101
+ // ~/.gemini (which every Antigravity user has); only the Antigravity skills
102
+ // dir, the `agy` CLI, or ANTIGRAVITY_HOME count as "Antigravity installed".
103
+ key: 'antigravity',
104
+ label: 'Google Antigravity',
105
+ cmd: 'agy',
106
+ home: process.env.ANTIGRAVITY_HOME || path.join(os.homedir(), '.gemini'),
107
+ envVar: 'ANTIGRAVITY_HOME',
108
+ skillsSubdir: 'config/skills',
109
+ homeMarker: path.join(
110
+ process.env.ANTIGRAVITY_HOME || path.join(os.homedir(), '.gemini'),
111
+ 'config',
112
+ 'skills',
113
+ ),
114
+ reload: 'Restart Antigravity, or run "agy skills reload", to load it. Invoke by asking for Agentic SDLC.',
115
+ },
116
+ ];
117
+
118
+ function commandExists(cmd) {
119
+ try {
120
+ execSync(`${cmd} --version`, { stdio: 'ignore' });
121
+ return true;
122
+ } catch (e) {
123
+ return false;
124
+ }
125
+ }
126
+
127
+ function clientDetected(client) {
128
+ // An entry may override the fs-existence probe with a `homeMarker` (a more
129
+ // specific path than the bare home) so two clients sharing a home dir do not
130
+ // both fire on its mere existence. Entries without a marker check `home`
131
+ // exactly as before (backward-compatible).
132
+ const homePathToCheck = client.homeMarker || client.home;
133
+ return commandExists(client.cmd)
134
+ || Boolean(process.env[client.envVar])
135
+ || fs.existsSync(homePathToCheck);
136
+ }
137
+
138
+ function skillTarget(client) {
139
+ // An entry may override the default `skills` sub-path with `skillsSubdir`
140
+ // (split on '/' to keep cross-platform path.join correctness). Entries
141
+ // without it resolve to <home>/skills/agentic-sdlc exactly as before.
142
+ const subdir = client.skillsSubdir ? client.skillsSubdir.split('/') : ['skills'];
143
+ return path.join(client.home, ...subdir, INSTALLED_SKILL_NAME);
144
+ }
145
+
146
+ function copyRecursive(src, dest) {
147
+ if (typeof fs.cpSync === 'function') {
148
+ fs.cpSync(src, dest, { recursive: true, force: true });
149
+ return;
150
+ }
151
+ // Fallback for Node < 16.7
152
+ if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
153
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
154
+ const s = path.join(src, entry.name);
155
+ const d = path.join(dest, entry.name);
156
+ if (entry.isDirectory()) copyRecursive(s, d);
157
+ else fs.copyFileSync(s, d);
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Parse templates.md into { headingText: [fencedBlock, ...] }.
163
+ * Templates are single-sourced there: the init script must extract them
164
+ * instead of carrying its own inline copies (which historically drifted).
165
+ */
166
+ function loadTemplates() {
167
+ const text = fs.readFileSync(TEMPLATES_PATH, 'utf8');
168
+ const lines = text.split(/\r?\n/);
169
+ const sections = {};
170
+ let heading = null;
171
+ let block = null;
172
+ for (const line of lines) {
173
+ const h = line.match(/^##\s+(.*)$/);
174
+ if (h && block === null) {
175
+ heading = h[1].trim();
176
+ sections[heading] = sections[heading] || [];
177
+ continue;
178
+ }
179
+ if (/^```/.test(line)) {
180
+ if (block === null) {
181
+ block = [];
182
+ } else {
183
+ if (heading) sections[heading].push(block.join('\n') + '\n');
184
+ block = null;
185
+ }
186
+ continue;
187
+ }
188
+ if (block !== null) block.push(line);
189
+ }
190
+ return sections;
191
+ }
192
+
193
+ /**
194
+ * Return the Nth fenced block of the section whose heading contains `needle`.
195
+ * Throws with a clear message when missing: writing a wrong or empty
196
+ * boilerplate silently would be worse than failing the init.
197
+ */
198
+ function templateFor(sections, needle, index = 0) {
199
+ const heading = Object.keys(sections).find((h) => h.includes(needle));
200
+ const blocks = heading ? sections[heading] : undefined;
201
+ if (!blocks || !blocks[index]) {
202
+ throw new Error(
203
+ `Template section containing "${needle}" (block ${index}) not found in ${TEMPLATES_PATH}. ` +
204
+ 'The package is corrupted or templates.md was restructured: fix templates.md, do not improvise content.'
205
+ );
206
+ }
207
+ return blocks[index];
208
+ }
209
+
210
+ module.exports = {
211
+ PACKAGE_ROOT,
212
+ SKILL_SOURCE,
213
+ INSTALLED_SKILL_NAME,
214
+ TEMPLATES_PATH,
215
+ CLIENTS,
216
+ commandExists,
217
+ clientDetected,
218
+ skillTarget,
219
+ copyRecursive,
220
+ loadTemplates,
221
+ templateFor,
222
+ };
223
+
224
+ // Lazy: reading routing.md is deferred to the consumer that actually asks.
225
+ Object.defineProperty(module.exports, 'SELF_LENS', { enumerable: true, get: selfLens });
226
+ Object.defineProperty(module.exports, 'SIBLING_LENSES', { enumerable: true, get: siblingLenses });
227
+
@@ -85,6 +85,14 @@ When you hand work to a reviewer (human or agent), give them:
85
85
  should have been. The design reviewer checks that every threat surface the change
86
86
  touches has a matching security requirement.
87
87
 
88
+ **The verdict travels back as the reviewer's own final output** — the text it
89
+ returns when it finishes, nothing else. A reviewer that tries to message the
90
+ requester mid-run depends on a delivery channel it cannot verify (a subagent
91
+ addressed by agent TYPE rather than by session gets no such channel, and the
92
+ attempt fails silently); a requester that waits for such a message stalls
93
+ holding a verdict that already exists. State the return form when you request
94
+ the review, and read the verdict where it actually arrives.
95
+
88
96
  Never ask a reviewer to "review my session" or "review what I just did"
89
97
  without the artifacts above — that forces them to reconstruct scope from
90
98
  conversation instead of reviewing the change itself. Say which finding
@@ -109,6 +117,10 @@ When you are the reviewer:
109
117
 
110
118
  - Verify claims against the real source, not against the diff's own
111
119
  description of itself.
120
+ - **Your verdict is your final output.** Deliver findings and verdict as the text
121
+ you return when you finish — never only through a message to the requester, a
122
+ channel you cannot verify and which fails silently when it is not there
123
+ (see `## Requesting`).
112
124
  - Cite evidence as `file:line` for every finding — a finding without a
113
125
  location is not actionable.
114
126
  - Keep severity honest: do not inflate a style preference to a blocker, and