@gobing-ai/spur 0.4.4 → 0.4.5
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/rules/docs/sourced-api-claims.yaml +3 -2
- package/config/rules/recommended-pre-check.yaml +11 -9
- 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 +2 -2
- 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/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 +11 -32
- 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 +289 -405
- package/config/rules/docs/verdict-confidence.yaml +0 -68
package/spur.js
CHANGED
|
@@ -88794,13 +88794,12 @@ function aggregateVerifyVerdict(input) {
|
|
|
88794
88794
|
return "PARTIAL";
|
|
88795
88795
|
return "PASS";
|
|
88796
88796
|
}
|
|
88797
|
-
var VERDICT_AGGREGATES, ROW_STATUSES, CHECK_SEVERITIES,
|
|
88797
|
+
var VERDICT_AGGREGATES, ROW_STATUSES, CHECK_SEVERITIES, verdictSchema, coverageRowSchema, checkSchema, verifyVerdictSchema, NORM = (s) => String(s ?? "").toUpperCase();
|
|
88798
88798
|
var init_verify_verdict = __esm(() => {
|
|
88799
88799
|
init_zod();
|
|
88800
88800
|
VERDICT_AGGREGATES = ["PASS", "PARTIAL", "FAIL", "UNKNOWN"];
|
|
88801
88801
|
ROW_STATUSES = ["MET", "PARTIAL", "UNMET", "N/A"];
|
|
88802
88802
|
CHECK_SEVERITIES = ["blocker", "major", "minor", "advisory"];
|
|
88803
|
-
CONFIDENCE_LEVELS = ["HIGH", "MEDIUM", "LOW"];
|
|
88804
88803
|
verdictSchema = string2().transform((v, ctx) => {
|
|
88805
88804
|
const up = v.toUpperCase();
|
|
88806
88805
|
if (!VERDICT_AGGREGATES.includes(up)) {
|
|
@@ -88856,18 +88855,6 @@ var init_verify_verdict = __esm(() => {
|
|
|
88856
88855
|
acceptanceCriteria: array(coverageRowSchema).optional().default([]),
|
|
88857
88856
|
checks: array(checkSchema).optional().default([]),
|
|
88858
88857
|
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
88858
|
pipelineRunId: string2().optional(),
|
|
88872
88859
|
recordedAt: string2().optional()
|
|
88873
88860
|
});
|
|
@@ -90130,19 +90117,6 @@ async function readVerdictFrom(fs, path, wbs) {
|
|
|
90130
90117
|
};
|
|
90131
90118
|
}
|
|
90132
90119
|
}
|
|
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
90120
|
return { artifact: parsed, path, missing: false };
|
|
90147
90121
|
}
|
|
90148
90122
|
async function readVerdictArtifact(fs, runDir, wbs) {
|
|
@@ -90237,19 +90211,6 @@ function evaluateDoneTransition(input) {
|
|
|
90237
90211
|
const acs = artifact.acceptanceCriteria ?? [];
|
|
90238
90212
|
const internallyConsistentPass = artifact.verdict !== "PASS" || reqs.length > 0 || acs.length > 0;
|
|
90239
90213
|
const effective = internallyConsistentPass ? harshnessMax(artifact.verdict, computed) : "UNKNOWN";
|
|
90240
|
-
if (effective === "PASS" && artifact.confidence === undefined) {
|
|
90241
|
-
return {
|
|
90242
|
-
kind: "deny",
|
|
90243
|
-
verdict: "PASS",
|
|
90244
|
-
message: [
|
|
90245
|
-
`Cannot transition task ${wbs} to done: PASS verdict does not state a confidence level.`,
|
|
90246
|
-
` task: ${taskFilePath}`,
|
|
90247
|
-
` verdict: ${verdictPath} (missing \`confidence\`)`,
|
|
90248
|
-
` remediation: add "confidence": "HIGH|MEDIUM|LOW" to the artifact ` + `(re-run \`/sp:dev-verify ${wbs}\`), ` + `or override with \`spur task update ${wbs} done --force-done --reason "<why>"\`.`
|
|
90249
|
-
].join(`
|
|
90250
|
-
`)
|
|
90251
|
-
};
|
|
90252
|
-
}
|
|
90253
90214
|
if (effective === "PASS") {
|
|
90254
90215
|
return { kind: "allow", reason: "pass" };
|
|
90255
90216
|
}
|
|
@@ -91196,8 +91157,6 @@ function renderTesting(v) {
|
|
|
91196
91157
|
lines.push("**Pipeline verify results**");
|
|
91197
91158
|
lines.push("");
|
|
91198
91159
|
lines.push(`- Verdict: ${v.verdict} (from verdict artifact)`);
|
|
91199
|
-
if (v.confidence !== undefined)
|
|
91200
|
-
lines.push(`- Confidence: ${v.confidence}`);
|
|
91201
91160
|
lines.push("");
|
|
91202
91161
|
if (v.requirements.length === 0) {
|
|
91203
91162
|
lines.push("| Requirement | Status | Evidence |");
|
|
@@ -91629,11 +91588,6 @@ function lintVerifyAnswer(answerText, taskContent, featureContent) {
|
|
|
91629
91588
|
} else if (!/^(PASS|PARTIAL|FAIL)$/i.test(tables.verdict.value)) {
|
|
91630
91589
|
add(tables.verdict.line, "verdict-value", `invalid Verdict value "${tables.verdict.value}" (PASS | PARTIAL | FAIL)`);
|
|
91631
91590
|
}
|
|
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
91591
|
const reqIds = extractRequirementIds(taskContent);
|
|
91638
91592
|
const acIndex = buildAcIdentityIndex(taskContent, featureContent ?? null);
|
|
91639
91593
|
const seenReq = new Set;
|
|
@@ -91693,19 +91647,6 @@ function lintVerifyAnswer(answerText, taskContent, featureContent) {
|
|
|
91693
91647
|
add(row.line, "evidence-hedged", hedgedMessage(`AC "${row.id.slice(0, 40)}"`, hedge));
|
|
91694
91648
|
}
|
|
91695
91649
|
}
|
|
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
91650
|
return findings;
|
|
91710
91651
|
}
|
|
91711
91652
|
function hasClaimMarker(evidence) {
|
|
@@ -91780,7 +91721,7 @@ function normalizeEvidenceType(raw) {
|
|
|
91780
91721
|
return EVIDENCE_TYPE_PRECEDENCE.find((candidate) => tokens.includes(candidate)) ?? null;
|
|
91781
91722
|
}
|
|
91782
91723
|
function parseAnswer(text) {
|
|
91783
|
-
const out = { verdict: null,
|
|
91724
|
+
const out = { verdict: null, reqs: [], acs: [] };
|
|
91784
91725
|
const lines = text.split(`
|
|
91785
91726
|
`);
|
|
91786
91727
|
let reqTable = false;
|
|
@@ -91840,26 +91781,11 @@ function parseAnswer(text) {
|
|
|
91840
91781
|
out.verdict = {
|
|
91841
91782
|
value: verdictMatches[0]?.[1] ?? "",
|
|
91842
91783
|
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
91784
|
`).length
|
|
91852
91785
|
};
|
|
91853
91786
|
}
|
|
91854
91787
|
return out;
|
|
91855
91788
|
}
|
|
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
91789
|
function sectionBetween(text, heading) {
|
|
91864
91790
|
const marker = new RegExp(`^#{1,6}\\s+${heading}\\s*$`, "im");
|
|
91865
91791
|
const match = marker.exec(text);
|
|
@@ -91969,7 +91895,7 @@ function resolveAcIdentity(rowId, index) {
|
|
|
91969
91895
|
}
|
|
91970
91896
|
return { ok: false, error: "" };
|
|
91971
91897
|
}
|
|
91972
|
-
var ANSWER_LINT_MAX_FINDINGS = 10, CLAIM_MARKERS, CITATION_FORMS, HEDGED_PATTERNS, EVIDENCE_TYPE_PRECEDENCE
|
|
91898
|
+
var ANSWER_LINT_MAX_FINDINGS = 10, CLAIM_MARKERS, CITATION_FORMS, HEDGED_PATTERNS, EVIDENCE_TYPE_PRECEDENCE;
|
|
91973
91899
|
var init_verify_answer_lint = __esm(() => {
|
|
91974
91900
|
CLAIM_MARKERS = [
|
|
91975
91901
|
/@[a-z0-9._-]+\/[a-z0-9._-]+/i,
|
|
@@ -91993,7 +91919,6 @@ var init_verify_answer_lint = __esm(() => {
|
|
|
91993
91919
|
/\bit could\b/i
|
|
91994
91920
|
];
|
|
91995
91921
|
EVIDENCE_TYPE_PRECEDENCE = ["test", "command", "static-ref", "manual-review", "llm-judge", "n/a"];
|
|
91996
|
-
CONFIDENCE_LEVELS2 = ["HIGH", "MEDIUM", "LOW"];
|
|
91997
91922
|
});
|
|
91998
91923
|
|
|
91999
91924
|
// ../../packages/app/src/services/feature-check.ts
|
|
@@ -114609,329 +114534,6 @@ var init_task_transition = __esm(() => {
|
|
|
114609
114534
|
init_done_transition_guard();
|
|
114610
114535
|
});
|
|
114611
114536
|
|
|
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
114537
|
// ../../packages/app/src/services/task-service.ts
|
|
114936
114538
|
import { dirname as dirname32, isAbsolute as isAbsolute12, join as join57, relative as relative13, sep as sep13 } from "path";
|
|
114937
114539
|
function citationOrigin(verdict, name) {
|
|
@@ -115680,7 +115282,7 @@ ${templateBackground}`;
|
|
|
115680
115282
|
const bytes = await this.ctx.fs.readFile(verdictPath);
|
|
115681
115283
|
ensureDurablePlaneIgnored(paths.projectRoot);
|
|
115682
115284
|
await this.ctx.fs.ensureDir(dirname32(durablePath));
|
|
115683
|
-
await atomicWriteAsync(durablePath,
|
|
115285
|
+
await atomicWriteAsync(durablePath, bytes, wbs, this.ctx.fs, this.ctx.projectName ?? "spur");
|
|
115684
115286
|
}
|
|
115685
115287
|
const result = {
|
|
115686
115288
|
testingWritten: false,
|
|
@@ -116235,7 +115837,6 @@ var init_task_service = __esm(() => {
|
|
|
116235
115837
|
init_task_section_normalizer();
|
|
116236
115838
|
init_task_size_precheck();
|
|
116237
115839
|
init_task_transition();
|
|
116238
|
-
init_task_verdict();
|
|
116239
115840
|
DependencyMutationError = class DependencyMutationError extends Error {
|
|
116240
115841
|
code;
|
|
116241
115842
|
constructor(code, message) {
|
|
@@ -116315,6 +115916,289 @@ var init_task_service = __esm(() => {
|
|
|
116315
115916
|
};
|
|
116316
115917
|
});
|
|
116317
115918
|
|
|
115919
|
+
// ../../packages/app/src/services/task-verdict.ts
|
|
115920
|
+
function deriveVerdict(answerText, taskCheckPassed) {
|
|
115921
|
+
const requirements = extractRequirements(answerText);
|
|
115922
|
+
const parsedAc = extractAcceptanceCriteria(answerText);
|
|
115923
|
+
const acceptanceCriteria = applyAcceptanceCriteriaEvidenceRule(parsedAc.rows);
|
|
115924
|
+
const checks = extractChecks(answerText, taskCheckPassed, requirements, acceptanceCriteria, parsedAc.dropped);
|
|
115925
|
+
if (requirements.length === 0) {
|
|
115926
|
+
return {
|
|
115927
|
+
verdict: "UNKNOWN",
|
|
115928
|
+
requirements,
|
|
115929
|
+
acceptanceCriteria,
|
|
115930
|
+
checks
|
|
115931
|
+
};
|
|
115932
|
+
}
|
|
115933
|
+
const aggregate = aggregateVerifyVerdict({
|
|
115934
|
+
requirements,
|
|
115935
|
+
acceptanceCriteria,
|
|
115936
|
+
checks: [],
|
|
115937
|
+
taskCheckPassed
|
|
115938
|
+
});
|
|
115939
|
+
return {
|
|
115940
|
+
verdict: aggregate,
|
|
115941
|
+
requirements,
|
|
115942
|
+
acceptanceCriteria,
|
|
115943
|
+
checks
|
|
115944
|
+
};
|
|
115945
|
+
}
|
|
115946
|
+
function extractRequirements(text) {
|
|
115947
|
+
const reqs = [];
|
|
115948
|
+
const lines = text.split(`
|
|
115949
|
+
`);
|
|
115950
|
+
let inTable = false;
|
|
115951
|
+
let colMap = null;
|
|
115952
|
+
for (const line of lines) {
|
|
115953
|
+
const trimmed = line.trim();
|
|
115954
|
+
if (inTable && /^#{1,6}\s/.test(trimmed)) {
|
|
115955
|
+
inTable = false;
|
|
115956
|
+
colMap = null;
|
|
115957
|
+
continue;
|
|
115958
|
+
}
|
|
115959
|
+
if (!trimmed.startsWith("|"))
|
|
115960
|
+
continue;
|
|
115961
|
+
const cells = splitTableCells2(trimmed);
|
|
115962
|
+
if (!inTable && cells.length >= 2) {
|
|
115963
|
+
const h0 = (cells[0] ?? "").toLowerCase().trim();
|
|
115964
|
+
const h0IsId = h0.includes("req") || h0 === "requirement" || h0 === "r#" || h0 === "r" || /^r\d+$/.test(h0);
|
|
115965
|
+
if (h0IsId) {
|
|
115966
|
+
const statusIdx = cells.findIndex((c) => {
|
|
115967
|
+
const x = c.toLowerCase().trim();
|
|
115968
|
+
return x.includes("status") || x === "verdict";
|
|
115969
|
+
});
|
|
115970
|
+
if (statusIdx >= 0) {
|
|
115971
|
+
const evidenceIdx = cells.findIndex((c) => c.toLowerCase().includes("evidence"));
|
|
115972
|
+
colMap = {
|
|
115973
|
+
id: 0,
|
|
115974
|
+
status: statusIdx,
|
|
115975
|
+
evidence: evidenceIdx >= 0 ? evidenceIdx : undefined
|
|
115976
|
+
};
|
|
115977
|
+
inTable = true;
|
|
115978
|
+
continue;
|
|
115979
|
+
}
|
|
115980
|
+
}
|
|
115981
|
+
}
|
|
115982
|
+
if (/^[-:]+$/.test(cells[0] ?? ""))
|
|
115983
|
+
continue;
|
|
115984
|
+
if (inTable && colMap && cells.length >= 2) {
|
|
115985
|
+
const first = (cells[0] ?? "").toLowerCase();
|
|
115986
|
+
const second = (cells[1] ?? "").toLowerCase();
|
|
115987
|
+
if ((first === "ac" || first.includes("acceptance") || first === "check" || first === "name") && (second.includes("status") || second === "verdict")) {
|
|
115988
|
+
inTable = false;
|
|
115989
|
+
colMap = null;
|
|
115990
|
+
continue;
|
|
115991
|
+
}
|
|
115992
|
+
const id = cells[colMap.id] ?? "";
|
|
115993
|
+
const statusRaw = (cells[colMap.status] ?? "").toUpperCase();
|
|
115994
|
+
const evidence = colMap.evidence !== undefined ? cells[colMap.evidence] ?? "" : "";
|
|
115995
|
+
const status = normalizeStatus(statusRaw);
|
|
115996
|
+
if (status !== null && id.length > 0) {
|
|
115997
|
+
reqs.push({ id, status, evidence });
|
|
115998
|
+
}
|
|
115999
|
+
}
|
|
116000
|
+
}
|
|
116001
|
+
return reqs;
|
|
116002
|
+
}
|
|
116003
|
+
function normalizeStatus(raw) {
|
|
116004
|
+
if (/\bMET\b/.test(raw))
|
|
116005
|
+
return "MET";
|
|
116006
|
+
if (/\bPARTIAL\b/.test(raw))
|
|
116007
|
+
return "PARTIAL";
|
|
116008
|
+
if (/\bUNMET\b/.test(raw))
|
|
116009
|
+
return "UNMET";
|
|
116010
|
+
return null;
|
|
116011
|
+
}
|
|
116012
|
+
function splitTableCells2(row) {
|
|
116013
|
+
return row.split(/(?<!\\)\|/).map((c) => c.replace(/\\\|/g, "|").trim()).filter(Boolean);
|
|
116014
|
+
}
|
|
116015
|
+
function extractAcceptanceCriteria(text) {
|
|
116016
|
+
const rows = [];
|
|
116017
|
+
const dropped = [];
|
|
116018
|
+
const lines = text.split(`
|
|
116019
|
+
`);
|
|
116020
|
+
let inTable = false;
|
|
116021
|
+
for (const line of lines) {
|
|
116022
|
+
const trimmed = line.trim();
|
|
116023
|
+
if (inTable && /^#{1,6}\s/.test(trimmed)) {
|
|
116024
|
+
inTable = false;
|
|
116025
|
+
continue;
|
|
116026
|
+
}
|
|
116027
|
+
if (!trimmed.startsWith("|"))
|
|
116028
|
+
continue;
|
|
116029
|
+
const cells = splitTableCells2(trimmed);
|
|
116030
|
+
if (!inTable && cells.length >= 4) {
|
|
116031
|
+
const h0 = (cells[0] ?? "").toLowerCase();
|
|
116032
|
+
const h1 = (cells[1] ?? "").toLowerCase();
|
|
116033
|
+
const h2 = (cells[2] ?? "").toLowerCase();
|
|
116034
|
+
if ((h0 === "ac" || h0.includes("acceptance")) && h1.includes("status") && h2.includes("evidence")) {
|
|
116035
|
+
inTable = true;
|
|
116036
|
+
continue;
|
|
116037
|
+
}
|
|
116038
|
+
}
|
|
116039
|
+
if (/^[-:]+$/.test(cells[0] ?? ""))
|
|
116040
|
+
continue;
|
|
116041
|
+
if (inTable && cells.length >= 3) {
|
|
116042
|
+
const id = cells[0] ?? "";
|
|
116043
|
+
const statusRaw = cells[1] ?? "";
|
|
116044
|
+
const evidenceTypeRaw = cells[2] ?? "";
|
|
116045
|
+
const status = normalizeAcceptanceCriteriaStatus(statusRaw);
|
|
116046
|
+
const evidenceType = normalizeEvidenceType2(evidenceTypeRaw);
|
|
116047
|
+
const acShaped = cells.length >= 4 || id.length > 0 && status !== null;
|
|
116048
|
+
const evidence = cells[3] ?? "";
|
|
116049
|
+
if (acShaped && status !== null && evidenceType !== null && id.length > 0) {
|
|
116050
|
+
rows.push({ id, status, evidenceType, evidence });
|
|
116051
|
+
} else if (acShaped && id.length > 0) {
|
|
116052
|
+
dropped.push(evidenceType === null && status !== null ? `${id} (unrecognised evidence type "${evidenceTypeRaw}")` : `${id} (unrecognised status "${statusRaw}")`);
|
|
116053
|
+
}
|
|
116054
|
+
}
|
|
116055
|
+
}
|
|
116056
|
+
return { rows, dropped };
|
|
116057
|
+
}
|
|
116058
|
+
function normalizeAcceptanceCriteriaStatus(raw) {
|
|
116059
|
+
const upper = raw.toUpperCase();
|
|
116060
|
+
if (/\bMET\b/.test(upper))
|
|
116061
|
+
return "MET";
|
|
116062
|
+
if (/\bPARTIAL\b/.test(upper))
|
|
116063
|
+
return "PARTIAL";
|
|
116064
|
+
if (/\bUNMET\b/.test(upper))
|
|
116065
|
+
return "UNMET";
|
|
116066
|
+
if (/\bN\/A\b/.test(upper) || /\bNA\b/.test(upper))
|
|
116067
|
+
return "N/A";
|
|
116068
|
+
return null;
|
|
116069
|
+
}
|
|
116070
|
+
function normalizeEvidenceTypeToken2(normalized) {
|
|
116071
|
+
if (normalized === "test")
|
|
116072
|
+
return "test";
|
|
116073
|
+
if (normalized === "command")
|
|
116074
|
+
return "command";
|
|
116075
|
+
if (normalized === "static-ref" || normalized === "static" || normalized === "doc" || normalized === "docs" || normalized === "documentation") {
|
|
116076
|
+
return "static-ref";
|
|
116077
|
+
}
|
|
116078
|
+
if (normalized === "manual-review" || normalized === "manual")
|
|
116079
|
+
return "manual-review";
|
|
116080
|
+
if (normalized === "llm-judge" || normalized === "judge")
|
|
116081
|
+
return "llm-judge";
|
|
116082
|
+
if (normalized === "n/a" || normalized === "na")
|
|
116083
|
+
return "n/a";
|
|
116084
|
+
return null;
|
|
116085
|
+
}
|
|
116086
|
+
function normalizeEvidenceType2(raw) {
|
|
116087
|
+
const normalized = raw.toLowerCase().trim();
|
|
116088
|
+
const single = normalizeEvidenceTypeToken2(normalized);
|
|
116089
|
+
if (single !== null)
|
|
116090
|
+
return single;
|
|
116091
|
+
const tokens = normalized.split(/[+,/]/).map((part) => normalizeEvidenceTypeToken2(part.trim())).filter((token) => token !== null);
|
|
116092
|
+
if (tokens.length < 2 || tokens.length !== normalized.split(/[+,/]/).filter((p) => p.trim()).length) {
|
|
116093
|
+
return null;
|
|
116094
|
+
}
|
|
116095
|
+
return EVIDENCE_TYPE_PRECEDENCE2.find((candidate) => tokens.includes(candidate)) ?? null;
|
|
116096
|
+
}
|
|
116097
|
+
function applyAcceptanceCriteriaEvidenceRule(rows) {
|
|
116098
|
+
return rows.map((row) => {
|
|
116099
|
+
if (row.status === "MET" && requiresExecutableEvidence(row.id) && row.evidenceType !== "test" && row.evidenceType !== "command") {
|
|
116100
|
+
return { ...row, status: "PARTIAL" };
|
|
116101
|
+
}
|
|
116102
|
+
return row;
|
|
116103
|
+
});
|
|
116104
|
+
}
|
|
116105
|
+
function requiresExecutableEvidence(id) {
|
|
116106
|
+
const normalized = id.toLowerCase();
|
|
116107
|
+
const nonCore = normalized.includes("[advisory]") || normalized.includes("[non-core]");
|
|
116108
|
+
const nonBehavior = normalized.includes("[non-behavior]") || normalized.includes("[docs-only]") || normalized.includes("[doc-only]");
|
|
116109
|
+
return !nonCore && !nonBehavior;
|
|
116110
|
+
}
|
|
116111
|
+
function extractChecks(_text, taskCheckPassed, requirements, acceptanceCriteria, droppedAcRows = []) {
|
|
116112
|
+
const checks = [
|
|
116113
|
+
{
|
|
116114
|
+
name: "spur task check",
|
|
116115
|
+
status: taskCheckPassed ? "pass" : "fail",
|
|
116116
|
+
evidence: taskCheckPassed ? "task check passed" : "task check failed"
|
|
116117
|
+
}
|
|
116118
|
+
];
|
|
116119
|
+
const lines = _text.split(`
|
|
116120
|
+
`);
|
|
116121
|
+
let inChecks = false;
|
|
116122
|
+
for (const line of lines) {
|
|
116123
|
+
const trimmed = line.trim();
|
|
116124
|
+
if (/^#{1,6}\s/.test(trimmed))
|
|
116125
|
+
inChecks = false;
|
|
116126
|
+
if (!trimmed.startsWith("|"))
|
|
116127
|
+
continue;
|
|
116128
|
+
const cells = splitTableCells2(trimmed);
|
|
116129
|
+
if (cells.length >= 2) {
|
|
116130
|
+
const h0 = (cells[0] ?? "").toLowerCase();
|
|
116131
|
+
const h1 = (cells[1] ?? "").toLowerCase();
|
|
116132
|
+
if ((h0 === "check" || h0 === "name") && h1 === "status") {
|
|
116133
|
+
inChecks = true;
|
|
116134
|
+
continue;
|
|
116135
|
+
}
|
|
116136
|
+
if (inChecks && cells.length >= 3 && !/^[-:]+$/.test(cells[0] ?? "")) {
|
|
116137
|
+
checks.push({
|
|
116138
|
+
name: cells[0] ?? "",
|
|
116139
|
+
status: (cells[1] ?? "").toLowerCase(),
|
|
116140
|
+
evidence: cells[2] ?? ""
|
|
116141
|
+
});
|
|
116142
|
+
}
|
|
116143
|
+
}
|
|
116144
|
+
}
|
|
116145
|
+
if (droppedAcRows.length > 0) {
|
|
116146
|
+
checks.push({
|
|
116147
|
+
name: "ac-row-dropped",
|
|
116148
|
+
status: "fail",
|
|
116149
|
+
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."
|
|
116150
|
+
});
|
|
116151
|
+
}
|
|
116152
|
+
const hollowMet = [
|
|
116153
|
+
...requirements.filter((r) => r.status === "MET" && r.evidence.trim() === "").map((r) => r.id),
|
|
116154
|
+
...acceptanceCriteria.filter((r) => r.status === "MET" && r.evidence.trim() === "").map((r) => r.id)
|
|
116155
|
+
];
|
|
116156
|
+
if (hollowMet.length > 0) {
|
|
116157
|
+
checks.push({
|
|
116158
|
+
name: "hollow-met-evidence",
|
|
116159
|
+
status: "fail",
|
|
116160
|
+
severity: "major",
|
|
116161
|
+
evidence: `${hollowMet.length} MET row(s) carry no recorded evidence and cannot support PASS: ` + `${hollowMet.join(", ")}. Re-run verify with populated evidence per row.`
|
|
116162
|
+
});
|
|
116163
|
+
}
|
|
116164
|
+
if (acceptanceCriteria.length > 0) {
|
|
116165
|
+
const downgraded = acceptanceCriteria.filter((row) => row.status === "PARTIAL" && requiresExecutableEvidence(row.id) && row.evidenceType !== "test" && row.evidenceType !== "command");
|
|
116166
|
+
checks.push({
|
|
116167
|
+
name: downgraded.length > 0 ? "evidence-rule-failed" : "evidence-rule-pass",
|
|
116168
|
+
status: downgraded.length > 0 ? "fail" : "pass",
|
|
116169
|
+
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."
|
|
116170
|
+
});
|
|
116171
|
+
}
|
|
116172
|
+
return checks;
|
|
116173
|
+
}
|
|
116174
|
+
function aggregateBatchVerdicts(results) {
|
|
116175
|
+
const notStarted = results.filter((r) => r.outcome === "NOT-STARTED");
|
|
116176
|
+
const rolledUp = results.filter((r) => r.outcome !== "NOT-STARTED");
|
|
116177
|
+
const counts = {
|
|
116178
|
+
PASS: rolledUp.filter((r) => r.outcome === "PASS").length,
|
|
116179
|
+
PARTIAL: rolledUp.filter((r) => r.outcome === "PARTIAL").length,
|
|
116180
|
+
FAIL: rolledUp.filter((r) => r.outcome === "FAIL").length,
|
|
116181
|
+
UNKNOWN: rolledUp.filter((r) => r.outcome === "UNKNOWN").length
|
|
116182
|
+
};
|
|
116183
|
+
let verdict;
|
|
116184
|
+
if (rolledUp.length === 0) {
|
|
116185
|
+
verdict = "UNKNOWN";
|
|
116186
|
+
} else if (counts.FAIL > 0) {
|
|
116187
|
+
verdict = "FAIL";
|
|
116188
|
+
} else if (counts.PARTIAL > 0 || counts.UNKNOWN > 0) {
|
|
116189
|
+
verdict = "PARTIAL";
|
|
116190
|
+
} else {
|
|
116191
|
+
verdict = "PASS";
|
|
116192
|
+
}
|
|
116193
|
+
const summary = `${counts.PASS} PASS, ${counts.PARTIAL} PARTIAL, ${counts.FAIL} FAIL, ${notStarted.length} NOT-STARTED (excluded)`;
|
|
116194
|
+
return { verdict, rolledUp, notStarted, summary };
|
|
116195
|
+
}
|
|
116196
|
+
var EVIDENCE_TYPE_PRECEDENCE2;
|
|
116197
|
+
var init_task_verdict = __esm(() => {
|
|
116198
|
+
init_verify_verdict();
|
|
116199
|
+
EVIDENCE_TYPE_PRECEDENCE2 = ["test", "command", "static-ref", "manual-review", "llm-judge", "n/a"];
|
|
116200
|
+
});
|
|
116201
|
+
|
|
116318
116202
|
// ../../packages/app/src/services/token-ledger-service.ts
|
|
116319
116203
|
import { closeSync as closeSync6, existsSync as existsSync19, fstatSync as fstatSync2, openSync as openSync6, readSync } from "fs";
|
|
116320
116204
|
import { join as join58 } from "path";
|
|
@@ -134429,7 +134313,7 @@ import { createRequire as createRequire3 } from "module";
|
|
|
134429
134313
|
var CLI_CONFIG = {
|
|
134430
134314
|
binaryName: "spur",
|
|
134431
134315
|
binaryLabel: "spur",
|
|
134432
|
-
binaryVersion: "0.4.
|
|
134316
|
+
binaryVersion: "0.4.5",
|
|
134433
134317
|
configDir: ".spur",
|
|
134434
134318
|
configFile: ".spur/config.yaml",
|
|
134435
134319
|
databaseFile: ".spur/spur.db"
|
|
@@ -143057,7 +142941,7 @@ await init_src3();
|
|
|
143057
142941
|
import { homedir as homedir14 } from "os";
|
|
143058
142942
|
import { basename as basename20, join as join70, relative as relative14, resolve as resolve28 } from "path";
|
|
143059
142943
|
// ../server/package.json
|
|
143060
|
-
var version3 = "0.4.
|
|
142944
|
+
var version3 = "0.4.5";
|
|
143061
142945
|
|
|
143062
142946
|
// ../server/src/modules/health/index.ts
|
|
143063
142947
|
var startedAt = Date.now();
|