@jaimevalasek/aioson 1.17.3 → 1.19.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.
Files changed (90) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +85 -51
  3. package/docs/en/3-recipes/full-feature-with-sheldon.md +1 -1
  4. package/docs/en/5-reference/cli-reference.md +4 -4
  5. package/docs/en/5-reference/qa-browser.md +2 -2
  6. package/docs/en/README.md +1 -1
  7. package/docs/en/deyvin-subtask-scout/how-to-use.md +2 -2
  8. package/docs/en/deyvin-subtask-scout/sub-task-scout.md +3 -3
  9. package/docs/en/deyvin-subtask-scout/troubleshooting.md +1 -1
  10. package/docs/pt/3-receitas/publicar-no-aioson-com.md +17 -0
  11. package/docs/pt/5-referencia/comandos-cli.md +2 -2
  12. package/docs/pt/5-referencia/inteligencia-adaptativa.md +3 -3
  13. package/docs/pt/5-referencia/skills.md +1 -1
  14. package/docs/pt/5-referencia/web3.md +3 -3
  15. package/docs/pt/README.md +1 -1
  16. package/docs/pt/_arquivo/README.md +1 -1
  17. package/docs/pt/_arquivo/cenarios.md +31 -31
  18. package/docs/pt/_arquivo/design-hybrid-forge.md +5 -5
  19. package/docs/pt/_arquivo/guia-engineer.md +1 -1
  20. package/docs/pt/_arquivo/profiler-system.md +1 -1
  21. package/docs/pt/_arquivo/site-forge.md +16 -16
  22. package/docs/pt/_arquivo/squad-genome.md +2 -2
  23. package/docs/pt/agentes.md +37 -37
  24. package/docs/pt/deyvin-subtask-scout/como-usar.md +2 -2
  25. package/docs/pt/deyvin-subtask-scout/sub-task-scout.md +1 -1
  26. package/docs/pt/deyvin-subtask-scout/troubleshooting.md +1 -1
  27. package/docs/pt/living-memory/README.md +1 -1
  28. package/docs/pt/living-memory/memoria-viva.md +2 -2
  29. package/docs/pt/living-memory/reflexao-in-harness.md +1 -1
  30. package/docs/pt/living-memory/troubleshooting.md +6 -6
  31. package/package.json +1 -1
  32. package/src/cli.js +111 -7
  33. package/src/commands/gate-approve.js +56 -1
  34. package/src/commands/live.js +81 -54
  35. package/src/commands/op-capture.js +27 -2
  36. package/src/commands/op-list.js +33 -1
  37. package/src/commands/store-system.js +4 -0
  38. package/src/commands/tool-capabilities.js +14 -10
  39. package/src/commands/workflow-heal.js +47 -1
  40. package/src/constants.js +73 -0
  41. package/src/i18n/messages/en.js +20 -2
  42. package/src/i18n/messages/es.js +18 -1
  43. package/src/i18n/messages/fr.js +18 -1
  44. package/src/i18n/messages/pt-BR.js +20 -2
  45. package/src/lib/dev-resume.js +6 -1
  46. package/src/lib/tool-capabilities.js +64 -37
  47. package/src/operator-memory/decision.js +11 -4
  48. package/src/operator-memory/proposal.js +11 -7
  49. package/src/session-handoff.js +52 -1
  50. package/template/.aioson/agents/analyst.md +33 -1
  51. package/template/.aioson/agents/architect.md +33 -1
  52. package/template/.aioson/agents/briefing.md +23 -0
  53. package/template/.aioson/agents/orchestrator.md +26 -0
  54. package/template/.aioson/agents/pentester.md +66 -14
  55. package/template/.aioson/agents/pm.md +18 -1
  56. package/template/.aioson/agents/product.md +11 -0
  57. package/template/.aioson/agents/sheldon.md +21 -1
  58. package/template/.aioson/agents/tester.md +114 -1
  59. package/template/.aioson/docs/pentester/browser-dast-playbook.md +398 -0
  60. package/template/.aioson/rules/agent-structural-contract.md +139 -0
  61. package/template/.aioson/skills/process/decision-presentation/SKILL.md +2 -2
  62. package/template/.claude/commands/aioson/agent/analyst.md +16 -5
  63. package/template/.claude/commands/aioson/agent/architect.md +17 -5
  64. package/template/.claude/commands/aioson/agent/briefing.md +16 -5
  65. package/template/.claude/commands/aioson/agent/committer.md +16 -5
  66. package/template/.claude/commands/aioson/agent/copywriter.md +16 -5
  67. package/template/.claude/commands/aioson/agent/design-hybrid-forge.md +16 -5
  68. package/template/.claude/commands/aioson/agent/dev.md +18 -5
  69. package/template/.claude/commands/aioson/agent/deyvin.md +16 -5
  70. package/template/.claude/commands/aioson/agent/discover.md +16 -5
  71. package/template/.claude/commands/aioson/agent/discovery-design-doc.md +16 -5
  72. package/template/.claude/commands/aioson/agent/genome.md +16 -5
  73. package/template/.claude/commands/aioson/agent/neo.md +16 -5
  74. package/template/.claude/commands/aioson/agent/orache.md +16 -5
  75. package/template/.claude/commands/aioson/agent/orchestrator.md +21 -5
  76. package/template/.claude/commands/aioson/agent/pair.md +16 -5
  77. package/template/.claude/commands/aioson/agent/pentester.md +22 -5
  78. package/template/.claude/commands/aioson/agent/pm.md +20 -5
  79. package/template/.claude/commands/aioson/agent/product.md +16 -5
  80. package/template/.claude/commands/aioson/agent/profiler-enricher.md +16 -5
  81. package/template/.claude/commands/aioson/agent/profiler-forge.md +16 -5
  82. package/template/.claude/commands/aioson/agent/profiler-researcher.md +16 -5
  83. package/template/.claude/commands/aioson/agent/qa.md +16 -5
  84. package/template/.claude/commands/aioson/agent/setup.md +16 -5
  85. package/template/.claude/commands/aioson/agent/sheldon.md +16 -5
  86. package/template/.claude/commands/aioson/agent/site-forge.md +16 -5
  87. package/template/.claude/commands/aioson/agent/squad.md +16 -5
  88. package/template/.claude/commands/aioson/agent/tester.md +16 -5
  89. package/template/.claude/commands/aioson/agent/ux-ui.md +19 -5
  90. package/template/.claude/commands/aioson/agent/validator.md +17 -5
