simt-dev 0.0.0-stage → 0.1.1
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 +130 -0
- package/LICENSE +202 -0
- package/README.md +92 -2
- package/bin/simt-dev.mjs +28 -0
- package/docs/decisions/ADR-001-push-requires-approval.md +42 -0
- package/docs/decisions/ADR-002-refactor-blast-radius.md +45 -0
- package/docs/decisions/ADR-003-test-breadth.md +46 -0
- package/docs/decisions/ADR-004-hook-owns-format.md +45 -0
- package/docs/decisions/ADR-005-file-length.md +42 -0
- package/docs/decisions/ADR-006-coverage-bar.md +59 -0
- package/docs/decisions/ADR-007-section-skeleton.md +54 -0
- package/docs/decisions/ADR-008-verdict-vocabulary.md +59 -0
- package/docs/decisions/ADR-009-skill-layout.md +48 -0
- package/docs/decisions/ADR-010-runner-invocation.md +56 -0
- package/docs/decisions/ADR-011-windows-support.md +52 -0
- package/docs/decisions/ADR-012-drop-windows-support.md +59 -0
- package/docs/decisions/ADR-013-genericity-by-facts-and-local-profiles.md +114 -0
- package/docs/decisions/ADR-014-workspace-is-configuration.md +211 -0
- package/docs/decisions/ADR-015-a-source-is-content-not-a-location.md +117 -0
- package/docs/decisions/ADR-016-a-divergent-rule-is-retired-not-merged.md +180 -0
- package/docs/decisions/ADR-017-a-declared-gate-must-be-runnable.md +89 -0
- package/docs/decisions/ADR-018-an-unset-hooks-path-is-not-an-absent-hook.md +183 -0
- package/docs/decisions/ADR-019-a-declared-command-is-the-runner.md +110 -0
- package/docs/decisions/ADR-020-a-rule-is-written-per-surface-and-pointed-not-copied.md +100 -0
- package/docs/decisions/ADR-021-one-surface-per-repo.md +94 -0
- package/docs/decisions/ADR-022-a-key-simt-dev-reads-is-prefixed.md +98 -0
- package/docs/decisions/ADR-023-the-gate-ships-as-a-file-the-repo-owns.md +106 -0
- package/docs/decisions/ADR-024-simt-dev-overwrites-its-own-gate-file.md +111 -0
- package/docs/decisions/ADR-025-a-baseline-is-a-region-in-a-file-the-repo-owns.md +153 -0
- package/docs/decisions/ADR-026-a-config-without-a-region-is-a-file-the-repo-owns.md +117 -0
- package/docs/decisions/ADR-027-the-one-gitattributes-line-that-is-neither-dangerous-nor-empty.md +130 -0
- package/docs/decisions/ADR-028-a-base-file-is-a-floor-so-the-disagreement-it-cannot-repair-becomes-a-check.md +131 -0
- package/docs/decisions/ADR-029-the-pins-a-repository-declares-are-its-own.md +120 -0
- package/docs/decisions/ADR-030-what-a-plan-does-not-ship-is-a-decision-with-a-record.md +114 -0
- package/docs/decisions/ADR-031-the-suite-stages-the-environment-it-needs.md +129 -0
- package/docs/decisions/ADR-032-provenance-is-a-content-digest-not-a-commit.md +141 -0
- package/docs/decisions/ADR-033-three-plan-rows-closed-by-decision-not-by-authoring.md +82 -0
- package/docs/decisions/ADR-034-a-staged-tool-refuses-a-partially-staged-file.md +91 -0
- package/docs/decisions/ADR-035-one-write-decision-and-one-json-document.md +119 -0
- package/docs/decisions/ADR-036-a-writer-that-writes-says-so-and-the-instructions-naming-it-move-with-it.md +103 -0
- package/docs/decisions/ADR-037-unchanged-means-writing-would-not-change-a-byte.md +100 -0
- package/docs/decisions/ADR-038-the-gate-filter-belongs-to-every-manifest-writer.md +95 -0
- package/docs/decisions/ADR-039-a-ci-step-runs-a-form-that-can-fail.md +101 -0
- package/docs/decisions/ADR-040-the-last-writer-that-wrote-by-default-was-not-the-one-named.md +158 -0
- package/docs/decisions/ADR-041-the-suite-installs-the-package-in-order-to-test-it.md +92 -0
- package/docs/decisions/ADR-042-a-declared-glob-must-match-a-path-built-from-it.md +94 -0
- package/docs/decisions/ADR-043-a-keyword-is-a-phrase-and-two-letters-is-a-word.md +90 -0
- package/docs/decisions/ADR-044-compatibility-is-not-evidence.md +116 -0
- package/docs/decisions/ADR-045-a-dashboard-that-counts-a-defect.md +119 -0
- package/docs/decisions/ADR-046-one-rule-one-copy.md +140 -0
- package/docs/decisions/ADR-047-a-profile-selects-on-what-a-repository-runs-on.md +141 -0
- package/docs/decisions/ADR-048-a-baseline-carries-only-keys-a-reader-honours.md +150 -0
- package/docs/decisions/ADR-049-a-ruling-that-a-later-record-moved-says-so-where-it-is-printed.md +153 -0
- package/docs/decisions/ADR-050-a-byte-count-is-not-a-region-and-updated-is-not-a-change-of-rules.md +102 -0
- package/docs/decisions/ADR-051-a-pointer-is-the-readers-only-route-so-it-is-not-a-convenience.md +134 -0
- package/docs/decisions/ADR-052-a-dependency-is-counted-against-the-budget-and-there-is-one-budget.md +101 -0
- package/docs/decisions/ADR-053-the-node-floor-is-22-20-0.md +49 -0
- package/docs/decisions/ADR-054-the-digest-covers-every-byte-that-leaves-the-package.md +100 -0
- package/docs/decisions/ADR-055-a-status-line-travels-so-it-may-not-name-a-location.md +98 -0
- package/docs/decisions/ADR-056-three-names-one-of-which-is-cheap-to-move.md +160 -0
- package/docs/decisions/README.md +528 -0
- package/graph/skills.graph.json +1622 -0
- package/package.json +41 -4
- package/rules/core.md +76 -0
- package/rules/mdc/intake-questions.mdc +50 -0
- package/rules/mdc/preview-postflight.mdc +61 -0
- package/rules/mdc/slice-commit.mdc +52 -0
- package/rules/mdc/test-gate.mdc +54 -0
- package/rules/stacks/android.md +63 -0
- package/rules/stacks/cloudflare-workers.md +65 -0
- package/rules/stacks/connectiq.md +62 -0
- package/rules/stacks/container.md +31 -0
- package/rules/stacks/jvm.md +54 -0
- package/rules/stacks/node.md +45 -0
- package/rules/stacks/python.md +48 -0
- package/rules/stacks/react-native.md +58 -0
- package/rules/stacks/react-web.md +61 -0
- package/rules/stacks/typescript.md +21 -0
- package/rules/stacks/uv.md +32 -0
- package/src/argv.mjs +116 -0
- package/src/ci/dependabot.mjs +268 -0
- package/src/ci/workflow.mjs +360 -0
- package/src/commands/ci.mjs +285 -0
- package/src/commands/compare.mjs +195 -0
- package/src/commands/graph.mjs +73 -0
- package/src/commands/help.mjs +108 -0
- package/src/commands/hooks.mjs +471 -0
- package/src/commands/index.mjs +241 -0
- package/src/commands/init.mjs +566 -0
- package/src/commands/mcp.mjs +63 -0
- package/src/commands/rules.mjs +374 -0
- package/src/commands/skills.mjs +647 -0
- package/src/commands/skillsinstall.mjs +515 -0
- package/src/commands/sync.mjs +331 -0
- package/src/commands/upgrade.mjs +202 -0
- package/src/compare/discover.mjs +212 -0
- package/src/compare/hooksstate.mjs +159 -0
- package/src/compare/row.mjs +299 -0
- package/src/decisions/index.mjs +512 -0
- package/src/graph/load.mjs +541 -0
- package/src/hooks/capability.mjs +129 -0
- package/src/hooks/dispatcher.mjs +268 -0
- package/src/hooks/gates.mjs +144 -0
- package/src/hooks/resolve.mjs +194 -0
- package/src/hooks/shim.mjs +140 -0
- package/src/hooks/where.mjs +118 -0
- package/src/locate.mjs +75 -0
- package/src/mcp/server.mjs +167 -0
- package/src/mcp/tools.mjs +410 -0
- package/src/name.mjs +95 -0
- package/src/output/index.mjs +96 -0
- package/src/projection/baselines.mjs +209 -0
- package/src/projection/conflicts.mjs +205 -0
- package/src/projection/manifest.mjs +478 -0
- package/src/projection/markers.mjs +349 -0
- package/src/projection/mdc.mjs +327 -0
- package/src/projection/project.mjs +504 -0
- package/src/projection/rules.mjs +640 -0
- package/src/projection/surfaces.mjs +398 -0
- package/src/projection/targets.mjs +221 -0
- package/src/resolve/fingerprint.mjs +553 -0
- package/src/resolve/near.mjs +61 -0
- package/src/resolve/resolve.mjs +457 -0
- package/src/resolve/source.mjs +105 -0
- package/src/toolchain.mjs +202 -0
- package/src/tsconfig.mjs +314 -0
- package/src/upgrade/plan.mjs +203 -0
- package/src/workspace.mjs +289 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# ADR-045 — A dashboard that counts a defect and will not name it is worse than one that does not count it
|
|
2
|
+
|
|
3
|
+
Status: **accepted** · 2026-10-08 · amends ADR-016 · affects what `compare` reports and exits on
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
|
|
7
|
+
Review 0002 §S25, marked P1, reported two defects in one row. Both measured against the estate
|
|
8
|
+
before anything was changed, and both confirmed exactly as written.
|
|
9
|
+
|
|
10
|
+
**One.** `src/compare/row.mjs` gated the stale-rule-file problem on `row.adoption === 'adopted'`.
|
|
11
|
+
`adoption` was initialised at line 78 and only assigned at lines 127–131, so the gate ran before
|
|
12
|
+
anything settled it and compared against the initialiser's `'unmanaged'`. It could never fire.
|
|
13
|
+
|
|
14
|
+
Measured over the 16 repositories at `~/code/simt`:
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
staleRuleFiles: 17
|
|
18
|
+
repos naming stale rule files in problems: 0
|
|
19
|
+
simt-app problems:
|
|
20
|
+
"the generated region was hand-edited; restore it with: simt-dev sync --yes"
|
|
21
|
+
"core.hooksPath is \"hooks\", not the shared dispatcher; run: simt-dev hooks install --yes"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Five adopted repositories have stale rule files — `simt-app`, `simt-blog` and `simt-social` at 0
|
|
25
|
+
of 3 current, `simt-social-api` and `simt-social-media` at 0 of 4 — and not one of them said so in
|
|
26
|
+
its problems list. The summary already said `staleRuleFiles: 17`, because `summarise` has always
|
|
27
|
+
counted them. So the dashboard was reporting a count of a defect it refused to call a defect.
|
|
28
|
+
|
|
29
|
+
**Two.** `profiles` was documented at `row.mjs:33` as "profile ids that would be selected",
|
|
30
|
+
initialised to `[]` at line 83, and never assigned anywhere in the file's life. Every row of every
|
|
31
|
+
run reported no profiles. `plan()` — the thing `sync` renders — returns them.
|
|
32
|
+
|
|
33
|
+
## Ruling
|
|
34
|
+
|
|
35
|
+
**One.** A row's problems list names every stale rule file, and the exit code follows. The gate
|
|
36
|
+
reads `adoption` after it is settled, which means the ordering is part of the contract: **a gate
|
|
37
|
+
that reads a field the line above has not set yet is not a gate.** That sentence is the whole fix.
|
|
38
|
+
|
|
39
|
+
**Two.** The gate stays. A repo that has never been adopted has its own files, and reporting them
|
|
40
|
+
as "not current" turns the normal mid-migration state into a red estate — ADR-018's reasoning for
|
|
41
|
+
hooks, applied here to rule files. There is a test for the negative half specifically, because
|
|
42
|
+
fixing the gate is exactly the kind of change that turns this on.
|
|
43
|
+
|
|
44
|
+
**Three.** `profiles` is filled from the same `select()` call, on the same inputs, that
|
|
45
|
+
`desiredRegion` makes. Not from a copy of the rule table and not from the manifest's declared
|
|
46
|
+
stack: a dashboard that disagrees with the tool about what it would write is worse than one that
|
|
47
|
+
says nothing, because it is believed.
|
|
48
|
+
|
|
49
|
+
**Four.** A field documented as an answer must be either answered or absent. `profiles` was neither:
|
|
50
|
+
it was present, empty, and described as populated in the type it was documented with.
|
|
51
|
+
|
|
52
|
+
## Consequences
|
|
53
|
+
|
|
54
|
+
The estate now names all 17: five repositories carry the problem line, and `compare` exits non-zero
|
|
55
|
+
on them. Nothing else about the detection changed — the count was 17 before the fix and is 17 after
|
|
56
|
+
it. This slice changed reporting, not measurement, and the test asserts that by requiring the
|
|
57
|
+
fixture to start current before the edit.
|
|
58
|
+
|
|
59
|
+
### The ruling this amends
|
|
60
|
+
|
|
61
|
+
ADR-016 ruled that "`compare` continues to report one row per repo using `AGENTS.md` only, because a
|
|
62
|
+
dashboard row cannot represent four files. The gate uses the full finding list, so the dashboard's
|
|
63
|
+
coarser view is not inherited by the thing that must not be wrong."
|
|
64
|
+
|
|
65
|
+
That ruling is about the row's **shape** — which files it displays — and it still holds. The row
|
|
66
|
+
already carried `ruleFiles.total`, `ruleFiles.current` and `ruleFiles.stale` before this change, so
|
|
67
|
+
ADR-016's coarser display was never the problem.
|
|
68
|
+
|
|
69
|
+
What was missing is that ADR-016 reasoned about the *display* and the *problems list* together, and
|
|
70
|
+
they came apart. The dashboard was allowed to be coarse about which files it shows, and it was also
|
|
71
|
+
allowed to be silent about whether an agent reading them would get correct rules. Those are
|
|
72
|
+
different permissions, and only the first was granted on purpose. The problems list is not a
|
|
73
|
+
display; it is the exit code.
|
|
74
|
+
|
|
75
|
+
So: ADR-016 is amended, not overruled. The row stays coarse; the problems list does not.
|
|
76
|
+
|
|
77
|
+
### Three mutations, each caught by exactly its own test
|
|
78
|
+
|
|
79
|
+
| Mutation | Caught by |
|
|
80
|
+
| --- | --- |
|
|
81
|
+
| the stale-rule-file gate cannot fire | `a stale rule file on an adopted repo is a problem` |
|
|
82
|
+
| `profiles` back to its initialiser | `compare reports the profiles sync would project` |
|
|
83
|
+
| `profiles` computed from the declared stack instead of the detected one | `compare reports the profiles sync would project` |
|
|
84
|
+
|
|
85
|
+
The third is the one that matters. It is the shape this defect actually takes when it recurs: not an
|
|
86
|
+
empty array but a plausible array computed from the wrong input. A test asserting a hardcoded
|
|
87
|
+
expected list would have caught the second mutation and passed the third; asserting
|
|
88
|
+
`row.profiles` equals `plan(root).profiles` catches both, because it is the relationship rather
|
|
89
|
+
than the value that is the contract.
|
|
90
|
+
|
|
91
|
+
### A fixture that asserted nothing
|
|
92
|
+
|
|
93
|
+
The first version of the stale-file test appended a line after the `END` marker. That is hand-owned
|
|
94
|
+
content by design — the containment guarantee another test in the same file already asserts — so the
|
|
95
|
+
file was correctly not stale and the test failed. The fixture was making an edit the test claimed
|
|
96
|
+
wasn't being made. It now edits inside the region and asserts that the edit landed by checking the
|
|
97
|
+
recorded SHA marker is present first.
|
|
98
|
+
|
|
99
|
+
A first attempt at the mutation was also too coarse: re-deriving `adoption` in the wrong place broke
|
|
100
|
+
ten tests at once, which told me the ordering is load-bearing but not which gate depended on it. The
|
|
101
|
+
isolated mutation is `if (false && …)` on the one gate.
|
|
102
|
+
|
|
103
|
+
## Rejected
|
|
104
|
+
|
|
105
|
+
**Reporting per-file drift in the row's display, so the dashboard shows all four files.** ADR-016
|
|
106
|
+
already rejected this and its reason has not changed. A row is read across sixteen repositories at
|
|
107
|
+
once; four filenames per row is noise at that scale.
|
|
108
|
+
|
|
109
|
+
**Making `compare` defer entirely to `check` and drop the problems list.** The dashboard is the thing
|
|
110
|
+
a reader runs when they want to know where the estate stands; a command that says "run the other one"
|
|
111
|
+
has moved the problem rather than answered it.
|
|
112
|
+
|
|
113
|
+
**Exiting non-zero on an unmanaged repo's stray rule files.** The gate exists for the reason in
|
|
114
|
+
ruling two, and there is a test that asserts the negative so the fix cannot quietly widen it.
|
|
115
|
+
|
|
116
|
+
**Filling `profiles` from the manifest's declared stack.** It is cheaper and it is wrong: the
|
|
117
|
+
manifest is a declaration, `fingerprint` is a measurement, and `sync` selects on the measurement.
|
|
118
|
+
A dashboard reporting the declaration would be consistent with itself and inconsistent with the
|
|
119
|
+
tool. Mutation three is what that looks like when it happens.
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# ADR-046 — One rule, one copy: a reader that reads AGENTS.md must not also get the file
|
|
2
|
+
|
|
3
|
+
Status: **accepted** · 2026-10-08 · supersedes ADR-021 ruling 3 · settles P7's context-budget question
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
|
|
7
|
+
Only ADR-021 **ruling 3** is superseded. Its other rulings — one surface per repo, a pointer is not
|
|
8
|
+
a copy, a written file is not removed, skills follow the same rule — stand, and ruling 6 is load
|
|
9
|
+
bearing *here*. So the status line deliberately does not name it in the form the index parses
|
|
10
|
+
(`supersedes [ADR-NNN]`): marking the whole record superseded would be false, exactly as it would
|
|
11
|
+
be for a narrowing.
|
|
12
|
+
|
|
13
|
+
Review 0001 §S12, marked P1 and "decision". Measured across the estate before anything changed —
|
|
14
|
+
the five repositories selecting `opencode,cursor`:
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
simt-app folded=3 .mdc=3 0/3 current
|
|
18
|
+
simt-blog folded=3 .mdc=3 0/3 current
|
|
19
|
+
simt-social folded=3 .mdc=3 0/3 current
|
|
20
|
+
simt-social-api folded=4 .mdc=4 0/4 current
|
|
21
|
+
simt-social-media folded=4 .mdc=4 0/4 current
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Seventeen generated rule files. Every one of them byte-identical to the section already folded into
|
|
25
|
+
the same repository's `AGENTS.md`, verified by extracting both bodies and diffing: the only
|
|
26
|
+
difference was a trailing marker line in my first extraction, and the second extraction made 14 of
|
|
27
|
+
the 17 identical and the remaining three identical apart from that marker. **28,101 bytes of context
|
|
28
|
+
loaded twice per session**, in five repositories, forever.
|
|
29
|
+
|
|
30
|
+
ADR-021 ruling 3 decided this deliberately: "Folding is `some`, not `all`: a repo that also opts into
|
|
31
|
+
Cursor still folds, because an opencode reader's only channel is `AGENTS.md` **and a Cursor reader
|
|
32
|
+
getting `.mdc` files does not serve them**."
|
|
33
|
+
|
|
34
|
+
The bolded clause is the whole dispute, and it is a factual claim about a vendor. It is false.
|
|
35
|
+
Cursor's own documentation says `AGENTS.md` is "a simple markdown file for defining agent
|
|
36
|
+
instructions... an alternative to `.cursor/rules`", and that "Cursor supports AGENTS.md in the
|
|
37
|
+
project root and subdirectories." Cursor reads `AGENTS.md`.
|
|
38
|
+
|
|
39
|
+
Note where the original claim came from: the `readers` string on **simt-dev's own opencode row**
|
|
40
|
+
already said "AGENTS.md is also read natively by Codex, Cursor, Windsurf, Zed, Amp, Jules, Copilot
|
|
41
|
+
CLI and the DeepSeek harness". The table knew. The decision that depended on the fact was written
|
|
42
|
+
without checking the table, which is the shape of error this repository keeps having.
|
|
43
|
+
|
|
44
|
+
## Ruling
|
|
45
|
+
|
|
46
|
+
**One.** A surface declares `readsPrimary: true` when its own vendor documents that its reader loads
|
|
47
|
+
`AGENTS.md`. When any selected surface folds, `ruleTargets` skips per-rule files for those surfaces
|
|
48
|
+
and says so in the plan: the note names the file and the reason.
|
|
49
|
+
|
|
50
|
+
**Two.** It is per-surface, not global, because Copilot is the counter-example and the row says so
|
|
51
|
+
already: its CLI reads `AGENTS.md`; its editor and code review do not. A per-rule file there is the
|
|
52
|
+
only channel two of its three readers have, and dropping it would remove the process rules from a
|
|
53
|
+
chat that had nowhere else to get them.
|
|
54
|
+
|
|
55
|
+
**Three.** The skip depends on folding. With no folding surface selected there is no region to
|
|
56
|
+
duplicate, so a repo on `cursor` alone still gets its files — asserted, because a flag that reads
|
|
57
|
+
as "cursor never gets files" would be a different and much larger change.
|
|
58
|
+
|
|
59
|
+
**Four.** `sync` never deletes a file it wrote (ADR-021 ruling 6), so a file that becomes orphaned
|
|
60
|
+
cannot be repaired by any command. It is therefore a **note with a remedy, never a problem**, and it
|
|
61
|
+
never fails the gate. A gate nothing can satisfy is worse than a gate that is merely noisy.
|
|
62
|
+
|
|
63
|
+
**Five.** The tool must not stop looking at it. `orphanedRuleFiles` scans the rule directories of
|
|
64
|
+
every `readsPrimary` surface — **whether or not the repo still selects it** — for files carrying a
|
|
65
|
+
simt-dev region, and reports them. A file without the marker is hand-owned and is never mentioned.
|
|
66
|
+
|
|
67
|
+
## Consequences
|
|
68
|
+
|
|
69
|
+
`sync --surfaces opencode,cursor` now writes `AGENTS.md` and no `.cursor/rules/*.mdc`. The three
|
|
70
|
+
always-on rules still arrive, folded, and the plan says why each file was not written.
|
|
71
|
+
|
|
72
|
+
The estate reports all 17 as notes with a delete command. Nothing was deleted: removal is a human
|
|
73
|
+
decision with a visible diff, and this record does not authorise a tool to make it.
|
|
74
|
+
|
|
75
|
+
### The invisibility that ruling five exists to prevent
|
|
76
|
+
|
|
77
|
+
The skip landed first and the estate run showed `staleRuleFiles` go from **17 to 0**. Every one of
|
|
78
|
+
those files was still on disk. A tool that stops looking at a file it generated has not cleaned up —
|
|
79
|
+
it has lost track of it — and the symptom is a dashboard reporting a clean estate while seventeen
|
|
80
|
+
files are being read twice.
|
|
81
|
+
|
|
82
|
+
The first version of the scan then made the same mistake in a subtler form: it iterated the
|
|
83
|
+
*selected* surfaces. A file becomes orphaned precisely when a repo **drops** the surface that wrote
|
|
84
|
+
it, so scanning the selected set finds nothing at all — the one repo that needs the note is the one
|
|
85
|
+
that dropped the surface. Both defects were found by running the command against the estate, not by
|
|
86
|
+
reading the code. The estate run is worth more than either unit test.
|
|
87
|
+
|
|
88
|
+
### Six mutations, three of which caught nothing at first
|
|
89
|
+
|
|
90
|
+
| Mutation | Caught by |
|
|
91
|
+
| --- | --- |
|
|
92
|
+
| the skip never fires | `opting into a rule surface ... writes one file, not two` |
|
|
93
|
+
| `readsPrimary` applied without the folding condition | three tests, including `cursor alone still gets its own files` |
|
|
94
|
+
| the orphan scan deleted | **nothing** — found, then fixed |
|
|
95
|
+
| `orphaned` made blocking | **nothing** — found, then fixed |
|
|
96
|
+
| `orphaned` counted as a stale problem | **nothing** — found, then fixed |
|
|
97
|
+
| orphan scan restricted to selected surfaces | `compare reports an orphaned rule file as a note` |
|
|
98
|
+
|
|
99
|
+
The middle three passed all 627 tests, which is worth recording plainly: the half of this slice with
|
|
100
|
+
the most dangerous failure mode — a gate nothing can satisfy — shipped with no test at all, and only
|
|
101
|
+
a deliberate mutation found it. Four tests were written afterwards, including a negative control
|
|
102
|
+
("a repo's own rule files are never reported as orphaned"), because a scan that always returned an
|
|
103
|
+
empty array satisfies every positive test in the file.
|
|
104
|
+
|
|
105
|
+
### Two tests that encoded the old ruling
|
|
106
|
+
|
|
107
|
+
`test/surfaces.test.mjs` asserted the three `.mdc` files beside the folded region, with the comment
|
|
108
|
+
"One canonical source, two destinations" — a fair description of the behaviour, and the behaviour
|
|
109
|
+
was wrong. `test/upgrade.test.mjs` used `--surfaces opencode,cursor` to produce the generated file
|
|
110
|
+
whose wholesale repair it tests. Both now use **copilot**, whose reader does not read the primary, so
|
|
111
|
+
the properties under test are unchanged while their fixtures remain honest. A test fixture built on
|
|
112
|
+
a behaviour a new ADR reverses has to be moved deliberately, and the reason recorded, or the next
|
|
113
|
+
reader moves it back.
|
|
114
|
+
|
|
115
|
+
`test/compare.test.mjs`'s S25 fixture also moved to copilot for the same reason — and its helper
|
|
116
|
+
selected rule files by the `.mdc` suffix, which silently matched nothing and reported "the fixture
|
|
117
|
+
must project at least one rule file" rather than "the fixture is broken".
|
|
118
|
+
|
|
119
|
+
## Rejected
|
|
120
|
+
|
|
121
|
+
**Deleting the orphaned files automatically.** ADR-021 ruling 6, and the reason is sound: `sync` is a
|
|
122
|
+
projection, and a projection that deletes is a second, silent writer. The file is reported with the
|
|
123
|
+
command that removes it.
|
|
124
|
+
|
|
125
|
+
**Making `readsPrimary` global.** It would drop Copilot's only channel for its editor and code
|
|
126
|
+
review. The flag is per-surface for that reason and the Copilot row carries the note that says why.
|
|
127
|
+
|
|
128
|
+
**Keeping the `.mdc` files and not folding into the region when both surfaces are selected** — the
|
|
129
|
+
alternative review 0001 named. It gives opencode users nothing in mixed repos, which is ADR-021's
|
|
130
|
+
original objection to one surface per repo and still true.
|
|
131
|
+
|
|
132
|
+
**Removing Cursor support entirely, on the grounds that it reads `AGENTS.md`.** That would be true
|
|
133
|
+
and would lose the `.mdc` channel for anyone whose Cursor does not surface `AGENTS.md`, and it would
|
|
134
|
+
make a narrowing into a removal — which is not this tool's habit. The rules still reach a Cursor
|
|
135
|
+
reader; they just reach them once.
|
|
136
|
+
|
|
137
|
+
**The worktree-agent caveat becoming a gate.** A Cursor bug report records `AGENTS.md` not being read
|
|
138
|
+
in worktree agents by default, fixed in 3.9. That is a fact about a product and it is recorded on
|
|
139
|
+
the row, but gating on a version number would make a rule's presence depend on a vendor's release
|
|
140
|
+
cadence — and the alternative for those readers is a duplicated 5 KB, which is the cheaper error.
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# ADR-047 — A profile selects on what a repository runs on, not on what it is written in
|
|
2
|
+
|
|
3
|
+
Status: **accepted** · 2026-10-09 · refines ADR-013 ruling 1
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
|
|
7
|
+
Review 0002 §S28, P1, marked "decision" (proposal P3). Four profiles selected on a language or a
|
|
8
|
+
build tool while their bodies advised on a platform. Measured across the estate before anything
|
|
9
|
+
changed — the review's four rows, plus two smaller defects in the same table:
|
|
10
|
+
|
|
11
|
+
| Repo shape | Profile it received | Why that is wrong |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| any TypeScript library | `cloudflare-workers` (D1, Durable Objects, wrangler) | applied on `typescript` |
|
|
14
|
+
| Spring or Ktor backend on Gradle | `jvm-android` (Compose, Wear OS, overlay policy) | applied on `gradle`, `kotlin` |
|
|
15
|
+
| React Native / Expo app | `react-web` (CSS logical properties, WCAG reflow, a browser matrix) | applied on `react`, and `DEP_TAGS` mapped `react` and `react-dom` to the same tag |
|
|
16
|
+
| Poetry project | a `python` body that says `uv run -m`, `uv.lock` | the body was uv-specific, the selection was any Python marker |
|
|
17
|
+
|
|
18
|
+
Two smaller ones in the same table: `hono` also keyed `cloudflare-workers`, and Hono runs on
|
|
19
|
+
Node, Bun and Deno as happily as on Workers — a library tag read as platform evidence. And
|
|
20
|
+
`wrangler.json` was missing from `MARKERS`, so a Worker configured with the plain JSON file and
|
|
21
|
+
no `wrangler` dependency had no evidence of being a Worker at all.
|
|
22
|
+
|
|
23
|
+
ADR-013 ruling 1 made platform tags and facts interchangeable in `applies`, and its consequences
|
|
24
|
+
promised that "every tag a profile may name is one the engine emits" would hold for facts too,
|
|
25
|
+
with "the registry validator and the profile loader share one derived vocabulary so they cannot
|
|
26
|
+
desynchronise." Half of that was implemented: `graph lint` checked a skill's `stack` array
|
|
27
|
+
against the vocabulary, and nothing checked a profile's `applies`. The four rows above are what
|
|
28
|
+
that half produced — not a typo, but selections the vocabulary permitted and no check objected to.
|
|
29
|
+
|
|
30
|
+
## Ruling
|
|
31
|
+
|
|
32
|
+
**One.** A profile's `applies` names the evidence of what its body advises on. Where platform
|
|
33
|
+
evidence exists, a bare language or build-tool tag is not that evidence:
|
|
34
|
+
|
|
35
|
+
| Profile | Before | After | What selects it |
|
|
36
|
+
| --- | --- | --- | --- |
|
|
37
|
+
| `cloudflare-workers` | `cloudflare-workers, hono, typescript` | `cloudflare-workers` | a wrangler config or the `wrangler` dependency |
|
|
38
|
+
| `jvm-android`, split into `jvm` | `gradle, kotlin, wear-os` | `jvm`: `gradle, has-maven-pom, kotlin, java` | a JVM build |
|
|
39
|
+
| `jvm-android`, split into `android` | (the same file) | `android`: `has-android-manifest` | a module with an `AndroidManifest.xml` |
|
|
40
|
+
| `react-web` | `react, react-router, maplibre, firebase` | `react-dom, react-router, maplibre, firebase` | the browser renderer |
|
|
41
|
+
| `react-native`, new | — | `react-native` | a `react-native` or `expo` dependency |
|
|
42
|
+
| `python`, split into `python` and `uv` | `python` (with a uv-specific body) | `python`: `python`; `uv`: `has-uv-lock` | the language / the lockfile |
|
|
43
|
+
|
|
44
|
+
The exception is where a language *is* the finest fact available: `python`'s body advises
|
|
45
|
+
invocation hygiene that holds in every Python repository, so the `python` marker is the right
|
|
46
|
+
key there. The test that separates those two cases is the pair of rows whose shared stack is
|
|
47
|
+
exactly `node, react` and which differ only by the renderer tag: one receives `react-web`, the
|
|
48
|
+
other `react-native`, and neither receives both.
|
|
49
|
+
|
|
50
|
+
**Two.** The Android and uv facts name a path at an unknown depth, which the two existing kinds
|
|
51
|
+
(`file`, `dir`) cannot express. A third kind, `glob`, walks at most `GLOB_DEPTH` directory levels
|
|
52
|
+
below the root. The bound is a convention, not a preference: the Android Gradle Plugin puts a
|
|
53
|
+
module's manifest at `<module>/src/main/AndroidManifest.xml`, and simt-app's sits at
|
|
54
|
+
`app/src/main/AndroidManifest.xml`. A shallower bound makes `has-android-manifest` the shape S07
|
|
55
|
+
found — a fact no repository could have. A deeper one is a scan rather than a probe, and
|
|
56
|
+
ADR-013's promise is that adding an ecosystem is data, so a fact must not cost a search.
|
|
57
|
+
|
|
58
|
+
The walk skips dot-directories and `node_modules` for the reason the extension sample skips them;
|
|
59
|
+
the matcher is the registry's `matchesGlob` (ADR-042), so a fact's shape and a skill's `path:`
|
|
60
|
+
are one language rather than two; and the evidence for a `glob` fact is the matched path, never
|
|
61
|
+
the pattern — `doctor` answers "why did you think that?", and a shape is not an answer.
|
|
62
|
+
|
|
63
|
+
**Three.** `wrangler.json` and `wrangler.jsonc` join the Workers markers beside `wrangler.toml`.
|
|
64
|
+
|
|
65
|
+
**Four.** The vocabulary check now covers profiles as well as skills. `describeDefect` rejects a
|
|
66
|
+
`simt-applies` entry that `fingerprint()` cannot emit — `simt-applies names "pyton", which
|
|
67
|
+
fingerprint() never emits, so this profile could never be selected` — for central and local
|
|
68
|
+
profiles alike. A tag the engine cannot emit is a profile that can never be selected, and the
|
|
69
|
+
defect lives in the file that names it, not in whatever repository happens to load it. This is
|
|
70
|
+
the half of ADR-013's shared-vocabulary consequence that was unimplemented, and it is what makes
|
|
71
|
+
ruling one enforceable rather than aspirational: a future profile that re-keys on a language tag
|
|
72
|
+
spelling no detector emits fails at load time, in the file's own defect list.
|
|
73
|
+
|
|
74
|
+
## Consequences
|
|
75
|
+
|
|
76
|
+
`compare` over the estate before and after — 16 repositories, **4 move, 12 unchanged**, and no
|
|
77
|
+
repository loses coverage:
|
|
78
|
+
|
|
79
|
+
| Repo | Before | After |
|
|
80
|
+
| --- | --- | --- |
|
|
81
|
+
| `app-ui`, `simt-app`, `wakt-app` | `core, jvm-android` | `core, android, jvm` |
|
|
82
|
+
| `bunnyland-server` | `container, core, python` | `container, core, python, uv` |
|
|
83
|
+
|
|
84
|
+
The three Android repositories gain the profile whose body they actually needed and keep the JVM
|
|
85
|
+
build rules; `simt-app` gaining `android` was the row's acceptance criterion. `bunnyland-server`
|
|
86
|
+
carries a committed `uv.lock`, so its gain is the defect repaired, not a new selection.
|
|
87
|
+
|
|
88
|
+
The rename is breaking in one narrow way: the id `jvm-android` no longer exists. A consumer that
|
|
89
|
+
pinned it — a manifest's `profiles` array, a local profile shadowing by id, a document that names
|
|
90
|
+
it — must rename to `jvm` and `android`. Nothing in the estate pinned it, which is why the table
|
|
91
|
+
above is complete; a consumer outside it learns from this record.
|
|
92
|
+
|
|
93
|
+
The provenance digest moves `47160e9e…` to `0cfea39f…` because files under `rules/stacks/` were
|
|
94
|
+
added and removed. Every adopted repository re-stamps on its next `sync` or `upgrade`.
|
|
95
|
+
|
|
96
|
+
The fixture table in `test/genericity.test.mjs` asserts each row's `stack` **and** its exact
|
|
97
|
+
profile set, in order — `core` first because it applies always, the rest in profile-file read
|
|
98
|
+
order. Asserting the set alone would still pass if a fixture stopped being the thing its row
|
|
99
|
+
claims (a "TypeScript library" that is no longer TypeScript), which is the weaker claim a
|
|
100
|
+
mutation produces. The hand-maintained `PROFILES` list in `test/shipped.test.mjs` is deleted:
|
|
101
|
+
S28's rename broke it, and it guarded nothing — the leak check already iterates `loadRules()`.
|
|
102
|
+
|
|
103
|
+
Seven mutations, all caught:
|
|
104
|
+
|
|
105
|
+
| Mutation | Caught by |
|
|
106
|
+
| --- | --- |
|
|
107
|
+
| `react-web` re-keyed on `react-dom, react` | the exact-set row test |
|
|
108
|
+
| the `glob` probe reduced to a root-presence check | four tests: row, evidence, bound, root shape |
|
|
109
|
+
| `GLOB_DEPTH` 3 → 2 | three tests: row, evidence, bound |
|
|
110
|
+
| `wrangler.json` dropped from the markers | the row test |
|
|
111
|
+
| `hono` restored to the Workers applies | the row test |
|
|
112
|
+
| the vocabulary check removed from `describeDefect` | the local-profile probe: an error where the message belongs |
|
|
113
|
+
| the android fact made a shallow `dir` probe (S07's shape) | three exact-set rows |
|
|
114
|
+
|
|
115
|
+
## Rejected
|
|
116
|
+
|
|
117
|
+
**Selecting on the language and excluding with an extra condition** — `cloudflare-workers`
|
|
118
|
+
applies on `typescript`, but not when …. Selection by subtraction needs its own detector for
|
|
119
|
+
every new shape of repository, and each exclusion is a hole until it is written. The positive
|
|
120
|
+
form asks one question — *is this a Worker?* — and the answer is a file or a dependency, which is
|
|
121
|
+
what a fact is for.
|
|
122
|
+
|
|
123
|
+
**The build-plugin half of `has-android-manifest`.** The review proposed the manifest *or* the
|
|
124
|
+
`com.android.application` / `com.android.library` plugin in a build file. The plugin has two
|
|
125
|
+
spellings (Groovy `apply plugin:` and the Kotlin DSL `id(...)`), moves with the plugin's
|
|
126
|
+
versioning, and a repository can carry the plugin without the manifest while it is mid-migration.
|
|
127
|
+
The manifest is the one path shape that makes a module buildable as Android, and it is what
|
|
128
|
+
`doctor` can point at. The plugin half is recorded here rather than implemented, so the next
|
|
129
|
+
reader does not discover it as a gap.
|
|
130
|
+
|
|
131
|
+
**An unbounded glob walk.** It would find the manifest, and it would also make detection a scan
|
|
132
|
+
of every file below the root, which is what ADR-013's promise rules out: adding an ecosystem is a
|
|
133
|
+
profile file, not a search. The bound is the convention the fact exists to see.
|
|
134
|
+
|
|
135
|
+
**Keeping `jvm-android` and documenting the mis-selection.** That ships a profile whose body the
|
|
136
|
+
review measured as wrong for a whole class of repositories, with a comment where a fix belongs.
|
|
137
|
+
|
|
138
|
+
**A per-renderer tag for every framework.** `react-dom` is the renderer, and it is what
|
|
139
|
+
distinguishes a browser from every other surface. The other keys on `react-web` — `react-router`,
|
|
140
|
+
`maplibre`, `firebase` — were already there as discriminators, and they stay: each names a
|
|
141
|
+
library whose presence implies a browser deployment the same way `wrangler` implies Workers.
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# ADR-048 — a baseline carries only keys a reader honours
|
|
2
|
+
|
|
3
|
+
Status: accepted · 2026-10-09 · amends ADR-025 (withdraws ruling 8, corrects its context)
|
|
4
|
+
**Affects:** `src/projection/baselines.mjs`, `test/baselines.test.mjs`,
|
|
5
|
+
`docs/plans/0001-centralise-agent-rules.md`. Closes S30 of
|
|
6
|
+
`docs/reviews/0002-full-review-adoption-and-public-release.md`.
|
|
7
|
+
|
|
8
|
+
## Context
|
|
9
|
+
|
|
10
|
+
ADR-025 ruling 8 put `[prettier] singleQuote = true` in the shared `.editorconfig` and
|
|
11
|
+
called it *"the only comment-capable home prettier reads"*. Both halves of that sentence
|
|
12
|
+
are false, for one normative reason and one vendor reason, and neither is visible from
|
|
13
|
+
inside this repository.
|
|
14
|
+
|
|
15
|
+
**A section header is a glob.** The EditorConfig specification (v0.17.2, *File Format*)
|
|
16
|
+
is explicit: a "Section Header … define[s] globs", and *Glob Expressions* lists `*`,
|
|
17
|
+
`**`, `?`, `[seq]`, `[!seq]` and `{s1,s2,s3}` as the characters that do the matching.
|
|
18
|
+
`[prettier]` therefore matches a file named `prettier`, in a directory of the
|
|
19
|
+
repository or below it. No repository has one. The block matched no file, ever.
|
|
20
|
+
|
|
21
|
+
**`singleQuote` is not a key anything honours.** The same specification's *Supported
|
|
22
|
+
Pairs* section defines nine keys — `indent_style`, `indent_size`, `tab_width`,
|
|
23
|
+
`end_of_line`, `charset`, `spelling_language`, `trim_trailing_whitespace`,
|
|
24
|
+
`insert_final_newline`, `root` — and `singleQuote` is not among them. That section also
|
|
25
|
+
settles what an undefined key does, and it settles it in the worst possible direction:
|
|
26
|
+
cores "shall accept and report" syntactically valid pairs even when the key is not
|
|
27
|
+
defined, while plugins "shall ignore unrecognized keys". An unknown key is not an error
|
|
28
|
+
to be reported; it is a line that parses and does nothing.
|
|
29
|
+
|
|
30
|
+
Prettier's own mapping (`src/config/editorconfig/editorconfig-to-prettier.js` on `main`)
|
|
31
|
+
reads exactly six properties: `indent_style`, `indent_size`, `tab_width`,
|
|
32
|
+
`max_line_length`, `quote_type` and `end_of_line`. `singleQuote` is not one of them, and
|
|
33
|
+
neither `quote_type` nor `max_line_length` is a key the specification defines — the
|
|
34
|
+
source labels `quote_type` an "Undocumented feature". Its published documentation
|
|
35
|
+
mentions only `max_line_length`, `indent_size`, `tab_width`, `indent_style` and
|
|
36
|
+
`end_of_line` as the editorconfig properties it reads.
|
|
37
|
+
|
|
38
|
+
So the region asserted, in a comment, that it was the file that settled the estate's
|
|
39
|
+
`singleQuote` disagreement; the value was in a section that matched nothing, under a key
|
|
40
|
+
that no reader honours. That is the "decoration that misdescribes the artefact" failure
|
|
41
|
+
this repository's own `AGENTS.md` names, and no gate here could see it: `check` compares
|
|
42
|
+
bytes, and the bytes were current.
|
|
43
|
+
|
|
44
|
+
**This record cites the specification and the vendor's source rather than a local run.**
|
|
45
|
+
That is a change from the review's evidence, which measured Prettier 3.8.3, and it is
|
|
46
|
+
deliberate. ADR-041 rules this package's suite against reaching for a formatter, and the
|
|
47
|
+
npm registry this was written against refuses the fetch (`403`), so a reproduced run was
|
|
48
|
+
not available. The two citations are stronger for the purpose anyway: one is normative and
|
|
49
|
+
one is the mapping Prettier actually runs, whereas a version-pinned run would date the
|
|
50
|
+
moment the question was asked.
|
|
51
|
+
|
|
52
|
+
## The survey S30 asked for
|
|
53
|
+
|
|
54
|
+
S30's fix was split in two on purpose: delete the block **now**, and add `quote_type =
|
|
55
|
+
single` **only with an estate survey**. The survey is this section, and it is the reason
|
|
56
|
+
the second half is not taken.
|
|
57
|
+
|
|
58
|
+
Five of the sixteen repositories run Prettier. Prettier resolves `.editorconfig` first and
|
|
59
|
+
merges the repository's own config over it (`resolve-config.js`: `{ ...editorConfigured,
|
|
60
|
+
...mergeOverrides(...) }`), so a repository that states `singleQuote` is unaffected by
|
|
61
|
+
anything this baseline says, and a repository that states nothing is decided entirely by
|
|
62
|
+
it.
|
|
63
|
+
|
|
64
|
+
| Repository | Prettier config | States `singleQuote` | Adopting `quote_type = single` |
|
|
65
|
+
| --- | --- | --- | --- |
|
|
66
|
+
| `simt-api` | `.prettierrc` | `true` | no change |
|
|
67
|
+
| `simt-blog` | `.prettierrc.json` | `true` | no change |
|
|
68
|
+
| `simt-space` | `.prettierrc.json` | `true` | no change |
|
|
69
|
+
| `simt-social-api` | `.prettierrc.json` | absent | **every source file reformatted** |
|
|
70
|
+
| `simt-social` | `.prettierrc.json` | absent | **every source file reformatted** |
|
|
71
|
+
|
|
72
|
+
Two repositories would have every source file rewritten on the day they adopted the
|
|
73
|
+
tool, in a commit whose message says it adopted a rules projection. The measurement is
|
|
74
|
+
the answer to the question S30 deferred: the second half is not taken, and the reason is
|
|
75
|
+
recorded here so the next person does not run the survey again to reach it.
|
|
76
|
+
|
|
77
|
+
## Rulings
|
|
78
|
+
|
|
79
|
+
1. **`[prettier] singleQuote = true` is withdrawn.** ADR-025 ruling 8 does not stand; its
|
|
80
|
+
`printWidth` half is unaffected and was never implemented. This changes no behaviour,
|
|
81
|
+
because the block was inert — which is why two reviews and the whole suite read past
|
|
82
|
+
it: the gate compares bytes, and the bytes were what the source said they were.
|
|
83
|
+
|
|
84
|
+
2. **A baseline section header is a glob over paths, never a name.** A header with no
|
|
85
|
+
special character matches one filename, and the only filenames a file every
|
|
86
|
+
repository receives may name are the ones every repository has. A literal travels
|
|
87
|
+
inside a brace group with the globs it belongs with — `[{*.go,*.proto}]`, which this
|
|
88
|
+
baseline already does for exactly this reason. `test/baselines.test.mjs` asserts it,
|
|
89
|
+
because the defect it prevents is a header that parses.
|
|
90
|
+
|
|
91
|
+
3. **Every key in a baseline is one a reader honours.** The nine keys the specification
|
|
92
|
+
defines are the whole vocabulary, and the test names them with the section they come
|
|
93
|
+
from. This is what rules `singleQuote` out by construction rather than by deletion:
|
|
94
|
+
`quote_type` is excluded by the same test, and the reason it is excluded is written
|
|
95
|
+
next to it.
|
|
96
|
+
|
|
97
|
+
4. **`quote_type = single` is not adopted, and the estate survey above is its record.**
|
|
98
|
+
Not rejected in principle: rejected for now, on a measurement — two of five
|
|
99
|
+
repositories would be wholly reformatted, and this tool does not reformat a
|
|
100
|
+
repository on the day it adopts one. The disagreement it would settle is real and is
|
|
101
|
+
still open (ADR-025's consequences already recorded `singleQuote` as unsettled
|
|
102
|
+
everywhere else), and it belongs in each repository's own Prettier config, which is
|
|
103
|
+
the only place that can change a repository's bytes.
|
|
104
|
+
|
|
105
|
+
## Consequences
|
|
106
|
+
|
|
107
|
+
- `.editorconfig` loses eleven lines, one of which was the only claim in the file about a
|
|
108
|
+
tool. Every adopting repository sees a real `.editorconfig` change on its next
|
|
109
|
+
`sync --yes`, and `check` reports it: the baseline target list is the one `check` and
|
|
110
|
+
`upgrade` both read, so the repository is correctly classified as behind. What `upgrade`
|
|
111
|
+
will *not* do is name the cause correctly. Measured on a scratch repository carrying the
|
|
112
|
+
body this record withdraws and every other byte current:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
DRIFT .editorconfig region is updated; run: simt-dev sync --yes
|
|
116
|
+
pin 0cfea39f…6671 (unchanged)
|
|
117
|
+
region restamped; the pin was already current (the rules changed)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
No rule changed. The provenance digest covers `rules/` and `graph/` and deliberately
|
|
121
|
+
excludes `src/` — the rationale is that "a change that moves a consumer's projection"
|
|
122
|
+
must move the stamp, and the baseline bodies are content that leaves the package while
|
|
123
|
+
living in `src/`. `check` is unaffected because it compares bytes, and `upgrade` is
|
|
124
|
+
affected because it compares the stamp. ADR-032 found this exact false cause once
|
|
125
|
+
already, for a different reason, and the fix there was to identify the projection by its
|
|
126
|
+
content rather than by a commit; the same gap survives for the bytes that are not in
|
|
127
|
+
`rules/`. It is filed as **S53** with this measurement attached rather than fixed here,
|
|
128
|
+
because widening the digest changes what the stamp means and that is ADR-032's subject
|
|
129
|
+
(ADR-002).
|
|
130
|
+
|
|
131
|
+
- Plan 0001 claims the contradiction is settled "in `.editorconfig`'s `[prettier]`
|
|
132
|
+
section" in two places. Both were true of the file and false of its meaning, so both
|
|
133
|
+
are amended to point here. Leaving them would leave the plan asserting a mechanism this
|
|
134
|
+
record has just withdrawn, which is ADR-036's failure one document up.
|
|
135
|
+
|
|
136
|
+
- `test/baselines.test.mjs` grew a guard rather than an assertion about today's string,
|
|
137
|
+
because a string assertion passes the day somebody pastes the block back.
|
|
138
|
+
|
|
139
|
+
## Rejected
|
|
140
|
+
|
|
141
|
+
- **Keeping the block and correcting only its comment.** Rejected: it would leave a
|
|
142
|
+
section matching nothing, and the next reader would have to rediscover that a comment
|
|
143
|
+
admitting it is inert is still inert.
|
|
144
|
+
- **`quote_type = single` under the JS/TS glob, as S30's second half proposed.**
|
|
145
|
+
Rejected on the survey above. It is recorded as a measurement, not as a plan, because
|
|
146
|
+
the cost lands on two repositories that did not ask for it.
|
|
147
|
+
- **Widening the digest to cover baseline bodies in this slice.** Rejected under
|
|
148
|
+
ADR-002: it is a change to what the stamp means, which is ADR-032's subject and
|
|
149
|
+
deserves its own record. It is filed as S53 with this measurement attached, so the
|
|
150
|
+
next reader has the evidence rather than the conclusion.
|