@gobing-ai/spur 0.3.95 → 0.3.97
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/plugin-scripts.json +0 -54
- package/config/rules/boundary/sp-script-placement.yaml +19 -0
- package/config/rules/strict/runtime-boundaries.yaml +6 -0
- package/config/rules/structure/test-location.yaml +2 -0
- package/config/rules/typescript/no-syscall-emulation-in-boundary-mock.yaml +1 -1
- package/config/rules/typescript/output-boundaries.yaml +15 -2
- package/config/script-placement-baseline.json +51 -0
- package/config/templates/AGENTS.md +3 -3
- package/config/workflows/feature-verification.yaml +1 -1
- package/config/workflows/history-anatomy.yaml +23 -11
- package/config/workflows/idea-pipeline.yaml +80 -85
- package/config/workflows/pr-review.yaml +18 -9
- package/config/workflows/task-pipeline.yaml +42 -95
- package/config/workflows/wrapup-pipeline.yaml +91 -38
- package/package.json +2 -2
- package/plugins/sp/README.md +14 -10
- package/plugins/sp/agents/super-reviewer.md +43 -18
- package/plugins/sp/commands/dev-fixgha.md +83 -0
- package/plugins/sp/commands/dev-gitmsg.md +8 -6
- package/plugins/sp/commands/dev-gtd.md +2 -2
- package/plugins/sp/commands/dev-idea.md +22 -12
- package/plugins/sp/commands/dev-job-dump.md +29 -0
- package/plugins/sp/commands/dev-job-resume.md +29 -0
- package/plugins/sp/commands/dev-plan.md +8 -9
- package/plugins/sp/commands/dev-review.md +22 -13
- package/plugins/sp/commands/dev-verifyall.md +1 -1
- package/plugins/sp/commands/spur-init.md +2 -2
- package/plugins/sp/lib/history-anatomy.generated.d.mts +112 -0
- package/plugins/sp/lib/history-anatomy.generated.mjs +686 -0
- package/plugins/sp/lib/idea-handoff.generated.mjs +5 -4
- package/plugins/sp/lib/inline-run.generated.d.mts +11 -0
- package/plugins/sp/lib/inline-run.generated.mjs +39 -13
- package/plugins/sp/lib/quality-gate.generated.d.mts +104 -0
- package/plugins/sp/lib/quality-gate.generated.mjs +445 -0
- package/plugins/sp/lib/residual-scan.generated.d.mts +63 -0
- package/plugins/sp/lib/residual-scan.generated.mjs +226 -0
- package/plugins/sp/lib/spur-bin.ts +36 -0
- package/plugins/sp/lib/step-profile.generated.d.mts +71 -0
- package/plugins/sp/lib/step-profile.generated.mjs +174 -0
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/references/roles.md +3 -3
- package/plugins/sp/scripts/feature-verification-steps.mjs +14 -1
- package/plugins/sp/scripts/feature-verification-steps.ts +14 -1
- package/plugins/sp/scripts/history-anatomy-cache.mjs +20 -19
- package/plugins/sp/scripts/history-anatomy-cache.ts +23 -928
- package/plugins/sp/scripts/inline-run-setup.mjs +85 -320
- package/plugins/sp/scripts/inline-run-setup.ts +104 -667
- package/plugins/sp/scripts/quality-gate.mjs +46 -24
- package/plugins/sp/scripts/quality-gate.ts +13 -659
- package/plugins/sp/scripts/residual-scan.mjs +179 -174
- package/plugins/sp/scripts/residual-scan.ts +125 -517
- package/plugins/sp/scripts/script-root.mjs +5 -1
- package/plugins/sp/scripts/script-root.ts +5 -1
- package/plugins/sp/scripts/workflow-step-profile.mjs +25 -17
- package/plugins/sp/scripts/workflow-step-profile.ts +21 -315
- package/plugins/sp/scripts/wrapup-drift-probe.mjs +8 -2
- package/plugins/sp/scripts/wrapup-drift-probe.ts +3 -2
- package/plugins/sp/scripts/wrapup-steps.mjs +72 -33
- package/plugins/sp/scripts/wrapup-steps.ts +128 -37
- package/plugins/sp/skills/code-improvement/SKILL.md +5 -4
- package/plugins/sp/skills/code-verification/SKILL.md +34 -9
- package/plugins/sp/skills/code-verification/references/verdict-schema.md +3 -3
- package/plugins/sp/skills/functional-review/SKILL.md +7 -4
- package/plugins/sp/skills/functional-review/references/verdict-schema.md +1 -1
- package/plugins/sp/skills/history-anatomy/references/modes.md +2 -1
- package/plugins/sp/skills/next-feature/references/handoff-routing.md +1 -1
- package/plugins/sp/skills/next-router/references/routing-table.md +1 -1
- package/plugins/sp/skills/source-driven-development/SKILL.md +11 -0
- package/plugins/sp/skills/spur-cli/SKILL.md +3 -3
- package/plugins/sp/skills/spur-cli/references/agent.md +10 -10
- package/plugins/sp/skills/spur-cli/references/features.md +17 -6
- package/plugins/sp/skills/spur-cli/references/init.md +17 -16
- package/plugins/sp/skills/spur-cli/references/self.md +3 -2
- package/plugins/sp/skills/spur-cli/references/serve.md +10 -10
- package/plugins/sp/skills/spur-cli/references/tasks/section-editing.md +9 -5
- package/plugins/sp/skills/spur-cli/references/tasks/verbs.md +21 -6
- package/plugins/sp/skills/spur-cli/references/tasks.md +14 -8
- package/plugins/sp/skills/spur-cli/references/workflows.md +17 -14
- package/plugins/sp/skills/spur-dev/SKILL.md +2 -0
- package/plugins/sp/skills/spur-dev/references/ac-style-guide.md +4 -3
- package/plugins/sp/skills/spur-dev/references/cross-cutting.md +14 -10
- package/plugins/sp/skills/spur-dev/references/decision-brief.md +1 -1
- package/plugins/sp/skills/spur-dev/references/dev-operations.md +165 -75
- package/plugins/sp/skills/spur-dev/references/done-housekeeping.md +7 -5
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +84 -39
- package/plugins/sp/skills/spur-dev/references/execution-workflow.md +8 -12
- package/plugins/sp/skills/spur-dev/references/feature-link-helper.md +3 -3
- package/plugins/sp/skills/spur-dev/references/flag-glossary.md +40 -21
- package/plugins/sp/skills/spur-dev/references/gate-checklists.md +19 -19
- package/plugins/sp/skills/spur-dev/references/idea-evaluation.md +4 -3
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +72 -15
- package/plugins/sp/skills/spur-doctor/SKILL.md +1 -1
- package/plugins/sp/skills/sys-architecture/SKILL.md +3 -2
- package/spur.js +4461 -2085
- package/web/_astro/{BoardApp.Cpxntzad.js → BoardApp.Bmv5WkJ9.js} +1 -1
- package/web/_astro/BoardApp.vveLOdDq.js +322 -0
- package/web/_astro/{TaskDetail.C5bW4WGV.js → TaskDetail.IKc3uDYV.js} +1 -1
- package/web/_astro/{arc.CyjRvNMY.js → arc.C63Ufg35.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.B4lWRzJA.js → architectureDiagram-3BPJPVTR.CP8Hyldk.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.Be38USQ2.js → blockDiagram-GPEHLZMM.Bo_48MRA.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.DT9Fj5Qx.js → c4Diagram-AAUBKEIU.Ch61sw-O.js} +1 -1
- package/web/_astro/channel.BKCmJpWM.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.SeSBWLg5.js → chunk-2J33WTMH.CHpzS7xf.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.DoO4VE14.js → chunk-4BX2VUAB.DzMNNWIw.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.DrmTFA53.js → chunk-55IACEB6.BqPrDw_Q.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.Bi-TUb_V.js → chunk-727SXJPM.ZzBHhNw-.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.B-xp8Uhi.js → chunk-AQP2D5EJ.1_O_LLkx.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.DGWrDnUU.js → chunk-FMBD7UC4.GS4UHg7_.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM._BagvPDy.js → chunk-ND2GUHAM.BnqotN4g.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.VOboYWQ0.js → chunk-QZHKN3VN.Ghuh8zoJ.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.D_apfHS7.js → classDiagram-4FO5ZUOK.BW9vC3zg.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.D_apfHS7.js → classDiagram-v2-Q7XG4LA2.BW9vC3zg.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.DNLU_L-x.js → cose-bilkent-S5V4N54A.D6jNC0ti.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.D8borhV-.js → cynefin-OW5HDTMX.BOGoCs4L.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.Uc0lvjcV.js → dagre-BM42HDAG.8iU5VcWi.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.DDIxtDBo.js → diagram-2AECGRRQ.CVt88HwH.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.DVoDnD3H.js → diagram-5GNKFQAL.C9HxUUMl.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.CRubTJ-0.js → diagram-KO2AKTUF.4ywkQN3t.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.m9SeYcXQ.js → diagram-LMA3HP47.C48A6gpz.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.C6s2ZjV5.js → diagram-OG6HWLK6.DxFieQW4.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.nSzr5KTj.js → erDiagram-TEJ5UH35.CtjtQqYs.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.DrZcys16.js → flowDiagram-I6XJVG4X.C5zAiahU.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.Do4M1b-K.js → ganttDiagram-6RSMTGT7.D_L6zsny.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.BWc9oJ4l.js → gitGraphDiagram-PVQCEYII.Ignnrk3V.js} +1 -1
- package/web/_astro/index.CbNz17Sx.css +1 -0
- package/web/_astro/{infoDiagram-5YYISTIA.BIdOctFk.js → infoDiagram-5YYISTIA.DLv8-P5u.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.CV5SuG2t.js → ishikawaDiagram-YF4QCWOH.p0OTpwFO.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.CxE-rEJ-.js → journeyDiagram-JHISSGLW.CIaBt-DD.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.BPRdKWs_.js → kanban-definition-UN3LZRKU.DGySIntq.js} +1 -1
- package/web/_astro/{linear.zRsuuDTE.js → linear.BATV4RXu.js} +1 -1
- package/web/_astro/{mermaid.core.jAJTcMKc.js → mermaid.core.DzkwM3VX.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.BffJxfCr.js → mindmap-definition-RKZ34NQL.3piBRRzA.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.CacwWaE3.js → pieDiagram-4H26LBE5.DUNueLub.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.hXELD06-.js → quadrantDiagram-W4KKPZXB.9bihVbrt.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.DdEbPcqT.js → requirementDiagram-4Y6WPE33.CkjsC-YT.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.DgaDocKv.js → sankeyDiagram-5OEKKPKP.Dbq4vaoQ.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.B0KScnAu.js → sequenceDiagram-3UESZ5HK.BivrH99P.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.CV9M_WNc.js → stateDiagram-AJRCARHV.CqLQ1Vzz.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.BOAo74Et.js → stateDiagram-v2-BHNVJYJU.DvvoLD3b.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.CqqepUMe.js → timeline-definition-PNZ67QCA.B-KR78gv.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.DDqe4dOG.js → vennDiagram-CIIHVFJN.Ck0Eieb5.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.B3R9OPdO.js → wardleyDiagram-YWT4CUSO.CRfdd_do.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.B3SvgXpQ.js → xychartDiagram-2RQKCTM6.Ck_Jxk9C.js} +1 -1
- package/web/index.html +2 -2
- package/plugins/sp/lib/artifact-digest.generated.d.mts +0 -7
- package/plugins/sp/lib/artifact-digest.generated.mjs +0 -48
- package/plugins/sp/scripts/feature-sync-bounded.mjs +0 -301
- package/plugins/sp/scripts/feature-sync-bounded.ts +0 -481
- package/plugins/sp/scripts/idea-coverage-check.ts +0 -168
- package/plugins/sp/scripts/inline-pipeline-parity-check.ts +0 -298
- package/plugins/sp/scripts/record-feature-sync.mjs +0 -63
- package/plugins/sp/scripts/record-feature-sync.ts +0 -84
- package/plugins/sp/scripts/script-contract-check.ts +0 -506
- package/plugins/sp/scripts/stage-registry-adapter.ts +0 -1533
- package/plugins/sp/scripts/surface-drift-inventory.ts +0 -989
- package/plugins/sp/scripts/task-evidence-precheck.ts +0 -189
- package/plugins/sp/scripts/task-size-precheck.ts +0 -212
- package/plugins/sp/scripts/transition-shim-check.ts +0 -238
- package/plugins/sp/scripts/validate-commands.ts +0 -689
- package/plugins/sp/scripts/validate-flag-contracts.ts +0 -890
- package/plugins/sp/scripts/verify-answer-lint.ts +0 -549
- package/web/_astro/BoardApp.BxJuwD7I.js +0 -191
- package/web/_astro/channel.CI6N_tCg.js +0 -1
- package/web/_astro/index.CENnIEqT.css +0 -1
|
@@ -3,21 +3,21 @@
|
|
|
3
3
|
|
|
4
4
|
// plugins/sp/scripts/residual-scan.ts
|
|
5
5
|
import { spawnSync } from "child_process";
|
|
6
|
-
import
|
|
7
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "fs";
|
|
6
|
+
import * as fs from "fs";
|
|
8
7
|
import { tmpdir } from "os";
|
|
9
|
-
import { join } from "path";
|
|
8
|
+
import { join as join2 } from "path";
|
|
10
9
|
|
|
11
10
|
// plugins/sp/lib/env.ts
|
|
12
11
|
function getEnvVars() {
|
|
13
12
|
return process.env;
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
// plugins/sp/
|
|
17
|
-
|
|
15
|
+
// plugins/sp/lib/residual-scan.generated.mjs
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import { createHash } from "node:crypto";
|
|
18
18
|
var MARKER_PATTERN = /TODO|FIXME|XXX|HACK/;
|
|
19
19
|
var PRIORITY_PATTERN = /^P[1-4]/;
|
|
20
|
-
var NONE_FINDING = /^(none( found)?|no (findings?|issues?)( found)
|
|
20
|
+
var NONE_FINDING = /^(none( found)?|no (findings?|issues?)( found)?|—)\s*(\(.*\))?\.?$/i;
|
|
21
21
|
var DISPOSITION_HEADER = /^(Disposition|Action|Status|Resolution|Fixed)$/i;
|
|
22
22
|
var RESOLVED_DISPOSITION = /^(FIXED|RESOLVED|DONE)\b/i;
|
|
23
23
|
var DEFERRED_DISPOSITION = /^DEFER(RED)?\b/i;
|
|
@@ -25,16 +25,6 @@ var ANCHOR_PATTERN = /[A-Za-z0-9_./-]+\.[A-Za-z]+:[0-9]+/g;
|
|
|
25
25
|
var RANGE_ANCHOR = /([A-Za-z0-9_./-]+\.[A-Za-z]+):([0-9]+)-[0-9]+/g;
|
|
26
26
|
var EXCLUDED_PATHS = ["docs/tasks", "docs/features/", ".spur/"];
|
|
27
27
|
var ALLOW_PRAGMA = "residual-scan:allow";
|
|
28
|
-
function spurCommand(spurBin) {
|
|
29
|
-
const parts = (spurBin ?? "spur").trim().split(/\s+/).filter((p) => p.length > 0);
|
|
30
|
-
return { cmd: parts[0] ?? "spur", prefix: parts.slice(1) };
|
|
31
|
-
}
|
|
32
|
-
function run(cmd, args, cwd) {
|
|
33
|
-
const result = spawnSync(cmd, args, { cwd, encoding: "utf8" });
|
|
34
|
-
if (result.error !== undefined)
|
|
35
|
-
return { status: result.status ?? 1, stdout: "" };
|
|
36
|
-
return { status: result.status ?? 1, stdout: result.stdout ?? "" };
|
|
37
|
-
}
|
|
38
28
|
function makeItemId(category, location, text) {
|
|
39
29
|
const normalized = text.trim().replace(/\s+/g, " ");
|
|
40
30
|
const hex = createHash("sha256").update(`${location}${normalized}`).digest("hex");
|
|
@@ -45,7 +35,7 @@ function normalizeAnchor(location) {
|
|
|
45
35
|
}
|
|
46
36
|
function locationOf(locationCell, finding) {
|
|
47
37
|
const cell = locationCell.trim().replace(/`/g, "");
|
|
48
|
-
if (cell.length > 0 && cell !== "
|
|
38
|
+
if (cell.length > 0 && cell !== "—")
|
|
49
39
|
return normalizeAnchor(cell);
|
|
50
40
|
const backtick = finding.match(/`([^`]+)`/)?.[1];
|
|
51
41
|
return backtick === undefined ? "" : normalizeAnchor(backtick);
|
|
@@ -96,42 +86,6 @@ function parseReviewFindings(taskContent) {
|
|
|
96
86
|
function splitRow(line) {
|
|
97
87
|
return line.trim().replace(/^\|/, "").replace(/\|$/, "").split("|").map((c) => c.trim());
|
|
98
88
|
}
|
|
99
|
-
function collectAddedLines(root, base) {
|
|
100
|
-
const out = [];
|
|
101
|
-
const diff = run("git", ["diff", "--unified=0", base], root);
|
|
102
|
-
let file = "";
|
|
103
|
-
let newLine = 0;
|
|
104
|
-
for (const line of diff.stdout.split(`
|
|
105
|
-
`)) {
|
|
106
|
-
if (line.startsWith("+++ b/"))
|
|
107
|
-
file = line.slice(6);
|
|
108
|
-
else if (line.startsWith("@@")) {
|
|
109
|
-
const m = line.match(/\+[0-9]+/);
|
|
110
|
-
newLine = m === null ? newLine : Number.parseInt(m[0].slice(1), 10);
|
|
111
|
-
} else if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
112
|
-
out.push({ file, line: newLine, text: line.slice(1) });
|
|
113
|
-
newLine++;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
const untracked = run("git", ["ls-files", "--others", "--exclude-standard"], root);
|
|
117
|
-
for (const f of untracked.stdout.split(`
|
|
118
|
-
`)) {
|
|
119
|
-
if (f.length === 0)
|
|
120
|
-
continue;
|
|
121
|
-
try {
|
|
122
|
-
if (!statSync(join(root, f)).isFile())
|
|
123
|
-
continue;
|
|
124
|
-
} catch {
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
const content = readFileSync(join(root, f), "utf8").split(`
|
|
128
|
-
`);
|
|
129
|
-
content.forEach((text, idx) => {
|
|
130
|
-
out.push({ file: f, line: idx + 1, text });
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
return out;
|
|
134
|
-
}
|
|
135
89
|
function parseDiffMarkers(addedLines) {
|
|
136
90
|
return addedLines.filter((l) => !EXCLUDED_PATHS.some((p) => l.file.startsWith(p))).filter((l) => !l.text.includes(ALLOW_PRAGMA)).filter((l) => MARKER_PATTERN.test(l.text)).map((l) => ({ location: `${l.file}:${l.line}`, text: l.text.trim() }));
|
|
137
91
|
}
|
|
@@ -140,34 +94,6 @@ function findUncheckedBoxes(taskContent) {
|
|
|
140
94
|
return taskContent.split(`
|
|
141
95
|
`).map((text, idx) => ({ text: text.trim(), line: idx + 1 })).filter((l) => l.text.startsWith("- [ ]")).map((l) => ({ location: `${path}:${l.line}`, text: l.text }));
|
|
142
96
|
}
|
|
143
|
-
function listStagingResidue(tmpDir, wbs) {
|
|
144
|
-
let names;
|
|
145
|
-
try {
|
|
146
|
-
names = readdirSync(tmpDir);
|
|
147
|
-
} catch {
|
|
148
|
-
return [];
|
|
149
|
-
}
|
|
150
|
-
return names.filter((n) => n.startsWith(`${wbs}-`)).filter((n) => {
|
|
151
|
-
try {
|
|
152
|
-
return statSync(join(tmpDir, n)).isFile();
|
|
153
|
-
} catch {
|
|
154
|
-
return false;
|
|
155
|
-
}
|
|
156
|
-
}).map((n) => join(tmpDir, n));
|
|
157
|
-
}
|
|
158
|
-
function readDeferrals(runDir, wbs) {
|
|
159
|
-
const path = join(runDir, `${wbs}-residual-deferrals.json`);
|
|
160
|
-
if (!existsSync(path))
|
|
161
|
-
return [];
|
|
162
|
-
try {
|
|
163
|
-
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
164
|
-
if (!Array.isArray(parsed))
|
|
165
|
-
return [];
|
|
166
|
-
return parsed.filter((e) => typeof e === "object" && e !== null && typeof e.id === "string" && typeof e.reason === "string" && e.reason.trim().length > 0);
|
|
167
|
-
} catch {
|
|
168
|
-
return [];
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
97
|
function classify(items, deferrals) {
|
|
172
98
|
const deferred = new Map(deferrals.map((d) => [d.id, d.reason]));
|
|
173
99
|
return items.map((item) => {
|
|
@@ -195,10 +121,8 @@ function classify(items, deferrals) {
|
|
|
195
121
|
};
|
|
196
122
|
});
|
|
197
123
|
}
|
|
198
|
-
function scanResiduals(
|
|
199
|
-
const
|
|
200
|
-
const basePath = join(runDir, `${wbs}-base.sha`);
|
|
201
|
-
const base = existsSync(basePath) ? readFileSync(basePath, "utf8").trim() : null;
|
|
124
|
+
function scanResiduals(inputs) {
|
|
125
|
+
const { wbs, base, taskContent, addedLines, stagingResidue, deferrals } = inputs;
|
|
202
126
|
const reviewRows = parseReviewFindings(taskContent);
|
|
203
127
|
const tableDeferrals = reviewRows.flatMap((r) => r.deferral === undefined ? [] : [{ id: makeItemId("review-finding", r.location, r.text), reason: r.deferral }]);
|
|
204
128
|
const review = reviewRows.map((r) => ({
|
|
@@ -207,7 +131,7 @@ function scanResiduals(root, wbs, tmpDir, taskContent, _env) {
|
|
|
207
131
|
location: r.location,
|
|
208
132
|
text: r.text
|
|
209
133
|
}));
|
|
210
|
-
const markers = base === null ? [] : parseDiffMarkers(
|
|
134
|
+
const markers = base === null ? [] : parseDiffMarkers(addedLines).map((m) => ({
|
|
211
135
|
category: "diff-marker",
|
|
212
136
|
location: m.location,
|
|
213
137
|
text: m.text
|
|
@@ -217,12 +141,12 @@ function scanResiduals(root, wbs, tmpDir, taskContent, _env) {
|
|
|
217
141
|
location: b.location,
|
|
218
142
|
text: b.text
|
|
219
143
|
}));
|
|
220
|
-
const residue =
|
|
144
|
+
const residue = stagingResidue.map((p) => ({
|
|
221
145
|
category: "staging-residue",
|
|
222
146
|
location: p,
|
|
223
147
|
text: p
|
|
224
148
|
}));
|
|
225
|
-
const items = classify([...review, ...markers, ...boxes, ...residue], [...tableDeferrals, ...
|
|
149
|
+
const items = classify([...review, ...markers, ...boxes, ...residue], [...tableDeferrals, ...deferrals]);
|
|
226
150
|
const counts = { blocking: 0, deferrable: 0, advisory: 0, housekeeping: 0 };
|
|
227
151
|
for (const item of items)
|
|
228
152
|
counts[item.class]++;
|
|
@@ -271,7 +195,7 @@ function foldVerdict(verdict, scan, existingFindings, maxFindings = 20) {
|
|
|
271
195
|
}
|
|
272
196
|
function renderReport(wbs, items, attemptCount) {
|
|
273
197
|
const lines = [
|
|
274
|
-
`# Residual report
|
|
198
|
+
`# Residual report — ${wbs}`,
|
|
275
199
|
"",
|
|
276
200
|
`Attempt: ${attemptCount}`,
|
|
277
201
|
"",
|
|
@@ -285,6 +209,101 @@ function renderReport(wbs, items, attemptCount) {
|
|
|
285
209
|
`)}
|
|
286
210
|
`;
|
|
287
211
|
}
|
|
212
|
+
function recordedVerdictPath(runDir, wbs, fs) {
|
|
213
|
+
const evidence = join(runDir, "..", "memory", "evidence");
|
|
214
|
+
const durable = join(evidence, `${wbs}-verdict.json`);
|
|
215
|
+
for (const path of [join(runDir, ".."), join(evidence, ".."), evidence, durable]) {
|
|
216
|
+
try {
|
|
217
|
+
if (fs.lstatSync(path).isSymbolicLink())
|
|
218
|
+
throw new Error(`residual-scan: symlink evidence path: ${path}`);
|
|
219
|
+
} catch (error) {
|
|
220
|
+
if (error.code !== "ENOENT")
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return fs.existsSync(durable) ? durable : join(runDir, `${wbs}-verdict.json`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// plugins/sp/lib/spur-bin.ts
|
|
228
|
+
function spurCommand(spurBin) {
|
|
229
|
+
const parts = (spurBin ?? "spur").trim().split(/\s+/).filter((p) => p.length > 0);
|
|
230
|
+
return { cmd: parts[0] ?? "spur", prefix: parts.slice(1) };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// plugins/sp/scripts/residual-scan.ts
|
|
234
|
+
var RESIDUAL_SCAN_USAGE = "usage: residual-scan.ts <scan|fold|settle|report> <wbs> [--spur-bin <bin>] [--root <dir>] [--tmp-dir <dir>]";
|
|
235
|
+
function run(cmd, args, cwd) {
|
|
236
|
+
const result = spawnSync(cmd, args, { cwd, encoding: "utf8" });
|
|
237
|
+
if (result.error !== undefined)
|
|
238
|
+
return { status: result.status ?? 1, stdout: "" };
|
|
239
|
+
return { status: result.status ?? 1, stdout: result.stdout ?? "" };
|
|
240
|
+
}
|
|
241
|
+
function spur(env, spurBinFlag, args, cwd) {
|
|
242
|
+
const { cmd, prefix } = spurCommand(spurBinFlag ?? env.spurBin);
|
|
243
|
+
return run(cmd, [...prefix, ...args], cwd);
|
|
244
|
+
}
|
|
245
|
+
function isRegularFile(path) {
|
|
246
|
+
try {
|
|
247
|
+
return fs.statSync(path).isFile();
|
|
248
|
+
} catch {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function collectAddedLines(root, base) {
|
|
253
|
+
const out = [];
|
|
254
|
+
const diff = run("git", ["diff", "--unified=0", base], root);
|
|
255
|
+
let file = "";
|
|
256
|
+
let newLine = 0;
|
|
257
|
+
for (const line of diff.stdout.split(`
|
|
258
|
+
`)) {
|
|
259
|
+
if (line.startsWith("+++ b/"))
|
|
260
|
+
file = line.slice(6);
|
|
261
|
+
else if (line.startsWith("@@"))
|
|
262
|
+
newLine = Number.parseInt((line.match(/\+[0-9]+/) ?? ["+0"])[0].slice(1), 10);
|
|
263
|
+
else if (line.startsWith("+") && !line.startsWith("+++"))
|
|
264
|
+
out.push({ file, line: newLine++, text: line.slice(1) });
|
|
265
|
+
}
|
|
266
|
+
const untracked = run("git", ["ls-files", "--others", "--exclude-standard"], root);
|
|
267
|
+
for (const f of untracked.stdout.split(`
|
|
268
|
+
`)) {
|
|
269
|
+
if (f.length === 0 || !isRegularFile(join2(root, f)))
|
|
270
|
+
continue;
|
|
271
|
+
for (const [idx, text] of fs.readFileSync(join2(root, f), "utf8").split(`
|
|
272
|
+
`).entries())
|
|
273
|
+
out.push({ file: f, line: idx + 1, text });
|
|
274
|
+
}
|
|
275
|
+
return out;
|
|
276
|
+
}
|
|
277
|
+
function listStagingResidue(tmpDir, wbs) {
|
|
278
|
+
let names;
|
|
279
|
+
try {
|
|
280
|
+
names = fs.readdirSync(tmpDir);
|
|
281
|
+
} catch {
|
|
282
|
+
return [];
|
|
283
|
+
}
|
|
284
|
+
return names.filter((n) => n.startsWith(`${wbs}-`) && isRegularFile(join2(tmpDir, n))).map((n) => join2(tmpDir, n));
|
|
285
|
+
}
|
|
286
|
+
function readDeferrals(runDir, wbs) {
|
|
287
|
+
const path = join2(runDir, `${wbs}-residual-deferrals.json`);
|
|
288
|
+
if (!fs.existsSync(path))
|
|
289
|
+
return [];
|
|
290
|
+
const isDeferral = (e) => typeof e === "object" && e !== null && typeof e.id === "string" && typeof e.reason === "string" && e.reason.trim().length > 0;
|
|
291
|
+
try {
|
|
292
|
+
const parsed = JSON.parse(fs.readFileSync(path, "utf8"));
|
|
293
|
+
return (Array.isArray(parsed) ? parsed : []).filter(isDeferral);
|
|
294
|
+
} catch {
|
|
295
|
+
return [];
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function scanResiduals2(root, wbs, tmpDir, taskContent, _env) {
|
|
299
|
+
const runDir = join2(root, ".spur", "run");
|
|
300
|
+
const basePath = join2(runDir, `${wbs}-base.sha`);
|
|
301
|
+
const base = fs.existsSync(basePath) ? fs.readFileSync(basePath, "utf8").trim() : null;
|
|
302
|
+
const stagingResidue = listStagingResidue(tmpDir, wbs);
|
|
303
|
+
const deferrals = readDeferrals(runDir, wbs);
|
|
304
|
+
const addedLines = base === null ? [] : collectAddedLines(root, base);
|
|
305
|
+
return scanResiduals({ wbs, base, taskContent, addedLines, stagingResidue, deferrals });
|
|
306
|
+
}
|
|
288
307
|
function parseArgs(argv) {
|
|
289
308
|
let mode = "";
|
|
290
309
|
let wbs = "";
|
|
@@ -308,96 +327,94 @@ function parseArgs(argv) {
|
|
|
308
327
|
else if (wbs === "")
|
|
309
328
|
wbs = a;
|
|
310
329
|
}
|
|
311
|
-
|
|
312
|
-
return null;
|
|
313
|
-
return { mode, wbs, spurBin, root, tmpDir };
|
|
314
|
-
}
|
|
315
|
-
function spur(env, spurBinFlag, args, cwd) {
|
|
316
|
-
return run(spurCommand(spurBinFlag ?? env.spurBin).cmd, [...spurCommand(spurBinFlag ?? env.spurBin).prefix, ...args], cwd);
|
|
330
|
+
return mode === "" || wbs === "" ? null : { mode, wbs, spurBin, root, tmpDir };
|
|
317
331
|
}
|
|
318
332
|
function loadTask(env, spurBinFlag, wbs, root) {
|
|
319
333
|
const res = spur(env, spurBinFlag, ["task", "show", wbs, "--json"], root);
|
|
320
334
|
if (res.status !== 0)
|
|
321
335
|
throw new Error(`task show ${wbs} failed`);
|
|
322
336
|
const parsed = JSON.parse(res.stdout);
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
const featureId = typeof parsed.feature_id === "string" ? parsed.feature_id : fm !== null && typeof fm === "object" && typeof fm.feature_id === "string" ? fm.feature_id : "";
|
|
326
|
-
return { content, featureId };
|
|
337
|
+
const featureId = [parsed.feature_id, parsed.frontmatter?.feature_id].find((v) => typeof v === "string") ?? "";
|
|
338
|
+
return { content: typeof parsed.content === "string" ? parsed.content : "", featureId };
|
|
327
339
|
}
|
|
328
340
|
function loadVerdict(runDir, wbs) {
|
|
329
|
-
return JSON.parse(readFileSync(
|
|
341
|
+
return JSON.parse(fs.readFileSync(recordedVerdictPath(runDir, wbs, fs), "utf8"));
|
|
330
342
|
}
|
|
331
343
|
function scanMode(opts, env, io) {
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
const artifact =
|
|
336
|
-
writeFileSync(
|
|
344
|
+
const runDir = join2(opts.root, ".spur", "run");
|
|
345
|
+
fs.mkdirSync(runDir, { recursive: true });
|
|
346
|
+
const { content } = loadTask(env, opts.spurBin, opts.wbs, opts.root);
|
|
347
|
+
const artifact = scanResiduals2(opts.root, opts.wbs, opts.tmpDir, content, env);
|
|
348
|
+
fs.writeFileSync(join2(runDir, `${opts.wbs}-residuals.json`), `${JSON.stringify(artifact, null, 2)}
|
|
337
349
|
`);
|
|
338
350
|
io.out(`residual-scan: ${opts.wbs} blocking=${artifact.counts.blocking} deferrable=${artifact.counts.deferrable} advisory=${artifact.counts.advisory} housekeeping=${artifact.counts.housekeeping}
|
|
339
351
|
`);
|
|
340
352
|
return 0;
|
|
341
353
|
}
|
|
342
354
|
function foldMode(opts, _env, io) {
|
|
343
|
-
const runDir =
|
|
344
|
-
const scan = JSON.parse(readFileSync(
|
|
345
|
-
const
|
|
355
|
+
const runDir = join2(opts.root, ".spur", "run");
|
|
356
|
+
const scan = JSON.parse(fs.readFileSync(join2(runDir, `${opts.wbs}-residuals.json`), "utf8"));
|
|
357
|
+
const target = recordedVerdictPath(runDir, opts.wbs, fs);
|
|
346
358
|
const verdict = loadVerdict(runDir, opts.wbs);
|
|
347
|
-
const findingsPath =
|
|
348
|
-
const
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
359
|
+
const findingsPath = join2(runDir, `${opts.wbs}-test-gate.findings`);
|
|
360
|
+
const fold = foldVerdict(verdict, scan, fs.existsSync(findingsPath) ? fs.readFileSync(findingsPath, "utf8") : "");
|
|
361
|
+
const bytes = `${JSON.stringify({ ...verdict, verdict: fold.verdict, checks: fold.checks }, null, 2)}
|
|
362
|
+
`;
|
|
363
|
+
const temporary = `${target}.${process.pid}.tmp`;
|
|
364
|
+
try {
|
|
365
|
+
fs.writeFileSync(temporary, bytes, { flag: "wx" });
|
|
366
|
+
fs.renameSync(temporary, target);
|
|
367
|
+
} finally {
|
|
368
|
+
fs.rmSync(temporary, { force: true });
|
|
369
|
+
}
|
|
370
|
+
const scratch = join2(runDir, `${opts.wbs}-verdict.json`);
|
|
371
|
+
if (target !== scratch)
|
|
372
|
+
fs.writeFileSync(scratch, bytes);
|
|
373
|
+
fs.writeFileSync(findingsPath, fold.findings);
|
|
374
|
+
io.out(`residual-fold: ${opts.wbs} verdict=${fold.verdict} residual-sweep=${fold.checks.find((c) => c.name === "residual-sweep")?.status}
|
|
354
375
|
`);
|
|
355
376
|
return 0;
|
|
356
377
|
}
|
|
357
378
|
function settleMode(opts, env, io) {
|
|
358
|
-
const
|
|
359
|
-
const
|
|
360
|
-
const
|
|
361
|
-
const
|
|
379
|
+
const wbs = opts.wbs;
|
|
380
|
+
const task = loadTask(env, opts.spurBin, wbs, opts.root);
|
|
381
|
+
const runDir = join2(opts.root, ".spur", "run");
|
|
382
|
+
const scan = scanResiduals2(opts.root, wbs, opts.tmpDir, task.content, env);
|
|
383
|
+
const residualsPath = join2(runDir, `${wbs}-residuals.json`);
|
|
384
|
+
let prior = {};
|
|
385
|
+
if (fs.existsSync(residualsPath))
|
|
386
|
+
prior = JSON.parse(fs.readFileSync(residualsPath, "utf8"));
|
|
362
387
|
const deferred = scan.items.filter((i) => i.class === "deferrable");
|
|
363
|
-
const prior = existsSync(residualsPath) ? JSON.parse(readFileSync(residualsPath, "utf8")) : {};
|
|
364
388
|
if (deferred.length > 0 && prior.followUp === undefined) {
|
|
365
389
|
if (task.featureId === "") {
|
|
366
|
-
io.err(`residual-settle: ${
|
|
367
|
-
`);
|
|
368
|
-
return 0;
|
|
369
|
-
}
|
|
370
|
-
const created = spur(env, opts.spurBin, ["task", "create", `Residuals from ${opts.wbs}`, "--feature", task.featureId, "--skip-ready", "--json"], opts.root);
|
|
371
|
-
if (created.status !== 0) {
|
|
372
|
-
io.err(`residual-settle: task create failed; re-run: residual-scan settle ${opts.wbs}
|
|
390
|
+
io.err(`residual-settle: ${wbs} deferrals pending but feature_id unknown; re-run: residual-scan settle ${wbs}
|
|
373
391
|
`);
|
|
374
392
|
return 0;
|
|
375
393
|
}
|
|
394
|
+
const args = ["task", "create", `Residuals from ${wbs}`, "--feature", task.featureId, "--skip-ready", "--json"];
|
|
395
|
+
const created = spur(env, opts.spurBin, args, opts.root);
|
|
376
396
|
let wbsNew = "";
|
|
377
397
|
try {
|
|
378
398
|
const parsed = JSON.parse(created.stdout);
|
|
379
|
-
const
|
|
380
|
-
wbsNew =
|
|
399
|
+
const data = typeof parsed.data === "object" && parsed.data !== null ? parsed.data : {};
|
|
400
|
+
wbsNew = [parsed.wbs, data.wbs].find((v) => typeof v === "string") ?? "";
|
|
381
401
|
} catch {
|
|
382
402
|
wbsNew = "";
|
|
383
403
|
}
|
|
384
|
-
if (wbsNew === "") {
|
|
385
|
-
io.err(`residual-settle:
|
|
404
|
+
if (created.status !== 0 || wbsNew === "") {
|
|
405
|
+
io.err(`residual-settle: task create failed or unreadable wbs; re-run: residual-scan settle ${wbs}
|
|
386
406
|
`);
|
|
387
407
|
return 0;
|
|
388
408
|
}
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
`);
|
|
395
|
-
const bgFile = join(runDir, `${opts.wbs}-residual-background.md`);
|
|
396
|
-
writeFileSync(bgFile, `${bg}
|
|
409
|
+
const rows = deferred.map((i) => `- ${i.id} \u2014 ${i.location}: ${i.text}`);
|
|
410
|
+
const head = `Source task: ${wbs} (feature ${task.featureId}) \u2014 deferred residuals filed by residual-scan settle.`;
|
|
411
|
+
const bgFile = join2(runDir, `${wbs}-residual-background.md`);
|
|
412
|
+
fs.writeFileSync(bgFile, `${[head, "", ...rows].join(`
|
|
413
|
+
`)}
|
|
397
414
|
`);
|
|
398
|
-
const
|
|
399
|
-
if (
|
|
400
|
-
io.err(`residual-settle: background write failed; re-run: residual-scan settle ${
|
|
415
|
+
const updArgs = ["task", "update", wbsNew, "--section", "Background", "--from-file", bgFile];
|
|
416
|
+
if (spur(env, opts.spurBin, updArgs, opts.root).status !== 0) {
|
|
417
|
+
io.err(`residual-settle: background write failed; re-run: residual-scan settle ${wbs}
|
|
401
418
|
`);
|
|
402
419
|
return 0;
|
|
403
420
|
}
|
|
@@ -405,32 +422,30 @@ function settleMode(opts, env, io) {
|
|
|
405
422
|
io.out(`residual-settle: filed follow-up ${wbsNew} for ${deferred.length} deferred item(s)
|
|
406
423
|
`);
|
|
407
424
|
}
|
|
408
|
-
for (const path of listStagingResidue(opts.tmpDir,
|
|
425
|
+
for (const path of listStagingResidue(opts.tmpDir, wbs)) {
|
|
409
426
|
try {
|
|
410
|
-
rmSync(path, { force: true });
|
|
427
|
+
fs.rmSync(path, { force: true });
|
|
411
428
|
} catch {
|
|
412
|
-
io.err(`residual-settle: could not remove ${path}; re-run: residual-scan settle ${
|
|
429
|
+
io.err(`residual-settle: could not remove ${path}; re-run: residual-scan settle ${wbs}
|
|
413
430
|
`);
|
|
414
431
|
return 0;
|
|
415
432
|
}
|
|
416
433
|
}
|
|
417
|
-
writeFileSync(residualsPath, `${JSON.stringify({ ...scan, ...prior }, null, 2)}
|
|
434
|
+
fs.writeFileSync(residualsPath, `${JSON.stringify({ ...scan, ...prior }, null, 2)}
|
|
418
435
|
`);
|
|
419
436
|
return 0;
|
|
420
437
|
}
|
|
421
438
|
function reportMode(opts, env, io) {
|
|
422
|
-
const runDir =
|
|
423
|
-
const
|
|
424
|
-
const sweep = verdict.checks.find((c) => c.name === "residual-sweep");
|
|
439
|
+
const runDir = join2(opts.root, ".spur", "run");
|
|
440
|
+
const sweep = loadVerdict(runDir, opts.wbs).checks.find((c) => c.name === "residual-sweep");
|
|
425
441
|
if (sweep === undefined || sweep.status !== "fail")
|
|
426
442
|
return 0;
|
|
427
|
-
const
|
|
428
|
-
const scan =
|
|
443
|
+
const { content } = loadTask(env, opts.spurBin, opts.wbs, opts.root);
|
|
444
|
+
const scan = scanResiduals2(opts.root, opts.wbs, opts.tmpDir, content, env);
|
|
429
445
|
const blocking = scan.items.filter((i) => i.class === "blocking");
|
|
430
|
-
const attemptFile =
|
|
431
|
-
const attempts = existsSync(attemptFile) ? Number.parseInt(readFileSync(attemptFile, "utf8").trim() || "0", 10) : 0;
|
|
432
|
-
|
|
433
|
-
writeFileSync(reportPath, renderReport(opts.wbs, blocking, Number.isNaN(attempts) ? 0 : attempts));
|
|
446
|
+
const attemptFile = join2(runDir, `${opts.wbs}-test-fix-attempt`);
|
|
447
|
+
const attempts = fs.existsSync(attemptFile) ? Number.parseInt(fs.readFileSync(attemptFile, "utf8").trim() || "0", 10) : 0;
|
|
448
|
+
fs.writeFileSync(join2(runDir, `${opts.wbs}-residual-report.md`), renderReport(opts.wbs, blocking, Number.isNaN(attempts) ? 0 : attempts));
|
|
434
449
|
io.out(`Recovery: fix the items in .spur/run/${opts.wbs}-residual-report.md, then /sp:dev-run ${opts.wbs}
|
|
435
450
|
`);
|
|
436
451
|
return 0;
|
|
@@ -440,33 +455,23 @@ function main(argv, env = getEnvVars(), options = {}) {
|
|
|
440
455
|
out: (line) => process.stdout.write(line),
|
|
441
456
|
err: (line) => process.stderr.write(line)
|
|
442
457
|
};
|
|
458
|
+
const cwd = options.cwd ?? process.cwd();
|
|
443
459
|
const opts = parseArgs(argv);
|
|
444
|
-
|
|
460
|
+
const modes = { scan: scanMode, fold: foldMode, settle: settleMode, report: reportMode };
|
|
461
|
+
const modeFn = opts === null ? undefined : modes[opts.mode];
|
|
462
|
+
if (opts === null || modeFn === undefined) {
|
|
445
463
|
io.err(`${RESIDUAL_SCAN_USAGE}
|
|
446
464
|
`);
|
|
447
465
|
return 2;
|
|
448
466
|
}
|
|
449
|
-
const
|
|
450
|
-
|
|
451
|
-
if (resolved.mode === "scan")
|
|
452
|
-
return scanMode(resolved, env, io);
|
|
453
|
-
if (resolved.mode === "fold")
|
|
454
|
-
return foldMode(resolved, env, io);
|
|
455
|
-
if (resolved.mode === "settle")
|
|
456
|
-
return settleMode(resolved, env, io);
|
|
457
|
-
if (resolved.mode === "report")
|
|
458
|
-
return reportMode(resolved, env, io);
|
|
459
|
-
io.err(`${RESIDUAL_SCAN_USAGE}
|
|
460
|
-
`);
|
|
461
|
-
return 2;
|
|
462
|
-
}
|
|
463
|
-
{
|
|
464
|
-
process.exit(main(process.argv.slice(2)));
|
|
467
|
+
const resolved = { ...opts, root: opts.root.startsWith("/") ? opts.root : join2(cwd, opts.root) };
|
|
468
|
+
return modeFn(resolved, env, io);
|
|
465
469
|
}
|
|
470
|
+
process.exit(main(process.argv.slice(2)));
|
|
466
471
|
export {
|
|
467
|
-
|
|
468
|
-
scanResiduals,
|
|
472
|
+
scanResiduals2 as scanResiduals,
|
|
469
473
|
renderReport,
|
|
474
|
+
recordedVerdictPath,
|
|
470
475
|
parseReviewFindings,
|
|
471
476
|
parseDiffMarkers,
|
|
472
477
|
normalizeAnchor,
|