sinapse-ai 1.6.1 → 1.8.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/.claude/CLAUDE.md +5 -11
- package/.claude/hooks/README.md +14 -1
- package/.claude/hooks/code-intel-pretool.cjs +115 -0
- package/.claude/hooks/enforce-delegation.cjs +31 -3
- package/.claude/hooks/enforce-framework-boundary.cjs +324 -0
- package/.claude/hooks/enforce-permission-mode.cjs +249 -0
- package/.claude/hooks/secret-scanning.cjs +34 -43
- package/.claude/hooks/synapse-engine.cjs +23 -23
- package/.claude/hooks/telemetry-post-tool.cjs +128 -0
- package/.claude/hooks/telemetry-stop.cjs +132 -0
- package/.claude/hooks/verify-packages.cjs +9 -2
- package/.claude/rules/documentation-first.md +1 -1
- package/.claude/rules/hook-governance.md +2 -0
- package/.sinapse-ai/cli/commands/health/index.js +24 -0
- package/.sinapse-ai/core/README.md +11 -0
- package/.sinapse-ai/core/config/config-loader.js +19 -0
- package/.sinapse-ai/core/config/merge-utils.js +8 -0
- package/.sinapse-ai/core/errors/constants.js +147 -0
- package/.sinapse-ai/core/errors/error-registry.js +176 -0
- package/.sinapse-ai/core/errors/index.js +50 -0
- package/.sinapse-ai/core/errors/serializer.js +147 -0
- package/.sinapse-ai/core/errors/sinapse-error.js +144 -0
- package/.sinapse-ai/core/errors/utils.js +187 -0
- package/.sinapse-ai/core/execution/build-orchestrator.js +47 -49
- package/.sinapse-ai/core/execution/build-state-manager.js +183 -31
- package/.sinapse-ai/core/execution/parallel-executor.js +7 -1
- package/.sinapse-ai/core/execution/semantic-merge-engine.js +26 -14
- package/.sinapse-ai/core/execution/subagent-dispatcher.js +201 -60
- package/.sinapse-ai/core/execution/wave-executor.js +4 -1
- package/.sinapse-ai/core/grounding/README.md +71 -11
- package/.sinapse-ai/core/health-check/checks/project/framework-config.js +38 -2
- package/.sinapse-ai/core/health-check/checks/project/package-json.js +47 -3
- package/.sinapse-ai/core/health-check/checks/services/gemini-cli.js +117 -0
- package/.sinapse-ai/core/health-check/checks/services/index.js +2 -0
- package/.sinapse-ai/core/health-check/healers/index.js +40 -3
- package/.sinapse-ai/core/ideation/ideation-engine.js +212 -107
- package/.sinapse-ai/core/ids/gate-evaluator.js +318 -0
- package/.sinapse-ai/core/ids/gates/g5-semantic-handshake.js +190 -0
- package/.sinapse-ai/core/ids/gates/g6-ci-integrity.js +162 -0
- package/.sinapse-ai/core/ids/index.js +30 -0
- package/.sinapse-ai/core/memory/__tests__/active-modules.verify.js +11 -0
- package/.sinapse-ai/core/memory/gotchas-memory.js +37 -2
- package/.sinapse-ai/core/orchestration/agent-invoker.js +29 -6
- package/.sinapse-ai/core/orchestration/brownfield-handler.js +36 -3
- package/.sinapse-ai/core/orchestration/condition-evaluator.js +57 -0
- package/.sinapse-ai/core/orchestration/executors/epic-3-executor.js +76 -5
- package/.sinapse-ai/core/orchestration/executors/epic-4-executor.js +63 -17
- package/.sinapse-ai/core/orchestration/executors/epic-6-executor.js +153 -41
- package/.sinapse-ai/core/orchestration/executors/epic-executor.js +40 -0
- package/.sinapse-ai/core/orchestration/greenfield-handler.js +87 -3
- package/.sinapse-ai/core/orchestration/master-orchestrator.js +150 -10
- package/.sinapse-ai/core/orchestration/parallel-executor.js +6 -1
- package/.sinapse-ai/core/orchestration/recovery-handler.js +81 -8
- package/.sinapse-ai/core/orchestration/workflow-executor.js +41 -0
- package/.sinapse-ai/core/registry/registry-loader.js +71 -5
- package/.sinapse-ai/core/registry/squad-agent-resolver.js +253 -0
- package/.sinapse-ai/core/synapse/context/context-tracker.js +104 -9
- package/.sinapse-ai/core/synapse/context/index.js +19 -0
- package/.sinapse-ai/core/synapse/context/semantic-handshake-engine.js +555 -0
- package/.sinapse-ai/core/synapse/diagnostics/collectors/pipeline-collector.js +4 -2
- package/.sinapse-ai/core/synapse/engine.js +43 -3
- package/.sinapse-ai/core/telemetry/ids-sink.js +188 -0
- package/.sinapse-ai/core/utils/output-formatter.js +8 -290
- package/.sinapse-ai/core/utils/spawn-safe.js +186 -0
- package/.sinapse-ai/core-config.yaml +68 -1
- package/.sinapse-ai/data/entity-registry.yaml +15082 -13618
- package/.sinapse-ai/data/registry-update-log.jsonl +143 -0
- package/.sinapse-ai/development/agents/developer.md +2 -0
- package/.sinapse-ai/development/agents/devops.md +9 -0
- package/.sinapse-ai/development/external-executors/README.md +18 -0
- package/.sinapse-ai/development/external-executors/codex.md +56 -0
- package/.sinapse-ai/development/scripts/populate-entity-registry.js +65 -9
- package/.sinapse-ai/development/scripts/squad/squad-downloader.js +169 -14
- package/.sinapse-ai/development/tasks/delegate-to-external-executor.md +152 -0
- package/.sinapse-ai/development/tasks/github-devops-pre-push-quality-gate.md +46 -29
- package/.sinapse-ai/development/tasks/update-sinapse.md +3 -3
- package/.sinapse-ai/hooks/sinapse-brand-grounding.cjs +4 -7
- package/.sinapse-ai/hooks/sinapse-ds-grounding.cjs +5 -8
- package/.sinapse-ai/hooks/sinapse-vault-grounding.cjs +6 -9
- package/.sinapse-ai/infrastructure/integrations/ai-providers/ai-provider-factory.js +4 -1
- package/.sinapse-ai/infrastructure/integrations/ai-providers/claude-provider.js +57 -55
- package/.sinapse-ai/infrastructure/integrations/pm-adapters/github-adapter.js +9 -7
- package/.sinapse-ai/infrastructure/scripts/ide-sync/gemini-commands.js +298 -0
- package/.sinapse-ai/infrastructure/scripts/ide-sync/index.js +127 -6
- package/.sinapse-ai/infrastructure/scripts/ide-sync/persona-renderer.js +97 -0
- package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/antigravity.js +121 -0
- package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/cursor.js +119 -0
- package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/github-copilot.js +191 -0
- package/.sinapse-ai/infrastructure/scripts/ide-sync/transformers/kimi.js +448 -0
- package/.sinapse-ai/install-manifest.yaml +218 -114
- package/.sinapse-ai/product/templates/engine/renderer.js +20 -1
- package/.sinapse-ai/scripts/pm.sh +18 -6
- package/bin/cli.js +17 -0
- package/bin/commands/agents.js +96 -0
- package/bin/commands/doctor.js +15 -0
- package/bin/commands/ideate.js +129 -0
- package/bin/commands/uninstall.js +40 -0
- package/bin/postinstall.js +50 -4
- package/bin/sinapse.js +146 -2
- package/bin/utils/secret-scanner-core.js +253 -0
- package/bin/utils/staged-secret-scan.js +106 -40
- package/docs/framework/collaboration-autonomy-plan.md +18 -18
- package/docs/guides/parallel-workflow.md +6 -6
- package/package.json +22 -5
- package/packages/installer/src/installer/git-hooks-installer.js +384 -0
- package/packages/installer/src/installer/sinapse-ai-installer.js +16 -0
- package/packages/installer/src/wizard/ide-config-generator.js +23 -0
- package/packages/installer/src/wizard/validators.js +38 -1
- package/packages/installer/tests/unit/artifact-copy-pipeline/artifact-copy-pipeline.test.js +5 -1
- package/packages/installer/tests/unit/doctor/doctor-checks.test.js +44 -22
- package/packages/installer/tests/unit/git-hooks-installer.test.js +262 -0
- package/scripts/eval-runner.js +422 -0
- package/scripts/generate-install-manifest.js +13 -9
- package/scripts/generate-synapse-runtime.js +51 -0
- package/scripts/regenerate-orqx-stubs.ps1 +6 -5
- package/scripts/validate-all.js +1 -0
- package/scripts/validate-evals.js +466 -0
- package/scripts/validate-schemas.js +539 -0
- package/scripts/validate-squad-orqx.js +9 -2
- package/squads/claude-code-mastery/knowledge-base/memory-systems-reference.md +1 -1
- package/squads/squad-brand/templates/client-delivery-template.md +1 -1
- package/squads/squad-content/knowledge-base/social-compression-framework.md +1 -1
- package/squads/squad-council/knowledge-base/brand-strategy-models.md +1 -1
- package/.sinapse-ai/development/scripts/elicitation-engine.js +0 -385
- package/.sinapse-ai/development/scripts/elicitation-session-manager.js +0 -300
- package/.sinapse-ai/development/tasks/test-validation-task.md +0 -172
- package/docs/chrome-brain-upgrade-plan.md +0 -624
- package/docs/constitution-compliance.md +0 -87
- package/docs/mega-upgrade-orchestration-plan.md +0 -71
- package/docs/research-synthesis-for-upgrade.md +0 -511
- package/docs/security-audit-report.md +0 -306
|
@@ -37,20 +37,12 @@ class Epic6Executor extends EpicExecutor {
|
|
|
37
37
|
this._qaOrchestrator = null;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
this._qaOrchestrator = require('../../infrastructure/scripts/qa-loop-orchestrator');
|
|
48
|
-
} catch (error) {
|
|
49
|
-
this._log(`QA Loop Orchestrator not available: ${error.message}`, 'warn');
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return this._qaOrchestrator;
|
|
53
|
-
}
|
|
40
|
+
// NOTE: the previous `_getQAOrchestrator()` path was triple-broken theater —
|
|
41
|
+
// it did `new` on the module object (not the QALoopOrchestrator class), passed
|
|
42
|
+
// the wrong constructor shape, and called a `runReview()` method that doesn't
|
|
43
|
+
// exist. It ALWAYS threw and silently fell back to basic checks while reporting
|
|
44
|
+
// success:true. Replaced by a real @quality-gate agent invocation (_reviewViaAgent),
|
|
45
|
+
// same proven path as epic-3 spec generation (epic: orchestration-consolidation).
|
|
54
46
|
|
|
55
47
|
/**
|
|
56
48
|
* Execute QA Loop
|
|
@@ -98,7 +90,9 @@ class Epic6Executor extends EpicExecutor {
|
|
|
98
90
|
|
|
99
91
|
if (currentVerdict === QAVerdict.NEEDS_REVISION && iteration < this.maxIterations) {
|
|
100
92
|
this._log('Review needs revision, applying fixes...');
|
|
101
|
-
|
|
93
|
+
// Record whether fixes were really applied (honesty invariant): a
|
|
94
|
+
// stubbed fix must not look like a real one in the QA report.
|
|
95
|
+
reviewResult.fixResult = await this._applyFixes(reviewResult.issues, context);
|
|
102
96
|
}
|
|
103
97
|
}
|
|
104
98
|
|
|
@@ -107,53 +101,60 @@ class Epic6Executor extends EpicExecutor {
|
|
|
107
101
|
this._addArtifact('qa-report', reportPath);
|
|
108
102
|
|
|
109
103
|
const passed = currentVerdict === QAVerdict.APPROVED;
|
|
104
|
+
const anyRealReview = reviewHistory.some((r) => r.reviewSource === 'agent');
|
|
110
105
|
|
|
111
|
-
|
|
106
|
+
const baseResult = {
|
|
112
107
|
verdict: currentVerdict,
|
|
113
108
|
passed,
|
|
114
109
|
iterations: iteration,
|
|
115
110
|
reviewHistory,
|
|
116
111
|
reportPath,
|
|
117
|
-
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// Honesty invariant (F0a/F7): a QA loop that only ran deterministic basic
|
|
115
|
+
// checks did NOT really review the work — it must report STUB, not success.
|
|
116
|
+
if (!anyRealReview) {
|
|
117
|
+
return this._stubExecution(
|
|
118
|
+
'QA ran deterministic basic checks only — no real review agent wired',
|
|
119
|
+
baseResult,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return this._completeExecution(baseResult);
|
|
118
124
|
} catch (error) {
|
|
119
125
|
return this._failExecution(error);
|
|
120
126
|
}
|
|
121
127
|
}
|
|
122
128
|
|
|
123
129
|
/**
|
|
124
|
-
* Run a single review cycle
|
|
130
|
+
* Run a single review cycle.
|
|
131
|
+
*
|
|
132
|
+
* Tries a REAL review via the @quality-gate agent (orchestrator.invokeAgent →
|
|
133
|
+
* SubagentDispatcher → claude). Falls back to deterministic basic checks when
|
|
134
|
+
* no executor is wired or real execution isn't allowed — and marks the source
|
|
135
|
+
* (`agent` vs `basic-checks`) so the epic can report honestly.
|
|
125
136
|
* @private
|
|
126
137
|
*/
|
|
127
138
|
async _runReview(context) {
|
|
128
|
-
const {
|
|
129
|
-
|
|
139
|
+
const { iteration } = context;
|
|
130
140
|
this._log(`Running review cycle ${iteration}`);
|
|
131
141
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (QAOrchestrator) {
|
|
142
|
+
const invokeAgent = this.orchestrator && this.orchestrator.invokeAgent;
|
|
143
|
+
if (typeof invokeAgent === 'function' && this._realExecutionAllowed()) {
|
|
136
144
|
try {
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
rootPath: this.projectRoot,
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
return await orchestrator.runReview(context);
|
|
145
|
+
const realReview = await this._reviewViaAgent(invokeAgent, context);
|
|
146
|
+
if (realReview) return realReview;
|
|
143
147
|
} catch (error) {
|
|
144
|
-
this._log(`
|
|
148
|
+
this._log(`Real review failed, falling back to basic checks: ${error.message}`, 'warn');
|
|
145
149
|
}
|
|
146
150
|
}
|
|
147
151
|
|
|
148
|
-
//
|
|
152
|
+
// Honest fallback: deterministic basic checks (clearly marked as a stub source).
|
|
149
153
|
const issues = await this._performBasicChecks(context);
|
|
150
154
|
|
|
151
|
-
// Determine verdict based on issues
|
|
152
155
|
let verdict = QAVerdict.APPROVED;
|
|
153
|
-
|
|
154
156
|
const criticalIssues = issues.filter((i) => i.severity === 'critical');
|
|
155
157
|
const majorIssues = issues.filter((i) => i.severity === 'major');
|
|
156
|
-
|
|
157
158
|
if (criticalIssues.length > 0) {
|
|
158
159
|
verdict = QAVerdict.BLOCKED;
|
|
159
160
|
} else if (majorIssues.length > 0 || issues.length > 5) {
|
|
@@ -164,10 +165,71 @@ class Epic6Executor extends EpicExecutor {
|
|
|
164
165
|
iteration,
|
|
165
166
|
verdict,
|
|
166
167
|
issues,
|
|
168
|
+
reviewSource: 'basic-checks',
|
|
167
169
|
timestamp: new Date().toISOString(),
|
|
168
170
|
};
|
|
169
171
|
}
|
|
170
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Perform a real QA review by invoking the @quality-gate agent.
|
|
175
|
+
* @param {Function} invokeAgent - orchestrator.invokeAgent
|
|
176
|
+
* @param {Object} context - review context
|
|
177
|
+
* @returns {Promise<Object|null>} review result with verdict, or null when the
|
|
178
|
+
* agent produced nothing usable (caller falls back to basic checks).
|
|
179
|
+
* @private
|
|
180
|
+
*/
|
|
181
|
+
async _reviewViaAgent(invokeAgent, context) {
|
|
182
|
+
const { storyId, iteration, codeChanges, testResults } = context;
|
|
183
|
+
|
|
184
|
+
const description =
|
|
185
|
+
`Perform a QA review for story "${storyId || 'unknown'}". ` +
|
|
186
|
+
'Assess correctness, test coverage, and adherence to acceptance criteria. ' +
|
|
187
|
+
`${Array.isArray(codeChanges) ? `${codeChanges.length} code change(s). ` : ''}` +
|
|
188
|
+
`${Array.isArray(testResults) ? `${testResults.length} test result(s). ` : ''}` +
|
|
189
|
+
'End your response with a single verdict line in the form: ' +
|
|
190
|
+
'VERDICT: APPROVED | NEEDS_REVISION | BLOCKED.';
|
|
191
|
+
|
|
192
|
+
const result = await invokeAgent(
|
|
193
|
+
{ name: 'quality-gate' },
|
|
194
|
+
{ id: `qa-review-${storyId || 'story'}-${iteration}`, type: 'review', description },
|
|
195
|
+
{ storyId, iteration },
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
const output = result && (result.output || result.content);
|
|
199
|
+
if (!result || result.success === false || !output || output.trim().length === 0) {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
iteration,
|
|
205
|
+
verdict: this._parseVerdict(output),
|
|
206
|
+
issues: [],
|
|
207
|
+
reviewSource: 'agent',
|
|
208
|
+
raw: output,
|
|
209
|
+
timestamp: new Date().toISOString(),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Parse a QA verdict from free-form agent output. Defaults to NEEDS_REVISION
|
|
215
|
+
* (the safe, non-approving default) when no explicit verdict is found.
|
|
216
|
+
* @param {string} output
|
|
217
|
+
* @returns {string} QAVerdict
|
|
218
|
+
* @private
|
|
219
|
+
*/
|
|
220
|
+
_parseVerdict(output) {
|
|
221
|
+
const text = String(output).toUpperCase();
|
|
222
|
+
const m = text.match(/VERDICT:\s*(APPROVED|NEEDS[_\s]?REVISION|BLOCKED)/);
|
|
223
|
+
const token = (m ? m[1] : '').replace(/\s/g, '_');
|
|
224
|
+
if (token === 'APPROVED') return QAVerdict.APPROVED;
|
|
225
|
+
if (token === 'BLOCKED') return QAVerdict.BLOCKED;
|
|
226
|
+
if (token === 'NEEDS_REVISION') return QAVerdict.NEEDS_REVISION;
|
|
227
|
+
// No explicit verdict → don't fabricate approval.
|
|
228
|
+
if (/\bBLOCKED\b/.test(text)) return QAVerdict.BLOCKED;
|
|
229
|
+
if (/\bAPPROVED\b/.test(text)) return QAVerdict.APPROVED;
|
|
230
|
+
return QAVerdict.NEEDS_REVISION;
|
|
231
|
+
}
|
|
232
|
+
|
|
171
233
|
/**
|
|
172
234
|
* Perform basic quality checks
|
|
173
235
|
* @private
|
|
@@ -193,17 +255,67 @@ class Epic6Executor extends EpicExecutor {
|
|
|
193
255
|
}
|
|
194
256
|
|
|
195
257
|
/**
|
|
196
|
-
* Apply fixes for found issues
|
|
258
|
+
* Apply fixes for found issues by invoking the real @developer agent through
|
|
259
|
+
* the orchestrator (orchestrator.invokeAgent → SubagentDispatcher → claude).
|
|
260
|
+
*
|
|
261
|
+
* epic: orchestration-consolidation — connects Epic 6 to the real executor,
|
|
262
|
+
* same pattern as Epic 3. Falls back honestly (logs that no real fix ran) when
|
|
263
|
+
* no executor is wired or real execution isn't allowed in this environment.
|
|
264
|
+
*
|
|
265
|
+
* @param {Array} issues - Issues found by the review
|
|
266
|
+
* @param {Object} context - Execution context (storyId, techStack, ...)
|
|
267
|
+
* @returns {Promise<Object>} { applied: boolean, fixed: number, stub: boolean }
|
|
197
268
|
* @private
|
|
198
269
|
*/
|
|
199
|
-
async _applyFixes(issues,
|
|
270
|
+
async _applyFixes(issues, context = {}) {
|
|
200
271
|
this._log(`Applying fixes for ${issues.length} issues`);
|
|
201
272
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
273
|
+
const invokeAgent = this.orchestrator && this.orchestrator.invokeAgent;
|
|
274
|
+
const canRunReal = typeof invokeAgent === 'function' && this._realExecutionAllowed();
|
|
275
|
+
|
|
276
|
+
if (!canRunReal || issues.length === 0) {
|
|
277
|
+
// Honest fallback: do NOT claim fixes were applied.
|
|
278
|
+
for (const issue of issues) {
|
|
279
|
+
this._log(`Fix not applied (no real executor): ${issue.type} - ${issue.message}`, 'warn');
|
|
280
|
+
}
|
|
281
|
+
return { applied: false, fixed: 0, stub: true };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const issueList = issues
|
|
285
|
+
.map((i, n) => `${n + 1}. [${i.severity || 'unknown'}] ${i.type}: ${i.message}`)
|
|
286
|
+
.join('\n');
|
|
287
|
+
|
|
288
|
+
const description =
|
|
289
|
+
`Fix the following QA issues found during review of story "${context.storyId || 'unknown'}". ` +
|
|
290
|
+
'Apply the minimal, correct change for each; preserve existing patterns and tests.\n\n' +
|
|
291
|
+
`Issues:\n${issueList}`;
|
|
292
|
+
|
|
293
|
+
try {
|
|
294
|
+
const result = await invokeAgent(
|
|
295
|
+
{ name: 'developer' },
|
|
296
|
+
{ id: `qa-fix-${context.storyId || 'story'}`, type: 'bugfix', description },
|
|
297
|
+
{
|
|
298
|
+
storyId: context.storyId,
|
|
299
|
+
techStack: context.techStack,
|
|
300
|
+
issues: issues.map((i) => ({ type: i.type, severity: i.severity, message: i.message })),
|
|
301
|
+
},
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
if (result && result.success !== false) {
|
|
305
|
+
this._log(`Applied fixes via real agent (${issues.length} issues addressed)`);
|
|
306
|
+
return {
|
|
307
|
+
applied: true,
|
|
308
|
+
fixed: issues.length,
|
|
309
|
+
stub: false,
|
|
310
|
+
filesModified: result.filesModified || [],
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
this._log(`Fix agent returned no success: ${result && result.error}`, 'warn');
|
|
314
|
+
} catch (err) {
|
|
315
|
+
this._log(`Fix agent invocation failed: ${err.message}`, 'warn');
|
|
206
316
|
}
|
|
317
|
+
|
|
318
|
+
return { applied: false, fixed: 0, stub: true };
|
|
207
319
|
}
|
|
208
320
|
|
|
209
321
|
/**
|
|
@@ -22,6 +22,7 @@ const ExecutionStatus = {
|
|
|
22
22
|
SUCCESS: 'success',
|
|
23
23
|
FAILED: 'failed',
|
|
24
24
|
SKIPPED: 'skipped',
|
|
25
|
+
STUB: 'stub',
|
|
25
26
|
};
|
|
26
27
|
|
|
27
28
|
/**
|
|
@@ -146,6 +147,45 @@ class EpicExecutor {
|
|
|
146
147
|
};
|
|
147
148
|
}
|
|
148
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Complete execution in STUB mode — work was NOT actually performed.
|
|
152
|
+
*
|
|
153
|
+
* Honesty invariant (epic: orchestration-consolidation, Frente F0a):
|
|
154
|
+
* a stub MUST NOT report success:true. getResult() yields success:false
|
|
155
|
+
* because this.status !== SUCCESS.
|
|
156
|
+
*
|
|
157
|
+
* @param {string} reason - What real work is missing
|
|
158
|
+
* @param {Object} [result] - Optional result data to merge
|
|
159
|
+
* @protected
|
|
160
|
+
*/
|
|
161
|
+
_stubExecution(reason, result = {}) {
|
|
162
|
+
this.status = ExecutionStatus.STUB;
|
|
163
|
+
this.endTime = new Date().toISOString();
|
|
164
|
+
this._log(`Epic ${this.epicNum} ran in STUB mode (no real work performed): ${reason}`, 'warn');
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
...this.getResult(),
|
|
168
|
+
status: ExecutionStatus.STUB,
|
|
169
|
+
stub: true,
|
|
170
|
+
stubReason: reason,
|
|
171
|
+
...result,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Whether real execution (spawning claude / running the real build) is allowed.
|
|
177
|
+
* Returns false inside the test runner unless SINAPSE_REAL_DISPATCH=1 — this
|
|
178
|
+
* prevents slow, costly, non-deterministic CLI/build calls during unit tests.
|
|
179
|
+
* epic: orchestration-consolidation, F1.
|
|
180
|
+
* @returns {boolean}
|
|
181
|
+
* @protected
|
|
182
|
+
*/
|
|
183
|
+
_realExecutionAllowed() {
|
|
184
|
+
return (
|
|
185
|
+
process.env.JEST_WORKER_ID === undefined || process.env.SINAPSE_REAL_DISPATCH === '1'
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
149
189
|
/**
|
|
150
190
|
* Add artifact to results
|
|
151
191
|
* @param {string} type - Artifact type (file, report, data)
|
|
@@ -39,6 +39,35 @@ const DEFAULT_GREENFIELD_INDICATORS = [
|
|
|
39
39
|
'.sinapse-ai/',
|
|
40
40
|
];
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Maps a classified project_type to its greenfield workflow variant. The
|
|
44
|
+
* variants stay separate files (distinct phase/agent contracts — fusion was
|
|
45
|
+
* refuted in the deep-dive); the handler just dispatches to the right one.
|
|
46
|
+
* Unknown/absent type falls back to fullstack (the historical default).
|
|
47
|
+
* @see project-intelligence.md classification matrix
|
|
48
|
+
*/
|
|
49
|
+
const GREENFIELD_WORKFLOW_BY_TYPE = Object.freeze({
|
|
50
|
+
site: 'greenfield-ui',
|
|
51
|
+
lp: 'greenfield-ui',
|
|
52
|
+
app: 'greenfield-ui',
|
|
53
|
+
platform: 'greenfield-fullstack',
|
|
54
|
+
saas: 'greenfield-fullstack',
|
|
55
|
+
api: 'greenfield-service',
|
|
56
|
+
service: 'greenfield-service',
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const DEFAULT_GREENFIELD_WORKFLOW = 'greenfield-fullstack';
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Resolve the greenfield workflow basename for a project_type.
|
|
63
|
+
* @param {string} [projectType]
|
|
64
|
+
* @returns {string} workflow name (without extension)
|
|
65
|
+
*/
|
|
66
|
+
function resolveGreenfieldWorkflow(projectType) {
|
|
67
|
+
const key = String(projectType || '').toLowerCase();
|
|
68
|
+
return GREENFIELD_WORKFLOW_BY_TYPE[key] || DEFAULT_GREENFIELD_WORKFLOW;
|
|
69
|
+
}
|
|
70
|
+
|
|
42
71
|
/**
|
|
43
72
|
* Greenfield workflow phases
|
|
44
73
|
* @enum {string}
|
|
@@ -112,10 +141,12 @@ class GreenfieldHandler extends EventEmitter {
|
|
|
112
141
|
this._surfaceChecker = options.surfaceChecker || null;
|
|
113
142
|
this._sessionState = options.sessionState || null;
|
|
114
143
|
|
|
115
|
-
// Workflow
|
|
144
|
+
// Workflow variant dispatched by project_type (site/lp/app → ui,
|
|
145
|
+
// platform/saas → fullstack, api/service → service). Default fullstack.
|
|
146
|
+
this.workflowName = resolveGreenfieldWorkflow(options.projectType);
|
|
116
147
|
this.workflowPath = path.join(
|
|
117
148
|
projectRoot,
|
|
118
|
-
|
|
149
|
+
`.sinapse-ai/development/workflows/${this.workflowName}.yaml`,
|
|
119
150
|
);
|
|
120
151
|
|
|
121
152
|
// Phase progress tracking
|
|
@@ -224,6 +255,12 @@ class GreenfieldHandler extends EventEmitter {
|
|
|
224
255
|
async handle(context = {}) {
|
|
225
256
|
this._log('Greenfield handler invoked');
|
|
226
257
|
|
|
258
|
+
// Stream B (Frente 4.2): ensure the secret-scan git guard is wired into the
|
|
259
|
+
// target project. Runs on EVERY entry (Phase 0 bootstrap AND the
|
|
260
|
+
// skip-bootstrap / resume paths) so a project that already has package.json
|
|
261
|
+
// + .git still gets the guard. Best-effort, idempotent, never throws.
|
|
262
|
+
await this._ensureGitHooks();
|
|
263
|
+
|
|
227
264
|
// Check for resume (AC14)
|
|
228
265
|
const resumePhase = context.resumeFromPhase;
|
|
229
266
|
if (typeof resumePhase === 'number' && resumePhase >= 0) {
|
|
@@ -273,6 +310,51 @@ class GreenfieldHandler extends EventEmitter {
|
|
|
273
310
|
}
|
|
274
311
|
}
|
|
275
312
|
|
|
313
|
+
/**
|
|
314
|
+
* Ensures the SINAPSE secret-scan git guard is installed in the project.
|
|
315
|
+
*
|
|
316
|
+
* Stream B (Frente 4.2): wires `core.hooksPath` to a managed Node pre-commit
|
|
317
|
+
* hook so every greenfield project the framework bootstraps blocks staged
|
|
318
|
+
* secrets. Best-effort and idempotent — re-running never duplicates hooks and
|
|
319
|
+
* a failure here never aborts the workflow.
|
|
320
|
+
*
|
|
321
|
+
* @returns {Promise<boolean>} true if the guard is wired
|
|
322
|
+
* @private
|
|
323
|
+
*/
|
|
324
|
+
async _ensureGitHooks() {
|
|
325
|
+
// Resolve the installer across both layouts: the sinapse-ai dev repo
|
|
326
|
+
// (`<root>/packages/installer/...`) and an installed project copy (where
|
|
327
|
+
// the framework lives under `.sinapse-ai/`). First match wins.
|
|
328
|
+
const candidates = [
|
|
329
|
+
path.join(this.projectRoot, 'packages', 'installer', 'src', 'installer', 'git-hooks-installer'),
|
|
330
|
+
path.join(this.projectRoot, '.sinapse-ai', 'packages', 'installer', 'src', 'installer', 'git-hooks-installer'),
|
|
331
|
+
];
|
|
332
|
+
|
|
333
|
+
let installGitHooks = null;
|
|
334
|
+
for (const candidate of candidates) {
|
|
335
|
+
try {
|
|
336
|
+
({ installGitHooks } = require(candidate));
|
|
337
|
+
if (typeof installGitHooks === 'function') break;
|
|
338
|
+
} catch {
|
|
339
|
+
// Try the next candidate.
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (typeof installGitHooks !== 'function') {
|
|
344
|
+
this._log('Git secret-scan guard not installed: git-hooks-installer not found', 'warn');
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
try {
|
|
349
|
+
const res = await installGitHooks({ projectDir: this.projectRoot });
|
|
350
|
+
this._log(`Git secret-scan guard: ${res.success ? 'installed' : 'skipped'}${res.error ? ` (${res.error})` : ''}`);
|
|
351
|
+
return res.success;
|
|
352
|
+
} catch (error) {
|
|
353
|
+
this._log(`Git secret-scan guard not installed: ${error.message}`, 'warn');
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
276
358
|
// ═══════════════════════════════════════════════════════════════════════════════════
|
|
277
359
|
// PHASE 0: BOOTSTRAP (AC2)
|
|
278
360
|
// ═══════════════════════════════════════════════════════════════════════════════════
|
|
@@ -820,7 +902,7 @@ class GreenfieldHandler extends EventEmitter {
|
|
|
820
902
|
const exists = await sessionState.exists();
|
|
821
903
|
if (exists) {
|
|
822
904
|
await sessionState.loadSessionState();
|
|
823
|
-
await sessionState.recordPhaseChange(`greenfield_${phase}`,
|
|
905
|
+
await sessionState.recordPhaseChange(`greenfield_${phase}`, this.workflowName, '@pm');
|
|
824
906
|
this._log(`Phase recorded in session state: ${phase}`);
|
|
825
907
|
}
|
|
826
908
|
} catch (error) {
|
|
@@ -944,5 +1026,7 @@ module.exports = {
|
|
|
944
1026
|
PhaseFailureAction,
|
|
945
1027
|
DEFAULT_GREENFIELD_INDICATORS,
|
|
946
1028
|
PHASE_1_SEQUENCE,
|
|
1029
|
+
GREENFIELD_WORKFLOW_BY_TYPE,
|
|
1030
|
+
resolveGreenfieldWorkflow,
|
|
947
1031
|
};
|
|
948
1032
|
|