@jaimevalasek/aioson 1.23.0 → 1.23.1
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/docs/en/5-reference/cli-reference.md +85 -0
- package/docs/pt/4-agentes/pm.md +31 -4
- package/docs/pt/5-referencia/README.md +3 -0
- package/docs/pt/5-referencia/autopilot-handoff.md +131 -0
- package/docs/pt/5-referencia/comandos-cli.md +72 -6
- package/docs/pt/5-referencia/harness-retro.md +133 -0
- package/docs/pt/5-referencia/loop-guardrails.md +225 -0
- package/docs/pt/5-referencia/sdd-automation-scripts.md +25 -13
- package/package.json +1 -1
- package/src/cli.js +54 -29
- package/src/commands/agent-epilogue.js +186 -0
- package/src/commands/context-select.js +33 -0
- package/src/commands/preflight-context.js +13 -9
- package/src/commands/review-cycle.js +328 -0
- package/src/commands/runtime.js +4 -4
- package/src/commands/state-save.js +2 -0
- package/src/commands/workflow-execute.js +138 -28
- package/src/commands/workflow-next.js +3 -2
- package/src/commands/workflow-status.js +30 -10
- package/src/constants.js +15 -13
- package/src/context-memory.js +50 -25
- package/src/context-selector.js +394 -0
- package/src/i18n/messages/en.js +13 -7
- package/src/i18n/messages/es.js +13 -7
- package/src/i18n/messages/fr.js +13 -7
- package/src/i18n/messages/pt-BR.js +13 -7
- package/src/parser.js +1 -1
- package/src/squad/preflight-context.js +26 -27
- package/template/.aioson/agents/analyst.md +41 -46
- package/template/.aioson/agents/architect.md +33 -46
- package/template/.aioson/agents/briefing.md +76 -67
- package/template/.aioson/agents/dev.md +66 -59
- package/template/.aioson/agents/deyvin.md +55 -50
- package/template/.aioson/agents/discovery-design-doc.md +35 -22
- package/template/.aioson/agents/manifests/architect.manifest.json +11 -1
- package/template/.aioson/agents/manifests/dev.manifest.json +15 -0
- package/template/.aioson/agents/manifests/pm.manifest.json +20 -0
- package/template/.aioson/agents/orchestrator.md +31 -18
- package/template/.aioson/agents/pentester.md +7 -7
- package/template/.aioson/agents/pm.md +41 -35
- package/template/.aioson/agents/product.md +116 -165
- package/template/.aioson/agents/qa.md +21 -14
- package/template/.aioson/agents/scope-check.md +46 -24
- package/template/.aioson/agents/tester.md +12 -6
- package/template/.aioson/agents/ux-ui.md +36 -31
- package/template/.aioson/agents/validator.md +3 -3
- package/template/.aioson/config/autonomy-protocol.json +7 -0
- package/template/.aioson/design-docs/code-reuse.md +10 -5
- package/template/.aioson/design-docs/componentization.md +10 -5
- package/template/.aioson/design-docs/file-size.md +10 -5
- package/template/.aioson/design-docs/folder-structure.md +10 -5
- package/template/.aioson/design-docs/naming.md +10 -5
- package/template/.aioson/docs/autonomy-protocol.md +2 -2
- package/template/.aioson/docs/autopilot-handoff.md +32 -21
- package/template/.aioson/docs/briefing/briefing-craft.md +9 -3
- package/template/.aioson/docs/deyvin/continuity-recovery.md +18 -22
- package/template/.aioson/docs/product/conversation-playbook.md +8 -3
- package/template/.aioson/docs/product/prd-contract.md +8 -3
- package/template/.aioson/docs/product/quality-lens.md +8 -3
- package/template/.aioson/docs/product/research-loop.md +8 -3
- package/template/.aioson/docs/ux-ui/accessibility-audit.md +7 -2
- package/template/.aioson/docs/ux-ui/audit-mode.md +7 -2
- package/template/.aioson/docs/ux-ui/component-map.md +7 -2
- package/template/.aioson/docs/ux-ui/design-execution.md +7 -2
- package/template/.aioson/docs/ux-ui/design-gate.md +7 -2
- package/template/.aioson/docs/ux-ui/research-mode.md +7 -2
- package/template/.aioson/docs/ux-ui/site-delivery.md +7 -2
- package/template/.aioson/docs/ux-ui/token-contract.md +7 -2
- package/template/.aioson/rules/aioson-context-boundary.md +1 -1
- package/template/.aioson/rules/disk-first-artifacts.md +1 -1
- package/template/.aioson/skills/process/aioson-spec-driven/references/approval-gates.md +1 -1
- package/template/.aioson/skills/process/aioson-spec-driven/references/architect.md +3 -2
- package/template/.aioson/skills/process/aioson-spec-driven/references/artifact-map.md +21 -9
- package/template/.aioson/skills/process/aioson-spec-driven/references/dev.md +2 -1
- package/template/.aioson/skills/process/aioson-spec-driven/references/pm.md +2 -1
- package/template/.aioson/skills/static/web-research-cache.md +29 -8
|
@@ -4,10 +4,11 @@
|
|
|
4
4
|
* aioson preflight:context — Estimate context budget before a session
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
|
-
* aioson preflight:context . --agent=dev
|
|
8
|
-
* aioson preflight:context . --agent=orchestrator --squad=content-team
|
|
9
|
-
* aioson preflight:context . --agent=dev --
|
|
10
|
-
* aioson preflight:context . --agent=dev --
|
|
7
|
+
* aioson preflight:context . --agent=dev
|
|
8
|
+
* aioson preflight:context . --agent=orchestrator --squad=content-team
|
|
9
|
+
* aioson preflight:context . --agent=dev --mode=executing --task="create command" --paths=src/commands/foo.js
|
|
10
|
+
* aioson preflight:context . --agent=dev --verbose
|
|
11
|
+
* aioson preflight:context . --agent=dev --json
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
14
|
const path = require('node:path');
|
|
@@ -15,11 +16,14 @@ const { estimateContext, formatReport } = require('../squad/preflight-context');
|
|
|
15
16
|
|
|
16
17
|
async function runPreflightContext({ args, options = {}, logger }) {
|
|
17
18
|
const targetDir = path.resolve(process.cwd(), args[0] || '.');
|
|
18
|
-
const agent = String(options.agent || options.a || 'dev').trim();
|
|
19
|
-
const squad = options.squad ? String(options.squad).trim() : undefined;
|
|
20
|
-
const verbose = Boolean(options.verbose || options.v);
|
|
21
|
-
|
|
22
|
-
const
|
|
19
|
+
const agent = String(options.agent || options.a || 'dev').trim();
|
|
20
|
+
const squad = options.squad ? String(options.squad).trim() : undefined;
|
|
21
|
+
const verbose = Boolean(options.verbose || options.v);
|
|
22
|
+
const mode = String(options.mode || 'planning').trim();
|
|
23
|
+
const task = String(options.task || options.goal || '').trim();
|
|
24
|
+
const paths = String(options.paths || options.path || '').trim();
|
|
25
|
+
|
|
26
|
+
const result = await estimateContext(targetDir, { agent, squad, verbose, mode, task, paths });
|
|
23
27
|
|
|
24
28
|
if (options.json) return result;
|
|
25
29
|
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs/promises');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { ensureDir, exists } = require('../utils');
|
|
6
|
+
|
|
7
|
+
const DEFAULT_MAX_CYCLES = 3;
|
|
8
|
+
const EXECUTION_STATE_RELATIVE_PATH = '.aioson/context/workflow-execute.json';
|
|
9
|
+
|
|
10
|
+
function resolveTargetDir(args) {
|
|
11
|
+
return path.resolve(process.cwd(), args[0] || '.');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function normalizeAgent(value, fallback) {
|
|
15
|
+
const normalized = String(value || fallback || '')
|
|
16
|
+
.trim()
|
|
17
|
+
.toLowerCase()
|
|
18
|
+
.replace(/^@/, '')
|
|
19
|
+
.replace(/[^a-z0-9-]+/g, '-')
|
|
20
|
+
.replace(/^-+|-+$/g, '');
|
|
21
|
+
return normalized || fallback;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function stateFileName(source, target) {
|
|
25
|
+
if (source === 'qa' && target === 'dev') return 'qa-dev-cycle.json';
|
|
26
|
+
return `review-cycle-${source}-${target}.json`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function resolveStatePath(targetDir, source, target) {
|
|
30
|
+
return path.join(targetDir, '.aioson', 'runtime', stateFileName(source, target));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function readJsonIfExists(filePath) {
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(await fs.readFile(filePath, 'utf8'));
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function writeJson(filePath, payload) {
|
|
42
|
+
await ensureDir(path.dirname(filePath));
|
|
43
|
+
await fs.writeFile(filePath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function parseMax(value, fallback = DEFAULT_MAX_CYCLES) {
|
|
47
|
+
const parsed = Number.parseInt(String(value || ''), 10);
|
|
48
|
+
if (!Number.isInteger(parsed) || parsed <= 0) return fallback;
|
|
49
|
+
return Math.max(1, Math.min(parsed, 10));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function readAgenticPolicy(targetDir) {
|
|
53
|
+
const payload = await readJsonIfExists(path.join(targetDir, EXECUTION_STATE_RELATIVE_PATH));
|
|
54
|
+
return payload && payload.agentic_policy && payload.agentic_policy.enabled
|
|
55
|
+
? payload.agentic_policy
|
|
56
|
+
: null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function resolveMaxCycles(targetDir, source, options = {}) {
|
|
60
|
+
const explicit = options['max-cycles'] || options.maxCycles;
|
|
61
|
+
if (explicit) return parseMax(explicit);
|
|
62
|
+
|
|
63
|
+
const policy = await readAgenticPolicy(targetDir);
|
|
64
|
+
const review = policy && policy.review_cycle ? policy.review_cycle : null;
|
|
65
|
+
if (!review) return DEFAULT_MAX_CYCLES;
|
|
66
|
+
|
|
67
|
+
if (source === 'qa') return parseMax(review.max_dev_qa_cycles, DEFAULT_MAX_CYCLES);
|
|
68
|
+
if (source === 'tester') return parseMax(review.max_tester_correction_cycles, DEFAULT_MAX_CYCLES);
|
|
69
|
+
if (source === 'pentester') return parseMax(review.max_pentester_correction_cycles, DEFAULT_MAX_CYCLES);
|
|
70
|
+
return DEFAULT_MAX_CYCLES;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function resolveSafeProjectPath(targetDir, filePath) {
|
|
74
|
+
if (!filePath) return null;
|
|
75
|
+
const absolute = path.isAbsolute(filePath) ? filePath : path.resolve(targetDir, filePath);
|
|
76
|
+
const relative = path.relative(targetDir, absolute);
|
|
77
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) return null;
|
|
78
|
+
return absolute;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function updatePlanStatus(targetDir, planPath, status) {
|
|
82
|
+
const absolute = resolveSafeProjectPath(targetDir, planPath);
|
|
83
|
+
if (!absolute || !(await exists(absolute))) {
|
|
84
|
+
return { ok: false, reason: 'plan_not_found' };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const relativePath = path.relative(targetDir, absolute).replace(/\\/g, '/');
|
|
88
|
+
const extension = path.extname(absolute).toLowerCase();
|
|
89
|
+
if (extension && extension !== '.md' && extension !== '.markdown') {
|
|
90
|
+
return {
|
|
91
|
+
ok: true,
|
|
92
|
+
skipped: true,
|
|
93
|
+
reason: 'non_markdown_plan',
|
|
94
|
+
path: relativePath,
|
|
95
|
+
status
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const raw = await fs.readFile(absolute, 'utf8');
|
|
100
|
+
let next;
|
|
101
|
+
if (/^---\r?\n/.test(raw)) {
|
|
102
|
+
const endMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
103
|
+
if (endMatch) {
|
|
104
|
+
const frontmatter = endMatch[1];
|
|
105
|
+
const rest = raw.slice(endMatch[0].length);
|
|
106
|
+
const lines = frontmatter.split(/\r?\n/);
|
|
107
|
+
let found = false;
|
|
108
|
+
const updated = lines.map((line) => {
|
|
109
|
+
if (/^status\s*:/i.test(line)) {
|
|
110
|
+
found = true;
|
|
111
|
+
return `status: ${status}`;
|
|
112
|
+
}
|
|
113
|
+
return line;
|
|
114
|
+
});
|
|
115
|
+
if (!found) updated.push(`status: ${status}`);
|
|
116
|
+
next = `---\n${updated.join('\n')}\n---${rest}`;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (!next) {
|
|
121
|
+
next = `---\nstatus: ${status}\n---\n\n${raw}`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
await fs.writeFile(absolute, next, 'utf8');
|
|
125
|
+
return {
|
|
126
|
+
ok: true,
|
|
127
|
+
path: relativePath,
|
|
128
|
+
status
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function buildNextTask({ source, planPath }) {
|
|
133
|
+
if (source === 'qa') return `apply mandatory corrections from ${planPath}`;
|
|
134
|
+
if (source === 'tester') return `apply test-engineering corrections from ${planPath}`;
|
|
135
|
+
if (source === 'pentester') return `fix security findings from ${planPath}`;
|
|
136
|
+
return `apply review corrections from ${planPath}`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function readStatus(targetDir, source, target, options = {}) {
|
|
140
|
+
const statePath = resolveStatePath(targetDir, source, target);
|
|
141
|
+
const state = await readJsonIfExists(statePath);
|
|
142
|
+
const maxCycles = await resolveMaxCycles(targetDir, source, options);
|
|
143
|
+
return {
|
|
144
|
+
ok: true,
|
|
145
|
+
source,
|
|
146
|
+
target,
|
|
147
|
+
path: path.relative(targetDir, statePath).replace(/\\/g, '/'),
|
|
148
|
+
exists: Boolean(state),
|
|
149
|
+
max_cycles: maxCycles,
|
|
150
|
+
remaining_cycles: Math.max(0, maxCycles - Number(state?.cycle || 0)),
|
|
151
|
+
state
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function runAdvance(targetDir, source, target, options = {}) {
|
|
156
|
+
const feature = options.feature ? String(options.feature).trim() : null;
|
|
157
|
+
const planPath = options.plan ? String(options.plan).trim() : null;
|
|
158
|
+
const criticalSecurity = Boolean(options['critical-security'] || options.criticalSecurity);
|
|
159
|
+
const maxCycles = await resolveMaxCycles(targetDir, source, options);
|
|
160
|
+
const statePath = resolveStatePath(targetDir, source, target);
|
|
161
|
+
|
|
162
|
+
if (!feature) return { ok: false, reason: 'missing_feature' };
|
|
163
|
+
if (!planPath) return { ok: false, reason: 'missing_plan' };
|
|
164
|
+
|
|
165
|
+
if (criticalSecurity) {
|
|
166
|
+
return {
|
|
167
|
+
ok: true,
|
|
168
|
+
action: 'human_gate',
|
|
169
|
+
reason: 'critical_security',
|
|
170
|
+
feature,
|
|
171
|
+
source,
|
|
172
|
+
target,
|
|
173
|
+
max_cycles: maxCycles,
|
|
174
|
+
next_agent: null,
|
|
175
|
+
plan: planPath
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const existing = await readJsonIfExists(statePath);
|
|
180
|
+
const sameFeature = existing && existing.slug === feature;
|
|
181
|
+
const currentCycle = sameFeature ? Number(existing.cycle || 0) : 0;
|
|
182
|
+
|
|
183
|
+
if (currentCycle >= maxCycles) {
|
|
184
|
+
try { await fs.unlink(statePath); } catch { /* absent is fine */ }
|
|
185
|
+
return {
|
|
186
|
+
ok: true,
|
|
187
|
+
action: 'stop_cycle_limit',
|
|
188
|
+
reason: 'cycle_limit_reached',
|
|
189
|
+
feature,
|
|
190
|
+
source,
|
|
191
|
+
target,
|
|
192
|
+
cycle: currentCycle,
|
|
193
|
+
max_cycles: maxCycles,
|
|
194
|
+
next_agent: null,
|
|
195
|
+
plan: planPath
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const now = new Date().toISOString();
|
|
200
|
+
const nextCycle = currentCycle + 1;
|
|
201
|
+
const state = {
|
|
202
|
+
slug: feature,
|
|
203
|
+
source,
|
|
204
|
+
target,
|
|
205
|
+
cycle: nextCycle,
|
|
206
|
+
max_cycles: maxCycles,
|
|
207
|
+
status: 'open',
|
|
208
|
+
started_at: sameFeature && existing.started_at ? existing.started_at : now,
|
|
209
|
+
updated_at: now,
|
|
210
|
+
last_plan: planPath,
|
|
211
|
+
last_summary: options.summary ? String(options.summary).trim() : null
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
await writeJson(statePath, state);
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
ok: true,
|
|
218
|
+
action: `invoke_${target}`,
|
|
219
|
+
feature,
|
|
220
|
+
source,
|
|
221
|
+
target,
|
|
222
|
+
next_agent: target,
|
|
223
|
+
cycle: nextCycle,
|
|
224
|
+
max_cycles: maxCycles,
|
|
225
|
+
remaining_cycles: Math.max(0, maxCycles - nextCycle),
|
|
226
|
+
plan: planPath,
|
|
227
|
+
task: buildNextTask({ source, planPath }),
|
|
228
|
+
state_path: path.relative(targetDir, statePath).replace(/\\/g, '/'),
|
|
229
|
+
state
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async function runResolve(targetDir, source, target, options = {}) {
|
|
234
|
+
const feature = options.feature ? String(options.feature).trim() : null;
|
|
235
|
+
const planPath = options.plan ? String(options.plan).trim() : null;
|
|
236
|
+
const statePath = resolveStatePath(targetDir, source, target);
|
|
237
|
+
const existing = await readJsonIfExists(statePath);
|
|
238
|
+
|
|
239
|
+
if (!feature) return { ok: false, reason: 'missing_feature' };
|
|
240
|
+
if (!existing || existing.slug !== feature) {
|
|
241
|
+
return { ok: true, action: 'no_active_cycle', feature, source, target, next_agent: 'qa' };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
let planUpdate = null;
|
|
245
|
+
if (planPath) {
|
|
246
|
+
planUpdate = await updatePlanStatus(targetDir, planPath, 'resolved');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const now = new Date().toISOString();
|
|
250
|
+
const state = {
|
|
251
|
+
...existing,
|
|
252
|
+
status: 'resolved',
|
|
253
|
+
resolved_at: now,
|
|
254
|
+
updated_at: now,
|
|
255
|
+
resolved_plan: planPath || existing.last_plan || null
|
|
256
|
+
};
|
|
257
|
+
await writeJson(statePath, state);
|
|
258
|
+
|
|
259
|
+
return {
|
|
260
|
+
ok: true,
|
|
261
|
+
action: 'invoke_qa',
|
|
262
|
+
feature,
|
|
263
|
+
source,
|
|
264
|
+
target,
|
|
265
|
+
next_agent: 'qa',
|
|
266
|
+
state_path: path.relative(targetDir, statePath).replace(/\\/g, '/'),
|
|
267
|
+
plan_update: planUpdate,
|
|
268
|
+
state
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function runReset(targetDir, source, target, options = {}) {
|
|
273
|
+
const statePath = resolveStatePath(targetDir, source, target);
|
|
274
|
+
const existed = await exists(statePath);
|
|
275
|
+
if (existed) {
|
|
276
|
+
await fs.unlink(statePath);
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
ok: true,
|
|
280
|
+
action: 'reset',
|
|
281
|
+
source,
|
|
282
|
+
target,
|
|
283
|
+
feature: options.feature ? String(options.feature).trim() : null,
|
|
284
|
+
removed: existed,
|
|
285
|
+
path: path.relative(targetDir, statePath).replace(/\\/g, '/')
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async function runReviewCycle({ args, options = {}, logger }) {
|
|
290
|
+
const targetDir = resolveTargetDir(args);
|
|
291
|
+
const action = normalizeAgent(options.sub || options.action || 'status', 'status');
|
|
292
|
+
const source = normalizeAgent(options.source || options.agent, 'qa');
|
|
293
|
+
const target = normalizeAgent(options.to || options.target, 'dev');
|
|
294
|
+
let result;
|
|
295
|
+
|
|
296
|
+
if (action === 'status') {
|
|
297
|
+
result = await readStatus(targetDir, source, target, options);
|
|
298
|
+
} else if (action === 'advance' || action === 'start') {
|
|
299
|
+
result = await runAdvance(targetDir, source, target, options);
|
|
300
|
+
} else if (action === 'resolve') {
|
|
301
|
+
result = await runResolve(targetDir, source, target, options);
|
|
302
|
+
} else if (action === 'reset') {
|
|
303
|
+
result = await runReset(targetDir, source, target, options);
|
|
304
|
+
} else {
|
|
305
|
+
result = { ok: false, reason: 'unknown_review_cycle_action', action };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (options.json) return result;
|
|
309
|
+
|
|
310
|
+
if (!result.ok) {
|
|
311
|
+
logger.error(`review-cycle:${action} failed: ${result.reason || 'unknown'}`);
|
|
312
|
+
return result;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
logger.log(`review-cycle:${action} — ${result.action || 'status'}`);
|
|
316
|
+
if (result.feature) logger.log(` feature: ${result.feature}`);
|
|
317
|
+
logger.log(` route: @${source} -> @${target}`);
|
|
318
|
+
if (result.cycle !== undefined) logger.log(` cycle: ${result.cycle}/${result.max_cycles}`);
|
|
319
|
+
if (result.next_agent) logger.log(` next: @${result.next_agent}`);
|
|
320
|
+
if (result.task) logger.log(` task: ${result.task}`);
|
|
321
|
+
return result;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
module.exports = {
|
|
325
|
+
DEFAULT_MAX_CYCLES,
|
|
326
|
+
resolveStatePath,
|
|
327
|
+
runReviewCycle
|
|
328
|
+
};
|
package/src/commands/runtime.js
CHANGED
|
@@ -1224,7 +1224,7 @@ async function runAgentDone({ args, options = {}, logger, t }) {
|
|
|
1224
1224
|
}
|
|
1225
1225
|
|
|
1226
1226
|
// F2 (workflow-handoff-integrity v1.9.5) — best-effort auto-advance workflow pointer
|
|
1227
|
-
await maybeAutoAdvanceWorkflow({ targetDir, normalizedAgent, options, logger, t });
|
|
1227
|
+
const autoAdvance = await maybeAutoAdvanceWorkflow({ targetDir, normalizedAgent, options, logger, t });
|
|
1228
1228
|
|
|
1229
1229
|
if (isDocCreatingAgent(normalizedAgent)) {
|
|
1230
1230
|
backupAiosonDocs(targetDir).catch(() => {});
|
|
@@ -1252,7 +1252,7 @@ async function runAgentDone({ args, options = {}, logger, t }) {
|
|
|
1252
1252
|
});
|
|
1253
1253
|
} catch { /* ignore — never blocks agent_done */ }
|
|
1254
1254
|
|
|
1255
|
-
return { ok: true, targetDir, dbPath, agent: normalizedAgent, mode: 'live_event', runKey: session.runKey };
|
|
1255
|
+
return { ok: true, targetDir, dbPath, agent: normalizedAgent, mode: 'live_event', runKey: session.runKey, auto_advance: autoAdvance };
|
|
1256
1256
|
}
|
|
1257
1257
|
|
|
1258
1258
|
// No active session — create a standalone task+run and immediately complete it.
|
|
@@ -1297,7 +1297,7 @@ async function runAgentDone({ args, options = {}, logger, t }) {
|
|
|
1297
1297
|
}
|
|
1298
1298
|
|
|
1299
1299
|
// F2 (workflow-handoff-integrity v1.9.5) — best-effort auto-advance workflow pointer
|
|
1300
|
-
await maybeAutoAdvanceWorkflow({ targetDir, normalizedAgent, options, logger, t });
|
|
1300
|
+
const autoAdvance = await maybeAutoAdvanceWorkflow({ targetDir, normalizedAgent, options, logger, t });
|
|
1301
1301
|
|
|
1302
1302
|
if (isDocCreatingAgent(normalizedAgent)) {
|
|
1303
1303
|
backupAiosonDocs(targetDir).catch(() => {});
|
|
@@ -1325,7 +1325,7 @@ async function runAgentDone({ args, options = {}, logger, t }) {
|
|
|
1325
1325
|
});
|
|
1326
1326
|
} catch { /* ignore — never blocks agent_done */ }
|
|
1327
1327
|
|
|
1328
|
-
return { ok: true, targetDir, dbPath, agent: normalizedAgent, mode: 'standalone', runKey, taskKey };
|
|
1328
|
+
return { ok: true, targetDir, dbPath, agent: normalizedAgent, mode: 'standalone', runKey, taskKey, auto_advance: autoAdvance };
|
|
1329
1329
|
} finally {
|
|
1330
1330
|
db.close();
|
|
1331
1331
|
}
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
* sheldon → sheldon-enrichment-{slug}.md
|
|
29
29
|
* design-doc → design-doc-{slug}.md (falls back to design-doc.md)
|
|
30
30
|
* readiness → readiness-{slug}.md (falls back to readiness.md)
|
|
31
|
+
* ui-spec → ui-spec.md
|
|
31
32
|
* dossier → features/{slug}/dossier.md
|
|
32
33
|
* simple-plan → simple-plans/{slug}.md
|
|
33
34
|
*
|
|
@@ -50,6 +51,7 @@ const CONTEXT_TYPE_MAP = {
|
|
|
50
51
|
sheldon: { rel: (slug) => `sheldon-enrichment-${slug}.md` },
|
|
51
52
|
'design-doc': { rel: (slug) => `design-doc-${slug}.md`, fallback: () => 'design-doc.md' },
|
|
52
53
|
readiness: { rel: (slug) => `readiness-${slug}.md`, fallback: () => 'readiness.md' },
|
|
54
|
+
'ui-spec': { rel: () => 'ui-spec.md' },
|
|
53
55
|
dossier: { rel: (slug) => `features/${slug}/dossier.md` },
|
|
54
56
|
'simple-plan': { rel: (slug) => `simple-plans/${slug}.md` }
|
|
55
57
|
};
|
|
@@ -37,8 +37,9 @@ const {
|
|
|
37
37
|
extractStatusWritePathItems
|
|
38
38
|
} = require('../parallel-workspace');
|
|
39
39
|
|
|
40
|
-
const BAR = '━'.repeat(45);
|
|
41
|
-
const EXECUTION_STATE_RELATIVE_PATH = '.aioson/context/workflow-execute.json';
|
|
40
|
+
const BAR = '━'.repeat(45);
|
|
41
|
+
const EXECUTION_STATE_RELATIVE_PATH = '.aioson/context/workflow-execute.json';
|
|
42
|
+
const DEFAULT_AGENTIC_MAX_CYCLES = 3;
|
|
42
43
|
|
|
43
44
|
const STEP_META = {
|
|
44
45
|
setup: { description: 'Initialize project context', gate_before: null, gate_after: null },
|
|
@@ -96,6 +97,96 @@ function quoteCliArg(value) {
|
|
|
96
97
|
return `'${String(value || '').replace(/'/g, "'\\''")}'`;
|
|
97
98
|
}
|
|
98
99
|
|
|
100
|
+
function parsePositiveIntegerOption(value, fallback, min = 1, max = 10) {
|
|
101
|
+
const parsed = Number.parseInt(String(value || ''), 10);
|
|
102
|
+
if (!Number.isInteger(parsed)) return fallback;
|
|
103
|
+
if (parsed < min) return min;
|
|
104
|
+
if (parsed > max) return max;
|
|
105
|
+
return parsed;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function isAgenticRequested(options = {}) {
|
|
109
|
+
return Boolean(
|
|
110
|
+
options.agentic ||
|
|
111
|
+
options['agentic-run'] ||
|
|
112
|
+
options.autopilot === 'agentic' ||
|
|
113
|
+
options.autopilot === 'runtime'
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function buildAgenticPolicy(options = {}, classification = 'SMALL') {
|
|
118
|
+
const enabled = isAgenticRequested(options);
|
|
119
|
+
if (!enabled) return null;
|
|
120
|
+
|
|
121
|
+
const maxDevQaCycles = parsePositiveIntegerOption(
|
|
122
|
+
options['max-dev-qa-cycles'] || options.maxDevQaCycles || options['max-cycles'],
|
|
123
|
+
DEFAULT_AGENTIC_MAX_CYCLES
|
|
124
|
+
);
|
|
125
|
+
const maxTesterCycles = parsePositiveIntegerOption(
|
|
126
|
+
options['max-tester-cycles'] || options.maxTesterCycles || options['max-specialist-cycles'],
|
|
127
|
+
DEFAULT_AGENTIC_MAX_CYCLES
|
|
128
|
+
);
|
|
129
|
+
const maxPentesterCycles = parsePositiveIntegerOption(
|
|
130
|
+
options['max-pentester-cycles'] || options.maxPentesterCycles || options['max-specialist-cycles'],
|
|
131
|
+
DEFAULT_AGENTIC_MAX_CYCLES
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
enabled: true,
|
|
136
|
+
mode: 'runtime_policy',
|
|
137
|
+
source: 'workflow:execute',
|
|
138
|
+
stop_conditions: [
|
|
139
|
+
'feature_status_done',
|
|
140
|
+
'human_decision_required',
|
|
141
|
+
'gate_blocked',
|
|
142
|
+
'context_budget_exceeded',
|
|
143
|
+
'cycle_limit_reached',
|
|
144
|
+
'critical_security_human_gate',
|
|
145
|
+
'feature_close_human_gate'
|
|
146
|
+
],
|
|
147
|
+
review_cycle: {
|
|
148
|
+
hub: 'qa',
|
|
149
|
+
max_dev_qa_cycles: maxDevQaCycles,
|
|
150
|
+
max_tester_correction_cycles: maxTesterCycles,
|
|
151
|
+
max_pentester_correction_cycles: maxPentesterCycles,
|
|
152
|
+
qa_fail_route: 'dev',
|
|
153
|
+
tester_route: 'tester after qa coverage trigger',
|
|
154
|
+
pentester_route: 'pentester after sensitive-surface trigger or MEDIUM sequence stage',
|
|
155
|
+
validator_route: 'validator when harness contract is present',
|
|
156
|
+
feature_close: 'human_gate'
|
|
157
|
+
},
|
|
158
|
+
lanes: {
|
|
159
|
+
enabled: classification === 'MEDIUM',
|
|
160
|
+
strategy: 'parallelize_only_independent_write_scopes',
|
|
161
|
+
guard_command: 'aioson parallel:guard . --lane=<n>',
|
|
162
|
+
conflict_action: 'block_lane'
|
|
163
|
+
},
|
|
164
|
+
sidecars: {
|
|
165
|
+
scouts: {
|
|
166
|
+
enabled: true,
|
|
167
|
+
read_only: true,
|
|
168
|
+
max_per_session: 3,
|
|
169
|
+
max_files_in_scope: 20,
|
|
170
|
+
allowed_parent_agents: ['deyvin', 'dev', 'product', 'briefing', 'orache']
|
|
171
|
+
},
|
|
172
|
+
research: {
|
|
173
|
+
enabled: true,
|
|
174
|
+
cache_dir: 'researchs/',
|
|
175
|
+
cache_ttl_days: 7
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function formatAgenticPolicyLines(policy) {
|
|
182
|
+
if (!policy || !policy.enabled) return [];
|
|
183
|
+
return [
|
|
184
|
+
`Agentic policy: enabled (dev<->qa max ${policy.review_cycle.max_dev_qa_cycles} cycles)`,
|
|
185
|
+
`Review loop: qa fail -> dev; tester max ${policy.review_cycle.max_tester_correction_cycles}; pentester max ${policy.review_cycle.max_pentester_correction_cycles}; close=${policy.review_cycle.feature_close}`,
|
|
186
|
+
`Parallel lanes: ${policy.lanes.enabled ? 'enabled for independent write scopes' : 'disabled for this classification'}`
|
|
187
|
+
];
|
|
188
|
+
}
|
|
189
|
+
|
|
99
190
|
function findNextFromSequence(sequence, completed, skipped = []) {
|
|
100
191
|
const done = new Set([...(completed || []), ...(skipped || [])].map(normalizeAgentName));
|
|
101
192
|
return sequence.find((stage) => !done.has(normalizeAgentName(stage))) || null;
|
|
@@ -474,9 +565,10 @@ async function writeExecutionCheckpoint(targetDir, payload) {
|
|
|
474
565
|
checkpoint: payload.checkpoint || null,
|
|
475
566
|
status_snapshot: payload.statusSnapshot || null,
|
|
476
567
|
suggestion: payload.suggestion || null,
|
|
477
|
-
resume_command: payload.resumeCommand || null,
|
|
478
|
-
|
|
479
|
-
|
|
568
|
+
resume_command: payload.resumeCommand || null,
|
|
569
|
+
agentic_policy: payload.agenticPolicy || null,
|
|
570
|
+
history
|
|
571
|
+
};
|
|
480
572
|
await writeJson(execPath, nextPayload);
|
|
481
573
|
return nextPayload;
|
|
482
574
|
}
|
|
@@ -630,6 +722,7 @@ async function runWorkflowExecute({ args, options = {}, logger }) {
|
|
|
630
722
|
if (!classification) classification = await detectClassification(targetDir, slug);
|
|
631
723
|
if (!classification) classification = 'SMALL';
|
|
632
724
|
classification = normalizeClassification(classification, 'SMALL');
|
|
725
|
+
const agenticPolicy = buildAgenticPolicy(options, classification);
|
|
633
726
|
|
|
634
727
|
const autonomyProtocol = await readAutonomyProtocol(targetDir);
|
|
635
728
|
const toolPolicy = getToolPolicy(autonomyProtocol, tool);
|
|
@@ -698,7 +791,17 @@ async function runWorkflowExecute({ args, options = {}, logger }) {
|
|
|
698
791
|
`--feature=${quoteCliArg(slug)}`,
|
|
699
792
|
`--tool=${quoteCliArg(tool)}`,
|
|
700
793
|
...(requestedMode ? [`--mode=${quoteCliArg(requestedMode)}`] : []),
|
|
701
|
-
...(maxCheckpoints !== 1 ? [`--max-checkpoints=${quoteCliArg(maxCheckpoints)}`] : [])
|
|
794
|
+
...(maxCheckpoints !== 1 ? [`--max-checkpoints=${quoteCliArg(maxCheckpoints)}`] : []),
|
|
795
|
+
...(agenticPolicy ? ['--agentic'] : []),
|
|
796
|
+
...(agenticPolicy && agenticPolicy.review_cycle.max_dev_qa_cycles !== DEFAULT_AGENTIC_MAX_CYCLES
|
|
797
|
+
? [`--max-dev-qa-cycles=${quoteCliArg(agenticPolicy.review_cycle.max_dev_qa_cycles)}`]
|
|
798
|
+
: []),
|
|
799
|
+
...(agenticPolicy && agenticPolicy.review_cycle.max_tester_correction_cycles !== DEFAULT_AGENTIC_MAX_CYCLES
|
|
800
|
+
? [`--max-tester-cycles=${quoteCliArg(agenticPolicy.review_cycle.max_tester_correction_cycles)}`]
|
|
801
|
+
: []),
|
|
802
|
+
...(agenticPolicy && agenticPolicy.review_cycle.max_pentester_correction_cycles !== DEFAULT_AGENTIC_MAX_CYCLES
|
|
803
|
+
? [`--max-pentester-cycles=${quoteCliArg(agenticPolicy.review_cycle.max_pentester_correction_cycles)}`]
|
|
804
|
+
: [])
|
|
702
805
|
].join(' ');
|
|
703
806
|
|
|
704
807
|
if (dryRun) {
|
|
@@ -718,10 +821,11 @@ async function runWorkflowExecute({ args, options = {}, logger }) {
|
|
|
718
821
|
blocked_steps: blockedSteps.length,
|
|
719
822
|
gates: planData.gates,
|
|
720
823
|
status_snapshot: statusSnapshot,
|
|
721
|
-
suggestion: statusSnapshot && statusSnapshot.suggestion ? statusSnapshot.suggestion : null,
|
|
722
|
-
resume_command: resumeCommand,
|
|
723
|
-
|
|
724
|
-
|
|
824
|
+
suggestion: statusSnapshot && statusSnapshot.suggestion ? statusSnapshot.suggestion : null,
|
|
825
|
+
resume_command: resumeCommand,
|
|
826
|
+
agentic_policy: agenticPolicy,
|
|
827
|
+
parallel_guard: parallelGuard
|
|
828
|
+
};
|
|
725
829
|
|
|
726
830
|
if (options.json) return result;
|
|
727
831
|
|
|
@@ -740,11 +844,14 @@ async function runWorkflowExecute({ args, options = {}, logger }) {
|
|
|
740
844
|
logger.log('');
|
|
741
845
|
logger.log(`Blocked steps: ${blockedSteps.length} | Remaining: ${activePlan.length}`);
|
|
742
846
|
logger.log(`Resume state: ${seeded.resumed ? 'existing workflow state reused' : 'new workflow state seeded'}`);
|
|
743
|
-
if (statusSnapshot && statusSnapshot.suggestion && statusSnapshot.suggestion.command) {
|
|
744
|
-
logger.log(`Suggested command: ${statusSnapshot.suggestion.command}`);
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
|
|
847
|
+
if (statusSnapshot && statusSnapshot.suggestion && statusSnapshot.suggestion.command) {
|
|
848
|
+
logger.log(`Suggested command: ${statusSnapshot.suggestion.command}`);
|
|
849
|
+
}
|
|
850
|
+
for (const line of formatAgenticPolicyLines(agenticPolicy)) {
|
|
851
|
+
logger.log(line);
|
|
852
|
+
}
|
|
853
|
+
logger.log('');
|
|
854
|
+
return result;
|
|
748
855
|
}
|
|
749
856
|
|
|
750
857
|
const executionTransitions = [];
|
|
@@ -824,10 +931,11 @@ async function runWorkflowExecute({ args, options = {}, logger }) {
|
|
|
824
931
|
resumed: seeded.resumed,
|
|
825
932
|
status: activation && activation.agent ? 'active' : 'completed',
|
|
826
933
|
checkpoint: buildCheckpointPayload(activation, handoff, handoffProtocol),
|
|
827
|
-
statusSnapshot: refreshedStatus,
|
|
828
|
-
suggestion: refreshedStatus && refreshedStatus.suggestion ? refreshedStatus.suggestion : null,
|
|
829
|
-
resumeCommand
|
|
830
|
-
|
|
934
|
+
statusSnapshot: refreshedStatus,
|
|
935
|
+
suggestion: refreshedStatus && refreshedStatus.suggestion ? refreshedStatus.suggestion : null,
|
|
936
|
+
resumeCommand,
|
|
937
|
+
agenticPolicy
|
|
938
|
+
});
|
|
831
939
|
|
|
832
940
|
const result = {
|
|
833
941
|
ok: true,
|
|
@@ -842,9 +950,10 @@ async function runWorkflowExecute({ args, options = {}, logger }) {
|
|
|
842
950
|
checkpoint: executionState.checkpoint,
|
|
843
951
|
execution_state: executionState,
|
|
844
952
|
status_snapshot: refreshedStatus,
|
|
845
|
-
suggestion: refreshedStatus && refreshedStatus.suggestion ? refreshedStatus.suggestion : null,
|
|
846
|
-
resume_command: resumeCommand,
|
|
847
|
-
|
|
953
|
+
suggestion: refreshedStatus && refreshedStatus.suggestion ? refreshedStatus.suggestion : null,
|
|
954
|
+
resume_command: resumeCommand,
|
|
955
|
+
agentic_policy: agenticPolicy,
|
|
956
|
+
transitions: executionTransitions,
|
|
848
957
|
active_stage: activation && activation.agent ? activation.agent : null,
|
|
849
958
|
next_stage: activation && activation.next ? activation.next : null,
|
|
850
959
|
handoff,
|
|
@@ -862,9 +971,10 @@ async function runWorkflowExecute({ args, options = {}, logger }) {
|
|
|
862
971
|
return result;
|
|
863
972
|
}
|
|
864
973
|
|
|
865
|
-
module.exports = {
|
|
866
|
-
EXECUTION_STATE_RELATIVE_PATH,
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
974
|
+
module.exports = {
|
|
975
|
+
EXECUTION_STATE_RELATIVE_PATH,
|
|
976
|
+
buildAgenticPolicy,
|
|
977
|
+
buildExecutionPlan,
|
|
978
|
+
seedFeatureWorkflowState,
|
|
979
|
+
runWorkflowExecute
|
|
980
|
+
};
|
|
@@ -41,8 +41,9 @@ const DEFAULT_FEATURE_WORKFLOW_BY_CLASSIFICATION = {
|
|
|
41
41
|
|
|
42
42
|
// Stages eligible for autopilot handoff (auto_handoff: true in project.context.md).
|
|
43
43
|
// Two segments — see .aioson/docs/autopilot-handoff.md:
|
|
44
|
-
// 1. analyst
|
|
45
|
-
//
|
|
44
|
+
// 1. analyst -> dev: deterministic pre-dev chain. Prompt-only clients stop
|
|
45
|
+
// before the first @dev entry; workflow:execute --agentic may resume it
|
|
46
|
+
// through a fresh checkpointed activation.
|
|
46
47
|
// 2. post-dev review cycle: @dev → @qa → @tester/@pentester (when their @qa triggers
|
|
47
48
|
// fire) → @validator → STOPS before feature:close (human approves the close).
|
|
48
49
|
const AUTOPILOT_HANDOFF_STAGES = new Set([
|