@veewo/gitnexus 1.5.7 → 1.5.8

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 (67) hide show
  1. package/dist/cli/ai-context.js +1 -7
  2. package/dist/cli/analyze-runtime-summary.js +0 -1
  3. package/dist/cli/analyze-runtime-summary.test.js +0 -2
  4. package/dist/cli/analyze-summary.d.ts +0 -2
  5. package/dist/cli/analyze-summary.js +0 -24
  6. package/dist/cli/analyze-summary.test.js +1 -65
  7. package/dist/cli/analyze.js +1 -5
  8. package/dist/cli/index.js +0 -2
  9. package/dist/core/ingestion/pipeline.js +0 -43
  10. package/dist/mcp/local/agent-safe-response.js +1 -1
  11. package/dist/mcp/local/local-backend.d.ts +0 -23
  12. package/dist/mcp/local/local-backend.js +69 -248
  13. package/dist/mcp/local/runtime-chain-verify.test.js +0 -49
  14. package/dist/mcp/local/runtime-claim-rule-registry.d.ts +0 -11
  15. package/dist/mcp/local/runtime-claim-rule-registry.js +0 -159
  16. package/dist/mcp/local/runtime-claim-rule-registry.test.js +67 -214
  17. package/dist/mcp/tools.js +0 -70
  18. package/dist/types/pipeline.d.ts +0 -3
  19. package/package.json +1 -1
  20. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.d.ts +0 -60
  21. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.js +0 -395
  22. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.test.d.ts +0 -1
  23. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.test.js +0 -41
  24. package/dist/cli/rule-lab.d.ts +0 -38
  25. package/dist/cli/rule-lab.js +0 -148
  26. package/dist/cli/rule-lab.test.d.ts +0 -1
  27. package/dist/cli/rule-lab.test.js +0 -31
  28. package/dist/core/ingestion/unity-runtime-binding-rules.d.ts +0 -26
  29. package/dist/core/ingestion/unity-runtime-binding-rules.js +0 -408
  30. package/dist/rule-lab/analyze.d.ts +0 -13
  31. package/dist/rule-lab/analyze.js +0 -125
  32. package/dist/rule-lab/analyze.test.d.ts +0 -1
  33. package/dist/rule-lab/analyze.test.js +0 -246
  34. package/dist/rule-lab/compile.d.ts +0 -5
  35. package/dist/rule-lab/compile.js +0 -51
  36. package/dist/rule-lab/compiled-bundles.d.ts +0 -30
  37. package/dist/rule-lab/compiled-bundles.js +0 -36
  38. package/dist/rule-lab/curate.d.ts +0 -33
  39. package/dist/rule-lab/curate.js +0 -155
  40. package/dist/rule-lab/curate.test.d.ts +0 -1
  41. package/dist/rule-lab/curate.test.js +0 -137
  42. package/dist/rule-lab/curation-input-builder.d.ts +0 -45
  43. package/dist/rule-lab/curation-input-builder.js +0 -133
  44. package/dist/rule-lab/discover.d.ts +0 -13
  45. package/dist/rule-lab/discover.js +0 -74
  46. package/dist/rule-lab/discover.test.d.ts +0 -1
  47. package/dist/rule-lab/discover.test.js +0 -42
  48. package/dist/rule-lab/paths.d.ts +0 -21
  49. package/dist/rule-lab/paths.js +0 -37
  50. package/dist/rule-lab/paths.test.d.ts +0 -1
  51. package/dist/rule-lab/paths.test.js +0 -46
  52. package/dist/rule-lab/promote.d.ts +0 -26
  53. package/dist/rule-lab/promote.js +0 -387
  54. package/dist/rule-lab/promote.test.d.ts +0 -1
  55. package/dist/rule-lab/promote.test.js +0 -314
  56. package/dist/rule-lab/regress.d.ts +0 -60
  57. package/dist/rule-lab/regress.js +0 -122
  58. package/dist/rule-lab/regress.test.d.ts +0 -1
  59. package/dist/rule-lab/regress.test.js +0 -68
  60. package/dist/rule-lab/review-pack.d.ts +0 -34
  61. package/dist/rule-lab/review-pack.js +0 -165
  62. package/dist/rule-lab/review-pack.test.d.ts +0 -1
  63. package/dist/rule-lab/review-pack.test.js +0 -116
  64. package/dist/rule-lab/types.d.ts +0 -135
  65. package/dist/rule-lab/types.js +0 -1
  66. package/skills/_shared/unity-rule-authoring-contract.md +0 -64
  67. package/skills/gitnexus-unity-rule-gen.md +0 -107
