@gobing-ai/spur 0.3.13 → 0.3.14

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 (55) hide show
  1. package/config/workflows/task-lifecycle.yaml +10 -0
  2. package/config/workflows/wayfinder-resolution.yaml +199 -0
  3. package/package.json +1 -1
  4. package/spur.js +229 -50
  5. package/web/_astro/BoardApp.BdyUxAQU.js +1 -0
  6. package/web/_astro/{BoardApp.CqBOrPzf.js → BoardApp.oOZMY52P.js} +65 -65
  7. package/web/_astro/{TaskDetail.BmxycoeS.js → TaskDetail.DZVfe6fy.js} +1 -1
  8. package/web/_astro/{arc.-ZkWbWcN.js → arc.CmzWyu5T.js} +1 -1
  9. package/web/_astro/{architectureDiagram-3BPJPVTR.fJ8ETc6n.js → architectureDiagram-3BPJPVTR.DEvXeSWP.js} +1 -1
  10. package/web/_astro/{blockDiagram-GPEHLZMM.CdhoveXr.js → blockDiagram-GPEHLZMM.DULgqAsN.js} +1 -1
  11. package/web/_astro/{c4Diagram-AAUBKEIU.CWv7Jy0p.js → c4Diagram-AAUBKEIU.CtwqGUxK.js} +1 -1
  12. package/web/_astro/channel.C6f9ncOV.js +1 -0
  13. package/web/_astro/{chunk-2J33WTMH.CvYWDwal.js → chunk-2J33WTMH.lyKyWJjV.js} +1 -1
  14. package/web/_astro/{chunk-4BX2VUAB.BVp_y1oc.js → chunk-4BX2VUAB.COAavIDd.js} +1 -1
  15. package/web/_astro/{chunk-55IACEB6.CQCviGQk.js → chunk-55IACEB6.EoRbsXM1.js} +1 -1
  16. package/web/_astro/{chunk-727SXJPM.B3E6Bisv.js → chunk-727SXJPM.DhjfPs_B.js} +1 -1
  17. package/web/_astro/{chunk-AQP2D5EJ.BvWfYj9E.js → chunk-AQP2D5EJ.BgSRWTUm.js} +1 -1
  18. package/web/_astro/{chunk-FMBD7UC4.DcLP2Ypw.js → chunk-FMBD7UC4.Dj0MuLrK.js} +1 -1
  19. package/web/_astro/{chunk-ND2GUHAM.DvXZJEqI.js → chunk-ND2GUHAM.B2_roVTb.js} +1 -1
  20. package/web/_astro/{chunk-QZHKN3VN.D4rK2HTI.js → chunk-QZHKN3VN.Cr3PSROo.js} +1 -1
  21. package/web/_astro/{classDiagram-4FO5ZUOK.DSCYtdVf.js → classDiagram-4FO5ZUOK.CXEuLRp6.js} +1 -1
  22. package/web/_astro/{classDiagram-v2-Q7XG4LA2.DSCYtdVf.js → classDiagram-v2-Q7XG4LA2.CXEuLRp6.js} +1 -1
  23. package/web/_astro/{cose-bilkent-S5V4N54A.Bf7HMV4Q.js → cose-bilkent-S5V4N54A._ulIkUyI.js} +1 -1
  24. package/web/_astro/{dagre-BM42HDAG.DsBoJTBN.js → dagre-BM42HDAG.D5SRsv4S.js} +1 -1
  25. package/web/_astro/{diagram-2AECGRRQ.BMVxdKV3.js → diagram-2AECGRRQ.rMwI7OR9.js} +1 -1
  26. package/web/_astro/{diagram-5GNKFQAL.BllW5zst.js → diagram-5GNKFQAL.DWZ2ui2V.js} +1 -1
  27. package/web/_astro/{diagram-KO2AKTUF.CNHoSD6q.js → diagram-KO2AKTUF.DuqJ_D0B.js} +1 -1
  28. package/web/_astro/{diagram-LMA3HP47.DZT6otFZ.js → diagram-LMA3HP47.BDxKBl9B.js} +1 -1
  29. package/web/_astro/{diagram-OG6HWLK6.EwFWHBRO.js → diagram-OG6HWLK6.D6IMfAPf.js} +1 -1
  30. package/web/_astro/{erDiagram-TEJ5UH35.CaXPcR5W.js → erDiagram-TEJ5UH35.DSsh9EFG.js} +1 -1
  31. package/web/_astro/{flowDiagram-I6XJVG4X.FEcfML44.js → flowDiagram-I6XJVG4X.DwjNzc4e.js} +1 -1
  32. package/web/_astro/{ganttDiagram-6RSMTGT7.Bs7t3AQM.js → ganttDiagram-6RSMTGT7.DbYs_nXA.js} +1 -1
  33. package/web/_astro/{gitGraphDiagram-PVQCEYII.DmxLV9JC.js → gitGraphDiagram-PVQCEYII.DX5-xqVb.js} +1 -1
  34. package/web/_astro/{infoDiagram-5YYISTIA.E7_NrFUE.js → infoDiagram-5YYISTIA.jP6EGbwr.js} +1 -1
  35. package/web/_astro/{ishikawaDiagram-YF4QCWOH.Bs5Q4r-U.js → ishikawaDiagram-YF4QCWOH.Cn89YzBJ.js} +1 -1
  36. package/web/_astro/{journeyDiagram-JHISSGLW.D3U7T1y1.js → journeyDiagram-JHISSGLW.CNT2WvMT.js} +1 -1
  37. package/web/_astro/{kanban-definition-UN3LZRKU.DfdWsO2E.js → kanban-definition-UN3LZRKU.CLEZjEjD.js} +1 -1
  38. package/web/_astro/{linear.CAqUTUf3.js → linear.Cn4ewR2z.js} +1 -1
  39. package/web/_astro/{mermaid.core.ZlPUJ21K.js → mermaid.core.BEACwKCZ.js} +4 -4
  40. package/web/_astro/{mindmap-definition-RKZ34NQL.ClTJkcTG.js → mindmap-definition-RKZ34NQL.BE9cnWaH.js} +1 -1
  41. package/web/_astro/{pieDiagram-4H26LBE5.B9kJfBw_.js → pieDiagram-4H26LBE5.CLcCK_JM.js} +1 -1
  42. package/web/_astro/{quadrantDiagram-W4KKPZXB.CtTJH5Fo.js → quadrantDiagram-W4KKPZXB.qw8LmkgG.js} +1 -1
  43. package/web/_astro/{requirementDiagram-4Y6WPE33.WVLq9rr2.js → requirementDiagram-4Y6WPE33.D02534qk.js} +1 -1
  44. package/web/_astro/{sankeyDiagram-5OEKKPKP.L_XUdyqe.js → sankeyDiagram-5OEKKPKP.I6nOztAU.js} +1 -1
  45. package/web/_astro/{sequenceDiagram-3UESZ5HK.DCdtsEdj.js → sequenceDiagram-3UESZ5HK.CXTuiAV8.js} +1 -1
  46. package/web/_astro/{stateDiagram-AJRCARHV.-Koi9kLh.js → stateDiagram-AJRCARHV.BV-mNJRM.js} +1 -1
  47. package/web/_astro/{stateDiagram-v2-BHNVJYJU.DHXu_l8w.js → stateDiagram-v2-BHNVJYJU.kF2_t5fy.js} +1 -1
  48. package/web/_astro/{timeline-definition-PNZ67QCA.Czotaq0j.js → timeline-definition-PNZ67QCA.BiIzvhe5.js} +1 -1
  49. package/web/_astro/{vennDiagram-CIIHVFJN.BfEI3zhc.js → vennDiagram-CIIHVFJN.9cR-p_zq.js} +1 -1
  50. package/web/_astro/{wardley-L42UT6IY.C33mahwT.js → wardley-L42UT6IY.CwwpzGfz.js} +1 -1
  51. package/web/_astro/{wardleyDiagram-YWT4CUSO.D8IzxB1w.js → wardleyDiagram-YWT4CUSO.Q3hswh_f.js} +1 -1
  52. package/web/_astro/{xychartDiagram-2RQKCTM6.D-cfi9uT.js → xychartDiagram-2RQKCTM6.Co-1OW2E.js} +1 -1
  53. package/web/index.html +1 -1
  54. package/web/_astro/BoardApp.XG2FoRlA.js +0 -1
  55. package/web/_astro/channel.BrBVc6tg.js +0 -1
