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.
Files changed (90) hide show
  1. package/CHANGELOG.md +375 -7
  2. package/README.md +4 -4
  3. package/docs/decisions/ADR-019-a-declared-command-is-the-runner.md +27 -0
  4. package/docs/decisions/ADR-059-the-version-a-repository-runs-is-the-one-it-pinned.md +75 -0
  5. package/docs/decisions/ADR-060-a-repository-with-nothing-to-run-is-adopted-and-says-so.md +131 -0
  6. package/docs/decisions/ADR-061-the-skill-registry-is-workspace-data.md +113 -0
  7. package/docs/decisions/ADR-062-a-fresh-adoption-seeds-the-gates-it-can-run.md +94 -0
  8. package/docs/decisions/ADR-063-the-adoption-escape-hatch-is-opt-in.md +69 -0
  9. package/docs/decisions/ADR-064-the-context-meter-counts-what-the-tool-wrote.md +107 -0
  10. package/docs/decisions/ADR-065-a-pack-is-one-shared-rule-set-pinned-by-digest.md +97 -0
  11. package/docs/decisions/ADR-066-a-monorepo-projects-once-per-package.md +115 -0
  12. package/docs/decisions/ADR-067-which-harnesses-read-agents-md-is-a-column.md +110 -0
  13. package/docs/decisions/ADR-068-the-mcp-revision-list-cites-what-was-read.md +111 -0
  14. package/docs/decisions/ADR-069-the-conformance-lint-checks-the-published-specification.md +90 -0
  15. package/docs/decisions/ADR-070-the-json-payload-carries-its-own-version.md +91 -0
  16. package/docs/decisions/ADR-071-a-rule-that-can-be-narrowed-is-written-narrow.md +106 -0
  17. package/docs/decisions/ADR-072-the-budget-is-a-declaration-beside-the-gates.md +104 -0
  18. package/docs/decisions/ADR-073-a-kernel-is-a-smaller-projection-the-repository-asks-for.md +92 -0
  19. package/docs/decisions/README.md +17 -0
  20. package/package.json +1 -2
  21. package/rules/core.md +1 -0
  22. package/rules/stacks/android.md +2 -0
  23. package/rules/stacks/cloudflare-workers.md +2 -0
  24. package/rules/stacks/connectiq.md +1 -0
  25. package/rules/stacks/container.md +2 -0
  26. package/rules/stacks/dart.md +40 -0
  27. package/rules/stacks/deno.md +38 -0
  28. package/rules/stacks/dotnet.md +37 -0
  29. package/rules/stacks/elixir.md +41 -0
  30. package/rules/stacks/go.md +36 -0
  31. package/rules/stacks/jvm.md +2 -0
  32. package/rules/stacks/node.md +2 -0
  33. package/rules/stacks/python.md +2 -0
  34. package/rules/stacks/react-native.md +2 -0
  35. package/rules/stacks/react-web.md +2 -0
  36. package/rules/stacks/ruby.md +38 -0
  37. package/rules/stacks/rust.md +36 -0
  38. package/rules/stacks/swift.md +39 -0
  39. package/rules/stacks/typescript.md +2 -0
  40. package/rules/stacks/uv.md +2 -0
  41. package/src/argv.mjs +54 -6
  42. package/src/ci/dependabot.mjs +1 -1
  43. package/src/ci/workflow.mjs +33 -11
  44. package/src/commands/compare.mjs +15 -0
  45. package/src/commands/graph.mjs +58 -25
  46. package/src/commands/harness.mjs +150 -0
  47. package/src/commands/help.mjs +37 -5
  48. package/src/commands/hooks.mjs +173 -57
  49. package/src/commands/index.mjs +95 -69
  50. package/src/commands/init.mjs +100 -20
  51. package/src/commands/mcp.mjs +20 -5
  52. package/src/commands/rules.mjs +96 -60
  53. package/src/commands/skills.mjs +189 -86
  54. package/src/commands/skillsinstall.mjs +20 -5
  55. package/src/commands/sync.mjs +213 -15
  56. package/src/commands/tokens.mjs +138 -0
  57. package/src/compare/hooksstate.mjs +96 -9
  58. package/src/compare/row.mjs +107 -7
  59. package/src/decisions/index.mjs +21 -6
  60. package/src/graph/load.mjs +125 -14
  61. package/src/graph/spec.mjs +227 -0
  62. package/src/graph/speccheck.mjs +68 -0
  63. package/src/hooks/capability.mjs +48 -4
  64. package/src/hooks/dispatcher.mjs +84 -7
  65. package/src/hooks/gates.mjs +0 -2
  66. package/src/hooks/resolve.mjs +24 -25
  67. package/src/hooks/shim.mjs +29 -5
  68. package/src/identity.mjs +0 -11
  69. package/src/mcp/schema.mjs +139 -0
  70. package/src/mcp/server.mjs +197 -20
  71. package/src/mcp/tools.mjs +290 -3
  72. package/src/measure/context.mjs +371 -0
  73. package/src/monorepo.mjs +339 -0
  74. package/src/output/index.mjs +86 -9
  75. package/src/package-version.mjs +74 -0
  76. package/src/packs.mjs +182 -0
  77. package/src/projection/conflicts.mjs +56 -5
  78. package/src/projection/manifest.mjs +335 -25
  79. package/src/projection/mdc.mjs +4 -13
  80. package/src/projection/project.mjs +139 -8
  81. package/src/projection/rules.mjs +308 -27
  82. package/src/projection/surfaces.mjs +133 -15
  83. package/src/projection/targets.mjs +90 -7
  84. package/src/resolve/eval.mjs +64 -0
  85. package/src/resolve/fingerprint.mjs +1 -0
  86. package/src/resolve/near.mjs +26 -7
  87. package/src/resolve/resolve.mjs +1 -1
  88. package/src/resolve/source.mjs +1 -1
  89. package/src/workspace.mjs +0 -9
  90. 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.
@@ -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.0",
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
@@ -2,6 +2,7 @@
2
2
  simt-id: core
3
3
  simt-title: Shared rules
4
4
  simt-applies: always
5
+ simt-priority: hard
5
6
  ---
6
7
 
7
8
  # Shared rules
@@ -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)
@@ -2,6 +2,7 @@
2
2
  simt-id: connectiq
3
3
  simt-title: Connect IQ (Monkey C)
4
4
  simt-applies: [connectiq]
5
+ simt-priority: convention
5
6
  ---
6
7
 
7
8
  # Stack profile: Connect IQ (Monkey C)
@@ -2,6 +2,8 @@
2
2
  simt-id: container
3
3
  simt-title: Containers
4
4
  simt-applies: [has-dockerfile]
5
+ simt-priority: convention
6
+ simt-scope: ["Dockerfile", "*.dockerfile"]
5
7
  simt-fallback: false
6
8
  ---
7
9
 
@@ -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.
@@ -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
@@ -2,6 +2,8 @@
2
2
  simt-id: node
3
3
  simt-title: Node (general)
4
4
  simt-applies: [node]
5
+ simt-priority: convention
6
+ simt-scope: ["package.json", "tsconfig.json", "jsconfig.json", ".nvmrc"]
5
7
  simt-fallback: true
6
8
  ---
7
9
 
@@ -2,6 +2,8 @@
2
2
  simt-id: python
3
3
  simt-title: Python
4
4
  simt-applies: [python]
5
+ simt-priority: convention
6
+ simt-scope: ["pyproject.toml", "requirements.txt", "setup.py", "*.py"]
5
7
  ---
6
8
 
7
9
  # Stack profile: Python
@@ -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.