@@ -19,7 +19,7 @@ const {
19
19
  const { ensureDir, exists } = require('../utils');
20
20
  const { SUPPORTED_PROMPT_TOOLS } = require('../prompt-tool');
21
21
  const { isTmuxAvailable, launchTmuxSession, buildSessionName, hasSession, attachSession } = require('../lib/tmux-launcher');
22
- const { resolveResumeArgs } = require('../lib/tool-capabilities');
22
+ const { resolvePermissionModeArgs, resolveResumeArgs } = require('../lib/tool-capabilities');
23
23
 
24
24
  const LIVE_EVENTS_LIMIT = 10;
25
25
  const LIVE_MESSAGE_LIMIT = 500;
@@ -116,14 +116,16 @@ function parseJsonOption(value) {
116
116
  }
117
117
  }
118
118
 
119
- // Combine `--resume` (mapped per-tool via TOOL_CAPS) with user-provided `--tool-args`.
120
- // Resume args go FIRST so that codex `resume --last` (subcommand) lands at argv[1].
121
- function buildLaunchArgs(options, tool) {
122
- const resumeOpt = options.resume !== undefined ? options.resume : options.Resume;
123
- const resumeArgs = resolveResumeArgs(tool, resumeOpt);
124
- const userArgs = parseToolArgs(options['tool-args'] || options.toolArgs);
125
- return [...resumeArgs, ...userArgs];
126
- }
119
+ // Combine `--resume` (mapped per-tool via TOOL_CAPS) with user-provided `--tool-args`.
120
+ // Resume args go FIRST so that codex `resume --last` (subcommand) lands at argv[1].
121
+ function buildLaunchArgs(options, tool) {
122
+ const resumeOpt = options.resume !== undefined ? options.resume : options.Resume;
123
+ const resumeArgs = resolveResumeArgs(tool, resumeOpt);
124
+ const permissionMode = options['permission-mode'] || options.permissionMode;
125
+ const permissionArgs = resolvePermissionModeArgs(tool, permissionMode);
126
+ const userArgs = parseToolArgs(options['tool-args'] || options.toolArgs);
127
+ return [...resumeArgs, ...permissionArgs, ...userArgs];
128
+ }
127
129
 
