Skip to content

Fix open issue regressions - #551

Merged
sandstream merged 1 commit into
mainfrom
fix/open-issues-529-550
Aug 28, 2026
Merged

Fix open issue regressions#551
sandstream merged 1 commit into
mainfrom
fix/open-issues-529-550

Conversation

@sandstream

Copy link
Copy Markdown
Owner

Fixes #529
Fixes #531
Fixes #533
Fixes #535
Fixes #536
Fixes #537
Fixes #538
Fixes #549
Fixes #550

Summary:

  • merge memory tombstones and add shared-memory provenance/aging surfacing
  • block fetch-to-shell repo installers and clarify guard/install-gate semantics
  • expose accepted CLI flags in OpenCLI and add inverse docs/script audits

Verification:

  • npm run format:check
  • npm run lint
  • npm run build
  • npm test
  • node dist/cli.js check --category tests --enforce-tests
  • node dist/cli.js self-audit --format json
  • node dist/cli.js check --category security --json
  • node dist/cli.js adr check
  • git diff --check

@github-actions

Copy link
Copy Markdown

✅ Docker image built successfully

  • CLI image: docker.io/sandstream/kit:test

@sandstream
sandstream merged commit 6cbe0fd into main Aug 28, 2026
19 checks passed
@sandstream
sandstream deleted the fix/open-issues-529-550 branch August 28, 2026 11:49
sandstream pushed a commit that referenced this pull request Aug 30, 2026
The decision belongs in the curated tier, but the provenance was the open question:
an agent derived this from measurement and no human stated it, so `operator` -- the
default when the field is absent -- would be exactly the mislabelling #550 was about.
Written as `derived`.

That has a useful second effect. The caveat left standing after #551 was that kit's own
.kit/shared measured identically to before the fix: 46 entries, 41 without provenance,
0 aging, 0 stale, because operator and legacy entries never age. The corpus had nothing
to classify. Measured now:

  entries=48  provenance={"(unset)":42, "operator":5, "derived":1}
  today      : aging=0 stale=0
  +200 days  : aging=1  ->  0ed015 (derived)

So the aging model has its first classifiable entry, and the 42 legacy ones stay exempt
as designed. The mechanism was proven in isolation before; this is the first time the
repo's own store exercises it.
sandstream added a commit that referenced this pull request Aug 30, 2026
…556)

* feat(adr): accept ADR-0006 — src/utils is a leaf

The first decision recovered by `kit adr derive` and then made binding. It replaces
three of the nine proposed candidates with their general form: derive proposed `utils ->
commands` (113 reverse edges), `utils -> adapters` (8) and `utils -> memory` (5)
separately; measurement showed `utils` imports NOTHING from src/ at all, so one rule
covers all three and any subsystem added later.

Enforcing the general form matters because `utils` is the most fanned-in directory in
the repo. A single upward import from it creates a cycle through most of the tree.

Mutation-proved rather than eyeballed: green on the repo as it stands, and injecting
`import "../adr.js"` into src/utils/colors.ts produces
`✗ src/utils/colors.ts:1 ... (ADR-0006)` with exit 1, green again on revert.

Two candidates deliberately NOT accepted, with the reason recorded in the ADR itself:

- "No subsystem imports the command layer" is true of all 16 subsystems, but its
  general form fires on src/commands/adr-derive.test.ts, where a fixture STRING
  containing `import "../commands/x.js"` is read as a real import by the text-level
  extractor. `paths` has no negation, so "every subsystem except src/commands/**"
  cannot be expressed. Left underived rather than encoded wrong -- and worth noting
  that the tool's narrow per-directory candidates were right where the hand-written
  consolidation was not.
- `profile` -> `exec-broker` (support 8) is a lone pair with no wider pattern.
  Insufficient evidence it is a decision rather than an ordering accident.

Known papercut, recorded not fixed: `adr derive` still proposes the three candidates
ADR-0006 now enforces. Suppressing them needs regex subsumption against accepted rules,
which is not trivially decidable and not worth guessing at.

