nexus-prime 7.9.7 → 7.9.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -293,6 +293,9 @@ export function decideWorkers(requestedWorkers, plannedWorkers, phaseCount, inte
293
293
  if (typeof requestedWorkers === 'number' && requestedWorkers > 0) {
294
294
  return Math.max(1, Math.min(7, requestedWorkers));
295
295
  }
296
+ if (intent.taskType === 'release') {
297
+ return 1;
298
+ }
296
299
  if (intent.riskClass !== 'high'
297
300
  && intent.complexity <= 3
298
301
  && phaseCount <= 2
@@ -2316,6 +2316,23 @@ export class OrchestratorEngine {
2316
2316
  limit: 4,
2317
2317
  selector: 'name',
2318
2318
  });
2319
+ const selectedWorkflowValues = intent.taskType === 'release'
2320
+ ? dedupeStrings(['release-pipeline', ...workflowSelection.selectedValues])
2321
+ : workflowSelection.selectedValues;
2322
+ const workflowSelectedEntries = [
2323
+ ...workflowSelection.selectedEntries,
2324
+ ...selectedWorkflowValues
2325
+ .filter((value) => !workflowSelection.selectedEntries.some((entry) => entry.name === value || entry.id === value))
2326
+ .map((value) => this.toArtifactAuditEntry('workflow', value, workflowItems, {
2327
+ score: 1,
2328
+ source: 'planner',
2329
+ confidence: 'high',
2330
+ reason: 'Release intent requires the release-pipeline workflow so package, audit, and smoke evidence is collected.',
2331
+ selector: 'name',
2332
+ selectionPolicy: 'global-first',
2333
+ authorityTier: this.getAuthorityTier('workflow'),
2334
+ })),
2335
+ ];
2319
2336
  const hookSelection = this.resolveCatalogVotes('hook', task, intent, hookItems, {
2320
2337
  explicit: options.hookSelectors,
2321
2338
  planner: [],
@@ -2356,7 +2373,7 @@ export class OrchestratorEngine {
2356
2373
  selectionSummary: {
2357
2374
  specialists: specialistSelection.selectedValues.length,
2358
2375
  skills: skillSelection.selectedValues.length,
2359
- workflows: workflowSelection.selectedValues.length,
2376
+ workflows: selectedWorkflowValues.length,
2360
2377
  hooks: hookSelection.selectedValues.length,
2361
2378
  automations: automationSelection.selectedValues.length,
2362
2379
  crews: crewSelection.selectedValues.length,
@@ -2367,7 +2384,7 @@ export class OrchestratorEngine {
2367
2384
  ...crewSelection.selectedEntries,
2368
2385
  ...specialistSelection.selectedEntries,
2369
2386
  ...skillSelection.selectedEntries,
2370
- ...workflowSelection.selectedEntries,
2387
+ ...workflowSelectedEntries,
2371
2388
  ...hookSelection.selectedEntries,
2372
2389
  ...automationSelection.selectedEntries,
2373
2390
  ];
@@ -2388,7 +2405,7 @@ export class OrchestratorEngine {
2388
2405
  selectionSummary: {
2389
2406
  specialists: specialistSelection.selectedValues.length,
2390
2407
  skills: skillSelection.selectedValues.length,
2391
- workflows: workflowSelection.selectedValues.length,
2408
+ workflows: selectedWorkflowValues.length,
2392
2409
  hooks: hookSelection.selectedValues.length,
2393
2410
  automations: automationSelection.selectedValues.length,
2394
2411
  crews: crewSelection.selectedValues.length,
@@ -2400,7 +2417,7 @@ export class OrchestratorEngine {
2400
2417
  crew: crewSelection.selectedValues[0],
2401
2418
  specialists: specialistSelection.selectedValues,
2402
2419
  skills: skillSelection.selectedValues,
2403
- workflows: workflowSelection.selectedValues,
2420
+ workflows: selectedWorkflowValues,
2404
2421
  hooks: hookSelection.selectedValues,
2405
2422
  automations: automationSelection.selectedValues,
2406
2423
  audit: {
@@ -595,6 +595,9 @@ const SPECIALIZED_WORKFLOW_PACKS = [
595
595
  title: 'Collect package, audit, and smoke evidence',
596
596
  checkpoint: 'before-verify',
597
597
  role: 'verifier',
598
+ bindings: [
599
+ { type: 'run_command', command: 'npm run qa:release' },
600
+ ],
598
601
  },
599
602
  {
600
603
  title: 'Record publish readiness and remaining blockers',
@@ -8,6 +8,10 @@ import { type WorktreeHealthSnapshot } from '../../engines/worktree-health.js';
8
8
  import type { MergeDecision } from '../index.js';
9
9
  import type { RuntimeWorkerResult, WorkerRole } from '../runtime.js';
10
10
  import { type CommandRecord } from './worker.js';
11
+ export interface BindingApplicationResult {
12
+ modifiedFiles: string[];
13
+ commandRecords: CommandRecord[];
14
+ }
11
15
  /**
12
16
  * Lightweight per-run artifact writer. Maintains a flat index of paths
13
17
  * written so callers can recover the full run artifact layout.
@@ -36,11 +40,12 @@ export declare class WorktreeSession {
36
40
  constructor(repoRoot: string, workerId: string, role: WorkerRole, recorder: ArtifactRecorder, reportHealth?: (snapshot: WorktreeHealthSnapshot) => void);
37
41
  create(): Promise<void>;
38
42
  run(command: string, allowFailure?: boolean, timeoutMs?: number): Promise<CommandRecord>;
39
- applyBindings(bindings: SkillBinding[], timeoutMs?: number): Promise<string[]>;
43
+ applyBindings(bindings: SkillBinding[], timeoutMs?: number): Promise<BindingApplicationResult>;
40
44
  captureDiff(): Promise<string>;
41
45
  applyPatchContent(diff: string): Promise<void>;
42
46
  cleanup(): Promise<void>;
43
47
  private resolve;
48
+ private linkWorkspaceDependencies;
44
49
  }
45
50
  /** Thin adapter that wraps a MemoryBackend for use inside MergeOracle. */
46
51
  export declare class MergeMemoryAdapter {
@@ -75,6 +75,7 @@ export class WorktreeSession {
75
75
  cwd: this.repoRoot,
76
76
  maxBuffer: 1024 * 1024 * 20,
77
77
  });
78
+ await this.linkWorkspaceDependencies();
78
79
  }
79
80
  catch (error) {
80
81
  const retryHealth = await doctorGitWorktrees(this.repoRoot);
@@ -87,6 +88,7 @@ export class WorktreeSession {
87
88
  }
88
89
  async applyBindings(bindings, timeoutMs) {
89
90
  const modified = new Set();
91
+ const commandRecords = [];
90
92
  for (const binding of bindings) {
91
93
  switch (binding.type) {
92
94
  case 'write_file': {
@@ -114,12 +116,13 @@ export class WorktreeSession {
114
116
  }
115
117
  case 'run_command': {
116
118
  const record = await this.run(binding.command || '', false, timeoutMs);
119
+ commandRecords.push(record);
117
120
  this.recorder.writeJson(path.join('workers', this.workerId, `${sanitizeFileName(record.command)}.json`), record);
118
121
  break;
119
122
  }
120
123
  }
121
124
  }
122
- return [...modified];
125
+ return { modifiedFiles: [...modified], commandRecords };
123
126
  }
124
127
  async captureDiff() {
125
128
  try {
@@ -130,6 +133,12 @@ export class WorktreeSession {
130
133
  catch {
131
134
  // Runtime skill overlays stay outside repo patches.
132
135
  }
136
+ try {
137
+ await exec('git reset HEAD -- node_modules', { cwd: this.worktreeDir, maxBuffer: 1024 * 1024 * 20 });
138
+ }
139
+ catch {
140
+ // Shared dependency symlink stays outside repo patches.
141
+ }
133
142
  const { stdout } = await exec('git diff --binary --cached HEAD', {
134
143
  cwd: this.worktreeDir,
135
144
  maxBuffer: 1024 * 1024 * 20,
@@ -167,6 +176,23 @@ export class WorktreeSession {
167
176
  return target;
168
177
  return path.join(this.worktreeDir, target);
169
178
  }
179
+ async linkWorkspaceDependencies() {
180
+ const source = path.join(this.repoRoot, 'node_modules');
181
+ const target = path.join(this.worktreeDir, 'node_modules');
182
+ try {
183
+ const stat = await fs.promises.lstat(source);
184
+ if (!stat.isDirectory() && !stat.isSymbolicLink())
185
+ return;
186
+ await fs.promises.lstat(target).then(() => undefined).catch(async (error) => {
187
+ if (error?.code !== 'ENOENT')
188
+ throw error;
189
+ await fs.promises.symlink(source, target, 'dir');
190
+ });
191
+ }
192
+ catch {
193
+ // Worktrees remain valid for repos that do not use node_modules or have not installed deps.
194
+ }
195
+ }
170
196
  }
171
197
  // ─── MergeMemoryAdapter ───────────────────────────────────────────────────────
172
198
  /** Thin adapter that wraps a MemoryBackend for use inside MergeOracle. */
@@ -199,6 +199,7 @@ export interface RuntimeWorkerResult extends WorkerResult {
199
199
  verification?: WorkerVerification;
200
200
  artifactsPath: string;
201
201
  modifiedFiles: string[];
202
+ commandRecords?: CommandRecord[];
202
203
  }
203
204
  export interface PlannerResult {
204
205
  summary: string;
@@ -533,6 +534,7 @@ export declare class SubAgentRuntime {
533
534
  private applyDecision;
534
535
  private evaluatePromotions;
535
536
  private resolveCandidateDiff;
537
+ private isCommandOnlyOperationalSuccess;
536
538
  private resolveFileRefs;
537
539
  private discoverTargetFiles;
538
540
  private collectLibraryCounts;
@@ -688,14 +688,15 @@ export class SubAgentRuntime {
688
688
  : enforcedBlockingGate
689
689
  ? { applied: false, rolledBack: false, summary: `Review gate ${enforcedBlockingGate.gate} remains ${enforcedBlockingGate.status}.` }
690
690
  : await this.applyDecision(recorder, { ...task, verifyCommands: beforeVerifyResolved.verifyCommands }, decision, consensusPolicy);
691
- // Read-only intents (inspect/plan) don't produce a diff to apply, so applied=false
692
- // is the expected outcome not a failure. Only mark 'failed' when a shield blocked,
693
- // a review gate enforced, or a mutate run produced no applicable diff.
691
+ const commandOnlyOperationalSuccess = this.isCommandOnlyOperationalSuccess(task, decision);
692
+ // Read-only and command-only operational runs don't produce a diff to apply.
693
+ // Only mark 'failed' when a shield blocked, a review gate enforced, or a
694
+ // mutate run produced neither a patch nor successful command evidence.
694
695
  const readOnlyIntent = task.intent === 'inspect' || task.intent === 'plan';
695
696
  const failureExpected = preApplyShield.blocked || Boolean(enforcedBlockingGate);
696
697
  run.state = applied.applied
697
698
  ? (applied.rolledBack ? 'rolled_back' : 'merged')
698
- : (readOnlyIntent && !failureExpected ? 'inspected' : 'failed');
699
+ : ((readOnlyIntent || commandOnlyOperationalSuccess) && !failureExpected ? 'inspected' : 'failed');
699
700
  // Surface completion on the SSE feed so the dashboard can move the
700
701
  // run from "Running" to "Done" without a poll round-trip.
701
702
  try {
@@ -2048,13 +2049,20 @@ export class SubAgentRuntime {
2048
2049
  verified: false,
2049
2050
  artifactsPath: workerDir,
2050
2051
  modifiedFiles: [],
2052
+ commandRecords: [],
2051
2053
  };
2052
2054
  }
2053
- const modifiedFiles = await session.applyBindings(allowedActions, timeoutMs);
2055
+ const bindingResult = await session.applyBindings(allowedActions, timeoutMs);
2056
+ const modifiedFiles = bindingResult.modifiedFiles;
2057
+ const commandRecords = bindingResult.commandRecords;
2054
2058
  modifiedFiles.forEach(file => {
2055
2059
  this.sessionDNA?.recordFileModified(file);
2056
2060
  learnings.push(`Modified ${file}`);
2057
2061
  });
2062
+ if (commandRecords.length > 0) {
2063
+ const passedCommands = commandRecords.filter((record) => record.exitCode === 0).length;
2064
+ learnings.push(`Ran ${commandRecords.length} command binding(s), ${passedCommands} passed.`);
2065
+ }
2058
2066
  const diff = await session.captureDiff();
2059
2067
  recorder.writeText(path.join('workers', manifest.workerId, 'diff.patch'), diff);
2060
2068
  podNetwork.publish(manifest.workerId, `Produced ${modifiedFiles.length} modified files`, 0.8, ['#runtime-worker']);
@@ -2063,23 +2071,30 @@ export class SubAgentRuntime {
2063
2071
  if (budgetExceeded) {
2064
2072
  learnings.push(`Token budget exceeded: used ${estimatedTokens}, budget ${manifest.tokenBudget}`);
2065
2073
  }
2066
- nexusEventBus.emit('phantom.worker.complete', { workerId: manifest.workerId, confidence: budgetExceeded ? 0.1 : (diff.trim() ? 0.7 : 0.2) });
2074
+ const commandOnlySuccess = !diff.trim()
2075
+ && commandRecords.length > 0
2076
+ && commandRecords.every((record) => record.exitCode === 0);
2077
+ nexusEventBus.emit('phantom.worker.complete', {
2078
+ workerId: manifest.workerId,
2079
+ confidence: budgetExceeded ? 0.1 : (diff.trim() ? 0.7 : (commandOnlySuccess ? 0.75 : 0.2)),
2080
+ });
2067
2081
  return {
2068
2082
  workerId: manifest.workerId,
2069
2083
  role: manifest.role,
2070
2084
  taskId: runId,
2071
2085
  approach: manifest.strategy,
2072
2086
  diff,
2073
- outcome: budgetExceeded ? 'budgetExceeded' : (diff.trim() ? 'partial' : 'failed'),
2074
- confidence: budgetExceeded ? 0.1 : (diff.trim() ? 0.68 : 0.18),
2087
+ outcome: budgetExceeded ? 'budgetExceeded' : (diff.trim() ? 'partial' : (commandOnlySuccess ? 'success' : 'failed')),
2088
+ confidence: budgetExceeded ? 0.1 : (diff.trim() ? 0.68 : (commandOnlySuccess ? 0.82 : 0.18)),
2075
2089
  tokensUsed: estimatedTokens,
2076
2090
  tokenEstimateSource: 'runtime-estimate',
2077
2091
  budgetExceeded,
2078
2092
  learnings,
2079
- testsPassing: 0,
2093
+ testsPassing: commandRecords.filter((record) => record.exitCode === 0).length,
2080
2094
  verified: false,
2081
2095
  artifactsPath: workerDir,
2082
2096
  modifiedFiles,
2097
+ commandRecords,
2083
2098
  };
2084
2099
  }
2085
2100
  catch (error) {
@@ -2102,6 +2117,7 @@ export class SubAgentRuntime {
2102
2117
  verified: false,
2103
2118
  artifactsPath: workerDir,
2104
2119
  modifiedFiles: [],
2120
+ commandRecords: [],
2105
2121
  };
2106
2122
  }
2107
2123
  finally {
@@ -2133,11 +2149,25 @@ export class SubAgentRuntime {
2133
2149
  const verifier = new WorktreeSession(this.repoRoot, `${runId}-${manifest.workerId}`, 'verifier', recorder, (snapshot) => this.recordWorktreeHealth(snapshot));
2134
2150
  const records = [];
2135
2151
  const artifactsPath = recorder.workerDir(manifest.workerId);
2136
- // Skip worktree creation if there is nothing to verify. An empty diff
2137
- // is not a verification failure — it means the worker produced no
2138
- // mutation (typical for inspect/plan intents). This is a skipped
2139
- // verification, not evidence that implementation passed.
2152
+ const commandRecords = target?.commandRecords ?? [];
2153
+ const commandOnlySuccess = !target?.diff?.trim()
2154
+ && commandRecords.length > 0
2155
+ && commandRecords.every((record) => record.exitCode === 0);
2156
+ // Skip worktree creation if there is no diff. Command-only operational
2157
+ // workers are verified from their command records; pure no-op workers
2158
+ // remain skipped rather than counted as implementation evidence.
2140
2159
  if (!target?.diff?.trim()) {
2160
+ if (commandOnlySuccess) {
2161
+ return {
2162
+ workerId: manifest.targetWorkerId ?? 'unknown',
2163
+ verifierId: manifest.workerId,
2164
+ passed: true,
2165
+ status: 'passed',
2166
+ commands: commandRecords,
2167
+ summary: `Verifier accepted command-only evidence (${commandRecords.length} command binding(s) passed).`,
2168
+ artifactsPath,
2169
+ };
2170
+ }
2141
2171
  return {
2142
2172
  workerId: manifest.targetWorkerId ?? 'unknown',
2143
2173
  verifierId: manifest.workerId,
@@ -2209,6 +2239,14 @@ export class SubAgentRuntime {
2209
2239
  async applyDecision(recorder, task, decision, consensusPolicy) {
2210
2240
  const candidateDiff = this.resolveCandidateDiff(decision);
2211
2241
  if (!candidateDiff.trim()) {
2242
+ if (this.isCommandOnlyOperationalSuccess(task, decision)) {
2243
+ const records = decision.winner?.commandRecords ?? [];
2244
+ return {
2245
+ applied: false,
2246
+ rolledBack: false,
2247
+ summary: `Operational workflow completed with command evidence only (${records.length} command binding(s) passed); no repository patch was required.`,
2248
+ };
2249
+ }
2212
2250
  return {
2213
2251
  applied: false,
2214
2252
  rolledBack: false,
@@ -2409,6 +2447,20 @@ export class SubAgentRuntime {
2409
2447
  }
2410
2448
  return decision.winner?.diff ?? '';
2411
2449
  }
2450
+ isCommandOnlyOperationalSuccess(task, decision) {
2451
+ if (decision.action === 'reject')
2452
+ return false;
2453
+ if (this.resolveCandidateDiff(decision).trim())
2454
+ return false;
2455
+ const winner = decision.winner;
2456
+ const records = winner?.commandRecords ?? [];
2457
+ if (records.length === 0 || !records.every((record) => record.exitCode === 0)) {
2458
+ return false;
2459
+ }
2460
+ return task.releasePolicy.mode === 'ship-ready'
2461
+ || task.workflowSelectors.some((selector) => /release|deploy|publish|ci|smoke|audit/i.test(selector))
2462
+ || /release|publish|deploy|npm|github action|ci\/cd|smoke|audit/i.test(task.goal);
2463
+ }
2412
2464
  async resolveFileRefs(files) {
2413
2465
  return Promise.all(files.map(async (file) => {
2414
2466
  const resolved = path.isAbsolute(file) ? file : path.join(this.repoRoot, file);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexus-prime",
3
- "version": "7.9.7",
3
+ "version": "7.9.8",
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",