@sabaiway/agent-workflow-kit 10.3.0 → 10.5.0
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 +85 -0
- package/README.md +5 -5
- package/SKILL.md +1 -1
- package/bridges/antigravity-cli-bridge/SKILL.md +7 -1
- package/bridges/antigravity-cli-bridge/bin/agy-review.sh +69 -17
- package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +73 -2
- package/bridges/antigravity-cli-bridge/capability.json +2 -2
- package/bridges/antigravity-cli-bridge/references/review-prompt.md +3 -0
- package/bridges/codex-cli-bridge/SKILL.md +8 -1
- package/bridges/codex-cli-bridge/bin/codex-exec.sh +1 -1
- package/bridges/codex-cli-bridge/bin/codex-review-honesty.test.mjs +1 -1
- package/bridges/codex-cli-bridge/bin/codex-review.sh +89 -18
- package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +55 -2
- package/bridges/codex-cli-bridge/capability.json +2 -2
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/agents/executor.md +40 -0
- package/references/agents/review-lens.md +5 -3
- package/references/modes/agents.md +9 -4
- package/references/modes/procedures.md +21 -8
- package/references/modes/recipes.md +7 -4
- package/references/modes/recommendations.md +3 -1
- package/references/modes/set-recipe.md +23 -6
- package/references/modes/status.md +2 -2
- package/references/modes/upgrade.md +1 -1
- package/references/modes/velocity.md +1 -0
- package/references/shared/composition-handoff.md +1 -1
- package/references/shared/deploy-tail.md +1 -1
- package/references/templates/orchestration.json +1 -1
- package/tools/autonomy-config.mjs +1 -1
- package/tools/bridge-posture.mjs +48 -0
- package/tools/carriers.mjs +152 -0
- package/tools/cheap-agents-read.mjs +234 -0
- package/tools/cheap-agents.mjs +101 -109
- package/tools/commands.mjs +3 -3
- package/tools/detect-backends.mjs +2 -2
- package/tools/direct-run.mjs +9 -0
- package/tools/family-registry.mjs +38 -18
- package/tools/flow-check.mjs +2 -7
- package/tools/fold-scope.mjs +5 -60
- package/tools/grounding.mjs +2 -2
- package/tools/inject-methodology.mjs +4 -0
- package/tools/orchestration-config.mjs +23 -61
- package/tools/orchestration-readme.mjs +70 -0
- package/tools/plan-shape-cli.mjs +112 -0
- package/tools/plan-shape-facts.mjs +204 -0
- package/tools/plan-shape.mjs +348 -0
- package/tools/procedures.mjs +197 -83
- package/tools/recipes.mjs +183 -230
- package/tools/recommendations.mjs +77 -11
- package/tools/renderers.mjs +27 -7
- package/tools/repo-lex.mjs +40 -0
- package/tools/review-roster-resolve.mjs +104 -0
- package/tools/review-roster.mjs +128 -0
- package/tools/review-rounds-cli.mjs +92 -0
- package/tools/review-rounds.mjs +115 -0
- package/tools/review-state.mjs +10 -11
- package/tools/set-recipe-roster.mjs +167 -0
- package/tools/set-recipe.mjs +138 -42
- package/tools/velocity-profile.mjs +8 -22
- package/tools/view-model.mjs +17 -3
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
// cheap-agents-read.mjs — the READ-ONLY core of the subagent-vehicle surface: the bundle, the
|
|
2
|
+
// placement plan and the executor vehicle's readiness. Split from cheap-agents.mjs (the writer) so
|
|
3
|
+
// the read-only advisor graph (procedures -> recipes) reaches these facts WITHOUT importing a module
|
|
4
|
+
// that can create `.claude/agents/` — by construction, pinned by the read-graph purity walk.
|
|
5
|
+
|
|
6
|
+
import { existsSync, lstatSync, readFileSync, readdirSync } from 'node:fs';
|
|
7
|
+
import { dirname, join, resolve } from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { refuseDirectRun } from './direct-run.mjs';
|
|
10
|
+
import { deriveLensTemplate } from './review-roster-resolve.mjs';
|
|
11
|
+
|
|
12
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
|
|
14
|
+
export const AGENTS_DIR = '.claude/agents';
|
|
15
|
+
export const CLAUDE_DIR = '.claude';
|
|
16
|
+
export const WORKFLOW_STAMP = 'docs/ai/.workflow-version';
|
|
17
|
+
export const EXPECTED_WORKFLOW_VERSION = '3.0.0';
|
|
18
|
+
export const BUNDLED_AGENTS_DIR = resolve(HERE, '..', 'references', 'agents');
|
|
19
|
+
|
|
20
|
+
export const UTF8 = 'utf8';
|
|
21
|
+
const ERROR_PREFIX = '[agent-workflow-kit]';
|
|
22
|
+
const EXIT_PRECONDITION = 1;
|
|
23
|
+
|
|
24
|
+
export const CHEAP_AGENTS_STAMP = 'CHEAP_AGENTS_STAMP';
|
|
25
|
+
export const CHEAP_AGENTS_SYMLINK = 'CHEAP_AGENTS_SYMLINK';
|
|
26
|
+
export const CHEAP_AGENTS_BUNDLE = 'CHEAP_AGENTS_BUNDLE';
|
|
27
|
+
export const CHEAP_AGENTS_CONFIG = 'CHEAP_AGENTS_CONFIG';
|
|
28
|
+
|
|
29
|
+
export const makeCheapAgentsError = (code, message) =>
|
|
30
|
+
Object.assign(new Error(`${ERROR_PREFIX} ${message}`), { name: 'CheapAgentsError', code, exitCode: EXIT_PRECONDITION });
|
|
31
|
+
|
|
32
|
+
// The injectable READ surface — four functions, none of which can create or modify a file. The
|
|
33
|
+
// writer shell adds its own mkdir/writeFile pair rather than widening this one.
|
|
34
|
+
export const readFsDeps = (deps = {}) => ({
|
|
35
|
+
exists: deps.exists ?? existsSync,
|
|
36
|
+
lstat: deps.lstat ?? lstatSync,
|
|
37
|
+
readFile: deps.readFile ?? readFileSync,
|
|
38
|
+
readdir: deps.readdir ?? readdirSync,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export const lstatNoFollow = (absPath, fs) => {
|
|
42
|
+
try {
|
|
43
|
+
return fs.lstat(absPath);
|
|
44
|
+
} catch (err) {
|
|
45
|
+
if (err && err.code === 'ENOENT') return null;
|
|
46
|
+
throw err;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// ── the bundle (the kit's own references/agents/) ─────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
export const readBundledAgents = (deps = {}) => {
|
|
53
|
+
const fs = readFsDeps(deps);
|
|
54
|
+
const bundleDir = deps.bundleDir ?? BUNDLED_AGENTS_DIR;
|
|
55
|
+
let names;
|
|
56
|
+
try {
|
|
57
|
+
names = fs.readdir(bundleDir);
|
|
58
|
+
} catch (err) {
|
|
59
|
+
throw makeCheapAgentsError(CHEAP_AGENTS_BUNDLE, `bundled agents dir unreadable (${err.code ?? err.message}): ${bundleDir}`);
|
|
60
|
+
}
|
|
61
|
+
const templates = names
|
|
62
|
+
.filter((name) => name.endsWith('.md'))
|
|
63
|
+
.sort()
|
|
64
|
+
.map((name) => ({ name, content: fs.readFile(join(bundleDir, name), UTF8) }));
|
|
65
|
+
if (templates.length === 0) {
|
|
66
|
+
throw makeCheapAgentsError(CHEAP_AGENTS_BUNDLE, `no bundled agent templates found in ${bundleDir} — the kit install is incomplete`);
|
|
67
|
+
}
|
|
68
|
+
return templates;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// ── the preflight reads (velocity discipline: symlink-safe, stamp read, no writes) ────
|
|
72
|
+
|
|
73
|
+
export const readStamp = (absPath, fs) => {
|
|
74
|
+
try {
|
|
75
|
+
if (!fs.exists(absPath)) return null;
|
|
76
|
+
const stamp = String(fs.readFile(absPath, UTF8)).trim();
|
|
77
|
+
return stamp.length ? stamp : null;
|
|
78
|
+
} catch {
|
|
79
|
+
return null; // unreadable stamp == not a valid deployment stamp (apply STOPs; dry-run reports)
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const assertDirSafe = (absPath, relPath, fs) => {
|
|
84
|
+
const stat = lstatNoFollow(absPath, fs);
|
|
85
|
+
if (stat === null) return { absent: true };
|
|
86
|
+
if (stat.isSymbolicLink()) throw makeCheapAgentsError(CHEAP_AGENTS_SYMLINK, `${relPath} is a symlink — refusing to write through it`);
|
|
87
|
+
if (!stat.isDirectory()) throw makeCheapAgentsError(CHEAP_AGENTS_SYMLINK, `${relPath} exists but is not a directory — refusing to write through it`);
|
|
88
|
+
return { absent: false };
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Per-template placement plan: place | already-current | customized-preserved (never clobbered).
|
|
92
|
+
export const planPlacement = (templates, projectDir, deps = {}) => {
|
|
93
|
+
const fs = readFsDeps(deps);
|
|
94
|
+
return templates.map((template) => {
|
|
95
|
+
const rel = `${AGENTS_DIR}/${template.name}`;
|
|
96
|
+
const abs = join(projectDir, AGENTS_DIR, template.name);
|
|
97
|
+
const stat = lstatNoFollow(abs, fs);
|
|
98
|
+
if (stat === null) return { ...template, rel, abs, action: 'place' };
|
|
99
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
100
|
+
throw makeCheapAgentsError(CHEAP_AGENTS_SYMLINK, `${rel} exists but is not a regular file — refusing to touch it`);
|
|
101
|
+
}
|
|
102
|
+
const existing = fs.readFile(abs, UTF8);
|
|
103
|
+
if (existing === template.content) return { ...template, rel, abs, action: 'already-current' };
|
|
104
|
+
return { ...template, rel, abs, action: 'customized-preserved', existing };
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// ── the executor vehicle's readiness (the subagent carrier's one instrument) ──────────
|
|
109
|
+
|
|
110
|
+
export const EXECUTOR_VEHICLE = 'executor.md';
|
|
111
|
+
export const EXECUTOR_VEHICLE_REL = `${AGENTS_DIR}/${EXECUTOR_VEHICLE}`;
|
|
112
|
+
export const EXECUTOR_VEHICLE_SPEC = Object.freeze({
|
|
113
|
+
stem: 'executor', template: 'executor', model: null, effort: null, tools: 'full', derived: false,
|
|
114
|
+
});
|
|
115
|
+
const READ_ONLY_TOOLS = new Set(['Read', 'Grep', 'Glob']);
|
|
116
|
+
|
|
117
|
+
// The YAML subset a vehicle's frontmatter is read with: a bare scalar, a single- or double-quoted
|
|
118
|
+
// scalar, or a flow sequence; an unquoted ` #comment` and surrounding whitespace are dropped first.
|
|
119
|
+
const stripComment = (raw) => String(raw ?? '').replace(/^((?:[^"'#]|"[^"]*"|'[^']*')*?)\s+#.*$/u, '$1').trim();
|
|
120
|
+
const cleanValue = (raw) => stripComment(raw).replace(/^(["'])(.*)\1$/u, '$2').replace(/^\[(.*)\]$/u, '$1').trim();
|
|
121
|
+
|
|
122
|
+
const scalarValue = (raw) => {
|
|
123
|
+
const value = stripComment(raw);
|
|
124
|
+
const quoted = value.match(/^(["'])(.*)\1$/u);
|
|
125
|
+
if (quoted) return quoted[2].trim();
|
|
126
|
+
return /^[[{|>]|["']/u.test(value) ? null : value;
|
|
127
|
+
};
|
|
128
|
+
const scalarOf = (frontmatter, key) => scalarValue(frontmatter.match(new RegExp(`^${key}:(.*)$`, 'mu'))?.[1]);
|
|
129
|
+
|
|
130
|
+
// The block-sequence items under the `tools:` key: `- item` lines indented deeper than the key,
|
|
131
|
+
// with blank and comment lines allowed between them; the first other line ends the list.
|
|
132
|
+
const blockItems = (frontmatter) => {
|
|
133
|
+
const lines = frontmatter.split('\n');
|
|
134
|
+
const start = lines.findIndex((line) => /^tools:/u.test(line));
|
|
135
|
+
if (start === -1) return [];
|
|
136
|
+
const keyIndent = lines[start].match(/^[ \t]*/u)[0].length;
|
|
137
|
+
const items = [];
|
|
138
|
+
for (const line of lines.slice(start + 1)) {
|
|
139
|
+
if (/^[ \t]*(#.*)?$/u.test(line)) continue;
|
|
140
|
+
const item = line.match(/^([ \t]*)-[ \t]*(.*)$/u);
|
|
141
|
+
if (!item || item[1].length <= keyIndent) break;
|
|
142
|
+
const value = cleanValue(item[2]);
|
|
143
|
+
if (value) items.push(value);
|
|
144
|
+
}
|
|
145
|
+
return items;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const frontmatterOf = (content) => String(content).replace(/\r\n/gu, '\n').match(/^---\n([\s\S]*?)\n---(?:\n|$)/u)?.[1] ?? '';
|
|
149
|
+
|
|
150
|
+
const listedTools = (frontmatter) => {
|
|
151
|
+
const inline = cleanValue(frontmatter.match(/^tools:(.*)$/mu)?.[1]);
|
|
152
|
+
return (inline || blockItems(frontmatter).join(', ')).split(',').map((tool) => cleanValue(tool)).filter(Boolean);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const executorFrontmatterRefusal = (frontmatter, stem) => {
|
|
156
|
+
for (const key of ['name', 'tools']) {
|
|
157
|
+
if ((frontmatter.match(new RegExp(`^${key}:`, 'gmu')) ?? []).length > 1) return `duplicate \`${key}:\` key in the frontmatter`;
|
|
158
|
+
}
|
|
159
|
+
if (scalarOf(frontmatter, 'name') !== stem) return `frontmatter does not declare \`name: ${stem}\``;
|
|
160
|
+
if (!/^tools:/mu.test(frontmatter)) return null;
|
|
161
|
+
const granted = listedTools(frontmatter);
|
|
162
|
+
if (granted.length === 0) return 'tools: is empty — grant a list that includes Bash, or drop the line';
|
|
163
|
+
return granted.includes('Bash') ? null : `tools: ${granted.join(', ')} is read-only`;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const lensFrontmatter = (frontmatter, stem) => {
|
|
167
|
+
for (const key of ['name', 'model', 'effort', 'tools']) {
|
|
168
|
+
const count = (frontmatter.match(new RegExp(`^${key}:`, 'gmu')) ?? []).length;
|
|
169
|
+
if (count !== 1) return { refusal: count === 0 ? `frontmatter has no \`${key}:\` key` : `duplicate \`${key}:\` key in the frontmatter` };
|
|
170
|
+
}
|
|
171
|
+
if (scalarOf(frontmatter, 'name') !== stem) {
|
|
172
|
+
return { refusal: `frontmatter does not declare \`name: ${stem}\`` };
|
|
173
|
+
}
|
|
174
|
+
const scalars = Object.fromEntries(['model', 'effort'].map((key) => [key, scalarOf(frontmatter, key)]));
|
|
175
|
+
for (const [key, value] of Object.entries(scalars)) {
|
|
176
|
+
if (value === null) return { refusal: `${key}: is not a scalar` };
|
|
177
|
+
if (value === '') return { refusal: `${key}: is empty` };
|
|
178
|
+
}
|
|
179
|
+
const tools = listedTools(frontmatter);
|
|
180
|
+
if (tools.length === 0) return { refusal: 'tools: must grant a non-empty read-only list' };
|
|
181
|
+
const unsafe = tools.find((tool) => !READ_ONLY_TOOLS.has(tool));
|
|
182
|
+
if (unsafe) return { refusal: `tools: grants non-read-only tool ${unsafe}` };
|
|
183
|
+
return { refusal: null, ...scalars };
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const templateFor = (spec, deps) => {
|
|
187
|
+
if (spec.template == null) return null;
|
|
188
|
+
const bundled = readBundledAgents(deps).find((item) => item.name === `${spec.template}.md`);
|
|
189
|
+
if (!bundled) throw makeCheapAgentsError(CHEAP_AGENTS_BUNDLE, `${spec.template}.md is missing from the bundle`);
|
|
190
|
+
return {
|
|
191
|
+
name: `${spec.stem}.md`,
|
|
192
|
+
content: spec.derived ? deriveLensTemplate(bundled.content, spec) : bundled.content,
|
|
193
|
+
};
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
export const surveyVehicle = (projectDir, spec, deps = {}) => {
|
|
197
|
+
const rel = `${AGENTS_DIR}/${spec.stem}.md`;
|
|
198
|
+
try {
|
|
199
|
+
const template = templateFor(spec, deps);
|
|
200
|
+
const fs = readFsDeps(deps);
|
|
201
|
+
assertDirSafe(join(projectDir, CLAUDE_DIR), CLAUDE_DIR, fs);
|
|
202
|
+
assertDirSafe(join(projectDir, AGENTS_DIR), AGENTS_DIR, fs);
|
|
203
|
+
const placeholder = template ?? { name: `${spec.stem}.md`, content: null };
|
|
204
|
+
const [placement] = planPlacement([placeholder], projectDir, deps);
|
|
205
|
+
if (placement.action === 'place') {
|
|
206
|
+
const reason = template === null ? 'no bundled template to derive it from' : null;
|
|
207
|
+
return { state: 'missing', reason, rel };
|
|
208
|
+
}
|
|
209
|
+
const frontmatter = frontmatterOf(placement.existing ?? template?.content);
|
|
210
|
+
if (placement.action === 'already-current') {
|
|
211
|
+
if (spec.tools !== 'read-only') return { state: 'placed', reason: null, rel };
|
|
212
|
+
const lens = lensFrontmatter(frontmatter, spec.stem);
|
|
213
|
+
return lens.refusal === null
|
|
214
|
+
? { state: 'placed', reason: null, rel, model: lens.model, effort: lens.effort }
|
|
215
|
+
: { state: 'unusable', reason: lens.refusal, rel };
|
|
216
|
+
}
|
|
217
|
+
if (spec.tools !== 'read-only') {
|
|
218
|
+
const refusal = executorFrontmatterRefusal(frontmatter, spec.stem);
|
|
219
|
+
return refusal === null
|
|
220
|
+
? { state: 'customized', reason: null, rel }
|
|
221
|
+
: { state: 'unusable', reason: refusal, rel };
|
|
222
|
+
}
|
|
223
|
+
const lens = lensFrontmatter(frontmatter, spec.stem);
|
|
224
|
+
return lens.refusal === null
|
|
225
|
+
? { state: 'customized', reason: null, rel, model: lens.model, effort: lens.effort }
|
|
226
|
+
: { state: 'unusable', reason: lens.refusal, rel };
|
|
227
|
+
} catch (err) {
|
|
228
|
+
return { state: 'unusable', reason: err?.message ?? String(err), rel };
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
export const surveyExecutorVehicle = (projectDir, deps = {}) => surveyVehicle(projectDir, EXECUTOR_VEHICLE_SPEC, deps);
|
|
233
|
+
|
|
234
|
+
refuseDirectRun(import.meta.url);
|
package/tools/cheap-agents.mjs
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// cheap-agents.mjs — the onboarding writer behind `/agent-workflow-kit agents`: places the
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// skeletons, gate triage) stops running on a frontier model by default
|
|
2
|
+
// cheap-agents.mjs — the onboarding writer behind `/agent-workflow-kit agents`: places the bundled
|
|
3
|
+
// subagent definitions (references/agents/*.md) into a project's .claude/agents/. FOUR vehicles
|
|
4
|
+
// grant NO shell — three ride the cheap lane (haiku/low-effort) so mechanical work (sweeps,
|
|
5
|
+
// changelog skeletons, gate triage) stops running on a frontier model by default, and review-lens
|
|
6
|
+
// is the read-only review opinion. The fifth, `executor`, is the ONE full-tool vehicle: dispatched
|
|
7
|
+
// only for a bounded execution, authoring, or write-capable routine slice the orchestrator verifies, never for read-only work, and it
|
|
8
|
+
// never commits. `surveyExecutorVehicle` is that vehicle's readiness, for the subagent carrier.
|
|
6
9
|
//
|
|
7
10
|
// The family's second `.claude/` writer, the velocity-profile.mjs writer discipline verbatim:
|
|
8
11
|
// • preview-then-mutate — `--dry-run` is the DEFAULT and writes nothing; `--apply` writes;
|
|
9
12
|
// • deployment-gated — `--apply` STOPs unless docs/ai/.workflow-version equals the lineage
|
|
10
|
-
// head (a dry-run stays usable
|
|
13
|
+
// head (a dry-run stays usable whatever the stamp says; an unreadable orchestration config
|
|
14
|
+
// STOPs both, since the derived lenses it names cannot be known);
|
|
11
15
|
// • symlink-safe — a symlinked `.claude` / `.claude/agents` / target file is a STOP, never a
|
|
12
16
|
// write-through;
|
|
13
17
|
// • NEVER overwrites an existing .claude/agents/ file whose content differs from the bundled
|
|
@@ -22,32 +26,59 @@
|
|
|
22
26
|
// apply report reminds you.
|
|
23
27
|
//
|
|
24
28
|
// Exit codes: 0 done / dry-run (incl. preserved customizations — a user's file is a legitimate
|
|
25
|
-
// state, not an error); 1 precondition STOP (stamp, symlink, missing bundle
|
|
29
|
+
// state, not an error); 1 precondition STOP (stamp, symlink, missing bundle, an unreadable
|
|
30
|
+
// orchestration config — the derived lenses it names cannot be known); 2 usage.
|
|
26
31
|
// Dependency-free, Node >= 22. No side effects on import.
|
|
27
32
|
|
|
28
|
-
import {
|
|
29
|
-
import { join
|
|
33
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
34
|
+
import { join } from 'node:path';
|
|
30
35
|
import { fileURLToPath } from 'node:url';
|
|
31
36
|
import { isDirectRun } from './direct-run.mjs';
|
|
32
37
|
import { shellQuoteArg } from './repo-lex.mjs';
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
import { loadConfig } from './orchestration-config.mjs';
|
|
39
|
+
import { lensMembersOf } from './review-roster.mjs';
|
|
40
|
+
import { deriveLensTemplate, lensVehicleSpec } from './review-roster-resolve.mjs';
|
|
41
|
+
// The READ core, never a second copy: the bundle, the placement plan and the executor survey live
|
|
42
|
+
// there so the read-only advisor graph can reach them without reaching this writer.
|
|
43
|
+
import {
|
|
44
|
+
AGENTS_DIR,
|
|
45
|
+
CLAUDE_DIR,
|
|
46
|
+
WORKFLOW_STAMP,
|
|
47
|
+
EXPECTED_WORKFLOW_VERSION,
|
|
48
|
+
UTF8,
|
|
49
|
+
CHEAP_AGENTS_STAMP,
|
|
50
|
+
CHEAP_AGENTS_BUNDLE,
|
|
51
|
+
CHEAP_AGENTS_CONFIG,
|
|
52
|
+
EXECUTOR_VEHICLE,
|
|
53
|
+
makeCheapAgentsError,
|
|
54
|
+
readFsDeps,
|
|
55
|
+
readBundledAgents,
|
|
56
|
+
readStamp,
|
|
57
|
+
assertDirSafe,
|
|
58
|
+
planPlacement,
|
|
59
|
+
} from './cheap-agents-read.mjs';
|
|
60
|
+
|
|
61
|
+
export {
|
|
62
|
+
AGENTS_DIR,
|
|
63
|
+
CLAUDE_DIR,
|
|
64
|
+
WORKFLOW_STAMP,
|
|
65
|
+
EXPECTED_WORKFLOW_VERSION,
|
|
66
|
+
BUNDLED_AGENTS_DIR,
|
|
67
|
+
CHEAP_AGENTS_STAMP,
|
|
68
|
+
CHEAP_AGENTS_SYMLINK,
|
|
69
|
+
CHEAP_AGENTS_BUNDLE,
|
|
70
|
+
CHEAP_AGENTS_CONFIG,
|
|
71
|
+
makeCheapAgentsError,
|
|
72
|
+
readBundledAgents,
|
|
73
|
+
planPlacement,
|
|
74
|
+
surveyExecutorVehicle,
|
|
75
|
+
EXECUTOR_VEHICLE,
|
|
76
|
+
EXECUTOR_VEHICLE_REL,
|
|
77
|
+
} from './cheap-agents-read.mjs';
|
|
41
78
|
|
|
42
79
|
const EXIT_OK = 0;
|
|
43
80
|
const EXIT_PRECONDITION = 1;
|
|
44
81
|
const EXIT_USAGE = 2;
|
|
45
|
-
const UTF8 = 'utf8';
|
|
46
|
-
const ERROR_PREFIX = '[agent-workflow-kit]';
|
|
47
|
-
|
|
48
|
-
export const CHEAP_AGENTS_STAMP = 'CHEAP_AGENTS_STAMP';
|
|
49
|
-
export const CHEAP_AGENTS_SYMLINK = 'CHEAP_AGENTS_SYMLINK';
|
|
50
|
-
export const CHEAP_AGENTS_BUNDLE = 'CHEAP_AGENTS_BUNDLE';
|
|
51
82
|
|
|
52
83
|
// The fallback-lens contract, formalized where it lives (flow-orchestration #15/#3, Phase 4.3):
|
|
53
84
|
// the internal-attestation evaluation consumes this sentence — a lens set claiming a configured
|
|
@@ -57,110 +88,69 @@ export const FALLBACK_LENS_ADDITIONAL_ONLY = 'review-lens is an ADDITIONAL read-
|
|
|
57
88
|
|
|
58
89
|
const USAGE = `usage: cheap-agents [--dry-run | --apply] [--cwd <dir>] [--help]
|
|
59
90
|
|
|
60
|
-
Places the bundled
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
91
|
+
Places the bundled subagent definitions into the project's ${AGENTS_DIR}/. Four vehicles grant NO
|
|
92
|
+
shell: three ride a cheap model (haiku/low) for mechanical work — extraction sweeps, changelog
|
|
93
|
+
fact-skeletons, gate triage — and review-lens is a read-only REVIEW vehicle on a review-capable
|
|
94
|
+
model. The fifth, executor, is the ONE full-tool vehicle: dispatched only for a bounded execution,
|
|
95
|
+
authoring, or write-capable routine slice the orchestrator verifies, never for read-only work, and
|
|
96
|
+
it never commits.
|
|
97
|
+
Default is --dry-run (a preview; writes nothing). --apply writes.
|
|
98
|
+
Configured derived review lenses are planned and placed beside the bundled vehicles.
|
|
64
99
|
An existing file with DIFFERENT content is preserved and reported, never overwritten.`;
|
|
65
100
|
|
|
66
101
|
export const fail = (exitCode, message) => Object.assign(new Error(message), { exitCode });
|
|
67
102
|
|
|
68
|
-
|
|
69
|
-
Object.assign(new Error(`${ERROR_PREFIX} ${message}`), { name: 'CheapAgentsError', code, exitCode: EXIT_PRECONDITION });
|
|
70
|
-
|
|
71
|
-
const fsDeps = (deps = {}) => ({
|
|
72
|
-
exists: deps.exists ?? existsSync,
|
|
73
|
-
lstat: deps.lstat ?? lstatSync,
|
|
103
|
+
const writeFsDeps = (deps = {}) => ({
|
|
74
104
|
mkdir: deps.mkdir ?? mkdirSync,
|
|
75
|
-
readFile: deps.readFile ?? readFileSync,
|
|
76
105
|
writeFile: deps.writeFile ?? writeFileSync,
|
|
77
|
-
readdir: deps.readdir ?? readdirSync,
|
|
78
106
|
});
|
|
79
107
|
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
108
|
+
export const preflightCheapAgents = ({ cwd, derived = [] }, deps = {}) => {
|
|
109
|
+
const fs = readFsDeps(deps);
|
|
110
|
+
const projectDir = cwd ?? process.cwd();
|
|
111
|
+
const templatesByName = new Map(readBundledAgents(deps).map((template) => [template.name, template]));
|
|
112
|
+
for (const template of derived) templatesByName.set(template.name, template);
|
|
113
|
+
const templates = [...templatesByName.values()];
|
|
114
|
+
const stamp = readStamp(join(projectDir, WORKFLOW_STAMP), fs);
|
|
115
|
+
const stampOk = stamp === EXPECTED_WORKFLOW_VERSION;
|
|
116
|
+
assertDirSafe(join(projectDir, CLAUDE_DIR), CLAUDE_DIR, fs);
|
|
117
|
+
assertDirSafe(join(projectDir, AGENTS_DIR), AGENTS_DIR, fs);
|
|
118
|
+
const plan = planPlacement(templates, projectDir, deps);
|
|
119
|
+
return { projectDir, stamp, stampOk, plan };
|
|
87
120
|
};
|
|
88
121
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
export const readBundledAgents = (deps = {}) => {
|
|
92
|
-
const fs = fsDeps(deps);
|
|
93
|
-
const bundleDir = deps.bundleDir ?? BUNDLED_AGENTS_DIR;
|
|
94
|
-
let names;
|
|
122
|
+
const loadConfigOrStop = (cwd, deps) => {
|
|
95
123
|
try {
|
|
96
|
-
|
|
124
|
+
return loadConfig(cwd, deps.readFile, deps.lstat).config;
|
|
97
125
|
} catch (err) {
|
|
98
|
-
throw makeCheapAgentsError(
|
|
99
|
-
}
|
|
100
|
-
const templates = names
|
|
101
|
-
.filter((name) => name.endsWith('.md'))
|
|
102
|
-
.sort()
|
|
103
|
-
.map((name) => ({ name, content: fs.readFile(join(bundleDir, name), UTF8) }));
|
|
104
|
-
if (templates.length === 0) {
|
|
105
|
-
throw makeCheapAgentsError(CHEAP_AGENTS_BUNDLE, `no bundled agent templates found in ${bundleDir} — the kit install is incomplete`);
|
|
126
|
+
throw makeCheapAgentsError(CHEAP_AGENTS_CONFIG, `${err?.message ?? err} — the agents writer cannot read the configured review lenses — nothing is placed`);
|
|
106
127
|
}
|
|
107
|
-
return templates;
|
|
108
128
|
};
|
|
109
129
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
130
|
+
const configuredDerivedTemplates = (cwd, deps) => {
|
|
131
|
+
const config = loadConfigOrStop(cwd, deps);
|
|
132
|
+
const bundle = readBundledAgents(deps);
|
|
133
|
+
const templates = new Map();
|
|
134
|
+
for (const member of lensMembersOf(config)) {
|
|
135
|
+
const spec = lensVehicleSpec(member);
|
|
136
|
+
if (!spec.derived) continue;
|
|
137
|
+
const base = bundle.find((template) => template.name === `${spec.template}.md`);
|
|
138
|
+
if (!base) throw makeCheapAgentsError(CHEAP_AGENTS_BUNDLE, `${spec.template}.md is missing from the bundle`);
|
|
139
|
+
templates.set(`${spec.stem}.md`, { name: `${spec.stem}.md`, content: deriveLensTemplate(base.content, spec) });
|
|
119
140
|
}
|
|
141
|
+
return [...templates.values()];
|
|
120
142
|
};
|
|
121
143
|
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
if (stat === null) return { absent: true };
|
|
125
|
-
if (stat.isSymbolicLink()) throw makeCheapAgentsError(CHEAP_AGENTS_SYMLINK, `${relPath} is a symlink — refusing to write through it`);
|
|
126
|
-
if (!stat.isDirectory()) throw makeCheapAgentsError(CHEAP_AGENTS_SYMLINK, `${relPath} exists but is not a directory — refusing to write through it`);
|
|
127
|
-
return { absent: false };
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
// Per-template placement plan: place | already-current | customized-preserved (never clobbered).
|
|
131
|
-
export const planPlacement = (templates, projectDir, deps = {}) => {
|
|
132
|
-
const fs = fsDeps(deps);
|
|
133
|
-
return templates.map((template) => {
|
|
134
|
-
const rel = `${AGENTS_DIR}/${template.name}`;
|
|
135
|
-
const abs = join(projectDir, AGENTS_DIR, template.name);
|
|
136
|
-
const stat = lstatNoFollow(abs, fs);
|
|
137
|
-
if (stat === null) return { ...template, rel, abs, action: 'place' };
|
|
138
|
-
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
139
|
-
throw makeCheapAgentsError(CHEAP_AGENTS_SYMLINK, `${rel} exists but is not a regular file — refusing to touch it`);
|
|
140
|
-
}
|
|
141
|
-
const existing = fs.readFile(abs, UTF8);
|
|
142
|
-
if (existing === template.content) return { ...template, rel, abs, action: 'already-current' };
|
|
143
|
-
return { ...template, rel, abs, action: 'customized-preserved' };
|
|
144
|
-
});
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
export const preflightCheapAgents = ({ cwd }, deps = {}) => {
|
|
148
|
-
const fs = fsDeps(deps);
|
|
149
|
-
const projectDir = cwd ?? process.cwd();
|
|
150
|
-
const templates = readBundledAgents(deps);
|
|
151
|
-
const stamp = readStamp(join(projectDir, WORKFLOW_STAMP), fs);
|
|
152
|
-
const stampOk = stamp === EXPECTED_WORKFLOW_VERSION;
|
|
153
|
-
assertDirSafe(join(projectDir, CLAUDE_DIR), CLAUDE_DIR, fs);
|
|
154
|
-
assertDirSafe(join(projectDir, AGENTS_DIR), AGENTS_DIR, fs);
|
|
155
|
-
const plan = planPlacement(templates, projectDir, deps);
|
|
156
|
-
return { projectDir, stamp, stampOk, plan };
|
|
157
|
-
};
|
|
144
|
+
export const applyCheapAgentsCommand = (root) =>
|
|
145
|
+
`node ${shellQuoteArg(fileURLToPath(import.meta.url))} --apply --cwd ${shellQuoteArg(root)}`;
|
|
158
146
|
|
|
159
147
|
// ── the writer ────────────────────────────────────────────────────────────────────────
|
|
160
148
|
|
|
161
149
|
export const writeCheapAgents = ({ cwd, dryRun = true } = {}, deps = {}) => {
|
|
162
|
-
const fs =
|
|
163
|
-
const
|
|
150
|
+
const fs = writeFsDeps(deps);
|
|
151
|
+
const projectDir = cwd ?? process.cwd();
|
|
152
|
+
const derived = configuredDerivedTemplates(projectDir, deps);
|
|
153
|
+
const preflight = preflightCheapAgents({ cwd: projectDir, derived }, deps);
|
|
164
154
|
if (dryRun) return { wrote: false, dryRun: true, ...preflight };
|
|
165
155
|
|
|
166
156
|
if (!preflight.stampOk) {
|
|
@@ -186,8 +176,8 @@ const ACTION_LABEL = {
|
|
|
186
176
|
export const formatResult = (result) => {
|
|
187
177
|
const lines = [
|
|
188
178
|
result.dryRun
|
|
189
|
-
? 'agent-workflow
|
|
190
|
-
: 'agent-workflow
|
|
179
|
+
? 'agent-workflow subagent vehicles — DRY RUN (no changes)'
|
|
180
|
+
: 'agent-workflow subagent vehicles — APPLY',
|
|
191
181
|
];
|
|
192
182
|
for (const item of result.plan) {
|
|
193
183
|
const verb = result.dryRun && item.action === 'place' ? 'would place' : ACTION_LABEL[item.action];
|
|
@@ -196,15 +186,17 @@ export const formatResult = (result) => {
|
|
|
196
186
|
if (!result.stampOk) {
|
|
197
187
|
lines.push(`note: no current deployment stamp found (${result.stamp ?? 'none'}) — --apply will refuse until init/upgrade runs.`);
|
|
198
188
|
}
|
|
189
|
+
const readOnlyCount = result.plan.filter((item) => item.name !== EXECUTOR_VEHICLE).length;
|
|
199
190
|
lines.push(
|
|
200
|
-
|
|
201
|
-
`three ride the cheap lane (model: haiku, effort: low) for mechanical work; ${FALLBACK_LENS_ADDITIONAL_ONLY}
|
|
191
|
+
`${readOnlyCount} vehicles are Claude Code subagents with READ-ONLY tools and NO shell as bundled or derived (a customized file keeps whatever it grants) — so a fan-out on the shipped templates can never turn into a wave of approval prompts.`,
|
|
192
|
+
`three of those ride the cheap lane (model: haiku, effort: low) for mechanical work; ${FALLBACK_LENS_ADDITIONAL_ONLY}`,
|
|
193
|
+
'executor is the one FULL-TOOL vehicle: dispatched only for a bounded execution, authoring, or write-capable routine slice the orchestrator verifies, never for read-only work, and it never commits.',
|
|
202
194
|
);
|
|
203
195
|
// A preview must print the EXACT command that applies it. The advisor renders this dry-run as an
|
|
204
196
|
// item's one-liner, and that flow's contract is "run the printed command, no improvisation" — a
|
|
205
197
|
// bare "re-run with --apply" would leave the caller to reconstruct --cwd and its quoting.
|
|
206
198
|
if (result.dryRun && result.plan.some((item) => item.action === 'place')) {
|
|
207
|
-
lines.push(`to apply, run exactly:
|
|
199
|
+
lines.push(`to apply, run exactly: ${applyCheapAgentsCommand(result.projectDir)}`);
|
|
208
200
|
}
|
|
209
201
|
if (!result.dryRun && result.wrote) {
|
|
210
202
|
lines.push('hidden-mode note: if this deployment is hidden, run the hide-footprint reconcile so the placed files stay out of `git status`.');
|
package/tools/commands.mjs
CHANGED
|
@@ -131,7 +131,7 @@ const CATALOG = [
|
|
|
131
131
|
invocation: invocationOf('agents'),
|
|
132
132
|
group: 'Configure',
|
|
133
133
|
kind: WRITER,
|
|
134
|
-
oneLine: 'Place bundled
|
|
134
|
+
oneLine: 'Place the bundled subagent vehicles: four read-only ones, none granted a shell — three cheap-model ones for mechanical work (sweeps, changelog skeletons, gate triage) and a review lens — plus the one full-tool executor that carries bounded slices you verify (Claude Code; opt-in; preview first).',
|
|
135
135
|
},
|
|
136
136
|
{
|
|
137
137
|
key: 'hook',
|
|
@@ -180,7 +180,7 @@ const CATALOG = [
|
|
|
180
180
|
invocation: invocationOf('recipes'),
|
|
181
181
|
group: 'Orchestrate',
|
|
182
182
|
kind: READ_ONLY,
|
|
183
|
-
oneLine: 'See the orchestration recipes (Solo / Reviewed / Council / Delegated), which one fits this environment, and the configured per-activity line to paste at session start.',
|
|
183
|
+
oneLine: 'See the orchestration recipes (Solo / Reviewed / Council / Delegated / Subagent), which one fits this environment, and the configured per-activity line to paste at session start.',
|
|
184
184
|
},
|
|
185
185
|
{
|
|
186
186
|
key: 'procedures',
|
|
@@ -341,7 +341,7 @@ const TUNE_TAIL = Object.freeze([
|
|
|
341
341
|
'',
|
|
342
342
|
'Tune — opt-in accelerators (consent-first: every writer previews before writing; nothing runs without your yes)',
|
|
343
343
|
` ${BARE_INVOCATION} velocity routine read-only commands stop prompting (incl. the --kit-tools tier for the kit's own read-only tools)`,
|
|
344
|
-
` ${BARE_INVOCATION} agents
|
|
344
|
+
` ${BARE_INVOCATION} agents subagent vehicles: four read-only (cheap ones take the mechanical work, a review lens gives another opinion) + the one full-tool executor that carries bounded slices you verify`,
|
|
345
345
|
` ${BARE_INVOCATION} gates run your declared gates (docs/ai/gates.json) as one batch; its guide also offers the consent-gated seeding preview — writes only on your yes`,
|
|
346
346
|
` ${BARE_INVOCATION} hook auto-approve exactly your declared gate commands (byte-exact matches only)`,
|
|
347
347
|
` ${BARE_INVOCATION} set-recipe put a ready review backend to work on plans and diffs`,
|
|
@@ -97,7 +97,7 @@ const RAW_BACKENDS = [
|
|
|
97
97
|
],
|
|
98
98
|
grounding: 'automatic — the wrapper precomputes the full working-tree change set (repo map, status, diffs, untracked contents) and codex auto-merges the root AGENTS.md; no grounding flags',
|
|
99
99
|
continue: [],
|
|
100
|
-
receipt: 'side effect — a successful review appends one JSON receipt line to <git dir>/agent-workflow-review-receipts.jsonl (AW_REVIEW_RECEIPTS overrides): fingerprint = sha256 over the canonical uncommitted-state payload (staged diff + unstaged diff + untracked-not-ignored contents — the review-payload domain; never-committable untracked paths — character/block devices, FIFOs, sockets — are excluded from the domain entirely, untracked symlinks/directories ride as name-only notes) in code mode, the artifact-file sha256 in plan mode; verdict parsed from the mandated literal verdict line (schema mode: the verdict field); always fresh:true (one-shot) + grounded:true (native AGENTS.md auto-merge, factsHash null); probe = whether the run relaxed the quality guards (CODEX_PROBE=1), written on EVERY receipt so it self-declares — the kit\'s review-state gate rejects a probe-marked receipt (a probe review never attests) and equally rejects an unmarked one (silence is not a declaration); posture = the ACTUAL run posture {model, effort, tier} (tier null on the standard tier), written on EVERY receipt (D5) — the gate rejects a receipt with an absent/invalid posture (a pre-D5 wrapper minted it; re-run the review), one stderr banner line states the same posture, and a posture value carrying control bytes refuses pre-spend in every mode; a run whose final message carries NO recognized \'Verdict: <ship|revise|rethink>\' line — empty or missing output included — exits 4 with NO receipt (D4: a FAILED review to RE-RUN, never a fatal session error); when the dispatch nonce seam is supplied — the AW_REVIEW_NONCE environment value or its plain-argument equivalent --nonce <n> (one seam: the flag assigns the same value; supplying both with different values refuses pre-spend) — under the safe grammar [A-Za-z0-9._-]{1,64} (anything else refuses pre-spend), the wrapper first mints the finding MANIFEST {schema, backend, nonce, fingerprint, findings} beside the receipts file (agent-workflow-finding-manifest-<backend>-<nonce>.json; atomic, no-clobber — a byte-identical rewrite is an idempotent no-op, different bytes refuse loudly) ORDERED before the receipt append — a failed manifest write EXCLUDES the receipt append, so a nonce-supplied dispatch can never land a receipt without its readable manifest; a nonce-less invocation adds NO nonce field and mints NO finding manifest (the existing wrapperVersion field still changes with each bridge release); a write failure warns, never fails the review',
|
|
100
|
+
receipt: 'side effect — a successful review appends one JSON receipt line to <git dir>/agent-workflow-review-receipts.jsonl (AW_REVIEW_RECEIPTS overrides): fingerprint = sha256 over the canonical uncommitted-state payload (staged diff + unstaged diff + untracked-not-ignored contents — the review-payload domain; never-committable untracked paths — character/block devices, FIFOs, sockets — are excluded from the domain entirely, untracked symlinks/directories ride as name-only notes) in code mode, the artifact-file sha256 in plan mode; durationS = integer wall-clock seconds from CLI start through verdict parsing and the wrapper prints review duration: <n>s; blocking = the count of [blocker] and [major] lines (schema mode: findings with either severity; a payload whose findings cannot be counted fails the run, no receipt); artifactPath = the normalized realpath on plan receipts only (repo-relative inside the work tree, absolute otherwise), while a double quote, backslash or control byte refuses pre-spend because the receipt encoder cannot carry it; verdict parsed from the mandated literal verdict line (schema mode: the verdict field); always fresh:true (one-shot) + grounded:true (native AGENTS.md auto-merge, factsHash null); probe = whether the run relaxed the quality guards (CODEX_PROBE=1), written on EVERY receipt so it self-declares — the kit\'s review-state gate rejects a probe-marked receipt (a probe review never attests) and equally rejects an unmarked one (silence is not a declaration); posture = the ACTUAL run posture {model, effort, tier} (tier null on the standard tier), written on EVERY receipt (D5) — the gate rejects a receipt with an absent/invalid posture (a pre-D5 wrapper minted it; re-run the review), one stderr banner line states the same posture, and a posture value carrying control bytes refuses pre-spend in every mode; a run whose final message carries NO recognized \'Verdict: <ship|revise|rethink>\' line — empty or missing output included — exits 4 with NO receipt (D4: a FAILED review to RE-RUN, never a fatal session error); when the dispatch nonce seam is supplied — the AW_REVIEW_NONCE environment value or its plain-argument equivalent --nonce <n> (one seam: the flag assigns the same value; supplying both with different values refuses pre-spend) — under the safe grammar [A-Za-z0-9._-]{1,64} (anything else refuses pre-spend), the wrapper first mints the finding MANIFEST {schema, backend, nonce, fingerprint, findings} beside the receipts file (agent-workflow-finding-manifest-<backend>-<nonce>.json; atomic, no-clobber — a byte-identical rewrite is an idempotent no-op, different bytes refuse loudly) ORDERED before the receipt append — a failed manifest write EXCLUDES the receipt append, so a nonce-supplied dispatch can never land a receipt without its readable manifest; a nonce-less invocation adds NO nonce field and mints NO finding manifest (the existing wrapperVersion field still changes with each bridge release); a write failure warns, never fails the review',
|
|
101
101
|
notes: [
|
|
102
102
|
'the review posture banner appends a banner-only timeout=<duration> field — exactly the duration handed to timeout(1); the hard-timeout preflight fails CLOSED when no timeout/gtimeout binary exists (the wrapper refuses by name before any CLI run, so an uncapped review run can no longer happen), and the field never enters the receipt posture or the D5 banner↔receipt parity',
|
|
103
103
|
'quote the posture banner verbatim when labeling this dispatch — the banner is the machine-stated posture; a prose re-type drifts',
|
|
@@ -139,7 +139,7 @@ const RAW_BACKENDS = [
|
|
|
139
139
|
'agy-review --continue [--decided @f] [--focus "…"] [--nonce <n>]',
|
|
140
140
|
'agy-review --conversation <id> [--decided @f] [--focus "…"] [--nonce <n>]',
|
|
141
141
|
],
|
|
142
|
-
receipt: "side effect — a successful review appends one JSON receipt line to <git dir>/agent-workflow-review-receipts.jsonl (AW_REVIEW_RECEIPTS overrides; plan/diff outside a git tree: warn + skip unless overridden): fingerprint = sha256 over the canonical uncommitted-state payload (staged diff + unstaged diff + untracked-not-ignored contents — the review-payload domain; never-committable untracked paths — character/block devices, FIFOs, sockets — are excluded from the domain entirely, untracked symlinks/directories ride as name-only notes) in code mode, the artifact-file sha256 in plan/diff mode; verdict recorded verbatim from the mandated '### Verdict' section (SHIP / SHIP WITH NITS / REWORK); grounded = whether a NON-EMPTY --facts payload was supplied (code mode refuses pre-spend without one — no run, no receipt — unless --ungrounded/AGY_PROBE=1; in plan/diff an empty payload records grounded:false — fail-closed, the state gate rejects it), factsHash = sha256 of the facts payload; a continuation receipt is fresh:false (informational-only — it cannot attest the folded tree); probe = whether the run relaxed the quality guards (AGY_PROBE=1), written on EVERY receipt so it self-declares — the kit's review-state gate rejects a probe-marked receipt (a probe review never attests) and equally rejects an unmarked one (silence is not a declaration); posture = the ACTUAL run posture {model} (agy has no tier), written on EVERY receipt (D5) — the gate rejects a receipt with an absent/invalid posture (a pre-D5 wrapper minted it; re-run the review), one stderr banner line states the same posture, an ATTESTING review with AGY_MODEL explicitly emptied refuses pre-spend, and a model string carrying control bytes refuses pre-spend in every mode; delivery = how the change set REACHED the model, currently emitted as 'inline' (the whole set rode one prompt — proven by construction) or 'fed' (a chunked feed whose per-part echo proof verified); REQUIRED on every agy code receipt and its ABSENCE is what stops a pre-fed-lane receipt attesting, while the gate accepts any well-formed declaration rather than a particular value; absent by construction on plan/diff/continuation receipts, which carry no change set; a run whose output carries NO recognized '### Verdict' section — empty output included — exits 4 with NO receipt (D4: a FAILED review to RE-RUN, never a fatal session error); when the dispatch nonce seam is supplied — the AW_REVIEW_NONCE environment value or its plain-argument equivalent --nonce <n> (one seam: the flag assigns the same value; supplying both with different values refuses pre-spend) — under the safe grammar [A-Za-z0-9._-]{1,64} (anything else refuses pre-spend), the wrapper first mints the finding MANIFEST {schema, backend, nonce, fingerprint, findings} beside the receipts file (agent-workflow-finding-manifest-<backend>-<nonce>.json; atomic, no-clobber — a byte-identical rewrite is an idempotent no-op, different bytes refuse loudly) ORDERED before the receipt append — a failed manifest write EXCLUDES the receipt append, so a nonce-supplied dispatch can never land a receipt without its readable manifest; a nonce-less invocation adds NO nonce field and mints NO finding manifest (the existing wrapperVersion field still changes with each bridge release); a write failure warns, never fails the review",
|
|
142
|
+
receipt: "side effect — a successful review appends one JSON receipt line to <git dir>/agent-workflow-review-receipts.jsonl (AW_REVIEW_RECEIPTS overrides; plan/diff outside a git tree: warn + skip unless overridden): fingerprint = sha256 over the canonical uncommitted-state payload (staged diff + unstaged diff + untracked-not-ignored contents — the review-payload domain; never-committable untracked paths — character/block devices, FIFOs, sockets — are excluded from the domain entirely, untracked symlinks/directories ride as name-only notes) in code mode, the artifact-file sha256 in plan/diff mode; durationS = integer wall-clock seconds from CLI start through verdict parsing and the wrapper prints review duration: <n>s; blocking = the count of numbered items in the first ### Blocking section; artifactPath = the normalized realpath on plan/diff receipts only (repo-relative inside the work tree, absolute otherwise), while a double quote, backslash or control byte refuses pre-spend because the receipt encoder cannot carry it; verdict recorded verbatim from the mandated '### Verdict' section (SHIP / SHIP WITH NITS / REWORK); grounded = whether a NON-EMPTY --facts payload was supplied (code mode refuses pre-spend without one — no run, no receipt — unless --ungrounded/AGY_PROBE=1; in plan/diff an empty payload records grounded:false — fail-closed, the state gate rejects it), factsHash = sha256 of the facts payload; a continuation receipt is fresh:false (informational-only — it cannot attest the folded tree); probe = whether the run relaxed the quality guards (AGY_PROBE=1), written on EVERY receipt so it self-declares — the kit's review-state gate rejects a probe-marked receipt (a probe review never attests) and equally rejects an unmarked one (silence is not a declaration); posture = the ACTUAL run posture {model} (agy has no tier), written on EVERY receipt (D5) — the gate rejects a receipt with an absent/invalid posture (a pre-D5 wrapper minted it; re-run the review), one stderr banner line states the same posture, an ATTESTING review with AGY_MODEL explicitly emptied refuses pre-spend, and a model string carrying control bytes refuses pre-spend in every mode; delivery = how the change set REACHED the model, currently emitted as 'inline' (the whole set rode one prompt — proven by construction) or 'fed' (a chunked feed whose per-part echo proof verified); REQUIRED on every agy code receipt and its ABSENCE is what stops a pre-fed-lane receipt attesting, while the gate accepts any well-formed declaration rather than a particular value; absent by construction on plan/diff/continuation receipts, which carry no change set; a run whose output carries NO recognized '### Verdict' section — empty output included — exits 4 with NO receipt (D4: a FAILED review to RE-RUN, never a fatal session error); when the dispatch nonce seam is supplied — the AW_REVIEW_NONCE environment value or its plain-argument equivalent --nonce <n> (one seam: the flag assigns the same value; supplying both with different values refuses pre-spend) — under the safe grammar [A-Za-z0-9._-]{1,64} (anything else refuses pre-spend), the wrapper first mints the finding MANIFEST {schema, backend, nonce, fingerprint, findings} beside the receipts file (agent-workflow-finding-manifest-<backend>-<nonce>.json; atomic, no-clobber — a byte-identical rewrite is an idempotent no-op, different bytes refuse loudly) ORDERED before the receipt append — a failed manifest write EXCLUDES the receipt append, so a nonce-supplied dispatch can never land a receipt without its readable manifest; a nonce-less invocation adds NO nonce field and mints NO finding manifest (the existing wrapperVersion field still changes with each bridge release); a write failure warns, never fails the review",
|
|
143
143
|
notes: [
|
|
144
144
|
'transport: every review dispatch drives the CLI in --output-format json (plus --disable-slash-commands) and the returned envelope is parsed in node (bin/agy-envelope.mjs) — the operator-facing invocations and flags above do NOT change, and on a ZERO exit the wrapper still PRINTS the review text, never JSON. A missing or unreadable envelope on a zero exit is a loud failure with NO receipt, never a downgraded verdict and never a fallback to raw-stdout parsing; a non-zero CLI exit keeps its own code and message, and publishes the captured stdout unchanged from the SINGLE dispatch or the FINAL fed turn (which may therefore be a JSON or partial payload — the envelope is parsed only on a zero exit); an INTERMEDIATE feed turn is the exception, its output stays private (Invariant E) and its failure prints only a named error. Enforced by a PRE-SPEND capability probe, not a version floor: agy --help must advertise --output-format and --disable-slash-commands, node must be >= 22, and bin/agy-envelope.mjs must be present — otherwise the review refuses before any run is spent and names the missing capability',
|
|
145
145
|
'pre-dispatch host-diff: before the FIRST dispatch of this bridge, diff its declared networkHosts against the live sandbox allow-list — a missing host is surfaced to the maintainer BEFORE dispatching, never fired into a known prompt',
|
package/tools/direct-run.mjs
CHANGED
|
@@ -56,6 +56,15 @@ export const LIBRARY_ONLY_MODULES = Object.freeze({
|
|
|
56
56
|
// Named by references/modes/mcp.md as the read half the advisor and uninstall ask. It only ever
|
|
57
57
|
// REPORTS; the command that acts on what it reports is the mode itself.
|
|
58
58
|
'mcp-registration.mjs': '/agent-workflow-kit mcp',
|
|
59
|
+
// Named by references/modes/set-recipe.md as the activity/slot registry; the command that shows
|
|
60
|
+
// the recipes it defines, resolved for this environment, is the recipes advisor.
|
|
61
|
+
'carriers.mjs': '/agent-workflow-kit recipes',
|
|
62
|
+
// The READ core of the subagent-vehicle surface: it sits one name away from the writer
|
|
63
|
+
// references/modes/agents.md DOES name, and reaching for it is reaching for the agents mode.
|
|
64
|
+
'cheap-agents-read.mjs': '/agent-workflow-kit agents',
|
|
65
|
+
'review-roster.mjs': '/agent-workflow-kit recipes',
|
|
66
|
+
'review-roster-resolve.mjs': '/agent-workflow-kit recipes',
|
|
67
|
+
'set-recipe-roster.mjs': '/agent-workflow-kit set-recipe',
|
|
59
68
|
});
|
|
60
69
|
|
|
61
70
|
// The frozen refusal line. One line, names the module, names the command.
|