simt-dev 0.2.0__py3-none-any.whl

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 (138) hide show
  1. python/simt_dev/__init__.py +32 -0
  2. python/simt_dev/cli.py +225 -0
  3. simt_dev/__init__.py +32 -0
  4. simt_dev/cli.py +225 -0
  5. simt_dev/vendored/CHANGELOG.md +166 -0
  6. simt_dev/vendored/bin/simt-dev.mjs +28 -0
  7. simt_dev/vendored/docs/decisions/ADR-001-push-requires-approval.md +42 -0
  8. simt_dev/vendored/docs/decisions/ADR-002-refactor-blast-radius.md +45 -0
  9. simt_dev/vendored/docs/decisions/ADR-003-test-breadth.md +46 -0
  10. simt_dev/vendored/docs/decisions/ADR-004-hook-owns-format.md +45 -0
  11. simt_dev/vendored/docs/decisions/ADR-005-file-length.md +42 -0
  12. simt_dev/vendored/docs/decisions/ADR-006-coverage-bar.md +59 -0
  13. simt_dev/vendored/docs/decisions/ADR-007-section-skeleton.md +54 -0
  14. simt_dev/vendored/docs/decisions/ADR-008-verdict-vocabulary.md +59 -0
  15. simt_dev/vendored/docs/decisions/ADR-009-skill-layout.md +48 -0
  16. simt_dev/vendored/docs/decisions/ADR-010-runner-invocation.md +56 -0
  17. simt_dev/vendored/docs/decisions/ADR-011-windows-support.md +52 -0
  18. simt_dev/vendored/docs/decisions/ADR-012-drop-windows-support.md +59 -0
  19. simt_dev/vendored/docs/decisions/ADR-013-genericity-by-facts-and-local-profiles.md +114 -0
  20. simt_dev/vendored/docs/decisions/ADR-014-workspace-is-configuration.md +211 -0
  21. simt_dev/vendored/docs/decisions/ADR-015-a-source-is-content-not-a-location.md +117 -0
  22. simt_dev/vendored/docs/decisions/ADR-016-a-divergent-rule-is-retired-not-merged.md +180 -0
  23. simt_dev/vendored/docs/decisions/ADR-017-a-declared-gate-must-be-runnable.md +89 -0
  24. simt_dev/vendored/docs/decisions/ADR-018-an-unset-hooks-path-is-not-an-absent-hook.md +183 -0
  25. simt_dev/vendored/docs/decisions/ADR-019-a-declared-command-is-the-runner.md +110 -0
  26. simt_dev/vendored/docs/decisions/ADR-020-a-rule-is-written-per-surface-and-pointed-not-copied.md +100 -0
  27. simt_dev/vendored/docs/decisions/ADR-021-one-surface-per-repo.md +94 -0
  28. simt_dev/vendored/docs/decisions/ADR-022-a-key-simt-dev-reads-is-prefixed.md +98 -0
  29. simt_dev/vendored/docs/decisions/ADR-023-the-gate-ships-as-a-file-the-repo-owns.md +106 -0
  30. simt_dev/vendored/docs/decisions/ADR-024-simt-dev-overwrites-its-own-gate-file.md +111 -0
  31. simt_dev/vendored/docs/decisions/ADR-025-a-baseline-is-a-region-in-a-file-the-repo-owns.md +153 -0
  32. simt_dev/vendored/docs/decisions/ADR-026-a-config-without-a-region-is-a-file-the-repo-owns.md +117 -0
  33. simt_dev/vendored/docs/decisions/ADR-027-the-one-gitattributes-line-that-is-neither-dangerous-nor-empty.md +130 -0
  34. simt_dev/vendored/docs/decisions/ADR-028-a-base-file-is-a-floor-so-the-disagreement-it-cannot-repair-becomes-a-check.md +131 -0
  35. simt_dev/vendored/docs/decisions/ADR-029-the-pins-a-repository-declares-are-its-own.md +120 -0
  36. simt_dev/vendored/docs/decisions/ADR-030-what-a-plan-does-not-ship-is-a-decision-with-a-record.md +114 -0
  37. simt_dev/vendored/docs/decisions/ADR-031-the-suite-stages-the-environment-it-needs.md +129 -0
  38. simt_dev/vendored/docs/decisions/ADR-032-provenance-is-a-content-digest-not-a-commit.md +141 -0
  39. simt_dev/vendored/docs/decisions/ADR-033-three-plan-rows-closed-by-decision-not-by-authoring.md +82 -0
  40. simt_dev/vendored/docs/decisions/ADR-034-a-staged-tool-refuses-a-partially-staged-file.md +91 -0
  41. simt_dev/vendored/docs/decisions/ADR-035-one-write-decision-and-one-json-document.md +119 -0
  42. simt_dev/vendored/docs/decisions/ADR-036-a-writer-that-writes-says-so-and-the-instructions-naming-it-move-with-it.md +103 -0
  43. simt_dev/vendored/docs/decisions/ADR-037-unchanged-means-writing-would-not-change-a-byte.md +100 -0
  44. simt_dev/vendored/docs/decisions/ADR-038-the-gate-filter-belongs-to-every-manifest-writer.md +95 -0
  45. simt_dev/vendored/docs/decisions/ADR-039-a-ci-step-runs-a-form-that-can-fail.md +101 -0
  46. simt_dev/vendored/docs/decisions/ADR-040-the-last-writer-that-wrote-by-default-was-not-the-one-named.md +158 -0
  47. simt_dev/vendored/docs/decisions/ADR-041-the-suite-installs-the-package-in-order-to-test-it.md +92 -0
  48. simt_dev/vendored/docs/decisions/ADR-042-a-declared-glob-must-match-a-path-built-from-it.md +94 -0
  49. simt_dev/vendored/docs/decisions/ADR-043-a-keyword-is-a-phrase-and-two-letters-is-a-word.md +90 -0
  50. simt_dev/vendored/docs/decisions/ADR-044-compatibility-is-not-evidence.md +116 -0
  51. simt_dev/vendored/docs/decisions/ADR-045-a-dashboard-that-counts-a-defect.md +119 -0
  52. simt_dev/vendored/docs/decisions/ADR-046-one-rule-one-copy.md +140 -0
  53. simt_dev/vendored/docs/decisions/ADR-047-a-profile-selects-on-what-a-repository-runs-on.md +141 -0
  54. simt_dev/vendored/docs/decisions/ADR-048-a-baseline-carries-only-keys-a-reader-honours.md +150 -0
  55. simt_dev/vendored/docs/decisions/ADR-049-a-ruling-that-a-later-record-moved-says-so-where-it-is-printed.md +153 -0
  56. simt_dev/vendored/docs/decisions/ADR-050-a-byte-count-is-not-a-region-and-updated-is-not-a-change-of-rules.md +102 -0
  57. simt_dev/vendored/docs/decisions/ADR-051-a-pointer-is-the-readers-only-route-so-it-is-not-a-convenience.md +134 -0
  58. simt_dev/vendored/docs/decisions/ADR-052-a-dependency-is-counted-against-the-budget-and-there-is-one-budget.md +101 -0
  59. simt_dev/vendored/docs/decisions/ADR-053-the-node-floor-is-22-20-0.md +49 -0
  60. simt_dev/vendored/docs/decisions/ADR-054-the-digest-covers-every-byte-that-leaves-the-package.md +100 -0
  61. simt_dev/vendored/docs/decisions/ADR-055-a-status-line-travels-so-it-may-not-name-a-location.md +98 -0
  62. simt_dev/vendored/docs/decisions/ADR-056-three-names-one-of-which-is-cheap-to-move.md +160 -0
  63. simt_dev/vendored/docs/decisions/ADR-057-an-identity-is-per-developer-and-a-repository-does-not-own-it.md +115 -0
  64. simt_dev/vendored/docs/decisions/ADR-058-one-tool-three-channels-and-the-order-is-forced.md +148 -0
  65. simt_dev/vendored/docs/decisions/README.md +530 -0
  66. simt_dev/vendored/graph/skills.graph.json +1622 -0
  67. simt_dev/vendored/package.json +43 -0
  68. simt_dev/vendored/rules/core.md +76 -0
  69. simt_dev/vendored/rules/mdc/intake-questions.mdc +50 -0
  70. simt_dev/vendored/rules/mdc/preview-postflight.mdc +61 -0
  71. simt_dev/vendored/rules/mdc/slice-commit.mdc +52 -0
  72. simt_dev/vendored/rules/mdc/test-gate.mdc +54 -0
  73. simt_dev/vendored/rules/stacks/android.md +63 -0
  74. simt_dev/vendored/rules/stacks/cloudflare-workers.md +65 -0
  75. simt_dev/vendored/rules/stacks/connectiq.md +62 -0
  76. simt_dev/vendored/rules/stacks/container.md +31 -0
  77. simt_dev/vendored/rules/stacks/jvm.md +54 -0
  78. simt_dev/vendored/rules/stacks/node.md +45 -0
  79. simt_dev/vendored/rules/stacks/python.md +48 -0
  80. simt_dev/vendored/rules/stacks/react-native.md +58 -0
  81. simt_dev/vendored/rules/stacks/react-web.md +61 -0
  82. simt_dev/vendored/rules/stacks/typescript.md +21 -0
  83. simt_dev/vendored/rules/stacks/uv.md +32 -0
  84. simt_dev/vendored/src/argv.mjs +116 -0
  85. simt_dev/vendored/src/ci/dependabot.mjs +268 -0
  86. simt_dev/vendored/src/ci/workflow.mjs +360 -0
  87. simt_dev/vendored/src/commands/ci.mjs +285 -0
  88. simt_dev/vendored/src/commands/compare.mjs +195 -0
  89. simt_dev/vendored/src/commands/graph.mjs +73 -0
  90. simt_dev/vendored/src/commands/help.mjs +108 -0
  91. simt_dev/vendored/src/commands/hooks.mjs +471 -0
  92. simt_dev/vendored/src/commands/index.mjs +241 -0
  93. simt_dev/vendored/src/commands/init.mjs +632 -0
  94. simt_dev/vendored/src/commands/mcp.mjs +63 -0
  95. simt_dev/vendored/src/commands/rules.mjs +374 -0
  96. simt_dev/vendored/src/commands/skills.mjs +647 -0
  97. simt_dev/vendored/src/commands/skillsinstall.mjs +515 -0
  98. simt_dev/vendored/src/commands/sync.mjs +331 -0
  99. simt_dev/vendored/src/commands/upgrade.mjs +202 -0
  100. simt_dev/vendored/src/compare/discover.mjs +212 -0
  101. simt_dev/vendored/src/compare/hooksstate.mjs +159 -0
  102. simt_dev/vendored/src/compare/row.mjs +299 -0
  103. simt_dev/vendored/src/decisions/index.mjs +536 -0
  104. simt_dev/vendored/src/graph/load.mjs +541 -0
  105. simt_dev/vendored/src/hooks/capability.mjs +129 -0
  106. simt_dev/vendored/src/hooks/dispatcher.mjs +268 -0
  107. simt_dev/vendored/src/hooks/gates.mjs +144 -0
  108. simt_dev/vendored/src/hooks/resolve.mjs +194 -0
  109. simt_dev/vendored/src/hooks/shim.mjs +140 -0
  110. simt_dev/vendored/src/hooks/where.mjs +118 -0
  111. simt_dev/vendored/src/identity.mjs +218 -0
  112. simt_dev/vendored/src/locate.mjs +75 -0
  113. simt_dev/vendored/src/mcp/server.mjs +167 -0
  114. simt_dev/vendored/src/mcp/tools.mjs +410 -0
  115. simt_dev/vendored/src/name.mjs +95 -0
  116. simt_dev/vendored/src/output/index.mjs +96 -0
  117. simt_dev/vendored/src/projection/baselines.mjs +209 -0
  118. simt_dev/vendored/src/projection/conflicts.mjs +205 -0
  119. simt_dev/vendored/src/projection/manifest.mjs +478 -0
  120. simt_dev/vendored/src/projection/markers.mjs +349 -0
  121. simt_dev/vendored/src/projection/mdc.mjs +327 -0
  122. simt_dev/vendored/src/projection/project.mjs +504 -0
  123. simt_dev/vendored/src/projection/rules.mjs +640 -0
  124. simt_dev/vendored/src/projection/surfaces.mjs +398 -0
  125. simt_dev/vendored/src/projection/targets.mjs +221 -0
  126. simt_dev/vendored/src/resolve/fingerprint.mjs +553 -0
  127. simt_dev/vendored/src/resolve/near.mjs +61 -0
  128. simt_dev/vendored/src/resolve/resolve.mjs +457 -0
  129. simt_dev/vendored/src/resolve/source.mjs +105 -0
  130. simt_dev/vendored/src/toolchain.mjs +202 -0
  131. simt_dev/vendored/src/tsconfig.mjs +314 -0
  132. simt_dev/vendored/src/upgrade/plan.mjs +203 -0
  133. simt_dev/vendored/src/workspace.mjs +289 -0
  134. simt_dev-0.2.0.dist-info/METADATA +150 -0
  135. simt_dev-0.2.0.dist-info/RECORD +138 -0
  136. simt_dev-0.2.0.dist-info/WHEEL +4 -0
  137. simt_dev-0.2.0.dist-info/entry_points.txt +2 -0
  138. simt_dev-0.2.0.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,114 @@
