@jaimevalasek/aioson 1.22.0 → 1.23.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/CHANGELOG.md +932 -919
  2. package/docs/en/5-reference/cli-reference.md +85 -0
  3. package/docs/pt/4-agentes/pm.md +31 -4
  4. package/docs/pt/5-referencia/README.md +3 -0
  5. package/docs/pt/5-referencia/autopilot-handoff.md +131 -0
  6. package/docs/pt/5-referencia/comandos-cli.md +72 -6
  7. package/docs/pt/5-referencia/harness-retro.md +133 -0
  8. package/docs/pt/5-referencia/loop-guardrails.md +225 -0
  9. package/docs/pt/5-referencia/sdd-automation-scripts.md +25 -13
  10. package/package.json +1 -1
  11. package/src/agents.js +1 -1
  12. package/src/cli.js +70 -29
  13. package/src/commands/agent-epilogue.js +186 -0
  14. package/src/commands/context-select.js +33 -0
  15. package/src/commands/harness-preview.js +74 -0
  16. package/src/commands/harness-retro.js +221 -0
  17. package/src/commands/preflight-context.js +13 -9
  18. package/src/commands/review-cycle.js +328 -0
  19. package/src/commands/runtime.js +4 -4
  20. package/src/commands/self-implement-loop.js +12 -2
  21. package/src/commands/state-save.js +2 -0
  22. package/src/commands/workflow-execute.js +138 -28
  23. package/src/commands/workflow-next.js +11 -2
  24. package/src/commands/workflow-status.js +30 -10
  25. package/src/constants.js +15 -13
  26. package/src/context-memory.js +50 -25
  27. package/src/context-selector.js +394 -0
  28. package/src/harness/preview-artifact.js +85 -0
  29. package/src/i18n/messages/en.js +34 -7
  30. package/src/i18n/messages/es.js +34 -7
  31. package/src/i18n/messages/fr.js +34 -7
  32. package/src/i18n/messages/pt-BR.js +34 -7
  33. package/src/lib/retro/retro-aggregate.js +192 -0
  34. package/src/lib/retro/retro-render.js +185 -0
  35. package/src/lib/retro/retro-sources.js +624 -0
  36. package/src/parser.js +1 -1
  37. package/src/squad/preflight-context.js +26 -27
  38. package/template/.aioson/agents/analyst.md +41 -46
  39. package/template/.aioson/agents/architect.md +33 -46
  40. package/template/.aioson/agents/briefing.md +76 -67
  41. package/template/.aioson/agents/dev.md +73 -55
  42. package/template/.aioson/agents/deyvin.md +55 -50
  43. package/template/.aioson/agents/discovery-design-doc.md +35 -22
  44. package/template/.aioson/agents/manifests/architect.manifest.json +11 -1
  45. package/template/.aioson/agents/manifests/dev.manifest.json +15 -0
  46. package/template/.aioson/agents/manifests/pm.manifest.json +20 -0
  47. package/template/.aioson/agents/orchestrator.md +31 -18
  48. package/template/.aioson/agents/pentester.md +12 -4
  49. package/template/.aioson/agents/pm.md +41 -35
  50. package/template/.aioson/agents/product.md +116 -165
  51. package/template/.aioson/agents/qa.md +44 -13
  52. package/template/.aioson/agents/scope-check.md +46 -24
  53. package/template/.aioson/agents/sheldon.md +13 -0
  54. package/template/.aioson/agents/tester.md +28 -5
  55. package/template/.aioson/agents/ux-ui.md +36 -31
  56. package/template/.aioson/agents/validator.md +10 -2
  57. package/template/.aioson/config/autonomy-protocol.json +7 -0
  58. package/template/.aioson/design-docs/code-reuse.md +10 -5
  59. package/template/.aioson/design-docs/componentization.md +10 -5
  60. package/template/.aioson/design-docs/file-size.md +10 -5
  61. package/template/.aioson/design-docs/folder-structure.md +10 -5
  62. package/template/.aioson/design-docs/naming.md +10 -5
  63. package/template/.aioson/docs/autonomy-protocol.md +2 -2
  64. package/template/.aioson/docs/autopilot-handoff.md +82 -34
  65. package/template/.aioson/docs/briefing/briefing-craft.md +9 -3
  66. package/template/.aioson/docs/deyvin/continuity-recovery.md +18 -22
  67. package/template/.aioson/docs/product/conversation-playbook.md +8 -3
  68. package/template/.aioson/docs/product/prd-contract.md +8 -3
  69. package/template/.aioson/docs/product/quality-lens.md +8 -3
  70. package/template/.aioson/docs/product/research-loop.md +8 -3
  71. package/template/.aioson/docs/ux-ui/accessibility-audit.md +7 -2
  72. package/template/.aioson/docs/ux-ui/audit-mode.md +7 -2
  73. package/template/.aioson/docs/ux-ui/component-map.md +7 -2
  74. package/template/.aioson/docs/ux-ui/design-execution.md +7 -2
  75. package/template/.aioson/docs/ux-ui/design-gate.md +7 -2
  76. package/template/.aioson/docs/ux-ui/research-mode.md +7 -2
  77. package/template/.aioson/docs/ux-ui/site-delivery.md +7 -2
  78. package/template/.aioson/docs/ux-ui/token-contract.md +7 -2
  79. package/template/.aioson/rules/aioson-context-boundary.md +11 -9
  80. package/template/.aioson/rules/disk-first-artifacts.md +1 -1
  81. package/template/.aioson/skills/process/aioson-spec-driven/references/approval-gates.md +1 -1
  82. package/template/.aioson/skills/process/aioson-spec-driven/references/architect.md +3 -2
  83. package/template/.aioson/skills/process/aioson-spec-driven/references/artifact-map.md +21 -9
  84. package/template/.aioson/skills/process/aioson-spec-driven/references/dev.md +2 -1
  85. package/template/.aioson/skills/process/aioson-spec-driven/references/pm.md +2 -1
  86. package/template/.aioson/skills/static/web-research-cache.md +29 -8
  87. package/template/AGENTS.md +1 -1
  88. package/template/CLAUDE.md +1 -1
