@gobing-ai/spur 0.4.1 → 0.4.3
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.global.yaml +4 -2
- package/config/decisions/maker-liveness.yaml +39 -0
- package/config/plugin-scripts.json +0 -4
- package/config/rules/README.md +1 -0
- package/config/rules/docs/sourced-api-claims.yaml +56 -12
- package/config/rules/strict/runtime-boundaries.yaml +7 -0
- package/config/script-placement-baseline.json +1 -19
- package/config/templates/AGENTS.md +1 -1
- package/config/workflows/feature-lifecycle.yaml +46 -1
- package/config/workflows/feature-verification.yaml +11 -0
- package/config/workflows/wrapup-pipeline.yaml +1 -1
- package/package.json +10 -10
- package/plugins/sp/README.md +23 -18
- package/plugins/sp/agents/super-planner.md +5 -6
- package/plugins/sp/agents/super-reviewer.md +43 -0
- package/plugins/sp/bunfig.toml +8 -5
- package/plugins/sp/commands/dev-review-session.md +2 -1
- package/plugins/sp/commands/dev-runall.md +18 -9
- package/plugins/sp/hooks/careful-guard.ts +1 -1
- package/plugins/sp/hooks/context-post-tool.ts +7 -404
- package/plugins/sp/hooks/context-session-start.ts +6 -192
- package/plugins/sp/hooks/context-session-stop.ts +6 -91
- package/plugins/sp/hooks/pi/guard-extension.ts +26 -12
- package/plugins/sp/hooks/task-write-guard.ts +1 -1
- package/plugins/sp/{hooks → lib}/agent-hint.ts +1 -1
- package/plugins/sp/lib/context-owner.ts +56 -0
- package/plugins/sp/lib/context-post-tool.ts +432 -0
- package/plugins/sp/lib/context-session-start.ts +214 -0
- package/plugins/sp/lib/context-session-stop.ts +196 -0
- package/plugins/sp/lib/dogfood-pipeline.ts +265 -0
- package/plugins/sp/lib/dogfood-report.ts +278 -0
- package/plugins/sp/lib/idea-handoff.generated.mjs +6 -4
- package/plugins/sp/lib/inline-run-app.ts +40 -0
- package/plugins/sp/lib/inline-run.generated.d.mts +3 -3
- package/plugins/sp/lib/inline-run.generated.mjs +1361 -87
- package/plugins/sp/lib/plan-projection.generated.d.mts +169 -0
- package/plugins/sp/lib/plan-projection.generated.mjs +704 -1
- package/plugins/sp/lib/quality-gate.generated.d.mts +0 -12
- package/plugins/sp/lib/quality-gate.generated.mjs +62 -231
- package/plugins/sp/lib/residual-scan.generated.d.mts +7 -1
- package/plugins/sp/lib/residual-scan.generated.mjs +71 -6
- package/plugins/sp/lib/run-summary-core.ts +34 -370
- package/plugins/sp/lib/transcript.ts +37 -200
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/references/roles.md +1 -1
- package/plugins/sp/scripts/batch-plan.mjs +740 -1
- package/plugins/sp/scripts/batch-plan.ts +48 -3
- package/plugins/sp/scripts/batch-preflight.mjs +23 -1
- package/plugins/sp/scripts/batch-preflight.ts +35 -295
- package/plugins/sp/scripts/daily-summary/daily-summary.mjs +108 -87
- package/plugins/sp/scripts/daily-summary/daily-summary.ts +111 -104
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.mjs +2 -1
- package/plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts +7 -265
- package/plugins/sp/scripts/dogfood-testing/validate-report.mjs +3 -0
- package/plugins/sp/scripts/dogfood-testing/validate-report.ts +6 -272
- package/plugins/sp/scripts/feature-verification-steps.mjs +7 -123
- package/plugins/sp/scripts/feature-verification-steps.ts +29 -181
- package/plugins/sp/scripts/inline-run-dispatch.mjs +27 -26
- package/plugins/sp/scripts/inline-run-dispatch.ts +1 -21
- package/plugins/sp/scripts/inline-run-setup.mjs +158 -120
- package/plugins/sp/scripts/inline-run-setup.ts +1 -21
- package/plugins/sp/scripts/pr-reviewing.mjs +19 -5
- package/plugins/sp/scripts/pr-reviewing.ts +34 -9
- package/plugins/sp/scripts/quality-gate.mjs +38 -208
- package/plugins/sp/scripts/residual-scan.mjs +73 -6
- package/plugins/sp/scripts/residual-scan.ts +8 -5
- package/plugins/sp/scripts/run-summary.mjs +160 -95
- package/plugins/sp/scripts/run-summary.ts +7 -5
- package/plugins/sp/scripts/session-timeline.mjs +57 -29
- package/plugins/sp/scripts/session-timeline.ts +4 -18
- package/plugins/sp/scripts/task-diffstat.mjs +43 -30
- package/plugins/sp/scripts/task-diffstat.ts +40 -71
- package/plugins/sp/skills/code-implementation/SKILL.md +7 -1
- package/plugins/sp/skills/code-verification/references/secu-review.md +2 -1
- package/plugins/sp/skills/code-verification/references/verdict-schema.md +4 -0
- package/plugins/sp/skills/dogfood-testing/SKILL.md +2 -2
- package/plugins/sp/skills/indexed-context/SKILL.md +14 -7
- package/plugins/sp/skills/next-feature/SKILL.md +1 -1
- package/plugins/sp/skills/parallel-execution/SKILL.md +2 -2
- package/plugins/sp/skills/parallel-execution/references/dispatch-surface.md +2 -2
- package/plugins/sp/skills/parallel-execution/references/fan-out-patterns.md +2 -2
- package/plugins/sp/skills/pr-reviewing/SKILL.md +7 -3
- package/plugins/sp/skills/session-review/SKILL.md +5 -3
- package/plugins/sp/skills/source-driven-development/SKILL.md +7 -4
- package/plugins/sp/skills/spur-cli/references/builder.md +3 -2
- package/plugins/sp/skills/spur-cli/references/decision.md +40 -8
- package/plugins/sp/skills/spur-cli/references/features.md +46 -5
- package/plugins/sp/skills/spur-cli/references/projects.md +7 -5
- package/plugins/sp/skills/spur-dev/SKILL.md +2 -0
- package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +27 -0
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +2 -3
- package/plugins/sp/skills/spur-dev/references/execution-batch-report.md +6 -1
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +56 -7
- package/plugins/sp/skills/spur-dev/references/execution-parallel-isolation.md +4 -1
- package/plugins/sp/skills/spur-dev/references/execution-worktree-landing.md +22 -0
- package/plugins/sp/skills/spur-dev/references/execution-worktree-setup.md +14 -0
- package/plugins/sp/skills/spur-dev/references/flag-glossary.md +13 -11
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +37 -25
- package/spur.js +89792 -76992
- package/web/_astro/{BoardApp.D66k_s_T.js → BoardApp.5IIl2O-z.js} +1 -1
- package/web/_astro/BoardApp.Drnx-KeM.js +324 -0
- package/web/_astro/TaskDetail.Ci4LNn9S.js +1 -0
- package/web/_astro/{arc._BUadrdH.js → arc.j2EVkYVd.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.Dh7xS1Pp.js → architectureDiagram-3BPJPVTR.DZWSFqEG.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.DFDVpfXm.js → blockDiagram-GPEHLZMM.DE6GpXCN.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.V7_3_glh.js → c4Diagram-AAUBKEIU.DoVR1G6B.js} +1 -1
- package/web/_astro/channel.DmdDxGsq.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.De1yx7Jz.js → chunk-2J33WTMH.Do6-Gbyf.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.BDhHFuxx.js → chunk-4BX2VUAB.BYV1HGHx.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.DDLkWnmC.js → chunk-55IACEB6.CvI1zkrc.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.CwfiLnA7.js → chunk-727SXJPM.DNGSCRlX.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.CtGDmUnM.js → chunk-AQP2D5EJ.DXhhgT5z.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.BOLjpw73.js → chunk-FMBD7UC4.dzkRKP2u.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.BDhQls6O.js → chunk-ND2GUHAM.DujGLeEf.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.76qBynDF.js → chunk-QZHKN3VN.BWtIT3ZU.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.N0mpPHT0.js → classDiagram-4FO5ZUOK.DyApg0Nn.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.N0mpPHT0.js → classDiagram-v2-Q7XG4LA2.DyApg0Nn.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.CKknFfjB.js → cose-bilkent-S5V4N54A.DJQB-u_l.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.Cw3ZWBAh.js → cynefin-OW5HDTMX.bi3D_siV.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.D3yIKCRD.js → dagre-BM42HDAG.C-f7TrRg.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.D51dO83Z.js → diagram-2AECGRRQ.DrPfjHQB.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.Bb-FViDj.js → diagram-5GNKFQAL.C9S4WUHO.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.D3mN8S6x.js → diagram-KO2AKTUF.Dzzye8QH.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.dMMyPcbJ.js → diagram-LMA3HP47.DX-LRiof.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.AzbvzY4C.js → diagram-OG6HWLK6.Z-kAKwIz.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.BR5I4OZb.js → erDiagram-TEJ5UH35.D6dyClSv.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.DFqvhGkn.js → flowDiagram-I6XJVG4X.Cx3q2cxD.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.ByJbhH6J.js → ganttDiagram-6RSMTGT7.DU3gH6CT.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.CInHjQR3.js → gitGraphDiagram-PVQCEYII.ISmCnJ87.js} +1 -1
- package/web/_astro/index.Do2lLdgo.css +1 -0
- package/web/_astro/{infoDiagram-5YYISTIA.Cm0dGVjJ.js → infoDiagram-5YYISTIA.DR6_iNOK.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.C3nOP6Sj.js → ishikawaDiagram-YF4QCWOH.D7wY-ZGA.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.4IvLa2MW.js → journeyDiagram-JHISSGLW.DkP9-WK8.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.DS87Cl-k.js → kanban-definition-UN3LZRKU.wtgY98M2.js} +1 -1
- package/web/_astro/{linear.MzmRrNER.js → linear.CI6djNi7.js} +1 -1
- package/web/_astro/{mermaid.core.fK-WNswS.js → mermaid.core.CuQPxS8b.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.BqipOHhY.js → mindmap-definition-RKZ34NQL.5HNIEG7R.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.0nBiPqFG.js → pieDiagram-4H26LBE5.CchhDFjE.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.Szj7Zpx5.js → quadrantDiagram-W4KKPZXB.PYXXJPSJ.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.D4UR6TWo.js → requirementDiagram-4Y6WPE33.cUQuNAKU.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.DAE7BdOs.js → sankeyDiagram-5OEKKPKP.BO7MnRIA.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.BP9ZVq25.js → sequenceDiagram-3UESZ5HK.D9bElyD6.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.BQW7mIxk.js → stateDiagram-AJRCARHV.DhGdRz7I.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.PbK4nDih.js → stateDiagram-v2-BHNVJYJU.5GvLeAoL.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.Ci_GvUpo.js → timeline-definition-PNZ67QCA.LtAT9haM.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.DNw1Y5qx.js → vennDiagram-CIIHVFJN.DRth1yLA.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.CwlkaaNe.js → wardleyDiagram-YWT4CUSO.h9MlZfcr.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.MI6ZuiH_.js → xychartDiagram-2RQKCTM6.DHqy9DYA.js} +1 -1
- package/web/index.html +2 -2
- package/config/proportional-route-table.ts +0 -155
- package/plugins/sp/commands/dev-parallel.md +0 -37
- package/plugins/sp/scripts/daily-summary/logger.ts +0 -28
- package/web/_astro/BoardApp.D6sjAJr_.js +0 -324
- package/web/_astro/TaskDetail.BynLnMNc.js +0 -1
- package/web/_astro/channel.DZVfto-r.js +0 -1
- package/web/_astro/index.COh_kBxN.css +0 -1
- /package/plugins/sp/{hooks → lib}/destructive-policy.ts +0 -0
- /package/plugins/sp/{hooks → lib}/task-file-policy.ts +0 -0
|
@@ -11,12 +11,20 @@ function getEnvVars() {
|
|
|
11
11
|
return process.env;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
// plugins/sp/
|
|
14
|
+
// plugins/sp/lib/plan-projection.generated.mjs
|
|
15
|
+
var NON_ABORTING_PREFLIGHT_CODES = new Set([
|
|
16
|
+
"L4.scenario-unverified",
|
|
17
|
+
"L4.verifying-incomplete-tasks"
|
|
18
|
+
]);
|
|
19
|
+
var OPERATOR_TOOLS = new Set(["AskUserQuestion", "ask_user_question"]);
|
|
20
|
+
var SHELL_TOOLS = new Set(["bash", "shell", "exec", "exec_command", "run_command", "command"]);
|
|
21
|
+
var SUBAGENT_TOOLS = new Set(["agent", "task", "invoke_subagent", "subagent", "dispatch_agent"]);
|
|
22
|
+
var LOWERCASE_OPERATOR_TOOLS = new Set(["askuserquestion", "ask_user_question"]);
|
|
15
23
|
var SENSITIVE_PREFIXES = ["drizzle/", "packages/config/", ".github/", "plugins/sp/hooks/"];
|
|
16
24
|
var SENSITIVE_GLOBS = ["apps/server/src/**/auth*", "**/*secret*", "**/*.sql"];
|
|
17
25
|
function globToRegex(glob) {
|
|
18
26
|
const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replaceAll("**/", "\x00").replaceAll("**", "\x01").replaceAll("*", "[^/]*").replaceAll("\x00", "(?:.*/)?").replaceAll("\x01", ".*");
|
|
19
|
-
return new RegExp(`^${escaped}
|
|
27
|
+
return new RegExp(`^${escaped}$`, "s");
|
|
20
28
|
}
|
|
21
29
|
var SENSITIVE_MATCHERS = [
|
|
22
30
|
...SENSITIVE_PREFIXES.map((prefix) => ({
|
|
@@ -32,6 +40,8 @@ function sensitiveReasonForPath(path) {
|
|
|
32
40
|
const match = SENSITIVE_MATCHERS.find((entry) => entry.test(path));
|
|
33
41
|
return match ? `matches sensitive pattern ${match.label}` : null;
|
|
34
42
|
}
|
|
43
|
+
|
|
44
|
+
// plugins/sp/scripts/task-diffstat.ts
|
|
35
45
|
function defaultGitRunner(cwd) {
|
|
36
46
|
return (args) => {
|
|
37
47
|
const run = spawnSync("git", args, { cwd, encoding: "utf8" });
|
|
@@ -52,22 +62,12 @@ function countNewlines(path, cwd) {
|
|
|
52
62
|
lines += 1;
|
|
53
63
|
return lines;
|
|
54
64
|
}
|
|
55
|
-
function
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
if (open !== -1 && close > open && open < arrow && arrow < close) {
|
|
62
|
-
const inner = raw.slice(open + 1, close);
|
|
63
|
-
const innerArrow = inner.indexOf(" => ");
|
|
64
|
-
if (innerArrow !== -1) {
|
|
65
|
-
const head = raw.slice(0, open);
|
|
66
|
-
const tail = raw.slice(close + 1);
|
|
67
|
-
return [head + inner.slice(0, innerArrow) + tail, head + inner.slice(innerArrow + 4) + tail];
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
return [raw.slice(0, arrow), raw.slice(arrow + 4)];
|
|
65
|
+
function addCounts(add, del) {
|
|
66
|
+
for (const count of [add, del])
|
|
67
|
+
if (count !== "-" && !/^\d+$/.test(count))
|
|
68
|
+
return null;
|
|
69
|
+
const [a, d] = [add === "-" ? 0 : Number(add), del === "-" ? 0 : Number(del)];
|
|
70
|
+
return Number.isSafeInteger(a) && Number.isSafeInteger(d) ? [a, d] : null;
|
|
71
71
|
}
|
|
72
72
|
function runDiffstat(env, options = {}, git = defaultGitRunner(options.cwd)) {
|
|
73
73
|
const cwd = options.cwd;
|
|
@@ -99,23 +99,37 @@ function runDiffstat(env, options = {}, git = defaultGitRunner(options.cwd)) {
|
|
|
99
99
|
} catch {}
|
|
100
100
|
if (!/^[0-9a-f]{7,40}$/i.test(base))
|
|
101
101
|
return failSafe("run base .spur/run/<wbs>-base.sha missing or malformed");
|
|
102
|
-
const numstat = git(["diff", "--numstat", base]);
|
|
102
|
+
const numstat = git(["diff", "--numstat", "-z", base]);
|
|
103
103
|
if (numstat.status !== 0)
|
|
104
104
|
return failSafe(`git diff --numstat failed (status=${numstat.status})`);
|
|
105
|
+
if (numstat.stdout.length > 0 && !numstat.stdout.endsWith("\x00"))
|
|
106
|
+
return failSafe("git diff --numstat -z output is not NUL-terminated (malformed framing)");
|
|
105
107
|
const paths = new Set;
|
|
106
108
|
let insertions = 0;
|
|
107
109
|
let deletions = 0;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
110
|
+
const records = numstat.stdout.split("\x00").slice(0, -1);
|
|
111
|
+
for (let i = 0;i < records.length; i++) {
|
|
112
|
+
const header = records[i] ?? "";
|
|
113
|
+
const first = header.indexOf("\t");
|
|
114
|
+
const second = header.indexOf("\t", first + 1);
|
|
115
|
+
if (first < 1 || second < first + 2)
|
|
116
|
+
return failSafe("git diff --numstat -z record is missing counts or path");
|
|
117
|
+
const counts = addCounts(header.slice(0, first), header.slice(first + 1, second));
|
|
118
|
+
if (counts === null)
|
|
119
|
+
return failSafe("git diff --numstat -z record has a malformed count");
|
|
120
|
+
insertions += counts[0];
|
|
121
|
+
deletions += counts[1];
|
|
122
|
+
const path = header.slice(second + 1);
|
|
123
|
+
if (path !== "") {
|
|
124
|
+
paths.add(path);
|
|
112
125
|
continue;
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
126
|
+
}
|
|
127
|
+
const oldPath = records[++i];
|
|
128
|
+
const newPath = records[++i];
|
|
129
|
+
if (!oldPath || !newPath)
|
|
130
|
+
return failSafe("git diff --numstat -z rename record has incomplete paths");
|
|
131
|
+
paths.add(oldPath);
|
|
132
|
+
paths.add(newPath);
|
|
119
133
|
}
|
|
120
134
|
const others = git(["ls-files", "--others", "--exclude-standard", "-z"]);
|
|
121
135
|
if (others.status !== 0)
|
|
@@ -161,7 +175,6 @@ export {
|
|
|
161
175
|
sensitiveReasonForPath,
|
|
162
176
|
runDiffstat,
|
|
163
177
|
main,
|
|
164
|
-
expandDiffPath,
|
|
165
178
|
TASK_DIFFSTAT_USAGE,
|
|
166
179
|
SENSITIVE_PREFIXES,
|
|
167
180
|
SENSITIVE_GLOBS
|
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
* `precheck`, F96 R1) and the current working tree, then writes:
|
|
8
8
|
* `.spur/run/<wbs>-diffstat.json` `{ files, insertions, deletions, paths[], sensitive }`
|
|
9
9
|
*
|
|
10
|
-
* Counts come from `git diff --numstat <base>` (tracked changes; binary rows count the
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* if a miscount
|
|
10
|
+
* Counts come from `git diff --numstat -z <base>` (tracked changes; binary rows count the path
|
|
11
|
+
* but no lines; NUL records carry RAW paths, so a non-ASCII or newline-bearing path reaches the
|
|
12
|
+
* sensitive matchers intact) plus untracked files (`git ls-files --others --exclude-standard -z`,
|
|
13
|
+
* newline-counted — # ponytail: untracked line counts are a newline scan, numstat-grade binary
|
|
14
|
+
* detection is unnecessary here; switch to `--numstat --no-index` if a miscount flips a lane).
|
|
15
15
|
*
|
|
16
16
|
* `sensitive` is true when ANY changed path matches the exported SENSITIVE list (0943 R2c);
|
|
17
17
|
* the workflow's pre-decide guard maps sensitive (or >400 changed lines) to the safety lane
|
|
@@ -65,45 +65,14 @@ export interface DiffstatResult extends Diffstat {
|
|
|
65
65
|
exitCode: number;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
* not prose, so the `*` / `/` sequences survive the doc comment).
|
|
75
|
-
*/
|
|
76
|
-
export const SENSITIVE_PREFIXES = ['drizzle/', 'packages/config/', '.github/', 'plugins/sp/hooks/'] as const;
|
|
77
|
-
export const SENSITIVE_GLOBS = ['apps/server/src/**/auth*', '**/*secret*', '**/*.sql'] as const;
|
|
78
|
-
|
|
79
|
-
// Glob → RegExp: `**` + `/` matches any-or-no directories, bare `**` any characters, `*` within one segment.
|
|
80
|
-
function globToRegex(glob: string): RegExp {
|
|
81
|
-
const escaped = glob
|
|
82
|
-
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
83
|
-
.replaceAll('**/', '\u0000')
|
|
84
|
-
.replaceAll('**', '\u0001')
|
|
85
|
-
.replaceAll('*', '[^/]*')
|
|
86
|
-
.replaceAll('\u0000', '(?:.*/)?')
|
|
87
|
-
.replaceAll('\u0001', '.*');
|
|
88
|
-
return new RegExp(`^${escaped}$`);
|
|
89
|
-
}
|
|
68
|
+
// Task 1176: the sensitive-path policy (0943 R2c) moved to the workflow/task owner —
|
|
69
|
+
// `packages/app/src/workflow/task-triage.ts`, reached through the generated pure bundle. That policy
|
|
70
|
+
// still forces the safety lane for any changed path under the frozen sensitive list before the
|
|
71
|
+
// `task-triage` decide runs, so sensitive work is never lane-negotiated with a model; this entry
|
|
72
|
+
// keeps git capture and the output artifact, and re-exports the policy for existing callers.
|
|
73
|
+
import { SENSITIVE_GLOBS, SENSITIVE_PREFIXES, sensitiveReasonForPath } from '../lib/plan-projection.generated.mjs';
|
|
90
74
|
|
|
91
|
-
|
|
92
|
-
...SENSITIVE_PREFIXES.map((prefix) => ({
|
|
93
|
-
label: prefix,
|
|
94
|
-
test: (path: string): boolean => path.startsWith(prefix),
|
|
95
|
-
})),
|
|
96
|
-
...SENSITIVE_GLOBS.map((glob) => {
|
|
97
|
-
const regex = globToRegex(glob);
|
|
98
|
-
return { label: glob, test: (path: string): boolean => regex.test(path) };
|
|
99
|
-
}),
|
|
100
|
-
];
|
|
101
|
-
|
|
102
|
-
/** Sensitive-pattern match for one path, or null — mirrors wrapup-drift-probe's driftReasonForPath. */
|
|
103
|
-
export function sensitiveReasonForPath(path: string): string | null {
|
|
104
|
-
const match = SENSITIVE_MATCHERS.find((entry) => entry.test(path));
|
|
105
|
-
return match ? `matches sensitive pattern ${match.label}` : null;
|
|
106
|
-
}
|
|
75
|
+
export { SENSITIVE_GLOBS, SENSITIVE_PREFIXES, sensitiveReasonForPath };
|
|
107
76
|
|
|
108
77
|
function defaultGitRunner(cwd?: string): GitRunner {
|
|
109
78
|
return (args: string[]): GitRunResult => {
|
|
@@ -125,26 +94,10 @@ function countNewlines(path: string, cwd?: string): number {
|
|
|
125
94
|
return lines;
|
|
126
95
|
}
|
|
127
96
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
*/
|
|
133
|
-
export function expandDiffPath(raw: string): string[] {
|
|
134
|
-
const arrow = raw.indexOf(' => ');
|
|
135
|
-
if (arrow === -1) return [raw];
|
|
136
|
-
const open = raw.indexOf('{');
|
|
137
|
-
const close = raw.lastIndexOf('}');
|
|
138
|
-
if (open !== -1 && close > open && open < arrow && arrow < close) {
|
|
139
|
-
const inner = raw.slice(open + 1, close);
|
|
140
|
-
const innerArrow = inner.indexOf(' => ');
|
|
141
|
-
if (innerArrow !== -1) {
|
|
142
|
-
const head = raw.slice(0, open);
|
|
143
|
-
const tail = raw.slice(close + 1);
|
|
144
|
-
return [head + inner.slice(0, innerArrow) + tail, head + inner.slice(innerArrow + 4) + tail];
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
return [raw.slice(0, arrow), raw.slice(arrow + 4)];
|
|
97
|
+
function addCounts(add: string, del: string): [number, number] | null {
|
|
98
|
+
for (const count of [add, del]) if (count !== '-' && !/^\d+$/.test(count)) return null;
|
|
99
|
+
const [a, d] = [add === '-' ? 0 : Number(add), del === '-' ? 0 : Number(del)];
|
|
100
|
+
return Number.isSafeInteger(a) && Number.isSafeInteger(d) ? [a, d] : null;
|
|
148
101
|
}
|
|
149
102
|
|
|
150
103
|
/**
|
|
@@ -190,18 +143,34 @@ export function runDiffstat(
|
|
|
190
143
|
}
|
|
191
144
|
if (!/^[0-9a-f]{7,40}$/i.test(base)) return failSafe('run base .spur/run/<wbs>-base.sha missing or malformed');
|
|
192
145
|
|
|
193
|
-
const numstat = git(['diff', '--numstat', base]);
|
|
146
|
+
const numstat = git(['diff', '--numstat', '-z', base]);
|
|
194
147
|
if (numstat.status !== 0) return failSafe(`git diff --numstat failed (status=${numstat.status})`);
|
|
148
|
+
if (numstat.stdout.length > 0 && !numstat.stdout.endsWith('\u0000'))
|
|
149
|
+
return failSafe('git diff --numstat -z output is not NUL-terminated (malformed framing)');
|
|
195
150
|
const paths = new Set<string>();
|
|
196
151
|
let insertions = 0;
|
|
197
152
|
let deletions = 0;
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if (
|
|
204
|
-
|
|
153
|
+
const records = numstat.stdout.split('\u0000').slice(0, -1); // drop the terminating NUL tail
|
|
154
|
+
for (let i = 0; i < records.length; i++) {
|
|
155
|
+
const header = records[i] ?? '';
|
|
156
|
+
const first = header.indexOf('\t'); // first two tabs only
|
|
157
|
+
const second = header.indexOf('\t', first + 1); // a raw path may itself hold more tabs
|
|
158
|
+
if (first < 1 || second < first + 2) return failSafe('git diff --numstat -z record is missing counts or path');
|
|
159
|
+
const counts = addCounts(header.slice(0, first), header.slice(first + 1, second));
|
|
160
|
+
if (counts === null) return failSafe('git diff --numstat -z record has a malformed count');
|
|
161
|
+
insertions += counts[0];
|
|
162
|
+
deletions += counts[1]; // once per count header, never once per rename half
|
|
163
|
+
const path = header.slice(second + 1);
|
|
164
|
+
if (path !== '') {
|
|
165
|
+
paths.add(path);
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
// An empty path field is Git's rename/copy form: the next two records are old and new.
|
|
169
|
+
const oldPath = records[++i];
|
|
170
|
+
const newPath = records[++i];
|
|
171
|
+
if (!oldPath || !newPath) return failSafe('git diff --numstat -z rename record has incomplete paths');
|
|
172
|
+
paths.add(oldPath);
|
|
173
|
+
paths.add(newPath);
|
|
205
174
|
}
|
|
206
175
|
const others = git(['ls-files', '--others', '--exclude-standard', '-z']);
|
|
207
176
|
if (others.status !== 0) return failSafe(`git ls-files failed (status=${others.status})`);
|
|
@@ -82,6 +82,12 @@ not have and work you were not asked to do.
|
|
|
82
82
|
- **Never implement a requirement belonging to another WBS**, even when it looks like a
|
|
83
83
|
prerequisite. If the target genuinely cannot proceed without it, stop and say so — a blocked task
|
|
84
84
|
is a cheap finding; a two-task diff costs the reverts.
|
|
85
|
+
- **Flip the target's `## Plan` boxes as each step completes.** They are this pass's progress
|
|
86
|
+
ledger, and the record-time residual sweep treats an *unchecked* Plan box as blocking: it
|
|
87
|
+
downgrades the verdict to PARTIAL and closes the `done` gate
|
|
88
|
+
(`docs/design/task-residual-sweep.md`), which costs a remediation cycle and a manual re-record.
|
|
89
|
+
Leave every finished step ticked through `spur task update <wbs> --section Plan --from-file`, and
|
|
90
|
+
if you deliberately skipped one, say so in the Solution change map.
|
|
85
91
|
|
|
86
92
|
Why this is a rule and not a nicety: driving task 0486 lost several hours to exactly this. Task 0485
|
|
87
93
|
was committed but still `todo` in the tree, and two different executors (omp run `ca130182`, claude
|
|
@@ -165,7 +171,7 @@ that single full gate.
|
|
|
165
171
|
1. The **changed-path matrix** below (narrow behavior tests + affected consumers);
|
|
166
172
|
2. the resolved `quality-gate` script in `light` mode (source-repo probe, else the installed twin) (biome on changed files, per-workspace typecheck, rule presets `rules:pre`/`rules:post`, related tests).
|
|
167
173
|
|
|
168
|
-
Neither step takes the
|
|
174
|
+
Neither step takes the full project gate. The full gate is named only for the pipeline's `test` and `test-recheck` hops.
|
|
169
175
|
|
|
170
176
|
| Changed surface | Required targeted tests | Required typechecks |
|
|
171
177
|
| --- | --- | --- |
|
|
@@ -92,7 +92,8 @@ Before declaring a task `done`, run this lightweight checklist. It catches the m
|
|
|
92
92
|
- [ ] Solution section contains `file:line` citations for every changed file.
|
|
93
93
|
- [ ] API/library claims in changed docs carry a source citation (URL or `path:line`) and an honest
|
|
94
94
|
confidence level (HIGH/MEDIUM/LOW) per source-driven-development; the deterministic advisory backstop is
|
|
95
|
-
the `cited-version-strings` / `cited-api-claims`
|
|
95
|
+
the `cited-version-strings` / `cited-api-claims` / `cited-api-claims-confidence` rules
|
|
96
|
+
(`.spur/rules/docs/sourced-api-claims.yaml`, run ad hoc — not wired into a preset).
|
|
96
97
|
- [ ] Review section has P1–P4 findings table (even if all rows are empty — the table itself proves review happened).
|
|
97
98
|
|
|
98
99
|
This checklist runs as part of the verify step when the task reaches `testing` status. It does not replace the formal gates — it augments them with between-pipeline hygiene checks.
|
|
@@ -202,6 +202,10 @@ citation in the SAME cell, or lint rejects the row with `evidence-citation` (blo
|
|
|
202
202
|
- The rule is cell-local: a citation in another row or a "receipt above" cross-reference does not
|
|
203
203
|
satisfy it. Marker-free receipts (test counts, reviewed-log notes) need no citation. The check is
|
|
204
204
|
presence-only — which citation proves which claim is not deterministically decidable.
|
|
205
|
+
- The same two predicates (`hasClaimMarker` / `hasCitationForm` in
|
|
206
|
+
`packages/app/src/services/verify-answer-lint.ts`) gate the review report's findings table through
|
|
207
|
+
`residual-scan review-gate` as the blocking `review-contract` category — one definition of
|
|
208
|
+
"external claim" and one set of accepted citation forms for both paths.
|
|
205
209
|
|
|
206
210
|
## Hedged evidence is rejected on MET rows
|
|
207
211
|
|
|
@@ -82,11 +82,11 @@ The command forwards these via `$ARGUMENTS`:
|
|
|
82
82
|
> <!-- pipeline-tokens:end -->
|
|
83
83
|
> matched as a **distinct hyphen-word**
|
|
84
84
|
> (machine-checked by
|
|
85
|
-
> [`detectPipelineDriving`](../../
|
|
85
|
+
> [`detectPipelineDriving`](../../lib/dogfood-pipeline.ts);
|
|
86
86
|
> see [§Pipeline-driving word-boundary contract](#pipeline-driving-word-boundary-contract))
|
|
87
87
|
> → `⚠ pipeline-driving testee detected; pass --max-retry 0 (observe-only) or --max-retry N (fix mode, tree mutation acknowledged)`.
|
|
88
88
|
> - **Mutating `--fix` modes** — `--fix all` / `--fix blockers-first`, boundary-guarded via
|
|
89
|
-
> [`hasMutatingFixMode`](../../
|
|
89
|
+
> [`hasMutatingFixMode`](../../lib/dogfood-pipeline.ts) (never matches
|
|
90
90
|
> `--fix none` / `--focus all` / `--prefix all`). No pipeline token required: a verify/review leg
|
|
91
91
|
> with `--fix all` mutates the tree on its own (0280 dogfood P2, task 0293). Honesty note:
|
|
92
92
|
> `--max-retry 0` here bounds **the driver only** — the testee still mutates the tree — so the
|
|
@@ -144,14 +144,21 @@ an explicit allowlist entry.
|
|
|
144
144
|
env dumps. Token estimates for Bash/Grep/Glob use response size **after a 4 KiB cap** and secret
|
|
145
145
|
scrub; omit `tokens` when unknown (never `0`).
|
|
146
146
|
|
|
147
|
+
**Session lifecycle:** `.spur/context/.session.json` is a private pointer to the in-flight session.
|
|
148
|
+
It records the creating host's owner identity (`{host, sessionId}` — Claude's hook `session_id`, Pi's
|
|
149
|
+
session-manager id) and only that owner's `SessionEnd` / `session_shutdown` may write the rollup and
|
|
150
|
+
remove the pointer. A nested run reuses the parent's ledger for tool recording but cannot finalize
|
|
151
|
+
or delete it; an ownerless (legacy) pointer is never finalized and expires under the existing
|
|
152
|
+
idle-start window instead.
|
|
153
|
+
|
|
147
154
|
```jsonl
|
|
148
|
-
{"ts":"2026-07-09T14:23:01Z","session":"session-2026-07-09-
|
|
149
|
-
{"ts":"2026-07-09T14:23:15Z","session":"session-2026-07-09-
|
|
150
|
-
{"ts":"2026-07-09T14:24:00Z","session":"session-2026-07-09-
|
|
151
|
-
{"ts":"2026-07-12T15:00:00Z","session":"session-2026-07-12-
|
|
152
|
-
{"ts":"2026-07-12T15:00:01Z","session":"session-2026-07-12-
|
|
153
|
-
{"ts":"2026-07-12T15:00:02Z","session":"session-2026-07-12-
|
|
154
|
-
{"ts":"2026-07-09T14:30:00Z","session":"session-2026-07-09-
|
|
155
|
+
{"ts":"2026-07-09T14:23:01Z","session":"session-2026-07-09-142318-a1b2c3d4","type":"session_start"}
|
|
156
|
+
{"ts":"2026-07-09T14:23:15Z","session":"session-2026-07-09-142318-a1b2c3d4","type":"read","file":"src/dao.ts","tokens":648}
|
|
157
|
+
{"ts":"2026-07-09T14:24:00Z","session":"session-2026-07-09-142318-a1b2c3d4","type":"write","file":"src/dao.ts","tokens":222,"action":"edit"}
|
|
158
|
+
{"ts":"2026-07-12T15:00:00Z","session":"session-2026-07-12-150007-e5f6a7b8","type":"bash","summary":"ls -la","tokens":12}
|
|
159
|
+
{"ts":"2026-07-12T15:00:01Z","session":"session-2026-07-12-150007-e5f6a7b8","type":"grep","summary":"/TODO/ src"}
|
|
160
|
+
{"ts":"2026-07-12T15:00:02Z","session":"session-2026-07-12-150007-e5f6a7b8","type":"glob","summary":"**/*.test.ts"}
|
|
161
|
+
{"ts":"2026-07-09T14:30:00Z","session":"session-2026-07-09-142318-a1b2c3d4","type":"session_end","totals":{"reads":5,"writes":19,"tokens":2783}}
|
|
155
162
|
```
|
|
156
163
|
|
|
157
164
|
**Never edit `token-ledger.jsonl` by hand.** It feeds the Board **Observability → Tool Using** tab.
|
|
@@ -64,7 +64,7 @@ Run the steps in order. Each step's depth lives in its reference; this file is t
|
|
|
64
64
|
change a frontier feature's status, the report **leads with the drift summary** ("sync first")
|
|
65
65
|
and the ranking is computed over the *post-sync* status view. Details:
|
|
66
66
|
[references/signal-derivation.md](references/signal-derivation.md) §0.
|
|
67
|
-
1. **Assemble the candidate set.** `spur feature list --json`; drop `done`/`cancelled`, drop
|
|
67
|
+
1. **Assemble the candidate set.** `spur feature list --json`; drop `done`/`cancelled`/`deprecated`, drop
|
|
68
68
|
structural containers (has children, no own open tasks — defect D1), drop the map/feature that
|
|
69
69
|
owns this investigation if present. The `group` tag is **not** a reliable container marker (D3).
|
|
70
70
|
2. **Gate on actionability.** Read the frontier predicate **at runtime** from
|
|
@@ -104,7 +104,7 @@ was meant to avoid. Let the reviewer reach its own conclusion.
|
|
|
104
104
|
|
|
105
105
|
- **`sp:spur-dev`** owns task selection and lifecycle. When a batch contains independent tasks, the spine consults this skill for the fan-out pattern.
|
|
106
106
|
- **`sp:super-planner`** is the orchestrator that executes the fan-out. Its parallel mode (documented in its agent definition) applies the patterns from this skill.
|
|
107
|
-
- **`/sp:dev-parallel`** is the
|
|
107
|
+
- **`/sp:dev-runall --mode parallel|auto`** is the task-batch entry point; its `batch-plan.mjs order` schedule applies this skill's dependency and file-overlap rows deterministically.
|
|
108
108
|
|
|
109
109
|
## When to use
|
|
110
110
|
|
|
@@ -135,7 +135,7 @@ Do **not** use this skill for:
|
|
|
135
135
|
|
|
136
136
|
- **`sp:spur-dev`** — the orchestration spine that consults this skill for fan-out decisions.
|
|
137
137
|
- **`sp:super-planner`** — the batch orchestrator that executes fan-out patterns.
|
|
138
|
-
- **`/sp:dev-parallel`** — the slash command entry point for parallel
|
|
138
|
+
- **`/sp:dev-runall --mode parallel|auto`** — the slash command entry point for parallel task batches.
|
|
139
139
|
|
|
140
140
|
## Platform Notes
|
|
141
141
|
|
|
@@ -70,7 +70,7 @@ own axis.
|
|
|
70
70
|
Surface choice and model choice stay separate axes, but the invocation surface is where the
|
|
71
71
|
cheap-model hint is applied. On hosts whose native subagent invocation accepts a per-call model
|
|
72
72
|
override (Claude Code: the Agent tool's `model` parameter), dispatch read-only fan-out workers —
|
|
73
|
-
|
|
73
|
+
N-way investigation and competency-lens review's read-only lenses — with the host's
|
|
74
74
|
cheapest capable model. The rule is owned by
|
|
75
75
|
[fan-out-patterns.md § Cheap-model hint for read-only fan-out](fan-out-patterns.md#cheap-model-hint-for-read-only-fan-out)
|
|
76
76
|
and carries two prohibitions and one exemption: invocation-side only, **never** a subagent
|
|
@@ -167,7 +167,7 @@ rule at the source, so no per-path shim is needed:
|
|
|
167
167
|
| `spur agent loop` | `AgentService.run` per drained iteration | Same resolution path as `spur agent run`; inherits its own `SPUR_ROLE` |
|
|
168
168
|
| `spur serve` team supervisor → member | spawns `spur agent loop` | Member inherits the supervisor's `SPUR_ROLE` (recursive by construction) |
|
|
169
169
|
| Native subagent fan-out (this skill's default) | in-session `Task()`/`Skill()` | In-session subagents share the host session; when they themselves dispatch, the host's role is already in the session env — the rule holds at the next `spur agent run` boundary |
|
|
170
|
-
| `
|
|
170
|
+
| `scripts/commands/skill-eval.ts` | `spawnSync('spur agent run', …)` per scenario | Out of scope: a top-level eval harness, not a fan-out — no dispatcher role exists to inherit; each scenario is an independent top-level run (documented, no shim) |
|
|
171
171
|
|
|
172
172
|
The inventory is recorded in `docs/04_DESIGN.md` § `spur agent run` envelope (`roleOrigin`).
|
|
173
173
|
Paths that shell out to `spur agent run` without the rule would silently drop or double-attribute
|
|
@@ -134,13 +134,13 @@ blocker** — never a permission-probing subsystem:
|
|
|
134
134
|
[Keep a durable progress ledger](../SKILL.md#keep-a-durable-progress-ledger)).
|
|
135
135
|
|
|
136
136
|
Read-only investigation fan-out avoids the write-permission class by construction:
|
|
137
|
-
|
|
137
|
+
N-way investigation and competency-lens review's read-only lenses dispatch
|
|
138
138
|
**read-only worker shapes** — search/read tools only, with no write surface declared.
|
|
139
139
|
|
|
140
140
|
## Cheap-model hint for read-only fan-out
|
|
141
141
|
|
|
142
142
|
On hosts whose native subagent invocation accepts a per-call model override (Claude Code: the Agent
|
|
143
|
-
tool's `model` parameter), dispatch read-only fan-out workers —
|
|
143
|
+
tool's `model` parameter), dispatch read-only fan-out workers — N-way investigation
|
|
144
144
|
and competency-lens review's read-only lenses — with the host's cheapest capable model. The rule is
|
|
145
145
|
invocation-side, with two prohibitions and one exemption:
|
|
146
146
|
|
|
@@ -112,9 +112,13 @@ unrelated uncommitted changes make safe editing or committing ambiguous.
|
|
|
112
112
|
**Current-HEAD invariant.** Treat the PR's pushed `headRefOid` as the review identity. After every
|
|
113
113
|
preflight, push, commit, and request, record the returned HEAD and require `wait`, `collect`, and
|
|
114
114
|
the report to match that exact HEAD. A result for an older commit is stale; a result absent for the
|
|
115
|
-
current HEAD is pending, not clean.
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
current HEAD is pending, not clean. `wait` re-asserts this at two points, not one: at the start of
|
|
116
|
+
every poll, and again immediately before it emits FOUND/CLEAN/TIMEOUT — a head that moved while the
|
|
117
|
+
review was being fetched, or between polls, fails the wait (exit 2) instead of accepting an old-head
|
|
118
|
+
result. The pinned identity is the PR number AND the expected head, so a different PR appearing on
|
|
119
|
+
the branch is also a failure, never a silent retarget. Reject uncorrelated issue/conversation
|
|
120
|
+
comments rather than using them to manufacture a current-HEAD result. In `fix` mode, discard the old
|
|
121
|
+
result after the fix commit and judge only the forced re-review for the new pushed HEAD.
|
|
118
122
|
|
|
119
123
|
**Reviewer authority.** Codex is an independent reviewer, not an authority. For every finding that
|
|
120
124
|
may lead to a code change: inspect the referenced code, verify reachability and intended behavior,
|
|
@@ -101,9 +101,11 @@ issues with its evidence. Notes stay report-only.
|
|
|
101
101
|
refresh it with `superskill install sp` before reporting numbers; report `n/a` until then.
|
|
102
102
|
The transcript resolves from `--transcript`, else `CLAUDE_CODE_SESSION_ID`, else `PI_SESSION_FILE`
|
|
103
103
|
(the pi host sets it and no Claude session id); with none of them the measurement reports
|
|
104
|
-
`available:false` with a reason and the table renders `n/a` instead of guessing. A
|
|
105
|
-
|
|
106
|
-
segment
|
|
104
|
+
`available:false` with a reason and the table renders `n/a` instead of guessing. A pi
|
|
105
|
+
`<skill name="…">` wrapper row is the OPERATOR'S own `/skill` invocation, not an injected body:
|
|
106
|
+
it opens a segment rendered `/<name> <args>` (or `/<name>` when no arguments follow the closing
|
|
107
|
+
tag), and the wrapper body never appears in the prompt excerpt. Only the operator's prompts
|
|
108
|
+
segment the session.
|
|
107
109
|
2. **Inventory outcomes.** List requested outcomes and classify each as completed, partial, blocked,
|
|
108
110
|
or not attempted. Collapse repeated attempts into one outcome.
|
|
109
111
|
3. **Classify issues.** For every material issue, distinguish resolved, open, or attempted. Record
|
|
@@ -101,10 +101,13 @@ line: `Verification: HIGH|MEDIUM|LOW — <first failing check and deviation, or
|
|
|
101
101
|
- **Honest confidence** — the stated HIGH/MEDIUM/LOW matches the evidence: HIGH requires a check against the pinned source this session; MEDIUM/LOW items are labeled, and LOW is never presented as fact.
|
|
102
102
|
- **HIGH** — all three checks pass. **MEDIUM** — coverage intact but some citations are off-version (latest docs for a pinned dependency) or secondary-source only. **LOW** — an API/library claim lacks any citation, or a LOW-confidence claim was asserted as fact.
|
|
103
103
|
|
|
104
|
-
Deterministic backstop: the `cited-version-strings
|
|
105
|
-
(`.spur/rules/docs/sourced-api-claims.yaml
|
|
106
|
-
assert version
|
|
107
|
-
|
|
104
|
+
Deterministic backstop: the `cited-version-strings`, `cited-api-claims` and
|
|
105
|
+
`cited-api-claims-confidence` rules (`.spur/rules/docs/sourced-api-claims.yaml`) flag markdown lines
|
|
106
|
+
that assert a version string or a time-anchored API claim with no citation anchor on the same line,
|
|
107
|
+
and claim lines that carry no HIGH/MEDIUM/LOW level. They are an info-severity advisory backstop for
|
|
108
|
+
this contract and are deliberately NOT wired into `recommended-pre-check` — run them ad hoc with
|
|
109
|
+
`spur rule run --file .spur/rules/docs/sourced-api-claims.yaml`. The checks above and the SECUA
|
|
110
|
+
checklist remain the enforcement points.
|
|
108
111
|
|
|
109
112
|
## Common Rationalizations
|
|
110
113
|
|
|
@@ -8,8 +8,9 @@ see_also:
|
|
|
8
8
|
# spur builder - release plumbing
|
|
9
9
|
|
|
10
10
|
`spur builder` is the CLI for **version bumps and release tags**. It wraps the internal
|
|
11
|
-
`
|
|
12
|
-
`scripts/commands/release.ts`
|
|
11
|
+
`release-ops` implementation behind a public two-verb surface. The repo-local forwarder
|
|
12
|
+
(`scripts/commands/release.ts`) and its `spur-dev` dispatcher cases were retired in task 1173; the
|
|
13
|
+
root `bun run bump-ver` / `bun run drop-tags` names route to `spur builder` directly. Package ids are
|
|
13
14
|
the unscoped short names (`@gobing-ai/spur` → `spur`); the released set and the aggregate tag are
|
|
14
15
|
discovered from the repo's own workspace manifests, so the same code serves any git+semver
|
|
15
16
|
monorepo.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: spur-cli-decision
|
|
3
|
-
description: "spur-cli noun reference: operate `spur decision` as the read/serve surface over the decision catalog - list registered decisions, show one served contract (parameters, criteria vocabulary, effective maker), run a decision to a served outcome (never writes a workflow resultFile), and status the catalog layers. Backed by @gobing-ai/ts-ai-decision 0.5.
|
|
3
|
+
description: "spur-cli noun reference: operate `spur decision` as the read/serve surface over the decision catalog - list registered decisions, show one served contract (parameters, criteria vocabulary, effective maker), run a decision to a served outcome (never writes a workflow resultFile), and status the catalog layers with per-maker readiness marks (registered, configured, recorded, probed). Backed by @gobing-ai/ts-ai-decision 0.5.22 hub; offline by default via the built-in `typesafe` maker. Introduced by task 1093; readiness marks and `--probe` by feature P11."
|
|
4
4
|
see_also:
|
|
5
5
|
- spur-cli
|
|
6
6
|
---
|
|
@@ -25,9 +25,10 @@ Decisions resolve first-wins across three layers:
|
|
|
25
25
|
| `registered` | folders listed in config `decisions.paths` | Operator-added catalogs |
|
|
26
26
|
| `shared` | `decisions/` under the bundled config root | Shipped catalogs (`config/decisions/` in-repo) |
|
|
27
27
|
|
|
28
|
-
The bundled shared catalog ships `task-triage`, `failure-class`,
|
|
29
|
-
`
|
|
30
|
-
|
|
28
|
+
The bundled shared catalog ships `task-triage`, `failure-class`, `review-failure-class`,
|
|
29
|
+
`gate-evidence`, `idea-recommendation`, `needs-design`, `anatomy-validation-verdict` and
|
|
30
|
+
`maker-liveness` (the last served only by `spur decision status --probe`; no workflow references
|
|
31
|
+
it). Duplicate ids across layers keep the higher layer; duplicates inside one file are a load error.
|
|
31
32
|
|
|
32
33
|
**Config keys** (under `decisions:` in the global `~/.config/spur/config.yaml` or project `.spur/config.yaml`):
|
|
33
34
|
`paths` (extra catalog folders forming the `registered` layer), `maker` (default DecisionMaker
|
|
@@ -41,7 +42,7 @@ config error, never a silent fallback (design §3.6).
|
|
|
41
42
|
| `list` | Every visible decision: id, type, layer, catalog file, description | `--layer <layer>`, `--json` |
|
|
42
43
|
| `show <id>` | One served contract: type, parameters, criteria, fallback, minConfidence, effective maker + source | `--json` |
|
|
43
44
|
| `run <id>` | Serve one outcome (closed vocabulary; **never writes a resultFile**) | `--param <pairs...>`, `--evidence <files...>`, `--maker <name>`, `--json` |
|
|
44
|
-
| `status` |
|
|
45
|
+
| `status` | Sectioned readiness report: one status line, errors, aligned `Makers` and `Decisions` tables (registration, configuration source, recorded evidence), `Catalog` block | `--probe`, `--reliability [--since <iso>]`, `--json` |
|
|
45
46
|
|
|
46
47
|
All verbs support `--json` (bare payload) and `--json-envelope` (`{ok, data|error}` envelope,
|
|
47
48
|
ADR-091). Failure output for `--json` commands uses the canonical error envelope only under
|
|
@@ -51,6 +52,25 @@ ADR-091). Failure output for `--json` commands uses the canonical error envelope
|
|
|
51
52
|
(design §3.4 — load errors surface via `status`) except for an invalid `--layer` value (exit 1).
|
|
52
53
|
Error envelopes use `NOT_FOUND` for an unknown id and `VALIDATION_FAILED` for every other caller mistake.
|
|
53
54
|
|
|
55
|
+
## Readiness marks (`status`, feature P11)
|
|
56
|
+
|
|
57
|
+
The default `status` output separates four marks per maker and per decision
|
|
58
|
+
(`docs/design/decision-observability-and-adoption.md` §3.4.1):
|
|
59
|
+
|
|
60
|
+
| Mark | Column | Source |
|
|
61
|
+
| ---- | ------ | ------ |
|
|
62
|
+
| registered | `ST` glyph on the maker row | the maker registry holds the name |
|
|
63
|
+
| configured | `CONFIGURED BY` | `decisions.maker` / `decisions.makers.<id>` |
|
|
64
|
+
| recorded | `RECORDED` / `ACCEPTED` / `LAST SEEN` | the recorded `decision.end` ledger, matched on `(decisionId, maker)` |
|
|
65
|
+
| proven | `PROBE` (only with `--probe`) | one live liveness decision per maker |
|
|
66
|
+
|
|
67
|
+
`RECORDED` with `ACCEPTED 0` is the state that reads as healthy everywhere else: the maker is
|
|
68
|
+
registered and configured but has never once succeeded. `--probe` is the only path that calls a
|
|
69
|
+
maker, is bounded per driver, writes no decision log row and emits no event (`--probe` with
|
|
70
|
+
`--reliability` is refused). Parse the marks with `--json`: `makers[]`
|
|
71
|
+
(`registered`, `configuredBy`, `serves`, `evidence`), `perDecision[].evidence`, and `probes[]`
|
|
72
|
+
under `--probe`.
|
|
73
|
+
|
|
54
74
|
**Exit contract for `run` (design §3.4):** any backend-observed outcome — low confidence, model
|
|
55
75
|
error surfaced as fallback, declined — is still a **successful serve** (exit 0, envelope carries
|
|
56
76
|
`source`/`confidence` so callers can see why). Exit 1 is reserved for caller mistakes caught
|
|
@@ -79,6 +99,13 @@ exist there). The final `catalog-default` maker is the hub built-in `typesafe`
|
|
|
79
99
|
without a configured backend still produce deterministic closed-vocabulary outcomes, which is why
|
|
80
100
|
`run` is usable in tests and scripts with no model access.
|
|
81
101
|
|
|
102
|
+
**Available makers:** the three upstream built-ins (`typesafe`, `fm-local`, `laya-local`) plus
|
|
103
|
+
Spur's hosted `clef` (Cloudflare Workers AI via `@gobing-ai/ts-decision-clef`). `clef` registers
|
|
104
|
+
only when both `CLOUDFLARE_ACCOUNT_ID` and `CLOUDFLARE_AUTH_TOKEN` are set in the environment;
|
|
105
|
+
without them it is absent from `registeredMakers`, so naming it in config or `--maker` is a config
|
|
106
|
+
error, never a silent fallback. Its model defaults to `clef-flash` (a catalog `model` selects
|
|
107
|
+
`clef`).
|
|
108
|
+
|
|
82
109
|
## Examples
|
|
83
110
|
|
|
84
111
|
```bash
|
|
@@ -88,11 +115,16 @@ spur decision run task-triage --param wbs=1093 --json
|
|
|
88
115
|
spur decision run failure-class --param wbs=1093 \
|
|
89
116
|
--evidence .spur/run/<runId>-test-output.txt # redacted evidence -> instructions
|
|
90
117
|
spur decision status --json | jq '.loadErrors' # audit catalog health
|
|
118
|
+
spur decision status # readiness marks: registered / configured / recorded
|
|
119
|
+
spur decision status --probe # prove each effective maker answers now
|
|
120
|
+
spur decision status --json | jq '.makers[] | {name, registered, recorded: .evidence.recorded}'
|
|
91
121
|
```
|
|
92
122
|
|
|
93
123
|
## References
|
|
94
124
|
|
|
95
|
-
- Design: `docs/design/decision-catalog.md` (§2 catalog authoring, §3.4 CLI surface)
|
|
96
|
-
-
|
|
97
|
-
-
|
|
125
|
+
- Design: `docs/design/decision-catalog.md` (§2 catalog authoring, §3.4 CLI surface),
|
|
126
|
+
`docs/design/decision-observability-and-adoption.md` (§3.4 reliability report, §3.4.1 readiness view)
|
|
127
|
+
- Hub: `@gobing-ai/ts-ai-decision` 0.5.22 (`DecisionService`, catalog schema, maker registry)
|
|
128
|
+
- App service: `packages/app/src/decision/decision-service.ts` (makers, `probeMaker`),
|
|
129
|
+
`packages/app/src/decision/decision-status-readiness.ts` (readiness marks, probe loop)
|
|
98
130
|
- Shipped catalogs: `config/decisions/*.yaml`
|