@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,46 +0,0 @@
1
- import fs from 'node:fs/promises';
2
- import path from 'node:path';
3
- import { describe, expect, it } from 'vitest';
4
- import { buildRunId, getRuleLabPaths } from './paths.js';
5
- const TEST_DIR = path.dirname(new URL(import.meta.url).pathname);
6
- describe('rule-lab paths', () => {
7
- it('builds deterministic run/slice paths under .gitnexus/rules/lab/runs', () => {
8
- const runId = buildRunId({ repo: 'GitNexus', scope: 'full', seed: 'abc' });
9
- const p = getRuleLabPaths('/repo', runId, 'slice-a');
10
- expect(p.manifestPath).toContain('/.gitnexus/rules/lab/runs/');
11
- expect(p.candidatesPath).toContain('/slices/slice-a/candidates.jsonl');
12
- });
13
- it('exposes DSL v2 topology and closure schema fields', async () => {
14
- const sample = {
15
- id: 'demo.reload.v2',
16
- match: { trigger_tokens: ['reload'] },
17
- topology: [
18
- {
19
- hop: 'resource',
20
- from: { entity: 'resource' },
21
- to: { entity: 'script' },
22
- edge: { kind: 'binds_script' },
23
- },
24
- ],
25
- closure: {
26
- required_hops: ['resource'],
27
- failure_map: { missing_evidence: 'rule_matched_but_evidence_missing' },
28
- },
29
- claims: {
30
- guarantees: ['g1'],
31
- non_guarantees: ['ng1'],
32
- next_action: 'gitnexus query "Reload"',
33
- },
34
- };
35
- expect(sample.topology[0].edge.kind).toBe('binds_script');
36
- const ruleDslSchema = JSON.parse(await fs.readFile(path.join(TEST_DIR, 'schema', 'rule-dsl.schema.json'), 'utf-8'));
37
- const draftSchema = JSON.parse(await fs.readFile(path.join(TEST_DIR, 'schema', 'dsl-draft.schema.json'), 'utf-8'));
38
- expect(ruleDslSchema.properties).toHaveProperty('match');
39
- expect(ruleDslSchema.properties).toHaveProperty('topology');
40
- expect(ruleDslSchema.properties).toHaveProperty('closure');
41
- expect(ruleDslSchema.properties).toHaveProperty('claims');
42
- expect(draftSchema.properties).toHaveProperty('topology');
43
- expect(draftSchema.properties).toHaveProperty('closure');
44
- expect(draftSchema.properties).toHaveProperty('claims');
45
- });
46
- });
@@ -1,26 +0,0 @@
1
- import { getRuleLabPaths } from './paths.js';
2
- interface CatalogEntry {
3
- id: string;
4
- version: string;
5
- enabled: boolean;
6
- file: string;
7
- family?: string;
8
- }
9
- interface CatalogShape {
10
- version: number;
11
- rules: CatalogEntry[];
12
- }
13
- export interface PromoteInput {
14
- repoPath: string;
15
- runId: string;
16
- sliceId: string;
17
- version?: string;
18
- }
19
- export interface PromoteOutput {
20
- catalog: CatalogShape;
21
- promotedFiles: string[];
22
- compiledPaths: Record<'analyze_rules' | 'retrieval_rules' | 'verification_rules', string>;
23
- paths: ReturnType<typeof getRuleLabPaths>;
24
- }
25
- export declare function promoteCuratedRules(input: PromoteInput): Promise<PromoteOutput>;
26
- export {};
@@ -1,387 +0,0 @@
1
- import fs from 'node:fs/promises';
2
- import path from 'node:path';
3
- import { getRuleLabPaths } from './paths.js';
4
- import { writeCompiledRuleBundle, loadCompiledRuleBundle } from './compiled-bundles.js';
5
- function quoteYaml(value) {
6
- const raw = String(value || '');
7
- if (/^[a-zA-Z0-9._-]+$/.test(raw))
8
- return raw;
9
- return `'${raw.replace(/'/g, "''")}'`;
10
- }
11
- function inferTriggerFamily(item) {
12
- const fromTitle = String(item.title || '').trim().split(/\s+/)[0];
13
- if (fromTitle)
14
- return fromTitle.toLowerCase();
15
- return 'runtime';
16
- }
17
- function unique(values) {
18
- return [...new Set(values.map((value) => String(value || '').trim()).filter(Boolean))];
19
- }
20
- function toComparableToken(value) {
21
- return String(value || '').trim().toLowerCase();
22
- }
23
- function isForbiddenPlaceholder(value) {
24
- const token = toComparableToken(value);
25
- return token === 'unknown' || token === 'todo' || token === 'tbd' || /<[^>]+>/.test(token);
26
- }
27
- function hasPlaceholderText(value) {
28
- const raw = String(value || '').trim();
29
- return !raw || /TODO|TBD|placeholder|<[^>]+>/i.test(raw);
30
- }
31
- function assertNoPlaceholderScope(values, field) {
32
- if (values.length === 0) {
33
- throw new Error(`promote lint failed: ${field} must be non-empty`);
34
- }
35
- if (values.some((entry) => isForbiddenPlaceholder(entry))) {
36
- throw new Error(`promote lint failed: unknown scope placeholder is forbidden (${field})`);
37
- }
38
- }
39
- function assertResolvedBindings(resourceBindings, ruleId) {
40
- if (!Array.isArray(resourceBindings) || resourceBindings.length === 0)
41
- return;
42
- const raw = JSON.stringify(resourceBindings);
43
- if (/UnknownClass|UnknownMethod|UnknownSource|UnknownTarget|TODO|TBD|placeholder|<[^>]+>/i.test(raw)) {
44
- throw new Error(`binding_unresolved: placeholder binding values are forbidden for rule ${ruleId}`);
45
- }
46
- }
47
- function requireEvidenceGuard(item, ruleId) {
48
- const steps = Array.isArray(item.confirmed_chain?.steps) ? item.confirmed_chain.steps : [];
49
- if (steps.length === 0) {
50
- throw new Error(`evidence_guard_failed: confirmed_chain.steps must be non-empty for rule ${ruleId}`);
51
- }
52
- for (let index = 0; index < steps.length; index += 1) {
53
- const step = steps[index];
54
- if (hasPlaceholderText(step.anchor)) {
55
- throw new Error(`evidence_guard_failed: confirmed_chain.steps[${index}].anchor invalid for rule ${ruleId}`);
56
- }
57
- if (hasPlaceholderText(step.snippet)) {
58
- throw new Error(`evidence_guard_failed: confirmed_chain.steps[${index}].snippet invalid for rule ${ruleId}`);
59
- }
60
- }
61
- }
62
- function isExactPairEventDelegateItem(item) {
63
- const triggerTokens = Array.isArray(item.match?.trigger_tokens)
64
- ? item.match?.trigger_tokens || []
65
- : [];
66
- const hasEventDelegateTrigger = triggerTokens.some((token) => toComparableToken(token) === 'event_delegate');
67
- if (!hasEventDelegateTrigger)
68
- return false;
69
- const hasCodeRuntimeTopology = Array.isArray(item.topology)
70
- && item.topology.some((hop) => toComparableToken(hop.hop) === 'code_runtime');
71
- const hasEvidence = Array.isArray(item.confirmed_chain?.steps) && item.confirmed_chain.steps.length > 0;
72
- return hasCodeRuntimeTopology && hasEvidence;
73
- }
74
- function requireBindingGuard(item, ruleId) {
75
- const bindings = item.resource_bindings;
76
- if (isExactPairEventDelegateItem(item) && (!Array.isArray(bindings) || bindings.length === 0)) {
77
- throw new Error(`binding_unresolved: exact_pair_binding_missing for rule ${ruleId}`);
78
- }
79
- assertResolvedBindings(bindings, ruleId);
80
- }
81
- function toDraftFromCurated(item) {
82
- const triggerTokens = unique(item.match?.trigger_tokens || [inferTriggerFamily(item)]);
83
- const topology = Array.isArray(item.topology) && item.topology.length > 0
84
- ? item.topology
85
- : item.confirmed_chain.steps.map((step) => ({
86
- hop: String(step.hop_type || 'resource'),
87
- from: { entity: 'resource' },
88
- to: { entity: 'script' },
89
- edge: { kind: 'binds_script' },
90
- }));
91
- const requiredHops = unique(item.closure?.required_hops || topology.map((step) => step.hop));
92
- const failureMap = item.closure?.failure_map && Object.keys(item.closure.failure_map).length > 0
93
- ? item.closure.failure_map
94
- : { missing_evidence: 'rule_matched_but_evidence_missing' };
95
- const guarantees = unique(item.claims?.guarantees || item.guarantees);
96
- const nonGuarantees = unique(item.claims?.non_guarantees || item.non_guarantees);
97
- const nextAction = String(item.claims?.next_action || '').trim() || 'gitnexus query "runtime"';
98
- return {
99
- id: String(item.rule_id || item.id || '').trim(),
100
- version: '2.0.0',
101
- match: {
102
- trigger_tokens: triggerTokens,
103
- symbol_kind: item.match?.symbol_kind || [],
104
- module_scope: item.match?.module_scope || [],
105
- resource_types: unique(item.match?.resource_types || []),
106
- host_base_type: unique(item.match?.host_base_type || []),
107
- },
108
- topology,
109
- closure: {
110
- required_hops: requiredHops,
111
- failure_map: failureMap,
112
- },
113
- claims: {
114
- guarantees,
115
- non_guarantees: nonGuarantees,
116
- next_action: nextAction,
117
- },
118
- ...(Array.isArray(item.resource_bindings)
119
- ? { resource_bindings: item.resource_bindings }
120
- : {}),
121
- };
122
- }
123
- function compileRule(ruleId, version, draft) {
124
- const triggerFamily = String(draft.match.trigger_tokens[0] || '').trim() || 'runtime';
125
- const resourceTypes = unique(draft.match.resource_types || []);
126
- const hostBaseType = unique(draft.match.host_base_type || []);
127
- if (resourceTypes.length === 0) {
128
- resourceTypes.push('unspecified_resource');
129
- }
130
- if (hostBaseType.length === 0) {
131
- hostBaseType.push('unspecified_host');
132
- }
133
- const requiredHops = unique(draft.closure.required_hops);
134
- const guarantees = unique(draft.claims.guarantees);
135
- const nonGuarantees = unique(draft.claims.non_guarantees);
136
- const nextAction = String(draft.claims.next_action || '').trim() || 'gitnexus query "runtime"';
137
- assertNoPlaceholderScope(resourceTypes, 'resource_types');
138
- assertNoPlaceholderScope(hostBaseType, 'host_base_type');
139
- assertResolvedBindings(draft.resource_bindings, ruleId);
140
- return {
141
- id: ruleId,
142
- version,
143
- trigger_family: triggerFamily,
144
- resource_types: resourceTypes,
145
- host_base_type: hostBaseType,
146
- required_hops: requiredHops,
147
- guarantees,
148
- non_guarantees: nonGuarantees,
149
- next_action: nextAction,
150
- match: draft.match,
151
- topology: draft.topology,
152
- closure: draft.closure,
153
- claims: draft.claims,
154
- ...(draft.resource_bindings ? { resource_bindings: draft.resource_bindings } : {}),
155
- ...(draft.lifecycle_overrides ? { lifecycle_overrides: draft.lifecycle_overrides } : {}),
156
- };
157
- }
158
- function toStageAwareCompiledRule(rule, relativeFile) {
159
- return {
160
- id: rule.id,
161
- version: rule.version,
162
- trigger_family: rule.trigger_family,
163
- trigger_tokens: [...rule.match.trigger_tokens],
164
- resource_types: [...rule.resource_types],
165
- host_base_type: [...rule.host_base_type],
166
- required_hops: [...rule.required_hops],
167
- guarantees: [...rule.guarantees],
168
- non_guarantees: [...rule.non_guarantees],
169
- next_action: rule.next_action,
170
- file_path: relativeFile,
171
- match: rule.match,
172
- topology: rule.topology,
173
- closure: rule.closure,
174
- claims: rule.claims,
175
- ...(rule.resource_bindings ? { resource_bindings: rule.resource_bindings } : {}),
176
- ...(rule.lifecycle_overrides ? { lifecycle_overrides: rule.lifecycle_overrides } : {}),
177
- };
178
- }
179
- function pushList(lines, key, values, indent = '') {
180
- lines.push(`${indent}${key}:`);
181
- values.forEach((value) => lines.push(`${indent} - ${quoteYaml(value)}`));
182
- }
183
- function renderObjectLines(lines, object, indent = '') {
184
- const entries = Object.entries(object || {});
185
- for (const [key, value] of entries) {
186
- const scalar = typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean'
187
- ? String(value)
188
- : JSON.stringify(value);
189
- lines.push(`${indent}${key}: ${quoteYaml(scalar)}`);
190
- }
191
- }
192
- function buildRuleYaml(rule) {
193
- const lines = [
194
- `id: ${quoteYaml(rule.id)}`,
195
- `version: ${quoteYaml(rule.version)}`,
196
- ...(rule.family ? [`family: ${quoteYaml(rule.family)}`] : []),
197
- `trigger_family: ${quoteYaml(rule.trigger_family)}`,
198
- ];
199
- pushList(lines, 'resource_types', rule.resource_types);
200
- pushList(lines, 'host_base_type', rule.host_base_type);
201
- pushList(lines, 'required_hops', rule.required_hops);
202
- pushList(lines, 'guarantees', rule.guarantees);
203
- pushList(lines, 'non_guarantees', rule.non_guarantees);
204
- lines.push(`next_action: ${quoteYaml(rule.next_action)}`);
205
- lines.push('match:');
206
- pushList(lines, 'trigger_tokens', rule.match.trigger_tokens, ' ');
207
- if (Array.isArray(rule.match.symbol_kind) && rule.match.symbol_kind.length > 0) {
208
- pushList(lines, 'symbol_kind', rule.match.symbol_kind, ' ');
209
- }
210
- if (Array.isArray(rule.match.module_scope) && rule.match.module_scope.length > 0) {
211
- pushList(lines, 'module_scope', rule.match.module_scope, ' ');
212
- }
213
- if (Array.isArray(rule.match.resource_types) && rule.match.resource_types.length > 0) {
214
- pushList(lines, 'resource_types', rule.match.resource_types, ' ');
215
- }
216
- if (Array.isArray(rule.match.host_base_type) && rule.match.host_base_type.length > 0) {
217
- pushList(lines, 'host_base_type', rule.match.host_base_type, ' ');
218
- }
219
- lines.push('topology:');
220
- for (const hop of rule.topology) {
221
- lines.push(` - hop: ${quoteYaml(hop.hop)}`);
222
- lines.push(' from:');
223
- renderObjectLines(lines, hop.from || {}, ' ');
224
- lines.push(' to:');
225
- renderObjectLines(lines, hop.to || {}, ' ');
226
- lines.push(' edge:');
227
- lines.push(` kind: ${quoteYaml(String(hop.edge?.kind || 'calls'))}`);
228
- if (hop.constraints && Object.keys(hop.constraints).length > 0) {
229
- lines.push(' constraints:');
230
- renderObjectLines(lines, hop.constraints, ' ');
231
- }
232
- }
233
- lines.push('closure:');
234
- pushList(lines, 'required_hops', rule.closure.required_hops, ' ');
235
- lines.push(' failure_map:');
236
- for (const [key, value] of Object.entries(rule.closure.failure_map || {})) {
237
- lines.push(` ${quoteYaml(key)}: ${quoteYaml(String(value || 'rule_matched_but_evidence_missing'))}`);
238
- }
239
- lines.push('claims:');
240
- pushList(lines, 'guarantees', rule.claims.guarantees, ' ');
241
- pushList(lines, 'non_guarantees', rule.claims.non_guarantees, ' ');
242
- lines.push(` next_action: ${quoteYaml(rule.claims.next_action)}`);
243
- if (rule.resource_bindings && rule.resource_bindings.length > 0) {
244
- lines.push('resource_bindings:');
245
- for (const binding of rule.resource_bindings) {
246
- lines.push(` - kind: ${binding.kind}`);
247
- if (binding.ref_field_pattern)
248
- lines.push(` ref_field_pattern: ${quoteYaml(binding.ref_field_pattern)}`);
249
- if (binding.target_entry_points?.length)
250
- pushList(lines, 'target_entry_points', binding.target_entry_points, ' ');
251
- if (binding.host_class_pattern)
252
- lines.push(` host_class_pattern: ${quoteYaml(binding.host_class_pattern)}`);
253
- if (binding.field_name)
254
- lines.push(` field_name: ${quoteYaml(binding.field_name)}`);
255
- if (binding.loader_methods?.length)
256
- pushList(lines, 'loader_methods', binding.loader_methods, ' ');
257
- if (binding.scene_name)
258
- lines.push(` scene_name: ${quoteYaml(binding.scene_name)}`);
259
- if (binding.source_class_pattern)
260
- lines.push(` source_class_pattern: ${quoteYaml(binding.source_class_pattern)}`);
261
- if (binding.source_method)
262
- lines.push(` source_method: ${quoteYaml(binding.source_method)}`);
263
- if (binding.target_class_pattern)
264
- lines.push(` target_class_pattern: ${quoteYaml(binding.target_class_pattern)}`);
265
- if (binding.target_method)
266
- lines.push(` target_method: ${quoteYaml(binding.target_method)}`);
267
- }
268
- }
269
- if (rule.lifecycle_overrides) {
270
- lines.push('lifecycle_overrides:');
271
- if (rule.lifecycle_overrides.additional_entry_points?.length) {
272
- pushList(lines, 'additional_entry_points', rule.lifecycle_overrides.additional_entry_points, ' ');
273
- }
274
- if (rule.lifecycle_overrides.scope) {
275
- lines.push(` scope: ${quoteYaml(rule.lifecycle_overrides.scope)}`);
276
- }
277
- }
278
- return `${lines.join('\n')}\n`;
279
- }
280
- async function readCatalog(catalogPath) {
281
- try {
282
- const raw = await fs.readFile(catalogPath, 'utf-8');
283
- const parsed = JSON.parse(raw);
284
- return {
285
- version: Number(parsed.version || 1),
286
- rules: Array.isArray(parsed.rules) ? parsed.rules : [],
287
- };
288
- }
289
- catch (error) {
290
- if (error?.code === 'ENOENT') {
291
- return { version: 1, rules: [] };
292
- }
293
- throw error;
294
- }
295
- }
296
- async function fileExists(filePath) {
297
- try {
298
- await fs.access(filePath);
299
- return true;
300
- }
301
- catch (error) {
302
- if (error?.code === 'ENOENT')
303
- return false;
304
- throw error;
305
- }
306
- }
307
- export async function promoteCuratedRules(input) {
308
- const normalizedRepoPath = path.resolve(input.repoPath);
309
- const paths = getRuleLabPaths(normalizedRepoPath, input.runId, input.sliceId);
310
- const version = String(input.version || '1.0.0');
311
- const curatedRaw = await fs.readFile(paths.curatedPath, 'utf-8');
312
- const curatedDoc = JSON.parse(curatedRaw);
313
- const curatedItems = Array.isArray(curatedDoc.curated) ? curatedDoc.curated : [];
314
- if (curatedItems.length === 0) {
315
- throw new Error('No curated candidates available for promotion');
316
- }
317
- let dslDraftFromCurate;
318
- try {
319
- const dslDraftRaw = await fs.readFile(path.join(path.dirname(paths.curatedPath), 'dsl-draft.json'), 'utf-8');
320
- dslDraftFromCurate = JSON.parse(dslDraftRaw);
321
- }
322
- catch (error) {
323
- if (error?.code !== 'ENOENT')
324
- throw error;
325
- }
326
- const catalogPath = path.join(paths.rulesRoot, 'catalog.json');
327
- const catalog = await readCatalog(catalogPath);
328
- const promotedFiles = [];
329
- const compiledRules = [];
330
- await fs.mkdir(paths.promotedRoot, { recursive: true });
331
- for (const item of curatedItems) {
332
- const ruleId = String(item.rule_id || item.id || '').trim();
333
- if (!ruleId) {
334
- throw new Error('curated item missing rule id');
335
- }
336
- requireEvidenceGuard(item, ruleId);
337
- requireBindingGuard(item, ruleId);
338
- if (catalog.rules.some((entry) => entry.id === ruleId)) {
339
- throw new Error(`duplicate_rule_id: ${ruleId} already exists in catalog`);
340
- }
341
- const relativeFile = path.join('approved', `${ruleId}.yaml`).split(path.sep).join('/');
342
- const absoluteFile = path.join(paths.rulesRoot, relativeFile);
343
- if (await fileExists(absoluteFile)) {
344
- throw new Error(`duplicate_rule_id: ${ruleId} already exists at ${relativeFile}`);
345
- }
346
- const draft = dslDraftFromCurate && curatedItems.length === 1
347
- ? { ...dslDraftFromCurate, id: ruleId, version }
348
- : { ...toDraftFromCurated(item), id: ruleId, version };
349
- const compiledRule = compileRule(ruleId, version, draft);
350
- const yaml = buildRuleYaml(compiledRule);
351
- await fs.writeFile(absoluteFile, yaml, 'utf-8');
352
- promotedFiles.push(absoluteFile);
353
- compiledRules.push(toStageAwareCompiledRule(compiledRule, relativeFile));
354
- const nextEntry = {
355
- id: ruleId,
356
- version,
357
- enabled: true,
358
- file: relativeFile,
359
- ...(compiledRule.family ? { family: compiledRule.family } : {}),
360
- };
361
- catalog.rules.push(nextEntry);
362
- }
363
- await fs.mkdir(path.dirname(catalogPath), { recursive: true });
364
- await fs.writeFile(catalogPath, `${JSON.stringify(catalog, null, 2)}\n`, 'utf-8');
365
- const mergeCompiledRules = async (family) => {
366
- const existing = await loadCompiledRuleBundle(normalizedRepoPath, family, paths.rulesRoot);
367
- const merged = new Map();
368
- for (const rule of existing?.rules || []) {
369
- merged.set(rule.id, rule);
370
- }
371
- for (const rule of compiledRules) {
372
- merged.set(rule.id, rule);
373
- }
374
- return writeCompiledRuleBundle(paths.rulesRoot, family, [...merged.values()]);
375
- };
376
- const compiledPaths = {
377
- analyze_rules: await mergeCompiledRules('analyze_rules'),
378
- retrieval_rules: await mergeCompiledRules('retrieval_rules'),
379
- verification_rules: await mergeCompiledRules('verification_rules'),
380
- };
381
- return {
382
- catalog,
383
- promotedFiles,
384
- compiledPaths,
385
- paths,
386
- };
387
- }
@@ -1 +0,0 @@
1
- export {};