@llm-dev-ops/agentics-cli 2.7.0 → 2.7.2
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/dist/cli/index.js +30 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/pipeline/auto-chain.d.ts +196 -2
- package/dist/pipeline/auto-chain.d.ts.map +1 -1
- package/dist/pipeline/auto-chain.js +1920 -884
- package/dist/pipeline/auto-chain.js.map +1 -1
- package/dist/pipeline/enterprise/agent-error-capture.d.ts +76 -0
- package/dist/pipeline/enterprise/agent-error-capture.d.ts.map +1 -0
- package/dist/pipeline/enterprise/agent-error-capture.js +141 -0
- package/dist/pipeline/enterprise/agent-error-capture.js.map +1 -0
- package/dist/pipeline/enterprise/artifact-renderers.d.ts +30 -0
- package/dist/pipeline/enterprise/artifact-renderers.d.ts.map +1 -1
- package/dist/pipeline/enterprise/artifact-renderers.js +129 -1
- package/dist/pipeline/enterprise/artifact-renderers.js.map +1 -1
- package/dist/pipeline/enterprise/pass-executor.d.ts.map +1 -1
- package/dist/pipeline/enterprise/pass-executor.js +52 -0
- package/dist/pipeline/enterprise/pass-executor.js.map +1 -1
- package/dist/pipeline/enterprise/pipeline-orchestrator.d.ts.map +1 -1
- package/dist/pipeline/enterprise/pipeline-orchestrator.js +15 -0
- package/dist/pipeline/enterprise/pipeline-orchestrator.js.map +1 -1
- package/dist/pipeline/enterprise/types.d.ts +21 -0
- package/dist/pipeline/enterprise/types.d.ts.map +1 -1
- package/dist/pipeline/gate/feature-flags.d.ts +30 -0
- package/dist/pipeline/gate/feature-flags.d.ts.map +1 -0
- package/dist/pipeline/gate/feature-flags.js +37 -0
- package/dist/pipeline/gate/feature-flags.js.map +1 -0
- package/dist/pipeline/gate/phase-dependency-gate.d.ts +179 -0
- package/dist/pipeline/gate/phase-dependency-gate.d.ts.map +1 -0
- package/dist/pipeline/gate/phase-dependency-gate.js +571 -0
- package/dist/pipeline/gate/phase-dependency-gate.js.map +1 -0
- package/dist/pipeline/local-fallback/phase1-consensus-reader.d.ts +33 -0
- package/dist/pipeline/local-fallback/phase1-consensus-reader.d.ts.map +1 -0
- package/dist/pipeline/local-fallback/phase1-consensus-reader.js +99 -0
- package/dist/pipeline/local-fallback/phase1-consensus-reader.js.map +1 -0
- package/dist/pipeline/local-fallback/phase3-local-fallback.d.ts +26 -0
- package/dist/pipeline/local-fallback/phase3-local-fallback.d.ts.map +1 -0
- package/dist/pipeline/local-fallback/phase3-local-fallback.js +127 -0
- package/dist/pipeline/local-fallback/phase3-local-fallback.js.map +1 -0
- package/dist/pipeline/local-fallback/phase4-local-fallback.d.ts +21 -0
- package/dist/pipeline/local-fallback/phase4-local-fallback.d.ts.map +1 -0
- package/dist/pipeline/local-fallback/phase4-local-fallback.js +240 -0
- package/dist/pipeline/local-fallback/phase4-local-fallback.js.map +1 -0
- package/dist/pipeline/local-fallback/phase5a-local-fallback.d.ts +28 -0
- package/dist/pipeline/local-fallback/phase5a-local-fallback.d.ts.map +1 -0
- package/dist/pipeline/local-fallback/phase5a-local-fallback.js +166 -0
- package/dist/pipeline/local-fallback/phase5a-local-fallback.js.map +1 -0
- package/dist/pipeline/phase3-sparc/phase3-sparc-coordinator.d.ts.map +1 -1
- package/dist/pipeline/phase3-sparc/phase3-sparc-coordinator.js +280 -40
- package/dist/pipeline/phase3-sparc/phase3-sparc-coordinator.js.map +1 -1
- package/dist/pipeline/phase4-adrs/phase4-adrs-coordinator.d.ts.map +1 -1
- package/dist/pipeline/phase4-adrs/phase4-adrs-coordinator.js +363 -87
- package/dist/pipeline/phase4-adrs/phase4-adrs-coordinator.js.map +1 -1
- package/dist/pipeline/phases/prompt-generator.d.ts.map +1 -1
- package/dist/pipeline/phases/prompt-generator.js +303 -6
- package/dist/pipeline/phases/prompt-generator.js.map +1 -1
- package/dist/pipeline/ruflo-phase-executor.d.ts +104 -1
- package/dist/pipeline/ruflo-phase-executor.d.ts.map +1 -1
- package/dist/pipeline/ruflo-phase-executor.js +406 -4
- package/dist/pipeline/ruflo-phase-executor.js.map +1 -1
- package/dist/pipeline/swarm-orchestrator.d.ts +47 -0
- package/dist/pipeline/swarm-orchestrator.d.ts.map +1 -1
- package/dist/pipeline/swarm-orchestrator.js +130 -3
- package/dist/pipeline/swarm-orchestrator.js.map +1 -1
- package/package.json +1 -1
|
@@ -24,6 +24,25 @@ const CHECK_TIMEOUT = 30_000;
|
|
|
24
24
|
const DEFAULT_SWARM_TIMEOUT = 900_000; // 15 min — SPARC generation takes ~10 min via ruflo
|
|
25
25
|
const DEFAULT_MAX_AGENTS = 8;
|
|
26
26
|
const MAX_ARTIFACT_CONTENT_BYTES = 32_000;
|
|
27
|
+
/**
|
|
28
|
+
* Per-phase wall-clock budget for `runPrimaryPhaseExecution` (ADR-PIPELINE-091 §6).
|
|
29
|
+
* Read once at module scope; overridable via `AGENTICS_RUFLO_PHASE_TIMEOUT` env var.
|
|
30
|
+
* Default 600000 ms (10 min) matches the ADR.
|
|
31
|
+
*/
|
|
32
|
+
const DEFAULT_PHASE_TIMEOUT = (() => {
|
|
33
|
+
const raw = process.env['AGENTICS_RUFLO_PHASE_TIMEOUT'];
|
|
34
|
+
if (!raw)
|
|
35
|
+
return 600_000;
|
|
36
|
+
const parsed = Number(raw);
|
|
37
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 600_000;
|
|
38
|
+
})();
|
|
39
|
+
/** True when the operator has explicitly disabled the Ruflo primary executor. */
|
|
40
|
+
function isRufloDisabledByEnv() {
|
|
41
|
+
const raw = process.env['AGENTICS_DISABLE_RUFLO'];
|
|
42
|
+
if (!raw)
|
|
43
|
+
return false;
|
|
44
|
+
return /^(1|true|yes|on)$/i.test(raw.trim());
|
|
45
|
+
}
|
|
27
46
|
/**
|
|
28
47
|
* Naming convention rules embedded in implementation prompts to prevent
|
|
29
48
|
* double-prefix bugs like "IIbudgetManagementPort".
|
|
@@ -121,11 +140,42 @@ export function resolveRufloBinary() {
|
|
|
121
140
|
return null;
|
|
122
141
|
return null;
|
|
123
142
|
}
|
|
124
|
-
/**
|
|
143
|
+
/**
|
|
144
|
+
* Check ruflo is installed and has swarm support.
|
|
145
|
+
*
|
|
146
|
+
* Adds an optional `reason` field when Ruflo is unavailable so callers
|
|
147
|
+
* (notably `runPrimaryPhaseExecution`) can distinguish operator-disabled
|
|
148
|
+
* from binary-missing from swarm-unsupported. Per ADR-PIPELINE-091 §6,
|
|
149
|
+
* `AGENTICS_DISABLE_RUFLO=true` short-circuits this check before any binary
|
|
150
|
+
* resolution so CI environments never attempt Ruflo at all.
|
|
151
|
+
*/
|
|
152
|
+
/**
|
|
153
|
+
* ADR-094 optimization — cache the result so repeated calls within the same
|
|
154
|
+
* process (e.g. multiple gate invocations during a fanout) don't re-probe
|
|
155
|
+
* the binary. The probe involves two `execSync` calls (`--version` and
|
|
156
|
+
* `swarm --help`) which together are ~hundreds of ms; caching collapses N
|
|
157
|
+
* gate calls' Ruflo overhead to a single probe per process.
|
|
158
|
+
*
|
|
159
|
+
* Tests can call `_resetCheckRufloAvailableCache()` to force a re-probe.
|
|
160
|
+
*/
|
|
161
|
+
let _checkRufloAvailableCache = null;
|
|
125
162
|
export function checkRufloAvailable() {
|
|
163
|
+
if (_checkRufloAvailableCache)
|
|
164
|
+
return _checkRufloAvailableCache;
|
|
165
|
+
_checkRufloAvailableCache = checkRufloAvailableUncached();
|
|
166
|
+
return _checkRufloAvailableCache;
|
|
167
|
+
}
|
|
168
|
+
/** Test-only: clear the availability cache so the next call re-probes. */
|
|
169
|
+
export function _resetCheckRufloAvailableCache() {
|
|
170
|
+
_checkRufloAvailableCache = null;
|
|
171
|
+
}
|
|
172
|
+
function checkRufloAvailableUncached() {
|
|
173
|
+
if (isRufloDisabledByEnv()) {
|
|
174
|
+
return { available: false, version: 'N/A', binary: '', reason: 'disabled-by-env' };
|
|
175
|
+
}
|
|
126
176
|
const binary = resolveRufloBinary();
|
|
127
177
|
if (!binary)
|
|
128
|
-
return { available: false, version: 'N/A', binary: '' };
|
|
178
|
+
return { available: false, version: 'N/A', binary: '', reason: 'binary-not-found' };
|
|
129
179
|
try {
|
|
130
180
|
const output = execSync(`${binary} --version`, {
|
|
131
181
|
encoding: 'utf-8', timeout: CHECK_TIMEOUT, stdio: 'pipe',
|
|
@@ -136,12 +186,12 @@ export function checkRufloAvailable() {
|
|
|
136
186
|
execSync(`${binary} swarm --help`, { encoding: 'utf-8', timeout: CHECK_TIMEOUT, stdio: 'pipe' });
|
|
137
187
|
}
|
|
138
188
|
catch {
|
|
139
|
-
return { available: false, version: `${version} (swarm not supported)`, binary };
|
|
189
|
+
return { available: false, version: `${version} (swarm not supported)`, binary, reason: 'swarm-unsupported' };
|
|
140
190
|
}
|
|
141
191
|
return { available: true, version, binary };
|
|
142
192
|
}
|
|
143
193
|
catch {
|
|
144
|
-
return { available: false, version: 'N/A', binary };
|
|
194
|
+
return { available: false, version: 'N/A', binary, reason: 'version-probe-failed' };
|
|
145
195
|
}
|
|
146
196
|
}
|
|
147
197
|
// ============================================================================
|
|
@@ -492,6 +542,7 @@ export function groupIntoWaves(sortedLevels, _language, scenarioQuery, artifacts
|
|
|
492
542
|
`base repository interfaces, and ERP client stubs. Source: ${artifactCtx}`,
|
|
493
543
|
targetDir: 'src/shared',
|
|
494
544
|
wave: 1,
|
|
545
|
+
argv: ['--local-only'],
|
|
495
546
|
}],
|
|
496
547
|
dependsOn: [],
|
|
497
548
|
});
|
|
@@ -519,6 +570,7 @@ export function groupIntoWaves(sortedLevels, _language, scenarioQuery, artifacts
|
|
|
519
570
|
`base repository interfaces, and ERP client stubs. Source: ${artifactCtx}`,
|
|
520
571
|
targetDir: 'src/shared',
|
|
521
572
|
wave: 1,
|
|
573
|
+
argv: ['--local-only'],
|
|
522
574
|
}],
|
|
523
575
|
dependsOn: [],
|
|
524
576
|
});
|
|
@@ -564,6 +616,7 @@ export function groupIntoWaves(sortedLevels, _language, scenarioQuery, artifacts
|
|
|
564
616
|
targetDir: 'ui',
|
|
565
617
|
wave: 4,
|
|
566
618
|
dependsOn: ['Database & Shared Infrastructure'],
|
|
619
|
+
argv: ['--local-only'],
|
|
567
620
|
},
|
|
568
621
|
{
|
|
569
622
|
label: 'Infrastructure & Integration',
|
|
@@ -575,6 +628,7 @@ export function groupIntoWaves(sortedLevels, _language, scenarioQuery, artifacts
|
|
|
575
628
|
targetDir: 'src/infra',
|
|
576
629
|
wave: 4,
|
|
577
630
|
dependsOn: ['Database & Shared Infrastructure'],
|
|
631
|
+
argv: ['--local-only'],
|
|
578
632
|
},
|
|
579
633
|
{
|
|
580
634
|
label: 'Test Suite',
|
|
@@ -592,6 +646,7 @@ export function groupIntoWaves(sortedLevels, _language, scenarioQuery, artifacts
|
|
|
592
646
|
targetDir: 'tests',
|
|
593
647
|
wave: 4,
|
|
594
648
|
dependsOn: testDependsOn,
|
|
649
|
+
argv: ['--local-only'],
|
|
595
650
|
},
|
|
596
651
|
{
|
|
597
652
|
label: 'Deployment Configuration',
|
|
@@ -603,6 +658,7 @@ export function groupIntoWaves(sortedLevels, _language, scenarioQuery, artifacts
|
|
|
603
658
|
targetDir: 'deploy',
|
|
604
659
|
wave: 4,
|
|
605
660
|
dependsOn: ['Database & Shared Infrastructure'],
|
|
661
|
+
argv: ['--local-only'],
|
|
606
662
|
},
|
|
607
663
|
],
|
|
608
664
|
dependsOn: [1, 2, 3],
|
|
@@ -804,6 +860,7 @@ export function buildIterativeImplementationPrompts(artifacts, scenarioQuery, la
|
|
|
804
860
|
targetDir: `src/${ctxSlug}`,
|
|
805
861
|
wave: wave.wave,
|
|
806
862
|
dependsOn: taskDependsOn.length > 0 ? taskDependsOn : undefined,
|
|
863
|
+
argv: ['--local-only'],
|
|
807
864
|
});
|
|
808
865
|
}
|
|
809
866
|
// Merge context tasks with any pre-existing wave tasks (e.g., infrastructure, cross-cutting)
|
|
@@ -1140,6 +1197,7 @@ Write all output to the ./plans folder as markdown files:
|
|
|
1140
1197
|
|
|
1141
1198
|
The implementation must be enterprise grade, commercially viable, production ready, bug and error free with no compilation issues.`,
|
|
1142
1199
|
targetDir: 'plans',
|
|
1200
|
+
argv: ['--local-only'],
|
|
1143
1201
|
},
|
|
1144
1202
|
];
|
|
1145
1203
|
}
|
|
@@ -1163,6 +1221,7 @@ Write all output to the ./plans folder:
|
|
|
1163
1221
|
- plans/adrs/ (one .md file per ADR)
|
|
1164
1222
|
- plans/ddd/ (domain-model.md + context-map.md)`,
|
|
1165
1223
|
targetDir: 'plans',
|
|
1224
|
+
argv: ['--local-only'],
|
|
1166
1225
|
},
|
|
1167
1226
|
];
|
|
1168
1227
|
}
|
|
@@ -1193,6 +1252,7 @@ Write all output to the ./plans/prompts/ folder:
|
|
|
1193
1252
|
- ... (continue for all phases, typically 10-30 depending on complexity)
|
|
1194
1253
|
- plans/prompts/execution-plan.json (ordered list of all prompts with dependencies)`,
|
|
1195
1254
|
targetDir: 'plans/prompts',
|
|
1255
|
+
argv: ['--local-only'],
|
|
1196
1256
|
},
|
|
1197
1257
|
];
|
|
1198
1258
|
}
|
|
@@ -1232,6 +1292,7 @@ CRITICAL — DO NOT generate scaffolding or stubs. This is a REAL implementation
|
|
|
1232
1292
|
Engineers on the client's team should be able to plug this into their ERP using the ERP surface endpoints that are already established, with light massaging only.`,
|
|
1233
1293
|
targetDir: 'src',
|
|
1234
1294
|
wave: 1,
|
|
1295
|
+
argv: ['--local-only'],
|
|
1235
1296
|
},
|
|
1236
1297
|
];
|
|
1237
1298
|
}
|
|
@@ -1257,6 +1318,7 @@ function buildPhase5TasksLegacy(scenarioQuery, language, artifacts) {
|
|
|
1257
1318
|
`and comprehensive error handling. Follow the technology decisions in the ADRs. Source files: ${artifactCtx}`,
|
|
1258
1319
|
targetDir: 'src',
|
|
1259
1320
|
wave: 1,
|
|
1321
|
+
argv: ['--local-only'],
|
|
1260
1322
|
},
|
|
1261
1323
|
{
|
|
1262
1324
|
label: 'Frontend Application',
|
|
@@ -1269,6 +1331,7 @@ function buildPhase5TasksLegacy(scenarioQuery, language, artifacts) {
|
|
|
1269
1331
|
targetDir: 'ui',
|
|
1270
1332
|
wave: 2,
|
|
1271
1333
|
dependsOn: ['Backend Implementation'],
|
|
1334
|
+
argv: ['--local-only'],
|
|
1272
1335
|
},
|
|
1273
1336
|
{
|
|
1274
1337
|
label: 'Infrastructure & Integration',
|
|
@@ -1281,6 +1344,7 @@ function buildPhase5TasksLegacy(scenarioQuery, language, artifacts) {
|
|
|
1281
1344
|
targetDir: 'src/infra',
|
|
1282
1345
|
wave: 2,
|
|
1283
1346
|
dependsOn: ['Backend Implementation'],
|
|
1347
|
+
argv: ['--local-only'],
|
|
1284
1348
|
},
|
|
1285
1349
|
{
|
|
1286
1350
|
label: 'Database & SQL',
|
|
@@ -1292,6 +1356,7 @@ function buildPhase5TasksLegacy(scenarioQuery, language, artifacts) {
|
|
|
1292
1356
|
`database-specific optimizations for the database engine specified in the ADRs. Source files: ${artifactCtx}`,
|
|
1293
1357
|
targetDir: 'sql',
|
|
1294
1358
|
wave: 1,
|
|
1359
|
+
argv: ['--local-only'],
|
|
1295
1360
|
},
|
|
1296
1361
|
{
|
|
1297
1362
|
label: 'Test Suite',
|
|
@@ -1310,6 +1375,7 @@ function buildPhase5TasksLegacy(scenarioQuery, language, artifacts) {
|
|
|
1310
1375
|
targetDir: 'tests',
|
|
1311
1376
|
wave: 4,
|
|
1312
1377
|
dependsOn: ['Backend Implementation', 'Database & SQL'],
|
|
1378
|
+
argv: ['--local-only'],
|
|
1313
1379
|
},
|
|
1314
1380
|
{
|
|
1315
1381
|
label: 'Deployment Configuration',
|
|
@@ -1324,6 +1390,7 @@ function buildPhase5TasksLegacy(scenarioQuery, language, artifacts) {
|
|
|
1324
1390
|
targetDir: 'deploy',
|
|
1325
1391
|
wave: 2,
|
|
1326
1392
|
dependsOn: ['Backend Implementation'],
|
|
1393
|
+
argv: ['--local-only'],
|
|
1327
1394
|
},
|
|
1328
1395
|
];
|
|
1329
1396
|
}
|
|
@@ -1364,9 +1431,336 @@ export function buildPhase6Tasks(scenarioQuery, artifacts) {
|
|
|
1364
1431
|
`data transformation adapters specific to this domain, and health monitoring for ERP connectivity. ` +
|
|
1365
1432
|
`Source files: ${artifactCtx}`,
|
|
1366
1433
|
targetDir: 'erp',
|
|
1434
|
+
argv: ['--local-only'],
|
|
1435
|
+
},
|
|
1436
|
+
];
|
|
1437
|
+
}
|
|
1438
|
+
// ============================================================================
|
|
1439
|
+
// Phase 5/5a task builders — ADR-PIPELINE-091
|
|
1440
|
+
// ============================================================================
|
|
1441
|
+
/**
|
|
1442
|
+
* Canonical SPARC section ordering. `buildImplPromptsTasks` emits one Ruflo
|
|
1443
|
+
* task per section so downstream tests can assert `task count ===
|
|
1444
|
+
* SPARC_SECTIONS.length` on the default path.
|
|
1445
|
+
*/
|
|
1446
|
+
const SPARC_SECTIONS = ['specification', 'pseudocode', 'architecture', 'refinement', 'completion'];
|
|
1447
|
+
/**
|
|
1448
|
+
* Build Ruflo tasks for the coverage-gap slot (normally handled by the remote
|
|
1449
|
+
* `quality-engineering/coverage-gap-detect` agent). Under ADR-PIPELINE-091
|
|
1450
|
+
* the local swarm produces the same output so remote 503s do not gate the
|
|
1451
|
+
* pipeline. The task writes a `coverage-gaps.json` under the phase-5
|
|
1452
|
+
* coverage directory.
|
|
1453
|
+
*/
|
|
1454
|
+
export function buildCoverageGapTasks(dossier, context) {
|
|
1455
|
+
const language = context.language ?? 'typescript';
|
|
1456
|
+
const sparcRef = dossier.artifacts?.['SPARC'] ?? dossier.artifacts?.['sparc-combined'] ?? '';
|
|
1457
|
+
const tddRef = dossier.artifacts?.['TDD'] ?? dossier.artifacts?.['tdd'] ?? '';
|
|
1458
|
+
const adrRef = dossier.artifacts?.['ADRs'] ?? dossier.artifacts?.['adrs'] ?? '';
|
|
1459
|
+
const dddRef = dossier.artifacts?.['DDD'] ?? dossier.artifacts?.['ddd'] ?? '';
|
|
1460
|
+
const sources = [
|
|
1461
|
+
sparcRef ? `SPARC=${sparcRef}` : '',
|
|
1462
|
+
tddRef ? `TDD=${tddRef}` : '',
|
|
1463
|
+
adrRef ? `ADRs=${adrRef}` : '',
|
|
1464
|
+
dddRef ? `DDD=${dddRef}` : '',
|
|
1465
|
+
].filter(Boolean).join(', ') || '(none yet — infer gaps from scenario query alone)';
|
|
1466
|
+
return [
|
|
1467
|
+
{
|
|
1468
|
+
label: 'Coverage Gap Detection',
|
|
1469
|
+
description: [
|
|
1470
|
+
`Task: emit the coverage-gap list the remote quality-engineering/coverage-gap-detect agent would normally produce.`,
|
|
1471
|
+
``,
|
|
1472
|
+
`## Project scenario`,
|
|
1473
|
+
dossier.scenarioQuery,
|
|
1474
|
+
``,
|
|
1475
|
+
`## Target language`,
|
|
1476
|
+
language,
|
|
1477
|
+
``,
|
|
1478
|
+
`## Source artifacts`,
|
|
1479
|
+
sources,
|
|
1480
|
+
``,
|
|
1481
|
+
`## Required output (write exactly these files under ./coverage/)`,
|
|
1482
|
+
`- coverage/coverage-gaps.json`,
|
|
1483
|
+
` Shape: { "traceId": "${dossier.traceId}", "gaps": [{ "area": string, "file": string, "severity": "high"|"medium"|"low", "reason": string, "suggestedTests": string[] }], "summary": { "high": number, "medium": number, "low": number } }`,
|
|
1484
|
+
`- coverage/coverage-gaps.md (human-readable companion report).`,
|
|
1485
|
+
``,
|
|
1486
|
+
`Rules:`,
|
|
1487
|
+
`1. Every gap must cite the SPARC or DDD element it comes from (command, query, aggregate, or domain event).`,
|
|
1488
|
+
`2. Severity uses "high" for unvalidated domain invariants, "medium" for missing command-handler integration tests, "low" for style/docstring gaps.`,
|
|
1489
|
+
`3. Do NOT invent files that are not in the SPARC/DDD/ADR set above — if no source is available, emit an empty gaps array and a summary with zeroes.`,
|
|
1490
|
+
`4. This is a LOCAL-ONLY run. Do not call any remote diligence agent; the primary executor has already marked this pass as local-only via --local-only.`,
|
|
1491
|
+
].join('\n'),
|
|
1492
|
+
targetDir: 'coverage',
|
|
1493
|
+
argv: ['--local-only'],
|
|
1367
1494
|
},
|
|
1368
1495
|
];
|
|
1369
1496
|
}
|
|
1497
|
+
/**
|
|
1498
|
+
* Build Ruflo tasks for the implementation-prompts slot (normally produced
|
|
1499
|
+
* from SPARC + ADRs + DDD). Emits one Ruflo task per canonical SPARC section
|
|
1500
|
+
* so the combined output populates `plans/prompts/impl-NNN-<slug>.md` plus
|
|
1501
|
+
* `plans/prompts/execution-plan.json`. Matches the filename format auto-chain
|
|
1502
|
+
* consumes: `impl-${String(order).padStart(3, '0')}-${phase.slug}.md`.
|
|
1503
|
+
*/
|
|
1504
|
+
export function buildImplPromptsTasks(dossier, context, adrs = [], ddd = []) {
|
|
1505
|
+
const language = context.language ?? 'typescript';
|
|
1506
|
+
const adrSummary = adrs.length
|
|
1507
|
+
? adrs.slice(0, 24).map(a => ` - ${a.id}: ${a.title}${a.decision ? ' — ' + a.decision.slice(0, 160) : ''}`).join('\n')
|
|
1508
|
+
: ' (no ADRs supplied — infer from SPARC alone)';
|
|
1509
|
+
const dddSummary = ddd.length
|
|
1510
|
+
? ddd.slice(0, 24).map(c => ` - ${c.name}${c.aggregates?.length ? ' (' + c.aggregates.slice(0, 6).join(', ') + ')' : ''}`).join('\n')
|
|
1511
|
+
: ' (no DDD contexts supplied — infer from SPARC alone)';
|
|
1512
|
+
const sparcRef = dossier.artifacts?.['SPARC'] ?? dossier.artifacts?.['sparc-combined'] ?? '';
|
|
1513
|
+
return SPARC_SECTIONS.map((section, idx) => {
|
|
1514
|
+
const order = idx + 1;
|
|
1515
|
+
const slug = section;
|
|
1516
|
+
const filename = `impl-${String(order).padStart(3, '0')}-${slug}.md`;
|
|
1517
|
+
return {
|
|
1518
|
+
label: `Impl Prompt — ${section}`,
|
|
1519
|
+
description: [
|
|
1520
|
+
`Task: produce the implementation prompt for the SPARC "${section}" section.`,
|
|
1521
|
+
``,
|
|
1522
|
+
`## Scenario`,
|
|
1523
|
+
dossier.scenarioQuery,
|
|
1524
|
+
``,
|
|
1525
|
+
`## Target language`,
|
|
1526
|
+
language,
|
|
1527
|
+
``,
|
|
1528
|
+
`## ADRs in scope`,
|
|
1529
|
+
adrSummary,
|
|
1530
|
+
``,
|
|
1531
|
+
`## Bounded contexts in scope`,
|
|
1532
|
+
dddSummary,
|
|
1533
|
+
``,
|
|
1534
|
+
`## Source SPARC artifact`,
|
|
1535
|
+
sparcRef || '(none — use scenario query alone)',
|
|
1536
|
+
``,
|
|
1537
|
+
`## Required output`,
|
|
1538
|
+
`Write exactly one file: plans/prompts/${filename}`,
|
|
1539
|
+
`The prompt must be dependency-coherent with its predecessors (prompts 001..${String(order - 1).padStart(3, '0')}).`,
|
|
1540
|
+
`Include:`,
|
|
1541
|
+
`- Exact typed interfaces with domain-specific fields (no Record<string, unknown>).`,
|
|
1542
|
+
`- The algorithms or database DDL this step implements.`,
|
|
1543
|
+
`- Test cases validating domain behavior (not "id is defined").`,
|
|
1544
|
+
`- A clear "Prerequisites" block that references prior prompts by filename.`,
|
|
1545
|
+
``,
|
|
1546
|
+
`After writing the ${filename} file, append an entry to plans/prompts/execution-plan.json (create if missing) with shape: `,
|
|
1547
|
+
` { "order": ${order}, "file": "${filename}", "section": "${section}", "dependsOn": [${order > 1 ? `"impl-${String(order - 1).padStart(3, '0')}-${SPARC_SECTIONS[idx - 1]}.md"` : ''}] }.`,
|
|
1548
|
+
``,
|
|
1549
|
+
`This is a LOCAL-ONLY run. --local-only is already set on the task; do not invoke remote diligence agents.`,
|
|
1550
|
+
].join('\n'),
|
|
1551
|
+
targetDir: 'plans/prompts',
|
|
1552
|
+
wave: order === 1 ? 1 : 2,
|
|
1553
|
+
dependsOn: order === 1 ? undefined : [`Impl Prompt — ${SPARC_SECTIONS[idx - 1]}`],
|
|
1554
|
+
argv: ['--local-only'],
|
|
1555
|
+
};
|
|
1556
|
+
});
|
|
1557
|
+
}
|
|
1558
|
+
/**
|
|
1559
|
+
* Map a canonical phase id onto the matching task builder + label + output
|
|
1560
|
+
* slot. Centralised here so both auto-chain and the ADR-093 dependency gate
|
|
1561
|
+
* see the same mapping.
|
|
1562
|
+
*/
|
|
1563
|
+
function buildTasksForPrimaryPhase(phaseId, dossier, context) {
|
|
1564
|
+
const artifacts = dossier.artifacts ?? {};
|
|
1565
|
+
switch (phaseId) {
|
|
1566
|
+
case 'phase3-sparc':
|
|
1567
|
+
return {
|
|
1568
|
+
tasks: buildPhase3Tasks(dossier.scenarioQuery, artifacts),
|
|
1569
|
+
label: 'SPARC + London TDD (primary)',
|
|
1570
|
+
phaseNumber: 3,
|
|
1571
|
+
};
|
|
1572
|
+
case 'phase4-adrs-ddd':
|
|
1573
|
+
return {
|
|
1574
|
+
tasks: buildPhase4Tasks(dossier.scenarioQuery, artifacts),
|
|
1575
|
+
label: 'ADRs + DDD (primary)',
|
|
1576
|
+
phaseNumber: 4,
|
|
1577
|
+
};
|
|
1578
|
+
case 'phase5a-prompts': {
|
|
1579
|
+
// Try to extract ADR + DDD summaries from dossier artifacts if present.
|
|
1580
|
+
const adrSummaries = artifacts['ADRs']
|
|
1581
|
+
? extractADRSummary(artifacts['ADRs']).map(a => ({ id: a.id, title: a.title, decision: a.decision }))
|
|
1582
|
+
: [];
|
|
1583
|
+
const dddSummaries = artifacts['DDD']
|
|
1584
|
+
? extractDDDSummary(artifacts['DDD']).contexts.map(c => ({ name: c.name, aggregates: c.aggregates }))
|
|
1585
|
+
: [];
|
|
1586
|
+
return {
|
|
1587
|
+
tasks: buildImplPromptsTasks(dossier, context, adrSummaries, dddSummaries),
|
|
1588
|
+
label: 'Implementation Prompts (primary)',
|
|
1589
|
+
phaseNumber: 5,
|
|
1590
|
+
};
|
|
1591
|
+
}
|
|
1592
|
+
case 'phase5-coverage':
|
|
1593
|
+
return {
|
|
1594
|
+
tasks: buildCoverageGapTasks(dossier, context),
|
|
1595
|
+
label: 'Coverage Gap Detection (primary)',
|
|
1596
|
+
phaseNumber: 5,
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
/**
|
|
1601
|
+
* Canonical primary-executor entry point per ADR-PIPELINE-091.
|
|
1602
|
+
*
|
|
1603
|
+
* Invariants:
|
|
1604
|
+
* 1. Never throws. Returns `{ success: false, executionTier: 'unavailable' }`
|
|
1605
|
+
* when Ruflo is not runnable — the caller decides fallback.
|
|
1606
|
+
* 2. Honors `AGENTICS_DISABLE_RUFLO=true` (short-circuits without touching
|
|
1607
|
+
* the binary; reason is `ruflo-disabled-by-env`).
|
|
1608
|
+
* 3. Timeout defaults to `DEFAULT_PHASE_TIMEOUT`, overridable per-call. When
|
|
1609
|
+
* the Ruflo swarm exceeds the budget, tier stays `ruflo-local` but
|
|
1610
|
+
* `success` flips to false with reason `ruflo-timeout` so the gate can
|
|
1611
|
+
* trigger template fallback.
|
|
1612
|
+
* 4. Every dispatched task carries `--local-only` (enforced both in builders
|
|
1613
|
+
* and defensively by the executor loop).
|
|
1614
|
+
*/
|
|
1615
|
+
/**
|
|
1616
|
+
* ADR-094 Decision 8 — local fallback delegate. When Ruflo is unavailable,
|
|
1617
|
+
* try to produce the requested phase's artifacts deterministically from
|
|
1618
|
+
* Phase-1 consensus. Returns a `local-fallback`-tier result on success, or
|
|
1619
|
+
* `undefined` if no fallback exists for this phase or Phase-1 consensus is
|
|
1620
|
+
* unrecoverable (in which case the caller returns the original `unavailable`
|
|
1621
|
+
* result).
|
|
1622
|
+
*
|
|
1623
|
+
* No `phase5-coverage` fallback: coverage analysis is a Ruflo-only
|
|
1624
|
+
* capability for now, so that path keeps its old behaviour.
|
|
1625
|
+
*/
|
|
1626
|
+
async function tryLocalFallback(phaseId, dossier, startTime) {
|
|
1627
|
+
// Lazy-load to avoid circular imports — local-fallback files import only
|
|
1628
|
+
// from `local-fallback/` and `node:fs`/`node:path`, so this is safe.
|
|
1629
|
+
let result;
|
|
1630
|
+
switch (phaseId) {
|
|
1631
|
+
case 'phase3-sparc': {
|
|
1632
|
+
const { runPhase3LocalFallback } = await import('./local-fallback/phase3-local-fallback.js');
|
|
1633
|
+
result = runPhase3LocalFallback(dossier.runDir);
|
|
1634
|
+
break;
|
|
1635
|
+
}
|
|
1636
|
+
case 'phase4-adrs-ddd': {
|
|
1637
|
+
const { runPhase4LocalFallback } = await import('./local-fallback/phase4-local-fallback.js');
|
|
1638
|
+
result = runPhase4LocalFallback(dossier.runDir);
|
|
1639
|
+
break;
|
|
1640
|
+
}
|
|
1641
|
+
case 'phase5a-prompts': {
|
|
1642
|
+
const { runPhase5aLocalFallback } = await import('./local-fallback/phase5a-local-fallback.js');
|
|
1643
|
+
result = runPhase5aLocalFallback(dossier.runDir);
|
|
1644
|
+
break;
|
|
1645
|
+
}
|
|
1646
|
+
default:
|
|
1647
|
+
// No fallback for this phase id — caller continues with `unavailable`.
|
|
1648
|
+
return undefined;
|
|
1649
|
+
}
|
|
1650
|
+
if (!result.success) {
|
|
1651
|
+
// Map fallback reasons onto the typed reason union; unrecognized strings
|
|
1652
|
+
// collapse to `local-fallback-write-error`.
|
|
1653
|
+
const reason = result.reason === 'no-phase1-consensus' ? 'local-fallback-no-consensus' : 'local-fallback-write-error';
|
|
1654
|
+
return {
|
|
1655
|
+
phaseId,
|
|
1656
|
+
success: false,
|
|
1657
|
+
executionTier: 'unavailable',
|
|
1658
|
+
reason,
|
|
1659
|
+
filesModified: result.filesWritten.length,
|
|
1660
|
+
timing: Date.now() - startTime,
|
|
1661
|
+
message: result.message,
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
return {
|
|
1665
|
+
phaseId,
|
|
1666
|
+
success: true,
|
|
1667
|
+
executionTier: 'local-fallback',
|
|
1668
|
+
filesModified: result.filesWritten.length,
|
|
1669
|
+
timing: Date.now() - startTime,
|
|
1670
|
+
message: result.message,
|
|
1671
|
+
};
|
|
1672
|
+
}
|
|
1673
|
+
export async function runPrimaryPhaseExecution(phaseId, dossier, context, timeout) {
|
|
1674
|
+
const startTime = Date.now();
|
|
1675
|
+
const effectiveTimeout = typeof timeout === 'number' && timeout > 0 ? timeout : DEFAULT_PHASE_TIMEOUT;
|
|
1676
|
+
const check = checkRufloAvailable();
|
|
1677
|
+
if (!check.available) {
|
|
1678
|
+
// ADR-094 Decision 8 — when Ruflo is unavailable, delegate to the
|
|
1679
|
+
// deterministic local fallback for phases 3, 4, 5a. This removes Ruflo
|
|
1680
|
+
// as a single point of failure for the engineering chain. The fallback
|
|
1681
|
+
// consumes Phase-1 consensus and produces minimal-but-non-empty artifacts
|
|
1682
|
+
// tagged `quality: degraded`. Phases that have no fallback (phase5-coverage)
|
|
1683
|
+
// still return `unavailable` for upstream handling.
|
|
1684
|
+
const fallbackResult = await tryLocalFallback(phaseId, dossier, startTime);
|
|
1685
|
+
if (fallbackResult)
|
|
1686
|
+
return fallbackResult;
|
|
1687
|
+
const reason = check.reason === 'disabled-by-env' ? 'ruflo-disabled-by-env' : 'ruflo-not-available';
|
|
1688
|
+
return {
|
|
1689
|
+
phaseId,
|
|
1690
|
+
success: false,
|
|
1691
|
+
executionTier: 'unavailable',
|
|
1692
|
+
reason,
|
|
1693
|
+
filesModified: 0,
|
|
1694
|
+
timing: Date.now() - startTime,
|
|
1695
|
+
message: check.reason === 'disabled-by-env'
|
|
1696
|
+
? `Ruflo disabled via AGENTICS_DISABLE_RUFLO; caller should fall through to templates for ${phaseId}`
|
|
1697
|
+
: `Ruflo not available (${check.version || 'no binary'}); caller should fall through to templates for ${phaseId}`,
|
|
1698
|
+
};
|
|
1699
|
+
}
|
|
1700
|
+
const { tasks, label, phaseNumber } = buildTasksForPrimaryPhase(phaseId, dossier, context);
|
|
1701
|
+
if (tasks.length === 0) {
|
|
1702
|
+
return {
|
|
1703
|
+
phaseId,
|
|
1704
|
+
success: false,
|
|
1705
|
+
executionTier: 'ruflo-local',
|
|
1706
|
+
reason: 'no-tasks-built',
|
|
1707
|
+
filesModified: 0,
|
|
1708
|
+
timing: Date.now() - startTime,
|
|
1709
|
+
message: `No Ruflo tasks built for ${phaseId}`,
|
|
1710
|
+
};
|
|
1711
|
+
}
|
|
1712
|
+
// Ensure output dir exists before dispatch — executeRufloPhaseSwarm does
|
|
1713
|
+
// this too but the caller expects the directory to exist on success.
|
|
1714
|
+
try {
|
|
1715
|
+
fs.mkdirSync(context.outputDir, { recursive: true, mode: 0o700 });
|
|
1716
|
+
}
|
|
1717
|
+
catch {
|
|
1718
|
+
// non-fatal; downstream will surface any permission problems
|
|
1719
|
+
}
|
|
1720
|
+
let result;
|
|
1721
|
+
try {
|
|
1722
|
+
result = executeRufloPhaseSwarm({
|
|
1723
|
+
phase: phaseNumber,
|
|
1724
|
+
label,
|
|
1725
|
+
scenarioQuery: dossier.scenarioQuery,
|
|
1726
|
+
runDir: dossier.runDir,
|
|
1727
|
+
traceId: dossier.traceId,
|
|
1728
|
+
outputDir: context.outputDir,
|
|
1729
|
+
tasks,
|
|
1730
|
+
agenticsResults: context.agenticsResults ?? [],
|
|
1731
|
+
priorArtifacts: dossier.artifacts ?? {},
|
|
1732
|
+
maxAgents: context.maxAgents ?? DEFAULT_MAX_AGENTS,
|
|
1733
|
+
timeoutMs: effectiveTimeout,
|
|
1734
|
+
});
|
|
1735
|
+
}
|
|
1736
|
+
catch (err) {
|
|
1737
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1738
|
+
return {
|
|
1739
|
+
phaseId,
|
|
1740
|
+
success: false,
|
|
1741
|
+
executionTier: 'ruflo-local',
|
|
1742
|
+
reason: 'swarm-error',
|
|
1743
|
+
filesModified: 0,
|
|
1744
|
+
timing: Date.now() - startTime,
|
|
1745
|
+
message: `Ruflo swarm errored for ${phaseId}: ${msg.slice(0, 200)}`,
|
|
1746
|
+
};
|
|
1747
|
+
}
|
|
1748
|
+
const timing = Date.now() - startTime;
|
|
1749
|
+
const success = result.filesModified > 0;
|
|
1750
|
+
const timedOut = timing >= effectiveTimeout && !result.swarmCompleted;
|
|
1751
|
+
return {
|
|
1752
|
+
phaseId,
|
|
1753
|
+
success,
|
|
1754
|
+
executionTier: 'ruflo-local',
|
|
1755
|
+
reason: success ? undefined : (timedOut ? 'ruflo-timeout' : 'swarm-error'),
|
|
1756
|
+
rufloResult: result,
|
|
1757
|
+
filesModified: result.filesModified,
|
|
1758
|
+
timing,
|
|
1759
|
+
message: success
|
|
1760
|
+
? `Ruflo primary executor produced ${result.filesModified} files for ${phaseId} in ${timing}ms`
|
|
1761
|
+
: `Ruflo primary executor produced no files for ${phaseId} in ${timing}ms`,
|
|
1762
|
+
};
|
|
1763
|
+
}
|
|
1370
1764
|
// ============================================================================
|
|
1371
1765
|
// Main Executor
|
|
1372
1766
|
// ============================================================================
|
|
@@ -1442,11 +1836,19 @@ export function executeRufloPhaseSwarm(config) {
|
|
|
1442
1836
|
agenticsContext,
|
|
1443
1837
|
artifactContent.length > 0 ? `\n--- PRIOR PHASE ARTIFACTS (actual content) ---${artifactContent}\n--- END PRIOR PHASE ARTIFACTS ---` : '',
|
|
1444
1838
|
].join('\n');
|
|
1839
|
+
// Per ADR-PIPELINE-091: every engineering task is dispatched `--local-only`
|
|
1840
|
+
// so the local swarm does not round-trip to the remote fleet it is meant
|
|
1841
|
+
// to be independent of. Task builders normalize this; if a legacy task
|
|
1842
|
+
// slipped through without argv, we defensively inject it here too.
|
|
1843
|
+
const extraArgs = Array.from(task.argv ?? []);
|
|
1844
|
+
if (!extraArgs.includes('--local-only'))
|
|
1845
|
+
extraArgs.push('--local-only');
|
|
1445
1846
|
try {
|
|
1446
1847
|
rufloExec(check.binary, [
|
|
1447
1848
|
'task', 'create',
|
|
1448
1849
|
'--type', 'implementation',
|
|
1449
1850
|
'--description', fullDescription,
|
|
1851
|
+
...extraArgs,
|
|
1450
1852
|
], config.outputDir, CHECK_TIMEOUT);
|
|
1451
1853
|
tasksCreated++;
|
|
1452
1854
|
}
|