@@ -21,12 +21,16 @@ module.exports = {
21
21
  'aioson agent:prompt <agent> [path] [--tool=codex|claude|opencode] [--lang=<bcp47-tag>] [--locale=en]',
22
22
  help_agent_help:
23
23
  'aioson agent:help [agent] [--json]',
24
- help_agent_invoke:
25
- 'aioson agent:invoke <agent> [path] [--tool=codex|claude|opencode] [--mode=framework_target|app_target] [--feature=<slug>] [--scope=<area>] [--lang=<bcp47-tag>] [--locale=en]',
24
+ help_agent_invoke:
25
+ 'aioson agent:invoke <agent> [path] [--tool=codex|claude|opencode] [--mode=framework_target|app_target] [--feature=<slug>] [--scope=<area>] [--lang=<bcp47-tag>] [--locale=en]',
26
+ help_agent_epilogue:
27
+ 'aioson agent:epilogue [path] --agent=<agent> --summary=<text> [--feature=<slug>] [--approve-gate=A|B|C|D] [--json] [--locale=en]',
26
28
  help_context_validate: 'aioson context:validate [path] [--json] [--locale=en]',
27
- help_context_pack:
28
- 'aioson context:pack [path] [--agent=<agent>] [--goal=<text>] [--module=<module-or-folder>] [--max-files=8] [--json] [--locale=en]',
29
- help_context_load:
29
+ help_context_pack:
30
+ 'aioson context:pack [path] [--agent=<agent>] [--goal=<text>] [--module=<module-or-folder>] [--max-files=8] [--json] [--locale=en]',
31
+ help_context_select:
32
+ 'aioson context:select [path] [--agent=<agent>] [--mode=planning|executing] [--task=<text>] [--paths=<path[,path2]>] [--feature=<slug>] [--json] [--locale=en]',
33
+ help_context_load:
30
34
  'aioson context:load [path] --target=<rule|brain>:<slug> --agent=<name> [--batch="slug1,slug2"] [--feature=<slug>] [--classification=<MICRO|SMALL|MEDIUM>] [--verbose] [--json] [--locale=en]',
31
35
  help_chain_audit:
32
36
  'aioson chain:audit <file> [path] [--limit=N] [--feature=<slug>] [--json] [--locale=en]',
@@ -118,8 +122,10 @@ module.exports = {
118
122
  'aioson workflow:next [path] [--complete[=<agent>]] [--agent=<agent>] [--skip=<agent>] [--status] [--suggest] [--tool=codex|claude|opencode] [--json] [--locale=en]',
119
123
  help_workflow_status:
120
124
  'aioson workflow:status [path] [--suggest] [--tool=codex|claude|opencode] [--json] [--locale=en]',
121
- help_workflow_execute:
122
- 'aioson workflow:execute [path] [--steps=<n>] [--dry-run] [--lane=<n>] [--json] [--locale=en]',
125
+ help_workflow_execute:
126
+ 'aioson workflow:execute [path] [--feature=<slug>] [--agentic] [--max-dev-qa-cycles=<n>] [--max-tester-cycles=<n>] [--max-pentester-cycles=<n>] [--dry-run] [--lane=<n>] [--json] [--locale=en]',
127
+ help_review_cycle:
128
+ 'aioson review-cycle:<status|advance|resolve|reset> [path] --feature=<slug> [--plan=<path>] [--source=qa|tester|pentester] [--json] [--locale=en]',
123
129
  help_parallel_init:
124
130
  'aioson parallel:init [path] [--workers=2..6] [--force] [--dry-run] [--json] [--locale=en]',
125
131
  help_parallel_doctor:
@@ -150,6 +156,27 @@ module.exports = {
150
156
  'aioson harness:init [path] --slug=<slug> [--mode=BALANCED|URGENT|ECONOMICAL] [--locale=en]',
151
157
  help_harness_validate:
152
158
  'aioson harness:validate [path] --slug=<slug> [--artifact=<path>] [--locale=en]',
159
+ help_harness_retro:
160
+ 'aioson harness:retro [path] --feature=<slug> | --last=<N> [--json] [--locale=en]',
161
+ help_harness_preview:
162
+ 'aioson harness:preview <file> [--max-bytes=8192] [--json] [--locale=en]',
163
+ harnessRetro: {
164
+ need_target: 'harness:retro requires --feature=<slug> or --last=<N>.',
165
+ invalid_slug: 'Invalid slug: {slug} (must match ^[a-z0-9][a-z0-9-]*$).',
166
+ invalid_last: 'Invalid --last value: {value} (use an integer >= 1).',
167
+ feature_not_found: 'Feature not found: {slug} (searched .aioson/context/, .aioson/plans/{slug}/, .aioson/context/features/{slug}/, .aioson/context/done/{slug}/).',
168
+ no_closed_features: 'No closed features under .aioson/context/done/ to mine.',
169
+ written: 'Retro dossier written: {path} ({candidates} candidates, {observations} observations).',
170
+ empty: 'Retro dossier written with no proposals: {path} (no minable trail).',
171
+ io_error: 'I/O error writing the dossier: {error}',
172
+ window_truncated: '--last={n} exceeds available features ({available}); mining all of them.',
173
+ undatable_excluded: '{count} feature(s) without a resolvable PASS date excluded from the window: {slugs}'
174
+ },
175
+ harnessPreview: {
176
+ file_required: 'harness:preview requires a <file> path argument.',
177
+ not_found: 'File not found: {path}',
178
+ read_error: 'Could not read file: {path} ({error})'
179
+ },
153
180
  help_web_map:
154
181
  'aioson web:map [path] --url=<url> [--depth=<N>] [--max-pages=<N>] [--include-external] [--json] [--locale=en]',
155
182
  help_web_scrape:
@@ -22,12 +22,16 @@ module.exports = {
22
22
  'aioson agent:prompt <agent> [path] [--tool=codex|claude|opencode] [--lang=<bcp47-tag>] [--locale=es]',
23
23
  help_agent_help:
24
24
  'aioson agent:help [agent] [--json]',
25
- help_agent_invoke:
26
- 'aioson agent:invoke <agent> [path] [--tool=codex|claude|opencode] [--mode=framework_target|app_target] [--feature=<slug>] [--scope=<area>] [--lang=<bcp47-tag>] [--locale=es]',
25
+ help_agent_invoke:
26
+ 'aioson agent:invoke <agent> [path] [--tool=codex|claude|opencode] [--mode=framework_target|app_target] [--feature=<slug>] [--scope=<area>] [--lang=<bcp47-tag>] [--locale=es]',
27
+ help_agent_epilogue:
28
+ 'aioson agent:epilogue [path] --agent=<agente> --summary=<texto> [--feature=<slug>] [--approve-gate=A|B|C|D] [--json] [--locale=es]',
27
29
  help_context_validate: 'aioson context:validate [path] [--json] [--locale=es]',
28
- help_context_pack:
29
- 'aioson context:pack [path] [--agent=<agente>] [--goal=<texto>] [--module=<modulo-o-carpeta>] [--max-files=8] [--json] [--locale=es]',
30
- help_context_load:
30
+ help_context_pack:
31
+ 'aioson context:pack [path] [--agent=<agente>] [--goal=<texto>] [--module=<modulo-o-carpeta>] [--max-files=8] [--json] [--locale=es]',
32
+ help_context_select:
33
+ 'aioson context:select [path] [--agent=<agente>] [--mode=planning|executing] [--task=<texto>] [--paths=<ruta[,ruta2]>] [--feature=<slug>] [--json] [--locale=es]',
34
+ help_context_load:
31
35
  'aioson context:load [path] --target=<rule|brain>:<slug> --agent=<nombre> [--batch="slug1,slug2"] [--feature=<slug>] [--classification=<MICRO|SMALL|MEDIUM>] [--verbose] [--json] [--locale=es]',
32
36
  help_chain_audit:
33
37
  'aioson chain:audit <archivo> [path] [--limit=N] [--feature=<slug>] [--json] [--locale=es]',
@@ -108,8 +112,10 @@ module.exports = {
108
112
  'aioson test:package [source-path] [--keep] [--dry-run] [--json] [--locale=es]',
109
113
  help_workflow_plan:
110
114
  'aioson workflow:plan [path] [--classification=MICRO|SMALL|MEDIUM] [--json] [--locale=es]',
111
- help_workflow_execute:
112
- 'aioson workflow:execute [path] [--steps=<n>] [--dry-run] [--lane=<n>] [--json] [--locale=es]',
115
+ help_workflow_execute:
116
+ 'aioson workflow:execute [path] [--feature=<slug>] [--agentic] [--max-dev-qa-cycles=<n>] [--max-tester-cycles=<n>] [--max-pentester-cycles=<n>] [--dry-run] [--lane=<n>] [--json] [--locale=es]',
117
+ help_review_cycle:
118
+ 'aioson review-cycle:<status|advance|resolve|reset> [path] --feature=<slug> [--plan=<path>] [--source=qa|tester|pentester] [--json] [--locale=es]',
113
119
  help_parallel_init:
114
120
  'aioson parallel:init [path] [--workers=2..6] [--force] [--dry-run] [--json] [--locale=es]',
115
121
  help_parallel_doctor:
@@ -136,6 +142,27 @@ module.exports = {
136
142
  'aioson qa:scan [path] [--url=<app-url>] [--depth=3] [--max-pages=50] [--headed] [--html] [--json] [--locale=es]',
137
143
  help_qa_report:
138
144
  'aioson qa:report [path] [--html] [--json] [--locale=es]',
145
+ help_harness_retro:
146
+ 'aioson harness:retro [path] --feature=<slug> | --last=<N> [--json] [--locale=es]',
147
+ help_harness_preview:
148
+ 'aioson harness:preview <file> [--max-bytes=8192] [--json] [--locale=es]',
149
+ harnessRetro: {
150
+ need_target: 'harness:retro requiere --feature=<slug> o --last=<N>.',
151
+ invalid_slug: 'Slug inválido: {slug} (debe cumplir ^[a-z0-9][a-z0-9-]*$).',
152
+ invalid_last: 'Valor inválido para --last: {value} (use un entero >= 1).',
153
+ feature_not_found: 'Feature no encontrada: {slug} (buscado en .aioson/context/, .aioson/plans/{slug}/, .aioson/context/features/{slug}/, .aioson/context/done/{slug}/).',
154
+ no_closed_features: 'No hay features cerradas en .aioson/context/done/ para minar.',
155
+ written: 'Dosier retrospectivo generado: {path} ({candidates} candidatos, {observations} observaciones).',
156
+ empty: 'Dosier generado sin propuestas: {path} (fuentes sin rastro minable).',
157
+ io_error: 'Error de E/S al escribir el dosier: {error}',
158
+ window_truncated: '--last={n} supera las features disponibles ({available}); minando todas.',
159
+ undatable_excluded: '{count} feature(s) sin fecha de PASS resoluble excluida(s) de la ventana: {slugs}'
160
+ },
161
+ harnessPreview: {
162
+ file_required: 'harness:preview requiere una ruta de archivo <file>.',
163
+ not_found: 'Archivo no encontrado: {path}',
164
+ read_error: 'No se pudo leer el archivo: {path} ({error})'
165
+ },
139
166
  help_web_map:
140
167
  'aioson web:map [path] --url=<url> [--depth=<N>] [--max-pages=<N>] [--include-external] [--json] [--locale=es]',
141
168
  help_web_scrape:
@@ -22,12 +22,16 @@ module.exports = {
22
22
  'aioson agent:prompt <agent> [path] [--tool=codex|claude|opencode] [--lang=<bcp47-tag>] [--locale=fr]',
23
23
  help_agent_help:
24
24
  'aioson agent:help [agent] [--json]',
25
- help_agent_invoke:
26
- 'aioson agent:invoke <agent> [path] [--tool=codex|claude|opencode] [--mode=framework_target|app_target] [--feature=<slug>] [--scope=<area>] [--lang=<bcp47-tag>] [--locale=fr]',
25
+ help_agent_invoke:
26
+ 'aioson agent:invoke <agent> [path] [--tool=codex|claude|opencode] [--mode=framework_target|app_target] [--feature=<slug>] [--scope=<area>] [--lang=<bcp47-tag>] [--locale=fr]',
27
+ help_agent_epilogue:
28
+ 'aioson agent:epilogue [path] --agent=<agent> --summary=<texte> [--feature=<slug>] [--approve-gate=A|B|C|D] [--json] [--locale=fr]',
27
29
  help_context_validate: 'aioson context:validate [path] [--json] [--locale=fr]',
28
- help_context_pack:
29
- 'aioson context:pack [path] [--agent=<agent>] [--goal=<texte>] [--module=<module-ou-dossier>] [--max-files=8] [--json] [--locale=fr]',
30
- help_context_load:
30
+ help_context_pack:
31
+ 'aioson context:pack [path] [--agent=<agent>] [--goal=<texte>] [--module=<module-ou-dossier>] [--max-files=8] [--json] [--locale=fr]',
32
+ help_context_select:
33
+ 'aioson context:select [path] [--agent=<agent>] [--mode=planning|executing] [--task=<texte>] [--paths=<chemin[,chemin2]>] [--feature=<slug>] [--json] [--locale=fr]',
34
+ help_context_load:
31
35
  'aioson context:load [path] --target=<rule|brain>:<slug> --agent=<nom> [--batch="slug1,slug2"] [--feature=<slug>] [--classification=<MICRO|SMALL|MEDIUM>] [--verbose] [--json] [--locale=fr]',
32
36
  help_chain_audit:
33
37
  'aioson chain:audit <fichier> [path] [--limit=N] [--feature=<slug>] [--json] [--locale=fr]',
@@ -108,8 +112,10 @@ module.exports = {
108
112
  'aioson test:package [source-path] [--keep] [--dry-run] [--json] [--locale=fr]',
109
113
  help_workflow_plan:
110
114
  'aioson workflow:plan [path] [--classification=MICRO|SMALL|MEDIUM] [--json] [--locale=fr]',
111
- help_workflow_execute:
112
- 'aioson workflow:execute [path] [--steps=<n>] [--dry-run] [--lane=<n>] [--json] [--locale=fr]',
115
+ help_workflow_execute:
116
+ 'aioson workflow:execute [path] [--feature=<slug>] [--agentic] [--max-dev-qa-cycles=<n>] [--max-tester-cycles=<n>] [--max-pentester-cycles=<n>] [--dry-run] [--lane=<n>] [--json] [--locale=fr]',
117
+ help_review_cycle:
118
+ 'aioson review-cycle:<status|advance|resolve|reset> [path] --feature=<slug> [--plan=<path>] [--source=qa|tester|pentester] [--json] [--locale=fr]',
113
119
  help_parallel_init:
114
120
  'aioson parallel:init [path] [--workers=2..6] [--force] [--dry-run] [--json] [--locale=fr]',
115
121
  help_parallel_doctor:
@@ -136,6 +142,27 @@ module.exports = {
136
142
  'aioson qa:scan [path] [--url=<app-url>] [--depth=3] [--max-pages=50] [--headed] [--html] [--json] [--locale=fr]',
137
143
  help_qa_report:
138
144
  'aioson qa:report [path] [--html] [--json] [--locale=fr]',
145
+ help_harness_retro:
146
+ 'aioson harness:retro [path] --feature=<slug> | --last=<N> [--json] [--locale=fr]',
147
+ help_harness_preview:
148
+ 'aioson harness:preview <file> [--max-bytes=8192] [--json] [--locale=fr]',
149
+ harnessRetro: {
150
+ need_target: 'harness:retro requiert --feature=<slug> ou --last=<N>.',
151
+ invalid_slug: 'Slug invalide : {slug} (doit correspondre à ^[a-z0-9][a-z0-9-]*$).',
152
+ invalid_last: 'Valeur --last invalide : {value} (utilisez un entier >= 1).',
153
+ feature_not_found: 'Feature introuvable : {slug} (cherché dans .aioson/context/, .aioson/plans/{slug}/, .aioson/context/features/{slug}/, .aioson/context/done/{slug}/).',
154
+ no_closed_features: 'Aucune feature clôturée dans .aioson/context/done/ à miner.',
155
+ written: 'Dossier rétrospectif généré : {path} ({candidates} candidats, {observations} observations).',
156
+ empty: 'Dossier généré sans propositions : {path} (sources sans piste exploitable).',
157
+ io_error: 'Erreur d’E/S lors de l’écriture du dossier : {error}',
158
+ window_truncated: '--last={n} dépasse les features disponibles ({available}) ; minage de toutes.',
159
+ undatable_excluded: '{count} feature(s) sans date de PASS résoluble exclue(s) de la fenêtre : {slugs}'
160
+ },
161
+ harnessPreview: {
162
+ file_required: 'harness:preview requiert un chemin de fichier <file>.',
163
+ not_found: 'Fichier introuvable : {path}',
164
+ read_error: 'Impossible de lire le fichier : {path} ({error})'
165
+ },
139
166
  help_web_map:
140
167
  'aioson web:map [path] --url=<url> [--depth=<N>] [--max-pages=<N>] [--include-external] [--json] [--locale=fr]',
141
168
  help_web_scrape:
@@ -22,12 +22,16 @@ module.exports = {
22
22
  'aioson agent:prompt <agent> [path] [--tool=codex|claude|opencode] [--lang=<bcp47-tag>] [--locale=pt-BR]',
23
23
  help_agent_help:
24
24
  'aioson agent:help [agent] [--json]',
25
- help_agent_invoke:
26
- 'aioson agent:invoke <agent> [path] [--tool=codex|claude|opencode] [--mode=framework_target|app_target] [--feature=<slug>] [--scope=<area>] [--lang=<bcp47-tag>] [--locale=pt-BR]',
25
+ help_agent_invoke:
26
+ 'aioson agent:invoke <agent> [path] [--tool=codex|claude|opencode] [--mode=framework_target|app_target] [--feature=<slug>] [--scope=<area>] [--lang=<bcp47-tag>] [--locale=pt-BR]',
27
+ help_agent_epilogue:
28
+ 'aioson agent:epilogue [path] --agent=<agente> --summary=<texto> [--feature=<slug>] [--approve-gate=A|B|C|D] [--json] [--locale=pt-BR]',
27
29
  help_context_validate: 'aioson context:validate [path] [--json] [--locale=pt-BR]',
28
- help_context_pack:
29
- 'aioson context:pack [path] [--agent=<agente>] [--goal=<texto>] [--module=<modulo-ou-pasta>] [--max-files=8] [--json] [--locale=pt-BR]',
30
- help_context_load:
30
+ help_context_pack:
31
+ 'aioson context:pack [path] [--agent=<agente>] [--goal=<texto>] [--module=<modulo-ou-pasta>] [--max-files=8] [--json] [--locale=pt-BR]',
32
+ help_context_select:
33
+ 'aioson context:select [path] [--agent=<agente>] [--mode=planning|executing] [--task=<texto>] [--paths=<caminho[,caminho2]>] [--feature=<slug>] [--json] [--locale=pt-BR]',
34
+ help_context_load:
31
35
  'aioson context:load [path] --target=<rule|brain>:<slug> --agent=<nome> [--batch="slug1,slug2"] [--feature=<slug>] [--classification=<MICRO|SMALL|MEDIUM>] [--verbose] [--json] [--locale=pt-BR]',
32
36
  help_chain_audit:
33
37
  'aioson chain:audit <arquivo> [path] [--limit=N] [--feature=<slug>] [--json] [--locale=pt-BR]',
@@ -120,8 +124,10 @@ module.exports = {
120
124
  'aioson workflow:next [path] [--complete[=<agente>]] [--agent=<agente>] [--skip=<agente>] [--status] [--suggest] [--tool=codex|claude|opencode] [--json] [--locale=pt-BR]',
121
125
  help_workflow_status:
122
126
  'aioson workflow:status [path] [--suggest] [--tool=codex|claude|opencode] [--json] [--locale=pt-BR]',
123
- help_workflow_execute:
124
- 'aioson workflow:execute [path] [--steps=<n>] [--dry-run] [--lane=<n>] [--json] [--locale=pt-BR]',
127
+ help_workflow_execute:
128
+ 'aioson workflow:execute [path] [--feature=<slug>] [--agentic] [--max-dev-qa-cycles=<n>] [--max-tester-cycles=<n>] [--max-pentester-cycles=<n>] [--dry-run] [--lane=<n>] [--json] [--locale=pt-BR]',
129
+ help_review_cycle:
130
+ 'aioson review-cycle:<status|advance|resolve|reset> [path] --feature=<slug> [--plan=<path>] [--source=qa|tester|pentester] [--json] [--locale=pt-BR]',
125
131
  help_parallel_init:
126
132
  'aioson parallel:init [path] [--workers=2..6] [--force] [--dry-run] [--json] [--locale=pt-BR]',
127
133
  help_parallel_doctor:
@@ -152,6 +158,27 @@ module.exports = {
152
158
  'aioson harness:init [path] --slug=<slug> [--mode=BALANCED|URGENT|ECONOMICAL] [--locale=pt-BR]',
153
159
  help_harness_validate:
154
160
  'aioson harness:validate [path] --slug=<slug> [--artifact=<path>] [--locale=pt-BR]',
161
+ help_harness_retro:
162
+ 'aioson harness:retro [path] --feature=<slug> | --last=<N> [--json] [--locale=pt-BR]',
163
+ help_harness_preview:
164
+ 'aioson harness:preview <file> [--max-bytes=8192] [--json] [--locale=pt-BR]',
165
+ harnessRetro: {
166
+ need_target: 'harness:retro requer --feature=<slug> ou --last=<N>.',
167
+ invalid_slug: 'Slug inválido: {slug} (deve casar ^[a-z0-9][a-z0-9-]*$).',
168
+ invalid_last: 'Valor inválido para --last: {value} (use inteiro >= 1).',
169
+ feature_not_found: 'Feature não encontrada: {slug} (procurado em .aioson/context/, .aioson/plans/{slug}/, .aioson/context/features/{slug}/, .aioson/context/done/{slug}/).',
170
+ no_closed_features: 'Nenhuma feature fechada em .aioson/context/done/ para minerar.',
171
+ written: 'Dossiê retrospectivo gerado: {path} ({candidates} candidatos, {observations} observações).',
172
+ empty: 'Dossiê gerado sem propostas: {path} (fontes sem trilha minerável).',
173
+ io_error: 'Erro de I/O ao escrever o dossiê: {error}',
174
+ window_truncated: '--last={n} excede features disponíveis ({available}); minerando todas.',
175
+ undatable_excluded: '{count} feature(s) sem data de PASS determinável excluída(s) da janela: {slugs}'
176
+ },
177
+ harnessPreview: {
178
+ file_required: 'harness:preview requer um caminho de arquivo <file>.',
179
+ not_found: 'Arquivo não encontrado: {path}',
180
+ read_error: 'Não foi possível ler o arquivo: {path} ({error})'
181
+ },
155
182
  help_web_map:
156
183
  'aioson web:map [path] --url=<url> [--depth=<N>] [--max-pages=<N>] [--include-external] [--json] [--locale=pt-BR]',
157
184
  help_web_scrape:
@@ -0,0 +1,192 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Agregação determinística para `aioson harness:retro` (requirements §3.1/§7).
5
+ *
6
+ * Agrupa findings por CHAVE DETERMINÍSTICA EXATA — nunca por classe semântica
7
+ * (isso é trabalho do @sheldon). A chave inclui sempre o slug (um finding-ID
8
+ * como C-01 existe em quase toda feature; nunca agrupar entre features — edge 5).
9
+ *
10
+ * Critério anti-opinião (REQ-2): um grupo vira "Proposta candidata" SOMENTE se
11
+ * (a) ≥2 ocorrências da mesma chave, OU
12
+ * (b) ≥1 ocorrência de severidade high/critical, OU
13
+ * (c) a feature tem ≥2 ciclos FAIL→PASS.
14
+ * Severidade `unknown` nunca satisfaz (b) sozinha; todo o resto vai para
15
+ * "Observações". (a) é independente de severidade — assinaturas sha1 (severidade
16
+ * unknown) promovem ao repetir, AC-6.
17
+ */
18
+
19
+ const crypto = require('node:crypto');
20
+
21
+ const SEVERITY_RANK = { critical: 5, high: 4, medium: 3, low: 2, info: 1, unknown: 0 };
22
+ const PHASE_RE = /(?:[-_/]|\b)(ph\d+|phase[-_]?\d+)\b/i;
23
+
24
+ function severityRank(sev) {
25
+ return SEVERITY_RANK[sev] ?? 0;
26
+ }
27
+
28
+ function sha1short(text) {
29
+ return crypto.createHash('sha1').update(String(text)).digest('hex').slice(0, 12);
30
+ }
31
+
32
+ /** Token de phase derivado do path da fonte (edge 4 — desambiguação entre phases). */
33
+ function phaseToken(sourcePath) {
34
+ if (!sourcePath) return '';
35
+ const m = String(sourcePath).match(PHASE_RE);
36
+ return m ? m[1].toLowerCase().replace(/[-_]/g, '') : '';
37
+ }
38
+
39
+ /** Chave determinística exata de um finding (slug sempre incluído). */
40
+ function groupKey(f) {
41
+ const phase = phaseToken(f.source_path);
42
+ const prefix = phase ? `${f.feature_slug}::${phase}` : f.feature_slug;
43
+ if (f.signature) return `${prefix}::sig:${f.signature}`;
44
+ if (f.finding_id) return `${prefix}::${f.finding_id}`;
45
+ return `${prefix}::title:${sha1short(f.title || '')}`;
46
+ }
47
+
48
+ /** Ordenação estável de ocorrências: severidade (critical→low), depois data, depois path. */
49
+ function compareOccurrences(a, b) {
50
+ const sr = severityRank(b.severity) - severityRank(a.severity);
51
+ if (sr !== 0) return sr;
52
+ const da = a.date || '';
53
+ const db = b.date || '';
54
+ if (da !== db) return da < db ? -1 : 1;
55
+ return (a.source_path || '') < (b.source_path || '') ? -1 : (a.source_path || '') > (b.source_path || '') ? 1 : 0;
56
+ }
57
+
58
+ /**
59
+ * @param {object} sources — saída de `collectSources` ({ findings, cycles, cost, costByFeature })
60
+ * @returns {{ candidates, observations, cost }}
61
+ */
62
+ function aggregate(sources) {
63
+ const findings = Array.isArray(sources.findings) ? sources.findings : [];
64
+ const cycles = Array.isArray(sources.cycles) ? sources.cycles : [];
65
+ const costByFeature = sources.costByFeature || {};
66
+
67
+ // Ciclos FAIL→PASS por feature (ordenados por data).
68
+ const cyclesByFeature = new Map();
69
+ for (const c of cycles) {
70
+ if (!cyclesByFeature.has(c.feature_slug)) cyclesByFeature.set(c.feature_slug, []);
71
+ cyclesByFeature.get(c.feature_slug).push(c);
72
+ }
73
+ for (const arr of cyclesByFeature.values()) {
74
+ arr.sort((a, b) => (a.fail_at < b.fail_at ? -1 : a.fail_at > b.fail_at ? 1 : 0));
75
+ }
76
+
77
+ // Agrupa findings por chave exata.
78
+ const groups = new Map();
79
+ for (const f of findings) {
80
+ const key = groupKey(f);
81
+ if (!groups.has(key)) groups.set(key, { key, feature_slug: f.feature_slug, finding_id: f.finding_id, signature: f.signature, occurrences: [] });
82
+ groups.get(key).occurrences.push(f);
83
+ }
84
+
85
+ const candidates = [];
86
+ const observations = [];
87
+
88
+ for (const g of groups.values()) {
89
+ g.occurrences.sort(compareOccurrences);
90
+ const maxSeverity = g.occurrences.reduce((acc, o) => (severityRank(o.severity) > severityRank(acc) ? o.severity : acc), 'unknown');
91
+ const occCount = g.occurrences.length;
92
+ const featureCycles = cyclesByFeature.get(g.feature_slug) || [];
93
+
94
+ const reasons = [];
95
+ if (occCount >= 2) reasons.push('recurrence');
96
+ if (severityRank(maxSeverity) >= severityRank('high')) reasons.push('severity');
97
+ if (featureCycles.length >= 2) reasons.push('fail_pass_cycle');
98
+
99
+ const feCost = costByFeature[g.feature_slug] || {};
100
+ const cost = {
101
+ occurrences: occCount,
102
+ corrections: g.occurrences.filter((o) => o.source_type === 'corrections').length,
103
+ fail_pass_cycles: featureCycles.length,
104
+ cycle_dates: featureCycles.map((c) => `${c.fail_at}→${c.pass_at}`),
105
+ execution_events: feCost.execution_events || 0,
106
+ corrections_bytes: feCost.corrections_bytes || 0,
107
+ tokens: feCost.token_count_available ? (feCost.token_total || 0) : null
108
+ };
109
+
110
+ if (reasons.length > 0) {
111
+ candidates.push({
112
+ key: g.key,
113
+ feature_slug: g.feature_slug,
114
+ finding_id: g.finding_id || null,
115
+ signature: g.signature || null,
116
+ max_severity: maxSeverity,
117
+ reasons,
118
+ occurrences: g.occurrences,
119
+ corrections_link: (g.occurrences.find((o) => o.source_type === 'corrections') || {}).source_path || null,
120
+ cost
121
+ });
122
+ } else {
123
+ // Ocorrência única Medium/Low/info/unknown → Observação (uma linha).
124
+ const o = g.occurrences[0];
125
+ observations.push({
126
+ key: g.key,
127
+ feature_slug: g.feature_slug,
128
+ finding_id: g.finding_id || null,
129
+ severity: o.severity,
130
+ title: o.title,
131
+ date: o.date,
132
+ source_path: o.source_path
133
+ });
134
+ }
135
+ }
136
+
137
+ // Candidato sintético por feature com ≥2 ciclos sem finding-âncora já candidato.
138
+ for (const [slug, arr] of cyclesByFeature.entries()) {
139
+ if (arr.length < 2) continue;
140
+ const alreadyCovered = candidates.some((c) => c.feature_slug === slug && c.reasons.includes('fail_pass_cycle'));
141
+ if (alreadyCovered) continue;
142
+ const feCost = costByFeature[slug] || {};
143
+ candidates.push({
144
+ key: `${slug}::cycles`,
145
+ feature_slug: slug,
146
+ finding_id: null,
147
+ signature: null,
148
+ max_severity: 'unknown',
149
+ reasons: ['fail_pass_cycle'],
150
+ occurrences: [],
151
+ corrections_link: null,
152
+ cost: {
153
+ occurrences: 0,
154
+ corrections: feCost.corrections || 0,
155
+ fail_pass_cycles: arr.length,
156
+ cycle_dates: arr.map((c) => `${c.fail_at}→${c.pass_at}`),
157
+ execution_events: feCost.execution_events || 0,
158
+ corrections_bytes: feCost.corrections_bytes || 0,
159
+ tokens: feCost.token_count_available ? (feCost.token_total || 0) : null
160
+ }
161
+ });
162
+ }
163
+
164
+ // Ordena candidatos: severidade (critical→low), depois data mais antiga, depois chave.
165
+ candidates.sort((a, b) => {
166
+ const sr = severityRank(b.max_severity) - severityRank(a.max_severity);
167
+ if (sr !== 0) return sr;
168
+ const da = (a.occurrences[0] && a.occurrences[0].date) || '';
169
+ const db = (b.occurrences[0] && b.occurrences[0].date) || '';
170
+ if (da !== db) return da < db ? -1 : 1;
171
+ return a.key < b.key ? -1 : a.key > b.key ? 1 : 0;
172
+ });
173
+
174
+ // Ordena observações: severidade, depois data, depois chave.
175
+ observations.sort((a, b) => {
176
+ const sr = severityRank(b.severity) - severityRank(a.severity);
177
+ if (sr !== 0) return sr;
178
+ const da = a.date || '';
179
+ const db = b.date || '';
180
+ if (da !== db) return da < db ? -1 : 1;
181
+ return a.key < b.key ? -1 : a.key > b.key ? 1 : 0;
182
+ });
183
+
184
+ return { candidates, observations, cost: sources.cost || {} };
185
+ }
186
+
187
+ module.exports = {
188
+ aggregate,
189
+ groupKey,
190
+ severityRank,
191
+ _internal: { phaseToken, compareOccurrences }
192
+ };
@@ -0,0 +1,185 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Renderiza o dossiê retrospectivo em Markdown (requirements §3.1).
5
+ *
6
+ * Saída byte-estável exceto `generated_at` (AC-4): nenhuma fonte de
7
+ * não-determinismo além do timestamp injetado pelo caller. As 4 seções existem
8
+ * SEMPRE — vazias com placeholder. O conteúdo do dossiê é um artefato em idioma
9
+ * fixo (pt-BR, conforme §3.1); `--locale` afeta só as mensagens de stdout.
10
+ */
11
+
12
+ const SOURCE_ORDER = ['qa_reports', 'corrections', 'dossier_trail', 'execution_events', 'attempts', 'failure_signatures', 'devlogs'];
13
+
14
+ // Defesa em profundidade (SF-01): o dossiê vira contexto do @sheldon. Texto livre
15
+ // minerado (títulos) é apresentado como DADO inline, nunca como estrutura Markdown
16
+ // injetável. Neutraliza newlines/controles/bidi/zero-width — impede que um título
17
+ // forjado injete um header `## …`, um fence ``` ou um bloco de instrução no dossiê.
18
+ // Determinístico e byte-estável: identidade sobre texto limpo (sem esses chars).
19
+ const INJECTABLE_CHARS_RE = new RegExp(
20
+ '[\\u0000-\\u001F\\u007F\\u200B-\\u200F\\u2028\\u2029\\u202A-\\u202E\\u2066-\\u2069\\uFEFF]',
21
+ 'g'
22
+ );
23
+
24
+ function neutralizeText(value) {
25
+ if (value === null || value === undefined) return '';
26
+ return String(value).replace(INJECTABLE_CHARS_RE, ' ').trim();
27
+ }
28
+
29
+ function severityLabel(sev) {
30
+ return sev || 'unknown';
31
+ }
32
+
33
+ function fmtCost(cost) {
34
+ const parts = [
35
+ `ocorrências ${cost.occurrences}`,
36
+ `correções ${cost.corrections}`,
37
+ `ciclos FAIL→PASS ${cost.fail_pass_cycles}${cost.cycle_dates && cost.cycle_dates.length ? ` (${cost.cycle_dates.join('; ')})` : ''}`,
38
+ `eventos ${cost.execution_events}`,
39
+ `bytes corrections ${cost.corrections_bytes}`,
40
+ `tokens ${cost.tokens === null || cost.tokens === undefined ? 'n/d' : cost.tokens}`
41
+ ];
42
+ return parts.join(', ');
43
+ }
44
+
45
+ function renderCandidate(c) {
46
+ const lines = [];
47
+ const anchor = c.finding_id || (c.signature ? `sig:${c.signature.slice(0, 12)}` : c.key);
48
+ lines.push(`### ${c.key}`);
49
+ lines.push('');
50
+ lines.push(`- Âncora: ${anchor} | severidade máxima: ${severityLabel(c.max_severity)} | motivos: ${c.reasons.join(', ')}`);
51
+ if (c.occurrences.length > 0) {
52
+ lines.push(`- Ocorrências (${c.occurrences.length}):`);
53
+ for (const o of c.occurrences) {
54
+ const id = o.finding_id || (o.signature ? `sig:${o.signature.slice(0, 12)}` : '—');
55
+ lines.push(` - (${o.feature_slug}, ${id}, ${severityLabel(o.severity)}, ${o.date || 'sem-data'}, ${o.source_path}, ${o.status})`);
56
+ }
57
+ } else {
58
+ lines.push('- Ocorrências: ciclos FAIL→PASS recorrentes (sem finding-âncora único)');
59
+ }
60
+ lines.push(`- Correções aplicadas: ${c.corrections_link || '—'}`);
61
+ lines.push(`- Custo de retrabalho: ${fmtCost(c.cost)}`);
62
+ lines.push('');
63
+ return lines.join('\n');
64
+ }
65
+
66
+ function renderObservation(o) {
67
+ const id = o.finding_id || '—';
68
+ return `- (${o.feature_slug}, ${id}, ${severityLabel(o.severity)}, ${o.date || 'sem-data'}) — ${neutralizeText(o.title) || id} [${o.source_path}]`;
69
+ }
70
+
71
+ function renderFrontmatter({ mode, slug, windowN, featuresMined, counts, candidatesCount, observationsCount, generatedAt }) {
72
+ const lines = ['---'];
73
+ if (mode === 'window') {
74
+ lines.push(`window: last-${windowN}`);
75
+ } else {
76
+ lines.push(`feature: ${slug}`);
77
+ }
78
+ lines.push(`generated_at: ${generatedAt}`);
79
+ lines.push('generated_by: harness-retro');
80
+ lines.push('schema_version: "1.0"');
81
+ lines.push(`features_mined: [${featuresMined.join(', ')}]`);
82
+ lines.push('sources:');
83
+ for (const key of SOURCE_ORDER) {
84
+ lines.push(` ${key}: ${counts[key] || 0}`);
85
+ }
86
+ lines.push(`candidates: ${candidatesCount}`);
87
+ lines.push(`observations: ${observationsCount}`);
88
+ lines.push('---');
89
+ return lines.join('\n');
90
+ }
91
+
92
+ /**
93
+ * @param {object} opts
94
+ * @param {'feature'|'window'} opts.mode
95
+ * @param {string} [opts.slug]
96
+ * @param {number} [opts.windowN]
97
+ * @param {string[]} opts.featuresMined
98
+ * @param {object} opts.counts — contagens por fonte
99
+ * @param {Array} opts.candidates
100
+ * @param {Array} opts.observations
101
+ * @param {string[]} opts.minedPaths
102
+ * @param {string[]} opts.warnings
103
+ * @param {string} opts.dossierRelPath — path relativo deste dossiê (para o Próximo passo)
104
+ * @param {string} opts.generatedAt — ISO 8601 (única fonte de não-determinismo)
105
+ * @returns {string}
106
+ */
107
+ function renderDossier(opts) {
108
+ const {
109
+ mode, slug, windowN, featuresMined, counts,
110
+ candidates, observations, minedPaths, warnings, dossierRelPath, generatedAt
111
+ } = opts;
112
+
113
+ const blocks = [];
114
+
115
+ blocks.push(renderFrontmatter({
116
+ mode, slug, windowN, featuresMined, counts,
117
+ candidatesCount: candidates.length, observationsCount: observations.length, generatedAt
118
+ }));
119
+
120
+ const title = mode === 'window' ? `Dossiê retrospectivo — janela last-${windowN}` : `Dossiê retrospectivo — ${slug}`;
121
+ blocks.push(`\n# ${title}\n`);
122
+
123
+ // 1. Propostas candidatas
124
+ const sec1 = ['## Propostas candidatas', ''];
125
+ if (candidates.length === 0) {
126
+ sec1.push('_(nenhuma proposta candidata — nenhum item atende ao critério REQ-2)_');
127
+ } else {
128
+ for (const c of candidates) sec1.push(renderCandidate(c));
129
+ }
130
+ blocks.push(sec1.join('\n'));
131
+
132
+ // 2. Observações
133
+ const sec2 = ['', '## Observações', ''];
134
+ if (observations.length === 0) {
135
+ sec2.push('_(nenhuma observação)_');
136
+ } else {
137
+ for (const o of observations) sec2.push(renderObservation(o));
138
+ }
139
+ blocks.push(sec2.join('\n'));
140
+
141
+ // 3. Trilha minerada
142
+ const sec3 = ['', '## Trilha minerada', '', '### Paths minerados'];
143
+ if (minedPaths.length === 0) {
144
+ sec3.push('- _(nenhum path encontrado)_');
145
+ } else {
146
+ for (const p of minedPaths) sec3.push(`- ${p}`);
147
+ }
148
+ sec3.push('', '### Contagens por fonte');
149
+ for (const key of SOURCE_ORDER) {
150
+ sec3.push(`- ${key}: ${counts[key] || 0}`);
151
+ }
152
+ sec3.push('', '### Avisos');
153
+ if (warnings.length === 0) {
154
+ sec3.push('- _(nenhum aviso — todas as fontes lidas sem degradação)_');
155
+ } else {
156
+ for (const w of warnings) sec3.push(`- ${w}`);
157
+ }
158
+ blocks.push(sec3.join('\n'));
159
+
160
+ // 4. Próximo passo (texto fixo — REQ-5)
161
+ const sec4 = [
162
+ '',
163
+ '## Próximo passo',
164
+ '',
165
+ `Ative o @sheldon sob demanda para analisar este dossiê (\`${dossierRelPath}\`):`,
166
+ '',
167
+ '```',
168
+ `aioson agent:prompt sheldon . --task="analisar ${dossierRelPath}"`,
169
+ '```',
170
+ '',
171
+ 'Critério de promoção (REQ-2): só vira proposta o item com ≥2 ocorrências da mesma chave determinística, ≥1 finding High/Critical, ou ≥2 ciclos FAIL→PASS na mesma feature.',
172
+ '',
173
+ '@sheldon classifica as classes de falha citando as ocorrências deste dossiê e propõe deltas que aterrissam APENAS em `.aioson/learnings/` e `.aioson/rules/`, sempre com aprovação humana. A CLI minera e materializa; ela nunca auto-aplica deltas.',
174
+ ''
175
+ ];
176
+ blocks.push(sec4.join('\n'));
177
+
178
+ // Normaliza para LF e garante newline final único (byte-estável).
179
+ return `${blocks.join('\n').replace(/\r\n/g, '\n').replace(/\n+$/, '')}\n`;
180
+ }
181
+
182
+ module.exports = {
183
+ renderDossier,
184
+ _internal: { renderFrontmatter, renderCandidate, fmtCost, neutralizeText, SOURCE_ORDER }
185
+ };