@jaimevalasek/aioson 1.21.8 → 1.23.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 +950 -923
- package/package.json +1 -1
- package/src/agents.js +21 -20
- package/src/cli.js +31 -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-preview.js +74 -0
- package/src/commands/harness-retro.js +221 -0
- package/src/commands/harness-status.js +157 -0
- package/src/commands/harness.js +18 -1
- package/src/commands/self-implement-loop.js +315 -5
- package/src/commands/workflow-next.js +45 -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/preview-artifact.js +85 -0
- package/src/harness/scope-guard.js +115 -0
- package/src/i18n/messages/en.js +23 -0
- package/src/i18n/messages/es.js +32 -9
- package/src/i18n/messages/fr.js +32 -9
- package/src/i18n/messages/pt-BR.js +23 -0
- package/src/lib/dev-resume.js +94 -45
- package/src/lib/retro/retro-aggregate.js +192 -0
- package/src/lib/retro/retro-render.js +185 -0
- package/src/lib/retro/retro-sources.js +624 -0
- 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 +14 -1
- package/template/.aioson/agents/discovery-design-doc.md +4 -0
- package/template/.aioson/agents/pentester.md +8 -0
- package/template/.aioson/agents/pm.md +10 -5
- package/template/.aioson/agents/qa.md +46 -14
- package/template/.aioson/agents/scope-check.md +176 -172
- package/template/.aioson/agents/sheldon.md +13 -0
- package/template/.aioson/agents/tester.md +17 -0
- package/template/.aioson/agents/validator.md +8 -0
- package/template/.aioson/config.md +31 -28
- package/template/.aioson/docs/autopilot-handoff.md +83 -0
- package/template/.aioson/rules/aioson-context-boundary.md +10 -8
- package/template/AGENTS.md +57 -57
- package/template/CLAUDE.md +33 -33
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Scope guard do self:loop (loop-guardrails REQ-4/5/6 + REQ-10).
|
|
5
|
+
*
|
|
6
|
+
* Módulo puro: recebe o changed set (já calculado por git-baseline) e o
|
|
7
|
+
* contrato RESOLVIDO (contract-schema.resolveContract — defaults proibidos já
|
|
8
|
+
* mesclados). Não faz I/O.
|
|
9
|
+
*
|
|
10
|
+
* Precedência (REQ-5): deny vence allow — path que casa `forbidden_files` é
|
|
11
|
+
* violação mesmo casando `allowed_files`. Defaults proibidos são sempre
|
|
12
|
+
* aplicados (REQ-4) porque vêm mesclados do resolveContract.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const { matchAny } = require('./glob-match');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {object} params
|
|
19
|
+
* @param {Array<{path, status}>} params.changedFiles — changed set da tentativa
|
|
20
|
+
* @param {Array<{path, reason}>} [params.rehashViolations] — D2 (git-baseline)
|
|
21
|
+
* @param {string[]|null} params.allowedGlobs — null = sem allowlist
|
|
22
|
+
* @param {string[]} params.forbiddenGlobs — já mesclados com defaults
|
|
23
|
+
* @returns {{ ok: boolean, violations: Array<{path, status, glob, reason}> }}
|
|
24
|
+
*/
|
|
25
|
+
function checkScope({ changedFiles = [], rehashViolations = [], allowedGlobs = null, forbiddenGlobs = [] }) {
|
|
26
|
+
const violations = [];
|
|
27
|
+
|
|
28
|
+
for (const file of changedFiles) {
|
|
29
|
+
const forbiddenMatch = matchAny(forbiddenGlobs, file.path);
|
|
30
|
+
if (forbiddenMatch) {
|
|
31
|
+
violations.push({
|
|
32
|
+
path: file.path,
|
|
33
|
+
status: file.status,
|
|
34
|
+
glob: forbiddenMatch,
|
|
35
|
+
reason: `matches forbidden glob "${forbiddenMatch}"${file.status === 'deleted' ? ' (deletion counts — EC-4)' : ''}`
|
|
36
|
+
});
|
|
37
|
+
continue; // deny vence allow (REQ-5)
|
|
38
|
+
}
|
|
39
|
+
if (allowedGlobs && !matchAny(allowedGlobs, file.path)) {
|
|
40
|
+
violations.push({
|
|
41
|
+
path: file.path,
|
|
42
|
+
status: file.status,
|
|
43
|
+
glob: null,
|
|
44
|
+
reason: 'outside allowed_files allowlist'
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
for (const rehash of rehashViolations) {
|
|
50
|
+
violations.push({
|
|
51
|
+
path: rehash.path,
|
|
52
|
+
status: 'modified',
|
|
53
|
+
glob: null,
|
|
54
|
+
reason: rehash.reason
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return { ok: violations.length === 0, violations };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Conta linhas efetivas de diff (+/− excluindo headers +++/---). */
|
|
62
|
+
function countDiffLines(diffPatch) {
|
|
63
|
+
if (!diffPatch) return 0;
|
|
64
|
+
let count = 0;
|
|
65
|
+
for (const line of String(diffPatch).split('\n')) {
|
|
66
|
+
if ((line.startsWith('+') && !line.startsWith('+++')) ||
|
|
67
|
+
(line.startsWith('-') && !line.startsWith('---'))) {
|
|
68
|
+
count += 1;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return count;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Limites de diff (REQ-10, should-have). Avaliados sobre o MESMO conjunto do
|
|
76
|
+
* scope guard. `null`/`undefined` = sem limite.
|
|
77
|
+
*
|
|
78
|
+
* @returns {{ ok: boolean, exceeded: Array<{limit, actual, max}> }}
|
|
79
|
+
*/
|
|
80
|
+
function checkDiffLimits({ changedFiles = [], diffPatch = '', maxChangedFiles = null, maxDiffLines = null }) {
|
|
81
|
+
const exceeded = [];
|
|
82
|
+
|
|
83
|
+
if (Number.isInteger(maxChangedFiles) && maxChangedFiles > 0 && changedFiles.length > maxChangedFiles) {
|
|
84
|
+
exceeded.push({ limit: 'max_changed_files', actual: changedFiles.length, max: maxChangedFiles });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (Number.isInteger(maxDiffLines) && maxDiffLines > 0) {
|
|
88
|
+
const actual = countDiffLines(diffPatch);
|
|
89
|
+
if (actual > maxDiffLines) {
|
|
90
|
+
exceeded.push({ limit: 'max_diff_lines', actual, max: maxDiffLines });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return { ok: exceeded.length === 0, exceeded };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Feedback de reparo/rollback injetado na próxima iteração após violação
|
|
99
|
+
* (REQ-6). Texto direto para o agente: reverter os paths e permanecer no escopo.
|
|
100
|
+
*/
|
|
101
|
+
function buildRollbackFeedback(violations) {
|
|
102
|
+
const lines = violations.slice(0, 10).map((v) => ` - ${v.path} (${v.reason})`);
|
|
103
|
+
return [
|
|
104
|
+
'SCOPE VIOLATION — files were changed outside the contract scope:',
|
|
105
|
+
...lines,
|
|
106
|
+
'Revert these changes (git checkout -- <path> / delete untracked files) and redo the task touching ONLY files inside the allowed scope.'
|
|
107
|
+
].join('\n');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
module.exports = {
|
|
111
|
+
checkScope,
|
|
112
|
+
checkDiffLimits,
|
|
113
|
+
countDiffLines,
|
|
114
|
+
buildRollbackFeedback
|
|
115
|
+
};
|
package/src/i18n/messages/en.js
CHANGED
|
@@ -150,6 +150,27 @@ module.exports = {
|
|
|
150
150
|
'aioson harness:init [path] --slug=<slug> [--mode=BALANCED|URGENT|ECONOMICAL] [--locale=en]',
|
|
151
151
|
help_harness_validate:
|
|
152
152
|
'aioson harness:validate [path] --slug=<slug> [--artifact=<path>] [--locale=en]',
|
|
153
|
+
help_harness_retro:
|
|
154
|
+
'aioson harness:retro [path] --feature=<slug> | --last=<N> [--json] [--locale=en]',
|
|
155
|
+
help_harness_preview:
|
|
156
|
+
'aioson harness:preview <file> [--max-bytes=8192] [--json] [--locale=en]',
|
|
157
|
+
harnessRetro: {
|
|
158
|
+
need_target: 'harness:retro requires --feature=<slug> or --last=<N>.',
|
|
159
|
+
invalid_slug: 'Invalid slug: {slug} (must match ^[a-z0-9][a-z0-9-]*$).',
|
|
160
|
+
invalid_last: 'Invalid --last value: {value} (use an integer >= 1).',
|
|
161
|
+
feature_not_found: 'Feature not found: {slug} (searched .aioson/context/, .aioson/plans/{slug}/, .aioson/context/features/{slug}/, .aioson/context/done/{slug}/).',
|
|
162
|
+
no_closed_features: 'No closed features under .aioson/context/done/ to mine.',
|
|
163
|
+
written: 'Retro dossier written: {path} ({candidates} candidates, {observations} observations).',
|
|
164
|
+
empty: 'Retro dossier written with no proposals: {path} (no minable trail).',
|
|
165
|
+
io_error: 'I/O error writing the dossier: {error}',
|
|
166
|
+
window_truncated: '--last={n} exceeds available features ({available}); mining all of them.',
|
|
167
|
+
undatable_excluded: '{count} feature(s) without a resolvable PASS date excluded from the window: {slugs}'
|
|
168
|
+
},
|
|
169
|
+
harnessPreview: {
|
|
170
|
+
file_required: 'harness:preview requires a <file> path argument.',
|
|
171
|
+
not_found: 'File not found: {path}',
|
|
172
|
+
read_error: 'Could not read file: {path} ({error})'
|
|
173
|
+
},
|
|
153
174
|
help_web_map:
|
|
154
175
|
'aioson web:map [path] --url=<url> [--depth=<N>] [--max-pages=<N>] [--include-external] [--json] [--locale=en]',
|
|
155
176
|
help_web_scrape:
|
|
@@ -450,6 +471,8 @@ module.exports = {
|
|
|
450
471
|
bootstrap_coverage_hint_seed: 'Run /discover to seed .aioson/context/bootstrap/{what-is,how-it-works,what-it-does,current-state}.md',
|
|
451
472
|
features_dir_present: 'Features directory present (.aioson/context/features/)',
|
|
452
473
|
features_dir_present_hint: 'Create .aioson/context/features/ to host per-feature dossiers (doctor --fix will create it).',
|
|
474
|
+
auto_handoff_declared: 'Autopilot handoff flag declared (auto_handoff in project.context.md)',
|
|
475
|
+
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
476
|
claude_commands_present: 'Claude slash commands present ({missing} missing of {required})',
|
|
454
477
|
claude_commands_present_hint: 'Missing: {paths}. Run `aioson doctor . --fix` to restore them from the template.',
|
|
455
478
|
version_drift: 'CLI version matches project.context.md (context: {context}, CLI: {cli})',
|
package/src/i18n/messages/es.js
CHANGED
|
@@ -136,6 +136,27 @@ module.exports = {
|
|
|
136
136
|
'aioson qa:scan [path] [--url=<app-url>] [--depth=3] [--max-pages=50] [--headed] [--html] [--json] [--locale=es]',
|
|
137
137
|
help_qa_report:
|
|
138
138
|
'aioson qa:report [path] [--html] [--json] [--locale=es]',
|
|
139
|
+
help_harness_retro:
|
|
140
|
+
'aioson harness:retro [path] --feature=<slug> | --last=<N> [--json] [--locale=es]',
|
|
141
|
+
help_harness_preview:
|
|
142
|
+
'aioson harness:preview <file> [--max-bytes=8192] [--json] [--locale=es]',
|
|
143
|
+
harnessRetro: {
|
|
144
|
+
need_target: 'harness:retro requiere --feature=<slug> o --last=<N>.',
|
|
145
|
+
invalid_slug: 'Slug inválido: {slug} (debe cumplir ^[a-z0-9][a-z0-9-]*$).',
|
|
146
|
+
invalid_last: 'Valor inválido para --last: {value} (use un entero >= 1).',
|
|
147
|
+
feature_not_found: 'Feature no encontrada: {slug} (buscado en .aioson/context/, .aioson/plans/{slug}/, .aioson/context/features/{slug}/, .aioson/context/done/{slug}/).',
|
|
148
|
+
no_closed_features: 'No hay features cerradas en .aioson/context/done/ para minar.',
|
|
149
|
+
written: 'Dosier retrospectivo generado: {path} ({candidates} candidatos, {observations} observaciones).',
|
|
150
|
+
empty: 'Dosier generado sin propuestas: {path} (fuentes sin rastro minable).',
|
|
151
|
+
io_error: 'Error de E/S al escribir el dosier: {error}',
|
|
152
|
+
window_truncated: '--last={n} supera las features disponibles ({available}); minando todas.',
|
|
153
|
+
undatable_excluded: '{count} feature(s) sin fecha de PASS resoluble excluida(s) de la ventana: {slugs}'
|
|
154
|
+
},
|
|
155
|
+
harnessPreview: {
|
|
156
|
+
file_required: 'harness:preview requiere una ruta de archivo <file>.',
|
|
157
|
+
not_found: 'Archivo no encontrado: {path}',
|
|
158
|
+
read_error: 'No se pudo leer el archivo: {path} ({error})'
|
|
159
|
+
},
|
|
139
160
|
help_web_map:
|
|
140
161
|
'aioson web:map [path] --url=<url> [--depth=<N>] [--max-pages=<N>] [--include-external] [--json] [--locale=es]',
|
|
141
162
|
help_web_scrape:
|
|
@@ -160,12 +181,12 @@ module.exports = {
|
|
|
160
181
|
'aioson squad:pipeline [path] [--sub=list|show|status] [--pipeline=<slug>] [--locale=es]',
|
|
161
182
|
help_squad_investigate:
|
|
162
183
|
'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]',
|
|
184
|
+
help_squad_learning:
|
|
185
|
+
'aioson squad:learning [path] [--sub=list|stats|archive|promote|export] [--squad=<slug>] [--status=<status>] [--locale=es]',
|
|
186
|
+
help_quality_audit:
|
|
187
|
+
'aioson quality:audit [path] [--feature=<slug>] [--provider-output=<path>] [--baseline=<path>] [--changed=<path[,path]>] [--json] [--locale=es]',
|
|
188
|
+
help_squad_dashboard:
|
|
189
|
+
'aioson squad:dashboard [path] [--port=4180] [--squad=<slug>] [--locale=es]',
|
|
169
190
|
help_squad_worker:
|
|
170
191
|
'aioson squad:worker [path] [--sub=list|run|test|logs|scaffold] [--squad=<slug>] [--worker=<slug>] [--input=<json>] [--locale=es]',
|
|
171
192
|
help_squad_daemon:
|
|
@@ -179,7 +200,7 @@ module.exports = {
|
|
|
179
200
|
help_commit_prepare:
|
|
180
201
|
'aioson commit:prepare [path] [--staged-only] [--agent-safe] [--mode=guarded|trusted|headless] [--json] [--locale=es]',
|
|
181
202
|
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]',
|
|
203
|
+
'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
204
|
dashboard_moved:
|
|
184
205
|
'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
206
|
dashboard_moved_line: '{message}\n',
|
|
@@ -324,6 +345,8 @@ module.exports = {
|
|
|
324
345
|
bootstrap_coverage_hint_seed: 'Ejecute /discover para sembrar .aioson/context/bootstrap/{what-is,how-it-works,what-it-does,current-state}.md',
|
|
325
346
|
features_dir_present: 'Directorio de features presente (.aioson/context/features/)',
|
|
326
347
|
features_dir_present_hint: 'Cree .aioson/context/features/ para hospedar dossiers por feature (doctor --fix lo crea).',
|
|
348
|
+
auto_handoff_declared: 'Flag de autopilot handoff declarada (auto_handoff en project.context.md)',
|
|
349
|
+
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
350
|
claude_commands_present: 'Slash commands de Claude presentes ({missing} ausentes de {required})',
|
|
328
351
|
claude_commands_present_hint: 'Ausentes: {paths}. Ejecute `aioson doctor . --fix` para restaurarlos.',
|
|
329
352
|
version_drift: 'Version del CLI coincide con project.context.md (contexto: {context}, CLI: {cli})',
|
|
@@ -609,7 +632,7 @@ module.exports = {
|
|
|
609
632
|
note_product_optional:
|
|
610
633
|
'@product es opcional para MICRO — omitelo y ve directo a @dev si la idea ya esta clara.',
|
|
611
634
|
note_feature_flow:
|
|
612
|
-
'Flujo para nueva feature (tras la configuracion inicial): @product → @analyst → @scope-check → @dev → @qa. Sin @setup.'
|
|
635
|
+
'Flujo para nueva feature (tras la configuracion inicial): @product → @analyst → @scope-check → @dev → @qa. Sin @setup.'
|
|
613
636
|
},
|
|
614
637
|
parallel_init: {
|
|
615
638
|
context_missing:
|
|
@@ -1020,7 +1043,7 @@ module.exports = {
|
|
|
1020
1043
|
folder_required_example_prompt:
|
|
1021
1044
|
' Prompt listo : aioson agent:prompt analyst --tool=codex',
|
|
1022
1045
|
folder_required_example_next:
|
|
1023
|
-
' Flujo tras escaneo completo: @analyst -> @scope-check -> @architect -> @dev',
|
|
1046
|
+
' Flujo tras escaneo completo: @analyst -> @scope-check -> @architect -> @dev',
|
|
1024
1047
|
folder_not_found: 'La carpeta "{folder}" no existe en este proyecto. Directorios de nivel superior detectados: {available}',
|
|
1025
1048
|
config_missing: '{file} no encontrado. Para usar el modo con LLM, copia aioson-models.json y completa tus claves de API.',
|
|
1026
1049
|
config_invalid: 'JSON invalido en aioson-models.json: {error}',
|
package/src/i18n/messages/fr.js
CHANGED
|
@@ -136,6 +136,27 @@ module.exports = {
|
|
|
136
136
|
'aioson qa:scan [path] [--url=<app-url>] [--depth=3] [--max-pages=50] [--headed] [--html] [--json] [--locale=fr]',
|
|
137
137
|
help_qa_report:
|
|
138
138
|
'aioson qa:report [path] [--html] [--json] [--locale=fr]',
|
|
139
|
+
help_harness_retro:
|
|
140
|
+
'aioson harness:retro [path] --feature=<slug> | --last=<N> [--json] [--locale=fr]',
|
|
141
|
+
help_harness_preview:
|
|
142
|
+
'aioson harness:preview <file> [--max-bytes=8192] [--json] [--locale=fr]',
|
|
143
|
+
harnessRetro: {
|
|
144
|
+
need_target: 'harness:retro requiert --feature=<slug> ou --last=<N>.',
|
|
145
|
+
invalid_slug: 'Slug invalide : {slug} (doit correspondre à ^[a-z0-9][a-z0-9-]*$).',
|
|
146
|
+
invalid_last: 'Valeur --last invalide : {value} (utilisez un entier >= 1).',
|
|
147
|
+
feature_not_found: 'Feature introuvable : {slug} (cherché dans .aioson/context/, .aioson/plans/{slug}/, .aioson/context/features/{slug}/, .aioson/context/done/{slug}/).',
|
|
148
|
+
no_closed_features: 'Aucune feature clôturée dans .aioson/context/done/ à miner.',
|
|
149
|
+
written: 'Dossier rétrospectif généré : {path} ({candidates} candidats, {observations} observations).',
|
|
150
|
+
empty: 'Dossier généré sans propositions : {path} (sources sans piste exploitable).',
|
|
151
|
+
io_error: 'Erreur d’E/S lors de l’écriture du dossier : {error}',
|
|
152
|
+
window_truncated: '--last={n} dépasse les features disponibles ({available}) ; minage de toutes.',
|
|
153
|
+
undatable_excluded: '{count} feature(s) sans date de PASS résoluble exclue(s) de la fenêtre : {slugs}'
|
|
154
|
+
},
|
|
155
|
+
harnessPreview: {
|
|
156
|
+
file_required: 'harness:preview requiert un chemin de fichier <file>.',
|
|
157
|
+
not_found: 'Fichier introuvable : {path}',
|
|
158
|
+
read_error: 'Impossible de lire le fichier : {path} ({error})'
|
|
159
|
+
},
|
|
139
160
|
help_web_map:
|
|
140
161
|
'aioson web:map [path] --url=<url> [--depth=<N>] [--max-pages=<N>] [--include-external] [--json] [--locale=fr]',
|
|
141
162
|
help_web_scrape:
|
|
@@ -160,12 +181,12 @@ module.exports = {
|
|
|
160
181
|
'aioson squad:pipeline [path] [--sub=list|show|status] [--pipeline=<slug>] [--locale=fr]',
|
|
161
182
|
help_squad_investigate:
|
|
162
183
|
'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]',
|
|
184
|
+
help_squad_learning:
|
|
185
|
+
'aioson squad:learning [path] [--sub=list|stats|archive|promote|export] [--squad=<slug>] [--status=<status>] [--locale=fr]',
|
|
186
|
+
help_quality_audit:
|
|
187
|
+
'aioson quality:audit [path] [--feature=<slug>] [--provider-output=<path>] [--baseline=<path>] [--changed=<path[,path]>] [--json] [--locale=fr]',
|
|
188
|
+
help_squad_dashboard:
|
|
189
|
+
'aioson squad:dashboard [path] [--port=4180] [--squad=<slug>] [--locale=fr]',
|
|
169
190
|
help_squad_worker:
|
|
170
191
|
'aioson squad:worker [path] [--sub=list|run|test|logs|scaffold] [--squad=<slug>] [--worker=<slug>] [--input=<json>] [--locale=fr]',
|
|
171
192
|
help_squad_daemon:
|
|
@@ -179,7 +200,7 @@ module.exports = {
|
|
|
179
200
|
help_commit_prepare:
|
|
180
201
|
'aioson commit:prepare [path] [--staged-only] [--agent-safe] [--mode=guarded|trusted|headless] [--json] [--locale=fr]',
|
|
181
202
|
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]',
|
|
203
|
+
'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
204
|
dashboard_moved:
|
|
184
205
|
'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
206
|
dashboard_moved_line: '{message}\n',
|
|
@@ -323,6 +344,8 @@ module.exports = {
|
|
|
323
344
|
bootstrap_coverage_hint_seed: 'Executez /discover pour creer .aioson/context/bootstrap/{what-is,how-it-works,what-it-does,current-state}.md',
|
|
324
345
|
features_dir_present: 'Repertoire features present (.aioson/context/features/)',
|
|
325
346
|
features_dir_present_hint: 'Creez .aioson/context/features/ pour heberger les dossiers par feature (doctor --fix le cree).',
|
|
347
|
+
auto_handoff_declared: 'Flag autopilot handoff declare (auto_handoff dans project.context.md)',
|
|
348
|
+
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
349
|
claude_commands_present: 'Slash commands de Claude presents ({missing} absents sur {required})',
|
|
327
350
|
claude_commands_present_hint: 'Absents : {paths}. Executez `aioson doctor . --fix` pour les restaurer.',
|
|
328
351
|
version_drift: 'Version du CLI conforme a project.context.md (contexte : {context}, CLI : {cli})',
|
|
@@ -612,7 +635,7 @@ module.exports = {
|
|
|
612
635
|
note_product_optional:
|
|
613
636
|
'@product est optionnel pour MICRO — passez directement a @dev si l idee est deja claire.',
|
|
614
637
|
note_feature_flow:
|
|
615
|
-
'Flux nouvelle feature (apres configuration initiale) : @product → @analyst → @scope-check → @dev → @qa. Pas de @setup.'
|
|
638
|
+
'Flux nouvelle feature (apres configuration initiale) : @product → @analyst → @scope-check → @dev → @qa. Pas de @setup.'
|
|
616
639
|
},
|
|
617
640
|
parallel_init: {
|
|
618
641
|
context_missing:
|
|
@@ -1028,7 +1051,7 @@ module.exports = {
|
|
|
1028
1051
|
folder_required_example_prompt:
|
|
1029
1052
|
' Prompt pret : aioson agent:prompt analyst --tool=codex',
|
|
1030
1053
|
folder_required_example_next:
|
|
1031
|
-
' Workflow apres scan complet : @analyst -> @scope-check -> @architect -> @dev',
|
|
1054
|
+
' Workflow apres scan complet : @analyst -> @scope-check -> @architect -> @dev',
|
|
1032
1055
|
folder_not_found: 'Le dossier "{folder}" est introuvable dans ce projet. Dossiers de premier niveau detectes : {available}',
|
|
1033
1056
|
config_missing: '{file} introuvable. Pour utiliser le mode LLM, copiez aioson-models.json et renseignez vos cles API.',
|
|
1034
1057
|
config_invalid: 'JSON invalide dans aioson-models.json : {error}',
|
|
@@ -152,6 +152,27 @@ module.exports = {
|
|
|
152
152
|
'aioson harness:init [path] --slug=<slug> [--mode=BALANCED|URGENT|ECONOMICAL] [--locale=pt-BR]',
|
|
153
153
|
help_harness_validate:
|
|
154
154
|
'aioson harness:validate [path] --slug=<slug> [--artifact=<path>] [--locale=pt-BR]',
|
|
155
|
+
help_harness_retro:
|
|
156
|
+
'aioson harness:retro [path] --feature=<slug> | --last=<N> [--json] [--locale=pt-BR]',
|
|
157
|
+
help_harness_preview:
|
|
158
|
+
'aioson harness:preview <file> [--max-bytes=8192] [--json] [--locale=pt-BR]',
|
|
159
|
+
harnessRetro: {
|
|
160
|
+
need_target: 'harness:retro requer --feature=<slug> ou --last=<N>.',
|
|
161
|
+
invalid_slug: 'Slug inválido: {slug} (deve casar ^[a-z0-9][a-z0-9-]*$).',
|
|
162
|
+
invalid_last: 'Valor inválido para --last: {value} (use inteiro >= 1).',
|
|
163
|
+
feature_not_found: 'Feature não encontrada: {slug} (procurado em .aioson/context/, .aioson/plans/{slug}/, .aioson/context/features/{slug}/, .aioson/context/done/{slug}/).',
|
|
164
|
+
no_closed_features: 'Nenhuma feature fechada em .aioson/context/done/ para minerar.',
|
|
165
|
+
written: 'Dossiê retrospectivo gerado: {path} ({candidates} candidatos, {observations} observações).',
|
|
166
|
+
empty: 'Dossiê gerado sem propostas: {path} (fontes sem trilha minerável).',
|
|
167
|
+
io_error: 'Erro de I/O ao escrever o dossiê: {error}',
|
|
168
|
+
window_truncated: '--last={n} excede features disponíveis ({available}); minerando todas.',
|
|
169
|
+
undatable_excluded: '{count} feature(s) sem data de PASS determinável excluída(s) da janela: {slugs}'
|
|
170
|
+
},
|
|
171
|
+
harnessPreview: {
|
|
172
|
+
file_required: 'harness:preview requer um caminho de arquivo <file>.',
|
|
173
|
+
not_found: 'Arquivo não encontrado: {path}',
|
|
174
|
+
read_error: 'Não foi possível ler o arquivo: {path} ({error})'
|
|
175
|
+
},
|
|
155
176
|
help_web_map:
|
|
156
177
|
'aioson web:map [path] --url=<url> [--depth=<N>] [--max-pages=<N>] [--include-external] [--json] [--locale=pt-BR]',
|
|
157
178
|
help_web_scrape:
|
|
@@ -424,6 +445,8 @@ module.exports = {
|
|
|
424
445
|
bootstrap_coverage_hint_seed: 'Rode /discover para criar .aioson/context/bootstrap/{what-is,how-it-works,what-it-does,current-state}.md',
|
|
425
446
|
features_dir_present: 'Diretorio de features presente (.aioson/context/features/)',
|
|
426
447
|
features_dir_present_hint: 'Crie .aioson/context/features/ para hospedar dossies por feature (doctor --fix cria automaticamente).',
|
|
448
|
+
auto_handoff_declared: 'Flag de autopilot handoff declarada (auto_handoff no project.context.md)',
|
|
449
|
+
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
450
|
claude_commands_present: 'Slash commands do Claude presentes ({missing} ausentes de {required})',
|
|
428
451
|
claude_commands_present_hint: 'Ausentes: {paths}. Rode `aioson doctor . --fix` para restaurar a partir do template.',
|
|
429
452
|
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
|
+
};
|