simt-dev 0.5.0 → 0.6.2

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 (85) hide show
  1. package/CHANGELOG.md +146 -0
  2. package/README.md +78 -59
  3. package/docs/decisions/ADR-058-one-tool-three-channels-and-the-order-is-forced.md +13 -0
  4. package/docs/decisions/ADR-059-the-version-a-repository-runs-is-the-one-it-pinned.md +1 -1
  5. package/docs/decisions/ADR-097-the-channels-publish-by-hand-and-the-version-rides-in-as-an-input.md +71 -0
  6. package/docs/decisions/ADR-098-the-fourth-channel-is-one-file-and-it-needs-no-toolchain.md +149 -0
  7. package/docs/decisions/ADR-099-the-release-verifies-served-bytes-and-the-formula-mirrors-them.md +79 -0
  8. package/package.json +4 -4
  9. package/src/agenthooks/render.mjs +87 -21
  10. package/src/argv.mjs +13 -0
  11. package/src/bench/ablate.mjs +1 -3
  12. package/src/bench/grade.mjs +1 -1
  13. package/src/bench/mine.mjs +3 -3
  14. package/src/bench/probes.mjs +1 -3
  15. package/src/bench/report.mjs +1 -3
  16. package/src/bench/run.mjs +1 -3
  17. package/src/bench/tasks.mjs +1 -3
  18. package/src/bench/variants.mjs +1 -3
  19. package/src/brief/assemble.mjs +2 -4
  20. package/src/ci/dependabot.mjs +1 -1
  21. package/src/ci/workflow.mjs +47 -17
  22. package/src/commands/agenthook.mjs +6 -2
  23. package/src/commands/bench.mjs +4 -4
  24. package/src/commands/ci.mjs +1 -1
  25. package/src/commands/compare.mjs +1 -1
  26. package/src/commands/distill.mjs +1 -1
  27. package/src/commands/gate.mjs +2 -2
  28. package/src/commands/graph.mjs +2 -2
  29. package/src/commands/harness.mjs +87 -14
  30. package/src/commands/help.mjs +29 -7
  31. package/src/commands/hooks.mjs +1 -1
  32. package/src/commands/index.mjs +17 -3
  33. package/src/commands/init.mjs +2 -2
  34. package/src/commands/local.mjs +11 -0
  35. package/src/commands/mcp.mjs +1 -1
  36. package/src/commands/pack.mjs +246 -0
  37. package/src/commands/plugin.mjs +61 -0
  38. package/src/commands/rules.mjs +1 -1
  39. package/src/commands/skills.mjs +1 -1
  40. package/src/commands/skillscompile.mjs +1 -3
  41. package/src/commands/skillsinstall.mjs +1 -1
  42. package/src/commands/sync.mjs +8 -8
  43. package/src/commands/tokens.mjs +1 -1
  44. package/src/commands/upgrade.mjs +1 -1
  45. package/src/commands/why.mjs +1 -1
  46. package/src/compare/discover.mjs +1 -1
  47. package/src/compare/hooksstate.mjs +19 -7
  48. package/src/compare/learn.mjs +2 -2
  49. package/src/compare/row.mjs +3 -3
  50. package/src/decisions/index.mjs +1 -1
  51. package/src/distill/distill.mjs +2 -2
  52. package/src/distribute/release.mjs +142 -0
  53. package/src/graph/load.mjs +9 -8
  54. package/src/hooks/dispatcher.mjs +3 -2
  55. package/src/hooks/shim.mjs +11 -6
  56. package/src/index/govern.mjs +1 -1
  57. package/src/index/map.mjs +1 -1
  58. package/src/index/persist.mjs +2 -2
  59. package/src/index/store.mjs +50 -20
  60. package/src/ledger/index.mjs +2 -2
  61. package/src/local/client.mjs +22 -8
  62. package/src/local/config.mjs +17 -0
  63. package/src/locate.mjs +22 -1
  64. package/src/mcp/prompts.mjs +118 -0
  65. package/src/mcp/resources.mjs +96 -22
  66. package/src/mcp/server.mjs +33 -6
  67. package/src/mcp/tools.mjs +12 -12
  68. package/src/measure/session.mjs +1 -1
  69. package/src/memory/decide.mjs +1 -1
  70. package/src/memory/lessons.mjs +14 -14
  71. package/src/name.mjs +11 -1
  72. package/src/package-version.mjs +12 -10
  73. package/src/packs/manage.mjs +563 -0
  74. package/src/packs.mjs +15 -5
  75. package/src/plugins.mjs +164 -0
  76. package/src/projection/loaders.mjs +84 -10
  77. package/src/projection/manifest.mjs +4 -4
  78. package/src/projection/project.mjs +3 -3
  79. package/src/projection/rules.mjs +24 -4
  80. package/src/projection/surfaces.mjs +20 -2
  81. package/src/review/critic.mjs +4 -4
  82. package/src/toolwords.mjs +8 -5
  83. package/src/tsconfig.mjs +3 -3
  84. package/src/upgrade/plan.mjs +1 -1
  85. /package/bin/{simt-dev.mjs → simt.mjs} +0 -0
