@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
@@ -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 () => ({
@@ -20,7 +20,6 @@ interface RepoStats {
20
20
  */
21
21
  export declare function generateAIContextFiles(repoPath: string, _storagePath: string, projectName: string, stats: RepoStats, options?: {
22
22
  skillScope?: SkillScope;
23
- cliPackageSpec?: string;
24
23
  }, generatedSkills?: GeneratedSkillInfo[]): Promise<{
25
24
  files: string[];
26
25
  }>;
@@ -8,7 +8,6 @@
8
8
  import fs from 'fs/promises';
9
9
  import path from 'path';
10
10
  import { fileURLToPath } from 'url';
11
- import { buildNpxCommand, resolveCliSpec } from '../config/cli-spec.js';
12
11
  // ESM equivalent of __dirname
13
12
  const __filename = fileURLToPath(import.meta.url);
14
13
  const __dirname = path.dirname(__filename);
@@ -25,14 +24,13 @@ const GITNEXUS_END_MARKER = '<!-- gitnexus:end -->';
25
24
  * - Exact tool commands with parameters — vague directives get ignored
26
25
  * - Self-review checklist — forces model to verify its own work
27
26
  */
28
- function generateGitNexusContent(projectName, stats, skillScope, cliPackageSpec, generatedSkills) {
27
+ function generateGitNexusContent(projectName, stats, skillScope, generatedSkills) {
29
28
  const skillRoot = skillScope === 'global'
30
29
  ? '~/.agents/skills/gitnexus'
31
30
  : '.agents/skills/gitnexus';
32
31
  const generatedRows = (generatedSkills && generatedSkills.length > 0)
33
32
  ? `\n${generatedSkills.map((s) => `| Work in the ${s.label} area (${s.symbolCount} symbols) | \`.claude/skills/generated/${s.name}/SKILL.md\` |`).join('\n')}`
34
33
  : '';
35
- const reindexCmd = buildNpxCommand(cliPackageSpec, 'analyze');
36
34
  return `${GITNEXUS_START_MARKER}
37
35
  # GitNexus MCP
38
36
 
@@ -44,7 +42,9 @@ function generateGitNexusContent(projectName, stats, skillScope, cliPackageSpec,
44
42
  4. **Follow config/state file rules:** \`docs/gitnexus-config-files.md\`
45
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\`).
46
44
 
47
- > 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 \`${reindexCmd}\` with that exact package spec (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.
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.
48
48
 
49
49
  ## Skills
50
50
 
@@ -204,8 +204,7 @@ async function copyDirRecursive(src, dest) {
204
204
  */
205
205
  export async function generateAIContextFiles(repoPath, _storagePath, projectName, stats, options, generatedSkills) {
206
206
  const skillScope = options?.skillScope === 'global' ? 'global' : 'project';
207
- const cliPackageSpec = options?.cliPackageSpec || resolveCliSpec().packageSpec;
208
- const content = generateGitNexusContent(projectName, stats, skillScope, cliPackageSpec, generatedSkills);
207
+ const content = generateGitNexusContent(projectName, stats, skillScope, generatedSkills);
209
208
  const createdFiles = [];
210
209
  // Create AGENTS.md (standard for Cursor, Windsurf, OpenCode, Codex, Cline, etc.)
211
210
  const agentsPath = path.join(repoPath, 'AGENTS.md');
@@ -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));
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import { normalizeScopeRules, parseScopeRules } from '../core/ingestion/scope-filter.js';
3
+ import { normalizeScopeRules } from '../core/ingestion/scope-filter.js';
4
+ import { parseScopeManifestConfig } from './scope-manifest-config.js';
4
5
  const REPO_ALIAS_REGEX = /^[a-zA-Z0-9._-]{3,64}$/;
5
6
  export function parseExtensionList(rawExtensions) {
6
7
  return (rawExtensions || '')
@@ -21,38 +22,16 @@ export function normalizeRepoAlias(repoAlias) {
21
22
  return normalized;
22
23
  }
23
24
  export async function resolveAnalyzeScopeRules(options) {
24
- const rules = [];
25
+ let manifestRules = [];
25
26
  if (options?.scopeManifest) {
26
27
  const manifestPath = path.resolve(options.scopeManifest);
27
- let content;
28
- try {
29
- content = await fs.readFile(manifestPath, 'utf-8');
30
- }
31
- catch {
32
- throw new Error(`Scope manifest not found: ${manifestPath}`);
33
- }
34
- const manifestRules = parseScopeRules(content);
28
+ const manifest = await readScopeManifestConfig(manifestPath);
29
+ manifestRules = manifest.scopeRules;
35
30
  if (manifestRules.length === 0) {
36
31
  throw new Error(`Scope manifest has no valid scope rules: ${manifestPath}`);
37
32
  }
38
- rules.push(...manifestRules);
39
- }
40
- const prefixesRaw = Array.isArray(options?.scopePrefix)
41
- ? options?.scopePrefix || []
42
- : options?.scopePrefix
43
- ? [options.scopePrefix]
44
- : [];
45
- for (const prefix of prefixesRaw) {
46
- const trimmed = prefix.trim();
47
- if (trimmed) {
48
- rules.push(trimmed);
49
- }
50
33
  }
51
- const normalizedRules = normalizeScopeRules(rules);
52
- if ((options?.scopeManifest || prefixesRaw.length > 0) && normalizedRules.length === 0) {
53
- throw new Error('No valid scope rules provided.');
54
- }
55
- return normalizedRules;
34
+ return resolveScopeRulesFromInput(manifestRules, normalizeScopePrefixes(options?.scopePrefix), Boolean(options?.scopeManifest));
56
35
  }
57
36
  function parseScopePrefixCount(scopePrefix) {
58
37
  if (Array.isArray(scopePrefix))
@@ -62,26 +41,36 @@ function parseScopePrefixCount(scopePrefix) {
62
41
  return 0;
63
42
  }
64
43
  export async function resolveEffectiveAnalyzeOptions(options, stored) {
44
+ const manifestConfig = options?.scopeManifest
45
+ ? await readScopeManifestConfig(path.resolve(options.scopeManifest))
46
+ : undefined;
65
47
  const includeExtensionsFromCli = parseExtensionList(options?.extensions);
66
- const scopeRulesFromCli = await resolveAnalyzeScopeRules({
67
- scopeManifest: options?.scopeManifest,
68
- scopePrefix: options?.scopePrefix,
69
- });
48
+ const scopeRulesFromCli = resolveScopeRulesFromInput(manifestConfig?.scopeRules || [], normalizeScopePrefixes(options?.scopePrefix), Boolean(options?.scopeManifest));
70
49
  const repoAliasFromCli = normalizeRepoAlias(options?.repoAlias);
50
+ const manifestExtensions = manifestConfig?.directives.extensions;
51
+ const manifestRepoAlias = manifestConfig?.directives.repoAlias;
52
+ const manifestEmbeddings = manifestConfig?.directives.embeddings;
71
53
  const hasCliExtensions = options?.extensions !== undefined;
72
54
  const hasCliScope = Boolean(options?.scopeManifest) || parseScopePrefixCount(options?.scopePrefix) > 0;
73
55
  const hasCliRepoAlias = options?.repoAlias !== undefined;
74
56
  const canReuse = options?.reuseOptions !== false;
75
57
  const includeExtensions = hasCliExtensions
76
58
  ? includeExtensionsFromCli
77
- : (canReuse ? (stored?.includeExtensions || []) : []);
59
+ : (manifestExtensions !== undefined
60
+ ? parseExtensionList(manifestExtensions)
61
+ : (canReuse ? (stored?.includeExtensions || []) : []));
78
62
  const scopeRules = hasCliScope
79
63
  ? scopeRulesFromCli
80
64
  : (canReuse ? (stored?.scopeRules || []) : []);
81
65
  const repoAlias = hasCliRepoAlias
82
66
  ? repoAliasFromCli
83
- : (canReuse ? normalizeRepoAlias(stored?.repoAlias) : undefined);
84
- const embeddings = options?.embeddings ?? (canReuse ? Boolean(stored?.embeddings) : false);
67
+ : (manifestRepoAlias !== undefined
68
+ ? normalizeRepoAlias(manifestRepoAlias)
69
+ : (canReuse ? normalizeRepoAlias(stored?.repoAlias) : undefined));
70
+ const embeddings = options?.embeddings
71
+ ?? (manifestEmbeddings !== undefined
72
+ ? parseManifestEmbeddings(manifestEmbeddings)
73
+ : (canReuse ? Boolean(stored?.embeddings) : false));
85
74
  return {
86
75
  includeExtensions: [...includeExtensions],
87
76
  scopeRules: [...scopeRules],
@@ -89,3 +78,38 @@ export async function resolveEffectiveAnalyzeOptions(options, stored) {
89
78
  embeddings,
90
79
  };
91
80
  }
81
+ function normalizeScopePrefixes(scopePrefix) {
82
+ const prefixesRaw = Array.isArray(scopePrefix)
83
+ ? scopePrefix || []
84
+ : scopePrefix
85
+ ? [scopePrefix]
86
+ : [];
87
+ return prefixesRaw
88
+ .map((prefix) => prefix.trim())
89
+ .filter(Boolean);
90
+ }
91
+ function resolveScopeRulesFromInput(manifestRules, prefixes, hasScopeManifest) {
92
+ const normalizedRules = normalizeScopeRules([...manifestRules, ...prefixes]);
93
+ if ((hasScopeManifest || prefixes.length > 0) && normalizedRules.length === 0) {
94
+ throw new Error('No valid scope rules provided.');
95
+ }
96
+ return normalizedRules;
97
+ }
98
+ async function readScopeManifestConfig(manifestPath) {
99
+ let content;
100
+ try {
101
+ content = await fs.readFile(manifestPath, 'utf-8');
102
+ }
103
+ catch {
104
+ throw new Error(`Scope manifest not found: ${manifestPath}`);
105
+ }
106
+ return parseScopeManifestConfig(content);
107
+ }
108
+ function parseManifestEmbeddings(raw) {
109
+ const normalized = raw.trim().toLowerCase();
110
+ if (normalized === 'true')
111
+ return true;
112
+ if (normalized === 'false')
113
+ return false;
114
+ throw new Error(`Invalid @embeddings directive value: ${raw}. Expected true or false.`);
115
+ }
@@ -4,6 +4,7 @@ import fs from 'node:fs/promises';
4
4
  import os from 'node:os';
5
5
  import path from 'node:path';
6
6
  import { normalizeRepoAlias, parseExtensionList, resolveAnalyzeScopeRules, resolveEffectiveAnalyzeOptions, } from './analyze-options.js';
7
+ import { parseScopeManifestConfig } from './scope-manifest-config.js';
7
8
  test('parseExtensionList normalizes dot prefixes', () => {
8
9
  const exts = parseExtensionList('cs,.ts, go ');
9
10
  assert.deepEqual(exts, ['.cs', '.ts', '.go']);
@@ -75,3 +76,59 @@ test('resolveEffectiveAnalyzeOptions prefers explicit CLI values over stored set
75
76
  assert.equal(resolved.repoAlias, 'new-alias');
76
77
  assert.equal(resolved.embeddings, false);
77
78
  });
79
+ test('resolveEffectiveAnalyzeOptions reads @extensions/@repoAlias/@embeddings from manifest', async () => {
80
+ const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-manifest-directives-'));
81
+ const manifestPath = path.join(tmpDir, 'sync-manifest.txt');
82
+ await fs.writeFile(manifestPath, ['Assets/', '@extensions=.cs,.meta', '@repoAlias=neonspark-core', '@embeddings=false'].join('\n'), 'utf-8');
83
+ const resolved = await resolveEffectiveAnalyzeOptions({ scopeManifest: manifestPath }, {
84
+ includeExtensions: ['.ts'],
85
+ scopeRules: ['src'],
86
+ repoAlias: 'stored-alias',
87
+ embeddings: true,
88
+ });
89
+ assert.deepEqual(resolved.scopeRules, ['Assets']);
90
+ assert.deepEqual(resolved.includeExtensions, ['.cs', '.meta']);
91
+ assert.equal(resolved.repoAlias, 'neonspark-core');
92
+ assert.equal(resolved.embeddings, false);
93
+ });
94
+ test('resolveEffectiveAnalyzeOptions enforces precedence CLI > manifest > meta', async () => {
95
+ const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-manifest-precedence-'));
96
+ const manifestPath = path.join(tmpDir, 'sync-manifest.txt');
97
+ await fs.writeFile(manifestPath, ['Assets/', '@extensions=.cs,.meta', '@repoAlias=manifest-alias', '@embeddings=false'].join('\n'), 'utf-8');
98
+ const resolved = await resolveEffectiveAnalyzeOptions({
99
+ scopeManifest: manifestPath,
100
+ extensions: '.ts',
101
+ }, {
102
+ includeExtensions: ['.js'],
103
+ scopeRules: ['tools'],
104
+ repoAlias: 'meta-alias',
105
+ embeddings: true,
106
+ });
107
+ assert.deepEqual(resolved.scopeRules, ['Assets']);
108
+ assert.deepEqual(resolved.includeExtensions, ['.ts']);
109
+ assert.equal(resolved.repoAlias, 'manifest-alias');
110
+ assert.equal(resolved.embeddings, false);
111
+ });
112
+ test('resolveEffectiveAnalyzeOptions rejects unknown manifest directives', async () => {
113
+ const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'gitnexus-manifest-unknown-'));
114
+ const manifestPath = path.join(tmpDir, 'sync-manifest.txt');
115
+ await fs.writeFile(manifestPath, ['Assets/', '@foo=bar'].join('\n'), 'utf-8');
116
+ await assert.rejects(resolveEffectiveAnalyzeOptions({ scopeManifest: manifestPath }), /unknown manifest directive/i);
117
+ });
118
+ test('parseScopeManifestConfig splits scope rules and directives', () => {
119
+ const parsed = parseScopeManifestConfig([
120
+ '# comment',
121
+ 'Assets/',
122
+ 'Packages/com.veewo.*',
123
+ '',
124
+ '@extensions=.cs,.meta',
125
+ '@repoAlias=demo-repo',
126
+ '@embeddings=false',
127
+ ].join('\n'));
128
+ assert.deepEqual(parsed.scopeRules, ['Assets', 'Packages/com.veewo.*']);
129
+ assert.deepEqual(parsed.directives, {
130
+ extensions: '.cs,.meta',
131
+ repoAlias: 'demo-repo',
132
+ embeddings: 'false',
133
+ });
134
+ });
@@ -4,6 +4,8 @@ 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,
9
+ csharpPreprocDiagnostics: input.csharpPreprocDiagnostics,
8
10
  };
9
11
  }
@@ -7,8 +7,20 @@ 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: [] } },
11
+ csharpPreprocDiagnostics: {
12
+ enabled: true,
13
+ defineSymbolCount: 2,
14
+ normalizedFiles: 1,
15
+ fallbackFiles: 0,
16
+ skippedFiles: 3,
17
+ expressionErrors: 0,
18
+ undefinedSymbols: [],
19
+ },
10
20
  });
11
21
  assert.equal('graph' in out, false);
12
22
  assert.equal(out.totalFileCount, 12);
13
23
  assert.equal(out.communityResult?.stats.totalCommunities, 3);
24
+ assert.equal(out.unityRuleBindingResult?.edgesInjected, 1);
25
+ assert.equal(out.csharpPreprocDiagnostics?.normalizedFiles, 1);
14
26
  });
@@ -1,8 +1,12 @@
1
+ import type { CSharpPreprocDiagnostics } from '../types/pipeline.js';
2
+ import type { UnityRuntimeBindingResult } from '../core/ingestion/unity-runtime-binding-rules.js';
1
3
  export interface FallbackInsertStats {
2
4
  attempted: number;
3
5
  succeeded: number;
4
6
  failed: number;
5
7
  }
8
+ export declare function formatCSharpPreprocDiagnosticsSummary(diagnostics: CSharpPreprocDiagnostics | undefined, previewLimit?: number): string[];
6
9
  export declare function formatUnityDiagnosticsSummary(diagnostics: string[] | undefined, previewLimit?: number): string[];
10
+ export declare function formatUnityRuleBindingSummary(result: UnityRuntimeBindingResult | undefined, previewLimit?: number): string[];
7
11
  export declare function formatFallbackSummary(warnings: string[] | undefined, stats: FallbackInsertStats | undefined, previewLimit?: number): string[];
8
12
  export declare function resolveFallbackStats(warnings: string[] | undefined, stats: FallbackInsertStats | undefined): FallbackInsertStats;
@@ -1,3 +1,22 @@
1
+ export function formatCSharpPreprocDiagnosticsSummary(diagnostics, previewLimit = 5) {
2
+ if (!diagnostics?.enabled)
3
+ return [];
4
+ const lines = [
5
+ `CSharp Preproc: defines=${diagnostics.defineSymbolCount}, normalized=${diagnostics.normalizedFiles}, fallback=${diagnostics.fallbackFiles}, skipped=${diagnostics.skippedFiles}, exprErrors=${diagnostics.expressionErrors}`,
6
+ ];
7
+ if (diagnostics.sourcePath) {
8
+ lines.push(`- source: ${diagnostics.sourcePath}`);
9
+ }
10
+ if (diagnostics.undefinedSymbols.length > 0) {
11
+ const limit = previewLimit > 0 ? previewLimit : diagnostics.undefinedSymbols.length;
12
+ const preview = diagnostics.undefinedSymbols.slice(0, limit);
13
+ lines.push(`- undefined symbols: ${preview.join(', ')}`);
14
+ if (diagnostics.undefinedSymbols.length > preview.length) {
15
+ lines.push(`... ${diagnostics.undefinedSymbols.length - preview.length} more`);
16
+ }
17
+ }
18
+ return lines;
19
+ }
1
20
  export function formatUnityDiagnosticsSummary(diagnostics, previewLimit = 3) {
2
21
  if (!diagnostics || diagnostics.length === 0) {
3
22
  return [];
@@ -13,6 +32,30 @@ export function formatUnityDiagnosticsSummary(diagnostics, previewLimit = 3) {
13
32
  }
14
33
  return lines;
15
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
+ }
16
59
  export function formatFallbackSummary(warnings, stats, previewLimit = 5) {
17
60
  if (!warnings || warnings.length === 0) {
18
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,
@@ -3,15 +3,26 @@
3
3
  *
4
4
  * Indexes a repository and stores the knowledge graph in .gitnexus/
5
5
  */
6
+ import { type SyncManifestPolicy } from './sync-manifest.js';
6
7
  export interface AnalyzeOptions {
7
8
  force?: boolean;
8
9
  embeddings?: boolean;
9
10
  extensions?: string;
10
11
  repoAlias?: string;
12
+ csharpDefineCsproj?: string;
11
13
  scopeManifest?: string;
12
14
  scopePrefix?: string[];
15
+ syncManifestPolicy?: SyncManifestPolicy;
13
16
  reuseOptions?: boolean;
14
17
  skills?: boolean;
15
18
  verbose?: boolean;
16
19
  }
17
20
  export declare const analyzeCommand: (inputPath?: string, options?: AnalyzeOptions) => Promise<void>;
21
+ export declare function buildPipelineRunOptionsForAnalyze(resolvedOptions: {
22
+ includeExtensions: string[];
23
+ scopeRules: string[];
24
+ }, options?: AnalyzeOptions): {
25
+ includeExtensions: string[];
26
+ scopeRules: string[];
27
+ csharpDefineCsproj?: string;
28
+ };
@@ -19,10 +19,10 @@ 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 { 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
- import { resolveCliSpec } from '../config/cli-spec.js';
25
+ import { enforceSyncManifestConsistency, resolveScopeManifestForAnalyze } from './sync-manifest.js';
26
26
  const HEAP_MB = 8192;
27
27
  const HEAP_FLAG = `--max-old-space-size=${HEAP_MB}`;
28
28
  /** Re-exec the process with an 8GB heap if we're currently below that. */
@@ -112,9 +112,21 @@ export const analyzeCommand = async (inputPath, options) => {
112
112
  let repoAlias;
113
113
  let embeddingsEnabled = false;
114
114
  try {
115
+ const scopeManifest = await resolveScopeManifestForAnalyze(repoPath, {
116
+ scopeManifest: options?.scopeManifest,
117
+ scopePrefix: options?.scopePrefix,
118
+ });
119
+ await enforceSyncManifestConsistency({
120
+ manifestPath: scopeManifest,
121
+ extensions: options?.extensions,
122
+ repoAlias: options?.repoAlias,
123
+ embeddings: options?.embeddings,
124
+ policy: options?.syncManifestPolicy,
125
+ stdinIsTTY: Boolean(process.stdin.isTTY),
126
+ });
115
127
  const effectiveOptions = await resolveEffectiveAnalyzeOptions({
116
128
  extensions: options?.extensions,
117
- scopeManifest: options?.scopeManifest,
129
+ scopeManifest,
118
130
  scopePrefix: options?.scopePrefix,
119
131
  repoAlias: options?.repoAlias,
120
132
  embeddings: options?.embeddings,
@@ -241,11 +253,12 @@ export const analyzeCommand = async (inputPath, options) => {
241
253
  // ── Phase 1: Full Pipeline (0–60%) ─────────────────────────────────
242
254
  let pipelineResult;
243
255
  try {
256
+ const pipelineRunOptions = buildPipelineRunOptionsForAnalyze({ includeExtensions, scopeRules }, options);
244
257
  pipelineResult = await runPipelineFromRepo(repoPath, (progress) => {
245
258
  const phaseLabel = PHASE_LABELS[progress.phase] || progress.phase;
246
259
  const scaled = Math.round(progress.percent * 0.6);
247
260
  updateBar(scaled, phaseLabel);
248
- }, { includeExtensions, scopeRules });
261
+ }, pipelineRunOptions);
249
262
  }
250
263
  catch (error) {
251
264
  clearInterval(elapsedTimer);
@@ -391,7 +404,6 @@ export const analyzeCommand = async (inputPath, options) => {
391
404
  processes: pipelineRuntime.processResult?.stats.totalProcesses,
392
405
  }, {
393
406
  skillScope: (cliConfig.setupScope === 'global') ? 'global' : 'project',
394
- cliPackageSpec: resolveCliSpec({ config: cliConfig }).packageSpec,
395
407
  }, generatedSkills);
396
408
  await closeLbug();
397
409
  // Note: we intentionally do NOT call disposeEmbedder() here.
@@ -432,6 +444,14 @@ export const analyzeCommand = async (inputPath, options) => {
432
444
  for (const line of unitySummaryLines) {
433
445
  console.log(` ${line}`);
434
446
  }
447
+ const unityRuleBindingSummaryLines = formatUnityRuleBindingSummary(pipelineRuntime.unityRuleBindingResult);
448
+ for (const line of unityRuleBindingSummaryLines) {
449
+ console.log(` ${line}`);
450
+ }
451
+ const csharpPreprocSummaryLines = formatCSharpPreprocDiagnosticsSummary(pipelineRuntime.csharpPreprocDiagnostics);
452
+ for (const line of csharpPreprocSummaryLines) {
453
+ console.log(` ${line}`);
454
+ }
435
455
  console.log(` ${stats.nodes.toLocaleString()} nodes | ${stats.edges.toLocaleString()} edges | ${pipelineRuntime.communityResult?.stats.totalCommunities || 0} clusters | ${pipelineRuntime.processResult?.stats.totalProcesses || 0} flows`);
436
456
  console.log(` LadybugDB ${lbugTime}s | FTS ${ftsTime}s | Embeddings ${embeddingSkipped ? embeddingSkipReason : embeddingTime + 's'}`);
437
457
  if (includeExtensions.length > 0) {
@@ -460,6 +480,15 @@ export const analyzeCommand = async (inputPath, options) => {
460
480
  // platforms (#38, #40). Force-exit to ensure clean termination.
461
481
  process.exit(0);
462
482
  };
483
+ export function buildPipelineRunOptionsForAnalyze(resolvedOptions, options) {
484
+ return {
485
+ includeExtensions: resolvedOptions.includeExtensions,
486
+ scopeRules: resolvedOptions.scopeRules,
487
+ ...(options?.csharpDefineCsproj
488
+ ? { csharpDefineCsproj: options.csharpDefineCsproj }
489
+ : {}),
490
+ };
491
+ }
463
492
  async function persistUnityParitySeed(storagePath, seed) {
464
493
  const seedPath = path.join(storagePath, 'unity-parity-seed.json');
465
494
  if (!seed) {
@@ -0,0 +1 @@
1
+ export {};