@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/config/corpus-baseline.json +8 -8
- package/config/tasks/section-matrix.yaml +7 -2
- package/config/workflows/task-lifecycle.yaml +7 -6
- package/config/workflows/task-pipeline2.yaml +778 -0
- package/package.json +1 -1
- package/plugins/sp/agents/super-planner.md +3 -2
- package/plugins/sp/agents/super-reviewer.md +5 -0
- package/plugins/sp/commands/dev-review.md +1 -1
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/scripts/pr-reviewing.ts +22 -4
- package/plugins/sp/scripts/stage-registry-adapter.ts +40 -25
- package/plugins/sp/skills/code-improvement/SKILL.md +5 -3
- package/plugins/sp/skills/code-verification/SKILL.md +28 -31
- package/plugins/sp/skills/code-verification/references/verdict-schema.md +47 -0
- package/plugins/sp/skills/functional-review/SKILL.md +16 -15
- package/plugins/sp/skills/spec-decomposition/references/decomposition.md +13 -14
- package/plugins/sp/skills/spur-cli/references/tasks/l3-guard-cheatsheet.md +7 -3
- package/plugins/sp/skills/spur-cli/references/tasks/section-editing.md +22 -13
- package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +22 -11
- package/plugins/sp/skills/spur-cli/references/tasks.md +16 -10
- package/plugins/sp/skills/spur-dev/SKILL.md +10 -5
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +5 -5
- package/plugins/sp/skills/spur-dev/references/execution-workflow.md +11 -4
- package/plugins/sp/skills/spur-dev/references/gate-checklists.md +5 -3
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +10 -1
- package/plugins/sp/skills/spur-dev/references/section-batching.md +29 -13
- 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:
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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: [
|
|
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: "
|
|
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: [
|
|
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
|
-
|
|
67070
|
-
|
|
67071
|
-
|
|
67072
|
-
|
|
67073
|
-
|
|
67074
|
-
|
|
67075
|
-
|
|
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
|
|
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,
|
|
68395
|
+
const entry = this.resolveMatrixEntry(variant, effectiveStatus);
|
|
68194
68396
|
this.runL2(doc2, entry, findings);
|
|
68195
|
-
this.runL3(doc2, entry,
|
|
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,
|
|
68199
|
-
await this.runL4Rollup(doc2, wbs,
|
|
68200
|
-
if (
|
|
68201
|
-
await this.runL4Readiness(doc2, fm, wbs,
|
|
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(
|
|
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
|
|
68867
|
-
|
|
68868
|
-
|
|
68869
|
-
|
|
68870
|
-
|
|
68871
|
-
|
|
68872
|
-
|
|
68873
|
-
|
|
68874
|
-
|
|
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
|
-
|
|
68880
|
-
|
|
68881
|
-
|
|
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 ${
|
|
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
|
-
|
|
74903
|
-
|
|
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
|
|
74907
|
-
|
|
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
|
|
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
|
|
75784
|
-
|
|
75785
|
-
|
|
75786
|
-
|
|
75787
|
-
|
|
75788
|
-
|
|
75789
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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`
|
|
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", "
|
|
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
|
-
|
|
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();
|