@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
|
@@ -5,8 +5,9 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
5
5
|
|
|
6
6
|
// plugins/sp/scripts/daily-summary/daily-summary.ts
|
|
7
7
|
import { spawn, spawnSync } from "child_process";
|
|
8
|
-
import { existsSync, mkdirSync, readlinkSync, writeFileSync } from "fs";
|
|
9
|
-
import { dirname, isAbsolute, join, resolve } from "path";
|
|
8
|
+
import { existsSync as existsSync2, mkdirSync, readlinkSync, writeFileSync } from "fs";
|
|
9
|
+
import { dirname as dirname2, isAbsolute, join as join2, resolve as resolve2 } from "path";
|
|
10
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
10
11
|
|
|
11
12
|
// plugins/sp/lib/env.ts
|
|
12
13
|
function getEnvVar(name, fallback) {
|
|
@@ -17,20 +18,54 @@ function getEnvVars() {
|
|
|
17
18
|
return process.env;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
// plugins/sp/
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
// plugins/sp/lib/inline-run-app.ts
|
|
22
|
+
import { existsSync } from "node:fs";
|
|
23
|
+
import { dirname, join, resolve } from "node:path";
|
|
24
|
+
import { fileURLToPath } from "node:url";
|
|
25
|
+
function resolveAppEntry(spurBin) {
|
|
26
|
+
const fallback = fileURLToPath(new URL("../../../apps/cli/src/index.ts", import.meta.url));
|
|
27
|
+
const candidates = spurBin !== "" ? [spurBin] : [fallback];
|
|
28
|
+
for (const candidate of candidates) {
|
|
29
|
+
const tokens = candidate.split(/\s+/).filter(Boolean);
|
|
30
|
+
const mainModule = [...tokens].reverse().find((t) => t.endsWith(".ts"));
|
|
31
|
+
if (mainModule === undefined || !existsSync(mainModule))
|
|
32
|
+
continue;
|
|
33
|
+
const repoRoot = resolve(dirname(mainModule), "..", "..", "..");
|
|
34
|
+
const appEntry = join(repoRoot, "packages", "app", "src", "index.ts");
|
|
35
|
+
if (existsSync(appEntry))
|
|
36
|
+
return { entry: appEntry, portable: false };
|
|
37
|
+
}
|
|
38
|
+
const entry = fileURLToPath(new URL("../lib/inline-run.generated.mjs", import.meta.url));
|
|
39
|
+
if (!existsSync(entry)) {
|
|
40
|
+
throw new Error("inline application bundle is missing — rebuild/install the sp plugin before running inline");
|
|
41
|
+
}
|
|
42
|
+
return { entry, portable: true };
|
|
43
|
+
}
|
|
44
|
+
async function loadInlineApp(spurBin, resolveAppEntry2, required) {
|
|
45
|
+
const { entry, portable } = resolveAppEntry2(spurBin);
|
|
46
|
+
const app = await import(entry);
|
|
47
|
+
const missing = portable ? required.filter((name) => typeof app[name] !== "function") : [];
|
|
48
|
+
if (missing.length > 0) {
|
|
49
|
+
throw new Error(`inline application bundle ${entry} is older than its scripts — missing ${missing.join(", ")}. Reinstall sp with a superskill release that stages lib/ (commit b42961b), or pass --spur-bin <spur checkout>/apps/cli/src/index.ts.`);
|
|
50
|
+
}
|
|
51
|
+
return { app, portable };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// plugins/sp/scripts/daily-summary/daily-summary.ts
|
|
55
|
+
var consoleEnabled = true;
|
|
56
|
+
function enableLogger(consoleOn) {
|
|
57
|
+
consoleEnabled = consoleOn;
|
|
58
|
+
}
|
|
59
|
+
function logAt(stream, message) {
|
|
60
|
+
if (!consoleEnabled)
|
|
24
61
|
return;
|
|
25
62
|
console[stream](message);
|
|
26
63
|
}
|
|
27
64
|
var logger = {
|
|
28
|
-
info: (message) =>
|
|
29
|
-
warn: (message) =>
|
|
30
|
-
error: (message) =>
|
|
65
|
+
info: (message) => logAt("log", message),
|
|
66
|
+
warn: (message) => logAt("warn", message),
|
|
67
|
+
error: (message) => logAt("error", message)
|
|
31
68
|
};
|
|
32
|
-
|
|
33
|
-
// plugins/sp/scripts/daily-summary/daily-summary.ts
|
|
34
69
|
var DAILY_DIR = "docs/daily";
|
|
35
70
|
var DEFAULT_DATE = "today";
|
|
36
71
|
function parseArgs(argv = process.argv.slice(2)) {
|
|
@@ -108,7 +143,7 @@ function getDateRange(dateStr) {
|
|
|
108
143
|
return { start, end };
|
|
109
144
|
}
|
|
110
145
|
var defaultProcessSpawner = (cmd, args, env) => {
|
|
111
|
-
return new Promise((
|
|
146
|
+
return new Promise((resolve3, reject) => {
|
|
112
147
|
try {
|
|
113
148
|
const proc = spawn(cmd, args, {
|
|
114
149
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -119,7 +154,7 @@ var defaultProcessSpawner = (cmd, args, env) => {
|
|
|
119
154
|
proc.stdout.on("data", (chunk) => stdoutChunks.push(Buffer.from(chunk)));
|
|
120
155
|
proc.stderr.on("data", (chunk) => stderrChunks.push(Buffer.from(chunk)));
|
|
121
156
|
proc.on("close", (exitCode) => {
|
|
122
|
-
|
|
157
|
+
resolve3({
|
|
123
158
|
stdout: Buffer.concat(stdoutChunks).toString("utf8"),
|
|
124
159
|
stderr: Buffer.concat(stderrChunks).toString("utf8"),
|
|
125
160
|
exitCode: exitCode ?? 0
|
|
@@ -158,72 +193,49 @@ async function getCcusageData(date) {
|
|
|
158
193
|
return null;
|
|
159
194
|
}
|
|
160
195
|
}
|
|
196
|
+
function composeHistoryHealth(reading) {
|
|
197
|
+
const loops = reading.loops.map((row) => ({
|
|
198
|
+
toolName: row.toolName,
|
|
199
|
+
argsDigest: row.argsDigest || "repeated execution",
|
|
200
|
+
repeats: row.repeats,
|
|
201
|
+
sessionId: row.sessionId,
|
|
202
|
+
fromSeq: row.firstSeq,
|
|
203
|
+
toSeq: row.lastSeq,
|
|
204
|
+
wastedTokens: row.repeats * 250
|
|
205
|
+
}));
|
|
206
|
+
const remediationProposals = [];
|
|
207
|
+
for (const lp of loops.slice(0, 5)) {
|
|
208
|
+
const cleanTool = lp.toolName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
209
|
+
const key = `repetition:${cleanTool}:${lp.argsDigest.slice(0, 16)}`;
|
|
210
|
+
const title = `Break execution loop in ${lp.toolName} (${lp.repeats} repeats)`;
|
|
211
|
+
const command = `spur task create "<title>" --feature <id> && spur task update <wbs> --section Plan --from-file <path>`;
|
|
212
|
+
remediationProposals.push({ key, title, command });
|
|
213
|
+
}
|
|
214
|
+
if (reading.toolCalls > 0 && reading.errorRatePct > 10) {
|
|
215
|
+
remediationProposals.push({
|
|
216
|
+
key: "reliability:tooling:high-error-rate",
|
|
217
|
+
title: `Investigate high tool error rate (${reading.errorRatePct.toFixed(1)}%)`,
|
|
218
|
+
command: `spur task create "<title>" --feature <id> && spur task update <wbs> --section Plan --from-file <path>`
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
toolCalls: reading.toolCalls,
|
|
223
|
+
toolErrors: reading.toolErrors,
|
|
224
|
+
errorRatePct: reading.errorRatePct,
|
|
225
|
+
loops,
|
|
226
|
+
redundantCalls: loops.reduce((acc, l) => acc + Math.max(0, l.repeats - 1), 0),
|
|
227
|
+
wastedTokens: loops.reduce((acc, l) => acc + l.wastedTokens, 0),
|
|
228
|
+
remediationProposals
|
|
229
|
+
};
|
|
230
|
+
}
|
|
161
231
|
async function getSpurHistoryHealth(date, dbPath = ".spur/spur.db") {
|
|
162
232
|
try {
|
|
163
|
-
const resolvedPath =
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
try {
|
|
170
|
-
const loopTable = db.query("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?").get("history_board_loop_findings");
|
|
171
|
-
let loops = [];
|
|
172
|
-
if (loopTable) {
|
|
173
|
-
const rows = db.query(`SELECT tool_name, args_digest, repeats, session_id, first_seq, last_seq, started_at
|
|
174
|
-
FROM history_board_loop_findings
|
|
175
|
-
WHERE started_at IS NULL OR started_at LIKE ?
|
|
176
|
-
ORDER BY repeats DESC
|
|
177
|
-
LIMIT 20`).all(`${date}%`);
|
|
178
|
-
loops = rows.map((r) => ({
|
|
179
|
-
toolName: r.tool_name,
|
|
180
|
-
argsDigest: r.args_digest || "repeated execution",
|
|
181
|
-
repeats: r.repeats,
|
|
182
|
-
sessionId: r.session_id,
|
|
183
|
-
fromSeq: r.first_seq,
|
|
184
|
-
toSeq: r.last_seq,
|
|
185
|
-
wastedTokens: r.repeats * 250
|
|
186
|
-
}));
|
|
187
|
-
}
|
|
188
|
-
let toolCalls = 0;
|
|
189
|
-
let toolErrors = 0;
|
|
190
|
-
const toolTable = db.query("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?").get("history_board_tool_5m");
|
|
191
|
-
if (toolTable) {
|
|
192
|
-
const stats = db.query(`SELECT SUM(calls) AS calls, SUM(errors) AS errors
|
|
193
|
-
FROM history_board_tool_5m
|
|
194
|
-
WHERE bucket_start LIKE ?`).get(`${date}%`);
|
|
195
|
-
toolCalls = stats?.calls ?? 0;
|
|
196
|
-
toolErrors = stats?.errors ?? 0;
|
|
197
|
-
}
|
|
198
|
-
const redundantCalls = loops.reduce((acc, l) => acc + Math.max(0, l.repeats - 1), 0);
|
|
199
|
-
const wastedTokens = loops.reduce((acc, l) => acc + l.wastedTokens, 0);
|
|
200
|
-
const errorRatePct = toolCalls > 0 ? toolErrors / toolCalls * 100 : 0;
|
|
201
|
-
const remediationProposals = [];
|
|
202
|
-
for (const lp of loops.slice(0, 5)) {
|
|
203
|
-
const cleanTool = lp.toolName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
204
|
-
const key = `repetition:${cleanTool}:${lp.argsDigest.slice(0, 16)}`;
|
|
205
|
-
const title = `Break execution loop in ${lp.toolName} (${lp.repeats} repeats)`;
|
|
206
|
-
const command = `spur task create "<title>" --feature <id> && spur task update <wbs> --section Plan --from-file <path>`;
|
|
207
|
-
remediationProposals.push({ key, title, command });
|
|
208
|
-
}
|
|
209
|
-
if (toolCalls > 0 && errorRatePct > 10) {
|
|
210
|
-
const key = "reliability:tooling:high-error-rate";
|
|
211
|
-
const title = `Investigate high tool error rate (${errorRatePct.toFixed(1)}%)`;
|
|
212
|
-
const command = `spur task create "<title>" --feature <id> && spur task update <wbs> --section Plan --from-file <path>`;
|
|
213
|
-
remediationProposals.push({ key, title, command });
|
|
214
|
-
}
|
|
215
|
-
return {
|
|
216
|
-
toolCalls,
|
|
217
|
-
toolErrors,
|
|
218
|
-
errorRatePct,
|
|
219
|
-
loops,
|
|
220
|
-
redundantCalls,
|
|
221
|
-
wastedTokens,
|
|
222
|
-
remediationProposals
|
|
223
|
-
};
|
|
224
|
-
} finally {
|
|
225
|
-
db.close();
|
|
226
|
-
}
|
|
233
|
+
const resolvedPath = resolve2(process.cwd(), dbPath);
|
|
234
|
+
const { app } = await loadInlineApp(getEnvVar("SPUR_BIN") ?? "", resolveAppEntry, [
|
|
235
|
+
"readHistoryHealth"
|
|
236
|
+
]);
|
|
237
|
+
const reading = await app.readHistoryHealth({ date, dbPath: resolvedPath });
|
|
238
|
+
return reading === null ? null : composeHistoryHealth(reading);
|
|
227
239
|
} catch {
|
|
228
240
|
return null;
|
|
229
241
|
}
|
|
@@ -320,9 +332,9 @@ Please provide the following (press Enter to skip):
|
|
|
320
332
|
input: process.stdin,
|
|
321
333
|
output: process.stdout
|
|
322
334
|
});
|
|
323
|
-
const question = (prompt) => new Promise((
|
|
335
|
+
const question = (prompt) => new Promise((resolve3) => {
|
|
324
336
|
rl.question(prompt, (answer) => {
|
|
325
|
-
|
|
337
|
+
resolve3(answer.trim());
|
|
326
338
|
});
|
|
327
339
|
});
|
|
328
340
|
const learnings = await question(`1. What did you learn today? (optional)
|
|
@@ -468,26 +480,26 @@ function generateMarkdown(summary) {
|
|
|
468
480
|
`);
|
|
469
481
|
}
|
|
470
482
|
function ensureDir(path) {
|
|
471
|
-
if (!
|
|
483
|
+
if (!existsSync2(path)) {
|
|
472
484
|
mkdirSync(path, { recursive: true });
|
|
473
485
|
}
|
|
474
486
|
}
|
|
475
487
|
function writeSummary(markdown, options) {
|
|
476
488
|
const filename = `summary_${options.date.replace(/-/g, "")}.md`;
|
|
477
|
-
const outputPath = options.outputPath ||
|
|
478
|
-
ensureDir(
|
|
489
|
+
const outputPath = options.outputPath || join2(DAILY_DIR, filename);
|
|
490
|
+
ensureDir(join2(outputPath, ".."));
|
|
479
491
|
writeFileSync(outputPath, markdown, "utf-8");
|
|
480
492
|
return outputPath;
|
|
481
493
|
}
|
|
482
494
|
function resolveHistoryReportPath() {
|
|
483
|
-
const pointer =
|
|
484
|
-
if (!
|
|
495
|
+
const pointer = resolve2(process.cwd(), ".spur", "reports", "history", "latest.json");
|
|
496
|
+
if (!existsSync2(pointer)) {
|
|
485
497
|
return;
|
|
486
498
|
}
|
|
487
499
|
try {
|
|
488
500
|
const target = readlinkSync(pointer);
|
|
489
|
-
const resolved = isAbsolute(target) ? target :
|
|
490
|
-
return
|
|
501
|
+
const resolved = isAbsolute(target) ? target : resolve2(dirname2(pointer), target);
|
|
502
|
+
return existsSync2(resolved) ? resolved : undefined;
|
|
491
503
|
} catch {
|
|
492
504
|
return;
|
|
493
505
|
}
|
|
@@ -540,13 +552,21 @@ async function buildDailySummary(options) {
|
|
|
540
552
|
result.gitActivity = gitActivity;
|
|
541
553
|
}
|
|
542
554
|
const historyHealth = await getSpurHistoryHealth(options.date);
|
|
543
|
-
if (historyHealth
|
|
555
|
+
if (historyHealth) {
|
|
544
556
|
result.historyHealth = historyHealth;
|
|
545
557
|
platforms.push("Spur History");
|
|
546
558
|
}
|
|
547
559
|
return result;
|
|
548
560
|
}
|
|
549
561
|
async function main() {
|
|
562
|
+
if (!process.versions.bun) {
|
|
563
|
+
const child = spawnSync("bun", [fileURLToPath2(import.meta.url), ...process.argv.slice(2)], {
|
|
564
|
+
stdio: "inherit"
|
|
565
|
+
});
|
|
566
|
+
if (!child.error)
|
|
567
|
+
process.exit(child.status ?? 1);
|
|
568
|
+
console.error(`daily-summary: Bun not on PATH (${child.error.message}); history health unavailable`);
|
|
569
|
+
}
|
|
550
570
|
const options = parseArgs();
|
|
551
571
|
logger.info(`Generating daily summary for ${options.date}...`);
|
|
552
572
|
try {
|
|
@@ -602,6 +622,7 @@ export {
|
|
|
602
622
|
getCcusageData,
|
|
603
623
|
generateMarkdown,
|
|
604
624
|
ensureDir,
|
|
625
|
+
enableLogger,
|
|
605
626
|
defaultProcessSpawner,
|
|
606
627
|
buildDailySummary
|
|
607
628
|
};
|
|
@@ -11,9 +11,31 @@
|
|
|
11
11
|
import { spawn, spawnSync } from 'node:child_process';
|
|
12
12
|
import { existsSync, mkdirSync, readlinkSync, writeFileSync } from 'node:fs';
|
|
13
13
|
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
14
15
|
import { getEnvVar, getEnvVars } from '../../lib/env';
|
|
16
|
+
import { loadInlineApp, resolveAppEntry } from '../../lib/inline-run-app';
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
// ─── In-script logger ────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
// The former `logger.ts` helper exposed a file channel nothing ever implemented, so the used part
|
|
21
|
+
// — a console toggle the tests need — lives here instead of a one-caller module (task 1173, plan P7).
|
|
22
|
+
let consoleEnabled = true;
|
|
23
|
+
|
|
24
|
+
/** Silence or restore this script's console output (test hook). */
|
|
25
|
+
export function enableLogger(consoleOn: boolean): void {
|
|
26
|
+
consoleEnabled = consoleOn;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function logAt(stream: 'log' | 'warn' | 'error', message: string): void {
|
|
30
|
+
if (!consoleEnabled) return;
|
|
31
|
+
console[stream](message);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const logger = {
|
|
35
|
+
info: (message: string): void => logAt('log', message),
|
|
36
|
+
warn: (message: string): void => logAt('warn', message),
|
|
37
|
+
error: (message: string): void => logAt('error', message),
|
|
38
|
+
};
|
|
17
39
|
|
|
18
40
|
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
19
41
|
|
|
@@ -287,113 +309,85 @@ export async function getCcusageData(date: string): Promise<CcusageData | null>
|
|
|
287
309
|
|
|
288
310
|
// ─── Spur History Health Integration ──────────────────────────────────────────
|
|
289
311
|
|
|
312
|
+
/** The day's raw history health read back across the generated inline app entry (task 1178 R3). */
|
|
313
|
+
interface HistoryHealthReading {
|
|
314
|
+
toolCalls: number;
|
|
315
|
+
toolErrors: number;
|
|
316
|
+
errorRatePct: number;
|
|
317
|
+
loops: Array<{
|
|
318
|
+
toolName: string;
|
|
319
|
+
argsDigest: string;
|
|
320
|
+
repeats: number;
|
|
321
|
+
sessionId: string;
|
|
322
|
+
firstSeq: number;
|
|
323
|
+
lastSeq: number;
|
|
324
|
+
startedAt: string | null;
|
|
325
|
+
}>;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** The generated inline app entry's history-health export. */
|
|
329
|
+
interface InlineHistoryApp {
|
|
330
|
+
readHistoryHealth(input: { date: string; dbPath: string }): Promise<HistoryHealthReading | null>;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Compose the daily report's health summary from the app reading. `wastedTokens` stays an
|
|
335
|
+
* explicit `repeats * 250` estimate; remediation proposals stay plain text (task 1178 R3).
|
|
336
|
+
*/
|
|
337
|
+
function composeHistoryHealth(reading: HistoryHealthReading): HistoryHealthSummary {
|
|
338
|
+
const loops: HistoryLoopFinding[] = reading.loops.map((row) => ({
|
|
339
|
+
toolName: row.toolName,
|
|
340
|
+
argsDigest: row.argsDigest || 'repeated execution',
|
|
341
|
+
repeats: row.repeats,
|
|
342
|
+
sessionId: row.sessionId,
|
|
343
|
+
fromSeq: row.firstSeq,
|
|
344
|
+
toSeq: row.lastSeq,
|
|
345
|
+
wastedTokens: row.repeats * 250,
|
|
346
|
+
}));
|
|
347
|
+
|
|
348
|
+
const remediationProposals: Array<{ key: string; title: string; command: string }> = [];
|
|
349
|
+
for (const lp of loops.slice(0, 5)) {
|
|
350
|
+
const cleanTool = lp.toolName.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
351
|
+
const key = `repetition:${cleanTool}:${lp.argsDigest.slice(0, 16)}`;
|
|
352
|
+
const title = `Break execution loop in ${lp.toolName} (${lp.repeats} repeats)`;
|
|
353
|
+
const command = `spur task create "<title>" --feature <id> && spur task update <wbs> --section Plan --from-file <path>`;
|
|
354
|
+
remediationProposals.push({ key, title, command });
|
|
355
|
+
}
|
|
356
|
+
if (reading.toolCalls > 0 && reading.errorRatePct > 10) {
|
|
357
|
+
remediationProposals.push({
|
|
358
|
+
key: 'reliability:tooling:high-error-rate',
|
|
359
|
+
title: `Investigate high tool error rate (${reading.errorRatePct.toFixed(1)}%)`,
|
|
360
|
+
command: `spur task create "<title>" --feature <id> && spur task update <wbs> --section Plan --from-file <path>`,
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
toolCalls: reading.toolCalls,
|
|
366
|
+
toolErrors: reading.toolErrors,
|
|
367
|
+
errorRatePct: reading.errorRatePct,
|
|
368
|
+
loops,
|
|
369
|
+
redundantCalls: loops.reduce((acc, l) => acc + Math.max(0, l.repeats - 1), 0),
|
|
370
|
+
wastedTokens: loops.reduce((acc, l) => acc + l.wastedTokens, 0),
|
|
371
|
+
remediationProposals,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Read the day's history health through the app owner (task 1178 R3): this adapter opens no
|
|
377
|
+
* SQLite and carries no SQL. A missing database file or required table returns `null`
|
|
378
|
+
* (unavailable); an existing empty table returns a measured-zero summary, never `n/a`.
|
|
379
|
+
*/
|
|
290
380
|
export async function getSpurHistoryHealth(
|
|
291
381
|
date: string,
|
|
292
382
|
dbPath = '.spur/spur.db',
|
|
293
383
|
): Promise<HistoryHealthSummary | null> {
|
|
294
384
|
try {
|
|
295
385
|
const resolvedPath = resolve(process.cwd(), dbPath);
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
const db = new Database(resolvedPath, { readonly: true });
|
|
302
|
-
|
|
303
|
-
try {
|
|
304
|
-
// 1. Query execution loop findings
|
|
305
|
-
const loopTable = db
|
|
306
|
-
.query<{ name: string }, [string]>("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?")
|
|
307
|
-
.get('history_board_loop_findings');
|
|
308
|
-
|
|
309
|
-
let loops: HistoryLoopFinding[] = [];
|
|
310
|
-
if (loopTable) {
|
|
311
|
-
const rows = db
|
|
312
|
-
.query<
|
|
313
|
-
{
|
|
314
|
-
tool_name: string;
|
|
315
|
-
args_digest: string;
|
|
316
|
-
repeats: number;
|
|
317
|
-
session_id: string;
|
|
318
|
-
first_seq: number;
|
|
319
|
-
last_seq: number;
|
|
320
|
-
started_at: string | null;
|
|
321
|
-
},
|
|
322
|
-
[string]
|
|
323
|
-
>(
|
|
324
|
-
`SELECT tool_name, args_digest, repeats, session_id, first_seq, last_seq, started_at
|
|
325
|
-
FROM history_board_loop_findings
|
|
326
|
-
WHERE started_at IS NULL OR started_at LIKE ?
|
|
327
|
-
ORDER BY repeats DESC
|
|
328
|
-
LIMIT 20`,
|
|
329
|
-
)
|
|
330
|
-
.all(`${date}%`);
|
|
331
|
-
|
|
332
|
-
loops = rows.map((r) => ({
|
|
333
|
-
toolName: r.tool_name,
|
|
334
|
-
argsDigest: r.args_digest || 'repeated execution',
|
|
335
|
-
repeats: r.repeats,
|
|
336
|
-
sessionId: r.session_id,
|
|
337
|
-
fromSeq: r.first_seq,
|
|
338
|
-
toSeq: r.last_seq,
|
|
339
|
-
wastedTokens: r.repeats * 250,
|
|
340
|
-
}));
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// 2. Query tool calls and errors
|
|
344
|
-
let toolCalls = 0;
|
|
345
|
-
let toolErrors = 0;
|
|
346
|
-
const toolTable = db
|
|
347
|
-
.query<{ name: string }, [string]>("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?")
|
|
348
|
-
.get('history_board_tool_5m');
|
|
349
|
-
|
|
350
|
-
if (toolTable) {
|
|
351
|
-
const stats = db
|
|
352
|
-
.query<{ calls: number | null; errors: number | null }, [string]>(
|
|
353
|
-
`SELECT SUM(calls) AS calls, SUM(errors) AS errors
|
|
354
|
-
FROM history_board_tool_5m
|
|
355
|
-
WHERE bucket_start LIKE ?`,
|
|
356
|
-
)
|
|
357
|
-
.get(`${date}%`);
|
|
358
|
-
|
|
359
|
-
toolCalls = stats?.calls ?? 0;
|
|
360
|
-
toolErrors = stats?.errors ?? 0;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
const redundantCalls = loops.reduce((acc, l) => acc + Math.max(0, l.repeats - 1), 0);
|
|
364
|
-
const wastedTokens = loops.reduce((acc, l) => acc + l.wastedTokens, 0);
|
|
365
|
-
const errorRatePct = toolCalls > 0 ? (toolErrors / toolCalls) * 100 : 0;
|
|
366
|
-
|
|
367
|
-
// 3. Generate auto-healing remediation proposals
|
|
368
|
-
const remediationProposals: Array<{ key: string; title: string; command: string }> = [];
|
|
369
|
-
|
|
370
|
-
for (const lp of loops.slice(0, 5)) {
|
|
371
|
-
const cleanTool = lp.toolName.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
372
|
-
const key = `repetition:${cleanTool}:${lp.argsDigest.slice(0, 16)}`;
|
|
373
|
-
const title = `Break execution loop in ${lp.toolName} (${lp.repeats} repeats)`;
|
|
374
|
-
const command = `spur task create "<title>" --feature <id> && spur task update <wbs> --section Plan --from-file <path>`;
|
|
375
|
-
remediationProposals.push({ key, title, command });
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
if (toolCalls > 0 && errorRatePct > 10) {
|
|
379
|
-
const key = 'reliability:tooling:high-error-rate';
|
|
380
|
-
const title = `Investigate high tool error rate (${errorRatePct.toFixed(1)}%)`;
|
|
381
|
-
const command = `spur task create "<title>" --feature <id> && spur task update <wbs> --section Plan --from-file <path>`;
|
|
382
|
-
remediationProposals.push({ key, title, command });
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
return {
|
|
386
|
-
toolCalls,
|
|
387
|
-
toolErrors,
|
|
388
|
-
errorRatePct,
|
|
389
|
-
loops,
|
|
390
|
-
redundantCalls,
|
|
391
|
-
wastedTokens,
|
|
392
|
-
remediationProposals,
|
|
393
|
-
};
|
|
394
|
-
} finally {
|
|
395
|
-
db.close();
|
|
396
|
-
}
|
|
386
|
+
const { app } = await loadInlineApp<InlineHistoryApp>(getEnvVar('SPUR_BIN') ?? '', resolveAppEntry, [
|
|
387
|
+
'readHistoryHealth',
|
|
388
|
+
]);
|
|
389
|
+
const reading = await app.readHistoryHealth({ date, dbPath: resolvedPath });
|
|
390
|
+
return reading === null ? null : composeHistoryHealth(reading);
|
|
397
391
|
} catch {
|
|
398
392
|
return null;
|
|
399
393
|
}
|
|
@@ -795,9 +789,11 @@ export async function buildDailySummary(options: CliOptions): Promise<DailySumma
|
|
|
795
789
|
result.gitActivity = gitActivity;
|
|
796
790
|
}
|
|
797
791
|
|
|
798
|
-
// Query Spur history health (loops, tool errors, and auto-healing proposals)
|
|
792
|
+
// Query Spur history health (loops, tool errors, and auto-healing proposals). A measured
|
|
793
|
+
// empty day is a real zero and keeps its "Clean" section; an unavailable database (missing
|
|
794
|
+
// file/table) stays absent — absence and zero never collapse (task 1178 R3).
|
|
799
795
|
const historyHealth = await getSpurHistoryHealth(options.date);
|
|
800
|
-
if (historyHealth
|
|
796
|
+
if (historyHealth) {
|
|
801
797
|
result.historyHealth = historyHealth;
|
|
802
798
|
platforms.push('Spur History');
|
|
803
799
|
}
|
|
@@ -806,6 +802,17 @@ export async function buildDailySummary(options: CliOptions): Promise<DailySumma
|
|
|
806
802
|
}
|
|
807
803
|
|
|
808
804
|
export async function main(): Promise<void> {
|
|
805
|
+
// The generated app entry read by getSpurHistoryHealth needs Bun (SQLite); the installed
|
|
806
|
+
// `.mjs` twin may be launched with plain node, so re-enter Bun before any app import.
|
|
807
|
+
// Without Bun on PATH, keep running under node: the history reader degrades to unavailable
|
|
808
|
+
// (n/a) instead of failing the whole report (task 1178 review P4).
|
|
809
|
+
if (!process.versions.bun) {
|
|
810
|
+
const child = spawnSync('bun', [fileURLToPath(import.meta.url), ...process.argv.slice(2)], {
|
|
811
|
+
stdio: 'inherit',
|
|
812
|
+
});
|
|
813
|
+
if (!child.error) process.exit(child.status ?? 1);
|
|
814
|
+
console.error(`daily-summary: Bun not on PATH (${child.error.message}); history health unavailable`);
|
|
815
|
+
}
|
|
809
816
|
const options = parseArgs();
|
|
810
817
|
|
|
811
818
|
logger.info(`Generating daily summary for ${options.date}...`);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
4
4
|
|
|
5
|
-
// plugins/sp/
|
|
5
|
+
// plugins/sp/lib/dogfood-pipeline.ts
|
|
6
6
|
var PIPELINE_TOKENS = [
|
|
7
7
|
"--next",
|
|
8
8
|
"dev-runall",
|
|
@@ -125,6 +125,7 @@ function evaluateDogfoodGate(testee, options = {}) {
|
|
|
125
125
|
exitCode: 0
|
|
126
126
|
};
|
|
127
127
|
}
|
|
128
|
+
// plugins/sp/scripts/dogfood-testing/detect-pipeline-driving.ts
|
|
128
129
|
function parseCliArgs(argv) {
|
|
129
130
|
let testee = null;
|
|
130
131
|
let maxRetryPresent = false;
|