sinapse-ai 1.20.0 β†’ 1.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/.sinapse-ai/core/atlas/render-html.js +10 -8
  2. package/.sinapse-ai/core/orchestration/brownfield-progress.js +219 -0
  3. package/.sinapse-ai/core/orchestration/doc-first-resolver.js +3 -0
  4. package/.sinapse-ai/core/orchestration/greenfield-handler.js +97 -0
  5. package/.sinapse-ai/core/orchestration/workflow-executor.js +17 -1
  6. package/.sinapse-ai/core/registry/service-registry.json +2 -32
  7. package/.sinapse-ai/data/entity-registry.yaml +473 -203
  8. package/.sinapse-ai/development/agents/analyst.md +18 -34
  9. package/.sinapse-ai/development/agents/architect.md +18 -34
  10. package/.sinapse-ai/development/agents/data-engineer.md +15 -28
  11. package/.sinapse-ai/development/agents/developer.md +0 -6
  12. package/.sinapse-ai/development/agents/devops.md +15 -28
  13. package/.sinapse-ai/development/agents/product-lead.md +18 -34
  14. package/.sinapse-ai/development/agents/project-lead.md +18 -34
  15. package/.sinapse-ai/development/agents/quality-gate.md +18 -34
  16. package/.sinapse-ai/development/agents/squad-creator.md +15 -27
  17. package/.sinapse-ai/development/agents/ux-design-expert.md +15 -27
  18. package/.sinapse-ai/development/scripts/populate-entity-registry.js +3 -0
  19. package/.sinapse-ai/development/tasks/execute-epic-plan.md +3 -4
  20. package/.sinapse-ai/development/workflows/brownfield-discovery.yaml +16 -0
  21. package/.sinapse-ai/development/workflows/epic-orchestration.yaml +11 -1
  22. package/.sinapse-ai/git-hooks/lib/staged-secret-scan.js +3 -1
  23. package/.sinapse-ai/install-manifest.yaml +52 -80
  24. package/.sinapse-ai/product/templates/personalized-agent-template.md +3 -5
  25. package/.sinapse-ai/workflow-intelligence/index.js +3 -21
  26. package/AGENTS.md +2 -2
  27. package/CHANGELOG.md +56 -22
  28. package/bin/commands/install.js +5 -4
  29. package/bin/lib/command-generator.js +4 -4
  30. package/bin/lib/fs-utils.js +18 -0
  31. package/bin/lib/setup-statusline.js +3 -1
  32. package/bin/modules/chrome-brain-installer.js +5 -3
  33. package/bin/modules/mcp-installer.js +3 -6
  34. package/bin/postinstall.js +2 -1
  35. package/bin/sinapse-init.js +8 -9
  36. package/bin/sinapse.js +3 -1
  37. package/bin/utils/staged-secret-scan.js +3 -1
  38. package/docs/framework/atlas/OPERATING-ATLAS.md +1 -1
  39. package/docs/framework/atlas/atlas-data.json +22 -22
  40. package/docs/framework/atlas/atlas.html +12 -10
  41. package/docs/framework/source-tree.md +1 -2
  42. package/package.json +3 -1
  43. package/packages/installer/src/config/configure-environment.js +8 -6
  44. package/packages/installer/src/installer/brownfield-upgrader.js +5 -3
  45. package/packages/installer/src/installer/file-hasher.js +29 -21
  46. package/packages/installer/src/installer/manifest-signature.js +61 -49
  47. package/packages/installer/src/installer/post-install-validator.js +24 -17
  48. package/packages/installer/src/merger/parsers/markdown-section-parser.js +3 -2
  49. package/packages/installer/src/updater/index.js +7 -1
  50. package/packages/installer/src/utils/atomic-write.js +35 -0
  51. package/packages/installer/src/wizard/index.js +5 -3
  52. package/packages/sinapse-install/src/capabilities/chrome-brain.js +5 -3
  53. package/packages/sinapse-install/src/installer.js +3 -1
  54. package/packages/sinapse-install/src/utils/atomic-write.js +67 -0
  55. package/scripts/apply-persona-disclaimer.js +2 -1
  56. package/scripts/eval-e2e.js +149 -0
  57. package/scripts/sinapse-patch.js +2 -1
  58. package/scripts/sync-squad-yaml-components.js +2 -1
  59. package/scripts/validate-agents-md.js +2 -1
  60. package/scripts/validate-all.js +3 -0
  61. package/scripts/validate-article-iv.js +289 -0
  62. package/scripts/validate-article-xi.js +10 -8
  63. package/scripts/validate-evals.js +9 -2
  64. package/scripts/validate-no-external-refs.js +14 -8
  65. package/scripts/wave-gate.js +207 -0
  66. package/squads/claude-code-mastery/agents/claude-mastery-chief.md +3 -8
  67. package/squads/claude-code-mastery/agents/config-engineer.md +5 -12
  68. package/squads/claude-code-mastery/agents/hooks-architect.md +5 -11
  69. package/squads/claude-code-mastery/agents/mcp-integrator.md +5 -12
  70. package/squads/claude-code-mastery/agents/project-integrator.md +5 -12
  71. package/squads/claude-code-mastery/agents/roadmap-sentinel.md +5 -12
  72. package/squads/claude-code-mastery/agents/skill-craftsman.md +5 -11
  73. package/squads/claude-code-mastery/agents/swarm-orqx.md +15 -28
  74. package/.sinapse-ai/core/execution/context-injector.js +0 -539
  75. package/.sinapse-ai/core/execution/parallel-executor.js +0 -301
  76. package/.sinapse-ai/core/execution/parallel-monitor.js +0 -432
  77. package/.sinapse-ai/core/execution/semantic-merge-engine.js +0 -1750
  78. package/.sinapse-ai/core/execution/wave-executor.js +0 -403
  79. package/.sinapse-ai/development/tasks/waves.md +0 -205
  80. package/.sinapse-ai/workflow-intelligence/engine/wave-analyzer.js +0 -692
