simt-dev 0.0.0-stage → 0.1.1
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 +130 -0
- package/LICENSE +202 -0
- package/README.md +92 -2
- package/bin/simt-dev.mjs +28 -0
- package/docs/decisions/ADR-001-push-requires-approval.md +42 -0
- package/docs/decisions/ADR-002-refactor-blast-radius.md +45 -0
- package/docs/decisions/ADR-003-test-breadth.md +46 -0
- package/docs/decisions/ADR-004-hook-owns-format.md +45 -0
- package/docs/decisions/ADR-005-file-length.md +42 -0
- package/docs/decisions/ADR-006-coverage-bar.md +59 -0
- package/docs/decisions/ADR-007-section-skeleton.md +54 -0
- package/docs/decisions/ADR-008-verdict-vocabulary.md +59 -0
- package/docs/decisions/ADR-009-skill-layout.md +48 -0
- package/docs/decisions/ADR-010-runner-invocation.md +56 -0
- package/docs/decisions/ADR-011-windows-support.md +52 -0
- package/docs/decisions/ADR-012-drop-windows-support.md +59 -0
- package/docs/decisions/ADR-013-genericity-by-facts-and-local-profiles.md +114 -0
- package/docs/decisions/ADR-014-workspace-is-configuration.md +211 -0
- package/docs/decisions/ADR-015-a-source-is-content-not-a-location.md +117 -0
- package/docs/decisions/ADR-016-a-divergent-rule-is-retired-not-merged.md +180 -0
- package/docs/decisions/ADR-017-a-declared-gate-must-be-runnable.md +89 -0
- package/docs/decisions/ADR-018-an-unset-hooks-path-is-not-an-absent-hook.md +183 -0
- package/docs/decisions/ADR-019-a-declared-command-is-the-runner.md +110 -0
- package/docs/decisions/ADR-020-a-rule-is-written-per-surface-and-pointed-not-copied.md +100 -0
- package/docs/decisions/ADR-021-one-surface-per-repo.md +94 -0
- package/docs/decisions/ADR-022-a-key-simt-dev-reads-is-prefixed.md +98 -0
- package/docs/decisions/ADR-023-the-gate-ships-as-a-file-the-repo-owns.md +106 -0
- package/docs/decisions/ADR-024-simt-dev-overwrites-its-own-gate-file.md +111 -0
- package/docs/decisions/ADR-025-a-baseline-is-a-region-in-a-file-the-repo-owns.md +153 -0
- package/docs/decisions/ADR-026-a-config-without-a-region-is-a-file-the-repo-owns.md +117 -0
- package/docs/decisions/ADR-027-the-one-gitattributes-line-that-is-neither-dangerous-nor-empty.md +130 -0
- package/docs/decisions/ADR-028-a-base-file-is-a-floor-so-the-disagreement-it-cannot-repair-becomes-a-check.md +131 -0
- package/docs/decisions/ADR-029-the-pins-a-repository-declares-are-its-own.md +120 -0
- package/docs/decisions/ADR-030-what-a-plan-does-not-ship-is-a-decision-with-a-record.md +114 -0
- package/docs/decisions/ADR-031-the-suite-stages-the-environment-it-needs.md +129 -0
- package/docs/decisions/ADR-032-provenance-is-a-content-digest-not-a-commit.md +141 -0
- package/docs/decisions/ADR-033-three-plan-rows-closed-by-decision-not-by-authoring.md +82 -0
- package/docs/decisions/ADR-034-a-staged-tool-refuses-a-partially-staged-file.md +91 -0
- package/docs/decisions/ADR-035-one-write-decision-and-one-json-document.md +119 -0
- package/docs/decisions/ADR-036-a-writer-that-writes-says-so-and-the-instructions-naming-it-move-with-it.md +103 -0
- package/docs/decisions/ADR-037-unchanged-means-writing-would-not-change-a-byte.md +100 -0
- package/docs/decisions/ADR-038-the-gate-filter-belongs-to-every-manifest-writer.md +95 -0
- package/docs/decisions/ADR-039-a-ci-step-runs-a-form-that-can-fail.md +101 -0
- package/docs/decisions/ADR-040-the-last-writer-that-wrote-by-default-was-not-the-one-named.md +158 -0
- package/docs/decisions/ADR-041-the-suite-installs-the-package-in-order-to-test-it.md +92 -0
- package/docs/decisions/ADR-042-a-declared-glob-must-match-a-path-built-from-it.md +94 -0
- package/docs/decisions/ADR-043-a-keyword-is-a-phrase-and-two-letters-is-a-word.md +90 -0
- package/docs/decisions/ADR-044-compatibility-is-not-evidence.md +116 -0
- package/docs/decisions/ADR-045-a-dashboard-that-counts-a-defect.md +119 -0
- package/docs/decisions/ADR-046-one-rule-one-copy.md +140 -0
- package/docs/decisions/ADR-047-a-profile-selects-on-what-a-repository-runs-on.md +141 -0
- package/docs/decisions/ADR-048-a-baseline-carries-only-keys-a-reader-honours.md +150 -0
- package/docs/decisions/ADR-049-a-ruling-that-a-later-record-moved-says-so-where-it-is-printed.md +153 -0
- package/docs/decisions/ADR-050-a-byte-count-is-not-a-region-and-updated-is-not-a-change-of-rules.md +102 -0
- package/docs/decisions/ADR-051-a-pointer-is-the-readers-only-route-so-it-is-not-a-convenience.md +134 -0
- package/docs/decisions/ADR-052-a-dependency-is-counted-against-the-budget-and-there-is-one-budget.md +101 -0
- package/docs/decisions/ADR-053-the-node-floor-is-22-20-0.md +49 -0
- package/docs/decisions/ADR-054-the-digest-covers-every-byte-that-leaves-the-package.md +100 -0
- package/docs/decisions/ADR-055-a-status-line-travels-so-it-may-not-name-a-location.md +98 -0
- package/docs/decisions/ADR-056-three-names-one-of-which-is-cheap-to-move.md +160 -0
- package/docs/decisions/README.md +528 -0
- package/graph/skills.graph.json +1622 -0
- package/package.json +41 -4
- package/rules/core.md +76 -0
- package/rules/mdc/intake-questions.mdc +50 -0
- package/rules/mdc/preview-postflight.mdc +61 -0
- package/rules/mdc/slice-commit.mdc +52 -0
- package/rules/mdc/test-gate.mdc +54 -0
- package/rules/stacks/android.md +63 -0
- package/rules/stacks/cloudflare-workers.md +65 -0
- package/rules/stacks/connectiq.md +62 -0
- package/rules/stacks/container.md +31 -0
- package/rules/stacks/jvm.md +54 -0
- package/rules/stacks/node.md +45 -0
- package/rules/stacks/python.md +48 -0
- package/rules/stacks/react-native.md +58 -0
- package/rules/stacks/react-web.md +61 -0
- package/rules/stacks/typescript.md +21 -0
- package/rules/stacks/uv.md +32 -0
- package/src/argv.mjs +116 -0
- package/src/ci/dependabot.mjs +268 -0
- package/src/ci/workflow.mjs +360 -0
- package/src/commands/ci.mjs +285 -0
- package/src/commands/compare.mjs +195 -0
- package/src/commands/graph.mjs +73 -0
- package/src/commands/help.mjs +108 -0
- package/src/commands/hooks.mjs +471 -0
- package/src/commands/index.mjs +241 -0
- package/src/commands/init.mjs +566 -0
- package/src/commands/mcp.mjs +63 -0
- package/src/commands/rules.mjs +374 -0
- package/src/commands/skills.mjs +647 -0
- package/src/commands/skillsinstall.mjs +515 -0
- package/src/commands/sync.mjs +331 -0
- package/src/commands/upgrade.mjs +202 -0
- package/src/compare/discover.mjs +212 -0
- package/src/compare/hooksstate.mjs +159 -0
- package/src/compare/row.mjs +299 -0
- package/src/decisions/index.mjs +512 -0
- package/src/graph/load.mjs +541 -0
- package/src/hooks/capability.mjs +129 -0
- package/src/hooks/dispatcher.mjs +268 -0
- package/src/hooks/gates.mjs +144 -0
- package/src/hooks/resolve.mjs +194 -0
- package/src/hooks/shim.mjs +140 -0
- package/src/hooks/where.mjs +118 -0
- package/src/locate.mjs +75 -0
- package/src/mcp/server.mjs +167 -0
- package/src/mcp/tools.mjs +410 -0
- package/src/name.mjs +95 -0
- package/src/output/index.mjs +96 -0
- package/src/projection/baselines.mjs +209 -0
- package/src/projection/conflicts.mjs +205 -0
- package/src/projection/manifest.mjs +478 -0
- package/src/projection/markers.mjs +349 -0
- package/src/projection/mdc.mjs +327 -0
- package/src/projection/project.mjs +504 -0
- package/src/projection/rules.mjs +640 -0
- package/src/projection/surfaces.mjs +398 -0
- package/src/projection/targets.mjs +221 -0
- package/src/resolve/fingerprint.mjs +553 -0
- package/src/resolve/near.mjs +61 -0
- package/src/resolve/resolve.mjs +457 -0
- package/src/resolve/source.mjs +105 -0
- package/src/toolchain.mjs +202 -0
- package/src/tsconfig.mjs +314 -0
- package/src/upgrade/plan.mjs +203 -0
- package/src/workspace.mjs +289 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# ADR-005 — File length is two tiers: 400 review trigger, 1000 hard ceiling
|
|
2
|
+
|
|
3
|
+
Status: **accepted** · 2026-10-05 · affects all repos
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
|
|
7
|
+
- `simt-social` / `simt-social-api`: "**Split any file once it exceeds 1000 lines**; extract
|
|
8
|
+
along the nearest cohesive boundary instead of growing the file further."
|
|
9
|
+
- `simt-garmin`: "**File length: 400 lines is a smell threshold, not a hard gate.**" Generated
|
|
10
|
+
`.mc` files are exempt, with a named exception for a 408-line template.
|
|
11
|
+
- Several repos: no file-length rule stated at all.
|
|
12
|
+
|
|
13
|
+
The two numbers are not in conflict — they measure different things. 400 is where a human
|
|
14
|
+
should start asking questions; 1000 is where the file must already have been split.
|
|
15
|
+
|
|
16
|
+
## Ruling
|
|
17
|
+
|
|
18
|
+
Two tiers, both advisory in wording but checkable in `simt-dev check`:
|
|
19
|
+
|
|
20
|
+
- **400 lines — review trigger.** Crossing it requires a stated reason to keep the file whole.
|
|
21
|
+
- **1000 lines — hard ceiling.** `simt-dev check` fails. Extract along the nearest cohesive
|
|
22
|
+
boundary.
|
|
23
|
+
- **Generated files are exempt**, declared per repo in `simt-dev.json`. The hook must name the
|
|
24
|
+
generator.
|
|
25
|
+
|
|
26
|
+
Per-repo overrides are permitted via `limits.fileLines` in `simt-dev.json`.
|
|
27
|
+
|
|
28
|
+
## Consequences
|
|
29
|
+
|
|
30
|
+
- The Social repos' hard 1000 gate and garmin's soft 400 both survive unchanged in force.
|
|
31
|
+
- The generated-file exemption that garmin had to state in prose becomes a declared property,
|
|
32
|
+
so the hook can honour it without hardcoding `.mc`.
|
|
33
|
+
- `simt-social`'s pre-commit file-size check (currently inlined as 1000 lines of shell, and
|
|
34
|
+
duplicating `scripts/check-file-sizes.ts`) becomes one implementation in the shared
|
|
35
|
+
dispatcher.
|
|
36
|
+
|
|
37
|
+
## Rejected
|
|
38
|
+
|
|
39
|
+
- **One number org-wide.** Either it is too tight for legitimately long declarative files or
|
|
40
|
+
too loose to prompt a human at 400.
|
|
41
|
+
- **No rule.** The Social repos' pre-commit check exists precisely because a rule was missing,
|
|
42
|
+
and it is now implemented twice.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# ADR-006 — Evidence discipline is central; the numeric bar is per-repo
|
|
2
|
+
|
|
3
|
+
Status: **accepted** · 2026-10-05 · affects all repos
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
|
|
7
|
+
The coverage bar is the one concept that is mutually incompatible *in kind* across the estate:
|
|
8
|
+
|
|
9
|
+
| Repo | Bar |
|
|
10
|
+
|---|---|
|
|
11
|
+
| Repos with a declared numeric bar (outside this estate) | Quantitative. `fail_under = 100`, branch coverage, "a behavioural coverage gate, not a line-count scoreboard" |
|
|
12
|
+
| `simt-garmin` | Quantitative, but a different quantity. Memory: 64 KB app / 24 KB Glance against a 32 KB ceiling, plus 12 named lint scripts |
|
|
13
|
+
| `simt-social`, `simt-social-api`, `simt-social-media` | Qualitative. No number anywhere. Evidence discipline: named commands, 100 % pass, no unjustified skips, behavioural test delta |
|
|
14
|
+
| `simt-api` | Neither. Three verification commands. |
|
|
15
|
+
|
|
16
|
+
You cannot average these. 100 % line coverage is unreachable for Kotlin/Compose UI code and for
|
|
17
|
+
Monkey C memory budgets; a memory ceiling is not a coverage proxy; "no number" is not a weaker
|
|
18
|
+
version of 100 %, it is a different instrument.
|
|
19
|
+
|
|
20
|
+
## Ruling
|
|
21
|
+
|
|
22
|
+
**The central rule mandates evidence discipline. The numeric threshold is a declared,
|
|
23
|
+
per-repo constant.**
|
|
24
|
+
|
|
25
|
+
Central, mandatory, portable — for every repo regardless of stack:
|
|
26
|
+
|
|
27
|
+
1. Name the exact commands and the exact test files.
|
|
28
|
+
2. Those commands must exit 0 with **all** selected tests passing. Partial pass is a hard
|
|
29
|
+
blocker; "6/8 pass" is the canonical worked example of a blocked state.
|
|
30
|
+
3. No test is skipped without a pre-existing written justification.
|
|
31
|
+
4. Every executable change carries a test delta: new or extended test for new behaviour, a
|
|
32
|
+
regression test reproducing the failure mode for a bugfix, or a stated reason existing tests
|
|
33
|
+
fully prove the invariant.
|
|
34
|
+
5. Never claim a test you did not run. Never cite a partial suite as evidence.
|
|
35
|
+
6. Approval by budget never converts a known failure into an approval.
|
|
36
|
+
|
|
37
|
+
Per-repo, declared in `simt-dev.json` under `limits`: the numeric gate, if any — coverage
|
|
38
|
+
percentage, memory budget, size ceiling. The central rule specifies *how to declare it*, never
|
|
39
|
+
the value.
|
|
40
|
+
|
|
41
|
+
## Consequences
|
|
42
|
+
|
|
43
|
+
- A repo that declares `coverage: 100` keeps it and its "untested paths are typed holes"
|
|
44
|
+
framing intact; the ruling constrains only what a new repo declares.
|
|
45
|
+
- garmin declares its memory budgets in the same slot, and its "layer-conditional 5a–5m matrix"
|
|
46
|
+
becomes a declared gate list rather than 12 prose sentences.
|
|
47
|
+
- The three social repos declare no number and keep their evidence discipline verbatim — which
|
|
48
|
+
is now shared rather than independently re-invented in each.
|
|
49
|
+
- A new repo inherits a real policy from `simt-dev init` with no configuration, and can opt into a
|
|
50
|
+
number later without changing the rule.
|
|
51
|
+
|
|
52
|
+
## Rejected
|
|
53
|
+
|
|
54
|
+
- **Hard 100 % coverage org-wide.** Unreachable in Kotlin/Compose and meaningless for Monkey C;
|
|
55
|
+
would push repos to delete the rule rather than meet it.
|
|
56
|
+
- **No number anywhere.** Leaves a genuinely strong gate unenforced org-wide and invites
|
|
57
|
+
every new repo to skip the question.
|
|
58
|
+
- **One number for coverage and a separate slot for memory.** Two slots invite a repo to declare
|
|
59
|
+
only the one it likes.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# ADR-007 — One canonical section skeleton
|
|
2
|
+
|
|
3
|
+
Status: **accepted** · 2026-10-05 · affects all repos
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
|
|
7
|
+
Six `AGENTS.md` files use the same conceptual sections under three different headings:
|
|
8
|
+
|
|
9
|
+
| File | Authority | Invariants |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| `simt-api`, `simt-social-media` | "Authority and Scope" | "Non-Negotiable Invariants" |
|
|
12
|
+
| `simt-garmin`, `simt-social` | "Authority and scope" | "Non-negotiable invariants" |
|
|
13
|
+
| `simt-social-api` | "Authority and **ownership**" | "Non-negotiable invariants" |
|
|
14
|
+
|
|
15
|
+
At least one repo outside this estate uses a numbered 12-part layout with no equivalent
|
|
16
|
+
sections at all.
|
|
17
|
+
|
|
18
|
+
The deeper difference is in what "non-negotiable" means. Garmin's are **platform physics**
|
|
19
|
+
("breaking any of these produces a product that is wrong, crashes, or cannot be shipped"); the
|
|
20
|
+
Social pair's are **product and privacy policy**; api/media's are **data and schema
|
|
21
|
+
invariants**. Those are three different kinds of claim wearing one heading, which is why the
|
|
22
|
+
naming drifted — nobody could tell whether the sections were meant to align.
|
|
23
|
+
|
|
24
|
+
## Ruling
|
|
25
|
+
|
|
26
|
+
One canonical skeleton, sentence case:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
## Authority and ownership
|
|
30
|
+
## Non-negotiable invariants
|
|
31
|
+
## Engineering conventions
|
|
32
|
+
## Workflow and validation
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Repos may add sections after these; they may not rename them. A repo that arrives with its
|
|
36
|
+
own numbered layout keeps it during adoption and maps each part onto a canonical section in
|
|
37
|
+
`simt-dev.json`, so both structures remain navigable while it migrates.
|
|
38
|
+
|
|
39
|
+
Each invariant states **which kind of claim it is** — platform, policy, or data — so a reader
|
|
40
|
+
knows immediately what breaking it costs and who may change it.
|
|
41
|
+
|
|
42
|
+
## Consequences
|
|
43
|
+
|
|
44
|
+
- Cross-repo reading becomes possible: section N means the same class of thing everywhere.
|
|
45
|
+
- A future grep for "what are the invariants" returns a comparable list across all 16 repos.
|
|
46
|
+
- "Ownership" is kept in the heading because `simt-social-api` earned it by being the only
|
|
47
|
+
file that enumerates ownership explicitly.
|
|
48
|
+
|
|
49
|
+
## Rejected
|
|
50
|
+
|
|
51
|
+
- **Standardise on Title Case** (`simt-api`'s variant). Capitalisation in headings is noise;
|
|
52
|
+
three repos already use sentence case.
|
|
53
|
+
- **Normalise a differently-shaped layout immediately.** A mechanical conversion of 12
|
|
54
|
+
numbered parts would produce a worse document than a mapping.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# ADR-008 — One review verdict vocabulary: three values
|
|
2
|
+
|
|
3
|
+
Status: **accepted** · 2026-10-05 · affects all repos
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
|
|
7
|
+
The estate has two incompatible verdict vocabularies.
|
|
8
|
+
|
|
9
|
+
`.cursor/rules/test-gate.mdc` — used by all three Social repos — allows one outcome:
|
|
10
|
+
|
|
11
|
+
> Return `BLOCK` when: any test fails, is skipped without pre-existing justification, or is
|
|
12
|
+
> unrun; the executable diff has no test delta without explicit justification; the coordinator
|
|
13
|
+
> cites partial suite results or defers failing tests.
|
|
14
|
+
|
|
15
|
+
`principal-milestone-reviewer.agent.md` — the final pre-commit gate — uses three:
|
|
16
|
+
|
|
17
|
+
> `APPROVE` / `APPROVE WITH RESIDUAL RISK` / `BLOCK`
|
|
18
|
+
|
|
19
|
+
With only two values, a reviewer who finds no blocker but did not finish has no honest answer
|
|
20
|
+
and is pushed toward `APPROVE` by omission or toward `BLOCK` by exaggeration. With three, the
|
|
21
|
+
budget mechanism works: "Use one focused pass, normally no more than 5 minutes or 8 targeted
|
|
22
|
+
tool calls… If the budget is reached without a concrete blocker, stop and return `APPROVE WITH
|
|
23
|
+
RESIDUAL RISK`. List what was not reviewed."
|
|
24
|
+
|
|
25
|
+
The agent also re-embeds the `.mdc` text verbatim as its checklist item 1, so the vocabulary
|
|
26
|
+
mismatch propagates from the rule into the gate.
|
|
27
|
+
|
|
28
|
+
## Ruling
|
|
29
|
+
|
|
30
|
+
Adopt the three-value vocabulary everywhere:
|
|
31
|
+
|
|
32
|
+
| Verdict | Meaning |
|
|
33
|
+
|---|---|
|
|
34
|
+
| `BLOCK` | A concrete defect, missing mandatory gate, partial test pass, or missing test delta. Always paired with a file:line or command reference. |
|
|
35
|
+
| `APPROVE WITH RESIDUAL RISK` | No blocker found within the review budget. **Must** list what was not reviewed. |
|
|
36
|
+
| `APPROVE` | Fully reviewed, no blocker. |
|
|
37
|
+
|
|
38
|
+
`APPROVE WITH RESIDUAL RISK` never converts a known failure into an approval — that constraint
|
|
39
|
+
travels with the verdict, verbatim from the review agent.
|
|
40
|
+
|
|
41
|
+
The `.mdc` files are updated to the three-value vocabulary. `BLOCK` remains the only value that
|
|
42
|
+
stops work; the other two do not weaken the block conditions, which are stated identically in
|
|
43
|
+
all three rules.
|
|
44
|
+
|
|
45
|
+
## Consequences
|
|
46
|
+
|
|
47
|
+
- A reviewer who runs out of budget has a correct answer, so the budget stops being a reason to
|
|
48
|
+
over-search.
|
|
49
|
+
- Rule text and agent text stop diverging, because they now share a vocabulary and the agent no
|
|
50
|
+
longer has to paraphrase the rule.
|
|
51
|
+
- Output format is uniform across the 15 agent files: severity-ordered findings, clickable
|
|
52
|
+
file:line references, a verdict, and — when not fully reviewed — the unreviewed list.
|
|
53
|
+
|
|
54
|
+
## Rejected
|
|
55
|
+
|
|
56
|
+
- **`BLOCK` only.** Loses the honest "I did not finish" answer and makes the strict review
|
|
57
|
+
budget unusable as written.
|
|
58
|
+
- **Four values** (adding `NEEDS WORK`). Three already distinguishes blocked from
|
|
59
|
+
budget-limited; a fourth would be chosen on judgement and collapse back into two.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# ADR-009 — Skills are `<name>/SKILL.md` with mandatory frontmatter
|
|
2
|
+
|
|
3
|
+
Status: **accepted** · 2026-10-05 · affects all repos
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
|
|
7
|
+
All 37 skill entries in the estate use `<name>/SKILL.md`. A wider audit that included repos
|
|
8
|
+
now out of scope found exactly one entry that did not: a flat 7 266-byte file that also omitted
|
|
9
|
+
the `name` frontmatter key. The layout ruling exists because that defect class is real, not
|
|
10
|
+
because this estate still has it.
|
|
11
|
+
|
|
12
|
+
Frontmatter field usage across the wider audit's 46 entries: `name` 45, `description` 46,
|
|
13
|
+
`argument-hint` 14, `disable-model-invocation` 2, `references` 1, `compatibility` 1, and
|
|
14
|
+
**`globs` zero**. No skill in any audited repo scopes itself by file pattern.
|
|
15
|
+
|
|
16
|
+
That last point matters for discovery. A skill that could declare `globs: "migrations/**"`
|
|
17
|
+
would let the resolver select it from the diff alone. Since none do, path-based resolution has
|
|
18
|
+
to come from the graph registry instead — which is why `paths` lives in
|
|
19
|
+
`graph/skills.graph.json` (plan 0002 §3.2) rather than in frontmatter.
|
|
20
|
+
|
|
21
|
+
## Ruling
|
|
22
|
+
|
|
23
|
+
- A skill is a directory `<name>/` containing `SKILL.md`.
|
|
24
|
+
- `name` and `description` are **mandatory** in frontmatter.
|
|
25
|
+
- `argument-hint` is recommended for any skill invoked by hand, and is optional otherwise —
|
|
26
|
+
simt-app's 7 skills omit it legitimately, since they are task-shaped rather than
|
|
27
|
+
user-invoked.
|
|
28
|
+
- Body size and reference-file count are declared in `graph/skills.graph.json`, not in
|
|
29
|
+
frontmatter, so the registry stays the single source of truth for resolution metadata.
|
|
30
|
+
|
|
31
|
+
The one flat file found in the wider audit was wrapped into `coverage-hunt/SKILL.md` with
|
|
32
|
+
its `name` added. This estate has no such entry left to migrate.
|
|
33
|
+
|
|
34
|
+
## Consequences
|
|
35
|
+
|
|
36
|
+
- Every skill becomes addressable by one stable name across all 16 repos, which is a
|
|
37
|
+
precondition for `simt-dev skills find/show/install` and for `variant-of` edges.
|
|
38
|
+
- The registry in `graph/skills.graph.json` becomes the one place resolution metadata lives,
|
|
39
|
+
instead of metadata split across frontmatter, prose and directory size.
|
|
40
|
+
- `simt-dev skills eval` can key on skill names, because every skill has one.
|
|
41
|
+
|
|
42
|
+
## Rejected
|
|
43
|
+
|
|
44
|
+
- **Require `globs` in frontmatter.** Attractive, but it would put resolution metadata in two
|
|
45
|
+
places and drift from the graph — the exact failure mode that produced the 8 divergent
|
|
46
|
+
`.mdc` files. Recorded as considered and rejected.
|
|
47
|
+
- **Keep flat `.md` as an accepted form.** Two layouts means two resolver paths and two sets of
|
|
48
|
+
conventions to police.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# ADR-010 — Invoke tools through the repo's own runner, never a package runner
|
|
2
|
+
|
|
3
|
+
Status: **accepted** · 2026-10-05 · affects all repos
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
|
|
7
|
+
Four repos independently arrived at the same rule, in two ecosystems, with the same rationale.
|
|
8
|
+
|
|
9
|
+
- `simt-social` / `simt-social-api`: "Run tests and quality checks through repository `npm run
|
|
10
|
+
...` scripts… **Do not invoke local package binaries through `npx` or `npm exec`** unless no
|
|
11
|
+
repository script supports the required operation."
|
|
12
|
+
- A Python repo outside this estate, in the same form: "**Do not use `uv run pytest`**; the
|
|
13
|
+
console entrypoint can miss the `uv` import path and fail to import dependencies." Its
|
|
14
|
+
contributor guide states the same rule with a stronger failure description: it "can
|
|
15
|
+
**silently** run against the wrong environment."
|
|
16
|
+
|
|
17
|
+
The failure mode is identical: a package runner resolves a binary outside the project's
|
|
18
|
+
configured environment, so the tool runs, the import path is wrong, and the result is either an
|
|
19
|
+
obvious import error or — worse — a pass against the wrong interpreter.
|
|
20
|
+
|
|
21
|
+
Note the severity disagreement on that last point: "fail to import" versus "silently run
|
|
22
|
+
against the wrong environment." The second is far worse and is treated as canonical below.
|
|
23
|
+
|
|
24
|
+
## Ruling
|
|
25
|
+
|
|
26
|
+
Invoke tools through the repository's own declared runner, in every ecosystem:
|
|
27
|
+
|
|
28
|
+
- Node: `npm run <script>`. Never `npx` or `npm exec` for a binary that is a declared
|
|
29
|
+
dependency.
|
|
30
|
+
- Python: the module form, `uv run -m pytest`, or the repo's own wrapper. Never the bare
|
|
31
|
+
console entrypoint.
|
|
32
|
+
- Gradle: `./gradlew`, never a system `gradle`.
|
|
33
|
+
- Any other ecosystem: the repo's declared entrypoint, never a bare global binary.
|
|
34
|
+
|
|
35
|
+
**Read the actual runner manifest before running anything.** A command that does not exist in
|
|
36
|
+
`package.json` is not a command. This is stated because it is the other half of the failure:
|
|
37
|
+
guessing a script name is how an agent concludes a check "passed" when it never ran.
|
|
38
|
+
|
|
39
|
+
Exception: a tool that is genuinely not a project dependency (a one-off inspector, a global
|
|
40
|
+
formatter) may be invoked directly, provided its version is pinned.
|
|
41
|
+
|
|
42
|
+
## Consequences
|
|
43
|
+
|
|
44
|
+
- The estates' two ecosystems converge on one rule, so an agent moving between a Python repo
|
|
45
|
+
and a TypeScript repo does not have to learn the rule twice.
|
|
46
|
+
- The stronger failure description is canonical: silently running against the wrong environment
|
|
47
|
+
is worse than failing, and the rule exists to prevent it.
|
|
48
|
+
- Repos with no declared scripts get them from the tooling baseline (plan 0001 §3.1), so this
|
|
49
|
+
ruling is always actionable.
|
|
50
|
+
|
|
51
|
+
## Rejected
|
|
52
|
+
|
|
53
|
+
- **Adopt only the npm half.** Leaves the identical failure unguarded in Python, Gradle and
|
|
54
|
+
every future ecosystem.
|
|
55
|
+
- **Permit `npx` for convenience.** `npx` will fetch a package that is not the project's
|
|
56
|
+
version, which reintroduces exactly the environment drift the rule prevents.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# ADR-011 — Windows is supported from v1
|
|
2
|
+
|
|
3
|
+
Status: **superseded by [ADR-012](ADR-012-drop-windows-support.md)** · 2026-10-05
|
|
4
|
+
Affects: the `simt-dev` CLI
|
|
5
|
+
|
|
6
|
+
> Superseded the same day. The cost table below is retained because it is what ADR-012's
|
|
7
|
+
> reasoning turns on: three of the five obligations are permanent tax on the hook dispatcher
|
|
8
|
+
> and the projection injector, not one-off work. See ADR-012 for the weighing.
|
|
9
|
+
|
|
10
|
+
## Context
|
|
11
|
+
|
|
12
|
+
No Windows developer currently appears in the estate, and the cheap alternative was
|
|
13
|
+
`"os": ["darwin", "linux"]` in `package.json`, which makes npm refuse to install on Windows
|
|
14
|
+
with a clear message instead of failing obscurely inside a git hook.
|
|
15
|
+
|
|
16
|
+
That option was put to you and declined in favour of full v1 support.
|
|
17
|
+
|
|
18
|
+
The cost is real and is recorded here so it is paid deliberately rather than discovered:
|
|
19
|
+
|
|
20
|
+
| Obligation | Why |
|
|
21
|
+
|---|---|
|
|
22
|
+
| Hook scripts must not assume POSIX | `core.hooksPath` hooks run under Git for Windows' bundled sh, which does exist — but the dispatcher cannot assume a POSIX `#!/bin/sh` shebang alone is sufficient. Hook entrypoints are `.cmd`-aware or invoked through `node` explicitly. |
|
|
23
|
+
| The projection injector must normalise line endings | The audit found **10 of 16 repos have no `.editorconfig`**, so a generated region written with the host's EOL would produce a whole-file diff on Windows checkouts. The injector writes the canonical EOL from `.gitattributes` (`* text=auto eol=lf`), defaulting to LF. |
|
|
24
|
+
| Path handling must not assume `/` | Resolver globs, `paths` matching and every emitted file reference must normalise separators. |
|
|
25
|
+
| npm bin shim | The published package must rely on npm's generated shim rather than a hand-written launcher. |
|
|
26
|
+
| CI must actually run it | A macOS-only CI run cannot verify a Windows claim. GitHub Actions gains a `windows-latest` job for the CLI itself — not for the repos it serves, which stay macOS/Linux as their own tooling dictates. |
|
|
27
|
+
|
|
28
|
+
## Ruling
|
|
29
|
+
|
|
30
|
+
Windows is a supported v1 platform for the `simt-dev` CLI itself. `package.json` declares no
|
|
31
|
+
`os` restriction, and CI verifies on `windows-latest`.
|
|
32
|
+
|
|
33
|
+
Scope is deliberately limited: this covers the CLI running *on* Windows. It does not imply that
|
|
34
|
+
any repo's own toolchain is Windows-ready — Python packaging, Monkey C, and Android Gradle
|
|
35
|
+
builds are governed by their own repos.
|
|
36
|
+
|
|
37
|
+
## Consequences
|
|
38
|
+
|
|
39
|
+
- The shared hook dispatcher ships with a Windows-safe entrypoint from day one. Retrofitting
|
|
40
|
+
this after 16 repos have installed the dispatcher is far more expensive than building it now.
|
|
41
|
+
- The EOL normalisation in the projection injector becomes a correctness requirement rather
|
|
42
|
+
than a nicety — which is worth doing regardless, given 10 repos lack `.editorconfig`.
|
|
43
|
+
- `windows-latest` CI costs minutes per run and buys an install story that does not break on the
|
|
44
|
+
first non-Mac contributor.
|
|
45
|
+
|
|
46
|
+
## Rejected
|
|
47
|
+
|
|
48
|
+
- **`os: ["darwin", "linux"]`.** Cheapest, and a defensible engineering call on estate
|
|
49
|
+
evidence — declined because the hook dispatcher's Windows-safety is substantially harder to
|
|
50
|
+
retrofit across 16 installed repos than to build.
|
|
51
|
+
- **Windows via WSL only.** Avoids the native path work but leaves a Windows contributor
|
|
52
|
+
needing a Linux environment, which is the failure this ruling exists to prevent.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# ADR-012 — Windows support is dropped; POSIX only
|
|
2
|
+
|
|
3
|
+
Status: **accepted** · 2026-10-05 · supersedes [ADR-011](ADR-011-windows-support.md)
|
|
4
|
+
Affects: the `simt-dev` CLI
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
ADR-011 committed to supporting Windows in the CLI from v1, against the recommendation to
|
|
9
|
+
declare `"os": ["darwin", "linux"]` and let npm refuse the install elsewhere. The reasoning
|
|
10
|
+
for that decision was sound but the weighing was wrong.
|
|
11
|
+
|
|
12
|
+
ADR-011's own cost table listed five obligations, three of which are permanent tax on
|
|
13
|
+
every future change to the hook dispatcher and the projection injector rather than one-off
|
|
14
|
+
work:
|
|
15
|
+
|
|
16
|
+
1. Hook entrypoints must be `.cmd`-aware instead of relying on a POSIX shebang.
|
|
17
|
+
2. The projection injector must normalise line endings on every write.
|
|
18
|
+
3. Glob matching and every emitted path reference must normalise separators.
|
|
19
|
+
4. npm's generated bin shim must be relied on rather than a hand-written launcher.
|
|
20
|
+
5. CI must carry a permanent `windows-latest` job.
|
|
21
|
+
|
|
22
|
+
Against that: **no Windows developer appears anywhere in the repos**, and no repo's own
|
|
23
|
+
toolchain is Windows-ready either — `simt-garmin` drives the Monkey C toolchain, and
|
|
24
|
+
`simt-app`/`wakt-app`/`app-ui` drive Gradle and Android tooling. So the CLI would have been the
|
|
25
|
+
*only* Windows-ready artefact in the estate, serving nobody.
|
|
26
|
+
|
|
27
|
+
The deciding argument is not the cost, it is the shape: obligations 1–3 are correctness
|
|
28
|
+
constraints that every contributor must keep satisfying forever. Adopting them to serve a
|
|
29
|
+
population of zero is how a codebase accumulates permanent complexity nobody can evaluate.
|
|
30
|
+
|
|
31
|
+
## Ruling
|
|
32
|
+
|
|
33
|
+
**POSIX only: macOS and Linux.** `package.json` declares `"os": ["darwin", "linux"]`, so npm
|
|
34
|
+
refuses to install on Windows with a clear message instead of failing obscurely inside a git
|
|
35
|
+
hook. CI verifies on `macos-latest` and `ubuntu-latest` only.
|
|
36
|
+
|
|
37
|
+
The code obligations of ADR-011 are withdrawn, with one exception that stands on its own
|
|
38
|
+
merits rather than on Windows: **the projection injector still normalises line endings to the
|
|
39
|
+
canonical EOL.** 10 of 16 repos have no `.editorconfig`, so a generated region written with
|
|
40
|
+
whatever the host produced would cause whole-file diffs on checkouts whose
|
|
41
|
+
`core.autocrlf` differs. That is a cross-platform problem among POSIX hosts and has nothing
|
|
42
|
+
to do with Windows.
|
|
43
|
+
|
|
44
|
+
## Consequences
|
|
45
|
+
|
|
46
|
+
- `bin/simt-dev.mjs` keeps its shebang as a plain POSIX entrypoint; no `.cmd` shim handling.
|
|
47
|
+
- Glob matching and emitted paths assume `/`. The existing forward-slash normalisation in
|
|
48
|
+
`fingerprint.mjs` is retained as cheap insurance, not as a Windows requirement.
|
|
49
|
+
- The permanent `windows-latest` CI job is not added.
|
|
50
|
+
- Revisit only when a Windows developer joins the estate — at which point the deciding
|
|
51
|
+
argument is gone and the cost table in ADR-011 becomes the deciding one.
|
|
52
|
+
|
|
53
|
+
## Rejected
|
|
54
|
+
|
|
55
|
+
- **Keeping Windows v1 (ADR-011).** Permanent correctness constraints in the dispatcher and
|
|
56
|
+
injector, serving zero users, in the only part of the estate that would have been
|
|
57
|
+
Windows-ready.
|
|
58
|
+
- **Deferring rather than dropping.** A deferred platform is an unbounded promise; the
|
|
59
|
+
obligations would still shape the code.
|
|
@@ -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.
|