@gobing-ai/spur 0.3.60 → 0.3.62

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 (110) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/config.example.yaml +2 -2
  3. package/config/config.global.yaml +68 -57
  4. package/config/corpus-baseline.json +505 -57
  5. package/config/plugin-scripts.json +2 -2
  6. package/config/rules/boundary/config-loading-ownership.yaml +21 -0
  7. package/config/workflow-composition-baseline.json +1 -1
  8. package/config/workflows/history-anatomy.yaml +366 -0
  9. package/config/workflows/wayfinder-resolution.yaml +4 -6
  10. package/package.json +1 -1
  11. package/plugins/README.md +5 -4
  12. package/plugins/sp/README.md +22 -9
  13. package/plugins/sp/agents/super-planner.md +1 -1
  14. package/plugins/sp/commands/dev-find-issue.md +25 -46
  15. package/plugins/sp/commands/dev-pr-review.md +1 -1
  16. package/plugins/sp/commands/dev-run.md +22 -3
  17. package/plugins/sp/commands/dev-wrap.md +2 -2
  18. package/plugins/sp/commands/dev-wrapall.md +2 -2
  19. package/plugins/sp/lib/artifact-digest.generated.d.mts +7 -0
  20. package/plugins/sp/lib/artifact-digest.generated.mjs +48 -0
  21. package/plugins/sp/plugin.json +1 -1
  22. package/plugins/sp/references/roles.md +17 -14
  23. package/plugins/sp/scripts/history-anatomy-cache.mjs +669 -0
  24. package/plugins/sp/scripts/history-anatomy-cache.ts +818 -0
  25. package/plugins/sp/scripts/surface-drift-inventory.ts +51 -17
  26. package/plugins/sp/scripts/validate-commands.ts +11 -4
  27. package/plugins/sp/skills/code-verification/SKILL.md +1 -1
  28. package/plugins/sp/skills/code-verification/references/verdict-schema.md +1 -1
  29. package/plugins/sp/skills/history-anatomy/SKILL.md +67 -0
  30. package/plugins/sp/skills/history-anatomy/references/modes.md +82 -0
  31. package/plugins/sp/skills/history-anatomy/references/operations.md +67 -0
  32. package/plugins/sp/skills/history-anatomy/references/report-contract.md +133 -0
  33. package/plugins/sp/skills/issue-finding/SKILL.md +1 -1
  34. package/plugins/sp/skills/next-router/references/routing-table.md +1 -1
  35. package/plugins/sp/skills/pr-reviewing/SKILL.md +2 -2
  36. package/plugins/sp/skills/spec-decomposition/references/decomposition.md +1 -1
  37. package/plugins/sp/skills/spur-cli/references/init.md +1 -1
  38. package/plugins/sp/skills/spur-cli/references/tasks/l3-guard-cheatsheet.md +1 -1
  39. package/plugins/sp/skills/spur-cli/references/workflows/operations.md +1 -1
  40. package/plugins/sp/skills/spur-cli/references/workflows.md +4 -4
  41. package/plugins/sp/skills/spur-dev/SKILL.md +4 -4
  42. package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +4 -4
  43. package/plugins/sp/skills/spur-dev/references/dev-operations.md +11 -11
  44. package/plugins/sp/skills/spur-dev/references/done-housekeeping.md +1 -1
  45. package/plugins/sp/skills/spur-dev/references/execution-batch.md +17 -4
  46. package/plugins/sp/skills/spur-dev/references/execution-workflow.md +8 -1
  47. package/plugins/sp/skills/spur-dev/references/feature-link-helper.md +3 -2
  48. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +25 -13
  49. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +1 -1
  50. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +5 -2
  51. package/plugins/sp/skills/spur-dev/references/planning-workflow.md +2 -2
  52. package/schemas/spur-config.schema.json +1 -1
  53. package/spur.js +1278 -890
  54. package/web/_astro/BoardApp.Z9jMwI9G.js +1 -0
  55. package/web/_astro/BoardApp.rkWGVwwK.js +178 -0
  56. package/web/_astro/{TaskDetail.B0l1uzsB.js → TaskDetail.B7ODt_bA.js} +1 -1
  57. package/web/_astro/{arc.DPruHC3P.js → arc.C5QRz6AQ.js} +1 -1
  58. package/web/_astro/{architectureDiagram-3BPJPVTR.CuOqAck6.js → architectureDiagram-3BPJPVTR.CHptn8gZ.js} +1 -1
  59. package/web/_astro/{blockDiagram-GPEHLZMM.Cxb7HDIA.js → blockDiagram-GPEHLZMM.b1nEDkDw.js} +1 -1
  60. package/web/_astro/{c4Diagram-AAUBKEIU.Dw7wa-0S.js → c4Diagram-AAUBKEIU.BWj8S_Qq.js} +1 -1
  61. package/web/_astro/channel.BJtn6CGV.js +1 -0
  62. package/web/_astro/{chunk-2J33WTMH.MECQitzo.js → chunk-2J33WTMH.q0BBuE1n.js} +1 -1
  63. package/web/_astro/{chunk-4BX2VUAB.p3ofhIGE.js → chunk-4BX2VUAB.DP2KDkAU.js} +1 -1
  64. package/web/_astro/{chunk-55IACEB6.BmQGsPZP.js → chunk-55IACEB6.CkCId72p.js} +1 -1
  65. package/web/_astro/{chunk-727SXJPM.CwrK23OA.js → chunk-727SXJPM.oHWzxMot.js} +1 -1
  66. package/web/_astro/{chunk-AQP2D5EJ.CUKI_QXp.js → chunk-AQP2D5EJ.DkkgxfMP.js} +1 -1
  67. package/web/_astro/{chunk-FMBD7UC4.7AtDgShM.js → chunk-FMBD7UC4.lmFJDvRK.js} +1 -1
  68. package/web/_astro/{chunk-ND2GUHAM.f_YJbTs3.js → chunk-ND2GUHAM.52jt0eHK.js} +1 -1
  69. package/web/_astro/{chunk-QZHKN3VN.0q7V3gEg.js → chunk-QZHKN3VN.Cg8KEFoD.js} +1 -1
  70. package/web/_astro/{classDiagram-4FO5ZUOK.C_hG3HMK.js → classDiagram-4FO5ZUOK.BuOUhxcD.js} +1 -1
  71. package/web/_astro/{classDiagram-v2-Q7XG4LA2.C_hG3HMK.js → classDiagram-v2-Q7XG4LA2.BuOUhxcD.js} +1 -1
  72. package/web/_astro/{cose-bilkent-S5V4N54A.vsaaaEMy.js → cose-bilkent-S5V4N54A.BRUU8E_0.js} +1 -1
  73. package/web/_astro/{dagre-BM42HDAG.BN7XFqQ4.js → dagre-BM42HDAG.GDbfFYVV.js} +1 -1
  74. package/web/_astro/{diagram-2AECGRRQ.ED6AfryZ.js → diagram-2AECGRRQ.CvIDBeJF.js} +1 -1
  75. package/web/_astro/{diagram-5GNKFQAL.Dxllzg8R.js → diagram-5GNKFQAL.DMgJjWOX.js} +1 -1
  76. package/web/_astro/{diagram-KO2AKTUF.RuWppieD.js → diagram-KO2AKTUF.njjl-0AP.js} +1 -1
  77. package/web/_astro/{diagram-LMA3HP47.CL32Xn6s.js → diagram-LMA3HP47.CcCqgP8M.js} +1 -1
  78. package/web/_astro/{diagram-OG6HWLK6.CUneEhqB.js → diagram-OG6HWLK6.Btqd-YVE.js} +1 -1
  79. package/web/_astro/{erDiagram-TEJ5UH35.DI-92Lec.js → erDiagram-TEJ5UH35.BbhML_Xo.js} +1 -1
  80. package/web/_astro/{flowDiagram-I6XJVG4X.69Aqa1FF.js → flowDiagram-I6XJVG4X.CkvsIgY8.js} +1 -1
  81. package/web/_astro/{ganttDiagram-6RSMTGT7.DdpClZpa.js → ganttDiagram-6RSMTGT7.m8IWD_wW.js} +1 -1
  82. package/web/_astro/{gitGraphDiagram-PVQCEYII.xqWfkvB4.js → gitGraphDiagram-PVQCEYII.5z87HXO-.js} +1 -1
  83. package/web/_astro/index.nWve6EHS.css +1 -0
  84. package/web/_astro/{infoDiagram-5YYISTIA.B3G5sfKm.js → infoDiagram-5YYISTIA.Bvyvsd7Q.js} +1 -1
  85. package/web/_astro/{ishikawaDiagram-YF4QCWOH.ahVNsoAQ.js → ishikawaDiagram-YF4QCWOH.DdWVYO87.js} +1 -1
  86. package/web/_astro/{journeyDiagram-JHISSGLW.PmFHLc-X.js → journeyDiagram-JHISSGLW.C3kOlYH1.js} +1 -1
  87. package/web/_astro/{kanban-definition-UN3LZRKU.Co1qOPTd.js → kanban-definition-UN3LZRKU.CfKTUHVC.js} +1 -1
  88. package/web/_astro/{linear.DoHZ-iRt.js → linear.CMDHnzgX.js} +1 -1
  89. package/web/_astro/{mermaid.core.DNRElYN2.js → mermaid.core.DN7-WrsP.js} +4 -4
  90. package/web/_astro/{mindmap-definition-RKZ34NQL.CdfJZidJ.js → mindmap-definition-RKZ34NQL.DcQxLvqG.js} +1 -1
  91. package/web/_astro/{pieDiagram-4H26LBE5.C1A8dby4.js → pieDiagram-4H26LBE5.Nu-Inbw6.js} +1 -1
  92. package/web/_astro/{quadrantDiagram-W4KKPZXB.NmZqjG_c.js → quadrantDiagram-W4KKPZXB.Dck2EChs.js} +1 -1
  93. package/web/_astro/{requirementDiagram-4Y6WPE33.tY36zYtK.js → requirementDiagram-4Y6WPE33.C--E5XuW.js} +1 -1
  94. package/web/_astro/{sankeyDiagram-5OEKKPKP.rOEGZYjo.js → sankeyDiagram-5OEKKPKP.B_0iRNna.js} +1 -1
  95. package/web/_astro/{sequenceDiagram-3UESZ5HK.Dpnw3S9J.js → sequenceDiagram-3UESZ5HK.PKuKr9mk.js} +1 -1
  96. package/web/_astro/{stateDiagram-AJRCARHV.DGpvQ3Wz.js → stateDiagram-AJRCARHV.ClhGHb-O.js} +1 -1
  97. package/web/_astro/{stateDiagram-v2-BHNVJYJU.CbwQdEfu.js → stateDiagram-v2-BHNVJYJU.Bv6BBnvg.js} +1 -1
  98. package/web/_astro/{timeline-definition-PNZ67QCA.DAYUpUJT.js → timeline-definition-PNZ67QCA.BZm2ZvAH.js} +1 -1
  99. package/web/_astro/{vennDiagram-CIIHVFJN.C7JCCd6-.js → vennDiagram-CIIHVFJN.Dt6yZXry.js} +1 -1
  100. package/web/_astro/{wardley-L42UT6IY.BGgwOH5_.js → wardley-L42UT6IY.rUs-E00M.js} +1 -1
  101. package/web/_astro/{wardleyDiagram-YWT4CUSO.oIZqzI2l.js → wardleyDiagram-YWT4CUSO.BcWgv4cA.js} +1 -1
  102. package/web/_astro/{xychartDiagram-2RQKCTM6.DohcJzqm.js → xychartDiagram-2RQKCTM6.DH-M-cX9.js} +1 -1
  103. package/web/index.html +2 -2
  104. package/plugins/sp/commands/dev-history-load.md +0 -63
  105. package/plugins/sp/scripts/history-load.mjs +0 -268
  106. package/plugins/sp/scripts/history-load.ts +0 -400
  107. package/web/_astro/BoardApp.BJs3l7O8.js +0 -179
  108. package/web/_astro/BoardApp.D8InuCYx.js +0 -1
  109. package/web/_astro/channel.XCf1DcsL.js +0 -1
  110. package/web/_astro/index.DJl2RAVZ.css +0 -1
package/spur.js CHANGED
@@ -37418,466 +37418,6 @@ var require_extra_typings = __commonJS((exports, module) => {
37418
37418
  exports.createArgument = (name, description) => new commander.Argument(name, description);
37419
37419
  });
37420
37420
 
