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,49 @@
|
|
|
1
|
+
# ADR-053 — The Node floor is 22.20.0
|
|
2
|
+
|
|
3
|
+
Status: **accepted** · 2026-10-10
|
|
4
|
+
**Affects:** `package.json`, `src/ci/workflow.mjs`, `src/resolve/resolve.mjs`.
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
Node 20 reached end of life on 2026-04-30. `engines.node` still said `>=20`, both workflow
|
|
9
|
+
templates pinned setup-node to `20`, and `matchesGlob` was a hand-written translation because
|
|
10
|
+
`path.matchesGlob` is stable from Node 22.20.0 (and 24.8). ADR-042 recorded that translation
|
|
11
|
+
and said ruling 1 collapses into the built-in when the floor moves. Review 0002 S47 settles
|
|
12
|
+
the floor at `>=22.20.0` and says the own CI runs 22, 24 and 26.
|
|
13
|
+
|
|
14
|
+
## Ruling
|
|
15
|
+
|
|
16
|
+
**One.** `engines.node` is `>=22.20.0`. Both workflow templates pin `actions/setup-node` to the
|
|
17
|
+
version that range names, read from `package.json`, so a template cannot name a version the
|
|
18
|
+
package says it does not run on. This repository's own gate also runs Node 24 and Node 26. A
|
|
19
|
+
floor that is only executed at the minimum is a floor in name.
|
|
20
|
+
|
|
21
|
+
**Two.** `matchesGlob` is `path.matchesGlob`. A trailing double star also matches the directory
|
|
22
|
+
itself: the built-in requires a child, so `migrations` misses `migrations/**`, and that empty
|
|
23
|
+
path is the one `graph lint` builds (ADR-042, ruling 3). A glob containing `?`, `{` or `[`
|
|
24
|
+
matches nothing, so ruling 4 still holds. Ruling 1 of ADR-042 is superseded by this record.
|
|
25
|
+
Rulings 2, 3 and 4 of ADR-042 stand.
|
|
26
|
+
|
|
27
|
+
**Three.** The runtime line is the same sentence in `package.json`, both templates, this
|
|
28
|
+
record, plan 0002's live Runtime line, and `README.md`. The README states the floor and
|
|
29
|
+
nothing else; the packaging README is S18 and does not get to pick a different floor.
|
|
30
|
+
|
|
31
|
+
## Rejected
|
|
32
|
+
|
|
33
|
+
**Waiting for S18's README before raising the floor.** The floor is what CI and the matcher
|
|
34
|
+
run on. A missing README was not a reason to keep declaring a runtime that is end of life.
|
|
35
|
+
|
|
36
|
+
**Pinning both templates at Node 24 in this record.** S48 owns "templates use 24", the SHA
|
|
37
|
+
pins, and the macOS matrix. This record's adopting template uses the floor so it agrees with
|
|
38
|
+
`engines`. The own gate's extra versions are coverage, not a second floor.
|
|
39
|
+
|
|
40
|
+
**Letting `path.matchesGlob` implement `?` and braces.** It does, and that is how `src/a?.ts`
|
|
41
|
+
matches `src/aq.ts`. ADR-042 ruling 4 refused that reading because the registry does not use
|
|
42
|
+
it and a glob that matches the wrong paths is worse than a glob lint rejects.
|
|
43
|
+
|
|
44
|
+
## Consequences
|
|
45
|
+
|
|
46
|
+
- `compileGlob` is gone. The cases ADR-042's tests lock (`**/*.ts`, a star that does not cross
|
|
47
|
+
`/`, a bare `**`) are now the built-in's, and the `?` case still fails lint.
|
|
48
|
+
- SHA-pinning `actions/checkout` and `actions/setup-node`, and the macOS runner, stay with S48.
|
|
49
|
+
- The adopting workflow's install step stays with S35.
|
simt_dev/vendored/docs/decisions/ADR-054-the-digest-covers-every-byte-that-leaves-the-package.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# ADR-054 — The digest covers every byte that leaves the package, and a cause is read off the body
|
|
2
|
+
|
|
3
|
+
Status: **accepted** · 2026-10-10 · amends [ADR-032](ADR-032-provenance-is-a-content-digest-not-a-commit.md)
|
|
4
|
+
**Affects:** `src/projection/rules.mjs`, `src/upgrade/plan.mjs`.
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
ADR-032 made the stamp a SHA-256 digest of `rules/` and `graph/`, and said why `src/` is
|
|
9
|
+
excluded: a refactor that changes no rule and no registry entry projects byte-identical
|
|
10
|
+
output, and stamping those consumers would report drift where there is none.
|
|
11
|
+
|
|
12
|
+
That reasoning is right and the boundary was drawn one level too low. ADR-032 excluded a
|
|
13
|
+
*directory*; the shared `.editorconfig` and `.gitattributes` blocks are not in a directory,
|
|
14
|
+
they are two string constants in `src/projection/baselines.mjs` — and every adopting
|
|
15
|
+
repository receives them. By ADR-015's own rule, a source is identified by its content rather
|
|
16
|
+
than by the file it happens to live in, so the body defined in `src/` is a source like any
|
|
17
|
+
other, and excluding `src/` as a path excluded content that leaves the package with it.
|
|
18
|
+
|
|
19
|
+
The finding arrived sideways, which is the only way this class gets found. Removing the
|
|
20
|
+
inert `[prettier]` block (S30) deleted seven lines from a shipped baseline — the first change
|
|
21
|
+
in this repository that moves bytes out of `src/` without moving the provenance digest.
|
|
22
|
+
Measured on a scratch repository with **only** that baseline body changed:
|
|
23
|
+
|
|
24
|
+
- `check` reported `DRIFT .editorconfig`, which is correct: the gate compares bytes, and the
|
|
25
|
+
bytes were wrong.
|
|
26
|
+
- `upgrade` reported the pin as `(unchanged)` and named the cause as
|
|
27
|
+
*"region restamped; the pin was already current (the rules changed)"*.
|
|
28
|
+
|
|
29
|
+
Both halves of that sentence are false. The pin was unchanged because the stamp could not see
|
|
30
|
+
the change, and the rules did not change — seven lines of an indentation setting did. The
|
|
31
|
+
first false statement made the second one reachable: with the stamp silent, the only cause
|
|
32
|
+
left to report was the one that was least true.
|
|
33
|
+
|
|
34
|
+
The severity is the estate's own: a report that names a cause which did not happen sends the
|
|
35
|
+
reader to diff `rules/` when the change is in `.editorconfig`. This is ADR-050's mistake one
|
|
36
|
+
layer up — a field read for the thing it was not — and ADR-036's, where an instruction that
|
|
37
|
+
is false in every repository is worse than one that is missing.
|
|
38
|
+
|
|
39
|
+
## Ruling
|
|
40
|
+
|
|
41
|
+
**One. The digest covers every byte string that leaves the package: `rules/` and `graph/` on
|
|
42
|
+
disk, plus the rendered baseline bodies.** The baselines enter keyed by the file each becomes
|
|
43
|
+
in the adopting repository (`baselines/.editorconfig`), not by the module that defines it,
|
|
44
|
+
because the stamp answers what content produced those bytes and `.editorconfig` is the name
|
|
45
|
+
a reader of a generated region recognises. `src/` remains excluded *as a directory*; the
|
|
46
|
+
ruling narrows ADR-032's exclusion from a path to a rule about content. Ruling 1 of ADR-032
|
|
47
|
+
and its "what the digest covers" section are superseded; ADR-032's framing, its framing
|
|
48
|
+
discipline and its other two consequences stand.
|
|
49
|
+
|
|
50
|
+
**Two. `provenanceInputs()` is exported, and `provenanceDigest()` hashes what it returns.**
|
|
51
|
+
"The digest covers the right things" is a claim about a set, and a set assembled only inside
|
|
52
|
+
the function that hashes it cannot be asserted from outside — the test would have to mutate
|
|
53
|
+
the package to get an answer. Reading the list and hashing it in one place is ADR-042's
|
|
54
|
+
ruling 3 applied to the stamp: the rendering is shared, so narrowing it narrows the test with
|
|
55
|
+
it, and the test passed with the rendering sabotaged is not evidence.
|
|
56
|
+
|
|
57
|
+
**Three. `upgrade`'s cause is read off the projected body, and the two kinds of content are
|
|
58
|
+
counted apart.** A baseline region is not a rule: `.editorconfig` and `.gitattributes` are
|
|
59
|
+
settings the repository owns. `rulesChanged` and `baselineChanged` are computed separately
|
|
60
|
+
and the sentence names one, both, or neither. The comparison is body to body rather than
|
|
61
|
+
byte to byte, because the region header carries the stamp and folding it in would make every
|
|
62
|
+
pin bump claim a change of policy — which is ADR-050's ruling, and it is why a target whose
|
|
63
|
+
seam moved reads as `updated` with no cause.
|
|
64
|
+
|
|
65
|
+
**Four. "Neither" is a real answer and is not collapsed into the nearest cause.** A seam
|
|
66
|
+
repair, a stamp-only move and a generated-field move are three different problems that one
|
|
67
|
+
command fixes, and the sentence distinguishing them is the whole reason the command exists.
|
|
68
|
+
|
|
69
|
+
## Consequences
|
|
70
|
+
|
|
71
|
+
- **Every adopted repo's stamp moves once more.** A repo pinned at a digest computed without
|
|
72
|
+
the baselines reports `behind` the first time it runs this version and `simt-dev upgrade`
|
|
73
|
+
fixes it. That is a pin doing its job, not a regression — the same reading as ADR-032's own
|
|
74
|
+
first consequence.
|
|
75
|
+
- **`upgrade` can no longer report a cause it cannot see.** The two defects were independent
|
|
76
|
+
and are now separately guarded: the digest test asserts the baselines are in the hashed set
|
|
77
|
+
and that changing one changes the value; the planner test asserts a baseline body move
|
|
78
|
+
produces "a baseline changed" and not "the rules changed".
|
|
79
|
+
- **The planner needed a `kind` it did not have.** Baseline targets already carried
|
|
80
|
+
`kind: 'baseline'`; rule and primary targets carry none. That is the discriminator, and it
|
|
81
|
+
is read from the targets `plan` produced rather than recomputed from the file list, so the
|
|
82
|
+
two cannot disagree about which file is a baseline.
|
|
83
|
+
- **A `src/` refactor still moves nobody.** The exclusion was narrowed from a path to a rule
|
|
84
|
+
about content, and the test asserts no `src/` entry appears in the hashed set — so the
|
|
85
|
+
narrowing cannot be re-widened silently by a future import.
|
|
86
|
+
|
|
87
|
+
## Rejected
|
|
88
|
+
|
|
89
|
+
- **Hash `src/` as a directory.** The smallest diff and the wrong one: it puts every
|
|
90
|
+
refactor, comment change and test edit into every consumer's stamp, which is precisely the
|
|
91
|
+
false report ADR-032 ruled out.
|
|
92
|
+
- **Leave the stamp alone and fix only the wording.** The wording was the visible half. With
|
|
93
|
+
the pin reporting `(unchanged)` beside a restamped region, `upgrade` would still be
|
|
94
|
+
describing a move that did not happen — and the reader has no way to tell which line of
|
|
95
|
+
the output to disbelieve.
|
|
96
|
+
- **Give baselines their own stamp field.** A second identity per repository, and the reader
|
|
97
|
+
would have to know which of two values to compare against. One stamp, covering everything
|
|
98
|
+
it stamps.
|
|
99
|
+
- **Call the baseline a "rule" in the message.** Cheaper than a discriminator, and it sends
|
|
100
|
+
the reader to `rules/` — which is the defect.
|
simt_dev/vendored/docs/decisions/ADR-055-a-status-line-travels-so-it-may-not-name-a-location.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# ADR-055 — A status line travels, so it may not name a location; prose is the record's own argument
|
|
2
|
+
|
|
3
|
+
Status: **accepted** · 2026-10-10 · filed as S54 · affects `src/decisions/index.mjs`
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
|
|
7
|
+
`rules why` prints a decision record's `Status:` line into whatever repository the reader
|
|
8
|
+
is standing in, and most of them are repositories that adopted simt-dev and therefore own
|
|
9
|
+
none of this package's files. The `summarise` contract said so itself, in the module that
|
|
10
|
+
produces the output: *"Never contains a path, because a record read inside a consumer repo
|
|
11
|
+
cannot name one."*
|
|
12
|
+
|
|
13
|
+
The `ruled` half honoured that. The `status` half did not, because it is the record's own
|
|
14
|
+
prose: five records wrote `· affects src/resolve/resolve.mjs` into a field the tool parses,
|
|
15
|
+
copies and prints verbatim on every `rules why`, `rules show` and MCP call. The review
|
|
16
|
+
measured this as "two records out of forty-eight, pre-existing". The corpus says **five**,
|
|
17
|
+
and **three of them were written after that measurement** — ADR-053 and ADR-054 in this
|
|
18
|
+
cycle, and ADR-041 days earlier. A finding that grows while it is being fixed is a finding
|
|
19
|
+
whose real cause is a convention rather than a typo, and the convention is what needed
|
|
20
|
+
ruling.
|
|
21
|
+
|
|
22
|
+
The convention was ADR-038's: *"That is what the `Affects:` line on a record is for."* Five
|
|
23
|
+
records had an `Affects:` field available and put the clause in `Status:` instead — because
|
|
24
|
+
`Status:` is the line the tool reads, so putting it there feels like the place that counts.
|
|
25
|
+
It is the opposite: it is the one field guaranteed to travel.
|
|
26
|
+
|
|
27
|
+
## Ruling
|
|
28
|
+
|
|
29
|
+
**One. A `Status:` line may not name a location.** It is this tool's own metadata — accepted,
|
|
30
|
+
the date, what superseded what, what it affects — and every clause in it is a statement about
|
|
31
|
+
*this package* by construction. There is no benign reading to confuse it with, so the
|
|
32
|
+
predicate needs no judgement: a location resolving at this package's root is redacted to a
|
|
33
|
+
phrase naming the thing rather than the path. The redaction happens once, in `loadDecisions`,
|
|
34
|
+
so the text command, the JSON payload and the MCP surface cannot disagree about it — which is
|
|
35
|
+
the same "one place decides" shape ADR-035 settled for writers.
|
|
36
|
+
|
|
37
|
+
**Two. `Affects:` is where a location belongs, and it stays unprinted.** The loader already
|
|
38
|
+
stops the status paragraph at a sibling field, so moving the clause is a reformat rather
|
|
39
|
+
than a change of ruling, and the five records are corrected in place for the clause only.
|
|
40
|
+
|
|
41
|
+
**Three. The exempt list is what `init` writes, and it is executed.** Dot-entries and three
|
|
42
|
+
named files (`AGENTS.md`, `package.json`, `simt-dev.json`) are excluded from redaction,
|
|
43
|
+
because a repository that adopted simt-dev has them. That is a claim about a set held in a
|
|
44
|
+
comment, and a claim about a set held in a comment is a value nobody runs — so a test adopts
|
|
45
|
+
a repository and asserts that every top-level file the tool writes survives the predicate
|
|
46
|
+
unchanged. A name that drifts on stops being redacted; a name that drifts off starts
|
|
47
|
+
redacting a file the reader does have.
|
|
48
|
+
|
|
49
|
+
**Four. Prose is the record's argument and is left as written.** The `ruled` and `rejected`
|
|
50
|
+
excerpts are a human argument, and they may legitimately quote a location — `docs/plans/`
|
|
51
|
+
means *this package's* plan in one record and *the reader's own plans* in another, and no
|
|
52
|
+
amount of reading the string separates them. Redaction there would have to guess, and a
|
|
53
|
+
guessed redaction makes a correct sentence wrong, which is the worse defect. `redactEstate`
|
|
54
|
+
redacts prose and is not a counter-example: an estate repository name is never the reader's
|
|
55
|
+
business either, whereas a path inside an argument sometimes is.
|
|
56
|
+
|
|
57
|
+
**Five. The cost of ruling four is a number, not a shrug.** Seven records still print a
|
|
58
|
+
location in their prose — six older ones, and this record, which quotes the collision it
|
|
59
|
+
turns on — and a test asserts the count and the ids. A record that stops quoting one moves
|
|
60
|
+
it; a record that starts moves it too. The alternative — leaving it unmeasured — is how "two
|
|
61
|
+
of forty-eight, pre-existing" came to describe ten.
|
|
62
|
+
|
|
63
|
+
**Six. The `summarise` docstring's overclaim is withdrawn.** It asserted a property of prose
|
|
64
|
+
that the function never had and could not have had. An unverifiable promise in a comment is
|
|
65
|
+
the defect ADR-049 is about, one level down: a sentence that reads like evidence and carries
|
|
66
|
+
none. The comment now states the scope it can keep.
|
|
67
|
+
|
|
68
|
+
## Consequences
|
|
69
|
+
|
|
70
|
+
- **Five records changed, and only their `Status:` line.** No ruling, no context and no
|
|
71
|
+
rejected alternative was touched. ADR-049's supersession machinery is unaffected:
|
|
72
|
+
`partialSupersessions` still reads the raw paragraph, because a ruling number is not a
|
|
73
|
+
location and redacting it would have been the wrong trade.
|
|
74
|
+
- **`rules why ADR-042` no longer prints `src/resolve/resolve.mjs`.** It prints
|
|
75
|
+
`accepted · 2026-10-08 · ruling 1 superseded by ADR-053`, which is the whole of what the
|
|
76
|
+
line is for.
|
|
77
|
+
- **A sixth record with a path in `Status:` is scrubbed rather than blocked.** The records
|
|
78
|
+
are corrected so they stay readable to a maintainer on this repository, where the path is
|
|
79
|
+
the useful part; the loader is hardened so a future one is harmless. The corpus test
|
|
80
|
+
asserts the *unscrubbed* status carries none, which is the stronger statement.
|
|
81
|
+
- **Prose leaks are visible rather than absent.** Seven records — six older and ADR-055
|
|
82
|
+
itself, which quotes the collision it turns on. Anyone who decides that is still too many
|
|
83
|
+
now has the count and the argument against redaction.
|
|
84
|
+
|
|
85
|
+
## Rejected
|
|
86
|
+
|
|
87
|
+
- **Redact the prose too.** The `docs/plans/` collision is not hypothetical — ADR-014,
|
|
88
|
+
ADR-016, ADR-025 and ADR-030 all use that exact string for both meanings, and `test/leaks.mjs`
|
|
89
|
+
already singles it out as a substitution *default* that names where a repo keeps its own
|
|
90
|
+
plans. Redacting it would corrupt four records to protect six.
|
|
91
|
+
- **Rewrite the ten records to drop the paths.** This repository's rule is that a record is
|
|
92
|
+
never edited in place, and the argument these records make is better with the file named.
|
|
93
|
+
The clause moves to the field designed for it; the sentence does not change.
|
|
94
|
+
- **Stop printing the status line.** It carries the supersession, which is the one thing a
|
|
95
|
+
reader needs in order not to act on a reversed ruling.
|
|
96
|
+
- **Put the redaction in `commands/rules.mjs`.** It would then apply to the text output and
|
|
97
|
+
not to `--json` or MCP, and the three surfaces would disagree about what a record says —
|
|
98
|
+
the copy ADR-052 exists to prevent.
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# ADR-056 — The command, the package and the project are three names, and only one of them is cheap to move
|
|
2
|
+
|
|
3
|
+
Status: **accepted** · 2026-10-10 · filed as S18 · amends [ADR-023](ADR-023-the-gate-ships-as-a-file-the-repo-owns.md) · affects `src/name.mjs`, `package.json`, `src/projection/manifest.mjs`, `src/projection/markers.mjs`
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
|
|
7
|
+
This tool had four names and no record of which was which. `PACKAGE_NAME` was spelled in
|
|
8
|
+
`locate.mjs`, the manifest name in `manifest.mjs`, the config directory in `workspace.mjs`
|
|
9
|
+
and the gate filename in `workflow.mjs` — four definitions, four places to forget. The
|
|
10
|
+
command itself appeared **276 times across 33 files**, in remedy strings, help columns, shim
|
|
11
|
+
source, MCP tool descriptions and the note every generated region carries.
|
|
12
|
+
|
|
13
|
+
Publishing forced the question S18 had been asking since it was filed: *what is this
|
|
14
|
+
called?* And the answer turned out to be four different answers depending on what you were
|
|
15
|
+
asking about, which is not an answer.
|
|
16
|
+
|
|
17
|
+
The measurements that settled it:
|
|
18
|
+
|
|
19
|
+
- `simt-dev.json` is on disk in **fourteen repositories** that adopted the tool.
|
|
20
|
+
- `<!-- simt-dev:begin -->` is in every projected `AGENTS.md` in every one of them.
|
|
21
|
+
- `.simt-hooks/` and `SIMT_DEV_BIN` are in the `.git` directories of those repositories.
|
|
22
|
+
- `simt-dev-check.yml` is in the `.github/workflows` of each of them.
|
|
23
|
+
- The command name appears in **no file at all** until a reader types it.
|
|
24
|
+
|
|
25
|
+
## Ruling
|
|
26
|
+
|
|
27
|
+
**One. There are three names, and they are recorded as three.** `src/name.mjs` owns all of
|
|
28
|
+
them and nothing else spells them:
|
|
29
|
+
|
|
30
|
+
- `PROJECT` — the repository, and the artefacts already on somebody else's disk.
|
|
31
|
+
- `CLI` — the command a reader types.
|
|
32
|
+
- `PACKAGE_NAME` — what you install to get the command.
|
|
33
|
+
|
|
34
|
+
They are three constants that happen to be related. Deriving one from another was
|
|
35
|
+
considered and rejected: a derivation that picks between a package and a scope turns a
|
|
36
|
+
one-line rename into a one-line diff whose meaning depends on a comparison nobody reading
|
|
37
|
+
it would think to check.
|
|
38
|
+
|
|
39
|
+
**Two. The command is `simt`; the project and the package keep `simt-dev`.** This is the
|
|
40
|
+
whole ruling, and it rests on the asymmetry in the measurement above: the command appears
|
|
41
|
+
only in text the tool itself writes, and that text migrates on the next write, while the
|
|
42
|
+
other two names sit in files the tool cannot rewrite in place. Spending the rename on the
|
|
43
|
+
cheap name is what made this a rename instead of a fourteen-repository roll-forward.
|
|
44
|
+
|
|
45
|
+
**Three. The manifest follows the command, and its move is a read-both/write-new.** The
|
|
46
|
+
manifest was one of the on-disk names and it is the one that moved: `simt-dev.json` is now
|
|
47
|
+
`simt.json`. Without a fallback, all fourteen adopting repositories would read as "no
|
|
48
|
+
manifest" on their next `check` — and "no manifest" is the answer that offers to re-adopt a
|
|
49
|
+
repository that is already done. So:
|
|
50
|
+
|
|
51
|
+
- **Read** takes `simt.json` if it exists, else `simt-dev.json`. Every read site goes
|
|
52
|
+
through one resolver (`manifestPath`) rather than knowing the fallback itself, because a
|
|
53
|
+
fallback that lives in five places is a fallback that eventually stops living in one of
|
|
54
|
+
them. The fingerprint, which reads the directory listing rather than a path, accepts both
|
|
55
|
+
spellings for the same reason: a fingerprint that misses the manifest silently loses the
|
|
56
|
+
repository's declared stack, which is the one input no amount of marker reading replaces.
|
|
57
|
+
- **Write** goes to `simt.json`, and a lone `simt-dev.json` is moved, not shadowed: two
|
|
58
|
+
manifests in one repository is a state nobody can reason about, and the second would be
|
|
59
|
+
the one that is never read.
|
|
60
|
+
- **The window closes per repository** on its first `init` or manifest write after the
|
|
61
|
+
upgrade. Until then, the old name keeps working everywhere.
|
|
62
|
+
|
|
63
|
+
The fallback is asserted in `test/naming.test.mjs` with both spellings — the new name wins
|
|
64
|
+
when both exist, the old name is still read, and a write moves the old file — because its
|
|
65
|
+
failure mode is silent: the tool would simply stop seeing a repository it has been
|
|
66
|
+
managing.
|
|
67
|
+
|
|
68
|
+
**Four. The region namespace follows the command, and its move is read-both/write-new
|
|
69
|
+
too.** The second on-disk name to move: the region was stamped `simt-dev:begin` /
|
|
70
|
+
`simt-dev:end`, and so was the provenance header inside it (`simt-dev:generated`,
|
|
71
|
+
`simt-dev:sha=`, `simt-dev:sources=`); all of those lines are now `simt:`. A marker cannot
|
|
72
|
+
be renamed the way a manifest can, because a region cannot be moved — it lives inside a
|
|
73
|
+
file the reader owns. So the fallback lives in the marker grammar itself, one read regex
|
|
74
|
+
per dialect:
|
|
75
|
+
|
|
76
|
+
- **Read** accepts either spelling in both dialects (HTML comment and `#` comment),
|
|
77
|
+
because a marker that does not match is simply not a region, and a caller that does not
|
|
78
|
+
accept the old spelling would silently report an adopted repository as unadopted.
|
|
79
|
+
- **Write** emits only the `simt:` spelling, so the first write to each region migrates it
|
|
80
|
+
in place: the body comparison in `inject` finds the header prefix drifted and rewrites
|
|
81
|
+
the region where it stands. The window closes per repository on its first `sync`.
|
|
82
|
+
- **A file holding both spellings is malformed, not merged.** Only a hand edit can produce
|
|
83
|
+
it — the tool writes one region per file and finds it by either spelling. Guessing which
|
|
84
|
+
region wins would silently delete the other, so `parse` reports it and a human deletes
|
|
85
|
+
the old one.
|
|
86
|
+
|
|
87
|
+
The provenance digest covers `rules/`, `graph/` and `baselines/` — not `src/`, where the
|
|
88
|
+
marker strings live (ADR-032, S53) — so the stamp *value* does not move with this rename;
|
|
89
|
+
only the key's prefix does. No repository needs re-pinning, and `upgrade` sees no version
|
|
90
|
+
change: the migration is marker bytes only.
|
|
91
|
+
|
|
92
|
+
The fallback is asserted in `test/markers.test.mjs` — a legacy region is read and its
|
|
93
|
+
stamp recovered, the first write migrates it in place, the migrated region is stable, and
|
|
94
|
+
a mixed file is reported malformed — because its failure mode is the manifest's: the tool
|
|
95
|
+
would simply stop seeing a repository it has been managing.
|
|
96
|
+
|
|
97
|
+
**Five. The remaining on-disk names are locked, and the lock is asserted.**
|
|
98
|
+
`test/naming.test.mjs` pins `simt-dev-check.yml`, `.simt-hooks`, `SIMT_DEV_BIN` and the
|
|
99
|
+
project name as literals, so renaming one is a deliberate edit to a test rather than a
|
|
100
|
+
constant change that looks harmless. A lock nobody can trip is a comment.
|
|
101
|
+
|
|
102
|
+
**Six. The command name lives in one constant and is executed, not agreed.**
|
|
103
|
+
`test/naming.test.mjs` walks `src/` and fails on any command string that *spells* the
|
|
104
|
+
command instead of interpolating it. The pattern matches the real verb list rather than
|
|
105
|
+
"space then a lowercase word", because the looser rule flags the tool naming itself in
|
|
106
|
+
prose — `simt-dev will not remove a file it wrote` and `the simt-dev region is not at the
|
|
107
|
+
top of the file` are the project talking about itself, and the project keeps its name.
|
|
108
|
+
The guard was proven with a negative control: one injected spelled command, one failure
|
|
109
|
+
naming the file and line, reverted.
|
|
110
|
+
|
|
111
|
+
**Seven. The package is unscoped.** `@simt` and `@quazarmedia` were both free on npm; an
|
|
112
|
+
unscoped `simt-dev` was already reserved by this project's own account. An unscoped name
|
|
113
|
+
is the one a reader can guess, and the reserved one was the only spelling already pointing
|
|
114
|
+
at something.
|
|
115
|
+
|
|
116
|
+
## Consequences
|
|
117
|
+
|
|
118
|
+
- **No repository has to do anything by hand, and the migration announces itself.** Every
|
|
119
|
+
adopted repo keeps its hooks directory and gate. The manifest is read under either name
|
|
120
|
+
and moves to `simt.json` on its first write; the region is read under either namespace
|
|
121
|
+
and migrates to `simt:` on its first `sync`. Until each repository has written once,
|
|
122
|
+
`check` reports the region as drifted — which is the migration making itself visible
|
|
123
|
+
rather than hiding, and it disappears on the `sync` that performs it. The migration is
|
|
124
|
+
real, but it runs itself — one repository per write — instead of running as a
|
|
125
|
+
fourteen-repository roll-forward.
|
|
126
|
+
- **The generated gate file names the command correctly.** `name: simt check`, "If `simt`
|
|
127
|
+
cannot be found the job FAILS", "run `simt ci --yes`". These are shipped bytes into
|
|
128
|
+
every adopting repository, which is why they were checked by rendering them, not by
|
|
129
|
+
reading the source.
|
|
130
|
+
- **One upgrade step is real and is documented.** A shim written before this change looks
|
|
131
|
+
for `simt-dev` on `PATH`. A machine that installed the package and never re-ran
|
|
132
|
+
`hooks install` finds nothing until the dependency is refreshed and `simt hooks install
|
|
133
|
+
--yes` re-runs. That is in the CHANGELOG rather than left to be discovered.
|
|
134
|
+
- **The remaining on-disk names can still move, but only deliberately.** The gate file,
|
|
135
|
+
the hooks directory and the shim's environment variable would each need the shape the
|
|
136
|
+
manifest and the markers got — read-both, write-new, an asserted fallback — and S36's
|
|
137
|
+
estate roll-out to execute it.
|
|
138
|
+
|
|
139
|
+
## Rejected
|
|
140
|
+
|
|
141
|
+
- **Rename the artefacts too — `masar.json`, `masar-hooks/`, `<!-- masar:begin -->`.**
|
|
142
|
+
This was the shape the slice was originally taken in and it is strictly worse: it turns
|
|
143
|
+
a rename into a fourteen-repository migration, every one of which would see its
|
|
144
|
+
`AGENTS.md` stop being managed on the first `check` after the upgrade. It was rejected
|
|
145
|
+
for what it would have cost, not for being unclear. The manifest and the region markers
|
|
146
|
+
are exceptions to this rejection, and they are exceptions only because rulings three
|
|
147
|
+
and four make their moves safe: a marker that does not match is simply not a region, so
|
|
148
|
+
the fallback lives in the marker grammar itself — one read regex per dialect that
|
|
149
|
+
accepts both spellings — and a file holding two regions is reported malformed rather
|
|
150
|
+
than merged. The hooks directory and the gate remain fully rejected: they have no
|
|
151
|
+
second spelling a reader's file could fall back to.
|
|
152
|
+
- **`@quazarmedia/simt-dev` as the package name.** Free, but already named in the shim
|
|
153
|
+
remedy, the review and the workflow install step. Changing it would have edited all
|
|
154
|
+
three for no availability gain, since the unscoped name was already reserved.
|
|
155
|
+
- **Rename the command by a script that rewrites every literal.** The first attempt at the
|
|
156
|
+
propagation used "space then a lowercase word" and rewrote `user.name=simt-dev tests`
|
|
157
|
+
and a test title — both of which would have passed. A rename that cannot tell a command
|
|
158
|
+
from a git identity in a fixture is a rename that will eventually rewrite a sentence.
|
|
159
|
+
- **Derive `PACKAGE_NAME` from `SCOPE` and `PROJECT`.** Rejected above; the three names
|
|
160
|
+
will not always agree, and the day they disagree the derivation picks for you.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# An identity is per-developer, and a repository does not own it
|
|
2
|
+
|
|
3
|
+
Status: **accepted** · 2026-10-11
|
|
4
|
+
|
|
5
|
+
Affects: `simt init`, `src/identity.mjs`, `test/identity.test.mjs`
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
Two repositories in the estate carried a `git.config` file in their tracked files.
|
|
10
|
+
It holds a name and an email address:
|
|
11
|
+
|
|
12
|
+
```ini
|
|
13
|
+
[user]
|
|
14
|
+
name = <one developer's name>
|
|
15
|
+
email = <that developer's email address>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Both had a local `include.path` pointing at it, and both worked. Both were also
|
|
19
|
+
wrong in ways that had nothing to do with each other.
|
|
20
|
+
|
|
21
|
+
**It was a tracked file.** A repository has more than one person in it. Committed,
|
|
22
|
+
one developer's name and email is published to every other developer and to
|
|
23
|
+
everybody who can read the repository. This repository's own release review had
|
|
24
|
+
already said so, in the table of findings, as: *"`git.config` tracked at the root
|
|
25
|
+
with a personal name and email — Identity file in a public tree, read by nothing
|
|
26
|
+
— Remove it from the tree."* The finding was recorded and the file stayed.
|
|
27
|
+
|
|
28
|
+
**It pointed somewhere else.** Both `include.path` values named
|
|
29
|
+
`../../simt-dev/git.config` — another repository's identity file, resolved through
|
|
30
|
+
the git directory rather than the working tree. Including the git config's own
|
|
31
|
+
file from a relative path three directories up means one repository's commits
|
|
32
|
+
depend on a sibling checkout existing at an expected path, which fails the moment
|
|
33
|
+
anybody clones without it. It went unnoticed because every developer here has a
|
|
34
|
+
global identity configured, so the include resolved to the same person the
|
|
35
|
+
include would have skipped to, and the file it never read was the reason nothing
|
|
36
|
+
broke.
|
|
37
|
+
|
|
38
|
+
The interesting part is that these are opposite mistakes with the same root.
|
|
39
|
+
`git.config` is not the repository's to own, and treating it as the repository's
|
|
40
|
+
produced both: a file that should have been ignored got committed, and an
|
|
41
|
+
`include` that should have named the repository's own file named a stranger's.
|
|
42
|
+
|
|
43
|
+
## Ruling
|
|
44
|
+
|
|
45
|
+
1. **`simt init` writes `git.config` when the repository has none.** It is planned
|
|
46
|
+
like everything else `init` writes, so it appears in the same preview that lists
|
|
47
|
+
the rest, and its source is named on the line — writing somebody's name is a
|
|
48
|
+
statement about a person and the plan says where the name came from.
|
|
49
|
+
|
|
50
|
+
2. **An existing `git.config` is left exactly as it is.** Not updated, not merged,
|
|
51
|
+
not reformatted. It may have been written by a person, by an older version of
|
|
52
|
+
this tool, or by hand years ago, and none of those are this tool's to
|
|
53
|
+
second-guess.
|
|
54
|
+
|
|
55
|
+
3. **The identity is derived, in this order, and the first answer wins:**
|
|
56
|
+
the repository's **last commit author**, then the identity **git would use on
|
|
57
|
+
this machine anyway**, then nothing.
|
|
58
|
+
|
|
59
|
+
Last commit first because it is a fact rather than a default: it is who has
|
|
60
|
+
actually been working here. It is read from `git log`, not from
|
|
61
|
+
`git config user.email`, because at that moment the configured value is what the
|
|
62
|
+
file is about to be derived from, and the step would be circular.
|
|
63
|
+
|
|
64
|
+
The second is `--get` rather than `--global`, so it resolves through git's own
|
|
65
|
+
precedence and returns what a commit would have used had this file never
|
|
66
|
+
existed. That is the fallback's whole point: it must not be a second opinion
|
|
67
|
+
about what identity means.
|
|
68
|
+
|
|
69
|
+
4. **When nothing is available, no file is written.** A placeholder is an identity
|
|
70
|
+
git would use on the next commit, and an adopting command is the last thing that
|
|
71
|
+
should invent one. The refusal is printed with its reason rather than swallowed.
|
|
72
|
+
|
|
73
|
+
5. **Only the keys that were found are written.** A partial identity is written as a
|
|
74
|
+
partial identity, for the same reason.
|
|
75
|
+
|
|
76
|
+
6. **`init` adds `git.config` to `.gitignore`** when it creates one, and points
|
|
77
|
+
`include.path` at `../git.config` — the repository's own file.
|
|
78
|
+
|
|
79
|
+
`--replace-all`, not `--add`: a value naming *another* repository's identity file
|
|
80
|
+
is not an addition to keep, it is a link to break.
|
|
81
|
+
|
|
82
|
+
A repository that already *tracks* `git.config` is not untracked by `init`.
|
|
83
|
+
Removing a tracked file is not something an adopting command should do silently,
|
|
84
|
+
and doing it would be a change to that repository's history rather than to its
|
|
85
|
+
working tree.
|
|
86
|
+
|
|
87
|
+
## Consequences
|
|
88
|
+
|
|
89
|
+
`src/identity.mjs` is new and is the single answer to "whose identity is this, and
|
|
90
|
+
may we write it". `init` plans it, writes it, gitignores it and repoints the
|
|
91
|
+
include; the four refusals are asserted in `test/identity.test.mjs` rather than
|
|
92
|
+
described, because each of them is the correct answer rather than a failure to try
|
|
93
|
+
harder.
|
|
94
|
+
|
|
95
|
+
One test scrubs `HOME` and `GIT_CONFIG_GLOBAL` before asserting the "nothing
|
|
96
|
+
anywhere" case. That is not tidiness: a developer with a global `user.email` — which
|
|
97
|
+
is most of them — makes the identity resolvable, so without scrubbing the test
|
|
98
|
+
would pass for the wrong reason on that machine and nowhere else. ADR-031 is about
|
|
99
|
+
a result depending on the author's environment; this test would have been the next
|
|
100
|
+
instance of it.
|
|
101
|
+
|
|
102
|
+
## Rejected
|
|
103
|
+
|
|
104
|
+
- **Write `git.config` into the tracked tree, as a shared record of who works
|
|
105
|
+
here.** This is what the two repositories did, and it is a publication of a
|
|
106
|
+
person's email address with a commit attached.
|
|
107
|
+
- **Take the identity from the OS (the login name, the home directory).** It
|
|
108
|
+
produces `<login-name>@example.com` and similar: an identity nobody configured,
|
|
109
|
+
used for authorship. Ruling 4's refusal covers it.
|
|
110
|
+
- **Overwrite an existing `git.config` when it differs from the derived one.**
|
|
111
|
+
Difference is not error. A file that has been there for years and disagrees with
|
|
112
|
+
the last commit author is a decision somebody made.
|
|
113
|
+
- **Fix the `include` only when it is wrong.** The include is only written when it
|
|
114
|
+
is missing or does not name this repository's own file. Rewriting it when it is
|
|
115
|
+
already correct would be a no-op that reads as a change.
|