@@ -162,17 +162,19 @@ renderDiagrams('s-model'); // initial visible tab
162
162
 
163
163
  function tbl(cols,rows){return '<table><thead><tr>'+cols.map(c=>'<th>'+c+'</th>').join('')+'</tr></thead><tbody>'+
164
164
  rows.map(r=>'<tr>'+r.map(c=>'<td>'+c+'</td>').join('')+'</tr>').join('')+'</tbody></table>';}
165
- const code=s=>'<code>'+(s||'')+'</code>';
166
- const sev=s=>'<span class="tag'+(/NON-NEGOTIABLE/.test(s)?' nn':'')+'">'+s+'</span>';
165
+ // Data cells come from atlas-data.json (framework registry, mutable via PR) β€” escape before innerHTML.
166
+ const esc=s=>String(s).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
167
+ const code=s=>'<code>'+esc(s||'')+'</code>';
168
+ const sev=s=>'<span class="tag'+(/NON-NEGOTIABLE/.test(s)?' nn':'')+'">'+esc(s)+'</span>';
167
169
 
168
170
  document.getElementById('t-constitution').innerHTML=tbl(['Art.','Principle','Severity'],
169
- D.articles.map(a=>[a.number,a.title,sev(a.severity)]));
171
+ D.articles.map(a=>[esc(a.number),esc(a.title),sev(a.severity)]));
170
172
 
171
173
  document.getElementById('t-squads').innerHTML=tbl(['Squad','Name','Agents','Workflows','Orchestrators'],
172
- D.squads.map(s=>[code(s.id),s.name,s.agents,s.workflows,(s.orqx||[]).map(code).join(' ')||'β€”']));
174
+ D.squads.map(s=>[code(s.id),esc(s.name),esc(s.agents),esc(s.workflows),(s.orqx||[]).map(code).join(' ')||'β€”']));
173
175
 
174
176
  document.getElementById('t-rules').innerHTML=tbl(['Rule','Governs'],
175
- D.rules.map(r=>[code(r.id),r.title]));
177
+ D.rules.map(r=>[code(r.id),esc(r.title)]));
176
178
 
177
179
  // Workflows (search + source filter)
178
180
  function renderWorkflows(){
@@ -185,14 +187,14 @@ function renderWorkflows(){
185
187
  });
186
188
  document.getElementById('c-workflows').textContent=rows.length+' of '+D.workflows.length;
187
189
  document.getElementById('t-workflows').innerHTML=tbl(['Workflow','Source','Type','Description'],
188
- rows.map(w=>[code(w.id),w.squad?code(w.squad):'<span class="tag">framework</span>',w.type||'β€”',w.description||'β€”']));
190
+ rows.map(w=>[code(w.id),w.squad?code(w.squad):'<span class="tag">framework</span>',esc(w.type||'β€”'),esc(w.description||'β€”')]));
189
191
  }
