@gobing-ai/spur 0.2.10 → 0.2.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobing-ai/spur",
3
- "version": "0.2.10",
3
+ "version": "0.2.11",
4
4
  "description": "Spur CLI — local-first harness for mainstream coding agents: constraint checking, workflow orchestration, agent health, and history analytics. Bun-native; exposes the `spur` command.",
5
5
  "keywords": [
6
6
  "spur",
@@ -1,8 +1,8 @@
1
- $schema: "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
2
1
  # Canonical basic workflow: implement -> check -> fix until the check passes or the
3
2
  # iteration bound is exhausted. Authored for the @gobing-ai/ts-dual-workflow-engine
4
3
  # state-machine schema (initialState / states[].id / onEnter / top-level transitions).
5
4
  # See Architecture Section 5 and Design Section 3.3.
5
+ "$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
6
6
  name: basic
7
7
  kind: state-machine
8
8
  description: The canonical implement-check-fix-until-pass loop
@@ -97,10 +97,9 @@ states:
97
97
  - id: approve
98
98
  description: >
99
99
  Human-in-the-loop approval gate. Under `profile=auto` this state is never entered
100
- (review routes around it straight to verify). NOTE: to make this state PAUSE for `spur workflow continue`
101
- (E3), add `pause: true` here deferred until the globally-installed `@gobing-ai/spur`
102
- schema (currently a stale 0.2.5 that lacks the `pause` field) is refreshed, so
103
- full `spur workflow validate` stays green. The workspace schema already supports it.
100
+ (review routes around it straight to verify). In interactive mode this state pauses
101
+ the run for `spur workflow continue` (E3), making approval an explicit operator action.
102
+ pause: true
104
103
  onEnter:
105
104
  - kind: hitl.confirm
106
105
  options:
package/spur.js CHANGED
@@ -32493,7 +32493,40 @@ var init_template_renderer = () => {};
32493
32493
  // ../../packages/config/src/loader.ts
32494
32494
  import { existsSync as existsSync3 } from "fs";
32495
32495
  import { homedir } from "os";
32496
- import { join as join2 } from "path";
32496
+ import { dirname as dirname3, join as join2 } from "path";
32497
+ import { fileURLToPath as fileURLToPath3 } from "url";
32498
+ function embeddedSchemasId(embeddedSchemas) {
32499
+ if (embeddedSchemas === undefined)
32500
+ return "disk";
32501
+ let id = embeddedSchemasIds.get(embeddedSchemas);
32502
+ if (id === undefined) {
32503
+ id = nextEmbeddedSchemasId;
32504
+ nextEmbeddedSchemasId += 1;
32505
+ embeddedSchemasIds.set(embeddedSchemas, id);
32506
+ }
32507
+ return `embedded:${id}`;
32508
+ }
32509
+ function cacheKey(configPath, opts, validateJsonSchema2) {
32510
+ return [
32511
+ configPath,
32512
+ validateJsonSchema2 ? "schema" : "zod",
32513
+ opts?.schemaManifestSpecifier ?? "@gobing-ai/spur/package.json",
32514
+ embeddedSchemasId(opts?.embeddedSchemas)
32515
+ ].join("\x00");
32516
+ }
32517
+ function resolveSchemaSpecifier(specifier, manifestSpecifier) {
32518
+ if (specifier === manifestSpecifier) {
32519
+ const workspaceManifest = join2(LOADER_DIR, "..", "..", "..", "apps", "cli", "package.json");
32520
+ if (existsSync3(workspaceManifest))
32521
+ return workspaceManifest;
32522
+ }
32523
+ try {
32524
+ const resolved = import.meta.resolve(specifier);
32525
+ return resolved.startsWith("file:") ? fileURLToPath3(resolved) : resolved;
32526
+ } catch {
32527
+ return specifier;
32528
+ }
32529
+ }
32497
32530
  function resolveConfigFile(cwd) {
32498
32531
  const projectConfig = join2(cwd ?? process.cwd(), SPUR_CONFIG_DIR, SPUR_CONFIG_FILE);
32499
32532
  if (existsSync3(projectConfig))
@@ -32518,11 +32551,21 @@ function makeEmbeddedReader(embeddedSchemas) {
32518
32551
  };
32519
32552
  }
32520
32553
  async function loadSpurConfig(cwd = process.cwd(), opts) {
32521
- const configPath = join2(cwd, SPUR_CONFIG_DIR, SPUR_CONFIG_FILE);
32522
- if (!existsSync3(configPath)) {
32554
+ const configPath = resolveConfigFile(cwd);
32555
+ if (configPath === undefined) {
32523
32556
  return spurConfigSchema.parse({});
32524
32557
  }
32525
32558
  const validateJsonSchema2 = opts?.validateJsonSchema ?? true;
32559
+ const key = cacheKey(configPath, opts, validateJsonSchema2);
32560
+ const cached2 = spurConfigCache.get(key);
32561
+ if (cached2 !== undefined)
32562
+ return cached2;
32563
+ const promise2 = loadSpurConfigFile(configPath, opts, validateJsonSchema2);
32564
+ spurConfigCache.set(key, promise2);
32565
+ promise2.catch(() => spurConfigCache.delete(key));
32566
+ return promise2;
32567
+ }
32568
+ async function loadSpurConfigFile(configPath, opts, validateJsonSchema2) {
32526
32569
  let raw;
32527
32570
  if (validateJsonSchema2) {
32528
32571
  const embeddedSchemas = opts?.embeddedSchemas;
@@ -32531,7 +32574,7 @@ async function loadSpurConfig(cwd = process.cwd(), opts) {
32531
32574
  if (embeddedSchemas !== undefined && specifier === manifestSpecifier) {
32532
32575
  return `${EMBEDDED_PREFIX}/package.json`;
32533
32576
  }
32534
- return specifier;
32577
+ return resolveSchemaSpecifier(specifier, manifestSpecifier);
32535
32578
  };
32536
32579
  const fileSystem = embeddedSchemas ? { readFile: makeEmbeddedReader(embeddedSchemas) } : undefined;
32537
32580
  raw = await loadStructuredConfig(configPath, {
@@ -32561,7 +32604,7 @@ async function loadStructuredSpurConfig(configPath, opts) {
32561
32604
  if (embeddedSchemas !== undefined && specifier === manifestSpecifier) {
32562
32605
  return `${EMBEDDED_PREFIX}/package.json`;
32563
32606
  }
32564
- return specifier;
32607
+ return resolveSchemaSpecifier(specifier, manifestSpecifier);
32565
32608
  };
32566
32609
  const fileSystem = embeddedSchemas ? { readFile: makeEmbeddedReader(embeddedSchemas) } : undefined;
32567
32610
  return await loadStructuredConfig(configPath, {
@@ -32584,6 +32627,14 @@ function defaultPlanningFolders() {
32584
32627
  };
32585
32628
  }
32586
32629
  async function resolvePlanningFolders(fs) {
32630
+ const cached2 = planningFoldersCache.get(fs);
32631
+ if (cached2 !== undefined)
32632
+ return cached2;
32633
+ const promise2 = resolvePlanningFoldersUncached(fs);
32634
+ planningFoldersCache.set(fs, promise2);
32635
+ return promise2;
32636
+ }
32637
+ async function resolvePlanningFoldersUncached(fs) {
32587
32638
  const configPath = fs.resolve(join2(SPUR_CONFIG_DIR, SPUR_CONFIG_FILE));
32588
32639
  if (!await fs.exists(configPath))
32589
32640
  return defaultPlanningFolders();
@@ -32614,7 +32665,7 @@ async function resolvePlanningFolders(fs) {
32614
32665
  return defaultPlanningFolders();
32615
32666
  }
32616
32667
  }
32617
- var SPUR_CONFIG_DIR = ".spur", SPUR_CONFIG_FILE = "config.yaml", GLOBAL_CONFIG_FILE, EMBEDDED_PREFIX = "\x00embedded-spur";
32668
+ var SPUR_CONFIG_DIR = ".spur", SPUR_CONFIG_FILE = "config.yaml", GLOBAL_CONFIG_FILE, LOADER_DIR, nextEmbeddedSchemasId = 1, embeddedSchemasIds, spurConfigCache, planningFoldersCache, EMBEDDED_PREFIX = "\x00embedded-spur";
32618
32669
  var init_loader = __esm(() => {
32619
32670
  init_dist3();
32620
32671
  init_dist2();
@@ -32622,6 +32673,10 @@ var init_loader = __esm(() => {
32622
32673
  init_bundled_config();
32623
32674
  init_template_renderer();
32624
32675
  GLOBAL_CONFIG_FILE = join2(homedir(), ".config", "spur", "config.yaml");
32676
+ LOADER_DIR = dirname3(fileURLToPath3(import.meta.url));
32677
+ embeddedSchemasIds = new WeakMap;
32678
+ spurConfigCache = new Map;
32679
+ planningFoldersCache = new WeakMap;
32625
32680
  });
32626
32681
 
32627
32682
  // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.4.2/node_modules/@gobing-ai/ts-runtime/dist/bun-sqlite.js
@@ -55614,7 +55669,7 @@ var init_db2 = __esm(() => {
55614
55669
 
55615
55670
  // ../../packages/domain/src/planning/locks.ts
55616
55671
  import { closeSync, fsyncSync, openSync, renameSync as renameSync2 } from "fs";
55617
- import { dirname as dirname5, join as join5 } from "path";
55672
+ import { dirname as dirname6, join as join5 } from "path";
55618
55673
  function lockPathForEntity(folder, entityId) {
55619
55674
  return join5(folder, `.${entityId}.lock`);
55620
55675
  }
@@ -55701,7 +55756,7 @@ function fsyncPath(path8, swallowErrors = false) {
55701
55756
  }
55702
55757
  }
55703
55758
  function atomicWrite(dest, content, entityId, fs3, projectName = "spur") {
55704
- const dir = dirname5(dest);
55759
+ const dir = dirname6(dest);
55705
55760
  const rand = Math.random().toString(36).slice(2, 10);
55706
55761
  const tempPath = join5(dir, `.${projectName}.${entityId}.${rand}.tmp`);
55707
55762
  fs3.writeFile(tempPath, content);
@@ -55710,7 +55765,7 @@ function atomicWrite(dest, content, entityId, fs3, projectName = "spur") {
55710
55765
  fsyncPath(dir, true);
55711
55766
  }
55712
55767
  async function atomicWriteAsync(dest, content, entityId, fs3, projectName = "spur") {
55713
- const dir = dirname5(dest);
55768
+ const dir = dirname6(dest);
55714
55769
  const rand = Math.random().toString(36).slice(2, 10);
55715
55770
  const tempPath = join5(dir, `.${projectName}.${entityId}.${rand}.tmp`);
55716
55771
  await fs3.writeFile(tempPath, content);
@@ -55774,6 +55829,19 @@ function findHeadings(body, hashes) {
55774
55829
  }
55775
55830
  return hits;
55776
55831
  }
55832
+ function normalizeYamlScalars(data) {
55833
+ const out = {};
55834
+ for (const [key, value] of Object.entries(data)) {
55835
+ if (value instanceof Date) {
55836
+ out[key] = value.toISOString();
55837
+ } else if (value !== null && typeof value === "object" && !Array.isArray(value)) {
55838
+ out[key] = normalizeYamlScalars(value);
55839
+ } else {
55840
+ out[key] = value;
55841
+ }
55842
+ }
55843
+ return out;
55844
+ }
55777
55845
 
55778
55846
  class MarkdownDocument {
55779
55847
  _domain;
@@ -55804,7 +55872,7 @@ class MarkdownDocument {
55804
55872
  try {
55805
55873
  const parsed = $parse(raw);
55806
55874
  if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
55807
- data = parsed;
55875
+ data = normalizeYamlScalars(parsed);
55808
55876
  }
55809
55877
  } catch {}
55810
55878
  frontmatter = { raw, data };
@@ -55971,8 +56039,19 @@ ${body.trimEnd()}
55971
56039
  ${content}
55972
56040
  ${END_MARKER}${after}`;
55973
56041
  }
56042
+ yamlSafeValue(value) {
56043
+ if (value.startsWith('"') && value.endsWith('"'))
56044
+ return value;
56045
+ if (/[:{}[\],&*?|>!%@`#"'\\\n\r]/.test(value))
56046
+ return `"${value}"`;
56047
+ if (/^(null|true|false|yes|no|on|off)$/i.test(value))
56048
+ return `"${value}"`;
56049
+ if (/^\d/.test(value) && !/^\d{4}-\d{2}-\d{2}/.test(value))
56050
+ return `"${value}"`;
56051
+ return value;
56052
+ }
55974
56053
  setFrontmatterField(key, value) {
55975
- const fieldLine = `${key}: ${value}`;
56054
+ const fieldLine = `${key}: ${this.yamlSafeValue(value)}`;
55976
56055
  if (this._frontmatter === null) {
55977
56056
  const raw2 = fieldLine;
55978
56057
  this._frontmatter = { raw: raw2, data: { [key]: value } };
@@ -58476,7 +58555,7 @@ var init_rule_service = __esm(() => {
58476
58555
  });
58477
58556
 
58478
58557
  // ../../packages/app/src/services/task-check.ts
58479
- import { dirname as dirname6, join as join8 } from "path";
58558
+ import { dirname as dirname7, join as join8 } from "path";
58480
58559
  function isPlaceholderBody(body) {
58481
58560
  const stripped = body.replace(/<!--[\s\S]*?-->/g, "").replace(/^\s*>\s*TBD\s*$/gim, "").trim();
58482
58561
  return stripped.length === 0;
@@ -58526,6 +58605,29 @@ function isProseOnlyReview(body) {
58526
58605
  return !stripped.split(`
58527
58606
  `).some((l) => l.includes("|"));
58528
58607
  }
58608
+ function hasAdjacentFileLineColumns(body) {
58609
+ const lines = body.split(`
58610
+ `);
58611
+ for (const line of lines) {
58612
+ if (!line.includes("|"))
58613
+ continue;
58614
+ const cells = line.split("|").map((c3) => c3.trim());
58615
+ if (cells.length < 3)
58616
+ continue;
58617
+ for (let i2 = 0;i2 < cells.length - 1; i2++) {
58618
+ const a2 = cells[i2];
58619
+ const b = cells[i2 + 1];
58620
+ if (a2 === undefined || b === undefined)
58621
+ continue;
58622
+ const fileText = a2.replace(/`/g, "");
58623
+ const fileCell = /\.(tsx?|jsx?|mjs|cjs|md|ya?ml|json|css|html|sql|sh|toml)$/i.test(fileText);
58624
+ const lineCell = /^\d+(-\d+)?$/.test(b);
58625
+ if (fileCell && lineCell)
58626
+ return true;
58627
+ }
58628
+ }
58629
+ return false;
58630
+ }
58529
58631
  var TaskCheckService;
58530
58632
  var init_task_check = __esm(() => {
58531
58633
  init_src2();
@@ -58554,8 +58656,8 @@ var init_task_check = __esm(() => {
58554
58656
  const entry = this.resolveMatrixEntry(variant, status);
58555
58657
  this.runL2(doc2, entry, findings);
58556
58658
  this.runL3(doc2, entry, findings);
58557
- const tasksDir = dirname6(filePath);
58558
- const featuresDir = join8(dirname6(tasksDir), "features");
58659
+ const tasksDir = dirname7(filePath);
58660
+ const featuresDir = join8(dirname7(tasksDir), "features");
58559
58661
  await this.runL4(doc2, fm, findings, featuresDir, tasksDir);
58560
58662
  await this.runL4Rollup(doc2, wbs, status, findings, tasksDir);
58561
58663
  return { wbs, ...this.summarizeWithStatus(status, findings, strict) };
@@ -58586,7 +58688,8 @@ var init_task_check = __esm(() => {
58586
58688
  const solBody = doc2.getSection("Solution");
58587
58689
  if (solBody !== null && !isPlaceholderBody(solBody)) {
58588
58690
  const hasFileLine = /`[^`]+?:\d+(-\d+)?`/.test(solBody) || /[^\s`]\.\w+:\d+/.test(solBody);
58589
- if (!hasFileLine) {
58691
+ const hasTableFileLine = hasFileLine || hasAdjacentFileLineColumns(solBody);
58692
+ if (!hasTableFileLine) {
58590
58693
  findings.push({
58591
58694
  layer: "L3",
58592
58695
  severity: "error",
@@ -58997,7 +59100,7 @@ function gitDiffU0(cwd) {
58997
59100
  var init_task_record = () => {};
58998
59101
 
58999
59102
  // ../../packages/app/src/services/task-service.ts
59000
- import { dirname as dirname7, isAbsolute, relative as relative2, resolve as resolve2 } from "path";
59103
+ import { dirname as dirname8, isAbsolute, relative as relative2, resolve as resolve2 } from "path";
59001
59104
  function patchFrontmatterField(rendered, key, value) {
59002
59105
  const existingRe = new RegExp(`^(?=\\s*${escapeRegex2(key)}:)`, "m");
59003
59106
  if (existingRe.test(rendered)) {
@@ -59590,7 +59693,7 @@ ${block}` : block);
59590
59693
  resolveListDir(folder) {
59591
59694
  if (folder === undefined)
59592
59695
  return this.ctx.tasksDir;
59593
- const root = resolve2(dirname7(this.ctx.tasksDir));
59696
+ const root = resolve2(dirname8(this.ctx.tasksDir));
59594
59697
  const candidate = resolve2(root, folder);
59595
59698
  const rel = relative2(root, candidate);
59596
59699
  if (rel.startsWith("..") || isAbsolute(rel)) {
@@ -59864,7 +59967,7 @@ var init_team_service = __esm(() => {
59864
59967
 
59865
59968
  // ../../packages/app/src/workflow/actions/agent-run.ts
59866
59969
  import { mkdir, writeFile } from "fs/promises";
59867
- import { dirname as dirname8, isAbsolute as isAbsolute2, join as join10 } from "path";
59970
+ import { dirname as dirname9, isAbsolute as isAbsolute2, join as join10 } from "path";
59868
59971
 
59869
59972
  class AgentRunActionRunner {
59870
59973
  kind = KIND;
@@ -59916,7 +60019,7 @@ class AgentRunActionRunner {
59916
60019
  const ok2 = exitCode2 === 0;
59917
60020
  if (answerFile !== undefined) {
59918
60021
  const target = isAbsolute2(answerFile) ? answerFile : join10(cwd, answerFile);
59919
- await mkdir(dirname8(target), { recursive: true });
60022
+ await mkdir(dirname9(target), { recursive: true });
59920
60023
  await writeFile(target, answer, "utf8");
59921
60024
  }
59922
60025
  return {
@@ -61232,7 +61335,7 @@ init_loader();
61232
61335
  init_dist4();
61233
61336
  init_dist3();
61234
61337
  import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4 } from "fs";
61235
- import { dirname as dirname3 } from "path";
61338
+ import { dirname as dirname4 } from "path";
61236
61339
 
61237
61340
  // ../../node_modules/.bun/@gobing-ai+ts-infra@0.4.2+be3e9d72efacb189/node_modules/@gobing-ai/ts-infra/dist/application/index.js
61238
61341
  init_default_observers();
@@ -64634,7 +64737,7 @@ function validateAppConfig(validator, raw, section, filePath) {
64634
64737
  function createFileSink(filePath) {
64635
64738
  return (line) => {
64636
64739
  try {
64637
- mkdirSync2(dirname3(filePath), { recursive: true });
64740
+ mkdirSync2(dirname4(filePath), { recursive: true });
64638
64741
  appendFileSync3(filePath, line);
64639
64742
  } catch {}
64640
64743
  };
@@ -66052,8 +66155,8 @@ var figlet = (() => {
66052
66155
  })();
66053
66156
 
66054
66157
  // ../../node_modules/.bun/figlet@1.11.0/node_modules/figlet/dist/node-figlet.mjs
66055
- import { fileURLToPath as fileURLToPath3 } from "url";
66056
- var __filename2 = fileURLToPath3(import.meta.url);
66158
+ import { fileURLToPath as fileURLToPath4 } from "url";
66159
+ var __filename2 = fileURLToPath4(import.meta.url);
66057
66160
  var __dirname2 = path7.dirname(__filename2);
66058
66161
  var fontPath = path7.join(__dirname2, "/../fonts/");
66059
66162
  var nodeFiglet = figlet;
@@ -68864,7 +68967,7 @@ import { join as join13, resolve as resolve4 } from "path";
68864
68967
  var CLI_CONFIG = {
68865
68968
  binaryName: "spur",
68866
68969
  binaryLabel: "spur",
68867
- binaryVersion: "0.2.10",
68970
+ binaryVersion: "0.2.11",
68868
68971
  configDir: ".spur",
68869
68972
  configFile: ".spur/config.yaml",
68870
68973
  databaseFile: ".spur/spur.db"
@@ -69394,7 +69497,7 @@ init_src();
69394
69497
 
69395
69498
  // ../server/src/serve.ts
69396
69499
  init_src3();
69397
- import { dirname as dirname9, isAbsolute as isAbsolute3, join as join16 } from "path";
69500
+ import { dirname as dirname10, isAbsolute as isAbsolute3, join as join16 } from "path";
69398
69501
  init_dist3();
69399
69502
 
69400
69503
  // ../server/src/bootstrap.ts
@@ -72635,15 +72738,15 @@ var getPattern = (label, next) => {
72635
72738
  }
72636
72739
  const match = label.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/);
72637
72740
  if (match) {
72638
- const cacheKey = `${label}#${next}`;
72639
- if (!patternCache[cacheKey]) {
72741
+ const cacheKey2 = `${label}#${next}`;
72742
+ if (!patternCache[cacheKey2]) {
72640
72743
  if (match[2]) {
72641
- patternCache[cacheKey] = next && next[0] !== ":" && next[0] !== "*" ? [cacheKey, match[1], new RegExp(`^${match[2]}(?=/${next})`)] : [label, match[1], new RegExp(`^${match[2]}$`)];
72744
+ patternCache[cacheKey2] = next && next[0] !== ":" && next[0] !== "*" ? [cacheKey2, match[1], new RegExp(`^${match[2]}(?=/${next})`)] : [label, match[1], new RegExp(`^${match[2]}$`)];
72642
72745
  } else {
72643
- patternCache[cacheKey] = [label, match[1], true];
72746
+ patternCache[cacheKey2] = [label, match[1], true];
72644
72747
  }
72645
72748
  }
72646
- return patternCache[cacheKey];
72749
+ return patternCache[cacheKey2];
72647
72750
  }
72648
72751
  return null;
72649
72752
  };
@@ -77367,7 +77470,7 @@ var defaultDeps = {
77367
77470
  async function resolveWebDistPath(configuredPath) {
77368
77471
  const candidates = configuredPath && configuredPath.trim() !== "" ? [isAbsolute3(configuredPath) ? configuredPath : join16(process.cwd(), configuredPath)] : [
77369
77472
  join16(process.cwd(), "dist/web"),
77370
- join16(dirname9(process.execPath), "../web"),
77473
+ join16(dirname10(process.execPath), "../web"),
77371
77474
  join16(import.meta.dir, "../../../dist/web")
77372
77475
  ];
77373
77476
  for (const candidate of candidates) {
@@ -78135,6 +78238,15 @@ ${result.content}`);
78135
78238
  } else {
78136
78239
  context3.output.write(`${result.ref.id}: ${result.fromStatus} \u2192 ${result.toStatus}`);
78137
78240
  }
78241
+ if (status === "done" && !options.json) {
78242
+ const task2 = await svc.show(wbs);
78243
+ const featureId2 = task2.frontmatter.feature_id;
78244
+ if (!featureId2 || featureId2.length === 0) {
78245
+ context3.output.write(`\u24D8 Task ${wbs} is now done but has no feature_id.
78246
+ ` + ` Link it with: spur task update ${wbs} --feature <id>
78247
+ ` + " (Advisory only \u2014 the task is already done.)");
78248
+ }
78249
+ }
78138
78250
  } else {
78139
78251
  context3.output.error("Either <status>, --section/--from-file, or --feature/--priority is required");
78140
78252
  context3.setExitCode(2);
@@ -78436,7 +78548,17 @@ async function runDoneGateCheck(context3, wbs, folderOverride) {
78436
78548
  const result = await svc.check(`${tasksDir}/${fileName}`, wbs, { strict: false });
78437
78549
  return result.pass;
78438
78550
  }
78551
+ var sectionMatrixCache = new Map;
78439
78552
  async function loadSectionMatrix(projectRoot) {
78553
+ const cached2 = sectionMatrixCache.get(projectRoot);
78554
+ if (cached2 !== undefined)
78555
+ return cached2;
78556
+ const promise2 = loadSectionMatrixUncached(projectRoot);
78557
+ sectionMatrixCache.set(projectRoot, promise2);
78558
+ promise2.catch(() => sectionMatrixCache.delete(projectRoot));
78559
+ return promise2;
78560
+ }
78561
+ async function loadSectionMatrixUncached(projectRoot) {
78440
78562
  const localPath = join18(projectRoot, ".spur", "tasks", "section-matrix.yaml");
78441
78563
  if (existsSync5(localPath)) {
78442
78564
  const data = await loadStructuredSpurConfig(localPath, {
@@ -78826,7 +78948,7 @@ init_src3();
78826
78948
  init_src();
78827
78949
  init_src2();
78828
78950
  init_dist3();
78829
- import { dirname as dirname10, join as join19, resolve as resolve6 } from "path";
78951
+ import { dirname as dirname11, join as join19, resolve as resolve6 } from "path";
78830
78952
  import { isatty as isatty3 } from "tty";
78831
78953
 
78832
78954
  // ../../node_modules/.bun/@clack+core@1.4.1/node_modules/@clack/core/dist/index.mjs
@@ -80238,7 +80360,7 @@ async function createMigratedDbAdapter(cwd = process.cwd(), env = process.env, d
80238
80360
  const configuredUrl = env.DATABASE_URL === undefined ? join19(cwd, CLI_CONFIG.databaseFile) : config3.database.url;
80239
80361
  const url2 = dbUrl ?? configuredUrl;
80240
80362
  if (url2 !== ":memory:") {
80241
- await createNodeFileSystem().ensureDir(dirname10(url2));
80363
+ await createNodeFileSystem().ensureDir(dirname11(url2));
80242
80364
  }
80243
80365
  return createMigratedDb({ url: url2 });
80244
80366
  }