@veewo/gitnexus 1.5.6 → 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 (104) hide show
  1. package/dist/benchmark/analyze-runner.d.ts +0 -2
  2. package/dist/benchmark/analyze-runner.js +0 -6
  3. package/dist/benchmark/analyze-runner.test.js +1 -10
  4. package/dist/benchmark/runner.d.ts +0 -2
  5. package/dist/benchmark/runner.js +0 -2
  6. package/dist/benchmark/u2-e2e/neonspark-full-e2e.js +0 -11
  7. package/dist/benchmark/u2-performance-sampler.js +3 -16
  8. package/dist/cli/ai-context.js +1 -7
  9. package/dist/cli/analyze-options.d.ts +19 -6
  10. package/dist/cli/analyze-options.js +76 -71
  11. package/dist/cli/analyze-options.test.js +78 -73
  12. package/dist/cli/analyze-runtime-summary.js +0 -1
  13. package/dist/cli/analyze-runtime-summary.test.js +0 -2
  14. package/dist/cli/analyze-summary.d.ts +0 -2
  15. package/dist/cli/analyze-summary.js +0 -24
  16. package/dist/cli/analyze-summary.test.js +1 -65
  17. package/dist/cli/analyze.d.ts +2 -4
  18. package/dist/cli/analyze.js +14 -30
  19. package/dist/cli/analyze.test.js +9 -15
  20. package/dist/cli/benchmark-agent-context.d.ts +0 -2
  21. package/dist/cli/benchmark-agent-context.js +0 -2
  22. package/dist/cli/benchmark-agent-safe-query-context.d.ts +0 -2
  23. package/dist/cli/benchmark-agent-safe-query-context.js +0 -2
  24. package/dist/cli/benchmark-unity.d.ts +0 -2
  25. package/dist/cli/benchmark-unity.js +0 -2
  26. package/dist/cli/clean.d.ts +2 -3
  27. package/dist/cli/clean.js +4 -25
  28. package/dist/cli/index.js +1 -12
  29. package/dist/core/ingestion/pipeline.js +1 -44
  30. package/dist/mcp/local/agent-safe-response.js +1 -1
  31. package/dist/mcp/local/local-backend.d.ts +0 -23
  32. package/dist/mcp/local/local-backend.js +69 -248
  33. package/dist/mcp/local/runtime-chain-verify.test.js +0 -49
  34. package/dist/mcp/local/runtime-claim-rule-registry.d.ts +0 -11
  35. package/dist/mcp/local/runtime-claim-rule-registry.js +0 -159
  36. package/dist/mcp/local/runtime-claim-rule-registry.test.js +67 -214
  37. package/dist/mcp/tools.js +0 -70
  38. package/dist/storage/repo-manager.d.ts +1 -0
  39. package/dist/types/pipeline.d.ts +0 -3
  40. package/package.json +1 -1
  41. package/skills/gitnexus-cli.md +62 -38
  42. package/vendor/node_modules/node-addon-api/node_addon_api.Makefile +6 -0
  43. package/vendor/node_modules/node-addon-api/node_addon_api.target.mk +122 -0
  44. package/vendor/node_modules/node-addon-api/node_addon_api_except.target.mk +126 -0
  45. package/vendor/node_modules/node-addon-api/node_addon_api_except_all.target.mk +122 -0
  46. package/vendor/node_modules/node-addon-api/node_addon_api_maybe.target.mk +122 -0
  47. package/vendor/tree-sitter-dart/build/Release/.deps/node_modules/node-addon-api/node_addon_api_except.stamp.d +1 -0
  48. package/vendor/tree-sitter-dart/build/node_modules/node-addon-api/node_addon_api_except.stamp +0 -0
  49. package/vendor/tree-sitter-proto/build/Release/.deps/node_modules/node-addon-api/node_addon_api_except.stamp.d +1 -0
  50. package/vendor/tree-sitter-proto/build/node_modules/node-addon-api/node_addon_api_except.stamp +0 -0
  51. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.d.ts +0 -60
  52. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.js +0 -395
  53. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.test.d.ts +0 -1
  54. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.test.js +0 -41
  55. package/dist/cli/rule-lab.d.ts +0 -38
  56. package/dist/cli/rule-lab.js +0 -148
  57. package/dist/cli/rule-lab.test.d.ts +0 -1
  58. package/dist/cli/rule-lab.test.js +0 -31
  59. package/dist/cli/scope-manifest-config.d.ts +0 -9
  60. package/dist/cli/scope-manifest-config.js +0 -37
  61. package/dist/cli/sync-manifest.d.ts +0 -27
  62. package/dist/cli/sync-manifest.js +0 -200
  63. package/dist/cli/sync-manifest.test.d.ts +0 -1
  64. package/dist/cli/sync-manifest.test.js +0 -88
  65. package/dist/core/ingestion/unity-runtime-binding-rules.d.ts +0 -26
  66. package/dist/core/ingestion/unity-runtime-binding-rules.js +0 -408
  67. package/dist/rule-lab/analyze.d.ts +0 -13
  68. package/dist/rule-lab/analyze.js +0 -125
  69. package/dist/rule-lab/analyze.test.d.ts +0 -1
  70. package/dist/rule-lab/analyze.test.js +0 -246
  71. package/dist/rule-lab/compile.d.ts +0 -5
  72. package/dist/rule-lab/compile.js +0 -51
  73. package/dist/rule-lab/compiled-bundles.d.ts +0 -30
  74. package/dist/rule-lab/compiled-bundles.js +0 -36
  75. package/dist/rule-lab/curate.d.ts +0 -33
  76. package/dist/rule-lab/curate.js +0 -155
  77. package/dist/rule-lab/curate.test.d.ts +0 -1
  78. package/dist/rule-lab/curate.test.js +0 -137
  79. package/dist/rule-lab/curation-input-builder.d.ts +0 -45
  80. package/dist/rule-lab/curation-input-builder.js +0 -133
  81. package/dist/rule-lab/discover.d.ts +0 -13
  82. package/dist/rule-lab/discover.js +0 -74
  83. package/dist/rule-lab/discover.test.d.ts +0 -1
  84. package/dist/rule-lab/discover.test.js +0 -42
  85. package/dist/rule-lab/paths.d.ts +0 -21
  86. package/dist/rule-lab/paths.js +0 -37
  87. package/dist/rule-lab/paths.test.d.ts +0 -1
  88. package/dist/rule-lab/paths.test.js +0 -46
  89. package/dist/rule-lab/promote.d.ts +0 -26
  90. package/dist/rule-lab/promote.js +0 -387
  91. package/dist/rule-lab/promote.test.d.ts +0 -1
  92. package/dist/rule-lab/promote.test.js +0 -314
  93. package/dist/rule-lab/regress.d.ts +0 -60
  94. package/dist/rule-lab/regress.js +0 -122
  95. package/dist/rule-lab/regress.test.d.ts +0 -1
  96. package/dist/rule-lab/regress.test.js +0 -68
  97. package/dist/rule-lab/review-pack.d.ts +0 -34
  98. package/dist/rule-lab/review-pack.js +0 -165
  99. package/dist/rule-lab/review-pack.test.d.ts +0 -1
  100. package/dist/rule-lab/review-pack.test.js +0 -116
  101. package/dist/rule-lab/types.d.ts +0 -135
  102. package/dist/rule-lab/types.js +0 -1
  103. package/skills/_shared/unity-rule-authoring-contract.md +0 -64
  104. package/skills/gitnexus-unity-rule-gen.md +0 -107
