simt-dev 0.6.3 → 0.7.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.
Files changed (187) hide show
  1. package/CHANGELOG.md +162 -841
  2. package/README.md +21 -135
  3. package/agents/simt-agent.md +84 -0
  4. package/bin/simt.mjs +19 -55
  5. package/docs/decisions/ADR-009-skill-layout.md +1 -1
  6. package/docs/decisions/ADR-014-workspace-is-configuration.md +1 -1
  7. package/docs/decisions/ADR-086-the-stable-prefix-comes-first-and-the-lock-records-both-digests.md +1 -1
  8. package/docs/decisions/ADR-099-the-release-verifies-served-bytes-and-the-formula-mirrors-them.md +1 -1
  9. package/docs/decisions/ADR-106-the-harness-hook-runs-through-a-repo-shim.md +46 -0
  10. package/docs/decisions/ADR-107-an-install-can-name-one-harness-and-the-skipped-ones-are-named.md +51 -0
  11. package/docs/decisions/ADR-108-an-adoption-builds-the-index-and-a-command-refreshes-it-on-read.md +59 -0
  12. package/docs/decisions/ADR-109-simt-dev-is-its-own-consumer-and-the-guards-that-kept-it-from-being-one-are-retired.md +47 -0
  13. package/docs/decisions/ADR-110-the-lock-file-follows-the-manifest-name-and-the-old-name-is-read-until-the-next-write.md +43 -0
  14. package/docs/decisions/ADR-111-the-tool-ships-one-agent-rendered-per-harness-and-installs-it-with-init-and-a-command.md +75 -0
  15. package/docs/decisions/ADR-112-the-registry-is-what-simt-dev-ships-and-a-repo-owns-its-own-skills.md +81 -0
  16. package/docs/decisions/ADR-113-a-pre-push-that-carries-only-documentation-skips-the-test-gate-and-says-so.md +87 -0
  17. package/docs/decisions/ADR-114-the-bench-pairs-at-the-task-level-a-missing-cost-is-not-zero-and-an-unasked-cross-group-compare-is-refused.md +74 -0
  18. package/docs/decisions/ADR-115-the-index-stamps-its-schema-version-migrates-older-databases-in-place-and-serves-newer-ones-from-a-sidecar.md +69 -0
  19. package/docs/decisions/ADR-116-rust-support-reaches-the-example-path-the-indent-and-the-bench-runner.md +100 -0
  20. package/docs/decisions/ADR-117-the-darwin-release-builds-on-supported-macos-15-runners-and-a-re-run-is-idempotent.md +81 -0
  21. package/docs/decisions/ADR-118-the-bench-corpus-is-keyed-on-history-is-append-only-and-is-portable-as-a-sealed-document.md +91 -0
  22. package/docs/decisions/ADR-119-the-environment-variable-points-into-a-workspace-the-repo-declared.md +90 -0
  23. package/docs/decisions/ADR-120-a-pre-push-whose-tree-is-already-proven-green-skips-the-test-gate-on-that-evidence.md +96 -0
  24. package/docs/decisions/ADR-122-the-shims-move-into-the-state-directory-and-the-legacy-directory-removes-itself.md +94 -0
  25. package/docs/decisions/ADR-123-a-surplus-positional-is-not-refused-because-arity-is-not-in-the-source.md +57 -0
  26. package/docs/decisions/ADR-124-the-manifest-has-a-schema-in-the-tool-and-its-unknown-keys-stay-the-repositorys.md +64 -0
  27. package/docs/decisions/ADR-125-a-shared-context-belongs-to-a-read-and-a-writer-reads-directly.md +65 -0
  28. package/package.json +13 -3
  29. package/rules/core.md +9 -2
  30. package/rules/mdc/slice-commit.mdc +57 -4
  31. package/rules/mdc/test-gate.mdc +2 -0
  32. package/rules/stacks/android.md +3 -3
  33. package/rules/stacks/connectiq.md +2 -2
  34. package/rules/stacks/dart.md +1 -1
  35. package/rules/stacks/deno.md +1 -1
  36. package/rules/stacks/dotnet.md +1 -1
  37. package/rules/stacks/elixir.md +1 -1
  38. package/rules/stacks/go.md +1 -1
  39. package/rules/stacks/jvm.md +3 -2
  40. package/rules/stacks/node.md +2 -2
  41. package/rules/stacks/python.md +7 -6
  42. package/rules/stacks/ruby.md +1 -1
  43. package/rules/stacks/rust.md +1 -1
  44. package/rules/stacks/swift.md +1 -1
  45. package/rules/stacks/typescript.md +2 -2
  46. package/rules/stacks/uv.md +3 -2
  47. package/skills/simt-commands/SKILL.md +70 -0
  48. package/src/advice/advice.mjs +7 -3
  49. package/src/agenthooks/decide.mjs +141 -9
  50. package/src/agenthooks/opencode.mjs +72 -0
  51. package/src/agenthooks/prompt.mjs +10 -3
  52. package/src/agenthooks/render.mjs +186 -19
  53. package/src/agenthooks/shim.mjs +55 -0
  54. package/src/agents/render.mjs +277 -0
  55. package/src/api.mjs +29 -0
  56. package/src/argv.mjs +37 -3
  57. package/src/bench/ablate.mjs +31 -14
  58. package/src/bench/adapters.json +16 -0
  59. package/src/bench/corpus.mjs +150 -0
  60. package/src/bench/grade.mjs +23 -49
  61. package/src/bench/mine.mjs +247 -36
  62. package/src/bench/probes.mjs +104 -68
  63. package/src/bench/process.mjs +128 -0
  64. package/src/bench/report.mjs +260 -84
  65. package/src/bench/run.mjs +251 -150
  66. package/src/bench/variants.mjs +112 -40
  67. package/src/brief/assemble.mjs +44 -3
  68. package/src/ci/workflow.mjs +46 -14
  69. package/src/cli.mjs +45 -0
  70. package/src/commands/agent.mjs +205 -0
  71. package/src/commands/agenthook.mjs +113 -20
  72. package/src/commands/bench.mjs +192 -18
  73. package/src/commands/brief.mjs +5 -4
  74. package/src/commands/ci.mjs +2 -1
  75. package/src/commands/compare.mjs +50 -49
  76. package/src/commands/completions.mjs +2 -6
  77. package/src/commands/decide.mjs +10 -9
  78. package/src/commands/distill.mjs +4 -3
  79. package/src/commands/doctor.mjs +11 -7
  80. package/src/commands/evidence.mjs +19 -12
  81. package/src/commands/explain.mjs +0 -1
  82. package/src/commands/gate.mjs +98 -28
  83. package/src/commands/graph.mjs +20 -8
  84. package/src/commands/harness.mjs +3 -2
  85. package/src/commands/help.mjs +160 -31
  86. package/src/commands/hooks.mjs +163 -50
  87. package/src/commands/ignore.mjs +2 -1
  88. package/src/commands/impact.mjs +8 -9
  89. package/src/commands/index.mjs +278 -180
  90. package/src/commands/init.mjs +171 -38
  91. package/src/commands/lease.mjs +206 -39
  92. package/src/commands/lessons.mjs +72 -15
  93. package/src/commands/local.mjs +3 -3
  94. package/src/commands/map.mjs +47 -27
  95. package/src/commands/mcp.mjs +0 -1
  96. package/src/commands/pack.mjs +9 -6
  97. package/src/commands/plan.mjs +6 -5
  98. package/src/commands/plugin.mjs +3 -3
  99. package/src/commands/prbody.mjs +3 -3
  100. package/src/commands/repoindex.mjs +5 -3
  101. package/src/commands/review.mjs +2 -2
  102. package/src/commands/rules.mjs +124 -33
  103. package/src/commands/shared.mjs +2 -3
  104. package/src/commands/skills.mjs +82 -45
  105. package/src/commands/skillscompile.mjs +12 -12
  106. package/src/commands/skillsinstall.mjs +14 -2
  107. package/src/commands/slice.mjs +396 -26
  108. package/src/commands/spec.mjs +151 -0
  109. package/src/commands/status.mjs +4 -7
  110. package/src/commands/sync.mjs +50 -33
  111. package/src/commands/tests.mjs +11 -11
  112. package/src/commands/tokens.mjs +2 -2
  113. package/src/commands/undo.mjs +29 -31
  114. package/src/commands/upgrade.mjs +40 -4
  115. package/src/commands/why.mjs +19 -12
  116. package/src/compare/hooksstate.mjs +23 -1
  117. package/src/compare/learn.mjs +82 -57
  118. package/src/compare/parallel.mjs +50 -0
  119. package/src/compare/row.mjs +26 -3
  120. package/src/completions.mjs +5 -0
  121. package/src/context.mjs +64 -0
  122. package/src/decisions/dir.mjs +84 -0
  123. package/src/decisions/index-doc.mjs +46 -0
  124. package/src/decisions/index.mjs +109 -11
  125. package/src/diff.mjs +1 -1
  126. package/src/distill/distill.mjs +17 -2
  127. package/src/distill/engine.mjs +5 -1
  128. package/src/ecosystem/node-runner.mjs +144 -0
  129. package/src/explain.mjs +18 -6
  130. package/src/graph/load.mjs +42 -20
  131. package/src/hooks/capability.mjs +17 -4
  132. package/src/hooks/dispatcher.mjs +513 -36
  133. package/src/hooks/gates.mjs +0 -43
  134. package/src/hooks/resolve.mjs +11 -10
  135. package/src/hooks/shim.mjs +17 -9
  136. package/src/identity.mjs +96 -15
  137. package/src/index/extract.mjs +192 -11
  138. package/src/index/persist.mjs +307 -47
  139. package/src/index/store.mjs +263 -60
  140. package/src/index/tests.mjs +6 -35
  141. package/src/journal.mjs +372 -84
  142. package/src/kernel/card.mjs +37 -12
  143. package/src/kernel/checks.mjs +44 -9
  144. package/src/kernel/variants.mjs +27 -16
  145. package/src/lease.mjs +97 -15
  146. package/src/ledger/index.mjs +120 -25
  147. package/src/local/client.mjs +4 -6
  148. package/src/locate.mjs +33 -9
  149. package/src/manifest/schema.mjs +213 -0
  150. package/src/mcp/resources.mjs +12 -13
  151. package/src/mcp/server.mjs +151 -38
  152. package/src/mcp/tools.mjs +90 -16
  153. package/src/measure/session.mjs +3 -2
  154. package/src/memory/decide.mjs +98 -43
  155. package/src/memory/lessons.mjs +168 -33
  156. package/src/name.mjs +9 -0
  157. package/src/node-version.mjs +37 -0
  158. package/src/output/index.mjs +111 -3
  159. package/src/packs/manage.mjs +17 -3
  160. package/src/packs.mjs +20 -2
  161. package/src/plan/index.mjs +31 -8
  162. package/src/plan/template.mjs +27 -5
  163. package/src/plugins.mjs +6 -2
  164. package/src/prbody.mjs +7 -5
  165. package/src/projection/baselines.mjs +1 -1
  166. package/src/projection/disclosure.mjs +2 -2
  167. package/src/projection/loaders.mjs +41 -1
  168. package/src/projection/lock.mjs +31 -14
  169. package/src/projection/manifest.mjs +89 -53
  170. package/src/projection/markers.mjs +43 -3
  171. package/src/projection/mdc.mjs +34 -7
  172. package/src/projection/project.mjs +162 -18
  173. package/src/projection/rules.mjs +58 -6
  174. package/src/projection/surfaces.mjs +9 -1
  175. package/src/repo-root.mjs +88 -0
  176. package/src/resolve/body.mjs +4 -0
  177. package/src/resolve/fingerprint.mjs +24 -15
  178. package/src/resolve/local.mjs +58 -0
  179. package/src/review/critic.mjs +6 -3
  180. package/src/secrets.mjs +165 -22
  181. package/src/state.mjs +76 -0
  182. package/src/status.mjs +9 -4
  183. package/src/toolwords.mjs +8 -0
  184. package/src/tsconfig.mjs +39 -99
  185. package/src/workspace.mjs +55 -10
  186. package/templates/plan/tracker.json +1 -1
  187. package/docs/decisions/README.md +0 -591