package/CHANGELOG.md CHANGED
@@ -6,6 +6,152 @@ All notable changes to this package are recorded here. The format follows
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.6.2] — 2026-10-27
10
+
11
+ ### Fixed
12
+
13
+ The v0.6.1 tag run failed its own tests step: two decision-corpus lists in
14
+ `test/rules.test.mjs` were not moved with ADR-099 (the partial-supersession
15
+ shapes and the leaking-paths count, both designed to move with the corpus).
16
+ Same product as v0.6.1; this tag exists so the release automation is proven by
17
+ a green run rather than described by a red one.
18
+
19
+ ## [0.6.1] — 2026-10-27
20
+
21
+ ### Fixed
22
+
23
+ The release automation, which the v0.6.0 tag proved broken in two places. The
24
+ tag workflow compared CI-built binaries against locally-built committed
25
+ checksums, but the build is deterministic per host family, not across them —
26
+ the check could never pass, and darwin binaries cannot be finished on ubuntu
27
+ runners for lack of `codesign` anyway. Each target now builds on its native
28
+ runner, the release verifies the served bytes equal the built bytes, and a
29
+ `sync-formula` job re-renders the committed formula from the served bytes
30
+ (ADR-099). The formula description no longer starts with an article, which is
31
+ what failed the Homebrew channel's `brew style` on v0.6.0. No product change.
32
+
33
+ ## [0.6.0] — 2026-10-27
34
+
35
+ ### Added
36
+
37
+ `simt plugin <claude|gemini|cursor> --out <dir>` generates one of the three
38
+ harness packages from the same tables `agent-hooks install` writes — the hooks
39
+ file, the MCP registration and, for cursor, the kernel rules — each carrying a
40
+ self-contained copy of the runtime so it installs into a clean profile. Dry-run
41
+ unless `--yes`. The Gemini reader row gains its hooks file (`.gemini/settings.json`),
42
+ which extends `agent-hooks install` and `status` to a fifth adapter.
43
+
44
+ The MCP server answers `prompts/list` and `prompts/get` with two templates:
45
+ `simt-brief` starts a task from the pre-assembled brief packet, and `simt-slice`
46
+ implements one slice of the working plan (the next one whose dependencies are
47
+ complete, or a named one) — reading the repo's rules through `simt://kernel`
48
+ first, testing per the slice's acceptance check, committing locally and pushing
49
+ only on explicit approval. A prompt names the tools; the server stays read-only.
50
+
51
+ Two fixed resources join `simt://rules/{id}` and `simt://decisions/{adr}`:
52
+ `simt://kernel` serves the collaborating region rendered in kernel mode for
53
+ whatever repository the server runs in — the URI promises the kernel's bytes,
54
+ whatever `mode` the manifest declares — and `simt://plan/next` serves the
55
+ section of the next ready plan slice, listed only while one is ready. A listed
56
+ resource's size is the size its read returns.
57
+
58
+ The tool descriptions in `tools/list` are cut from 2,783 bytes to 1,949,
59
+ inside the 2 KB budget plan 0003 R41 sets for a cacheable tools list; the
60
+ input and output schemas stay full, per ADR-068.
61
+
62
+ `harness detect` reports the MCP registration files the loader table records —
63
+ `.mcp.json` (Claude Code), `.codex/config.toml`, `.cursor/mcp.json`,
64
+ `.agents/mcp_config.json`, `.gemini/settings.json`, `.vscode/mcp.json` — as
65
+ `kind: 'mcp'` rows whose provenance is the vendor page each path was read from.
66
+ A row rests on the file existing, never on a directory's presence, and never
67
+ counts toward the no-rule-channel note: a config file doesn't claim one.
68
+
69
+ The loader table gains the Gemini CLI reader, whose configuration reference the
70
+ table previously recorded as unavailable: it is live again (read 2026-10-26), so
71
+ the row carries the GEMINI.md chain with `.git` boundary markers, the
72
+ `context.fileName` rename (the only documented way this reader picks up
73
+ AGENTS.md), depth-5 `@path` imports, no published cap, and the `mcpServers`
74
+ registration. It stays a facts row — the `gemini-md` pointer remains the one
75
+ rule channel for that reader.
76
+
77
+ Each release now carries a fourth artefact beside the tarball, the wheel and the
78
+ formula: one single-file executable per target (`darwin-arm64`, `darwin-x64`,
79
+ `linux-x64`), built from the same `files` set with a pinned Node runtime, published to
80
+ the GitHub release with a `.sha256` beside it and a build-provenance attestation. It is
81
+ the channel for a machine that has no toolchain to install one with — the binary lands
82
+ as one file in `.simt/bin/` and carries its own runtime — and its acceptance is
83
+ executable: a Gradle fixture with every directory containing a node removed from `PATH`
84
+ runs `check` and the hook shim from the built bytes.
85
+
86
+ `simt pack new <name> --source <npm:<package>|git+https://…>` scaffolds a
87
+ versioned pack — `pack.json`, an unmeasured `bench.json`, one stack rule — and
88
+ `simt pack lint [dir]` validates it and states its measured ΔR, rejecting a pack
89
+ with no bench summary under `--require-measured`. `simt pack publish [dir]` lints
90
+ first, publishes an npm-sourced pack idempotently (a live version is skipped),
91
+ and names the tag-and-push for a git-sourced one. The summary binds to every
92
+ byte but itself, so recording a measurement cannot invalidate it.
93
+
94
+ ### Changed
95
+
96
+ `harness detect`'s "some readers read AGENTS.md" rows no longer say bare "some"
97
+ when the row knows which one: the `readsAgentsMdNote` travels to the text
98
+ output, and `.gemini` graduates from unverified candidate to verified detection
99
+ facts (presence implies Gemini CLI; AGENTS.md only when `context.fileName`
100
+ names it).
101
+
102
+ The one-line description the three channels publish (npm, PyPI and Homebrew) now
103
+ names the goal plan `0003` is building toward — an intelligence amplifier for AI
104
+ coding agents — instead of the adoption-only story it grew out of.
105
+
106
+ The per-repository state directory is renamed from `.simt-dev/` to `.simt/`: the
107
+ index, ledger, lessons, bench artifacts, local-model proposals (`distilled/`,
108
+ `reviews/`) and repo-local profiles all live under `.simt/` now. Existing state is
109
+ not migrated — a repository that carries a `.simt-dev/` directory moves it once by
110
+ hand (`mv .simt-dev .simt`); everything in it is cache or ledger, regenerable from
111
+ git. The names this tool writes into files it does not own stay put: the `simt.json`
112
+ manifest, the `simt-dev-check.yml` gate, the user-level config directory and the
113
+ `SIMT_DEV_*` environment variables keep their names.
114
+
115
+ The skill registry leaves the published set again, as ADR-061 ruled when it
116
+ first left in 0.2.1. It is a catalogue of one workspace's skills — the
117
+ repositories whose bodies it resolves — and an installed copy serving that
118
+ catalogue to an adopting repository is a confident wrong answer: the skills
119
+ it names do not live in the reader's workspace. 0.5.0 carried the registry
120
+ because a fresh install's `simt skills *` and `simt distill` otherwise found
121
+ none; the fix is the honest absence the tiered loader already reports — the
122
+ path it would have read, and the `simt graph lint <path>` remedy — with a
123
+ workspace that owns the registry naming it through `--workspace`,
124
+ `SIMT_DEV_WORKSPACE` or the user config file. Custom skills were never part
125
+ of the registry: they live in each repository, and the disclosure scan
126
+ projects them into AGENTS.md.
127
+
128
+ The entry point file is renamed from `bin/simt-dev.mjs` to `bin/simt.mjs`,
129
+ matching the command it installs: the command has been `simt` for a long
130
+ time, the package is still `simt-dev`, and the file path was the last place
131
+ that carried the old spelling. `BIN_REL` in `src/name.mjs` is the single
132
+ source of truth, so the generated hook shim and CI workflow pick up the new
133
+ path on their next regeneration; the channels (npm, PyPI, Homebrew), the
134
+ repository's own CI and the test harnesses follow.
135
+
136
+ The hook shim's remedy and the generated CI workflow now install the release binary
137
+ for a repository without npm, instead of the pinned global npm install (`npm i -g`)
138
+ that needed the npm that repository kind is defined by not having. The remedy is the
139
+ one-line download the binary channel names, with its platform derived from `uname`;
140
+ the CI step verifies the download against the published `.sha256` and runs with no
141
+ `setup-node` step at all — a file that runs without a Node toolchain should not have
142
+ one installed to run it.
143
+
144
+ The locator resolves `.simt/bin/simt` between `node_modules` and `PATH`, so the bytes
145
+ a repository downloaded are the bytes its gate runs and a machine-wide install cannot
146
+ shadow them. The dispatcher's version check is unchanged and still refuses a stale
147
+ copy, whichever path found it.
148
+
149
+ Homebrew's formula downloads the release binaries rather than the npm tarball — one
150
+ `url`/`sha256` pair per target, chosen at install time — and no longer declares
151
+ `depends_on "node"`. `scripts/release-formula.mjs` patches each pair by content, and
152
+ the release refuses to publish a formula whose checksums are not the bytes the tagged
153
+ tree builds (ADR-098).
154
+
9
155
  ## [0.5.0] — 2026-10-25