@@ -24,12 +24,6 @@ import { adjustRuntimeClaimForPolicy } from './runtime-claim.js';
24
24
  // git utilities available if needed
25
25
  // import { isGitRepo, getCurrentCommit, getGitRoot } from '../../storage/git.js';
26
26
  import { listRegisteredRepos, cleanupOldKuzuFiles, } from '../../storage/repo-manager.js';
27
- import { analyzeRuleLabSlice } from '../../rule-lab/analyze.js';
28
- import { buildReviewPack } from '../../rule-lab/review-pack.js';
29
- import { curateRuleLabSlice } from '../../rule-lab/curate.js';
30
- import { promoteCuratedRules } from '../../rule-lab/promote.js';
31
- import { runRuleLabRegress } from '../../rule-lab/regress.js';
32
- import { loadCompiledRuleBundle } from '../../rule-lab/compiled-bundles.js';
33
27
  // AI context generation is CLI-only (gitnexus analyze)
34
28
  // import { generateAIContextFiles } from '../../cli/ai-context.js';
35
29
  /**
@@ -246,10 +240,28 @@ async function loadSeedUnityResourceChains(input) {
246
240
  }
247
241
  catch (e) {
248
242
  logQueryError('unity-resource-chains:seed-second-hop', e);
249
- return [];
243
+ }
244
+ // One-hop query: direct UNITY_GRAPH_NODE_SCRIPT_REF from seed to Class
245
+ let oneHopRows = [];
246
+ try {
247
+ oneHopRows = await executeParameterized(input.repoId, `
248
+ MATCH (source:File {filePath: $seedPath})-[r:CodeRelation {type: 'UNITY_GRAPH_NODE_SCRIPT_REF'}]->(target)
249
+ WHERE labels(target)[0] = 'Class'
250
+ RETURN source.filePath AS sourceResourcePath,
251
+ r.type AS relationType,
252
+ r.reason AS relationReason,
253
+ target.id AS targetUid,
254
+ target.name AS targetName,
255
+ labels(target)[0] AS targetKind,
256
+ target.filePath AS targetFilePath
257
+ LIMIT 200
258
+ `, { seedPath });
259
+ }
260
+ catch (e) {
261
+ logQueryError('unity-resource-chains:seed-one-hop', e);
250
262
  }
251
263
  const seen = new Set();
252
- const chains = rows
264
+ const twoHopEntries = rows
253
265
  .map((row, index) => {
254
266
  const targetUid = String(row?.targetUid || row?.[6] || '').trim();
255
267
  const targetName = String(row?.targetName || row?.[7] || '').trim();
@@ -268,18 +280,35 @@ async function loadSeedUnityResourceChains(input) {
268
280
  ...(targetFilePath ? { filePath: targetFilePath } : {}),
269
281
  },
270
282
  };
271
- return {
272
- chain,
273
- index,
274
- score: scoreUnityResourceChainTarget(chain, targetSymbols),
283
+ return { chain, index, score: scoreUnityResourceChainTarget(chain, targetSymbols) };
284
+ });
285
+ const oneHopEntries = oneHopRows
286
+ .map((row, index) => {
287
+ const targetUid = String(row?.targetUid || row?.[4] || '').trim();
288
+ const targetName = String(row?.targetName || row?.[5] || '').trim();
289
+ const targetFilePath = normalizePath(String(row?.targetFilePath || row?.[7] || '').trim());
290
+ const chain = {
291
+ sourceResourcePath: normalizePath(String(row?.sourceResourcePath || row?.[0] || '').trim()),
292
+ relationType: 'UNITY_GRAPH_NODE_SCRIPT_REF',
293
+ relationReason: String(row?.relationReason || row?.[2] || '').trim() || undefined,
294
+ targetSymbol: {
295
+ ...(targetUid ? { uid: targetUid } : {}),
296
+ ...(targetName ? { name: targetName } : {}),
297
+ kind: String(row?.targetKind || row?.[6] || '').trim() || undefined,
298
+ ...(targetFilePath ? { filePath: targetFilePath } : {}),
299
+ },
275
300
  };
276
- })
301
+ return { chain, index: rows.length + index, score: scoreUnityResourceChainTarget(chain, targetSymbols) };
302
+ });
303
+ const chains = [...twoHopEntries, ...oneHopEntries]
277
304
  .filter(({ chain, score }) => {
278
- if (!chain.sourceResourcePath || !chain.intermediateResourcePath || !chain.targetSymbol?.name)
305
+ if (!chain.sourceResourcePath || !chain.targetSymbol?.name)
279
306
  return false;
280
307
  if (targetSymbols.length > 0 && score <= 0)
281
308
  return false;
282
- const key = `${chain.sourceResourcePath}->${chain.intermediateResourcePath}->${chain.targetSymbol.uid || chain.targetSymbol.name}`;
309
+ const key = chain.intermediateResourcePath
310
+ ? `${chain.sourceResourcePath}->${chain.intermediateResourcePath}->${chain.targetSymbol.uid || chain.targetSymbol.name}`
311
+ : `${chain.sourceResourcePath}->${chain.targetSymbol.uid || chain.targetSymbol.name}`;
283
312
  if (seen.has(key))
284
313
  return false;
285
314
  seen.add(key);
@@ -473,14 +502,7 @@ export function buildNextHops(input) {
473
502
  const mappedRemainder = input.mappedSeedTargets
474
503
  .map((value) => normalizePath(value))
475
504
  .filter((value) => value && !bindingSet.has(value));
476
- const retrievalHostScope = (input.retrievalRule?.host_base_type || [])
477
- .map((value) => String(value || '').trim().toLowerCase())
478
- .filter(Boolean);
479
- const currentSymbolMatchesRetrievalScope = retrievalHostScope.length === 0
480
- || retrievalHostScope.includes(String(input.symbolName || '').trim().toLowerCase());
481
- const shouldSuppressRawResourceHops = !input.seedPath
482
- && mappedIntersectBindings.length === 0
483
- && currentSymbolMatchesRetrievalScope === false;
505
+ const shouldSuppressRawResourceHops = false;
484
506
  const candidateResources = shouldSuppressRawResourceHops ? [] : rankCandidateResources([
485
507
  ...(input.seedPath ? [{ target: normalizePath(input.seedPath), bucket: 0 }] : []),
486
508
  ...mappedIntersectBindings.map((target) => ({ target, bucket: 1 })),
@@ -506,14 +528,6 @@ export function buildNextHops(input) {
506
528
  next_command: `gitnexus query${repoArg} --unity-resources on --unity-hydration parity --resource-path-prefix "${target}" "${input.queryForSymbol}"`,
507
529
  });
508
530
  }
509
- if (input.retrievalRule?.next_action) {
510
- addHop({
511
- kind: 'verify',
512
- target: input.seedPath || input.symbolName,
513
- why: `Retrieval rule ${input.retrievalRule.id} configured this follow-up action.`,
514
- next_command: withRepoInCommand(input.retrievalRule.next_action),
515
- });
516
- }
517
531
  if (input.verificationHint?.target
518
532
  && !(shouldSuppressRawResourceHops && isUnityResourcePathLike(String(input.verificationHint.target)))) {
519
533
  addHop({
@@ -579,69 +593,6 @@ function scoreResourcePathNoise(resourcePath) {
579
593
  penalty += 6;
580
594
  return penalty;
581
595
  }
582
- async function resolveRetrievalRuleHint(input) {
583
- const bundle = await loadCompiledRuleBundle(input.repoPath, 'retrieval_rules');
584
- if (!bundle)
585
- return undefined;
586
- return pickRetrievalRuleHintFromBundle({
587
- queryText: input.queryText,
588
- symbolName: input.symbolName,
589
- seedPath: input.seedPath,
590
- rules: bundle.rules,
591
- });
592
- }
593
- export function pickRetrievalRuleHintFromBundle(input) {
594
- const haystack = [
595
- String(input.queryText || ''),
596
- String(input.symbolName || ''),
597
- String(input.seedPath || ''),
598
- ].join(' ').toLowerCase();
599
- const rank = (rule) => {
600
- let score = 0;
601
- let matchedTrigger = false;
602
- let matchedEvidence = false;
603
- for (const token of rule.trigger_tokens || []) {
604
- const normalized = String(token || '').trim().toLowerCase();
605
- if (!normalized)
606
- continue;
607
- if (haystack.includes(normalized)) {
608
- matchedTrigger = true;
609
- matchedEvidence = true;
610
- score += 10 + normalized.length;
611
- }
612
- }
613
- for (const token of rule.host_base_type || []) {
614
- const normalized = String(token || '').trim().toLowerCase();
615
- if (normalized && haystack.includes(normalized)) {
616
- matchedEvidence = true;
617
- score += 20 + normalized.length;
618
- }
619
- }
620
- for (const token of rule.resource_types || []) {
621
- const normalized = String(token || '').trim().toLowerCase();
622
- if (normalized && haystack.includes(normalized)) {
623
- matchedEvidence = true;
624
- score += 4 + normalized.length;
625
- }
626
- }
627
- if (!matchedEvidence)
628
- return Number.NEGATIVE_INFINITY;
629
- if (!matchedTrigger)
630
- score -= 3;
631
- return score;
632
- };
633
- const matched = [...input.rules]
634
- .map((rule) => ({ rule, score: rank(rule) }))
635
- .filter((entry) => Number.isFinite(entry.score))
636
- .sort((a, b) => (b.score - a.score) || a.rule.id.localeCompare(b.rule.id))[0]?.rule;
637
- if (!matched || !String(matched.next_action || '').trim())
638
- return undefined;
639
- return {
640
- id: matched.id,
641
- next_action: matched.next_action,
642
- host_base_type: matched.host_base_type,
643
- };
644
- }
645
596
  export async function resolveSeedTargetsFromResourceFile(repoPath, seedPath) {
646
597
  if (!isUnityResourcePath(seedPath))
647
598
  return [];
@@ -1072,16 +1023,6 @@ export class LocalBackend {
1072
1023
  return this.detectChanges(repo, params);
1073
1024
  case 'rename':
1074
1025
  return this.rename(repo, params);
1075
- case 'rule_lab_analyze':
1076
- return this.ruleLabAnalyze(repo, params);
1077
- case 'rule_lab_review_pack':
1078
- return this.ruleLabReviewPack(repo, params);
1079
- case 'rule_lab_curate':
1080
- return this.ruleLabCurate(repo, params);
1081
- case 'rule_lab_promote':
1082
- return this.ruleLabPromote(repo, params);
1083
- case 'rule_lab_regress':
1084
- return this.ruleLabRegress(repo, params);
1085
1026
  // Legacy aliases for backwards compatibility
1086
1027
  case 'search':
1087
1028
  return this.query(repo, params);
@@ -1118,136 +1059,6 @@ export class LocalBackend {
1118
1059
  return { error: err?.message || 'unity_ui_trace failed' };
1119
1060
  }
1120
1061
  }
1121
- async ruleLabAnalyze(repo, params) {
1122
- const runId = String(params?.run_id || params?.runId || '').trim();
1123
- const sliceId = String(params?.slice_id || params?.sliceId || '').trim();
1124
- if (!runId || !sliceId) {
1125
- return { error: 'run_id and slice_id are required for rule_lab_analyze' };
1126
- }
1127
- try {
1128
- const out = await analyzeRuleLabSlice({
1129
- repoPath: repo.repoPath,
1130
- runId,
1131
- sliceId,
1132
- });
1133
- return {
1134
- ...out,
1135
- artifact_paths: {
1136
- candidates: out.paths.candidatesPath,
1137
- },
1138
- };
1139
- }
1140
- catch (err) {
1141
- return { error: err?.message || 'rule_lab_analyze failed' };
1142
- }
1143
- }
1144
- async ruleLabReviewPack(repo, params) {
1145
- const runId = String(params?.run_id || params?.runId || '').trim();
1146
- const sliceId = String(params?.slice_id || params?.sliceId || '').trim();
1147
- if (!runId || !sliceId) {
1148
- return { error: 'run_id and slice_id are required for rule_lab_review_pack' };
1149
- }
1150
- const maxTokens = Number.isFinite(Number(params?.max_tokens ?? params?.maxTokens))
1151
- ? Number(params?.max_tokens ?? params?.maxTokens)
1152
- : 6000;
1153
- try {
1154
- const out = await buildReviewPack({
1155
- repoPath: repo.repoPath,
1156
- runId,
1157
- sliceId,
1158
- maxTokens,
1159
- });
1160
- return {
1161
- ...out,
1162
- artifact_paths: {
1163
- review_pack: out.paths.reviewCardsPath,
1164
- },
1165
- };
1166
- }
1167
- catch (err) {
1168
- return { error: err?.message || 'rule_lab_review_pack failed' };
1169
- }
1170
- }
1171
- async ruleLabCurate(repo, params) {
1172
- const runId = String(params?.run_id || params?.runId || '').trim();
1173
- const sliceId = String(params?.slice_id || params?.sliceId || '').trim();
1174
- const inputPath = String(params?.input_path || params?.inputPath || '').trim();
1175
- if (!runId || !sliceId || !inputPath) {
1176
- return { error: 'run_id, slice_id, and input_path are required for rule_lab_curate' };
1177
- }
1178
- try {
1179
- const out = await curateRuleLabSlice({
1180
- repoPath: repo.repoPath,
1181
- runId,
1182
- sliceId,
1183
- inputPath,
1184
- });
1185
- return {
1186
- ...out,
1187
- artifact_paths: {
1188
- curated: out.paths.curatedPath,
1189
- },
1190
- };
1191
- }
1192
- catch (err) {
1193
- return { error: err?.message || 'rule_lab_curate failed' };
1194
- }
1195
- }
1196
- async ruleLabPromote(repo, params) {
1197
- const runId = String(params?.run_id || params?.runId || '').trim();
1198
- const sliceId = String(params?.slice_id || params?.sliceId || '').trim();
1199
- if (!runId || !sliceId) {
1200
- return { error: 'run_id and slice_id are required for rule_lab_promote' };
1201
- }
1202
- try {
1203
- const out = await promoteCuratedRules({
1204
- repoPath: repo.repoPath,
1205
- runId,
1206
- sliceId,
1207
- version: typeof params?.version === 'string' ? params.version : undefined,
1208
- });
1209
- return {
1210
- ...out,
1211
- artifact_paths: {
1212
- catalog: path.join(out.paths.rulesRoot, 'catalog.json'),
1213
- promoted_files: out.promotedFiles,
1214
- compiled_bundles: out.compiledPaths,
1215
- },
1216
- };
1217
- }
1218
- catch (err) {
1219
- return { error: err?.message || 'rule_lab_promote failed' };
1220
- }
1221
- }
1222
- async ruleLabRegress(repo, params) {
1223
- const precision = Number(params?.precision);
1224
- const coverage = Number(params?.coverage);
1225
- if (!Number.isFinite(precision) || !Number.isFinite(coverage)) {
1226
- return { error: 'precision and coverage are required numeric fields for rule_lab_regress' };
1227
- }
1228
- try {
1229
- let probes = Array.isArray(params?.probes) ? params.probes : undefined;
1230
- const probesPath = String(params?.probes_path || params?.probesPath || '').trim();
1231
- if (!probes && probesPath) {
1232
- const raw = await fs.readFile(path.isAbsolute(probesPath) ? probesPath : path.join(repo.repoPath, probesPath), 'utf-8');
1233
- probes = JSON.parse(raw);
1234
- }
1235
- const out = await runRuleLabRegress({
1236
- precision,
1237
- coverage,
1238
- probes,
1239
- repoPath: repo.repoPath,
1240
- runId: String(params?.run_id || params?.runId || '').trim() || undefined,
1241
- });
1242
- return {
1243
- ...out,
1244
- artifact_paths: out.reportPath ? { report: out.reportPath } : {},
1245
- };
1246
- }
1247
- catch (err) {
1248
- return { error: err?.message || 'rule_lab_regress failed' };
1249
- }
1250
- }
1251
1062
  // ─── Tool Implementations ────────────────────────────────────────
1252
1063
  /**
1253
1064
  * Query tool — process-grouped search.
@@ -1692,18 +1503,23 @@ export class LocalBackend {
1692
1503
  const firstResourceBindings = Array.isArray(firstSymbolForHops?.resourceBindings)
1693
1504
  ? firstSymbolForHops.resourceBindings
1694
1505
  : [];
1695
- const retrievalRule = await resolveRetrievalRuleHint({
1696
- repoPath: repo.repoPath,
1697
- queryText: params.query,
1698
- symbolName: String(firstSymbolForHops?.name || searchQuery),
1699
- seedPath,
1700
- });
1506
+ // Bindings→chains fallback: if resource_chains is empty but bindings exist, generate one-hop chains
1507
+ if ((!result.resource_chains || result.resource_chains.length === 0) && firstResourceBindings.length > 0) {
1508
+ result.resource_chains = firstResourceBindings.slice(0, 20).map((binding) => ({
1509
+ sourceResourcePath: binding.resourcePath,
1510
+ relationType: 'UNITY_GRAPH_NODE_SCRIPT_REF',
1511
+ targetSymbol: {
1512
+ uid: firstSymbolForHops?.id || undefined,
1513
+ name: firstSymbolForHops?.name || undefined,
1514
+ filePath: firstSymbolForHops?.filePath || undefined,
1515
+ },
1516
+ }));
1517
+ }
1701
1518
  result.next_hops = buildNextHops({
1702
1519
  seedPath,
1703
1520
  mappedSeedTargets,
1704
1521
  resourceBindings: firstResourceBindings,
1705
1522
  verificationHint: firstVerificationHint,
1706
- retrievalRule,
1707
1523
  repoName: repo.name,
1708
1524
  symbolName: String(firstSymbolForHops?.name || searchQuery),
1709
1525
  queryForSymbol: String(firstSymbolForHops?.name || searchQuery),
@@ -2462,18 +2278,23 @@ export class LocalBackend {
2462
2278
  }],
2463
2279
  });
2464
2280
  }
2465
- const retrievalRule = await resolveRetrievalRuleHint({
2466
- repoPath: repo.repoPath,
2467
- queryText: name,
2468
- symbolName: symName || String(name || uid || ''),
2469
- seedPath,
2470
- });
2281
+ // Bindings→chains fallback: if resource_chains is empty but bindings exist, generate one-hop chains
2282
+ if ((!result.resource_chains || result.resource_chains.length === 0) && contextResourceBindings.length > 0) {
2283
+ result.resource_chains = contextResourceBindings.slice(0, 20).map((binding) => ({
2284
+ sourceResourcePath: binding.resourcePath,
2285
+ relationType: 'UNITY_GRAPH_NODE_SCRIPT_REF',
2286
+ targetSymbol: {
2287
+ uid: symNodeId || undefined,
2288
+ name: symName || undefined,
2289
+ filePath: symFilePath || undefined,
2290
+ },
2291
+ }));
2292
+ }
2471
2293
  result.next_hops = buildNextHops({
2472
2294
  seedPath,
2473
2295
  mappedSeedTargets,
2474
2296
  resourceBindings: contextResourceBindings,
2475
2297
  verificationHint: topVerificationHint,
2476
- retrievalRule,
2477
2298
  repoName: repo.name,
2478
2299
  symbolName: symName || String(name || uid || ''),
2479
2300
  queryForSymbol: symName || String(name || uid || ''),
@@ -3,7 +3,6 @@ import os from 'node:os';
3
3
  import path from 'node:path';
4
4
  import { describe, expect, it } from 'vitest';
5
5
  import { verifyRuntimeChainOnDemand, verifyRuntimeClaimOnDemand } from './runtime-chain-verify.js';
6
- import { promoteCuratedRules } from '../../rule-lab/promote.js';
7
6
  async function makeTempRepo() {
8
7
  const repoPath = await fs.mkdtemp(path.join(os.tmpdir(), 'runtime-chain-verify-'));
9
8
  await fs.mkdir(path.join(repoPath, 'Assets/NEON/DataAssets/Powerups/1_newWeapon/0_pick/法器_Orb'), { recursive: true });
@@ -590,52 +589,4 @@ describe('runtime chain verify', () => {
590
589
  expect(String(out.next_action || '')).toContain('Reload runtime start sequence');
591
590
  await fs.rm(repoPath, { recursive: true, force: true });
592
591
  });
593
- it('phase5 promote artifacts do not alter query-time graph-only rule identity', async () => {
594
- const repoPath = await fs.mkdtemp(path.join(os.tmpdir(), 'runtime-chain-rule-lab-promote-'));
595
- const sliceDir = path.join(repoPath, '.gitnexus', 'rules', 'lab', 'runs', 'run-x', 'slices', 'slice-a');
596
- await fs.mkdir(sliceDir, { recursive: true });
597
- await fs.writeFile(path.join(sliceDir, 'curated.json'), JSON.stringify({
598
- run_id: 'run-x',
599
- slice_id: 'slice-a',
600
- curated: [
601
- {
602
- id: 'candidate-startup-1',
603
- rule_id: 'demo.startup.v1',
604
- title: 'startup startup graph',
605
- confirmed_chain: {
606
- steps: [{ hop_type: 'resource', anchor: 'Assets/Rules/startup.asset:1', snippet: 'Startup Graph Trigger' }],
607
- },
608
- guarantees: ['startup trigger matching is confirmed'],
609
- non_guarantees: ['does not prove full runtime ordering'],
610
- },
611
- ],
612
- }, null, 2), 'utf-8');
613
- await promoteCuratedRules({ repoPath, runId: 'run-x', sliceId: 'slice-a', version: '1.0.0' });
614
- const out = await verifyRuntimeClaimOnDemand({
615
- repoPath,
616
- queryText: 'Startup Graph Trigger',
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
- },
632
- resourceBindings: [{ resourcePath: 'Assets/Rules/startup.asset' }],
633
- });
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');
638
- expect(out.reason).toBeUndefined();
639
- await fs.rm(repoPath, { recursive: true, force: true });
640
- });
641
592
  });
@@ -1,10 +1,8 @@
1
- import type { UnityResourceBinding, LifecycleOverrides } from '../../rule-lab/types.js';
2
1
  export interface RuntimeClaimRuleCatalogEntry {
3
2
  id: string;
4
3
  version: string;
5
4
  file?: string;
6
5
  enabled?: boolean;
7
- family?: 'analyze_rules' | 'verification_rules';
8
6
  }
9
7
  export interface RuntimeClaimRule {
10
8
  id: string;
@@ -24,9 +22,6 @@ export interface RuntimeClaimRule {
24
22
  guarantees: string[];
25
23
  non_guarantees: string[];
26
24
  next_action?: string;
27
- family?: 'analyze_rules' | 'verification_rules';
28
- resource_bindings?: UnityResourceBinding[];
29
- lifecycle_overrides?: LifecycleOverrides;
30
25
  file_path: string;
31
26
  topology?: Array<{
32
27
  hop: string;
@@ -60,9 +55,3 @@ export declare class RuleRegistryLoadError extends Error {
60
55
  constructor(code: RuleRegistryLoadErrorCode, message: string, details?: Record<string, string>);
61
56
  }
62
57
  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
- */
67
- export declare function loadRuleRegistry(repoPath: string, rulesRoot?: string): Promise<RuntimeClaimRuleRegistry>;
68
- export declare function loadAnalyzeRules(repoPath: string, rulesRoot?: string): Promise<RuntimeClaimRule[]>;
@@ -1,6 +1,3 @@
1
- import fs from 'node:fs/promises';
2
- import path from 'node:path';
3
- import { loadCompiledRuleBundle } from '../../rule-lab/compiled-bundles.js';
4
1
  export class RuleRegistryLoadError extends Error {
5
2
  code;
6
3
  details;
@@ -139,59 +136,6 @@ export function parseRuleYaml(raw, filePath) {
139
136
  const legacyGuarantees = readList(raw, 'guarantees');
140
137
  const legacyNonGuarantees = readList(raw, 'non_guarantees');
141
138
  const legacyNextAction = readScalar(raw, 'next_action');
142
- // Parse resource_bindings
143
- const rbLines = readSectionLines(raw, 'resource_bindings');
144
- let resource_bindings;
145
- if (rbLines.length > 0) {
146
- resource_bindings = [];
147
- const joined = rbLines.map((l) => l.replace(/^\s{2}/, '')).join('\n');
148
- const entries = joined.split(/(?=^\s*- kind:)/m).filter((s) => s.trim());
149
- for (const entry of entries) {
150
- const kindMatch = entry.match(/- kind:\s*(.+)/);
151
- if (!kindMatch)
152
- continue;
153
- const binding = { kind: decodeYamlScalar(kindMatch[1]) };
154
- const scalar = (k) => {
155
- const m = entry.match(new RegExp(`^\\s+${k}:\\s*(.+)$`, 'm'));
156
- return m ? decodeYamlScalar(m[1]) : undefined;
157
- };
158
- const list = (k) => {
159
- const lines = entry.split('\n');
160
- const idx = lines.findIndex((l) => new RegExp(`^\\s+${k}:\\s*$`).test(l));
161
- if (idx < 0)
162
- return undefined;
163
- const out = [];
164
- for (let i = idx + 1; i < lines.length; i++) {
165
- if (!/^\s+-\s+/.test(lines[i]))
166
- break;
167
- out.push(decodeYamlScalar(lines[i].replace(/^\s+-\s+/, '')));
168
- }
169
- return out.length > 0 ? out : undefined;
170
- };
171
- binding.ref_field_pattern = scalar('ref_field_pattern');
172
- binding.target_entry_points = list('target_entry_points');
173
- binding.host_class_pattern = scalar('host_class_pattern');
174
- binding.field_name = scalar('field_name');
175
- binding.loader_methods = list('loader_methods');
176
- binding.scene_name = scalar('scene_name');
177
- binding.source_class_pattern = scalar('source_class_pattern');
178
- binding.source_method = scalar('source_method');
179
- binding.target_class_pattern = scalar('target_class_pattern');
180
- binding.target_method = scalar('target_method');
181
- resource_bindings.push(binding);
182
- }
183
- if (resource_bindings.length === 0)
184
- resource_bindings = undefined;
185
- }
186
- // Parse lifecycle_overrides
187
- const loEntryPoints = readNestedList(raw, 'lifecycle_overrides', 'additional_entry_points');
188
- const loScope = readNestedScalar(raw, 'lifecycle_overrides', 'scope');
189
- const lifecycle_overrides = loEntryPoints.length > 0 || loScope
190
- ? {
191
- ...(loEntryPoints.length > 0 ? { additional_entry_points: loEntryPoints } : {}),
192
- ...(loScope ? { scope: loScope } : {}),
193
- }
194
- : undefined;
195
139
  return {
196
140
  id,
197
141
  version,
@@ -209,109 +153,6 @@ export function parseRuleYaml(raw, filePath) {
209
153
  guarantees: claimGuarantees.length > 0 ? claimGuarantees : legacyGuarantees,
210
154
  non_guarantees: claimNonGuarantees.length > 0 ? claimNonGuarantees : legacyNonGuarantees,
211
155
  next_action: claimNextAction || legacyNextAction,
212
- family: readScalar(raw, 'family') || 'verification_rules',
213
- resource_bindings,
214
- lifecycle_overrides,
215
156
  file_path: filePath,
216
157
  };
217
158
  }
218
- /**
219
- * Runtime claim rule registry remains the source for analyze-time synthetic-edge production
220
- * and offline governance/report workflows. Query-time runtime closure verification is graph-only.
221
- */
222
- export async function loadRuleRegistry(repoPath, rulesRoot) {
223
- const normalizedRepoPath = path.resolve(repoPath);
224
- const root = rulesRoot
225
- ? path.resolve(rulesRoot)
226
- : path.join(normalizedRepoPath, '.gitnexus', 'rules');
227
- const compiledVerificationBundle = await loadCompiledRuleBundle(normalizedRepoPath, 'verification_rules', root);
228
- if (compiledVerificationBundle && compiledVerificationBundle.rules.length > 0) {
229
- return {
230
- repoPath: normalizedRepoPath,
231
- rulesRoot: root,
232
- catalogPath: path.join(root, 'compiled', 'verification_rules.v2.json'),
233
- activeRules: compiledVerificationBundle.rules.map((rule) => ({
234
- id: rule.id,
235
- version: rule.version,
236
- trigger_family: rule.trigger_family,
237
- resource_types: rule.resource_types,
238
- host_base_type: rule.host_base_type,
239
- match: rule.match,
240
- required_hops: rule.required_hops,
241
- guarantees: rule.guarantees,
242
- non_guarantees: rule.non_guarantees,
243
- next_action: rule.next_action,
244
- file_path: rule.file_path,
245
- topology: rule.topology,
246
- closure: rule.closure,
247
- claims: rule.claims,
248
- })),
249
- };
250
- }
251
- const catalogPath = path.join(root, 'catalog.json');
252
- let catalogRaw;
253
- try {
254
- catalogRaw = await fs.readFile(catalogPath, 'utf-8');
255
- }
256
- catch (error) {
257
- if (error?.code === 'ENOENT') {
258
- throw new RuleRegistryLoadError('rule_catalog_missing', `Runtime claim rule catalog not found: ${catalogPath}`, { repoPath: normalizedRepoPath, rulesRoot: root, catalogPath });
259
- }
260
- throw error;
261
- }
262
- let catalog;
263
- try {
264
- catalog = JSON.parse(catalogRaw);
265
- }
266
- catch {
267
- throw new RuleRegistryLoadError('rule_catalog_invalid', `Runtime claim rule catalog is invalid JSON: ${catalogPath}`, { repoPath: normalizedRepoPath, rulesRoot: root, catalogPath });
268
- }
269
- const catalogRules = Array.isArray(catalog.rules) ? catalog.rules : [];
270
- const activeRules = [];
271
- for (const entry of catalogRules) {
272
- if (entry.enabled === false)
273
- continue;
274
- const relativeRulePath = String(entry.file || path.join('approved', `${entry.id}.yaml`));
275
- const rulePath = path.join(root, relativeRulePath);
276
- let raw;
277
- try {
278
- raw = await fs.readFile(rulePath, 'utf-8');
279
- }
280
- catch (error) {
281
- if (error?.code === 'ENOENT') {
282
- throw new RuleRegistryLoadError('rule_file_missing', `Runtime claim rule file not found: ${rulePath}`, { repoPath: normalizedRepoPath, rulesRoot: root, catalogPath, rulePath, ruleId: entry.id });
283
- }
284
- throw error;
285
- }
286
- const parsed = parseRuleYaml(raw, rulePath);
287
- if (parsed.id !== entry.id) {
288
- throw new Error(`Rule id mismatch between catalog and yaml: ${entry.id} vs ${parsed.id}`);
289
- }
290
- activeRules.push({
291
- ...parsed,
292
- version: entry.version || parsed.version,
293
- family: entry.family || parsed.family || 'verification_rules',
294
- });
295
- }
296
- return {
297
- repoPath: normalizedRepoPath,
298
- rulesRoot: root,
299
- catalogPath,
300
- activeRules,
301
- };
302
- }
303
- export async function loadAnalyzeRules(repoPath, rulesRoot) {
304
- const normalizedRepoPath = path.resolve(repoPath);
305
- const root = rulesRoot
306
- ? path.resolve(rulesRoot)
307
- : path.join(normalizedRepoPath, '.gitnexus', 'rules');
308
- const analyzeBundle = await loadCompiledRuleBundle(normalizedRepoPath, 'analyze_rules', root);
309
- if (analyzeBundle && analyzeBundle.rules.length > 0) {
310
- return analyzeBundle.rules.map((rule) => ({
311
- ...rule,
312
- family: 'analyze_rules',
313
- }));
314
- }
315
- const registry = await loadRuleRegistry(repoPath, rulesRoot);
316
- return registry.activeRules.filter((r) => r.family === 'analyze_rules');
317
- }