simt-dev 0.6.3 → 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.
- package/CHANGELOG.md +35 -0
- package/README.md +2 -2
- package/agents/simt-agent.md +84 -0
- package/docs/decisions/ADR-009-skill-layout.md +1 -1
- package/docs/decisions/ADR-086-the-stable-prefix-comes-first-and-the-lock-records-both-digests.md +1 -1
- package/docs/decisions/ADR-106-the-harness-hook-runs-through-a-repo-shim.md +46 -0
- package/docs/decisions/ADR-107-an-install-can-name-one-harness-and-the-skipped-ones-are-named.md +51 -0
- package/docs/decisions/ADR-108-an-adoption-builds-the-index-and-a-command-refreshes-it-on-read.md +59 -0
- 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
- 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
- package/docs/decisions/ADR-111-the-tool-ships-one-agent-rendered-per-harness-and-installs-it-with-init-and-a-command.md +75 -0
- package/docs/decisions/ADR-112-the-registry-is-what-simt-dev-ships-and-a-repo-owns-its-own-skills.md +81 -0
- package/docs/decisions/ADR-113-a-pre-push-that-carries-only-documentation-skips-the-test-gate-and-says-so.md +87 -0
- 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
- 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
- package/docs/decisions/ADR-116-rust-support-reaches-the-example-path-the-indent-and-the-bench-runner.md +100 -0
- package/docs/decisions/README.md +9 -0
- package/package.json +4 -2
- package/rules/core.md +2 -2
- package/rules/mdc/test-gate.mdc +2 -0
- package/rules/stacks/android.md +3 -3
- package/rules/stacks/connectiq.md +2 -2
- package/rules/stacks/dart.md +1 -1
- package/rules/stacks/deno.md +1 -1
- package/rules/stacks/dotnet.md +1 -1
- package/rules/stacks/elixir.md +1 -1
- package/rules/stacks/go.md +1 -1
- package/rules/stacks/jvm.md +3 -2
- package/rules/stacks/node.md +2 -2
- package/rules/stacks/python.md +7 -6
- package/rules/stacks/ruby.md +1 -1
- package/rules/stacks/rust.md +1 -1
- package/rules/stacks/swift.md +1 -1
- package/rules/stacks/typescript.md +2 -2
- package/rules/stacks/uv.md +3 -2
- package/skills/simt-commands/SKILL.md +70 -0
- package/src/advice/advice.mjs +7 -3
- package/src/agenthooks/decide.mjs +141 -9
- package/src/agenthooks/opencode.mjs +72 -0
- package/src/agenthooks/prompt.mjs +6 -2
- package/src/agenthooks/render.mjs +186 -19
- package/src/agenthooks/shim.mjs +55 -0
- package/src/agents/render.mjs +277 -0
- package/src/argv.mjs +8 -0
- package/src/bench/ablate.mjs +31 -14
- package/src/bench/mine.mjs +48 -1
- package/src/bench/probes.mjs +5 -6
- package/src/bench/report.mjs +243 -80
- package/src/bench/run.mjs +12 -11
- package/src/bench/variants.mjs +112 -40
- package/src/brief/assemble.mjs +6 -0
- package/src/ci/workflow.mjs +16 -7
- package/src/commands/agent.mjs +206 -0
- package/src/commands/agenthook.mjs +109 -15
- package/src/commands/bench.mjs +2 -1
- package/src/commands/compare.mjs +8 -7
- package/src/commands/decide.mjs +6 -5
- package/src/commands/evidence.mjs +13 -3
- package/src/commands/gate.mjs +85 -19
- package/src/commands/graph.mjs +18 -5
- package/src/commands/help.mjs +35 -14
- package/src/commands/hooks.mjs +68 -25
- package/src/commands/impact.mjs +4 -2
- package/src/commands/index.mjs +17 -4
- package/src/commands/init.mjs +169 -37
- package/src/commands/lease.mjs +203 -35
- package/src/commands/lessons.mjs +65 -9
- package/src/commands/map.mjs +33 -18
- package/src/commands/plugin.mjs +1 -1
- package/src/commands/repoindex.mjs +1 -0
- package/src/commands/rules.mjs +92 -18
- package/src/commands/skills.mjs +60 -25
- package/src/commands/skillscompile.mjs +8 -7
- package/src/commands/skillsinstall.mjs +12 -0
- package/src/commands/slice.mjs +377 -18
- package/src/commands/sync.mjs +37 -22
- package/src/commands/tests.mjs +5 -2
- package/src/commands/undo.mjs +24 -16
- package/src/commands/upgrade.mjs +33 -1
- package/src/commands/why.mjs +10 -4
- package/src/compare/hooksstate.mjs +23 -1
- package/src/compare/learn.mjs +82 -57
- package/src/compare/parallel.mjs +50 -0
- package/src/compare/row.mjs +26 -1
- package/src/completions.mjs +5 -0
- package/src/decisions/dir.mjs +84 -0
- package/src/decisions/index.mjs +109 -11
- package/src/distill/distill.mjs +3 -1
- package/src/ecosystem/node-runner.mjs +144 -0
- package/src/explain.mjs +18 -6
- package/src/graph/load.mjs +38 -19
- package/src/hooks/capability.mjs +14 -1
- package/src/hooks/dispatcher.mjs +335 -13
- package/src/hooks/resolve.mjs +9 -3
- package/src/hooks/shim.mjs +12 -7
- package/src/identity.mjs +67 -14
- package/src/index/extract.mjs +192 -11
- package/src/index/persist.mjs +307 -47
- package/src/index/store.mjs +263 -60
- package/src/index/tests.mjs +6 -35
- package/src/journal.mjs +296 -77
- package/src/kernel/card.mjs +37 -12
- package/src/kernel/checks.mjs +44 -9
- package/src/kernel/variants.mjs +27 -16
- package/src/lease.mjs +97 -15
- package/src/ledger/index.mjs +91 -25
- package/src/mcp/resources.mjs +5 -3
- package/src/mcp/server.mjs +81 -22
- package/src/mcp/tools.mjs +45 -9
- package/src/memory/decide.mjs +98 -43
- package/src/memory/lessons.mjs +168 -33
- package/src/name.mjs +9 -0
- package/src/plan/index.mjs +31 -8
- package/src/plan/template.mjs +27 -5
- package/src/plugins.mjs +6 -2
- package/src/prbody.mjs +7 -5
- package/src/projection/baselines.mjs +1 -1
- package/src/projection/disclosure.mjs +2 -2
- package/src/projection/loaders.mjs +41 -1
- package/src/projection/lock.mjs +21 -14
- package/src/projection/manifest.mjs +69 -19
- package/src/projection/markers.mjs +43 -3
- package/src/projection/mdc.mjs +34 -7
- package/src/projection/project.mjs +162 -18
- package/src/projection/rules.mjs +54 -3
- package/src/projection/surfaces.mjs +9 -1
- package/src/repo-root.mjs +69 -0
- package/src/resolve/body.mjs +4 -0
- package/src/resolve/fingerprint.mjs +5 -0
- package/src/resolve/local.mjs +58 -0
- package/src/review/critic.mjs +4 -2
- package/src/secrets.mjs +165 -22
- package/src/state.mjs +76 -0
- package/src/status.mjs +9 -4
- package/src/toolwords.mjs +5 -0
- package/src/tsconfig.mjs +39 -99
- 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.
|
package/docs/decisions/README.md
CHANGED
|
@@ -112,6 +112,15 @@ record's status line — never by editing the ruling in place.
|
|
|
112
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
113
|
| [103](ADR-103-co-projected-duplicates-merge-down.md) | duplicates stand alone, fingerprints generalize, references spell out | **accepted** · 2026-10-28 |
|
|
114
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 |
|
|
115
124
|
|
|
116
125
|
ADR-001 to ADR-010 resolve the contradictions found across the 16 repos in the 2026-10-05
|
|
117
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.
|
|
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
|
|
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
|
-
`
|
|
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)
|
package/rules/mdc/test-gate.mdc
CHANGED
|
@@ -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
|
package/rules/stacks/android.md
CHANGED
|
@@ -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:
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
|
@@ -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
|
-
(`
|
|
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
|
|
package/rules/stacks/dart.md
CHANGED
|
@@ -8,7 +8,7 @@ simt-scope: ["pubspec.yaml", "**/*.dart"]
|
|
|
8
8
|
|
|
9
9
|
# Stack profile: Dart
|
|
10
10
|
|
|
11
|
-
Applies to:
|
|
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
|
|
package/rules/stacks/deno.md
CHANGED
package/rules/stacks/dotnet.md
CHANGED
package/rules/stacks/elixir.md
CHANGED
package/rules/stacks/go.md
CHANGED
package/rules/stacks/jvm.md
CHANGED
|
@@ -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`, `
|
|
16
|
-
rules live in the `android` profile
|
|
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
|
|
package/rules/stacks/node.md
CHANGED
|
@@ -42,6 +42,6 @@ Applies to: `node`, `node-web`, `typescript` when no more specific profile match
|
|
|
42
42
|
## Tests
|
|
43
43
|
|
|
44
44
|
- `vitest`, `jest` or `node --test` — one runner per repo, declared, not inherited.
|
|
45
|
-
- Focused selection is the default invocation. A full suite runs against the triggers in
|
|
46
|
-
`
|
|
45
|
+
- Focused selection is the default invocation. A full suite runs against the triggers in the
|
|
46
|
+
repository's manifest (`{manifest}`) (ADR-003).
|
|
47
47
|
- No snapshot-only acceptance: a snapshot proves the code did not change, not that it is right.
|
package/rules/stacks/python.md
CHANGED
|
@@ -8,14 +8,15 @@ simt-scope: ["pyproject.toml", "requirements.txt", "setup.py", "*.py"]
|
|
|
8
8
|
|
|
9
9
|
# Stack profile: Python
|
|
10
10
|
|
|
11
|
-
Applies to:
|
|
12
|
-
|
|
11
|
+
Applies to: Python — `pyproject.toml`, `requirements.txt` or `setup.py`. The
|
|
12
|
+
environment rules — how to invoke tools, and which lockfile owns them — are in the
|
|
13
|
+
`uv` profile (`simt rules show uv`), which selects on a committed `uv.lock`.
|
|
13
14
|
|
|
14
15
|
## Invocation
|
|
15
16
|
|
|
16
17
|
- Run through the repository's own runner: `uv run` where a `uv.lock` is committed,
|
|
17
18
|
`poetry run` where a `poetry.lock` is, and the repository's documented wrapper
|
|
18
|
-
otherwise. See
|
|
19
|
+
otherwise. See `simt rules show uv` for the form that must not be used.
|
|
19
20
|
- Never invoke a tool from whatever environment the shell happens to have. It will
|
|
20
21
|
usually be a different copy of the package than the one under test, and the failure
|
|
21
22
|
it produces looks like a bug in the code.
|
|
@@ -30,9 +31,9 @@ owns them — are in the `uv` profile, which selects on `has-uv-lock`.
|
|
|
30
31
|
|
|
31
32
|
## Tests
|
|
32
33
|
|
|
33
|
-
- `fail_under` is set per repo in `
|
|
34
|
-
100 %, treat it as a behavioural gate: an uncovered path is
|
|
35
|
-
or dead code, and both are defects.
|
|
34
|
+
- `fail_under` is set per repo in the repository's manifest (`{manifest}`) (ADR-006).
|
|
35
|
+
Where a repo requires 100 %, treat it as a behavioural gate: an uncovered path is
|
|
36
|
+
either a missing test or dead code, and both are defects.
|
|
36
37
|
- No `pytest.skip` without a pre-existing written justification.
|
|
37
38
|
- Test the narrowest layer that proves the behaviour; reach for an integration test
|
|
38
39
|
only when the behaviour is genuinely cross-system.
|
package/rules/stacks/ruby.md
CHANGED
package/rules/stacks/rust.md
CHANGED
package/rules/stacks/swift.md
CHANGED
|
@@ -14,8 +14,8 @@ simt-fallback: true
|
|
|
14
14
|
|
|
15
15
|
# Stack profile: TypeScript
|
|
16
16
|
|
|
17
|
-
Applies to: `
|
|
18
|
-
profile
|
|
17
|
+
Applies to: `tsconfig.json` is committed with a TypeScript dependency. A repository
|
|
18
|
+
with no more specific profile receives the `node` rules alongside it.
|
|
19
19
|
|
|
20
20
|
## Types
|
|
21
21
|
|
package/rules/stacks/uv.md
CHANGED
|
@@ -11,8 +11,9 @@ simt-scope: ["uv.lock", "pyproject.toml"]
|
|
|
11
11
|
|
|
12
12
|
# Stack profile: uv
|
|
13
13
|
|
|
14
|
-
Applies to:
|
|
15
|
-
|
|
14
|
+
Applies to: a committed `uv.lock`, here or in a workspace package. The language rules
|
|
15
|
+
are in the `python` profile (`simt rules show python`), which every Python repository
|
|
16
|
+
gets.
|
|
16
17
|
|
|
17
18
|
## Invocation
|
|
18
19
|
|