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
|
@@ -85,6 +85,18 @@ const { G2StoryCreationGate } = require('./gates/g2-story-creation');
|
|
|
85
85
|
const { G3StoryValidationGate } = require('./gates/g3-story-validation');
|
|
86
86
|
const { G4DevContextGate, G4_DEFAULT_TIMEOUT_MS } = require('./gates/g4-dev-context');
|
|
87
87
|
|
|
88
|
+
// IDS-5b: Gate G5 (Blocking — Semantic Handshake)
|
|
89
|
+
const {
|
|
90
|
+
G5SemanticHandshakeGate,
|
|
91
|
+
G5_DEFAULT_TIMEOUT_MS,
|
|
92
|
+
} = require('./gates/g5-semantic-handshake');
|
|
93
|
+
|
|
94
|
+
// IDS Gate Wiring: Gate G6 (Blocking — CI/CD Registry Integrity)
|
|
95
|
+
const {
|
|
96
|
+
G6CiIntegrityGate,
|
|
97
|
+
G6_DEFAULT_TIMEOUT_MS,
|
|
98
|
+
} = require('./gates/g6-ci-integrity');
|
|
99
|
+
|
|
88
100
|
// IDS-7: Framework Governor (sinapse-orqx integration)
|
|
89
101
|
const {
|
|
90
102
|
FrameworkGovernor,
|
|
@@ -92,6 +104,12 @@ const {
|
|
|
92
104
|
RISK_THRESHOLDS,
|
|
93
105
|
} = require('./framework-governor');
|
|
94
106
|
|
|
107
|
+
// IDS Gate Wiring: Gate Evaluator (wires G1-G5 into the SDC workflow)
|
|
108
|
+
const {
|
|
109
|
+
GateEvaluator,
|
|
110
|
+
DEFAULT_PHASE_GATES,
|
|
111
|
+
} = require('./gate-evaluator');
|
|
112
|
+
|
|
95
113
|
module.exports = {
|
|
96
114
|
// IDS-1: Registry Foundation
|
|
97
115
|
RegistryLoader,
|
|
@@ -149,9 +167,21 @@ module.exports = {
|
|
|
149
167
|
G4DevContextGate,
|
|
150
168
|
G4_DEFAULT_TIMEOUT_MS,
|
|
151
169
|
|
|
170
|
+
// IDS-5b: Gate G5 (Blocking)
|
|
171
|
+
G5SemanticHandshakeGate,
|
|
172
|
+
G5_DEFAULT_TIMEOUT_MS,
|
|
173
|
+
|
|
174
|
+
// IDS Gate Wiring: Gate G6 (Blocking — CI/CD Registry Integrity)
|
|
175
|
+
G6CiIntegrityGate,
|
|
176
|
+
G6_DEFAULT_TIMEOUT_MS,
|
|
177
|
+
|
|
152
178
|
// IDS-7: Framework Governor
|
|
153
179
|
FrameworkGovernor,
|
|
154
180
|
TIMEOUT_MS,
|
|
155
181
|
RISK_THRESHOLDS,
|
|
182
|
+
|
|
183
|
+
// IDS Gate Wiring: Gate Evaluator
|
|
184
|
+
GateEvaluator,
|
|
185
|
+
DEFAULT_PHASE_GATES,
|
|
156
186
|
};
|
|
157
187
|
|
|
@@ -7,6 +7,17 @@
|
|
|
7
7
|
*
|
|
8
8
|
* @created 2026-01-29
|
|
9
9
|
* @updated 2026-02-09 - Removed orphan modules tests (Story MIS-2)
|
|
10
|
+
* @updated 2026-06-15 - Status note added (see below)
|
|
11
|
+
*
|
|
12
|
+
* STATUS — IMPORTANT (do not mistake this for a passing CI gate):
|
|
13
|
+
* This is a `.verify.js` standalone script (run manually via `node`), NOT a
|
|
14
|
+
* Jest spec — jest.config.js only matches `*.test.js`/`*.spec.js`, so it never
|
|
15
|
+
* runs in CI. The "Feedback Loop" section below asserts the Story 9.4 surface
|
|
16
|
+
* (FeedbackType enum + trackUserFeedback / getAccuracyMetrics / getSuggestedRules)
|
|
17
|
+
* which is NOT yet implemented in gotchas-memory.js — those assertions are a
|
|
18
|
+
* forward spec for pending work, not a regression check. The Custom Rules
|
|
19
|
+
* section reflects shipped behavior. Do not "fix" this by inventing the feedback
|
|
20
|
+
* feature without its story (Constitution Art. IV — No Invention).
|
|
10
21
|
*/
|
|
11
22
|
|
|
12
23
|
const path = require('path');
|
|
@@ -253,15 +253,29 @@ class GotchasMemory extends EventEmitter {
|
|
|
253
253
|
count: 0,
|
|
254
254
|
firstSeen: now,
|
|
255
255
|
lastSeen: now,
|
|
256
|
+
timestamps: [],
|
|
256
257
|
samples: [],
|
|
257
258
|
errorPattern: errorData.message,
|
|
258
259
|
category: this._detectCategory(errorData.message + ' ' + (errorData.stack || '')),
|
|
259
260
|
};
|
|
260
261
|
}
|
|
261
262
|
|
|
262
|
-
//
|
|
263
|
-
|
|
263
|
+
// Keep only occurrences inside the configured rolling error window.
|
|
264
|
+
const windowStart = now - this.options.errorWindowMs;
|
|
265
|
+
const timestamps = this._normalizeErrorTimestamps(tracking).filter(
|
|
266
|
+
(timestamp) => timestamp >= windowStart,
|
|
267
|
+
);
|
|
268
|
+
timestamps.push(now);
|
|
269
|
+
|
|
270
|
+
tracking.timestamps = timestamps;
|
|
271
|
+
tracking.count = timestamps.length;
|
|
272
|
+
tracking.firstSeen = timestamps[0];
|
|
264
273
|
tracking.lastSeen = now;
|
|
274
|
+
tracking.samples = (tracking.samples || []).filter((sample) => {
|
|
275
|
+
const timestamp = Date.parse(sample.timestamp);
|
|
276
|
+
return Number.isFinite(timestamp) && timestamp >= windowStart;
|
|
277
|
+
});
|
|
278
|
+
|
|
265
279
|
if (tracking.samples.length < 5) {
|
|
266
280
|
tracking.samples.push({
|
|
267
281
|
timestamp: new Date(now).toISOString(),
|
|
@@ -759,6 +773,27 @@ class GotchasMemory extends EventEmitter {
|
|
|
759
773
|
return Math.abs(hash).toString(16);
|
|
760
774
|
}
|
|
761
775
|
|
|
776
|
+
/**
|
|
777
|
+
* Normalize persisted error occurrence timestamps.
|
|
778
|
+
* Backward-compat: if tracking only has lastSeen (legacy format), fall back to [lastSeen].
|
|
779
|
+
* @private
|
|
780
|
+
* @param {Object} tracking - Error tracking entry
|
|
781
|
+
* @returns {number[]} Timestamp list in milliseconds
|
|
782
|
+
*/
|
|
783
|
+
_normalizeErrorTimestamps(tracking) {
|
|
784
|
+
const timestamps = Array.isArray(tracking.timestamps) ? tracking.timestamps : [tracking.lastSeen];
|
|
785
|
+
|
|
786
|
+
return timestamps
|
|
787
|
+
.map((timestamp) => {
|
|
788
|
+
if (typeof timestamp === 'number') {
|
|
789
|
+
return timestamp;
|
|
790
|
+
}
|
|
791
|
+
const parsed = Date.parse(timestamp);
|
|
792
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
793
|
+
})
|
|
794
|
+
.filter((timestamp) => timestamp !== null);
|
|
795
|
+
}
|
|
796
|
+
|
|
762
797
|
/**
|
|
763
798
|
* Detect category from text
|
|
764
799
|
* @private
|
|
@@ -116,6 +116,12 @@ class AgentInvoker extends EventEmitter {
|
|
|
116
116
|
// Audit log (AC7)
|
|
117
117
|
this.invocations = [];
|
|
118
118
|
this.logs = [];
|
|
119
|
+
|
|
120
|
+
// Memoization caches: agent/task definitions are read from disk on every
|
|
121
|
+
// invocation but never change during a process run. Caching avoids the
|
|
122
|
+
// repeated fs.readFile hops the deep-dive flagged as a redundant I/O hop.
|
|
123
|
+
this._agentCache = new Map();
|
|
124
|
+
this._taskCache = new Map();
|
|
119
125
|
}
|
|
120
126
|
|
|
121
127
|
/**
|
|
@@ -218,6 +224,10 @@ class AgentInvoker extends EventEmitter {
|
|
|
218
224
|
// Normalize agent name (remove @ prefix if present)
|
|
219
225
|
const name = agentName.replace(/^@/, '').toLowerCase();
|
|
220
226
|
|
|
227
|
+
if (this._agentCache.has(name)) {
|
|
228
|
+
return this._agentCache.get(name);
|
|
229
|
+
}
|
|
230
|
+
|
|
221
231
|
// Check if supported
|
|
222
232
|
const agentConfig = SUPPORTED_AGENTS[name];
|
|
223
233
|
if (!agentConfig) {
|
|
@@ -239,12 +249,14 @@ class AgentInvoker extends EventEmitter {
|
|
|
239
249
|
|
|
240
250
|
const content = await fs.readFile(agentPath, 'utf8');
|
|
241
251
|
|
|
242
|
-
|
|
252
|
+
const result = {
|
|
243
253
|
...agentConfig,
|
|
244
254
|
loaded: true,
|
|
245
255
|
content,
|
|
246
256
|
path: agentPath,
|
|
247
257
|
};
|
|
258
|
+
this._agentCache.set(name, result);
|
|
259
|
+
return result;
|
|
248
260
|
}
|
|
249
261
|
|
|
250
262
|
/**
|
|
@@ -252,6 +264,10 @@ class AgentInvoker extends EventEmitter {
|
|
|
252
264
|
* @private
|
|
253
265
|
*/
|
|
254
266
|
async _loadTask(taskPath) {
|
|
267
|
+
if (this._taskCache.has(taskPath)) {
|
|
268
|
+
return this._taskCache.get(taskPath);
|
|
269
|
+
}
|
|
270
|
+
|
|
255
271
|
// Handle both full path and task name
|
|
256
272
|
let fullPath = taskPath;
|
|
257
273
|
if (!path.isAbsolute(taskPath)) {
|
|
@@ -275,12 +291,14 @@ class AgentInvoker extends EventEmitter {
|
|
|
275
291
|
// Parse task metadata from frontmatter
|
|
276
292
|
const metadata = this._parseTaskMetadata(content);
|
|
277
293
|
|
|
278
|
-
|
|
294
|
+
const result = {
|
|
279
295
|
path: fullPath,
|
|
280
296
|
name: path.basename(fullPath, '.md'),
|
|
281
297
|
content,
|
|
282
298
|
...metadata,
|
|
283
299
|
};
|
|
300
|
+
this._taskCache.set(taskPath, result);
|
|
301
|
+
return result;
|
|
284
302
|
}
|
|
285
303
|
|
|
286
304
|
/**
|
|
@@ -401,11 +419,16 @@ class AgentInvoker extends EventEmitter {
|
|
|
401
419
|
);
|
|
402
420
|
}
|
|
403
421
|
|
|
404
|
-
//
|
|
405
|
-
//
|
|
422
|
+
// Honesty invariant (epic: orchestration-consolidation, F1):
|
|
423
|
+
// No executor wired → do NOT fabricate a 'simulated' success. Report a stub so
|
|
424
|
+
// callers never mistake "nothing ran" for real work. The MasterOrchestrator
|
|
425
|
+
// supplies a real SubagentDispatcher executor by default (_createDispatchExecutor).
|
|
406
426
|
return {
|
|
407
|
-
status: '
|
|
408
|
-
|
|
427
|
+
status: 'stub',
|
|
428
|
+
stub: true,
|
|
429
|
+
stubReason:
|
|
430
|
+
'No executor wired into AgentInvoker — real dispatch is supplied by MasterOrchestrator.',
|
|
431
|
+
message: `Task ${task.name} not executed (no executor) for @${agent.name}`,
|
|
409
432
|
context: {
|
|
410
433
|
agentName: agent.name,
|
|
411
434
|
taskName: task.name,
|
|
@@ -67,6 +67,35 @@ const PhaseFailureAction = {
|
|
|
67
67
|
ABORT: 'abort',
|
|
68
68
|
};
|
|
69
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Maps a classified project_type to its brownfield workflow variant. Variants
|
|
72
|
+
* stay separate files (distinct contracts — fusion refuted in the deep-dive);
|
|
73
|
+
* the handler dispatches to the right one. Unknown/absent type falls back to
|
|
74
|
+
* brownfield-discovery (the generic 10-phase tech-debt assessment default).
|
|
75
|
+
* @see project-intelligence.md classification matrix
|
|
76
|
+
*/
|
|
77
|
+
const BROWNFIELD_WORKFLOW_BY_TYPE = Object.freeze({
|
|
78
|
+
site: 'brownfield-ui',
|
|
79
|
+
lp: 'brownfield-ui',
|
|
80
|
+
app: 'brownfield-ui',
|
|
81
|
+
platform: 'brownfield-fullstack',
|
|
82
|
+
saas: 'brownfield-fullstack',
|
|
83
|
+
api: 'brownfield-service',
|
|
84
|
+
service: 'brownfield-service',
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const DEFAULT_BROWNFIELD_WORKFLOW = 'brownfield-discovery';
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Resolve the brownfield workflow basename for a project_type.
|
|
91
|
+
* @param {string} [projectType]
|
|
92
|
+
* @returns {string} workflow name (without extension)
|
|
93
|
+
*/
|
|
94
|
+
function resolveBrownfieldWorkflow(projectType) {
|
|
95
|
+
const key = String(projectType || '').toLowerCase();
|
|
96
|
+
return BROWNFIELD_WORKFLOW_BY_TYPE[key] || DEFAULT_BROWNFIELD_WORKFLOW;
|
|
97
|
+
}
|
|
98
|
+
|
|
70
99
|
// ═══════════════════════════════════════════════════════════════════════════════════
|
|
71
100
|
// BROWNFIELD HANDLER CLASS
|
|
72
101
|
// ═══════════════════════════════════════════════════════════════════════════════════
|
|
@@ -101,10 +130,12 @@ class BrownfieldHandler extends EventEmitter {
|
|
|
101
130
|
this._surfaceChecker = options.surfaceChecker || null;
|
|
102
131
|
this._sessionState = options.sessionState || null;
|
|
103
132
|
|
|
104
|
-
// Workflow
|
|
133
|
+
// Workflow variant dispatched by project_type (site/lp/app → ui,
|
|
134
|
+
// platform/saas → fullstack, api/service → service). Default discovery.
|
|
135
|
+
this.workflowName = resolveBrownfieldWorkflow(options.projectType);
|
|
105
136
|
this.workflowPath = path.join(
|
|
106
137
|
projectRoot,
|
|
107
|
-
|
|
138
|
+
`.sinapse-ai/development/workflows/${this.workflowName}.yaml`,
|
|
108
139
|
);
|
|
109
140
|
|
|
110
141
|
// Phase progress tracking
|
|
@@ -723,7 +754,7 @@ Quer que eu comece?`;
|
|
|
723
754
|
const exists = await sessionState.exists();
|
|
724
755
|
if (exists) {
|
|
725
756
|
await sessionState.loadSessionState();
|
|
726
|
-
await sessionState.recordPhaseChange(`brownfield_${phase}`,
|
|
757
|
+
await sessionState.recordPhaseChange(`brownfield_${phase}`, this.workflowName, '@architect');
|
|
727
758
|
this._log(`Phase recorded in session state: ${phase}`);
|
|
728
759
|
}
|
|
729
760
|
} catch (error) {
|
|
@@ -758,5 +789,7 @@ module.exports = {
|
|
|
758
789
|
BrownfieldPhase,
|
|
759
790
|
PostDiscoveryChoice,
|
|
760
791
|
PhaseFailureAction,
|
|
792
|
+
BROWNFIELD_WORKFLOW_BY_TYPE,
|
|
793
|
+
resolveBrownfieldWorkflow,
|
|
761
794
|
};
|
|
762
795
|
|
|
@@ -23,6 +23,28 @@
|
|
|
23
23
|
* @property {string} reason - Reason for the decision
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Runtime-resolved condition defaults.
|
|
28
|
+
* These values are used when a condition is not a built-in evaluator and
|
|
29
|
+
* no runtime override has been set. Frozen so callers cannot mutate them.
|
|
30
|
+
*/
|
|
31
|
+
const RUNTIME_CONDITION_DEFAULTS = Object.freeze({
|
|
32
|
+
after_prd_creation: true,
|
|
33
|
+
architecture_changes_needed: true,
|
|
34
|
+
architecture_suggests_prd_changes: true,
|
|
35
|
+
based_on_classification: true,
|
|
36
|
+
documentation_inadequate: true,
|
|
37
|
+
epic_complete: true,
|
|
38
|
+
gate_approved: true,
|
|
39
|
+
major_enhancement_path: true,
|
|
40
|
+
po_checklist_issues: true,
|
|
41
|
+
qa_left_unchecked_items: true,
|
|
42
|
+
stories_remaining: true,
|
|
43
|
+
user_has_generated_ui: true,
|
|
44
|
+
user_wants_ai_generation: true,
|
|
45
|
+
user_wants_story_review: true,
|
|
46
|
+
});
|
|
47
|
+
|
|
26
48
|
/**
|
|
27
49
|
* Evaluates workflow conditions based on detected tech stack
|
|
28
50
|
*/
|
|
@@ -36,6 +58,30 @@ class ConditionEvaluator {
|
|
|
36
58
|
// Context for QA approval tracking (updated externally)
|
|
37
59
|
this._qaApproved = false;
|
|
38
60
|
this._phaseOutputs = {};
|
|
61
|
+
|
|
62
|
+
// Runtime condition overrides — take precedence over defaults and built-ins
|
|
63
|
+
this._runtimeOverrides = new Map();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Set a single runtime-resolved condition value.
|
|
68
|
+
* Runtime overrides take precedence over RUNTIME_CONDITION_DEFAULTS and
|
|
69
|
+
* over built-in tech-stack evaluators.
|
|
70
|
+
* @param {string} name - Condition name
|
|
71
|
+
* @param {boolean} value - Runtime condition value
|
|
72
|
+
*/
|
|
73
|
+
setRuntimeCondition(name, value) {
|
|
74
|
+
this._runtimeOverrides.set(name, Boolean(value));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Set multiple runtime-resolved condition values at once.
|
|
79
|
+
* @param {Object} overrides - Map of condition names to boolean values
|
|
80
|
+
*/
|
|
81
|
+
setRuntimeConditions(overrides = {}) {
|
|
82
|
+
for (const [name, value] of Object.entries(overrides)) {
|
|
83
|
+
this.setRuntimeCondition(name, value);
|
|
84
|
+
}
|
|
39
85
|
}
|
|
40
86
|
|
|
41
87
|
/**
|
|
@@ -65,6 +111,11 @@ class ConditionEvaluator {
|
|
|
65
111
|
return true;
|
|
66
112
|
}
|
|
67
113
|
|
|
114
|
+
// Runtime overrides take highest precedence
|
|
115
|
+
if (this._runtimeOverrides.has(condition)) {
|
|
116
|
+
return this._runtimeOverrides.get(condition);
|
|
117
|
+
}
|
|
118
|
+
|
|
68
119
|
// Built-in condition evaluators
|
|
69
120
|
const evaluators = {
|
|
70
121
|
// Tech stack conditions
|
|
@@ -105,6 +156,11 @@ class ConditionEvaluator {
|
|
|
105
156
|
return evaluator();
|
|
106
157
|
}
|
|
107
158
|
|
|
159
|
+
// Fall back to frozen defaults for known runtime conditions
|
|
160
|
+
if (Object.prototype.hasOwnProperty.call(RUNTIME_CONDITION_DEFAULTS, condition)) {
|
|
161
|
+
return RUNTIME_CONDITION_DEFAULTS[condition];
|
|
162
|
+
}
|
|
163
|
+
|
|
108
164
|
// Handle negation first
|
|
109
165
|
if (condition.startsWith('!')) {
|
|
110
166
|
return !this.evaluate(condition.substring(1).trim());
|
|
@@ -377,4 +433,5 @@ class ConditionEvaluator {
|
|
|
377
433
|
}
|
|
378
434
|
|
|
379
435
|
module.exports = ConditionEvaluator;
|
|
436
|
+
module.exports.RUNTIME_CONDITION_DEFAULTS = RUNTIME_CONDITION_DEFAULTS;
|
|
380
437
|
|
|
@@ -77,6 +77,7 @@ class Epic3Executor extends EpicExecutor {
|
|
|
77
77
|
// Execute spec pipeline phases
|
|
78
78
|
const phaseResults = {};
|
|
79
79
|
let specPath = null;
|
|
80
|
+
let specWasStubbed = false;
|
|
80
81
|
|
|
81
82
|
for (const phase of SPEC_PHASES) {
|
|
82
83
|
this._log(`Running phase: ${phase}`);
|
|
@@ -113,9 +114,18 @@ class Epic3Executor extends EpicExecutor {
|
|
|
113
114
|
|
|
114
115
|
// Check if spec file exists
|
|
115
116
|
if (!(await fs.pathExists(specPath))) {
|
|
116
|
-
//
|
|
117
|
-
await this.
|
|
118
|
-
|
|
117
|
+
// F1: try to generate a REAL spec via a real agent through the orchestrator.
|
|
118
|
+
const generated = await this._generateSpecViaAgent(storyId, source, techStack, context);
|
|
119
|
+
if (generated) {
|
|
120
|
+
await fs.ensureDir(path.dirname(specPath));
|
|
121
|
+
await fs.writeFile(specPath, generated);
|
|
122
|
+
this._log('Generated spec via real agent invocation');
|
|
123
|
+
} else {
|
|
124
|
+
// No executor wired (or agent produced nothing) → honest stub.
|
|
125
|
+
await this._createStubSpec(specPath, storyId, context);
|
|
126
|
+
this._log('Created stub spec (no real agent available)');
|
|
127
|
+
specWasStubbed = true;
|
|
128
|
+
}
|
|
119
129
|
}
|
|
120
130
|
|
|
121
131
|
this._addArtifact('spec', specPath);
|
|
@@ -124,12 +134,20 @@ class Epic3Executor extends EpicExecutor {
|
|
|
124
134
|
const complexity = phaseResults['assess-complexity']?.complexity || 'STANDARD';
|
|
125
135
|
const requirements = phaseResults['gather-requirements']?.requirements || [];
|
|
126
136
|
|
|
127
|
-
|
|
137
|
+
// Honesty invariant (epic: orchestration-consolidation, F0a):
|
|
138
|
+
// if the spec was auto-stubbed (no real agent ran), report STUB, not success.
|
|
139
|
+
const specResult = {
|
|
128
140
|
specPath,
|
|
129
141
|
complexity,
|
|
130
142
|
requirements,
|
|
131
143
|
phases: Object.keys(phaseResults),
|
|
132
|
-
}
|
|
144
|
+
};
|
|
145
|
+
return specWasStubbed
|
|
146
|
+
? this._stubExecution(
|
|
147
|
+
'Spec auto-stubbed — real spec generation requires agent invocation',
|
|
148
|
+
specResult,
|
|
149
|
+
)
|
|
150
|
+
: this._completeExecution(specResult);
|
|
133
151
|
} catch (error) {
|
|
134
152
|
return this._failExecution(error);
|
|
135
153
|
}
|
|
@@ -181,6 +199,59 @@ class Epic3Executor extends EpicExecutor {
|
|
|
181
199
|
};
|
|
182
200
|
}
|
|
183
201
|
|
|
202
|
+
/**
|
|
203
|
+
* Generate the spec by invoking a real agent through the orchestrator.
|
|
204
|
+
*
|
|
205
|
+
* epic: orchestration-consolidation, F1 — connects Epic 3 to the real executor
|
|
206
|
+
* (orchestrator.invokeAgent → SubagentDispatcher → claude). Returns the generated
|
|
207
|
+
* spec markdown, or null when no executor is wired or the agent produced nothing
|
|
208
|
+
* (the caller then falls back to an honest stub).
|
|
209
|
+
*
|
|
210
|
+
* @returns {Promise<string|null>} Generated spec markdown, or null
|
|
211
|
+
* @private
|
|
212
|
+
*/
|
|
213
|
+
async _generateSpecViaAgent(storyId, source, techStack, context = {}) {
|
|
214
|
+
const invokeAgent = this.orchestrator && this.orchestrator.invokeAgent;
|
|
215
|
+
if (typeof invokeAgent !== 'function') {
|
|
216
|
+
return null; // No real executor wired → caller falls back to stub
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const description =
|
|
220
|
+
`Generate a complete, implementation-ready specification (spec.md) for story "${storyId}". ` +
|
|
221
|
+
`Source: ${source || 'story'}. ` +
|
|
222
|
+
(techStack ? `Tech stack: ${JSON.stringify(techStack)}. ` : '') +
|
|
223
|
+
'Output ONLY the spec markdown: overview, scope (in/out), acceptance criteria, ' +
|
|
224
|
+
'dependencies and complexity estimate. No commentary outside the spec.';
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
// Pass only serializable, prompt-relevant context — never the execution
|
|
228
|
+
// context wholesale (it carries `orchestrator: this` → circular reference).
|
|
229
|
+
const result = await invokeAgent(
|
|
230
|
+
{ name: 'analyst' },
|
|
231
|
+
{ id: `spec-${storyId}`, type: 'analysis', description },
|
|
232
|
+
{ storyId, source, techStack, prdPath: context.prdPath },
|
|
233
|
+
);
|
|
234
|
+
const content = result && (result.output || result.content);
|
|
235
|
+
// Honesty: an agent "success" whose output is a CLI error banner or a
|
|
236
|
+
// few stray lines is NOT a spec. Require minimum substance + markdown
|
|
237
|
+
// structure before accepting, otherwise fall back to the honest stub.
|
|
238
|
+
const looksLikeSpec =
|
|
239
|
+
typeof content === 'string' &&
|
|
240
|
+
content.trim().length >= 200 &&
|
|
241
|
+
/^#{1,3}\s/m.test(content) &&
|
|
242
|
+
!/issue with the selected model|may not exist or you may not have access/i.test(content);
|
|
243
|
+
if (result && result.success !== false && looksLikeSpec) {
|
|
244
|
+
return content;
|
|
245
|
+
}
|
|
246
|
+
if (content && !looksLikeSpec) {
|
|
247
|
+
this._log('Agent output rejected (not a plausible spec) — falling back to stub', 'warn');
|
|
248
|
+
}
|
|
249
|
+
} catch (err) {
|
|
250
|
+
this._log(`Spec agent invocation failed: ${err.message}`, 'warn');
|
|
251
|
+
}
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
|
|
184
255
|
/**
|
|
185
256
|
* Create stub spec file
|
|
186
257
|
* @private
|
|
@@ -105,37 +105,83 @@ class Epic4Executor extends EpicExecutor {
|
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
//
|
|
109
|
-
|
|
108
|
+
// F1/convergência (epic: orchestration-consolidation): Epic 4 no longer
|
|
109
|
+
// re-implements execution. It DELEGATES to the BuildOrchestrator — the single
|
|
110
|
+
// real build path (worktree → plan → execute via claude → QA → merge). This
|
|
111
|
+
// kills the duplication the audit flagged (two competing execution engines).
|
|
112
|
+
if (this._realExecutionAllowed()) {
|
|
113
|
+
const buildResult = await this._executeViaBuildOrchestrator(storyId, context);
|
|
114
|
+
if (buildResult && buildResult.success) {
|
|
115
|
+
return this._completeExecution({
|
|
116
|
+
implementationPath: planPath,
|
|
117
|
+
planPath,
|
|
118
|
+
build: buildResult,
|
|
119
|
+
reportPath: buildResult.reportPath,
|
|
120
|
+
phases: buildResult.phases,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
// Build actually ran and failed → honest failure (not a stub, not fake success).
|
|
124
|
+
return this._failExecution(
|
|
125
|
+
(buildResult && buildResult.error) || 'BuildOrchestrator failed',
|
|
126
|
+
);
|
|
127
|
+
}
|
|
110
128
|
|
|
111
|
-
//
|
|
129
|
+
// Fallback (test runner without SINAPSE_REAL_DISPATCH): honest stub — never
|
|
130
|
+
// fabricate success. Preserves the F0a invariant and keeps unit tests fast.
|
|
131
|
+
const subtaskResults = await this._executeSubtasks(storyId, tracker, context);
|
|
112
132
|
const codeChanges = this._collectCodeChanges(subtaskResults);
|
|
113
|
-
|
|
114
|
-
// Run tests if available
|
|
115
133
|
const testResults = await this._runTests(context);
|
|
116
|
-
|
|
117
|
-
// Calculate final progress
|
|
118
134
|
const progress = {
|
|
119
135
|
total: subtaskResults.length,
|
|
120
136
|
completed: subtaskResults.filter((r) => r.success).length,
|
|
121
137
|
failed: subtaskResults.filter((r) => !r.success).length,
|
|
122
138
|
};
|
|
123
|
-
|
|
124
139
|
this._addArtifact('progress', JSON.stringify(progress));
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
140
|
+
return this._stubExecution(
|
|
141
|
+
'Epic 4 stub mode — real build is delegated to BuildOrchestrator outside the test runner',
|
|
142
|
+
{
|
|
143
|
+
implementationPath: planPath,
|
|
144
|
+
planPath,
|
|
145
|
+
progress,
|
|
146
|
+
subtaskResults,
|
|
147
|
+
codeChanges,
|
|
148
|
+
testResults,
|
|
149
|
+
},
|
|
150
|
+
);
|
|
134
151
|
} catch (error) {
|
|
135
152
|
return this._failExecution(error);
|
|
136
153
|
}
|
|
137
154
|
}
|
|
138
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Delegate execution to the BuildOrchestrator — the single real build path.
|
|
158
|
+
*
|
|
159
|
+
* epic: orchestration-consolidation, F1/convergence — instead of re-implementing
|
|
160
|
+
* the build (and duplicating it), Epic 4 hands off to the BuildOrchestrator, which
|
|
161
|
+
* runs plan → execute (claude) → QA → merge. Conservative defaults inside the
|
|
162
|
+
* pipeline (no worktree/merge side-effects, QA is Epic 6's job); tune via
|
|
163
|
+
* context.buildOptions.
|
|
164
|
+
*
|
|
165
|
+
* @returns {Promise<Object>} BuildOrchestrator result ({ success, ... })
|
|
166
|
+
* @private
|
|
167
|
+
*/
|
|
168
|
+
async _executeViaBuildOrchestrator(storyId, context) {
|
|
169
|
+
try {
|
|
170
|
+
const { BuildOrchestrator } = require('../../execution/build-orchestrator');
|
|
171
|
+
const builder = new BuildOrchestrator({
|
|
172
|
+
rootPath: this.projectRoot,
|
|
173
|
+
useWorktree: false,
|
|
174
|
+
autoMerge: false,
|
|
175
|
+
runQA: false,
|
|
176
|
+
...(context.buildOptions || {}),
|
|
177
|
+
});
|
|
178
|
+
return await builder.build(storyId, context.buildOptions || {});
|
|
179
|
+
} catch (err) {
|
|
180
|
+
this._log(`BuildOrchestrator delegation failed: ${err.message}`, 'error');
|
|
181
|
+
return { success: false, error: err.message };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
139
185
|
/**
|
|
140
186
|
* Find existing plan or create new one
|
|
141
187
|
* @private
|