Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
05c3f62
fix(init): make a freshly scaffolded satellite pass its own governance
beyondnetPeru Jul 29, 2026
c8270e4
docs(board): register GT-633 and close it on the scaffold fix
beyondnetPeru Jul 29, 2026
d18b32d
chore(integration): merge the GT-633 init-scaffold slice
beyondnetPeru Jul 29, 2026
ca035bd
docs(interfaces): regenerate the derived how-tos after the MTN-05 ann…
beyondnetPeru Jul 29, 2026
5acb29e
fix(ci): the GT-578 dead-reference ratchet was stuck by two bugs in i…
beyondnetPeru Jul 29, 2026
7df35ab
docs(board): register GT-634 and close it on 5acb29ed
beyondnetPeru Jul 29, 2026
d11b989
fix(standards): generate the evaluability capture and make its guard …
beyondnetPeru Jul 29, 2026
5db81f9
docs(board): register GT-636, the three pre-existing red checks on PR…
beyondnetPeru Jul 29, 2026
3dd6f7c
fix(GT-636): two of three CI checks were real bugs, not accepted debt
beyondnetPeru Jul 29, 2026
b5fde72
docs(board): move GT-636 to in-progress, 2 of 3 checks closed
beyondnetPeru Jul 29, 2026
8bc1b6e
fix(deps)!: the CLI and MCP must resolve the SDK that carries the sec…
beyondnetPeru Jul 29, 2026
f672255
fix(release): carry the two pins that move with the CLI version (GT-634)
beyondnetPeru Jul 29, 2026
93b4173
fix(release): regenerate the product inventory, which pins the CLI ve…
beyondnetPeru Jul 29, 2026
87ebd60
fix(release): the install-smoke gate cannot run under a dry-run publi…
beyondnetPeru Jul 29, 2026
d85c621
fix(deps): brace-expansion's 2.1.2 override predates the advisory it'…
beyondnetPeru Jul 29, 2026
dd064a6
Merge pull request #282 from beyondnetcode/release/npm-1.2.2
beyondnetPeru Jul 29, 2026
2f32aaa
docs(board): close GT-636 on d85c6216, all three checks fixed
beyondnetPeru Jul 29, 2026
6f7cb3c
fix(ci): the GT-636 closure record itself broke the ratchet it was ve…
beyondnetPeru Jul 30, 2026
8449af3
chore(board): renumber the ratchet fix GT-634 -> GT-637, ID collision…
beyondnetPeru Jul 30, 2026
57edee7
Merge remote-tracking branch 'origin/develop' into claude/recursing-c…
beyondnetPeru Jul 30, 2026
69e8ec9
chore(board): regenerate maturity-reconciliation.json after the devel…
beyondnetPeru Jul 30, 2026
f85a1ab
Merge pull request #275 from beyondnetcode/integration/gt-572-595-604
beyondnetPeru Jul 30, 2026
3769e05
fix(ci): the root-cleanliness allowlists assumed .git is always a dir…
beyondnetPeru Jul 30, 2026
70eecf7
Merge pull request #291 from beyondnetcode/claude/recursing-carson-72…
beyondnetPeru Jul 30, 2026
648ad5c
fix(standards): one renderer, two callers — reconcile the two GT-633 …
beyondnetPeru Jul 30, 2026
823f633
Merge pull request #294 from beyondnetcode/integration/gt-633-one-ren…
beyondnetPeru Jul 30, 2026
40b3d11
fix(standards): the sticky capture date was rewritten on every run
beyondnetPeru Jul 30, 2026
745274d
Merge pull request #296 from beyondnetcode/fix/sticky-capturedon-develop
beyondnetPeru Jul 30, 2026
ef4fb29
chore(standards): align the capture script's comment with main, byte …
beyondnetPeru Jul 30, 2026
2eb244b
Merge pull request #297 from beyondnetcode/chore/align-capture-comment
beyondnetPeru Jul 30, 2026
1b807f0
Merge branch 'main' into develop — resolve the 17-file divergence
beyondnetPeru Jul 30, 2026
983db90
Merge pull request #302 from beyondnetcode/integration/develop-to-mai…
beyondnetPeru Jul 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 18 additions & 38 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@ jobs:
- name: Execute the board's recorded validationCommands (report mode)
run: node .harness/scripts/ci/41-validate-evidence-commands.mjs --execute --verbose