package/spur.js CHANGED
@@ -48,10 +48,7 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
48
48
  var __require = import.meta.require;
49
49
 
50
50
  // ../../node_modules/.bun/@gobing-ai+ts-utils@0.4.10/node_modules/@gobing-ai/ts-utils/dist/errors.js
51
- function isAppError(error) {
52
- return error instanceof AppError;
53
- }
54
- var ErrorCode, AppError, NotFoundError;
51
+ var ErrorCode;
55
52
  var init_errors = __esm(() => {
56
53
  ErrorCode = {
57
54
  NotFound: "NOT_FOUND",
@@ -59,20 +56,6 @@ var init_errors = __esm(() => {
59
56
  Conflict: "CONFLICT",
60
57
  Internal: "INTERNAL"
61
58
  };
62
- AppError = class AppError extends Error {
63
- code;
64
- constructor(code, message) {
65
- super(message);
66
- this.name = "AppError";
67
- this.code = code;
68
- }
69
- };
70
- NotFoundError = class NotFoundError extends AppError {
71
- constructor(message) {
72
- super(ErrorCode.NotFound, message);
73
- this.name = "NotFoundError";
74
- }
75
- };
76
59
  });
77
60
 
78
61
  // ../../node_modules/.bun/@gobing-ai+ts-utils@0.4.10/node_modules/@gobing-ai/ts-utils/dist/api-response.js
@@ -58399,6 +58382,8 @@ var init_schema4 = __esm(() => {
58399
58382
  priority: exports_external.enum(PRIORITIES).optional(),
58400
58383
  tags: exports_external.array(exports_external.string()).optional(),
58401
58384
  dependencies: exports_external.array(exports_external.string()).optional(),
58385
+ done_forced: exports_external.preprocess((v) => typeof v === "string" ? v === "true" : v, exports_external.boolean().optional()),
58386
+ done_reason: exports_external.string().optional(),
58402
58387
  created_at: isoDateString,
58403
58388
  updated_at: isoDateString
58404
58389
  });
@@ -59046,6 +59031,110 @@ var init_corpus_migrator = __esm(() => {
59046
59031
  NULL_PRESERVING_KEYS = new Set(["parent_wbs"]);
59047
59032
  });
59048
59033
 
59034
+ // ../../packages/app/src/services/done-transition-guard.ts
59035
+ async function readVerdictArtifact(fs3, runDir, wbs) {
59036
+ const path9 = `${runDir}/${wbs}-verdict.json`;
59037
+ let exists2;
59038
+ try {
59039
+ exists2 = await fs3.exists(path9);
59040
+ } catch {
59041
+ exists2 = true;
59042
+ }
59043
+ if (!exists2) {
59044
+ return { artifact: undefined, path: path9 };
59045
+ }
59046
+ let raw;
59047
+ try {
59048
+ raw = await fs3.readFile(path9);
59049
+ } catch (err) {
59050
+ return { artifact: undefined, path: path9, readError: `unreadable artifact: ${err.message}` };
59051
+ }
59052
+ let parsed;
59053
+ try {
59054
+ parsed = JSON.parse(raw);
59055
+ } catch (err) {
59056
+ return { artifact: undefined, path: path9, readError: `malformed JSON: ${err.message}` };
59057
+ }
59058
+ if (typeof parsed !== "object" || parsed === null || !("verdict" in parsed)) {
59059
+ return { artifact: undefined, path: path9, readError: "missing required `verdict` field" };
59060
+ }
59061
+ return { artifact: parsed, path: path9 };
59062
+ }
59063
+ function computeAggregate(artifact) {
59064
+ const reqs = artifact.requirements ?? [];
59065
+ const acs = artifact.acceptanceCriteria ?? [];
59066
+ if (reqs.length === 0 && acs.length === 0) {
59067
+ return artifact.verdict;
59068
+ }
59069
+ if (reqs.some((r) => r.status === "UNMET") || acs.some((a2) => a2.status === "UNMET")) {
59070
+ return "FAIL";
59071
+ }
59072
+ if (reqs.some((r) => r.status === "PARTIAL") || acs.some((a2) => a2.status === "PARTIAL")) {
59073
+ return "PARTIAL";
59074
+ }
59075
+ return "PASS";
59076
+ }
59077
+ function formatDenialMessage(args) {
59078
+ const { wbs, taskFilePath, verdictPath, verdict, inconsistency, artifact } = args;
59079
+ const lines = [
59080
+ `Cannot transition task ${wbs} to done: verify verdict is ${verdict}.`,
59081
+ ` task: ${taskFilePath}`,
59082
+ ` verdict: ${verdictPath}`
59083
+ ];
59084
+ if (inconsistency) {
59085
+ lines.push(` warning: artifact is self-inconsistent \u2014 stored aggregate ${inconsistency.stored} contradicts rows (computed ${inconsistency.computed}). Treated as non-PASS.`);
59086
+ }
59087
+ const reqCount = artifact?.requirements?.length ?? 0;
59088
+ const acCount = artifact?.acceptanceCriteria?.length ?? 0;
59089
+ const rowCount = reqCount + acCount;
59090
+ if (verdict === "UNKNOWN" && artifact !== undefined && (artifact.source === "spur-task-verdict" || rowCount === 0)) {
59091
+ const source = artifact.source ?? "unknown";
59092
+ lines.push(` source: ${source} artifact contains ${rowCount} structured row${rowCount === 1 ? "" : "s"} ` + `(${reqCount} requirement${reqCount === 1 ? "" : "s"}, ${acCount} AC${acCount === 1 ? "" : "s"}). ` + `UNKNOWN means the verify answer file carried no parseable markdown tables ` + `(\`| Req | Status | Evidence |\` and \`| AC | Status | Evidence Type | Evidence |\`).`);
59093
+ lines.push(` see sp:spur-cli \`tasks/verbs.md\` \xA7Answer-file shape for the expected format. ` + `Re-run \`/sp:dev-verify ${wbs}\` so the skill authors the tables; do not loosen the parser.`);
59094
+ }
59095
+ lines.push(" remediation: re-run `/sp:dev-verify " + wbs + "` until PASS, or override with `spur task update " + wbs + ' done --force-done --reason "<why>"`.');
59096
+ return lines.join(`
59097
+ `);
59098
+ }
59099
+ function formatNoopMessage(wbs, status) {
59100
+ return `${wbs}: already ${status} \u2014 no transition`;
59101
+ }
59102
+ function evaluateDoneTransition(input) {
59103
+ const { wbs, taskFilePath, currentStatus, targetStatus, forced, reason, artifact } = input;
59104
+ if (targetStatus === currentStatus) {
59105
+ return { kind: "noop", fromStatus: currentStatus, message: formatNoopMessage(wbs, currentStatus) };
59106
+ }
59107
+ if (artifact === undefined) {
59108
+ return { kind: "allow", reason: "no-artifact" };
59109
+ }
59110
+ if (forced) {
59111
+ return { kind: "allow", reason: "forced" };
59112
+ }
59113
+ const computed = computeAggregate(artifact);
59114
+ const effective = harshnessMax(artifact.verdict, computed);
59115
+ if (effective === "PASS") {
59116
+ return { kind: "allow", reason: "pass" };
59117
+ }
59118
+ const verdictPath = `.spur/run/${wbs}-verdict.json`;
59119
+ const inconsistency = artifact.verdict !== computed ? { stored: artifact.verdict, computed } : undefined;
59120
+ return {
59121
+ kind: "deny",
59122
+ verdict: effective,
59123
+ message: formatDenialMessage({
59124
+ wbs,
59125
+ taskFilePath,
59126
+ verdictPath,
59127
+ verdict: effective,
59128
+ inconsistency,
59129
+ artifact
59130
+ })
59131
+ };
59132
+ }
59133
+ function harshnessMax(a2, b) {
59134
+ const rank = { PASS: 0, UNKNOWN: 1, PARTIAL: 2, FAIL: 3 };
59135
+ return rank[a2] >= rank[b] ? a2 : b;
59136
+ }
59137
+
59049
59138
  // ../../packages/app/src/services/event-names.ts
59050
59139
  function event(name, source, renderer, payloadPolicy = "metadata-only", tier = "default") {
59051
59140
  return {
@@ -61663,6 +61752,18 @@ var init_task_check = __esm(() => {
61663
61752
  }
61664
61753
  }
61665
61754
  async runL4(doc2, fm, findings, featuresDir, tasksDir) {
61755
+ const template = typeof fm.template === "string" ? fm.template : DEFAULT_TASK_VARIANT;
61756
+ if (template === DEFAULT_TASK_VARIANT) {
61757
+ const designBody = doc2.getSection("Design");
61758
+ if (designBody !== null && isPlaceholderBody(designBody)) {
61759
+ findings.push({
61760
+ layer: "L4",
61761
+ severity: "warning",
61762
+ section: "Design",
61763
+ message: "Design section is present but empty (placeholder-only) \u2014 populate it so the task retains an explicit implementation approach"
61764
+ });
61765
+ }
61766
+ }
61666
61767
  const featureId2 = fm.feature_id ?? fm["feature-id"];
61667
61768
  const parentWbs = fm.parent_wbs ?? fm["parent-wbs"];
61668
61769
  const deps = fm.dependencies;
@@ -62360,9 +62461,15 @@ class TaskService {
62360
62461
  return this.writeService.transition(ref, toStatus, actor ?? this.ctx.actor ?? "system");
62361
62462
  }
62362
62463
  async updateField(wbs, key, value) {
62363
- const allowed = new Set(["feature_id", "parent_wbs", "priority"]);
62364
- if (!allowed.has(key)) {
62365
- throw new Error(`Field "${key}" is not settable via update; allowed: ${[...allowed].join(", ")}.`);
62464
+ const allowed = {
62465
+ feature_id: true,
62466
+ parent_wbs: true,
62467
+ priority: true,
62468
+ done_forced: true,
62469
+ done_reason: true
62470
+ };
62471
+ if (!(key in allowed)) {
62472
+ throw new Error(`Field "${key}" is not settable via update; allowed: ${Object.keys(allowed).join(", ")}.`);
62366
62473
  }
62367
62474
  const filePath = await this.resolveTaskFile(wbs);
62368
62475
  const ref = { kind: "task", id: wbs, filePath, folder: this.ctx.tasksDir };
@@ -65068,6 +65175,7 @@ __export(exports_src2, {
65068
65175
  renderReview: () => renderReview,
65069
65176
  registerSystemEventTap: () => registerSystemEventTap,
65070
65177
  registerSpurBuiltins: () => registerSpurBuiltins,
65178
+ readVerdictArtifact: () => readVerdictArtifact,
65071
65179
  readVerdict: () => readVerdict,
65072
65180
  parseVerdict: () => parseVerdict,
65073
65181
  parsePsOutput: () => parsePsOutput,
@@ -65077,10 +65185,14 @@ __export(exports_src2, {
65077
65185
  hitlConfirmDefault: () => hitlConfirmDefault,
65078
65186
  hitlAutoApproveEnabled: () => hitlAutoApproveEnabled,
65079
65187
  hasPopulatedPriorityTable: () => hasPopulatedPriorityTable,
65188
+ formatNoopMessage: () => formatNoopMessage,
65189
+ formatDenialMessage: () => formatDenialMessage,
65080
65190
  extractSystemEventActor: () => extractSystemEventActor,
65081
65191
  extractReviewSectionBody: () => extractReviewSectionBody,
65192
+ evaluateDoneTransition: () => evaluateDoneTransition,
65082
65193
  deriveVerdict: () => deriveVerdict,
65083
65194
  createPsProcessInspector: () => createPsProcessInspector,
65195
+ computeAggregate: () => computeAggregate,
65084
65196
  clampToolUseLimit: () => clampToolUseLimit,
65085
65197
  bridgeEventBus: () => bridgeEventBus,
65086
65198
  WorkflowAppService: () => WorkflowAppService,
@@ -73122,7 +73234,7 @@ import { join as join15, resolve as resolve5 } from "path";
73122
73234
  var CLI_CONFIG = {
73123
73235
  binaryName: "spur",
73124
73236
  binaryLabel: "spur",
73125
- binaryVersion: "0.3.13",
73237
+ binaryVersion: "0.3.14",
73126
73238
  configDir: ".spur",
73127
73239
  configFile: ".spur/config.yaml",
73128
73240
  databaseFile: ".spur/spur.db"
@@ -78973,18 +79085,15 @@ var contextInjector = (appRt) => async function contextInjectorMiddleware(c3, ne
78973
79085
  await next();
78974
79086
  };
78975
79087
 
78976
- // ../server/src/middleware/error-handler.ts
78977
- init_dist();
78978
-
78979
79088
  // ../server/src/errors.ts
78980
- init_src3();
79089
+ init_errors5();
78981
79090
 
78982
79091
  // ../server/src/middleware/error-handler.ts
78983
79092
  function hasHttpStatus(err) {
78984
79093
  return typeof err === "object" && err !== null && "status" in err && typeof err.status === "number";
78985
79094
  }
78986
79095
  function isAppErrorLike(err) {
78987
- return isAppError(err);
79096
+ return typeof err === "object" && err !== null && "code" in err && typeof err.code === "string" && "message" in err && typeof err.message === "string";
78988
79097
  }
78989
79098
  function errorEnvelope(code, message, details) {
78990
79099
  return { ok: false, error: { code, message, ...details !== undefined ? { details } : {} } };
@@ -79661,7 +79770,6 @@ var contract = {
79661
79770
 
79662
79771
  // ../server/src/modules/feature/handlers.ts
79663
79772
  init_schema4();
79664
- init_dist();
79665
79773
  var os2 = implement(contract);
79666
79774
  function createResponseShape(r2) {
79667
79775
  return { id: r2.ref.id, filePath: r2.ref.filePath };
@@ -79684,7 +79792,7 @@ function createFeatureHandlers(ctx) {
79684
79792
  show: os2.feature.show.handler(async ({ input }) => {
79685
79793
  const result = await ctx.featureService().show(input.id);
79686
79794
  if (!result)
79687
- throw new NotFoundError(`Feature ${input.id} not found`);
79795
+ throw new HTTPException(404, { message: `Feature ${input.id} not found` });
79688
79796
  return {
79689
79797
  ok: true,
79690
79798
  data: {
@@ -79713,7 +79821,7 @@ function createFeatureHandlers(ctx) {
79713
79821
  const folders = ctx.planningFolders();
79714
79822
  const feature = await ctx.featureService().show(input.id);
79715
79823
  if (!feature)
79716
- throw new NotFoundError(`Feature ${input.id} not found`);
79824
+ throw new HTTPException(404, { message: `Feature ${input.id} not found` });
79717
79825
  const { FeatureCheckService: FeatureCheckService2 } = await Promise.resolve().then(() => (init_feature_check(), exports_feature_check));
79718
79826
  const svc = new FeatureCheckService2(ctx.fs);
79719
79827
  return {
@@ -79727,7 +79835,7 @@ function createFeatureHandlers(ctx) {
79727
79835
  body: os2.feature.body.handler(async ({ input }) => {
79728
79836
  const feature = await ctx.featureService().show(input.id);
79729
79837
  if (!feature)
79730
- throw new NotFoundError(`Feature ${input.id} not found`);
79838
+ throw new HTTPException(404, { message: `Feature ${input.id} not found` });
79731
79839
  await ctx.featureService().updateBody(input.id, input.body);
79732
79840
  return { ok: true, data: {} };
79733
79841
  }),
@@ -79899,17 +80007,33 @@ function parseLimit(raw2, fallback) {
79899
80007
  }
79900
80008
 
79901
80009
  // ../server/src/modules/observability/index.ts
79902
- init_src3();
79903
80010
  var ledgerWatcher;
80011
+ var ledgerWatcherLoad;
79904
80012
  var watcherPath;
79905
- function getLedgerWatcher(path9) {
79906
- if (!ledgerWatcher || watcherPath !== path9) {
79907
- ledgerWatcher?.stop();
79908
- ledgerWatcher = new TokenLedgerWatcher({ ledgerPath: path9 });
79909
- watcherPath = path9;
79910
- ledgerWatcher.start();
80013
+ async function getLedgerWatcher(path9) {
80014
+ if (watcherPath === path9) {
80015
+ if (ledgerWatcher)
80016
+ return ledgerWatcher;
80017
+ if (ledgerWatcherLoad)
80018
+ return ledgerWatcherLoad;
80019
+ }
80020
+ ledgerWatcher?.stop();
80021
+ ledgerWatcher = undefined;
80022
+ watcherPath = path9;
80023
+ const load2 = Promise.resolve().then(() => (init_src3(), exports_src2)).then(({ TokenLedgerWatcher: TokenLedgerWatcher2 }) => {
80024
+ const watcher2 = new TokenLedgerWatcher2({ ledgerPath: path9 });
80025
+ watcher2.start();
80026
+ return watcher2;
80027
+ });
80028
+ ledgerWatcherLoad = load2;
80029
+ const watcher = await load2;
80030
+ if (watcherPath === path9 && ledgerWatcherLoad === load2) {
80031
+ ledgerWatcher = watcher;
80032
+ ledgerWatcherLoad = undefined;
80033
+ } else {
80034
+ watcher.stop();
79911
80035
  }
79912
- return ledgerWatcher;
80036
+ return watcher;
79913
80037
  }
79914
80038
  function toolUseSsePayload(event2) {
79915
80039
  return {
@@ -79924,7 +80048,7 @@ function handleProcesses(ctx) {
79924
80048
  const snapshot = await ctx.processInventory().snapshot();
79925
80049
  return c3.json(snapshot);
79926
80050
  } catch (err) {
79927
- if (err instanceof UnsupportedProcessPlatformError) {
80051
+ if (err instanceof Error && "code" in err && err.code === "UNSUPPORTED_PLATFORM") {
79928
80052
  return c3.json({ error: err.message, code: err.code }, 501);
79929
80053
  }
79930
80054
  const message = err instanceof Error ? err.message : String(err);
@@ -79943,7 +80067,7 @@ function handleToolUseGet(ctx) {
79943
80067
  limitRaw = parsed;
79944
80068
  }
79945
80069
  const before = c3.req.query("before") ?? undefined;
79946
- const limit = clampToolUseLimit(limitRaw);
80070
+ const limit = limitRaw === undefined || !Number.isFinite(limitRaw) || limitRaw <= 0 ? 200 : Math.min(limitRaw, 1000);
79947
80071
  const snapshot = ctx.tokenLedger().snapshot({ limit, before });
79948
80072
  return c3.json(snapshot);
79949
80073
  } catch (err) {
@@ -79972,7 +80096,7 @@ function handleToolUseStream(ctx) {
79972
80096
  closeController();
79973
80097
  };
79974
80098
  const stream = new ReadableStream({
79975
- start(controller) {
80099
+ async start(controller) {
79976
80100
  const encoder = new TextEncoder;
79977
80101
  closeController = () => {
79978
80102
  try {
@@ -79985,17 +80109,17 @@ function handleToolUseStream(ctx) {
79985
80109
  }
79986
80110
  signal.addEventListener("abort", teardown);
79987
80111
  heartbeatInterval = setInterval(sendSseKeepalive, 15000, closed, controller, encoder);
79988
- enqueueSseFrame(closed, controller, encoder, {
79989
- type: "connected",
79990
- occurredAt: new Date().toISOString()
79991
- });
79992
80112
  try {
79993
80113
  const path9 = ctx.tokenLedger().path;
79994
- const watcher = getLedgerWatcher(path9);
80114
+ const watcher = await getLedgerWatcher(path9);
79995
80115
  unsubscribe = watcher.subscribe((event2) => {
79996
80116
  if (!enqueueSseFrame(closed, controller, encoder, toolUseSsePayload(event2)))
79997
80117
  teardown();
79998
80118
  });
80119
+ enqueueSseFrame(closed, controller, encoder, {
80120
+ type: "connected",
80121
+ occurredAt: new Date().toISOString()
80122
+ });
79999
80123
  } catch {
80000
80124
  enqueueSseFrame(closed, controller, encoder, {
80001
80125
  type: "error",
@@ -80027,7 +80151,6 @@ var observabilityModule = {
80027
80151
 
80028
80152
  // ../server/src/modules/task/handlers.ts
80029
80153
  init_schema4();
80030
- init_dist();
80031
80154
  var os3 = implement(contract);
80032
80155
  function toFilters(query) {
80033
80156
  if (!query)
@@ -80068,7 +80191,7 @@ function createTaskHandlers(ctx) {
80068
80191
  show: os3.task.show.handler(async ({ input }) => {
80069
80192
  const result = await ctx.taskService().show(input.wbs);
80070
80193
  if (!result)
80071
- throw new NotFoundError(`Task ${input.wbs} not found`);
80194
+ throw new HTTPException(404, { message: `Task ${input.wbs} not found` });
80072
80195
  return {
80073
80196
  ok: true,
80074
80197
  data: {
@@ -83546,6 +83669,13 @@ var STATUS_TITLE = {
83546
83669
  done: "Done",
83547
83670
  cancelled: "Canceled"
83548
83671
  };
83672
+ function canonicalStatusOrRaw(raw2) {
83673
+ try {
83674
+ return normalizeTaskStatus(raw2);
83675
+ } catch {
83676
+ return raw2;
83677
+ }
83678
+ }
83549
83679
  function renderTaskBoard(tasks, boardTitle, color, columns2) {
83550
83680
  const lines = ["", ` ${color.blue(`Kanban Board \u2014 ${boardTitle}`)}`, ""];
83551
83681
  for (const status of columns2) {
@@ -83631,7 +83761,7 @@ ${result.content}`);
83631
83761
  context4.setExitCode(1);
83632
83762
  }
83633
83763
  });
83634
- task.command("update").summary("Update a task status or replace a section.").argument("<wbs>", "Task WBS number").argument("[status]", "New status (for lifecycle transition)").option("--section <name>", "Section name to replace").option("--from-file <path>", "File to read section body from (requires --section)").option("--feature <id>", "Set the feature_id frontmatter field (traceability edge)").option("--priority <p>", "Set the priority frontmatter field (P0\u2013P3)").option("--no-lifecycle", "Suppress lifecycle workflow run creation (use during pipeline runs to avoid orphaned lifecycle runs)").option("--folder <path>", "Custom tasks folder").option("--json", "Output machine-readable JSON").action(async (wbs, status, options) => {
83764
+ task.command("update").summary("Update a task status or replace a section.").argument("<wbs>", "Task WBS number").argument("[status]", "New status (for lifecycle transition)").option("--section <name>", "Section name to replace").option("--from-file <path>", "File to read section body from (requires --section)").option("--feature <id>", "Set the feature_id frontmatter field (traceability edge)").option("--priority <p>", "Set the priority frontmatter field (P0\u2013P3)").option("--no-lifecycle", "Suppress lifecycle workflow run creation (use during pipeline runs to avoid orphaned lifecycle runs)").option("--force-done", "Allow transitioning to `done` even when the verify verdict is not PASS; records an override (task 0292)").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("--folder <path>", "Custom tasks folder").option("--json", "Output machine-readable JSON").action(async (wbs, status, options) => {
83635
83765
  const svc = await makeService2(context4, options.folder, options.lifecycle === false);
83636
83766
  try {
83637
83767
  if (options.section !== undefined) {
@@ -83659,6 +83789,7 @@ ${result.content}`);
83659
83789
  context4.output.write(`Set ${key}=${value2} on task ${result.ref.id}`);
83660
83790
  }
83661
83791
  } else if (status !== undefined) {
83792
+ status = canonicalStatusOrRaw(status);
83662
83793
  if (options.lifecycle !== false && (status === "done" || status === "testing")) {
83663
83794
  const adapter = makeLifecycleAdapter(context4, TASK_LIFECYCLE_PROFILE);
83664
83795
  if (adapter === undefined) {
@@ -83671,11 +83802,59 @@ ${result.content}`);
83671
83802
  }
83672
83803
  }
83673
83804
  }
83805
+ let forcedDone = false;
83806
+ let forcedDoneReason;
83807
+ let forcedDoneVerdict;
83808
+ if (status === "done") {
83809
+ const current = await svc.show(wbs);
83810
+ const verdictDir = options.verdictDir ?? join22(context4.cwd, ".spur", "run");
83811
+ const loaded = await readVerdictArtifact(context4.fs, verdictDir, wbs);
83812
+ const guardOutcome = evaluateDoneTransition({
83813
+ wbs,
83814
+ taskFilePath: current.filePath,
83815
+ currentStatus: canonicalStatusOrRaw(String(current.frontmatter.status)),
83816
+ targetStatus: "done",
83817
+ forced: options.forceDone === true,
83818
+ reason: options.reason,
83819
+ artifact: loaded.artifact
83820
+ });
83821
+ if (guardOutcome.kind === "noop") {
83822
+ if (options.json) {
83823
+ context4.output.write(toJson2({ ok: true, noop: true, wbs, status: "done" }));
83824
+ } else {
83825
+ context4.output.write(guardOutcome.message);
83826
+ }
83827
+ return;
83828
+ }
83829
+ if (guardOutcome.kind === "deny") {
83830
+ context4.output.error(guardOutcome.message);
83831
+ context4.setExitCode(1);
83832
+ return;
83833
+ }
83834
+ if (guardOutcome.reason === "forced" && loaded.artifact !== undefined) {
83835
+ forcedDone = true;
83836
+ forcedDoneReason = options.reason;
83837
+ forcedDoneVerdict = loaded.artifact.verdict;
83838
+ }
83839
+ }
83674
83840
  const result = await svc.updateStatus(wbs, status);
83841
+ if (forcedDone) {
83842
+ try {
83843
+ await svc.updateField(wbs, "done_forced", "true");
83844
+ if (forcedDoneReason !== undefined && forcedDoneReason.length > 0) {
83845
+ await svc.updateField(wbs, "done_reason", forcedDoneReason);
83846
+ }
83847
+ } catch (auditErr) {
83848
+ context4.output.error(`warning: failed to record done-forced audit fields: ${String(auditErr)}`);
83849
+ }
83850
+ }
83675
83851
  if (options.json) {
83676
83852
  context4.output.write(toJson2(result));
83677
83853
  } else {
83678
83854
  context4.output.write(`${result.ref.id}: ${result.fromStatus} \u2192 ${result.toStatus}`);
83855
+ if (forcedDone && forcedDoneVerdict !== undefined) {
83856
+ context4.output.write(`\u24D8 Override recorded: task advanced to done despite ${forcedDoneVerdict} verdict (done_forced=true).`);
83857
+ }
83679
83858
  }
83680
83859
  if (status === "done" && !options.json) {
83681
83860
  const task2 = await svc.show(wbs);
@@ -0,0 +1 @@
1
+ import{a as t}from"./BoardApp.oOZMY52P.js";import"./index.A0eX93qW.js";export{t as default};