opencode-swarm 7.100.1 → 7.101.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.
@@ -6,7 +6,7 @@ import {
6
6
  loadPlanJsonOnly,
7
7
  mergeDurableGateEntriesFromEvidence,
8
8
  readDurableGateEvidence
9
- } from "./index-3emh7rny.js";
9
+ } from "./index-bnkd9ejn.js";
10
10
  import"./index-r8f89sm9.js";
11
11
  import"./index-9b7qp18e.js";
12
12
  import"./index-7ztmmqpt.js";
@@ -1,15 +1,15 @@
1
1
  // @bun
2
2
  import {
3
3
  handleGuardrailExplain
4
- } from "./index-4xqnktvr.js";
5
- import"./index-qvdxzjqt.js";
4
+ } from "./index-wc8g49dp.js";
5
+ import"./index-rmh6nqbp.js";
6
6
  import"./index-pj3a5fbt.js";
7
7
  import"./index-ydgy7vg5.js";
8
8
  import"./index-vwyjkzgs.js";
9
9
  import"./index-f7nma02k.js";
10
10
  import"./index-rpb763g8.js";
11
11
  import"./index-m0qshbr6.js";
12
- import"./index-3emh7rny.js";
12
+ import"./index-bnkd9ejn.js";
13
13
  import"./index-adz3nk9b.js";
14
14
  import"./index-v4fcn4tr.js";
15
15
  import"./index-r8f89sm9.js";
