@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.
Files changed (100) hide show
  1. package/README.md +677 -0
  2. package/dist/browserOpener.d.ts +9 -0
  3. package/dist/browserOpener.js +47 -0
  4. package/dist/cli.d.ts +18 -0
  5. package/dist/cli.js +2374 -0
  6. package/dist/gitWorktreeService.d.ts +57 -0
  7. package/dist/gitWorktreeService.js +157 -0
  8. package/dist/index.d.ts +47 -0
  9. package/dist/index.js +47 -0
  10. package/dist/nexusAgentMcpConfig.d.ts +30 -0
  11. package/dist/nexusAgentMcpConfig.js +228 -0
  12. package/dist/nexusAutomation.d.ts +103 -0
  13. package/dist/nexusAutomation.js +390 -0
  14. package/dist/nexusAutomationAgentLaunch.d.ts +148 -0
  15. package/dist/nexusAutomationAgentLaunch.js +855 -0
  16. package/dist/nexusAutomationAgentProfile.d.ts +39 -0
  17. package/dist/nexusAutomationAgentProfile.js +103 -0
  18. package/dist/nexusAutomationAgentSurface.d.ts +62 -0
  19. package/dist/nexusAutomationAgentSurface.js +90 -0
  20. package/dist/nexusAutomationCommandExecutor.d.ts +29 -0
  21. package/dist/nexusAutomationCommandExecutor.js +251 -0
  22. package/dist/nexusAutomationConfig.d.ts +114 -0
  23. package/dist/nexusAutomationConfig.js +547 -0
  24. package/dist/nexusAutomationEnqueue.d.ts +37 -0
  25. package/dist/nexusAutomationEnqueue.js +128 -0
  26. package/dist/nexusAutomationRunOnce.d.ts +91 -0
  27. package/dist/nexusAutomationRunOnce.js +586 -0
  28. package/dist/nexusAutomationScheduler.d.ts +50 -0
  29. package/dist/nexusAutomationScheduler.js +196 -0
  30. package/dist/nexusAutomationStatus.d.ts +55 -0
  31. package/dist/nexusAutomationStatus.js +462 -0
  32. package/dist/nexusAutomationTarget.d.ts +19 -0
  33. package/dist/nexusAutomationTarget.js +33 -0
  34. package/dist/nexusAutomationTargetCycle.d.ts +90 -0
  35. package/dist/nexusAutomationTargetCycle.js +282 -0
  36. package/dist/nexusAutomationTargetReport.d.ts +136 -0
  37. package/dist/nexusAutomationTargetReport.js +504 -0
  38. package/dist/nexusAutomationWorktreeSetup.d.ts +89 -0
  39. package/dist/nexusAutomationWorktreeSetup.js +661 -0
  40. package/dist/nexusCoordination.d.ts +198 -0
  41. package/dist/nexusCoordination.js +1018 -0
  42. package/dist/nexusExtension.d.ts +31 -0
  43. package/dist/nexusExtension.js +1 -0
  44. package/dist/nexusHomeConfig.d.ts +38 -0
  45. package/dist/nexusHomeConfig.js +133 -0
  46. package/dist/nexusMcpServer.d.ts +31 -0
  47. package/dist/nexusMcpServer.js +1036 -0
  48. package/dist/nexusPluginCapabilities.d.ts +197 -0
  49. package/dist/nexusPluginCapabilities.js +201 -0
  50. package/dist/nexusProjectConfig.d.ts +95 -0
  51. package/dist/nexusProjectConfig.js +880 -0
  52. package/dist/nexusProjectHomeService.d.ts +121 -0
  53. package/dist/nexusProjectHomeService.js +171 -0
  54. package/dist/nexusProjectLifecycle.d.ts +62 -0
  55. package/dist/nexusProjectLifecycle.js +205 -0
  56. package/dist/nexusProjectOperations.d.ts +101 -0
  57. package/dist/nexusProjectOperations.js +296 -0
  58. package/dist/nexusProjectRegistry.d.ts +42 -0
  59. package/dist/nexusProjectRegistry.js +91 -0
  60. package/dist/nexusProjectScaffold.d.ts +25 -0
  61. package/dist/nexusProjectScaffold.js +61 -0
  62. package/dist/nexusProjectTemplate.d.ts +34 -0
  63. package/dist/nexusProjectTemplate.js +354 -0
  64. package/dist/nexusSkills.d.ts +134 -0
  65. package/dist/nexusSkills.js +647 -0
  66. package/dist/nexusWorkerContextBundle.d.ts +142 -0
  67. package/dist/nexusWorkerContextBundle.js +375 -0
  68. package/dist/processSupervisor.d.ts +89 -0
  69. package/dist/processSupervisor.js +440 -0
  70. package/dist/vibeKanbanApi.d.ts +11 -0
  71. package/dist/vibeKanbanApi.js +14 -0
  72. package/dist/vibeKanbanAuth.d.ts +25 -0
  73. package/dist/vibeKanbanAuth.js +101 -0
  74. package/dist/vibeKanbanBoardAdapter.d.ts +36 -0
  75. package/dist/vibeKanbanBoardAdapter.js +196 -0
  76. package/dist/vibeKanbanMcpConfig.d.ts +36 -0
  77. package/dist/vibeKanbanMcpConfig.js +191 -0
  78. package/dist/vibeKanbanProjectAdapter.d.ts +39 -0
  79. package/dist/vibeKanbanProjectAdapter.js +113 -0
  80. package/dist/vibeKanbanWorkspaceSetup.d.ts +1 -0
  81. package/dist/vibeKanbanWorkspaceSetup.js +96 -0
  82. package/dist/workItemService.d.ts +60 -0
  83. package/dist/workItemService.js +163 -0
  84. package/dist/workTrackingGitHubProvider.d.ts +71 -0
  85. package/dist/workTrackingGitHubProvider.js +663 -0
  86. package/dist/workTrackingGitLabProvider.d.ts +62 -0
  87. package/dist/workTrackingGitLabProvider.js +523 -0
  88. package/dist/workTrackingJiraProvider.d.ts +67 -0
  89. package/dist/workTrackingJiraProvider.js +652 -0
  90. package/dist/workTrackingLocalProvider.d.ts +49 -0
  91. package/dist/workTrackingLocalProvider.js +463 -0
  92. package/dist/workTrackingProviderService.d.ts +21 -0
  93. package/dist/workTrackingProviderService.js +117 -0
  94. package/dist/workTrackingTypes.d.ts +202 -0
  95. package/dist/workTrackingTypes.js +1 -0
  96. package/dist/workTrackingVibeProvider.d.ts +35 -0
  97. package/dist/workTrackingVibeProvider.js +119 -0
  98. package/dist/worktreeExecutionMetadata.d.ts +76 -0
  99. package/dist/worktreeExecutionMetadata.js +239 -0
  100. package/package.json +37 -0
