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.
- package/.sinapse-ai/core/atlas/render-html.js +10 -8
- package/.sinapse-ai/core/orchestration/brownfield-progress.js +219 -0
- package/.sinapse-ai/core/orchestration/doc-first-resolver.js +3 -0
- package/.sinapse-ai/core/orchestration/greenfield-handler.js +97 -0
- package/.sinapse-ai/core/orchestration/workflow-executor.js +17 -1
- package/.sinapse-ai/core/registry/service-registry.json +2 -32
- package/.sinapse-ai/data/entity-registry.yaml +473 -203
- package/.sinapse-ai/development/agents/analyst.md +18 -34
- package/.sinapse-ai/development/agents/architect.md +18 -34
- package/.sinapse-ai/development/agents/data-engineer.md +15 -28
- package/.sinapse-ai/development/agents/developer.md +0 -6
- package/.sinapse-ai/development/agents/devops.md +15 -28
- package/.sinapse-ai/development/agents/product-lead.md +18 -34
- package/.sinapse-ai/development/agents/project-lead.md +18 -34
- package/.sinapse-ai/development/agents/quality-gate.md +18 -34
- package/.sinapse-ai/development/agents/squad-creator.md +15 -27
- package/.sinapse-ai/development/agents/ux-design-expert.md +15 -27
- package/.sinapse-ai/development/scripts/populate-entity-registry.js +3 -0
- package/.sinapse-ai/development/tasks/execute-epic-plan.md +3 -4
- package/.sinapse-ai/development/workflows/brownfield-discovery.yaml +16 -0
- package/.sinapse-ai/development/workflows/epic-orchestration.yaml +11 -1
- package/.sinapse-ai/git-hooks/lib/staged-secret-scan.js +3 -1
- package/.sinapse-ai/install-manifest.yaml +52 -80
- package/.sinapse-ai/product/templates/personalized-agent-template.md +3 -5
- package/.sinapse-ai/workflow-intelligence/index.js +3 -21
- package/AGENTS.md +2 -2
- package/CHANGELOG.md +56 -22
- package/bin/commands/install.js +5 -4
- package/bin/lib/command-generator.js +4 -4
- package/bin/lib/fs-utils.js +18 -0
- package/bin/lib/setup-statusline.js +3 -1
- package/bin/modules/chrome-brain-installer.js +5 -3
- package/bin/modules/mcp-installer.js +3 -6
- package/bin/postinstall.js +2 -1
- package/bin/sinapse-init.js +8 -9
- package/bin/sinapse.js +3 -1
- package/bin/utils/staged-secret-scan.js +3 -1
- package/docs/framework/atlas/OPERATING-ATLAS.md +1 -1
- package/docs/framework/atlas/atlas-data.json +22 -22
- package/docs/framework/atlas/atlas.html +12 -10
- package/docs/framework/source-tree.md +1 -2
- package/package.json +3 -1
- package/packages/installer/src/config/configure-environment.js +8 -6
- package/packages/installer/src/installer/brownfield-upgrader.js +5 -3
- package/packages/installer/src/installer/file-hasher.js +29 -21
- package/packages/installer/src/installer/manifest-signature.js +61 -49
- package/packages/installer/src/installer/post-install-validator.js +24 -17
- package/packages/installer/src/merger/parsers/markdown-section-parser.js +3 -2
- package/packages/installer/src/updater/index.js +7 -1
- package/packages/installer/src/utils/atomic-write.js +35 -0
- package/packages/installer/src/wizard/index.js +5 -3
- package/packages/sinapse-install/src/capabilities/chrome-brain.js +5 -3
- package/packages/sinapse-install/src/installer.js +3 -1
- package/packages/sinapse-install/src/utils/atomic-write.js +67 -0
- package/scripts/apply-persona-disclaimer.js +2 -1
- package/scripts/eval-e2e.js +149 -0
- package/scripts/sinapse-patch.js +2 -1
- package/scripts/sync-squad-yaml-components.js +2 -1
- package/scripts/validate-agents-md.js +2 -1
- package/scripts/validate-all.js +3 -0
- package/scripts/validate-article-iv.js +289 -0
- package/scripts/validate-article-xi.js +10 -8
- package/scripts/validate-evals.js +9 -2
- package/scripts/validate-no-external-refs.js +14 -8
- package/scripts/wave-gate.js +207 -0
- package/squads/claude-code-mastery/agents/claude-mastery-chief.md +3 -8
- package/squads/claude-code-mastery/agents/config-engineer.md +5 -12
- package/squads/claude-code-mastery/agents/hooks-architect.md +5 -11
- package/squads/claude-code-mastery/agents/mcp-integrator.md +5 -12
- package/squads/claude-code-mastery/agents/project-integrator.md +5 -12
- package/squads/claude-code-mastery/agents/roadmap-sentinel.md +5 -12
- package/squads/claude-code-mastery/agents/skill-craftsman.md +5 -11
- package/squads/claude-code-mastery/agents/swarm-orqx.md +15 -28
- package/.sinapse-ai/core/execution/context-injector.js +0 -539
- package/.sinapse-ai/core/execution/parallel-executor.js +0 -301
- package/.sinapse-ai/core/execution/parallel-monitor.js +0 -432
- package/.sinapse-ai/core/execution/semantic-merge-engine.js +0 -1750
- package/.sinapse-ai/core/execution/wave-executor.js +0 -403
- package/.sinapse-ai/development/tasks/waves.md +0 -205
- 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
|
-
|
|
166
|
-
const
|
|
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=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[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
|
-
|
|
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":
|
|
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":
|
|
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",
|