@veewo/gitnexus 1.4.10-rc → 1.4.11-rc

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 (84) hide show
  1. package/dist/benchmark/u2-e2e/live-evidence-validator.d.ts +19 -0
  2. package/dist/benchmark/u2-e2e/live-evidence-validator.js +87 -0
  3. package/dist/benchmark/u2-e2e/live-evidence-validator.test.d.ts +1 -0
  4. package/dist/benchmark/u2-e2e/live-evidence-validator.test.js +33 -0
  5. package/dist/benchmark/u2-e2e/neonspark-full-e2e.js +23 -4
  6. package/dist/benchmark/u2-e2e/reload-v1-acceptance-runner.d.ts +38 -0
  7. package/dist/benchmark/u2-e2e/reload-v1-acceptance-runner.js +206 -0
  8. package/dist/benchmark/u2-e2e/reload-v1-acceptance-runner.test.d.ts +1 -0
  9. package/dist/benchmark/u2-e2e/reload-v1-acceptance-runner.test.js +72 -0
  10. package/dist/benchmark/u2-e2e/report.d.ts +1 -0
  11. package/dist/benchmark/u2-e2e/report.js +2 -0
  12. package/dist/benchmark/u2-e2e/retrieval-runner.d.ts +34 -0
  13. package/dist/benchmark/u2-e2e/retrieval-runner.js +95 -5
  14. package/dist/benchmark/u2-e2e/retrieval-runner.test.js +161 -2
  15. package/dist/cli/ai-context.js +31 -1
  16. package/dist/cli/ai-context.test.js +10 -0
  17. package/dist/cli/analyze-summary.d.ts +1 -0
  18. package/dist/cli/analyze-summary.js +21 -0
  19. package/dist/cli/analyze-summary.test.js +7 -1
  20. package/dist/cli/analyze.js +3 -10
  21. package/dist/cli/eval-server.js +1 -1
  22. package/dist/cli/index.js +2 -0
  23. package/dist/cli/setup.js +9 -0
  24. package/dist/cli/setup.test.js +2 -0
  25. package/dist/cli/tool.d.ts +2 -0
  26. package/dist/cli/tool.js +2 -0
  27. package/dist/core/ingestion/pipeline.js +24 -3
  28. package/dist/core/ingestion/process-processor.d.ts +6 -0
  29. package/dist/core/ingestion/process-processor.js +188 -7
  30. package/dist/core/ingestion/unity-lifecycle-config.d.ts +5 -0
  31. package/dist/core/ingestion/unity-lifecycle-config.js +25 -0
  32. package/dist/core/ingestion/unity-lifecycle-synthetic-calls.d.ts +26 -0
  33. package/dist/core/ingestion/unity-lifecycle-synthetic-calls.js +384 -0
  34. package/dist/core/ingestion/unity-lifecycle-synthetic-calls.test.d.ts +1 -0
  35. package/dist/core/ingestion/unity-lifecycle-synthetic-calls.test.js +541 -0
  36. package/dist/core/ingestion/unity-resource-processor.test.js +81 -0
  37. package/dist/core/lbug/csv-generator.js +11 -1
  38. package/dist/core/lbug/fallback-relationship-replay.d.ts +21 -0
  39. package/dist/core/lbug/fallback-relationship-replay.js +39 -0
  40. package/dist/core/lbug/fallback-relationship-replay.test.d.ts +1 -0
  41. package/dist/core/lbug/fallback-relationship-replay.test.js +25 -0
  42. package/dist/core/lbug/lbug-adapter.d.ts +5 -0
  43. package/dist/core/lbug/lbug-adapter.js +22 -23
  44. package/dist/core/lbug/schema.d.ts +2 -2
  45. package/dist/core/lbug/schema.js +9 -0
  46. package/dist/core/lbug/schema.test.js +1 -0
  47. package/dist/mcp/local/local-backend.d.ts +1 -1
  48. package/dist/mcp/local/local-backend.js +339 -50
  49. package/dist/mcp/local/local-backend.unity-merge.test.js +1 -1
  50. package/dist/mcp/local/process-confidence.d.ts +19 -0
  51. package/dist/mcp/local/process-confidence.js +29 -0
  52. package/dist/mcp/local/process-confidence.test.d.ts +1 -0
  53. package/dist/mcp/local/process-confidence.test.js +36 -0
  54. package/dist/mcp/local/process-evidence.d.ts +28 -0
  55. package/dist/mcp/local/process-evidence.js +65 -0
  56. package/dist/mcp/local/process-evidence.test.d.ts +1 -0
  57. package/dist/mcp/local/process-evidence.test.js +56 -0
  58. package/dist/mcp/local/runtime-chain-evidence.d.ts +7 -0
  59. package/dist/mcp/local/runtime-chain-evidence.js +13 -0
  60. package/dist/mcp/local/runtime-chain-evidence.test.d.ts +1 -0
  61. package/dist/mcp/local/runtime-chain-evidence.test.js +24 -0
  62. package/dist/mcp/local/runtime-chain-verify.d.ts +37 -0
  63. package/dist/mcp/local/runtime-chain-verify.js +221 -0
  64. package/dist/mcp/local/runtime-chain-verify.test.d.ts +1 -0
  65. package/dist/mcp/local/runtime-chain-verify.test.js +56 -0
  66. package/dist/mcp/local/unity-process-confidence-config.d.ts +1 -0
  67. package/dist/mcp/local/unity-process-confidence-config.js +4 -0
  68. package/dist/mcp/local/unity-runtime-chain-verify-config.d.ts +1 -0
  69. package/dist/mcp/local/unity-runtime-chain-verify-config.js +10 -0
  70. package/dist/mcp/local/unity-runtime-hydration.d.ts +50 -0
  71. package/dist/mcp/local/unity-runtime-hydration.js +323 -0
  72. package/dist/mcp/local/unity-runtime-hydration.test.d.ts +1 -0
  73. package/dist/mcp/local/unity-runtime-hydration.test.js +108 -0
  74. package/dist/mcp/resources.js +12 -2
  75. package/dist/mcp/tools.js +32 -0
  76. package/package.json +1 -1
  77. package/skills/_shared/unity-runtime-process-contract.md +38 -0
  78. package/skills/gitnexus-cli.md +16 -0
  79. package/skills/gitnexus-debugging.md +6 -0
  80. package/skills/gitnexus-exploring.md +6 -0
  81. package/skills/gitnexus-guide.md +4 -0
  82. package/skills/gitnexus-impact-analysis.md +6 -0
  83. package/skills/gitnexus-pr-review.md +5 -0
  84. package/skills/gitnexus-refactoring.md +4 -0
