@veewo/gitnexus 1.5.7 → 1.5.9
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/cli/ai-context.js +1 -7
- package/dist/cli/analyze-options.d.ts +4 -0
- package/dist/cli/analyze-options.js +14 -1
- package/dist/cli/analyze-options.test.js +23 -0
- package/dist/cli/analyze-runtime-summary.js +0 -1
- package/dist/cli/analyze-runtime-summary.test.js +0 -2
- package/dist/cli/analyze-summary.d.ts +0 -2
- package/dist/cli/analyze-summary.js +0 -24
- package/dist/cli/analyze-summary.test.js +1 -65
- package/dist/cli/analyze.d.ts +1 -0
- package/dist/cli/analyze.js +26 -18
- package/dist/cli/clean.js +23 -2
- package/dist/cli/index.js +3 -3
- package/dist/cli/repo-manager-alias.test.js +2 -0
- package/dist/core/ingestion/pipeline.js +0 -43
- package/dist/core/ingestion/tree-sitter-queries.d.ts +1 -1
- package/dist/core/ingestion/tree-sitter-queries.js +3 -3
- package/dist/mcp/local/agent-safe-response.js +1 -1
- package/dist/mcp/local/local-backend.d.ts +0 -23
- package/dist/mcp/local/local-backend.js +69 -248
- package/dist/mcp/local/runtime-chain-verify.test.js +0 -49
- package/dist/mcp/local/runtime-claim-rule-registry.d.ts +0 -11
- package/dist/mcp/local/runtime-claim-rule-registry.js +0 -159
- package/dist/mcp/local/runtime-claim-rule-registry.test.js +67 -214
- package/dist/mcp/tools.js +0 -70
- package/dist/storage/repo-manager.d.ts +1 -0
- package/dist/types/pipeline.d.ts +0 -3
- package/package.json +4 -4
- package/skills/gitnexus-cli.md +5 -2
- package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.d.ts +0 -60
- package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.js +0 -395
- package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.test.d.ts +0 -1
- package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.test.js +0 -41
- package/dist/cli/rule-lab.d.ts +0 -38
- package/dist/cli/rule-lab.js +0 -148
- package/dist/cli/rule-lab.test.d.ts +0 -1
- package/dist/cli/rule-lab.test.js +0 -31
- package/dist/core/ingestion/unity-runtime-binding-rules.d.ts +0 -26
- package/dist/core/ingestion/unity-runtime-binding-rules.js +0 -408
- package/dist/rule-lab/analyze.d.ts +0 -13
- package/dist/rule-lab/analyze.js +0 -125
- package/dist/rule-lab/analyze.test.d.ts +0 -1
- package/dist/rule-lab/analyze.test.js +0 -246
- package/dist/rule-lab/compile.d.ts +0 -5
- package/dist/rule-lab/compile.js +0 -51
- package/dist/rule-lab/compiled-bundles.d.ts +0 -30
- package/dist/rule-lab/compiled-bundles.js +0 -36
- package/dist/rule-lab/curate.d.ts +0 -33
- package/dist/rule-lab/curate.js +0 -155
- package/dist/rule-lab/curate.test.d.ts +0 -1
- package/dist/rule-lab/curate.test.js +0 -137
- package/dist/rule-lab/curation-input-builder.d.ts +0 -45
- package/dist/rule-lab/curation-input-builder.js +0 -133
- package/dist/rule-lab/discover.d.ts +0 -13
- package/dist/rule-lab/discover.js +0 -74
- package/dist/rule-lab/discover.test.d.ts +0 -1
- package/dist/rule-lab/discover.test.js +0 -42
- package/dist/rule-lab/paths.d.ts +0 -21
- package/dist/rule-lab/paths.js +0 -37
- package/dist/rule-lab/paths.test.d.ts +0 -1
- package/dist/rule-lab/paths.test.js +0 -46
- package/dist/rule-lab/promote.d.ts +0 -26
- package/dist/rule-lab/promote.js +0 -387
- package/dist/rule-lab/promote.test.d.ts +0 -1
- package/dist/rule-lab/promote.test.js +0 -314
- package/dist/rule-lab/regress.d.ts +0 -60
- package/dist/rule-lab/regress.js +0 -122
- package/dist/rule-lab/regress.test.d.ts +0 -1
- package/dist/rule-lab/regress.test.js +0 -68
- package/dist/rule-lab/review-pack.d.ts +0 -34
- package/dist/rule-lab/review-pack.js +0 -165
- package/dist/rule-lab/review-pack.test.d.ts +0 -1
- package/dist/rule-lab/review-pack.test.js +0 -116
- package/dist/rule-lab/types.d.ts +0 -135
- package/dist/rule-lab/types.js +0 -1
- package/skills/_shared/unity-rule-authoring-contract.md +0 -64
- package/skills/gitnexus-unity-rule-gen.md +0 -107
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import fs from 'node:fs/promises';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { loadCompiledRuleBundle } from '../../rule-lab/compiled-bundles.js';
|
|
4
1
|
export class RuleRegistryLoadError extends Error {
|
|
5
2
|
code;
|
|
6
3
|
details;
|
|
@@ -139,59 +136,6 @@ export function parseRuleYaml(raw, filePath) {
|
|
|
139
136
|
const legacyGuarantees = readList(raw, 'guarantees');
|
|
140
137
|
const legacyNonGuarantees = readList(raw, 'non_guarantees');
|
|
141
138
|
const legacyNextAction = readScalar(raw, 'next_action');
|
|
142
|
-
// Parse resource_bindings
|
|
143
|
-
const rbLines = readSectionLines(raw, 'resource_bindings');
|
|
144
|
-
let resource_bindings;
|
|
145
|
-
if (rbLines.length > 0) {
|
|
146
|
-
resource_bindings = [];
|
|
147
|
-
const joined = rbLines.map((l) => l.replace(/^\s{2}/, '')).join('\n');
|
|
148
|
-
const entries = joined.split(/(?=^\s*- kind:)/m).filter((s) => s.trim());
|
|
149
|
-
for (const entry of entries) {
|
|
150
|
-
const kindMatch = entry.match(/- kind:\s*(.+)/);
|
|
151
|
-
if (!kindMatch)
|
|
152
|
-
continue;
|
|
153
|
-
const binding = { kind: decodeYamlScalar(kindMatch[1]) };
|
|
154
|
-
const scalar = (k) => {
|
|
155
|
-
const m = entry.match(new RegExp(`^\\s+${k}:\\s*(.+)$`, 'm'));
|
|
156
|
-
return m ? decodeYamlScalar(m[1]) : undefined;
|
|
157
|
-
};
|
|
158
|
-
const list = (k) => {
|
|
159
|
-
const lines = entry.split('\n');
|
|
160
|
-
const idx = lines.findIndex((l) => new RegExp(`^\\s+${k}:\\s*$`).test(l));
|
|
161
|
-
if (idx < 0)
|
|
162
|
-
return undefined;
|
|
163
|
-
const out = [];
|
|
164
|
-
for (let i = idx + 1; i < lines.length; i++) {
|
|
165
|
-
if (!/^\s+-\s+/.test(lines[i]))
|
|
166
|
-
break;
|
|
167
|
-
out.push(decodeYamlScalar(lines[i].replace(/^\s+-\s+/, '')));
|
|
168
|
-
}
|
|
169
|
-
return out.length > 0 ? out : undefined;
|
|
170
|
-
};
|
|
171
|
-
binding.ref_field_pattern = scalar('ref_field_pattern');
|
|
172
|
-
binding.target_entry_points = list('target_entry_points');
|
|
173
|
-
binding.host_class_pattern = scalar('host_class_pattern');
|
|
174
|
-
binding.field_name = scalar('field_name');
|
|
175
|
-
binding.loader_methods = list('loader_methods');
|
|
176
|
-
binding.scene_name = scalar('scene_name');
|
|
177
|
-
binding.source_class_pattern = scalar('source_class_pattern');
|
|
178
|
-
binding.source_method = scalar('source_method');
|
|
179
|
-
binding.target_class_pattern = scalar('target_class_pattern');
|
|
180
|
-
binding.target_method = scalar('target_method');
|
|
181
|
-
resource_bindings.push(binding);
|
|
182
|
-
}
|
|
183
|
-
if (resource_bindings.length === 0)
|
|
184
|
-
resource_bindings = undefined;
|
|
185
|
-
}
|
|
186
|
-
// Parse lifecycle_overrides
|
|
187
|
-
const loEntryPoints = readNestedList(raw, 'lifecycle_overrides', 'additional_entry_points');
|
|
188
|
-
const loScope = readNestedScalar(raw, 'lifecycle_overrides', 'scope');
|
|
189
|
-
const lifecycle_overrides = loEntryPoints.length > 0 || loScope
|
|
190
|
-
? {
|
|
191
|
-
...(loEntryPoints.length > 0 ? { additional_entry_points: loEntryPoints } : {}),
|
|
192
|
-
...(loScope ? { scope: loScope } : {}),
|
|
193
|
-
}
|
|
194
|
-
: undefined;
|
|
195
139
|
return {
|
|
196
140
|
id,
|
|
197
141
|
version,
|
|
@@ -209,109 +153,6 @@ export function parseRuleYaml(raw, filePath) {
|
|
|
209
153
|
guarantees: claimGuarantees.length > 0 ? claimGuarantees : legacyGuarantees,
|
|
210
154
|
non_guarantees: claimNonGuarantees.length > 0 ? claimNonGuarantees : legacyNonGuarantees,
|
|
211
155
|
next_action: claimNextAction || legacyNextAction,
|
|
212
|
-
family: readScalar(raw, 'family') || 'verification_rules',
|
|
213
|
-
resource_bindings,
|
|
214
|
-
lifecycle_overrides,
|
|
215
156
|
file_path: filePath,
|
|
216
157
|
};
|
|
217
158
|
}
|
|
218
|
-
/**
|
|
219
|
-
* Runtime claim rule registry remains the source for analyze-time synthetic-edge production
|
|
220
|
-
* and offline governance/report workflows. Query-time runtime closure verification is graph-only.
|
|
221
|
-
*/
|
|
222
|
-
export async function loadRuleRegistry(repoPath, rulesRoot) {
|
|
223
|
-
const normalizedRepoPath = path.resolve(repoPath);
|
|
224
|
-
const root = rulesRoot
|
|
225
|
-
? path.resolve(rulesRoot)
|
|
226
|
-
: path.join(normalizedRepoPath, '.gitnexus', 'rules');
|
|
227
|
-
const compiledVerificationBundle = await loadCompiledRuleBundle(normalizedRepoPath, 'verification_rules', root);
|
|
228
|
-
if (compiledVerificationBundle && compiledVerificationBundle.rules.length > 0) {
|
|
229
|
-
return {
|
|
230
|
-
repoPath: normalizedRepoPath,
|
|
231
|
-
rulesRoot: root,
|
|
232
|
-
catalogPath: path.join(root, 'compiled', 'verification_rules.v2.json'),
|
|
233
|
-
activeRules: compiledVerificationBundle.rules.map((rule) => ({
|
|
234
|
-
id: rule.id,
|
|
235
|
-
version: rule.version,
|
|
236
|
-
trigger_family: rule.trigger_family,
|
|
237
|
-
resource_types: rule.resource_types,
|
|
238
|
-
host_base_type: rule.host_base_type,
|
|
239
|
-
match: rule.match,
|
|
240
|
-
required_hops: rule.required_hops,
|
|
241
|
-
guarantees: rule.guarantees,
|
|
242
|
-
non_guarantees: rule.non_guarantees,
|
|
243
|
-
next_action: rule.next_action,
|
|
244
|
-
file_path: rule.file_path,
|
|
245
|
-
topology: rule.topology,
|
|
246
|
-
closure: rule.closure,
|
|
247
|
-
claims: rule.claims,
|
|
248
|
-
})),
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
const catalogPath = path.join(root, 'catalog.json');
|
|
252
|
-
let catalogRaw;
|
|
253
|
-
try {
|
|
254
|
-
catalogRaw = await fs.readFile(catalogPath, 'utf-8');
|
|
255
|
-
}
|
|
256
|
-
catch (error) {
|
|
257
|
-
if (error?.code === 'ENOENT') {
|
|
258
|
-
throw new RuleRegistryLoadError('rule_catalog_missing', `Runtime claim rule catalog not found: ${catalogPath}`, { repoPath: normalizedRepoPath, rulesRoot: root, catalogPath });
|
|
259
|
-
}
|
|
260
|
-
throw error;
|
|
261
|
-
}
|
|
262
|
-
let catalog;
|
|
263
|
-
try {
|
|
264
|
-
catalog = JSON.parse(catalogRaw);
|
|
265
|
-
}
|
|
266
|
-
catch {
|
|
267
|
-
throw new RuleRegistryLoadError('rule_catalog_invalid', `Runtime claim rule catalog is invalid JSON: ${catalogPath}`, { repoPath: normalizedRepoPath, rulesRoot: root, catalogPath });
|
|
268
|
-
}
|
|
269
|
-
const catalogRules = Array.isArray(catalog.rules) ? catalog.rules : [];
|
|
270
|
-
const activeRules = [];
|
|
271
|
-
for (const entry of catalogRules) {
|
|
272
|
-
if (entry.enabled === false)
|
|
273
|
-
continue;
|
|
274
|
-
const relativeRulePath = String(entry.file || path.join('approved', `${entry.id}.yaml`));
|
|
275
|
-
const rulePath = path.join(root, relativeRulePath);
|
|
276
|
-
let raw;
|
|
277
|
-
try {
|
|
278
|
-
raw = await fs.readFile(rulePath, 'utf-8');
|
|
279
|
-
}
|
|
280
|
-
catch (error) {
|
|
281
|
-
if (error?.code === 'ENOENT') {
|
|
282
|
-
throw new RuleRegistryLoadError('rule_file_missing', `Runtime claim rule file not found: ${rulePath}`, { repoPath: normalizedRepoPath, rulesRoot: root, catalogPath, rulePath, ruleId: entry.id });
|
|
283
|
-
}
|
|
284
|
-
throw error;
|
|
285
|
-
}
|
|
286
|
-
const parsed = parseRuleYaml(raw, rulePath);
|
|
287
|
-
if (parsed.id !== entry.id) {
|
|
288
|
-
throw new Error(`Rule id mismatch between catalog and yaml: ${entry.id} vs ${parsed.id}`);
|
|
289
|
-
}
|
|
290
|
-
activeRules.push({
|
|
291
|
-
...parsed,
|
|
292
|
-
version: entry.version || parsed.version,
|
|
293
|
-
family: entry.family || parsed.family || 'verification_rules',
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
return {
|
|
297
|
-
repoPath: normalizedRepoPath,
|
|
298
|
-
rulesRoot: root,
|
|
299
|
-
catalogPath,
|
|
300
|
-
activeRules,
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
export async function loadAnalyzeRules(repoPath, rulesRoot) {
|
|
304
|
-
const normalizedRepoPath = path.resolve(repoPath);
|
|
305
|
-
const root = rulesRoot
|
|
306
|
-
? path.resolve(rulesRoot)
|
|
307
|
-
: path.join(normalizedRepoPath, '.gitnexus', 'rules');
|
|
308
|
-
const analyzeBundle = await loadCompiledRuleBundle(normalizedRepoPath, 'analyze_rules', root);
|
|
309
|
-
if (analyzeBundle && analyzeBundle.rules.length > 0) {
|
|
310
|
-
return analyzeBundle.rules.map((rule) => ({
|
|
311
|
-
...rule,
|
|
312
|
-
family: 'analyze_rules',
|
|
313
|
-
}));
|
|
314
|
-
}
|
|
315
|
-
const registry = await loadRuleRegistry(repoPath, rulesRoot);
|
|
316
|
-
return registry.activeRules.filter((r) => r.family === 'analyze_rules');
|
|
317
|
-
}
|
|
@@ -1,24 +1,14 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
|
-
import fs from 'node:fs/promises';
|
|
3
|
-
import os from 'node:os';
|
|
4
|
-
import path from 'node:path';
|
|
5
2
|
import { test } from 'vitest';
|
|
6
|
-
import { RuleRegistryLoadError,
|
|
7
|
-
test('
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
id: 'demo.reload.rule.v1',
|
|
16
|
-
version: '1.2.3',
|
|
17
|
-
file: 'approved/demo.reload.rule.v1.yaml',
|
|
18
|
-
},
|
|
19
|
-
],
|
|
20
|
-
}), 'utf-8');
|
|
21
|
-
await fs.writeFile(path.join(rulesRoot, 'approved', 'demo.reload.rule.v1.yaml'), [
|
|
3
|
+
import { RuleRegistryLoadError, parseRuleYaml } from './runtime-claim-rule-registry.js';
|
|
4
|
+
test('RuleRegistryLoadError has code and context', () => {
|
|
5
|
+
const err = new RuleRegistryLoadError('rule_catalog_missing', 'Catalog not found', { repoPath: '/tmp/repo' });
|
|
6
|
+
assert.ok(err instanceof RuleRegistryLoadError);
|
|
7
|
+
assert.equal(err.code, 'rule_catalog_missing');
|
|
8
|
+
assert.match(String(err.message || ''), /Catalog not found/);
|
|
9
|
+
});
|
|
10
|
+
test('parseRuleYaml parses basic fields', () => {
|
|
11
|
+
const yaml = [
|
|
22
12
|
'id: demo.reload.rule.v1',
|
|
23
13
|
'version: 1.2.3',
|
|
24
14
|
'trigger_family: reload',
|
|
@@ -33,106 +23,37 @@ test('loads active runtime claim rules from project catalog', async () => {
|
|
|
33
23
|
'non_guarantees:',
|
|
34
24
|
' - no_runtime_execution',
|
|
35
25
|
'next_action: gitnexus query "reload"',
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const originalCwd = process.cwd();
|
|
65
|
-
process.chdir(nestedCwd);
|
|
66
|
-
try {
|
|
67
|
-
await assert.rejects(() => loadRuleRegistry(path.join(tempRoot, 'does-not-exist')), (error) => {
|
|
68
|
-
assert.ok(error instanceof RuleRegistryLoadError);
|
|
69
|
-
assert.equal(error.code, 'rule_catalog_missing');
|
|
70
|
-
assert.match(String(error.message || ''), /catalog not found/i);
|
|
71
|
-
return true;
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
finally {
|
|
75
|
-
process.chdir(originalCwd);
|
|
76
|
-
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
test('throws rule_catalog_missing when rulesRoot exists but catalog.json is missing', async () => {
|
|
80
|
-
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-runtime-claim-rules-'));
|
|
81
|
-
const repoPath = path.join(tempRoot, 'repo');
|
|
82
|
-
const rulesRoot = path.join(repoPath, '.gitnexus', 'rules');
|
|
83
|
-
await fs.mkdir(path.join(rulesRoot, 'approved'), { recursive: true });
|
|
84
|
-
try {
|
|
85
|
-
await assert.rejects(() => loadRuleRegistry(repoPath), (error) => {
|
|
86
|
-
assert.ok(error instanceof RuleRegistryLoadError);
|
|
87
|
-
assert.equal(error.code, 'rule_catalog_missing');
|
|
88
|
-
return true;
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
finally {
|
|
92
|
-
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
test('throws rule_file_missing when catalog entry points to missing yaml file', async () => {
|
|
96
|
-
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-runtime-claim-rules-'));
|
|
97
|
-
const repoPath = path.join(tempRoot, 'repo');
|
|
98
|
-
const rulesRoot = path.join(repoPath, '.gitnexus', 'rules');
|
|
99
|
-
await fs.mkdir(path.join(rulesRoot, 'approved'), { recursive: true });
|
|
100
|
-
await fs.writeFile(path.join(rulesRoot, 'catalog.json'), JSON.stringify({
|
|
101
|
-
rules: [
|
|
102
|
-
{
|
|
103
|
-
id: 'demo.reload.rule.v1',
|
|
104
|
-
version: '1.0.0',
|
|
105
|
-
file: 'approved/demo.reload.rule.v1.yaml',
|
|
106
|
-
},
|
|
107
|
-
],
|
|
108
|
-
}), 'utf-8');
|
|
109
|
-
try {
|
|
110
|
-
await assert.rejects(() => loadRuleRegistry(repoPath), (error) => {
|
|
111
|
-
assert.ok(error instanceof RuleRegistryLoadError);
|
|
112
|
-
assert.equal(error.code, 'rule_file_missing');
|
|
113
|
-
assert.match(String(error.message || ''), /rule file not found/i);
|
|
114
|
-
return true;
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
finally {
|
|
118
|
-
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
119
|
-
}
|
|
26
|
+
'match:',
|
|
27
|
+
' trigger_tokens:',
|
|
28
|
+
' - reload',
|
|
29
|
+
'closure:',
|
|
30
|
+
' required_hops:',
|
|
31
|
+
' - resource',
|
|
32
|
+
'claims:',
|
|
33
|
+
' guarantees:',
|
|
34
|
+
' - reload_chain_closed',
|
|
35
|
+
' non_guarantees:',
|
|
36
|
+
' - no_runtime_execution',
|
|
37
|
+
' next_action: gitnexus query "reload"',
|
|
38
|
+
'topology:',
|
|
39
|
+
' - hop: resource',
|
|
40
|
+
' from:',
|
|
41
|
+
' entity: resource',
|
|
42
|
+
' to:',
|
|
43
|
+
' entity: script',
|
|
44
|
+
' edge:',
|
|
45
|
+
' kind: binds_script',
|
|
46
|
+
].join('\n');
|
|
47
|
+
const rule = parseRuleYaml(yaml, 'test.yaml');
|
|
48
|
+
assert.equal(rule.id, 'demo.reload.rule.v1');
|
|
49
|
+
assert.equal(rule.version, '1.2.3');
|
|
50
|
+
assert.equal(rule.trigger_family, 'reload');
|
|
51
|
+
assert.deepEqual(rule.resource_types, ['asset']);
|
|
52
|
+
assert.deepEqual(rule.host_base_type, ['ReloadBase']);
|
|
53
|
+
assert.equal(rule.file_path, 'test.yaml');
|
|
120
54
|
});
|
|
121
|
-
test('parses scalar/list values with spaces, quotes, and escapes
|
|
122
|
-
const
|
|
123
|
-
const repoPath = path.join(tempRoot, 'repo');
|
|
124
|
-
const rulesRoot = path.join(repoPath, '.gitnexus', 'rules');
|
|
125
|
-
await fs.mkdir(path.join(rulesRoot, 'approved'), { recursive: true });
|
|
126
|
-
await fs.writeFile(path.join(rulesRoot, 'catalog.json'), JSON.stringify({
|
|
127
|
-
rules: [
|
|
128
|
-
{
|
|
129
|
-
id: 'demo.scalar-parser.v1',
|
|
130
|
-
version: '1.0.0',
|
|
131
|
-
file: 'approved/demo.scalar-parser.v1.yaml',
|
|
132
|
-
},
|
|
133
|
-
],
|
|
134
|
-
}), 'utf-8');
|
|
135
|
-
await fs.writeFile(path.join(rulesRoot, 'approved', 'demo.scalar-parser.v1.yaml'), [
|
|
55
|
+
test('parseRuleYaml parses scalar/list values with spaces, quotes, and escapes', () => {
|
|
56
|
+
const yaml = [
|
|
136
57
|
'id: demo.scalar-parser.v1',
|
|
137
58
|
'version: 1.0.0',
|
|
138
59
|
'trigger_family: reload',
|
|
@@ -149,100 +70,32 @@ test('parses scalar/list values with spaces, quotes, and escapes without truncat
|
|
|
149
70
|
' - "double-quote \\"inside\\""',
|
|
150
71
|
" - 'single-quote ''inside'''",
|
|
151
72
|
'next_action: node gitnexus/dist/cli/index.js query --runtime-chain-verify on-demand "Reload NEON.Game.Graph.Nodes.Reloads"',
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
await fs.writeFile(path.join(rulesRoot, 'approved', 'demo.reload.rule.v2.yaml'), [
|
|
181
|
-
'id: demo.reload.rule.v2',
|
|
182
|
-
'version: 2.0.0',
|
|
183
|
-
'trigger_family: reload',
|
|
184
|
-
'resource_types:',
|
|
185
|
-
' - asset',
|
|
186
|
-
'host_base_type:',
|
|
187
|
-
' - ReloadBase',
|
|
188
|
-
'required_hops:',
|
|
189
|
-
' - resource',
|
|
190
|
-
'guarantees:',
|
|
191
|
-
' - reload_chain_closed',
|
|
192
|
-
'non_guarantees:',
|
|
193
|
-
' - no_runtime_execution_guarantee',
|
|
194
|
-
].join('\n'), 'utf-8');
|
|
195
|
-
try {
|
|
196
|
-
await assert.rejects(() => loadRuleRegistry(repoPath), /topology|closure|claims/i);
|
|
197
|
-
}
|
|
198
|
-
finally {
|
|
199
|
-
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
test('loads v2 verification bundle from explicit compiled path without catalog fallback', async () => {
|
|
203
|
-
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-runtime-claim-rules-'));
|
|
204
|
-
const repoPath = path.join(tempRoot, 'repo');
|
|
205
|
-
const compiledRoot = path.join(repoPath, '.gitnexus', 'rules', 'compiled');
|
|
206
|
-
await fs.mkdir(compiledRoot, { recursive: true });
|
|
207
|
-
await fs.writeFile(path.join(compiledRoot, 'verification_rules.v2.json'), JSON.stringify({
|
|
208
|
-
bundle_version: '2.0.0',
|
|
209
|
-
family: 'verification_rules',
|
|
210
|
-
generated_at: new Date().toISOString(),
|
|
211
|
-
rules: [
|
|
212
|
-
{
|
|
213
|
-
id: 'demo.bundle.rule.v2',
|
|
214
|
-
version: '2.0.0',
|
|
215
|
-
trigger_family: 'reload',
|
|
216
|
-
resource_types: ['asset'],
|
|
217
|
-
host_base_type: ['ReloadBase'],
|
|
218
|
-
required_hops: ['resource', 'code_runtime'],
|
|
219
|
-
guarantees: ['reload_chain_closed'],
|
|
220
|
-
non_guarantees: ['no_runtime_execution'],
|
|
221
|
-
next_action: 'gitnexus query "reload"',
|
|
222
|
-
file_path: '.gitnexus/rules/compiled/verification_rules.v2.json',
|
|
223
|
-
match: { trigger_tokens: ['reload'] },
|
|
224
|
-
topology: [
|
|
225
|
-
{ hop: 'resource', from: { entity: 'resource' }, to: { entity: 'script' }, edge: { kind: 'binds_script' } },
|
|
226
|
-
],
|
|
227
|
-
closure: {
|
|
228
|
-
required_hops: ['resource', 'code_runtime'],
|
|
229
|
-
failure_map: { missing_evidence: 'rule_matched_but_evidence_missing' },
|
|
230
|
-
},
|
|
231
|
-
claims: {
|
|
232
|
-
guarantees: ['reload_chain_closed'],
|
|
233
|
-
non_guarantees: ['no_runtime_execution'],
|
|
234
|
-
next_action: 'gitnexus query "reload"',
|
|
235
|
-
},
|
|
236
|
-
},
|
|
237
|
-
],
|
|
238
|
-
}, null, 2), 'utf-8');
|
|
239
|
-
try {
|
|
240
|
-
const registry = await loadRuleRegistry(repoPath);
|
|
241
|
-
assert.equal(registry.activeRules[0].id, 'demo.bundle.rule.v2');
|
|
242
|
-
assert.equal(registry.activeRules[0].version, '2.0.0');
|
|
243
|
-
assert.deepEqual(registry.activeRules[0].required_hops, ['resource', 'code_runtime']);
|
|
244
|
-
}
|
|
245
|
-
finally {
|
|
246
|
-
await fs.rm(tempRoot, { recursive: true, force: true });
|
|
247
|
-
}
|
|
73
|
+
'match:',
|
|
74
|
+
' trigger_tokens:',
|
|
75
|
+
' - reload',
|
|
76
|
+
'closure:',
|
|
77
|
+
' required_hops:',
|
|
78
|
+
' - resource',
|
|
79
|
+
'claims:',
|
|
80
|
+
' guarantees:',
|
|
81
|
+
" - 'guarantee with spaces'",
|
|
82
|
+
' non_guarantees:',
|
|
83
|
+
' - "double-quote \\"inside\\""',
|
|
84
|
+
" - 'single-quote ''inside'''",
|
|
85
|
+
' next_action: query "reload"',
|
|
86
|
+
'topology:',
|
|
87
|
+
' - hop: resource',
|
|
88
|
+
' from:',
|
|
89
|
+
' entity: resource',
|
|
90
|
+
' to:',
|
|
91
|
+
' entity: script',
|
|
92
|
+
' edge:',
|
|
93
|
+
' kind: binds_script',
|
|
94
|
+
].join('\n');
|
|
95
|
+
const rule = parseRuleYaml(yaml, 'scalar.yaml');
|
|
96
|
+
assert.equal(rule.id, 'demo.scalar-parser.v1');
|
|
97
|
+
assert.deepEqual(rule.resource_types, ['asset ref', 'prefab ref']);
|
|
98
|
+
assert.deepEqual(rule.guarantees, ['guarantee with spaces']);
|
|
99
|
+
assert.deepEqual(rule.non_guarantees, ['double-quote "inside"', "single-quote 'inside'"]);
|
|
100
|
+
assert.equal(rule.next_action, 'query "reload"');
|
|
248
101
|
});
|
package/dist/mcp/tools.js
CHANGED
|
@@ -383,76 +383,6 @@ Output enforces unique-result policy and includes path+line evidence hops.`,
|
|
|
383
383
|
required: ['target', 'goal'],
|
|
384
384
|
},
|
|
385
385
|
},
|
|
386
|
-
{
|
|
387
|
-
name: 'rule_lab_analyze',
|
|
388
|
-
description: `Analyze one Rule Lab slice and emit anchor-backed candidates.jsonl.`,
|
|
389
|
-
inputSchema: {
|
|
390
|
-
type: 'object',
|
|
391
|
-
properties: {
|
|
392
|
-
run_id: { type: 'string', description: 'Rule Lab run id' },
|
|
393
|
-
slice_id: { type: 'string', description: 'Rule Lab slice id' },
|
|
394
|
-
repo: { type: 'string', description: 'Repository name or path. Omit if only one repo is indexed.' },
|
|
395
|
-
},
|
|
396
|
-
required: ['run_id', 'slice_id'],
|
|
397
|
-
},
|
|
398
|
-
},
|
|
399
|
-
{
|
|
400
|
-
name: 'rule_lab_review_pack',
|
|
401
|
-
description: `Pack analyzed candidates into review cards with token budget enforcement.`,
|
|
402
|
-
inputSchema: {
|
|
403
|
-
type: 'object',
|
|
404
|
-
properties: {
|
|
405
|
-
run_id: { type: 'string', description: 'Rule Lab run id' },
|
|
406
|
-
slice_id: { type: 'string', description: 'Rule Lab slice id' },
|
|
407
|
-
max_tokens: { type: 'number', description: 'Token budget cap (default: 6000)', default: 6000 },
|
|
408
|
-
repo: { type: 'string', description: 'Repository name or path. Omit if only one repo is indexed.' },
|
|
409
|
-
},
|
|
410
|
-
required: ['run_id', 'slice_id'],
|
|
411
|
-
},
|
|
412
|
-
},
|
|
413
|
-
{
|
|
414
|
-
name: 'rule_lab_curate',
|
|
415
|
-
description: `Validate human-curated semantic closure input and persist curated artifacts for promotion.`,
|
|
416
|
-
inputSchema: {
|
|
417
|
-
type: 'object',
|
|
418
|
-
properties: {
|
|
419
|
-
run_id: { type: 'string', description: 'Rule Lab run id' },
|
|
420
|
-
slice_id: { type: 'string', description: 'Rule Lab slice id' },
|
|
421
|
-
input_path: { type: 'string', description: 'Absolute or repo-relative path to curation input JSON' },
|
|
422
|
-
repo: { type: 'string', description: 'Repository name or path. Omit if only one repo is indexed.' },
|
|
423
|
-
},
|
|
424
|
-
required: ['run_id', 'slice_id', 'input_path'],
|
|
425
|
-
},
|
|
426
|
-
},
|
|
427
|
-
{
|
|
428
|
-
name: 'rule_lab_promote',
|
|
429
|
-
description: `Promote curated candidates into approved YAML rules and upsert catalog.json entries.`,
|
|
430
|
-
inputSchema: {
|
|
431
|
-
type: 'object',
|
|
432
|
-
properties: {
|
|
433
|
-
run_id: { type: 'string', description: 'Rule Lab run id' },
|
|
434
|
-
slice_id: { type: 'string', description: 'Rule Lab slice id' },
|
|
435
|
-
version: { type: 'string', description: 'Promoted rule version (default: 1.0.0)', default: '1.0.0' },
|
|
436
|
-
repo: { type: 'string', description: 'Repository name or path. Omit if only one repo is indexed.' },
|
|
437
|
-
},
|
|
438
|
-
required: ['run_id', 'slice_id'],
|
|
439
|
-
},
|
|
440
|
-
},
|
|
441
|
-
{
|
|
442
|
-
name: 'rule_lab_regress',
|
|
443
|
-
description: `Evaluate Rule Lab precision/coverage gates and optionally persist a regression report.`,
|
|
444
|
-
inputSchema: {
|
|
445
|
-
type: 'object',
|
|
446
|
-
properties: {
|
|
447
|
-
precision: { type: 'number', description: 'Observed precision metric' },
|
|
448
|
-
coverage: { type: 'number', description: 'Observed coverage metric' },
|
|
449
|
-
probes_path: { type: 'string', description: 'Optional path to a JSON array of regression probes with bucket metadata' },
|
|
450
|
-
run_id: { type: 'string', description: 'Optional run id for report naming' },
|
|
451
|
-
repo: { type: 'string', description: 'Repository name or path. Omit if only one repo is indexed.' },
|
|
452
|
-
},
|
|
453
|
-
required: ['precision', 'coverage'],
|
|
454
|
-
},
|
|
455
|
-
},
|
|
456
386
|
{
|
|
457
387
|
name: 'impact',
|
|
458
388
|
description: `Analyze the blast radius of changing a code symbol.
|
package/dist/types/pipeline.d.ts
CHANGED
|
@@ -2,7 +2,6 @@ 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';
|
|
6
5
|
import type { ScopeSelectionDiagnostics } from '../core/ingestion/scope-filter.js';
|
|
7
6
|
export type PipelinePhase = 'idle' | 'extracting' | 'structure' | 'parsing' | 'imports' | 'calls' | 'heritage' | 'communities' | 'processes' | 'enriching' | 'complete' | 'error';
|
|
8
7
|
export interface PipelineProgress {
|
|
@@ -40,7 +39,6 @@ export interface PipelineResult {
|
|
|
40
39
|
communityResult?: CommunityDetectionResult;
|
|
41
40
|
processResult?: ProcessDetectionResult;
|
|
42
41
|
unityResult?: UnityResourceProcessingResult;
|
|
43
|
-
unityRuleBindingResult?: UnityRuntimeBindingResult;
|
|
44
42
|
scopeDiagnostics?: ScopeSelectionDiagnostics;
|
|
45
43
|
csharpPreprocDiagnostics?: CSharpPreprocDiagnostics;
|
|
46
44
|
}
|
|
@@ -49,7 +47,6 @@ export interface PipelineRuntimeSummary {
|
|
|
49
47
|
communityResult?: CommunityDetectionResult;
|
|
50
48
|
processResult?: ProcessDetectionResult;
|
|
51
49
|
unityResult?: UnityResourceProcessingResult;
|
|
52
|
-
unityRuleBindingResult?: UnityRuntimeBindingResult;
|
|
53
50
|
scopeDiagnostics?: ScopeSelectionDiagnostics;
|
|
54
51
|
csharpPreprocDiagnostics?: CSharpPreprocDiagnostics;
|
|
55
52
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veewo/gitnexus",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.9",
|
|
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",
|
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
},
|
|
72
72
|
"dependencies": {
|
|
73
73
|
"@huggingface/transformers": "^3.0.0",
|
|
74
|
+
"@ladybugdb/core": "0.15.1",
|
|
74
75
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
75
76
|
"cli-progress": "^3.12.0",
|
|
76
77
|
"commander": "^12.0.0",
|
|
@@ -80,7 +81,6 @@
|
|
|
80
81
|
"graphology": "^0.25.4",
|
|
81
82
|
"graphology-indices": "^0.17.0",
|
|
82
83
|
"graphology-utils": "^2.3.0",
|
|
83
|
-
"@ladybugdb/core": "0.15.1",
|
|
84
84
|
"ignore": "^7.0.5",
|
|
85
85
|
"lru-cache": "^11.0.0",
|
|
86
86
|
"mnemonist": "^0.39.0",
|
|
@@ -100,9 +100,9 @@
|
|
|
100
100
|
"uuid": "^13.0.0"
|
|
101
101
|
},
|
|
102
102
|
"optionalDependencies": {
|
|
103
|
+
"tree-sitter-gdscript": "^6.1.0",
|
|
103
104
|
"tree-sitter-kotlin": "^0.3.8",
|
|
104
|
-
"tree-sitter-swift": "^0.6.0"
|
|
105
|
-
"tree-sitter-gdscript": "^6.1.0"
|
|
105
|
+
"tree-sitter-swift": "^0.6.0"
|
|
106
106
|
},
|
|
107
107
|
"devDependencies": {
|
|
108
108
|
"@types/cli-progress": "^3.11.6",
|
package/skills/gitnexus-cli.md
CHANGED
|
@@ -58,7 +58,7 @@ fi
|
|
|
58
58
|
$GN analyze
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
Run from the project root. This parses all source files, builds the knowledge graph, writes it to `.gitnexus/`, and generates CLAUDE.md / AGENTS.md context files.
|
|
61
|
+
Run from the project root. This parses all source files, builds the knowledge graph, writes it to `.gitnexus/`, and generates CLAUDE.md / AGENTS.md context files unless `--no-ai-context` is set.
|
|
62
62
|
|
|
63
63
|
Analyze options are resolved with two-layer priority: **CLI arguments** > **stored options** in `meta.json.analyzeOptions`. On first run, pass CLI flags; they are persisted automatically for subsequent runs.
|
|
64
64
|
|
|
@@ -66,6 +66,7 @@ Analyze options are resolved with two-layer priority: **CLI arguments** > **stor
|
|
|
66
66
|
|------|--------|
|
|
67
67
|
| `--force` | Force full re-index even if up to date |
|
|
68
68
|
| `--no-reuse-options` | Do not reuse stored analyze options from previous index |
|
|
69
|
+
| `--no-ai-context` | Skip writing `AGENTS.md` / `CLAUDE.md` and installing repo-local GitNexus skills |
|
|
69
70
|
| `--embeddings` | Enable embedding generation (off by default) |
|
|
70
71
|
| `--extensions <list>` | Comma-separated file extensions (e.g. `.cs,.ts`) |
|
|
71
72
|
| `--scope <rules>` | Comma-separated scope path-prefix rules (e.g. `Assets/,Packages/com.veewo.*`) |
|
|
@@ -74,7 +75,9 @@ Analyze options are resolved with two-layer priority: **CLI arguments** > **stor
|
|
|
74
75
|
| `--skills` | Generate repo-specific skill files from detected communities |
|
|
75
76
|
| `-v, --verbose` | Enable verbose ingestion warnings |
|
|
76
77
|
|
|
77
|
-
**Option persistence:** `--extensions`, `--scope`, `--repo-alias`, `--embeddings`,
|
|
78
|
+
**Option persistence:** `--extensions`, `--scope`, `--repo-alias`, `--embeddings`, `--csharp-define-csproj`, and the effective AI-context setting are automatically saved to `meta.json.analyzeOptions` after a successful run. On subsequent runs, these stored values are reused unless you pass new CLI flags or use `--no-reuse-options`.
|
|
79
|
+
|
|
80
|
+
When `--no-ai-context` is saved, later `analyze` runs continue skipping AGENTS/CLAUDE generation until you explicitly re-enable AI context (for example by passing `--ai-context` through a direct CLI invocation or clearing the stored setting with `--no-reuse-options`).
|
|
78
81
|
|
|
79
82
|
#### Scope rules
|
|
80
83
|
|