@gobing-ai/spur 0.3.50 → 0.3.52

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 (30) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/corpus-baseline.json +1995 -1939
  3. package/config/rules/quality/coverage-gate.yaml +3 -2
  4. package/config/tasks/section-matrix.yaml +7 -2
  5. package/config/workflows/pr-review.yaml +13 -11
  6. package/config/workflows/task-lifecycle.yaml +7 -6
  7. package/config/workflows/task-pipeline.yaml +37 -16
  8. package/package.json +1 -1
  9. package/plugins/sp/agents/super-planner.md +3 -2
  10. package/plugins/sp/agents/super-reviewer.md +5 -0
  11. package/plugins/sp/commands/dev-review.md +1 -1
  12. package/plugins/sp/plugin.json +1 -1
  13. package/plugins/sp/scripts/pr-reviewing.ts +57 -5
  14. package/plugins/sp/scripts/stage-registry-adapter.ts +40 -25
  15. package/plugins/sp/skills/code-improvement/SKILL.md +5 -3
  16. package/plugins/sp/skills/code-verification/SKILL.md +28 -31
  17. package/plugins/sp/skills/code-verification/references/verdict-schema.md +47 -0
  18. package/plugins/sp/skills/functional-review/SKILL.md +16 -15
  19. package/plugins/sp/skills/pr-reviewing/SKILL.md +6 -3
  20. package/plugins/sp/skills/spec-decomposition/references/decomposition.md +13 -14
  21. package/plugins/sp/skills/spur-cli/references/tasks/l3-guard-cheatsheet.md +7 -3
  22. package/plugins/sp/skills/spur-cli/references/tasks/section-editing.md +22 -13
  23. package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +22 -11
  24. package/plugins/sp/skills/spur-cli/references/tasks.md +16 -10
  25. package/plugins/sp/skills/spur-dev/SKILL.md +10 -5
  26. package/plugins/sp/skills/spur-dev/references/dev-operations.md +5 -5
  27. package/plugins/sp/skills/spur-dev/references/execution-workflow.md +11 -4
  28. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +5 -3
  29. package/plugins/sp/skills/spur-dev/references/section-batching.md +29 -13
  30. package/spur.js +382 -231