190
192
  ['q-workflows','f-workflows-src'].forEach(id=>document.getElementById(id).addEventListener('input',renderWorkflows));
191
193
 
192
194
  // Agents (search + squad filter + orqx toggle)
193
195
  const squads=[...new Set(D.agents.map(a=>a.squad))].sort();
194
196
  const sel=document.getElementById('f-agents-squad');
195
- sel.innerHTML='<option value="">all squads</option>'+squads.map(s=>'<option>'+s+'</option>').join('');
197
+ sel.innerHTML='<option value="">all squads</option>'+squads.map(s=>'<option>'+esc(s)+'</option>').join('');
196
198
  function renderAgents(){
197
199
  const q=(document.getElementById('q-agents').value||'').toLowerCase();
198
200
  const sq=sel.value; const onlyOrqx=document.getElementById('f-agents-orqx').checked;
@@ -202,7 +204,7 @@ function renderAgents(){
202
204
  });
203
205
  document.getElementById('c-agents').textContent=rows.length+' of '+D.agents.length;
204
206
  document.getElementById('t-agents').innerHTML=tbl(['Agent','Persona','Squad','Role',''],
205
- rows.map(a=>[code(a.id),a.persona||'β€”',code(a.squad),a.role||a.title||'β€”',a.isOrqx?'<span class="tag nn">orqx</span>':'']));
207
+ rows.map(a=>[code(a.id),esc(a.persona||'β€”'),code(a.squad),esc(a.role||a.title||'β€”'),a.isOrqx?'<span class="tag nn">orqx</span>':'']));
206
208
  }
207
209
  ['q-agents','f-agents-orqx'].forEach(id=>document.getElementById(id).addEventListener('input',renderAgents));
