@gobing-ai/spur 0.3.51 → 0.3.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/config/tasks/section-matrix.yaml +7 -2
- package/config/workflows/task-lifecycle.yaml +7 -6
- 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/section-batching.md +29 -13
- package/spur.js +331 -221
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}"`,
|
|
@@ -75462,28 +75589,6 @@ ${issues}`);
|
|
|
75462
75589
|
const slug = this.slugify(item.name);
|
|
75463
75590
|
const { wbs, filePath } = await this.allocateWbsChecked(slug);
|
|
75464
75591
|
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
75592
|
const fmLines = [
|
|
75488
75593
|
"schema_version: 1",
|
|
75489
75594
|
`name: "${item.name}"`,
|
|
@@ -75707,7 +75812,7 @@ ${block}` : block);
|
|
|
75707
75812
|
return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60);
|
|
75708
75813
|
}
|
|
75709
75814
|
}
|
|
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
|
|
75815
|
+
var DependencyMutationError, SectionMutationError, DuplicateFollowUpError, WbsCollisionError, ROSTER_START = "<!-- AUTO-GENERATED by spur task refresh-roster -->", ROSTER_END = "<!-- END AUTO-GENERATED -->", ROSTER_REGION_RE, TASK_ACTION_COMMANDS;
|
|
75711
75816
|
var init_task_service = __esm(() => {
|
|
75712
75817
|
init_src2();
|
|
75713
75818
|
init_errors7();
|
|
@@ -75765,10 +75870,6 @@ var init_task_service = __esm(() => {
|
|
|
75765
75870
|
decompose: (wbs) => `/sp:dev-plan "Decompose task ${wbs} into implementation subtasks" --auto`,
|
|
75766
75871
|
evaluate: (wbs) => `/sp:dev-review ${wbs} --auto`
|
|
75767
75872
|
};
|
|
75768
|
-
DEFAULT_CREATION_SECTIONS = {
|
|
75769
|
-
backlog: ["Background"],
|
|
75770
|
-
todo: ["Background", "Requirements", "Acceptance Criteria", "Q&A", "Design", "Plan"]
|
|
75771
|
-
};
|
|
75772
75873
|
});
|
|
75773
75874
|
|
|
75774
75875
|
// ../../packages/app/src/services/task-verdict.ts
|
|
@@ -75780,20 +75881,13 @@ function deriveVerdict(answerText, taskCheckPassed) {
|
|
|
75780
75881
|
if (requirements.length === 0) {
|
|
75781
75882
|
return { verdict: "UNKNOWN", requirements, acceptanceCriteria, checks: checks4 };
|
|
75782
75883
|
}
|
|
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 };
|
|
75884
|
+
const aggregate2 = aggregateVerifyVerdict({
|
|
75885
|
+
requirements,
|
|
75886
|
+
acceptanceCriteria,
|
|
75887
|
+
checks: [],
|
|
75888
|
+
taskCheckPassed
|
|
75889
|
+
});
|
|
75890
|
+
return { verdict: aggregate2, requirements, acceptanceCriteria, checks: checks4 };
|
|
75797
75891
|
}
|
|
75798
75892
|
function extractRequirements(text4) {
|
|
75799
75893
|
const reqs = [];
|
|
@@ -75805,7 +75899,7 @@ function extractRequirements(text4) {
|
|
|
75805
75899
|
const trimmed = line.trim();
|
|
75806
75900
|
if (!trimmed.startsWith("|"))
|
|
75807
75901
|
continue;
|
|
75808
|
-
const cells = trimmed
|
|
75902
|
+
const cells = splitTableCells(trimmed);
|
|
75809
75903
|
if (!inTable && cells.length >= 2) {
|
|
75810
75904
|
const h0 = (cells[0] ?? "").toLowerCase().trim();
|
|
75811
75905
|
const h0IsId = h0.includes("req") || h0 === "requirement" || h0 === "r#" || h0 === "r" || /^r\d+$/.test(h0);
|
|
@@ -75856,6 +75950,9 @@ function normalizeStatus(raw) {
|
|
|
75856
75950
|
return "UNMET";
|
|
75857
75951
|
return null;
|
|
75858
75952
|
}
|
|
75953
|
+
function splitTableCells(row) {
|
|
75954
|
+
return row.split(/(?<!\\)\|/).map((c3) => c3.replace(/\\\|/g, "|").trim()).filter(Boolean);
|
|
75955
|
+
}
|
|
75859
75956
|
function extractAcceptanceCriteria(text4) {
|
|
75860
75957
|
const rows = [];
|
|
75861
75958
|
const dropped = [];
|
|
@@ -75864,9 +75961,13 @@ function extractAcceptanceCriteria(text4) {
|
|
|
75864
75961
|
let inTable = false;
|
|
75865
75962
|
for (const line of lines) {
|
|
75866
75963
|
const trimmed = line.trim();
|
|
75964
|
+
if (inTable && /^#{1,6}\s/.test(trimmed)) {
|
|
75965
|
+
inTable = false;
|
|
75966
|
+
continue;
|
|
75967
|
+
}
|
|
75867
75968
|
if (!trimmed.startsWith("|"))
|
|
75868
75969
|
continue;
|
|
75869
|
-
const cells = trimmed
|
|
75970
|
+
const cells = splitTableCells(trimmed);
|
|
75870
75971
|
if (!inTable && cells.length >= 4) {
|
|
75871
75972
|
const h0 = (cells[0] ?? "").toLowerCase();
|
|
75872
75973
|
const h1 = (cells[1] ?? "").toLowerCase();
|
|
@@ -75962,7 +76063,7 @@ function extractChecks(_text, taskCheckPassed, acceptanceCriteria, droppedAcRows
|
|
|
75962
76063
|
const trimmed = line.trim();
|
|
75963
76064
|
if (!trimmed.startsWith("|"))
|
|
75964
76065
|
continue;
|
|
75965
|
-
const cells = trimmed
|
|
76066
|
+
const cells = splitTableCells(trimmed);
|
|
75966
76067
|
if (cells.length >= 2) {
|
|
75967
76068
|
const h0 = (cells[0] ?? "").toLowerCase();
|
|
75968
76069
|
const h1 = (cells[1] ?? "").toLowerCase();
|
|
@@ -76030,6 +76131,7 @@ function aggregateBatchVerdicts(results) {
|
|
|
76030
76131
|
}
|
|
76031
76132
|
var EVIDENCE_TYPE_PRECEDENCE, NOT_STARTED_STATUSES;
|
|
76032
76133
|
var init_task_verdict = __esm(() => {
|
|
76134
|
+
init_verify_verdict();
|
|
76033
76135
|
EVIDENCE_TYPE_PRECEDENCE = ["test", "command", "static-ref", "manual-review", "llm-judge", "n/a"];
|
|
76034
76136
|
NOT_STARTED_STATUSES = {
|
|
76035
76137
|
backlog: true,
|
|
@@ -79626,6 +79728,7 @@ var init_src3 = __esm(() => {
|
|
|
79626
79728
|
init_anchor_qualifier();
|
|
79627
79729
|
init_corpus_check();
|
|
79628
79730
|
init_corpus_migrator();
|
|
79731
|
+
init_done_transition_guard();
|
|
79629
79732
|
init_event_names();
|
|
79630
79733
|
init_failure_classification();
|
|
79631
79734
|
init_feature_check();
|
|
@@ -87903,7 +88006,7 @@ import { createRequire } from "module";
|
|
|
87903
88006
|
var CLI_CONFIG = {
|
|
87904
88007
|
binaryName: "spur",
|
|
87905
88008
|
binaryLabel: "spur",
|
|
87906
|
-
binaryVersion: "0.3.
|
|
88009
|
+
binaryVersion: "0.3.52",
|
|
87907
88010
|
configDir: ".spur",
|
|
87908
88011
|
configFile: ".spur/config.yaml",
|
|
87909
88012
|
databaseFile: ".spur/spur.db"
|
|
@@ -98270,7 +98373,8 @@ function createServerContext(appRt, options) {
|
|
|
98270
98373
|
writeService: new PlanningWriteService({ fs: fs3, projectName: "spur", emitter: lazyEmitter }),
|
|
98271
98374
|
tasksDir: folders.tasksDir,
|
|
98272
98375
|
foldersConfig: folders.foldersConfig,
|
|
98273
|
-
projectName: "spur"
|
|
98376
|
+
projectName: "spur",
|
|
98377
|
+
...options.sectionMatrix !== undefined ? { sectionMatrix: options.sectionMatrix } : {}
|
|
98274
98378
|
});
|
|
98275
98379
|
}
|
|
98276
98380
|
return taskSvc;
|
|
@@ -98450,6 +98554,27 @@ async function openUrl(url2, deps = {}) {
|
|
|
98450
98554
|
|
|
98451
98555
|
// ../server/src/serve.ts
|
|
98452
98556
|
init_src3();
|
|
98557
|
+
async function loadServerSectionMatrix() {
|
|
98558
|
+
const cwd = process.cwd();
|
|
98559
|
+
const nodeFs = createNodeFileSystem3(cwd);
|
|
98560
|
+
const localPath = nodeFs.resolve(".spur", "tasks", "section-matrix.yaml");
|
|
98561
|
+
if (await nodeFs.exists(localPath)) {
|
|
98562
|
+
return await loadStructuredSpurConfig(localPath, { validateJsonSchema: false });
|
|
98563
|
+
}
|
|
98564
|
+
const root = bundledConfigRoot();
|
|
98565
|
+
if (root !== null) {
|
|
98566
|
+
const matrixPath = join27(root, "tasks", "section-matrix.yaml");
|
|
98567
|
+
if (await nodeFs.exists(matrixPath)) {
|
|
98568
|
+
return await loadStructuredSpurConfig(matrixPath, {
|
|
98569
|
+
validateJsonSchema: false
|
|
98570
|
+
});
|
|
98571
|
+
}
|
|
98572
|
+
}
|
|
98573
|
+
throw new Error(`no canonical section-matrix found for task creation (F92 R1); tried:
|
|
98574
|
+
` + ` - ${localPath}
|
|
98575
|
+
` + (root !== null ? ` - ${join27(root, "tasks", "section-matrix.yaml")}
|
|
98576
|
+
` : "") + "copy/generate section-matrix.yaml from the canonical build-time matrix asset (repo `config` `tasks` tree) into one of those paths");
|
|
98577
|
+
}
|
|
98453
98578
|
var SYSTEM_EVENTS_PRUNE_JOB = "system-events-prune";
|
|
98454
98579
|
var SMOKE_JOB = "smoke";
|
|
98455
98580
|
var TASK_ACTION_JOB = "task-action";
|
|
@@ -98621,6 +98746,7 @@ async function startServer(options, deps = defaultDeps) {
|
|
|
98621
98746
|
fs: fs3,
|
|
98622
98747
|
dbUrl: options.dbUrl,
|
|
98623
98748
|
folders: await resolvePlanningFolders(fs3),
|
|
98749
|
+
sectionMatrix: await loadServerSectionMatrix(),
|
|
98624
98750
|
webDistPath,
|
|
98625
98751
|
jobQueueEnabled: bootConfig.jobqueue.enabled,
|
|
98626
98752
|
scheduler: scheduler3,
|
|
@@ -99903,7 +100029,8 @@ ${result.content}`);
|
|
|
99903
100029
|
task.command("update").summary("Update a task status or replace a section.").argument("<wbs>", "Task WBS number").argument("[status]", "New status (for lifecycle transition)").addHelpText("after", [
|
|
99904
100030
|
"Lifecycle: `task update <wbs> <status>` moves a task through",
|
|
99905
100031
|
"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`
|
|
100032
|
+
"`wip \u2192 testing` (`spur task check --as testing`) and `testing \u2192 done`",
|
|
100033
|
+
"(`spur task check --as done`) \u2014 each guard evaluates the transition target (F92 R3).",
|
|
99907
100034
|
"A GuardDeniedError on `testing \u2192 done` means no pipeline run is recorded for the",
|
|
99908
100035
|
"task: run `/sp:dev-verify <wbs> --next` to PASS it, or record the audited bypass with",
|
|
99909
100036
|
'`SPUR_PROVENANCE_OVERRIDE=1 spur task update <wbs> done --force-done --reason "\u2026"`.',
|
|
@@ -99945,7 +100072,7 @@ ${result.content}`);
|
|
|
99945
100072
|
if (options.lifecycle !== false) {
|
|
99946
100073
|
context4.output.error(`warning: lifecycle adapter unavailable \u2014 running \`spur task check\` inline as the ${status} gate. ` + "Restore the bundled task-lifecycle workflow to re-enable the real guard.");
|
|
99947
100074
|
}
|
|
99948
|
-
const ok = await runDoneGateCheck(context4, wbs, options.folder);
|
|
100075
|
+
const ok = await runDoneGateCheck(context4, wbs, options.folder, status);
|
|
99949
100076
|
if (!ok) {
|
|
99950
100077
|
context4.output.error(`Lifecycle transition blocked: \`spur task check ${wbs}\` failed. Fix the findings before transitioning to ${status}.`);
|
|
99951
100078
|
context4.setExitCode(1);
|
|
@@ -100408,9 +100535,20 @@ ${result.content}`);
|
|
|
100408
100535
|
context4.setExitCode(1);
|
|
100409
100536
|
}
|
|
100410
100537
|
});
|
|
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", "
|
|
100538
|
+
task.command("check").summary("Validate a task file through the four-layer check (design \xA73).").argument("[wbs]", "Task WBS number (validates all tasks in the folder when omitted)").option("--strict", "Elevate ALL warnings to failures").option("--strict-core", "Compatibility alias (F92 R2): historically the done-gate label; kept so installed plugins/workflows that call it keep working. No longer meaningful on its own \u2014 target-state selection (`--as`) supplies the real done semantics.").option("--as <status>", "Evaluate the task AS if it were in <status> (F92 R2): the lifecycle guards pass the transition target so testing\u2192done checks the done row. Validate against canonical task statuses. Omitted \u2192 current-status diagnostics.").option("--corpus", "Sweep every task and feature against config/corpus-baseline.json").option("--since <ref>", "Scope the corpus fog check to changes since a git ref (requires --corpus)").option("--folder <path>", "Custom tasks folder").option("--json", "Output machine-readable JSON").action(async (wbs, options) => {
|
|
100412
100539
|
const json3 = options.json === true;
|
|
100413
100540
|
const strict = options.strict === true;
|
|
100541
|
+
const asStatus = options.as === undefined ? undefined : canonicalStatusOrRaw(options.as);
|
|
100542
|
+
if (options.as !== undefined && !TASK_STATUSES.includes(asStatus ?? "")) {
|
|
100543
|
+
context4.output.error(`invalid --as status "${options.as}" (canonical: ${TASK_STATUSES.join(", ")})`);
|
|
100544
|
+
context4.setExitCode(2);
|
|
100545
|
+
return;
|
|
100546
|
+
}
|
|
100547
|
+
if (asStatus !== undefined && options.corpus === true) {
|
|
100548
|
+
context4.output.error("--as <status> is a single-task target projection and cannot be combined with --corpus");
|
|
100549
|
+
context4.setExitCode(2);
|
|
100550
|
+
return;
|
|
100551
|
+
}
|
|
100414
100552
|
try {
|
|
100415
100553
|
if (options.corpus === true) {
|
|
100416
100554
|
if (wbs !== undefined) {
|
|
@@ -100487,6 +100625,7 @@ ${result.wbs} (${result.status}): ${result.pass ? "PASS" : "FAIL"}`);
|
|
|
100487
100625
|
} else {
|
|
100488
100626
|
const result = await svc.check(hit.filePath, wbs, {
|
|
100489
100627
|
strict,
|
|
100628
|
+
asStatus,
|
|
100490
100629
|
severityOverrides: planningFolders.severityOverrides,
|
|
100491
100630
|
accepted
|
|
100492
100631
|
});
|
|
@@ -100505,6 +100644,7 @@ ${result.wbs} (${result.status}): ${result.pass ? "PASS" : "FAIL"}`);
|
|
|
100505
100644
|
}
|
|
100506
100645
|
const result = await svc.check(`${tasksDir}/${fileName}`, w, {
|
|
100507
100646
|
strict,
|
|
100647
|
+
asStatus,
|
|
100508
100648
|
severityOverrides: planningFolders.severityOverrides,
|
|
100509
100649
|
accepted
|
|
100510
100650
|
});
|
|
@@ -100645,36 +100785,10 @@ async function makeService2(context4, folderOverride, noLifecycle = false) {
|
|
|
100645
100785
|
writeService,
|
|
100646
100786
|
getDb: () => context4.getDb(),
|
|
100647
100787
|
sectionMatrix: await loadSectionMatrix(context4.cwd),
|
|
100648
|
-
resolveTemplate: (variant) => loadTemplateContent(context4.cwd, variant),
|
|
100649
100788
|
resolveTemplateBodies: (variant) => loadTemplateBodies(context4.cwd, variant),
|
|
100650
100789
|
foldersConfig
|
|
100651
100790
|
});
|
|
100652
100791
|
}
|
|
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
100792
|
var templateBodiesCache = new Map;
|
|
100679
100793
|
function loadTemplateBodies(projectRoot, variant) {
|
|
100680
100794
|
const cached2 = templateBodiesCache.get(variant);
|
|
@@ -100707,7 +100821,7 @@ async function makeTaskLocator(context4) {
|
|
|
100707
100821
|
async function makeCheckService(context4) {
|
|
100708
100822
|
return new TaskCheckService(context4.fs, await loadSectionMatrix(context4.cwd), await makeTaskLocator(context4));
|
|
100709
100823
|
}
|
|
100710
|
-
async function runDoneGateCheck(context4, wbs, folderOverride) {
|
|
100824
|
+
async function runDoneGateCheck(context4, wbs, folderOverride, targetStatus) {
|
|
100711
100825
|
const planningFolders = await resolvePlanningFolders(context4.fs);
|
|
100712
100826
|
const foldersConfig = planningFolders.foldersConfig;
|
|
100713
100827
|
const tasksDir = folderOverride ?? context4.fs.resolve(foldersConfig.active_folder);
|
|
@@ -100719,6 +100833,7 @@ async function runDoneGateCheck(context4, wbs, folderOverride) {
|
|
|
100719
100833
|
const accepted = await loadAcceptedFindings(context4.cwd);
|
|
100720
100834
|
const result = await svc.check(hit.filePath, wbs, {
|
|
100721
100835
|
strict: false,
|
|
100836
|
+
asStatus: targetStatus,
|
|
100722
100837
|
severityOverrides: planningFolders.severityOverrides,
|
|
100723
100838
|
accepted
|
|
100724
100839
|
});
|
|
@@ -100755,16 +100870,11 @@ async function loadSectionMatrixUncached(projectRoot) {
|
|
|
100755
100870
|
return data;
|
|
100756
100871
|
}
|
|
100757
100872
|
}
|
|
100758
|
-
|
|
100873
|
+
throw new Error(`no canonical section-matrix found for task section authority (F92 R1); tried:
|
|
100874
|
+
` + ` - ${localPath}
|
|
100875
|
+
` + (root !== null ? ` - ${join30(root, "tasks", "section-matrix.yaml")}
|
|
100876
|
+
` : "") + "copy/generate section-matrix.yaml from the canonical build-time matrix asset (repo `config` `tasks` tree) into one of those paths");
|
|
100759
100877
|
}
|
|
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
100878
|
|
|
100769
100879
|
// src/commands/team.ts
|
|
100770
100880
|
init_src3();
|