instar 1.3.1102 → 1.3.1104
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/dist/data/standards-guard-index.json +1 -1
- package/dist/data/standards-guard-index.meta.json +2 -2
- package/dist/data/standards-registry.meta.json +1 -1
- package/package.json +1 -1
- package/scripts/ux-impact-lint.mjs +3 -3
- package/src/data/builtin-manifest.json +2 -2
- package/src/data/standards-guard-index.json +1 -1
- package/src/data/standards-guard-index.meta.json +2 -2
- package/src/data/standards-registry.meta.json +1 -1
- package/upgrades/1.3.1104.md +82 -0
- package/upgrades/side-effects/pnpm-build-allowlist.md +88 -0
- package/upgrades/side-effects/ux-gate-scaffold-scope.md +152 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"generatedFrom": "source-tree",
|
|
4
4
|
"registrySha256": "5413a0c6ef9ba2bda876b509d1c0bfebe450da3708cd5a5d627d6cb836012d58",
|
|
5
|
-
"packageVersion": "1.3.
|
|
5
|
+
"packageVersion": "1.3.1104",
|
|
6
6
|
"guards": [
|
|
7
7
|
{
|
|
8
8
|
"ref": "docs/canonical-migration-contracts.json",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"sha256": "
|
|
2
|
+
"sha256": "df7bf3346549230693cf23ef0a622e04298755308c8d142bf96014a87804d9e2",
|
|
3
3
|
"registrySha256": "5413a0c6ef9ba2bda876b509d1c0bfebe450da3708cd5a5d627d6cb836012d58",
|
|
4
|
-
"packageVersion": "1.3.
|
|
4
|
+
"packageVersion": "1.3.1104"
|
|
5
5
|
}
|
package/package.json
CHANGED
|
@@ -22,12 +22,12 @@ try {
|
|
|
22
22
|
const report = { version: 1, base, head, authorInScope, scope, allowlistedPaths: [], exempt: false, internalError: false };
|
|
23
23
|
const writeReport = () => { if (reportPath) writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`); };
|
|
24
24
|
if (!authorInScope) { report.outOfScopeAuthor = true; await writeReport(); console.log('UX lint: out-of-scope author'); process.exit(0); }
|
|
25
|
-
const allowlisted = names.filter((p) => p === 'src/server/routes.ts' || p === 'src/commands/server.ts' || p.startsWith('src/messaging/') || p.startsWith('src/dashboard/') || p.startsWith('src/templates/'));
|
|
25
|
+
const allowlisted = names.filter((p) => p === 'src/server/routes.ts' || p === 'src/commands/server.ts' || p === 'src/scaffold/templates.ts' || p.startsWith('src/messaging/') || p.startsWith('src/dashboard/') || p.startsWith('src/templates/'));
|
|
26
26
|
report.allowlistedPaths = allowlisted;
|
|
27
27
|
if (allowlisted.length === 0) { await writeReport(); console.log('UX lint: out of scope'); process.exit(0); }
|
|
28
28
|
const diff = execFileSync('git', ['diff', '--unified=0', `${base}...${head}`, '--', ...allowlisted], { encoding: 'utf8' });
|
|
29
29
|
const added = diff.split('\n').filter((line) => line.startsWith('+') && !line.startsWith('+++')).join('\n');
|
|
30
|
-
const refactorOnly = !allowlisted.some((p) => p.startsWith('src/templates/') || p === 'src/server/routes.ts' || p === 'src/commands/server.ts')
|
|
30
|
+
const refactorOnly = !allowlisted.some((p) => p.startsWith('src/templates/') || p === 'src/server/routes.ts' || p === 'src/commands/server.ts' || p === 'src/scaffold/templates.ts')
|
|
31
31
|
&& !/(?:^|\s)[`'\"](?:[^`'\"]+)[`'\"]/.test(added);
|
|
32
32
|
const section = body.match(/^## UX Impact\s*\n([\s\S]*?)(?=^##\s|(?![\s\S]))/im)?.[1]?.trim() || '';
|
|
33
33
|
if (/UX-Impact:\s*refactor-only/i.test(section) && refactorOnly) { report.exempt = true; report.exemption = 'refactor-only'; await writeReport(); console.log('UX lint PASS: deterministic refactor-only exemption'); process.exit(0); }
|
|
@@ -38,7 +38,7 @@ try {
|
|
|
38
38
|
}
|
|
39
39
|
const quoted = [...section.matchAll(/[`'"“]([^`'"”]+)[`'"”]/g)].map((m) => m[1]);
|
|
40
40
|
if (!quoted.some((q) => q.length > 2 && diff.includes(q))) {
|
|
41
|
-
console.error(
|
|
41
|
+
console.error(`::error::UX Impact must quote a concrete string from the USER-FACING paths this PR touches (${allowlisted.join(', ')}). Quotes from other changed files are not checked.`); process.exit(1);
|
|
42
42
|
}
|
|
43
43
|
await writeReport();
|
|
44
44
|
console.log(`UX lint PASS: ${allowlisted.length} allowlisted path(s)`);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./builtin-manifest.schema.json",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"generatedAt": "2026-08-
|
|
5
|
-
"instarVersion": "1.3.
|
|
4
|
+
"generatedAt": "2026-08-01T18:31:54.310Z",
|
|
5
|
+
"instarVersion": "1.3.1104",
|
|
6
6
|
"entryCount": 202,
|
|
7
7
|
"entries": {
|
|
8
8
|
"hook:session-start": {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"generatedFrom": "source-tree",
|
|
4
4
|
"registrySha256": "5413a0c6ef9ba2bda876b509d1c0bfebe450da3708cd5a5d627d6cb836012d58",
|
|
5
|
-
"packageVersion": "1.3.
|
|
5
|
+
"packageVersion": "1.3.1104",
|
|
6
6
|
"guards": [
|
|
7
7
|
{
|
|
8
8
|
"ref": "docs/canonical-migration-contracts.json",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"sha256": "
|
|
2
|
+
"sha256": "df7bf3346549230693cf23ef0a622e04298755308c8d142bf96014a87804d9e2",
|
|
3
3
|
"registrySha256": "5413a0c6ef9ba2bda876b509d1c0bfebe450da3708cd5a5d627d6cb836012d58",
|
|
4
|
-
"packageVersion": "1.3.
|
|
4
|
+
"packageVersion": "1.3.1104"
|
|
5
5
|
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
`pnpm install` failed on a fresh checkout of `main` and had done for some time. pnpm >= 11 refuses to run a dependency's install script without an explicit allowlist and exits non-zero while any remain unresolved; no allowlist was ever committed. Reproduced on v1.3.1071 with pnpm 11.5.1 from a branch off current `origin/main`: `exit 1`, `ERR_PNPM_IGNORED_BUILDS`, 13 ignored build scripts, nothing built.
|
|
9
|
+
|
|
10
|
+
This commits `pnpm-workspace.yaml` with an explicit boolean decision per package, and a unit-test guard.
|
|
11
|
+
|
|
12
|
+
Nine are allowed because their install step produces a real runtime artifact — a compiled native binding or a downloaded platform binary: `better-sqlite3`, `bufferutil`, `cloudflared`, `cpu-features`, `esbuild`, `onnxruntime-node`, `sharp`, `ssh2`, `utf-8-validate`. Three are declined because they produce nothing this project needs: `baileys` (engine-requirements check), `es5-ext` (prints a notice), `protobufjs`. Each entry carries an inline comment naming what its script actually does.
|
|
13
|
+
|
|
14
|
+
Two findings recorded while fixing it:
|
|
15
|
+
|
|
16
|
+
- **The failure was invisible to CI.** `ci.yml` installs with `npm ci` across all six jobs, so the pipeline never exercised the pnpm path that the bundled CLAUDE.md Quick Reference tells every agent to use. A green build proved nothing about it. The guard test exists because of this: without the pnpm path in CI, deleting the file or flipping a required entry to `false` would re-break the documented route without turning anything red.
|
|
17
|
+
- **The same allowlist in `package.json` under `pnpm.onlyBuiltDependencies` is silently ignored** by pnpm 11.5.1 — tried directly, install still exited 1 with all 13 still refused. The guard asserts that field is absent, so a later tidy-up cannot move the list somewhere that reads as configuration while doing nothing.
|
|
18
|
+
|
|
19
|
+
Deliberately not changed: which package manager this project standardises on. The docs say pnpm, CI uses npm, and both lock files are committed. That is an operator decision with consequences for contributors and the pipeline, and making the install work does not settle it. `packageManager` is also not added, because pinning it would make corepack authoritative over the `npm ci` jobs CI depends on — a regression risk for no benefit here. <!-- tracked: ACT-1613 -->
|
|
20
|
+
|
|
21
|
+
`scripts/ux-impact-lint.mjs` decides whether a PR counts as user-facing by matching
|
|
22
|
+
changed files against a hard-coded allowlist. That allowlist omitted
|
|
23
|
+
`src/scaffold/templates.ts` — the file the Agent Awareness Standard names as THE
|
|
24
|
+
user-awareness surface ("An agent that doesn't know about a capability effectively
|
|
25
|
+
doesn't have it"). A PR touching only that file matched nothing, hit
|
|
26
|
+
`allowlisted.length === 0`, and exited 0. The change that alters what every agent tells
|
|
27
|
+
its users required no UX declaration at all. `src/templates/` (hook and helper scripts)
|
|
28
|
+
was allowlisted; `src/scaffold/` was not.
|
|
29
|
+
|
|
30
|
+
Three edits, one defect:
|
|
31
|
+
|
|
32
|
+
1. The `:25` allowlist gains `p === 'src/scaffold/templates.ts'` — the exact file the
|
|
33
|
+
standard names, in the exact exact-match style already used for two other files.
|
|
34
|
+
Deliberately **not** `startsWith('src/scaffold/')`: widening a gate beyond what a
|
|
35
|
+
standard names is a policy change nobody approved.
|
|
36
|
+
2. The `:30` refactor-only exemption excludes that path too — rewriting agent-visible
|
|
37
|
+
text is never a pure refactor.
|
|
38
|
+
3. The `:41` failure message now names the concept and lists the paths actually
|
|
39
|
+
searched. It previously read "UX Impact must quote a concrete string from the diff"
|
|
40
|
+
while searching only the allowlisted subset of the diff, so an author who correctly
|
|
41
|
+
quoted a real added line from an unlisted file was told something untrue about why
|
|
42
|
+
they failed.
|
|
43
|
+
|
|
44
|
+
The gating predicate at `:40` is untouched. `scripts/` is CI-only, is not bundled into
|
|
45
|
+
`dist/`, and is executed by no deployed agent, so no running agent's behaviour changes.
|
|
46
|
+
|
|
47
|
+
## What to Tell Your User
|
|
48
|
+
|
|
49
|
+
None — internal change (no user-facing surface).
|
|
50
|
+
|
|
51
|
+
## Summary of New Capabilities
|
|
52
|
+
|
|
53
|
+
None — internal change (no user-facing surface).
|
|
54
|
+
|
|
55
|
+
## Evidence
|
|
56
|
+
|
|
57
|
+
- **Reproduced before fixing**, on a branch off freshly-fetched `origin/main` (3c90a8a65, v1.3.1071), with the pre-existing untracked placeholder file moved aside so the measurement reflected true fresh-checkout state: `pnpm install --frozen-lockfile </dev/null` → `exit=1`, `ERR_PNPM_IGNORED_BUILDS` listing all 13.
|
|
58
|
+
- **After the fix:** `exit=0`, and the native builds genuinely ran — ssh2 compiled and linked its optional crypto binding (`SOLINK_MODULE(target) Release/sshcrypto.node`, "Succeeded in building optional crypto binding").
|
|
59
|
+
- **Idempotent:** immediate re-run `exit=0` in 152ms.
|
|
60
|
+
- **Build unaffected:** `pnpm build` → `exit=0`.
|
|
61
|
+
- **Guard test red-green verified on every failure mode rather than assumed:** passes as written (4/4); with `pnpm-workspace.yaml` hidden → 3 failures, first being "pnpm-workspace.yaml is required"; with `sharp: true` flipped to `false` → 1 failure naming sharp specifically; restored → 4/4 again.
|
|
62
|
+
|
|
63
|
+
Verified against real commit `e29259c49`, which touches `src/scaffold/templates.ts` and
|
|
64
|
+
zero allowlisted paths (both confirmed by control counts):
|
|
65
|
+
|
|
66
|
+
| lint | result |
|
|
67
|
+
|---|---|
|
|
68
|
+
| shipping (`origin/main`) | `UX lint: out of scope`, exit 0 — gate skipped entirely |
|
|
69
|
+
| patched | engages, exit 1 on `UX-Impact: none` |
|
|
70
|
+
| patched, replaying PR #1813 | exit 0 — no regression on a real passing PR |
|
|
71
|
+
|
|
72
|
+
The static contradiction, with a control: `CLAUDE.md` names `src/scaffold/templates.ts`
|
|
73
|
+
as the user-awareness surface, while `ux-impact-lint.mjs` contained `scaffold` zero
|
|
74
|
+
times (control: `templates` = 2, so the search works and this is a true negative).
|
|
75
|
+
|
|
76
|
+
New test `tests/unit/ux-impact-lint-scaffold-scope.test.ts` covers both sides of the
|
|
77
|
+
boundary and was trip-tested by reverting the lint to `origin/main`: 3/3 pass with the
|
|
78
|
+
fix, 2 fail without it, and the internal-path control passes either way.
|
|
79
|
+
|
|
80
|
+
Known residual, not claimed as fixed: the allowlist remains a proxy for "user-facing"
|
|
81
|
+
and still omits `PostUpdateMigrator.ts`, skill bodies, and hook templates. This closes
|
|
82
|
+
one hole in one list; the list still has no owner and no review trigger.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Side-Effects Review — Commit the pnpm dependency build allowlist
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `pnpm-build-allowlist`
|
|
4
|
+
**Date:** `2026-07-30`
|
|
5
|
+
**Author:** `echo (instar-dev agent)`
|
|
6
|
+
**Second-pass reviewer:** `not required — no decision-point surface (see inventory)`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
`pnpm install` exits non-zero on a fresh checkout of current `main` (reproduced on v1.3.1071 / pnpm 11.5.1: `exit 1`, `ERR_PNPM_IGNORED_BUILDS`, 13 ignored build scripts) because pnpm >= 11 refuses to run a dependency's install script without an explicit allowlist, and no allowlist was committed. This adds `pnpm-workspace.yaml` recording an explicit boolean per package — 9 `true` (native binding compiled or platform binary downloaded), 3 `false` (engine check / printed notice / no artifact this project needs) — plus `tests/unit/pnpm-build-allowlist.test.ts` as a regression guard. Files touched: `pnpm-workspace.yaml` (new), `tests/unit/pnpm-build-allowlist.test.ts` (new), `docs/specs/pnpm-build-allowlist.eli16.md` (new), `upgrades/next/pnpm-build-allowlist.md` (new), this artifact.
|
|
11
|
+
|
|
12
|
+
Verified: install `exit 0` (native builds genuinely ran — ssh2 compiled its optional crypto binding), idempotent re-run `exit 0` in 152ms, `pnpm build` `exit 0`.
|
|
13
|
+
|
|
14
|
+
## Decision-point inventory
|
|
15
|
+
|
|
16
|
+
This change adds **no** instar decision point. It does not gate information flow, block an action, filter a message, or constrain agent behavior. It is build-time dependency configuration consumed by pnpm, plus a test.
|
|
17
|
+
|
|
18
|
+
- `pnpm-workspace.yaml` → `allowBuilds` — **add** — build-time allowlist read by pnpm at install; no runtime surface, no instar code reads it.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 1. Over-block
|
|
23
|
+
|
|
24
|
+
**What legitimate inputs does this change reject that it shouldn't?**
|
|
25
|
+
|
|
26
|
+
No block/allow surface over inputs — over-block not applicable in the messaging/gating sense.
|
|
27
|
+
|
|
28
|
+
The nearest analogue is the three `false` entries, which decline a build that *could* have run. Assessed individually against what each script actually does: `baileys` (`preinstall: node ./engine-requirements.js`) only asserts a minimum Node version; `es5-ext` (`postinstall: node -e "try{require('./_postinstall')}..."`) prints a notice and is written to swallow its own failure; `protobufjs` (`postinstall: node scripts/postinstall`) produces nothing this project consumes. If any of those judgements is wrong the symptom is a runtime failure in that specific package, and the remedy is a one-line flip to `true` with no migration. That is the residual risk and it is small and cheaply reversible.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 2. Under-block
|
|
33
|
+
|
|
34
|
+
**What failure modes does this still miss?**
|
|
35
|
+
|
|
36
|
+
- **The real question is untouched.** The documented package manager (CLAUDE.md Quick Reference says `pnpm`) is not the one CI validates (`ci.yml` runs `npm ci` with `cache: npm` across all six jobs), and both `package-lock.json` and `pnpm-lock.yaml` are committed and can drift. This change makes the documented path *work*; it does not make it *tested*. A green pipeline still proves nothing about the pnpm route. <!-- tracked: ACT-1613 -->
|
|
37
|
+
- **No version pin.** `packageManager` is deliberately not added. Adding it would make corepack authoritative and could change behavior for the `npm ci` jobs that CI depends on — a real regression risk for zero benefit to this fix. So a contributor on a pnpm older than 11 sees different (though not failing) behavior. <!-- tracked: ACT-1613 -->
|
|
38
|
+
- **New dependencies won't be caught by the guard test.** A dependency added later with its own install script will re-trigger `ERR_PNPM_IGNORED_BUILDS` for whoever installs with pnpm. The guard asserts the current nine are present; it cannot know about a package that does not exist yet. Detecting that properly needs the pnpm path to run in CI, which is the item above.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 3. Level-of-abstraction fit
|
|
43
|
+
|
|
44
|
+
Correct layer. The allowlist is pnpm's own configuration mechanism at its documented location, not an instar abstraction over it.
|
|
45
|
+
|
|
46
|
+
One placement finding worth recording: the same allowlist expressed as `pnpm.onlyBuiltDependencies` in `package.json` is **silently ignored** by pnpm 11.5.1 — tried it directly, and the install still exited 1 with all 13 scripts still reported ignored. Dead config that reads as authoritative is worse than no config, so the guard test asserts `package.json` does **not** carry that field. This is the sort of thing that would otherwise be "fixed" by a later tidy-up and silently regress.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 4. Signal vs authority compliance
|
|
51
|
+
|
|
52
|
+
Not applicable — no authority and no signal. Per `docs/signal-vs-authority.md` the principle governs decision points that gate agent behavior; this is build configuration with no brittle logic and no blocking power over anything at runtime.
|
|
53
|
+
|
|
54
|
+
The one test added is an assertion over a committed file, which holds no authority over agent behavior either.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 5. Interactions
|
|
59
|
+
|
|
60
|
+
- **npm is unaffected.** `pnpm-workspace.yaml` is a pnpm-specific filename; npm does not read it. CI's `npm ci` path is byte-for-byte unchanged.
|
|
61
|
+
- **No shadowing or double-firing.** Nothing else in the repo reads `allowBuilds`; no other check governs dependency build approval.
|
|
62
|
+
- **Adjacent postinstall preserved.** The repo's own root `postinstall` (`fix-better-sqlite3`) still runs and reported the native binary working. `prepare$ husky` still runs. Neither is affected by the dependency-level allowlist.
|
|
63
|
+
- **Pre-existing untracked placeholder.** A generated `pnpm-workspace.yaml` full of `set this to true or false` placeholders existed untracked in the local checkout. It was moved aside (preserved, not deleted) to reproduce true fresh-checkout state before measuring. Anyone carrying that local file will have it replaced by the committed one on merge, which is the desired outcome — the placeholder version does not resolve the error.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 6. External surfaces
|
|
68
|
+
|
|
69
|
+
- **Visible to contributors and to agents onboarding to this repo**: the documented install command starts working. That is the entire user-facing effect.
|
|
70
|
+
- **No runtime behavior change** for any running agent. Nothing in `src/` is touched; no route, hook, template, scaffold, or gate changes. An already-installed agent is unaffected.
|
|
71
|
+
- **Timing / runtime conditions**: none. The file is read only at install time.
|
|
72
|
+
- **Supply-chain surface**: approving a build script permits that package to execute code at install time. Net effect versus today is a *reduction*: CI installs via npm, which runs all 13 with no gate, so 9-approved-3-declined leaves strictly less executing, with per-entry reasoning recorded inline.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## 7. Multi-machine posture (Cross-Machine Coherence)
|
|
77
|
+
|
|
78
|
+
**Machine-local BY DESIGN**, and correctly so. This is a file in the git repository consumed by a package manager on whichever machine performs an install. It is not agent state, so there is nothing to replicate, proxy on read, or merge; every machine gets the identical committed file by cloning. There is no notice, no durable per-machine state that could strand on topic transfer, and no generated URL.
|
|
79
|
+
|
|
80
|
+
Stated explicitly because the question exists to catch silent single-machine assumptions in *features*: this is not a feature, and the machine-local answer is structural rather than an oversight.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 8. Rollback cost
|
|
85
|
+
|
|
86
|
+
Near-zero and immediate. `git revert` the commit: the file disappears and pnpm returns to its prior failing-but-harmless state. No release required beyond the normal one, no data migration, no agent state repair, nothing to un-migrate. Already-installed `node_modules` trees are unaffected either way.
|
|
87
|
+
|
|
88
|
+
If a specific approval turns out wrong, the narrower rollback is flipping that one entry to `false` (or the reverse) — a one-line change with no other consequence.
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Side-Effects Review — UX-impact gate: recognise `src/scaffold/templates.ts` as a user-facing path
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `ux-gate-scaffold-scope`
|
|
4
|
+
**Date:** `2026-08-01`
|
|
5
|
+
**Author:** `Echo (instar-dev)`
|
|
6
|
+
**Second-pass reviewer:** `REQUIRED — this change modifies a gate` (see §Second pass)
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
`scripts/ux-impact-lint.mjs` decides whether a PR is "user-facing" by matching changed
|
|
11
|
+
files against a hard-coded allowlist (`:25`). That allowlist omitted
|
|
12
|
+
`src/scaffold/templates.ts` — the file the Agent Awareness Standard names as THE
|
|
13
|
+
user-awareness surface ("An agent that doesn't know about a capability effectively
|
|
14
|
+
doesn't have it"). A PR touching only that file therefore hit `allowlisted.length === 0`
|
|
15
|
+
(`:27`) and exited 0, requiring no UX declaration for the change that alters what every
|
|
16
|
+
agent tells its users. Three edits, one defect: the path joins the allowlist (`:25`), it
|
|
17
|
+
also disqualifies the refactor-only exemption (`:30`), and the quote-check failure message
|
|
18
|
+
(`:41`) now names the concept and lists the paths actually searched. Adds
|
|
19
|
+
`tests/unit/ux-impact-lint-scaffold-scope.test.ts`.
|
|
20
|
+
|
|
21
|
+
## Decision-point inventory
|
|
22
|
+
|
|
23
|
+
- `ux-impact-lint.mjs:25` — **modify** — allowlist membership; adds one exact-match path.
|
|
24
|
+
- `ux-impact-lint.mjs:27` — **pass-through** — the in-scope/out-of-scope exit. Unchanged
|
|
25
|
+
logic; a strictly smaller set of PRs now reaches the `exit(0)` skip.
|
|
26
|
+
- `ux-impact-lint.mjs:30` — **modify** — refactor-only exemption eligibility.
|
|
27
|
+
- `ux-impact-lint.mjs:41` — **modify** — failure MESSAGE only. The predicate at `:40` is
|
|
28
|
+
untouched.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 1. Over-block
|
|
33
|
+
|
|
34
|
+
**What legitimate inputs does this change reject that it shouldn't?**
|
|
35
|
+
|
|
36
|
+
A PR that touches `src/scaffold/templates.ts` for a genuinely non-user-visible reason —
|
|
37
|
+
e.g. renaming a local variable inside `generateClaudeMd()`, or reflowing a comment — now
|
|
38
|
+
requires a UX Impact section where previously it required none. That is a real new cost
|
|
39
|
+
imposed on a legitimate input.
|
|
40
|
+
|
|
41
|
+
Two mitigations, both pre-existing: the `refactor-only` exemption still applies when the
|
|
42
|
+
change adds no quoted strings (`:31`), and the author can satisfy the gate by quoting any
|
|
43
|
+
concrete added string. It is a friction increase, not a wall. Concretely: a
|
|
44
|
+
whitespace-only change to that file will now demand a declaration, which is mildly
|
|
45
|
+
annoying and is the price of the file being in scope at all.
|
|
46
|
+
|
|
47
|
+
## 2. Under-block
|
|
48
|
+
|
|
49
|
+
**What failure modes does this still miss?**
|
|
50
|
+
|
|
51
|
+
Substantially more than it catches, and this must not be oversold:
|
|
52
|
+
|
|
53
|
+
- The allowlist remains a **proxy** for "user-facing" and still omits every other path
|
|
54
|
+
that can reach a user — `src/core/PostUpdateMigrator.ts` (which writes agent-visible
|
|
55
|
+
guidance on upgrade), skill `SKILL.md` bodies, `src/data/http-hook-templates.ts`, and
|
|
56
|
+
any future file that renders text a human reads.
|
|
57
|
+
- The gate only fires on **PR paths**, so a user-visible change delivered by a config
|
|
58
|
+
default or a runtime string has no coverage here at all.
|
|
59
|
+
- A PR touching `src/scaffold/templates.ts` **and** an already-allowlisted file was
|
|
60
|
+
already in scope, so for that (common) shape this change alters nothing.
|
|
61
|
+
- The quote check is satisfied by ANY quoted substring present in the allowlisted diff.
|
|
62
|
+
An author can still quote something trivially true and unrelated to the actual user
|
|
63
|
+
impact. This change does not make declarations honest; it makes them required.
|
|
64
|
+
|
|
65
|
+
## 3. Level-of-abstraction fit
|
|
66
|
+
|
|
67
|
+
The fix operates at the same level as the defect: a path missing from a path list is
|
|
68
|
+
repaired by adding the path. No new abstraction, no new config surface, no new file.
|
|
69
|
+
The alternative — deriving "user-facing" from something semantic rather than a path list
|
|
70
|
+
— would be a genuinely better design and is deliberately NOT attempted here; it is a
|
|
71
|
+
redesign, not a fix, and would ship as its own spec.
|
|
72
|
+
|
|
73
|
+
## 4. Signal vs authority compliance
|
|
74
|
+
|
|
75
|
+
The gate is an **authority** (it exits non-zero and blocks a PR), and it stays exactly as
|
|
76
|
+
authoritative as it was — the change alters WHICH files it considers, never WHETHER it
|
|
77
|
+
may block. No new authority is created. The message change at `:41` is pure signal
|
|
78
|
+
improvement and carries no gating weight.
|
|
79
|
+
|
|
80
|
+
Notably this change does not grant the gate any new power over anything outside the repo,
|
|
81
|
+
and it cannot affect a running agent: `scripts/` is CI-only and ships in no runtime path.
|
|
82
|
+
|
|
83
|
+
## 4b. Judgment-point check (Judgment Within Floors standard)
|
|
84
|
+
|
|
85
|
+
No judgment point is added. Every predicate is deterministic string matching; there is no
|
|
86
|
+
LLM call, no heuristic, and no scoring. The one judgment exercised was authorial and is
|
|
87
|
+
recorded here: **exact-match `src/scaffold/templates.ts` rather than
|
|
88
|
+
`startsWith('src/scaffold/')`**, because the standard names one file and widening a gate
|
|
89
|
+
beyond what a standard names is a policy change no one approved.
|
|
90
|
+
|
|
91
|
+
## 5. Interactions
|
|
92
|
+
|
|
93
|
+
- **`.github/workflows/ux-impact-pr-gate.yml`** — unchanged. Its `paths:` trigger already
|
|
94
|
+
includes `scripts/ux-impact-lint.mjs`, so the workflow's own trigger set needs no edit.
|
|
95
|
+
- **Agent Awareness Standard (CLAUDE.md)** — this change makes the gate agree with the
|
|
96
|
+
standard rather than contradict it. Nothing else consumes the allowlist.
|
|
97
|
+
- **`report.allowlistedPaths`** (`:26`) — the emitted JSON report may now contain one more
|
|
98
|
+
path. Any downstream consumer reading that array sees a longer list; no consumer in this
|
|
99
|
+
repo branches on its contents.
|
|
100
|
+
- **No interaction with the release pipeline, migrations, or PostUpdateMigrator.**
|
|
101
|
+
|
|
102
|
+
## 6. External surfaces
|
|
103
|
+
|
|
104
|
+
None. `scripts/ux-impact-lint.mjs` runs only in CI, is not bundled into `dist/`, is not
|
|
105
|
+
served by any route, and is never executed by a deployed agent. No API, no dashboard, no
|
|
106
|
+
Telegram surface, no config key. The only humans who observe a behaviour change are
|
|
107
|
+
contributors opening PRs against this repo.
|
|
108
|
+
|
|
109
|
+
## 6b. Operator-surface quality (Operator-Surface Quality standard)
|
|
110
|
+
|
|
111
|
+
The operator-visible surface is the CI failure message, and this change exists partly to
|
|
112
|
+
improve it. Before: `UX Impact must quote a concrete string from the diff` — which is
|
|
113
|
+
misleading, because the script searches only the allowlisted subset of the diff, so an
|
|
114
|
+
author quoting a genuine added line from an unlisted file is told something false about
|
|
115
|
+
why they failed. After: the message names the concept ("the USER-FACING paths this PR
|
|
116
|
+
touches"), enumerates those paths, and states plainly that quotes from other changed files
|
|
117
|
+
are not checked. An author can now self-diagnose in one read.
|
|
118
|
+
|
|
119
|
+
## 7. Multi-machine posture (Cross-Machine Coherence)
|
|
120
|
+
|
|
121
|
+
Not applicable. CI-only script with no state, no lease interaction, no replication, and no
|
|
122
|
+
per-machine behaviour. It executes once per workflow run on a GitHub runner.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Risks accepted
|
|
127
|
+
|
|
128
|
+
1. **Friction on incidental edits** to `src/scaffold/templates.ts` (§1). Accepted: the
|
|
129
|
+
file's whole purpose is agent-visible text, so incidental edits are rare and a
|
|
130
|
+
declaration is cheap.
|
|
131
|
+
2. **The proxy remains a proxy** (§2). Accepted for this change and explicitly NOT claimed
|
|
132
|
+
as fixed. The allowlist still has no owner and no review trigger; that is a real
|
|
133
|
+
residual and is stated in the ELI16 rather than hidden.
|
|
134
|
+
|
|
135
|
+
## Evidence
|
|
136
|
+
|
|
137
|
+
Verified against real commit `e29259c49` (touches `src/scaffold/templates.ts`, zero
|
|
138
|
+
allowlisted paths — both confirmed by control counts):
|
|
139
|
+
|
|
140
|
+
| lint | result |
|
|
141
|
+
|---|---|
|
|
142
|
+
| shipping (`origin/main`) | `UX lint: out of scope`, **exit 0** — gate skipped |
|
|
143
|
+
| patched | engages, **exit 1** on `UX-Impact: none` |
|
|
144
|
+
| patched, replaying PR #1813 | **exit 0** — no regression on a real passing PR |
|
|
145
|
+
|
|
146
|
+
Tests trip-tested in both directions: **3/3 pass** with the fix; **2 fail** without it;
|
|
147
|
+
the internal-path control passes either way (as a control must).
|
|
148
|
+
|
|
149
|
+
## Second pass
|
|
150
|
+
|
|
151
|
+
This change modifies a **gate**, which mandates second-pass review. Requested from Codey
|
|
152
|
+
on the PR. Not self-certified: the reviewer's response will be appended here.
|