simt-dev 0.2.1 → 0.3.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 +239 -0
- package/bin/simt-dev.mjs +21 -2
- package/docs/decisions/ADR-064-the-context-meter-counts-what-the-tool-wrote.md +107 -0
- package/docs/decisions/ADR-065-a-pack-is-one-shared-rule-set-pinned-by-digest.md +97 -0
- package/docs/decisions/ADR-066-a-monorepo-projects-once-per-package.md +115 -0
- package/docs/decisions/ADR-067-which-harnesses-read-agents-md-is-a-column.md +110 -0
- package/docs/decisions/ADR-068-the-mcp-revision-list-cites-what-was-read.md +111 -0
- package/docs/decisions/ADR-069-the-conformance-lint-checks-the-published-specification.md +90 -0
- package/docs/decisions/ADR-070-the-json-payload-carries-its-own-version.md +91 -0
- package/docs/decisions/ADR-071-a-rule-that-can-be-narrowed-is-written-narrow.md +106 -0
- package/docs/decisions/ADR-072-the-budget-is-a-declaration-beside-the-gates.md +104 -0
- package/docs/decisions/ADR-073-a-kernel-is-a-smaller-projection-the-repository-asks-for.md +92 -0
- package/docs/decisions/ADR-074-a-rule-and-a-decision-are-readable-by-uri.md +99 -0
- package/docs/decisions/ADR-075-nothing-leaves-the-machine.md +56 -0
- package/docs/decisions/ADR-076-a-change-to-what-agents-read-is-measured.md +70 -0
- package/docs/decisions/ADR-077-the-command-card-leads-the-region-and-reads-committed-evidence.md +91 -0
- package/docs/decisions/ADR-078-a-line-the-repository-already-proves-does-not-ship.md +117 -0
- package/docs/decisions/ADR-079-the-rules-are-enforced-at-the-tool-call-from-one-policy-table.md +162 -0
- package/docs/decisions/ADR-080-the-tools-own-names-are-not-repositories-and-neither-reader-decides-by-shape.md +99 -0
- package/docs/decisions/ADR-081-a-tracker-is-read-through-the-repositorys-template.md +36 -0
- package/docs/decisions/ADR-082-the-tracker-override-is-a-manifest-key.md +35 -0
- package/package.json +2 -1
- package/rules/core.md +1 -0
- package/rules/stacks/android.md +2 -0
- package/rules/stacks/cloudflare-workers.md +2 -0
- package/rules/stacks/connectiq.md +1 -0
- package/rules/stacks/container.md +2 -0
- package/rules/stacks/dart.md +2 -0
- package/rules/stacks/deno.md +2 -0
- package/rules/stacks/dotnet.md +2 -0
- package/rules/stacks/elixir.md +2 -0
- package/rules/stacks/go.md +2 -0
- package/rules/stacks/jvm.md +2 -0
- package/rules/stacks/node.md +2 -0
- package/rules/stacks/python.md +2 -0
- package/rules/stacks/react-native.md +2 -0
- package/rules/stacks/react-web.md +2 -0
- package/rules/stacks/ruby.md +2 -0
- package/rules/stacks/rust.md +2 -0
- package/rules/stacks/swift.md +2 -0
- package/rules/stacks/typescript.md +2 -0
- package/rules/stacks/uv.md +2 -0
- package/src/agenthooks/decide.mjs +123 -0
- package/src/agenthooks/policy.mjs +78 -0
- package/src/agenthooks/render.mjs +240 -0
- package/src/argv.mjs +55 -1
- package/src/bench/ablate.mjs +630 -0
- package/src/bench/adapters.json +149 -0
- package/src/bench/grade.mjs +229 -0
- package/src/bench/mine.mjs +811 -0
- package/src/bench/probes.mjs +1350 -0
- package/src/bench/report.mjs +899 -0
- package/src/bench/run.mjs +1002 -0
- package/src/commands/agenthook.mjs +282 -0
- package/src/commands/bench.mjs +359 -0
- package/src/commands/compare.mjs +15 -0
- package/src/commands/graph.mjs +45 -21
- package/src/commands/harness.mjs +301 -0
- package/src/commands/help.mjs +73 -2
- package/src/commands/index.mjs +67 -25
- package/src/commands/init.mjs +12 -8
- package/src/commands/mcp.mjs +20 -5
- package/src/commands/plan.mjs +99 -0
- package/src/commands/rules.mjs +42 -57
- package/src/commands/skills.mjs +67 -92
- package/src/commands/skillsinstall.mjs +17 -2
- package/src/commands/sync.mjs +62 -6
- package/src/commands/tokens.mjs +169 -0
- package/src/compare/row.mjs +73 -2
- package/src/decisions/index.mjs +23 -5
- package/src/graph/spec.mjs +227 -0
- package/src/graph/speccheck.mjs +68 -0
- package/src/hooks/dispatcher.mjs +35 -3
- package/src/kernel/card.mjs +254 -0
- package/src/kernel/inferability.mjs +188 -0
- package/src/mcp/resources.mjs +157 -0
- package/src/mcp/schema.mjs +139 -0
- package/src/mcp/server.mjs +228 -20
- package/src/mcp/tools.mjs +372 -1
- package/src/measure/context.mjs +542 -0
- package/src/measure/loaderView.mjs +435 -0
- package/src/measure/session.mjs +267 -0
- package/src/monorepo.mjs +339 -0
- package/src/output/index.mjs +78 -2
- package/src/packs.mjs +182 -0
- package/src/plan/index.mjs +650 -0
- package/src/plan/template.mjs +331 -0
- package/src/projection/loaders.mjs +689 -0
- package/src/projection/manifest.mjs +114 -3
- package/src/projection/project.mjs +140 -9
- package/src/projection/rules.mjs +365 -22
- package/src/projection/surfaces.mjs +155 -23
- package/src/projection/targets.mjs +37 -0
- package/src/toolwords.mjs +69 -0
- package/templates/plan/tracker.json +36 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# ADR-067 — Which harnesses read AGENTS.md is a column, and a row without a document gets none
|
|
2
|
+
|
|
3
|
+
Status: **accepted** · 2026-10-13 · filed as S42
|
|
4
|
+
**Affects:** `harness detect`, `src/projection/surfaces.mjs`, `src/commands/harness.mjs`, the
|
|
5
|
+
table tests in `test/surfaces.test.mjs`, `test/harness.test.mjs`
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
Review 0002 filed the finding as "harness coverage stops at five surfaces", and the table
|
|
10
|
+
stops at five: opencode, Cursor, Claude Code, Antigravity and Copilot. The same review then
|
|
11
|
+
listed a dozen more harnesses that read instruction files — Gemini CLI, Windsurf, Zed, Kiro,
|
|
12
|
+
Cline, Roo Code, Junie, Aider, Continue, Amp, Goose — and its own fix had two halves that pull
|
|
13
|
+
against each other.
|
|
14
|
+
|
|
15
|
+
The first half is mechanical: add a column recording whether each harness reads AGENTS.md,
|
|
16
|
+
and add rows only for the ones that do not. Most of the candidates need no row at all,
|
|
17
|
+
because most of them read AGENTS.md natively, and that fact is exactly what the column is for:
|
|
18
|
+
it turns "which harnesses do we support" from an open question into a lookup.
|
|
19
|
+
|
|
20
|
+
The second half is the one the review flagged on itself. It proposes a row per vendor and a
|
|
21
|
+
`verified` field citing that vendor's documentation — while stating in the same paragraph that
|
|
22
|
+
it did not re-verify each vendor's current documentation and that its list should be treated as
|
|
23
|
+
candidates. So the rows would carry a field whose entire purpose is to say "this was checked
|
|
24
|
+
against a source", filled in by a list that was not checked.
|
|
25
|
+
|
|
26
|
+
That combination is the failure this repository exists to prevent, in its exact form. The
|
|
27
|
+
repository's own ruling on manifest comments records the shape of it: a comment claiming each
|
|
28
|
+
value cites its decision record, where none did, is decoration that misdescribes the artefact.
|
|
29
|
+
A surface row is not decoration — it decides whether a reader's agent gets a rule at all. A
|
|
30
|
+
guessed row puts an unverified assumption into every repository that adopts the release, and
|
|
31
|
+
the reader who is wrong about it cannot tell, because the field that would tell them is the
|
|
32
|
+
one that was invented.
|
|
33
|
+
|
|
34
|
+
Detection was the other half and it has no such trap. "Which harnesses is this repository set
|
|
35
|
+
up for" is answerable from directories that exist, provided the command claims exactly that
|
|
36
|
+
much and no more.
|
|
37
|
+
|
|
38
|
+
## Rulings
|
|
39
|
+
|
|
40
|
+
1. **Whether a harness reads AGENTS.md is a vendor fact recorded per row, in three values.**
|
|
41
|
+
`all` means every reader the row names loads it; `some` means a subset does and the row
|
|
42
|
+
says which, in its own words; `none` means the row is the only channel those readers have.
|
|
43
|
+
Two values would be wrong in a way that matters: Copilot's CLI reads AGENTS.md and its
|
|
44
|
+
editor, code review and cloud agent do not, and a boolean there would have to be read as
|
|
45
|
+
"this repository is covered for Copilot", which is false for two of its three readers.
|
|
46
|
+
Every row whose value is not `all` carries the sentence that makes it true.
|
|
47
|
+
|
|
48
|
+
2. **The column is not the existing `readsPrimary` flag, and the two must not be merged.**
|
|
49
|
+
That flag answers whether a per-rule file would repeat text the folded region already
|
|
50
|
+
carries, and it is only meaningful while folding is on. This column answers whether the
|
|
51
|
+
file is read at all, and it decides whether a harness needs a row. Merging them would make
|
|
52
|
+
a duplication question stand in for a coverage question, and the value would then change
|
|
53
|
+
answer a reader is relying on.
|
|
54
|
+
|
|
55
|
+
3. **A row requires provenance, so no row ships from the candidate list.** Review 0002's
|
|
56
|
+
candidates are recorded as configuration directory names, not as surfaces. Adding a row
|
|
57
|
+
requires a vendor document, and a row whose `verified` cites nothing does not get added —
|
|
58
|
+
which means this ruling lands fewer surfaces than the review asked for, deliberately. The
|
|
59
|
+
cost of waiting is that a harness which does not read AGENTS.md is not served until
|
|
60
|
+
somebody reads its documentation; the cost of guessing is that every adopting repository
|
|
61
|
+
inherits the guess and the reader of the row is the only one who can catch it.
|
|
62
|
+
|
|
63
|
+
4. **Detection reports presence and calls it presence.** A directory that exists implies a
|
|
64
|
+
harness is in use. It does not imply the harness reads AGENTS.md, is installed, or is
|
|
65
|
+
configured the way this tool would configure it, and the output never says it does. The
|
|
66
|
+
candidate list claims nothing about what those readers load for the same reason ruling 3
|
|
67
|
+
does: the directory name is evidence of a harness, and the fact nobody has verified is
|
|
68
|
+
what it loads.
|
|
69
|
+
|
|
70
|
+
5. **A configuration directory with no channel is a note, never a problem.** It is the
|
|
71
|
+
expected shape of a repository whose team uses something this release does not serve, and
|
|
72
|
+
the remedy for it is not something the reader can be told to do. The command also exits 0
|
|
73
|
+
whatever it finds, including on a repository with no harness configuration at all: a
|
|
74
|
+
non-zero exit would be read as "this repository is set up wrong", which is the one thing a
|
|
75
|
+
directory scan can never establish.
|
|
76
|
+
|
|
77
|
+
6. **The home directory is opt-in.** `--user` adds it. A command whose answer changes because
|
|
78
|
+
something was installed elsewhere is one a reader stops trusting, and the repository is
|
|
79
|
+
not the reader's machine.
|
|
80
|
+
|
|
81
|
+
## Consequences
|
|
82
|
+
|
|
83
|
+
A harness that does not read AGENTS.md and has no row is now visible rather than invisible:
|
|
84
|
+
`harness detect` names its configuration directory as present with no channel, and says in the
|
|
85
|
+
same breath that presence is not a coverage claim. That is the honest answer available without
|
|
86
|
+
vendor documentation, and it is the one a reader can act on — by asking, or by adding a row
|
|
87
|
+
with a citation.
|
|
88
|
+
|
|
89
|
+
The candidate list will be wrong sometimes: a harness may name its directory differently, or
|
|
90
|
+
use a dotless one, or write its config elsewhere. Each of those produces a miss, and a miss is
|
|
91
|
+
silent. That is the shape of the problem as it stands; the alternative was a table of claims.
|
|
92
|
+
|
|
93
|
+
## Rejected
|
|
94
|
+
|
|
95
|
+
- **Adding a row per review-0002 candidate with `verified` citing the review.** The field would
|
|
96
|
+
name a document that was never read. It is the exact defect the ruling on manifest comments
|
|
97
|
+
records, and it would be harder to spot later because each row would look like a verified
|
|
98
|
+
one.
|
|
99
|
+
- **A two-valued column.** Every row would have to pick the more flattering half, and the
|
|
100
|
+
flattering half for Copilot is the one two of its three readers contradict.
|
|
101
|
+
- **Merging the column into `readsPrimary`.** It would turn "does this harness read the file"
|
|
102
|
+
into "would this duplicate the region", and the answer a reader gets would depend on whether
|
|
103
|
+
they had folded the rules.
|
|
104
|
+
- **Reporting an unclaimed configuration directory as a finding.** It would put a red mark on a
|
|
105
|
+
repository for using a tool this project has not documented, which is a statement about the
|
|
106
|
+
project, not about the repository.
|
|
107
|
+
- **Scanning for configuration files rather than directories.** A file such as a workflow or
|
|
108
|
+
a settings blob is not evidence that a harness is in use, and the one directory deliberately
|
|
109
|
+
excluded here — the one that holds workflows and issues — would have been reported for every
|
|
110
|
+
repository with CI.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# ADR-068 — The revision list is a claim about revisions that were read
|
|
2
|
+
|
|
3
|
+
Status: **accepted** · 2026-10-13 · filed as S43
|
|
4
|
+
**Affects:** `mcp`, `src/mcp/server.mjs`, `src/mcp/tools.mjs`, `src/mcp/schema.mjs`, the
|
|
5
|
+
protocol tests in `test/mcp.test.mjs`
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
Review 0002 found the MCP surface pinned to one protocol revision and two behind the
|
|
10
|
+
published specification, and returning text only: no structured content, no output
|
|
11
|
+
schemas, and no hint that any of the ten tools were read-only. The finding is accurate and
|
|
12
|
+
so is its severity, which is P2 rather than P1 for a reason the row states itself — a
|
|
13
|
+
client that predates a field still reads the text block, so the surface works, it just
|
|
14
|
+
cannot be validated and cannot cache.
|
|
15
|
+
|
|
16
|
+
The tempting fix is to look up what the newest revision says and implement it. ADR-067
|
|
17
|
+
settled a neighbouring question one release ago: a row that cites a document nobody read
|
|
18
|
+
is decoration that misdescribes the artefact. The same trap waits here, and it is worse,
|
|
19
|
+
because a protocol is the one thing in this repository where being confidently wrong is
|
|
20
|
+
not a style defect — it is a client that believes a feature is on.
|
|
21
|
+
|
|
22
|
+
So the revisions this server speaks were read. Each of the three changelogs was fetched
|
|
23
|
+
and read, and what follows is what actually changed for a server on stdio — which is a
|
|
24
|
+
smaller list than the changelogs, because most of each revision's changes are about HTTP
|
|
25
|
+
transports, sessions and authorization that a stdio server never had.
|
|
26
|
+
|
|
27
|
+
## Rulings
|
|
28
|
+
|
|
29
|
+
1. **The revision list cites what changed for this transport.** Three revisions are
|
|
30
|
+
supported. The 2025-06-18 revision already had structured tool output, so the gap this
|
|
31
|
+
review found was never the missing feature — it was the missing declaration. The
|
|
32
|
+
2025-11-25 revision changed nothing a stdio server depends on, and is supported because
|
|
33
|
+
a client on it is a real client. The 2026-07-28 revision removed the handshake, made
|
|
34
|
+
every request carry its own version in `_meta`, added a discovery method that servers
|
|
35
|
+
must implement, required a result-type field on every result, required a cache lifetime
|
|
36
|
+
on list results, removed the liveness method, and moved the resource-not-found code from
|
|
37
|
+
the implementation range to the invalid-params one. Each of those is implemented and
|
|
38
|
+
each has a test.
|
|
39
|
+
|
|
40
|
+
2. **The version is read from the request, and remembered only where a client needs it.**
|
|
41
|
+
A request that names its version in `_meta` is answered at that version, with no
|
|
42
|
+
memory involved — that is the newest revision's shape, and it is stateless by design. A
|
|
43
|
+
client that negotiated through `initialize` sends bare requests forever after, so the
|
|
44
|
+
stream keeps one remembered revision for it. A client that never says anything gets the
|
|
45
|
+
newest. The remembered value exists for exactly one group of clients and is labelled as
|
|
46
|
+
the residue of a handshake the specification removed.
|
|
47
|
+
|
|
48
|
+
3. **An unsupported version fails at the one place the client checks.** `initialize`
|
|
49
|
+
answers with the server's own revision, which is the older revision's guidance and the
|
|
50
|
+
only thing that works there. A per-request version we do not speak returns the
|
|
51
|
+
specification's own error for it, naming every revision we do. Silence there would be
|
|
52
|
+
the failure this whole row is about: a client proceeding against a shape we never
|
|
53
|
+
implemented.
|
|
54
|
+
|
|
55
|
+
4. **Removed methods are refused by name, not dropped.** The liveness method was removed
|
|
56
|
+
in the newest revision, so a client on it gets a method error saying which revision
|
|
57
|
+
removed it, while a client on either older revision still gets its answer. Dropping the
|
|
58
|
+
method entirely would have broken the older clients; answering it to everyone would
|
|
59
|
+
have told the newest one we support something we do not.
|
|
60
|
+
|
|
61
|
+
5. **Every result carries both blocks, and every tool declares both shapes.** The text
|
|
62
|
+
block is what a client that predates structured content reads; the structured block is
|
|
63
|
+
what a client validates against the declared output schema and what it can index. One
|
|
64
|
+
without the other leaves half the clients guessing. The same holds for a failure, which
|
|
65
|
+
stays a result rather than a protocol error, because a client that renders an exception
|
|
66
|
+
renders no answer at all. The declared shape per tool is what makes the pair checkable,
|
|
67
|
+
and the test calls every tool with the least a client would send and validates what
|
|
68
|
+
comes back.
|
|
69
|
+
|
|
70
|
+
6. **The byte ceiling bounds the text block only.** A caller who sets a ceiling wants a
|
|
71
|
+
smaller answer, not a malformed one: the structured block stays whole, so it keeps
|
|
72
|
+
matching the schema, and the text block says how much was omitted and how to ask for
|
|
73
|
+
more. A tool that truncated the structured block would make the one representation a
|
|
74
|
+
client can validate fail validation.
|
|
75
|
+
|
|
76
|
+
7. **The schema checker is in this repository.** The output schemas are here, so the thing
|
|
77
|
+
that reads them is here too: a dependency would be a second copy of rules that could
|
|
78
|
+
drift out of date without anyone noticing, and a tool that has to validate an answer
|
|
79
|
+
has to work on a machine where nothing was installed. The checker understands a small
|
|
80
|
+
set of keywords and throws on one it does not, because a keyword it ignored would turn
|
|
81
|
+
every schema using it into decoration — the check would pass, and the payload would not
|
|
82
|
+
match.
|
|
83
|
+
|
|
84
|
+
## Consequences
|
|
85
|
+
|
|
86
|
+
A client on any of the three revisions gets an answer in the shape it asked for, and one on
|
|
87
|
+
the newest can cache the tool list for an hour instead of asking on every conversation. A
|
|
88
|
+
client that validates gets a real failure message when a payload grows a field nobody
|
|
89
|
+
wrote down, which is how the schemas here were corrected on the day they were written:
|
|
90
|
+
five of them described a payload that does not exist in that shape.
|
|
91
|
+
|
|
92
|
+
Nothing here adds a resource. Rules and decisions are readable through tools today, and
|
|
93
|
+
publishing them as resources is the next half of the review's fix rather than a detail of
|
|
94
|
+
this one.
|
|
95
|
+
|
|
96
|
+
## Rejected
|
|
97
|
+
|
|
98
|
+
- **Implementing the newest revision only.** It would have broken every client that
|
|
99
|
+
negotiates, and the specification's own guidance for `initialize` is to answer with what
|
|
100
|
+
the server speaks.
|
|
101
|
+
- **Echoing whatever version a client asks for, as the old code did.** That was the
|
|
102
|
+
original defect: the echo was true for one revision and a guess for the rest.
|
|
103
|
+
- **Answering the removed liveness method to everyone.** A client on the newest revision
|
|
104
|
+
would read a success as support for a method the protocol deleted.
|
|
105
|
+
- **Truncating the structured block with the text.** The one representation a client can
|
|
106
|
+
check would fail the check.
|
|
107
|
+
- **Taking a JSON Schema validator from npm.** Zero runtime dependencies is a hard
|
|
108
|
+
requirement, and the schemas being validated live in this file, so the rules and the
|
|
109
|
+
reader of the rules belong together.
|
|
110
|
+
- **Declaring a resource capability with no resources behind it.** A capability is a
|
|
111
|
+
promise a client will wait on, so it is declared only when a method answers.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# ADR-069 — The conformance lint checks the published specification
|
|
2
|
+
|
|
3
|
+
Status: **accepted** · 2026-10-13 · filed as S44
|
|
4
|
+
**Affects:** `graph lint`, `skills install`, `src/graph/spec.mjs`, `src/graph/speccheck.mjs`,
|
|
5
|
+
`src/commands/graph.mjs`, `src/commands/skillsinstall.mjs`, `test/graphspec.test.mjs`
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
Review 0002 found that the Agent Skills specification sets out limits this package does not
|
|
10
|
+
check, and that `graph lint` — the command whose whole job is to hold skills to their
|
|
11
|
+
declarations — checks bytes and structure instead. Forty extra characters of description
|
|
12
|
+
are not a formatting problem: a client that validates a skill at load time will refuse it,
|
|
13
|
+
and the discoverer of that is whoever runs the client, not whoever wrote the file.
|
|
14
|
+
|
|
15
|
+
So the specification was fetched and read rather than recalled. Two things fell out of
|
|
16
|
+
reading it. The first is that the review's list was incomplete: it named the name, the
|
|
17
|
+
description and the compatibility limits, and missed four rules — a name may not start or
|
|
18
|
+
end with a hyphen, may not contain consecutive hyphens, and must equal the directory it
|
|
19
|
+
lives in, and the main file is recommended to stay under five hundred lines. The second is
|
|
20
|
+
that the specification is explicit about which of its rules are musts and which are
|
|
21
|
+
recommendations, which is a distinction a lint has to keep.
|
|
22
|
+
|
|
23
|
+
It also turns out most real descriptions use a folded block scalar, `description: >-` with
|
|
24
|
+
indented lines under it. A reader that treated the arrow as the value measured a
|
|
25
|
+
two-hundred-character description as two characters, and the first run of this lint produced
|
|
26
|
+
that wrong answer on five real skills before the reader was rewritten.
|
|
27
|
+
|
|
28
|
+
## Rulings
|
|
29
|
+
|
|
30
|
+
1. **The lint checks the specification it names, and the flag refuses any other name.**
|
|
31
|
+
`--spec agentskills` is the only value that works, and a different one is a usage error
|
|
32
|
+
rather than an unnamed default. A lint against an unnamed set of rules cannot be argued
|
|
33
|
+
with, and the argument is the whole point of publishing the rules.
|
|
34
|
+
|
|
35
|
+
2. **A must is an error and a recommendation is a warning.** Name, description and
|
|
36
|
+
compatibility limits, a name that disagrees with its directory, and a file not named
|
|
37
|
+
SKILL.md fail the exit code. Body length and line count warn, because the specification
|
|
38
|
+
words them as recommendations for progressive disclosure, and a lint that fails on a
|
|
39
|
+
recommendation teaches a reader to skip the lint — which costs it every rule it does
|
|
40
|
+
enforce.
|
|
41
|
+
|
|
42
|
+
3. **The name rules live in one place, and `skills install` asks them too.** The
|
|
43
|
+
specification requires a name to equal its directory, and an install creates the
|
|
44
|
+
directory from the name. So an install is not the place to allow a name the lint would
|
|
45
|
+
reject: it is the last moment anybody can notice before a skill is deployed that no
|
|
46
|
+
client can address by the name inside it. The command refuses with the rule broken.
|
|
47
|
+
|
|
48
|
+
4. **The frontmatter reader reads block scalars.** A folded or literal block is the ordinary
|
|
49
|
+
spelling of a long description, and a reader that misses one reports a conforming skill
|
|
50
|
+
as broken. This is the one rule in this file that is about the reader rather than the
|
|
51
|
+
skill, and it is here because the first version of this lint was confidently wrong about
|
|
52
|
+
five files.
|
|
53
|
+
|
|
54
|
+
5. **The conformance pass is additive to the registry pass, not gated by it.** The registry
|
|
55
|
+
answers "does the bookkeeping match the files"; the specification answers "would a
|
|
56
|
+
client accept these files". A reader with no registry can still lint a skill directory,
|
|
57
|
+
so the two passes run side by side, and a path the first cannot read is a warning from
|
|
58
|
+
the second and never a second error for the same thing — one defect, one exit path.
|
|
59
|
+
|
|
60
|
+
6. **Progressively disclosed fields that are not musts are reported as unknowns, not
|
|
61
|
+
rejected.** An experimental field and a vendor's extra key appear as warnings naming the
|
|
62
|
+
field. The specification says clients may add metadata, and a lint that failed on it
|
|
63
|
+
would fail on a skill built exactly the way the specification invites.
|
|
64
|
+
|
|
65
|
+
## Consequences
|
|
66
|
+
|
|
67
|
+
Thirty warnings and no errors across the skills this workspace declares, which is the
|
|
68
|
+
outcome the review predicted. The warnings are real: seven bodies over the recommended
|
|
69
|
+
size, one at six times it, and a set of vendor-specific frontmatter keys the specification
|
|
70
|
+
does not define. None of those is a defect in the skills — they are the cost a reader pays
|
|
71
|
+
per activation, which is what the recommendation exists to make visible.
|
|
72
|
+
|
|
73
|
+
The naming rules now hold at install time as well as at lint time, so a skill can no longer
|
|
74
|
+
be written to a directory its own name contradicts.
|
|
75
|
+
|
|
76
|
+
## Rejected
|
|
77
|
+
|
|
78
|
+
- **Recalling the limits instead of reading them.** The review's summary was missing four
|
|
79
|
+
of them, and the ones it missed are the ones that break an install.
|
|
80
|
+
- **A byte-length check alone.** The specification counts characters and tokens, and the
|
|
81
|
+
body limit is a token recommendation with a line count beside it; a byte check answers a
|
|
82
|
+
different question.
|
|
83
|
+
- **Failing the exit code on the body recommendations.** They are recommendations, and a
|
|
84
|
+
gate that fails on advice is a gate that gets skipped.
|
|
85
|
+
- **A dependency on the upstream reference validator.** It would be a second copy of the
|
|
86
|
+
rules, maintained elsewhere, and this package ships no runtime dependencies.
|
|
87
|
+
- **Rejecting an unknown frontmatter field.** The specification invites extra metadata, and
|
|
88
|
+
a lint that failed on it would refuse skills built to specification.
|
|
89
|
+
- **Two copies of the name rules, one for the lint and one for the install.** The one
|
|
90
|
+
nobody linted would be the one that let an unaddressable skill through.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# ADR-070 — The JSON payload carries its own version
|
|
2
|
+
|
|
3
|
+
Status: **accepted** · 2026-10-13 · filed as S51
|
|
4
|
+
**Affects:** `src/output/index.mjs`, `src/commands/index.mjs`, `src/commands/mcp.mjs`,
|
|
5
|
+
`src/commands/graph.mjs`, `src/commands/rules.mjs`, `src/commands/skills.mjs`,
|
|
6
|
+
`src/commands/skillsinstall.mjs`, `src/commands/init.mjs`, `src/argv.mjs`,
|
|
7
|
+
`src/commands/help.mjs`, `test/jsoncontract.test.mjs`
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
|
|
11
|
+
Review 0002 filed that `--json` output had no schema version and no compact form:
|
|
12
|
+
`emitJson` was `JSON.stringify(value, null, 2)` and every payload was four columns of
|
|
13
|
+
whitespace. Two consequences, both invisible until they mattered.
|
|
14
|
+
|
|
15
|
+
The first is that a reader could not detect a shape change. An agent that parses `--json`
|
|
16
|
+
gets a document that parses, and if a field is renamed the parse still succeeds and the
|
|
17
|
+
next lookup returns `undefined`. A version in a README does not help: nobody reads the
|
|
18
|
+
README, and it goes stale without failing anything. The version has to travel inside the
|
|
19
|
+
payload, because the payload is the only thing a reader is guaranteed to see.
|
|
20
|
+
|
|
21
|
+
The second is that indentation is paid for on every call. The reader of `--json` is
|
|
22
|
+
`JSON.parse`, not an eye; the same document is a third larger for nothing. The one case
|
|
23
|
+
where indentation is worth its bytes is a terminal, where a person is the reader.
|
|
24
|
+
|
|
25
|
+
Writing the contract test found the two things the review missed. `doctor` did not use
|
|
26
|
+
`emitJson` at all — it hand-rolled `emit(JSON.stringify(..., null, 2))` — and neither did
|
|
27
|
+
`mcp --list`, `graph lint`, `rules list`, `skills index`, `skills find`, `skills show` or
|
|
28
|
+
`skills install`, so seven surfaces answered `--json` in a shape no reader could rely on.
|
|
29
|
+
And `fail()` wrote only to stderr, which means a command that failed under `--json`
|
|
30
|
+
returned an empty stdout: the caller was told something went wrong and given no way to
|
|
31
|
+
learn what. A failure is a result — the question was asked and the answer is "no, and here
|
|
32
|
+
is why" — and it belongs where results go.
|
|
33
|
+
|
|
34
|
+
One note on how this was found, because it is the argument for the test existing at all:
|
|
35
|
+
the contract test itself passed a `mcp` entry whose argument list was silently dropped by
|
|
36
|
+
an `Array.isArray` mistake, so it ran `mcp --json` with no `--list`, and the command served
|
|
37
|
+
on stdio and waited forever. A contract that cannot fail loudly is not a contract.
|
|
38
|
+
|
|
39
|
+
## Rulings
|
|
40
|
+
|
|
41
|
+
1. **The version is in the payload, not in the documentation.**
|
|
42
|
+
`schemaVersion` is written into every object payload that does not already carry one.
|
|
43
|
+
Adding a field is not a bump; removing, renaming or retyping one is. A reader that
|
|
44
|
+
wants to know what changed reads the field, not a changelog.
|
|
45
|
+
|
|
46
|
+
2. **Compact is the default and `--pretty` asks for indentation, with a terminal as the
|
|
47
|
+
exception.** A pipe gets one document on one line because its reader is a program; a TTY
|
|
48
|
+
gets indentation because its reader is a person. The flag exists to override the
|
|
49
|
+
terminal case, not the pipe.
|
|
50
|
+
|
|
51
|
+
3. **One place decides the shape for every command.** `dispatch` sets the indent default
|
|
52
|
+
and the failure mode once, from the parsed flags. Threading either through twenty-five
|
|
53
|
+
call sites would mean twenty-five places to forget, and the one that forgot would be the
|
|
54
|
+
one a reader pipes into a script.
|
|
55
|
+
|
|
56
|
+
4. **A failure under `--json` is a JSON document on stdout.** `ok: false`, the error, the
|
|
57
|
+
remedy and the exit code. Without this, a scripted caller has an empty document and a
|
|
58
|
+
non-zero exit and nothing else, which is the failure mode that makes an agent give up
|
|
59
|
+
rather than retry.
|
|
60
|
+
|
|
61
|
+
5. **`emitJson` owns the byte ceiling too.** Every `emit(JSON.stringify(...))` call that
|
|
62
|
+
also passed `maxBytes` becomes an `emitJson(payload, { maxBytes })` call, so the
|
|
63
|
+
truncation announcement reads the same everywhere and no surface gets an unbounded
|
|
64
|
+
document by being spelled differently.
|
|
65
|
+
|
|
66
|
+
6. **The contract test is generated from the command registry.** It iterates the commands
|
|
67
|
+
that emit JSON, runs each through the real binary, and asserts one line, no leading
|
|
68
|
+
whitespace, a parse, and the version. A command added to the registry is not
|
|
69
|
+
automatically in the test — the test asserts the list is still real, and the list is the
|
|
70
|
+
contract.
|
|
71
|
+
|
|
72
|
+
## Consequences
|
|
73
|
+
|
|
74
|
+
Every `--json` payload now begins with `schemaVersion`, and twelve commands no longer have
|
|
75
|
+
a private idea of what `--json` means. `doctor --json` went from seventeen indented lines
|
|
76
|
+
to one, and the failure of `skills install` in a bare directory now answers in the shape
|
|
77
|
+
its caller asked for.
|
|
78
|
+
|
|
79
|
+
What is not here: the README half of the review's fix — an exit-code table and a per-command
|
|
80
|
+
JSON contract generated from the registry. The exit codes are already named in `EXIT`, and
|
|
81
|
+
generating README prose from them is a documentation change that belongs to whoever owns
|
|
82
|
+
the README, so it is noted in the tracker rather than done silently here.
|
|
83
|
+
|
|
84
|
+
## Rejected
|
|
85
|
+
|
|
86
|
+
- **A version in a comment or the changelog only.** It is exactly the artefact that goes
|
|
87
|
+
stale without failing anything.
|
|
88
|
+
- **Threading `--pretty` through the call sites.** Twenty-five chances to forget, to fix a
|
|
89
|
+
problem that has one place to live.
|
|
90
|
+
- **Keeping failures on stderr under `--json`.** It leaves a scripted caller with an empty
|
|
91
|
+
document and a number, which is a contract that cannot be acted on.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# ADR-071 — A rule that can be narrowed is written narrow
|
|
2
|
+
|
|
3
|
+
Status: **accepted** · 2026-10-14 · filed as S50
|
|
4
|
+
**Affects:** `src/projection/rules.mjs`, `src/projection/targets.mjs`,
|
|
5
|
+
`src/projection/surfaces.mjs`, `src/projection/project.mjs`, `rules/stacks/*.md`,
|
|
6
|
+
`test/scoped.test.mjs`, `test/project.test.mjs`, `test/surfaces.test.mjs`
|
|
7
|
+
|
|
8
|
+
## Context
|
|
9
|
+
|
|
10
|
+
Every per-rule file this tool ships was always-on, and so was every stack profile, because
|
|
11
|
+
profiles had nowhere else to live: they reached a reader through the generated region in
|
|
12
|
+
AGENTS.md and through nothing else. A repository on Cursor got the profile in both places,
|
|
13
|
+
and a repository on opencode got it in one place, always, whether or not a Python file was
|
|
14
|
+
ever open.
|
|
15
|
+
|
|
16
|
+
Two vendors document how to make that conditional, and both were read rather than recalled.
|
|
17
|
+
Cursor attaches a rule with `alwaysApply: false` and `globs` "when a matching file is in
|
|
18
|
+
context", and separates patterns with commas. Antigravity takes `trigger: glob` with a
|
|
19
|
+
comma-separated `globs`, requires patterns that start with `*` to be **quoted** so YAML does
|
|
20
|
+
not read them as alias anchors, and **discards a rule with invalid frontmatter without a
|
|
21
|
+
message**. It also documents a 24 KB per-file limit and a 20,000-token budget for always_on
|
|
22
|
+
rules, past which rules are demoted to pointers — so an eager profile is not merely
|
|
23
|
+
wasteful, it is a budget the reader spends on rules that did not apply.
|
|
24
|
+
|
|
25
|
+
One row of the surface table is the reason this needed a ruling rather than a patch.
|
|
26
|
+
Decision 3 — an always-on rule emits no scoping frontmatter — was settled on Claude Code's
|
|
27
|
+
`paths:` key silently failing to lazy-load for quoted values and YAML sequences
|
|
28
|
+
(anthropics/claude-code#13905, #17204, #19377), with the documented examples not valid YAML.
|
|
29
|
+
That decision stands. This record says what happens on either side of it, and does not
|
|
30
|
+
rewrite it.
|
|
31
|
+
|
|
32
|
+
## Rulings
|
|
33
|
+
|
|
34
|
+
1. **A profile leaves the region only when every selected surface can narrow it.**
|
|
35
|
+
`desiredRegion` asks whether all of them can; when one cannot, the profile stays in the
|
|
36
|
+
region and a note says which surface stopped it. The alternative — writing the profile
|
|
37
|
+
only as a scoped file for the three surfaces that can scope it — leaves a Claude Code
|
|
38
|
+
reader with no rule at all, and a rule nobody loads is worse than a rule that loads
|
|
39
|
+
eagerly.
|
|
40
|
+
|
|
41
|
+
2. **Three surfaces can narrow, and the answer is per surface rather than per key.**
|
|
42
|
+
Cursor (`globs`), Antigravity (`trigger: glob` plus `globs`) and Copilot (`applyTo`)
|
|
43
|
+
can; Claude Code cannot, for the reason in decision 3; opencode cannot, because it has
|
|
44
|
+
no per-rule channel at all (`rule: null`). `canScope` names the answer, and the shipped
|
|
45
|
+
gate now probes the scoped frontmatter shape as well as the eager one, so a row that
|
|
46
|
+
emitted `globs` without documenting it would fail the gate rather than ship an inert
|
|
47
|
+
key.
|
|
48
|
+
|
|
49
|
+
3. **Antigravity's patterns are quoted, and a scoped rule always carries a description.**
|
|
50
|
+
Unquoted `**/*.py` is an alias anchor and the whole rule is discarded silently. Cursor
|
|
51
|
+
documents no description plus no globs as manual `@`-mention only, so a scoped rule with
|
|
52
|
+
neither would never attach to anything — a rule that looks installed and is not.
|
|
53
|
+
|
|
54
|
+
4. **A scope that matches everything is a defect, not a narrowing.** `**`, `**/*` and `*`
|
|
55
|
+
are what a surface applies when it is given no pattern, so a profile carrying one is
|
|
56
|
+
the eager profile wearing a costume. An empty list is the same thing spelled plainly.
|
|
57
|
+
Both are refused by `describeDefect`, where a profile's other frontmatter is checked.
|
|
58
|
+
|
|
59
|
+
5. **A quoted pattern is read as the pattern.** `parseFrontmatter` did not strip quotes, so
|
|
60
|
+
a profile author quoting `"src/**/*.ts"` would have projected a glob matching nothing.
|
|
61
|
+
The reader now strips them, because the alternative is that the obvious spelling of a
|
|
62
|
+
glob silently does not work.
|
|
63
|
+
|
|
64
|
+
6. **One construction, and the gate uses it.** `scopedTargets` delegates to `ruleTargets`
|
|
65
|
+
with the profile marked `alwaysApply: false`, and `checkTargets` calls the same function
|
|
66
|
+
`plan` does. A scoped file that was planned and written but never gated would show a
|
|
67
|
+
clean `check` over a stale reader — the asymmetry this module exists to prevent.
|
|
68
|
+
|
|
69
|
+
7. **The region keeps a pointer section.** A profile that moved is named in AGENTS.md with
|
|
70
|
+
its patterns and the files it moved to. A reader who knows the profile was there and
|
|
71
|
+
cannot find it will otherwise conclude it was deleted, and the region's source list is
|
|
72
|
+
the provenance a review reads.
|
|
73
|
+
|
|
74
|
+
8. **The shipped profiles carry the scope that selected them.** Eighteen of nineteen: node,
|
|
75
|
+
typescript, react-web, react-native, python, uv, rust, go, ruby, jvm, swift, dotnet,
|
|
76
|
+
dart, deno, elixir, android, cloudflare-workers, container. Each is anchored on the file
|
|
77
|
+
whose presence selected it, so a reader loads the profile exactly when the thing that
|
|
78
|
+
made it apply is in play. `connectiq` has no scope: it is selected by a tag no file
|
|
79
|
+
announces, and inventing an anchor for it would be a claim about a file this tool has
|
|
80
|
+
never seen.
|
|
81
|
+
|
|
82
|
+
## Consequences
|
|
83
|
+
|
|
84
|
+
A repository on Cursor, Copilot or Antigravity now receives its stack profiles as
|
|
85
|
+
`.cursor/rules/python.mdc`, `.agents/rules/python.md` and
|
|
86
|
+
`.github/instructions/python.instructions.md`, each carrying the patterns and no
|
|
87
|
+
`alwaysApply: true`, with a pointer in the region replacing the profile text. A repository
|
|
88
|
+
whose surfaces include opencode or Claude Code sees no change at all, and says why in a
|
|
89
|
+
note when it asked for narrowing it could not have.
|
|
90
|
+
|
|
91
|
+
A monorepo gets the same treatment per package, because package regions call the same
|
|
92
|
+
`desiredRegion` (ADR-066).
|
|
93
|
+
|
|
94
|
+
## Rejected
|
|
95
|
+
|
|
96
|
+
- **Emitting `paths:` for Claude Code anyway.** Its parser defect is documented with issue
|
|
97
|
+
numbers and the failure is silent: the reader loads nothing and believes the rule is
|
|
98
|
+
installed.
|
|
99
|
+
- **Narrowing on whichever surfaces can, and leaving the rest to find the profile
|
|
100
|
+
elsewhere.** It trades an eagerly loaded rule for a rule that is never loaded, which is
|
|
101
|
+
the same defect with a worse diagnosis.
|
|
102
|
+
- **A catch-all default scope for a profile with none.** It would look like narrowing in a
|
|
103
|
+
diff and behave exactly like not narrowing.
|
|
104
|
+
- **Reusing the mdc rule-file path for a profile file.** The name comes from `ruleName`,
|
|
105
|
+
which reads `.mdc` targets; a profile has none, and the first version of this wrote
|
|
106
|
+
`null.mdc` and then `python.md.mdc` before either was noticed.
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# ADR-072 — The budget is a declaration beside the gates, not a number inside the projection
|
|
2
|
+
|
|
3
|
+
Status: **accepted** · 2026-10-14 · filed as S38
|
|
4
|
+
**Affects:** `src/projection/manifest.mjs`, `src/measure/context.mjs`,
|
|
5
|
+
`src/projection/project.mjs`, `src/commands/sync.mjs`, `simt tokens`, `simt-dev check`,
|
|
6
|
+
`test/tokens.test.mjs`, `test/surfaces.test.mjs`
|
|
7
|
+
|
|
8
|
+
## Context
|
|
9
|
+
|
|
10
|
+
Review 0002 row S38 asks for a per-repository always-on budget: "for these 7 roots,
|
|
11
|
+
generated always-on bytes are at most 30 KB". Plan 0003 spells the mechanism as R08, and
|
|
12
|
+
the plan was read rather than recalled, which is how the two facts below were found.
|
|
13
|
+
|
|
14
|
+
The first is that R08 as written presupposes R07. Its compiler "packs kernel lines by
|
|
15
|
+
priority class" and `check` reports "each cut line" — and there are no kernel lines
|
|
16
|
+
without R07's `projection.mode` and the per-line kernel frontmatter it introduces, whose own default stays
|
|
17
|
+
`full` until R02 proves a narrow mode is not worse. With no kernel there is nothing to cut,
|
|
18
|
+
so the packing half of R08 cannot be built honestly before the R01–R05 evaluation has been
|
|
19
|
+
run. Building it anyway would mean inventing a kernel nobody measured.
|
|
20
|
+
|
|
21
|
+
The second is where the number lives. R08 says `projection.budget.bytes`. In this tool
|
|
22
|
+
`projection` is a generated key: `GENERATED_KEYS` names it, and `withGenerated()` replaces
|
|
23
|
+
it with the tool's own `{ simtDevSha, version }` on every write. A budget written there
|
|
24
|
+
would be deleted by the next `upgrade`, and — because `manifestDrift` compares only the
|
|
25
|
+
generated keys — not reported as deleted. The repo would watch its budget disappear on a
|
|
26
|
+
green gate. That is the same defect this package treats as unrecoverable everywhere else:
|
|
27
|
+
content the repository chose must be preserved by the repair, not overwritten by it.
|
|
28
|
+
|
|
29
|
+
So the part of R08 that is independent of the kernel ships now, and the part that is not
|
|
30
|
+
does not.
|
|
31
|
+
|
|
32
|
+
## Rulings
|
|
33
|
+
|
|
34
|
+
1. **The budget is a repository declaration at the top level of the manifest, beside
|
|
35
|
+
`limits` and `gates`.** It is content the repo chose, so it survives `upgrade`, and the
|
|
36
|
+
plan's `projection.budget.bytes` spelling is superseded rather than edited in place. The
|
|
37
|
+
default is not a field either: it is the value whoever measures applies when the key is
|
|
38
|
+
absent, so `simt tokens` and the gate read one number from one place.
|
|
39
|
+
|
|
40
|
+
2. **A repository that declares nothing is not reported as having declared the default.**
|
|
41
|
+
`buildManifest` omits the key entirely when the repo has none, which keeps
|
|
42
|
+
`budget.declared` an honest fact. Writing the default in would make every adopted repo
|
|
43
|
+
look like it had chosen the review's line — a reader told "declared: true" about a
|
|
44
|
+
number nobody wrote is being lied to about the only thing the number is for.
|
|
45
|
+
|
|
46
|
+
3. **The default is 30,000 bytes and it is the review's figure, not this tool's
|
|
47
|
+
measurement.** It is written as 30,000 rather than 30 × 1024 because it is a ceiling and
|
|
48
|
+
the conservative reading of "at most 30 KB" is the one that trips first.
|
|
49
|
+
|
|
50
|
+
4. **Over budget is reported, never enforced.** The finding names the bytes measured, the
|
|
51
|
+
bytes declared and what was not done: nothing was cut, because the projection is still
|
|
52
|
+
in full mode. A reader who sees an over-budget line must not conclude the projection
|
|
53
|
+
shrank, and a gate that failed here would fail on a measurement this tool has not
|
|
54
|
+
validated against a study.
|
|
55
|
+
|
|
56
|
+
5. **`simt tokens` carries the finding it would raise, produced by the same function.**
|
|
57
|
+
One function words the fact, so the report and the gate cannot describe the same
|
|
58
|
+
overflow two ways.
|
|
59
|
+
|
|
60
|
+
6. **The rows themselves are unchanged.** Which rules move behind skills is R01–R05's
|
|
61
|
+
finding, not this record's, and a budget that silently dropped rules would be enforcing
|
|
62
|
+
a decision nobody has made.
|
|
63
|
+
|
|
64
|
+
7. **The meter is the definition of the number, and the gate quotes it.**
|
|
65
|
+
`simt-dev check` calls the same `measureRoot` the meter does rather than summing the
|
|
66
|
+
bytes of the targets it happens to compare. That was the other way round first, and it
|
|
67
|
+
was wrong: the meter also counts managed skills and `.mdc` files, so one repository had
|
|
68
|
+
two budgets and two different numbers for the same declaration. Two definitions of one
|
|
69
|
+
ceiling is the same defect as two projections of one fact, one layer up.
|
|
70
|
+
|
|
71
|
+
8. **The gate raises the finding and does not block on it.** `budget` is in both
|
|
72
|
+
non-blocking sets, `check` and `sync` alike. Nothing was cut, so the projection is
|
|
73
|
+
exactly what the repository agreed to project; failing the run over a ceiling it wrote
|
|
74
|
+
down itself would train people to raise the ceiling. R08 says non-blocking for the same
|
|
75
|
+
reason, and this record agrees with it now that the finding has a shape to be.
|
|
76
|
+
|
|
77
|
+
9. **The cost of ruling 7 is accepted rather than avoided.** `check` now reads what the
|
|
78
|
+
meter reads — the projected region, the rule files, the skills — on every run, where it
|
|
79
|
+
previously compared bytes it had already read. A cheaper gate would have to keep its own
|
|
80
|
+
notion of what counts, and this is the one place where the two are not allowed to differ.
|
|
81
|
+
|
|
82
|
+
## Consequences
|
|
83
|
+
|
|
84
|
+
S38's accept criterion is now checkable rather than asserted: a repository can declare the
|
|
85
|
+
line it intends to carry, and `simt tokens` reports it against that line and says how far
|
|
86
|
+
over it is. Three tests hold the two halves that were easy to get backwards — the budget
|
|
87
|
+
survives an upgrade, a repository that declared nothing keeps `declared: false`, and the
|
|
88
|
+
finding says that nothing was cut.
|
|
89
|
+
|
|
90
|
+
What has not landed: the gate reporting the budget as a finding, and R08's packing. The
|
|
91
|
+
first needs the number the gate is already comparing — the bytes of the region and the rule
|
|
92
|
+
files it checks — so that `check` and `tokens` cannot report different totals for one
|
|
93
|
+
repository; that is a wiring decision, not a measurement. The second needs R07.
|
|
94
|
+
|
|
95
|
+
## Rejected
|
|
96
|
+
|
|
97
|
+
- **The plan's `projection.budget.bytes`.** The generated block is replaced wholesale on
|
|
98
|
+
every upgrade, and nothing in the repair would notice.
|
|
99
|
+
- **A default written into every manifest.** It would make every adopted repo look like it
|
|
100
|
+
had chosen a number it never chose.
|
|
101
|
+
- **Cutting lines to fit the budget now.** There is no kernel to pack and no measurement
|
|
102
|
+
saying which lines a reader can lose.
|
|
103
|
+
- **Failing the gate on an over-budget projection.** The number is this tool's own
|
|
104
|
+
estimate, and a gate that fails on its own estimate teaches a reader to ignore it.
|