@veewo/gitnexus 1.5.0 → 1.5.2

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 (137) 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/runtime-poc/provenance-artifact.d.ts +47 -0
  29. package/dist/benchmark/runtime-poc/provenance-artifact.js +89 -0
  30. package/dist/benchmark/runtime-poc/runner.d.ts +31 -0
  31. package/dist/benchmark/runtime-poc/runner.js +163 -0
  32. package/dist/benchmark/u2-e2e/hydration-policy-repeatability-runner.d.ts +8 -0
  33. package/dist/benchmark/u2-e2e/hydration-policy-repeatability-runner.js +21 -0
  34. package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.d.ts +0 -1
  35. package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.js +53 -51
  36. package/dist/benchmark/u2-e2e/phase2-runtime-claim-acceptance-runner.test.js +0 -1
  37. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.d.ts +1 -1
  38. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.js +82 -18
  39. package/dist/benchmark/u2-e2e/phase5-rule-lab-acceptance-runner.test.js +1 -2
  40. package/dist/benchmark/u2-e2e/retrieval-runner.js +15 -7
  41. package/dist/benchmark/u2-e2e/retrieval-runner.test.js +46 -0
  42. package/dist/cli/ai-context.js +2 -12
  43. package/dist/cli/ai-context.test.js +8 -0
  44. package/dist/cli/analyze-runtime-summary.js +1 -0
  45. package/dist/cli/analyze-runtime-summary.test.js +2 -0
  46. package/dist/cli/analyze-summary.d.ts +2 -0
  47. package/dist/cli/analyze-summary.js +24 -0
  48. package/dist/cli/analyze-summary.test.js +65 -1
  49. package/dist/cli/analyze.js +5 -1
  50. package/dist/cli/benchmark-agent-safe-query-context.d.ts +20 -0
  51. package/dist/cli/benchmark-agent-safe-query-context.js +39 -0
  52. package/dist/cli/benchmark-agent-safe-query-context.test.d.ts +1 -0
  53. package/dist/cli/benchmark-agent-safe-query-context.test.js +271 -0
  54. package/dist/cli/benchmark.d.ts +29 -0
  55. package/dist/cli/benchmark.js +55 -0
  56. package/dist/cli/index.js +23 -0
  57. package/dist/cli/rule-lab.d.ts +3 -7
  58. package/dist/cli/rule-lab.js +13 -22
  59. package/dist/cli/rule-lab.test.js +23 -3
  60. package/dist/cli/tool.d.ts +2 -0
  61. package/dist/cli/tool.js +2 -0
  62. package/dist/core/config/unity-config.d.ts +0 -1
  63. package/dist/core/config/unity-config.js +0 -1
  64. package/dist/core/ingestion/pipeline.js +35 -6
  65. package/dist/core/ingestion/unity-lifecycle-synthetic-calls.test.js +18 -20
  66. package/dist/core/ingestion/unity-parity-seed.d.ts +2 -1
  67. package/dist/core/ingestion/unity-parity-seed.js +8 -0
  68. package/dist/core/ingestion/unity-resource-processor.d.ts +11 -0
  69. package/dist/core/ingestion/unity-resource-processor.js +102 -0
  70. package/dist/core/ingestion/unity-resource-processor.test.js +449 -0
  71. package/dist/core/ingestion/unity-runtime-binding-rules.d.ts +15 -0
  72. package/dist/core/ingestion/unity-runtime-binding-rules.js +178 -30
  73. package/dist/core/lbug/csv-generator.test.js +2 -2
  74. package/dist/core/unity/doc-contract.test.d.ts +1 -0
  75. package/dist/core/unity/doc-contract.test.js +30 -0
  76. package/dist/core/unity/prefab-source-scan.d.ts +25 -0
  77. package/dist/core/unity/prefab-source-scan.js +152 -0
  78. package/dist/core/unity/prefab-source-scan.test.d.ts +1 -0
  79. package/dist/core/unity/prefab-source-scan.test.js +70 -0
  80. package/dist/core/unity/scan-context.d.ts +12 -0
  81. package/dist/core/unity/scan-context.js +50 -2
  82. package/dist/core/unity/scan-context.test.js +74 -0
  83. package/dist/mcp/local/agent-safe-response.d.ts +10 -0
  84. package/dist/mcp/local/agent-safe-response.js +639 -0
  85. package/dist/mcp/local/derived-process-reader.js +1 -1
  86. package/dist/mcp/local/local-backend.d.ts +18 -1
  87. package/dist/mcp/local/local-backend.js +319 -125
  88. package/dist/mcp/local/process-confidence.d.ts +1 -2
  89. package/dist/mcp/local/process-confidence.js +0 -3
  90. package/dist/mcp/local/process-confidence.test.js +4 -2
  91. package/dist/mcp/local/process-evidence.d.ts +1 -8
  92. package/dist/mcp/local/process-evidence.js +1 -23
  93. package/dist/mcp/local/process-evidence.test.js +2 -16
  94. package/dist/mcp/local/process-ref.d.ts +1 -1
  95. package/dist/mcp/local/runtime-chain-closure-evaluator.d.ts +33 -0
  96. package/dist/mcp/local/runtime-chain-closure-evaluator.js +273 -0
  97. package/dist/mcp/local/runtime-chain-graph-candidates.d.ts +23 -0
  98. package/dist/mcp/local/runtime-chain-graph-candidates.js +131 -0
  99. package/dist/mcp/local/runtime-chain-verify.d.ts +1 -1
  100. package/dist/mcp/local/runtime-chain-verify.js +149 -138
  101. package/dist/mcp/local/runtime-chain-verify.test.js +126 -68
  102. package/dist/mcp/local/runtime-claim-rule-registry.d.ts +4 -0
  103. package/dist/mcp/local/runtime-claim-rule-registry.js +4 -0
  104. package/dist/mcp/local/runtime-claim-rule-registry.test.js +37 -4
  105. package/dist/mcp/local/runtime-claim.d.ts +11 -0
  106. package/dist/mcp/local/runtime-claim.js +28 -0
  107. package/dist/mcp/local/unity-evidence-view.d.ts +1 -1
  108. package/dist/mcp/local/unity-evidence-view.js +1 -1
  109. package/dist/mcp/local/unity-evidence-view.test.js +22 -0
  110. package/dist/mcp/tools.js +51 -21
  111. package/dist/rule-lab/analyze.d.ts +2 -1
  112. package/dist/rule-lab/analyze.js +94 -59
  113. package/dist/rule-lab/analyze.test.js +238 -20
  114. package/dist/rule-lab/curate.d.ts +2 -1
  115. package/dist/rule-lab/curate.js +24 -3
  116. package/dist/rule-lab/curate.test.js +65 -0
  117. package/dist/rule-lab/curation-input-builder.d.ts +45 -0
  118. package/dist/rule-lab/curation-input-builder.js +133 -0
  119. package/dist/rule-lab/promote.js +80 -7
  120. package/dist/rule-lab/promote.test.js +150 -0
  121. package/dist/rule-lab/review-pack.d.ts +3 -0
  122. package/dist/rule-lab/review-pack.js +41 -1
  123. package/dist/rule-lab/review-pack.test.js +67 -0
  124. package/dist/rule-lab/types.d.ts +29 -0
  125. package/dist/types/pipeline.d.ts +3 -0
  126. package/package.json +4 -3
  127. package/scripts/run-node-tests.mjs +61 -0
  128. package/skills/_shared/unity-rule-authoring-contract.md +64 -0
  129. package/skills/_shared/unity-runtime-process-contract.md +16 -0
  130. package/skills/gitnexus-cli.md +8 -0
  131. package/skills/gitnexus-debugging.md +9 -0
  132. package/skills/gitnexus-exploring.md +66 -18
  133. package/skills/gitnexus-guide.md +42 -3
  134. package/skills/gitnexus-impact-analysis.md +8 -0
  135. package/skills/gitnexus-pr-review.md +8 -0
  136. package/skills/gitnexus-refactoring.md +8 -0
  137. package/skills/gitnexus-unity-rule-gen.md +66 -312
