instar 1.3.471 → 1.3.473

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.
Files changed (37) hide show
  1. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  2. package/dist/config/ConfigDefaults.js +20 -0
  3. package/dist/config/ConfigDefaults.js.map +1 -1
  4. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  5. package/dist/core/PostUpdateMigrator.js +9 -0
  6. package/dist/core/PostUpdateMigrator.js.map +1 -1
  7. package/dist/core/SessionManager.d.ts +7 -2
  8. package/dist/core/SessionManager.d.ts.map +1 -1
  9. package/dist/core/SessionManager.js +33 -10
  10. package/dist/core/SessionManager.js.map +1 -1
  11. package/dist/core/StandardEnforcementExtractor.d.ts +44 -0
  12. package/dist/core/StandardEnforcementExtractor.d.ts.map +1 -0
  13. package/dist/core/StandardEnforcementExtractor.js +98 -0
  14. package/dist/core/StandardEnforcementExtractor.js.map +1 -0
  15. package/dist/core/StandardsEnforcementAuditor.d.ts +78 -0
  16. package/dist/core/StandardsEnforcementAuditor.d.ts.map +1 -0
  17. package/dist/core/StandardsEnforcementAuditor.js +295 -0
  18. package/dist/core/StandardsEnforcementAuditor.js.map +1 -0
  19. package/dist/core/StandardsRegistryParser.d.ts +7 -0
  20. package/dist/core/StandardsRegistryParser.d.ts.map +1 -1
  21. package/dist/core/StandardsRegistryParser.js +7 -0
  22. package/dist/core/StandardsRegistryParser.js.map +1 -1
  23. package/dist/core/componentCategories.d.ts.map +1 -1
  24. package/dist/core/componentCategories.js +6 -0
  25. package/dist/core/componentCategories.js.map +1 -1
  26. package/dist/server/CapabilityIndex.d.ts.map +1 -1
  27. package/dist/server/CapabilityIndex.js +3 -1
  28. package/dist/server/CapabilityIndex.js.map +1 -1
  29. package/dist/server/routes.d.ts.map +1 -1
  30. package/dist/server/routes.js +80 -0
  31. package/dist/server/routes.js.map +1 -1
  32. package/package.json +1 -1
  33. package/scripts/standards-coverage.mjs +279 -0
  34. package/src/data/builtin-manifest.json +64 -64
  35. package/upgrades/1.3.473.md +84 -0
  36. package/upgrades/side-effects/cartographer-conformance-audit.md +109 -0
  37. package/upgrades/side-effects/pin-interactive-session-lane.md +47 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.3.471",
