simt-dev 0.6.2 → 0.7.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 (170) hide show
  1. package/CHANGELOG.md +83 -1
  2. package/README.md +46 -17
  3. package/agents/simt-agent.md +84 -0
  4. package/bin/simt.mjs +19 -6
  5. package/docs/decisions/ADR-009-skill-layout.md +1 -1
  6. package/docs/decisions/ADR-086-the-stable-prefix-comes-first-and-the-lock-records-both-digests.md +1 -1
  7. package/docs/decisions/ADR-100-a-staged-diff-is-the-last-gate-before-history-and-it-runs-inside-the-tool.md +94 -0
  8. package/docs/decisions/ADR-101-a-file-lease-belongs-to-one-session-and-the-pre-edit-hook-refuses-another.md +95 -0
  9. package/docs/decisions/ADR-102-the-two-queued-review-rows-are-closed-by-ruling.md +49 -0
  10. package/docs/decisions/ADR-103-co-projected-duplicates-merge-down.md +74 -0
  11. package/docs/decisions/ADR-104-the-pre-tool-use-policy-reads-the-command-the-way-a-shell-reads-it.md +76 -0
  12. package/docs/decisions/ADR-105-the-json-ceiling-shrinks-arrays-and-never-cuts-bytes.md +44 -0
  13. package/docs/decisions/ADR-106-the-harness-hook-runs-through-a-repo-shim.md +46 -0
  14. package/docs/decisions/ADR-107-an-install-can-name-one-harness-and-the-skipped-ones-are-named.md +51 -0
  15. package/docs/decisions/ADR-108-an-adoption-builds-the-index-and-a-command-refreshes-it-on-read.md +59 -0
  16. 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
  17. 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
  18. package/docs/decisions/ADR-111-the-tool-ships-one-agent-rendered-per-harness-and-installs-it-with-init-and-a-command.md +75 -0
  19. package/docs/decisions/ADR-112-the-registry-is-what-simt-dev-ships-and-a-repo-owns-its-own-skills.md +81 -0
  20. package/docs/decisions/ADR-113-a-pre-push-that-carries-only-documentation-skips-the-test-gate-and-says-so.md +87 -0
  21. 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
  22. 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
  23. package/docs/decisions/ADR-116-rust-support-reaches-the-example-path-the-indent-and-the-bench-runner.md +100 -0
  24. package/docs/decisions/README.md +53 -0
  25. package/package.json +4 -2
  26. package/rules/core.md +2 -2
  27. package/rules/mdc/preview-postflight.mdc +5 -6
  28. package/rules/mdc/test-gate.mdc +2 -0
  29. package/rules/stacks/android.md +10 -11
  30. package/rules/stacks/cloudflare-workers.md +1 -1
  31. package/rules/stacks/connectiq.md +2 -2
  32. package/rules/stacks/container.md +2 -0
  33. package/rules/stacks/dart.md +1 -1
  34. package/rules/stacks/deno.md +1 -1
  35. package/rules/stacks/dotnet.md +1 -1
  36. package/rules/stacks/elixir.md +1 -1
  37. package/rules/stacks/go.md +1 -1
  38. package/rules/stacks/jvm.md +4 -3
  39. package/rules/stacks/node.md +2 -2
  40. package/rules/stacks/python.md +7 -6
  41. package/rules/stacks/react-native.md +3 -5
  42. package/rules/stacks/react-web.md +9 -9
  43. package/rules/stacks/ruby.md +1 -1
  44. package/rules/stacks/rust.md +1 -1
  45. package/rules/stacks/swift.md +1 -1
  46. package/rules/stacks/typescript.md +2 -2
  47. package/rules/stacks/uv.md +3 -2
  48. package/skills/simt-commands/SKILL.md +70 -0
  49. package/src/advice/advice.mjs +7 -3
  50. package/src/agenthooks/decide.mjs +238 -15
  51. package/src/agenthooks/opencode.mjs +72 -0
  52. package/src/agenthooks/policy.mjs +81 -5
  53. package/src/agenthooks/prompt.mjs +6 -2
  54. package/src/agenthooks/render.mjs +186 -19
  55. package/src/agenthooks/shell.mjs +523 -0
  56. package/src/agenthooks/shim.mjs +55 -0
  57. package/src/agents/render.mjs +277 -0
  58. package/src/argv.mjs +62 -9
  59. package/src/bench/ablate.mjs +31 -14
  60. package/src/bench/mine.mjs +48 -1
  61. package/src/bench/probes.mjs +5 -6
  62. package/src/bench/report.mjs +243 -80
  63. package/src/bench/run.mjs +12 -11
  64. package/src/bench/variants.mjs +112 -40
  65. package/src/brief/assemble.mjs +6 -0
  66. package/src/ci/workflow.mjs +16 -7
  67. package/src/commands/agent.mjs +206 -0
  68. package/src/commands/agenthook.mjs +133 -17
  69. package/src/commands/bench.mjs +2 -1
  70. package/src/commands/ci.mjs +28 -3
  71. package/src/commands/compare.mjs +8 -7
  72. package/src/commands/completions.mjs +27 -0
  73. package/src/commands/decide.mjs +20 -6
  74. package/src/commands/doctor.mjs +367 -0
  75. package/src/commands/evidence.mjs +13 -3
  76. package/src/commands/explain.mjs +52 -0
  77. package/src/commands/gate.mjs +85 -19
  78. package/src/commands/graph.mjs +18 -5
  79. package/src/commands/help.mjs +96 -26
  80. package/src/commands/hooks.mjs +258 -67
  81. package/src/commands/ignore.mjs +152 -0
  82. package/src/commands/impact.mjs +4 -2
  83. package/src/commands/index.mjs +69 -102
  84. package/src/commands/init.mjs +239 -37
  85. package/src/commands/lease.mjs +304 -0
  86. package/src/commands/lessons.mjs +77 -9
  87. package/src/commands/map.mjs +33 -18
  88. package/src/commands/mcp.mjs +2 -2
  89. package/src/commands/pack.mjs +9 -0
  90. package/src/commands/plan.mjs +13 -1
  91. package/src/commands/plugin.mjs +17 -2
  92. package/src/commands/prbody.mjs +45 -0
  93. package/src/commands/repoindex.mjs +4 -0
  94. package/src/commands/rules.mjs +219 -105
  95. package/src/commands/shared.mjs +36 -5
  96. package/src/commands/skills.mjs +329 -206
  97. package/src/commands/skillscompile.mjs +8 -7
  98. package/src/commands/skillsinstall.mjs +112 -30
  99. package/src/commands/slice.mjs +712 -0
  100. package/src/commands/status.mjs +62 -0
  101. package/src/commands/sync.mjs +146 -36
  102. package/src/commands/tests.mjs +5 -2
  103. package/src/commands/undo.mjs +114 -0
  104. package/src/commands/upgrade.mjs +103 -9
  105. package/src/commands/why.mjs +10 -4
  106. package/src/compare/hooksstate.mjs +23 -1
  107. package/src/compare/learn.mjs +82 -57
  108. package/src/compare/parallel.mjs +50 -0
  109. package/src/compare/row.mjs +26 -1
  110. package/src/completions.mjs +196 -0
  111. package/src/decisions/dir.mjs +84 -0
  112. package/src/decisions/index.mjs +109 -11
  113. package/src/diff.mjs +254 -0
  114. package/src/distill/distill.mjs +3 -1
  115. package/src/ecosystem/node-runner.mjs +144 -0
  116. package/src/explain.mjs +270 -0
  117. package/src/federation/config.mjs +44 -0
  118. package/src/graph/load.mjs +52 -24
  119. package/src/hooks/capability.mjs +14 -1
  120. package/src/hooks/dispatcher.mjs +441 -9
  121. package/src/hooks/gates.mjs +14 -3
  122. package/src/hooks/resolve.mjs +23 -3
  123. package/src/hooks/shim.mjs +12 -7
  124. package/src/identity.mjs +67 -14
  125. package/src/ignore.mjs +133 -0
  126. package/src/index/extract.mjs +192 -11
  127. package/src/index/persist.mjs +372 -55
  128. package/src/index/store.mjs +263 -60
  129. package/src/index/tests.mjs +6 -35
  130. package/src/journal.mjs +545 -0
  131. package/src/kernel/card.mjs +37 -12
  132. package/src/kernel/checks.mjs +44 -9
  133. package/src/kernel/variants.mjs +27 -16
  134. package/src/lease.mjs +195 -0
  135. package/src/ledger/index.mjs +91 -25
  136. package/src/mcp/resources.mjs +5 -3
  137. package/src/mcp/server.mjs +86 -13
  138. package/src/mcp/tools.mjs +340 -264
  139. package/src/memory/decide.mjs +98 -38
  140. package/src/memory/lessons.mjs +180 -33
  141. package/src/name.mjs +9 -0
  142. package/src/output/index.mjs +82 -12
  143. package/src/package-version.mjs +49 -1
  144. package/src/packs/manage.mjs +13 -4
  145. package/src/plan/index.mjs +37 -8
  146. package/src/plan/template.mjs +27 -5
  147. package/src/plugins.mjs +33 -8
  148. package/src/prbody.mjs +253 -0
  149. package/src/projection/baselines.mjs +1 -1
  150. package/src/projection/disclosure.mjs +2 -2
  151. package/src/projection/loaders.mjs +41 -1
  152. package/src/projection/lock.mjs +21 -14
  153. package/src/projection/manifest.mjs +82 -21
  154. package/src/projection/markers.mjs +43 -3
  155. package/src/projection/mdc.mjs +34 -7
  156. package/src/projection/project.mjs +162 -18
  157. package/src/projection/rules.mjs +54 -3
  158. package/src/projection/shared.mjs +4 -1
  159. package/src/projection/surfaces.mjs +9 -1
  160. package/src/repo-root.mjs +69 -0
  161. package/src/resolve/body.mjs +4 -0
  162. package/src/resolve/fingerprint.mjs +5 -0
  163. package/src/resolve/local.mjs +58 -0
  164. package/src/review/critic.mjs +4 -2
  165. package/src/secrets.mjs +374 -0
  166. package/src/state.mjs +76 -0
  167. package/src/status.mjs +157 -0
  168. package/src/toolwords.mjs +5 -0
  169. package/src/tsconfig.mjs +39 -99
  170. package/templates/plan/tracker.json +1 -1
