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.
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +34 -0
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/CartographerNavigator.d.ts +89 -0
- package/dist/core/CartographerNavigator.d.ts.map +1 -0
- package/dist/core/CartographerNavigator.js +354 -0
- package/dist/core/CartographerNavigator.js.map +1 -0
- package/dist/core/CartographerTree.d.ts +7 -0
- package/dist/core/CartographerTree.d.ts.map +1 -1
- package/dist/core/CartographerTree.js +9 -0
- package/dist/core/CartographerTree.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +9 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/StageTransitionValidator.d.ts +15 -0
- package/dist/core/StageTransitionValidator.d.ts.map +1 -1
- package/dist/core/StageTransitionValidator.js +23 -2
- package/dist/core/StageTransitionValidator.js.map +1 -1
- package/dist/core/types.d.ts +10 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/server/CapabilityIndex.d.ts.map +1 -1
- package/dist/server/CapabilityIndex.js +1 -0
- package/dist/server/CapabilityIndex.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +55 -0
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/scripts/instar-dev-precommit.js +69 -5
- package/scripts/lib/convergence-recognition.mjs +126 -0
- package/src/data/builtin-manifest.json +63 -63
- package/upgrades/1.3.477.md +63 -0
- package/upgrades/1.3.478.md +44 -0
- package/upgrades/side-effects/cartographer-subtree-nav.md +81 -0
- package/upgrades/side-effects/converging-audit-default.md +82 -0
package/package.json
CHANGED
|
@@ -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
|
-
|
|
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 (!
|
|
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
|
|
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
|
+
}
|