instar 1.3.947 → 1.3.949
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/messaging/detectors/assertUserVisible.d.ts +3 -0
- package/dist/messaging/detectors/assertUserVisible.d.ts.map +1 -1
- package/dist/messaging/detectors/assertUserVisible.js +17 -1
- package/dist/messaging/detectors/assertUserVisible.js.map +1 -1
- package/dist/messaging/detectors/clock.d.ts +14 -0
- package/dist/messaging/detectors/clock.d.ts.map +1 -0
- package/dist/messaging/detectors/clock.js +14 -0
- package/dist/messaging/detectors/clock.js.map +1 -0
- package/package.json +1 -1
- package/scripts/eli16-overview-check.mjs +5 -1
- package/scripts/ux-impact-lint.mjs +20 -2
- package/src/data/builtin-manifest.json +2 -2
- package/upgrades/1.3.948.md +18 -0
- package/upgrades/1.3.949.md +18 -0
- package/upgrades/side-effects/ux-first-enforcement-inc2.md +7 -0
- package/upgrades/side-effects/ux-first-enforcement-inc3.md +5 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/** Deterministic UX assertions for Tier-3 user-surface tests. */
|
|
2
|
+
import type { Clock } from './clock.js';
|
|
2
3
|
/** Reject internal identifiers, paths, config keys, and known implementation jargon. */
|
|
3
4
|
export declare function assertPlainEnglish(text: unknown): asserts text is string;
|
|
4
5
|
/** Every failure must explain what happened and give the user a next action. */
|
|
@@ -8,4 +9,6 @@ export declare function assertHonestFailure(state: unknown): asserts state is {
|
|
|
8
9
|
};
|
|
9
10
|
/** A queue must not contain an expired/stale entry that can be redelivered. */
|
|
10
11
|
export declare function assertNoZombie(queue: unknown): asserts queue is Array<Record<string, unknown>>;
|
|
12
|
+
/** Assert that a visible event landed within its declared bound. */
|
|
13
|
+
export declare function assertTimely(events: unknown, boundMs: number, clock: Clock): void;
|
|
11
14
|
//# sourceMappingURL=assertUserVisible.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assertUserVisible.d.ts","sourceRoot":"","sources":["../../../src/messaging/detectors/assertUserVisible.ts"],"names":[],"mappings":"AAAA,iEAAiE;
|
|
1
|
+
{"version":3,"file":"assertUserVisible.d.ts","sourceRoot":"","sources":["../../../src/messaging/detectors/assertUserVisible.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAWxC,wFAAwF;AACxF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,IAAI,MAAM,CAKxE;AAED,gFAAgF;AAChF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,CAO7G;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAa9F;AAED,oEAAoE;AACpE,wBAAgB,YAAY,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,IAAI,CAWjF"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/** Deterministic UX assertions for Tier-3 user-surface tests. */
|
|
2
1
|
const INTERNAL_ID = /\b(?:CMT|ACT|PR)[-_#]?\d+\b/i;
|
|
3
2
|
const FILE_PATH = /(?:^|\s)(?:\/|\.\.\/|src\/|tests\/)[^\s]+/;
|
|
4
3
|
const CONFIG_KEY = /\b[a-z][a-z0-9]*(?:\.[a-z][a-z0-9]*){2,}\b/;
|
|
@@ -44,4 +43,21 @@ export function assertNoZombie(queue) {
|
|
|
44
43
|
}
|
|
45
44
|
}
|
|
46
45
|
}
|
|
46
|
+
/** Assert that a visible event landed within its declared bound. */
|
|
47
|
+
export function assertTimely(events, boundMs, clock) {
|
|
48
|
+
if (!Array.isArray(events) || events.length === 0)
|
|
49
|
+
throw new Error('timely response event is missing');
|
|
50
|
+
if (!Number.isFinite(boundMs) || boundMs < 0)
|
|
51
|
+
throw new Error('timely bound must be non-negative');
|
|
52
|
+
if (!clock || typeof clock.now !== 'function')
|
|
53
|
+
throw new Error('timely assertion requires an injected clock');
|
|
54
|
+
const now = clock.now();
|
|
55
|
+
const visible = events.find((event) => event && typeof event === 'object' && event.userVisible !== false);
|
|
56
|
+
if (!visible || typeof visible.startedAt !== 'number' || typeof visible.at !== 'number') {
|
|
57
|
+
throw new Error('timely response event is malformed');
|
|
58
|
+
}
|
|
59
|
+
const event = visible;
|
|
60
|
+
if (event.at > now || event.at - event.startedAt > boundMs)
|
|
61
|
+
throw new Error('user-visible response exceeded its time bound');
|
|
62
|
+
}
|
|
47
63
|
//# sourceMappingURL=assertUserVisible.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assertUserVisible.js","sourceRoot":"","sources":["../../../src/messaging/detectors/assertUserVisible.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"assertUserVisible.js","sourceRoot":"","sources":["../../../src/messaging/detectors/assertUserVisible.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Small clock seam for deterministic user-visible timing assertions. */
|
|
2
|
+
export interface Clock {
|
|
3
|
+
now(): number;
|
|
4
|
+
}
|
|
5
|
+
export declare class RealClock implements Clock {
|
|
6
|
+
now(): number;
|
|
7
|
+
}
|
|
8
|
+
export declare class TestClock implements Clock {
|
|
9
|
+
private current;
|
|
10
|
+
constructor(startAt?: number);
|
|
11
|
+
now(): number;
|
|
12
|
+
advance(ms: number): void;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=clock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clock.d.ts","sourceRoot":"","sources":["../../../src/messaging/detectors/clock.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,MAAM,WAAW,KAAK;IAAG,GAAG,IAAI,MAAM,CAAC;CAAE;AAEzC,qBAAa,SAAU,YAAW,KAAK;IACrC,GAAG,IAAI,MAAM;CACd;AAED,qBAAa,SAAU,YAAW,KAAK;IACrC,OAAO,CAAC,OAAO,CAAS;gBACZ,OAAO,SAAI;IACvB,GAAG,IAAI,MAAM;IACb,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;CAI1B"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export class RealClock {
|
|
2
|
+
now() { return Date.now(); }
|
|
3
|
+
}
|
|
4
|
+
export class TestClock {
|
|
5
|
+
current;
|
|
6
|
+
constructor(startAt = 0) { this.current = startAt; }
|
|
7
|
+
now() { return this.current; }
|
|
8
|
+
advance(ms) {
|
|
9
|
+
if (!Number.isFinite(ms) || ms < 0)
|
|
10
|
+
throw new Error('clock advance must be non-negative');
|
|
11
|
+
this.current += ms;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=clock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clock.js","sourceRoot":"","sources":["../../../src/messaging/detectors/clock.ts"],"names":[],"mappings":"AAGA,MAAM,OAAO,SAAS;IACpB,GAAG,KAAa,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;CACrC;AAED,MAAM,OAAO,SAAS;IACZ,OAAO,CAAS;IACxB,YAAY,OAAO,GAAG,CAAC,IAAI,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC;IACpD,GAAG,KAAa,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IACtC,OAAO,CAAC,EAAU;QAChB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;QAC1F,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC;IACrB,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -29,7 +29,11 @@ export const MIN_ELI16_CHARS = 800;
|
|
|
29
29
|
export function resolveEli16Path(specPath, specFm) {
|
|
30
30
|
const specDir = path.dirname(specPath);
|
|
31
31
|
const specBase = path.basename(specPath, '.md');
|
|
32
|
-
|
|
32
|
+
// Specs already named *.eli16.md use a readable overview sibling; never
|
|
33
|
+
// manufacture the historical *.eli16.eli16.md suffix.
|
|
34
|
+
const siblingPath = specBase.endsWith('.eli16')
|
|
35
|
+
? path.join(specDir, `${specBase.slice(0, -'.eli16'.length)}.overview.md`)
|
|
36
|
+
: path.join(specDir, `${specBase}.eli16.md`);
|
|
33
37
|
const fmMatch = specFm.match(/^\s*eli16-overview\s*:\s*["']?([^"'\n]+)/m);
|
|
34
38
|
if (fmMatch) {
|
|
35
39
|
const declared = fmMatch[1].trim().replace(/["']/g, '');
|
|
@@ -2,31 +2,49 @@
|
|
|
2
2
|
// safe-git-allow: read-only base-ref diff inspection for pull-request lint
|
|
3
3
|
/** L1 UX-impact declaration lint. Exit 0=pass/out-of-scope, 1=violation, 2=internal error. */
|
|
4
4
|
import { execFileSync } from 'node:child_process';
|
|
5
|
+
import { writeFileSync } from 'node:fs';
|
|
5
6
|
|
|
6
7
|
function arg(name) { const i = process.argv.indexOf(name); return i >= 0 ? process.argv[i + 1] : ''; }
|
|
7
8
|
const base = arg('--base');
|
|
8
9
|
const head = arg('--head') || 'HEAD';
|
|
9
10
|
const body = arg('--body') || '';
|
|
11
|
+
const scope = (arg('--scope') || '').split(',').map((v) => v.trim().toLowerCase()).filter(Boolean);
|
|
12
|
+
const pusher = (arg('--pusher') || '').trim().toLowerCase();
|
|
13
|
+
const reportPath = arg('--report');
|
|
10
14
|
if (process.env.INSTAR_UX_LINT === 'off') { console.log('UX lint disabled by literal kill switch'); process.exit(0); }
|
|
11
15
|
if (!base) { console.error('::error::UX lint requires a base ref'); process.exit(2); }
|
|
12
16
|
try {
|
|
13
17
|
const names = execFileSync('git', ['diff', '--name-only', `${base}...${head}`], { encoding: 'utf8' }).trim().split('\n').filter(Boolean);
|
|
18
|
+
const commits = execFileSync('git', ['log', '--format=%an <%ae>%n%cn <%ce>', `${base}..${head}`], { encoding: 'utf8' }).toLowerCase();
|
|
19
|
+
const authorInScope = scope.length === 0
|
|
20
|
+
|| (Boolean(pusher) && scope.includes(pusher))
|
|
21
|
+
|| scope.some((token) => commits.includes(token));
|
|
22
|
+
const report = { version: 1, base, head, authorInScope, scope, allowlistedPaths: [], exempt: false, internalError: false };
|
|
23
|
+
const writeReport = () => { if (reportPath) writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`); };
|
|
24
|
+
if (!authorInScope) { report.outOfScopeAuthor = true; await writeReport(); console.log('UX lint: out-of-scope author'); process.exit(0); }
|
|
14
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/'));
|
|
15
|
-
|
|
26
|
+
report.allowlistedPaths = allowlisted;
|
|
27
|
+
if (allowlisted.length === 0) { await writeReport(); console.log('UX lint: out of scope'); process.exit(0); }
|
|
28
|
+
const diff = execFileSync('git', ['diff', '--unified=0', `${base}...${head}`, '--', ...allowlisted], { encoding: 'utf8' });
|
|
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')
|
|
31
|
+
&& !/(?:^|\s)[`'\"](?:[^`'\"]+)[`'\"]/.test(added);
|
|
16
32
|
const section = body.match(/^## UX Impact\s*\n([\s\S]*?)(?=^##\s|(?![\s\S]))/im)?.[1]?.trim() || '';
|
|
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); }
|
|
17
34
|
if (!section) { console.error('::error::UX Impact section is required for user-facing paths'); process.exit(1); }
|
|
18
35
|
if (/UX-Impact:\s*none/i.test(section)) { console.error('::error::UX-Impact: none is not allowed for allowlisted paths'); process.exit(1); }
|
|
19
36
|
if (!/who\s+sees|what\s+(?:the\s+)?user|first[- ]contact|user[- ]visible/i.test(section)) {
|
|
20
37
|
console.error('::error::UX Impact must describe audience, visible behavior, and first contact'); process.exit(1);
|
|
21
38
|
}
|
|
22
|
-
const diff = execFileSync('git', ['diff', '--unified=0', `${base}...${head}`, '--', ...allowlisted], { encoding: 'utf8' });
|
|
23
39
|
const quoted = [...section.matchAll(/[`'"“]([^`'"”]+)[`'"”]/g)].map((m) => m[1]);
|
|
24
40
|
if (!quoted.some((q) => q.length > 2 && diff.includes(q))) {
|
|
25
41
|
console.error('::error::UX Impact must quote a concrete string from the diff'); process.exit(1);
|
|
26
42
|
}
|
|
43
|
+
await writeReport();
|
|
27
44
|
console.log(`UX lint PASS: ${allowlisted.length} allowlisted path(s)`);
|
|
28
45
|
process.exit(0);
|
|
29
46
|
} catch (error) {
|
|
47
|
+
if (reportPath) writeFileSync(reportPath, `${JSON.stringify({ version: 1, internalError: true, message: error instanceof Error ? error.message : String(error) })}\n`);
|
|
30
48
|
console.error(`::error::UX lint internal error: ${error instanceof Error ? error.message : String(error)}`);
|
|
31
49
|
process.exit(2);
|
|
32
50
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./builtin-manifest.schema.json",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"generatedAt": "2026-07-
|
|
5
|
-
"instarVersion": "1.3.
|
|
4
|
+
"generatedAt": "2026-07-25T01:35:29.077Z",
|
|
5
|
+
"instarVersion": "1.3.949",
|
|
6
6
|
"entryCount": 202,
|
|
7
7
|
"entries": {
|
|
8
8
|
"hook:session-start": {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
The UX PR gate now scopes enforcement to commit authors/committers and the branch pusher, verifies refactor-only exemptions deterministically, publishes an exemption-rate artifact, and blocks merge safety on internal lint errors. ELI16 companion generation no longer creates `.eli16.eli16.md` duplicates.
|
|
9
|
+
|
|
10
|
+
## What to Tell Your User
|
|
11
|
+
|
|
12
|
+
Pull requests now show whether a UX lint decision was in scope, whether a refactor exemption was proven, and whether the result was safe to auto-merge. Internal uncertainty stops auto-merge instead of being silently accepted.
|
|
13
|
+
|
|
14
|
+
## Summary of New Capabilities
|
|
15
|
+
|
|
16
|
+
- Author-scope union and deterministic refactor-only verification for UX lint.
|
|
17
|
+
- Per-run exemption-rate artifact for the weekly retro.
|
|
18
|
+
- Internal-error merge-safety gate and corrected ELI16 companion naming.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
The UX assertion tier now includes deterministic timing checks using an injected clock. The real messaging E2E covers both a response inside its declared bound and one that exceeds it.
|
|
9
|
+
|
|
10
|
+
## What to Tell Your User
|
|
11
|
+
|
|
12
|
+
Tests now verify not only what a user sees and whether failures are honest, but also whether the visible response arrives within its promised time.
|
|
13
|
+
|
|
14
|
+
## Summary of New Capabilities
|
|
15
|
+
|
|
16
|
+
- Reusable `Clock`, `RealClock`, and manually advanced `TestClock`.
|
|
17
|
+
- `assertTimely(events, boundMs, clock)` with no external dependencies.
|
|
18
|
+
- CI-visible within-bound and exceeded-bound messaging cases.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Side-effects review: UX-first enforcement increment 2
|
|
2
|
+
|
|
3
|
+
- Extends the deterministic PR lint with author/committer plus pusher scope, a verified refactor-only exemption, and a per-run exemption-rate artifact.
|
|
4
|
+
- Internal lint errors remain distinct and now make the merge-safety job fail, so native auto-merge cannot arm on an uncertain verdict.
|
|
5
|
+
- Fixes ELI16 companion resolution so an existing `.eli16.md` spec is never given a double suffix; historical duplicate companions are removed.
|
|
6
|
+
- No runtime user-facing behavior changes; proof is the real GitHub PR workflow and its uploaded artifact.
|
|
7
|
+
- The configured author scope is literal and reviewed alongside the kill switch; a pusher outside it is out of scope.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Side-effects review: UX-first enforcement increment 3
|
|
2
|
+
|
|
3
|
+
- Adds a pure injected clock seam and the fourth deterministic UX assertion, `assertTimely`.
|
|
4
|
+
- Exercises both within-bound and exceeded-bound responses in the real messaging E2E; CI remains the proof surface.
|
|
5
|
+
- No network, subprocess, or LLM dependency is introduced.
|