@gobing-ai/spur 0.3.50 → 0.3.52
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/config/corpus-baseline.json +1995 -1939
- package/config/rules/quality/coverage-gate.yaml +3 -2
- package/config/tasks/section-matrix.yaml +7 -2
- package/config/workflows/pr-review.yaml +13 -11
- package/config/workflows/task-lifecycle.yaml +7 -6
- package/config/workflows/task-pipeline.yaml +37 -16
- 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 +57 -5
- 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/pr-reviewing/SKILL.md +6 -3
- 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 +382 -231
package/spur.js
CHANGED
|
@@ -62573,7 +62573,7 @@ var init_schema4 = __esm(() => {
|
|
|
62573
62573
|
init_zod();
|
|
62574
62574
|
STAGE_REGISTRY_SCHEMA_VERSION = {
|
|
62575
62575
|
major: 1,
|
|
62576
|
-
minor:
|
|
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(() => {
|
|
@@ -67152,6 +67351,10 @@ var init_finding_codes2 = __esm(() => {
|
|
|
67152
67351
|
});
|
|
67153
67352
|
|
|
67154
67353
|
// ../../packages/app/src/services/planning-check-base.ts
|
|
67354
|
+
function key(e) {
|
|
67355
|
+
return `${e.kind}:${e.id}:${e.code}`;
|
|
67356
|
+
}
|
|
67357
|
+
|
|
67155
67358
|
class PlanningCheckService {
|
|
67156
67359
|
fs;
|
|
67157
67360
|
matrix;
|
|
@@ -67244,7 +67447,7 @@ class PlanningCheckService {
|
|
|
67244
67447
|
}
|
|
67245
67448
|
}
|
|
67246
67449
|
}
|
|
67247
|
-
summarizeWithStatus(status, findings, strict, overrides) {
|
|
67450
|
+
summarizeWithStatus(status, findings, strict, overrides, accepted, id) {
|
|
67248
67451
|
const effectiveFindings = [];
|
|
67249
67452
|
for (const f of findings) {
|
|
67250
67453
|
const override = overrides?.[f.code];
|
|
@@ -67257,6 +67460,13 @@ class PlanningCheckService {
|
|
|
67257
67460
|
if (strict && f.severity === "warning") {
|
|
67258
67461
|
f.severity = "error";
|
|
67259
67462
|
}
|
|
67463
|
+
if (accepted && id) {
|
|
67464
|
+
const k = key({ kind: this.docKind, id, code: f.code });
|
|
67465
|
+
const acceptedSev = accepted.get(k);
|
|
67466
|
+
if (acceptedSev !== undefined && acceptedSev === f.severity) {
|
|
67467
|
+
continue;
|
|
67468
|
+
}
|
|
67469
|
+
}
|
|
67260
67470
|
effectiveFindings.push(f);
|
|
67261
67471
|
}
|
|
67262
67472
|
let hasError = false;
|
|
@@ -67411,6 +67621,7 @@ function verdictRowsMatchScenarios(rows, ac) {
|
|
|
67411
67621
|
var DEFAULT_FEATURE_MATRIX, FeatureCheckService;
|
|
67412
67622
|
var init_feature_check = __esm(() => {
|
|
67413
67623
|
init_src2();
|
|
67624
|
+
init_done_transition_guard();
|
|
67414
67625
|
init_planning_check_base();
|
|
67415
67626
|
DEFAULT_FEATURE_MATRIX = {
|
|
67416
67627
|
variants: {
|
|
@@ -67877,7 +68088,7 @@ class TaskLocator {
|
|
|
67877
68088
|
return;
|
|
67878
68089
|
}
|
|
67879
68090
|
const folderKeys = source.foldersConfig ? Object.keys(source.foldersConfig.folders) : [];
|
|
67880
|
-
this.dirs = [...new Set([source.tasksDir, ...folderKeys.map((
|
|
68091
|
+
this.dirs = [...new Set([source.tasksDir, ...folderKeys.map((key2) => source.fs.resolve(key2))])];
|
|
67881
68092
|
}
|
|
67882
68093
|
static forSingleDir(fs3, dir) {
|
|
67883
68094
|
return new TaskLocator({ fs: fs3, tasksDir: dir });
|
|
@@ -68146,6 +68357,7 @@ var init_task_check = __esm(() => {
|
|
|
68146
68357
|
init_src2();
|
|
68147
68358
|
init_planning_check_base();
|
|
68148
68359
|
init_task_locator();
|
|
68360
|
+
init_verify_verdict();
|
|
68149
68361
|
EXTERNAL_EVIDENCE_RE = /`([^`\n]+?)`\s+(?:line|lines?)\s+(\d+)(?:-(\d+))?/g;
|
|
68150
68362
|
TaskCheckService = class TaskCheckService extends PlanningCheckService {
|
|
68151
68363
|
locator;
|
|
@@ -68171,22 +68383,29 @@ var init_task_check = __esm(() => {
|
|
|
68171
68383
|
const findings = [];
|
|
68172
68384
|
const doc2 = this.runL1(raw, wbs, findings);
|
|
68173
68385
|
if (doc2 === null) {
|
|
68174
|
-
return {
|
|
68386
|
+
return {
|
|
68387
|
+
wbs,
|
|
68388
|
+
...this.summarizeWithStatus("", findings, strict, options?.severityOverrides, options?.accepted, wbs)
|
|
68389
|
+
};
|
|
68175
68390
|
}
|
|
68176
68391
|
const fm = doc2.frontmatterData ?? {};
|
|
68177
68392
|
const status = fm.status ?? "backlog";
|
|
68393
|
+
const effectiveStatus = options?.asStatus ?? status;
|
|
68178
68394
|
const variant = fm.template ?? DEFAULT_TASK_VARIANT;
|
|
68179
|
-
const entry = this.resolveMatrixEntry(variant,
|
|
68395
|
+
const entry = this.resolveMatrixEntry(variant, effectiveStatus);
|
|
68180
68396
|
this.runL2(doc2, entry, findings);
|
|
68181
|
-
this.runL3(doc2, entry,
|
|
68397
|
+
this.runL3(doc2, entry, effectiveStatus, findings);
|
|
68182
68398
|
const tasksDir = dirname9(filePath);
|
|
68183
68399
|
const featuresDir = join10(dirname9(tasksDir), "features");
|
|
68184
|
-
await this.runL4(doc2, fm,
|
|
68185
|
-
await this.runL4Rollup(doc2, wbs,
|
|
68186
|
-
if (
|
|
68187
|
-
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);
|
|
68188
68404
|
}
|
|
68189
|
-
return {
|
|
68405
|
+
return {
|
|
68406
|
+
wbs,
|
|
68407
|
+
...this.summarizeWithStatus(effectiveStatus, findings, strict, options?.severityOverrides, options?.accepted, wbs)
|
|
68408
|
+
};
|
|
68190
68409
|
}
|
|
68191
68410
|
runL3(doc2, entry, status, findings) {
|
|
68192
68411
|
const reqBodyRaw = doc2.getSection("Requirements");
|
|
@@ -68846,22 +69065,33 @@ var init_task_check = __esm(() => {
|
|
|
68846
69065
|
async checkVerdictArtifact(wbs, tasksDir, findings) {
|
|
68847
69066
|
const projectRoot = resolveProjectRootFromTasksDir(tasksDir);
|
|
68848
69067
|
const runDir = join10(projectRoot, ".spur", "run");
|
|
68849
|
-
const
|
|
68850
|
-
|
|
68851
|
-
|
|
68852
|
-
|
|
68853
|
-
|
|
68854
|
-
|
|
68855
|
-
|
|
68856
|
-
|
|
68857
|
-
|
|
68858
|
-
|
|
68859
|
-
|
|
69068
|
+
const verdictPath = `${runDir}/${wbs}-verdict.json`;
|
|
69069
|
+
const outcome = await readVerifyVerdict(this.fs, verdictPath, wbs);
|
|
69070
|
+
if (outcome.kind === "missing")
|
|
69071
|
+
return;
|
|
69072
|
+
if (outcome.kind === "malformed") {
|
|
69073
|
+
findings.push({
|
|
69074
|
+
layer: "L4",
|
|
69075
|
+
code: FINDING_CODES.L4_MALFORMED_VERDICT_ARTIFACT,
|
|
69076
|
+
severity: "error",
|
|
69077
|
+
section: "Testing",
|
|
69078
|
+
message: `Verdict artifact at ${verdictPath} is malformed: ${outcome.message}`
|
|
69079
|
+
});
|
|
69080
|
+
return;
|
|
69081
|
+
}
|
|
69082
|
+
if (outcome.kind === "invalid") {
|
|
69083
|
+
findings.push({
|
|
69084
|
+
layer: "L4",
|
|
69085
|
+
code: FINDING_CODES.L4_MALFORMED_VERDICT_ARTIFACT,
|
|
69086
|
+
severity: "error",
|
|
69087
|
+
section: "Testing",
|
|
69088
|
+
message: `Verdict artifact at ${verdictPath} is invalid: ${outcome.reason}`
|
|
69089
|
+
});
|
|
68860
69090
|
return;
|
|
68861
69091
|
}
|
|
68862
|
-
const artifact =
|
|
68863
|
-
const reqs = artifact.requirements
|
|
68864
|
-
const acs = artifact.acceptanceCriteria
|
|
69092
|
+
const artifact = outcome.verdict;
|
|
69093
|
+
const reqs = artifact.requirements;
|
|
69094
|
+
const acs = artifact.acceptanceCriteria;
|
|
68865
69095
|
const isUnknown = artifact.verdict === "UNKNOWN";
|
|
68866
69096
|
const isEmpty = reqs.length === 0 && acs.length === 0;
|
|
68867
69097
|
if (isUnknown || isEmpty) {
|
|
@@ -68871,7 +69101,7 @@ var init_task_check = __esm(() => {
|
|
|
68871
69101
|
code: FINDING_CODES.L4_MALFORMED_VERDICT_ARTIFACT,
|
|
68872
69102
|
severity: "error",
|
|
68873
69103
|
section: "Testing",
|
|
68874
|
-
message: `Verdict artifact at ${
|
|
69104
|
+
message: `Verdict artifact at ${verdictPath} is malformed: ${reason}`
|
|
68875
69105
|
});
|
|
68876
69106
|
}
|
|
68877
69107
|
}
|
|
@@ -68883,9 +69113,6 @@ import { basename as basename4, dirname as dirname10, join as join11, relative,
|
|
|
68883
69113
|
function baselineSeverity(e) {
|
|
68884
69114
|
return e.severity ?? "error";
|
|
68885
69115
|
}
|
|
68886
|
-
function key(e) {
|
|
68887
|
-
return `${e.kind}:${e.id}:${e.code}`;
|
|
68888
|
-
}
|
|
68889
69116
|
function resolveProjectRoot(cwd) {
|
|
68890
69117
|
const fs3 = createNodeFileSystem3(cwd);
|
|
68891
69118
|
let current = resolve3(cwd);
|
|
@@ -69253,12 +69480,29 @@ async function runCorpusCheck(cwd, since) {
|
|
|
69253
69480
|
}
|
|
69254
69481
|
return result;
|
|
69255
69482
|
}
|
|
69483
|
+
async function loadAcceptedFindings(cwd) {
|
|
69484
|
+
const projectRoot = resolveProjectRoot(cwd);
|
|
69485
|
+
const baselineFile = join11(projectRoot, "config", "corpus-baseline.json");
|
|
69486
|
+
const accepted = new Map;
|
|
69487
|
+
try {
|
|
69488
|
+
if (await Bun.file(baselineFile).exists()) {
|
|
69489
|
+
const baseline = await Bun.file(baselineFile).json();
|
|
69490
|
+
if (Array.isArray(baseline?.entries)) {
|
|
69491
|
+
for (const e of baseline.entries) {
|
|
69492
|
+
accepted.set(key(e), baselineSeverity(e));
|
|
69493
|
+
}
|
|
69494
|
+
}
|
|
69495
|
+
}
|
|
69496
|
+
} catch {}
|
|
69497
|
+
return accepted;
|
|
69498
|
+
}
|
|
69256
69499
|
var FOG_HEADING, OUT_OF_SCOPE_HEADING, FEATURE_ID, DEFAULT_BRANCHES;
|
|
69257
69500
|
var init_corpus_check = __esm(() => {
|
|
69258
69501
|
init_loader();
|
|
69259
69502
|
init_dist5();
|
|
69260
69503
|
init_dist2();
|
|
69261
69504
|
init_feature_check();
|
|
69505
|
+
init_planning_check_base();
|
|
69262
69506
|
init_task_check();
|
|
69263
69507
|
init_task_locator();
|
|
69264
69508
|
FOG_HEADING = /^###\s+Not yet specified\b/;
|
|
@@ -74731,78 +74975,6 @@ var init_task_size_precheck = __esm(() => {
|
|
|
74731
74975
|
|
|
74732
74976
|
// ../../packages/app/src/services/task-service.ts
|
|
74733
74977
|
import { dirname as dirname14, isAbsolute as isAbsolute4, join as join16, relative as relative4 } from "path";
|
|
74734
|
-
function patchFrontmatterField(rendered, key2, value) {
|
|
74735
|
-
const openIdx = rendered.indexOf("---");
|
|
74736
|
-
if (openIdx === -1)
|
|
74737
|
-
return rendered;
|
|
74738
|
-
let fmStart = openIdx + 3;
|
|
74739
|
-
if (rendered[fmStart] === "\r")
|
|
74740
|
-
fmStart += 1;
|
|
74741
|
-
if (rendered[fmStart] === `
|
|
74742
|
-
`)
|
|
74743
|
-
fmStart += 1;
|
|
74744
|
-
const closeRel = rendered.indexOf(`
|
|
74745
|
-
---`, fmStart);
|
|
74746
|
-
if (closeRel === -1)
|
|
74747
|
-
return rendered;
|
|
74748
|
-
const before = rendered.slice(0, fmStart);
|
|
74749
|
-
const fm = rendered.slice(fmStart, closeRel);
|
|
74750
|
-
const after = rendered.slice(closeRel);
|
|
74751
|
-
const existingRe = new RegExp(`^${escapeRegex2(key2)}:.*$`, "m");
|
|
74752
|
-
if (existingRe.test(fm)) {
|
|
74753
|
-
const newFm = fm.replace(existingRe, () => `${key2}: ${value}`);
|
|
74754
|
-
return before + newFm + after;
|
|
74755
|
-
}
|
|
74756
|
-
return `${before}${key2}: ${value}
|
|
74757
|
-
${fm}${after}`;
|
|
74758
|
-
}
|
|
74759
|
-
function escapeRegex2(s2) {
|
|
74760
|
-
return s2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
74761
|
-
}
|
|
74762
|
-
function renderCreatedTaskContent(params) {
|
|
74763
|
-
let content = renderTaskTemplate(params.rawTemplate, {
|
|
74764
|
-
NAME: params.name,
|
|
74765
|
-
WBS: params.wbs,
|
|
74766
|
-
BACKGROUND: params.background,
|
|
74767
|
-
CREATED_AT: params.createdAt,
|
|
74768
|
-
...params.featureId !== undefined ? { FEATURE_ID: params.featureId } : {}
|
|
74769
|
-
});
|
|
74770
|
-
content = patchFrontmatterField(content, "status", params.status);
|
|
74771
|
-
content = patchFrontmatterField(content, "template", params.variant);
|
|
74772
|
-
if (params.featureId !== undefined) {
|
|
74773
|
-
content = patchFrontmatterField(content, "feature_id", escapeYamlValue(params.featureId));
|
|
74774
|
-
}
|
|
74775
|
-
if (params.parentWbs !== undefined) {
|
|
74776
|
-
content = patchFrontmatterField(content, "parent_wbs", escapeYamlValue(params.parentWbs));
|
|
74777
|
-
}
|
|
74778
|
-
if (params.priority !== undefined) {
|
|
74779
|
-
content = patchFrontmatterField(content, "priority", params.priority);
|
|
74780
|
-
}
|
|
74781
|
-
if (params.tags !== undefined && params.tags.length > 0) {
|
|
74782
|
-
content = patchFrontmatterField(content, "tags", `[${params.tags.map((tag) => JSON.stringify(tag)).join(", ")}]`);
|
|
74783
|
-
}
|
|
74784
|
-
const sectionPatches = {};
|
|
74785
|
-
if ((params.requirements ?? "").trim() !== "") {
|
|
74786
|
-
sectionPatches.Requirements = bulletizeRequirements(params.requirements ?? "");
|
|
74787
|
-
}
|
|
74788
|
-
if ((params.design ?? "").trim() !== "") {
|
|
74789
|
-
sectionPatches.Design = (params.design ?? "").trim();
|
|
74790
|
-
}
|
|
74791
|
-
if ((params.plan ?? "").trim() !== "") {
|
|
74792
|
-
sectionPatches.Plan = (params.plan ?? "").trim();
|
|
74793
|
-
}
|
|
74794
|
-
if ((params.acceptanceCriteria ?? "").trim() !== "") {
|
|
74795
|
-
sectionPatches["Acceptance Criteria"] = normalizeAcFence((params.acceptanceCriteria ?? "").trim());
|
|
74796
|
-
}
|
|
74797
|
-
if (Object.keys(sectionPatches).length > 0) {
|
|
74798
|
-
const doc2 = MarkdownDocument.parse(content, "task");
|
|
74799
|
-
for (const [section, body] of Object.entries(sectionPatches)) {
|
|
74800
|
-
doc2.replaceSection(section, body);
|
|
74801
|
-
}
|
|
74802
|
-
content = doc2.serialize();
|
|
74803
|
-
}
|
|
74804
|
-
return content;
|
|
74805
|
-
}
|
|
74806
74978
|
function renderRosterTable(rows) {
|
|
74807
74979
|
const header = `| WBS | Sub-task | Status |
|
|
74808
74980
|
| --- | -------- | ------ |`;
|
|
@@ -74868,12 +75040,14 @@ class TaskService {
|
|
|
74868
75040
|
}
|
|
74869
75041
|
sectionsForStatus(variant, status) {
|
|
74870
75042
|
const matrix = this.ctx.sectionMatrix;
|
|
74871
|
-
|
|
74872
|
-
|
|
74873
|
-
return [...entry.required ?? [], ...entry.optional ?? [], "History"];
|
|
75043
|
+
if (matrix === undefined) {
|
|
75044
|
+
throw new Error(`no section-matrix available for create (variant=${variant}, status=${status}); ` + "a canonical section-matrix.yaml is required for task creation (F92 R1)");
|
|
74874
75045
|
}
|
|
74875
|
-
const
|
|
74876
|
-
|
|
75046
|
+
const entry = matrix.variants[variant]?.[status] ?? matrix.variants.standard?.[status];
|
|
75047
|
+
if (entry === undefined) {
|
|
75048
|
+
throw new Error(`no section-matrix entry for variant="${variant}" status="${status}" during create; ` + "resolve the canonical section-matrix.yaml");
|
|
75049
|
+
}
|
|
75050
|
+
return [...entry.required ?? [], ...entry.optional ?? [], "References", "History"];
|
|
74877
75051
|
}
|
|
74878
75052
|
bodiesFor(variant, taskBodies) {
|
|
74879
75053
|
const templateBodies = this.ctx.resolveTemplateBodies?.(variant) ?? {};
|
|
@@ -74918,22 +75092,6 @@ class TaskService {
|
|
|
74918
75092
|
const slug = this.slugify(params.title);
|
|
74919
75093
|
const { wbs, filePath } = await this.allocateWbsChecked(slug);
|
|
74920
75094
|
const now2 = new Date().toISOString();
|
|
74921
|
-
const rawTemplate = this.ctx.resolveTemplate?.(variant);
|
|
74922
|
-
if (rawTemplate !== undefined) {
|
|
74923
|
-
const content2 = renderCreatedTaskContent({
|
|
74924
|
-
rawTemplate,
|
|
74925
|
-
name: params.title,
|
|
74926
|
-
wbs,
|
|
74927
|
-
background,
|
|
74928
|
-
createdAt: now2,
|
|
74929
|
-
status,
|
|
74930
|
-
variant,
|
|
74931
|
-
featureId: params.featureId,
|
|
74932
|
-
parentWbs: params.parentWbs
|
|
74933
|
-
});
|
|
74934
|
-
const ref2 = { kind: "task", id: wbs, filePath, folder };
|
|
74935
|
-
return { ref: ref2, content: content2 };
|
|
74936
|
-
}
|
|
74937
75095
|
const frontmatter = [
|
|
74938
75096
|
"schema_version: 1",
|
|
74939
75097
|
`name: "${params.title}"`,
|
|
@@ -75431,28 +75589,6 @@ ${issues}`);
|
|
|
75431
75589
|
const slug = this.slugify(item.name);
|
|
75432
75590
|
const { wbs, filePath } = await this.allocateWbsChecked(slug);
|
|
75433
75591
|
const now2 = new Date().toISOString();
|
|
75434
|
-
const rawTemplate = this.ctx.resolveTemplate?.(variant);
|
|
75435
|
-
if (rawTemplate !== undefined) {
|
|
75436
|
-
const content2 = renderCreatedTaskContent({
|
|
75437
|
-
rawTemplate,
|
|
75438
|
-
name: item.name,
|
|
75439
|
-
wbs,
|
|
75440
|
-
background,
|
|
75441
|
-
createdAt: now2,
|
|
75442
|
-
status,
|
|
75443
|
-
variant,
|
|
75444
|
-
featureId: item.feature_id ?? undefined,
|
|
75445
|
-
parentWbs: item.parent_wbs ?? undefined,
|
|
75446
|
-
priority: item.priority,
|
|
75447
|
-
tags: item.tags,
|
|
75448
|
-
requirements: item.requirements,
|
|
75449
|
-
design: item.design,
|
|
75450
|
-
plan: item.plan,
|
|
75451
|
-
acceptanceCriteria: item.acceptance_criteria
|
|
75452
|
-
});
|
|
75453
|
-
const ref2 = { kind: "task", id: wbs, filePath, folder };
|
|
75454
|
-
return { ref: ref2, content: content2 };
|
|
75455
|
-
}
|
|
75456
75592
|
const fmLines = [
|
|
75457
75593
|
"schema_version: 1",
|
|
75458
75594
|
`name: "${item.name}"`,
|
|
@@ -75676,7 +75812,7 @@ ${block}` : block);
|
|
|
75676
75812
|
return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60);
|
|
75677
75813
|
}
|
|
75678
75814
|
}
|
|
75679
|
-
var DependencyMutationError, SectionMutationError, DuplicateFollowUpError, WbsCollisionError, ROSTER_START = "<!-- AUTO-GENERATED by spur task refresh-roster -->", ROSTER_END = "<!-- END AUTO-GENERATED -->", ROSTER_REGION_RE, TASK_ACTION_COMMANDS
|
|
75815
|
+
var DependencyMutationError, SectionMutationError, DuplicateFollowUpError, WbsCollisionError, ROSTER_START = "<!-- AUTO-GENERATED by spur task refresh-roster -->", ROSTER_END = "<!-- END AUTO-GENERATED -->", ROSTER_REGION_RE, TASK_ACTION_COMMANDS;
|
|
75680
75816
|
var init_task_service = __esm(() => {
|
|
75681
75817
|
init_src2();
|
|
75682
75818
|
init_errors7();
|
|
@@ -75734,10 +75870,6 @@ var init_task_service = __esm(() => {
|
|
|
75734
75870
|
decompose: (wbs) => `/sp:dev-plan "Decompose task ${wbs} into implementation subtasks" --auto`,
|
|
75735
75871
|
evaluate: (wbs) => `/sp:dev-review ${wbs} --auto`
|
|
75736
75872
|
};
|
|
75737
|
-
DEFAULT_CREATION_SECTIONS = {
|
|
75738
|
-
backlog: ["Background"],
|
|
75739
|
-
todo: ["Background", "Requirements", "Acceptance Criteria", "Q&A", "Design", "Plan"]
|
|
75740
|
-
};
|
|
75741
75873
|
});
|
|
75742
75874
|
|
|
75743
75875
|
// ../../packages/app/src/services/task-verdict.ts
|
|
@@ -75749,20 +75881,13 @@ function deriveVerdict(answerText, taskCheckPassed) {
|
|
|
75749
75881
|
if (requirements.length === 0) {
|
|
75750
75882
|
return { verdict: "UNKNOWN", requirements, acceptanceCriteria, checks: checks4 };
|
|
75751
75883
|
}
|
|
75752
|
-
const
|
|
75753
|
-
|
|
75754
|
-
|
|
75755
|
-
|
|
75756
|
-
|
|
75757
|
-
|
|
75758
|
-
|
|
75759
|
-
if (hasPartial || hasPartialAc) {
|
|
75760
|
-
return { verdict: "PARTIAL", requirements, acceptanceCriteria, checks: checks4 };
|
|
75761
|
-
}
|
|
75762
|
-
if (!taskCheckPassed) {
|
|
75763
|
-
return { verdict: "PARTIAL", requirements, acceptanceCriteria, checks: checks4 };
|
|
75764
|
-
}
|
|
75765
|
-
return { verdict: "PASS", requirements, acceptanceCriteria, checks: checks4 };
|
|
75884
|
+
const aggregate2 = aggregateVerifyVerdict({
|
|
75885
|
+
requirements,
|
|
75886
|
+
acceptanceCriteria,
|
|
75887
|
+
checks: [],
|
|
75888
|
+
taskCheckPassed
|
|
75889
|
+
});
|
|
75890
|
+
return { verdict: aggregate2, requirements, acceptanceCriteria, checks: checks4 };
|
|
75766
75891
|
}
|
|
75767
75892
|
function extractRequirements(text4) {
|
|
75768
75893
|
const reqs = [];
|
|
@@ -75774,7 +75899,7 @@ function extractRequirements(text4) {
|
|
|
75774
75899
|
const trimmed = line.trim();
|
|
75775
75900
|
if (!trimmed.startsWith("|"))
|
|
75776
75901
|
continue;
|
|
75777
|
-
const cells = trimmed
|
|
75902
|
+
const cells = splitTableCells(trimmed);
|
|
75778
75903
|
if (!inTable && cells.length >= 2) {
|
|
75779
75904
|
const h0 = (cells[0] ?? "").toLowerCase().trim();
|
|
75780
75905
|
const h0IsId = h0.includes("req") || h0 === "requirement" || h0 === "r#" || h0 === "r" || /^r\d+$/.test(h0);
|
|
@@ -75825,6 +75950,9 @@ function normalizeStatus(raw) {
|
|
|
75825
75950
|
return "UNMET";
|
|
75826
75951
|
return null;
|
|
75827
75952
|
}
|
|
75953
|
+
function splitTableCells(row) {
|
|
75954
|
+
return row.split(/(?<!\\)\|/).map((c3) => c3.replace(/\\\|/g, "|").trim()).filter(Boolean);
|
|
75955
|
+
}
|
|
75828
75956
|
function extractAcceptanceCriteria(text4) {
|
|
75829
75957
|
const rows = [];
|
|
75830
75958
|
const dropped = [];
|
|
@@ -75833,9 +75961,13 @@ function extractAcceptanceCriteria(text4) {
|
|
|
75833
75961
|
let inTable = false;
|
|
75834
75962
|
for (const line of lines) {
|
|
75835
75963
|
const trimmed = line.trim();
|
|
75964
|
+
if (inTable && /^#{1,6}\s/.test(trimmed)) {
|
|
75965
|
+
inTable = false;
|
|
75966
|
+
continue;
|
|
75967
|
+
}
|
|
75836
75968
|
if (!trimmed.startsWith("|"))
|
|
75837
75969
|
continue;
|
|
75838
|
-
const cells = trimmed
|
|
75970
|
+
const cells = splitTableCells(trimmed);
|
|
75839
75971
|
if (!inTable && cells.length >= 4) {
|
|
75840
75972
|
const h0 = (cells[0] ?? "").toLowerCase();
|
|
75841
75973
|
const h1 = (cells[1] ?? "").toLowerCase();
|
|
@@ -75931,7 +76063,7 @@ function extractChecks(_text, taskCheckPassed, acceptanceCriteria, droppedAcRows
|
|
|
75931
76063
|
const trimmed = line.trim();
|
|
75932
76064
|
if (!trimmed.startsWith("|"))
|
|
75933
76065
|
continue;
|
|
75934
|
-
const cells = trimmed
|
|
76066
|
+
const cells = splitTableCells(trimmed);
|
|
75935
76067
|
if (cells.length >= 2) {
|
|
75936
76068
|
const h0 = (cells[0] ?? "").toLowerCase();
|
|
75937
76069
|
const h1 = (cells[1] ?? "").toLowerCase();
|
|
@@ -75999,6 +76131,7 @@ function aggregateBatchVerdicts(results) {
|
|
|
75999
76131
|
}
|
|
76000
76132
|
var EVIDENCE_TYPE_PRECEDENCE, NOT_STARTED_STATUSES;
|
|
76001
76133
|
var init_task_verdict = __esm(() => {
|
|
76134
|
+
init_verify_verdict();
|
|
76002
76135
|
EVIDENCE_TYPE_PRECEDENCE = ["test", "command", "static-ref", "manual-review", "llm-judge", "n/a"];
|
|
76003
76136
|
NOT_STARTED_STATUSES = {
|
|
76004
76137
|
backlog: true,
|
|
@@ -79461,6 +79594,7 @@ __export(exports_src2, {
|
|
|
79461
79594
|
parseEtimeToSeconds: () => parseEtimeToSeconds,
|
|
79462
79595
|
normalizeSystemEventPayload: () => normalizeSystemEventPayload,
|
|
79463
79596
|
normalizeProjectPath: () => normalizeProjectPath,
|
|
79597
|
+
loadAcceptedFindings: () => loadAcceptedFindings,
|
|
79464
79598
|
isSystemEventEnvelopeV2: () => isSystemEventEnvelopeV2,
|
|
79465
79599
|
isPortLive: () => isPortLive,
|
|
79466
79600
|
isPortAvailable: () => isPortAvailable,
|
|
@@ -79594,6 +79728,7 @@ var init_src3 = __esm(() => {
|
|
|
79594
79728
|
init_anchor_qualifier();
|
|
79595
79729
|
init_corpus_check();
|
|
79596
79730
|
init_corpus_migrator();
|
|
79731
|
+
init_done_transition_guard();
|
|
79597
79732
|
init_event_names();
|
|
79598
79733
|
init_failure_classification();
|
|
79599
79734
|
init_feature_check();
|
|
@@ -87871,7 +88006,7 @@ import { createRequire } from "module";
|
|
|
87871
88006
|
var CLI_CONFIG = {
|
|
87872
88007
|
binaryName: "spur",
|
|
87873
88008
|
binaryLabel: "spur",
|
|
87874
|
-
binaryVersion: "0.3.
|
|
88009
|
+
binaryVersion: "0.3.52",
|
|
87875
88010
|
configDir: ".spur",
|
|
87876
88011
|
configFile: ".spur/config.yaml",
|
|
87877
88012
|
databaseFile: ".spur/spur.db"
|
|
@@ -98238,7 +98373,8 @@ function createServerContext(appRt, options) {
|
|
|
98238
98373
|
writeService: new PlanningWriteService({ fs: fs3, projectName: "spur", emitter: lazyEmitter }),
|
|
98239
98374
|
tasksDir: folders.tasksDir,
|
|
98240
98375
|
foldersConfig: folders.foldersConfig,
|
|
98241
|
-
projectName: "spur"
|
|
98376
|
+
projectName: "spur",
|
|
98377
|
+
...options.sectionMatrix !== undefined ? { sectionMatrix: options.sectionMatrix } : {}
|
|
98242
98378
|
});
|
|
98243
98379
|
}
|
|
98244
98380
|
return taskSvc;
|
|
@@ -98418,6 +98554,27 @@ async function openUrl(url2, deps = {}) {
|
|
|
98418
98554
|
|
|
98419
98555
|
// ../server/src/serve.ts
|
|
98420
98556
|
init_src3();
|
|
98557
|
+
async function loadServerSectionMatrix() {
|
|
98558
|
+
const cwd = process.cwd();
|
|
98559
|
+
const nodeFs = createNodeFileSystem3(cwd);
|
|
98560
|
+
const localPath = nodeFs.resolve(".spur", "tasks", "section-matrix.yaml");
|
|
98561
|
+
if (await nodeFs.exists(localPath)) {
|
|
98562
|
+
return await loadStructuredSpurConfig(localPath, { validateJsonSchema: false });
|
|
98563
|
+
}
|
|
98564
|
+
const root = bundledConfigRoot();
|
|
98565
|
+
if (root !== null) {
|
|
98566
|
+
const matrixPath = join27(root, "tasks", "section-matrix.yaml");
|
|
98567
|
+
if (await nodeFs.exists(matrixPath)) {
|
|
98568
|
+
return await loadStructuredSpurConfig(matrixPath, {
|
|
98569
|
+
validateJsonSchema: false
|
|
98570
|
+
});
|
|
98571
|
+
}
|
|
98572
|
+
}
|
|
98573
|
+
throw new Error(`no canonical section-matrix found for task creation (F92 R1); tried:
|
|
98574
|
+
` + ` - ${localPath}
|
|
98575
|
+
` + (root !== null ? ` - ${join27(root, "tasks", "section-matrix.yaml")}
|
|
98576
|
+
` : "") + "copy/generate section-matrix.yaml from the canonical build-time matrix asset (repo `config` `tasks` tree) into one of those paths");
|
|
98577
|
+
}
|
|
98421
98578
|
var SYSTEM_EVENTS_PRUNE_JOB = "system-events-prune";
|
|
98422
98579
|
var SMOKE_JOB = "smoke";
|
|
98423
98580
|
var TASK_ACTION_JOB = "task-action";
|
|
@@ -98589,6 +98746,7 @@ async function startServer(options, deps = defaultDeps) {
|
|
|
98589
98746
|
fs: fs3,
|
|
98590
98747
|
dbUrl: options.dbUrl,
|
|
98591
98748
|
folders: await resolvePlanningFolders(fs3),
|
|
98749
|
+
sectionMatrix: await loadServerSectionMatrix(),
|
|
98592
98750
|
webDistPath,
|
|
98593
98751
|
jobQueueEnabled: bootConfig.jobqueue.enabled,
|
|
98594
98752
|
scheduler: scheduler3,
|
|
@@ -99871,7 +100029,8 @@ ${result.content}`);
|
|
|
99871
100029
|
task.command("update").summary("Update a task status or replace a section.").argument("<wbs>", "Task WBS number").argument("[status]", "New status (for lifecycle transition)").addHelpText("after", [
|
|
99872
100030
|
"Lifecycle: `task update <wbs> <status>` moves a task through",
|
|
99873
100031
|
"backlog \u2192 todo \u2192 wip \u2192 testing \u2192 done, running the lifecycle guards on",
|
|
99874
|
-
"`wip \u2192 testing` (`spur task check`) and `testing \u2192 done`
|
|
100032
|
+
"`wip \u2192 testing` (`spur task check --as testing`) and `testing \u2192 done`",
|
|
100033
|
+
"(`spur task check --as done`) \u2014 each guard evaluates the transition target (F92 R3).",
|
|
99875
100034
|
"A GuardDeniedError on `testing \u2192 done` means no pipeline run is recorded for the",
|
|
99876
100035
|
"task: run `/sp:dev-verify <wbs> --next` to PASS it, or record the audited bypass with",
|
|
99877
100036
|
'`SPUR_PROVENANCE_OVERRIDE=1 spur task update <wbs> done --force-done --reason "\u2026"`.',
|
|
@@ -99913,7 +100072,7 @@ ${result.content}`);
|
|
|
99913
100072
|
if (options.lifecycle !== false) {
|
|
99914
100073
|
context4.output.error(`warning: lifecycle adapter unavailable \u2014 running \`spur task check\` inline as the ${status} gate. ` + "Restore the bundled task-lifecycle workflow to re-enable the real guard.");
|
|
99915
100074
|
}
|
|
99916
|
-
const ok = await runDoneGateCheck(context4, wbs, options.folder);
|
|
100075
|
+
const ok = await runDoneGateCheck(context4, wbs, options.folder, status);
|
|
99917
100076
|
if (!ok) {
|
|
99918
100077
|
context4.output.error(`Lifecycle transition blocked: \`spur task check ${wbs}\` failed. Fix the findings before transitioning to ${status}.`);
|
|
99919
100078
|
context4.setExitCode(1);
|
|
@@ -100376,9 +100535,20 @@ ${result.content}`);
|
|
|
100376
100535
|
context4.setExitCode(1);
|
|
100377
100536
|
}
|
|
100378
100537
|
});
|
|
100379
|
-
task.command("check").summary("Validate a task file through the four-layer check (design \xA73).").argument("[wbs]", "Task WBS number (validates all tasks in the folder when omitted)").option("--strict", "Elevate ALL warnings to failures").option("--strict-core", "
|
|
100538
|
+
task.command("check").summary("Validate a task file through the four-layer check (design \xA73).").argument("[wbs]", "Task WBS number (validates all tasks in the folder when omitted)").option("--strict", "Elevate ALL warnings to failures").option("--strict-core", "Compatibility alias (F92 R2): historically the done-gate label; kept so installed plugins/workflows that call it keep working. No longer meaningful on its own \u2014 target-state selection (`--as`) supplies the real done semantics.").option("--as <status>", "Evaluate the task AS if it were in <status> (F92 R2): the lifecycle guards pass the transition target so testing\u2192done checks the done row. Validate against canonical task statuses. Omitted \u2192 current-status diagnostics.").option("--corpus", "Sweep every task and feature against config/corpus-baseline.json").option("--since <ref>", "Scope the corpus fog check to changes since a git ref (requires --corpus)").option("--folder <path>", "Custom tasks folder").option("--json", "Output machine-readable JSON").action(async (wbs, options) => {
|
|
100380
100539
|
const json3 = options.json === true;
|
|
100381
100540
|
const strict = options.strict === true;
|
|
100541
|
+
const asStatus = options.as === undefined ? undefined : canonicalStatusOrRaw(options.as);
|
|
100542
|
+
if (options.as !== undefined && !TASK_STATUSES.includes(asStatus ?? "")) {
|
|
100543
|
+
context4.output.error(`invalid --as status "${options.as}" (canonical: ${TASK_STATUSES.join(", ")})`);
|
|
100544
|
+
context4.setExitCode(2);
|
|
100545
|
+
return;
|
|
100546
|
+
}
|
|
100547
|
+
if (asStatus !== undefined && options.corpus === true) {
|
|
100548
|
+
context4.output.error("--as <status> is a single-task target projection and cannot be combined with --corpus");
|
|
100549
|
+
context4.setExitCode(2);
|
|
100550
|
+
return;
|
|
100551
|
+
}
|
|
100382
100552
|
try {
|
|
100383
100553
|
if (options.corpus === true) {
|
|
100384
100554
|
if (wbs !== undefined) {
|
|
@@ -100429,6 +100599,7 @@ ${result.content}`);
|
|
|
100429
100599
|
}
|
|
100430
100600
|
const svc = await makeCheckService(context4);
|
|
100431
100601
|
const planningFolders = await resolvePlanningFolders(context4.fs);
|
|
100602
|
+
const accepted = await loadAcceptedFindings(context4.cwd);
|
|
100432
100603
|
const activeFolder = planningFolders.foldersConfig.active_folder;
|
|
100433
100604
|
const tasksDir = context4.fs.resolve(options.folder ?? activeFolder);
|
|
100434
100605
|
const printResult = (result) => {
|
|
@@ -100454,7 +100625,9 @@ ${result.wbs} (${result.status}): ${result.pass ? "PASS" : "FAIL"}`);
|
|
|
100454
100625
|
} else {
|
|
100455
100626
|
const result = await svc.check(hit.filePath, wbs, {
|
|
100456
100627
|
strict,
|
|
100457
|
-
|
|
100628
|
+
asStatus,
|
|
100629
|
+
severityOverrides: planningFolders.severityOverrides,
|
|
100630
|
+
accepted
|
|
100458
100631
|
});
|
|
100459
100632
|
results.push(result);
|
|
100460
100633
|
printResult(result);
|
|
@@ -100471,7 +100644,9 @@ ${result.wbs} (${result.status}): ${result.pass ? "PASS" : "FAIL"}`);
|
|
|
100471
100644
|
}
|
|
100472
100645
|
const result = await svc.check(`${tasksDir}/${fileName}`, w, {
|
|
100473
100646
|
strict,
|
|
100474
|
-
|
|
100647
|
+
asStatus,
|
|
100648
|
+
severityOverrides: planningFolders.severityOverrides,
|
|
100649
|
+
accepted
|
|
100475
100650
|
});
|
|
100476
100651
|
results.push(result);
|
|
100477
100652
|
printResult(result);
|
|
@@ -100610,36 +100785,10 @@ async function makeService2(context4, folderOverride, noLifecycle = false) {
|
|
|
100610
100785
|
writeService,
|
|
100611
100786
|
getDb: () => context4.getDb(),
|
|
100612
100787
|
sectionMatrix: await loadSectionMatrix(context4.cwd),
|
|
100613
|
-
resolveTemplate: (variant) => loadTemplateContent(context4.cwd, variant),
|
|
100614
100788
|
resolveTemplateBodies: (variant) => loadTemplateBodies(context4.cwd, variant),
|
|
100615
100789
|
foldersConfig
|
|
100616
100790
|
});
|
|
100617
100791
|
}
|
|
100618
|
-
var templateContentCache = new Map;
|
|
100619
|
-
var templateMissSet = new Set;
|
|
100620
|
-
function loadTemplateContent(projectRoot, variant) {
|
|
100621
|
-
if (templateContentCache.has(variant))
|
|
100622
|
-
return templateContentCache.get(variant);
|
|
100623
|
-
if (templateMissSet.has(variant))
|
|
100624
|
-
return;
|
|
100625
|
-
const localPath = join30(projectRoot, ".spur", "tasks", "templates", `${variant}.md`);
|
|
100626
|
-
if (existsSync11(localPath)) {
|
|
100627
|
-
const content = readFileSync10(localPath, "utf8");
|
|
100628
|
-
templateContentCache.set(variant, content);
|
|
100629
|
-
return content;
|
|
100630
|
-
}
|
|
100631
|
-
const root = bundledConfigRoot();
|
|
100632
|
-
if (root !== null) {
|
|
100633
|
-
const templatePath = join30(root, "templates", "task", `${variant}.md`);
|
|
100634
|
-
if (existsSync11(templatePath)) {
|
|
100635
|
-
const content = readFileSync10(templatePath, "utf8");
|
|
100636
|
-
templateContentCache.set(variant, content);
|
|
100637
|
-
return content;
|
|
100638
|
-
}
|
|
100639
|
-
}
|
|
100640
|
-
templateMissSet.add(variant);
|
|
100641
|
-
return;
|
|
100642
|
-
}
|
|
100643
100792
|
var templateBodiesCache = new Map;
|
|
100644
100793
|
function loadTemplateBodies(projectRoot, variant) {
|
|
100645
100794
|
const cached2 = templateBodiesCache.get(variant);
|
|
@@ -100672,15 +100821,22 @@ async function makeTaskLocator(context4) {
|
|
|
100672
100821
|
async function makeCheckService(context4) {
|
|
100673
100822
|
return new TaskCheckService(context4.fs, await loadSectionMatrix(context4.cwd), await makeTaskLocator(context4));
|
|
100674
100823
|
}
|
|
100675
|
-
async function runDoneGateCheck(context4, wbs, folderOverride) {
|
|
100676
|
-
const
|
|
100824
|
+
async function runDoneGateCheck(context4, wbs, folderOverride, targetStatus) {
|
|
100825
|
+
const planningFolders = await resolvePlanningFolders(context4.fs);
|
|
100826
|
+
const foldersConfig = planningFolders.foldersConfig;
|
|
100677
100827
|
const tasksDir = folderOverride ?? context4.fs.resolve(foldersConfig.active_folder);
|
|
100678
100828
|
const hit = await new TaskLocator({ fs: context4.fs, tasksDir, foldersConfig }).findByWbs(wbs);
|
|
100679
100829
|
if (!hit) {
|
|
100680
100830
|
return false;
|
|
100681
100831
|
}
|
|
100682
100832
|
const svc = new TaskCheckService(context4.fs, await loadSectionMatrix(context4.cwd), await makeTaskLocator(context4));
|
|
100683
|
-
const
|
|
100833
|
+
const accepted = await loadAcceptedFindings(context4.cwd);
|
|
100834
|
+
const result = await svc.check(hit.filePath, wbs, {
|
|
100835
|
+
strict: false,
|
|
100836
|
+
asStatus: targetStatus,
|
|
100837
|
+
severityOverrides: planningFolders.severityOverrides,
|
|
100838
|
+
accepted
|
|
100839
|
+
});
|
|
100684
100840
|
return result.pass;
|
|
100685
100841
|
}
|
|
100686
100842
|
var sectionMatrixCache = new Map;
|
|
@@ -100714,16 +100870,11 @@ async function loadSectionMatrixUncached(projectRoot) {
|
|
|
100714
100870
|
return data;
|
|
100715
100871
|
}
|
|
100716
100872
|
}
|
|
100717
|
-
|
|
100873
|
+
throw new Error(`no canonical section-matrix found for task section authority (F92 R1); tried:
|
|
100874
|
+
` + ` - ${localPath}
|
|
100875
|
+
` + (root !== null ? ` - ${join30(root, "tasks", "section-matrix.yaml")}
|
|
100876
|
+
` : "") + "copy/generate section-matrix.yaml from the canonical build-time matrix asset (repo `config` `tasks` tree) into one of those paths");
|
|
100718
100877
|
}
|
|
100719
|
-
var FALLBACK_MATRIX = {
|
|
100720
|
-
variants: {
|
|
100721
|
-
standard: {
|
|
100722
|
-
backlog: { required: ["Background"], forbidden: ["Solution", "Review", "Testing"] },
|
|
100723
|
-
done: { required: ["Solution", "Testing", "Review"], gate: true }
|
|
100724
|
-
}
|
|
100725
|
-
}
|
|
100726
|
-
};
|
|
100727
100878
|
|
|
100728
100879
|
// src/commands/team.ts
|
|
100729
100880
|
init_src3();
|