@gobing-ai/spur 0.3.82 → 0.3.84

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 (68) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/rules/strict/runtime-boundaries.yaml +1 -0
  3. package/package.json +1 -1
  4. package/plugins/sp/README.md +17 -11
  5. package/plugins/sp/lib/idea-handoff.generated.mjs +53 -25
  6. package/plugins/sp/plugin.json +1 -1
  7. package/plugins/sp/skills/parallel-execution/references/dispatch-surface.md +37 -0
  8. package/plugins/sp/skills/parallel-execution/references/fan-out-patterns.md +55 -1
  9. package/plugins/sp/skills/spec-decomposition/references/decomposition.md +12 -0
  10. package/plugins/sp/skills/spur-dev/references/cross-cutting.md +4 -1
  11. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +60 -4
  12. package/schemas/spur-config.schema.json +5 -0
  13. package/schemas/task-batch.schema.json +5 -0
  14. package/spur.js +910 -358
  15. package/web/_astro/{BoardApp.D8bM9pKL.js → BoardApp.D4mBUGLT.js} +64 -64
  16. package/web/_astro/BoardApp.DFPYpI5O.js +1 -0
  17. package/web/_astro/{TaskDetail.BPRqgVUE.js → TaskDetail.DhCWaOPR.js} +1 -1
  18. package/web/_astro/{arc.BPrPES3z.js → arc.D3X0nfq-.js} +1 -1
  19. package/web/_astro/{architectureDiagram-3BPJPVTR.qX_7q02P.js → architectureDiagram-3BPJPVTR.BQDoiGme.js} +1 -1
  20. package/web/_astro/{blockDiagram-GPEHLZMM.CUZfj5V7.js → blockDiagram-GPEHLZMM.BjHekBbM.js} +1 -1
  21. package/web/_astro/{c4Diagram-AAUBKEIU.CTaOr8hH.js → c4Diagram-AAUBKEIU.BvHt171l.js} +1 -1
  22. package/web/_astro/channel.BlQwxyoI.js +1 -0
  23. package/web/_astro/{chunk-2J33WTMH.Dt-9wf3h.js → chunk-2J33WTMH.CwxGXlDR.js} +1 -1
  24. package/web/_astro/{chunk-4BX2VUAB.CTC2sdoN.js → chunk-4BX2VUAB.Dia1YtwU.js} +1 -1
  25. package/web/_astro/{chunk-55IACEB6.DQcxt2_g.js → chunk-55IACEB6.Oz9epCXa.js} +1 -1
  26. package/web/_astro/{chunk-727SXJPM.DXFPSn-a.js → chunk-727SXJPM.DKFYJjYJ.js} +1 -1
  27. package/web/_astro/{chunk-AQP2D5EJ.BCx3U4bT.js → chunk-AQP2D5EJ.AD7w5oVz.js} +1 -1
  28. package/web/_astro/{chunk-FMBD7UC4.DL2tJkdO.js → chunk-FMBD7UC4.D0OqTj52.js} +1 -1
  29. package/web/_astro/{chunk-ND2GUHAM.DZyflMro.js → chunk-ND2GUHAM.CU5ppR7j.js} +1 -1
  30. package/web/_astro/{chunk-QZHKN3VN.CUI2mT09.js → chunk-QZHKN3VN.Bu1rFztw.js} +1 -1
  31. package/web/_astro/{classDiagram-4FO5ZUOK.g4rX4Fr1.js → classDiagram-4FO5ZUOK.C0Pgm5yE.js} +1 -1
  32. package/web/_astro/{classDiagram-v2-Q7XG4LA2.g4rX4Fr1.js → classDiagram-v2-Q7XG4LA2.C0Pgm5yE.js} +1 -1
  33. package/web/_astro/{cose-bilkent-S5V4N54A.CzWJLqp0.js → cose-bilkent-S5V4N54A.C0ecLb1e.js} +1 -1
  34. package/web/_astro/{cynefin-OW5HDTMX.WgsvQeCR.js → cynefin-OW5HDTMX.CpStVmhO.js} +1 -1
  35. package/web/_astro/{dagre-BM42HDAG.Dzv6ngql.js → dagre-BM42HDAG.BPWN3W9f.js} +1 -1
  36. package/web/_astro/{diagram-2AECGRRQ.CpJ4a9rU.js → diagram-2AECGRRQ.DnXbEot4.js} +1 -1
  37. package/web/_astro/{diagram-5GNKFQAL.CzPlF_dq.js → diagram-5GNKFQAL.BYJsgUiI.js} +1 -1
  38. package/web/_astro/{diagram-KO2AKTUF.TAkZNTcQ.js → diagram-KO2AKTUF.CU4Bqprx.js} +1 -1
  39. package/web/_astro/{diagram-LMA3HP47.uCjoKSag.js → diagram-LMA3HP47.BQLOANIu.js} +1 -1
  40. package/web/_astro/{diagram-OG6HWLK6.eMplIjoK.js → diagram-OG6HWLK6.Cn1UVhqj.js} +1 -1
  41. package/web/_astro/{erDiagram-TEJ5UH35.Bf7zoXGz.js → erDiagram-TEJ5UH35.C8FdZyvA.js} +1 -1
  42. package/web/_astro/{flowDiagram-I6XJVG4X.B_bHj3gN.js → flowDiagram-I6XJVG4X.QSZCR8Uo.js} +1 -1
  43. package/web/_astro/{ganttDiagram-6RSMTGT7.BasrHRMj.js → ganttDiagram-6RSMTGT7.O1Py31MY.js} +1 -1
  44. package/web/_astro/{gitGraphDiagram-PVQCEYII.C6iphq1x.js → gitGraphDiagram-PVQCEYII.DoeJRBfY.js} +1 -1
  45. package/web/_astro/index.TPF1FerP.css +1 -0
  46. package/web/_astro/{infoDiagram-5YYISTIA.HXmDMhW4.js → infoDiagram-5YYISTIA.9npLJecW.js} +1 -1
  47. package/web/_astro/{ishikawaDiagram-YF4QCWOH.BSmW8NiU.js → ishikawaDiagram-YF4QCWOH.DXanvOr3.js} +1 -1
  48. package/web/_astro/{journeyDiagram-JHISSGLW.DEQow5fo.js → journeyDiagram-JHISSGLW.DxbVjxAv.js} +1 -1
  49. package/web/_astro/{kanban-definition-UN3LZRKU.IVm9cTdc.js → kanban-definition-UN3LZRKU.BDk6_iq0.js} +1 -1
  50. package/web/_astro/{linear.CrsM73_9.js → linear.C9cCDjT3.js} +1 -1
  51. package/web/_astro/{mermaid.core.CfBeDJls.js → mermaid.core.CpHRe5nH.js} +4 -4
  52. package/web/_astro/{mindmap-definition-RKZ34NQL.C3j60Y-0.js → mindmap-definition-RKZ34NQL.D-6C85bn.js} +1 -1
  53. package/web/_astro/{pieDiagram-4H26LBE5.B-aCMeEA.js → pieDiagram-4H26LBE5.B3Oxodty.js} +1 -1
  54. package/web/_astro/{quadrantDiagram-W4KKPZXB.Cib965yq.js → quadrantDiagram-W4KKPZXB.QZ_9Hshu.js} +1 -1
  55. package/web/_astro/{requirementDiagram-4Y6WPE33.D61cS4O-.js → requirementDiagram-4Y6WPE33.BimvuK53.js} +1 -1
  56. package/web/_astro/{sankeyDiagram-5OEKKPKP.GKF2qVPy.js → sankeyDiagram-5OEKKPKP.D5Kkpx2C.js} +1 -1
  57. package/web/_astro/{sequenceDiagram-3UESZ5HK.DZnq8F2h.js → sequenceDiagram-3UESZ5HK.B4YR6fAE.js} +1 -1
  58. package/web/_astro/{stateDiagram-AJRCARHV.DXUFmdgM.js → stateDiagram-AJRCARHV.Bt2wkZok.js} +1 -1
  59. package/web/_astro/{stateDiagram-v2-BHNVJYJU.BtHmhLEz.js → stateDiagram-v2-BHNVJYJU.YZ4X7dfQ.js} +1 -1
  60. package/web/_astro/{timeline-definition-PNZ67QCA.Cy-WW2ln.js → timeline-definition-PNZ67QCA.DmiTBL8w.js} +1 -1
  61. package/web/_astro/{vennDiagram-CIIHVFJN.SLp5b9KI.js → vennDiagram-CIIHVFJN.KNJV40mf.js} +1 -1
  62. package/web/_astro/{wardleyDiagram-YWT4CUSO.Bww45mWV.js → wardleyDiagram-YWT4CUSO.BVAaKgWH.js} +1 -1
  63. package/web/_astro/{xychartDiagram-2RQKCTM6.DR4swI6a.js → xychartDiagram-2RQKCTM6.D5Jx7zB2.js} +1 -1
  64. package/web/index.html +2 -2
  65. package/plugins/README.md +0 -656
  66. package/web/_astro/BoardApp.D-WlxiN2.js +0 -1
  67. package/web/_astro/channel.DGZaFHZx.js +0 -1
  68. package/web/_astro/index.DayyIngm.css +0 -1
package/spur.js CHANGED
@@ -39503,6 +39503,34 @@ var init_projects = __esm(() => {
39503
39503
  });
39504
39504
  });
39505
39505
 
39506
+ // ../../packages/config/src/slash-commands.ts
39507
+ import { homedir as homedir2 } from "os";
39508
+ import { join as join2 } from "path";
39509
+ function getSlashCommandsFilePath() {
39510
+ if (process.env.SPUR_SLASH_COMMANDS_FILE) {
39511
+ return process.env.SPUR_SLASH_COMMANDS_FILE;
39512
+ }
39513
+ return join2(homedir2(), ".config", "spur", "slash_commands.json");
39514
+ }
39515
+ var slashCommandCategorySchema, slashCommandItemSchema, slashCommandsFileSchema;
39516
+ var init_slash_commands = __esm(() => {
39517
+ init_zod();
39518
+ slashCommandCategorySchema = exports_external.enum(["git", "dev", "sys", "harness"]);
39519
+ slashCommandItemSchema = exports_external.object({
39520
+ name: exports_external.string().min(1),
39521
+ description: exports_external.string().min(1),
39522
+ category: slashCommandCategorySchema.default("dev"),
39523
+ argumentHint: exports_external.string().optional(),
39524
+ role: exports_external.string().optional(),
39525
+ source: exports_external.string().optional()
39526
+ });
39527
+ slashCommandsFileSchema = exports_external.object({
39528
+ version: exports_external.number().int().default(1),
39529
+ updatedAt: exports_external.string().optional(),
39530
+ commands: exports_external.array(slashCommandItemSchema).default([])
39531
+ });
39532
+ });
39533
+
39506
39534
  // ../../packages/config/src/index.ts