208
210
  sel.addEventListener('change',renderAgents);
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Brownfield Discovery β€” deterministic progress + QA gate in code.
3
+ *
4
+ * Story onda3-s3-brownfield-progress-gate (AF-20260702 item 3.3).
5
+ *
6
+ * The 10 phases of brownfield-discovery.yaml are executed by agents (prose,
7
+ * orchestrate-then-handoff). What was missing β€” and what this module adds β€”
8
+ * is the DETERMINISTIC layer around that prose:
9
+ *
10
+ * 1. resolveBrownfieldProgress(): where the assessment actually stands,
11
+ * measured from artifacts ON DISK (not from what anyone claims), giving
12
+ * an exact resume point per phase.
13
+ * 2. evaluateQaGate() / recordRework(): the Phase 7 verdict (APPROVED /
14
+ * NEEDS WORK) parsed from docs/reviews/qa-review.md by code, with a
15
+ * persisted rework counter capped at MAX_REWORK β€” the NEEDS WORK loop
16
+ * escalates instead of spinning forever.
17
+ *
18
+ * Honesty contract: an unfilled template ("[APPROVED / NEEDS WORK]") is
19
+ * PENDING, not a verdict. Autonomous fan-out of phases 1-3 remains gated on
20
+ * a measured pilot (same pre-registered gate as epic waves β€” see the epic
21
+ * README of epic-onda3-estrutural); this module is the safe deterministic
22
+ * substrate either way.
23
+ *
24
+ * @module core/orchestration/brownfield-progress
25
+ */
26
+
27
+ 'use strict';
28
+
29
+ const fs = require('fs');
30
+ const path = require('path');
31
+
32
+ const { fileHasContent } = require('./doc-first-resolver');
33
+
34
+ const QA_REVIEW_RELPATH = path.join('docs', 'reviews', 'qa-review.md');
35
+ const STATE_RELPATH = path.join('.sinapse', 'workflow-state', 'brownfield-discovery.json');
36
+
37
+ /** Rework ceiling for the Phase 7 loop (NEEDS WORK β†’ fix β†’ re-review). */
38
+ const MAX_REWORK = 2;
39
+
40
+ /** Normalize a step's `creates` (string | string[]) into a string list. */
41
+ function listArtifacts(step) {
42
+ if (!step || !step.creates) return [];
43
+ const raw = Array.isArray(step.creates) ? step.creates : [step.creates];
44
+ return raw.filter((a) => typeof a === 'string' && a.trim().length > 0);
45
+ }
46
+
47
+ /** Glob-ish artifacts ("story-X.X-*.md") cannot be verified deterministically. */
48
+ function isCheckable(artifact) {
49
+ return !artifact.includes('*');
50
+ }
51
+
52
+ /**
53
+ * Resolve the real state of the brownfield discovery from disk.
54
+ *
55
+ * @param {string} projectRoot
56
+ * @param {Object} workflow - Parsed `workflow` node (needs `.sequence`)
57
+ * @returns {{phases: Array, nextPhase: ?number, complete: boolean}}
58
+ */
59
+ function resolveBrownfieldProgress(projectRoot, workflow) {
60
+ const sequence = (workflow && Array.isArray(workflow.sequence)) ? workflow.sequence : [];
61
+ const byPhase = new Map();
62
+
63
+ for (const step of sequence) {
64
+ if (!step || typeof step.phase !== 'number') continue;
65
+ if (!byPhase.has(step.phase)) {
66
+ byPhase.set(step.phase, { phase: step.phase, name: step.phase_name || null, steps: [] });
67
+ }
68
+ const artifacts = listArtifacts(step).map((artifact) => {
69
+ const checkable = isCheckable(artifact);
70
+ return {
71
+ path: artifact,
72
+ checkable,
73
+ exists: checkable ? fileHasContent(path.join(projectRoot, artifact)) : null,
74
+ };
75
+ });
76
+ byPhase.get(step.phase).steps.push({
77
+ id: step.step || step.id || null,
78
+ agent: step.agent || null,
79
+ conditional: Boolean(step.condition),
80
+ condition: step.condition || null,
81
+ artifacts,
82
+ });
83
+ }
84
+
85
+ const phases = [...byPhase.values()].sort((a, b) => a.phase - b.phase);
86
+
87
+ for (const phase of phases) {
88
+ // Required = checkable artifacts of unconditional steps. Conditional steps
89
+ // (e.g. project_has_database) never block completeness β€” but when their
90
+ // artifacts DO exist they are reported, so nothing done is invisible.
91
+ const required = phase.steps
92
+ .filter((s) => !s.conditional)
93
+ .flatMap((s) => s.artifacts.filter((a) => a.checkable));
94
+ const present = required.filter((a) => a.exists);
95
+ phase.requiredCount = required.length;
96
+ phase.presentCount = present.length;
97
+ if (required.length === 0) {
98
+ phase.status = 'unverifiable';
99
+ } else if (present.length === required.length) {
100
+ phase.status = 'complete';
101
+ } else if (present.length > 0) {
102
+ phase.status = 'partial';
103
+ } else {
104
+ phase.status = 'pending';
105
+ }
106
+ }
107
+
108
+ const firstIncomplete = phases.find(
109
+ (p) => p.status === 'pending' || p.status === 'partial',
110
+ );
111
+
112
+ return {
113
+ phases,
114
+ nextPhase: firstIncomplete ? firstIncomplete.phase : null,
115
+ complete: phases.length > 0 && !firstIncomplete,
116
+ };
117
+ }
118
+
119
+ /**
120
+ * Parse the Phase 7 verdict out of qa-review.md content.
121
+ *
122
+ * @param {string} content
123
+ * @returns {'approved'|'needs_work'|'pending'|'malformed'}
124
+ */
125
+ function parseQaGateStatus(content) {
126
+ if (typeof content !== 'string') return 'malformed';
127
+ const m = content.match(/gate status:\s*(.*)$/im);
128
+ if (!m) return 'pending';
129
+ const value = m[1].trim();
130
+ const hasApproved = /approved/i.test(value);
131
+ const hasNeedsWork = /needs\s+work/i.test(value);
132
+ // The unfilled template is "[APPROVED / NEEDS WORK]" β€” both tokens present
133
+ // means the reviewer never picked. That is PENDING, never a verdict.
134
+ if (hasApproved && hasNeedsWork) return 'pending';
135
+ if (hasApproved) return 'approved';
136
+ if (hasNeedsWork) return 'needs_work';
137
+ return 'malformed';
138
+ }
139
+
140
+ function statePath(projectRoot) {
141
+ return path.join(projectRoot, STATE_RELPATH);
142
+ }
143
+
144
+ function readState(projectRoot) {
145
+ try {
146
+ const parsed = JSON.parse(fs.readFileSync(statePath(projectRoot), 'utf8'));
147
+ return { reworkCount: 0, ...parsed };
148
+ } catch {
149
+ return { reworkCount: 0 };
150
+ }
151
+ }
152
+
153
+ function writeState(projectRoot, state) {
154
+ const file = statePath(projectRoot);
155
+ fs.mkdirSync(path.dirname(file), { recursive: true });
156
+ fs.writeFileSync(file, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
157
+ }
158
+
159
+ /**
160
+ * Evaluate the Phase 7 QA gate deterministically (read-only β€” never mutates
161
+ * the rework counter; that is recordRework's job).
162
+ *
163
+ * @param {string} projectRoot
164
+ * @returns {{verdict: string, reworkCount: number, maxRework: number, escalate: boolean, reviewPath: string, reason?: string}}
165
+ */
166
+ function evaluateQaGate(projectRoot) {
167
+ const reviewPath = path.join(projectRoot, QA_REVIEW_RELPATH);
168
+ const state = readState(projectRoot);
169
+ const base = {
170
+ reworkCount: state.reworkCount,
171
+ maxRework: MAX_REWORK,
172
+ reviewPath: QA_REVIEW_RELPATH,
173
+ };
174
+
175
+ if (!fileHasContent(reviewPath)) {
176
+ return { ...base, verdict: 'pending', escalate: false, reason: 'qa-review.md ausente ou vazio' };
177
+ }
178
+
179
+ let content;
180
+ try {
181
+ content = fs.readFileSync(reviewPath, 'utf8');
182
+ } catch (error) {
183
+ return { ...base, verdict: 'malformed', escalate: false, reason: `qa-review.md ilegΓ­vel: ${error.message}` };
184
+ }
185
+
186
+ const verdict = parseQaGateStatus(content);
187
+ const escalate = verdict === 'needs_work' && state.reworkCount >= MAX_REWORK;
188
+ return { ...base, verdict, escalate };
189
+ }
190
+
191
+ /**
192
+ * Record one NEEDS WORK β†’ rework loop iteration. Call it when the flow
193
+ * returns to Phase 4 after a NEEDS WORK verdict. At MAX_REWORK the gate
194
+ * escalates instead of looping.
195
+ *
196
+ * @param {string} projectRoot
197
+ * @returns {{reworkCount: number, escalate: boolean}}
198
+ */
199
+ function recordRework(projectRoot) {
200
+ const state = readState(projectRoot);
201
+ state.reworkCount += 1;
202
+ state.lastReworkAt = new Date().toISOString();
203
+ writeState(projectRoot, state);
204
+ return { reworkCount: state.reworkCount, escalate: state.reworkCount >= MAX_REWORK };
205
+ }
206
+
207
+ /** Reset the rework counter (new assessment cycle). */
208
+ function resetQaGateState(projectRoot) {
209
+ writeState(projectRoot, { reworkCount: 0 });
210
+ }
211
+
212
+ module.exports = {
213
+ resolveBrownfieldProgress,
214
+ parseQaGateStatus,
215
+ evaluateQaGate,
216
+ recordRework,
217
+ resetQaGateState,
218
+ MAX_REWORK,
219
+ };
@@ -319,4 +319,7 @@ module.exports = {
319
319
  TYPE_KEYWORDS,
320
320
  // re-exported for callers that want the raw map
321
321
  GREENFIELD_WORKFLOW_BY_TYPE,
322
+ // Onda3-S2 (AF-20260702 item 3.4): exported so artifact gates share ONE
323
+ // definition of "artifact exists and is non-empty" instead of forking it.
324
+ fileHasContent,
322
325
  };
@@ -152,9 +152,63 @@ class GreenfieldHandler extends EventEmitter {
152
152
  // Phase progress tracking
153
153
  this.phaseProgress = {};
154
154
 
155
+ // Onda3-S2: cached read of workflow metadata (confirmation_required).
156
+ this._confirmationRequired = null;
157
+
155
158
  this._log('GreenfieldHandler initialized');
156
159
  }
157
160
 
161
+ /**
162
+ * Reads `metadata.confirmation_required` from the resolved workflow YAML.
163
+ * Onda3-S2 (AF-20260702 item 3.4): the field used to be decorative β€” no code
164
+ * consumed it. Now `false` turns go_pause surfaces into auto-GO; `true` or
165
+ * any read/parse failure preserves the safe default (pause for the human).
166
+ *
167
+ * @returns {boolean} true = pause between phases (default), false = auto-GO
168
+ */
169
+ getConfirmationRequired() {
170
+ if (this._confirmationRequired !== null) return this._confirmationRequired;
171
+ let required = true;
172
+ try {
173
+ const yaml = require('js-yaml');
174
+ const raw = fs.readFileSync(this.workflowPath, 'utf8');
175
+ const parsed = yaml.load(raw);
176
+ if (parsed && parsed.metadata && parsed.metadata.confirmation_required === false) {
177
+ required = false;
178
+ }
179
+ } catch (error) {
180
+ this._log(`confirmation_required unreadable (default: true): ${error.message}`, 'warn');
181
+ }
182
+ this._confirmationRequired = required;
183
+ return required;
184
+ }
185
+
186
+ /**
187
+ * Deterministic artifact gate (Onda3-S2, AF-20260702 item 3.4).
188
+ * For every step that declares a file artifact (`creates` ending in .md),
189
+ * verifies the artifact exists on disk with content. Echoes the empty-build
190
+ * honesty fix: a phase does not advance on promises, only on artifacts.
191
+ *
192
+ * @param {Array<{agent: string, task: string, creates: ?string}>} sequence
193
+ * @returns {{passed: boolean, missing: Array<{agent: string, task: string, artifact: string}>, checked: number}}
194
+ */
195
+ evaluateArtifactGate(sequence) {
196
+ // Lazy require: doc-first-resolver imports constants from this module, so a
197
+ // top-level require here would create a load-order cycle with partial exports.
198
+ const { fileHasContent } = require('./doc-first-resolver');
199
+ const missing = [];
200
+ let checked = 0;
201
+ for (const step of sequence) {
202
+ if (!step.creates || !step.creates.endsWith('.md')) continue;
203
+ checked += 1;
204
+ const artifactPath = path.join(this.projectRoot, step.creates);
205
+ if (!fileHasContent(artifactPath)) {
206
+ missing.push({ agent: step.agent, task: step.task, artifact: step.creates });
207
+ }
208
+ }
209
+ return { passed: missing.length === 0, missing, checked };
210
+ }
211
+
158
212
  // ═══════════════════════════════════════════════════════════════════════════════════
159
213
  // LAZY DEPENDENCY LOADING
160
214
  // ═══════════════════════════════════════════════════════════════════════════════════
@@ -453,10 +507,45 @@ class GreenfieldHandler extends EventEmitter {
453
507
  }
454
508
  }
