@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
|
@@ -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();
|
|
@@ -24449,6 +24437,7 @@ function isRfc3339Timestamp(value) {
|
|
|
24449
24437
|
var ALL_FINDING_CODES = [
|
|
24450
24438
|
"L1.markdown-parse",
|
|
24451
24439
|
"L1.schema-validation",
|
|
24440
|
+
"L1.wbs-collision",
|
|
24452
24441
|
"L2.missing-required-section",
|
|
24453
24442
|
"L2.forbidden-section",
|
|
24454
24443
|
"L2.disallowed-section",
|
|
@@ -24476,6 +24465,7 @@ var ALL_FINDING_CODES = [
|
|
|
24476
24465
|
"L4.design-placeholder",
|
|
24477
24466
|
"L4.feature-not-found",
|
|
24478
24467
|
"L4.feature-terminal",
|
|
24468
|
+
"L4.feature-deprecated",
|
|
24479
24469
|
"L4.missing-feature-id",
|
|
24480
24470
|
"L4.parent-not-found",
|
|
24481
24471
|
"L4.dependency-not-found",
|
|
@@ -24913,7 +24903,7 @@ var POST_WATERMARK_TABLES = [
|
|
|
24913
24903
|
"history_daily_stats",
|
|
24914
24904
|
"history_board_source_daily"
|
|
24915
24905
|
];
|
|
24916
|
-
var HISTORY_IMPORT_SCHEMA_VERSION = "0.5.
|
|
24906
|
+
var HISTORY_IMPORT_SCHEMA_VERSION = "0.5.24";
|
|
24917
24907
|
var HISTORY_IMPORT_SCHEMA_SQL = `
|
|
24918
24908
|
CREATE TABLE IF NOT EXISTS history_import_checkpoint (
|
|
24919
24909
|
source TEXT NOT NULL,
|
|
@@ -27632,6 +27622,8 @@ var artifactsTable = defineTable("artifacts", {
|
|
|
27632
27622
|
...standardColumns
|
|
27633
27623
|
});
|
|
27634
27624
|
var artifacts = artifactsTable.table;
|
|
27625
|
+
var encoder = new TextEncoder;
|
|
27626
|
+
var decoder = new TextDecoder;
|
|
27635
27627
|
init_schema();
|
|
27636
27628
|
var phaseRunsTable = defineTable("phase_runs", {
|
|
27637
27629
|
id: text("id").primaryKey(),
|
|
@@ -27867,6 +27859,9 @@ var INBOX_MESSAGES_REQUEST_KEY_SCHEMA_SQL = `
|
|
|
27867
27859
|
ALTER TABLE inbox_messages ADD COLUMN request_key TEXT;
|
|
27868
27860
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_inbox_messages_request_key ON inbox_messages (request_key) WHERE request_key IS NOT NULL;
|
|
27869
27861
|
`;
|
|
27862
|
+
var INBOX_MESSAGES_READ_AT_SCHEMA_SQL = `
|
|
27863
|
+
ALTER TABLE inbox_messages ADD COLUMN read_at INTEGER;
|
|
27864
|
+
`;
|
|
27870
27865
|
var QUEUE_JOBS_SCHEMA_SQL = `
|
|
27871
27866
|
CREATE TABLE IF NOT EXISTS queue_jobs (
|
|
27872
27867
|
id TEXT PRIMARY KEY,
|
|
@@ -28879,6 +28874,11 @@ var CLI_MIGRATIONS = [
|
|
|
28879
28874
|
{
|
|
28880
28875
|
id: "0052_spur_cli_decision_logs",
|
|
28881
28876
|
sql: DECISION_LOGS_SCHEMA_SQL
|
|
28877
|
+
},
|
|
28878
|
+
{
|
|
28879
|
+
id: "0053_spur_cli_inbox_messages_read_at",
|
|
28880
|
+
sql: INBOX_MESSAGES_READ_AT_SCHEMA_SQL,
|
|
28881
|
+
addColumnIfMissing: { table: "inbox_messages", column: "read_at" }
|
|
28882
28882
|
}
|
|
28883
28883
|
];
|
|
28884
28884
|
init_zod();
|
|
@@ -29824,7 +29824,15 @@ function escapeYamlValue(value) {
|
|
|
29824
29824
|
}
|
|
29825
29825
|
init_zod();
|
|
29826
29826
|
var TASK_STATUSES = ["backlog", "todo", "wip", "testing", "blocked", "done", "cancelled"];
|
|
29827
|
-
var FEATURE_STATUSES = [
|
|
29827
|
+
var FEATURE_STATUSES = [
|
|
29828
|
+
"backlog",
|
|
29829
|
+
"active",
|
|
29830
|
+
"verifying",
|
|
29831
|
+
"blocked",
|
|
29832
|
+
"done",
|
|
29833
|
+
"cancelled",
|
|
29834
|
+
"deprecated"
|
|
29835
|
+
];
|
|
29828
29836
|
var PRIORITIES = ["P0", "P1", "P2", "P3"];
|
|
29829
29837
|
var PROFILES = [
|
|
29830
29838
|
"simple",
|
|
@@ -30367,159 +30375,6 @@ function runShellCommand(cmd, cwd, extraEnv) {
|
|
|
30367
30375
|
rmSync(dir, { recursive: true, force: true });
|
|
30368
30376
|
}
|
|
30369
30377
|
}
|
|
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
30378
|
function recomputeGateProofFingerprint(cwd, env2) {
|
|
30524
30379
|
const taskSpecPath = env2.taskSpecPath ?? env2.taskFile;
|
|
30525
30380
|
if (!taskSpecPath || taskSpecPath.trim() === "") {
|
|
@@ -30643,44 +30498,27 @@ function runQualityGate(mode, env2, options = {}) {
|
|
|
30643
30498
|
appendFileSync(abs(logFile), probe.output);
|
|
30644
30499
|
rmSync(abs(`${logFile}.probe`), { force: true });
|
|
30645
30500
|
}
|
|
30646
|
-
let queueWaitMs = 0;
|
|
30647
30501
|
let gateRuntimeMs = 0;
|
|
30648
30502
|
if (gateRc === 0) {
|
|
30649
30503
|
const delayMs = retryDelayMs(env2);
|
|
30650
|
-
const
|
|
30504
|
+
const gateRuntimeStartMs = Date.now();
|
|
30505
|
+
for (gateAttempt = 1;gateAttempt <= MAX_GATE_ATTEMPTS; gateAttempt++) {
|
|
30506
|
+
const attemptLogPath = `${logFile}.attempt-${gateAttempt}`;
|
|
30507
|
+
const attempt = runShellCommand(env2.qualityGateCmd ?? "", cwd);
|
|
30508
|
+
writeFileSync(abs(attemptLogPath), attempt.output);
|
|
30509
|
+
const locked = isTransientLock(attempt.output);
|
|
30510
|
+
appendFileSync(abs(logFile), attempt.output);
|
|
30511
|
+
rmSync(abs(attemptLogPath), { force: true });
|
|
30512
|
+
gateRc = attempt.code;
|
|
30513
|
+
if (gateRc === 0 || !locked || gateAttempt >= MAX_GATE_ATTEMPTS)
|
|
30514
|
+
break;
|
|
30515
|
+
const line = retryMessage(gateAttempt);
|
|
30651
30516
|
process.stdout.write(line);
|
|
30652
30517
|
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();
|
|
30518
|
+
gateSleep(delayMs);
|
|
30681
30519
|
}
|
|
30682
30520
|
gateRuntimeMs = Date.now() - gateRuntimeStartMs;
|
|
30683
|
-
appendFileSync(abs(logFile), `
|
|
30521
|
+
appendFileSync(abs(logFile), `gate runtime: ${gateRuntimeMs}ms
|
|
30684
30522
|
`);
|
|
30685
30523
|
}
|
|
30686
30524
|
if (gateRc !== 0) {
|
|
@@ -30733,7 +30571,6 @@ function runQualityGate(mode, env2, options = {}) {
|
|
|
30733
30571
|
}
|
|
30734
30572
|
],
|
|
30735
30573
|
completedAt: new Date().toISOString(),
|
|
30736
|
-
queueWaitMs,
|
|
30737
30574
|
gateRuntimeMs
|
|
30738
30575
|
});
|
|
30739
30576
|
writeFileSync(abs(receiptFile), `${JSON.stringify(receipt, null, 2)}
|
|
@@ -30811,7 +30648,6 @@ export {
|
|
|
30811
30648
|
isTransientLock,
|
|
30812
30649
|
extractFindings,
|
|
30813
30650
|
buildReceipt,
|
|
30814
|
-
acquireGateLock,
|
|
30815
30651
|
RETRY_DELAY_MS_ENV,
|
|
30816
30652
|
RETRY_DELAY_MS_DEFAULT,
|
|
30817
30653
|
RECEIPT_SCHEMA_VERSION,
|
|
@@ -30819,12 +30655,6 @@ export {
|
|
|
30819
30655
|
MAX_GATE_ATTEMPTS,
|
|
30820
30656
|
MAX_FINDINGS,
|
|
30821
30657
|
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
30658
|
FINDINGS_PATTERN,
|
|
30829
30659
|
COVERAGE_ROW_PATTERN
|
|
30830
30660
|
};
|
|
@@ -15,6 +15,32 @@ function getEnvVars() {
|
|
|
15
15
|
// plugins/sp/lib/residual-scan.generated.mjs
|
|
16
16
|
import { join } from "node:path";
|
|
17
17
|
import { createHash } from "node:crypto";
|
|
18
|
+
var CLAIM_MARKERS = [
|
|
19
|
+
/@[a-z0-9._-]+\/[a-z0-9._-]+/i,
|
|
20
|
+
/\bnode_modules\//,
|
|
21
|
+
/\b\d+\.\d+\.\d+\b/,
|
|
22
|
+
/`[A-Za-z_$][\w.$]*\s*\([^`]*\)`/
|
|
23
|
+
];
|
|
24
|
+
var CITATION_FORMS = [
|
|
25
|
+
/`[^`\s]+:\d+(?:-\d+)?`/,
|
|
26
|
+
/`[^`]+`\s+lines?\s+\d+/i,
|
|
27
|
+
/\b[\w./-]+\.[A-Za-z0-9]+:\d+(?:-\d+)?\b/,
|
|
28
|
+
/https?:\/\/\S+/i
|
|
29
|
+
];
|
|
30
|
+
function hasClaimMarker(evidence) {
|
|
31
|
+
return CLAIM_MARKERS.some((re) => re.test(evidence));
|
|
32
|
+
}
|
|
33
|
+
function hasCitationForm(evidence) {
|
|
34
|
+
return CITATION_FORMS.some((re) => re.test(evidence));
|
|
35
|
+
}
|
|
36
|
+
var CONFIDENCE_LEVELS = ["HIGH", "MEDIUM", "LOW"];
|
|
37
|
+
function extractAnswerConfidence(text) {
|
|
38
|
+
const matches = [...text.matchAll(/^\s*Confidence:\s*(\S+)\s*$/gim)];
|
|
39
|
+
if (matches.length !== 1)
|
|
40
|
+
return;
|
|
41
|
+
const value = (matches[0]?.[1] ?? "").toUpperCase();
|
|
42
|
+
return CONFIDENCE_LEVELS.includes(value) ? value : undefined;
|
|
43
|
+
}
|
|
18
44
|
var MARKER_PATTERN = /TODO|FIXME|XXX|HACK/;
|
|
19
45
|
var PRIORITY_PATTERN = /^P[1-4]/;
|
|
20
46
|
var RANGE_PRIORITY = /^P[1-4]\s*[\u2013\u2014-]\s*P?[1-4]/;
|
|
@@ -41,11 +67,15 @@ function locationOf(locationCell, finding) {
|
|
|
41
67
|
const backtick = finding.match(/`([^`]+)`/)?.[1];
|
|
42
68
|
return backtick === undefined ? "" : normalizeAnchor(backtick);
|
|
43
69
|
}
|
|
44
|
-
function
|
|
70
|
+
function reviewBody(taskContent) {
|
|
45
71
|
const section = taskContent.split(/^### Review\b/m)[1];
|
|
46
|
-
|
|
72
|
+
return section === undefined ? null : section.split(/^### /m)[0] ?? "";
|
|
73
|
+
}
|
|
74
|
+
function parseReviewFindings(taskContent) {
|
|
75
|
+
const section = reviewBody(taskContent);
|
|
76
|
+
if (section === null)
|
|
47
77
|
return [];
|
|
48
|
-
const body = section
|
|
78
|
+
const body = section;
|
|
49
79
|
const out = [];
|
|
50
80
|
const lines = body.split(`
|
|
51
81
|
`);
|
|
@@ -158,7 +188,8 @@ function scanResiduals(inputs) {
|
|
|
158
188
|
"review-finding": true,
|
|
159
189
|
"diff-marker": base !== null,
|
|
160
190
|
"unchecked-box": true,
|
|
161
|
-
"staging-residue": true
|
|
191
|
+
"staging-residue": true,
|
|
192
|
+
"review-contract": false
|
|
162
193
|
},
|
|
163
194
|
items,
|
|
164
195
|
counts
|
|
@@ -179,8 +210,36 @@ function blockingReviewFindings(taskContent, deferrals) {
|
|
|
179
210
|
}));
|
|
180
211
|
return classify(items, [...tableDeferrals, ...deferrals]);
|
|
181
212
|
}
|
|
213
|
+
function reviewContractViolations(taskContent) {
|
|
214
|
+
const violations = [];
|
|
215
|
+
const body = reviewBody(taskContent);
|
|
216
|
+
if (body !== null && extractAnswerConfidence(body.replace(/\*\*/g, "")) === undefined) {
|
|
217
|
+
violations.push({
|
|
218
|
+
location: "task-file:Review",
|
|
219
|
+
text: "Review report carries no single valid `**Confidence:** HIGH | MEDIUM | LOW` line — " + "state the level the report can defend (HIGH: every requirement row MET, every finding " + "dispositioned, evidence cited; MEDIUM/LOW: name the gap)."
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
for (const row of parseReviewFindings(taskContent)) {
|
|
223
|
+
if (hasClaimMarker(row.text) && !hasCitationForm(row.text)) {
|
|
224
|
+
violations.push({
|
|
225
|
+
location: row.location.length > 0 ? row.location : "task-file:Review",
|
|
226
|
+
text: `${row.priority} finding asserts an external API/library/version claim without a ` + "same-cell citation (add a repo `path:line`, an external named-origin form, or a URL " + `to the Finding cell): ${row.text.slice(0, 80)}`
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return violations;
|
|
231
|
+
}
|
|
182
232
|
function buildReviewGateArtifact(wbs, taskContent, deferrals) {
|
|
183
|
-
const items =
|
|
233
|
+
const items = [
|
|
234
|
+
...blockingReviewFindings(taskContent, deferrals),
|
|
235
|
+
...reviewContractViolations(taskContent).map((v) => ({
|
|
236
|
+
id: makeItemId("review-contract", v.location, v.text),
|
|
237
|
+
category: "review-contract",
|
|
238
|
+
class: "blocking",
|
|
239
|
+
location: v.location,
|
|
240
|
+
text: v.text
|
|
241
|
+
}))
|
|
242
|
+
];
|
|
184
243
|
const blocking = items.filter((i) => i.class === "blocking");
|
|
185
244
|
const counts = { blocking: 0, deferrable: 0, advisory: 0, housekeeping: 0 };
|
|
186
245
|
for (const item of items)
|
|
@@ -192,7 +251,8 @@ function buildReviewGateArtifact(wbs, taskContent, deferrals) {
|
|
|
192
251
|
"review-finding": true,
|
|
193
252
|
"diff-marker": false,
|
|
194
253
|
"unchecked-box": false,
|
|
195
|
-
"staging-residue": false
|
|
254
|
+
"staging-residue": false,
|
|
255
|
+
"review-contract": true
|
|
196
256
|
},
|
|
197
257
|
items,
|
|
198
258
|
counts
|
|
@@ -518,6 +578,12 @@ function settleMode(opts, env, io) {
|
|
|
518
578
|
}
|
|
519
579
|
function reportMode(opts, env, io) {
|
|
520
580
|
const runDir = join2(opts.root, ".spur", "run");
|
|
581
|
+
const verdictPath = recordedVerdictPath(runDir, opts.wbs, fs);
|
|
582
|
+
if (!fs.existsSync(verdictPath)) {
|
|
583
|
+
io.err(`residual-report: no verdict at ${verdictPath}; nothing to report for ${opts.wbs}
|
|
584
|
+
`);
|
|
585
|
+
return 0;
|
|
586
|
+
}
|
|
521
587
|
const sweep = loadVerdict(runDir, opts.wbs).checks.find((c) => c.name === "residual-sweep");
|
|
522
588
|
if (sweep === undefined || sweep.status !== "fail")
|
|
523
589
|
return 0;
|
|
@@ -558,6 +624,7 @@ process.exit(main(process.argv.slice(2)));
|
|
|
558
624
|
export {
|
|
559
625
|
verdictDisagreementNote,
|
|
560
626
|
scanResiduals2 as scanResiduals,
|
|
627
|
+
reviewContractViolations,
|
|
561
628
|
renderReport,
|
|
562
629
|
recordedVerdictPath,
|
|
563
630
|
parseScanArgs,
|
|
@@ -162,11 +162,8 @@ function settleMode(opts: ParsedArgs, env: ScanEnv, io: ScanIo): number {
|
|
|
162
162
|
if (fs.existsSync(residualsPath)) prior = JSON.parse(fs.readFileSync(residualsPath, 'utf8'));
|
|
163
163
|
const deferred = scan.items.filter((i) => i.class === 'deferrable');
|
|
164
164
|
if (deferred.length > 0 && prior.followUp === undefined) {
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
// residual was deferred (2026-10-04 session, E72/1086→1087), so a deferral must not be able
|
|
168
|
-
// to block the completing feature. The source task and its feature stay named in the
|
|
169
|
-
// Background, which keeps the traceability without the lifecycle coupling.
|
|
165
|
+
// Filed UNLINKED: a feature edge failed that feature's done-gate (`L4.verifying-incomplete-tasks`,
|
|
166
|
+
// E72/1086→1087); the Background still names the source task and feature for traceability.
|
|
170
167
|
const args = ['task', 'create', `Residuals from ${wbs}`, '--skip-ready', '--json'];
|
|
171
168
|
const created = spur(env, opts.spurBin, args, opts.root);
|
|
172
169
|
let wbsNew = '';
|
|
@@ -206,6 +203,12 @@ function settleMode(opts: ParsedArgs, env: ScanEnv, io: ScanIo): number {
|
|
|
206
203
|
}
|
|
207
204
|
function reportMode(opts: ParsedArgs, env: ScanEnv, io: ScanIo): number {
|
|
208
205
|
const runDir = join(opts.root, '.spur', 'run');
|
|
206
|
+
// A fix-budget-exhausted run never reaches verify: no verdict is a no-op, not ENOENT (1178 run 1).
|
|
207
|
+
const verdictPath = core.recordedVerdictPath(runDir, opts.wbs, fs);
|
|
208
|
+
if (!fs.existsSync(verdictPath)) {
|
|
209
|
+
io.err(`residual-report: no verdict at ${verdictPath}; nothing to report for ${opts.wbs}\n`);
|
|
210
|
+
return 0;
|
|
211
|
+
}
|
|
209
212
|
const sweep = loadVerdict(runDir, opts.wbs).checks.find((c) => c.name === 'residual-sweep');
|
|
210
213
|
if (sweep === undefined || sweep.status !== 'fail') return 0;
|
|
211
214
|
const { content } = loadTask(env, opts.spurBin, opts.wbs, opts.root);
|