simt-dev 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. package/CHANGELOG.md +191 -870
  2. package/README.md +188 -213
  3. package/bin/simt.mjs +19 -55
  4. package/docs/decisions/ADR-014-workspace-is-configuration.md +2 -2
  5. package/docs/decisions/ADR-025-a-baseline-is-a-region-in-a-file-the-repo-owns.md +1 -1
  6. package/docs/decisions/ADR-026-a-config-without-a-region-is-a-file-the-repo-owns.md +1 -1
  7. package/docs/decisions/ADR-027-the-one-gitattributes-line-that-is-neither-dangerous-nor-empty.md +1 -1
  8. package/docs/decisions/ADR-028-a-base-file-is-a-floor-so-the-disagreement-it-cannot-repair-becomes-a-check.md +1 -1
  9. package/docs/decisions/ADR-029-the-pins-a-repository-declares-are-its-own.md +1 -1
  10. package/docs/decisions/ADR-030-what-a-plan-does-not-ship-is-a-decision-with-a-record.md +1 -1
  11. package/docs/decisions/ADR-048-a-baseline-carries-only-keys-a-reader-honours.md +1 -1
  12. package/docs/decisions/ADR-082-the-tracker-override-is-a-manifest-key.md +1 -1
  13. package/docs/decisions/ADR-099-the-release-verifies-served-bytes-and-the-formula-mirrors-them.md +1 -1
  14. package/docs/decisions/ADR-117-the-darwin-release-builds-on-supported-macos-15-runners-and-a-re-run-is-idempotent.md +81 -0
  15. 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
  16. package/docs/decisions/ADR-119-the-environment-variable-points-into-a-workspace-the-repo-declared.md +90 -0
  17. package/docs/decisions/ADR-120-a-pre-push-whose-tree-is-already-proven-green-skips-the-test-gate-on-that-evidence.md +96 -0
  18. package/docs/decisions/ADR-122-the-shims-move-into-the-state-directory-and-the-legacy-directory-removes-itself.md +94 -0
  19. package/docs/decisions/ADR-123-a-surplus-positional-is-not-refused-because-arity-is-not-in-the-source.md +57 -0
  20. package/docs/decisions/ADR-124-the-manifest-has-a-schema-in-the-tool-and-its-unknown-keys-stay-the-repositorys.md +64 -0
  21. package/docs/decisions/ADR-125-a-shared-context-belongs-to-a-read-and-a-writer-reads-directly.md +65 -0
  22. package/docs/decisions/ADR-126-the-mcp-surface-stays-a-reader-so-there-is-no-write-path-to-build.md +68 -0
  23. package/docs/decisions/ADR-127-the-mcp-tool-list-is-written-by-hand-and-roots-is-not-asked-for.md +65 -0
  24. package/docs/decisions/ADR-128-a-research-plan-is-a-plan-and-the-tracker-reads-it.md +42 -0
  25. package/ecosystems/bun.json +6 -0
  26. package/ecosystems/cargo.json +25 -0
  27. package/ecosystems/composer.json +6 -0
  28. package/ecosystems/dart.json +24 -0
  29. package/ecosystems/deno.json +21 -0
  30. package/ecosystems/devcontainers.json +6 -0
  31. package/ecosystems/docker-compose.json +6 -0
  32. package/ecosystems/docker.json +6 -0
  33. package/ecosystems/dotnet.json +25 -0
  34. package/ecosystems/elixir.json +23 -0
  35. package/ecosystems/gitsubmodule.json +6 -0
  36. package/ecosystems/go.json +25 -0
  37. package/ecosystems/gradle.json +13 -0
  38. package/ecosystems/maven.json +23 -0
  39. package/ecosystems/node.json +25 -0
  40. package/ecosystems/python.json +20 -0
  41. package/ecosystems/ruby.json +23 -0
  42. package/ecosystems/swift.json +25 -0
  43. package/ecosystems/terraform.json +6 -0
  44. package/ecosystems/uv.json +6 -0
  45. package/harnesses/antigravity.json +162 -0
  46. package/harnesses/claude.json +206 -0
  47. package/harnesses/codex.json +136 -0
  48. package/harnesses/copilot.json +88 -0
  49. package/harnesses/cursor.json +140 -0
  50. package/harnesses/deepseek.json +99 -0
  51. package/harnesses/gemini.json +122 -0
  52. package/harnesses/opencode.json +173 -0
  53. package/package.json +12 -2
  54. package/rules/core.md +7 -0
  55. package/rules/mdc/intake-questions.mdc +8 -0
  56. package/rules/mdc/slice-commit.mdc +57 -4
  57. package/src/advice/advice.mjs +3 -2
  58. package/src/agenthooks/prompt.mjs +5 -2
  59. package/src/agenthooks/render.mjs +86 -180
  60. package/src/agenthooks/shim.mjs +1 -1
  61. package/src/agents/render.mjs +15 -18
  62. package/src/api.mjs +31 -0
  63. package/src/argv.mjs +48 -3
  64. package/src/bench/ablate.mjs +3 -4
  65. package/src/bench/adapters.json +163 -105
  66. package/src/bench/adapters.mjs +466 -0
  67. package/src/bench/arms.mjs +333 -0
  68. package/src/bench/grade.mjs +33 -73
  69. package/src/bench/graders.mjs +42 -0
  70. package/src/bench/mine.mjs +121 -475
  71. package/src/bench/probe-ledger.mjs +149 -0
  72. package/src/bench/probes.catalog.mjs +918 -0
  73. package/src/bench/probes.mjs +79 -1064
  74. package/src/bench/render.mjs +111 -0
  75. package/src/bench/report.mjs +120 -962
  76. package/src/bench/run.mjs +185 -802
  77. package/src/bench/variants.mjs +4 -5
  78. package/src/bench/workspace.mjs +160 -0
  79. package/src/brief/assemble.mjs +48 -15
  80. package/src/ci/dependabot.mjs +25 -55
  81. package/src/ci/workflow.mjs +30 -7
  82. package/src/cli.mjs +45 -0
  83. package/src/commands/agent.mjs +4 -13
  84. package/src/commands/agenthook.mjs +70 -40
  85. package/src/commands/bench.mjs +191 -18
  86. package/src/commands/brief.mjs +5 -4
  87. package/src/commands/ci.mjs +13 -14
  88. package/src/commands/compare.mjs +42 -42
  89. package/src/commands/completions.mjs +2 -6
  90. package/src/commands/decide.mjs +4 -4
  91. package/src/commands/distill.mjs +4 -3
  92. package/src/commands/docs.mjs +88 -0
  93. package/src/commands/doctor.mjs +11 -7
  94. package/src/commands/evidence.mjs +6 -9
  95. package/src/commands/explain.mjs +0 -1
  96. package/src/commands/gate.mjs +26 -25
  97. package/src/commands/graph.mjs +2 -3
  98. package/src/commands/harness.mjs +6 -5
  99. package/src/commands/help.mjs +168 -19
  100. package/src/commands/hooks.mjs +119 -49
  101. package/src/commands/ignore.mjs +13 -8
  102. package/src/commands/impact.mjs +8 -15
  103. package/src/commands/index.mjs +318 -193
  104. package/src/commands/init-scan.mjs +90 -0
  105. package/src/commands/init.mjs +124 -105
  106. package/src/commands/lease.mjs +73 -25
  107. package/src/commands/lessons.mjs +10 -9
  108. package/src/commands/local.mjs +3 -3
  109. package/src/commands/map.mjs +24 -14
  110. package/src/commands/mcp.mjs +2 -18
  111. package/src/commands/pack.mjs +9 -6
  112. package/src/commands/plan.mjs +6 -5
  113. package/src/commands/plugin.mjs +2 -2
  114. package/src/commands/prbody.mjs +3 -3
  115. package/src/commands/repoindex.mjs +12 -11
  116. package/src/commands/review.mjs +2 -2
  117. package/src/commands/rules.mjs +37 -16
  118. package/src/commands/shared.mjs +14 -19
  119. package/src/commands/skills.mjs +33 -32
  120. package/src/commands/skillscompile.mjs +7 -8
  121. package/src/commands/skillsinstall.mjs +7 -6
  122. package/src/commands/slice.mjs +220 -74
  123. package/src/commands/spec.mjs +167 -0
  124. package/src/commands/status.mjs +27 -18
  125. package/src/commands/sync.mjs +27 -25
  126. package/src/commands/tests.mjs +18 -21
  127. package/src/commands/tokens.mjs +21 -26
  128. package/src/commands/undo.mjs +5 -15
  129. package/src/commands/upgrade.mjs +15 -7
  130. package/src/commands/why.mjs +15 -21
  131. package/src/compare/hooksstate.mjs +2 -6
  132. package/src/compare/learn.mjs +3 -1
  133. package/src/compare/row.mjs +0 -2
  134. package/src/context.mjs +64 -0
  135. package/src/decisions/index-doc.mjs +46 -0
  136. package/src/decisions/index.mjs +3 -2
  137. package/src/diff.mjs +1 -1
  138. package/src/distill/distill.mjs +14 -1
  139. package/src/distill/engine.mjs +5 -1
  140. package/src/docs/commands.mjs +65 -0
  141. package/src/ecosystem/records.mjs +219 -0
  142. package/src/explain.mjs +31 -0
  143. package/src/gates/index.mjs +385 -0
  144. package/src/graph/compile.mjs +8 -4
  145. package/src/graph/load.mjs +6 -2
  146. package/src/graph/spec.mjs +10 -8
  147. package/src/harness/records.mjs +143 -0
  148. package/src/hooks/capability.mjs +7 -5
  149. package/src/hooks/dispatcher.mjs +209 -60
  150. package/src/hooks/gates.mjs +10 -43
  151. package/src/hooks/resolve.mjs +4 -14
  152. package/src/hooks/shim.mjs +5 -2
  153. package/src/hooks/where.mjs +2 -7
  154. package/src/identity.mjs +31 -12
  155. package/src/index/coverage.mjs +58 -0
  156. package/src/index/extract.mjs +6 -16
  157. package/src/index/gate.mjs +1 -1
  158. package/src/index/govern.mjs +26 -9
  159. package/src/index/impact.mjs +4 -8
  160. package/src/index/map.mjs +14 -15
  161. package/src/index/open.mjs +173 -0
  162. package/src/index/persist.mjs +224 -57
  163. package/src/index/store.mjs +82 -39
  164. package/src/index/tests.mjs +2 -6
  165. package/src/journal.mjs +94 -20
  166. package/src/kernel/card.mjs +37 -63
  167. package/src/kernel/checks.mjs +4 -10
  168. package/src/lease.mjs +102 -7
  169. package/src/ledger/index.mjs +150 -14
  170. package/src/local/client.mjs +4 -6
  171. package/src/locate.mjs +33 -9
  172. package/src/manifest/schema.mjs +213 -0
  173. package/src/mcp/paginate.mjs +33 -0
  174. package/src/mcp/resources.mjs +7 -10
  175. package/src/mcp/schemas.mjs +512 -0
  176. package/src/mcp/server.mjs +119 -25
  177. package/src/mcp/tools-facts.mjs +326 -0
  178. package/src/mcp/tools-read.mjs +221 -0
  179. package/src/mcp/tools.mjs +4 -967
  180. package/src/measure/corpus.mjs +150 -0
  181. package/src/measure/metrics.mjs +236 -0
  182. package/src/measure/records.mjs +211 -0
  183. package/src/measure/session.mjs +3 -2
  184. package/src/measure/stats.mjs +538 -0
  185. package/src/measure/task-schema.mjs +125 -0
  186. package/src/measure/tasks.mjs +69 -0
  187. package/src/memory/decide.mjs +6 -15
  188. package/src/memory/lesson-text.mjs +61 -0
  189. package/src/memory/lessons.mjs +29 -82
  190. package/src/name.mjs +7 -0
  191. package/src/node-version.mjs +37 -0
  192. package/src/output/index.mjs +209 -10
  193. package/src/packs/manage.mjs +32 -12
  194. package/src/packs.mjs +20 -2
  195. package/src/plan/index.mjs +1 -1
  196. package/src/plugins.mjs +22 -16
  197. package/src/prbody.mjs +7 -11
  198. package/src/projection/loaders.mjs +7 -692
  199. package/src/projection/lock.mjs +11 -1
  200. package/src/projection/manifest.mjs +29 -468
  201. package/src/projection/mdc.mjs +7 -8
  202. package/src/projection/project.mjs +126 -221
  203. package/src/projection/region.mjs +561 -0
  204. package/src/projection/rules.mjs +20 -550
  205. package/src/projection/surfaces.mjs +43 -210
  206. package/src/projection/targets.mjs +5 -5
  207. package/src/repo-root.mjs +31 -0
  208. package/src/resolve/eval.mjs +2 -1
  209. package/src/resolve/fingerprint.mjs +19 -22
  210. package/src/resolve/resolve.mjs +1 -41
  211. package/src/review/critic.mjs +10 -13
  212. package/src/run.mjs +98 -0
  213. package/src/secrets.mjs +14 -22
  214. package/src/state/events.mjs +129 -0
  215. package/src/status-snapshot.mjs +233 -0
  216. package/src/status.mjs +2 -6
  217. package/src/sys/git.mjs +82 -0
  218. package/src/sys/proc.mjs +151 -0
  219. package/src/testing/parsers.mjs +265 -0
  220. package/src/testing/patches.mjs +23 -0
  221. package/src/testing/paths.mjs +49 -0
  222. package/src/testing/runners.mjs +186 -0
  223. package/src/testing/vault.mjs +68 -0
  224. package/src/text/frontmatter.mjs +49 -0
  225. package/src/text/tokens.mjs +52 -0
  226. package/src/toolwords.mjs +3 -0
  227. package/src/util/fs.mjs +20 -0
  228. package/src/util/sort.mjs +12 -0
  229. package/src/workspace.mjs +58 -11
  230. package/src/write/plan.mjs +82 -0
  231. package/templates/plan/tracker.json +1 -0
  232. package/docs/decisions/ADR-033-three-plan-rows-closed-by-decision-not-by-authoring.md +0 -82
  233. package/docs/decisions/ADR-102-the-two-queued-review-rows-are-closed-by-ruling.md +0 -49
  234. package/docs/decisions/README.md +0 -600
  235. package/src/bench/tasks.mjs +0 -36