@@ -1,314 +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 { promoteCuratedRules } from './promote.js';
6
- import { loadRuleRegistry } from '../mcp/local/runtime-claim-rule-registry.js';
7
- describe('rule-lab promote', () => {
8
- it('promotes curated candidate into approved yaml and catalog entry', async () => {
9
- const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'rule-lab-promote-'));
10
- const rulesRoot = path.join(repoRoot, '.gitnexus', 'rules');
11
- const sliceDir = path.join(rulesRoot, 'lab', 'runs', 'run-x', 'slices', 'slice-a');
12
- await fs.mkdir(path.join(rulesRoot, 'approved'), { recursive: true });
13
- await fs.mkdir(sliceDir, { recursive: true });
14
- await fs.writeFile(path.join(rulesRoot, 'catalog.json'), JSON.stringify({ version: 1, rules: [] }, null, 2), 'utf-8');
15
- await fs.writeFile(path.join(sliceDir, 'curated.json'), JSON.stringify({
16
- run_id: 'run-x',
17
- slice_id: 'slice-a',
18
- curated: [
19
- {
20
- id: 'candidate-1',
21
- rule_id: 'demo.rule.v1',
22
- title: 'demo rule',
23
- match: { trigger_tokens: ['reload'] },
24
- topology: [
25
- { hop: 'resource', from: { entity: 'resource' }, to: { entity: 'script' }, edge: { kind: 'binds_script' } },
26
- ],
27
- closure: {
28
- required_hops: ['resource'],
29
- failure_map: { missing_evidence: 'rule_matched_but_evidence_missing' },
30
- },
31
- claims: {
32
- guarantees: ['can verify startup graph trigger'],
33
- non_guarantees: ['does not prove all runtime states'],
34
- next_action: 'gitnexus query "reload"',
35
- },
36
- confirmed_chain: {
37
- steps: [{ hop_type: 'resource', anchor: 'Assets/Demo.prefab:12', snippet: 'Reload' }],
38
- },
39
- guarantees: ['can verify startup graph trigger'],
40
- non_guarantees: ['does not prove all runtime states'],
41
- },
42
- ],
43
- }, null, 2), 'utf-8');
44
- const out = await promoteCuratedRules({ repoPath: repoRoot, runId: 'run-x', sliceId: 'slice-a' });
45
- expect(out.catalog.rules.some((r) => r.id === 'demo.rule.v1')).toBe(true);
46
- expect(out.promotedFiles[0]).toMatch(/rules\/approved\/.*\.yaml$/);
47
- await fs.rm(repoRoot, { recursive: true, force: true });
48
- });
49
- it('emits stage-aware compiled bundles for analyze, retrieval, and verification', async () => {
50
- const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'rule-lab-promote-'));
51
- const rulesRoot = path.join(repoRoot, '.gitnexus', 'rules');
52
- const sliceDir = path.join(rulesRoot, 'lab', 'runs', 'run-x', 'slices', 'slice-a');
53
- await fs.mkdir(path.join(rulesRoot, 'approved'), { recursive: true });
54
- await fs.mkdir(sliceDir, { recursive: true });
55
- await fs.writeFile(path.join(rulesRoot, 'catalog.json'), JSON.stringify({ version: 1, rules: [] }, null, 2), 'utf-8');
56
- await fs.writeFile(path.join(sliceDir, 'curated.json'), JSON.stringify({
57
- run_id: 'run-x',
58
- slice_id: 'slice-a',
59
- curated: [
60
- {
61
- id: 'candidate-1',
62
- rule_id: 'demo.rule.v2',
63
- title: 'demo rule',
64
- match: {
65
- trigger_tokens: ['reload'],
66
- resource_types: ['asset'],
67
- host_base_type: ['ReloadBase'],
68
- },
69
- topology: [
70
- { hop: 'resource', from: { entity: 'resource' }, to: { entity: 'script' }, edge: { kind: 'binds_script' } },
71
- { hop: 'code_runtime', from: { entity: 'script' }, to: { entity: 'runtime' }, edge: { kind: 'calls' } },
72
- ],
73
- closure: {
74
- required_hops: ['resource', 'code_runtime'],
75
- failure_map: { missing_evidence: 'rule_matched_but_evidence_missing' },
76
- },
77
- claims: {
78
- guarantees: ['reload_chain_closed'],
79
- non_guarantees: ['no_runtime_execution'],
80
- next_action: 'gitnexus query "reload"',
81
- },
82
- confirmed_chain: {
83
- steps: [{ hop_type: 'resource', anchor: 'Assets/Demo.prefab:12', snippet: 'Reload' }],
84
- },
85
- guarantees: ['reload_chain_closed'],
86
- non_guarantees: ['no_runtime_execution'],
87
- },
88
- ],
89
- }, null, 2), 'utf-8');
90
- const out = await promoteCuratedRules({ repoPath: repoRoot, runId: 'run-x', sliceId: 'slice-a', version: '2.0.0' });
91
- expect(out).toHaveProperty('compiledPaths');
92
- const analyzeBundlePath = path.join(rulesRoot, 'compiled', 'analyze_rules.v2.json');
93
- const retrievalBundlePath = path.join(rulesRoot, 'compiled', 'retrieval_rules.v2.json');
94
- const verificationBundlePath = path.join(rulesRoot, 'compiled', 'verification_rules.v2.json');
95
- const analyzeBundle = JSON.parse(await fs.readFile(analyzeBundlePath, 'utf-8'));
96
- const retrievalBundle = JSON.parse(await fs.readFile(retrievalBundlePath, 'utf-8'));
97
- const verificationBundle = JSON.parse(await fs.readFile(verificationBundlePath, 'utf-8'));
98
- expect(analyzeBundle.family).toBe('analyze_rules');
99
- expect(retrievalBundle.family).toBe('retrieval_rules');
100
- expect(verificationBundle.family).toBe('verification_rules');
101
- expect(analyzeBundle.rules[0].id).toBe('demo.rule.v2');
102
- expect(retrievalBundle.rules[0].claims.next_action).toBe('gitnexus query "reload"');
103
- expect(verificationBundle.rules[0].closure.required_hops).toEqual(['resource', 'code_runtime']);
104
- await fs.rm(repoRoot, { recursive: true, force: true });
105
- });
106
- it('rejects promote when resource_types or host_base_type are unknown', async () => {
107
- const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'rule-lab-promote-'));
108
- const rulesRoot = path.join(repoRoot, '.gitnexus', 'rules');
109
- const sliceDir = path.join(rulesRoot, 'lab', 'runs', 'run-x', 'slices', 'slice-a');
110
- await fs.mkdir(path.join(rulesRoot, 'approved'), { recursive: true });
111
- await fs.mkdir(sliceDir, { recursive: true });
112
- await fs.writeFile(path.join(rulesRoot, 'catalog.json'), JSON.stringify({ version: 1, rules: [] }, null, 2), 'utf-8');
113
- await fs.writeFile(path.join(sliceDir, 'curated.json'), JSON.stringify({
114
- run_id: 'run-x',
115
- slice_id: 'slice-a',
116
- curated: [
117
- {
118
- id: 'candidate-unknown',
119
- rule_id: 'demo.rule.v2',
120
- match: { trigger_tokens: ['reload'] },
121
- topology: [
122
- { hop: 'resource', from: { entity: 'resource' }, to: { entity: 'script' }, edge: { kind: 'binds_script' } },
123
- ],
124
- closure: {
125
- required_hops: ['resource'],
126
- failure_map: { missing_evidence: 'rule_matched_but_evidence_missing' },
127
- },
128
- claims: {
129
- guarantees: ['reload_chain_closed'],
130
- non_guarantees: ['no_runtime_execution'],
131
- next_action: 'gitnexus query "reload"',
132
- },
133
- confirmed_chain: {
134
- steps: [{ hop_type: 'resource', anchor: 'Assets/Demo.prefab:9', snippet: 'Reload' }],
135
- },
136
- guarantees: ['reload_chain_closed'],
137
- non_guarantees: ['no_runtime_execution'],
138
- },
139
- ],
140
- }, null, 2), 'utf-8');
141
- await fs.writeFile(path.join(sliceDir, 'dsl-draft.json'), JSON.stringify({
142
- id: 'demo.rule.v2',
143
- version: '2.0.0',
144
- match: {
145
- trigger_tokens: ['reload'],
146
- resource_types: ['unknown'],
147
- host_base_type: ['unknown'],
148
- },
149
- topology: [
150
- { hop: 'resource', from: { entity: 'resource' }, to: { entity: 'script' }, edge: { kind: 'binds_script' } },
151
- ],
152
- closure: {
153
- required_hops: ['resource'],
154
- failure_map: { missing_evidence: 'rule_matched_but_evidence_missing' },
155
- },
156
- claims: {
157
- guarantees: ['reload_chain_closed'],
158
- non_guarantees: ['no_runtime_execution'],
159
- next_action: 'gitnexus query "reload"',
160
- },
161
- }, null, 2), 'utf-8');
162
- await expect(promoteCuratedRules({ repoPath: repoRoot, runId: 'run-x', sliceId: 'slice-a' })).rejects.toThrow(/unknown/i);
163
- await fs.rm(repoRoot, { recursive: true, force: true });
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
- });
314
- });
@@ -1,60 +0,0 @@
1
- export interface RuleLabRegressInput {
2
- precision: number;
3
- coverage: number;
4
- probes?: Array<{
5
- id: string;
6
- pass: boolean;
7
- replay_command: string;
8
- bucket?: 'anchor' | 'holdout' | 'negative';
9
- key_resource_hit?: boolean;
10
- next_hop_usable?: boolean;
11
- hint_drift?: boolean;
12
- false_positive_anchor_leak?: boolean;
13
- }>;
14
- repoPath?: string;
15
- runId?: string;
16
- }
17
- export interface RuleLabRegressOutput {
18
- pass: boolean;
19
- failures: string[];
20
- metrics: {
21
- precision: number;
22
- coverage: number;
23
- probe_pass_rate: number;
24
- key_resource_hit_rate: number;
25
- next_hop_usability_rate: number;
26
- hint_drift_rate: number;
27
- };
28
- bucket_metrics: {
29
- anchor: {
30
- total: number;
31
- passed: number;
32
- anchor_pass_rate: number;
33
- };
34
- holdout: {
35
- total: number;
36
- usable: number;
37
- next_hop_usability_rate: number;
38
- };
39
- negative: {
40
- total: number;
41
- false_positive: number;
42
- false_positive_rate: number;
43
- };
44
- };
45
- threshold_checks: {
46
- precision_pass: boolean;
47
- coverage_pass: boolean;
48
- probe_pass_rate_pass: boolean;
49
- anchor_pass: boolean;
50
- holdout_pass: boolean;
51
- negative_pass: boolean;
52
- };
53
- probe_results: Array<{
54
- id: string;
55
- pass: boolean;
56
- replay_command: string;
57
- }>;
58
- reportPath?: string;
59
- }
60
- export declare function runRuleLabRegress(input: RuleLabRegressInput): Promise<RuleLabRegressOutput>;
@@ -1,122 +0,0 @@
1
- import fs from 'node:fs/promises';
2
- import path from 'node:path';
3
- const PRECISION_THRESHOLD = 0.9;
4
- const COVERAGE_THRESHOLD = 0.8;
5
- const PROBE_PASS_RATE_THRESHOLD = 0.85;
6
- function buildReportMarkdown(output) {
7
- const lines = [];
8
- lines.push('# Rule Lab Regression Report');
9
- lines.push('');
10
- lines.push('## Metrics');
11
- lines.push(`- metrics.precision: ${output.metrics.precision}`);
12
- lines.push(`- metrics.coverage: ${output.metrics.coverage}`);
13
- lines.push(`- metrics.probe_pass_rate: ${output.metrics.probe_pass_rate}`);
14
- lines.push(`- metrics.key_resource_hit_rate: ${output.metrics.key_resource_hit_rate}`);
15
- lines.push(`- metrics.next_hop_usability_rate: ${output.metrics.next_hop_usability_rate}`);
16
- lines.push(`- metrics.hint_drift_rate: ${output.metrics.hint_drift_rate}`);
17
- lines.push('');
18
- lines.push('## Gate');
19
- lines.push(`- pass: ${output.pass}`);
20
- lines.push(`- failures: ${output.failures.join(', ') || 'none'}`);
21
- lines.push(`- threshold_checks: ${JSON.stringify(output.threshold_checks)}`);
22
- lines.push('');
23
- lines.push('## Buckets');
24
- lines.push(`- anchor: ${JSON.stringify(output.bucket_metrics.anchor)}`);
25
- lines.push(`- holdout: ${JSON.stringify(output.bucket_metrics.holdout)}`);
26
- lines.push(`- negative: ${JSON.stringify(output.bucket_metrics.negative)}`);
27
- lines.push('');
28
- lines.push('## Probe Results');
29
- if (output.probe_results.length === 0) {
30
- lines.push('- none');
31
- }
32
- else {
33
- for (const probe of output.probe_results) {
34
- lines.push(`- ${probe.id}: pass=${probe.pass} | replay_command=${probe.replay_command}`);
35
- }
36
- }
37
- lines.push('');
38
- return `${lines.join('\n')}\n`;
39
- }
40
- export async function runRuleLabRegress(input) {
41
- const failures = [];
42
- const probes = Array.isArray(input.probes) ? input.probes : [];
43
- if (input.precision < PRECISION_THRESHOLD) {
44
- failures.push('precision_below_threshold');
45
- }
46
- if (input.coverage < COVERAGE_THRESHOLD) {
47
- failures.push('coverage_below_threshold');
48
- }
49
- const passedProbes = probes.filter((probe) => probe.pass).length;
50
- const keyResourceProbeCount = probes.filter((probe) => typeof probe.key_resource_hit === 'boolean').length;
51
- const keyResourceHitCount = probes.filter((probe) => probe.key_resource_hit === true).length;
52
- const nextHopProbeCount = probes.filter((probe) => typeof probe.next_hop_usable === 'boolean').length;
53
- const nextHopUsableCount = probes.filter((probe) => probe.next_hop_usable === true).length;
54
- const hintDriftProbeCount = probes.filter((probe) => typeof probe.hint_drift === 'boolean').length;
55
- const hintDriftCount = probes.filter((probe) => probe.hint_drift === true).length;
56
- const probePassRate = probes.length > 0
57
- ? passedProbes / probes.length
58
- : 0;
59
- if (probePassRate < PROBE_PASS_RATE_THRESHOLD) {
60
- failures.push('probe_pass_rate_below_threshold');
61
- }
62
- const anchorProbes = probes.filter((probe) => probe.bucket === 'anchor');
63
- const holdoutProbes = probes.filter((probe) => probe.bucket === 'holdout');
64
- const negativeProbes = probes.filter((probe) => probe.bucket === 'negative');
65
- const anchorPassed = anchorProbes.filter((probe) => probe.pass).length;
66
- const holdoutUsable = holdoutProbes.filter((probe) => probe.next_hop_usable === true).length;
67
- const negativeFalsePositive = negativeProbes.filter((probe) => probe.false_positive_anchor_leak === true).length;
68
- const anchorPassRate = anchorProbes.length > 0 ? anchorPassed / anchorProbes.length : 0;
69
- const holdoutUsabilityRate = holdoutProbes.length > 0 ? holdoutUsable / holdoutProbes.length : 0;
70
- const negativeFalsePositiveRate = negativeProbes.length > 0 ? negativeFalsePositive / negativeProbes.length : 0;
71
- if (anchorProbes.length === 0) {
72
- failures.push('anchor_bucket_missing');
73
- }
74
- if (holdoutProbes.length === 0) {
75
- failures.push('holdout_bucket_missing');
76
- }
77
- if (negativeProbes.length === 0) {
78
- failures.push('negative_bucket_missing');
79
- }
80
- if (anchorProbes.length > 0 && anchorPassRate < 1) {
81
- failures.push('anchor_pass_rate_below_threshold');
82
- }
83
- if (holdoutProbes.length > 0 && holdoutUsabilityRate < 0.85) {
84
- failures.push('holdout_next_hop_usability_below_threshold');
85
- }
86
- if (negativeProbes.length > 0 && negativeFalsePositiveRate > 0.1) {
87
- failures.push('negative_false_positive_rate_above_threshold');
88
- }
89
- const output = {
90
- pass: failures.length === 0,
91
- failures,
92
- metrics: {
93
- precision: input.precision,
94
- coverage: input.coverage,
95
- probe_pass_rate: probePassRate,
96
- key_resource_hit_rate: keyResourceProbeCount > 0 ? keyResourceHitCount / keyResourceProbeCount : 0,
97
- next_hop_usability_rate: nextHopProbeCount > 0 ? nextHopUsableCount / nextHopProbeCount : 0,
98
- hint_drift_rate: hintDriftProbeCount > 0 ? hintDriftCount / hintDriftProbeCount : 0,
99
- },
100
- bucket_metrics: {
101
- anchor: { total: anchorProbes.length, passed: anchorPassed, anchor_pass_rate: anchorPassRate },
102
- holdout: { total: holdoutProbes.length, usable: holdoutUsable, next_hop_usability_rate: holdoutUsabilityRate },
103
- negative: { total: negativeProbes.length, false_positive: negativeFalsePositive, false_positive_rate: negativeFalsePositiveRate },
104
- },
105
- threshold_checks: {
106
- precision_pass: input.precision >= PRECISION_THRESHOLD,
107
- coverage_pass: input.coverage >= COVERAGE_THRESHOLD,
108
- probe_pass_rate_pass: probePassRate >= PROBE_PASS_RATE_THRESHOLD,
109
- anchor_pass: anchorProbes.length > 0 && anchorPassRate >= 1,
110
- holdout_pass: holdoutProbes.length > 0 && holdoutUsabilityRate >= 0.85,
111
- negative_pass: negativeProbes.length > 0 && negativeFalsePositiveRate <= 0.1,
112
- },
113
- probe_results: probes,
114
- };
115
- if (input.repoPath && input.runId) {
116
- const reportPath = path.join(path.resolve(input.repoPath), '.gitnexus', 'rules', 'reports', `${input.runId}-regress.md`);
117
- await fs.mkdir(path.dirname(reportPath), { recursive: true });
118
- await fs.writeFile(reportPath, buildReportMarkdown(output), 'utf-8');
119
- output.reportPath = reportPath;
120
- }
121
- return output;
122
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,68 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { runRuleLabRegress } from './regress.js';
3
- describe('rule-lab regress', () => {
4
- it('fails when precision or coverage is below threshold', async () => {
5
- const out = await runRuleLabRegress({ precision: 0.85, coverage: 0.92 });
6
- expect(out.pass).toBe(false);
7
- expect(out.failures).toContain('precision_below_threshold');
8
- });
9
- it('fails regress when probe pass-rate is below threshold even if metrics are high', async () => {
10
- const out = await runRuleLabRegress({
11
- precision: 0.95,
12
- coverage: 0.95,
13
- probes: [
14
- { id: 'p1', pass: false, replay_command: 'gitnexus query "Reload"' },
15
- ],
16
- });
17
- expect(out.pass).toBe(false);
18
- expect(out.failures).toContain('probe_pass_rate_below_threshold');
19
- expect(out.metrics.probe_pass_rate).toBeLessThan(0.85);
20
- });
21
- it('reports stage-aware metrics and three-bucket threshold checks', async () => {
22
- const out = await runRuleLabRegress({
23
- precision: 0.95,
24
- coverage: 0.9,
25
- probes: [
26
- {
27
- id: 'anchor-1',
28
- bucket: 'anchor',
29
- pass: true,
30
- replay_command: 'gitnexus query "anchor"',
31
- key_resource_hit: true,
32
- next_hop_usable: true,
33
- hint_drift: false,
34
- false_positive_anchor_leak: false,
35
- },
36
- {
37
- id: 'holdout-1',
38
- bucket: 'holdout',
39
- pass: true,
40
- replay_command: 'gitnexus query "holdout"',
41
- key_resource_hit: true,
42
- next_hop_usable: true,
43
- hint_drift: false,
44
- false_positive_anchor_leak: false,
45
- },
46
- {
47
- id: 'negative-1',
48
- bucket: 'negative',
49
- pass: true,
50
- replay_command: 'gitnexus query "negative"',
51
- key_resource_hit: false,
52
- next_hop_usable: false,
53
- hint_drift: false,
54
- false_positive_anchor_leak: false,
55
- },
56
- ],
57
- });
58
- expect(out.metrics).toHaveProperty('key_resource_hit_rate');
59
- expect(out.metrics).toHaveProperty('next_hop_usability_rate');
60
- expect(out.metrics).toHaveProperty('hint_drift_rate');
61
- expect(out.bucket_metrics.anchor.anchor_pass_rate).toBe(1);
62
- expect(out.bucket_metrics.holdout.next_hop_usability_rate).toBe(1);
63
- expect(out.bucket_metrics.negative.false_positive_rate).toBe(0);
64
- expect(out.threshold_checks.anchor_pass).toBe(true);
65
- expect(out.threshold_checks.holdout_pass).toBe(true);
66
- expect(out.threshold_checks.negative_pass).toBe(true);
67
- });
68
- });
@@ -1,34 +0,0 @@
1
- import { getRuleLabPaths } from './paths.js';
2
- export interface ReviewPackInput {
3
- repoPath: string;
4
- runId: string;
5
- sliceId: string;
6
- maxTokens: number;
7
- }
8
- export interface ReviewPackCard {
9
- card_id: string;
10
- title: string;
11
- candidate_ids: string[];
12
- decision_inputs: {
13
- required_hops: string[];
14
- failure_map: Record<string, string>;
15
- guarantees: string[];
16
- non_guarantees: string[];
17
- draft_rule_ids: string[];
18
- aggregation_modes: string[];
19
- binding_kinds: string[];
20
- };
21
- }
22
- export interface ReviewPackMeta {
23
- token_budget: number;
24
- token_budget_estimate: number;
25
- truncated: boolean;
26
- total_candidates: number;
27
- included_candidates: number;
28
- }
29
- export interface ReviewPackOutput {
30
- paths: ReturnType<typeof getRuleLabPaths>;
31
- meta: ReviewPackMeta;
32
- cards: ReviewPackCard[];
33
- }
34
- export declare function buildReviewPack(input: ReviewPackInput): Promise<ReviewPackOutput>;