@rsaglobaltech/specgate 0.22.3 → 0.24.0
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/CHANGELOG.md +60 -0
- package/dist/packages/core/src/domain/DoneVerification.js +21 -2
- package/dist/packages/core/src/domain/DoneVerification.js.map +1 -1
- package/dist/packages/core/test/unit/done-verification.test.js +18 -0
- package/dist/packages/core/test/unit/done-verification.test.js.map +1 -1
- package/dist/scripts/cli/commands/harness/RunCommand.js +50 -5
- package/dist/scripts/cli/commands/harness/RunCommand.js.map +1 -1
- package/dist/scripts/cli/commands/quality/CheckCommand.js +6 -0
- package/dist/scripts/cli/commands/quality/CheckCommand.js.map +1 -1
- package/dist/scripts/cli/commands/spec/DoneCommand.js +2 -1
- package/dist/scripts/cli/commands/spec/DoneCommand.js.map +1 -1
- package/dist/scripts/cli/commands/spec/StudioCommand.js +31 -0
- package/dist/scripts/cli/commands/spec/StudioCommand.js.map +1 -1
- package/dist/scripts/harness/events.js +57 -0
- package/dist/scripts/harness/events.js.map +1 -1
- package/dist/scripts/lib/harness-events.js +32 -1
- package/dist/scripts/lib/harness-events.js.map +1 -1
- package/dist/scripts/lib/harness-guard.js +186 -0
- package/dist/scripts/lib/harness-guard.js.map +1 -0
- package/dist/scripts/lib/in-flight.js +17 -4
- package/dist/scripts/lib/in-flight.js.map +1 -1
- package/dist/scripts/lib/live-detail.js +345 -0
- package/dist/scripts/lib/live-detail.js.map +1 -0
- package/dist/scripts/lib/live-model.js +158 -9
- package/dist/scripts/lib/live-model.js.map +1 -1
- package/dist/scripts/lib/live-page.js +599 -67
- package/dist/scripts/lib/live-page.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,66 @@ The release process is in [`docs/release-process.md`](docs/release-process.md).
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [0.24.0] — 2026-10-26
|
|
13
|
+
|
|
14
|
+
A live view you can work from, and a harness that survives a night: what a twenty-requirement chain in a Next.js app ran into.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **A requirement opens into everything the project holds about it**
|
|
19
|
+
(REQ-180): clicking a requirement in `/live` shows its statement, its
|
|
20
|
+
scenarios with their steps, the spec file and line that define it, its
|
|
21
|
+
assumptions, the tests that name it, its feature, every attempt with the
|
|
22
|
+
stage it failed at, and the files the last attempt changed, each linked to
|
|
23
|
+
the editor.
|
|
24
|
+
- **The agent's session in the live view** (REQ-182):
|
|
25
|
+
`harness events --from claude` now records the model, the context of each message against the
|
|
26
|
+
model's window, and the session's tokens, cost and turns; `harness run`
|
|
27
|
+
keeps the run's queue and each attempt's prompt beside the events. The
|
|
28
|
+
sidebar is the run's work queue (now, needs attention, up next with an
|
|
29
|
+
estimate, finished recently), the summary adds tokens and cost, and an
|
|
30
|
+
Agent tab shows the selected attempt's session and the prompt it was given.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- **The live view groups by module and keeps its header in view** (REQ-181):
|
|
35
|
+
the header and summary stay put while the page scrolls; the centre is a
|
|
36
|
+
matrix by module, one segment per requirement, queued ones included; and a
|
|
37
|
+
Traceability view shows the matrix as it stands, filtered as you type.
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- **The harness no longer fills the disk** (REQ-184): the in-flight view
|
|
42
|
+
copied each worktree whole, `.next/cache` included, until a chain stopped
|
|
43
|
+
with ENOSPC. It now copies only what git keeps, and a requirement does not
|
|
44
|
+
start with less than 2 GB free (`SPECGATE_HARNESS_MIN_FREE_GB`).
|
|
45
|
+
- **Re-running delivered requirements works in the order asked** (REQ-185):
|
|
46
|
+
`--req` order is kept, and a branch whose own work is already merged is
|
|
47
|
+
recreated without `--force`.
|
|
48
|
+
- **One harness run per working copy** (REQ-186): a lock beside the events
|
|
49
|
+
refuses a second run; `check` warns while a run holds it; a post-merge
|
|
50
|
+
failure says it ran in the working copy.
|
|
51
|
+
- **`check` fills the test command's placeholders** (REQ-183): a test command
|
|
52
|
+
with `{req}` ran with the literal text. `check` runs it for the whole
|
|
53
|
+
project, with the placeholders empty; `apply` and `done` fill `{req}` with
|
|
54
|
+
the requirement they deliver.
|
|
55
|
+
- **The live view takes the last attempt that started**, not the highest
|
|
56
|
+
attempt number, so a new run's attempt 1 is not hidden by an earlier run's
|
|
57
|
+
attempt 3.
|
|
58
|
+
|
|
59
|
+
## [0.23.0] — 2026-10-25
|
|
60
|
+
|
|
61
|
+
The live view, read like a CI run: watch the harness work through a run requirement by requirement, step by step.
|
|
62
|
+
|
|
63
|
+
### Changed
|
|
64
|
+
|
|
65
|
+
- **The live view reads like a CI run** (REQ-179): `/live` listed every
|
|
66
|
+
requirement the same way, by id alone. It now has a sidebar of the run's
|
|
67
|
+
requirements by module, a summary of the run, its requirements as a graph of
|
|
68
|
+
jobs, and the selected one's steps (Agent, each gate stage, the stages still
|
|
69
|
+
to come) with the agent's stream as the log of the step in progress. Each
|
|
70
|
+
requirement shows its title from the specification. Light and dark.
|
|
71
|
+
|
|
12
72
|
## [0.22.3] — 2026-10-25
|
|
13
73
|
|
|
14
74
|
What a first project from a brief tripped over, from statuses to the harness's first attempt: rehearsed on a Spanish brief with eight modules and a Next.js monorepo.
|
|
@@ -1,10 +1,29 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.NO_TEST_COMMAND_WARNING = void 0;
|
|
4
|
+
exports.gateTestCommand = gateTestCommand;
|
|
4
5
|
exports.planDoneVerification = planDoneVerification;
|
|
5
6
|
exports.planGateCheck = planGateCheck;
|
|
6
7
|
exports.deliveryBlocker = deliveryBlocker;
|
|
7
8
|
const TraceabilityFormat_1 = require("./TraceabilityFormat");
|
|
9
|
+
/**
|
|
10
|
+
* The test command as the gate outside the harness runs it (REQ-183): `{req}`
|
|
11
|
+
* is the requirement being delivered, or empty when the whole project is
|
|
12
|
+
* checked; `{scenario}` and `{feature_file}` are always empty, since no one
|
|
13
|
+
* scenario is meant. The harness fills them per requirement; `check` used to
|
|
14
|
+
* run them as written, and a command that named the requirement broke it.
|
|
15
|
+
*/
|
|
16
|
+
function gateTestCommand(testCmd, reqId) {
|
|
17
|
+
return testCmd
|
|
18
|
+
.split("{req}")
|
|
19
|
+
.join(reqId || "")
|
|
20
|
+
.split("{scenario}")
|
|
21
|
+
.join("")
|
|
22
|
+
.split("{feature_file}")
|
|
23
|
+
.join("")
|
|
24
|
+
.replace(/[ \t]+$/gm, "")
|
|
25
|
+
.trim();
|
|
26
|
+
}
|
|
8
27
|
/**
|
|
9
28
|
* The checks `done` should run, in the order they should run.
|
|
10
29
|
*
|
|
@@ -33,7 +52,7 @@ function planDoneVerification(projectDir, opts) {
|
|
|
33
52
|
const archCmd = (opts.archCmd || "").trim();
|
|
34
53
|
if (archCmd !== "")
|
|
35
54
|
steps.push({ stage: "arch", argv: [archCmd] });
|
|
36
|
-
const testCmd = (opts.testCmd || "").trim();
|
|
55
|
+
const testCmd = gateTestCommand((opts.testCmd || "").trim(), opts.reqId);
|
|
37
56
|
if (testCmd !== "")
|
|
38
57
|
steps.push({ stage: "tests", argv: [testCmd] });
|
|
39
58
|
return { steps, testsUnverified: testCmd === "" };
|
|
@@ -51,7 +70,7 @@ function planGateCheck(projectDir, testCmd, archCmd) {
|
|
|
51
70
|
const arch = (archCmd || "").trim();
|
|
52
71
|
if (arch !== "")
|
|
53
72
|
steps.push({ stage: "arch", argv: [arch] });
|
|
54
|
-
const cmd = (testCmd || "").trim();
|
|
73
|
+
const cmd = gateTestCommand((testCmd || "").trim());
|
|
55
74
|
if (cmd !== "")
|
|
56
75
|
steps.push({ stage: "tests", argv: [cmd] });
|
|
57
76
|
return { steps, testsUnverified: cmd === "" };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DoneVerification.js","sourceRoot":"","sources":["../../../../../packages/core/src/domain/DoneVerification.ts"],"names":[],"mappings":";;;AA2EA,oDA6BC;AAUD,sCAWC;AA8BD,0CAcC;
|
|
1
|
+
{"version":3,"file":"DoneVerification.js","sourceRoot":"","sources":["../../../../../packages/core/src/domain/DoneVerification.ts"],"names":[],"mappings":"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"}
|
|
@@ -84,4 +84,22 @@ const DoneVerification_1 = require("../../src/domain/DoneVerification");
|
|
|
84
84
|
assert.match(DoneVerification_1.NO_TEST_COMMAND_WARNING.fix.join(" "), /`apply` and `check`/);
|
|
85
85
|
assert.doesNotMatch(DoneVerification_1.NO_TEST_COMMAND_WARNING.fix.join(" "), /`done`/);
|
|
86
86
|
});
|
|
87
|
+
// REQ-183 — the gate's placeholders outside the harness. `check` ran
|
|
88
|
+
// `node check.mjs {req}` with the literal text `{req}`, so a test command that
|
|
89
|
+
// names the requirement broke every `check` (fuhrerschein, S1).
|
|
90
|
+
(0, node_test_1.test)("REQ-183 SCN-183a: check runs the test command for the whole project, its placeholders empty", () => {
|
|
91
|
+
const plan = (0, DoneVerification_1.planGateCheck)("/p", "npm test && node e2e-names.mjs {req} {scenario} {feature_file}");
|
|
92
|
+
const tests = plan.steps.find((s) => s.stage === "tests");
|
|
93
|
+
assert.equal(tests?.argv[0], "npm test && node e2e-names.mjs");
|
|
94
|
+
});
|
|
95
|
+
(0, node_test_1.test)("REQ-183 SCN-183b: done and apply run it for the requirement they deliver", () => {
|
|
96
|
+
const plan = (0, DoneVerification_1.planDoneVerification)("/p", {
|
|
97
|
+
check: true,
|
|
98
|
+
strict: false,
|
|
99
|
+
reqId: "REQ-007",
|
|
100
|
+
testCmd: "npm test && node e2e-names.mjs {req}",
|
|
101
|
+
});
|
|
102
|
+
const tests = plan.steps.find((s) => s.stage === "tests");
|
|
103
|
+
assert.equal(tests?.argv[0], "npm test && node e2e-names.mjs REQ-007");
|
|
104
|
+
});
|
|
87
105
|
//# sourceMappingURL=done-verification.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"done-verification.test.js","sourceRoot":"","sources":["../../../../../packages/core/test/unit/done-verification.test.ts"],"names":[],"mappings":";AAAA;;GAEG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEH,yCAAiC;AACjC,2DAA6C;AAE7C,
|
|
1
|
+
{"version":3,"file":"done-verification.test.js","sourceRoot":"","sources":["../../../../../packages/core/test/unit/done-verification.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -81,6 +81,7 @@ const prompt_1 = require("../../../harness/prompt");
|
|
|
81
81
|
const init_1 = require("../../../harness/init");
|
|
82
82
|
const in_flight_1 = require("../../../lib/in-flight");
|
|
83
83
|
const harness_events_1 = require("../../../lib/harness-events");
|
|
84
|
+
const harness_guard_1 = require("../../../lib/harness-guard");
|
|
84
85
|
const HarnessEvents_1 = require("../../../../packages/core/src/domain/HarnessEvents");
|
|
85
86
|
/**
|
|
86
87
|
* `read` against the project as it will be after its open changes are
|
|
@@ -990,6 +991,14 @@ function attemptRequirement(req, ctx) {
|
|
|
990
991
|
const promptFile = path.join(os.tmpdir(), `specgate-harness-prompt-${req.requirement}-${crypto.randomBytes(4).toString("hex")}.md`);
|
|
991
992
|
fs.writeFileSync(promptFile, prompt, "utf8");
|
|
992
993
|
archivePrompt(worktreeDir, req.requirement, attempt, step, prompt);
|
|
994
|
+
// The live view shows what the agent was told and how big it was (REQ-182).
|
|
995
|
+
(0, harness_events_1.keepPrompt)(ctx.projectDir || worktreeDir, req.requirement, attempt, prompt);
|
|
996
|
+
event({
|
|
997
|
+
type: "prompt",
|
|
998
|
+
role: step.profile || (step.advisory ? "reviewer" : "agent"),
|
|
999
|
+
chars: prompt.length,
|
|
1000
|
+
lines: prompt.split("\n").length,
|
|
1001
|
+
});
|
|
993
1002
|
// D1: a profile chosen by matching this requirement, when one matches.
|
|
994
1003
|
// The role ladder still wins — `attempt_profiles` names a profile for
|
|
995
1004
|
// *this step*, and a step that says which role it is has already
|
|
@@ -1667,15 +1676,32 @@ function processRequirement(req, ctx) {
|
|
|
1667
1676
|
};
|
|
1668
1677
|
}
|
|
1669
1678
|
const worktreeDir = path.join(os.tmpdir(), `specgate-harness-${req.requirement}-${crypto.randomBytes(4).toString("hex")}`);
|
|
1679
|
+
// A full disk mid-copy ended a night's chain with ENOSPC (REQ-184): stop
|
|
1680
|
+
// before starting, with the reason, instead.
|
|
1681
|
+
const lowDisk = (0, harness_guard_1.lowDiskMessage)((0, harness_guard_1.freeBytes)(os.tmpdir()), (0, harness_guard_1.minFreeBytes)(), os.tmpdir());
|
|
1682
|
+
if (lowDisk) {
|
|
1683
|
+
return {
|
|
1684
|
+
requirement: req.requirement,
|
|
1685
|
+
category: req.category,
|
|
1686
|
+
result: "fail",
|
|
1687
|
+
attempts: 0,
|
|
1688
|
+
branch,
|
|
1689
|
+
error: `Not started: ${lowDisk}`,
|
|
1690
|
+
};
|
|
1691
|
+
}
|
|
1670
1692
|
// Three ways to meet an existing branch, and until C3 there were only two:
|
|
1671
1693
|
// skip it, or delete it with --force. After an interruption — a crash, a
|
|
1672
1694
|
// Ctrl-C, a spend limit — neither is what anyone wants.
|
|
1673
1695
|
let resuming = false;
|
|
1674
|
-
|
|
1675
|
-
|
|
1696
|
+
const exists = branchExists(projectDir, branch);
|
|
1697
|
+
// A branch already merged into the base holds nothing to lose (REQ-185).
|
|
1698
|
+
const merged = exists && (0, harness_guard_1.branchMergedInto)(projectDir, branch, baseRef);
|
|
1699
|
+
const action = (0, harness_guard_1.existingBranchAction)({ exists, merged, resume: Boolean(ctx.resume), force });
|
|
1700
|
+
if (exists) {
|
|
1701
|
+
if (action === "resume") {
|
|
1676
1702
|
resuming = true;
|
|
1677
1703
|
}
|
|
1678
|
-
else if (
|
|
1704
|
+
else if (action === "skip") {
|
|
1679
1705
|
return {
|
|
1680
1706
|
requirement: req.requirement,
|
|
1681
1707
|
category: req.category,
|
|
@@ -1687,6 +1713,8 @@ function processRequirement(req, ctx) {
|
|
|
1687
1713
|
};
|
|
1688
1714
|
}
|
|
1689
1715
|
else {
|
|
1716
|
+
if (merged && !force)
|
|
1717
|
+
info(`${req.requirement}: ${branch} is already merged — recreating it`);
|
|
1690
1718
|
git(projectDir, ["branch", "-D", branch]);
|
|
1691
1719
|
}
|
|
1692
1720
|
}
|
|
@@ -2047,7 +2075,9 @@ async function runChain(pending, ctx, hintByReq) {
|
|
|
2047
2075
|
result: "fail",
|
|
2048
2076
|
error: `post-merge gate: ${gate.stage}\n${String(gate.output || "").slice(-4000)}`,
|
|
2049
2077
|
});
|
|
2050
|
-
warn(`${req.requirement}: green on ${branch}, red once merged — the merge was undone`
|
|
2078
|
+
warn(`${req.requirement}: green on ${branch}, red once merged — the merge was undone. ` +
|
|
2079
|
+
`This gate runs in the working copy: tests or a server started there by hand ` +
|
|
2080
|
+
`while the harness runs can break it.`);
|
|
2051
2081
|
stop(`the chain stopped at ${req.requirement}`);
|
|
2052
2082
|
break;
|
|
2053
2083
|
}
|
|
@@ -2393,7 +2423,8 @@ class RunCommand extends command_1.BaseCommand {
|
|
|
2393
2423
|
});
|
|
2394
2424
|
if (args.reqs.length > 0) {
|
|
2395
2425
|
const wanted = new Set(args.reqs);
|
|
2396
|
-
|
|
2426
|
+
// In the order given (REQ-185): the plan's order reshuffled a chain silently.
|
|
2427
|
+
pending = (0, harness_guard_1.orderAsGiven)(pending.filter((r) => wanted.has(r.requirement)), args.reqs);
|
|
2397
2428
|
const found = new Set(pending.map((r) => r.requirement));
|
|
2398
2429
|
for (const want of args.reqs) {
|
|
2399
2430
|
if (!found.has(want))
|
|
@@ -2497,6 +2528,20 @@ class RunCommand extends command_1.BaseCommand {
|
|
|
2497
2528
|
strictArtifacts: args.strictArtifacts,
|
|
2498
2529
|
verbose: args.verbose,
|
|
2499
2530
|
};
|
|
2531
|
+
// One run per working copy (REQ-186): the post-merge gate runs in it.
|
|
2532
|
+
if (!process.env.SPECGATE_HARNESS_WORKER) {
|
|
2533
|
+
const release = (0, harness_guard_1.acquireRunLock)(projectDir, pending.map((r) => r.requirement));
|
|
2534
|
+
if (!release) {
|
|
2535
|
+
const holder = (0, harness_guard_1.runLockHolder)(projectDir);
|
|
2536
|
+
throw new Error(`Another harness run (pid ${holder ? holder.pid : "?"}) is working in this project` +
|
|
2537
|
+
(holder ? ` on ${holder.reqs.join(", ")}` : "") +
|
|
2538
|
+
`. Wait for it to finish: its post-merge gate runs in this working copy.`);
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
// What the live view lists as up next (REQ-182).
|
|
2542
|
+
if (!process.env.SPECGATE_HARNESS_WORKER) {
|
|
2543
|
+
(0, harness_events_1.writeQueue)(projectDir, pending.map((r) => r.requirement));
|
|
2544
|
+
}
|
|
2500
2545
|
const startedAt = new Date().toISOString();
|
|
2501
2546
|
const results = args.chain
|
|
2502
2547
|
? await runChain(pending, ctx, hintByReq)
|