simt-dev 0.7.0 → 0.8.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 +191 -870
- package/README.md +188 -213
- package/bin/simt.mjs +19 -55
- package/docs/decisions/ADR-014-workspace-is-configuration.md +2 -2
- package/docs/decisions/ADR-025-a-baseline-is-a-region-in-a-file-the-repo-owns.md +1 -1
- package/docs/decisions/ADR-026-a-config-without-a-region-is-a-file-the-repo-owns.md +1 -1
- package/docs/decisions/ADR-027-the-one-gitattributes-line-that-is-neither-dangerous-nor-empty.md +1 -1
- package/docs/decisions/ADR-028-a-base-file-is-a-floor-so-the-disagreement-it-cannot-repair-becomes-a-check.md +1 -1
- package/docs/decisions/ADR-029-the-pins-a-repository-declares-are-its-own.md +1 -1
- package/docs/decisions/ADR-030-what-a-plan-does-not-ship-is-a-decision-with-a-record.md +1 -1
- package/docs/decisions/ADR-048-a-baseline-carries-only-keys-a-reader-honours.md +1 -1
- package/docs/decisions/ADR-082-the-tracker-override-is-a-manifest-key.md +1 -1
- package/docs/decisions/ADR-099-the-release-verifies-served-bytes-and-the-formula-mirrors-them.md +1 -1
- package/docs/decisions/ADR-117-the-darwin-release-builds-on-supported-macos-15-runners-and-a-re-run-is-idempotent.md +81 -0
- package/docs/decisions/ADR-118-the-bench-corpus-is-keyed-on-history-is-append-only-and-is-portable-as-a-sealed-document.md +91 -0
- package/docs/decisions/ADR-119-the-environment-variable-points-into-a-workspace-the-repo-declared.md +90 -0
- package/docs/decisions/ADR-120-a-pre-push-whose-tree-is-already-proven-green-skips-the-test-gate-on-that-evidence.md +96 -0
- package/docs/decisions/ADR-122-the-shims-move-into-the-state-directory-and-the-legacy-directory-removes-itself.md +94 -0
- package/docs/decisions/ADR-123-a-surplus-positional-is-not-refused-because-arity-is-not-in-the-source.md +57 -0
- package/docs/decisions/ADR-124-the-manifest-has-a-schema-in-the-tool-and-its-unknown-keys-stay-the-repositorys.md +64 -0
- package/docs/decisions/ADR-125-a-shared-context-belongs-to-a-read-and-a-writer-reads-directly.md +65 -0
- package/docs/decisions/ADR-126-the-mcp-surface-stays-a-reader-so-there-is-no-write-path-to-build.md +68 -0
- package/docs/decisions/ADR-127-the-mcp-tool-list-is-written-by-hand-and-roots-is-not-asked-for.md +65 -0
- package/docs/decisions/ADR-128-a-research-plan-is-a-plan-and-the-tracker-reads-it.md +42 -0
- package/ecosystems/bun.json +6 -0
- package/ecosystems/cargo.json +25 -0
- package/ecosystems/composer.json +6 -0
- package/ecosystems/dart.json +24 -0
- package/ecosystems/deno.json +21 -0
- package/ecosystems/devcontainers.json +6 -0
- package/ecosystems/docker-compose.json +6 -0
- package/ecosystems/docker.json +6 -0
- package/ecosystems/dotnet.json +25 -0
- package/ecosystems/elixir.json +23 -0
- package/ecosystems/gitsubmodule.json +6 -0
- package/ecosystems/go.json +25 -0
- package/ecosystems/gradle.json +13 -0
- package/ecosystems/maven.json +23 -0
- package/ecosystems/node.json +25 -0
- package/ecosystems/python.json +20 -0
- package/ecosystems/ruby.json +23 -0
- package/ecosystems/swift.json +25 -0
- package/ecosystems/terraform.json +6 -0
- package/ecosystems/uv.json +6 -0
- package/harnesses/antigravity.json +162 -0
- package/harnesses/claude.json +206 -0
- package/harnesses/codex.json +136 -0
- package/harnesses/copilot.json +88 -0
- package/harnesses/cursor.json +140 -0
- package/harnesses/deepseek.json +99 -0
- package/harnesses/gemini.json +122 -0
- package/harnesses/opencode.json +173 -0
- package/package.json +12 -2
- package/rules/core.md +7 -0
- package/rules/mdc/intake-questions.mdc +8 -0
- package/rules/mdc/slice-commit.mdc +57 -4
- package/src/advice/advice.mjs +3 -2
- package/src/agenthooks/prompt.mjs +5 -2
- package/src/agenthooks/render.mjs +86 -180
- package/src/agenthooks/shim.mjs +1 -1
- package/src/agents/render.mjs +15 -18
- package/src/api.mjs +31 -0
- package/src/argv.mjs +48 -3
- package/src/bench/ablate.mjs +3 -4
- package/src/bench/adapters.json +163 -105
- package/src/bench/adapters.mjs +466 -0
- package/src/bench/arms.mjs +333 -0
- package/src/bench/grade.mjs +33 -73
- package/src/bench/graders.mjs +42 -0
- package/src/bench/mine.mjs +121 -475
- package/src/bench/probe-ledger.mjs +149 -0
- package/src/bench/probes.catalog.mjs +918 -0
- package/src/bench/probes.mjs +79 -1064
- package/src/bench/render.mjs +111 -0
- package/src/bench/report.mjs +120 -962
- package/src/bench/run.mjs +185 -802
- package/src/bench/variants.mjs +4 -5
- package/src/bench/workspace.mjs +160 -0
- package/src/brief/assemble.mjs +48 -15
- package/src/ci/dependabot.mjs +25 -55
- package/src/ci/workflow.mjs +30 -7
- package/src/cli.mjs +45 -0
- package/src/commands/agent.mjs +4 -13
- package/src/commands/agenthook.mjs +70 -40
- package/src/commands/bench.mjs +191 -18
- package/src/commands/brief.mjs +5 -4
- package/src/commands/ci.mjs +13 -14
- package/src/commands/compare.mjs +42 -42
- package/src/commands/completions.mjs +2 -6
- package/src/commands/decide.mjs +4 -4
- package/src/commands/distill.mjs +4 -3
- package/src/commands/docs.mjs +88 -0
- package/src/commands/doctor.mjs +11 -7
- package/src/commands/evidence.mjs +6 -9
- package/src/commands/explain.mjs +0 -1
- package/src/commands/gate.mjs +26 -25
- package/src/commands/graph.mjs +2 -3
- package/src/commands/harness.mjs +6 -5
- package/src/commands/help.mjs +168 -19
- package/src/commands/hooks.mjs +119 -49
- package/src/commands/ignore.mjs +13 -8
- package/src/commands/impact.mjs +8 -15
- package/src/commands/index.mjs +318 -193
- package/src/commands/init-scan.mjs +90 -0
- package/src/commands/init.mjs +124 -105
- package/src/commands/lease.mjs +73 -25
- package/src/commands/lessons.mjs +10 -9
- package/src/commands/local.mjs +3 -3
- package/src/commands/map.mjs +24 -14
- package/src/commands/mcp.mjs +2 -18
- package/src/commands/pack.mjs +9 -6
- package/src/commands/plan.mjs +6 -5
- package/src/commands/plugin.mjs +2 -2
- package/src/commands/prbody.mjs +3 -3
- package/src/commands/repoindex.mjs +12 -11
- package/src/commands/review.mjs +2 -2
- package/src/commands/rules.mjs +37 -16
- package/src/commands/shared.mjs +14 -19
- package/src/commands/skills.mjs +33 -32
- package/src/commands/skillscompile.mjs +7 -8
- package/src/commands/skillsinstall.mjs +7 -6
- package/src/commands/slice.mjs +220 -74
- package/src/commands/spec.mjs +167 -0
- package/src/commands/status.mjs +27 -18
- package/src/commands/sync.mjs +27 -25
- package/src/commands/tests.mjs +18 -21
- package/src/commands/tokens.mjs +21 -26
- package/src/commands/undo.mjs +5 -15
- package/src/commands/upgrade.mjs +15 -7
- package/src/commands/why.mjs +15 -21
- package/src/compare/hooksstate.mjs +2 -6
- package/src/compare/learn.mjs +3 -1
- package/src/compare/row.mjs +0 -2
- package/src/context.mjs +64 -0
- package/src/decisions/index-doc.mjs +46 -0
- package/src/decisions/index.mjs +3 -2
- package/src/diff.mjs +1 -1
- package/src/distill/distill.mjs +14 -1
- package/src/distill/engine.mjs +5 -1
- package/src/docs/commands.mjs +65 -0
- package/src/ecosystem/records.mjs +219 -0
- package/src/explain.mjs +31 -0
- package/src/gates/index.mjs +385 -0
- package/src/graph/compile.mjs +8 -4
- package/src/graph/load.mjs +6 -2
- package/src/graph/spec.mjs +10 -8
- package/src/harness/records.mjs +143 -0
- package/src/hooks/capability.mjs +7 -5
- package/src/hooks/dispatcher.mjs +209 -60
- package/src/hooks/gates.mjs +10 -43
- package/src/hooks/resolve.mjs +4 -14
- package/src/hooks/shim.mjs +5 -2
- package/src/hooks/where.mjs +2 -7
- package/src/identity.mjs +31 -12
- package/src/index/coverage.mjs +58 -0
- package/src/index/extract.mjs +6 -16
- package/src/index/gate.mjs +1 -1
- package/src/index/govern.mjs +26 -9
- package/src/index/impact.mjs +4 -8
- package/src/index/map.mjs +14 -15
- package/src/index/open.mjs +173 -0
- package/src/index/persist.mjs +224 -57
- package/src/index/store.mjs +82 -39
- package/src/index/tests.mjs +2 -6
- package/src/journal.mjs +94 -20
- package/src/kernel/card.mjs +37 -63
- package/src/kernel/checks.mjs +4 -10
- package/src/lease.mjs +102 -7
- package/src/ledger/index.mjs +150 -14
- package/src/local/client.mjs +4 -6
- package/src/locate.mjs +33 -9
- package/src/manifest/schema.mjs +213 -0
- package/src/mcp/paginate.mjs +33 -0
- package/src/mcp/resources.mjs +7 -10
- package/src/mcp/schemas.mjs +512 -0
- package/src/mcp/server.mjs +119 -25
- package/src/mcp/tools-facts.mjs +326 -0
- package/src/mcp/tools-read.mjs +221 -0
- package/src/mcp/tools.mjs +4 -967
- package/src/measure/corpus.mjs +150 -0
- package/src/measure/metrics.mjs +236 -0
- package/src/measure/records.mjs +211 -0
- package/src/measure/session.mjs +3 -2
- package/src/measure/stats.mjs +538 -0
- package/src/measure/task-schema.mjs +125 -0
- package/src/measure/tasks.mjs +69 -0
- package/src/memory/decide.mjs +6 -15
- package/src/memory/lesson-text.mjs +61 -0
- package/src/memory/lessons.mjs +29 -82
- package/src/name.mjs +7 -0
- package/src/node-version.mjs +37 -0
- package/src/output/index.mjs +209 -10
- package/src/packs/manage.mjs +32 -12
- package/src/packs.mjs +20 -2
- package/src/plan/index.mjs +1 -1
- package/src/plugins.mjs +22 -16
- package/src/prbody.mjs +7 -11
- package/src/projection/loaders.mjs +7 -692
- package/src/projection/lock.mjs +11 -1
- package/src/projection/manifest.mjs +29 -468
- package/src/projection/mdc.mjs +7 -8
- package/src/projection/project.mjs +126 -221
- package/src/projection/region.mjs +561 -0
- package/src/projection/rules.mjs +20 -550
- package/src/projection/surfaces.mjs +43 -210
- package/src/projection/targets.mjs +5 -5
- package/src/repo-root.mjs +31 -0
- package/src/resolve/eval.mjs +2 -1
- package/src/resolve/fingerprint.mjs +19 -22
- package/src/resolve/resolve.mjs +1 -41
- package/src/review/critic.mjs +10 -13
- package/src/run.mjs +98 -0
- package/src/secrets.mjs +14 -22
- package/src/state/events.mjs +129 -0
- package/src/status-snapshot.mjs +233 -0
- package/src/status.mjs +2 -6
- package/src/sys/git.mjs +82 -0
- package/src/sys/proc.mjs +151 -0
- package/src/testing/parsers.mjs +265 -0
- package/src/testing/patches.mjs +23 -0
- package/src/testing/paths.mjs +49 -0
- package/src/testing/runners.mjs +186 -0
- package/src/testing/vault.mjs +68 -0
- package/src/text/frontmatter.mjs +49 -0
- package/src/text/tokens.mjs +52 -0
- package/src/toolwords.mjs +3 -0
- package/src/util/fs.mjs +20 -0
- package/src/util/sort.mjs +12 -0
- package/src/workspace.mjs +58 -11
- package/src/write/plan.mjs +82 -0
- package/templates/plan/tracker.json +1 -0
- package/docs/decisions/ADR-033-three-plan-rows-closed-by-decision-not-by-authoring.md +0 -82
- package/docs/decisions/ADR-102-the-two-queued-review-rows-are-closed-by-ruling.md +0 -49
- package/docs/decisions/README.md +0 -600
- package/src/bench/tasks.mjs +0 -36
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Status: accepted · 2026-10-09 · amends ADR-025 (withdraws ruling 8, corrects its context)
|
|
4
4
|
**Affects:** `src/projection/baselines.mjs`, `test/baselines.test.mjs`,
|
|
5
|
-
`docs/
|
|
5
|
+
`docs/research/0001-centralise-agent-rules.md`. Closes S30 of
|
|
6
6
|
`docs/reviews/0002-full-review-adoption-and-public-release.md`.
|
|
7
7
|
|
|
8
8
|
## Context
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ADR-082 — The tracker override is a manifest key, and init keeps what it did not write
|
|
2
2
|
|
|
3
|
-
Status: **accepted** · 2026-10-16 · supersedes ADR-081 rulings 1 and 2
|
|
3
|
+
Status: **accepted** · 2026-10-16 · supersedes ADR-081 rulings 1 and 2 · ruling 1 superseded by ADR-128
|
|
4
4
|
|
|
5
5
|
## Context
|
|
6
6
|
|
package/docs/decisions/ADR-099-the-release-verifies-served-bytes-and-the-formula-mirrors-them.md
CHANGED
|
@@ -5,7 +5,7 @@ simt-supersedes: [{ adr: "ADR-098", ruling: 3, kind: "narrowed" }]
|
|
|
5
5
|
|
|
6
6
|
# ADR-099 — the release verifies served bytes, and the formula mirrors them
|
|
7
7
|
|
|
8
|
-
Status: **accepted** · 2026-10-27 · narrows ADR-098 ruling 3
|
|
8
|
+
Status: **accepted** · 2026-10-27 · narrows ADR-098 ruling 3 · ruling 1 superseded by [ADR-117](ADR-117-the-darwin-release-builds-on-supported-macos-15-runners-and-a-re-run-is-idempotent.md)
|
|
9
9
|
|
|
10
10
|
Affects: `.github/workflows/publish.yml`, `scripts/build-sea.mjs`,
|
|
11
11
|
`scripts/verify-release-asset.mjs`, `Formula/simt-dev.rb`
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
simt-affects: [".github/workflows/publish.yml", "test/binary.test.mjs"]
|
|
3
|
+
simt-supersedes: [{ adr: "ADR-099", ruling: 1, kind: "superseded" }]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ADR-117 — the darwin release builds on supported macOS 15 runners, and a re-run is idempotent
|
|
7
|
+
|
|
8
|
+
Status: **accepted** · 2026-10-31 · supersedes ADR-099 ruling 1
|
|
9
|
+
|
|
10
|
+
## Context
|
|
11
|
+
|
|
12
|
+
The v0.7.0 release run `41215349142` reached the first binary release: the npm stage
|
|
13
|
+
succeeded, the release record was created, and `linux-x64` built. Both darwin jobs failed
|
|
14
|
+
in the same place — `macos-14`, the runner ADR-099 ruling 1 named, is deprecated, and
|
|
15
|
+
GitHub was running scheduled brownouts of that image ahead of its retirement.
|
|
16
|
+
|
|
17
|
+
The old matrix also used one label for two architectures. GitHub's runner-images table
|
|
18
|
+
now names `macos-14` as arm64 and `macos-14-large` as Intel, so "both targets on
|
|
19
|
+
`macos-14`" was not the native-runner arrangement ADR-099 claimed. The replacement has
|
|
20
|
+
to name the arm64 and Intel images explicitly.
|
|
21
|
+
|
|
22
|
+
The same run exposed a second release-path defect. `publish-npm` decided whether to skip
|
|
23
|
+
by asking the registry whether the version was published. A staged version is not
|
|
24
|
+
published, so re-running the workflow after every other job succeeded would try to stage
|
|
25
|
+
the same version again and fail on the duplicate. "Re-run all jobs" is the normal way to
|
|
26
|
+
recover a partially failed release, and it must not turn a completed stage into a red
|
|
27
|
+
job.
|
|
28
|
+
|
|
29
|
+
## Ruling
|
|
30
|
+
|
|
31
|
+
1. **Darwin targets build on the supported native macOS 15 images.**
|
|
32
|
+
`darwin-arm64` runs on `macos-15` and `darwin-x64` runs on `macos-15-intel`;
|
|
33
|
+
`linux-x64` stays on `ubuntu-latest`. These are the standard, non-deprecated labels
|
|
34
|
+
for the two architectures, not the larger-runner variants. ADR-099's per-host
|
|
35
|
+
determinism and upload-integrity claims are unchanged; only the label that provides
|
|
36
|
+
the native host moves.
|
|
37
|
+
|
|
38
|
+
2. **A re-run is idempotent for a stage that already succeeded.**
|
|
39
|
+
`publish-npm` checks the npm staging queue as well as the registry. A version that is
|
|
40
|
+
published *or* already staged sets `SKIP=true`, so the tests, the wheel check and
|
|
41
|
+
`npm stage publish` are not repeated. The staging check runs after npm 11 is
|
|
42
|
+
available, because `npm stage list` is the command that makes the queue visible. A
|
|
43
|
+
failed stage list is not treated as proof of absence: the job attempts the stage and
|
|
44
|
+
fails loudly rather than silently skipping a release that does not exist.
|
|
45
|
+
|
|
46
|
+
The release record, asset upload and formula mirror were already idempotent —
|
|
47
|
+
`create-release` edits an existing release, `gh release upload --clobber` replaces
|
|
48
|
+
assets, and `sync-formula` exits when the mirror already names the served bytes.
|
|
49
|
+
They stay that way. A re-run does not skip a build on the grounds that an asset with
|
|
50
|
+
the same name exists: the tag can have moved, and the asset must be built from the
|
|
51
|
+
tree the tag now names.
|
|
52
|
+
|
|
53
|
+
## Consequences
|
|
54
|
+
|
|
55
|
+
- A darwin failure is recovered on a runner GitHub supports, and the arm64 and Intel
|
|
56
|
+
assets are produced on the architectures they claim.
|
|
57
|
+
- "Re-run all jobs" after a successful stage is green: the stage job says the version is
|
|
58
|
+
already staged, the release record is refreshed in place, and the formula is compared
|
|
59
|
+
rather than overwritten.
|
|
60
|
+
- "Re-run failed jobs" remains the narrower recovery: it rebuilds only the targets that
|
|
61
|
+
failed, which is the path the v0.7.0 run needed.
|
|
62
|
+
- ADR-099 ruling 1 is superseded. Its ruling 2 (upload integrity) and ruling 3 (the
|
|
63
|
+
formula mirrors served bytes) still govern the release.
|
|
64
|
+
- `test/binary.test.mjs` reads the committed workflow and asserts the two darwin labels,
|
|
65
|
+
so a future edit that returns to a deprecated or wrong-architecture runner fails the
|
|
66
|
+
suite instead of a release.
|
|
67
|
+
|
|
68
|
+
## Rejected
|
|
69
|
+
|
|
70
|
+
- **`macos-latest` for arm64.** It moves with GitHub's newest stable image. A release
|
|
71
|
+
builder should change when this repository decides to change it, not when a label
|
|
72
|
+
migrates.
|
|
73
|
+
- **`macos-26`.** It is newer than the release needs, and no run has proven the pinned
|
|
74
|
+
Node base and `codesign` pipeline on it. Moving to it is a separate reviewed change.
|
|
75
|
+
- **`macos-14-large`.** It is the Intel half of a deprecated image, so it inherits the
|
|
76
|
+
brownout that caused this failure.
|
|
77
|
+
- **Skipping a build because the asset name already exists.** The tag this workflow runs
|
|
78
|
+
for can be moved, and an asset left from an earlier tree would then be served as the
|
|
79
|
+
new tag's binary. Rebuilding and clobbering keeps the bytes tied to the tagged tree.
|
|
80
|
+
- **Detecting a prior stage through the registry alone.** A stage is deliberately not
|
|
81
|
+
public, so the registry cannot answer whether it exists.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
simt-affects: ["src/bench/mine.mjs", "src/bench/corpus.mjs", "src/bench/grade.mjs", "src/argv.mjs", "src/commands/bench.mjs", "src/commands/index.mjs", "src/commands/help.mjs", "test/bench.test.mjs"]
|
|
3
|
+
simt-supersedes: []
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ADR-118 — the bench corpus is keyed on history, is append-only, and is portable as a sealed document
|
|
7
|
+
|
|
8
|
+
Status: **accepted** · 2026-10-31 · filed as review 0003 Q48
|
|
9
|
+
|
|
10
|
+
## Context
|
|
11
|
+
|
|
12
|
+
Review 0003 (Q48) verified four failures in the mined bench corpus:
|
|
13
|
+
|
|
14
|
+
- The vault was keyed on the absolute checkout path, so the 20 committed tasks
|
|
15
|
+
failed from every clone (`ENOENT …/vault/repo-ca1b5891/…/gold.patch`): a corpus
|
|
16
|
+
was unrunnable from any machine that was not the one that mined it.
|
|
17
|
+
- Re-mining silently replaced the corpus: `mineTasks(target 3)` deleted all 20
|
|
18
|
+
committed tasks and wrote 3 others, with no signal that anything was lost.
|
|
19
|
+
- The Gradle grader read one report file and the Maven grader read none, while
|
|
20
|
+
both runners write one JUnit XML *per test class* under a directory: both
|
|
21
|
+
graders graded on an empty parse.
|
|
22
|
+
- Code-read: 7-character ids with a fixed `-01` suffix can collide, and the
|
|
23
|
+
exclusion list and the per-test timeout were hardcoded, so a repository could
|
|
24
|
+
not declare its own.
|
|
25
|
+
|
|
26
|
+
## Ruling
|
|
27
|
+
|
|
28
|
+
1. **The vault is keyed on the repository's history, not its checkout path.**
|
|
29
|
+
The key is the checkout basename plus the root commit
|
|
30
|
+
(`git rev-list --max-parents=0 HEAD`), read synchronously like
|
|
31
|
+
`package-version.mjs`: a vault path that throws is worse than one that is
|
|
32
|
+
occasionally less specific. The root commit is identical in every clone and
|
|
33
|
+
survives remote renames; a root with no git history degrades to the path
|
|
34
|
+
digest, because a vault path must never fail because git is absent.
|
|
35
|
+
2. **Re-mining is append-only, and taking over the corpus is explicit.** Tasks
|
|
36
|
+
the current mine does not reproduce survive a re-mine; only `--replace`
|
|
37
|
+
deletes them. An id that already holds a task from a different
|
|
38
|
+
`sourceCommit` is a hard error naming `--replace`, not a silent overwrite —
|
|
39
|
+
guessing which corpus the reader wants is worse than refusing.
|
|
40
|
+
3. **New task ids are 12 hex characters plus `-01`.** The 20 committed tasks
|
|
41
|
+
keep their 7-character ids: the corpus is not regenerated to widen them, and
|
|
42
|
+
the collision check — not a wider prefix — is what makes a collision an
|
|
43
|
+
error.
|
|
44
|
+
4. **JUnit XML is read from the directory the runners write.**
|
|
45
|
+
`readReports(paths)` walks report directories recursively in sort order
|
|
46
|
+
(Gradle `build/test-results/test/`, Maven `target/surefire-reports/`), reads
|
|
47
|
+
single files, and skips missing paths. The miner and the grader clean
|
|
48
|
+
`reportPaths` before every run — a report left by a prior run would be
|
|
49
|
+
graded as this run's result — and read through `readReports`.
|
|
50
|
+
5. **Exclusion and timeout are repository-declared.** `simt.json`
|
|
51
|
+
`bench.mine.{exclude,timeoutMs}`; an explicit option wins, then the manifest,
|
|
52
|
+
then the default (`['test/pack.test.mjs']`, 5000 ms).
|
|
53
|
+
6. **The corpus is portable as a sealed document.** `bench export <corpus.json>`
|
|
54
|
+
and `bench import <corpus.json>` (in `src/bench/corpus.mjs`) carry the task
|
|
55
|
+
descriptors and both hidden patches for every task in a single file: tasks
|
|
56
|
+
sorted by id, canonical key-sorted JSON, base64 patches, no timestamp, and a
|
|
57
|
+
sha256 seal over everything but the seal. The seal is verified before a
|
|
58
|
+
single byte is trusted, and two exports of the same corpus are
|
|
59
|
+
byte-identical.
|
|
60
|
+
7. **The mine records where the corpus came from.** `.simt/bench/corpus.json`
|
|
61
|
+
carries `{minedFrom, target, simtVersion}` — no timestamp, so a re-mine at
|
|
62
|
+
the same HEAD is byte-identical.
|
|
63
|
+
|
|
64
|
+
## Consequences
|
|
65
|
+
|
|
66
|
+
- A `bench export` on the machine that mined a corpus and a `bench import` on
|
|
67
|
+
any other machine reproduce it exactly, hidden test patches included: the
|
|
68
|
+
committed corpus is runnable from a clone.
|
|
69
|
+
- 7-character and 12-character ids coexist in one corpus; the schema test
|
|
70
|
+
asserts the committed corpus keeps its 7-character ids and the miner asserts
|
|
71
|
+
12 for every new mine.
|
|
72
|
+
- `bench import` and `bench export` preview like every other write: without
|
|
73
|
+
`--yes` nothing is written, and a dry-run import writes nothing.
|
|
74
|
+
- The sealed document lives in its own module (`src/bench/corpus.mjs`);
|
|
75
|
+
`mine.mjs` is trimmed to the 1000-line ceiling so the slice adds no new
|
|
76
|
+
file-lines finding.
|
|
77
|
+
|
|
78
|
+
## Rejected
|
|
79
|
+
|
|
80
|
+
- **Keying the vault on the remote URL.** It breaks on a remote rename, and it
|
|
81
|
+
is a second mutable identity where the root commit is immutable and
|
|
82
|
+
clone-invariant.
|
|
83
|
+
- **Regenerating the committed corpus to 12-character ids.** It churns 20 task
|
|
84
|
+
files and their vault layout for a width only new mines need; the collision
|
|
85
|
+
check, not a wider prefix, is what protects the corpus.
|
|
86
|
+
- **A timestamp in the exported document.** It breaks byte-identity: two
|
|
87
|
+
exports of the same corpus differ, and a reader cannot tell content drift
|
|
88
|
+
from time drift.
|
|
89
|
+
- **Silent overwrite on re-mine.** The failure this ADR exists to fix: a corpus
|
|
90
|
+
is data, and a routine command deleting twenty tasks is a data loss wearing a
|
|
91
|
+
success exit code.
|
package/docs/decisions/ADR-119-the-environment-variable-points-into-a-workspace-the-repo-declared.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
simt-affects: ["src/workspace.mjs", "src/graph/load.mjs", "test/workspace.test.mjs", "simt.json"]
|
|
3
|
+
simt-supersedes: [{ adr: "ADR-014", ruling: 1, kind: "superseded" }]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ADR-119 — the environment variable points into a workspace the repo declared
|
|
7
|
+
|
|
8
|
+
Status: **accepted** · 2026-10-31 · supersedes ADR-014 ruling 1
|
|
9
|
+
|
|
10
|
+
## Context
|
|
11
|
+
|
|
12
|
+
ADR-014 made the workspace configuration, resolved in a fixed, reported order:
|
|
13
|
+
`--workspace`, then `SIMT_DEV_WORKSPACE`, then the user config file, then the sibling
|
|
14
|
+
heuristic. Its ruling 1 named the variable's purpose as "CI and one-off invocations".
|
|
15
|
+
|
|
16
|
+
A variable that is *exported* is not a one-off invocation. Measured: a developer whose
|
|
17
|
+
shell exports `SIMT_DEV_WORKSPACE` for a multi-repo workspace, working in a single repo
|
|
18
|
+
that declared no workspace, gets that repo treated as a member of the exported workspace
|
|
19
|
+
— `repo/path` skill sources resolve against it, `compare` discovers its siblings, and
|
|
20
|
+
`doctor` reports `env` as the source. The default case ADR-014 ruling 3 exists to keep
|
|
21
|
+
first-class — a single repo with no workspace — silently depends on ambient environment.
|
|
22
|
+
|
|
23
|
+
The asymmetry is the defect: the user config file is a *per-developer* fact, and an
|
|
24
|
+
exported variable is one too, but a workspace is a *per-repo* fact. Nothing in the
|
|
25
|
+
precedence list asked the repository whether it has a workspace at all.
|
|
26
|
+
|
|
27
|
+
## Ruling
|
|
28
|
+
|
|
29
|
+
### 1. The precedence gains the repository's own declaration, above the environment variable
|
|
30
|
+
|
|
31
|
+
Precedence, highest first:
|
|
32
|
+
|
|
33
|
+
1. `--workspace <path>` — an explicit flag always wins.
|
|
34
|
+
2. The repository's own manifest declaration — a `workspace` key in `simt.json` (or the
|
|
35
|
+
legacy `simt-dev.json`, read by the same reader every other command uses). An `off`
|
|
36
|
+
value declines exactly as it does from any other source, and the environment variable
|
|
37
|
+
cannot override a decline: a repo that said it has no workspace is not one.
|
|
38
|
+
3. `SIMT_DEV_WORKSPACE` — a value that names a directory is honoured **only where
|
|
39
|
+
the manifest declares a non-declining workspace**: the variable is a pointer INTO
|
|
40
|
+
a workspace the repo opted into, not one imposed on a repo that never declared
|
|
41
|
+
one. A value that declines is honoured from anywhere, because a decline points
|
|
42
|
+
at nothing and so needs no declaration to work.
|
|
43
|
+
4. The manifest's value — a relative path resolves against the manifest's directory, not
|
|
44
|
+
the process's, so `workspace: ".."` means the same thing from any subdirectory.
|
|
45
|
+
5. The user config file — unchanged, and honoured regardless of the declaration, because
|
|
46
|
+
it is a per-developer fact that predates the repo.
|
|
47
|
+
6. The sibling heuristic — unchanged: consulted last, accepted only when it verifies.
|
|
48
|
+
|
|
49
|
+
Every command that consults the workspace reports where it came from, as before; the
|
|
50
|
+
declaration introduces the source label `repo`.
|
|
51
|
+
|
|
52
|
+
### 2. A manifest that exists and does not parse is an error, and it names the file
|
|
53
|
+
|
|
54
|
+
Same shape as the unparseable-config error ADR-014 ruling 2 established: precedence
|
|
55
|
+
decides which *answer* wins, but a file that exists and does not parse is a defect in
|
|
56
|
+
the reader's setup, and hiding it because some other source outranked it would send
|
|
57
|
+
them looking in the wrong place.
|
|
58
|
+
|
|
59
|
+
### 3. The memoisation key names the directory whose manifest decides
|
|
60
|
+
|
|
61
|
+
`src/graph/load.mjs` memoised the resolution per flag, variable and config location.
|
|
62
|
+
The current directory's manifest now decides part of the answer, so the key names it;
|
|
63
|
+
a memo that cannot see the directory serves one caller's declaration to a later caller
|
|
64
|
+
standing in another repo.
|
|
65
|
+
|
|
66
|
+
## Consequences
|
|
67
|
+
|
|
68
|
+
This repository dogfoods the declaration: its own `simt.json` carries
|
|
69
|
+
`"workspace": ".."`. The resolved root is unchanged — the sibling heuristic already
|
|
70
|
+
verified the same directory — but the source label moves from `heuristic` to `repo`,
|
|
71
|
+
which is the point: the workspace this repo resolves is declared, not guessed.
|
|
72
|
+
|
|
73
|
+
An exported `SIMT_DEV_WORKSPACE` is inert in a repo that declared no workspace, which
|
|
74
|
+
is the measured case this record exists to fix. In a repo that did declare one, the
|
|
75
|
+
variable still overrides the declared value, so CI pinning a workspace for a known
|
|
76
|
+
multi-repo checkout is unchanged.
|
|
77
|
+
|
|
78
|
+
## Rejected
|
|
79
|
+
|
|
80
|
+
- **Honouring the variable only in CI.** The tool has no CI detector, and "CI" is not
|
|
81
|
+
a fact about the repository; a detector that is wrong in one direction reproduces
|
|
82
|
+
the measured defect, and wrong in the other breaks the one-off use the variable
|
|
83
|
+
exists for.
|
|
84
|
+
- **Making the declaration a precondition for the config file too.** The config file
|
|
85
|
+
is per-developer and works from any repo, including one that has no manifest yet;
|
|
86
|
+
requiring a declaration would break the single-repo reader with a config, which is
|
|
87
|
+
the audience ADR-014 ruling 3 protects.
|
|
88
|
+
- **Renaming the variable.** It is the documented pointer in every remedy string this
|
|
89
|
+
tool ships; renaming it moves the defect into the reader's muscle memory rather
|
|
90
|
+
than deleting it.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
---
|
|
2
|
+
simt-affects: ["src/hooks/dispatcher.mjs", "rules/core.md", "rules/mdc/slice-commit.mdc", "test/hooks.test.mjs"]
|
|
3
|
+
simt-supersedes: []
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ADR-120 — a pre-push whose tree is already proven green skips the test gate on that evidence
|
|
7
|
+
|
|
8
|
+
Status: **accepted** · 2026-10-31
|
|
9
|
+
|
|
10
|
+
## Context
|
|
11
|
+
|
|
12
|
+
The shared dispatcher runs a repository's declared pre-push gates on every push. For
|
|
13
|
+
this repository that is the full test suite — minutes of wall clock — and the common
|
|
14
|
+
shape of push pays it twice: the author ran the suite green on the very tree being
|
|
15
|
+
pushed, committed nothing further, and the gate runs it again at push time.
|
|
16
|
+
|
|
17
|
+
ADR-113 already ruled that a push carrying only documentation skips the test gate and
|
|
18
|
+
says so. It is the right shape of the answer and the template for this one: a skip
|
|
19
|
+
earned by a property of the push, reported with a reason, and the record that earned it
|
|
20
|
+
named in the report. This record extends it with a second property: a push whose
|
|
21
|
+
committed tree is one the suite has already passed.
|
|
22
|
+
|
|
23
|
+
The dispatcher records every gate run against the tree it ran on (ADR-087). A passing
|
|
24
|
+
run is evidence about one tree and no other; editing any file afterwards produces a
|
|
25
|
+
different tree, and the old line no longer proves anything about what is on disk. The
|
|
26
|
+
evidence the ledger already keeps is exactly the evidence this skip needs, and no
|
|
27
|
+
other state is required.
|
|
28
|
+
|
|
29
|
+
## Ruling
|
|
30
|
+
|
|
31
|
+
### 1. The skip is unconditional, evidence-driven, and fail-closed
|
|
32
|
+
|
|
33
|
+
A pre-push `test` gate is skipped when — and only when — the ledger records a passing
|
|
34
|
+
`test` run whose tree equals the committed tree being pushed (`HEAD^{tree}`). There is
|
|
35
|
+
no manifest key to declare: the condition is a fact about the evidence, not a policy
|
|
36
|
+
choice. Every uncertainty fails closed to the suite — an unborn HEAD, an unreadable
|
|
37
|
+
ledger, no entry for this tree, or a later failure on the same tree. A gate that cannot
|
|
38
|
+
prove it need not run does not run; a gate that cannot prove it is a failure, not a
|
|
39
|
+
skip.
|
|
40
|
+
|
|
41
|
+
"No changes since" is enforced by the digest, not by trusting the author: a clean
|
|
42
|
+
working tree's recorded `worktreeTree` equals `HEAD^{tree}`, and any later edit changes
|
|
43
|
+
the tree id, so the match fails and the suite runs.
|
|
44
|
+
|
|
45
|
+
### 2. The skip is reported, and names the record that earned it
|
|
46
|
+
|
|
47
|
+
The report prints the skip with its reason and the ADR that earned it — the same shape
|
|
48
|
+
ADR-113 fixed for the docs-only path. The `test` gate line reads
|
|
49
|
+
`SKIPPED test … [ADR-120]`, so a skip is never silent and the two skip paths are
|
|
50
|
+
distinguishable in the same report.
|
|
51
|
+
|
|
52
|
+
### 3. The docs-only skip keeps its own record
|
|
53
|
+
|
|
54
|
+
The two skips are independent and compose: the docs-only classification is consulted
|
|
55
|
+
first (it is cheaper — no ledger read), and where it does not fire, the evidence check
|
|
56
|
+
decides. A push that earns both is reported under whichever fired first; both earn the
|
|
57
|
+
same outcome, and the report names the one that did the work.
|
|
58
|
+
|
|
59
|
+
### 4. The rule tells the agent to cite the pass, not re-run it
|
|
60
|
+
|
|
61
|
+
`rules/core.md`'s Git section and the `slice-commit` rule both state the agent-side
|
|
62
|
+
half: once a push is approved and the suite ran green on the current state with nothing
|
|
63
|
+
changed since, the agent pushes directly and cites the already-passing run as the pass.
|
|
64
|
+
The gate recognising the evidence is what makes that citation verifiable — the agent
|
|
65
|
+
claims a run the ledger records, and a claim the ledger does not record is a claim the
|
|
66
|
+
evidence check flags.
|
|
67
|
+
|
|
68
|
+
## Consequences
|
|
69
|
+
|
|
70
|
+
- `src/hooks/dispatcher.mjs` gains `resolveAlreadyPassedSkip(root)` and threads the
|
|
71
|
+
earning record (`adr`) through `skipTest` so the report distinguishes ADR-113 from
|
|
72
|
+
ADR-120.
|
|
73
|
+
- The `slice-commit` rule becomes the git-workflow home — commit order, message
|
|
74
|
+
format, push approval, worktrees — and the Git section of `rules/core.md` points
|
|
75
|
+
at it. This is a change to shipped rule text: every adopting repository's
|
|
76
|
+
projection re-renders on its next sync.
|
|
77
|
+
- A repository that never records a run (a fresh clone, a clean checkout) is
|
|
78
|
+
unaffected: the ledger is empty, the check fails closed, and the suite runs exactly
|
|
79
|
+
as before.
|
|
80
|
+
- The skip is strictly weaker than the re-run — it proves the same tree, with the
|
|
81
|
+
same suite, already passed — so it cannot admit a state the suite would have
|
|
82
|
+
rejected.
|
|
83
|
+
|
|
84
|
+
## Rejected
|
|
85
|
+
|
|
86
|
+
- **A manifest key (opt-in).** Consistent with ADR-113's declared skip, but the
|
|
87
|
+
condition here is a fact about the evidence, not a policy a repository chooses; a
|
|
88
|
+
key would let a repository declare a skip it never earns and hide the evidence
|
|
89
|
+
requirement. The docs-only skip stays declared because its condition — the shape of
|
|
90
|
+
the push — is not observable from the ledger.
|
|
91
|
+
- **Matching the working tree.** The pre-push gate runs at HEAD, so the evidence that
|
|
92
|
+
matters is a pass on the committed tree. Matching the working tree would let a
|
|
93
|
+
dirty tree skip on a pass that did not include its changes.
|
|
94
|
+
- **A TTL or recency window.** The tree digest is a stronger condition than age: an
|
|
95
|
+
entry from a week ago is valid if the tree is the one being pushed, and a fresh
|
|
96
|
+
entry is void the moment a file changes. Age is not evidence; the digest is.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
---
|
|
2
|
+
simt-affects: ["src/hooks/shim.mjs", "src/commands/hooks.mjs", "src/journal.mjs", "src/agenthooks/shim.mjs", "test/hooks.test.mjs", "test/agenthooks.test.mjs"]
|
|
3
|
+
simt-supersedes: []
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ADR-122 — the shims move into the state directory, and the legacy directory removes itself
|
|
7
|
+
|
|
8
|
+
Status: **accepted** · 2026-10-31
|
|
9
|
+
|
|
10
|
+
## Context
|
|
11
|
+
|
|
12
|
+
The git shims live in a top-level `.simt-hooks/` directory, and `core.hooksPath` points
|
|
13
|
+
git at it. Every other piece of per-repo state — the journal, the ledger, the evidence,
|
|
14
|
+
the index — lives under the state directory `.simt/`, which the repository ignores as a
|
|
15
|
+
single line. The top-level split has no technical reason: `core.hooksPath` accepts any
|
|
16
|
+
path, and nothing about the shims requires a location of its own. It does have a cost —
|
|
17
|
+
two roots for what is one body of state, and an ignore line that exists only for the
|
|
18
|
+
shims.
|
|
19
|
+
|
|
20
|
+
The move has one genuine collision. The journal's stated boundary is that nothing under
|
|
21
|
+
the state directory is journaled: the journal recording itself is recursion, and the
|
|
22
|
+
ledger, index and candidates are evidence and caches whose byte-shape must not be
|
|
23
|
+
rolled back. Review 0003 Q22 ruled the opposite for the shims: `undo` restores
|
|
24
|
+
`core.hooksPath` and the shims together, because a routing restored without its shims is
|
|
25
|
+
a hook that no longer runs. Shims that land under the state directory unjournaled would
|
|
26
|
+
recreate Q22 exactly — the undo restores the routing and the shims stay. The move is
|
|
27
|
+
therefore not a rename; it is a rename plus a named exception to a recorded boundary.
|
|
28
|
+
|
|
29
|
+
Repositories installed before the move carry the legacy directory. The move must be
|
|
30
|
+
self-healing in those repositories, and the cleanup must obey the rule that already
|
|
31
|
+
governs the hooks directory: a file this tool cannot recognise as its own is never
|
|
32
|
+
deleted on its way.
|
|
33
|
+
|
|
34
|
+
## Ruling
|
|
35
|
+
|
|
36
|
+
### 1. The shims live in the state directory, and everything derives from one constant
|
|
37
|
+
|
|
38
|
+
`HOOKS_DIR` is `.simt/hooks`. The shim file names (`fileFor`), the agent-hook shim
|
|
39
|
+
(`.simt/hooks/agent-hook`), the `core.hooksPath` value a write installs, the hooks
|
|
40
|
+
status, and the agent-hooks `rm` policy all derive from that one constant, so the
|
|
41
|
+
location has exactly one home. The preceding location is `HOOKS_DIR_LEGACY`
|
|
42
|
+
(`.simt-hooks`), named because it is read during cleanup and cited in the plan.
|
|
43
|
+
|
|
44
|
+
### 2. The legacy directory removes itself on install, judged in the plan
|
|
45
|
+
|
|
46
|
+
On `hooks install` (and `doctor --fix`, which shares the plan), a present legacy
|
|
47
|
+
directory is scanned and judged removable in the plan: every entry in it must be a
|
|
48
|
+
regular file carrying a simt-dev marker — the shim marker or the chain marker. Removable
|
|
49
|
+
means the plan announces the removal and the apply removes each file journaled and then
|
|
50
|
+
takes the directory down, so `undo` re-creates the files and their directory. A single
|
|
51
|
+
entry the tool cannot recognise as its own — a foreign file, or anything that is not a
|
|
52
|
+
regular file — leaves the directory in place, unannounced and untouched. The judgment
|
|
53
|
+
lives in the plan and the deletion in the apply, so preview and apply agree by
|
|
54
|
+
construction, the way every other write in this command does.
|
|
55
|
+
|
|
56
|
+
### 3. The shim directory is the one named exception to the unjournaled state directory
|
|
57
|
+
|
|
58
|
+
`journaledWriteFile` and `journaledRemoveFile` record writes under the state directory
|
|
59
|
+
only when the path is the shim directory. The exception is named, not broadened: the
|
|
60
|
+
recursion and evidence arguments for the rest of the directory are untouched, and the
|
|
61
|
+
module header states the exception alongside the rule. Without it, `undo` after an
|
|
62
|
+
install restores `core.hooksPath` to unset and leaves the shims behind — the Q22
|
|
63
|
+
defect, relocated.
|
|
64
|
+
|
|
65
|
+
## Consequences
|
|
66
|
+
|
|
67
|
+
- `src/hooks/shim.mjs` exports both constants; `src/commands/hooks.mjs` gains
|
|
68
|
+
`legacyHooksScan` (the plan-side judgment) and the apply-side removal; `doctor --fix`
|
|
69
|
+
inherits both through the shared plan.
|
|
70
|
+
- `src/journal.mjs` imports the shim location from `src/hooks/shim.mjs` — the
|
|
71
|
+
dependency direction is the journal learning one named path, not the location module
|
|
72
|
+
learning about the journal.
|
|
73
|
+
- The repository's ignore file drops the `.simt-hooks/` line: the state directory line
|
|
74
|
+
already covers the shims.
|
|
75
|
+
- A repository installed before the move self-heals on its next `hooks install --yes`:
|
|
76
|
+
the routing re-points, the new shims land, and the legacy directory is gone — journaled,
|
|
77
|
+
so an `undo` brings it back.
|
|
78
|
+
- `src/toolwords.mjs` keeps `simt-hooks` as a tool word: the legacy name is still a
|
|
79
|
+
reference to the tool in remedies and history, and the estate pattern that exempts it
|
|
80
|
+
is unchanged.
|
|
81
|
+
- The agent-hook shim is re-rendered at its new location by the agent-hooks installer,
|
|
82
|
+
which reads the same constant.
|
|
83
|
+
|
|
84
|
+
## Rejected
|
|
85
|
+
|
|
86
|
+
- **Leaving the shims at the top level.** The split buys nothing and costs an ignore
|
|
87
|
+
line, a second state root, and a location that the state-directory story does not
|
|
88
|
+
explain.
|
|
89
|
+
- **Removing the legacy directory without the marker check.** A file a person put there
|
|
90
|
+
is not the tool's to delete, and the hooks directory already carries exactly this rule
|
|
91
|
+
for its own files; the cleanup is the same principle applied to the directory.
|
|
92
|
+
- **Journaled everything under the state directory.** The recursion and evidence
|
|
93
|
+
arguments are load-bearing for the ledger and the index; the exception must be the one
|
|
94
|
+
directory git's routing points at, and no wider.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
simt-affects: ["src/commands/index.mjs", "src/commands/spec.mjs", "test/argv-repeat.test.mjs"]
|
|
3
|
+
simt-supersedes: []
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ADR-123 — a surplus positional is not refused, because a command's arity is not in its source
|
|
7
|
+
|
|
8
|
+
Status: **accepted** · 2026-10-31
|
|
9
|
+
|
|
10
|
+
## Context
|
|
11
|
+
|
|
12
|
+
Review 0003 Q62 asked dispatch to validate a command's positionals the way it validates
|
|
13
|
+
its flags, and the cheapest place to read the maximum looked like the module that
|
|
14
|
+
receives `args`: a command that reads `args[0]` takes one, a command that reads nothing
|
|
15
|
+
takes none, and a command that joins or spreads `args` has no maximum at all.
|
|
16
|
+
|
|
17
|
+
Measured against the tree, that rule is wrong for five of the 76 rows. `init`, `tokens`,
|
|
18
|
+
`explain`, `graph lint` and `bench report` all accept a positional that their own module
|
|
19
|
+
does not index — it is forwarded to a helper in another module, or deliberately ignored.
|
|
20
|
+
Enforcing the derived maximum failed 27 tests, 11 of them on `init` alone (`init` a path
|
|
21
|
+
is the command's most ordinary call). The source tells you where an argument is *read*;
|
|
22
|
+
it does not tell you what the command *accepts*, and for the five commands whose reading
|
|
23
|
+
happens elsewhere the two answers differ.
|
|
24
|
+
|
|
25
|
+
## Ruling
|
|
26
|
+
|
|
27
|
+
### 1. Dispatch does not enforce a maximum number of positionals
|
|
28
|
+
|
|
29
|
+
Dispatch enforces what a spec row can state truthfully: the flags a command reads, the values a closed set accepts, and that a single-valued flag is given once. It does not enforce a positional maximum, and a surplus positional is accepted exactly as it was before the rule was attempted.
|
|
30
|
+
|
|
31
|
+
### 2. A command that refuses a surplus positional says so itself, in its own words
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
### 3. The withdrawn arity is recorded here rather than carried as a spec field
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
## Consequences
|
|
40
|
+
|
|
41
|
+
- Q62's surplus-positional clause is withdrawn. The arity it would have needed (29 rows
|
|
42
|
+
with a maximum, 47 unbounded) is not shipped, and no row carries an `args` field.
|
|
43
|
+
- `test/argv-repeat.test.mjs` keeps the refusal of a repeated flag and the closed set;
|
|
44
|
+
the surplus-positional test is gone with the rule that would have made it pass.
|
|
45
|
+
- The source-reading heuristic is recorded here rather than kept in a comment, because
|
|
46
|
+
the next agent to read Q62 will propose it again, and it will fail the same 27 tests.
|
|
47
|
+
|
|
48
|
+
## Rejected
|
|
49
|
+
|
|
50
|
+
- **Reading arity from the module, as written.** A command's accepted positionals are a
|
|
51
|
+
contract, and the source is an implementation of it; `init` proves they diverge. A
|
|
52
|
+
check that refuses `init <path>` is worse than the silence it replaces.
|
|
53
|
+
- **Declaring arity by hand for all 76 rows.** A number nobody derives and no caller
|
|
54
|
+
checks drifts silently, and a wrong maximum refuses a real call — the same failure,
|
|
55
|
+
arriving a release later and without a test to find it.
|
|
56
|
+
- **Making every surplus positional an error.** The forwarding commands would need a
|
|
57
|
+
per-command exception list, which is the manual declaration above with extra steps.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
simt-affects: ["src/manifest/schema.mjs", "src/resolve/fingerprint.mjs", "test/manifest-schema.test.mjs"]
|
|
3
|
+
simt-supersedes: []
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ADR-124 — the manifest has a schema in the tool, and its unknown keys stay the repository's
|
|
7
|
+
|
|
8
|
+
Status: **accepted** · 2026-10-31
|
|
9
|
+
|
|
10
|
+
## Context
|
|
11
|
+
|
|
12
|
+
`simt.json` is read in 29 places and 41 keys are read ad hoc across the tree. There was
|
|
13
|
+
no list of the keys, so `"mode": "kernal"` was accepted by every reader and reached the
|
|
14
|
+
projection as a mode nothing had heard of, and one reader (`fingerprint`) returned
|
|
15
|
+
`{error}` as the manifest, so a file that did not parse looked like a declaration of a
|
|
16
|
+
key called `error`.
|
|
17
|
+
|
|
18
|
+
Writing that list is where this repository's characteristic mistake is easiest to make:
|
|
19
|
+
the first draft was written from memory and called `limits.fileLines` a positive integer
|
|
20
|
+
and `surfaces` an object. In the only manifest the tool has to hand, the first is an
|
|
21
|
+
object of two tiers and the second is a list.
|
|
22
|
+
|
|
23
|
+
## Ruling
|
|
24
|
+
|
|
25
|
+
### 1. One table declares the manifest keys, their owner and their check
|
|
26
|
+
|
|
27
|
+
One table in `src/manifest/schema.mjs` declares every key with its owner (`tool` or
|
|
28
|
+
`repo`), the version it arrived in, and a check that returns a sentence naming what is
|
|
29
|
+
wrong. One loader, `loadManifest(root)`, returns the manifest, whether a file exists,
|
|
30
|
+
the version, and the problems. The table is held to this repository's own `simt.json` by
|
|
31
|
+
a test, because a schema written from memory fails that test in the same run that proves
|
|
32
|
+
the loader works.
|
|
33
|
+
|
|
34
|
+
### 2. An unparseable file is no manifest, and an absent version field is version 1
|
|
35
|
+
|
|
36
|
+
A caller that receives a half-read object cannot tell it from a declaration, so a file
|
|
37
|
+
that does not parse yields no manifest and one problem naming the parse error. `version`
|
|
38
|
+
arrived after every manifest now on disk, so absent means 1 rather than a problem.
|
|
39
|
+
|
|
40
|
+
### 3. An unknown key is not a problem, because the file invites the repository's own
|
|
41
|
+
|
|
42
|
+
The manifest comment tells a repository that a value it adds survives a re-init. A
|
|
43
|
+
validator that refused an unknown key would make the tool the owner of a file it calls
|
|
44
|
+
the repository's, so only a known key with an impossible value is reported.
|
|
45
|
+
|
|
46
|
+
## Consequences
|
|
47
|
+
|
|
48
|
+
- `fingerprint` reads through the loader, so a broken manifest no longer arrives as an
|
|
49
|
+
object with an `error` key; a dependent read sees no declaration.
|
|
50
|
+
- The 29 direct `JSON.parse` call sites remain. Migrating them is the rest of Q69 and
|
|
51
|
+
changes no behaviour this table already answers for a single call.
|
|
52
|
+
- `GENERATED_KEYS` is not yet derived from the table; the table distinguishes `tool`
|
|
53
|
+
from `repo` keys, so the derivation is possible and is not taken here.
|
|
54
|
+
|
|
55
|
+
## Rejected
|
|
56
|
+
|
|
57
|
+
- **A JSON Schema file and a validator dependency.** `package.json` declares an empty
|
|
58
|
+
dependency set (ADR-012), and a schema in a second language would be a second place
|
|
59
|
+
the keys are stated.
|
|
60
|
+
- **Refusing unknown keys.** The file comment invites a repository to keep its own
|
|
61
|
+
keys, and refusing them breaks every repository that already does.
|
|
62
|
+
- **Throwing on a bad value.** A manifest is read on the path of every command; a throw
|
|
63
|
+
turns a typo into a crash with no remedy, where a named problem can be printed beside
|
|
64
|
+
the answer.
|