@gobing-ai/spur 0.3.61 → 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 (81) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/config.global.yaml +64 -57
  3. package/config/corpus-baseline.json +409 -49
  4. package/config/plugin-scripts.json +2 -2
  5. package/config/rules/boundary/config-loading-ownership.yaml +21 -0
  6. package/config/workflows/history-anatomy.yaml +366 -0
  7. package/package.json +1 -1
  8. package/plugins/sp/README.md +19 -6
  9. package/plugins/sp/commands/dev-find-issue.md +25 -46
  10. package/plugins/sp/commands/dev-run.md +22 -3
  11. package/plugins/sp/lib/artifact-digest.generated.d.mts +7 -0
  12. package/plugins/sp/lib/artifact-digest.generated.mjs +48 -0
  13. package/plugins/sp/plugin.json +1 -1
  14. package/plugins/sp/references/roles.md +5 -5
  15. package/plugins/sp/scripts/history-anatomy-cache.mjs +669 -0
  16. package/plugins/sp/scripts/history-anatomy-cache.ts +818 -0
  17. package/plugins/sp/skills/history-anatomy/SKILL.md +67 -0
  18. package/plugins/sp/skills/history-anatomy/references/modes.md +82 -0
  19. package/plugins/sp/skills/history-anatomy/references/operations.md +67 -0
  20. package/plugins/sp/skills/history-anatomy/references/report-contract.md +133 -0
  21. package/plugins/sp/skills/spur-dev/references/dev-operations.md +2 -2
  22. package/plugins/sp/skills/spur-dev/references/execution-batch.md +14 -1
  23. package/plugins/sp/skills/spur-dev/references/execution-workflow.md +7 -0
  24. package/plugins/sp/skills/spur-dev/references/flag-glossary.md +25 -13
  25. package/spur.js +1091 -823
  26. package/web/_astro/BoardApp.Z9jMwI9G.js +1 -0
  27. package/web/_astro/BoardApp.rkWGVwwK.js +178 -0
  28. package/web/_astro/{TaskDetail.D2N60cfE.js → TaskDetail.B7ODt_bA.js} +1 -1
  29. package/web/_astro/{arc.D-EfJJwf.js → arc.C5QRz6AQ.js} +1 -1
  30. package/web/_astro/{architectureDiagram-3BPJPVTR.CgvTTzqp.js → architectureDiagram-3BPJPVTR.CHptn8gZ.js} +1 -1
  31. package/web/_astro/{blockDiagram-GPEHLZMM.CnOohvvn.js → blockDiagram-GPEHLZMM.b1nEDkDw.js} +1 -1
  32. package/web/_astro/{c4Diagram-AAUBKEIU.BFcBGUy_.js → c4Diagram-AAUBKEIU.BWj8S_Qq.js} +1 -1
  33. package/web/_astro/channel.BJtn6CGV.js +1 -0
  34. package/web/_astro/{chunk-2J33WTMH.C-6MR-XY.js → chunk-2J33WTMH.q0BBuE1n.js} +1 -1
  35. package/web/_astro/{chunk-4BX2VUAB.BN4LP5AR.js → chunk-4BX2VUAB.DP2KDkAU.js} +1 -1
  36. package/web/_astro/{chunk-55IACEB6.tUJ_CTtZ.js → chunk-55IACEB6.CkCId72p.js} +1 -1
  37. package/web/_astro/{chunk-727SXJPM.BNCk-sxv.js → chunk-727SXJPM.oHWzxMot.js} +1 -1
  38. package/web/_astro/{chunk-AQP2D5EJ.BkpFIbNa.js → chunk-AQP2D5EJ.DkkgxfMP.js} +1 -1
  39. package/web/_astro/{chunk-FMBD7UC4.DcSZ87PN.js → chunk-FMBD7UC4.lmFJDvRK.js} +1 -1
  40. package/web/_astro/{chunk-ND2GUHAM.BkLvHFFf.js → chunk-ND2GUHAM.52jt0eHK.js} +1 -1
  41. package/web/_astro/{chunk-QZHKN3VN.HOdgszax.js → chunk-QZHKN3VN.Cg8KEFoD.js} +1 -1
  42. package/web/_astro/{classDiagram-4FO5ZUOK.CWfRiY6b.js → classDiagram-4FO5ZUOK.BuOUhxcD.js} +1 -1
  43. package/web/_astro/{classDiagram-v2-Q7XG4LA2.CWfRiY6b.js → classDiagram-v2-Q7XG4LA2.BuOUhxcD.js} +1 -1
  44. package/web/_astro/{cose-bilkent-S5V4N54A.C6j4PxoN.js → cose-bilkent-S5V4N54A.BRUU8E_0.js} +1 -1
  45. package/web/_astro/{dagre-BM42HDAG.BATZG1II.js → dagre-BM42HDAG.GDbfFYVV.js} +1 -1
  46. package/web/_astro/{diagram-2AECGRRQ.B0f5yY6x.js → diagram-2AECGRRQ.CvIDBeJF.js} +1 -1
  47. package/web/_astro/{diagram-5GNKFQAL.Bewupqv0.js → diagram-5GNKFQAL.DMgJjWOX.js} +1 -1
  48. package/web/_astro/{diagram-KO2AKTUF.C23r1tB2.js → diagram-KO2AKTUF.njjl-0AP.js} +1 -1
  49. package/web/_astro/{diagram-LMA3HP47.DUErdBOR.js → diagram-LMA3HP47.CcCqgP8M.js} +1 -1
  50. package/web/_astro/{diagram-OG6HWLK6.BubmwAjb.js → diagram-OG6HWLK6.Btqd-YVE.js} +1 -1
  51. package/web/_astro/{erDiagram-TEJ5UH35.BE_Qs5mv.js → erDiagram-TEJ5UH35.BbhML_Xo.js} +1 -1
  52. package/web/_astro/{flowDiagram-I6XJVG4X.COWdbzon.js → flowDiagram-I6XJVG4X.CkvsIgY8.js} +1 -1
  53. package/web/_astro/{ganttDiagram-6RSMTGT7.5EGYC4MK.js → ganttDiagram-6RSMTGT7.m8IWD_wW.js} +1 -1
  54. package/web/_astro/{gitGraphDiagram-PVQCEYII.bUUV7iEw.js → gitGraphDiagram-PVQCEYII.5z87HXO-.js} +1 -1
  55. package/web/_astro/{index.Cestp9nh.css → index.nWve6EHS.css} +1 -1
  56. package/web/_astro/{infoDiagram-5YYISTIA.CbiInFvz.js → infoDiagram-5YYISTIA.Bvyvsd7Q.js} +1 -1
  57. package/web/_astro/{ishikawaDiagram-YF4QCWOH.CB7lrSsJ.js → ishikawaDiagram-YF4QCWOH.DdWVYO87.js} +1 -1
  58. package/web/_astro/{journeyDiagram-JHISSGLW.rhGZgWt8.js → journeyDiagram-JHISSGLW.C3kOlYH1.js} +1 -1
  59. package/web/_astro/{kanban-definition-UN3LZRKU.DPeZD_lP.js → kanban-definition-UN3LZRKU.CfKTUHVC.js} +1 -1
  60. package/web/_astro/{linear.l60Nyp5b.js → linear.CMDHnzgX.js} +1 -1
  61. package/web/_astro/{mermaid.core.DmtMJcmL.js → mermaid.core.DN7-WrsP.js} +4 -4
  62. package/web/_astro/{mindmap-definition-RKZ34NQL.Cc47AcCn.js → mindmap-definition-RKZ34NQL.DcQxLvqG.js} +1 -1
  63. package/web/_astro/{pieDiagram-4H26LBE5.B8cN-S9g.js → pieDiagram-4H26LBE5.Nu-Inbw6.js} +1 -1
  64. package/web/_astro/{quadrantDiagram-W4KKPZXB.DY1FN89L.js → quadrantDiagram-W4KKPZXB.Dck2EChs.js} +1 -1
  65. package/web/_astro/{requirementDiagram-4Y6WPE33.CnM6VbV8.js → requirementDiagram-4Y6WPE33.C--E5XuW.js} +1 -1
  66. package/web/_astro/{sankeyDiagram-5OEKKPKP.B9DcGfNV.js → sankeyDiagram-5OEKKPKP.B_0iRNna.js} +1 -1
  67. package/web/_astro/{sequenceDiagram-3UESZ5HK.ymxaNlAY.js → sequenceDiagram-3UESZ5HK.PKuKr9mk.js} +1 -1
  68. package/web/_astro/{stateDiagram-AJRCARHV.DDOJ3d8P.js → stateDiagram-AJRCARHV.ClhGHb-O.js} +1 -1
  69. package/web/_astro/{stateDiagram-v2-BHNVJYJU.Ck0Nb_KY.js → stateDiagram-v2-BHNVJYJU.Bv6BBnvg.js} +1 -1
  70. package/web/_astro/{timeline-definition-PNZ67QCA.BNOvZwAN.js → timeline-definition-PNZ67QCA.BZm2ZvAH.js} +1 -1
  71. package/web/_astro/{vennDiagram-CIIHVFJN.D3k5ivSF.js → vennDiagram-CIIHVFJN.Dt6yZXry.js} +1 -1
  72. package/web/_astro/{wardley-L42UT6IY.BylaxVqn.js → wardley-L42UT6IY.rUs-E00M.js} +1 -1
  73. package/web/_astro/{wardleyDiagram-YWT4CUSO.HLNtFUm9.js → wardleyDiagram-YWT4CUSO.BcWgv4cA.js} +1 -1
  74. package/web/_astro/{xychartDiagram-2RQKCTM6.B-WooKSU.js → xychartDiagram-2RQKCTM6.DH-M-cX9.js} +1 -1
  75. package/web/index.html +2 -2
  76. package/plugins/sp/commands/dev-history-load.md +0 -63
  77. package/plugins/sp/scripts/history-load.mjs +0 -268
  78. package/plugins/sp/scripts/history-load.ts +0 -400
  79. package/web/_astro/BoardApp.BtRfVADq.js +0 -1
  80. package/web/_astro/BoardApp.CBIzcvqi.js +0 -178
  81. package/web/_astro/channel.kIu33Gui.js +0 -1