@@ -0,0 +1,43 @@
1
+ ---
2
+ simt-affects: ["src/name.mjs", "src/projection/lock.mjs", "src/kernel/variants.mjs", "src/commands/sync.mjs", "src/commands/help.mjs", "src/bench/variants.mjs", "test/lock.test.mjs", "test/variants.test.mjs"]
3
+ simt-supersedes: [{ adr: "ADR-086", ruling: 3, kind: "superseded" }]
4
+ ---
5
+
6
+ # ADR-110 — the lock is `simt.lock.json`, and the old name is read until the next write
7
+
8
+ Status: **accepted** · 2026-10-30 · supersedes ADR-086 ruling 3's file name
9
+
10
+ ## Context
11
+
12
+ The lock followed the command's old name: `simt-dev.lock.json`. The command
13
+ is `simt`, the manifest is `simt.json`, and the lock was the one remaining
14
+ file that still carried the pre-rename spelling.
15
+
16
+ A rename cannot be a bare move. A lock written by a pre-rename run may hold
17
+ `variants` winners that nothing else records — the bench evidence for a
18
+ locked rule text — so a reader that only knows the new name would silently
19
+ unlock those decisions.
20
+
21
+ ## Ruling
22
+
23
+ 1. **The lock is `simt.lock.json`.** The old name is a read-only fallback:
24
+ a reader tries the new name first and falls back to the old one, and a
25
+ write always lands under the new name, which is how a legacy lock
26
+ migrates. The fallback is for absence, not for damage: a permission
27
+ failure on the new name does not fall back, and a file that exists but
28
+ does not parse is reported, not repaired by an older file. This is the
29
+ same read-both/write-new pattern as the manifest rename (ADR-056).
30
+ 2. **The name lives in one place.** `src/name.mjs` owns both names, the way
31
+ it owns the manifest's. The kernel and the projection each carried a
32
+ private copy — the kernel cannot import the projection, because the
33
+ projection imports the kernel — and two constants for one file is how a
34
+ rename ships half-done.
35
+
36
+ ## Consequences
37
+
38
+ - A repository whose lock predates the rename keeps its locked variant
39
+ winners: `simt check --frozen` and the projection read them from the old
40
+ file, and the next `simt sync --yes` or `simt bench variants --yes` writes
41
+ them back under the new name.
42
+ - ADR-086's ruling 3 stands for what the lock records — an input digest and
43
+ an output digest; the file name it named is superseded.
@@ -0,0 +1,75 @@
1
+ ---
2
+ simt-affects: ["agents/simt-agent.md", "src/agents/render.mjs", "src/commands/agent.mjs", "src/commands/init.mjs", "src/projection/loaders.mjs", "src/toolwords.mjs", "test/agents.test.mjs"]
3
+ simt-supersedes: []
4
+ ---
5
+
6
+ # ADR-111 — the tool ships one agent, rendered per harness, and installs it with init and a command
7
+
8
+ Status: **accepted** · 2026-10-30
9
+
10
+ ## Context
11
+
12
+ The rules this tool projects tell an agent *what* to obey; nothing told it
13
+ *how to act* through the tool. The plan for that was two agents — a planner
14
+ and a builder — but the two jobs are one job in practice: read the tracker,
15
+ take the next slice, build it, test it, commit it. Two files that both say
16
+ "run the simt commands" are two copies of the same logic, and the second copy
17
+ is where the next divergence grows.
18
+
19
+ Six harnesses read a subagent and no two agree on the file. Five write a
20
+ markdown file with YAML frontmatter and a body that becomes the system
21
+ prompt; Codex writes a TOML file whose `developer_instructions` holds the
22
+ same body. The path, the extension, the frontmatter keys and the document
23
+ shape are all different, and none of them is the same fact.
24
+
25
+ A hooks entry is a config key the tool merges into a file it does not own.
26
+ An agent is a file the tool owns whole: there is nothing to merge, and the
27
+ only question is whether the bytes on disk are the bytes the tool would write
28
+ now.
29
+
30
+ ## Ruling
31
+
32
+ 1. **The tool ships one agent, `Simt-Agent`.** Its body is the system prompt
33
+ and it encodes how to act with simt-dev across the whole slice lifecycle —
34
+ orient (`simt brief`, `simt why`), plan (`simt plan status`, `simt plan
35
+ next`), build (`simt slice start`, the focused test, `simt slice finish`,
36
+ `simt plan done`) and the boundaries that hold everywhere. Planning and
37
+ building are one agent because they are one job.
38
+ 2. **The source of truth is one file, `agents/simt-agent.md`.** Neutral
39
+ frontmatter (`name`, `description`) plus a harness-agnostic body. The
40
+ renderer strips that frontmatter and re-emits the keys each harness
41
+ documents, so the body — the part that says how to act — is written once
42
+ and cannot drift between harnesses. The `simt-agent` stem is a name this
43
+ tool owns, and it is derived into the tool-word set from the module that
44
+ owns it, the way the other prefixed names are.
45
+ 3. **An agent is a file the tool owns whole, so the install is a byte
46
+ comparison.** `rendered` produces the text and reports `created`,
47
+ `unchanged` or `updated`. There is no parse that can fail and no partial
48
+ state, which is what makes the command shorter than `agent-hooks install`.
49
+ 4. **It is installed per harness, and the readers it does not write are
50
+ named.** Six harnesses document an agents file and receive one; the two
51
+ that document none are reported `NO FILE` with the reason, the way
52
+ ADR-079 ruling 11 requires. `--harness <id>` narrows the install to one
53
+ reader and names the skipped ones (ADR-107), and `status` reports
54
+ present, absent, drift, and the readers with no file.
55
+ 5. **It is written by `simt init` and by a separate `simt agents install`.**
56
+ `init` plans the agent files with the rest of the adoption and writes them
57
+ in the apply path, journaled the same way, so `undo` restores them with the
58
+ rest. The separate command exists for a repository that is already adopted
59
+ and wants only the agent, and for a narrowed install. Both call the same
60
+ `planAgents` and `writeAgentPlan`, so a dry run and a run describe the same
61
+ set of files.
62
+
63
+ ## Consequences
64
+
65
+ - A repository that is adopted and later upgrades the tool's agent body runs
66
+ `simt agents install --yes` (or re-runs `init`) and the files come back
67
+ `updated` with the new body; the frontmatter each harness documents is
68
+ re-emitted from the same source, so it cannot drift.
69
+ - The agent body names only commands a consumer repository runs — `simt
70
+ brief`, `simt why`, `simt plan`, `simt slice` — so every byte in it is
71
+ actionable in the receiving repository, the rule this repository exists to
72
+ enforce.
73
+ - `agents/` is published with the package: the tool reads its own agent body
74
+ from the installed copy, and a tarball without it would install cleanly and
75
+ then fail on the one command that writes the agent.
@@ -0,0 +1,81 @@
1
+ ---
2
+ simt-affects: ["graph/skills.graph.json", "src/resolve/local.mjs", "src/commands/skills.mjs", "src/projection/disclosure.mjs", "src/resolve/body.mjs"]
3
+ simt-supersedes: [{ adr: "ADR-009", ruling: 4 }]
4
+ ---
5
+
6
+ # ADR-112 — the registry is what simt-dev ships, and a repo owns its own skills
7
+
8
+ Status: **accepted** · 2026-10-31 · filed as S4 (plan 0005) · supersedes ADR-009 ruling 4
9
+
10
+ ## Context
11
+
12
+ ADR-009 settled the layout: a skill is `<name>/SKILL.md` with mandatory `name`
13
+ and `description` frontmatter, and body size and reference count are declared in
14
+ `graph/skills.graph.json`, "so the registry stays the single source of truth for
15
+ resolution metadata". The layout half of that ruling was about one estate's
16
+ thirty-nine skills, and the metadata half assumed the estate was the universe: a
17
+ skill not in the graph is a skill that does not exist.
18
+
19
+ Plan 0005 broke that assumption in two slices. S2 removed the thirty-one
20
+ repo-specific entries from the graph, leaving the eight skills this tool ships —
21
+ `simt-commands` and seven vendored stack skills. A graph that describes one
22
+ estate and is read by everyone was ADR-061's finding; a graph that is one
23
+ estate's whole skill population is a stronger claim ADR-061 did not make, and S2
24
+ made it false. S3 then gave the repo's own skill directories standing in the
25
+ resolver: `skills find`, `brief`, `show` and `why` answer for a skill the
26
+ registry never declared, synthesising its node from the file — name and summary
27
+ from frontmatter, size from the file, body from the path (commit 7d44071).
28
+
29
+ A reader of ADR-009 acting on the metadata sentence concludes that a skill absent
30
+ from the graph has no resolution metadata, and therefore cannot resolve. That
31
+ conclusion is now false in every repo that keeps its own skills, and a record
32
+ that says what the tool no longer believes is the failure this directory exists
33
+ to prevent.
34
+
35
+ ## Ruling
36
+
37
+ 1. **The graph is a catalogue of what simt-dev ships, not the sole home of
38
+ skills.** Membership in `graph/skills.graph.json` is a statement about the
39
+ package, not about existence. A skill a repository installed in its own skill
40
+ directories exists and resolves with no entry in the graph.
41
+ 2. **A repo's skill directories are a first-class source of resolution.**
42
+ Presence is the eligibility signal — a skill the repo installed is about the
43
+ repo, and the stack it belongs to is the stack the repo has. Frontmatter
44
+ supplies the name and the summary; the file supplies the body and the size.
45
+ This is the mechanism S3 shipped; the ruling records it so the reader learns
46
+ what the tool believes without reading the plan.
47
+ 3. **Where a name is in both, the registry entry is canonical.** The graph entry
48
+ and the local file are one skill — the file is the installed copy of what the
49
+ graph declares — and keeping both would rank one skill twice. The graph wins
50
+ the dedupe; the local node is dropped, not merged.
51
+
52
+ ## Consequences
53
+
54
+ - ADR-009's layout ruling stands and is load-bearing: discovery reads `name` and
55
+ `description` from frontmatter, so a local skill without them is invisible,
56
+ which is the defect class ADR-009 exists to prevent. What moves is the metadata
57
+ sentence: a local skill's resolution metadata is synthesised from the
58
+ filesystem, not read from the graph, so the graph is no longer the single
59
+ source of truth for it.
60
+ - `skills find`, `brief`, `show` and `why` answer for names the registry never
61
+ declared. The NOT SELECTED block no longer points a reader at a `skills show
62
+ <name>` that 404s for a name the graph does not carry.
63
+ - Nothing in the shipped bytes changes. ADR-061 stands as written: the registry
64
+ is workspace data, the package ships none of it, and a command that finds no
65
+ registry says so. This record settles the half ADR-061 left open — who is
66
+ allowed to have skills the catalogue does not name — without touching its
67
+ rulings.
68
+ - `skills index` keeps reporting the catalogue only: it is the tier-0 view of
69
+ what this tool ships, and local skills surface where a repo is the context,
70
+ which is `find` and `brief`.
71
+
72
+ ## Rejected
73
+
74
+ - **Rewrite ADR-009 in place.** The supersession rule this directory opens with
75
+ is a new record and an edited status line, never a rewritten ruling. ADR-009's
76
+ audit data — the forty-six frontmatter tallies that found the flat file — is
77
+ evidence its own ruling relies on, the same shape that kept ADR-011 standing
78
+ when ADR-012 reversed it.
79
+ - **Annotate ADR-009 without a record.** The supersession is a claim about what
80
+ the tool believes, and a claim without a record is a ruling nobody can cite.
81
+ The annotation points at ADR-112; ADR-112 carries the argument.
@@ -0,0 +1,87 @@
1
+ ---
2
+ simt-affects: ["src/hooks/dispatcher.mjs", "src/commands/hooks.mjs", "simt.json", "test/hooks.test.mjs"]
3
+ simt-supersedes: []
4
+ ---
5
+
6
+ # ADR-113 — a pre-push that carries only documentation skips the test gate, and says so
7
+
8
+ Status: **accepted** · 2026-10-31
9
+
10
+ ## Context
11
+
12
+ The shared dispatcher runs a repository's declared pre-push gates on every push.
13
+ For this repository that is the full test suite — fifteen hundred tests, minutes
14
+ of wall clock — and the push it guards is frequently the one shape of push the
15
+ suite adds nothing to: a release commit (version, changelog, manifest pin), a
16
+ tag, a documentation edit. The suite runs there for the same reason it runs
17
+ anywhere, which is the correct reason in general and a tax in this case.
18
+
19
+ The dispatcher's standing commitments shape what the fix may be. It fails
20
+ closed, never fail open: a gate that cannot run is a failure, not a skip. It
21
+ never weakens a control to make a hook pass, and a control is downgraded only
22
+ from the manifest, where a reviewer sees it. And it reports what did not run,
23
+ so "passed" can never be mistaken for "only some of it passed". A skip that is
24
+ invisible is a weakened control the reader did not approve; a skip that is
25
+ guessed at is a fail-open.
26
+
27
+ git answers both problems: it feeds the pre-push hook a ref list on stdin —
28
+ one line per ref being pushed, with the local and remote shas — so the push
29
+ can be diffed against exactly what the remote already has, and the manifest is
30
+ already the declared home of gate configuration.
31
+
32
+ ## Ruling
33
+
34
+ 1. **The `test` gate is the only gate a push can skip, and only on evidence.**
35
+ Before running its gates, the dispatcher classifies the push from the ref
36
+ list git fed on stdin. The `test` gate is skipped when, and only when, every
37
+ ref being pushed is one of: a branch whose diff against the remote is at
38
+ most a declared number of commits and touches only declared paths; a tag
39
+ whose commit is confirmed already delivered; or a deletion. Every other gate
40
+ runs unconditionally: they are cheap and repo-specific, and a skip the
41
+ classifier does not own is a skip it did not earn.
42
+ 2. **The skip is opt-in per repository, and absence runs the suite.** The
43
+ classification is declared in the manifest as `gates.prePushSkipTest` with
44
+ `maxCommits` and `paths`. A repository without the key runs the full suite
45
+ on every push; a key with missing or mis-shaped fields does the same. The
46
+ control is downgraded from the manifest where a reviewer sees it, and a
47
+ downgrade the manifest cannot state precisely is one it did not make.
48
+ 3. **Every uncertainty fails closed to the full suite, and a skip is reported.**
49
+ An unreadable ref list, a new branch (no remote sha to diff against), a tag
50
+ whose commit cannot be confirmed already delivered, a commit count above the
51
+ ceiling, a file outside the declared paths, or a git error each run the suite.
52
+ When the skip does fire, the report prints the gate as `SKIPPED` with the
53
+ reason — the commit count, file count, and tag count that earned it — so the
54
+ reader sees the suite was not run and why.
55
+
56
+ ## Consequences
57
+
58
+ - A push of one or two documentation or release commits no longer pays for the
59
+ full suite in this repository, whose manifest declares a ceiling of two
60
+ commits over markdown, `docs/`, `package.json` and `simt.json` — the shape
61
+ of a release commit.
62
+ - The file list is a tree diff over `remote..local`, so a force push is judged
63
+ on the trees it replaces, not on the commits it adds.
64
+ - A release tag rides along in the skip. In the release flow a tag names commits
65
+ already delivered (and tested) by a branch push, so the classifier accepts it
66
+ only when the commit it points to is reachable from a remote-tracking ref, or
67
+ is among the commits the same push is delivering on a branch. A tag on a
68
+ commit no pushed ref contains runs the suite: the gate does not assume a tag
69
+ is harmless, it verifies the commit behind it.
70
+ - The classification reads the repository's git state, so it runs on the real
71
+ root; the disposable worktree at HEAD is for the gates, and a push that
72
+ earns the skip does not need one.
73
+ - Nothing in this ruling widens to other events. Pre-commit has no ref list to
74
+ classify and keeps running its gates on every commit.
75
+
76
+ ## Rejected
77
+
78
+ - **Skip by file extension in the hook, with no manifest declaration.** The
79
+ hook would then own a policy no reviewer can read in a diff, which is the
80
+ exact failure the manifest requirement exists to prevent — and a reader of
81
+ another repository could not tell whether its suite had run.
82
+ - **Skip when the push is small, whatever it touches.** A two-line change to a
83
+ gate definition is a code change the suite exists to catch, and a size
84
+ threshold without a path rule cannot tell it from a two-line doc edit.
85
+ - **Make the skip silent.** The dispatcher's report is its contract: a gate
86
+ that did not run is named with its disposition and its reason, or the reader
87
+ cannot distinguish "passed" from "never ran".
@@ -0,0 +1,74 @@
1
+ ---
2
+ simt-affects: ["src/bench/report.mjs", "src/bench/run.mjs", "src/bench/ablate.mjs", "src/bench/variants.mjs", "src/advice/advice.mjs", "src/commands/bench.mjs", "test/bench.test.mjs"]
3
+ simt-supersedes: []
4
+ ---
5
+
6
+ # ADR-114 — the bench pairs at the task level, a missing cost is not zero, and an unasked cross-group compare is refused
7
+
8
+ Status: **accepted** · 2026-10-31
9
+
10
+ ## Context
11
+
12
+ Review 0003 (Q46) verified three failures in the bench statistics and read a fourth
13
+ in the code:
14
+
15
+ - One task with ten reps produced `pairedRuns 10, mcnemar p 0.00195` — the run-level
16
+ pairing treated reps of a task as independent tasks, inflating significance by an
17
+ order of magnitude.
18
+ - A claude run with usage but no prices table reported `C $0.0000 | CPI $0.0000` — a
19
+ missing cost read as a zero cost, the cheapest outcome measurable.
20
+ - `gold` and `noop` harness runs of the same arm name pooled into one 50 % row — the
21
+ harness that produced each run was invisible.
22
+ - Ablation tested ~N sections at α 0.05 each with no multiplicity correction, and the
23
+ bootstrap CPI confidence intervals silently dropped the resamples whose cost was
24
+ undefined.
25
+
26
+ Plan 0004 §1 fixes the shape of the answer: repeats of a task are not independent
27
+ tasks; the cost per verified outcome is undefined when none resolve, never zero; and
28
+ non-inferiority is judged by a one-sided 95 % lower bound of ΔR above −2 percentage
29
+ points, with inconclusive evidence reported when the interval is too wide to decide.
30
+
31
+ ## Ruling
32
+
33
+ 1. **Pairing is at the task level only.** `compareArms` collapses each arm to
34
+ task means and pairs on shared tasks. The exact two-sided sign test on task-mean R
35
+ replaces McNemar (the sign of a task-mean difference does not assume the 0/1
36
+ contingency McNemar requires); the Wilcoxon signed-rank test runs on task-mean C.
37
+ `pairedRuns` survives as a descriptive count (Σ min reps) and is never used in a
38
+ statistic.
39
+ 2. **An unknown cost is null, never zero.** `computeRunCost` returns `{ usd, source }`
40
+ with `source` ∈ `priced | reported | missing`; `usd` is null when no prices table
41
+ applies and the harness reported no cost. A task's C is null if any rep's cost is
42
+ missing, an arm's C likewise, and the arm's C confidence interval is null rather
43
+ than narrowed to the resamples that happened to price out. `CPI` is total cost
44
+ divided by resolved outcomes and is null when the cost is incomplete or nothing
45
+ resolved — never zero, never a favourable finite score.
46
+ 3. **A report row is a (harness, model, arm, experiment) group.** Pooling across
47
+ harnesses is gone. A `--compare` whose arm name matches more than one group is
48
+ refused as ambiguous, and a compare that crosses harness, model or experiment is
49
+ refused unless the caller passes `crossGroup` (CLI `--cross-group`).
50
+ 4. **Multiplicity is controlled with Holm step-down.** The family is the set of
51
+ comparisons tested in one run — the ablation sections, and per slot the
52
+ variant-versus-baseline comparisons — and the adjusted p-values feed the canary
53
+ escalation decision.
54
+ 5. **The verdict is a non-inferiority verdict.** With `n < 5` paired tasks the
55
+ verdict is `insufficient-tasks` — the smallest two-sided sign-test p-value at
56
+ n = 4 is 0.125, so fewer tasks cannot reject at α = 0.05 and the sample is said
57
+ to be too small, not that the p-value was high. Otherwise: `non-inferior` when
58
+ the one-sided 95 % lower bound of ΔR is above −2 pp, `inferior` when the two-sided
59
+ bound is entirely below 0, `inconclusive` when the interval is too wide to decide.
60
+ 6. **v1 records keep reading.** A numeric `costUsd` is a reported cost; a null one is
61
+ missing. No migration, no new schema version.
62
+
63
+ ## Consequences
64
+
65
+ - `mcnemarTest` stays exported and is exercised by its unit test; `compareArms` no
66
+ longer returns an `mcnemar` key, so its only live caller inside the tool is the
67
+ sign test.
68
+ - `simt bench report` markdown gains Harness and Model columns, a `Sign` row, and
69
+ non-inferiority and verdict lines; the `--cross-group` flag exists only to override
70
+ refusal.
71
+ - `simt bench ablate` and `simt bench variants` escalate on Holm-adjusted p-values,
72
+ and a section entry carries `rTest` instead of `mcnemar`.
73
+ - Advice skips un-costed runs when grounding a cost floor, and variant ranking treats
74
+ an unknown cost as worst — it cannot be ranked cheaper than anything.
@@ -0,0 +1,69 @@
1
+ ---
2
+ simt-affects: ["src/index/persist.mjs", "src/index/store.mjs", "src/commands/map.mjs", "test/index.test.mjs", "test/map.test.mjs"]
3
+ simt-supersedes: []
4
+ ---
5
+
6
+ # ADR-115 — the index stamps its schema version, migrates older databases in place, serves newer ones from a sidecar, and names a corrupt index
7
+
8
+ Status: **accepted** · 2026-10-31 · filed as review 0003 Q44
9
+
10
+ ## Context
11
+
12
+ Review 0003 (Q44) verified three failures in the local index's persistence:
13
+
14
+ - The database carried no schema version a reader trusted. A newer simt
15
+ silently rewrote a database an older simt had written, and two pinned
16
+ versions rewrote one file past each other — the ADR-059 flip-flop.
17
+ - The FTS tables, whose tokenizer lives in their definition, were
18
+ recreated by a per-write wipe (`ensureFtsSchema`) whenever the stored DDL
19
+ drifted: a migration that ran on every build, unnamed and unobservable.
20
+ - Every read failure answered `[]`. "Locked", "no such column" and "corrupt"
21
+ all looked like a cold index, so a damaged database read back as an empty
22
+ one and the command quoted the emptiness as the repository's shape.
23
+
24
+ Plan 0004 §1 fixes the shape of the answer: the index declares the version it
25
+ was written at; a version change is a named, ordered migration; a file this
26
+ version does not own is not opened for write; and a read failure is named, not
27
+ silenced.
28
+
29
+ ## Ruling
30
+
31
+ 1. **The version is stamped on every write.** `PRAGMA user_version` carries
32
+ the schema version in the sqlite database; the meta `schema` row is kept
33
+ as the fallback for databases written before the stamp existed, and as the
34
+ version carrier in the jsonl backend, whose lines carry no pragma.
35
+ 2. **Migrations are an ordered list, and the 3 → 4 step is the FTS
36
+ recreation.** Each step is idempotent and runs in list order; a database
37
+ older than the current version is opened in place and upgraded on the next
38
+ write. A database at a version past the last step is not opened for write
39
+ at all — opening it would demote the stamp.
40
+ 3. **A file a newer version owns stays with its owner.** This version reads
41
+ and writes a sidecar named after itself, `index.v<SCHEMA_V>.sqlite` (and
42
+ `.jsonl`); the canonical file is untouched. A sidecar that has not been
43
+ built yet is cold, not broken. `indexFileFor` is the single owner of the
44
+ sidecar name, shared by readers and writers.
45
+ 4. **A read returns `{ rows, error }`, and a failure is named.** `INDEX_BUSY`
46
+ is a retryable write contention; `INDEX_CORRUPT` is a file that cannot be
47
+ read as a database. The store's read wrappers attach the error to the row
48
+ array, so callers that iterate are untouched and callers that report can
49
+ tell a broken index from an empty one. `loadGraph` answers `broken`
50
+ alongside `cold` and `stale`: `cold` is "nothing here", `broken` is
51
+ "something is here and the read failed".
52
+ 5. **A corrupt canonical file is rebuilt in place by the next build.** The
53
+ index is a cache of git blobs (ADR-089): a damaged file is named on read
54
+ and replaced on write, and the rebuildability that makes the file
55
+ disposable is what makes in-place replacement safe.
56
+
57
+ ## Consequences
58
+
59
+ - `ensureFtsSchema` and its per-write wipe are gone; the FTS tables converge
60
+ through the named migration, which runs once per upgrade, not per build.
61
+ - `migrateDb` stays module-private: it is called by the writer, and the
62
+ dead-code gate reads an export as a promise to a caller this package does
63
+ not have.
64
+ - `reindex`'s `force = version !== SCHEMA_V` remains sound: an older database
65
+ is fully rewritten and then migrated in place; a newer one is left alone
66
+ and the build goes to the sidecar.
67
+ - `simt repoindex`, `simt init` and `simt map` output paths are unchanged;
68
+ `SQLITE_FILE` and `JSONL_FILE` keep their names, and the sidecar is the
69
+ only new file this ruling introduces.
@@ -0,0 +1,100 @@
1
+ ---
2
+ simt-affects: ["rules/mdc/test-gate.mdc", "src/projection/baselines.mjs", "src/bench/mine.mjs", "src/compare/learn.mjs", "test/bench.test.mjs", "test/mdc.test.mjs", "test/baselines.test.mjs", "test/compare.test.mjs"]
3
+ simt-supersedes: []
4
+ ---
5
+
6
+ # ADR-116 — Rust support reaches the example path, the indent and the bench runner
7
+
8
+ Status: **accepted** · 2026-10-31
9
+
10
+ ## Context
11
+
12
+ ADR-062 made Rust a first-class adoption: `has-cargo-toml` selects the `rust` profile,
13
+ `simt init` seeds `cargo fmt --check`, `cargo clippy -- -D warnings` and `cargo test`
14
+ when `cargo` is on PATH, and dependabot watches `Cargo.toml`. Much of the rest of the
15
+ tool already knew the ecosystem — the index extracts Rust `use` edges and records `.rs`
16
+ files, `simt secrets` reads `Cargo.lock`, the monorepo reader reads `[workspace] members`
17
+ from `Cargo.toml`, and the card resolves `cargo build` and a focused `cargo test {name}`.
18
+
19
+ Three places a Rust repository reads or runs did not know it:
20
+
21
+ - `rules/mdc/test-gate.mdc` substitutes one example test path, and only Go had a
22
+ per-signal default. A Rust repository was handed `path/to/the.changed.test.ts` — an
23
+ instruction that names a file its test gate can never run.
24
+ - The shared `.editorconfig` assigns four spaces to Python, Java and Kotlin, and left
25
+ `.rs` at the default two. Rust's own formatter writes four, so the shared baseline
26
+ contradicted `cargo fmt` in every Rust repository that adopted it.
27
+ - `detectTestRunner` knew package.json, vitest, Go, Python, Gradle and Maven. A Cargo
28
+ repository fell through to no runner, so `simt bench mine` could not grade a Rust
29
+ patch and `simt gate` could not select its tests.
30
+
31
+ The gate classifier had the same blind spot in its heuristic fallback: a declared
32
+ `cargo test` was classified through the declared command, but a command read from a
33
+ repository that had not declared it was not recognised as a test gate.
34
+
35
+ ## Ruling
36
+
37
+ 1. **A Rust repository is shown a Rust test path.** `exampleTestPath` gains
38
+ `default-has-cargo-toml: tests/the_changed.rs`. Rust's test paths are Cargo
39
+ integration tests in `tests/`; inline `#[cfg(test)]` modules live in the source file
40
+ and have no separate path to name. The signal is the detection fact, not a new stack
41
+ tag, so ADR-013 and ADR-062 still hold.
42
+ 2. **`.rs` is four spaces in the shared editorconfig.** It joins the
43
+ `[*.{py,pyi,java,kt,kts,rs}]` section with `indent_size = 4`, matching rustfmt.
44
+ 3. **The bench runs Cargo the way Cargo can be run.** A `tests/<stem>.rs` file is a
45
+ test target, so a patch touching only integration tests selects one `--test <stem>`
46
+ per file (`cargo test --test api`). A patch touching a source file means inline unit
47
+ tests, which have no file argument, so the crate's whole test binary runs
48
+ (`cargo test`). No changed files also runs the crate. The prompt command, the
49
+ executed command and the parser come from the same branch, so a preview cannot name
50
+ a narrower command than the one that ran.
51
+ 4. **Cargo's text output is parsed by its result lines.** `parseCargoTestOutput` accepts
52
+ only lines of the form `test <name> ... ok|FAILED|ignored`. `ignored` is neither a
53
+ pass nor a failure. `test result:` summaries and compile output are not tests. The
54
+ parser is shared by `simt bench mine`, `simt bench grade` and `simt gate`, which all
55
+ go through `detectTestRunner`.
56
+ 5. **`cargo test` is classified as a test gate.** The fallback in
57
+ `classifyGateCommand` recognises it alongside `npm test`, `node --test`, `pytest` and
58
+ `go test`, with ADR-003's focused-default ruling.
59
+
60
+ This record does not supersede ADR-062 (what a fresh Rust adoption seeds) or ADR-092
61
+ (which test edges count). It closes the three reader-facing gaps those records left
62
+ open.
63
+
64
+ ## Consequences
65
+
66
+ - A Rust repository's projected `test-gate` names `tests/the_changed.rs`; no TypeScript
67
+ or Go example path leaks into it. The signal is `has-cargo-toml`, so a mixed
68
+ repository with both `package.json` and `Cargo.toml` is shown the Rust example.
69
+ - Every repository that adopts `.editorconfig` now writes `.rs` at four spaces. A
70
+ repository that deliberately wants another indent still overrides it below the
71
+ region, which is what last-match-wins is for.
72
+ - `simt gate <path>` on a Rust repository selects `cargo test --test <target>` for an
73
+ integration test and `cargo test` for a source file. The bench can mine and grade
74
+ Rust tasks with the same evidence discipline as the other ecosystems.
75
+ - The parser has no JSON dependency: `cargo test --format json` is nightly-only, and
76
+ this package has zero runtime dependencies and must not require a nightly toolchain
77
+ to read a test result.
78
+ - Acceptance: the Rust render of `test-gate` names only `tests/the_changed.rs`; the
79
+ editorconfig section for `.rs` states four spaces; a Cargo runner is detected for
80
+ both `tests/*.rs` and `src/*.rs`; libtest output yields passed and failed names and
81
+ drops ignored tests.
82
+
83
+ ## Rejected
84
+
85
+ - **A `rust` stack tag.** Rust is detected by `has-cargo-toml`; adding a tag would give
86
+ one ecosystem two identities and break the fact/tag separation ADR-013 relies on.
87
+ - **Seeding `typecheck: cargo check`.** ADR-062 rules the seeded Rust gates to format,
88
+ lint and test. Adding a gate changes what a fresh adoption declares, and that is a
89
+ separate decision with its own acceptance tests, not a silent part of this one.
90
+ - **A conventional `tests/foo.rs` → `src/foo.rs` edge.** A Cargo integration test is a
91
+ separate crate and need not mirror a source file. ADR-092 permits a conventional edge
92
+ only when the convention is real; guessing one would manufacture test coverage that
93
+ does not exist.
94
+ - **A Rust toolchain pin check.** The Rust profile requires a pin only where the
95
+ repository commits one. `src/toolchain.mjs` is deliberately Node-specific; extending
96
+ it to read `rust-toolchain.toml` or `rust-version` is a new belief and needs its own
97
+ record.
98
+ - **Adding `.rs` to `FORMATTABLE`.** That set is the staged-file list for Prettier.
99
+ Rust formatting is a declared command gate (`cargo fmt --check`), and handing `.rs`
100
+ to Prettier would format it with the wrong tool.
@@ -0,0 +1,81 @@
1
+ ---
2
+ simt-affects: [".github/workflows/publish.yml", "test/binary.test.mjs"]
3
+ simt-supersedes: [{ adr: "ADR-099", ruling: 1, kind: "superseded" }]
4
+ ---
5
+
6
+ # ADR-117 — the darwin release builds on supported macOS 15 runners, and a re-run is idempotent
7
+
8
+ Status: **accepted** · 2026-10-31 · supersedes ADR-099 ruling 1
9
+
10
+ ## Context
11
+
12
+ The v0.7.0 release run `41215349142` reached the first binary release: the npm stage
13
+ succeeded, the release record was created, and `linux-x64` built. Both darwin jobs failed
14
+ in the same place — `macos-14`, the runner ADR-099 ruling 1 named, is deprecated, and
15
+ GitHub was running scheduled brownouts of that image ahead of its retirement.
16
+
17
+ The old matrix also used one label for two architectures. GitHub's runner-images table
18
+ now names `macos-14` as arm64 and `macos-14-large` as Intel, so "both targets on
19
+ `macos-14`" was not the native-runner arrangement ADR-099 claimed. The replacement has
20
+ to name the arm64 and Intel images explicitly.
21
+
22
+ The same run exposed a second release-path defect. `publish-npm` decided whether to skip
23
+ by asking the registry whether the version was published. A staged version is not
24
+ published, so re-running the workflow after every other job succeeded would try to stage
25
+ the same version again and fail on the duplicate. "Re-run all jobs" is the normal way to
26
+ recover a partially failed release, and it must not turn a completed stage into a red
27
+ job.
28
+
29
+ ## Ruling
30
+
31
+ 1. **Darwin targets build on the supported native macOS 15 images.**
32
+ `darwin-arm64` runs on `macos-15` and `darwin-x64` runs on `macos-15-intel`;
33
+ `linux-x64` stays on `ubuntu-latest`. These are the standard, non-deprecated labels
34
+ for the two architectures, not the larger-runner variants. ADR-099's per-host
35
+ determinism and upload-integrity claims are unchanged; only the label that provides
36
+ the native host moves.
37
+
38
+ 2. **A re-run is idempotent for a stage that already succeeded.**
39
+ `publish-npm` checks the npm staging queue as well as the registry. A version that is
40
+ published *or* already staged sets `SKIP=true`, so the tests, the wheel check and
41
+ `npm stage publish` are not repeated. The staging check runs after npm 11 is
42
+ available, because `npm stage list` is the command that makes the queue visible. A
43
+ failed stage list is not treated as proof of absence: the job attempts the stage and
44
+ fails loudly rather than silently skipping a release that does not exist.
45
+
46
+ The release record, asset upload and formula mirror were already idempotent —
47
+ `create-release` edits an existing release, `gh release upload --clobber` replaces
48
+ assets, and `sync-formula` exits when the mirror already names the served bytes.
49
+ They stay that way. A re-run does not skip a build on the grounds that an asset with
50
+ the same name exists: the tag can have moved, and the asset must be built from the
51
+ tree the tag now names.
52
+
53
+ ## Consequences
54
+
55
+ - A darwin failure is recovered on a runner GitHub supports, and the arm64 and Intel
56
+ assets are produced on the architectures they claim.
57
+ - "Re-run all jobs" after a successful stage is green: the stage job says the version is
58
+ already staged, the release record is refreshed in place, and the formula is compared
59
+ rather than overwritten.
60
+ - "Re-run failed jobs" remains the narrower recovery: it rebuilds only the targets that
61
+ failed, which is the path the v0.7.0 run needed.
62
+ - ADR-099 ruling 1 is superseded. Its ruling 2 (upload integrity) and ruling 3 (the
63
+ formula mirrors served bytes) still govern the release.
64
+ - `test/binary.test.mjs` reads the committed workflow and asserts the two darwin labels,
65
+ so a future edit that returns to a deprecated or wrong-architecture runner fails the
66
+ suite instead of a release.
67
+
68
+ ## Rejected
69
+
70
+ - **`macos-latest` for arm64.** It moves with GitHub's newest stable image. A release
71
+ builder should change when this repository decides to change it, not when a label
72
+ migrates.
73
+ - **`macos-26`.** It is newer than the release needs, and no run has proven the pinned
74
+ Node base and `codesign` pipeline on it. Moving to it is a separate reviewed change.
75
+ - **`macos-14-large`.** It is the Intel half of a deprecated image, so it inherits the
76
+ brownout that caused this failure.
77
+ - **Skipping a build because the asset name already exists.** The tag this workflow runs
78
+ for can be moved, and an asset left from an earlier tree would then be served as the
79
+ new tag's binary. Rebuilding and clobbering keeps the bytes tied to the tagged tree.
80
+ - **Detecting a prior stage through the registry alone.** A stage is deliberately not
81
+ public, so the registry cannot answer whether it exists.