@jaimevalasek/aioson 1.21.8 → 1.22.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 +18 -4
- package/package.json +1 -1
- package/src/agents.js +21 -20
- package/src/cli.js +15 -0
- package/src/commands/feature-close.js +40 -0
- package/src/commands/gate-check.js +8 -3
- package/src/commands/git-guard.js +58 -0
- package/src/commands/harness-gate.js +120 -0
- package/src/commands/harness-status.js +157 -0
- package/src/commands/harness.js +18 -1
- package/src/commands/self-implement-loop.js +305 -5
- package/src/commands/workflow-next.js +37 -2
- package/src/doctor.js +24 -8
- package/src/harness/active-contract.js +41 -0
- package/src/harness/attempt-artifacts.js +95 -0
- package/src/harness/budget-guard.js +127 -0
- package/src/harness/circuit-breaker.js +7 -0
- package/src/harness/contract-schema.js +324 -0
- package/src/harness/criteria-runner.js +136 -0
- package/src/harness/git-baseline.js +204 -0
- package/src/harness/glob-match.js +126 -0
- package/src/harness/guard-events.js +71 -0
- package/src/harness/human-gate.js +182 -0
- package/src/harness/scope-guard.js +115 -0
- package/src/i18n/messages/en.js +2 -0
- package/src/i18n/messages/es.js +11 -9
- package/src/i18n/messages/fr.js +11 -9
- package/src/i18n/messages/pt-BR.js +2 -0
- package/src/lib/dev-resume.js +94 -45
- package/src/preflight-engine.js +88 -84
- package/template/.aioson/agents/analyst.md +4 -0
- package/template/.aioson/agents/architect.md +4 -0
- package/template/.aioson/agents/dev.md +3 -1
- package/template/.aioson/agents/discovery-design-doc.md +4 -0
- package/template/.aioson/agents/pm.md +10 -5
- package/template/.aioson/agents/qa.md +22 -14
- package/template/.aioson/agents/scope-check.md +176 -172
- package/template/.aioson/config.md +31 -28
- package/template/.aioson/docs/autopilot-handoff.md +46 -0
- package/template/AGENTS.md +57 -57
- package/template/CLAUDE.md +33 -33
package/src/i18n/messages/en.js
CHANGED
|
@@ -450,6 +450,8 @@ module.exports = {
|
|
|
450
450
|
bootstrap_coverage_hint_seed: 'Run /discover to seed .aioson/context/bootstrap/{what-is,how-it-works,what-it-does,current-state}.md',
|
|
451
451
|
features_dir_present: 'Features directory present (.aioson/context/features/)',
|
|
452
452
|
features_dir_present_hint: 'Create .aioson/context/features/ to host per-feature dossiers (doctor --fix will create it).',
|
|
453
|
+
auto_handoff_declared: 'Autopilot handoff flag declared (auto_handoff in project.context.md)',
|
|
454
|
+
auto_handoff_declared_hint: 'The autopilot-handoff protocol is installed but auto_handoff is not set in project.context.md frontmatter — autopilot stays inactive. Set auto_handoff: true to enable it, or auto_handoff: false to silence this warning.',
|
|
453
455
|
claude_commands_present: 'Claude slash commands present ({missing} missing of {required})',
|
|
454
456
|
claude_commands_present_hint: 'Missing: {paths}. Run `aioson doctor . --fix` to restore them from the template.',
|
|
455
457
|
version_drift: 'CLI version matches project.context.md (context: {context}, CLI: {cli})',
|
package/src/i18n/messages/es.js
CHANGED
|
@@ -160,12 +160,12 @@ module.exports = {
|
|
|
160
160
|
'aioson squad:pipeline [path] [--sub=list|show|status] [--pipeline=<slug>] [--locale=es]',
|
|
161
161
|
help_squad_investigate:
|
|
162
162
|
'aioson squad:investigate [path] [--sub=list|show|score|link|register] [--investigation=<slug>] [--squad=<slug>] [--locale=es]',
|
|
163
|
-
help_squad_learning:
|
|
164
|
-
'aioson squad:learning [path] [--sub=list|stats|archive|promote|export] [--squad=<slug>] [--status=<status>] [--locale=es]',
|
|
165
|
-
help_quality_audit:
|
|
166
|
-
'aioson quality:audit [path] [--feature=<slug>] [--provider-output=<path>] [--baseline=<path>] [--changed=<path[,path]>] [--json] [--locale=es]',
|
|
167
|
-
help_squad_dashboard:
|
|
168
|
-
'aioson squad:dashboard [path] [--port=4180] [--squad=<slug>] [--locale=es]',
|
|
163
|
+
help_squad_learning:
|
|
164
|
+
'aioson squad:learning [path] [--sub=list|stats|archive|promote|export] [--squad=<slug>] [--status=<status>] [--locale=es]',
|
|
165
|
+
help_quality_audit:
|
|
166
|
+
'aioson quality:audit [path] [--feature=<slug>] [--provider-output=<path>] [--baseline=<path>] [--changed=<path[,path]>] [--json] [--locale=es]',
|
|
167
|
+
help_squad_dashboard:
|
|
168
|
+
'aioson squad:dashboard [path] [--port=4180] [--squad=<slug>] [--locale=es]',
|
|
169
169
|
help_squad_worker:
|
|
170
170
|
'aioson squad:worker [path] [--sub=list|run|test|logs|scaffold] [--squad=<slug>] [--worker=<slug>] [--input=<json>] [--locale=es]',
|
|
171
171
|
help_squad_daemon:
|
|
@@ -179,7 +179,7 @@ module.exports = {
|
|
|
179
179
|
help_commit_prepare:
|
|
180
180
|
'aioson commit:prepare [path] [--staged-only] [--agent-safe] [--mode=guarded|trusted|headless] [--json] [--locale=es]',
|
|
181
181
|
help_learning:
|
|
182
|
-
'aioson learning [path] [--sub=list|stats|promote|import-from-claude] [--status=<status>] [--id=<learning-id>] [--project-hash=<hash>] [--dry-run] [--select=<n[,n]|all>] [--locale=es]',
|
|
182
|
+
'aioson learning [path] [--sub=list|stats|promote|import-from-claude] [--status=<status>] [--id=<learning-id>] [--project-hash=<hash>] [--dry-run] [--select=<n[,n]|all>] [--locale=es]',
|
|
183
183
|
dashboard_moved:
|
|
184
184
|
'El flujo `{command}` fue eliminado del CLI. El dashboard de AIOSON ahora se instala por separado. Abre la app del dashboard en tu computadora, crea o agrega un proyecto y selecciona la carpeta que ya contiene `.aioson/`.',
|
|
185
185
|
dashboard_moved_line: '{message}\n',
|
|
@@ -324,6 +324,8 @@ module.exports = {
|
|
|
324
324
|
bootstrap_coverage_hint_seed: 'Ejecute /discover para sembrar .aioson/context/bootstrap/{what-is,how-it-works,what-it-does,current-state}.md',
|
|
325
325
|
features_dir_present: 'Directorio de features presente (.aioson/context/features/)',
|
|
326
326
|
features_dir_present_hint: 'Cree .aioson/context/features/ para hospedar dossiers por feature (doctor --fix lo crea).',
|
|
327
|
+
auto_handoff_declared: 'Flag de autopilot handoff declarada (auto_handoff en project.context.md)',
|
|
328
|
+
auto_handoff_declared_hint: 'El protocolo autopilot-handoff esta instalado pero auto_handoff no esta definido en el frontmatter de project.context.md — el autopilot queda inactivo. Defina auto_handoff: true para activarlo, o auto_handoff: false para silenciar este aviso.',
|
|
327
329
|
claude_commands_present: 'Slash commands de Claude presentes ({missing} ausentes de {required})',
|
|
328
330
|
claude_commands_present_hint: 'Ausentes: {paths}. Ejecute `aioson doctor . --fix` para restaurarlos.',
|
|
329
331
|
version_drift: 'Version del CLI coincide con project.context.md (contexto: {context}, CLI: {cli})',
|
|
@@ -609,7 +611,7 @@ module.exports = {
|
|
|
609
611
|
note_product_optional:
|
|
610
612
|
'@product es opcional para MICRO — omitelo y ve directo a @dev si la idea ya esta clara.',
|
|
611
613
|
note_feature_flow:
|
|
612
|
-
'Flujo para nueva feature (tras la configuracion inicial): @product → @analyst → @scope-check → @dev → @qa. Sin @setup.'
|
|
614
|
+
'Flujo para nueva feature (tras la configuracion inicial): @product → @analyst → @scope-check → @dev → @qa. Sin @setup.'
|
|
613
615
|
},
|
|
614
616
|
parallel_init: {
|
|
615
617
|
context_missing:
|
|
@@ -1020,7 +1022,7 @@ module.exports = {
|
|
|
1020
1022
|
folder_required_example_prompt:
|
|
1021
1023
|
' Prompt listo : aioson agent:prompt analyst --tool=codex',
|
|
1022
1024
|
folder_required_example_next:
|
|
1023
|
-
' Flujo tras escaneo completo: @analyst -> @scope-check -> @architect -> @dev',
|
|
1025
|
+
' Flujo tras escaneo completo: @analyst -> @scope-check -> @architect -> @dev',
|
|
1024
1026
|
folder_not_found: 'La carpeta "{folder}" no existe en este proyecto. Directorios de nivel superior detectados: {available}',
|
|
1025
1027
|
config_missing: '{file} no encontrado. Para usar el modo con LLM, copia aioson-models.json y completa tus claves de API.',
|
|
1026
1028
|
config_invalid: 'JSON invalido en aioson-models.json: {error}',
|
package/src/i18n/messages/fr.js
CHANGED
|
@@ -160,12 +160,12 @@ module.exports = {
|
|
|
160
160
|
'aioson squad:pipeline [path] [--sub=list|show|status] [--pipeline=<slug>] [--locale=fr]',
|
|
161
161
|
help_squad_investigate:
|
|
162
162
|
'aioson squad:investigate [path] [--sub=list|show|score|link|register] [--investigation=<slug>] [--squad=<slug>] [--locale=fr]',
|
|
163
|
-
help_squad_learning:
|
|
164
|
-
'aioson squad:learning [path] [--sub=list|stats|archive|promote|export] [--squad=<slug>] [--status=<status>] [--locale=fr]',
|
|
165
|
-
help_quality_audit:
|
|
166
|
-
'aioson quality:audit [path] [--feature=<slug>] [--provider-output=<path>] [--baseline=<path>] [--changed=<path[,path]>] [--json] [--locale=fr]',
|
|
167
|
-
help_squad_dashboard:
|
|
168
|
-
'aioson squad:dashboard [path] [--port=4180] [--squad=<slug>] [--locale=fr]',
|
|
163
|
+
help_squad_learning:
|
|
164
|
+
'aioson squad:learning [path] [--sub=list|stats|archive|promote|export] [--squad=<slug>] [--status=<status>] [--locale=fr]',
|
|
165
|
+
help_quality_audit:
|
|
166
|
+
'aioson quality:audit [path] [--feature=<slug>] [--provider-output=<path>] [--baseline=<path>] [--changed=<path[,path]>] [--json] [--locale=fr]',
|
|
167
|
+
help_squad_dashboard:
|
|
168
|
+
'aioson squad:dashboard [path] [--port=4180] [--squad=<slug>] [--locale=fr]',
|
|
169
169
|
help_squad_worker:
|
|
170
170
|
'aioson squad:worker [path] [--sub=list|run|test|logs|scaffold] [--squad=<slug>] [--worker=<slug>] [--input=<json>] [--locale=fr]',
|
|
171
171
|
help_squad_daemon:
|
|
@@ -179,7 +179,7 @@ module.exports = {
|
|
|
179
179
|
help_commit_prepare:
|
|
180
180
|
'aioson commit:prepare [path] [--staged-only] [--agent-safe] [--mode=guarded|trusted|headless] [--json] [--locale=fr]',
|
|
181
181
|
help_learning:
|
|
182
|
-
'aioson learning [path] [--sub=list|stats|promote|import-from-claude] [--status=<status>] [--id=<learning-id>] [--project-hash=<hash>] [--dry-run] [--select=<n[,n]|all>] [--locale=fr]',
|
|
182
|
+
'aioson learning [path] [--sub=list|stats|promote|import-from-claude] [--status=<status>] [--id=<learning-id>] [--project-hash=<hash>] [--dry-run] [--select=<n[,n]|all>] [--locale=fr]',
|
|
183
183
|
dashboard_moved:
|
|
184
184
|
'Le flux `{command}` a été supprimé du CLI. Le dashboard AIOSON est désormais installé séparément. Ouvrez l application dashboard sur votre ordinateur, créez ou ajoutez un projet, puis sélectionnez le dossier qui contient déjà `.aioson/`.',
|
|
185
185
|
dashboard_moved_line: '{message}\n',
|
|
@@ -323,6 +323,8 @@ module.exports = {
|
|
|
323
323
|
bootstrap_coverage_hint_seed: 'Executez /discover pour creer .aioson/context/bootstrap/{what-is,how-it-works,what-it-does,current-state}.md',
|
|
324
324
|
features_dir_present: 'Repertoire features present (.aioson/context/features/)',
|
|
325
325
|
features_dir_present_hint: 'Creez .aioson/context/features/ pour heberger les dossiers par feature (doctor --fix le cree).',
|
|
326
|
+
auto_handoff_declared: 'Flag autopilot handoff declare (auto_handoff dans project.context.md)',
|
|
327
|
+
auto_handoff_declared_hint: 'Le protocole autopilot-handoff est installe mais auto_handoff n\'est pas defini dans le frontmatter de project.context.md — l\'autopilot reste inactif. Definissez auto_handoff: true pour l\'activer, ou auto_handoff: false pour faire taire cet avertissement.',
|
|
326
328
|
claude_commands_present: 'Slash commands de Claude presents ({missing} absents sur {required})',
|
|
327
329
|
claude_commands_present_hint: 'Absents : {paths}. Executez `aioson doctor . --fix` pour les restaurer.',
|
|
328
330
|
version_drift: 'Version du CLI conforme a project.context.md (contexte : {context}, CLI : {cli})',
|
|
@@ -612,7 +614,7 @@ module.exports = {
|
|
|
612
614
|
note_product_optional:
|
|
613
615
|
'@product est optionnel pour MICRO — passez directement a @dev si l idee est deja claire.',
|
|
614
616
|
note_feature_flow:
|
|
615
|
-
'Flux nouvelle feature (apres configuration initiale) : @product → @analyst → @scope-check → @dev → @qa. Pas de @setup.'
|
|
617
|
+
'Flux nouvelle feature (apres configuration initiale) : @product → @analyst → @scope-check → @dev → @qa. Pas de @setup.'
|
|
616
618
|
},
|
|
617
619
|
parallel_init: {
|
|
618
620
|
context_missing:
|
|
@@ -1028,7 +1030,7 @@ module.exports = {
|
|
|
1028
1030
|
folder_required_example_prompt:
|
|
1029
1031
|
' Prompt pret : aioson agent:prompt analyst --tool=codex',
|
|
1030
1032
|
folder_required_example_next:
|
|
1031
|
-
' Workflow apres scan complet : @analyst -> @scope-check -> @architect -> @dev',
|
|
1033
|
+
' Workflow apres scan complet : @analyst -> @scope-check -> @architect -> @dev',
|
|
1032
1034
|
folder_not_found: 'Le dossier "{folder}" est introuvable dans ce projet. Dossiers de premier niveau detectes : {available}',
|
|
1033
1035
|
config_missing: '{file} introuvable. Pour utiliser le mode LLM, copiez aioson-models.json et renseignez vos cles API.',
|
|
1034
1036
|
config_invalid: 'JSON invalide dans aioson-models.json : {error}',
|
|
@@ -424,6 +424,8 @@ module.exports = {
|
|
|
424
424
|
bootstrap_coverage_hint_seed: 'Rode /discover para criar .aioson/context/bootstrap/{what-is,how-it-works,what-it-does,current-state}.md',
|
|
425
425
|
features_dir_present: 'Diretorio de features presente (.aioson/context/features/)',
|
|
426
426
|
features_dir_present_hint: 'Crie .aioson/context/features/ para hospedar dossies por feature (doctor --fix cria automaticamente).',
|
|
427
|
+
auto_handoff_declared: 'Flag de autopilot handoff declarada (auto_handoff no project.context.md)',
|
|
428
|
+
auto_handoff_declared_hint: 'O protocolo autopilot-handoff esta instalado mas auto_handoff nao esta definido no frontmatter do project.context.md — o autopilot fica inativo. Defina auto_handoff: true para ativar, ou auto_handoff: false para silenciar este aviso.',
|
|
427
429
|
claude_commands_present: 'Slash commands do Claude presentes ({missing} ausentes de {required})',
|
|
428
430
|
claude_commands_present_hint: 'Ausentes: {paths}. Rode `aioson doctor . --fix` para restaurar a partir do template.',
|
|
429
431
|
version_drift: 'Versao do CLI bate com project.context.md (contexto: {context}, CLI: {cli})',
|
package/src/lib/dev-resume.js
CHANGED
|
@@ -49,34 +49,34 @@ function readClassificationFromFrontmatters(rawList) {
|
|
|
49
49
|
return null;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
function extractDevStateFields(raw) {
|
|
53
|
-
if (!raw) return { active_feature: null, active_phase: null, next_step: null, status: null };
|
|
54
|
-
const fmMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
55
|
-
if (!fmMatch) return { active_feature: null, active_phase: null, next_step: null, status: null };
|
|
56
|
-
const fm = {};
|
|
57
|
-
for (const line of fmMatch[1].split(/\r?\n/)) {
|
|
52
|
+
function extractDevStateFields(raw) {
|
|
53
|
+
if (!raw) return { active_feature: null, active_phase: null, next_step: null, status: null };
|
|
54
|
+
const fmMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
55
|
+
if (!fmMatch) return { active_feature: null, active_phase: null, next_step: null, status: null };
|
|
56
|
+
const fm = {};
|
|
57
|
+
for (const line of fmMatch[1].split(/\r?\n/)) {
|
|
58
58
|
const idx = line.indexOf(':');
|
|
59
59
|
if (idx === -1) continue;
|
|
60
60
|
const key = line.slice(0, idx).trim();
|
|
61
61
|
const val = line.slice(idx + 1).trim().replace(/^["']|["']$/g, '');
|
|
62
62
|
fm[key] = val;
|
|
63
|
-
}
|
|
64
|
-
return {
|
|
65
|
-
active_feature: fm.active_feature || null,
|
|
66
|
-
active_phase: fm.active_phase || null,
|
|
67
|
-
next_step: fm.next_step || null,
|
|
68
|
-
status: fm.status || null
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function isCurrentDevStateForFeature(fields, featureSlug) {
|
|
73
|
-
if (!fields) return false;
|
|
74
|
-
if (!fields.active_feature) return false;
|
|
75
|
-
const status = String(fields.status || '').toLowerCase();
|
|
76
|
-
if (status === 'done' || status === 'abandoned') return false;
|
|
77
|
-
if (fields.active_feature !== featureSlug) return false;
|
|
78
|
-
return true;
|
|
79
|
-
}
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
active_feature: fm.active_feature || null,
|
|
66
|
+
active_phase: fm.active_phase || null,
|
|
67
|
+
next_step: fm.next_step || null,
|
|
68
|
+
status: fm.status || null
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isCurrentDevStateForFeature(fields, featureSlug) {
|
|
73
|
+
if (!fields) return false;
|
|
74
|
+
if (!fields.active_feature) return false;
|
|
75
|
+
const status = String(fields.status || '').toLowerCase();
|
|
76
|
+
if (status === 'done' || status === 'abandoned') return false;
|
|
77
|
+
if (fields.active_feature !== featureSlug) return false;
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
80
|
|
|
81
81
|
function extractCodeMapPaths(dossierRaw) {
|
|
82
82
|
if (!dossierRaw) return [];
|
|
@@ -91,6 +91,42 @@ function extractCodeMapPaths(dossierRaw) {
|
|
|
91
91
|
return Array.from(new Set(paths));
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
function readCorrectionsStatus(raw) {
|
|
95
|
+
const fmMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
96
|
+
// No frontmatter or no status field: treat as open — a corrections plan
|
|
97
|
+
// must never be silently skipped because of a malformed header.
|
|
98
|
+
if (!fmMatch) return 'open';
|
|
99
|
+
for (const line of fmMatch[1].split(/\r?\n/)) {
|
|
100
|
+
const m = line.match(/^status:\s*([^#]*)/);
|
|
101
|
+
if (m) {
|
|
102
|
+
const val = m[1].trim().replace(/^["']|["']$/g, '').toLowerCase();
|
|
103
|
+
return val || 'open';
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return 'open';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function listOpenCorrections(targetDir, featureSlug) {
|
|
110
|
+
const planDir = path.join(targetDir, '.aioson', 'plans', featureSlug);
|
|
111
|
+
let entries;
|
|
112
|
+
try {
|
|
113
|
+
entries = await fs.readdir(planDir);
|
|
114
|
+
} catch (err) {
|
|
115
|
+
if (err && err.code === 'ENOENT') return [];
|
|
116
|
+
throw err;
|
|
117
|
+
}
|
|
118
|
+
const out = [];
|
|
119
|
+
for (const name of entries.filter((n) => /^corrections-.+\.md$/.test(n)).sort()) {
|
|
120
|
+
const raw = await readFileOrNull(path.join(planDir, name));
|
|
121
|
+
if (!raw) continue;
|
|
122
|
+
const status = readCorrectionsStatus(raw);
|
|
123
|
+
if (status === 'open' || status === 'in_progress') {
|
|
124
|
+
out.push(`.aioson/plans/${featureSlug}/${name}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
|
|
94
130
|
function deriveNextStepFromPlan(planRaw) {
|
|
95
131
|
if (!planRaw) return null;
|
|
96
132
|
const lines = planRaw.split('\n');
|
|
@@ -121,12 +157,12 @@ async function buildDevResumeData(projectPath) {
|
|
|
121
157
|
const specRaw = await readFileOrNull(path.join(ctxDir, `spec-${featureSlug}.md`));
|
|
122
158
|
const classification = readClassificationFromFrontmatters([dossierRaw, prdRaw, specRaw]);
|
|
123
159
|
|
|
124
|
-
const devStateRaw = await readFileOrNull(path.join(ctxDir, 'dev-state.md'));
|
|
125
|
-
const devStateFields = extractDevStateFields(devStateRaw);
|
|
126
|
-
const useDevState = isCurrentDevStateForFeature(devStateFields, featureSlug);
|
|
127
|
-
|
|
128
|
-
const planManifestPath = path.join(targetDir, '.aioson', 'plans', featureSlug, 'manifest.md');
|
|
129
|
-
const planRaw = await readFileOrNull(planManifestPath);
|
|
160
|
+
const devStateRaw = await readFileOrNull(path.join(ctxDir, 'dev-state.md'));
|
|
161
|
+
const devStateFields = extractDevStateFields(devStateRaw);
|
|
162
|
+
const useDevState = isCurrentDevStateForFeature(devStateFields, featureSlug);
|
|
163
|
+
|
|
164
|
+
const planManifestPath = path.join(targetDir, '.aioson', 'plans', featureSlug, 'manifest.md');
|
|
165
|
+
const planRaw = await readFileOrNull(planManifestPath);
|
|
130
166
|
const sheldonPlan = planRaw ? `.aioson/plans/${featureSlug}/manifest.md` : null;
|
|
131
167
|
|
|
132
168
|
const artifactsConsumed = Array.isArray(lastHandoff && lastHandoff.artifact_uris)
|
|
@@ -137,22 +173,35 @@ async function buildDevResumeData(projectPath) {
|
|
|
137
173
|
? lastHandoff.decision_rationale
|
|
138
174
|
: [];
|
|
139
175
|
|
|
176
|
+
// QA corrections plans with status open/in_progress take precedence over any
|
|
177
|
+
// persisted next_step: a stale dev-state pointer must not hide mandatory
|
|
178
|
+
// corrections from a fresh @dev session (loop-guardrails incident, 2026-06-09).
|
|
179
|
+
const openCorrections = await listOpenCorrections(targetDir, featureSlug);
|
|
180
|
+
const baseNextStep = useDevState && devStateFields.next_step
|
|
181
|
+
? devStateFields.next_step
|
|
182
|
+
: deriveNextStepFromPlan(planRaw);
|
|
183
|
+
|
|
140
184
|
return {
|
|
141
|
-
feature_slug: featureSlug,
|
|
142
|
-
classification,
|
|
143
|
-
current_phase: useDevState && devStateFields.active_phase ? devStateFields.active_phase : 'unknown',
|
|
144
|
-
artifacts_consumed: artifactsConsumed,
|
|
145
|
-
code_map_paths: extractCodeMapPaths(dossierRaw),
|
|
146
|
-
sheldon_plan: sheldonPlan,
|
|
147
|
-
next_step:
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
185
|
+
feature_slug: featureSlug,
|
|
186
|
+
classification,
|
|
187
|
+
current_phase: useDevState && devStateFields.active_phase ? devStateFields.active_phase : 'unknown',
|
|
188
|
+
artifacts_consumed: artifactsConsumed,
|
|
189
|
+
code_map_paths: extractCodeMapPaths(dossierRaw),
|
|
190
|
+
sheldon_plan: sheldonPlan,
|
|
191
|
+
next_step: openCorrections.length > 0
|
|
192
|
+
? `Apply mandatory corrections from ${openCorrections[0]}, then return to @qa for re-verification`
|
|
193
|
+
: baseNextStep,
|
|
194
|
+
open_corrections: openCorrections.length > 0 ? openCorrections : undefined,
|
|
195
|
+
decision_rationale: decisionRationale.length > 0 ? decisionRationale : undefined
|
|
196
|
+
};
|
|
197
|
+
}
|
|
151
198
|
|
|
152
199
|
module.exports = {
|
|
153
|
-
buildDevResumeData,
|
|
154
|
-
extractDevStateFields,
|
|
155
|
-
isCurrentDevStateForFeature,
|
|
156
|
-
extractCodeMapPaths,
|
|
157
|
-
deriveNextStepFromPlan
|
|
158
|
-
|
|
200
|
+
buildDevResumeData,
|
|
201
|
+
extractDevStateFields,
|
|
202
|
+
isCurrentDevStateForFeature,
|
|
203
|
+
extractCodeMapPaths,
|
|
204
|
+
deriveNextStepFromPlan,
|
|
205
|
+
readCorrectionsStatus,
|
|
206
|
+
listOpenCorrections
|
|
207
|
+
};
|
package/src/preflight-engine.js
CHANGED
|
@@ -225,9 +225,9 @@ async function scanActiveManifest(targetDir, slug) {
|
|
|
225
225
|
async function scanArtifacts(targetDir, slug) {
|
|
226
226
|
const dir = contextDir(targetDir);
|
|
227
227
|
|
|
228
|
-
async function check(name, filePath) {
|
|
229
|
-
const stat = await fileStat(filePath);
|
|
230
|
-
if (!stat) return { exists: false };
|
|
228
|
+
async function check(name, filePath) {
|
|
229
|
+
const stat = await fileStat(filePath);
|
|
230
|
+
if (!stat) return { exists: false };
|
|
231
231
|
|
|
232
232
|
const content = await readFileSafe(filePath);
|
|
233
233
|
const fm = content ? parseFrontmatter(content) : {};
|
|
@@ -237,37 +237,37 @@ async function scanArtifacts(targetDir, slug) {
|
|
|
237
237
|
path: path.relative(targetDir, filePath),
|
|
238
238
|
size: stat.size,
|
|
239
239
|
frontmatter: fm,
|
|
240
|
-
content
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
async function checkFirst(name, filePaths) {
|
|
245
|
-
for (const filePath of filePaths) {
|
|
246
|
-
const result = await check(name, filePath);
|
|
247
|
-
if (result.exists) return result;
|
|
248
|
-
}
|
|
249
|
-
return { exists: false };
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
const designDocCandidates = slug
|
|
253
|
-
? [path.join(dir, `design-doc-${slug}.md`), path.join(dir, 'design-doc.md')]
|
|
254
|
-
: [path.join(dir, 'design-doc.md')];
|
|
255
|
-
const readinessCandidates = slug
|
|
256
|
-
? [path.join(dir, `readiness-${slug}.md`), path.join(dir, 'readiness.md')]
|
|
257
|
-
: [path.join(dir, 'readiness.md')];
|
|
258
|
-
|
|
259
|
-
const results = {
|
|
260
|
-
project_context: await check('project.context', path.join(dir, 'project.context.md')),
|
|
261
|
-
prd: slug ? await check('prd', path.join(dir, `prd-${slug}.md`)) : { exists: false },
|
|
262
|
-
sheldon_enrichment: slug ? await check('sheldon', path.join(dir, `sheldon-enrichment-${slug}.md`)) : { exists: false },
|
|
263
|
-
requirements: slug ? await check('requirements', path.join(dir, `requirements-${slug}.md`)) : { exists: false },
|
|
264
|
-
spec: slug ? await check('spec', path.join(dir, `spec-${slug}.md`)) : await check('spec', path.join(dir, 'spec.md')),
|
|
265
|
-
architecture: await check('architecture', path.join(dir, 'architecture.md')),
|
|
266
|
-
design_doc: await checkFirst('design-doc', designDocCandidates),
|
|
267
|
-
readiness: await checkFirst('readiness', readinessCandidates),
|
|
268
|
-
implementation_plan: slug ? await check('impl-plan', path.join(dir, `implementation-plan-${slug}.md`)) : { exists: false },
|
|
269
|
-
conformance: slug ? await check('conformance', path.join(dir, `conformance-${slug}.yaml`)) : { exists: false },
|
|
270
|
-
dev_state: await check('dev-state', path.join(dir, 'dev-state.md')),
|
|
240
|
+
content
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function checkFirst(name, filePaths) {
|
|
245
|
+
for (const filePath of filePaths) {
|
|
246
|
+
const result = await check(name, filePath);
|
|
247
|
+
if (result.exists) return result;
|
|
248
|
+
}
|
|
249
|
+
return { exists: false };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const designDocCandidates = slug
|
|
253
|
+
? [path.join(dir, `design-doc-${slug}.md`), path.join(dir, 'design-doc.md')]
|
|
254
|
+
: [path.join(dir, 'design-doc.md')];
|
|
255
|
+
const readinessCandidates = slug
|
|
256
|
+
? [path.join(dir, `readiness-${slug}.md`), path.join(dir, 'readiness.md')]
|
|
257
|
+
: [path.join(dir, 'readiness.md')];
|
|
258
|
+
|
|
259
|
+
const results = {
|
|
260
|
+
project_context: await check('project.context', path.join(dir, 'project.context.md')),
|
|
261
|
+
prd: slug ? await check('prd', path.join(dir, `prd-${slug}.md`)) : { exists: false },
|
|
262
|
+
sheldon_enrichment: slug ? await check('sheldon', path.join(dir, `sheldon-enrichment-${slug}.md`)) : { exists: false },
|
|
263
|
+
requirements: slug ? await check('requirements', path.join(dir, `requirements-${slug}.md`)) : { exists: false },
|
|
264
|
+
spec: slug ? await check('spec', path.join(dir, `spec-${slug}.md`)) : await check('spec', path.join(dir, 'spec.md')),
|
|
265
|
+
architecture: await check('architecture', path.join(dir, 'architecture.md')),
|
|
266
|
+
design_doc: await checkFirst('design-doc', designDocCandidates),
|
|
267
|
+
readiness: await checkFirst('readiness', readinessCandidates),
|
|
268
|
+
implementation_plan: slug ? await check('impl-plan', path.join(dir, `implementation-plan-${slug}.md`)) : { exists: false },
|
|
269
|
+
conformance: slug ? await check('conformance', path.join(dir, `conformance-${slug}.yaml`)) : { exists: false },
|
|
270
|
+
dev_state: await check('dev-state', path.join(dir, 'dev-state.md')),
|
|
271
271
|
features: await check('features', path.join(dir, 'features.md'))
|
|
272
272
|
};
|
|
273
273
|
|
|
@@ -362,19 +362,19 @@ async function readProjectPulse(targetDir) {
|
|
|
362
362
|
// ─── Classification reader ────────────────────────────────────────────────────
|
|
363
363
|
|
|
364
364
|
async function detectClassification(targetDir, slug) {
|
|
365
|
-
//
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
// 2. Try spec frontmatter
|
|
365
|
+
// Feature classification takes precedence over the project classification —
|
|
366
|
+
// same contract as resolveClassification (handoff-contract.js) and
|
|
367
|
+
// workflow:next sequencing. A SMALL feature inside a MEDIUM project must be
|
|
368
|
+
// gated as SMALL; the project value is only the fallback.
|
|
370
369
|
if (slug) {
|
|
370
|
+
// 1. Try spec frontmatter
|
|
371
371
|
const specContent = await readFileSafe(path.join(contextDir(targetDir), `spec-${slug}.md`));
|
|
372
372
|
if (specContent) {
|
|
373
373
|
const fm = parseFrontmatter(specContent);
|
|
374
374
|
if (fm.classification) return fm.classification.toUpperCase();
|
|
375
375
|
}
|
|
376
376
|
|
|
377
|
-
//
|
|
377
|
+
// 2. Try PRD frontmatter
|
|
378
378
|
const prdContent = await readFileSafe(path.join(contextDir(targetDir), `prd-${slug}.md`));
|
|
379
379
|
if (prdContent) {
|
|
380
380
|
const fm = parseFrontmatter(prdContent);
|
|
@@ -382,6 +382,10 @@ async function detectClassification(targetDir, slug) {
|
|
|
382
382
|
}
|
|
383
383
|
}
|
|
384
384
|
|
|
385
|
+
// 3. Fall back to project context
|
|
386
|
+
const ctx = await loadProjectContext(targetDir);
|
|
387
|
+
if (ctx.data.classification) return ctx.data.classification.toUpperCase();
|
|
388
|
+
|
|
385
389
|
return null;
|
|
386
390
|
}
|
|
387
391
|
|
|
@@ -464,15 +468,15 @@ async function discoverDesignDocs(targetDir, agent) {
|
|
|
464
468
|
|
|
465
469
|
// ─── Context package builder ──────────────────────────────────────────────────
|
|
466
470
|
|
|
467
|
-
function buildContextPackage(agent, slug, classification, artifacts, devState, manifest) {
|
|
468
|
-
const pkg = [];
|
|
469
|
-
const designDoc = artifacts.design_doc || { exists: false };
|
|
470
|
-
const readiness = artifacts.readiness || { exists: false };
|
|
471
|
-
|
|
472
|
-
if (artifacts.project_context.exists) pkg.push(artifacts.project_context.path);
|
|
471
|
+
function buildContextPackage(agent, slug, classification, artifacts, devState, manifest) {
|
|
472
|
+
const pkg = [];
|
|
473
|
+
const designDoc = artifacts.design_doc || { exists: false };
|
|
474
|
+
const readiness = artifacts.readiness || { exists: false };
|
|
475
|
+
|
|
476
|
+
if (artifacts.project_context.exists) pkg.push(artifacts.project_context.path);
|
|
473
477
|
|
|
474
478
|
if (slug) {
|
|
475
|
-
const downstreamAgents = ['discovery-design-doc', 'pm', 'orchestrator', 'dev', 'deyvin', 'qa'];
|
|
479
|
+
const downstreamAgents = ['discovery-design-doc', 'pm', 'orchestrator', 'dev', 'deyvin', 'qa'];
|
|
476
480
|
const shouldCarryFullFeatureContext = downstreamAgents.includes(agent);
|
|
477
481
|
|
|
478
482
|
if (shouldCarryFullFeatureContext && artifacts.prd.exists) pkg.push(artifacts.prd.path);
|
|
@@ -483,10 +487,10 @@ function buildContextPackage(agent, slug, classification, artifacts, devState, m
|
|
|
483
487
|
|
|
484
488
|
if (artifacts.spec.exists) pkg.push(artifacts.spec.path);
|
|
485
489
|
|
|
486
|
-
if (shouldCarryFullFeatureContext && artifacts.architecture.exists) pkg.push(artifacts.architecture.path);
|
|
487
|
-
if (shouldCarryFullFeatureContext && designDoc.exists) pkg.push(designDoc.path);
|
|
488
|
-
if (shouldCarryFullFeatureContext && readiness.exists) pkg.push(readiness.path);
|
|
489
|
-
if (shouldCarryFullFeatureContext && artifacts.conformance.exists) pkg.push(artifacts.conformance.path);
|
|
490
|
+
if (shouldCarryFullFeatureContext && artifacts.architecture.exists) pkg.push(artifacts.architecture.path);
|
|
491
|
+
if (shouldCarryFullFeatureContext && designDoc.exists) pkg.push(designDoc.path);
|
|
492
|
+
if (shouldCarryFullFeatureContext && readiness.exists) pkg.push(readiness.path);
|
|
493
|
+
if (shouldCarryFullFeatureContext && artifacts.conformance.exists) pkg.push(artifacts.conformance.path);
|
|
490
494
|
|
|
491
495
|
// Manifest precedence (AC-SDLC-24, AC-SDLC-25):
|
|
492
496
|
// If active Sheldon manifest exists and is not done, it is the primary execution artifact.
|
|
@@ -608,11 +612,11 @@ function parseFeaturesMap(content) {
|
|
|
608
612
|
|
|
609
613
|
// ─── Readiness evaluator ─────────────────────────────────────────────────────
|
|
610
614
|
|
|
611
|
-
function evaluateReadiness(artifacts, phaseGates, classification, agent, devState, slug) {
|
|
612
|
-
const blockers = [];
|
|
613
|
-
const warnings = [];
|
|
614
|
-
const designDoc = artifacts.design_doc || { exists: false };
|
|
615
|
-
const readiness = artifacts.readiness || { exists: false };
|
|
615
|
+
function evaluateReadiness(artifacts, phaseGates, classification, agent, devState, slug) {
|
|
616
|
+
const blockers = [];
|
|
617
|
+
const warnings = [];
|
|
618
|
+
const designDoc = artifacts.design_doc || { exists: false };
|
|
619
|
+
const readiness = artifacts.readiness || { exists: false };
|
|
616
620
|
|
|
617
621
|
if (!artifacts.project_context.exists) blockers.push('project.context.md missing');
|
|
618
622
|
|
|
@@ -625,11 +629,11 @@ function evaluateReadiness(artifacts, phaseGates, classification, agent, devStat
|
|
|
625
629
|
}
|
|
626
630
|
}
|
|
627
631
|
|
|
628
|
-
if (agent === 'analyst') {
|
|
629
|
-
if (slug && !artifacts.prd.exists) {
|
|
630
|
-
warnings.push('prd file missing — feature is not framed yet; @analyst may run project discovery/research only, or hand off to @product/@briefing to create prd-{slug}.md before formal feature requirements');
|
|
631
|
-
}
|
|
632
|
-
}
|
|
632
|
+
if (agent === 'analyst') {
|
|
633
|
+
if (slug && !artifacts.prd.exists) {
|
|
634
|
+
warnings.push('prd file missing — feature is not framed yet; @analyst may run project discovery/research only, or hand off to @product/@briefing to create prd-{slug}.md before formal feature requirements');
|
|
635
|
+
}
|
|
636
|
+
}
|
|
633
637
|
|
|
634
638
|
if (agent === 'architect') {
|
|
635
639
|
if (!artifacts.requirements.exists) {
|
|
@@ -637,25 +641,25 @@ function evaluateReadiness(artifacts, phaseGates, classification, agent, devStat
|
|
|
637
641
|
}
|
|
638
642
|
}
|
|
639
643
|
|
|
640
|
-
if (agent === 'pm') {
|
|
644
|
+
if (agent === 'pm') {
|
|
641
645
|
if (!artifacts.architecture.exists) {
|
|
642
646
|
blockers.push('architecture.md missing — @architect must complete design first (Gate B)');
|
|
643
647
|
}
|
|
644
648
|
if (!artifacts.requirements.exists) {
|
|
645
649
|
warnings.push('requirements file missing — @pm should review it before writing the implementation plan');
|
|
646
650
|
}
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
if (agent === 'discovery-design-doc') {
|
|
650
|
-
if (!artifacts.architecture.exists) {
|
|
651
|
-
blockers.push('architecture.md missing — @architect must complete design first (Gate B)');
|
|
652
|
-
}
|
|
653
|
-
if (!designDoc.exists) {
|
|
654
|
-
warnings.push('design-doc.md missing — @discovery-design-doc must create the project baseline before implementation');
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
if (agent === 'orchestrator') {
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
if (agent === 'discovery-design-doc') {
|
|
654
|
+
if (!artifacts.architecture.exists) {
|
|
655
|
+
blockers.push('architecture.md missing — @architect must complete design first (Gate B)');
|
|
656
|
+
}
|
|
657
|
+
if (!designDoc.exists) {
|
|
658
|
+
warnings.push('design-doc.md missing — @discovery-design-doc must create the project baseline before implementation');
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
if (agent === 'orchestrator') {
|
|
659
663
|
if (!artifacts.requirements.exists) {
|
|
660
664
|
blockers.push('requirements file missing — Gate A not satisfied');
|
|
661
665
|
}
|
|
@@ -673,16 +677,16 @@ function evaluateReadiness(artifacts, phaseGates, classification, agent, devStat
|
|
|
673
677
|
}
|
|
674
678
|
}
|
|
675
679
|
|
|
676
|
-
if (agent === 'dev' || agent === 'deyvin') {
|
|
677
|
-
if (!artifacts.spec.exists) blockers.push('spec file missing');
|
|
678
|
-
if (classification && classification !== 'MICRO') {
|
|
679
|
-
if (!designDoc.exists) {
|
|
680
|
-
blockers.push('design-doc.md missing — @discovery-design-doc must run before implementation');
|
|
681
|
-
}
|
|
682
|
-
if (!readiness.exists) {
|
|
683
|
-
blockers.push('readiness.md missing — @discovery-design-doc must run before implementation');
|
|
684
|
-
}
|
|
685
|
-
}
|
|
680
|
+
if (agent === 'dev' || agent === 'deyvin') {
|
|
681
|
+
if (!artifacts.spec.exists) blockers.push('spec file missing');
|
|
682
|
+
if (classification && classification !== 'MICRO') {
|
|
683
|
+
if (!designDoc.exists) {
|
|
684
|
+
blockers.push('design-doc.md missing — @discovery-design-doc must run before implementation');
|
|
685
|
+
}
|
|
686
|
+
if (!readiness.exists) {
|
|
687
|
+
blockers.push('readiness.md missing — @discovery-design-doc must run before implementation');
|
|
688
|
+
}
|
|
689
|
+
}
|
|
686
690
|
if (classification === 'MEDIUM') {
|
|
687
691
|
const implementationPlan = artifacts.implementation_plan || { exists: false, frontmatter: {} };
|
|
688
692
|
if (!implementationPlan.exists) {
|
|
@@ -366,6 +366,10 @@ Action: /scope-check or /architect
|
|
|
366
366
|
```
|
|
367
367
|
> Recommended: `/clear` before activating — fresh context window.
|
|
368
368
|
|
|
369
|
+
## Autopilot handoff
|
|
370
|
+
|
|
371
|
+
If `auto_handoff: true` in `project.context.md` frontmatter and a feature workflow is active, follow `.aioson/docs/autopilot-handoff.md`: after Gate A is approved and all closing duties above are done, do not stop for manual activation. Determine the next agent from the workflow state (never guess) and auto-invoke `Skill(aioson:agent:<next>)` with `"continue feature {slug} — autopilot handoff from @analyst"`. No user prompt — Ctrl+C interrupts. Emit the manual handoff instead when any stop condition applies: next agent is `@dev`, Gate A not approved, context ≥ `context_warning_threshold`, or routing is ambiguous.
|
|
372
|
+
|
|
369
373
|
## Strategic commands (use during session)
|
|
370
374
|
|
|
371
375
|
- Search memory before web research: `aioson memory:search . --query="<topic>" 2>/dev/null || true`
|
|
@@ -140,6 +140,10 @@ Action: /pm or /dev
|
|
|
140
140
|
```
|
|
141
141
|
> Recommended: `/clear` before activating — fresh context window.
|
|
142
142
|
|
|
143
|
+
## Autopilot handoff
|
|
144
|
+
|
|
145
|
+
If `auto_handoff: true` in `project.context.md` frontmatter, a feature workflow is active, and Gate B passed, follow `.aioson/docs/autopilot-handoff.md`: auto-invoke `Skill(aioson:agent:<next>)` for the next workflow stage with `"continue feature {slug} — autopilot handoff from @architect"`. No user prompt — Ctrl+C interrupts. Emit the manual handoff instead when Gate B is blocked, the next agent is `@dev`, or context ≥ `context_warning_threshold`.
|
|
146
|
+
|
|
143
147
|
## Rules
|
|
144
148
|
- Do not redesign entities produced by `@analyst`. Consume the data design as-is.
|
|
145
149
|
- Keep architecture proportional to classification. Never apply MEDIUM patterns to a MICRO project.
|
|
@@ -152,7 +152,7 @@ If flagged, recommend a new chat and offer a handoff with slug, completed phase,
|
|
|
152
152
|
|
|
153
153
|
Check `.aioson/context/features/{slug}/dossier.md` before per-slug PRD/spec. If present, read it FIRST — it consolidates Why/What + code map and is the canonical entry point for chained context. If absent, continue with standard input (legacy flow).
|
|
154
154
|
|
|
155
|
-
**Auto-resume (session start):** `aioson dev:resume-data .` returns `{feature_slug, classification, current_phase, artifacts_consumed, code_map_paths, sheldon_plan, next_step}` or `null` (cold start). Skip discovery, start on `next_step`, then emit `aioson runtime:emit . --agent=dev --type=dev_auto_resume --summary="<feature>: phase <N> auto-resumed" 2>/dev/null || true`.
|
|
155
|
+
**Auto-resume (session start):** `aioson dev:resume-data .` returns `{feature_slug, classification, current_phase, artifacts_consumed, code_map_paths, sheldon_plan, next_step, open_corrections?}` or `null` (cold start). When `open_corrections` is non-empty, those QA corrections plans are the top priority regardless of any other pointer. Skip discovery, start on `next_step`, then emit `aioson runtime:emit . --agent=dev --type=dev_auto_resume --summary="<feature>: phase <N> auto-resumed" 2>/dev/null || true`.
|
|
156
156
|
|
|
157
157
|
**Drift detection (prompt-driven):** before modifying/creating a file, check if its path is in `code_map_paths`. If registered AND your change diverges from the upstream plan, or a Sheldon plan step already ran without an Agent Trail entry → DRIFT. On DRIFT: emit `aioson runtime:emit . --agent=dev --type=dev_drift_detected --summary="Drift detected: {what}" 2>/dev/null || true`, give the user 3 options (proceed/revise/abort), record `dossier:add-finding --section="Agent Trail" --content="DRIFT: {what}. Decision. Reason."`.
|
|
158
158
|
|
|
@@ -285,6 +285,8 @@ Run `aioson` CLI yourself to keep the workflow moving:
|
|
|
285
285
|
|
|
286
286
|
If `.aioson/runtime/qa-dev-cycle.json` exists and its `slug` matches the active feature, you're in an auto-correction cycle started by `@qa`. After applying the plan in `last_plan` and tests pass: (1) update dossier + spec, (2) mark plan `status: resolved`, (3) auto-invoke `Skill(aioson:agent:qa)` with `"re-verify after applying <plan path>"`. No user prompt — Ctrl+C interrupts. If the file is absent or slug differs, manual handoff as before.
|
|
287
287
|
|
|
288
|
+
**Safety net — open corrections without the cycle file:** on every activation with an active feature, also check `.aioson/plans/{active-feature}/corrections-*.md`. If any has frontmatter `status: open` or `in_progress`, those mandatory corrections take priority over the dev-state `next_step` — apply them first, mark the plan `resolved`, then hand off to `@qa` for re-verification. `aioson dev:resume-data` surfaces them as `open_corrections` and already rewrites `next_step` accordingly; trust that over a stale dev-state pointer. This covers QA sessions that created a corrections plan but failed to persist the trail.
|
|
289
|
+
|
|
288
290
|
## Optional scope drift checkpoint
|
|
289
291
|
|
|
290
292
|
After a feature slice lands, recommend optional `@scope-check --scope-mode=post-dev` before `@qa` when the implementation changed planned behavior, touched unexpected files, skipped a planned item, or required a trade-off not already captured in the design artifacts. Skip the recommendation for routine implementation that matches the approved plan.
|
|
@@ -66,5 +66,9 @@ aioson dossier:add-finding . --slug={slug} --agent=discovery-design-doc --sectio
|
|
|
66
66
|
|
|
67
67
|
Skip silently when the dossier is absent — projects without dossier still get the appropriate design-doc/readiness pair as the primary handoff.
|
|
68
68
|
|
|
69
|
+
## Autopilot handoff
|
|
70
|
+
|
|
71
|
+
If `auto_handoff: true` in `project.context.md` frontmatter, a feature workflow is active, and readiness is `ready` or `ready_with_warnings`, follow `.aioson/docs/autopilot-handoff.md`: auto-invoke `Skill(aioson:agent:<next>)` for the next workflow stage with `"continue feature {slug} — autopilot handoff from @discovery-design-doc"`. No user prompt — Ctrl+C interrupts. Emit the manual handoff instead when readiness is `blocked`, the next agent is `@dev` (standard handoff + recommend `/clear`), or context ≥ `context_warning_threshold`.
|
|
72
|
+
|
|
69
73
|
## Observability
|
|
70
74
|
At session end, register: `aioson agent:done . --agent=discovery-design-doc --summary="Design doc <slug>: readiness=<level>, next=<agent>" 2>/dev/null || true`
|