@gobing-ai/spur 0.4.5 → 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/quality/coverage-gate.yaml +6 -4
- package/config/transition-shims.json +0 -14
- package/package.json +1 -1
- package/plugins/sp/lib/inline-run.generated.mjs +1 -1
- 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/skills/code-verification/references/verdict-schema.md +5 -3
- 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/spur.js +155 -48
- 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/web/_astro/channel.DkpvN2tD.js +0 -1
- package/web/_astro/index.BFyW13JJ.css +0 -1
|
@@ -219,7 +219,9 @@ clean at authoring time.
|
|
|
219
219
|
### Which tags exempt a row from executable evidence
|
|
220
220
|
|
|
221
221
|
A `MET` row is silently demoted to `PARTIAL` unless it carries `test` or `command` evidence — the
|
|
222
|
-
executable-evidence rule. Five tags opt a row out, because not every scenario is behavioral
|
|
222
|
+
executable-evidence rule. Five tags opt a row out, because not every scenario is behavioral. The
|
|
223
|
+
tag counts only when the task author writes it on the AC item in the task file; `spur task verdict`
|
|
224
|
+
ignores (and flags) a tag that appears only in the verify answer:
|
|
223
225
|
|
|
224
226
|
`[doc-only]` · `[docs-only]` · `[non-behavior]` · `[advisory]` · `[non-core]`
|
|
225
227
|
|
|
@@ -139,8 +139,9 @@ silently incomplete (H6 shipped at 23/48 that way, with one verdict carrying an
|
|
|
139
139
|
parses zero AC rows while lint still reports PASS.
|
|
140
140
|
3. A behavioral AC marked `MET` with a non-executable evidence type (`static-ref`,
|
|
141
141
|
`manual-review`, `llm-judge`) is **downgraded to PARTIAL** by `task verdict`, making the whole
|
|
142
|
-
verdict PARTIAL. Use `test`/`command` (grep-based verification counts as `command`)
|
|
143
|
-
AC
|
|
142
|
+
verdict PARTIAL. Use `test`/`command` (grep-based verification counts as `command`). Only the
|
|
143
|
+
task author can exempt an AC, by tagging the AC item in the task file `[non-behavior]`/`[advisory]`;
|
|
144
|
+
the same tag added in the verify answer alone is ignored.
|
|
144
145
|
|
|
145
146
|
**Invariant:** a force-done task has a non-empty `done_reason` naming the timeout, a verdict
|
|
146
147
|
artifact whose AC rows cover every declared scenario, and a green lint/test run recorded in
|
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 ?? [],
|
|
@@ -90130,6 +90131,30 @@ async function readVerdictArtifact(fs, runDir, wbs) {
|
|
|
90130
90131
|
const { missing: _scratchMissing, ...result } = scratch;
|
|
90131
90132
|
return result;
|
|
90132
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
|
+
}
|
|
90133
90158
|
function computeAggregate(artifact) {
|
|
90134
90159
|
const reqs = artifact.requirements ?? [];
|
|
90135
90160
|
const acs = artifact.acceptanceCriteria ?? [];
|
|
@@ -90211,6 +90236,21 @@ function evaluateDoneTransition(input) {
|
|
|
90211
90236
|
const acs = artifact.acceptanceCriteria ?? [];
|
|
90212
90237
|
const internallyConsistentPass = artifact.verdict !== "PASS" || reqs.length > 0 || acs.length > 0;
|
|
90213
90238
|
const effective = internallyConsistentPass ? harshnessMax(artifact.verdict, computed) : "UNKNOWN";
|
|
90239
|
+
const unresolved = input.unresolvedTestEvidence ?? [];
|
|
90240
|
+
if (effective === "PASS" && unresolved.length > 0) {
|
|
90241
|
+
return {
|
|
90242
|
+
kind: "deny",
|
|
90243
|
+
verdict: "PARTIAL",
|
|
90244
|
+
message: [
|
|
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>"\`.`
|
|
90250
|
+
].join(`
|
|
90251
|
+
`)
|
|
90252
|
+
};
|
|
90253
|
+
}
|
|
90214
90254
|
if (effective === "PASS") {
|
|
90215
90255
|
return { kind: "allow", reason: "pass" };
|
|
90216
90256
|
}
|
|
@@ -90232,8 +90272,10 @@ function harshnessMax(a, b) {
|
|
|
90232
90272
|
const rank = { PASS: 0, UNKNOWN: 1, PARTIAL: 2, FAIL: 3 };
|
|
90233
90273
|
return rank[a] >= rank[b] ? a : b;
|
|
90234
90274
|
}
|
|
90275
|
+
var TEST_PATH_RE;
|
|
90235
90276
|
var init_done_transition_guard = __esm(() => {
|
|
90236
90277
|
init_verify_verdict();
|
|
90278
|
+
TEST_PATH_RE = /(?:^|[\s`'"([,])((?:[\w@.-]+\/)+[\w@.-]+\.(?:test|spec)\.[cm]?[jt]sx?)\b/g;
|
|
90237
90279
|
});
|
|
90238
90280
|
|
|
90239
90281
|
// ../../packages/app/src/workflow/workflow-resolver.ts
|
|
@@ -91839,7 +91881,9 @@ function buildAcIdentityIndex(taskContent, featureContent) {
|
|
|
91839
91881
|
if (!label)
|
|
91840
91882
|
continue;
|
|
91841
91883
|
declareIdentity(label);
|
|
91842
|
-
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();
|
|
91843
91887
|
if (head && head !== label)
|
|
91844
91888
|
declareIdentity(head);
|
|
91845
91889
|
const leading = label.split(/\s+/)[0] ?? "";
|
|
@@ -92177,6 +92221,21 @@ async function readFeatureAcBody(fs, featuresDir, featureId) {
|
|
|
92177
92221
|
return null;
|
|
92178
92222
|
}
|
|
92179
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
|
+
}
|
|
92180
92239
|
async function lintVerifyAnswerForTask(wbs, answerText, deps) {
|
|
92181
92240
|
let taskContent;
|
|
92182
92241
|
let featureId = "";
|
|
@@ -104398,7 +104457,11 @@ class AgentService {
|
|
|
104398
104457
|
const phaseSuffix = phase !== undefined ? ` for phase '${phase}'` : "";
|
|
104399
104458
|
if (executor !== undefined) {
|
|
104400
104459
|
if (source === "default") {
|
|
104401
|
-
|
|
104460
|
+
return {
|
|
104461
|
+
ok: false,
|
|
104462
|
+
exitCode: 2,
|
|
104463
|
+
message: agentDefaultExecutorMessage(selector, this.ctx.roles)
|
|
104464
|
+
};
|
|
104402
104465
|
}
|
|
104403
104466
|
if (executorDisabled(executor)) {
|
|
104404
104467
|
return {
|
|
@@ -104447,13 +104510,11 @@ class AgentService {
|
|
|
104447
104510
|
}
|
|
104448
104511
|
if (source === "default") {
|
|
104449
104512
|
const roleNames = this.ctx.roles !== undefined ? [...this.ctx.roles.keys()].join(", ") : "";
|
|
104450
|
-
const
|
|
104451
|
-
const roleList = roleNames !== "" ? `role (${roleNames})` : "a role";
|
|
104452
|
-
const executorList = names.length > 0 ? `configured executor (${names.join(", ")})` : "a configured executor";
|
|
104513
|
+
const roleList = roleNames !== "" ? `a role (${roleNames})` : "a role";
|
|
104453
104514
|
return {
|
|
104454
104515
|
ok: false,
|
|
104455
104516
|
exitCode: 2,
|
|
104456
|
-
message: `Unknown agent.default value: '${selector}'. Accepted: ${roleList}
|
|
104517
|
+
message: `Unknown agent.default value: '${selector}'. Accepted: ${roleList}, e.g. agent.default: coder.`
|
|
104457
104518
|
};
|
|
104458
104519
|
}
|
|
104459
104520
|
const canonical = resolveAgentName(selector);
|
|
@@ -104765,11 +104826,9 @@ function warnBareBinaryOnce(selector, output) {
|
|
|
104765
104826
|
warnedBareBinary.add(selector);
|
|
104766
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).`);
|
|
104767
104828
|
}
|
|
104768
|
-
function
|
|
104769
|
-
|
|
104770
|
-
|
|
104771
|
-
warnedAgentDefaultExecutor.add(selector);
|
|
104772
|
-
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.`;
|
|
104773
104832
|
}
|
|
104774
104833
|
function buildDoctorRows(results, executors, roles) {
|
|
104775
104834
|
const executorByName = new Map((executors ?? []).map((e) => [e.name, e]));
|
|
@@ -105021,7 +105080,7 @@ function buildRoutingAttribution(result) {
|
|
|
105021
105080
|
...result.model !== undefined ? { model: result.model } : {}
|
|
105022
105081
|
};
|
|
105023
105082
|
}
|
|
105024
|
-
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;
|
|
105025
105084
|
var init_agent_service = __esm(() => {
|
|
105026
105085
|
init_src();
|
|
105027
105086
|
init_src2();
|
|
@@ -105078,7 +105137,6 @@ var init_agent_service = __esm(() => {
|
|
|
105078
105137
|
SENSITIVE_FLAG = /^--?(?:api[-_]?key|authorization|credential|password|secret|token)$/i;
|
|
105079
105138
|
SENSITIVE_INLINE_FLAG = /^(--?(?:api[-_]?key|authorization|credential|password|secret|token)=).+$/i;
|
|
105080
105139
|
warnedBareBinary = new Set;
|
|
105081
|
-
warnedAgentDefaultExecutor = new Set;
|
|
105082
105140
|
});
|
|
105083
105141
|
|
|
105084
105142
|
// ../../packages/app/src/services/agent-trace-service.ts
|
|
@@ -105794,8 +105852,20 @@ async function prepareBoardModules(input) {
|
|
|
105794
105852
|
}
|
|
105795
105853
|
}
|
|
105796
105854
|
let realProjectRoot;
|
|
105797
|
-
const
|
|
105798
|
-
|
|
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");
|
|
105799
105869
|
return realProjectRoot;
|
|
105800
105870
|
};
|
|
105801
105871
|
for (const [index, declaration] of input.declarations.entries()) {
|
|
@@ -105808,22 +105878,35 @@ async function prepareBoardModules(input) {
|
|
|
105808
105878
|
}
|
|
105809
105879
|
const root = resolve17(input.projectRoot, declaration.directory);
|
|
105810
105880
|
const entryPath = resolveContained(index, input.projectRoot, declaration.directory, declaration.entry, declaration.id);
|
|
105811
|
-
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);
|
|
105812
105886
|
if (!await input.probe.isDirectory(root)) {
|
|
105813
105887
|
throw new BoardModuleConfigError(index, declaration.id, `module directory "${declaration.directory}" was not found`);
|
|
105814
105888
|
}
|
|
105815
105889
|
if (!await input.probe.isFile(entryPath)) {
|
|
105816
105890
|
throw new BoardModuleConfigError(index, declaration.id, `entry asset "${declaration.entry}" was not found in "${declaration.directory}"`);
|
|
105817
105891
|
}
|
|
105818
|
-
for (const
|
|
105819
|
-
if (!await input.probe.isFile(
|
|
105820
|
-
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`);
|
|
105821
105895
|
}
|
|
105822
105896
|
}
|
|
105823
|
-
const
|
|
105824
|
-
const
|
|
105825
|
-
if (
|
|
105826
|
-
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"));
|
|
105827
105910
|
}
|
|
105828
105911
|
descriptors.push({
|
|
105829
105912
|
...descriptorBase(declaration),
|
|
@@ -114496,7 +114579,8 @@ async function transitionTaskGuarded(deps, input) {
|
|
|
114496
114579
|
reason: input.reason,
|
|
114497
114580
|
artifact: loaded.artifact,
|
|
114498
114581
|
verdictPath: loaded.path,
|
|
114499
|
-
readError: loaded.readError
|
|
114582
|
+
readError: loaded.readError,
|
|
114583
|
+
unresolvedTestEvidence: input.forceDone === true ? [] : await findUnresolvedTestEvidence(deps.fs, dirname31(dirname31(deps.runDir)), loaded.artifact)
|
|
114500
114584
|
});
|
|
114501
114585
|
if (guardOutcome.kind === "noop") {
|
|
114502
114586
|
return { kind: "noop", wbs, status: "done", message: guardOutcome.message };
|
|
@@ -115917,11 +116001,12 @@ var init_task_service = __esm(() => {
|
|
|
115917
116001
|
});
|
|
115918
116002
|
|
|
115919
116003
|
// ../../packages/app/src/services/task-verdict.ts
|
|
115920
|
-
function deriveVerdict(answerText, taskCheckPassed) {
|
|
116004
|
+
function deriveVerdict(answerText, taskCheckPassed, taskAcceptanceCriteria = "") {
|
|
115921
116005
|
const requirements = extractRequirements(answerText);
|
|
115922
116006
|
const parsedAc = extractAcceptanceCriteria(answerText);
|
|
115923
|
-
const
|
|
115924
|
-
const
|
|
116007
|
+
const exempt = taskAcExemptions(taskAcceptanceCriteria);
|
|
116008
|
+
const acceptanceCriteria = applyAcceptanceCriteriaEvidenceRule(parsedAc.rows, exempt);
|
|
116009
|
+
const checks = extractChecks(answerText, taskCheckPassed, requirements, acceptanceCriteria, parsedAc.dropped, exempt);
|
|
115925
116010
|
if (requirements.length === 0) {
|
|
115926
116011
|
return {
|
|
115927
116012
|
verdict: "UNKNOWN",
|
|
@@ -116094,21 +116179,31 @@ function normalizeEvidenceType2(raw) {
|
|
|
116094
116179
|
}
|
|
116095
116180
|
return EVIDENCE_TYPE_PRECEDENCE2.find((candidate) => tokens.includes(candidate)) ?? null;
|
|
116096
116181
|
}
|
|
116097
|
-
function
|
|
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) {
|
|
116098
116196
|
return rows.map((row) => {
|
|
116099
|
-
if (row.status === "MET" && requiresExecutableEvidence(row.id) && row.evidenceType !== "test" && row.evidenceType !== "command") {
|
|
116197
|
+
if (row.status === "MET" && requiresExecutableEvidence(row.id, exempt) && row.evidenceType !== "test" && row.evidenceType !== "command") {
|
|
116100
116198
|
return { ...row, status: "PARTIAL" };
|
|
116101
116199
|
}
|
|
116102
116200
|
return row;
|
|
116103
116201
|
});
|
|
116104
116202
|
}
|
|
116105
|
-
function requiresExecutableEvidence(id) {
|
|
116106
|
-
|
|
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;
|
|
116203
|
+
function requiresExecutableEvidence(id, exempt) {
|
|
116204
|
+
return !exempt.has(normalizeTitle(id));
|
|
116110
116205
|
}
|
|
116111
|
-
function extractChecks(_text, taskCheckPassed, requirements, acceptanceCriteria, droppedAcRows = []) {
|
|
116206
|
+
function extractChecks(_text, taskCheckPassed, requirements, acceptanceCriteria, droppedAcRows = [], exempt = new Set) {
|
|
116112
116207
|
const checks = [
|
|
116113
116208
|
{
|
|
116114
116209
|
name: "spur task check",
|
|
@@ -116161,8 +116256,17 @@ function extractChecks(_text, taskCheckPassed, requirements, acceptanceCriteria,
|
|
|
116161
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.`
|
|
116162
116257
|
});
|
|
116163
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
|
+
}
|
|
116164
116268
|
if (acceptanceCriteria.length > 0) {
|
|
116165
|
-
const downgraded = acceptanceCriteria.filter((row) => row.status === "PARTIAL" && requiresExecutableEvidence(row.id) && row.evidenceType !== "test" && row.evidenceType !== "command");
|
|
116269
|
+
const downgraded = acceptanceCriteria.filter((row) => row.status === "PARTIAL" && requiresExecutableEvidence(row.id, exempt) && row.evidenceType !== "test" && row.evidenceType !== "command");
|
|
116166
116270
|
checks.push({
|
|
116167
116271
|
name: downgraded.length > 0 ? "evidence-rule-failed" : "evidence-rule-pass",
|
|
116168
116272
|
status: downgraded.length > 0 ? "fail" : "pass",
|
|
@@ -116193,10 +116297,12 @@ function aggregateBatchVerdicts(results) {
|
|
|
116193
116297
|
const summary = `${counts.PASS} PASS, ${counts.PARTIAL} PARTIAL, ${counts.FAIL} FAIL, ${notStarted.length} NOT-STARTED (excluded)`;
|
|
116194
116298
|
return { verdict, rolledUp, notStarted, summary };
|
|
116195
116299
|
}
|
|
116196
|
-
var EVIDENCE_TYPE_PRECEDENCE2;
|
|
116300
|
+
var EVIDENCE_TYPE_PRECEDENCE2, EXEMPTION_TAG_RE;
|
|
116197
116301
|
var init_task_verdict = __esm(() => {
|
|
116302
|
+
init_src2();
|
|
116198
116303
|
init_verify_verdict();
|
|
116199
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;
|
|
116200
116306
|
});
|
|
116201
116307
|
|
|
116202
116308
|
// ../../packages/app/src/services/token-ledger-service.ts
|
|
@@ -131412,7 +131518,8 @@ Recover with: ${err.recoveryCommand}` : err.message;
|
|
|
131412
131518
|
return;
|
|
131413
131519
|
}
|
|
131414
131520
|
const taskCheckPassed = true;
|
|
131415
|
-
const
|
|
131521
|
+
const taskAc = await readTaskAcceptanceCriteria(wbs, corpusDeps);
|
|
131522
|
+
const result = deriveVerdict(answerText, taskCheckPassed, taskAc);
|
|
131416
131523
|
const gap = await verdictScenarioKeyGap(wbs, [...result.requirements, ...result.acceptanceCriteria ?? []], corpusDeps);
|
|
131417
131524
|
if (gap !== null) {
|
|
131418
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})`);
|
|
@@ -132609,8 +132716,9 @@ ${prompt}`;
|
|
|
132609
132716
|
};
|
|
132610
132717
|
}
|
|
132611
132718
|
function drainAgentSelector(spec, context) {
|
|
132612
|
-
if (spec.executor === undefined)
|
|
132613
|
-
|
|
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
|
+
}
|
|
132614
132722
|
try {
|
|
132615
132723
|
resolveExecutor(spec.executor, context.agentConfig, { isCanonicalAgent: isAgentName });
|
|
132616
132724
|
} catch (error) {
|
|
@@ -134313,7 +134421,7 @@ import { createRequire as createRequire3 } from "module";
|
|
|
134313
134421
|
var CLI_CONFIG = {
|
|
134314
134422
|
binaryName: "spur",
|
|
134315
134423
|
binaryLabel: "spur",
|
|
134316
|
-
binaryVersion: "0.4.
|
|
134424
|
+
binaryVersion: "0.4.6",
|
|
134317
134425
|
configDir: ".spur",
|
|
134318
134426
|
configFile: ".spur/config.yaml",
|
|
134319
134427
|
databaseFile: ".spur/spur.db"
|
|
@@ -142419,14 +142527,13 @@ async function serveModuleAsset(c, modules, fs) {
|
|
|
142419
142527
|
const target = resolve27(declared.root, relative);
|
|
142420
142528
|
if (!isInsideRoot(declared.root, target))
|
|
142421
142529
|
return refusal(403, "Forbidden path");
|
|
142530
|
+
if (!(await fs.stat(target))?.isFile())
|
|
142531
|
+
return refusal(404, "Not Found");
|
|
142422
142532
|
const realRoot = resolvedOrUndefined(fs, declared.root);
|
|
142423
|
-
const
|
|
142424
|
-
if (realRoot
|
|
142533
|
+
const realTarget = resolvedOrUndefined(fs, target);
|
|
142534
|
+
if (realRoot === undefined || realTarget === undefined || !isInsideRoot(realRoot, realTarget)) {
|
|
142425
142535
|
return refusal(403, "Forbidden path");
|
|
142426
142536
|
}
|
|
142427
|
-
const realTarget = resolvedTarget ?? target;
|
|
142428
|
-
if (!(await fs.stat(realTarget))?.isFile())
|
|
142429
|
-
return refusal(404, "Not Found");
|
|
142430
142537
|
const file = Bun.file(realTarget);
|
|
142431
142538
|
const headers = new Headers({
|
|
142432
142539
|
"content-type": file.type || "application/octet-stream",
|
|
@@ -142941,7 +143048,7 @@ await init_src3();
|
|
|
142941
143048
|
import { homedir as homedir14 } from "os";
|
|
142942
143049
|
import { basename as basename20, join as join70, relative as relative14, resolve as resolve28 } from "path";
|
|
142943
143050
|
// ../server/package.json
|
|
142944
|
-
var version3 = "0.4.
|
|
143051
|
+
var version3 = "0.4.6";
|
|
142945
143052
|
|
|
142946
143053
|
// ../server/src/modules/health/index.ts
|
|
142947
143054
|
var startedAt = Date.now();
|