@@ -0,0 +1,57 @@
1
+ export interface GitCommandResult {
2
+ args: string[];
3
+ stdout: string;
4
+ stderr: string;
5
+ exitCode: number | null;
6
+ }
7
+ export type GitRunner = (args: readonly string[], cwd?: string) => GitCommandResult;
8
+ export interface PrepareGitWorktreeOptions {
9
+ componentId: string;
10
+ sourceRoot: string;
11
+ worktreesRoot: string;
12
+ branchName: string;
13
+ worktreeName?: string;
14
+ baseRef?: string | null;
15
+ workItemId?: string | null;
16
+ workItemTitle?: string | null;
17
+ gitRunner?: GitRunner;
18
+ }
19
+ export interface PreparedGitWorktreeWorkItem {
20
+ id: string;
21
+ title: string | null;
22
+ }
23
+ export interface PrepareGitWorktreeResult {
24
+ componentId: string;
25
+ sourceRoot: string;
26
+ worktreesRoot: string;
27
+ worktreePath: string;
28
+ branchName: string;
29
+ baseRef: string | null;
30
+ workItem: PreparedGitWorktreeWorkItem | null;
31
+ git: {
32
+ commands: GitCommandResult[];
33
+ };
34
+ }
35
+ export interface RemoveGitWorktreeOptions {
36
+ sourceRoot: string;
37
+ worktreePath: string;
38
+ gitRunner?: GitRunner;
39
+ }
40
+ export interface RemoveGitWorktreeResult {
41
+ sourceRoot: string;
42
+ worktreePath: string;
43
+ git: {
44
+ commands: GitCommandResult[];
45
+ };
46
+ }
47
+ export declare class GitWorktreeServiceError extends Error {
48
+ constructor(message: string);
49
+ }
50
+ export declare function prepareGitWorktree(options: PrepareGitWorktreeOptions): PrepareGitWorktreeResult;
51
+ export declare function removeGitWorktree(options: RemoveGitWorktreeOptions): RemoveGitWorktreeResult;
52
+ export declare function normalizeBranchName(value: string): string;
53
+ export declare function safeDirectoryName(value: string): string;
54
+ export declare function normalizeWorktreeName(value: string): string;
55
+ export declare function assertSafeWorktreePath(worktreesRoot: string, worktreePath: string): void;
56
+ export declare function defaultGitRunner(args: readonly string[], cwd?: string): GitCommandResult;
57
+ export declare function runGitCommand(gitRunner: GitRunner, commands: GitCommandResult[], args: readonly string[], cwd?: string): GitCommandResult;
@@ -0,0 +1,157 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ export class GitWorktreeServiceError extends Error {
5
+ constructor(message) {
6
+ super(message);
7
+ this.name = "GitWorktreeServiceError";
8
+ }
9
+ }
10
+ export function prepareGitWorktree(options) {
11
+ const componentId = requiredNonEmptyString(options.componentId, "componentId");
12
+ const sourceRoot = path.resolve(options.sourceRoot);
13
+ const worktreesRoot = path.resolve(options.worktreesRoot);
14
+ const branchName = normalizeBranchName(options.branchName);
15
+ const baseRef = optionalNullableString(options.baseRef, "baseRef");
16
+ const worktreeName = options.worktreeName
17
+ ? normalizeWorktreeName(options.worktreeName)
18
+ : safeDirectoryName(branchName);
19
+ const workItem = normalizePreparedWorktreeWorkItem(options.workItemId, options.workItemTitle);
20
+ const worktreePath = path.join(worktreesRoot, worktreeName);
21
+ assertSafeWorktreePath(worktreesRoot, worktreePath);
22
+ if (fs.existsSync(worktreePath)) {
23
+ throw new GitWorktreeServiceError(`Worktree path already exists: ${worktreePath}`);
24
+ }
25
+ const gitRunner = options.gitRunner ?? defaultGitRunner;
26
+ const commands = [];
27
+ fs.mkdirSync(worktreesRoot, { recursive: true });
28
+ runGitCommand(gitRunner, commands, [
29
+ "worktree",
30
+ "add",
31
+ "-b",
32
+ branchName,
33
+ worktreePath,
34
+ ...(baseRef ? [baseRef] : []),
35
+ ], sourceRoot);
36
+ return {
37
+ componentId,
38
+ sourceRoot,
39
+ worktreesRoot,
40
+ worktreePath,
41
+ branchName,
42
+ baseRef,
43
+ workItem,
44
+ git: {
45
+ commands,
46
+ },
47
+ };
48
+ }
49
+ export function removeGitWorktree(options) {
50
+ const sourceRoot = path.resolve(options.sourceRoot);
51
+ const worktreePath = path.resolve(options.worktreePath);
52
+ const gitRunner = options.gitRunner ?? defaultGitRunner;
53
+ const commands = [];
54
+ runGitCommand(gitRunner, commands, ["worktree", "remove", worktreePath], sourceRoot);
55
+ return {
56
+ sourceRoot,
57
+ worktreePath,
58
+ git: {
59
+ commands,
60
+ },
61
+ };
62
+ }
63
+ export function normalizeBranchName(value) {
64
+ const trimmed = value.trim().replaceAll("\\", "/");
65
+ if (trimmed.length === 0) {
66
+ throw new GitWorktreeServiceError("branchName must be non-empty");
67
+ }
68
+ if (trimmed.startsWith("/") ||
69
+ trimmed.endsWith("/") ||
70
+ trimmed.includes("..") ||
71
+ trimmed.includes("//") ||
72
+ /[\u0000-\u001F ~^:?*[\\]/u.test(trimmed)) {
73
+ throw new GitWorktreeServiceError(`Invalid branchName: ${value}`);
74
+ }
75
+ return trimmed;
76
+ }
77
+ export function safeDirectoryName(value) {
78
+ const sanitized = value
79
+ .trim()
80
+ .replace(/([a-z0-9])([A-Z])/g, "$1-$2")
81
+ .replace(/[^A-Za-z0-9._-]+/g, "-")
82
+ .replace(/^-+|-+$/g, "")
83
+ .toLowerCase();
84
+ if (!sanitized) {
85
+ throw new GitWorktreeServiceError("Worktree name must contain at least one filesystem-safe character");
86
+ }
87
+ if (sanitized === "." || sanitized === ".." || sanitized.includes("..")) {
88
+ throw new GitWorktreeServiceError(`Invalid worktreeName: ${value}`);
89
+ }
90
+ return sanitized;
91
+ }
92
+ export function normalizeWorktreeName(value) {
93
+ const trimmed = requiredNonEmptyString(value, "worktreeName");
94
+ if (path.isAbsolute(trimmed) ||
95
+ trimmed === "." ||
96
+ trimmed === ".." ||
97
+ trimmed.includes("..") ||
98
+ /[\\/]/u.test(trimmed) ||
99
+ !/^[A-Za-z0-9._-]+$/u.test(trimmed)) {
100
+ throw new GitWorktreeServiceError(`Invalid worktreeName: ${value}`);
101
+ }
102
+ return trimmed;
103
+ }
104
+ export function assertSafeWorktreePath(worktreesRoot, worktreePath) {
105
+ const resolvedRoot = path.resolve(worktreesRoot);
106
+ const resolvedTarget = path.resolve(worktreePath);
107
+ const relative = path.relative(resolvedRoot, resolvedTarget);
108
+ if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) {
109
+ throw new GitWorktreeServiceError(`Worktree path must be inside worktrees root: ${resolvedTarget}`);
110
+ }
111
+ }
112
+ export function defaultGitRunner(args, cwd) {
113
+ const result = spawnSync("git", [...args], {
114
+ cwd,
115
+ encoding: "utf8",
116
+ shell: false,
117
+ windowsHide: true,
118
+ });
119
+ if (result.error) {
120
+ throw new GitWorktreeServiceError(`Failed to run git ${args.join(" ")}: ${result.error.message}`);
121
+ }
122
+ return {
123
+ args: [...args],
124
+ stdout: result.stdout ?? "",
125
+ stderr: result.stderr ?? "",
126
+ exitCode: result.status,
127
+ };
128
+ }
129
+ export function runGitCommand(gitRunner, commands, args, cwd) {
130
+ const result = gitRunner(args, cwd);
131
+ commands.push(result);
132
+ if (result.exitCode !== 0) {
133
+ throw new GitWorktreeServiceError(`git ${args.join(" ")} failed with exit code ${result.exitCode}: ${result.stderr.trim() || result.stdout.trim()}`);
134
+ }
135
+ return result;
136
+ }
137
+ function normalizePreparedWorktreeWorkItem(workItemId, workItemTitle) {
138
+ if (workItemId === undefined || workItemId === null) {
139
+ return null;
140
+ }
141
+ return {
142
+ id: requiredNonEmptyString(workItemId, "workItemId"),
143
+ title: optionalNullableString(workItemTitle, "workItemTitle"),
144
+ };
145
+ }
146
+ function optionalNullableString(value, name) {
147
+ if (value === undefined || value === null) {
148
+ return null;
149
+ }
150
+ return requiredNonEmptyString(value, name);
151
+ }
152
+ function requiredNonEmptyString(value, name) {
153
+ if (typeof value !== "string" || value.trim().length === 0) {
154
+ throw new GitWorktreeServiceError(`${name} must be a non-empty string`);
155
+ }
156
+ return value.trim();
157
+ }
@@ -0,0 +1,47 @@
1
+ export * from "./browserOpener.js";
2
+ export * from "./gitWorktreeService.js";
3
+ export * from "./nexusAutomation.js";
4
+ export * from "./nexusAutomationAgentLaunch.js";
5
+ export * from "./nexusAutomationAgentProfile.js";
6
+ export * from "./nexusAutomationAgentSurface.js";
7
+ export * from "./nexusAutomationCommandExecutor.js";
8
+ export * from "./nexusAutomationConfig.js";
9
+ export * from "./nexusAutomationEnqueue.js";
10
+ export * from "./nexusAutomationRunOnce.js";
11
+ export * from "./nexusAutomationScheduler.js";
12
+ export * from "./nexusAutomationStatus.js";
13
+ export * from "./nexusAutomationTarget.js";
14
+ export * from "./nexusAutomationTargetCycle.js";
15
+ export * from "./nexusAutomationTargetReport.js";
16
+ export * from "./nexusCoordination.js";
17
+ export * from "./nexusWorkerContextBundle.js";
18
+ export * from "./nexusAgentMcpConfig.js";
19
+ export * from "./nexusAutomationWorktreeSetup.js";
20
+ export * from "./nexusMcpServer.js";
21
+ export * from "./nexusPluginCapabilities.js";
22
+ export * from "./nexusProjectScaffold.js";
23
+ export * from "./nexusProjectTemplate.js";
24
+ export * from "./processSupervisor.js";
25
+ export * from "./workTrackingGitHubProvider.js";
26
+ export * from "./workTrackingGitLabProvider.js";
27
+ export * from "./workTrackingJiraProvider.js";
28
+ export * from "./workItemService.js";
29
+ export * from "./worktreeExecutionMetadata.js";
30
+ export * from "./nexusExtension.js";
31
+ export * from "./nexusHomeConfig.js";
32
+ export * from "./nexusProjectConfig.js";
33
+ export * from "./nexusProjectHomeService.js";
34
+ export * from "./nexusProjectLifecycle.js";
35
+ export * from "./nexusProjectOperations.js";
36
+ export * from "./nexusProjectRegistry.js";
37
+ export * from "./nexusSkills.js";
38
+ export * from "./workTrackingLocalProvider.js";
39
+ export * from "./workTrackingProviderService.js";
40
+ export * from "./workTrackingTypes.js";
41
+ export * from "./vibeKanbanApi.js";
42
+ export * from "./vibeKanbanAuth.js";
43
+ export * from "./vibeKanbanBoardAdapter.js";
44
+ export * from "./vibeKanbanMcpConfig.js";
45
+ export * from "./vibeKanbanProjectAdapter.js";
46
+ export * from "./vibeKanbanWorkspaceSetup.js";
47
+ export * from "./workTrackingVibeProvider.js";
package/dist/index.js ADDED
@@ -0,0 +1,47 @@
1
+ export * from "./browserOpener.js";
2
+ export * from "./gitWorktreeService.js";
3
+ export * from "./nexusAutomation.js";
4
+ export * from "./nexusAutomationAgentLaunch.js";
5
+ export * from "./nexusAutomationAgentProfile.js";
6
+ export * from "./nexusAutomationAgentSurface.js";
7
+ export * from "./nexusAutomationCommandExecutor.js";
8
+ export * from "./nexusAutomationConfig.js";
9
+ export * from "./nexusAutomationEnqueue.js";
10
+ export * from "./nexusAutomationRunOnce.js";
11
+ export * from "./nexusAutomationScheduler.js";
12
+ export * from "./nexusAutomationStatus.js";
13
+ export * from "./nexusAutomationTarget.js";
14
+ export * from "./nexusAutomationTargetCycle.js";
15
+ export * from "./nexusAutomationTargetReport.js";
16
+ export * from "./nexusCoordination.js";
17
+ export * from "./nexusWorkerContextBundle.js";
18
+ export * from "./nexusAgentMcpConfig.js";
19
+ export * from "./nexusAutomationWorktreeSetup.js";
20
+ export * from "./nexusMcpServer.js";
21
+ export * from "./nexusPluginCapabilities.js";
22
+ export * from "./nexusProjectScaffold.js";
23
+ export * from "./nexusProjectTemplate.js";
24
+ export * from "./processSupervisor.js";
25
+ export * from "./workTrackingGitHubProvider.js";
26
+ export * from "./workTrackingGitLabProvider.js";
27
+ export * from "./workTrackingJiraProvider.js";
28
+ export * from "./workItemService.js";
29
+ export * from "./worktreeExecutionMetadata.js";
30
+ export * from "./nexusExtension.js";
31
+ export * from "./nexusHomeConfig.js";
32
+ export * from "./nexusProjectConfig.js";
33
+ export * from "./nexusProjectHomeService.js";
34
+ export * from "./nexusProjectLifecycle.js";
35
+ export * from "./nexusProjectOperations.js";
36
+ export * from "./nexusProjectRegistry.js";
37
+ export * from "./nexusSkills.js";
38
+ export * from "./workTrackingLocalProvider.js";
39
+ export * from "./workTrackingProviderService.js";
40
+ export * from "./workTrackingTypes.js";
41
+ export * from "./vibeKanbanApi.js";
42
+ export * from "./vibeKanbanAuth.js";
43
+ export * from "./vibeKanbanBoardAdapter.js";
44
+ export * from "./vibeKanbanMcpConfig.js";
45
+ export * from "./vibeKanbanProjectAdapter.js";
46
+ export * from "./vibeKanbanWorkspaceSetup.js";
47
+ export * from "./workTrackingVibeProvider.js";
@@ -0,0 +1,30 @@
1
+ import type { NexusProjectAgentMcpTarget, NexusProjectMcpConfig } from "./nexusProjectConfig.js";
2
+ import type { NexusSkillSourceControl } from "./nexusSkills.js";
3
+ export declare const defaultNexusMcpServerName = "dev_nexus";
4
+ export declare const defaultNexusMcpCommand = "dev-nexus";
5
+ export declare const defaultNexusMcpArgs: readonly ["mcp-stdio"];
6
+ export interface MaterializeNexusProjectAgentMcpConfigOptions {
7
+ projectRoot: string;
8
+ mcpConfig?: NexusProjectMcpConfig;
9
+ agentTargets?: NexusProjectAgentMcpTarget[];
10
+ excludeFromGit?: boolean;
11
+ }
12
+ export interface MaterializedNexusAgentMcpTarget {
13
+ agent: string;
14
+ serverName: string;
15
+ command: string;
16
+ args: string[];
17
+ sourceControl: NexusSkillSourceControl;
18
+ configPath: string;
19
+ configFormat: "toml" | "json";
20
+ }
21
+ export interface MaterializeNexusProjectAgentMcpConfigResult {
22
+ agentTargets: MaterializedNexusAgentMcpTarget[];
23
+ gitExcludePath: string | null;
24
+ gitExcludeEntries: string[];
25
+ }
26
+ export declare class NexusAgentMcpConfigError extends Error {
27
+ constructor(message: string);
28
+ }
29
+ export declare function emptyNexusProjectAgentMcpConfigResult(): MaterializeNexusProjectAgentMcpConfigResult;
30
+ export declare function materializeNexusProjectAgentMcpConfig(options: MaterializeNexusProjectAgentMcpConfigOptions): MaterializeNexusProjectAgentMcpConfigResult;
@@ -0,0 +1,228 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ export const defaultNexusMcpServerName = "dev_nexus";
4
+ export const defaultNexusMcpCommand = "dev-nexus";
5
+ export const defaultNexusMcpArgs = ["mcp-stdio"];
6
+ export class NexusAgentMcpConfigError extends Error {
7
+ constructor(message) {
8
+ super(message);
9
+ this.name = "NexusAgentMcpConfigError";
10
+ }
11
+ }
12
+ export function emptyNexusProjectAgentMcpConfigResult() {
13
+ return {
14
+ agentTargets: [],
15
+ gitExcludePath: null,
16
+ gitExcludeEntries: [],
17
+ };
18
+ }
19
+ export function materializeNexusProjectAgentMcpConfig(options) {
20
+ if (options.mcpConfig?.enabled === false) {
21
+ return emptyNexusProjectAgentMcpConfigResult();
22
+ }
23
+ const projectRoot = path.resolve(options.projectRoot);
24
+ const targets = resolveAgentMcpTargets(projectRoot, options);
25
+ for (const target of targets) {
26
+ if (target.configFormat === "toml") {
27
+ writeCodexMcpConfig(target);
28
+ }
29
+ else {
30
+ writeClaudeMcpConfig(target);
31
+ }
32
+ }
33
+ const supportEntries = targets
34
+ .filter((target) => target.sourceControl === "support")
35
+ .map((target) => gitExcludeEntryForPath(projectRoot, target.configPath));
36
+ const gitExclude = options.excludeFromGit === false
37
+ ? { gitExcludePath: null, gitExcludeEntries: [] }
38
+ : addGitExcludeEntries(projectRoot, supportEntries);
39
+ return {
40
+ agentTargets: targets,
41
+ gitExcludePath: gitExclude.gitExcludePath,
42
+ gitExcludeEntries: gitExclude.gitExcludeEntries,
43
+ };
44
+ }
45
+ function resolveAgentMcpTargets(projectRoot, options) {
46
+ const configuredTargets = options.agentTargets ?? options.mcpConfig?.agentTargets ?? [{ agent: "codex" }];
47
+ return configuredTargets
48
+ .filter((target) => target.enabled !== false)
49
+ .map((target) => {
50
+ const serverName = target.serverName ??
51
+ options.mcpConfig?.serverName ??
52
+ defaultNexusMcpServerName;
53
+ assertTomlBareKey(serverName, `mcp agent target ${target.agent}.serverName`);
54
+ const command = target.command ?? options.mcpConfig?.command ?? defaultNexusMcpCommand;
55
+ const args = [
56
+ ...(target.args ?? options.mcpConfig?.args ?? defaultNexusMcpArgs),
57
+ ];
58
+ const sourceControl = target.sourceControl ?? options.mcpConfig?.sourceControl ?? "support";
59
+ const configFormat = configFormatForAgent(target.agent);
60
+ const configPath = path.join(projectRoot, assertProjectRelativeFilePath(target.configPath ?? defaultConfigPathForAgent(target.agent), `mcp agent target ${target.agent}.configPath`));
61
+ return {
62
+ agent: target.agent,
63
+ serverName,
64
+ command,
65
+ args,
66
+ sourceControl,
67
+ configPath,
68
+ configFormat,
69
+ };
70
+ });
71
+ }
72
+ function configFormatForAgent(agent) {
73
+ if (agent === "codex") {
74
+ return "toml";
75
+ }
76
+ if (agent === "claude") {
77
+ return "json";
78
+ }
79
+ throw new NexusAgentMcpConfigError(`Agent MCP target is not supported: ${agent}`);
80
+ }
81
+ function defaultConfigPathForAgent(agent) {
82
+ if (agent === "codex") {
83
+ return path.join(".codex", "config.toml");
84
+ }
85
+ if (agent === "claude") {
86
+ return ".mcp.json";
87
+ }
88
+ throw new NexusAgentMcpConfigError(`Agent MCP target is not supported: ${agent}`);
89
+ }
90
+ function assertProjectRelativeFilePath(filePath, pathName) {
91
+ if (!filePath ||
92
+ path.isAbsolute(filePath) ||
93
+ filePath
94
+ .split(/[\\/]/u)
95
+ .some((part) => part === ".." || part === "." || part === "")) {
96
+ throw new NexusAgentMcpConfigError(`${pathName} must be a project-relative file path`);
97
+ }
98
+ return filePath;
99
+ }
100
+ function assertTomlBareKey(value, pathName) {
101
+ if (!/^[A-Za-z0-9_-]+$/u.test(value)) {
102
+ throw new NexusAgentMcpConfigError(`${pathName} must contain only letters, digits, underscores, and hyphens`);
103
+ }
104
+ }
105
+ function writeCodexMcpConfig(target) {
106
+ const existing = fs.existsSync(target.configPath)
107
+ ? fs.readFileSync(target.configPath, "utf8")
108
+ : "";
109
+ const nextContent = upsertCodexMcpServerBlock(existing, target);
110
+ fs.mkdirSync(path.dirname(target.configPath), { recursive: true });
111
+ fs.writeFileSync(target.configPath, nextContent, "utf8");
112
+ }
113
+ function upsertCodexMcpServerBlock(existing, target) {
114
+ const retainedLines = removeTomlServerTable(existing, target.serverName);
115
+ const retained = trimTrailingBlankLines(retainedLines).join("\n");
116
+ const serverBlock = [
117
+ `[mcp_servers.${target.serverName}]`,
118
+ `command = ${tomlString(target.command)}`,
119
+ `args = ${tomlStringArray(target.args)}`,
120
+ ].join("\n");
121
+ return retained.length > 0
122
+ ? `${retained}\n\n${serverBlock}\n`
123
+ : `${serverBlock}\n`;
124
+ }
125
+ function removeTomlServerTable(existing, serverName) {
126
+ const lines = existing.replace(/\r\n/gu, "\n").split("\n");
127
+ const retained = [];
128
+ let skipping = false;
129
+ for (const line of lines) {
130
+ const tableName = tomlTableName(line);
131
+ if (tableName) {
132
+ skipping = isMcpServerTable(tableName, serverName);
133
+ if (skipping) {
134
+ continue;
135
+ }
136
+ }
137
+ if (!skipping) {
138
+ retained.push(line);
139
+ }
140
+ }
141
+ return retained;
142
+ }
143
+ function tomlTableName(line) {
144
+ const match = line.match(/^\s*\[([^\]]+)\]\s*(?:#.*)?$/u);
145
+ return match?.[1]?.trim() ?? null;
146
+ }
147
+ function isMcpServerTable(tableName, serverName) {
148
+ return (tableName === `mcp_servers.${serverName}` ||
149
+ tableName.startsWith(`mcp_servers.${serverName}.`));
150
+ }
151
+ function trimTrailingBlankLines(lines) {
152
+ const trimmed = [...lines];
153
+ while (trimmed.length > 0 && trimmed.at(-1)?.trim() === "") {
154
+ trimmed.pop();
155
+ }
156
+ return trimmed;
157
+ }
158
+ function tomlString(value) {
159
+ return JSON.stringify(value);
160
+ }
161
+ function tomlStringArray(values) {
162
+ return `[${values.map((value) => tomlString(value)).join(", ")}]`;
163
+ }
164
+ function writeClaudeMcpConfig(target) {
165
+ const existing = readJsonObject(target.configPath);
166
+ const existingServers = existing.mcpServers;
167
+ if (existingServers !== undefined &&
168
+ (!existingServers ||
169
+ typeof existingServers !== "object" ||
170
+ Array.isArray(existingServers))) {
171
+ throw new NexusAgentMcpConfigError(`Claude MCP config mcpServers must be an object: ${target.configPath}`);
172
+ }
173
+ const mcpServers = {
174
+ ...(existingServers ?? {}),
175
+ [target.serverName]: {
176
+ command: target.command,
177
+ args: target.args,
178
+ },
179
+ };
180
+ fs.mkdirSync(path.dirname(target.configPath), { recursive: true });
181
+ fs.writeFileSync(target.configPath, `${JSON.stringify({ ...existing, mcpServers }, null, 2)}\n`, "utf8");
182
+ }
183
+ function readJsonObject(filePath) {
184
+ if (!fs.existsSync(filePath)) {
185
+ return {};
186
+ }
187
+ const parsed = JSON.parse(fs.readFileSync(filePath, "utf8"));
188
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
189
+ throw new NexusAgentMcpConfigError(`MCP config must be a JSON object: ${filePath}`);
190
+ }
191
+ return parsed;
192
+ }
193
+ function addGitExcludeEntries(projectRoot, entries) {
194
+ const gitInfoDir = path.join(projectRoot, ".git", "info");
195
+ if (!fs.existsSync(gitInfoDir) || !fs.statSync(gitInfoDir).isDirectory()) {
196
+ return {
197
+ gitExcludePath: null,
198
+ gitExcludeEntries: [],
199
+ };
200
+ }
201
+ const excludePath = path.join(gitInfoDir, "exclude");
202
+ const existing = fs.existsSync(excludePath)
203
+ ? fs.readFileSync(excludePath, "utf8")
204
+ : "";
205
+ const existingLines = new Set(existing
206
+ .split(/\r?\n/u)
207
+ .map((line) => line.trim())
208
+ .filter(Boolean));
209
+ const appended = [];
210
+ for (const entry of entries) {
211
+ if (!existingLines.has(entry)) {
212
+ appended.push(entry);
213
+ existingLines.add(entry);
214
+ }
215
+ }
216
+ if (appended.length > 0) {
217
+ fs.mkdirSync(gitInfoDir, { recursive: true });
218
+ const prefix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
219
+ fs.appendFileSync(excludePath, `${prefix}${appended.join("\n")}\n`, "utf8");
220
+ }
221
+ return {
222
+ gitExcludePath: excludePath,
223
+ gitExcludeEntries: appended,
224
+ };
225
+ }
226
+ function gitExcludeEntryForPath(projectRoot, filePath) {
227
+ return path.relative(projectRoot, filePath).replace(/\\/gu, "/");
228
+ }
@@ -0,0 +1,103 @@
1
+ import type { NexusAutomationConfig } from "./nexusAutomationConfig.js";
2
+ import type { WorkItem, WorkItemQuery } from "./workTrackingTypes.js";
3
+ import { type WorktreePublicationDecision, type WorktreeVerificationRecord } from "./worktreeExecutionMetadata.js";
4
+ export type NexusAutomationRunStatus = "started" | "completed" | "failed" | "blocked" | "skipped";
5
+ export interface NexusAutomationRunRecord {
6
+ id: string;
7
+ projectId: string;
8
+ componentId: string | null;
9
+ status: NexusAutomationRunStatus;
10
+ startedAt: string;
11
+ finishedAt: string | null;
12
+ workItemId: string | null;
13
+ workItemTitle: string | null;
14
+ sourceRoot: string | null;
15
+ worktreePath: string | null;
16
+ branchName: string | null;
17
+ baseRef: string | null;
18
+ commitIds: string[];
19
+ summary: string | null;
20
+ verification: WorktreeVerificationRecord[];
21
+ publicationDecision: WorktreePublicationDecision | null;
22
+ error: string | null;
23
+ nextRunNotBefore: string | null;
24
+ }
25
+ export interface NexusAutomationRunRecordInput {
26
+ id: string;
27
+ projectId: string;
28
+ componentId?: string | null;
29
+ status: NexusAutomationRunStatus;
30
+ startedAt?: string;
31
+ finishedAt?: string | null;
32
+ workItemId?: string | null;
33
+ workItemTitle?: string | null;
34
+ sourceRoot?: string | null;
35
+ worktreePath?: string | null;
36
+ branchName?: string | null;
37
+ baseRef?: string | null;
38
+ commitIds?: string[];
39
+ summary?: string | null;
40
+ verification?: WorktreeVerificationRecord[];
41
+ publicationDecision?: WorktreePublicationDecision | null;
42
+ error?: string | null;
43
+ nextRunNotBefore?: string | null;
44
+ }
45
+ export interface NexusAutomationRunLedger {
46
+ version: 1;
47
+ runs: NexusAutomationRunRecord[];
48
+ updatedAt: string | null;
49
+ }
50
+ export interface NexusAutomationRunLock {
51
+ runId: string;
52
+ owner: string | null;
53
+ acquiredAt: string;
54
+ expiresAt: string;
55
+ }
56
+ export interface AcquireNexusAutomationRunLockOptions {
57
+ projectRoot: string;
58
+ config: NexusAutomationConfig;
59
+ runId: string;
60
+ owner?: string | null;
61
+ now?: Date | string;
62
+ }
63
+ export interface AcquireNexusAutomationRunLockResult {
64
+ lockPath: string;
65
+ lock: NexusAutomationRunLock;
66
+ replacedStaleLock: boolean;
67
+ }
68
+ export interface ReleaseNexusAutomationRunLockOptions {
69
+ projectRoot: string;
70
+ config: NexusAutomationConfig;
71
+ runId?: string;
72
+ }
73
+ export interface AppendNexusAutomationRunRecordOptions {
74
+ projectRoot: string;
75
+ config: NexusAutomationConfig;
76
+ record: NexusAutomationRunRecordInput;
77
+ now?: Date | string;
78
+ }
79
+ export interface NexusAutomationBackoffDecision {
80
+ consecutiveFailures: number;
81
+ shouldRun: boolean;
82
+ retryAfter: string | null;
83
+ delayMs: number | null;
84
+ reason: string | null;
85
+ }
86
+ export declare class NexusAutomationError extends Error {
87
+ constructor(message: string);
88
+ }
89
+ export declare function nexusAutomationLedgerPath(projectRoot: string, config: NexusAutomationConfig): string;
90
+ export declare function nexusAutomationLockPath(projectRoot: string, config: NexusAutomationConfig): string;
91
+ export declare function emptyNexusAutomationRunLedger(): NexusAutomationRunLedger;
92
+ export declare function readNexusAutomationRunLedger(projectRoot: string, config: NexusAutomationConfig): NexusAutomationRunLedger;
93
+ export declare function writeNexusAutomationRunLedger(projectRoot: string, config: NexusAutomationConfig, ledger: NexusAutomationRunLedger): string;
94
+ export declare function appendNexusAutomationRunRecord(options: AppendNexusAutomationRunRecordOptions): NexusAutomationRunLedger;
95
+ export declare function normalizeNexusAutomationRunLedger(value: unknown): NexusAutomationRunLedger;
96
+ export declare function acquireNexusAutomationRunLock(options: AcquireNexusAutomationRunLockOptions): AcquireNexusAutomationRunLockResult;
97
+ export declare function releaseNexusAutomationRunLock(options: ReleaseNexusAutomationRunLockOptions): boolean;
98
+ export declare function buildNexusAutomationWorkItemQuery(config: NexusAutomationConfig): WorkItemQuery;
99
+ export declare function selectNexusAutomationWorkItem(items: readonly WorkItem[], config: NexusAutomationConfig): WorkItem | undefined;
100
+ export declare function eligibleNexusAutomationWorkItems(items: readonly WorkItem[], config: NexusAutomationConfig): WorkItem[];
101
+ export declare function evaluateNexusAutomationBackoff(config: NexusAutomationConfig, consecutiveFailures: number, lastFailureAt: Date | string): NexusAutomationBackoffDecision;
102
+ export declare function countConsecutiveNexusAutomationFailures(ledger: NexusAutomationRunLedger): number;
103
+ export declare function evaluateNexusAutomationLedgerBackoff(config: NexusAutomationConfig, ledger: NexusAutomationRunLedger, now?: Date | string): NexusAutomationBackoffDecision;