@@ -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.
@@ -68,6 +68,59 @@ record's status line — never by editing the ruling in place.
68
68
  | [058](ADR-058-one-tool-three-channels-and-the-order-is-forced.md) | One tool, three channels, and the order is forced | accepted |
69
69
  | [059](ADR-059-the-version-a-repository-runs-is-the-one-it-pinned.md) | The version a repository runs is the one it pinned, and the gate refuses otherwise | accepted |
70
70
  | [060](ADR-060-a-repository-with-nothing-to-run-is-adopted-and-says-so.md) | A repository with nothing to run is adopted and says so; a gate is verified where it runs | accepted |
71
+ | [061](ADR-061-the-skill-registry-is-workspace-data.md) | The skill registry is workspace data, and the package ships none of it | **accepted** · 2026-10-12 · filed as S61 |
72
+ | [062](ADR-062-a-fresh-adoption-seeds-the-gates-it-can-run.md) | A fresh adoption seeds the canonical gates it can run, and never more | **accepted** · 2026-10-13 · filed as S40 |
73
+ | [063](ADR-063-the-adoption-escape-hatch-is-opt-in.md) | The adoption escape hatch is opt-in, and the default writes in place | **accepted** · 2026-10-13 · filed after a consumer asked why every adoption branched |
74
+ | [064](ADR-064-the-context-meter-counts-what-the-tool-wrote.md) | The context meter counts what the tool wrote, and says what it did not | **accepted** · 2026-10-13 · filed after review 0002 S38 |
75
+ | [065](ADR-065-a-pack-is-one-shared-rule-set-pinned-by-digest.md) | A pack is one shared rule set, vendored into the repository and pinned by digest | **accepted** · 2026-10-13 · filed as S39 |
76
+ | [066](ADR-066-a-monorepo-projects-once-per-package.md) | A monorepo projects once per package | **accepted** · 2026-10-13 · filed as S41 |
77
+ | [067](ADR-067-which-harnesses-read-agents-md-is-a-column.md) | Which harnesses read AGENTS.md is a column, and a row without a document gets none | **accepted** · 2026-10-13 · filed as S42 |
78
+ | [068](ADR-068-the-mcp-revision-list-cites-what-was-read.md) | The revision list is a claim about revisions that were read | **accepted** · 2026-10-13 · filed as S43 |
79
+ | [069](ADR-069-the-conformance-lint-checks-the-published-specification.md) | The conformance lint checks the published specification | **accepted** · 2026-10-13 · filed as S44 |
80
+ | [070](ADR-070-the-json-payload-carries-its-own-version.md) | The JSON payload carries its own version | **accepted** · 2026-10-13 · filed as S51 |
81
+ | [071](ADR-071-a-rule-that-can-be-narrowed-is-written-narrow.md) | A rule that can be narrowed is written narrow | **accepted** · 2026-10-14 · filed as S50 |
82
+ | [072](ADR-072-the-budget-is-a-declaration-beside-the-gates.md) | The budget is a declaration beside the gates, not a number inside the projection | **accepted** · 2026-10-14 · filed as S38 |
83
+ | [073](ADR-073-a-kernel-is-a-smaller-projection-the-repository-asks-for.md) | A kernel is a smaller projection the repository asks for, not one it is given | **accepted** · 2026-10-14 · filed as S38 (R07) · ruling 6 narrowed by ADR-084 |
84
+ | [074](ADR-074-a-rule-and-a-decision-are-readable-by-uri.md) | A rule and a decision are addressable, so they are readable by URI | **accepted** · 2026-10-14 · filed as S43 |
85
+ | [075](ADR-075-nothing-leaves-the-machine.md) | Nothing leaves the machine: no telemetry, local transcripts, loopback models by default | **accepted** · 2026-10-14 · filed as D7 (R06) |
86
+ | [076](ADR-076-a-change-to-what-agents-read-is-measured.md) | A change to what agents read is measured before it ships, or labelled unmeasured in its record | **accepted** · 2026-10-15 · filed as D1 (R01) |
87
+ | [077](ADR-077-the-command-card-leads-the-region-and-reads-committed-evidence.md) | The command card leads the region, and is derived from committed evidence only | **accepted** · 2026-10-16 · filed as R10 (plan 0003) · ruling 1 narrowed by ADR-086 |
88
+ | [078](ADR-078-a-line-the-repository-already-proves-does-not-ship.md) | A line the repository already proves does not ship, and the author says so | **accepted** · 2026-10-16 · filed as R09 (plan 0003) |
89
+ | [079](ADR-079-the-rules-are-enforced-at-the-tool-call-from-one-policy-table.md) | The rules are enforced at the tool call, from one policy table every harness renders | **accepted** · 2026-10-16 · filed as R15 (plan 0003) |
90
+ | [080](ADR-080-the-tools-own-names-are-not-repositories-and-neither-reader-decides-by-shape.md) | The tool's own names are not repositories, and neither reader decides by shape | **accepted** · 2026-10-16 · settles what `rules why` calls a repository |
91
+ | [081](ADR-081-a-tracker-is-read-through-the-repositorys-template.md) | A tracker is read through the repository's template | **accepted** · 2026-10-16 · rulings 1 and 2 superseded by ADR-082 |
92
+ | [082](ADR-082-the-tracker-override-is-a-manifest-key.md) | The tracker override is a manifest key, and init keeps what it did not write | **accepted** · 2026-10-16 · supersedes ADR-081 rulings 1 and 2 |
93
+ | [083](ADR-083-a-budget-is-met-by-cutting-the-kernel-and-hard-is-the-floor.md) | A budget is met by cutting the kernel, and the hard constraints are the floor | **accepted** · 2026-10-17 · filed as R08 (plan 0003) · ruling 7 narrowed by ADR-084 |
94
+ | [084](ADR-084-a-workspace-carries-its-repeated-rules-once.md) | A workspace carries its repeated rules once, and the region does not read the reader's home | **accepted** · 2026-10-17 · filed as R11 (plan 0003) |
95
+ | [085](ADR-085-the-region-ends-with-the-names-it-kept-out.md) | The region ends with the names of what it kept out | **accepted** · 2026-10-18 · filed as R12 (plan 0003) |
96
+ | [086](ADR-086-the-stable-prefix-comes-first-and-the-lock-records-both-digests.md) | The stable prefix comes first, and the lock records both digests | **accepted** · 2026-10-18 · filed as R13 (plan 0003) |
97
+ | [087](ADR-087-a-run-is-evidence-for-one-tree.md) | A run is evidence for one tree | **accepted** · 2026-10-18 · filed as R17 (plan 0003) |
98
+ | [088](ADR-088-a-marked-line-is-enforced-by-a-checker.md) | A marked line is enforced by a checker | **accepted** · 2026-10-18 · filed as R19 (plan 0003) · ruling 3 narrowed by ADR-090 |
99
+ | [089](ADR-089-the-local-index-is-a-cache-of-git-blobs.md) | The local index is a cache of git blobs | **accepted** · 2026-10-18 · filed as R20 (plan 0003) |
100
+ | [090](ADR-090-an-optional-adapter-is-off-until-named.md) | An optional adapter is off until a repository names it | **accepted** · 2026-10-18 · filed as R21 (plan 0003) |
101
+ | [091](ADR-091-the-map-ranks-files-a-task-mentions.md) | The map ranks the files a task mentions, and the files next to them | **accepted** · 2026-10-18 · filed as R22 (plan 0003) |
102
+ | [092](ADR-092-tests-are-imports-conventions-and-coverage.md) | A test is an import, a conventional neighbour, or a coverage record | **accepted** · 2026-10-18 · filed as R24 (plan 0003) |
103
+ | [093](ADR-093-why-names-what-governs-a-file.md) | why names what governs a file | **accepted** · 2026-10-18 · filed as R25 (plan 0003) |
104
+ | [094](ADR-094-a-change-runs-the-tests-that-import-it.md) | a change runs the tests that import it | **accepted** · 2026-10-18 · filed as R16 (plan 0003) |
105
+ | [095](ADR-095-gate-output-keeps-the-failures.md) | gate output keeps the failures | **accepted** · 2026-10-18 · filed as R18 (plan 0003) |
106
+ | [096](ADR-096-impact-names-who-a-change-reaches.md) | impact names who a change reaches | **accepted** · 2026-10-18 · filed as R23 (plan 0003) |
107
+ | [097](ADR-097-the-channels-publish-by-hand-and-the-version-rides-in-as-an-input.md) | the channels publish by hand, and the version rides in as an input | **accepted** · 2026-10-25 · ruling 3's Homebrew precondition is superseded by [ADR-098](ADR-098-the-fourth-channel-is-one-file-and-it-needs-no-toolchain.md), which also restates what each channel now waits on |
108
+ | [098](ADR-098-the-fourth-channel-is-one-file-and-it-needs-no-toolchain.md) | The fourth channel is one file, and it needs no toolchain | **accepted** · 2026-10-26 · supersedes ADR-058 ruling 5 · narrows ADR-058 ruling 7 and ADR-097 ruling 3 · supersedes ADR-059 ruling 3 · ruling 3's cross-host byte-identicality narrowed by [ADR-099](ADR-099-the-release-verifies-served-bytes-and-the-formula-mirrors-them.md) |
109
+ | [099](ADR-099-the-release-verifies-served-bytes-and-the-formula-mirrors-them.md) | the release verifies served bytes, and the formula mirrors them | **accepted** · 2026-10-27 · narrows ADR-098 ruling 3 |
110
+ | [100](ADR-100-a-staged-diff-is-the-last-gate-before-history-and-it-runs-inside-the-tool.md) | a staged diff is the last gate before history, and it runs inside the tool | **accepted** · 2026-10-27 |
111
+ | [101](ADR-101-a-file-lease-belongs-to-one-session-and-the-pre-edit-hook-refuses-another.md) | a file lease belongs to one session, and the pre-edit hook refuses another's edit | **accepted** · 2026-10-28 |
112
+ | [102](ADR-102-the-two-queued-review-rows-are-closed-by-ruling.md) | the two rows review 0002 left queued are closed by ruling, not by authoring | **accepted** · 2026-10-28 |
113
+ | [103](ADR-103-co-projected-duplicates-merge-down.md) | duplicates stand alone, fingerprints generalize, references spell out | **accepted** · 2026-10-28 |
114
+ | [104](ADR-104-the-pre-tool-use-policy-reads-the-command-the-way-a-shell-reads-it.md) | the pre-tool-use policy reads the command the way a shell reads it | **accepted** · 2026-10-28 |
115
+ | [105](ADR-105-the-json-ceiling-shrinks-arrays-and-never-cuts-bytes.md) | the JSON ceiling shrinks arrays and never cuts bytes | **accepted** · 2026-10-29 |
116
+ | [106](ADR-106-the-harness-hook-runs-through-a-repo-shim.md) | the harness hook runs through a repo shim, and `failClosed` only with it | **accepted** · 2026-10-29 |
117
+ | [107](ADR-107-an-install-can-name-one-harness-and-the-skipped-ones-are-named.md) | an install can be narrowed to one harness, and the skipped ones are named | **accepted** · 2026-10-30 |
118
+ | [108](ADR-108-an-adoption-builds-the-index-and-a-command-refreshes-it-on-read.md) | an adoption builds the index, and a command that reads it refreshes it on read | **accepted** · 2026-10-30 |
119
+ | [109](ADR-109-simt-dev-is-its-own-consumer-and-the-guards-that-kept-it-from-being-one-are-retired.md) | simt-dev is its own consumer, and the guards that kept it from being one are retired | **accepted** · 2026-10-30 |
120
+ | [110](ADR-110-the-lock-file-follows-the-manifest-name-and-the-old-name-is-read-until-the-next-write.md) | the lock is `simt.lock.json`, and the old name is read until the next write | **accepted** · 2026-10-30 |
121
+ | [111](ADR-111-the-tool-ships-one-agent-rendered-per-harness-and-installs-it-with-init-and-a-command.md) | the tool ships one agent, rendered per harness, and installs it with init and a command | **accepted** · 2026-10-30 |
122
+ | [112](ADR-112-the-registry-is-what-simt-dev-ships-and-a-repo-owns-its-own-skills.md) | the registry is what simt-dev ships, and a repo owns its own skills | **accepted** · 2026-10-31 · supersedes ADR-009 ruling 4 |
123
+ | [116](ADR-116-rust-support-reaches-the-example-path-the-indent-and-the-bench-runner.md) | Rust support reaches the example path, the indent and the bench runner | **accepted** · 2026-10-31 |
71
124
 
