simt-dev 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +209 -7
- package/README.md +32 -5
- package/docs/decisions/ADR-001-push-requires-approval.md +5 -5
- package/docs/decisions/ADR-002-refactor-blast-radius.md +1 -1
- package/docs/decisions/ADR-003-test-breadth.md +4 -4
- package/docs/decisions/ADR-004-hook-owns-format.md +5 -5
- package/docs/decisions/ADR-005-file-length.md +5 -5
- package/docs/decisions/ADR-006-coverage-bar.md +3 -3
- package/docs/decisions/ADR-007-section-skeleton.md +5 -5
- package/docs/decisions/ADR-008-verdict-vocabulary.md +1 -1
- package/docs/decisions/ADR-009-skill-layout.md +1 -1
- package/docs/decisions/ADR-010-runner-invocation.md +1 -1
- package/docs/decisions/ADR-012-drop-windows-support.md +2 -2
- package/docs/decisions/ADR-015-a-source-is-content-not-a-location.md +2 -2
- package/docs/decisions/ADR-016-a-divergent-rule-is-retired-not-merged.md +13 -13
- package/docs/decisions/ADR-017-a-declared-gate-must-be-runnable.md +2 -2
- package/docs/decisions/ADR-018-an-unset-hooks-path-is-not-an-absent-hook.md +9 -9
- package/docs/decisions/ADR-019-a-declared-command-is-the-runner.md +34 -7
- package/docs/decisions/ADR-023-the-gate-ships-as-a-file-the-repo-owns.md +2 -2
- package/docs/decisions/ADR-027-the-one-gitattributes-line-that-is-neither-dangerous-nor-empty.md +5 -5
- package/docs/decisions/ADR-028-a-base-file-is-a-floor-so-the-disagreement-it-cannot-repair-becomes-a-check.md +2 -2
- package/docs/decisions/ADR-029-the-pins-a-repository-declares-are-its-own.md +1 -1
- package/docs/decisions/ADR-030-what-a-plan-does-not-ship-is-a-decision-with-a-record.md +2 -2
- package/docs/decisions/ADR-031-the-suite-stages-the-environment-it-needs.md +3 -3
- package/docs/decisions/ADR-033-three-plan-rows-closed-by-decision-not-by-authoring.md +4 -4
- package/docs/decisions/ADR-041-the-suite-installs-the-package-in-order-to-test-it.md +2 -2
- package/docs/decisions/ADR-045-a-dashboard-that-counts-a-defect.md +3 -3
- package/docs/decisions/ADR-046-one-rule-one-copy.md +5 -5
- package/docs/decisions/ADR-047-a-profile-selects-on-what-a-repository-runs-on.md +4 -4
- package/docs/decisions/ADR-048-a-baseline-carries-only-keys-a-reader-honours.md +5 -5
- package/docs/decisions/ADR-057-an-identity-is-per-developer-and-a-repository-does-not-own-it.md +115 -0
- package/docs/decisions/ADR-058-one-tool-three-channels-and-the-order-is-forced.md +148 -0
- package/docs/decisions/ADR-059-the-version-a-repository-runs-is-the-one-it-pinned.md +75 -0
- package/docs/decisions/ADR-060-a-repository-with-nothing-to-run-is-adopted-and-says-so.md +131 -0
- package/docs/decisions/ADR-061-the-skill-registry-is-workspace-data.md +113 -0
- package/docs/decisions/ADR-062-a-fresh-adoption-seeds-the-gates-it-can-run.md +94 -0
- package/docs/decisions/ADR-063-the-adoption-escape-hatch-is-opt-in.md +69 -0
- package/docs/decisions/README.md +20 -1
- package/package.json +1 -2
- package/rules/stacks/dart.md +38 -0
- package/rules/stacks/deno.md +36 -0
- package/rules/stacks/dotnet.md +35 -0
- package/rules/stacks/elixir.md +39 -0
- package/rules/stacks/go.md +34 -0
- package/rules/stacks/ruby.md +36 -0
- package/rules/stacks/rust.md +34 -0
- package/rules/stacks/swift.md +37 -0
- package/src/argv.mjs +35 -6
- package/src/ci/dependabot.mjs +1 -1
- package/src/ci/workflow.mjs +33 -11
- package/src/commands/graph.mjs +13 -4
- package/src/commands/help.mjs +22 -4
- package/src/commands/hooks.mjs +183 -67
- package/src/commands/index.mjs +53 -45
- package/src/commands/init.mjs +156 -14
- package/src/commands/rules.mjs +54 -3
- package/src/commands/skills.mjs +143 -15
- package/src/commands/skillsinstall.mjs +4 -4
- package/src/commands/sync.mjs +153 -11
- package/src/compare/discover.mjs +1 -1
- package/src/compare/hooksstate.mjs +96 -9
- package/src/compare/row.mjs +36 -7
- package/src/decisions/index.mjs +51 -12
- package/src/graph/load.mjs +126 -15
- package/src/hooks/capability.mjs +49 -5
- package/src/hooks/dispatcher.mjs +51 -6
- package/src/hooks/gates.mjs +1 -3
- package/src/hooks/resolve.mjs +24 -25
- package/src/hooks/shim.mjs +29 -5
- package/src/hooks/where.mjs +1 -1
- package/src/identity.mjs +207 -0
- package/src/mcp/tools.mjs +2 -2
- package/src/output/index.mjs +8 -7
- package/src/package-version.mjs +74 -0
- package/src/projection/baselines.mjs +1 -1
- package/src/projection/conflicts.mjs +57 -6
- package/src/projection/manifest.mjs +248 -26
- package/src/projection/markers.mjs +3 -3
- package/src/projection/mdc.mjs +6 -15
- package/src/projection/project.mjs +1 -1
- package/src/projection/rules.mjs +21 -8
- package/src/projection/surfaces.mjs +2 -2
- package/src/projection/targets.mjs +53 -7
- package/src/resolve/eval.mjs +64 -0
- package/src/resolve/fingerprint.mjs +4 -3
- package/src/resolve/near.mjs +26 -7
- package/src/resolve/resolve.mjs +1 -1
- package/src/resolve/source.mjs +2 -2
- package/src/tsconfig.mjs +1 -1
- package/src/workspace.mjs +0 -9
- package/graph/skills.graph.json +0 -1622
|
@@ -6,14 +6,14 @@ Affects: `simt-dev hooks install`, `simt-dev hooks status`, `simt-dev compare`,
|
|
|
6
6
|
|
|
7
7
|
## Context
|
|
8
8
|
|
|
9
|
-
Phase 4 began by piloting `simt-dev init` on `
|
|
9
|
+
Phase 4 began by piloting `simt-dev init` on `repo-15`. The dry run was
|
|
10
10
|
unremarkable. The defect appeared when I read what `hooks install` would actually
|
|
11
11
|
do to that repository, which is something I had not done in any of the earlier
|
|
12
12
|
phases.
|
|
13
13
|
|
|
14
|
-
`
|
|
14
|
+
`repo-15` has a hand-written `.git/hooks/pre-commit` — 4088 bytes, with prose
|
|
15
15
|
explaining why it deliberately does not run tests. `core.hooksPath` is unset.
|
|
16
|
-
`simt-dev hooks status --root ../
|
|
16
|
+
`simt-dev hooks status --root ../repo-15 --json` reported:
|
|
17
17
|
|
|
18
18
|
```json
|
|
19
19
|
{ "hooksPath": null, "installed": [{ "event": "pre-commit", "present": false }],
|
|
@@ -64,7 +64,7 @@ question, which is why fixing the shared one changed nothing visible.
|
|
|
64
64
|
1. **Ask git where hooks are, never infer it from whether config exists.**
|
|
65
65
|
`resolveHooksDir(root)` implements the two rules directly: `core.hooksPath`
|
|
66
66
|
when set (a relative value resolves against the top of the working tree, which
|
|
67
|
-
is what makes `
|
|
67
|
+
is what makes `repo-05`'s `hooks` work), otherwise `<git-common-dir>/hooks`.
|
|
68
68
|
|
|
69
69
|
`git rev-parse --git-path hooks` is the obvious shortcut and is unusable. It
|
|
70
70
|
returns `.git/hooks` in a normal repository, `hooks` in a linked worktree, and
|
|
@@ -103,7 +103,7 @@ question, which is why fixing the shared one changed nothing visible.
|
|
|
103
103
|
7. **A foreign hook for an event this tool does not gate is chained, not stopped.**
|
|
104
104
|
`core.hooksPath` does not scope this tool to the events it cares about and leave
|
|
105
105
|
the rest to git; git reads *every* hook from the named directory and from nowhere
|
|
106
|
-
else. So the moment routing lands, `
|
|
106
|
+
else. So the moment routing lands, `repo-05`'s `post-checkout`, `post-commit`
|
|
107
107
|
and `post-merge` — events this tool gates nothing about — stop running. Nothing
|
|
108
108
|
fails, nothing is logged, and the hook is still on disk looking intact: the
|
|
109
109
|
failure is indistinguishable from the hook having passed. Ruling 3's refusal
|
|
@@ -128,9 +128,9 @@ question, which is why fixing the shared one changed nothing visible.
|
|
|
128
128
|
anything the author sees.
|
|
129
129
|
|
|
130
130
|
"Does this tool gate it" is a fact about the manifest, not about the event vocabulary,
|
|
131
|
-
and reading it the second way cost `
|
|
131
|
+
and reading it the second way cost `repo-05` its `pre-push`. That hook is git-lfs.
|
|
132
132
|
`pre-push` is an event this tool gates *in general*, so a vocabulary check displaced it
|
|
133
|
-
— and because `
|
|
133
|
+
— and because `repo-05` declares no `pre-push` gate there is no shim for that event
|
|
134
134
|
either, so git would have found no `pre-push` hook at all. LFS objects would have
|
|
135
135
|
stopped being uploaded, with nothing to notice it: the same silent failure this ruling
|
|
136
136
|
exists to prevent, reached by fixing the wrong half of it. The test asks the manifest.
|
|
@@ -154,8 +154,8 @@ a list that rots, so one test now asserts the two agree and another asserts ever
|
|
|
154
154
|
row carries `notes` and `foreignHooks` even when empty.
|
|
155
155
|
|
|
156
156
|
Measured across the estate after the fix, six repositories run hooks simt-dev did
|
|
157
|
-
not write — `
|
|
158
|
-
`
|
|
157
|
+
not write — `repo-15` (4088 B), `repo-05` (four hooks), `repo-14` (two),
|
|
158
|
+
`repo-11` (two), `repo-10` (one), and the `.wt-wear-redesign`
|
|
159
159
|
worktree (five, including the `git-lfs` `pre-push`). Every one of them would have
|
|
160
160
|
been silently taken over.
|
|
161
161
|
|
|
@@ -8,21 +8,21 @@ Affects: `simt-dev init`, `simt-dev check`, `simt-dev hooks status`, `simt-dev h
|
|
|
8
8
|
## Context
|
|
9
9
|
|
|
10
10
|
ADR-017 made `simt-dev init` declare only gates a repository can actually run, and it shipped
|
|
11
|
-
green because the first adopter — `
|
|
11
|
+
green because the first adopter — `repo-13` — is a Node repository whose scripts
|
|
12
12
|
and binaries are exactly what the filter understood. It was verified by running that
|
|
13
13
|
repository's own tests.
|
|
14
14
|
|
|
15
|
-
`
|
|
15
|
+
`repo-05`, the third pilot, is a Gradle repository. It has **no `package.json` at all**. The
|
|
16
16
|
dry run dropped all five proposed gates, and the remedy for each one was an instruction the
|
|
17
17
|
reader cannot follow. The output as it stood, before this record was written:
|
|
18
18
|
|
|
19
19
|
```
|
|
20
|
-
$ simt-dev init --root ../
|
|
20
|
+
$ simt-dev init --root ../repo-05
|
|
21
21
|
dropped gate test: no "test" script in package.json
|
|
22
22
|
remedy: add a "test" script to package.json, then re-run: simt-dev init --yes
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
Ruling 4 changes both lines. `
|
|
25
|
+
Ruling 4 changes both lines. `repo-05` will never have a `package.json`. "Install prettier as
|
|
26
26
|
a dev dependency" is equally impossible. The filter was right about the facts and wrong about
|
|
27
27
|
what followed from them, and the reader's only honest reaction to that remedy is to stop
|
|
28
28
|
trusting the next one.
|
|
@@ -72,13 +72,40 @@ One manifest. Three answers. The one I would have shipped if I had only run `che
|
|
|
72
72
|
every file (`ENOTDIR`) and classified every Gradle repository in the estate as having no
|
|
73
73
|
runner at all — a wrong answer from an evidence reader, which is worse than no answer
|
|
74
74
|
because the caller cannot tell them apart.
|
|
75
|
+
8. **A declared command is a shell command line, not an argv.** *(S17, 2026-10-12)*
|
|
76
|
+
|
|
77
|
+
It was split on whitespace and handed to `execFile` as an argv. That made a declaration
|
|
78
|
+
a repository would plausibly write run the wrong thing rather than fail: a flag whose
|
|
79
|
+
value contains a space became two arguments, and the quotes in a compound line were
|
|
80
|
+
passed to the program as literal characters. There was no way to express a quoted
|
|
81
|
+
argument at all, and nothing said so.
|
|
82
|
+
|
|
83
|
+
So `gates.commands[name]` is one line, run through `sh -c`, and everything the
|
|
84
|
+
repository wrote in it survives — quotes, pipes, `&&`, a path with a space. This is the
|
|
85
|
+
same line the repository would type into its own shell, which is the only definition of
|
|
86
|
+
"what this repository means by that" that does not require a parser here.
|
|
87
|
+
|
|
88
|
+
**The trust boundary is the repository, and it is not a sandbox.** The line comes from
|
|
89
|
+
the repository's own manifest, which it can read and change, and it runs on its author's
|
|
90
|
+
own machine when that author commits. `sh -c` adds no privilege: the same person who
|
|
91
|
+
wrote the line could run it. What `sh -c` must not do is apply to anything this tool
|
|
92
|
+
resolved itself — a formatter path out of `node_modules/.bin` stays an argv, because
|
|
93
|
+
handing it to a shell would change who decided its arguments for no gain. The two
|
|
94
|
+
sources are separate branches in the dispatcher rather than a flag, so the distinction
|
|
95
|
+
cannot be forgotten by a caller that does not know it exists.
|
|
96
|
+
|
|
97
|
+
**Consequence for a repository that relied on the split.** A declaration with no quotes
|
|
98
|
+
behaves identically, because whitespace-splitting a shell word that needed no quoting
|
|
99
|
+
is the same thing the shell does. Every use of `gates.commands` in this estate declares
|
|
100
|
+
a bare path and is unaffected; the change only makes declarations *possible* that were
|
|
101
|
+
previously impossible rather than making any of them wrong.
|
|
75
102
|
|
|
76
103
|
## Consequences
|
|
77
104
|
|
|
78
|
-
- `
|
|
105
|
+
- `repo-05` declares `i18n-completeness: scripts/check_string_translations.sh` on
|
|
79
106
|
pre-commit — the gate its own hand-written pre-commit hook already runs — and
|
|
80
107
|
`test: ./gradlew check` on pre-push.
|
|
81
|
-
- The `
|
|
108
|
+
- The `repo-13` and `repo-15` manifests are unaffected: they declare no commands.
|
|
82
109
|
- A Node repository that wants to override a gate now can, without abandoning the gate name it
|
|
83
110
|
shares with every other repository. `gates.commands.format` is a supported thing to write.
|
|
84
111
|
- `readCapability` reads one more file per invocation, and it reads it in a `try` that cannot
|
|
@@ -104,7 +131,7 @@ One manifest. Three answers. The one I would have shipped if I had only run `che
|
|
|
104
131
|
|
|
105
132
|
## Evidence
|
|
106
133
|
|
|
107
|
-
`
|
|
134
|
+
`repo-05` is the first adopter with a non-npm runner and the first to declare a command for a
|
|
108
135
|
gate that is not of kind `command`. Every earlier adopter was a Node repository, which is why
|
|
109
136
|
ADR-017's own verification passed. The estate's three runners — npm, Gradle and Python — were
|
|
110
137
|
all in the repository set the whole time; only the pilot order had not reached them.
|
|
@@ -14,7 +14,7 @@ the machine of whoever remembers to run it, and a hook is a developer's machine.
|
|
|
14
14
|
review is somebody else's, and every merge is a machine nobody gated.
|
|
15
15
|
|
|
16
16
|
The plan called this "a reusable workflow", and named
|
|
17
|
-
`
|
|
17
|
+
`the organisation/simt-dev/.github/workflows/sync-check.yml` as a file "referenced by every
|
|
18
18
|
consumer". Nothing in this repository had a `.github/` at all. There were three ways to
|
|
19
19
|
close the gap, and they differ in who the resulting file belongs to:
|
|
20
20
|
|
|
@@ -93,7 +93,7 @@ rules once told readers to edit a source tree they did not have.
|
|
|
93
93
|
|
|
94
94
|
## Rejected
|
|
95
95
|
|
|
96
|
-
- **`uses:
|
|
96
|
+
- **`uses: the organisation/simt-dev/.github/workflows/sync-check.yml@main`.** Rejected under
|
|
97
97
|
ruling 1: it is the plan's own suggestion, and it points every reader at bytes they do
|
|
98
98
|
not have. Worth recording that the plan wrote it, because the next person to read §6
|
|
99
99
|
will find it there.
|
package/docs/decisions/ADR-027-the-one-gitattributes-line-that-is-neither-dangerous-nor-empty.md
CHANGED
|
@@ -19,8 +19,8 @@ empty", and deferred it to a record of its own.
|
|
|
19
19
|
|
|
20
20
|
The estate confirms the first half and not the conclusion. Thirteen of sixteen repositories
|
|
21
21
|
have no `.gitattributes` at all. The three that do disagree about almost everything:
|
|
22
|
-
`
|
|
23
|
-
endings plus ConnectIQ binary markers; `
|
|
22
|
+
`repo-05` declares only LFS filters for `.tif` and `.zip`; `repo-07` declares line
|
|
23
|
+
endings plus ConnectIQ binary markers; `repo-11` declares exactly one whitespace
|
|
24
24
|
suppression for hash-locked vendored content. There is no divergence here of the kind a
|
|
25
25
|
baseline fixes by being consistent. There is a file missing.
|
|
26
26
|
|
|
@@ -48,7 +48,7 @@ to be the line ruling 7 sent this file to make.
|
|
|
48
48
|
because nothing in the pattern says those are binary. `text=auto` is a detection: git
|
|
49
49
|
sniffs each path and applies normalisation only to files it concludes are text. This is
|
|
50
50
|
the most common way a repository corrupts its own binaries, and it is invisible until
|
|
51
|
-
someone diffs an image. `
|
|
51
|
+
someone diffs an image. `repo-07` had independently arrived at `text=auto eol=lf` on
|
|
52
52
|
its first line, which is the strongest available evidence that the estate would pick the
|
|
53
53
|
cautious form if asked — this ruling does not ask, it supplies.
|
|
54
54
|
|
|
@@ -96,8 +96,8 @@ what any file is". A future baseline for a per-stack attribute would have to arr
|
|
|
96
96
|
different row with its own record, which is what §3.2's keep-it-local rule asks for anyway.
|
|
97
97
|
|
|
98
98
|
The repositories with an existing `.gitattributes` are the interesting case and they are not
|
|
99
|
-
in conflict: `
|
|
100
|
-
`
|
|
99
|
+
in conflict: `repo-05`'s LFS filters, `repo-07`'s binary markers and
|
|
100
|
+
`repo-11`'s whitespace suppression all stay beneath the region and all keep winning,
|
|
101
101
|
because git takes the last match. No repository loses a line it wrote.
|
|
102
102
|
|
|
103
103
|
This repository adopts neither baseline for itself, and the reason is the same one that keeps
|
|
@@ -10,7 +10,7 @@ a copied byte range. Settles the `tsconfig.base.json` item of §3.1 row 5 and ro
|
|
|
10
10
|
## Context
|
|
11
11
|
|
|
12
12
|
Row 5 promised eight baselines and named two problems among them that are not about a file
|
|
13
|
-
being missing:
|
|
13
|
+
being missing: repo-08's seven absent strictness flags, and `moduleResolution` spelled
|
|
14
14
|
inconsistently across the workspace. ADR-025 ruled `tsconfig.base.json` out of the region
|
|
15
15
|
mechanism because JSON has no comments, and its consequences promised that "the next fix is
|
|
16
16
|
three more JSON files and therefore a different mechanism". ADR-026 supplied that mechanism
|
|
@@ -95,7 +95,7 @@ one an external document uses.
|
|
|
95
95
|
|
|
96
96
|
A TypeScript repository now gets twelve flags it did not have, and one of them — the floor
|
|
97
97
|
arriving through `extends` — is a behaviour change that can surface real type errors in
|
|
98
|
-
`
|
|
98
|
+
`repo-08`, which is the point of the record and also the reason adoption is one commit
|
|
99
99
|
with a `tsc` run rather than a silent upgrade. A base file is a floor, and a repository
|
|
100
100
|
that had not been standing on one is the case where raising it finds something.
|
|
101
101
|
|
|
@@ -104,7 +104,7 @@ nobody has been asked, and it is the record to read next for either.
|
|
|
104
104
|
|
|
105
105
|
- **A shipped `.nvmrc` or an `engines` value in a shared baseline.** Rejected as
|
|
106
106
|
unrulable: the value is per-repository and the tool can only guess it. Shipping `22` would
|
|
107
|
-
break `
|
|
107
|
+
break `repo-14`, which pins 24.
|
|
108
108
|
- **Reporting cross-repository node divergence as a finding.** Rejected by the Context. The
|
|
109
109
|
plan's premise was wrong and the correction is recorded here so it is not re-derived from
|
|
110
110
|
the plan text.
|
|
@@ -56,7 +56,7 @@ correcting at all.
|
|
|
56
56
|
detectable by reading the file. Corrected in the row.
|
|
57
57
|
|
|
58
58
|
5. **Two of the four configurations are plain JavaScript with no TypeScript in them,**
|
|
59
|
-
`
|
|
59
|
+
`repo-04` and `repo-06`. A "shared 6-rule ESLint block" cannot describe all four,
|
|
60
60
|
because two of them have no rules to share — they differ in `ignores` and per-file
|
|
61
61
|
`globals`, which are per-repository facts about paths.
|
|
62
62
|
|
|
@@ -67,7 +67,7 @@ correcting at all.
|
|
|
67
67
|
may make on its own.
|
|
68
68
|
|
|
69
69
|
7. **The fix for the two conflicts is one dependency each,** and it is not a shared block.
|
|
70
|
-
Installing `eslint-config-prettier` in `
|
|
70
|
+
Installing `eslint-config-prettier` in `repo-14` and `repo-11` and spreading
|
|
71
71
|
it last in the config array resolves both. That is a command per repository, run by the
|
|
72
72
|
repositories, and simt-dev has no business writing it.
|
|
73
73
|
|
|
@@ -16,9 +16,9 @@ landed. Not intermittently: 22 of 543 tests failed on runs `37476671363` through
|
|
|
16
16
|
Three separate causes, none of them recent:
|
|
17
17
|
|
|
18
18
|
1. **`test/install.test.mjs:17` read the estate off the author's disk.**
|
|
19
|
-
`const WORKSPACE = resolve(HERE, '..', '..')` is `/
|
|
20
|
-
install tests copied skill bodies out of it — `
|
|
21
|
-
and `singleRepoClone()` out of `
|
|
19
|
+
`const WORKSPACE = resolve(HERE, '..', '..')` is `/home/developer/code/simt`, and the
|
|
20
|
+
install tests copied skill bodies out of it — `repo-11/.agents/skills/d1-safe-change`,
|
|
21
|
+
and `singleRepoClone()` out of `repo-14`. On a runner the checkout is
|
|
22
22
|
`/home/runner/work/simt-dev/simt-dev`, so `../..` is `/home/runner/work/simt-dev`,
|
|
23
23
|
which contains this repository and nothing else. The bodies were not there,
|
|
24
24
|
`skills install` exited 3, and nineteen tests asserted on a refusal they never asked for.
|
|
@@ -25,7 +25,7 @@ Phase 6 asked to "collapse the 7 drifted Social skill twins to canonical + stub"
|
|
|
25
25
|
answers the row directly. All three resolvers select `role === 'canonical'` and nothing else —
|
|
26
26
|
`src/commands/skills.mjs` twice, `src/commands/skillsinstall.mjs:291`, `src/mcp/tools.mjs:192` — so
|
|
27
27
|
a copy declared `duplicate` or `variant` is never installed. The one pair that genuinely had to be
|
|
28
|
-
collapsed, `add-country-galaxy`, is already a stub in `
|
|
28
|
+
collapsed, `add-country-galaxy`, is already a stub in `repo-14` pointing at the API repo.
|
|
29
29
|
|
|
30
30
|
Phase 7 asked for a sweep of hardcoded absolute paths. The sweep found 17 tracked files. Six were
|
|
31
31
|
fixed. The two Android `local.properties` files were not, and were nearly fixed by reflex.
|
|
@@ -47,7 +47,7 @@ fixed. The two Android `local.properties` files were not, and were nearly fixed
|
|
|
47
47
|
because resolution never read those bytes. Phase 6's twin collapse is therefore closed as
|
|
48
48
|
already answered, at `a9983c8`.
|
|
49
49
|
|
|
50
|
-
3. **`local.properties` stays tracked.** Both `
|
|
50
|
+
3. **`local.properties` stays tracked.** Both `repo-05` and `repo-03` commit it, and both
|
|
51
51
|
`.gitignore` files have the conventional `# local.properties` line commented out at lines 22 and
|
|
52
52
|
24 — a deliberate override of Android's own convention, not an oversight. Untracking them would
|
|
53
53
|
be a house-style change made while closing a plan, and a build machine that checks out without
|
|
@@ -74,8 +74,8 @@ unmade decision is not a gap to fill by invention.
|
|
|
74
74
|
|
|
75
75
|
**Collapsing `graphify` and `parallel-feature-team`.** Both are declared `duplicate` with matching
|
|
76
76
|
byte counts, so they look like the obvious candidates. `graphify` is `vendored: true` — an
|
|
77
|
-
84 534-byte, ten-file, version-pinned tool present in both
|
|
78
|
-
|
|
77
|
+
84 534-byte, ten-file, version-pinned tool present in both one repository on purpose. Deleting
|
|
78
|
+
repo-14's copy would leave nothing to detect the loss, because resolution only ever read the
|
|
79
79
|
canonical copy.
|
|
80
80
|
|
|
81
81
|
**Untracking `local.properties`.** An Android SDK path is machine state, and two repos chose to
|
|
@@ -21,7 +21,7 @@ Two facts about this repository make that gap harder to notice than it looks. `t
|
|
|
21
21
|
reach outside the checkout (ADR-031), and this package has no dependencies (ADR-012). Together
|
|
22
22
|
those mean the suite has no installed copy of itself to test, and the natural workaround —
|
|
23
23
|
borrowing a formatter from a neighbouring checkout — is the one ADR-031 rules out. Prettier 3.8.1
|
|
24
|
-
is sitting in `../
|
|
24
|
+
is sitting in `../repo-06/node_modules/.bin` and is not available here.
|
|
25
25
|
|
|
26
26
|
## Ruling
|
|
27
27
|
|
|
@@ -87,6 +87,6 @@ node, git and a POSIX shell; npm is in the same class. A conditional skip conver
|
|
|
87
87
|
into an unverified one, which is the failure this file was written to stop.
|
|
88
88
|
|
|
89
89
|
**Copying Prettier out of a sibling checkout.** It exists, it works, and ADR-031 rules it out for
|
|
90
|
-
the same reason it rules out reading skill bodies from `../
|
|
90
|
+
the same reason it rules out reading skill bodies from `../repo-14`: a suite that passes on
|
|
91
91
|
the author's machine and fails on a runner with only this checkout is evidence about the author's
|
|
92
92
|
directory.
|
|
@@ -16,13 +16,13 @@ Measured over the 16 repositories at `~/code/simt`:
|
|
|
16
16
|
```
|
|
17
17
|
staleRuleFiles: 17
|
|
18
18
|
repos naming stale rule files in problems: 0
|
|
19
|
-
|
|
19
|
+
repo-05 problems:
|
|
20
20
|
"the generated region was hand-edited; restore it with: simt-dev sync --yes"
|
|
21
21
|
"core.hooksPath is \"hooks\", not the shared dispatcher; run: simt-dev hooks install --yes"
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
Five adopted repositories have stale rule files — `
|
|
25
|
-
of 3 current, `
|
|
24
|
+
Five adopted repositories have stale rule files — `repo-05`, `repo-06` and `repo-14` at 0
|
|
25
|
+
of 3 current, `repo-11` and `repo-13` at 0 of 4 — and not one of them said so in
|
|
26
26
|
its problems list. The summary already said `staleRuleFiles: 17`, because `summarise` has always
|
|
27
27
|
counted them. So the dashboard was reporting a count of a defect it refused to call a defect.
|
|
28
28
|
|
|
@@ -14,11 +14,11 @@ Review 0001 §S12, marked P1 and "decision". Measured across the estate before a
|
|
|
14
14
|
the five repositories selecting `opencode,cursor`:
|
|
15
15
|
|
|
16
16
|
```
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
repo-05 folded=3 .mdc=3 0/3 current
|
|
18
|
+
repo-06 folded=3 .mdc=3 0/3 current
|
|
19
|
+
repo-14 folded=3 .mdc=3 0/3 current
|
|
20
|
+
repo-11 folded=4 .mdc=4 0/4 current
|
|
21
|
+
repo-13 folded=4 .mdc=4 0/4 current
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
Seventeen generated rule files. Every one of them byte-identical to the section already folded into
|
|
@@ -50,7 +50,7 @@ other `react-native`, and neither receives both.
|
|
|
50
50
|
**Two.** The Android and uv facts name a path at an unknown depth, which the two existing kinds
|
|
51
51
|
(`file`, `dir`) cannot express. A third kind, `glob`, walks at most `GLOB_DEPTH` directory levels
|
|
52
52
|
below the root. The bound is a convention, not a preference: the Android Gradle Plugin puts a
|
|
53
|
-
module's manifest at `<module>/src/main/AndroidManifest.xml`, and
|
|
53
|
+
module's manifest at `<module>/src/main/AndroidManifest.xml`, and repo-05's sits at
|
|
54
54
|
`app/src/main/AndroidManifest.xml`. A shallower bound makes `has-android-manifest` the shape S07
|
|
55
55
|
found — a fact no repository could have. A deeper one is a scan rather than a probe, and
|
|
56
56
|
ADR-013's promise is that adding an ecosystem is data, so a fact must not cost a search.
|
|
@@ -78,11 +78,11 @@ repository loses coverage:
|
|
|
78
78
|
|
|
79
79
|
| Repo | Before | After |
|
|
80
80
|
| --- | --- | --- |
|
|
81
|
-
| `
|
|
82
|
-
| `
|
|
81
|
+
| `repo-01`, `repo-05`, `repo-03` | `core, jvm-android` | `core, android, jvm` |
|
|
82
|
+
| `repo-02` | `container, core, python` | `container, core, python, uv` |
|
|
83
83
|
|
|
84
84
|
The three Android repositories gain the profile whose body they actually needed and keep the JVM
|
|
85
|
-
build rules; `
|
|
85
|
+
build rules; `repo-05` gaining `android` was the row's acceptance criterion. `repo-02`
|
|
86
86
|
carries a committed `uv.lock`, so its gain is the defect repaired, not a new selection.
|
|
87
87
|
|
|
88
88
|
The rename is breaking in one narrow way: the id `jvm-android` no longer exists. A consumer that
|
|
@@ -63,11 +63,11 @@ it.
|
|
|
63
63
|
|
|
64
64
|
| Repository | Prettier config | States `singleQuote` | Adopting `quote_type = single` |
|
|
65
65
|
| --- | --- | --- | --- |
|
|
66
|
-
| `
|
|
67
|
-
| `
|
|
68
|
-
| `
|
|
69
|
-
| `
|
|
70
|
-
| `
|
|
66
|
+
| `repo-04` | `.prettierrc` | `true` | no change |
|
|
67
|
+
| `repo-06` | `.prettierrc.json` | `true` | no change |
|
|
68
|
+
| `repo-15` | `.prettierrc.json` | `true` | no change |
|
|
69
|
+
| `repo-11` | `.prettierrc.json` | absent | **every source file reformatted** |
|
|
70
|
+
| `repo-14` | `.prettierrc.json` | absent | **every source file reformatted** |
|
|
71
71
|
|
|
72
72
|
Two repositories would have every source file rewritten on the day they adopted the
|
|
73
73
|
tool, in a commit whose message says it adopted a rules projection. The measurement is
|
package/docs/decisions/ADR-057-an-identity-is-per-developer-and-a-repository-does-not-own-it.md
ADDED
|
@@ -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.
|