jules-orchestrator-kit 0.72.0 → 0.72.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/CHANGELOG.md +16 -0
- package/README.md +3 -1
- package/ROADMAP_V1.md +13 -3
- package/bin/agentctl.mjs +7 -4
- package/package.json +1 -1
- package/src/git.mjs +34 -23
- package/src/security.mjs +36 -11
- package/src/stack-detector.mjs +18 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.72.2] - 2026-09-08
|
|
9
|
+
*A test suite that writes to stderr did not write silence.*
|
|
10
|
+
|
|
11
|
+
Fixes stream preservation in child process execution, stack-detection build defaults, and CLI flag parity uncovered during Bun + TypeScript trial execution:
|
|
12
|
+
- **Child Process Stream Fidelity & Stderr Preservation (`src/git.mjs`)**: `runCmd()` used `execSync`/`execFileSync` which hardcoded `stderr: ""` on zero exit status. Test runners that print progress and summary statistics to `stderr` (notably `bun test`) had their results silenced, causing `parseCollectedTests` to report unverified test counts. `runCmd()` now invokes `spawnSync` natively, capturing both `stdout` and `stderr` streams regardless of exit status.
|
|
13
|
+
- **Conditional Build Command Resolution (`src/stack-detector.mjs`)**: Bun and Deno detectors previously assigned `buildCmd: "bun run build"` and `buildCmd: "deno task build"` unconditionally upon discovering `bunfig.toml` or `deno.json`. In pure zero-dependency projects lacking a `build` script/task, `agentctl gate` failed in the build stage. `detectPolyglotStack()` now checks `package.json` (or `deno.json`) for explicit build scripts before populating `buildCmd`.
|
|
14
|
+
- **CLI Ergonomics & Alias Parity (`bin/agentctl.mjs`)**: Added `--verify` as a first-class alias for `--verify-cmd` across `agentctl task create`, `task template`, and `task optimize`.
|
|
15
|
+
|
|
16
|
+
## [0.72.1] - 2026-09-08
|
|
17
|
+
*A diff cannot judge what it cannot see.*
|
|
18
|
+
|
|
19
|
+
Fixes staged-mode diff extraction and cross-language dead-guard detection uncovered during polyglot trial execution on Python/Make repositories:
|
|
20
|
+
- **Staged Mode Diff Fidelity (`src/git.mjs`)**: `diffText(root, base, "staged")` failed to query the git index, falling through to `git diff <base>...HEAD`. Consequently, staged modifications (`git add`) on feature branches were omitted from the diff passed to the secret scanner and anti-tamper guard. `diffText` now queries `git diff --cached <base>` in staged mode, ensuring cached index additions are fully inspected.
|
|
21
|
+
- **Indentation-Aware Dead Guard Tamper Detection (`src/security.mjs`)**: Extended `DEAD_GUARD_CONDITION` to recognize literal boolean and zero falsities (`if False:`, `if (false)`, `if 0:`). Replaced brace-only block tracking with indentation-aware block traversal, enabling Python, YAML, and brace-less test suites to detect assertions and failure calls shielded inside dead logic.
|
|
22
|
+
- **Assertion Coverage in Dead Conditions (`src/security.mjs`)**: `VACUOUS_ASSERTION` now detects both failure calls (`FAILURE_CALL`) and test assertions (`ASSERTION_PATTERN`) inside dead blocks.
|
|
23
|
+
|
|
8
24
|
## [0.72.0] - 2026-09-08
|
|
9
25
|
*A guard is not a policy if the diff under review can rewrite the rules.*
|
|
10
26
|
|
package/README.md
CHANGED
|
@@ -208,7 +208,7 @@ To maximize PR merge rates, dispatch tasks according to deterministic boundaries
|
|
|
208
208
|
* **Fail-Closed Security & Secret Redaction:** Evaluates explicit Deny rules before Allow rules against canonicalized, case-folded paths. Redacts high-entropy keys and base64-encoded credentials (such as Kubernetes `Secret` manifests).
|
|
209
209
|
* **Complexity & Cost Router:** Zero-dependency heuristic classifier (`src/router.mjs`) routing mechanical tasks to lightweight models while reserving primary models for complex refactors, with a `node --check` syntax-verification gate that transparently escalates a FAST-tier result to the primary provider if it left broken JS on disk.
|
|
210
210
|
* **Terminal UI & Diagnostic Matrix (`agentctl doctor`):** Interactive terminal dashboard, task sidecar manager, and automated transactional self-repair.
|
|
211
|
-
* **Verified Test Suite:** Tested with **
|
|
211
|
+
* **Verified Test Suite:** Tested with **1403 unit tests across 196 suites**, green on every supported platform.
|
|
212
212
|
|
|
213
213
|
<br/>
|
|
214
214
|
|
|
@@ -448,6 +448,8 @@ const result = await fast.dispatch({ prompt: "Fix a typo." }, { root: process.cw
|
|
|
448
448
|
|
|
449
449
|
| Feature | Module / Command | Architectural Description | Status |
|
|
450
450
|
| :--- | :--- | :--- | :---: |
|
|
451
|
+
| **Child Streams & Polyglot Build Detection** | `src/git.mjs`, `src/stack-detector.mjs`, `bin/agentctl.mjs` | Native `spawnSync` execution in `runCmd()` preserving stderr stream on status 0 (supporting Bun test output), conditional `buildCmd` resolution for Bun/Deno scripts, and `--verify` alias parity. | **v0.72.2** *(Shipped)* |
|
|
452
|
+
| **Staged Diff Fidelity & Indentation Dead Guards** | `src/git.mjs`, `src/security.mjs` | Query cached index in staged mode (`git diff --cached <base>`), detect literal falsity dead guards (`if False:`, `if (false)`, `if 0:`), and support indentation-aware block traversal for Python test suites. | **v0.72.1** *(Shipped)* |
|
|
451
453
|
| **Cold-Start Hardened Kernel & Tamper Defense** | `src/config.mjs`, `src/engine.mjs`, `src/git.mjs`, `src/security.mjs` | Full remediation of 22 cold-start audit findings (F01–F22): authoritative base policy resolution, ephemeral snapshot worktree isolation, canonical root test tamper guard, conditional assertion defense, multi-target Cargo test aggregation, Python src-layout injection, and complete repository uninstall documentation. | **v0.72.0** *(Shipped)* |
|
|
452
454
|
| **Silence Is Not A Suite & Scaffolding Linter Fixes** | `src/ops/test-collection.mjs`, `src/wizard-init.mjs`, `src/config.mjs` | Reject zero-output test suite commands, quote-aware YAML parser with scalar emission, test de-registration detection (`TEST_DEREGISTERED`), and active waiver telemetry banner. | **v0.71.0** *(Shipped)* |
|
|
453
455
|
| **Diagnostics That Reach the Operator** | `src/security.mjs`, `src/engine.mjs`, `bin/agentctl.mjs` | Secret findings name the file and line, a failed verify stage reports its command, exit code and output, and `queue`/`swarm` name each failed task and exit `1` rather than reporting success for a run that dispatched nothing. | **v0.41.1** *(Shipped)* |
|
package/ROADMAP_V1.md
CHANGED
|
@@ -11,14 +11,24 @@ The **jules-orchestrator-kit** is the zero-dependency safety gatekeeper and self
|
|
|
11
11
|
## 📌 Release Milestones Overview
|
|
12
12
|
|
|
13
13
|
```
|
|
14
|
-
v0.72.
|
|
15
|
-
(
|
|
14
|
+
v0.72.2 (Current Stable) ──► v0.73.0 (Distributed Swarms & Leases) ──► v1.0.0 (Production Hardened Kernel)
|
|
15
|
+
(Child Streams & Bun/Deno) (Multi-Agent DAG & Resource Locks) (Enterprise Telemetry & SLA)
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
---
|
|
19
19
|
|
|
20
|
-
## ✅ Shipped Milestones (v0.20.0 – v0.72.
|
|
20
|
+
## ✅ Shipped Milestones (v0.20.0 – v0.72.2)
|
|
21
21
|
|
|
22
|
+
### v0.72.2: Child Process Stream Fidelity & Polyglot Build Detection
|
|
23
|
+
- [x] **Child Process Stream Fidelity (`src/git.mjs`)** — `runCmd()` invokes native `spawnSync`, preserving both `stdout` and `stderr` streams on exit 0 so test runners emitting summaries to stderr (`bun test`) are fully recognized by `parseCollectedTests`.
|
|
24
|
+
- [x] **Conditional Polyglot Build Resolution (`src/stack-detector.mjs`)** — Bun and Deno projects without declared build scripts default to `buildCmd: ""`, preventing `agentctl gate` false reds during pure script verification.
|
|
25
|
+
- [x] **CLI Flag Parity (`bin/agentctl.mjs`)** — `--verify` supported alongside `--verify-cmd` across `agentctl task create`, `task template`, and `task optimize`.
|
|
26
|
+
|
|
27
|
+
### v0.72.1: Staged Mode Diff Fidelity & Indentation-Aware Tamper Defense
|
|
28
|
+
- [x] **Staged Mode Diff Fidelity (`src/git.mjs`)** — `diffText` in staged mode queries `git diff --cached <base>`, ensuring staged additions on feature branches are visible to secret and tamper scanners.
|
|
29
|
+
- [x] **Universal Dead Guard Detection (`src/security.mjs`)** — extended `DEAD_GUARD_CONDITION` to recognize literal falsities (`if False:`, `if (false)`, `if 0:`).
|
|
30
|
+
- [x] **Indentation-Aware Block Traversal (`src/security.mjs`)** — detects dead-guard assertions and failure calls in indentation-based languages (Python) without requiring `{}` braces.
|
|
31
|
+
- [x] **Comprehensive Guard Call Detection (`src/security.mjs`)** — `VACUOUS_ASSERTION` now covers both failure calls and test assertions.
|
|
22
32
|
|
|
23
33
|
### v0.72.0: Cold-Start Hardened Kernel & Tamper Defense
|
|
24
34
|
- [x] **Canonical Root Test Guard (F01)** — `test.js` at repository root is inside the tamper guard.
|
package/bin/agentctl.mjs
CHANGED
|
@@ -1854,6 +1854,7 @@ async function main() {
|
|
|
1854
1854
|
template: { type: "string" },
|
|
1855
1855
|
depends: { type: "string" },
|
|
1856
1856
|
"depends-on": { type: "string" },
|
|
1857
|
+
verify: { type: "string" },
|
|
1857
1858
|
"verify-cmd": { type: "string", short: "v" },
|
|
1858
1859
|
"auto-pr": { type: "boolean" },
|
|
1859
1860
|
"require-plan-approval": { type: "boolean" },
|
|
@@ -1886,7 +1887,7 @@ async function main() {
|
|
|
1886
1887
|
tier: values.tier,
|
|
1887
1888
|
template: values.template,
|
|
1888
1889
|
dependsOn: values["depends-on"] || values.depends,
|
|
1889
|
-
verifyCmd: values["verify-cmd"],
|
|
1890
|
+
verifyCmd: values["verify-cmd"] || values.verify,
|
|
1890
1891
|
autoPr: values["auto-pr"],
|
|
1891
1892
|
requirePlanApproval: values["require-plan-approval"],
|
|
1892
1893
|
repoless: values.repoless,
|
|
@@ -1916,6 +1917,7 @@ async function main() {
|
|
|
1916
1917
|
options: {
|
|
1917
1918
|
list: { type: "boolean", short: "l" },
|
|
1918
1919
|
json: { type: "boolean", short: "j" },
|
|
1920
|
+
verify: { type: "string" },
|
|
1919
1921
|
"verify-cmd": { type: "string", short: "v" },
|
|
1920
1922
|
"dry-run": { type: "boolean", short: "d" },
|
|
1921
1923
|
},
|
|
@@ -1949,7 +1951,7 @@ async function main() {
|
|
|
1949
1951
|
process.exit(1);
|
|
1950
1952
|
}
|
|
1951
1953
|
|
|
1952
|
-
const envelope = synthesizeWebEnvelope(templateName, {}, { verifyCmd: values["verify-cmd"] });
|
|
1954
|
+
const envelope = synthesizeWebEnvelope(templateName, {}, { verifyCmd: values["verify-cmd"] || values.verify });
|
|
1953
1955
|
if (values.json) {
|
|
1954
1956
|
console.log(JSON.stringify({ ok: true, ...envelope }, null, 2));
|
|
1955
1957
|
} else {
|
|
@@ -1971,6 +1973,7 @@ async function main() {
|
|
|
1971
1973
|
dir: { type: "string", short: "d" },
|
|
1972
1974
|
web: { type: "boolean", short: "w" },
|
|
1973
1975
|
json: { type: "boolean", short: "j" },
|
|
1976
|
+
verify: { type: "string" },
|
|
1974
1977
|
"verify-cmd": { type: "string", short: "v" },
|
|
1975
1978
|
"dry-run": { type: "boolean" },
|
|
1976
1979
|
},
|
|
@@ -1982,7 +1985,7 @@ async function main() {
|
|
|
1982
1985
|
const promptText = resolvePromptInput(values, positionals);
|
|
1983
1986
|
|
|
1984
1987
|
if (values.fix) {
|
|
1985
|
-
const opt = optimizeTaskPrompt(promptText, { rootDir: targetDir, verifyCmd: values["verify-cmd"], web: values.web });
|
|
1988
|
+
const opt = optimizeTaskPrompt(promptText, { rootDir: targetDir, verifyCmd: values["verify-cmd"] || values.verify, web: values.web });
|
|
1986
1989
|
if (values.json) {
|
|
1987
1990
|
console.log(JSON.stringify(opt, null, 2));
|
|
1988
1991
|
} else {
|
|
@@ -1991,7 +1994,7 @@ async function main() {
|
|
|
1991
1994
|
process.exit(0);
|
|
1992
1995
|
}
|
|
1993
1996
|
|
|
1994
|
-
const analysis = scorePromptFalsifiability(promptText, { rootDir: targetDir, verifyCmd: values["verify-cmd"] });
|
|
1997
|
+
const analysis = scorePromptFalsifiability(promptText, { rootDir: targetDir, verifyCmd: values["verify-cmd"] || values.verify });
|
|
1995
1998
|
if (values.json) {
|
|
1996
1999
|
console.log(JSON.stringify(analysis, null, 2));
|
|
1997
2000
|
} else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jules-orchestrator-kit",
|
|
3
|
-
"version": "0.72.
|
|
3
|
+
"version": "0.72.2",
|
|
4
4
|
"description": "Zero-dependency safety gatekeeper, test oracle generator, and multi-agent coordination protocol for autonomous coding agents — Google Jules, Claude Code, Codex and Gemini CLI.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
package/src/git.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { execFileSync,
|
|
1
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
2
2
|
import { readFileSync, existsSync, statSync, lstatSync, readlinkSync, mkdtempSync, rmSync, symlinkSync } from "node:fs";
|
|
3
3
|
import { join, delimiter } from "node:path";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
@@ -216,16 +216,17 @@ export function runCmd(command, opts = {}) {
|
|
|
216
216
|
}
|
|
217
217
|
|
|
218
218
|
try {
|
|
219
|
-
const
|
|
220
|
-
?
|
|
219
|
+
const ret = useShell
|
|
220
|
+
? spawnSync(shellCmd, {
|
|
221
221
|
cwd,
|
|
222
222
|
encoding: "utf-8",
|
|
223
|
+
shell: true,
|
|
223
224
|
stdio: ["ignore", "pipe", "pipe"],
|
|
224
225
|
env: childEnv,
|
|
225
226
|
timeout,
|
|
226
227
|
maxBuffer,
|
|
227
228
|
})
|
|
228
|
-
:
|
|
229
|
+
: spawnSync(winSpawn ? winSpawn.file : binary, winSpawn ? winSpawn.args : args, {
|
|
229
230
|
cwd,
|
|
230
231
|
encoding: "utf-8",
|
|
231
232
|
shell: winShim,
|
|
@@ -236,20 +237,14 @@ export function runCmd(command, opts = {}) {
|
|
|
236
237
|
maxBuffer,
|
|
237
238
|
});
|
|
238
239
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const isTimeout = err.code === "ETIMEDOUT" || (err.signal === "SIGTERM" && err.killed);
|
|
242
|
-
const isNobufs = err.code === "ENOBUFS" || (err.message && err.message.includes("maxBuffer"));
|
|
240
|
+
const isTimeout = Boolean(ret.error && (ret.error.code === "ETIMEDOUT" || (ret.signal === "SIGTERM" && ret.error.killed)));
|
|
241
|
+
const isNobufs = Boolean(ret.error && (ret.error.code === "ENOBUFS" || (ret.error.message && ret.error.message.includes("maxBuffer"))));
|
|
243
242
|
|
|
244
|
-
const status =
|
|
245
|
-
let stdout = (
|
|
246
|
-
let stderr = (
|
|
243
|
+
const status = typeof ret.status === "number" ? ret.status : (isTimeout ? 124 : 1);
|
|
244
|
+
let stdout = (ret.stdout || "").toString().trim();
|
|
245
|
+
let stderr = (ret.stderr || (ret.error && ret.error.message) || "").toString().trim();
|
|
247
246
|
|
|
248
247
|
if (isTimeout) {
|
|
249
|
-
// Node's own message for this is `spawnSync sh ETIMEDOUT`, which already
|
|
250
|
-
// contains the token the old guard tested for — so the explanation was
|
|
251
|
-
// skipped exactly when it was needed, and the user was left with five
|
|
252
|
-
// words that name neither the limit nor the way to raise it.
|
|
253
248
|
stderr =
|
|
254
249
|
`Command execution timed out after ${timeout}ms (ETIMEDOUT). ` +
|
|
255
250
|
`The command was killed, not failed: raise verify.timeout_ms in ` +
|
|
@@ -260,17 +255,31 @@ export function runCmd(command, opts = {}) {
|
|
|
260
255
|
stderr = `Command output buffer exceeded limit of ${maxBuffer} bytes (ENOBUFS)${stderr ? "\n" + stderr : ""}`;
|
|
261
256
|
}
|
|
262
257
|
|
|
263
|
-
if (
|
|
264
|
-
|
|
265
|
-
|
|
258
|
+
if (ret.error || status !== 0) {
|
|
259
|
+
if (opts.ignoreError) {
|
|
260
|
+
return { status, stdout, stderr };
|
|
261
|
+
}
|
|
266
262
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
263
|
+
if (isTimeout) {
|
|
264
|
+
throw new GateError(`Command execution timed out (ETIMEDOUT): ${useShell ? shellCmd : binary}`, { code: status });
|
|
265
|
+
}
|
|
266
|
+
if (isNobufs) {
|
|
267
|
+
throw new GateError(`Command output buffer exceeded limit (ENOBUFS): ${useShell ? shellCmd : binary}`, { code: status });
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const err = ret.error || new Error(`Command failed with status ${status}: ${useShell ? shellCmd : binary}`);
|
|
271
|
+
err.status = status;
|
|
272
|
+
err.code = status;
|
|
273
|
+
err.stdout = stdout;
|
|
274
|
+
err.stderr = stderr;
|
|
275
|
+
throw err;
|
|
272
276
|
}
|
|
273
277
|
|
|
278
|
+
return { status: 0, stdout, stderr };
|
|
279
|
+
} catch (err) {
|
|
280
|
+
if (opts.ignoreError && !(err instanceof GateError)) {
|
|
281
|
+
return { status: err.status || 1, stdout: (err.stdout || "").toString().trim(), stderr: (err.stderr || err.message || "").toString().trim() };
|
|
282
|
+
}
|
|
274
283
|
throw err;
|
|
275
284
|
}
|
|
276
285
|
}
|
|
@@ -453,6 +462,8 @@ export function diffText(root = process.cwd(), base = "main", mode = "committed"
|
|
|
453
462
|
} catch (_) {}
|
|
454
463
|
}
|
|
455
464
|
return [committed, uncommitted, untrackedDiff].filter(Boolean).join("\n");
|
|
465
|
+
} else if (mode === "staged" || mode === "index") {
|
|
466
|
+
return git(["diff", "--cached", resolvedRef], { cwd: root, raw: true });
|
|
456
467
|
}
|
|
457
468
|
return git(["diff", `${resolvedRef}...HEAD`], { cwd: root, raw: true });
|
|
458
469
|
}
|
package/src/security.mjs
CHANGED
|
@@ -2395,7 +2395,7 @@ export const TAMPER_KIND_NAMES = Object.freeze([...new Set(TAMPER_KINDS.values()
|
|
|
2395
2395
|
* branch behind real state) is not guessable and is deliberately left alone.
|
|
2396
2396
|
*/
|
|
2397
2397
|
const DEAD_GUARD_CONDITION =
|
|
2398
|
-
/\bif\b[^;{}]*\b(?:len|len\s+of|count|size|length|num\w*|total)\s*\([^)]*\)\s*(?:<\s*0|<\s*-0\b
|
|
2398
|
+
/\bif\b[^;{}]*\b(?:len|len\s+of|count|size|length|num\w*|total)\s*\([^)]*\)\s*(?:<\s*0|<\s*-0\b|<=\s*-1\b)|\bif\s+(?:False|false|0)\s*(?::|\{|$|\b)|\bif\s*\(\s*(?:False|false|0)\s*\)/;
|
|
2399
2399
|
|
|
2400
2400
|
/**
|
|
2401
2401
|
* The calls a test uses to say "this failed": the assertion's actual teeth.
|
|
@@ -2983,24 +2983,49 @@ export function checkTestTampering(diffOrText = "", options = {}) {
|
|
|
2983
2983
|
if (L.kind !== "+" || !DEAD_GUARD_CONDITION.test(L.text)) continue;
|
|
2984
2984
|
const guardLineNo = L.newNo;
|
|
2985
2985
|
const guardText = L.text;
|
|
2986
|
-
|
|
2987
|
-
let found =
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2986
|
+
const isFailureOrAssertion = (text) => FAILURE_CALL.test(text) || ASSERTION_PATTERN.test(text);
|
|
2987
|
+
let found = false;
|
|
2988
|
+
|
|
2989
|
+
if (guardText.includes("{")) {
|
|
2990
|
+
let depth = (guardText.match(/\{/g) || []).length - (guardText.match(/\}/g) || []).length;
|
|
2991
|
+
if (depth > 0 && isFailureOrAssertion(guardText.slice(guardText.indexOf("{")))) {
|
|
2992
|
+
found = true;
|
|
2993
|
+
}
|
|
2994
|
+
for (let k = i + 1; k < hunk.lines.length && depth > 0; k++) {
|
|
2995
|
+
const t = hunk.lines[k].text || "";
|
|
2996
|
+
const opens = (t.match(/\{/g) || []).length;
|
|
2997
|
+
const closes = (t.match(/\}/g) || []).length;
|
|
2998
|
+
if (depth > 0 && isFailureOrAssertion(t)) { found = true; break; }
|
|
2999
|
+
depth += opens - closes;
|
|
3000
|
+
}
|
|
3001
|
+
} else {
|
|
3002
|
+
// Indentation-based block (Python, etc.) or single-line guard without braces
|
|
3003
|
+
const colonIdx = guardText.indexOf(":");
|
|
3004
|
+
const parenIdx = guardText.lastIndexOf(")");
|
|
3005
|
+
const afterGuard = colonIdx !== -1 ? guardText.slice(colonIdx + 1) : (parenIdx !== -1 ? guardText.slice(parenIdx + 1) : "");
|
|
3006
|
+
if (afterGuard && isFailureOrAssertion(afterGuard)) {
|
|
3007
|
+
found = true;
|
|
3008
|
+
} else {
|
|
3009
|
+
const guardIndent = (guardText.match(/^\s*/)?.[0] || "").length;
|
|
3010
|
+
for (let k = i + 1; k < hunk.lines.length; k++) {
|
|
3011
|
+
const t = hunk.lines[k].text || "";
|
|
3012
|
+
if (!t.trim() || isCommentLine(t)) continue;
|
|
3013
|
+
const lineIndent = (t.match(/^\s*/)?.[0] || "").length;
|
|
3014
|
+
if (lineIndent <= guardIndent) break;
|
|
3015
|
+
if (isFailureOrAssertion(t)) { found = true; break; }
|
|
3016
|
+
}
|
|
3017
|
+
}
|
|
2994
3018
|
}
|
|
3019
|
+
|
|
2995
3020
|
if (found) {
|
|
2996
3021
|
violations.push({
|
|
2997
3022
|
file,
|
|
2998
3023
|
line: guardLineNo,
|
|
2999
3024
|
type: "VACUOUS_ASSERTION",
|
|
3000
3025
|
reason:
|
|
3001
|
-
`Test Tamper Guard: the failure call in ${file}${guardLineNo ? `:${guardLineNo}` : ""} ` +
|
|
3026
|
+
`Test Tamper Guard: the assertion or failure call in ${file}${guardLineNo ? `:${guardLineNo}` : ""} ` +
|
|
3002
3027
|
`is guarded by a condition that can never be true ("${collapseWhitespace(guardText).trim()}"), so the ` +
|
|
3003
|
-
`assertion cannot fail. Keeping the
|
|
3028
|
+
`assertion cannot fail. Keeping the assertion in dead logic is not preservation of verification.`,
|
|
3004
3029
|
});
|
|
3005
3030
|
}
|
|
3006
3031
|
}
|
package/src/stack-detector.mjs
CHANGED
|
@@ -475,11 +475,27 @@ export function detectPolyglotStack(projectRoot = process.cwd()) {
|
|
|
475
475
|
: existsSync(join(projectRoot, "bun.lockb"))
|
|
476
476
|
? "bun.lockb"
|
|
477
477
|
: "bun.lock";
|
|
478
|
-
|
|
478
|
+
let buildCmd = "";
|
|
479
|
+
if (existsSync(join(projectRoot, "package.json"))) {
|
|
480
|
+
try {
|
|
481
|
+
const pkg = JSON.parse(readFileSync(join(projectRoot, "package.json"), "utf-8"));
|
|
482
|
+
if (pkg && typeof pkg === "object" && pkg.scripts && pkg.scripts.build) {
|
|
483
|
+
buildCmd = "bun run build";
|
|
484
|
+
}
|
|
485
|
+
} catch (_) {}
|
|
486
|
+
}
|
|
487
|
+
return { ...container, stack: "bun", setupCmd, testCmd: "bun test", buildCmd, triggerFile };
|
|
479
488
|
}
|
|
480
489
|
if (existsSync(join(projectRoot, "deno.json")) || existsSync(join(projectRoot, "deno.jsonc"))) {
|
|
481
490
|
const triggerFile = existsSync(join(projectRoot, "deno.json")) ? "deno.json" : "deno.jsonc";
|
|
482
|
-
|
|
491
|
+
let buildCmd = "";
|
|
492
|
+
try {
|
|
493
|
+
const denoConfig = JSON.parse(readFileSync(join(projectRoot, triggerFile), "utf-8"));
|
|
494
|
+
if (denoConfig && typeof denoConfig === "object" && denoConfig.tasks && denoConfig.tasks.build) {
|
|
495
|
+
buildCmd = "deno task build";
|
|
496
|
+
}
|
|
497
|
+
} catch (_) {}
|
|
498
|
+
return { ...container, stack: "deno", setupCmd, testCmd: "deno test", buildCmd, triggerFile };
|
|
483
499
|
}
|
|
484
500
|
if (existsSync(join(projectRoot, "package.json"))) {
|
|
485
501
|
const hasPnpm = existsSync(join(projectRoot, "pnpm-lock.yaml"));
|