instar 1.3.476 → 1.3.478

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 (35) hide show
  1. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  2. package/dist/config/ConfigDefaults.js +34 -0
  3. package/dist/config/ConfigDefaults.js.map +1 -1
  4. package/dist/core/CartographerNavigator.d.ts +89 -0
  5. package/dist/core/CartographerNavigator.d.ts.map +1 -0
  6. package/dist/core/CartographerNavigator.js +354 -0
  7. package/dist/core/CartographerNavigator.js.map +1 -0
  8. package/dist/core/CartographerTree.d.ts +7 -0
  9. package/dist/core/CartographerTree.d.ts.map +1 -1
  10. package/dist/core/CartographerTree.js +9 -0
  11. package/dist/core/CartographerTree.js.map +1 -1
  12. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  13. package/dist/core/PostUpdateMigrator.js +9 -0
  14. package/dist/core/PostUpdateMigrator.js.map +1 -1
  15. package/dist/core/StageTransitionValidator.d.ts +15 -0
  16. package/dist/core/StageTransitionValidator.d.ts.map +1 -1
  17. package/dist/core/StageTransitionValidator.js +23 -2
  18. package/dist/core/StageTransitionValidator.js.map +1 -1
  19. package/dist/core/types.d.ts +10 -0
  20. package/dist/core/types.d.ts.map +1 -1
  21. package/dist/core/types.js.map +1 -1
  22. package/dist/server/CapabilityIndex.d.ts.map +1 -1
  23. package/dist/server/CapabilityIndex.js +1 -0
  24. package/dist/server/CapabilityIndex.js.map +1 -1
  25. package/dist/server/routes.d.ts.map +1 -1
  26. package/dist/server/routes.js +55 -0
  27. package/dist/server/routes.js.map +1 -1
  28. package/package.json +1 -1
  29. package/scripts/instar-dev-precommit.js +69 -5
  30. package/scripts/lib/convergence-recognition.mjs +126 -0
  31. package/src/data/builtin-manifest.json +63 -63
  32. package/upgrades/1.3.477.md +63 -0
  33. package/upgrades/1.3.478.md +44 -0
  34. package/upgrades/side-effects/cartographer-subtree-nav.md +81 -0
  35. package/upgrades/side-effects/converging-audit-default.md +82 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.3.476",
3
+ "version": "1.3.478",
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",
@@ -31,6 +31,15 @@ import { fileURLToPath } from 'node:url';
31
31
  import { checkEli16Overview, MIN_ELI16_CHARS } from './eli16-overview-check.mjs';
32
32
  import { verifyProposalDerivedRunbooks } from '../skills/instar-dev/scripts/verify-proposal-derived-runbook.mjs';
33
33
  import { classifyTier, decideRequirementSet } from './lib/classify-tier.mjs';
34
+ import { recognizeConvergence } from './lib/convergence-recognition.mjs';
35
+
36
+ // Report-Backed Converging Audit (docs/specs/CONVERGING-AUDIT-DEFAULT.md, Part B).
37
+ // The precommit reads NO config file and runs pre-compile, so it cannot import
38
+ // the TS config loader. The flag specReview.requireConvergenceReport is threaded
39
+ // in as an ENV VAR by the .husky/pre-commit hook, mirroring the existing in-file
40
+ // INSTAR_DEV_ALLOW_ORPHAN_DEFERRALS pattern. When unset, the new report-backing
41
+ // branch never runs → byte-identical to today's precommit behavior.
42
+ const REQUIRE_CONVERGENCE_REPORT = process.env.INSTAR_DEV_REQUIRE_CONVERGENCE_REPORT === '1';
34
43
 
35
44
  const __filename = fileURLToPath(import.meta.url);
36
45
  const __dirname = path.dirname(__filename);
@@ -521,10 +530,34 @@ if (!specFmMatch) {
521
530
  );
522
531
  }
523
532
  const specFm = specFmMatch[1];
