@gobing-ai/spur 0.3.8 → 0.3.10

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 (123) hide show
  1. package/README.md +18 -2
  2. package/{spur-cli/config → config}/rules/boundary/sp-runtime-path.yaml +9 -0
  3. package/package.json +4 -3
  4. package/spur.js +580 -98
  5. package/web/_astro/BoardApp.C6AZyK1W.js +179 -0
  6. package/web/_astro/BoardApp.EXjc9f_0.js +1 -0
  7. package/web/_astro/BoardApp.t7x-dYDo.css +1 -0
  8. package/web/_astro/TaskDetail.qCmrwQkG.js +1 -0
  9. package/web/_astro/arc.CbRppOdF.js +1 -0
  10. package/web/_astro/architectureDiagram-3BPJPVTR.9PfIcrsr.js +36 -0
  11. package/web/_astro/blockDiagram-GPEHLZMM.D8X1aH9F.js +132 -0
  12. package/web/_astro/c4Diagram-AAUBKEIU.BnAemEBs.js +10 -0
  13. package/web/_astro/channel.DDlSVLNA.js +1 -0
  14. package/web/_astro/chunk-2J33WTMH.DzOqcSmH.js +1 -0
  15. package/web/_astro/chunk-4BX2VUAB.AqtW-DKu.js +1 -0
  16. package/web/_astro/chunk-55IACEB6.BIRjE6jR.js +1 -0
  17. package/web/_astro/chunk-727SXJPM.CgDzPlSk.js +206 -0
  18. package/web/_astro/chunk-AQP2D5EJ.DjOW-ScW.js +231 -0
  19. package/web/_astro/chunk-FMBD7UC4.wdJmHym6.js +15 -0
  20. package/web/_astro/chunk-ND2GUHAM.C-Ho8u3p.js +1 -0
  21. package/web/_astro/chunk-QZHKN3VN.BF1HouUh.js +1 -0
  22. package/web/_astro/classDiagram-4FO5ZUOK.DAyOMMHn.js +1 -0
  23. package/web/_astro/classDiagram-v2-Q7XG4LA2.DAyOMMHn.js +1 -0
  24. package/web/_astro/client.CdFpTatq.js +9 -0
  25. package/web/_astro/cose-bilkent-S5V4N54A.CNoutjqT.js +1 -0
  26. package/web/_astro/cytoscape.esm.D3_iZ_3b.js +321 -0
  27. package/web/_astro/dagre-BM42HDAG.yQbaJnir.js +4 -0
  28. package/web/_astro/defaultLocale.CrowFXzY.js +1 -0
  29. package/web/_astro/diagram-2AECGRRQ.Y-0ZX0wP.js +43 -0
  30. package/web/_astro/diagram-5GNKFQAL.Co7hW1JF.js +10 -0
  31. package/web/_astro/diagram-KO2AKTUF.BW6g9zEB.js +3 -0
  32. package/web/_astro/diagram-LMA3HP47.5laYcnBs.js +24 -0
  33. package/web/_astro/diagram-OG6HWLK6.DOXfn0B3.js +24 -0
  34. package/web/_astro/erDiagram-TEJ5UH35.B65zPLW1.js +85 -0
  35. package/web/_astro/flowDiagram-I6XJVG4X.EyV3nzyc.js +162 -0
  36. package/web/_astro/ganttDiagram-6RSMTGT7.Dbg3JgqV.js +292 -0
  37. package/web/_astro/gitGraphDiagram-PVQCEYII.C_h4o8Bn.js +106 -0
  38. package/web/_astro/graph.D2o_JWn5.js +1 -0
  39. package/web/_astro/index.A0eX93qW.js +1 -0
  40. package/web/_astro/index.BSoQ0ZL0.css +1 -0
  41. package/web/_astro/infoDiagram-5YYISTIA.DmMa9Qku.js +2 -0
  42. package/web/_astro/init.Gi6I4Gst.js +1 -0
  43. package/web/_astro/ishikawaDiagram-YF4QCWOH.GWMrgoLC.js +70 -0
  44. package/web/_astro/journeyDiagram-JHISSGLW.BWABlU6N.js +139 -0
  45. package/web/_astro/kanban-definition-UN3LZRKU.MbfHFA2-.js +89 -0
  46. package/web/_astro/katex.HP8lGamR.js +257 -0
  47. package/web/_astro/layout.DNLMvjEt.js +1 -0
  48. package/web/_astro/linear.BmIHCCO9.js +1 -0
  49. package/web/_astro/mermaid.core.CpHEEQAK.js +301 -0
  50. package/web/_astro/mindmap-definition-RKZ34NQL.DYK94Ave.js +96 -0
  51. package/web/_astro/ordinal.BYWQX77i.js +1 -0
  52. package/web/_astro/pieDiagram-4H26LBE5.CnEGAkqK.js +30 -0
  53. package/web/_astro/quadrantDiagram-W4KKPZXB.CsByTJjg.js +7 -0
  54. package/web/_astro/requirementDiagram-4Y6WPE33.AL6ZRSrQ.js +84 -0
  55. package/web/_astro/sankeyDiagram-5OEKKPKP.BLAeuWlE.js +40 -0
  56. package/web/_astro/sequenceDiagram-3UESZ5HK.BGpsvxUR.js +162 -0
  57. package/web/_astro/stateDiagram-AJRCARHV.BBk0gd25.js +1 -0
  58. package/web/_astro/stateDiagram-v2-BHNVJYJU.BrqvmSUS.js +1 -0
  59. package/web/_astro/timeline-definition-PNZ67QCA.DkZf5CQT.js +120 -0
  60. package/web/_astro/vennDiagram-CIIHVFJN.BZvWwMZF.js +34 -0
  61. package/web/_astro/wardley-L42UT6IY.HfB08mt3.js +161 -0
  62. package/web/_astro/wardleyDiagram-YWT4CUSO.DgpZzmgA.js +78 -0
  63. package/web/_astro/xychartDiagram-2RQKCTM6.BlgrPsbQ.js +7 -0
  64. package/web/favicon.svg +4 -0
  65. package/web/index.html +2 -0
  66. /package/{spur-cli/config → config}/config.example.yaml +0 -0
  67. /package/{spur-cli/config → config}/plugins/.gitkeep +0 -0
  68. /package/{spur-cli/config → config}/rules/README.md +0 -0
  69. /package/{spur-cli/config → config}/rules/boundary/config-loading-ownership.yaml +0 -0
  70. /package/{spur-cli/config → config}/rules/boundary/dao-boundary.yaml +0 -0
  71. /package/{spur-cli/config → config}/rules/boundary/planning-folder-hardcode.yaml +0 -0
  72. /package/{spur-cli/config → config}/rules/boundary/sp-no-vendor-refs.yaml +0 -0
  73. /package/{spur-cli/config → config}/rules/migration/rg-dialect.yaml +0 -0
  74. /package/{spur-cli/config → config}/rules/quality/coverage-gate.yaml +0 -0
  75. /package/{spur-cli/config → config}/rules/quality/tsdoc-exports.yaml +0 -0
  76. /package/{spur-cli/config → config}/rules/recommended-post-check.yaml +0 -0
  77. /package/{spur-cli/config → config}/rules/recommended-pre-check.yaml +0 -0
  78. /package/{spur-cli/config → config}/rules/rg-migration.yaml +0 -0
  79. /package/{spur-cli/config → config}/rules/strict/http-boundaries.yaml +0 -0
  80. /package/{spur-cli/config → config}/rules/strict/rule-files-structural.yaml +0 -0
  81. /package/{spur-cli/config → config}/rules/strict/runtime-boundaries.yaml +0 -0
  82. /package/{spur-cli/config → config}/rules/strict-check.yaml +0 -0
  83. /package/{spur-cli/config → config}/rules/structure/protected-files.yaml +0 -0
  84. /package/{spur-cli/config → config}/rules/structure/test-focus-skip.yaml +0 -0
  85. /package/{spur-cli/config → config}/rules/structure/test-location.yaml +0 -0
  86. /package/{spur-cli/config → config}/rules/surface/check-cli-surface.yaml +0 -0
  87. /package/{spur-cli/config → config}/rules/typescript/bun-tooling.yaml +0 -0
  88. /package/{spur-cli/config → config}/rules/typescript/guarded-happy-dom-register.yaml +0 -0
  89. /package/{spur-cli/config → config}/rules/typescript/happy-dom-teardown.yaml +0 -0
  90. /package/{spur-cli/config → config}/rules/typescript/no-biome-suppressions.yaml +0 -0
  91. /package/{spur-cli/config → config}/rules/typescript/no-debugger.yaml +0 -0
  92. /package/{spur-cli/config → config}/rules/typescript/no-leaky-module-mocks.yaml +0 -0
  93. /package/{spur-cli/config → config}/rules/typescript/no-module-scope-import-calls.yaml +0 -0
  94. /package/{spur-cli/config → config}/rules/typescript/no-unmocked-module-eval-side-effects.yaml +0 -0
  95. /package/{spur-cli/config → config}/rules/typescript/output-boundaries.yaml +0 -0
  96. /package/{spur-cli/config → config}/rules/ui/ui-import-boundary.yaml +0 -0
  97. /package/{spur-cli/config → config}/tasks/section-matrix.yaml +0 -0
  98. /package/{spur-cli/config → config}/templates/AGENTS.md +0 -0
  99. /package/{spur-cli/config → config}/templates/bdd/checklist.md +0 -0
  100. /package/{spur-cli/config → config}/templates/bdd/gherkin.md +0 -0
  101. /package/{spur-cli/config → config}/templates/docs/00_ADR.md +0 -0
  102. /package/{spur-cli/config → config}/templates/docs/01_PRD.md +0 -0
  103. /package/{spur-cli/config → config}/templates/docs/02_ROADMAP.md +0 -0
  104. /package/{spur-cli/config → config}/templates/docs/03_ARCHITECTURE.md +0 -0
  105. /package/{spur-cli/config → config}/templates/docs/04_DESIGN.md +0 -0
  106. /package/{spur-cli/config → config}/templates/docs/05_FEATURES.md +0 -0
  107. /package/{spur-cli/config → config}/templates/docs/99_PROJECT_CONSTITUTION.md +0 -0
  108. /package/{spur-cli/config → config}/templates/feature/default.md +0 -0
  109. /package/{spur-cli/config → config}/templates/task/brainstorm.md +0 -0
  110. /package/{spur-cli/config → config}/templates/task/feature-impl.md +0 -0
  111. /package/{spur-cli/config → config}/templates/task/issue.md +0 -0
  112. /package/{spur-cli/config → config}/templates/task/meta.md +0 -0
  113. /package/{spur-cli/config → config}/templates/task/review.md +0 -0
  114. /package/{spur-cli/config → config}/templates/task/standard.md +0 -0
  115. /package/{spur-cli/config → config}/workflows/basic.yaml +0 -0
  116. /package/{spur-cli/config → config}/workflows/docs-pipeline.yaml +0 -0
  117. /package/{spur-cli/config → config}/workflows/feature-dev.yaml +0 -0
  118. /package/{spur-cli/config → config}/workflows/feature-lifecycle.yaml +0 -0
  119. /package/{spur-cli/config → config}/workflows/idea-pipeline.yaml +0 -0
  120. /package/{spur-cli/config → config}/workflows/planning-pipeline.yaml +0 -0
  121. /package/{spur-cli/config → config}/workflows/task-lifecycle.yaml +0 -0
  122. /package/{spur-cli/config → config}/workflows/task-pipeline.yaml +0 -0
  123. /package/{spur-cli/config → config}/workflows/wrapup-pipeline.yaml +0 -0
