gitnexus 1.6.3-rc.14 → 1.6.3-rc.15

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.
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Shadow-mode parity harness — dual-run observability for the RFC #909
3
+ * registry rollout (RFC §6.3; Ring 2 PKG #923).
4
+ *
5
+ * ## What it does
6
+ *
7
+ * - Exposes `record({ language, callsite, legacy, newResult })` for
8
+ * every call site where the caller has BOTH a legacy-DAG resolution
9
+ * and a new `Registry.lookup` resolution.
10
+ * - Computes a `ShadowDiff` per record via shared `diffResolutions`
11
+ * (#918) and accumulates them in a per-language bucket.
12
+ * - At the end of a run, aggregates into a `ShadowParityReport` via
13
+ * shared `aggregateDiffs` (#918) — per-language parity %,
14
+ * evidence-kind breakdown of divergences, grand-total overall row.
15
+ * - Optionally persists the report as JSON under
16
+ * `.gitnexus/shadow-parity/` so the static dashboard at
17
+ * `gitnexus/shadow-parity-dashboard/` can render it offline.
18
+ *
19
+ * ## What it does NOT do
20
+ *
21
+ * - **Invoke either resolution path itself.** The caller must run
22
+ * legacy + `Registry.lookup` and pass results in. The harness is a
23
+ * side-car, not a dispatcher — this keeps call-processor integration
24
+ * surgical when it lands (tracked as a follow-up; the shared model
25
+ * doesn't dual-invoke on its own).
26
+ * - **Flip anything.** `REGISTRY_PRIMARY_<LANG>` lives in
27
+ * `registry-primary-flag.ts` (#924); the harness records the
28
+ * caller-supplied "which side is primary" bit for each record so the
29
+ * dashboard can label rows, but it does not consult the flag itself.
30
+ *
31
+ * ## Activation
32
+ *
33
+ * `GITNEXUS_SHADOW_MODE=1` (or `'true'`, `'yes'`, case-insensitive,
34
+ * trimmed) enables the harness. When disabled, `record()` is a cheap
35
+ * no-op: no accumulation, no allocation beyond the harness object
36
+ * itself. Callers can always construct a harness and hand it through;
37
+ * the "off" overhead is near-zero.
38
+ *
39
+ * ## Persistence shape
40
+ *
41
+ * When `persist()` is called, the harness writes TWO files:
42
+ *
43
+ * - `<outputDir>/<runId>.json` — the timestamped snapshot (immutable)
44
+ * - `<outputDir>/latest.json` — a pointer that the dashboard reads
45
+ *
46
+ * Both files contain the same `PersistedShadowReport` payload:
47
+ *
48
+ * {
49
+ * schemaVersion: 1,
50
+ * runId: "<iso-8601>-<rand>",
51
+ * generatedAt: "<iso-8601>",
52
+ * primaryByLanguage: { [lang]: "legacy" | "registry" },
53
+ * report: <ShadowParityReport>
54
+ * }
55
+ *
56
+ * Schema-version-gated so future format changes don't silently confuse
57
+ * older dashboards. The dashboard renders `report.perLanguage` rows and
58
+ * annotates each with `primaryByLanguage[lang]`.
59
+ */
60
+ import { type Resolution, type ShadowCallsite, type ShadowParityReport, type SupportedLanguages } from '../../_shared/index.js';
61
+ /** Which side of the dual-run is considered authoritative for this language. */
62
+ export type PrimarySide = 'legacy' | 'registry';
63
+ /** One record per call site the caller dual-runs. */
64
+ export interface ShadowRecordInput {
65
+ readonly language: SupportedLanguages;
66
+ readonly callsite: ShadowCallsite;
67
+ readonly legacy: readonly Resolution[];
68
+ readonly newResult: readonly Resolution[];
69
+ /**
70
+ * Which side drove the actual runtime answer for this record. Lets the
71
+ * dashboard distinguish "registry-primary, legacy is shadow" from the
72
+ * default "legacy-primary, registry is shadow" without re-reading
73
+ * `REGISTRY_PRIMARY_<LANG>` env vars at render time.
74
+ */
75
+ readonly primary: PrimarySide;
76
+ }
77
+ /** Persisted JSON shape. Schema-versioned for future migrations. */
78
+ export interface PersistedShadowReport {
79
+ readonly schemaVersion: 1;
80
+ readonly runId: string;
81
+ readonly generatedAt: string;
82
+ readonly primaryByLanguage: Readonly<Partial<Record<SupportedLanguages, PrimarySide>>>;
83
+ readonly report: ShadowParityReport;
84
+ }
85
+ export interface ShadowHarness {
86
+ /** `true` iff `GITNEXUS_SHADOW_MODE` is truthy. When `false`, `record()` is a no-op. */
87
+ readonly enabled: boolean;
88
+ /** Accumulate a dual-run observation. No-op when `enabled === false`. */
89
+ record(input: ShadowRecordInput): void;
90
+ /** Number of records accumulated so far. Useful for diagnostics / tests. */
91
+ size(): number;
92
+ /**
93
+ * Aggregate the accumulated records into a `ShadowParityReport`
94
+ * without persisting. Returns a deterministic snapshot each call;
95
+ * idempotent with respect to `record()` ordering.
96
+ */
97
+ snapshot(now?: Date): ShadowParityReport;
98
+ /**
99
+ * Write the aggregated snapshot to JSON. Resolves to the path of the
100
+ * per-run file. Also writes/overwrites `latest.json` alongside.
101
+ *
102
+ * Creates `outputDir` if it doesn't exist.
103
+ */
104
+ persist(outputDir: string, now?: Date): Promise<string>;
105
+ /** Reset the accumulator. Preserves `enabled`. */
106
+ clear(): void;
107
+ }
108
+ /**
109
+ * Construct a harness. Reads `GITNEXUS_SHADOW_MODE` at construction time
110
+ * (not per-`record()` call) so repeated no-op records don't re-check the
111
+ * env var in the hot path.
112
+ */
113
+ export declare function createShadowHarness(): ShadowHarness;
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Shadow-mode parity harness — dual-run observability for the RFC #909
3
+ * registry rollout (RFC §6.3; Ring 2 PKG #923).
4
+ *
5
+ * ## What it does
6
+ *
7
+ * - Exposes `record({ language, callsite, legacy, newResult })` for
8
+ * every call site where the caller has BOTH a legacy-DAG resolution
9
+ * and a new `Registry.lookup` resolution.
10
+ * - Computes a `ShadowDiff` per record via shared `diffResolutions`
11
+ * (#918) and accumulates them in a per-language bucket.
12
+ * - At the end of a run, aggregates into a `ShadowParityReport` via
13
+ * shared `aggregateDiffs` (#918) — per-language parity %,
14
+ * evidence-kind breakdown of divergences, grand-total overall row.
15
+ * - Optionally persists the report as JSON under
16
+ * `.gitnexus/shadow-parity/` so the static dashboard at
17
+ * `gitnexus/shadow-parity-dashboard/` can render it offline.
18
+ *
19
+ * ## What it does NOT do
20
+ *
21
+ * - **Invoke either resolution path itself.** The caller must run
22
+ * legacy + `Registry.lookup` and pass results in. The harness is a
23
+ * side-car, not a dispatcher — this keeps call-processor integration
24
+ * surgical when it lands (tracked as a follow-up; the shared model
25
+ * doesn't dual-invoke on its own).
26
+ * - **Flip anything.** `REGISTRY_PRIMARY_<LANG>` lives in
27
+ * `registry-primary-flag.ts` (#924); the harness records the
28
+ * caller-supplied "which side is primary" bit for each record so the
29
+ * dashboard can label rows, but it does not consult the flag itself.
30
+ *
31
+ * ## Activation
32
+ *
33
+ * `GITNEXUS_SHADOW_MODE=1` (or `'true'`, `'yes'`, case-insensitive,
34
+ * trimmed) enables the harness. When disabled, `record()` is a cheap
35
+ * no-op: no accumulation, no allocation beyond the harness object
36
+ * itself. Callers can always construct a harness and hand it through;
37
+ * the "off" overhead is near-zero.
38
+ *
39
+ * ## Persistence shape
40
+ *
41
+ * When `persist()` is called, the harness writes TWO files:
42
+ *
43
+ * - `<outputDir>/<runId>.json` — the timestamped snapshot (immutable)
44
+ * - `<outputDir>/latest.json` — a pointer that the dashboard reads
45
+ *
46
+ * Both files contain the same `PersistedShadowReport` payload:
47
+ *
48
+ * {
49
+ * schemaVersion: 1,
50
+ * runId: "<iso-8601>-<rand>",
51
+ * generatedAt: "<iso-8601>",
52
+ * primaryByLanguage: { [lang]: "legacy" | "registry" },
53
+ * report: <ShadowParityReport>
54
+ * }
55
+ *
56
+ * Schema-version-gated so future format changes don't silently confuse
57
+ * older dashboards. The dashboard renders `report.perLanguage` rows and
58
+ * annotates each with `primaryByLanguage[lang]`.
59
+ */
60
+ import * as fs from 'node:fs/promises';
61
+ import * as path from 'node:path';
62
+ import { aggregateDiffs, diffResolutions, } from '../../_shared/index.js';
63
+ /**
64
+ * Construct a harness. Reads `GITNEXUS_SHADOW_MODE` at construction time
65
+ * (not per-`record()` call) so repeated no-op records don't re-check the
66
+ * env var in the hot path.
67
+ */
68
+ export function createShadowHarness() {
69
+ const enabled = parseShadowModeEnv(process.env['GITNEXUS_SHADOW_MODE']);
70
+ const records = [];
71
+ const primaryByLanguage = {};
72
+ const recordImpl = (input) => {
73
+ if (!enabled)
74
+ return;
75
+ const diff = diffResolutions(input.callsite, input.legacy, input.newResult);
76
+ records.push({ language: input.language, diff });
77
+ // Primary per-language is resolved by last-write. In practice a run
78
+ // is single-threaded with respect to flag readings, so this is
79
+ // deterministic; a language's primary cannot change mid-run.
80
+ primaryByLanguage[input.language] = input.primary;
81
+ };
82
+ const snapshotImpl = (now = new Date()) => {
83
+ return aggregateDiffs(records, now);
84
+ };
85
+ const persistImpl = async (outputDir, now = new Date()) => {
86
+ await fs.mkdir(outputDir, { recursive: true });
87
+ const report = snapshotImpl(now);
88
+ const runId = makeRunId(now);
89
+ const payload = {
90
+ schemaVersion: 1,
91
+ runId,
92
+ generatedAt: now.toISOString(),
93
+ primaryByLanguage,
94
+ report,
95
+ };
96
+ const json = JSON.stringify(payload, null, 2);
97
+ const perRunPath = path.join(outputDir, `${runId}.json`);
98
+ const latestPath = path.join(outputDir, 'latest.json');
99
+ await fs.writeFile(perRunPath, json, 'utf8');
100
+ await fs.writeFile(latestPath, json, 'utf8');
101
+ return perRunPath;
102
+ };
103
+ const clearImpl = () => {
104
+ records.length = 0;
105
+ for (const key of Object.keys(primaryByLanguage)) {
106
+ delete primaryByLanguage[key];
107
+ }
108
+ };
109
+ return {
110
+ enabled,
111
+ record: recordImpl,
112
+ size: () => records.length,
113
+ snapshot: snapshotImpl,
114
+ persist: persistImpl,
115
+ clear: clearImpl,
116
+ };
117
+ }
118
+ // ─── Internal helpers ─────────────────────────────────────────────────────
119
+ /**
120
+ * Env-var parser for `GITNEXUS_SHADOW_MODE`. Accepts the same truthy
121
+ * conventions as `REGISTRY_PRIMARY_<LANG>` from #924: `'true'` / `'1'` /
122
+ * `'yes'`, case-insensitive, whitespace-trimmed. Anything else — including
123
+ * `undefined`, `''`, `'false'`, `'off'`, typos — is false.
124
+ */
125
+ function parseShadowModeEnv(raw) {
126
+ if (raw === undefined)
127
+ return false;
128
+ const normalized = raw.trim().toLowerCase();
129
+ return normalized === 'true' || normalized === '1' || normalized === 'yes';
130
+ }
131
+ /**
132
+ * Deterministic run id derived from the timestamp plus 4 random bytes
133
+ * of entropy. The timestamp comes first so files sort chronologically;
134
+ * the entropy suffix prevents collisions when multiple runs share a
135
+ * clock-second. Shape: `YYYYMMDD-HHMMSS-xxxxxxxx`.
136
+ */
137
+ function makeRunId(now) {
138
+ const y = now.getUTCFullYear().toString().padStart(4, '0');
139
+ const m = (now.getUTCMonth() + 1).toString().padStart(2, '0');
140
+ const d = now.getUTCDate().toString().padStart(2, '0');
141
+ const h = now.getUTCHours().toString().padStart(2, '0');
142
+ const min = now.getUTCMinutes().toString().padStart(2, '0');
143
+ const s = now.getUTCSeconds().toString().padStart(2, '0');
144
+ const entropy = Math.floor(Math.random() * 0xffffffff)
145
+ .toString(16)
146
+ .padStart(8, '0');
147
+ return `${y}${m}${d}-${h}${min}${s}-${entropy}`;
148
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.3-rc.14",
3
+ "version": "1.6.3-rc.15",
4
4
  "description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
5
5
  "author": "Abhigyan Patwari",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",