nexus-prime 7.9.6 → 7.9.7

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.
@@ -35,6 +35,8 @@ export declare class SessionTelemetry {
35
35
  private fileIntentPaths;
36
36
  private tokenAutoApplied;
37
37
  private stickyTokenOptimization;
38
+ private tokenOptimizationPending;
39
+ private orchestrationPending;
38
40
  bootstrapped: boolean;
39
41
  bootstrapCallCount: number;
40
42
  recordCall(): void;
@@ -54,12 +56,16 @@ export declare class SessionTelemetry {
54
56
  fileReadIntentCount: number;
55
57
  callsSinceOrchestrate: number;
56
58
  tokenAutoApplied: boolean;
59
+ tokenOptimizationPending: boolean;
60
+ orchestrationPending: boolean;
57
61
  };
58
62
  elapsedMs(): number;
59
63
  advancePhase(nextPhase: LifecyclePhase): void;
60
64
  observeSuccessfulToolCall(toolName: string, args: Record<string, unknown>): void;
65
+ observeQueuedToolCall(toolName: string, args: Record<string, unknown>): void;
61
66
  needsOptimizeTokens(currentToolName?: string): boolean;
62
67
  markTokenAutoApplied(): void;
68
+ needsOrchestration(currentToolName?: string): boolean;
63
69
  needsStoreMemory(currentToolName?: string): boolean;
64
70
  needsSessionDna(currentToolName?: string): boolean;
65
71
  notifyStore(priority: number, tags: string[], memStats: {
@@ -24,6 +24,8 @@ export class SessionTelemetry {
24
24
  fileIntentPaths = new Set();
25
25
  tokenAutoApplied = false;
26
26
  stickyTokenOptimization = false;
27
+ tokenOptimizationPending = false;
28
+ orchestrationPending = false;
27
29
  bootstrapped = false;
28
30
  bootstrapCallCount = 0;
29
31
  recordCall() {
@@ -49,6 +51,8 @@ export class SessionTelemetry {
49
51
  fileReadIntentCount: this.fileReadIntentCount,
50
52
  callsSinceOrchestrate: this.callsSinceOrchestrate,
51
53
  tokenAutoApplied: this.tokenAutoApplied,
54
+ tokenOptimizationPending: this.tokenOptimizationPending,
55
+ orchestrationPending: this.orchestrationPending,
52
56
  };
53
57
  }
54
58
  elapsedMs() {
@@ -69,8 +73,10 @@ export class SessionTelemetry {
69
73
  this.optimizeTokensCalled = false;
70
74
  this.tokenAutoApplied = false;
71
75
  }
76
+ this.tokenOptimizationPending = false;
72
77
  }
73
78
  if (nextPhase === 'orchestrated') {
79
+ this.orchestrationPending = false;
74
80
  this.mindkitCheckCalled = false;
75
81
  this.storeMemoryCalledPostOrchestrate = false;
76
82
  this.sessionDnaCalled = false;
@@ -85,12 +91,17 @@ export class SessionTelemetry {
85
91
  return;
86
92
  }
87
93
  if (toolName === 'nexus_orchestrate') {
94
+ this.stickyTokenOptimization = true;
88
95
  this.advancePhase('orchestrated');
96
+ this.optimizeTokensCalled = true;
97
+ this.tokenAutoApplied = true;
98
+ this.tokenOptimizationPending = false;
89
99
  this.noteFileIntent(args.files);
90
100
  return;
91
101
  }
92
102
  if (toolName === 'nexus_optimize_tokens') {
93
103
  this.optimizeTokensCalled = true;
104
+ this.tokenOptimizationPending = false;
94
105
  if (this.lifecyclePhase === 'orchestrated')
95
106
  this.advancePhase('working');
96
107
  this.noteFileIntent(args.files);
@@ -126,6 +137,20 @@ export class SessionTelemetry {
126
137
  this.advancePhase('working');
127
138
  }
128
139
  }
140
+ observeQueuedToolCall(toolName, args) {
141
+ if (toolName === 'nexus_orchestrate') {
142
+ this.orchestrationPending = true;
143
+ this.tokenOptimizationPending = true;
144
+ this.optimizeTokensCalled = true;
145
+ this.noteFileIntent(args.files);
146
+ return;
147
+ }
148
+ if (toolName === 'nexus_optimize_tokens') {
149
+ this.tokenOptimizationPending = true;
150
+ this.optimizeTokensCalled = true;
151
+ this.noteFileIntent(args.files);
152
+ }
153
+ }
129
154
  needsOptimizeTokens(currentToolName) {
130
155
  if (currentToolName === 'nexus_optimize_tokens')
131
156
  return false;
@@ -133,12 +158,26 @@ export class SessionTelemetry {
133
158
  return false;
134
159
  if (this.tokenAutoApplied)
135
160
  return false;
161
+ if (this.tokenOptimizationPending)
162
+ return false;
136
163
  return this.fileReadIntentCount >= 1 && !this.optimizeTokensCalled;
137
164
  }
138
165
  markTokenAutoApplied() {
139
166
  this.tokenAutoApplied = true;
140
167
  this.optimizeTokensCalled = true;
141
168
  this.stickyTokenOptimization = true;
169
+ this.tokenOptimizationPending = false;
170
+ }
171
+ needsOrchestration(currentToolName) {
172
+ if (currentToolName === 'nexus_orchestrate')
173
+ return false;
174
+ if (this.lifecyclePhase === 'pre-bootstrap')
175
+ return false;
176
+ if (this.lifecyclePhase === 'orchestrated' || this.lifecyclePhase === 'working' || this.lifecyclePhase === 'closing')
177
+ return false;
178
+ if (this.orchestrationPending)
179
+ return false;
180
+ return this.bootstrapped && this.lifecyclePhase === 'bootstrapped';
142
181
  }
143
182
  needsStoreMemory(currentToolName) {
144
183
  if (currentToolName === 'nexus_store_memory')
@@ -184,9 +223,8 @@ export class SessionTelemetry {
184
223
  }
185
224
  break;
186
225
  case 'store':
187
- if ((context.priority ?? 0) > 0.8) {
188
- nudges.push('High-priority insight stored. Next: call nexus_store_memory to persist related findings.');
189
- }
226
+ // Storing memory completes the lifecycle step; do not create a
227
+ // self-referential "store again" loop.
190
228
  break;
191
229
  case 'mindkit_fail':
192
230
  nudges.push('Guardrail FAILED. Do NOT proceed. Re-scope the task or call nexus_ghost_pass for a safer approach.');
@@ -41,6 +41,7 @@ export declare class MCPAdapter implements Adapter {
41
41
  private formatRemainingProtocolSteps;
42
42
  private prependTextToResponse;
43
43
  private decorateLifecycleResponse;
44
+ private buildPreOrchestrationBlock;
44
45
  private extractGoalLikeText;
45
46
  private isMemoryTool;
46
47
  private injectMemoryContext;
@@ -124,6 +124,23 @@ const PRE_BOOTSTRAP_ALLOWED_TOOLS = new Set([
124
124
  'nexus_list_specialists',
125
125
  'nexus_list_crews',
126
126
  ]);
127
+ const PRE_ORCHESTRATE_ALLOWED_TOOLS = new Set([
128
+ ...PRE_BOOTSTRAP_ALLOWED_TOOLS,
129
+ 'nexus_orchestrate',
130
+ 'nexus_plan_execution',
131
+ 'nexus_recall_memory',
132
+ 'nexus_temporal_query',
133
+ 'nexus_store_memory',
134
+ 'nexus_search',
135
+ 'nexus_optimize_tokens',
136
+ 'nexus_mindkit_check',
137
+ 'nexus_ghost_pass',
138
+ 'nexus_token_report',
139
+ 'nexus_session_dna',
140
+ 'nexus_run_status',
141
+ 'nexus_selection_ledger',
142
+ 'nexus_license_usage',
143
+ ]);
127
144
  export class MCPAdapter {
128
145
  name;
129
146
  type = 'mcp';
@@ -513,6 +530,24 @@ export class MCPAdapter {
513
530
  ],
514
531
  };
515
532
  }
533
+ buildPreOrchestrationBlock(toolName) {
534
+ if (!this.telemetry.needsOrchestration(toolName))
535
+ return null;
536
+ if (PRE_ORCHESTRATE_ALLOWED_TOOLS.has(toolName) || isReadOnlyMcpTool(toolName))
537
+ return null;
538
+ return {
539
+ content: [{
540
+ type: 'text',
541
+ text: JSON.stringify({
542
+ status: 'blocked',
543
+ code: 'orchestration-required',
544
+ reason: 'nexus_orchestrate has not been called for this session',
545
+ action: 'Call nexus_orchestrate(prompt="<your task>") before invoking mutation, worker, kernel, hook, automation, or low-level execution tools.',
546
+ hint: 'Nexus Prime must own planning, token budgeting, memory recall, hooks, and review gates before work starts.',
547
+ }, null, 2),
548
+ }],
549
+ };
550
+ }
516
551
  extractGoalLikeText(args) {
517
552
  const candidates = [args.goal, args.task, args.prompt, args.query, args.action, args.content]
518
553
  .map((value) => String(value ?? '').trim())
@@ -790,6 +825,9 @@ export class MCPAdapter {
790
825
  }],
791
826
  };
792
827
  }
828
+ const preOrchestrationBlock = this.buildPreOrchestrationBlock(toolName);
829
+ if (preOrchestrationBlock)
830
+ return preOrchestrationBlock;
793
831
  const callId = `mcp_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
794
832
  const startTimeMs = Date.now();
795
833
  nexusEventBus.emit('mcp.call.start', {
@@ -831,8 +869,13 @@ export class MCPAdapter {
831
869
  ? { result: resultPreview }
832
870
  : { error: envelope.error.message }),
833
871
  });
834
- if (envelope.ok && !asyncReceipt?.queued) {
835
- this.telemetry.observeSuccessfulToolCall(toolName, args);
872
+ if (envelope.ok) {
873
+ if (asyncReceipt?.queued) {
874
+ this.telemetry.observeQueuedToolCall(toolName, args);
875
+ }
876
+ else {
877
+ this.telemetry.observeSuccessfulToolCall(toolName, args);
878
+ }
836
879
  }
837
880
  // Surface meaningful tool calls on the SSE feed; persist only high-signal outcomes.
838
881
  if (this.nexusRef) {
@@ -1028,8 +1071,10 @@ export class MCPAdapter {
1028
1071
  return;
1029
1072
  // Skip if an explicit optimize/orchestrate already ran this session.
1030
1073
  const usage = this.nexusRef?.getRuntime?.()?.getUsageSnapshot?.();
1031
- if (usage?.tokenOptimizationApplied)
1074
+ if (usage?.tokenOptimizationApplied) {
1075
+ this.telemetry.markTokenAutoApplied?.();
1032
1076
  return;
1077
+ }
1033
1078
  const fileRefs = this.extractFileRefsFromArgs(args);
1034
1079
  if (fileRefs.length < 5)
1035
1080
  return;
@@ -1043,6 +1088,7 @@ export class MCPAdapter {
1043
1088
  const compressionRatio = grossInput > 0 ? totalEstimated / grossInput : 0;
1044
1089
  const pct = grossInput > 0 ? Math.round((savings / grossInput) * 100) : 0;
1045
1090
  // Mark applied so we don't repeat this for every subsequent tool call.
1091
+ this.telemetry.markTokenAutoApplied?.();
1046
1092
  try {
1047
1093
  this.nexusRef?.getRuntime?.()?.recordClientToolCall?.(toolName, {
1048
1094
  tokenOptimizationApplied: true,
package/dist/cli.js CHANGED
@@ -1329,12 +1329,14 @@ program
1329
1329
  .alias('claude')
1330
1330
  .description('Integrate with Claude Code')
1331
1331
  .option('--dry-run', 'Preview changes')
1332
- .option('--hooks', 'Also install Claude Code hooks into ~/.claude/settings.json for auto-bootstrap, auto-memory, and governance')
1332
+ .option('--hooks', 'Install Claude Code hooks into ~/.claude/settings.json for auto-bootstrap, auto-memory, and governance (default)')
1333
+ .option('--no-hooks', 'Skip Claude Code hooks')
1333
1334
  .action((options) => {
1334
1335
  const definition = getSetupDefinition('claude-code');
1336
+ const shouldInstallHooks = options.hooks !== false;
1335
1337
  if (options.dryRun) {
1336
1338
  printSetupPreview(definition);
1337
- if (options.hooks) {
1339
+ if (shouldInstallHooks) {
1338
1340
  const settingsPath = join(homedir(), '.claude', 'settings.json');
1339
1341
  writeClaudeCodeHooks(settingsPath, true);
1340
1342
  }
@@ -1344,7 +1346,7 @@ program
1344
1346
  console.log(`✅ Nexus Prime installed for Claude Code`);
1345
1347
  console.log(` MCP: ${definition.configPath}`);
1346
1348
  definition.instructionFiles.forEach((file) => console.log(` Instruction: ${file.path}`));
1347
- if (options.hooks) {
1349
+ if (shouldInstallHooks) {
1348
1350
  const settingsPath = join(homedir(), '.claude', 'settings.json');
1349
1351
  writeClaudeCodeHooks(settingsPath, false);
1350
1352
  console.log(`✅ Claude Code hooks installed (auto-bootstrap, auto-memory, governance)`);
@@ -1428,9 +1430,11 @@ program
1428
1430
  .map((clientId) => getSetupDefinition(clientId));
1429
1431
  if (options.dryRun) {
1430
1432
  definitions.forEach((definition) => printSetupPreview(definition));
1433
+ writeClaudeCodeHooks(join(homedir(), '.claude', 'settings.json'), true);
1431
1434
  return;
1432
1435
  }
1433
1436
  definitions.forEach((definition) => installSetup(definition));
1437
+ writeClaudeCodeHooks(join(homedir(), '.claude', 'settings.json'), false);
1434
1438
  console.log('✅ Nexus Prime installed for all supported clients');
1435
1439
  definitions.forEach((definition) => {
1436
1440
  console.log(` ${definition.label}`);
@@ -2618,6 +2618,8 @@ export class OrchestratorEngine {
2618
2618
  isCatalogVoteSelected(entry) {
2619
2619
  if (entry.eligible === false)
2620
2620
  return false;
2621
+ if (!entry.item && entry.source !== 'explicit')
2622
+ return false;
2621
2623
  if (entry.source === 'explicit' || entry.source === 'planner')
2622
2624
  return true;
2623
2625
  if (entry.source === 'runtime-resolver')
@@ -2657,6 +2659,12 @@ export class OrchestratorEngine {
2657
2659
  if (entry.source === 'explicit') {
2658
2660
  return { eligible: true };
2659
2661
  }
2662
+ if (!entry.item) {
2663
+ return {
2664
+ eligible: false,
2665
+ blockedReason: 'Recommended artifact is not present in the runtime catalog.',
2666
+ };
2667
+ }
2660
2668
  if (entry.source === 'scorer' && entry.confidence === 'low') {
2661
2669
  return {
2662
2670
  eligible: false,
@@ -568,30 +568,36 @@ export const BUILTIN_SKILL_PACKS = BASE_DOMAIN_SKILLS.flatMap((seed) => ([
568
568
  promotionThresholds: { minSuccesses: 2, maxFailures: 1 },
569
569
  },
570
570
  ]));
571
- export const BUILTIN_WORKFLOW_PACKS = BASE_DOMAIN_SKILLS.flatMap((seed) => ([
571
+ const SPECIALIZED_WORKFLOW_PACKS = [
572
572
  {
573
- key: `${seed.domain}-execution-loop`,
574
- name: `${seed.domain}-execution-loop`,
575
- domain: seed.domain,
576
- description: `Bundled ${seed.domain} workflow with planning, execution, and verification checkpoints.`,
577
- triggerConditions: [`goal mentions ${seed.domain}`, `${seed.domain} pack selected`],
578
- expectedOutputs: seed.outputs,
579
- guardrails: seed.guardrails,
580
- verifierHooks: ['Collect verifier evidence before workflow promotion.', ...seed.verifierHooks],
581
- roleAffinity: ['planner', 'coder', 'verifier', 'skill-maker'],
573
+ key: 'release-pipeline',
574
+ name: 'release-pipeline',
575
+ domain: 'pdlc',
576
+ description: 'Release workflow with packaging, audit, smoke, and publish-readiness checkpoints.',
577
+ triggerConditions: ['release requested', 'publish or deploy task detected', 'version or tag work selected'],
578
+ expectedOutputs: ['release checklist', 'verification evidence', 'publish decision'],
579
+ guardrails: [
580
+ 'Do not treat a draft release as published.',
581
+ 'Do not publish without explicit release-gate evidence.',
582
+ ],
583
+ verifierHooks: [
584
+ 'Attach package, audit, and smoke evidence before promotion.',
585
+ 'Record publish-readiness outcome in the run ledger.',
586
+ ],
587
+ roleAffinity: ['planner', 'verifier', 'devops'],
582
588
  steps: [
583
589
  {
584
- title: `Plan ${seed.domain} outcome and artifacts`,
590
+ title: 'Frame release criteria and affected package surface',
585
591
  checkpoint: 'before-read',
586
592
  role: 'planner',
587
593
  },
588
594
  {
589
- title: `Execute ${seed.domain} changes with bounded scope`,
590
- checkpoint: 'before-mutate',
591
- role: 'coder',
595
+ title: 'Collect package, audit, and smoke evidence',
596
+ checkpoint: 'before-verify',
597
+ role: 'verifier',
592
598
  },
593
599
  {
594
- title: `Review ${seed.domain} outputs and attach evidence`,
600
+ title: 'Record publish readiness and remaining blockers',
595
601
  checkpoint: 'before-verify',
596
602
  role: 'verifier',
597
603
  },
@@ -599,35 +605,104 @@ export const BUILTIN_WORKFLOW_PACKS = BASE_DOMAIN_SKILLS.flatMap((seed) => ([
599
605
  promotionThresholds: { minSuccesses: 2, maxFailures: 1 },
600
606
  },
601
607
  {
602
- key: `${seed.domain}-approval-loop`,
603
- name: `${seed.domain}-approval-loop`,
604
- domain: seed.domain,
605
- description: `Bundled ${seed.domain} approval loop with review, verification, and promotion control.`,
606
- triggerConditions: [`${seed.domain} approval requested`, `${seed.domain} promotion under review`],
607
- expectedOutputs: ['approval note', 'review evidence', 'promotion decision'],
608
- guardrails: ['Do not approve unverified work.', ...seed.guardrails],
609
- verifierHooks: ['Require approval evidence before promotion.', ...seed.verifierHooks],
610
- roleAffinity: ['planner', 'verifier', 'research-shadow'],
608
+ key: 'research-and-implement',
609
+ name: 'research-and-implement',
610
+ domain: 'deep-tech',
611
+ description: 'Research-backed implementation workflow tying retrieved context to bounded code changes and verification.',
612
+ triggerConditions: ['rag context attached', 'research-backed implementation requested', 'unknown code path requires grounding'],
613
+ expectedOutputs: ['source-grounded plan', 'bounded implementation notes', 'verification evidence'],
614
+ guardrails: [
615
+ 'Do not promote research claims without source-grounded evidence.',
616
+ 'Keep implementation scope tied to retrieved context.',
617
+ ],
618
+ verifierHooks: [
619
+ 'Attach source-grounding notes before verification.',
620
+ 'Record how research context changed the implementation decision.',
621
+ ],
622
+ roleAffinity: ['planner', 'coder', 'verifier', 'research-shadow'],
611
623
  steps: [
612
624
  {
613
- title: `Frame ${seed.domain} approval criteria`,
625
+ title: 'Extract source-grounded implementation constraints',
614
626
  checkpoint: 'before-read',
615
627
  role: 'planner',
616
628
  },
617
629
  {
618
- title: `Review ${seed.domain} evidence and risks`,
619
- checkpoint: 'before-verify',
620
- role: 'verifier',
630
+ title: 'Apply the smallest implementation compatible with the research context',
631
+ checkpoint: 'before-mutate',
632
+ role: 'coder',
621
633
  },
622
634
  {
623
- title: `Publish ${seed.domain} approval outcome`,
624
- checkpoint: 'retry',
625
- role: 'research-shadow',
635
+ title: 'Verify behavior and cite grounding evidence',
636
+ checkpoint: 'before-verify',
637
+ role: 'verifier',
626
638
  },
627
639
  ],
628
640
  promotionThresholds: { minSuccesses: 2, maxFailures: 1 },
629
641
  },
630
- ]));
642
+ ];
643
+ export const BUILTIN_WORKFLOW_PACKS = [
644
+ ...BASE_DOMAIN_SKILLS.flatMap((seed) => ([
645
+ {
646
+ key: `${seed.domain}-execution-loop`,
647
+ name: `${seed.domain}-execution-loop`,
648
+ domain: seed.domain,
649
+ description: `Bundled ${seed.domain} workflow with planning, execution, and verification checkpoints.`,
650
+ triggerConditions: [`goal mentions ${seed.domain}`, `${seed.domain} pack selected`],
651
+ expectedOutputs: seed.outputs,
652
+ guardrails: seed.guardrails,
653
+ verifierHooks: ['Collect verifier evidence before workflow promotion.', ...seed.verifierHooks],
654
+ roleAffinity: ['planner', 'coder', 'verifier', 'skill-maker'],
655
+ steps: [
656
+ {
657
+ title: `Plan ${seed.domain} outcome and artifacts`,
658
+ checkpoint: 'before-read',
659
+ role: 'planner',
660
+ },
661
+ {
662
+ title: `Execute ${seed.domain} changes with bounded scope`,
663
+ checkpoint: 'before-mutate',
664
+ role: 'coder',
665
+ },
666
+ {
667
+ title: `Review ${seed.domain} outputs and attach evidence`,
668
+ checkpoint: 'before-verify',
669
+ role: 'verifier',
670
+ },
671
+ ],
672
+ promotionThresholds: { minSuccesses: 2, maxFailures: 1 },
673
+ },
674
+ {
675
+ key: `${seed.domain}-approval-loop`,
676
+ name: `${seed.domain}-approval-loop`,
677
+ domain: seed.domain,
678
+ description: `Bundled ${seed.domain} approval loop with review, verification, and promotion control.`,
679
+ triggerConditions: [`${seed.domain} approval requested`, `${seed.domain} promotion under review`],
680
+ expectedOutputs: ['approval note', 'review evidence', 'promotion decision'],
681
+ guardrails: ['Do not approve unverified work.', ...seed.guardrails],
682
+ verifierHooks: ['Require approval evidence before promotion.', ...seed.verifierHooks],
683
+ roleAffinity: ['planner', 'verifier', 'research-shadow'],
684
+ steps: [
685
+ {
686
+ title: `Frame ${seed.domain} approval criteria`,
687
+ checkpoint: 'before-read',
688
+ role: 'planner',
689
+ },
690
+ {
691
+ title: `Review ${seed.domain} evidence and risks`,
692
+ checkpoint: 'before-verify',
693
+ role: 'verifier',
694
+ },
695
+ {
696
+ title: `Publish ${seed.domain} approval outcome`,
697
+ checkpoint: 'retry',
698
+ role: 'research-shadow',
699
+ },
700
+ ],
701
+ promotionThresholds: { minSuccesses: 2, maxFailures: 1 },
702
+ },
703
+ ])),
704
+ ...SPECIALIZED_WORKFLOW_PACKS,
705
+ ];
631
706
  export function slugify(value) {
632
707
  return value
633
708
  .toLowerCase()
@@ -40,7 +40,7 @@ export interface SessionSummary {
40
40
  sessionId: string;
41
41
  timestamp: number;
42
42
  files: string[];
43
- fileSHAs: Record<string, string>;
43
+ fileSHAs?: Record<string, string>;
44
44
  summary: string;
45
45
  keyDecisions: string[];
46
46
  }
@@ -204,6 +204,10 @@ export class TokenSupremacyEngine {
204
204
  // Saves ~60-70% tokens on follow-up sessions
205
205
  // ───────────────────────────────────────────────────────────────────────────
206
206
  differential(prev, curr) {
207
+ const previousFileSHAs = prev.fileSHAs && typeof prev.fileSHAs === 'object'
208
+ ? prev.fileSHAs
209
+ : {};
210
+ const hasComparableFingerprints = Object.keys(previousFileSHAs).length > 0;
207
211
  const delta = {
208
212
  added: [],
209
213
  changed: [],
@@ -214,10 +218,15 @@ export class TokenSupremacyEngine {
214
218
  : undefined,
215
219
  };
216
220
  for (const file of curr) {
217
- const prevSHA = prev.fileSHAs[file.path];
221
+ const prevSHA = previousFileSHAs[file.path];
218
222
  const currSHA = this.fingerprintFile(file);
219
223
  if (!prevSHA) {
220
- delta.added.push(file);
224
+ if (hasComparableFingerprints) {
225
+ delta.added.push(file);
226
+ }
227
+ else {
228
+ delta.changed.push(file);
229
+ }
221
230
  }
222
231
  else if (prevSHA !== currSHA) {
223
232
  delta.changed.push(file);
@@ -33,14 +33,14 @@ export class ArtifactRecorder {
33
33
  writeJson(relativePath, value) {
34
34
  const target = path.join(this.runDir, relativePath);
35
35
  fs.mkdirSync(path.dirname(target), { recursive: true });
36
- void fs.promises.writeFile(target, JSON.stringify(value, null, 2), 'utf-8');
36
+ fs.writeFileSync(target, JSON.stringify(value, null, 2), 'utf-8');
37
37
  this.index[relativePath] = target;
38
38
  return target;
39
39
  }
40
40
  writeText(relativePath, value) {
41
41
  const target = path.join(this.runDir, relativePath);
42
42
  fs.mkdirSync(path.dirname(target), { recursive: true });
43
- void fs.promises.writeFile(target, value, 'utf-8');
43
+ fs.writeFileSync(target, value, 'utf-8');
44
44
  this.index[relativePath] = target;
45
45
  return target;
46
46
  }
@@ -187,6 +187,8 @@ export interface WorkerVerification {
187
187
  workerId: string;
188
188
  verifierId: string;
189
189
  passed: boolean;
190
+ status: 'passed' | 'failed' | 'skipped';
191
+ skippedReason?: string;
190
192
  commands: CommandRecord[];
191
193
  summary: string;
192
194
  artifactsPath: string;
@@ -619,6 +619,7 @@ export class SubAgentRuntime {
619
619
  workerId: manifest.targetWorkerId ?? manifest.workerId,
620
620
  verifierId: manifest.workerId,
621
621
  passed: false,
622
+ status: 'failed',
622
623
  commands: [],
623
624
  summary: target?.budgetExceeded
624
625
  ? 'Skipped verification for over-budget worker.'
@@ -637,12 +638,14 @@ export class SubAgentRuntime {
637
638
  }
638
639
  const verification = verificationResults.find((entry) => entry.workerId === result.workerId);
639
640
  if (verification) {
641
+ const verificationPassed = verification.status === 'passed' || (verification.passed && verification.status !== 'skipped');
640
642
  result.verification = verification;
641
- result.verified = verification.passed;
643
+ result.verified = verificationPassed;
642
644
  result.testsPassing = verification.commands.filter((command) => command.exitCode === 0).length;
643
- result.outcome = verification.passed ? 'success' : (result.diff.trim() ? 'partial' : 'failed');
645
+ result.outcome = verificationPassed ? 'success' : (result.diff.trim() ? 'partial' : 'failed');
644
646
  }
645
647
  }
648
+ recorder.writeJson('worker-results.json', run.workerResults);
646
649
  const decision = await consensusPolicy.merge(mergeableCoderResults);
647
650
  run.finalDecision = decision;
648
651
  run.backendEvidence.consensus = consensusPolicy.shadowStats();
@@ -2132,13 +2135,15 @@ export class SubAgentRuntime {
2132
2135
  const artifactsPath = recorder.workerDir(manifest.workerId);
2133
2136
  // Skip worktree creation if there is nothing to verify. An empty diff
2134
2137
  // is not a verification failure — it means the worker produced no
2135
- // mutation (typical for inspect/plan intents). Mark passed=true so
2136
- // the run state is not flagged as failed for read-only orchestrations.
2138
+ // mutation (typical for inspect/plan intents). This is a skipped
2139
+ // verification, not evidence that implementation passed.
2137
2140
  if (!target?.diff?.trim()) {
2138
2141
  return {
2139
2142
  workerId: manifest.targetWorkerId ?? 'unknown',
2140
2143
  verifierId: manifest.workerId,
2141
- passed: true,
2144
+ passed: false,
2145
+ status: 'skipped',
2146
+ skippedReason: 'no_candidate_diff',
2142
2147
  commands: [],
2143
2148
  summary: 'Verifier skipped — no candidate diff to verify (read-only or no-op run).',
2144
2149
  artifactsPath,
@@ -2175,6 +2180,7 @@ export class SubAgentRuntime {
2175
2180
  workerId: manifest.targetWorkerId ?? 'unknown',
2176
2181
  verifierId: manifest.workerId,
2177
2182
  passed,
2183
+ status: passed ? 'passed' : 'failed',
2178
2184
  commands: records,
2179
2185
  summary: passed
2180
2186
  ? `Verifier passed ${records.length} command(s).`
@@ -2190,6 +2196,7 @@ export class SubAgentRuntime {
2190
2196
  workerId: manifest.targetWorkerId ?? 'unknown',
2191
2197
  verifierId: manifest.workerId,
2192
2198
  passed: false,
2199
+ status: 'failed',
2193
2200
  commands: [],
2194
2201
  summary,
2195
2202
  artifactsPath,
@@ -2945,8 +2952,8 @@ export class SubAgentRuntime {
2945
2952
  };
2946
2953
  }
2947
2954
  evaluateReviewGates(run) {
2948
- const verificationPassed = run.verificationResults.filter((result) => result.passed).length;
2949
- const verificationFailed = run.verificationResults.filter((result) => !result.passed).length;
2955
+ const verificationPassed = run.verificationResults.filter((result) => result.status === 'passed' || (result.passed && result.status !== 'skipped')).length;
2956
+ const verificationFailed = run.verificationResults.filter((result) => result.status === 'failed' || (!result.passed && result.status !== 'skipped')).length;
2950
2957
  const hasPlannerEvidence = Boolean(run.plannerState?.ledger?.length || run.plannerResult?.summary || run.executionLedger?.steps?.some((step) => step.id === 'planner-selection' && step.status === 'completed'));
2951
2958
  const hasImplementation = run.workerResults.some((result) => result.role === 'coder');
2952
2959
  const hasVerifiedImplementation = run.workerResults.some((result) => result.role === 'coder' && result.verified);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexus-prime",
3
- "version": "7.9.6",
3
+ "version": "7.9.7",
4
4
  "description": "Local-first MCP control plane for coding agents with bootstrap-orchestrate execution, memory fabric, token budgeting, and worktree-backed swarms",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",