455
509
 
510
+ // Onda3-S2: deterministic artifact gate β€” the phase only completes when
511
+ // every declared artifact actually exists with content on disk.
512
+ const gate = this.evaluateArtifactGate(PHASE_1_SEQUENCE);
513
+ if (!gate.passed) {
514
+ this.phaseProgress[GreenfieldPhase.DISCOVERY] = {
515
+ status: 'gated',
516
+ endTime: Date.now(),
517
+ stepResults,
518
+ gate,
519
+ };
520
+ this.emit('phaseGated', { phase: GreenfieldPhase.DISCOVERY, gate, context });
521
+
522
+ const missingList = gate.missing
523
+ .map((m) => ` - ${m.artifact} (${m.agent} β†’ *${m.task})`)
524
+ .join('\n');
525
+ return {
526
+ action: 'greenfield_gate_blocked',
527
+ phase: GreenfieldPhase.DISCOVERY,
528
+ nextPhase: 1,
529
+ data: {
530
+ gate: 'missing_artifact',
531
+ missing: gate.missing,
532
+ message:
533
+ 'Gate de artefato: a Phase 1 nΓ£o avanΓ§a sem os artefatos no disco.\n' +
534
+ `Faltando:\n${missingList}\n\n` +
535
+ 'Execute os agentes indicados (ou crie os documentos) e responda GO para revalidar.',
536
+ promptType: 'go_pause',
537
+ options: ['GO', 'PAUSE'],
538
+ resumeFromPhase: 1,
539
+ context: { ...context, phase1Results: stepResults },
540
+ },
541
+ };
542
+ }
543
+
456
544
  this.phaseProgress[GreenfieldPhase.DISCOVERY] = {
457
545
  status: 'complete',
458
546
  endTime: Date.now(),
459
547
  stepResults,
548
+ gate,
460
549
  };