@@ -0,0 +1,65 @@
1
+ ---
2
+ simt-affects: ["src/context.mjs", "src/commands/hooks.mjs", "test/hooks-context.test.mjs"]
3
+ simt-supersedes: []
4
+ ---
5
+
6
+ # ADR-125 — a shared context belongs to a read, and a writer reads directly
7
+
8
+ Status: **accepted** · 2026-10-31
9
+
10
+ ## Context
11
+
12
+ `repoContext` holds one pending promise per question for the length of a command. It
13
+ was threaded through the reads — `check`, the four skills reads, `diagnose`, the rules
14
+ catalogue — and Q67's last clause was to finish the job, which raised the question the
15
+ row did not answer: should a command that *writes* hold a context too?
16
+
17
+ The two cases are not the same. A reader asks several questions about an unchanging
18
+ tree and pays once. A writer changes the tree between its questions: `init` writes a
19
+ manifest and then renders against it, `upgrade` replaces files and then reports drift,
20
+ `sync` rewrites the region and then computes the lock. A context that survived those
21
+ writes would answer the second question with the first answer, and the command would
22
+ report a repository it had already changed.
23
+
24
+ ## Ruling
25
+
26
+ ### 1. A read command may hold one context for its whole run
27
+
28
+ A command that only reads creates one `repoContext` at its entry and passes it to
29
+ every reader beneath it. Two questions with the same answer cost one computation, and
30
+ the cache reports its own hits so a context that is wired but never used cannot pass
31
+ for a fast machine.
32
+
33
+ ### 2. A writer reads its repository directly
34
+
35
+ A writer calls the readers as it always has. It is not an optimisation left undone: a
36
+ shared context would make a stale answer the reward for a correct write, and the
37
+ failure would surface as a drifted manifest in a repository that had just been fixed.
38
+
39
+ ### 3. The hook path keeps its dynamic import
40
+
41
+ `planHooksInstall` resolves the node runner from a fingerprint, and it imports the
42
+ fingerprint module dynamically so that loading the hooks command does not load it.
43
+ Static-importing a context into `src/commands/hooks.mjs` costs about 3 ms of module
44
+ graph on the shim path, which Q64 measured and removed once. A caller that holds a
45
+ context passes it and shares the walk; a caller that does not keeps the light path.
46
+
47
+ ## Consequences
48
+
49
+ - Q67 closes with the reads threaded and the writers explained rather than converted.
50
+ - The 3 ms module cost is paid only when a command actually uses a context; `hooks
51
+ run` keeps the dynamic path.
52
+ - A future writer that wants a context has this record to argue against, instead of
53
+ discovering the staleness as a bug report.
54
+
55
+ ## Rejected
56
+
57
+ - **Giving every writer a context and invalidating entries after each write.** The
58
+ invalidation is the whole bug surface: forget one entry and the failure is a stale
59
+ answer that looks like a correct one, in the command whose job is to make the tree
60
+ correct.
61
+ - **A process-lifetime context.** `bench` writes manifests mid-run; a process-wide
62
+ memo would answer a benchmark for a repository it had just changed.
63
+ - **Making the context required on every reader signature.** It would put the cache in
64
+ the contract of functions that do not need one, and a caller with no context would
65
+ have to invent an empty one.
@@ -0,0 +1,68 @@
1
+ ---
2
+ simt-affects: ["src/mcp/server.mjs", "src/mcp/tools.mjs", "docs/reviews/0003-pre-plan-0004-review.md"]
3
+ simt-supersedes: []
4
+ ---
5
+
6
+ # ADR-126 — the MCP surface stays a reader, so there is no write path to build
7
+
8
+ Status: **accepted** · 2026-11-01
9
+
10
+ ## Context
11
+
12
+ Review 0003's Q66 lists "the two-step write path" among the clauses still open for the
13
+ MCP surface. The clause reads as work to do; the file says otherwise. `src/mcp/server.mjs`
14
+ opens with three decisions, the third of which is that no mutating tool is exposed —
15
+ `init`, `sync`, `hooks install`, `skills install` and `upgrade` are absent on purpose,
16
+ because an editor that can call a tool can call it without a human reading a diff first.
17
+
18
+ Those two statements have stood in the same repository for months without meeting. The
19
+ question the row asks and the rule the file states are the same question, and the answer
20
+ was written down where the row's reader would not look.
21
+
22
+ The argument for exposure is real and worth stating: a client that can read a repository
23
+ can also want to fix it, and a plan-then-apply pair would let it see what would change
24
+ before changing anything. What it would not do is make the change reviewed. `--yes` on
25
+ the command line is a human decision with a preview in front of it; the same flag arriving
26
+ inside a tool call is a model deciding to pass it.
27
+
28
+ ## Ruling
29
+
30
+ ### 1. No tool on this surface writes
31
+
32
+ Every tool returns a value and changes nothing. The capability block advertises no write
33
+ capability, and this is not a gap to be filled later: a tool that writes is a different
34
+ surface with a different review obligation, and it is not this one.
35
+
36
+ ### 2. A caller that wants to write runs the command
37
+
38
+ The commands already do plan-then-apply properly: they print a preview, refuse without
39
+ `--yes`, journal what they write, and `undo` restores it. A client that wants that power
40
+ runs `simt <command>` as a subprocess, where the human who opened the editor sees the
41
+ preview and passes the flag — or does not.
42
+
43
+ ### 3. Exposing a writer needs a superseding record, not a patch
44
+
45
+ If a future surface must write, it arrives with its own decision: what the plan token
46
+ proves, who approves the apply, how a half-applied run is undone, and what the journal
47
+ says. Approving a diff in an editor is a claim the server cannot verify, and a
48
+ confirmation prompt is a user interface the server does not own.
49
+
50
+ ## Consequences
51
+
52
+ - Q66's write-path clause closes as withdrawn, not deferred. The row names this record so
53
+ the next reader does not reopen it as unfinished work.
54
+ - The read-only guarantee in the server's header, the capability block and the tests now
55
+ have one record behind them instead of three comments.
56
+ - A tool that returns `{ written: false }` for every input is not a compromise: it is the
57
+ shape that makes a client believe a write happened.
58
+
59
+ ## Rejected
60
+
61
+ - **A plan/apply pair behind a confirmation prompt.** The prompt is rendered by the client,
62
+ which may not render one, and the server cannot tell an approved apply from an unapproved
63
+ one. A safety property that depends on another program's UI is not a property.
64
+ - **A write tool gated on `--yes` passed through the arguments.** The flag would be asserted
65
+ by the same model that decided to write; the preview would be read by nobody.
66
+ - **Exposing `sync` alone because it is idempotent.** It rewrites the generated region of
67
+ every repository in a workspace. Idempotence says a second run changes nothing, not that
68
+ the first run was reviewed.
@@ -0,0 +1,65 @@
1
+ ---
2
+ simt-affects: ["src/mcp/tools.mjs", "src/commands/spec.mjs", "test/mcp-spec.test.mjs", "docs/reviews/0003-pre-plan-0004-review.md"]
3
+ simt-supersedes: []
4
+ ---
5
+
6
+ # ADR-127 — the MCP tool list is written by hand and held to the spec, and roots is not asked for
7
+
8
+ Status: **accepted** · 2026-11-01
9
+
10
+ ## Context
11
+
12
+ Two clauses of review 0003's Q66 read as work still to do: "`tools/list` generated from
13
+ spec rows with `mcp` set", and "`roots`". Both were written before the spec rows existed,
14
+ when the four lists this review found disagreeing were the live problem and generation
15
+ looked like the answer to all of them.
16
+
17
+ The spec rows now exist, and they carry `path`, `summary`, `flags` and, for the commands
18
+ the surface exposes, `mcp: {name, flags}`. That is enough to say *which* flags a tool
19
+ exposes and not enough to say what they are: no types, no per-argument descriptions, and
20
+ no vocabulary shared with the transport. `check` declares no `mcp` flags and its tool
21
+ takes `root`; `compare` declares none and its tool takes `root`, `limit`, `cursor` and
22
+ `concurrency`. An `inputSchema` derived from the rows would describe `--dry-run` as a
23
+ property and omit `root`, which is a contract a client cannot call.
24
+
25
+ `roots` is the other direction. It is a client capability: the server sends `roots/list`
26
+ and the client answers with the directories it may touch. `src/mcp/server.mjs` answers
27
+ requests and never sends them — the only thing it writes unprompted is a notification for
28
+ a progress token the client supplied. A server that asks a question needs a correlation
29
+ table for replies, a timeout for a client that never answers, and a fallback for the
30
+ clients that do not implement the capability; and every tool here already takes its root
31
+ as an argument (`root`, `workspace`, or `--paths`), which is the same information stated
32
+ once, by the caller, where it can be read in the call rather than inferred from a
33
+ handshake.
34
+
35
+ ## Ruling
36
+
37
+ ### 1. The tool list stays written by hand, and tests hold it to the spec
38
+
39
+ The spec row is the source of the *command*: its path, its summary and its flags. It is
40
+ not the source of the transport's schema, which carries descriptions, types and defaults
41
+ the row does not have, and arguments the command line does not spell as flags.
42
+
43
+ What generation was meant to prevent — a tool with no command, a command nobody can reach
44
+ over the transport, an advertised flag the tool refuses — is enforced in
45
+ `test/mcp-spec.test.mjs`, in all three directions, plus `MCP_ONLY` for the one tool that is
46
+ deliberately not a command (`federation_query`, which names its reason). A hand-written
47
+ list under a test that fails on every kind of drift is the same guarantee as a generated
48
+ one, without a schema nobody would trust enough to keep descriptions in.
49
+
50
+ ### 2. `roots` is withdrawn, not deferred
51
+
52
+ No tool is changed to accept an implicit root, and the server gains no request path. A
53
+ client that wants a tool to run against a directory passes it as the argument the schema
54
+ already names. If a future capability genuinely needs an answer from the client, that is
55
+ a different surface with its own correlation and timeout design, and it needs its own
56
+ record.
57
+
58
+ ## Consequences
59
+
60
+ - Reading `mcp: {name, flags}` tells a reader which flags reach the transport; reading
61
+ `src/mcp/tools.mjs` tells them what a caller sees. Both are checked against each other.
62
+ - Adding a second MCP-only tool means a `MCP_ONLY` entry with a reason, which is a place a
63
+ reviewer looks, rather than a hole in the table.
64
+ - The 2 KB `tools/list` budget keeps being spent on descriptions a reader can act on,
65
+ because nothing regenerates them from a summary phrase.
@@ -0,0 +1,42 @@
1
+ ---
2
+ simt-affects: ["templates/plan/tracker.json", "src/plan/index.mjs", "src/projection/manifest.mjs", "test/rules.test.mjs", "test/plan.test.mjs"]
3
+ simt-supersedes: ["ADR-082"]
4
+ ---
5
+
6
+ # ADR-128 — A research plan is a plan, and the tracker reads it
7
+
8
+ Status: **accepted** · 2026-11-01 · supersedes ADR-082 ruling 1
9
+
10
+ ## Context
11
+
12
+ The four founding plans left `docs/plans` for `docs/research`: completed work of a
13
+ different kind — research — but plans nonetheless, with tracker tables, slice ids and wave
14
+ dependencies. The tracker reads only the directories its template lists, so the move
15
+ de-indexed all four at once. A wave dependency that resolved through them broke with it:
16
+ `R01` in the research plan 0003 depends on `review 0002 Wave 0`, and the slices it names
17
+ live in the de-indexed documents.
18
+
19
+ The directory set was never code. ADR-081 and ADR-082 settled the mechanism — a template
20
+ lists any number of directories, each with a `kind`, a `word` and a `path`, and the parser,
21
+ the help text and `plan status` all read it — and ADR-082 ruling 1 fixed the *delivered*
22
+ set at two. Fixing the delivered set is a ruling, and this is the new one.
23
+
24
+ ## Ruling
25
+
26
+ 1. **The delivered template lists `docs/research` as a third directory, kind `research`,
27
+ word `research`.** A repository that sets nothing gets all three; a repository that
28
+ overrides `plans` replaces the `directories` section wholesale, exactly as before, so no
29
+ existing override is disturbed.
30
+ 2. **The directory is data, not a special case.** Nothing in `src/` names `research`; the
31
+ parser, help and status keep reading the template. A fourth directory arrives the same
32
+ way: one entry in the file, one record here.
33
+
34
+ ## Consequences
35
+
36
+ - `simt plan status` names three directories, and the research plans are indexed again:
37
+ their slices count, and a dependency may name one as `research 0003 R01`.
38
+ - Template order is index order. `research` sits between `plans` and `reviews`, which keeps
39
+ the moved documents ahead of the reviews they depend on — the same relative order they
40
+ had while they were in `docs/plans`.
41
+ - The tests that walk the tracker's documents (`test/rules.test.mjs`) read the template's
42
+ directories instead of a hardcoded pair, so a future entry reaches them without an edit.
@@ -0,0 +1,6 @@
1
+ {
2
+ "id": "bun",
3
+ "dependabot": [
4
+ { "order": 20, "fact": "has-bun-lock", "value": "bun", "manifest": "bun.lock" }
5
+ ]
6
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "id": "cargo",
3
+ "facts": ["has-cargo-toml"],
4
+ "tool": "cargo",
5
+ "manifestOrder": 1,
6
+ "gates": {
7
+ "preCommit": ["format", "lint"],
8
+ "prePush": ["test"],
9
+ "commands": {
10
+ "format": "cargo fmt --check",
11
+ "lint": "cargo clippy -- -D warnings",
12
+ "test": "cargo test"
13
+ }
14
+ },
15
+ "card": {
16
+ "build": { "order": 1, "condition": { "fact": "has-cargo-toml" }, "command": "cargo build" },
17
+ "focused": [
18
+ { "order": 5, "kind": "Rust", "condition": { "fact": "has-cargo-toml" }, "template": "cargo test {name}" }
19
+ ]
20
+ },
21
+ "dependabot": [
22
+ { "order": 6, "fact": "has-cargo-toml", "value": "cargo", "manifest": "Cargo.toml" }
23
+ ],
24
+ "languages": [{ "lang": "rust", "exts": [".rs"] }]
25
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "id": "composer",
3
+ "dependabot": [
4
+ { "order": 8, "fact": "has-composer", "value": "composer", "manifest": "composer.json" }
5
+ ]
6
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "id": "dart",
3
+ "facts": ["has-pubspec"],
4
+ "tool": "dart",
5
+ "manifestOrder": 7,
6
+ "gates": {
7
+ "preCommit": ["format", "lint"],
8
+ "prePush": ["test"],
9
+ "commands": {
10
+ "format": "dart format --output=none --set-exit-if-changed .",
11
+ "lint": "dart analyze",
12
+ "test": "dart test"
13
+ }
14
+ },
15
+ "card": {
16
+ "focused": [
17
+ { "order": 12, "kind": "Dart", "condition": { "fact": "has-pubspec" }, "template": "dart test {file}" }
18
+ ]
19
+ },
20
+ "dependabot": [
21
+ { "order": 16, "fact": "has-pubspec", "value": "pub", "manifest": "pubspec.yaml" }
22
+ ],
23
+ "languages": [{ "lang": "dart", "exts": [".dart"] }]
24
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "id": "deno",
3
+ "facts": ["has-deno-config"],
4
+ "tool": "deno",
5
+ "manifestOrder": 5,
6
+ "gates": {
7
+ "preCommit": ["format", "lint"],
8
+ "prePush": ["test", "typecheck"],
9
+ "commands": {
10
+ "format": "deno fmt --check",
11
+ "lint": "deno lint",
12
+ "test": "deno test",
13
+ "typecheck": "deno check ."
14
+ }
15
+ },
16
+ "card": {
17
+ "focused": [
18
+ { "order": 10, "kind": "Deno", "condition": { "fact": "has-deno-config" }, "template": "deno test {file}" }
19
+ ]
20
+ }
21
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "id": "devcontainers",
3
+ "dependabot": [
4
+ { "order": 21, "fact": "has-devcontainer", "value": "devcontainers", "manifest": "devcontainer.json" }
5
+ ]
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "id": "docker-compose",
3
+ "dependabot": [
4
+ { "order": 11, "fact": "has-compose", "value": "docker-compose", "manifest": "compose file" }
5
+ ]
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "id": "docker",
3
+ "dependabot": [
4
+ { "order": 10, "fact": "has-dockerfile", "value": "docker", "manifest": "Dockerfile" }
5
+ ]
6
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "id": "dotnet",
3
+ "facts": ["has-nuget"],
4
+ "tool": "dotnet",
5
+ "manifestOrder": 3,
6
+ "gates": {
7
+ "preCommit": ["format"],
8
+ "prePush": ["test", "typecheck"],
9
+ "commands": {
10
+ "format": "dotnet format --verify-no-changes",
11
+ "test": "dotnet test",
12
+ "typecheck": "dotnet build -warnaserror"
13
+ }
14
+ },
15
+ "card": {
16
+ "build": { "order": 3, "condition": { "fact": "has-nuget" }, "command": "dotnet build" },
17
+ "focused": [
18
+ { "order": 8, "kind": ".NET", "condition": { "fact": "has-nuget" }, "template": "dotnet test --filter {test}" }
19
+ ]
20
+ },
21
+ "dependabot": [
22
+ { "order": 15, "fact": "has-nuget", "value": "nuget", "manifest": "*.csproj" }
23
+ ],
24
+ "languages": [{ "lang": "csharp", "exts": [".cs"] }]
25
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "id": "elixir",
3
+ "facts": ["has-mix"],
4
+ "tool": "mix",
5
+ "manifestOrder": 8,
6
+ "gates": {
7
+ "preCommit": ["format"],
8
+ "prePush": ["test"],
9
+ "commands": {
10
+ "format": "mix format --check-formatted",
11
+ "test": "mix test"
12
+ }
13
+ },
14
+ "card": {
15
+ "build": { "order": 5, "condition": { "fact": "has-mix" }, "command": "mix compile" },
16
+ "focused": [
17
+ { "order": 13, "kind": "Elixir", "condition": { "fact": "has-mix" }, "template": "mix test {file}" }
18
+ ]
19
+ },
20
+ "dependabot": [
21
+ { "order": 18, "fact": "has-mix", "value": "mix", "manifest": "mix.exs" }
22
+ ]
23
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "id": "gitsubmodule",
3
+ "dependabot": [
4
+ { "order": 14, "fact": "has-submodules", "value": "gitsubmodule", "manifest": ".gitmodules" }
5
+ ]
6
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "id": "go",
3
+ "facts": ["has-go-mod"],
4
+ "tool": "go",
5
+ "manifestOrder": 2,
6
+ "gates": {
7
+ "preCommit": ["format", "lint"],
8
+ "prePush": ["test"],
9
+ "commands": {
10
+ "format": "test -z \"$(gofmt -l .)\"",
11
+ "lint": "go vet ./...",
12
+ "test": "go test ./..."
13
+ }
14
+ },
15
+ "card": {
16
+ "build": { "order": 2, "condition": { "fact": "has-go-mod" }, "command": "go build ./..." },
17
+ "focused": [
18
+ { "order": 4, "kind": "Go package", "condition": { "fact": "has-go-mod" }, "template": "go test ./{pkg} -run {Test}" }
19
+ ]
20
+ },
21
+ "dependabot": [
22
+ { "order": 5, "fact": "has-go-mod", "value": "gomod", "manifest": "go.mod" }
23
+ ],
24
+ "languages": [{ "lang": "go", "exts": [".go"] }]
25
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "id": "gradle",
3
+ "card": {
4
+ "build": { "order": 6, "condition": { "stack": "gradle" }, "command": "./gradlew build" },
5
+ "focused": [
6
+ { "order": 1, "kind": "Android", "condition": { "fact": "has-android-manifest" }, "template": "./gradlew :{module}:testDebugUnitTest --tests {class}" },
7
+ { "order": 2, "kind": "JVM", "condition": { "stack": "gradle" }, "template": "./gradlew test --tests {class}" }
8
+ ]
9
+ },
10
+ "dependabot": [
11
+ { "order": 13, "stack": "gradle", "value": "gradle", "manifest": "build.gradle" }
12
+ ]
13
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "id": "maven",
3
+ "facts": ["has-maven-pom"],
4
+ "tool": "mvnw",
5
+ "file": true,
6
+ "manifestOrder": 9,
7
+ "gates": {
8
+ "prePush": ["test"],
9
+ "commands": { "test": "./mvnw -q verify" }
10
+ },
11
+ "card": {
12
+ "focused": [
13
+ { "order": 3, "kind": "Maven", "condition": { "fact": "has-maven-pom" }, "template": "./mvnw test -Dtest={class}" }
14
+ ]
15
+ },
16
+ "dependabot": [
17
+ { "order": 9, "fact": "has-maven-pom", "value": "maven", "manifest": "pom.xml" }
18
+ ],
19
+ "languages": [
20
+ { "lang": "java", "exts": [".java"], "testConventions": { "base": ["Tests?\\.(?:java|kt)$"] } },
21
+ { "lang": "kotlin", "exts": [".kt", ".kts"], "testConventions": { "base": ["Tests?\\.(?:java|kt)$"] } }
22
+ ]
23
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "id": "node",
3
+ "card": {
4
+ "focused": [
5
+ {
6
+ "order": 14,
7
+ "kind": "Node",
8
+ "condition": { "stack": "node" },
9
+ "template": {
10
+ "static": "node --test {file}",
11
+ "ifScriptMatches": "vitest|jest",
12
+ "dynamic": "{runner} test -- {file}"
13
+ }
14
+ }
15
+ ]
16
+ },
17
+ "dependabot": [
18
+ { "order": 1, "stack": "node", "value": "npm", "manifest": "package.json" },
19
+ { "order": 2, "stack": "typescript", "value": "npm", "manifest": "package.json" }
20
+ ],
21
+ "languages": [
22
+ { "lang": "javascript", "exts": [".js", ".mjs", ".cjs", ".jsx", ".ts", ".tsx"], "canonical": [".js", ".mjs", ".cjs", ".jsx"] },
23
+ { "lang": "typescript", "exts": [".ts", ".tsx", ".js", ".mjs", ".jsx"], "canonical": [".ts", ".tsx"] }
24
+ ]
25
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "id": "python",
3
+ "card": {
4
+ "focused": [
5
+ { "order": 6, "kind": "Python", "condition": { "fact": "has-uv-lock" }, "template": "uv run -m pytest {file}::{test}" },
6
+ { "order": 7, "kind": "Python", "condition": { "facts": ["has-pyproject", "has-requirements"] }, "template": "pytest {file}::{test}" }
7
+ ],
8
+ "fallback": [
9
+ { "order": 1, "condition": { "fact": "has-uv-lock" }, "commands": { "test": "uv run -m pytest" } },
10
+ { "order": 2, "condition": { "facts": ["has-pyproject", "has-requirements"] }, "commands": { "test": "pytest" } }
11
+ ]
12
+ },
13
+ "dependabot": [
14
+ { "order": 3, "fact": "has-pyproject", "value": "pip", "manifest": "pyproject.toml" },
15
+ { "order": 4, "fact": "has-requirements", "value": "pip", "manifest": "requirements.txt" }
16
+ ],
17
+ "languages": [
18
+ { "lang": "python", "exts": [".py"], "testConventions": { "base": ["^test_[^/]+\\.py$"] } }
19
+ ]
20
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "id": "ruby",
3
+ "facts": ["has-gemfile"],
4
+ "tool": "bundle",
5
+ "manifestOrder": 6,
6
+ "gates": {
7
+ "preCommit": ["format"],
8
+ "prePush": ["test"],
9
+ "commands": {
10
+ "format": "bundle exec rubocop",
11
+ "test": "bundle exec rspec"
12
+ }
13
+ },
14
+ "card": {
15
+ "focused": [
16
+ { "order": 11, "kind": "Ruby", "condition": { "fact": "has-gemfile" }, "template": "bundle exec rspec {file}" }
17
+ ]
18
+ },
19
+ "dependabot": [
20
+ { "order": 7, "fact": "has-gemfile", "value": "bundler", "manifest": "Gemfile" }
21
+ ],
22
+ "languages": [{ "lang": "ruby", "exts": [".rb"] }]
23
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "id": "swift",
3
+ "facts": ["has-swift-package"],
4
+ "tool": "swift",
5
+ "manifestOrder": 4,
6
+ "gates": {
7
+ "preCommit": ["format"],
8
+ "prePush": ["test", "typecheck"],
9
+ "commands": {
10
+ "format": "swift format lint -r .",
11
+ "test": "swift test",
12
+ "typecheck": "swift build"
13
+ }
14
+ },
15
+ "card": {
16
+ "build": { "order": 4, "condition": { "fact": "has-swift-package" }, "command": "swift build" },
17
+ "focused": [
18
+ { "order": 9, "kind": "Swift", "condition": { "fact": "has-swift-package" }, "template": "swift test --filter {test}" }
19
+ ]
20
+ },
21
+ "dependabot": [
22
+ { "order": 17, "fact": "has-swift-package", "value": "swift", "manifest": "Package.swift" }
23
+ ],
24
+ "languages": [{ "lang": "swift", "exts": [".swift"] }]
25
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "id": "terraform",
3
+ "dependabot": [
4
+ { "order": 12, "fact": "has-terraform", "value": "terraform", "manifest": "*.tf" }
5
+ ]
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "id": "uv",
3
+ "dependabot": [
4
+ { "order": 19, "fact": "has-uv-lock", "value": "uv", "manifest": "uv.lock" }
5
+ ]
6
+ }