@veewo/gitnexus 1.5.0-rc.4 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/dist/benchmark/agent-context/runner.js +3 -0
  2. package/dist/benchmark/agent-context/runner.test.js +22 -0
  3. package/dist/benchmark/agent-context/tool-runner.d.ts +7 -6
  4. package/dist/benchmark/agent-safe-query-context/io.d.ts +2 -0
  5. package/dist/benchmark/agent-safe-query-context/io.js +86 -0
  6. package/dist/benchmark/agent-safe-query-context/io.test.d.ts +1 -0
  7. package/dist/benchmark/agent-safe-query-context/io.test.js +13 -0
  8. package/dist/benchmark/agent-safe-query-context/report.d.ts +57 -0
  9. package/dist/benchmark/agent-safe-query-context/report.js +159 -0
  10. package/dist/benchmark/agent-safe-query-context/report.test.d.ts +1 -0
  11. package/dist/benchmark/agent-safe-query-context/report.test.js +362 -0
  12. package/dist/benchmark/agent-safe-query-context/runner.d.ts +44 -0
  13. package/dist/benchmark/agent-safe-query-context/runner.js +406 -0
  14. package/dist/benchmark/agent-safe-query-context/runner.test.d.ts +1 -0
  15. package/dist/benchmark/agent-safe-query-context/runner.test.js +290 -0
  16. package/dist/benchmark/agent-safe-query-context/semantic-tuple.d.ts +20 -0
  17. package/dist/benchmark/agent-safe-query-context/semantic-tuple.js +225 -0
  18. package/dist/benchmark/agent-safe-query-context/semantic-tuple.test.d.ts +1 -0
  19. package/dist/benchmark/agent-safe-query-context/semantic-tuple.test.js +122 -0
  20. package/dist/benchmark/agent-safe-query-context/subagent-live.d.ts +47 -0
  21. package/dist/benchmark/agent-safe-query-context/subagent-live.js +128 -0
  22. package/dist/benchmark/agent-safe-query-context/subagent-live.test.d.ts +1 -0
  23. package/dist/benchmark/agent-safe-query-context/subagent-live.test.js +155 -0
  24. package/dist/benchmark/agent-safe-query-context/telemetry-tool.d.ts +9 -0
  25. package/dist/benchmark/agent-safe-query-context/telemetry-tool.js +77 -0
  26. package/dist/benchmark/agent-safe-query-context/types.d.ts +61 -0
  27. package/dist/benchmark/agent-safe-query-context/types.js +8 -0
  28. package/dist/benchmark/analyze-runner.d.ts +1 -1
  29. package/dist/benchmark/analyze-runner.js +4 -3
  30. package/dist/benchmark/analyze-runner.test.js +7 -0
  31. package/dist/benchmark/runtime-poc/provenance-artifact.d.ts +47 -0
  32. package/dist/benchmark/runtime-poc/provenance-artifact.js +89 -0
  33. package/dist/benchmark/runtime-poc/runner.d.ts +31 -0
  34. package/dist/benchmark/runtime-poc/runner.js +163 -0
  35. package/dist/benchmark/u2-e2e/hydration-policy-repeatability-runner.d.ts +8 -0
  36. package/dist/benchmark/u2-e2e/hydration-policy-repeatability-runner.js +21 -0
  37. package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.d.ts +0 -1
  38. package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.js +53 -51
  39. package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.test.js +0 -1
  40. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.d.ts +1 -1
  41. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.js +82 -18
  42. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.test.js +1 -2
  43. package/dist/benchmark/u2-e2e/retrieval-runner.js +15 -7
  44. package/dist/benchmark/u2-e2e/retrieval-runner.test.js +46 -0
  45. package/dist/cli/ai-context.d.ts +0 -1
  46. package/dist/cli/ai-context.js +5 -6
  47. package/dist/cli/ai-context.test.js +8 -0
  48. package/dist/cli/analyze-options.js +58 -34
  49. package/dist/cli/analyze-options.test.js +57 -0
  50. package/dist/cli/analyze-runtime-summary.js +2 -0
  51. package/dist/cli/analyze-runtime-summary.test.js +12 -0
  52. package/dist/cli/analyze-summary.d.ts +4 -0
  53. package/dist/cli/analyze-summary.js +43 -0
  54. package/dist/cli/analyze-summary.test.js +65 -1
  55. package/dist/cli/analyze.d.ts +11 -0
  56. package/dist/cli/analyze.js +34 -5
  57. package/dist/cli/analyze.test.d.ts +1 -0
  58. package/dist/cli/analyze.test.js +25 -0
  59. package/dist/cli/benchmark-agent-context.js +1 -1
  60. package/dist/cli/benchmark-agent-safe-query-context.d.ts +20 -0
  61. package/dist/cli/benchmark-agent-safe-query-context.js +39 -0
  62. package/dist/cli/benchmark-agent-safe-query-context.test.d.ts +1 -0
  63. package/dist/cli/benchmark-agent-safe-query-context.test.js +271 -0
  64. package/dist/cli/benchmark-unity.js +1 -1
  65. package/dist/cli/benchmark-unity.test.js +5 -1
  66. package/dist/cli/benchmark.d.ts +29 -0
  67. package/dist/cli/benchmark.js +55 -0
  68. package/dist/cli/index.js +27 -2
  69. package/dist/cli/rule-lab.d.ts +3 -7
  70. package/dist/cli/rule-lab.js +13 -22
  71. package/dist/cli/rule-lab.test.js +23 -3
  72. package/dist/cli/scope-manifest-config.d.ts +9 -0
  73. package/dist/cli/scope-manifest-config.js +37 -0
  74. package/dist/cli/setup.js +40 -41
  75. package/dist/cli/setup.test.js +14 -14
  76. package/dist/cli/sync-manifest.d.ts +27 -0
  77. package/dist/cli/sync-manifest.js +200 -0
  78. package/dist/cli/sync-manifest.test.d.ts +1 -0
  79. package/dist/cli/sync-manifest.test.js +88 -0
  80. package/dist/cli/tool.d.ts +2 -0
  81. package/dist/cli/tool.js +2 -0
  82. package/dist/core/config/unity-config.d.ts +1 -1
  83. package/dist/core/config/unity-config.js +1 -1
  84. package/dist/core/ingestion/call-processor.d.ts +2 -1
  85. package/dist/core/ingestion/call-processor.js +28 -6
  86. package/dist/core/ingestion/heritage-processor.d.ts +2 -1
  87. package/dist/core/ingestion/heritage-processor.js +30 -7
  88. package/dist/core/ingestion/import-processor.d.ts +2 -1
  89. package/dist/core/ingestion/import-processor.js +28 -6
  90. package/dist/core/ingestion/parsing-processor.d.ts +5 -3
  91. package/dist/core/ingestion/parsing-processor.js +46 -13
  92. package/dist/core/ingestion/pipeline.js +100 -19
  93. package/dist/core/ingestion/unity-lifecycle-synthetic-calls.test.js +18 -20
  94. package/dist/core/ingestion/unity-parity-seed.d.ts +2 -1
  95. package/dist/core/ingestion/unity-parity-seed.js +8 -0
  96. package/dist/core/ingestion/unity-resource-processor.d.ts +11 -0
  97. package/dist/core/ingestion/unity-resource-processor.js +102 -0
  98. package/dist/core/ingestion/unity-resource-processor.test.js +449 -0
  99. package/dist/core/ingestion/unity-runtime-binding-rules.d.ts +16 -1
  100. package/dist/core/ingestion/unity-runtime-binding-rules.js +193 -42
  101. package/dist/core/ingestion/workers/parse-worker.d.ts +2 -0
  102. package/dist/core/ingestion/workers/parse-worker.js +50 -6
  103. package/dist/core/lbug/csv-generator.test.js +2 -2
  104. package/dist/core/tree-sitter/csharp-define-profile.d.ts +6 -0
  105. package/dist/core/tree-sitter/csharp-define-profile.js +43 -0
  106. package/dist/core/tree-sitter/csharp-preproc-normalizer.d.ts +14 -0
  107. package/dist/core/tree-sitter/csharp-preproc-normalizer.js +261 -0
  108. package/dist/core/tree-sitter/parser-loader.d.ts +10 -0
  109. package/dist/core/tree-sitter/parser-loader.js +19 -0
  110. package/dist/core/unity/doc-contract.test.d.ts +1 -0
  111. package/dist/core/unity/doc-contract.test.js +30 -0
  112. package/dist/core/unity/prefab-source-scan.d.ts +25 -0
  113. package/dist/core/unity/prefab-source-scan.js +152 -0
  114. package/dist/core/unity/prefab-source-scan.test.d.ts +1 -0
  115. package/dist/core/unity/prefab-source-scan.test.js +70 -0
  116. package/dist/core/unity/scan-context.d.ts +12 -0
  117. package/dist/core/unity/scan-context.js +50 -2
  118. package/dist/core/unity/scan-context.test.js +74 -0
  119. package/dist/mcp/local/agent-safe-response.d.ts +10 -0
  120. package/dist/mcp/local/agent-safe-response.js +639 -0
  121. package/dist/mcp/local/derived-process-reader.js +1 -1
  122. package/dist/mcp/local/local-backend.d.ts +18 -1
  123. package/dist/mcp/local/local-backend.js +319 -125
  124. package/dist/mcp/local/process-confidence.d.ts +1 -2
  125. package/dist/mcp/local/process-confidence.js +0 -3
  126. package/dist/mcp/local/process-confidence.test.js +4 -2
  127. package/dist/mcp/local/process-evidence.d.ts +1 -8
  128. package/dist/mcp/local/process-evidence.js +1 -23
  129. package/dist/mcp/local/process-evidence.test.js +2 -16
  130. package/dist/mcp/local/process-ref.d.ts +1 -1
  131. package/dist/mcp/local/runtime-chain-closure-evaluator.d.ts +33 -0
  132. package/dist/mcp/local/runtime-chain-closure-evaluator.js +273 -0
  133. package/dist/mcp/local/runtime-chain-graph-candidates.d.ts +23 -0
  134. package/dist/mcp/local/runtime-chain-graph-candidates.js +131 -0
  135. package/dist/mcp/local/runtime-chain-verify.d.ts +1 -1
  136. package/dist/mcp/local/runtime-chain-verify.js +149 -138
  137. package/dist/mcp/local/runtime-chain-verify.test.js +126 -68
  138. package/dist/mcp/local/runtime-claim-rule-registry.d.ts +4 -0
  139. package/dist/mcp/local/runtime-claim-rule-registry.js +4 -0
  140. package/dist/mcp/local/runtime-claim-rule-registry.test.js +37 -4
  141. package/dist/mcp/local/runtime-claim.d.ts +11 -0
  142. package/dist/mcp/local/runtime-claim.js +28 -0
  143. package/dist/mcp/local/unity-evidence-view.d.ts +1 -1
  144. package/dist/mcp/local/unity-evidence-view.js +1 -1
  145. package/dist/mcp/local/unity-evidence-view.test.js +22 -0
  146. package/dist/mcp/tools.js +51 -21
  147. package/dist/rule-lab/analyze.d.ts +2 -1
  148. package/dist/rule-lab/analyze.js +94 -59
  149. package/dist/rule-lab/analyze.test.js +238 -20
  150. package/dist/rule-lab/curate.d.ts +2 -1
  151. package/dist/rule-lab/curate.js +24 -3
  152. package/dist/rule-lab/curate.test.js +65 -0
  153. package/dist/rule-lab/curation-input-builder.d.ts +45 -0
  154. package/dist/rule-lab/curation-input-builder.js +133 -0
  155. package/dist/rule-lab/promote.js +80 -7
  156. package/dist/rule-lab/promote.test.js +150 -0
  157. package/dist/rule-lab/review-pack.d.ts +3 -0
  158. package/dist/rule-lab/review-pack.js +41 -1
  159. package/dist/rule-lab/review-pack.test.js +67 -0
  160. package/dist/rule-lab/types.d.ts +29 -0
  161. package/dist/types/pipeline.d.ts +16 -0
  162. package/package.json +14 -13
  163. package/scripts/check-sync-manifest-traceability.mjs +203 -0
  164. package/scripts/run-node-tests.mjs +61 -0
  165. package/scripts/tree-sitter-audit-classify.mjs +172 -0
  166. package/skills/_shared/unity-rule-authoring-contract.md +64 -0
  167. package/skills/_shared/unity-runtime-process-contract.md +16 -0
  168. package/skills/gitnexus-cli.md +44 -4
  169. package/skills/gitnexus-debugging.md +9 -0
  170. package/skills/gitnexus-exploring.md +66 -18
  171. package/skills/gitnexus-guide.md +42 -3
  172. package/skills/gitnexus-impact-analysis.md +8 -0
  173. package/skills/gitnexus-pr-review.md +8 -0
  174. package/skills/gitnexus-refactoring.md +8 -0
  175. package/skills/gitnexus-unity-rule-gen.md +66 -312
