@gobing-ai/spur 0.4.4 → 0.4.6
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/config.example.yaml +3 -4
- package/config/config.global.yaml +3 -4
- package/config/rules/docs/sourced-api-claims.yaml +3 -2
- package/config/rules/quality/coverage-gate.yaml +6 -4
- package/config/rules/recommended-pre-check.yaml +11 -9
- package/config/transition-shims.json +0 -14
- package/config/workflows/task-pipeline.yaml +31 -71
- package/package.json +1 -1
- package/plugins/sp/agents/super-reviewer.md +11 -27
- package/plugins/sp/commands/dev-review.md +1 -1
- package/plugins/sp/lib/inline-run.generated.mjs +3 -3
- package/plugins/sp/lib/residual-scan.generated.d.mts +0 -13
- package/plugins/sp/lib/residual-scan.generated.mjs +3 -47
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/scripts/commit-guard.mjs +49 -39
- package/plugins/sp/scripts/commit-guard.ts +60 -61
- package/plugins/sp/scripts/persist-out-check.mjs +28 -24
- package/plugins/sp/scripts/persist-out-check.ts +42 -33
- package/plugins/sp/scripts/residual-scan.mjs +5 -50
- package/plugins/sp/scripts/residual-scan.ts +4 -6
- package/plugins/sp/skills/code-verification/SKILL.md +1 -2
- package/plugins/sp/skills/code-verification/references/verdict-schema.md +16 -35
- package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +4 -2
- package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +3 -1
- package/plugins/sp/skills/spur-dev/references/done-housekeeping.md +3 -2
- package/plugins/sp/skills/spur-dev/references/execution-workflow.md +7 -11
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +7 -6
- package/spur.js +422 -431
- package/web/_astro/{BoardApp.hF0RAIrg.js → BoardApp.C5jnkkKP.js} +124 -124
- package/web/_astro/{BoardApp.Niubpe6z.js → BoardApp.CaNvQBD_.js} +1 -1
- package/web/_astro/{TaskDetail.DJpapt38.js → TaskDetail.DGKndhI8.js} +1 -1
- package/web/_astro/{arc.BgKcK0i8.js → arc.DHrwdg3G.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.ChP-Ig0V.js → architectureDiagram-3BPJPVTR.pvKpK1s5.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.BVaCBoeN.js → blockDiagram-GPEHLZMM.BQmRcZsS.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.CHSDpEO5.js → c4Diagram-AAUBKEIU.CCLjHeH1.js} +1 -1
- package/web/_astro/channel.C7wG9RxA.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.MYRcYVRa.js → chunk-2J33WTMH.DL6Yuoma.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.S5j22Zqa.js → chunk-4BX2VUAB.CzlyEI7l.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.gciRPvmZ.js → chunk-55IACEB6.B60EWxp1.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.CdqrSQkX.js → chunk-727SXJPM.c7v_fRLo.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.CyokdHT3.js → chunk-AQP2D5EJ.8BxLWdDc.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.Bg9KJhjW.js → chunk-FMBD7UC4.DmKuxGFT.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.1gxxI-vD.js → chunk-ND2GUHAM.1Zwd4mqV.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.DDEYtCKU.js → chunk-QZHKN3VN.BvYLu-A1.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.Ch3zi37T.js → classDiagram-4FO5ZUOK.CEqRt0WU.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.Ch3zi37T.js → classDiagram-v2-Q7XG4LA2.CEqRt0WU.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.D2q5AINI.js → cose-bilkent-S5V4N54A.C7JiUdj2.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.BA9kC2YT.js → cynefin-OW5HDTMX.B6mZ-1be.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.CHO08nRp.js → dagre-BM42HDAG.BuryfDy6.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.BfFQt0LW.js → diagram-2AECGRRQ.dao39YQM.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.CHMwMTe4.js → diagram-5GNKFQAL.lnGZe4At.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.BJ-7gK-w.js → diagram-KO2AKTUF.uWfhzEfT.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.C5xqcc0t.js → diagram-LMA3HP47.ppq8-198.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.C2pQjfrC.js → diagram-OG6HWLK6.CKSCqDt5.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35._FoNyxgC.js → erDiagram-TEJ5UH35.sFSf2yBN.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.B_ENkT2I.js → flowDiagram-I6XJVG4X.BJQjKLbz.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.B3q8h77C.js → ganttDiagram-6RSMTGT7.DAHKNj2N.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.QWZFDboZ.js → gitGraphDiagram-PVQCEYII.BlmzcW_O.js} +1 -1
- package/web/_astro/index.BCY1JJHd.css +1 -0
- package/web/_astro/{infoDiagram-5YYISTIA.Cs6eEc92.js → infoDiagram-5YYISTIA.DGhjaqsi.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.BU9DJMHA.js → ishikawaDiagram-YF4QCWOH.gKIpgt6F.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.D35k6Jpe.js → journeyDiagram-JHISSGLW.DG3tmP0R.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.DBqcAT4m.js → kanban-definition-UN3LZRKU.CgHDSxkR.js} +1 -1
- package/web/_astro/{linear.D-LSNK8W.js → linear.BuGEpcuM.js} +1 -1
- package/web/_astro/{mermaid.core.CSruWVOa.js → mermaid.core.DBVqkWBp.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.BiFaUsfj.js → mindmap-definition-RKZ34NQL.D-kOcji9.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.D1KTKqb9.js → pieDiagram-4H26LBE5.BvGPwtZt.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.DBHflsMG.js → quadrantDiagram-W4KKPZXB.BKb_9FV6.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.hwX9CNdk.js → requirementDiagram-4Y6WPE33.Cx0fvD8v.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.DkI8mJiE.js → sankeyDiagram-5OEKKPKP.fatULi0W.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.DMz7jBDx.js → sequenceDiagram-3UESZ5HK.ChHyKqco.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.B6Ef2xJ1.js → stateDiagram-AJRCARHV.BdUpKWyj.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.CgoaufB9.js → stateDiagram-v2-BHNVJYJU.fmJxqIm5.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.B3c8h1t-.js → timeline-definition-PNZ67QCA.BayDn5-V.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.BdHPAxAb.js → vennDiagram-CIIHVFJN.Bm2Aggwa.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.BrJXPPAj.js → wardleyDiagram-YWT4CUSO.DI75O-uG.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.CMH5yFsV.js → xychartDiagram-2RQKCTM6.DL90E7Q7.js} +1 -1
- package/web/index.html +2 -2
- package/config/rules/docs/verdict-confidence.yaml +0 -68
- package/web/_astro/channel.DkpvN2tD.js +0 -1
- package/web/_astro/index.BFyW13JJ.css +0 -1
package/spur.js
CHANGED
|
@@ -50681,7 +50681,7 @@ class DatabaseInspector {
|
|
|
50681
50681
|
name: row.name,
|
|
50682
50682
|
type: row.type ?? "",
|
|
50683
50683
|
notNull: row.notnull === 1,
|
|
50684
|
-
pk: row.pk
|
|
50684
|
+
pk: row.pk > 0
|
|
50685
50685
|
}));
|
|
50686
50686
|
}
|
|
50687
50687
|
resolveColumnName(table, columns, column) {
|
|
@@ -86156,6 +86156,7 @@ class AgentCoordinationService {
|
|
|
86156
86156
|
id: input.id,
|
|
86157
86157
|
name: input.name ?? input.id,
|
|
86158
86158
|
type: input.type,
|
|
86159
|
+
...input.executor !== undefined ? { executor: input.executor } : {},
|
|
86159
86160
|
workspace: input.workspace ?? this.ctx.cwd,
|
|
86160
86161
|
purpose: input.purpose && input.purpose.length > 0 ? input.purpose : `${input.type} agent`,
|
|
86161
86162
|
tags: input.tags ?? [],
|
|
@@ -88794,13 +88795,12 @@ function aggregateVerifyVerdict(input) {
|
|
|
88794
88795
|
return "PARTIAL";
|
|
88795
88796
|
return "PASS";
|
|
88796
88797
|
}
|
|
88797
|
-
var VERDICT_AGGREGATES, ROW_STATUSES, CHECK_SEVERITIES,
|
|
88798
|
+
var VERDICT_AGGREGATES, ROW_STATUSES, CHECK_SEVERITIES, verdictSchema, coverageRowSchema, checkSchema, verifyVerdictSchema, NORM = (s) => String(s ?? "").toUpperCase();
|
|
88798
88799
|
var init_verify_verdict = __esm(() => {
|
|
88799
88800
|
init_zod();
|
|
88800
88801
|
VERDICT_AGGREGATES = ["PASS", "PARTIAL", "FAIL", "UNKNOWN"];
|
|
88801
88802
|
ROW_STATUSES = ["MET", "PARTIAL", "UNMET", "N/A"];
|
|
88802
88803
|
CHECK_SEVERITIES = ["blocker", "major", "minor", "advisory"];
|
|
88803
|
-
CONFIDENCE_LEVELS = ["HIGH", "MEDIUM", "LOW"];
|
|
88804
88804
|
verdictSchema = string2().transform((v, ctx) => {
|
|
88805
88805
|
const up = v.toUpperCase();
|
|
88806
88806
|
if (!VERDICT_AGGREGATES.includes(up)) {
|
|
@@ -88856,18 +88856,6 @@ var init_verify_verdict = __esm(() => {
|
|
|
88856
88856
|
acceptanceCriteria: array(coverageRowSchema).optional().default([]),
|
|
88857
88857
|
checks: array(checkSchema).optional().default([]),
|
|
88858
88858
|
source: string2().optional(),
|
|
88859
|
-
confidence: string2().transform((c, ctx) => {
|
|
88860
|
-
const up = c.toUpperCase();
|
|
88861
|
-
if (!CONFIDENCE_LEVELS.includes(up)) {
|
|
88862
|
-
ctx.addIssue({
|
|
88863
|
-
code: "custom",
|
|
88864
|
-
message: `invalid confidence "${c}" (expected HIGH|MEDIUM|LOW)`,
|
|
88865
|
-
path: ["confidence"]
|
|
88866
|
-
});
|
|
88867
|
-
return "LOW";
|
|
88868
|
-
}
|
|
88869
|
-
return up;
|
|
88870
|
-
}).optional(),
|
|
88871
88859
|
pipelineRunId: string2().optional(),
|
|
88872
88860
|
recordedAt: string2().optional()
|
|
88873
88861
|
});
|
|
@@ -90130,19 +90118,6 @@ async function readVerdictFrom(fs, path, wbs) {
|
|
|
90130
90118
|
};
|
|
90131
90119
|
}
|
|
90132
90120
|
}
|
|
90133
|
-
if ("confidence" in parsed) {
|
|
90134
|
-
const rawConfidence = parsed.confidence;
|
|
90135
|
-
const up = typeof rawConfidence === "string" ? rawConfidence.toUpperCase() : "";
|
|
90136
|
-
if (up !== "HIGH" && up !== "MEDIUM" && up !== "LOW") {
|
|
90137
|
-
return {
|
|
90138
|
-
artifact: undefined,
|
|
90139
|
-
path,
|
|
90140
|
-
readError: `invalid confidence field at ${path}: expected HIGH|MEDIUM|LOW, actual ${JSON.stringify(rawConfidence)}`,
|
|
90141
|
-
missing: false
|
|
90142
|
-
};
|
|
90143
|
-
}
|
|
90144
|
-
parsed.confidence = up;
|
|
90145
|
-
}
|
|
90146
90121
|
return { artifact: parsed, path, missing: false };
|
|
90147
90122
|
}
|
|
90148
90123
|
async function readVerdictArtifact(fs, runDir, wbs) {
|
|
@@ -90156,6 +90131,30 @@ async function readVerdictArtifact(fs, runDir, wbs) {
|
|
|
90156
90131
|
const { missing: _scratchMissing, ...result } = scratch;
|
|
90157
90132
|
return result;
|
|
90158
90133
|
}
|
|
90134
|
+
function citedTestPaths(evidence) {
|
|
90135
|
+
const paths = new Set;
|
|
90136
|
+
for (const match of evidence.matchAll(TEST_PATH_RE)) {
|
|
90137
|
+
const path = (match[1] ?? "").replace(/^\.\//, "");
|
|
90138
|
+
if (path !== "" && !path.startsWith("/"))
|
|
90139
|
+
paths.add(path);
|
|
90140
|
+
}
|
|
90141
|
+
return [...paths];
|
|
90142
|
+
}
|
|
90143
|
+
async function findUnresolvedTestEvidence(fs, projectRoot, artifact) {
|
|
90144
|
+
if (artifact === undefined)
|
|
90145
|
+
return [];
|
|
90146
|
+
const rows = [...artifact.requirements ?? [], ...artifact.acceptanceCriteria ?? []];
|
|
90147
|
+
const unresolved = [];
|
|
90148
|
+
for (const row of rows) {
|
|
90149
|
+
if (String(row.status).toUpperCase() !== "MET" || typeof row.evidence !== "string")
|
|
90150
|
+
continue;
|
|
90151
|
+
for (const path of citedTestPaths(row.evidence)) {
|
|
90152
|
+
if (!await fs.exists(join31(projectRoot, path)))
|
|
90153
|
+
unresolved.push(`${row.id ?? "?"}: ${path}`);
|
|
90154
|
+
}
|
|
90155
|
+
}
|
|
90156
|
+
return unresolved;
|
|
90157
|
+
}
|
|
90159
90158
|
function computeAggregate(artifact) {
|
|
90160
90159
|
const reqs = artifact.requirements ?? [];
|
|
90161
90160
|
const acs = artifact.acceptanceCriteria ?? [];
|
|
@@ -90237,15 +90236,17 @@ function evaluateDoneTransition(input) {
|
|
|
90237
90236
|
const acs = artifact.acceptanceCriteria ?? [];
|
|
90238
90237
|
const internallyConsistentPass = artifact.verdict !== "PASS" || reqs.length > 0 || acs.length > 0;
|
|
90239
90238
|
const effective = internallyConsistentPass ? harshnessMax(artifact.verdict, computed) : "UNKNOWN";
|
|
90240
|
-
|
|
90239
|
+
const unresolved = input.unresolvedTestEvidence ?? [];
|
|
90240
|
+
if (effective === "PASS" && unresolved.length > 0) {
|
|
90241
90241
|
return {
|
|
90242
90242
|
kind: "deny",
|
|
90243
|
-
verdict: "
|
|
90243
|
+
verdict: "PARTIAL",
|
|
90244
90244
|
message: [
|
|
90245
|
-
`Cannot transition task ${wbs} to done: PASS verdict
|
|
90246
|
-
` task:
|
|
90247
|
-
` verdict:
|
|
90248
|
-
|
|
90245
|
+
`Cannot transition task ${wbs} to done: the PASS verdict cites test files that do not exist.`,
|
|
90246
|
+
` task: ${taskFilePath}`,
|
|
90247
|
+
` verdict: ${verdictPath}`,
|
|
90248
|
+
...unresolved.map((u) => ` missing: ${u}`),
|
|
90249
|
+
` remediation: re-run \`/sp:dev-verify ${wbs}\` with evidence naming real tests, ` + `or override with \`spur task update ${wbs} done --force-done --reason "<why>"\`.`
|
|
90249
90250
|
].join(`
|
|
90250
90251
|
`)
|
|
90251
90252
|
};
|
|
@@ -90271,8 +90272,10 @@ function harshnessMax(a, b) {
|
|
|
90271
90272
|
const rank = { PASS: 0, UNKNOWN: 1, PARTIAL: 2, FAIL: 3 };
|
|
90272
90273
|
return rank[a] >= rank[b] ? a : b;
|
|
90273
90274
|
}
|
|
90275
|
+
var TEST_PATH_RE;
|
|
90274
90276
|
var init_done_transition_guard = __esm(() => {
|
|
90275
90277
|
init_verify_verdict();
|
|
90278
|
+
TEST_PATH_RE = /(?:^|[\s`'"([,])((?:[\w@.-]+\/)+[\w@.-]+\.(?:test|spec)\.[cm]?[jt]sx?)\b/g;
|
|
90276
90279
|
});
|
|
90277
90280
|
|
|
90278
90281
|
// ../../packages/app/src/workflow/workflow-resolver.ts
|
|
@@ -91196,8 +91199,6 @@ function renderTesting(v) {
|
|
|
91196
91199
|
lines.push("**Pipeline verify results**");
|
|
91197
91200
|
lines.push("");
|
|
91198
91201
|
lines.push(`- Verdict: ${v.verdict} (from verdict artifact)`);
|
|
91199
|
-
if (v.confidence !== undefined)
|
|
91200
|
-
lines.push(`- Confidence: ${v.confidence}`);
|
|
91201
91202
|
lines.push("");
|
|
91202
91203
|
if (v.requirements.length === 0) {
|
|
91203
91204
|
lines.push("| Requirement | Status | Evidence |");
|
|
@@ -91629,11 +91630,6 @@ function lintVerifyAnswer(answerText, taskContent, featureContent) {
|
|
|
91629
91630
|
} else if (!/^(PASS|PARTIAL|FAIL)$/i.test(tables.verdict.value)) {
|
|
91630
91631
|
add(tables.verdict.line, "verdict-value", `invalid Verdict value "${tables.verdict.value}" (PASS | PARTIAL | FAIL)`);
|
|
91631
91632
|
}
|
|
91632
|
-
if (tables.confidence === null) {
|
|
91633
|
-
add(0, "confidence-missing", "no `Confidence:` line (expected exactly one `Confidence: HIGH|MEDIUM|LOW` line)");
|
|
91634
|
-
} else if (!/^(HIGH|MEDIUM|LOW)$/i.test(tables.confidence.value)) {
|
|
91635
|
-
add(tables.confidence.line, "confidence-value", `invalid Confidence value "${tables.confidence.value}" (HIGH | MEDIUM | LOW)`);
|
|
91636
|
-
}
|
|
91637
91633
|
const reqIds = extractRequirementIds(taskContent);
|
|
91638
91634
|
const acIndex = buildAcIdentityIndex(taskContent, featureContent ?? null);
|
|
91639
91635
|
const seenReq = new Set;
|
|
@@ -91693,19 +91689,6 @@ function lintVerifyAnswer(answerText, taskContent, featureContent) {
|
|
|
91693
91689
|
add(row.line, "evidence-hedged", hedgedMessage(`AC "${row.id.slice(0, 40)}"`, hedge));
|
|
91694
91690
|
}
|
|
91695
91691
|
}
|
|
91696
|
-
const confidenceLevel = tables.confidence === null ? null : tables.confidence.value.toUpperCase();
|
|
91697
|
-
if (confidenceLevel === "HIGH" || confidenceLevel === "LOW") {
|
|
91698
|
-
const reqStatuses = tables.reqs.map((row) => normalizeReqStatus(row.status));
|
|
91699
|
-
const acStatuses = tables.acs.map((row) => normalizeAcStatus(row.status));
|
|
91700
|
-
const allRowsMet = reqStatuses.length > 0 && reqStatuses.every((status) => status === "MET") && acStatuses.every((status) => status === "MET");
|
|
91701
|
-
const hedgedRow = [...tables.reqs, ...tables.acs].some((row) => (normalizeReqStatus(row.status) ?? normalizeAcStatus(row.status)) === "MET" && firstHedgedPhrase(row.evidence) !== null);
|
|
91702
|
-
const line = tables.confidence?.line ?? 0;
|
|
91703
|
-
if (confidenceLevel === "HIGH" && (!allRowsMet || hedgedRow)) {
|
|
91704
|
-
add(line, "confidence-unwarranted", "Confidence HIGH is not earned by the rows: at least one row is not MET, or a MET " + "row hedges its evidence (downgrade the level, or finish the row)");
|
|
91705
|
-
} else if (confidenceLevel === "LOW" && allRowsMet) {
|
|
91706
|
-
add(line, "confidence-understated", "Confidence LOW understates an answer whose every row is MET (raise the level to " + "HIGH or MEDIUM)");
|
|
91707
|
-
}
|
|
91708
|
-
}
|
|
91709
91692
|
return findings;
|
|
91710
91693
|
}
|
|
91711
91694
|
function hasClaimMarker(evidence) {
|
|
@@ -91780,7 +91763,7 @@ function normalizeEvidenceType(raw) {
|
|
|
91780
91763
|
return EVIDENCE_TYPE_PRECEDENCE.find((candidate) => tokens.includes(candidate)) ?? null;
|
|
91781
91764
|
}
|
|
91782
91765
|
function parseAnswer(text) {
|
|
91783
|
-
const out = { verdict: null,
|
|
91766
|
+
const out = { verdict: null, reqs: [], acs: [] };
|
|
91784
91767
|
const lines = text.split(`
|
|
91785
91768
|
`);
|
|
91786
91769
|
let reqTable = false;
|
|
@@ -91840,26 +91823,11 @@ function parseAnswer(text) {
|
|
|
91840
91823
|
out.verdict = {
|
|
91841
91824
|
value: verdictMatches[0]?.[1] ?? "",
|
|
91842
91825
|
line: text.slice(0, verdictMatches[0]?.index ?? 0).split(`
|
|
91843
|
-
`).length
|
|
91844
|
-
};
|
|
91845
|
-
}
|
|
91846
|
-
const confidenceMatches = [...text.matchAll(/^\s*Confidence:\s*(\S+)\s*$/gim)];
|
|
91847
|
-
if (confidenceMatches.length === 1) {
|
|
91848
|
-
out.confidence = {
|
|
91849
|
-
value: confidenceMatches[0]?.[1] ?? "",
|
|
91850
|
-
line: text.slice(0, confidenceMatches[0]?.index ?? 0).split(`
|
|
91851
91826
|
`).length
|
|
91852
91827
|
};
|
|
91853
91828
|
}
|
|
91854
91829
|
return out;
|
|
91855
91830
|
}
|
|
91856
|
-
function extractAnswerConfidence(text) {
|
|
91857
|
-
const matches = [...text.matchAll(/^\s*Confidence:\s*(\S+)\s*$/gim)];
|
|
91858
|
-
if (matches.length !== 1)
|
|
91859
|
-
return;
|
|
91860
|
-
const value = (matches[0]?.[1] ?? "").toUpperCase();
|
|
91861
|
-
return CONFIDENCE_LEVELS2.includes(value) ? value : undefined;
|
|
91862
|
-
}
|
|
91863
91831
|
function sectionBetween(text, heading) {
|
|
91864
91832
|
const marker = new RegExp(`^#{1,6}\\s+${heading}\\s*$`, "im");
|
|
91865
91833
|
const match = marker.exec(text);
|
|
@@ -91913,7 +91881,9 @@ function buildAcIdentityIndex(taskContent, featureContent) {
|
|
|
91913
91881
|
if (!label)
|
|
91914
91882
|
continue;
|
|
91915
91883
|
declareIdentity(label);
|
|
91916
|
-
const
|
|
91884
|
+
const balancedHead = /^(.+?\([^()]*\))\s*:(?:\s|$)/.exec(label)?.[1];
|
|
91885
|
+
const fallback = /\([^():]*:[^()]*\)/.test(label) ? null : label.split(/:(?:\s|$)/)[0];
|
|
91886
|
+
const head = (balancedHead ?? fallback ?? "").trim();
|
|
91917
91887
|
if (head && head !== label)
|
|
91918
91888
|
declareIdentity(head);
|
|
91919
91889
|
const leading = label.split(/\s+/)[0] ?? "";
|
|
@@ -91969,7 +91939,7 @@ function resolveAcIdentity(rowId, index) {
|
|
|
91969
91939
|
}
|
|
91970
91940
|
return { ok: false, error: "" };
|
|
91971
91941
|
}
|
|
91972
|
-
var ANSWER_LINT_MAX_FINDINGS = 10, CLAIM_MARKERS, CITATION_FORMS, HEDGED_PATTERNS, EVIDENCE_TYPE_PRECEDENCE
|
|
91942
|
+
var ANSWER_LINT_MAX_FINDINGS = 10, CLAIM_MARKERS, CITATION_FORMS, HEDGED_PATTERNS, EVIDENCE_TYPE_PRECEDENCE;
|
|
91973
91943
|
var init_verify_answer_lint = __esm(() => {
|
|
91974
91944
|
CLAIM_MARKERS = [
|
|
91975
91945
|
/@[a-z0-9._-]+\/[a-z0-9._-]+/i,
|
|
@@ -91993,7 +91963,6 @@ var init_verify_answer_lint = __esm(() => {
|
|
|
91993
91963
|
/\bit could\b/i
|
|
91994
91964
|
];
|
|
91995
91965
|
EVIDENCE_TYPE_PRECEDENCE = ["test", "command", "static-ref", "manual-review", "llm-judge", "n/a"];
|
|
91996
|
-
CONFIDENCE_LEVELS2 = ["HIGH", "MEDIUM", "LOW"];
|
|
91997
91966
|
});
|
|
91998
91967
|
|
|
91999
91968
|
// ../../packages/app/src/services/feature-check.ts
|
|
@@ -92252,6 +92221,21 @@ async function readFeatureAcBody(fs, featuresDir, featureId) {
|
|
|
92252
92221
|
return null;
|
|
92253
92222
|
}
|
|
92254
92223
|
}
|
|
92224
|
+
async function readTaskAcceptanceCriteria(wbs, deps) {
|
|
92225
|
+
try {
|
|
92226
|
+
const hit = await new TaskLocator({
|
|
92227
|
+
fs: deps.fs,
|
|
92228
|
+
tasksDir: deps.tasksDir,
|
|
92229
|
+
...deps.foldersConfig !== undefined ? { foldersConfig: deps.foldersConfig } : {}
|
|
92230
|
+
}).findByWbs(wbs);
|
|
92231
|
+
if (hit === null)
|
|
92232
|
+
return "";
|
|
92233
|
+
const doc = MarkdownDocument.parse(await deps.fs.readFile(hit.filePath), "task");
|
|
92234
|
+
return stripAcFence(doc.getSection("Acceptance Criteria") ?? "");
|
|
92235
|
+
} catch {
|
|
92236
|
+
return "";
|
|
92237
|
+
}
|
|
92238
|
+
}
|
|
92255
92239
|
async function lintVerifyAnswerForTask(wbs, answerText, deps) {
|
|
92256
92240
|
let taskContent;
|
|
92257
92241
|
let featureId = "";
|
|
@@ -104473,7 +104457,11 @@ class AgentService {
|
|
|
104473
104457
|
const phaseSuffix = phase !== undefined ? ` for phase '${phase}'` : "";
|
|
104474
104458
|
if (executor !== undefined) {
|
|
104475
104459
|
if (source === "default") {
|
|
104476
|
-
|
|
104460
|
+
return {
|
|
104461
|
+
ok: false,
|
|
104462
|
+
exitCode: 2,
|
|
104463
|
+
message: agentDefaultExecutorMessage(selector, this.ctx.roles)
|
|
104464
|
+
};
|
|
104477
104465
|
}
|
|
104478
104466
|
if (executorDisabled(executor)) {
|
|
104479
104467
|
return {
|
|
@@ -104522,13 +104510,11 @@ class AgentService {
|
|
|
104522
104510
|
}
|
|
104523
104511
|
if (source === "default") {
|
|
104524
104512
|
const roleNames = this.ctx.roles !== undefined ? [...this.ctx.roles.keys()].join(", ") : "";
|
|
104525
|
-
const
|
|
104526
|
-
const roleList = roleNames !== "" ? `role (${roleNames})` : "a role";
|
|
104527
|
-
const executorList = names.length > 0 ? `configured executor (${names.join(", ")})` : "a configured executor";
|
|
104513
|
+
const roleList = roleNames !== "" ? `a role (${roleNames})` : "a role";
|
|
104528
104514
|
return {
|
|
104529
104515
|
ok: false,
|
|
104530
104516
|
exitCode: 2,
|
|
104531
|
-
message: `Unknown agent.default value: '${selector}'. Accepted: ${roleList}
|
|
104517
|
+
message: `Unknown agent.default value: '${selector}'. Accepted: ${roleList}, e.g. agent.default: coder.`
|
|
104532
104518
|
};
|
|
104533
104519
|
}
|
|
104534
104520
|
const canonical = resolveAgentName(selector);
|
|
@@ -104840,11 +104826,9 @@ function warnBareBinaryOnce(selector, output) {
|
|
|
104840
104826
|
warnedBareBinary.add(selector);
|
|
104841
104827
|
output.error(`Warning: --agent "${selector}" is a bare coding-agent binary name, not a role or a configured executor. It keeps working during the transition; prefer a role (scribe, coder, reviewer, planner) or an executor name (config/transition-shims.json: agent-bare-binary-name).`);
|
|
104842
104828
|
}
|
|
104843
|
-
function
|
|
104844
|
-
|
|
104845
|
-
|
|
104846
|
-
warnedAgentDefaultExecutor.add(selector);
|
|
104847
|
-
output.error(`Warning: agent.default "${selector}" is a configured executor name, not a role. It keeps working during the transition; prefer a role (scribe, coder, reviewer, planner) (config/transition-shims.json: agent-default-executor).`);
|
|
104829
|
+
function agentDefaultExecutorMessage(selector, roles) {
|
|
104830
|
+
const roleNames = roles !== undefined && roles.size > 0 ? [...roles.keys()].join(", ") : "scribe, coder, reviewer, planner";
|
|
104831
|
+
return `agent.default '${selector}' names a configured executor, but agent.default takes a role (${roleNames}). ` + `Fix: set agent.default to a role such as 'coder' in .spur/config.yaml, and pin '${selector}' explicitly ` + `with --agent ${selector} where you need that executor.`;
|
|
104848
104832
|
}
|
|
104849
104833
|
function buildDoctorRows(results, executors, roles) {
|
|
104850
104834
|
const executorByName = new Map((executors ?? []).map((e) => [e.name, e]));
|
|
@@ -105096,7 +105080,7 @@ function buildRoutingAttribution(result) {
|
|
|
105096
105080
|
...result.model !== undefined ? { model: result.model } : {}
|
|
105097
105081
|
};
|
|
105098
105082
|
}
|
|
105099
|
-
var PidObservingProcessExecutor, RolePropagatingProcessExecutor, DOCTOR_CACHE_REL = ".spur/run/agent-doctor.json", DOCTOR_CACHE_TTL_MS = 60000, USAGE_SNAPSHOT_STALE_MS, TRACE_SAFE_SLASH_COMMAND, TRACE_SAFE_SLASH_TOKEN, SENSITIVE_FLAG, SENSITIVE_INLINE_FLAG, warnedBareBinary
|
|
105083
|
+
var PidObservingProcessExecutor, RolePropagatingProcessExecutor, DOCTOR_CACHE_REL = ".spur/run/agent-doctor.json", DOCTOR_CACHE_TTL_MS = 60000, USAGE_SNAPSHOT_STALE_MS, TRACE_SAFE_SLASH_COMMAND, TRACE_SAFE_SLASH_TOKEN, SENSITIVE_FLAG, SENSITIVE_INLINE_FLAG, warnedBareBinary;
|
|
105100
105084
|
var init_agent_service = __esm(() => {
|
|
105101
105085
|
init_src();
|
|
105102
105086
|
init_src2();
|
|
@@ -105153,7 +105137,6 @@ var init_agent_service = __esm(() => {
|
|
|
105153
105137
|
SENSITIVE_FLAG = /^--?(?:api[-_]?key|authorization|credential|password|secret|token)$/i;
|
|
105154
105138
|
SENSITIVE_INLINE_FLAG = /^(--?(?:api[-_]?key|authorization|credential|password|secret|token)=).+$/i;
|
|
105155
105139
|
warnedBareBinary = new Set;
|
|
105156
|
-
warnedAgentDefaultExecutor = new Set;
|
|
105157
105140
|
});
|
|
105158
105141
|
|
|
105159
105142
|
// ../../packages/app/src/services/agent-trace-service.ts
|
|
@@ -105869,8 +105852,20 @@ async function prepareBoardModules(input) {
|
|
|
105869
105852
|
}
|
|
105870
105853
|
}
|
|
105871
105854
|
let realProjectRoot;
|
|
105872
|
-
const
|
|
105873
|
-
|
|
105855
|
+
const verifiedRealPath = (index, id, path, label) => {
|
|
105856
|
+
let resolved;
|
|
105857
|
+
try {
|
|
105858
|
+
resolved = input.probe.realPath(path);
|
|
105859
|
+
} catch {
|
|
105860
|
+
resolved = undefined;
|
|
105861
|
+
}
|
|
105862
|
+
if (resolved === undefined) {
|
|
105863
|
+
throw new BoardModuleConfigError(index, id, `${label} "${path}" could not be resolved to a real path`);
|
|
105864
|
+
}
|
|
105865
|
+
return resolved;
|
|
105866
|
+
};
|
|
105867
|
+
const projectRootRealPath = (index, id) => {
|
|
105868
|
+
realProjectRoot ??= verifiedRealPath(index, id, input.projectRoot, "project root");
|
|
105874
105869
|
return realProjectRoot;
|
|
105875
105870
|
};
|
|
105876
105871
|
for (const [index, declaration] of input.declarations.entries()) {
|
|
@@ -105883,22 +105878,35 @@ async function prepareBoardModules(input) {
|
|
|
105883
105878
|
}
|
|
105884
105879
|
const root = resolve17(input.projectRoot, declaration.directory);
|
|
105885
105880
|
const entryPath = resolveContained(index, input.projectRoot, declaration.directory, declaration.entry, declaration.id);
|
|
105886
|
-
const
|
|
105881
|
+
const styles = declaration.styles.map((style) => ({
|
|
105882
|
+
declared: style,
|
|
105883
|
+
absolute: resolveContained(index, input.projectRoot, declaration.directory, style, declaration.id)
|
|
105884
|
+
}));
|
|
105885
|
+
const stylePaths = styles.map((style) => style.absolute);
|
|
105887
105886
|
if (!await input.probe.isDirectory(root)) {
|
|
105888
105887
|
throw new BoardModuleConfigError(index, declaration.id, `module directory "${declaration.directory}" was not found`);
|
|
105889
105888
|
}
|
|
105890
105889
|
if (!await input.probe.isFile(entryPath)) {
|
|
105891
105890
|
throw new BoardModuleConfigError(index, declaration.id, `entry asset "${declaration.entry}" was not found in "${declaration.directory}"`);
|
|
105892
105891
|
}
|
|
105893
|
-
for (const
|
|
105894
|
-
if (!await input.probe.isFile(
|
|
105895
|
-
throw new BoardModuleConfigError(index, declaration.id, `style asset "${
|
|
105892
|
+
for (const style of styles) {
|
|
105893
|
+
if (!await input.probe.isFile(style.absolute)) {
|
|
105894
|
+
throw new BoardModuleConfigError(index, declaration.id, `style asset "${style.declared}" was not found`);
|
|
105896
105895
|
}
|
|
105897
105896
|
}
|
|
105898
|
-
const
|
|
105899
|
-
const
|
|
105900
|
-
if (
|
|
105901
|
-
throw new BoardModuleConfigError(index, declaration.id, `"${declaration.directory}" resolves outside the project root
|
|
105897
|
+
const realProject = projectRootRealPath(index, declaration.id);
|
|
105898
|
+
const realRoot = verifiedRealPath(index, declaration.id, root, "module directory");
|
|
105899
|
+
if (!isInsideRoot(realProject, realRoot)) {
|
|
105900
|
+
throw new BoardModuleConfigError(index, declaration.id, `"${declaration.directory}" resolves outside the project root`);
|
|
105901
|
+
}
|
|
105902
|
+
const requireInsideModule = (declaredPath, realPath) => {
|
|
105903
|
+
if (!isInsideRoot(realRoot, realPath)) {
|
|
105904
|
+
throw new BoardModuleConfigError(index, declaration.id, `"${declaredPath}" resolves outside the project root or its module directory`);
|
|
105905
|
+
}
|
|
105906
|
+
};
|
|
105907
|
+
requireInsideModule(declaration.entry, verifiedRealPath(index, declaration.id, entryPath, "entry asset"));
|
|
105908
|
+
for (const style of styles) {
|
|
105909
|
+
requireInsideModule(style.declared, verifiedRealPath(index, declaration.id, style.absolute, "style asset"));
|
|
105902
105910
|
}
|
|
105903
105911
|
descriptors.push({
|
|
105904
105912
|
...descriptorBase(declaration),
|
|
@@ -114571,7 +114579,8 @@ async function transitionTaskGuarded(deps, input) {
|
|
|
114571
114579
|
reason: input.reason,
|
|
114572
114580
|
artifact: loaded.artifact,
|
|
114573
114581
|
verdictPath: loaded.path,
|
|
114574
|
-
readError: loaded.readError
|
|
114582
|
+
readError: loaded.readError,
|
|
114583
|
+
unresolvedTestEvidence: input.forceDone === true ? [] : await findUnresolvedTestEvidence(deps.fs, dirname31(dirname31(deps.runDir)), loaded.artifact)
|
|
114575
114584
|
});
|
|
114576
114585
|
if (guardOutcome.kind === "noop") {
|
|
114577
114586
|
return { kind: "noop", wbs, status: "done", message: guardOutcome.message };
|
|
@@ -114609,329 +114618,6 @@ var init_task_transition = __esm(() => {
|
|
|
114609
114618
|
init_done_transition_guard();
|
|
114610
114619
|
});
|
|
114611
114620
|
|
|
114612
|
-
// ../../packages/app/src/services/task-verdict.ts
|
|
114613
|
-
function confidenceCheckRow(confidence, requirements, acceptanceCriteria) {
|
|
114614
|
-
const rows = [
|
|
114615
|
-
...requirements,
|
|
114616
|
-
...acceptanceCriteria ?? []
|
|
114617
|
-
];
|
|
114618
|
-
const notMet = rows.filter((r) => r.status !== "MET" && r.status !== "N/A");
|
|
114619
|
-
const hedged = rows.filter((r) => r.status === "MET").map((r) => firstHedgedPhrase(r.evidence ?? "")).find((phrase) => phrase !== null);
|
|
114620
|
-
const level = confidence ?? "LOW";
|
|
114621
|
-
const earned = confidence !== undefined && level !== "LOW" && notMet.length === 0 && hedged === undefined;
|
|
114622
|
-
const basis = `${confidence === undefined ? "LOW (absent from the answer)" : level} \u2014 ${rows.length - notMet.length}/${rows.length} rows MET` + (notMet.length > 0 ? ` (not MET: ${notMet.map((r) => r.id).join(", ")})` : "") + (hedged !== undefined ? `; hedged MET evidence: "${hedged}"` : "");
|
|
114623
|
-
return { name: "confidence", status: earned ? "pass" : "warn", evidence: basis };
|
|
114624
|
-
}
|
|
114625
|
-
function injectEarnedConfidenceRow(text) {
|
|
114626
|
-
let parsed;
|
|
114627
|
-
try {
|
|
114628
|
-
parsed = JSON.parse(text);
|
|
114629
|
-
} catch {
|
|
114630
|
-
return text;
|
|
114631
|
-
}
|
|
114632
|
-
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
114633
|
-
return text;
|
|
114634
|
-
const artifact = parsed;
|
|
114635
|
-
const checks = Array.isArray(artifact.checks) ? artifact.checks : [];
|
|
114636
|
-
if (checks.some((c) => c !== null && typeof c === "object" && c.name === "confidence"))
|
|
114637
|
-
return text;
|
|
114638
|
-
const level = typeof artifact.confidence === "string" ? artifact.confidence : undefined;
|
|
114639
|
-
const row = confidenceCheckRow(level, artifact.requirements ?? [], artifact.acceptanceCriteria);
|
|
114640
|
-
if (row.status !== "pass")
|
|
114641
|
-
return text;
|
|
114642
|
-
return `${JSON.stringify({ ...artifact, checks: [...checks, row] }, null, 4)}
|
|
114643
|
-
`;
|
|
114644
|
-
}
|
|
114645
|
-
function deriveVerdict(answerText, taskCheckPassed) {
|
|
114646
|
-
const requirements = extractRequirements(answerText);
|
|
114647
|
-
const parsedAc = extractAcceptanceCriteria(answerText);
|
|
114648
|
-
const acceptanceCriteria = applyAcceptanceCriteriaEvidenceRule(parsedAc.rows);
|
|
114649
|
-
const checks = extractChecks(answerText, taskCheckPassed, requirements, acceptanceCriteria, parsedAc.dropped);
|
|
114650
|
-
const confidence = extractAnswerConfidence(answerText);
|
|
114651
|
-
const checksWithConfidence = [
|
|
114652
|
-
...checks.filter((c) => c.name !== "confidence"),
|
|
114653
|
-
confidenceCheckRow(confidence, requirements, acceptanceCriteria)
|
|
114654
|
-
];
|
|
114655
|
-
if (requirements.length === 0) {
|
|
114656
|
-
return {
|
|
114657
|
-
verdict: "UNKNOWN",
|
|
114658
|
-
...confidence !== undefined ? { confidence } : {},
|
|
114659
|
-
requirements,
|
|
114660
|
-
acceptanceCriteria,
|
|
114661
|
-
checks: checksWithConfidence
|
|
114662
|
-
};
|
|
114663
|
-
}
|
|
114664
|
-
const aggregate = aggregateVerifyVerdict({
|
|
114665
|
-
requirements,
|
|
114666
|
-
acceptanceCriteria,
|
|
114667
|
-
checks: [],
|
|
114668
|
-
taskCheckPassed
|
|
114669
|
-
});
|
|
114670
|
-
return {
|
|
114671
|
-
verdict: aggregate,
|
|
114672
|
-
...confidence !== undefined ? { confidence } : {},
|
|
114673
|
-
requirements,
|
|
114674
|
-
acceptanceCriteria,
|
|
114675
|
-
checks: checksWithConfidence
|
|
114676
|
-
};
|
|
114677
|
-
}
|
|
114678
|
-
function extractRequirements(text) {
|
|
114679
|
-
const reqs = [];
|
|
114680
|
-
const lines = text.split(`
|
|
114681
|
-
`);
|
|
114682
|
-
let inTable = false;
|
|
114683
|
-
let colMap = null;
|
|
114684
|
-
for (const line of lines) {
|
|
114685
|
-
const trimmed = line.trim();
|
|
114686
|
-
if (inTable && /^#{1,6}\s/.test(trimmed)) {
|
|
114687
|
-
inTable = false;
|
|
114688
|
-
colMap = null;
|
|
114689
|
-
continue;
|
|
114690
|
-
}
|
|
114691
|
-
if (!trimmed.startsWith("|"))
|
|
114692
|
-
continue;
|
|
114693
|
-
const cells = splitTableCells2(trimmed);
|
|
114694
|
-
if (!inTable && cells.length >= 2) {
|
|
114695
|
-
const h0 = (cells[0] ?? "").toLowerCase().trim();
|
|
114696
|
-
const h0IsId = h0.includes("req") || h0 === "requirement" || h0 === "r#" || h0 === "r" || /^r\d+$/.test(h0);
|
|
114697
|
-
if (h0IsId) {
|
|
114698
|
-
const statusIdx = cells.findIndex((c) => {
|
|
114699
|
-
const x = c.toLowerCase().trim();
|
|
114700
|
-
return x.includes("status") || x === "verdict";
|
|
114701
|
-
});
|
|
114702
|
-
if (statusIdx >= 0) {
|
|
114703
|
-
const evidenceIdx = cells.findIndex((c) => c.toLowerCase().includes("evidence"));
|
|
114704
|
-
colMap = {
|
|
114705
|
-
id: 0,
|
|
114706
|
-
status: statusIdx,
|
|
114707
|
-
evidence: evidenceIdx >= 0 ? evidenceIdx : undefined
|
|
114708
|
-
};
|
|
114709
|
-
inTable = true;
|
|
114710
|
-
continue;
|
|
114711
|
-
}
|
|
114712
|
-
}
|
|
114713
|
-
}
|
|
114714
|
-
if (/^[-:]+$/.test(cells[0] ?? ""))
|
|
114715
|
-
continue;
|
|
114716
|
-
if (inTable && colMap && cells.length >= 2) {
|
|
114717
|
-
const first = (cells[0] ?? "").toLowerCase();
|
|
114718
|
-
const second = (cells[1] ?? "").toLowerCase();
|
|
114719
|
-
if ((first === "ac" || first.includes("acceptance") || first === "check" || first === "name") && (second.includes("status") || second === "verdict")) {
|
|
114720
|
-
inTable = false;
|
|
114721
|
-
colMap = null;
|
|
114722
|
-
continue;
|
|
114723
|
-
}
|
|
114724
|
-
const id = cells[colMap.id] ?? "";
|
|
114725
|
-
const statusRaw = (cells[colMap.status] ?? "").toUpperCase();
|
|
114726
|
-
const evidence = colMap.evidence !== undefined ? cells[colMap.evidence] ?? "" : "";
|
|
114727
|
-
const status = normalizeStatus(statusRaw);
|
|
114728
|
-
if (status !== null && id.length > 0) {
|
|
114729
|
-
reqs.push({ id, status, evidence });
|
|
114730
|
-
}
|
|
114731
|
-
}
|
|
114732
|
-
}
|
|
114733
|
-
return reqs;
|
|
114734
|
-
}
|
|
114735
|
-
function normalizeStatus(raw) {
|
|
114736
|
-
if (/\bMET\b/.test(raw))
|
|
114737
|
-
return "MET";
|
|
114738
|
-
if (/\bPARTIAL\b/.test(raw))
|
|
114739
|
-
return "PARTIAL";
|
|
114740
|
-
if (/\bUNMET\b/.test(raw))
|
|
114741
|
-
return "UNMET";
|
|
114742
|
-
return null;
|
|
114743
|
-
}
|
|
114744
|
-
function splitTableCells2(row) {
|
|
114745
|
-
return row.split(/(?<!\\)\|/).map((c) => c.replace(/\\\|/g, "|").trim()).filter(Boolean);
|
|
114746
|
-
}
|
|
114747
|
-
function extractAcceptanceCriteria(text) {
|
|
114748
|
-
const rows = [];
|
|
114749
|
-
const dropped = [];
|
|
114750
|
-
const lines = text.split(`
|
|
114751
|
-
`);
|
|
114752
|
-
let inTable = false;
|
|
114753
|
-
for (const line of lines) {
|
|
114754
|
-
const trimmed = line.trim();
|
|
114755
|
-
if (inTable && /^#{1,6}\s/.test(trimmed)) {
|
|
114756
|
-
inTable = false;
|
|
114757
|
-
continue;
|
|
114758
|
-
}
|
|
114759
|
-
if (!trimmed.startsWith("|"))
|
|
114760
|
-
continue;
|
|
114761
|
-
const cells = splitTableCells2(trimmed);
|
|
114762
|
-
if (!inTable && cells.length >= 4) {
|
|
114763
|
-
const h0 = (cells[0] ?? "").toLowerCase();
|
|
114764
|
-
const h1 = (cells[1] ?? "").toLowerCase();
|
|
114765
|
-
const h2 = (cells[2] ?? "").toLowerCase();
|
|
114766
|
-
if ((h0 === "ac" || h0.includes("acceptance")) && h1.includes("status") && h2.includes("evidence")) {
|
|
114767
|
-
inTable = true;
|
|
114768
|
-
continue;
|
|
114769
|
-
}
|
|
114770
|
-
}
|
|
114771
|
-
if (/^[-:]+$/.test(cells[0] ?? ""))
|
|
114772
|
-
continue;
|
|
114773
|
-
if (inTable && cells.length >= 3) {
|
|
114774
|
-
const id = cells[0] ?? "";
|
|
114775
|
-
const statusRaw = cells[1] ?? "";
|
|
114776
|
-
const evidenceTypeRaw = cells[2] ?? "";
|
|
114777
|
-
const status = normalizeAcceptanceCriteriaStatus(statusRaw);
|
|
114778
|
-
const evidenceType = normalizeEvidenceType2(evidenceTypeRaw);
|
|
114779
|
-
const acShaped = cells.length >= 4 || id.length > 0 && status !== null;
|
|
114780
|
-
const evidence = cells[3] ?? "";
|
|
114781
|
-
if (acShaped && status !== null && evidenceType !== null && id.length > 0) {
|
|
114782
|
-
rows.push({ id, status, evidenceType, evidence });
|
|
114783
|
-
} else if (acShaped && id.length > 0) {
|
|
114784
|
-
dropped.push(evidenceType === null && status !== null ? `${id} (unrecognised evidence type "${evidenceTypeRaw}")` : `${id} (unrecognised status "${statusRaw}")`);
|
|
114785
|
-
}
|
|
114786
|
-
}
|
|
114787
|
-
}
|
|
114788
|
-
return { rows, dropped };
|
|
114789
|
-
}
|
|
114790
|
-
function normalizeAcceptanceCriteriaStatus(raw) {
|
|
114791
|
-
const upper = raw.toUpperCase();
|
|
114792
|
-
if (/\bMET\b/.test(upper))
|
|
114793
|
-
return "MET";
|
|
114794
|
-
if (/\bPARTIAL\b/.test(upper))
|
|
114795
|
-
return "PARTIAL";
|
|
114796
|
-
if (/\bUNMET\b/.test(upper))
|
|
114797
|
-
return "UNMET";
|
|
114798
|
-
if (/\bN\/A\b/.test(upper) || /\bNA\b/.test(upper))
|
|
114799
|
-
return "N/A";
|
|
114800
|
-
return null;
|
|
114801
|
-
}
|
|
114802
|
-
function normalizeEvidenceTypeToken2(normalized) {
|
|
114803
|
-
if (normalized === "test")
|
|
114804
|
-
return "test";
|
|
114805
|
-
if (normalized === "command")
|
|
114806
|
-
return "command";
|
|
114807
|
-
if (normalized === "static-ref" || normalized === "static" || normalized === "doc" || normalized === "docs" || normalized === "documentation") {
|
|
114808
|
-
return "static-ref";
|
|
114809
|
-
}
|
|
114810
|
-
if (normalized === "manual-review" || normalized === "manual")
|
|
114811
|
-
return "manual-review";
|
|
114812
|
-
if (normalized === "llm-judge" || normalized === "judge")
|
|
114813
|
-
return "llm-judge";
|
|
114814
|
-
if (normalized === "n/a" || normalized === "na")
|
|
114815
|
-
return "n/a";
|
|
114816
|
-
return null;
|
|
114817
|
-
}
|
|
114818
|
-
function normalizeEvidenceType2(raw) {
|
|
114819
|
-
const normalized = raw.toLowerCase().trim();
|
|
114820
|
-
const single = normalizeEvidenceTypeToken2(normalized);
|
|
114821
|
-
if (single !== null)
|
|
114822
|
-
return single;
|
|
114823
|
-
const tokens = normalized.split(/[+,/]/).map((part) => normalizeEvidenceTypeToken2(part.trim())).filter((token) => token !== null);
|
|
114824
|
-
if (tokens.length < 2 || tokens.length !== normalized.split(/[+,/]/).filter((p) => p.trim()).length) {
|
|
114825
|
-
return null;
|
|
114826
|
-
}
|
|
114827
|
-
return EVIDENCE_TYPE_PRECEDENCE2.find((candidate) => tokens.includes(candidate)) ?? null;
|
|
114828
|
-
}
|
|
114829
|
-
function applyAcceptanceCriteriaEvidenceRule(rows) {
|
|
114830
|
-
return rows.map((row) => {
|
|
114831
|
-
if (row.status === "MET" && requiresExecutableEvidence(row.id) && row.evidenceType !== "test" && row.evidenceType !== "command") {
|
|
114832
|
-
return { ...row, status: "PARTIAL" };
|
|
114833
|
-
}
|
|
114834
|
-
return row;
|
|
114835
|
-
});
|
|
114836
|
-
}
|
|
114837
|
-
function requiresExecutableEvidence(id) {
|
|
114838
|
-
const normalized = id.toLowerCase();
|
|
114839
|
-
const nonCore = normalized.includes("[advisory]") || normalized.includes("[non-core]");
|
|
114840
|
-
const nonBehavior = normalized.includes("[non-behavior]") || normalized.includes("[docs-only]") || normalized.includes("[doc-only]");
|
|
114841
|
-
return !nonCore && !nonBehavior;
|
|
114842
|
-
}
|
|
114843
|
-
function extractChecks(_text, taskCheckPassed, requirements, acceptanceCriteria, droppedAcRows = []) {
|
|
114844
|
-
const checks = [
|
|
114845
|
-
{
|
|
114846
|
-
name: "spur task check",
|
|
114847
|
-
status: taskCheckPassed ? "pass" : "fail",
|
|
114848
|
-
evidence: taskCheckPassed ? "task check passed" : "task check failed"
|
|
114849
|
-
}
|
|
114850
|
-
];
|
|
114851
|
-
const lines = _text.split(`
|
|
114852
|
-
`);
|
|
114853
|
-
let inChecks = false;
|
|
114854
|
-
for (const line of lines) {
|
|
114855
|
-
const trimmed = line.trim();
|
|
114856
|
-
if (/^#{1,6}\s/.test(trimmed))
|
|
114857
|
-
inChecks = false;
|
|
114858
|
-
if (!trimmed.startsWith("|"))
|
|
114859
|
-
continue;
|
|
114860
|
-
const cells = splitTableCells2(trimmed);
|
|
114861
|
-
if (cells.length >= 2) {
|
|
114862
|
-
const h0 = (cells[0] ?? "").toLowerCase();
|
|
114863
|
-
const h1 = (cells[1] ?? "").toLowerCase();
|
|
114864
|
-
if ((h0 === "check" || h0 === "name") && h1 === "status") {
|
|
114865
|
-
inChecks = true;
|
|
114866
|
-
continue;
|
|
114867
|
-
}
|
|
114868
|
-
if (inChecks && cells.length >= 3 && !/^[-:]+$/.test(cells[0] ?? "")) {
|
|
114869
|
-
checks.push({
|
|
114870
|
-
name: cells[0] ?? "",
|
|
114871
|
-
status: (cells[1] ?? "").toLowerCase(),
|
|
114872
|
-
evidence: cells[2] ?? ""
|
|
114873
|
-
});
|
|
114874
|
-
}
|
|
114875
|
-
}
|
|
114876
|
-
}
|
|
114877
|
-
if (droppedAcRows.length > 0) {
|
|
114878
|
-
checks.push({
|
|
114879
|
-
name: "ac-row-dropped",
|
|
114880
|
-
status: "fail",
|
|
114881
|
-
evidence: `${droppedAcRows.length} AC row(s) could not be parsed and were omitted from the verdict: ` + `${droppedAcRows.join("; ")}. Accepted evidence types: test, command, static-ref (aliases: ` + "static, doc, docs, documentation), manual-review, llm-judge, n/a. Accepted statuses: " + "MET, PARTIAL, UNMET, N/A."
|
|
114882
|
-
});
|
|
114883
|
-
}
|
|
114884
|
-
const hollowMet = [
|
|
114885
|
-
...requirements.filter((r) => r.status === "MET" && r.evidence.trim() === "").map((r) => r.id),
|
|
114886
|
-
...acceptanceCriteria.filter((r) => r.status === "MET" && r.evidence.trim() === "").map((r) => r.id)
|
|
114887
|
-
];
|
|
114888
|
-
if (hollowMet.length > 0) {
|
|
114889
|
-
checks.push({
|
|
114890
|
-
name: "hollow-met-evidence",
|
|
114891
|
-
status: "fail",
|
|
114892
|
-
severity: "major",
|
|
114893
|
-
evidence: `${hollowMet.length} MET row(s) carry no recorded evidence and cannot support PASS: ` + `${hollowMet.join(", ")}. Re-run verify with populated evidence per row.`
|
|
114894
|
-
});
|
|
114895
|
-
}
|
|
114896
|
-
if (acceptanceCriteria.length > 0) {
|
|
114897
|
-
const downgraded = acceptanceCriteria.filter((row) => row.status === "PARTIAL" && requiresExecutableEvidence(row.id) && row.evidenceType !== "test" && row.evidenceType !== "command");
|
|
114898
|
-
checks.push({
|
|
114899
|
-
name: downgraded.length > 0 ? "evidence-rule-failed" : "evidence-rule-pass",
|
|
114900
|
-
status: downgraded.length > 0 ? "fail" : "pass",
|
|
114901
|
-
evidence: downgraded.length > 0 ? `Executable evidence missing for: ${downgraded.map((row) => row.id).join(", ")}` : "All behavior-bearing AC rows have executable evidence or are explicitly non-behavioral."
|
|
114902
|
-
});
|
|
114903
|
-
}
|
|
114904
|
-
return checks;
|
|
114905
|
-
}
|
|
114906
|
-
function aggregateBatchVerdicts(results) {
|
|
114907
|
-
const notStarted = results.filter((r) => r.outcome === "NOT-STARTED");
|
|
114908
|
-
const rolledUp = results.filter((r) => r.outcome !== "NOT-STARTED");
|
|
114909
|
-
const counts = {
|
|
114910
|
-
PASS: rolledUp.filter((r) => r.outcome === "PASS").length,
|
|
114911
|
-
PARTIAL: rolledUp.filter((r) => r.outcome === "PARTIAL").length,
|
|
114912
|
-
FAIL: rolledUp.filter((r) => r.outcome === "FAIL").length,
|
|
114913
|
-
UNKNOWN: rolledUp.filter((r) => r.outcome === "UNKNOWN").length
|
|
114914
|
-
};
|
|
114915
|
-
let verdict;
|
|
114916
|
-
if (rolledUp.length === 0) {
|
|
114917
|
-
verdict = "UNKNOWN";
|
|
114918
|
-
} else if (counts.FAIL > 0) {
|
|
114919
|
-
verdict = "FAIL";
|
|
114920
|
-
} else if (counts.PARTIAL > 0 || counts.UNKNOWN > 0) {
|
|
114921
|
-
verdict = "PARTIAL";
|
|
114922
|
-
} else {
|
|
114923
|
-
verdict = "PASS";
|
|
114924
|
-
}
|
|
114925
|
-
const summary = `${counts.PASS} PASS, ${counts.PARTIAL} PARTIAL, ${counts.FAIL} FAIL, ${notStarted.length} NOT-STARTED (excluded)`;
|
|
114926
|
-
return { verdict, rolledUp, notStarted, summary };
|
|
114927
|
-
}
|
|
114928
|
-
var EVIDENCE_TYPE_PRECEDENCE2;
|
|
114929
|
-
var init_task_verdict = __esm(() => {
|
|
114930
|
-
init_verify_answer_lint();
|
|
114931
|
-
init_verify_verdict();
|
|
114932
|
-
EVIDENCE_TYPE_PRECEDENCE2 = ["test", "command", "static-ref", "manual-review", "llm-judge", "n/a"];
|
|
114933
|
-
});
|
|
114934
|
-
|
|
114935
114621
|
// ../../packages/app/src/services/task-service.ts
|
|
114936
114622
|
import { dirname as dirname32, isAbsolute as isAbsolute12, join as join57, relative as relative13, sep as sep13 } from "path";
|
|
114937
114623
|
function citationOrigin(verdict, name) {
|
|
@@ -115680,7 +115366,7 @@ ${templateBackground}`;
|
|
|
115680
115366
|
const bytes = await this.ctx.fs.readFile(verdictPath);
|
|
115681
115367
|
ensureDurablePlaneIgnored(paths.projectRoot);
|
|
115682
115368
|
await this.ctx.fs.ensureDir(dirname32(durablePath));
|
|
115683
|
-
await atomicWriteAsync(durablePath,
|
|
115369
|
+
await atomicWriteAsync(durablePath, bytes, wbs, this.ctx.fs, this.ctx.projectName ?? "spur");
|
|
115684
115370
|
}
|
|
115685
115371
|
const result = {
|
|
115686
115372
|
testingWritten: false,
|
|
@@ -116235,7 +115921,6 @@ var init_task_service = __esm(() => {
|
|
|
116235
115921
|
init_task_section_normalizer();
|
|
116236
115922
|
init_task_size_precheck();
|
|
116237
115923
|
init_task_transition();
|
|
116238
|
-
init_task_verdict();
|
|
116239
115924
|
DependencyMutationError = class DependencyMutationError extends Error {
|
|
116240
115925
|
code;
|
|
116241
115926
|
constructor(code, message) {
|
|
@@ -116315,6 +116000,311 @@ var init_task_service = __esm(() => {
|
|
|
116315
116000
|
};
|
|
116316
116001
|
});
|
|
116317
116002
|
|
|
116003
|
+
// ../../packages/app/src/services/task-verdict.ts
|
|
116004
|
+
function deriveVerdict(answerText, taskCheckPassed, taskAcceptanceCriteria = "") {
|
|
116005
|
+
const requirements = extractRequirements(answerText);
|
|
116006
|
+
const parsedAc = extractAcceptanceCriteria(answerText);
|
|
116007
|
+
const exempt = taskAcExemptions(taskAcceptanceCriteria);
|
|
116008
|
+
const acceptanceCriteria = applyAcceptanceCriteriaEvidenceRule(parsedAc.rows, exempt);
|
|
116009
|
+
const checks = extractChecks(answerText, taskCheckPassed, requirements, acceptanceCriteria, parsedAc.dropped, exempt);
|
|
116010
|
+
if (requirements.length === 0) {
|
|
116011
|
+
return {
|
|
116012
|
+
verdict: "UNKNOWN",
|
|
116013
|
+
requirements,
|
|
116014
|
+
acceptanceCriteria,
|
|
116015
|
+
checks
|
|
116016
|
+
};
|
|
116017
|
+
}
|
|
116018
|
+
const aggregate = aggregateVerifyVerdict({
|
|
116019
|
+
requirements,
|
|
116020
|
+
acceptanceCriteria,
|
|
116021
|
+
checks: [],
|
|
116022
|
+
taskCheckPassed
|
|
116023
|
+
});
|
|
116024
|
+
return {
|
|
116025
|
+
verdict: aggregate,
|
|
116026
|
+
requirements,
|
|
116027
|
+
acceptanceCriteria,
|
|
116028
|
+
checks
|
|
116029
|
+
};
|
|
116030
|
+
}
|
|
116031
|
+
function extractRequirements(text) {
|
|
116032
|
+
const reqs = [];
|
|
116033
|
+
const lines = text.split(`
|
|
116034
|
+
`);
|
|
116035
|
+
let inTable = false;
|
|
116036
|
+
let colMap = null;
|
|
116037
|
+
for (const line of lines) {
|
|
116038
|
+
const trimmed = line.trim();
|
|
116039
|
+
if (inTable && /^#{1,6}\s/.test(trimmed)) {
|
|
116040
|
+
inTable = false;
|
|
116041
|
+
colMap = null;
|
|
116042
|
+
continue;
|
|
116043
|
+
}
|
|
116044
|
+
if (!trimmed.startsWith("|"))
|
|
116045
|
+
continue;
|
|
116046
|
+
const cells = splitTableCells2(trimmed);
|
|
116047
|
+
if (!inTable && cells.length >= 2) {
|
|
116048
|
+
const h0 = (cells[0] ?? "").toLowerCase().trim();
|
|
116049
|
+
const h0IsId = h0.includes("req") || h0 === "requirement" || h0 === "r#" || h0 === "r" || /^r\d+$/.test(h0);
|
|
116050
|
+
if (h0IsId) {
|
|
116051
|
+
const statusIdx = cells.findIndex((c) => {
|
|
116052
|
+
const x = c.toLowerCase().trim();
|
|
116053
|
+
return x.includes("status") || x === "verdict";
|
|
116054
|
+
});
|
|
116055
|
+
if (statusIdx >= 0) {
|
|
116056
|
+
const evidenceIdx = cells.findIndex((c) => c.toLowerCase().includes("evidence"));
|
|
116057
|
+
colMap = {
|
|
116058
|
+
id: 0,
|
|
116059
|
+
status: statusIdx,
|
|
116060
|
+
evidence: evidenceIdx >= 0 ? evidenceIdx : undefined
|
|
116061
|
+
};
|
|
116062
|
+
inTable = true;
|
|
116063
|
+
continue;
|
|
116064
|
+
}
|
|
116065
|
+
}
|
|
116066
|
+
}
|
|
116067
|
+
if (/^[-:]+$/.test(cells[0] ?? ""))
|
|
116068
|
+
continue;
|
|
116069
|
+
if (inTable && colMap && cells.length >= 2) {
|
|
116070
|
+
const first = (cells[0] ?? "").toLowerCase();
|
|
116071
|
+
const second = (cells[1] ?? "").toLowerCase();
|
|
116072
|
+
if ((first === "ac" || first.includes("acceptance") || first === "check" || first === "name") && (second.includes("status") || second === "verdict")) {
|
|
116073
|
+
inTable = false;
|
|
116074
|
+
colMap = null;
|
|
116075
|
+
continue;
|
|
116076
|
+
}
|
|
116077
|
+
const id = cells[colMap.id] ?? "";
|
|
116078
|
+
const statusRaw = (cells[colMap.status] ?? "").toUpperCase();
|
|
116079
|
+
const evidence = colMap.evidence !== undefined ? cells[colMap.evidence] ?? "" : "";
|
|
116080
|
+
const status = normalizeStatus(statusRaw);
|
|
116081
|
+
if (status !== null && id.length > 0) {
|
|
116082
|
+
reqs.push({ id, status, evidence });
|
|
116083
|
+
}
|
|
116084
|
+
}
|
|
116085
|
+
}
|
|
116086
|
+
return reqs;
|
|
116087
|
+
}
|
|
116088
|
+
function normalizeStatus(raw) {
|
|
116089
|
+
if (/\bMET\b/.test(raw))
|
|
116090
|
+
return "MET";
|
|
116091
|
+
if (/\bPARTIAL\b/.test(raw))
|
|
116092
|
+
return "PARTIAL";
|
|
116093
|
+
if (/\bUNMET\b/.test(raw))
|
|
116094
|
+
return "UNMET";
|
|
116095
|
+
return null;
|
|
116096
|
+
}
|
|
116097
|
+
function splitTableCells2(row) {
|
|
116098
|
+
return row.split(/(?<!\\)\|/).map((c) => c.replace(/\\\|/g, "|").trim()).filter(Boolean);
|
|
116099
|
+
}
|
|
116100
|
+
function extractAcceptanceCriteria(text) {
|
|
116101
|
+
const rows = [];
|
|
116102
|
+
const dropped = [];
|
|
116103
|
+
const lines = text.split(`
|
|
116104
|
+
`);
|
|
116105
|
+
let inTable = false;
|
|
116106
|
+
for (const line of lines) {
|
|
116107
|
+
const trimmed = line.trim();
|
|
116108
|
+
if (inTable && /^#{1,6}\s/.test(trimmed)) {
|
|
116109
|
+
inTable = false;
|
|
116110
|
+
continue;
|
|
116111
|
+
}
|
|
116112
|
+
if (!trimmed.startsWith("|"))
|
|
116113
|
+
continue;
|
|
116114
|
+
const cells = splitTableCells2(trimmed);
|
|
116115
|
+
if (!inTable && cells.length >= 4) {
|
|
116116
|
+
const h0 = (cells[0] ?? "").toLowerCase();
|
|
116117
|
+
const h1 = (cells[1] ?? "").toLowerCase();
|
|
116118
|
+
const h2 = (cells[2] ?? "").toLowerCase();
|
|
116119
|
+
if ((h0 === "ac" || h0.includes("acceptance")) && h1.includes("status") && h2.includes("evidence")) {
|
|
116120
|
+
inTable = true;
|
|
116121
|
+
continue;
|
|
116122
|
+
}
|
|
116123
|
+
}
|
|
116124
|
+
if (/^[-:]+$/.test(cells[0] ?? ""))
|
|
116125
|
+
continue;
|
|
116126
|
+
if (inTable && cells.length >= 3) {
|
|
116127
|
+
const id = cells[0] ?? "";
|
|
116128
|
+
const statusRaw = cells[1] ?? "";
|
|
116129
|
+
const evidenceTypeRaw = cells[2] ?? "";
|
|
116130
|
+
const status = normalizeAcceptanceCriteriaStatus(statusRaw);
|
|
116131
|
+
const evidenceType = normalizeEvidenceType2(evidenceTypeRaw);
|
|
116132
|
+
const acShaped = cells.length >= 4 || id.length > 0 && status !== null;
|
|
116133
|
+
const evidence = cells[3] ?? "";
|
|
116134
|
+
if (acShaped && status !== null && evidenceType !== null && id.length > 0) {
|
|
116135
|
+
rows.push({ id, status, evidenceType, evidence });
|
|
116136
|
+
} else if (acShaped && id.length > 0) {
|
|
116137
|
+
dropped.push(evidenceType === null && status !== null ? `${id} (unrecognised evidence type "${evidenceTypeRaw}")` : `${id} (unrecognised status "${statusRaw}")`);
|
|
116138
|
+
}
|
|
116139
|
+
}
|
|
116140
|
+
}
|
|
116141
|
+
return { rows, dropped };
|
|
116142
|
+
}
|
|
116143
|
+
function normalizeAcceptanceCriteriaStatus(raw) {
|
|
116144
|
+
const upper = raw.toUpperCase();
|
|
116145
|
+
if (/\bMET\b/.test(upper))
|
|
116146
|
+
return "MET";
|
|
116147
|
+
if (/\bPARTIAL\b/.test(upper))
|
|
116148
|
+
return "PARTIAL";
|
|
116149
|
+
if (/\bUNMET\b/.test(upper))
|
|
116150
|
+
return "UNMET";
|
|
116151
|
+
if (/\bN\/A\b/.test(upper) || /\bNA\b/.test(upper))
|
|
116152
|
+
return "N/A";
|
|
116153
|
+
return null;
|
|
116154
|
+
}
|
|
116155
|
+
function normalizeEvidenceTypeToken2(normalized) {
|
|
116156
|
+
if (normalized === "test")
|
|
116157
|
+
return "test";
|
|
116158
|
+
if (normalized === "command")
|
|
116159
|
+
return "command";
|
|
116160
|
+
if (normalized === "static-ref" || normalized === "static" || normalized === "doc" || normalized === "docs" || normalized === "documentation") {
|
|
116161
|
+
return "static-ref";
|
|
116162
|
+
}
|
|
116163
|
+
if (normalized === "manual-review" || normalized === "manual")
|
|
116164
|
+
return "manual-review";
|
|
116165
|
+
if (normalized === "llm-judge" || normalized === "judge")
|
|
116166
|
+
return "llm-judge";
|
|
116167
|
+
if (normalized === "n/a" || normalized === "na")
|
|
116168
|
+
return "n/a";
|
|
116169
|
+
return null;
|
|
116170
|
+
}
|
|
116171
|
+
function normalizeEvidenceType2(raw) {
|
|
116172
|
+
const normalized = raw.toLowerCase().trim();
|
|
116173
|
+
const single = normalizeEvidenceTypeToken2(normalized);
|
|
116174
|
+
if (single !== null)
|
|
116175
|
+
return single;
|
|
116176
|
+
const tokens = normalized.split(/[+,/]/).map((part) => normalizeEvidenceTypeToken2(part.trim())).filter((token) => token !== null);
|
|
116177
|
+
if (tokens.length < 2 || tokens.length !== normalized.split(/[+,/]/).filter((p) => p.trim()).length) {
|
|
116178
|
+
return null;
|
|
116179
|
+
}
|
|
116180
|
+
return EVIDENCE_TYPE_PRECEDENCE2.find((candidate) => tokens.includes(candidate)) ?? null;
|
|
116181
|
+
}
|
|
116182
|
+
function taskAcExemptions(taskAcceptanceCriteria) {
|
|
116183
|
+
const exempt = new Set;
|
|
116184
|
+
for (const line of taskAcceptanceCriteria.split(`
|
|
116185
|
+
`)) {
|
|
116186
|
+
if (!EXEMPTION_TAG_RE.test(line))
|
|
116187
|
+
continue;
|
|
116188
|
+
const item = line.replace(/^\s*(?:[-*+]|\d+[.)])\s*(?:\[[ xX]\]\s*)?/, "");
|
|
116189
|
+
const key = normalizeTitle(item);
|
|
116190
|
+
if (key !== "")
|
|
116191
|
+
exempt.add(key);
|
|
116192
|
+
}
|
|
116193
|
+
return exempt;
|
|
116194
|
+
}
|
|
116195
|
+
function applyAcceptanceCriteriaEvidenceRule(rows, exempt) {
|
|
116196
|
+
return rows.map((row) => {
|
|
116197
|
+
if (row.status === "MET" && requiresExecutableEvidence(row.id, exempt) && row.evidenceType !== "test" && row.evidenceType !== "command") {
|
|
116198
|
+
return { ...row, status: "PARTIAL" };
|
|
116199
|
+
}
|
|
116200
|
+
return row;
|
|
116201
|
+
});
|
|
116202
|
+
}
|
|
116203
|
+
function requiresExecutableEvidence(id, exempt) {
|
|
116204
|
+
return !exempt.has(normalizeTitle(id));
|
|
116205
|
+
}
|
|
116206
|
+
function extractChecks(_text, taskCheckPassed, requirements, acceptanceCriteria, droppedAcRows = [], exempt = new Set) {
|
|
116207
|
+
const checks = [
|
|
116208
|
+
{
|
|
116209
|
+
name: "spur task check",
|
|
116210
|
+
status: taskCheckPassed ? "pass" : "fail",
|
|
116211
|
+
evidence: taskCheckPassed ? "task check passed" : "task check failed"
|
|
116212
|
+
}
|
|
116213
|
+
];
|
|
116214
|
+
const lines = _text.split(`
|
|
116215
|
+
`);
|
|
116216
|
+
let inChecks = false;
|
|
116217
|
+
for (const line of lines) {
|
|
116218
|
+
const trimmed = line.trim();
|
|
116219
|
+
if (/^#{1,6}\s/.test(trimmed))
|
|
116220
|
+
inChecks = false;
|
|
116221
|
+
if (!trimmed.startsWith("|"))
|
|
116222
|
+
continue;
|
|
116223
|
+
const cells = splitTableCells2(trimmed);
|
|
116224
|
+
if (cells.length >= 2) {
|
|
116225
|
+
const h0 = (cells[0] ?? "").toLowerCase();
|
|
116226
|
+
const h1 = (cells[1] ?? "").toLowerCase();
|
|
116227
|
+
if ((h0 === "check" || h0 === "name") && h1 === "status") {
|
|
116228
|
+
inChecks = true;
|
|
116229
|
+
continue;
|
|
116230
|
+
}
|
|
116231
|
+
if (inChecks && cells.length >= 3 && !/^[-:]+$/.test(cells[0] ?? "")) {
|
|
116232
|
+
checks.push({
|
|
116233
|
+
name: cells[0] ?? "",
|
|
116234
|
+
status: (cells[1] ?? "").toLowerCase(),
|
|
116235
|
+
evidence: cells[2] ?? ""
|
|
116236
|
+
});
|
|
116237
|
+
}
|
|
116238
|
+
}
|
|
116239
|
+
}
|
|
116240
|
+
if (droppedAcRows.length > 0) {
|
|
116241
|
+
checks.push({
|
|
116242
|
+
name: "ac-row-dropped",
|
|
116243
|
+
status: "fail",
|
|
116244
|
+
evidence: `${droppedAcRows.length} AC row(s) could not be parsed and were omitted from the verdict: ` + `${droppedAcRows.join("; ")}. Accepted evidence types: test, command, static-ref (aliases: ` + "static, doc, docs, documentation), manual-review, llm-judge, n/a. Accepted statuses: " + "MET, PARTIAL, UNMET, N/A."
|
|
116245
|
+
});
|
|
116246
|
+
}
|
|
116247
|
+
const hollowMet = [
|
|
116248
|
+
...requirements.filter((r) => r.status === "MET" && r.evidence.trim() === "").map((r) => r.id),
|
|
116249
|
+
...acceptanceCriteria.filter((r) => r.status === "MET" && r.evidence.trim() === "").map((r) => r.id)
|
|
116250
|
+
];
|
|
116251
|
+
if (hollowMet.length > 0) {
|
|
116252
|
+
checks.push({
|
|
116253
|
+
name: "hollow-met-evidence",
|
|
116254
|
+
status: "fail",
|
|
116255
|
+
severity: "major",
|
|
116256
|
+
evidence: `${hollowMet.length} MET row(s) carry no recorded evidence and cannot support PASS: ` + `${hollowMet.join(", ")}. Re-run verify with populated evidence per row.`
|
|
116257
|
+
});
|
|
116258
|
+
}
|
|
116259
|
+
const answerOnlyTags = acceptanceCriteria.filter((row) => EXEMPTION_TAG_RE.test(row.id) && requiresExecutableEvidence(row.id, exempt)).map((row) => row.id);
|
|
116260
|
+
if (answerOnlyTags.length > 0) {
|
|
116261
|
+
checks.push({
|
|
116262
|
+
name: "ac-exemption-not-in-task",
|
|
116263
|
+
status: "warn",
|
|
116264
|
+
severity: "advisory",
|
|
116265
|
+
evidence: `${answerOnlyTags.length} AC row(s) carry an evidence-rule exemption tag the task file does not: ` + `${answerOnlyTags.join("; ")}. Exemptions come only from the task's Acceptance Criteria; ` + "these rows still need test or command evidence."
|
|
116266
|
+
});
|
|
116267
|
+
}
|
|
116268
|
+
if (acceptanceCriteria.length > 0) {
|
|
116269
|
+
const downgraded = acceptanceCriteria.filter((row) => row.status === "PARTIAL" && requiresExecutableEvidence(row.id, exempt) && row.evidenceType !== "test" && row.evidenceType !== "command");
|
|
116270
|
+
checks.push({
|
|
116271
|
+
name: downgraded.length > 0 ? "evidence-rule-failed" : "evidence-rule-pass",
|
|
116272
|
+
status: downgraded.length > 0 ? "fail" : "pass",
|
|
116273
|
+
evidence: downgraded.length > 0 ? `Executable evidence missing for: ${downgraded.map((row) => row.id).join(", ")}` : "All behavior-bearing AC rows have executable evidence or are explicitly non-behavioral."
|
|
116274
|
+
});
|
|
116275
|
+
}
|
|
116276
|
+
return checks;
|
|
116277
|
+
}
|
|
116278
|
+
function aggregateBatchVerdicts(results) {
|
|
116279
|
+
const notStarted = results.filter((r) => r.outcome === "NOT-STARTED");
|
|
116280
|
+
const rolledUp = results.filter((r) => r.outcome !== "NOT-STARTED");
|
|
116281
|
+
const counts = {
|
|
116282
|
+
PASS: rolledUp.filter((r) => r.outcome === "PASS").length,
|
|
116283
|
+
PARTIAL: rolledUp.filter((r) => r.outcome === "PARTIAL").length,
|
|
116284
|
+
FAIL: rolledUp.filter((r) => r.outcome === "FAIL").length,
|
|
116285
|
+
UNKNOWN: rolledUp.filter((r) => r.outcome === "UNKNOWN").length
|
|
116286
|
+
};
|
|
116287
|
+
let verdict;
|
|
116288
|
+
if (rolledUp.length === 0) {
|
|
116289
|
+
verdict = "UNKNOWN";
|
|
116290
|
+
} else if (counts.FAIL > 0) {
|
|
116291
|
+
verdict = "FAIL";
|
|
116292
|
+
} else if (counts.PARTIAL > 0 || counts.UNKNOWN > 0) {
|
|
116293
|
+
verdict = "PARTIAL";
|
|
116294
|
+
} else {
|
|
116295
|
+
verdict = "PASS";
|
|
116296
|
+
}
|
|
116297
|
+
const summary = `${counts.PASS} PASS, ${counts.PARTIAL} PARTIAL, ${counts.FAIL} FAIL, ${notStarted.length} NOT-STARTED (excluded)`;
|
|
116298
|
+
return { verdict, rolledUp, notStarted, summary };
|
|
116299
|
+
}
|
|
116300
|
+
var EVIDENCE_TYPE_PRECEDENCE2, EXEMPTION_TAG_RE;
|
|
116301
|
+
var init_task_verdict = __esm(() => {
|
|
116302
|
+
init_src2();
|
|
116303
|
+
init_verify_verdict();
|
|
116304
|
+
EVIDENCE_TYPE_PRECEDENCE2 = ["test", "command", "static-ref", "manual-review", "llm-judge", "n/a"];
|
|
116305
|
+
EXEMPTION_TAG_RE = /\[(?:advisory|non-core|non-behavior|docs-only|doc-only)\]/i;
|
|
116306
|
+
});
|
|
116307
|
+
|
|
116318
116308
|
// ../../packages/app/src/services/token-ledger-service.ts
|
|
116319
116309
|
import { closeSync as closeSync6, existsSync as existsSync19, fstatSync as fstatSync2, openSync as openSync6, readSync } from "fs";
|
|
116320
116310
|
import { join as join58 } from "path";
|
|
@@ -131528,7 +131518,8 @@ Recover with: ${err.recoveryCommand}` : err.message;
|
|
|
131528
131518
|
return;
|
|
131529
131519
|
}
|
|
131530
131520
|
const taskCheckPassed = true;
|
|
131531
|
-
const
|
|
131521
|
+
const taskAc = await readTaskAcceptanceCriteria(wbs, corpusDeps);
|
|
131522
|
+
const result = deriveVerdict(answerText, taskCheckPassed, taskAc);
|
|
131532
131523
|
const gap = await verdictScenarioKeyGap(wbs, [...result.requirements, ...result.acceptanceCriteria ?? []], corpusDeps);
|
|
131533
131524
|
if (gap !== null) {
|
|
131534
131525
|
writeJsonError(context.output, options, `Verdict rows key to no scenario of linked feature ${gap.featureId}: ${summarizeRowIds(gap.offendingIds)}. Key each row by ${VERDICT_SCENARIO_KEY_FORMS} (repair: /sp:dev-verify ${wbs})`);
|
|
@@ -132725,8 +132716,9 @@ ${prompt}`;
|
|
|
132725
132716
|
};
|
|
132726
132717
|
}
|
|
132727
132718
|
function drainAgentSelector(spec, context) {
|
|
132728
|
-
if (spec.executor === undefined)
|
|
132729
|
-
|
|
132719
|
+
if (spec.executor === undefined) {
|
|
132720
|
+
throw new Error(`Spec "${spec.id}" has no executor field \u2014 add \`executor: <name>\` (an agent.executors entry) to the spec under .spur/agents/ or re-materialize it, then drain again`);
|
|
132721
|
+
}
|
|
132730
132722
|
try {
|
|
132731
132723
|
resolveExecutor(spec.executor, context.agentConfig, { isCanonicalAgent: isAgentName });
|
|
132732
132724
|
} catch (error) {
|
|
@@ -134429,7 +134421,7 @@ import { createRequire as createRequire3 } from "module";
|
|
|
134429
134421
|
var CLI_CONFIG = {
|
|
134430
134422
|
binaryName: "spur",
|
|
134431
134423
|
binaryLabel: "spur",
|
|
134432
|
-
binaryVersion: "0.4.
|
|
134424
|
+
binaryVersion: "0.4.6",
|
|
134433
134425
|
configDir: ".spur",
|
|
134434
134426
|
configFile: ".spur/config.yaml",
|
|
134435
134427
|
databaseFile: ".spur/spur.db"
|
|
@@ -142535,14 +142527,13 @@ async function serveModuleAsset(c, modules, fs) {
|
|
|
142535
142527
|
const target = resolve27(declared.root, relative);
|
|
142536
142528
|
if (!isInsideRoot(declared.root, target))
|
|
142537
142529
|
return refusal(403, "Forbidden path");
|
|
142530
|
+
if (!(await fs.stat(target))?.isFile())
|
|
142531
|
+
return refusal(404, "Not Found");
|
|
142538
142532
|
const realRoot = resolvedOrUndefined(fs, declared.root);
|
|
142539
|
-
const
|
|
142540
|
-
if (realRoot
|
|
142533
|
+
const realTarget = resolvedOrUndefined(fs, target);
|
|
142534
|
+
if (realRoot === undefined || realTarget === undefined || !isInsideRoot(realRoot, realTarget)) {
|
|
142541
142535
|
return refusal(403, "Forbidden path");
|
|
142542
142536
|
}
|
|
142543
|
-
const realTarget = resolvedTarget ?? target;
|
|
142544
|
-
if (!(await fs.stat(realTarget))?.isFile())
|
|
142545
|
-
return refusal(404, "Not Found");
|
|
142546
142537
|
const file = Bun.file(realTarget);
|
|
142547
142538
|
const headers = new Headers({
|
|
142548
142539
|
"content-type": file.type || "application/octet-stream",
|
|
@@ -143057,7 +143048,7 @@ await init_src3();
|
|
|
143057
143048
|
import { homedir as homedir14 } from "os";
|
|
143058
143049
|
import { basename as basename20, join as join70, relative as relative14, resolve as resolve28 } from "path";
|
|
143059
143050
|
// ../server/package.json
|
|
143060
|
-
var version3 = "0.4.
|
|
143051
|
+
var version3 = "0.4.6";
|
|
143061
143052
|
|
|
143062
143053
|
// ../server/src/modules/health/index.ts
|
|
143063
143054
|
var startedAt = Date.now();
|