DEV Community

Rich Tillman
Rich Tillman

Posted on

bisect2-1794189502704-3

// tools/guardrails.mjs - lines 67-92
const gateRule = manifest.gateTargets
if (gateRule) {
  const pkg = JSON.parse(readFileSync(join(root, "package.json"), "utf8"))
  const gate = pkg.scripts?.[gateRule.script] ?? ""
  const match = /nx\s+affected\s+-t\s+([^\s&|]+)/.exec(gate)
  const targets = match ? match[1].split(",").map((t) => t.trim()) : []
  if (targets.length === 0) {
    problems.push(
      `the ${gateRule.script} script does not derive its targets from the project graph`
    )
  }
  for (const target of gateRule.required ?? []) {
    if (!targets.includes(target)) {
      problems.push(
        `the ${gateRule.script} script does not run "${target}" (${gateRule.why ?? ""})`.trim()
      )
    }
  }
  for (const target of gateRule.forbidden ?? []) {
    if (targets.includes(target)) {
      problems.push(
        `the ${gateRule.script} script runs "${target}", which the repository does not use (${gateRule.why ?? ""})`.trim()
      )
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The targets are parsed out of the gate script, so they come from the project
graph rather than from a list somebody edits. The history of this field is why
it has a direction at all:

$ git show 8c75684 -- tools/guardrails.manifest.json   # gateTargets introduced
…
+    "forbidden": ["test", "test-storybook"],
…

$ git show b200e23 -- tools/guardrails.manifest.json    # the flip
-    "forbidden": ["test", "test-storybook"],
…
+    "required": ["test"],
+    "forbidden": ["test-storybook"],
Enter fullscreen mode Exit fullscreen mode

A rule that can only ever say no has to be rewritten when the decision
changes. A rule with a direction survives the decision changing: test moved
from forbidden to required, and the manifest moved with it.

That test target is the one F1 removed. It came back on 2026-11-03, and the
gate that runs it today also runs the guardrails first:

$ git show b200e23:package.json | grep '"gate"'
    "gate": "bun run budget && bun run guardrails && nx affected -t lint,format,typecheck,build,build-storybook,test"
Enter fullscreen mode Exit fullscreen mode

4. Separately bounded budgets

// tools/size-budget.mjs - lines 11-14
const MAX_FILES = 95
const MAX_LINES = 3300
const MAX_TEST_FILES = 12
const MAX_TEST_LINES = 400
Enter fullscreen mode Exit fullscreen mode
$ bun run budget
size budget: 81/95 files, 3073/3300 non-blank lines
test budget: 4/12 files, 227/400 non-blank lines
Enter fullscreen mode Exit fullscreen mode

The product budget and the test budget are counted separately, and the split
is the point. If tests counted against the product budget, the first test
would cost a line of product code, and the budget would push toward removing
verification. Excluding tests without bounding them would make the exclusion
a hole. Bounding them separately makes it a rule.

All four, running

$ bun run guardrails
Recorded removals:
  HARNESS.md — removed with the test harness in #43 — the spec-first scaffold the harness owned
  docs/specs/ — removed with the test harness in #43 — the spec-and-test loop; a unit test loop came back under 0019, without the specs
  docs/decisions/0005-sdd-and-tdd.md — removed with the test harness in #43 — it recorded the spec-and-test rules that went with it
  docs/decisions/0007-storybook-scope.md — removed with the test harness in #43 — its per-library Storybook and story-per-component rules are still in force
  apps/web/AGENTS.md, libs/shared/ui/AGENTS.md, libs/web/features/*/AGENTS.md — per-project agent instructions removed with the test harness in #43; the root AGENTS.md is the instruction file now

guardrails: 9 files, 6 invariants, 17 records, 165 tracked files scanned — all present
Enter fullscreen mode Exit fullscreen mode

Both scripts are Node built-ins only and run from the repository root, a
worktree or CI. The guardrails script also runs as its own job, Guardrails,
and inside the gate.


The rest of it

Four more checks had to become mechanical before the first four meant anything.

Recorded removals

Five entries, each naming what was removed and why, printed on every run:

// tools/guardrails.mjs - lines 219-224
const removed = Object.entries(manifest.removed ?? {})
if (removed.length > 0) {
  console.log("Recorded removals:")
  for (const [path, reason] of removed) console.log(`  ${path} — ${reason}`)
  console.log("")
}
Enter fullscreen mode Exit fullscreen mode
// tools/guardrails.manifest.json - lines 147-149
  "removed": {
    "HARNESS.md": "removed with the test harness in #43 — the spec-first scaffold the harness owned",
    "docs/specs/": "removed with the test harness in #43 — the spec-and-test loop; a unit test loop came back under 0019, without the specs",
Enter fullscreen mode Exit fullscreen mode

This is decision record 0009's principle, made mechanical: "An unexplained
absence reads as an oversight. A recorded exclusion reads as judgement."

(docs/decisions/0009-not-built.md, Context). The repository now prints its
own absences, with reasons, every time the guardrails run.

Forbidden paths

Fourteen globs, matched against every tracked file at any depth:

// tools/guardrails.mjs - lines 123-131
for (const path of tracked) {
  for (const rule of forbidden) {
    // Matched against the whole path and against the file name, so a pattern
    // like "*-original" means any depth rather than only the repository root.
    if (rule.matcher.test(path) || rule.matcher.test(basename(path))) {
      problems.push(`tracked file matches a forbidden path pattern: ${path} (${rule.pattern})`)
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The comment in that loop is the fix for one of the three wrong-reason checks
in the section above: the earlier version matched the repository root only, so
a nested forbidden path was never matched.

Secret patterns

Eight regular expressions (GitHub, Chromatic, Nx Cloud, AWS, private keys)
applied to every tracked text file under a megabyte:

// tools/guardrails.mjs - lines 160-172
for (const path of tracked) {
  if (secretPatterns.length === 0) break
  if (BINARY_EXTENSIONS.has(extname(path).toLowerCase())) continue
  const full = join(root, path)
  if (!existsSync(full)) continue
  if (statSync(full).size > 1024 * 1024) continue
  const text = readFileSync(full, "utf8")
  for (const rule of secretPatterns) {
    if (rule.matcher.test(text)) {
      problems.push(`tracked file contains something shaped like a secret: ${path} (${rule.name})`)
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Project tags

Tags come from a closed vocabulary: a scope (shared, web)
and a type (ui, feature, app). A project with no tags fails, because the
boundary rule matches on tags and a project with no tags has no boundary:

// tools/guardrails.mjs - lines 205-209
  const tags = isProjectFile ? json.tags : json.nx?.tags
  if (!Array.isArray(tags) || tags.length === 0) {
    problems.push(`project has no tags: ${path}`)
    continue
  }
Enter fullscreen mode Exit fullscreen mode

The distinction that keeps the secret check honest

A pattern check is cheap and mechanical, and it covers the shape of a
credential. It did not find the credential that was on the machine. That was
found by a separate search, run by hand, for the exact values of the credentials
held in local files. The corpus says so itself:

Top comments (0)