@veewo/gitnexus 1.4.11-rc.2 → 1.5.0-rc.2
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/benchmark/u2-e2e/hydration-policy-repeatability-runner.d.ts +55 -0
- package/dist/benchmark/u2-e2e/hydration-policy-repeatability-runner.js +190 -0
- package/dist/benchmark/u2-e2e/hydration-policy-repeatability-runner.test.js +13 -0
- package/dist/benchmark/u2-e2e/phase1-process-ref-acceptance-runner.d.ts +22 -0
- package/dist/benchmark/u2-e2e/phase1-process-ref-acceptance-runner.js +100 -0
- package/dist/benchmark/u2-e2e/phase1-process-ref-acceptance-runner.test.d.ts +1 -0
- package/dist/benchmark/u2-e2e/phase1-process-ref-acceptance-runner.test.js +13 -0
- package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.d.ts +27 -0
- package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.js +118 -0
- package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.test.d.ts +1 -0
- package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.test.js +16 -0
- package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.d.ts +60 -0
- package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.js +331 -0
- package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.test.d.ts +1 -0
- package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.test.js +42 -0
- package/dist/benchmark/u2-e2e/reload-v1-acceptance-runner.js +4 -4
- package/dist/benchmark/unity-lazy-context-sampler.d.ts +6 -0
- package/dist/benchmark/unity-lazy-context-sampler.js +49 -13
- package/dist/benchmark/unity-lazy-context-sampler.test.js +4 -0
- package/dist/cli/ai-context.js +6 -1
- package/dist/cli/eval-server.js +0 -3
- package/dist/cli/index.js +8 -0
- package/dist/cli/mcp.js +0 -3
- package/dist/cli/rule-lab.d.ts +42 -0
- package/dist/cli/rule-lab.js +157 -0
- package/dist/cli/rule-lab.test.d.ts +1 -0
- package/dist/cli/rule-lab.test.js +11 -0
- package/dist/cli/tool.d.ts +7 -1
- package/dist/cli/tool.js +6 -0
- package/dist/core/config/unity-config.d.ts +20 -0
- package/dist/core/config/unity-config.js +46 -0
- package/dist/core/graph/types.d.ts +1 -1
- package/dist/core/ingestion/pipeline.js +38 -13
- package/dist/core/ingestion/unity-lifecycle-synthetic-calls.d.ts +0 -2
- package/dist/core/ingestion/unity-lifecycle-synthetic-calls.js +26 -213
- package/dist/core/ingestion/unity-lifecycle-synthetic-calls.test.js +1 -1
- package/dist/core/ingestion/unity-resource-processor.js +87 -22
- package/dist/core/ingestion/unity-resource-processor.test.js +67 -2
- package/dist/core/ingestion/unity-runtime-binding-rules.d.ts +11 -0
- package/dist/core/ingestion/unity-runtime-binding-rules.js +179 -0
- package/dist/core/unity/options.d.ts +4 -0
- package/dist/core/unity/options.js +18 -0
- package/dist/core/unity/options.test.js +11 -1
- package/dist/core/unity/resolver.js +11 -1
- package/dist/core/unity/resolver.test.js +62 -0
- package/dist/core/unity/yaml-object-graph.js +1 -1
- package/dist/core/unity/yaml-object-graph.test.js +16 -0
- package/dist/mcp/local/derived-process-reader.d.ts +2 -0
- package/dist/mcp/local/derived-process-reader.js +15 -0
- package/dist/mcp/local/local-backend.d.ts +56 -0
- package/dist/mcp/local/local-backend.js +1003 -53
- package/dist/mcp/local/local-backend.unity-merge.test.js +1 -1
- package/dist/mcp/local/process-confidence.js +1 -1
- package/dist/mcp/local/process-evidence.d.ts +1 -0
- package/dist/mcp/local/process-evidence.js +22 -0
- package/dist/mcp/local/process-evidence.test.js +11 -1
- package/dist/mcp/local/process-ref.d.ts +24 -0
- package/dist/mcp/local/process-ref.js +33 -0
- package/dist/mcp/local/process-ref.test.d.ts +1 -0
- package/dist/mcp/local/process-ref.test.js +24 -0
- package/dist/mcp/local/runtime-chain-verify.d.ts +15 -1
- package/dist/mcp/local/runtime-chain-verify.js +191 -187
- package/dist/mcp/local/runtime-chain-verify.test.js +546 -19
- package/dist/mcp/local/runtime-claim-rule-registry.d.ts +63 -0
- package/dist/mcp/local/runtime-claim-rule-registry.js +308 -0
- package/dist/mcp/local/runtime-claim-rule-registry.test.d.ts +1 -0
- package/dist/mcp/local/runtime-claim-rule-registry.test.js +215 -0
- package/dist/mcp/local/runtime-claim.d.ts +38 -0
- package/dist/mcp/local/runtime-claim.js +54 -0
- package/dist/mcp/local/runtime-claim.test.d.ts +1 -0
- package/dist/mcp/local/runtime-claim.test.js +27 -0
- package/dist/mcp/local/unity-enrichment.d.ts +1 -0
- package/dist/mcp/local/unity-enrichment.js +1 -1
- package/dist/mcp/local/unity-evidence-view.d.ts +26 -0
- package/dist/mcp/local/unity-evidence-view.js +96 -0
- package/dist/mcp/local/unity-evidence-view.test.d.ts +1 -0
- package/dist/mcp/local/unity-evidence-view.test.js +39 -0
- package/dist/mcp/local/unity-lazy-hydrator.d.ts +2 -2
- package/dist/mcp/local/unity-lazy-hydrator.js +3 -3
- package/dist/mcp/local/unity-lazy-hydrator.test.js +4 -4
- package/dist/mcp/local/unity-parity-cache.js +2 -6
- package/dist/mcp/local/unity-parity-seed-loader.d.ts +1 -0
- package/dist/mcp/local/unity-parity-seed-loader.js +10 -16
- package/dist/mcp/local/unity-parity-seed-loader.test.js +3 -12
- package/dist/mcp/local/unity-runtime-hydration.d.ts +3 -2
- package/dist/mcp/local/unity-runtime-hydration.js +13 -16
- package/dist/mcp/local/unity-runtime-hydration.test.js +15 -1
- package/dist/mcp/resources.js +13 -0
- package/dist/mcp/tools.js +166 -13
- package/dist/rule-lab/analyze.d.ts +12 -0
- package/dist/rule-lab/analyze.js +90 -0
- package/dist/rule-lab/analyze.test.d.ts +1 -0
- package/dist/rule-lab/analyze.test.js +28 -0
- package/dist/rule-lab/compile.d.ts +5 -0
- package/dist/rule-lab/compile.js +51 -0
- package/dist/rule-lab/compiled-bundles.d.ts +30 -0
- package/dist/rule-lab/compiled-bundles.js +36 -0
- package/dist/rule-lab/curate.d.ts +32 -0
- package/dist/rule-lab/curate.js +134 -0
- package/dist/rule-lab/curate.test.d.ts +1 -0
- package/dist/rule-lab/curate.test.js +72 -0
- package/dist/rule-lab/discover.d.ts +13 -0
- package/dist/rule-lab/discover.js +74 -0
- package/dist/rule-lab/discover.test.d.ts +1 -0
- package/dist/rule-lab/discover.test.js +42 -0
- package/dist/rule-lab/paths.d.ts +21 -0
- package/dist/rule-lab/paths.js +37 -0
- package/dist/rule-lab/paths.test.d.ts +1 -0
- package/dist/rule-lab/paths.test.js +46 -0
- package/dist/rule-lab/promote.d.ts +26 -0
- package/dist/rule-lab/promote.js +314 -0
- package/dist/rule-lab/promote.test.d.ts +1 -0
- package/dist/rule-lab/promote.test.js +164 -0
- package/dist/rule-lab/regress.d.ts +60 -0
- package/dist/rule-lab/regress.js +122 -0
- package/dist/rule-lab/regress.test.d.ts +1 -0
- package/dist/rule-lab/regress.test.js +68 -0
- package/dist/rule-lab/review-pack.d.ts +31 -0
- package/dist/rule-lab/review-pack.js +125 -0
- package/dist/rule-lab/review-pack.test.d.ts +1 -0
- package/dist/rule-lab/review-pack.test.js +49 -0
- package/dist/rule-lab/types.d.ts +99 -0
- package/dist/rule-lab/types.js +1 -0
- package/package.json +1 -1
- package/skills/_shared/unity-hydration-contract.md +11 -0
- package/skills/_shared/unity-ui-trace-contract.md +33 -0
- package/skills/gitnexus-cli.md +14 -25
- package/skills/gitnexus-guide.md +2 -0
- package/skills/gitnexus-unity-rule-gen.md +318 -0
- package/dist/core/ingestion/unity-lifecycle-config.d.ts +0 -5
- package/dist/core/ingestion/unity-lifecycle-config.js +0 -25
- package/dist/mcp/local/unity-lazy-config.d.ts +0 -6
- package/dist/mcp/local/unity-lazy-config.js +0 -7
- package/dist/mcp/local/unity-lazy-config.test.js +0 -9
- package/dist/mcp/local/unity-process-confidence-config.d.ts +0 -1
- package/dist/mcp/local/unity-process-confidence-config.js +0 -4
- package/dist/mcp/local/unity-runtime-chain-verify-config.d.ts +0 -1
- package/dist/mcp/local/unity-runtime-chain-verify-config.js +0 -10
- /package/dist/{mcp/local/unity-lazy-config.test.d.ts → benchmark/u2-e2e/hydration-policy-repeatability-runner.test.d.ts} +0 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { UnityEvidenceMode } from '../../core/unity/options.js';
|
|
2
|
+
import type { ResolvedUnityBinding, UnitySerializedFields } from '../../core/unity/resolver.js';
|
|
3
|
+
export interface UnityEvidenceMeta {
|
|
4
|
+
truncated: boolean;
|
|
5
|
+
omitted_count: number;
|
|
6
|
+
next_fetch_hint?: string;
|
|
7
|
+
filter_exhausted?: boolean;
|
|
8
|
+
minimum_evidence_satisfied: boolean;
|
|
9
|
+
verifier_minimum_evidence_satisfied?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface UnityEvidenceViewResult {
|
|
12
|
+
resourceBindings: ResolvedUnityBinding[];
|
|
13
|
+
serializedFields: UnitySerializedFields;
|
|
14
|
+
evidence_meta: UnityEvidenceMeta;
|
|
15
|
+
filter_diagnostics: string[];
|
|
16
|
+
}
|
|
17
|
+
export interface UnityEvidenceViewInput {
|
|
18
|
+
resourceBindings: ResolvedUnityBinding[];
|
|
19
|
+
mode?: UnityEvidenceMode;
|
|
20
|
+
scopePreset?: string;
|
|
21
|
+
resourcePathPrefix?: string;
|
|
22
|
+
bindingKind?: string;
|
|
23
|
+
maxBindings?: number;
|
|
24
|
+
maxReferenceFields?: number;
|
|
25
|
+
}
|
|
26
|
+
export declare function buildUnityEvidenceView(input: UnityEvidenceViewInput): UnityEvidenceViewResult;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
function normalizePath(value) {
|
|
2
|
+
return String(value || '').replace(/\\/g, '/').toLowerCase();
|
|
3
|
+
}
|
|
4
|
+
function parsePositiveInt(raw) {
|
|
5
|
+
if (raw === undefined || raw === null || raw === '')
|
|
6
|
+
return undefined;
|
|
7
|
+
const parsed = Number.parseInt(String(raw), 10);
|
|
8
|
+
if (!Number.isFinite(parsed) || parsed <= 0)
|
|
9
|
+
return undefined;
|
|
10
|
+
return parsed;
|
|
11
|
+
}
|
|
12
|
+
function resolveModeDefaults(mode) {
|
|
13
|
+
if (mode === 'summary') {
|
|
14
|
+
return { maxBindings: 1, maxReferenceFields: 1 };
|
|
15
|
+
}
|
|
16
|
+
if (mode === 'focused') {
|
|
17
|
+
return { maxBindings: 5, maxReferenceFields: 5 };
|
|
18
|
+
}
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
function aggregateSerializedFields(resourceBindings) {
|
|
22
|
+
return {
|
|
23
|
+
scalarFields: resourceBindings.flatMap((binding) => binding.serializedFields.scalarFields),
|
|
24
|
+
referenceFields: resourceBindings.flatMap((binding) => binding.serializedFields.referenceFields),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export function buildUnityEvidenceView(input) {
|
|
28
|
+
const diagnostics = [];
|
|
29
|
+
const originalCount = input.resourceBindings.length;
|
|
30
|
+
let filtered = [...input.resourceBindings];
|
|
31
|
+
if (input.scopePreset === 'unity-gameplay') {
|
|
32
|
+
filtered = filtered.filter((binding) => {
|
|
33
|
+
const p = normalizePath(binding.resourcePath);
|
|
34
|
+
return p.startsWith('assets/')
|
|
35
|
+
&& !p.startsWith('assets/plugins/')
|
|
36
|
+
&& !p.startsWith('packages/')
|
|
37
|
+
&& !p.startsWith('library/');
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const prefix = normalizePath(String(input.resourcePathPrefix || ''));
|
|
41
|
+
if (prefix) {
|
|
42
|
+
filtered = filtered.filter((binding) => normalizePath(binding.resourcePath).startsWith(prefix));
|
|
43
|
+
}
|
|
44
|
+
const bindingKind = String(input.bindingKind || '').trim();
|
|
45
|
+
if (bindingKind) {
|
|
46
|
+
filtered = filtered.filter((binding) => String(binding.bindingKind) === bindingKind);
|
|
47
|
+
}
|
|
48
|
+
const filterExhausted = filtered.length === 0 && originalCount > 0;
|
|
49
|
+
if (filterExhausted) {
|
|
50
|
+
diagnostics.push('filter_exhausted');
|
|
51
|
+
}
|
|
52
|
+
const modeDefaults = resolveModeDefaults(input.mode);
|
|
53
|
+
const maxBindings = parsePositiveInt(input.maxBindings) ?? modeDefaults.maxBindings;
|
|
54
|
+
const maxReferenceFields = parsePositiveInt(input.maxReferenceFields) ?? modeDefaults.maxReferenceFields;
|
|
55
|
+
const beforeBindingTrim = filtered.length;
|
|
56
|
+
if (maxBindings !== undefined) {
|
|
57
|
+
filtered = filtered.slice(0, maxBindings);
|
|
58
|
+
}
|
|
59
|
+
let omittedCount = Math.max(0, beforeBindingTrim - filtered.length);
|
|
60
|
+
if (maxReferenceFields !== undefined) {
|
|
61
|
+
filtered = filtered.map((binding) => {
|
|
62
|
+
const referenceFields = binding.serializedFields.referenceFields;
|
|
63
|
+
const referenceTrimmed = referenceFields.length > maxReferenceFields;
|
|
64
|
+
if (referenceTrimmed) {
|
|
65
|
+
omittedCount += referenceFields.length - maxReferenceFields;
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
...binding,
|
|
69
|
+
serializedFields: {
|
|
70
|
+
...binding.serializedFields,
|
|
71
|
+
referenceFields: referenceTrimmed
|
|
72
|
+
? referenceFields.slice(0, maxReferenceFields)
|
|
73
|
+
: referenceFields,
|
|
74
|
+
},
|
|
75
|
+
resolvedReferences: binding.resolvedReferences.length > (maxReferenceFields || 0)
|
|
76
|
+
? binding.resolvedReferences.slice(0, maxReferenceFields)
|
|
77
|
+
: binding.resolvedReferences,
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
const truncated = omittedCount > 0;
|
|
82
|
+
const evidence_meta = {
|
|
83
|
+
truncated,
|
|
84
|
+
omitted_count: omittedCount,
|
|
85
|
+
...(truncated ? { next_fetch_hint: 'Rerun with unity_evidence_mode=full to fetch complete evidence.' } : {}),
|
|
86
|
+
...(filterExhausted ? { filter_exhausted: true } : {}),
|
|
87
|
+
minimum_evidence_satisfied: !truncated && filtered.length > 0,
|
|
88
|
+
verifier_minimum_evidence_satisfied: filtered.length > 0,
|
|
89
|
+
};
|
|
90
|
+
return {
|
|
91
|
+
resourceBindings: filtered,
|
|
92
|
+
serializedFields: aggregateSerializedFields(filtered),
|
|
93
|
+
evidence_meta,
|
|
94
|
+
filter_diagnostics: diagnostics,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { buildUnityEvidenceView } from './unity-evidence-view.js';
|
|
4
|
+
test('unity evidence view emits truncation metadata and fetch hint', () => {
|
|
5
|
+
const out = buildUnityEvidenceView({
|
|
6
|
+
resourceBindings: [
|
|
7
|
+
{
|
|
8
|
+
resourcePath: 'Assets/A.prefab',
|
|
9
|
+
resourceType: 'prefab',
|
|
10
|
+
bindingKind: 'direct',
|
|
11
|
+
componentObjectId: '1',
|
|
12
|
+
evidence: { line: 1, lineText: 'x' },
|
|
13
|
+
serializedFields: {
|
|
14
|
+
scalarFields: [],
|
|
15
|
+
referenceFields: [
|
|
16
|
+
{ name: 'r1', sourceLayer: 'base' },
|
|
17
|
+
{ name: 'r2', sourceLayer: 'base' },
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
resolvedReferences: [],
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
resourcePath: 'Assets/B.prefab',
|
|
24
|
+
resourceType: 'prefab',
|
|
25
|
+
bindingKind: 'direct',
|
|
26
|
+
componentObjectId: '2',
|
|
27
|
+
evidence: { line: 2, lineText: 'y' },
|
|
28
|
+
serializedFields: { scalarFields: [], referenceFields: [] },
|
|
29
|
+
resolvedReferences: [],
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
mode: 'summary',
|
|
33
|
+
maxBindings: 1,
|
|
34
|
+
maxReferenceFields: 1,
|
|
35
|
+
});
|
|
36
|
+
assert.equal(out.evidence_meta.truncated, true);
|
|
37
|
+
assert.ok(out.evidence_meta.omitted_count > 0);
|
|
38
|
+
assert.match(out.evidence_meta.next_fetch_hint || '', /unity_evidence_mode=full/i);
|
|
39
|
+
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { ResolvedUnityBinding } from '../../core/unity/resolver.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { UnityConfig } from '../../core/config/unity-config.js';
|
|
3
3
|
export interface HydrateLazyBindingsInput {
|
|
4
4
|
pendingPaths: string[];
|
|
5
|
-
config:
|
|
5
|
+
config: Pick<UnityConfig, 'lazyMaxPaths' | 'lazyBatchSize' | 'lazyMaxMs'>;
|
|
6
6
|
resolveBatch: (paths: string[]) => Promise<Map<string, ResolvedUnityBinding[]>>;
|
|
7
7
|
dedupeKey?: string;
|
|
8
8
|
}
|
|
@@ -14,14 +14,14 @@ export async function hydrateLazyBindings(input) {
|
|
|
14
14
|
return pending;
|
|
15
15
|
}
|
|
16
16
|
async function runHydration(input) {
|
|
17
|
-
const pending = input.pendingPaths.slice(0, Math.max(0, input.config.
|
|
18
|
-
const batchSize = Math.max(1, input.config.
|
|
17
|
+
const pending = input.pendingPaths.slice(0, Math.max(0, input.config.lazyMaxPaths));
|
|
18
|
+
const batchSize = Math.max(1, input.config.lazyBatchSize);
|
|
19
19
|
const startedAt = Date.now();
|
|
20
20
|
const resolvedByPath = new Map();
|
|
21
21
|
let timedOut = false;
|
|
22
22
|
const diagnostics = [];
|
|
23
23
|
for (let i = 0; i < pending.length; i += batchSize) {
|
|
24
|
-
if (Date.now() - startedAt > input.config.
|
|
24
|
+
if (Date.now() - startedAt > input.config.lazyMaxMs) {
|
|
25
25
|
timedOut = true;
|
|
26
26
|
break;
|
|
27
27
|
}
|
|
@@ -5,7 +5,7 @@ test('hydrateLazyBindings processes pending paths in bounded chunks', async () =
|
|
|
5
5
|
const calls = [];
|
|
6
6
|
await hydrateLazyBindings({
|
|
7
7
|
pendingPaths: ['a', 'b', 'c', 'd', 'e'],
|
|
8
|
-
config: {
|
|
8
|
+
config: { lazyMaxPaths: 4, lazyBatchSize: 2, lazyMaxMs: 5000 },
|
|
9
9
|
resolveBatch: async (paths) => {
|
|
10
10
|
calls.push(paths);
|
|
11
11
|
return new Map();
|
|
@@ -17,7 +17,7 @@ test('parallel requests dedupe same hydration work', async () => {
|
|
|
17
17
|
let resolveCalls = 0;
|
|
18
18
|
const sharedInput = {
|
|
19
19
|
pendingPaths: ['Assets/A.prefab'],
|
|
20
|
-
config: {
|
|
20
|
+
config: { lazyMaxPaths: 10, lazyBatchSize: 5, lazyMaxMs: 5000 },
|
|
21
21
|
dedupeKey: 'symbol:door::Assets/A.prefab',
|
|
22
22
|
resolveBatch: async (_paths) => {
|
|
23
23
|
resolveCalls += 1;
|
|
@@ -34,7 +34,7 @@ test('parallel requests dedupe same hydration work', async () => {
|
|
|
34
34
|
test('context lazy hydration returns partial results when budget exceeded and reports diagnostics', async () => {
|
|
35
35
|
const out = await hydrateLazyBindings({
|
|
36
36
|
pendingPaths: ['a', 'b', 'c', 'd'],
|
|
37
|
-
config: {
|
|
37
|
+
config: { lazyMaxPaths: 4, lazyBatchSize: 2, lazyMaxMs: 1 },
|
|
38
38
|
resolveBatch: async (paths) => {
|
|
39
39
|
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
40
40
|
return new Map(paths.map((p) => [p, []]));
|
|
@@ -46,7 +46,7 @@ test('context lazy hydration returns partial results when budget exceeded and re
|
|
|
46
46
|
test('summary-only Unity analyze persistence still returns full bindings after lazy hydration', async () => {
|
|
47
47
|
const out = await hydrateLazyBindings({
|
|
48
48
|
pendingPaths: ['Assets/Doors/Door.prefab'],
|
|
49
|
-
config: {
|
|
49
|
+
config: { lazyMaxPaths: 10, lazyBatchSize: 5, lazyMaxMs: 5000 },
|
|
50
50
|
resolveBatch: async () => new Map([
|
|
51
51
|
['Assets/Doors/Door.prefab', [{
|
|
52
52
|
resourcePath: 'Assets/Doors/Door.prefab',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
|
+
import { resolveUnityConfig } from '../../core/config/unity-config.js';
|
|
4
5
|
const PARITY_CACHE_DIRNAME = 'unity-parity-cache';
|
|
5
6
|
const DEFAULT_MAX_PARITY_CACHE_ENTRIES = 500;
|
|
6
7
|
function buildKey(symbolUid) {
|
|
@@ -66,12 +67,7 @@ function resolveMaxEntries(options) {
|
|
|
66
67
|
if (Number.isFinite(options?.maxEntries) && Number(options?.maxEntries) > 0) {
|
|
67
68
|
return Math.floor(Number(options?.maxEntries));
|
|
68
69
|
}
|
|
69
|
-
|
|
70
|
-
const parsed = Number.parseInt(raw, 10);
|
|
71
|
-
if (Number.isFinite(parsed) && parsed > 0) {
|
|
72
|
-
return parsed;
|
|
73
|
-
}
|
|
74
|
-
return DEFAULT_MAX_PARITY_CACHE_ENTRIES;
|
|
70
|
+
return resolveUnityConfig().config.parityCacheMaxEntries ?? DEFAULT_MAX_PARITY_CACHE_ENTRIES;
|
|
75
71
|
}
|
|
76
72
|
function pruneOldestEntries(entries, maxEntries) {
|
|
77
73
|
const rows = Object.entries(entries);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { UnityParitySeed } from '../../core/ingestion/unity-parity-seed.js';
|
|
2
2
|
interface LoadUnityParitySeedOptions {
|
|
3
3
|
indexedCommit?: string;
|
|
4
|
+
idleMsOverride?: number;
|
|
4
5
|
}
|
|
5
6
|
export declare function loadUnityParitySeed(storagePath: string, options?: LoadUnityParitySeedOptions): Promise<UnityParitySeed | null>;
|
|
6
7
|
export declare function __resetUnityParitySeedLoaderCacheForTest(): void;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { resolveUnityConfig } from '../../core/config/unity-config.js';
|
|
3
4
|
const SEED_FILENAME = 'unity-parity-seed.json';
|
|
4
5
|
const DEFAULT_IDLE_MS = 30_000;
|
|
5
6
|
const DEFAULT_MAX_ENTRIES = 2;
|
|
@@ -32,7 +33,7 @@ export async function loadUnityParitySeed(storagePath, options) {
|
|
|
32
33
|
throw error;
|
|
33
34
|
}
|
|
34
35
|
const parsed = parseSeed(raw);
|
|
35
|
-
setCacheEntry(cacheKey, parsed);
|
|
36
|
+
setCacheEntry(cacheKey, parsed, options?.idleMsOverride);
|
|
36
37
|
return parsed;
|
|
37
38
|
})().finally(() => {
|
|
38
39
|
inFlightLoads.delete(cacheKey);
|
|
@@ -69,7 +70,7 @@ async function buildSeedCacheKey(seedPath, storagePath, indexedCommit) {
|
|
|
69
70
|
throw error;
|
|
70
71
|
}
|
|
71
72
|
}
|
|
72
|
-
function setCacheEntry(cacheKey, value) {
|
|
73
|
+
function setCacheEntry(cacheKey, value, idleMsOverride) {
|
|
73
74
|
const now = Date.now();
|
|
74
75
|
const existing = seedCache.get(cacheKey);
|
|
75
76
|
if (existing?.idleTimer) {
|
|
@@ -78,9 +79,10 @@ function setCacheEntry(cacheKey, value) {
|
|
|
78
79
|
const entry = {
|
|
79
80
|
value,
|
|
80
81
|
lastAccessMs: now,
|
|
82
|
+
idleMsOverride,
|
|
81
83
|
};
|
|
82
84
|
seedCache.set(cacheKey, entry);
|
|
83
|
-
scheduleEviction(cacheKey, entry);
|
|
85
|
+
scheduleEviction(cacheKey, entry, idleMsOverride);
|
|
84
86
|
pruneOldestEntries(resolveMaxEntries());
|
|
85
87
|
}
|
|
86
88
|
function touchCacheEntry(cacheKey, entry) {
|
|
@@ -88,10 +90,10 @@ function touchCacheEntry(cacheKey, entry) {
|
|
|
88
90
|
if (entry.idleTimer) {
|
|
89
91
|
clearTimeout(entry.idleTimer);
|
|
90
92
|
}
|
|
91
|
-
scheduleEviction(cacheKey, entry);
|
|
93
|
+
scheduleEviction(cacheKey, entry, entry.idleMsOverride);
|
|
92
94
|
}
|
|
93
|
-
function scheduleEviction(cacheKey, entry) {
|
|
94
|
-
const idleMs = resolveIdleMs();
|
|
95
|
+
function scheduleEviction(cacheKey, entry, idleMsOverride) {
|
|
96
|
+
const idleMs = idleMsOverride ?? resolveIdleMs();
|
|
95
97
|
entry.idleTimer = setTimeout(() => {
|
|
96
98
|
const current = seedCache.get(cacheKey);
|
|
97
99
|
if (!current || current !== entry) {
|
|
@@ -116,18 +118,10 @@ function pruneOldestEntries(maxEntries) {
|
|
|
116
118
|
}
|
|
117
119
|
}
|
|
118
120
|
function resolveIdleMs() {
|
|
119
|
-
|
|
120
|
-
if (Number.isFinite(parsed) && parsed > 0) {
|
|
121
|
-
return parsed;
|
|
122
|
-
}
|
|
123
|
-
return DEFAULT_IDLE_MS;
|
|
121
|
+
return resolveUnityConfig().config.paritySeedCacheIdleMs ?? DEFAULT_IDLE_MS;
|
|
124
122
|
}
|
|
125
123
|
function resolveMaxEntries() {
|
|
126
|
-
|
|
127
|
-
if (Number.isFinite(parsed) && parsed > 0) {
|
|
128
|
-
return parsed;
|
|
129
|
-
}
|
|
130
|
-
return DEFAULT_MAX_ENTRIES;
|
|
124
|
+
return resolveUnityConfig().config.paritySeedCacheMaxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
131
125
|
}
|
|
132
126
|
export function __resetUnityParitySeedLoaderCacheForTest() {
|
|
133
127
|
for (const entry of seedCache.values()) {
|
|
@@ -54,9 +54,6 @@ test('loadUnityParitySeed deduplicates concurrent requests for same storage key'
|
|
|
54
54
|
});
|
|
55
55
|
test('loadUnityParitySeed evicts idle cache entry after ttl', async (t) => {
|
|
56
56
|
const storagePath = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-seed-loader-'));
|
|
57
|
-
const idleEnvKey = 'GITNEXUS_UNITY_PARITY_SEED_CACHE_IDLE_MS';
|
|
58
|
-
const previousIdle = process.env[idleEnvKey];
|
|
59
|
-
process.env[idleEnvKey] = '15';
|
|
60
57
|
try {
|
|
61
58
|
await writeSeed(storagePath, 'IdleSymbol');
|
|
62
59
|
const readFileOriginal = fs.readFile.bind(fs);
|
|
@@ -65,21 +62,15 @@ test('loadUnityParitySeed evicts idle cache entry after ttl', async (t) => {
|
|
|
65
62
|
readFileCalls += 1;
|
|
66
63
|
return readFileOriginal(...args);
|
|
67
64
|
});
|
|
68
|
-
await loadUnityParitySeed(storagePath);
|
|
69
|
-
await loadUnityParitySeed(storagePath);
|
|
65
|
+
await loadUnityParitySeed(storagePath, { idleMsOverride: 15 });
|
|
66
|
+
await loadUnityParitySeed(storagePath, { idleMsOverride: 15 });
|
|
70
67
|
assert.equal(readFileCalls, 1);
|
|
71
68
|
await new Promise((resolve) => setTimeout(resolve, 30));
|
|
72
|
-
await loadUnityParitySeed(storagePath);
|
|
69
|
+
await loadUnityParitySeed(storagePath, { idleMsOverride: 15 });
|
|
73
70
|
assert.equal(readFileCalls, 2);
|
|
74
71
|
}
|
|
75
72
|
finally {
|
|
76
73
|
__resetUnityParitySeedLoaderCacheForTest();
|
|
77
|
-
if (previousIdle === undefined) {
|
|
78
|
-
delete process.env[idleEnvKey];
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
process.env[idleEnvKey] = previousIdle;
|
|
82
|
-
}
|
|
83
74
|
await fs.rm(storagePath, { recursive: true, force: true });
|
|
84
75
|
}
|
|
85
76
|
});
|
|
@@ -2,7 +2,7 @@ import { buildUnityScanContext, buildUnityScanContextFromSeed } from '../../core
|
|
|
2
2
|
import { resolveUnityBindings, type ResolvedUnityBinding } from '../../core/unity/resolver.js';
|
|
3
3
|
import type { UnityHydrationMode } from '../../core/unity/options.js';
|
|
4
4
|
import { type UnityContextPayload, type UnityHydrationMeta } from './unity-enrichment.js';
|
|
5
|
-
import {
|
|
5
|
+
import { resolveUnityConfig } from '../../core/config/unity-config.js';
|
|
6
6
|
import { hydrateLazyBindings, type HydrateLazyBindingsInput } from './unity-lazy-hydrator.js';
|
|
7
7
|
import { readUnityOverlayBindings, upsertUnityOverlayBindings } from './unity-lazy-overlay.js';
|
|
8
8
|
import { readUnityParityCache, upsertUnityParityCache } from './unity-parity-cache.js';
|
|
@@ -28,7 +28,7 @@ export interface HydrateUnityInput {
|
|
|
28
28
|
interface HydrationRuntime {
|
|
29
29
|
now: () => number;
|
|
30
30
|
queue: ParityWarmupQueue;
|
|
31
|
-
resolveLazyConfig: typeof
|
|
31
|
+
resolveLazyConfig: () => Pick<ReturnType<typeof resolveUnityConfig>['config'], 'lazyMaxPaths' | 'lazyBatchSize' | 'lazyMaxMs'>;
|
|
32
32
|
hydrateLazyBindings: (input: HydrateLazyBindingsInput) => ReturnType<typeof hydrateLazyBindings>;
|
|
33
33
|
readOverlayBindings: typeof readUnityOverlayBindings;
|
|
34
34
|
upsertOverlayBindings: typeof upsertUnityOverlayBindings;
|
|
@@ -45,6 +45,7 @@ export declare function mergeParityUnityBindings(baseNonLightweightBindings: Res
|
|
|
45
45
|
export declare function attachUnityHydrationMeta(payload: UnityContextPayload, input: Pick<UnityHydrationMeta, 'requestedMode' | 'effectiveMode' | 'elapsedMs' | 'fallbackToCompact'> & {
|
|
46
46
|
hasExpandableBindings: boolean;
|
|
47
47
|
}): UnityContextPayload;
|
|
48
|
+
export declare function buildMissingEvidenceFromHydrationMeta(meta?: UnityHydrationMeta): string[];
|
|
48
49
|
export declare function hydrateUnityForSymbol(input: HydrateUnityInput): Promise<UnityContextPayload>;
|
|
49
50
|
export declare function __resetUnityRuntimeHydrationStateForTest(): void;
|
|
50
51
|
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { buildUnityScanContext, buildUnityScanContextFromSeed } from '../../core/unity/scan-context.js';
|
|
2
2
|
import { resolveUnityBindings } from '../../core/unity/resolver.js';
|
|
3
3
|
import { formatLazyHydrationBudgetDiagnostic, } from './unity-enrichment.js';
|
|
4
|
-
import {
|
|
4
|
+
import { resolveUnityConfig } from '../../core/config/unity-config.js';
|
|
5
5
|
import { hydrateLazyBindings } from './unity-lazy-hydrator.js';
|
|
6
6
|
import { readUnityOverlayBindings, upsertUnityOverlayBindings } from './unity-lazy-overlay.js';
|
|
7
7
|
import { readUnityParityCache, upsertUnityParityCache } from './unity-parity-cache.js';
|
|
@@ -9,16 +9,8 @@ import { createParityWarmupQueue } from './unity-parity-warmup-queue.js';
|
|
|
9
9
|
import { loadUnityParitySeed } from './unity-parity-seed-loader.js';
|
|
10
10
|
const inFlightParityHydration = new Map();
|
|
11
11
|
const parityWarmupQueue = createParityWarmupQueue({
|
|
12
|
-
maxParallel:
|
|
12
|
+
maxParallel: resolveUnityConfig().config.parityWarmupMaxParallel ?? 2,
|
|
13
13
|
});
|
|
14
|
-
function resolveParityWarmupMaxParallel(env) {
|
|
15
|
-
const raw = String(env.GITNEXUS_UNITY_PARITY_WARMUP_MAX_PARALLEL || '').trim();
|
|
16
|
-
const parsed = Number.parseInt(raw, 10);
|
|
17
|
-
if (Number.isFinite(parsed) && parsed > 0) {
|
|
18
|
-
return parsed;
|
|
19
|
-
}
|
|
20
|
-
return 2;
|
|
21
|
-
}
|
|
22
14
|
function normalizePath(filePath) {
|
|
23
15
|
return String(filePath || '').replace(/\\/g, '/');
|
|
24
16
|
}
|
|
@@ -92,6 +84,12 @@ export function attachUnityHydrationMeta(payload, input) {
|
|
|
92
84
|
},
|
|
93
85
|
};
|
|
94
86
|
}
|
|
87
|
+
export function buildMissingEvidenceFromHydrationMeta(meta) {
|
|
88
|
+
if (!meta || meta.isComplete) {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
return [...new Set((meta.completenessReason || []).map((value) => String(value || '').trim()).filter(Boolean))];
|
|
92
|
+
}
|
|
95
93
|
export async function hydrateUnityForSymbol(input) {
|
|
96
94
|
const runtime = resolveRuntime(input.runtime);
|
|
97
95
|
const startedAt = runtime.now();
|
|
@@ -122,7 +120,7 @@ function resolveRuntime(overrides) {
|
|
|
122
120
|
return {
|
|
123
121
|
now: () => Date.now(),
|
|
124
122
|
queue: parityWarmupQueue,
|
|
125
|
-
resolveLazyConfig:
|
|
123
|
+
resolveLazyConfig: () => resolveUnityConfig().config,
|
|
126
124
|
hydrateLazyBindings,
|
|
127
125
|
readOverlayBindings: readUnityOverlayBindings,
|
|
128
126
|
upsertOverlayBindings: upsertUnityOverlayBindings,
|
|
@@ -153,9 +151,8 @@ function scheduleParityWarmup(input, runtime) {
|
|
|
153
151
|
.then(() => undefined)
|
|
154
152
|
.catch(() => undefined);
|
|
155
153
|
}
|
|
156
|
-
function shouldEnableWarmup(
|
|
157
|
-
|
|
158
|
-
return raw === '1' || raw === 'true' || raw === 'on';
|
|
154
|
+
function shouldEnableWarmup(_env) {
|
|
155
|
+
return resolveUnityConfig().config.parityWarmup ?? false;
|
|
159
156
|
}
|
|
160
157
|
async function getOrRunParityHydration(input, runtime) {
|
|
161
158
|
const key = buildParityWarmupKey(input);
|
|
@@ -256,7 +253,7 @@ async function runCompactHydration(input, runtime) {
|
|
|
256
253
|
const unityDiagnostics = [...input.basePayload.unityDiagnostics];
|
|
257
254
|
if (pendingPaths.length > 0) {
|
|
258
255
|
try {
|
|
259
|
-
const cfg = runtime.resolveLazyConfig(
|
|
256
|
+
const cfg = runtime.resolveLazyConfig();
|
|
260
257
|
const hydration = await runtime.hydrateLazyBindings({
|
|
261
258
|
pendingPaths,
|
|
262
259
|
config: cfg,
|
|
@@ -315,7 +312,7 @@ function toUnityContextPayload(resourceBindings, unityDiagnostics) {
|
|
|
315
312
|
scalarFields: resourceBindings.flatMap((binding) => binding.serializedFields.scalarFields),
|
|
316
313
|
referenceFields: resourceBindings.flatMap((binding) => binding.serializedFields.referenceFields),
|
|
317
314
|
},
|
|
318
|
-
unityDiagnostics,
|
|
315
|
+
unityDiagnostics: [...new Set(unityDiagnostics)],
|
|
319
316
|
};
|
|
320
317
|
}
|
|
321
318
|
export function __resetUnityRuntimeHydrationStateForTest() {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import test from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
|
-
import { hydrateUnityForSymbol } from './unity-runtime-hydration.js';
|
|
3
|
+
import { buildMissingEvidenceFromHydrationMeta, hydrateUnityForSymbol } from './unity-runtime-hydration.js';
|
|
4
4
|
test('hydrateUnityForSymbol(compact) marks needsParityRetry when lightweight bindings remain', async () => {
|
|
5
5
|
const out = await hydrateUnityForSymbol({
|
|
6
6
|
mode: 'compact',
|
|
@@ -106,3 +106,17 @@ test('hydrateUnityForSymbol(parity) sets isComplete=true on parity success', asy
|
|
|
106
106
|
assert.equal(out.hydrationMeta?.effectiveMode, 'parity');
|
|
107
107
|
assert.equal(out.hydrationMeta?.isComplete, true);
|
|
108
108
|
});
|
|
109
|
+
test('buildMissingEvidenceFromHydrationMeta maps incomplete reasons', () => {
|
|
110
|
+
const missing = buildMissingEvidenceFromHydrationMeta({
|
|
111
|
+
requestedMode: 'compact',
|
|
112
|
+
effectiveMode: 'compact',
|
|
113
|
+
elapsedMs: 1,
|
|
114
|
+
fallbackToCompact: false,
|
|
115
|
+
resourceBindingCount: 1,
|
|
116
|
+
unityDiagnosticsCount: 0,
|
|
117
|
+
isComplete: false,
|
|
118
|
+
completenessReason: ['lightweight_bindings_remaining', 'budget_exceeded'],
|
|
119
|
+
needsParityRetry: true,
|
|
120
|
+
});
|
|
121
|
+
assert.deepEqual(missing, ['lightweight_bindings_remaining', 'budget_exceeded']);
|
|
122
|
+
});
|
package/dist/mcp/resources.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Provides structured on-demand data to AI agents.
|
|
5
5
|
* All resources use repo-scoped URIs: gitnexus://repo/{name}/context
|
|
6
6
|
*/
|
|
7
|
+
import { getDerivedProcessDetailResource } from './local/derived-process-reader.js';
|
|
7
8
|
import { checkStaleness } from './staleness.js';
|
|
8
9
|
import { loadCLIConfig } from '../storage/repo-manager.js';
|
|
9
10
|
import { buildNpxCommand, resolveCliSpec } from '../config/cli-spec.js';
|
|
@@ -67,6 +68,12 @@ export function getResourceTemplates() {
|
|
|
67
68
|
description: 'Step-by-step execution trace with lifecycle subtype and step evidence when available',
|
|
68
69
|
mimeType: 'text/yaml',
|
|
69
70
|
},
|
|
71
|
+
{
|
|
72
|
+
uriTemplate: 'gitnexus://repo/{name}/derived-process/{id}',
|
|
73
|
+
name: 'Derived Process Trace',
|
|
74
|
+
description: 'Readable trace metadata for derived process references',
|
|
75
|
+
mimeType: 'text/yaml',
|
|
76
|
+
},
|
|
70
77
|
];
|
|
71
78
|
}
|
|
72
79
|
/**
|
|
@@ -88,6 +95,9 @@ function parseUri(uri) {
|
|
|
88
95
|
if (rest.startsWith('process/')) {
|
|
89
96
|
return { repoName, resourceType: 'process', param: decodeURIComponent(rest.replace('process/', '')) };
|
|
90
97
|
}
|
|
98
|
+
if (rest.startsWith('derived-process/')) {
|
|
99
|
+
return { repoName, resourceType: 'derived-process', param: decodeURIComponent(rest.replace('derived-process/', '')) };
|
|
100
|
+
}
|
|
91
101
|
return { repoName, resourceType: rest };
|
|
92
102
|
}
|
|
93
103
|
throw new Error(`Unknown resource URI: ${uri}`);
|
|
@@ -119,6 +129,8 @@ export async function readResource(uri, backend) {
|
|
|
119
129
|
return getClusterDetailResource(parsed.param, backend, repoName);
|
|
120
130
|
case 'process':
|
|
121
131
|
return getProcessDetailResource(parsed.param, backend, repoName);
|
|
132
|
+
case 'derived-process':
|
|
133
|
+
return getDerivedProcessDetailResource(parsed.param, backend, repoName);
|
|
122
134
|
default:
|
|
123
135
|
throw new Error(`Unknown resource: ${uri}`);
|
|
124
136
|
}
|
|
@@ -202,6 +214,7 @@ async function getContextResource(backend, repoName) {
|
|
|
202
214
|
lines.push(` - gitnexus://repo/${context.projectName}/processes: All execution flows`);
|
|
203
215
|
lines.push(` - gitnexus://repo/${context.projectName}/cluster/{name}: Module details`);
|
|
204
216
|
lines.push(` - gitnexus://repo/${context.projectName}/process/{name}: Process trace`);
|
|
217
|
+
lines.push(` - gitnexus://repo/${context.projectName}/derived-process/{id}: Derived process trace metadata`);
|
|
205
218
|
return lines.join('\n');
|
|
206
219
|
}
|
|
207
220
|
/**
|