72
125
  ADR-001 to ADR-010 resolve the contradictions found across the 16 repos in the 2026-10-05
73
126
  audit; each record names the repos whose text it replaces. ADR-011 and ADR-012 are the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "simt-dev",
3
- "version": "0.6.2",
3
+ "version": "0.7.0",
4
4
  "description": "An intelligence amplifier for AI coding agents: rules and dual gates adopted by one command, plus local retrieval, task briefs, benches and repo memory — deterministic by default, with local-model adapters that propose and never select.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Said Elimam",
@@ -25,6 +25,8 @@
25
25
  "src",
26
26
  "rules",
27
27
  "templates",
28
+ "agents",
29
+ "skills",
28
30
  "docs/decisions",
29
31
  "CHANGELOG.md"
30
32
  ],
@@ -41,7 +43,7 @@
41
43
  "skills"
42
44
  ],
43
45
  "scripts": {
44
- "test": "node --test --test-concurrency=4 test/*.test.mjs",
46
+ "test": "node --test --test-concurrency=$(node -p 'os.cpus().length') test/*.test.mjs",
45
47
  "lint": "node bin/simt.mjs doctor"
46
48
  }
47
49
  }
package/rules/core.md CHANGED
@@ -45,8 +45,8 @@ commands belongs in this repository, not here.
45
45
  - Name the exact commands and test files. Partial pass is a blocker; `6/8 pass` is the