1
+ # ADR-013 — Genericity comes from emitted facts and repo-local profiles, not from a bigger hardcoded tag table
2
+
3
+ Status: **accepted** · 2026-10-05
4
+ Affects: the `simt-dev` CLI (fingerprint, profile selection, registry validation)
5
+
6
+ ## Context
7
+
8
+ The engine is already only loosely coupled to SIMT. Measured, not asserted: `src/argv.mjs`,
9
+ `src/resolve/resolve.mjs` and `src/graph/load.mjs` contain **zero** non-comment,
10
+ non-string references to SIMT. Everywhere else, "SIMT" appears as ADR citations in comments
11
+ documenting *why*, plus one hardcoded npm scope in the hook shim. The project is named SIMT;
12
+ the mechanisms are not.
13
+
14
+ The coupling that does exist is in the **vocabulary**, not the code. `fingerprint()` emits 17
15
+ tags from three hardcoded tables (`MARKERS`, `EXTENSION_TAGS`, `DEP_TAGS`) plus a literal
16
+ `'wear-os'`, and those tables are keyed by *curated product* names: `connectiq`,
17
+ `maplibre`, `hono`, `react-router`. That has three consequences, and only the first is
18
+ obvious:
19
+
20
+ 1. **A new ecosystem needs a code change.** Detecting Django is one line in `MARKERS`; the
21
+ engine then hands a Django repo the generic `node` fallback profile, which is wrong advice,
22
+ and there is nowhere to put the correct profile because `rules/stacks/` is central-only.
23
+ 2. **The tag names encode opinions that age badly.** `hono` and `maplibre` are libraries, not
24
+ stacks. A profile that selects on a library breaks the day the library is swapped.
25
+ 3. **Detection is untestable in the abstract.** There is no way to ask "what did the tool
26
+ actually observe?" other than reading three tables.
27
+
28
+ There is also an unresolved question from the design review: a **hosted MCP service** would
29
+ make the tool more capable, but it was not clear whether it would make it more *generic*, more
30
+ *accurate*, or merely more available.
31
+
32
+ ## Ruling
33
+
34
+ ### 1. Detection emits facts; profiles select on facts
35
+
36
+ `fingerprint()` gains a second, orthogonal output: **`facts`** — observable, ecosystem-neutral
37
+ properties of the working tree, named `has-*` (`has-pyproject`, `has-go-mod`,
38
+ `has-cargo-toml`, `has-migrations-dir`, `has-dockerfile`, …). Facts are derived from evidence,
39
+ never from a product taxonomy, so there is no table to grow when a new ecosystem arrives: the
40
+ fact was already being observed.
41
+
42
+ The existing product tags remain, because a profile that genuinely wants "this is a Cloudflare
43
+ Worker" should say so in words that mean something to a reader. But a profile may now select
44
+ on **either**, and new profiles are expected to prefer facts where a fact will do:
45
+
46
+ - `applies: [has-cargo-toml]` — honest, survives the framework being replaced
47
+ - `applies: [cloudflare-workers]` — precise, dies with the platform
48
+
49
+ Selection is the union of both sets. A profile therefore needs **no engine change** to be
50
+ usable by an ecosystem nobody has heard of.
51
+
52
+ ### 2. Profiles are content, and content may live in the repo
53
+
54
+ `simt-dev init` prints where local profiles live and the manifest gains a `profiles` array
55
+ naming profile ids. The directory needs no creation — the first file in it creates it, and
56
+ an empty directory would not survive git anyway. Central profiles ship in `rules/stacks/`; **repo-local profiles are read from
57
+ `.simt-dev/profiles/*.md`**, use the same frontmatter contract (ADR-009), and are selected
58
+ with the same matcher. A local profile may declare `fallback: true`.
59
+
60
+ A repo that ships its own profile has made a decision about its own conventions. It does not
61
+ need a central contribution, a release, or a maintainer's permission — and when it is wrong,
62
+ the fix is a one-file PR in the repo that is wrong, which is the correct blast radius
63
+ (ADR-002).
64
+
65
+ Local profiles are **additive and shadowing**: a local `id` replaces a central one with the
66
+ same id, and otherwise both are considered. Central content is never silently overridden in
67
+ place — it is superseded by an explicit, reviewable file.
68
+
69
+ ### 3. A hosted service may serve authoring; it may never serve resolution
70
+
71
+ These are different products and must not be conflated.
72
+
73
+ | | Authoring | Resolution |
74
+ |---|---|---|
75
+ | Hosted MCP is useful | yes — discovering profiles, learning an ecosystem, publishing findings | **no** |
76
+ | Non-determinism | tolerable: a human reviews the output | fatal: "did the agent follow the rules?" becomes unanswerable |
77
+ | Offline | irrelevant | required: a hook and a gate must not depend on a network call |
78
+ | Diffable in a PR | partially | required: `simt-dev check` output is the gate's whole product |
79
+ | Fails | loudly, at authoring time | must fail **closed** with a remedy |
80
+
81
+ A hosted resolver would make every gate in the estate depend on a network round trip and a
82
+ model's ranking, and would make CI results unreproducible. That is a worse tool, not a more
83
+ capable one.
84
+
85
+ **Community extensibility therefore ships as a registry of portable profile files** in a git
86
+ repository, fetched and pinned by the same single-SHA mechanism (ruling 4) that already pins
87
+ rules. Same trust model, no hosted runtime, and the diff a reviewer sees is the diff of a
88
+ profile file.
89
+
90
+ ## Consequences
91
+
92
+ - `fingerprint()` returns `{stack, facts, evidence, manifest, error}`. `evidence` gains the
93
+ file that proved each fact, so `simt-dev doctor` can answer "why did you think that?" without a
94
+ human re-deriving it.
95
+ - Every tag a profile may name is still emitted by the engine. The existing test — *tags used
96
+ by content but not emitted by the engine is the empty set* — now covers facts too, and the
97
+ registry validator and the profile loader share one derived vocabulary so they cannot
98
+ desynchronise.
99
+ - `graph lint` validates against the same derived vocabulary, so a profile naming a fact the
100
+ engine cannot emit fails at lint time rather than selecting nothing at runtime.
101
+ - Step 11 of the implementation sequence is genericity; it lands **before** `compare`, because
102
+ it changes the tag vocabulary and every later step is cheaper once it is settled.
103
+
104
+ ## Rejected
105
+
106
+ - **A larger hardcoded tag table.** Moves the wall, does not remove it. Every new ecosystem
107
+ would still need a maintainer, a code change and a release, and the names would still encode
108
+ today's libraries rather than the repo's actual properties.
109
+ - **Community profiles in a hosted registry served at runtime.** See the table above.
110
+ - **Semantic or embedding search as the mechanism for choosing rules.** Already rejected in
111
+ plan 0002 §3, and this ruling reinforces it: it is a resolution mechanism, so it inherits
112
+ every constraint in row two of the table above.
113
+ - **Fingerprinting on library names alone.** Precise today, and silently wrong the day a
114
+ dependency is replaced.
@@ -0,0 +1,211 @@
1
+ # ADR-014 — The workspace is configuration, and a single repo is a first-class case
2
+
3
+ Status: **accepted** · 2026-10-05
4
+ Affects: `simt-dev` CLI (workspace resolution, `compare`, `skills`, registry validation, packaging)
5
+
6
+ ## Context
7
+
8
+ ADR-013 made the *vocabulary* generic. It did not make the *filesystem layout* generic, and the
9
+ gap was invisible from inside the checkout. Three failures were measured rather than reasoned
10
+ about, by running the tool from a throwaway repo outside `/code/simt` and from a simulated
11
+ `node_modules` install:
12
+
13
+ 1. **Every `skills` command and `graph lint` fails outside the SIMT workspace.** `WORKSPACE_ROOT`
14
+ was `resolve(HERE, '..', '..', '..')` — three levels up from the source file. In the checkout
15
+ that is `/code/simt`, which is correct. In an npm install it is
16
+ `node_modules/@quazarmedia`:
17
+
18
+ | install location | `WORKSPACE_ROOT` resolves to |
19
+ |---|---|
20
+ | `/code/simt/simt-dev/src/graph` | `/code/simt` — correct |
21
+ | `…/node_modules/@quazarmedia/simt-dev/src/graph` | `…/node_modules/@quazarmedia` — wrong |
22
+
23
+ The symptom is `ENOENT` for all 38 sources. The *remedy* printed is
24
+ `run: simt-dev graph lint`, which is itself wrong, because the reader has no workspace to lint.
25
+
26
+ 2. **`rules why` cannot work in the published package.** `package.json` `files` lists
27
+ `bin src graph rules skills agents tooling hooks` and not `docs`. `npm pack` produces 39 files
28
+ and zero from `docs/`, so every ADR lookup fails in a real install. The whole provenance
29
+ feature — the reason `rules why` exists — is unusable outside the checkout.
30
+
31
+ 3. **`compare` treats a repo as a workspace and rejects it.** `--root <a-repo>` fails with
32
+ `contains no git repositories` and exit 3, because `discover` scans the *children* of the
33
+ given directory. The most common invocation outside a multi-repo workspace — point the tool at
34
+ one repo — is an error. The remedy, `no git repositories found under <path>`, is also
35
+ misleading: there is one, you are standing in it.
36
+
37
+ None of this was visible in the tests, because every test ran from the checkout. That is the
38
+ general lesson and it is the reason this record exists: **a tool's own layout is part of its
39
+ interface, and a test suite that only runs the tool from its source tree cannot detect that the
40
+ tool only works from its source tree.**
41
+
42
+ The related question — how does `simt-dev upgrade` know which repos to bump? — has the same root
43
+ cause. There is no consumer registry on disk. The only thing aware of sibling repos is
44
+ `compare`'s one-level-deep scan, rooted at `..` of the *current checkout*. That is why
45
+ `compare` already prints `run: simt-dev upgrade` for every drifted repo: the detection half exists and
46
+ the remedy is a dangling reference.
47
+
48
+ ## Ruling
49
+
50
+ ### 1. The workspace is configuration, resolved in a fixed, reported order
51
+
52
+ Precedence, highest first:
53
+
54
+ 1. `--workspace <path>` — an explicit flag always wins.
55
+ 2. `SIMT_DEV_WORKSPACE` — the environment variable, for CI and one-off invocations.
56
+ 3. A user config file at the platform's conventional location (§2).
57
+ 4. The sibling heuristic — the parent of this package, when that parent actually contains git
58
+ repositories. This is what makes a fresh clone of the checkout work with no configuration at
59
+ all, and it is a **heuristic, not a rule**: it is consulted last and it is only accepted when
60
+ it verifies.
61
+
62
+ **Every command that consults the workspace reports where it came from.** `simt-dev doctor` prints
63
+ the resolved root and its source. An invisible configuration is a configuration nobody can debug,
64
+ and a silent fallback to a wrong directory is the worst outcome available: every answer is then
65
+ confidently wrong.
66
+
67
+ ### 2. The user config file lives where the platform expects it, with a documented fallback
68
+
69
+ Candidate paths, searched in order; the first that exists wins, and the first is the one named
70
+ when reporting a path to create:
71
+
72
+ | Platform | Candidates, in order |
73
+ |---|---|
74
+ | any | `$XDG_CONFIG_HOME/simt-dev/config.json` |
75
+ | POSIX | `$HOME/.config/simt-dev/config.json` |
76
+ | macOS | `$HOME/Library/Application Support/simt-dev/config.json` |
77
+ | Windows | `%APPDATA%/simt-dev/config.json` |
78
+
79
+ `$XDG_CONFIG_HOME` first because it is the only way to *override* the location on a POSIX system,
80
+ and honouring an explicit override before guessing is the whole point. macOS is listed after the
81
+ XDG candidates deliberately: `~/Library/Application Support` is Apple's documented location for
82
+ application data, but a tool that honours XDG where set is friendlier to a developer who already
83
+ uses it, and the fallback means neither convention is unsupported.
84
+
85
+ The file is JSON. Its shape is deliberately **not** a workspace-shaped object:
86
+
87
+ ```json
88
+ {
89
+ "workspace": "/path/containing/repos"
90
+ }
91
+ ```
92
+
93
+ `workspace` is an optional key. An empty, absent or unreadable file is not an error — it means
94
+ "not configured", and resolution continues down the precedence list. Only a file that exists and
95
+ does not parse is reported, because that is a real mistake the reader can fix.
96
+
97
+ Windows appears in the table although [ADR-012](ADR-012-drop-windows-support.md) drops Windows
98
+ support. The lookup function stays total so it is testable on any host and correct if that
99
+ reversal ever happens; `package.json` still refuses to install on Windows.
100
+
101
+ ### 3. A workspace is optional; a single repo is a normal, supported case
102
+
103
+ Every command that needs a workspace must work with **one repo and no workspace at all**. Concretely:
104
+
105
+ - `compare --root <path>` where `<path>` is a repository compares that repository. Where it is a
106
+ directory, its git-repository children are compared. The distinction is detected, not configured,
107
+ because the two cases are already distinguishable from the filesystem.
108
+ - Discovery no longer reports "no git repositories" when it is standing in one.
109
+ - Nothing anywhere requires a second repo to exist.
110
+
111
+ ### 4. A missing workspace is a fact, reported once — never a fatal error
112
+
113
+ When no workspace is configured and the heuristic does not verify, the skill catalogue cannot be
114
+ resolved from disk. That is a **warning with one remedy**, not an error:
115
+
116
+ ```
117
+ WARNING skill sources are not available: no workspace configured
118
+ REMEDY set one with --workspace <path>, SIMT_DEV_WORKSPACE, or the config file
119
+ ```
120
+
121
+ Registry disk verification is **skipped** in that state, with the skip itself reported by
122
+ `graph lint`. The alternative — 38 `ENOENT` lines — teaches the reader that the lint is noisy,
123
+ and a lint that is always noisy is a lint that is always ignored.
124
+
125
+ The exit code follows the same principle as `compare`'s non-adoption rule
126
+ ([ADR-013](ADR-013-genericity-by-facts-and-local-profiles.md), and step 9 of plan 0002): exit
127
+ non-zero when something is **wrong**, zero when something is merely **absent**.
128
+
129
+ ### 5. The decision records ship with the package
130
+
131
+ `docs/decisions` is added to `package.json` `files`. A feature whose input is not packaged is not
132
+ a feature; `rules why` is the provenance mechanism the whole one-canonical-home doctrine rests
133
+ on, and it cannot depend on a git checkout being present.
134
+
135
+ `docs/plans` ships with it, because every non-zero exit prints a remedy pointing at
136
+ `docs/plans/0002-simt-dev-cli.md section 11`.
137
+
138
+ ### 6. "Absent", "wrong" and "moved" are three different facts
139
+
140
+ Section 4 settled the case where **no** workspace is configured. Writing the tests for it exposed
141
+ two more, and they are not the same case:
142
+
143
+ | State | Reported as |
144
+ |---|---|
145
+ | Nothing configured | `no workspace configured` + the remedy |
146
+ | Configured, directory **missing** | `the configured workspace <path> (from <source>) does not exist` |
147
+ | Configured, directory exists but **holds none of the named repositories** | `<path> contains none of the repositories the registry names (…)` |
148
+
149
+ All three are warnings and all three skip disk verification. What changed is the **specificity**
150
+ of the message, and that specificity is the whole value: "you configured nothing" and "you
151
+ configured the wrong directory" are different problems with different fixes, and the reader can
152
+ only fix the one they are told about.
153
+
154
+ The third state is the one that mattered. Existence is not a sufficient test for a workspace, and
155
+ before this section `--workspace <an empty directory>` produced 38 `ENOENT` lines reading as a
156
+ broken registry — precisely backwards, because the registry is fine and the pointer is wrong. So
157
+ verification requires that the workspace hold at least one of the repositories the registry
158
+ names, checked before any file is measured.
159
+
160
+ ### 7. A workspace can be declined, and a broken config is reported whatever outranks it
161
+
162
+ Two things the first implementation got wrong, both found by running the CLI rather than by
163
+ testing a function:
164
+
165
+ - **There was no way to say "no workspace".** The sibling heuristic always fired when nothing
166
+ was configured, so a reader who did not want a directory guessed for them had no way to refuse
167
+ — and no test running from the checkout could reach the unconfigured path at all, because the
168
+ checkout always has a verifying sibling. `--workspace off`, `SIMT_DEV_WORKSPACE=off` and
169
+ `"workspace": "off"` now all mean *declined*, and return the same shape as any other answer so
170
+ callers need no special case.
171
+ - **A config file that does not parse was only read if nothing outranked it.** Precedence decided
172
+ the *answer*, but it was also suppressing a *defect*: an unparseable config is a real mistake in
173
+ the reader's setup, and hiding it because `SIMT_DEV_WORKSPACE` happened to be set sends them
174
+ looking for a configuration problem in the wrong place. The config file is therefore read
175
+ first, and a parse failure is returned immediately.
176
+
177
+ That second bug is the standing lesson of this record, discovered by its own meta-lesson:
178
+ `configPaths` defaulted `env` to `{}` so that tests could inject a fake environment, which meant
179
+ `XDG_CONFIG_HOME` — the one documented way to *override* the config location — was ignored on
180
+ every real invocation. Every unit test passed `{env}` explicitly. **A test that supplies a
181
+ dependency explicitly cannot detect that the default for that dependency is wrong.**
182
+
183
+ ## Consequences
184
+
185
+ - The checkout keeps working with zero configuration, because the sibling heuristic is consulted
186
+ last and verified.
187
+ - A single developer with one repository never has to learn what a workspace is. `compare`,
188
+ `rules`, `sync`, `check`, `hooks` and `init` all work against one repo.
189
+ - The skill catalogue — which is SIMT content — degrades to a single warning outside the SIMT
190
+ workspace, while the *rules* engine stays fully functional, because `rules/` ships in the
191
+ package and `rules/*.md` has no external sources.
192
+ - `simt-dev upgrade` gets a real answer for consumer discovery: the resolved workspace, plus an
193
+ explicit `--root` for a single repo. No consumer list needs to be maintained in simt-dev,
194
+ because a repo that has not adopted cannot be listed by the thing it has not adopted.
195
+
196
+ ## Rejected
197
+
198
+ - **A consumer list inside simt-dev.** A repo that has not adopted has no manifest, so it cannot
199
+ appear in a manifest kept by the tool it has not adopted. Discovery has to come from the
200
+ filesystem.
201
+ - **Inferring the workspace from git remotes.** Fragile, and wrong in the common case: several
202
+ sibling repos legitimately share one remote organisation, and a personal fork of one repo has a
203
+ remote that points nowhere useful.
204
+ - **Requiring a workspace.** It would make the common case — one repo, one user, no org — pay a
205
+ configuration tax to use a tool whose rules engine is already ecosystem-neutral.
206
+ - **A machine-wide registry of skill sources.** A directory of symlinks into the workspace would
207
+ reintroduce exactly the cross-repo symlink fragility that made plan 0001 choose a generated
208
+ projection instead.
209
+ - **Treating an unresolvable workspace as a registry error.** Same failure as printing 38
210
+ `ENOENT` lines: it converts an absent thing into a loud failure and trains the reader to ignore
211
+ the output.
@@ -0,0 +1,117 @@
1
+ # ADR-015 — A source is identified by its content; the repository is a location, not a precondition
2
+
3
+ Status: **accepted** · 2026-10-05
4
+ Affects: `resolve/source.mjs`, `skills install`, `skills show`, `skills brief`
5
+
6
+ ## Context
7
+
8
+ [ADR-014](../decisions/ADR-014-workspace-is-configuration.md) made the workspace optional and
9
+ single-repo use first class. It fixed resolution, not lookup, and the next defect was found
10
+ immediately by the user who asked for the previous one.
11
+
12
+ Someone who cloned **only `repo-14`** has the `repo-14` body of `slice-commit` on disk at
13
+ `.agents/skills/slice-commit`. They have no workspace. The registry entry
14
+ `repo-14/.agents/skills/slice-commit` is satisfied by that file. Both commands refused:
15
+
16
+ ```
17
+ $ simt-dev skills install slice-commit
18
+ ERROR 2 slice-commit has 2 variants and none matches this repo (node)
19
+
20
+ $ simt-dev skills show slice-commit
21
+ (body unavailable: this installation has no workspace, so slice-commit has no body to fetch; …)
22
+ ```
23
+
24
+ The body was in the current directory for both.
25
+
26
+ The root cause is a definition that was never written down. `sources: [{repo, path, bytes, role}]`
27
+ resolves at exactly one place — `join(workspaceRoot, repo, path)`. There is a second place, and it is
28
+ not even exotic: **`<root>/<path>`**, because the repository being operated on is itself a location
29
+ for a declared source when the registry entry's `repo` is that repository.
30
+
31
+ The refusal was also wrong for a reason that had nothing to do with the workspace. `slice-commit`
32
+ has two variants, `web` and `api`, discriminated by stack tag. That clone's `package.json` emitted
33
+ `node`, which matches neither, so variant selection failed — while the clone's own 5048 bytes were
34
+ an exact answer sitting in the working tree. **The tool asked the manifest a question the manifest
35
+ could not answer, and ignored the evidence in front of it.**
36
+
37
+ ## Ruling
38
+
39
+ ### 1. A declared source is found by content, at every legal location
40
+
41
+ `resolve/source.mjs` exports `candidateLocations(source, {workspaceRoot, root})` — the workspace
42
+ location first, then the repo-relative location — and `locateSource(source, options)`, which accepts
43
+ a candidate **only when the measured size equals the declared size**.
44
+
45
+ Bytes are what make this safe rather than hopeful. Every source declares its size, and `graph lint`
46
+ already verifies every declared size against the file on disk. A candidate that exists at the right
47
+ path with the wrong size is not a degraded match; it is a different file, and it is rejected.
48
+
49
+ Two rules keep this from becoming a way to install the wrong body:
50
+
51
+ - **A workspace location wins over a repo location.** The canonical body beats the incidental copy.
52
+ - **Content decides identity, never location.**
53
+
54
+ ### 2. What the repo already holds is a resolution signal, not a fallback
55
+
56
+ `sourceHeldLocally(skill, root)` is a first-class input to source selection, placed after explicit
57
+ flags and after the repo's stack signals, and ahead of refusing. `chooseSource`'s precedence is now:
58
+
59
+ 1. `--from` — explicit. Obeyed even when it contradicts the repo's contents; the disagreement is a
60
+ note, because the author is answering a question the tool cannot.
61
+ 2. `--variant` — explicit, same treatment.
62
+ 3. **The repo's signals name exactly one variant.** The normal path.
63
+ 4. **The repo already holds exactly one declared source.** Its bytes are an exact answer, so this
64
+ settles both an unmatched repo (step 3 found nothing) and an ambiguous one (step 3 found several).
65
+ 5. Refuse — with the reason named.
66
+
67
+ Content beats taxonomy because a byte count cannot be a coincidence. A stack tag can.
68
+
69
+ ### 3. A contradiction is a refusal; an ambiguity is a decision
70
+
71
+ Two rules that look similar and are not:
72
+
73
+ - **Signals name one variant and the repo holds another** → **refuse.** One of two inputs is wrong
74
+ and the reader needs to know which; installing either silently picks a side.
75
+ - **Signals name several variants and the repo holds one of them** → **install that one**, because
76
+ the repo's bytes are not a guess. Reported as `the repo's own copy decides`.
77
+
78
+ ### 4. A file of the right shape and the wrong body gets its own message
79
+
80
+ `simt-dev skills install` reports `<path> exists but is not the declared body (5048 bytes declared)`.
81
+ The alternative message — "none of the variants matches this repo" — is true and useless: it sends
82
+ the reader to inspect their stack tags when the actual problem is the file in front of them.
83
+
84
+ ### 5. `skills show` and `skills brief` read a repo-local body, and say which
85
+
86
+ `sourcePath` tries the canonical source first and falls back to what the repo holds.
87
+ `skills show` gained a `body <workspace|repo> <path>` line, because a reader who has two candidate
88
+ locations needs to know which one answered. `skills install` gained a matching `read` line.
89
+
90
+ An install that cannot read a body **fails**. `skills show` that cannot read one still answers,
91
+ because a catalogue plus a pointer is useful with no workspace at all. The asymmetry is deliberate
92
+ and is the same asymmetry as ADR-014's: degrade the read, never the write.
93
+
94
+ ## Consequences
95
+
96
+ - A clone of any single repository is fully self-sufficient for `skills show`, `skills brief` and
97
+ `skills install`, with no configuration at all.
98
+ - A repo holding two declared sources at once (`{ambiguous: true}`) is refused: the registry and the
99
+ repo disagree, and neither is authoritative enough to break the tie.
100
+ - `graph lint` remains the thing that proves the registry still matches the world. It is unchanged,
101
+ and it is the reason a byte comparison is trustworthy rather than optimistic.
102
+ - The `--from` and `--variant` escapes are load-bearing: they are now the only ways to override the
103
+ repo's own contents, which is correct, because overriding the evidence should be deliberate.
104
+
105
+ ## Rejected
106
+
107
+ - **Treat any existing path as a match.** A near-miss body would install silently, which is worse
108
+ than refusing: the tool would have written a file it had not verified.
109
+ - **Ask for a workspace when the repo holds the body.** The workspace would then be required for the
110
+ one case where it is provably unnecessary.
111
+ - **Select the variant from the repo's directory name.** `<root>/.agents/skills/slice-commit` says
112
+ which skill it is, not which variant it is; only its bytes do.
113
+ - **Prefer the repo's copy over the workspace's.** A workspace checkout is the canonical body, and a
114
+ symlink fan-out can make them the same file; preferring either silently would hide a divergence.
115
+ - **Make variant selection advisory everywhere.** "Signals say api, the repo holds web" would
116
+ install one of them and print nothing, which is exactly the silent divergence variants exist to
117
+ prevent.
@@ -0,0 +1,180 @@
1
+ # ADR-016 — A divergent rule is retired, not merged
2
+
3
+ Status: **accepted** · 2026-10-06 · one ruling narrowed by [ADR-045](ADR-045-a-dashboard-that-counts-a-defect.md)
4
+ Affects: `rules/mdc/*.mdc`, `projection/mdc.mjs`, `projection/project.mjs`, `projection/conflicts.mjs`, `sync`, `check`, `init`
5
+
6
+ > **Narrowed by ADR-045.** The bullet below that says `compare` reports "using `AGENTS.md`
7
+ > only" still governs the row's **display**, which is where it was aimed. It was read more
8
+ > broadly than it was written, and `compare`'s *problems list* stayed silent about stale rule
9
+ > files as a result: seventeen of them across five adopted repositories, none named. The
10
+ > problems list is not a display — it is the exit code. Every other ruling here stands, which
11
+ > is why this is a narrowing and not a supersession.
12
+
13
+ ## Context
14
+
15
+ The estate carried three always-on `.mdc` rule families across three repos, in eight
16
+ files, with no two byte-identical.
17
+
18
+ The worst pair differed by **one byte** (`repo-14/.cursor/rules/intake-questions.mdc`
19
+ at 1280 against `repo-11`'s at 1281) while hiding three semantically loaded line
20
+ changes: a plan-tracker path rewritten to point at the other repo, `migrations` added to the
21
+ list of decisions worth asking about, and a requirement silently dropped. No size-, hash-,
22
+ hash-adjacency- or line-count-based duplicate detector could ever have flagged that pair. The
23
+ reason is not that the detectors were weak: it is that **divergence here is not a formatting
24
+ difference, it is a semantic loss**, and the smaller copy is the one missing a rule.
25
+
26
+ Reading all eight produced three findings that a variant mechanism cannot represent:
27
+
28
+ 1. `test-gate`'s fourth `BLOCK` bullet is **complementary across the pair, not alternative**.
29
+ `repo-14` blocks a consumer that requires an API field with no backward-compatibility
30
+ or deploy-order evidence; `repo-11` blocks a producer that ships without a test
31
+ asserting the new invariant. Each repo's reviewer was told to check only its own half.
32
+ 2. `preview-postflight`'s second Requirements bullet is likewise complementary, and looks
33
+ like a conflict because the two files put different rules in the same slot: `repo-14`
34
+ says set `SOCIAL_API_BASE_URL` in the Cloudflare Workers Builds preview environment;
35
+ `repo-11` says credentials live in the operator keychain only, never in source,
36
+ chat, D1 or logs.
37
+ 3. Two deletions left no trace. `repo-11` dropped `repo-14`'s worked example
38
+ (`Partial pass (e.g. 6/8 pass) is a hard blocker`) — the example is what does the work,
39
+ and its removal is invisible in a diff review because the rule's normative force looks
40
+ unchanged. `repo-13` dropped the entire `## Allowed Without Account` section, so
41
+ it has no documented set of probes that are legal without credentials.
42
+
43
+ A variant mechanism models "these are the same rule, rendered per stack". None of these three
44
+ is that. Two are **unions**, and one is a **deletion that must be undone**.
45
+
46
+ ## Rulings
47
+
48
+ ### 1. Canonical `.mdc` rules are unions, never variants
49
+
50
+ `rules/mdc/*.mdc` holds one body per rule, carrying every clause any repo needs. Where two
51
+ copies disagreed, both clauses are present and both are unconditional. Concretely:
52
+
53
+ - `test-gate` keeps both `BLOCK` bullets and says explicitly that they are complementary, so
54
+ a reader cannot tell that checking one is sufficient.
55
+ - `preview-postflight` keeps both Requirements bullets.
56
+ - `test-gate` keeps the `6/8 pass` example, and `preview-postflight` restores
57
+ `## Allowed Without Account` with social-api's `D1 migration verification` addition.
58
+
59
+ **Why:** a variant preserves a choice. These files do not record a choice; they record three
60
+ independent accidents, and two of the eight copies are strictly weaker than the best one. A
61
+ mechanism that keeps choosing between them will keep choosing the weaker one.
62
+
63
+ ### 2. Only substrate is a substitution; only what varies per repo is declared
64
+
65
+ What genuinely differs between the copies is paths and names: a plan-tracker glob, a test
66
+ file path, a path vocabulary, a production domain, a preview Worker name. Those become
67
+ `{{placeholders}}` with **declared defaults**, in `rules/mdc/*.mdc`:
68
+
69
+ ```yaml
70
+ substitutions:
71
+ - key: plansGlob
72
+ default: docs/plans/*
73
+ describe: glob for this repo's active plan tracker tables
74
+ ```
75
+
76
+ Values live in the repo's `simt-dev.json` under `substitutions`, keyed by rule id. They are
77
+ facts about the repo, while a rule is shared by every repo that adopts it — putting them in
78
+ the rule would either hard-code one repo's paths for everyone or push the decision back into
79
+ prose, which is the drift this mechanism exists to remove.
80
+
81
+ An **empty** value, or an empty default, drops the line it sits on. That is how one canonical
82
+ file reaches `repo-13` (no plan-tracker note) and `repo-15` (not a Workers repo
83
+ at all) without printing a blank bullet. A placeholder with neither a default nor a
84
+ declaration is a **gate failure**, because a rule shipping a literal `{{key}}` reads as
85
+ finished and is not.
86
+
87
+ ### 3. A rule that names one product's infrastructure is opt-in, not stack-detected
88
+
89
+ `preview-postflight` declares `applies: []` and `optIn: true`. It names specific preview
90
+ Workers, `AUTH_PROVIDER_POLICY`, `POSTFLIGHT_PASSWORD_UID` and specific production domains.
91
+ Selecting it on `cloudflare-workers` would hand it to `repo-15` and `repo-08` — Workers
92
+ repos with no preview Workers and no Social identity — which is the same over-match bug that
93
+ `rules/stacks/react-web.md` had when it declared `typescript` and `vitest`.
94
+
95
+ Detection cannot distinguish a Workers repo that has this problem from one that does not, so
96
+ the repo declares it: `"substitutions": { "__optIn": ["preview-postflight"] }`. `intake-questions`
97
+ and `test-gate` are `applies: always`, because they are genuinely stack-agnostic.
98
+
99
+ This is the first place ADR-013's fact-based detection is deliberately **not** used. A fact
100
+ answers "what is this repo made of"; opt-in answers "does this repo have this problem", which
101
+ is a different question and detection cannot reach it.
102
+
103
+ ### 4. A managed `.mdc` rule is a generated file, not a region inside a hand-owned one
104
+
105
+ `AGENTS.md` keeps its hand-written prose and receives a region. A `.mdc` rule does not: the
106
+ body after the frontmatter **is** the region, full stop. There is no hand-owned prose to
107
+ preserve, because any prose that was there is exactly the divergent copy being retired.
108
+
109
+ This was found by running it. The first implementation appended the region and produced a
110
+ file with two `## Requirements` sections and every bullet twice — the duplication the
111
+ estate's own review agent (`principal-slice-reviewer` checklist item 5) is explicitly
112
+ instructed to block, produced by the tool meant to remove it. Appending is the obvious
113
+ implementation and it is wrong here.
114
+
115
+ So `sync` keeps the existing frontmatter (the editor reads `description` and `alwaysApply`
116
+ before any rule text) and replaces everything below it. `check` compares against the same
117
+ reconstruction, or it would demand the divergent text still be present.
118
+
119
+ Because that is a destructive edit to a real file, `init` and `sync` report it:
120
+
121
+ ```
122
+ note .cursor/rules/test-gate.mdc: retiring 928 bytes of divergent hand-written rule text
123
+ ```
124
+
125
+ Silent deletion is the one thing a reviewer cannot audit.
126
+
127
+ ### 5. The conflict scanner must reach every rule surface, including ones added later
128
+
129
+ `RULE_FILES` named only `AGENTS.md`, `CLAUDE.md` and `CONTRIBUTING.md` for the whole life of
130
+ the tool. `simt-dev init` therefore reported **`CONFLICTS none detected`** on
131
+ `repo-11` — the repo holding the worst-drifted content in the estate — because it
132
+ never looked at the `.mdc` files at all.
133
+
134
+ A detector that only knows the filenames it was written for will quietly miss a whole rule
135
+ surface added later, and it will miss it *confidently*, reporting a clean result. Rule
136
+ discovery is now by name **and** by glob (`.cursor/rules/*.mdc`,
137
+ `.github/instructions/*.md`), and generated regions are stripped before scanning so that
138
+ quoting a ruling is never mistaken for contradicting it.
139
+
140
+ Writing that test found a second-order bug worth recording: the glob was split at its first
141
+ `*` and the tail matched as a name *prefix*, so `.cursor/rules/*.mdc` matched nothing. A
142
+ detector that silently matches zero files is indistinguishable from a repo with no rules —
143
+ the same failure, one level down.
144
+
145
+ ### 6. An autonomous-push probe must match every phrasing the estate actually used
146
+
147
+ The probe matched `committed and pushed`, which is `repo-07`'s exact DoD wording. A
148
+ different author wrote `Always commit and pushed`; another would write `commit and push`. The
149
+ first version matched only the first form and reported a real autonomous-push rule as clean.
150
+
151
+ The general lesson is the one this repository keeps re-learning: **probes are tested against
152
+ the estate's real text, not against text written to match the probe.**
153
+
154
+ ## Consequences
155
+
156
+ - `rules/mdc/{intake-questions,test-gate,preview-postflight}.mdc` are the canonical bodies.
157
+ Eight files in three repos become generated output.
158
+ - `simt-dev.json` gains a `substitutions` object, and the manifest generator seeds it empty
159
+ rather than leaving the key absent, so the shape is visible before anything needs it.
160
+ - `simt-dev sync` and `simt-dev check` handle four targets, not one. A malformed region in any target
161
+ refuses the whole run **before the first write** — writing the good three and skipping the
162
+ broken one would leave a repo half-adopted with no record of which half.
163
+ - `compare` continues to report one row per repo using `AGENTS.md` only, because a dashboard
164
+ row cannot represent four files. The gate uses the full finding list, so the dashboard's
165
+ coarser view is not inherited by the thing that must not be wrong.
166
+
167
+ ## Rejected
168
+
169
+ - **A variant mechanism per stack** (one `test-gate.mdc` per repo). This is what produced the
170
+ drift; modelling it as intended behaviour would make the drift permanent and legitimate.
171
+ - **Picking the "best" copy per family and adopting it as canonical.** `repo-14`'s
172
+ `test-gate` is the better copy only by accident; it is missing the producer-side `BLOCK`
173
+ bullet that social-api has. Choosing one per family would have discarded a real rule.
174
+ - **Deleting the 8 files outright** rather than generating over them. A repo with no
175
+ `intake-questions.mdc` and one with a generated one must behave identically to an agent, and
176
+ only the second is verifiable by `simt-dev check`.
177
+ - **Making `preview-postflight` apply to `cloudflare-workers`.** Hands an un-actionable rule
178
+ to two repos, and is the same bug `react-web.md` had.
179
+ - **Leaving `RULE_FILES` as a fixed list** on the grounds that the three families are the only
180
+ `.mdc` rules that exist. They are the ones that exist *today*; that is not a reason.