@@ -43,6 +43,52 @@ test('runSymbolScenario executes context off/on + deepDive and records metrics',
43
43
  assert.ok(out.steps.every((s) => s.totalTokensEst >= 0));
44
44
  assert.equal(out.assertions.pass, true);
45
45
  });
46
+ test('runSymbolScenario injects response_profile=full for legacy context/query steps', async () => {
47
+ const seen = [];
48
+ const runner = {
49
+ context: async (input) => {
50
+ seen.push({ tool: 'context', input });
51
+ if (input.unity_resources === 'on') {
52
+ return {
53
+ status: 'found',
54
+ hydrationMeta: {
55
+ requestedMode: 'compact',
56
+ effectiveMode: 'compact',
57
+ isComplete: false,
58
+ needsParityRetry: true,
59
+ },
60
+ resourceBindings: [{ resourcePath: 'Assets/Prefabs/UI.prefab', resourceType: 'prefab' }],
61
+ };
62
+ }
63
+ return { status: 'found' };
64
+ },
65
+ query: async (input) => {
66
+ seen.push({ tool: 'query', input });
67
+ return {
68
+ process_symbols: [{ id: 'Class:MainUIManager' }],
69
+ definitions: [{ name: 'DoorObj', resourceBindings: [{ resourcePath: 'Assets/A.prefab' }] }],
70
+ };
71
+ },
72
+ impact: async (input) => {
73
+ seen.push({ tool: 'impact', input });
74
+ return { impactedCount: 1 };
75
+ },
76
+ cypher: async (input) => {
77
+ seen.push({ tool: 'cypher', input });
78
+ return { rows: [] };
79
+ },
80
+ };
81
+ await runSymbolScenario(runner, {
82
+ symbol: 'MainUIManager',
83
+ kind: 'component',
84
+ objectives: ['verify context'],
85
+ deepDivePlan: [{ tool: 'query', input: { query: 'MainUIManager' } }],
86
+ });
87
+ const contextCalls = seen.filter((entry) => entry.tool === 'context');
88
+ const queryCalls = seen.filter((entry) => entry.tool === 'query');
89
+ assert.equal(contextCalls.every((entry) => entry.input.response_profile === 'full'), true);
90
+ assert.equal(queryCalls.every((entry) => entry.input.response_profile === 'full'), true);
91
+ });
46
92
  test('AssetRef requires context(on) resourceBindings after serializable-class coverage', async () => {
47
93
  const noEvidenceRunner = {
48
94
  context: async () => ({
@@ -43,6 +43,8 @@ function generateGitNexusContent(projectName, stats, skillScope, generatedSkills
43
43
  5. **If user asks to release/publish a specific version and this repo has \`DISTRIBUTION.md\`, execute that workflow in full-release mode by default** (unless user explicitly asks \`prepare-only\` or \`publish-only\`).
44
44
 
45
45
  > If step 1 warns the index is stale, ask user whether to rebuild index via \`gitnexus analyze\` when local CLI exists; otherwise resolve the pinned npx package spec from \`~/.gitnexus/config.json\` (\`cliPackageSpec\` first, then \`cliVersion\`) and run \`npx -y <resolved-spec> analyze\` (it reuses previous analyze scope/options by default; add \`--no-reuse-options\` to reset). If user declines, explicitly warn that retrieval may not reflect current codebase. For build/analyze/test commands, use a 10-30 minute timeout; on failure/timeout, report exact tool output and do not auto-retry or silently fall back to glob/grep.
46
+ > \`query/context\` slim guidance is narrowing-first: inspect \`decision.recommended_follow_up\`, \`missing_proof_targets\`, and \`suggested_context_targets\` before upgrading to \`response_profile=full\`.
47
+ > Query-time runtime closure is graph-only and does not require \`verification_rules\` / \`trigger_tokens\`; if you need hydration diagnostics such as \`needsParityRetry\` or strict fallback state, rerun with \`response_profile=full\` and then use parity before closure claims.
46
48
 
47
49
  ## Skills
48
50
 
@@ -56,18 +58,6 @@ function generateGitNexusContent(projectName, stats, skillScope, generatedSkills
56
58
  | Index, status, clean, wiki CLI commands | \`${skillRoot}/gitnexus-cli/SKILL.md\` |
57
59
  | Create Unity analyze_rules interactively | \`${skillRoot}/gitnexus-unity-rule-gen/SKILL.md\` |${generatedRows}
58
60
 
59
- ## Dev Workflow (Source Build)
60
-
61
- To use a locally built dist instead of the globally installed package (useful when testing unreleased changes):
62
-
63
- \`\`\`bash
64
- cd /path/to/GitNexus/gitnexus
65
- npm run build
66
- npm link # replaces global install with symlink to local dist/cli/index.js
67
- \`\`\`
68
-
69
- After \`npm link\`, \`gitnexus\` on this machine points to the local dist. All repos using \`gitnexus mcp\` in their MCP config will pick up the new build after restarting the agent session. To restore the published package: \`npm unlink -g @veewo/gitnexus && npm install -g @veewo/gitnexus\`.
70
-
71
61
  ${GITNEXUS_END_MARKER}`;
72
62
  }
73
63
  /**
@@ -21,10 +21,14 @@ test('generateAIContextFiles installs repo skills under .agents/skills/gitnexus'
21
21
  const claudeContent = await fs.readFile(claudePath, 'utf-8');
22
22
  await fs.access(skillPath);
23
23
  await fs.access(sharedRuntimeContractPath);
24
+ assert.match(agentsContent, /slim guidance is narrowing-first/);
25
+ assert.match(agentsContent, /Query-time runtime closure is graph-only/);
24
26
  assert.match(agentsContent, /\.agents\/skills\/gitnexus\/gitnexus-exploring\/SKILL\.md/);
25
27
  assert.match(claudeContent, /\.agents\/skills\/gitnexus\/gitnexus-exploring\/SKILL\.md/);
26
28
  assert.doesNotMatch(agentsContent, /## Unity Runtime Process 真理源/);
27
29
  assert.doesNotMatch(claudeContent, /## Unity Runtime Process 真理源/);
30
+ assert.doesNotMatch(agentsContent, /## Dev Workflow \(Source Build\)/);
31
+ assert.doesNotMatch(claudeContent, /## Dev Workflow \(Source Build\)/);
28
32
  assert.equal(agentsContent, claudeContent, 'AGENTS.md and CLAUDE.md should stay content-identical');
29
33
  assert.ok(result.files.some((entry) => entry.includes('.agents/skills/gitnexus/')), 'expected generated file summary to include .agents/skills/gitnexus/');
30
34
  await assert.rejects(fs.access(legacyClaudeSkillsDir));
@@ -46,10 +50,14 @@ test('generateAIContextFiles with global scope skips repo skill install', async
46
50
  const localSkillsDir = path.join(repoPath, '.agents', 'skills', 'gitnexus');
47
51
  const agentsContent = await fs.readFile(agentsPath, 'utf-8');
48
52
  const claudeContent = await fs.readFile(claudePath, 'utf-8');
53
+ assert.match(agentsContent, /slim guidance is narrowing-first/);
54
+ assert.match(agentsContent, /Query-time runtime closure is graph-only/);
49
55
  assert.match(agentsContent, /~\/\.agents\/skills\/gitnexus\/gitnexus-exploring\/SKILL\.md/);
50
56
  assert.match(claudeContent, /~\/\.agents\/skills\/gitnexus\/gitnexus-exploring\/SKILL\.md/);
51
57
  assert.doesNotMatch(agentsContent, /## Unity Runtime Process 真理源/);
52
58
  assert.doesNotMatch(claudeContent, /## Unity Runtime Process 真理源/);
59
+ assert.doesNotMatch(agentsContent, /## Dev Workflow \(Source Build\)/);
60
+ assert.doesNotMatch(claudeContent, /## Dev Workflow \(Source Build\)/);
53
61
  assert.equal(agentsContent, claudeContent, 'AGENTS.md and CLAUDE.md should stay content-identical');
54
62
  assert.ok(!result.files.some((entry) => entry.includes('.agents/skills/gitnexus/')), 'did not expect repo-local skills in generated file summary');
55
63
  await assert.rejects(fs.access(localSkillsDir));
@@ -4,6 +4,7 @@ export function toPipelineRuntimeSummary(input) {
4
4
  communityResult: input.communityResult,
5
5
  processResult: input.processResult,
6
6
  unityResult: input.unityResult,
7
+ unityRuleBindingResult: input.unityRuleBindingResult,
7
8
  scopeDiagnostics: input.scopeDiagnostics,
8
9
  csharpPreprocDiagnostics: input.csharpPreprocDiagnostics,
9
10
  };
@@ -7,6 +7,7 @@ test('toPipelineRuntimeSummary drops graph reference and preserves reporting fie
7
7
  communityResult: { stats: { totalCommunities: 3 } },
8
8
  processResult: { stats: { totalProcesses: 2 } },
9
9
  unityResult: { diagnostics: ['scanContext: scripts=1'] },
10
+ unityRuleBindingResult: { edgesInjected: 1, ruleResults: [], diagnostics: { summary: [] } },
10
11
  csharpPreprocDiagnostics: {
11
12
  enabled: true,
12
13
  defineSymbolCount: 2,
@@ -20,5 +21,6 @@ test('toPipelineRuntimeSummary drops graph reference and preserves reporting fie
20
21
  assert.equal('graph' in out, false);
21
22
  assert.equal(out.totalFileCount, 12);
22
23
  assert.equal(out.communityResult?.stats.totalCommunities, 3);
24
+ assert.equal(out.unityRuleBindingResult?.edgesInjected, 1);
23
25
  assert.equal(out.csharpPreprocDiagnostics?.normalizedFiles, 1);
24
26
  });
@@ -1,4 +1,5 @@
1
1
  import type { CSharpPreprocDiagnostics } from '../types/pipeline.js';
2
+ import type { UnityRuntimeBindingResult } from '../core/ingestion/unity-runtime-binding-rules.js';
2
3
  export interface FallbackInsertStats {
3
4
  attempted: number;
4
5
  succeeded: number;
@@ -6,5 +7,6 @@ export interface FallbackInsertStats {
6
7
  }
7
8
  export declare function formatCSharpPreprocDiagnosticsSummary(diagnostics: CSharpPreprocDiagnostics | undefined, previewLimit?: number): string[];
8
9
  export declare function formatUnityDiagnosticsSummary(diagnostics: string[] | undefined, previewLimit?: number): string[];
10
+ export declare function formatUnityRuleBindingSummary(result: UnityRuntimeBindingResult | undefined, previewLimit?: number): string[];
9
11
  export declare function formatFallbackSummary(warnings: string[] | undefined, stats: FallbackInsertStats | undefined, previewLimit?: number): string[];
10
12
  export declare function resolveFallbackStats(warnings: string[] | undefined, stats: FallbackInsertStats | undefined): FallbackInsertStats;
@@ -32,6 +32,30 @@ export function formatUnityDiagnosticsSummary(diagnostics, previewLimit = 3) {
32
32
  }
33
33
  return lines;
34
34
  }
35
+ export function formatUnityRuleBindingSummary(result, previewLimit = 3) {
36
+ if (!result)
37
+ return [];
38
+ const diagnostics = result.diagnostics;
39
+ const lines = ['Unity Rule Binding Diagnostics:'];
40
+ for (const message of diagnostics.summary) {
41
+ if (!message.startsWith('rule_binding.anomaly:')) {
42
+ lines.push(`- ${message}`);
43
+ }
44
+ }
45
+ const anomalies = diagnostics.anomalies;
46
+ if (anomalies.length === 0) {
47
+ return lines;
48
+ }
49
+ lines.push(`- rule_binding.anomalies: count=${anomalies.length}`);
50
+ const limit = previewLimit > 0 ? previewLimit : anomalies.length;
51
+ for (const message of anomalies.slice(0, limit)) {
52
+ lines.push(`- rule_binding.anomaly: ${message}`);
53
+ }
54
+ if (anomalies.length > limit) {
55
+ lines.push(`- rule_binding.anomaly: ... ${anomalies.length - limit} more`);
56
+ }
57
+ return lines;
58
+ }
35
59
  export function formatFallbackSummary(warnings, stats, previewLimit = 5) {
36
60
  if (!warnings || warnings.length === 0) {
37
61
  return [];
@@ -1,6 +1,6 @@
1
1
  import test from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
- import { formatFallbackSummary, formatUnityDiagnosticsSummary, resolveFallbackStats } from './analyze-summary.js';
3
+ import { formatFallbackSummary, formatUnityDiagnosticsSummary, formatUnityRuleBindingSummary, resolveFallbackStats, } from './analyze-summary.js';
4
4
  test('formatUnityDiagnosticsSummary returns empty when diagnostics are missing', () => {
5
5
  const lines = formatUnityDiagnosticsSummary([]);
6
6
  assert.deepEqual(lines, []);
@@ -29,6 +29,70 @@ test('formatUnityDiagnosticsSummary truncates output after max preview items', (
29
29
  '... 1 more',
30
30
  ]);
31
31
  });
32
+ test('formatUnityRuleBindingSummary renders diagnostics and agent report status', () => {
33
+ const lines = formatUnityRuleBindingSummary({
34
+ edgesInjected: 3,
35
+ ruleResults: [{ ruleId: 'unity.global-init', edgesInjected: 3 }],
36
+ diagnostics: {
37
+ rulesEvaluated: 1,
38
+ bindingsEvaluated: 1,
39
+ bindingsByKind: { method_triggers_scene_load: 1 },
40
+ methodLookupCalls: 5,
41
+ methodLookupCacheHits: 4,
42
+ sceneRuntimeTraversalCalls: 3,
43
+ sceneRuntimeTraversalCacheHits: 2,
44
+ sceneRuntimeResourcesVisited: 6,
45
+ anomalies: [],
46
+ shouldAgentReport: false,
47
+ agentReportReason: 'no anomalies detected',
48
+ summary: [
49
+ 'rule_binding.summary: rules=1, bindings=1, edges=3',
50
+ 'rule_binding.lookup: method_calls=5, cache_hits=4',
51
+ 'rule_binding.agent_report: should_report=false reason="no anomalies detected"',
52
+ ],
53
+ },
54
+ });
55
+ assert.deepEqual(lines, [
56
+ 'Unity Rule Binding Diagnostics:',
57
+ '- rule_binding.summary: rules=1, bindings=1, edges=3',
58
+ '- rule_binding.lookup: method_calls=5, cache_hits=4',
59
+ '- rule_binding.agent_report: should_report=false reason="no anomalies detected"',
60
+ ]);
61
+ });
62
+ test('formatUnityRuleBindingSummary renders anomaly preview', () => {
63
+ const lines = formatUnityRuleBindingSummary({
64
+ edgesInjected: 0,
65
+ ruleResults: [],
66
+ diagnostics: {
67
+ rulesEvaluated: 1,
68
+ bindingsEvaluated: 1,
69
+ bindingsByKind: { method_triggers_scene_load: 1 },
70
+ methodLookupCalls: 0,
71
+ methodLookupCacheHits: 0,
72
+ sceneRuntimeTraversalCalls: 0,
73
+ sceneRuntimeTraversalCacheHits: 0,
74
+ sceneRuntimeResourcesVisited: 0,
75
+ anomalies: [
76
+ 'rule=unity.global-init: scene "Global" not found in File(.unity) index',
77
+ 'rule=unity.global-init: method_triggers_scene_load missing host_class_pattern, loader_methods, or scene_name',
78
+ ],
79
+ shouldAgentReport: true,
80
+ agentReportReason: 'rule-binding anomalies detected',
81
+ summary: [
82
+ 'rule_binding.summary: rules=1, bindings=1, edges=0',
83
+ 'rule_binding.agent_report: should_report=true reason="rule-binding anomalies detected"',
84
+ ],
85
+ },
86
+ }, 1);
87
+ assert.deepEqual(lines, [
88
+ 'Unity Rule Binding Diagnostics:',
89
+ '- rule_binding.summary: rules=1, bindings=1, edges=0',
90
+ '- rule_binding.agent_report: should_report=true reason="rule-binding anomalies detected"',
91
+ '- rule_binding.anomalies: count=2',
92
+ '- rule_binding.anomaly: rule=unity.global-init: scene "Global" not found in File(.unity) index',
93
+ '- rule_binding.anomaly: ... 1 more',
94
+ ]);
95
+ });
32
96
  test('formatFallbackSummary returns empty when no warnings exist', () => {
33
97
  const lines = formatFallbackSummary([], {
34
98
  attempted: 0,
@@ -19,7 +19,7 @@ import { generateAIContextFiles } from './ai-context.js';
19
19
  import { generateSkillFiles } from './skill-gen.js';
20
20
  import fs from 'fs/promises';
21
21
  import { resolveEffectiveAnalyzeOptions } from './analyze-options.js';
22
- import { formatCSharpPreprocDiagnosticsSummary, formatFallbackSummary, formatUnityDiagnosticsSummary, resolveFallbackStats } from './analyze-summary.js';
22
+ import { formatCSharpPreprocDiagnosticsSummary, formatFallbackSummary, formatUnityDiagnosticsSummary, formatUnityRuleBindingSummary, resolveFallbackStats, } from './analyze-summary.js';
23
23
  import { resolveChildProcessExit } from './exit-code.js';
24
24
  import { toPipelineRuntimeSummary } from './analyze-runtime-summary.js';
25
25
  import { enforceSyncManifestConsistency, resolveScopeManifestForAnalyze } from './sync-manifest.js';
@@ -444,6 +444,10 @@ export const analyzeCommand = async (inputPath, options) => {
444
444
  for (const line of unitySummaryLines) {
445
445
  console.log(` ${line}`);
446
446
  }
447
+ const unityRuleBindingSummaryLines = formatUnityRuleBindingSummary(pipelineRuntime.unityRuleBindingResult);
448
+ for (const line of unityRuleBindingSummaryLines) {
449
+ console.log(` ${line}`);
450
+ }
447
451
  const csharpPreprocSummaryLines = formatCSharpPreprocDiagnosticsSummary(pipelineRuntime.csharpPreprocDiagnostics);
448
452
  for (const line of csharpPreprocSummaryLines) {
449
453
  console.log(` ${line}`);
@@ -0,0 +1,20 @@
1
+ import { runAnalyze } from '../benchmark/analyze-runner.js';
2
+ import { runAgentSafeQueryContextBenchmark, writeAgentSafeQueryContextReports, type AgentSafeQueryContextBenchmarkReport } from '../benchmark/agent-safe-query-context/report.js';
3
+ import type { AgentSafeBenchmarkSuite } from '../benchmark/agent-safe-query-context/types.js';
4
+ export declare function benchmarkAgentSafeQueryContextCommand(dataset: string, options: {
5
+ repo?: string;
6
+ repoAlias?: string;
7
+ targetPath?: string;
8
+ reportDir?: string;
9
+ subagentRunsDir?: string;
10
+ extensions?: string;
11
+ scopeManifest?: string;
12
+ scopePrefix?: string[];
13
+ skipAnalyze?: boolean;
14
+ }, deps?: {
15
+ loadSuite?: (root: string) => Promise<AgentSafeBenchmarkSuite>;
16
+ runBenchmark?: typeof runAgentSafeQueryContextBenchmark;
17
+ writeReports?: typeof writeAgentSafeQueryContextReports;
18
+ writeLine?: (line: string) => void;
19
+ analyze?: typeof runAnalyze;
20
+ }): Promise<AgentSafeQueryContextBenchmarkReport>;
@@ -0,0 +1,39 @@
1
+ import path from 'node:path';
2
+ import { runAnalyze } from '../benchmark/analyze-runner.js';
3
+ import { loadAgentSafeQueryContextSuite } from '../benchmark/agent-safe-query-context/io.js';
4
+ import { runAgentSafeQueryContextBenchmark, writeAgentSafeQueryContextReports, } from '../benchmark/agent-safe-query-context/report.js';
5
+ export async function benchmarkAgentSafeQueryContextCommand(dataset, options, deps) {
6
+ const loadSuite = deps?.loadSuite || loadAgentSafeQueryContextSuite;
7
+ const runBenchmark = deps?.runBenchmark || runAgentSafeQueryContextBenchmark;
8
+ const writeReports = deps?.writeReports || writeAgentSafeQueryContextReports;
9
+ const writeLine = deps?.writeLine || ((line) => process.stderr.write(`${line}\n`));
10
+ const analyze = deps?.analyze || runAnalyze;
11
+ const reportDir = path.resolve(options.reportDir || '.gitnexus/benchmark-agent-safe-query-context');
12
+ if (!(options.skipAnalyze ?? false)) {
13
+ if (!options.targetPath) {
14
+ throw new Error('targetPath is required unless skipAnalyze is true');
15
+ }
16
+ await analyze(path.resolve(options.targetPath), {
17
+ extensions: options.extensions,
18
+ repoAlias: options.repoAlias,
19
+ scopeManifest: options.scopeManifest,
20
+ scopePrefix: options.scopePrefix,
21
+ });
22
+ }
23
+ const suite = await loadSuite(path.resolve(dataset));
24
+ const report = await runBenchmark(suite, {
25
+ repo: options.repo || options.repoAlias || (options.targetPath ? path.basename(path.resolve(options.targetPath)) : undefined),
26
+ subagentRunsDir: options.subagentRunsDir ? path.resolve(options.subagentRunsDir) : undefined,
27
+ });
28
+ await writeReports(reportDir, report);
29
+ writeLine(`${report.pass ? 'PASS' : 'FAIL'}`);
30
+ for (const key of ['weapon_powerup', 'reload']) {
31
+ const row = report.workflow_replay_slim[key];
32
+ writeLine(`${key}: guid_invariance_pass=${row.guid_invariance_pass}, live_tool_evidence_pass=${row.live_tool_evidence_pass}, freeze_ready=${row.freeze_ready}, confirmed_chain_steps=${row.confirmed_chain.steps.length}, placeholder_leak_detected=${row.placeholder_leak_detected}, heuristic_top_summary_detected=${row.heuristic_top_summary_detected}`);
33
+ }
34
+ writeLine(`Report: ${reportDir}`);
35
+ if (!report.pass) {
36
+ process.exitCode = 1;
37
+ }
38
+ return report;
39
+ }
@@ -0,0 +1,271 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import fs from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { benchmarkAgentSafeQueryContextCommand } from './benchmark-agent-safe-query-context.js';
7
+ test('benchmark-agent-safe-query-context runs suite loader, benchmark, and report writer', async () => {
8
+ const output = [];
9
+ const calls = [];
10
+ const report = await benchmarkAgentSafeQueryContextCommand('../benchmarks/agent-safe-query-context/neonspark-v1', {
11
+ repo: 'neonspark-core',
12
+ reportDir: '.gitnexus/benchmark-agent-safe-query-context-test',
13
+ subagentRunsDir: '.gitnexus/subagent-runs',
14
+ skipAnalyze: true,
15
+ }, {
16
+ loadSuite: async () => ({
17
+ thresholds: {
18
+ workflowReplay: { maxSteps: 5 },
19
+ tokenReduction: { weapon_powerup: 0.5, reload: 0.4 },
20
+ },
21
+ cases: {
22
+ weapon_powerup: {
23
+ label: 'weapon_powerup',
24
+ start_query: 'weapon powerup equip chain',
25
+ retry_query: 'retry',
26
+ proof_contexts: ['WeaponPowerUp'],
27
+ proof_cypher: 'MATCH () RETURN 1',
28
+ tool_plan: [],
29
+ live_task: {
30
+ objective: 'Investigate WeaponPowerUp from the provided asset seed.',
31
+ symbol_seed: 'WeaponPowerUp',
32
+ resource_seed: 'Assets/NEON/DataAssets/Powerups/1_newWeapon/0_pick/法器_Orb/1_weapon_orb_key.asset',
33
+ },
34
+ semantic_tuple: {
35
+ resource_anchor: 'Assets/NEON/DataAssets/Powerups/1_newWeapon/0_pick/法器_Orb/1_weapon_orb_key.asset',
36
+ symbol_anchor: 'WeaponPowerUp',
37
+ proof_edges: ['HoldPickup -> WeaponPowerUp.PickItUp'],
38
+ closure_status: 'not_verified_full',
39
+ },
40
+ },
41
+ reload: {
42
+ label: 'reload',
43
+ start_query: 'reload getvalue checkreload',
44
+ retry_query: 'retry',
45
+ proof_contexts: ['ReloadBase'],
46
+ proof_cypher: 'MATCH () RETURN 1',
47
+ tool_plan: [],
48
+ live_task: {
49
+ objective: 'Investigate ReloadBase from the provided graph seed.',
50
+ symbol_seed: 'ReloadBase',
51
+ resource_seed: 'Assets/NEON/Graphs/PlayerGun/Gungraph_use/1_weapon_orb_key.asset',
52
+ },
53
+ semantic_tuple: {
54
+ resource_anchor: 'Assets/NEON/Graphs/PlayerGun/Gungraph_use/1_weapon_orb_key.asset',
55
+ symbol_anchor: 'ReloadBase',
56
+ proof_edge: 'ReloadBase.GetValue -> ReloadBase.CheckReload',
57
+ closure_status: 'not_verified_full',
58
+ },
59
+ },
60
+ },
61
+ }),
62
+ runBenchmark: async (_suite, options) => {
63
+ calls.push({ repo: options.repo });
64
+ const workflowReplayCases = {
65
+ weapon_powerup: {
66
+ steps: [],
67
+ semantic_tuple: {
68
+ resource_anchor: 'Assets/NEON/DataAssets/Powerups/1_newWeapon/0_pick/法器_Orb/1_weapon_orb_key.asset',
69
+ symbol_anchor: 'WeaponPowerUp',
70
+ proof_edges: ['HoldPickup -> WeaponPowerUp.PickItUp'],
71
+ closure_status: 'not_verified_full',
72
+ },
73
+ normalized_tuple_pass: true,
74
+ evidence_validation_pass: true,
75
+ failure_class: undefined,
76
+ semantic_tuple_pass: true,
77
+ anchor_top1_pass: true,
78
+ recommended_follow_up_hit: true,
79
+ post_narrowing_anchor_pass: true,
80
+ post_narrowing_follow_up_hit: true,
81
+ guid_invariance_pass: true,
82
+ base: {
83
+ primary_candidate: 'WeaponPowerUp',
84
+ recommended_follow_up: 'resource_path_prefix=Assets/NEON/DataAssets/Powerups/1_newWeapon/0_pick/法器_Orb/1_weapon_orb_key.asset',
85
+ },
86
+ guid_variant: {
87
+ primary_candidate: 'WeaponPowerUp',
88
+ recommended_follow_up: 'resource_path_prefix=Assets/NEON/DataAssets/Powerups/1_newWeapon/0_pick/法器_Orb/1_weapon_orb_key.asset',
89
+ },
90
+ confirmed_chain: { steps: ['HoldPickup -> PickItUp'] },
91
+ live_tool_evidence_pass: true,
92
+ freeze_ready: true,
93
+ tier_envelope: {
94
+ facts_present: true,
95
+ closure_present: true,
96
+ clues_present: true,
97
+ semantic_order_pass: true,
98
+ summary_source: 'facts',
99
+ },
100
+ ambiguity_detour_count: 0,
101
+ placeholder_leak_detected: false,
102
+ heuristic_top_summary_detected: false,
103
+ tool_calls_to_completion: 1,
104
+ tokens_to_completion: 1,
105
+ retry_breakdown: { query_retry_count: 0, context_retry_count: 0, cypher_retry_count: 0 },
106
+ stop_reason: 'semantic_tuple_satisfied',
107
+ },
108
+ reload: {
109
+ steps: [],
110
+ semantic_tuple: {
111
+ resource_anchor: 'Assets/NEON/Graphs/PlayerGun/Gungraph_use/1_weapon_orb_key.asset',
112
+ symbol_anchor: 'ReloadBase',
113
+ proof_edge: 'ReloadBase.GetValue -> ReloadBase.CheckReload',
114
+ closure_status: 'not_verified_full',
115
+ },
116
+ normalized_tuple_pass: true,
117
+ evidence_validation_pass: true,
118
+ failure_class: undefined,
119
+ semantic_tuple_pass: true,
120
+ anchor_top1_pass: true,
121
+ recommended_follow_up_hit: true,
122
+ post_narrowing_anchor_pass: true,
123
+ post_narrowing_follow_up_hit: true,
124
+ guid_invariance_pass: true,
125
+ base: {
126
+ primary_candidate: 'ReloadBase',
127
+ recommended_follow_up: 'resource_path_prefix=Assets/NEON/Graphs/PlayerGun/Gungraph_use/1_weapon_orb_key.asset',
128
+ },
129
+ guid_variant: {
130
+ primary_candidate: 'ReloadBase',
131
+ recommended_follow_up: 'resource_path_prefix=Assets/NEON/Graphs/PlayerGun/Gungraph_use/1_weapon_orb_key.asset',
132
+ },
133
+ confirmed_chain: { steps: ['GetValue -> CheckReload'] },
134
+ live_tool_evidence_pass: true,
135
+ freeze_ready: true,
136
+ tier_envelope: {
137
+ facts_present: true,
138
+ closure_present: true,
139
+ clues_present: true,
140
+ semantic_order_pass: true,
141
+ summary_source: 'facts',
142
+ },
143
+ ambiguity_detour_count: 0,
144
+ placeholder_leak_detected: false,
145
+ heuristic_top_summary_detected: false,
146
+ tool_calls_to_completion: 1,
147
+ tokens_to_completion: 1,
148
+ retry_breakdown: { query_retry_count: 0, context_retry_count: 0, cypher_retry_count: 0 },
149
+ stop_reason: 'semantic_tuple_satisfied',
150
+ },
151
+ };
152
+ const sameScriptCases = {
153
+ weapon_powerup: {
154
+ tool_plan: [],
155
+ steps: [],
156
+ semantic_tuple: {
157
+ resource_anchor: 'Assets/NEON/DataAssets/Powerups/1_newWeapon/0_pick/法器_Orb/1_weapon_orb_key.asset',
158
+ symbol_anchor: 'WeaponPowerUp',
159
+ proof_edges: ['HoldPickup -> WeaponPowerUp.PickItUp'],
160
+ closure_status: 'not_verified_full',
161
+ },
162
+ semantic_tuple_pass: true,
163
+ tool_calls_to_completion: 1,
164
+ tokens_to_completion: 1,
165
+ },
166
+ reload: {
167
+ tool_plan: [],
168
+ steps: [],
169
+ semantic_tuple: {
170
+ resource_anchor: 'Assets/NEON/Graphs/PlayerGun/Gungraph_use/1_weapon_orb_key.asset',
171
+ symbol_anchor: 'ReloadBase',
172
+ proof_edge: 'ReloadBase.GetValue -> ReloadBase.CheckReload',
173
+ closure_status: 'not_verified_full',
174
+ },
175
+ semantic_tuple_pass: true,
176
+ tool_calls_to_completion: 1,
177
+ tokens_to_completion: 1,
178
+ },
179
+ };
180
+ const subagentLive = {
181
+ weapon_powerup: {
182
+ steps: [],
183
+ semantic_tuple: {
184
+ resource_anchor: 'Assets/NEON/DataAssets/Powerups/1_newWeapon/0_pick/法器_Orb/1_weapon_orb_key.asset',
185
+ symbol_anchor: 'WeaponPowerUp',
186
+ proof_edges: ['HoldPickup -> WeaponPowerUp.PickItUp'],
187
+ closure_status: 'not_verified_full',
188
+ },
189
+ normalized_tuple_pass: true,
190
+ evidence_validation_pass: true,
191
+ failure_class: undefined,
192
+ semantic_tuple_pass: true,
193
+ tool_calls_to_completion: 1,
194
+ tokens_to_completion: 1,
195
+ stop_reason: 'semantic_tuple_satisfied',
196
+ prompt: 'Use only telemetry-tool.js\nFinal JSON schema:',
197
+ prompt_path: '/tmp/prompt.txt',
198
+ result_path: '/tmp/result.json',
199
+ telemetry_path: '/tmp/telemetry.jsonl',
200
+ final_result: {},
201
+ },
202
+ reload: {
203
+ steps: [],
204
+ semantic_tuple: {
205
+ resource_anchor: 'Assets/NEON/Graphs/PlayerGun/Gungraph_use/1_weapon_orb_key.asset',
206
+ symbol_anchor: 'ReloadBase',
207
+ proof_edge: 'ReloadBase.GetValue -> ReloadBase.CheckReload',
208
+ closure_status: 'not_verified_full',
209
+ },
210
+ normalized_tuple_pass: true,
211
+ evidence_validation_pass: true,
212
+ failure_class: undefined,
213
+ semantic_tuple_pass: true,
214
+ tool_calls_to_completion: 1,
215
+ tokens_to_completion: 1,
216
+ stop_reason: 'semantic_tuple_satisfied',
217
+ prompt: 'Use only telemetry-tool.js\nFinal JSON schema:',
218
+ prompt_path: '/tmp/prompt.txt',
219
+ result_path: '/tmp/result.json',
220
+ telemetry_path: '/tmp/telemetry.jsonl',
221
+ final_result: {},
222
+ },
223
+ };
224
+ return {
225
+ generatedAt: '2026-04-08T00:00:00.000Z',
226
+ workflow_replay_full: workflowReplayCases,
227
+ workflow_replay_slim: workflowReplayCases,
228
+ same_script_full: sameScriptCases,
229
+ same_script_slim: sameScriptCases,
230
+ cases: subagentLive,
231
+ same_script: {
232
+ tool_plan: { weapon_powerup: [], reload: [] },
233
+ cases: sameScriptCases,
234
+ },
235
+ subagent_live: subagentLive,
236
+ acceptance: { pass: true, cases: { weapon_powerup: true, reload: true } },
237
+ pass: true,
238
+ semantic_equivalence: { pass: false, cases: { weapon_powerup: false, reload: false } },
239
+ token_summary: {
240
+ weapon_powerup: { before: 1, after: 1, saved: 0, reduction: 0 },
241
+ reload: { before: 1, after: 1, saved: 0, reduction: 0 },
242
+ },
243
+ call_summary: {
244
+ weapon_powerup: { before: 1, after: 1, saved: 0 },
245
+ reload: { before: 1, after: 1, saved: 0 },
246
+ },
247
+ };
248
+ },
249
+ writeReports: async () => { },
250
+ writeLine: (line) => output.push(line),
251
+ analyze: async () => ({ stdout: '', stderr: '' }),
252
+ });
253
+ assert.equal(calls[0].repo, 'neonspark-core');
254
+ assert.ok(output.some((line) => line.includes('PASS')));
255
+ assert.ok(output.some((line) => line.includes('weapon_powerup: guid_invariance_pass=true, live_tool_evidence_pass=true, freeze_ready')));
256
+ assert.ok(output.some((line) => line.includes('Report:')));
257
+ assert.equal(report.workflow_replay_slim.weapon_powerup.placeholder_leak_detected, false);
258
+ assert.equal(report.workflow_replay_slim.weapon_powerup.heuristic_top_summary_detected, false);
259
+ });
260
+ test('runtime retrieval contract docs remove heuristic mode and pin full as debug-only', async () => {
261
+ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../..');
262
+ const docPaths = [
263
+ 'gitnexus/src/mcp/tools.ts',
264
+ ].map((relativePath) => path.join(repoRoot, relativePath));
265
+ const text = (await Promise.all(docPaths.map((filePath) => fs.readFile(filePath, 'utf-8')))).join('\n');
266
+ assert.ok(text.includes('discovery -> seed narrowing -> closure verification'));
267
+ assert.ok(!text.includes('resource_heuristic'));
268
+ assert.ok(text.includes('response_profile=slim is the default and sufficient'));
269
+ assert.ok(text.includes('response_profile=full is for debugging'));
270
+ assert.ok(text.includes('strong graph hops can coexist with failed closure'));
271
+ });
@@ -0,0 +1,29 @@
1
+ export interface BenchmarkCommandOptions {
2
+ repo?: string;
3
+ reportDir?: string;
4
+ recordsPath?: string;
5
+ casesPath?: string;
6
+ }
7
+ export declare function benchmarkCommand(suite: string, options?: BenchmarkCommandOptions): Promise<{
8
+ artifactPath?: string;
9
+ indexPath?: string;
10
+ sha256?: string;
11
+ comparisonPath?: string;
12
+ summaryPath?: string;
13
+ provenanceArtifactPath?: string;
14
+ provenanceIndexPath?: string;
15
+ }>;
16
+ export declare function benchmarkSuiteCommand(suite: string, options: {
17
+ repo?: string;
18
+ reportDir?: string;
19
+ recordsPath?: string;
20
+ casesPath?: string;
21
+ }): Promise<{
22
+ artifactPath?: string;
23
+ indexPath?: string;
24
+ sha256?: string;
25
+ comparisonPath?: string;
26
+ summaryPath?: string;
27
+ provenanceArtifactPath?: string;
28
+ provenanceIndexPath?: string;
29
+ }>;