@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.
Files changed (64) hide show
  1. package/dist/cli/index.js +30 -0
  2. package/dist/cli/index.js.map +1 -1
  3. package/dist/pipeline/auto-chain.d.ts +196 -2
  4. package/dist/pipeline/auto-chain.d.ts.map +1 -1
  5. package/dist/pipeline/auto-chain.js +1920 -884
  6. package/dist/pipeline/auto-chain.js.map +1 -1
  7. package/dist/pipeline/enterprise/agent-error-capture.d.ts +76 -0
  8. package/dist/pipeline/enterprise/agent-error-capture.d.ts.map +1 -0
  9. package/dist/pipeline/enterprise/agent-error-capture.js +141 -0
  10. package/dist/pipeline/enterprise/agent-error-capture.js.map +1 -0
  11. package/dist/pipeline/enterprise/artifact-renderers.d.ts +30 -0
  12. package/dist/pipeline/enterprise/artifact-renderers.d.ts.map +1 -1
  13. package/dist/pipeline/enterprise/artifact-renderers.js +129 -1
  14. package/dist/pipeline/enterprise/artifact-renderers.js.map +1 -1
  15. package/dist/pipeline/enterprise/pass-executor.d.ts.map +1 -1
  16. package/dist/pipeline/enterprise/pass-executor.js +52 -0
  17. package/dist/pipeline/enterprise/pass-executor.js.map +1 -1
  18. package/dist/pipeline/enterprise/pipeline-orchestrator.d.ts.map +1 -1
  19. package/dist/pipeline/enterprise/pipeline-orchestrator.js +15 -0
  20. package/dist/pipeline/enterprise/pipeline-orchestrator.js.map +1 -1
  21. package/dist/pipeline/enterprise/types.d.ts +21 -0
  22. package/dist/pipeline/enterprise/types.d.ts.map +1 -1
  23. package/dist/pipeline/gate/feature-flags.d.ts +30 -0
  24. package/dist/pipeline/gate/feature-flags.d.ts.map +1 -0
  25. package/dist/pipeline/gate/feature-flags.js +37 -0
  26. package/dist/pipeline/gate/feature-flags.js.map +1 -0
  27. package/dist/pipeline/gate/phase-dependency-gate.d.ts +179 -0
  28. package/dist/pipeline/gate/phase-dependency-gate.d.ts.map +1 -0
  29. package/dist/pipeline/gate/phase-dependency-gate.js +571 -0
  30. package/dist/pipeline/gate/phase-dependency-gate.js.map +1 -0
  31. package/dist/pipeline/local-fallback/phase1-consensus-reader.d.ts +33 -0
  32. package/dist/pipeline/local-fallback/phase1-consensus-reader.d.ts.map +1 -0
  33. package/dist/pipeline/local-fallback/phase1-consensus-reader.js +99 -0
  34. package/dist/pipeline/local-fallback/phase1-consensus-reader.js.map +1 -0
  35. package/dist/pipeline/local-fallback/phase3-local-fallback.d.ts +26 -0
  36. package/dist/pipeline/local-fallback/phase3-local-fallback.d.ts.map +1 -0
  37. package/dist/pipeline/local-fallback/phase3-local-fallback.js +127 -0
  38. package/dist/pipeline/local-fallback/phase3-local-fallback.js.map +1 -0
  39. package/dist/pipeline/local-fallback/phase4-local-fallback.d.ts +21 -0
  40. package/dist/pipeline/local-fallback/phase4-local-fallback.d.ts.map +1 -0
  41. package/dist/pipeline/local-fallback/phase4-local-fallback.js +240 -0
  42. package/dist/pipeline/local-fallback/phase4-local-fallback.js.map +1 -0
  43. package/dist/pipeline/local-fallback/phase5a-local-fallback.d.ts +28 -0
  44. package/dist/pipeline/local-fallback/phase5a-local-fallback.d.ts.map +1 -0
  45. package/dist/pipeline/local-fallback/phase5a-local-fallback.js +166 -0
  46. package/dist/pipeline/local-fallback/phase5a-local-fallback.js.map +1 -0
  47. package/dist/pipeline/phase3-sparc/phase3-sparc-coordinator.d.ts.map +1 -1
  48. package/dist/pipeline/phase3-sparc/phase3-sparc-coordinator.js +280 -40
  49. package/dist/pipeline/phase3-sparc/phase3-sparc-coordinator.js.map +1 -1
  50. package/dist/pipeline/phase4-adrs/phase4-adrs-coordinator.d.ts.map +1 -1
  51. package/dist/pipeline/phase4-adrs/phase4-adrs-coordinator.js +363 -87
  52. package/dist/pipeline/phase4-adrs/phase4-adrs-coordinator.js.map +1 -1
  53. package/dist/pipeline/phases/prompt-generator.d.ts.map +1 -1
  54. package/dist/pipeline/phases/prompt-generator.js +303 -6
  55. package/dist/pipeline/phases/prompt-generator.js.map +1 -1
  56. package/dist/pipeline/ruflo-phase-executor.d.ts +104 -1
  57. package/dist/pipeline/ruflo-phase-executor.d.ts.map +1 -1
  58. package/dist/pipeline/ruflo-phase-executor.js +406 -4
  59. package/dist/pipeline/ruflo-phase-executor.js.map +1 -1
  60. package/dist/pipeline/swarm-orchestrator.d.ts +47 -0
  61. package/dist/pipeline/swarm-orchestrator.d.ts.map +1 -1
  62. package/dist/pipeline/swarm-orchestrator.js +130 -3
  63. package/dist/pipeline/swarm-orchestrator.js.map +1 -1
  64. 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
- /** Check ruflo is installed and has swarm support. */
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
  }