@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.
Files changed (77) hide show
  1. package/dist/cli/ai-context.js +1 -7
  2. package/dist/cli/analyze-options.d.ts +4 -0
  3. package/dist/cli/analyze-options.js +14 -1
  4. package/dist/cli/analyze-options.test.js +23 -0
  5. package/dist/cli/analyze-runtime-summary.js +0 -1
  6. package/dist/cli/analyze-runtime-summary.test.js +0 -2
  7. package/dist/cli/analyze-summary.d.ts +0 -2
  8. package/dist/cli/analyze-summary.js +0 -24
  9. package/dist/cli/analyze-summary.test.js +1 -65
  10. package/dist/cli/analyze.d.ts +1 -0
  11. package/dist/cli/analyze.js +26 -18
  12. package/dist/cli/clean.js +23 -2
  13. package/dist/cli/index.js +3 -3
  14. package/dist/cli/repo-manager-alias.test.js +2 -0
  15. package/dist/core/ingestion/pipeline.js +0 -43
  16. package/dist/core/ingestion/tree-sitter-queries.d.ts +1 -1
  17. package/dist/core/ingestion/tree-sitter-queries.js +3 -3
  18. package/dist/mcp/local/agent-safe-response.js +1 -1
  19. package/dist/mcp/local/local-backend.d.ts +0 -23
  20. package/dist/mcp/local/local-backend.js +69 -248
  21. package/dist/mcp/local/runtime-chain-verify.test.js +0 -49
  22. package/dist/mcp/local/runtime-claim-rule-registry.d.ts +0 -11
  23. package/dist/mcp/local/runtime-claim-rule-registry.js +0 -159
  24. package/dist/mcp/local/runtime-claim-rule-registry.test.js +67 -214
  25. package/dist/mcp/tools.js +0 -70
  26. package/dist/storage/repo-manager.d.ts +1 -0
  27. package/dist/types/pipeline.d.ts +0 -3
  28. package/package.json +4 -4
  29. package/skills/gitnexus-cli.md +5 -2
  30. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.d.ts +0 -60
  31. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.js +0 -395
  32. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.test.d.ts +0 -1
  33. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.test.js +0 -41
  34. package/dist/cli/rule-lab.d.ts +0 -38
  35. package/dist/cli/rule-lab.js +0 -148
  36. package/dist/cli/rule-lab.test.d.ts +0 -1
  37. package/dist/cli/rule-lab.test.js +0 -31
  38. package/dist/core/ingestion/unity-runtime-binding-rules.d.ts +0 -26
  39. package/dist/core/ingestion/unity-runtime-binding-rules.js +0 -408
  40. package/dist/rule-lab/analyze.d.ts +0 -13
  41. package/dist/rule-lab/analyze.js +0 -125
  42. package/dist/rule-lab/analyze.test.d.ts +0 -1
  43. package/dist/rule-lab/analyze.test.js +0 -246
  44. package/dist/rule-lab/compile.d.ts +0 -5
  45. package/dist/rule-lab/compile.js +0 -51
  46. package/dist/rule-lab/compiled-bundles.d.ts +0 -30
  47. package/dist/rule-lab/compiled-bundles.js +0 -36
  48. package/dist/rule-lab/curate.d.ts +0 -33
  49. package/dist/rule-lab/curate.js +0 -155
  50. package/dist/rule-lab/curate.test.d.ts +0 -1
  51. package/dist/rule-lab/curate.test.js +0 -137
  52. package/dist/rule-lab/curation-input-builder.d.ts +0 -45
  53. package/dist/rule-lab/curation-input-builder.js +0 -133
  54. package/dist/rule-lab/discover.d.ts +0 -13
  55. package/dist/rule-lab/discover.js +0 -74
  56. package/dist/rule-lab/discover.test.d.ts +0 -1
  57. package/dist/rule-lab/discover.test.js +0 -42
  58. package/dist/rule-lab/paths.d.ts +0 -21
  59. package/dist/rule-lab/paths.js +0 -37
  60. package/dist/rule-lab/paths.test.d.ts +0 -1
  61. package/dist/rule-lab/paths.test.js +0 -46
  62. package/dist/rule-lab/promote.d.ts +0 -26
  63. package/dist/rule-lab/promote.js +0 -387
  64. package/dist/rule-lab/promote.test.d.ts +0 -1
  65. package/dist/rule-lab/promote.test.js +0 -314
  66. package/dist/rule-lab/regress.d.ts +0 -60
  67. package/dist/rule-lab/regress.js +0 -122
  68. package/dist/rule-lab/regress.test.d.ts +0 -1
  69. package/dist/rule-lab/regress.test.js +0 -68
  70. package/dist/rule-lab/review-pack.d.ts +0 -34
  71. package/dist/rule-lab/review-pack.js +0 -165
  72. package/dist/rule-lab/review-pack.test.d.ts +0 -1
  73. package/dist/rule-lab/review-pack.test.js +0 -116
  74. package/dist/rule-lab/types.d.ts +0 -135
  75. package/dist/rule-lab/types.js +0 -1
  76. package/skills/_shared/unity-rule-authoring-contract.md +0 -64
  77. package/skills/gitnexus-unity-rule-gen.md +0 -107