@@ -1,6 +1,9 @@
1
1
  import { performance } from 'node:perf_hooks';
2
2
  import path from 'node:path';
3
3
  import { estimateTokens } from './metrics.js';
4
+ export function containsPlaceholderLeak(value) {
5
+ return /TODO|TBD|placeholder|<symbol-or-query>/i.test(String(value || ''));
6
+ }
4
7
  function stringify(value) {
5
8
  try {
6
9
  return JSON.stringify(value ?? null);
@@ -53,7 +56,57 @@ function hasDeepDiveEvidence(output) {
53
56
  const outgoingRefs = countRefs(output?.outgoing);
54
57
  return processSymbols + definitions + candidates + rows + byDepth + impacted + incomingRefs + outgoingRefs > 0;
55
58
  }
56
- function assertScenario(scenario, contextOnOutput, deepDiveOutputs, contextUnityHydration) {
59
+ function hasQueryUnityEvidence(output) {
60
+ const symbols = [
61
+ ...(Array.isArray(output?.process_symbols) ? output.process_symbols : []),
62
+ ...(Array.isArray(output?.definitions) ? output.definitions : []),
63
+ ];
64
+ return symbols.some((symbol) => {
65
+ const bindings = Array.isArray(symbol?.resourceBindings) ? symbol.resourceBindings.length : 0;
66
+ const scalarFields = Array.isArray(symbol?.serializedFields?.scalarFields) ? symbol.serializedFields.scalarFields.length : 0;
67
+ const referenceFields = Array.isArray(symbol?.serializedFields?.referenceFields) ? symbol.serializedFields.referenceFields.length : 0;
68
+ return bindings + scalarFields + referenceFields > 0;
69
+ });
70
+ }
71
+ function normalizeRate(count, total) {
72
+ if (!Number.isFinite(total) || total <= 0)
73
+ return 0;
74
+ const pct = (count / total) * 100;
75
+ return Number(pct.toFixed(3));
76
+ }
77
+ export function summarizePhase5ConfidenceCalibration(input) {
78
+ const artifactPath = String(input.baseline?.artifactPath || '').trim();
79
+ const gitCommit = String(input.baseline?.gitCommit || '').trim();
80
+ const sha256 = String(input.baseline?.sha256 || '').trim();
81
+ if (!artifactPath || !gitCommit || !sha256) {
82
+ throw new Error('baseline provenance missing: artifactPath/gitCommit/sha256 are required');
83
+ }
84
+ const baselineTotal = Number(input.baseline.totalEvaluated || input.current.totalEvaluated || 0);
85
+ const baselineFalseNegative = Number(input.baseline.falseNegativeCount || 0);
86
+ const baselineFalseConfidence = Number(input.baseline.falseConfidenceCount || 0);
87
+ const current = input.current;
88
+ const falseNegativeRateBaselinePct = normalizeRate(baselineFalseNegative, baselineTotal);
89
+ const falseNegativeRateCurrentPct = normalizeRate(current.falseNegativeCount, current.totalEvaluated);
90
+ const falseConfidenceRateBaselinePct = normalizeRate(baselineFalseConfidence, baselineTotal);
91
+ const falseConfidenceRateCurrentPct = normalizeRate(current.falseConfidenceCount, current.totalEvaluated);
92
+ return {
93
+ lowConfidenceHintCoverage: normalizeRate(current.lowConfidenceHintCovered, current.lowConfidenceCount),
94
+ falseConfidenceFailures: current.falseConfidenceCount,
95
+ falseNegativeFallbackCoverage: current.fallbackCovered,
96
+ falseNegativeRateBaselinePct,
97
+ falseNegativeRateCurrentPct,
98
+ falseNegativeRateDeltaPct: Number((falseNegativeRateCurrentPct - falseNegativeRateBaselinePct).toFixed(3)),
99
+ falseConfidenceRateBaselinePct,
100
+ falseConfidenceRateCurrentPct,
101
+ falseConfidenceRateDeltaPct: Number((falseConfidenceRateCurrentPct - falseConfidenceRateBaselinePct).toFixed(3)),
102
+ baseline: {
103
+ artifactPath,
104
+ gitCommit,
105
+ sha256,
106
+ },
107
+ };
108
+ }
109
+ function assertScenario(scenario, contextOnOutput, deepDiveExecutions, contextUnityHydration) {
57
110
  const failures = [];
58
111
  const bindings = Array.isArray(contextOnOutput?.resourceBindings) ? contextOnOutput.resourceBindings : [];
59
112
  const hasBindings = bindings.length > 0;
@@ -90,11 +143,48 @@ function assertScenario(scenario, contextOnOutput, deepDiveOutputs, contextUnity
90
143
  if (!hasBindings) {
91
144
  failures.push('AssetRef: context(on) must include resourceBindings');
92
145
  }
93
- const deepDiveEvidence = deepDiveOutputs.some((output) => hasDeepDiveEvidence(output));
146
+ const deepDiveEvidence = deepDiveExecutions.some((step) => hasDeepDiveEvidence(step.output));
94
147
  if (!deepDiveEvidence) {
95
148
  failures.push('AssetRef: deep-dive must provide usage/dependency evidence');
96
149
  }
97
150
  }
151
+ const queryOnRuns = deepDiveExecutions.filter((step) => step.tool === 'query' && String(step.input?.unity_resources || '').toLowerCase() === 'on');
152
+ if (queryOnRuns.length > 0) {
153
+ const hasUnityEvidenceFromQueryOn = queryOnRuns.some((step) => hasQueryUnityEvidence(step.output));
154
+ if (!hasUnityEvidenceFromQueryOn) {
155
+ failures.push(`${scenario.symbol}: query(on) must include unity serialized/resource evidence`);
156
+ }
157
+ for (const step of queryOnRuns) {
158
+ const processes = Array.isArray(step.output?.processes) ? step.output.processes : [];
159
+ const hasUnityEvidence = hasQueryUnityEvidence(step.output);
160
+ if (hasUnityEvidence && processes.length === 0) {
161
+ failures.push(`${scenario.symbol}: empty process query(on) with unity evidence must emit confidence-guided fallback clue`);
162
+ }
163
+ for (const processRow of processes) {
164
+ const confidence = String(processRow?.confidence || '').toLowerCase();
165
+ const evidenceMode = String(processRow?.evidence_mode || '').toLowerCase();
166
+ const processSubtype = String(processRow?.process_subtype || '').toLowerCase();
167
+ if (confidence === 'low') {
168
+ const hint = processRow?.verification_hint;
169
+ const action = String(hint?.action || '').trim();
170
+ const target = String(hint?.target || '').trim();
171
+ const nextCommand = String(hint?.next_command || '').trim();
172
+ if (!action || !target || !nextCommand) {
173
+ failures.push(`${scenario.symbol}: low confidence query(on) row missing verification_hint action/target/next_command`);
174
+ }
175
+ else if (!/parity|asset|meta/i.test(nextCommand)) {
176
+ failures.push(`${scenario.symbol}: low confidence verification_hint must include parity/manual asset-meta guidance`);
177
+ }
178
+ else if (containsPlaceholderLeak(nextCommand)) {
179
+ failures.push(`${scenario.symbol}: low confidence verification_hint leaks placeholder command text`);
180
+ }
181
+ }
182
+ if (evidenceMode === 'direct_step' && processSubtype === 'static_calls' && confidence !== 'high') {
183
+ failures.push(`${scenario.symbol}: direct static chain rows must remain high confidence`);
184
+ }
185
+ }
186
+ }
187
+ }
98
188
  return {
99
189
  pass: failures.length === 0,
100
190
  failures,
@@ -168,7 +258,7 @@ export async function runSymbolScenario(runner, scenario, repo) {
168
258
  const t1 = performance.now();
169
259
  const contextOn = await runContextWithDisambiguation(runner, scenario, contextOnInput);
170
260
  steps.push(buildMetric('context-on', 'context', performance.now() - t1, contextOnInput, contextOn));
171
- const deepDiveOutputs = [];
261
+ const deepDiveExecutions = [];
172
262
  for (let i = 0; i < scenario.deepDivePlan.length; i += 1) {
173
263
  const step = scenario.deepDivePlan[i];
174
264
  const input = { ...(step.input || {}) };
@@ -177,12 +267,12 @@ export async function runSymbolScenario(runner, scenario, repo) {
177
267
  }
178
268
  const ts = performance.now();
179
269
  const output = await invokeTool(runner, step.tool, input);
180
- deepDiveOutputs.push(output);
270
+ deepDiveExecutions.push({ tool: step.tool, input, output });
181
271
  steps.push(buildMetric(`deep-dive-${i + 1}`, step.tool, performance.now() - ts, input, output));
182
272
  }
183
273
  return {
184
274
  symbol: scenario.symbol,
185
275
  steps,
186
- assertions: assertScenario(scenario, contextOn, deepDiveOutputs, contextUnityHydration),
276
+ assertions: assertScenario(scenario, contextOn, deepDiveExecutions, contextUnityHydration),
187
277
  };
188
278
  }
@@ -1,7 +1,10 @@
1
- import test from 'node:test';
2
1
  import assert from 'node:assert/strict';
3
- import { runSymbolScenario } from './retrieval-runner.js';
2
+ import { containsPlaceholderLeak, runSymbolScenario, summarizePhase5ConfidenceCalibration } from './retrieval-runner.js';
4
3
  import { loadE2EConfig } from './config.js';
4
+ const { test: rawTest } = process.env.VITEST
5
+ ? await import('vitest')
6
+ : await import('node:test');
7
+ const test = rawTest;
5
8
  test('runSymbolScenario executes context off/on + deepDive and records metrics', async () => {
6
9
  const mockToolRunner = {
7
10
  context: async (input) => {
@@ -186,3 +189,159 @@ test('runSymbolScenario fails when compact context hydrationMeta.needsParityRetr
186
189
  assert.equal(out.assertions.pass, false);
187
190
  assert.ok(out.assertions.failures.some((f) => f.includes('hydrationMeta.needsParityRetry')));
188
191
  });
192
+ test('runSymbolScenario fails when query(on) has no unity serialized/resource evidence', async () => {
193
+ const runner = {
194
+ context: async (input) => {
195
+ if (input.unity_resources === 'on') {
196
+ return {
197
+ status: 'found',
198
+ hydrationMeta: {
199
+ requestedMode: 'compact',
200
+ effectiveMode: 'compact',
201
+ isComplete: false,
202
+ needsParityRetry: true,
203
+ },
204
+ resourceBindings: [{ resourcePath: 'Assets/Prefabs/A.prefab', resourceType: 'prefab' }],
205
+ serializedFields: { scalarFields: [], referenceFields: [] },
206
+ };
207
+ }
208
+ return { status: 'found' };
209
+ },
210
+ query: async () => ({ process_symbols: [{ id: 'Class:A' }] }),
211
+ impact: async () => ({ impactedCount: 1 }),
212
+ cypher: async () => ({ rows: [] }),
213
+ };
214
+ const out = await runSymbolScenario(runner, {
215
+ symbol: 'MainUIManager',
216
+ kind: 'component',
217
+ objectives: ['verify query evidence gate'],
218
+ deepDivePlan: [{ tool: 'query', input: { query: 'MainUIManager', unity_resources: 'on' } }],
219
+ });
220
+ assert.equal(out.assertions.pass, false);
221
+ assert.ok(out.assertions.failures.some((f) => f.includes('query(on) must include unity serialized/resource evidence')));
222
+ });
223
+ test('phase5 confidence calibration fails when low confidence process is missing verification_hint', async () => {
224
+ const runner = {
225
+ context: async (input) => {
226
+ if (input.unity_resources === 'on') {
227
+ return {
228
+ status: 'found',
229
+ hydrationMeta: {
230
+ requestedMode: 'compact',
231
+ effectiveMode: 'compact',
232
+ isComplete: false,
233
+ needsParityRetry: true,
234
+ },
235
+ resourceBindings: [{ resourcePath: 'Assets/Prefabs/A.prefab', resourceType: 'prefab' }],
236
+ };
237
+ }
238
+ return { status: 'found' };
239
+ },
240
+ query: async () => ({
241
+ processes: [{ confidence: 'low', evidence_mode: 'resource_heuristic' }],
242
+ process_symbols: [{ id: 'Class:A', resourceBindings: [{ resourcePath: 'Assets/Prefabs/A.prefab' }] }],
243
+ }),
244
+ impact: async () => ({ impactedCount: 0 }),
245
+ cypher: async () => ({ rows: [] }),
246
+ };
247
+ const out = await runSymbolScenario(runner, {
248
+ symbol: 'MainUIManager',
249
+ kind: 'component',
250
+ objectives: ['phase5 low confidence hint gate'],
251
+ deepDivePlan: [{ tool: 'query', input: { query: 'MainUIManager', unity_resources: 'on' } }],
252
+ });
253
+ assert.equal(out.assertions.pass, false);
254
+ assert.ok(out.assertions.failures.some((f) => /verification_hint/i.test(f)));
255
+ });
256
+ test('phase5 confidence calibration fails when empty process result with unity evidence has no fallback clue', async () => {
257
+ const runner = {
258
+ context: async (input) => {
259
+ if (input.unity_resources === 'on') {
260
+ return {
261
+ status: 'found',
262
+ hydrationMeta: {
263
+ requestedMode: 'compact',
264
+ effectiveMode: 'compact',
265
+ isComplete: false,
266
+ needsParityRetry: true,
267
+ },
268
+ resourceBindings: [{ resourcePath: 'Assets/Prefabs/A.prefab', resourceType: 'prefab' }],
269
+ };
270
+ }
271
+ return { status: 'found' };
272
+ },
273
+ query: async () => ({
274
+ processes: [],
275
+ process_symbols: [{ id: 'Class:A', resourceBindings: [{ resourcePath: 'Assets/Prefabs/A.prefab' }] }],
276
+ }),
277
+ impact: async () => ({ impactedCount: 0 }),
278
+ cypher: async () => ({ rows: [] }),
279
+ };
280
+ const out = await runSymbolScenario(runner, {
281
+ symbol: 'MainUIManager',
282
+ kind: 'component',
283
+ objectives: ['phase5 empty process fallback gate'],
284
+ deepDivePlan: [{ tool: 'query', input: { query: 'MainUIManager', unity_resources: 'on' } }],
285
+ });
286
+ assert.equal(out.assertions.pass, false);
287
+ assert.ok(out.assertions.failures.some((f) => /fallback|empty process/i.test(f)));
288
+ });
289
+ test('phase5 confidence calibration fails when direct static chain is not high confidence', async () => {
290
+ const runner = {
291
+ context: async (input) => {
292
+ if (input.unity_resources === 'on') {
293
+ return {
294
+ status: 'found',
295
+ hydrationMeta: {
296
+ requestedMode: 'compact',
297
+ effectiveMode: 'compact',
298
+ isComplete: false,
299
+ needsParityRetry: true,
300
+ },
301
+ resourceBindings: [{ resourcePath: 'Assets/Prefabs/A.prefab', resourceType: 'prefab' }],
302
+ };
303
+ }
304
+ return { status: 'found' };
305
+ },
306
+ query: async () => ({
307
+ processes: [{
308
+ confidence: 'medium',
309
+ evidence_mode: 'direct_step',
310
+ process_subtype: 'static_calls',
311
+ verification_hint: { action: 'none', target: 'none', next_command: 'none' },
312
+ }],
313
+ process_symbols: [{ id: 'Class:A', resourceBindings: [{ resourcePath: 'Assets/Prefabs/A.prefab' }] }],
314
+ }),
315
+ impact: async () => ({ impactedCount: 0 }),
316
+ cypher: async () => ({ rows: [] }),
317
+ };
318
+ const out = await runSymbolScenario(runner, {
319
+ symbol: 'MainUIManager',
320
+ kind: 'component',
321
+ objectives: ['phase5 direct static confidence gate'],
322
+ deepDivePlan: [{ tool: 'query', input: { query: 'MainUIManager', unity_resources: 'on' } }],
323
+ });
324
+ assert.equal(out.assertions.pass, false);
325
+ assert.ok(out.assertions.failures.some((f) => /direct.*static.*high/i.test(f)));
326
+ });
327
+ test('phase5 confidence calibration summary requires baseline provenance fields', async () => {
328
+ assert.throws(() => summarizePhase5ConfidenceCalibration({
329
+ current: {
330
+ totalEvaluated: 4,
331
+ falseNegativeCount: 1,
332
+ falseConfidenceCount: 1,
333
+ lowConfidenceHintCovered: 1,
334
+ lowConfidenceCount: 2,
335
+ fallbackCovered: 2,
336
+ },
337
+ baseline: {
338
+ totalEvaluated: 4,
339
+ falseNegativeCount: 2,
340
+ falseConfidenceCount: 2,
341
+ },
342
+ }), /baseline provenance/i);
343
+ });
344
+ test('phase5 confidence calibration detects placeholder leakage in next_command', async () => {
345
+ assert.equal(containsPlaceholderLeak('Inspect <symbol-or-query> later'), true);
346
+ assert.equal(containsPlaceholderLeak('gitnexus query --unity-resources on'), false);
347
+ });
@@ -57,6 +57,12 @@ function generateGitNexusContent(projectName, stats, skillScope, cliPackageSpec,
57
57
  | Tools, resources, schema reference | \`${skillRoot}/gitnexus-guide/SKILL.md\` |
58
58
  | Index, status, clean, wiki CLI commands | \`${skillRoot}/gitnexus-cli/SKILL.md\` |${generatedRows}
59
59
 
60
+ ## Unity Runtime Process 真理源
61
+
62
+ - 统一设计与实现对照文档:\`docs/unity-runtime-process-source-of-truth.md\`
63
+ - 涉及 Unity runtime process 的任务,先阅读该文档,再执行检索/实现/验收。
64
+ - 若历史设计文档与当前实现不一致,以该真理源文档和对应代码为准,并在变更后同步更新。
65
+
60
66
  ${GITNEXUS_END_MARKER}`;
61
67
  }
62
68
  /**
@@ -107,6 +113,7 @@ async function upsertGitNexusSection(filePath, content) {
107
113
  async function installSkills(repoPath) {
108
114
  const skillsDir = path.join(repoPath, '.agents', 'skills', 'gitnexus');
109
115
  const installedSkills = [];
116
+ const packageSkillsRoot = path.join(__dirname, '..', '..', 'skills');
110
117
  // Skill definitions bundled with the package
111
118
  const skills = [
112
119
  {
@@ -141,7 +148,7 @@ async function installSkills(repoPath) {
141
148
  // Create skill directory
142
149
  await fs.mkdir(skillDir, { recursive: true });
143
150
  // Try to read from package skills directory
144
- const packageSkillPath = path.join(__dirname, '..', '..', 'skills', `${skill.name}.md`);
151
+ const packageSkillPath = path.join(packageSkillsRoot, `${skill.name}.md`);
145
152
  let skillContent;
146
153
  try {
147
154
  skillContent = await fs.readFile(packageSkillPath, 'utf-8');
@@ -168,8 +175,31 @@ Use GitNexus tools to accomplish this task.
168
175
  console.warn(`Warning: Could not install skill ${skill.name}:`, err);
169
176
  }
170
177
  }
178
+ // Shared workflow contracts (if bundled).
179
+ const packageSharedDir = path.join(packageSkillsRoot, '_shared');
180
+ try {
181
+ await fs.access(packageSharedDir);
182
+ await copyDirRecursive(packageSharedDir, path.join(skillsDir, '_shared'));
183
+ }
184
+ catch {
185
+ // Optional: older bundles may not include shared docs.
186
+ }
171
187
  return installedSkills;
172
188
  }
189
+ async function copyDirRecursive(src, dest) {
190
+ await fs.mkdir(dest, { recursive: true });
191
+ const entries = await fs.readdir(src, { withFileTypes: true });
192
+ for (const entry of entries) {
193
+ const srcPath = path.join(src, entry.name);
194
+ const destPath = path.join(dest, entry.name);
195
+ if (entry.isDirectory()) {
196
+ await copyDirRecursive(srcPath, destPath);
197
+ }
198
+ else {
199
+ await fs.copyFile(srcPath, destPath);
200
+ }
201
+ }
202
+ }
173
203
  /**
174
204
  * Generate AI context files after indexing
175
205
  */
@@ -15,12 +15,18 @@ test('generateAIContextFiles installs repo skills under .agents/skills/gitnexus'
15
15
  const agentsPath = path.join(repoPath, 'AGENTS.md');
16
16
  const claudePath = path.join(repoPath, 'CLAUDE.md');
17
17
  const skillPath = path.join(repoPath, '.agents', 'skills', 'gitnexus', 'gitnexus-exploring', 'SKILL.md');
18
+ const sharedRuntimeContractPath = path.join(repoPath, '.agents', 'skills', 'gitnexus', '_shared', 'unity-runtime-process-contract.md');
18
19
  const legacyClaudeSkillsDir = path.join(repoPath, '.claude', 'skills');
19
20
  const agentsContent = await fs.readFile(agentsPath, 'utf-8');
20
21
  const claudeContent = await fs.readFile(claudePath, 'utf-8');
21
22
  await fs.access(skillPath);
23
+ await fs.access(sharedRuntimeContractPath);
22
24
  assert.match(agentsContent, /\.agents\/skills\/gitnexus\/gitnexus-exploring\/SKILL\.md/);
23
25
  assert.match(claudeContent, /\.agents\/skills\/gitnexus\/gitnexus-exploring\/SKILL\.md/);
26
+ assert.match(agentsContent, /## Unity Runtime Process 真理源/);
27
+ assert.match(agentsContent, /docs\/unity-runtime-process-source-of-truth\.md/);
28
+ assert.match(claudeContent, /## Unity Runtime Process 真理源/);
29
+ assert.match(claudeContent, /docs\/unity-runtime-process-source-of-truth\.md/);
24
30
  assert.ok(result.files.some((entry) => entry.includes('.agents/skills/gitnexus/')), 'expected generated file summary to include .agents/skills/gitnexus/');
25
31
  await assert.rejects(fs.access(legacyClaudeSkillsDir));
26
32
  }
@@ -43,6 +49,10 @@ test('generateAIContextFiles with global scope skips repo skill install', async
43
49
  const claudeContent = await fs.readFile(claudePath, 'utf-8');
44
50
  assert.match(agentsContent, /~\/\.agents\/skills\/gitnexus\/gitnexus-exploring\/SKILL\.md/);
45
51
  assert.match(claudeContent, /~\/\.agents\/skills\/gitnexus\/gitnexus-exploring\/SKILL\.md/);
52
+ assert.match(agentsContent, /## Unity Runtime Process 真理源/);
53
+ assert.match(agentsContent, /docs\/unity-runtime-process-source-of-truth\.md/);
54
+ assert.match(claudeContent, /## Unity Runtime Process 真理源/);
55
+ assert.match(claudeContent, /docs\/unity-runtime-process-source-of-truth\.md/);
46
56
  assert.ok(!result.files.some((entry) => entry.includes('.agents/skills/gitnexus/')), 'did not expect repo-local skills in generated file summary');
47
57
  await assert.rejects(fs.access(localSkillsDir));
48
58
  }
@@ -5,3 +5,4 @@ export interface FallbackInsertStats {
5
5
  }
6
6
  export declare function formatUnityDiagnosticsSummary(diagnostics: string[] | undefined, previewLimit?: number): string[];
7
7
  export declare function formatFallbackSummary(warnings: string[] | undefined, stats: FallbackInsertStats | undefined, previewLimit?: number): string[];
8
+ export declare function resolveFallbackStats(warnings: string[] | undefined, stats: FallbackInsertStats | undefined): FallbackInsertStats;
@@ -35,3 +35,24 @@ export function formatFallbackSummary(warnings, stats, previewLimit = 5) {
35
35
  }
36
36
  return lines;
37
37
  }
38
+ export function resolveFallbackStats(warnings, stats) {
39
+ if (stats) {
40
+ return stats;
41
+ }
42
+ if (!warnings || warnings.length === 0) {
43
+ return {
44
+ attempted: 0,
45
+ succeeded: 0,
46
+ failed: 0,
47
+ };
48
+ }
49
+ const attempted = warnings.reduce((sum, warning) => {
50
+ const match = warning.match(/\((\d+)\s+edges\)/);
51
+ return sum + (match ? Number.parseInt(match[1] || '0', 10) : 0);
52
+ }, 0);
53
+ return {
54
+ attempted,
55
+ succeeded: 0,
56
+ failed: attempted,
57
+ };
58
+ }
@@ -1,6 +1,6 @@
1
1
  import test from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
- import { formatFallbackSummary, formatUnityDiagnosticsSummary } from './analyze-summary.js';
3
+ import { formatFallbackSummary, formatUnityDiagnosticsSummary, 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, []);
@@ -56,3 +56,9 @@ test('formatFallbackSummary renders attempted/succeeded/failed with warning prev
56
56
  '... 1 more',
57
57
  ]);
58
58
  });
59
+ test('resolveFallbackStats prefers runtime fallback insert stats when available', () => {
60
+ assert.deepEqual(resolveFallbackStats(['Class->File (12 edges): missing rel pair in schema'], { attempted: 12, succeeded: 3, failed: 9 }), { attempted: 12, succeeded: 3, failed: 9 });
61
+ });
62
+ test('resolveFallbackStats derives attempted/failed from warnings when runtime stats are missing', () => {
63
+ assert.deepEqual(resolveFallbackStats(['Class->File (7 edges): missing rel pair in schema'], undefined), { attempted: 7, succeeded: 0, failed: 7 });
64
+ });
@@ -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 { formatFallbackSummary, formatUnityDiagnosticsSummary } from './analyze-summary.js';
22
+ import { formatFallbackSummary, formatUnityDiagnosticsSummary, 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 { resolveCliSpec } from '../config/cli-spec.js';
@@ -442,15 +442,8 @@ export const analyzeCommand = async (inputPath, options) => {
442
442
  console.log(` Context: ${aiContext.files.join(', ')}`);
443
443
  }
444
444
  if (lbugWarnings.length > 0) {
445
- const totalFallback = lbugWarnings.reduce((sum, warning) => {
446
- const match = warning.match(/\((\d+) edges\)/);
447
- return sum + (match ? Number.parseInt(match[1], 10) : 0);
448
- }, 0);
449
- const fallbackLines = formatFallbackSummary(lbugWarnings, {
450
- attempted: totalFallback,
451
- succeeded: totalFallback,
452
- failed: 0,
453
- });
445
+ const fallbackStats = resolveFallbackStats(lbugWarnings, lbugResult.fallbackInsertStats);
446
+ const fallbackLines = formatFallbackSummary(lbugWarnings, fallbackStats);
454
447
  for (const line of fallbackLines) {
455
448
  console.log(` ${line}`);
456
449
  }
@@ -111,7 +111,7 @@ export function formatContextResult(result) {
111
111
  if (procs.length > 0) {
112
112
  lines.push(`Participates in ${procs.length} execution flow(s):`);
113
113
  for (const p of procs) {
114
- lines.push(` • ${p.name} (step ${p.step_index}/${p.step_count})`);
114
+ lines.push(` • ${p.name} (step ${p.step_index}/${p.step_count}, evidence=${p.evidence_mode || 'direct_step'}, confidence=${p.confidence || 'high'})`);
115
115
  }
116
116
  }
117
117
  if (sym.content) {
package/dist/cli/index.js CHANGED
@@ -85,6 +85,7 @@ program
85
85
  .option('--scope-preset <preset>', 'Scope preset for retrieval: unity-gameplay|unity-all')
86
86
  .option('--unity-resources <mode>', 'Unity resource retrieval mode: off|on|auto', 'off')
87
87
  .option('--unity-hydration <mode>', 'Unity hydration mode when resources are enabled: parity|compact', 'compact')
88
+ .option('--runtime-chain-verify <mode>', 'Runtime chain verification mode: off|on-demand', 'off')
88
89
  .action(createLazyAction(() => import('./tool.js'), 'queryCommand'));
89
90
  program
90
91
  .command('context [name]')
@@ -95,6 +96,7 @@ program
95
96
  .option('--content', 'Include full symbol source code')
96
97
  .option('--unity-resources <mode>', 'Unity resource retrieval mode: off|on|auto', 'off')
97
98
  .option('--unity-hydration <mode>', 'Unity hydration mode when resources are enabled: parity|compact', 'compact')
99
+ .option('--runtime-chain-verify <mode>', 'Runtime chain verification mode: off|on-demand', 'off')
98
100
  .action(createLazyAction(() => import('./tool.js'), 'contextCommand'));
99
101
  program
100
102
  .command('unity-bindings <symbol>')
package/dist/cli/setup.js CHANGED
@@ -450,6 +450,15 @@ async function installSkillsTo(targetDir) {
450
450
  // Source skill not found — skip
451
451
  }
452
452
  }
453
+ // Shared workflow contracts distributed alongside skills.
454
+ const sharedSource = path.join(skillsRoot, '_shared');
455
+ try {
456
+ await fs.access(sharedSource);
457
+ await copyDirRecursive(sharedSource, path.join(targetDir, '_shared'));
458
+ }
459
+ catch {
460
+ // Optional shared contracts directory may be absent in older packages.
461
+ }
453
462
  return installed;
454
463
  }
455
464
  /**
@@ -90,8 +90,10 @@ test('setup installs global skills under ~/.agents/skills/gitnexus', async () =>
90
90
  USERPROFILE: fakeHome,
91
91
  });
92
92
  const skillPath = path.join(fakeHome, '.agents', 'skills', 'gitnexus', 'gitnexus-exploring', 'SKILL.md');
93
+ const sharedRuntimeContractPath = path.join(fakeHome, '.agents', 'skills', 'gitnexus', '_shared', 'unity-runtime-process-contract.md');
93
94
  const configPath = path.join(fakeHome, '.gitnexus', 'config.json');
94
95
  await fs.access(skillPath);
96
+ await fs.access(sharedRuntimeContractPath);
95
97
  const configRaw = await fs.readFile(configPath, 'utf-8');
96
98
  const config = JSON.parse(configRaw);
97
99
  assert.equal(config.setupScope, 'global');
@@ -24,6 +24,7 @@ export declare function queryCommand(queryText: string, options?: {
24
24
  scopePreset?: 'unity-gameplay' | 'unity-all';
25
25
  unityResources?: UnityResourcesMode;
26
26
  unityHydration?: UnityHydrationMode;
27
+ runtimeChainVerify?: 'off' | 'on-demand';
27
28
  }): Promise<void>;
28
29
  export declare function contextCommand(name: string, options?: {
29
30
  repo?: string;
@@ -32,6 +33,7 @@ export declare function contextCommand(name: string, options?: {
32
33
  content?: boolean;
33
34
  unityResources?: UnityResourcesMode;
34
35
  unityHydration?: UnityHydrationMode;
36
+ runtimeChainVerify?: 'off' | 'on-demand';
35
37
  }): Promise<void>;
36
38
  export declare function impactCommand(target: string, options?: {
37
39
  direction?: string;
package/dist/cli/tool.js CHANGED
@@ -94,6 +94,7 @@ export async function queryCommand(queryText, options) {
94
94
  scope_preset: options?.scopePreset,
95
95
  unity_resources: options?.unityResources,
96
96
  unity_hydration_mode: options?.unityHydration,
97
+ runtime_chain_verify: options?.runtimeChainVerify,
97
98
  repo,
98
99
  });
99
100
  output(result);
@@ -112,6 +113,7 @@ export async function contextCommand(name, options) {
112
113
  include_content: options?.content ?? false,
113
114
  unity_resources: options?.unityResources,
114
115
  unity_hydration_mode: options?.unityHydration,
116
+ runtime_chain_verify: options?.runtimeChainVerify,
115
117
  repo,
116
118
  });
117
119
  output(result);
@@ -8,6 +8,8 @@ import { computeMRO } from './mro-processor.js';
8
8
  import { processCommunities } from './community-processor.js';
9
9
  import { processProcesses } from './process-processor.js';
10
10
  import { processUnityResources } from './unity-resource-processor.js';
11
+ import { applyUnityLifecycleSyntheticCalls } from './unity-lifecycle-synthetic-calls.js';
12
+ import { resolveUnityLifecycleConfig } from './unity-lifecycle-config.js';
11
13
  import { createResolutionContext } from './resolution-context.js';
12
14
  import { createASTCache } from './ast-cache.js';
13
15
  import { walkRepositoryPaths, readFileContents, walkUnityResourcePaths } from './filesystem-walker.js';
@@ -342,11 +344,20 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
342
344
  reason: 'leiden-algorithm',
343
345
  });
344
346
  });
347
+ const unityLifecycleConfig = resolveUnityLifecycleConfig(process.env);
348
+ const persistLifecycleProcessMetadata = unityLifecycleConfig.persistLifecycleProcessMetadata;
349
+ const unityLifecycleSyntheticResult = applyUnityLifecycleSyntheticCalls(graph, unityLifecycleConfig);
350
+ const syntheticEdgeDetail = unityLifecycleSyntheticResult.syntheticEdgeCount > 0
351
+ ? ` (Unity synthetic edges: ${unityLifecycleSyntheticResult.syntheticEdgeCount})`
352
+ : '';
353
+ if (isDev && unityLifecycleConfig.enabled) {
354
+ console.log(`[UnityLifecycle] enabled=${unityLifecycleConfig.enabled} hosts=${unityLifecycleSyntheticResult.hostCount} syntheticEdges=${unityLifecycleSyntheticResult.syntheticEdgeCount} rejectedHosts=${unityLifecycleSyntheticResult.rejectedHostCount}`);
355
+ }
345
356
  // ── Phase 6: Processes ─────────────────────────────────────────────
346
357
  onProgress({
347
358
  phase: 'processes',
348
359
  percent: 94,
349
- message: 'Detecting execution flows...',
360
+ message: `Detecting execution flows...${syntheticEdgeDetail}`,
350
361
  stats: { filesProcessed: totalFiles, totalFiles, nodesCreated: graph.nodeCount },
351
362
  });
352
363
  let symbolCount = 0;
@@ -378,17 +389,27 @@ export const runPipelineFromRepo = async (repoPath, onProgress, options) => {
378
389
  communities: proc.communities,
379
390
  entryPointId: proc.entryPointId,
380
391
  terminalId: proc.terminalId,
392
+ ...(persistLifecycleProcessMetadata
393
+ ? {
394
+ processSubtype: proc.processSubtype,
395
+ runtimeChainConfidence: proc.runtimeChainConfidence,
396
+ sourceReasons: proc.sourceReasons,
397
+ sourceConfidences: proc.sourceConfidences,
398
+ }
399
+ : {}),
381
400
  }
382
401
  });
383
402
  });
384
403
  processResult.steps.forEach(step => {
404
+ const persistedReason = persistLifecycleProcessMetadata ? (step.reason || 'trace-detection') : 'trace-detection';
405
+ const persistedConfidence = persistLifecycleProcessMetadata ? (step.confidence ?? 1.0) : 1.0;
385
406
  graph.addRelationship({
386
407
  id: `${step.nodeId}_step_${step.step}_${step.processId}`,
387
408
  type: 'STEP_IN_PROCESS',
388
409
  sourceId: step.nodeId,
389
410
  targetId: step.processId,
390
- confidence: 1.0,
391
- reason: 'trace-detection',
411
+ confidence: persistedConfidence,
412
+ reason: persistedReason,
392
413
  step: step.step,
393
414
  });
394
415
  });
@@ -22,6 +22,10 @@ export interface ProcessNode {
22
22
  label: string;
23
23
  heuristicLabel: string;
24
24
  processType: 'intra_community' | 'cross_community';
25
+ processSubtype: 'unity_lifecycle' | 'static_calls';
26
+ runtimeChainConfidence: 'high' | 'medium';
27
+ sourceReasons: string[];
28
+ sourceConfidences: number[];
25
29
  stepCount: number;
26
30
  communities: string[];
27
31
  entryPointId: string;
@@ -32,6 +36,8 @@ export interface ProcessStep {
32
36
  nodeId: string;
33
37
  processId: string;
34
38
  step: number;
39
+ reason?: string;
40
+ confidence?: number;
35
41
  }
36
42
  export interface ProcessDetectionResult {
37
43
  processes: ProcessNode[];