package/spur.js CHANGED
@@ -62573,7 +62573,7 @@ var init_schema4 = __esm(() => {
62573
62573
  init_zod();
62574
62574
  STAGE_REGISTRY_SCHEMA_VERSION = {
62575
62575
  major: 1,
62576
- minor: 2
62576
+ minor: 3
62577
62577
  };
62578
62578
  stageSchemaVersionSchema = exports_external.object({
62579
62579
  major: exports_external.number().int().nonnegative(),
@@ -62628,6 +62628,7 @@ var init_schema4 = __esm(() => {
62628
62628
  stageArtifactSchema = exports_external.object({
62629
62629
  kind: exports_external.string().min(1),
62630
62630
  direction: exports_external.enum(ARTIFACT_DIRECTIONS),
62631
+ identity: exports_external.string().optional(),
62631
62632
  description: exports_external.string().optional(),
62632
62633
  required: exports_external.boolean().optional().default(true)
62633
62634
  }).strict();
@@ -62719,7 +62720,15 @@ var init_schema4 = __esm(() => {
62719
62720
  id: "refine",
62720
62721
  aliases: ["dev-refine"],
62721
62722
  description: "dev-refine: Q&A refinement, section filling, AC tightening",
62722
- artifacts: [{ kind: "task-section", direction: "output", required: true }],
62723
+ artifacts: [
62724
+ {
62725
+ kind: "task-section",
62726
+ direction: "input",
62727
+ required: true,
62728
+ description: "Background/Requirements sections"
62729
+ },
62730
+ { kind: "task-section", direction: "output", required: true, description: "Q&A/Design/Plan/AC sections" }
62731
+ ],
62723
62732
  reasoning_skill: "sp:spur-dev",
62724
62733
  required_references: [],
62725
62734
  gates: [],
@@ -62742,10 +62751,16 @@ var init_schema4 = __esm(() => {
62742
62751
  id: "plan",
62743
62752
  aliases: ["dev-plan"],
62744
62753
  description: "dev-plan: feature intake -> AC generation -> decomposition",
62745
- artifacts: [{ kind: "task-batch", direction: "output", required: true }],
62754
+ artifacts: [
62755
+ { kind: "feature-frontmatter", direction: "input", required: true },
62756
+ { kind: "task-batch", direction: "output", required: true }
62757
+ ],
62746
62758
  reasoning_skill: "sp:spur-dev",
62747
62759
  required_references: [],
62748
- gates: [],
62760
+ gates: [
62761
+ { name: "feature-check", timing: "post", min_verdict: "pass" },
62762
+ { name: "batch-create", timing: "post", min_verdict: "pass" }
62763
+ ],
62749
62764
  mutation_class: "corpus",
62750
62765
  retry: { max_attempts: 3, terminal_stop: "block" },
62751
62766
  model_policy: {
@@ -62765,7 +62780,10 @@ var init_schema4 = __esm(() => {
62765
62780
  id: "implement",
62766
62781
  aliases: ["dev-run"],
62767
62782
  description: "dev-run --mode implement: code edits in worktree",
62768
- artifacts: [{ kind: "worktree-diff", direction: "output", required: true }],
62783
+ artifacts: [
62784
+ { kind: "worktree-diff", direction: "output", required: true },
62785
+ { kind: "task-section", direction: "output", required: true, identity: "Solution" }
62786
+ ],
62769
62787
  reasoning_skill: "sp:code-implementation",
62770
62788
  required_references: [],
62771
62789
  gates: [],
@@ -62789,7 +62807,10 @@ var init_schema4 = __esm(() => {
62789
62807
  id: "test",
62790
62808
  aliases: ["dev-unit", "dev-fixall"],
62791
62809
  description: "dev-unit: generate/extend tests to coverage target",
62792
- artifacts: [{ kind: "test-file", direction: "output", required: true }],
62810
+ artifacts: [
62811
+ { kind: "test-file", direction: "output", required: true },
62812
+ { kind: "coverage-report", direction: "output", required: false }
62813
+ ],
62793
62814
  reasoning_skill: "sp:code-testing",
62794
62815
  required_references: [],
62795
62816
  gates: [],
@@ -62812,10 +62833,16 @@ var init_schema4 = __esm(() => {
62812
62833
  id: "verify",
62813
62834
  aliases: ["dev-verify"],
62814
62835
  description: "dev-verify: SECUA review + requirements traceability",
62815
- artifacts: [{ kind: "verdict-artifact", direction: "output", required: true }],
62836
+ artifacts: [
62837
+ { kind: "worktree-diff", direction: "input", required: true },
62838
+ { kind: "verdict-artifact", direction: "output", required: true, identity: "<wbs>-verdict.json" }
62839
+ ],
62816
62840
  reasoning_skill: "sp:code-verification",
62817
62841
  required_references: [],
62818
- gates: [],
62842
+ gates: [
62843
+ { name: "verdict-artifact", timing: "post", min_verdict: "pass" },
62844
+ { name: "strict-core", timing: "post", min_verdict: "pass" }
62845
+ ],
62819
62846
  mutation_class: "verdict",
62820
62847
  retry: { max_attempts: 2, terminal_stop: "escalate" },
62821
62848
  model_policy: {
@@ -62838,7 +62865,7 @@ var init_schema4 = __esm(() => {
62838
62865
  artifacts: [{ kind: "learning-entry", direction: "output", required: true }],
62839
62866
  reasoning_skill: "sp:spur-dev",
62840
62867
  required_references: [],
62841
- gates: [],
62868
+ gates: [{ name: "task-check", timing: "pre", min_verdict: "pass" }],
62842
62869
  mutation_class: "learnings",
62843
62870
  retry: { max_attempts: 2, terminal_stop: "block" },
62844
62871
  model_policy: {
@@ -62851,14 +62878,14 @@ var init_schema4 = __esm(() => {
62851
62878
  },
62852
62879
  context_layers: [],
62853
62880
  observability: [],
62854
- execution: { kind: "inline", current_agent_allowed: true }
62881
+ execution: { kind: "hitl", current_agent_allowed: true, gate_timing: "both" }
62855
62882
  },
62856
62883
  {
62857
62884
  schema_version: STAGE_REGISTRY_SCHEMA_VERSION,
62858
62885
  id: "review",
62859
62886
  aliases: ["dev-review"],
62860
- description: "dev-review: multi-dimensional code review",
62861
- artifacts: [{ kind: "review-findings", direction: "output", required: true }],
62887
+ description: "dev-review: multi-dimensional code review (coordinator writes the combined Review)",
62888
+ artifacts: [{ kind: "review-findings", direction: "output", required: true, identity: "Review" }],
62862
62889
  reasoning_skill: "sp:code-verification",
62863
62890
  required_references: [],
62864
62891
  gates: [],
@@ -62876,12 +62903,41 @@ var init_schema4 = __esm(() => {
62876
62903
  observability: [],
62877
62904
  execution: { kind: "inline", current_agent_allowed: true }
62878
62905
  },
62906
+ {
62907
+ schema_version: STAGE_REGISTRY_SCHEMA_VERSION,
62908
+ id: "record",
62909
+ aliases: ["dev-record"],
62910
+ description: "record: deterministic Testing write-back from the verdict artifact; bare-Review fallback only",
62911
+ artifacts: [
62912
+ { kind: "verdict-artifact", direction: "input", required: true, identity: "<wbs>-verdict.json" },
62913
+ { kind: "task-section", direction: "output", required: true, identity: "Testing" },
62914
+ {
62915
+ kind: "task-section",
62916
+ direction: "output",
62917
+ required: false,
62918
+ identity: "Review",
62919
+ description: "fallback-only: backfills Review only when the section is bare; never overwrites authored Review"
62920
+ }
62921
+ ],
62922
+ reasoning_skill: "inline",
62923
+ required_references: [],
62924
+ gates: [],
62925
+ mutation_class: "corpus",
62926
+ retry: { max_attempts: 1, terminal_stop: "block" },
62927
+ model_policy: { min_tier: "cheap", fallback: [] },
62928
+ context_layers: [],
62929
+ observability: [],
62930
+ execution: { kind: "deterministic", current_agent_allowed: false, executor: "cli" }
62931
+ },
62879
62932
  {
62880
62933
  schema_version: STAGE_REGISTRY_SCHEMA_VERSION,
62881
62934
  id: "dogfood",
62882
62935
  aliases: ["dev-dogfood"],
62883
62936
  description: "dev-dogfood: end-to-end driver test",
62884
- artifacts: [{ kind: "dogfood-report", direction: "output", required: true }],
62937
+ artifacts: [
62938
+ { kind: "dogfood-report", direction: "output", required: true },
62939
+ { kind: "monitor-ledger", direction: "output", required: false }
62940
+ ],
62885
62941
  reasoning_skill: "sp:dogfood-testing",
62886
62942
  required_references: [],
62887
62943
  gates: [],
@@ -67031,6 +67087,142 @@ var init_anchor_qualifier = __esm(() => {
67031
67087
  ANCHOR_RE = /`([^`\n]+?):(\d+)(?:-(\d+))?`/g;
67032
67088
  });
67033
67089
 
67090
+ // ../../packages/app/src/services/verify-verdict.ts
67091
+ function parseVerifyVerdict(raw, fallbackWbs) {
67092
+ const wbs = fallbackWbs ?? "";
67093
+ if (raw.trim() === "")
67094
+ return { kind: "missing", wbs };
67095
+ let parsed;
67096
+ try {
67097
+ parsed = JSON.parse(raw);
67098
+ } catch (err) {
67099
+ return { kind: "malformed", wbs, message: err.message };
67100
+ }
67101
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
67102
+ return { kind: "invalid", wbs, reason: "root must be a JSON object" };
67103
+ }
67104
+ const result = verifyVerdictSchema.safeParse(parsed);
67105
+ if (!result.success) {
67106
+ const issues = result.error.issues.map((i2) => `${i2.path.join(".")}: ${i2.message}`);
67107
+ return { kind: "invalid", wbs, reason: issues.join("; "), issues };
67108
+ }
67109
+ const verdict = { ...result.data, wbs: result.data.wbs !== "" ? result.data.wbs : wbs };
67110
+ return { kind: "valid", wbs, verdict };
67111
+ }
67112
+ async function readVerifyVerdict(fs3, path9, fallbackWbs) {
67113
+ let raw;
67114
+ try {
67115
+ raw = await fs3.readFile(path9);
67116
+ } catch {
67117
+ return { kind: "missing", wbs: fallbackWbs ?? "" };
67118
+ }
67119
+ return parseVerifyVerdict(raw, fallbackWbs);
67120
+ }
67121
+ function checkRowName(row) {
67122
+ for (const v of [row.name, row.check, row.id]) {
67123
+ if (typeof v === "string" && v.trim() !== "")
67124
+ return v.trim();
67125
+ }
67126
+ return "";
67127
+ }
67128
+ function isTaskCheckRow(row) {
67129
+ return /task[ _-]?check/i.test(checkRowName(row));
67130
+ }
67131
+ function aggregateVerifyVerdict(input) {
67132
+ const reqs = Array.isArray(input.requirements) ? input.requirements : [];
67133
+ const acs = Array.isArray(input.acceptanceCriteria) ? input.acceptanceCriteria : [];
67134
+ const checks4 = Array.isArray(input.checks) ? input.checks : [];
67135
+ if (reqs.length === 0 && acs.length === 0)
67136
+ return "UNKNOWN";
67137
+ if (reqs.some((r) => NORM(r.status) === "UNMET") || acs.some((a2) => NORM(a2.status) === "UNMET"))
67138
+ return "FAIL";
67139
+ let majorBlocked = false;
67140
+ for (const c3 of checks4) {
67141
+ if (isTaskCheckRow(c3))
67142
+ continue;
67143
+ const status = NORM(c3.status);
67144
+ if (status === "PASS" || status === "")
67145
+ continue;
67146
+ const severity = NORM(c3.severity);
67147
+ if (severity === "BLOCKER" || severity === "" && status === "FAIL")
67148
+ return "FAIL";
67149
+ if (severity === "MAJOR" || severity === "" && status === "WARN")
67150
+ majorBlocked = true;
67151
+ }
67152
+ if (majorBlocked)
67153
+ return "PARTIAL";
67154
+ if (reqs.some((r) => NORM(r.status) === "PARTIAL") || acs.some((a2) => NORM(a2.status) === "PARTIAL"))
67155
+ return "PARTIAL";
67156
+ if (input.taskCheckPassed === false)
67157
+ return "PARTIAL";
67158
+ return "PASS";
67159
+ }
67160
+ var VERDICT_AGGREGATES, ROW_STATUSES, CHECK_SEVERITIES, verdictSchema, coverageRowSchema, checkSchema, verifyVerdictSchema, NORM = (s2) => String(s2 ?? "").toUpperCase();
67161
+ var init_verify_verdict = __esm(() => {
67162
+ init_zod();
67163
+ VERDICT_AGGREGATES = ["PASS", "PARTIAL", "FAIL", "UNKNOWN"];
67164
+ ROW_STATUSES = ["MET", "PARTIAL", "UNMET", "N/A"];
67165
+ CHECK_SEVERITIES = ["blocker", "major", "minor", "advisory"];
67166
+ verdictSchema = exports_external.string().transform((v, ctx) => {
67167
+ const up = v.toUpperCase();
67168
+ if (!VERDICT_AGGREGATES.includes(up)) {
67169
+ ctx.addIssue({ code: "custom", message: `invalid verdict "${v}" (expected PASS|PARTIAL|FAIL|UNKNOWN)` });
67170
+ return "UNKNOWN";
67171
+ }
67172
+ return up;
67173
+ });
67174
+ coverageRowSchema = exports_external.object({
67175
+ id: exports_external.string().optional(),
67176
+ scenario: exports_external.string().optional(),
67177
+ status: exports_external.string(),
67178
+ evidenceType: exports_external.string().optional(),
67179
+ evidence: exports_external.string().optional()
67180
+ }).superRefine((r, ctx) => {
67181
+ if (r.id !== undefined && r.scenario !== undefined && r.id !== r.scenario) {
67182
+ ctx.addIssue({
67183
+ code: "custom",
67184
+ message: `id/scenario conflict ("${r.id}" vs "${r.scenario}")`,
67185
+ path: ["scenario"]
67186
+ });
67187
+ }
67188
+ if (!ROW_STATUSES.includes(r.status.toUpperCase())) {
67189
+ ctx.addIssue({ code: "custom", message: `invalid row status "${r.status}"`, path: ["status"] });
67190
+ }
67191
+ }).transform((r) => ({
67192
+ id: r.id ?? r.scenario ?? "",
67193
+ status: r.status.toUpperCase(),
67194
+ evidenceType: r.evidenceType ?? "",
67195
+ evidence: r.evidence ?? ""
67196
+ }));
67197
+ checkSchema = exports_external.object({
67198
+ name: exports_external.string().optional(),
67199
+ check: exports_external.string().optional(),
67200
+ id: exports_external.string().optional(),
67201
+ status: exports_external.string(),
67202
+ evidence: exports_external.string().optional().default(""),
67203
+ severity: exports_external.enum(CHECK_SEVERITIES).optional()
67204
+ }).superRefine((c3, ctx) => {
67205
+ if (c3.name === undefined && c3.check === undefined && c3.id === undefined) {
67206
+ ctx.addIssue({ code: "custom", message: "check row needs one of name/check/id", path: ["name"] });
67207
+ }
67208
+ }).transform((c3) => ({
67209
+ name: c3.name ?? c3.check ?? c3.id ?? "",
67210
+ status: c3.status,
67211
+ evidence: c3.evidence,
67212
+ severity: c3.severity
67213
+ }));
67214
+ verifyVerdictSchema = exports_external.object({
67215
+ wbs: exports_external.string().optional().default(""),
67216
+ verdict: verdictSchema,
67217
+ requirements: exports_external.array(coverageRowSchema).optional().default([]),
67218
+ acceptanceCriteria: exports_external.array(coverageRowSchema).optional().default([]),
67219
+ checks: exports_external.array(checkSchema).optional().default([]),
67220
+ source: exports_external.string().optional(),
67221
+ pipelineRunId: exports_external.string().optional(),
67222
+ recordedAt: exports_external.string().optional()
67223
+ });
67224
+ });
67225
+
67034
67226
  // ../../packages/app/src/services/done-transition-guard.ts
67035
67227
  async function readVerdictArtifact(fs3, runDir, wbs) {
67036
67228
  const path9 = `${runDir}/${wbs}-verdict.json`;
@@ -67066,13 +67258,14 @@ function computeAggregate(artifact) {
67066
67258
  if (reqs.length === 0 && acs.length === 0) {
67067
67259
  return artifact.verdict;
67068
67260
  }
67069
- if (reqs.some((r) => r.status === "UNMET") || acs.some((a2) => a2.status === "UNMET")) {
67070
- return "FAIL";
67071
- }
67072
- if (reqs.some((r) => r.status === "PARTIAL") || acs.some((a2) => a2.status === "PARTIAL")) {
67073
- return "PARTIAL";
67074
- }
67075
- return "PASS";
67261
+ const taskCheck = (artifact.checks ?? []).find((c3) => /task[ _-]?check/i.test(checkRowName(c3)));
67262
+ const taskCheckPassed = taskCheck === undefined ? true : String(taskCheck.status).toLowerCase() !== "fail";
67263
+ return aggregateVerifyVerdict({
67264
+ requirements: reqs,
67265
+ acceptanceCriteria: acs,
67266
+ checks: artifact.checks ?? [],
67267
+ taskCheckPassed
67268
+ });
67076
67269
  }
67077
67270
  function formatDenialMessage(args) {
67078
67271
  const { wbs, taskFilePath, verdictPath, verdict, inconsistency, artifact } = args;
@@ -67122,7 +67315,10 @@ function evaluateDoneTransition(input) {
67122
67315
  };
67123
67316
  }
67124
67317
  const computed = computeAggregate(artifact);
67125
- const effective = harshnessMax(artifact.verdict, computed);
67318
+ const reqs = artifact.requirements ?? [];
67319
+ const acs = artifact.acceptanceCriteria ?? [];
67320
+ const internallyConsistentPass = artifact.verdict !== "PASS" || reqs.length > 0 || acs.length > 0;
67321
+ const effective = internallyConsistentPass ? harshnessMax(artifact.verdict, computed) : "UNKNOWN";
67126
67322
  if (effective === "PASS") {
67127
67323
  return { kind: "allow", reason: "pass" };
67128
67324
  }
@@ -67145,6 +67341,9 @@ function harshnessMax(a2, b) {
67145
67341
  const rank = { PASS: 0, UNKNOWN: 1, PARTIAL: 2, FAIL: 3 };
67146
67342
  return rank[a2] >= rank[b] ? a2 : b;
67147
67343
  }
67344
+ var init_done_transition_guard = __esm(() => {
67345
+ init_verify_verdict();
67346
+ });
67148
67347
 
67149
67348
  // ../../packages/app/src/services/finding-codes.ts
67150
67349
  var init_finding_codes2 = __esm(() => {
@@ -67152,6 +67351,10 @@ var init_finding_codes2 = __esm(() => {
67152
67351
  });
67153
67352
 
67154
67353
  // ../../packages/app/src/services/planning-check-base.ts
67354
+ function key(e) {
67355
+ return `${e.kind}:${e.id}:${e.code}`;
67356
+ }
67357
+
67155
67358
  class PlanningCheckService {
67156
67359
  fs;
67157
67360
  matrix;
@@ -67244,7 +67447,7 @@ class PlanningCheckService {
67244
67447
  }
67245
67448
  }
67246
67449
  }
67247
- summarizeWithStatus(status, findings, strict, overrides) {
67450
+ summarizeWithStatus(status, findings, strict, overrides, accepted, id) {
67248
67451
  const effectiveFindings = [];
67249
67452
  for (const f of findings) {
67250
67453
  const override = overrides?.[f.code];
@@ -67257,6 +67460,13 @@ class PlanningCheckService {
67257
67460
  if (strict && f.severity === "warning") {
67258
67461
  f.severity = "error";
67259
67462
  }
67463
+ if (accepted && id) {
67464
+ const k = key({ kind: this.docKind, id, code: f.code });
67465
+ const acceptedSev = accepted.get(k);
67466
+ if (acceptedSev !== undefined && acceptedSev === f.severity) {
67467
+ continue;
67468
+ }
67469
+ }
67260
67470
  effectiveFindings.push(f);
67261
67471
  }
67262
67472
  let hasError = false;
@@ -67411,6 +67621,7 @@ function verdictRowsMatchScenarios(rows, ac) {
67411
67621
  var DEFAULT_FEATURE_MATRIX, FeatureCheckService;
67412
67622
  var init_feature_check = __esm(() => {
67413
67623
  init_src2();
67624
+ init_done_transition_guard();
67414
67625
  init_planning_check_base();
67415
67626
  DEFAULT_FEATURE_MATRIX = {
67416
67627
  variants: {
@@ -67877,7 +68088,7 @@ class TaskLocator {
67877
68088
  return;
67878
68089
  }
67879
68090
  const folderKeys = source.foldersConfig ? Object.keys(source.foldersConfig.folders) : [];
67880
- this.dirs = [...new Set([source.tasksDir, ...folderKeys.map((key) => source.fs.resolve(key))])];
68091
+ this.dirs = [...new Set([source.tasksDir, ...folderKeys.map((key2) => source.fs.resolve(key2))])];
67881
68092
  }
67882
68093
  static forSingleDir(fs3, dir) {
67883
68094
  return new TaskLocator({ fs: fs3, tasksDir: dir });
@@ -68146,6 +68357,7 @@ var init_task_check = __esm(() => {
68146
68357
  init_src2();
68147
68358
  init_planning_check_base();
68148
68359
  init_task_locator();
68360
+ init_verify_verdict();
68149
68361
  EXTERNAL_EVIDENCE_RE = /`([^`\n]+?)`\s+(?:line|lines?)\s+(\d+)(?:-(\d+))?/g;
68150
68362
  TaskCheckService = class TaskCheckService extends PlanningCheckService {
68151
68363
  locator;
@@ -68171,22 +68383,29 @@ var init_task_check = __esm(() => {
68171
68383
  const findings = [];
68172
68384
  const doc2 = this.runL1(raw, wbs, findings);
68173
68385
  if (doc2 === null) {
68174
- return { wbs, ...this.summarizeWithStatus("", findings, strict, options?.severityOverrides) };
68386
+ return {
68387
+ wbs,
68388
+ ...this.summarizeWithStatus("", findings, strict, options?.severityOverrides, options?.accepted, wbs)
68389
+ };
68175
68390
  }
68176
68391
  const fm = doc2.frontmatterData ?? {};
68177
68392
  const status = fm.status ?? "backlog";
68393
+ const effectiveStatus = options?.asStatus ?? status;
68178
68394
  const variant = fm.template ?? DEFAULT_TASK_VARIANT;
68179
- const entry = this.resolveMatrixEntry(variant, status);
68395
+ const entry = this.resolveMatrixEntry(variant, effectiveStatus);
68180
68396
  this.runL2(doc2, entry, findings);
68181
- this.runL3(doc2, entry, status, findings);
68397
+ this.runL3(doc2, entry, effectiveStatus, findings);
68182
68398
  const tasksDir = dirname9(filePath);
68183
68399
  const featuresDir = join10(dirname9(tasksDir), "features");
68184
- await this.runL4(doc2, fm, status, findings, featuresDir, tasksDir, wbs);
68185
- await this.runL4Rollup(doc2, wbs, status, findings, tasksDir);
68186
- if (status !== "done" && status !== "cancelled") {
68187
- await this.runL4Readiness(doc2, fm, wbs, status, findings, tasksDir);
68400
+ await this.runL4(doc2, fm, effectiveStatus, findings, featuresDir, tasksDir, wbs);
68401
+ await this.runL4Rollup(doc2, wbs, effectiveStatus, findings, tasksDir);
68402
+ if (effectiveStatus !== "done" && effectiveStatus !== "cancelled") {
68403
+ await this.runL4Readiness(doc2, fm, wbs, effectiveStatus, findings, tasksDir);
68188
68404
  }
68189
- return { wbs, ...this.summarizeWithStatus(status, findings, strict, options?.severityOverrides) };
68405
+ return {
68406
+ wbs,
68407
+ ...this.summarizeWithStatus(effectiveStatus, findings, strict, options?.severityOverrides, options?.accepted, wbs)
68408
+ };
68190
68409
  }
68191
68410
  runL3(doc2, entry, status, findings) {
68192
68411
  const reqBodyRaw = doc2.getSection("Requirements");
@@ -68846,22 +69065,33 @@ var init_task_check = __esm(() => {
68846
69065
  async checkVerdictArtifact(wbs, tasksDir, findings) {
68847
69066
  const projectRoot = resolveProjectRootFromTasksDir(tasksDir);
68848
69067
  const runDir = join10(projectRoot, ".spur", "run");
68849
- const loaded = await readVerdictArtifact(this.fs, runDir, wbs);
68850
- if (loaded.artifact === undefined) {
68851
- if (loaded.readError && loaded.readError !== "artifact is missing") {
68852
- findings.push({
68853
- layer: "L4",
68854
- code: FINDING_CODES.L4_MALFORMED_VERDICT_ARTIFACT,
68855
- severity: "error",
68856
- section: "Testing",
68857
- message: `Verdict artifact at ${loaded.path} is malformed: ${loaded.readError}`
68858
- });
68859
- }
69068
+ const verdictPath = `${runDir}/${wbs}-verdict.json`;
69069
+ const outcome = await readVerifyVerdict(this.fs, verdictPath, wbs);
69070
+ if (outcome.kind === "missing")
69071
+ return;
69072
+ if (outcome.kind === "malformed") {
69073
+ findings.push({
69074
+ layer: "L4",
69075
+ code: FINDING_CODES.L4_MALFORMED_VERDICT_ARTIFACT,
69076
+ severity: "error",
69077
+ section: "Testing",
69078
+ message: `Verdict artifact at ${verdictPath} is malformed: ${outcome.message}`
69079
+ });
69080
+ return;
69081
+ }
69082
+ if (outcome.kind === "invalid") {
69083
+ findings.push({
69084
+ layer: "L4",
69085
+ code: FINDING_CODES.L4_MALFORMED_VERDICT_ARTIFACT,
69086
+ severity: "error",
69087
+ section: "Testing",
69088
+ message: `Verdict artifact at ${verdictPath} is invalid: ${outcome.reason}`
69089
+ });
68860
69090
  return;
68861
69091
  }
68862
- const artifact = loaded.artifact;
68863
- const reqs = artifact.requirements ?? [];
68864
- const acs = artifact.acceptanceCriteria ?? [];
69092
+ const artifact = outcome.verdict;
69093
+ const reqs = artifact.requirements;
69094
+ const acs = artifact.acceptanceCriteria;
68865
69095
  const isUnknown = artifact.verdict === "UNKNOWN";
68866
69096
  const isEmpty = reqs.length === 0 && acs.length === 0;
68867
69097
  if (isUnknown || isEmpty) {
@@ -68871,7 +69101,7 @@ var init_task_check = __esm(() => {
68871
69101
  code: FINDING_CODES.L4_MALFORMED_VERDICT_ARTIFACT,
68872
69102
  severity: "error",
68873
69103
  section: "Testing",
68874
- message: `Verdict artifact at ${loaded.path} is malformed: ${reason}`
69104
+ message: `Verdict artifact at ${verdictPath} is malformed: ${reason}`
68875
69105
  });
68876
69106
  }
68877
69107
  }
@@ -68883,9 +69113,6 @@ import { basename as basename4, dirname as dirname10, join as join11, relative,
68883
69113
  function baselineSeverity(e) {
68884
69114
  return e.severity ?? "error";
68885
69115
  }
68886
- function key(e) {
68887
- return `${e.kind}:${e.id}:${e.code}`;
68888
- }
68889
69116
  function resolveProjectRoot(cwd) {
68890
69117
  const fs3 = createNodeFileSystem3(cwd);
68891
69118
  let current = resolve3(cwd);
@@ -69253,12 +69480,29 @@ async function runCorpusCheck(cwd, since) {
69253
69480
  }
69254
69481
  return result;
69255
69482
  }
69483
+ async function loadAcceptedFindings(cwd) {
69484
+ const projectRoot = resolveProjectRoot(cwd);
69485
+ const baselineFile = join11(projectRoot, "config", "corpus-baseline.json");
69486
+ const accepted = new Map;
69487
+ try {
69488
+ if (await Bun.file(baselineFile).exists()) {
69489
+ const baseline = await Bun.file(baselineFile).json();
69490
+ if (Array.isArray(baseline?.entries)) {
69491
+ for (const e of baseline.entries) {
69492
+ accepted.set(key(e), baselineSeverity(e));
69493
+ }
69494
+ }
69495
+ }
69496
+ } catch {}
69497
+ return accepted;
69498
+ }
69256
69499
  var FOG_HEADING, OUT_OF_SCOPE_HEADING, FEATURE_ID, DEFAULT_BRANCHES;
69257
69500
  var init_corpus_check = __esm(() => {
69258
69501
  init_loader();
69259
69502
  init_dist5();
69260
69503
  init_dist2();
69261
69504
  init_feature_check();
69505
+ init_planning_check_base();
69262
69506
  init_task_check();
69263
69507
  init_task_locator();
69264
69508
  FOG_HEADING = /^###\s+Not yet specified\b/;
@@ -74731,78 +74975,6 @@ var init_task_size_precheck = __esm(() => {
74731
74975
 
74732
74976
  // ../../packages/app/src/services/task-service.ts
74733
74977
  import { dirname as dirname14, isAbsolute as isAbsolute4, join as join16, relative as relative4 } from "path";
74734
- function patchFrontmatterField(rendered, key2, value) {
74735
- const openIdx = rendered.indexOf("---");
74736
- if (openIdx === -1)
74737
- return rendered;
74738
- let fmStart = openIdx + 3;
74739
- if (rendered[fmStart] === "\r")
74740
- fmStart += 1;
74741
- if (rendered[fmStart] === `
74742
- `)
74743
- fmStart += 1;
74744
- const closeRel = rendered.indexOf(`
74745
- ---`, fmStart);
74746
- if (closeRel === -1)
74747
- return rendered;
74748
- const before = rendered.slice(0, fmStart);
74749
- const fm = rendered.slice(fmStart, closeRel);
74750
- const after = rendered.slice(closeRel);
74751
- const existingRe = new RegExp(`^${escapeRegex2(key2)}:.*$`, "m");
74752
- if (existingRe.test(fm)) {
74753
- const newFm = fm.replace(existingRe, () => `${key2}: ${value}`);
74754
- return before + newFm + after;
74755
- }
74756
- return `${before}${key2}: ${value}
74757
- ${fm}${after}`;
74758
- }
74759
- function escapeRegex2(s2) {
74760
- return s2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
74761
- }
74762
- function renderCreatedTaskContent(params) {
74763
- let content = renderTaskTemplate(params.rawTemplate, {
74764
- NAME: params.name,
74765
- WBS: params.wbs,
74766
- BACKGROUND: params.background,
74767
- CREATED_AT: params.createdAt,
74768
- ...params.featureId !== undefined ? { FEATURE_ID: params.featureId } : {}
74769
- });
74770
- content = patchFrontmatterField(content, "status", params.status);
74771
- content = patchFrontmatterField(content, "template", params.variant);
74772
- if (params.featureId !== undefined) {
74773
- content = patchFrontmatterField(content, "feature_id", escapeYamlValue(params.featureId));
74774
- }
74775
- if (params.parentWbs !== undefined) {
74776
- content = patchFrontmatterField(content, "parent_wbs", escapeYamlValue(params.parentWbs));
74777
- }
74778
- if (params.priority !== undefined) {
74779
- content = patchFrontmatterField(content, "priority", params.priority);
74780
- }
74781
- if (params.tags !== undefined && params.tags.length > 0) {
74782
- content = patchFrontmatterField(content, "tags", `[${params.tags.map((tag) => JSON.stringify(tag)).join(", ")}]`);
74783
- }
74784
- const sectionPatches = {};
74785
- if ((params.requirements ?? "").trim() !== "") {
74786
- sectionPatches.Requirements = bulletizeRequirements(params.requirements ?? "");
74787
- }
74788
- if ((params.design ?? "").trim() !== "") {
74789
- sectionPatches.Design = (params.design ?? "").trim();
74790
- }
74791
- if ((params.plan ?? "").trim() !== "") {
74792
- sectionPatches.Plan = (params.plan ?? "").trim();
74793
- }
74794
- if ((params.acceptanceCriteria ?? "").trim() !== "") {
74795
- sectionPatches["Acceptance Criteria"] = normalizeAcFence((params.acceptanceCriteria ?? "").trim());
74796
- }
74797
- if (Object.keys(sectionPatches).length > 0) {
74798
- const doc2 = MarkdownDocument.parse(content, "task");
74799
- for (const [section, body] of Object.entries(sectionPatches)) {
74800
- doc2.replaceSection(section, body);
74801
- }
74802
- content = doc2.serialize();
74803
- }
74804
- return content;
74805
- }
74806
74978
  function renderRosterTable(rows) {
74807
74979
  const header = `| WBS | Sub-task | Status |
74808
74980
  | --- | -------- | ------ |`;
@@ -74868,12 +75040,14 @@ class TaskService {
74868
75040
  }
74869
75041
  sectionsForStatus(variant, status) {
74870
75042
  const matrix = this.ctx.sectionMatrix;
74871
- const entry = matrix?.variants[variant]?.[status] ?? matrix?.variants.standard?.[status];
74872
- if (entry !== undefined) {
74873
- return [...entry.required ?? [], ...entry.optional ?? [], "History"];
75043
+ if (matrix === undefined) {
75044
+ throw new Error(`no section-matrix available for create (variant=${variant}, status=${status}); ` + "a canonical section-matrix.yaml is required for task creation (F92 R1)");
74874
75045
  }
74875
- const fallback = DEFAULT_CREATION_SECTIONS[status] ?? ["Background"];
74876
- return [...fallback, "History"];
75046
+ const entry = matrix.variants[variant]?.[status] ?? matrix.variants.standard?.[status];
75047
+ if (entry === undefined) {
75048
+ throw new Error(`no section-matrix entry for variant="${variant}" status="${status}" during create; ` + "resolve the canonical section-matrix.yaml");
75049
+ }
75050
+ return [...entry.required ?? [], ...entry.optional ?? [], "References", "History"];
74877
75051
  }
74878
75052
  bodiesFor(variant, taskBodies) {
74879
75053
  const templateBodies = this.ctx.resolveTemplateBodies?.(variant) ?? {};
@@ -74918,22 +75092,6 @@ class TaskService {
74918
75092
  const slug = this.slugify(params.title);
74919
75093
  const { wbs, filePath } = await this.allocateWbsChecked(slug);
74920
75094
  const now2 = new Date().toISOString();
74921
- const rawTemplate = this.ctx.resolveTemplate?.(variant);
74922
- if (rawTemplate !== undefined) {
74923
- const content2 = renderCreatedTaskContent({
74924
- rawTemplate,
74925
- name: params.title,
74926
- wbs,
74927
- background,
74928
- createdAt: now2,
74929
- status,
74930
- variant,
74931
- featureId: params.featureId,
74932
- parentWbs: params.parentWbs
74933
- });
74934
- const ref2 = { kind: "task", id: wbs, filePath, folder };
74935
- return { ref: ref2, content: content2 };
74936
- }
74937
75095
  const frontmatter = [
74938
75096
  "schema_version: 1",
74939
75097
  `name: "${params.title}"`,
@@ -75431,28 +75589,6 @@ ${issues}`);
75431
75589
  const slug = this.slugify(item.name);
75432
75590
  const { wbs, filePath } = await this.allocateWbsChecked(slug);
75433
75591
  const now2 = new Date().toISOString();
75434
- const rawTemplate = this.ctx.resolveTemplate?.(variant);
75435
- if (rawTemplate !== undefined) {
75436
- const content2 = renderCreatedTaskContent({
75437
- rawTemplate,
75438
- name: item.name,
75439
- wbs,
75440
- background,
75441
- createdAt: now2,
75442
- status,
75443
- variant,
75444
- featureId: item.feature_id ?? undefined,
75445
- parentWbs: item.parent_wbs ?? undefined,
75446
- priority: item.priority,
75447
- tags: item.tags,
75448
- requirements: item.requirements,
75449
- design: item.design,
75450
- plan: item.plan,
75451
- acceptanceCriteria: item.acceptance_criteria
75452
- });
75453
- const ref2 = { kind: "task", id: wbs, filePath, folder };
75454
- return { ref: ref2, content: content2 };
75455
- }
75456
75592
  const fmLines = [
75457
75593
  "schema_version: 1",
75458
75594
  `name: "${item.name}"`,
@@ -75676,7 +75812,7 @@ ${block}` : block);
75676
75812
  return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60);
75677
75813
  }
75678
75814
  }
75679
- var DependencyMutationError, SectionMutationError, DuplicateFollowUpError, WbsCollisionError, ROSTER_START = "<!-- AUTO-GENERATED by spur task refresh-roster -->", ROSTER_END = "<!-- END AUTO-GENERATED -->", ROSTER_REGION_RE, TASK_ACTION_COMMANDS, DEFAULT_CREATION_SECTIONS;
75815
+ var DependencyMutationError, SectionMutationError, DuplicateFollowUpError, WbsCollisionError, ROSTER_START = "<!-- AUTO-GENERATED by spur task refresh-roster -->", ROSTER_END = "<!-- END AUTO-GENERATED -->", ROSTER_REGION_RE, TASK_ACTION_COMMANDS;
75680
75816
  var init_task_service = __esm(() => {
75681
75817
  init_src2();
75682
75818
  init_errors7();
@@ -75734,10 +75870,6 @@ var init_task_service = __esm(() => {
75734
75870
  decompose: (wbs) => `/sp:dev-plan "Decompose task ${wbs} into implementation subtasks" --auto`,
75735
75871
  evaluate: (wbs) => `/sp:dev-review ${wbs} --auto`
75736
75872
  };
75737
- DEFAULT_CREATION_SECTIONS = {
75738
- backlog: ["Background"],
75739
- todo: ["Background", "Requirements", "Acceptance Criteria", "Q&A", "Design", "Plan"]
75740
- };
75741
75873
  });
75742
75874
 
75743
75875
  // ../../packages/app/src/services/task-verdict.ts
@@ -75749,20 +75881,13 @@ function deriveVerdict(answerText, taskCheckPassed) {
75749
75881
  if (requirements.length === 0) {
75750
75882
  return { verdict: "UNKNOWN", requirements, acceptanceCriteria, checks: checks4 };
75751
75883
  }
75752
- const hasUnmet = requirements.some((r) => r.status === "UNMET");
75753
- const hasUnmetAc = acceptanceCriteria.some((ac) => ac.status === "UNMET");
75754
- if (hasUnmet || hasUnmetAc) {
75755
- return { verdict: "FAIL", requirements, acceptanceCriteria, checks: checks4 };
75756
- }
75757
- const hasPartial = requirements.some((r) => r.status === "PARTIAL");
75758
- const hasPartialAc = acceptanceCriteria.some((ac) => ac.status === "PARTIAL");
75759
- if (hasPartial || hasPartialAc) {
75760
- return { verdict: "PARTIAL", requirements, acceptanceCriteria, checks: checks4 };
75761
- }
75762
- if (!taskCheckPassed) {
75763
- return { verdict: "PARTIAL", requirements, acceptanceCriteria, checks: checks4 };
75764
- }
75765
- return { verdict: "PASS", requirements, acceptanceCriteria, checks: checks4 };
75884
+ const aggregate2 = aggregateVerifyVerdict({
75885
+ requirements,
75886
+ acceptanceCriteria,
75887
+ checks: [],
75888
+ taskCheckPassed
75889
+ });
75890
+ return { verdict: aggregate2, requirements, acceptanceCriteria, checks: checks4 };
75766
75891
  }
75767
75892
  function extractRequirements(text4) {
75768
75893
  const reqs = [];
@@ -75774,7 +75899,7 @@ function extractRequirements(text4) {
75774
75899
  const trimmed = line.trim();
75775
75900
  if (!trimmed.startsWith("|"))
75776
75901
  continue;
75777
- const cells = trimmed.split("|").map((c3) => c3.trim()).filter(Boolean);
75902
+ const cells = splitTableCells(trimmed);
75778
75903
  if (!inTable && cells.length >= 2) {
75779
75904
  const h0 = (cells[0] ?? "").toLowerCase().trim();
75780
75905
  const h0IsId = h0.includes("req") || h0 === "requirement" || h0 === "r#" || h0 === "r" || /^r\d+$/.test(h0);
@@ -75825,6 +75950,9 @@ function normalizeStatus(raw) {
75825
75950
  return "UNMET";
75826
75951
  return null;
75827
75952
  }
75953
+ function splitTableCells(row) {
75954
+ return row.split(/(?<!\\)\|/).map((c3) => c3.replace(/\\\|/g, "|").trim()).filter(Boolean);
75955
+ }
75828
75956
  function extractAcceptanceCriteria(text4) {
75829
75957
  const rows = [];
75830
75958
  const dropped = [];
@@ -75833,9 +75961,13 @@ function extractAcceptanceCriteria(text4) {
75833
75961
  let inTable = false;
75834
75962
  for (const line of lines) {
75835
75963
  const trimmed = line.trim();
75964
+ if (inTable && /^#{1,6}\s/.test(trimmed)) {
75965
+ inTable = false;
75966
+ continue;
75967
+ }
75836
75968
  if (!trimmed.startsWith("|"))
75837
75969
  continue;
75838
- const cells = trimmed.split("|").map((c3) => c3.trim()).filter(Boolean);
75970
+ const cells = splitTableCells(trimmed);
75839
75971
  if (!inTable && cells.length >= 4) {
75840
75972
  const h0 = (cells[0] ?? "").toLowerCase();
75841
75973
  const h1 = (cells[1] ?? "").toLowerCase();
@@ -75931,7 +76063,7 @@ function extractChecks(_text, taskCheckPassed, acceptanceCriteria, droppedAcRows
75931
76063
  const trimmed = line.trim();
75932
76064
  if (!trimmed.startsWith("|"))
75933
76065
  continue;
75934
- const cells = trimmed.split("|").map((c3) => c3.trim()).filter(Boolean);
76066
+ const cells = splitTableCells(trimmed);
75935
76067
  if (cells.length >= 2) {
75936
76068
  const h0 = (cells[0] ?? "").toLowerCase();
75937
76069
  const h1 = (cells[1] ?? "").toLowerCase();
@@ -75999,6 +76131,7 @@ function aggregateBatchVerdicts(results) {
75999
76131
  }
76000
76132
  var EVIDENCE_TYPE_PRECEDENCE, NOT_STARTED_STATUSES;
76001
76133
  var init_task_verdict = __esm(() => {
76134
+ init_verify_verdict();
76002
76135
  EVIDENCE_TYPE_PRECEDENCE = ["test", "command", "static-ref", "manual-review", "llm-judge", "n/a"];
76003
76136
  NOT_STARTED_STATUSES = {
76004
76137
  backlog: true,
@@ -79461,6 +79594,7 @@ __export(exports_src2, {
79461
79594
  parseEtimeToSeconds: () => parseEtimeToSeconds,
79462
79595
  normalizeSystemEventPayload: () => normalizeSystemEventPayload,
79463
79596
  normalizeProjectPath: () => normalizeProjectPath,
79597
+ loadAcceptedFindings: () => loadAcceptedFindings,
79464
79598
  isSystemEventEnvelopeV2: () => isSystemEventEnvelopeV2,
79465
79599
  isPortLive: () => isPortLive,
79466
79600
  isPortAvailable: () => isPortAvailable,
@@ -79594,6 +79728,7 @@ var init_src3 = __esm(() => {
79594
79728
  init_anchor_qualifier();
79595
79729
  init_corpus_check();
79596
79730
  init_corpus_migrator();
79731
+ init_done_transition_guard();
79597
79732
  init_event_names();
79598
79733
  init_failure_classification();
79599
79734
  init_feature_check();
@@ -87871,7 +88006,7 @@ import { createRequire } from "module";
87871
88006
  var CLI_CONFIG = {
87872
88007
  binaryName: "spur",
87873
88008
  binaryLabel: "spur",
87874
- binaryVersion: "0.3.50",
88009
+ binaryVersion: "0.3.52",
87875
88010
  configDir: ".spur",
87876
88011
  configFile: ".spur/config.yaml",
87877
88012
  databaseFile: ".spur/spur.db"
@@ -98238,7 +98373,8 @@ function createServerContext(appRt, options) {
98238
98373
  writeService: new PlanningWriteService({ fs: fs3, projectName: "spur", emitter: lazyEmitter }),
98239
98374
  tasksDir: folders.tasksDir,
98240
98375
  foldersConfig: folders.foldersConfig,
98241
- projectName: "spur"
98376
+ projectName: "spur",
98377
+ ...options.sectionMatrix !== undefined ? { sectionMatrix: options.sectionMatrix } : {}
98242
98378
  });
98243
98379
  }
98244
98380
  return taskSvc;
@@ -98418,6 +98554,27 @@ async function openUrl(url2, deps = {}) {
98418
98554
 
98419
98555
  // ../server/src/serve.ts
98420
98556
  init_src3();
98557
+ async function loadServerSectionMatrix() {
98558
+ const cwd = process.cwd();
98559
+ const nodeFs = createNodeFileSystem3(cwd);
98560
+ const localPath = nodeFs.resolve(".spur", "tasks", "section-matrix.yaml");
98561
+ if (await nodeFs.exists(localPath)) {
98562
+ return await loadStructuredSpurConfig(localPath, { validateJsonSchema: false });
98563
+ }
98564
+ const root = bundledConfigRoot();
98565
+ if (root !== null) {
98566
+ const matrixPath = join27(root, "tasks", "section-matrix.yaml");
98567
+ if (await nodeFs.exists(matrixPath)) {
98568
+ return await loadStructuredSpurConfig(matrixPath, {
98569
+ validateJsonSchema: false
98570
+ });
98571
+ }
98572
+ }
98573
+ throw new Error(`no canonical section-matrix found for task creation (F92 R1); tried:
98574
+ ` + ` - ${localPath}
98575
+ ` + (root !== null ? ` - ${join27(root, "tasks", "section-matrix.yaml")}
98576
+ ` : "") + "copy/generate section-matrix.yaml from the canonical build-time matrix asset (repo `config` `tasks` tree) into one of those paths");
98577
+ }
98421
98578
  var SYSTEM_EVENTS_PRUNE_JOB = "system-events-prune";
98422
98579
  var SMOKE_JOB = "smoke";
98423
98580
  var TASK_ACTION_JOB = "task-action";
@@ -98589,6 +98746,7 @@ async function startServer(options, deps = defaultDeps) {
98589
98746
  fs: fs3,
98590
98747
  dbUrl: options.dbUrl,
98591
98748
  folders: await resolvePlanningFolders(fs3),
98749
+ sectionMatrix: await loadServerSectionMatrix(),
98592
98750
  webDistPath,
98593
98751
  jobQueueEnabled: bootConfig.jobqueue.enabled,
98594
98752
  scheduler: scheduler3,
@@ -99871,7 +100029,8 @@ ${result.content}`);
99871
100029
  task.command("update").summary("Update a task status or replace a section.").argument("<wbs>", "Task WBS number").argument("[status]", "New status (for lifecycle transition)").addHelpText("after", [
99872
100030
  "Lifecycle: `task update <wbs> <status>` moves a task through",
99873
100031
  "backlog \u2192 todo \u2192 wip \u2192 testing \u2192 done, running the lifecycle guards on",
99874
- "`wip \u2192 testing` (`spur task check`) and `testing \u2192 done` (`--strict-core`).",
100032
+ "`wip \u2192 testing` (`spur task check --as testing`) and `testing \u2192 done`",
100033
+ "(`spur task check --as done`) \u2014 each guard evaluates the transition target (F92 R3).",
99875
100034
  "A GuardDeniedError on `testing \u2192 done` means no pipeline run is recorded for the",
99876
100035
  "task: run `/sp:dev-verify <wbs> --next` to PASS it, or record the audited bypass with",
99877
100036
  '`SPUR_PROVENANCE_OVERRIDE=1 spur task update <wbs> done --force-done --reason "\u2026"`.',
@@ -99913,7 +100072,7 @@ ${result.content}`);
99913
100072
  if (options.lifecycle !== false) {
99914
100073
  context4.output.error(`warning: lifecycle adapter unavailable \u2014 running \`spur task check\` inline as the ${status} gate. ` + "Restore the bundled task-lifecycle workflow to re-enable the real guard.");
99915
100074
  }
99916
- const ok = await runDoneGateCheck(context4, wbs, options.folder);
100075
+ const ok = await runDoneGateCheck(context4, wbs, options.folder, status);
99917
100076
  if (!ok) {
99918
100077
  context4.output.error(`Lifecycle transition blocked: \`spur task check ${wbs}\` failed. Fix the findings before transitioning to ${status}.`);
99919
100078
  context4.setExitCode(1);
@@ -100376,9 +100535,20 @@ ${result.content}`);
100376
100535
  context4.setExitCode(1);
100377
100536
  }
100378
100537
  });
100379
- task.command("check").summary("Validate a task file through the four-layer check (design \xA73).").argument("[wbs]", "Task WBS number (validates all tasks in the folder when omitted)").option("--strict", "Elevate ALL warnings to failures").option("--strict-core", "Gate variant: fail only on hard-core errors (the testing\u2192done guard)").option("--corpus", "Sweep every task and feature against config/corpus-baseline.json").option("--since <ref>", "Scope the corpus fog check to changes since a git ref (requires --corpus)").option("--folder <path>", "Custom tasks folder").option("--json", "Output machine-readable JSON").action(async (wbs, options) => {
100538
+ task.command("check").summary("Validate a task file through the four-layer check (design \xA73).").argument("[wbs]", "Task WBS number (validates all tasks in the folder when omitted)").option("--strict", "Elevate ALL warnings to failures").option("--strict-core", "Compatibility alias (F92 R2): historically the done-gate label; kept so installed plugins/workflows that call it keep working. No longer meaningful on its own \u2014 target-state selection (`--as`) supplies the real done semantics.").option("--as <status>", "Evaluate the task AS if it were in <status> (F92 R2): the lifecycle guards pass the transition target so testing\u2192done checks the done row. Validate against canonical task statuses. Omitted \u2192 current-status diagnostics.").option("--corpus", "Sweep every task and feature against config/corpus-baseline.json").option("--since <ref>", "Scope the corpus fog check to changes since a git ref (requires --corpus)").option("--folder <path>", "Custom tasks folder").option("--json", "Output machine-readable JSON").action(async (wbs, options) => {
100380
100539
  const json3 = options.json === true;
100381
100540
  const strict = options.strict === true;
100541
+ const asStatus = options.as === undefined ? undefined : canonicalStatusOrRaw(options.as);
100542
+ if (options.as !== undefined && !TASK_STATUSES.includes(asStatus ?? "")) {
100543
+ context4.output.error(`invalid --as status "${options.as}" (canonical: ${TASK_STATUSES.join(", ")})`);
100544
+ context4.setExitCode(2);
100545
+ return;
100546
+ }
100547
+ if (asStatus !== undefined && options.corpus === true) {
100548
+ context4.output.error("--as <status> is a single-task target projection and cannot be combined with --corpus");
100549
+ context4.setExitCode(2);
100550
+ return;
100551
+ }
100382
100552
  try {
100383
100553
  if (options.corpus === true) {
100384
100554
  if (wbs !== undefined) {
@@ -100429,6 +100599,7 @@ ${result.content}`);
100429
100599
  }
100430
100600
  const svc = await makeCheckService(context4);
100431
100601
  const planningFolders = await resolvePlanningFolders(context4.fs);
100602
+ const accepted = await loadAcceptedFindings(context4.cwd);
100432
100603
  const activeFolder = planningFolders.foldersConfig.active_folder;
100433
100604
  const tasksDir = context4.fs.resolve(options.folder ?? activeFolder);
100434
100605
  const printResult = (result) => {
@@ -100454,7 +100625,9 @@ ${result.wbs} (${result.status}): ${result.pass ? "PASS" : "FAIL"}`);
100454
100625
  } else {
100455
100626
  const result = await svc.check(hit.filePath, wbs, {
100456
100627
  strict,
100457
- severityOverrides: planningFolders.severityOverrides
100628
+ asStatus,
100629
+ severityOverrides: planningFolders.severityOverrides,
100630
+ accepted
100458
100631
  });
100459
100632
  results.push(result);
100460
100633
  printResult(result);
@@ -100471,7 +100644,9 @@ ${result.wbs} (${result.status}): ${result.pass ? "PASS" : "FAIL"}`);
100471
100644
  }
100472
100645
  const result = await svc.check(`${tasksDir}/${fileName}`, w, {
100473
100646
  strict,
100474
- severityOverrides: planningFolders.severityOverrides
100647
+ asStatus,
100648
+ severityOverrides: planningFolders.severityOverrides,
100649
+ accepted
100475
100650
  });
100476
100651
  results.push(result);
100477
100652
  printResult(result);
@@ -100610,36 +100785,10 @@ async function makeService2(context4, folderOverride, noLifecycle = false) {
100610
100785
  writeService,
100611
100786
  getDb: () => context4.getDb(),
100612
100787
  sectionMatrix: await loadSectionMatrix(context4.cwd),
100613
- resolveTemplate: (variant) => loadTemplateContent(context4.cwd, variant),
100614
100788
  resolveTemplateBodies: (variant) => loadTemplateBodies(context4.cwd, variant),
100615
100789
  foldersConfig
100616
100790
  });
100617
100791
  }
100618
- var templateContentCache = new Map;
100619
- var templateMissSet = new Set;
100620
- function loadTemplateContent(projectRoot, variant) {
100621
- if (templateContentCache.has(variant))
100622
- return templateContentCache.get(variant);
100623
- if (templateMissSet.has(variant))
100624
- return;
100625
- const localPath = join30(projectRoot, ".spur", "tasks", "templates", `${variant}.md`);
100626
- if (existsSync11(localPath)) {
100627
- const content = readFileSync10(localPath, "utf8");
100628
- templateContentCache.set(variant, content);
100629
- return content;
100630
- }
100631
- const root = bundledConfigRoot();
100632
- if (root !== null) {
100633
- const templatePath = join30(root, "templates", "task", `${variant}.md`);
100634
- if (existsSync11(templatePath)) {
100635
- const content = readFileSync10(templatePath, "utf8");
100636
- templateContentCache.set(variant, content);
100637
- return content;
100638
- }
100639
- }
100640
- templateMissSet.add(variant);
100641
- return;
100642
- }
100643
100792
  var templateBodiesCache = new Map;
100644
100793
  function loadTemplateBodies(projectRoot, variant) {
100645
100794
  const cached2 = templateBodiesCache.get(variant);
@@ -100672,15 +100821,22 @@ async function makeTaskLocator(context4) {
100672
100821
  async function makeCheckService(context4) {
100673
100822
  return new TaskCheckService(context4.fs, await loadSectionMatrix(context4.cwd), await makeTaskLocator(context4));
100674
100823
  }
100675
- async function runDoneGateCheck(context4, wbs, folderOverride) {
100676
- const foldersConfig = (await resolvePlanningFolders(context4.fs)).foldersConfig;
100824
+ async function runDoneGateCheck(context4, wbs, folderOverride, targetStatus) {
100825
+ const planningFolders = await resolvePlanningFolders(context4.fs);
100826
+ const foldersConfig = planningFolders.foldersConfig;
100677
100827
  const tasksDir = folderOverride ?? context4.fs.resolve(foldersConfig.active_folder);
100678
100828
  const hit = await new TaskLocator({ fs: context4.fs, tasksDir, foldersConfig }).findByWbs(wbs);
100679
100829
  if (!hit) {
100680
100830
  return false;
100681
100831
  }
100682
100832
  const svc = new TaskCheckService(context4.fs, await loadSectionMatrix(context4.cwd), await makeTaskLocator(context4));
100683
- const result = await svc.check(hit.filePath, wbs, { strict: false });
100833
+ const accepted = await loadAcceptedFindings(context4.cwd);
100834
+ const result = await svc.check(hit.filePath, wbs, {
100835
+ strict: false,
100836
+ asStatus: targetStatus,
100837
+ severityOverrides: planningFolders.severityOverrides,
100838
+ accepted
100839
+ });
100684
100840
  return result.pass;
100685
100841
  }
100686
100842
  var sectionMatrixCache = new Map;
@@ -100714,16 +100870,11 @@ async function loadSectionMatrixUncached(projectRoot) {
100714
100870
  return data;
100715
100871
  }
100716
100872
  }
100717
- return FALLBACK_MATRIX;
100873
+ throw new Error(`no canonical section-matrix found for task section authority (F92 R1); tried:
100874
+ ` + ` - ${localPath}
100875
+ ` + (root !== null ? ` - ${join30(root, "tasks", "section-matrix.yaml")}
100876
+ ` : "") + "copy/generate section-matrix.yaml from the canonical build-time matrix asset (repo `config` `tasks` tree) into one of those paths");
100718
100877
  }
100719
- var FALLBACK_MATRIX = {
100720
- variants: {
100721
- standard: {
100722
- backlog: { required: ["Background"], forbidden: ["Solution", "Review", "Testing"] },
100723
- done: { required: ["Solution", "Testing", "Review"], gate: true }
100724
- }
100725
- }
100726
- };
100727
100878
 
100728
100879
  // src/commands/team.ts
100729
100880
  init_src3();