37421
- // ../../packages/config/src/finding-codes.ts
37422
- function isFindingCode(code) {
37423
- return ALL_FINDING_CODES.includes(code);
37424
- }
37425
- var ALL_FINDING_CODES, FINDING_CODES;
37426
- var init_finding_codes = __esm(() => {
37427
- ALL_FINDING_CODES = [
37428
- "L1.markdown-parse",
37429
- "L1.schema-validation",
37430
- "L2.missing-required-section",
37431
- "L2.forbidden-section",
37432
- "L2.disallowed-section",
37433
- "L2.heading-level",
37434
- "L2.section-order",
37435
- "L3.requirements-format",
37436
- "L3.requirements-checkbox",
37437
- "L3.requirements-empty",
37438
- "L3.ac-empty",
37439
- "L3.ac-requirement-coverage",
37440
- "L3.solution-file-line",
37441
- "L3.review-priority-table",
37442
- "L3.testing-coverage",
37443
- "L3.required-section-placeholder",
37444
- "L3.plan-format",
37445
- "L3.unchecked-checklist",
37446
- "L3.ac-checklist-text",
37447
- "L3.ac-bdd-error",
37448
- "L3.ac-bdd-warning",
37449
- "L3.ac-bdd-invalid",
37450
- "L3.scope-delineation",
37451
- "L3.one-active-goal",
37452
- "L3.children-limit",
37453
- "L4.design-placeholder",
37454
- "L4.feature-not-found",
37455
- "L4.feature-terminal",
37456
- "L4.missing-feature-id",
37457
- "L4.parent-not-found",
37458
- "L4.dependency-not-found",
37459
- "L4.rollup-subtasks-open",
37460
- "L4.rollup-parent-open",
37461
- "L4.rollup-missing-roster",
37462
- "L4.readiness-blocked",
37463
- "L4.prose-prerequisite-unlisted",
37464
- "L4.prerequisite-cycle",
37465
- "L4.prerequisite-not-done",
37466
- "L4.gate-language",
37467
- "L4.linked-task-parse-failed",
37468
- "L4.orphan-scenarios",
37469
- "L4.uncovered-task-scenario",
37470
- "L4.uncovered-feature-scenario",
37471
- "L4.verifying-incomplete-tasks",
37472
- "L4.dogfood-missing",
37473
- "L4.stale-line-anchor",
37474
- "L4.anchor-subject-mismatch",
37475
- "L4.malformed-verdict-artifact",
37476
- "L4.scenario-unverified",
37477
- "L4.testing-verdict-stub"
37478
- ];
37479
- FINDING_CODES = {
37480
- L1_MARKDOWN_PARSE: "L1.markdown-parse",
37481
- L1_SCHEMA_VALIDATION: "L1.schema-validation",
37482
- L2_MISSING_REQUIRED_SECTION: "L2.missing-required-section",
37483
- L2_FORBIDDEN_SECTION: "L2.forbidden-section",
37484
- L2_DISALLOWED_SECTION: "L2.disallowed-section",
37485
- L2_HEADING_LEVEL: "L2.heading-level",
37486
- L2_SECTION_ORDER: "L2.section-order",
37487
- L3_REQUIREMENTS_FORMAT: "L3.requirements-format",
37488
- L3_REQUIREMENTS_CHECKBOX: "L3.requirements-checkbox",
37489
- L3_REQUIREMENTS_EMPTY: "L3.requirements-empty",
37490
- L3_AC_EMPTY: "L3.ac-empty",
37491
- L3_AC_REQUIREMENT_COVERAGE: "L3.ac-requirement-coverage",
37492
- L3_SOLUTION_FILE_LINE: "L3.solution-file-line",
37493
- L3_REVIEW_PRIORITY_TABLE: "L3.review-priority-table",
37494
- L3_TESTING_COVERAGE: "L3.testing-coverage",
37495
- L3_REQUIRED_SECTION_PLACEHOLDER: "L3.required-section-placeholder",
37496
- L3_PLAN_FORMAT: "L3.plan-format",
37497
- L3_UNCHECKED_CHECKLIST: "L3.unchecked-checklist",
37498
- L3_AC_CHECKLIST_TEXT: "L3.ac-checklist-text",
37499
- L3_AC_BDD_ERROR: "L3.ac-bdd-error",
37500
- L3_AC_BDD_WARNING: "L3.ac-bdd-warning",
37501
- L3_AC_BDD_INVALID: "L3.ac-bdd-invalid",
37502
- L3_SCOPE_DELINEATION: "L3.scope-delineation",
37503
- L3_ONE_ACTIVE_GOAL: "L3.one-active-goal",
37504
- L3_CHILDREN_LIMIT: "L3.children-limit",
37505
- L4_DESIGN_PLACEHOLDER: "L4.design-placeholder",
37506
- L4_FEATURE_NOT_FOUND: "L4.feature-not-found",
37507
- L4_FEATURE_TERMINAL: "L4.feature-terminal",
37508
- L4_MISSING_FEATURE_ID: "L4.missing-feature-id",
37509
- L4_PARENT_NOT_FOUND: "L4.parent-not-found",
37510
- L4_DEPENDENCY_NOT_FOUND: "L4.dependency-not-found",
37511
- L4_ROLLUP_SUBTASKS_OPEN: "L4.rollup-subtasks-open",
37512
- L4_ROLLUP_PARENT_OPEN: "L4.rollup-parent-open",
37513
- L4_ROLLUP_MISSING_ROSTER: "L4.rollup-missing-roster",
37514
- L4_READINESS_BLOCKED: "L4.readiness-blocked",
37515
- L4_PROSE_PREREQUISITE_UNLISTED: "L4.prose-prerequisite-unlisted",
37516
- L4_PREREQUISITE_CYCLE: "L4.prerequisite-cycle",
37517
- L4_PREREQUISITE_NOT_DONE: "L4.prerequisite-not-done",
37518
- L4_GATE_LANGUAGE: "L4.gate-language",
37519
- L4_LINKED_TASK_PARSE_FAILED: "L4.linked-task-parse-failed",
37520
- L4_ORPHAN_SCENARIOS: "L4.orphan-scenarios",
37521
- L4_UNCOVERED_TASK_SCENARIO: "L4.uncovered-task-scenario",
37522
- L4_UNCOVERED_FEATURE_SCENARIO: "L4.uncovered-feature-scenario",
37523
- L4_DOGFOOD_MISSING: "L4.dogfood-missing",
37524
- L4_VERIFYING_INCOMPLETE_TASKS: "L4.verifying-incomplete-tasks",
37525
- L4_STALE_LINE_ANCHOR: "L4.stale-line-anchor",
37526
- L4_ANCHOR_SUBJECT_MISMATCH: "L4.anchor-subject-mismatch",
37527
- L4_MALFORMED_VERDICT_ARTIFACT: "L4.malformed-verdict-artifact",
37528
- L4_SCENARIO_UNVERIFIED: "L4.scenario-unverified",
37529
- L4_TESTING_VERDICT_STUB: "L4.testing-verdict-stub"
37530
- };
37531
- });
37532
-
37533
- // ../../packages/config/src/projects.ts
37534
- import { homedir } from "os";
37535
- import { join } from "path";
37536
- function getProjectsFilePath() {
37537
- if (process.env.SPUR_PROJECTS_FILE) {
37538
- return process.env.SPUR_PROJECTS_FILE;
37539
- }
37540
- return join(homedir(), ".config", "spur", "projects.json");
37541
- }
37542
- var projectEntrySchema, projectsFileSchema;
37543
- var init_projects = __esm(() => {
37544
- init_zod();
37545
- projectEntrySchema = exports_external.object({
37546
- name: exports_external.string().min(1),
37547
- path: exports_external.string().min(1),
37548
- port: exports_external.number().int().nonnegative().default(0)
37549
- });
37550
- projectsFileSchema = exports_external.object({
37551
- schema_version: exports_external.literal(1).default(1),
37552
- projects: exports_external.array(projectEntrySchema).default([])
37553
- });
37554
- });
37555
-
37556
- // ../../packages/config/src/index.ts
37557
- function folderPathWithDefault(defaultPath) {
37558
- return exports_external.preprocess((v) => v == null ? undefined : v, exports_external.string().default(defaultPath));
37559
- }
37560
- function normalizeExecutorTier(value) {
37561
- return value === "capable" ? "capable-1" : value;
37562
- }
37563
- function normalizeMember(member) {
37564
- return typeof member === "string" ? { executor: member } : { ...member };
37565
- }
37566
- function memberLocalId(member, roster, index2) {
37567
- if (member.id !== undefined)
37568
- return member.id;
37569
- if (member.executor !== undefined)
37570
- return member.executor;
37571
- const role = member.role;
37572
- if (role === undefined)
37573
- return "";
37574
- let n2 = 0;
37575
- for (let i2 = 0;i2 <= index2; i2++) {
37576
- const m = roster[i2];
37577
- if (m !== undefined && m.id === undefined && m.executor === undefined && m.role === role)
37578
- n2 += 1;
37579
- }
37580
- return `${role}-${n2}`;
37581
- }
37582
- function resolveExecutor(name, agentConfig, opts) {
37583
- const exec = agentConfig?.executors?.find((entry) => entry.name === name);
37584
- if (exec !== undefined)
37585
- return { agent: exec.agent, model: exec.model };
37586
- if (opts?.isCanonicalAgent === undefined || opts.isCanonicalAgent(name)) {
37587
- return { agent: name };
37588
- }
37589
- throw new Error(`Unknown executor or agent reference: "${name}"`);
37590
- }
37591
- function resolveHistoryRefreshTrigger(config3) {
37592
- const parsed = HistoryRefreshConfigSchema.parse(config3?.history?.refresh ?? {});
37593
- return { onCompletion: parsed.on_completion, debounceMs: parsed.debounce_ms };
37594
- }
37595
- function parseEnvBoolean(value) {
37596
- if (value === undefined || value === "")
37597
- return;
37598
- const normalized = value.toLowerCase();
37599
- if (["1", "true", "yes", "on"].includes(normalized))
37600
- return true;
37601
- if (["0", "false", "no", "off"].includes(normalized))
37602
- return false;
37603
- throw new Error(`Invalid boolean environment value: expected true/false, received ${value}`);
37604
- }
37605
- function buildConfigFromEnv(env = process.env) {
37606
- return configSchema2.parse({
37607
- database: {
37608
- url: env[SPUR_ENV_VARS.databaseUrl]
37609
- },
37610
- server: {
37611
- port: env[SPUR_ENV_VARS.port],
37612
- host: env[SPUR_ENV_VARS.host]
37613
- },
37614
- telemetry: {
37615
- enabled: parseEnvBoolean(env.SPUR_TELEMETRY_ENABLED),
37616
- endpoint: env.SPUR_TELEMETRY_ENDPOINT
37617
- },
37618
- logging: {
37619
- level: env.SPUR_LOG_LEVEL
37620
- }
37621
- });
37622
- }
37623
- var SPUR_ENV_VARS, SPUR_LOG_LEVELS, DEFAULT_TASKS_DIR = "docs/tasks", DEFAULT_FEATURES_DIR = "docs/features", DEFAULT_DATABASE_URL = ".spur/spur.db", IN_MEMORY_DATABASE_URL = ":memory:", folderConfigSchema, tasksConfigSchema, featuresConfigSchema, EXECUTOR_CAPABILITY_TIERS, executorCapabilityTierSchema, AGENT_ROLE_NAMES, DEFAULT_AGENT_ROLES, AgentExecutorConfigSchema, AgentRoleConfigSchema, AGENT_ID_REGEX, TeamMemberConfigSchema, TeamConfigSchema, AgentOutputConfigSchema, AgentConfigSchema, RulesConfigSchema, WorkflowsConfigSchema, WorkflowConfigSchema, RedactionConfigSchema, HistoryRefreshConfigSchema, HistoryConfigSchema, spurConfigSchema, configSchema2;
37624
- var init_src = __esm(() => {
37625
- init_zod();
37626
- init_finding_codes();
37627
- init_projects();
37628
- SPUR_ENV_VARS = {
37629
- nodeEnv: "NODE_ENV",
37630
- port: "PORT",
37631
- host: "HOST",
37632
- publicApiUrl: "PUBLIC_API_URL",
37633
- databaseUrl: "DATABASE_URL"
37634
- };
37635
- SPUR_LOG_LEVELS = ["debug", "info", "warn", "error"];
37636
- folderConfigSchema = exports_external.object({
37637
- baseCounter: exports_external.number().int().nonnegative().default(0),
37638
- label: exports_external.string().optional()
37639
- });
37640
- tasksConfigSchema = exports_external.object({
37641
- folders: exports_external.record(exports_external.string(), folderConfigSchema).default({}),
37642
- active: folderPathWithDefault(DEFAULT_TASKS_DIR),
37643
- severity: exports_external.record(exports_external.string(), exports_external.enum(["error", "warning", "off"])).optional()
37644
- }).superRefine((data, ctx) => {
37645
- if (data.severity) {
37646
- for (const code of Object.keys(data.severity)) {
37647
- if (!isFindingCode(code)) {
37648
- ctx.addIssue({
37649
- code: exports_external.ZodIssueCode.custom,
37650
- path: ["severity", code],
37651
- message: `Unknown finding code "${code}" in severity overrides map`
37652
- });
37653
- }
37654
- }
37655
- }
37656
- });
37657
- featuresConfigSchema = exports_external.object({
37658
- dir: folderPathWithDefault(DEFAULT_FEATURES_DIR)
37659
- });
37660
- EXECUTOR_CAPABILITY_TIERS = ["cheap", "standard", "capable-1", "capable-2", "capable-3"];
37661
- executorCapabilityTierSchema = exports_external.preprocess((value) => {
37662
- if (value === undefined || value === null)
37663
- return;
37664
- return normalizeExecutorTier(value);
37665
- }, exports_external.enum(EXECUTOR_CAPABILITY_TIERS).optional());
37666
- AGENT_ROLE_NAMES = ["scribe", "coder", "reviewer", "planner"];
37667
- DEFAULT_AGENT_ROLES = new Map([
37668
- ["scribe", { tier: "cheap", stages: ["changelog"] }],
37669
- ["coder", { tier: "standard", stages: ["implement", "test", "wrap"] }],
37670
- ["reviewer", { tier: "capable-1", stages: ["verify", "review", "dogfood"] }],
37671
- ["planner", { tier: "capable-2", stages: ["plan", "refine", "brainstorm"] }]
37672
- ]);
37673
- AgentExecutorConfigSchema = exports_external.object({
37674
- name: exports_external.string().min(1),
37675
- agent: exports_external.string().min(1),
37676
- model: exports_external.string().min(1).optional(),
37677
- tier: executorCapabilityTierSchema.optional()
37678
- });
37679
- AgentRoleConfigSchema = exports_external.object({
37680
- tier: exports_external.enum(EXECUTOR_CAPABILITY_TIERS).optional(),
37681
- stages: exports_external.array(exports_external.string().min(1)).min(1).optional()
37682
- });
37683
- AGENT_ID_REGEX = /^[a-z][a-z0-9_-]{1,63}$/;
37684
- TeamMemberConfigSchema = exports_external.union([
37685
- exports_external.string().min(1),
37686
- exports_external.object({
37687
- executor: exports_external.string().min(1).optional(),
37688
- id: exports_external.string().min(1).optional(),
37689
- role: exports_external.enum(AGENT_ROLE_NAMES, {
37690
- error: (issue2) => new Error(`Unknown role "${issue2.input}" \u2014 expected one of: ${AGENT_ROLE_NAMES.join(", ")}`)
37691
- }).optional(),
37692
- purpose: exports_external.string().optional(),
37693
- workspace: exports_external.string().min(1).optional(),
37694
- model: exports_external.string().min(1).optional(),
37695
- autonomy: exports_external.string().optional(),
37696
- systemPrompt: exports_external.string().optional(),
37697
- command: exports_external.array(exports_external.string().min(1)).optional(),
37698
- autostart: exports_external.boolean().optional()
37699
- })
37700
- ]);
37701
- TeamConfigSchema = exports_external.object({
37702
- name: exports_external.string().min(1),
37703
- work_dir: exports_external.string().min(1),
37704
- autostart: exports_external.boolean().optional(),
37705
- members: exports_external.array(TeamMemberConfigSchema).min(1)
37706
- });
37707
- AgentOutputConfigSchema = exports_external.object({
37708
- "max-bytes": exports_external.number().int().positive().optional(),
37709
- "max-lines": exports_external.number().int().positive().optional()
37710
- });
37711
- AgentConfigSchema = exports_external.object({
37712
- default: exports_external.string().optional(),
37713
- executors: exports_external.array(AgentExecutorConfigSchema).optional(),
37714
- roles: exports_external.record(exports_external.string(), AgentRoleConfigSchema).optional(),
37715
- team: exports_external.record(exports_external.string(), TeamConfigSchema).optional(),
37716
- output: AgentOutputConfigSchema.optional(),
37717
- sessionAffinity: exports_external.boolean().optional()
37718
- }).superRefine((value, ctx) => {
37719
- for (const key of Object.keys(value.roles ?? {})) {
37720
- if (!AGENT_ROLE_NAMES.includes(key)) {
37721
- ctx.addIssue({
37722
- code: exports_external.ZodIssueCode.custom,
37723
- path: ["roles", key],
37724
- message: `Unknown role "${key}" \u2014 expected one of: ${AGENT_ROLE_NAMES.join(", ")}`
37725
- });
37726
- }
37727
- }
37728
- const executors = value.executors;
37729
- const executorNames = new Set;
37730
- if (executors !== undefined) {
37731
- const seen = new Set;
37732
- for (const [index2, executor] of executors.entries()) {
37733
- if (seen.has(executor.name)) {
37734
- ctx.addIssue({
37735
- code: exports_external.ZodIssueCode.custom,
37736
- message: `Duplicate executor name: ${executor.name}`,
37737
- path: ["executors", index2, "name"]
37738
- });
37739
- }
37740
- if (executor.name === "inline" || executor.name === "auto") {
37741
- ctx.addIssue({
37742
- code: exports_external.ZodIssueCode.custom,
37743
- message: `Executor name "${executor.name}" is reserved (the --agent selector value '${executor.name}' has fixed semantics); rename executor at index ${index2}.`,
37744
- path: ["executors", index2, "name"]
37745
- });
37746
- }
37747
- if (AGENT_ROLE_NAMES.includes(executor.name)) {
37748
- ctx.addIssue({
37749
- code: exports_external.ZodIssueCode.custom,
37750
- message: `Executor name "${executor.name}" collides with the role selector namespace ("${executor.name}"); rename executor at index ${index2}.`,
37751
- path: ["executors", index2, "name"]
37752
- });
37753
- }
37754
- seen.add(executor.name);
37755
- executorNames.add(executor.name);
37756
- }
37757
- }
37758
- const team = value.team;
37759
- if (team === undefined)
37760
- return;
37761
- const seenComposed = new Set;
37762
- for (const [teamId, teamConfig] of Object.entries(team)) {
37763
- const seenLocal = new Set;
37764
- const members = teamConfig.members.map(normalizeMember);
37765
- members.forEach((ref, index2) => {
37766
- if (ref.executor === undefined && ref.role === undefined) {
37767
- ctx.addIssue({
37768
- code: exports_external.ZodIssueCode.custom,
37769
- message: `Team member at index ${index2} in team "${teamId}" declares neither role nor executor \u2014 at least one of role or executor is required.`,
37770
- path: ["team", teamId, "members", index2]
37771
- });
37772
- return;
37773
- }
37774
- const localId = memberLocalId(ref, members, index2);
37775
- if (seenLocal.has(localId)) {
37776
- ctx.addIssue({
37777
- code: exports_external.ZodIssueCode.custom,
37778
- message: `Duplicate team member id: ${localId} in team "${teamId}"`,
37779
- path: ["team", teamId, "members", index2]
37780
- });
37781
- }
37782
- seenLocal.add(localId);
37783
- const composedId = `${teamId}-${localId}`;
37784
- if (!AGENT_ID_REGEX.test(composedId)) {
37785
- ctx.addIssue({
37786
- code: exports_external.ZodIssueCode.custom,
37787
- message: `Invalid composed agent id "${composedId}": team key "${teamId}" + member "${localId}" must match ^[a-z][a-z0-9_-]{1,63}$ (2-64 chars, lowercase).`,
37788
- path: ["team", teamId, "members", index2]
37789
- });
37790
- }
37791
- if (seenComposed.has(composedId)) {
37792
- ctx.addIssue({
37793
- code: exports_external.ZodIssueCode.custom,
37794
- message: `Composed agent id "${composedId}" collides across teams \u2014 every <teamId>-<localId> must be globally unique (a hyphenated team key can overlap another team's member id).`,
37795
- path: ["team", teamId, "members", index2]
37796
- });
37797
- }
37798
- seenComposed.add(composedId);
37799
- if (ref.id !== undefined && AGENT_ROLE_NAMES.includes(ref.id)) {
37800
- ctx.addIssue({
37801
- code: exports_external.ZodIssueCode.custom,
37802
- message: `Team member id "${ref.id}" collides with the role selector namespace ("${ref.id}") in team "${teamId}"; rename the member id.`,
37803
- path: ["team", teamId, "members", index2]
37804
- });
37805
- }
37806
- if (ref.id !== undefined && executorNames.has(ref.id)) {
37807
- ctx.addIssue({
37808
- code: exports_external.ZodIssueCode.custom,
37809
- message: `Team member id "${ref.id}" collides with executor name "${ref.id}" in team "${teamId}"; the --agent selector namespace must be pairwise disjoint.`,
37810
- path: ["team", teamId, "members", index2]
37811
- });
37812
- }
37813
- if (executorNames.has(composedId)) {
37814
- ctx.addIssue({
37815
- code: exports_external.ZodIssueCode.custom,
37816
- message: `Composed agent id "${composedId}" collides with executor name "${composedId}"; the --agent selector namespace must be pairwise disjoint.`,
37817
- path: ["team", teamId, "members", index2]
37818
- });
37819
- }
37820
- if (AGENT_ROLE_NAMES.includes(composedId)) {
37821
- ctx.addIssue({
37822
- code: exports_external.ZodIssueCode.custom,
37823
- message: `Composed agent id "${composedId}" collides with the role selector namespace ("${composedId}"); the --agent selector namespace must be pairwise disjoint.`,
37824
- path: ["team", teamId, "members", index2]
37825
- });
37826
- }
37827
- });
37828
- }
37829
- });
37830
- RulesConfigSchema = exports_external.object({
37831
- paths: exports_external.array(exports_external.string()).optional()
37832
- });
37833
- WorkflowsConfigSchema = exports_external.object({
37834
- paths: exports_external.array(exports_external.string()).optional()
37835
- });
37836
- WorkflowConfigSchema = exports_external.object({
37837
- logRetentionDays: exports_external.number().int().positive().default(30)
37838
- });
37839
- RedactionConfigSchema = exports_external.object({
37840
- enabled: exports_external.boolean().optional()
37841
- });
37842
- HistoryRefreshConfigSchema = exports_external.object({
37843
- on_completion: exports_external.boolean().default(false),
37844
- debounce_ms: exports_external.number().int().min(1000).default(600000)
37845
- });
37846
- HistoryConfigSchema = exports_external.object({
37847
- refresh: HistoryRefreshConfigSchema.optional()
37848
- });
37849
- spurConfigSchema = exports_external.object({
37850
- version: exports_external.string().optional(),
37851
- name: exports_external.string().optional(),
37852
- agent: AgentConfigSchema.optional(),
37853
- rules: RulesConfigSchema.optional(),
37854
- workflows: WorkflowsConfigSchema.optional(),
37855
- workflow: WorkflowConfigSchema.optional(),
37856
- redaction: RedactionConfigSchema.optional(),
37857
- history: HistoryConfigSchema.optional(),
37858
- tasks: tasksConfigSchema.optional(),
37859
- features: featuresConfigSchema.optional()
37860
- });
37861
- configSchema2 = exports_external.object({
37862
- database: exports_external.object({
37863
- url: exports_external.string().default(DEFAULT_DATABASE_URL)
37864
- }).default({ url: DEFAULT_DATABASE_URL }),
37865
- server: exports_external.object({
37866
- port: exports_external.coerce.number().int().positive().default(3000),
37867
- host: exports_external.string().default("localhost"),
37868
- openBrowser: exports_external.boolean().default(true),
37869
- webDistPath: exports_external.string().nullable().default(null)
37870
- }).default({ port: 3000, host: "localhost", openBrowser: true, webDistPath: null }),
37871
- telemetry: exports_external.object({
37872
- enabled: exports_external.boolean().default(false),
37873
- endpoint: exports_external.string().optional()
37874
- }).default({ enabled: false }),
37875
- logging: exports_external.object({
37876
- level: exports_external.enum(SPUR_LOG_LEVELS).default("info")
37877
- }).default({ level: "info" })
37878
- });
37879
- });
37880
-
37881
37421
  // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.4.42+815552c3ca302057/node_modules/@gobing-ai/ts-runtime/dist/config.js
