@gobing-ai/spur 0.4.1 → 0.4.2
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/templates/AGENTS.md +1 -1
- package/config/workflows/wrapup-pipeline.yaml +1 -1
- package/package.json +10 -10
- package/plugins/sp/README.md +1 -2
- package/plugins/sp/agents/super-planner.md +5 -6
- package/plugins/sp/commands/dev-review-session.md +2 -1
- package/plugins/sp/commands/dev-runall.md +18 -9
- package/plugins/sp/hooks/context-owner.ts +56 -0
- package/plugins/sp/hooks/context-post-tool.ts +23 -0
- package/plugins/sp/hooks/context-session-start.ts +49 -27
- package/plugins/sp/hooks/context-session-stop.ts +131 -30
- package/plugins/sp/hooks/pi/guard-extension.ts +21 -8
- package/plugins/sp/lib/idea-handoff.generated.mjs +4 -2
- package/plugins/sp/lib/inline-run.generated.mjs +47 -30
- package/plugins/sp/lib/plan-projection.generated.d.mts +19 -0
- package/plugins/sp/lib/plan-projection.generated.mjs +113 -0
- package/plugins/sp/lib/quality-gate.generated.d.mts +0 -12
- package/plugins/sp/lib/quality-gate.generated.mjs +48 -230
- package/plugins/sp/lib/transcript.ts +49 -21
- package/plugins/sp/plugin.json +1 -1
- package/plugins/sp/references/roles.md +1 -1
- package/plugins/sp/scripts/batch-plan.mjs +154 -1
- package/plugins/sp/scripts/batch-plan.ts +48 -3
- package/plugins/sp/scripts/inline-run-setup.mjs +24 -13
- 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 +25 -207
- package/plugins/sp/scripts/run-summary.mjs +24 -13
- package/plugins/sp/scripts/session-timeline.mjs +26 -25
- package/plugins/sp/scripts/session-timeline.ts +4 -18
- package/plugins/sp/scripts/task-diffstat.mjs +32 -29
- package/plugins/sp/scripts/task-diffstat.ts +34 -34
- package/plugins/sp/skills/indexed-context/SKILL.md +14 -7
- package/plugins/sp/skills/parallel-execution/SKILL.md +2 -2
- package/plugins/sp/skills/parallel-execution/references/dispatch-surface.md +1 -1
- 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/spur-cli/references/decision.md +9 -2
- 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 +5 -0
- package/plugins/sp/skills/spur-dev/references/execution-batch.md +43 -4
- package/plugins/sp/skills/spur-dev/references/execution-parallel-isolation.md +4 -1
- package/plugins/sp/skills/spur-dev/references/flag-glossary.md +13 -11
- package/plugins/sp/skills/spur-dev/references/inline-pipeline-driver.md +8 -13
- package/spur.js +14261 -2846
- package/web/_astro/{BoardApp.D66k_s_T.js → BoardApp.B3GQZvj8.js} +1 -1
- package/web/_astro/{BoardApp.D6sjAJr_.js → BoardApp.BT7L2kSb.js} +192 -192
- package/web/_astro/TaskDetail.Cxov1_N_.js +1 -0
- package/web/_astro/{arc._BUadrdH.js → arc.vaT7tPOB.js} +1 -1
- package/web/_astro/{architectureDiagram-3BPJPVTR.Dh7xS1Pp.js → architectureDiagram-3BPJPVTR.DRMmTjkN.js} +1 -1
- package/web/_astro/{blockDiagram-GPEHLZMM.DFDVpfXm.js → blockDiagram-GPEHLZMM.cla0LO_q.js} +1 -1
- package/web/_astro/{c4Diagram-AAUBKEIU.V7_3_glh.js → c4Diagram-AAUBKEIU.D4YAIBKn.js} +1 -1
- package/web/_astro/channel.D4nG9YLf.js +1 -0
- package/web/_astro/{chunk-2J33WTMH.De1yx7Jz.js → chunk-2J33WTMH.BrSJbMRZ.js} +1 -1
- package/web/_astro/{chunk-4BX2VUAB.BDhHFuxx.js → chunk-4BX2VUAB.CuLMgKF5.js} +1 -1
- package/web/_astro/{chunk-55IACEB6.DDLkWnmC.js → chunk-55IACEB6.zkvgu54F.js} +1 -1
- package/web/_astro/{chunk-727SXJPM.CwfiLnA7.js → chunk-727SXJPM.DxWkACh8.js} +1 -1
- package/web/_astro/{chunk-AQP2D5EJ.CtGDmUnM.js → chunk-AQP2D5EJ.42XrmIlH.js} +1 -1
- package/web/_astro/{chunk-FMBD7UC4.BOLjpw73.js → chunk-FMBD7UC4.Bao2wMK9.js} +1 -1
- package/web/_astro/{chunk-ND2GUHAM.BDhQls6O.js → chunk-ND2GUHAM.BhydojD1.js} +1 -1
- package/web/_astro/{chunk-QZHKN3VN.76qBynDF.js → chunk-QZHKN3VN.CmZ82g-H.js} +1 -1
- package/web/_astro/{classDiagram-4FO5ZUOK.N0mpPHT0.js → classDiagram-4FO5ZUOK.Blr83Gt_.js} +1 -1
- package/web/_astro/{classDiagram-v2-Q7XG4LA2.N0mpPHT0.js → classDiagram-v2-Q7XG4LA2.Blr83Gt_.js} +1 -1
- package/web/_astro/{cose-bilkent-S5V4N54A.CKknFfjB.js → cose-bilkent-S5V4N54A.qmfv0SPn.js} +1 -1
- package/web/_astro/{cynefin-OW5HDTMX.Cw3ZWBAh.js → cynefin-OW5HDTMX.BtELhXPR.js} +1 -1
- package/web/_astro/{dagre-BM42HDAG.D3yIKCRD.js → dagre-BM42HDAG.AWmaAvXo.js} +1 -1
- package/web/_astro/{diagram-2AECGRRQ.D51dO83Z.js → diagram-2AECGRRQ.DdH34KDi.js} +1 -1
- package/web/_astro/{diagram-5GNKFQAL.Bb-FViDj.js → diagram-5GNKFQAL.BmNPix1k.js} +1 -1
- package/web/_astro/{diagram-KO2AKTUF.D3mN8S6x.js → diagram-KO2AKTUF.BNQd_Ppo.js} +1 -1
- package/web/_astro/{diagram-LMA3HP47.dMMyPcbJ.js → diagram-LMA3HP47.BAPgDi0e.js} +1 -1
- package/web/_astro/{diagram-OG6HWLK6.AzbvzY4C.js → diagram-OG6HWLK6.DZ4zyLdC.js} +1 -1
- package/web/_astro/{erDiagram-TEJ5UH35.BR5I4OZb.js → erDiagram-TEJ5UH35.Q1TySMmw.js} +1 -1
- package/web/_astro/{flowDiagram-I6XJVG4X.DFqvhGkn.js → flowDiagram-I6XJVG4X.BXhFBicG.js} +1 -1
- package/web/_astro/{ganttDiagram-6RSMTGT7.ByJbhH6J.js → ganttDiagram-6RSMTGT7.vHSgcuo2.js} +1 -1
- package/web/_astro/{gitGraphDiagram-PVQCEYII.CInHjQR3.js → gitGraphDiagram-PVQCEYII.Bi1f73C7.js} +1 -1
- package/web/_astro/{index.COh_kBxN.css → index.BhSwN8yY.css} +1 -1
- package/web/_astro/{infoDiagram-5YYISTIA.Cm0dGVjJ.js → infoDiagram-5YYISTIA.DPvXZEWm.js} +1 -1
- package/web/_astro/{ishikawaDiagram-YF4QCWOH.C3nOP6Sj.js → ishikawaDiagram-YF4QCWOH.Cz829fsV.js} +1 -1
- package/web/_astro/{journeyDiagram-JHISSGLW.4IvLa2MW.js → journeyDiagram-JHISSGLW.enRnB9Me.js} +1 -1
- package/web/_astro/{kanban-definition-UN3LZRKU.DS87Cl-k.js → kanban-definition-UN3LZRKU.DnTNk6d2.js} +1 -1
- package/web/_astro/{linear.MzmRrNER.js → linear.BzGnBwNL.js} +1 -1
- package/web/_astro/{mermaid.core.fK-WNswS.js → mermaid.core.CSbTpIVz.js} +4 -4
- package/web/_astro/{mindmap-definition-RKZ34NQL.BqipOHhY.js → mindmap-definition-RKZ34NQL.DGJDy8zT.js} +1 -1
- package/web/_astro/{pieDiagram-4H26LBE5.0nBiPqFG.js → pieDiagram-4H26LBE5.HSPdi5_8.js} +1 -1
- package/web/_astro/{quadrantDiagram-W4KKPZXB.Szj7Zpx5.js → quadrantDiagram-W4KKPZXB.6Gv8mz2Y.js} +1 -1
- package/web/_astro/{requirementDiagram-4Y6WPE33.D4UR6TWo.js → requirementDiagram-4Y6WPE33.CnBCtFwa.js} +1 -1
- package/web/_astro/{sankeyDiagram-5OEKKPKP.DAE7BdOs.js → sankeyDiagram-5OEKKPKP.5M8VWAu8.js} +1 -1
- package/web/_astro/{sequenceDiagram-3UESZ5HK.BP9ZVq25.js → sequenceDiagram-3UESZ5HK.DN41XN-U.js} +1 -1
- package/web/_astro/{stateDiagram-AJRCARHV.BQW7mIxk.js → stateDiagram-AJRCARHV.ErOO3W8j.js} +1 -1
- package/web/_astro/{stateDiagram-v2-BHNVJYJU.PbK4nDih.js → stateDiagram-v2-BHNVJYJU.B_ZHICM-.js} +1 -1
- package/web/_astro/{timeline-definition-PNZ67QCA.Ci_GvUpo.js → timeline-definition-PNZ67QCA.CWdosr07.js} +1 -1
- package/web/_astro/{vennDiagram-CIIHVFJN.DNw1Y5qx.js → vennDiagram-CIIHVFJN.BHzYjNV5.js} +1 -1
- package/web/_astro/{wardleyDiagram-YWT4CUSO.CwlkaaNe.js → wardleyDiagram-YWT4CUSO.h8x4IwPX.js} +1 -1
- package/web/_astro/{xychartDiagram-2RQKCTM6.MI6ZuiH_.js → xychartDiagram-2RQKCTM6.RTapW2X8.js} +1 -1
- package/web/index.html +2 -2
- package/plugins/sp/commands/dev-parallel.md +0 -37
- package/web/_astro/TaskDetail.BynLnMNc.js +0 -1
- package/web/_astro/channel.DZVfto-r.js +0 -1
|
@@ -21,16 +21,14 @@ import {
|
|
|
21
21
|
mkdirSync,
|
|
22
22
|
mkdtempSync,
|
|
23
23
|
openSync,
|
|
24
|
-
readdirSync,
|
|
25
24
|
readFileSync,
|
|
26
|
-
rmdirSync,
|
|
27
25
|
rmSync,
|
|
28
26
|
statSync,
|
|
29
27
|
unlinkSync,
|
|
30
28
|
writeFileSync
|
|
31
29
|
} from "node:fs";
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
30
|
+
import { tmpdir } from "node:os";
|
|
31
|
+
import { join, resolve } from "node:path";
|
|
34
32
|
import { createHash } from "node:crypto";
|
|
35
33
|
var __defProp = Object.defineProperty;
|
|
36
34
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
@@ -82,19 +80,9 @@ var init_api_response = __esm(() => {
|
|
|
82
80
|
CLIENT_SAFE_CODES = new Set([ErrorCode.NotFound, ErrorCode.Validation, ErrorCode.Conflict]);
|
|
83
81
|
});
|
|
84
82
|
var init_cursor = () => {};
|
|
85
|
-
function getEnvVar(name, fallback) {
|
|
86
|
-
const raw = process.env[name];
|
|
87
|
-
return raw === undefined ? fallback : raw;
|
|
88
|
-
}
|
|
89
83
|
function getEnvVars2() {
|
|
90
84
|
return process.env;
|
|
91
85
|
}
|
|
92
|
-
function setEnvVar(name, value) {
|
|
93
|
-
if (value === undefined)
|
|
94
|
-
delete process.env[name];
|
|
95
|
-
else
|
|
96
|
-
process.env[name] = value;
|
|
97
|
-
}
|
|
98
86
|
var init_event_severity = () => {};
|
|
99
87
|
var init_dist = __esm(() => {
|
|
100
88
|
init_api_response();
|
|
@@ -24913,7 +24901,7 @@ var POST_WATERMARK_TABLES = [
|
|
|
24913
24901
|
"history_daily_stats",
|
|
24914
24902
|
"history_board_source_daily"
|
|
24915
24903
|
];
|
|
24916
|
-
var HISTORY_IMPORT_SCHEMA_VERSION = "0.5.
|
|
24904
|
+
var HISTORY_IMPORT_SCHEMA_VERSION = "0.5.22";
|
|
24917
24905
|
var HISTORY_IMPORT_SCHEMA_SQL = `
|
|
24918
24906
|
CREATE TABLE IF NOT EXISTS history_import_checkpoint (
|
|
24919
24907
|
source TEXT NOT NULL,
|
|
@@ -27867,6 +27855,9 @@ var INBOX_MESSAGES_REQUEST_KEY_SCHEMA_SQL = `
|
|
|
27867
27855
|
ALTER TABLE inbox_messages ADD COLUMN request_key TEXT;
|
|
27868
27856
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_inbox_messages_request_key ON inbox_messages (request_key) WHERE request_key IS NOT NULL;
|
|
27869
27857
|
`;
|
|
27858
|
+
var INBOX_MESSAGES_READ_AT_SCHEMA_SQL = `
|
|
27859
|
+
ALTER TABLE inbox_messages ADD COLUMN read_at INTEGER;
|
|
27860
|
+
`;
|
|
27870
27861
|
var QUEUE_JOBS_SCHEMA_SQL = `
|
|
27871
27862
|
CREATE TABLE IF NOT EXISTS queue_jobs (
|
|
27872
27863
|
id TEXT PRIMARY KEY,
|
|
@@ -28879,6 +28870,11 @@ var CLI_MIGRATIONS = [
|
|
|
28879
28870
|
{
|
|
28880
28871
|
id: "0052_spur_cli_decision_logs",
|
|
28881
28872
|
sql: DECISION_LOGS_SCHEMA_SQL
|
|
28873
|
+
},
|
|
28874
|
+
{
|
|
28875
|
+
id: "0053_spur_cli_inbox_messages_read_at",
|
|
28876
|
+
sql: INBOX_MESSAGES_READ_AT_SCHEMA_SQL,
|
|
28877
|
+
addColumnIfMissing: { table: "inbox_messages", column: "read_at" }
|
|
28882
28878
|
}
|
|
28883
28879
|
];
|
|
28884
28880
|
init_zod();
|
|
@@ -30367,159 +30363,6 @@ function runShellCommand(cmd, cwd, extraEnv) {
|
|
|
30367
30363
|
rmSync(dir, { recursive: true, force: true });
|
|
30368
30364
|
}
|
|
30369
30365
|
}
|
|
30370
|
-
var GATE_LOCK_DIR_ENV = "SPUR_GATE_LOCK_DIR";
|
|
30371
|
-
var GATE_LOCK_POLL_MS_ENV = "SPUR_GATE_LOCK_POLL_MS";
|
|
30372
|
-
var GATE_LOCK_OFF_ENV = "SPUR_GATE_LOCK";
|
|
30373
|
-
var GATE_LOCK_TOKEN_ENV = "SPUR_GATE_LOCK_TOKEN";
|
|
30374
|
-
var GATE_LOCK_POLL_MS_DEFAULT = 5000;
|
|
30375
|
-
var GATE_LOCK_MKDIR_HOLD_MS_ENV = "SPUR_GATE_LOCK_TEST_MKDIR_HOLD_MS";
|
|
30376
|
-
var GATE_LOCK_LOST_RACE_SLEEP_MS = 20;
|
|
30377
|
-
var GATE_LOCK_LOST_RACE_LOG_AT = 250;
|
|
30378
|
-
function gateLockDir() {
|
|
30379
|
-
const raw = getEnvVar(GATE_LOCK_DIR_ENV);
|
|
30380
|
-
return raw !== undefined && raw.length > 0 ? raw : join(homedir(), ".config", "spur", "run", "full-gate.lock");
|
|
30381
|
-
}
|
|
30382
|
-
function gateLockPollMs() {
|
|
30383
|
-
const raw = Number.parseInt(getEnvVar(GATE_LOCK_POLL_MS_ENV) ?? "", 10);
|
|
30384
|
-
return Number.isFinite(raw) && raw > 0 ? raw : GATE_LOCK_POLL_MS_DEFAULT;
|
|
30385
|
-
}
|
|
30386
|
-
function isProcessAlive(pid) {
|
|
30387
|
-
try {
|
|
30388
|
-
process.kill(pid, 0);
|
|
30389
|
-
return true;
|
|
30390
|
-
} catch (error51) {
|
|
30391
|
-
return error51.code !== "ESRCH";
|
|
30392
|
-
}
|
|
30393
|
-
}
|
|
30394
|
-
function readGateLockClaim(dir) {
|
|
30395
|
-
let markers;
|
|
30396
|
-
try {
|
|
30397
|
-
markers = readdirSync(dir).sort();
|
|
30398
|
-
} catch {
|
|
30399
|
-
return null;
|
|
30400
|
-
}
|
|
30401
|
-
for (const marker of markers) {
|
|
30402
|
-
try {
|
|
30403
|
-
const claim = JSON.parse(readFileSync(join(dir, marker), "utf8"));
|
|
30404
|
-
if (typeof claim.pid === "number")
|
|
30405
|
-
return { claim, marker };
|
|
30406
|
-
} catch {
|
|
30407
|
-
const pid = Number.parseInt(/^(\d+)-/.exec(marker)?.[1] ?? "", 10);
|
|
30408
|
-
if (pid > 0) {
|
|
30409
|
-
return { claim: { pid, startedAt: Date.now(), wbs: "?", runId: "?", cwd: "?" }, marker };
|
|
30410
|
-
}
|
|
30411
|
-
}
|
|
30412
|
-
}
|
|
30413
|
-
return null;
|
|
30414
|
-
}
|
|
30415
|
-
function acquireGateLock(env2, logLine = () => {}) {
|
|
30416
|
-
if ((getEnvVar(GATE_LOCK_OFF_ENV) ?? "") === "off") {
|
|
30417
|
-
return { release() {}, queueWaitMs: 0, token: "" };
|
|
30418
|
-
}
|
|
30419
|
-
const dir = gateLockDir();
|
|
30420
|
-
mkdirSync(dirname(dir), { recursive: true });
|
|
30421
|
-
const pollMs = gateLockPollMs();
|
|
30422
|
-
const claim = {
|
|
30423
|
-
pid: process.pid,
|
|
30424
|
-
startedAt: Date.now(),
|
|
30425
|
-
wbs: env2.wbs,
|
|
30426
|
-
runId: receiptRunId(env2),
|
|
30427
|
-
cwd: process.cwd()
|
|
30428
|
-
};
|
|
30429
|
-
const marker = `${process.pid}-${randomUUID()}`;
|
|
30430
|
-
const startedWaitingAt = Date.now();
|
|
30431
|
-
let holderLogged = false;
|
|
30432
|
-
let graceLogged = false;
|
|
30433
|
-
let lostRaces = 0;
|
|
30434
|
-
let lostRaceLogged = false;
|
|
30435
|
-
const mkdirHoldMs = Number.parseInt(getEnvVar(GATE_LOCK_MKDIR_HOLD_MS_ENV) ?? "", 10);
|
|
30436
|
-
for (;; ) {
|
|
30437
|
-
let dirIno = null;
|
|
30438
|
-
try {
|
|
30439
|
-
mkdirSync(dir);
|
|
30440
|
-
dirIno = statSync(dir).ino;
|
|
30441
|
-
if (Number.isFinite(mkdirHoldMs) && mkdirHoldMs > 0)
|
|
30442
|
-
gateSleep(mkdirHoldMs);
|
|
30443
|
-
writeFileSync(join(dir, marker), JSON.stringify(claim), { flag: "wx" });
|
|
30444
|
-
} catch (error51) {
|
|
30445
|
-
const code = error51.code;
|
|
30446
|
-
if (code === "ENOENT" || code === "EINVAL") {
|
|
30447
|
-
lostRaces += 1;
|
|
30448
|
-
if (lostRaces === GATE_LOCK_LOST_RACE_LOG_AT && !lostRaceLogged) {
|
|
30449
|
-
lostRaceLogged = true;
|
|
30450
|
-
logLine(`quality gate lock: still losing the claim race after ${lostRaces} attempts (dir ${dir})
|
|
30451
|
-
`);
|
|
30452
|
-
}
|
|
30453
|
-
gateSleep(GATE_LOCK_LOST_RACE_SLEEP_MS);
|
|
30454
|
-
continue;
|
|
30455
|
-
}
|
|
30456
|
-
if (code !== "EEXIST") {
|
|
30457
|
-
throw error51;
|
|
30458
|
-
}
|
|
30459
|
-
}
|
|
30460
|
-
if (dirIno !== null) {
|
|
30461
|
-
let currentIno = null;
|
|
30462
|
-
let entries = [];
|
|
30463
|
-
try {
|
|
30464
|
-
currentIno = statSync(dir).ino;
|
|
30465
|
-
entries = readdirSync(dir);
|
|
30466
|
-
} catch {}
|
|
30467
|
-
if (currentIno !== dirIno || entries.length !== 1 || entries[0] !== marker) {
|
|
30468
|
-
try {
|
|
30469
|
-
unlinkSync(join(dir, marker));
|
|
30470
|
-
} catch {}
|
|
30471
|
-
lostRaces += 1;
|
|
30472
|
-
gateSleep(GATE_LOCK_LOST_RACE_SLEEP_MS);
|
|
30473
|
-
continue;
|
|
30474
|
-
}
|
|
30475
|
-
return {
|
|
30476
|
-
release() {
|
|
30477
|
-
try {
|
|
30478
|
-
rmSync(join(dir, marker), { force: true });
|
|
30479
|
-
rmdirSync(dir);
|
|
30480
|
-
} catch {}
|
|
30481
|
-
},
|
|
30482
|
-
queueWaitMs: Date.now() - startedWaitingAt,
|
|
30483
|
-
token: marker
|
|
30484
|
-
};
|
|
30485
|
-
}
|
|
30486
|
-
const held = readGateLockClaim(dir);
|
|
30487
|
-
if (held !== null && isProcessAlive(held.claim.pid)) {
|
|
30488
|
-
if (getEnvVar(GATE_LOCK_TOKEN_ENV) === held.marker) {
|
|
30489
|
-
return { release() {}, queueWaitMs: Date.now() - startedWaitingAt, token: held.marker };
|
|
30490
|
-
}
|
|
30491
|
-
if (!holderLogged) {
|
|
30492
|
-
holderLogged = true;
|
|
30493
|
-
logLine(`quality gate lock: waiting for holder (wbs ${held.claim.wbs}, run ${held.claim.runId}, pid ${held.claim.pid}, cwd ${held.claim.cwd}, held ${Date.now() - held.claim.startedAt}ms)
|
|
30494
|
-
`);
|
|
30495
|
-
}
|
|
30496
|
-
} else if (held !== null) {
|
|
30497
|
-
logLine(`quality gate lock: reclaimed stale claim (pid ${held.claim.pid})
|
|
30498
|
-
`);
|
|
30499
|
-
try {
|
|
30500
|
-
unlinkSync(join(dir, held.marker));
|
|
30501
|
-
rmdirSync(dir);
|
|
30502
|
-
} catch {}
|
|
30503
|
-
} else {
|
|
30504
|
-
try {
|
|
30505
|
-
rmdirSync(dir);
|
|
30506
|
-
graceLogged = false;
|
|
30507
|
-
} catch {
|
|
30508
|
-
gateSleep(pollMs);
|
|
30509
|
-
try {
|
|
30510
|
-
rmdirSync(dir);
|
|
30511
|
-
graceLogged = false;
|
|
30512
|
-
} catch {
|
|
30513
|
-
if (!graceLogged) {
|
|
30514
|
-
graceLogged = true;
|
|
30515
|
-
logLine(`quality gate lock: dir has no readable claim and is not empty — waiting
|
|
30516
|
-
`);
|
|
30517
|
-
}
|
|
30518
|
-
}
|
|
30519
|
-
}
|
|
30520
|
-
}
|
|
30521
|
-
}
|
|
30522
|
-
}
|
|
30523
30366
|
function recomputeGateProofFingerprint(cwd, env2) {
|
|
30524
30367
|
const taskSpecPath = env2.taskSpecPath ?? env2.taskFile;
|
|
30525
30368
|
if (!taskSpecPath || taskSpecPath.trim() === "") {
|
|
@@ -30643,44 +30486,27 @@ function runQualityGate(mode, env2, options = {}) {
|
|
|
30643
30486
|
appendFileSync(abs(logFile), probe.output);
|
|
30644
30487
|
rmSync(abs(`${logFile}.probe`), { force: true });
|
|
30645
30488
|
}
|
|
30646
|
-
let queueWaitMs = 0;
|
|
30647
30489
|
let gateRuntimeMs = 0;
|
|
30648
30490
|
if (gateRc === 0) {
|
|
30649
30491
|
const delayMs = retryDelayMs(env2);
|
|
30650
|
-
const
|
|
30492
|
+
const gateRuntimeStartMs = Date.now();
|
|
30493
|
+
for (gateAttempt = 1;gateAttempt <= MAX_GATE_ATTEMPTS; gateAttempt++) {
|
|
30494
|
+
const attemptLogPath = `${logFile}.attempt-${gateAttempt}`;
|
|
30495
|
+
const attempt = runShellCommand(env2.qualityGateCmd ?? "", cwd);
|
|
30496
|
+
writeFileSync(abs(attemptLogPath), attempt.output);
|
|
30497
|
+
const locked = isTransientLock(attempt.output);
|
|
30498
|
+
appendFileSync(abs(logFile), attempt.output);
|
|
30499
|
+
rmSync(abs(attemptLogPath), { force: true });
|
|
30500
|
+
gateRc = attempt.code;
|
|
30501
|
+
if (gateRc === 0 || !locked || gateAttempt >= MAX_GATE_ATTEMPTS)
|
|
30502
|
+
break;
|
|
30503
|
+
const line = retryMessage(gateAttempt);
|
|
30651
30504
|
process.stdout.write(line);
|
|
30652
30505
|
appendFileSync(abs(logFile), line);
|
|
30653
|
-
|
|
30654
|
-
const lock = acquireGateLock(env2, lockLine);
|
|
30655
|
-
queueWaitMs = lock.queueWaitMs;
|
|
30656
|
-
lockLine(`quality gate lock: queueWaitMs=${queueWaitMs}
|
|
30657
|
-
`);
|
|
30658
|
-
const gateRuntimeStartMs = Date.now();
|
|
30659
|
-
const previousToken = getEnvVar(GATE_LOCK_TOKEN_ENV);
|
|
30660
|
-
if (lock.token.length > 0)
|
|
30661
|
-
setEnvVar(GATE_LOCK_TOKEN_ENV, lock.token);
|
|
30662
|
-
try {
|
|
30663
|
-
for (gateAttempt = 1;gateAttempt <= MAX_GATE_ATTEMPTS; gateAttempt++) {
|
|
30664
|
-
const attemptLogPath = `${logFile}.attempt-${gateAttempt}`;
|
|
30665
|
-
const attempt = runShellCommand(env2.qualityGateCmd ?? "", cwd, lock.token.length > 0 ? { [GATE_LOCK_TOKEN_ENV]: lock.token } : undefined);
|
|
30666
|
-
writeFileSync(abs(attemptLogPath), attempt.output);
|
|
30667
|
-
const locked = isTransientLock(attempt.output);
|
|
30668
|
-
appendFileSync(abs(logFile), attempt.output);
|
|
30669
|
-
rmSync(abs(attemptLogPath), { force: true });
|
|
30670
|
-
gateRc = attempt.code;
|
|
30671
|
-
if (gateRc === 0 || !locked || gateAttempt >= MAX_GATE_ATTEMPTS)
|
|
30672
|
-
break;
|
|
30673
|
-
const line = retryMessage(gateAttempt);
|
|
30674
|
-
process.stdout.write(line);
|
|
30675
|
-
appendFileSync(abs(logFile), line);
|
|
30676
|
-
gateSleep(delayMs);
|
|
30677
|
-
}
|
|
30678
|
-
} finally {
|
|
30679
|
-
setEnvVar(GATE_LOCK_TOKEN_ENV, previousToken);
|
|
30680
|
-
lock.release();
|
|
30506
|
+
gateSleep(delayMs);
|
|
30681
30507
|
}
|
|
30682
30508
|
gateRuntimeMs = Date.now() - gateRuntimeStartMs;
|
|
30683
|
-
appendFileSync(abs(logFile), `
|
|
30509
|
+
appendFileSync(abs(logFile), `gate runtime: ${gateRuntimeMs}ms
|
|
30684
30510
|
`);
|
|
30685
30511
|
}
|
|
30686
30512
|
if (gateRc !== 0) {
|
|
@@ -30733,7 +30559,6 @@ function runQualityGate(mode, env2, options = {}) {
|
|
|
30733
30559
|
}
|
|
30734
30560
|
],
|
|
30735
30561
|
completedAt: new Date().toISOString(),
|
|
30736
|
-
queueWaitMs,
|
|
30737
30562
|
gateRuntimeMs
|
|
30738
30563
|
});
|
|
30739
30564
|
writeFileSync(abs(receiptFile), `${JSON.stringify(receipt, null, 2)}
|
|
@@ -30811,7 +30636,6 @@ export {
|
|
|
30811
30636
|
isTransientLock,
|
|
30812
30637
|
extractFindings,
|
|
30813
30638
|
buildReceipt,
|
|
30814
|
-
acquireGateLock,
|
|
30815
30639
|
RETRY_DELAY_MS_ENV,
|
|
30816
30640
|
RETRY_DELAY_MS_DEFAULT,
|
|
30817
30641
|
RECEIPT_SCHEMA_VERSION,
|
|
@@ -30819,12 +30643,6 @@ export {
|
|
|
30819
30643
|
MAX_GATE_ATTEMPTS,
|
|
30820
30644
|
MAX_FINDINGS,
|
|
30821
30645
|
LOCKED_PATTERN,
|
|
30822
|
-
GATE_LOCK_TOKEN_ENV,
|
|
30823
|
-
GATE_LOCK_POLL_MS_ENV,
|
|
30824
|
-
GATE_LOCK_POLL_MS_DEFAULT,
|
|
30825
|
-
GATE_LOCK_OFF_ENV,
|
|
30826
|
-
GATE_LOCK_MKDIR_HOLD_MS_ENV,
|
|
30827
|
-
GATE_LOCK_DIR_ENV,
|
|
30828
30646
|
FINDINGS_PATTERN,
|
|
30829
30647
|
COVERAGE_ROW_PATTERN
|
|
30830
30648
|
};
|
|
@@ -40,17 +40,19 @@ function formatTokenSplit(t) {
|
|
|
40
40
|
return `${formatTokens(nonCached + t.cacheRead)} / ${formatTokens(nonCached)}`;
|
|
41
41
|
}
|
|
42
42
|
function promptText(row) {
|
|
43
|
+
if (row.type === "custom_message") {
|
|
44
|
+
if (row.display === false)
|
|
45
|
+
return;
|
|
46
|
+
const text = typeof row.content === "string" ? row.content.trim() : "";
|
|
47
|
+
return text === "" ? undefined : text;
|
|
48
|
+
}
|
|
43
49
|
if (row.type === "message") {
|
|
44
50
|
if (row.message?.role !== "user")
|
|
45
51
|
return;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const content2 = row.message.content;
|
|
49
|
-
if (typeof content2 === "string")
|
|
50
|
-
return content2;
|
|
51
|
-
if (!Array.isArray(content2))
|
|
52
|
+
const text = firstTextContent(row);
|
|
53
|
+
if (text === undefined)
|
|
52
54
|
return;
|
|
53
|
-
return
|
|
55
|
+
return skillInvocationPrompt(text) ?? text;
|
|
54
56
|
}
|
|
55
57
|
if (row.type !== "user" || row.isMeta || row.isCompactSummary)
|
|
56
58
|
return;
|
|
@@ -61,12 +63,21 @@ function promptText(row) {
|
|
|
61
63
|
return;
|
|
62
64
|
return content.find((b) => b.type === "text")?.text;
|
|
63
65
|
}
|
|
64
|
-
function
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
function firstTextContent(row) {
|
|
67
|
+
const content = row.message?.content;
|
|
68
|
+
if (typeof content === "string")
|
|
69
|
+
return content;
|
|
70
|
+
if (!Array.isArray(content))
|
|
71
|
+
return;
|
|
72
|
+
return content.find((b) => b.type === "text")?.text;
|
|
73
|
+
}
|
|
74
|
+
function skillInvocationPrompt(text) {
|
|
75
|
+
const name = /^\s*<skill name="([^"]+)"/.exec(text)?.[1];
|
|
76
|
+
if (name === undefined)
|
|
77
|
+
return;
|
|
78
|
+
const close = text.lastIndexOf("</skill>");
|
|
79
|
+
const args = close < 0 ? "" : text.slice(close + "</skill>".length).trim();
|
|
80
|
+
return args === "" ? `/${name}` : `/${name} ${args}`;
|
|
70
81
|
}
|
|
71
82
|
function sumTokens(all) {
|
|
72
83
|
const total = zeroTokens();
|
|
@@ -36,17 +36,19 @@ function formatTokenSplit(t) {
|
|
|
36
36
|
return `${formatTokens(nonCached + t.cacheRead)} / ${formatTokens(nonCached)}`;
|
|
37
37
|
}
|
|
38
38
|
function promptText(row) {
|
|
39
|
+
if (row.type === "custom_message") {
|
|
40
|
+
if (row.display === false)
|
|
41
|
+
return;
|
|
42
|
+
const text = typeof row.content === "string" ? row.content.trim() : "";
|
|
43
|
+
return text === "" ? undefined : text;
|
|
44
|
+
}
|
|
39
45
|
if (row.type === "message") {
|
|
40
46
|
if (row.message?.role !== "user")
|
|
41
47
|
return;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const content2 = row.message.content;
|
|
45
|
-
if (typeof content2 === "string")
|
|
46
|
-
return content2;
|
|
47
|
-
if (!Array.isArray(content2))
|
|
48
|
+
const text = firstTextContent(row);
|
|
49
|
+
if (text === undefined)
|
|
48
50
|
return;
|
|
49
|
-
return
|
|
51
|
+
return skillInvocationPrompt(text) ?? text;
|
|
50
52
|
}
|
|
51
53
|
if (row.type !== "user" || row.isMeta || row.isCompactSummary)
|
|
52
54
|
return;
|
|
@@ -57,12 +59,21 @@ function promptText(row) {
|
|
|
57
59
|
return;
|
|
58
60
|
return content.find((b) => b.type === "text")?.text;
|
|
59
61
|
}
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
function firstTextContent(row) {
|
|
63
|
+
const content = row.message?.content;
|
|
64
|
+
if (typeof content === "string")
|
|
65
|
+
return content;
|
|
66
|
+
if (!Array.isArray(content))
|
|
67
|
+
return;
|
|
68
|
+
return content.find((b) => b.type === "text")?.text;
|
|
69
|
+
}
|
|
70
|
+
function skillInvocationPrompt(text) {
|
|
71
|
+
const name = /^\s*<skill name="([^"]+)"/.exec(text)?.[1];
|
|
72
|
+
if (name === undefined)
|
|
73
|
+
return;
|
|
74
|
+
const close = text.lastIndexOf("</skill>");
|
|
75
|
+
const args = close < 0 ? "" : text.slice(close + "</skill>".length).trim();
|
|
76
|
+
return args === "" ? `/${name}` : `/${name} ${args}`;
|
|
66
77
|
}
|
|
67
78
|
function sumTokens(all) {
|
|
68
79
|
const total = zeroTokens();
|
|
@@ -212,15 +223,7 @@ function buildTimeline(lines, group) {
|
|
|
212
223
|
const open = [];
|
|
213
224
|
const { rows, skippedLines, format } = parseRows(lines);
|
|
214
225
|
const isPi = format === "pi";
|
|
215
|
-
let injectedPrompts = 0;
|
|
216
226
|
for (const [row, ts] of rows) {
|
|
217
|
-
if (isPi && isInjectedPrompt(row)) {
|
|
218
|
-
injectedPrompts++;
|
|
219
|
-
const injectedSeg = open.at(-1);
|
|
220
|
-
if (injectedSeg)
|
|
221
|
-
accumulate(injectedSeg, row, ts);
|
|
222
|
-
continue;
|
|
223
|
-
}
|
|
224
227
|
const prompt = promptText(row);
|
|
225
228
|
if (prompt !== undefined) {
|
|
226
229
|
open.push({ start: ts, prompt, ...newAcc(ts) });
|
|
@@ -253,8 +256,7 @@ function buildTimeline(lines, group) {
|
|
|
253
256
|
available: true,
|
|
254
257
|
segments,
|
|
255
258
|
totals: render(isPi ? { ...totals, compactions: segments.reduce((n, s) => n + (s.compactions ?? 0), 0) } : totals),
|
|
256
|
-
skippedLines
|
|
257
|
-
...isPi ? { injectedPrompts } : {}
|
|
259
|
+
skippedLines
|
|
258
260
|
};
|
|
259
261
|
if (group) {
|
|
260
262
|
timeline.stages = parseGroups(group, segments.length).map(([a, b]) => render({ ...sumSpans(segments.slice(a - 1, b)), segments: a === b ? `${a}` : `${a}-${b}` }));
|
|
@@ -273,8 +275,7 @@ function zeroSegmentReason(rows) {
|
|
|
273
275
|
const census = [...counts.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, 5).map(([kind, n]) => `${kind} ${n}`).join(", ");
|
|
274
276
|
return `unrecognized transcript format (row types: ${census})`;
|
|
275
277
|
}
|
|
276
|
-
|
|
277
|
-
return `${format} transcript with no operator prompts (${rows.length} rows, ${injected} injected)`;
|
|
278
|
+
return `${format} transcript with no operator prompts (${rows.length} rows)`;
|
|
278
279
|
}
|
|
279
280
|
function main(argv, env = getEnvVars(), write = (s) => process.stdout.write(s), projectsRoot, writeErr = (s) => process.stderr.write(s)) {
|
|
280
281
|
let transcript;
|
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
accumulate,
|
|
17
17
|
formatDuration,
|
|
18
18
|
formatTokenSplit,
|
|
19
|
-
isInjectedPrompt,
|
|
20
19
|
newAcc,
|
|
21
20
|
parseRows,
|
|
22
21
|
promptText,
|
|
@@ -40,8 +39,6 @@ export interface Timeline {
|
|
|
40
39
|
stages?: Rendered<Span & { segments: string }>[];
|
|
41
40
|
totals: Rendered<Span & { elapsedMs: number; elapsed: string; compactions?: number }>;
|
|
42
41
|
skippedLines: number;
|
|
43
|
-
/** pi only (1138 R2): skill-injected user bodies counted as activity, never as segments. */
|
|
44
|
-
injectedPrompts?: number;
|
|
45
42
|
}
|
|
46
43
|
|
|
47
44
|
const PROMPT_EXCERPT = 80;
|
|
@@ -75,17 +72,8 @@ export function buildTimeline(lines: string[], group?: string): Timeline {
|
|
|
75
72
|
const open: Open[] = [];
|
|
76
73
|
const { rows, skippedLines, format } = parseRows(lines);
|
|
77
74
|
const isPi = format === 'pi';
|
|
78
|
-
let injectedPrompts = 0;
|
|
79
75
|
|
|
80
76
|
for (const [row, ts] of rows) {
|
|
81
|
-
// 1138 R2: an injected skill body is activity, not a prompt — accumulate it into the open
|
|
82
|
-
// segment (its tools and usage are real work) and count it, never open a segment.
|
|
83
|
-
if (isPi && isInjectedPrompt(row)) {
|
|
84
|
-
injectedPrompts++;
|
|
85
|
-
const injectedSeg = open.at(-1);
|
|
86
|
-
if (injectedSeg) accumulate(injectedSeg, row, ts);
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
77
|
const prompt = promptText(row);
|
|
90
78
|
if (prompt !== undefined) {
|
|
91
79
|
open.push({ start: ts, prompt, ...newAcc(ts) });
|
|
@@ -124,8 +112,6 @@ export function buildTimeline(lines: string[], group?: string): Timeline {
|
|
|
124
112
|
isPi ? { ...totals, compactions: segments.reduce((n, s) => n + (s.compactions ?? 0), 0) } : totals,
|
|
125
113
|
),
|
|
126
114
|
skippedLines,
|
|
127
|
-
// Emitted only for pi, as `compactions` is, so Claude output is unchanged (1138 R2).
|
|
128
|
-
...(isPi ? { injectedPrompts } : {}),
|
|
129
115
|
};
|
|
130
116
|
if (group) {
|
|
131
117
|
timeline.stages = parseGroups(group, segments.length).map(([a, b]) =>
|
|
@@ -139,8 +125,9 @@ export const SESSION_TIMELINE_USAGE = 'usage: session-timeline [--transcript <pa
|
|
|
139
125
|
|
|
140
126
|
/**
|
|
141
127
|
* 1138 R3: a zero-segment result must say WHAT was detected, so an unparseable-but-recognised
|
|
142
|
-
* transcript can never read as an authoritative all-zero timeline. A known format names the
|
|
143
|
-
*
|
|
128
|
+
* transcript can never read as an authoritative all-zero timeline. A known format names the row
|
|
129
|
+
* count; an unknown one lists the top-5 row-type census. (1151 R2 removed the pi-only injected
|
|
130
|
+
* count: pi injects no skill bodies, so a wrapper row is an operator prompt and the class is gone.)
|
|
144
131
|
*/
|
|
145
132
|
export function zeroSegmentReason(rows: [Row, number][]): string {
|
|
146
133
|
const format = sniffFormat(rows.map(([r]) => r));
|
|
@@ -157,8 +144,7 @@ export function zeroSegmentReason(rows: [Row, number][]): string {
|
|
|
157
144
|
.join(', ');
|
|
158
145
|
return `unrecognized transcript format (row types: ${census})`;
|
|
159
146
|
}
|
|
160
|
-
|
|
161
|
-
return `${format} transcript with no operator prompts (${rows.length} rows, ${injected} injected)`;
|
|
147
|
+
return `${format} transcript with no operator prompts (${rows.length} rows)`;
|
|
162
148
|
}
|
|
163
149
|
|
|
164
150
|
export function main(
|
|
@@ -16,7 +16,7 @@ var SENSITIVE_PREFIXES = ["drizzle/", "packages/config/", ".github/", "plugins/s
|
|
|
16
16
|
var SENSITIVE_GLOBS = ["apps/server/src/**/auth*", "**/*secret*", "**/*.sql"];
|
|
17
17
|
function globToRegex(glob) {
|
|
18
18
|
const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replaceAll("**/", "\x00").replaceAll("**", "\x01").replaceAll("*", "[^/]*").replaceAll("\x00", "(?:.*/)?").replaceAll("\x01", ".*");
|
|
19
|
-
return new RegExp(`^${escaped}
|
|
19
|
+
return new RegExp(`^${escaped}$`, "s");
|
|
20
20
|
}
|
|
21
21
|
var SENSITIVE_MATCHERS = [
|
|
22
22
|
...SENSITIVE_PREFIXES.map((prefix) => ({
|
|
@@ -52,22 +52,12 @@ function countNewlines(path, cwd) {
|
|
|
52
52
|
lines += 1;
|
|
53
53
|
return lines;
|
|
54
54
|
}
|
|
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)];
|
|
55
|
+
function addCounts(add, del) {
|
|
56
|
+
for (const count of [add, del])
|
|
57
|
+
if (count !== "-" && !/^\d+$/.test(count))
|
|
58
|
+
return null;
|
|
59
|
+
const [a, d] = [add === "-" ? 0 : Number(add), del === "-" ? 0 : Number(del)];
|
|
60
|
+
return Number.isSafeInteger(a) && Number.isSafeInteger(d) ? [a, d] : null;
|
|
71
61
|
}
|
|
72
62
|
function runDiffstat(env, options = {}, git = defaultGitRunner(options.cwd)) {
|
|
73
63
|
const cwd = options.cwd;
|
|
@@ -99,23 +89,37 @@ function runDiffstat(env, options = {}, git = defaultGitRunner(options.cwd)) {
|
|
|
99
89
|
} catch {}
|
|
100
90
|
if (!/^[0-9a-f]{7,40}$/i.test(base))
|
|
101
91
|
return failSafe("run base .spur/run/<wbs>-base.sha missing or malformed");
|
|
102
|
-
const numstat = git(["diff", "--numstat", base]);
|
|
92
|
+
const numstat = git(["diff", "--numstat", "-z", base]);
|
|
103
93
|
if (numstat.status !== 0)
|
|
104
94
|
return failSafe(`git diff --numstat failed (status=${numstat.status})`);
|
|
95
|
+
if (numstat.stdout.length > 0 && !numstat.stdout.endsWith("\x00"))
|
|
96
|
+
return failSafe("git diff --numstat -z output is not NUL-terminated (malformed framing)");
|
|
105
97
|
const paths = new Set;
|
|
106
98
|
let insertions = 0;
|
|
107
99
|
let deletions = 0;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
100
|
+
const records = numstat.stdout.split("\x00").slice(0, -1);
|
|
101
|
+
for (let i = 0;i < records.length; i++) {
|
|
102
|
+
const header = records[i] ?? "";
|
|
103
|
+
const first = header.indexOf("\t");
|
|
104
|
+
const second = header.indexOf("\t", first + 1);
|
|
105
|
+
if (first < 1 || second < first + 2)
|
|
106
|
+
return failSafe("git diff --numstat -z record is missing counts or path");
|
|
107
|
+
const counts = addCounts(header.slice(0, first), header.slice(first + 1, second));
|
|
108
|
+
if (counts === null)
|
|
109
|
+
return failSafe("git diff --numstat -z record has a malformed count");
|
|
110
|
+
insertions += counts[0];
|
|
111
|
+
deletions += counts[1];
|
|
112
|
+
const path = header.slice(second + 1);
|
|
113
|
+
if (path !== "") {
|
|
114
|
+
paths.add(path);
|
|
112
115
|
continue;
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
}
|
|
117
|
+
const oldPath = records[++i];
|
|
118
|
+
const newPath = records[++i];
|
|
119
|
+
if (!oldPath || !newPath)
|
|
120
|
+
return failSafe("git diff --numstat -z rename record has incomplete paths");
|
|
121
|
+
paths.add(oldPath);
|
|
122
|
+
paths.add(newPath);
|
|
119
123
|
}
|
|
120
124
|
const others = git(["ls-files", "--others", "--exclude-standard", "-z"]);
|
|
121
125
|
if (others.status !== 0)
|
|
@@ -161,7 +165,6 @@ export {
|
|
|
161
165
|
sensitiveReasonForPath,
|
|
162
166
|
runDiffstat,
|
|
163
167
|
main,
|
|
164
|
-
expandDiffPath,
|
|
165
168
|
TASK_DIFFSTAT_USAGE,
|
|
166
169
|
SENSITIVE_PREFIXES,
|
|
167
170
|
SENSITIVE_GLOBS
|