@@ -1,125 +0,0 @@
1
- import fs from 'node:fs/promises';
2
- import path from 'node:path';
3
- import { createHash } from 'node:crypto';
4
- import { getRuleLabPaths } from './paths.js';
5
- import { buildCurationInput } from './curation-input-builder.js';
6
- function buildCandidateId(slice, variant) {
7
- return createHash('sha1')
8
- .update(`${slice.id}:${slice.trigger_family}:${slice.resource_types.join('|')}:${slice.host_base_type.join('|')}:${variant}`)
9
- .digest('hex')
10
- .slice(0, 12);
11
- }
12
- function normalizeToken(value) {
13
- return String(value || '')
14
- .trim()
15
- .toLowerCase()
16
- .replace(/[^a-z0-9]+/g, '-')
17
- .replace(/^-+|-+$/g, '');
18
- }
19
- function inferRuleStem(slice, pair) {
20
- const source = normalizeToken(pair.source_anchor.symbol || pair.source_anchor.file || `${slice.id}-source`);
21
- const target = normalizeToken(pair.target_anchor.symbol || pair.target_anchor.file || `${slice.id}-target`);
22
- const joined = [source, target].filter(Boolean).join('-');
23
- return joined || normalizeToken(slice.id) || 'runtime-rule';
24
- }
25
- function buildProposalTopology(slice) {
26
- const requiredHops = Array.isArray(slice.required_hops) && slice.required_hops.length > 0
27
- ? slice.required_hops
28
- : ['resource', 'code_runtime'];
29
- return requiredHops.map((hop) => ({
30
- hop,
31
- from: { entity: hop === 'resource' ? 'resource' : 'script' },
32
- to: { entity: hop === 'code_runtime' ? 'runtime' : 'script' },
33
- edge: { kind: hop === 'resource' ? 'binds_script' : 'calls' },
34
- }));
35
- }
36
- function buildExactPairCandidates(slice) {
37
- const pairs = Array.isArray(slice.exact_pairs) ? slice.exact_pairs : [];
38
- if (pairs.length === 0) {
39
- throw new Error('exact_pairs must be non-empty for reduced rule-lab analyze flow');
40
- }
41
- const seenPairIds = new Set();
42
- for (const pair of pairs) {
43
- const pairId = String(pair.id || '').trim();
44
- if (!pairId)
45
- continue;
46
- if (seenPairIds.has(pairId)) {
47
- throw new Error(`duplicate_exact_pair_id: ${pairId}`);
48
- }
49
- seenPairIds.add(pairId);
50
- }
51
- const topology = buildProposalTopology(slice);
52
- return pairs.map((pair, index) => {
53
- const pairKey = String(pair.id || `${index + 1}`).trim();
54
- const sourceAnchor = `${String(pair.source_anchor.file || '').trim()}:${Number(pair.source_anchor.line || 1)}`;
55
- const targetAnchor = `${String(pair.target_anchor.file || '').trim()}:${Number(pair.target_anchor.line || 1)}`;
56
- const draftRuleId = String(pair.draft_rule_id || '').trim() || `unity.event.${inferRuleStem(slice, pair)}.v1`;
57
- const bindingKind = pair.binding_kind || 'method_triggers_method';
58
- return {
59
- id: buildCandidateId(slice, `exact:${pairKey}`),
60
- title: `${slice.trigger_family} exact pair ${pairKey}`,
61
- rule_hint: `${slice.trigger_family}.${slice.id}.exact.${pairKey}`,
62
- proposal_kind: 'per_anchor_rule',
63
- aggregation_mode: 'per_anchor_rules',
64
- binding_kind: bindingKind,
65
- draft_rule_id: draftRuleId,
66
- topology,
67
- closure: {
68
- required_hops: topology.map((hop) => hop.hop),
69
- failure_map: {
70
- missing_evidence: 'rule_matched_but_evidence_missing',
71
- },
72
- },
73
- claims: {
74
- guarantees: [`exact pair linked: ${sourceAnchor} -> ${targetAnchor}`],
75
- non_guarantees: ['sparse gap path only; no exhaustive discovery semantics'],
76
- next_action: `gitnexus query "${slice.trigger_family}"`,
77
- },
78
- exact_pair: pair,
79
- evidence: {
80
- hops: [
81
- {
82
- hop_type: 'code_runtime',
83
- anchor: sourceAnchor,
84
- snippet: String(pair.source_anchor.symbol || 'source'),
85
- },
86
- {
87
- hop_type: 'code_runtime',
88
- anchor: targetAnchor,
89
- snippet: String(pair.target_anchor.symbol || 'target'),
90
- },
91
- ],
92
- },
93
- };
94
- });
95
- }
96
- function assertNoPlaceholderIds(runId, sliceId) {
97
- const placeholderRe = /<[^>]+>|placeholder|todo|tbd/i;
98
- if (placeholderRe.test(runId) || placeholderRe.test(sliceId)) {
99
- throw new Error('placeholder run/slice ids are not allowed');
100
- }
101
- }
102
- export async function analyzeRuleLabSlice(input) {
103
- assertNoPlaceholderIds(input.runId, input.sliceId);
104
- const normalizedRepoPath = path.resolve(input.repoPath);
105
- const paths = getRuleLabPaths(normalizedRepoPath, input.runId, input.sliceId);
106
- const slicePath = path.join(paths.slicesRoot, input.sliceId, 'slice.json');
107
- const raw = await fs.readFile(slicePath, 'utf-8');
108
- const slice = JSON.parse(raw);
109
- const candidates = buildExactPairCandidates(slice);
110
- const curation = buildCurationInput({
111
- runId: input.runId,
112
- sliceId: input.sliceId,
113
- slice,
114
- candidates,
115
- });
116
- await fs.mkdir(path.dirname(paths.candidatesPath), { recursive: true });
117
- await fs.writeFile(paths.candidatesPath, `${candidates.map((candidate) => JSON.stringify(candidate)).join('\n')}\n`, 'utf-8');
118
- await fs.writeFile(slicePath, `${JSON.stringify(slice, null, 2)}\n`, 'utf-8');
119
- await fs.writeFile(path.join(path.dirname(paths.candidatesPath), 'curation-input.json'), `${JSON.stringify(curation, null, 2)}\n`, 'utf-8');
120
- return {
121
- paths,
122
- candidates,
123
- slice,
124
- };
125
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,246 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import fs from 'node:fs/promises';
3
- import os from 'node:os';
4
- import path from 'node:path';
5
- import { analyzeRuleLabSlice } from './analyze.js';
6
- import { curateRuleLabSlice } from './curate.js';
7
- import { promoteCuratedRules } from './promote.js';
8
- async function writeJson(filePath, value) {
9
- await fs.mkdir(path.dirname(filePath), { recursive: true });
10
- await fs.writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf-8');
11
- }
12
- describe('rule-lab analyze (exact pair flow)', () => {
13
- it('builds proposal candidates and curation input directly from exact_pairs', async () => {
14
- const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'rule-lab-analyze-exact-'));
15
- const runId = 'run-x';
16
- const sliceId = 'slice-a';
17
- const slicePath = path.join(repoRoot, '.gitnexus', 'rules', 'lab', 'runs', runId, 'slices', sliceId, 'slice.json');
18
- await writeJson(slicePath, {
19
- id: sliceId,
20
- trigger_family: 'event_delegate',
21
- resource_types: ['syncvar_hook'],
22
- host_base_type: ['network_behaviour'],
23
- required_hops: ['code_runtime'],
24
- exact_pairs: [
25
- {
26
- id: 'pair-a',
27
- binding_kind: 'method_triggers_method',
28
- source_anchor: { file: 'Assets/Gameplay/SourceA.cs', line: 12, symbol: 'SourceA.Trigger' },
29
- target_anchor: { file: 'Assets/Gameplay/TargetA.cs', line: 32, symbol: 'TargetA.OnTrigger' },
30
- },
31
- {
32
- id: 'pair-b',
33
- binding_kind: 'method_triggers_method',
34
- source_anchor: { file: 'Assets/Gameplay/SourceB.cs', line: 15, symbol: 'SourceB.Trigger' },
35
- target_anchor: { file: 'Assets/Gameplay/TargetB.cs', line: 36, symbol: 'TargetB.OnTrigger' },
36
- },
37
- ],
38
- });
39
- const out = await analyzeRuleLabSlice({ repoPath: repoRoot, runId, sliceId });
40
- expect(out.candidates).toHaveLength(2);
41
- expect(out.candidates.every((candidate) => candidate.proposal_kind === 'per_anchor_rule')).toBe(true);
42
- expect(out.candidates.every((candidate) => candidate.exact_pair)).toBe(true);
43
- const curationPath = path.join(path.dirname(out.paths.candidatesPath), 'curation-input.json');
44
- const curation = JSON.parse(await fs.readFile(curationPath, 'utf-8'));
45
- expect(curation.curated).toHaveLength(2);
46
- expect(curation.curated.every((item) => item.confirmed_chain.steps.length > 0)).toBe(true);
47
- expect(curation.curated.every((item) => Array.isArray(item.resource_bindings) && item.resource_bindings.length > 0)).toBe(true);
48
- await fs.rm(repoRoot, { recursive: true, force: true });
49
- });
50
- it('fails when exact_pairs are missing', async () => {
51
- const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'rule-lab-analyze-exact-missing-'));
52
- const runId = 'run-x';
53
- const sliceId = 'slice-a';
54
- const slicePath = path.join(repoRoot, '.gitnexus', 'rules', 'lab', 'runs', runId, 'slices', sliceId, 'slice.json');
55
- await writeJson(slicePath, {
56
- id: sliceId,
57
- trigger_family: 'event_delegate',
58
- resource_types: ['syncvar_hook'],
59
- host_base_type: ['network_behaviour'],
60
- required_hops: ['code_runtime'],
61
- });
62
- await expect(analyzeRuleLabSlice({ repoPath: repoRoot, runId, sliceId })).rejects.toThrow(/exact_pairs/i);
63
- await fs.rm(repoRoot, { recursive: true, force: true });
64
- });
65
- it('fails fast when exact_pairs contain duplicate non-empty ids', async () => {
66
- const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'rule-lab-analyze-exact-dup-id-'));
67
- const runId = 'run-x';
68
- const sliceId = 'slice-a';
69
- const slicePath = path.join(repoRoot, '.gitnexus', 'rules', 'lab', 'runs', runId, 'slices', sliceId, 'slice.json');
70
- await writeJson(slicePath, {
71
- id: sliceId,
72
- trigger_family: 'event_delegate',
73
- resource_types: ['syncvar_hook'],
74
- host_base_type: ['network_behaviour'],
75
- required_hops: ['code_runtime'],
76
- exact_pairs: [
77
- {
78
- id: 'pair-dup',
79
- binding_kind: 'method_triggers_method',
80
- source_anchor: { file: 'Assets/Gameplay/SourceA.cs', line: 12, symbol: 'SourceA.Trigger' },
81
- target_anchor: { file: 'Assets/Gameplay/TargetA.cs', line: 32, symbol: 'TargetA.OnTrigger' },
82
- },
83
- {
84
- id: 'pair-dup',
85
- binding_kind: 'method_triggers_method',
86
- source_anchor: { file: 'Assets/Gameplay/SourceB.cs', line: 15, symbol: 'SourceB.Trigger' },
87
- target_anchor: { file: 'Assets/Gameplay/TargetB.cs', line: 36, symbol: 'TargetB.OnTrigger' },
88
- },
89
- ],
90
- });
91
- await expect(analyzeRuleLabSlice({ repoPath: repoRoot, runId, sliceId })).rejects.toThrow(/duplicate_exact_pair_id/i);
92
- await fs.rm(repoRoot, { recursive: true, force: true });
93
- });
94
- it('supports exact-pair analyze -> curate -> promote flow', async () => {
95
- const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'rule-lab-analyze-exact-e2e-'));
96
- const runId = 'run-x';
97
- const sliceId = 'slice-a';
98
- const sliceDir = path.join(repoRoot, '.gitnexus', 'rules', 'lab', 'runs', runId, 'slices', sliceId);
99
- const rulesRoot = path.join(repoRoot, '.gitnexus', 'rules');
100
- const slicePath = path.join(sliceDir, 'slice.json');
101
- await fs.mkdir(path.join(rulesRoot, 'approved'), { recursive: true });
102
- await fs.writeFile(path.join(rulesRoot, 'catalog.json'), JSON.stringify({ version: 1, rules: [] }, null, 2), 'utf-8');
103
- await writeJson(slicePath, {
104
- id: sliceId,
105
- trigger_family: 'event_delegate',
106
- resource_types: ['syncvar_hook'],
107
- host_base_type: ['network_behaviour'],
108
- required_hops: ['code_runtime'],
109
- exact_pairs: [
110
- {
111
- id: 'pair-a',
112
- binding_kind: 'method_triggers_method',
113
- source_anchor: { file: 'Assets/Gameplay/SourceA.cs', line: 12, symbol: 'SourceA.Trigger' },
114
- target_anchor: { file: 'Assets/Gameplay/TargetA.cs', line: 32, symbol: 'TargetA.OnTrigger' },
115
- },
116
- ],
117
- });
118
- const analyzed = await analyzeRuleLabSlice({ repoPath: repoRoot, runId, sliceId });
119
- const inputPath = path.join(path.dirname(analyzed.paths.candidatesPath), 'curation-input.json');
120
- await curateRuleLabSlice({ repoPath: repoRoot, runId, sliceId, inputPath });
121
- const promoted = await promoteCuratedRules({ repoPath: repoRoot, runId, sliceId, version: '1.0.0' });
122
- expect(promoted.promotedFiles).toHaveLength(1);
123
- await expect(fs.access(promoted.promotedFiles[0])).resolves.toBeUndefined();
124
- await fs.rm(repoRoot, { recursive: true, force: true });
125
- });
126
- it('fails closed when exact-pair symbols cannot resolve Class.Method', async () => {
127
- const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'rule-lab-analyze-exact-unresolved-'));
128
- const runId = 'run-x';
129
- const sliceId = 'slice-a';
130
- const slicePath = path.join(repoRoot, '.gitnexus', 'rules', 'lab', 'runs', runId, 'slices', sliceId, 'slice.json');
131
- await writeJson(slicePath, {
132
- id: sliceId,
133
- trigger_family: 'event_delegate',
134
- resource_types: ['syncvar_hook'],
135
- host_base_type: ['network_behaviour'],
136
- required_hops: ['code_runtime'],
137
- exact_pairs: [
138
- {
139
- id: 'pair-a',
140
- binding_kind: 'method_triggers_method',
141
- source_anchor: { file: 'Assets/Gameplay/SourceA.cs', line: 12, symbol: 'TriggerOnly' },
142
- target_anchor: { file: 'Assets/Gameplay/TargetA.cs', line: 32, symbol: 'TargetA.OnTrigger' },
143
- },
144
- ],
145
- });
146
- await expect(analyzeRuleLabSlice({ repoPath: repoRoot, runId, sliceId })).rejects.toThrow(/binding_unresolved/i);
147
- await fs.rm(repoRoot, { recursive: true, force: true });
148
- });
149
- it('builds scene_load binding with host_class_pattern + loader_methods + scene_name', async () => {
150
- const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'rule-lab-analyze-scene-load-shape-'));
151
- const runId = 'run-x';
152
- const sliceId = 'slice-a';
153
- const slicePath = path.join(repoRoot, '.gitnexus', 'rules', 'lab', 'runs', runId, 'slices', sliceId, 'slice.json');
154
- await writeJson(slicePath, {
155
- id: sliceId,
156
- trigger_family: 'event_delegate',
157
- resource_types: ['scene'],
158
- host_base_type: ['network_behaviour'],
159
- required_hops: ['code_runtime'],
160
- exact_pairs: [
161
- {
162
- id: 'pair-scene',
163
- binding_kind: 'method_triggers_scene_load',
164
- source_anchor: { file: 'Assets/Gameplay/SourceA.cs', line: 12, symbol: 'SourceA.Trigger' },
165
- target_anchor: { file: 'Assets/Scenes/BattleScene.unity', line: 1, symbol: 'BattleScene' },
166
- },
167
- ],
168
- });
169
- const analyzed = await analyzeRuleLabSlice({ repoPath: repoRoot, runId, sliceId });
170
- const curationPath = path.join(path.dirname(analyzed.paths.candidatesPath), 'curation-input.json');
171
- const curation = JSON.parse(await fs.readFile(curationPath, 'utf-8'));
172
- const binding = curation.curated[0].resource_bindings[0];
173
- expect(binding.kind).toBe('method_triggers_scene_load');
174
- expect(binding.host_class_pattern).toBe('SourceA');
175
- expect(binding.loader_methods).toEqual(['Trigger']);
176
- expect(binding.scene_name).toBe('BattleScene');
177
- expect(binding.source_class_pattern).toBeUndefined();
178
- expect(binding.source_method).toBeUndefined();
179
- expect(binding.target_class_pattern).toBeUndefined();
180
- expect(binding.target_method).toBeUndefined();
181
- await fs.rm(repoRoot, { recursive: true, force: true });
182
- });
183
- it('fails closed for scene_load when target scene token is missing', async () => {
184
- const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'rule-lab-analyze-scene-load-missing-scene-'));
185
- const runId = 'run-x';
186
- const sliceId = 'slice-a';
187
- const slicePath = path.join(repoRoot, '.gitnexus', 'rules', 'lab', 'runs', runId, 'slices', sliceId, 'slice.json');
188
- await writeJson(slicePath, {
189
- id: sliceId,
190
- trigger_family: 'event_delegate',
191
- resource_types: ['scene'],
192
- host_base_type: ['network_behaviour'],
193
- required_hops: ['code_runtime'],
194
- exact_pairs: [
195
- {
196
- id: 'pair-scene',
197
- binding_kind: 'method_triggers_scene_load',
198
- source_anchor: { file: 'Assets/Gameplay/SourceA.cs', line: 12, symbol: 'SourceA.Trigger' },
199
- target_anchor: { file: '', line: 1, symbol: '' },
200
- },
201
- ],
202
- });
203
- await expect(analyzeRuleLabSlice({ repoPath: repoRoot, runId, sliceId })).rejects.toThrow(/binding_unresolved/i);
204
- await fs.rm(repoRoot, { recursive: true, force: true });
205
- });
206
- it('ignores legacy parity/coverage fields when exact_pairs are valid', async () => {
207
- const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'rule-lab-analyze-exact-legacy-'));
208
- const runId = 'run-x';
209
- const sliceId = 'slice-a';
210
- const slicePath = path.join(repoRoot, '.gitnexus', 'rules', 'lab', 'runs', runId, 'slices', sliceId, 'slice.json');
211
- await writeJson(slicePath, {
212
- id: sliceId,
213
- trigger_family: 'event_delegate',
214
- resource_types: ['syncvar_hook'],
215
- host_base_type: ['network_behaviour'],
216
- required_hops: ['code_runtime'],
217
- exact_pairs: [
218
- {
219
- id: 'pair-a',
220
- binding_kind: 'method_triggers_method',
221
- source_anchor: { file: 'Assets/Gameplay/SourceA.cs', line: 12, symbol: 'SourceA.Trigger' },
222
- target_anchor: { file: 'Assets/Gameplay/TargetA.cs', line: 32, symbol: 'TargetA.OnTrigger' },
223
- },
224
- ],
225
- coverage_gate: {
226
- status: 'blocked',
227
- reason: 'coverage_incomplete',
228
- processed_user_matches: 0,
229
- user_raw_matches: 9,
230
- },
231
- parity_status: {
232
- status: 'blocked',
233
- reason: 'parity_missing_rules_slice',
234
- },
235
- });
236
- const analyzed = await analyzeRuleLabSlice({ repoPath: repoRoot, runId, sliceId });
237
- expect(analyzed.candidates).toHaveLength(1);
238
- expect(analyzed.candidates[0].exact_pair?.id).toBe('pair-a');
239
- await fs.rm(repoRoot, { recursive: true, force: true });
240
- });
241
- it('rejects placeholder run/slice ids', async () => {
242
- const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'rule-lab-analyze-exact-placeholder-'));
243
- await expect(analyzeRuleLabSlice({ repoPath: repoRoot, runId: '<run_id>', sliceId: '<slice_id>' })).rejects.toThrow(/placeholder/i);
244
- await fs.rm(repoRoot, { recursive: true, force: true });
245
- });
246
- });
@@ -1,5 +0,0 @@
1
- import { type RuleBundleFamily } from './compiled-bundles.js';
2
- export declare function compileRules(options: {
3
- repoPath?: string;
4
- family?: RuleBundleFamily;
5
- }): Promise<void>;
@@ -1,51 +0,0 @@
1
- import fs from 'node:fs/promises';
2
- import path from 'node:path';
3
- import { parseRuleYaml } from '../mcp/local/runtime-claim-rule-registry.js';
4
- import { writeCompiledRuleBundle } from './compiled-bundles.js';
5
- export async function compileRules(options) {
6
- const repoPath = path.resolve(options.repoPath || process.cwd());
7
- const family = options.family || 'analyze_rules';
8
- const rulesRoot = path.join(repoPath, '.gitnexus', 'rules');
9
- const catalogPath = path.join(rulesRoot, 'catalog.json');
10
- let catalog;
11
- try {
12
- catalog = JSON.parse(await fs.readFile(catalogPath, 'utf-8'));
13
- }
14
- catch {
15
- console.error(`No catalog.json found at ${catalogPath}`);
16
- process.exitCode = 1;
17
- return;
18
- }
19
- const entries = catalog.rules.filter((e) => e.enabled !== false && e.family === family);
20
- if (entries.length === 0) {
21
- console.log(`No enabled ${family} rules in catalog.`);
22
- return;
23
- }
24
- const compiled = [];
25
- for (const entry of entries) {
26
- const yamlPath = path.join(rulesRoot, entry.file);
27
- const raw = await fs.readFile(yamlPath, 'utf-8');
28
- const rule = parseRuleYaml(raw, entry.file);
29
- compiled.push({
30
- id: rule.id,
31
- version: rule.version,
32
- trigger_family: rule.trigger_family,
33
- trigger_tokens: [...(rule.match?.trigger_tokens || [])],
34
- resource_types: [...rule.resource_types],
35
- host_base_type: [...rule.host_base_type],
36
- required_hops: [...rule.required_hops],
37
- guarantees: [...rule.guarantees],
38
- non_guarantees: [...rule.non_guarantees],
39
- next_action: rule.next_action || '',
40
- file_path: entry.file,
41
- match: rule.match || { trigger_tokens: [] },
42
- topology: [],
43
- closure: { required_hops: rule.required_hops, failure_map: {} },
44
- claims: { guarantees: rule.guarantees, non_guarantees: rule.non_guarantees, next_action: rule.next_action || '' },
45
- ...(rule.resource_bindings ? { resource_bindings: rule.resource_bindings } : {}),
46
- ...(rule.lifecycle_overrides ? { lifecycle_overrides: rule.lifecycle_overrides } : {}),
47
- });
48
- }
49
- const outPath = await writeCompiledRuleBundle(rulesRoot, family, compiled);
50
- console.log(`Compiled ${compiled.length} ${family} rules → ${outPath}`);
51
- }
@@ -1,30 +0,0 @@
1
- import type { RuleDslClaims, RuleDslClosure, RuleDslMatch, RuleDslTopologyHop } from './types.js';
2
- export type RuleBundleFamily = 'analyze_rules' | 'retrieval_rules' | 'verification_rules';
3
- export interface StageAwareCompiledRule {
4
- id: string;
5
- version: string;
6
- trigger_family: string;
7
- trigger_tokens: string[];
8
- resource_types: string[];
9
- host_base_type: string[];
10
- required_hops: string[];
11
- guarantees: string[];
12
- non_guarantees: string[];
13
- next_action: string;
14
- file_path: string;
15
- match: RuleDslMatch;
16
- topology: RuleDslTopologyHop[];
17
- closure: RuleDslClosure;
18
- claims: RuleDslClaims;
19
- resource_bindings?: import('./types.js').UnityResourceBinding[];
20
- lifecycle_overrides?: import('./types.js').LifecycleOverrides;
21
- }
22
- export interface CompiledRuleBundle {
23
- bundle_version: '2.0.0';
24
- family: RuleBundleFamily;
25
- generated_at: string;
26
- rules: StageAwareCompiledRule[];
27
- }
28
- export declare function compiledBundlePath(rulesRoot: string, family: RuleBundleFamily): string;
29
- export declare function writeCompiledRuleBundle(rulesRoot: string, family: RuleBundleFamily, rules: StageAwareCompiledRule[]): Promise<string>;
30
- export declare function loadCompiledRuleBundle(repoPath: string, family: RuleBundleFamily, rulesRoot?: string): Promise<CompiledRuleBundle | undefined>;
@@ -1,36 +0,0 @@
1
- import fs from 'node:fs/promises';
2
- import path from 'node:path';
3
- export function compiledBundlePath(rulesRoot, family) {
4
- return path.join(path.resolve(rulesRoot), 'compiled', `${family}.v2.json`);
5
- }
6
- export async function writeCompiledRuleBundle(rulesRoot, family, rules) {
7
- const outPath = compiledBundlePath(rulesRoot, family);
8
- const bundle = {
9
- bundle_version: '2.0.0',
10
- family,
11
- generated_at: new Date().toISOString(),
12
- rules,
13
- };
14
- await fs.mkdir(path.dirname(outPath), { recursive: true });
15
- await fs.writeFile(outPath, `${JSON.stringify(bundle, null, 2)}\n`, 'utf-8');
16
- return outPath;
17
- }
18
- export async function loadCompiledRuleBundle(repoPath, family, rulesRoot) {
19
- const root = rulesRoot
20
- ? path.resolve(rulesRoot)
21
- : path.join(path.resolve(repoPath), '.gitnexus', 'rules');
22
- const bundlePath = compiledBundlePath(root, family);
23
- try {
24
- const raw = await fs.readFile(bundlePath, 'utf-8');
25
- const parsed = JSON.parse(raw);
26
- if (parsed.family !== family || !Array.isArray(parsed.rules)) {
27
- throw new Error(`Invalid compiled ${family} bundle: ${bundlePath}`);
28
- }
29
- return parsed;
30
- }
31
- catch (error) {
32
- if (error?.code === 'ENOENT')
33
- return undefined;
34
- throw error;
35
- }
36
- }
@@ -1,33 +0,0 @@
1
- import { getRuleLabPaths } from './paths.js';
2
- import type { RuleDslMatch, RuleDslTopologyHop, RuleDslClosure, RuleDslClaims, UnityResourceBinding } from './types.js';
3
- export interface CurateInput {
4
- repoPath: string;
5
- runId: string;
6
- sliceId: string;
7
- inputPath: string;
8
- }
9
- export interface CuratedStep {
10
- hop_type?: string;
11
- anchor: string;
12
- snippet: string;
13
- }
14
- export interface CuratedItem {
15
- id: string;
16
- rule_id?: string;
17
- title?: string;
18
- match?: RuleDslMatch;
19
- topology?: RuleDslTopologyHop[];
20
- closure?: RuleDslClosure;
21
- claims?: RuleDslClaims;
22
- resource_bindings?: UnityResourceBinding[];
23
- confirmed_chain: {
24
- steps: CuratedStep[];
25
- };
26
- guarantees: string[];
27
- non_guarantees: string[];
28
- }
29
- export interface CurateOutput {
30
- paths: ReturnType<typeof getRuleLabPaths>;
31
- curated: CuratedItem[];
32
- }
33
- export declare function curateRuleLabSlice(input: CurateInput): Promise<CurateOutput>;