@evref-bl/dev-nexus 0.1.0-alpha.0
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/README.md +677 -0
- package/dist/browserOpener.d.ts +9 -0
- package/dist/browserOpener.js +47 -0
- package/dist/cli.d.ts +18 -0
- package/dist/cli.js +2374 -0
- package/dist/gitWorktreeService.d.ts +57 -0
- package/dist/gitWorktreeService.js +157 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.js +47 -0
- package/dist/nexusAgentMcpConfig.d.ts +30 -0
- package/dist/nexusAgentMcpConfig.js +228 -0
- package/dist/nexusAutomation.d.ts +103 -0
- package/dist/nexusAutomation.js +390 -0
- package/dist/nexusAutomationAgentLaunch.d.ts +148 -0
- package/dist/nexusAutomationAgentLaunch.js +855 -0
- package/dist/nexusAutomationAgentProfile.d.ts +39 -0
- package/dist/nexusAutomationAgentProfile.js +103 -0
- package/dist/nexusAutomationAgentSurface.d.ts +62 -0
- package/dist/nexusAutomationAgentSurface.js +90 -0
- package/dist/nexusAutomationCommandExecutor.d.ts +29 -0
- package/dist/nexusAutomationCommandExecutor.js +251 -0
- package/dist/nexusAutomationConfig.d.ts +114 -0
- package/dist/nexusAutomationConfig.js +547 -0
- package/dist/nexusAutomationEnqueue.d.ts +37 -0
- package/dist/nexusAutomationEnqueue.js +128 -0
- package/dist/nexusAutomationRunOnce.d.ts +91 -0
- package/dist/nexusAutomationRunOnce.js +586 -0
- package/dist/nexusAutomationScheduler.d.ts +50 -0
- package/dist/nexusAutomationScheduler.js +196 -0
- package/dist/nexusAutomationStatus.d.ts +55 -0
- package/dist/nexusAutomationStatus.js +462 -0
- package/dist/nexusAutomationTarget.d.ts +19 -0
- package/dist/nexusAutomationTarget.js +33 -0
- package/dist/nexusAutomationTargetCycle.d.ts +90 -0
- package/dist/nexusAutomationTargetCycle.js +282 -0
- package/dist/nexusAutomationTargetReport.d.ts +136 -0
- package/dist/nexusAutomationTargetReport.js +504 -0
- package/dist/nexusAutomationWorktreeSetup.d.ts +89 -0
- package/dist/nexusAutomationWorktreeSetup.js +661 -0
- package/dist/nexusCoordination.d.ts +198 -0
- package/dist/nexusCoordination.js +1018 -0
- package/dist/nexusExtension.d.ts +31 -0
- package/dist/nexusExtension.js +1 -0
- package/dist/nexusHomeConfig.d.ts +38 -0
- package/dist/nexusHomeConfig.js +133 -0
- package/dist/nexusMcpServer.d.ts +31 -0
- package/dist/nexusMcpServer.js +1036 -0
- package/dist/nexusPluginCapabilities.d.ts +197 -0
- package/dist/nexusPluginCapabilities.js +201 -0
- package/dist/nexusProjectConfig.d.ts +95 -0
- package/dist/nexusProjectConfig.js +880 -0
- package/dist/nexusProjectHomeService.d.ts +121 -0
- package/dist/nexusProjectHomeService.js +171 -0
- package/dist/nexusProjectLifecycle.d.ts +62 -0
- package/dist/nexusProjectLifecycle.js +205 -0
- package/dist/nexusProjectOperations.d.ts +101 -0
- package/dist/nexusProjectOperations.js +296 -0
- package/dist/nexusProjectRegistry.d.ts +42 -0
- package/dist/nexusProjectRegistry.js +91 -0
- package/dist/nexusProjectScaffold.d.ts +25 -0
- package/dist/nexusProjectScaffold.js +61 -0
- package/dist/nexusProjectTemplate.d.ts +34 -0
- package/dist/nexusProjectTemplate.js +354 -0
- package/dist/nexusSkills.d.ts +134 -0
- package/dist/nexusSkills.js +647 -0
- package/dist/nexusWorkerContextBundle.d.ts +142 -0
- package/dist/nexusWorkerContextBundle.js +375 -0
- package/dist/processSupervisor.d.ts +89 -0
- package/dist/processSupervisor.js +440 -0
- package/dist/vibeKanbanApi.d.ts +11 -0
- package/dist/vibeKanbanApi.js +14 -0
- package/dist/vibeKanbanAuth.d.ts +25 -0
- package/dist/vibeKanbanAuth.js +101 -0
- package/dist/vibeKanbanBoardAdapter.d.ts +36 -0
- package/dist/vibeKanbanBoardAdapter.js +196 -0
- package/dist/vibeKanbanMcpConfig.d.ts +36 -0
- package/dist/vibeKanbanMcpConfig.js +191 -0
- package/dist/vibeKanbanProjectAdapter.d.ts +39 -0
- package/dist/vibeKanbanProjectAdapter.js +113 -0
- package/dist/vibeKanbanWorkspaceSetup.d.ts +1 -0
- package/dist/vibeKanbanWorkspaceSetup.js +96 -0
- package/dist/workItemService.d.ts +60 -0
- package/dist/workItemService.js +163 -0
- package/dist/workTrackingGitHubProvider.d.ts +71 -0
- package/dist/workTrackingGitHubProvider.js +663 -0
- package/dist/workTrackingGitLabProvider.d.ts +62 -0
- package/dist/workTrackingGitLabProvider.js +523 -0
- package/dist/workTrackingJiraProvider.d.ts +67 -0
- package/dist/workTrackingJiraProvider.js +652 -0
- package/dist/workTrackingLocalProvider.d.ts +49 -0
- package/dist/workTrackingLocalProvider.js +463 -0
- package/dist/workTrackingProviderService.d.ts +21 -0
- package/dist/workTrackingProviderService.js +117 -0
- package/dist/workTrackingTypes.d.ts +202 -0
- package/dist/workTrackingTypes.js +1 -0
- package/dist/workTrackingVibeProvider.d.ts +35 -0
- package/dist/workTrackingVibeProvider.js +119 -0
- package/dist/worktreeExecutionMetadata.d.ts +76 -0
- package/dist/worktreeExecutionMetadata.js +239 -0
- package/package.json +37 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { NexusAutomationAgentProfileIntendedUse, NexusAutomationAgentProfileConfig, NexusAutomationConfig, NexusAutomationSafetyConfig } from "./nexusAutomationConfig.js";
|
|
2
|
+
export type NexusAutomationAgentCommandSource = "override" | "agent_command" | "coordinator_profile";
|
|
3
|
+
export interface ResolvedNexusAutomationAgentCommand {
|
|
4
|
+
command: string;
|
|
5
|
+
source: NexusAutomationAgentCommandSource;
|
|
6
|
+
profile: NexusAutomationAgentProfileConfig | null;
|
|
7
|
+
}
|
|
8
|
+
export interface NexusAutomationAgentProfilePolicy {
|
|
9
|
+
id: string;
|
|
10
|
+
executor: string;
|
|
11
|
+
model: string | null;
|
|
12
|
+
version: string | null;
|
|
13
|
+
variant: string | null;
|
|
14
|
+
reasoning: string | null;
|
|
15
|
+
intelligence: string | null;
|
|
16
|
+
intendedUse: NexusAutomationAgentProfileIntendedUse;
|
|
17
|
+
safety: NexusAutomationSafetyConfig;
|
|
18
|
+
command: string | null;
|
|
19
|
+
args: string[];
|
|
20
|
+
}
|
|
21
|
+
export interface NexusAutomationAgentPolicy {
|
|
22
|
+
coordinatorProfileId: string | null;
|
|
23
|
+
maxConcurrentSubagents: number;
|
|
24
|
+
safety: NexusAutomationSafetyConfig;
|
|
25
|
+
coordinatorProfile: NexusAutomationAgentProfilePolicy | null;
|
|
26
|
+
profiles: NexusAutomationAgentProfilePolicy[];
|
|
27
|
+
}
|
|
28
|
+
export interface ResolveNexusAutomationAgentCommandOptions {
|
|
29
|
+
automationConfig: NexusAutomationConfig;
|
|
30
|
+
overrideCommand?: string;
|
|
31
|
+
commandName?: "run-once" | "schedule";
|
|
32
|
+
}
|
|
33
|
+
export declare class NexusAutomationAgentProfileError extends Error {
|
|
34
|
+
constructor(message: string);
|
|
35
|
+
}
|
|
36
|
+
export declare function resolveNexusAutomationAgentCommand(options: ResolveNexusAutomationAgentCommandOptions): ResolvedNexusAutomationAgentCommand;
|
|
37
|
+
export declare function normalizeNexusAutomationAgentPolicy(automationConfig: NexusAutomationConfig): NexusAutomationAgentPolicy;
|
|
38
|
+
export declare function shellCommandFromProfile(profile: NexusAutomationAgentProfileConfig): string;
|
|
39
|
+
export declare function shellQuoteArgument(value: string): string;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
export class NexusAutomationAgentProfileError extends Error {
|
|
2
|
+
constructor(message) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = "NexusAutomationAgentProfileError";
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export function resolveNexusAutomationAgentCommand(options) {
|
|
8
|
+
const overrideCommand = optionalNonEmptyString(options.overrideCommand);
|
|
9
|
+
if (overrideCommand) {
|
|
10
|
+
return {
|
|
11
|
+
command: overrideCommand,
|
|
12
|
+
source: "override",
|
|
13
|
+
profile: null,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
const configuredCommand = optionalNonEmptyString(options.automationConfig.agent.command);
|
|
17
|
+
if (configuredCommand) {
|
|
18
|
+
return {
|
|
19
|
+
command: configuredCommand,
|
|
20
|
+
source: "agent_command",
|
|
21
|
+
profile: null,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const coordinatorProfileId = options.automationConfig.agent.coordinatorProfileId;
|
|
25
|
+
if (!coordinatorProfileId) {
|
|
26
|
+
throw new NexusAutomationAgentProfileError(`automation ${options.commandName ?? "run"} requires --command, project config automation.agent.command, or automation.agent.coordinatorProfileId with a command-capable profile`);
|
|
27
|
+
}
|
|
28
|
+
const profile = options.automationConfig.agent.profiles.find((item) => item.id === coordinatorProfileId);
|
|
29
|
+
if (!profile) {
|
|
30
|
+
throw new NexusAutomationAgentProfileError(`automation.agent.coordinatorProfileId references missing profile: ${coordinatorProfileId}`);
|
|
31
|
+
}
|
|
32
|
+
if (!profile.command) {
|
|
33
|
+
throw new NexusAutomationAgentProfileError(`automation.agent.profiles.${coordinatorProfileId}.command must be configured for coordinator launch`);
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
command: shellCommandFromProfile(profile),
|
|
37
|
+
source: "coordinator_profile",
|
|
38
|
+
profile,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function normalizeNexusAutomationAgentPolicy(automationConfig) {
|
|
42
|
+
const profiles = automationConfig.agent.profiles.map((profile) => normalizeNexusAutomationAgentProfilePolicy(profile, automationConfig.safety));
|
|
43
|
+
const coordinatorProfile = profiles.find((profile) => profile.id === automationConfig.agent.coordinatorProfileId) ?? null;
|
|
44
|
+
return {
|
|
45
|
+
coordinatorProfileId: automationConfig.agent.coordinatorProfileId,
|
|
46
|
+
maxConcurrentSubagents: automationConfig.agent.maxConcurrentSubagents,
|
|
47
|
+
safety: automationConfig.safety,
|
|
48
|
+
coordinatorProfile,
|
|
49
|
+
profiles,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function normalizeNexusAutomationAgentProfilePolicy(profile, fallbackSafety) {
|
|
53
|
+
return {
|
|
54
|
+
id: requiredNonEmptyString(profile.id, "profile.id"),
|
|
55
|
+
executor: requiredNonEmptyString(profile.executor, "profile.executor"),
|
|
56
|
+
model: optionalNullableString(profile.model, "profile.model") ?? null,
|
|
57
|
+
version: optionalNullableString(profile.version, "profile.version") ?? null,
|
|
58
|
+
variant: optionalNullableString(profile.variant, "profile.variant") ?? null,
|
|
59
|
+
reasoning: optionalNullableString(profile.reasoning, "profile.reasoning") ??
|
|
60
|
+
null,
|
|
61
|
+
intelligence: optionalNullableString(profile.intelligence, "profile.intelligence") ??
|
|
62
|
+
null,
|
|
63
|
+
intendedUse: profile.intendedUse ?? "any",
|
|
64
|
+
safety: profile.safety ?? fallbackSafety,
|
|
65
|
+
command: optionalNullableString(profile.command, "profile.command") ?? null,
|
|
66
|
+
args: profile.args.map((arg) => requiredNonEmptyString(arg, "profile.args[]")),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export function shellCommandFromProfile(profile) {
|
|
70
|
+
const command = requiredNonEmptyString(profile.command, "profile.command");
|
|
71
|
+
return [command, ...profile.args.map(shellQuoteArgument)].join(" ");
|
|
72
|
+
}
|
|
73
|
+
export function shellQuoteArgument(value) {
|
|
74
|
+
const arg = requiredNonEmptyString(value, "profile.args[]");
|
|
75
|
+
if (/[\r\n]/u.test(arg)) {
|
|
76
|
+
throw new NexusAutomationAgentProfileError("profile args must not contain line breaks");
|
|
77
|
+
}
|
|
78
|
+
if (/^[A-Za-z0-9_./:@%+=,-]+$/u.test(arg)) {
|
|
79
|
+
return arg;
|
|
80
|
+
}
|
|
81
|
+
return `"${arg.replace(/(["\\])/gu, "\\$1")}"`;
|
|
82
|
+
}
|
|
83
|
+
function optionalNonEmptyString(value) {
|
|
84
|
+
if (value === undefined || value === null) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
return requiredNonEmptyString(value, "value");
|
|
88
|
+
}
|
|
89
|
+
function optionalNullableString(value, name) {
|
|
90
|
+
if (value === undefined) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
if (value === null) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
return requiredNonEmptyString(value, name);
|
|
97
|
+
}
|
|
98
|
+
function requiredNonEmptyString(value, name) {
|
|
99
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
100
|
+
throw new NexusAutomationAgentProfileError(`${name} must be a non-empty string`);
|
|
101
|
+
}
|
|
102
|
+
return value.trim();
|
|
103
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { NexusAutomationConfig } from "./nexusAutomationConfig.js";
|
|
2
|
+
import { type GetNexusAutomationStatusOptions } from "./nexusAutomationStatus.js";
|
|
3
|
+
import { type NexusPluginCapabilityProjection } from "./nexusPluginCapabilities.js";
|
|
4
|
+
import type { WorkItem } from "./workTrackingTypes.js";
|
|
5
|
+
export interface NexusAutomationProjectSummary {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
}
|
|
9
|
+
export interface NexusAutomationEligibleWorkItemSummary {
|
|
10
|
+
componentId: string;
|
|
11
|
+
id: string;
|
|
12
|
+
title: string;
|
|
13
|
+
status: WorkItem["status"];
|
|
14
|
+
labels: string[];
|
|
15
|
+
assignees: string[];
|
|
16
|
+
milestone: string | null;
|
|
17
|
+
updatedAt: string | null;
|
|
18
|
+
webUrl: string | null;
|
|
19
|
+
}
|
|
20
|
+
export interface NexusAutomationEligibleWorkComponentSummary {
|
|
21
|
+
componentId: string;
|
|
22
|
+
componentName: string;
|
|
23
|
+
role: string;
|
|
24
|
+
sourceRoot: string | null;
|
|
25
|
+
workTrackingProvider: string | null;
|
|
26
|
+
workItems: NexusAutomationEligibleWorkItemSummary[];
|
|
27
|
+
}
|
|
28
|
+
export interface NexusAutomationEligibleWorkSummary {
|
|
29
|
+
projectRoot: string;
|
|
30
|
+
project: NexusAutomationProjectSummary;
|
|
31
|
+
status: string;
|
|
32
|
+
summary: string;
|
|
33
|
+
selector: NexusAutomationConfig["selector"] | null;
|
|
34
|
+
eligibleWorkItemCount: number;
|
|
35
|
+
components: NexusAutomationEligibleWorkComponentSummary[];
|
|
36
|
+
}
|
|
37
|
+
export interface NexusAutomationAgentProfilePolicySummary {
|
|
38
|
+
id: string;
|
|
39
|
+
executor: string;
|
|
40
|
+
model: string | null;
|
|
41
|
+
version: string | null;
|
|
42
|
+
variant: string | null;
|
|
43
|
+
reasoning: string | null;
|
|
44
|
+
intelligence: string | null;
|
|
45
|
+
intendedUse: string;
|
|
46
|
+
safety: NexusAutomationConfig["safety"];
|
|
47
|
+
commandConfigured: boolean;
|
|
48
|
+
argsCount: number;
|
|
49
|
+
}
|
|
50
|
+
export interface NexusAutomationAgentProfileSummary {
|
|
51
|
+
projectRoot: string;
|
|
52
|
+
project: NexusAutomationProjectSummary;
|
|
53
|
+
automationEnabled: boolean;
|
|
54
|
+
automationMode: NexusAutomationConfig["mode"] | null;
|
|
55
|
+
coordinatorProfileId: string | null;
|
|
56
|
+
maxConcurrentSubagents: number | null;
|
|
57
|
+
safety: NexusAutomationConfig["safety"] | null;
|
|
58
|
+
profiles: NexusAutomationAgentProfilePolicySummary[];
|
|
59
|
+
pluginCapabilities: NexusPluginCapabilityProjection[];
|
|
60
|
+
}
|
|
61
|
+
export declare function getNexusAutomationEligibleWorkSummary(options: GetNexusAutomationStatusOptions): Promise<NexusAutomationEligibleWorkSummary>;
|
|
62
|
+
export declare function getNexusAutomationAgentProfileSummary(projectRoot: string): NexusAutomationAgentProfileSummary;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { normalizeNexusAutomationAgentPolicy } from "./nexusAutomationAgentProfile.js";
|
|
3
|
+
import { getNexusAutomationStatus, } from "./nexusAutomationStatus.js";
|
|
4
|
+
import { projectPluginCapabilityProjections, } from "./nexusPluginCapabilities.js";
|
|
5
|
+
import { loadProjectConfig } from "./nexusProjectConfig.js";
|
|
6
|
+
export async function getNexusAutomationEligibleWorkSummary(options) {
|
|
7
|
+
const status = await getNexusAutomationStatus(options);
|
|
8
|
+
const componentById = new Map(status.components.map((component) => [component.id, component]));
|
|
9
|
+
const grouped = status.componentEligibleWorkItems ??
|
|
10
|
+
(status.selectedWorkItem
|
|
11
|
+
? [
|
|
12
|
+
{
|
|
13
|
+
componentId: status.components[0]?.id ?? "primary",
|
|
14
|
+
workItems: [status.selectedWorkItem],
|
|
15
|
+
},
|
|
16
|
+
]
|
|
17
|
+
: []);
|
|
18
|
+
const components = grouped
|
|
19
|
+
.filter((component) => component.workItems.length > 0)
|
|
20
|
+
.map((component) => {
|
|
21
|
+
const resolved = componentById.get(component.componentId);
|
|
22
|
+
return {
|
|
23
|
+
componentId: component.componentId,
|
|
24
|
+
componentName: resolved?.name ?? component.componentId,
|
|
25
|
+
role: resolved?.role ?? "primary",
|
|
26
|
+
sourceRoot: resolved?.sourceRoot ?? null,
|
|
27
|
+
workTrackingProvider: resolved?.workTracking?.provider ?? null,
|
|
28
|
+
workItems: component.workItems.map((item) => summarizeEligibleWorkItem(component.componentId, item)),
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
return {
|
|
32
|
+
projectRoot: status.projectRoot,
|
|
33
|
+
project: {
|
|
34
|
+
id: status.projectConfig.id,
|
|
35
|
+
name: status.projectConfig.name,
|
|
36
|
+
},
|
|
37
|
+
status: status.status,
|
|
38
|
+
summary: status.summary,
|
|
39
|
+
selector: status.automationConfig?.selector ?? null,
|
|
40
|
+
eligibleWorkItemCount: components.reduce((total, component) => total + component.workItems.length, 0),
|
|
41
|
+
components,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export function getNexusAutomationAgentProfileSummary(projectRoot) {
|
|
45
|
+
const resolvedProjectRoot = path.resolve(projectRoot);
|
|
46
|
+
const projectConfig = loadProjectConfig(resolvedProjectRoot);
|
|
47
|
+
const automationConfig = projectConfig.automation ?? null;
|
|
48
|
+
const agentPolicy = automationConfig
|
|
49
|
+
? normalizeNexusAutomationAgentPolicy(automationConfig)
|
|
50
|
+
: null;
|
|
51
|
+
return {
|
|
52
|
+
projectRoot: resolvedProjectRoot,
|
|
53
|
+
project: {
|
|
54
|
+
id: projectConfig.id,
|
|
55
|
+
name: projectConfig.name,
|
|
56
|
+
},
|
|
57
|
+
automationEnabled: automationConfig?.enabled ?? false,
|
|
58
|
+
automationMode: automationConfig?.mode ?? null,
|
|
59
|
+
coordinatorProfileId: agentPolicy?.coordinatorProfileId ?? null,
|
|
60
|
+
maxConcurrentSubagents: agentPolicy?.maxConcurrentSubagents ?? null,
|
|
61
|
+
safety: automationConfig?.safety ?? null,
|
|
62
|
+
profiles: agentPolicy?.profiles.map((profile) => ({
|
|
63
|
+
id: profile.id,
|
|
64
|
+
executor: profile.executor,
|
|
65
|
+
model: profile.model,
|
|
66
|
+
version: profile.version,
|
|
67
|
+
variant: profile.variant,
|
|
68
|
+
reasoning: profile.reasoning,
|
|
69
|
+
intelligence: profile.intelligence,
|
|
70
|
+
intendedUse: profile.intendedUse,
|
|
71
|
+
safety: profile.safety,
|
|
72
|
+
commandConfigured: profile.command !== null,
|
|
73
|
+
argsCount: profile.args.length,
|
|
74
|
+
})) ?? [],
|
|
75
|
+
pluginCapabilities: projectPluginCapabilityProjections(projectConfig),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function summarizeEligibleWorkItem(componentId, item) {
|
|
79
|
+
return {
|
|
80
|
+
componentId,
|
|
81
|
+
id: item.id,
|
|
82
|
+
title: item.title,
|
|
83
|
+
status: item.status,
|
|
84
|
+
labels: item.labels ?? [],
|
|
85
|
+
assignees: item.assignees ?? [],
|
|
86
|
+
milestone: item.milestone ?? null,
|
|
87
|
+
updatedAt: item.updatedAt ?? null,
|
|
88
|
+
webUrl: item.webUrl ?? null,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type GitRunner } from "./gitWorktreeService.js";
|
|
2
|
+
import type { NexusAutomationExecutor } from "./nexusAutomationRunOnce.js";
|
|
3
|
+
export interface NexusAutomationCommandRunOptions {
|
|
4
|
+
cwd: string;
|
|
5
|
+
env: NodeJS.ProcessEnv;
|
|
6
|
+
timeoutMs?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface NexusAutomationCommandRunResult {
|
|
9
|
+
command: string;
|
|
10
|
+
cwd: string;
|
|
11
|
+
stdout: string;
|
|
12
|
+
stderr: string;
|
|
13
|
+
exitCode: number | null;
|
|
14
|
+
error?: string;
|
|
15
|
+
}
|
|
16
|
+
export type NexusAutomationCommandRunner = (command: string, options: NexusAutomationCommandRunOptions) => NexusAutomationCommandRunResult;
|
|
17
|
+
export interface CreateNexusAutomationCommandExecutorOptions {
|
|
18
|
+
command: string;
|
|
19
|
+
runFullVerification?: boolean;
|
|
20
|
+
commandRunner?: NexusAutomationCommandRunner;
|
|
21
|
+
gitRunner?: GitRunner;
|
|
22
|
+
env?: NodeJS.ProcessEnv;
|
|
23
|
+
timeoutMs?: number;
|
|
24
|
+
}
|
|
25
|
+
export declare class NexusAutomationCommandExecutorError extends Error {
|
|
26
|
+
constructor(message: string);
|
|
27
|
+
}
|
|
28
|
+
export declare function createNexusAutomationCommandExecutor(options: CreateNexusAutomationCommandExecutorOptions): NexusAutomationExecutor;
|
|
29
|
+
export declare function defaultNexusAutomationCommandRunner(command: string, options: NexusAutomationCommandRunOptions): NexusAutomationCommandRunResult;
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import process from "node:process";
|
|
6
|
+
import { defaultGitRunner, } from "./gitWorktreeService.js";
|
|
7
|
+
export class NexusAutomationCommandExecutorError extends Error {
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "NexusAutomationCommandExecutorError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function createNexusAutomationCommandExecutor(options) {
|
|
14
|
+
const command = requiredNonEmptyString(options.command, "command");
|
|
15
|
+
const commandRunner = options.commandRunner ?? defaultNexusAutomationCommandRunner;
|
|
16
|
+
const gitRunner = options.gitRunner ?? defaultGitRunner;
|
|
17
|
+
return async (input) => {
|
|
18
|
+
const env = executorEnvironment(options.env ?? process.env, input);
|
|
19
|
+
const commandResult = commandRunner(command, {
|
|
20
|
+
cwd: input.worktree.worktreePath,
|
|
21
|
+
env,
|
|
22
|
+
timeoutMs: options.timeoutMs,
|
|
23
|
+
});
|
|
24
|
+
const verification = [
|
|
25
|
+
verificationFromCommandResult(commandResult),
|
|
26
|
+
];
|
|
27
|
+
if (!commandSucceeded(commandResult)) {
|
|
28
|
+
return {
|
|
29
|
+
status: "failed",
|
|
30
|
+
summary: `Executor command failed: ${commandSummary(commandResult)}`,
|
|
31
|
+
verification,
|
|
32
|
+
commitIds: newCommitIds(input, gitRunner),
|
|
33
|
+
publicationDecision: publicationDecision(input, "failed"),
|
|
34
|
+
error: commandSummary(commandResult),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const verificationCommands = [
|
|
38
|
+
...input.automationConfig.verification.focusedCommands,
|
|
39
|
+
...(options.runFullVerification
|
|
40
|
+
? input.automationConfig.verification.fullCommands
|
|
41
|
+
: []),
|
|
42
|
+
];
|
|
43
|
+
for (const verificationCommand of verificationCommands) {
|
|
44
|
+
verification.push(verificationFromCommandResult(commandRunner(verificationCommand, {
|
|
45
|
+
cwd: input.worktree.worktreePath,
|
|
46
|
+
env,
|
|
47
|
+
timeoutMs: options.timeoutMs,
|
|
48
|
+
})));
|
|
49
|
+
}
|
|
50
|
+
const failedVerification = verification.filter((record) => record.status === "failed");
|
|
51
|
+
const status = input.automationConfig.verification.requirePassing &&
|
|
52
|
+
failedVerification.length > 0
|
|
53
|
+
? "failed"
|
|
54
|
+
: "completed";
|
|
55
|
+
return {
|
|
56
|
+
status,
|
|
57
|
+
summary: executorSummary(status, verification),
|
|
58
|
+
verification,
|
|
59
|
+
commitIds: newCommitIds(input, gitRunner),
|
|
60
|
+
publicationDecision: publicationDecision(input, status),
|
|
61
|
+
...(status === "failed"
|
|
62
|
+
? { error: `${failedVerification.length} command(s) failed` }
|
|
63
|
+
: {}),
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
export function defaultNexusAutomationCommandRunner(command, options) {
|
|
68
|
+
const capture = createCommandCaptureFiles();
|
|
69
|
+
let result = null;
|
|
70
|
+
try {
|
|
71
|
+
result = spawnSync(command, {
|
|
72
|
+
cwd: options.cwd,
|
|
73
|
+
env: options.env,
|
|
74
|
+
shell: true,
|
|
75
|
+
stdio: ["ignore", capture.stdoutFd, capture.stderrFd],
|
|
76
|
+
timeout: options.timeoutMs,
|
|
77
|
+
windowsHide: true,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
capture.close();
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
return {
|
|
85
|
+
command,
|
|
86
|
+
cwd: options.cwd,
|
|
87
|
+
stdout: readOutputPreview(capture.stdoutPath),
|
|
88
|
+
stderr: readOutputPreview(capture.stderrPath),
|
|
89
|
+
exitCode: result?.status ?? null,
|
|
90
|
+
...(result?.error ? { error: result.error.message } : {}),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
capture.remove();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function createCommandCaptureFiles() {
|
|
98
|
+
const captureDir = fs.mkdtempSync(path.join(os.tmpdir(), "dev-nexus-command-"));
|
|
99
|
+
const stdoutPath = path.join(captureDir, "stdout.log");
|
|
100
|
+
const stderrPath = path.join(captureDir, "stderr.log");
|
|
101
|
+
const stdoutFd = fs.openSync(stdoutPath, "w");
|
|
102
|
+
const stderrFd = fs.openSync(stderrPath, "w");
|
|
103
|
+
let closed = false;
|
|
104
|
+
return {
|
|
105
|
+
stdoutPath,
|
|
106
|
+
stderrPath,
|
|
107
|
+
stdoutFd,
|
|
108
|
+
stderrFd,
|
|
109
|
+
close: () => {
|
|
110
|
+
if (closed) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
closed = true;
|
|
114
|
+
fs.closeSync(stdoutFd);
|
|
115
|
+
fs.closeSync(stderrFd);
|
|
116
|
+
},
|
|
117
|
+
remove: () => {
|
|
118
|
+
fs.rmSync(captureDir, { recursive: true, force: true });
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function executorEnvironment(baseEnv, input) {
|
|
123
|
+
return {
|
|
124
|
+
...baseEnv,
|
|
125
|
+
DEV_NEXUS_RUN_ID: input.runId,
|
|
126
|
+
DEV_NEXUS_STARTED_AT: input.startedAt,
|
|
127
|
+
DEV_NEXUS_PROJECT_ROOT: input.projectRoot,
|
|
128
|
+
DEV_NEXUS_SOURCE_ROOT: input.sourceRoot,
|
|
129
|
+
DEV_NEXUS_WORKTREE_PATH: input.worktree.worktreePath,
|
|
130
|
+
DEV_NEXUS_WORKTREE_BRANCH: input.worktree.branchName,
|
|
131
|
+
DEV_NEXUS_WORK_ITEM_ID: input.workItem.id,
|
|
132
|
+
DEV_NEXUS_WORK_ITEM_TITLE: input.workItem.title,
|
|
133
|
+
...(input.setup.context
|
|
134
|
+
? {
|
|
135
|
+
DEV_NEXUS_CONTEXT_FILE: input.setup.context.contextJsonPath,
|
|
136
|
+
DEV_NEXUS_BRIEFING_FILE: input.setup.context.briefingPath,
|
|
137
|
+
}
|
|
138
|
+
: {}),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function verificationFromCommandResult(result) {
|
|
142
|
+
return {
|
|
143
|
+
command: result.command,
|
|
144
|
+
status: commandStatus(result),
|
|
145
|
+
summary: commandSummary(result),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function commandStatus(result) {
|
|
149
|
+
return commandSucceeded(result) ? "passed" : "failed";
|
|
150
|
+
}
|
|
151
|
+
function commandSucceeded(result) {
|
|
152
|
+
return result.exitCode === 0 && !result.error;
|
|
153
|
+
}
|
|
154
|
+
function commandSummary(result) {
|
|
155
|
+
if (result.error) {
|
|
156
|
+
return result.error;
|
|
157
|
+
}
|
|
158
|
+
const exit = result.exitCode === null ? "no exit code" : `exit ${result.exitCode}`;
|
|
159
|
+
const output = firstNonEmptyLine(result.stderr) ?? firstNonEmptyLine(result.stdout);
|
|
160
|
+
return output ? `${exit}: ${truncate(output, 180)}` : exit;
|
|
161
|
+
}
|
|
162
|
+
function executorSummary(status, verification) {
|
|
163
|
+
const failed = verification.filter((record) => record.status === "failed").length;
|
|
164
|
+
const passed = verification.filter((record) => record.status === "passed").length;
|
|
165
|
+
if (status === "failed") {
|
|
166
|
+
return `Command executor failed: ${failed} failed, ${passed} passed`;
|
|
167
|
+
}
|
|
168
|
+
return `Command executor completed: ${passed} passed, ${failed} failed`;
|
|
169
|
+
}
|
|
170
|
+
function publicationDecision(input, status) {
|
|
171
|
+
const publication = input.automationConfig.publication;
|
|
172
|
+
if (status !== "completed") {
|
|
173
|
+
return {
|
|
174
|
+
type: "blocked",
|
|
175
|
+
remote: publication.remote,
|
|
176
|
+
targetBranch: publication.targetBranch,
|
|
177
|
+
reason: "Command executor did not complete cleanly",
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
type: publication.strategy,
|
|
182
|
+
remote: publication.remote,
|
|
183
|
+
targetBranch: publication.targetBranch,
|
|
184
|
+
reason: publication.push
|
|
185
|
+
? "Configured publication policy requests push; the CLI executor did not publish automatically"
|
|
186
|
+
: "Recorded configured publication policy; push is disabled",
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
function newCommitIds(input, gitRunner) {
|
|
190
|
+
if (!input.worktree.baseRef) {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
try {
|
|
194
|
+
const result = gitRunner(["rev-list", "--reverse", `${input.worktree.baseRef}..HEAD`], input.worktree.worktreePath);
|
|
195
|
+
if (result.exitCode !== 0) {
|
|
196
|
+
return [];
|
|
197
|
+
}
|
|
198
|
+
return result.stdout
|
|
199
|
+
.split(/\r?\n/u)
|
|
200
|
+
.map((line) => line.trim())
|
|
201
|
+
.filter(Boolean);
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function outputText(value) {
|
|
208
|
+
if (typeof value === "string") {
|
|
209
|
+
return value;
|
|
210
|
+
}
|
|
211
|
+
return value?.toString("utf8") ?? "";
|
|
212
|
+
}
|
|
213
|
+
function readOutputPreview(filePath) {
|
|
214
|
+
const maxOutputBytes = 256 * 1024;
|
|
215
|
+
const stats = fs.statSync(filePath);
|
|
216
|
+
if (stats.size <= maxOutputBytes) {
|
|
217
|
+
return outputText(fs.readFileSync(filePath));
|
|
218
|
+
}
|
|
219
|
+
const chunkBytes = maxOutputBytes / 2;
|
|
220
|
+
const fd = fs.openSync(filePath, "r");
|
|
221
|
+
try {
|
|
222
|
+
const head = Buffer.alloc(chunkBytes);
|
|
223
|
+
const tail = Buffer.alloc(chunkBytes);
|
|
224
|
+
const headBytes = fs.readSync(fd, head, 0, chunkBytes, 0);
|
|
225
|
+
const tailBytes = fs.readSync(fd, tail, 0, chunkBytes, stats.size - chunkBytes);
|
|
226
|
+
const omittedBytes = stats.size - headBytes - tailBytes;
|
|
227
|
+
return [
|
|
228
|
+
head.subarray(0, headBytes).toString("utf8"),
|
|
229
|
+
`\n[dev-nexus output truncated: ${omittedBytes} bytes omitted]\n`,
|
|
230
|
+
tail.subarray(0, tailBytes).toString("utf8"),
|
|
231
|
+
].join("");
|
|
232
|
+
}
|
|
233
|
+
finally {
|
|
234
|
+
fs.closeSync(fd);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function firstNonEmptyLine(value) {
|
|
238
|
+
return value
|
|
239
|
+
.split(/\r?\n/u)
|
|
240
|
+
.map((line) => line.trim())
|
|
241
|
+
.find(Boolean);
|
|
242
|
+
}
|
|
243
|
+
function truncate(value, length) {
|
|
244
|
+
return value.length <= length ? value : `${value.slice(0, length - 3)}...`;
|
|
245
|
+
}
|
|
246
|
+
function requiredNonEmptyString(value, name) {
|
|
247
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
248
|
+
throw new NexusAutomationCommandExecutorError(`${name} must be a non-empty string`);
|
|
249
|
+
}
|
|
250
|
+
return value.trim();
|
|
251
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { WorkStatus } from "./workTrackingTypes.js";
|
|
2
|
+
export type NexusAutomationMode = "run_once" | "agent_launch";
|
|
3
|
+
export type NexusAutomationSafetyProfile = "local" | "isolated" | "host-authorized";
|
|
4
|
+
export type NexusAutomationPublicationStrategy = "local_only" | "direct_integration" | "review_handoff";
|
|
5
|
+
export type NexusAutomationAgentProfileIntendedUse = "any" | "coordinator" | "subagent";
|
|
6
|
+
export interface NexusAutomationSelectorConfig {
|
|
7
|
+
statuses: WorkStatus[];
|
|
8
|
+
labels: string[];
|
|
9
|
+
excludeLabels: string[];
|
|
10
|
+
assignees: string[];
|
|
11
|
+
search: string | null;
|
|
12
|
+
limit: number;
|
|
13
|
+
}
|
|
14
|
+
export interface NexusAutomationVerificationConfig {
|
|
15
|
+
focusedCommands: string[];
|
|
16
|
+
fullCommands: string[];
|
|
17
|
+
requirePassing: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface NexusAutomationLedgerConfig {
|
|
20
|
+
path: string;
|
|
21
|
+
retention: number;
|
|
22
|
+
}
|
|
23
|
+
export interface NexusAutomationLockConfig {
|
|
24
|
+
path: string;
|
|
25
|
+
staleAfterMs: number;
|
|
26
|
+
}
|
|
27
|
+
export interface NexusAutomationBackoffConfig {
|
|
28
|
+
failureLimit: number;
|
|
29
|
+
baseDelayMs: number;
|
|
30
|
+
maxDelayMs: number;
|
|
31
|
+
}
|
|
32
|
+
export interface NexusAutomationScheduleConfig {
|
|
33
|
+
enabled: boolean;
|
|
34
|
+
intervalMs: number;
|
|
35
|
+
}
|
|
36
|
+
export interface NexusAutomationDependencyLinkConfig {
|
|
37
|
+
source: string;
|
|
38
|
+
target: string;
|
|
39
|
+
required: boolean;
|
|
40
|
+
}
|
|
41
|
+
export interface NexusAutomationSetupConfig {
|
|
42
|
+
dependencyLinks: NexusAutomationDependencyLinkConfig[];
|
|
43
|
+
}
|
|
44
|
+
export interface NexusAutomationExecutorConfig {
|
|
45
|
+
command: string | null;
|
|
46
|
+
timeoutMs: number | null;
|
|
47
|
+
runFullVerification: boolean;
|
|
48
|
+
}
|
|
49
|
+
export interface NexusAutomationAgentRelaunchConfig {
|
|
50
|
+
whileEligible: boolean;
|
|
51
|
+
}
|
|
52
|
+
export interface NexusAutomationAgentProfileConfig {
|
|
53
|
+
id: string;
|
|
54
|
+
executor: string;
|
|
55
|
+
model: string | null;
|
|
56
|
+
version?: string | null;
|
|
57
|
+
variant?: string | null;
|
|
58
|
+
reasoning: string | null;
|
|
59
|
+
intelligence?: string | null;
|
|
60
|
+
intendedUse?: NexusAutomationAgentProfileIntendedUse;
|
|
61
|
+
safety?: NexusAutomationSafetyConfig | null;
|
|
62
|
+
command: string | null;
|
|
63
|
+
args: string[];
|
|
64
|
+
}
|
|
65
|
+
export interface NexusAutomationAgentConfig {
|
|
66
|
+
command: string | null;
|
|
67
|
+
timeoutMs: number | null;
|
|
68
|
+
coordinatorProfileId: string | null;
|
|
69
|
+
maxConcurrentSubagents: number;
|
|
70
|
+
profiles: NexusAutomationAgentProfileConfig[];
|
|
71
|
+
relaunch: NexusAutomationAgentRelaunchConfig;
|
|
72
|
+
}
|
|
73
|
+
export interface NexusAutomationTargetConfig {
|
|
74
|
+
id: string | null;
|
|
75
|
+
objective: string | null;
|
|
76
|
+
statePath: string;
|
|
77
|
+
cycleLedgerPath: string;
|
|
78
|
+
stopWhenNoEligibleWork: boolean;
|
|
79
|
+
maxCycles: number | null;
|
|
80
|
+
maxWorkItems: number | null;
|
|
81
|
+
}
|
|
82
|
+
export interface NexusAutomationSafetyConfig {
|
|
83
|
+
profile: NexusAutomationSafetyProfile;
|
|
84
|
+
allowHostMutation: boolean;
|
|
85
|
+
allowDependencyInstall: boolean;
|
|
86
|
+
allowLiveServices: boolean;
|
|
87
|
+
}
|
|
88
|
+
export interface NexusAutomationPublicationConfig {
|
|
89
|
+
strategy: NexusAutomationPublicationStrategy;
|
|
90
|
+
remote: string | null;
|
|
91
|
+
targetBranch: string | null;
|
|
92
|
+
push: boolean;
|
|
93
|
+
}
|
|
94
|
+
export interface NexusAutomationConfig {
|
|
95
|
+
enabled: boolean;
|
|
96
|
+
mode: NexusAutomationMode;
|
|
97
|
+
selector: NexusAutomationSelectorConfig;
|
|
98
|
+
verification: NexusAutomationVerificationConfig;
|
|
99
|
+
ledger: NexusAutomationLedgerConfig;
|
|
100
|
+
lock: NexusAutomationLockConfig;
|
|
101
|
+
backoff: NexusAutomationBackoffConfig;
|
|
102
|
+
schedule: NexusAutomationScheduleConfig;
|
|
103
|
+
setup: NexusAutomationSetupConfig;
|
|
104
|
+
executor: NexusAutomationExecutorConfig;
|
|
105
|
+
agent: NexusAutomationAgentConfig;
|
|
106
|
+
target: NexusAutomationTargetConfig;
|
|
107
|
+
safety: NexusAutomationSafetyConfig;
|
|
108
|
+
publication: NexusAutomationPublicationConfig;
|
|
109
|
+
}
|
|
110
|
+
export declare const defaultNexusAutomationConfig: NexusAutomationConfig;
|
|
111
|
+
export declare class NexusAutomationConfigError extends Error {
|
|
112
|
+
constructor(message: string);
|
|
113
|
+
}
|
|
114
|
+
export declare function validateNexusAutomationConfig(value: unknown): NexusAutomationConfig | undefined;
|