package/spur.js CHANGED
@@ -37418,479 +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 misplacedGlobalKeys(parsed) {
37596
- const misplaced = [];
37597
- for (const key of PROJECT_SHAPED_GLOBAL_KEYS) {
37598
- if (key in parsed)
37599
- misplaced.push(key);
37600
- }
37601
- const agent = parsed.agent;
37602
- if (typeof agent === "object" && agent !== null && "team" in agent) {
37603
- misplaced.push("agent.team");
37604
- }
37605
- return misplaced;
37606
- }
37607
- function parseEnvBoolean(value) {
37608
- if (value === undefined || value === "")
37609
- return;
37610
- const normalized = value.toLowerCase();
37611
- if (["1", "true", "yes", "on"].includes(normalized))
37612
- return true;
37613
- if (["0", "false", "no", "off"].includes(normalized))
37614
- return false;
37615
- throw new Error(`Invalid boolean environment value: expected true/false, received ${value}`);
37616
- }
37617
- function buildConfigFromEnv(env = process.env) {
37618
- return configSchema2.parse({
37619
- database: {
37620
- url: env[SPUR_ENV_VARS.databaseUrl]
37621
- },
37622
- server: {
37623
- port: env[SPUR_ENV_VARS.port],
37624
- host: env[SPUR_ENV_VARS.host]
37625
- },
37626
- telemetry: {
37627
- enabled: parseEnvBoolean(env.SPUR_TELEMETRY_ENABLED),
37628
- endpoint: env.SPUR_TELEMETRY_ENDPOINT
37629
- },
37630
- logging: {
37631
- level: env.SPUR_LOG_LEVEL
37632
- }
37633
- });
37634
- }
37635
- 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, configSchema2;
37636
- var init_src = __esm(() => {
37637
- init_zod();
37638
- init_finding_codes();
37639
- init_projects();
37640
- SPUR_ENV_VARS = {
37641
- nodeEnv: "NODE_ENV",
37642
- port: "PORT",
37643
- host: "HOST",
37644
- publicApiUrl: "PUBLIC_API_URL",
37645
- databaseUrl: "DATABASE_URL"
37646
- };
37647
- SPUR_LOG_LEVELS = ["debug", "info", "warn", "error"];
37648
- folderConfigSchema = exports_external.object({
37649
- baseCounter: exports_external.number().int().nonnegative().default(0),
37650
- label: exports_external.string().optional()
37651
- });
37652
- tasksConfigSchema = exports_external.object({
37653
- folders: exports_external.record(exports_external.string(), folderConfigSchema).default({}),
37654
- active: folderPathWithDefault(DEFAULT_TASKS_DIR),
37655
- severity: exports_external.record(exports_external.string(), exports_external.enum(["error", "warning", "off"])).optional()
37656
- }).superRefine((data, ctx) => {
37657
- if (data.severity) {
37658
- for (const code of Object.keys(data.severity)) {
37659
- if (!isFindingCode(code)) {
37660
- ctx.addIssue({
37661
- code: exports_external.ZodIssueCode.custom,
37662
- path: ["severity", code],
37663
- message: `Unknown finding code "${code}" in severity overrides map`
37664
- });
37665
- }
37666
- }
37667
- }
37668
- });
37669
- featuresConfigSchema = exports_external.object({
37670
- dir: folderPathWithDefault(DEFAULT_FEATURES_DIR)
37671
- });
37672
- EXECUTOR_CAPABILITY_TIERS = ["cheap", "standard", "capable-1", "capable-2", "capable-3"];
37673
- executorCapabilityTierSchema = exports_external.preprocess((value) => {
37674
- if (value === undefined || value === null)
37675
- return;
37676
- return typeof value === "string" ? normalizeExecutorTier(value) : value;
37677
- }, exports_external.enum(EXECUTOR_CAPABILITY_TIERS).optional());
37678
- AGENT_ROLE_NAMES = ["scribe", "coder", "reviewer", "planner"];
37679
- DEFAULT_AGENT_ROLES = new Map([
37680
- ["scribe", { tier: "cheap", stages: ["changelog"] }],
37681
- ["coder", { tier: "standard", stages: ["implement", "test", "wrap"] }],
37682
- ["reviewer", { tier: "capable-1", stages: ["verify", "review", "dogfood"] }],
37683
- ["planner", { tier: "capable-2", stages: ["plan", "refine", "brainstorm"] }]
37684
- ]);
37685
- AgentExecutorConfigSchema = exports_external.object({
37686
- name: exports_external.string().min(1),
37687
- agent: exports_external.string().min(1),
37688
- model: exports_external.string().min(1).optional(),
37689
- tier: executorCapabilityTierSchema.optional()
37690
- });
37691
- AgentRoleConfigSchema = exports_external.object({
37692
- tier: exports_external.enum(EXECUTOR_CAPABILITY_TIERS).optional(),
37693
- stages: exports_external.array(exports_external.string().min(1)).min(1).optional()
37694
- });
37695
- AGENT_ID_REGEX = /^[a-z][a-z0-9_-]{1,63}$/;
37696
- TeamMemberConfigSchema = exports_external.union([
37697
- exports_external.string().min(1),
37698
- exports_external.object({
37699
- executor: exports_external.string().min(1).optional(),
37700
- id: exports_external.string().min(1).optional(),
37701
- role: exports_external.enum(AGENT_ROLE_NAMES, {
37702
- error: (issue2) => new Error(`Unknown role "${issue2.input}" \u2014 expected one of: ${AGENT_ROLE_NAMES.join(", ")}`)
37703
- }).optional(),
37704
- purpose: exports_external.string().optional(),
37705
- workspace: exports_external.string().min(1).optional(),
37706
- model: exports_external.string().min(1).optional(),
37707
- autonomy: exports_external.string().optional(),
37708
- systemPrompt: exports_external.string().optional(),
37709
- command: exports_external.array(exports_external.string().min(1)).optional(),
37710
- autostart: exports_external.boolean().optional()
37711
- })
37712
- ]);
37713
- TeamConfigSchema = exports_external.object({
37714
- name: exports_external.string().min(1),
37715
- work_dir: exports_external.string().min(1),
37716
- autostart: exports_external.boolean().optional(),
37717
- members: exports_external.array(TeamMemberConfigSchema).min(1)
37718
- });
37719
- AgentOutputConfigSchema = exports_external.object({
37720
- "max-bytes": exports_external.number().int().positive().optional(),
37721
- "max-lines": exports_external.number().int().positive().optional()
37722
- });
37723
- AgentConfigSchema = exports_external.object({
37724
- default: exports_external.string().optional(),
37725
- executors: exports_external.array(AgentExecutorConfigSchema).optional(),
37726
- roles: exports_external.record(exports_external.string(), AgentRoleConfigSchema).optional(),
37727
- team: exports_external.record(exports_external.string(), TeamConfigSchema).optional(),
37728
- output: AgentOutputConfigSchema.optional(),
37729
- sessionAffinity: exports_external.boolean().optional()
37730
- }).superRefine((value, ctx) => {
37731
- for (const key of Object.keys(value.roles ?? {})) {
37732
- if (!AGENT_ROLE_NAMES.includes(key)) {
37733
- ctx.addIssue({
37734
- code: exports_external.ZodIssueCode.custom,
37735
- path: ["roles", key],
37736
- message: `Unknown role "${key}" \u2014 expected one of: ${AGENT_ROLE_NAMES.join(", ")}`
37737
- });
37738
- }
37739
- }
37740
- const executors = value.executors;
37741
- const executorNames = new Set;
37742
- if (executors !== undefined) {
37743
- const seen = new Set;
37744
- for (const [index2, executor] of executors.entries()) {
37745
- if (seen.has(executor.name)) {
37746
- ctx.addIssue({
37747
- code: exports_external.ZodIssueCode.custom,
37748
- message: `Duplicate executor name: ${executor.name}`,
37749
- path: ["executors", index2, "name"]
37750
- });
37751
- }
37752
- if (executor.name === "inline" || executor.name === "auto") {
37753
- ctx.addIssue({
37754
- code: exports_external.ZodIssueCode.custom,
37755
- message: `Executor name "${executor.name}" is reserved (the --agent selector value '${executor.name}' has fixed semantics); rename executor at index ${index2}.`,
37756
- path: ["executors", index2, "name"]
37757
- });
37758
- }
37759
- if (AGENT_ROLE_NAMES.includes(executor.name)) {
37760
- ctx.addIssue({
37761
- code: exports_external.ZodIssueCode.custom,
37762
- message: `Executor name "${executor.name}" collides with the role selector namespace ("${executor.name}"); rename executor at index ${index2}.`,
37763
- path: ["executors", index2, "name"]
37764
- });
37765
- }
37766
- seen.add(executor.name);
37767
- executorNames.add(executor.name);
37768
- }
37769
- }
37770
- const team = value.team;
37771
- if (team === undefined)
37772
- return;
37773
- const seenComposed = new Set;
37774
- for (const [teamId, teamConfig] of Object.entries(team)) {
37775
- const seenLocal = new Set;
37776
- const members = teamConfig.members.map(normalizeMember);
37777
- members.forEach((ref, index2) => {
37778
- if (ref.executor === undefined && ref.role === undefined) {
37779
- ctx.addIssue({
37780
- code: exports_external.ZodIssueCode.custom,
37781
- message: `Team member at index ${index2} in team "${teamId}" declares neither role nor executor \u2014 at least one of role or executor is required.`,
37782
- path: ["team", teamId, "members", index2]
37783
- });
37784
- return;
37785
- }
37786
- const localId = memberLocalId(ref, members, index2);
37787
- if (seenLocal.has(localId)) {
37788
- ctx.addIssue({
37789
- code: exports_external.ZodIssueCode.custom,
37790
- message: `Duplicate team member id: ${localId} in team "${teamId}"`,
37791
- path: ["team", teamId, "members", index2]
37792
- });
37793
- }
37794
- seenLocal.add(localId);
37795
- const composedId = `${teamId}-${localId}`;
37796
- if (!AGENT_ID_REGEX.test(composedId)) {
37797
- ctx.addIssue({
37798
- code: exports_external.ZodIssueCode.custom,
37799
- message: `Invalid composed agent id "${composedId}": team key "${teamId}" + member "${localId}" must match ^[a-z][a-z0-9_-]{1,63}$ (2-64 chars, lowercase).`,
37800
- path: ["team", teamId, "members", index2]
37801
- });
37802
- }
37803
- if (seenComposed.has(composedId)) {
37804
- ctx.addIssue({
37805
- code: exports_external.ZodIssueCode.custom,
37806
- 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).`,
37807
- path: ["team", teamId, "members", index2]
37808
- });
37809
- }
37810
- seenComposed.add(composedId);
37811
- if (ref.id !== undefined && AGENT_ROLE_NAMES.includes(ref.id)) {
37812
- ctx.addIssue({
37813
- code: exports_external.ZodIssueCode.custom,
37814
- message: `Team member id "${ref.id}" collides with the role selector namespace ("${ref.id}") in team "${teamId}"; rename the member id.`,
37815
- path: ["team", teamId, "members", index2]
37816
- });
37817
- }
37818
- if (ref.id !== undefined && executorNames.has(ref.id)) {
37819
- ctx.addIssue({
37820
- code: exports_external.ZodIssueCode.custom,
37821
- message: `Team member id "${ref.id}" collides with executor name "${ref.id}" in team "${teamId}"; the --agent selector namespace must be pairwise disjoint.`,
37822
- path: ["team", teamId, "members", index2]
37823
- });
37824
- }
37825
- if (executorNames.has(composedId)) {
37826
- ctx.addIssue({
37827
- code: exports_external.ZodIssueCode.custom,
37828
- message: `Composed agent id "${composedId}" collides with executor name "${composedId}"; the --agent selector namespace must be pairwise disjoint.`,
37829
- path: ["team", teamId, "members", index2]
37830
- });
37831
- }
37832
- if (AGENT_ROLE_NAMES.includes(composedId)) {
37833
- ctx.addIssue({
37834
- code: exports_external.ZodIssueCode.custom,
37835
- message: `Composed agent id "${composedId}" collides with the role selector namespace ("${composedId}"); the --agent selector namespace must be pairwise disjoint.`,
37836
- path: ["team", teamId, "members", index2]
37837
- });
37838
- }
37839
- });
37840
- }
37841
- });
37842
- RulesConfigSchema = exports_external.object({
37843
- paths: exports_external.array(exports_external.string()).optional()
37844
- });
37845
- WorkflowsConfigSchema = exports_external.object({
37846
- paths: exports_external.array(exports_external.string()).optional()
37847
- });
37848
- WorkflowConfigSchema = exports_external.object({
37849
- logRetentionDays: exports_external.number().int().positive().default(30)
37850
- });
37851
- RedactionConfigSchema = exports_external.object({
37852
- enabled: exports_external.boolean().optional()
37853
- });
37854
- HistoryRefreshConfigSchema = exports_external.object({
37855
- on_completion: exports_external.boolean().default(false),
37856
- debounce_ms: exports_external.number().int().min(1000).default(600000)
37857
- });
37858
- HistoryConfigSchema = exports_external.object({
37859
- refresh: HistoryRefreshConfigSchema.optional()
37860
- });
37861
- spurConfigSchema = exports_external.object({
37862
- version: exports_external.string().optional(),
37863
- name: exports_external.string().optional(),
37864
- agent: AgentConfigSchema.optional(),
37865
- rules: RulesConfigSchema.optional(),
37866
- workflows: WorkflowsConfigSchema.optional(),
37867
- workflow: WorkflowConfigSchema.optional(),
37868
- redaction: RedactionConfigSchema.optional(),
37869
- history: HistoryConfigSchema.optional(),
37870
- tasks: tasksConfigSchema.optional(),
37871
- features: featuresConfigSchema.optional()
37872
- });
37873
- PROJECT_SHAPED_GLOBAL_KEYS = ["name", "bootstrap", "rules", "redaction", "tasks", "features"];
37874
- configSchema2 = exports_external.object({
37875
- database: exports_external.object({
37876
- url: exports_external.string().default(DEFAULT_DATABASE_URL)
37877
- }).default({ url: DEFAULT_DATABASE_URL }),
37878
- server: exports_external.object({
37879
- port: exports_external.coerce.number().int().positive().default(3000),
37880
- host: exports_external.string().default("localhost"),
37881
- openBrowser: exports_external.boolean().default(true),
37882
- webDistPath: exports_external.string().nullable().default(null)
37883
- }).default({ port: 3000, host: "localhost", openBrowser: true, webDistPath: null }),
37884
- telemetry: exports_external.object({
37885
- enabled: exports_external.boolean().default(false),
37886
- endpoint: exports_external.string().optional()
37887
- }).default({ enabled: false }),
37888
- logging: exports_external.object({
37889
- level: exports_external.enum(SPUR_LOG_LEVELS).default("info")
37890
- }).default({ level: "info" })
37891
- });
37892
- });
37893
-
37894
37421
  // ../../node_modules/.bun/@gobing-ai+ts-runtime@0.4.42+815552c3ca302057/node_modules/@gobing-ai/ts-runtime/dist/config.js
37895
37422
  function parseYamlObject(text3) {
37896
37423
  let parsed;
@@ -37940,7 +37467,7 @@ function interpolateTree2(value) {
37940
37467
  function buildConfigFromObject2(raw, options = {}) {
37941
37468
  const interpolated = interpolateTree2(raw);
37942
37469
  const merged = options.overrides ? deepMerge(interpolated, options.overrides) : interpolated;
37943
- const result = configSchema3.safeParse(merged);
37470
+ const result = configSchema2.safeParse(merged);
37944
37471
  if (!result.success) {
37945
37472
  throw new ConfigLoadError2("Config validation failed", result.error.issues);
37946
37473
  }
@@ -37977,12 +37504,12 @@ function deepFreeze2(obj) {
37977
37504
  }
37978
37505
  return obj;
37979
37506
  }
37980
- var configSchema3, ENV_INTERPOLATION_RE2, YamlParseError, ConfigLoadError2;
37507
+ var configSchema2, ENV_INTERPOLATION_RE2, YamlParseError, ConfigLoadError2;
37981
37508
  var init_config2 = __esm(() => {
37982
37509
  init_dist();
37983
37510
  init_dist2();
37984
37511
  init_zod();
37985
- configSchema3 = exports_external.object({
37512
+ configSchema2 = exports_external.object({
37986
37513
  app: exports_external.object({
37987
37514
  name: exports_external.string().default("app"),
37988
37515
  env: exports_external.enum(["development", "staging", "production", "test"]).default("development"),
@@ -39448,7 +38975,7 @@ __export(exports_dist2, {
39448
38975
  createLogStream: () => createLogStream2,
39449
38976
  createInMemoryProcessRegistry: () => createInMemoryProcessRegistry2,
39450
38977
  createCfFileSystem: () => createCfFileSystem2,
39451
- configSchema: () => configSchema3,
38978
+ configSchema: () => configSchema2,
39452
38979
  cloudflareWorkersFactory: () => cloudflareWorkersFactory2,
39453
38980
  buildConfigFromYaml: () => buildConfigFromYaml,
39454
38981
  buildConfigFromObject: () => buildConfigFromObject2,
@@ -39488,6 +39015,481 @@ var init_dist5 = __esm(() => {
39488
39015
  init_types2();
39489
39016
  });
39490
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
+
39491
39493
  // ../../packages/config/src/bundled-config.ts
39492
39494
  import { existsSync as existsSync3, readdirSync as readdirSync3, statSync as statSync4 } from "fs";
39493
39495
  import { dirname as dirname3, join as join2 } from "path";
@@ -50269,6 +50271,53 @@ function selectorDigest(selector) {
50269
50271
  var HISTORY_ARTIFACT_SCHEMA_VERSION = 1;
50270
50272
  var init_artifact = () => {};
50271
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
+
50272
50321
  // ../../packages/domain/src/analytics/models.ts
50273
50322
  function resolvePricing(model) {
50274
50323
  if (model === undefined || model.length === 0)
@@ -50885,6 +50934,22 @@ async function bySession(db2, sel, top, opts) {
50885
50934
  };
50886
50935
  });
50887
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
+ }
50888
50953
  async function loops(db2, sel, opts) {
50889
50954
  const { where, params } = buildMessageWhere(sel);
50890
50955
  const wm = applyWatermarkToWhere(where, opts?.watermark);
@@ -52030,7 +52095,11 @@ function narrowArtifact(artifact, opts, artifactPath) {
52030
52095
  narrowed = {
52031
52096
  ...artifact,
52032
52097
  byTool: artifact.byTool.slice(0, top),
52033
- 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
52034
52103
  };
52035
52104
  }
52036
52105
  parts.push(`top ${top}`);
@@ -52425,7 +52494,7 @@ function renderSessionSummary(artifact, derived) {
52425
52494
  "",
52426
52495
  "| Metric | Value |",
52427
52496
  "| --- | --- |",
52428
- `| Sessions | ${artifact.bySession.length.toLocaleString("en-US")} |`,
52497
+ `| Sessions | ${fmtTopOf(artifact.population?.sessions, artifact.population?.appliedTop)} |`,
52429
52498
  `| Wall-clock span | ${wall} |`,
52430
52499
  `| Messages | ${t.messages.toLocaleString("en-US")} |`,
52431
52500
  `| Tool calls | ${t.toolCalls.toLocaleString("en-US")} |`,
@@ -52650,23 +52719,38 @@ function renderBottlenecks(derived) {
52650
52719
  }
52651
52720
  function renderRawData(artifact) {
52652
52721
  const s = artifact.selector;
52722
+ const p = artifact.population;
52653
52723
  const lines = [
52654
52724
  "## Raw Data",
52655
52725
  "",
52656
52726
  `- Selector: since=${s.since ?? "-"} until=${s.until ?? "-"} sources=${s.sources ? s.sources.join(",") : "all"} session=${s.sessionId ?? "-"} run=${s.runId ?? "-"} task=${s.taskWbs ?? "-"}`,
52657
- `- 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`
52658
52728
  ];
52659
52729
  if (artifact.warnings.length > 0) {
52660
- 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
+ }
52661
52734
  }
52662
52735
  if (artifact.coverage.length > 0) {
52663
- 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 |", "| --- | --- | ---: | ---: | ---: | ---: | --- | ---: | ---: |");
52664
52737
  for (const c3 of artifact.coverage) {
52665
- 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} |`);
52666
52741
  }
52667
52742
  }
52668
52743
  return lines;
52669
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
+ }
52670
52754
  function fmtWall(ms) {
52671
52755
  if (ms < 1000)
52672
52756
  return `${Math.round(ms)}ms`;
@@ -52695,7 +52779,7 @@ function fmtToolDur(ms, bucket) {
52695
52779
  const allUnmeasured = bucket.calls > 0 && bucket.durationUnmeasured === bucket.calls;
52696
52780
  return allUnmeasured ? "n/a" : fmtDur(ms);
52697
52781
  }
52698
- 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;
52699
52783
  var init_render_forensics = __esm(() => {
52700
52784
  init_artifact();
52701
52785
  init_render_report();
@@ -53494,6 +53578,7 @@ var init_run_cost = __esm(() => {
53494
53578
  // ../../packages/domain/src/analytics/index.ts
53495
53579
  var init_analytics = __esm(() => {
53496
53580
  init_artifact();
53581
+ init_artifact_digest();
53497
53582
  init_costs();
53498
53583
  init_forensic_query();
53499
53584
  init_models();
@@ -57413,7 +57498,7 @@ var init_dist7 = __esm(() => {
57413
57498
  });
57414
57499
 
57415
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
57416
- import { createHash as createHash2 } from "crypto";
57501
+ import { createHash as createHash3 } from "crypto";
57417
57502
  function stableJson(value) {
57418
57503
  if (Array.isArray(value)) {
57419
57504
  return `[${value.map((entry) => entry === undefined ? "null" : stableJson(entry)).join(",")}]`;
@@ -57425,7 +57510,7 @@ function stableJson(value) {
57425
57510
  return JSON.stringify(value) ?? "null";
57426
57511
  }
57427
57512
  function sha256(value) {
57428
- return createHash2("sha256").update(stableJson(value)).digest("hex");
57513
+ return createHash3("sha256").update(stableJson(value)).digest("hex");
57429
57514
  }
57430
57515
  var init_hash = () => {};
57431
57516
 
@@ -64451,9 +64536,9 @@ var init_db2 = __esm(() => {
64451
64536
  });
64452
64537
 
64453
64538
  // ../../packages/domain/src/envelope/fingerprint.ts
64454
- import { createHash as createHash3 } from "crypto";
64539
+ import { createHash as createHash4 } from "crypto";
64455
64540
  function computeContentHash(content) {
64456
- return createHash3("sha256").update(content, "utf-8").digest("hex");
64541
+ return createHash4("sha256").update(content, "utf-8").digest("hex");
64457
64542
  }
64458
64543
  var init_fingerprint = () => {};
64459
64544
 
@@ -65273,7 +65358,7 @@ function attributeWithoutTelemetry(layers, capturedLayers) {
65273
65358
  }
65274
65359
 
65275
65360
  // ../../packages/domain/src/envelope/invalidation.ts
65276
- import { createHash as createHash4 } from "crypto";
65361
+ import { createHash as createHash5 } from "crypto";
65277
65362
  function computeInvalidationFingerprint(trigger) {
65278
65363
  if (trigger.kind === "tool-output")
65279
65364
  return null;
@@ -65297,7 +65382,7 @@ function computeInvalidationFingerprint(trigger) {
65297
65382
  default:
65298
65383
  return null;
65299
65384
  }
65300
- return createHash4("sha256").update(raw, "utf-8").digest("hex");
65385
+ return createHash5("sha256").update(raw, "utf-8").digest("hex");
65301
65386
  }
65302
65387
  function checkLayerStale(layer, freshFingerprint) {
65303
65388
  if (freshFingerprint === null) {
@@ -66605,7 +66690,9 @@ __export(exports_src, {
66605
66690
  serializeStablePrefix: () => serializeStablePrefix,
66606
66691
  serializeLayer: () => serializeLayer,
66607
66692
  serializeEnvelope: () => serializeEnvelope,
66693
+ semanticArtifactDigest: () => semanticArtifactDigest,
66608
66694
  selectorDigest: () => selectorDigest,
66695
+ selectionPopulation: () => selectionPopulation,
66609
66696
  sectionMatrixSchema: () => sectionMatrixSchema,
66610
66697
  scaffoldFeatureScenarios: () => scaffoldFeatureScenarios,
66611
66698
  runRetention: () => runRetention,
@@ -66780,6 +66867,7 @@ __export(exports_src, {
66780
66867
  ROUTING_SUMMARY_DEFAULT_WINDOW_MS: () => ROUTING_SUMMARY_DEFAULT_WINDOW_MS,
66781
66868
  REPORT_MODES: () => REPORT_MODES,
66782
66869
  REGISTERED_CANONICAL_STAGES: () => REGISTERED_CANONICAL_STAGES,
66870
+ RANKED_ARTIFACT_KEYS: () => RANKED_ARTIFACT_KEYS,
66783
66871
  QUEUE_JOBS_SCHEMA_SQL: () => QUEUE_JOBS_SCHEMA_SQL,
66784
66872
  PlanningEventDao: () => PlanningEventDao,
66785
66873
  PhaseRunDao: () => PhaseRunDao,
@@ -66820,7 +66908,8 @@ __export(exports_src, {
66820
66908
  ArtifactDao: () => ArtifactDao,
66821
66909
  ActionRunDao: () => ActionRunDao,
66822
66910
  AUTHORITY_LANES: () => AUTHORITY_LANES,
66823
- ARTIFACT_DIRECTIONS: () => ARTIFACT_DIRECTIONS
66911
+ ARTIFACT_DIRECTIONS: () => ARTIFACT_DIRECTIONS,
66912
+ ARTIFACT_ARRAY_CLASSIFICATION: () => ARTIFACT_ARRAY_CLASSIFICATION
66824
66913
  });
66825
66914
  var init_src2 = __esm(() => {
66826
66915
  init_db2();
@@ -67901,6 +67990,9 @@ class AgentService {
67901
67990
  async doctor(args, deps) {
67902
67991
  const executors = this.ctx.agentConfig?.executors;
67903
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
+ }
67904
67996
  if (args.agent === undefined) {
67905
67997
  const results2 = await doctorRunner.runAll();
67906
67998
  return this.renderDoctor(results2, executors, args.json, args.agent);
@@ -67909,7 +68001,11 @@ class AgentService {
67909
68001
  if (roleDef !== undefined) {
67910
68002
  const resolved = await this.resolveRole(args.agent, roleDef.tier, doctorRunner);
67911
68003
  if (!resolved.ok) {
67912
- 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
+ }
67913
68009
  return resolved.exitCode;
67914
68010
  }
67915
68011
  const results2 = [await doctorRunner.runOne(resolved.executor ?? resolved.agent)];
@@ -67932,7 +68028,7 @@ class AgentService {
67932
68028
  const executor = executorByName.get(result.agent) ?? { name: result.agent, agent: result.agent };
67933
68029
  return { ...result, capabilityTier: getExecutorTier(executor) };
67934
68030
  });
67935
- this.ctx.output.write(toJson({ agents: rows }));
68031
+ this.ctx.output.write(toJson({ agents: rows, rolesSource: this.ctx.rolesSource ?? "config" }));
67936
68032
  } else if (agent !== undefined) {
67937
68033
  this.ctx.output.write(renderDoctorDetail(results[0] ?? null));
67938
68034
  } else {
@@ -67946,7 +68042,11 @@ class AgentService {
67946
68042
  execution: this.defaultExecutionOptions(flags)
67947
68043
  });
67948
68044
  if (!outcome.ok) {
67949
- 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
+ }
67950
68050
  return outcome.exitCode;
67951
68051
  }
67952
68052
  const result = outcome.result;
@@ -69864,6 +69964,294 @@ var init_planning_check_base = __esm(() => {
69864
69964
  init_structural_repair();
69865
69965
  });
69866
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
+
69867
70255
  // ../../packages/app/src/services/feature-check.ts
69868
70256
  var exports_feature_check = {};
69869
70257
  __export(exports_feature_check, {
@@ -69996,6 +70384,8 @@ var init_feature_check = __esm(() => {
69996
70384
  init_done_transition_guard();
69997
70385
  init_planning_check_base();
69998
70386
  init_structural_repair();
70387
+ init_task_record();
70388
+ init_verify_verdict();
69999
70389
  DEFAULT_FEATURE_MATRIX = {
70000
70390
  variants: {
70001
70391
  standard: {
@@ -70255,7 +70645,12 @@ var init_feature_check = __esm(() => {
70255
70645
  if (tac.trim().length > 0)
70256
70646
  linkedTaskAc.push(tac);
70257
70647
  linkedTaskSolutions.push(taskDoc.getSection("Solution") ?? "");
70258
- linkedTaskRecords.push({ wbs, status: tStatus, ac: tac });
70648
+ linkedTaskRecords.push({
70649
+ wbs,
70650
+ status: tStatus,
70651
+ ac: tac,
70652
+ testing: taskDoc.getSection("Testing") ?? ""
70653
+ });
70259
70654
  } catch {
70260
70655
  findings.push({
70261
70656
  layer: "L4",
@@ -70367,9 +70762,42 @@ var init_feature_check = __esm(() => {
70367
70762
  }
70368
70763
  }
70369
70764
  const artifacts2 = new Map;
70765
+ const testingByWbs = new Map(linkedTasks.map((t) => [t.wbs, t.testing]));
70370
70766
  for (const wbs of doneWbs) {
70371
70767
  const artifact = await this.readVerdictArtifact(runDir, wbs);
70372
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
+ }
70373
70801
  const diagnosticParts = [];
70374
70802
  if (artifact.diagnostics.artifactError !== undefined) {
70375
70803
  if (artifact.diagnostics.artifactError !== "artifact is missing") {
@@ -70415,7 +70843,7 @@ var init_feature_check = __esm(() => {
70415
70843
  const artifact = artifacts2.get(task.wbs);
70416
70844
  if (artifact === undefined)
70417
70845
  continue;
70418
- if (artifact.verdict !== "PASS")
70846
+ if (artifact.verdict !== "PASS" || artifact.computedVerdict !== "PASS")
70419
70847
  continue;
70420
70848
  const rows = [...artifact.requirements, ...artifact.acceptanceCriteria];
70421
70849
  const matched = rows.find((r) => rowMatchesScenario(r.id, sc));
@@ -70445,6 +70873,7 @@ var init_feature_check = __esm(() => {
70445
70873
  return {
70446
70874
  path: loaded.path,
70447
70875
  verdict: typeof parsed.verdict === "string" ? parsed.verdict : undefined,
70876
+ computedVerdict: computeAggregate(loaded.artifact),
70448
70877
  requirements: req.rows,
70449
70878
  acceptanceCriteria: ac.rows,
70450
70879
  diagnostics: {
@@ -77049,7 +77478,8 @@ class HistoryService {
77049
77478
  stepDurationRows,
77050
77479
  cacheWasteAggRow,
77051
77480
  cacheWasteRows,
77052
- stepSupportRows
77481
+ stepSupportRows,
77482
+ population
77053
77483
  ] = await Promise.all([
77054
77484
  messageRollup(db2, selector, queryOpts),
77055
77485
  toolRollup(db2, selector, queryOpts),
@@ -77066,7 +77496,8 @@ class HistoryService {
77066
77496
  topStepsByDuration(db2, selector, top, queryOpts),
77067
77497
  cacheWasteAggregate(db2, selector, queryOpts),
77068
77498
  topCacheWasteSteps(db2, selector, top, queryOpts),
77069
- stepSupport(db2, selector, queryOpts)
77499
+ stepSupport(db2, selector, queryOpts),
77500
+ selectionPopulation(db2, selector, queryOpts)
77070
77501
  ]);
77071
77502
  await dropWatermarkExclude();
77072
77503
  const totals = foldTotals2(mRows, tRows);
@@ -77076,6 +77507,11 @@ class HistoryService {
77076
77507
  const daily = Object.entries(byDay).map(([date6, totals2]) => ({ date: date6, ...totals2 })).sort((a2, b) => a2.date.localeCompare(b.date));
77077
77508
  const coverage = buildCoverage(sourceRows, tRows, driftRows, opts.coverageErrors, opts.importCoverage);
77078
77509
  const derived = computeDerived(spanRows, toolDurRows, todoRows);
77510
+ const warnings = [
77511
+ ...buildWarnings(driftRows, coverage),
77512
+ ...derivedWarnings(derived),
77513
+ ...opts.extraWarnings ?? []
77514
+ ];
77079
77515
  const stateByKey = new Map;
77080
77516
  for (const w of watermarks)
77081
77517
  stateByKey.set(`${w.sessionId}\x00${w.source}`, w.state);
@@ -77114,11 +77550,7 @@ class HistoryService {
77114
77550
  sessionState: stateByKey.get(`${r.sessionId}\x00${r.source}`) ?? "complete"
77115
77551
  })),
77116
77552
  loops: loopRows,
77117
- warnings: [
77118
- ...buildWarnings(driftRows, coverage),
77119
- ...derivedWarnings(derived),
77120
- ...opts.extraWarnings ?? []
77121
- ],
77553
+ warnings,
77122
77554
  pairings,
77123
77555
  ladderSnapshot,
77124
77556
  derived,
@@ -77129,7 +77561,14 @@ class HistoryService {
77129
77561
  inputTokens: cacheWasteAggRow?.inputTokens ?? 0,
77130
77562
  topSteps: cacheWasteRows
77131
77563
  },
77132
- 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
+ }
77133
77572
  };
77134
77573
  await refreshHistoryRollups(db2);
77135
77574
  if (opts.out !== undefined || opts.cwd !== undefined) {
@@ -77234,8 +77673,8 @@ class HistoryService {
77234
77673
  lastImportedAt: null,
77235
77674
  parseErrors: result.parseErrors.length,
77236
77675
  validationErrors: result.validationErrors.length,
77237
- parseErrorSamples: result.parseErrors.slice(0, MAX_ERROR_SAMPLES).map(formatIssue),
77238
- 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),
77239
77678
  ...result.reconciliation ? { reconciliation: result.reconciliation } : {}
77240
77679
  };
77241
77680
  return { coverageEntry, sourceWarnings };
@@ -77438,16 +77877,16 @@ function boundCoverage(coverage) {
77438
77877
  const bounded2 = coverage.map((entry) => {
77439
77878
  const pe = entry.parseErrorSamples;
77440
77879
  const ve = entry.validationErrorSamples;
77441
- for (const sample of pe.slice(MAX_ERROR_SAMPLES)) {
77880
+ for (const sample of pe.slice(MAX_ERROR_SAMPLES2)) {
77442
77881
  overflow.push({ source: entry.source, kind: "parse", sample });
77443
77882
  }
77444
- for (const sample of ve.slice(MAX_ERROR_SAMPLES)) {
77883
+ for (const sample of ve.slice(MAX_ERROR_SAMPLES2)) {
77445
77884
  overflow.push({ source: entry.source, kind: "validation", sample });
77446
77885
  }
77447
77886
  return {
77448
77887
  ...entry,
77449
- parseErrorSamples: pe.slice(0, MAX_ERROR_SAMPLES),
77450
- validationErrorSamples: ve.slice(0, MAX_ERROR_SAMPLES)
77888
+ parseErrorSamples: pe.slice(0, MAX_ERROR_SAMPLES2),
77889
+ validationErrorSamples: ve.slice(0, MAX_ERROR_SAMPLES2)
77451
77890
  };
77452
77891
  });
77453
77892
  return { bounded: bounded2, overflow };
@@ -77544,7 +77983,7 @@ function pruneReports(cwd, retentionDays = REPORT_RETENTION_DAYS, now2 = new Dat
77544
77983
  }
77545
77984
  return pruned.sort((a2, b) => a2.localeCompare(b));
77546
77985
  }
77547
- 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;
77548
77987
  var init_history_service = __esm(() => {
77549
77988
  init_src2();
77550
77989
  init_dist8();
@@ -80194,205 +80633,6 @@ var init_system_event_follow = __esm(() => {
80194
80633
  init_src2();
80195
80634
  });
80196
80635
 
80197
- // ../../packages/app/src/services/task-record.ts
80198
- function escapeTablePipe(s3) {
80199
- return s3.replace(/\|/g, "\\|");
80200
- }
80201
- function parseVerdict(raw, fallbackWbs) {
80202
- if (raw.trim() === "") {
80203
- return { wbs: fallbackWbs ?? "", verdict: "UNKNOWN", requirements: [], checks: [] };
80204
- }
80205
- try {
80206
- const parsed = JSON.parse(raw);
80207
- if (parsed !== null && typeof parsed === "object") {
80208
- const obj = parsed;
80209
- const wbs = typeof obj.wbs === "string" ? obj.wbs : fallbackWbs ?? "";
80210
- const verdict = normalizeVerdict(obj.verdict);
80211
- const requirements = normalizeRequirements(obj.requirements);
80212
- const acceptanceCriteria = normalizeAcceptanceCriteria(obj.acceptanceCriteria);
80213
- const checks4 = normalizeChecks(obj.checks);
80214
- return { wbs, verdict, requirements, acceptanceCriteria, checks: checks4 };
80215
- }
80216
- } catch {}
80217
- return { wbs: fallbackWbs ?? "", verdict: "UNKNOWN", requirements: [], checks: [] };
80218
- }
80219
- async function readVerdict(fs3, path9, fallbackWbs) {
80220
- let raw;
80221
- try {
80222
- raw = await fs3.readFile(path9);
80223
- } catch {
80224
- return { wbs: fallbackWbs ?? "", verdict: "UNKNOWN", requirements: [], checks: [] };
80225
- }
80226
- return parseVerdict(raw, fallbackWbs);
80227
- }
80228
- function normalizeVerdict(raw) {
80229
- if (typeof raw !== "string")
80230
- return "UNKNOWN";
80231
- const upper = raw.toUpperCase();
80232
- if (upper === "PASS")
80233
- return "PASS";
80234
- if (upper === "PARTIAL")
80235
- return "PARTIAL";
80236
- if (upper === "FAIL")
80237
- return "FAIL";
80238
- return "UNKNOWN";
80239
- }
80240
- function normalizeRequirements(raw) {
80241
- if (!Array.isArray(raw))
80242
- return [];
80243
- return raw.filter((r) => r !== null && typeof r === "object").map((r) => ({
80244
- id: typeof r.id === "string" ? r.id : "",
80245
- status: typeof r.status === "string" ? r.status : "",
80246
- evidence: typeof r.evidence === "string" ? r.evidence : ""
80247
- }));
80248
- }
80249
- function normalizeAcceptanceCriteria(raw) {
80250
- if (!Array.isArray(raw))
80251
- return [];
80252
- return raw.filter((ac) => ac !== null && typeof ac === "object").map((ac) => ({
80253
- id: typeof ac.id === "string" ? ac.id : "",
80254
- status: typeof ac.status === "string" ? ac.status : "",
80255
- evidenceType: typeof ac.evidenceType === "string" ? ac.evidenceType : "",
80256
- evidence: typeof ac.evidence === "string" ? ac.evidence : ""
80257
- }));
80258
- }
80259
- function normalizeChecks(raw) {
80260
- if (!Array.isArray(raw))
80261
- return [];
80262
- return raw.filter((c3) => c3 !== null && typeof c3 === "object").map((c3) => ({
80263
- name: typeof c3.name === "string" ? c3.name : "",
80264
- status: typeof c3.status === "string" ? c3.status : "",
80265
- evidence: typeof c3.evidence === "string" ? c3.evidence : ""
80266
- }));
80267
- }
80268
- function renderTesting(v) {
80269
- const lines = [];
80270
- lines.push("**Pipeline verify results**");
80271
- lines.push("");
80272
- lines.push(`- Verdict: ${v.verdict} (from verdict artifact)`);
80273
- lines.push("");
80274
- if (v.requirements.length === 0) {
80275
- lines.push("| Requirement | Status | Evidence |");
80276
- lines.push("|-------------|--------|----------|");
80277
- lines.push(`| \u2014 | \u2014 | No requirements recorded; verify verdict ${v.verdict} |`);
80278
- } else {
80279
- lines.push("| Requirement | Status | Evidence |");
80280
- lines.push("|-------------|--------|----------|");
80281
- for (const req of v.requirements) {
80282
- const evidence = escapeTablePipe(req.evidence.replace(/\n/g, " "));
80283
- lines.push(`| ${req.id} | ${req.status} | ${evidence} |`);
80284
- }
80285
- }
80286
- if ((v.acceptanceCriteria ?? []).length > 0) {
80287
- lines.push("");
80288
- lines.push("| Acceptance Criteria | Status | Evidence Type | Evidence |");
80289
- lines.push("|---------------------|--------|---------------|----------|");
80290
- for (const ac of v.acceptanceCriteria ?? []) {
80291
- const evidence = escapeTablePipe(ac.evidence.replace(/\n/g, " "));
80292
- lines.push(`| ${ac.id} | ${ac.status} | ${ac.evidenceType} | ${evidence} |`);
80293
- }
80294
- }
80295
- lines.push("- Coverage: N/A (verdict-based; verify pipeline does not measure code coverage)");
80296
- return lines.join(`
80297
- `);
80298
- }
80299
- function renderReview(v) {
80300
- const lines = [];
80301
- lines.push(`**SECU findings** (pipeline verify step \u2014 verdict: ${v.verdict})`);
80302
- lines.push("");
80303
- lines.push("| Priority | Dimension | Location | Finding |");
80304
- lines.push("|----------|-----------|----------|----------|");
80305
- if (v.checks.length === 0) {
80306
- lines.push(`| P4 | \u2014 | \u2014 | No P1\u2013P3 findings; verify verdict ${v.verdict} |`);
80307
- } else {
80308
- for (const check2 of v.checks) {
80309
- const finding = escapeTablePipe(check2.evidence.replace(/\n/g, " "));
80310
- const priority = /^P[1-4]$/.test(check2.status) ? check2.status : check2.status === "fail" ? "P1" : "P4";
80311
- lines.push(`| ${priority} | ${check2.name} | \u2014 | ${finding} |`);
80312
- }
80313
- }
80314
- lines.push("");
80315
- return lines.join(`
80316
- `);
80317
- }
80318
- function renderSolutionFromDiff(diffText) {
80319
- const lines = [];
80320
- lines.push("Change-map (auto-generated \u2014 implement step did not record a Solution).");
80321
- lines.push("Each entry cites the first changed line per file (`file:line`).");
80322
- lines.push("");
80323
- lines.push("| Change (`file:line`) |");
80324
- lines.push("|----------------------|");
80325
- const citations = extractFileLineCitations(diffText);
80326
- if (citations.length === 0) {
80327
- const files = extractChangedFiles(diffText);
80328
- if (files.length === 0) {
80329
- lines.push("| `(no changes detected)` |");
80330
- } else {
80331
- for (const f of files) {
80332
- lines.push(`| \`${f}:1\` |`);
80333
- }
80334
- }
80335
- } else {
80336
- for (const citation of citations) {
80337
- lines.push(`| \`${citation}\` |`);
80338
- }
80339
- }
80340
- lines.push("");
80341
- return lines.join(`
80342
- `);
80343
- }
80344
- function extractFileLineCitations(diffText) {
80345
- const citations = new Set;
80346
- let currentFile = null;
80347
- for (const line of diffText.split(`
80348
- `)) {
80349
- const fileMatch = /^\+\+\+ b\/(.+)$/.exec(line);
80350
- if (fileMatch) {
80351
- currentFile = fileMatch[1] ?? null;
80352
- continue;
80353
- }
80354
- if (currentFile === null)
80355
- continue;
80356
- const hunkMatch = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(line);
80357
- if (hunkMatch) {
80358
- const newLine = hunkMatch[1];
80359
- if (newLine !== undefined) {
80360
- citations.add(`${currentFile}:${newLine}`);
80361
- }
80362
- }
80363
- }
80364
- return [...citations].sort();
80365
- }
80366
- function extractChangedFiles(diffText) {
80367
- const files = new Set;
80368
- for (const line of diffText.split(`
80369
- `)) {
80370
- const match = /^\+\+\+ b\/(.+)$/.exec(line);
80371
- if (match) {
80372
- const filePath = match[1];
80373
- if (filePath !== undefined)
80374
- files.add(filePath);
80375
- }
80376
- }
80377
- return [...files].sort();
80378
- }
80379
- function gitDiffU0(cwd) {
80380
- try {
80381
- const result = new BunSyncProcessExecutor2().runSync({
80382
- command: "git",
80383
- args: ["diff", "-U0", "HEAD", "--", "*.ts", "*.tsx", "*.js"],
80384
- ...cwd !== undefined ? { cwd } : {},
80385
- rejectOnError: false
80386
- });
80387
- return result.exitCode === 0 ? result.stdout : "";
80388
- } catch {
80389
- return "";
80390
- }
80391
- }
80392
- var init_task_record = __esm(() => {
80393
- init_dist5();
80394
- });
80395
-
80396
80636
  // ../../packages/app/src/services/task-scaffold.ts
80397
80637
  import { dirname as dirname13 } from "path";
80398
80638
 
@@ -81902,7 +82142,7 @@ class TeamService {
81902
82142
  }
81903
82143
  async listTeams() {
81904
82144
  const specs = await loadAgentSpecs(this.configDir);
81905
- const config4 = await this.loadTeamConfig();
82145
+ const config4 = this.ctx.spurConfig ?? null;
81906
82146
  const teams = new Map;
81907
82147
  const configTeamIds = new Set;
81908
82148
  if (config4?.agent?.team) {
@@ -81961,7 +82201,7 @@ class TeamService {
81961
82201
  return Array.from(teams.values());
81962
82202
  }
81963
82203
  async materializeTeam(teamId, opts) {
81964
- const config4 = await this.loadTeamConfig();
82204
+ const config4 = this.ctx.spurConfig ?? null;
81965
82205
  const teamConfig = config4?.agent?.team?.[teamId];
81966
82206
  if (!teamConfig) {
81967
82207
  throw new Error(`Team "${teamId}" not found in agent.team config`);
@@ -82086,13 +82326,6 @@ class TeamService {
82086
82326
  const workDir = this.resolveWorkspaceDir(first);
82087
82327
  return { workDir, isCurrentProject: this.isCurrentProject(workDir) };
82088
82328
  }
82089
- async loadTeamConfig() {
82090
- try {
82091
- return await loadSpurConfig(this.ctx.cwd);
82092
- } catch {
82093
- return null;
82094
- }
82095
- }
82096
82329
  async inboxDao() {
82097
82330
  const db2 = await this.ctx.getDb();
82098
82331
  return new InboxMessageDao(db2);
@@ -82198,7 +82431,6 @@ function resolveAutostartSet(config4, envAutostart) {
82198
82431
  }
82199
82432
  var init_team_service = __esm(() => {
82200
82433
  init_src();
82201
- init_loader();
82202
82434
  init_src2();
82203
82435
  init_dist9();
82204
82436
  init_planning_folders();
@@ -83324,21 +83556,26 @@ var init_agent_run = __esm(() => {
83324
83556
  init_steering();
83325
83557
  });
83326
83558
 
83327
- // ../../packages/app/src/workflow/actions/command-gate.ts
83328
- import { normalize, resolve as resolve9 } from "path";
83329
- function splitExecutable(executable) {
83330
- if (SHELL_METACHARACTERS.test(executable)) {
83331
- return {
83332
- error: `command.gate "executable" must not contain shell metacharacters (got ${executable})`
83333
- };
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})` };
83334
83563
  }
83335
- const tokens = executable.trim().split(/\s+/).filter((t) => t.length > 0);
83564
+ const tokens = value.trim().split(/\s+/).filter((t) => t.length > 0);
83336
83565
  const command = tokens[0];
83337
83566
  if (command === undefined) {
83338
- 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` };
83339
83569
  }
83340
83570
  return { command, leadingArgs: tokens.slice(1) };
83341
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";
83342
83579
  function sleep3(ms) {
83343
83580
  return new Promise((resolve10) => setTimeout(resolve10, ms));
83344
83581
  }
@@ -83399,7 +83636,7 @@ class CommandGateActionRunner {
83399
83636
  error: `resultFile must resolve beneath .spur/run/ (got ${resultFileRaw})`
83400
83637
  };
83401
83638
  }
83402
- const split = splitExecutable(executable);
83639
+ const split = splitLaunchCommand(executable, 'command.gate "executable"');
83403
83640
  if ("error" in split) {
83404
83641
  return { ok: false, error: split.error };
83405
83642
  }
@@ -83479,25 +83716,14 @@ ${lastStderr}`;
83479
83716
  };
83480
83717
  }
83481
83718
  }
83482
- var KIND2 = "command.gate", SHELL_METACHARACTERS;
83719
+ var KIND2 = "command.gate";
83483
83720
  var init_command_gate = __esm(() => {
83484
83721
  init_dist5();
83485
- SHELL_METACHARACTERS = /[;&|<>$`(){}[\]!*?~#\n\r"']/;
83722
+ init_split_launch_command();
83486
83723
  });
83487
83724
 
83488
83725
  // ../../packages/app/src/workflow/actions/doctor-probe.ts
83489
83726
  import { normalize as normalize2, resolve as resolve10, sep as sep2 } from "path";
83490
- function splitSpurBin(spurBin) {
83491
- if (SHELL_METACHARACTERS2.test(spurBin)) {
83492
- return { error: `doctor.probe "spurBin" must not contain shell metacharacters (got ${spurBin})` };
83493
- }
83494
- const tokens = spurBin.trim().split(/\s+/).filter((t) => t.length > 0);
83495
- const command = tokens[0];
83496
- if (command === undefined) {
83497
- return { error: 'Action option "spurBin" must be a non-empty string' };
83498
- }
83499
- return { command, leadingArgs: tokens.slice(1) };
83500
- }
83501
83727
  function stringOption2(options, key2, fallback) {
83502
83728
  const value = options[key2];
83503
83729
  if (typeof value === "string")
@@ -83557,7 +83783,7 @@ class DoctorProbeActionRunner {
83557
83783
  error: `resultFile must resolve beneath .spur/run/ (got ${resultFileRaw})`
83558
83784
  };
83559
83785
  }
83560
- const split = splitSpurBin(spurBin);
83786
+ const split = splitLaunchCommand(spurBin, 'doctor.probe "spurBin"');
83561
83787
  if ("error" in split) {
83562
83788
  return { ok: false, error: split.error };
83563
83789
  }
@@ -83633,11 +83859,11 @@ ${res.stderr}`.trim();
83633
83859
  return { ok: true, data: { status, resultFile: normalized, output: lines } };
83634
83860
  }
83635
83861
  }
83636
- 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;
83637
83863
  var init_doctor_probe = __esm(() => {
83638
83864
  init_dist5();
83639
83865
  init_observability();
83640
- SHELL_METACHARACTERS2 = /[;&|<>$`(){}[\]!*?~#\n\r"']/;
83866
+ init_split_launch_command();
83641
83867
  RELAY_FAMILY = /^(omp|pi)(-.*)?$/;
83642
83868
  ENV_MISS_PATTERN = /API key not found for provider|no probe registered/i;
83643
83869
  AUTH_FAIL_PATTERN = /invalid (API )?key|unauthori[sz]ed|forbidden|authentication failed|credential(s)? rejected/i;
@@ -85006,7 +85232,7 @@ class WorkflowAppService {
85006
85232
  const isDry = opts.dryRun === true;
85007
85233
  const warnings = [];
85008
85234
  const runVars = {
85009
- ...await resolveDefaultAgentVar(this.ctx.cwd, opts.vars, (m) => warnings.push(m)),
85235
+ ...resolveDefaultAgentVar(this.ctx.spurConfig ?? null, opts.vars, (m) => warnings.push(m)),
85010
85236
  ...opts.vars ?? {},
85011
85237
  __runId: runId
85012
85238
  };
@@ -85329,14 +85555,12 @@ class WorkflowAppService {
85329
85555
  let agentSlice = {
85330
85556
  ...this.ctx.secretValues !== undefined ? { secretValues: this.ctx.secretValues } : {}
85331
85557
  };
85332
- try {
85333
- const agent = (await loadSpurConfig(this.ctx.cwd)).agent;
85334
- agentSlice = {
85335
- ...agentSlice,
85336
- ...agent?.default !== undefined ? { default: agent.default } : {},
85337
- ...agent?.sessionAffinity !== undefined ? { sessionAffinity: agent.sessionAffinity } : {}
85338
- };
85339
- } 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
+ };
85340
85564
  try {
85341
85565
  const fs3 = createNodeFileSystem3(this.ctx.cwd);
85342
85566
  const { foldersConfig, featuresDir } = await resolvePlanningFolders(fs3);
@@ -85581,22 +85805,16 @@ function resolveWorkflowFile(cwd, file2) {
85581
85805
  }
85582
85806
  return { path: null, probed: [projectPath, null] };
85583
85807
  }
85584
- async function resolveDefaultAgentVar(cwd, callerVars, warn) {
85808
+ function resolveDefaultAgentVar(config4, callerVars, warn) {
85585
85809
  const result = {};
85586
85810
  if (callerVars?.implementAgent === undefined && callerVars?.agent !== undefined) {
85587
85811
  result.implementAgent = callerVars.agent;
85588
85812
  }
85589
- let config4;
85590
- try {
85591
- config4 = await loadSpurConfig(cwd);
85592
- } catch {
85593
- return result;
85594
- }
85595
- const configured = config4.agent?.default;
85813
+ const configured = config4?.agent?.default;
85596
85814
  if (typeof configured !== "string" || configured.length === 0) {
85597
85815
  return result;
85598
85816
  }
85599
- 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;
85600
85818
  if (!valid) {
85601
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`);
85602
85820
  return result;
@@ -85608,25 +85826,17 @@ async function resolveDefaultAgentVar(cwd, callerVars, warn) {
85608
85826
  }
85609
85827
  return result;
85610
85828
  }
85611
- async function resolveWorkflowLogRetentionDays(cwd) {
85612
- try {
85613
- return (await loadSpurConfig(cwd)).workflow?.logRetentionDays ?? 30;
85614
- } catch {
85615
- return 30;
85616
- }
85829
+ function resolveWorkflowLogRetentionDays(config4) {
85830
+ return config4?.workflow?.logRetentionDays ?? 30;
85617
85831
  }
85618
- async function resolveOutputLogConfig(cwd) {
85619
- try {
85620
- const output2 = (await loadSpurConfig(cwd)).agent?.output;
85621
- if (output2 === undefined)
85622
- return {};
85623
- return {
85624
- ...output2["max-bytes"] !== undefined ? { maxBytes: output2["max-bytes"] } : {},
85625
- ...output2["max-lines"] !== undefined ? { maxLines: output2["max-lines"] } : {}
85626
- };
85627
- } catch {
85832
+ function resolveOutputLogConfig(config4) {
85833
+ const output2 = config4?.agent?.output;
85834
+ if (output2 === undefined)
85628
85835
  return {};
85629
- }
85836
+ return {
85837
+ ...output2["max-bytes"] !== undefined ? { maxBytes: output2["max-bytes"] } : {},
85838
+ ...output2["max-lines"] !== undefined ? { maxLines: output2["max-lines"] } : {}
85839
+ };
85630
85840
  }
85631
85841
  async function outputArtifactForRun(cwd, runId) {
85632
85842
  const relative5 = join22(".spur", "run", `${runId}.log`);
@@ -85842,6 +86052,9 @@ var init_workflow_service = __esm(() => {
85842
86052
 
85843
86053
  // ../../packages/app/src/workflow/idea-handoff.ts
85844
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
+ }
85845
86058
  async function finalizeIdeaHandoff(options) {
85846
86059
  const root = options.projectRoot ?? process.cwd();
85847
86060
  const fs3 = options.fileSystem ?? createNodeFileSystem3();
@@ -85853,6 +86066,18 @@ async function finalizeIdeaHandoff(options) {
85853
86066
  const resultPath = resolve14(runDir, `${runId}-idea-batch-create-result.json`);
85854
86067
  const orderPath = resolve14(runDir, `${runId}-idea-task-order.json`);
85855
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;
85856
86081
  if (!await fs3.exists(batchPath) || !await fs3.exists(resultPath) || !await fs3.exists(orderPath)) {
85857
86082
  return {
85858
86083
  ok: false,
@@ -85922,8 +86147,8 @@ async function finalizeIdeaHandoff(options) {
85922
86147
  }
85923
86148
  if (deps.length > 0) {
85924
86149
  const depRes = await executor.run({
85925
- command: spurBin,
85926
- args: ["task", "deps", ownWbs, "set", ...deps, "--json"],
86150
+ command: spurCommand,
86151
+ args: [...spurArgsPrefix, "task", "deps", ownWbs, "set", ...deps, "--json"],
85927
86152
  cwd: root,
85928
86153
  forceBuffered: true,
85929
86154
  rejectOnError: false
@@ -85934,27 +86159,45 @@ async function finalizeIdeaHandoff(options) {
85934
86159
  wbsList: result.wbs,
85935
86160
  nextCommand: "",
85936
86161
  reportPath,
85937
- error: `Failed to set dependencies for task ${ownWbs}: ${depRes.stderr}`
86162
+ error: `Failed to set dependencies for task ${ownWbs}: ${processEvidence(depRes)}`
85938
86163
  };
85939
86164
  }
85940
86165
  }
85941
86166
  }
85942
- await executor.run({
85943
- command: spurBin,
85944
- args: ["feature", "refresh", "--feature", featureId2, "--json"],
86167
+ const refreshRes = await executor.run({
86168
+ command: spurCommand,
86169
+ args: [...spurArgsPrefix, "feature", "refresh", "--feature", featureId2, "--json"],
85945
86170
  cwd: root,
85946
86171
  forceBuffered: true,
85947
86172
  rejectOnError: false
85948
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
+ }
85949
86183
  const checkResults = [];
85950
86184
  for (const wbs of result.wbs) {
85951
86185
  const checkRes = await executor.run({
85952
- command: spurBin,
85953
- args: ["task", "check", wbs, "--json"],
86186
+ command: spurCommand,
86187
+ args: [...spurArgsPrefix, "task", "check", wbs, "--json"],
85954
86188
  cwd: root,
85955
86189
  forceBuffered: true,
85956
86190
  rejectOnError: false
85957
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
+ }
85958
86201
  checkResults.push({ wbs, pass: checkRes.exitCode === 0 });
85959
86202
  }
85960
86203
  const anyFailed = checkResults.some((c3) => !c3.pass);
@@ -86000,6 +86243,7 @@ async function finalizeIdeaHandoff(options) {
86000
86243
  }
86001
86244
  var init_idea_handoff = __esm(() => {
86002
86245
  init_dist5();
86246
+ init_split_launch_command();
86003
86247
  });
86004
86248
 
86005
86249
  // ../../packages/app/src/workflow/idea-handoff-cli.ts
@@ -86909,7 +87153,6 @@ var {
86909
87153
  } = import__.default;
86910
87154
 
86911
87155
  // src/index.ts
86912
- init_src();
86913
87156
  init_loader();
86914
87157
 
86915
87158
  // ../../node_modules/.bun/@gobing-ai+ts-infra@0.4.42+4012563f10a1cd34/node_modules/@gobing-ai/ts-infra/dist/application-node.js
@@ -95693,7 +95936,7 @@ import { createRequire } from "module";
95693
95936
  var CLI_CONFIG = {
95694
95937
  binaryName: "spur",
95695
95938
  binaryLabel: "spur",
95696
- binaryVersion: "0.3.61",
95939
+ binaryVersion: "0.3.62",
95697
95940
  configDir: ".spur",
95698
95941
  configFile: ".spur/config.yaml",
95699
95942
  databaseFile: ".spur/spur.db"
@@ -96350,7 +96593,11 @@ function registerMessageCommand(program2, context4) {
96350
96593
  async function runMessageSend(svc, context4, body, options) {
96351
96594
  const trimmed = body.trim();
96352
96595
  if (trimmed === "") {
96353
- 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
+ }
96354
96601
  return 2;
96355
96602
  }
96356
96603
  const from = options.from || DEFAULT_FROM;
@@ -106547,7 +106794,8 @@ function createServerContext(appRt, options) {
106547
106794
  fs: fs3,
106548
106795
  getDb: this.getDb.bind(this),
106549
106796
  eventBus: bridgeEventBus(eventsBus),
106550
- events: bridgeEventBus(eventsBus)
106797
+ events: bridgeEventBus(eventsBus),
106798
+ ...options.spurConfig !== undefined ? { spurConfig: options.spurConfig } : {}
106551
106799
  });
106552
106800
  }
106553
106801
  return teamSvc;
@@ -106614,6 +106862,7 @@ function createServerContext(appRt, options) {
106614
106862
  agentService: this.agentService.bind(this),
106615
106863
  ruleService: this.ruleService.bind(this),
106616
106864
  hitlResponder: this.hitlResponder.bind(this),
106865
+ ...options.spurConfig !== undefined ? { spurConfig: options.spurConfig } : {},
106617
106866
  observabilityBus: () => bridgeEventBus(eventsBus),
106618
106867
  events: () => bridgeEventBus(eventsBus)
106619
106868
  });
@@ -106887,6 +107136,7 @@ async function startServer(options, deps = defaultDeps) {
106887
107136
  if (!webDistPath) {
106888
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).");
106889
107138
  }
107139
+ const spurConfig = await loadSpurConfig(process.cwd()).catch(() => null);
106890
107140
  const ctx = deps.createServerContext(appRt, {
106891
107141
  cwd: process.cwd(),
106892
107142
  fs: fs3,
@@ -106897,10 +107147,10 @@ async function startServer(options, deps = defaultDeps) {
106897
107147
  jobQueueEnabled: bootConfig.jobqueue.enabled,
106898
107148
  scheduler: scheduler3,
106899
107149
  teamAutostart: bootConfig.teamAutostart,
106900
- bootConfig
107150
+ bootConfig,
107151
+ ...spurConfig !== undefined ? { spurConfig } : {}
106901
107152
  });
106902
107153
  let jobWorker;
106903
- const spurConfig = await loadSpurConfig(process.cwd()).catch(() => null);
106904
107154
  const autostartIds = resolveAutostartSet(spurConfig, env.SPUR_TEAM_AUTOSTART);
106905
107155
  if (autostartIds.length > 0) {
106906
107156
  try {
@@ -107991,17 +108241,11 @@ var EMBEDDED_SPUR_SCHEMAS = new Map([
107991
108241
  ]);
107992
108242
 
107993
108243
  // src/history-refresh.ts
107994
- init_loader();
107995
108244
  init_dist6();
107996
108245
  await init_src3();
107997
108246
  async function maybeTriggerHistoryRefresh(context4, trigger, triggerId) {
107998
108247
  try {
107999
- let config4 = null;
108000
- try {
108001
- config4 = await loadSpurConfig(context4.cwd, { embeddedSchemas: EMBEDDED_SPUR_SCHEMAS });
108002
- } catch {
108003
- config4 = null;
108004
- }
108248
+ const config4 = context4.spurConfig ?? null;
108005
108249
  const db2 = await context4.getDb();
108006
108250
  const result = await enqueueHistoryRefresh(db2, { config: config4, trigger, triggerId });
108007
108251
  if (result.status === "disabled")
@@ -109456,13 +109700,20 @@ function submitSteeringLine(controller, output2, line) {
109456
109700
  if (ack === undefined)
109457
109701
  output2.error(`[steer] ignored command: ${line}`);
109458
109702
  }
109459
- async function resolveWorkflowPaths(cwd) {
109460
- try {
109461
- const config4 = await loadSpurConfig(cwd, { embeddedSchemas: EMBEDDED_SPUR_SCHEMAS });
109462
- return config4.workflows?.paths ?? [".spur/workflows/"];
109463
- } catch {
109464
- 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
+ }
109465
109715
  }
109716
+ return expanded;
109466
109717
  }
109467
109718
  function registerWorkflowCommand(program2, context4) {
109468
109719
  const makeSvc = (json3, bus) => new WorkflowAppService({
@@ -109625,7 +109876,7 @@ Monitor with: spur workflow trace ${runId2} --follow`);
109625
109876
  dir: join34(context4.cwd, ".spur", "run"),
109626
109877
  runId,
109627
109878
  ...planPreview !== undefined ? { planPreview } : {},
109628
- ...await resolveOutputLogConfig(context4.cwd)
109879
+ ...resolveOutputLogConfig(context4.spurConfig ?? null)
109629
109880
  });
109630
109881
  if (humanProgress) {
109631
109882
  context4.output.write(`Run: ${runId}`);
@@ -109787,7 +110038,7 @@ Monitor with: spur workflow trace ${runId2} --follow`);
109787
110038
  }
109788
110039
  const svc = makeSvc(options.json);
109789
110040
  const result = logsOnly ? undefined : await svc.clean(minutes, dryRun);
109790
- const retentionDays = await resolveWorkflowLogRetentionDays(context4.cwd);
110041
+ const retentionDays = resolveWorkflowLogRetentionDays(context4.spurConfig ?? null);
109791
110042
  const logResult2 = await svc.cleanRunLogs(retentionDays, dryRun);
109792
110043
  if (options.json) {
109793
110044
  context4.output.write(toJson2(logsOnly ? logResult2 : { ...result, logs: logResult2 }));
@@ -109836,7 +110087,7 @@ Monitor with: spur workflow trace ${runId2} --follow`);
109836
110087
  }
109837
110088
  });
109838
110089
  workflow.command("list").description("List available workflow YAML files.").option(...SHARED_OPTIONS.jsonSupported).action(async (options) => {
109839
- const paths = await resolveWorkflowPaths(context4.cwd);
110090
+ const paths = resolveWorkflowPaths(context4.spurConfig ?? null);
109840
110091
  const result = await makeSvc().list(paths);
109841
110092
  if (options.json) {
109842
110093
  context4.output.write(toJson2(result));
@@ -111584,7 +111835,9 @@ function createCliContext(options) {
111584
111835
  const cwd = resolve18(options.cwd ?? process.cwd());
111585
111836
  const env = options.env ?? process.env;
111586
111837
  const fs3 = createNodeFileSystem3(cwd);
111587
- 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";
111588
111841
  let dbPromise;
111589
111842
  if (options.db) {
111590
111843
  dbPromise = Promise.resolve(options.db);
@@ -111601,13 +111854,16 @@ function createCliContext(options) {
111601
111854
  output: options.output,
111602
111855
  getDb,
111603
111856
  ...options.agentConfig !== undefined ? { agentConfig: options.agentConfig } : {},
111857
+ ...options.spurConfig !== undefined ? { spurConfig: options.spurConfig } : {},
111604
111858
  agentRoles,
111859
+ agentRolesSource,
111605
111860
  agentService: (serviceOptions) => new AgentService({
111606
111861
  cwd,
111607
111862
  env,
111608
111863
  output: options.output,
111609
- agentConfig: options.agentConfig,
111864
+ agentConfig,
111610
111865
  roles: agentRoles,
111866
+ rolesSource: agentRolesSource,
111611
111867
  getDb,
111612
111868
  ...serviceOptions?.events !== undefined ? { events: serviceOptions.events } : {},
111613
111869
  ...serviceOptions?.processRegistry !== undefined ? { processRegistry: serviceOptions.processRegistry } : {}
@@ -111636,34 +111892,46 @@ async function createMigratedDbAdapter(cwd = process.cwd(), env = process.env, d
111636
111892
  async function main(argv = process.argv.slice(2), options = {}) {
111637
111893
  const output2 = options.output ?? consoleOutput;
111638
111894
  let exitCode = 0;
111639
- const configFile = resolveConfigFile(options.cwd);
111640
- 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
+ }
111641
111912
  try {
111642
111913
  if (configFile !== undefined) {
111643
- await loadSpurConfig(options.cwd ?? process.cwd(), { embeddedSchemas: EMBEDDED_SPUR_SCHEMAS });
111644
111914
  const app = await runNodeApplication({
111645
111915
  configLoader: {
111646
111916
  configFile,
111647
- bootstrapSection: "bootstrap",
111648
- appConfig: { safeParse: (raw2) => spurConfigSchema.safeParse(raw2) }
111917
+ bootstrapSection: "bootstrap"
111649
111918
  },
111650
111919
  config: { logging: { console: false } },
111651
111920
  services: { db: db2 },
111652
111921
  async start(appRt) {
111653
111922
  const context4 = createCliContext({
111654
- cwd: options.cwd,
111655
- env: options.env,
111923
+ cwd,
111924
+ env,
111656
111925
  output: output2,
111657
111926
  db: db2,
111658
- agentConfig: appRt.appConfig?.agent
111927
+ spurConfig
111659
111928
  });
111660
111929
  exitCode = await runCommandDispatch(argv, context4, output2);
111661
111930
  }
111662
111931
  });
111663
111932
  await app.stop("shutdown");
111664
111933
  } else {
111665
- const ctxOpts = { cwd: options.cwd, env: options.env, output: output2, db: db2 };
111666
- const context4 = createCliContext(ctxOpts);
111934
+ const context4 = createCliContext({ cwd, env, output: output2, db: db2, spurConfig });
111667
111935
  exitCode = await runCommandDispatch(argv, context4, output2);
111668
111936
  }
111669
111937
  } finally {