128
130
  function parseToolArgs(value) {
129
131
  if (value === undefined || value === null || value === '') return [];
@@ -234,7 +236,7 @@ async function resolveExecutablePath(command) {
234
236
  .filter(Boolean);
235
237
 
236
238
  const extensions = process.platform === 'win32'
237
- ? Array.from(new Set(['', ...String(process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM').split(';').map((entry) => entry.toLowerCase())]))
239
+ ? Array.from(new Set([...String(process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM').split(';').map((entry) => entry.toLowerCase())]))
238
240
  : [''];
239
241
 
240
242
  for (const dir of pathEntries) {
@@ -1235,57 +1237,80 @@ async function runLiveStart({ args, options = {}, logger, t }) {
1235
1237
  };
1236
1238
  }
1237
1239
  } else {
1238
- // Non-tmux reuse logic (original behavior)
1240
+ // Non-tmux reuse logic
1239
1241
  const existingTool = state.tool_session || null;
1240
1242
  if (existingTool && existingTool !== tool) {
1241
- throw new Error(t('live.tool_mismatch', { existing: existingTool, requested: tool }));
1242
- }
1243
-
1244
- const attach = Boolean(options.attach);
1245
- let attachChild = null;
1246
- let attachResult = null;
1247
-
1248
- if (attach && !noLaunch) {
1249
- attachChild = spawn(binaryPath, buildLaunchArgs(options, tool), {
1250
- cwd: targetDir,
1251
- env: process.env,
1252
- stdio: 'inherit'
1243
+ // Auto-close stale session when tool changed (same pattern as tmux recovery above)
1244
+ updateRun(db, {
1245
+ runKey: existing.run.run_key,
1246
+ status: 'completed',
1247
+ summary: `Auto-closed: tool changed from ${existingTool} to ${tool}`,
1248
+ eventType: 'session_closed',
1249
+ phase: 'live',
1250
+ message: `Tool mismatch auto-closed previous ${existingTool} session`
1253
1251
  });
1254
- state.child_pid = attachChild.pid || null;
1255
1252
  if (existing.task?.task_key) {
1256
- const taskMeta = parseTaskMeta(existing.task);
1257
- taskMeta.child_pid = state.child_pid;
1258
- updateTask(db, { taskKey: existing.task.task_key, metaJson: taskMeta });
1253
+ updateTask(db, {
1254
+ taskKey: existing.task.task_key,
1255
+ status: 'completed',
1256
+ goal: `Auto-closed after tool change to ${tool}`
1257
+ });
1258
+ }
1259
+ await clearAgentSession(runtimeDir, agentName);
1260
+ if (!options.json) {
1261
+ logger.log(t('live.tool_mismatch_auto_closed', { existing: existingTool, requested: tool }) ||
1262
+ `Previous session (${existingTool}) auto-closed — starting new with ${tool}`);
1263
+ }
1264
+ // Fall through to create a new session below
1265
+ } else {
1266
+ // Tools match (or no previous tool) — reuse existing session
1267
+ const attach = Boolean(options.attach);
1268
+ let attachChild = null;
1269
+ let attachResult = null;
1270
+
1271
+ if (attach && !noLaunch) {
1272
+ attachChild = spawn(binaryPath, buildLaunchArgs(options, tool), {
1273
+ cwd: targetDir,
1274
+ env: process.env,
1275
+ stdio: 'inherit',
1276
+ shell: process.platform === 'win32'
1277
+ });
1278
+ state.child_pid = attachChild.pid || null;
1279
+ if (existing.task?.task_key) {
1280
+ const taskMeta = parseTaskMeta(existing.task);
1281
+ taskMeta.child_pid = state.child_pid;
1282
+ updateTask(db, { taskKey: existing.task.task_key, metaJson: taskMeta });
1283
+ }
1259
1284
  }
1260
- }
1261
1285
 
1262
- await writeLiveState(runtimeDir, existing.sessionKey, state);
1286
+ await writeLiveState(runtimeDir, existing.sessionKey, state);
1263
1287
 
1264
- if (!options.json) {
1265
- logger.log(t('live.session_already_active', { agent: agentName, session: existing.sessionKey, runKey: existing.run.run_key, dbPath }));
1266
- }
1288
+ if (!options.json) {
1289
+ logger.log(t('live.session_already_active', { agent: agentName, session: existing.sessionKey, runKey: existing.run.run_key, dbPath }));
1290
+ }
1267
1291
 
1268
- if (attachChild) {
1269
- attachResult = await waitForChild(attachChild);
1270
- }
1292
+ if (attachChild) {
1293
+ attachResult = await waitForChild(attachChild);
1294
+ }
1271
1295
 
1272
- return {
1273
- ok: true,
1274
- targetDir,
1275
- dbPath,
1276
- agent: existing.agentName,
1277
- tool: state.tool_session || tool,
1278
- taskKey: existing.task?.task_key || existing.sessionRef?.taskKey || null,
1279
- runKey: existing.run.run_key,
1280
- sessionKey: existing.sessionKey,
1281
- pid: state.child_pid || null,
1282
- processState: detectProcessState(state.child_pid),
1283
- reused: true,
1284
- open: true,
1285
- attached: attach,
1286
- childExitCode: attachResult?.code ?? null,
1287
- childSignal: attachResult?.signal ?? null
1288
- };
1296
+ return {
1297
+ ok: true,
1298
+ targetDir,
1299
+ dbPath,
1300
+ agent: existing.agentName,
1301
+ tool: state.tool_session || tool,
1302
+ taskKey: existing.task?.task_key || existing.sessionRef?.taskKey || null,
1303
+ runKey: existing.run.run_key,
1304
+ sessionKey: existing.sessionKey,
1305
+ pid: state.child_pid || null,
1306
+ processState: detectProcessState(state.child_pid),
1307
+ reused: true,
1308
+ open: true,
1309
+ attached: attach,
1310
+ childExitCode: attachResult?.code ?? null,
1311
+ childSignal: attachResult?.signal ?? null
1312
+ };
1313
+ }
1289
1314
  }
1290
1315
  }
1291
1316
 
@@ -1356,7 +1381,8 @@ async function runLiveStart({ args, options = {}, logger, t }) {
1356
1381
  child = spawn(binaryPath, buildLaunchArgs(options, tool), {
1357
1382
  cwd: targetDir,
1358
1383
  env: process.env,
1359
- stdio: 'inherit'
1384
+ stdio: 'inherit',
1385
+ shell: process.platform === 'win32'
1360
1386
  });
1361
1387
  taskMeta.child_pid = child.pid || null;
1362
1388
  updateTask(db, {
@@ -1368,7 +1394,8 @@ async function runLiveStart({ args, options = {}, logger, t }) {
1368
1394
  child = spawn(binaryPath, buildLaunchArgs(options, tool), {
1369
1395
  cwd: targetDir,
1370
1396
  env: process.env,
1371
- stdio: 'inherit'
1397
+ stdio: 'inherit',
1398
+ shell: process.platform === 'win32'
1372
1399
  });
1373
1400
  taskMeta.child_pid = child.pid || null;
1374
1401
  updateTask(db, {
@@ -27,6 +27,8 @@ const { captureSignal, readProposal, VALID_SIGNAL_TYPES } = require('../operator
27
27
  const { promoteProposal } = require('../operator-memory/decision');
28
28
  const { emitDossierEvent } = require('../lib/dossier-telemetry');
29
29
 
30
+ const CONFIRMATIONS_JSONL = '.aioson/runtime/session-confirmations.jsonl';
31
+
30
32
  const PROMOTION_THRESHOLD = 2;
31
33
 
32
34
  function existsCheckFactory(identity) {
@@ -55,6 +57,8 @@ First detection writes to proposals/{slug}.md. Second detection promotes to deci
55
57
  const quote = options.quote;
56
58
  const proposal = options.proposal;
57
59
  const sourceAgent = options['source-agent'] || options.sourceAgent || 'unknown';
60
+ const featureSlug = options.feature ? String(options.feature) : null;
61
+ const sessionId = options['session-id'] || options.sessionId || null;
58
62
 
59
63
  if (!signal || !proposal) {
60
64
  const err = `op:capture — required: --signal=<type> --proposal=<paraphrase>. Got signal=${signal}, proposal=${proposal ? 'present' : 'missing'}.`;
@@ -95,7 +99,9 @@ First detection writes to proposals/{slug}.md. Second detection promotes to deci
95
99
  signal_type: signal,
96
100
  quote,
97
101
  proposal,
98
- source_agent: sourceAgent
102
+ source_agent: sourceAgent,
103
+ feature_slug: featureSlug,
104
+ session_id: sessionId
99
105
  });
100
106
  } catch (err) {
101
107
  const errMsg = `op:capture failed: ${err.message}`;
@@ -104,6 +110,24 @@ First detection writes to proposals/{slug}.md. Second detection promotes to deci
104
110
  return { ok: false, exitCode: 1, error: errMsg };
105
111
  }
106
112
 
113
+ // M2: append confirmation signals to session accumulator for decision_rationale
114
+ if (signal === 'confirmation') {
115
+ try {
116
+ const accPath = path.join(targetDir, CONFIRMATIONS_JSONL);
117
+ const accDir = path.dirname(accPath);
118
+ fs.mkdirSync(accDir, { recursive: true });
119
+ const entry = JSON.stringify({
120
+ agent: sourceAgent,
121
+ decision: proposal,
122
+ quote: quote || null,
123
+ timestamp: new Date().toISOString()
124
+ });
125
+ fs.appendFileSync(accPath, entry + '\n', 'utf8');
126
+ } catch {
127
+ // best-effort — never block op:capture
128
+ }
129
+ }
130
+
107
131
  const count = result.proposal.detected_count;
108
132
 
109
133
  if (count >= PROMOTION_THRESHOLD) {
@@ -142,5 +166,6 @@ First detection writes to proposals/{slug}.md. Second detection promotes to deci
142
166
 
143
167
  module.exports = {
144
168
  runOpCapture,
145
- PROMOTION_THRESHOLD
169
+ PROMOTION_THRESHOLD,
170
+ CONFIRMATIONS_JSONL
146
171
  };
@@ -21,13 +21,15 @@ const { readDecision } = require('../operator-memory/decision');
21
21
 
22
22
  async function runOpList({ args = [], options = {}, logger }) {
23
23
  if (options.help === true || args.includes('--help') || args.includes('-h')) {
24
- if (logger) logger.log('op:list [--proposals] [--include-archived] [--format=table|json] — list active decisions.');
24
+ if (logger) logger.log('op:list [--proposals] [--include-archived] [--feature=<slug>] [--agent=<name>] [--format=table|json] — list active decisions.');
25
25
  return { ok: true };
26
26
  }
27
27
 
28
28
  const format = options.format || 'table';
29
29
  const showProposals = Boolean(options.proposals);
30
30
  const includeArchived = Boolean(options['include-archived']);
31
+ const filterFeature = options.feature ? String(options.feature) : null;
32
+ const filterAgent = options.agent ? String(options.agent) : null;
31
33
 
32
34
  const resolved = resolveIdentity();
33
35
  ensureStorageTree(resolved.identity);
@@ -65,9 +67,39 @@ async function runOpList({ args = [], options = {}, logger }) {
65
67
  }
66
68
  }
67
69
 
70
+ // M3: apply --feature and --agent filters (BR-AO-07: AND-composable)
71
+ if (filterFeature) {
72
+ items = items.filter((item) => item.feature_slug === filterFeature);
73
+ }
74
+ if (filterAgent) {
75
+ items = items.filter((item) => item.source_agent === filterAgent);
76
+ }
77
+
68
78
  if (format === 'json' || options.json) {
79
+ // BR-AO-09: structured JSON output when --feature is used
80
+ if (filterFeature) {
81
+ const decisions = items.map((item) => ({
82
+ agent: item.source_agent || 'unknown',
83
+ signal: item.signal_type || null,
84
+ quote: Array.isArray(item.quotes) ? (item.quotes[item.quotes.length - 1] || null) : null,
85
+ proposal: item.proposal || item.body || item.title || null,
86
+ timestamp: item.last_reinforced || item.last_detected || null,
87
+ session_id: item.session_id || null
88
+ }));
89
+ const result = {
90
+ ok: true,
91
+ feature: filterFeature,
92
+ decisions,
93
+ total: decisions.length
94
+ };
95
+ if (options.json) return result;
96
+ if (logger) logger.log(JSON.stringify(result, null, 2));
97
+ return result;
98
+ }
99
+
69
100
  const result = {
70
101
  ok: true,
102
+ feature: null,
71
103
  identity: resolved.identity,
72
104
  identity_source: resolved.source,
73
105
  tier: showProposals ? 'proposals' : (includeArchived ? 'active+archive' : 'active'),
@@ -351,6 +351,10 @@ async function runSystemPublish({ args, options, logger, t }) {
351
351
 
352
352
  logger.log('Creating ZIP package...');
353
353
  const zipBuffer = await createZipBuffer(files);
354
+ const MAX_ZIP_BYTES = 2 * 1024 * 1024;
355
+ if (zipBuffer.length > MAX_ZIP_BYTES) {
356
+ throw new Error(`ZIP exceeds 2 MB limit (${(zipBuffer.length / 1024 / 1024).toFixed(2)} MB). Reduce the number of files or bundle size.`);
357
+ }
354
358
  const zipBase64 = zipBuffer.toString('base64');
355
359
  const zipKb = (zipBuffer.length / 1024).toFixed(1);
356
360
  logger.log(`ZIP: ${zipKb} KB (${fileCount} files)`);
@@ -6,9 +6,9 @@ const { TOOL_CAPS, getToolCapabilities, listSupportedTools } = require('../lib/t
6
6
  // so external clients (AIOSON Play, IDE extensions) can drive UI without
7
7
  // hard-coding their own copy of this lookup.
8
8
  //
9
- // Usage:
10
- // aioson tool:capabilities --json
11
- // aioson tool:capabilities --tool=claude --json
9
+ // Usage:
10
+ // aioson tool:capabilities --json
11
+ // aioson tool:capabilities --tool=claude --json
12
12
  async function runToolCapabilities({ args: _args, options = {}, logger, t: _t }) {
13
13
  const tool = options.tool ? String(options.tool).trim() : null;
14
14
 
@@ -22,8 +22,8 @@ async function runToolCapabilities({ args: _args, options = {}, logger, t: _t })
22
22
  payload = { tool: tool.toLowerCase(), capabilities: caps };
23
23
  } else {
24
24
  payload = {
25
- tools: TOOL_CAPS,
26
- schema_version: 1,
25
+ tools: TOOL_CAPS,
26
+ schema_version: 2,
27
27
  };
28
28
  }
29
29
 
@@ -37,8 +37,8 @@ async function runToolCapabilities({ args: _args, options = {}, logger, t: _t })
37
37
  const caps = payload.capabilities;
38
38
  logger.log(`Tool: ${payload.tool}`);
39
39
  logger.log(` binary: ${caps.binary}`);
40
- logger.log(` install_command: ${caps.install_command}`);
41
- logger.log(` supports_resume: ${caps.supports_resume}`);
40
+ logger.log(` install_command: ${caps.install_command}`);
41
+ logger.log(` supports_resume: ${caps.supports_resume}`);
42
42
  if (caps.supports_resume) {
43
43
  logger.log(` resume_last: ${(caps.resume_last || []).join(' ')}`);
44
44
  logger.log(` supports_session_id: ${caps.supports_session_id}`);
@@ -48,9 +48,13 @@ async function runToolCapabilities({ args: _args, options = {}, logger, t: _t })
48
48
  logger.log(` supports_session_picker: ${caps.supports_session_picker}`);
49
49
  if (caps.supports_session_picker) {
50
50
  logger.log(` session_picker: ${(caps.session_picker || []).join(' ')}`);
51
- }
52
- }
53
- } else {
51
+ }
52
+ }
53
+ logger.log(` supports_yolo: ${caps.supports_yolo}`);
54
+ if (caps.supports_yolo) {
55
+ logger.log(` yolo_args: ${(caps.yolo_args || []).join(' ')}`);
56
+ }
57
+ } else {
54
58
  logger.log(`Supported tools: ${listSupportedTools().join(', ')}`);
55
59
  logger.log(`Run with --tool=<name> for details, or --json for the full map.`);
56
60
  }
@@ -12,6 +12,7 @@
12
12
  */
13
13
 
14
14
  const path = require('node:path');
15
+ const fs = require('node:fs/promises');
15
16
  const {
16
17
  loadOrCreateState,
17
18
  activateStage,
@@ -26,6 +27,28 @@ const {
26
27
  incrementRetryCount,
27
28
  buildHealingActivation
28
29
  } = require('../self-healing');
30
+ const { CHECKPOINTS_DIR } = require('./gate-approve');
31
+
32
+ const GATE_ORDER = ['D', 'C', 'B', 'A'];
33
+
34
+ async function readLatestCheckpoint(targetDir, slug) {
35
+ const dir = path.join(targetDir, CHECKPOINTS_DIR);
36
+ try {
37
+ const files = await fs.readdir(dir);
38
+ const matching = files
39
+ .filter((f) => f.endsWith(`-${slug}.json`))
40
+ .sort((a, b) => {
41
+ const gateA = a.replace('gate-', '').charAt(0);
42
+ const gateB = b.replace('gate-', '').charAt(0);
43
+ return GATE_ORDER.indexOf(gateA) - GATE_ORDER.indexOf(gateB);
44
+ });
45
+ if (matching.length === 0) return null;
46
+ const raw = await fs.readFile(path.join(dir, matching[0]), 'utf8');
47
+ return JSON.parse(raw);
48
+ } catch {
49
+ return null;
50
+ }
51
+ }
29
52
 
30
53
  async function runWorkflowHeal({ args, options, logger, t }) {
31
54
  const targetDir = path.resolve(process.cwd(), args[0] || '.');
@@ -58,6 +81,10 @@ async function runWorkflowHeal({ args, options, logger, t }) {
58
81
  return { ok: false, reason: 'stage_not_in_sequence' };
59
82
  }
60
83
 
84
+ // M1 checkpoint-at-gate: read latest checkpoint for recovery context (EC-AO-02: graceful fallback)
85
+ const featureSlug = state.featureSlug || null;
86
+ const checkpoint = featureSlug ? await readLatestCheckpoint(targetDir, featureSlug) : null;
87
+
61
88
  // Build healing activation
62
89
  let activation;
63
90
  try {
@@ -74,6 +101,24 @@ async function runWorkflowHeal({ args, options, logger, t }) {
74
101
  return { ok: false, reason: 'activation_failed', error: err.message };
75
102
  }
76
103
 
104
+ if (checkpoint) {
105
+ const sanitize = (s) => String(s || '').replace(/[\r\n]+/g, ' ').slice(0, 200);
106
+ const files = Array.isArray(checkpoint.prerequisites_snapshot)
107
+ ? checkpoint.prerequisites_snapshot.map((s) => sanitize(s && s.file)).join(', ') || 'none'
108
+ : 'none';
109
+ const cpBlock = [
110
+ '',
111
+ '## Checkpoint Recovery Context (auto-injected by AIOSON motor)',
112
+ '',
113
+ `> Last approved gate: **${sanitize(checkpoint.gate)}** (${sanitize(checkpoint.timestamp)})`,
114
+ `> Approved by: ${sanitize(checkpoint.agent)}`,
115
+ `> Artifacts at gate time: ${files}`,
116
+ ''
117
+ ].join('\n');
118
+ activation.prompt = activation.prompt + cpBlock;
119
+ activation.checkpoint = checkpoint;
120
+ }
121
+
77
122
  // Increment retry counter
78
123
  const newCount = await incrementRetryCount(targetDir, stage, activation.prompt.substring(0, 200));
79
124
 
@@ -127,10 +172,11 @@ async function runWorkflowHeal({ args, options, logger, t }) {
127
172
  retryCount: newCount,
128
173
  maxRetries: 3,
129
174
  runtime,
175
+ checkpoint: checkpoint || null,
130
176
  agent: activation.agent,
131
177
  instructionPath: activation.instructionPath,
132
178
  prompt: activation.prompt
133
179
  };
134
180
  }
135
181
 
136
- module.exports = { runWorkflowHeal };
182
+ module.exports = { runWorkflowHeal, readLatestCheckpoint };
package/src/constants.js CHANGED
@@ -190,6 +190,7 @@ const AGENT_DEFINITIONS = [
190
190
  {
191
191
  id: 'setup',
192
192
  displayName: 'Setup',
193
+ description: 'Project onboarding and context setup',
193
194
  command: '@setup',
194
195
  path: '.aioson/agents/setup.md',
195
196
  dependsOn: [],
@@ -198,6 +199,7 @@ const AGENT_DEFINITIONS = [
198
199
  {
199
200
  id: 'discovery-design-doc',
200
201
  displayName: 'Discovery/Design Doc',
202
+ description: 'Discovery and design doc generation',
201
203
  command: '@discovery-design-doc',
202
204
  path: '.aioson/agents/discovery-design-doc.md',
203
205
  dependsOn: ['.aioson/context/project.context.md'],
@@ -206,6 +208,7 @@ const AGENT_DEFINITIONS = [
206
208
  {
207
209
  id: 'discover',
208
210
  displayName: 'Discover',
211
+ description: 'Semantic knowledge discovery and bootstrap cache generation',
209
212
  command: '@discover',
210
213
  path: '.aioson/agents/discover.md',
211
214
  dependsOn: ['.aioson/context/project.context.md'],
@@ -214,6 +217,7 @@ const AGENT_DEFINITIONS = [
214
217
  {
215
218
  id: 'product',
216
219
  displayName: 'Product',
220
+ description: 'Product vision, PRD and feature scoping',
217
221
  command: '@product',
218
222
  path: '.aioson/agents/product.md',
219
223
  dependsOn: ['.aioson/context/project.context.md'],
@@ -222,6 +226,7 @@ const AGENT_DEFINITIONS = [
222
226
  {
223
227
  id: 'deyvin',
224
228
  displayName: 'Deyvin',
229
+ description: 'Pair programming partner for continuity sessions',
225
230
  command: '@deyvin',
226
231
  path: '.aioson/agents/deyvin.md',
227
232
  aliases: ['pair'],
@@ -231,6 +236,7 @@ const AGENT_DEFINITIONS = [
231
236
  {
232
237
  id: 'analyst',
233
238
  displayName: 'Analyst',
239
+ description: 'Domain discovery and entity mapping (SMALL/MEDIUM)',
234
240
  command: '@analyst',
235
241
  path: '.aioson/agents/analyst.md',
236
242
  dependsOn: ['.aioson/context/project.context.md'],
@@ -239,6 +245,7 @@ const AGENT_DEFINITIONS = [
239
245
  {
240
246
  id: 'architect',
241
247
  displayName: 'Architect',
248
+ description: 'Project structure and technical decisions (SMALL/MEDIUM)',
242
249
  command: '@architect',
243
250
  path: '.aioson/agents/architect.md',
244
251
  dependsOn: [
@@ -250,6 +257,7 @@ const AGENT_DEFINITIONS = [
250
257
  {
251
258
  id: 'ux-ui',
252
259
  displayName: 'UI/UX',
260
+ description: 'UI/UX design system and component spec (SMALL/MEDIUM)',
253
261
  command: '@ux-ui',
254
262
  path: '.aioson/agents/ux-ui.md',
255
263
  dependsOn: [
@@ -263,6 +271,7 @@ const AGENT_DEFINITIONS = [
263
271
  {
264
272
  id: 'pm',
265
273
  displayName: 'PM',
274
+ description: 'Backlog and user stories (MEDIUM only)',
266
275
  command: '@pm',
267
276
  path: '.aioson/agents/pm.md',
268
277
  dependsOn: [
@@ -277,6 +286,7 @@ const AGENT_DEFINITIONS = [
277
286
  {
278
287
  id: 'dev',
279
288
  displayName: 'Dev',
289
+ description: 'Feature implementation (any stack)',
280
290
  command: '@dev',
281
291
  path: '.aioson/agents/dev.md',
282
292
  dependsOn: [
@@ -289,8 +299,14 @@ const AGENT_DEFINITIONS = [
289
299
  {
290
300
  id: 'pentester',
291
301
  displayName: 'Pentester',
302
+ description: 'Adversarial security review and threat-surface mapping',
292
303
  command: '@pentester',
293
304
  path: '.aioson/agents/pentester.md',
305
+ flags: [
306
+ { name: 'mode', value: 'framework_target|app_target', description: 'Target mode for security review' },
307
+ { name: 'feature', value: '<slug>', description: 'Feature slug (required for app_target)' },
308
+ { name: 'scope', value: '<scope>', description: 'Target scope (required for app_target)' }
309
+ ],
294
310
  dependsOn: [
295
311
  '.aioson/context/project.context.md',
296
312
  '.aioson/context/spec-{slug}.md (active feature)'
@@ -300,6 +316,7 @@ const AGENT_DEFINITIONS = [
300
316
  {
301
317
  id: 'qa',
302
318
  displayName: 'QA',
319
+ description: 'Risk-first review and test generation (SMALL/MEDIUM)',
303
320
  command: '@qa',
304
321
  path: '.aioson/agents/qa.md',
305
322
  dependsOn: ['.aioson/context/discovery.md'],
@@ -308,6 +325,7 @@ const AGENT_DEFINITIONS = [
308
325
  {
309
326
  id: 'validator',
310
327
  displayName: 'Validator',
328
+ description: 'Technical validation against success contract',
311
329
  command: '@validator',
312
330
  path: '.aioson/agents/validator.md',
313
331
  dependsOn: [
@@ -319,6 +337,7 @@ const AGENT_DEFINITIONS = [
319
337
  {
320
338
  id: 'tester',
321
339
  displayName: 'Tester',
340
+ description: 'Systematic test engineering for implemented apps (all sizes)',
322
341
  command: '@tester',
323
342
  path: '.aioson/agents/tester.md',
324
343
  dependsOn: ['.aioson/context/project.context.md'],
@@ -327,6 +346,7 @@ const AGENT_DEFINITIONS = [
327
346
  {
328
347
  id: 'orchestrator',
329
348
  displayName: 'Orchestrator',
349
+ description: 'Session protocol and parallel execution (MEDIUM)',
330
350
  command: '@orchestrator',
331
351
  path: '.aioson/agents/orchestrator.md',
332
352
  dependsOn: [
@@ -342,6 +362,7 @@ const AGENT_DEFINITIONS = [
342
362
  {
343
363
  id: 'squad',
344
364
  displayName: 'Squad',
365
+ description: 'Squad assembly and management',
345
366
  command: '@squad',
346
367
  path: '.aioson/agents/squad.md',
347
368
  dependsOn: [],
@@ -351,6 +372,7 @@ const AGENT_DEFINITIONS = [
351
372
  {
352
373
  id: 'orache',
353
374
  displayName: 'Orache',
375
+ description: 'Domain investigation and strategic research',
354
376
  command: '@orache',
355
377
  path: '.aioson/agents/orache.md',
356
378
  dependsOn: [],
@@ -359,6 +381,7 @@ const AGENT_DEFINITIONS = [
359
381
  {
360
382
  id: 'genome',
361
383
  displayName: 'Genome',
384
+ description: 'Domain genome creation and application',
362
385
  command: '@genome',
363
386
  path: '.aioson/agents/genome.md',
364
387
  dependsOn: [],
@@ -367,6 +390,7 @@ const AGENT_DEFINITIONS = [
367
390
  {
368
391
  id: 'design-hybrid-forge',
369
392
  displayName: 'Design Hybrid Forge',
393
+ description: 'Generate hybrid design skill from two visual parents',
370
394
  command: '@design-hybrid-forge',
371
395
  path: '.aioson/agents/design-hybrid-forge.md',
372
396
  dependsOn: ['.aioson/context/project.context.md'],
@@ -375,14 +399,61 @@ const AGENT_DEFINITIONS = [
375
399
  {
376
400
  id: 'site-forge',
377
401
  displayName: 'Site Forge',
402
+ description: 'Clone, extract, and forge sites and design skills from any URL',
378
403
  command: '@site-forge',
379
404
  path: '.aioson/agents/site-forge.md',
380
405
  dependsOn: ['.aioson/context/project.context.md'],
381
406
  output: 'src/components/*.tsx + src/app/page.tsx + docs/research/{hostname}/ + public/images/{hostname}/'
382
407
  },
408
+ {
409
+ id: 'neo',
410
+ displayName: 'Neo',
411
+ description: 'System router: see the full picture, get guided to the right agent',
412
+ command: '@neo',
413
+ path: '.aioson/agents/neo.md',
414
+ dependsOn: ['.aioson/context/project.context.md'],
415
+ output: 'routing decision + agent handoff'
416
+ },
417
+ {
418
+ id: 'sheldon',
419
+ displayName: 'Sheldon',
420
+ description: 'Deep technical analysis and architecture review',
421
+ command: '@sheldon',
422
+ path: '.aioson/agents/sheldon.md',
423
+ dependsOn: ['.aioson/context/project.context.md'],
424
+ output: 'enriched PRD or architecture review'
425
+ },
426
+ {
427
+ id: 'committer',
428
+ displayName: 'Committer',
429
+ description: 'Professional Git commit generation from changes and context',
430
+ command: '@committer',
431
+ path: '.aioson/agents/committer.md',
432
+ dependsOn: [],
433
+ output: 'git commit(s)'
434
+ },
435
+ {
436
+ id: 'copywriter',
437
+ displayName: 'Copywriter',
438
+ description: 'Conversion-focused marketing copy',
439
+ command: '@copywriter',
440
+ path: '.aioson/agents/copywriter.md',
441
+ dependsOn: [],
442
+ output: 'marketing copy + content assets'
443
+ },
444
+ {
445
+ id: 'briefing',
446
+ displayName: 'Briefing',
447
+ description: 'Pre-production briefings and planning',
448
+ command: '@briefing',
449
+ path: '.aioson/agents/briefing.md',
450
+ dependsOn: ['.aioson/context/project.context.md'],
451
+ output: '.aioson/briefings/{slug}/'
452
+ },
383
453
  {
384
454
  id: 'profiler-researcher',
385
455
  displayName: 'Profiler Researcher',
456
+ description: 'Clone profiler: research phase',
386
457
  command: '@profiler-researcher',
387
458
  path: '.aioson/agents/profiler-researcher.md',
388
459
  dependsOn: [],
@@ -391,6 +462,7 @@ const AGENT_DEFINITIONS = [
391
462
  {
392
463
  id: 'profiler-enricher',
393
464
  displayName: 'Profiler Enricher',
465
+ description: 'Clone profiler: enrichment phase',
394
466
  command: '@profiler-enricher',
395
467
  path: '.aioson/agents/profiler-enricher.md',
396
468
  dependsOn: ['.aioson/profiler-reports/{person-slug}/research-report.md'],
@@ -399,6 +471,7 @@ const AGENT_DEFINITIONS = [
399
471
  {
400
472
  id: 'profiler-forge',
401
473
  displayName: 'Profiler Forge',
474
+ description: 'Clone profiler: forge and validate',
402
475
  command: '@profiler-forge',
403
476
  path: '.aioson/agents/profiler-forge.md',
404
477
  dependsOn: ['.aioson/profiler-reports/{person-slug}/enriched-profile.md'],