39507
39535
  function folderPathWithDefault(defaultPath) {
39508
39536
  return exports_external.preprocess((v) => v == null ? undefined : v, exports_external.string().default(defaultPath));
@@ -39608,6 +39636,7 @@ var init_src = __esm(() => {
39608
39636
  init_zod();
39609
39637
  init_finding_codes();
39610
39638
  init_projects();
39639
+ init_slash_commands();
39611
39640
  SPUR_ENV_VARS = {
39612
39641
  nodeEnv: "NODE_ENV",
39613
39642
  port: "PORT",
@@ -39789,6 +39818,7 @@ var init_src = __esm(() => {
39789
39818
  VersionCarrierEntrySchema = exports_external.looseObject({ type: exports_external.string().min(1) });
39790
39819
  BuilderBumpVerConfigSchema = exports_external.object({
39791
39820
  tagVersionSeparator: exports_external.string().min(1).default("-v"),
39821
+ aggregatePackage: exports_external.string().min(1).optional(),
39792
39822
  publishWorkflow: exports_external.string().min(1).default("publish.yml"),
39793
39823
  releaseCommitType: exports_external.string().min(1).default("chore"),
39794
39824
  releaseCommitScope: exports_external.string().min(1).default("release"),
@@ -39834,14 +39864,14 @@ var init_src = __esm(() => {
39834
39864
 
39835
39865
  // ../../packages/config/src/bundled-config.ts
39836
39866
  import { existsSync as existsSync3, readdirSync as readdirSync3, statSync as statSync4 } from "fs";
39837
- import { dirname as dirname3, join as join2 } from "path";
39867
+ import { dirname as dirname3, join as join3 } from "path";
39838
39868
  function bundledConfigRoot() {
39839
39869
  if (cachedRoot !== undefined)
39840
39870
  return cachedRoot;
39841
39871
  let dir = import.meta.dirname;
39842
39872
  while (true) {
39843
39873
  for (const name of BUNDLED_CONFIG_DIRS) {
39844
- const candidate = join2(dir, name);
39874
+ const candidate = join3(dir, name);
39845
39875
  if (isBundledConfigDir(candidate)) {
39846
39876
  cachedRoot = candidate;
39847
39877
  return cachedRoot;
@@ -39856,7 +39886,7 @@ function bundledConfigRoot() {
39856
39886
  return cachedRoot;
39857
39887
  }
39858
39888
  function isBundledConfigDir(candidate) {
39859
- return existsSync3(candidate) && statSync4(candidate).isDirectory() && existsSync3(join2(candidate, "rules")) && existsSync3(join2(candidate, "workflows"));
39889
+ return existsSync3(candidate) && statSync4(candidate).isDirectory() && existsSync3(join3(candidate, "rules")) && existsSync3(join3(candidate, "workflows"));
39860
39890
  }
39861
39891
  function listBundledConfigFiles() {
39862
39892
  const root = bundledConfigRoot();
@@ -39867,7 +39897,7 @@ function listBundledConfigFiles() {
39867
39897
  function walk(dir, relPrefix, filter = /\.(ya?ml|json)$/i) {
39868
39898
  const acc = [];
39869
39899
  for (const entry of readdirSync3(dir)) {
39870
- const abs = join2(dir, entry);
39900
+ const abs = join3(dir, entry);
39871
39901
  const rel = relPrefix.length > 0 ? `${relPrefix}/${entry}` : entry;
39872
39902
  if (statSync4(abs).isDirectory()) {
39873
39903
  acc.push(...walk(abs, rel, filter));
@@ -39905,7 +39935,7 @@ var init_template_renderer = () => {};
39905
39935
 
39906
39936
  // ../../packages/config/src/executor-update.ts
39907
39937
  import { chmod, lstat, open, readFile, rename, stat, unlink } from "fs/promises";
39908
- import { basename, dirname as dirname4, join as join3 } from "path";
39938
+ import { basename, dirname as dirname4, join as join4 } from "path";
39909
39939
  async function setProjectExecutorDisabled(projectRoot, executorName, disabled) {
39910
39940
  if (executorName.length === 0) {
39911
39941
  throw new ExecutorUpdateError("INVALID_CONFIG", "executor name must be a non-empty string");
@@ -39913,7 +39943,7 @@ async function setProjectExecutorDisabled(projectRoot, executorName, disabled) {
39913
39943
  if (typeof disabled !== "boolean") {
39914
39944
  throw new ExecutorUpdateError("INVALID_CONFIG", `disabled must be a boolean, received ${typeof disabled}`);
39915
39945
  }
39916
- const configPath = join3(projectRoot, ".spur", "config.yaml");
39946
+ const configPath = join4(projectRoot, ".spur", "config.yaml");
39917
39947
  let original;
39918
39948
  try {
39919
39949
  original = await stat(configPath);
@@ -39968,7 +39998,7 @@ async function setProjectExecutorDisabled(projectRoot, executorName, disabled) {
39968
39998
  }
39969
39999
  target.set("disabled", disabled);
39970
40000
  const serialized = String(doc2);
39971
- const tmpPath = join3(dirname4(configPath), `.${basename(configPath)}.tmp-${process.pid}`);
40001
+ const tmpPath = join4(dirname4(configPath), `.${basename(configPath)}.tmp-${process.pid}`);
39972
40002
  try {
39973
40003
  const latest = await stat(configPath);
39974
40004
  if (latest.mtimeMs !== baseline.mtimeMs || latest.size !== baseline.size || latest.ino !== baseline.ino) {
@@ -40056,8 +40086,8 @@ var init_executor_update = __esm(() => {
40056
40086
 
40057
40087
  // ../../packages/config/src/loader.ts
40058
40088
  import { existsSync as existsSync4, statSync as statSync5 } from "fs";
40059
- import { homedir as homedir2 } from "os";
40060
- import { dirname as dirname5, join as join4, resolve } from "path";
40089
+ import { homedir as homedir3 } from "os";
40090
+ import { dirname as dirname5, join as join5, resolve } from "path";
40061
40091
  import { fileURLToPath as fileURLToPath3 } from "url";
40062
40092
  function embeddedSchemasId(embeddedSchemas) {
40063
40093
  if (embeddedSchemas === undefined)
@@ -40080,7 +40110,7 @@ function cacheKey(configPath, opts, validateJsonSchema2) {
40080
40110
  }
40081
40111
  function resolveSchemaSpecifier(specifier, manifestSpecifier) {
40082
40112
  if (specifier === manifestSpecifier) {
40083
- const workspaceManifest = join4(LOADER_DIR, "..", "..", "..", "apps", "cli", "package.json");
40113
+ const workspaceManifest = join5(LOADER_DIR, "..", "..", "..", "apps", "cli", "package.json");
40084
40114
  if (existsSync4(workspaceManifest))
40085
40115
  return workspaceManifest;
40086
40116
  }
@@ -40095,7 +40125,7 @@ function resolveConfigLayers(cwd) {
40095
40125
  const layers = {};
40096
40126
  const skipProject = cwd === undefined && process.env.SPUR_SKIP_PROJECT_CONFIG === "true";
40097
40127
  if (!skipProject) {
40098
- const projectConfig = join4(cwd ?? process.cwd(), SPUR_CONFIG_DIR, SPUR_CONFIG_FILE);
40128
+ const projectConfig = join5(cwd ?? process.cwd(), SPUR_CONFIG_DIR, SPUR_CONFIG_FILE);
40099
40129
  if (existsSync4(projectConfig))
40100
40130
  layers.project = projectConfig;
40101
40131
  }
@@ -40168,9 +40198,9 @@ function assertNoGlobalFleetSection(globalRaw, layerPath, projectFile) {
40168
40198
  throw new Error(`agent.fleet is not supported in the global config (${layerPath ?? SPUR_CONFIG_FILE}). ` + `Declare the project fleet under agent.fleet in ${projectFile} \u2014 the flock belongs to a project, not to the machine.`);
40169
40199
  }
40170
40200
  function assertNoLegacyFleetFile(projectRoot, projectConfigPath) {
40171
- const candidates = new Set([join4(projectRoot, SPUR_CONFIG_DIR, "fleet.json")]);
40201
+ const candidates = new Set([join5(projectRoot, SPUR_CONFIG_DIR, "fleet.json")]);
40172
40202
  if (projectConfigPath !== undefined)
40173
- candidates.add(join4(dirname5(projectConfigPath), "fleet.json"));
40203
+ candidates.add(join5(dirname5(projectConfigPath), "fleet.json"));
40174
40204
  for (const legacyFile of candidates) {
40175
40205
  if (!existsSync4(legacyFile))
40176
40206
  continue;
@@ -40178,7 +40208,7 @@ function assertNoLegacyFleetFile(projectRoot, projectConfigPath) {
40178
40208
  }
40179
40209
  }
40180
40210
  function projectConfigReplacement(layers) {
40181
- return layers.project ?? join4(SPUR_CONFIG_DIR, SPUR_CONFIG_FILE);
40211
+ return layers.project ?? join5(SPUR_CONFIG_DIR, SPUR_CONFIG_FILE);
40182
40212
  }
40183
40213
  function schemaValidationContext(opts) {
40184
40214
  const embeddedSchemas = opts?.embeddedSchemas;
@@ -40433,7 +40463,7 @@ async function resolvePlanningFolders(fs2) {
40433
40463
  return promise2;
40434
40464
  }
40435
40465
  async function resolvePlanningFoldersUncached(fs2) {
40436
- const configPath = fs2.resolve(join4(SPUR_CONFIG_DIR, SPUR_CONFIG_FILE));
40466
+ const configPath = fs2.resolve(join5(SPUR_CONFIG_DIR, SPUR_CONFIG_FILE));
40437
40467
  if (!await fs2.exists(configPath))
40438
40468
  return defaultPlanningFolders();
40439
40469
  try {
@@ -40472,7 +40502,7 @@ var init_loader = __esm(() => {
40472
40502
  init_template_renderer();
40473
40503
  init_executor_update();
40474
40504
  init_projects();
40475
- GLOBAL_CONFIG_FILE = join4(homedir2(), ".config", "spur", "config.yaml");
40505
+ GLOBAL_CONFIG_FILE = join5(homedir3(), ".config", "spur", "config.yaml");
40476
40506
  LOADER_DIR = dirname5(fileURLToPath3(import.meta.url));
40477
40507
  embeddedSchemasIds = new WeakMap;
40478
40508
  spurConfigCache = new Map;
@@ -68002,7 +68032,7 @@ var init_schema3 = __esm(() => {
68002
68032
  });
68003
68033
 
68004
68034
  // ../../packages/domain/src/migrations.ts
68005
- import { join as join6 } from "path";
68035
+ import { join as join7 } from "path";
68006
68036
  async function allMigrationsJournaled(adapter, migrations) {
68007
68037
  if (migrations.length === 0)
68008
68038
  return true;
@@ -68068,6 +68098,7 @@ async function applyCliMigrations(adapter, migrations = CLI_MIGRATIONS) {
68068
68098
  const runsStatusDoneSkip = migration.id === "0017_spur_cli_runs_status_completed_to_done" && (!await tableExists(adapter, "runs") || !await columnExists(adapter, "runs", "status"));
68069
68099
  const historyBoardToolStatsColumnsSkip = migration.id === "0031_spur_cli_history_board_tool_stats_columns" && (!await tableExists(adapter, "history_board_tool_stats") || await columnExists(adapter, "history_board_tool_stats", "fresh_input_tokens"));
68070
68100
  const queueJobsActiveIndexSkip = migration.id === "0027_spur_cli_history_refresh_active_unique" && !await tableExists(adapter, "queue_jobs");
68101
+ const schedulerCustomActiveIndexSkip = migration.id === "0047_spur_cli_scheduler_custom_active_unique" && !await tableExists(adapter, "queue_jobs");
68071
68102
  const queueJobsDeadlineLeaseSkip = migration.id === "0041_spur_cli_queue_jobs_deadline_lease_columns" && !await tableExists(adapter, "queue_jobs");
68072
68103
  const inboxRequestKeySkip = migration.id === "0043_spur_cli_inbox_messages_request_key" && !await tableExists(adapter, "inbox_messages");
68073
68104
  const coordinationReceiptColumnsSkip = migration.id === "0044_spur_cli_coordination_runs_receipt_columns" && !await tableExists(adapter, "coordination_runs");
@@ -68076,7 +68107,7 @@ async function applyCliMigrations(adapter, migrations = CLI_MIGRATIONS) {
68076
68107
  const historyRollupWatermarkSkip = migration.id === "0036_spur_cli_history_rollup_watermark" && (await tableExists(adapter, "history_board_rollup_watermark") || !await tableExists(adapter, "history_message") || !await columnExists(adapter, "history_message", "imported_at"));
68077
68108
  const boundedRollupDerivationsSkip = migration.id === "0039_spur_cli_bounded_rollup_derivations" && (!await tableExists(adapter, "history_message") || !await columnExists(adapter, "history_message", "source_file") || !await tableExists(adapter, "history_board_source_daily"));
68078
68109
  const historyModelFallbackIndexSkip = migration.id === "0042_spur_cli_history_model_fallback_index" && (!await tableExists(adapter, "history_message") || !await columnExists(adapter, "history_message", "model"));
68079
- if (shouldApplySql && !sequenceIndexSkip && !argsRawSkip && !runsStatusDoneSkip && !nameOccurredIndexSkip && !historyBoardQueryIndexesSkip && !historyPerformanceIndexesSkip && !historyToolCallIndexesSkip && !historyBoardCoveringIndexesSkip && !historyBoardToolStatsColumnsSkip && !callIdSkip && !tsNullableSkip && !queueJobsActiveIndexSkip && !queueJobsDeadlineLeaseSkip && !inboxRequestKeySkip && !coordinationReceiptColumnsSkip && !historyToolIdentitySkip && !historyMeasureVectorSkip && !historyRollupWatermarkSkip && !boundedRollupDerivationsSkip && !historyModelFallbackIndexSkip) {
68110
+ if (shouldApplySql && !sequenceIndexSkip && !argsRawSkip && !runsStatusDoneSkip && !nameOccurredIndexSkip && !historyBoardQueryIndexesSkip && !historyPerformanceIndexesSkip && !historyToolCallIndexesSkip && !historyBoardCoveringIndexesSkip && !historyBoardToolStatsColumnsSkip && !callIdSkip && !tsNullableSkip && !queueJobsActiveIndexSkip && !schedulerCustomActiveIndexSkip && !queueJobsDeadlineLeaseSkip && !inboxRequestKeySkip && !coordinationReceiptColumnsSkip && !historyToolIdentitySkip && !historyMeasureVectorSkip && !historyRollupWatermarkSkip && !boundedRollupDerivationsSkip && !historyModelFallbackIndexSkip) {
68080
68111
  for (const statement of splitSqlStatements(migration.sql)) {
68081
68112
  await adapter.exec(statement);
68082
68113
  }
@@ -68114,7 +68145,7 @@ async function loadSqlMigrations(folder) {
68114
68145
  for (const entry of entries) {
68115
68146
  migrations.push({
68116
68147
  id: entry.replace(/\.sql$/, ""),
68117
- sql: await fs3.readFile(join6(folder, entry))
68148
+ sql: await fs3.readFile(join7(folder, entry))
68118
68149
  });
68119
68150
  }
68120
68151
  return migrations.length > 0 ? migrations : CLI_MIGRATIONS;
@@ -68189,6 +68220,15 @@ CREATE INDEX IF NOT EXISTS queue_jobs_ready_idx ON queue_jobs (status, next_retr
68189
68220
  -- jobs that legitimately have multiple active rows concurrently, so a global
68190
68221
  -- (type, status) unique index would break them.
68191
68222
  CREATE UNIQUE INDEX IF NOT EXISTS queue_jobs_history_refresh_active_unique ON queue_jobs (type) WHERE type = 'history.refresh' AND status IN ('pending', 'processing');
68223
+
68224
+ -- At most one ACTIVE configured-scheduler job per job NAME (task 0863): the tick
68225
+ -- reads findActiveSchedulerCustomJob and then inserts, which is not atomic across
68226
+ -- spur serve daemons sharing one project DB \u2014 every daemon's cron occurrence used
68227
+ -- to insert its own row (3-14 per tick on 2026-09-14/15). The name lives inside the
68228
+ -- JSON payload, so this is an expression index \u2014 scoped to scheduler.custom +
68229
+ -- active rows exactly like the 0716 history.refresh index, so terminal rows and
68230
+ -- other job types (task-action, feature-action, history.refresh) stay unaffected.
68231
+ CREATE UNIQUE INDEX IF NOT EXISTS queue_jobs_scheduler_custom_active_unique ON queue_jobs (json_extract(payload, '$.name')) WHERE type = 'scheduler.custom' AND status IN ('pending', 'processing');
68192
68232
  `, SYSTEM_EVENTS_SCHEMA_SQL = `
68193
68233
  CREATE TABLE IF NOT EXISTS system_events (
68194
68234
  id TEXT PRIMARY KEY,
@@ -68630,6 +68670,25 @@ CREATE TABLE IF NOT EXISTS agent_instances (
68630
68670
  CREATE INDEX IF NOT EXISTS idx_agent_instances_role ON agent_instances (role);
68631
68671
  CREATE INDEX IF NOT EXISTS idx_agent_instances_executor ON agent_instances (executor);
68632
68672
  CREATE INDEX IF NOT EXISTS idx_agent_instances_team ON agent_instances (team_id);
68673
+ `, SCHEDULER_CUSTOM_ACTIVE_UNIQUE_SCHEMA_SQL = `
68674
+ UPDATE queue_jobs
68675
+ SET status = 'failed',
68676
+ last_error = 'retired by migration 0047_spur_cli_scheduler_custom_active_unique: superseded duplicate active scheduler.custom job name',
68677
+ processing_at = NULL,
68678
+ updated_at = CAST(strftime('%s', 'now') AS INTEGER) * 1000
68679
+ WHERE type = 'scheduler.custom'
68680
+ AND status IN ('pending', 'processing')
68681
+ AND id NOT IN (
68682
+ SELECT id FROM (
68683
+ SELECT id, ROW_NUMBER() OVER (
68684
+ PARTITION BY json_extract(payload, '$.name') ORDER BY created_at ASC, id ASC
68685
+ ) AS rank
68686
+ FROM queue_jobs
68687
+ WHERE type = 'scheduler.custom' AND status IN ('pending', 'processing')
68688
+ ) WHERE rank = 1
68689
+ );
68690
+
68691
+ CREATE UNIQUE INDEX IF NOT EXISTS queue_jobs_scheduler_custom_active_unique ON queue_jobs (json_extract(payload, '$.name')) WHERE type = 'scheduler.custom' AND status IN ('pending', 'processing');
68633
68692
  `, HISTORY_REFRESH_ACTIVE_UNIQUE_SCHEMA_SQL = `
68634
68693
  UPDATE queue_jobs
68635
68694
  SET status = 'failed',
@@ -69031,6 +69090,10 @@ ${HISTORY_TASK_SESSION_SCHEMA_SQL}
69031
69090
  {
69032
69091
  id: "0046_spur_cli_project_strategy",
69033
69092
  sql: PROJECT_STRATEGY_SCHEMA_SQL
69093
+ },
69094
+ {
69095
+ id: "0047_spur_cli_scheduler_custom_active_unique",
69096
+ sql: SCHEDULER_CUSTOM_ACTIVE_UNIQUE_SCHEMA_SQL
69034
69097
  }
69035
69098
  ];
69036
69099
  });
@@ -70769,7 +70832,7 @@ var init_envelope = __esm(() => {
70769
70832
 
70770
70833
  // ../../packages/domain/src/retention.ts
70771
70834
  import { readdirSync as readdirSync5, rmSync as rmSync3, statfsSync, statSync as statSync6 } from "fs";
70772
- import { join as join7 } from "path";
70835
+ import { join as join8 } from "path";
70773
70836
  async function estimateReclaimableBytes(db2) {
70774
70837
  try {
70775
70838
  const rows = await db2.queryAll("SELECT pageno, pagetype, ncell, payload, unused FROM dbstat ORDER BY pageno");
@@ -70837,7 +70900,7 @@ async function runRetention(db2, cwd, now = new Date) {
70837
70900
  result.queueJobs = await purgeQueueJobs(db2, now);
70838
70901
  result.ledgerRows = await purgeLedger(db2, now);
70839
70902
  result.backupFiles = pruneBackups(cwd, now);
70840
- result.compaction = await compactDatabase(db2, { dbPath: join7(cwd, ".spur", "spur.db"), now });
70903
+ result.compaction = await compactDatabase(db2, { dbPath: join8(cwd, ".spur", "spur.db"), now });
70841
70904
  return result;
70842
70905
  }
70843
70906
  async function purgeRuleEvalRuns(db2, now) {
@@ -70850,7 +70913,7 @@ async function purgeLedger(db2, now) {
70850
70913
  return purgeRows(db2, `DELETE FROM history_import_ledger WHERE imported_at < ?`, new Date(now.getTime() - LEDGER_RETENTION_DAYS * DAY_MS).toISOString());
70851
70914
  }
70852
70915
  function pruneBackups(cwd, now) {
70853
- const dir = join7(cwd, ".spur", "backups");
70916
+ const dir = join8(cwd, ".spur", "backups");
70854
70917
  let entries;
70855
70918
  try {
70856
70919
  entries = readdirSync5(dir);
@@ -70860,7 +70923,7 @@ function pruneBackups(cwd, now) {
70860
70923
  const cutoffMs = now.getTime() - BACKUP_RETENTION_DAYS * DAY_MS;
70861
70924
  let reclaimed = 0;
70862
70925
  for (const name of entries) {
70863
- const path9 = join7(dir, name);
70926
+ const path9 = join8(dir, name);
70864
70927
  try {
70865
70928
  const stat2 = statSync6(path9);
70866
70929
  if (!stat2.isFile() || stat2.mtimeMs >= cutoffMs)
@@ -70945,12 +71008,12 @@ var init_maintenance = __esm(() => {
70945
71008
 
70946
71009
  // ../../packages/domain/src/planning/locks.ts
70947
71010
  import { closeSync, fsyncSync, mkdirSync as mkdirSync4, openSync, readFileSync as readFileSync7, renameSync as renameSync3, unlinkSync, writeSync } from "fs";
70948
- import { dirname as dirname8, join as join8 } from "path";
71011
+ import { dirname as dirname8, join as join9 } from "path";
70949
71012
  function lockPathForEntity(folder, entityId) {
70950
- return join8(folder, `.${entityId}.lock`);
71013
+ return join9(folder, `.${entityId}.lock`);
70951
71014
  }
70952
71015
  function createLockPath(folder) {
70953
- return join8(folder, ".create.lock");
71016
+ return join9(folder, ".create.lock");
70954
71017
  }
70955
71018
  function parseLockContent(raw) {
70956
71019
  const str = typeof raw === "string" ? raw : raw.toString();
@@ -71055,7 +71118,7 @@ function fsyncPath(path9, swallowErrors = false) {
71055
71118
  function atomicWrite(dest, content, entityId, fs3, projectName = "spur") {
71056
71119
  const dir = dirname8(dest);
71057
71120
  const rand = Math.random().toString(36).slice(2, 10);
71058
- const tempPath = join8(dir, `.${projectName}.${entityId}.${rand}.tmp`);
71121
+ const tempPath = join9(dir, `.${projectName}.${entityId}.${rand}.tmp`);
71059
71122
  fs3.writeFile(tempPath, content);
71060
71123
  fsyncPath(tempPath);
71061
71124
  renameSync3(tempPath, dest);
@@ -71064,7 +71127,7 @@ function atomicWrite(dest, content, entityId, fs3, projectName = "spur") {
71064
71127
  async function atomicWriteAsync(dest, content, entityId, fs3, projectName = "spur") {
71065
71128
  const dir = dirname8(dest);
71066
71129
  const rand = Math.random().toString(36).slice(2, 10);
71067
- const tempPath = join8(dir, `.${projectName}.${entityId}.${rand}.tmp`);
71130
+ const tempPath = join9(dir, `.${projectName}.${entityId}.${rand}.tmp`);
71068
71131
  await fs3.writeFile(tempPath, content);
71069
71132
  fsyncPath(tempPath);
71070
71133
  renameSync3(tempPath, dest);
@@ -71079,7 +71142,7 @@ function cleanupTempFiles(folder, entityId, fs3, projectName = "spur") {
71079
71142
  let count3 = 0;
71080
71143
  for (const entry of entries) {
71081
71144
  if (entry.startsWith(prefix) && entry.endsWith(suffix)) {
71082
- fs3.deleteFile(join8(folder, entry));
71145
+ fs3.deleteFile(join9(folder, entry));
71083
71146
  count3++;
71084
71147
  }
71085
71148
  }
@@ -71660,6 +71723,7 @@ var init_schema6 = __esm(() => {
71660
71723
  done_forced: exports_external.preprocess((v) => typeof v === "string" ? v === "true" : v, exports_external.boolean().optional()),
71661
71724
  done_reason: exports_external.string().optional(),
71662
71725
  feature_link_declined: exports_external.preprocess((v) => typeof v === "string" ? v === "true" : v, exports_external.boolean().optional()),
71726
+ estimate_hours: exports_external.preprocess((v) => typeof v === "string" && v.trim() !== "" ? Number(v) : v, exports_external.number().positive().optional()),
71663
71727
  created_at: isoDateString,
71664
71728
  updated_at: isoDateString
71665
71729
  });
@@ -71687,7 +71751,8 @@ var init_schema6 = __esm(() => {
71687
71751
  parent_wbs: wbsString,
71688
71752
  priority: exports_external.enum(PRIORITIES).optional(),
71689
71753
  tags: exports_external.array(exports_external.string()).optional(),
71690
- template: exports_external.enum(TASK_VARIANTS).optional()
71754
+ template: exports_external.enum(TASK_VARIANTS).optional(),
71755
+ estimate_hours: exports_external.number().positive().optional()
71691
71756
  }).strict();
71692
71757
  taskBatchSchema = exports_external.array(taskBatchItemSchema).min(1, "batch must contain at least one task");
71693
71758
  });
@@ -73012,7 +73077,7 @@ var init_escalation_packet = __esm(() => {
73012
73077
 
73013
73078
  // ../../packages/app/src/observability/escalation-packet-sink.ts
73014
73079
  import { randomUUID } from "crypto";
73015
- import { join as join9 } from "path";
73080
+ import { join as join10 } from "path";
73016
73081
  function parseDryRunProbeMetadata(metadataJson) {
73017
73082
  if (metadataJson === "")
73018
73083
  return false;
@@ -73059,7 +73124,7 @@ class EscalationPacketSink {
73059
73124
  }
73060
73125
  }
73061
73126
  async readPacket(runId) {
73062
- const path9 = join9(this.options.cwd, ".spur", "run", `${runId}-escalation.json`);
73127
+ const path9 = join10(this.options.cwd, ".spur", "run", `${runId}-escalation.json`);
73063
73128
  if (!await this.options.fs.exists(path9))
73064
73129
  return;
73065
73130
  return JSON.parse(await this.options.fs.readFile(path9));
@@ -73127,7 +73192,7 @@ class EscalationPacketSink {
73127
73192
  const identity6 = await this.resolveIdentity(wbs);
73128
73193
  const now2 = (this.options.now ?? (() => new Date().toISOString()))();
73129
73194
  const packet = buildEscalationPacket({ ...packetInput, identity: identity6, now: now2 });
73130
- const path9 = join9(this.options.cwd, ".spur", "run", `${input.runId}-escalation.json`);
73195
+ const path9 = join10(this.options.cwd, ".spur", "run", `${input.runId}-escalation.json`);
73131
73196
  if (!await this.options.fs.exists(path9)) {
73132
73197
  await this.options.fs.writeFile(path9, `${JSON.stringify(packet, null, 2)}
73133
73198
  `);
@@ -73418,7 +73483,7 @@ var init_step_reporter = () => {};
73418
73483
 
73419
73484
  // ../../packages/app/src/observability/workflow-run-log-sink.ts
73420
73485
  import { closeSync as closeSync2, mkdirSync as mkdirSync5, openSync as openSync2, writeSync as writeSync2 } from "fs";
73421
- import { join as join10 } from "path";
73486
+ import { join as join11 } from "path";
73422
73487
 
73423
73488
  class WorkflowRunLogSink {
73424
73489
  filePath;
@@ -73434,7 +73499,7 @@ class WorkflowRunLogSink {
73434
73499
  bus;
73435
73500
  handlers;
73436
73501
  constructor(options) {
73437
- this.filePath = join10(options.dir, `${options.runId}.log`);
73502
+ this.filePath = join11(options.dir, `${options.runId}.log`);
73438
73503
  this.maxBytes = options.maxBytes ?? DEFAULT_RUN_LOG_MAX_BYTES;
73439
73504
  this.maxLines = options.maxLines;
73440
73505
  this.planPreview = options.planPreview;
@@ -73889,7 +73954,7 @@ var init_failure_classification = __esm(() => {
73889
73954
  });
73890
73955
 
73891
73956
  // ../../packages/app/src/services/run-session-observer.ts
73892
- import { homedir as homedir3 } from "os";
73957
+ import { homedir as homedir4 } from "os";
73893
73958
  import { basename as basename2, isAbsolute as isAbsolute2, resolve as resolve3 } from "path";
73894
73959
 
73895
73960
  class RunSessionObserver {
@@ -73901,7 +73966,7 @@ class RunSessionObserver {
73901
73966
  now;
73902
73967
  constructor(options) {
73903
73968
  this.options = options;
73904
- this.home = options.home ?? homedir3();
73969
+ this.home = options.home ?? homedir4();
73905
73970
  this.cwd = options.cwd ?? process.cwd();
73906
73971
  this.now = options.now ?? Date.now;
73907
73972
  }
@@ -74113,7 +74178,7 @@ var init_run_session_observer = __esm(() => {
74113
74178
 
74114
74179
  // ../../packages/app/src/services/agent-service.ts
74115
74180
  import { createHash as createHash8 } from "crypto";
74116
- import { homedir as homedir4 } from "os";
74181
+ import { homedir as homedir5 } from "os";
74117
74182
  import { isatty as isatty3 } from "tty";
74118
74183
 
74119
74184
  class AgentService {
@@ -74531,7 +74596,7 @@ class AgentService {
74531
74596
  getDb,
74532
74597
  output: this.ctx.output,
74533
74598
  registry: this.sessionRootRegistry,
74534
- home: homedir4(),
74599
+ home: homedir5(),
74535
74600
  cwd: cwd || this.ctx.cwd,
74536
74601
  json: jsonOutput
74537
74602
  }));
@@ -75635,15 +75700,15 @@ var init_agent_service = __esm(() => {
75635
75700
  // ../../packages/app/src/services/project-registry.ts
75636
75701
  import { existsSync as existsSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync8, realpathSync as realpathSync3, renameSync as renameSync4, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "fs";
75637
75702
  import { connect, createServer } from "net";
75638
- import { homedir as homedir5 } from "os";
75639
- import { dirname as dirname9, join as join11, resolve as resolve4 } from "path";
75703
+ import { homedir as homedir6 } from "os";
75704
+ import { dirname as dirname9, join as join12, resolve as resolve4 } from "path";
75640
75705
  function normalizeProjectPath(pathInput) {
75641
75706
  const trimmed = pathInput.trim();
75642
75707
  let expanded = trimmed;
75643
75708
  if (trimmed === "~") {
75644
- expanded = homedir5();
75709
+ expanded = homedir6();
75645
75710
  } else if (trimmed.startsWith("~/")) {
75646
- expanded = join11(homedir5(), trimmed.slice(2));
75711
+ expanded = join12(homedir6(), trimmed.slice(2));
75647
75712
  } else {
75648
75713
  expanded = resolve4(trimmed);
75649
75714
  }
@@ -75944,7 +76009,7 @@ var init_project_registry = __esm(() => {
75944
76009
  });
75945
76010
 
75946
76011
  // ../../packages/app/src/services/fleet-service.ts
75947
- import { basename as basename3, dirname as dirname10, join as join12 } from "path";
76012
+ import { basename as basename3, dirname as dirname10, join as join13 } from "path";
75948
76013
  function resolveMemberExecutor(params) {
75949
76014
  const { member, index: index2, label, agentConfig, roles, roster } = params;
75950
76015
  if (member.executor !== undefined) {
@@ -76154,7 +76219,7 @@ class FleetService {
76154
76219
  }
76155
76220
  const slug = await this.projectSlug(normalized);
76156
76221
  const config4 = await this.effectiveConfig();
76157
- const configDir = join12(normalized, ".spur", "agents");
76222
+ const configDir = join13(normalized, ".spur", "agents");
76158
76223
  const specs = await loadAgentSpecs(configDir);
76159
76224
  const projection = materializeRoster({
76160
76225
  slug,
@@ -76208,7 +76273,7 @@ class FleetService {
76208
76273
  }
76209
76274
  async assertLaunchGroundTruth(projectPath) {
76210
76275
  const expected = normalizeProjectPath(projectPath);
76211
- const expectedStorageRoot = join12(expected, ".spur");
76276
+ const expectedStorageRoot = join13(expected, ".spur");
76212
76277
  const cwd = normalizeProjectPath(process.cwd());
76213
76278
  const storageRoot = normalizeProjectPath(this.fs.resolve(".spur"));
76214
76279
  const databasePath = this.ctx.openDb ? await new ProjectClaimDao(await this.ctx.openDb(expected)).databasePath() : "";
@@ -76316,7 +76381,7 @@ var init_task_locator = __esm(() => {
76316
76381
  });
76317
76382
 
76318
76383
  // ../../packages/app/src/services/agent-coordination-service.ts
76319
- import { join as join13 } from "path";
76384
+ import { join as join14 } from "path";
76320
76385
 
76321
76386
  class AgentCoordinationService {
76322
76387
  ctx;
@@ -76324,7 +76389,7 @@ class AgentCoordinationService {
76324
76389
  orchestratorPromise;
76325
76390
  constructor(ctx) {
76326
76391
  this.ctx = ctx;
76327
- this.configDir = join13(ctx.cwd, ".spur", "agents");
76392
+ this.configDir = join14(ctx.cwd, ".spur", "agents");
76328
76393
  }
76329
76394
  async sendMessage(fromId, toId, body, replyTo, requestKey) {
76330
76395
  validateAgentId(toId);
@@ -76590,7 +76655,7 @@ class AgentCoordinationService {
76590
76655
  async resolveTaskFile(taskId) {
76591
76656
  const fs3 = this.ctx.fs;
76592
76657
  const { foldersConfig } = await resolvePlanningFolders(fs3);
76593
- const dirs = [...new Set([foldersConfig.active_folder, ...Object.keys(foldersConfig.folders)])].map((dir) => join13(this.ctx.cwd, dir));
76658
+ const dirs = [...new Set([foldersConfig.active_folder, ...Object.keys(foldersConfig.folders)])].map((dir) => join14(this.ctx.cwd, dir));
76594
76659
  return await TaskLocator.forDirs(fs3, dirs).findPathByWbs(taskId);
76595
76660
  }
76596
76661
  }
@@ -76942,7 +77007,7 @@ var init_agent_quota_updates = __esm(() => {
76942
77007
  });
76943
77008
 
76944
77009
  // ../../packages/app/src/services/anchor-qualifier.ts
76945
- import { basename as basename4, join as join14 } from "path";
77010
+ import { basename as basename4, join as join15 } from "path";
76946
77011
  async function resolveRepoRoot(projectRoot, hintDir) {
76947
77012
  if (projectRoot)
76948
77013
  return projectRoot;
@@ -77071,7 +77136,7 @@ async function qualifyAnchors(fs3, opts) {
77071
77136
  } catch {
77072
77137
  continue;
77073
77138
  }
77074
- const mdFiles = entries.filter((name) => name.endsWith(".md") && name !== "kanban.md").map((name) => join14(dir, name)).sort();
77139
+ const mdFiles = entries.filter((name) => name.endsWith(".md") && name !== "kanban.md").map((name) => join15(dir, name)).sort();
77075
77140
  for (const filePath of mdFiles) {
77076
77141
  let raw;
77077
77142
  try {
@@ -78293,7 +78358,7 @@ __export(exports_feature_check, {
78293
78358
  FeatureCheckService: () => FeatureCheckService,
78294
78359
  DEFAULT_FEATURE_MATRIX: () => DEFAULT_FEATURE_MATRIX
78295
78360
  });
78296
- import { dirname as dirname11, join as join15 } from "path";
78361
+ import { dirname as dirname11, join as join16 } from "path";
78297
78362
  function isGroupFeature(fm) {
78298
78363
  const tags = fm.tags;
78299
78364
  return Array.isArray(tags) && tags.includes("group") ? "group" : "standard";
@@ -78391,9 +78456,9 @@ function decodeVerdictRows(source, sectionName, required2) {
78391
78456
  function defaultVerdictRunDir(tasksDir) {
78392
78457
  const norm = tasksDir.replace(/\\/g, "/");
78393
78458
  if (/\/docs\/tasks\d*$/.test(norm) || /\/docs\/tasks$/.test(norm)) {
78394
- return join15(dirname11(dirname11(tasksDir)), ".spur", "run");
78459
+ return join16(dirname11(dirname11(tasksDir)), ".spur", "run");
78395
78460
  }
78396
- return join15(dirname11(tasksDir), ".spur", "run");
78461
+ return join16(dirname11(tasksDir), ".spur", "run");
78397
78462
  }
78398
78463
  function rowMatchesScenario(id, sc) {
78399
78464
  const stripped = id.replace(/^\[[^\]]*\]\s*/, "").replace(/^Scenario:\s*/i, "").replace(/^\[[^\]]*\]\s*/, "").trim();
@@ -78523,7 +78588,7 @@ var init_feature_check = __esm(() => {
78523
78588
  await this.checkOneActiveGoal(fm, featureId2, options.featuresDir, findings, options.asStatus);
78524
78589
  await this.checkChildrenLimit(featureId2, options.featuresDir, findings);
78525
78590
  }
78526
- const dogfoodDir = options?.dogfoodDir ?? (options?.featuresDir ? join15(dirname11(options.featuresDir), "dogfood") : undefined);
78591
+ const dogfoodDir = options?.dogfoodDir ?? (options?.featuresDir ? join16(dirname11(options.featuresDir), "dogfood") : undefined);
78527
78592
  const primaryTasksDir = options?.tasksDir ?? options?.tasksDirs?.[0];
78528
78593
  const runDir = options?.runDir ?? (primaryTasksDir ? defaultVerdictRunDir(primaryTasksDir) : undefined);
78529
78594
  const taskScanDirs = options?.tasksDirs && options.tasksDirs.length > 0 ? options.tasksDirs : options?.tasksDir ? [options.tasksDir] : [];
@@ -78954,7 +79019,7 @@ var init_feature_check = __esm(() => {
78954
79019
  });
78955
79020
 
78956
79021
  // ../../packages/app/src/services/task-check.ts
78957
- import { basename as basename5, dirname as dirname12, join as join16 } from "path";
79022
+ import { basename as basename5, dirname as dirname12, join as join17 } from "path";
78958
79023
  function isPlaceholderBody(body) {
78959
79024
  const stripped = body.replace(/<!--[\s\S]*?-->/g, "").replace(/^\s*>\s*TBD\s*$/gim, "").trim();
78960
79025
  return stripped.length === 0;
@@ -79214,7 +79279,7 @@ var init_task_check = __esm(() => {
79214
79279
  this.runL2(doc2, entry, findings, raw);
79215
79280
  this.runL3(doc2, entry, effectiveStatus, findings, isTransitionTarget);
79216
79281
  const tasksDir = dirname12(filePath);
79217
- const featuresDir = join16(dirname12(tasksDir), "features");
79282
+ const featuresDir = join17(dirname12(tasksDir), "features");
79218
79283
  await this.runL4(doc2, fm, effectiveStatus, findings, featuresDir, tasksDir, wbs);
79219
79284
  await this.runL4Rollup(doc2, wbs, effectiveStatus, findings, tasksDir);
79220
79285
  if (effectiveStatus !== "done" && effectiveStatus !== "cancelled") {
@@ -79781,7 +79846,7 @@ var init_task_check = __esm(() => {
79781
79846
  for (const cite of citations) {
79782
79847
  if (reported >= 5)
79783
79848
  break;
79784
- const abs = join16(projectRoot, cite.path);
79849
+ const abs = join17(projectRoot, cite.path);
79785
79850
  let exists2 = false;
79786
79851
  try {
79787
79852
  exists2 = await this.fs.exists(abs);
@@ -79865,7 +79930,7 @@ var init_task_check = __esm(() => {
79865
79930
  continue;
79866
79931
  }
79867
79932
  try {
79868
- const abs = join16(dir, name);
79933
+ const abs = join17(dir, name);
79869
79934
  const stat2 = await this.fs.stat(abs);
79870
79935
  if (stat2 !== null && stat2 !== undefined && stat2.isDirectory?.())
79871
79936
  stack.push(abs);
@@ -79960,7 +80025,7 @@ var init_task_check = __esm(() => {
79960
80025
  }
79961
80026
  async checkVerdictArtifact(wbs, tasksDir, findings) {
79962
80027
  const projectRoot = resolveProjectRootFromTasksDir(tasksDir);
79963
- const runDir = join16(projectRoot, ".spur", "run");
80028
+ const runDir = join17(projectRoot, ".spur", "run");
79964
80029
  const verdictPath = `${runDir}/${wbs}-verdict.json`;
79965
80030
  const outcome = await readVerifyVerdict(this.fs, verdictPath, wbs);
79966
80031
  if (outcome.kind === "missing")
@@ -80005,13 +80070,13 @@ var init_task_check = __esm(() => {
80005
80070
  });
80006
80071
 
80007
80072
  // ../../packages/app/src/services/corpus-check.ts
80008
- import { basename as basename6, dirname as dirname13, join as join17, relative, resolve as resolve5 } from "path";
80073
+ import { basename as basename6, dirname as dirname13, join as join18, relative, resolve as resolve5 } from "path";
80009
80074
  async function loadTaskMatrix(projectRoot) {
80010
80075
  const fs3 = createNodeFileSystem3(projectRoot);
80011
- const candidates = [join17(projectRoot, ".spur", "tasks", "section-matrix.yaml")];
80076
+ const candidates = [join18(projectRoot, ".spur", "tasks", "section-matrix.yaml")];
80012
80077
  const bundledRoot = bundledConfigRoot();
80013
80078
  if (bundledRoot !== null)
80014
- candidates.push(join17(bundledRoot, "tasks", "section-matrix.yaml"));
80079
+ candidates.push(join18(bundledRoot, "tasks", "section-matrix.yaml"));
80015
80080
  let matrixPath;
80016
80081
  for (const candidate of candidates) {
80017
80082
  if (await fs3.exists(candidate)) {
@@ -80054,7 +80119,7 @@ async function structuralSweep(projectRoot) {
80054
80119
  const wbs = fileName.match(/^(\d{4})_.+\.md$/)?.[1];
80055
80120
  if (wbs === undefined)
80056
80121
  continue;
80057
- const result = await taskService.check(join17(tasksDir, fileName), wbs);
80122
+ const result = await taskService.check(join18(tasksDir, fileName), wbs);
80058
80123
  for (const finding of result.findings) {
80059
80124
  findings.push({
80060
80125
  kind: "task",
@@ -80072,7 +80137,7 @@ async function structuralSweep(projectRoot) {
80072
80137
  const id = fileName.match(/^([A-Z][0-9]*)_.+\.md$/)?.[1];
80073
80138
  if (id === undefined)
80074
80139
  continue;
80075
- const result = await featureService.check(join17(featuresDir, fileName), id, {
80140
+ const result = await featureService.check(join18(featuresDir, fileName), id, {
80076
80141
  featuresDir,
80077
80142
  tasksDir: activeTasksDir,
80078
80143
  tasksDirs: taskDirs,
@@ -80101,7 +80166,7 @@ async function duplicateIds(cwd, taskDirs, featuresDir) {
80101
80166
  return [];
80102
80167
  }
80103
80168
  const pattern = kind === "task" ? /^(\d{4})_/ : /^([A-Z][0-9]*)_/;
80104
- return names.map((n2) => ({ m: n2.match(pattern), n: n2 })).filter((x) => x.m !== null).map((x) => ({ id: x.m[1], file: relative(cwd, join17(dir, x.n)), kind }));
80169
+ return names.map((n2) => ({ m: n2.match(pattern), n: n2 })).filter((x) => x.m !== null).map((x) => ({ id: x.m[1], file: relative(cwd, join18(dir, x.n)), kind }));
80105
80170
  };
80106
80171
  const all = [
80107
80172
  ...(await Promise.all(taskDirs.map((dir) => scan(dir, "task")))).flat(),
@@ -80155,7 +80220,7 @@ function resolveProjectRoot(cwd) {
80155
80220
  const fs3 = createNodeFileSystem3(cwd);
80156
80221
  let current = resolve5(cwd);
80157
80222
  while (true) {
80158
- if (fs3.exists(join17(current, ".spur", "config.yaml"))) {
80223
+ if (fs3.exists(join18(current, ".spur", "config.yaml"))) {
80159
80224
  return current;
80160
80225
  }
80161
80226
  const parent = dirname13(current);
@@ -80177,14 +80242,14 @@ function diskReader(cwd) {
80177
80242
  return {
80178
80243
  list: async (dir) => {
80179
80244
  try {
80180
- return (await fs3.readDir(join17(cwd, dir))).map((n2) => `${dir}/${n2}`);
80245
+ return (await fs3.readDir(join18(cwd, dir))).map((n2) => `${dir}/${n2}`);
80181
80246
  } catch {
80182
80247
  return [];
80183
80248
  }
80184
80249
  },
80185
80250
  read: async (path9) => {
80186
80251
  try {
80187
- return await fs3.readFile(join17(cwd, path9));
80252
+ return await fs3.readFile(join18(cwd, path9));
80188
80253
  } catch {
80189
80254
  return null;
80190
80255
  }
@@ -80344,7 +80409,7 @@ var init_corpus_check = __esm(() => {
80344
80409
  });
80345
80410
 
80346
80411
  // ../../packages/app/src/services/corpus-migrator.ts
80347
- import { join as join18, relative as relative2 } from "path";
80412
+ import { join as join19, relative as relative2 } from "path";
80348
80413
  function applyM1(data, flags) {
80349
80414
  const raw = data.status;
80350
80415
  if (typeof raw !== "string")
@@ -80644,7 +80709,7 @@ class CorpusMigrator {
80644
80709
  }
80645
80710
  async discoverFiles() {
80646
80711
  const entries = await this.fs.readDir(this.corpusDir);
80647
- return entries.filter((name) => name.endsWith(".md")).filter((name) => name !== "kanban.md").map((name) => join18(this.corpusDir, name)).sort();
80712
+ return entries.filter((name) => name.endsWith(".md")).filter((name) => name !== "kanban.md").map((name) => join19(this.corpusDir, name)).sort();
80648
80713
  }
80649
80714
  async migrateFile(filePath, dryRun, gitDates = new Map) {
80650
80715
  const flags = [];
@@ -86369,6 +86434,14 @@ var init_job_exclusion_guard = __esm(() => {
86369
86434
  });
86370
86435
 
86371
86436
  // ../../packages/app/src/services/scheduler-custom-job-service.ts
86437
+ function isSchedulerCustomActiveConflict(error51) {
86438
+ if (typeof error51 !== "object" || error51 === null)
86439
+ return false;
86440
+ const { code, message } = error51;
86441
+ if (typeof message !== "string" || !message.includes(SCHEDULER_CUSTOM_ACTIVE_INDEX))
86442
+ return false;
86443
+ return code === undefined || String(code).startsWith("SQLITE_CONSTRAINT");
86444
+ }
86372
86445
  function resolveSchedulerCustomTimeoutMs(env) {
86373
86446
  return normalizeLegacyTimeoutMs(env.SPUR_SCHEDULER_CUSTOM_TIMEOUT_MS, SCHEDULER_CUSTOM_TIMEOUT_MS);
86374
86447
  }
@@ -86408,7 +86481,8 @@ function validateSchedulerCustomJobPayload(raw) {
86408
86481
  async function handleSchedulerCustomJob(deps, job) {
86409
86482
  const payload = validateSchedulerCustomJobPayload(job.payload);
86410
86483
  if (activeJobs.has(payload.name)) {
86411
- throw new Error(`scheduler job "${payload.name}" is already running in this process; skipping duplicate execution`);
86484
+ deps.onDuplicate?.(payload.name);
86485
+ return;
86412
86486
  }
86413
86487
  const exclusiveKey = payload.exclusiveKey;
86414
86488
  if (exclusiveKey !== undefined) {
@@ -86444,7 +86518,7 @@ async function handleSchedulerCustomJob(deps, job) {
86444
86518
  releaseExclusiveJob(exclusiveKey, `scheduler job "${payload.name}"`);
86445
86519
  }
86446
86520
  }
86447
- var SCHEDULER_CUSTOM_JOB = "scheduler.custom", SCHEDULER_CUSTOM_TIMEOUT_MS = 600000, SCHEDULER_CUSTOM_MAX_OUTPUT = 1e6, activeJobs;
86521
+ var SCHEDULER_CUSTOM_JOB = "scheduler.custom", SCHEDULER_CUSTOM_ACTIVE_INDEX = "queue_jobs_scheduler_custom_active_unique", SCHEDULER_CUSTOM_TIMEOUT_MS = 600000, SCHEDULER_CUSTOM_MAX_OUTPUT = 1e6, activeJobs;
86448
86522
  var init_scheduler_custom_job_service = __esm(() => {
86449
86523
  init_execution_policy();
86450
86524
  init_job_exclusion_guard();
@@ -86854,8 +86928,8 @@ import {
86854
86928
  unlinkSync as unlinkSync2,
86855
86929
  writeFileSync as writeFileSync5
86856
86930
  } from "fs";
86857
- import { homedir as homedir6 } from "os";
86858
- import { dirname as dirname14, isAbsolute as isAbsolute3, join as join19, resolve as resolve6, sep } from "path";
86931
+ import { homedir as homedir7 } from "os";
86932
+ import { dirname as dirname14, isAbsolute as isAbsolute3, join as join20, resolve as resolve6, sep } from "path";
86859
86933
  function parseImporterVersion(version3) {
86860
86934
  if (version3 === undefined) {
86861
86935
  return null;
@@ -86910,7 +86984,7 @@ async function buildRefreshCoverage(db2, selector, fanOut) {
86910
86984
  async function runSessionAugmentedRoots(source, home, cwd, dao2) {
86911
86985
  const roots = getSourceDefinition(source).defaultRoots.map((root) => resolve6(home, root));
86912
86986
  const runRoots = [];
86913
- const runRoot = join19(cwd, ".spur", "run");
86987
+ const runRoot = join20(cwd, ".spur", "run");
86914
86988
  if (!existsSync6(runRoot))
86915
86989
  return { roots, runRoots };
86916
86990
  const sourcesByRun = new Map;
@@ -86920,7 +86994,7 @@ async function runSessionAugmentedRoots(source, home, cwd, dao2) {
86920
86994
  sourcesByRun.set(row.run_id, sources);
86921
86995
  }
86922
86996
  for (const runId of readdirSync6(runRoot)) {
86923
- const sessionsRoot = join19(runRoot, runId, "agent-sessions");
86997
+ const sessionsRoot = join20(runRoot, runId, "agent-sessions");
86924
86998
  if (!existsSync6(sessionsRoot))
86925
86999
  continue;
86926
87000
  const mappedSources = sourcesByRun.get(runId);
@@ -86929,7 +87003,7 @@ async function runSessionAugmentedRoots(source, home, cwd, dao2) {
86929
87003
  const soleMappedSource = mappedSources?.size === 1 && mappedSources.has(source);
86930
87004
  if (!directMatch && !soleMappedSource)
86931
87005
  continue;
86932
- const root = realpathSync4(join19(sessionsRoot, agent));
87006
+ const root = realpathSync4(join20(sessionsRoot, agent));
86933
87007
  roots.push(root);
86934
87008
  runRoots.push({ runId, root });
86935
87009
  }
@@ -86950,7 +87024,7 @@ class HistoryService {
86950
87024
  const startedAt = new Date().toISOString();
86951
87025
  const db2 = await this.ctx.getDb();
86952
87026
  const dao2 = new RunSessionDao(db2);
86953
- const discovery = parsedSource !== "opencode" && opts.file === undefined && opts.root === undefined ? await runSessionAugmentedRoots(parsedSource, this.ctx.historyHome ?? homedir6(), this.ctx.cwd ?? process.cwd(), dao2) : undefined;
87027
+ const discovery = parsedSource !== "opencode" && opts.file === undefined && opts.root === undefined ? await runSessionAugmentedRoots(parsedSource, this.ctx.historyHome ?? homedir7(), this.ctx.cwd ?? process.cwd(), dao2) : undefined;
86954
87028
  const result = parsedSource === "opencode" && opts.file === undefined && opts.root === undefined ? await runOpenCodeImport({
86955
87029
  db: db2,
86956
87030
  sourceDatabase: this.ctx.openCodeSourceDatabase,
@@ -87017,7 +87091,7 @@ class HistoryService {
87017
87091
  }
87018
87092
  async maintain(opts = {}) {
87019
87093
  const db2 = await this.ctx.getDb();
87020
- const dbPath = this.ctx.cwd ? join19(this.ctx.cwd, ".spur", "spur.db") : undefined;
87094
+ const dbPath = this.ctx.cwd ? join20(this.ctx.cwd, ".spur", "spur.db") : undefined;
87021
87095
  return maintainDatabase(db2, {
87022
87096
  ...opts,
87023
87097
  ...dbPath && opts.dbPath === undefined ? { dbPath } : {}
@@ -87525,9 +87599,9 @@ function resolveArtifactPaths(artifact, opts) {
87525
87599
  return { artifactPath: out, sidecarPath: `${out.replace(/\.json$/, "")}.errors.jsonl` };
87526
87600
  }
87527
87601
  const base2 = resolve6(opts.cwd, ".spur", "reports", "history");
87528
- const dateDir = join19(base2, artifact.generatedAt.slice(0, 10));
87602
+ const dateDir = join20(base2, artifact.generatedAt.slice(0, 10));
87529
87603
  const name = `analyze-${selectorDigest(artifact.selector)}`;
87530
- return { artifactPath: join19(dateDir, `${name}.json`), sidecarPath: join19(dateDir, `${name}.errors.jsonl`) };
87604
+ return { artifactPath: join20(dateDir, `${name}.json`), sidecarPath: join20(dateDir, `${name}.errors.jsonl`) };
87531
87605
  }
87532
87606
  function boundCoverage(coverage) {
87533
87607
  const overflow = [];
@@ -87550,7 +87624,7 @@ function boundCoverage(coverage) {
87550
87624
  }
87551
87625
  function updateLatestPointer(opts, artifactPath) {
87552
87626
  const base2 = resolve6(opts.cwd, ".spur", "reports", "history");
87553
- const latest = join19(base2, "latest.json");
87627
+ const latest = join20(base2, "latest.json");
87554
87628
  mkdirSync7(base2, { recursive: true });
87555
87629
  if (existsSync6(latest))
87556
87630
  unlinkSync2(latest);
@@ -87616,7 +87690,7 @@ function formatIssue(issue2) {
87616
87690
  return `${issue2.sourceFile}:${issue2.sourceLine}: ${issue2.reason}`;
87617
87691
  }
87618
87692
  function pruneReports(cwd, retentionDays = REPORT_RETENTION_DAYS, now2 = new Date) {
87619
- const dir = join19(cwd, ".spur", "reports", "history");
87693
+ const dir = join20(cwd, ".spur", "reports", "history");
87620
87694
  let entries;
87621
87695
  try {
87622
87696
  entries = readdirSync6(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
@@ -87634,7 +87708,7 @@ function pruneReports(cwd, retentionDays = REPORT_RETENTION_DAYS, now2 = new Dat
87634
87708
  if (entryDate >= cutoff)
87635
87709
  continue;
87636
87710
  try {
87637
- rmSync5(join19(dir, name), { recursive: true, force: true });
87711
+ rmSync5(join20(dir, name), { recursive: true, force: true });
87638
87712
  pruned.push(name);
87639
87713
  } catch {}
87640
87714
  }
@@ -87744,7 +87818,7 @@ function extractResolvedWorkflowFacts(workflow) {
87744
87818
  var init_composition_baseline = () => {};
87745
87819
 
87746
87820
  // ../../packages/app/src/workflow/workflow-resolver.ts
87747
- import { basename as basename8, isAbsolute as isAbsolute4, join as join20, resolve as resolve7 } from "path";
87821
+ import { basename as basename8, isAbsolute as isAbsolute4, join as join21, resolve as resolve7 } from "path";
87748
87822
  function registeredWorkflowPaths(config4) {
87749
87823
  const paths = config4?.workflows?.paths ?? [".spur/workflows/"];
87750
87824
  const bundledRoot = bundledConfigRoot();
@@ -87752,7 +87826,7 @@ function registeredWorkflowPaths(config4) {
87752
87826
  for (const path9 of paths) {
87753
87827
  if (path9.startsWith(BUNDLED_PATH_PREFIX)) {
87754
87828
  if (bundledRoot !== null)
87755
- expanded.push(join20(bundledRoot, path9.slice(BUNDLED_PATH_PREFIX.length)));
87829
+ expanded.push(join21(bundledRoot, path9.slice(BUNDLED_PATH_PREFIX.length)));
87756
87830
  } else {
87757
87831
  expanded.push(path9);
87758
87832
  }
@@ -87764,7 +87838,7 @@ function normalizeLayerPath(cwd, path9) {
87764
87838
  }
87765
87839
  function workflowLayers(opts) {
87766
87840
  const sharedRoot = opts.sharedRoot !== undefined ? opts.sharedRoot : bundledConfigRoot();
87767
- const sharedPath = sharedRoot !== null ? normalizeLayerPath(opts.cwd, join20(sharedRoot, "workflows")) : null;
87841
+ const sharedPath = sharedRoot !== null ? normalizeLayerPath(opts.cwd, join21(sharedRoot, "workflows")) : null;
87768
87842
  const layers = [];
87769
87843
  const seen = new Set;
87770
87844
  const add = (id, path9) => {
@@ -87773,7 +87847,7 @@ function workflowLayers(opts) {
87773
87847
  seen.add(path9);
87774
87848
  layers.push({ id, path: path9 });
87775
87849
  };
87776
- add("project", normalizeLayerPath(opts.cwd, join20(opts.cwd, ".spur", "workflows")));
87850
+ add("project", normalizeLayerPath(opts.cwd, join21(opts.cwd, ".spur", "workflows")));
87777
87851
  for (const entry of opts.registered) {
87778
87852
  if (sharedPath !== null && normalizeLayerPath(opts.cwd, entry) === sharedPath)
87779
87853
  continue;
@@ -87817,7 +87891,7 @@ async function scanWorkflowByName(cwd, name, registered) {
87817
87891
  try {
87818
87892
  const files = (await fs3.readDir(layer.path)).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
87819
87893
  for (const f of files) {
87820
- const abs = join20(layer.path, f);
87894
+ const abs = join21(layer.path, f);
87821
87895
  const wfName = await readWorkflowNameFast(fs3, abs);
87822
87896
  if (wfName === name) {
87823
87897
  return { path: abs, source: layer.id };
@@ -87859,16 +87933,16 @@ function resolveWorkflowFile(cwd, file2) {
87859
87933
  const bundledRoot = bundledConfigRoot();
87860
87934
  if (bundledRoot !== null) {
87861
87935
  const sharedName = isYaml ? base2 : `${base2}.yaml`;
87862
- const sharedPath = join20(bundledRoot, "workflows", sharedName);
87936
+ const sharedPath = join21(bundledRoot, "workflows", sharedName);
87863
87937
  if (fs3.exists(sharedPath)) {
87864
87938
  return { path: sharedPath, source: "shared" };
87865
87939
  }
87866
87940
  if (!isYaml) {
87867
- const sharedPipeline = join20(bundledRoot, "workflows", `${base2}-pipeline.yaml`);
87941
+ const sharedPipeline = join21(bundledRoot, "workflows", `${base2}-pipeline.yaml`);
87868
87942
  if (fs3.exists(sharedPipeline)) {
87869
87943
  return { path: sharedPipeline, source: "shared" };
87870
87944
  }
87871
- const sharedLiteral = join20(bundledRoot, "workflows", base2);
87945
+ const sharedLiteral = join21(bundledRoot, "workflows", base2);
87872
87946
  if (fs3.exists(sharedLiteral)) {
87873
87947
  return { path: sharedLiteral, source: "shared" };
87874
87948
  }
@@ -88272,7 +88346,7 @@ var init_tripwire = __esm(() => {
88272
88346
 
88273
88347
  // ../../packages/app/src/workflow/actions/agent-run.ts
88274
88348
  import { tmpdir } from "os";
88275
- import { dirname as dirname15, isAbsolute as isAbsolute5, join as join21 } from "path";
88349
+ import { dirname as dirname15, isAbsolute as isAbsolute5, join as join22 } from "path";
88276
88350
 
88277
88351
  class AgentRunActionRunner {
88278
88352
  observabilityBus;
@@ -88306,15 +88380,15 @@ class AgentRunActionRunner {
88306
88380
  let sessionDir = freshSession ? undefined : asOptionalString(context4.vars.__agentSessionDir);
88307
88381
  if (affinityOn) {
88308
88382
  if (!sessionDir || prevAgent && prevAgent !== targetAgentDir) {
88309
- sessionDir = join21(cwd, ".spur", "run", context4.runId, "agent-sessions", targetAgentDir);
88383
+ sessionDir = join22(cwd, ".spur", "run", context4.runId, "agent-sessions", targetAgentDir);
88310
88384
  }
88311
88385
  } else {
88312
88386
  if (!sessionDir) {
88313
- sessionDir = join21(cwd, ".spur", "run", context4.runId, "agent-sessions", targetAgentDir);
88387
+ sessionDir = join22(cwd, ".spur", "run", context4.runId, "agent-sessions", targetAgentDir);
88314
88388
  }
88315
88389
  }
88316
88390
  if (freshSession) {
88317
- sessionDir = join21(cwd, ".spur", "run", context4.runId, "agent-sessions", targetAgentDir, `fresh-${context4.stateOrNodeId}`);
88391
+ sessionDir = join22(cwd, ".spur", "run", context4.runId, "agent-sessions", targetAgentDir, `fresh-${context4.stateOrNodeId}`);
88318
88392
  }
88319
88393
  const storedSessionId = freshSession ? undefined : asOptionalString(context4.vars.__agentSessionId);
88320
88394
  let continueFlag = asOptionalBoolean(options.continue);
@@ -88406,7 +88480,7 @@ class AgentRunActionRunner {
88406
88480
  flags.requiresCapabilities = JSON.stringify(requiresCapabilities.requires);
88407
88481
  }
88408
88482
  if (expectFile !== undefined) {
88409
- const target = isAbsolute5(expectFile) ? expectFile : join21(cwd, expectFile);
88483
+ const target = isAbsolute5(expectFile) ? expectFile : join22(cwd, expectFile);
88410
88484
  const fs3 = createNodeFileSystem3(cwd);
88411
88485
  if (await fs3.exists(target)) {
88412
88486
  try {
@@ -88425,7 +88499,7 @@ class AgentRunActionRunner {
88425
88499
  return;
88426
88500
  return `
88427
88501
 
88428
- Write the required artifact to this absolute path: ${join21(cwd, rel)}`;
88502
+ Write the required artifact to this absolute path: ${join22(cwd, rel)}`;
88429
88503
  })();
88430
88504
  const dispatchInput = input === undefined || absoluteArtifactHint === undefined ? input : `${input}${absoluteArtifactHint}`;
88431
88505
  const observer = this.observabilityBus === undefined ? undefined : (event) => {
@@ -88579,13 +88653,13 @@ Write the required artifact to this absolute path: ${join21(cwd, rel)}`;
88579
88653
  }
88580
88654
  }
88581
88655
  if (capture && answerFile !== undefined) {
88582
- const target = isAbsolute5(answerFile) ? answerFile : join21(cwd, answerFile);
88656
+ const target = isAbsolute5(answerFile) ? answerFile : join22(cwd, answerFile);
88583
88657
  const fs3 = createNodeFileSystem3(cwd);
88584
88658
  await fs3.ensureDir(dirname15(target));
88585
88659
  await fs3.writeFile(target, answer);
88586
88660
  }
88587
88661
  if (ok && expectFile !== undefined) {
88588
- const target = isAbsolute5(expectFile) ? expectFile : join21(cwd, expectFile);
88662
+ const target = isAbsolute5(expectFile) ? expectFile : join22(cwd, expectFile);
88589
88663
  const fs3 = createNodeFileSystem3(cwd);
88590
88664
  if (!await fs3.exists(target)) {
88591
88665
  await writePartialWorkArtifact(context4, agentLabel, model, traced, cwd, sessionDir, this.agentConfig.secretValues);
@@ -88630,7 +88704,7 @@ ${traced.stdout ?? ""}`);
88630
88704
  const resolvedAgent = invocation?.agent ?? targetAgentDir;
88631
88705
  let resolvedSessionDir = sessionDir;
88632
88706
  if (ok && affinityOn && resolvedAgent !== targetAgentDir && !context4.vars.__agentSessionDir) {
88633
- resolvedSessionDir = join21(cwd, ".spur", "run", context4.runId, "agent-sessions", resolvedAgent);
88707
+ resolvedSessionDir = join22(cwd, ".spur", "run", context4.runId, "agent-sessions", resolvedAgent);
88634
88708
  }
88635
88709
  let discoveredSessionId = storedSessionId;
88636
88710
  if (ok && affinityOn && resolvedSessionDir && !discoveredSessionId) {
@@ -88638,7 +88712,7 @@ ${traced.stdout ?? ""}`);
88638
88712
  }
88639
88713
  if (ok && affinityOn && resolvedSessionDir) {
88640
88714
  try {
88641
- const sidecarPath = join21(cwd, ".spur", "run", `${context4.runId}-agent-session.json`);
88715
+ const sidecarPath = join22(cwd, ".spur", "run", `${context4.runId}-agent-session.json`);
88642
88716
  const fs3 = createNodeFileSystem3(cwd);
88643
88717
  await fs3.ensureDir(dirname15(sidecarPath));
88644
88718
  await fs3.writeFile(sidecarPath, JSON.stringify({
@@ -88687,7 +88761,7 @@ async function discoverSessionId(sessionDir) {
88687
88761
  let newestFile;
88688
88762
  let newestMtime = 0;
88689
88763
  for (const entry of jsonEntries) {
88690
- const fullPath = join21(sessionDir, entry);
88764
+ const fullPath = join22(sessionDir, entry);
88691
88765
  const st = await fs3.stat(fullPath);
88692
88766
  if (st?.isFile() && st.mtimeMs > newestMtime) {
88693
88767
  newestMtime = st.mtimeMs;
@@ -88783,7 +88857,7 @@ async function writePartialWorkArtifact(context4, agentLabel, model, traced, cwd
88783
88857
  const headerLine = model !== undefined ? `${agentLabel} (model: ${model})` : agentLabel;
88784
88858
  const inv = traced.invocation;
88785
88859
  const argvLine = redactAndBound(inv ? `${inv.command} ${inv.argv.join(" ")}` : "(invocation not captured)", secretValues, PARTIAL_ARTIFACT_TAIL_CHARS);
88786
- const latchedSessionPath = join21(cwd, ".spur", "run", `${context4.runId}-agent-session.json`);
88860
+ const latchedSessionPath = join22(cwd, ".spur", "run", `${context4.runId}-agent-session.json`);
88787
88861
  const resumeContext = [
88788
88862
  "",
88789
88863
  "## resume context",
@@ -88799,9 +88873,9 @@ async function writePartialWorkArtifact(context4, agentLabel, model, traced, cwd
88799
88873
  try {
88800
88874
  const fs4 = createNodeFileSystem3(cwd);
88801
88875
  const locator = TaskLocator.forDirs(fs4, [
88802
- join21(cwd, "docs", "tasks3"),
88803
- join21(cwd, "docs", "tasks2"),
88804
- join21(cwd, "docs", "tasks")
88876
+ join22(cwd, "docs", "tasks3"),
88877
+ join22(cwd, "docs", "tasks2"),
88878
+ join22(cwd, "docs", "tasks")
88805
88879
  ]);
88806
88880
  const hit = await locator.findByWbs(wbs);
88807
88881
  if (hit) {
@@ -88857,7 +88931,7 @@ async function writePartialWorkArtifact(context4, agentLabel, model, traced, cwd
88857
88931
  completedSection
88858
88932
  ].join(`
88859
88933
  `);
88860
- const target = join21(cwd, ".spur", "run", `${context4.runId}-${context4.stateOrNodeId}-partial.md`);
88934
+ const target = join22(cwd, ".spur", "run", `${context4.runId}-${context4.stateOrNodeId}-partial.md`);
88861
88935
  const fs3 = createNodeFileSystem3(cwd);
88862
88936
  await fs3.ensureDir(dirname15(target));
88863
88937
  await fs3.writeFile(target, body);
@@ -88879,7 +88953,7 @@ async function gitDiffStat(cwd) {
88879
88953
  }
88880
88954
  }
88881
88955
  async function createGitWorkingTreeSnapshot(cwd, excludeGlobs = ["docs/tasks3/*", "docs/features/*"]) {
88882
- const indexFile = join21(tmpdir(), `spur-implement-scope-${crypto.randomUUID()}.index`);
88956
+ const indexFile = join22(tmpdir(), `spur-implement-scope-${crypto.randomUUID()}.index`);
88883
88957
  const fs3 = createNodeFileSystem3(cwd);
88884
88958
  let keepIndex = false;
88885
88959
  try {
@@ -89010,9 +89084,9 @@ async function findOutOfScopeChanges(cwd, wbs, changed) {
89010
89084
  try {
89011
89085
  const fs3 = createNodeFileSystem3(cwd);
89012
89086
  const locator = TaskLocator.forDirs(fs3, [
89013
- join21(cwd, "docs", "tasks3"),
89014
- join21(cwd, "docs", "tasks2"),
89015
- join21(cwd, "docs", "tasks")
89087
+ join22(cwd, "docs", "tasks3"),
89088
+ join22(cwd, "docs", "tasks2"),
89089
+ join22(cwd, "docs", "tasks")
89016
89090
  ]);
89017
89091
  const hit = await locator.findByWbs(wbs);
89018
89092
  if (!hit)
@@ -89062,7 +89136,7 @@ var init_child_env = __esm(() => {
89062
89136
  });
89063
89137
 
89064
89138
  // ../../packages/app/src/workflow/actions/run-path.ts
89065
- import { dirname as dirname16, join as join22, normalize, relative as relative3, resolve as resolve8, sep as sep2 } from "path";
89139
+ import { dirname as dirname16, join as join23, normalize, relative as relative3, resolve as resolve8, sep as sep2 } from "path";
89066
89140
  function within(child, ancestor) {
89067
89141
  return child === ancestor || child.startsWith(`${ancestor}${sep2}`);
89068
89142
  }
@@ -89071,7 +89145,7 @@ async function resolveRunArtifactPath(fileSystem, workdir, pathRaw) {
89071
89145
  throw new RunArtifactPathError("physical path confinement requires a FileSystem with realPath support \u2014 refusing to skip confinement (0785 R2)");
89072
89146
  }
89073
89147
  const lexicalTarget = normalize(resolve8(workdir, pathRaw));
89074
- const lexicalRunRoot = normalize(join22(resolve8(workdir), ".spur", "run"));
89148
+ const lexicalRunRoot = normalize(join23(resolve8(workdir), ".spur", "run"));
89075
89149
  if (!lexicalTarget.startsWith(`${lexicalRunRoot}${sep2}`)) {
89076
89150
  throw new RunArtifactPathError(`must resolve beneath .spur/run/ (got ${pathRaw})`);
89077
89151
  }
@@ -89082,7 +89156,7 @@ async function resolveRunArtifactPath(fileSystem, workdir, pathRaw) {
89082
89156
  } catch (error51) {
89083
89157
  throw new RunArtifactPathError(`project workdir could not be canonicalized: ${error51.message}`);
89084
89158
  }
89085
- const canonicalRunRootCandidate = join22(canonicalWorkdir, ".spur", "run");
89159
+ const canonicalRunRootCandidate = join23(canonicalWorkdir, ".spur", "run");
89086
89160
  let canonicalRunRoot = canonicalRunRootCandidate;
89087
89161
  const rootStat = await fileSystem.stat(canonicalRunRootCandidate);
89088
89162
  if (rootStat !== null) {
@@ -89130,7 +89204,7 @@ async function resolveRunArtifactPath(fileSystem, workdir, pathRaw) {
89130
89204
  if (!within(anchor, canonicalWorkdir)) {
89131
89205
  throw new RunArtifactPathError(`ancestor ${probe} escapes the project workdir through a symlink`);
89132
89206
  }
89133
- const reconstructed = normalize(join22(anchor, relative3(probe, lexicalTarget)));
89207
+ const reconstructed = normalize(join23(anchor, relative3(probe, lexicalTarget)));
89134
89208
  if (!reconstructed.startsWith(`${canonicalRunRoot}${sep2}`)) {
89135
89209
  throw new RunArtifactPathError(`path escapes .spur/run/ through a symlink (got ${pathRaw})`);
89136
89210
  }
@@ -89147,7 +89221,7 @@ var init_run_path = __esm(() => {
89147
89221
  });
89148
89222
 
89149
89223
  // ../../packages/app/src/workflow/actions/command-gate.ts
89150
- import { join as join23, normalize as normalize2 } from "path";
89224
+ import { join as join24, normalize as normalize2 } from "path";
89151
89225
  function sleep2(ms) {
89152
89226
  return new Promise((resolve9) => setTimeout(resolve9, ms));
89153
89227
  }
@@ -89219,7 +89293,7 @@ class CommandGateActionRunner {
89219
89293
  const retryOn = Array.isArray(retry.on) ? retry.on : [];
89220
89294
  const timeoutMs = typeof options.timeoutMs === "number" ? options.timeoutMs : undefined;
89221
89295
  const env = childProcessEnv(context4.vars);
89222
- await this.fileSystem.ensureDir(normalize2(join23(normalized, "..")));
89296
+ await this.fileSystem.ensureDir(normalize2(join24(normalized, "..")));
89223
89297
  let lastStdout = "";
89224
89298
  let lastStderr = "";
89225
89299
  let lastExitCode = -1;
@@ -89876,7 +89950,7 @@ var init_http_request = __esm(() => {
89876
89950
  // ../../packages/app/src/workflow/proof-input-fingerprint.ts
89877
89951
  import crypto4 from "crypto";
89878
89952
  import { tmpdir as tmpdir2 } from "os";
89879
- import { dirname as dirname17, join as join24, resolve as resolve10, sep as sep4 } from "path";
89953
+ import { dirname as dirname17, join as join25, resolve as resolve10, sep as sep4 } from "path";
89880
89954
  function taskDocumentShapeError(content) {
89881
89955
  const doc2 = MarkdownDocument.parse(content, "task");
89882
89956
  const heading = CANONICAL_TASK_HEADING_RE.exec(doc2.bodyWithoutFrontmatter);
@@ -89951,7 +90025,7 @@ async function readProofInputContents(fileSystem = createNodeFileSystem3(), work
89951
90025
  };
89952
90026
  }
89953
90027
  async function createGitAlternateTree(cwd, excludeGlobs = DEFAULT_EXCLUDE_GLOBS, executor = new NodeProcessExecutor3, fs3 = createNodeFileSystem3()) {
89954
- const indexFile = join24(tmpdir2(), `spur-proof-fingerprint-${crypto4.randomUUID()}.index`);
90028
+ const indexFile = join25(tmpdir2(), `spur-proof-fingerprint-${crypto4.randomUUID()}.index`);
89955
90029
  try {
89956
90030
  await fs3.ensureDir(dirname17(indexFile));
89957
90031
  const env = Object.fromEntries(Object.entries(process.env).filter((entry) => entry[1] !== undefined));
@@ -90246,7 +90320,7 @@ var init_rule_check = __esm(() => {
90246
90320
  });
90247
90321
 
90248
90322
  // ../../packages/app/src/workflow/actions/run-artifact.ts
90249
- import { join as join25, resolve as resolve11 } from "path";
90323
+ import { join as join26, resolve as resolve11 } from "path";
90250
90324
 
90251
90325
  class RunArtifactActionRunner {
90252
90326
  getDb;
@@ -90524,7 +90598,7 @@ class RunArtifactActionRunner {
90524
90598
  error: `${KIND14}: proof.stages.review.status is "${String(review.status)}" \u2014 a skipped or stale review is not completed evidence (0785 R4)`
90525
90599
  };
90526
90600
  }
90527
- const markerPath = join25(resolve11(workdir), ".spur", "run", `${context4.runId}-review-proof.digest`);
90601
+ const markerPath = join26(resolve11(workdir), ".spur", "run", `${context4.runId}-review-proof.digest`);
90528
90602
  let marker = null;
90529
90603
  try {
90530
90604
  marker = await this.fileSystem.readFile(markerPath);
@@ -90868,7 +90942,7 @@ var init_checkpoint_contract = __esm(() => {
90868
90942
 
90869
90943
  // ../../packages/app/src/services/workflow-service.ts
90870
90944
  import { realpathSync as realpathSync5 } from "fs";
90871
- import { basename as basename9, dirname as dirname18, isAbsolute as isAbsolute6, join as join26, resolve as resolve12, sep as sep5 } from "path";
90945
+ import { basename as basename9, dirname as dirname18, isAbsolute as isAbsolute6, join as join27, resolve as resolve12, sep as sep5 } from "path";
90872
90946
  function signalSubprocess(pid) {
90873
90947
  if (!Number.isInteger(pid) || pid <= 1)
90874
90948
  return false;
@@ -91133,7 +91207,7 @@ class WorkflowAppService {
91133
91207
  };
91134
91208
  }
91135
91209
  async cleanRunLogs(retentionDays = 30, dryRun = false) {
91136
- const runDir = join26(this.ctx.cwd, ".spur", "run");
91210
+ const runDir = join27(this.ctx.cwd, ".spur", "run");
91137
91211
  const fs3 = createNodeFileSystem3();
91138
91212
  const cutoffMs = Date.now() - retentionDays * 24 * 60 * 60 * 1000;
91139
91213
  const reclaimed = [];
@@ -91145,7 +91219,7 @@ class WorkflowAppService {
91145
91219
  return { retentionDays, dryRun, reclaimed, failures };
91146
91220
  }
91147
91221
  for (const name of entries) {
91148
- const path9 = join26(runDir, name);
91222
+ const path9 = join27(runDir, name);
91149
91223
  const stat2 = await fs3.stat(path9);
91150
91224
  if (stat2 === null || !stat2.isFile() || stat2.mtimeMs >= cutoffMs)
91151
91225
  continue;
@@ -91164,7 +91238,7 @@ class WorkflowAppService {
91164
91238
  return { retentionDays, dryRun, reclaimed, failures };
91165
91239
  }
91166
91240
  async cleanCheckpoints(retentionDays = 30, dryRun = false) {
91167
- const sessionsDir = join26(this.ctx.cwd, ".spur", "memory", "sessions");
91241
+ const sessionsDir = join27(this.ctx.cwd, ".spur", "memory", "sessions");
91168
91242
  const fs3 = createNodeFileSystem3();
91169
91243
  let sessionsReal;
91170
91244
  try {
@@ -91184,7 +91258,7 @@ class WorkflowAppService {
91184
91258
  }
91185
91259
  const activeRunIds = new Set((await new RunDao(await this.ctx.getDb()).listActiveRuns()).map((r) => r.id));
91186
91260
  for (const name of entries) {
91187
- const path9 = join26(sessionsDir, name);
91261
+ const path9 = join27(sessionsDir, name);
91188
91262
  let resolved;
91189
91263
  try {
91190
91264
  resolved = realpathSync5(path9);
@@ -91384,7 +91458,7 @@ class WorkflowAppService {
91384
91458
  await new RunDao(db2).stampFailureReason(runId, result.reason);
91385
91459
  }
91386
91460
  async validateResumeCheckpointFreshness(runId, row, workdir) {
91387
- const sessionsDir = join26(workdir, ".spur", "memory", "sessions");
91461
+ const sessionsDir = join27(workdir, ".spur", "memory", "sessions");
91388
91462
  const fs3 = createNodeFileSystem3();
91389
91463
  let entries;
91390
91464
  try {
@@ -91418,7 +91492,7 @@ class WorkflowAppService {
91418
91492
  return head;
91419
91493
  };
91420
91494
  for (const name of entries) {
91421
- const filePath = join26(sessionsDir, name);
91495
+ const filePath = join27(sessionsDir, name);
91422
91496
  let raw;
91423
91497
  try {
91424
91498
  raw = await fs3.readFile(filePath);
@@ -91449,7 +91523,7 @@ class WorkflowAppService {
91449
91523
  const staleness = checkpointStaleness(meta3, {
91450
91524
  ...wbs !== undefined ? { taskWbs: wbs } : {},
91451
91525
  ...meta3.sourceCommit === "" ? {} : { sourceCommit: await resolveHead() },
91452
- artifactExists: (p) => fs3.exists(isAbsolute6(p) ? p : join26(workdir, p))
91526
+ artifactExists: (p) => fs3.exists(isAbsolute6(p) ? p : join27(workdir, p))
91453
91527
  });
91454
91528
  if (staleness.stale) {
91455
91529
  throw new Error(`Cannot resume run "${runId}": checkpoint is stale (${staleness.reason}).`);
@@ -91994,8 +92068,8 @@ function resolveOutputLogConfig(config4) {
91994
92068
  };
91995
92069
  }
91996
92070
  async function outputArtifactForRun(cwd, runId) {
91997
- const relative4 = join26(".spur", "run", `${runId}.log`);
91998
- return await fileExists(join26(cwd, relative4)) ? relative4 : undefined;
92071
+ const relative4 = join27(".spur", "run", `${runId}.log`);
92072
+ return await fileExists(join27(cwd, relative4)) ? relative4 : undefined;
91999
92073
  }
92000
92074
  async function scanWorkflowFiles(rootPath, source, loadEntry) {
92001
92075
  const entries = [];
@@ -92193,7 +92267,7 @@ function projectActionTraceResult(resultJson, secretValues = []) {
92193
92267
  async function partialArtifactForAction(cwd, runId, node, ok) {
92194
92268
  if (ok !== false || !TRACE_IDENTIFIER.test(runId) || !TRACE_IDENTIFIER.test(node))
92195
92269
  return;
92196
- const relativePath2 = join26(".spur", "run", `${runId}-${node}-partial.md`);
92270
+ const relativePath2 = join27(".spur", "run", `${runId}-${node}-partial.md`);
92197
92271
  return await fileExists(resolve12(cwd, relativePath2)) ? relativePath2 : undefined;
92198
92272
  }
92199
92273
  var TASK_PIPELINE_WORKFLOW = "task-pipeline", PIPELINE_LINK_KIND = "pipeline", COMPOSITION_CAPS, STRUCTURE_TOKENS, TRACE_IDENTIFIER, TRACE_RESULT_FIELDS, TRACE_INVOCATION_FIELDS;
@@ -92238,7 +92312,7 @@ var init_workflow_service = __esm(() => {
92238
92312
  });
92239
92313
 
92240
92314
  // ../../packages/app/src/services/inline-run-setup.ts
92241
- import { join as join27, resolve as resolve13 } from "path";
92315
+ import { join as join28, resolve as resolve13 } from "path";
92242
92316
  function parseIdentityMetadata(raw) {
92243
92317
  if (raw.trim() === "")
92244
92318
  return {};
@@ -92252,8 +92326,8 @@ function parseIdentityMetadata(raw) {
92252
92326
  }
92253
92327
  }
92254
92328
  async function openInlineRunProjectDb(workdir) {
92255
- const url2 = join27(resolve13(workdir), ".spur", "spur.db");
92256
- mkdirSync8(join27(url2, ".."), { recursive: true });
92329
+ const url2 = join28(resolve13(workdir), ".spur", "spur.db");
92330
+ mkdirSync8(join28(url2, ".."), { recursive: true });
92257
92331
  const adapter = await createMigratedDb({ url: url2 });
92258
92332
  return { adapter, close: () => adapter.close() };
92259
92333
  }
@@ -93205,8 +93279,8 @@ var init_project_start = __esm(() => {
93205
93279
  });
93206
93280
 
93207
93281
  // ../../packages/app/src/services/rule-service.ts
93208
- import { homedir as homedir7 } from "os";
93209
- import { delimiter, join as join28, relative as relative4, resolve as resolve15 } from "path";
93282
+ import { homedir as homedir8 } from "os";
93283
+ import { delimiter, join as join29, relative as relative4, resolve as resolve15 } from "path";
93210
93284
 
93211
93285
  class RuleService {
93212
93286
  context;
@@ -93401,13 +93475,13 @@ ${ruleIds.join(`
93401
93475
  const hasGlobalOverride = globalOverride !== undefined && globalOverride.length > 0;
93402
93476
  layers.push({
93403
93477
  id: "global",
93404
- path: hasGlobalOverride ? resolve15(cwd, globalOverride) : join28(homedir7(), GLOBAL_RULES_DIR),
93478
+ path: hasGlobalOverride ? resolve15(cwd, globalOverride) : join29(homedir8(), GLOBAL_RULES_DIR),
93405
93479
  priority: 10
93406
93480
  });
93407
93481
  if (opts.includeBundled && !hasGlobalOverride) {
93408
93482
  const bundledConfig = bundledConfigRoot();
93409
93483
  if (bundledConfig !== null) {
93410
- layers.push({ id: "bundled-config", path: join28(bundledConfig, "rules"), priority: 15 });
93484
+ layers.push({ id: "bundled-config", path: join29(bundledConfig, "rules"), priority: 15 });
93411
93485
  }
93412
93486
  const bundled = await bundledRulesRoot();
93413
93487
  if (bundled !== null)
@@ -93620,7 +93694,7 @@ ${ruleIds.join(`
93620
93694
  const { fs: fs3 } = this.context;
93621
93695
  for (const root of await this.ruleRoots()) {
93622
93696
  for (const ext of ["yaml", "yml", "json"]) {
93623
- if (await fs3.exists(join28(root, `${name}.${ext}`)))
93697
+ if (await fs3.exists(join29(root, `${name}.${ext}`)))
93624
93698
  return true;
93625
93699
  }
93626
93700
  }
@@ -93656,7 +93730,7 @@ ${ruleIds.join(`
93656
93730
  for (const layer of await this.existingRuleSourceLayers()) {
93657
93731
  const entries = await this.context.fs.readDir(layer.path);
93658
93732
  const named = entries.map((entry) => ({ entry, name: entry.match(/^([\w-]+)\.(?:ya?ml|json)$/i)?.[1] ?? "" })).filter(({ name }) => name.length > 0);
93659
- const stats = await Promise.all(named.map(({ entry }) => this.context.fs.stat(join28(layer.path, entry))));
93733
+ const stats = await Promise.all(named.map(({ entry }) => this.context.fs.stat(join29(layer.path, entry))));
93660
93734
  named.forEach(({ name }, i2) => {
93661
93735
  if (stats[i2]?.isFile())
93662
93736
  candidates.add(name);
@@ -93678,7 +93752,7 @@ ${ruleIds.join(`
93678
93752
  for (const layer of layers) {
93679
93753
  for (const file2 of await this.listRuleFilesInLayer(layer.path)) {
93680
93754
  if (!merged.has(file2))
93681
- merged.set(file2, { absolutePath: join28(layer.path, file2), source: layer.id });
93755
+ merged.set(file2, { absolutePath: join29(layer.path, file2), source: layer.id });
93682
93756
  }
93683
93757
  }
93684
93758
  const files = [];
@@ -93746,7 +93820,7 @@ ${ruleIds.join(`
93746
93820
  for (const entry of entries.sort()) {
93747
93821
  if (entry === "presets")
93748
93822
  continue;
93749
- const stat2 = await this.context.fs.stat(join28(root, entry));
93823
+ const stat2 = await this.context.fs.stat(join29(root, entry));
93750
93824
  if (stat2?.isDirectory())
93751
93825
  dirs.push(entry);
93752
93826
  }
@@ -93754,12 +93828,12 @@ ${ruleIds.join(`
93754
93828
  }
93755
93829
  async listRuleFiles(root, relativeDir) {
93756
93830
  const { fs: fs3 } = this.context;
93757
- const dir = join28(root, relativeDir);
93831
+ const dir = join29(root, relativeDir);
93758
93832
  const entries = await fs3.readDir(dir);
93759
93833
  const files = [];
93760
93834
  for (const entry of entries.sort()) {
93761
- const relativePath2 = relativeDir.length === 0 ? entry : join28(relativeDir, entry);
93762
- const absolutePath = join28(root, relativePath2);
93835
+ const relativePath2 = relativeDir.length === 0 ? entry : join29(relativeDir, entry);
93836
+ const absolutePath = join29(root, relativePath2);
93763
93837
  const stat2 = await fs3.stat(absolutePath);
93764
93838
  if (stat2?.isDirectory()) {
93765
93839
  files.push(...await this.listRuleFiles(root, relativePath2));
@@ -93893,8 +93967,8 @@ var init_rule_service = __esm(() => {
93893
93967
  init_dist10();
93894
93968
  init_agent_execution();
93895
93969
  init_system_event_envelope();
93896
- LOCAL_RULES_DIR = join28(".spur", "rules");
93897
- GLOBAL_RULES_DIR = join28(".config", "spur", "rules");
93970
+ LOCAL_RULES_DIR = join29(".spur", "rules");
93971
+ GLOBAL_RULES_DIR = join29(".config", "spur", "rules");
93898
93972
  SEVERITY_RANK2 = {
93899
93973
  info: 0,
93900
93974
  warning: 1,
@@ -94109,6 +94183,368 @@ var init_run_store_service = __esm(() => {
94109
94183
  };
94110
94184
  });
94111
94185
 
94186
+ // ../../packages/app/src/services/slash-commands-service.ts
94187
+ import { existsSync as existsSync8, mkdirSync as mkdirSync9, readdirSync as readdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
94188
+ import { homedir as homedir9 } from "os";
94189
+ import { basename as basename11, dirname as dirname19, join as join30, resolve as resolve16 } from "path";
94190
+ function getBuiltinClaudeCommands() {
94191
+ return BUILTIN_CLAUDE_COMMANDS.map((cmd) => ({ ...cmd }));
94192
+ }
94193
+ function deduceCommandCategory(nameOrStem) {
94194
+ const lower = nameOrStem.toLowerCase();
94195
+ if (/(git|commit|review|revert|pr-review|diff|branch|stash)/.test(lower)) {
94196
+ return "git";
94197
+ }
94198
+ if (/(rule|workflow|init|harness|spur|plan|run|verify|wrap|refine|parallel|idea)/.test(lower)) {
94199
+ return "harness";
94200
+ }
94201
+ if (/(doctor|cost|compact|clear|terminal|\bbug\b|config|login|logout|sys|status|ps)/.test(lower)) {
94202
+ return "sys";
94203
+ }
94204
+ return "dev";
94205
+ }
94206
+ function findPluginsSpCommandsDir(startDir = process.cwd()) {
94207
+ let current = resolve16(startDir);
94208
+ while (true) {
94209
+ try {
94210
+ const candidate = join30(current, "plugins", "sp", "commands");
94211
+ if (existsSync8(candidate)) {
94212
+ return candidate;
94213
+ }
94214
+ } catch {
94215
+ break;
94216
+ }
94217
+ const parent = dirname19(current);
94218
+ if (parent === current) {
94219
+ break;
94220
+ }
94221
+ current = parent;
94222
+ }
94223
+ return null;
94224
+ }
94225
+ function findProjectClaudeCommandsDir(startDir = process.cwd()) {
94226
+ let current = resolve16(startDir);
94227
+ while (true) {
94228
+ try {
94229
+ const candidate = join30(current, ".claude", "commands");
94230
+ if (existsSync8(candidate)) {
94231
+ return candidate;
94232
+ }
94233
+ } catch {
94234
+ break;
94235
+ }
94236
+ const parent = dirname19(current);
94237
+ if (parent === current) {
94238
+ break;
94239
+ }
94240
+ current = parent;
94241
+ }
94242
+ return null;
94243
+ }
94244
+ function parseMarkdownFrontmatter(rawContent) {
94245
+ const trimmed = rawContent.trimStart();
94246
+ if (!trimmed.startsWith("---")) {
94247
+ return { frontmatter: {}, body: rawContent };
94248
+ }
94249
+ const match = trimmed.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
94250
+ if (!match) {
94251
+ return { frontmatter: {}, body: rawContent };
94252
+ }
94253
+ try {
94254
+ const parsed = $parse(match[1] ?? "");
94255
+ return {
94256
+ frontmatter: typeof parsed === "object" && parsed !== null ? parsed : {},
94257
+ body: match[2] ?? ""
94258
+ };
94259
+ } catch {
94260
+ return { frontmatter: {}, body: rawContent };
94261
+ }
94262
+ }
94263
+ function scanMarkdownCommands(dirPath, options = {}) {
94264
+ if (!existsSync8(dirPath)) {
94265
+ return [];
94266
+ }
94267
+ const { prefix = "/", source = "plugin:sp", defaultCategory } = options;
94268
+ const items = [];
94269
+ try {
94270
+ const entries = readdirSync7(dirPath, { withFileTypes: true });
94271
+ for (const entry of entries) {
94272
+ if (!entry.isFile() || !entry.name.endsWith(".md")) {
94273
+ continue;
94274
+ }
94275
+ const fullPath = join30(dirPath, entry.name);
94276
+ const stem = basename11(entry.name, ".md");
94277
+ const fileContent = readFileSync10(fullPath, "utf-8");
94278
+ const { frontmatter, body } = parseMarkdownFrontmatter(fileContent);
94279
+ const cleanPrefix = prefix.endsWith(":") ? prefix : prefix.endsWith("/") ? prefix : `${prefix}:`;
94280
+ const name = prefix === "/" ? `/${stem}` : `${cleanPrefix}${stem}`;
94281
+ let description = "";
94282
+ if (typeof frontmatter.description === "string" && frontmatter.description.trim().length > 0) {
94283
+ description = frontmatter.description.trim();
94284
+ } else {
94285
+ const lines = body.split(`
94286
+ `).map((l) => l.trim()).filter((l) => l.length > 0);
94287
+ const firstHeading = lines.find((l) => l.startsWith("# "));
94288
+ const firstLine = lines[0];
94289
+ if (firstHeading) {
94290
+ description = firstHeading.replace(/^#\s+/, "").trim();
94291
+ } else if (firstLine !== undefined) {
94292
+ description = firstLine.replace(/^[#*-\s]+/, "").trim();
94293
+ } else {
94294
+ description = stem.replace(/[-_]/g, " ");
94295
+ }
94296
+ }
94297
+ let category;
94298
+ const fmCategory = typeof frontmatter.category === "string" ? frontmatter.category.toLowerCase() : "";
94299
+ if (fmCategory === "git" || fmCategory === "dev" || fmCategory === "sys" || fmCategory === "harness") {
94300
+ category = fmCategory;
94301
+ } else {
94302
+ category = defaultCategory ?? deduceCommandCategory(stem);
94303
+ }
94304
+ const hintRaw = frontmatter["argument-hint"] ?? frontmatter.argumentHint;
94305
+ const argumentHint = typeof hintRaw === "string" && hintRaw.trim().length > 0 ? hintRaw.trim() : undefined;
94306
+ const roleRaw = frontmatter.role;
94307
+ const role = typeof roleRaw === "string" && roleRaw.trim().length > 0 ? roleRaw.trim() : undefined;
94308
+ items.push({
94309
+ name,
94310
+ description,
94311
+ category,
94312
+ argumentHint,
94313
+ role,
94314
+ source
94315
+ });
94316
+ }
94317
+ } catch {}
94318
+ return items;
94319
+ }
94320
+ function generateSlashCommands(options = {}) {
94321
+ const {
94322
+ projectRoot = process.cwd(),
94323
+ includeBuiltin = true,
94324
+ includePluginSp = true,
94325
+ includeProjectClaude = true,
94326
+ includeUserClaude = true,
94327
+ includeUserSpur = true
94328
+ } = options;
94329
+ const commandMap = new Map;
94330
+ if (includeBuiltin) {
94331
+ for (const cmd of getBuiltinClaudeCommands()) {
94332
+ commandMap.set(cmd.name, cmd);
94333
+ }
94334
+ }
94335
+ if (includePluginSp) {
94336
+ const spCommandsDir = findPluginsSpCommandsDir(projectRoot) ?? resolve16(projectRoot, "plugins", "sp", "commands");
94337
+ const spCommands = scanMarkdownCommands(spCommandsDir, {
94338
+ prefix: "/sp:",
94339
+ source: "plugin:sp",
94340
+ defaultCategory: "harness"
94341
+ });
94342
+ for (const cmd of spCommands) {
94343
+ commandMap.set(cmd.name, cmd);
94344
+ }
94345
+ }
94346
+ if (includeProjectClaude) {
94347
+ const projectClaudeDir = findProjectClaudeCommandsDir(projectRoot) ?? resolve16(projectRoot, ".claude", "commands");
94348
+ const projectCommands = scanMarkdownCommands(projectClaudeDir, {
94349
+ prefix: "/",
94350
+ source: "claude:project"
94351
+ });
94352
+ for (const cmd of projectCommands) {
94353
+ commandMap.set(cmd.name, cmd);
94354
+ }
94355
+ }
94356
+ if (includeUserClaude) {
94357
+ const userClaudeDir = join30(homedir9(), ".claude", "commands");
94358
+ const userClaudeCommands = scanMarkdownCommands(userClaudeDir, {
94359
+ prefix: "/",
94360
+ source: "claude:user"
94361
+ });
94362
+ for (const cmd of userClaudeCommands) {
94363
+ commandMap.set(cmd.name, cmd);
94364
+ }
94365
+ }
94366
+ if (includeUserSpur) {
94367
+ const userSpurDir = join30(homedir9(), ".config", "spur", "commands");
94368
+ const userSpurCommands = scanMarkdownCommands(userSpurDir, {
94369
+ prefix: "/sp:",
94370
+ source: "spur:user",
94371
+ defaultCategory: "harness"
94372
+ });
94373
+ for (const cmd of userSpurCommands) {
94374
+ commandMap.set(cmd.name, cmd);
94375
+ }
94376
+ }
94377
+ return Array.from(commandMap.values());
94378
+ }
94379
+ function generateSlashCommandsFile(targetPath = getSlashCommandsFilePath(), options = {}) {
94380
+ const commands = generateSlashCommands(options);
94381
+ const filePayload = {
94382
+ version: 1,
94383
+ updatedAt: new Date().toISOString(),
94384
+ commands
94385
+ };
94386
+ try {
94387
+ const dir = dirname19(targetPath);
94388
+ if (!existsSync8(dir)) {
94389
+ mkdirSync9(dir, { recursive: true });
94390
+ }
94391
+ writeFileSync6(targetPath, JSON.stringify(filePayload, null, 2), "utf-8");
94392
+ } catch {}
94393
+ return {
94394
+ path: targetPath,
94395
+ count: commands.length,
94396
+ commands
94397
+ };
94398
+ }
94399
+ function loadSlashCommands(filePath = getSlashCommandsFilePath(), autoGenerateIfMissing = true, options = {}) {
94400
+ if (!existsSync8(filePath)) {
94401
+ if (autoGenerateIfMissing) {
94402
+ const generated = generateSlashCommandsFile(filePath, options);
94403
+ return generated.commands;
94404
+ }
94405
+ return getBuiltinClaudeCommands();
94406
+ }
94407
+ try {
94408
+ const content = readFileSync10(filePath, "utf-8");
94409
+ const raw = JSON.parse(content);
94410
+ const parsed = slashCommandsFileSchema.safeParse(raw);
94411
+ if (parsed.success && parsed.data.commands.length > 0) {
94412
+ return parsed.data.commands;
94413
+ }
94414
+ } catch {}
94415
+ if (autoGenerateIfMissing) {
94416
+ const generated = generateSlashCommandsFile(filePath, options);
94417
+ return generated.commands;
94418
+ }
94419
+ return getBuiltinClaudeCommands();
94420
+ }
94421
+
94422
+ class SlashCommandsService {
94423
+ filePath;
94424
+ projectRoot;
94425
+ cachedCommands = null;
94426
+ constructor(options = {}) {
94427
+ this.filePath = options.filePath ?? getSlashCommandsFilePath();
94428
+ this.projectRoot = options.projectRoot ?? process.cwd();
94429
+ }
94430
+ getFilePath() {
94431
+ return this.filePath;
94432
+ }
94433
+ getCommands(forceReload = false) {
94434
+ if (!this.cachedCommands || forceReload) {
94435
+ this.cachedCommands = loadSlashCommands(this.filePath, true, {
94436
+ projectRoot: this.projectRoot
94437
+ });
94438
+ }
94439
+ return this.cachedCommands;
94440
+ }
94441
+ generate(targetPath) {
94442
+ const dest = targetPath ?? this.filePath;
94443
+ const result = generateSlashCommandsFile(dest, {
94444
+ projectRoot: this.projectRoot
94445
+ });
94446
+ this.cachedCommands = result.commands;
94447
+ return result;
94448
+ }
94449
+ }
94450
+ var BUILTIN_CLAUDE_COMMANDS;
94451
+ var init_slash_commands_service = __esm(() => {
94452
+ init_src();
94453
+ init_dist2();
94454
+ BUILTIN_CLAUDE_COMMANDS = [
94455
+ {
94456
+ name: "/help",
94457
+ description: "View available commands, tools, and usage hints",
94458
+ category: "sys",
94459
+ source: "builtin"
94460
+ },
94461
+ {
94462
+ name: "/clear",
94463
+ description: "Clear conversation history and reset context window",
94464
+ category: "sys",
94465
+ source: "builtin"
94466
+ },
94467
+ {
94468
+ name: "/compact",
94469
+ description: "Purge execution trace and condense context window",
94470
+ category: "sys",
94471
+ source: "builtin"
94472
+ },
94473
+ {
94474
+ name: "/cost",
94475
+ description: "Inspect token breakdown and USD expenditure",
94476
+ category: "sys",
94477
+ source: "builtin"
94478
+ },
94479
+ {
94480
+ name: "/doctor",
94481
+ description: "Run diagnostic health checks on environment and tools",
94482
+ category: "sys",
94483
+ source: "builtin"
94484
+ },
94485
+ {
94486
+ name: "/init",
94487
+ description: "Initialize project instructions, settings and guidelines",
94488
+ category: "harness",
94489
+ source: "builtin"
94490
+ },
94491
+ {
94492
+ name: "/review",
94493
+ description: "Inspect staged git diff, verify test coverage & lint",
94494
+ category: "git",
94495
+ source: "builtin"
94496
+ },
94497
+ {
94498
+ name: "/commit",
94499
+ description: "Draft conventional commit message from staged hunks",
94500
+ category: "git",
94501
+ source: "builtin"
94502
+ },
94503
+ {
94504
+ name: "/terminal",
94505
+ description: "Run isolated bash execution in sandbox mirror",
94506
+ category: "sys",
94507
+ source: "builtin"
94508
+ },
94509
+ {
94510
+ name: "/revert",
94511
+ description: "Undo last agent file changes or restore git stash",
94512
+ category: "git",
94513
+ source: "builtin"
94514
+ },
94515
+ {
94516
+ name: "/resume",
94517
+ description: "Resume paused background agent run",
94518
+ category: "dev",
94519
+ source: "builtin"
94520
+ },
94521
+ {
94522
+ name: "/bug",
94523
+ description: "Report a bug or issue with diagnostic transcript",
94524
+ category: "sys",
94525
+ source: "builtin"
94526
+ },
94527
+ {
94528
+ name: "/config",
94529
+ description: "View or update local/global configuration settings",
94530
+ category: "sys",
94531
+ source: "builtin"
94532
+ },
94533
+ {
94534
+ name: "/login",
94535
+ description: "Authenticate account and refresh API credentials",
94536
+ category: "sys",
94537
+ source: "builtin"
94538
+ },
94539
+ {
94540
+ name: "/logout",
94541
+ description: "Sign out of current account and remove stored session",
94542
+ category: "sys",
94543
+ source: "builtin"
94544
+ }
94545
+ ];
94546
+ });
94547
+
94112
94548
  // ../../packages/app/src/services/write-slot-service.ts
94113
94549
  import { randomUUID as randomUUID2 } from "crypto";
94114
94550
 
@@ -94458,7 +94894,7 @@ var init_strategy_runtime = __esm(() => {
94458
94894
  });
94459
94895
 
94460
94896
  // ../../packages/app/src/services/supervisor-service.ts
94461
- import { dirname as dirname19 } from "path";
94897
+ import { dirname as dirname20 } from "path";
94462
94898
  function defaultWrapperArgv(agentId) {
94463
94899
  return {
94464
94900
  command: process.execPath,
@@ -94518,7 +94954,7 @@ class SupervisorService {
94518
94954
  }
94519
94955
  if (spec.tags.some((tag) => tag.startsWith("fleet:"))) {
94520
94956
  await new FleetService({
94521
- fs: createNodeFileSystem3(dirname19(dirname19(this.configDir)))
94957
+ fs: createNodeFileSystem3(dirname20(dirname20(this.configDir)))
94522
94958
  }).assertLaunchGroundTruth(spec.workspace);
94523
94959
  }
94524
94960
  const { command, args } = this.resolveCommand(spec);
@@ -94608,10 +95044,10 @@ class SupervisorService {
94608
95044
  proc.handle.kill("SIGTERM");
94609
95045
  let timedOut = false;
94610
95046
  const exitPromise = proc.handle.exited;
94611
- const timeout = new Promise((resolve16) => {
95047
+ const timeout = new Promise((resolve17) => {
94612
95048
  setTimeout(() => {
94613
95049
  timedOut = true;
94614
- resolve16();
95050
+ resolve17();
94615
95051
  }, 3000);
94616
95052
  });
94617
95053
  await Promise.race([exitPromise, timeout]);
@@ -95011,7 +95447,7 @@ async function* followSystemEventsAfter(getDb, opts) {
95011
95447
  await sleep3(FOLLOW_POLL_INTERVAL_MS);
95012
95448
  }
95013
95449
  }
95014
- var FOLLOW_POLL_INTERVAL_MS = 100, FOLLOW_BATCH_SIZE = 512, sleep3 = (ms) => new Promise((resolve16) => setTimeout(resolve16, ms));
95450
+ var FOLLOW_POLL_INTERVAL_MS = 100, FOLLOW_BATCH_SIZE = 512, sleep3 = (ms) => new Promise((resolve17) => setTimeout(resolve17, ms));
95015
95451
  var init_system_event_follow = __esm(() => {
95016
95452
  init_src2();
95017
95453
  });
@@ -95259,7 +95695,7 @@ var init_task_readiness = __esm(() => {
95259
95695
  });
95260
95696
 
95261
95697
  // ../../packages/app/src/services/task-scaffold.ts
95262
- import { dirname as dirname20 } from "path";
95698
+ import { dirname as dirname21 } from "path";
95263
95699
 
95264
95700
  class TaskScaffoldService {
95265
95701
  ctx;
@@ -95302,7 +95738,7 @@ class TaskScaffoldService {
95302
95738
  fileExists2 = false;
95303
95739
  }
95304
95740
  const mergeResult = mergeStubs(existingContent, stubs);
95305
- const dir = dirname20(targetFile);
95741
+ const dir = dirname21(targetFile);
95306
95742
  await this.ctx.fs.ensureDir(dir);
95307
95743
  if (mergeResult.created > 0 || !fileExists2) {
95308
95744
  await this.ctx.fs.writeFile(targetFile, mergeResult.content);
@@ -95384,7 +95820,7 @@ var init_task_size_precheck = __esm(() => {
95384
95820
  });
95385
95821
 
95386
95822
  // ../../packages/app/src/services/task-service.ts
95387
- import { dirname as dirname21, isAbsolute as isAbsolute7, join as join29, relative as relative5 } from "path";
95823
+ import { dirname as dirname22, isAbsolute as isAbsolute7, join as join31, relative as relative5 } from "path";
95388
95824
  function renderRosterTable(rows) {
95389
95825
  const header = `| WBS | Sub-task | Status |
95390
95826
  | --- | -------- | ------ |`;
@@ -95524,7 +95960,8 @@ ${templateBackground}`;
95524
95960
  ...input.featureId !== undefined ? { feature_id: input.featureId } : {},
95525
95961
  ...input.parentWbs !== undefined ? { parent_wbs: input.parentWbs } : {},
95526
95962
  ...input.priority !== undefined ? { priority: input.priority } : {},
95527
- ...input.tags !== undefined && input.tags.length > 0 ? { tags: input.tags } : {}
95963
+ ...input.tags !== undefined && input.tags.length > 0 ? { tags: input.tags } : {},
95964
+ ...input.estimateHours !== undefined ? { estimate_hours: input.estimateHours } : {}
95528
95965
  });
95529
95966
  let status;
95530
95967
  if (input.explicitStatus !== undefined) {
@@ -95624,7 +96061,8 @@ ${templateBackground}`;
95624
96061
  done_forced: true,
95625
96062
  done_reason: true,
95626
96063
  ac_numbering: true,
95627
- ac_altitude: true
96064
+ ac_altitude: true,
96065
+ estimate_hours: true
95628
96066
  };
95629
96067
  if (!(key2 in allowed)) {
95630
96068
  throw new Error(`Field "${key2}" is not settable via update; allowed: ${Object.keys(allowed).join(", ")}.`);
@@ -95874,8 +96312,8 @@ ${templateBackground}`;
95874
96312
  const featureId2 = fm.feature_id ?? fm["feature-id"];
95875
96313
  if (!featureId2 || featureId2.length === 0)
95876
96314
  return [];
95877
- const tasksDir = dirname21(taskFilePath);
95878
- const featuresDir = join29(tasksDir, "..", "features");
96315
+ const tasksDir = dirname22(taskFilePath);
96316
+ const featuresDir = join31(tasksDir, "..", "features");
95879
96317
  const featurePath = await (async () => {
95880
96318
  try {
95881
96319
  for (const name of await this.ctx.fs.readDir(featuresDir)) {
@@ -96019,7 +96457,8 @@ ${issues}`);
96019
96457
  plan: item.plan,
96020
96458
  acceptanceCriteria: item.acceptance_criteria,
96021
96459
  priority: item.priority,
96022
- tags: item.tags
96460
+ tags: item.tags,
96461
+ estimateHours: item.estimate_hours
96023
96462
  });
96024
96463
  try {
96025
96464
  this.validateCandidateOrThrow(item.name, shape);
@@ -96186,8 +96625,8 @@ ${block}` : block);
96186
96625
  return { wbs: hit.wbs, filePath: hit.filePath };
96187
96626
  if (opts.strict === true)
96188
96627
  return null;
96189
- const basename11 = filePath.split("/").pop() ?? "";
96190
- const capturedWbs = /^(\d{4})_.+\.md$/.exec(basename11)?.[1];
96628
+ const basename12 = filePath.split("/").pop() ?? "";
96629
+ const capturedWbs = /^(\d{4})_.+\.md$/.exec(basename12)?.[1];
96191
96630
  if (capturedWbs) {
96192
96631
  const taskPath = await this.locator.findPathByWbs(capturedWbs);
96193
96632
  if (taskPath !== null)
@@ -96659,8 +97098,8 @@ var init_task_verdict = __esm(() => {
96659
97098
  });
96660
97099
 
96661
97100
  // ../../packages/app/src/services/token-ledger-service.ts
96662
- import { closeSync as closeSync3, existsSync as existsSync8, fstatSync, openSync as openSync3, readSync } from "fs";
96663
- import { join as join30 } from "path";
97101
+ import { closeSync as closeSync3, existsSync as existsSync9, fstatSync, openSync as openSync3, readSync } from "fs";
97102
+ import { join as join32 } from "path";
96664
97103
  function isSparseToolActivity(events2) {
96665
97104
  return events2.length === 0 || !events2.some((e) => TOOL_ACTIVITY_TYPES.has(e.type));
96666
97105
  }
@@ -96711,7 +97150,7 @@ function parseLedgerLine(line) {
96711
97150
  }
96712
97151
  }
96713
97152
  function tailTokenLedgerFile(filePath, limit, chunkSize = DEFAULT_CHUNK, before) {
96714
- if (!existsSync8(filePath)) {
97153
+ if (!existsSync9(filePath)) {
96715
97154
  return { events: [], truncated: false };
96716
97155
  }
96717
97156
  const target = limit + 1;
@@ -96761,7 +97200,7 @@ class TokenLedgerService {
96761
97200
  ledgerPath;
96762
97201
  chunkSize;
96763
97202
  constructor(options) {
96764
- this.ledgerPath = options.ledgerPath ?? join30(options.cwd, TOKEN_LEDGER_RELATIVE_PATH);
97203
+ this.ledgerPath = options.ledgerPath ?? join32(options.cwd, TOKEN_LEDGER_RELATIVE_PATH);
96765
97204
  this.chunkSize = options.chunkSize ?? DEFAULT_CHUNK;
96766
97205
  }
96767
97206
  get path() {
@@ -96802,12 +97241,12 @@ class TokenLedgerService {
96802
97241
  var TOOL_ACTIVITY_TYPES, TOKEN_LEDGER_DEFAULT_LIMIT = 200, TOKEN_LEDGER_MAX_LIMIT = 1000, TOKEN_LEDGER_RELATIVE_PATH, DEFAULT_CHUNK;
96803
97242
  var init_token_ledger_service = __esm(() => {
96804
97243
  TOOL_ACTIVITY_TYPES = new Set(["read", "write", "bash", "grep", "glob"]);
96805
- TOKEN_LEDGER_RELATIVE_PATH = join30(".spur", "context", "token-ledger.jsonl");
97244
+ TOKEN_LEDGER_RELATIVE_PATH = join32(".spur", "context", "token-ledger.jsonl");
96806
97245
  DEFAULT_CHUNK = 64 * 1024;
96807
97246
  });
96808
97247
 
96809
97248
  // ../../packages/app/src/services/token-ledger-watcher.ts
96810
- import { closeSync as closeSync4, existsSync as existsSync9, fstatSync as fstatSync2, openSync as openSync4, readSync as readSync2, watch } from "fs";
97249
+ import { closeSync as closeSync4, existsSync as existsSync10, fstatSync as fstatSync2, openSync as openSync4, readSync as readSync2, watch } from "fs";
96811
97250
 
96812
97251
  class TokenLedgerWatcher {
96813
97252
  ledgerPath;
@@ -96828,7 +97267,7 @@ class TokenLedgerWatcher {
96828
97267
  this.started = true;
96829
97268
  this.offset = this.currentSize();
96830
97269
  try {
96831
- const target = existsSync9(this.ledgerPath) ? this.ledgerPath : this.parentDir();
97270
+ const target = existsSync10(this.ledgerPath) ? this.ledgerPath : this.parentDir();
96832
97271
  this.watcher = watch(target, () => this.schedulePoll());
96833
97272
  } catch {
96834
97273
  this.watcher = undefined;
@@ -96863,7 +97302,7 @@ class TokenLedgerWatcher {
96863
97302
  return idx > 0 ? this.ledgerPath.slice(0, idx) : ".";
96864
97303
  }
96865
97304
  currentSize() {
96866
- if (!existsSync9(this.ledgerPath))
97305
+ if (!existsSync10(this.ledgerPath))
96867
97306
  return 0;
96868
97307
  try {
96869
97308
  const fd = openSync4(this.ledgerPath, "r");
@@ -96885,7 +97324,7 @@ class TokenLedgerWatcher {
96885
97324
  }, this.debounceMs);
96886
97325
  }
96887
97326
  pollNewBytes() {
96888
- if (!existsSync9(this.ledgerPath)) {
97327
+ if (!existsSync10(this.ledgerPath)) {
96889
97328
  this.offset = 0;
96890
97329
  this.carry = "";
96891
97330
  return;
@@ -96930,7 +97369,7 @@ var init_token_ledger_watcher = __esm(() => {
96930
97369
  });
96931
97370
 
96932
97371
  // ../../packages/app/src/workflow/idea-handoff.ts
96933
- import { resolve as resolve16 } from "path";
97372
+ import { resolve as resolve17 } from "path";
96934
97373
  function processEvidence(r) {
96935
97374
  return `exit=${r.exitCode ?? "null"}${r.signal ? ` signal=${r.signal}` : ""}: ${r.stderr.trim() || "no stderr"}`;
96936
97375
  }
@@ -96940,12 +97379,12 @@ async function finalizeIdeaHandoff(options) {
96940
97379
  const executor = options.processExecutor ?? new NodeProcessExecutor3;
96941
97380
  const spurBin = options.spurBin ?? "spur";
96942
97381
  const { runId, featureId: featureId2 } = options;
96943
- const runDir = resolve16(root, ".spur", "run");
96944
- const batchPath = resolve16(runDir, `${runId}-idea-task-batch.json`);
96945
- const resultPath = resolve16(runDir, `${runId}-idea-batch-create-result.json`);
96946
- const orderPath = resolve16(runDir, `${runId}-idea-task-order.json`);
96947
- const readyPath = resolve16(runDir, `${runId}-idea-ready.json`);
96948
- const reportPath = resolve16(runDir, `${runId}-idea-handoff.md`);
97382
+ const runDir = resolve17(root, ".spur", "run");
97383
+ const batchPath = resolve17(runDir, `${runId}-idea-task-batch.json`);
97384
+ const resultPath = resolve17(runDir, `${runId}-idea-batch-create-result.json`);
97385
+ const orderPath = resolve17(runDir, `${runId}-idea-task-order.json`);
97386
+ const readyPath = resolve17(runDir, `${runId}-idea-ready.json`);
97387
+ const reportPath = resolve17(runDir, `${runId}-idea-handoff.md`);
96949
97388
  const spurSplit = splitLaunchCommand(spurBin, 'idea-handoff "spurBin"');
96950
97389
  if ("error" in spurSplit) {
96951
97390
  return {
@@ -97738,7 +98177,7 @@ var init_progress_follow = __esm(() => {
97738
98177
  });
97739
98178
 
97740
98179
  // ../../packages/app/src/workflow/trace-writer.ts
97741
- import { dirname as dirname22, join as join31 } from "path";
98180
+ import { dirname as dirname23, join as join33 } from "path";
97742
98181
 
97743
98182
  class WorkflowTraceWriter {
97744
98183
  path;
@@ -97746,7 +98185,7 @@ class WorkflowTraceWriter {
97746
98185
  pending = Promise.resolve();
97747
98186
  constructor(cwd, runId) {
97748
98187
  const safeRunId = runId.replace(/[^A-Za-z0-9._-]/g, "_");
97749
- this.path = join31(cwd, ".spur", "workflow", `${safeRunId}.jsonl`);
98188
+ this.path = join33(cwd, ".spur", "workflow", `${safeRunId}.jsonl`);
97750
98189
  }
97751
98190
  attach(bus) {
97752
98191
  bus.on("workflow.run.started", (event) => this.enqueue("workflow.run.started", event));
@@ -97772,7 +98211,7 @@ class WorkflowTraceWriter {
97772
98211
  `;
97773
98212
  this.pending = this.pending.then(async () => {
97774
98213
  const fs3 = createNodeFileSystem3();
97775
- await fs3.ensureDir(dirname22(this.path));
98214
+ await fs3.ensureDir(dirname23(this.path));
97776
98215
  await fs3.appendFile(this.path, line);
97777
98216
  });
97778
98217
  }
@@ -97867,6 +98306,7 @@ __export(exports_src2, {
97867
98306
  setPortProbeForTests: () => setPortProbeForTests,
97868
98307
  setDetachedServeSpawnForTests: () => setDetachedServeSpawnForTests,
97869
98308
  schedulerJobTimeoutEnvName: () => schedulerJobTimeoutEnvName,
98309
+ scanMarkdownCommands: () => scanMarkdownCommands,
97870
98310
  satisfies: () => satisfies,
97871
98311
  runIdeaHandoffCli: () => runIdeaHandoffCli,
97872
98312
  runHistoryReport: () => runHistoryReport,
@@ -97939,7 +98379,9 @@ __export(exports_src2, {
97939
98379
  normalizeLegacyTimeoutMs: () => normalizeLegacyTimeoutMs,
97940
98380
  materializeRoster: () => materializeRoster,
97941
98381
  looksLikeOpaqueId: () => looksLikeOpaqueId,
98382
+ loadSlashCommands: () => loadSlashCommands,
97942
98383
  isSystemEventEnvelopeV2: () => isSystemEventEnvelopeV2,
98384
+ isSchedulerCustomActiveConflict: () => isSchedulerCustomActiveConflict,
97943
98385
  isRecordAuthoredReview: () => isRecordAuthoredReview,
97944
98386
  isPortLive: () => isPortLive,
97945
98387
  isPortAvailable: () => isPortAvailable,
@@ -97957,7 +98399,10 @@ __export(exports_src2, {
97957
98399
  handleHistoryRefreshJob: () => handleHistoryRefreshJob,
97958
98400
  gtdStrategy: () => gtdStrategy,
97959
98401
  getLatestSystemEventSequence: () => getLatestSystemEventSequence,
98402
+ getBuiltinClaudeCommands: () => getBuiltinClaudeCommands,
97960
98403
  genericSystemEventCatalogEntry: () => genericSystemEventCatalogEntry,
98404
+ generateSlashCommandsFile: () => generateSlashCommandsFile,
98405
+ generateSlashCommands: () => generateSlashCommands,
97961
98406
  formatNoopMessage: () => formatNoopMessage,
97962
98407
  formatIssue: () => formatIssue,
97963
98408
  formatDenialMessage: () => formatDenialMessage,
@@ -97980,6 +98425,7 @@ __export(exports_src2, {
97980
98425
  encodeRunListCursor: () => encodeRunListCursor,
97981
98426
  drainPendingAgentQuotaUpdates: () => drainPendingAgentQuotaUpdates,
97982
98427
  deriveVerdict: () => deriveVerdict,
98428
+ deduceCommandCategory: () => deduceCommandCategory,
97983
98429
  decorateWorkflowEvent: () => decorateWorkflowEvent,
97984
98430
  decodeRunListCursor: () => decodeRunListCursor,
97985
98431
  decisionKindForGate: () => decisionKindForGate,
@@ -98042,6 +98488,7 @@ __export(exports_src2, {
98042
98488
  SystemEventEmitter: () => SystemEventEmitter,
98043
98489
  SupervisorService: () => SupervisorService,
98044
98490
  StrategyRuntime: () => StrategyRuntime,
98491
+ SlashCommandsService: () => SlashCommandsService,
98045
98492
  SectionMutationError: () => SectionMutationError,
98046
98493
  SYSTEM_EVENT_UNCATALOGED_WARN_EVENT: () => SYSTEM_EVENT_UNCATALOGED_WARN_EVENT,
98047
98494
  SYSTEM_EVENT_STREAMED_NAMES: () => SYSTEM_EVENT_STREAMED_NAMES,
@@ -98054,6 +98501,7 @@ __export(exports_src2, {
98054
98501
  STRATEGIES: () => STRATEGIES,
98055
98502
  SCHEDULER_CUSTOM_TIMEOUT_MS: () => SCHEDULER_CUSTOM_TIMEOUT_MS,
98056
98503
  SCHEDULER_CUSTOM_JOB: () => SCHEDULER_CUSTOM_JOB,
98504
+ SCHEDULER_CUSTOM_ACTIVE_INDEX: () => SCHEDULER_CUSTOM_ACTIVE_INDEX,
98057
98505
  RunStoreService: () => RunStoreService,
98058
98506
  RunStoreNotFoundError: () => RunStoreNotFoundError,
98059
98507
  RunStoreBadCursorError: () => RunStoreBadCursorError,
@@ -98129,6 +98577,7 @@ __export(exports_src2, {
98129
98577
  CommandGateActionRunner: () => CommandGateActionRunner,
98130
98578
  CHILD_KILL_GRACE_MS: () => CHILD_KILL_GRACE_MS,
98131
98579
  BusPlanningEventEmitter: () => BusPlanningEventEmitter,
98580
+ BUILTIN_CLAUDE_COMMANDS: () => BUILTIN_CLAUDE_COMMANDS,
98132
98581
  AgentService: () => AgentService,
98133
98582
  AgentRunActionRunner: () => AgentRunActionRunner,
98134
98583
  AgentExecutionLifecycle: () => AgentExecutionLifecycle,
@@ -98179,6 +98628,7 @@ var init_src3 = __esm(async () => {
98179
98628
  init_run_session_observer();
98180
98629
  init_run_store_service();
98181
98630
  init_scheduler_custom_job_service();
98631
+ init_slash_commands_service();
98182
98632
  init_strategy_runtime();
98183
98633
  init_supervisor_service();
98184
98634
  init_system_event_catch_all();
@@ -105979,10 +106429,10 @@ init_src2();
105979
106429
  init_dist7();
105980
106430
  init_dist5();
105981
106431
  await init_src3();
105982
- import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
106432
+ import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
105983
106433
  import { rm, writeFile } from "fs/promises";
105984
106434
  import { tmpdir as tmpdir3 } from "os";
105985
- import { dirname as dirname23, join as join32 } from "path";
106435
+ import { dirname as dirname24, join as join34 } from "path";
105986
106436
 
105987
106437
  // src/colors.ts
105988
106438
  var CODES = {
@@ -106397,6 +106847,11 @@ var spur_config_schema_default = {
106397
106847
  type: "object",
106398
106848
  description: "Release knobs for bump-ver/drop-tags. Defaults: -v separator, publish.yml workflow, chore(release) commit, gh run list limit 5.",
106399
106849
  properties: {
106850
+ aggregatePackage: {
106851
+ type: "string",
106852
+ minLength: 1,
106853
+ description: 'Package whose own release tag is the publish trigger for the aggregate (--all) path, as an unscoped package id (e.g. "spur" for @gobing-ai/spur). Default: the package whose full name equals the workspace root manifest name.'
106854
+ },
106400
106855
  tagVersionSeparator: {
106401
106856
  type: "string",
106402
106857
  minLength: 1,
@@ -106942,9 +107397,9 @@ init_src2();
106942
107397
  await init_src3();
106943
107398
 
106944
107399
  // src/workflow/resolve-spur-bin.ts
106945
- import { basename as basename11 } from "path";
107400
+ import { basename as basename12 } from "path";
106946
107401
  function resolveSpurBinFrom(launch) {
106947
- const runtime = basename11(launch.execPath).toLowerCase().replace(/\.exe$/, "");
107402
+ const runtime = basename12(launch.execPath).toLowerCase().replace(/\.exe$/, "");
106948
107403
  const isJsRuntime = runtime === "bun" || runtime === "node";
106949
107404
  if (isJsRuntime && launch.mainModule) {
106950
107405
  return `${launch.execPath} ${launch.mainModule}`;
@@ -107202,7 +107657,7 @@ ${result.content}`);
107202
107657
  'Sections replace, with one exception: `--section "Q&A"` APPENDS a timestamped',
107203
107658
  "`#### Q&A entry \u2014 <ISO>` block. Start the body with `<!-- qa:replace -->` to replace it wholesale."
107204
107659
  ].join(`
107205
- `)).option(...SHARED_OPTIONS.section).option(...SHARED_OPTIONS.fromFile).option(...SHARED_OPTIONS.featureFrontmatter).option(...SHARED_OPTIONS.prioritySet).option("--ac-numbering <mode>", "Set the ac_numbering frontmatter field (task-local) \u2014 opts the task into the Requirements\u2194AC coverage check").option("--ac-altitude <mode>", "Set the ac_altitude frontmatter field. Valid: `graduating` (default; feature-AC subset rule enforced) or `task-local` (skip the DD-09 subset rule \u2014 task scenarios are intentionally not feature ship criteria). Mirrors the L1 schema enum (packages/domain/src/planning/schema.ts:304).").option("--no-lifecycle", "Suppress lifecycle workflow run creation (use during pipeline runs to avoid orphaned lifecycle runs)").option("--assignee <spec-id>", "Set the assignee frontmatter field to an agent spec id").option("--force-done", "Allow transitioning to `done` even when the verify verdict is not PASS; records an override (task 0292). Waives the verdict only \u2014 the FSM path still applies, so from an earlier status walk the hops first: `todo` \u2192 `wip` \u2192 `testing` \u2192 `done` (each hop runs the structural `spur task check`)").option("--reason <text>", "Rationale for a forced-done override (paired with --force-done; persisted as done_reason)").option("--verdict-dir <path>", "Directory holding <wbs>-verdict.json artifacts (default: .spur/run)").option(...SHARED_OPTIONS.folderTasks).option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (wbs, status, options) => {
107660
+ `)).option(...SHARED_OPTIONS.section).option(...SHARED_OPTIONS.fromFile).option(...SHARED_OPTIONS.featureFrontmatter).option(...SHARED_OPTIONS.prioritySet).option("--ac-numbering <mode>", "Set the ac_numbering frontmatter field (task-local) \u2014 opts the task into the Requirements\u2194AC coverage check").option("--ac-altitude <mode>", "Set the ac_altitude frontmatter field. Valid: `graduating` (default; feature-AC subset rule enforced) or `task-local` (skip the DD-09 subset rule \u2014 task scenarios are intentionally not feature ship criteria). Mirrors the L1 schema enum (packages/domain/src/planning/schema.ts:304).").option("--estimate-hours <n>", "Set the estimate_hours frontmatter field (positive number) \u2014 decomposition size estimate; the inline pipeline driver dispatches no subagent at/below its floor").option("--no-lifecycle", "Suppress lifecycle workflow run creation (use during pipeline runs to avoid orphaned lifecycle runs)").option("--assignee <spec-id>", "Set the assignee frontmatter field to an agent spec id").option("--force-done", "Allow transitioning to `done` even when the verify verdict is not PASS; records an override (task 0292). Waives the verdict only \u2014 the FSM path still applies, so from an earlier status walk the hops first: `todo` \u2192 `wip` \u2192 `testing` \u2192 `done` (each hop runs the structural `spur task check`)").option("--reason <text>", "Rationale for a forced-done override (paired with --force-done; persisted as done_reason)").option("--verdict-dir <path>", "Directory holding <wbs>-verdict.json artifacts (default: .spur/run)").option(...SHARED_OPTIONS.folderTasks).option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (wbs, status, options) => {
107206
107661
  const svc = await makeService(context4, options.folder, options.lifecycle === false);
107207
107662
  try {
107208
107663
  if (options.assignee !== undefined) {
@@ -107240,9 +107695,9 @@ ${result.content}`);
107240
107695
  }
107241
107696
  context4.output.write(`Updated section '${options.section}' in task ${result.ref.id}`);
107242
107697
  }
107243
- } else if (options.feature !== undefined || options.priority !== undefined || options.acNumbering !== undefined || options.acAltitude !== undefined) {
107244
- const key2 = options.feature !== undefined ? "feature_id" : options.priority !== undefined ? "priority" : options.acNumbering !== undefined ? "ac_numbering" : "ac_altitude";
107245
- const value = options.feature ?? options.priority ?? options.acNumbering ?? options.acAltitude ?? "";
107698
+ } else if (options.feature !== undefined || options.priority !== undefined || options.acNumbering !== undefined || options.acAltitude !== undefined || options.estimateHours !== undefined) {
107699
+ const key2 = options.feature !== undefined ? "feature_id" : options.priority !== undefined ? "priority" : options.acNumbering !== undefined ? "ac_numbering" : options.acAltitude !== undefined ? "ac_altitude" : "estimate_hours";
107700
+ const value = options.feature ?? options.priority ?? options.acNumbering ?? options.acAltitude ?? options.estimateHours ?? "";
107246
107701
  const result = await svc.updateField(wbs, key2, value);
107247
107702
  if (options.json) {
107248
107703
  context4.output.write(toEnvelopeJson(result, { enveloped: options.jsonEnvelope }));
@@ -107272,7 +107727,7 @@ ${result.content}`);
107272
107727
  let forcedDoneVerdict;
107273
107728
  if (status === "done") {
107274
107729
  const current = await svc.show(wbs);
107275
- const verdictDir = options.verdictDir ?? join32(context4.cwd, ".spur", "run");
107730
+ const verdictDir = options.verdictDir ?? join34(context4.cwd, ".spur", "run");
107276
107731
  const loaded = await readVerdictArtifact(context4.fs, verdictDir, wbs);
107277
107732
  const guardOutcome = evaluateDoneTransition({
107278
107733
  wbs,
@@ -107334,7 +107789,7 @@ ${result.content}`);
107334
107789
  }
107335
107790
  }
107336
107791
  } else {
107337
- context4.output.error("Either <status>, --section/--from-file, or --feature/--priority is required");
107792
+ context4.output.error("Either <status>, --section/--from-file, or a field flag (--feature/--priority/--ac-numbering/--ac-altitude/--estimate-hours) is required");
107338
107793
  context4.setExitCode(2);
107339
107794
  }
107340
107795
  } catch (err) {
@@ -107508,7 +107963,7 @@ ${result.content}`);
107508
107963
  dryRun,
107509
107964
  projectRoot: context4.cwd,
107510
107965
  write: async (filePath, wbs, section, newBody) => {
107511
- const ref = { kind: "task", id: wbs, filePath, folder: dirname23(filePath) };
107966
+ const ref = { kind: "task", id: wbs, filePath, folder: dirname24(filePath) };
107512
107967
  const ws = new PlanningWriteService({ fs: context4.fs, emitter: makePlanningEmitter(context4) });
107513
107968
  await ws.updateSection(ref, section, newBody);
107514
107969
  }
@@ -107582,7 +108037,7 @@ ${result.content}`);
107582
108037
  agentSelector: options.agent,
107583
108038
  cwd: context4.cwd
107584
108039
  });
107585
- tmpPath = join32(tmpdir3(), `spur-ready-batch-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.json`);
108040
+ tmpPath = join34(tmpdir3(), `spur-ready-batch-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.json`);
107586
108041
  await writeFile(tmpPath, `${JSON.stringify(prepared.items, null, 2)}
107587
108042
  `);
107588
108043
  batchFile = tmpPath;
@@ -108015,15 +108470,15 @@ function loadTemplateBodies(projectRoot, variant) {
108015
108470
  if (cached2 !== undefined)
108016
108471
  return cached2;
108017
108472
  let bodies = {};
108018
- const localPath = join32(projectRoot, ".spur", "tasks", "templates", `${variant}.md`);
108019
- if (existsSync10(localPath)) {
108020
- bodies = extractTemplateBodies(readFileSync10(localPath, "utf8"));
108473
+ const localPath = join34(projectRoot, ".spur", "tasks", "templates", `${variant}.md`);
108474
+ if (existsSync11(localPath)) {
108475
+ bodies = extractTemplateBodies(readFileSync11(localPath, "utf8"));
108021
108476
  } else {
108022
108477
  const root = bundledConfigRoot();
108023
108478
  if (root !== null) {
108024
- const templatePath = join32(root, "templates", "task", `${variant}.md`);
108025
- if (existsSync10(templatePath)) {
108026
- bodies = extractTemplateBodies(readFileSync10(templatePath, "utf8"));
108479
+ const templatePath = join34(root, "templates", "task", `${variant}.md`);
108480
+ if (existsSync11(templatePath)) {
108481
+ bodies = extractTemplateBodies(readFileSync11(templatePath, "utf8"));
108027
108482
  }
108028
108483
  }
108029
108484
  }
@@ -108079,7 +108534,7 @@ async function loadSectionMatrixUncached(projectRoot) {
108079
108534
  }
108080
108535
  const root = bundledConfigRoot();
108081
108536
  if (root !== null) {
108082
- const matrixPath = join32(root, "tasks", "section-matrix.yaml");
108537
+ const matrixPath = join34(root, "tasks", "section-matrix.yaml");
108083
108538
  if (await fs3.exists(matrixPath)) {
108084
108539
  const data = await loadStructuredSpurConfig(matrixPath, {
108085
108540
  validateJsonSchema: true,
@@ -108090,7 +108545,7 @@ async function loadSectionMatrixUncached(projectRoot) {
108090
108545
  }
108091
108546
  throw new Error(`no canonical section-matrix found for task section authority (F92 R1); tried:
108092
108547
  ` + ` - ${localPath}
108093
- ` + (root !== null ? ` - ${join32(root, "tasks", "section-matrix.yaml")}
108548
+ ` + (root !== null ? ` - ${join34(root, "tasks", "section-matrix.yaml")}
108094
108549
  ` : "") + "copy/generate section-matrix.yaml from the canonical build-time matrix asset (repo `config` `tasks` tree) into one of those paths");
108095
108550
  }
108096
108551
 
@@ -108468,15 +108923,15 @@ function parseTimeout(raw) {
108468
108923
  return n3;
108469
108924
  }
108470
108925
  function loopSleep(ms, signal) {
108471
- return new Promise((resolve17) => {
108926
+ return new Promise((resolve18) => {
108472
108927
  if (signal?.aborted) {
108473
- resolve17();
108928
+ resolve18();
108474
108929
  return;
108475
108930
  }
108476
- const timer = setTimeout(resolve17, ms);
108931
+ const timer = setTimeout(resolve18, ms);
108477
108932
  signal?.addEventListener("abort", () => {
108478
108933
  clearTimeout(timer);
108479
- resolve17();
108934
+ resolve18();
108480
108935
  }, { once: true });
108481
108936
  });
108482
108937
  }
@@ -108796,13 +109251,13 @@ function waitFail(context4, options, code, message) {
108796
109251
  // src/release-ops.ts
108797
109252
  init_src();
108798
109253
  init_dist5();
108799
- import { existsSync as existsSync12, readdirSync as readdirSync7, readFileSync as readFileSync11 } from "fs";
108800
- import { join as join34 } from "path";
109254
+ import { existsSync as existsSync13, readdirSync as readdirSync8, readFileSync as readFileSync12 } from "fs";
109255
+ import { join as join36 } from "path";
108801
109256
 
108802
109257
  // src/version-carriers.ts
108803
109258
  init_zod();
108804
- import { existsSync as existsSync11 } from "fs";
108805
- import { join as join33 } from "path";
109259
+ import { existsSync as existsSync12 } from "fs";
109260
+ import { join as join35 } from "path";
108806
109261
  var defaultLiteralProbe = { file: "src/config.ts", identifier: "binaryVersion" };
108807
109262
  var registry2 = new Map;
108808
109263
  function registerCarrierType(type, def) {
@@ -108838,7 +109293,7 @@ function findLiteralProbe(carriers, packageDir) {
108838
109293
  return;
108839
109294
  }
108840
109295
  async function readJson(path9) {
108841
- if (!existsSync11(path9))
109296
+ if (!existsSync12(path9))
108842
109297
  return null;
108843
109298
  try {
108844
109299
  return JSON.parse(await Bun.file(path9).text());
@@ -108854,7 +109309,7 @@ registerCarrierType("plugin-manifest", {
108854
109309
  schema: PluginManifestCarrierSchema,
108855
109310
  async syncRepoWide(carrier, { repoRoot }, version3, staged, output2) {
108856
109311
  for (const rel of carrier.paths) {
108857
- const absPath = join33(repoRoot, rel);
109312
+ const absPath = join35(repoRoot, rel);
108858
109313
  const manifest = await readJson(absPath);
108859
109314
  if (manifest === null) {
108860
109315
  output2.write(` \u26A0 ${rel}: not found or malformed \u2014 skipping`);
@@ -108884,6 +109339,19 @@ var SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?(?:\+[0-9A-Za-z-.]+)?$/;
108884
109339
  function releaseTag(config4, version3) {
108885
109340
  return `${config4.packageName}${config4.tagVersionSeparator}${version3}`;
108886
109341
  }
109342
+ function resolveAggregatePackage(aggregatePackage, packages, rootName) {
109343
+ if (aggregatePackage !== undefined) {
109344
+ const config4 = packages.get(aggregatePackage);
109345
+ if (config4 === undefined) {
109346
+ throw new Error(`unknown builder.bump-ver.aggregatePackage "${aggregatePackage}". Package IDs: ${[...packages.keys()].join(", ")}`);
109347
+ }
109348
+ return config4;
109349
+ }
109350
+ return [...packages.values()].find((c3) => c3.packageName === rootName);
109351
+ }
109352
+ function resolveAggregateTag(ctx, version3) {
109353
+ return ctx.aggregate === undefined ? `${ctx.rootName}-v${version3}` : releaseTag(ctx.aggregate, version3);
109354
+ }
108887
109355
  async function run(repoRoot, cmd) {
108888
109356
  const executor = new NodeProcessExecutor3({ defaultTimeout: 30000, defaultMaxOutput: 512000 });
108889
109357
  const result = await executor.run({
@@ -108925,7 +109393,7 @@ async function readJson2(path9) {
108925
109393
  function rootWorkspaceGlobs(repoRoot) {
108926
109394
  let root;
108927
109395
  try {
108928
- root = JSON.parse(readFileSync11(join34(repoRoot, "package.json"), "utf8"));
109396
+ root = JSON.parse(readFileSync12(join36(repoRoot, "package.json"), "utf8"));
108929
109397
  } catch {
108930
109398
  return [];
108931
109399
  }
@@ -108942,11 +109410,11 @@ function workspaceDirs(repoRoot) {
108942
109410
  if (!glob.endsWith("/*"))
108943
109411
  continue;
108944
109412
  const parent = glob.slice(0, -2);
108945
- const parentAbs = join34(repoRoot, parent);
108946
- if (!existsSync12(parentAbs))
109413
+ const parentAbs = join36(repoRoot, parent);
109414
+ if (!existsSync13(parentAbs))
108947
109415
  continue;
108948
- for (const entry of readdirSync7(parentAbs, { withFileTypes: true })) {
108949
- if (entry.isDirectory() && existsSync12(join34(parentAbs, entry.name, "package.json"))) {
109416
+ for (const entry of readdirSync8(parentAbs, { withFileTypes: true })) {
109417
+ if (entry.isDirectory() && existsSync13(join36(parentAbs, entry.name, "package.json"))) {
108950
109418
  dirs.push(`${parent}/${entry.name}`);
108951
109419
  }
108952
109420
  }
@@ -108954,14 +109422,14 @@ function workspaceDirs(repoRoot) {
108954
109422
  return dirs;
108955
109423
  }
108956
109424
  async function releaseContext(repoRoot, bumpVerConfig = resolveBuilderBumpVerConfig(null)) {
108957
- const rootManifest = await readJson2(join34(repoRoot, "package.json"));
109425
+ const rootManifest = await readJson2(join36(repoRoot, "package.json"));
108958
109426
  if (rootManifest === null) {
108959
109427
  throw new Error(`no package.json found at ${repoRoot} \u2014 builder operates on a package workspace root.`);
108960
109428
  }
108961
109429
  const rootName = typeof rootManifest.name === "string" ? rootManifest.name : "root";
108962
109430
  const workspaces2 = [];
108963
109431
  for (const dir of workspaceDirs(repoRoot)) {
108964
- const manifest = await readJson2(join34(repoRoot, dir, "package.json"));
109432
+ const manifest = await readJson2(join36(repoRoot, dir, "package.json"));
108965
109433
  if (manifest === null)
108966
109434
  continue;
108967
109435
  workspaces2.push({
@@ -108976,8 +109444,8 @@ async function releaseContext(repoRoot, bumpVerConfig = resolveBuilderBumpVerCon
108976
109444
  const packages = new Map;
108977
109445
  for (const ws of workspaces2) {
108978
109446
  const probe = findLiteralProbe(versionCarriers, ws.dir) ?? defaultLiteralProbe;
108979
- const literalProbe = join34(ws.dir, probe.file);
108980
- const versionSourceFile = existsSync12(join34(repoRoot, literalProbe)) ? literalProbe : undefined;
109447
+ const literalProbe = join36(ws.dir, probe.file);
109448
+ const versionSourceFile = existsSync13(join36(repoRoot, literalProbe)) ? literalProbe : undefined;
108981
109449
  packages.set(shortName(ws.name), {
108982
109450
  packageDir: ws.dir,
108983
109451
  packageName: ws.name,
@@ -109005,15 +109473,17 @@ async function releaseContext(repoRoot, bumpVerConfig = resolveBuilderBumpVerCon
109005
109473
  }
109006
109474
  }
109007
109475
  }
109008
- pinnedNames.add(rootName);
109476
+ const aggregate2 = resolveAggregatePackage(bumpVerConfig.aggregatePackage, packages, rootName);
109477
+ if (aggregate2 !== undefined)
109478
+ pinnedNames.add(aggregate2.packageName);
109009
109479
  const allPackages = [...packages.values()].filter((c3) => pinnedNames.has(c3.packageName));
109010
- return { repoRoot, rootName, packages, allPackages, versionCarriers };
109480
+ return { repoRoot, rootName, aggregate: aggregate2, packages, allPackages, versionCarriers };
109011
109481
  }
109012
109482
  async function updateWorkspacePins(ctx, pkgName, oldVersion, newVersion, output2) {
109013
109483
  const changed = [];
109014
109484
  for (const dir of workspaceDirs(ctx.repoRoot)) {
109015
109485
  const relPath = `${dir}/package.json`;
109016
- const manifest = await readJson2(join34(ctx.repoRoot, relPath));
109486
+ const manifest = await readJson2(join36(ctx.repoRoot, relPath));
109017
109487
  if (manifest === null)
109018
109488
  continue;
109019
109489
  let dirty = false;
@@ -109030,7 +109500,7 @@ async function updateWorkspacePins(ctx, pkgName, oldVersion, newVersion, output2
109030
109500
  }
109031
109501
  }
109032
109502
  if (dirty) {
109033
- await Bun.write(join34(ctx.repoRoot, relPath), `${JSON.stringify(manifest, null, 4)}
109503
+ await Bun.write(join36(ctx.repoRoot, relPath), `${JSON.stringify(manifest, null, 4)}
109034
109504
  `);
109035
109505
  output2.write(` \u21B3 ${relPath}: ${pkgName} workspace pin ${oldVersion} \u2192 ${newVersion}`);
109036
109506
  changed.push(relPath);
@@ -109039,7 +109509,7 @@ async function updateWorkspacePins(ctx, pkgName, oldVersion, newVersion, output2
109039
109509
  return changed;
109040
109510
  }
109041
109511
  async function updateVersionSourceFile(ctx, filePath, previous, next, output2, identifier = "binaryVersion") {
109042
- const absPath = join34(ctx.repoRoot, filePath);
109512
+ const absPath = join36(ctx.repoRoot, filePath);
109043
109513
  const content = await Bun.file(absPath).text();
109044
109514
  const pattern = new RegExp(`(${identifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}:\\s*['"])\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z-.]+)?(?:\\+[0-9A-Za-z-.]+)?(['"])`);
109045
109515
  const updated = content.replace(pattern, `$1${next}$2`);
@@ -109057,7 +109527,7 @@ async function syncExtraCarriers(ctx, version3, staged, output2) {
109057
109527
  }
109058
109528
  }
109059
109529
  async function syncMarketplaceAndPlugins(ctx, version3, staged, output2) {
109060
- const marketplacePath = join34(ctx.repoRoot, ".claude-plugin", "marketplace.json");
109530
+ const marketplacePath = join36(ctx.repoRoot, ".claude-plugin", "marketplace.json");
109061
109531
  const marketplaceFile = Bun.file(marketplacePath);
109062
109532
  if (!await marketplaceFile.exists())
109063
109533
  return;
@@ -109082,7 +109552,7 @@ async function syncMarketplaceAndPlugins(ctx, version3, staged, output2) {
109082
109552
  output2.write(`Bumped marketplace plugins to ${version3}`);
109083
109553
  }
109084
109554
  for (const entry of plugins) {
109085
- const pluginJsonPath = join34(ctx.repoRoot, entry.source, "plugin.json");
109555
+ const pluginJsonPath = join36(ctx.repoRoot, entry.source, "plugin.json");
109086
109556
  const pluginJson = await readJson2(pluginJsonPath);
109087
109557
  if (pluginJson === null) {
109088
109558
  output2.write(` \u26A0 plugin.json not found at ${entry.source}/plugin.json \u2014 skipping`);
@@ -109135,7 +109605,7 @@ async function bumpVersion(ctx, config4, version3, options, output2) {
109135
109605
  if (await npmViewVersion(ctx.repoRoot, config4.packageName, version3)) {
109136
109606
  throw new Error(`${config4.packageName}@${version3} is already published on npm. Use a new version.`);
109137
109607
  }
109138
- const manifestPath = join34(ctx.repoRoot, config4.packageDir, "package.json");
109608
+ const manifestPath = join36(ctx.repoRoot, config4.packageDir, "package.json");
109139
109609
  const manifest = await readJson2(manifestPath);
109140
109610
  if (manifest === null)
109141
109611
  throw new Error(`missing or malformed manifest at ${config4.packageDir}/package.json`);
@@ -109153,8 +109623,8 @@ async function bumpVersion(ctx, config4, version3, options, output2) {
109153
109623
  staged.push(...await updateWorkspacePins(ctx, config4.packageName, previous, version3, output2));
109154
109624
  await syncMarketplaceAndPlugins(ctx, version3, staged, output2);
109155
109625
  await syncExtraCarriers(ctx, version3, staged, output2);
109156
- const lockPath = join34(ctx.repoRoot, "bun.lock");
109157
- if (existsSync12(lockPath) && Bun.file(lockPath).size > 0)
109626
+ const lockPath = join36(ctx.repoRoot, "bun.lock");
109627
+ if (existsSync13(lockPath) && Bun.file(lockPath).size > 0)
109158
109628
  staged.push("bun.lock");
109159
109629
  await git2(ctx.repoRoot, ["add", ...staged]);
109160
109630
  const message = `${config4.releaseCommitType}(${config4.releaseCommitScope}): ${config4.releaseCommitSubject(version3)}`;
@@ -109188,7 +109658,7 @@ async function bumpAll(ctx, version3, options, output2) {
109188
109658
  }
109189
109659
  const branch = await assertCleanTreeOnBranch(ctx);
109190
109660
  const configs = ctx.allPackages;
109191
- const aggregateTag = `${ctx.rootName}-v${version3}`;
109661
+ const aggregateTag = resolveAggregateTag(ctx, version3);
109192
109662
  const existingLocal = new Set((await git2(ctx.repoRoot, ["tag", "-l"])).split(`
109193
109663
  `).filter(Boolean));
109194
109664
  const allTags = [aggregateTag, ...configs.map((c3) => releaseTag(c3, version3))];
@@ -109209,7 +109679,7 @@ async function bumpAll(ctx, version3, options, output2) {
109209
109679
  }
109210
109680
  const staged = [];
109211
109681
  for (const config4 of configs) {
109212
- const manifestPath = join34(ctx.repoRoot, config4.packageDir, "package.json");
109682
+ const manifestPath = join36(ctx.repoRoot, config4.packageDir, "package.json");
109213
109683
  const manifest = await readJson2(manifestPath);
109214
109684
  if (manifest === null) {
109215
109685
  throw new Error(`missing or malformed manifest at ${config4.packageDir}/package.json`);
@@ -109229,8 +109699,8 @@ async function bumpAll(ctx, version3, options, output2) {
109229
109699
  }
109230
109700
  await syncMarketplaceAndPlugins(ctx, version3, staged, output2);
109231
109701
  await syncExtraCarriers(ctx, version3, staged, output2);
109232
- const lockPath = join34(ctx.repoRoot, "bun.lock");
109233
- if (existsSync12(lockPath) && Bun.file(lockPath).size > 0)
109702
+ const lockPath = join36(ctx.repoRoot, "bun.lock");
109703
+ if (existsSync13(lockPath) && Bun.file(lockPath).size > 0)
109234
109704
  staged.push("bun.lock");
109235
109705
  await git2(ctx.repoRoot, ["add", ...staged]);
109236
109706
  const shortNames = configs.map((c3) => c3.packageName.split("/").pop() ?? c3.packageName).join(" + ");
@@ -109244,7 +109714,13 @@ async function bumpAll(ctx, version3, options, output2) {
109244
109714
  output2.write(`Tagged (trace): ${tag}`);
109245
109715
  }
109246
109716
  }
109247
- await git2(ctx.repoRoot, ["tag", "-a", aggregateTag, "-m", `${ctx.rootName} ${version3} \u2014 ${shortNames}`]);
109717
+ await git2(ctx.repoRoot, [
109718
+ "tag",
109719
+ "-a",
109720
+ aggregateTag,
109721
+ "-m",
109722
+ `${ctx.aggregate?.packageName ?? ctx.rootName} ${version3} \u2014 ${shortNames}`
109723
+ ]);
109248
109724
  output2.write(`Tagged (publish): ${aggregateTag}`);
109249
109725
  if (!options.push) {
109250
109726
  output2.write(`
@@ -109288,7 +109764,7 @@ async function dropAll(ctx, version3, options, output2) {
109288
109764
  for (const config4 of ctx.allPackages) {
109289
109765
  await dropTagsFor(ctx, config4, version3, options, output2);
109290
109766
  }
109291
- const aggregateTag = `${ctx.rootName}-v${version3}`;
109767
+ const aggregateTag = resolveAggregateTag(ctx, version3);
109292
109768
  const localTags = new Set((await git2(ctx.repoRoot, ["tag", "-l"])).split(`
109293
109769
  `).filter(Boolean));
109294
109770
  if (localTags.has(aggregateTag)) {
@@ -109829,7 +110305,7 @@ import { createRequire } from "module";
109829
110305
  var CLI_CONFIG = {
109830
110306
  binaryName: "spur",
109831
110307
  binaryLabel: "spur",
109832
- binaryVersion: "0.3.82",
110308
+ binaryVersion: "0.3.84",
109833
110309
  configDir: ".spur",
109834
110310
  configFile: ".spur/config.yaml",
109835
110311
  databaseFile: ".spur/spur.db"
@@ -110240,8 +110716,8 @@ init_src();
110240
110716
  init_loader();
110241
110717
  init_src2();
110242
110718
  init_dist10();
110243
- import { homedir as homedir8 } from "os";
110244
- import { join as join35, resolve as resolve17 } from "path";
110719
+ import { homedir as homedir10 } from "os";
110720
+ import { join as join37, resolve as resolve18 } from "path";
110245
110721
 
110246
110722
  // src/config/scaffold-manifest.ts
110247
110723
  var SCAFFOLD_MANIFEST = [
@@ -110270,8 +110746,8 @@ var SCAFFOLD_MANIFEST = [
110270
110746
  ];
110271
110747
 
110272
110748
  // src/commands/init.ts
110273
- var GLOBAL_CONFIG_DIR = join35(".config", "spur");
110274
- var GLOBAL_RULES_DIR2 = join35(GLOBAL_CONFIG_DIR, "rules");
110749
+ var GLOBAL_CONFIG_DIR = join37(".config", "spur");
110750
+ var GLOBAL_RULES_DIR2 = join37(GLOBAL_CONFIG_DIR, "rules");
110275
110751
  var GLOBAL_CONFIG_EXAMPLE = BUNDLED_GLOBAL_CONFIG;
110276
110752
  var GLOBAL_CONFIG_FILE2 = "config.yaml";
110277
110753
  var INDEXED_CONTEXT_MARKER = "## Indexed context";
@@ -110302,7 +110778,7 @@ If \`.spur/context/\` is absent, proceed normally. Never block work on its absen
110302
110778
  `;
110303
110779
  function globalRulesRoot(context4) {
110304
110780
  const override = context4.env.SPUR_GLOBAL_RULES_DIR;
110305
- return override !== undefined && override.length > 0 ? resolve17(context4.cwd, override) : join35(homedir8(), GLOBAL_RULES_DIR2);
110781
+ return override !== undefined && override.length > 0 ? resolve18(context4.cwd, override) : join37(homedir10(), GLOBAL_RULES_DIR2);
110306
110782
  }
110307
110783
  async function writeIfNew(context4, path9, content, force, result) {
110308
110784
  if (!force && await context4.fs.exists(path9)) {
@@ -110319,18 +110795,18 @@ async function seedGlobalRules(context4) {
110319
110795
  const target = globalRulesRoot(context4);
110320
110796
  let written = 0;
110321
110797
  for (const relPath of await listBundledRuleFiles()) {
110322
- const destination = join35(target, relPath);
110798
+ const destination = join37(target, relPath);
110323
110799
  if (await context4.fs.exists(destination))
110324
110800
  continue;
110325
- await context4.fs.ensureDir(join35(target, ...relPath.split("/").slice(0, -1)));
110326
- await context4.fs.writeFile(destination, await context4.fs.readFile(join35(source, relPath)));
110801
+ await context4.fs.ensureDir(join37(target, ...relPath.split("/").slice(0, -1)));
110802
+ await context4.fs.writeFile(destination, await context4.fs.readFile(join37(source, relPath)));
110327
110803
  written += 1;
110328
110804
  }
110329
110805
  return written;
110330
110806
  }
110331
110807
  function resolveGlobalConfigDir(context4) {
110332
110808
  const globalOverride = context4.env.SPUR_GLOBAL_RULES_DIR;
110333
- return globalOverride !== undefined && globalOverride.length > 0 ? resolve17(context4.cwd, globalOverride) : join35(homedir8(), GLOBAL_CONFIG_DIR);
110809
+ return globalOverride !== undefined && globalOverride.length > 0 ? resolve18(context4.cwd, globalOverride) : join37(homedir10(), GLOBAL_CONFIG_DIR);
110334
110810
  }
110335
110811
  async function seedGlobalConfig(context4) {
110336
110812
  const source = bundledConfigRoot();
@@ -110341,15 +110817,15 @@ async function seedGlobalConfig(context4) {
110341
110817
  for (const relPath of listBundledConfigFiles()) {
110342
110818
  if (relPath === GLOBAL_CONFIG_EXAMPLE)
110343
110819
  continue;
110344
- const destination = join35(target, relPath);
110820
+ const destination = join37(target, relPath);
110345
110821
  if (await context4.fs.exists(destination))
110346
110822
  continue;
110347
- await context4.fs.ensureDir(join35(target, ...relPath.split("/").slice(0, -1)));
110348
- await context4.fs.writeFile(destination, await context4.fs.readFile(join35(source, relPath)));
110823
+ await context4.fs.ensureDir(join37(target, ...relPath.split("/").slice(0, -1)));
110824
+ await context4.fs.writeFile(destination, await context4.fs.readFile(join37(source, relPath)));
110349
110825
  written += 1;
110350
110826
  }
110351
- const examplePath = join35(source, GLOBAL_CONFIG_EXAMPLE);
110352
- const globalConfigPath = join35(target, GLOBAL_CONFIG_FILE2);
110827
+ const examplePath = join37(source, GLOBAL_CONFIG_EXAMPLE);
110828
+ const globalConfigPath = join37(target, GLOBAL_CONFIG_FILE2);
110353
110829
  if (await context4.fs.exists(examplePath) && !await context4.fs.exists(globalConfigPath)) {
110354
110830
  await context4.fs.ensureDir(target);
110355
110831
  await context4.fs.writeFile(globalConfigPath, await context4.fs.readFile(examplePath));
@@ -110364,10 +110840,10 @@ function registerInitCommand(program2, context4, options = {}) {
110364
110840
  const minimal = options2.minimal === true;
110365
110841
  const adoptGlobal = options2.adoptGlobalConfig === true;
110366
110842
  const projectName = options2.name ?? "default";
110367
- const configPath = join35(context4.cwd, CLI_CONFIG.configFile);
110843
+ const configPath = join37(context4.cwd, CLI_CONFIG.configFile);
110368
110844
  const result = { created: [], skipped: [] };
110369
110845
  const globalConfigDir = resolveGlobalConfigDir(context4);
110370
- const globalConfigPath = join35(globalConfigDir, GLOBAL_CONFIG_FILE2);
110846
+ const globalConfigPath = join37(globalConfigDir, GLOBAL_CONFIG_FILE2);
110371
110847
  const globalConfigExists = await context4.fs.exists(globalConfigPath);
110372
110848
  let misplacedKeys = [];
110373
110849
  if (globalConfigExists) {
@@ -110380,20 +110856,20 @@ function registerInitCommand(program2, context4, options = {}) {
110380
110856
  const configRoot = bundledConfigRoot();
110381
110857
  if (configRoot !== null && !minimal) {
110382
110858
  for (const relPath of listBundledProjectSeedFiles()) {
110383
- const sourcePath = join35(configRoot, relPath);
110859
+ const sourcePath = join37(configRoot, relPath);
110384
110860
  if (!await context4.fs.exists(sourcePath))
110385
110861
  continue;
110386
- const targetPath = join35(context4.cwd, CLI_CONFIG.configDir, relPath);
110387
- await context4.fs.ensureDir(join35(targetPath, ".."));
110862
+ const targetPath = join37(context4.cwd, CLI_CONFIG.configDir, relPath);
110863
+ await context4.fs.ensureDir(join37(targetPath, ".."));
110388
110864
  await writeIfNew(context4, targetPath, await context4.fs.readFile(sourcePath), false, result);
110389
110865
  }
110390
110866
  for (const entry of SCAFFOLD_MANIFEST) {
110391
- const sourcePath = join35(configRoot, entry.source);
110867
+ const sourcePath = join37(configRoot, entry.source);
110392
110868
  if (!await context4.fs.exists(sourcePath))
110393
110869
  continue;
110394
- const baseDir = entry.root === true ? context4.cwd : join35(context4.cwd, CLI_CONFIG.configDir);
110395
- const targetPath = join35(baseDir, entry.target);
110396
- await context4.fs.ensureDir(join35(targetPath, ".."));
110870
+ const baseDir = entry.root === true ? context4.cwd : join37(context4.cwd, CLI_CONFIG.configDir);
110871
+ const targetPath = join37(baseDir, entry.target);
110872
+ await context4.fs.ensureDir(join37(targetPath, ".."));
110397
110873
  const entryForce = false;
110398
110874
  let body = await context4.fs.readFile(sourcePath);
110399
110875
  if (entry.target === "AGENTS.md") {
@@ -110410,7 +110886,7 @@ function registerInitCommand(program2, context4, options = {}) {
110410
110886
  const backupPath = `${globalConfigPath}.bak-${new Date().toISOString().replace(/[:.]/g, "-")}`;
110411
110887
  await context4.fs.writeFile(backupPath, await context4.fs.readFile(globalConfigPath));
110412
110888
  const bundledRoot = bundledConfigRoot();
110413
- const examplePath = bundledRoot !== null ? join35(bundledRoot, GLOBAL_CONFIG_EXAMPLE) : null;
110889
+ const examplePath = bundledRoot !== null ? join37(bundledRoot, GLOBAL_CONFIG_EXAMPLE) : null;
110414
110890
  if (examplePath !== null && await context4.fs.exists(examplePath)) {
110415
110891
  await context4.fs.writeFile(globalConfigPath, await context4.fs.readFile(examplePath));
110416
110892
  adoptedGlobal = true;
@@ -110480,13 +110956,13 @@ function registerInitCommand(program2, context4, options = {}) {
110480
110956
  ].join(`
110481
110957
  `)}
110482
110958
  `;
110483
- await context4.fs.ensureDir(join35(context4.cwd, CLI_CONFIG.configDir));
110959
+ await context4.fs.ensureDir(join37(context4.cwd, CLI_CONFIG.configDir));
110484
110960
  await context4.fs.writeFile(configPath, configYaml);
110485
110961
  result.created.push(configPath);
110486
- const agentsDir = join35(context4.cwd, CLI_CONFIG.configDir, "agents");
110962
+ const agentsDir = join37(context4.cwd, CLI_CONFIG.configDir, "agents");
110487
110963
  await context4.fs.ensureDir(agentsDir);
110488
- await writeIfNew(context4, join35(agentsDir, ".gitkeep"), "", force, result);
110489
- const gitignorePath = join35(context4.cwd, ".gitignore");
110964
+ await writeIfNew(context4, join37(agentsDir, ".gitkeep"), "", force, result);
110965
+ const gitignorePath = join37(context4.cwd, ".gitignore");
110490
110966
  const contextEntry = ".spur/context/";
110491
110967
  if (await context4.fs.exists(gitignorePath)) {
110492
110968
  const existing = await context4.fs.readFile(gitignorePath);
@@ -110506,20 +110982,20 @@ ${contextEntry}
110506
110982
  const configRoot = bundledConfigRoot();
110507
110983
  if (configRoot !== null) {
110508
110984
  for (const relPath of listBundledProjectSeedFiles()) {
110509
- const sourcePath = join35(configRoot, relPath);
110985
+ const sourcePath = join37(configRoot, relPath);
110510
110986
  if (!await context4.fs.exists(sourcePath))
110511
110987
  continue;
110512
- const targetPath = join35(context4.cwd, CLI_CONFIG.configDir, relPath);
110513
- await context4.fs.ensureDir(join35(targetPath, ".."));
110988
+ const targetPath = join37(context4.cwd, CLI_CONFIG.configDir, relPath);
110989
+ await context4.fs.ensureDir(join37(targetPath, ".."));
110514
110990
  await writeIfNew(context4, targetPath, await context4.fs.readFile(sourcePath), force, result);
110515
110991
  }
110516
110992
  for (const entry of SCAFFOLD_MANIFEST) {
110517
- const sourcePath = join35(configRoot, entry.source);
110993
+ const sourcePath = join37(configRoot, entry.source);
110518
110994
  if (!await context4.fs.exists(sourcePath))
110519
110995
  continue;
110520
- const baseDir = entry.root === true ? context4.cwd : join35(context4.cwd, CLI_CONFIG.configDir);
110521
- const targetPath = join35(baseDir, entry.target);
110522
- await context4.fs.ensureDir(join35(targetPath, ".."));
110996
+ const baseDir = entry.root === true ? context4.cwd : join37(context4.cwd, CLI_CONFIG.configDir);
110997
+ const targetPath = join37(baseDir, entry.target);
110998
+ await context4.fs.ensureDir(join37(targetPath, ".."));
110523
110999
  const entryForce = entry.preserve === true ? false : force;
110524
111000
  let body = await context4.fs.readFile(sourcePath);
110525
111001
  if (entry.target === "AGENTS.md") {
@@ -110532,7 +111008,7 @@ ${contextEntry}
110532
111008
  }
110533
111009
  }
110534
111010
  }
110535
- const agentsMdPath = join35(context4.cwd, "AGENTS.md");
111011
+ const agentsMdPath = join37(context4.cwd, "AGENTS.md");
110536
111012
  if (await context4.fs.exists(agentsMdPath)) {
110537
111013
  const existing = await context4.fs.readFile(agentsMdPath);
110538
111014
  if (!existing.includes(INDEXED_CONTEXT_MARKER)) {
@@ -110573,11 +111049,11 @@ ${contextEntry}
110573
111049
  // src/commands/maintain.ts
110574
111050
  init_src();
110575
111051
  init_src2();
110576
- import { join as join36 } from "path";
111052
+ import { join as join38 } from "path";
110577
111053
  function registerMaintainCommand(program2, context4, options = {}) {
110578
111054
  program2.command("maintain", { hidden: options.hidden === true }).summary("run database maintenance (optimize, WAL checkpoint truncation, optional VACUUM)").description("Run database maintenance: PRAGMA optimize, WAL checkpoint truncation; optionally VACUUM compaction.").option("--vacuum", "Run VACUUM defragmentation and page compaction", false).option(...SHARED_OPTIONS.jsonSupported).option(...SHARED_OPTIONS.jsonEnvelope).action(async (options2) => {
110579
111055
  const db2 = await context4.getDb();
110580
- const dbPath = context4.env.DATABASE_URL ?? join36(context4.cwd, DEFAULT_DATABASE_URL);
111056
+ const dbPath = context4.env.DATABASE_URL ?? join38(context4.cwd, DEFAULT_DATABASE_URL);
110581
111057
  const result = await maintainDatabase(db2, {
110582
111058
  vacuum: options2.vacuum === true,
110583
111059
  dbPath: dbPath.startsWith(":memory:") ? undefined : dbPath
@@ -110900,15 +111376,15 @@ async function runMessageWatch(svc, output2, options, runtime = {}) {
110900
111376
  }
110901
111377
  }
110902
111378
  function defaultSleep(ms, signal) {
110903
- return new Promise((resolve18) => {
111379
+ return new Promise((resolve19) => {
110904
111380
  if (signal?.aborted) {
110905
- resolve18();
111381
+ resolve19();
110906
111382
  return;
110907
111383
  }
110908
- const timer = setTimeout(resolve18, ms);
111384
+ const timer = setTimeout(resolve19, ms);
110909
111385
  signal?.addEventListener("abort", () => {
110910
111386
  clearTimeout(timer);
110911
- resolve18();
111387
+ resolve19();
110912
111388
  }, { once: true });
110913
111389
  });
110914
111390
  }
@@ -110935,15 +111411,15 @@ function parseTimeout2(raw) {
110935
111411
  return n3;
110936
111412
  }
110937
111413
  function sendSleep(ms, signal) {
110938
- return new Promise((resolve18) => {
111414
+ return new Promise((resolve19) => {
110939
111415
  if (signal?.aborted) {
110940
- resolve18();
111416
+ resolve19();
110941
111417
  return;
110942
111418
  }
110943
- const timer = setTimeout(resolve18, ms);
111419
+ const timer = setTimeout(resolve19, ms);
110944
111420
  signal?.addEventListener("abort", () => {
110945
111421
  clearTimeout(timer);
110946
- resolve18();
111422
+ resolve19();
110947
111423
  }, { once: true });
110948
111424
  });
110949
111425
  }
@@ -111001,10 +111477,10 @@ async function waitForPendingDrain(agentService, teamService, pin, timeoutMs, si
111001
111477
 
111002
111478
  // src/commands/migrate.ts
111003
111479
  init_src2();
111004
- import { join as join37 } from "path";
111480
+ import { join as join39 } from "path";
111005
111481
  function registerMigrateCommand(program2, context4, options = {}) {
111006
111482
  program2.command("migrate", { hidden: options.hidden === true }).summary("apply CLI-owned schema migrations").option(...SHARED_OPTIONS.json).option(...SHARED_OPTIONS.jsonEnvelope).action(async (options2) => {
111007
- const migrations = await loadSqlMigrations(join37(context4.cwd, "drizzle")).catch(() => {
111483
+ const migrations = await loadSqlMigrations(join39(context4.cwd, "drizzle")).catch(() => {
111008
111484
  return;
111009
111485
  });
111010
111486
  const applied = await applyCliMigrations(await context4.getDb(), migrations);
@@ -111017,16 +111493,16 @@ function registerMigrateCommand(program2, context4, options = {}) {
111017
111493
  init_src2();
111018
111494
  init_dist5();
111019
111495
  await init_src3();
111020
- import { basename as basename12, dirname as dirname24, join as join38, resolve as resolve18 } from "path";
111496
+ import { basename as basename13, dirname as dirname25, join as join40, resolve as resolve19 } from "path";
111021
111497
  function registerProjectsCommand(program2, context4) {
111022
111498
  const projectsCmd = program2.command("projects").summary("manage the Spur multi-project registry");
111023
111499
  projectsCmd.command("add").argument("<path>", "Project root directory path").option(...SHARED_OPTIONS.nameProjectDisplay).option(...SHARED_OPTIONS.jsonProjectsResponse).option(...SHARED_OPTIONS.jsonEnvelope).action(async (pathArg, options) => {
111024
111500
  try {
111025
- const absolutePath = resolve18(context4.cwd, pathArg);
111501
+ const absolutePath = resolve19(context4.cwd, pathArg);
111026
111502
  if (!await context4.fs.exists(absolutePath)) {
111027
111503
  throw new Error(`Directory does not exist: ${absolutePath}`);
111028
111504
  }
111029
- const name = options.name ?? basename12(absolutePath);
111505
+ const name = options.name ?? basename13(absolutePath);
111030
111506
  const registry3 = new ProjectRegistry;
111031
111507
  const entry = await registry3.upsert({ path: absolutePath, name, port: 0 });
111032
111508
  if (options.json) {
@@ -111075,8 +111551,8 @@ function registerProjectsCommand(program2, context4) {
111075
111551
  running: p.port > 0 ? await isPortLive(p.port) : false
111076
111552
  })));
111077
111553
  const openProjectDb = async (projectPath) => {
111078
- const url2 = join38(projectPath, ".spur", "spur.db");
111079
- await context4.fs.ensureDir(dirname24(url2));
111554
+ const url2 = join40(projectPath, ".spur", "spur.db");
111555
+ await context4.fs.ensureDir(dirname25(url2));
111080
111556
  return createMigratedDb({ url: url2 });
111081
111557
  };
111082
111558
  const fleets = options.fleet ? await Promise.all(projects3.map(async (p) => {
@@ -111518,7 +111994,7 @@ function formatTraceDetail(detail) {
111518
111994
  // src/commands/serve.ts
111519
111995
  init_src();
111520
111996
  import { statSync as statSync7 } from "fs";
111521
- import { join as join42, resolve as resolve19 } from "path";
111997
+ import { join as join44, resolve as resolve20 } from "path";
111522
111998
 
111523
111999
  // ../server/src/index.ts
111524
112000
  init_src();
@@ -111528,11 +112004,11 @@ init_src();
111528
112004
  init_loader();
111529
112005
  init_src2();
111530
112006
  await init_src3();
111531
- import { basename as basename14, dirname as dirname26, isAbsolute as isAbsolute8, join as join41 } from "path";
112007
+ import { basename as basename15, dirname as dirname27, isAbsolute as isAbsolute8, join as join43 } from "path";
111532
112008
  init_dist5();
111533
112009
 
111534
112010
  // ../server/src/bootstrap.ts
111535
- import { join as join39 } from "path";
112011
+ import { join as join41 } from "path";
111536
112012
 
111537
112013
  // ../../node_modules/.bun/radash@12.1.1/node_modules/radash/dist/esm/async.mjs
111538
112014
  var guard = (func, shouldGuard) => {
@@ -111805,8 +112281,8 @@ class EventPublisher {
111805
112281
  if (bufferedEvents.length > 0) {
111806
112282
  return { done: false, value: bufferedEvents.shift() };
111807
112283
  }
111808
- return new Promise((resolve19, reject) => {
111809
- pullResolvers.push([resolve19, reject]);
112284
+ return new Promise((resolve20, reject) => {
112285
+ pullResolvers.push([resolve20, reject]);
111810
112286
  });
111811
112287
  }, async () => {
111812
112288
  unsubscribe();
@@ -116926,6 +117402,55 @@ function mountMiddleware(app, appRt) {
116926
117402
  }
116927
117403
  }
116928
117404
 
117405
+ // ../server/src/modules/commands/index.ts
117406
+ init_src();
117407
+ await init_src3();
117408
+ var commandsModule = {
117409
+ name: "commands",
117410
+ mount(app, ctx) {
117411
+ const projectRoot = ctx?.cwd ?? process.cwd();
117412
+ const filePath = getSlashCommandsFilePath();
117413
+ const service = new SlashCommandsService({ filePath, projectRoot });
117414
+ let cachedCommands = [];
117415
+ try {
117416
+ cachedCommands = service.getCommands();
117417
+ } catch {}
117418
+ const handleList = (c3) => {
117419
+ const reload = c3.req.query("reload") === "true";
117420
+ if (reload || cachedCommands.length === 0) {
117421
+ try {
117422
+ cachedCommands = service.getCommands(reload);
117423
+ } catch {}
117424
+ }
117425
+ return c3.json({
117426
+ commands: cachedCommands,
117427
+ count: cachedCommands.length,
117428
+ filePath: service.getFilePath()
117429
+ });
117430
+ };
117431
+ app.get("/api/commands", handleList);
117432
+ app.get("/api/agent/commands", handleList);
117433
+ app.post("/api/commands/generate", (c3) => {
117434
+ try {
117435
+ const result = service.generate();
117436
+ cachedCommands = result.commands;
117437
+ return c3.json({
117438
+ ok: true,
117439
+ count: result.count,
117440
+ filePath: result.path,
117441
+ commands: result.commands
117442
+ });
117443
+ } catch (err) {
117444
+ return c3.json({
117445
+ ok: false,
117446
+ error: err instanceof Error ? err.message : String(err),
117447
+ filePath: service.getFilePath()
117448
+ }, 500);
117449
+ }
117450
+ });
117451
+ }
117452
+ };
117453
+
116929
117454
  // ../server/src/modules/sse/stream-helpers.ts
116930
117455
  function sendSseKeepalive(closed, controller, encoder) {
116931
117456
  if (closed.current)
@@ -118234,7 +118759,7 @@ var featureModule = {
118234
118759
  // ../server/src/modules/health/index.ts
118235
118760
  init_src2();
118236
118761
  await init_src3();
118237
- import { basename as basename13 } from "path";
118762
+ import { basename as basename14 } from "path";
118238
118763
  var startedAt = Date.now();
118239
118764
  var OPERATOR_AGENT_ID = "board-operator";
118240
118765
  var healthModule = {
@@ -118261,7 +118786,7 @@ var healthModule = {
118261
118786
  return c3.json({ status: "error", db: "unreachable" }, 503);
118262
118787
  });
118263
118788
  app.get("/api/project", (c3) => {
118264
- return c3.json({ name: ctx ? basename13(ctx.cwd) : null, path: ctx ? normalizeProjectPath(ctx.cwd) : null });
118789
+ return c3.json({ name: ctx ? basename14(ctx.cwd) : null, path: ctx ? normalizeProjectPath(ctx.cwd) : null });
118265
118790
  });
118266
118791
  app.get("/api/project/fleet", async (c3) => {
118267
118792
  if (!ctx) {
@@ -119341,7 +119866,8 @@ var builtins = [
119341
119866
  processesModule,
119342
119867
  observabilityModule,
119343
119868
  runsModule,
119344
- historyModule
119869
+ historyModule,
119870
+ commandsModule
119345
119871
  ];
119346
119872
  function registerModules(app, ctx, modules = builtins) {
119347
119873
  for (const mod of modules) {
@@ -121359,7 +121885,7 @@ function createApp(appRt, opts) {
121359
121885
  app.use("*", async (c3, next) => {
121360
121886
  const pathname = c3.req.path === "/" ? "/index.html" : c3.req.path;
121361
121887
  try {
121362
- const file2 = Bun.file(join39(webDistPath, pathname));
121888
+ const file2 = Bun.file(join41(webDistPath, pathname));
121363
121889
  if (await file2.exists()) {
121364
121890
  const headers = new Headers({ "content-type": file2.type });
121365
121891
  return new Response(file2.stream(), { headers });
@@ -121372,7 +121898,7 @@ function createApp(appRt, opts) {
121372
121898
  return c3.json({ error: "Not Found" }, 404);
121373
121899
  }
121374
121900
  try {
121375
- const indexFile = Bun.file(join39(webDistPath, "index.html"));
121901
+ const indexFile = Bun.file(join41(webDistPath, "index.html"));
121376
121902
  if (await indexFile.exists()) {
121377
121903
  return new Response(indexFile.stream(), {
121378
121904
  headers: { "content-type": "text/html; charset=utf-8" }
@@ -121394,7 +121920,7 @@ init_loader();
121394
121920
  init_src2();
121395
121921
  init_dist5();
121396
121922
  await init_src3();
121397
- import { dirname as dirname25, join as join40 } from "path";
121923
+ import { dirname as dirname26, join as join42 } from "path";
121398
121924
  var NOOP_OUTPUT = { write: (_s) => {}, error: (_s) => {} };
121399
121925
 
121400
121926
  class LazyPlanningEventEmitter {
@@ -121430,7 +121956,7 @@ class NotConfiguredError extends Error {
121430
121956
  function createServerContext(appRt, options) {
121431
121957
  const cwd = options.cwd;
121432
121958
  const fs3 = options.fs;
121433
- const dbUrl = options.dbUrl ?? join40(cwd, DEFAULT_DATABASE_URL);
121959
+ const dbUrl = options.dbUrl ?? join42(cwd, DEFAULT_DATABASE_URL);
121434
121960
  const eventsBus = options.eventsBus ?? appRt.events;
121435
121961
  const jobQueueEnabled = options.jobQueueEnabled ?? false;
121436
121962
  const eventProjectContext = systemEventProjectContext(cwd);
@@ -121467,7 +121993,7 @@ function createServerContext(appRt, options) {
121467
121993
  if (!dbPromise) {
121468
121994
  const created = (async () => {
121469
121995
  if (dbUrl !== IN_MEMORY_DATABASE_URL) {
121470
- await fs3.ensureDir(dirname25(dbUrl));
121996
+ await fs3.ensureDir(dirname26(dbUrl));
121471
121997
  }
121472
121998
  return createMigratedDbViaRuntime({ url: dbUrl });
121473
121999
  })();
@@ -121735,7 +122261,7 @@ async function loadServerSectionMatrix(projectRoot) {
121735
122261
  }
121736
122262
  const root = bundledConfigRoot();
121737
122263
  if (root !== null) {
121738
- const matrixPath = join41(root, "tasks", "section-matrix.yaml");
122264
+ const matrixPath = join43(root, "tasks", "section-matrix.yaml");
121739
122265
  if (await nodeFs.exists(matrixPath)) {
121740
122266
  return await loadStructuredSpurConfig(matrixPath, {
121741
122267
  validateJsonSchema: false
@@ -121744,7 +122270,7 @@ async function loadServerSectionMatrix(projectRoot) {
121744
122270
  }
121745
122271
  throw new Error(`no canonical section-matrix found for task creation (F92 R1); tried:
121746
122272
  ` + ` - ${localPath}
121747
- ` + (root !== null ? ` - ${join41(root, "tasks", "section-matrix.yaml")}
122273
+ ` + (root !== null ? ` - ${join43(root, "tasks", "section-matrix.yaml")}
121748
122274
  ` : "") + "copy/generate section-matrix.yaml from the canonical build-time matrix asset (repo `config` `tasks` tree) into one of those paths");
121749
122275
  }
121750
122276
  var SYSTEM_EVENTS_PRUNE_JOB = "system-events-prune";
@@ -121813,41 +122339,58 @@ function registerSchedulerEntries(scheduler3, ctx, jobs = [], options = {}) {
121813
122339
  const sweepThresholdMs = jobPolicy === null ? null : jobPolicy + resolveKillGraceMs3(env);
121814
122340
  register(schedule, `${SCHEDULER_CUSTOM_JOB}:${job.name}`, async () => {
121815
122341
  const queue = await ctx.jobQueue();
122342
+ const enqueueFresh = () => queue.enqueue(SCHEDULER_CUSTOM_JOB, {
122343
+ name: job.name,
122344
+ command: job.command,
122345
+ ...historyProducerExclusiveKeyFor(job.command) !== undefined && {
122346
+ exclusiveKey: historyProducerExclusiveKeyFor(job.command)
122347
+ }
122348
+ }, { maxRetries: 1 });
122349
+ try {
122350
+ await enqueueFresh();
122351
+ return;
122352
+ } catch (error51) {
122353
+ if (!isSchedulerCustomActiveConflict(error51))
122354
+ throw error51;
122355
+ }
121816
122356
  const db2 = await ctx.getDb();
121817
122357
  const active = await findActiveSchedulerCustomJob(db2, job.name);
121818
- if (active !== undefined) {
121819
- const now3 = Date.now();
121820
- const stale = sweepThresholdMs !== null && active.status === "processing" && now3 - (active.processingAt ?? active.updatedAt) > sweepThresholdMs;
121821
- if (stale) {
121822
- const reason = `age-sweep: processing exceeded ${sweepThresholdMs}ms`;
121823
- const swept = await failStaleSchedulerCustomJob(db2, active.id, now3, reason);
121824
- if (swept) {
122358
+ const now3 = Date.now();
122359
+ const stale = active !== undefined && sweepThresholdMs !== null && active.status === "processing" && now3 - (active.processingAt ?? active.updatedAt) > sweepThresholdMs;
122360
+ if (active !== undefined && stale && sweepThresholdMs !== null) {
122361
+ const reason = `age-sweep: processing exceeded ${sweepThresholdMs}ms`;
122362
+ const swept = await failStaleSchedulerCustomJob(db2, active.id, now3, reason);
122363
+ if (swept) {
122364
+ ctx.eventBus().emit("scheduler.job.executed", {
122365
+ name: `${SCHEDULER_CUSTOM_JOB}:${job.name}`,
122366
+ durationMs: 0,
122367
+ severity: "info",
122368
+ swept: true,
122369
+ reason
122370
+ });
122371
+ try {
122372
+ await enqueueFresh();
122373
+ } catch (error51) {
122374
+ if (!isSchedulerCustomActiveConflict(error51))
122375
+ throw error51;
121825
122376
  ctx.eventBus().emit("scheduler.job.executed", {
121826
122377
  name: `${SCHEDULER_CUSTOM_JOB}:${job.name}`,
121827
122378
  durationMs: 0,
121828
122379
  severity: "info",
121829
- swept: true,
121830
- reason
122380
+ skipped: true,
122381
+ reason: "post-sweep enqueue lost to a concurrent tick"
121831
122382
  });
121832
122383
  }
121833
- } else {
121834
- ctx.eventBus().emit("scheduler.job.executed", {
121835
- name: `${SCHEDULER_CUSTOM_JOB}:${job.name}`,
121836
- durationMs: 0,
121837
- severity: "info",
121838
- skipped: true,
121839
- reason: `active job ${active.id} already exists`
121840
- });
121841
122384
  return;
121842
122385
  }
121843
122386
  }
121844
- await queue.enqueue(SCHEDULER_CUSTOM_JOB, {
121845
- name: job.name,
121846
- command: job.command,
121847
- ...historyProducerExclusiveKeyFor(job.command) !== undefined && {
121848
- exclusiveKey: historyProducerExclusiveKeyFor(job.command)
121849
- }
121850
- }, { maxRetries: 1 });
122387
+ ctx.eventBus().emit("scheduler.job.executed", {
122388
+ name: `${SCHEDULER_CUSTOM_JOB}:${job.name}`,
122389
+ durationMs: 0,
122390
+ severity: "info",
122391
+ skipped: true,
122392
+ reason: `active job ${active?.id ?? "unknown"} already exists`
122393
+ });
121851
122394
  });
121852
122395
  registrations.push({
121853
122396
  name: job.name,
@@ -121962,15 +122505,15 @@ async function handleFeatureActionJob(ctx, env, payload) {
121962
122505
  await runFeatureActionJob(ctx, env, payload);
121963
122506
  }
121964
122507
  async function resolveWebDistPath(configuredPath, projectRoot = process.cwd()) {
121965
- const candidates = configuredPath && configuredPath.trim() !== "" ? [isAbsolute8(configuredPath) ? configuredPath : join41(projectRoot, configuredPath)] : [
121966
- join41(projectRoot, "dist/web"),
121967
- join41(import.meta.dir, "web"),
121968
- join41(dirname26(process.execPath), "web"),
121969
- join41(dirname26(process.execPath), "../web"),
121970
- join41(import.meta.dir, "../../../dist/web")
122508
+ const candidates = configuredPath && configuredPath.trim() !== "" ? [isAbsolute8(configuredPath) ? configuredPath : join43(projectRoot, configuredPath)] : [
122509
+ join43(projectRoot, "dist/web"),
122510
+ join43(import.meta.dir, "web"),
122511
+ join43(dirname27(process.execPath), "web"),
122512
+ join43(dirname27(process.execPath), "../web"),
122513
+ join43(import.meta.dir, "../../../dist/web")
121971
122514
  ];
121972
122515
  for (const candidate of candidates) {
121973
- if (await Bun.file(join41(candidate, "index.html")).exists()) {
122516
+ if (await Bun.file(join43(candidate, "index.html")).exists()) {
121974
122517
  return candidate;
121975
122518
  }
121976
122519
  }
@@ -121987,7 +122530,7 @@ async function startServer(options, deps = defaultDeps) {
121987
122530
  async start(appRt) {
121988
122531
  const fs3 = deps.createNodeFileSystem(projectRoot);
121989
122532
  if (options.dbUrl && options.dbUrl !== IN_MEMORY_DATABASE_URL) {
121990
- await fs3.ensureDir(dirname26(options.dbUrl));
122533
+ await fs3.ensureDir(dirname27(options.dbUrl));
121991
122534
  }
121992
122535
  const scheduler3 = appRt.config.scheduler.enabled ? appRt.scheduler : undefined;
121993
122536
  const webDistPath = await resolveWebDistPath(options.webDistPath, projectRoot);
@@ -122126,7 +122669,16 @@ async function startServer(options, deps = defaultDeps) {
122126
122669
  return handleSchedulerCustomJob({
122127
122670
  cwd: ctx.cwd,
122128
122671
  executor: childExecutor,
122129
- resolveTimeoutMs: (name) => resolveSchedulerJobTimeoutMs(name, env, schedulerCustomTimeoutMs)
122672
+ resolveTimeoutMs: (name) => resolveSchedulerJobTimeoutMs(name, env, schedulerCustomTimeoutMs),
122673
+ onDuplicate: (name) => {
122674
+ ctx.eventBus().emit("scheduler.job.executed", {
122675
+ name: `${SCHEDULER_CUSTOM_JOB}:${name}`,
122676
+ durationMs: 0,
122677
+ severity: "info",
122678
+ skipped: true,
122679
+ reason: `duplicate execution suppressed; ${name} is already running in this process`
122680
+ });
122681
+ }
122130
122682
  }, job);
122131
122683
  });
122132
122684
  const unlimitedJobNames = appRt.config.scheduler.jobs.filter((job) => resolveSchedulerJobTimeoutMs(job.name, env, schedulerCustomTimeoutMs) === null).map((job) => job.name);
@@ -122157,7 +122709,7 @@ async function startServer(options, deps = defaultDeps) {
122157
122709
  });
122158
122710
  const projectRegistry = new ProjectRegistry;
122159
122711
  const projectCwd = projectRoot;
122160
- const projectName = basename14(projectCwd);
122712
+ const projectName = basename15(projectCwd);
122161
122713
  try {
122162
122714
  const existingProject = await projectRegistry.getByPath(projectCwd);
122163
122715
  await projectRegistry.upsert({
@@ -122277,10 +122829,10 @@ var SQLITE_BUSY_MESSAGE_CONSTANTS = Object.freeze({
122277
122829
 
122278
122830
  // src/commands/serve.ts
122279
122831
  function resolveServeDbUrl(cwd, env, configuredUrl) {
122280
- return env.DATABASE_URL === undefined ? join42(cwd, DEFAULT_DATABASE_URL) : configuredUrl;
122832
+ return env.DATABASE_URL === undefined ? join44(cwd, DEFAULT_DATABASE_URL) : configuredUrl;
122281
122833
  }
122282
122834
  function resolveServeCwd(cwd) {
122283
- const resolved = resolve19(cwd);
122835
+ const resolved = resolve20(cwd);
122284
122836
  let isDirectory = false;
122285
122837
  try {
122286
122838
  isDirectory = statSync7(resolved).isDirectory();
@@ -122333,7 +122885,7 @@ function registerServeCommand(program2, context4, options = {}) {
122333
122885
  }
122334
122886
 
122335
122887
  // src/commands/status.ts
122336
- import { join as join43 } from "path";
122888
+ import { join as join45 } from "path";
122337
122889
 
122338
122890
  // src/git-context.ts
122339
122891
  init_dist5();
@@ -122381,8 +122933,8 @@ function registerStatusCommand(program2, context4, options = {}) {
122381
122933
  }
122382
122934
  async function runStatusCore(path9, options, context4) {
122383
122935
  const [packageJsonExists, spurConfigExists, git3, agentSpecs] = await Promise.all([
122384
- context4.fs.exists(join43(context4.cwd, "package.json")),
122385
- context4.fs.exists(join43(context4.cwd, ".spur", "config.yaml")),
122936
+ context4.fs.exists(join45(context4.cwd, "package.json")),
122937
+ context4.fs.exists(join45(context4.cwd, ".spur", "config.yaml")),
122386
122938
  gitContext(context4.cwd),
122387
122939
  listAgentSpecIds(context4)
122388
122940
  ]);
@@ -122410,14 +122962,14 @@ async function runStatusCore(path9, options, context4) {
122410
122962
  return status.ok ? 0 : 1;
122411
122963
  }
122412
122964
  async function listAgentSpecIds(context4) {
122413
- const dir = join43(context4.cwd, ".spur", "agents");
122965
+ const dir = join45(context4.cwd, ".spur", "agents");
122414
122966
  if (!await context4.fs.exists(dir))
122415
122967
  return [];
122416
122968
  const entries = await context4.fs.readDir(dir);
122417
122969
  return entries.filter((entry) => entry.endsWith(".yaml") || entry.endsWith(".yml")).map((entry) => entry.replace(/\.ya?ml$/, "")).sort();
122418
122970
  }
122419
122971
  async function readTargetStatus(context4, targetPath) {
122420
- const resolved = join43(context4.cwd, targetPath);
122972
+ const resolved = join45(context4.cwd, targetPath);
122421
122973
  const stat2 = await context4.fs.stat(resolved);
122422
122974
  if (stat2 === null)
122423
122975
  throw new CommandError(`status failed: path does not exist at ${resolved}`);
@@ -122430,7 +122982,7 @@ init_dist7();
122430
122982
  init_dist5();
122431
122983
  await init_src3();
122432
122984
  import { closeSync as closeSync5, fstatSync as fstatSync3, openSync as openSync5, readSync as readSync3 } from "fs";
122433
- import { join as join44 } from "path";
122985
+ import { join as join46 } from "path";
122434
122986
  import { createInterface } from "readline";
122435
122987
  import { setTimeout as sleep5 } from "timers/promises";
122436
122988
 
@@ -122609,9 +123161,9 @@ async function writeWorkflowPlanArtifact(cwd, runId, resolved, secretValues) {
122609
123161
  }))
122610
123162
  });
122611
123163
  const { mkdir, writeFile: writeFile2 } = await import("fs/promises");
122612
- const artifactsDir = join44(cwd, ".spur", "run");
123164
+ const artifactsDir = join46(cwd, ".spur", "run");
122613
123165
  await mkdir(artifactsDir, { recursive: true });
122614
- const artifactPath = join44(artifactsDir, `${runId}-workflow-plan.json`);
123166
+ const artifactPath = join46(artifactsDir, `${runId}-workflow-plan.json`);
122615
123167
  await writeFile2(artifactPath, payload);
122616
123168
  return artifactPath;
122617
123169
  }
@@ -122896,7 +123448,7 @@ Monitor with: spur workflow trace ${runId2} --follow`);
122896
123448
  }
122897
123449
  const runLog = options.log === false ? undefined : new WorkflowRunLogSink({
122898
123450
  bus,
122899
- dir: join44(context4.cwd, ".spur", "run"),
123451
+ dir: join46(context4.cwd, ".spur", "run"),
122900
123452
  runId,
122901
123453
  ...planPreview !== undefined ? { planPreview } : {},
122902
123454
  ...resolveOutputLogConfig(context4.spurConfig ?? null)
@@ -123434,7 +123986,7 @@ function readRunLogChunk(logPath, offset) {
123434
123986
  }
123435
123987
  }
123436
123988
  async function followRunLog(service, runId, dir, pollMs, write, wait = (ms) => sleep5(ms)) {
123437
- const logPath = join44(dir, ".spur", "run", `${runId}.log`);
123989
+ const logPath = join46(dir, ".spur", "run", `${runId}.log`);
123438
123990
  let offset = 0;
123439
123991
  let everRead = false;
123440
123992
  while (true) {
@@ -123497,7 +124049,7 @@ init_src();
123497
124049
  init_src2();
123498
124050
  init_dist5();
123499
124051
  await init_src3();
123500
- import { dirname as dirname27, join as join45, resolve as resolve20 } from "path";
124052
+ import { dirname as dirname28, join as join47, resolve as resolve21 } from "path";
123501
124053
  import { isatty as isatty4 } from "tty";
123502
124054
 
123503
124055
  // ../../node_modules/.bun/@clack+core@1.4.1/node_modules/@clack/core/dist/index.mjs
@@ -124881,7 +125433,7 @@ class DefaultHitlResponder {
124881
125433
  // src/context.ts
124882
125434
  await init_src3();
124883
125435
  function createCliContext(options) {
124884
- const cwd = resolve20(options.cwd ?? process.cwd());
125436
+ const cwd = resolve21(options.cwd ?? process.cwd());
124885
125437
  const env = options.env ?? process.env;
124886
125438
  const fs3 = createNodeFileSystem3(cwd);
124887
125439
  const agentConfig = options.agentConfig ?? options.spurConfig?.agent;
@@ -124934,10 +125486,10 @@ function createCliContext(options) {
124934
125486
  function noopSetExitCode(_code) {}
124935
125487
  async function createMigratedDbAdapter(cwd = process.cwd(), env = process.env, dbUrl) {
124936
125488
  const config4 = buildConfigFromEnv(env);
124937
- const configuredUrl = env.DATABASE_URL === undefined ? join45(cwd, DEFAULT_DATABASE_URL) : config4.database.url;
125489
+ const configuredUrl = env.DATABASE_URL === undefined ? join47(cwd, DEFAULT_DATABASE_URL) : config4.database.url;
124938
125490
  const url2 = dbUrl ?? configuredUrl;
124939
125491
  if (url2 !== IN_MEMORY_DATABASE_URL) {
124940
- await createNodeFileSystem3().ensureDir(dirname27(url2));
125492
+ await createNodeFileSystem3().ensureDir(dirname28(url2));
124941
125493
  }
124942
125494
  return createMigratedDb({ url: url2 });
124943
125495
  }