@veewo/gitnexus 1.5.0 → 1.5.1
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/agent-context/runner.js +3 -0
- package/dist/benchmark/agent-context/runner.test.js +22 -0
- package/dist/benchmark/agent-context/tool-runner.d.ts +7 -6
- package/dist/benchmark/agent-safe-query-context/io.d.ts +2 -0
- package/dist/benchmark/agent-safe-query-context/io.js +86 -0
- package/dist/benchmark/agent-safe-query-context/io.test.d.ts +1 -0
- package/dist/benchmark/agent-safe-query-context/io.test.js +13 -0
- package/dist/benchmark/agent-safe-query-context/report.d.ts +57 -0
- package/dist/benchmark/agent-safe-query-context/report.js +159 -0
- package/dist/benchmark/agent-safe-query-context/report.test.d.ts +1 -0
- package/dist/benchmark/agent-safe-query-context/report.test.js +362 -0
- package/dist/benchmark/agent-safe-query-context/runner.d.ts +44 -0
- package/dist/benchmark/agent-safe-query-context/runner.js +406 -0
- package/dist/benchmark/agent-safe-query-context/runner.test.d.ts +1 -0
- package/dist/benchmark/agent-safe-query-context/runner.test.js +290 -0
- package/dist/benchmark/agent-safe-query-context/semantic-tuple.d.ts +20 -0
- package/dist/benchmark/agent-safe-query-context/semantic-tuple.js +225 -0
- package/dist/benchmark/agent-safe-query-context/semantic-tuple.test.d.ts +1 -0
- package/dist/benchmark/agent-safe-query-context/semantic-tuple.test.js +122 -0
- package/dist/benchmark/agent-safe-query-context/subagent-live.d.ts +47 -0
- package/dist/benchmark/agent-safe-query-context/subagent-live.js +128 -0
- package/dist/benchmark/agent-safe-query-context/subagent-live.test.d.ts +1 -0
- package/dist/benchmark/agent-safe-query-context/subagent-live.test.js +155 -0
- package/dist/benchmark/agent-safe-query-context/telemetry-tool.d.ts +9 -0
- package/dist/benchmark/agent-safe-query-context/telemetry-tool.js +77 -0
- package/dist/benchmark/agent-safe-query-context/types.d.ts +61 -0
- package/dist/benchmark/agent-safe-query-context/types.js +8 -0
- package/dist/benchmark/runtime-poc/provenance-artifact.d.ts +47 -0
- package/dist/benchmark/runtime-poc/provenance-artifact.js +89 -0
- package/dist/benchmark/runtime-poc/runner.d.ts +31 -0
- package/dist/benchmark/runtime-poc/runner.js +163 -0
- package/dist/benchmark/u2-e2e/hydration-policy-repeatability-runner.d.ts +8 -0
- package/dist/benchmark/u2-e2e/hydration-policy-repeatability-runner.js +21 -0
- package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.d.ts +0 -1
- package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.js +53 -51
- package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.test.js +0 -1
- package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.d.ts +1 -1
- package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.js +82 -18
- package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.test.js +1 -2
- package/dist/benchmark/u2-e2e/retrieval-runner.js +15 -7
- package/dist/benchmark/u2-e2e/retrieval-runner.test.js +46 -0
- package/dist/cli/ai-context.js +2 -12
- package/dist/cli/ai-context.test.js +8 -0
- package/dist/cli/analyze-runtime-summary.js +1 -0
- package/dist/cli/analyze-runtime-summary.test.js +2 -0
- package/dist/cli/analyze-summary.d.ts +2 -0
- package/dist/cli/analyze-summary.js +24 -0
- package/dist/cli/analyze-summary.test.js +65 -1
- package/dist/cli/analyze.js +5 -1
- package/dist/cli/benchmark-agent-safe-query-context.d.ts +20 -0
- package/dist/cli/benchmark-agent-safe-query-context.js +39 -0
- package/dist/cli/benchmark-agent-safe-query-context.test.d.ts +1 -0
- package/dist/cli/benchmark-agent-safe-query-context.test.js +271 -0
- package/dist/cli/benchmark.d.ts +29 -0
- package/dist/cli/benchmark.js +55 -0
- package/dist/cli/index.js +23 -0
- package/dist/cli/rule-lab.d.ts +3 -7
- package/dist/cli/rule-lab.js +13 -22
- package/dist/cli/rule-lab.test.js +23 -3
- package/dist/cli/tool.d.ts +2 -0
- package/dist/cli/tool.js +2 -0
- package/dist/core/config/unity-config.d.ts +0 -1
- package/dist/core/config/unity-config.js +0 -1
- package/dist/core/ingestion/pipeline.js +35 -6
- package/dist/core/ingestion/unity-lifecycle-synthetic-calls.test.js +18 -20
- package/dist/core/ingestion/unity-parity-seed.d.ts +2 -1
- package/dist/core/ingestion/unity-parity-seed.js +8 -0
- package/dist/core/ingestion/unity-resource-processor.d.ts +11 -0
- package/dist/core/ingestion/unity-resource-processor.js +102 -0
- package/dist/core/ingestion/unity-resource-processor.test.js +449 -0
- package/dist/core/ingestion/unity-runtime-binding-rules.d.ts +15 -0
- package/dist/core/ingestion/unity-runtime-binding-rules.js +178 -30
- package/dist/core/lbug/csv-generator.test.js +2 -2
- package/dist/core/unity/doc-contract.test.d.ts +1 -0
- package/dist/core/unity/doc-contract.test.js +30 -0
- package/dist/core/unity/prefab-source-scan.d.ts +25 -0
- package/dist/core/unity/prefab-source-scan.js +152 -0
- package/dist/core/unity/prefab-source-scan.test.d.ts +1 -0
- package/dist/core/unity/prefab-source-scan.test.js +70 -0
- package/dist/core/unity/scan-context.d.ts +12 -0
- package/dist/core/unity/scan-context.js +50 -2
- package/dist/core/unity/scan-context.test.js +74 -0
- package/dist/mcp/local/agent-safe-response.d.ts +10 -0
- package/dist/mcp/local/agent-safe-response.js +639 -0
- package/dist/mcp/local/derived-process-reader.js +1 -1
- package/dist/mcp/local/local-backend.d.ts +18 -1
- package/dist/mcp/local/local-backend.js +319 -125
- package/dist/mcp/local/process-confidence.d.ts +1 -2
- package/dist/mcp/local/process-confidence.js +0 -3
- package/dist/mcp/local/process-confidence.test.js +4 -2
- package/dist/mcp/local/process-evidence.d.ts +1 -8
- package/dist/mcp/local/process-evidence.js +1 -23
- package/dist/mcp/local/process-evidence.test.js +2 -16
- package/dist/mcp/local/process-ref.d.ts +1 -1
- package/dist/mcp/local/runtime-chain-closure-evaluator.d.ts +33 -0
- package/dist/mcp/local/runtime-chain-closure-evaluator.js +273 -0
- package/dist/mcp/local/runtime-chain-graph-candidates.d.ts +23 -0
- package/dist/mcp/local/runtime-chain-graph-candidates.js +131 -0
- package/dist/mcp/local/runtime-chain-verify.d.ts +1 -1
- package/dist/mcp/local/runtime-chain-verify.js +149 -138
- package/dist/mcp/local/runtime-chain-verify.test.js +126 -68
- package/dist/mcp/local/runtime-claim-rule-registry.d.ts +4 -0
- package/dist/mcp/local/runtime-claim-rule-registry.js +4 -0
- package/dist/mcp/local/runtime-claim-rule-registry.test.js +37 -4
- package/dist/mcp/local/runtime-claim.d.ts +11 -0
- package/dist/mcp/local/runtime-claim.js +28 -0
- package/dist/mcp/local/unity-evidence-view.d.ts +1 -1
- package/dist/mcp/local/unity-evidence-view.js +1 -1
- package/dist/mcp/local/unity-evidence-view.test.js +22 -0
- package/dist/mcp/tools.js +51 -21
- package/dist/rule-lab/analyze.d.ts +2 -1
- package/dist/rule-lab/analyze.js +94 -59
- package/dist/rule-lab/analyze.test.js +238 -20
- package/dist/rule-lab/curate.d.ts +2 -1
- package/dist/rule-lab/curate.js +24 -3
- package/dist/rule-lab/curate.test.js +65 -0
- package/dist/rule-lab/curation-input-builder.d.ts +45 -0
- package/dist/rule-lab/curation-input-builder.js +133 -0
- package/dist/rule-lab/promote.js +80 -7
- package/dist/rule-lab/promote.test.js +150 -0
- package/dist/rule-lab/review-pack.d.ts +3 -0
- package/dist/rule-lab/review-pack.js +41 -1
- package/dist/rule-lab/review-pack.test.js +67 -0
- package/dist/rule-lab/types.d.ts +29 -0
- package/dist/types/pipeline.d.ts +3 -0
- package/package.json +3 -2
- package/scripts/run-node-tests.mjs +61 -0
- package/skills/_shared/unity-rule-authoring-contract.md +64 -0
- package/skills/_shared/unity-runtime-process-contract.md +16 -0
- package/skills/gitnexus-cli.md +8 -0
- package/skills/gitnexus-debugging.md +9 -0
- package/skills/gitnexus-exploring.md +66 -18
- package/skills/gitnexus-guide.md +42 -3
- package/skills/gitnexus-impact-analysis.md +8 -0
- package/skills/gitnexus-pr-review.md +8 -0
- package/skills/gitnexus-refactoring.md +8 -0
- package/skills/gitnexus-unity-rule-gen.md +66 -312
package/dist/rule-lab/promote.js
CHANGED
|
@@ -24,6 +24,10 @@ function isForbiddenPlaceholder(value) {
|
|
|
24
24
|
const token = toComparableToken(value);
|
|
25
25
|
return token === 'unknown' || token === 'todo' || token === 'tbd' || /<[^>]+>/.test(token);
|
|
26
26
|
}
|
|
27
|
+
function hasPlaceholderText(value) {
|
|
28
|
+
const raw = String(value || '').trim();
|
|
29
|
+
return !raw || /TODO|TBD|placeholder|<[^>]+>/i.test(raw);
|
|
30
|
+
}
|
|
27
31
|
function assertNoPlaceholderScope(values, field) {
|
|
28
32
|
if (values.length === 0) {
|
|
29
33
|
throw new Error(`promote lint failed: ${field} must be non-empty`);
|
|
@@ -32,6 +36,48 @@ function assertNoPlaceholderScope(values, field) {
|
|
|
32
36
|
throw new Error(`promote lint failed: unknown scope placeholder is forbidden (${field})`);
|
|
33
37
|
}
|
|
34
38
|
}
|
|
39
|
+
function assertResolvedBindings(resourceBindings, ruleId) {
|
|
40
|
+
if (!Array.isArray(resourceBindings) || resourceBindings.length === 0)
|
|
41
|
+
return;
|
|
42
|
+
const raw = JSON.stringify(resourceBindings);
|
|
43
|
+
if (/UnknownClass|UnknownMethod|UnknownSource|UnknownTarget|TODO|TBD|placeholder|<[^>]+>/i.test(raw)) {
|
|
44
|
+
throw new Error(`binding_unresolved: placeholder binding values are forbidden for rule ${ruleId}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function requireEvidenceGuard(item, ruleId) {
|
|
48
|
+
const steps = Array.isArray(item.confirmed_chain?.steps) ? item.confirmed_chain.steps : [];
|
|
49
|
+
if (steps.length === 0) {
|
|
50
|
+
throw new Error(`evidence_guard_failed: confirmed_chain.steps must be non-empty for rule ${ruleId}`);
|
|
51
|
+
}
|
|
52
|
+
for (let index = 0; index < steps.length; index += 1) {
|
|
53
|
+
const step = steps[index];
|
|
54
|
+
if (hasPlaceholderText(step.anchor)) {
|
|
55
|
+
throw new Error(`evidence_guard_failed: confirmed_chain.steps[${index}].anchor invalid for rule ${ruleId}`);
|
|
56
|
+
}
|
|
57
|
+
if (hasPlaceholderText(step.snippet)) {
|
|
58
|
+
throw new Error(`evidence_guard_failed: confirmed_chain.steps[${index}].snippet invalid for rule ${ruleId}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function isExactPairEventDelegateItem(item) {
|
|
63
|
+
const triggerTokens = Array.isArray(item.match?.trigger_tokens)
|
|
64
|
+
? item.match?.trigger_tokens || []
|
|
65
|
+
: [];
|
|
66
|
+
const hasEventDelegateTrigger = triggerTokens.some((token) => toComparableToken(token) === 'event_delegate');
|
|
67
|
+
if (!hasEventDelegateTrigger)
|
|
68
|
+
return false;
|
|
69
|
+
const hasCodeRuntimeTopology = Array.isArray(item.topology)
|
|
70
|
+
&& item.topology.some((hop) => toComparableToken(hop.hop) === 'code_runtime');
|
|
71
|
+
const hasEvidence = Array.isArray(item.confirmed_chain?.steps) && item.confirmed_chain.steps.length > 0;
|
|
72
|
+
return hasCodeRuntimeTopology && hasEvidence;
|
|
73
|
+
}
|
|
74
|
+
function requireBindingGuard(item, ruleId) {
|
|
75
|
+
const bindings = item.resource_bindings;
|
|
76
|
+
if (isExactPairEventDelegateItem(item) && (!Array.isArray(bindings) || bindings.length === 0)) {
|
|
77
|
+
throw new Error(`binding_unresolved: exact_pair_binding_missing for rule ${ruleId}`);
|
|
78
|
+
}
|
|
79
|
+
assertResolvedBindings(bindings, ruleId);
|
|
80
|
+
}
|
|
35
81
|
function toDraftFromCurated(item) {
|
|
36
82
|
const triggerTokens = unique(item.match?.trigger_tokens || [inferTriggerFamily(item)]);
|
|
37
83
|
const topology = Array.isArray(item.topology) && item.topology.length > 0
|
|
@@ -69,6 +115,9 @@ function toDraftFromCurated(item) {
|
|
|
69
115
|
non_guarantees: nonGuarantees,
|
|
70
116
|
next_action: nextAction,
|
|
71
117
|
},
|
|
118
|
+
...(Array.isArray(item.resource_bindings)
|
|
119
|
+
? { resource_bindings: item.resource_bindings }
|
|
120
|
+
: {}),
|
|
72
121
|
};
|
|
73
122
|
}
|
|
74
123
|
function compileRule(ruleId, version, draft) {
|
|
@@ -87,6 +136,7 @@ function compileRule(ruleId, version, draft) {
|
|
|
87
136
|
const nextAction = String(draft.claims.next_action || '').trim() || 'gitnexus query "runtime"';
|
|
88
137
|
assertNoPlaceholderScope(resourceTypes, 'resource_types');
|
|
89
138
|
assertNoPlaceholderScope(hostBaseType, 'host_base_type');
|
|
139
|
+
assertResolvedBindings(draft.resource_bindings, ruleId);
|
|
90
140
|
return {
|
|
91
141
|
id: ruleId,
|
|
92
142
|
version,
|
|
@@ -204,6 +254,16 @@ function buildRuleYaml(rule) {
|
|
|
204
254
|
lines.push(` field_name: ${quoteYaml(binding.field_name)}`);
|
|
205
255
|
if (binding.loader_methods?.length)
|
|
206
256
|
pushList(lines, 'loader_methods', binding.loader_methods, ' ');
|
|
257
|
+
if (binding.scene_name)
|
|
258
|
+
lines.push(` scene_name: ${quoteYaml(binding.scene_name)}`);
|
|
259
|
+
if (binding.source_class_pattern)
|
|
260
|
+
lines.push(` source_class_pattern: ${quoteYaml(binding.source_class_pattern)}`);
|
|
261
|
+
if (binding.source_method)
|
|
262
|
+
lines.push(` source_method: ${quoteYaml(binding.source_method)}`);
|
|
263
|
+
if (binding.target_class_pattern)
|
|
264
|
+
lines.push(` target_class_pattern: ${quoteYaml(binding.target_class_pattern)}`);
|
|
265
|
+
if (binding.target_method)
|
|
266
|
+
lines.push(` target_method: ${quoteYaml(binding.target_method)}`);
|
|
207
267
|
}
|
|
208
268
|
}
|
|
209
269
|
if (rule.lifecycle_overrides) {
|
|
@@ -233,6 +293,17 @@ async function readCatalog(catalogPath) {
|
|
|
233
293
|
throw error;
|
|
234
294
|
}
|
|
235
295
|
}
|
|
296
|
+
async function fileExists(filePath) {
|
|
297
|
+
try {
|
|
298
|
+
await fs.access(filePath);
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
catch (error) {
|
|
302
|
+
if (error?.code === 'ENOENT')
|
|
303
|
+
return false;
|
|
304
|
+
throw error;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
236
307
|
export async function promoteCuratedRules(input) {
|
|
237
308
|
const normalizedRepoPath = path.resolve(input.repoPath);
|
|
238
309
|
const paths = getRuleLabPaths(normalizedRepoPath, input.runId, input.sliceId);
|
|
@@ -262,8 +333,16 @@ export async function promoteCuratedRules(input) {
|
|
|
262
333
|
if (!ruleId) {
|
|
263
334
|
throw new Error('curated item missing rule id');
|
|
264
335
|
}
|
|
336
|
+
requireEvidenceGuard(item, ruleId);
|
|
337
|
+
requireBindingGuard(item, ruleId);
|
|
338
|
+
if (catalog.rules.some((entry) => entry.id === ruleId)) {
|
|
339
|
+
throw new Error(`duplicate_rule_id: ${ruleId} already exists in catalog`);
|
|
340
|
+
}
|
|
265
341
|
const relativeFile = path.join('approved', `${ruleId}.yaml`).split(path.sep).join('/');
|
|
266
342
|
const absoluteFile = path.join(paths.rulesRoot, relativeFile);
|
|
343
|
+
if (await fileExists(absoluteFile)) {
|
|
344
|
+
throw new Error(`duplicate_rule_id: ${ruleId} already exists at ${relativeFile}`);
|
|
345
|
+
}
|
|
267
346
|
const draft = dslDraftFromCurate && curatedItems.length === 1
|
|
268
347
|
? { ...dslDraftFromCurate, id: ruleId, version }
|
|
269
348
|
: { ...toDraftFromCurated(item), id: ruleId, version };
|
|
@@ -279,13 +358,7 @@ export async function promoteCuratedRules(input) {
|
|
|
279
358
|
file: relativeFile,
|
|
280
359
|
...(compiledRule.family ? { family: compiledRule.family } : {}),
|
|
281
360
|
};
|
|
282
|
-
|
|
283
|
-
if (existingIndex >= 0) {
|
|
284
|
-
catalog.rules[existingIndex] = nextEntry;
|
|
285
|
-
}
|
|
286
|
-
else {
|
|
287
|
-
catalog.rules.push(nextEntry);
|
|
288
|
-
}
|
|
361
|
+
catalog.rules.push(nextEntry);
|
|
289
362
|
}
|
|
290
363
|
await fs.mkdir(path.dirname(catalogPath), { recursive: true });
|
|
291
364
|
await fs.writeFile(catalogPath, `${JSON.stringify(catalog, null, 2)}\n`, 'utf-8');
|
|
@@ -3,6 +3,7 @@ import fs from 'node:fs/promises';
|
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { promoteCuratedRules } from './promote.js';
|
|
6
|
+
import { loadRuleRegistry } from '../mcp/local/runtime-claim-rule-registry.js';
|
|
6
7
|
describe('rule-lab promote', () => {
|
|
7
8
|
it('promotes curated candidate into approved yaml and catalog entry', async () => {
|
|
8
9
|
const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'rule-lab-promote-'));
|
|
@@ -161,4 +162,153 @@ describe('rule-lab promote', () => {
|
|
|
161
162
|
await expect(promoteCuratedRules({ repoPath: repoRoot, runId: 'run-x', sliceId: 'slice-a' })).rejects.toThrow(/unknown/i);
|
|
162
163
|
await fs.rm(repoRoot, { recursive: true, force: true });
|
|
163
164
|
});
|
|
165
|
+
it('promotes every curated item when dsl-drafts includes multiple candidates', async () => {
|
|
166
|
+
const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'rule-lab-promote-multi-'));
|
|
167
|
+
const rulesRoot = path.join(repoRoot, '.gitnexus', 'rules');
|
|
168
|
+
const sliceDir = path.join(rulesRoot, 'lab', 'runs', 'run-x', 'slices', 'slice-a');
|
|
169
|
+
await fs.mkdir(path.join(rulesRoot, 'approved'), { recursive: true });
|
|
170
|
+
await fs.mkdir(sliceDir, { recursive: true });
|
|
171
|
+
await fs.writeFile(path.join(rulesRoot, 'catalog.json'), JSON.stringify({ version: 1, rules: [] }, null, 2), 'utf-8');
|
|
172
|
+
const curated = {
|
|
173
|
+
run_id: 'run-x',
|
|
174
|
+
slice_id: 'slice-a',
|
|
175
|
+
curated: [
|
|
176
|
+
{
|
|
177
|
+
id: 'candidate-1',
|
|
178
|
+
rule_id: 'demo.multi.first.v1',
|
|
179
|
+
title: 'demo first',
|
|
180
|
+
match: { trigger_tokens: ['reload'], resource_types: ['syncvar_hook'], host_base_type: ['network_behaviour'] },
|
|
181
|
+
topology: [{ hop: 'code_runtime', from: { entity: 'script' }, to: { entity: 'runtime' }, edge: { kind: 'calls' } }],
|
|
182
|
+
closure: { required_hops: ['code_runtime'], failure_map: { missing_evidence: 'rule_matched_but_evidence_missing' } },
|
|
183
|
+
claims: { guarantees: ['reload_chain_closed'], non_guarantees: ['no_runtime_execution'], next_action: 'gitnexus query "reload"' },
|
|
184
|
+
confirmed_chain: { steps: [{ hop_type: 'code_runtime', anchor: 'Assets/A.cs:1', snippet: 'A' }] },
|
|
185
|
+
guarantees: ['reload_chain_closed'],
|
|
186
|
+
non_guarantees: ['no_runtime_execution'],
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
id: 'candidate-2',
|
|
190
|
+
rule_id: 'demo.multi.second.v1',
|
|
191
|
+
title: 'demo second',
|
|
192
|
+
match: { trigger_tokens: ['reload'], resource_types: ['syncvar_hook'], host_base_type: ['network_behaviour'] },
|
|
193
|
+
topology: [{ hop: 'code_runtime', from: { entity: 'script' }, to: { entity: 'runtime' }, edge: { kind: 'calls' } }],
|
|
194
|
+
closure: { required_hops: ['code_runtime'], failure_map: { missing_evidence: 'rule_matched_but_evidence_missing' } },
|
|
195
|
+
claims: { guarantees: ['reload_chain_closed'], non_guarantees: ['no_runtime_execution'], next_action: 'gitnexus query "reload"' },
|
|
196
|
+
confirmed_chain: { steps: [{ hop_type: 'code_runtime', anchor: 'Assets/B.cs:2', snippet: 'B' }] },
|
|
197
|
+
guarantees: ['reload_chain_closed'],
|
|
198
|
+
non_guarantees: ['no_runtime_execution'],
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
};
|
|
202
|
+
await fs.writeFile(path.join(sliceDir, 'curated.json'), JSON.stringify(curated, null, 2), 'utf-8');
|
|
203
|
+
await fs.writeFile(path.join(sliceDir, 'dsl-drafts.json'), JSON.stringify({
|
|
204
|
+
drafts: [
|
|
205
|
+
{
|
|
206
|
+
id: 'demo.multi.first.v1',
|
|
207
|
+
version: '2.0.0',
|
|
208
|
+
match: curated.curated[0].match,
|
|
209
|
+
topology: curated.curated[0].topology,
|
|
210
|
+
closure: curated.curated[0].closure,
|
|
211
|
+
claims: curated.curated[0].claims,
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
id: 'demo.multi.second.v1',
|
|
215
|
+
version: '2.0.0',
|
|
216
|
+
match: curated.curated[1].match,
|
|
217
|
+
topology: curated.curated[1].topology,
|
|
218
|
+
closure: curated.curated[1].closure,
|
|
219
|
+
claims: curated.curated[1].claims,
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
}, null, 2), 'utf-8');
|
|
223
|
+
await fs.writeFile(path.join(sliceDir, 'dsl-draft.json'), JSON.stringify({
|
|
224
|
+
compatibility_warning: 'multi-draft compatibility alias',
|
|
225
|
+
primary_draft_id: 'demo.multi.first.v1',
|
|
226
|
+
}, null, 2), 'utf-8');
|
|
227
|
+
const out = await promoteCuratedRules({ repoPath: repoRoot, runId: 'run-x', sliceId: 'slice-a' });
|
|
228
|
+
expect(out.promotedFiles).toHaveLength(2);
|
|
229
|
+
await fs.rm(repoRoot, { recursive: true, force: true });
|
|
230
|
+
});
|
|
231
|
+
it('writes all binding fields into yaml and keeps them parseable via loadRuleRegistry', async () => {
|
|
232
|
+
const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'rule-lab-promote-bindings-roundtrip-'));
|
|
233
|
+
const rulesRoot = path.join(repoRoot, '.gitnexus', 'rules');
|
|
234
|
+
const sliceDir = path.join(rulesRoot, 'lab', 'runs', 'run-x', 'slices', 'slice-a');
|
|
235
|
+
await fs.mkdir(path.join(rulesRoot, 'approved'), { recursive: true });
|
|
236
|
+
await fs.mkdir(sliceDir, { recursive: true });
|
|
237
|
+
await fs.writeFile(path.join(rulesRoot, 'catalog.json'), JSON.stringify({ version: 1, rules: [] }, null, 2), 'utf-8');
|
|
238
|
+
await fs.writeFile(path.join(sliceDir, 'curated.json'), JSON.stringify({
|
|
239
|
+
run_id: 'run-x',
|
|
240
|
+
slice_id: 'slice-a',
|
|
241
|
+
curated: [
|
|
242
|
+
{
|
|
243
|
+
id: 'candidate-1',
|
|
244
|
+
rule_id: 'demo.bindings.roundtrip.v1',
|
|
245
|
+
title: 'demo bindings',
|
|
246
|
+
match: {
|
|
247
|
+
trigger_tokens: ['reload'],
|
|
248
|
+
resource_types: ['syncvar_hook'],
|
|
249
|
+
host_base_type: ['network_behaviour'],
|
|
250
|
+
},
|
|
251
|
+
topology: [
|
|
252
|
+
{ hop: 'code_runtime', from: { entity: 'script' }, to: { entity: 'runtime' }, edge: { kind: 'calls' } },
|
|
253
|
+
],
|
|
254
|
+
closure: {
|
|
255
|
+
required_hops: ['code_runtime'],
|
|
256
|
+
failure_map: { missing_evidence: 'rule_matched_but_evidence_missing' },
|
|
257
|
+
},
|
|
258
|
+
claims: {
|
|
259
|
+
guarantees: ['binding_fields_roundtrip'],
|
|
260
|
+
non_guarantees: ['no_runtime_execution'],
|
|
261
|
+
next_action: 'gitnexus query "reload"',
|
|
262
|
+
},
|
|
263
|
+
confirmed_chain: {
|
|
264
|
+
steps: [{ hop_type: 'code_runtime', anchor: 'Assets/Demo.cs:12', snippet: 'Demo.Trigger' }],
|
|
265
|
+
},
|
|
266
|
+
guarantees: ['binding_fields_roundtrip'],
|
|
267
|
+
non_guarantees: ['no_runtime_execution'],
|
|
268
|
+
resource_bindings: [
|
|
269
|
+
{
|
|
270
|
+
kind: 'method_triggers_scene_load',
|
|
271
|
+
host_class_pattern: 'BattleController',
|
|
272
|
+
loader_methods: ['EnterBattle'],
|
|
273
|
+
scene_name: 'BattleScene',
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
kind: 'method_triggers_method',
|
|
277
|
+
source_class_pattern: 'SourceClass',
|
|
278
|
+
source_method: 'Emit',
|
|
279
|
+
target_class_pattern: 'TargetClass',
|
|
280
|
+
target_method: 'Handle',
|
|
281
|
+
},
|
|
282
|
+
],
|
|
283
|
+
},
|
|
284
|
+
],
|
|
285
|
+
}, null, 2), 'utf-8');
|
|
286
|
+
const out = await promoteCuratedRules({ repoPath: repoRoot, runId: 'run-x', sliceId: 'slice-a', version: '1.0.0' });
|
|
287
|
+
const yaml = await fs.readFile(out.promotedFiles[0], 'utf-8');
|
|
288
|
+
expect(yaml).toContain('scene_name: BattleScene');
|
|
289
|
+
expect(yaml).toContain('host_class_pattern: BattleController');
|
|
290
|
+
expect(yaml).toContain('loader_methods:');
|
|
291
|
+
expect(yaml).toContain('- EnterBattle');
|
|
292
|
+
expect(yaml).toContain('target_class_pattern: TargetClass');
|
|
293
|
+
expect(yaml).toContain('target_method: Handle');
|
|
294
|
+
await fs.rm(path.join(rulesRoot, 'compiled'), { recursive: true, force: true });
|
|
295
|
+
const registry = await loadRuleRegistry(repoRoot);
|
|
296
|
+
const rule = registry.activeRules.find((item) => item.id === 'demo.bindings.roundtrip.v1');
|
|
297
|
+
expect(rule).toBeTruthy();
|
|
298
|
+
expect(rule?.resource_bindings).toBeDefined();
|
|
299
|
+
expect(rule?.resource_bindings?.[0].kind).toBe('method_triggers_scene_load');
|
|
300
|
+
expect(rule?.resource_bindings?.[0].host_class_pattern).toBe('BattleController');
|
|
301
|
+
expect(rule?.resource_bindings?.[0].loader_methods).toEqual(['EnterBattle']);
|
|
302
|
+
expect(rule?.resource_bindings?.[0].scene_name).toBe('BattleScene');
|
|
303
|
+
expect(rule?.resource_bindings?.[0].source_class_pattern).toBeUndefined();
|
|
304
|
+
expect(rule?.resource_bindings?.[0].source_method).toBeUndefined();
|
|
305
|
+
expect(rule?.resource_bindings?.[0].target_class_pattern).toBeUndefined();
|
|
306
|
+
expect(rule?.resource_bindings?.[0].target_method).toBeUndefined();
|
|
307
|
+
expect(rule?.resource_bindings?.[1].kind).toBe('method_triggers_method');
|
|
308
|
+
expect(rule?.resource_bindings?.[1].source_class_pattern).toBe('SourceClass');
|
|
309
|
+
expect(rule?.resource_bindings?.[1].source_method).toBe('Emit');
|
|
310
|
+
expect(rule?.resource_bindings?.[1].target_class_pattern).toBe('TargetClass');
|
|
311
|
+
expect(rule?.resource_bindings?.[1].target_method).toBe('Handle');
|
|
312
|
+
await fs.rm(repoRoot, { recursive: true, force: true });
|
|
313
|
+
});
|
|
164
314
|
});
|
|
@@ -14,6 +14,9 @@ export interface ReviewPackCard {
|
|
|
14
14
|
failure_map: Record<string, string>;
|
|
15
15
|
guarantees: string[];
|
|
16
16
|
non_guarantees: string[];
|
|
17
|
+
draft_rule_ids: string[];
|
|
18
|
+
aggregation_modes: string[];
|
|
19
|
+
binding_kinds: string[];
|
|
17
20
|
};
|
|
18
21
|
}
|
|
19
22
|
export interface ReviewPackMeta {
|
|
@@ -49,6 +49,15 @@ function collectClaims(candidates) {
|
|
|
49
49
|
non_guarantees: nonGuarantees,
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
|
+
function collectDraftRuleIds(candidates) {
|
|
53
|
+
return unique(candidates.map((candidate) => String(candidate.draft_rule_id || '').trim()).filter(Boolean));
|
|
54
|
+
}
|
|
55
|
+
function collectAggregationModes(candidates) {
|
|
56
|
+
return unique(candidates.map((candidate) => String(candidate.aggregation_mode || '').trim()).filter(Boolean));
|
|
57
|
+
}
|
|
58
|
+
function collectBindingKinds(candidates) {
|
|
59
|
+
return unique(candidates.map((candidate) => String(candidate.binding_kind || '').trim()).filter(Boolean));
|
|
60
|
+
}
|
|
52
61
|
function buildCards(candidates) {
|
|
53
62
|
const cards = [];
|
|
54
63
|
const chunkSize = 4;
|
|
@@ -64,6 +73,9 @@ function buildCards(candidates) {
|
|
|
64
73
|
failure_map: mergeFailureMaps(chunk),
|
|
65
74
|
guarantees: claims.guarantees,
|
|
66
75
|
non_guarantees: claims.non_guarantees,
|
|
76
|
+
draft_rule_ids: collectDraftRuleIds(chunk),
|
|
77
|
+
aggregation_modes: collectAggregationModes(chunk),
|
|
78
|
+
binding_kinds: collectBindingKinds(chunk),
|
|
67
79
|
},
|
|
68
80
|
});
|
|
69
81
|
}
|
|
@@ -87,15 +99,43 @@ function renderReviewPack(meta, cards) {
|
|
|
87
99
|
lines.push(`- required_hops: ${card.decision_inputs.required_hops.join(', ')}`);
|
|
88
100
|
lines.push(`- guarantees: ${card.decision_inputs.guarantees.join(', ')}`);
|
|
89
101
|
lines.push(`- non_guarantees: ${card.decision_inputs.non_guarantees.join(', ')}`);
|
|
102
|
+
lines.push(`- draft_rule_ids: ${card.decision_inputs.draft_rule_ids.join(', ')}`);
|
|
103
|
+
lines.push(`- aggregation_modes: ${card.decision_inputs.aggregation_modes.join(', ')}`);
|
|
104
|
+
lines.push(`- binding_kinds: ${card.decision_inputs.binding_kinds.join(', ')}`);
|
|
90
105
|
lines.push(`- failure_map: ${JSON.stringify(card.decision_inputs.failure_map)}`);
|
|
91
106
|
lines.push('');
|
|
92
107
|
}
|
|
93
108
|
return `${lines.join('\n')}\n`;
|
|
94
109
|
}
|
|
110
|
+
function isENOENT(error) {
|
|
111
|
+
return typeof error === 'object' && error !== null && error.code === 'ENOENT';
|
|
112
|
+
}
|
|
113
|
+
function sleep(ms) {
|
|
114
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
115
|
+
}
|
|
116
|
+
async function readCandidatesFileWithRetry(candidatesPath, analyzeCommandHint, timeoutMs = 3000, intervalMs = 100) {
|
|
117
|
+
const deadline = Date.now() + timeoutMs;
|
|
118
|
+
while (true) {
|
|
119
|
+
try {
|
|
120
|
+
return await fs.readFile(candidatesPath, 'utf-8');
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
if (!isENOENT(error))
|
|
124
|
+
throw error;
|
|
125
|
+
if (Date.now() >= deadline) {
|
|
126
|
+
throw new Error(`Missing candidates file for review-pack: ${candidatesPath}\n` +
|
|
127
|
+
`Run analyze first and wait for completion, then retry review-pack.\n` +
|
|
128
|
+
`Suggested command: ${analyzeCommandHint}`);
|
|
129
|
+
}
|
|
130
|
+
await sleep(intervalMs);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
95
134
|
export async function buildReviewPack(input) {
|
|
96
135
|
const normalizedRepoPath = path.resolve(input.repoPath);
|
|
97
136
|
const paths = getRuleLabPaths(normalizedRepoPath, input.runId, input.sliceId);
|
|
98
|
-
const
|
|
137
|
+
const analyzeCommandHint = `gitnexus rule-lab analyze --repo-path "${normalizedRepoPath}" --run-id "${input.runId}" --slice-id "${input.sliceId}"`;
|
|
138
|
+
const raw = await readCandidatesFileWithRetry(paths.candidatesPath, analyzeCommandHint);
|
|
99
139
|
const candidates = parseCandidates(raw);
|
|
100
140
|
const included = [];
|
|
101
141
|
let tokenEstimate = 0;
|
|
@@ -46,4 +46,71 @@ describe('rule-lab review-pack', () => {
|
|
|
46
46
|
expect(persisted).toContain('token_budget_estimate');
|
|
47
47
|
await fs.rm(repoRoot, { recursive: true, force: true });
|
|
48
48
|
});
|
|
49
|
+
it('shows actionable guidance when candidates are missing', async () => {
|
|
50
|
+
const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'rule-lab-review-pack-missing-'));
|
|
51
|
+
const sliceDir = path.join(repoRoot, '.gitnexus', 'rules', 'lab', 'runs', 'run-y', 'slices', 'slice-b');
|
|
52
|
+
await fs.mkdir(sliceDir, { recursive: true });
|
|
53
|
+
const err = await buildReviewPack({ repoPath: repoRoot, runId: 'run-y', sliceId: 'slice-b', maxTokens: 6000 })
|
|
54
|
+
.then(() => null)
|
|
55
|
+
.catch((error) => error);
|
|
56
|
+
expect(err).toBeTruthy();
|
|
57
|
+
expect(String(err?.message || '')).toMatch(/Missing candidates file for review-pack/);
|
|
58
|
+
expect(String(err?.message || '')).toMatch(/rule-lab analyze --repo-path/);
|
|
59
|
+
await fs.rm(repoRoot, { recursive: true, force: true });
|
|
60
|
+
});
|
|
61
|
+
it('waits briefly for candidates to avoid analyze/review-pack races', async () => {
|
|
62
|
+
const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'rule-lab-review-pack-race-'));
|
|
63
|
+
const sliceDir = path.join(repoRoot, '.gitnexus', 'rules', 'lab', 'runs', 'run-z', 'slices', 'slice-c');
|
|
64
|
+
await fs.mkdir(sliceDir, { recursive: true });
|
|
65
|
+
const candidatesPath = path.join(sliceDir, 'candidates.jsonl');
|
|
66
|
+
const line = JSON.stringify({
|
|
67
|
+
id: 'cand-race',
|
|
68
|
+
title: 'race candidate',
|
|
69
|
+
topology: [{ hop: 'resource', from: { entity: 'resource' }, to: { entity: 'script' }, edge: { kind: 'binds_script' } }],
|
|
70
|
+
evidence: { hops: [{ hop_type: 'resource', anchor: 'Assets/Test.prefab:1', snippet: 'Race' }] },
|
|
71
|
+
});
|
|
72
|
+
const delayedWrite = new Promise((resolve, reject) => {
|
|
73
|
+
setTimeout(() => {
|
|
74
|
+
fs.writeFile(candidatesPath, `${line}\n`, 'utf-8').then(() => resolve()).catch(reject);
|
|
75
|
+
}, 150);
|
|
76
|
+
});
|
|
77
|
+
const out = await buildReviewPack({ repoPath: repoRoot, runId: 'run-z', sliceId: 'slice-c', maxTokens: 6000 });
|
|
78
|
+
await delayedWrite;
|
|
79
|
+
expect(out.meta.total_candidates).toBe(1);
|
|
80
|
+
await fs.rm(repoRoot, { recursive: true, force: true });
|
|
81
|
+
});
|
|
82
|
+
it('renders decision inputs without lineage-only fields', async () => {
|
|
83
|
+
const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'rule-lab-review-pack-direct-'));
|
|
84
|
+
const sliceDir = path.join(repoRoot, '.gitnexus', 'rules', 'lab', 'runs', 'run-h', 'slices', 'slice-h');
|
|
85
|
+
await fs.mkdir(sliceDir, { recursive: true });
|
|
86
|
+
const rows = [
|
|
87
|
+
{
|
|
88
|
+
id: 'proposal-1',
|
|
89
|
+
title: 'event_delegate proposal accepted-a',
|
|
90
|
+
proposal_kind: 'per_anchor_rule',
|
|
91
|
+
aggregation_mode: 'per_anchor_rules',
|
|
92
|
+
draft_rule_id: 'unity.event.netplayer-gameover-syncvar-hook-ondeadchange.v1',
|
|
93
|
+
binding_kind: 'method_triggers_method',
|
|
94
|
+
topology: [{ hop: 'code_runtime', from: { entity: 'script' }, to: { entity: 'runtime' }, edge: { kind: 'calls' } }],
|
|
95
|
+
evidence: { hops: [{ hop_type: 'code_runtime', anchor: 'Assets/A.cs:1', snippet: 'A' }] },
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
id: 'proposal-2',
|
|
99
|
+
title: 'event_delegate proposal accepted-b',
|
|
100
|
+
proposal_kind: 'per_anchor_rule',
|
|
101
|
+
aggregation_mode: 'per_anchor_rules',
|
|
102
|
+
draft_rule_id: 'unity.event.mirrorbattlemgr-createnetplayer-syncvar-hook-changeroomgrid.v1',
|
|
103
|
+
binding_kind: 'method_triggers_method',
|
|
104
|
+
topology: [{ hop: 'code_runtime', from: { entity: 'script' }, to: { entity: 'runtime' }, edge: { kind: 'calls' } }],
|
|
105
|
+
evidence: { hops: [{ hop_type: 'code_runtime', anchor: 'Assets/B.cs:2', snippet: 'B' }] },
|
|
106
|
+
},
|
|
107
|
+
];
|
|
108
|
+
await fs.writeFile(path.join(sliceDir, 'candidates.jsonl'), `${rows.map((row) => JSON.stringify(row)).join('\n')}\n`, 'utf-8');
|
|
109
|
+
const out = await buildReviewPack({ repoPath: repoRoot, runId: 'run-h', sliceId: 'slice-h', maxTokens: 6000 });
|
|
110
|
+
const persisted = await fs.readFile(out.paths.reviewCardsPath, 'utf-8');
|
|
111
|
+
expect(persisted).not.toContain('Handoff Summary');
|
|
112
|
+
expect(persisted).not.toContain('source_gap_candidate_ids');
|
|
113
|
+
expect(persisted).toContain('draft_rule_ids: unity.event.netplayer-gameover-syncvar-hook-ondeadchange.v1, unity.event.mirrorbattlemgr-createnetplayer-syncvar-hook-changeroomgrid.v1');
|
|
114
|
+
await fs.rm(repoRoot, { recursive: true, force: true });
|
|
115
|
+
});
|
|
49
116
|
});
|
package/dist/rule-lab/types.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export interface RuleLabSlice {
|
|
|
6
6
|
resource_types: string[];
|
|
7
7
|
host_base_type: string[];
|
|
8
8
|
required_hops?: string[];
|
|
9
|
+
exact_pairs?: RuleLabExactPair[];
|
|
9
10
|
}
|
|
10
11
|
export interface RuleLabManifest {
|
|
11
12
|
run_id: string;
|
|
@@ -25,6 +26,11 @@ export interface RuleLabCandidate {
|
|
|
25
26
|
id: string;
|
|
26
27
|
title: string;
|
|
27
28
|
rule_hint?: string;
|
|
29
|
+
proposal_kind?: 'per_anchor_rule' | 'aggregate_rule';
|
|
30
|
+
source_slice_id?: string;
|
|
31
|
+
aggregation_mode?: 'per_anchor_rules' | 'aggregate_single_rule';
|
|
32
|
+
binding_kind?: string;
|
|
33
|
+
draft_rule_id?: string;
|
|
28
34
|
topology?: Array<{
|
|
29
35
|
hop: string;
|
|
30
36
|
from: Record<string, unknown>;
|
|
@@ -46,10 +52,33 @@ export interface RuleLabCandidate {
|
|
|
46
52
|
missing_hop?: string;
|
|
47
53
|
evidence_anchor?: string;
|
|
48
54
|
}>;
|
|
55
|
+
closure?: {
|
|
56
|
+
required_hops: string[];
|
|
57
|
+
failure_map: Record<string, RuntimeClaimFailureReason | string>;
|
|
58
|
+
};
|
|
59
|
+
claims?: {
|
|
60
|
+
guarantees: string[];
|
|
61
|
+
non_guarantees: string[];
|
|
62
|
+
next_action: string;
|
|
63
|
+
};
|
|
64
|
+
proposal_evidence_keys?: string[];
|
|
65
|
+
exact_pair?: RuleLabExactPair;
|
|
49
66
|
evidence: {
|
|
50
67
|
hops: RuleLabCandidateHop[];
|
|
51
68
|
};
|
|
52
69
|
}
|
|
70
|
+
export interface RuleLabExactPairAnchor {
|
|
71
|
+
file: string;
|
|
72
|
+
line?: number;
|
|
73
|
+
symbol?: string;
|
|
74
|
+
}
|
|
75
|
+
export interface RuleLabExactPair {
|
|
76
|
+
id?: string;
|
|
77
|
+
binding_kind?: UnityResourceBinding['kind'];
|
|
78
|
+
draft_rule_id?: string;
|
|
79
|
+
source_anchor: RuleLabExactPairAnchor;
|
|
80
|
+
target_anchor: RuleLabExactPairAnchor;
|
|
81
|
+
}
|
|
53
82
|
export interface RuleDslMatch {
|
|
54
83
|
trigger_tokens: string[];
|
|
55
84
|
symbol_kind?: string[];
|
package/dist/types/pipeline.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { GraphNode, GraphRelationship, KnowledgeGraph } from '../core/graph/type
|
|
|
2
2
|
import { CommunityDetectionResult } from '../core/ingestion/community-processor.js';
|
|
3
3
|
import { ProcessDetectionResult } from '../core/ingestion/process-processor.js';
|
|
4
4
|
import type { UnityResourceProcessingResult } from '../core/ingestion/unity-resource-processor.js';
|
|
5
|
+
import type { UnityRuntimeBindingResult } from '../core/ingestion/unity-runtime-binding-rules.js';
|
|
5
6
|
import type { ScopeSelectionDiagnostics } from '../core/ingestion/scope-filter.js';
|
|
6
7
|
export type PipelinePhase = 'idle' | 'extracting' | 'structure' | 'parsing' | 'imports' | 'calls' | 'heritage' | 'communities' | 'processes' | 'enriching' | 'complete' | 'error';
|
|
7
8
|
export interface PipelineProgress {
|
|
@@ -39,6 +40,7 @@ export interface PipelineResult {
|
|
|
39
40
|
communityResult?: CommunityDetectionResult;
|
|
40
41
|
processResult?: ProcessDetectionResult;
|
|
41
42
|
unityResult?: UnityResourceProcessingResult;
|
|
43
|
+
unityRuleBindingResult?: UnityRuntimeBindingResult;
|
|
42
44
|
scopeDiagnostics?: ScopeSelectionDiagnostics;
|
|
43
45
|
csharpPreprocDiagnostics?: CSharpPreprocDiagnostics;
|
|
44
46
|
}
|
|
@@ -47,6 +49,7 @@ export interface PipelineRuntimeSummary {
|
|
|
47
49
|
communityResult?: CommunityDetectionResult;
|
|
48
50
|
processResult?: ProcessDetectionResult;
|
|
49
51
|
unityResult?: UnityResourceProcessingResult;
|
|
52
|
+
unityRuleBindingResult?: UnityRuntimeBindingResult;
|
|
50
53
|
scopeDiagnostics?: ScopeSelectionDiagnostics;
|
|
51
54
|
csharpPreprocDiagnostics?: CSharpPreprocDiagnostics;
|
|
52
55
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veewo/gitnexus",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.1",
|
|
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",
|
|
@@ -41,7 +41,8 @@
|
|
|
41
41
|
"dev": "tsx watch src/cli/index.ts",
|
|
42
42
|
"test": "vitest run test/unit",
|
|
43
43
|
"test:integration": "vitest run test/integration",
|
|
44
|
-
"test:
|
|
44
|
+
"test:src:node": "npm run build && node scripts/run-node-tests.mjs",
|
|
45
|
+
"test:all": "vitest run && npm run test:src:node",
|
|
45
46
|
"test:watch": "vitest",
|
|
46
47
|
"test:coverage": "vitest run --coverage",
|
|
47
48
|
"prepare": "npm run build",
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { readdirSync, readFileSync, existsSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
const repoRoot = path.resolve(__dirname, '..');
|
|
9
|
+
const srcRoot = path.join(repoRoot, 'src');
|
|
10
|
+
const distRoot = path.join(repoRoot, 'dist');
|
|
11
|
+
|
|
12
|
+
function walk(dir, out) {
|
|
13
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
14
|
+
const full = path.join(dir, entry.name);
|
|
15
|
+
if (entry.isDirectory()) {
|
|
16
|
+
walk(full, out);
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (entry.isFile() && entry.name.endsWith('.test.ts')) {
|
|
20
|
+
out.push(full);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const sourceTests = [];
|
|
26
|
+
walk(srcRoot, sourceTests);
|
|
27
|
+
|
|
28
|
+
const nodeTests = sourceTests
|
|
29
|
+
.filter((filePath) => {
|
|
30
|
+
const content = readFileSync(filePath, 'utf8');
|
|
31
|
+
return content.includes("from 'node:test'") || content.includes('from "node:test"');
|
|
32
|
+
})
|
|
33
|
+
.map((filePath) => {
|
|
34
|
+
const rel = path.relative(srcRoot, filePath).replace(/\.ts$/, '.js');
|
|
35
|
+
return path.join(distRoot, rel);
|
|
36
|
+
})
|
|
37
|
+
.sort();
|
|
38
|
+
|
|
39
|
+
if (nodeTests.length === 0) {
|
|
40
|
+
console.error('No node:test suites detected under src/**/*.test.ts');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const missing = nodeTests.filter((filePath) => !existsSync(filePath));
|
|
45
|
+
if (missing.length > 0) {
|
|
46
|
+
console.error('Missing compiled node:test files in dist/. Run build first.');
|
|
47
|
+
for (const filePath of missing.slice(0, 20)) {
|
|
48
|
+
console.error(`- ${filePath}`);
|
|
49
|
+
}
|
|
50
|
+
if (missing.length > 20) {
|
|
51
|
+
console.error(`... and ${missing.length - 20} more`);
|
|
52
|
+
}
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const result = spawnSync(process.execPath, ['--test', ...nodeTests], {
|
|
57
|
+
stdio: 'inherit',
|
|
58
|
+
cwd: repoRoot,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
process.exit(result.status ?? 1);
|