461
550
 
462
551
  this.emit('phaseComplete', { phase: GreenfieldPhase.DISCOVERY, result: stepResults, context });
@@ -667,6 +756,14 @@ class GreenfieldHandler extends EventEmitter {
667
756
  * @private
668
757
  */
669
758
  _surfaceBetweenPhases(fromPhase, toPhase, surfaceConfig) {
759
+ // Onda3-S2: honor metadata.confirmation_required from the workflow YAML.
760
+ // Only go_pause surfaces auto-advance β€” text_input needs the human's words,
761
+ // and gate blocks never route through here.
762
+ if (surfaceConfig.promptType === 'go_pause' && this.getConfirmationRequired() === false) {
763
+ this._log(`confirmation_required=false β€” auto-GO Phase ${fromPhase} β†’ ${toPhase}`);
764
+ return this._executeFromPhase(toPhase, surfaceConfig.context);
765
+ }
766
+
670
767
  const surfaceChecker = this._getSurfaceChecker();
671
768
 
672
769
  // Build options based on prompt type
@@ -474,7 +474,7 @@ class WorkflowExecutor {
474
474
  return { success: false, error: `Workflow definition is empty: ${workflowPath}` };
475
475
  }
476
476
 
477
- return {
477
+ const result = {
478
478
  success: true,
479
479
  workflowPath,
480
480
  workflow,
@@ -484,6 +484,22 @@ class WorkflowExecutor {
484
484
  techStack: options.techStack || {},
485
485
  },
486
486
  };
487
+
488
+ // Onda3-S3 (AF-20260702 item 3.3): the brownfield handoff now carries
489
+ // deterministic state β€” phase progress measured from artifacts on disk and
490
+ // the Phase 7 QA gate parsed by code (with capped rework loop). Fail-open:
491
+ // enrichment errors degrade to the plain handoff, never to a crash.
492
+ if (workflow.id === 'brownfield-discovery') {
493
+ try {
494
+ const { resolveBrownfieldProgress, evaluateQaGate } = require('./brownfield-progress');
495
+ result.progress = resolveBrownfieldProgress(result.context.projectRoot, workflow);
496
+ result.qaGate = evaluateQaGate(result.context.projectRoot);
497
+ } catch (error) {
498
+ result.progressError = `brownfield progress unavailable: ${error.message}`;
499
+ }
500
+ }
501
+
502
+ return result;
487
503
  }
488
504
 
489
505
  /**
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": "1.0.0",
3
3
  "generated": "2026-06-19T20:32:21.238Z",
4
- "totalWorkers": 373,
4
+ "totalWorkers": 372,
5
5
  "categories": {
6
6
  "checklist": {
7
7
  "count": 16,
@@ -24,7 +24,7 @@
24
24
  "description": "JavaScript utility scripts"
25
25
  },
26
26
  "task": {
27
- "count": 215,
27
+ "count": 214,
28
28
  "subcategories": [
29
29
  "analysis",
30
30
  "creation",
@@ -10933,36 +10933,6 @@
10933
10933
  "addedVersion": "1.0.0"
10934
10934
  }
10935
10935
  },
10936
- {
10937
- "id": "waves",
10938
- "name": "Waves",
10939
- "description": "<!-- Story: WIS-4 - Wave Analysis Engine -->",
10940
- "category": "task",
10941
- "subcategory": "general",
10942
- "inputs": [],
10943
- "outputs": [],
10944
- "tags": [
10945
- "task",
10946
- "general",
10947
- "waves"
10948
- ],
10949
- "path": ".sinapse-ai/development/tasks/waves.md",
10950
- "taskFormat": "TASK-FORMAT-V1",
10951
- "executorTypes": [
10952
- "Agent",
10953
- "Worker"
10954
- ],
10955
- "performance": {
10956
- "avgDuration": "1m",
10957
- "cacheable": false,
10958
- "parallelizable": false
10959
- },
10960
- "agents": [],
10961
- "metadata": {
10962
- "source": "development",
10963
- "addedVersion": "1.0.0"
10964
- }
10965
- },
10966
10936
  {
10967
10937
  "id": "yolo-toggle",
10968
10938
  "name": "Yolo Toggle",