package/spur.js CHANGED
@@ -38376,9 +38376,16 @@ function walk(dir, relPrefix, filter = /\.(ya?ml|json)$/i) {
38376
38376
  }
38377
38377
  return acc;
38378
38378
  }
38379
- var BUNDLED_CONFIG_DIRS, cachedRoot;
38379
+ function listBundledProjectSeedFiles() {
38380
+ const root = bundledConfigRoot();
38381
+ if (root === null)
38382
+ return [];
38383
+ return walk(root, "", PROJECT_SEED_FILTER).filter((rel) => rel !== BUNDLED_CONFIG_EXAMPLE).sort();
38384
+ }
38385
+ var BUNDLED_CONFIG_DIRS, cachedRoot, PROJECT_SEED_FILTER, BUNDLED_CONFIG_EXAMPLE = "config.example.yaml";
38380
38386
  var init_bundled_config = __esm(() => {
38381
38387
  BUNDLED_CONFIG_DIRS = ["config", "spur-cli/config"];
38388
+ PROJECT_SEED_FILTER = /\.(ya?ml|json|md|gitkeep)$/i;
38382
38389
  });
38383
38390
 
38384
38391
  // ../../packages/config/src/template-renderer.ts
@@ -60758,6 +60765,41 @@ var init_system_event_tap = __esm(() => {
60758
60765
  init_event_names();
60759
60766
  });
60760
60767
 
60768
+ // ../../packages/app/src/services/system-event-emitter.ts
60769
+ class SystemEventEmitter {
60770
+ dao;
60771
+ logger;
60772
+ constructor(dao2, logger3) {
60773
+ this.dao = dao2;
60774
+ this.logger = logger3;
60775
+ }
60776
+ async emit(event2) {
60777
+ const entry = systemEventCatalogEntry(event2.event);
60778
+ if (!entry)
60779
+ return;
60780
+ try {
60781
+ await this.dao.insert({
60782
+ id: createId("sev"),
60783
+ event_name: entry.name,
60784
+ occurred_at: event2.at,
60785
+ actor: extractSystemEventActor(event2),
60786
+ payload_json: safeStringify(normalizeSystemEventPayload(entry, event2))
60787
+ });
60788
+ await this.dao.prune(SYSTEM_EVENTS_CAP);
60789
+ } catch (error51) {
60790
+ this.logger.warn("system_events emitter: persist failed", {
60791
+ event: event2.event,
60792
+ error: error51 instanceof Error ? `${error51.name}: ${error51.message}` : String(error51)
60793
+ });
60794
+ }
60795
+ }
60796
+ }
60797
+ var init_system_event_emitter = __esm(() => {
60798
+ init_src2();
60799
+ init_event_names();
60800
+ init_system_event_tap();
60801
+ });
60802
+
60761
60803
  // ../../packages/app/src/services/task-check.ts
60762
60804
  import { dirname as dirname9, join as join9 } from "path";