524
- const convergenceMatch = specFm.match(/^\s*review-convergence\s*:\s*["']?([^"'\n]+)/m);
525
- const approvedMatch = specFm.match(/^\s*approved\s*:\s*(true|"true"|'true')/m);
526
533
 
527
- if (!convergenceMatch) {
534
+ // Convergence / approval / report recognition is factored into a pure,
535
+ // dependency-free module (scripts/lib/convergence-recognition.mjs) that a unit
536
+ // test cross-checks against the TS validator's `isConvergenceTagPresent` —
537
+ // keeping the two gates in agreement (CONVERGING-AUDIT-DEFAULT.md, Part C). The
538
+ // report-existence input depends on the spec's slug + the env flag, computed
539
+ // just below; the recognizer does no I/O of its own.
540
+
541
+ // Slug → report path, matching StageTransitionValidator's derivation exactly.
542
+ const specSlugMatch = specFm.match(/^\s*slug\s*:\s*["']?([a-z0-9][a-z0-9-]{0,63})["']?\s*$/m);
543
+ const specSlug = specSlugMatch ? specSlugMatch[1] : '';
544
+ const convergenceReportRel = specSlug
545
+ ? path.join('docs/specs/reports', `${specSlug}-convergence.md`)
546
+ : '';
547
+ // Only probe the filesystem when the report requirement is actually on. With
548
+ // the flag unset, reportExists stays false but is never consulted (the
549
+ // recognizer's reportBacked is vacuously true), so this branch is byte-inert.
550
+ const convergenceReportExists =
551
+ REQUIRE_CONVERGENCE_REPORT && convergenceReportRel
552
+ ? fs.existsSync(path.resolve(ROOT, convergenceReportRel))
553
+ : false;
554
+
555
+ const recognition = recognizeConvergence(specFm, {
556
+ requireReport: REQUIRE_CONVERGENCE_REPORT,
557
+ reportExists: convergenceReportExists,
558
+ });
559
+
560
+ if (!recognition.converged) {
528
561
  blockCommit(
529
562
  inScopeFiles,
530
563
  [
@@ -534,7 +567,7 @@ if (!convergenceMatch) {
534
567
  );
535
568
  }
536
569
 
537
- if (!approvedMatch) {
570
+ if (!recognition.approved) {
538
571
  blockCommit(
539
572
  inScopeFiles,
540
573
  [
@@ -545,6 +578,35 @@ if (!approvedMatch) {
545
578
  );
546
579
  }
547
580
 
581
+ // ── Report-backing (Part B — dark, env-gated) ──
582
+ // When INSTAR_DEV_REQUIRE_CONVERGENCE_REPORT=1, a converged + approved spec must
583
+ // ALSO have its converging-audit report on disk (proving the audit RAN, not just
584
+ // that a tag was added). This brings the precommit UP to the formal validator's
585
+ // strictness (which requires the report unconditionally). With the env unset,
586
+ // recognition.reportBacked is always true and this branch never blocks.
587
+ if (!recognition.reportBacked) {
588
+ blockCommit(
589
+ inScopeFiles,
590
+ [
591
+ `Spec ${spec} is tagged review-convergence + approved, but its converging-audit`,
592
+ `report is missing: ${convergenceReportRel || '(spec has no valid slug to locate a report)'}`,
593
+ '',
594
+ 'INSTAR_DEV_REQUIRE_CONVERGENCE_REPORT is on (specReview.requireConvergenceReport:',
595
+ 'true) — a convergence tag without its report can fake convergence. The report is',
596
+ 'the audit\'s proof-of-work; run /spec-converge to produce it (Phase 5 writes',
597
+ `docs/specs/reports/<slug>-convergence.md), then commit. To turn this requirement`,
598
+ 'off, set specReview.requireConvergenceReport: false in your instar config.',
599
+ ].join('\n'),
600
+ );
601
+ }
602
+
603
+ // ── Part D: surface cross-model-review depth (observe-only, never blocks) ──
604
+ // The converging audit records how much external (cross-model) review actually
605
+ // ran. Surfacing it here makes the audit's depth visible to the operator/agent
606
+ // reading the gate output, without ever gating on it (Signal vs. Authority).
607
+ const crossModelMatch = specFm.match(/^\s*cross-model-review\s*:\s*["']?([^"'\n]+)/m);
608
+ const crossModelReview = crossModelMatch ? crossModelMatch[1].trim() : 'not-recorded';
609
+
548
610
  // ─── Step 7: ELI16 overview verification ─────────────────────────────────
549
611
  // Every approved spec must ship with a plain-English ELI16 overview. The
550
612
  // overview is the entry point for any reader who has to make a real decision
@@ -824,7 +886,9 @@ if (!promotionGateResult.ok) {
824
886
  assertFrameworkGenerality(inScopeFiles, validTrace.trace);
825
887
 
826
888
  console.error(
827
- `[instar-dev-precommit] OK — trace ${path.basename(validTrace.entry.file)} covers ${inScopeFiles.length} in-scope file(s), artifact ${validTrace.trace.artifactPath} verified, spec ${spec} is converged + approved, ELI16 overview ${eli16Rel} present (${eli16Result.charCount} chars), promotion-gate: ${promotionGateResult.reason}.`,
889
+ `[instar-dev-precommit] OK — trace ${path.basename(validTrace.entry.file)} covers ${inScopeFiles.length} in-scope file(s), artifact ${validTrace.trace.artifactPath} verified, spec ${spec} is converged + approved` +
890
+ `${REQUIRE_CONVERGENCE_REPORT ? ` + report-backed (${convergenceReportRel})` : ''}` +
891
+ ` [cross-model: ${crossModelReview}], ELI16 overview ${eli16Rel} present (${eli16Result.charCount} chars), promotion-gate: ${promotionGateResult.reason}.`,
828
892
  );
829
893
  process.exit(0);
830
894
 
@@ -0,0 +1,126 @@
1
+ /**
2
+ * convergence-recognition.mjs — the PRECOMMIT gate's pure recognizer for a
3
+ * spec's convergence / approval / report-backing state.
4
+ *
5
+ * Spec: docs/specs/CONVERGING-AUDIT-DEFAULT.md (Part C — gate consistency).
6
+ *
7
+ * WHY a separate pure module: the precommit (scripts/instar-dev-precommit.js)
8
+ * runs PRE-COMPILE and CANNOT import the TS StageTransitionValidator across the
9
+ * compile boundary, so the two gates cannot share a single source of truth in
10
+ * code. Instead, the precommit's recognition logic is factored HERE, into a
11
+ * tiny dependency-free function, and a unit test
12
+ * (tests/unit/convergence-gate-consistency.test.ts) feeds the SAME fixture
13
+ * table to both this recognizer and the validator's `isConvergenceTagPresent`
14
+ * + report logic, asserting they agree. A drift between the two gates fails CI.
15
+ *
16
+ * This module does NO I/O: callers pass the frontmatter text and the
17
+ * report-existence boolean. It is the precommit-side mirror of the validator's
18
+ * pure predicate — same convergence-tag rule (non-empty string OR boolean
19
+ * true), same report-backing rule (report must exist when required).
20
+ */
21
+
22
+ /**
23
+ * Recognize the convergence tag in a `review-convergence` frontmatter VALUE.
24
+ *
25
+ * Mirror of StageTransitionValidator.isConvergenceTagPresent: a non-empty
26
+ * string (the ISO timestamp the converging-audit tooling writes) OR boolean
27
+ * `true` (the legacy/hand-added form) counts as present. Empty string, false,
28
+ * null/undefined, and any other type do NOT.
29
+ *
30
+ * @param {unknown} value
31
+ * @returns {boolean}
32
+ */
33
+ export function isConvergenceTagPresent(value) {
34
+ if (value === true) return true;
35
+ if (typeof value === 'string' && value.trim().length > 0) return true;
36
+ return false;
37
+ }
38
+
39
+ /**
40
+ * Parse the raw `review-convergence` value out of a YAML frontmatter BLOCK
41
+ * (the text between the `---` fences, NOT including the fences). Mirrors the
42
+ * precommit's existing lenient regex exactly so recognition stays identical.
43
+ *
44
+ * Returns the matched string value (quotes stripped, trimmed) when the line is
45
+ * present, or `undefined` when the key is absent. Note: this returns a STRING
46
+ * for both `review-convergence: true` and `review-convergence: "<ts>"` — the
47
+ * precommit's regex captures the literal token; `isConvergenceTagPresent`
48
+ * treats any non-empty captured token as present, which matches the validator's
49
+ * acceptance of both the boolean-true and timestamp-string forms.
50
+ *
51
+ * @param {string} frontmatterText
52
+ * @returns {string | undefined}
53
+ */
54
+ export function parseConvergenceValue(frontmatterText) {
55
+ const m = String(frontmatterText).match(
56
+ /^\s*review-convergence\s*:\s*["']?([^"'\n]+)/m,
57
+ );
58
+ if (!m) return undefined;
59
+ return m[1].trim();
60
+ }
61
+
62
+ /**
63
+ * Parse whether the spec carries an `approved: true` tag (precommit's regex).
64
+ *
65
+ * @param {string} frontmatterText
66
+ * @returns {boolean}
67
+ */
68
+ export function isApprovedTagPresent(frontmatterText) {
69
+ return /^\s*approved\s*:\s*(true|"true"|'true')/m.test(String(frontmatterText));
70
+ }
71
+
72
+ /**
73
+ * The precommit's verdict for "is this spec converged + approved (+ report-backed
74
+ * when required)?" — the same question the formal validator answers, expressed
75
+ * over frontmatter text + a report-existence boolean.
76
+ *
77
+ * @param {string} frontmatterText — the YAML frontmatter block (between the `---` fences).
78
+ * @param {{ requireReport?: boolean, reportExists?: boolean }} [opts]
79
+ * requireReport — true when INSTAR_DEV_REQUIRE_CONVERGENCE_REPORT=1.
80
+ * reportExists — whether docs/specs/reports/<slug>-convergence.md exists.
81
+ * @returns {{ converged: boolean, approved: boolean, reportBacked: boolean,
82
+ * accepted: boolean, reason: string }}
83
+ * - converged: the convergence tag is present.
84
+ * - approved: the approved tag is present.
85
+ * - reportBacked: the report requirement is satisfied (vacuously true when
86
+ * requireReport is false).
87
+ * - accepted: converged AND approved AND reportBacked — the precommit's
88
+ * Step-6 verdict for a tier-2/3 spec.
89
+ * - reason: a short machine-stable reason for the verdict.
90
+ */
91
+ export function recognizeConvergence(frontmatterText, opts = {}) {
92
+ const requireReport = opts.requireReport === true;
93
+ const reportExists = opts.reportExists === true;
94
+
95
+ const convergenceValue = parseConvergenceValue(frontmatterText);
96
+ const converged = isConvergenceTagPresent(convergenceValue);
97
+ const approved = isApprovedTagPresent(frontmatterText);
98
+ const reportBacked = !requireReport || reportExists;
99
+
100
+ let reason;
101
+ if (!converged) reason = 'convergence-tag-missing';
102
+ else if (!approved) reason = 'approved-tag-missing';
103
+ else if (!reportBacked) reason = 'convergence-report-missing';
104
+ else reason = 'accepted';
105
+
106
+ const accepted = converged && approved && reportBacked;
107
+ return { converged, approved, reportBacked, accepted, reason };
108
+ }
109
+
110
+ /**
111
+ * Convenience: the precommit-side answer to "is this spec CONVERGED?" in the
112
+ * exact sense the consistency test compares against the validator's converged
113
+ * verdict (convergence tag present AND report-backed when required). Approval
114
+ * is a separate downstream gate in BOTH paths, so it is NOT folded in here.
115
+ *
116
+ * @param {string} frontmatterText
117
+ * @param {{ requireReport?: boolean, reportExists?: boolean }} [opts]
118
+ * @returns {boolean}
119
+ */
120
+ export function isSpecConverged(frontmatterText, opts = {}) {
121
+ const requireReport = opts.requireReport === true;
122
+ const reportExists = opts.reportExists === true;
123
+ const converged = isConvergenceTagPresent(parseConvergenceValue(frontmatterText));
124
+ const reportBacked = !requireReport || reportExists;
125
+ return converged && reportBacked;
126
+ }