46
46
  canonical example of a blocked state. Never justify a skip without a pre-existing written
47
47
  reason. (ADR-006)
48
- - Evidence discipline is mandatory everywhere. A **numeric** bar is declared per repo in
49
- `simt-dev.json`, never assumed centrally. (ADR-006)
48
+ - Evidence discipline is mandatory everywhere. A **numeric** bar is declared per repo in the
49
+ repository's manifest (`{manifest}`), never assumed centrally. (ADR-006)
50
50
  - The pre-commit hook owns format and lint for staged files; do not re-run what it just ran.
51
51
  Manual runs are correct when the hook is absent, when diagnosing a failure, or for a check
52
52
  the hook does not cover. (ADR-004)
@@ -27,18 +27,17 @@ simt-substitutions:
27
27
  # neither, declares that. A worker that serves derivatives and calls no API has no
28
28
  # API base URL to set, so it declares only its own custom-domain binding.
29
29
  - key: previewApiBaseUrlNote
30
- default: "- Point the preview build at the preview API Worker: set `SOCIAL_API_BASE_URL` in the Cloudflare Workers Builds preview environment to the preview API Worker URL."
30
+ default: "- Point the preview build at the preview API: set the preview environment's API base URL to the preview API URL."
31
31
  describe: build-config instruction, when this repo actually has a preview API base URL to set