60763
60805
  function isPlaceholderBody(body) {
@@ -62475,11 +62517,282 @@ var init_team_service = __esm(() => {
62475
62517
  init_planning_folders();
62476
62518
  });
62477
62519
 
62520
+ // ../../packages/app/src/services/token-ledger-service.ts
62521
+ import { closeSync as closeSync2, existsSync as existsSync5, fstatSync, openSync as openSync2, readSync } from "fs";
62522
+ import { join as join11 } from "path";
62523
+ function isSparseToolActivity(events2) {
62524
+ return events2.length === 0 || !events2.some((e) => TOOL_ACTIVITY_TYPES.has(e.type));
62525
+ }
62526
+ function clampToolUseLimit(raw) {
62527
+ if (raw === undefined || !Number.isFinite(raw) || raw <= 0) {
62528
+ return TOKEN_LEDGER_DEFAULT_LIMIT;
62529
+ }
62530
+ return Math.min(Math.floor(raw), TOKEN_LEDGER_MAX_LIMIT);
62531
+ }
62532
+ function parseLedgerLine(line) {
62533
+ const trimmed = line.trim();
62534
+ if (!trimmed)
62535
+ return null;
62536
+ try {
62537
+ const obj = JSON.parse(trimmed);
62538
+ if (typeof obj.ts !== "string" || typeof obj.session !== "string" || typeof obj.type !== "string") {
62539
+ return null;
62540
+ }
62541
+ const event2 = {
62542
+ ts: obj.ts,
62543
+ session: obj.session,
62544
+ type: obj.type
62545
+ };
62546
+ if (typeof obj.file === "string")
62547
+ event2.file = obj.file;
62548
+ if (typeof obj.summary === "string")
62549
+ event2.summary = obj.summary;
62550
+ if (typeof obj.tokens === "number" && Number.isFinite(obj.tokens) && obj.tokens >= 0) {
62551
+ event2.tokens = obj.tokens;
62552
+ }
62553
+ if (typeof obj.action === "string")
62554
+ event2.action = obj.action;
62555
+ if (typeof obj.sessionId === "string")
62556
+ event2.sessionId = obj.sessionId;
62557
+ if (typeof obj.agent === "string")
62558
+ event2.agent = obj.agent;
62559
+ if (typeof obj.model === "string")
62560
+ event2.model = obj.model;
62561
+ if (obj.totals !== null && typeof obj.totals === "object") {
62562
+ const t = obj.totals;
62563
+ if (typeof t.reads === "number" && typeof t.writes === "number" && typeof t.tokens === "number") {
62564
+ event2.totals = { reads: t.reads, writes: t.writes, tokens: t.tokens };
62565
+ }
62566
+ }
62567
+ return event2;
62568
+ } catch {
62569
+ return null;
62570
+ }
62571
+ }
62572
+ function tailTokenLedgerFile(filePath, limit, chunkSize = DEFAULT_CHUNK, before) {
62573
+ if (!existsSync5(filePath)) {
62574
+ return { events: [], truncated: false };
62575
+ }
62576
+ const target = limit + 1;
62577
+ const fd = openSync2(filePath, "r");
62578
+ try {
62579
+ const { size } = fstatSync(fd);
62580
+ if (size === 0)
62581
+ return { events: [], truncated: false };
62582
+ let pos = size;
62583
+ let carry = "";
62584
+ const events2 = [];
62585
+ while (pos > 0 && events2.length < target) {
62586
+ const n2 = Math.min(chunkSize, pos);
62587
+ pos -= n2;
62588
+ const buf = Buffer.alloc(n2);
62589
+ readSync(fd, buf, 0, n2, pos);
62590
+ const text4 = buf.toString("utf8") + carry;
62591
+ const parts = text4.split(`
62592
+ `);
62593
+ if (pos > 0) {
62594
+ carry = parts.shift() ?? "";
62595
+ } else {
62596
+ carry = "";
62597
+ }
62598
+ for (let i2 = parts.length - 1;i2 >= 0; i2--) {
62599
+ const evt = parseLedgerLine(parts[i2] ?? "");
62600
+ if (!evt)
62601
+ continue;
62602
+ if (before !== undefined && evt.ts >= before)
62603
+ continue;
62604
+ events2.push(evt);
62605
+ if (events2.length >= target)
62606
+ break;
62607
+ }
62608
+ }
62609
+ const truncated = events2.length > limit;
62610
+ return {
62611
+ events: events2.slice(0, limit),
62612
+ truncated
62613
+ };
62614
+ } finally {
62615
+ closeSync2(fd);
62616
+ }
62617
+ }
62618
+
62619
+ class TokenLedgerService {
62620
+ ledgerPath;
62621
+ chunkSize;
62622
+ constructor(options) {
62623
+ this.ledgerPath = options.ledgerPath ?? join11(options.cwd, TOKEN_LEDGER_RELATIVE_PATH);
62624
+ this.chunkSize = options.chunkSize ?? DEFAULT_CHUNK;
62625
+ }
62626
+ get path() {
62627
+ return this.ledgerPath;
62628
+ }
62629
+ snapshot(limitRawOrOpts, maybeBefore) {
62630
+ let limitRaw;
62631
+ let before;
62632
+ if (typeof limitRawOrOpts === "object" && limitRawOrOpts !== null) {
62633
+ limitRaw = limitRawOrOpts.limit;
62634
+ before = limitRawOrOpts.before;
62635
+ } else {
62636
+ limitRaw = limitRawOrOpts;
62637
+ before = maybeBefore;
62638
+ }
62639
+ if (before !== undefined && (typeof before !== "string" || before.length === 0)) {
62640
+ before = undefined;
62641
+ }
62642
+ const limit = clampToolUseLimit(limitRaw);
62643
+ const capturedAt = new Date().toISOString();
62644
+ const { events: raw, truncated } = tailTokenLedgerFile(this.ledgerPath, limit, this.chunkSize, before);
62645
+ const events2 = raw.map((e, seq) => ({ ...e, seq }));
62646
+ const sparseToolActivity = isSparseToolActivity(events2);
62647
+ const oldest = events2.length > 0 ? events2[events2.length - 1] : undefined;
62648
+ const nextBefore = truncated && oldest ? oldest.ts : null;
62649
+ return {
62650
+ events: events2,
62651
+ count: events2.length,
62652
+ limit,
62653
+ truncated,
62654
+ path: this.ledgerPath,
62655
+ capturedAt,
62656
+ sparseToolActivity,
62657
+ nextBefore
62658
+ };
62659
+ }
62660
+ }
62661
+ var TOOL_ACTIVITY_TYPES, TOKEN_LEDGER_DEFAULT_LIMIT = 200, TOKEN_LEDGER_MAX_LIMIT = 1000, TOKEN_LEDGER_RELATIVE_PATH, DEFAULT_CHUNK;
62662
+ var init_token_ledger_service = __esm(() => {
62663
+ TOOL_ACTIVITY_TYPES = new Set(["read", "write", "bash", "grep", "glob"]);
62664
+ TOKEN_LEDGER_RELATIVE_PATH = join11(".spur", "context", "token-ledger.jsonl");
62665
+ DEFAULT_CHUNK = 64 * 1024;
62666
+ });
62667
+
62668
+ // ../../packages/app/src/services/token-ledger-watcher.ts
62669
+ import { closeSync as closeSync3, existsSync as existsSync6, fstatSync as fstatSync2, openSync as openSync3, readSync as readSync2, watch } from "fs";
62670
+
62671
+ class TokenLedgerWatcher {
62672
+ ledgerPath;
62673
+ debounceMs;
62674
+ listeners = new Set;
62675
+ watcher;
62676
+ offset = 0;
62677
+ debounceTimer;
62678
+ started = false;
62679
+ carry = "";
62680
+ constructor(options) {
62681
+ this.ledgerPath = options.ledgerPath;
62682
+ this.debounceMs = options.debounceMs ?? 50;
62683
+ }
62684
+ start() {
62685
+ if (this.started)
62686
+ return;
62687
+ this.started = true;
62688
+ this.offset = this.currentSize();
62689
+ try {
62690
+ const target = existsSync6(this.ledgerPath) ? this.ledgerPath : this.parentDir();
62691
+ this.watcher = watch(target, () => this.schedulePoll());
62692
+ } catch {
62693
+ this.watcher = undefined;
62694
+ }
62695
+ }
62696
+ stop() {
62697
+ this.started = false;
62698
+ if (this.debounceTimer)
62699
+ clearTimeout(this.debounceTimer);
62700
+ this.debounceTimer = undefined;
62701
+ try {
62702
+ this.watcher?.close();
62703
+ } catch {}
62704
+ this.watcher = undefined;
62705
+ this.listeners.clear();
62706
+ }
62707
+ subscribe(listener) {
62708
+ this.listeners.add(listener);
62709
+ if (!this.started)
62710
+ this.start();
62711
+ return () => {
62712
+ this.listeners.delete(listener);
62713
+ if (this.listeners.size === 0)
62714
+ this.stop();
62715
+ };
62716
+ }
62717
+ get subscriberCount() {
62718
+ return this.listeners.size;
62719
+ }
62720
+ parentDir() {
62721
+ const idx = Math.max(this.ledgerPath.lastIndexOf("/"), this.ledgerPath.lastIndexOf("\\"));
62722
+ return idx > 0 ? this.ledgerPath.slice(0, idx) : ".";
62723
+ }
62724
+ currentSize() {
62725
+ if (!existsSync6(this.ledgerPath))
62726
+ return 0;
62727
+ try {
62728
+ const fd = openSync3(this.ledgerPath, "r");
62729
+ try {
62730
+ return fstatSync2(fd).size;
62731
+ } finally {
62732
+ closeSync3(fd);
62733
+ }
62734
+ } catch {
62735
+ return 0;
62736
+ }
62737
+ }
62738
+ schedulePoll() {
62739
+ if (this.debounceTimer)
62740
+ clearTimeout(this.debounceTimer);
62741
+ this.debounceTimer = setTimeout(() => {
62742
+ this.debounceTimer = undefined;
62743
+ this.pollNewBytes();
62744
+ }, this.debounceMs);
62745
+ }
62746
+ pollNewBytes() {
62747
+ if (!existsSync6(this.ledgerPath)) {
62748
+ this.offset = 0;
62749
+ this.carry = "";
62750
+ return;
62751
+ }
62752
+ let size;
62753
+ try {
62754
+ const fd = openSync3(this.ledgerPath, "r");
62755
+ try {
62756
+ size = fstatSync2(fd).size;
62757
+ if (size < this.offset) {
62758
+ this.offset = 0;
62759
+ this.carry = "";
62760
+ }
62761
+ if (size === this.offset)
62762
+ return;
62763
+ const len = size - this.offset;
62764
+ const buf = Buffer.alloc(len);
62765
+ readSync2(fd, buf, 0, len, this.offset);
62766
+ this.offset = size;
62767
+ const text4 = this.carry + buf.toString("utf8");
62768
+ const parts = text4.split(`
62769
+ `);
62770
+ this.carry = parts.pop() ?? "";
62771
+ for (const line of parts) {
62772
+ const evt = parseLedgerLine(line);
62773
+ if (!evt)
62774
+ continue;
62775
+ for (const listener of this.listeners) {
62776
+ try {
62777
+ listener(evt);
62778
+ } catch {}
62779
+ }
62780
+ }
62781
+ } finally {
62782
+ closeSync3(fd);
62783
+ }
62784
+ } catch {}
62785
+ }
62786
+ }
62787
+ var init_token_ledger_watcher = __esm(() => {
62788
+ init_token_ledger_service();
62789
+ });
62790
+
62478
62791
  // ../../packages/app/src/workflow/actions/agent-run.ts
62479
62792
  import { execFile } from "child_process";
62480
- import { existsSync as existsSync5 } from "fs";
62793
+ import { existsSync as existsSync7 } from "fs";
62481
62794
  import { mkdir, writeFile } from "fs/promises";
62482
- import { dirname as dirname10, isAbsolute as isAbsolute2, join as join11 } from "path";
62795
+ import { dirname as dirname10, isAbsolute as isAbsolute2, join as join12 } from "path";
62483
62796
  import { promisify as promisify3 } from "util";
62484
62797
 
62485
62798
  class AgentRunActionRunner {
@@ -62533,13 +62846,13 @@ class AgentRunActionRunner {
62533
62846
  const { exitCode: exitCode2, answer } = captured;
62534
62847
  const ok2 = exitCode2 === 0;
62535
62848
  if (answerFile !== undefined) {
62536
- const target = isAbsolute2(answerFile) ? answerFile : join11(cwd, answerFile);
62849
+ const target = isAbsolute2(answerFile) ? answerFile : join12(cwd, answerFile);
62537
62850
  await mkdir(dirname10(target), { recursive: true });
62538
62851
  await writeFile(target, answer, "utf8");
62539
62852
  }
62540
62853
  if (ok2 && expectFile !== undefined) {
62541
- const target = isAbsolute2(expectFile) ? expectFile : join11(cwd, expectFile);
62542
- if (!existsSync5(target)) {
62854
+ const target = isAbsolute2(expectFile) ? expectFile : join12(cwd, expectFile);
62855
+ if (!existsSync7(target)) {
62543
62856
  return {
62544
62857
  ok: false,
62545
62858
  data: { exitCode: exitCode2, agent: agentLabel, answer },
@@ -62560,8 +62873,8 @@ class AgentRunActionRunner {
62560
62873
  const exitCode = await this.agentService.run(input, flags);
62561
62874
  const ok = exitCode === 0;
62562
62875
  if (ok && expectFile !== undefined) {
62563
- const target = isAbsolute2(expectFile) ? expectFile : join11(cwd, expectFile);
62564
- if (!existsSync5(target)) {
62876
+ const target = isAbsolute2(expectFile) ? expectFile : join12(cwd, expectFile);
62877
+ if (!existsSync7(target)) {
62565
62878
  return {
62566
62879
  ok: false,
62567
62880
  data: { exitCode, agent: agentLabel },
@@ -62638,7 +62951,7 @@ async function writePartialWorkArtifact(context4, agentLabel, model, captured, c
62638
62951
  ""
62639
62952
  ].join(`
62640
62953
  `);
62641
- const target = join11(cwd, ".spur", "run", `${context4.runId}-${context4.stateOrNodeId}-partial.md`);
62954
+ const target = join12(cwd, ".spur", "run", `${context4.runId}-${context4.stateOrNodeId}-partial.md`);
62642
62955
  await mkdir(dirname10(target), { recursive: true });
62643
62956
  await writeFile(target, body, "utf8");
62644
62957
  } catch {}
@@ -63302,7 +63615,7 @@ var now = () => new Date().toISOString();
63302
63615
  // ../../packages/app/src/services/workflow-service.ts
63303
63616
  import { access as access2, lstat, readdir as readdir3, readFile as readFile2, realpath } from "fs/promises";
63304
63617
  import { homedir as homedir3 } from "os";
63305
- import { join as join12, resolve as resolve3 } from "path";
63618
+ import { join as join13, resolve as resolve3 } from "path";
63306
63619
  function signalSubprocess(pid) {
63307
63620
  try {
63308
63621
  process.kill(-pid, "SIGTERM");
@@ -63506,7 +63819,7 @@ class WorkflowAppService {
63506
63819
  }
63507
63820
  async list(workflowPaths = [".spur/workflows/"]) {
63508
63821
  const projectRoot = this.ctx.cwd;
63509
- const globalRoot = join12(homedir3(), ".config", "spur");
63822
+ const globalRoot = join13(homedir3(), ".config", "spur");
63510
63823
  const layers = [];
63511
63824
  const entries = [];
63512
63825
  const scannedPaths = new Set;
@@ -63842,6 +64155,7 @@ var isActionStarted = (e) => ("kind" in e) && ("node" in e), isActionFinished =
63842
64155
  // ../../packages/app/src/index.ts
63843
64156
  var exports_src2 = {};
63844
64157
  __export(exports_src2, {
64158
+ tailTokenLedgerFile: () => tailTokenLedgerFile,
63845
64159
  systemEventCatalogEntry: () => systemEventCatalogEntry,
63846
64160
  resolvePlanningFolders: () => resolvePlanningFolders,
63847
64161
  renderTesting: () => renderTesting,
@@ -63854,6 +64168,7 @@ __export(exports_src2, {
63854
64168
  readVerdict: () => readVerdict,
63855
64169
  parseVerdict: () => parseVerdict,
63856
64170
  parsePsOutput: () => parsePsOutput,
64171
+ parseLedgerLine: () => parseLedgerLine,
63857
64172
  parseEtimeToSeconds: () => parseEtimeToSeconds,
63858
64173
  normalizeSystemEventPayload: () => normalizeSystemEventPayload,
63859
64174
  hitlConfirmDefault: () => hitlConfirmDefault,
@@ -63861,14 +64176,21 @@ __export(exports_src2, {
63861
64176
  extractSystemEventActor: () => extractSystemEventActor,
63862
64177
  deriveVerdict: () => deriveVerdict,
63863
64178
  createPsProcessInspector: () => createPsProcessInspector,
64179
+ clampToolUseLimit: () => clampToolUseLimit,
63864
64180
  bridgeEventBus: () => bridgeEventBus,
63865
64181
  WorkflowAppService: () => WorkflowAppService,
63866
64182
  UnsupportedProcessPlatformError: () => UnsupportedProcessPlatformError,
64183
+ TokenLedgerWatcher: () => TokenLedgerWatcher,
64184
+ TokenLedgerService: () => TokenLedgerService,
63867
64185
  TeamService: () => TeamService,
63868
64186
  TaskService: () => TaskService,
63869
64187
  TaskCheckService: () => TaskCheckService,
64188
+ TOKEN_LEDGER_RELATIVE_PATH: () => TOKEN_LEDGER_RELATIVE_PATH,
64189
+ TOKEN_LEDGER_MAX_LIMIT: () => TOKEN_LEDGER_MAX_LIMIT,
64190
+ TOKEN_LEDGER_DEFAULT_LIMIT: () => TOKEN_LEDGER_DEFAULT_LIMIT,
63870
64191
  TASK_LIFECYCLE_PROFILE: () => TASK_LIFECYCLE_PROFILE,
63871
64192
  TASK_ACTION_COMMANDS: () => TASK_ACTION_COMMANDS,
64193
+ SystemEventEmitter: () => SystemEventEmitter,
63872
64194
  SupervisorService: () => SupervisorService,
63873
64195
  SYSTEM_EVENT_STREAMED_NAMES: () => SYSTEM_EVENT_STREAMED_NAMES,
63874
64196
  SYSTEM_EVENT_PREFIXES: () => SYSTEM_EVENT_PREFIXES,
@@ -63920,11 +64242,14 @@ var init_src3 = __esm(() => {
63920
64242
  init_process_inspector();
63921
64243
  init_process_inventory_service();
63922
64244
  init_rule_service();
64245
+ init_system_event_emitter();
63923
64246
  init_system_event_tap();
63924
64247
  init_task_check();
63925
64248
  init_task_record();
63926
64249
  init_task_service();
63927
64250
  init_team_service();
64251
+ init_token_ledger_service();
64252
+ init_token_ledger_watcher();
63928
64253
  init_workflow_service();
63929
64254
  init_agent_run();
63930
64255
  init_file_exists();
@@ -71371,13 +71696,38 @@ ${prompt}`;
71371
71696
  // src/commands/feature.ts
71372
71697
  init_src3();
71373
71698
 
71699
+ // src/planning-emitter.ts
71700
+ init_src3();
71701
+ init_src2();
71702
+ function makeWarnLogger(context4) {
71703
+ return {
71704
+ warn: (msg, data) => {
71705
+ context4.output.error(`${msg}${data ? ` ${JSON.stringify(data)}` : ""}`);
71706
+ }
71707
+ };
71708
+ }
71709
+ function makePlanningEmitter(context4) {
71710
+ let cached2;
71711
+ const logger3 = makeWarnLogger(context4);
71712
+ return {
71713
+ async emit(event2) {
71714
+ try {
71715
+ cached2 ??= new SystemEventEmitter(new SystemEventDao(await context4.getDb()), logger3);
71716
+ await cached2.emit(event2);
71717
+ } catch (error51) {
71718
+ context4.output.error(`system_events emitter: ${error51 instanceof Error ? error51.message : String(error51)}`);
71719
+ }
71720
+ }
71721
+ };
71722
+ }
71723
+
71374
71724
  // src/workflow/make-lifecycle-adapter.ts
71375
71725
  init_src3();
71376
71726
  init_loader();
71377
71727
  init_src2();
71378
- import { existsSync as existsSync6 } from "fs";
71728
+ import { existsSync as existsSync8 } from "fs";
71379
71729
  import { homedir as homedir4 } from "os";
71380
- import { join as join13, resolve as resolve4 } from "path";
71730
+ import { join as join14, resolve as resolve4 } from "path";
71381
71731
 
71382
71732
  // src/workflow/resolve-spur-bin.ts
71383
71733
  import { basename as basename2 } from "path";
@@ -71395,23 +71745,23 @@ function resolveSpurBin() {
71395
71745
  }
71396
71746
 
71397
71747
  // src/workflow/make-lifecycle-adapter.ts
71398
- var GLOBAL_CONFIG_DIR = join13(".config", "spur");
71748
+ var GLOBAL_CONFIG_DIR = join14(".config", "spur");
71399
71749
  function globalConfigRoot(context4) {
71400
71750
  const override = context4.env.SPUR_GLOBAL_RULES_DIR;
71401
- return override !== undefined && override.length > 0 ? resolve4(context4.cwd, override) : join13(homedir4(), GLOBAL_CONFIG_DIR);
71751
+ return override !== undefined && override.length > 0 ? resolve4(context4.cwd, override) : join14(homedir4(), GLOBAL_CONFIG_DIR);
71402
71752
  }
71403
71753
  function resolveWorkflowPath(context4, profile) {
71404
71754
  const bundledRoot = bundledConfigRoot();
71405
71755
  if (bundledRoot !== null) {
71406
- const bundledPath = join13(bundledRoot, "workflows", `${profile.workflowName}.yaml`);
71407
- if (existsSync6(bundledPath))
71756
+ const bundledPath = join14(bundledRoot, "workflows", `${profile.workflowName}.yaml`);
71757
+ if (existsSync8(bundledPath))
71408
71758
  return bundledPath;
71409
71759
  }
71410
- const projectPath = join13(context4.cwd, ".spur", "workflows", `${profile.workflowName}.yaml`);
71411
- if (existsSync6(projectPath))
71760
+ const projectPath = join14(context4.cwd, ".spur", "workflows", `${profile.workflowName}.yaml`);
71761
+ if (existsSync8(projectPath))
71412
71762
  return projectPath;
71413
- const globalPath = join13(globalConfigRoot(context4), "workflows", `${profile.workflowName}.yaml`);
71414
- if (existsSync6(globalPath))
71763
+ const globalPath = join14(globalConfigRoot(context4), "workflows", `${profile.workflowName}.yaml`);
71764
+ if (existsSync8(globalPath))
71415
71765
  return globalPath;
71416
71766
  return null;
71417
71767
  }
@@ -71694,7 +72044,8 @@ async function makeService(context4, folderOverride) {
71694
72044
  const lifecycle = makeLifecycleAdapter(context4, FEATURE_LIFECYCLE_PROFILE);
71695
72045
  const writeService = new PlanningWriteService({
71696
72046
  fs: context4.fs,
71697
- ...lifecycle ? { lifecycle } : {}
72047
+ ...lifecycle ? { lifecycle } : {},
72048
+ emitter: makePlanningEmitter(context4)
71698
72049
  });
71699
72050
  return new FeatureService({ fs: context4.fs, writeService, featuresDir, tasksDir });
71700
72051
  }
@@ -71764,13 +72115,13 @@ init_loader();
71764
72115
  init_src2();
71765
72116
  init_dist10();
71766
72117
  import { homedir as homedir5 } from "os";
71767
- import { join as join14, resolve as resolve5 } from "path";
72118
+ import { join as join15, resolve as resolve5 } from "path";
71768
72119
 
71769
72120
  // src/config.ts
71770
72121
  var CLI_CONFIG = {
71771
72122
  binaryName: "spur",
71772
72123
  binaryLabel: "spur",
71773
- binaryVersion: "0.3.8",
72124
+ binaryVersion: "0.3.10",
71774
72125
  configDir: ".spur",
71775
72126
  configFile: ".spur/config.yaml",
71776
72127
  databaseFile: ".spur/spur.db"
@@ -71821,8 +72172,8 @@ var SCAFFOLD_MANIFEST = [
71821
72172
  ];
71822
72173
 
71823
72174
  // src/commands/init.ts
71824
- var GLOBAL_CONFIG_DIR2 = join14(".config", "spur");
71825
- var GLOBAL_RULES_DIR2 = join14(GLOBAL_CONFIG_DIR2, "rules");
72175
+ var GLOBAL_CONFIG_DIR2 = join15(".config", "spur");
72176
+ var GLOBAL_RULES_DIR2 = join15(GLOBAL_CONFIG_DIR2, "rules");
71826
72177
  var GLOBAL_CONFIG_EXAMPLE = "config.example.yaml";
71827
72178
  var GLOBAL_CONFIG_FILE2 = "config.yaml";
71828
72179
  var INDEXED_CONTEXT_MARKER = "## Indexed context";
@@ -71849,7 +72200,7 @@ If \`.spur/context/\` is absent, proceed normally. Never block work on its absen
71849
72200
  `;
71850
72201
  function globalRulesRoot(context4) {
71851
72202
  const override = context4.env.SPUR_GLOBAL_RULES_DIR;
71852
- return override !== undefined && override.length > 0 ? resolve5(context4.cwd, override) : join14(homedir5(), GLOBAL_RULES_DIR2);
72203
+ return override !== undefined && override.length > 0 ? resolve5(context4.cwd, override) : join15(homedir5(), GLOBAL_RULES_DIR2);
71853
72204
  }
71854
72205
  async function writeIfNew(context4, path9, content, force, result) {
71855
72206
  if (!force && await context4.fs.exists(path9)) {
@@ -71866,11 +72217,11 @@ async function seedGlobalRules(context4) {
71866
72217
  const target = globalRulesRoot(context4);
71867
72218
  let written = 0;
71868
72219
  for (const relPath of await listBundledRuleFiles()) {
71869
- const destination = join14(target, relPath);
72220
+ const destination = join15(target, relPath);
71870
72221
  if (await context4.fs.exists(destination))
71871
72222
  continue;
71872
- await context4.fs.ensureDir(join14(target, ...relPath.split("/").slice(0, -1)));
71873
- await context4.fs.writeFile(destination, await context4.fs.readFile(join14(source, relPath)));
72223
+ await context4.fs.ensureDir(join15(target, ...relPath.split("/").slice(0, -1)));
72224
+ await context4.fs.writeFile(destination, await context4.fs.readFile(join15(source, relPath)));
71874
72225
  written += 1;
71875
72226
  }
71876
72227
  return written;
@@ -71880,20 +72231,20 @@ async function seedGlobalConfig(context4) {
71880
72231
  if (source === null)
71881
72232
  return 0;
71882
72233
  const globalOverride = context4.env.SPUR_GLOBAL_RULES_DIR;
71883
- const target = globalOverride !== undefined && globalOverride.length > 0 ? resolve5(context4.cwd, globalOverride) : join14(homedir5(), GLOBAL_CONFIG_DIR2);
72234
+ const target = globalOverride !== undefined && globalOverride.length > 0 ? resolve5(context4.cwd, globalOverride) : join15(homedir5(), GLOBAL_CONFIG_DIR2);
71884
72235
  let written = 0;
71885
72236
  for (const relPath of listBundledConfigFiles()) {
71886
72237
  if (relPath === GLOBAL_CONFIG_EXAMPLE)
71887
72238
  continue;
71888
- const destination = join14(target, relPath);
72239
+ const destination = join15(target, relPath);
71889
72240
  if (await context4.fs.exists(destination))
71890
72241
  continue;
71891
- await context4.fs.ensureDir(join14(target, ...relPath.split("/").slice(0, -1)));
71892
- await context4.fs.writeFile(destination, await context4.fs.readFile(join14(source, relPath)));
72242
+ await context4.fs.ensureDir(join15(target, ...relPath.split("/").slice(0, -1)));
72243
+ await context4.fs.writeFile(destination, await context4.fs.readFile(join15(source, relPath)));
71893
72244
  written += 1;
71894
72245
  }
71895
- const examplePath = join14(source, GLOBAL_CONFIG_EXAMPLE);
71896
- const globalConfigPath = join14(target, GLOBAL_CONFIG_FILE2);
72246
+ const examplePath = join15(source, GLOBAL_CONFIG_EXAMPLE);
72247
+ const globalConfigPath = join15(target, GLOBAL_CONFIG_FILE2);
71897
72248
  if (await context4.fs.exists(examplePath) && !await context4.fs.exists(globalConfigPath)) {
71898
72249
  await context4.fs.ensureDir(target);
71899
72250
  await context4.fs.writeFile(globalConfigPath, await context4.fs.readFile(examplePath));
@@ -71907,7 +72258,7 @@ function registerInitCommand(program2, context4) {
71907
72258
  const force = options.force === true;
71908
72259
  const minimal = options.minimal === true;
71909
72260
  const projectName = options.name ?? "default";
71910
- const configPath = join14(context4.cwd, CLI_CONFIG.configFile);
72261
+ const configPath = join15(context4.cwd, CLI_CONFIG.configFile);
71911
72262
  if (!force && await context4.fs.exists(configPath)) {
71912
72263
  const message = `Already initialized: ${CLI_CONFIG.configFile}. Use --force to overwrite.`;
71913
72264
  context4.output.write(json3 ? toJson2({ ok: false, reason: "already-initialized", config: CLI_CONFIG.configFile }) : message);
@@ -71942,13 +72293,13 @@ function registerInitCommand(program2, context4) {
71942
72293
  ].join(`
71943
72294
  `)}
71944
72295
  `;
71945
- await context4.fs.ensureDir(join14(context4.cwd, CLI_CONFIG.configDir));
72296
+ await context4.fs.ensureDir(join15(context4.cwd, CLI_CONFIG.configDir));
71946
72297
  await context4.fs.writeFile(configPath, configYaml);
71947
72298
  result.created.push(configPath);
71948
- const agentsDir = join14(context4.cwd, CLI_CONFIG.configDir, "agents");
72299
+ const agentsDir = join15(context4.cwd, CLI_CONFIG.configDir, "agents");
71949
72300
  await context4.fs.ensureDir(agentsDir);
71950
- await writeIfNew(context4, join14(agentsDir, ".gitkeep"), "", force, result);
71951
- const gitignorePath = join14(context4.cwd, ".gitignore");
72301
+ await writeIfNew(context4, join15(agentsDir, ".gitkeep"), "", force, result);
72302
+ const gitignorePath = join15(context4.cwd, ".gitignore");
71952
72303
  const contextEntry = ".spur/context/";
71953
72304
  if (await context4.fs.exists(gitignorePath)) {
71954
72305
  const existing = await context4.fs.readFile(gitignorePath);
@@ -71967,13 +72318,21 @@ ${contextEntry}
71967
72318
  if (!minimal) {
71968
72319
  const configRoot = bundledConfigRoot();
71969
72320
  if (configRoot !== null) {
72321
+ for (const relPath of listBundledProjectSeedFiles()) {
72322
+ const sourcePath = join15(configRoot, relPath);
72323
+ if (!await context4.fs.exists(sourcePath))
72324
+ continue;
72325
+ const targetPath = join15(context4.cwd, CLI_CONFIG.configDir, relPath);
72326
+ await context4.fs.ensureDir(join15(targetPath, ".."));
72327
+ await writeIfNew(context4, targetPath, await context4.fs.readFile(sourcePath), force, result);
72328
+ }
71970
72329
  for (const entry of SCAFFOLD_MANIFEST) {
71971
- const sourcePath = join14(configRoot, entry.source);
72330
+ const sourcePath = join15(configRoot, entry.source);
71972
72331
  if (!await context4.fs.exists(sourcePath))
71973
72332
  continue;
71974
- const baseDir = entry.root === true ? context4.cwd : join14(context4.cwd, CLI_CONFIG.configDir);
71975
- const targetPath = join14(baseDir, entry.target);
71976
- await context4.fs.ensureDir(join14(targetPath, ".."));
72333
+ const baseDir = entry.root === true ? context4.cwd : join15(context4.cwd, CLI_CONFIG.configDir);
72334
+ const targetPath = join15(baseDir, entry.target);
72335
+ await context4.fs.ensureDir(join15(targetPath, ".."));
71977
72336
  const entryForce = entry.preserve === true ? false : force;
71978
72337
  let body = await context4.fs.readFile(sourcePath);
71979
72338
  if (entry.target === "AGENTS.md") {
@@ -71983,7 +72342,7 @@ ${contextEntry}
71983
72342
  }
71984
72343
  }
71985
72344
  }
71986
- const agentsMdPath = join14(context4.cwd, "AGENTS.md");
72345
+ const agentsMdPath = join15(context4.cwd, "AGENTS.md");
71987
72346
  if (await context4.fs.exists(agentsMdPath)) {
71988
72347
  const existing = await context4.fs.readFile(agentsMdPath);
71989
72348
  if (!existing.includes(INDEXED_CONTEXT_MARKER)) {
@@ -72161,10 +72520,10 @@ function parseInterval2(raw) {
72161
72520
 
72162
72521
  // src/commands/migrate.ts
72163
72522
  init_src2();
72164
- import { join as join15 } from "path";
72523
+ import { join as join16 } from "path";
72165
72524
  function registerMigrateCommand(program2, context4) {
72166
72525
  program2.command("migrate").summary("apply CLI-owned schema migrations").option("--json", "Output machine-readable JSON").action(async (options) => {
72167
- const migrations = await loadSqlMigrations(join15(context4.cwd, "drizzle")).catch(() => {
72526
+ const migrations = await loadSqlMigrations(join16(context4.cwd, "drizzle")).catch(() => {
72168
72527
  return;
72169
72528
  });
72170
72529
  const applied = await applyCliMigrations(await context4.getDb(), migrations);
@@ -72416,7 +72775,7 @@ function formatTraceDetail(detail) {
72416
72775
 
72417
72776
  // src/commands/serve.ts
72418
72777
  init_src();
72419
- import { join as join19 } from "path";
72778
+ import { join as join20 } from "path";
72420
72779
 
72421
72780
  // ../server/src/index.ts
72422
72781
  init_src();
@@ -72426,11 +72785,11 @@ init_src3();
72426
72785
  init_src();
72427
72786
  init_loader();
72428
72787
  init_src2();
72429
- import { dirname as dirname12, isAbsolute as isAbsolute3, join as join18 } from "path";
72788
+ import { dirname as dirname12, isAbsolute as isAbsolute3, join as join19 } from "path";
72430
72789
  init_dist5();
72431
72790
 
72432
72791
  // ../server/src/bootstrap.ts
72433
- import { join as join16 } from "path";
72792
+ import { join as join17 } from "path";
72434
72793
 
72435
72794
  // ../../node_modules/.bun/radash@12.1.1/node_modules/radash/dist/esm/async.mjs
72436
72795
  var guard = (func, shouldGuard) => {
@@ -78534,23 +78893,128 @@ function parseLimit(raw2, fallback) {
78534
78893
 
78535
78894
  // ../server/src/modules/observability/index.ts
78536
78895
  init_src3();
78896
+ var ledgerWatcher;
78897
+ var watcherPath;
78898
+ function getLedgerWatcher(path9) {
78899
+ if (!ledgerWatcher || watcherPath !== path9) {
78900
+ ledgerWatcher?.stop();
78901
+ ledgerWatcher = new TokenLedgerWatcher({ ledgerPath: path9 });
78902
+ watcherPath = path9;
78903
+ ledgerWatcher.start();
78904
+ }
78905
+ return ledgerWatcher;
78906
+ }
78907
+ function toolUseSsePayload(event2) {
78908
+ return {
78909
+ type: "tool-use",
78910
+ occurredAt: event2.ts,
78911
+ event: event2
78912
+ };
78913
+ }
78914
+ function handleProcesses(ctx) {
78915
+ return async (c3) => {
78916
+ try {
78917
+ const snapshot = await ctx.processInventory().snapshot();
78918
+ return c3.json(snapshot);
78919
+ } catch (err) {
78920
+ if (err instanceof UnsupportedProcessPlatformError) {
78921
+ return c3.json({ error: err.message, code: err.code }, 501);
78922
+ }
78923
+ const message = err instanceof Error ? err.message : String(err);
78924
+ return c3.json({ error: message }, 500);
78925
+ }
78926
+ };
78927
+ }
78928
+ function handleToolUseGet(ctx) {
78929
+ return (c3) => {
78930
+ try {
78931
+ const limitParam = c3.req.query("limit");
78932
+ let limitRaw;
78933
+ if (limitParam !== undefined) {
78934
+ const parsed = Number.parseInt(limitParam, 10);
78935
+ if (!Number.isNaN(parsed))
78936
+ limitRaw = parsed;
78937
+ }
78938
+ const before = c3.req.query("before") ?? undefined;
78939
+ const limit = clampToolUseLimit(limitRaw);
78940
+ const snapshot = ctx.tokenLedger().snapshot({ limit, before });
78941
+ return c3.json(snapshot);
78942
+ } catch (err) {
78943
+ const message = err instanceof Error ? err.message : String(err);
78944
+ return c3.json({ error: message }, 500);
78945
+ }
78946
+ };
78947
+ }
78948
+ function handleToolUseStream(ctx) {
78949
+ return (c3) => {
78950
+ const closed = { current: false };
78951
+ let heartbeatInterval;
78952
+ let unsubscribe;
78953
+ const signal = c3.req.raw.signal;
78954
+ let closeController = () => {};
78955
+ const teardown = () => {
78956
+ if (closed.current)
78957
+ return;
78958
+ closed.current = true;
78959
+ if (heartbeatInterval)
78960
+ clearInterval(heartbeatInterval);
78961
+ try {
78962
+ unsubscribe?.();
78963
+ } catch {}
78964
+ signal.removeEventListener("abort", teardown);
78965
+ closeController();
78966
+ };
78967
+ const stream = new ReadableStream({
78968
+ start(controller) {
78969
+ const encoder = new TextEncoder;
78970
+ closeController = () => {
78971
+ try {
78972
+ controller.close();
78973
+ } catch {}
78974
+ };
78975
+ if (signal.aborted) {
78976
+ teardown();
78977
+ return;
78978
+ }
78979
+ signal.addEventListener("abort", teardown);
78980
+ heartbeatInterval = setInterval(sendSseKeepalive, 15000, closed, controller, encoder);
78981
+ enqueueSseFrame(closed, controller, encoder, {
78982
+ type: "connected",
78983
+ occurredAt: new Date().toISOString()
78984
+ });
78985
+ try {
78986
+ const path9 = ctx.tokenLedger().path;
78987
+ const watcher = getLedgerWatcher(path9);
78988
+ unsubscribe = watcher.subscribe((event2) => {
78989
+ if (!enqueueSseFrame(closed, controller, encoder, toolUseSsePayload(event2)))
78990
+ teardown();
78991
+ });
78992
+ } catch {
78993
+ enqueueSseFrame(closed, controller, encoder, {
78994
+ type: "error",
78995
+ error: "ledger watch unavailable",
78996
+ occurredAt: new Date().toISOString()
78997
+ });
78998
+ }
78999
+ },
79000
+ cancel() {
79001
+ teardown();
79002
+ }
79003
+ });
79004
+ return c3.newResponse(stream, 200, {
79005
+ "Content-Type": "text/event-stream",
79006
+ "Cache-Control": "no-cache"
79007
+ });
79008
+ };
79009
+ }
78537
79010
  var observabilityModule = {
78538
79011
  name: "observability",
78539
79012
  mount(app, ctx) {
78540
79013
  if (!ctx)
78541
79014
  return;
78542
- app.get("/api/observability/processes", async (c3) => {
78543
- try {
78544
- const snapshot = await ctx.processInventory().snapshot();
78545
- return c3.json(snapshot);
78546
- } catch (err) {
78547
- if (err instanceof UnsupportedProcessPlatformError) {
78548
- return c3.json({ error: err.message, code: err.code }, 501);
78549
- }
78550
- const message = err instanceof Error ? err.message : String(err);
78551
- return c3.json({ error: message }, 500);
78552
- }
78553
- });
79015
+ app.get("/api/observability/processes", handleProcesses(ctx));
79016
+ app.get("/api/observability/tool-use", handleToolUseGet(ctx));
79017
+ app.get("/api/observability/tool-use/stream", handleToolUseStream(ctx));
78554
79018
  }
78555
79019
  };
78556
79020
 
@@ -80788,7 +81252,7 @@ function createApp(appRt, opts) {
80788
81252
  app.use("*", async (c3, next) => {
80789
81253
  const pathname = c3.req.path === "/" ? "/index.html" : c3.req.path;
80790
81254
  try {
80791
- const file2 = Bun.file(join16(webDistPath, pathname));
81255
+ const file2 = Bun.file(join17(webDistPath, pathname));
80792
81256
  if (await file2.exists()) {
80793
81257
  const headers = new Headers({ "content-type": file2.type });
80794
81258
  return new Response(file2.stream(), { headers });
@@ -80801,7 +81265,7 @@ function createApp(appRt, opts) {
80801
81265
  return c3.json({ error: "Not Found" }, 404);
80802
81266
  }
80803
81267
  try {
80804
- const indexFile = Bun.file(join16(webDistPath, "index.html"));
81268
+ const indexFile = Bun.file(join17(webDistPath, "index.html"));
80805
81269
  if (await indexFile.exists()) {
80806
81270
  return new Response(indexFile.stream(), {
80807
81271
  headers: { "content-type": "text/html; charset=utf-8" }
@@ -80822,7 +81286,7 @@ init_src3();
80822
81286
  init_src();
80823
81287
  init_src2();
80824
81288
  init_dist5();
80825
- import { dirname as dirname11, join as join17 } from "path";
81289
+ import { dirname as dirname11, join as join18 } from "path";
80826
81290
  var NOOP_OUTPUT = { write: (_s) => {}, error: (_s) => {} };
80827
81291
 
80828
81292
  class LazyPlanningEventEmitter {
@@ -80857,7 +81321,7 @@ class NotConfiguredError extends Error {
80857
81321
  function createServerContext(appRt, options) {
80858
81322
  const cwd = options.cwd;
80859
81323
  const fs3 = options.fs;
80860
- const dbUrl = options.dbUrl ?? join17(cwd, DEFAULT_DATABASE_URL);
81324
+ const dbUrl = options.dbUrl ?? join18(cwd, DEFAULT_DATABASE_URL);
80861
81325
  const eventsBus = options.eventsBus ?? appRt.events;
80862
81326
  const jobQueueEnabled = options.jobQueueEnabled ?? false;
80863
81327
  const folders = options.folders ?? DEFAULT_PLANNING_FOLDERS;
@@ -80875,6 +81339,7 @@ function createServerContext(appRt, options) {
80875
81339
  let teamSvc;
80876
81340
  let supervisorSvc;
80877
81341
  let processInventorySvc;
81342
+ let tokenLedgerSvc;
80878
81343
  let agentSvc;
80879
81344
  let ruleSvc;
80880
81345
  let workflowSvc;
@@ -80965,6 +81430,12 @@ function createServerContext(appRt, options) {
80965
81430
  }
80966
81431
  return processInventorySvc;
80967
81432
  },
81433
+ tokenLedger() {
81434
+ if (!tokenLedgerSvc) {
81435
+ tokenLedgerSvc = new TokenLedgerService({ cwd });
81436
+ }
81437
+ return tokenLedgerSvc;
81438
+ },
80968
81439
  agentService() {
80969
81440
  agentSvc ??= new AgentService({
80970
81441
  cwd,
@@ -81144,13 +81615,15 @@ async function runTaskActionJob(ctx, env, payload, createAgentService = createTa
81144
81615
  }
81145
81616
  }
81146
81617
  async function resolveWebDistPath(configuredPath) {
81147
- const candidates = configuredPath && configuredPath.trim() !== "" ? [isAbsolute3(configuredPath) ? configuredPath : join18(process.cwd(), configuredPath)] : [
81148
- join18(process.cwd(), "dist/web"),
81149
- join18(dirname12(process.execPath), "../web"),
81150
- join18(import.meta.dir, "../../../dist/web")
81618
+ const candidates = configuredPath && configuredPath.trim() !== "" ? [isAbsolute3(configuredPath) ? configuredPath : join19(process.cwd(), configuredPath)] : [
81619
+ join19(process.cwd(), "dist/web"),
81620
+ join19(import.meta.dir, "web"),
81621
+ join19(dirname12(process.execPath), "web"),
81622
+ join19(dirname12(process.execPath), "../web"),
81623
+ join19(import.meta.dir, "../../../dist/web")
81151
81624
  ];
81152
81625
  for (const candidate of candidates) {
81153
- if (await Bun.file(join18(candidate, "index.html")).exists()) {
81626
+ if (await Bun.file(join19(candidate, "index.html")).exists()) {
81154
81627
  return candidate;
81155
81628
  }
81156
81629
  }
@@ -81172,12 +81645,16 @@ async function startServer(options, deps = defaultDeps) {
81172
81645
  if (bootConfig.scheduler.enabled) {
81173
81646
  scheduler3 = await deps.createScheduler();
81174
81647
  }
81648
+ const webDistPath = await resolveWebDistPath(options.webDistPath);
81649
+ if (!webDistPath) {
81650
+ appRt.logger.warn("Board UI static assets not found \u2014 /board will return 404. " + "Reinstall a package that includes the web board, or set server.webDistPath to a built dist/web directory (index.html).");
81651
+ }
81175
81652
  const ctx = deps.createServerContext(appRt, {
81176
81653
  cwd: process.cwd(),
81177
81654
  fs: fs3,
81178
81655
  dbUrl: options.dbUrl,
81179
81656
  folders: await resolvePlanningFolders(fs3),
81180
- webDistPath: await resolveWebDistPath(options.webDistPath),
81657
+ webDistPath,
81181
81658
  jobQueueEnabled: bootConfig.jobqueue.enabled,
81182
81659
  scheduler: scheduler3,
81183
81660
  teamAutostart: bootConfig.teamAutostart,
@@ -81255,9 +81732,13 @@ async function startServer(options, deps = defaultDeps) {
81255
81732
  process.on("SIGINT", () => void shutdown("SIGINT"));
81256
81733
  process.on("SIGTERM", () => void shutdown("SIGTERM"));
81257
81734
  const url2 = `http://${options.host}:${options.port}`;
81258
- appRt.logger.info("Server started", { port: options.port, host: options.host });
81735
+ appRt.logger.info("Server started", {
81736
+ port: options.port,
81737
+ host: options.host,
81738
+ board: webDistPath ? `${url2}/board` : null
81739
+ });
81259
81740
  if (options.openBrowser) {
81260
- await deps.openUrl(`${url2}/board`);
81741
+ await deps.openUrl(webDistPath ? `${url2}/board` : `${url2}/api/health`);
81261
81742
  }
81262
81743
  if (options.keepAlive !== false) {
81263
81744
  await new Promise(() => {});
@@ -81270,7 +81751,7 @@ if (false) {}
81270
81751
 
81271
81752
  // src/commands/serve.ts
81272
81753
  function resolveServeDbUrl(cwd, env, configuredUrl) {
81273
- return env.DATABASE_URL === undefined ? join19(cwd, DEFAULT_DATABASE_URL) : configuredUrl;
81754
+ return env.DATABASE_URL === undefined ? join20(cwd, DEFAULT_DATABASE_URL) : configuredUrl;
81274
81755
  }
81275
81756
  function registerServeCommand(program2, context4) {
81276
81757
  program2.command("serve").summary("start the Spur web server (local fallback)").option("--port <n>", "Server port (env: PORT, default: 3000)", parseInt).option("--host <addr>", "Bind address (env: HOST, default: localhost)").option("--no-open", "Skip opening the browser").option("--cwd <path>", "Working directory", context4.cwd).option("--json", "Output { port, url, pid } and exit").action(async (options) => {
@@ -81309,7 +81790,7 @@ function registerServeCommand(program2, context4) {
81309
81790
  }
81310
81791
 
81311
81792
  // src/commands/status.ts
81312
- import { join as join20 } from "path";
81793
+ import { join as join21 } from "path";
81313
81794
 
81314
81795
  // src/errors.ts
81315
81796
  class CommandError extends Error {
@@ -81380,8 +81861,8 @@ function registerStatusCommand(program2, context4) {
81380
81861
  }
81381
81862
  async function runStatusCore(path9, options, context4) {
81382
81863
  const [packageJsonExists, spurConfigExists, git, agentSpecs] = await Promise.all([
81383
- context4.fs.exists(join20(context4.cwd, "package.json")),
81384
- context4.fs.exists(join20(context4.cwd, ".spur", "config.yaml")),
81864
+ context4.fs.exists(join21(context4.cwd, "package.json")),
81865
+ context4.fs.exists(join21(context4.cwd, ".spur", "config.yaml")),
81385
81866
  gitContext(context4.cwd),
81386
81867
  listAgentSpecIds(context4)
81387
81868
  ]);
@@ -81409,14 +81890,14 @@ async function runStatusCore(path9, options, context4) {
81409
81890
  return status.ok ? 0 : 1;
81410
81891
  }
81411
81892
  async function listAgentSpecIds(context4) {
81412
- const dir = join20(context4.cwd, ".spur", "agents");
81893
+ const dir = join21(context4.cwd, ".spur", "agents");
81413
81894
  if (!await context4.fs.exists(dir))
81414
81895
  return [];
81415
81896
  const entries = await context4.fs.readDir(dir);
81416
81897
  return entries.filter((entry) => entry.endsWith(".yaml") || entry.endsWith(".yml")).map((entry) => entry.replace(/\.ya?ml$/, "")).sort();
81417
81898
  }
81418
81899
  async function readTargetStatus(context4, targetPath) {
81419
- const resolved = join20(context4.cwd, targetPath);
81900
+ const resolved = join21(context4.cwd, targetPath);
81420
81901
  const stat2 = await context4.fs.stat(resolved);
81421
81902
  if (stat2 === null)
81422
81903
  throw new CommandError(`status failed: path does not exist at ${resolved}`);
@@ -81427,8 +81908,8 @@ async function readTargetStatus(context4, targetPath) {
81427
81908
  init_src3();
81428
81909
  init_loader();
81429
81910
  init_src2();
81430
- import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
81431
- import { join as join21 } from "path";
81911
+ import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
81912
+ import { join as join22 } from "path";
81432
81913
  // schemas/section-matrix.schema.json
81433
81914
  var section_matrix_schema_default = {
81434
81915
  $schema: "http://json-schema.org/draft-07/schema#",
@@ -82283,7 +82764,8 @@ async function makeService2(context4, folderOverride, noLifecycle = false) {
82283
82764
  const lifecycle = noLifecycle ? undefined : makeLifecycleAdapter(context4, TASK_LIFECYCLE_PROFILE);
82284
82765
  const writeService = new PlanningWriteService({
82285
82766
  fs: context4.fs,
82286
- ...lifecycle ? { lifecycle } : {}
82767
+ ...lifecycle ? { lifecycle } : {},
82768
+ emitter: makePlanningEmitter(context4)
82287
82769
  });
82288
82770
  return new TaskService({
82289
82771
  fs: context4.fs,
@@ -82302,16 +82784,16 @@ function loadTemplateContent(projectRoot, variant) {
82302
82784
  return templateContentCache.get(variant);
82303
82785
  if (templateMissSet.has(variant))
82304
82786
  return;
82305
- const localPath = join21(projectRoot, ".spur", "tasks", "templates", `${variant}.md`);
82306
- if (existsSync7(localPath)) {
82787
+ const localPath = join22(projectRoot, ".spur", "tasks", "templates", `${variant}.md`);
82788
+ if (existsSync9(localPath)) {
82307
82789
  const content = readFileSync8(localPath, "utf8");
82308
82790
  templateContentCache.set(variant, content);
82309
82791
  return content;
82310
82792
  }
82311
82793
  const root = bundledConfigRoot();
82312
82794
  if (root !== null) {
82313
- const templatePath = join21(root, "templates", "task", `${variant}.md`);
82314
- if (existsSync7(templatePath)) {
82795
+ const templatePath = join22(root, "templates", "task", `${variant}.md`);
82796
+ if (existsSync9(templatePath)) {
82315
82797
  const content = readFileSync8(templatePath, "utf8");
82316
82798
  templateContentCache.set(variant, content);
82317
82799
  return content;
@@ -82326,14 +82808,14 @@ function loadTemplateBodies(projectRoot, variant) {
82326
82808
  if (cached2 !== undefined)
82327
82809
  return cached2;
82328
82810
  let bodies = {};
82329
- const localPath = join21(projectRoot, ".spur", "tasks", "templates", `${variant}.md`);
82330
- if (existsSync7(localPath)) {
82811
+ const localPath = join22(projectRoot, ".spur", "tasks", "templates", `${variant}.md`);
82812
+ if (existsSync9(localPath)) {
82331
82813
  bodies = extractTemplateBodies(readFileSync8(localPath, "utf8"));
82332
82814
  } else {
82333
82815
  const root = bundledConfigRoot();
82334
82816
  if (root !== null) {
82335
- const templatePath = join21(root, "templates", "task", `${variant}.md`);
82336
- if (existsSync7(templatePath)) {
82817
+ const templatePath = join22(root, "templates", "task", `${variant}.md`);
82818
+ if (existsSync9(templatePath)) {
82337
82819
  bodies = extractTemplateBodies(readFileSync8(templatePath, "utf8"));
82338
82820
  }
82339
82821
  }
@@ -82367,8 +82849,8 @@ async function loadSectionMatrix(projectRoot) {
82367
82849
  return promise2;
82368
82850
  }
82369
82851
  async function loadSectionMatrixUncached(projectRoot) {
82370
- const localPath = join21(projectRoot, ".spur", "tasks", "section-matrix.yaml");
82371
- if (existsSync7(localPath)) {
82852
+ const localPath = join22(projectRoot, ".spur", "tasks", "section-matrix.yaml");
82853
+ if (existsSync9(localPath)) {
82372
82854
  const data = await loadStructuredSpurConfig(localPath, {
82373
82855
  validateJsonSchema: true,
82374
82856
  embeddedSchemas: EMBEDDED_SPUR_SCHEMAS
@@ -82377,8 +82859,8 @@ async function loadSectionMatrixUncached(projectRoot) {
82377
82859
  }
82378
82860
  const root = bundledConfigRoot();
82379
82861
  if (root !== null) {
82380
- const matrixPath = join21(root, "tasks", "section-matrix.yaml");
82381
- if (existsSync7(matrixPath)) {
82862
+ const matrixPath = join22(root, "tasks", "section-matrix.yaml");
82863
+ if (existsSync9(matrixPath)) {
82382
82864
  const data = await loadStructuredSpurConfig(matrixPath, {
82383
82865
  validateJsonSchema: true,
82384
82866
  embeddedSchemas: EMBEDDED_SPUR_SCHEMAS
@@ -82823,7 +83305,7 @@ init_src3();
82823
83305
  init_src();
82824
83306
  init_src2();
82825
83307
  init_dist5();
82826
- import { dirname as dirname13, join as join22, resolve as resolve7 } from "path";
83308
+ import { dirname as dirname13, join as join23, resolve as resolve7 } from "path";
82827
83309
  import { isatty as isatty4 } from "tty";
82828
83310
 
82829
83311
  // ../../node_modules/.bun/@clack+core@1.4.1/node_modules/@clack/core/dist/index.mjs
@@ -84237,7 +84719,7 @@ function createCliContext(options) {
84237
84719
  function noopSetExitCode(_code) {}
84238
84720
  async function createMigratedDbAdapter(cwd = process.cwd(), env = process.env, dbUrl) {
84239
84721
  const config4 = buildConfigFromEnv(env);
84240
- const configuredUrl = env.DATABASE_URL === undefined ? join22(cwd, DEFAULT_DATABASE_URL) : config4.database.url;
84722
+ const configuredUrl = env.DATABASE_URL === undefined ? join23(cwd, DEFAULT_DATABASE_URL) : config4.database.url;
84241
84723
  const url2 = dbUrl ?? configuredUrl;
84242
84724
  if (url2 !== IN_MEMORY_DATABASE_URL) {
84243
84725
  await createNodeFileSystem3().ensureDir(dirname13(url2));