# Ratchet, not a cliff. The budget is 38, which is what THIS RUNNER counts.
# Ratchet, not a cliff. The budget is 19, which is what THIS RUNNER counts.
#
# It was briefly lowered to 303 on the strength of the guard's computed
# "ratchet basis" — 288 dead locally plus 15 that resolve only through
Expand All @@ -493,47 +493,27 @@ jobs:
# first place, and I broke it by trusting a derived figure over a measured
# one.
#
# 305 -> 304 obeying that rule rather than working around it. Five closure
# records (GT-424/621/629/630/632) quoted their self-test as
# `node --test 34-check-skill-registry-parity.test.mjs`, with no directory,
# while naming the real `.harness/scripts/ci/` path in the same record's
# `files` list — evidence pointing at something real through a command that
# cannot run. Repairing the five took the count to 304, and THIS JOB printed
# `304 dead (budget 305)` in run 30498743635 before the budget moved. The
# local count agreed, and that agreement is not what authorises this: the
# printed line is.
#
# 304 -> 40, the same way. 264 of the 304 were ONE event: the `src/` refactor
# moved every workspace and the closure evidence kept quoting the old layout.
# Migrating it — rewriting only where the same artifact moved and the new
# target was verified to exist — took the count to 40, and THIS JOB printed
# `40 dead (budget 304)` in run 30500947483 before the budget moved.
#
# The 40 that remain are not path rot and must not be "repaired" into
# silence: 15 reference the vendored, gitignored `.harness/bin/opa`; 4 name a
# script that was ABSORBED into 04-check-bilingual-parity rather than moved,
# so repointing them would claim a command nobody ran; 6 name artifacts that
# were deleted; 5 are root-level `npm test` recorded without the workspace
# they ran in; 2 are valid `pnpm`/`yarn info` this guard mis-parses as npm;
# the rest are build output and prose the extractor reads as commands.
#
# Two of those are guard limitations rather than evidence defects, and fixing
# them is the next real move: the npm-script parser should skip a flag's
# VALUE (`npm run --workspace <path> <script>` reads the path as the script),
# and `pnpm`/`yarn` are not `npm`.
#
# 40 -> 38: those two parser limitations were FIXED rather than tolerated,
# so the two valid `pnpm`/`yarn info` commands stopped being counted as dead
# evidence. THIS JOB printed `38 dead (budget 40)` in run 30502009205 before
# the budget moved. Note what moved and what did not: the flag-value defect
# recovered nothing today, because those records had already been reordered
# to suit the broken parser — the fix is what stops the NEXT record written
# the other way from being misreported as stale.
# 305 -> 21 (GT-637): most of it was two real defects in THIS GUARD, not the
# evidence corpus — `npmScriptOperand` was called but never defined (a
# ReferenceError the guard swallowed into a generic crash), and
# `npm run --workspace <path> <script>` (the flag BEFORE the script name,
# used throughout the corpus) mistook the workspace's own value for the
# script name. Fixing both, plus migrating ~18 pre-`src/`-refactor and
# pre-`product/`-move paths that have real successors, plus classifying
# `.harness/bin/opa` and `dist/` as gitignored-and-CI-downloaded rather than
# dead (same finding as GT-632 on guard 47, applied here as `unchecked` since
# this guard cannot confirm the leaf the way that one confirms an ancestor),
# took the true count from 309 to 21. The residual is retired artifacts with
# no successor (a consolidated `.bmad-core/AGENTS.md`, the retired
# `evolith-bff` gateway, the retired `packages/mcp-tools`, two retired k8s
# manifests, a retired sandbox app, three root-level scripts no longer
# declared) plus three records where `vision/` and `tools/list` are prose,
# not paths — see GT-637 for the record.
#
# Lower it as records are repaired, from a number this job printed; at 0,
# delete the flag and this becomes the strict gate.
- name: Dead-reference ratchet (must not grow)
run: node .harness/scripts/ci/41-validate-evidence-commands.mjs --strict --max-dead 38
run: node .harness/scripts/ci/41-validate-evidence-commands.mjs --strict --max-dead 19