@@ -1,32 +1,73 @@
1
- import { buildRuntimeClaimFromRule } from './runtime-claim.js';
2
- import { RuleRegistryLoadError, loadRuleRegistry } from './runtime-claim-rule-registry.js';
3
- function buildDefaultVerifyNextCommand(queryText) {
4
- const normalizedQuery = String(queryText || '').trim() || 'Reload NEON.Game.Graph.Nodes.Reloads';
5
- const escapedQuery = normalizedQuery
1
+ import { extractRuntimeGraphCandidates } from './runtime-chain-graph-candidates.js';
2
+ import { evaluateRuntimeClosure } from './runtime-chain-closure-evaluator.js';
3
+ function hasStructuredVerifierAnchors(input) {
4
+ const hasValue = (value) => String(value || '').trim().length > 0;
5
+ if (hasValue(input.resourceSeedPath))
6
+ return true;
7
+ if (hasValue(input.symbolName))
8
+ return true;
9
+ if (hasValue(input.symbolFilePath))
10
+ return true;
11
+ if (Array.isArray(input.mappedSeedTargets) && input.mappedSeedTargets.some((value) => hasValue(value)))
12
+ return true;
13
+ if (Array.isArray(input.resourceBindings)
14
+ && input.resourceBindings.some((binding) => hasValue(binding?.resourcePath)))
15
+ return true;
16
+ return false;
17
+ }
18
+ function toGraphOnlyRuntimeChainResult(input) {
19
+ const nextCommand = buildDefaultVerifyNextCommand({
20
+ queryText: input.queryText,
21
+ symbolName: input.symbolName,
22
+ resourceSeedPath: input.resourceSeedPath,
23
+ });
24
+ const closure = evaluateRuntimeClosure({
25
+ queryText: input.queryText,
26
+ symbolName: input.symbolName,
27
+ resourceSeedPath: input.resourceSeedPath,
28
+ mappedSeedTargets: input.mappedSeedTargets,
29
+ resourceBindings: input.resourceBindings,
30
+ candidates: input.candidates,
31
+ nextCommand,
32
+ });
33
+ const hops = input.candidates.slice(0, 20).map((candidate) => ({
34
+ hop_type: String(candidate.reason || '').startsWith('unity-rule-')
35
+ || String(candidate.reason || '').toLowerCase().includes('bridge')
36
+ ? 'code_loader'
37
+ : 'code_runtime',
38
+ anchor: `${candidate.sourceFilePath || candidate.sourceName}:${candidate.sourceStartLine || 1}->${candidate.targetFilePath || candidate.targetName}:${candidate.targetStartLine || 1}`,
39
+ confidence: String(candidate.reason || '').startsWith('unity-rule-') ? 'high' : 'medium',
40
+ note: String(candidate.reason || '').startsWith('unity-rule-')
41
+ ? `Synthetic edge observed in graph (${candidate.reason}).`
42
+ : 'Graph continuity candidate from structured anchors.',
43
+ snippet: `${candidate.sourceName} -> ${candidate.targetName}`,
44
+ }));
45
+ return {
46
+ status: closure.status,
47
+ evidence_level: closure.evidence_level,
48
+ evidence_source: 'query_time',
49
+ hops,
50
+ gaps: closure.gaps,
51
+ };
52
+ }
53
+ function resolveFollowUpSubject(input) {
54
+ const seedPath = String(input.resourceSeedPath || '').trim();
55
+ if (seedPath)
56
+ return seedPath;
57
+ const symbolName = String(input.symbolName || '').trim();
58
+ if (symbolName)
59
+ return symbolName;
60
+ const queryText = String(input.queryText || '').trim();
61
+ if (queryText)
62
+ return queryText;
63
+ return 'unity-runtime-chain';
64
+ }
65
+ function buildDefaultVerifyNextCommand(input) {
66
+ const escapedQuery = resolveFollowUpSubject(input)
6
67
  .replace(/\\/g, '\\\\')
7
68
  .replace(/"/g, '\\"');
8
69
  return `node gitnexus/dist/cli/index.js query --unity-resources on --unity-hydration parity --runtime-chain-verify on-demand "${escapedQuery}"`;
9
70
  }
10
- function buildRuntimeMatchHaystack(input) {
11
- return [
12
- input.queryText,
13
- input.resourceSeedPath,
14
- input.symbolName,
15
- input.symbolFilePath,
16
- ...(input.mappedSeedTargets || []),
17
- ...(input.resourceBindings || []).map((binding) => binding.resourcePath),
18
- ]
19
- .filter(Boolean)
20
- .join(' ')
21
- .toLowerCase();
22
- }
23
- function parseTriggerTokens(triggerFamily) {
24
- return String(triggerFamily || '')
25
- .toLowerCase()
26
- .split(/[\s,|/]+/)
27
- .map((token) => token.trim())
28
- .filter((token) => token.length > 0);
29
- }
30
71
  async function verifyRuleDrivenRuntimeChain(input) {
31
72
  const ruleId = input.rule?.id;
32
73
  if (!ruleId) {
@@ -71,8 +112,23 @@ async function verifyRuleDrivenRuntimeChain(input) {
71
112
  };
72
113
  }
73
114
  export async function verifyRuntimeChainOnDemand(input) {
74
- if (!input.rule)
75
- return undefined;
115
+ if (!input.rule) {
116
+ if (!hasStructuredVerifierAnchors(input) || !String(input.symbolName || '').trim())
117
+ return undefined;
118
+ const candidates = await extractRuntimeGraphCandidates({
119
+ executeParameterized: input.executeParameterized,
120
+ symbolName: input.symbolName,
121
+ symbolFilePath: input.symbolFilePath,
122
+ });
123
+ return toGraphOnlyRuntimeChainResult({
124
+ queryText: input.queryText,
125
+ symbolName: input.symbolName,
126
+ resourceSeedPath: input.resourceSeedPath,
127
+ mappedSeedTargets: input.mappedSeedTargets,
128
+ resourceBindings: input.resourceBindings,
129
+ candidates,
130
+ });
131
+ }
76
132
  return await verifyRuleDrivenRuntimeChain(input);
77
133
  }
78
134
  function buildFailureRuntimeClaim(input) {
@@ -96,130 +152,85 @@ function buildFailureRuntimeClaim(input) {
96
152
  next_action: input.next_action,
97
153
  };
98
154
  }
99
- function scoreRuntimeClaimRule(rule, input) {
100
- const haystack = buildRuntimeMatchHaystack(input);
101
- const tokens = Array.isArray(rule.match?.trigger_tokens) && rule.match.trigger_tokens.length > 0
102
- ? rule.match.trigger_tokens
103
- : parseTriggerTokens(rule.trigger_family);
104
- if (tokens.length === 0)
105
- return Number.NEGATIVE_INFINITY;
106
- let score = 0;
107
- let matchedTrigger = false;
108
- for (const token of tokens) {
109
- const normalized = String(token || '').trim().toLowerCase();
110
- if (!normalized)
111
- continue;
112
- if (haystack.includes(normalized)) {
113
- matchedTrigger = true;
114
- score += 10 + normalized.length;
115
- }
116
- }
117
- if (!matchedTrigger)
118
- return Number.NEGATIVE_INFINITY;
119
- const boostLists = [
120
- ...(Array.isArray(rule.match?.host_base_type) ? [rule.match.host_base_type] : []),
121
- ...(Array.isArray(rule.host_base_type) ? [rule.host_base_type] : []),
122
- ];
123
- for (const list of boostLists) {
124
- for (const token of list) {
125
- const normalized = String(token || '').trim().toLowerCase();
126
- if (normalized && haystack.includes(normalized))
127
- score += 20 + normalized.length;
128
- }
129
- }
130
- const resourceLists = [
131
- ...(Array.isArray(rule.match?.resource_types) ? [rule.match.resource_types] : []),
132
- ...(Array.isArray(rule.resource_types) ? [rule.resource_types] : []),
133
- ];
134
- for (const list of resourceLists) {
135
- for (const token of list) {
136
- const normalized = String(token || '').trim().toLowerCase();
137
- if (normalized && haystack.includes(normalized))
138
- score += 4 + normalized.length;
139
- }
140
- }
141
- for (const token of rule.match?.module_scope || []) {
142
- const normalized = String(token || '').trim().toLowerCase();
143
- if (normalized && haystack.includes(normalized))
144
- score += 8 + normalized.length;
145
- }
146
- return score;
147
- }
148
- export async function verifyRuntimeClaimOnDemand(input) {
149
- let registry;
150
- try {
151
- registry = await loadRuleRegistry(input.repoPath, input.rulesRoot);
152
- }
153
- catch (error) {
154
- if (error instanceof RuleRegistryLoadError) {
155
- if (error.code === 'rule_catalog_missing' || error.code === 'rule_file_missing') {
156
- return buildFailureRuntimeClaim({
157
- reason: 'rule_not_matched',
158
- next_action: buildDefaultVerifyNextCommand(input.queryText),
159
- });
160
- }
161
- }
162
- throw error;
163
- }
164
- const activeRules = registry.activeRules || [];
165
- const fallbackNextAction = buildDefaultVerifyNextCommand(input.queryText);
166
- if (activeRules.length === 0) {
167
- return buildFailureRuntimeClaim({
168
- reason: 'rule_not_matched',
169
- next_action: fallbackNextAction,
170
- });
171
- }
172
- const matchedRule = [...activeRules]
173
- .map((rule) => ({ rule, score: scoreRuntimeClaimRule(rule, input) }))
174
- .filter((entry) => Number.isFinite(entry.score))
175
- .sort((a, b) => (b.score - a.score) || a.rule.id.localeCompare(b.rule.id))[0]?.rule;
176
- if (!matchedRule) {
177
- return buildFailureRuntimeClaim({
178
- reason: 'rule_not_matched',
179
- next_action: fallbackNextAction,
180
- });
181
- }
182
- const runtimeChain = await verifyRuntimeChainOnDemand({
183
- ...input,
184
- requiredHops: matchedRule.required_hops,
185
- rule: matchedRule,
186
- });
187
- if (!runtimeChain) {
188
- return buildFailureRuntimeClaim({
189
- reason: 'rule_matched_but_evidence_missing',
190
- next_action: matchedRule.next_action || buildDefaultVerifyNextCommand(input.queryText),
191
- rule: matchedRule,
192
- });
193
- }
194
- const normalizedStatus = (runtimeChain.status === 'pending' ? 'failed' : runtimeChain.status);
155
+ function buildGraphOnlyRuntimeClaim(input) {
156
+ const normalizedStatus = (input.runtimeChain.status === 'pending' ? 'failed' : input.runtimeChain.status);
195
157
  const verificationFailed = normalizedStatus === 'failed'
196
- || (runtimeChain.evidence_level === 'none' && normalizedStatus !== 'verified_full');
197
- const resolved = buildRuntimeClaimFromRule({
198
- rule: matchedRule,
158
+ || (input.runtimeChain.evidence_level === 'none' && normalizedStatus !== 'verified_full');
159
+ const nextAction = buildDefaultVerifyNextCommand({
160
+ queryText: input.queryText,
161
+ symbolName: input.symbolName,
162
+ resourceSeedPath: input.resourceSeedPath,
163
+ });
164
+ const base = {
165
+ rule_id: 'graph-only.runtime-closure.v1',
166
+ rule_version: '1.0.0',
167
+ scope: {
168
+ resource_types: ['asset', 'prefab', 'unity'],
169
+ host_base_type: input.symbolName ? [input.symbolName] : [],
170
+ trigger_family: 'graph_only',
171
+ },
199
172
  status: verificationFailed ? 'failed' : normalizedStatus,
200
- evidence_level: runtimeChain.evidence_level,
201
- hops: runtimeChain.hops,
202
- gaps: runtimeChain.gaps,
173
+ evidence_level: input.runtimeChain.evidence_level,
174
+ guarantees: (!verificationFailed && normalizedStatus === 'verified_full')
175
+ ? ['runtime_chain_graph_closure']
176
+ : [],
177
+ non_guarantees: [
178
+ 'no_runtime_execution',
179
+ 'no_dynamic_data_flow_proof',
180
+ 'no_state_transition_proof',
181
+ ],
182
+ hops: input.runtimeChain.hops,
183
+ gaps: input.runtimeChain.gaps,
203
184
  ...(verificationFailed
204
185
  ? {
205
186
  reason: 'rule_matched_but_verification_failed',
206
- next_action: matchedRule.next_action || buildDefaultVerifyNextCommand(input.queryText),
187
+ next_action: nextAction,
207
188
  }
208
189
  : {}),
209
- });
210
- const chainClosed = resolved.status === 'verified_full'
211
- && resolved.evidence_level === 'verified_chain'
212
- && resolved.gaps.length === 0;
190
+ };
191
+ const chainClosed = base.status === 'verified_full'
192
+ && base.evidence_level === 'verified_chain'
193
+ && base.gaps.length === 0;
213
194
  if (input.minimumEvidenceSatisfied === false && !chainClosed) {
214
195
  return {
215
- ...resolved,
196
+ ...base,
216
197
  status: 'failed',
217
198
  evidence_level: 'clue',
218
199
  guarantees: [],
219
- non_guarantees: [...resolved.non_guarantees, 'minimum_evidence_contract_not_satisfied'],
200
+ non_guarantees: [...base.non_guarantees, 'minimum_evidence_contract_not_satisfied'],
220
201
  reason: 'rule_matched_but_evidence_missing',
221
- next_action: matchedRule.next_action || buildDefaultVerifyNextCommand(input.queryText),
202
+ next_action: nextAction,
222
203
  };
223
204
  }
224
- return resolved;
205
+ return base;
206
+ }
207
+ export async function verifyRuntimeClaimOnDemand(input) {
208
+ const fallbackNextAction = buildDefaultVerifyNextCommand({
209
+ queryText: input.queryText,
210
+ symbolName: input.symbolName,
211
+ resourceSeedPath: input.resourceSeedPath,
212
+ });
213
+ if (!hasStructuredVerifierAnchors(input)) {
214
+ return buildFailureRuntimeClaim({
215
+ reason: 'rule_not_matched',
216
+ next_action: fallbackNextAction,
217
+ });
218
+ }
219
+ const graphOnlyRuntimeChain = await verifyRuntimeChainOnDemand({
220
+ ...input,
221
+ rule: undefined,
222
+ });
223
+ if (graphOnlyRuntimeChain) {
224
+ return buildGraphOnlyRuntimeClaim({
225
+ runtimeChain: graphOnlyRuntimeChain,
226
+ queryText: input.queryText,
227
+ symbolName: input.symbolName,
228
+ resourceSeedPath: input.resourceSeedPath,
229
+ minimumEvidenceSatisfied: input.minimumEvidenceSatisfied,
230
+ });
231
+ }
232
+ return buildFailureRuntimeClaim({
233
+ reason: 'rule_not_matched',
234
+ next_action: fallbackNextAction,
235
+ });
225
236
  }
@@ -29,18 +29,6 @@ async function fileExists(filePath) {
29
29
  return false;
30
30
  }
31
31
  }
32
- async function resolveWorkspaceRulesRoot() {
33
- const candidates = [
34
- path.resolve('.gitnexus/rules'),
35
- path.resolve('../.gitnexus/rules'),
36
- ];
37
- for (const candidate of candidates) {
38
- if (await fileExists(path.join(candidate, 'catalog.json'))) {
39
- return candidate;
40
- }
41
- }
42
- return candidates[0];
43
- }
44
32
  function makeExecuteParameterized(repoPath) {
45
33
  return async (query, params) => {
46
34
  const q = String(query || '');
@@ -231,65 +219,109 @@ describe('runtime chain verify', () => {
231
219
  expect(out).toBeUndefined();
232
220
  await fs.rm(repoPath, { recursive: true, force: true });
233
221
  });
234
- it('runtime chain gaps are actionable under matched rule execution', async () => {
222
+ it('runtime chain gaps are actionable under graph-only verification', async () => {
235
223
  const out = await verifyRuntimeChainOnDemand({
236
224
  repoPath: await fs.mkdtemp(path.join(os.tmpdir(), 'runtime-chain-gaps-')),
237
225
  queryText: 'Reload',
226
+ symbolName: 'ReloadBase',
227
+ symbolFilePath: 'Assets/NEON/Code/Game/Graph/Nodes/Reloads/ReloadBase.cs',
238
228
  executeParameterized: async () => [],
239
229
  resourceBindings: [],
240
- rule: {
241
- id: 'demo.reload.strict.v2',
242
- version: '2.0.0',
243
- trigger_family: 'reload',
244
- resource_types: ['asset'],
245
- host_base_type: ['ReloadBase'],
246
- required_hops: ['resource', 'guid_map', 'code_loader', 'code_runtime'],
247
- guarantees: ['strict_chain_closed'],
248
- non_guarantees: ['strict_no_runtime_execution'],
249
- next_action: 'node strict',
250
- file_path: '.gitnexus/rules/approved/demo.reload.strict.v2.yaml',
251
- },
252
- requiredHops: ['resource', 'guid_map', 'code_loader', 'code_runtime'],
253
230
  });
254
231
  expect(out?.gaps.length).toBeGreaterThan(0);
255
232
  expect(out?.gaps.every((gap) => !!gap.next_command)).toBe(true);
256
233
  });
257
- it('accepts seed-to-mapped resource equivalence for resource hop verification', async () => {
234
+ it('builds graph-only follow-up command from symbol anchor when query text is missing', async () => {
235
+ const out = await verifyRuntimeClaimOnDemand({
236
+ repoPath: await fs.mkdtemp(path.join(os.tmpdir(), 'runtime-chain-anchor-symbol-')),
237
+ executeParameterized: async () => [],
238
+ queryText: '',
239
+ symbolName: 'InitGlobal',
240
+ symbolFilePath: 'Assets/NEON/Code/Game/Core/GameBootstrap.cs',
241
+ resourceBindings: [],
242
+ });
243
+ expect(out.next_action).toContain('InitGlobal');
244
+ expect(out.next_action).not.toContain('Reload NEON.Game.Graph.Nodes.Reloads');
245
+ expect(out.gaps.every((gap) => !gap.next_command.includes('Reload NEON.Game.Graph.Nodes.Reloads'))).toBe(true);
246
+ expect(out.gaps.every((gap) => gap.next_command.includes('InitGlobal'))).toBe(true);
247
+ });
248
+ it('prefers resource seed path in follow-up command subject when seed is present', async () => {
249
+ const seedPath = 'Assets/NEON/DataAssets/Powerups/Startup/init_global.asset';
250
+ const out = await verifyRuntimeClaimOnDemand({
251
+ repoPath: await fs.mkdtemp(path.join(os.tmpdir(), 'runtime-chain-anchor-seed-')),
252
+ executeParameterized: async () => [],
253
+ queryText: '',
254
+ symbolName: 'InitGlobal',
255
+ symbolFilePath: 'Assets/NEON/Code/Game/Core/GameBootstrap.cs',
256
+ resourceSeedPath: seedPath,
257
+ resourceBindings: [],
258
+ });
259
+ expect(out.next_action).toContain(seedPath);
260
+ expect(out.next_action).not.toContain('Reload NEON.Game.Graph.Nodes.Reloads');
261
+ expect(out.gaps.every((gap) => !gap.next_command.includes('Reload NEON.Game.Graph.Nodes.Reloads'))).toBe(true);
262
+ });
263
+ it('accepts seed-to-mapped resource equivalence for bind segment in graph-only verifier', async () => {
258
264
  const repoPath = await fs.mkdtemp(path.join(os.tmpdir(), 'runtime-chain-mapped-resource-'));
259
- const out = await verifyRuntimeChainOnDemand({
265
+ const out = await verifyRuntimeClaimOnDemand({
260
266
  repoPath,
261
267
  queryText: 'EnergyByAttackCount Assets/NEON/DataAssets/Powerups/1_newWeapon/0_pick/0_初始武器/1_weapon_0_james_new.asset',
268
+ symbolName: 'EnergyNode',
269
+ symbolFilePath: 'Assets/NEON/Code/Game/Graph/Nodes/Energy/EnergyNode.cs',
262
270
  resourceSeedPath: 'Assets/NEON/DataAssets/Powerups/1_newWeapon/0_pick/0_初始武器/1_weapon_0_james_new.asset',
263
271
  mappedSeedTargets: ['Assets/NEON/Graphs/PlayerGun/Gungraph_use/1_weapon_0_james1.asset'],
264
- executeParameterized: async () => [],
265
272
  resourceBindings: [{ resourcePath: 'Assets/NEON/Graphs/PlayerGun/Gungraph_use/1_weapon_0_james1.asset' }],
266
- rule: {
267
- id: 'demo.energy.seed-map.v1',
268
- version: '1.0.0',
269
- trigger_family: 'energy',
270
- resource_types: ['asset'],
271
- host_base_type: ['GunGraphNode'],
272
- required_hops: ['resource'],
273
- guarantees: ['seed_mapped_resource_is_accepted'],
274
- non_guarantees: ['does_not_verify_full_runtime_order'],
275
- next_action: 'node mapped-resource',
276
- file_path: '.gitnexus/rules/approved/demo.energy.seed-map.v1.yaml',
273
+ executeParameterized: async (query) => {
274
+ const q = String(query || '');
275
+ if (q.includes('WHERE n.name IN $symbolNames')) {
276
+ return [{
277
+ id: 'Class:Assets/NEON/Code/Game/Graph/Nodes/Energy/EnergyNode.cs:EnergyNode',
278
+ name: 'EnergyNode',
279
+ type: 'Class',
280
+ filePath: 'Assets/NEON/Code/Game/Graph/Nodes/Energy/EnergyNode.cs',
281
+ startLine: 1,
282
+ }];
283
+ }
284
+ if (q.includes("MATCH (s {id: $symbolId})-[r:CodeRelation {type: 'CALLS'}]->(t)")) {
285
+ return [{
286
+ sourceId: 'Method:Assets/NEON/Code/Game/Graph/Nodes/Energy/EnergyNode.cs:Apply',
287
+ sourceName: 'Apply',
288
+ sourceFilePath: 'Assets/NEON/Code/Game/Graph/Nodes/Energy/EnergyNode.cs',
289
+ sourceStartLine: 22,
290
+ targetId: 'Method:Assets/NEON/Code/Game/Graph/Nodes/Energy/EnergyRuntime.cs:Run',
291
+ targetName: 'Run',
292
+ targetFilePath: 'Assets/NEON/Code/Game/Graph/Nodes/Energy/EnergyRuntime.cs',
293
+ targetStartLine: 10,
294
+ reason: 'unity-rule-method-bridge:demo.energy.seed-map.v1',
295
+ }];
296
+ }
297
+ if (q.includes("MATCH (n {id: $symbolId})-[:CodeRelation {type: 'HAS_METHOD'}]->(m)")
298
+ && q.includes("MATCH (m)-[r:CodeRelation {type: 'CALLS'}]->(t)")) {
299
+ return [{
300
+ sourceId: 'Method:Assets/NEON/Code/Game/Graph/Nodes/Energy/EnergyNode.cs:Bootstrap',
301
+ sourceName: 'Bootstrap',
302
+ sourceFilePath: 'Assets/NEON/Code/Game/Graph/Nodes/Energy/EnergyNode.cs',
303
+ sourceStartLine: 8,
304
+ targetId: 'Method:Assets/NEON/Code/Game/Graph/Nodes/Energy/EnergyNode.cs:Apply',
305
+ targetName: 'Apply',
306
+ targetFilePath: 'Assets/NEON/Code/Game/Graph/Nodes/Energy/EnergyNode.cs',
307
+ targetStartLine: 22,
308
+ reason: 'static-call',
309
+ }];
310
+ }
311
+ return [];
277
312
  },
278
- requiredHops: ['resource'],
279
313
  });
280
- expect(out?.status).toBe('verified_full');
281
- expect(out?.gaps.length).toBe(0);
282
- expect(out?.hops[0]?.note || '').toContain('mapped resource equivalence');
314
+ expect(out.rule_id).toBe('graph-only.runtime-closure.v1');
315
+ expect(out.status).toBe('verified_full');
316
+ expect(out.gaps.length).toBe(0);
283
317
  await fs.rm(repoPath, { recursive: true, force: true });
284
318
  });
285
319
  it('phase2 runtime claim returns explicit rule_not_matched', async () => {
286
- const workspaceRulesRoot = await resolveWorkspaceRulesRoot();
287
320
  const out = await verifyRuntimeClaimOnDemand({
288
321
  repoPath: path.resolve('.'),
289
322
  queryText: 'CompletelyUnrelatedChain',
290
323
  executeParameterized: async () => [],
291
324
  resourceBindings: [],
292
- rulesRoot: workspaceRulesRoot,
293
325
  });
294
326
  expect(out.status).toBe('failed');
295
327
  expect(out.reason).toBe('rule_not_matched');
@@ -328,33 +360,32 @@ describe('runtime chain verify', () => {
328
360
  expect(out.next_action).toBeTruthy();
329
361
  await fs.rm(repoPath, { recursive: true, force: true });
330
362
  });
331
- it('phase2 runtime claim uses bootstrap reload rule metadata', async () => {
363
+ it('phase2 runtime claim uses graph-only metadata', async () => {
332
364
  const repoPath = await makeTempRepo();
333
- const workspaceRulesRoot = await resolveWorkspaceRulesRoot();
334
365
  const out = await verifyRuntimeClaimOnDemand({
335
366
  repoPath,
336
367
  queryText: 'Reload NEON.Game.Graph.Nodes.Reloads',
368
+ symbolName: 'ReloadBase',
369
+ symbolFilePath: 'Assets/NEON/Code/Game/Graph/Nodes/Reloads/ReloadBase.cs',
337
370
  executeParameterized: makeExecuteParameterized(repoPath),
371
+ resourceSeedPath: 'Assets/NEON/DataAssets/Powerups/1_newWeapon/0_pick/法器_Orb/1_weapon_orb_key.asset',
338
372
  resourceBindings: [{ resourcePath: 'Assets/NEON/DataAssets/Powerups/1_newWeapon/0_pick/法器_Orb/1_weapon_orb_key.asset' }],
339
- rulesRoot: workspaceRulesRoot,
340
373
  });
341
- expect(out.rule_id).toBe('unity.gungraph.reload.output-getvalue.v1');
374
+ expect(out.rule_id).toBe('graph-only.runtime-closure.v1');
342
375
  expect(out.rule_version).toBe('1.0.0');
343
376
  });
344
377
  it('phase2 next_action remains shell-parsable when unmatched', async () => {
345
- const workspaceRulesRoot = await resolveWorkspaceRulesRoot();
346
378
  const out = await verifyRuntimeClaimOnDemand({
347
379
  repoPath: path.resolve('.'),
348
380
  queryText: 'CompletelyUnrelatedChain',
349
381
  executeParameterized: async () => [],
350
382
  resourceBindings: [],
351
- rulesRoot: workspaceRulesRoot,
352
383
  });
353
384
  expect(out.reason).toBe('rule_not_matched');
354
385
  expect(typeof out.next_action).toBe('string');
355
386
  expect(hasBalancedShellQuotes(String(out.next_action || ''))).toBe(true);
356
387
  });
357
- it('phase2 runtime claim required_hops are rule-driven', async () => {
388
+ it('phase2 runtime claim is stable under different rulesRoot values (graph-only)', async () => {
358
389
  const repoPath = await makeTempRepo();
359
390
  const executeParameterized = makeExecuteParameterized(repoPath);
360
391
  const strictExecuteParameterized = async (query, params) => {
@@ -408,6 +439,9 @@ describe('runtime chain verify', () => {
408
439
  const strict = await verifyRuntimeClaimOnDemand({
409
440
  repoPath,
410
441
  queryText: 'Reload NEON.Game.Graph.Nodes.Reloads',
442
+ symbolName: 'ReloadBase',
443
+ symbolFilePath: 'Assets/NEON/Code/Game/Graph/Nodes/Reloads/ReloadBase.cs',
444
+ resourceSeedPath: 'Assets/NEON/DataAssets/Powerups/1_newWeapon/0_pick/法器_Orb/1_weapon_orb_key.asset',
411
445
  executeParameterized: strictExecuteParameterized,
412
446
  resourceBindings: [{ resourcePath: 'Assets/NEON/DataAssets/Powerups/1_newWeapon/0_pick/法器_Orb/1_weapon_orb_key.asset' }],
413
447
  rulesRoot: strictRulesRoot,
@@ -415,15 +449,20 @@ describe('runtime chain verify', () => {
415
449
  const relaxed = await verifyRuntimeClaimOnDemand({
416
450
  repoPath,
417
451
  queryText: 'Reload NEON.Game.Graph.Nodes.Reloads',
452
+ symbolName: 'ReloadBase',
453
+ symbolFilePath: 'Assets/NEON/Code/Game/Graph/Nodes/Reloads/ReloadBase.cs',
454
+ resourceSeedPath: 'Assets/NEON/DataAssets/Powerups/1_newWeapon/0_pick/法器_Orb/1_weapon_orb_key.asset',
418
455
  executeParameterized,
419
456
  resourceBindings: [{ resourcePath: 'Assets/NEON/DataAssets/Powerups/1_newWeapon/0_pick/法器_Orb/1_weapon_orb_key.asset' }],
420
457
  rulesRoot: relaxedRulesRoot,
421
458
  });
422
- expect(strict.status).toBe('verified_partial');
423
- expect(relaxed.status).toBe('verified_full');
459
+ expect(strict.rule_id).toBe('graph-only.runtime-closure.v1');
460
+ expect(relaxed.rule_id).toBe('graph-only.runtime-closure.v1');
461
+ expect(strict.status).toBe(relaxed.status);
462
+ expect(strict.evidence_level).toBe(relaxed.evidence_level);
424
463
  await fs.rm(repoPath, { recursive: true, force: true });
425
464
  });
426
- it('phase2 runtime claim guarantees/non_guarantees come from matched rule', async () => {
465
+ it('phase2 runtime claim guarantees/non_guarantees follow graph-only contract', async () => {
427
466
  const repoPath = await makeTempRepo();
428
467
  const rulesRoot = await writeRules(repoPath, {
429
468
  'approved/demo.reload.claim-semantics.v1.yaml': [
@@ -474,17 +513,21 @@ describe('runtime chain verify', () => {
474
513
  const out = await verifyRuntimeClaimOnDemand({
475
514
  repoPath,
476
515
  queryText: 'Reload runtime start sequence',
516
+ symbolName: 'ReloadBase',
517
+ symbolFilePath: 'Assets/NEON/Code/Game/Graph/Nodes/Reloads/ReloadBase.cs',
518
+ resourceSeedPath: 'Assets/NEON/DataAssets/Powerups/1_newWeapon/0_pick/法器_Orb/1_weapon_orb_key.asset',
477
519
  executeParameterized: makeExecuteParameterized(repoPath),
478
520
  resourceBindings: [{ resourcePath: 'Assets/NEON/DataAssets/Powerups/1_newWeapon/0_pick/法器_Orb/1_weapon_orb_key.asset' }],
479
521
  rulesRoot,
480
522
  });
481
- expect(out.rule_id).toBe('demo.reload.claim-semantics.v1');
482
- expect(out.rule_version).toBe('2.0.0');
483
- expect(out.guarantees).toEqual(['custom_guarantee_from_rule']);
484
- expect(out.non_guarantees).toEqual(['custom_non_guarantee_from_rule']);
523
+ expect(out.rule_id).toBe('graph-only.runtime-closure.v1');
524
+ expect(out.non_guarantees).toContain('no_runtime_execution');
525
+ expect(out.non_guarantees).toContain('no_dynamic_data_flow_proof');
526
+ expect(out.non_guarantees).toContain('no_state_transition_proof');
527
+ expect(out.non_guarantees).not.toContain('custom_non_guarantee_from_rule');
485
528
  await fs.rm(repoPath, { recursive: true, force: true });
486
529
  });
487
- it('phase2 non-reload trigger family executes rule-driven verifier', async () => {
530
+ it('phase2 without structured anchors returns rule_not_matched', async () => {
488
531
  const repoPath = await makeTempRepo();
489
532
  const rulesRoot = await writeRules(repoPath, {
490
533
  'approved/demo.startup.v1.yaml': [
@@ -511,10 +554,8 @@ describe('runtime chain verify', () => {
511
554
  resourceBindings: [{ resourcePath: 'Assets/Custom/Rules/startup.asset' }],
512
555
  rulesRoot,
513
556
  });
514
- expect(out.rule_id).toBe('demo.startup.v1');
515
- expect(out.status).toBe('verified_full');
516
- expect(out.evidence_level).toBe('verified_segment');
517
- expect(out.reason).toBeUndefined();
557
+ expect(out.rule_id).toBe('none');
558
+ expect(out.reason).toBe('rule_not_matched');
518
559
  await fs.rm(repoPath, { recursive: true, force: true });
519
560
  });
520
561
  it('phase2 rule_not_matched does not leak first rule next_action', async () => {
@@ -549,7 +590,7 @@ describe('runtime chain verify', () => {
549
590
  expect(String(out.next_action || '')).toContain('Reload runtime start sequence');
550
591
  await fs.rm(repoPath, { recursive: true, force: true });
551
592
  });
552
- it('phase5 rule-lab promoted rule is loadable', async () => {
593
+ it('phase5 promote artifacts do not alter query-time graph-only rule identity', async () => {
553
594
  const repoPath = await fs.mkdtemp(path.join(os.tmpdir(), 'runtime-chain-rule-lab-promote-'));
554
595
  const sliceDir = path.join(repoPath, '.gitnexus', 'rules', 'lab', 'runs', 'run-x', 'slices', 'slice-a');
555
596
  await fs.mkdir(sliceDir, { recursive: true });
@@ -573,10 +614,27 @@ describe('runtime chain verify', () => {
573
614
  const out = await verifyRuntimeClaimOnDemand({
574
615
  repoPath,
575
616
  queryText: 'Startup Graph Trigger',
576
- executeParameterized: async () => [],
617
+ symbolName: 'StartupNode',
618
+ symbolFilePath: 'Assets/Rules/StartupNode.cs',
619
+ resourceSeedPath: 'Assets/Rules/startup.asset',
620
+ executeParameterized: async (query) => {
621
+ if (String(query || '').includes('WHERE n.name IN $symbolNames')) {
622
+ return [{
623
+ id: 'Class:Assets/Rules/StartupNode.cs:StartupNode',
624
+ name: 'StartupNode',
625
+ type: 'Class',
626
+ filePath: 'Assets/Rules/StartupNode.cs',
627
+ startLine: 1,
628
+ }];
629
+ }
630
+ return [];
631
+ },
577
632
  resourceBindings: [{ resourcePath: 'Assets/Rules/startup.asset' }],
578
633
  });
579
- expect(out.rule_id).toBe('demo.startup.v1');
634
+ expect(out.rule_id).toBe('graph-only.runtime-closure.v1');
635
+ expect(out.rule_id).not.toBe('demo.startup.v1');
636
+ expect(out.status).toBe('verified_partial');
637
+ expect(out.evidence_level).toBe('verified_segment');
580
638
  expect(out.reason).toBeUndefined();
581
639
  await fs.rm(repoPath, { recursive: true, force: true });
582
640
  });
@@ -60,5 +60,9 @@ export declare class RuleRegistryLoadError extends Error {
60
60
  constructor(code: RuleRegistryLoadErrorCode, message: string, details?: Record<string, string>);
61
61
  }
62
62
  export declare function parseRuleYaml(raw: string, filePath: string): RuntimeClaimRule;
63
+ /**
64
+ * Runtime claim rule registry remains the source for analyze-time synthetic-edge production
65
+ * and offline governance/report workflows. Query-time runtime closure verification is graph-only.
66
+ */
63
67
  export declare function loadRuleRegistry(repoPath: string, rulesRoot?: string): Promise<RuntimeClaimRuleRegistry>;
64
68
  export declare function loadAnalyzeRules(repoPath: string, rulesRoot?: string): Promise<RuntimeClaimRule[]>;
@@ -215,6 +215,10 @@ export function parseRuleYaml(raw, filePath) {
215
215
  file_path: filePath,
216
216
  };
217
217
  }
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
+ */
218
222
  export async function loadRuleRegistry(repoPath, rulesRoot) {
219
223
  const normalizedRepoPath = path.resolve(repoPath);
220
224
  const root = rulesRoot