37882
37422
  function parseYamlObject(text3) {
37883
37423
  let parsed;
@@ -37927,7 +37467,7 @@ function interpolateTree2(value) {
37927
37467
  function buildConfigFromObject2(raw, options = {}) {
37928
37468
  const interpolated = interpolateTree2(raw);
37929
37469
  const merged = options.overrides ? deepMerge(interpolated, options.overrides) : interpolated;
37930
- const result = configSchema3.safeParse(merged);
37470
+ const result = configSchema2.safeParse(merged);
37931
37471
  if (!result.success) {
37932
37472
  throw new ConfigLoadError2("Config validation failed", result.error.issues);
37933
37473
  }
@@ -37964,12 +37504,12 @@ function deepFreeze2(obj) {
37964
37504
  }
37965
37505
  return obj;
37966
37506
  }
37967
- var configSchema3, ENV_INTERPOLATION_RE2, YamlParseError, ConfigLoadError2;
37507
+ var configSchema2, ENV_INTERPOLATION_RE2, YamlParseError, ConfigLoadError2;
37968
37508
  var init_config2 = __esm(() => {
37969
37509
  init_dist();
37970
37510
  init_dist2();
37971
37511
  init_zod();
37972
- configSchema3 = exports_external.object({
37512
+ configSchema2 = exports_external.object({
37973
37513
  app: exports_external.object({
37974
37514
  name: exports_external.string().default("app"),
37975
37515
  env: exports_external.enum(["development", "staging", "production", "test"]).default("development"),
@@ -39435,7 +38975,7 @@ __export(exports_dist2, {
39435
38975
  createLogStream: () => createLogStream2,
39436
38976
  createInMemoryProcessRegistry: () => createInMemoryProcessRegistry2,
39437
38977
  createCfFileSystem: () => createCfFileSystem2,
39438
- configSchema: () => configSchema3,
38978
+ configSchema: () => configSchema2,
39439
38979
  cloudflareWorkersFactory: () => cloudflareWorkersFactory2,
39440
38980
  buildConfigFromYaml: () => buildConfigFromYaml,
39441
38981
  buildConfigFromObject: () => buildConfigFromObject2,
@@ -39475,6 +39015,481 @@ var init_dist5 = __esm(() => {
39475
39015
  init_types2();
39476
39016
  });
39477
39017
 
39018
+ // ../../packages/config/src/finding-codes.ts
39019
+ function isFindingCode(code) {
39020
+ return ALL_FINDING_CODES.includes(code);
39021
+ }
39022
+ var ALL_FINDING_CODES, FINDING_CODES;
39023
+ var init_finding_codes = __esm(() => {
39024
+ ALL_FINDING_CODES = [
39025
+ "L1.markdown-parse",
39026
+ "L1.schema-validation",
39027
+ "L2.missing-required-section",
39028
+ "L2.forbidden-section",
39029
+ "L2.disallowed-section",
39030
+ "L2.heading-level",
39031
+ "L2.section-order",
39032
+ "L3.requirements-format",
39033
+ "L3.requirements-checkbox",
39034
+ "L3.requirements-empty",
39035
+ "L3.ac-empty",
39036
+ "L3.ac-requirement-coverage",
39037
+ "L3.solution-file-line",
39038
+ "L3.review-priority-table",
39039
+ "L3.testing-coverage",
39040
+ "L3.required-section-placeholder",
39041
+ "L3.plan-format",
39042
+ "L3.unchecked-checklist",
39043
+ "L3.ac-checklist-text",
39044
+ "L3.ac-bdd-error",
39045
+ "L3.ac-bdd-warning",
39046
+ "L3.ac-bdd-invalid",
39047
+ "L3.scope-delineation",
39048
+ "L3.one-active-goal",
39049
+ "L3.children-limit",
39050
+ "L4.design-placeholder",
39051
+ "L4.feature-not-found",
39052
+ "L4.feature-terminal",
39053
+ "L4.missing-feature-id",
39054
+ "L4.parent-not-found",
39055
+ "L4.dependency-not-found",
39056
+ "L4.rollup-subtasks-open",
39057
+ "L4.rollup-parent-open",
39058
+ "L4.rollup-missing-roster",
39059
+ "L4.readiness-blocked",
39060
+ "L4.prose-prerequisite-unlisted",
39061
+ "L4.prerequisite-cycle",
39062
+ "L4.prerequisite-not-done",
39063
+ "L4.gate-language",
39064
+ "L4.linked-task-parse-failed",
39065
+ "L4.orphan-scenarios",
39066
+ "L4.uncovered-task-scenario",
39067
+ "L4.uncovered-feature-scenario",
39068
+ "L4.verifying-incomplete-tasks",
39069
+ "L4.dogfood-missing",
39070
+ "L4.stale-line-anchor",
39071
+ "L4.anchor-subject-mismatch",
39072
+ "L4.malformed-verdict-artifact",
39073
+ "L4.scenario-unverified",
39074
+ "L4.testing-verdict-stub",
39075
+ "L4.evidence-not-recoverable"
39076
+ ];
39077
+ FINDING_CODES = {
39078
+ L1_MARKDOWN_PARSE: "L1.markdown-parse",
39079
+ L1_SCHEMA_VALIDATION: "L1.schema-validation",
39080
+ L2_MISSING_REQUIRED_SECTION: "L2.missing-required-section",
39081
+ L2_FORBIDDEN_SECTION: "L2.forbidden-section",
39082
+ L2_DISALLOWED_SECTION: "L2.disallowed-section",
39083
+ L2_HEADING_LEVEL: "L2.heading-level",
39084
+ L2_SECTION_ORDER: "L2.section-order",
39085
+ L3_REQUIREMENTS_FORMAT: "L3.requirements-format",
39086
+ L3_REQUIREMENTS_CHECKBOX: "L3.requirements-checkbox",
39087
+ L3_REQUIREMENTS_EMPTY: "L3.requirements-empty",
39088
+ L3_AC_EMPTY: "L3.ac-empty",
39089
+ L3_AC_REQUIREMENT_COVERAGE: "L3.ac-requirement-coverage",
39090
+ L3_SOLUTION_FILE_LINE: "L3.solution-file-line",
39091
+ L3_REVIEW_PRIORITY_TABLE: "L3.review-priority-table",
39092
+ L3_TESTING_COVERAGE: "L3.testing-coverage",
39093
+ L3_REQUIRED_SECTION_PLACEHOLDER: "L3.required-section-placeholder",
39094
+ L3_PLAN_FORMAT: "L3.plan-format",
39095
+ L3_UNCHECKED_CHECKLIST: "L3.unchecked-checklist",
39096
+ L3_AC_CHECKLIST_TEXT: "L3.ac-checklist-text",
39097
+ L3_AC_BDD_ERROR: "L3.ac-bdd-error",
39098
+ L3_AC_BDD_WARNING: "L3.ac-bdd-warning",
39099
+ L3_AC_BDD_INVALID: "L3.ac-bdd-invalid",
39100
+ L3_SCOPE_DELINEATION: "L3.scope-delineation",
39101
+ L3_ONE_ACTIVE_GOAL: "L3.one-active-goal",
39102
+ L3_CHILDREN_LIMIT: "L3.children-limit",
39103
+ L4_DESIGN_PLACEHOLDER: "L4.design-placeholder",
39104
+ L4_FEATURE_NOT_FOUND: "L4.feature-not-found",
39105
+ L4_FEATURE_TERMINAL: "L4.feature-terminal",
39106
+ L4_MISSING_FEATURE_ID: "L4.missing-feature-id",
39107
+ L4_PARENT_NOT_FOUND: "L4.parent-not-found",
39108
+ L4_DEPENDENCY_NOT_FOUND: "L4.dependency-not-found",
39109
+ L4_ROLLUP_SUBTASKS_OPEN: "L4.rollup-subtasks-open",
39110
+ L4_ROLLUP_PARENT_OPEN: "L4.rollup-parent-open",
39111
+ L4_ROLLUP_MISSING_ROSTER: "L4.rollup-missing-roster",
39112
+ L4_READINESS_BLOCKED: "L4.readiness-blocked",
39113
+ L4_PROSE_PREREQUISITE_UNLISTED: "L4.prose-prerequisite-unlisted",
39114
+ L4_PREREQUISITE_CYCLE: "L4.prerequisite-cycle",
39115
+ L4_PREREQUISITE_NOT_DONE: "L4.prerequisite-not-done",
39116
+ L4_GATE_LANGUAGE: "L4.gate-language",
39117
+ L4_LINKED_TASK_PARSE_FAILED: "L4.linked-task-parse-failed",
39118
+ L4_ORPHAN_SCENARIOS: "L4.orphan-scenarios",
39119
+ L4_UNCOVERED_TASK_SCENARIO: "L4.uncovered-task-scenario",
39120
+ L4_UNCOVERED_FEATURE_SCENARIO: "L4.uncovered-feature-scenario",
39121
+ L4_DOGFOOD_MISSING: "L4.dogfood-missing",
39122
+ L4_VERIFYING_INCOMPLETE_TASKS: "L4.verifying-incomplete-tasks",
39123
+ L4_STALE_LINE_ANCHOR: "L4.stale-line-anchor",
39124
+ L4_ANCHOR_SUBJECT_MISMATCH: "L4.anchor-subject-mismatch",
39125
+ L4_MALFORMED_VERDICT_ARTIFACT: "L4.malformed-verdict-artifact",
39126
+ L4_SCENARIO_UNVERIFIED: "L4.scenario-unverified",
39127
+ L4_TESTING_VERDICT_STUB: "L4.testing-verdict-stub",
39128
+ L4_EVIDENCE_NOT_RECOVERABLE: "L4.evidence-not-recoverable"
39129
+ };
39130
+ });
39131
+
39132
+ // ../../packages/config/src/projects.ts
39133
+ import { homedir } from "os";
39134
+ import { join } from "path";
39135
+ function getProjectsFilePath() {
39136
+ if (process.env.SPUR_PROJECTS_FILE) {
39137
+ return process.env.SPUR_PROJECTS_FILE;
39138
+ }
39139
+ return join(homedir(), ".config", "spur", "projects.json");
39140
+ }
39141
+ var projectEntrySchema, projectsFileSchema;
39142
+ var init_projects = __esm(() => {
39143
+ init_zod();
39144
+ projectEntrySchema = exports_external.object({
39145
+ name: exports_external.string().min(1),
39146
+ path: exports_external.string().min(1),
39147
+ port: exports_external.number().int().nonnegative().default(0)
39148
+ });
39149
+ projectsFileSchema = exports_external.object({
39150
+ schema_version: exports_external.literal(1).default(1),
39151
+ projects: exports_external.array(projectEntrySchema).default([])
39152
+ });
39153
+ });
39154
+
39155
+ // ../../packages/config/src/index.ts
39156
+ function folderPathWithDefault(defaultPath) {
39157
+ return exports_external.preprocess((v) => v == null ? undefined : v, exports_external.string().default(defaultPath));
39158
+ }
39159
+ function normalizeExecutorTier(value) {
39160
+ return value === "capable" ? "capable-1" : value;
39161
+ }
39162
+ function normalizeMember(member) {
39163
+ return typeof member === "string" ? { executor: member } : { ...member };
39164
+ }
39165
+ function memberLocalId(member, roster, index2) {
39166
+ if (member.id !== undefined)
39167
+ return member.id;
39168
+ if (member.executor !== undefined)
39169
+ return member.executor;
39170
+ const role = member.role;
39171
+ if (role === undefined)
39172
+ return "";
39173
+ let n2 = 0;
39174
+ for (let i2 = 0;i2 <= index2; i2++) {
39175
+ const m = roster[i2];
39176
+ if (m !== undefined && m.id === undefined && m.executor === undefined && m.role === role)
39177
+ n2 += 1;
39178
+ }
39179
+ return `${role}-${n2}`;
39180
+ }
39181
+ function resolveExecutor(name, agentConfig, opts) {
39182
+ const exec = agentConfig?.executors?.find((entry) => entry.name === name);
39183
+ if (exec !== undefined)
39184
+ return { agent: exec.agent, model: exec.model };
39185
+ if (opts?.isCanonicalAgent === undefined || opts.isCanonicalAgent(name)) {
39186
+ return { agent: name };
39187
+ }
39188
+ throw new Error(`Unknown executor or agent reference: "${name}"`);
39189
+ }
39190
+ function resolveHistoryRefreshTrigger(config4) {
39191
+ const parsed = HistoryRefreshConfigSchema.parse(config4?.history?.refresh ?? {});
39192
+ return { onCompletion: parsed.on_completion, debounceMs: parsed.debounce_ms };
39193
+ }
39194
+ function misplacedGlobalKeys(parsed) {
39195
+ const misplaced = [];
39196
+ for (const key of PROJECT_SHAPED_GLOBAL_KEYS) {
39197
+ if (key in parsed)
39198
+ misplaced.push(key);
39199
+ }
39200
+ const agent = parsed.agent;
39201
+ if (typeof agent === "object" && agent !== null && "team" in agent) {
39202
+ misplaced.push("agent.team");
39203
+ }
39204
+ return misplaced;
39205
+ }
39206
+ function parseEnvBoolean(value) {
39207
+ if (value === undefined || value === "")
39208
+ return;
39209
+ const normalized = value.toLowerCase();
39210
+ if (["1", "true", "yes", "on"].includes(normalized))
39211
+ return true;
39212
+ if (["0", "false", "no", "off"].includes(normalized))
39213
+ return false;
39214
+ throw new Error(`Invalid boolean environment value: expected true/false, received ${value}`);
39215
+ }
39216
+ function buildConfigFromEnv(env = process.env) {
39217
+ return configSchema3.parse({
39218
+ database: {
39219
+ url: env[SPUR_ENV_VARS.databaseUrl]
39220
+ },
39221
+ server: {
39222
+ port: env[SPUR_ENV_VARS.port],
39223
+ host: env[SPUR_ENV_VARS.host]
39224
+ },
39225
+ telemetry: {
39226
+ enabled: parseEnvBoolean(env.SPUR_TELEMETRY_ENABLED),
39227
+ endpoint: env.SPUR_TELEMETRY_ENDPOINT
39228
+ },
39229
+ logging: {
39230
+ level: env.SPUR_LOG_LEVEL
39231
+ }
39232
+ });
39233
+ }
39234
+ var SPUR_ENV_VARS, SPUR_LOG_LEVELS, DEFAULT_TASKS_DIR = "docs/tasks", DEFAULT_FEATURES_DIR = "docs/features", DEFAULT_DATABASE_URL = ".spur/spur.db", IN_MEMORY_DATABASE_URL = ":memory:", folderConfigSchema, tasksConfigSchema, featuresConfigSchema, EXECUTOR_CAPABILITY_TIERS, executorCapabilityTierSchema, AGENT_ROLE_NAMES, DEFAULT_AGENT_ROLES, AgentExecutorConfigSchema, AgentRoleConfigSchema, AGENT_ID_REGEX, TeamMemberConfigSchema, TeamConfigSchema, AgentOutputConfigSchema, AgentConfigSchema, RulesConfigSchema, WorkflowsConfigSchema, WorkflowConfigSchema, RedactionConfigSchema, HistoryRefreshConfigSchema, HistoryConfigSchema, spurConfigSchema, PROJECT_SHAPED_GLOBAL_KEYS, configSchema3;
39235
+ var init_src = __esm(() => {
39236
+ init_zod();
39237
+ init_finding_codes();
39238
+ init_projects();
39239
+ SPUR_ENV_VARS = {
39240
+ nodeEnv: "NODE_ENV",
39241
+ port: "PORT",
39242
+ host: "HOST",
39243
+ publicApiUrl: "PUBLIC_API_URL",
39244
+ databaseUrl: "DATABASE_URL"
39245
+ };
39246
+ SPUR_LOG_LEVELS = ["debug", "info", "warn", "error"];
39247
+ folderConfigSchema = exports_external.object({
39248
+ baseCounter: exports_external.number().int().nonnegative().default(0),
39249
+ label: exports_external.string().optional()
39250
+ });
39251
+ tasksConfigSchema = exports_external.object({
39252
+ folders: exports_external.record(exports_external.string(), folderConfigSchema).default({}),
39253
+ active: folderPathWithDefault(DEFAULT_TASKS_DIR),
39254
+ severity: exports_external.record(exports_external.string(), exports_external.enum(["error", "warning", "off"])).optional()
39255
+ }).superRefine((data, ctx) => {
39256
+ if (data.severity) {
39257
+ for (const code of Object.keys(data.severity)) {
39258
+ if (!isFindingCode(code)) {
39259
+ ctx.addIssue({
39260
+ code: exports_external.ZodIssueCode.custom,
39261
+ path: ["severity", code],
39262
+ message: `Unknown finding code "${code}" in severity overrides map`
39263
+ });
39264
+ }
39265
+ }
39266
+ }
39267
+ });
39268
+ featuresConfigSchema = exports_external.object({
39269
+ dir: folderPathWithDefault(DEFAULT_FEATURES_DIR)
39270
+ });
39271
+ EXECUTOR_CAPABILITY_TIERS = ["cheap", "standard", "capable-1", "capable-2", "capable-3"];
39272
+ executorCapabilityTierSchema = exports_external.preprocess((value) => {
39273
+ if (value === undefined || value === null)
39274
+ return;
39275
+ return typeof value === "string" ? normalizeExecutorTier(value) : value;
39276
+ }, exports_external.enum(EXECUTOR_CAPABILITY_TIERS).optional());
39277
+ AGENT_ROLE_NAMES = ["scribe", "coder", "reviewer", "planner"];
39278
+ DEFAULT_AGENT_ROLES = new Map([
39279
+ ["scribe", { tier: "cheap", stages: ["changelog"] }],
39280
+ ["coder", { tier: "standard", stages: ["implement", "test", "wrap"] }],
39281
+ ["reviewer", { tier: "capable-1", stages: ["verify", "review", "dogfood"] }],
39282
+ ["planner", { tier: "capable-2", stages: ["plan", "refine", "brainstorm"] }]
39283
+ ]);
39284
+ AgentExecutorConfigSchema = exports_external.object({
39285
+ name: exports_external.string().min(1),
39286
+ agent: exports_external.string().min(1),
39287
+ model: exports_external.string().min(1).optional(),
39288
+ tier: executorCapabilityTierSchema.optional()
39289
+ });
39290
+ AgentRoleConfigSchema = exports_external.object({
39291
+ tier: exports_external.enum(EXECUTOR_CAPABILITY_TIERS).optional(),
39292
+ stages: exports_external.array(exports_external.string().min(1)).min(1).optional()
39293
+ });
39294
+ AGENT_ID_REGEX = /^[a-z][a-z0-9_-]{1,63}$/;
39295
+ TeamMemberConfigSchema = exports_external.union([
39296
+ exports_external.string().min(1),
39297
+ exports_external.object({
39298
+ executor: exports_external.string().min(1).optional(),
39299
+ id: exports_external.string().min(1).optional(),
39300
+ role: exports_external.enum(AGENT_ROLE_NAMES, {
39301
+ error: (issue2) => new Error(`Unknown role "${issue2.input}" \u2014 expected one of: ${AGENT_ROLE_NAMES.join(", ")}`)
39302
+ }).optional(),
39303
+ purpose: exports_external.string().optional(),
39304
+ workspace: exports_external.string().min(1).optional(),
39305
+ model: exports_external.string().min(1).optional(),
39306
+ autonomy: exports_external.string().optional(),
39307
+ systemPrompt: exports_external.string().optional(),
39308
+ command: exports_external.array(exports_external.string().min(1)).optional(),
39309
+ autostart: exports_external.boolean().optional()
39310
+ })
39311
+ ]);
39312
+ TeamConfigSchema = exports_external.object({
39313
+ name: exports_external.string().min(1),
39314
+ work_dir: exports_external.string().min(1),
39315
+ autostart: exports_external.boolean().optional(),
39316
+ members: exports_external.array(TeamMemberConfigSchema).min(1)
39317
+ });
39318
+ AgentOutputConfigSchema = exports_external.object({
39319
+ "max-bytes": exports_external.number().int().positive().optional(),
39320
+ "max-lines": exports_external.number().int().positive().optional()
39321
+ });
39322
+ AgentConfigSchema = exports_external.object({
39323
+ default: exports_external.string().optional(),
39324
+ executors: exports_external.array(AgentExecutorConfigSchema).optional(),
39325
+ roles: exports_external.record(exports_external.string(), AgentRoleConfigSchema).optional(),
39326
+ team: exports_external.record(exports_external.string(), TeamConfigSchema).optional(),
39327
+ output: AgentOutputConfigSchema.optional(),
39328
+ sessionAffinity: exports_external.boolean().optional()
39329
+ }).superRefine((value, ctx) => {
39330
+ for (const key of Object.keys(value.roles ?? {})) {
39331
+ if (!AGENT_ROLE_NAMES.includes(key)) {
39332
+ ctx.addIssue({
39333
+ code: exports_external.ZodIssueCode.custom,
39334
+ path: ["roles", key],
39335
+ message: `Unknown role "${key}" \u2014 expected one of: ${AGENT_ROLE_NAMES.join(", ")}`
39336
+ });
39337
+ }
39338
+ }
39339
+ const executors = value.executors;
39340
+ const executorNames = new Set;
39341
+ if (executors !== undefined) {
39342
+ const seen = new Set;
39343
+ for (const [index2, executor] of executors.entries()) {
39344
+ if (seen.has(executor.name)) {
39345
+ ctx.addIssue({
39346
+ code: exports_external.ZodIssueCode.custom,
39347
+ message: `Duplicate executor name: ${executor.name}`,
39348
+ path: ["executors", index2, "name"]
39349
+ });
39350
+ }
39351
+ if (executor.name === "inline" || executor.name === "auto") {
39352
+ ctx.addIssue({
39353
+ code: exports_external.ZodIssueCode.custom,
39354
+ message: `Executor name "${executor.name}" is reserved (the --agent selector value '${executor.name}' has fixed semantics); rename executor at index ${index2}.`,
39355
+ path: ["executors", index2, "name"]
39356
+ });
39357
+ }
39358
+ if (AGENT_ROLE_NAMES.includes(executor.name)) {
39359
+ ctx.addIssue({
39360
+ code: exports_external.ZodIssueCode.custom,
39361
+ message: `Executor name "${executor.name}" collides with the role selector namespace ("${executor.name}"); rename executor at index ${index2}.`,
39362
+ path: ["executors", index2, "name"]
39363
+ });
39364
+ }
39365
+ seen.add(executor.name);
39366
+ executorNames.add(executor.name);
39367
+ }
39368
+ }
39369
+ const team = value.team;
39370
+ if (team === undefined)
39371
+ return;
39372
+ const seenComposed = new Set;
39373
+ for (const [teamId, teamConfig] of Object.entries(team)) {
39374
+ const seenLocal = new Set;
39375
+ const members = teamConfig.members.map(normalizeMember);
39376
+ members.forEach((ref, index2) => {
39377
+ if (ref.executor === undefined && ref.role === undefined) {
39378
+ ctx.addIssue({
39379
+ code: exports_external.ZodIssueCode.custom,
39380
+ message: `Team member at index ${index2} in team "${teamId}" declares neither role nor executor \u2014 at least one of role or executor is required.`,
39381
+ path: ["team", teamId, "members", index2]
39382
+ });
39383
+ return;
39384
+ }
39385
+ const localId = memberLocalId(ref, members, index2);
39386
+ if (seenLocal.has(localId)) {
39387
+ ctx.addIssue({
39388
+ code: exports_external.ZodIssueCode.custom,
39389
+ message: `Duplicate team member id: ${localId} in team "${teamId}"`,
39390
+ path: ["team", teamId, "members", index2]
39391
+ });
39392
+ }
39393
+ seenLocal.add(localId);
39394
+ const composedId = `${teamId}-${localId}`;
39395
+ if (!AGENT_ID_REGEX.test(composedId)) {
39396
+ ctx.addIssue({
39397
+ code: exports_external.ZodIssueCode.custom,
39398
+ message: `Invalid composed agent id "${composedId}": team key "${teamId}" + member "${localId}" must match ^[a-z][a-z0-9_-]{1,63}$ (2-64 chars, lowercase).`,
39399
+ path: ["team", teamId, "members", index2]
39400
+ });
39401
+ }
39402
+ if (seenComposed.has(composedId)) {
39403
+ ctx.addIssue({
39404
+ code: exports_external.ZodIssueCode.custom,
39405
+ message: `Composed agent id "${composedId}" collides across teams \u2014 every <teamId>-<localId> must be globally unique (a hyphenated team key can overlap another team's member id).`,
39406
+ path: ["team", teamId, "members", index2]
39407
+ });
39408
+ }
39409
+ seenComposed.add(composedId);
39410
+ if (ref.id !== undefined && AGENT_ROLE_NAMES.includes(ref.id)) {
39411
+ ctx.addIssue({
39412
+ code: exports_external.ZodIssueCode.custom,
39413
+ message: `Team member id "${ref.id}" collides with the role selector namespace ("${ref.id}") in team "${teamId}"; rename the member id.`,
39414
+ path: ["team", teamId, "members", index2]
39415
+ });
39416
+ }
39417
+ if (ref.id !== undefined && executorNames.has(ref.id)) {
39418
+ ctx.addIssue({
39419
+ code: exports_external.ZodIssueCode.custom,
39420
+ message: `Team member id "${ref.id}" collides with executor name "${ref.id}" in team "${teamId}"; the --agent selector namespace must be pairwise disjoint.`,
39421
+ path: ["team", teamId, "members", index2]
39422
+ });
39423
+ }
39424
+ if (executorNames.has(composedId)) {
39425
+ ctx.addIssue({
39426
+ code: exports_external.ZodIssueCode.custom,
39427
+ message: `Composed agent id "${composedId}" collides with executor name "${composedId}"; the --agent selector namespace must be pairwise disjoint.`,
39428
+ path: ["team", teamId, "members", index2]
39429
+ });
39430
+ }
39431
+ if (AGENT_ROLE_NAMES.includes(composedId)) {
39432
+ ctx.addIssue({
39433
+ code: exports_external.ZodIssueCode.custom,
39434
+ message: `Composed agent id "${composedId}" collides with the role selector namespace ("${composedId}"); the --agent selector namespace must be pairwise disjoint.`,
39435
+ path: ["team", teamId, "members", index2]
39436
+ });
39437
+ }
39438
+ });
39439
+ }
39440
+ });
39441
+ RulesConfigSchema = exports_external.object({
39442
+ paths: exports_external.array(exports_external.string()).optional()
39443
+ });
39444
+ WorkflowsConfigSchema = exports_external.object({
39445
+ paths: exports_external.array(exports_external.string()).optional()
39446
+ });
39447
+ WorkflowConfigSchema = exports_external.object({
39448
+ logRetentionDays: exports_external.number().int().positive().default(30)
39449
+ });
39450
+ RedactionConfigSchema = exports_external.object({
39451
+ enabled: exports_external.boolean().optional()
39452
+ });
39453
+ HistoryRefreshConfigSchema = exports_external.object({
39454
+ on_completion: exports_external.boolean().default(false),
39455
+ debounce_ms: exports_external.number().int().min(1000).default(600000)
39456
+ });
39457
+ HistoryConfigSchema = exports_external.object({
39458
+ refresh: HistoryRefreshConfigSchema.optional()
39459
+ });
39460
+ spurConfigSchema = exports_external.object({
39461
+ version: exports_external.string().optional(),
39462
+ name: exports_external.string().optional(),
39463
+ agent: AgentConfigSchema.optional(),
39464
+ rules: RulesConfigSchema.optional(),
39465
+ workflows: WorkflowsConfigSchema.optional(),
39466
+ workflow: WorkflowConfigSchema.optional(),
39467
+ redaction: RedactionConfigSchema.optional(),
39468
+ history: HistoryConfigSchema.optional(),
39469
+ tasks: tasksConfigSchema.optional(),
39470
+ features: featuresConfigSchema.optional()
39471
+ });
39472
+ PROJECT_SHAPED_GLOBAL_KEYS = ["name", "bootstrap", "rules", "redaction", "tasks", "features"];
39473
+ configSchema3 = exports_external.object({
39474
+ database: exports_external.object({
39475
+ url: exports_external.string().default(DEFAULT_DATABASE_URL)
39476
+ }).default({ url: DEFAULT_DATABASE_URL }),
39477
+ server: exports_external.object({
39478
+ port: exports_external.coerce.number().int().positive().default(3000),
39479
+ host: exports_external.string().default("localhost"),
39480
+ openBrowser: exports_external.boolean().default(true),
39481
+ webDistPath: exports_external.string().nullable().default(null)
39482
+ }).default({ port: 3000, host: "localhost", openBrowser: true, webDistPath: null }),
39483
+ telemetry: exports_external.object({
39484
+ enabled: exports_external.boolean().default(false),
39485
+ endpoint: exports_external.string().optional()
39486
+ }).default({ enabled: false }),
39487
+ logging: exports_external.object({
39488
+ level: exports_external.enum(SPUR_LOG_LEVELS).default("info")
39489
+ }).default({ level: "info" })
39490
+ });
39491
+ });
39492
+
39478
39493
  // ../../packages/config/src/bundled-config.ts
39479
39494
  import { existsSync as existsSync3, readdirSync as readdirSync3, statSync as statSync4 } from "fs";
39480
39495
  import { dirname as dirname3, join as join2 } from "path";
@@ -39523,7 +39538,9 @@ function walk(dir, relPrefix, filter = /\.(ya?ml|json)$/i) {
39523
39538
  function isDroppedFromProjectSeed(rel) {
39524
39539
  if (rel === BUNDLED_GLOBAL_CONFIG)
39525
39540
  return true;
39526
- if (rel.startsWith("templates/task/"))
39541
+ if (rel.startsWith("templates/"))
39542
+ return true;
39543
+ if (rel.startsWith("workflows/"))
39527
39544
  return true;
39528
39545
  if (rel.startsWith("plugins/"))
39529
39546
  return true;
@@ -39676,6 +39693,9 @@ async function readRawYamlLayer(configPath, kind) {
39676
39693
  throw new Error(`Failed to parse ${kind} config ${configPath}: ${error51.message}`);
39677
39694
  }
39678
39695
  }
39696
+ function parseConfigYaml2(text3) {
39697
+ return $parse(text3) ?? {};
39698
+ }
39679
39699
  function isPlainObject6(value) {
39680
39700
  return typeof value === "object" && value !== null && !Array.isArray(value);
39681
39701
  }
@@ -39718,21 +39738,20 @@ function mergeByKeyIdentity(globalItems, projectItems, identityOf, segments) {
39718
39738
  const indexByIdentity = new Map;
39719
39739
  for (const item of globalItems) {
39720
39740
  const identity4 = identityOf(item);
39721
- if (identity4 === undefined || !indexByIdentity.has(identity4)) {
39722
- if (identity4 !== undefined)
39723
- indexByIdentity.set(identity4, merged.length);
39724
- merged.push(item);
39725
- }
39741
+ if (identity4 !== undefined && !indexByIdentity.has(identity4))
39742
+ indexByIdentity.set(identity4, merged.length);
39743
+ merged.push(item);
39726
39744
  }
39745
+ const matchedProjectIdentities = new Set;
39727
39746
  for (const item of projectItems) {
39728
39747
  const identity4 = identityOf(item);
39729
- const existing = identity4 === undefined ? undefined : indexByIdentity.get(identity4);
39748
+ const existing = identity4 === undefined || matchedProjectIdentities.has(identity4) ? undefined : indexByIdentity.get(identity4);
39730
39749
  if (existing === undefined) {
39731
- if (identity4 !== undefined)
39732
- indexByIdentity.set(identity4, merged.length);
39733
39750
  merged.push(item);
39734
39751
  continue;
39735
39752
  }
39753
+ if (identity4 !== undefined)
39754
+ matchedProjectIdentities.add(identity4);
39736
39755
  const base2 = merged[existing];
39737
39756
  merged[existing] = isPlainObject6(base2) && isPlainObject6(item) ? mergeDeep(base2, item, [...segments, existing]) : item;
39738
39757
  }
@@ -50252,6 +50271,53 @@ function selectorDigest(selector) {
50252
50271
  var HISTORY_ARTIFACT_SCHEMA_VERSION = 1;
50253
50272
  var init_artifact = () => {};
50254
50273
 
50274
+ // ../../packages/domain/src/analytics/artifact-digest.ts
50275
+ import { createHash as createHash2 } from "crypto";
50276
+ function canonicalize(value, key) {
50277
+ if (key === "generatedAt" || key === "validatedAt" || key === "baselineArtifactDigest")
50278
+ return null;
50279
+ if (Array.isArray(value)) {
50280
+ const raw = value.map((v) => JSON.stringify(canonicalize(v, "")));
50281
+ return RANKED_ARTIFACT_KEYS.has(key) ? raw : [...raw].sort();
50282
+ }
50283
+ if (value !== null && typeof value === "object") {
50284
+ const out = {};
50285
+ for (const k of Object.keys(value).sort()) {
50286
+ out[k] = canonicalize(value[k], k);
50287
+ }
50288
+ return out;
50289
+ }
50290
+ return value;
50291
+ }
50292
+ function semanticArtifactDigest(artifactJson) {
50293
+ const material = JSON.stringify(canonicalize(artifactJson, "root"));
50294
+ return createHash2("sha256").update(material).digest("hex");
50295
+ }
50296
+ var ARTIFACT_ARRAY_CLASSIFICATION, RANKED_ARTIFACT_KEYS;
50297
+ var init_artifact_digest = __esm(() => {
50298
+ ARTIFACT_ARRAY_CLASSIFICATION = {
50299
+ byTool: "ranked",
50300
+ bySession: "ranked",
50301
+ topStepsByTokens: "ranked",
50302
+ topStepsByDuration: "ranked",
50303
+ topSteps: "ranked",
50304
+ bottlenecks: "ranked",
50305
+ coverage: "set",
50306
+ daily: "set",
50307
+ loops: "set",
50308
+ warnings: "set",
50309
+ pairings: "set",
50310
+ ladderSnapshot: "set",
50311
+ stepSupport: "set",
50312
+ phases: "set",
50313
+ tools: "set",
50314
+ skills: "set",
50315
+ sources: "set",
50316
+ models: "set"
50317
+ };
50318
+ RANKED_ARTIFACT_KEYS = new Set(Object.entries(ARTIFACT_ARRAY_CLASSIFICATION).filter(([, kind]) => kind === "ranked").map(([key]) => key));
50319
+ });
50320
+
50255
50321
  // ../../packages/domain/src/analytics/models.ts
50256
50322
  function resolvePricing(model) {
50257
50323
  if (model === undefined || model.length === 0)
@@ -50868,6 +50934,22 @@ async function bySession(db2, sel, top, opts) {
50868
50934
  };
50869
50935
  });
50870
50936
  }
50937
+ async function selectionPopulation(db2, sel, opts) {
50938
+ const { where, params } = buildMessageWhere(sel);
50939
+ const folded = withMessageDedup(where);
50940
+ const sessionScoped = `${folded} AND m.session_id NOT IN ('', 'unknown', 'session')`;
50941
+ const wm = applyWatermarkToWhere(where, opts?.watermark);
50942
+ const wmSession = applyWatermarkToWhere(sessionScoped, opts?.watermark);
50943
+ const [sessionRow, toolRow] = await Promise.all([
50944
+ db2.queryFirst(`SELECT COUNT(DISTINCT m.session_id) AS n FROM history_message m ${wmSession.where} LIMIT ?`, ...params, ...wmSession.params, 1),
50945
+ db2.queryFirst(`SELECT COUNT(DISTINCT tc.tool_name) AS n
50946
+ FROM history_tool_call tc
50947
+ JOIN history_message m ON m.record_hash = tc.message_hash
50948
+ ${wm.where}
50949
+ LIMIT ?`, ...params, ...wm.params, 1)
50950
+ ]);
50951
+ return { sessions: sessionRow?.n ?? 0, tools: toolRow?.n ?? 0 };
50952
+ }
50871
50953
  async function loops(db2, sel, opts) {
50872
50954
  const { where, params } = buildMessageWhere(sel);
50873
50955
  const wm = applyWatermarkToWhere(where, opts?.watermark);
@@ -52013,7 +52095,11 @@ function narrowArtifact(artifact, opts, artifactPath) {
52013
52095
  narrowed = {
52014
52096
  ...artifact,
52015
52097
  byTool: artifact.byTool.slice(0, top),
52016
- bySession: artifact.bySession.slice(0, top)
52098
+ bySession: artifact.bySession.slice(0, top),
52099
+ population: artifact.population ? {
52100
+ ...artifact.population,
52101
+ appliedTop: Math.min(top, artifact.population.appliedTop)
52102
+ } : undefined
52017
52103
  };
52018
52104
  }
52019
52105
  parts.push(`top ${top}`);
@@ -52408,7 +52494,7 @@ function renderSessionSummary(artifact, derived) {
52408
52494
  "",
52409
52495
  "| Metric | Value |",
52410
52496
  "| --- | --- |",
52411
- `| Sessions | ${artifact.bySession.length.toLocaleString("en-US")} |`,
52497
+ `| Sessions | ${fmtTopOf(artifact.population?.sessions, artifact.population?.appliedTop)} |`,
52412
52498
  `| Wall-clock span | ${wall} |`,
52413
52499
  `| Messages | ${t.messages.toLocaleString("en-US")} |`,
52414
52500
  `| Tool calls | ${t.toolCalls.toLocaleString("en-US")} |`,
@@ -52633,23 +52719,38 @@ function renderBottlenecks(derived) {
52633
52719
  }
52634
52720
  function renderRawData(artifact) {
52635
52721
  const s = artifact.selector;
52722
+ const p = artifact.population;
52636
52723
  const lines = [
52637
52724
  "## Raw Data",
52638
52725
  "",
52639
52726
  `- Selector: since=${s.since ?? "-"} until=${s.until ?? "-"} sources=${s.sources ? s.sources.join(",") : "all"} session=${s.sessionId ?? "-"} run=${s.runId ?? "-"} task=${s.taskWbs ?? "-"}`,
52640
- `- Counts: ${artifact.bySession.length} sessions \xB7 ${artifact.byTool.length} tools \xB7 ${artifact.loops.length} loops \xB7 ${artifact.warnings.length} warnings`
52727
+ `- Counts: ${fmtTopOf(p?.sessions, p?.appliedTop)} sessions \xB7 ${fmtTopOf(p?.tools, p?.appliedTop)} tools \xB7 ${p?.loops ?? artifact.loops.length} loops \xB7 ${p?.warnings ?? artifact.warnings.length} warnings`
52641
52728
  ];
52642
52729
  if (artifact.warnings.length > 0) {
52643
- lines.push(`- Warning codes: ${[...new Set(artifact.warnings.map((w) => w.code))].join(", ")}`);
52730
+ lines.push("- Warnings:");
52731
+ for (const w of artifact.warnings) {
52732
+ lines.push(` - ${w.code}${w.source !== undefined ? ` (${w.source})` : ""} \u2014 ${w.detail}`);
52733
+ }
52644
52734
  }
52645
52735
  if (artifact.coverage.length > 0) {
52646
- lines.push("", "| Source | Status | Files | Messages | Tool calls | Unknown |", "| --- | --- | ---: | ---: | ---: | ---: |");
52736
+ lines.push("", "| Source | Status | Files | Messages | Tool calls | Unknown | Last imported | Parse err | Validation err |", "| --- | --- | ---: | ---: | ---: | ---: | --- | ---: | ---: |");
52647
52737
  for (const c3 of artifact.coverage) {
52648
- lines.push(`| ${c3.source} | ${c3.status} | ${c3.files} | ${c3.messages} | ${c3.toolCalls} | ${c3.unknownRecords} |`);
52738
+ const parseTrunc = c3.parseErrorSamples.length >= MAX_ERROR_SAMPLES ? " (truncated)" : "";
52739
+ const validationTrunc = c3.validationErrorSamples.length >= MAX_ERROR_SAMPLES ? " (truncated)" : "";
52740
+ lines.push(`| ${c3.source} | ${c3.status} | ${c3.files} | ${c3.messages} | ${c3.toolCalls} | ${c3.unknownRecords} | ${c3.lastImportedAt ?? "not available"} | ${c3.parseErrors}${parseTrunc} | ${c3.validationErrors}${validationTrunc} |`);
52649
52741
  }
52650
52742
  }
52651
52743
  return lines;
52652
52744
  }
52745
+ function fmtTopOf(population, appliedTop) {
52746
+ if (population === undefined) {
52747
+ return "not available";
52748
+ }
52749
+ if (appliedTop !== undefined && appliedTop < population) {
52750
+ return `top ${appliedTop} of ${population.toLocaleString("en-US")}`;
52751
+ }
52752
+ return population.toLocaleString("en-US");
52753
+ }
52653
52754
  function fmtWall(ms) {
52654
52755
  if (ms < 1000)
52655
52756
  return `${Math.round(ms)}ms`;
@@ -52678,7 +52779,7 @@ function fmtToolDur(ms, bucket) {
52678
52779
  const allUnmeasured = bucket.calls > 0 && bucket.durationUnmeasured === bucket.calls;
52679
52780
  return allUnmeasured ? "n/a" : fmtDur(ms);
52680
52781
  }
52681
- var NOT_AVAILABLE_PER_STEP = "> not available - artifact predates the per-step sections (rerun `spur history analyze`)", NOT_AVAILABLE_DERIVED = "> not available \u2014 artifact has no derived block (rerun `spur history analyze`)";
52782
+ var NOT_AVAILABLE_PER_STEP = "> not available - artifact predates the per-step sections (rerun `spur history analyze`)", NOT_AVAILABLE_DERIVED = "> not available \u2014 artifact has no derived block (rerun `spur history analyze`)", MAX_ERROR_SAMPLES = 20;
52682
52783
  var init_render_forensics = __esm(() => {
52683
52784
  init_artifact();
52684
52785
  init_render_report();
@@ -53477,6 +53578,7 @@ var init_run_cost = __esm(() => {
53477
53578
  // ../../packages/domain/src/analytics/index.ts
53478
53579
  var init_analytics = __esm(() => {
53479
53580
  init_artifact();
53581
+ init_artifact_digest();
53480
53582
  init_costs();
53481
53583
  init_forensic_query();
53482
53584
  init_models();
@@ -57396,7 +57498,7 @@ var init_dist7 = __esm(() => {
57396
57498
  });
57397
57499
 
57398
57500
  // ../../node_modules/.bun/@gobing-ai+ts-llm-jsonl-importer@0.4.42+f66f1d7f0d37c145/node_modules/@gobing-ai/ts-llm-jsonl-importer/dist/hash.js
57399
- import { createHash as createHash2 } from "crypto";
57501
+ import { createHash as createHash3 } from "crypto";
57400
57502
  function stableJson(value) {
57401
57503
  if (Array.isArray(value)) {
57402
57504
  return `[${value.map((entry) => entry === undefined ? "null" : stableJson(entry)).join(",")}]`;
@@ -57408,7 +57510,7 @@ function stableJson(value) {
57408
57510
  return JSON.stringify(value) ?? "null";
57409
57511
  }
57410
57512
  function sha256(value) {
57411
- return createHash2("sha256").update(stableJson(value)).digest("hex");
57513
+ return createHash3("sha256").update(stableJson(value)).digest("hex");
57412
57514
  }
57413
57515
  var init_hash = () => {};
57414
57516
 
@@ -64434,9 +64536,9 @@ var init_db2 = __esm(() => {
64434
64536
  });
64435
64537
 
64436
64538
  // ../../packages/domain/src/envelope/fingerprint.ts
64437
- import { createHash as createHash3 } from "crypto";
64539
+ import { createHash as createHash4 } from "crypto";
64438
64540
  function computeContentHash(content) {
64439
- return createHash3("sha256").update(content, "utf-8").digest("hex");
64541
+ return createHash4("sha256").update(content, "utf-8").digest("hex");
64440
64542
  }
64441
64543
  var init_fingerprint = () => {};
64442
64544
 
@@ -65256,7 +65358,7 @@ function attributeWithoutTelemetry(layers, capturedLayers) {
65256
65358
  }
65257
65359
 
65258
65360
  // ../../packages/domain/src/envelope/invalidation.ts
65259
- import { createHash as createHash4 } from "crypto";
65361
+ import { createHash as createHash5 } from "crypto";
65260
65362
  function computeInvalidationFingerprint(trigger) {
65261
65363
  if (trigger.kind === "tool-output")
65262
65364
  return null;
@@ -65280,7 +65382,7 @@ function computeInvalidationFingerprint(trigger) {
65280
65382
  default:
65281
65383
  return null;
65282
65384
  }
65283
- return createHash4("sha256").update(raw, "utf-8").digest("hex");
65385
+ return createHash5("sha256").update(raw, "utf-8").digest("hex");
65284
65386
  }
65285
65387
  function checkLayerStale(layer, freshFingerprint) {
65286
65388
  if (freshFingerprint === null) {
@@ -66588,7 +66690,9 @@ __export(exports_src, {
66588
66690
  serializeStablePrefix: () => serializeStablePrefix,
66589
66691
  serializeLayer: () => serializeLayer,
66590
66692
  serializeEnvelope: () => serializeEnvelope,
66693
+ semanticArtifactDigest: () => semanticArtifactDigest,
66591
66694
  selectorDigest: () => selectorDigest,
66695
+ selectionPopulation: () => selectionPopulation,
66592
66696
  sectionMatrixSchema: () => sectionMatrixSchema,
66593
66697
  scaffoldFeatureScenarios: () => scaffoldFeatureScenarios,
66594
66698
  runRetention: () => runRetention,
@@ -66763,6 +66867,7 @@ __export(exports_src, {
66763
66867
  ROUTING_SUMMARY_DEFAULT_WINDOW_MS: () => ROUTING_SUMMARY_DEFAULT_WINDOW_MS,
66764
66868
  REPORT_MODES: () => REPORT_MODES,
66765
66869
  REGISTERED_CANONICAL_STAGES: () => REGISTERED_CANONICAL_STAGES,
66870
+ RANKED_ARTIFACT_KEYS: () => RANKED_ARTIFACT_KEYS,
66766
66871
  QUEUE_JOBS_SCHEMA_SQL: () => QUEUE_JOBS_SCHEMA_SQL,
66767
66872
  PlanningEventDao: () => PlanningEventDao,
66768
66873
  PhaseRunDao: () => PhaseRunDao,
@@ -66803,7 +66908,8 @@ __export(exports_src, {
66803
66908
  ArtifactDao: () => ArtifactDao,
66804
66909
  ActionRunDao: () => ActionRunDao,
66805
66910
  AUTHORITY_LANES: () => AUTHORITY_LANES,
66806
- ARTIFACT_DIRECTIONS: () => ARTIFACT_DIRECTIONS
66911
+ ARTIFACT_DIRECTIONS: () => ARTIFACT_DIRECTIONS,
66912
+ ARTIFACT_ARRAY_CLASSIFICATION: () => ARTIFACT_ARRAY_CLASSIFICATION
66807
66913
  });
66808
66914
  var init_src2 = __esm(() => {
66809
66915
  init_db2();
@@ -67884,6 +67990,9 @@ class AgentService {
67884
67990
  async doctor(args, deps) {
67885
67991
  const executors = this.ctx.agentConfig?.executors;
67886
67992
  const doctorRunner = deps?.doctorRunner ?? new DoctorRunner({ env: this.ctx.env, executors });
67993
+ if (this.ctx.rolesSource === "fallback" && !args.json) {
67994
+ this.ctx.output.error("agent.roles: no config layer defines a table \u2014 built-in DEFAULT_AGENT_ROLES fallback in effect");
67995
+ }
67887
67996
  if (args.agent === undefined) {
67888
67997
  const results2 = await doctorRunner.runAll();
67889
67998
  return this.renderDoctor(results2, executors, args.json, args.agent);
@@ -67892,7 +68001,11 @@ class AgentService {
67892
68001
  if (roleDef !== undefined) {
67893
68002
  const resolved = await this.resolveRole(args.agent, roleDef.tier, doctorRunner);
67894
68003
  if (!resolved.ok) {
67895
- this.ctx.output.error(resolved.message);
68004
+ if (args.json) {
68005
+ this.ctx.output.write(toJson({ error: { code: "agent-resolution", message: resolved.message } }));
68006
+ } else {
68007
+ this.ctx.output.error(resolved.message);
68008
+ }
67896
68009
  return resolved.exitCode;
67897
68010
  }
67898
68011
  const results2 = [await doctorRunner.runOne(resolved.executor ?? resolved.agent)];
@@ -67915,7 +68028,7 @@ class AgentService {
67915
68028
  const executor = executorByName.get(result.agent) ?? { name: result.agent, agent: result.agent };
67916
68029
  return { ...result, capabilityTier: getExecutorTier(executor) };
67917
68030
  });
67918
- this.ctx.output.write(toJson({ agents: rows }));
68031
+ this.ctx.output.write(toJson({ agents: rows, rolesSource: this.ctx.rolesSource ?? "config" }));
67919
68032
  } else if (agent !== undefined) {
67920
68033
  this.ctx.output.write(renderDoctorDetail(results[0] ?? null));
67921
68034
  } else {
@@ -67929,7 +68042,11 @@ class AgentService {
67929
68042
  execution: this.defaultExecutionOptions(flags)
67930
68043
  });
67931
68044
  if (!outcome.ok) {
67932
- this.ctx.output.error(outcome.message);
68045
+ if (booleanFlag(flags, "json")) {
68046
+ this.ctx.output.write(toJson({ error: { code: "agent-resolution", message: outcome.message } }));
68047
+ } else {
68048
+ this.ctx.output.error(outcome.message);
68049
+ }
67933
68050
  return outcome.exitCode;
67934
68051
  }
67935
68052
  const result = outcome.result;
@@ -69847,6 +69964,294 @@ var init_planning_check_base = __esm(() => {
69847
69964
  init_structural_repair();
69848
69965
  });
69849
69966
 
69967
+ // ../../packages/app/src/services/task-record.ts
69968
+ function escapeTablePipe(s2) {
69969
+ return s2.replace(/\|/g, "\\|");
69970
+ }
69971
+ function parseVerdict(raw, fallbackWbs) {
69972
+ const parsed = parseVerifyVerdict(raw, fallbackWbs);
69973
+ if (parsed.kind === "valid")
69974
+ return parsed.verdict;
69975
+ return { wbs: fallbackWbs ?? "", verdict: "UNKNOWN", requirements: [], acceptanceCriteria: [], checks: [] };
69976
+ }
69977
+ async function readVerdict(fs3, path9, fallbackWbs) {
69978
+ let raw;
69979
+ try {
69980
+ raw = await fs3.readFile(path9);
69981
+ } catch {
69982
+ return { wbs: fallbackWbs ?? "", verdict: "UNKNOWN", requirements: [], acceptanceCriteria: [], checks: [] };
69983
+ }
69984
+ return parseVerdict(raw, fallbackWbs);
69985
+ }
69986
+ function renderTesting(v) {
69987
+ const lines = [];
69988
+ lines.push("**Pipeline verify results**");
69989
+ lines.push("");
69990
+ lines.push(`- Verdict: ${v.verdict} (from verdict artifact)`);
69991
+ lines.push("");
69992
+ if (v.requirements.length === 0) {
69993
+ lines.push("| Requirement | Status | Evidence |");
69994
+ lines.push("|-------------|--------|----------|");
69995
+ lines.push(`| \u2014 | \u2014 | No requirements recorded; verify verdict ${v.verdict} |`);
69996
+ } else {
69997
+ lines.push("| Requirement | Status | Evidence |");
69998
+ lines.push("|-------------|--------|----------|");
69999
+ for (const req of v.requirements) {
70000
+ const evidence = escapeTablePipe(req.evidence.replace(/\n/g, " "));
70001
+ lines.push(`| ${req.id} | ${req.status} | ${evidence} |`);
70002
+ }
70003
+ }
70004
+ if (v.acceptanceCriteria.length > 0) {
70005
+ lines.push("");
70006
+ lines.push("| Acceptance Criteria | Status | Evidence Type | Evidence |");
70007
+ lines.push("|---------------------|--------|---------------|----------|");
70008
+ for (const ac of v.acceptanceCriteria) {
70009
+ const evidence = escapeTablePipe(ac.evidence.replace(/\n/g, " "));
70010
+ lines.push(`| ${ac.id} | ${ac.status} | ${ac.evidenceType} | ${evidence} |`);
70011
+ }
70012
+ }
70013
+ lines.push("- Coverage: N/A (verdict-based; verify pipeline does not measure code coverage)");
70014
+ return lines.join(`
70015
+ `);
70016
+ }
70017
+ function parseTesting(markdown, wbs) {
70018
+ const section = extractTestingSection(markdown);
70019
+ if (section === null || section.trim() === "")
70020
+ return { kind: "missing", wbs };
70021
+ return parseTestingBody(section, wbs);
70022
+ }
70023
+ function extractTestingSection(markdown) {
70024
+ const heading = /^#{1,6}\s+Testing\s*$/m.exec(markdown);
70025
+ if (!heading || heading.index === undefined) {
70026
+ const taskDocument = /^##\s+\d+\.\s+|^###\s+(?:Background|Requirements|Acceptance Criteria|Q&A|Design|Plan|Solution|Review|References|History|Notes)\s*$/m.test(markdown);
70027
+ return taskDocument ? null : markdown;
70028
+ }
70029
+ const level = heading[0].match(/^#+/)?.[0]?.length ?? 2;
70030
+ const bodyStart = heading.index + heading[0].length;
70031
+ const rest = markdown.slice(bodyStart);
70032
+ const next = new RegExp(`^#{1,${level}}\\s+\\S`, "m").exec(rest);
70033
+ return next && next.index !== undefined ? rest.slice(0, next.index) : rest;
70034
+ }
70035
+ function parseTestingBody(body, wbs) {
70036
+ const lines = body.split(`
70037
+ `);
70038
+ const verdict = parseVerdictLine(lines);
70039
+ const requirements = parseCoverageTable(lines, "requirement");
70040
+ const acceptanceCriteria = parseCoverageTable(lines, "acceptance");
70041
+ if (requirements.kind === "malformed" || acceptanceCriteria.kind === "malformed") {
70042
+ return {
70043
+ kind: "malformed",
70044
+ wbs,
70045
+ message: `${requirements.kind === "malformed" ? "requirement" : "acceptance-criteria"} table is truncated or malformed`
70046
+ };
70047
+ }
70048
+ const rows = requirements.rows.length + acceptanceCriteria.rows.length;
70049
+ if (rows === 0) {
70050
+ const reason = verdict !== null ? "Verdict line present but no parseable coverage rows" : "no recognisable coverage rows";
70051
+ return { kind: "invalid", wbs, reason };
70052
+ }
70053
+ const aggregate2 = verdict ?? aggregateVerifyVerdict({ requirements: requirements.rows, acceptanceCriteria: acceptanceCriteria.rows });
70054
+ return {
70055
+ kind: "valid",
70056
+ wbs,
70057
+ verdict: {
70058
+ wbs,
70059
+ verdict: aggregate2,
70060
+ requirements: requirements.rows,
70061
+ acceptanceCriteria: acceptanceCriteria.rows,
70062
+ checks: []
70063
+ }
70064
+ };
70065
+ }
70066
+ function parseVerdictLine(lines) {
70067
+ for (const line of lines) {
70068
+ const m = /^(?:-\s*|\*\*)?Verdict:\s*(PASS|PARTIAL|FAIL|UNKNOWN)\b/i.exec(line.trim());
70069
+ if (m) {
70070
+ const v = m[1]?.toUpperCase();
70071
+ if (v === "PASS" || v === "PARTIAL" || v === "FAIL" || v === "UNKNOWN")
70072
+ return v;
70073
+ }
70074
+ }
70075
+ return null;
70076
+ }
70077
+ function parseCoverageTable(lines, kind) {
70078
+ const rows = [];
70079
+ const headerRe = kind === "requirement" ? /^\|\s*(Requirement|Req|R#)\s*\|/i : /^\|\s*(Acceptance Criteria|AC)\s*\|/i;
70080
+ let colStatus = -1;
70081
+ let colEvidence = -1;
70082
+ let colEvidenceType = -1;
70083
+ let columnCount = -1;
70084
+ let inTable = false;
70085
+ for (const rawLine of lines) {
70086
+ const line = rawLine.trim();
70087
+ if (!line.startsWith("|")) {
70088
+ inTable = false;
70089
+ continue;
70090
+ }
70091
+ const cells = splitTableRow(line);
70092
+ if (cells.length === 0)
70093
+ continue;
70094
+ if (cells.every((c3) => /^[-: ]*$/.test(c3))) {
70095
+ if (inTable && cells.length !== columnCount) {
70096
+ return { kind: "malformed", rows };
70097
+ }
70098
+ continue;
70099
+ }
70100
+ if (!inTable) {
70101
+ if (headerRe.test(line)) {
70102
+ inTable = true;
70103
+ colStatus = cells.findIndex((c3) => /status/i.test(c3));
70104
+ colEvidenceType = cells.findIndex((c3) => /evidence type/i.test(c3));
70105
+ colEvidence = cells.findIndex((c3) => /evidence/i.test(c3) && !/type/i.test(c3));
70106
+ columnCount = cells.length;
70107
+ if (colStatus < 0 || colEvidence < 0 || kind === "acceptance" && colEvidenceType < 0) {
70108
+ return { kind: "malformed", rows };
70109
+ }
70110
+ }
70111
+ continue;
70112
+ }
70113
+ if (cells.length < columnCount) {
70114
+ return { kind: "malformed", rows };
70115
+ }
70116
+ const extraIdCells = cells.length - columnCount;
70117
+ const shifted = (column2) => column2 + extraIdCells;
70118
+ const id = cells.slice(0, extraIdCells + 1).join("|").trim();
70119
+ const statusCell = (cells[shifted(colStatus)] ?? "").trim();
70120
+ if (id === "\u2014" && statusCell === "\u2014")
70121
+ continue;
70122
+ const status = parseRowStatus(statusCell);
70123
+ if (id === "" || status === null)
70124
+ return { kind: "malformed", rows };
70125
+ const evidence = unescapeTablePipe((cells[shifted(colEvidence)] ?? "").trim());
70126
+ rows.push({
70127
+ id,
70128
+ status,
70129
+ evidenceType: kind === "acceptance" ? (cells[shifted(colEvidenceType)] ?? "").trim() : "",
70130
+ evidence
70131
+ });
70132
+ }
70133
+ return { kind: "ok", rows };
70134
+ }
70135
+ function parseRowStatus(raw) {
70136
+ const status = raw.toUpperCase().trim();
70137
+ return ROW_STATUSES.includes(status) ? status : null;
70138
+ }
70139
+ function splitTableRow(line) {
70140
+ const body = line.replace(/^\|/, "").replace(/\|$/, "");
70141
+ const cells = [];
70142
+ let cur = "";
70143
+ for (const ch of body) {
70144
+ if (ch === "|" && !cur.endsWith("\\")) {
70145
+ cells.push(cur);
70146
+ cur = "";
70147
+ continue;
70148
+ }
70149
+ cur += ch;
70150
+ }
70151
+ cells.push(cur);
70152
+ return cells;
70153
+ }
70154
+ function unescapeTablePipe(s2) {
70155
+ return s2.replace(/\\\|/g, "|");
70156
+ }
70157
+ function renderReview(v) {
70158
+ const lines = [];
70159
+ lines.push(`**SECU findings** (pipeline verify step \u2014 verdict: ${v.verdict})`);
70160
+ lines.push("");
70161
+ lines.push("| Priority | Dimension | Location | Finding |");
70162
+ lines.push("|----------|-----------|----------|----------|");
70163
+ if (v.checks.length === 0) {
70164
+ lines.push(`| P4 | \u2014 | \u2014 | No P1\u2013P3 findings; verify verdict ${v.verdict} |`);
70165
+ } else {
70166
+ for (const check2 of v.checks) {
70167
+ const finding = escapeTablePipe(check2.evidence.replace(/\n/g, " "));
70168
+ const priority = /^P[1-4]$/.test(check2.status) ? check2.status : check2.status === "fail" ? "P1" : "P4";
70169
+ lines.push(`| ${priority} | ${check2.name} | \u2014 | ${finding} |`);
70170
+ }
70171
+ }
70172
+ lines.push("");
70173
+ return lines.join(`
70174
+ `);
70175
+ }
70176
+ function renderSolutionFromDiff(diffText) {
70177
+ const lines = [];
70178
+ lines.push("Change-map (auto-generated \u2014 implement step did not record a Solution).");
70179
+ lines.push("Each entry cites the first changed line per file (`file:line`).");
70180
+ lines.push("");
70181
+ lines.push("| Change (`file:line`) |");
70182
+ lines.push("|----------------------|");
70183
+ const citations = extractFileLineCitations(diffText);
70184
+ if (citations.length === 0) {
70185
+ const files = extractChangedFiles(diffText);
70186
+ if (files.length === 0) {
70187
+ lines.push("| `(no changes detected)` |");
70188
+ } else {
70189
+ for (const f of files) {
70190
+ lines.push(`| \`${f}:1\` |`);
70191
+ }
70192
+ }
70193
+ } else {
70194
+ for (const citation of citations) {
70195
+ lines.push(`| \`${citation}\` |`);
70196
+ }
70197
+ }
70198
+ lines.push("");
70199
+ return lines.join(`
70200
+ `);
70201
+ }
70202
+ function extractFileLineCitations(diffText) {
70203
+ const citations = new Set;
70204
+ let currentFile = null;
70205
+ for (const line of diffText.split(`
70206
+ `)) {
70207
+ const fileMatch = /^\+\+\+ b\/(.+)$/.exec(line);
70208
+ if (fileMatch) {
70209
+ currentFile = fileMatch[1] ?? null;
70210
+ continue;
70211
+ }
70212
+ if (currentFile === null)
70213
+ continue;
70214
+ const hunkMatch = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(line);
70215
+ if (hunkMatch) {
70216
+ const newLine = hunkMatch[1];
70217
+ if (newLine !== undefined) {
70218
+ citations.add(`${currentFile}:${newLine}`);
70219
+ }
70220
+ }
70221
+ }
70222
+ return [...citations].sort();
70223
+ }
70224
+ function extractChangedFiles(diffText) {
70225
+ const files = new Set;
70226
+ for (const line of diffText.split(`
70227
+ `)) {
70228
+ const match = /^\+\+\+ b\/(.+)$/.exec(line);
70229
+ if (match) {
70230
+ const filePath = match[1];
70231
+ if (filePath !== undefined)
70232
+ files.add(filePath);
70233
+ }
70234
+ }
70235
+ return [...files].sort();
70236
+ }
70237
+ function gitDiffU0(cwd) {
70238
+ try {
70239
+ const result = new BunSyncProcessExecutor2().runSync({
70240
+ command: "git",
70241
+ args: ["diff", "-U0", "HEAD", "--", "*.ts", "*.tsx", "*.js"],
70242
+ ...cwd !== undefined ? { cwd } : {},
70243
+ rejectOnError: false
70244
+ });
70245
+ return result.exitCode === 0 ? result.stdout : "";
70246
+ } catch {
70247
+ return "";
70248
+ }
70249
+ }
70250
+ var init_task_record = __esm(() => {
70251
+ init_dist5();
70252
+ init_verify_verdict();
70253
+ });
70254
+
69850
70255
  // ../../packages/app/src/services/feature-check.ts
69851
70256
  var exports_feature_check = {};
69852
70257
  __export(exports_feature_check, {
@@ -69979,6 +70384,8 @@ var init_feature_check = __esm(() => {
69979
70384
  init_done_transition_guard();
69980
70385
  init_planning_check_base();
69981
70386
  init_structural_repair();
70387
+ init_task_record();
70388
+ init_verify_verdict();
69982
70389
  DEFAULT_FEATURE_MATRIX = {
69983
70390
  variants: {
69984
70391
  standard: {
@@ -70238,7 +70645,12 @@ var init_feature_check = __esm(() => {
70238
70645
  if (tac.trim().length > 0)
70239
70646
  linkedTaskAc.push(tac);
70240
70647
  linkedTaskSolutions.push(taskDoc.getSection("Solution") ?? "");
70241
- linkedTaskRecords.push({ wbs, status: tStatus, ac: tac });
70648
+ linkedTaskRecords.push({
70649
+ wbs,
70650
+ status: tStatus,
70651
+ ac: tac,
70652
+ testing: taskDoc.getSection("Testing") ?? ""
70653
+ });
70242
70654
  } catch {
70243
70655
  findings.push({
70244
70656
  layer: "L4",
@@ -70350,9 +70762,42 @@ var init_feature_check = __esm(() => {
70350
70762
  }
70351
70763
  }
70352
70764
  const artifacts2 = new Map;
70765
+ const testingByWbs = new Map(linkedTasks.map((t) => [t.wbs, t.testing]));
70353
70766
  for (const wbs of doneWbs) {
70354
70767
  const artifact = await this.readVerdictArtifact(runDir, wbs);
70355
70768
  artifacts2.set(wbs, artifact);
70769
+ const missing = artifact.diagnostics.artifactError === "artifact is missing";
70770
+ if (missing) {
70771
+ const parsed2 = parseTesting(testingByWbs.get(wbs) ?? "", wbs);
70772
+ if (parsed2.kind === "valid") {
70773
+ artifacts2.set(wbs, {
70774
+ path: "tracked ## Testing section",
70775
+ verdict: parsed2.verdict.verdict,
70776
+ computedVerdict: aggregateVerifyVerdict({
70777
+ requirements: parsed2.verdict.requirements,
70778
+ acceptanceCriteria: parsed2.verdict.acceptanceCriteria
70779
+ }),
70780
+ requirements: parsed2.verdict.requirements.map((r) => ({ id: r.id, status: r.status })),
70781
+ acceptanceCriteria: parsed2.verdict.acceptanceCriteria.map((r) => ({
70782
+ id: r.id,
70783
+ status: r.status
70784
+ })),
70785
+ diagnostics: {
70786
+ rejectedRowCount: 0,
70787
+ invalidFields: [],
70788
+ arrayStates: { requirements: "populated", acceptanceCriteria: "populated" }
70789
+ }
70790
+ });
70791
+ } else {
70792
+ findings.push({
70793
+ layer: "L4",
70794
+ code: FINDING_CODES.L4_EVIDENCE_NOT_RECOVERABLE,
70795
+ severity: "warning",
70796
+ section: "Acceptance Criteria",
70797
+ message: `Task ${wbs} has no verdict artifact and its tracked ## Testing section ` + `carries no recoverable coverage evidence (${parsed2.kind}); its evidence ` + "predates durable recording, so it is neither verified nor failed."
70798
+ });
70799
+ }
70800
+ }
70356
70801
  const diagnosticParts = [];
70357
70802
  if (artifact.diagnostics.artifactError !== undefined) {
70358
70803
  if (artifact.diagnostics.artifactError !== "artifact is missing") {
@@ -70398,7 +70843,7 @@ var init_feature_check = __esm(() => {
70398
70843
  const artifact = artifacts2.get(task.wbs);
70399
70844
  if (artifact === undefined)
70400
70845
  continue;
70401
- if (artifact.verdict !== "PASS")
70846
+ if (artifact.verdict !== "PASS" || artifact.computedVerdict !== "PASS")
70402
70847
  continue;
70403
70848
  const rows = [...artifact.requirements, ...artifact.acceptanceCriteria];
70404
70849
  const matched = rows.find((r) => rowMatchesScenario(r.id, sc));
@@ -70428,6 +70873,7 @@ var init_feature_check = __esm(() => {
70428
70873
  return {
70429
70874
  path: loaded.path,
70430
70875
  verdict: typeof parsed.verdict === "string" ? parsed.verdict : undefined,
70876
+ computedVerdict: computeAggregate(loaded.artifact),
70431
70877
  requirements: req.rows,
70432
70878
  acceptanceCriteria: ac.rows,
70433
70879
  diagnostics: {
@@ -77032,7 +77478,8 @@ class HistoryService {
77032
77478
  stepDurationRows,
77033
77479
  cacheWasteAggRow,
77034
77480
  cacheWasteRows,
77035
- stepSupportRows
77481
+ stepSupportRows,
77482
+ population
77036
77483
  ] = await Promise.all([
77037
77484
  messageRollup(db2, selector, queryOpts),
77038
77485
  toolRollup(db2, selector, queryOpts),
@@ -77049,7 +77496,8 @@ class HistoryService {
77049
77496
  topStepsByDuration(db2, selector, top, queryOpts),
77050
77497
  cacheWasteAggregate(db2, selector, queryOpts),
77051
77498
  topCacheWasteSteps(db2, selector, top, queryOpts),
77052
- stepSupport(db2, selector, queryOpts)
77499
+ stepSupport(db2, selector, queryOpts),
77500
+ selectionPopulation(db2, selector, queryOpts)
77053
77501
  ]);
77054
77502
  await dropWatermarkExclude();
77055
77503
  const totals = foldTotals2(mRows, tRows);
@@ -77059,6 +77507,11 @@ class HistoryService {
77059
77507
  const daily = Object.entries(byDay).map(([date6, totals2]) => ({ date: date6, ...totals2 })).sort((a2, b) => a2.date.localeCompare(b.date));
77060
77508
  const coverage = buildCoverage(sourceRows, tRows, driftRows, opts.coverageErrors, opts.importCoverage);
77061
77509
  const derived = computeDerived(spanRows, toolDurRows, todoRows);
77510
+ const warnings = [
77511
+ ...buildWarnings(driftRows, coverage),
77512
+ ...derivedWarnings(derived),
77513
+ ...opts.extraWarnings ?? []
77514
+ ];
77062
77515
  const stateByKey = new Map;
77063
77516
  for (const w of watermarks)
77064
77517
  stateByKey.set(`${w.sessionId}\x00${w.source}`, w.state);
@@ -77097,11 +77550,7 @@ class HistoryService {
77097
77550
  sessionState: stateByKey.get(`${r.sessionId}\x00${r.source}`) ?? "complete"
77098
77551
  })),
77099
77552
  loops: loopRows,
77100
- warnings: [
77101
- ...buildWarnings(driftRows, coverage),
77102
- ...derivedWarnings(derived),
77103
- ...opts.extraWarnings ?? []
77104
- ],
77553
+ warnings,
77105
77554
  pairings,
77106
77555
  ladderSnapshot,
77107
77556
  derived,
@@ -77112,7 +77561,14 @@ class HistoryService {
77112
77561
  inputTokens: cacheWasteAggRow?.inputTokens ?? 0,
77113
77562
  topSteps: cacheWasteRows
77114
77563
  },
77115
- stepSupport: stepSupportRows
77564
+ stepSupport: stepSupportRows,
77565
+ population: {
77566
+ sessions: population.sessions,
77567
+ tools: population.tools,
77568
+ loops: loopRows.length,
77569
+ warnings: warnings.length,
77570
+ appliedTop: top
77571
+ }
77116
77572
  };
77117
77573
  await refreshHistoryRollups(db2);
77118
77574
  if (opts.out !== undefined || opts.cwd !== undefined) {
@@ -77217,8 +77673,8 @@ class HistoryService {
77217
77673
  lastImportedAt: null,
77218
77674
  parseErrors: result.parseErrors.length,
77219
77675
  validationErrors: result.validationErrors.length,
77220
- parseErrorSamples: result.parseErrors.slice(0, MAX_ERROR_SAMPLES).map(formatIssue),
77221
- validationErrorSamples: result.validationErrors.slice(0, MAX_ERROR_SAMPLES).map(formatIssue),
77676
+ parseErrorSamples: result.parseErrors.slice(0, MAX_ERROR_SAMPLES2).map(formatIssue),
77677
+ validationErrorSamples: result.validationErrors.slice(0, MAX_ERROR_SAMPLES2).map(formatIssue),
77222
77678
  ...result.reconciliation ? { reconciliation: result.reconciliation } : {}
77223
77679
  };
77224
77680
  return { coverageEntry, sourceWarnings };
@@ -77421,16 +77877,16 @@ function boundCoverage(coverage) {
77421
77877
  const bounded2 = coverage.map((entry) => {
77422
77878
  const pe = entry.parseErrorSamples;
77423
77879
  const ve = entry.validationErrorSamples;
77424
- for (const sample of pe.slice(MAX_ERROR_SAMPLES)) {
77880
+ for (const sample of pe.slice(MAX_ERROR_SAMPLES2)) {
77425
77881
  overflow.push({ source: entry.source, kind: "parse", sample });
77426
77882
  }
77427
- for (const sample of ve.slice(MAX_ERROR_SAMPLES)) {
77883
+ for (const sample of ve.slice(MAX_ERROR_SAMPLES2)) {
77428
77884
  overflow.push({ source: entry.source, kind: "validation", sample });
77429
77885
  }
77430
77886
  return {
77431
77887
  ...entry,
77432
- parseErrorSamples: pe.slice(0, MAX_ERROR_SAMPLES),
77433
- validationErrorSamples: ve.slice(0, MAX_ERROR_SAMPLES)
77888
+ parseErrorSamples: pe.slice(0, MAX_ERROR_SAMPLES2),
77889
+ validationErrorSamples: ve.slice(0, MAX_ERROR_SAMPLES2)
77434
77890
  };
77435
77891
  });
77436
77892
  return { bounded: bounded2, overflow };
@@ -77527,7 +77983,7 @@ function pruneReports(cwd, retentionDays = REPORT_RETENTION_DAYS, now2 = new Dat
77527
77983
  }
77528
77984
  return pruned.sort((a2, b) => a2.localeCompare(b));
77529
77985
  }
77530
- var SOURCES, FULL_FIDELITY_SOURCES, DEFERRED_SOURCES, MODES, MAX_ERROR_SAMPLES = 20, DEFAULT_SOURCE_TIMEOUT_MS = 600000, REPORT_RETENTION_DAYS = 90;
77986
+ var SOURCES, FULL_FIDELITY_SOURCES, DEFERRED_SOURCES, MODES, MAX_ERROR_SAMPLES2 = 20, DEFAULT_SOURCE_TIMEOUT_MS = 600000, REPORT_RETENTION_DAYS = 90;
77531
77987
  var init_history_service = __esm(() => {
77532
77988
  init_src2();
77533
77989
  init_dist8();
@@ -80177,205 +80633,6 @@ var init_system_event_follow = __esm(() => {
80177
80633
  init_src2();
80178
80634
  });
80179
80635
 
80180
- // ../../packages/app/src/services/task-record.ts
80181
- function escapeTablePipe(s3) {
80182
- return s3.replace(/\|/g, "\\|");
80183
- }
80184
- function parseVerdict(raw, fallbackWbs) {
80185
- if (raw.trim() === "") {
80186
- return { wbs: fallbackWbs ?? "", verdict: "UNKNOWN", requirements: [], checks: [] };
80187
- }
80188
- try {
80189
- const parsed = JSON.parse(raw);
80190
- if (parsed !== null && typeof parsed === "object") {
80191
- const obj = parsed;
80192
- const wbs = typeof obj.wbs === "string" ? obj.wbs : fallbackWbs ?? "";
80193
- const verdict = normalizeVerdict(obj.verdict);
80194
- const requirements = normalizeRequirements(obj.requirements);
80195
- const acceptanceCriteria = normalizeAcceptanceCriteria(obj.acceptanceCriteria);
80196
- const checks4 = normalizeChecks(obj.checks);
80197
- return { wbs, verdict, requirements, acceptanceCriteria, checks: checks4 };
80198
- }
80199
- } catch {}
80200
- return { wbs: fallbackWbs ?? "", verdict: "UNKNOWN", requirements: [], checks: [] };
80201
- }
80202
- async function readVerdict(fs3, path9, fallbackWbs) {
80203
- let raw;
80204
- try {
80205
- raw = await fs3.readFile(path9);
80206
- } catch {
80207
- return { wbs: fallbackWbs ?? "", verdict: "UNKNOWN", requirements: [], checks: [] };
80208
- }
80209
- return parseVerdict(raw, fallbackWbs);
80210
- }
80211
- function normalizeVerdict(raw) {
80212
- if (typeof raw !== "string")
80213
- return "UNKNOWN";
80214
- const upper = raw.toUpperCase();
80215
- if (upper === "PASS")
80216
- return "PASS";
80217
- if (upper === "PARTIAL")
80218
- return "PARTIAL";
80219
- if (upper === "FAIL")
80220
- return "FAIL";
80221
- return "UNKNOWN";
80222
- }
80223
- function normalizeRequirements(raw) {
80224
- if (!Array.isArray(raw))
80225
- return [];
80226
- return raw.filter((r) => r !== null && typeof r === "object").map((r) => ({
80227
- id: typeof r.id === "string" ? r.id : "",
80228
- status: typeof r.status === "string" ? r.status : "",
80229
- evidence: typeof r.evidence === "string" ? r.evidence : ""
80230
- }));
80231
- }
80232
- function normalizeAcceptanceCriteria(raw) {
80233
- if (!Array.isArray(raw))
80234
- return [];
80235
- return raw.filter((ac) => ac !== null && typeof ac === "object").map((ac) => ({
80236
- id: typeof ac.id === "string" ? ac.id : "",
80237
- status: typeof ac.status === "string" ? ac.status : "",
80238
- evidenceType: typeof ac.evidenceType === "string" ? ac.evidenceType : "",
80239
- evidence: typeof ac.evidence === "string" ? ac.evidence : ""
80240
- }));
80241
- }
80242
- function normalizeChecks(raw) {
80243
- if (!Array.isArray(raw))
80244
- return [];
80245
- return raw.filter((c3) => c3 !== null && typeof c3 === "object").map((c3) => ({
80246
- name: typeof c3.name === "string" ? c3.name : "",
80247
- status: typeof c3.status === "string" ? c3.status : "",
80248
- evidence: typeof c3.evidence === "string" ? c3.evidence : ""
80249
- }));
80250
- }
80251
- function renderTesting(v) {
80252
- const lines = [];
80253
- lines.push("**Pipeline verify results**");
80254
- lines.push("");
80255
- lines.push(`- Verdict: ${v.verdict} (from verdict artifact)`);
80256
- lines.push("");
80257
- if (v.requirements.length === 0) {
80258
- lines.push("| Requirement | Status | Evidence |");
80259
- lines.push("|-------------|--------|----------|");
80260
- lines.push(`| \u2014 | \u2014 | No requirements recorded; verify verdict ${v.verdict} |`);
80261
- } else {
80262
- lines.push("| Requirement | Status | Evidence |");
80263
- lines.push("|-------------|--------|----------|");
80264
- for (const req of v.requirements) {
80265
- const evidence = escapeTablePipe(req.evidence.replace(/\n/g, " "));
80266
- lines.push(`| ${req.id} | ${req.status} | ${evidence} |`);
80267
- }
80268
- }
80269
- if ((v.acceptanceCriteria ?? []).length > 0) {
80270
- lines.push("");
80271
- lines.push("| Acceptance Criteria | Status | Evidence Type | Evidence |");
80272
- lines.push("|---------------------|--------|---------------|----------|");
80273
- for (const ac of v.acceptanceCriteria ?? []) {
80274
- const evidence = escapeTablePipe(ac.evidence.replace(/\n/g, " "));
80275
- lines.push(`| ${ac.id} | ${ac.status} | ${ac.evidenceType} | ${evidence} |`);
80276
- }
80277
- }
80278
- lines.push("- Coverage: N/A (verdict-based; verify pipeline does not measure code coverage)");
80279
- return lines.join(`
80280
- `);
80281
- }
80282
- function renderReview(v) {
80283
- const lines = [];
80284
- lines.push(`**SECU findings** (pipeline verify step \u2014 verdict: ${v.verdict})`);
80285
- lines.push("");
80286
- lines.push("| Priority | Dimension | Location | Finding |");
80287
- lines.push("|----------|-----------|----------|----------|");
80288
- if (v.checks.length === 0) {
80289
- lines.push(`| P4 | \u2014 | \u2014 | No P1\u2013P3 findings; verify verdict ${v.verdict} |`);
80290
- } else {
80291
- for (const check2 of v.checks) {
80292
- const finding = escapeTablePipe(check2.evidence.replace(/\n/g, " "));
80293
- const priority = /^P[1-4]$/.test(check2.status) ? check2.status : check2.status === "fail" ? "P1" : "P4";
80294
- lines.push(`| ${priority} | ${check2.name} | \u2014 | ${finding} |`);
80295
- }
80296
- }
80297
- lines.push("");
80298
- return lines.join(`
80299
- `);
80300
- }
80301
- function renderSolutionFromDiff(diffText) {
80302
- const lines = [];
80303
- lines.push("Change-map (auto-generated \u2014 implement step did not record a Solution).");
80304
- lines.push("Each entry cites the first changed line per file (`file:line`).");
80305
- lines.push("");
80306
- lines.push("| Change (`file:line`) |");
80307
- lines.push("|----------------------|");
80308
- const citations = extractFileLineCitations(diffText);
80309
- if (citations.length === 0) {
80310
- const files = extractChangedFiles(diffText);
80311
- if (files.length === 0) {
80312
- lines.push("| `(no changes detected)` |");
80313
- } else {
80314
- for (const f of files) {
80315
- lines.push(`| \`${f}:1\` |`);
80316
- }
80317
- }
80318
- } else {
80319
- for (const citation of citations) {
80320
- lines.push(`| \`${citation}\` |`);
80321
- }
80322
- }
80323
- lines.push("");
80324
- return lines.join(`
80325
- `);
80326
- }
80327
- function extractFileLineCitations(diffText) {
80328
- const citations = new Set;
80329
- let currentFile = null;
80330
- for (const line of diffText.split(`
80331
- `)) {
80332
- const fileMatch = /^\+\+\+ b\/(.+)$/.exec(line);
80333
- if (fileMatch) {
80334
- currentFile = fileMatch[1] ?? null;
80335
- continue;
80336
- }
80337
- if (currentFile === null)
80338
- continue;
80339
- const hunkMatch = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(line);
80340
- if (hunkMatch) {
80341
- const newLine = hunkMatch[1];
80342
- if (newLine !== undefined) {
80343
- citations.add(`${currentFile}:${newLine}`);
80344
- }
80345
- }
80346
- }
80347
- return [...citations].sort();
80348
- }
80349
- function extractChangedFiles(diffText) {
80350
- const files = new Set;
80351
- for (const line of diffText.split(`
80352
- `)) {
80353
- const match = /^\+\+\+ b\/(.+)$/.exec(line);
80354
- if (match) {
80355
- const filePath = match[1];
80356
- if (filePath !== undefined)
80357
- files.add(filePath);
80358
- }
80359
- }
80360
- return [...files].sort();
80361
- }
80362
- function gitDiffU0(cwd) {
80363
- try {
80364
- const result = new BunSyncProcessExecutor2().runSync({
80365
- command: "git",
80366
- args: ["diff", "-U0", "HEAD", "--", "*.ts", "*.tsx", "*.js"],
80367
- ...cwd !== undefined ? { cwd } : {},
80368
- rejectOnError: false
80369
- });
80370
- return result.exitCode === 0 ? result.stdout : "";
80371
- } catch {
80372
- return "";
80373
- }
80374
- }
80375
- var init_task_record = __esm(() => {
80376
- init_dist5();
80377
- });
80378
-
80379
80636
  // ../../packages/app/src/services/task-scaffold.ts
80380
80637
  import { dirname as dirname13 } from "path";
80381
80638
 
@@ -81885,7 +82142,7 @@ class TeamService {
81885
82142
  }
81886
82143
  async listTeams() {
81887
82144
  const specs = await loadAgentSpecs(this.configDir);
81888
- const config4 = await this.loadTeamConfig();
82145
+ const config4 = this.ctx.spurConfig ?? null;
81889
82146
  const teams = new Map;
81890
82147
  const configTeamIds = new Set;
81891
82148
  if (config4?.agent?.team) {
@@ -81944,7 +82201,7 @@ class TeamService {
81944
82201
  return Array.from(teams.values());
81945
82202
  }
81946
82203
  async materializeTeam(teamId, opts) {
81947
- const config4 = await this.loadTeamConfig();
82204
+ const config4 = this.ctx.spurConfig ?? null;
81948
82205
  const teamConfig = config4?.agent?.team?.[teamId];
81949
82206
  if (!teamConfig) {
81950
82207
  throw new Error(`Team "${teamId}" not found in agent.team config`);
@@ -82069,13 +82326,6 @@ class TeamService {
82069
82326
  const workDir = this.resolveWorkspaceDir(first);
82070
82327
  return { workDir, isCurrentProject: this.isCurrentProject(workDir) };
82071
82328
  }
82072
- async loadTeamConfig() {
82073
- try {
82074
- return await loadSpurConfig(this.ctx.cwd);
82075
- } catch {
82076
- return null;
82077
- }
82078
- }
82079
82329
  async inboxDao() {
82080
82330
  const db2 = await this.ctx.getDb();
82081
82331
  return new InboxMessageDao(db2);
@@ -82181,7 +82431,6 @@ function resolveAutostartSet(config4, envAutostart) {
82181
82431
  }
82182
82432
  var init_team_service = __esm(() => {
82183
82433
  init_src();
82184
- init_loader();
82185
82434
  init_src2();
82186
82435
  init_dist9();
82187
82436
  init_planning_folders();
@@ -83307,21 +83556,26 @@ var init_agent_run = __esm(() => {
83307
83556
  init_steering();
83308
83557
  });
83309
83558
 
83310
- // ../../packages/app/src/workflow/actions/command-gate.ts
83311
- import { normalize, resolve as resolve9 } from "path";
83312
- function splitExecutable(executable) {
83313
- if (SHELL_METACHARACTERS.test(executable)) {
83314
- return {
83315
- error: `command.gate "executable" must not contain shell metacharacters (got ${executable})`
83316
- };
83559
+ // ../../packages/app/src/workflow/split-launch-command.ts
83560
+ function splitLaunchCommand(value, label) {
83561
+ if (SHELL_METACHARACTERS.test(value)) {
83562
+ return { error: `${label} must not contain shell metacharacters (got ${value})` };
83317
83563
  }
83318
- const tokens = executable.trim().split(/\s+/).filter((t) => t.length > 0);
83564
+ const tokens = value.trim().split(/\s+/).filter((t) => t.length > 0);
83319
83565
  const command = tokens[0];
83320
83566
  if (command === undefined) {
83321
- return { error: 'Action option "executable" must be a non-empty string' };
83567
+ const key2 = label.slice(label.indexOf('"'));
83568
+ return { error: `Action option ${key2} must be a non-empty string` };
83322
83569
  }
83323
83570
  return { command, leadingArgs: tokens.slice(1) };
83324
83571
  }
83572
+ var SHELL_METACHARACTERS;
83573
+ var init_split_launch_command = __esm(() => {
83574
+ SHELL_METACHARACTERS = /[;&|<>$`(){}[\]!*?~#\n\r"']/;
83575
+ });
83576
+
83577
+ // ../../packages/app/src/workflow/actions/command-gate.ts
83578
+ import { normalize, resolve as resolve9 } from "path";
83325
83579
  function sleep3(ms) {
83326
83580
  return new Promise((resolve10) => setTimeout(resolve10, ms));
83327
83581
  }
@@ -83382,7 +83636,7 @@ class CommandGateActionRunner {
83382
83636
  error: `resultFile must resolve beneath .spur/run/ (got ${resultFileRaw})`
83383
83637
  };
83384
83638
  }
83385
- const split = splitExecutable(executable);
83639
+ const split = splitLaunchCommand(executable, 'command.gate "executable"');
83386
83640
  if ("error" in split) {
83387
83641
  return { ok: false, error: split.error };
83388
83642
  }
@@ -83462,25 +83716,14 @@ ${lastStderr}`;
83462
83716
  };
83463
83717
  }
83464
83718
  }
83465
- var KIND2 = "command.gate", SHELL_METACHARACTERS;
83719
+ var KIND2 = "command.gate";
83466
83720
  var init_command_gate = __esm(() => {
83467
83721
  init_dist5();
83468
- SHELL_METACHARACTERS = /[;&|<>$`(){}[\]!*?~#\n\r"']/;
83722
+ init_split_launch_command();
83469
83723
  });
83470
83724
 
83471
83725
  // ../../packages/app/src/workflow/actions/doctor-probe.ts
83472
83726
  import { normalize as normalize2, resolve as resolve10, sep as sep2 } from "path";
83473
- function splitSpurBin(spurBin) {
83474
- if (SHELL_METACHARACTERS2.test(spurBin)) {
83475
- return { error: `doctor.probe "spurBin" must not contain shell metacharacters (got ${spurBin})` };
83476
- }
83477
- const tokens = spurBin.trim().split(/\s+/).filter((t) => t.length > 0);
83478
- const command = tokens[0];
83479
- if (command === undefined) {
83480
- return { error: 'Action option "spurBin" must be a non-empty string' };
83481
- }
83482
- return { command, leadingArgs: tokens.slice(1) };
83483
- }
83484
83727
  function stringOption2(options, key2, fallback) {
83485
83728
  const value = options[key2];
83486
83729
  if (typeof value === "string")
@@ -83540,7 +83783,7 @@ class DoctorProbeActionRunner {
83540
83783
  error: `resultFile must resolve beneath .spur/run/ (got ${resultFileRaw})`
83541
83784
  };
83542
83785
  }
83543
- const split = splitSpurBin(spurBin);
83786
+ const split = splitLaunchCommand(spurBin, 'doctor.probe "spurBin"');
83544
83787
  if ("error" in split) {
83545
83788
  return { ok: false, error: split.error };
83546
83789
  }
@@ -83616,11 +83859,11 @@ ${res.stderr}`.trim();
83616
83859
  return { ok: true, data: { status, resultFile: normalized, output: lines } };
83617
83860
  }
83618
83861
  }
83619
- var KIND3 = "doctor.probe", SHELL_METACHARACTERS2, RELAY_FAMILY, ENV_MISS_PATTERN, AUTH_FAIL_PATTERN;
83862
+ var KIND3 = "doctor.probe", RELAY_FAMILY, ENV_MISS_PATTERN, AUTH_FAIL_PATTERN;
83620
83863
  var init_doctor_probe = __esm(() => {
83621
83864
  init_dist5();
83622
83865
  init_observability();
83623
- SHELL_METACHARACTERS2 = /[;&|<>$`(){}[\]!*?~#\n\r"']/;
83866
+ init_split_launch_command();
83624
83867
  RELAY_FAMILY = /^(omp|pi)(-.*)?$/;
83625
83868
  ENV_MISS_PATTERN = /API key not found for provider|no probe registered/i;
83626
83869
  AUTH_FAIL_PATTERN = /invalid (API )?key|unauthori[sz]ed|forbidden|authentication failed|credential(s)? rejected/i;
@@ -84907,10 +85150,19 @@ class WorkflowAppService {
84907
85150
  };
84908
85151
  }
84909
85152
  async validate(file2, opts = {}) {
84910
- const absolute = resolve13(this.ctx.cwd, file2);
84911
- if (!await fileExists(absolute)) {
84912
- return { ok: false, valid: false, file: file2, errors: [`File not found: ${absolute}`] };
85153
+ const resolved = resolveWorkflowFile(this.ctx.cwd, file2);
85154
+ if (resolved.path === null) {
85155
+ const [probedProject, probedBundled] = resolved.probed;
85156
+ return {
85157
+ ok: false,
85158
+ valid: false,
85159
+ file: file2,
85160
+ errors: [
85161
+ `File not found: ${probedProject}${probedBundled !== null ? ` (bundled: ${probedBundled})` : ""}`
85162
+ ]
85163
+ };
84913
85164
  }
85165
+ const absolute = resolved.path;
84914
85166
  try {
84915
85167
  const embedded = this.embeddedSchemaOptions();
84916
85168
  const workflow = await loadWorkflowDef(absolute, {
@@ -84959,7 +85211,12 @@ class WorkflowAppService {
84959
85211
  }
84960
85212
  async run(file2, opts = {}) {
84961
85213
  const eventsBus = this.ctx.events?.();
84962
- const absolute = resolve13(this.ctx.cwd, file2);
85214
+ const resolved = resolveWorkflowFile(this.ctx.cwd, file2);
85215
+ if (resolved.path === null) {
85216
+ const [probedProject, probedBundled] = resolved.probed;
85217
+ throw new Error(`Workflow not found: ${probedProject}${probedBundled !== null ? ` (bundled: ${probedBundled})` : ""}`);
85218
+ }
85219
+ const absolute = resolved.path;
84963
85220
  const embedded = this.embeddedSchemaOptions();
84964
85221
  const workflow = await loadWorkflowDef(absolute, {
84965
85222
  validateSchema: true,
@@ -84975,7 +85232,7 @@ class WorkflowAppService {
84975
85232
  const isDry = opts.dryRun === true;
84976
85233
  const warnings = [];
84977
85234
  const runVars = {
84978
- ...await resolveDefaultAgentVar(this.ctx.cwd, opts.vars, (m) => warnings.push(m)),
85235
+ ...resolveDefaultAgentVar(this.ctx.spurConfig ?? null, opts.vars, (m) => warnings.push(m)),
84979
85236
  ...opts.vars ?? {},
84980
85237
  __runId: runId
84981
85238
  };
@@ -85019,7 +85276,10 @@ class WorkflowAppService {
85019
85276
  return;
85020
85277
  let workflowName;
85021
85278
  try {
85022
- const def = await loadWorkflowDef(resolve13(this.ctx.cwd, file2), { validateSchema: false });
85279
+ const resolved = resolveWorkflowFile(this.ctx.cwd, file2);
85280
+ if (resolved.path === null)
85281
+ return;
85282
+ const def = await loadWorkflowDef(resolved.path, { validateSchema: false });
85023
85283
  workflowName = def.name;
85024
85284
  } catch {
85025
85285
  return;
@@ -85295,14 +85555,12 @@ class WorkflowAppService {
85295
85555
  let agentSlice = {
85296
85556
  ...this.ctx.secretValues !== undefined ? { secretValues: this.ctx.secretValues } : {}
85297
85557
  };
85298
- try {
85299
- const agent = (await loadSpurConfig(this.ctx.cwd)).agent;
85300
- agentSlice = {
85301
- ...agentSlice,
85302
- ...agent?.default !== undefined ? { default: agent.default } : {},
85303
- ...agent?.sessionAffinity !== undefined ? { sessionAffinity: agent.sessionAffinity } : {}
85304
- };
85305
- } catch {}
85558
+ const agent = this.ctx.spurConfig?.agent;
85559
+ agentSlice = {
85560
+ ...agentSlice,
85561
+ ...agent?.default !== undefined ? { default: agent.default } : {},
85562
+ ...agent?.sessionAffinity !== undefined ? { sessionAffinity: agent.sessionAffinity } : {}
85563
+ };
85306
85564
  try {
85307
85565
  const fs3 = createNodeFileSystem3(this.ctx.cwd);
85308
85566
  const { foldersConfig, featuresDir } = await resolvePlanningFolders(fs3);
@@ -85532,22 +85790,31 @@ async function fileExists(path9) {
85532
85790
  const fs3 = createNodeFileSystem3();
85533
85791
  return await fs3.exists(path9);
85534
85792
  }
85535
- async function resolveDefaultAgentVar(cwd, callerVars, warn) {
85793
+ function resolveWorkflowFile(cwd, file2) {
85794
+ const projectPath = resolve13(cwd, file2);
85795
+ if (existsSync10(projectPath)) {
85796
+ return { path: projectPath, source: "project" };
85797
+ }
85798
+ const bundledRoot = bundledConfigRoot();
85799
+ if (bundledRoot !== null) {
85800
+ const bundledPath = join22(bundledRoot, "workflows", basename7(file2));
85801
+ if (existsSync10(bundledPath)) {
85802
+ return { path: bundledPath, source: "bundled" };
85803
+ }
85804
+ return { path: null, probed: [projectPath, bundledPath] };
85805
+ }
85806
+ return { path: null, probed: [projectPath, null] };
85807
+ }
85808
+ function resolveDefaultAgentVar(config4, callerVars, warn) {
85536
85809
  const result = {};
85537
85810
  if (callerVars?.implementAgent === undefined && callerVars?.agent !== undefined) {
85538
85811
  result.implementAgent = callerVars.agent;
85539
85812
  }
85540
- let config4;
85541
- try {
85542
- config4 = await loadSpurConfig(cwd);
85543
- } catch {
85544
- return result;
85545
- }
85546
- const configured = config4.agent?.default;
85813
+ const configured = config4?.agent?.default;
85547
85814
  if (typeof configured !== "string" || configured.length === 0) {
85548
85815
  return result;
85549
85816
  }
85550
- const valid = AGENT_ROLE_NAMES.includes(configured) || config4.agent?.executors?.some((e) => e.name === configured) === true || resolveAgentName(configured) !== undefined;
85817
+ const valid = AGENT_ROLE_NAMES.includes(configured) || config4?.agent?.executors?.some((e) => e.name === configured) === true || resolveAgentName(configured) !== undefined;
85551
85818
  if (!valid) {
85552
85819
  warn(`agent.default "${configured}" does not name a Layer-1 role, a configured executor, or an agent binary; leaving the pipeline's literal agent in force`);
85553
85820
  return result;
@@ -85559,25 +85826,17 @@ async function resolveDefaultAgentVar(cwd, callerVars, warn) {
85559
85826
  }
85560
85827
  return result;
85561
85828
  }
85562
- async function resolveWorkflowLogRetentionDays(cwd) {
85563
- try {
85564
- return (await loadSpurConfig(cwd)).workflow?.logRetentionDays ?? 30;
85565
- } catch {
85566
- return 30;
85567
- }
85829
+ function resolveWorkflowLogRetentionDays(config4) {
85830
+ return config4?.workflow?.logRetentionDays ?? 30;
85568
85831
  }
85569
- async function resolveOutputLogConfig(cwd) {
85570
- try {
85571
- const output2 = (await loadSpurConfig(cwd)).agent?.output;
85572
- if (output2 === undefined)
85573
- return {};
85574
- return {
85575
- ...output2["max-bytes"] !== undefined ? { maxBytes: output2["max-bytes"] } : {},
85576
- ...output2["max-lines"] !== undefined ? { maxLines: output2["max-lines"] } : {}
85577
- };
85578
- } catch {
85832
+ function resolveOutputLogConfig(config4) {
85833
+ const output2 = config4?.agent?.output;
85834
+ if (output2 === undefined)
85579
85835
  return {};
85580
- }
85836
+ return {
85837
+ ...output2["max-bytes"] !== undefined ? { maxBytes: output2["max-bytes"] } : {},
85838
+ ...output2["max-lines"] !== undefined ? { maxLines: output2["max-lines"] } : {}
85839
+ };
85581
85840
  }
85582
85841
  async function outputArtifactForRun(cwd, runId) {
85583
85842
  const relative5 = join22(".spur", "run", `${runId}.log`);
@@ -85793,6 +86052,9 @@ var init_workflow_service = __esm(() => {
85793
86052
 
85794
86053
  // ../../packages/app/src/workflow/idea-handoff.ts
85795
86054
  import { resolve as resolve14 } from "path";
86055
+ function processEvidence(r) {
86056
+ return `exit=${r.exitCode ?? "null"}${r.signal ? ` signal=${r.signal}` : ""}: ${r.stderr.trim() || "no stderr"}`;
86057
+ }
85796
86058
  async function finalizeIdeaHandoff(options) {
85797
86059
  const root = options.projectRoot ?? process.cwd();
85798
86060
  const fs3 = options.fileSystem ?? createNodeFileSystem3();
@@ -85804,6 +86066,18 @@ async function finalizeIdeaHandoff(options) {
85804
86066
  const resultPath = resolve14(runDir, `${runId}-idea-batch-create-result.json`);
85805
86067
  const orderPath = resolve14(runDir, `${runId}-idea-task-order.json`);
85806
86068
  const reportPath = resolve14(runDir, `${runId}-idea-handoff.md`);
86069
+ const spurSplit = splitLaunchCommand(spurBin, 'idea-handoff "spurBin"');
86070
+ if ("error" in spurSplit) {
86071
+ return {
86072
+ ok: false,
86073
+ wbsList: [],
86074
+ nextCommand: "",
86075
+ reportPath,
86076
+ error: spurSplit.error
86077
+ };
86078
+ }
86079
+ const spurCommand = spurSplit.command;
86080
+ const spurArgsPrefix = spurSplit.leadingArgs;
85807
86081
  if (!await fs3.exists(batchPath) || !await fs3.exists(resultPath) || !await fs3.exists(orderPath)) {
85808
86082
  return {
85809
86083
  ok: false,
@@ -85873,8 +86147,8 @@ async function finalizeIdeaHandoff(options) {
85873
86147
  }
85874
86148
  if (deps.length > 0) {
85875
86149
  const depRes = await executor.run({
85876
- command: spurBin,
85877
- args: ["task", "deps", ownWbs, "set", ...deps, "--json"],
86150
+ command: spurCommand,
86151
+ args: [...spurArgsPrefix, "task", "deps", ownWbs, "set", ...deps, "--json"],
85878
86152
  cwd: root,
85879
86153
  forceBuffered: true,
85880
86154
  rejectOnError: false
@@ -85885,27 +86159,45 @@ async function finalizeIdeaHandoff(options) {
85885
86159
  wbsList: result.wbs,
85886
86160
  nextCommand: "",
85887
86161
  reportPath,
85888
- error: `Failed to set dependencies for task ${ownWbs}: ${depRes.stderr}`
86162
+ error: `Failed to set dependencies for task ${ownWbs}: ${processEvidence(depRes)}`
85889
86163
  };
85890
86164
  }
85891
86165
  }
85892
86166
  }
85893
- await executor.run({
85894
- command: spurBin,
85895
- args: ["feature", "refresh", "--feature", featureId2, "--json"],
86167
+ const refreshRes = await executor.run({
86168
+ command: spurCommand,
86169
+ args: [...spurArgsPrefix, "feature", "refresh", "--feature", featureId2, "--json"],
85896
86170
  cwd: root,
85897
86171
  forceBuffered: true,
85898
86172
  rejectOnError: false
85899
86173
  });
86174
+ if (refreshRes.exitCode !== 0) {
86175
+ return {
86176
+ ok: false,
86177
+ wbsList: result.wbs,
86178
+ nextCommand: "",
86179
+ reportPath,
86180
+ error: `Feature refresh for ${featureId2} failed: ${processEvidence(refreshRes)}`
86181
+ };
86182
+ }
85900
86183
  const checkResults = [];
85901
86184
  for (const wbs of result.wbs) {
85902
86185
  const checkRes = await executor.run({
85903
- command: spurBin,
85904
- args: ["task", "check", wbs, "--json"],
86186
+ command: spurCommand,
86187
+ args: [...spurArgsPrefix, "task", "check", wbs, "--json"],
85905
86188
  cwd: root,
85906
86189
  forceBuffered: true,
85907
86190
  rejectOnError: false
85908
86191
  });
86192
+ if (checkRes.exitCode === null) {
86193
+ return {
86194
+ ok: false,
86195
+ wbsList: result.wbs,
86196
+ nextCommand: "",
86197
+ reportPath,
86198
+ error: `Task check for ${wbs} could not be spawned (${spurCommand}): ${processEvidence(checkRes)}`
86199
+ };
86200
+ }
85909
86201
  checkResults.push({ wbs, pass: checkRes.exitCode === 0 });
85910
86202
  }
85911
86203
  const anyFailed = checkResults.some((c3) => !c3.pass);
@@ -85951,6 +86243,7 @@ async function finalizeIdeaHandoff(options) {
85951
86243
  }
85952
86244
  var init_idea_handoff = __esm(() => {
85953
86245
  init_dist5();
86246
+ init_split_launch_command();
85954
86247
  });
85955
86248
 
85956
86249
  // ../../packages/app/src/workflow/idea-handoff-cli.ts
@@ -86534,6 +86827,7 @@ __export(exports_src2, {
86534
86827
  runHistoryReport: () => runHistoryReport,
86535
86828
  runCorpusCheck: () => runCorpusCheck,
86536
86829
  resolveWorkflowLogRetentionDays: () => resolveWorkflowLogRetentionDays,
86830
+ resolveWorkflowFile: () => resolveWorkflowFile,
86537
86831
  resolveSpurServeCommand: () => resolveSpurServeCommand,
86538
86832
  resolveRetentionQuotas: () => resolveRetentionQuotas,
86539
86833
  resolvePlanningFolders: () => resolvePlanningFolders,
@@ -86859,7 +87153,6 @@ var {
86859
87153
  } = import__.default;
86860
87154
 
86861
87155
  // src/index.ts
86862
- init_src();
86863
87156
  init_loader();
86864
87157
 
86865
87158
  // ../../node_modules/.bun/@gobing-ai+ts-infra@0.4.42+4012563f10a1cd34/node_modules/@gobing-ai/ts-infra/dist/application-node.js
@@ -95200,8 +95493,7 @@ init_loader();
95200
95493
  init_src2();
95201
95494
  await init_src3();
95202
95495
  import { existsSync as existsSync12 } from "fs";
95203
- import { homedir as homedir9 } from "os";
95204
- import { join as join25, resolve as resolve16 } from "path";
95496
+ import { join as join25 } from "path";
95205
95497
 
95206
95498
  // src/workflow/resolve-spur-bin.ts
95207
95499
  import { basename as basename8 } from "path";
@@ -95219,11 +95511,6 @@ function resolveSpurBin() {
95219
95511
  }
95220
95512
 
95221
95513
  // src/workflow/make-lifecycle-adapter.ts
95222
- var GLOBAL_CONFIG_DIR = join25(".config", "spur");
95223
- function globalConfigRoot(context4) {
95224
- const override = context4.env.SPUR_GLOBAL_RULES_DIR;
95225
- return override !== undefined && override.length > 0 ? resolve16(context4.cwd, override) : join25(homedir9(), GLOBAL_CONFIG_DIR);
95226
- }
95227
95514
  function resolveWorkflowPath(context4, profile) {
95228
95515
  const bundledRoot = bundledConfigRoot();
95229
95516
  if (bundledRoot !== null) {
@@ -95234,9 +95521,6 @@ function resolveWorkflowPath(context4, profile) {
95234
95521
  const projectPath = join25(context4.cwd, ".spur", "workflows", `${profile.workflowName}.yaml`);
95235
95522
  if (existsSync12(projectPath))
95236
95523
  return projectPath;
95237
- const globalPath = join25(globalConfigRoot(context4), "workflows", `${profile.workflowName}.yaml`);
95238
- if (existsSync12(globalPath))
95239
- return globalPath;
95240
95524
  return null;
95241
95525
  }
95242
95526
  function makeLifecycleAdapter(context4, profile) {
@@ -95652,7 +95936,7 @@ import { createRequire } from "module";
95652
95936
  var CLI_CONFIG = {
95653
95937
  binaryName: "spur",
95654
95938
  binaryLabel: "spur",
95655
- binaryVersion: "0.3.60",
95939
+ binaryVersion: "0.3.62",
95656
95940
  configDir: ".spur",
95657
95941
  configFile: ".spur/config.yaml",
95658
95942
  databaseFile: ".spur/spur.db"
@@ -95926,24 +96210,17 @@ function formatDailyResult(r) {
95926
96210
  }
95927
96211
 
95928
96212
  // src/commands/init.ts
96213
+ init_src();
95929
96214
  init_loader();
95930
96215
  init_src2();
95931
96216
  init_dist10();
95932
- import { homedir as homedir10 } from "os";
95933
- import { join as join26, resolve as resolve17 } from "path";
96217
+ import { homedir as homedir9 } from "os";
96218
+ import { join as join26, resolve as resolve16 } from "path";
95934
96219
 
95935
96220
  // src/config/scaffold-manifest.ts
95936
96221
  var SCAFFOLD_MANIFEST = [
95937
96222
  { source: "rules/recommended-pre-check.yaml", target: "rules/recommended-pre-check.yaml" },
95938
96223
  { source: "rules/recommended-post-check.yaml", target: "rules/recommended-post-check.yaml" },
95939
- { source: "workflows/basic.yaml", target: "workflows/basic.yaml" },
95940
- { source: "workflows/task-lifecycle.yaml", target: "workflows/task-lifecycle.yaml" },
95941
- { source: "workflows/feature-lifecycle.yaml", target: "workflows/feature-lifecycle.yaml" },
95942
- { source: "workflows/feature-dev.yaml", target: "workflows/feature-dev.yaml" },
95943
- { source: "workflows/task-pipeline.yaml", target: "workflows/task-pipeline.yaml" },
95944
- { source: "workflows/idea-pipeline.yaml", target: "workflows/idea-pipeline.yaml" },
95945
- { source: "workflows/wrapup-pipeline.yaml", target: "workflows/wrapup-pipeline.yaml" },
95946
- { source: "workflows/pr-review.yaml", target: "workflows/pr-review.yaml" },
95947
96224
  { source: "tasks/section-matrix.yaml", target: "tasks/section-matrix.yaml" },
95948
96225
  { source: "templates/task/standard.md", target: "tasks/templates/standard.md" },
95949
96226
  { source: "templates/task/feature-impl.md", target: "tasks/templates/feature-impl.md" },
@@ -95951,9 +96228,6 @@ var SCAFFOLD_MANIFEST = [
95951
96228
  { source: "templates/task/review.md", target: "tasks/templates/review.md" },
95952
96229
  { source: "templates/task/brainstorm.md", target: "tasks/templates/brainstorm.md" },
95953
96230
  { source: "templates/task/meta.md", target: "tasks/templates/meta.md" },
95954
- { source: "templates/feature/default.md", target: "templates/feature/default.md" },
95955
- { source: "templates/bdd/gherkin.md", target: "templates/bdd/gherkin.md" },
95956
- { source: "templates/bdd/checklist.md", target: "templates/bdd/checklist.md" },
95957
96231
  {
95958
96232
  source: "templates/docs/99_PROJECT_CONSTITUTION.md",
95959
96233
  target: "docs/99_PROJECT_CONSTITUTION.md",
@@ -95966,19 +96240,12 @@ var SCAFFOLD_MANIFEST = [
95966
96240
  { source: "templates/docs/03_ARCHITECTURE.md", target: "docs/03_ARCHITECTURE.md", root: true, preserve: true },
95967
96241
  { source: "templates/docs/04_DESIGN.md", target: "docs/04_DESIGN.md", root: true, preserve: true },
95968
96242
  { source: "templates/docs/05_FEATURES.md", target: "docs/05_FEATURES.md", root: true, preserve: true },
95969
- { source: "templates/docs/99_PROJECT_CONSTITUTION.md", target: "templates/docs/99_PROJECT_CONSTITUTION.md" },
95970
- { source: "templates/docs/00_ADR.md", target: "templates/docs/00_ADR.md" },
95971
- { source: "templates/docs/01_PRD.md", target: "templates/docs/01_PRD.md" },
95972
- { source: "templates/docs/02_ROADMAP.md", target: "templates/docs/02_ROADMAP.md" },
95973
- { source: "templates/docs/03_ARCHITECTURE.md", target: "templates/docs/03_ARCHITECTURE.md" },
95974
- { source: "templates/docs/04_DESIGN.md", target: "templates/docs/04_DESIGN.md" },
95975
- { source: "templates/docs/05_FEATURES.md", target: "templates/docs/05_FEATURES.md" },
95976
96243
  { source: "templates/AGENTS.md", target: "AGENTS.md", root: true, preserve: true }
95977
96244
  ];
95978
96245
 
95979
96246
  // src/commands/init.ts
95980
- var GLOBAL_CONFIG_DIR2 = join26(".config", "spur");
95981
- var GLOBAL_RULES_DIR2 = join26(GLOBAL_CONFIG_DIR2, "rules");
96247
+ var GLOBAL_CONFIG_DIR = join26(".config", "spur");
96248
+ var GLOBAL_RULES_DIR2 = join26(GLOBAL_CONFIG_DIR, "rules");
95982
96249
  var GLOBAL_CONFIG_EXAMPLE = BUNDLED_GLOBAL_CONFIG;
95983
96250
  var GLOBAL_CONFIG_FILE2 = "config.yaml";
95984
96251
  var INDEXED_CONTEXT_MARKER = "## Indexed context";
@@ -96009,7 +96276,7 @@ If \`.spur/context/\` is absent, proceed normally. Never block work on its absen
96009
96276
  `;
96010
96277
  function globalRulesRoot(context4) {
96011
96278
  const override = context4.env.SPUR_GLOBAL_RULES_DIR;
96012
- return override !== undefined && override.length > 0 ? resolve17(context4.cwd, override) : join26(homedir10(), GLOBAL_RULES_DIR2);
96279
+ return override !== undefined && override.length > 0 ? resolve16(context4.cwd, override) : join26(homedir9(), GLOBAL_RULES_DIR2);
96013
96280
  }
96014
96281
  async function writeIfNew(context4, path9, content, force, result) {
96015
96282
  if (!force && await context4.fs.exists(path9)) {
@@ -96035,12 +96302,15 @@ async function seedGlobalRules(context4) {
96035
96302
  }
96036
96303
  return written;
96037
96304
  }
96305
+ function resolveGlobalConfigDir(context4) {
96306
+ const globalOverride = context4.env.SPUR_GLOBAL_RULES_DIR;
96307
+ return globalOverride !== undefined && globalOverride.length > 0 ? resolve16(context4.cwd, globalOverride) : join26(homedir9(), GLOBAL_CONFIG_DIR);
96308
+ }
96038
96309
  async function seedGlobalConfig(context4) {
96039
96310
  const source = bundledConfigRoot();
96040
96311
  if (source === null)
96041
96312
  return 0;
96042
- const globalOverride = context4.env.SPUR_GLOBAL_RULES_DIR;
96043
- const target = globalOverride !== undefined && globalOverride.length > 0 ? resolve17(context4.cwd, globalOverride) : join26(homedir10(), GLOBAL_CONFIG_DIR2);
96313
+ const target = resolveGlobalConfigDir(context4);
96044
96314
  let written = 0;
96045
96315
  for (const relPath of listBundledConfigFiles()) {
96046
96316
  if (relPath === GLOBAL_CONFIG_EXAMPLE)
@@ -96062,19 +96332,101 @@ async function seedGlobalConfig(context4) {
96062
96332
  return written;
96063
96333
  }
96064
96334
  function registerInitCommand(program2, context4, options = {}) {
96065
- program2.command("init", { hidden: options.hidden === true }).summary("scaffold a local Spur project").option(...SHARED_OPTIONS.nameProjectInit).option(...SHARED_OPTIONS.forceInitRecreate).option("--minimal", "Only write the minimal .spur scaffold").option(...SHARED_OPTIONS.json).action(async (options2) => {
96335
+ program2.command("init", { hidden: options.hidden === true }).summary("scaffold a local Spur project").option(...SHARED_OPTIONS.nameProjectInit).option(...SHARED_OPTIONS.forceInitRecreate).option("--minimal", "Only write the minimal .spur scaffold").option("--adopt-global-config", "Also rewrite ~/.config/spur/config.yaml from the shipped global default (backed up first)").option(...SHARED_OPTIONS.json).action(async (options2) => {
96066
96336
  const json3 = options2.json === true;
96067
96337
  const force = options2.force === true;
96068
96338
  const minimal = options2.minimal === true;
96339
+ const adoptGlobal = options2.adoptGlobalConfig === true;
96069
96340
  const projectName = options2.name ?? "default";
96070
96341
  const configPath = join26(context4.cwd, CLI_CONFIG.configFile);
96342
+ const result = { created: [], skipped: [] };
96343
+ const globalConfigDir = resolveGlobalConfigDir(context4);
96344
+ const globalConfigPath = join26(globalConfigDir, GLOBAL_CONFIG_FILE2);
96345
+ const globalConfigExists = await context4.fs.exists(globalConfigPath);
96346
+ let misplacedKeys = [];
96347
+ if (globalConfigExists) {
96348
+ try {
96349
+ const raw = await context4.fs.readFile(globalConfigPath);
96350
+ misplacedKeys = misplacedGlobalKeys(parseConfigYaml2(raw));
96351
+ } catch {}
96352
+ }
96071
96353
  if (!force && await context4.fs.exists(configPath)) {
96072
- const message = `Already initialized: ${CLI_CONFIG.configFile}. Use --force to overwrite.`;
96073
- context4.output.write(json3 ? toJson2({ ok: false, reason: "already-initialized", config: CLI_CONFIG.configFile }) : message);
96074
- context4.setExitCode(1);
96354
+ const configRoot = bundledConfigRoot();
96355
+ if (configRoot !== null && !minimal) {
96356
+ for (const relPath of listBundledProjectSeedFiles()) {
96357
+ const sourcePath = join26(configRoot, relPath);
96358
+ if (!await context4.fs.exists(sourcePath))
96359
+ continue;
96360
+ const targetPath = join26(context4.cwd, CLI_CONFIG.configDir, relPath);
96361
+ await context4.fs.ensureDir(join26(targetPath, ".."));
96362
+ await writeIfNew(context4, targetPath, await context4.fs.readFile(sourcePath), false, result);
96363
+ }
96364
+ for (const entry of SCAFFOLD_MANIFEST) {
96365
+ const sourcePath = join26(configRoot, entry.source);
96366
+ if (!await context4.fs.exists(sourcePath))
96367
+ continue;
96368
+ const baseDir = entry.root === true ? context4.cwd : join26(context4.cwd, CLI_CONFIG.configDir);
96369
+ const targetPath = join26(baseDir, entry.target);
96370
+ await context4.fs.ensureDir(join26(targetPath, ".."));
96371
+ const entryForce = false;
96372
+ let body = await context4.fs.readFile(sourcePath);
96373
+ if (entry.target === "AGENTS.md") {
96374
+ body = substituteAgentsMdTemplate(body, projectName);
96375
+ }
96376
+ if (entry.source.startsWith("templates/docs/")) {
96377
+ body = substituteDocTemplateTokens(body);
96378
+ }
96379
+ await writeIfNew(context4, targetPath, body, entryForce, result);
96380
+ }
96381
+ }
96382
+ let adoptedGlobal = false;
96383
+ if (adoptGlobal && globalConfigExists) {
96384
+ const backupPath = `${globalConfigPath}.bak-${new Date().toISOString().replace(/[:.]/g, "-")}`;
96385
+ await context4.fs.writeFile(backupPath, await context4.fs.readFile(globalConfigPath));
96386
+ const bundledRoot = bundledConfigRoot();
96387
+ const examplePath = bundledRoot !== null ? join26(bundledRoot, GLOBAL_CONFIG_EXAMPLE) : null;
96388
+ if (examplePath !== null && await context4.fs.exists(examplePath)) {
96389
+ await context4.fs.writeFile(globalConfigPath, await context4.fs.readFile(examplePath));
96390
+ adoptedGlobal = true;
96391
+ }
96392
+ result.created.push(backupPath);
96393
+ }
96394
+ const rulesSeeded2 = await seedGlobalRules(context4);
96395
+ const configSeeded2 = await seedGlobalConfig(context4);
96396
+ const payload = {
96397
+ ok: true,
96398
+ converged: true,
96399
+ config: CLI_CONFIG.configFile,
96400
+ ...result,
96401
+ globalRulesSeeded: rulesSeeded2,
96402
+ globalConfigSeeded: configSeeded2,
96403
+ ...adoptedGlobal ? { adoptGlobalConfig: adoptedGlobal } : {},
96404
+ ...misplacedKeys.length > 0 ? { misplacedGlobalKeys: misplacedKeys } : {}
96405
+ };
96406
+ if (json3) {
96407
+ context4.output.write(toJson2(payload));
96408
+ } else {
96409
+ context4.output.write("Already initialized \u2014 converged (no project files overwritten)");
96410
+ for (const path9 of result.created)
96411
+ context4.output.write(` \u2713 ${path9}`);
96412
+ for (const path9 of result.skipped)
96413
+ context4.output.write(` - ${path9} (exists)`);
96414
+ if (rulesSeeded2 > 0) {
96415
+ context4.output.write(` \u2713 seeded ${rulesSeeded2} rule file(s) to ~/${GLOBAL_RULES_DIR2}`);
96416
+ }
96417
+ if (configSeeded2 > 0) {
96418
+ context4.output.write(` \u2713 seeded ${configSeeded2} config file(s) to ~/${GLOBAL_CONFIG_DIR}`);
96419
+ }
96420
+ if (adoptedGlobal) {
96421
+ context4.output.write(" \u2713 adopted global config from bundled default (backup written)");
96422
+ }
96423
+ if (misplacedKeys.length > 0) {
96424
+ context4.output.write(` \u26A0 global config carries project-shaped key(s): ${misplacedKeys.join(", ")}`);
96425
+ }
96426
+ }
96427
+ context4.setExitCode(0);
96075
96428
  return;
96076
96429
  }
96077
- const result = { created: [], skipped: [] };
96078
96430
  const configYaml = `${[
96079
96431
  `# Spur project configuration for ${projectName}`,
96080
96432
  `# Generated by \`spur init\``,
@@ -96128,8 +96480,6 @@ ${contextEntry}
96128
96480
  const configRoot = bundledConfigRoot();
96129
96481
  if (configRoot !== null) {
96130
96482
  for (const relPath of listBundledProjectSeedFiles()) {
96131
- if (relPath.startsWith("templates/docs/"))
96132
- continue;
96133
96483
  const sourcePath = join26(configRoot, relPath);
96134
96484
  if (!await context4.fs.exists(sourcePath))
96135
96485
  continue;
@@ -96188,7 +96538,7 @@ ${contextEntry}
96188
96538
  context4.output.write(` \u2713 seeded ${rulesSeeded} rule file(s) to ~/${GLOBAL_RULES_DIR2}`);
96189
96539
  }
96190
96540
  if (configSeeded > 0) {
96191
- context4.output.write(` \u2713 seeded ${configSeeded} config file(s) to ~/${GLOBAL_CONFIG_DIR2}`);
96541
+ context4.output.write(` \u2713 seeded ${configSeeded} config file(s) to ~/${GLOBAL_CONFIG_DIR}`);
96192
96542
  }
96193
96543
  }
96194
96544
  });
@@ -96243,7 +96593,11 @@ function registerMessageCommand(program2, context4) {
96243
96593
  async function runMessageSend(svc, context4, body, options) {
96244
96594
  const trimmed = body.trim();
96245
96595
  if (trimmed === "") {
96246
- context4.output.error("message send requires a non-empty body");
96596
+ if (options.json) {
96597
+ context4.output.write(toJson2({ error: { code: "usage", message: "message send requires a non-empty body" } }));
96598
+ } else {
96599
+ context4.output.error("message send requires a non-empty body");
96600
+ }
96247
96601
  return 2;
96248
96602
  }
96249
96603
  const from = options.from || DEFAULT_FROM;
@@ -96401,15 +96755,15 @@ async function runMessageWatch(svc, output2, options, runtime = {}) {
96401
96755
  }
96402
96756
  }
96403
96757
  function defaultSleep(ms, signal) {
96404
- return new Promise((resolve18) => {
96758
+ return new Promise((resolve17) => {
96405
96759
  if (signal?.aborted) {
96406
- resolve18();
96760
+ resolve17();
96407
96761
  return;
96408
96762
  }
96409
- const timer = setTimeout(resolve18, ms);
96763
+ const timer = setTimeout(resolve17, ms);
96410
96764
  signal?.addEventListener("abort", () => {
96411
96765
  clearTimeout(timer);
96412
- resolve18();
96766
+ resolve17();
96413
96767
  }, { once: true });
96414
96768
  });
96415
96769
  }
@@ -96436,15 +96790,15 @@ function parseTimeout2(raw) {
96436
96790
  return n3;
96437
96791
  }
96438
96792
  function sendSleep(ms, signal) {
96439
- return new Promise((resolve18) => {
96793
+ return new Promise((resolve17) => {
96440
96794
  if (signal?.aborted) {
96441
- resolve18();
96795
+ resolve17();
96442
96796
  return;
96443
96797
  }
96444
- const timer = setTimeout(resolve18, ms);
96798
+ const timer = setTimeout(resolve17, ms);
96445
96799
  signal?.addEventListener("abort", () => {
96446
96800
  clearTimeout(timer);
96447
- resolve18();
96801
+ resolve17();
96448
96802
  }, { once: true });
96449
96803
  });
96450
96804
  }
@@ -96514,12 +96868,12 @@ function registerMigrateCommand(program2, context4, options = {}) {
96514
96868
  // src/commands/projects.ts
96515
96869
  init_dist5();
96516
96870
  await init_src3();
96517
- import { basename as basename9, resolve as resolve18 } from "path";
96871
+ import { basename as basename9, resolve as resolve17 } from "path";
96518
96872
  function registerProjectsCommand(program2, context4) {
96519
96873
  const projectsCmd = program2.command("projects").summary("manage the Spur multi-project registry");
96520
96874
  projectsCmd.command("add").argument("<path>", "Project root directory path").option(...SHARED_OPTIONS.nameProjectDisplay).option(...SHARED_OPTIONS.jsonProjectsResponse).action(async (pathArg, options) => {
96521
96875
  try {
96522
- const absolutePath = resolve18(context4.cwd, pathArg);
96876
+ const absolutePath = resolve17(context4.cwd, pathArg);
96523
96877
  if (!await context4.fs.exists(absolutePath)) {
96524
96878
  throw new Error(`Directory does not exist: ${absolutePath}`);
96525
96879
  }
@@ -97227,8 +97581,8 @@ class EventPublisher {
97227
97581
  if (bufferedEvents.length > 0) {
97228
97582
  return { done: false, value: bufferedEvents.shift() };
97229
97583
  }
97230
- return new Promise((resolve19, reject) => {
97231
- pullResolvers.push([resolve19, reject]);
97584
+ return new Promise((resolve18, reject) => {
97585
+ pullResolvers.push([resolve18, reject]);
97232
97586
  });
97233
97587
  }, async () => {
97234
97588
  unsubscribe();
@@ -106440,7 +106794,8 @@ function createServerContext(appRt, options) {
106440
106794
  fs: fs3,
106441
106795
  getDb: this.getDb.bind(this),
106442
106796
  eventBus: bridgeEventBus(eventsBus),
106443
- events: bridgeEventBus(eventsBus)
106797
+ events: bridgeEventBus(eventsBus),
106798
+ ...options.spurConfig !== undefined ? { spurConfig: options.spurConfig } : {}
106444
106799
  });
106445
106800
  }
106446
106801
  return teamSvc;
@@ -106507,6 +106862,7 @@ function createServerContext(appRt, options) {
106507
106862
  agentService: this.agentService.bind(this),
106508
106863
  ruleService: this.ruleService.bind(this),
106509
106864
  hitlResponder: this.hitlResponder.bind(this),
106865
+ ...options.spurConfig !== undefined ? { spurConfig: options.spurConfig } : {},
106510
106866
  observabilityBus: () => bridgeEventBus(eventsBus),
106511
106867
  events: () => bridgeEventBus(eventsBus)
106512
106868
  });
@@ -106780,6 +107136,7 @@ async function startServer(options, deps = defaultDeps) {
106780
107136
  if (!webDistPath) {
106781
107137
  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).");
106782
107138
  }
107139
+ const spurConfig = await loadSpurConfig(process.cwd()).catch(() => null);
106783
107140
  const ctx = deps.createServerContext(appRt, {
106784
107141
  cwd: process.cwd(),
106785
107142
  fs: fs3,
@@ -106790,10 +107147,10 @@ async function startServer(options, deps = defaultDeps) {
106790
107147
  jobQueueEnabled: bootConfig.jobqueue.enabled,
106791
107148
  scheduler: scheduler3,
106792
107149
  teamAutostart: bootConfig.teamAutostart,
106793
- bootConfig
107150
+ bootConfig,
107151
+ ...spurConfig !== undefined ? { spurConfig } : {}
106794
107152
  });
106795
107153
  let jobWorker;
106796
- const spurConfig = await loadSpurConfig(process.cwd()).catch(() => null);
106797
107154
  const autostartIds = resolveAutostartSet(spurConfig, env.SPUR_TEAM_AUTOSTART);
106798
107155
  if (autostartIds.length > 0) {
106799
107156
  try {
@@ -107164,7 +107521,7 @@ var spur_config_schema_default = {
107164
107521
  },
107165
107522
  version: {
107166
107523
  type: "string",
107167
- description: "Config schema version (string, not integer). Current recommended: '1.1' (ADR-033 executor tiers + planning blocks). '1' remains accepted for backward compatibility."
107524
+ description: "Config schema version (string, not integer). Current recommended: '1.2' (config-1.2 layered global/project merge, ADR-033 executor tiers + planning blocks). '1' remains accepted for backward compatibility."
107168
107525
  },
107169
107526
  name: {
107170
107527
  type: "string",
@@ -107884,17 +108241,11 @@ var EMBEDDED_SPUR_SCHEMAS = new Map([
107884
108241
  ]);
107885
108242
 
107886
108243
  // src/history-refresh.ts
107887
- init_loader();
107888
108244
  init_dist6();
107889
108245
  await init_src3();
107890
108246
  async function maybeTriggerHistoryRefresh(context4, trigger, triggerId) {
107891
108247
  try {
107892
- let config4 = null;
107893
- try {
107894
- config4 = await loadSpurConfig(context4.cwd, { embeddedSchemas: EMBEDDED_SPUR_SCHEMAS });
107895
- } catch {
107896
- config4 = null;
107897
- }
108248
+ const config4 = context4.spurConfig ?? null;
107898
108249
  const db2 = await context4.getDb();
107899
108250
  const result = await enqueueHistoryRefresh(db2, { config: config4, trigger, triggerId });
107900
108251
  if (result.status === "disabled")
@@ -109196,7 +109547,7 @@ init_dist6();
109196
109547
  init_dist5();
109197
109548
  await init_src3();
109198
109549
  import { closeSync as closeSync5, fstatSync as fstatSync3, openSync as openSync5, readSync as readSync3 } from "fs";
109199
- import { join as join34, resolve as resolve19 } from "path";
109550
+ import { join as join34 } from "path";
109200
109551
  import { createInterface } from "readline";
109201
109552
  import { setTimeout as sleep5 } from "timers/promises";
109202
109553
 
@@ -109349,13 +109700,20 @@ function submitSteeringLine(controller, output2, line) {
109349
109700
  if (ack === undefined)
109350
109701
  output2.error(`[steer] ignored command: ${line}`);
109351
109702
  }
109352
- async function resolveWorkflowPaths(cwd) {
109353
- try {
109354
- const config4 = await loadSpurConfig(cwd, { embeddedSchemas: EMBEDDED_SPUR_SCHEMAS });
109355
- return config4.workflows?.paths ?? [".spur/workflows/"];
109356
- } catch {
109357
- return [".spur/workflows/"];
109703
+ var BUNDLED_PATH_PREFIX = "bundled:";
109704
+ function resolveWorkflowPaths(config4) {
109705
+ const paths = config4?.workflows?.paths ?? [".spur/workflows/"];
109706
+ const bundledRoot = bundledConfigRoot();
109707
+ const expanded = [];
109708
+ for (const path9 of paths) {
109709
+ if (path9.startsWith(BUNDLED_PATH_PREFIX)) {
109710
+ if (bundledRoot !== null)
109711
+ expanded.push(join34(bundledRoot, path9.slice(BUNDLED_PATH_PREFIX.length)));
109712
+ } else {
109713
+ expanded.push(path9);
109714
+ }
109358
109715
  }
109716
+ return expanded;
109359
109717
  }
109360
109718
  function registerWorkflowCommand(program2, context4) {
109361
109719
  const makeSvc = (json3, bus) => new WorkflowAppService({
@@ -109506,8 +109864,11 @@ Monitor with: spur workflow trace ${runId2} --follow`);
109506
109864
  let planPreview;
109507
109865
  if (options.plan !== false) {
109508
109866
  try {
109509
- const def = await loadWorkflowDef(resolve19(context4.cwd, file2), { validateSchema: false });
109510
- planPreview = renderRunPlan(def);
109867
+ const resolved = resolveWorkflowFile(context4.cwd, file2);
109868
+ if (resolved.path !== null) {
109869
+ const def = await loadWorkflowDef(resolved.path, { validateSchema: false });
109870
+ planPreview = renderRunPlan(def);
109871
+ }
109511
109872
  } catch {}
109512
109873
  }
109513
109874
  const runLog = options.log === false ? undefined : new WorkflowRunLogSink({
@@ -109515,7 +109876,7 @@ Monitor with: spur workflow trace ${runId2} --follow`);
109515
109876
  dir: join34(context4.cwd, ".spur", "run"),
109516
109877
  runId,
109517
109878
  ...planPreview !== undefined ? { planPreview } : {},
109518
- ...await resolveOutputLogConfig(context4.cwd)
109879
+ ...resolveOutputLogConfig(context4.spurConfig ?? null)
109519
109880
  });
109520
109881
  if (humanProgress) {
109521
109882
  context4.output.write(`Run: ${runId}`);
@@ -109562,10 +109923,13 @@ Monitor with: spur workflow trace ${runId2} --follow`);
109562
109923
  const steeringController = options.steer === true ? await (async () => {
109563
109924
  let identity7;
109564
109925
  try {
109565
- const def = await loadWorkflowDef(resolve19(context4.cwd, file2), {
109566
- validateSchema: false
109567
- });
109568
- identity7 = createWorkflowEventIdentity(def);
109926
+ const resolved = resolveWorkflowFile(context4.cwd, file2);
109927
+ if (resolved.path !== null) {
109928
+ const def = await loadWorkflowDef(resolved.path, {
109929
+ validateSchema: false
109930
+ });
109931
+ identity7 = createWorkflowEventIdentity(def);
109932
+ }
109569
109933
  } catch {}
109570
109934
  return new WorkflowSteeringController((ack) => {
109571
109935
  bus.emit("workflow.steering", identity7 === undefined ? ack : decorateWorkflowEvent(identity7, "workflow.steering", ack));
@@ -109674,7 +110038,7 @@ Monitor with: spur workflow trace ${runId2} --follow`);
109674
110038
  }
109675
110039
  const svc = makeSvc(options.json);
109676
110040
  const result = logsOnly ? undefined : await svc.clean(minutes, dryRun);
109677
- const retentionDays = await resolveWorkflowLogRetentionDays(context4.cwd);
110041
+ const retentionDays = resolveWorkflowLogRetentionDays(context4.spurConfig ?? null);
109678
110042
  const logResult2 = await svc.cleanRunLogs(retentionDays, dryRun);
109679
110043
  if (options.json) {
109680
110044
  context4.output.write(toJson2(logsOnly ? logResult2 : { ...result, logs: logResult2 }));
@@ -109723,7 +110087,7 @@ Monitor with: spur workflow trace ${runId2} --follow`);
109723
110087
  }
109724
110088
  });
109725
110089
  workflow.command("list").description("List available workflow YAML files.").option(...SHARED_OPTIONS.jsonSupported).action(async (options) => {
109726
- const paths = await resolveWorkflowPaths(context4.cwd);
110090
+ const paths = resolveWorkflowPaths(context4.spurConfig ?? null);
109727
110091
  const result = await makeSvc().list(paths);
109728
110092
  if (options.json) {
109729
110093
  context4.output.write(toJson2(result));
@@ -109732,7 +110096,14 @@ Monitor with: spur workflow trace ${runId2} --follow`);
109732
110096
  }
109733
110097
  });
109734
110098
  workflow.command("show").description("Render a workflow definition as a mermaid FSM diagram.").argument("<file>", "Workflow YAML file").action(async (file2) => {
109735
- const filePath = resolve19(context4.cwd, file2);
110099
+ const resolved = resolveWorkflowFile(context4.cwd, file2);
110100
+ if (resolved.path === null) {
110101
+ const [probedProject, probedBundled] = resolved.probed;
110102
+ context4.output.error(`workflow show: file not found: ${probedProject}${probedBundled !== null ? ` (bundled: ${probedBundled})` : ""}`);
110103
+ context4.setExitCode(1);
110104
+ return;
110105
+ }
110106
+ const filePath = resolved.path;
109736
110107
  let def;
109737
110108
  try {
109738
110109
  def = await loadWorkflowDef(filePath, { validateSchema: true });
@@ -110046,7 +110417,7 @@ init_src();
110046
110417
  init_src2();
110047
110418
  init_dist5();
110048
110419
  await init_src3();
110049
- import { dirname as dirname22, join as join35, resolve as resolve20 } from "path";
110420
+ import { dirname as dirname22, join as join35, resolve as resolve18 } from "path";
110050
110421
  import { isatty as isatty4 } from "tty";
110051
110422
 
110052
110423
  // ../../node_modules/.bun/@clack+core@1.4.1/node_modules/@clack/core/dist/index.mjs
@@ -111461,10 +111832,12 @@ function resolveAgentRoles(agentConfig) {
111461
111832
  return resolved;
111462
111833
  }
111463
111834
  function createCliContext(options) {
111464
- const cwd = resolve20(options.cwd ?? process.cwd());
111835
+ const cwd = resolve18(options.cwd ?? process.cwd());
111465
111836
  const env = options.env ?? process.env;
111466
111837
  const fs3 = createNodeFileSystem3(cwd);
111467
- const agentRoles = options.agentRoles ?? resolveAgentRoles(options.agentConfig);
111838
+ const agentConfig = options.agentConfig ?? options.spurConfig?.agent;
111839
+ const agentRoles = options.agentRoles ?? resolveAgentRoles(agentConfig);
111840
+ const agentRolesSource = agentConfig?.roles === undefined ? "fallback" : "config";
111468
111841
  let dbPromise;
111469
111842
  if (options.db) {
111470
111843
  dbPromise = Promise.resolve(options.db);
@@ -111481,13 +111854,16 @@ function createCliContext(options) {
111481
111854
  output: options.output,
111482
111855
  getDb,
111483
111856
  ...options.agentConfig !== undefined ? { agentConfig: options.agentConfig } : {},
111857
+ ...options.spurConfig !== undefined ? { spurConfig: options.spurConfig } : {},
111484
111858
  agentRoles,
111859
+ agentRolesSource,
111485
111860
  agentService: (serviceOptions) => new AgentService({
111486
111861
  cwd,
111487
111862
  env,
111488
111863
  output: options.output,
111489
- agentConfig: options.agentConfig,
111864
+ agentConfig,
111490
111865
  roles: agentRoles,
111866
+ rolesSource: agentRolesSource,
111491
111867
  getDb,
111492
111868
  ...serviceOptions?.events !== undefined ? { events: serviceOptions.events } : {},
111493
111869
  ...serviceOptions?.processRegistry !== undefined ? { processRegistry: serviceOptions.processRegistry } : {}
@@ -111516,34 +111892,46 @@ async function createMigratedDbAdapter(cwd = process.cwd(), env = process.env, d
111516
111892
  async function main(argv = process.argv.slice(2), options = {}) {
111517
111893
  const output2 = options.output ?? consoleOutput;
111518
111894
  let exitCode = 0;
111519
- const configFile = resolveConfigFile(options.cwd);
111520
- const db2 = options.db ?? await createMigratedDbAdapter(options.cwd ?? process.cwd(), options.env ?? process.env, options.dbUrl);
111895
+ const cwd = options.cwd ?? process.cwd();
111896
+ const env = options.env ?? process.env;
111897
+ const configFile = resolveConfigFile(cwd);
111898
+ const db2 = options.db ?? await createMigratedDbAdapter(cwd, env, options.dbUrl);
111899
+ let spurConfig;
111900
+ try {
111901
+ spurConfig = await loadSpurConfig(cwd, { embeddedSchemas: EMBEDDED_SPUR_SCHEMAS });
111902
+ } catch (err) {
111903
+ const message = errorMessage4(err);
111904
+ if (argv.includes("--json")) {
111905
+ output2.write(toJson2({ error: { code: "config", message } }));
111906
+ } else {
111907
+ output2.error(message);
111908
+ }
111909
+ await db2.close();
111910
+ return 1;
111911
+ }
111521
111912
  try {
111522
111913
  if (configFile !== undefined) {
111523
- await loadSpurConfig(options.cwd ?? process.cwd(), { embeddedSchemas: EMBEDDED_SPUR_SCHEMAS });
111524
111914
  const app = await runNodeApplication({
111525
111915
  configLoader: {
111526
111916
  configFile,
111527
- bootstrapSection: "bootstrap",
111528
- appConfig: { safeParse: (raw2) => spurConfigSchema.safeParse(raw2) }
111917
+ bootstrapSection: "bootstrap"
111529
111918
  },
111530
111919
  config: { logging: { console: false } },
111531
111920
  services: { db: db2 },
111532
111921
  async start(appRt) {
111533
111922
  const context4 = createCliContext({
111534
- cwd: options.cwd,
111535
- env: options.env,
111923
+ cwd,
111924
+ env,
111536
111925
  output: output2,
111537
111926
  db: db2,
111538
- agentConfig: appRt.appConfig?.agent
111927
+ spurConfig
111539
111928
  });
111540
111929
  exitCode = await runCommandDispatch(argv, context4, output2);
111541
111930
  }
111542
111931
  });
111543
111932
  await app.stop("shutdown");
111544
111933
  } else {
111545
- const ctxOpts = { cwd: options.cwd, env: options.env, output: output2, db: db2 };
111546
- const context4 = createCliContext(ctxOpts);
111934
+ const context4 = createCliContext({ cwd, env, output: output2, db: db2, spurConfig });
111547
111935
  exitCode = await runCommandDispatch(argv, context4, output2);
111548
111936
  }
111549
111937
  } finally {