# RETIRED 2026-07-27 (GT-570): the `publish-npm` job lived here and published
# ONLY `src/sdk/cli`, with `--tag beta` and no provenance, writing the token
Expand Down
42 changes: 39 additions & 3 deletions .harness/scripts/ci/02-optimize-repo.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@ const allowedFiles = new Set([
"package-lock.json",
".env",
"tsconfig.json",
"tsconfig.base.json"
"tsconfig.base.json",
// The Core is a satellite of itself (see evolith.yaml's own metadata.name
// comment) and GIT-08's commitlint config — both tracked, both legitimate
// at root, missing here only because this allowlist predates them.
"evolith.yaml",
"commitlint.config.mjs"
]);

const allowedDirectories = new Set([
Expand Down Expand Up @@ -81,10 +86,38 @@ const protectedEntries = [];
if (tracked !== null) {
// Quarantine instead of unlink: untracked files are unrecoverable once
// removed, and this script runs unattended from the pre-push hook.
const quarantineRoot = path.join(root, ".git", "evolith-quarantine");
//
// In a `git worktree`, `.git` at the worktree root is a plain text file
// (`gitdir: <path>/.git/worktrees/<name>`) redirecting to the real one, not
// a directory. Two things here assumed otherwise, and broke in different ways:
//
// 1. `path.join(root, ".git", "evolith-quarantine")` assumed `.git` was
// always a writable directory, so `fs.mkdirSync` failed with ENOTDIR
// the moment there was anything to quarantine — silently crashing the
// pre-push hook for every worktree checkout in the repo. Fixed below by
// resolving the real, writable git-dir with `git rev-parse --git-dir`
// (it IS `.git` in a normal checkout, and the per-worktree directory
// under `.git/worktrees/<name>` here) via `path.resolve`, not
// `path.join` — `--git-dir` prints a RELATIVE path in a normal checkout
// but an ABSOLUTE one in a worktree, and `path.join` would have
// mangled the absolute case by concatenating it onto `root` instead of
// using it as-is. This also keeps quarantine local to whichever
// checkout produced the untracked file, instead of colliding across
// worktrees that shared one.
// 2. `.git` is only in `allowedDirectories`, not `allowedFiles` — so once
// (1) stopped crashing, the loop below saw `.git` itself as an
// unrecognised untracked FILE in a worktree and quarantined it,
// severing the worktree from git entirely (recoverably, since
// quarantine moves rather than deletes, but still not something a
// cleanup script should ever do). `.git` is excluded unconditionally
// below, by name, before the directory/file allowlist check — it must
// never be touched regardless of which shape it takes here.
const gitDir = execSync("git rev-parse --git-dir", { encoding: "utf8", cwd: root }).trim();
const quarantineRoot = path.resolve(root, gitDir, "evolith-quarantine");

for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
const name = entry.name;
if (name === ".git") continue;
const allowed = entry.isDirectory() ? allowedDirectories.has(name) : allowedFiles.has(name);
if (allowed) continue;
if (!entry.isDirectory() && !entry.isFile()) continue;
Expand All @@ -103,7 +136,10 @@ if (tracked !== null) {
}

if (quarantinedCount > 0) {
console.log(` Recover with: mv .git/evolith-quarantine/<ref>/<name> .`);
// Absolute and worktree-correct — `.git/evolith-quarantine/...` reads fine
// in a normal checkout but names nothing in a worktree, where `.git` is a
// redirect file rather than the directory that actually holds this.
console.log(` Recover with: mv ${path.join(quarantineRoot, "<ref>", "<name>")} .`);
}
}

Expand Down
10 changes: 10 additions & 0 deletions .harness/scripts/ci/03-validate-root-cleanliness.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,16 @@ if (missingAnchors.length > 0) {
}

for (const entry of rootEntries) {
// `.git` is only in `allowedDirectories`, which is correct in a normal
// checkout but not in a `git worktree`: there, `.git` at the root is a
// plain text file (`gitdir: <path>`) redirecting to the real one, so it
// fell into the `isFile()` branch below and read as an unauthorized file —
// a false failure specific to local worktree development, since CI always
// checks out fresh (never a worktree) and never hits this. Same root cause
// as the quarantine bug 02-optimize-repo.mjs fixed; excluded unconditionally
// here too, before the directory/file split, regardless of which shape it
// takes in a given checkout.
if (entry.name === ".git") continue;
if (entry.isDirectory()) {
if (explicitlyDeniedDirectories.has(entry.name)) {
failures.push(explicitlyDeniedDirectories.get(entry.name));
Expand Down
58 changes: 44 additions & 14 deletions .harness/scripts/ci/41-validate-evidence-commands.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,19 @@
*
* This guard flips to strict (its `--strict` flag becomes the wired default)
* when BOTH hold:
* (a) dead references == 0. Today they are not: 500+ recorded commands name
* pre-refactor locations (`sdk/cli`, `apps/core-api`, `packages/*` as
* top-level workspaces, `.harness/scripts/validate-docs.mjs`) that the
* `src/` move invalidated, plus the retired `@evolith/*` npm scope. These
* are historical records of commands that were correct when they ran;
* repairing them is a data migration of the evidence file
* (a) dead references == 0. Today they are not: 21 recorded commands name a
* retired artifact with no successor (a consolidated `.bmad-core/AGENTS.md`,
* the retired `evolith-bff` gateway, the retired `packages/mcp-tools`, two
* retired k8s manifests, a retired sandbox app, three root-level scripts no
* longer declared) or embed `vision/` / `tools/list` as prose rather than a
* path (GT-637). These are historical records of commands that were correct
* when they ran, or narrative that only looks path-shaped; repairing them
* is a data migration of the evidence file
* (`reference/core/control-center/evidence/**`, owned by the board), not
* a code change, and it is deliberately out of scope here.
* a code change, and it is deliberately out of scope here. (GT-637 already
* closed the ~290 that were pre-`src/`/`product/`-move paths with real
* successors, plus two defects in THIS guard that inflated the count on
* top of them — see the ratchet step in `ci-cd.yml` for the detail.)
* (b) executed failures == 0 on a clean checkout.
* Until (a) is done, wiring `--strict` would make the governance run red for a
* reason no code change can fix, and the guard would be deleted within a week.
Expand Down Expand Up @@ -151,6 +156,23 @@ const EXTERNAL_BINARIES = new Set([
'ansible', 'act', 'gitleaks', 'trivy', 'snyk', 'zap-cli',
]);

/**
* Repo-relative path PREFIXES this checkout's own `.gitignore` disclaims —
* `.harness/bin/` (downloaded by a separate CI step, e.g. `.harness/bin/opa`)
* and build output (`dist/`, at any depth). A referent under one of these is
* not a wrong path: this guard runs `npm ci` and nothing else, so neither has
* been materialized here regardless of whether the referent is correct. Same
* finding as `GT-632` on guard 47 — a gitignored artifact absent on a clean
* checkout is not evidence the record is dead — but the fix here is narrower:
* that guard checks a path's ANCESTOR is real; this one still can't confirm
* the leaf exists, so the honest verdict is `unchecked`, not `resolved`.
*/
const GITIGNORED_PREFIXES = [/^\.harness\/bin\//, /(^|\/)dist\//];

function isGitignoredArtifact(relPath) {
return GITIGNORED_PREFIXES.some(re => re.test(relPath));
}

/** Real toolchains, but minutes each and needing a built workspace. */
const HEAVY_BINARIES = new Set([
'npx', 'jest', 'tsc', 'vitest', 'playwright', 'cypress', 'dotnet', 'mvn',
Expand Down Expand Up @@ -532,6 +554,14 @@ function pathOperands(cmd) {
return out;
}

/**
* The `<script>` operand of `npm run <script>` / `run-script <script>` — the
* first non-flag token, correctly skipping `--workspace <path>` / `-w <path>`
* when it appears BEFORE the script name. `npm run --workspace <path> <script>`
* is a valid invocation and the corpus uses it throughout; without this, the
* workspace's own VALUE token (which does not start with `-`) is mistaken for
* the script name.
*/
/**
* Resolve a command's referent.
* @returns {{ status: 'resolved'|'dead'|'unchecked', detail: string }}
Expand All @@ -549,9 +579,10 @@ export function resolveCommand(cmd, ctx) {
const script = rest.find(x => !x.startsWith('-'));
if (!script) return { status: 'unchecked', detail: 'no script operand' };
if (!looksLikePath(script)) return { status: 'unchecked', detail: `operand is not a path: ${script}` };
return pathResolves(root, cmd.cwd, script)
? { status: 'resolved', detail: script }
: { status: 'dead', detail: `script not found: ${join(cmd.cwd || '.', script)}` };
if (pathResolves(root, cmd.cwd, script)) return { status: 'resolved', detail: script };
const rel = join(cmd.cwd || '.', script);
if (isGitignoredArtifact(rel)) return { status: 'unchecked', detail: `gitignored artifact, not built here: ${rel}` };
return { status: 'dead', detail: `script not found: ${rel}` };
}

// npm ... --workspace <w> / npm run <script>
Expand Down Expand Up @@ -589,10 +620,9 @@ export function resolveCommand(cmd, ctx) {
// A relative binary: `.harness/bin/opa test rulesets/opa/`
if (cmd.binRaw.includes('/')) {
const b = cmd.binRaw.replace(/^\.\//, '');
if (!pathResolves(root, cmd.cwd, b)) {
return { status: 'dead', detail: `binary not found: ${b}` };
}
return { status: 'resolved', detail: b };
if (pathResolves(root, cmd.cwd, b)) return { status: 'resolved', detail: b };
if (isGitignoredArtifact(b)) return { status: 'unchecked', detail: `gitignored artifact, not built here: ${b}` };
return { status: 'dead', detail: `binary not found: ${b}` };
}

// grep / rg / ls / cat / find / wc — the last path-shaped operand is the target.
Expand Down
39 changes: 39 additions & 0 deletions .harness/scripts/ci/41-validate-evidence-commands.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,45 @@ describe('resolveCommand', () => {
);
});

test('resolves `npm run --workspace <path> <script>` — the flag BEFORE the script name', () => {
// GT-633 follow-on: `npm run --workspace @fixture/alpha test` is a valid
// invocation the corpus uses throughout. Before the fix, the workspace's own
// VALUE token (`@fixture/alpha`, which does not start with `-`) was mistaken
// for the script name, and reported dead as
// `npm script "@fixture/alpha" is not declared in pkgs/alpha/package.json`.
const r = resolveCommand(extractCommand('npm run --workspace @fixture/alpha test'), ctx());
assert.equal(r.status, 'resolved', r.detail);
});

test('DEAD: `npm run --workspace <path> <script>` still catches a script the workspace does not declare', () => {
const r = resolveCommand(extractCommand('npm run --workspace @fixture/alpha nonexistent'), ctx());
assert.equal(r.status, 'dead');
assert.match(r.detail, /is not declared/);
});

test('a gitignored, CI-downloaded binary is `unchecked`, not `dead`', () => {
// `.harness/bin/opa` is real and correct but gitignored: a separate CI step
// downloads it, and this guard runs right after `npm ci` with nothing else.
// Reporting it dead would penalize a correct record for an environment fact,
// same finding as GT-632 on guard 47 — but here the guard genuinely cannot
// confirm the leaf exists, so the honest verdict is `unchecked`, not `resolved`.
const r = resolveCommand(extractCommand('.harness/bin/opa eval -d rulesets/'), ctx());
assert.equal(r.status, 'unchecked', r.detail);
assert.match(r.detail, /gitignored artifact/);
});

test('a gitignored build script (`dist/`) is `unchecked`, not `dead`', () => {
const r = resolveCommand(extractCommand('node dist/main.js validate'), ctx());
assert.equal(r.status, 'unchecked', r.detail);
assert.match(r.detail, /gitignored artifact/);
});

test('a binary that is merely absent (not gitignored) is STILL dead — the fix must not blind it', () => {
const r = resolveCommand(extractCommand('scripts/gone.sh --check'), ctx());
assert.equal(r.status, 'dead');
assert.match(r.detail, /binary not found/);
});

test('a genuinely missing grep target is STILL dead — the fix must not blind it', () => {
const r = resolveCommand(extractCommand("grep -L 'a/b' scripts/gone.mjs"), ctx());
assert.equal(r.status, 'dead');
Expand Down
Loading
Loading