simt-dev 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,32 @@ All notable changes to this package are recorded here. The format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project follows
5
5
  [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.1.1] — 2026-10-11
8
+
9
+ ### A foreign hook on an event this repo does not gate is chained
10
+
11
+ `0.1.0` chained the wrong set of hooks and is worth replacing rather than
12
+ recommending. It chained on the event *vocabulary* — anything that was not
13
+ `pre-commit` or `pre-push` — when the question worth asking is whether **this
14
+ repository** declares a gate for that event.
15
+
16
+ The difference is `simt-app`, whose `pre-push` is `git-lfs`. `pre-push` is an
17
+ event this tool gates in general, so `0.1.0` displaced that hook; and because
18
+ `simt-app` declares no `pre-push` gate there was no shim for the event either, so
19
+ git would have found no `pre-push` hook at all. LFS objects would have stopped
20
+ being uploaded, with nothing to notice it — the exact silent failure chaining
21
+ exists to prevent, reached by fixing the wrong half of it.
22
+
23
+ `0.1.1` asks the manifest. An event the repo declares a gate for is left to
24
+ `gates.commands`, where the hook runs as a gate and its verdict appears in the
25
+ report; every other foreign hook is chained. That also picks up
26
+ `simt-landing`'s Lighthouse pre-push, which declares no gates at all.
27
+ *(ADR-018, as amended)*
28
+
29
+ Upgrade with `npm i -D simt-dev@0.1.1`, then `simt hooks install --yes`. Any
30
+ repository already routed with `0.1.0` should re-run it: the shims it writes
31
+ will differ where a gated event was previously displaced.
32
+
7
33
  ## [0.1.0] — 2026-10-11
8
34
 
9
35
  The first release with real contents in it. `1.0.0` was an empty reservation of the name
@@ -100,4 +126,5 @@ never ran `simt hooks install` again will find nothing until the dependency is r
100
126
  and `simt hooks install --yes` is re-run. That is a real upgrade step and it is called out
101
127
  here rather than discovered.
102
128
 
129
+ [0.1.1]: https://github.com/QuazarMedia/simt-dev/releases/tag/v0.1.1
103
130
  [0.1.0]: https://github.com/QuazarMedia/simt-dev/releases/tag/v0.1.0
package/README.md CHANGED
@@ -23,13 +23,13 @@ Requires **Node >= 22.20.0**.
23
23
 
24
24
  ```sh
25
25
  # pinned, per repository - what CI and a teammate both get
26
- npm i -D simt-dev@0.1.0
26
+ npm i -D simt-dev@0.1.1
27
27
 
28
28
  # or globally, if you would rather not add a dependency
29
- npm i -g simt-dev@0.1.0
29
+ npm i -g simt-dev@0.1.1
30
30
 
31
31
  # or with no install at all, for a one-off
32
- npx -y simt-dev@0.1.0 check
32
+ npx -y simt-dev@0.1.1 check
33
33
  ```
34
34
 
35
35
  Pin the version. The tool writes a content digest into the files it generates, and an
@@ -121,13 +121,23 @@ question, which is why fixing the shared one changed nothing visible.
121
121
  hook is not there at all, the shim refuses and names the path — a hook that
122
122
  silently stops running is the exact outcome this ruling exists to prevent.
123
123
 
