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.
- package/CHANGELOG.md +83 -1
- package/README.md +46 -17
- package/agents/simt-agent.md +84 -0
- package/bin/simt.mjs +19 -6
- package/docs/decisions/ADR-009-skill-layout.md +1 -1
- package/docs/decisions/ADR-086-the-stable-prefix-comes-first-and-the-lock-records-both-digests.md +1 -1
- package/docs/decisions/ADR-100-a-staged-diff-is-the-last-gate-before-history-and-it-runs-inside-the-tool.md +94 -0
- package/docs/decisions/ADR-101-a-file-lease-belongs-to-one-session-and-the-pre-edit-hook-refuses-another.md +95 -0
- package/docs/decisions/ADR-102-the-two-queued-review-rows-are-closed-by-ruling.md +49 -0
- package/docs/decisions/ADR-103-co-projected-duplicates-merge-down.md +74 -0
- package/docs/decisions/ADR-104-the-pre-tool-use-policy-reads-the-command-the-way-a-shell-reads-it.md +76 -0
- package/docs/decisions/ADR-105-the-json-ceiling-shrinks-arrays-and-never-cuts-bytes.md +44 -0
- package/docs/decisions/ADR-106-the-harness-hook-runs-through-a-repo-shim.md +46 -0
- package/docs/decisions/ADR-107-an-install-can-name-one-harness-and-the-skipped-ones-are-named.md +51 -0
- package/docs/decisions/ADR-108-an-adoption-builds-the-index-and-a-command-refreshes-it-on-read.md +59 -0
- package/docs/decisions/ADR-109-simt-dev-is-its-own-consumer-and-the-guards-that-kept-it-from-being-one-are-retired.md +47 -0
- package/docs/decisions/ADR-110-the-lock-file-follows-the-manifest-name-and-the-old-name-is-read-until-the-next-write.md +43 -0
- package/docs/decisions/ADR-111-the-tool-ships-one-agent-rendered-per-harness-and-installs-it-with-init-and-a-command.md +75 -0
- package/docs/decisions/ADR-112-the-registry-is-what-simt-dev-ships-and-a-repo-owns-its-own-skills.md +81 -0
- package/docs/decisions/ADR-113-a-pre-push-that-carries-only-documentation-skips-the-test-gate-and-says-so.md +87 -0
- package/docs/decisions/ADR-114-the-bench-pairs-at-the-task-level-a-missing-cost-is-not-zero-and-an-unasked-cross-group-compare-is-refused.md +74 -0
- package/docs/decisions/ADR-115-the-index-stamps-its-schema-version-migrates-older-databases-in-place-and-serves-newer-ones-from-a-sidecar.md +69 -0
- package/docs/decisions/ADR-116-rust-support-reaches-the-example-path-the-indent-and-the-bench-runner.md +100 -0
- package/docs/decisions/README.md +53 -0
- package/package.json +4 -2
- package/rules/core.md +2 -2
- package/rules/mdc/preview-postflight.mdc +5 -6
- package/rules/mdc/test-gate.mdc +2 -0
- package/rules/stacks/android.md +10 -11
- package/rules/stacks/cloudflare-workers.md +1 -1
- package/rules/stacks/connectiq.md +2 -2
- package/rules/stacks/container.md +2 -0
- package/rules/stacks/dart.md +1 -1
- package/rules/stacks/deno.md +1 -1
- package/rules/stacks/dotnet.md +1 -1
- package/rules/stacks/elixir.md +1 -1
- package/rules/stacks/go.md +1 -1
- package/rules/stacks/jvm.md +4 -3
- package/rules/stacks/node.md +2 -2
- package/rules/stacks/python.md +7 -6
- package/rules/stacks/react-native.md +3 -5
- package/rules/stacks/react-web.md +9 -9
- package/rules/stacks/ruby.md +1 -1
- package/rules/stacks/rust.md +1 -1
- package/rules/stacks/swift.md +1 -1
- package/rules/stacks/typescript.md +2 -2
- package/rules/stacks/uv.md +3 -2
- package/skills/simt-commands/SKILL.md +70 -0
- package/src/advice/advice.mjs +7 -3
- package/src/agenthooks/decide.mjs +238 -15
- package/src/agenthooks/opencode.mjs +72 -0
- package/src/agenthooks/policy.mjs +81 -5
- package/src/agenthooks/prompt.mjs +6 -2
- package/src/agenthooks/render.mjs +186 -19
- package/src/agenthooks/shell.mjs +523 -0
- package/src/agenthooks/shim.mjs +55 -0
- package/src/agents/render.mjs +277 -0
- package/src/argv.mjs +62 -9
- package/src/bench/ablate.mjs +31 -14
- package/src/bench/mine.mjs +48 -1
- package/src/bench/probes.mjs +5 -6
- package/src/bench/report.mjs +243 -80
- package/src/bench/run.mjs +12 -11
- package/src/bench/variants.mjs +112 -40
- package/src/brief/assemble.mjs +6 -0
- package/src/ci/workflow.mjs +16 -7
- package/src/commands/agent.mjs +206 -0
- package/src/commands/agenthook.mjs +133 -17
- package/src/commands/bench.mjs +2 -1
- package/src/commands/ci.mjs +28 -3
- package/src/commands/compare.mjs +8 -7
- package/src/commands/completions.mjs +27 -0
- package/src/commands/decide.mjs +20 -6
- package/src/commands/doctor.mjs +367 -0
- package/src/commands/evidence.mjs +13 -3
- package/src/commands/explain.mjs +52 -0
- package/src/commands/gate.mjs +85 -19
- package/src/commands/graph.mjs +18 -5
- package/src/commands/help.mjs +96 -26
- package/src/commands/hooks.mjs +258 -67
- package/src/commands/ignore.mjs +152 -0
- package/src/commands/impact.mjs +4 -2
- package/src/commands/index.mjs +69 -102
- package/src/commands/init.mjs +239 -37
- package/src/commands/lease.mjs +304 -0
- package/src/commands/lessons.mjs +77 -9
- package/src/commands/map.mjs +33 -18
- package/src/commands/mcp.mjs +2 -2
- package/src/commands/pack.mjs +9 -0
- package/src/commands/plan.mjs +13 -1
- package/src/commands/plugin.mjs +17 -2
- package/src/commands/prbody.mjs +45 -0
- package/src/commands/repoindex.mjs +4 -0
- package/src/commands/rules.mjs +219 -105
- package/src/commands/shared.mjs +36 -5
- package/src/commands/skills.mjs +329 -206
- package/src/commands/skillscompile.mjs +8 -7
- package/src/commands/skillsinstall.mjs +112 -30
- package/src/commands/slice.mjs +712 -0
- package/src/commands/status.mjs +62 -0
- package/src/commands/sync.mjs +146 -36
- package/src/commands/tests.mjs +5 -2
- package/src/commands/undo.mjs +114 -0
- package/src/commands/upgrade.mjs +103 -9
- package/src/commands/why.mjs +10 -4
- package/src/compare/hooksstate.mjs +23 -1
- package/src/compare/learn.mjs +82 -57
- package/src/compare/parallel.mjs +50 -0
- package/src/compare/row.mjs +26 -1
- package/src/completions.mjs +196 -0
- package/src/decisions/dir.mjs +84 -0
- package/src/decisions/index.mjs +109 -11
- package/src/diff.mjs +254 -0
- package/src/distill/distill.mjs +3 -1
- package/src/ecosystem/node-runner.mjs +144 -0
- package/src/explain.mjs +270 -0
- package/src/federation/config.mjs +44 -0
- package/src/graph/load.mjs +52 -24
- package/src/hooks/capability.mjs +14 -1
- package/src/hooks/dispatcher.mjs +441 -9
- package/src/hooks/gates.mjs +14 -3
- package/src/hooks/resolve.mjs +23 -3
- package/src/hooks/shim.mjs +12 -7
- package/src/identity.mjs +67 -14
- package/src/ignore.mjs +133 -0
- package/src/index/extract.mjs +192 -11
- package/src/index/persist.mjs +372 -55
- package/src/index/store.mjs +263 -60
- package/src/index/tests.mjs +6 -35
- package/src/journal.mjs +545 -0
- package/src/kernel/card.mjs +37 -12
- package/src/kernel/checks.mjs +44 -9
- package/src/kernel/variants.mjs +27 -16
- package/src/lease.mjs +195 -0
- package/src/ledger/index.mjs +91 -25
- package/src/mcp/resources.mjs +5 -3
- package/src/mcp/server.mjs +86 -13
- package/src/mcp/tools.mjs +340 -264
- package/src/memory/decide.mjs +98 -38
- package/src/memory/lessons.mjs +180 -33
- package/src/name.mjs +9 -0
- package/src/output/index.mjs +82 -12
- package/src/package-version.mjs +49 -1
- package/src/packs/manage.mjs +13 -4
- package/src/plan/index.mjs +37 -8
- package/src/plan/template.mjs +27 -5
- package/src/plugins.mjs +33 -8
- package/src/prbody.mjs +253 -0
- package/src/projection/baselines.mjs +1 -1
- package/src/projection/disclosure.mjs +2 -2
- package/src/projection/loaders.mjs +41 -1
- package/src/projection/lock.mjs +21 -14
- package/src/projection/manifest.mjs +82 -21
- package/src/projection/markers.mjs +43 -3
- package/src/projection/mdc.mjs +34 -7
- package/src/projection/project.mjs +162 -18
- package/src/projection/rules.mjs +54 -3
- package/src/projection/shared.mjs +4 -1
- package/src/projection/surfaces.mjs +9 -1
- package/src/repo-root.mjs +69 -0
- package/src/resolve/body.mjs +4 -0
- package/src/resolve/fingerprint.mjs +5 -0
- package/src/resolve/local.mjs +58 -0
- package/src/review/critic.mjs +4 -2
- package/src/secrets.mjs +374 -0
- package/src/state.mjs +76 -0
- package/src/status.mjs +157 -0
- package/src/toolwords.mjs +5 -0
- package/src/tsconfig.mjs +39 -99
- 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.
|
package/docs/decisions/ADR-104-the-pre-tool-use-policy-reads-the-command-the-way-a-shell-reads-it.md
ADDED
|
@@ -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.
|
package/docs/decisions/ADR-107-an-install-can-name-one-harness-and-the-skipped-ones-are-named.md
ADDED
|
@@ -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.
|
package/docs/decisions/ADR-108-an-adoption-builds-the-index-and-a-command-refreshes-it-on-read.md
ADDED
|
@@ -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.
|
package/docs/decisions/ADR-112-the-registry-is-what-simt-dev-ships-and-a-repo-owns-its-own-skills.md
ADDED
|
@@ -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.
|