@gobing-ai/spur 0.3.51 → 0.3.53

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 (28) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/config/corpus-baseline.json +8 -8
  3. package/config/tasks/section-matrix.yaml +7 -2
  4. package/config/workflows/task-lifecycle.yaml +7 -6
  5. package/config/workflows/task-pipeline2.yaml +778 -0
  6. package/package.json +1 -1
  7. package/plugins/sp/agents/super-planner.md +3 -2
  8. package/plugins/sp/agents/super-reviewer.md +5 -0
  9. package/plugins/sp/commands/dev-review.md +1 -1
  10. package/plugins/sp/plugin.json +1 -1
  11. package/plugins/sp/scripts/pr-reviewing.ts +22 -4
  12. package/plugins/sp/scripts/stage-registry-adapter.ts +40 -25
  13. package/plugins/sp/skills/code-improvement/SKILL.md +5 -3
  14. package/plugins/sp/skills/code-verification/SKILL.md +28 -31
  15. package/plugins/sp/skills/code-verification/references/verdict-schema.md +47 -0
  16. package/plugins/sp/skills/functional-review/SKILL.md +16 -15
  17. package/plugins/sp/skills/spec-decomposition/references/decomposition.md +13 -14
  18. package/plugins/sp/skills/spur-cli/references/tasks/l3-guard-cheatsheet.md +7 -3
  19. package/plugins/sp/skills/spur-cli/references/tasks/section-editing.md +22 -13
  20. package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +22 -11
  21. package/plugins/sp/skills/spur-cli/references/tasks.md +16 -10
  22. package/plugins/sp/skills/spur-dev/SKILL.md +10 -5
  23. package/plugins/sp/skills/spur-dev/references/dev-operations.md +5 -5
  24. package/plugins/sp/skills/spur-dev/references/execution-workflow.md +11 -4
  25. package/plugins/sp/skills/spur-dev/references/gate-checklists.md +5 -3
  26. package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +10 -1
  27. package/plugins/sp/skills/spur-dev/references/section-batching.md +29 -13
  28. package/spur.js +337 -226
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(() => {
@@ -67422,6 +67621,7 @@ function verdictRowsMatchScenarios(rows, ac) {
67422
67621
  var DEFAULT_FEATURE_MATRIX, FeatureCheckService;
67423
67622
  var init_feature_check = __esm(() => {
67424
67623
  init_src2();
67624
+ init_done_transition_guard();
67425
67625
  init_planning_check_base();
67426
67626
  DEFAULT_FEATURE_MATRIX = {
67427
67627
  variants: {
@@ -68157,6 +68357,7 @@ var init_task_check = __esm(() => {
68157
68357
  init_src2();
68158
68358
  init_planning_check_base();
68159
68359
  init_task_locator();
68360
+ init_verify_verdict();
68160
68361
  EXTERNAL_EVIDENCE_RE = /`([^`\n]+?)`\s+(?:line|lines?)\s+(\d+)(?:-(\d+))?/g;
68161
68362
  TaskCheckService = class TaskCheckService extends PlanningCheckService {
68162
68363
  locator;
@@ -68189,20 +68390,21 @@ var init_task_check = __esm(() => {
68189
68390
  }
68190
68391
  const fm = doc2.frontmatterData ?? {};
68191
68392
  const status = fm.status ?? "backlog";
68393
+ const effectiveStatus = options?.asStatus ?? status;
68192
68394
  const variant = fm.template ?? DEFAULT_TASK_VARIANT;
68193
- const entry = this.resolveMatrixEntry(variant, status);
68395
+ const entry = this.resolveMatrixEntry(variant, effectiveStatus);
68194
68396
  this.runL2(doc2, entry, findings);
68195
- this.runL3(doc2, entry, status, findings);
68397
+ this.runL3(doc2, entry, effectiveStatus, findings);
68196
68398
  const tasksDir = dirname9(filePath);
68197
68399
  const featuresDir = join10(dirname9(tasksDir), "features");
68198
- await this.runL4(doc2, fm, status, findings, featuresDir, tasksDir, wbs);
68199
- await this.runL4Rollup(doc2, wbs, status, findings, tasksDir);
68200
- if (status !== "done" && status !== "cancelled") {
68201
- 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);
68202
68404
  }
68203
68405
  return {
68204
68406
  wbs,
68205
- ...this.summarizeWithStatus(status, findings, strict, options?.severityOverrides, options?.accepted, wbs)
68407
+ ...this.summarizeWithStatus(effectiveStatus, findings, strict, options?.severityOverrides, options?.accepted, wbs)
68206
68408
  };
68207
68409
  }
68208
68410
  runL3(doc2, entry, status, findings) {
@@ -68863,22 +69065,33 @@ var init_task_check = __esm(() => {
68863
69065
  async checkVerdictArtifact(wbs, tasksDir, findings) {
68864
69066
  const projectRoot = resolveProjectRootFromTasksDir(tasksDir);
68865
69067
  const runDir = join10(projectRoot, ".spur", "run");
68866
- const loaded = await readVerdictArtifact(this.fs, runDir, wbs);
68867
- if (loaded.artifact === undefined) {
68868
- if (loaded.readError && loaded.readError !== "artifact is missing") {
68869
- findings.push({
68870
- layer: "L4",
68871
- code: FINDING_CODES.L4_MALFORMED_VERDICT_ARTIFACT,
68872
- severity: "error",
68873
- section: "Testing",
68874
- message: `Verdict artifact at ${loaded.path} is malformed: ${loaded.readError}`
68875
- });
68876
- }
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
+ });
68877
69080
  return;
68878
69081
  }
68879
- const artifact = loaded.artifact;
68880
- const reqs = artifact.requirements ?? [];
68881
- const acs = artifact.acceptanceCriteria ?? [];
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
+ });
69090
+ return;
69091
+ }
69092
+ const artifact = outcome.verdict;
69093
+ const reqs = artifact.requirements;
69094
+ const acs = artifact.acceptanceCriteria;
68882
69095
  const isUnknown = artifact.verdict === "UNKNOWN";
68883
69096
  const isEmpty = reqs.length === 0 && acs.length === 0;
68884
69097
  if (isUnknown || isEmpty) {
@@ -68888,7 +69101,7 @@ var init_task_check = __esm(() => {
68888
69101
  code: FINDING_CODES.L4_MALFORMED_VERDICT_ARTIFACT,
68889
69102
  severity: "error",
68890
69103
  section: "Testing",
68891
- message: `Verdict artifact at ${loaded.path} is malformed: ${reason}`
69104
+ message: `Verdict artifact at ${verdictPath} is malformed: ${reason}`
68892
69105
  });
68893
69106
  }
68894
69107
  }
@@ -74762,78 +74975,6 @@ var init_task_size_precheck = __esm(() => {
74762
74975
 
74763
74976
  // ../../packages/app/src/services/task-service.ts
74764
74977
  import { dirname as dirname14, isAbsolute as isAbsolute4, join as join16, relative as relative4 } from "path";
74765
- function patchFrontmatterField(rendered, key2, value) {
74766
- const openIdx = rendered.indexOf("---");
74767
- if (openIdx === -1)
74768
- return rendered;
74769
- let fmStart = openIdx + 3;
74770
- if (rendered[fmStart] === "\r")
74771
- fmStart += 1;
74772
- if (rendered[fmStart] === `
74773
- `)
74774
- fmStart += 1;
74775
- const closeRel = rendered.indexOf(`
74776
- ---`, fmStart);
74777
- if (closeRel === -1)
74778
- return rendered;
74779
- const before = rendered.slice(0, fmStart);
74780
- const fm = rendered.slice(fmStart, closeRel);
74781
- const after = rendered.slice(closeRel);
74782
- const existingRe = new RegExp(`^${escapeRegex2(key2)}:.*$`, "m");
74783
- if (existingRe.test(fm)) {
74784
- const newFm = fm.replace(existingRe, () => `${key2}: ${value}`);
74785
- return before + newFm + after;
74786
- }
74787
- return `${before}${key2}: ${value}
74788
- ${fm}${after}`;
74789
- }
74790
- function escapeRegex2(s2) {
74791
- return s2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
74792
- }
74793
- function renderCreatedTaskContent(params) {
74794
- let content = renderTaskTemplate(params.rawTemplate, {
74795
- NAME: params.name,
74796
- WBS: params.wbs,
74797
- BACKGROUND: params.background,
74798
- CREATED_AT: params.createdAt,
74799
- ...params.featureId !== undefined ? { FEATURE_ID: params.featureId } : {}
74800
- });
74801
- content = patchFrontmatterField(content, "status", params.status);
74802
- content = patchFrontmatterField(content, "template", params.variant);
74803
- if (params.featureId !== undefined) {
74804
- content = patchFrontmatterField(content, "feature_id", escapeYamlValue(params.featureId));
74805
- }
74806
- if (params.parentWbs !== undefined) {
74807
- content = patchFrontmatterField(content, "parent_wbs", escapeYamlValue(params.parentWbs));
74808
- }
74809
- if (params.priority !== undefined) {
74810
- content = patchFrontmatterField(content, "priority", params.priority);
74811
- }
74812
- if (params.tags !== undefined && params.tags.length > 0) {
74813
- content = patchFrontmatterField(content, "tags", `[${params.tags.map((tag) => JSON.stringify(tag)).join(", ")}]`);
74814
- }
74815
- const sectionPatches = {};
74816
- if ((params.requirements ?? "").trim() !== "") {
74817
- sectionPatches.Requirements = bulletizeRequirements(params.requirements ?? "");
74818
- }
74819
- if ((params.design ?? "").trim() !== "") {
74820
- sectionPatches.Design = (params.design ?? "").trim();
74821
- }
74822
- if ((params.plan ?? "").trim() !== "") {
74823
- sectionPatches.Plan = (params.plan ?? "").trim();
74824
- }
74825
- if ((params.acceptanceCriteria ?? "").trim() !== "") {
74826
- sectionPatches["Acceptance Criteria"] = normalizeAcFence((params.acceptanceCriteria ?? "").trim());
74827
- }
74828
- if (Object.keys(sectionPatches).length > 0) {
74829
- const doc2 = MarkdownDocument.parse(content, "task");
74830
- for (const [section, body] of Object.entries(sectionPatches)) {
74831
- doc2.replaceSection(section, body);
74832
- }
74833
- content = doc2.serialize();
74834
- }
74835
- return content;
74836
- }
74837
74978
  function renderRosterTable(rows) {
74838
74979
  const header = `| WBS | Sub-task | Status |
74839
74980
  | --- | -------- | ------ |`;
@@ -74899,12 +75040,14 @@ class TaskService {
74899
75040
  }
74900
75041
  sectionsForStatus(variant, status) {
74901
75042
  const matrix = this.ctx.sectionMatrix;
74902
- const entry = matrix?.variants[variant]?.[status] ?? matrix?.variants.standard?.[status];
74903
- if (entry !== undefined) {
74904
- 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)");
74905
75045
  }
74906
- const fallback = DEFAULT_CREATION_SECTIONS[status] ?? ["Background"];
74907
- 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"];
74908
75051
  }
74909
75052
  bodiesFor(variant, taskBodies) {
74910
75053
  const templateBodies = this.ctx.resolveTemplateBodies?.(variant) ?? {};
@@ -74949,22 +75092,6 @@ class TaskService {
74949
75092
  const slug = this.slugify(params.title);
74950
75093
  const { wbs, filePath } = await this.allocateWbsChecked(slug);
74951
75094
  const now2 = new Date().toISOString();
74952
- const rawTemplate = this.ctx.resolveTemplate?.(variant);
74953
- if (rawTemplate !== undefined) {
74954
- const content2 = renderCreatedTaskContent({
74955
- rawTemplate,
74956
- name: params.title,
74957
- wbs,
74958
- background,
74959
- createdAt: now2,
74960
- status,
74961
- variant,
74962
- featureId: params.featureId,
74963
- parentWbs: params.parentWbs
74964
- });
74965
- const ref2 = { kind: "task", id: wbs, filePath, folder };
74966
- return { ref: ref2, content: content2 };
74967
- }
74968
75095
  const frontmatter = [
74969
75096
  "schema_version: 1",
74970
75097
  `name: "${params.title}"`,
@@ -75019,7 +75146,8 @@ class TaskService {
75019
75146
  priority: true,
75020
75147
  done_forced: true,
75021
75148
  done_reason: true,
75022
- ac_numbering: true
75149
+ ac_numbering: true,
75150
+ ac_altitude: true
75023
75151
  };
75024
75152
  if (!(key2 in allowed)) {
75025
75153
  throw new Error(`Field "${key2}" is not settable via update; allowed: ${Object.keys(allowed).join(", ")}.`);
@@ -75462,28 +75590,6 @@ ${issues}`);
75462
75590
  const slug = this.slugify(item.name);
75463
75591
  const { wbs, filePath } = await this.allocateWbsChecked(slug);
75464
75592
  const now2 = new Date().toISOString();
75465
- const rawTemplate = this.ctx.resolveTemplate?.(variant);
75466
- if (rawTemplate !== undefined) {
75467
- const content2 = renderCreatedTaskContent({
75468
- rawTemplate,
75469
- name: item.name,
75470
- wbs,
75471
- background,
75472
- createdAt: now2,
75473
- status,
75474
- variant,
75475
- featureId: item.feature_id ?? undefined,
75476
- parentWbs: item.parent_wbs ?? undefined,
75477
- priority: item.priority,
75478
- tags: item.tags,
75479
- requirements: item.requirements,
75480
- design: item.design,
75481
- plan: item.plan,
75482
- acceptanceCriteria: item.acceptance_criteria
75483
- });
75484
- const ref2 = { kind: "task", id: wbs, filePath, folder };
75485
- return { ref: ref2, content: content2 };
75486
- }
75487
75593
  const fmLines = [
75488
75594
  "schema_version: 1",
75489
75595
  `name: "${item.name}"`,
@@ -75707,7 +75813,7 @@ ${block}` : block);
75707
75813
  return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60);
75708
75814
  }
75709
75815
  }
75710
- 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;
75816
+ 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;
75711
75817
  var init_task_service = __esm(() => {
75712
75818
  init_src2();
75713
75819
  init_errors7();
@@ -75765,10 +75871,6 @@ var init_task_service = __esm(() => {
75765
75871
  decompose: (wbs) => `/sp:dev-plan "Decompose task ${wbs} into implementation subtasks" --auto`,
75766
75872
  evaluate: (wbs) => `/sp:dev-review ${wbs} --auto`
75767
75873
  };
75768
- DEFAULT_CREATION_SECTIONS = {
75769
- backlog: ["Background"],
75770
- todo: ["Background", "Requirements", "Acceptance Criteria", "Q&A", "Design", "Plan"]
75771
- };
75772
75874
  });
75773
75875
 
75774
75876
  // ../../packages/app/src/services/task-verdict.ts
@@ -75780,20 +75882,13 @@ function deriveVerdict(answerText, taskCheckPassed) {
75780
75882
  if (requirements.length === 0) {
75781
75883
  return { verdict: "UNKNOWN", requirements, acceptanceCriteria, checks: checks4 };
75782
75884
  }
75783
- const hasUnmet = requirements.some((r) => r.status === "UNMET");
75784
- const hasUnmetAc = acceptanceCriteria.some((ac) => ac.status === "UNMET");
75785
- if (hasUnmet || hasUnmetAc) {
75786
- return { verdict: "FAIL", requirements, acceptanceCriteria, checks: checks4 };
75787
- }
75788
- const hasPartial = requirements.some((r) => r.status === "PARTIAL");
75789
- const hasPartialAc = acceptanceCriteria.some((ac) => ac.status === "PARTIAL");
75790
- if (hasPartial || hasPartialAc) {
75791
- return { verdict: "PARTIAL", requirements, acceptanceCriteria, checks: checks4 };
75792
- }
75793
- if (!taskCheckPassed) {
75794
- return { verdict: "PARTIAL", requirements, acceptanceCriteria, checks: checks4 };
75795
- }
75796
- return { verdict: "PASS", requirements, acceptanceCriteria, checks: checks4 };
75885
+ const aggregate2 = aggregateVerifyVerdict({
75886
+ requirements,
75887
+ acceptanceCriteria,
75888
+ checks: [],
75889
+ taskCheckPassed
75890
+ });
75891
+ return { verdict: aggregate2, requirements, acceptanceCriteria, checks: checks4 };
75797
75892
  }
75798
75893
  function extractRequirements(text4) {
75799
75894
  const reqs = [];
@@ -75805,7 +75900,7 @@ function extractRequirements(text4) {
75805
75900
  const trimmed = line.trim();
75806
75901
  if (!trimmed.startsWith("|"))
75807
75902
  continue;
75808
- const cells = trimmed.split("|").map((c3) => c3.trim()).filter(Boolean);
75903
+ const cells = splitTableCells(trimmed);
75809
75904
  if (!inTable && cells.length >= 2) {
75810
75905
  const h0 = (cells[0] ?? "").toLowerCase().trim();
75811
75906
  const h0IsId = h0.includes("req") || h0 === "requirement" || h0 === "r#" || h0 === "r" || /^r\d+$/.test(h0);
@@ -75856,6 +75951,9 @@ function normalizeStatus(raw) {
75856
75951
  return "UNMET";
75857
75952
  return null;
75858
75953
  }
75954
+ function splitTableCells(row) {
75955
+ return row.split(/(?<!\\)\|/).map((c3) => c3.replace(/\\\|/g, "|").trim()).filter(Boolean);
75956
+ }
75859
75957
  function extractAcceptanceCriteria(text4) {
75860
75958
  const rows = [];
75861
75959
  const dropped = [];
@@ -75864,9 +75962,13 @@ function extractAcceptanceCriteria(text4) {
75864
75962
  let inTable = false;
75865
75963
  for (const line of lines) {
75866
75964
  const trimmed = line.trim();
75965
+ if (inTable && /^#{1,6}\s/.test(trimmed)) {
75966
+ inTable = false;
75967
+ continue;
75968
+ }
75867
75969
  if (!trimmed.startsWith("|"))
75868
75970
  continue;
75869
- const cells = trimmed.split("|").map((c3) => c3.trim()).filter(Boolean);
75971
+ const cells = splitTableCells(trimmed);
75870
75972
  if (!inTable && cells.length >= 4) {
75871
75973
  const h0 = (cells[0] ?? "").toLowerCase();
75872
75974
  const h1 = (cells[1] ?? "").toLowerCase();
@@ -75962,7 +76064,7 @@ function extractChecks(_text, taskCheckPassed, acceptanceCriteria, droppedAcRows
75962
76064
  const trimmed = line.trim();
75963
76065
  if (!trimmed.startsWith("|"))
75964
76066
  continue;
75965
- const cells = trimmed.split("|").map((c3) => c3.trim()).filter(Boolean);
76067
+ const cells = splitTableCells(trimmed);
75966
76068
  if (cells.length >= 2) {
75967
76069
  const h0 = (cells[0] ?? "").toLowerCase();
75968
76070
  const h1 = (cells[1] ?? "").toLowerCase();
@@ -76030,6 +76132,7 @@ function aggregateBatchVerdicts(results) {
76030
76132
  }
76031
76133
  var EVIDENCE_TYPE_PRECEDENCE, NOT_STARTED_STATUSES;
76032
76134
  var init_task_verdict = __esm(() => {
76135
+ init_verify_verdict();
76033
76136
  EVIDENCE_TYPE_PRECEDENCE = ["test", "command", "static-ref", "manual-review", "llm-judge", "n/a"];
76034
76137
  NOT_STARTED_STATUSES = {
76035
76138
  backlog: true,
@@ -79626,6 +79729,7 @@ var init_src3 = __esm(() => {
79626
79729
  init_anchor_qualifier();
79627
79730
  init_corpus_check();
79628
79731
  init_corpus_migrator();
79732
+ init_done_transition_guard();
79629
79733
  init_event_names();
79630
79734
  init_failure_classification();
79631
79735
  init_feature_check();
@@ -87903,7 +88007,7 @@ import { createRequire } from "module";
87903
88007
  var CLI_CONFIG = {
87904
88008
  binaryName: "spur",
87905
88009
  binaryLabel: "spur",
87906
- binaryVersion: "0.3.51",
88010
+ binaryVersion: "0.3.53",
87907
88011
  configDir: ".spur",
87908
88012
  configFile: ".spur/config.yaml",
87909
88013
  databaseFile: ".spur/spur.db"
@@ -98270,7 +98374,8 @@ function createServerContext(appRt, options) {
98270
98374
  writeService: new PlanningWriteService({ fs: fs3, projectName: "spur", emitter: lazyEmitter }),
98271
98375
  tasksDir: folders.tasksDir,
98272
98376
  foldersConfig: folders.foldersConfig,
98273
- projectName: "spur"
98377
+ projectName: "spur",
98378
+ ...options.sectionMatrix !== undefined ? { sectionMatrix: options.sectionMatrix } : {}
98274
98379
  });
98275
98380
  }
98276
98381
  return taskSvc;
@@ -98450,6 +98555,27 @@ async function openUrl(url2, deps = {}) {
98450
98555
 
98451
98556
  // ../server/src/serve.ts
98452
98557
  init_src3();
98558
+ async function loadServerSectionMatrix() {
98559
+ const cwd = process.cwd();
98560
+ const nodeFs = createNodeFileSystem3(cwd);
98561
+ const localPath = nodeFs.resolve(".spur", "tasks", "section-matrix.yaml");
98562
+ if (await nodeFs.exists(localPath)) {
98563
+ return await loadStructuredSpurConfig(localPath, { validateJsonSchema: false });
98564
+ }
98565
+ const root = bundledConfigRoot();
98566
+ if (root !== null) {
98567
+ const matrixPath = join27(root, "tasks", "section-matrix.yaml");
98568
+ if (await nodeFs.exists(matrixPath)) {
98569
+ return await loadStructuredSpurConfig(matrixPath, {
98570
+ validateJsonSchema: false
98571
+ });
98572
+ }
98573
+ }
98574
+ throw new Error(`no canonical section-matrix found for task creation (F92 R1); tried:
98575
+ ` + ` - ${localPath}
98576
+ ` + (root !== null ? ` - ${join27(root, "tasks", "section-matrix.yaml")}
98577
+ ` : "") + "copy/generate section-matrix.yaml from the canonical build-time matrix asset (repo `config` `tasks` tree) into one of those paths");
98578
+ }
98453
98579
  var SYSTEM_EVENTS_PRUNE_JOB = "system-events-prune";
98454
98580
  var SMOKE_JOB = "smoke";
98455
98581
  var TASK_ACTION_JOB = "task-action";
@@ -98621,6 +98747,7 @@ async function startServer(options, deps = defaultDeps) {
98621
98747
  fs: fs3,
98622
98748
  dbUrl: options.dbUrl,
98623
98749
  folders: await resolvePlanningFolders(fs3),
98750
+ sectionMatrix: await loadServerSectionMatrix(),
98624
98751
  webDistPath,
98625
98752
  jobQueueEnabled: bootConfig.jobqueue.enabled,
98626
98753
  scheduler: scheduler3,
@@ -99903,14 +100030,15 @@ ${result.content}`);
99903
100030
  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", [
99904
100031
  "Lifecycle: `task update <wbs> <status>` moves a task through",
99905
100032
  "backlog \u2192 todo \u2192 wip \u2192 testing \u2192 done, running the lifecycle guards on",
99906
- "`wip \u2192 testing` (`spur task check`) and `testing \u2192 done` (`--strict-core`).",
100033
+ "`wip \u2192 testing` (`spur task check --as testing`) and `testing \u2192 done`",
100034
+ "(`spur task check --as done`) \u2014 each guard evaluates the transition target (F92 R3).",
99907
100035
  "A GuardDeniedError on `testing \u2192 done` means no pipeline run is recorded for the",
99908
100036
  "task: run `/sp:dev-verify <wbs> --next` to PASS it, or record the audited bypass with",
99909
100037
  '`SPUR_PROVENANCE_OVERRIDE=1 spur task update <wbs> done --force-done --reason "\u2026"`.',
99910
100038
  "See the gate checklist (spur-dev/references/gate-checklists.md).",
99911
100039
  "Valid section names (no failed write): `spur task sections <wbs> list`."
99912
100040
  ].join(`
99913
- `)).option("--section <name>", "Section name to replace").option("--from-file <path>", "File to read section body from (requires --section)").option("--feature <id>", "Set the feature_id frontmatter field (traceability edge)").option("--priority <p>", "Set the priority frontmatter field (P0\u2013P3)").option("--ac-numbering <mode>", "Set the ac_numbering frontmatter field (task-local) \u2014 opts the task into the Requirements\u2194AC coverage check").option("--no-lifecycle", "Suppress lifecycle workflow run creation (use during pipeline runs to avoid orphaned lifecycle runs)").option("--force-done", "Allow transitioning to `done` even when the verify verdict is not PASS; records an override (task 0292). Waives the verdict only \u2014 the FSM path still applies, so from an earlier status walk the hops first: `todo` \u2192 `wip` \u2192 `testing` \u2192 `done` (each hop runs the structural `spur task check`)").option("--reason <text>", "Rationale for a forced-done override (paired with --force-done; persisted as done_reason)").option("--verdict-dir <path>", "Directory holding <wbs>-verdict.json artifacts (default: .spur/run)").option("--folder <path>", "Custom tasks folder").option("--json", "Output machine-readable JSON").action(async (wbs, status, options) => {
100041
+ `)).option("--section <name>", "Section name to replace").option("--from-file <path>", "File to read section body from (requires --section)").option("--feature <id>", "Set the feature_id frontmatter field (traceability edge)").option("--priority <p>", "Set the priority frontmatter field (P0\u2013P3)").option("--ac-numbering <mode>", "Set the ac_numbering frontmatter field (task-local) \u2014 opts the task into the Requirements\u2194AC coverage check").option("--ac-altitude <mode>", "Set the ac_altitude frontmatter field. Valid: `graduating` (default; feature-AC subset rule enforced) or `task-local` (skip the DD-09 subset rule \u2014 task scenarios are intentionally not feature ship criteria). Mirrors the L1 schema enum (packages/domain/src/planning/schema.ts:304).").option("--no-lifecycle", "Suppress lifecycle workflow run creation (use during pipeline runs to avoid orphaned lifecycle runs)").option("--force-done", "Allow transitioning to `done` even when the verify verdict is not PASS; records an override (task 0292). Waives the verdict only \u2014 the FSM path still applies, so from an earlier status walk the hops first: `todo` \u2192 `wip` \u2192 `testing` \u2192 `done` (each hop runs the structural `spur task check`)").option("--reason <text>", "Rationale for a forced-done override (paired with --force-done; persisted as done_reason)").option("--verdict-dir <path>", "Directory holding <wbs>-verdict.json artifacts (default: .spur/run)").option("--folder <path>", "Custom tasks folder").option("--json", "Output machine-readable JSON").action(async (wbs, status, options) => {
99914
100042
  const svc = await makeService2(context4, options.folder, options.lifecycle === false);
99915
100043
  try {
99916
100044
  if (options.section !== undefined) {
@@ -99928,9 +100056,9 @@ ${result.content}`);
99928
100056
  }
99929
100057
  context4.output.write(`Updated section '${options.section}' in task ${result.ref.id}`);
99930
100058
  }
99931
- } else if (options.feature !== undefined || options.priority !== undefined || options.acNumbering !== undefined) {
99932
- const key2 = options.feature !== undefined ? "feature_id" : options.priority !== undefined ? "priority" : "ac_numbering";
99933
- const value2 = options.feature ?? options.priority ?? options.acNumbering ?? "";
100059
+ } else if (options.feature !== undefined || options.priority !== undefined || options.acNumbering !== undefined || options.acAltitude !== undefined) {
100060
+ const key2 = options.feature !== undefined ? "feature_id" : options.priority !== undefined ? "priority" : options.acNumbering !== undefined ? "ac_numbering" : "ac_altitude";
100061
+ const value2 = options.feature ?? options.priority ?? options.acNumbering ?? options.acAltitude ?? "";
99934
100062
  const result = await svc.updateField(wbs, key2, value2);
99935
100063
  if (options.json) {
99936
100064
  context4.output.write(toJson2(result));
@@ -99945,7 +100073,7 @@ ${result.content}`);
99945
100073
  if (options.lifecycle !== false) {
99946
100074
  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.");
99947
100075
  }
99948
- const ok = await runDoneGateCheck(context4, wbs, options.folder);
100076
+ const ok = await runDoneGateCheck(context4, wbs, options.folder, status);
99949
100077
  if (!ok) {
99950
100078
  context4.output.error(`Lifecycle transition blocked: \`spur task check ${wbs}\` failed. Fix the findings before transitioning to ${status}.`);
99951
100079
  context4.setExitCode(1);
@@ -100408,9 +100536,20 @@ ${result.content}`);
100408
100536
  context4.setExitCode(1);
100409
100537
  }
100410
100538
  });
100411
- 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) => {
100539
+ 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) => {
100412
100540
  const json3 = options.json === true;
100413
100541
  const strict = options.strict === true;
100542
+ const asStatus = options.as === undefined ? undefined : canonicalStatusOrRaw(options.as);
100543
+ if (options.as !== undefined && !TASK_STATUSES.includes(asStatus ?? "")) {
100544
+ context4.output.error(`invalid --as status "${options.as}" (canonical: ${TASK_STATUSES.join(", ")})`);
100545
+ context4.setExitCode(2);
100546
+ return;
100547
+ }
100548
+ if (asStatus !== undefined && options.corpus === true) {
100549
+ context4.output.error("--as <status> is a single-task target projection and cannot be combined with --corpus");
100550
+ context4.setExitCode(2);
100551
+ return;
100552
+ }
100414
100553
  try {
100415
100554
  if (options.corpus === true) {
100416
100555
  if (wbs !== undefined) {
@@ -100487,6 +100626,7 @@ ${result.wbs} (${result.status}): ${result.pass ? "PASS" : "FAIL"}`);
100487
100626
  } else {
100488
100627
  const result = await svc.check(hit.filePath, wbs, {
100489
100628
  strict,
100629
+ asStatus,
100490
100630
  severityOverrides: planningFolders.severityOverrides,
100491
100631
  accepted
100492
100632
  });
@@ -100505,6 +100645,7 @@ ${result.wbs} (${result.status}): ${result.pass ? "PASS" : "FAIL"}`);
100505
100645
  }
100506
100646
  const result = await svc.check(`${tasksDir}/${fileName}`, w, {
100507
100647
  strict,
100648
+ asStatus,
100508
100649
  severityOverrides: planningFolders.severityOverrides,
100509
100650
  accepted
100510
100651
  });
@@ -100645,36 +100786,10 @@ async function makeService2(context4, folderOverride, noLifecycle = false) {
100645
100786
  writeService,
100646
100787
  getDb: () => context4.getDb(),
100647
100788
  sectionMatrix: await loadSectionMatrix(context4.cwd),
100648
- resolveTemplate: (variant) => loadTemplateContent(context4.cwd, variant),
100649
100789
  resolveTemplateBodies: (variant) => loadTemplateBodies(context4.cwd, variant),
100650
100790
  foldersConfig
100651
100791
  });
100652
100792
  }
100653
- var templateContentCache = new Map;
100654
- var templateMissSet = new Set;
100655
- function loadTemplateContent(projectRoot, variant) {
100656
- if (templateContentCache.has(variant))
100657
- return templateContentCache.get(variant);
100658
- if (templateMissSet.has(variant))
100659
- return;
100660
- const localPath = join30(projectRoot, ".spur", "tasks", "templates", `${variant}.md`);
100661
- if (existsSync11(localPath)) {
100662
- const content = readFileSync10(localPath, "utf8");
100663
- templateContentCache.set(variant, content);
100664
- return content;
100665
- }
100666
- const root = bundledConfigRoot();
100667
- if (root !== null) {
100668
- const templatePath = join30(root, "templates", "task", `${variant}.md`);
100669
- if (existsSync11(templatePath)) {
100670
- const content = readFileSync10(templatePath, "utf8");
100671
- templateContentCache.set(variant, content);
100672
- return content;
100673
- }
100674
- }
100675
- templateMissSet.add(variant);
100676
- return;
100677
- }
100678
100793
  var templateBodiesCache = new Map;
100679
100794
  function loadTemplateBodies(projectRoot, variant) {
100680
100795
  const cached2 = templateBodiesCache.get(variant);
@@ -100707,7 +100822,7 @@ async function makeTaskLocator(context4) {
100707
100822
  async function makeCheckService(context4) {
100708
100823
  return new TaskCheckService(context4.fs, await loadSectionMatrix(context4.cwd), await makeTaskLocator(context4));
100709
100824
  }
100710
- async function runDoneGateCheck(context4, wbs, folderOverride) {
100825
+ async function runDoneGateCheck(context4, wbs, folderOverride, targetStatus) {
100711
100826
  const planningFolders = await resolvePlanningFolders(context4.fs);
100712
100827
  const foldersConfig = planningFolders.foldersConfig;
100713
100828
  const tasksDir = folderOverride ?? context4.fs.resolve(foldersConfig.active_folder);
@@ -100719,6 +100834,7 @@ async function runDoneGateCheck(context4, wbs, folderOverride) {
100719
100834
  const accepted = await loadAcceptedFindings(context4.cwd);
100720
100835
  const result = await svc.check(hit.filePath, wbs, {
100721
100836
  strict: false,
100837
+ asStatus: targetStatus,
100722
100838
  severityOverrides: planningFolders.severityOverrides,
100723
100839
  accepted
100724
100840
  });
@@ -100755,16 +100871,11 @@ async function loadSectionMatrixUncached(projectRoot) {
100755
100871
  return data;
100756
100872
  }
100757
100873
  }
100758
- return FALLBACK_MATRIX;
100874
+ throw new Error(`no canonical section-matrix found for task section authority (F92 R1); tried:
100875
+ ` + ` - ${localPath}
100876
+ ` + (root !== null ? ` - ${join30(root, "tasks", "section-matrix.yaml")}
100877
+ ` : "") + "copy/generate section-matrix.yaml from the canonical build-time matrix asset (repo `config` `tasks` tree) into one of those paths");
100759
100878
  }
100760
- var FALLBACK_MATRIX = {
100761
- variants: {
100762
- standard: {
100763
- backlog: { required: ["Background"], forbidden: ["Solution", "Review", "Testing"] },
100764
- done: { required: ["Solution", "Testing", "Review"], gate: true }
100765
- }
100766
- }
100767
- };
100768
100879
 
100769
100880
  // src/commands/team.ts
100770
100881
  init_src3();