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.
- package/dist/engines/orchestrator/scoring.js +3 -0
- package/dist/engines/orchestrator.js +21 -4
- package/dist/engines/runtime-assets.js +3 -0
- package/dist/phantom/runtime/worktree.d.ts +6 -1
- package/dist/phantom/runtime/worktree.js +27 -1
- package/dist/phantom/runtime.d.ts +2 -0
- package/dist/phantom/runtime.js +65 -13
- package/package.json +1 -1
|
@@ -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:
|
|
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
|
-
...
|
|
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:
|
|
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:
|
|
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<
|
|
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;
|
package/dist/phantom/runtime.js
CHANGED
|
@@ -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
|
-
|
|
692
|
-
//
|
|
693
|
-
// a review gate enforced, or a
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
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.
|
|
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",
|