32
32
  - key: googleOnlyNote
33
- default: "- Production and public browser flows remain Google-only."
33
+ default: "- Production and public browser flows use the production identity only."
34
34
  describe: the identity policy this repo enforces in production
35
35
  ---
36
36
 
37
37
  # Preview-Only Authenticated Postflight
38
38
 
39
39
  Authenticated remote postflight and release checks use only {{previewWorkers}}, deployed
40
- with `AUTH_PROVIDER_POLICY=preview_postflight_password` and a secret-bound Firebase password
41
- identity (`POSTFLIGHT_PASSWORD_UID`).
40
+ with a preview-only auth provider policy and a secret-bound password identity.
42
41
 
43
42
  ## Requirements
44
43
 
@@ -52,8 +51,8 @@ identity (`POSTFLIGHT_PASSWORD_UID`).
52
51
  ## Forbidden
53
52
 
54
53
  - Authenticated postflight against production domains ({{productionDomains}}).
55
- - Using real Social Google accounts for feature slice verification.
56
- - Demanding, pasting, or storing production Google OAuth tokens in chat or tickets.
54
+ - Using real user accounts for feature slice verification.
55
+ - Demanding, pasting, or storing production OAuth tokens in chat or tickets.
57
56
 
58
57
  ## Allowed Without Account