3
+ "version": "1.3.473",
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",
@@ -0,0 +1,279 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * standards-coverage.mjs — Tier-3 CI ratchet for the Standards Enforcement-Coverage
4
+ * Audit (cartographer-conformance-audit spec #3, Part E). Parity with
5
+ * scripts/docs-coverage.mjs + scripts/cartographer-freshness.mjs: a hardcoded
6
+ * committed FLOOR on the enforced ratio + a hard ZERO ceiling on dangling refs,
7
+ * a gitignored output file that is NEVER the read baseline, deterministic by
8
+ * construction, fails OPEN on a transient (missing registry ⇒ vacuous pass).
9
+ *
10
+ * What it measures: for each constitutional standard in docs/STANDARDS-REGISTRY.md,
11
+ * whether the structural guard its prose NAMES (a `*.test.ts`/`no-*` ratchet, a
12
+ * `scripts/lint-*`, a gate marker/route, a `docs/specs/*`) actually resolves on
13
+ * disk. It reports:
14
+ * - enforcedRatio = (ratchet + gate + lint) / total — fails the build if it drops
15
+ * below the committed floor (a new standard shipped with NO verifiable guard).
16
+ * - danglingCount = refs a standard names that are NOT on disk — fails the build if
17
+ * ABOVE ZERO (a guard file removed while a standard still cites it: a broken
18
+ * guarantee, the loudest signal).
19
+ *
20
+ * Self-contained (no dist import) so it runs in CI without a build step — it
21
+ * re-implements the same deterministic parse → extract → verify the auditor does.
22
+ *
23
+ * Usage:
24
+ * node scripts/standards-coverage.mjs # report, exit 0
25
+ * node scripts/standards-coverage.mjs --check # exit 1 on regression
26
+ * node scripts/standards-coverage.mjs --json # JSON to stdout
27
+ *
28
+ * Floors (env override):
29
+ * STANDARDS_ENFORCED_RATIO_FLOOR — min enforced ratio 0..1 (default 0 — starts
30
+ * loose, ratcheted up as gaps close)
31
+ * STANDARDS_DANGLING_CEILING — max dangling refs (default 0 — zero tolerance)
32
+ */
33
+ import fs from 'node:fs';
34
+ import path from 'node:path';
35
+ import crypto from 'node:crypto';
36
+ import { fileURLToPath } from 'node:url';
37
+
38
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
39
+ const args = new Set(process.argv.slice(2));
40
+ const CHECK = args.has('--check');
41
+ const JSON_ONLY = args.has('--json');
42
+ const QUIET = args.has('--quiet');
43
+
44
+ function resolveRoot() {
45
+ if (process.env.STANDARDS_COVERAGE_ROOT) return process.env.STANDARDS_COVERAGE_ROOT;
46
+ const cwd = process.cwd();
47
+ if (fs.existsSync(path.join(cwd, 'src'))) return cwd;
48
+ return path.resolve(__dirname, '..');
49
+ }
50
+ const ROOT = resolveRoot();
51
+ const REGISTRY_PATH = path.join(ROOT, 'docs', 'STANDARDS-REGISTRY.md');
52
+ const OUT_PATH = path.join(ROOT, '.instar', 'standards-coverage.json');
53
+
54
+ // ── Hardcoded committed floors (the read baseline; output file is never it) ──
55
+ const numEnv = (env, def) => {
56
+ const v = process.env[env];
57
+ return v !== undefined && v !== '' && Number.isFinite(Number(v)) ? Number(v) : def;
58
+ };
59
+ const FLOORS = {
60
+ // Starts at 0 ("starts loose", the docs-coverage rationale) — the script's first
61
+ // job is to PREVENT regression (a new unguarded standard / a removed guard file)
62
+ // while the existing gap closes. Ratchet this upward (a visible PR diff) as the
63
+ // documented-only set shrinks.
64
+ enforcedRatio: numEnv('STANDARDS_ENFORCED_RATIO_FLOOR', 0),
65
+ // Zero tolerance: a standard must NEVER cite a guard that doesn't exist.
66
+ danglingCeiling: numEnv('STANDARDS_DANGLING_CEILING', 0),
67
+ };
68
+
69
+ // ── Deterministic parse → extract → verify (mirrors the auditor, self-contained) ──
70
+
71
+ const STANDARDS_FAMILY_RE = /^##\s+(The Root|The Substrate|Building|Shipping|Interaction)\b/;
72
+ const ANY_H2_RE = /^##\s+/;
73
+ const ARTICLE_RE = /^###\s+(.+?)\s*$/;
74
+ function fieldAfter(line, label) {
75
+ const m = line.match(new RegExp(`^\\*\\*${label}\\.\\*\\*\\s*(.*)$`));
76
+ return m ? m[1].trim() : null;
77
+ }
78
+ function parseRegistry(markdown) {
79
+ const lines = markdown.split('\n');
80
+ const articles = [];
81
+ let fam = null, cur = null;
82
+ const flush = () => { if (cur && cur.rule) articles.push(cur); cur = null; };
83
+ for (const line of lines) {
84
+ const fm = line.match(STANDARDS_FAMILY_RE);
85
+ if (fm) { flush(); fam = fm[1]; continue; }
86
+ if (ANY_H2_RE.test(line) && !fm) { flush(); fam = null; continue; }
87
+ if (!fam) continue;
88
+ const am = line.match(ARTICLE_RE);
89
+ if (am) { flush(); cur = { family: fam, name: am[1].trim(), rule: '', inPractice: '', appliedThrough: '' }; continue; }
90
+ if (!cur) continue;
91
+ const r = fieldAfter(line, 'Rule'); if (r !== null) { cur.rule = r; continue; }
92
+ const ip = fieldAfter(line, 'In practice'); if (ip !== null) { cur.inPractice = ip; continue; }
93
+ const at = fieldAfter(line, 'Applied through'); if (at !== null) { cur.appliedThrough = at; continue; }
94
+ }
95
+ flush();
96
+ return articles;
97
+ }
98
+
99
+ const FILE_RE = /`([a-zA-Z0-9_./-]+\.(?:ts|js|mjs|cjs|md|json|sh))`/g;
100
+ const ROUTE_RE = /`(GET|POST|PUT|DELETE|PATCH)\s+(\/[a-zA-Z0-9/_:-]+)`/g;
101
+ const MARKER_RE = /\b([A-Z][A-Z0-9]{2,}_[A-Z0-9_]{2,})\b/g;
102
+ const SYMBOL_RE = /`([A-Z][a-zA-Z0-9]+(?:\.[a-zA-Z][a-zA-Z0-9]*)?)`/g;
103
+ const ENFORCEMENT_PATH_PREFIXES = ['tests/', 'scripts/', 'src/', 'docs/', '.github/', '.instar/', '.husky/'];
104
+ const isEnforcementPath = (p) => ENFORCEMENT_PATH_PREFIXES.some((pre) => p.startsWith(pre));
105
+ const dedupe = (xs) => [...new Set(xs)];
106
+
107
+ function extractRefs(a) {
108
+ const text = `${a.inPractice ?? ''}\n${a.appliedThrough ?? ''}`;
109
+ const files = [];
110
+ for (const m of text.matchAll(FILE_RE)) { if (isEnforcementPath(m[1])) files.push(m[1]); }
111
+ const routes = [];
112
+ for (const m of text.matchAll(ROUTE_RE)) routes.push(`${m[1].toUpperCase()} ${m[2]}`);
113
+ const markers = [];
114
+ for (const m of text.matchAll(MARKER_RE)) markers.push(m[1]);
115
+ for (const m of text.matchAll(SYMBOL_RE)) markers.push(m[1].split('.')[0]);
116
+ return { files: dedupe(files).sort(), routes: dedupe(routes).sort(), markers: dedupe(markers).sort() };
117
+ }
118
+
119
+ const KIND_RANK = { ratchet: 4, gate: 3, lint: 2, 'spec-only': 1 };
120
+ function classifyFileGuard(ref) {
121
+ const base = ref.split('/').pop() ?? ref;
122
+ if (/\.test\.(ts|js|mjs)$/.test(base) || base.startsWith('no-') || /-coverage\.(mjs|js)$/.test(base)) return 'ratchet';
123
+ if (ref.startsWith('scripts/') && base.startsWith('lint-')) return 'lint';
124
+ if (ref.startsWith('.husky/') || /precommit/i.test(base)) return 'gate';
125
+ if (ref.startsWith('scripts/')) return 'lint';
126
+ if (ref.startsWith('docs/')) return 'spec-only';
127
+ if (ref.startsWith('src/')) return 'gate';
128
+ return 'spec-only';
129
+ }
130
+
131
+ function loadRouteTable() {
132
+ const out = new Set();
133
+ const serverDir = path.join(ROOT, 'src', 'server');
134
+ let files;
135
+ try { files = fs.readdirSync(serverDir).filter((f) => f.endsWith('.ts') && !f.endsWith('.test.ts')); } catch { return out; }
136
+ const re = /router\.(get|post|put|delete|patch)\s*\(\s*['"`]([^'"`]+)['"`]/g;
137
+ for (const f of files) {
138
+ let content;
139
+ try { content = fs.readFileSync(path.join(serverDir, f), 'utf-8'); } catch { continue; }
140
+ for (const m of content.matchAll(re)) out.add(`${m[1].toUpperCase()} ${m[2]}`);
141
+ }
142
+ return out;
143
+ }
144
+
145
+ function buildSymbolIndex(wanted) {
146
+ const found = new Set();
147
+ if (wanted.size === 0) return found;
148
+ const srcDir = path.join(ROOT, 'src');
149
+ try { if (!fs.statSync(srcDir).isDirectory()) return found; } catch { return found; }
150
+ const escaped = [...wanted].map((s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
151
+ const re = new RegExp(`\\b(${escaped.join('|')})\\b`, 'g');
152
+ let readBytes = 0;
153
+ const MAX = 64 * 1024 * 1024;
154
+ const walk = (dir) => {
155
+ let entries;
156
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
157
+ for (const e of entries) {
158
+ if (found.size === wanted.size) return;
159
+ const full = path.join(dir, e.name);
160
+ if (e.isDirectory()) {
161
+ if (e.name === 'node_modules' || e.name.startsWith('.')) continue;
162
+ walk(full);
163
+ } else if (/\.(ts|js|mjs|cjs)$/.test(e.name)) {
164
+ if (readBytes > MAX) return;
165
+ let content;
166
+ try { content = fs.readFileSync(full, 'utf-8'); } catch { continue; }
167
+ readBytes += content.length;
168
+ for (const m of content.matchAll(re)) found.add(m[1]);
169
+ }
170
+ }
171
+ };
172
+ walk(srcDir);
173
+ return found;
174
+ }
175
+
176
+ function compute() {
177
+ let markdown = null;
178
+ try { markdown = fs.readFileSync(REGISTRY_PATH, 'utf-8'); } catch { markdown = null; }
179
+ if (markdown === null) {
180
+ // Missing registry (a transient / a partial checkout) → vacuous pass (fail-open).
181
+ return {
182
+ generatedAt: new Date().toISOString(),
183
+ registryFound: false,
184
+ total: 0, byKind: { ratchet: 0, gate: 0, lint: 0, 'spec-only': 0, 'documented-only': 0 },
185
+ enforcedRatio: 1, gaps: [], danglingCount: 0, danglingByStandard: [],
186
+ };
187
+ }
188
+
189
+ const articles = parseRegistry(markdown);
190
+ const routeTable = loadRouteTable();
191
+ const extracted = articles.map((a) => ({ a, refs: extractRefs(a) }));
192
+ const wanted = new Set();
193
+ for (const { refs } of extracted) for (const m of refs.markers) wanted.add(m);
194
+ const symbolIndex = buildSymbolIndex(wanted);
195
+
196
+ const byKind = { ratchet: 0, gate: 0, lint: 0, 'spec-only': 0, 'documented-only': 0 };
197
+ const gaps = [];
198
+ const danglingByStandard = [];
199
+ let danglingCount = 0;
200
+
201
+ for (const { a, refs } of extracted) {
202
+ const guards = [];
203
+ const dangling = [];
204
+ for (const ref of refs.files) {
205
+ const verified = fs.existsSync(path.join(ROOT, ref));
206
+ if (verified) guards.push(classifyFileGuard(ref)); else dangling.push(ref);
207
+ }
208
+ for (const ref of refs.routes) {
209
+ const verified = routeTable.has(ref);
210
+ if (verified) guards.push('gate'); else dangling.push(ref);
211
+ }
212
+ for (const ref of refs.markers) {
213
+ const verified = symbolIndex.has(ref);
214
+ if (verified) guards.push('gate'); else dangling.push(ref);
215
+ }
216
+ let best = null;
217
+ for (const g of guards) { if (best === null || KIND_RANK[g] > KIND_RANK[best]) best = g; }
218
+ const kind = best ?? 'documented-only';
219
+ byKind[kind] += 1;
220
+ if (kind === 'documented-only') gaps.push(a.name);
221
+ if (dangling.length > 0) { danglingByStandard.push({ standard: a.name, refs: dangling.sort() }); danglingCount += dangling.length; }
222
+ }
223
+
224
+ const total = articles.length;
225
+ const enforced = byKind.ratchet + byKind.gate + byKind.lint;
226
+ const enforcedRatio = total === 0 ? 1 : Number((enforced / total).toFixed(4));
227
+ return {
228
+ generatedAt: new Date().toISOString(),
229
+ registryFound: true,
230
+ total, byKind, enforcedRatio, gaps, danglingCount, danglingByStandard,
231
+ };
232
+ }
233
+
234
+ function main() {
235
+ const report = compute();
236
+ report.floors = FLOORS;
237
+ report.inputHash = (() => {
238
+ let reg = '';
239
+ try { reg = fs.readFileSync(REGISTRY_PATH, 'utf-8'); } catch { reg = ''; }
240
+ return crypto.createHash('sha256').update(reg).digest('hex').slice(0, 16);
241
+ })();
242
+
243
+ try {
244
+ fs.mkdirSync(path.dirname(OUT_PATH), { recursive: true });
245
+ fs.writeFileSync(OUT_PATH, JSON.stringify(report, null, 2) + '\n');
246
+ } catch { /* output is advisory; never fail the build on a write error */ }
247
+
248
+ if (JSON_ONLY) {
249
+ process.stdout.write(JSON.stringify(report, null, 2) + '\n');
250
+ } else if (!QUIET) {
251
+ console.error(`[standards-coverage] registry=${report.registryFound} total=${report.total} ` +
252
+ `enforced-ratio=${report.enforcedRatio} (ratchet ${report.byKind.ratchet} / gate ${report.byKind.gate} / ` +
253
+ `lint ${report.byKind.lint} / spec-only ${report.byKind['spec-only']} / gap ${report.byKind['documented-only']}) ` +
254
+ `dangling=${report.danglingCount}`);
255
+ console.error(`[standards-coverage] floors: enforced-ratio>=${FLOORS.enforcedRatio} dangling<=${FLOORS.danglingCeiling}`);
256
+ }
257
+
258
+ if (CHECK) {
259
+ const failures = [];
260
+ if (report.enforcedRatio < FLOORS.enforcedRatio) {
261
+ failures.push(`enforced ratio ${report.enforcedRatio} < floor ${FLOORS.enforcedRatio}`);
262
+ }
263
+ if (report.danglingCount > FLOORS.danglingCeiling) {
264
+ failures.push(`dangling refs ${report.danglingCount} > ceiling ${FLOORS.danglingCeiling}` +
265
+ (report.danglingByStandard.length
266
+ ? ` — ${report.danglingByStandard.map((d) => `${d.standard}: [${d.refs.join(', ')}]`).join('; ')}`
267
+ : ''));
268
+ }
269
+ if (failures.length > 0) {
270
+ process.stderr.write('\n❌ standards-coverage check failed:\n');
271
+ for (const f of failures) process.stderr.write(` - ${f}\n`);
272
+ process.stderr.write('\nFix: build a guard for an unguarded standard (raise the ratio), or repair the dangling reference (the cited guard file was renamed/removed).\n');
273
+ process.exit(1);
274
+ }
275
+ if (!QUIET) console.error('✅ standards-coverage check passed.');
276
+ }
277
+ }
278
+
279
+ main();