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.
- python/simt_dev/__init__.py +32 -0
- python/simt_dev/cli.py +225 -0
- simt_dev/__init__.py +32 -0
- simt_dev/cli.py +225 -0
- simt_dev/vendored/CHANGELOG.md +166 -0
- simt_dev/vendored/bin/simt-dev.mjs +28 -0
- simt_dev/vendored/docs/decisions/ADR-001-push-requires-approval.md +42 -0
- simt_dev/vendored/docs/decisions/ADR-002-refactor-blast-radius.md +45 -0
- simt_dev/vendored/docs/decisions/ADR-003-test-breadth.md +46 -0
- simt_dev/vendored/docs/decisions/ADR-004-hook-owns-format.md +45 -0
- simt_dev/vendored/docs/decisions/ADR-005-file-length.md +42 -0
- simt_dev/vendored/docs/decisions/ADR-006-coverage-bar.md +59 -0
- simt_dev/vendored/docs/decisions/ADR-007-section-skeleton.md +54 -0
- simt_dev/vendored/docs/decisions/ADR-008-verdict-vocabulary.md +59 -0
- simt_dev/vendored/docs/decisions/ADR-009-skill-layout.md +48 -0
- simt_dev/vendored/docs/decisions/ADR-010-runner-invocation.md +56 -0
- simt_dev/vendored/docs/decisions/ADR-011-windows-support.md +52 -0
- simt_dev/vendored/docs/decisions/ADR-012-drop-windows-support.md +59 -0
- simt_dev/vendored/docs/decisions/ADR-013-genericity-by-facts-and-local-profiles.md +114 -0
- simt_dev/vendored/docs/decisions/ADR-014-workspace-is-configuration.md +211 -0
- simt_dev/vendored/docs/decisions/ADR-015-a-source-is-content-not-a-location.md +117 -0
- simt_dev/vendored/docs/decisions/ADR-016-a-divergent-rule-is-retired-not-merged.md +180 -0
- simt_dev/vendored/docs/decisions/ADR-017-a-declared-gate-must-be-runnable.md +89 -0
- simt_dev/vendored/docs/decisions/ADR-018-an-unset-hooks-path-is-not-an-absent-hook.md +183 -0
- simt_dev/vendored/docs/decisions/ADR-019-a-declared-command-is-the-runner.md +110 -0
- simt_dev/vendored/docs/decisions/ADR-020-a-rule-is-written-per-surface-and-pointed-not-copied.md +100 -0
- simt_dev/vendored/docs/decisions/ADR-021-one-surface-per-repo.md +94 -0
- simt_dev/vendored/docs/decisions/ADR-022-a-key-simt-dev-reads-is-prefixed.md +98 -0
- simt_dev/vendored/docs/decisions/ADR-023-the-gate-ships-as-a-file-the-repo-owns.md +106 -0
- simt_dev/vendored/docs/decisions/ADR-024-simt-dev-overwrites-its-own-gate-file.md +111 -0
- simt_dev/vendored/docs/decisions/ADR-025-a-baseline-is-a-region-in-a-file-the-repo-owns.md +153 -0
- simt_dev/vendored/docs/decisions/ADR-026-a-config-without-a-region-is-a-file-the-repo-owns.md +117 -0
- simt_dev/vendored/docs/decisions/ADR-027-the-one-gitattributes-line-that-is-neither-dangerous-nor-empty.md +130 -0
- 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
- simt_dev/vendored/docs/decisions/ADR-029-the-pins-a-repository-declares-are-its-own.md +120 -0
- simt_dev/vendored/docs/decisions/ADR-030-what-a-plan-does-not-ship-is-a-decision-with-a-record.md +114 -0
- simt_dev/vendored/docs/decisions/ADR-031-the-suite-stages-the-environment-it-needs.md +129 -0
- simt_dev/vendored/docs/decisions/ADR-032-provenance-is-a-content-digest-not-a-commit.md +141 -0
- simt_dev/vendored/docs/decisions/ADR-033-three-plan-rows-closed-by-decision-not-by-authoring.md +82 -0
- simt_dev/vendored/docs/decisions/ADR-034-a-staged-tool-refuses-a-partially-staged-file.md +91 -0
- simt_dev/vendored/docs/decisions/ADR-035-one-write-decision-and-one-json-document.md +119 -0
- simt_dev/vendored/docs/decisions/ADR-036-a-writer-that-writes-says-so-and-the-instructions-naming-it-move-with-it.md +103 -0
- simt_dev/vendored/docs/decisions/ADR-037-unchanged-means-writing-would-not-change-a-byte.md +100 -0
- simt_dev/vendored/docs/decisions/ADR-038-the-gate-filter-belongs-to-every-manifest-writer.md +95 -0
- simt_dev/vendored/docs/decisions/ADR-039-a-ci-step-runs-a-form-that-can-fail.md +101 -0
- simt_dev/vendored/docs/decisions/ADR-040-the-last-writer-that-wrote-by-default-was-not-the-one-named.md +158 -0
- simt_dev/vendored/docs/decisions/ADR-041-the-suite-installs-the-package-in-order-to-test-it.md +92 -0
- simt_dev/vendored/docs/decisions/ADR-042-a-declared-glob-must-match-a-path-built-from-it.md +94 -0
- simt_dev/vendored/docs/decisions/ADR-043-a-keyword-is-a-phrase-and-two-letters-is-a-word.md +90 -0
- simt_dev/vendored/docs/decisions/ADR-044-compatibility-is-not-evidence.md +116 -0
- simt_dev/vendored/docs/decisions/ADR-045-a-dashboard-that-counts-a-defect.md +119 -0
- simt_dev/vendored/docs/decisions/ADR-046-one-rule-one-copy.md +140 -0
- simt_dev/vendored/docs/decisions/ADR-047-a-profile-selects-on-what-a-repository-runs-on.md +141 -0
- simt_dev/vendored/docs/decisions/ADR-048-a-baseline-carries-only-keys-a-reader-honours.md +150 -0
- simt_dev/vendored/docs/decisions/ADR-049-a-ruling-that-a-later-record-moved-says-so-where-it-is-printed.md +153 -0
- 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
- simt_dev/vendored/docs/decisions/ADR-051-a-pointer-is-the-readers-only-route-so-it-is-not-a-convenience.md +134 -0
- simt_dev/vendored/docs/decisions/ADR-052-a-dependency-is-counted-against-the-budget-and-there-is-one-budget.md +101 -0
- simt_dev/vendored/docs/decisions/ADR-053-the-node-floor-is-22-20-0.md +49 -0
- simt_dev/vendored/docs/decisions/ADR-054-the-digest-covers-every-byte-that-leaves-the-package.md +100 -0
- simt_dev/vendored/docs/decisions/ADR-055-a-status-line-travels-so-it-may-not-name-a-location.md +98 -0
- simt_dev/vendored/docs/decisions/ADR-056-three-names-one-of-which-is-cheap-to-move.md +160 -0
- simt_dev/vendored/docs/decisions/ADR-057-an-identity-is-per-developer-and-a-repository-does-not-own-it.md +115 -0
- simt_dev/vendored/docs/decisions/ADR-058-one-tool-three-channels-and-the-order-is-forced.md +148 -0
- simt_dev/vendored/docs/decisions/README.md +530 -0
- simt_dev/vendored/graph/skills.graph.json +1622 -0
- simt_dev/vendored/package.json +43 -0
- simt_dev/vendored/rules/core.md +76 -0
- simt_dev/vendored/rules/mdc/intake-questions.mdc +50 -0
- simt_dev/vendored/rules/mdc/preview-postflight.mdc +61 -0
- simt_dev/vendored/rules/mdc/slice-commit.mdc +52 -0
- simt_dev/vendored/rules/mdc/test-gate.mdc +54 -0
- simt_dev/vendored/rules/stacks/android.md +63 -0
- simt_dev/vendored/rules/stacks/cloudflare-workers.md +65 -0
- simt_dev/vendored/rules/stacks/connectiq.md +62 -0
- simt_dev/vendored/rules/stacks/container.md +31 -0
- simt_dev/vendored/rules/stacks/jvm.md +54 -0
- simt_dev/vendored/rules/stacks/node.md +45 -0
- simt_dev/vendored/rules/stacks/python.md +48 -0
- simt_dev/vendored/rules/stacks/react-native.md +58 -0
- simt_dev/vendored/rules/stacks/react-web.md +61 -0
- simt_dev/vendored/rules/stacks/typescript.md +21 -0
- simt_dev/vendored/rules/stacks/uv.md +32 -0
- simt_dev/vendored/src/argv.mjs +116 -0
- simt_dev/vendored/src/ci/dependabot.mjs +268 -0
- simt_dev/vendored/src/ci/workflow.mjs +360 -0
- simt_dev/vendored/src/commands/ci.mjs +285 -0
- simt_dev/vendored/src/commands/compare.mjs +195 -0
- simt_dev/vendored/src/commands/graph.mjs +73 -0
- simt_dev/vendored/src/commands/help.mjs +108 -0
- simt_dev/vendored/src/commands/hooks.mjs +471 -0
- simt_dev/vendored/src/commands/index.mjs +241 -0
- simt_dev/vendored/src/commands/init.mjs +632 -0
- simt_dev/vendored/src/commands/mcp.mjs +63 -0
- simt_dev/vendored/src/commands/rules.mjs +374 -0
- simt_dev/vendored/src/commands/skills.mjs +647 -0
- simt_dev/vendored/src/commands/skillsinstall.mjs +515 -0
- simt_dev/vendored/src/commands/sync.mjs +331 -0
- simt_dev/vendored/src/commands/upgrade.mjs +202 -0
- simt_dev/vendored/src/compare/discover.mjs +212 -0
- simt_dev/vendored/src/compare/hooksstate.mjs +159 -0
- simt_dev/vendored/src/compare/row.mjs +299 -0
- simt_dev/vendored/src/decisions/index.mjs +536 -0
- simt_dev/vendored/src/graph/load.mjs +541 -0
- simt_dev/vendored/src/hooks/capability.mjs +129 -0
- simt_dev/vendored/src/hooks/dispatcher.mjs +268 -0
- simt_dev/vendored/src/hooks/gates.mjs +144 -0
- simt_dev/vendored/src/hooks/resolve.mjs +194 -0
- simt_dev/vendored/src/hooks/shim.mjs +140 -0
- simt_dev/vendored/src/hooks/where.mjs +118 -0
- simt_dev/vendored/src/identity.mjs +218 -0
- simt_dev/vendored/src/locate.mjs +75 -0
- simt_dev/vendored/src/mcp/server.mjs +167 -0
- simt_dev/vendored/src/mcp/tools.mjs +410 -0
- simt_dev/vendored/src/name.mjs +95 -0
- simt_dev/vendored/src/output/index.mjs +96 -0
- simt_dev/vendored/src/projection/baselines.mjs +209 -0
- simt_dev/vendored/src/projection/conflicts.mjs +205 -0
- simt_dev/vendored/src/projection/manifest.mjs +478 -0
- simt_dev/vendored/src/projection/markers.mjs +349 -0
- simt_dev/vendored/src/projection/mdc.mjs +327 -0
- simt_dev/vendored/src/projection/project.mjs +504 -0
- simt_dev/vendored/src/projection/rules.mjs +640 -0
- simt_dev/vendored/src/projection/surfaces.mjs +398 -0
- simt_dev/vendored/src/projection/targets.mjs +221 -0
- simt_dev/vendored/src/resolve/fingerprint.mjs +553 -0
- simt_dev/vendored/src/resolve/near.mjs +61 -0
- simt_dev/vendored/src/resolve/resolve.mjs +457 -0
- simt_dev/vendored/src/resolve/source.mjs +105 -0
- simt_dev/vendored/src/toolchain.mjs +202 -0
- simt_dev/vendored/src/tsconfig.mjs +314 -0
- simt_dev/vendored/src/upgrade/plan.mjs +203 -0
- simt_dev/vendored/src/workspace.mjs +289 -0
- simt_dev-0.2.0.dist-info/METADATA +150 -0
- simt_dev-0.2.0.dist-info/RECORD +138 -0
- simt_dev-0.2.0.dist-info/WHEEL +4 -0
- simt_dev-0.2.0.dist-info/entry_points.txt +2 -0
- simt_dev-0.2.0.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// simt-dev - developer and agent tooling for the SIMT ecosystem.
|
|
3
|
+
//
|
|
4
|
+
// POSIX only (ADR-012). package.json declares os: [darwin, linux], so npm refuses
|
|
5
|
+
// the install elsewhere rather than letting a hook fail obscurely.
|
|
6
|
+
|
|
7
|
+
import { dispatch } from '../src/commands/index.mjs';
|
|
8
|
+
import { EXIT, fail } from '../src/output/index.mjs';
|
|
9
|
+
import { parse } from '../src/argv.mjs';
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const argv = process.argv.slice(2);
|
|
13
|
+
const parsed = parse(argv);
|
|
14
|
+
if (!parsed.ok) {
|
|
15
|
+
process.exit(parsed.code);
|
|
16
|
+
}
|
|
17
|
+
const { flags, positional } = parsed.value;
|
|
18
|
+
|
|
19
|
+
// No arguments at all is a usage error, not a silent help dump: an agent that
|
|
20
|
+
// ran the wrong thing should be told so rather than handed 40 lines of prose.
|
|
21
|
+
if (positional.length === 0 && !flags.help && !flags.version) {
|
|
22
|
+
process.exit(fail('no command given', EXIT.USAGE));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
process.exit((await dispatch(argv)) ?? EXIT.OK);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
process.exit(fail(error?.message ?? String(error), EXIT.FAIL));
|
|
28
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# ADR-001 — Push requires explicit approval
|
|
2
|
+
|
|
3
|
+
Status: **accepted** · 2026-10-05 · affects all repos
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
|
|
7
|
+
The repos state a commit/push policy. They disagree on whether an agent may push.
|
|
8
|
+
|
|
9
|
+
- A repo outside this estate: "always create a focused Git commit… skip the commit only
|
|
10
|
+
when the user explicitly asks not to commit." Silent on push.
|
|
11
|
+
- `repo-07/AGENTS.md` DoD #8: "**Committed and pushed** with a conventional message."
|
|
12
|
+
- `repo-14` and `repo-11`: "create a non-interactive Conventional Commit. Report
|
|
13
|
+
the commit SHA; **do not push**."
|
|
14
|
+
- `repo-04`, `repo-13`: silent.
|
|
15
|
+
|
|
16
|
+
The Social pair justify it: a push to the production branch triggers Cloudflare Workers Builds,
|
|
17
|
+
so "an approved push is therefore a production deployment action, not only source publication."
|
|
18
|
+
`repo-07` has no such coupling, and its rule asks for an autonomous push.
|
|
19
|
+
|
|
20
|
+
## Ruling
|
|
21
|
+
|
|
22
|
+
Commit locally, report the SHA, **push only on explicit user approval**.
|
|
23
|
+
|
|
24
|
+
When a repo's push triggers a deployment, a migration, or any remote mutation, say so before
|
|
25
|
+
asking for approval — do not treat approval for the push as approval for the deploy.
|
|
26
|
+
|
|
27
|
+
`repo-07` DoD #8 changes to "committed with a conventional message; pushed on approval."
|
|
28
|
+
This is the single outlier, and the 4:1 majority is also the safe direction.
|
|
29
|
+
|
|
30
|
+
## Consequences
|
|
31
|
+
|
|
32
|
+
- An agent that finishes a task ends at a reported SHA. Pushes become a separate, explicit step.
|
|
33
|
+
- `simt-dev init` and `upgrade` propose changes and never push, including for simt-dev itself.
|
|
34
|
+
- Repos whose CI deploys on push must state that consequence in their own AGENTS.md, which is
|
|
35
|
+
where the Social pair's rule already lives.
|
|
36
|
+
|
|
37
|
+
## Rejected
|
|
38
|
+
|
|
39
|
+
- **Autonomous push (garmin's current rule).** Removes the only checkpoint between "the agent
|
|
40
|
+
thinks it is done" and "production changed".
|
|
41
|
+
- **Push on completion, as a commit-always contributor loop implies.** Same objection; its
|
|
42
|
+
PR step does not require an agent to open the PR unattended.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# ADR-002 — Blast radius bounds refactoring; a third copy is a defect
|
|
2
|
+
|
|
3
|
+
Status: **accepted** · 2026-10-05 · affects all repos
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
|
|
7
|
+
Two repos state opposite philosophies, and both are reasonable in isolation.
|
|
8
|
+
|
|
9
|
+
- `repo-14` / `repo-11`: "When a second real use or a near-duplicate already
|
|
10
|
+
exists, refactor the existing code to extract the shared part… A third copy of the same
|
|
11
|
+
logic is a defect, and so is an abstraction invented for a single caller."
|
|
12
|
+
- A repo outside this estate: "Match existing style, naming, and test layout. **Do not
|
|
13
|
+
refactor neighboring code unless the requested change requires it.** Do not add speculative
|
|
14
|
+
flexibility, configuration, or abstractions."
|
|
15
|
+
|
|
16
|
+
Neither can be an org rule as written. The Social rule mandates proactive refactoring;
|
|
17
|
+
the other forbids it.
|
|
18
|
+
|
|
19
|
+
## Ruling
|
|
20
|
+
|
|
21
|
+
Both survive, split by scope:
|
|
22
|
+
|
|
23
|
+
- **Outside the current change's blast radius** — do not refactor. Unrelated code is not
|
|
24
|
+
collateral.
|
|
25
|
+
- **Inside the blast radius** — a second real use extracts, and the extraction is *finished*:
|
|
26
|
+
no orphaned imports, exports, styles, messages, files, feature flags or compatibility
|
|
27
|
+
aliases remain anywhere in the affected dependency graph.
|
|
28
|
+
- **A third copy of the same logic is a defect.** So is an abstraction invented for a single
|
|
29
|
+
caller.
|
|
30
|
+
|
|
31
|
+
## Consequences
|
|
32
|
+
|
|
33
|
+
- "Do not refactor neighbours" survives verbatim as a reviewer's objection to drive-by changes.
|
|
34
|
+
- "A third copy is a defect" survives verbatim as a reviewer's objection to copy-paste.
|
|
35
|
+
- Refactor completion becomes a checkable state, not an intention: the reviewer greps for
|
|
36
|
+
orphans rather than trusting the claim.
|
|
37
|
+
- The "no speculative flexibility" half is restated as the single-caller half of this
|
|
38
|
+
ruling rather than as a blanket ban.
|
|
39
|
+
|
|
40
|
+
## Rejected
|
|
41
|
+
|
|
42
|
+
- **Social's rule verbatim.** Sanctions unrelated refactors, which is how a 10-line fix turns
|
|
43
|
+
into a 400-line diff nobody can review.
|
|
44
|
+
- **The opposite rule verbatim.** Sanctions the third copy, which is how a codebase grows four
|
|
45
|
+
subtly different implementations of the same validation.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# ADR-003 — Focused tests are the default; a full suite needs a named trigger
|
|
2
|
+
|
|
3
|
+
Status: **accepted** · 2026-10-05 · affects all repos
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
|
|
7
|
+
- `repo-14` / `repo-11`: "Focused tests are the default gate, and full suites are
|
|
8
|
+
the exception… **absent a named trigger, running a full suite is a defect, not diligence.**"
|
|
9
|
+
- A repo outside this estate: `fail_under = 100` plus a full-suite script as the pre-push
|
|
10
|
+
default, and a contributor loop that says "run the full gate locally (lint + tests +
|
|
11
|
+
coverage)."
|
|
12
|
+
- `repo-07`: reframes the axis entirely as a layer-conditional 5a–5m matrix.
|
|
13
|
+
- `repo-13`: no policy — it just lists `npm run test:unit && npm run test:e2e`.
|
|
14
|
+
|
|
15
|
+
The Social pair make a full suite a sin. media presents it as unremarkable. The hard-gate
|
|
16
|
+
repo makes it mandatory. These cannot all be org rules as stated.
|
|
17
|
+
|
|
18
|
+
## Ruling
|
|
19
|
+
|
|
20
|
+
Focused tests are the default gate. Running a full suite is legitimate **only** against a
|
|
21
|
+
named trigger:
|
|
22
|
+
|
|
23
|
+
1. Phase exit.
|
|
24
|
+
2. Release gate.
|
|
25
|
+
3. Behaviour that crosses a system boundary.
|
|
26
|
+
4. A repo-declared numeric gate (see ADR-006 for where that number lives).
|
|
27
|
+
5. Explicit user or caller instruction.
|
|
28
|
+
6. Pre-push on a protected branch, in a repo that declares this trigger in `simt-dev.json`.
|
|
29
|
+
|
|
30
|
+
Re-running a full suite as a progress meter is a defect. Repos outside this estate already
|
|
31
|
+
say exactly this ("do not run the full gate repeatedly as a progress meter"); the principle is
|
|
32
|
+
adopted as canonical.
|
|
33
|
+
|
|
34
|
+
## Consequences
|
|
35
|
+
|
|
36
|
+
- Each repo's `simt-dev.json` declares which of the six triggers are active for it. A repo
|
|
37
|
+
with a declared numeric gate activates 4 and 6; one repository activate 1, 2 and 3.
|
|
38
|
+
- A reviewer can ask "which trigger?" and get a number, not an opinion.
|
|
39
|
+
- media gains an explicit policy instead of an implicit one that contradicts its siblings.
|
|
40
|
+
|
|
41
|
+
## Rejected
|
|
42
|
+
|
|
43
|
+
- **"A full suite is a defect."** Wrong for a release gate and wrong for a repo whose coverage
|
|
44
|
+
floor is a hard number; it makes the rule get ignored rather than followed.
|
|
45
|
+
- **Full suite by default.** Re-pays the whole cost on every iteration; the Social pair are
|
|
46
|
+
right that an inflated brief is a defect in the brief, not thoroughness.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# ADR-004 — The hook owns format and lint for staged files
|
|
2
|
+
|
|
3
|
+
Status: **accepted** · 2026-10-05 · affects all repos
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
|
|
7
|
+
- `repo-11`: "lint and format are enforced by the pre-commit hook — **do not run them
|
|
8
|
+
manually.**"
|
|
9
|
+
- `repo-14`: "the local pre-push hook republishes opportunistically when a token happens to
|
|
10
|
+
be present." Never says the hook is the gate; instructs agents to run static checks directly.
|
|
11
|
+
- A repo outside this estate: tells agents to run its linter and `git diff --check` by hand,
|
|
12
|
+
and its contributor guide repeats it.
|
|
13
|
+
- `repo-04`, and 11 repos with no hooks at all: silent.
|
|
14
|
+
|
|
15
|
+
The audit also found the estate's hook reality: 4 incompatible conventions, and
|
|
16
|
+
`repo-05/hooks/pre-push` is **0 bytes** while the real git-lfs pre-push sits unused in
|
|
17
|
+
`scripts/hooks/`.
|
|
18
|
+
|
|
19
|
+
## Ruling
|
|
20
|
+
|
|
21
|
+
The pre-commit hook owns format and lint **for staged files**. Do not re-run what the hook
|
|
22
|
+
just ran on the same files.
|
|
23
|
+
|
|
24
|
+
Manual runs are correct when:
|
|
25
|
+
|
|
26
|
+
1. The hook is not installed in this checkout, or
|
|
27
|
+
2. A check failed and you are diagnosing it, or
|
|
28
|
+
3. The check is not hook-covered (typecheck, migration verification, contract generation).
|
|
29
|
+
|
|
30
|
+
A repo with no hook installed still runs its checks manually. Silence is not permission to
|
|
31
|
+
skip.
|
|
32
|
+
|
|
33
|
+
## Consequences
|
|
34
|
+
|
|
35
|
+
- The shared hook dispatcher from plan 0001 lands in every repo that has one, which also
|
|
36
|
+
delivers the dead `repo-05/hooks/pre-push` fix.
|
|
37
|
+
- `simt-dev doctor` reports hook installation state, so "the hook will cover it" is a
|
|
38
|
+
verifiable claim rather than an assumption.
|
|
39
|
+
- The 11 hook-less repos get a pre-commit on adoption, which ends the silent case entirely.
|
|
40
|
+
|
|
41
|
+
## Rejected
|
|
42
|
+
|
|
43
|
+
- **"Never run lint manually."** False whenever the hook is absent, which is most of the
|
|
44
|
+
estate today.
|
|
45
|
+
- **"Always run lint manually."** Re-runs what a hook already verified, on every iteration.
|
|
@@ -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
|
+
- `repo-14` / `repo-11`: "**Split any file once it exceeds 1000 lines**; extract
|
|
8
|
+
along the nearest cohesive boundary instead of growing the file further."
|
|
9
|
+
- `repo-07`: "**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
|
+
- one repository' 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
|
+
- `repo-14`'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.** one repository' 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
|
+
| `repo-07` | Quantitative, but a different quantity. Memory: 64 KB app / 24 KB Glance against a 32 KB ceiling, plus 12 named lint scripts |
|
|
13
|
+
| `repo-14`, `repo-11`, `repo-13` | Qualitative. No number anywhere. Evidence discipline: named commands, 100 % pass, no unjustified skips, behavioural test delta |
|
|
14
|
+
| `repo-04` | 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
|
+
| `repo-04`, `repo-13` | "Authority and Scope" | "Non-Negotiable Invariants" |
|
|
12
|
+
| `repo-07`, `repo-14` | "Authority and scope" | "Non-negotiable invariants" |
|
|
13
|
+
| `repo-11` | "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 `repo-11` earned it by being the only
|
|
47
|
+
file that enumerates ownership explicitly.
|
|
48
|
+
|
|
49
|
+
## Rejected
|
|
50
|
+
|
|
51
|
+
- **Standardise on Title Case** (`repo-04`'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 one repository — 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
|
+
repo-05'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
|
+
- `repo-14` / `repo-11`: "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 — `repo-07` drives the Monkey C toolchain, and
|
|
24
|
+
`repo-05`/`repo-03`/`repo-01` 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.
|