simt-dev 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +375 -7
- package/README.md +4 -4
- package/docs/decisions/ADR-019-a-declared-command-is-the-runner.md +27 -0
- package/docs/decisions/ADR-059-the-version-a-repository-runs-is-the-one-it-pinned.md +75 -0
- package/docs/decisions/ADR-060-a-repository-with-nothing-to-run-is-adopted-and-says-so.md +131 -0
- package/docs/decisions/ADR-061-the-skill-registry-is-workspace-data.md +113 -0
- package/docs/decisions/ADR-062-a-fresh-adoption-seeds-the-gates-it-can-run.md +94 -0
- package/docs/decisions/ADR-063-the-adoption-escape-hatch-is-opt-in.md +69 -0
- package/docs/decisions/ADR-064-the-context-meter-counts-what-the-tool-wrote.md +107 -0
- package/docs/decisions/ADR-065-a-pack-is-one-shared-rule-set-pinned-by-digest.md +97 -0
- package/docs/decisions/ADR-066-a-monorepo-projects-once-per-package.md +115 -0
- package/docs/decisions/ADR-067-which-harnesses-read-agents-md-is-a-column.md +110 -0
- package/docs/decisions/ADR-068-the-mcp-revision-list-cites-what-was-read.md +111 -0
- package/docs/decisions/ADR-069-the-conformance-lint-checks-the-published-specification.md +90 -0
- package/docs/decisions/ADR-070-the-json-payload-carries-its-own-version.md +91 -0
- package/docs/decisions/ADR-071-a-rule-that-can-be-narrowed-is-written-narrow.md +106 -0
- package/docs/decisions/ADR-072-the-budget-is-a-declaration-beside-the-gates.md +104 -0
- package/docs/decisions/ADR-073-a-kernel-is-a-smaller-projection-the-repository-asks-for.md +92 -0
- package/docs/decisions/README.md +17 -0
- package/package.json +1 -2
- package/rules/core.md +1 -0
- package/rules/stacks/android.md +2 -0
- package/rules/stacks/cloudflare-workers.md +2 -0
- package/rules/stacks/connectiq.md +1 -0
- package/rules/stacks/container.md +2 -0
- package/rules/stacks/dart.md +40 -0
- package/rules/stacks/deno.md +38 -0
- package/rules/stacks/dotnet.md +37 -0
- package/rules/stacks/elixir.md +41 -0
- package/rules/stacks/go.md +36 -0
- package/rules/stacks/jvm.md +2 -0
- package/rules/stacks/node.md +2 -0
- package/rules/stacks/python.md +2 -0
- package/rules/stacks/react-native.md +2 -0
- package/rules/stacks/react-web.md +2 -0
- package/rules/stacks/ruby.md +38 -0
- package/rules/stacks/rust.md +36 -0
- package/rules/stacks/swift.md +39 -0
- package/rules/stacks/typescript.md +2 -0
- package/rules/stacks/uv.md +2 -0
- package/src/argv.mjs +54 -6
- package/src/ci/dependabot.mjs +1 -1
- package/src/ci/workflow.mjs +33 -11
- package/src/commands/compare.mjs +15 -0
- package/src/commands/graph.mjs +58 -25
- package/src/commands/harness.mjs +150 -0
- package/src/commands/help.mjs +37 -5
- package/src/commands/hooks.mjs +173 -57
- package/src/commands/index.mjs +95 -69
- package/src/commands/init.mjs +100 -20
- package/src/commands/mcp.mjs +20 -5
- package/src/commands/rules.mjs +96 -60
- package/src/commands/skills.mjs +189 -86
- package/src/commands/skillsinstall.mjs +20 -5
- package/src/commands/sync.mjs +213 -15
- package/src/commands/tokens.mjs +138 -0
- package/src/compare/hooksstate.mjs +96 -9
- package/src/compare/row.mjs +107 -7
- package/src/decisions/index.mjs +21 -6
- package/src/graph/load.mjs +125 -14
- package/src/graph/spec.mjs +227 -0
- package/src/graph/speccheck.mjs +68 -0
- package/src/hooks/capability.mjs +48 -4
- package/src/hooks/dispatcher.mjs +84 -7
- package/src/hooks/gates.mjs +0 -2
- package/src/hooks/resolve.mjs +24 -25
- package/src/hooks/shim.mjs +29 -5
- package/src/identity.mjs +0 -11
- package/src/mcp/schema.mjs +139 -0
- package/src/mcp/server.mjs +197 -20
- package/src/mcp/tools.mjs +290 -3
- package/src/measure/context.mjs +371 -0
- package/src/monorepo.mjs +339 -0
- package/src/output/index.mjs +86 -9
- package/src/package-version.mjs +74 -0
- package/src/packs.mjs +182 -0
- package/src/projection/conflicts.mjs +56 -5
- package/src/projection/manifest.mjs +335 -25
- package/src/projection/mdc.mjs +4 -13
- package/src/projection/project.mjs +139 -8
- package/src/projection/rules.mjs +308 -27
- package/src/projection/surfaces.mjs +133 -15
- package/src/projection/targets.mjs +90 -7
- package/src/resolve/eval.mjs +64 -0
- package/src/resolve/fingerprint.mjs +1 -0
- package/src/resolve/near.mjs +26 -7
- package/src/resolve/resolve.mjs +1 -1
- package/src/resolve/source.mjs +1 -1
- package/src/workspace.mjs +0 -9
- package/graph/skills.graph.json +0 -1622
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# ADR-072 — The budget is a declaration beside the gates, not a number inside the projection
|
|
2
|
+
|
|
3
|
+
Status: **accepted** · 2026-10-14 · filed as S38
|
|
4
|
+
**Affects:** `src/projection/manifest.mjs`, `src/measure/context.mjs`,
|
|
5
|
+
`src/projection/project.mjs`, `src/commands/sync.mjs`, `simt tokens`, `simt-dev check`,
|
|
6
|
+
`test/tokens.test.mjs`, `test/surfaces.test.mjs`
|
|
7
|
+
|
|
8
|
+
## Context
|
|
9
|
+
|
|
10
|
+
Review 0002 row S38 asks for a per-repository always-on budget: "for these 7 roots,
|
|
11
|
+
generated always-on bytes are at most 30 KB". Plan 0003 spells the mechanism as R08, and
|
|
12
|
+
the plan was read rather than recalled, which is how the two facts below were found.
|
|
13
|
+
|
|
14
|
+
The first is that R08 as written presupposes R07. Its compiler "packs kernel lines by
|
|
15
|
+
priority class" and `check` reports "each cut line" — and there are no kernel lines
|
|
16
|
+
without R07's `projection.mode` and the per-line kernel frontmatter it introduces, whose own default stays
|
|
17
|
+
`full` until R02 proves a narrow mode is not worse. With no kernel there is nothing to cut,
|
|
18
|
+
so the packing half of R08 cannot be built honestly before the R01–R05 evaluation has been
|
|
19
|
+
run. Building it anyway would mean inventing a kernel nobody measured.
|
|
20
|
+
|
|
21
|
+
The second is where the number lives. R08 says `projection.budget.bytes`. In this tool
|
|
22
|
+
`projection` is a generated key: `GENERATED_KEYS` names it, and `withGenerated()` replaces
|
|
23
|
+
it with the tool's own `{ simtDevSha, version }` on every write. A budget written there
|
|
24
|
+
would be deleted by the next `upgrade`, and — because `manifestDrift` compares only the
|
|
25
|
+
generated keys — not reported as deleted. The repo would watch its budget disappear on a
|
|
26
|
+
green gate. That is the same defect this package treats as unrecoverable everywhere else:
|
|
27
|
+
content the repository chose must be preserved by the repair, not overwritten by it.
|
|
28
|
+
|
|
29
|
+
So the part of R08 that is independent of the kernel ships now, and the part that is not
|
|
30
|
+
does not.
|
|
31
|
+
|
|
32
|
+
## Rulings
|
|
33
|
+
|
|
34
|
+
1. **The budget is a repository declaration at the top level of the manifest, beside
|
|
35
|
+
`limits` and `gates`.** It is content the repo chose, so it survives `upgrade`, and the
|
|
36
|
+
plan's `projection.budget.bytes` spelling is superseded rather than edited in place. The
|
|
37
|
+
default is not a field either: it is the value whoever measures applies when the key is
|
|
38
|
+
absent, so `simt tokens` and the gate read one number from one place.
|
|
39
|
+
|
|
40
|
+
2. **A repository that declares nothing is not reported as having declared the default.**
|
|
41
|
+
`buildManifest` omits the key entirely when the repo has none, which keeps
|
|
42
|
+
`budget.declared` an honest fact. Writing the default in would make every adopted repo
|
|
43
|
+
look like it had chosen the review's line — a reader told "declared: true" about a
|
|
44
|
+
number nobody wrote is being lied to about the only thing the number is for.
|
|
45
|
+
|
|
46
|
+
3. **The default is 30,000 bytes and it is the review's figure, not this tool's
|
|
47
|
+
measurement.** It is written as 30,000 rather than 30 × 1024 because it is a ceiling and
|
|
48
|
+
the conservative reading of "at most 30 KB" is the one that trips first.
|
|
49
|
+
|
|
50
|
+
4. **Over budget is reported, never enforced.** The finding names the bytes measured, the
|
|
51
|
+
bytes declared and what was not done: nothing was cut, because the projection is still
|
|
52
|
+
in full mode. A reader who sees an over-budget line must not conclude the projection
|
|
53
|
+
shrank, and a gate that failed here would fail on a measurement this tool has not
|
|
54
|
+
validated against a study.
|
|
55
|
+
|
|
56
|
+
5. **`simt tokens` carries the finding it would raise, produced by the same function.**
|
|
57
|
+
One function words the fact, so the report and the gate cannot describe the same
|
|
58
|
+
overflow two ways.
|
|
59
|
+
|
|
60
|
+
6. **The rows themselves are unchanged.** Which rules move behind skills is R01–R05's
|
|
61
|
+
finding, not this record's, and a budget that silently dropped rules would be enforcing
|
|
62
|
+
a decision nobody has made.
|
|
63
|
+
|
|
64
|
+
7. **The meter is the definition of the number, and the gate quotes it.**
|
|
65
|
+
`simt-dev check` calls the same `measureRoot` the meter does rather than summing the
|
|
66
|
+
bytes of the targets it happens to compare. That was the other way round first, and it
|
|
67
|
+
was wrong: the meter also counts managed skills and `.mdc` files, so one repository had
|
|
68
|
+
two budgets and two different numbers for the same declaration. Two definitions of one
|
|
69
|
+
ceiling is the same defect as two projections of one fact, one layer up.
|
|
70
|
+
|
|
71
|
+
8. **The gate raises the finding and does not block on it.** `budget` is in both
|
|
72
|
+
non-blocking sets, `check` and `sync` alike. Nothing was cut, so the projection is
|
|
73
|
+
exactly what the repository agreed to project; failing the run over a ceiling it wrote
|
|
74
|
+
down itself would train people to raise the ceiling. R08 says non-blocking for the same
|
|
75
|
+
reason, and this record agrees with it now that the finding has a shape to be.
|
|
76
|
+
|
|
77
|
+
9. **The cost of ruling 7 is accepted rather than avoided.** `check` now reads what the
|
|
78
|
+
meter reads — the projected region, the rule files, the skills — on every run, where it
|
|
79
|
+
previously compared bytes it had already read. A cheaper gate would have to keep its own
|
|
80
|
+
notion of what counts, and this is the one place where the two are not allowed to differ.
|
|
81
|
+
|
|
82
|
+
## Consequences
|
|
83
|
+
|
|
84
|
+
S38's accept criterion is now checkable rather than asserted: a repository can declare the
|
|
85
|
+
line it intends to carry, and `simt tokens` reports it against that line and says how far
|
|
86
|
+
over it is. Three tests hold the two halves that were easy to get backwards — the budget
|
|
87
|
+
survives an upgrade, a repository that declared nothing keeps `declared: false`, and the
|
|
88
|
+
finding says that nothing was cut.
|
|
89
|
+
|
|
90
|
+
What has not landed: the gate reporting the budget as a finding, and R08's packing. The
|
|
91
|
+
first needs the number the gate is already comparing — the bytes of the region and the rule
|
|
92
|
+
files it checks — so that `check` and `tokens` cannot report different totals for one
|
|
93
|
+
repository; that is a wiring decision, not a measurement. The second needs R07.
|
|
94
|
+
|
|
95
|
+
## Rejected
|
|
96
|
+
|
|
97
|
+
- **The plan's `projection.budget.bytes`.** The generated block is replaced wholesale on
|
|
98
|
+
every upgrade, and nothing in the repair would notice.
|
|
99
|
+
- **A default written into every manifest.** It would make every adopted repo look like it
|
|
100
|
+
had chosen a number it never chose.
|
|
101
|
+
- **Cutting lines to fit the budget now.** There is no kernel to pack and no measurement
|
|
102
|
+
saying which lines a reader can lose.
|
|
103
|
+
- **Failing the gate on an over-budget projection.** The number is this tool's own
|
|
104
|
+
estimate, and a gate that fails on its own estimate teaches a reader to ignore it.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# ADR-073 — A kernel is a smaller projection the repository asks for, not one it is given
|
|
2
|
+
|
|
3
|
+
Status: **accepted** · 2026-10-14 · filed as S38 (R07)
|
|
4
|
+
**Affects:** `src/projection/manifest.mjs`, `src/projection/rules.mjs`, `src/measure/context.mjs`,
|
|
5
|
+
`rules/core.md`, `rules/stacks/*.md`, `test/kernel.test.mjs`
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
S38 asked for a number — generated always-on bytes per turn — and this repository built the
|
|
10
|
+
meter (ADR-064) and the budget line to measure it against (ADR-072). What the meter then
|
|
11
|
+
made unavoidable is that the budget cannot be met by measuring more carefully. A python
|
|
12
|
+
repository projects 11,305 always-on bytes: the shared core, the python profile, and the
|
|
13
|
+
process rules. A kernel keeps the first of those and defers the other two, and the same
|
|
14
|
+
repository projects 9,551. Sixteen percent, for a declaration.
|
|
15
|
+
|
|
16
|
+
Plan 0003 R07 describes this as packing kernel lines by priority class, and R08's accept
|
|
17
|
+
criterion — the budget is never exceeded — is only reachable once something is packed. So
|
|
18
|
+
this is the piece R08 waits on.
|
|
19
|
+
|
|
20
|
+
What R07 does not say is which lines are the kernel, and that question is not rhetorical.
|
|
21
|
+
Classifying a rule is a claim about what a reader loses by not having it: a rule that says
|
|
22
|
+
never force-push is not equivalent to a rule that says prefer descriptive commit subjects,
|
|
23
|
+
and only one of those can be the one thing loaded when nothing matches. The classification
|
|
24
|
+
lives in the rule's own frontmatter, because the judgement belongs beside the text it
|
|
25
|
+
describes and not in a table that would have to be kept in step with 20 files.
|
|
26
|
+
|
|
27
|
+
## Rulings
|
|
28
|
+
|
|
29
|
+
1. **The mode is a repository declaration, beside the budget, not inside `projection`.**
|
|
30
|
+
`mode` is written only when the repository declared it, for exactly the reason ADR-072
|
|
31
|
+
gave for `budget`: `projection` is a generated key, `upgrade` replaces it wholesale, and
|
|
32
|
+
a mode that reset itself on upgrade would reset the reader's context size without anyone
|
|
33
|
+
asking.
|
|
34
|
+
|
|
35
|
+
2. **Absent means `full`, and `full` stays the default.** Nobody has measured that a kernel
|
|
36
|
+
is not worse at getting a task done; plan 0003 R02 is that measurement and it has not
|
|
37
|
+
been run. Shipping a default that quietly withholds rules from every repository on the
|
|
38
|
+
strength of an unrun study is the same defect as a rule nobody can find.
|
|
39
|
+
|
|
40
|
+
3. **A rule declares its class in its own frontmatter, and the vocabulary is closed.**
|
|
41
|
+
`simt-priority: hard | command | convention | style`, ordered as a kernel packs them.
|
|
42
|
+
An unrecognised value is a defect reported by `graph lint` and `check`, in the order the
|
|
43
|
+
other frontmatter checks run, rather than a class nobody happened to use.
|
|
44
|
+
|
|
45
|
+
4. **A rule that states no class is deferred in kernel mode.** An unstated class is not a
|
|
46
|
+
hard constraint, and the asymmetry decides it: wrongly keeping a line defeats the mode
|
|
47
|
+
the repository asked for, while wrongly dropping one costs a convention or a style, which
|
|
48
|
+
`rules show <id>` returns on demand.
|
|
49
|
+
|
|
50
|
+
5. **The kernel is the two classes a reader loses least from.** `hard` and `command` are
|
|
51
|
+
what a repository must not do wrong; `convention` and `style` are guidance that is
|
|
52
|
+
valuable in context and worthless out of it. The shipped content is classified to match:
|
|
53
|
+
`core.md` is `hard`, and the 19 stack profiles are `convention`.
|
|
54
|
+
|
|
55
|
+
6. **Everything deferred is named in the region, with the command that shows it.** The
|
|
56
|
+
region gains a `## Deferred rules` section listing each omitted rule, its class and
|
|
57
|
+
`rules show <id>`, sorted by class then rule id. A rule left out and named nowhere is a
|
|
58
|
+
rule the reader cannot find, which is indistinguishable from a rule that was never
|
|
59
|
+
written — the same defect S50's scoped pointers exist to prevent, in the other
|
|
60
|
+
direction.
|
|
61
|
+
|
|
62
|
+
7. **The order is deterministic and is the only thing that determines it.** Class, then
|
|
63
|
+
rule id. Two machines that project the same repository produce byte-identical regions,
|
|
64
|
+
which is R08's second half and this repository's standing rule.
|
|
65
|
+
|
|
66
|
+
8. **A kernel over the budget is a different finding from a full projection over it.** The
|
|
67
|
+
budget finding takes the mode and says so: in `full`, nothing was cut and the advice is
|
|
68
|
+
to raise the number or switch to kernel; in `kernel`, the projection is already as small
|
|
69
|
+
as this mode makes it, so the advice is to raise the number or move rules out of the
|
|
70
|
+
hard and command classes. Conflating the two would leave a reader believing a trimmed
|
|
71
|
+
projection was waiting to be trimmed.
|
|
72
|
+
|
|
73
|
+
## Consequences
|
|
74
|
+
|
|
75
|
+
A repository that declares `mode: kernel` gets the shared core plus the hard and command
|
|
76
|
+
rules, and a list of everything else with the command to fetch it. The meter's own reading
|
|
77
|
+
drops by about a sixth on a python repository, and the budget finding now distinguishes the
|
|
78
|
+
two reasons a projection can be over its line.
|
|
79
|
+
|
|
80
|
+
What is not here: the measurement that would justify making `kernel` the default, and the
|
|
81
|
+
workspace-level dedupe R11 proposes. Both are named in the tracker's remaining note for this
|
|
82
|
+
row. This ruling is what makes them measurable rather than what concludes them.
|
|
83
|
+
|
|
84
|
+
## Rejected
|
|
85
|
+
|
|
86
|
+
- **Making `kernel` the default.** It withholds guidance from every repository on the
|
|
87
|
+
strength of a study that has not been run.
|
|
88
|
+
- **A central table of classes rather than frontmatter.** A table beside 20 rule files is a
|
|
89
|
+
thing to keep in step with them, and the judgement it records belongs next to the text.
|
|
90
|
+
- **Silently deferring, or deferring with a count.** A reader told "12 rules deferred" has
|
|
91
|
+
to guess which ones apply to the file in front of them; the names are the part that
|
|
92
|
+
answers.
|
package/docs/decisions/README.md
CHANGED
|
@@ -66,6 +66,8 @@ record's status line — never by editing the ruling in place.
|
|
|
66
66
|
| [056](ADR-056-three-names-one-of-which-is-cheap-to-move.md) | The command, the package and the project are three names, and only one of them is cheap to move | accepted · amends ADR-023 |
|
|
67
67
|
| [057](ADR-057-an-identity-is-per-developer-and-a-repository-does-not-own-it.md) | An identity is per-developer, and a repository does not own it | accepted |
|
|
68
68
|
| [058](ADR-058-one-tool-three-channels-and-the-order-is-forced.md) | One tool, three channels, and the order is forced | accepted |
|
|
69
|
+
| [059](ADR-059-the-version-a-repository-runs-is-the-one-it-pinned.md) | The version a repository runs is the one it pinned, and the gate refuses otherwise | accepted |
|
|
70
|
+
| [060](ADR-060-a-repository-with-nothing-to-run-is-adopted-and-says-so.md) | A repository with nothing to run is adopted and says so; a gate is verified where it runs | accepted |
|
|
69
71
|
|
|
70
72
|
ADR-001 to ADR-010 resolve the contradictions found across the 16 repos in the 2026-10-05
|
|
71
73
|
audit; each record names the repos whose text it replaces. ADR-011 and ADR-012 are the
|
|
@@ -402,6 +404,21 @@ docstring that used to claim the whole output "never contains a path" is withdra
|
|
|
402
404
|
asserted a property the function never had, which is the same defect as a citation that
|
|
403
405
|
cannot be resolved — a sentence that reads like evidence and carries none.
|
|
404
406
|
|
|
407
|
+
ADR-059 is the first record here whose subject is the version of the tool itself, and it
|
|
408
|
+
exists because a check that runs from the wrong binary is a pass that never happened. ADR-010
|
|
409
|
+
allowed a tool that is not a project dependency to be invoked directly, *provided its version
|
|
410
|
+
is pinned* — and for a repository without npm that "pinned" half had no mechanism. The version
|
|
411
|
+
on `PATH` was whatever the last global install left there, and the gate that ran on commit was
|
|
412
|
+
the one in it: a green tick from a check running a different gate than CI is worse than a red
|
|
413
|
+
build, because it is the failure that looks like evidence. The record rules that the version a
|
|
414
|
+
repository adopted is recorded in the manifest, that the gate refuses when the running binary
|
|
415
|
+
differs, and that the install and the remedy are per kind — so the check CI runs is the one the
|
|
416
|
+
machine remedy names. Its most load-bearing consequence is the one that keeps it from becoming a
|
|
417
|
+
trap: a repository adopted before the field existed has nothing to compare against, so it is
|
|
418
|
+
skipped rather than failed, and reported as drift instead — because a gate that turns red on the
|
|
419
|
+
day a check ships, for a mismatch nobody can resolve until they re-adopt, is a gate no one will
|
|
420
|
+
trust again.
|
|
421
|
+
|
|
405
422
|
## The rule these records enforce
|
|
406
423
|
|
|
407
424
|
Every shared rule has exactly **one** canonical home. Every other location is either a
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "simt-dev",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Shared agent rules, skills and git hooks for a repository: one command adopts a repo, and every gate it declares is probed before it is written.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Said Elimam",
|
|
@@ -19,7 +19,6 @@
|
|
|
19
19
|
"files": [
|
|
20
20
|
"bin",
|
|
21
21
|
"src",
|
|
22
|
-
"graph",
|
|
23
22
|
"rules",
|
|
24
23
|
"docs/decisions",
|
|
25
24
|
"CHANGELOG.md"
|
package/rules/core.md
CHANGED
package/rules/stacks/android.md
CHANGED
|
@@ -6,6 +6,8 @@ simt-title: Android
|
|
|
6
6
|
# JVM backend, which is how a Spring or Ktor service used to receive this profile
|
|
7
7
|
# (S28, ADR-047).
|
|
8
8
|
simt-applies: [has-android-manifest]
|
|
9
|
+
simt-priority: convention
|
|
10
|
+
simt-scope: ["AndroidManifest.xml", "**/build.gradle", "**/build.gradle.kts", "**/*.kt"]
|
|
9
11
|
---
|
|
10
12
|
|
|
11
13
|
# Stack profile: Android
|
|
@@ -7,6 +7,8 @@ simt-title: Cloudflare Workers (TypeScript)
|
|
|
7
7
|
# happily as on Workers. A wrangler config or the `wrangler` dependency is the
|
|
8
8
|
# only evidence that this is a Worker (S28, ADR-047).
|
|
9
9
|
simt-applies: [cloudflare-workers]
|
|
10
|
+
simt-priority: convention
|
|
11
|
+
simt-scope: ["wrangler.jsonc", "wrangler.toml", "**/worker.ts"]
|
|
10
12
|
---
|
|
11
13
|
|
|
12
14
|
# Stack profile: Cloudflare Workers (TypeScript)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
simt-id: dart
|
|
3
|
+
simt-title: Dart
|
|
4
|
+
simt-applies: [has-pubspec]
|
|
5
|
+
simt-priority: convention
|
|
6
|
+
simt-scope: ["pubspec.yaml", "**/*.dart"]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Stack profile: Dart
|
|
10
|
+
|
|
11
|
+
Applies to: `has-pubspec` — a `pubspec.yaml`. A Flutter project is a Dart project
|
|
12
|
+
with the `flutter` SDK dependency; the Flutter-specific rules live with the tools
|
|
13
|
+
that build it, not here.
|
|
14
|
+
|
|
15
|
+
## Build and gates
|
|
16
|
+
|
|
17
|
+
- `dart analyze`, `dart test` are the entrypoints; `dart format --output=none
|
|
18
|
+
--set-exit-if-changed .` is the formatting gate. A Flutter project runs the same
|
|
19
|
+
commands through `flutter`, and the two must not be mixed in one change.
|
|
20
|
+
- Dependencies are declared in `pubspec.yaml`; `pub get` resolves them into
|
|
21
|
+
`pubspec.lock`, which is committed for an application. A package that imports a
|
|
22
|
+
library the manifest does not list resolves only by accident.
|
|
23
|
+
|
|
24
|
+
## Style
|
|
25
|
+
|
|
26
|
+
- `package:lints` (or the repo's stricter set) is the lint source; a rule the repo
|
|
27
|
+
disabled is a decision, and re-enabling it is a change, not a fix.
|
|
28
|
+
- No `dynamic` where a real type exists. A `dynamic` that is guessed rather than
|
|
29
|
+
checked is a hole the analyzer was told to stop looking through.
|
|
30
|
+
- `late` and `?` are decisions: a field that is `late` because the author did not
|
|
31
|
+
know when it is set is a defect, not a convenience.
|
|
32
|
+
- Prefer a small `sealed` class hierarchy over booleans; a pair of `bool` fields is
|
|
33
|
+
four states, one of which is always wrong.
|
|
34
|
+
|
|
35
|
+
## Testing
|
|
36
|
+
|
|
37
|
+
- `dart test` is the gate; a library with no tests is allowed to have none, but a
|
|
38
|
+
change that touches one must not break its neighbours.
|
|
39
|
+
- Assert on values through the public API; a test that reaches into a private field
|
|
40
|
+
is coupled to an implementation it should not own.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
simt-id: deno
|
|
3
|
+
simt-title: Deno
|
|
4
|
+
simt-applies: [has-deno-config]
|
|
5
|
+
simt-priority: convention
|
|
6
|
+
simt-scope: ["deno.json", "deno.jsonc", "**/*.ts"]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Stack profile: Deno
|
|
10
|
+
|
|
11
|
+
Applies to: `has-deno-config` — a `deno.json` or `deno.jsonc`.
|
|
12
|
+
|
|
13
|
+
## Build and gates
|
|
14
|
+
|
|
15
|
+
- `deno test`, `deno check .` are the entrypoints; `deno fmt --check` and
|
|
16
|
+
`deno lint` are the formatting and lint gates. A repo that vendors dependencies
|
|
17
|
+
runs them with `--vendor`; one that does not must not add the flag.
|
|
18
|
+
- The lockfile (`deno.lock`) is committed where it exists; a build that ignores it
|
|
19
|
+
is not reproducible. A lockfile the repo chose to delete is a decision, and
|
|
20
|
+
re-adding it is a change, not a fix.
|
|
21
|
+
- Import from `npm:` specifiers where the repo does; do not introduce a second
|
|
22
|
+
registry convention into a repo that has one.
|
|
23
|
+
|
|
24
|
+
## Style
|
|
25
|
+
|
|
26
|
+
- Types are checked with `deno check .`; a file that compiles under `deno run` but
|
|
27
|
+
not under `deno check` is a defect the gate exists to catch.
|
|
28
|
+
- No `any`. An `unknown` that is narrowed at the boundary is a type; an `any` is a
|
|
29
|
+
hole the checker was told to stop looking through.
|
|
30
|
+
- Prefer a small `enum`-shaped union over booleans; a pair of `boolean` fields is
|
|
31
|
+
four states, one of which is always wrong.
|
|
32
|
+
|
|
33
|
+
## Testing
|
|
34
|
+
|
|
35
|
+
- `Deno.test` is the runner; a test that needs a fixture reads it from a path
|
|
36
|
+
relative to the test file, never from a machine-specific location.
|
|
37
|
+
- Assert on values through the public API; a test that reaches into module-private
|
|
38
|
+
state is coupled to an implementation it should not own.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
simt-id: dotnet
|
|
3
|
+
simt-title: .NET
|
|
4
|
+
simt-applies: [has-nuget]
|
|
5
|
+
simt-priority: convention
|
|
6
|
+
simt-scope: ["*.csproj", "*.sln", "*.fsproj"]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Stack profile: .NET
|
|
10
|
+
|
|
11
|
+
Applies to: `has-nuget` — a `.csproj` or solution.
|
|
12
|
+
|
|
13
|
+
## Build and gates
|
|
14
|
+
|
|
15
|
+
- `dotnet build`, `dotnet test` are the entrypoints; `dotnet format
|
|
16
|
+
--verify-no-changes` is the formatting gate. A solution builds with `dotnet build
|
|
17
|
+
<solution>`, never a bare project path that skips the solution's configuration.
|
|
18
|
+
- Build with `-warnaserror` where the repo sets it; a warning that is not an error
|
|
19
|
+
is a defect the gate is being asked to ignore.
|
|
20
|
+
- Restore is deterministic: commit `global.json` and any NuGet.config the repo has,
|
|
21
|
+
so a build does not depend on a machine's cache.
|
|
22
|
+
|
|
23
|
+
## Style
|
|
24
|
+
|
|
25
|
+
- Prefer `required` members and `init` over a public setter that exists to satisfy
|
|
26
|
+
the constructor. A mutable property nobody should write is a hole in the type.
|
|
27
|
+
- `async`/`await` all the way: no `.Result`, no `.Wait()`. A synchronous call into
|
|
28
|
+
an async boundary is a deadlock waiting for the thread pool.
|
|
29
|
+
- Nullability is on: `#nullable enable` in new code, and a `?` that is guessed
|
|
30
|
+
rather than checked is a defect.
|
|
31
|
+
|
|
32
|
+
## Testing
|
|
33
|
+
|
|
34
|
+
- `dotnet test` runs xUnit, NUnit or MSTest — whichever the project references; do
|
|
35
|
+
not add a second framework to a repo that has one.
|
|
36
|
+
- Assert on behaviour through the public surface; a test that reaches into a private
|
|
37
|
+
field is coupled to an implementation it should not own.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
simt-id: elixir
|
|
3
|
+
simt-title: Elixir
|
|
4
|
+
simt-applies: [has-mix]
|
|
5
|
+
simt-priority: convention
|
|
6
|
+
simt-scope: ["mix.exs", "lib/**/*.ex"]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Stack profile: Elixir
|
|
10
|
+
|
|
11
|
+
Applies to: `has-mix` — a `mix.exs`.
|
|
12
|
+
|
|
13
|
+
## Build and gates
|
|
14
|
+
|
|
15
|
+
- `mix test` is the entrypoint; `mix format --check-formatted` is the formatting
|
|
16
|
+
gate. A repo that uses `mix credo` runs it with `mix credo --strict` where the
|
|
17
|
+
`.credo.exs` exists; a check the repo configured is a decision, and skipping it
|
|
18
|
+
is a change, not a fix.
|
|
19
|
+
- Dependencies are declared in `mix.exs`; `mix deps.get` resolves them into
|
|
20
|
+
`mix.lock`, which is committed. A module that imports a library the manifest does
|
|
21
|
+
not list compiles only by accident.
|
|
22
|
+
- A release is built with `mix release` where the repo defines one; do not run a
|
|
23
|
+
`mix run` against a release's expectations.
|
|
24
|
+
|
|
25
|
+
## Style
|
|
26
|
+
|
|
27
|
+
- Pattern-match on the way in, not guard-and-raise on the way out. A function that
|
|
28
|
+
takes a map and digs three levels deep is a boundary somebody refused to name;
|
|
29
|
+
match the shape at the edge and pass values in.
|
|
30
|
+
- Errors are returned, not raised, for expected failures; `raise` is for bugs, not
|
|
31
|
+
for a missing record. A caller that cannot handle the failure is a defect in the
|
|
32
|
+
caller's contract, not a reason to crash.
|
|
33
|
+
- Prefer a small `struct` with named fields over a map of strings; a `%{}` that is
|
|
34
|
+
passed through five functions is a type somebody refused to give a name.
|
|
35
|
+
|
|
36
|
+
## Testing
|
|
37
|
+
|
|
38
|
+
- `mix test` is the gate; ExUnit is the runner, and a change that touches a module
|
|
39
|
+
must not break its neighbours.
|
|
40
|
+
- Assert on behaviour through the public API; a test that reaches into a private
|
|
41
|
+
field is coupled to an implementation it should not own.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
simt-id: go
|
|
3
|
+
simt-title: Go
|
|
4
|
+
simt-applies: [has-go-mod]
|
|
5
|
+
simt-priority: convention
|
|
6
|
+
simt-scope: ["go.mod", "**/*.go"]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Stack profile: Go
|
|
10
|
+
|
|
11
|
+
Applies to: `has-go-mod` — a module.
|
|
12
|
+
|
|
13
|
+
## Build and gates
|
|
14
|
+
|
|
15
|
+
- `go build ./...`, `go test ./...` are the entrypoints; `go vet ./...` runs with
|
|
16
|
+
them. Never rely on a file the build does not include.
|
|
17
|
+
- Format with `gofmt` (or `go fmt ./...`). A diff that is only formatting is a
|
|
18
|
+
defect: the gate fails closed on it.
|
|
19
|
+
- Depend on the module, not on a `GOPATH` layout. A package import that resolves
|
|
20
|
+
only because of a local checkout is a broken dependency.
|
|
21
|
+
|
|
22
|
+
## Style
|
|
23
|
+
|
|
24
|
+
- Errors are values, not exceptions. Wrap with `fmt.Errorf("...: %w", err)` so the
|
|
25
|
+
caller can `errors.Is`/`errors.As`; a bare string loses the chain.
|
|
26
|
+
- `context.Context` is the first parameter of anything that can be cancelled; never
|
|
27
|
+
store one in a struct.
|
|
28
|
+
- Table-driven tests: one `for range` over named cases beats ten copy-pasted
|
|
29
|
+
subtests.
|
|
30
|
+
|
|
31
|
+
## Testing
|
|
32
|
+
|
|
33
|
+
- `go test ./...` is the gate; a package with no test file is allowed to have none,
|
|
34
|
+
but a change that touches one must not break its neighbours.
|
|
35
|
+
- Benchmark with `-bench` only where the number is a decision; an unowned benchmark
|
|
36
|
+
is decoration.
|
package/rules/stacks/jvm.md
CHANGED
|
@@ -6,6 +6,8 @@ simt-title: JVM backend
|
|
|
6
6
|
# or Ktor service received rules about Compose, Wear OS application ids and
|
|
7
7
|
# instrumentation tests — advice with nothing to apply it to (S28, ADR-047).
|
|
8
8
|
simt-applies: [gradle, has-maven-pom, kotlin, java]
|
|
9
|
+
simt-priority: convention
|
|
10
|
+
simt-scope: ["build.gradle", "build.gradle.kts", "pom.xml", "settings.gradle", "settings.gradle.kts"]
|
|
9
11
|
---
|
|
10
12
|
|
|
11
13
|
# Stack profile: JVM backend
|
package/rules/stacks/node.md
CHANGED
package/rules/stacks/python.md
CHANGED
|
@@ -6,6 +6,8 @@ simt-title: React Native
|
|
|
6
6
|
# CSS logical properties, a browser matrix, WCAG reflow at 400 % — none of which a
|
|
7
7
|
# native surface has (S28, ADR-047).
|
|
8
8
|
simt-applies: [react-native]
|
|
9
|
+
simt-priority: convention
|
|
10
|
+
simt-scope: ["app.json", "metro.config.js", "ios/**", "android/**"]
|
|
9
11
|
---
|
|
10
12
|
|
|
11
13
|
# Stack profile: React Native
|
|
@@ -6,6 +6,8 @@ simt-title: React web
|
|
|
6
6
|
# listing the bare language tags (`typescript`, `vitest`, `node-web`, `less`) put
|
|
7
7
|
# this profile on backend and static repositories that share them (S28, ADR-047).
|
|
8
8
|
simt-applies: [react-dom, react-router, maplibre, firebase]
|
|
9
|
+
simt-priority: convention
|
|
10
|
+
simt-scope: ["src/**/*.{ts,tsx,js,jsx}", "index.html"]
|
|
9
11
|
---
|
|
10
12
|
|
|
11
13
|
# Stack profile: React web
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
simt-id: ruby
|
|
3
|
+
simt-title: Ruby
|
|
4
|
+
simt-applies: [has-gemfile]
|
|
5
|
+
simt-priority: convention
|
|
6
|
+
simt-scope: ["Gemfile", "**/*.rb"]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Stack profile: Ruby
|
|
10
|
+
|
|
11
|
+
Applies to: `has-gemfile` — a Bundled application or gem.
|
|
12
|
+
|
|
13
|
+
## Build and gates
|
|
14
|
+
|
|
15
|
+
- `bundle install` then `bundle exec rspec` are the entrypoints; run every command
|
|
16
|
+
through `bundle exec`, never a bare binary, so the Gemfile.lock is the only
|
|
17
|
+
source of truth for versions.
|
|
18
|
+
- `bundle exec rubocop` is the lint gate where the repo has a `.rubocop.yml`; a
|
|
19
|
+
cop the repo disabled is a decision, and re-enabling it is a change, not a fix.
|
|
20
|
+
- `Gemfile.lock` is committed for an application; for a library it is not, and the
|
|
21
|
+
distinction is a decision the repo made, not one to reverse silently.
|
|
22
|
+
|
|
23
|
+
## Style
|
|
24
|
+
|
|
25
|
+
- Frozen string literals (`# frozen_string_literal: true`) at the top of new files
|
|
26
|
+
where the repo already uses them; a mixed repo is a transition, not a licence to
|
|
27
|
+
pick either.
|
|
28
|
+
- Prefer `case`/`when` or a hash over a chain of `if`/`elsif` on one variable; a
|
|
29
|
+
five-branch if-chain is a lookup table somebody refused to name.
|
|
30
|
+
- `raise` with a message that names the failure and the value that caused it; a
|
|
31
|
+
bare `raise` is a defect the next reader cannot debug.
|
|
32
|
+
|
|
33
|
+
## Testing
|
|
34
|
+
|
|
35
|
+
- RSpec is the runner where the repo has one; do not add a second framework to a
|
|
36
|
+
repo that has one.
|
|
37
|
+
- Assert on behaviour through the public interface; a spec that stubs everything
|
|
38
|
+
and asserts on the stub is testing the stub, not the code.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
simt-id: rust
|
|
3
|
+
simt-title: Rust
|
|
4
|
+
simt-applies: [has-cargo-toml]
|
|
5
|
+
simt-priority: convention
|
|
6
|
+
simt-scope: ["Cargo.toml", "src/**/*.rs"]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Stack profile: Rust
|
|
10
|
+
|
|
11
|
+
Applies to: `has-cargo-toml` — a Cargo workspace or crate.
|
|
12
|
+
|
|
13
|
+
## Build and gates
|
|
14
|
+
|
|
15
|
+
- `cargo build`, `cargo test` are the entrypoints; a workspace builds with
|
|
16
|
+
`cargo build --workspace`. Never invoke a toolchain the manifest does not pin.
|
|
17
|
+
- Format with `cargo fmt`; lint with `cargo clippy -- -D warnings`. A warning is a
|
|
18
|
+
defect, not a note: fix it or the gate fails.
|
|
19
|
+
- Pin the toolchain in `rust-toolchain.toml` where one is committed; a rustup
|
|
20
|
+
default on the developer's machine is not a floor anything enforces.
|
|
21
|
+
|
|
22
|
+
## Style
|
|
23
|
+
|
|
24
|
+
- No `unwrap()` or `expect()` outside tests. A panic path in library code is an
|
|
25
|
+
unhandled error; return a `Result` and let the caller decide.
|
|
26
|
+
- `#[must_use]` on fallible constructors; a `Result` that is ignored is a bug the
|
|
27
|
+
compiler should flag.
|
|
28
|
+
- Prefer a small `enum` of states over booleans; a pair of `bool` fields is four
|
|
29
|
+
states, one of which is always wrong.
|
|
30
|
+
|
|
31
|
+
## Testing
|
|
32
|
+
|
|
33
|
+
- Unit tests live in `#[cfg(test)]` next to the code; integration tests in `tests/`
|
|
34
|
+
exercise the public API only.
|
|
35
|
+
- Assert on values, not on panic messages. A test that asserts a panic string has
|
|
36
|
+
coupled itself to an implementation detail it should not own.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
simt-id: swift
|
|
3
|
+
simt-title: Swift
|
|
4
|
+
simt-applies: [has-swift-package]
|
|
5
|
+
simt-priority: convention
|
|
6
|
+
simt-scope: ["Package.swift", "**/*.swift"]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Stack profile: Swift
|
|
10
|
+
|
|
11
|
+
Applies to: `has-swift-package` — a Swift package.
|
|
12
|
+
|
|
13
|
+
## Build and gates
|
|
14
|
+
|
|
15
|
+
- `swift build`, `swift test` are the entrypoints; `swift format lint -r .` is the
|
|
16
|
+
formatting gate. A platform build (Xcode) is a different question from a package
|
|
17
|
+
build — do not conflate the two in one command.
|
|
18
|
+
- Pin the toolchain where the repo does (a `Package.swift` tools version or a
|
|
19
|
+
pinned Xcode); a compiler on the developer's machine is not a floor anything
|
|
20
|
+
enforces.
|
|
21
|
+
- Dependencies are declared in `Package.swift`; a package that imports a module the
|
|
22
|
+
manifest does not list builds only by accident.
|
|
23
|
+
|
|
24
|
+
## Style
|
|
25
|
+
|
|
26
|
+
- Concurrency is checked, not assumed: `Sendable` conformance is a decision. A
|
|
27
|
+
`@unchecked Sendable` that papers over a real race is a defect the compiler was
|
|
28
|
+
told to stop reporting.
|
|
29
|
+
- `throws` for expected failures, not `fatalError`. A crash path in library code is
|
|
30
|
+
an unhandled error; return a `Result` or throw.
|
|
31
|
+
- Prefer a small `enum` of states over optionals of optionals; a triple `??` is a
|
|
32
|
+
state machine somebody refused to name.
|
|
33
|
+
|
|
34
|
+
## Testing
|
|
35
|
+
|
|
36
|
+
- `swift test` is the gate; a target with no tests is allowed to have none, but a
|
|
37
|
+
change that touches one must not break its neighbours.
|
|
38
|
+
- Assert on values through the public API; a test that reaches into an internal
|
|
39
|
+
field is coupled to an implementation it should not own.
|