10
156
 
11
157
  ### An offline distiller that proposes, never projects
package/README.md CHANGED
@@ -2,7 +2,10 @@
2
2
 
3
3
  ![simt-dev, the intelligence amplifier for coding agents, and its four claims. One: say less, always — a kernel under 2 KB whose bytes stay stable so the prompt cache survives. Two: answers over exploration — a local code graph and a short task brief. Three: enforce, don't instruct — dual gates, before a tool call and at git time. Four: close the loop — benches and memory. Beneath them, the harnesses it works across and its three channels.](https://raw.githubusercontent.com/QuazarMedia/simt-dev/main/res/59e21e6d-6bf2-4d7d-b6ea-c7349a697d11.jpg)
4
4
 
5
- `simt` adopts a repository for the agents that work in it.
5
+ `simt` adopts a repository for the agents that work in it, and builds an intelligence
6
+ amplifier around them: a brief that answers before the agent explores, gates that
7
+ validate before a tool call, benches that price every rule text, and memory that puts
8
+ a recorded failure to work on the next one.
6
9
 
7
10
  It reads what the repository actually runs, projects the rules that are not already
8
11
  obvious into the files those agents read, and installs only the gates that repository
@@ -20,7 +23,64 @@ Anything that needs a parser grammar, a local model, or another server is a sepa
20
23
  package, off until the repository names it, and it proposes rows or text for a person
21
24
  to merge. It does not write the projection.
22
25
 
23
- ## Today
26
+ ## Install
27
+
28
+ Requires **Node >= 22.20.0** for the npm and PyPI channels; the release binary carries
29
+ its own runtime. The current published release is **0.5.0**. Pin it. The tool writes a
30
+ content digest into the files it generates, and an unpinned upgrade changes them under
31
+ you.
32
+
33
+ ```sh
34
+ npm i -D simt-dev@0.5.0
35
+
36
+ # or globally
37
+ npm i -g simt-dev@0.5.0
38
+
39
+ # or once, with no install
40
+ npx -y simt-dev@0.5.0 check
41
+ ```
42
+
43
+ ### From PyPI
44
+
45
+ The wheel carries the same bytes as the npm tarball. It does not fetch them from npm
46
+ at install time.
47
+
48
+ ```sh
49
+ pip install simt-dev
50
+
51
+ # when this machine has no node
52
+ pip install "simt-dev[node]"
53
+ ```
54
+
55
+ `simt` is the command on both channels. Node is resolved in this order, and the first
56
+ one that answers wins: `SIMT_DEV_NODE`, the node bundled with the `node` extra, then
57
+ `node` on `PATH`.
58
+
59
+ ### From Homebrew
60
+
61
+ ```sh
62
+ brew install QuazarMedia/homebrew-tap/simt-dev
63
+ ```
64
+
65
+ The formula downloads the single-file binary for this machine, which carries its own
66
+ runtime: the command works with no Node installed.
67
+
68
+ ### As a single-file binary
69
+
70
+ Each release carries one executable per platform — macOS arm64 and x64, Linux x64 —
71
+ beside its `.sha256`. The channel's first release is the version after 0.5.0; pin that,
72
+ and run this from the repository's top level:
73
+
74
+ ```sh
75
+ mkdir -p .simt/bin && os=$(uname -s | tr A-Z a-z) && arch=$(uname -m | sed "s/x86_64/x64/") && curl -fsSL "https://github.com/QuazarMedia/simt-dev/releases/download/v<version>/simt-dev-<version>-$os-$arch" -o .simt/bin/simt && chmod +x .simt/bin/simt
76
+ ```
77
+
78
+ The locator checks `.simt/bin/simt` before `PATH`, so the repository's own copy is the
79
+ one its hooks and its CI run — this is the same command the hook shim prints as its
80
+ remedy. The generated CI workflow downloads the same asset and verifies it against the
81
+ published `.sha256` before running anything.
82
+
83
+ ## What `simt` does
24
84
 
25
85
  ```sh
26
86
  # Adopt, inspect, and maintain a repository
@@ -83,11 +143,15 @@ simt bench report [--compare a b] # resolved rate, quality, cost, CPI, bootstr
83
143
  simt bench ablate --yes # leave-one-out rule ablation ranked by value per KB
84
144
  simt bench probes --yes # 12 deterministic rule-compliance probes
85
145
  simt bench variants --yes # evaluate simt-variants slots and lock winners in simt-dev.lock.json
146
+ simt pack new <name> --source <spec> # scaffold a versioned pack: pack.json, bench.json, one rule
147
+ simt pack lint [dir] [--require-measured] # validate a pack and state its measured ΔR
148
+ simt pack publish [dir] --yes # publish an npm-sourced pack (git packs name the tag-and-push)
86
149
  simt mcp [--list] # serve the 13 read-only tools over stdio JSON-RPC
87
150
  ```
88
151
 
89
152
  `init`, `sync`, `upgrade`, `hooks install`, `agent-hooks install`, `shared install`,
90
- `skills install`, `plan done`, `lessons review`, `decide` and the `bench` writers print
153
+ `skills install`, `plan done`, `lessons review`, `decide`, `pack new`, `pack publish`
154
+ and the `bench` writers print
91
155
  a plan and write nothing without `--yes`. None of them commit or push. `simt index`
92
156
  writes its cache without asking: the file is gitignored and `git ls-files` can rebuild it.
93
157
 
@@ -209,7 +273,7 @@ returns a JSON object with `task`, `commands`, `files`, `tests`, `decisions`, `s
209
273
  `DRIFT` with `simt sync --yes`, and resolve any rule checker or manifest findings.
210
274
  2. **Verify execution evidence matches the current tree.** Run `simt evidence` to confirm
211
275
  that the tests and gates you plan to cite were recorded against the current
212
- `git write-tree` digest in `.simt-dev/ledger.jsonl`. Any file edit invalidates earlier
276
+ `git write-tree` digest in `.simt/ledger.jsonl`. Any file edit invalidates earlier
213
277
  evidence.
214
278
  3. **Record decisions, lessons, and tracker state:**
215
279
  - If the change alters what the repository or tool believes, scaffold an ADR with
@@ -232,11 +296,11 @@ returns a JSON object with `task`, `commands`, `files`, `tests`, `decisions`, `s
232
296
  | Shape | How to configure and run `simt` optimally |
233
297
  | --- | --- |
234
298
  | **Single-package repository** (Node, TypeScript, Python/`uv`, Go, Rust, JVM/Gradle/Maven, Android, Swift, .NET, Deno, Ruby, Dart, Elixir, Cloudflare Workers, React Web, React Native, ConnectIQ, Container) | Run `simt init --yes`, `simt hooks install --yes`, `simt agent-hooks install --yes`, and `simt index`. Stack profiles and runnable gates are selected from committed manifests and build files (`simt doctor` shows `stack`, `facts`, and `factEvidence`). Set `"mode": "kernel"` and `"budget": { "bytes": 12000 }` in `simt.json` to keep session-start tokens minimal while keeping convention/style rules reachable via `simt rules show <id>`. |
235
- | **Custom stack or repo-specific house rules** | An ecosystem with no built-in profile is still adopted with `core` rules. Add repo-local profiles in `.simt-dev/profiles/<id>.md` with `simt-id`, `simt-title`, `simt-applies`, and `simt-priority` (`hard`, `command`, `convention`, `style`); local profiles project alongside or shadow central ones. Declare custom gates under `gates.commands` and placeholders under `substitutions` in `simt.json`. |
299
+ | **Custom stack or repo-specific house rules** | An ecosystem with no built-in profile is still adopted with `core` rules. Add repo-local profiles in `.simt/profiles/<id>.md` with `simt-id`, `simt-title`, `simt-applies`, and `simt-priority` (`hard`, `command`, `convention`, `style`); local profiles project alongside or shadow central ones. Declare custom gates under `gates.commands` and placeholders under `substitutions` in `simt.json`. |
236
300
  | **Single-language or polyglot monorepo** (`package.json` `workspaces`, `pnpm-workspace.yaml`, `turbo.json`, `Cargo.toml` `[workspace]`, `go.work`, `settings.gradle`, `settings.gradle.kts`) | `simt init --yes` and `simt sync --yes` discover every workspace member and write both the root `AGENTS.md` and nested `<package>/AGENTS.md` projections carrying only the profiles specific to each package. Set `gates.cwd` in `simt.json` for gates that execute inside a sub-package. Measure nested context load with `simt harness loaders --at <package-dir>`, and use `simt gate --changed` to run only tests for modified packages. |
237
- | **Multi-repository workspace / estate** (sibling checkouts under a workspace directory or `SIMT_DEV_WORKSPACE`) | Run `simt compare` across the workspace root to inspect adoption, drift, pinned SHAs, hook routing, most-violated rules, noisy/slow gates (`NOISY-GATE`), zero-value rules, and proposed ADR drafts. Run `simt tokens --repos` and `simt shared plan`, then `simt shared install --yes` to move rules repeated across repos into user-level harness files (`~/.claude/CLAUDE.md`, `~/.codex/AGENTS.md`, etc.). Vendor shared rule packs in `.simt-dev/packs/<name>/` pinned by digest in `simt.json`, and upgrade the entire estate atomically with `simt upgrade --yes`. |
301
+ | **Multi-repository workspace / estate** (sibling checkouts under a workspace directory or `SIMT_DEV_WORKSPACE`) | Run `simt compare` across the workspace root to inspect adoption, drift, pinned SHAs, hook routing, most-violated rules, noisy/slow gates (`NOISY-GATE`), zero-value rules, and proposed ADR drafts. Run `simt tokens --repos` and `simt shared plan`, then `simt shared install --yes` to move rules repeated across repos into user-level harness files (`~/.claude/CLAUDE.md`, `~/.codex/AGENTS.md`, etc.). Vendor shared rule packs in `.simt/packs/<name>/` pinned by digest in `simt.json`, and upgrade the entire estate atomically with `simt upgrade --yes`. |
238
302
  | **Repositories with external code-graph MCP servers** | Declare external stdio MCP servers under `federation` in `simt.json` (`{ name, command, args, tool, argument, languages, timeoutMs }`). `simt brief "<task>"` and the MCP `federation_query` tool ask each server one bounded structural question and cite the server name in `FEDERATION`. |
239
- | **Offline or local-model-assisted workflows** (Ollama, `llama-server`, LM Studio, MLX) | By default, `simt` uses zero models and opens zero sockets (`ADR-075`, `ADR-090`). To opt in, add `local: { chat: { url, model }, embed: { url, model } }` in `simt.json` pointing at `http://127.0.0.1:<port>/v1`. Verify with `simt local doctor`, and build/query SQLite vector embeddings with `simt index --embed` and `simt map --embed` (non-loopback URLs are refused without `--allow-remote`). |
303
+ | **Offline or local-model-assisted workflows** (Ollama, `llama-server`, LM Studio, MLX) | By default, `simt` uses zero models and opens zero sockets (`ADR-075`, `ADR-090`). To opt in, add `local: { chat: { url, model }, embed: { url, model } }` in `simt.json` pointing at `http://127.0.0.1:<port>/v1`. Each role accepts an optional `apiKey` (sent as `Authorization: Bearer <key>`) for servers that require a token. Verify with `simt local doctor`, and build/query SQLite vector embeddings with `simt index --embed` and `simt map --embed` (non-loopback URLs are refused without `--allow-remote`). |
240
304
  | **Rule, skill, and benchmark maintainers** | Lint skills with `simt graph lint --spec agentskills`, compile and split > 5,000-token skills with `simt skills compile`, and score the resolver with `simt skills eval`. Mine git history tasks with `simt bench mine --yes`, run arms with `simt bench run --yes`, compare arms with `simt bench report --compare bare kernel`, run leave-one-out ablation with `simt bench ablate --yes`, run compliance probes with `simt bench probes --yes`, and lock winning `simt-variants` wordings with `simt bench variants --yes`. |
241
305
 
242
306
  ## Module and MCP reference
@@ -256,13 +320,9 @@ returns a JSON object with `task`, `commands`, `files`, `tests`, `decisions`, `s
256
320
 
257
321
  ## Where the plan finishes
258
322
 
259
- Slices `R01`–`R40` are complete and shipped in `0.5.0`. The remaining queued slices
260
- (`R41`–`R55`) finish packaging and the small ergonomics:
323
+ Slices `R01`–`R45` are complete and shipped in `0.6.0`. The remaining queued slices are the
324
+ small ergonomics:
261
325
 
262
- - **Packaging and declarative harness data (`R41`–`R45`).** MCP 2026-07-28 revision
263
- tightening, declarative harness adapter files with provenance, single-command plugin
264
- packaging, a single-file binary alongside npm/PyPI/Homebrew, and benchmark-measured rule
265
- packs.
266
326
  - **The small doors (`R46`–`R55`).** `simt explain <CODE>` for every finding code, safe
267
327
  idempotent `simt doctor --fix`, a `< 50 ms` `simt status --line`, a write journal with
268
328
  `simt undo`, `--diff` on every writer, shell completions, one cross-harness ignore list
@@ -274,47 +334,6 @@ budgeted summaries with line citations, `simt review` proposes an advisory ADR-0
274
334
  verdict over the staged diff, and `simt brief` carries model/effort advice from bench
275
335
  history. All remain off until named and propose rather than write projected text.
276
336
 
277
- ## Install
278
-
279
- Requires **Node >= 22.20.0**. The current published release is **0.5.0**. Pin it.
280
- The tool writes a content digest into the files it generates, and an unpinned upgrade
281
- changes them under you.
282
-
283
- ```sh
284
- npm i -D simt-dev@0.5.0
285
-
286
- # or globally
287
- npm i -g simt-dev@0.5.0
288
-
289
- # or once, with no install
290
- npx -y simt-dev@0.5.0 check
291
- ```
292
-
293
- ### From PyPI
294
-
295
- The wheel carries the same bytes as the npm tarball. It does not fetch them from npm
296
- at install time.
297
-
298
- ```sh
299
- pip install simt-dev
300
-
301
- # when this machine has no node
302
- pip install "simt-dev[node]"
303
- ```
304
-
305
- `simt` is the command on both channels. Node is resolved in this order, and the first
306
- one that answers wins: `SIMT_DEV_NODE`, the node bundled with the `node` extra, then
307
- `node` on `PATH`.
308
-
309
- ### From Homebrew
310
-
311
- ```sh
312
- brew install QuazarMedia/homebrew-tap/simt-dev
313
- ```
314
-
315
- The formula depends on `node`, so the command works on a machine that has never had
316
- Node installed.
317
-
318
337
  ## What it writes
319
338
 
320
339
  | Path | What |
@@ -344,11 +363,11 @@ These are caches, gitignored, and rebuildable. They are not repository content:
344
363
 
345
364
  | Path | What |
346
365
  | --- | --- |
347
- | `.simt-dev/index.sqlite` | files, symbols, import/call/test/governs/cochange edges, FTS5 `fts` tokens across `code`, `docs`, `skills`, `decisions` and `lessons`, and optional `embeddings` vector blobs. JSON-lines at `.simt-dev/index.jsonl` when this Node cannot open `node:sqlite` with FTS5 |
348
- | `.simt-dev/ledger.jsonl` | one line per gate or test the dispatcher actually ran, against one git tree, plus normalised failure signatures |
349
- | `.simt-dev/lessons.jsonl` | deterministic lesson candidates captured from fail-to-fix pairs, reverts, `BLOCK` reviews, and hook denials |
350
- | `.simt-dev/bench/` | mined tasks (`tasks/`), test patch vault (`vault/`), and benchmark runs (`runs/`) |
351
- | `.simt-dev/proposals/` | non-authoritative skill description proposals with `{model, promptHash, digest}` provenance |
366
+ | `.simt/index.sqlite` | files, symbols, import/call/test/governs/cochange edges, FTS5 `fts` tokens across `code`, `docs`, `skills`, `decisions` and `lessons`, and optional `embeddings` vector blobs. JSON-lines at `.simt/index.jsonl` when this Node cannot open `node:sqlite` with FTS5 |
367
+ | `.simt/ledger.jsonl` | one line per gate or test the dispatcher actually ran, against one git tree, plus normalised failure signatures |
368
+ | `.simt/lessons.jsonl` | deterministic lesson candidates captured from fail-to-fix pairs, reverts, `BLOCK` reviews, and hook denials |
369
+ | `.simt/bench/` | mined tasks (`tasks/`), test patch vault (`vault/`), and benchmark runs (`runs/`) |
370
+ | `.simt/proposals/` | non-authoritative skill description proposals with `{model, promptHash, digest}` provenance |
352
371
 
353
372
  `simt shared install --yes` writes a user-level copy of the rules a workspace
354
373
  repeats, into the file that harness documents, and records the ids on each
@@ -2,6 +2,19 @@
2
2
 
3
3
  Status: **accepted** · 2026-10-11
4
4
 
5
+ > Ruling 7's re-run mechanics are superseded by
6
+ > [ADR-097](ADR-097-the-channels-publish-by-hand-and-the-version-rides-in-as-an-input.md):
7
+ > the two channel jobs moved to a manual `workflow_dispatch` workflow whose `version`
8
+ > input dissolves the branch-vs-tag failure recorded below. The ordering ruling —
9
+ > npm live before either channel publishes — stands.
10
+
11
+ > Ruling 5's artefact choice and the Homebrew half of ruling 7 are superseded by
12
+ > [ADR-098](ADR-098-the-fourth-channel-is-one-file-and-it-needs-no-toolchain.md): the
13
+ > formula now downloads the release binary, not the npm tarball, and the Homebrew job's
14
+ > precondition is that those assets exist rather than that the npm version is live. The
15
+ > PyPI half of ruling 7 — npm live before the wheel publishes — stands, as does ruling
16
+ > 5's refusal of `std_npm_args`.
17
+
5
18
  Affects: `pyproject.toml`, `python/simt_dev/`, `Formula/simt-dev.rb`,
6
19
  `.github/workflows/publish.yml`, `scripts/release-formula.mjs`,
7
20
  `scripts/verify-wheel.mjs`, `test/packaging.test.mjs`
@@ -1,6 +1,6 @@
1
1
  # ADR-059 — The version a repository runs is the one it pinned, and the gate refuses otherwise
2
2
 
3
- Status: **accepted** · 2026-10-12 · affects all repos
3
+ Status: **accepted** · 2026-10-12 · affects all repos · ruling 3's no-npm install and remedy superseded by [ADR-098](ADR-098-the-fourth-channel-is-one-file-and-it-needs-no-toolchain.md)
4
4
 
5
5
  ## Context
6
6
 
@@ -0,0 +1,71 @@
1
+ # ADR-097 — the channels publish by hand, and the version rides in as an input
2
+
3
+ Status: **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
4
+
5
+ **Affects:** `.github/workflows/publish.yml`,
6
+ `.github/workflows/publish-channels.yml`, `scripts/publish-local.mjs`,
7
+ `docs/decisions/ADR-058-one-tool-three-channels-and-the-order-is-forced.md`
8
+
9
+ ## Context
10
+
11
+ ADR-058 ruled the three channels and the order between them — npm first, PyPI and
12
+ Homebrew only once the npm version is live — and its ruling 7 also pinned the
13
+ mechanics: the two channel jobs lived inside `publish.yml`, next to the staging job,
14
+ and a maintainer re-ran them through "Re-run failed jobs" once `npm stage approve` had
15
+ landed. The same ruling recorded why no `workflow_dispatch` could replace that:
16
+ on a dispatch, `github.ref_name` is the branch, and the version guard would compare a
17
+ tag against a branch.
18
+
19
+ Running it that way taught something the ruling did not predict. The channel jobs fail
20
+ on every release **by design** — the stage is not approvable until a human decides, so
21
+ a healthy release hours in is a release whose run is red. A run that is red when
22
+ nothing is wrong is the argument ADR-058 itself used against a gate that is red on the
23
+ day it lands: it teaches the reader to ignore red. And the only supported recovery is
24
+ re-running part of an old run, an action buried in the run page's retry menu rather
25
+ than in anything a maintainer can name.
26
+
27
+ The branch problem stays, but it points at a different fix. A dispatch cannot carry a
28
+ tag in `ref_name`, but it doesn't have to: the version is one string, it is already
29
+ written in exactly one place (`package.json`), and the maintainer who just approved
30
+ the stage knows it — they typed or copied it to get here.
31
+
32
+ ## Ruling
33
+
34
+ 1. **`publish.yml` owns npm staging and stops there.** The tag workflow keeps the
35
+ version guard and the staging job, and no longer carries a job designed to sit red
36
+ for hours. A green publish run means "staged", and nothing in it promises more.
37
+
38
+ 2. **The channels run from `.github/workflows/publish-channels.yml`, a
39
+ `workflow_dispatch` whose `version` input is the release (e.g. `0.5.0`).** The
40
+ version is the *input*, not the ref, which is what dissolves the branch-vs-tag
41
+ failure ADR-058 recorded: the input travels into the guard, and the guard refuses
42
+ to run unless tag `v<version>` exists on the default branch's history and its
43
+ checkout's `package.json` agrees with it. A dispatch cannot guess; the input makes
44
+ guessing impossible.
45
+
46
+ 3. **The ordering ruling is unchanged.** Both channel jobs still begin with the check
47
+ that `npm view` serves the version (PyPI) and that the tarball is downloadable
48
+ (Homebrew), and both still name the remedy — `npm stage approve <stage-id>` —
49
+ when it is not. What moved is who runs the steps and how the version travels;
50
+ what did not move is that npm is live before either channel publishes.
51
+
52
+ 4. **The channel jobs keep their identity unchanged.** PyPI still publishes with
53
+ trusted publishing against the `pypi` environment; Homebrew still audits, renders
54
+ and pushes the formula to the tap. Nothing about the artefacts changed, so ADR-058
55
+ rulings 1–6 read as written.
56
+
57
+ ## Consequences
58
+
59
+ A maintainer's release tour is two named actions instead of one retry:
60
+
61
+ ```
62
+ npm stage approve <stage-id>
63
+ gh workflow run publish-channels.yml -f version=<VER>
64
+ ```
65
+
66
+ Red is honest again: a red channel run means a channel defect, and the release page
67
+ carries no run whose expected end state is red. `scripts/publish-local.mjs` keeps its
68
+ role as the not-CI twin — it mirrors `publish.yml` for npm and
69
+ `publish-channels.yml` for the two channels, and its header now says exactly that,
70
+ because a twin of a file whose name no longer holds what it did is a comment that
71
+ misdescribes the artefact (the estate's own rule against decoration that lies).
@@ -0,0 +1,149 @@
1
+ ---
2
+ simt-affects: ["scripts/build-sea.mjs", "src/distribute/release.mjs", "src/locate.mjs", "src/package-version.mjs", "src/hooks/shim.mjs", "src/ci/workflow.mjs", ".github/workflows/publish.yml", ".github/workflows/publish-channels.yml", "Formula/simt-dev.rb", "scripts/release-formula.mjs", "scripts/publish-local.mjs", ".gitignore", "test/binary.test.mjs", "test/ci.test.mjs", "test/packaging.test.mjs"]
3
+ simt-supersedes: [{ adr: "ADR-058", ruling: 5 }, { adr: "ADR-058", ruling: 7, kind: "narrowed" }, { adr: "ADR-059", ruling: 3, kind: "superseded" }, { adr: "ADR-097", ruling: 3, kind: "narrowed" }]
4
+ ---
5
+
6
+ # ADR-098 — The fourth channel is one file, and it needs no toolchain
7
+
8
+ Status: **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)
9
+
10
+ Affects: `scripts/build-sea.mjs`, `src/distribute/release.mjs`, `src/locate.mjs`,
11
+ `src/hooks/shim.mjs`, `src/ci/workflow.mjs`, `.github/workflows/publish.yml`,
12
+ `.github/workflows/publish-channels.yml`, `Formula/simt-dev.rb`,
13
+ `scripts/release-formula.mjs`, `scripts/publish-local.mjs`, `test/binary.test.mjs`
14
+
15
+ ## Context
16
+
17
+ Review 0002's S35 ruled that no estate repository and no CI runner may be unable to
18
+ obtain this tool, and ADR-059 shipped its first half: the remedy and the consumer CI
19
+ step branch by repository kind. The half that did not ship is the one this record
20
+ settles. For a repository without npm — a Gradle build, a Connect IQ project — every
21
+ channel that existed needed a toolchain the repository does not have: the npm tarball
22
+ needs npm, the wheel needs a Python, and the formula, until this record, declared
23
+ `depends_on "node"` and so installed a command that could not start without one. The CI
24
+ step that stood in for the missing channel ran `npm i -g simt-dev@<v>`, which needs the
25
+ npm the repository kind is defined by not having. The one repository where the gate had
26
+ no toolchain already was also the one where obtaining the tool required installing one.
27
+
28
+ Two properties of the package made the alternative cheap. It has zero runtime
29
+ dependencies and no build step (ADR-012), so "packaging" is copying ~1.3 MB of text
30
+ around a pinned runtime; and the text reads its own shipped files — `rules/`,
31
+ `templates/`, `docs/decisions/` — relative to `import.meta.url`, so it needs real files
32
+ at real paths, not a module graph. Node's single-executable-application support embeds
33
+ a script in an official node binary, and postject injects that blob into any target's
34
+ copy of one; the missing half was a payload that materializes as a tree.
35
+
36
+ ## Ruling
37
+
38
+ 1. **A fourth artefact is released: one file per target, carrying the same payload the
39
+ npm tarball ships.** `scripts/build-sea.mjs` collects exactly `package.json`'s
40
+ `files` set plus `package.json` itself, and wraps it in a pinned official node base
41
+ at the version `engines` names. The targets are `darwin-arm64`, `darwin-x64` and
42
+ `linux-x64` — the set the one-line remedy and the consumer CI step can name — and a
43
+ platform outside it gets a loud 404, never a silent install of nothing. Each asset is
44
+ published to the GitHub release beside its `.sha256`, with a build-provenance
45
+ attestation binding the digest to the tag workflow's OIDC identity.
46
+
47
+ 2. **The payload materializes as real files before the tool runs.** The SEA entry is a
48
+ small CommonJS bootstrap (Node 22's SEA host runs the entry as CJS, whatever the
49
+ entry's own shape) that writes the embedded file set into
50
+ `${XDG_CACHE_HOME:-$HOME/.cache}/simt-dev/<node version>/<digest>/` and then imports
51
+ `bin/simt.mjs` from there. The digest is a hash of the canonical payload, so one
52
+ directory per payload, written to a staging directory and renamed into place, with a
53
+ marker file making a second run a no-op. Real files at real paths are what keep every
54
+ `import.meta.url`-relative read in `src/` working unmodified; a bootstrap that inlined
55
+ the tool instead would have to re-implement each one.
56
+
57
+ 3. **Determinism is the release contract, and the release checks it.** The same tree
58
+ builds byte-identical assets on any host: the node base is pinned, the SEA config uses
59
+ relative paths so no checkout path lands in the blob, and darwin binaries are ad-hoc
60
+ codesigned with a fixed identifier (without one, codesign derives the identifier from
61
+ the file path, which is what made two checkouts differ). Because of that, the tag
62
+ workflow re-builds each asset and fails the release when its checksum is not the one
63
+ `Formula/simt-dev.rb` in the tagged tree promises — the equality is checked, not
64
+ assumed.
65
+
66
+ 4. **Homebrew's artefact becomes the binary (supersedes ADR-058 ruling 5).** The formula
67
+ carries one `url`/`sha256` pair per target, selected by OS and CPU at install time,
68
+ and no longer declares `depends_on "node"`: the binary is the runtime. The "not
69
+ `std_npm_args`" half of ADR-058 ruling 5 stands unchanged — the formula still does no
70
+ build-time fetch of a package manager. `scripts/release-formula.mjs` patches each
71
+ pair by content, one url and the sha256 line that follows it, and refuses a formula
72
+ whose layout has drifted rather than patching blind.
73
+
74
+ 5. **The order each channel waits on is what that channel downloads (narrows ADR-058
75
+ ruling 7 and ADR-097 ruling 3).** PyPI still begins by checking that `npm view` serves
76
+ the version, because the npm channel is the one a maintainer has to approve by hand.
77
+ Homebrew now begins by checking that the release assets exist, because the formula
78
+ downloads those — a formula naming a tag whose binaries were never built installs a
79
+ 404 just as surely as one naming an unapproved npm version. The ordering rule
80
+ survives; only its second premise moved.
81
+
82
+ 6. **The repository's own copy is checked before `PATH`, and the version pin still
83
+ decides.** The locator resolves `$SIMT_DEV_BIN`, then `node_modules`, then
84
+ `.simt/bin/simt`, then `PATH` — so the bytes a repository downloaded are the bytes its
85
+ gate runs, and a machine-wide install cannot shadow them. The dispatcher's version
86
+ comparison (ADR-059 rulings 1 and 2) is unchanged and still refuses a stale binary,
87
+ whatever path it was found by.
88
+
89
+ 7. **A repository without npm installs the binary with one command, and its CI needs no
90
+ Node (supersedes ADR-059 ruling 3's no-npm remedy).** The remedy the shim prints for
91
+ such a repository is one line — `curl` the asset for this machine into `.simt/bin` and
92
+ `chmod` it — with the platform derived at run time from `uname`, because the remedy is
93
+ committed text that a colleague on another machine will read. The generated consumer
94
+ workflow downloads the same asset, verifies it against the published `.sha256`, and
95
+ installs it into `.simt/bin` with no `setup-node` step at all; the install fails loudly
96
+ rather than letting a gate pass that never started. The remedy stays one line a reader
97
+ copies (it carries no checksum — the dispatcher re-verifies the version, and the
98
+ automated CI install is where the published checksum is checked).
99
+
100
+ ## Consequences
101
+
102
+ - **The acceptance criterion is executable.** A Gradle fixture with every directory
103
+ containing `node` removed from `PATH` runs the release binary's `check` and its
104
+ `agent-hook` path, and the rendered hook shim hands over to `.simt/bin/simt` and
105
+ refuses when it is absent (`test/binary.test.mjs`). The test skips rather than fails on
106
+ a host that is not a release target or cannot reach `nodejs.org` for the base, because
107
+ a contributor's suite must still go green (ADR-031).
108
+ - **One `files` list feeds three artefacts.** The npm tarball, the wheel and the binary
109
+ payload all derive from `package.json`'s `files`, so the parity ADR-058 ruled survives
110
+ the new artefact instead of being restated in a third place.
111
+ - **The assets are large — one node runtime each.** That is the price of the only channel
112
+ a repository with no toolchain can use, and it is paid per release, not per install:
113
+ the payload is materialized once per node version and digest and reused after that.
114
+ - **The formula's committed checksums are the deterministic build of this tree.** Editing
115
+ anything in the payload changes them, and a release whose build disagrees with the
116
+ formula fails at tag time — which is the moment the maintainer still has the tree in
117
+ front of them. The committed values are never a placeholder: they are what the next
118
+ tag of this tree must hash to.
119
+ - **A repository that never downloads the binary is unaffected.** The locator's new
120
+ branch is one `[ -x … ]` test that is false when the path is absent, and the npm and
121
+ wheel channels do not consult it.
122
+
123
+ ## Rejected
124
+
125
+ - **Bun and Deno `compile`.** Evaluated on their documented behaviour, not measured. The
126
+ package's `engines` floor is Node 22.20.0, its index is `node:sqlite` (ADR-089), and
127
+ every latency figure this plan records was taken on that runtime. A binary compiled by
128
+ another runtime would be the one channel not running the runtime the other three run,
129
+ so the records would stop describing it, and the estate would have to re-measure per
130
+ channel to say anything true about it. Node's SEA keeps every channel on the declared
131
+ runtime, and its base is an official node build — the thing that can be pinned and
132
+ attested byte-for-byte. If the floor ever moves to another runtime, the bootstrap is
133
+ the only part that needs rewriting, because the payload is plain files.
134
+ - **A bundler that inlines the tool into one script.** The tool reads `rules/`,
135
+ `templates/` and its own decision records at run time; a bundle would have to
136
+ re-implement every `import.meta.url` root computation, and ADR-012's zero-dependency
137
+ rule leaves no bundler to depend on. The payload here is runtime-agnostic text; only
138
+ the wrapper is node-specific.
139
+ - **An archive plus a runner script.** Two files is an install step, and the channel
140
+ exists so that a repository with no toolchain gets one file that runs.
141
+ - **A checksum inside the one-line remedy.** The remedy is text a reader copies out of an
142
+ error message; adding a second verification command makes it no longer one line. The
143
+ download is TLS from the release, the dispatcher re-verifies the version the repository
144
+ pinned, and the automated install — the CI step, which no human is watching — is where
145
+ the published `.sha256` is checked before anything runs.
146
+ - **`npm i -g simt-dev@<v>` for a repository without npm** (what ADR-059 ruling 3 ruled).
147
+ The command needs npm, which is the one thing the repository kind is defined by not
148
+ having; that this was the remedy for the kind it could not serve is the defect S35
149
+ found.