instar 1.3.947 → 1.3.948

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.3.947",
3
+ "version": "1.3.948",
4
4
  "description": "Coherence infrastructure for self-evolving AI agents — on the Claude Code or Codex subscription you already have.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -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
- const siblingPath = path.join(specDir, `${specBase}.eli16.md`);
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
- if (allowlisted.length === 0) { console.log('UX lint: out of scope'); process.exit(0); }
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-25T00:39:47.923Z",
5
- "instarVersion": "1.3.947",
4
+ "generatedAt": "2026-07-25T01:11:00.882Z",
5
+ "instarVersion": "1.3.948",
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,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.