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,49 @@
1
+ ---
2
+ simt-affects: ["docs/reviews/0002-full-review-adoption-and-public-release.md"]
3
+ simt-supersedes: []
4
+ ---
5
+
6
+ # ADR-102 — the two rows review 0002 left queued are closed by ruling
7
+
8
+ Status: **accepted** · 2026-10-28
9
+
10
+ ## Context
11
+
12
+ Review 0002's §3 tracker is the only tracker of slice state, and two rows in it
13
+ still read Queued while everything around them shipped: S38 (workspace token
14
+ cost) and S39 (estate policy as a pack). Both rows' evidence cells are
15
+ palimpsests — notes from three different landings disagreeing about what is
16
+ still open — so reading them as a work list sends the next reader to build what
17
+ is already built. Closing plan 0001 meant the same audit, and ADR-033 is the
18
+ precedent: rows closed by saying what shipped, what was refused by ruling, and
19
+ where the remainder lives, not by authoring what they asked for.
20
+
21
+ ## Ruling
22
+
23
+ 1. **S38 is Complete.** The instrument shipped (`simt tokens`, ADR-064), the
24
+ declaration shipped (`budget.bytes` beside `limits` and `gates`, ADR-072),
25
+ and the gate raises it as a finding through one `budgetFinding` read from
26
+ one place by both `tokens` and `check` — the "still open" clause the row's
27
+ first note ends on. Kernel-line packing shipped with it (R07's `mode:
28
+ kernel`, R08's budget compilation). Blocking enforcement is refused, not
29
+ outstanding: a gate that fails a repository for exceeding a ceiling it set
30
+ itself, when nothing was cut, has no repair except lowering the declared
31
+ number (ADR-072). The one genuine remainder is the kernel-default
32
+ compliance study, and it is owned by plan 0003 R07's own note, not by this
33
+ row — the study gates making `kernel` the default, which no row asks to
34
+ decide here.
35
+
36
+ 2. **S39 is Complete.** The mechanism shipped (packs vendored and digest-pinned,
37
+ ADR-065; measured ΔR, plan 0003 R45). P4's pack-carries-the-registry line was
38
+ superseded by ADR-061, which made the registry workspace data, so there is
39
+ no registry-carrying pack to build. The remaining half — moving estate
40
+ policy into a pack — is the estate's own commit: no estate content may enter
41
+ this repository, and a row that waits on another repository's commit is
42
+ closed by recording whose commit it waits on.
43
+
44
+ ## Consequences
45
+
46
+ - The §3 tracker reads Complete on both rows, with this record as the evidence,
47
+ so the next audit starts from rulings rather than from mid-flight notes.
48
+ - A reader asking for a blocking budget gate or a registry-carrying pack gets
49
+ the refusal and its reason instead of an open row that looks like a promise.
@@ -0,0 +1,74 @@
1
+ ---
2
+ simt-affects: ["rules/stacks/jvm.md", "rules/stacks/android.md", "rules/stacks/react-web.md", "rules/stacks/react-native.md", "rules/stacks/cloudflare-workers.md", "rules/stacks/container.md", "rules/mdc/preview-postflight.mdc"]
3
+ simt-supersedes: []
4
+ ---
5
+
6
+ # ADR-103 — duplicates stand alone, fingerprints generalize, references spell out
7
+
8
+ Status: **accepted** · 2026-10-28
9
+
10
+ ## Context
11
+
12
+ Twenty-four rule files ship: one core, nineteen stack profiles, four process
13
+ rule files. An audit of all of them found three kinds of defect and one thing
14
+ that looks like a defect but is not:
15
+
16
+ 1. **Verbatim duplicates inside one projection.** An Android repository selects
17
+ both `jvm` (gradle) and `android` (manifest), so it received the gradlew
18
+ invocation, the Spotless entrypoint, the `:core:domain` direction rule and
19
+ the assert-behaviour testing paragraph twice, in two sections of one region.
20
+ 2. **Estate fingerprints.** Literal module paths (`:core:domain`), a custom
21
+ check name (`checkOverlayModalPolicy`), an allowlisted surface (`core:ui`),
22
+ a string-resource prefix (`cd_`), product nouns (`block`, `reveal`),
23
+ an Arabic-only mirroring rule, a locale-parity claim about the estate's
24
+ tests, a privacy-probe list naming the estate's data categories, and a
25
+ preview-postflight file whose defaults name the estate's env vars, Firebase
26
+ identity and Google-only policy.
27
+ 3. **Two dangling references.** `react-web` named "the full strictness set
28
+ listed in the Workers profile", which a browser-only repository never
29
+ receives; `react-native` said "see that profile", pointing at a profile a
30
+ native-only repository never receives. Both fail the receiving-repo test:
31
+ a reader who has only their own repository cannot act on them.
32
+ 4. **Not a defect: duplicates across mutually exclusive profiles.**
33
+ `react-web` and `react-native`, `go` and `rust`, `deno` and `node` repeat
34
+ maxims, but no repository receives both sides. Merging them needs an
35
+ include mechanism profiles do not have, and a pointer to a non-projected
36
+ profile is defect 3 again. Each profile stands alone.
37
+
38
+ ## Ruling
39
+
40
+ 1. **Co-projected duplicates stay duplicated: the merge was attempted and
41
+ refused by a render test.** `android` pointed at the `jvm` section for
42
+ invocation, formatting and the shared suffixes — and a manifest-only
43
+ fixture (an `AndroidManifest.xml` with no root Gradle file) selected
44
+ `android` without `jvm`, leaving the pointers dangling. Gradle at the root
45
+ is usual, not guaranteed, and a rule that is present only usually is
46
+ silently absent exactly when its reader needs it. So `android` stays
47
+ self-contained and the standing jvm/android overlaps are recorded rather
48
+ than merged: merging them needs a profile-implication mechanism profiles
49
+ do not have, and that mechanism needs its own record. No hard-priority
50
+ lines move, so kernel bytes do not move either way.
51
+ 2. **Fingerprints generalize in place; no profile is removed and no id is
52
+ renamed.** `:core:domain` becomes the core domain module; the modal rule
53
+ keeps the public M3 names and loses the estate check and surface; `cd_`
54
+ becomes a content description; `block`/`reveal` become the generic nouns;
55
+ mirroring covers RTL locales, not Arabic alone; the locale-parity sentence
56
+ and the product data categories go; preview-postflight's env vars, Firebase
57
+ identity and Google-only defaults become neutral wording with the slots
58
+ repos already declare. `rules list`, the catalogue and selection behaviour
59
+ are unchanged.
60
+ 3. **The two dangling references are spelled out.** `react-web` inlines the
61
+ strictness flags; `react-native` names browser rules instead of pointing at
62
+ them.
63
+
64
+ ## Consequences
65
+
66
+ - Regions change only where a fingerprint generalized or a dangling reference
67
+ was spelled out; `android` is byte-comparable to before modulo the
68
+ generalizations. `mdc.test.mjs`'s build-config test follows the renamed
69
+ default; its intent (the union survives) is unchanged.
70
+ - A repository that opted into preview-postflight on the estate defaults sees
71
+ neutral wording and declares its own provider values through the slots that
72
+ already exist.
73
+ - Mutually exclusive duplicates stay duplicated on purpose. A future include
74
+ mechanism needs its own record; this one refuses to invent it by pointer.
@@ -0,0 +1,76 @@
1
+ ---
2
+ simt-affects: ["src/agenthooks/shell.mjs", "src/agenthooks/policy.mjs", "src/agenthooks/decide.mjs"]
3
+ simt-supersedes: []
4
+ ---
5
+
6
+ # ADR-104 — the pre-tool-use policy reads the command the way a shell reads it
7
+
8
+ Status: **accepted** · 2026-10-28
9
+
10
+ ## Context
11
+
12
+ The two command policies — push (ADR-001) and rewrite (ADR-017) — matched the
13
+ payload's command string with a regular expression. Review 0003 (Q01) verified
14
+ against the shipped binary that an agent routes around both of them: `git -C .
15
+ push`, `git --no-pager push`, `sh -c 'git push'`, `$(git push)`, `(git push)`,
16
+ `git push;echo ok`, the alias `git p`, `git send-pack`, `git commit -n`,
17
+ `--amen`, `--no-verif`, `git reset --har`, `git -C . commit --amend`,
18
+ `git pull --rebase`, and with `SIMT_ALLOW=push`, `git push -fu` and
19
+ `git push origin +main`. Three acts no policy named at all: `git -c
20
+ core.hooksPath=/dev/null commit`, `git config core.hooksPath …` and
21
+ `rm -rf .simt-hooks` — the repository's gates going away without a policy ever
22
+ seeing them. And two false positives: `echo --no-verify` and `grep -r "git
23
+ rebase" docs` were denied because a flag or a phrase sat in a command that was
24
+ not git.
25
+
26
+ Every bypass is the same act spelled another way, and a policy that reads text
27
+ cannot tell them apart. The decision is made on the act.
28
+
29
+ ## Ruling
30
+
31
+ 1. **The command is normalised before a policy matches it.**
32
+ `src/agenthooks/shell.mjs` tokenises the command the way POSIX sh tokenises
33
+ it — quotes, escapes, `; && || | &`, newlines, subshells, `$()` and
34
+ backticks — descends into the wrappers a shell executes for you (`sh|bash|
35
+ zsh|dash -c`, `eval`, `env`, `command`, `exec`, `xargs`, `nice`, `time`,
36
+ `sudo`), and reduces every git invocation to the fact a policy can read:
37
+ which subcommand, with which options, in which repository. Long options are
38
+ resolved by git's unique-prefix rule (`--amen` is `--amend`), short clusters
39
+ are expanded (`-fu` is `-f -u`), and a leading `+` on a refspec is force.
40
+ The policies match that fact, not the text.
41
+ 2. **A command the tokenizer cannot read is judged by the row's regex, never by
42
+ nothing.** Unbalanced quotes and an unclosed `$()` mark the command as
43
+ unparseable, and the pre-normaliser regex runs on it, so a parse failure
44
+ weakens nothing. Facts that were never normalised — a test that hands
45
+ `decide` a bare command string — take the same path. The push and rewrite
46
+ ids and messages are unchanged, so an adopted repository reads the same
47
+ denial it read before.
48
+ 3. **A third command policy, `hooks-disable`, names the act the other two
49
+ cannot:** removing or rerouting the git hooks a repository's gates run in —
50
+ `git -c core.hooksPath=…`, `git config … core.hooksPath`, and `rm` of the
51
+ hooks directory. It carries no regex fallback because it did not exist
52
+ before this ruling: a command that cannot be tokenised is judged exactly as
53
+ it was judged before the row was written.
54
+ 4. **The author's aliases are resolved with one `git config --get-regexp
55
+ '^alias\.'` per command, and only when a fact's subcommand is not one git
56
+ ships.** A value that starts with `!` is a shell command and is read the
57
+ way any other command string is; an alias that resolves to nothing the
58
+ table can read is left as-is, which is the same position the pre-lookup
59
+ policy found it in. A git that is missing or cannot answer yields no
60
+ aliases, and the facts stand where the pre-lookup policy would have left
61
+ them.
62
+
63
+ ## Consequences
64
+
65
+ - The false positives go with the text: `echo --no-verify` and `grep -r "git
66
+ rebase" docs` carry no git fact, so the rewrite policy does not fire on
67
+ them.
68
+ - Every force spelling — `--force`, `-f` in a cluster, `+refspec` — is both a
69
+ push and a rewrite, so lifting `push` no longer carries a force-push
70
+ through, in any of the spellings the regex let past.
71
+ - The decision stays pure and fast: normalisation is a fact-gathering step in
72
+ `factsFor`, and `decide` reads facts. The latency test, which measures
73
+ `decide` over prebuilt facts, is unchanged.
74
+ - A command that names only builtin subcommands pays nothing for the lookup;
75
+ one that names an alias pays a single `git config` spawn with a one-second
76
+ ceiling.
@@ -0,0 +1,44 @@
1
+ ---
2
+ simt-affects: ["src/output/index.mjs"]
3
+ simt-supersedes: []
4
+ ---
5
+
6
+ # ADR-105 — the JSON ceiling shrinks arrays and never cuts bytes
7
+
8
+ Status: **accepted** · 2026-10-29 · filed as review 0003 Q10
9
+
10
+ ## Context
11
+
12
+ ADR-070 ruling 5 gave `emitJson` the byte ceiling: a document over `--max-bytes`
13
+ was cut at the byte count with a `TRUNCATED` line after it, the same announcement
14
+ the text renderer prints. Compact JSON has no newlines, so the cut landed
15
+ mid-token and the command exited 0: `why --json --max-bytes 120` answered with a
16
+ document no parser accepts and a status that called it a success.
17
+
18
+ ## Ruling
19
+
20
+ 1. **JSON is never byte-truncated.** `emitJson` shrinks the payload to fit: the
21
+ longest arrays empty first, and the document carries
22
+ `truncated: { field, omitted, limit }` (or `fields: [{ field, omitted }]`
23
+ when several lists shrank) naming exactly which list lost how many items.
24
+ 2. **A ceiling no trimming can fit is a `MAX_BYTES` error envelope, not a cut.**
25
+ `{ ok: false, error: { code: 'MAX_BYTES', message, remedy } }` with
26
+ `truncated: { omitted, limit }`, still one parseable document. The remedy
27
+ reads `rerun with --max-bytes 0 for the whole document` — the same ceiling
28
+ the text renderer names, in the same words.
29
+ 3. **The text ceiling is unchanged.** `emit` still cuts lines and announces the
30
+ omitted bytes; prose has line boundaries worth cutting at, JSON does not.
31
+
32
+ ## Consequences
33
+
34
+ - Every `--json --max-bytes` answer parses, trimmed or enveloped. A reader can
35
+ trust that a document on stdout is the whole answer when no `truncated` key
36
+ rides with it.
37
+ - `truncated` is additive: readers that ignore unknown fields see today's shape
38
+ with shorter lists, so no schema bump.
39
+
40
+ ## Rejected
41
+
42
+ - **Cutting at a newline for JSON too.** Compact output has none and pretty
43
+ output's newlines are indentation, not item boundaries; either cut still
44
+ breaks the parse the caller was promised.
@@ -0,0 +1,46 @@
1
+ ---
2
+ simt-affects: ["src/agenthooks/shim.mjs", "src/agenthooks/render.mjs", "src/commands/agenthook.mjs"]
3
+ simt-supersedes: []
4
+ ---
5
+
6
+ # ADR-106 — the harness hook runs through a repo shim, and `failClosed` only with it
7
+
8
+ Status: **accepted** · 2026-10-29
9
+
10
+ ## Context
11
+
12
+ Review 0003 (Q21) verified by code-read that every harness config named the
13
+ bare `simt` binary. A devDependency install puts `simt` in
14
+ `node_modules/.bin`, not on a harness's PATH, so that entry runs nothing on
15
+ Claude — a non-2 exit is silent: no enforcement — and blocks every tool call
16
+ on Cursor, whose entry sets `failClosed: true`. The install wrote a command
17
+ that worked only on machines that happened to have the binary on PATH, which
18
+ are exactly the machines that least need the hook.
19
+
20
+ ## Ruling
21
+
22
+ 1. **Every harness config names the repo shim, never the binary.**
23
+ `agent-hooks install` writes `.simt-hooks/agent-hook` beside the git shims,
24
+ rendered with the same `renderLocator` block from `src/locate.mjs`, and
25
+ every entry's command is `<shim> <event> --harness <id>`. The shim takes its
26
+ arguments through (`agent-hook "$@"`), so one file serves every event and
27
+ every harness, and `--harness` stays explicit rather than detected.
28
+ 2. **`failClosed` is set only because the command is the shim.** Cursor keeps
29
+ `failClosed: true` on entries that point at the shim. It is never set on a
30
+ bare-PATH binary: that combination blocks every tool call on the machines
31
+ where the binary is not installed, which is exactly the machines the shim
32
+ exists for.
33
+ 3. **A shim that cannot resolve refuses.** When no binary is reachable the shim
34
+ exits 1 with a remedy, the way the git shims do. On Claude that is still a
35
+ non-2 exit — silent, no enforcement — and that limitation is accepted and
36
+ stated here rather than hidden: the shim fixes the machines where `simt`
37
+ is installed but not on PATH, not the machines where it is absent.
38
+
39
+ ## Consequences
40
+
41
+ - `ours()` recognises both spellings, so an install over a pre-shim config
42
+ migrates the entry in place instead of stacking a second one.
43
+ - The shim is journaled with the configs, so `undo` removes or restores it
44
+ with the run that wrote it.
45
+ - A hand-written bare-`simt` entry is the author's, not this tool's: `status`
46
+ does not claim it, and `install` replaces it.
@@ -0,0 +1,51 @@
1
+ ---
2
+ simt-affects: ["src/commands/agenthook.mjs", "src/commands/help.mjs", "test/agenthooks.test.mjs"]
3
+ simt-supersedes: []
4
+ ---
5
+
6
+ # ADR-107 — an install can be narrowed to one harness, and the skipped ones are named
7
+
8
+ Status: **accepted** · 2026-10-30
9
+
10
+ ## Context
11
+
12
+ `agent-hooks install` wrote every harness that documents a hooks file. A
13
+ repository whose agents run in one of them received entries in the others'
14
+ files as well — files no agent there reads, which a later `status` then
15
+ reports PRESENT while nothing in that harness enforces anything. For a
16
+ one-harness repository the install wrote more files than it needed to, and
17
+ there was no way to ask for fewer.
18
+
19
+ ADR-079 ruling 11 settled that the readers this tool does not write are
20
+ *named* in the report — a command that writes four files and stays silent
21
+ about the fifth reads as covering it. A narrowed install creates a third
22
+ category that ruling did not anticipate: readers this tool *can* write and
23
+ did not.
24
+
25
+ ## Ruling
26
+
27
+ 1. **`agent-hooks install --harness <id>` writes that one reader.** The id is
28
+ validated against the adapter table before anything is planned, and an
29
+ unknown id is a usage error that names the valid ids — the same validation
30
+ `agent-hook` applies, with the same remedy. The shim is written as before:
31
+ every config points at it, and a one-harness install would leave a config
32
+ pointing at a shim that is not there.
33
+ 2. **The skipped adapters are named in both surfaces.** The text report
34
+ prints a `SKIPPED` line per skipped reader with a note that says how to
35
+ write them all, and the JSON document carries them in `notWritten` with
36
+ `state: "skipped"`. A narrowed install that stays silent about the readers
37
+ it left alone is the failure ADR-079 ruling 11 ruled out for the full one.
38
+ 3. **`status` is unchanged.** It reports what each harness *will* run, and a
39
+ file that was never written is `ABSENT` there — the true answer. The
40
+ narrowing is a choice the install made, and the install's report is where
41
+ that choice is recorded.
42
+
43
+ ## Consequences
44
+
45
+ - A repository that installs for one harness and later adds a second runs
46
+ install again — with the new id, or without any — and the merge creates the
47
+ entry in the new file while the files it already wrote come back
48
+ `UNCHANGED`. Nothing about a narrowed install has to be undone.
49
+ - The prompt opt-in hint prints only when the selected harness can receive a
50
+ packet; a narrowed install to a harness with no prompt event does not ask
51
+ for an opt-in it cannot use.
@@ -0,0 +1,59 @@
1
+ ---
2
+ simt-affects: ["src/commands/init.mjs", "src/commands/map.mjs", "test/init.test.mjs", "test/map.test.mjs"]
3
+ simt-supersedes: []
4
+ ---
5
+
6
+ # ADR-108 — an adoption builds the index, and a command that reads it refreshes it on read
7
+
8
+ Status: **accepted** · 2026-10-30
9
+
10
+ ## Context
11
+
12
+ The index is a cache of git blobs (ADR-089): gitignored, rebuildable from
13
+ `git ls-files -s`, and the data source for `simt map`, `simt brief` and
14
+ `simt slice`. Two states of that cache were wrong for an adopted repository.
15
+
16
+ First, `simt init` adopted a repository and left the index unbuilt. The
17
+ first `simt map` in a fresh adoption paid for a walk of the whole tree, and
18
+ the prompt-submit hook delivered a cold index — "the index is empty" — until
19
+ somebody happened to run `simt index`. An adoption that promises the tool
20
+ works in that repository should leave it able to answer.
21
+
22
+ Second, once built, the cache went stale silently. `loadGraph` rebuilt only
23
+ when the index was *empty*, so a file added after the build was invisible to
24
+ every command that reads the index, with no way to tell from their output
25
+ that they were answering from an old tree.
26
+
27
+ ## Ruling
28
+
29
+ 1. **`init` builds the index in the apply phase**, after the manifest is
30
+ written — `reindex` reads it for the declared parsers, so the manifest has
31
+ to exist first. The plan names the file it will build, and the JSON
32
+ document carries the result. A failure is a *note*, not a failed adoption:
33
+ the index is a cache, and an adopted repository whose index build failed
34
+ is better than one that refused to be adopted for it. The next read
35
+ rebuilds what the apply could not.
36
+ 2. **A command that reads the index refreshes it on read.** `loadGraph` with
37
+ `build` (the default) reindexes from the git index before it reads. The
38
+ refresh is incremental — a blob that has not changed is not re-read — so
39
+ the cost of a current repository is one `git ls-files -s`, not a walk of
40
+ the tree. The callers that build are commands a person typed: `map`,
41
+ `brief`, `slice`.
42
+ 3. **The `build: false` callers are unchanged.** The prompt-submit hook (R29)
43
+ and the MCP surface report a cold index rather than build one: a delivered
44
+ packet that walked the whole repository would make a person's first prompt
45
+ in a fresh clone wait, and a prompt that takes a minute to appear is how a
46
+ hook gets uninstalled.
47
+
48
+ ## Consequences
49
+
50
+ - Amends ADR-091's consequence that "`simt map` reads the index and rebuilds
51
+ it when the cache is empty": the refresh now happens on every read, not
52
+ only on an empty one.
53
+ - In an adopted repository the prompt hook can deliver a packet from the
54
+ *first* prompt: `init` has already built the index, so R29's "the first
55
+ prompt gets nothing, the second gets the packet" no longer applies to a
56
+ repository that was adopted rather than cloned.
57
+ - `simt why` is unchanged: it deliberately does not write the cache to answer
58
+ a question, because writing a cache to answer a question would change the
59
+ tree.
@@ -0,0 +1,47 @@
1
+ ---
2
+ simt-affects: ["AGENTS.md", "rules/core.md", "rules/stacks/node.md", "rules/stacks/python.md", "rules/stacks/connectiq.md", "src/projection/project.mjs", "test/baselines.test.mjs", "test/upgrade.test.mjs"]
3
+ simt-supersedes: []
4
+ ---
5
+
6
+ # ADR-109 — simt-dev is its own consumer, and the guards that kept it from being one are retired
7
+
8
+ Status: **accepted** · 2026-10-30
9
+
10
+ ## Context
11
+
12
+ This repository ran `simt init` on itself, and the suite still believed the
13
+ opposite. Two tests asserted that simt-dev is *not* a consumer of itself — no
14
+ manifest, no region — and `AGENTS.md` explained why self-adoption was
15
+ impossible: the region's stamp records a SHA, so every commit would make the
16
+ file stale.
17
+
18
+ That explanation is no longer true. The `simt:sha=` stamp is a digest of the
19
+ rule *content* — `rules/` and the baselines — not of this repository's HEAD.
20
+ A commit here does not move it, so `check` stays green until the rule text
21
+ itself changes, and `simt sync --yes` re-renders it when that happens.
22
+
23
+ ## Ruling
24
+
25
+ 1. **This repository is a consumer of itself.** `simt.json` declares its
26
+ gates, and the generated region in `AGENTS.md` carries the rules it
27
+ projects. The two tests that asserted the opposite are rewritten to assert
28
+ the new state: the baselines exist with the region first, and an `upgrade`
29
+ from its own source is a no-op that moves no stamp.
30
+ 2. **The rule text names the manifest by the name that resolves in every
31
+ repository.** Four rule files said `simt-dev.json`, the pre-rename name;
32
+ ADR-056 reads both and writes the new one, so a reader in any repository
33
+ finds only `simt.json`. The text now says `simt.json`.
34
+ 3. **A gate finding whose `detail` is absent is `null`, not `undefined`.** A
35
+ key with an `undefined` value survives into the structured MCP surface but
36
+ is dropped by `JSON.stringify`, so the two surfaces of one answer no longer
37
+ deep-equal. The four constructions of a region verdict now share one helper
38
+ that emits `null`.
39
+
40
+ ## Consequences
41
+
42
+ - The suite's view of this repository is the adopted one: `check` here is
43
+ green until the rule content changes, and the baselines test asserts the
44
+ region-first placement that keeps a hand-written setting below it winning
45
+ (ADR-025).
46
+ - `AGENTS.md`'s hand-written section now says what is true: the repository
47
+ adopts itself, and the stamp is a content digest.
@@ -0,0 +1,43 @@
1
+ ---
2
+ simt-affects: ["src/name.mjs", "src/projection/lock.mjs", "src/kernel/variants.mjs", "src/commands/sync.mjs", "src/commands/help.mjs", "src/bench/variants.mjs", "test/lock.test.mjs", "test/variants.test.mjs"]
3
+ simt-supersedes: [{ adr: "ADR-086", ruling: 3, kind: "superseded" }]
4
+ ---
5
+
6
+ # ADR-110 — the lock is `simt.lock.json`, and the old name is read until the next write
7
+
8
+ Status: **accepted** · 2026-10-30 · supersedes ADR-086 ruling 3's file name
9
+
10
+ ## Context
11
+
12
+ The lock followed the command's old name: `simt-dev.lock.json`. The command
13
+ is `simt`, the manifest is `simt.json`, and the lock was the one remaining
14
+ file that still carried the pre-rename spelling.
15
+
16
+ A rename cannot be a bare move. A lock written by a pre-rename run may hold
17
+ `variants` winners that nothing else records — the bench evidence for a
18
+ locked rule text — so a reader that only knows the new name would silently
19
+ unlock those decisions.
20
+
21
+ ## Ruling
22
+
23
+ 1. **The lock is `simt.lock.json`.** The old name is a read-only fallback:
24
+ a reader tries the new name first and falls back to the old one, and a
25
+ write always lands under the new name, which is how a legacy lock
26
+ migrates. The fallback is for absence, not for damage: a permission
27
+ failure on the new name does not fall back, and a file that exists but
28
+ does not parse is reported, not repaired by an older file. This is the
29
+ same read-both/write-new pattern as the manifest rename (ADR-056).
30
+ 2. **The name lives in one place.** `src/name.mjs` owns both names, the way
31
+ it owns the manifest's. The kernel and the projection each carried a
32
+ private copy — the kernel cannot import the projection, because the
33
+ projection imports the kernel — and two constants for one file is how a
34
+ rename ships half-done.
35
+
36
+ ## Consequences
37
+
38
+ - A repository whose lock predates the rename keeps its locked variant
39
+ winners: `simt check --frozen` and the projection read them from the old
40
+ file, and the next `simt sync --yes` or `simt bench variants --yes` writes
41
+ them back under the new name.
42
+ - ADR-086's ruling 3 stands for what the lock records — an input digest and
43
+ an output digest; the file name it named is superseded.
@@ -0,0 +1,75 @@
1
+ ---
2
+ simt-affects: ["agents/simt-agent.md", "src/agents/render.mjs", "src/commands/agent.mjs", "src/commands/init.mjs", "src/projection/loaders.mjs", "src/toolwords.mjs", "test/agents.test.mjs"]
3
+ simt-supersedes: []
4
+ ---
5
+
6
+ # ADR-111 — the tool ships one agent, rendered per harness, and installs it with init and a command
7
+
8
+ Status: **accepted** · 2026-10-30
9
+
10
+ ## Context
11
+
12
+ The rules this tool projects tell an agent *what* to obey; nothing told it
13
+ *how to act* through the tool. The plan for that was two agents — a planner
14
+ and a builder — but the two jobs are one job in practice: read the tracker,
15
+ take the next slice, build it, test it, commit it. Two files that both say
16
+ "run the simt commands" are two copies of the same logic, and the second copy
17
+ is where the next divergence grows.
18
+
19
+ Six harnesses read a subagent and no two agree on the file. Five write a
20
+ markdown file with YAML frontmatter and a body that becomes the system
21
+ prompt; Codex writes a TOML file whose `developer_instructions` holds the
22
+ same body. The path, the extension, the frontmatter keys and the document
23
+ shape are all different, and none of them is the same fact.
24
+
25
+ A hooks entry is a config key the tool merges into a file it does not own.
26
+ An agent is a file the tool owns whole: there is nothing to merge, and the
27
+ only question is whether the bytes on disk are the bytes the tool would write
28
+ now.
29
+
30
+ ## Ruling
31
+
32
+ 1. **The tool ships one agent, `Simt-Agent`.** Its body is the system prompt
33
+ and it encodes how to act with simt-dev across the whole slice lifecycle —
34
+ orient (`simt brief`, `simt why`), plan (`simt plan status`, `simt plan
35
+ next`), build (`simt slice start`, the focused test, `simt slice finish`,
36
+ `simt plan done`) and the boundaries that hold everywhere. Planning and
37
+ building are one agent because they are one job.
38
+ 2. **The source of truth is one file, `agents/simt-agent.md`.** Neutral
39
+ frontmatter (`name`, `description`) plus a harness-agnostic body. The
40
+ renderer strips that frontmatter and re-emits the keys each harness
41
+ documents, so the body — the part that says how to act — is written once
42
+ and cannot drift between harnesses. The `simt-agent` stem is a name this
43
+ tool owns, and it is derived into the tool-word set from the module that
44
+ owns it, the way the other prefixed names are.
45
+ 3. **An agent is a file the tool owns whole, so the install is a byte
46
+ comparison.** `rendered` produces the text and reports `created`,
47
+ `unchanged` or `updated`. There is no parse that can fail and no partial
48
+ state, which is what makes the command shorter than `agent-hooks install`.
49
+ 4. **It is installed per harness, and the readers it does not write are
50
+ named.** Six harnesses document an agents file and receive one; the two
51
+ that document none are reported `NO FILE` with the reason, the way
52
+ ADR-079 ruling 11 requires. `--harness <id>` narrows the install to one
53
+ reader and names the skipped ones (ADR-107), and `status` reports
54
+ present, absent, drift, and the readers with no file.
55
+ 5. **It is written by `simt init` and by a separate `simt agents install`.**
56
+ `init` plans the agent files with the rest of the adoption and writes them
57
+ in the apply path, journaled the same way, so `undo` restores them with the
58
+ rest. The separate command exists for a repository that is already adopted
59
+ and wants only the agent, and for a narrowed install. Both call the same
60
+ `planAgents` and `writeAgentPlan`, so a dry run and a run describe the same
61
+ set of files.
62
+
63
+ ## Consequences
64
+
65
+ - A repository that is adopted and later upgrades the tool's agent body runs
66
+ `simt agents install --yes` (or re-runs `init`) and the files come back
67
+ `updated` with the new body; the frontmatter each harness documents is
68
+ re-emitted from the same source, so it cannot drift.
69
+ - The agent body names only commands a consumer repository runs — `simt
70
+ brief`, `simt why`, `simt plan`, `simt slice` — so every byte in it is
71
+ actionable in the receiving repository, the rule this repository exists to
72
+ enforce.
73
+ - `agents/` is published with the package: the tool reads its own agent body
74
+ from the installed copy, and a tarball without it would install cleanly and
75
+ then fail on the one command that writes the agent.
@@ -0,0 +1,81 @@
1
+ ---
2
+ simt-affects: ["graph/skills.graph.json", "src/resolve/local.mjs", "src/commands/skills.mjs", "src/projection/disclosure.mjs", "src/resolve/body.mjs"]
3
+ simt-supersedes: [{ adr: "ADR-009", ruling: 4 }]
4
+ ---
5
+
6
+ # ADR-112 — the registry is what simt-dev ships, and a repo owns its own skills
7
+
8
+ Status: **accepted** · 2026-10-31 · filed as S4 (plan 0005) · supersedes ADR-009 ruling 4
9
+
10
+ ## Context
11
+
12
+ ADR-009 settled the layout: a skill is `<name>/SKILL.md` with mandatory `name`
13
+ and `description` frontmatter, and body size and reference count are declared in
14
+ `graph/skills.graph.json`, "so the registry stays the single source of truth for
15
+ resolution metadata". The layout half of that ruling was about one estate's
16
+ thirty-nine skills, and the metadata half assumed the estate was the universe: a
17
+ skill not in the graph is a skill that does not exist.
18
+
19
+ Plan 0005 broke that assumption in two slices. S2 removed the thirty-one
20
+ repo-specific entries from the graph, leaving the eight skills this tool ships —
21
+ `simt-commands` and seven vendored stack skills. A graph that describes one
22
+ estate and is read by everyone was ADR-061's finding; a graph that is one
23
+ estate's whole skill population is a stronger claim ADR-061 did not make, and S2
24
+ made it false. S3 then gave the repo's own skill directories standing in the
25
+ resolver: `skills find`, `brief`, `show` and `why` answer for a skill the
26
+ registry never declared, synthesising its node from the file — name and summary
27
+ from frontmatter, size from the file, body from the path (commit 7d44071).
28
+
29
+ A reader of ADR-009 acting on the metadata sentence concludes that a skill absent
30
+ from the graph has no resolution metadata, and therefore cannot resolve. That
31
+ conclusion is now false in every repo that keeps its own skills, and a record
32
+ that says what the tool no longer believes is the failure this directory exists
33
+ to prevent.
34
+
35
+ ## Ruling
36
+
37
+ 1. **The graph is a catalogue of what simt-dev ships, not the sole home of
38
+ skills.** Membership in `graph/skills.graph.json` is a statement about the
39
+ package, not about existence. A skill a repository installed in its own skill
40
+ directories exists and resolves with no entry in the graph.
41
+ 2. **A repo's skill directories are a first-class source of resolution.**
42
+ Presence is the eligibility signal — a skill the repo installed is about the
43
+ repo, and the stack it belongs to is the stack the repo has. Frontmatter
44
+ supplies the name and the summary; the file supplies the body and the size.
45
+ This is the mechanism S3 shipped; the ruling records it so the reader learns
46
+ what the tool believes without reading the plan.
47
+ 3. **Where a name is in both, the registry entry is canonical.** The graph entry
48
+ and the local file are one skill — the file is the installed copy of what the
49
+ graph declares — and keeping both would rank one skill twice. The graph wins
50
+ the dedupe; the local node is dropped, not merged.
51
+
52
+ ## Consequences
53
+
54
+ - ADR-009's layout ruling stands and is load-bearing: discovery reads `name` and
55
+ `description` from frontmatter, so a local skill without them is invisible,
56
+ which is the defect class ADR-009 exists to prevent. What moves is the metadata
57
+ sentence: a local skill's resolution metadata is synthesised from the
58
+ filesystem, not read from the graph, so the graph is no longer the single
59
+ source of truth for it.
60
+ - `skills find`, `brief`, `show` and `why` answer for names the registry never
61
+ declared. The NOT SELECTED block no longer points a reader at a `skills show
62
+ <name>` that 404s for a name the graph does not carry.
63
+ - Nothing in the shipped bytes changes. ADR-061 stands as written: the registry
64
+ is workspace data, the package ships none of it, and a command that finds no
65
+ registry says so. This record settles the half ADR-061 left open — who is
66
+ allowed to have skills the catalogue does not name — without touching its
67
+ rulings.
68
+ - `skills index` keeps reporting the catalogue only: it is the tier-0 view of
69
+ what this tool ships, and local skills surface where a repo is the context,
70
+ which is `find` and `brief`.
71
+
72
+ ## Rejected
73
+
74
+ - **Rewrite ADR-009 in place.** The supersession rule this directory opens with
75
+ is a new record and an edited status line, never a rewritten ruling. ADR-009's
76
+ audit data — the forty-six frontmatter tallies that found the flat file — is
77
+ evidence its own ruling relies on, the same shape that kept ADR-011 standing
78
+ when ADR-012 reversed it.
79
+ - **Annotate ADR-009 without a record.** The supersession is a claim about what
80
+ the tool believes, and a claim without a record is a ruling nobody can cite.
81
+ The annotation points at ADR-112; ADR-112 carries the argument.