* chore(memory): record the skill-gate decision as derived, not operator

The decision belongs in the curated tier, but the provenance was the open question:
an agent derived this from measurement and no human stated it, so `operator` -- the
default when the field is absent -- would be exactly the mislabelling #550 was about.
Written as `derived`.

That has a useful second effect. The caveat left standing after #551 was that kit's own
.kit/shared measured identically to before the fix: 46 entries, 41 without provenance,
0 aging, 0 stale, because operator and legacy entries never age. The corpus had nothing
to classify. Measured now:

  entries=48  provenance={"(unset)":42, "operator":5, "derived":1}
  today      : aging=0 stale=0
  +200 days  : aging=1  ->  0ed015 (derived)

So the aging model has its first classifiable entry, and the 42 legacy ones stay exempt
as designed. The mechanism was proven in isolation before; this is the first time the
repo's own store exercises it.

* fix(lint): clear every eslint error, and stop a test from depending on a missing tool

Provisioning the standards gates turned up more than the gates themselves.

**The eslint findings were not where I said they were.** I had reported 174 findings "all
in examples/", based on the ten sample lines kit happened to print. Measured properly:
174 = 6 errors + 168 warnings, and by directory it is src=161, examples=10, packages=3.
The six errors are now fixed:

- two scanners (snyk, sentrux) rethrew a JSON parse failure without attaching `cause`,
  discarding the only thing that says WHERE the output went wrong. In a scanner that
  turns "cannot verify" into an opaque failure, so both now pass `{ cause: err }`.
- a `require()` in an ESM example, now a `node:crypto` import
- three unused imports/bindings

Two more warnings were dead `eslint-disable` directives for `no-control-regex` that
suppressed nothing -- a suppression that suppresses nothing is a lie in the code for the
next reader. Removed. 174 -> 166, and 0 errors.

The remaining 166 are structural: complexity=71, no-explicit-any=32,
max-lines-per-function=27, max-depth=18, max-lines=11, max-params=6. That is the same
debt lizard and scc report, and refactoring it is a program rather than a task. It is in
the frozen baseline, which is what makes the gate usable at all.

**And a test whose green depended on the machine.** `collectStandardsKeys` "returns empty
slices when the general tools are absent" asserted against the real cwd with the comment
"lizard/jscpd/scc are not installed in this environment". Installing lizard and jscpd
turned it red with no code change. A test that passes because the box happens to be
missing something proves nothing on a box that has it. It now empties PATH for the
duration of the call, so the absent-tools condition is forced rather than hoped for, and
it passes on both kinds of machine.

Baseline: .kit-baseline.json now carries 833 frozen standards findings from the first run
of complexity/duplication/size. It includes the six eslint errors fixed above -- a frozen
finding that no longer occurs simply never matches -- and the 49 scc findings, so the file
size gate does not report 49 "new" findings the day scc is provisioned somewhere.

* feat: say what a green check covers, enforce the dependency floor, and draw the model-rig boundary

Three fixes for three gaps between what kit claims and what kit enforces.

**1. `kit check` now states its scope next to its verdict.**

    scope: 42 check(s) — 41 inspect the code, 1 executes it. A pass here does not
    cover runtime behaviour; `kit broker` is that tier.

Counted from the checks that ran, not asserted, so it cannot go stale as categories are
added. The reason it matters is measured, not stylistic: multi-tier verification research
(arXiv:2607.00107, 8,918 programs across four tiers) found AI-generated code ~2x as likely
as human code to trigger a confirmed runtime violation -- while under STATIC analysis the
two appear equally safe, a similarity the authors call misleading. kit's surface is 41/42
static, so on a weak model its green says roughly what it would have said about
human-written code. That is not a wrong verdict, it is an uninformative one wearing the
same colour as an informative one. Now it says so. Sibling to the existing partial-run
line, for the same reason.

**2. ADR-0002's dependency floor is enforced for the first time.**