59
58
 
@@ -10,6 +10,8 @@ simt-substitutions:
10
10
  describe: how this repo actually runs one named test file
11
11
  - key: exampleTestPath
12
12
  default: path/to/the.changed.test.ts
13
+ default-has-go-mod: path/to/the_changed_test.go
14
+ default-has-cargo-toml: tests/the_changed.rs
13
15
  describe: one real test file for this repo, used only as the named example
14
16
  - key: pathKinds
15
17
  default: feature/contract/route
@@ -12,9 +12,9 @@ simt-scope: ["AndroidManifest.xml", "**/build.gradle", "**/build.gradle.kts", "*
12
12
 
13
13
  # Stack profile: Android
14
14
 
15
- Applies to: `has-android-manifest` — some module has a
16
- `src/main/AndroidManifest.xml`. A JVM backend with no Android module does not get
17
- this profile; see `jvm`.
15
+ Applies to: Android modules — some module has a `src/main/AndroidManifest.xml`.
16
+ A JVM backend with no Android module does not get this profile; see `simt rules
17
+ show jvm`.
18
18
 
19
19
  ## Build
20
20
 
@@ -27,17 +27,17 @@ this profile; see `jvm`.
27
27
 
28
28
  ## Modules and layers
29
29
 
30
- - `:core:domain` depends on no other internal module. Enforce it; do not add an
30
+ - The core domain module depends on no other internal module. Enforce it; do not add an
31
31
  import that breaks the direction.
32
32
  - Layer suffixes: `*Repository`, `*ViewModel`, `*UiState`, `*Screen`, `*Service`.
33
- - Feature modules never import `androidx.compose.ui.window.Dialog`, M3 `AlertDialog`
34
- or M3 `ModalBottomSheet`. The raw `Dialog` window belongs to allowlisted `core:ui`
35
- surfaces, and `checkOverlayModalPolicy` fails `./gradlew check` when a feature
36
- bypasses it.
33
+ - Feature modules never present a raw `Dialog` window (`androidx.compose.ui.window.Dialog`,
34
+ M3 `AlertDialog`, M3 `ModalBottomSheet`) that bypasses the shared modal policy.
35
+ Modals go through the design system's dialog surfaces, and the repo's check fails
36
+ the build on a bypass.
37
37
 
38
38
  ## Accessibility
39
39
 
40
- - Every user-facing string is a resource, with a `cd_` content description where an
40
+ - Every user-facing string is a resource, with a content description where an
41
41
  icon or image carries meaning. Decorative images declare `contentDescription = null`
42
42
  explicitly.
43
43
  - Touch targets meet the platform minimum, and the minimum is asserted rather than
@@ -61,5 +61,4 @@ this profile; see `jvm`.
61
61
 
62
62
  - Unit tests for view models, repositories and mappers; instrumentation tests for UI.
63
63
  - `Dispatchers.setMain` in a `@BeforeTest`, restored in `@AfterTest`.
64
- - Assert behaviour, not implementation. Never weaken an assertion, an accessibility
65
- check or a fixture to match current behaviour.
64
+ - Assert behaviour, not implementation — including accessibility checks and fixtures. Never weaken one to match current behaviour.
@@ -41,7 +41,7 @@ Applies to: `cloudflare-workers` — a `wrangler.json`, `wrangler.jsonc` or
41
41
  - Validate at the edge with the schema layer. Authorization is server-authoritative; a
42
42
  client-side role, claim, cache entry or URL parameter grants nothing.
43
43
  - `Request`/`Response` are globals. Do not pass them through as values into domain code.
44
- - Caching defaults to `private, no-store`. Never cache token, account, block, reveal, admin
44
+ - Caching defaults to `private, no-store`. Never cache tokens, account data, admin
45
45
  decisions or authenticated errors.
46
46
  - Streaming is the default for large bodies; do not buffer without a stated reason.
47
47
 
@@ -25,8 +25,8 @@ Known traps to assume until verified otherwise:
25
25
 
26
26
  ## Memory
27
27
 
28
- - Memory budgets are hard limits, not targets, and they are the repo's declared gate
29
- (`simt-dev.json`). Zero allocation in `onUpdate`; no allocation in a draw callback.
28
+ - Memory budgets are hard limits, not targets, and they are the repo's declared gate in its
29
+ manifest (`{manifest}`). Zero allocation in `onUpdate`; no allocation in a draw callback.
30
30
  - A memory check that "currently exits 1 by design" is not a gate — it is noise that teaches
31
31
  everyone to ignore it. Declare the budget or drop the check.
32
32
 
@@ -7,6 +7,8 @@ simt-scope: ["Dockerfile", "*.dockerfile"]
7
7
  simt-fallback: false
8
8
  ---
9
9
 
10
+ # Stack profile: Containers
11
+
10
12
  ## Containers
11
13
 
12
14
  A Dockerfile is the build. If a rule here and the repo's own instructions disagree, the
@@ -8,7 +8,7 @@ simt-scope: ["pubspec.yaml", "**/*.dart"]
8
8
 
9
9
  # Stack profile: Dart
10
10
 
11
- Applies to: `has-pubspec` — a `pubspec.yaml`. A Flutter project is a Dart project
11
+ Applies to: a committed `pubspec.yaml`. A Flutter project is a Dart project
12
12
  with the `flutter` SDK dependency; the Flutter-specific rules live with the tools
13
13
  that build it, not here.
14
14
 
@@ -8,7 +8,7 @@ simt-scope: ["deno.json", "deno.jsonc", "**/*.ts"]
8
8
 
9
9
  # Stack profile: Deno
10
10
 
11
- Applies to: `has-deno-config` — a `deno.json` or `deno.jsonc`.
11
+ Applies to: a committed `deno.json` or `deno.jsonc`.
12
12
 
13
13
  ## Build and gates
14
14
 
@@ -8,7 +8,7 @@ simt-scope: ["*.csproj", "*.sln", "*.fsproj"]
8
8
 
9
9
  # Stack profile: .NET
10
10
 
11
- Applies to: `has-nuget` — a `.csproj` or solution.
11
+ Applies to: a committed `.csproj` or solution.
12
12
 
13
13
  ## Build and gates
14
14
 
@@ -8,7 +8,7 @@ simt-scope: ["mix.exs", "lib/**/*.ex"]
8
8
 
9
9
  # Stack profile: Elixir
10
10
 
11
- Applies to: `has-mix` — a `mix.exs`.
11
+ Applies to: a committed `mix.exs`.
12
12
 
13
13
  ## Build and gates
14
14
 
@@ -8,7 +8,7 @@ simt-scope: ["go.mod", "**/*.go"]
8
8
 
9
9
  # Stack profile: Go
10
10
 
11
- Applies to: `has-go-mod` — a module.
11
+ Applies to: Go modules — a committed `go.mod`.
12
12
 
13
13
  ## Build and gates
14
14
 
@@ -12,8 +12,9 @@ simt-scope: ["build.gradle", "build.gradle.kts", "pom.xml", "settings.gradle", "
12
12
 
13
13
  # Stack profile: JVM backend
14
14
 
15
- Applies to: `gradle`, `has-maven-pom`, `kotlin`, `java` — a JVM build. The Android
16
- rules live in the `android` profile, which selects on `has-android-manifest`.
15
+ Applies to: a JVM build — `gradle`, a committed `pom.xml`, Kotlin or Java. The
16
+ Android rules live in the `android` profile (`simt rules show android`), which
17
+ selects when an `AndroidManifest.xml` is present.
17
18
 
18
19
  ## Build
19
20
 
@@ -26,7 +27,7 @@ rules live in the `android` profile, which selects on `has-android-manifest`.
26
27
 
27
28
  ## Modules and layers
28
29
 
29
- - `:core:domain` depends on no other internal module. Enforce it; do not add an
30
+ - The core domain module depends on no other internal module. Enforce it; do not add an
30
31
  import that breaks the direction.
31
32
  - Layer suffixes: `*Repository`, `*Service`, `*Mapper`, `*Controller`. A class named
32
33
  `*Repository` that talks to HTTP is not a repository.