124
- A gate-type foreign hook is deliberately **not** chained. `pre-commit` and
125
- `pre-push` are events this tool gates, so the hook belongs in the manifest under
126
- `gates.commands` and its verdict belongs in the gate report. Chaining it would
127
- run it *after* the gates passed, outside anything the author sees. The install
128
- note now says which of the two happened, because "stopped running" was wrong
129
- about a hook that was carried across and would have pushed an operator to restore
130
- a hook that was already running for them.
124
+ A gate-type foreign hook is deliberately **not** chained — *when this repository
125
+ declares a gate for that event*. The dispatcher replaces the hook there by design, the
126
+ hook's real place is `gates.commands`, and its verdict belongs in the gate report next
127
+ to the others. Chaining it would run it *after* the gates had already passed, outside
128
+ anything the author sees.
129
+
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 `simt-app` its `pre-push`. That hook is git-lfs.
132
+ `pre-push` is an event this tool gates *in general*, so a vocabulary check displaced it
133
+ — and because `simt-app` declares no `pre-push` gate there is no shim for that event
134
+ either, so git would have found no `pre-push` hook at all. LFS objects would have
135
+ stopped being uploaded, with nothing to notice it: the same silent failure this ruling
136
+ exists to prevent, reached by fixing the wrong half of it. The test asks the manifest.
137
+
138
+ The install note says which of the two happened, because "stopped running" was wrong
139
+ about a carried hook and would have pushed an operator to restore a hook that was
140
+ already running for them.
131
141
 
132
142
  ## Consequences
133
143
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "simt-dev",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Shared agent rules, skills and git hooks for a repository: one command adopts a repo, and every gate it declares is probed before it is written.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Said Elimam",
@@ -39,7 +39,5 @@
39
39
  "scripts": {
40
40
  "test": "node --test test/*.test.mjs",
41
41
  "lint": "node bin/simt-dev.mjs doctor"
42
- },
43
- "dependencies": {},
44
- "devDependencies": {}
45
- }
42
+ }
43
+ }
@@ -169,19 +169,33 @@ async function installHooks(root, flags) {
169
169
  // stop running - and they stop running silently, which is why they are planned here
170
170
  // rather than left to the operator to notice (ADR-018, as amended).
171
171
  //
172
- // A gate-type foreign hook is deliberately NOT chained: it belongs in the manifest
173
- // as a declared command, so the dispatcher runs it and its verdict is visible in the
174
- // gate output. Chaining it would run it after a gate passed, outside the report.
172
+ // "Does not gate" is a fact about THIS repository: `gateCount` asks what its manifest
173
+ // declares for that event, so an event outside the known vocabulary (post-commit) and
174
+ // an event the repo simply leaves empty (a pre-push with no gates) are both chained,
175
+ // while an event the repo does gate is left to the manifest's gates.commands - there
176
+ // the hook runs as a gate and its verdict appears in the report beside the others,
177
+ // rather than after the gates passed where an author cannot see it.
178
+ //
179
+ // Keying this on the event vocabulary instead of on the manifest cost simt-app its
180
+ // git-lfs hook: pre-push is a gated event in general, but this repo declares no
181
+ // pre-push gate, so nothing replaces its hook and routing would have dropped it.
175
182
  const chained = [
176
183
  // Already chained by an earlier install, read back off disk: re-rendered from the
177
184
  // file's own contents so the bytes cannot drift on a second run.
178
185
  ...(await Promise.all(
179
186
  state.chained.map(async (c) => ({ event: c.event, target: await relativeTarget(root, c.target) })),
180
187
  )),
181
- // Newly found foreign hooks on events this tool does not gate.
188
+ // Foreign hooks on events THIS repo has no gate for.
189
+ //
190
+ // Per repository, not per event vocabulary - and the difference is not academic.
191
+ // simt-app declares no pre-push gate, and its pre-push is git-lfs: routing without a
192
+ // shim for that event means git finds no pre-push hook at all and LFS objects stop
193
+ // being uploaded, silently, with nothing to notice it. An event this repo gates is
194
+ // different: the dispatcher replaces the hook there by design, and the hook's real
195
+ // place is gates.commands.
182
196
  ...(await Promise.all(
183
197
  state.foreign
184
- .filter((f) => !EVENTS.includes(f.event))
198
+ .filter((f) => gateCount(manifest, f.event) === 0)
185
199
  .map(async (f) => ({ event: f.event, target: await relativeTarget(root, f.path) })),
186
200
  )),
187
201
  ].reduce((all, c) => (all.some((x) => x.event === c.event) ? all : [...all, c]), []);