@@ -1181,10 +1181,16 @@ function validateSpecContent(content) {
1181
1181
  // src/sdd/effective-spec.ts
1182
1182
  var SWARM_SPEC_REL = path4.join(".swarm", "spec.md");
1183
1183
  var OPENSPEC_ROOT = "openspec";
1184
+ var SPECKIT_MARKER = ".specify";
1185
+ var SPECKIT_SPECS_DIR = "specs";
1184
1186
  var MAX_SPEC_BYTES = 256 * 1024;
1185
1187
  var MAX_SOURCE_BYTES = 512 * 1024;
1186
1188
  var MAX_SPEC_FILES = 100;
1187
1189
  var MAX_WALK_DEPTH = 10;
1190
+ var SPECKIT_REQUIRED_SECTIONS = [
1191
+ "## Functional Requirements",
1192
+ "## Success Criteria"
1193
+ ];
1188
1194
  function toPosix(relPath) {
1189
1195
  return relPath.split(path4.sep).join("/");
1190
1196
  }
@@ -1192,11 +1198,20 @@ function hash(content) {
1192
1198
  return createHash("sha256").update(content, "utf-8").digest("hex");
1193
1199
  }
1194
1200
  function readTextBounded(absPath) {
1195
- const stat = fs4.lstatSync(absPath);
1201
+ let stat;
1202
+ try {
1203
+ stat = fs4.lstatSync(absPath);
1204
+ } catch {
1205
+ return null;
1206
+ }
1196
1207
  if (!stat.isFile() || stat.size > MAX_SOURCE_BYTES) {
1197
1208
  return null;
1198
1209
  }
1199
- return fs4.readFileSync(absPath, "utf-8");
1210
+ try {
1211
+ return fs4.readFileSync(absPath, "utf-8");
1212
+ } catch {
1213
+ return null;
1214
+ }
1200
1215
  }
1201
1216
  function fileArtifact(root, absPath) {
1202
1217
  try {
@@ -1335,10 +1350,10 @@ function parseRequirements(content, sourceRel, defaultKind) {
1335
1350
  }
1336
1351
  return requirements;
1337
1352
  }
1338
- function nextFrId(used, warnings) {
1353
+ function nextFrId(used, warnings, reserved) {
1339
1354
  for (let n = 1;n <= 999; n++) {
1340
1355
  const id = `FR-${String(n).padStart(3, "0")}`;
1341
- if (!used.has(id)) {
1356
+ if (!used.has(id) && !reserved?.has(id)) {
1342
1357
  used.add(id);
1343
1358
  return id;
1344
1359
  }
@@ -1346,8 +1361,8 @@ function nextFrId(used, warnings) {
1346
1361
  warnings.push("More than 999 FR identifiers are required; reusing FR-999.");
1347
1362
  return "FR-999";
1348
1363
  }
1349
- function renderRequirement(req, used, warnings) {
1350
- const id = req.id && !used.has(req.id) ? req.id : nextFrId(used, warnings);
1364
+ function renderRequirement(req, used, warnings, reserved) {
1365
+ const id = req.id && !used.has(req.id) ? req.id : nextFrId(used, warnings, reserved);
1351
1366
  if (req.id && req.id !== id) {
1352
1367
  warnings.push(`Duplicate requirement id ${req.id} in ${req.sourceRel}; generated ${id}.`);
1353
1368
  }
@@ -1359,7 +1374,173 @@ function renderRequirement(req, used, warnings) {
1359
1374
  }
1360
1375
  return `- ${text} _(source: ${req.sourceRel})_`;
1361
1376
  }
1362
- function loadSddStatusSync(directory) {
1377
+ function detectSpeckit(directory) {
1378
+ const root = path4.resolve(directory);
1379
+ const markerPath = path4.join(root, SPECKIT_MARKER);
1380
+ const markerPresent = fs4.statSync(markerPath, { throwIfNoEntry: false })?.isDirectory() ?? false;
1381
+ if (!markerPresent) {
1382
+ return { markerPresent: false, features: [] };
1383
+ }
1384
+ const specsRoot = path4.join(root, SPECKIT_SPECS_DIR);
1385
+ if (!fs4.existsSync(specsRoot)) {
1386
+ return { markerPresent: true, features: [] };
1387
+ }
1388
+ let entries;
1389
+ try {
1390
+ entries = fs4.readdirSync(specsRoot, { withFileTypes: true });
1391
+ } catch {
1392
+ return { markerPresent: true, features: [] };
1393
+ }
1394
+ const featureDirs = entries.filter((entry) => typeof entry?.name === "string" && !entry.isSymbolicLink() && entry.isDirectory()).sort((a, b) => a.name.localeCompare(b.name));
1395
+ const features = [];
1396
+ for (const entry of featureDirs) {
1397
+ if (features.length >= MAX_SPEC_FILES)
1398
+ break;
1399
+ const specAbs = path4.join(specsRoot, entry.name, "spec.md");
1400
+ let stat;
1401
+ try {
1402
+ stat = fs4.lstatSync(specAbs);
1403
+ } catch {
1404
+ continue;
1405
+ }
1406
+ if (!stat.isFile() || stat.size > MAX_SOURCE_BYTES)
1407
+ continue;
1408
+ features.push({
1409
+ featureId: entry.name,
1410
+ specRelPath: toPosix(path4.relative(root, specAbs))
1411
+ });
1412
+ }
1413
+ return { markerPresent: true, features };
1414
+ }
1415
+ function parseSpeckitRequirements(content, sourceRel) {
1416
+ const requirements = [];
1417
+ const lines = content.replace(/\r\n/g, `
1418
+ `).split(`
1419
+ `);
1420
+ let inFrSection = false;
1421
+ for (const line of lines) {
1422
+ if (/^##\s+/.test(line)) {
1423
+ inFrSection = /^##\s+Functional Requirements\s*$/i.test(line);
1424
+ continue;
1425
+ }
1426
+ if (!inFrSection)
1427
+ continue;
1428
+ if (!/^\s*[-*]\s+/.test(line))
1429
+ continue;
1430
+ if (!/\b(MUST|SHALL|SHOULD|MAY)\b/i.test(line))
1431
+ continue;
1432
+ const text = line.trim().replace(/^\s*[-*]\s+/, "");
1433
+ const explicit = line.match(/\b(FR-(?!000)\d{3})\b/);
1434
+ if (explicit) {
1435
+ requirements.push({
1436
+ id: explicit[1].toUpperCase(),
1437
+ kind: "CURRENT",
1438
+ title: explicit[1].toUpperCase(),
1439
+ text,
1440
+ sourceRel
1441
+ });
1442
+ continue;
1443
+ }
1444
+ requirements.push({
1445
+ id: null,
1446
+ kind: "CURRENT",
1447
+ title: text,
1448
+ text,
1449
+ sourceRel
1450
+ });
1451
+ }
1452
+ return requirements;
1453
+ }
1454
+ function resolveSpeckitProjection(directory, options = {}) {
1455
+ const root = path4.resolve(directory);
1456
+ const detection = detectSpeckit(root);
1457
+ if (!detection.markerPresent) {
1458
+ return { kind: "not_speckit" };
1459
+ }
1460
+ if (detection.features.length === 0) {
1461
+ return { kind: "empty" };
1462
+ }
1463
+ let selectedFeature;
1464
+ if (options.feature) {
1465
+ const found = detection.features.find((f) => f.featureId === options.feature);
1466
+ if (!found) {
1467
+ return {
1468
+ kind: "unknown_feature",
1469
+ feature: options.feature,
1470
+ available: detection.features.map((f) => f.featureId)
1471
+ };
1472
+ }
1473
+ selectedFeature = found;
1474
+ } else if (detection.features.length === 1) {
1475
+ selectedFeature = detection.features[0];
1476
+ } else {
1477
+ return {
1478
+ kind: "ambiguous",
1479
+ features: detection.features.map((f) => f.featureId)
1480
+ };
1481
+ }
1482
+ const specAbs = path4.join(root, selectedFeature.specRelPath);
1483
+ const content = readTextBounded(specAbs);
1484
+ if (content === null) {
1485
+ return { kind: "zero_requirements", feature: selectedFeature.featureId };
1486
+ }
1487
+ const warnings = [];
1488
+ const usedIds = new Set;
1489
+ const requirements = parseSpeckitRequirements(content, selectedFeature.specRelPath);
1490
+ if (requirements.length === 0) {
1491
+ return { kind: "zero_requirements", feature: selectedFeature.featureId };
1492
+ }
1493
+ const reservedIds = new Set;
1494
+ for (const req of requirements) {
1495
+ if (req.id)
1496
+ reservedIds.add(req.id);
1497
+ }
1498
+ let mtimeMs = 0;
1499
+ try {
1500
+ mtimeMs = fs4.lstatSync(specAbs).mtimeMs;
1501
+ } catch {}
1502
+ const lines = [
1503
+ "# Specification: Effective SDD Projection",
1504
+ "",
1505
+ "Generated from Spec-Kit feature artifacts. Update the source artifacts, then run `/swarm sdd project` to refresh this projection.",
1506
+ "",
1507
+ "## Source Artifacts",
1508
+ `- ${selectedFeature.specRelPath}`,
1509
+ "",
1510
+ "## Functional Requirements"
1511
+ ];
1512
+ for (const req of requirements) {
1513
+ lines.push(renderRequirement(req, usedIds, warnings, reservedIds));
1514
+ }
1515
+ const projected = `${lines.join(`
1516
+ `)}
1517
+ `;
1518
+ if (projected.length > MAX_SPEC_BYTES) {
1519
+ return {
1520
+ kind: "too_large",
1521
+ feature: selectedFeature.featureId,
1522
+ bytes: projected.length
1523
+ };
1524
+ }
1525
+ const validation = validateSpecContent(projected);
1526
+ if (!validation.valid) {
1527
+ warnings.push(...validation.issues.map((issue) => `Projection line ${issue.line}: ${issue.message}`));
1528
+ }
1529
+ const spec = {
1530
+ source: "speckit_projection",
1531
+ content: projected,
1532
+ hash: hash(projected),
1533
+ mtime: mtimeMs > 0 ? new Date(mtimeMs).toISOString() : null,
1534
+ sourcePaths: [selectedFeature.specRelPath],
1535
+ warnings
1536
+ };
1537
+ return { kind: "ok", spec, feature: selectedFeature.featureId };
1538
+ }
1539
+ function buildSpeckitProjectionSync(directory, options = {}) {
1540
+ const resolution = resolveSpeckitProjection(directory, options);
1541
+ return resolution.kind === "ok" ? resolution.spec : null;
1542
+ }
1543
+ function loadSddStatusSync(directory, opts) {
1363
1544
  const root = path4.resolve(directory);
1364
1545
  const swSpecPath = path4.join(root, SWARM_SPEC_REL);
1365
1546
  const openSpecPath = path4.join(root, OPENSPEC_ROOT);
@@ -1369,7 +1550,7 @@ function loadSddStatusSync(directory) {
1369
1550
  const openSpecExists = fs4.existsSync(openSpecPath);
1370
1551
  const currentSpecs = walkSpecFiles(root, path4.join(OPENSPEC_ROOT, "specs"));
1371
1552
  const changes = listOpenSpecChanges(root);
1372
- const effectiveSpec = readEffectiveSpecSync(root);
1553
+ const effectiveSpec = readEffectiveSpecSync(root, opts);
1373
1554
  if (openSpecExists && currentSpecs.length === 0 && changes.length === 0) {
1374
1555
  errors.push("openspec/ exists but contains no specs or active changes.");
1375
1556
  }
@@ -1493,7 +1674,7 @@ function buildOpenSpecProjectionSync(directory, options = {}) {
1493
1674
  warnings
1494
1675
  };
1495
1676
  }
1496
- function readEffectiveSpecSync(directory) {
1677
+ function readEffectiveSpecSync(directory, opts) {
1497
1678
  const root = path4.resolve(directory);
1498
1679
  const swSpecPath = path4.join(root, SWARM_SPEC_REL);
1499
1680
  try {
@@ -1514,13 +1695,35 @@ function readEffectiveSpecSync(directory) {
1514
1695
  throw error2;
1515
1696
  }
1516
1697
  }
1517
- return buildOpenSpecProjectionSync(root);
1698
+ if (opts?.source) {
1699
+ switch (opts.source) {
1700
+ case "openspec":
1701
+ return buildOpenSpecProjectionSync(root);
1702
+ case "speckit":
1703
+ return buildSpeckitProjectionSync(root, { feature: opts.feature });
1704
+ case "swarm":
1705
+ return null;
1706
+ }
1707
+ }
1708
+ const speckitDetection = detectSpeckit(root);
1709
+ if (!speckitDetection.markerPresent) {
1710
+ return buildOpenSpecProjectionSync(root);
1711
+ }
1712
+ const speckitPresent = speckitDetection.features.length > 0;
1713
+ if (!speckitPresent) {
1714
+ return buildOpenSpecProjectionSync(root);
1715
+ }
1716
+ const openspecProjection = buildOpenSpecProjectionSync(root);
1717
+ const openspecPresent = openspecProjection !== null;
1718
+ if (openspecPresent) {
1719
+ console.warn("[opencode-swarm] Multiple SDD sources detected (openspec and speckit). " + "Enforcement/projection is suppressed until you disambiguate. " + "Pass --source openspec or --source speckit to select a provider.");
1720
+ return null;
1721
+ }
1722
+ return buildSpeckitProjectionSync(root, { feature: opts?.feature });
1518
1723
  }
1519
1724
  function writeProjectedSpecSync(directory, options = {}) {
1520
1725
  const root = path4.resolve(directory);
1521
- const projection = buildOpenSpecProjectionSync(root, {
1522
- changeId: options.changeId
1523
- });
1726
+ const projection = options.source === "speckit" ? buildSpeckitProjectionSync(root, { feature: options.feature }) : buildOpenSpecProjectionSync(root, { changeId: options.changeId });
1524
1727
  const target = path4.join(root, SWARM_SPEC_REL);
1525
1728
  if (!projection || options.dryRun) {
1526
1729
  return { written: false, projection, path: target };
@@ -1542,6 +1745,59 @@ function writeProjectedSpecSync(directory, options = {}) {
1542
1745
  fs4.renameSync(tmp, target);
1543
1746
  return { written: true, projection, archivePath, path: target };
1544
1747
  }
1748
+ function validateSpeckit(directory, options = {}) {
1749
+ const root = path4.resolve(directory);
1750
+ const resolution = resolveSpeckitProjection(root, options);
1751
+ const problems = [];
1752
+ if (resolution.kind !== "ok" && resolution.kind !== "zero_requirements") {
1753
+ return { resolution, problems };
1754
+ }
1755
+ if (resolution.kind === "zero_requirements") {
1756
+ problems.push(`Feature '${resolution.feature}' contains no parsable functional requirements.`);
1757
+ }
1758
+ const detection = detectSpeckit(root);
1759
+ const featureEntry = detection.features.find((f) => f.featureId === resolution.feature);
1760
+ if (!featureEntry) {
1761
+ return { resolution, problems };
1762
+ }
1763
+ const specAbs = path4.join(root, featureEntry.specRelPath);
1764
+ let specContent = null;
1765
+ try {
1766
+ specContent = readTextBounded(specAbs);
1767
+ } catch {}
1768
+ if (specContent !== null) {
1769
+ const specLines = specContent.replace(/\r\n/g, `
1770
+ `).split(`
1771
+ `);
1772
+ for (const requiredHeader of SPECKIT_REQUIRED_SECTIONS) {
1773
+ if (!specLines.some((line) => line.trimEnd() === requiredHeader)) {
1774
+ problems.push(`Missing required spec.md section: ${requiredHeader}`);
1775
+ }
1776
+ }
1777
+ }
1778
+ const tasksAbs = path4.join(path4.dirname(specAbs), "tasks.md");
1779
+ let tasksContent = null;
1780
+ try {
1781
+ tasksContent = readTextBounded(tasksAbs);
1782
+ } catch {}
1783
+ if (tasksContent !== null) {
1784
+ const tasksLines = tasksContent.replace(/\r\n/g, `
1785
+ `).split(`
1786
+ `);
1787
+ for (const line of tasksLines) {
1788
+ const taskMatch = line.match(/^\s*-\s+\[[ xX]\]\s+(T\d+)/);
1789
+ if (!taskMatch)
1790
+ continue;
1791
+ const taskId = taskMatch[1];
1792
+ const hasStoryRef = /\[US\d+\]/i.test(line);
1793
+ const hasReqRef = /\bFR-(?!000)\d{3}\b/i.test(line);
1794
+ if (!hasStoryRef && !hasReqRef) {
1795
+ problems.push(`Task ${taskId} has no spec/requirement reference.`);
1796
+ }
1797
+ }
1798
+ }
1799
+ return { resolution, problems };
1800
+ }
1545
1801
 
1546
1802
  // src/utils/spec-hash.ts
1547
1803
  async function computeSpecHash(directory) {
@@ -3196,7 +3452,7 @@ import {
3196
3452
  readdirSync as readdirSync3,
3197
3453
  realpathSync,
3198
3454
  rmSync,
3199
- statSync
3455
+ statSync as statSync2
3200
3456
  } from "fs";
3201
3457
  import * as fs6 from "fs/promises";
3202
3458
  import * as path7 from "path";
@@ -3536,11 +3792,11 @@ function validateProjectRoot(directory) {
3536
3792
  break;
3537
3793
  const parentSwarm = path7.join(parent, ".swarm");
3538
3794
  try {
3539
- if (statSync(parentSwarm).isDirectory()) {
3795
+ if (statSync2(parentSwarm).isDirectory()) {
3540
3796
  let hasProjectIndicator = false;
3541
3797
  for (const indicator of PROJECT_INDICATORS) {
3542
3798
  try {
3543
- const indicatorStat = statSync(path7.join(parent, indicator));
3799
+ const indicatorStat = statSync2(path7.join(parent, indicator));
3544
3800
  if (indicatorStat.isFile() || indicatorStat.isDirectory()) {
3545
3801
  hasProjectIndicator = true;
3546
3802
  break;
@@ -3716,7 +3972,7 @@ async function loadEvidence(directory, taskId) {
3716
3972
  async function listEvidenceTaskIds(directory) {
3717
3973
  const evidenceBasePath = validateSwarmPath(directory, "evidence");
3718
3974
  try {
3719
- statSync(evidenceBasePath);
3975
+ statSync2(evidenceBasePath);
3720
3976
  } catch {
3721
3977
  return [];
3722
3978
  }
@@ -3730,7 +3986,7 @@ async function listEvidenceTaskIds(directory) {
3730
3986
  for (const entry of entries) {
3731
3987
  const entryPath = path7.join(evidenceBasePath, entry);
3732
3988
  try {
3733
- const stats = statSync(entryPath);
3989
+ const stats = statSync2(entryPath);
3734
3990
  if (!stats.isDirectory()) {
3735
3991
  continue;
3736
3992
  }
@@ -3749,7 +4005,7 @@ async function deleteEvidence(directory, taskId) {
3749
4005
  const relativePath = path7.join("evidence", sanitizedTaskId);
3750
4006
  const evidenceDir = validateSwarmPath(directory, relativePath);
3751
4007
  try {
3752
- statSync(evidenceDir);
4008
+ statSync2(evidenceDir);
3753
4009
  } catch {
3754
4010
  return false;
3755
4011
  }
@@ -3928,4 +4184,4 @@ function mergeDurableGateEntriesFromEvidence(taskId, entries, evidence) {
3928
4184
  return merged;
3929
4185
  }
3930
4186
 
3931
- export { PlanSchema, MAX_TRANSIENT_RETRIES, isTransientSpawnError, transientBackoff, getGitRepositoryStatus, isGitRepo, resetToRemoteBranch, resetToMainAfterMerge, isStateUnreadable, loadEpicSessionState, isEpicModeActive, enableEpicMode, disableEpicMode, normalizePath, pathsConflict, isGlobalFile, isProtectedPath, readTaskScopes, loadSddStatusSync, buildOpenSpecProjectionSync, readEffectiveSpecSync, writeProjectedSpecSync, computeSpecHash, derivePlanId, computePlanHash, initLedger, appendLedgerEvent, loadPlanJsonOnly, loadPlan, savePlan, closePlanTerminalState, derivePlanMarkdown, RetrospectiveEvidenceSchema, isValidEvidenceType, sanitizeTaskId2 as sanitizeTaskId, validateProjectRoot, saveEvidence, loadEvidence, listEvidenceTaskIds, checkRequirementCoverage, archiveEvidence, readDurableGateEvidence, getDurableGateEvidenceStatus, getDurableGateEvidenceStatusForTask, mergeDurableGateEntriesFromEvidence };
4187
+ export { PlanSchema, MAX_TRANSIENT_RETRIES, isTransientSpawnError, transientBackoff, getGitRepositoryStatus, isGitRepo, resetToRemoteBranch, resetToMainAfterMerge, isStateUnreadable, loadEpicSessionState, isEpicModeActive, enableEpicMode, disableEpicMode, normalizePath, pathsConflict, isGlobalFile, isProtectedPath, readTaskScopes, detectSpeckit, resolveSpeckitProjection, loadSddStatusSync, buildOpenSpecProjectionSync, readEffectiveSpecSync, writeProjectedSpecSync, validateSpeckit, computeSpecHash, derivePlanId, computePlanHash, initLedger, appendLedgerEvent, loadPlanJsonOnly, loadPlan, savePlan, closePlanTerminalState, derivePlanMarkdown, RetrospectiveEvidenceSchema, isValidEvidenceType, sanitizeTaskId2 as sanitizeTaskId, validateProjectRoot, saveEvidence, loadEvidence, listEvidenceTaskIds, checkRequirementCoverage, archiveEvidence, readDurableGateEvidence, getDurableGateEvidenceStatus, getDurableGateEvidenceStatusForTask, mergeDurableGateEntriesFromEvidence };
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  handleGuardrailExplain
4
- } from "./index-4xqnktvr.js";
4
+ } from "./index-wc8g49dp.js";
5
5
  import {
6
6
  handleGuardrailLog
7
7
  } from "./index-5bstvpct.js";
@@ -79,7 +79,7 @@ import {
79
79
  handleWriteRetroCommand,
80
80
  normalizeSwarmCommandInput,
81
81
  resolveCommand
82
- } from "./index-qvdxzjqt.js";
82
+ } from "./index-rmh6nqbp.js";
83
83
  import"./index-pj3a5fbt.js";
84
84
  import"./index-ydgy7vg5.js";
85
85
  import"./index-vwyjkzgs.js";
@@ -90,7 +90,7 @@ import {
90
90
  ORCHESTRATOR_NAME,
91
91
  stripKnownSwarmPrefix
92
92
  } from "./index-m0qshbr6.js";
93
- import"./index-3emh7rny.js";
93
+ import"./index-bnkd9ejn.js";
94
94
  import"./index-adz3nk9b.js";
95
95
  import"./index-v4fcn4tr.js";
96
96
  import"./index-r8f89sm9.js";