Found by breaking it: `kit pkg npm:jscpd` added a fifth runtime dependency and
`kit adr check` reported "4 enforced ADR(s) — no new violations". The kit-enforce block is
a deny-list of twelve named packages at the import level; the ADR's title says "four
runtime deps". It declared more than it enforced.

It is not expressible in the grammar, for two structural reasons now recorded in the ADR:
package.json is not in the file set the ADR gate walks (CODE_EXTS is source extensions
only), and forbid/require_pattern match LINE BY LINE, so a `dependencies` entry cannot be
told apart from a `devDependencies` one. Widening the walk and adding block-aware matching
to serve one rule is a bigger change than the rule is worth, so the floor is enforced by
src/dependency-floor.test.ts -- exact dependency set, exact versions, no ranges. The
kit-enforce block stays as the cheaper first line.

**3. ADR-0007 draws the line for the model + kit residual-risk rig.**

kit owns the frozen input, the ingest schema, the deterministic adjudication and the
receipts; the models run outside and the repository decides whether a finding is real.
kit never produces, ranks or grades a judgement. ADR-0001 is untouched -- its
forbid_import over src/** is the mechanical half, which is why 0007 carries no rule of its
own rather than duplicating one that could drift. Three limits are written into the
consequences so they ship with any number: lower bound only, Goodhart, and unproven
transfer from C++.

Everything mutation-proved rather than eyeballed: a fifth dependency and a floated version
each break exactly the assertion that should notice; treating every category as executing
breaks three tier-notice tests; dropping the `kit broker` pointer breaks exactly the one
about naming the remedy.

* feat(adr): let an ADR name where it is enforced when the grammar cannot hold it

Future-proofing for a pattern that showed up three times this week rather than once.

Three limits of the `kit-enforce` grammar are now measured, each found by trying to write
a rule and failing:

  - `paths` has no negation, so "every subsystem except src/commands/**" is inexpressible
  - `forbid_pattern`/`require_pattern` match LINE BY LINE, so a JSON block cannot be
    pinned and package.json is not in the walked file set anyway
  - the import extractor is text-level, so a test fixture STRING that looks like an import
    counts as one

Today, when a rule hits one of those, nothing happens. The decision quietly does not get
encoded and the ADR goes on declaring more than it enforces. ADR-0002 sat in exactly that
state for months: titled "four runtime deps", enforcing "not these twelve imports". The
gap was invisible because a narrowly-enforced ADR and a fully-enforced one print
identically.

So an ADR can now name its real enforcement point:

    enforced_by: [src/dependency-floor.test.ts]

`adr list` prints it under the ADR, so "documented, not enforced" is never confused with
"not enforced anywhere". And `adr check` FAILS when a named file does not exist -- which
is the half that matters. A pointer nobody verifies is worse than no pointer, because it
reads as coverage. Same rule class as ci-adr-gate.test.ts (a gate must be invoked) and
self-audit (a workflow's script must exist), applied to enforcement claims.

Checked only for `accepted` ADRs: a proposal's pointer is an intention, not yet a claim.

Mutation-proved end to end: adding a second, non-existent pointer to ADR-0002 fails
`adr check` with the file cited; deleting the real dependency-floor test fails it too;
turning the accepted-status guard off, or on for every status, each breaks exactly the one
test that should notice.

The three grammar limits are now written into CLAUDE.md and AGENTS.md alongside the
mechanism, so the next person who cannot express a rule reaches for this instead of
dropping it silently.

* style: run prettier over the three files CI flagged

`npm run format:check` failed on src/adr.ts, src/commands/usage.ts and
src/commands/usage.test.ts. Formatting only — no behaviour change.

Two causes, both mine: `eslint --fix` left trailing whitespace where it removed the dead
`no-control-regex` disable comments, and the `list()` helper added to adr.ts had lines
past the print width.

The real miss is the process one. I ran build, tsc, eslint, the full suite and
`review --stages adr,skill` before pushing, and never ran `format:check` — which is a
step CI runs and a contributor runs locally. Reproduced the failure first, then confirmed
the same command passes ("All matched files use Prettier code style!"), rather than
assuming --write was sufficient.

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment