@nathapp/nax 0.48.2 → 0.48.3
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/nax.js +89 -57
- package/package.json +1 -1
- package/src/cli/generate.ts +36 -27
- package/src/cli/init.ts +0 -1
- package/src/context/generator.ts +66 -36
- package/src/execution/crash-recovery.ts +2 -0
- package/src/execution/crash-signals.ts +15 -0
- package/src/execution/lifecycle/run-setup.ts +5 -0
- package/src/precheck/checks-git.ts +1 -0
package/dist/nax.js
CHANGED
|
@@ -22210,7 +22210,7 @@ var package_default;
|
|
|
22210
22210
|
var init_package = __esm(() => {
|
|
22211
22211
|
package_default = {
|
|
22212
22212
|
name: "@nathapp/nax",
|
|
22213
|
-
version: "0.48.
|
|
22213
|
+
version: "0.48.3",
|
|
22214
22214
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
22215
22215
|
type: "module",
|
|
22216
22216
|
bin: {
|
|
@@ -22283,8 +22283,8 @@ var init_version = __esm(() => {
|
|
|
22283
22283
|
NAX_VERSION = package_default.version;
|
|
22284
22284
|
NAX_COMMIT = (() => {
|
|
22285
22285
|
try {
|
|
22286
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
22287
|
-
return "
|
|
22286
|
+
if (/^[0-9a-f]{6,10}$/.test("fa27043"))
|
|
22287
|
+
return "fa27043";
|
|
22288
22288
|
} catch {}
|
|
22289
22289
|
try {
|
|
22290
22290
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -30597,6 +30597,7 @@ var init_checks_git = __esm(() => {
|
|
|
30597
30597
|
/^.{2} nax\.lock$/,
|
|
30598
30598
|
/^.{2} nax\/metrics\.json$/,
|
|
30599
30599
|
/^.{2} nax\/features\/[^/]+\/status\.json$/,
|
|
30600
|
+
/^.{2} nax\/features\/[^/]+\/prd\.json$/,
|
|
30600
30601
|
/^.{2} nax\/features\/[^/]+\/runs\//,
|
|
30601
30602
|
/^.{2} nax\/features\/[^/]+\/plan\//,
|
|
30602
30603
|
/^.{2} nax\/features\/[^/]+\/acp-sessions\.json$/,
|
|
@@ -31659,6 +31660,9 @@ function createSignalHandler(ctx) {
|
|
|
31659
31660
|
if (ctx.pidRegistry) {
|
|
31660
31661
|
await ctx.pidRegistry.killAll();
|
|
31661
31662
|
}
|
|
31663
|
+
if (ctx.onShutdown) {
|
|
31664
|
+
await ctx.onShutdown().catch(() => {});
|
|
31665
|
+
}
|
|
31662
31666
|
ctx.emitError?.(signal.toLowerCase());
|
|
31663
31667
|
await writeFatalLog(ctx.jsonlFilePath, signal);
|
|
31664
31668
|
await writeRunComplete(ctx, signal.toLowerCase());
|
|
@@ -31677,6 +31681,9 @@ function createUncaughtExceptionHandler(ctx) {
|
|
|
31677
31681
|
if (ctx.pidRegistry) {
|
|
31678
31682
|
await ctx.pidRegistry.killAll();
|
|
31679
31683
|
}
|
|
31684
|
+
if (ctx.onShutdown) {
|
|
31685
|
+
await ctx.onShutdown().catch(() => {});
|
|
31686
|
+
}
|
|
31680
31687
|
ctx.emitError?.("uncaughtException");
|
|
31681
31688
|
await writeFatalLog(ctx.jsonlFilePath, "uncaughtException", error48);
|
|
31682
31689
|
await updateStatusToCrashed(ctx.statusWriter, ctx.getTotalCost(), ctx.getIterations(), "uncaughtException", ctx.featureDir);
|
|
@@ -31694,6 +31701,9 @@ function createUnhandledRejectionHandler(ctx) {
|
|
|
31694
31701
|
if (ctx.pidRegistry) {
|
|
31695
31702
|
await ctx.pidRegistry.killAll();
|
|
31696
31703
|
}
|
|
31704
|
+
if (ctx.onShutdown) {
|
|
31705
|
+
await ctx.onShutdown().catch(() => {});
|
|
31706
|
+
}
|
|
31697
31707
|
ctx.emitError?.("unhandledRejection");
|
|
31698
31708
|
await writeFatalLog(ctx.jsonlFilePath, "unhandledRejection", error48);
|
|
31699
31709
|
await updateStatusToCrashed(ctx.statusWriter, ctx.getTotalCost(), ctx.getIterations(), "unhandledRejection", ctx.featureDir);
|
|
@@ -34965,6 +34975,10 @@ async function setupRun(options) {
|
|
|
34965
34975
|
getStoriesCompleted: options.getStoriesCompleted,
|
|
34966
34976
|
emitError: (reason) => {
|
|
34967
34977
|
pipelineEventBus.emit({ type: "run:errored", reason, feature: options.feature });
|
|
34978
|
+
},
|
|
34979
|
+
onShutdown: async () => {
|
|
34980
|
+
const { sweepFeatureSessions: sweepFeatureSessions2 } = await Promise.resolve().then(() => (init_adapter2(), exports_adapter));
|
|
34981
|
+
await sweepFeatureSessions2(workdir, feature).catch(() => {});
|
|
34968
34982
|
}
|
|
34969
34983
|
});
|
|
34970
34984
|
let prd = await loadPRD(prdPath);
|
|
@@ -66820,10 +66834,11 @@ async function generateFor(agent, options, config2) {
|
|
|
66820
66834
|
return { agent, outputFile: generator.outputFile, content: "", written: false, error: error48 };
|
|
66821
66835
|
}
|
|
66822
66836
|
}
|
|
66823
|
-
async function generateAll(options, config2) {
|
|
66837
|
+
async function generateAll(options, config2, agentFilter) {
|
|
66824
66838
|
const context = await loadContextContent(options, config2);
|
|
66825
66839
|
const results = [];
|
|
66826
|
-
|
|
66840
|
+
const entries = Object.entries(GENERATORS).filter(([agentKey]) => !agentFilter || agentFilter.length === 0 || agentFilter.includes(agentKey));
|
|
66841
|
+
for (const [agentKey, generator] of entries) {
|
|
66827
66842
|
try {
|
|
66828
66843
|
const content = generator.generate(context);
|
|
66829
66844
|
const outputPath = join11(options.outputDir, generator.outputFile);
|
|
@@ -66925,34 +66940,42 @@ async function discoverWorkspacePackages(repoRoot) {
|
|
|
66925
66940
|
async function generateForPackage(packageDir, config2, dryRun = false) {
|
|
66926
66941
|
const contextPath = join11(packageDir, "nax", "context.md");
|
|
66927
66942
|
if (!existsSync10(contextPath)) {
|
|
66928
|
-
return
|
|
66929
|
-
|
|
66930
|
-
|
|
66931
|
-
|
|
66932
|
-
|
|
66933
|
-
|
|
66934
|
-
|
|
66943
|
+
return [
|
|
66944
|
+
{
|
|
66945
|
+
packageDir,
|
|
66946
|
+
outputFile: "CLAUDE.md",
|
|
66947
|
+
content: "",
|
|
66948
|
+
written: false,
|
|
66949
|
+
error: `context.md not found: ${contextPath}`
|
|
66950
|
+
}
|
|
66951
|
+
];
|
|
66935
66952
|
}
|
|
66936
|
-
|
|
66937
|
-
|
|
66938
|
-
|
|
66939
|
-
|
|
66940
|
-
|
|
66941
|
-
|
|
66942
|
-
|
|
66943
|
-
|
|
66944
|
-
|
|
66945
|
-
|
|
66946
|
-
|
|
66947
|
-
|
|
66948
|
-
|
|
66949
|
-
|
|
66950
|
-
|
|
66951
|
-
|
|
66952
|
-
|
|
66953
|
-
|
|
66954
|
-
|
|
66953
|
+
const agentsToGenerate = config2?.generate?.agents && config2.generate.agents.length > 0 ? config2.generate.agents : ["claude"];
|
|
66954
|
+
const options = {
|
|
66955
|
+
contextPath,
|
|
66956
|
+
outputDir: packageDir,
|
|
66957
|
+
workdir: packageDir,
|
|
66958
|
+
dryRun,
|
|
66959
|
+
autoInject: true
|
|
66960
|
+
};
|
|
66961
|
+
const results = [];
|
|
66962
|
+
for (const agent of agentsToGenerate) {
|
|
66963
|
+
try {
|
|
66964
|
+
const result = await generateFor(agent, options, config2);
|
|
66965
|
+
results.push({
|
|
66966
|
+
packageDir,
|
|
66967
|
+
outputFile: result.outputFile,
|
|
66968
|
+
content: result.content,
|
|
66969
|
+
written: result.written,
|
|
66970
|
+
error: result.error
|
|
66971
|
+
});
|
|
66972
|
+
} catch (err) {
|
|
66973
|
+
const error48 = err instanceof Error ? err.message : String(err);
|
|
66974
|
+
const fallbackFile = GENERATORS[agent]?.outputFile ?? `${agent}.md`;
|
|
66975
|
+
results.push({ packageDir, outputFile: fallbackFile, content: "", written: false, error: error48 });
|
|
66976
|
+
}
|
|
66955
66977
|
}
|
|
66978
|
+
return results;
|
|
66956
66979
|
}
|
|
66957
66980
|
|
|
66958
66981
|
// src/cli/plan.ts
|
|
@@ -68773,16 +68796,18 @@ async function generateCommand(options) {
|
|
|
68773
68796
|
console.log(source_default.yellow(" No packages found (no */nax/context.md or */*/nax/context.md)"));
|
|
68774
68797
|
return;
|
|
68775
68798
|
}
|
|
68776
|
-
console.log(source_default.blue(`\u2192 Generating
|
|
68799
|
+
console.log(source_default.blue(`\u2192 Generating agent files for ${packages.length} package(s)...`));
|
|
68777
68800
|
let errorCount = 0;
|
|
68778
68801
|
for (const pkgDir of packages) {
|
|
68779
|
-
const
|
|
68780
|
-
|
|
68781
|
-
|
|
68782
|
-
|
|
68783
|
-
|
|
68784
|
-
|
|
68785
|
-
|
|
68802
|
+
const results = await generateForPackage(pkgDir, config2, dryRun);
|
|
68803
|
+
for (const result of results) {
|
|
68804
|
+
if (result.error) {
|
|
68805
|
+
console.error(source_default.red(`\u2717 ${pkgDir}: ${result.error}`));
|
|
68806
|
+
errorCount++;
|
|
68807
|
+
} else {
|
|
68808
|
+
const suffix = dryRun ? " (dry run)" : "";
|
|
68809
|
+
console.log(source_default.green(`\u2713 ${pkgDir}/${result.outputFile} (${result.content.length} bytes${suffix})`));
|
|
68810
|
+
}
|
|
68786
68811
|
}
|
|
68787
68812
|
}
|
|
68788
68813
|
if (errorCount > 0) {
|
|
@@ -68797,14 +68822,20 @@ async function generateCommand(options) {
|
|
|
68797
68822
|
if (dryRun) {
|
|
68798
68823
|
console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
|
|
68799
68824
|
}
|
|
68800
|
-
console.log(source_default.blue(`\u2192 Generating
|
|
68801
|
-
const
|
|
68802
|
-
|
|
68803
|
-
|
|
68804
|
-
|
|
68825
|
+
console.log(source_default.blue(`\u2192 Generating agent files for package: ${options.package}`));
|
|
68826
|
+
const pkgResults = await generateForPackage(packageDir, config2, dryRun);
|
|
68827
|
+
let pkgHasError = false;
|
|
68828
|
+
for (const result of pkgResults) {
|
|
68829
|
+
if (result.error) {
|
|
68830
|
+
console.error(source_default.red(`\u2717 ${result.error}`));
|
|
68831
|
+
pkgHasError = true;
|
|
68832
|
+
} else {
|
|
68833
|
+
const suffix = dryRun ? " (dry run)" : "";
|
|
68834
|
+
console.log(source_default.green(`\u2713 ${options.package}/${result.outputFile} (${result.content.length} bytes${suffix})`));
|
|
68835
|
+
}
|
|
68805
68836
|
}
|
|
68806
|
-
|
|
68807
|
-
|
|
68837
|
+
if (pkgHasError)
|
|
68838
|
+
process.exit(1);
|
|
68808
68839
|
return;
|
|
68809
68840
|
}
|
|
68810
68841
|
const contextPath = options.context ? join34(workdir, options.context) : join34(workdir, "nax/context.md");
|
|
@@ -68859,8 +68890,7 @@ async function generateCommand(options) {
|
|
|
68859
68890
|
} else {
|
|
68860
68891
|
console.log(source_default.blue("\u2192 Generating configs for all agents..."));
|
|
68861
68892
|
}
|
|
68862
|
-
const
|
|
68863
|
-
const results = agentFilter ? allResults.filter((r) => agentFilter.includes(r.agent)) : allResults;
|
|
68893
|
+
const results = await generateAll(genOptions, config2, agentFilter ?? undefined);
|
|
68864
68894
|
let errorCount = 0;
|
|
68865
68895
|
for (const result of results) {
|
|
68866
68896
|
if (result.error) {
|
|
@@ -68879,17 +68909,19 @@ async function generateCommand(options) {
|
|
|
68879
68909
|
const packages = await discoverPackages(workdir);
|
|
68880
68910
|
if (packages.length > 0) {
|
|
68881
68911
|
console.log(source_default.blue(`
|
|
68882
|
-
\u2192 Discovered ${packages.length} package(s) with nax/context.md \u2014 generating
|
|
68912
|
+
\u2192 Discovered ${packages.length} package(s) with nax/context.md \u2014 generating agent files...`));
|
|
68883
68913
|
let pkgErrorCount = 0;
|
|
68884
68914
|
for (const pkgDir of packages) {
|
|
68885
|
-
const
|
|
68886
|
-
|
|
68887
|
-
|
|
68888
|
-
|
|
68889
|
-
|
|
68890
|
-
|
|
68891
|
-
|
|
68892
|
-
|
|
68915
|
+
const pkgResults = await generateForPackage(pkgDir, config2, dryRun);
|
|
68916
|
+
for (const result of pkgResults) {
|
|
68917
|
+
if (result.error) {
|
|
68918
|
+
console.error(source_default.red(`\u2717 ${pkgDir}: ${result.error}`));
|
|
68919
|
+
pkgErrorCount++;
|
|
68920
|
+
} else {
|
|
68921
|
+
const suffix = dryRun ? " (dry run)" : "";
|
|
68922
|
+
const rel = pkgDir.startsWith(workdir) ? pkgDir.slice(workdir.length + 1) : pkgDir;
|
|
68923
|
+
console.log(source_default.green(`\u2713 ${rel}/${result.outputFile} (${result.content.length} bytes${suffix})`));
|
|
68924
|
+
}
|
|
68893
68925
|
}
|
|
68894
68926
|
}
|
|
68895
68927
|
if (pkgErrorCount > 0) {
|
package/package.json
CHANGED
package/src/cli/generate.ts
CHANGED
|
@@ -67,17 +67,19 @@ export async function generateCommand(options: GenerateCommandOptions): Promise<
|
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
console.log(chalk.blue(`→ Generating
|
|
70
|
+
console.log(chalk.blue(`→ Generating agent files for ${packages.length} package(s)...`));
|
|
71
71
|
let errorCount = 0;
|
|
72
72
|
|
|
73
73
|
for (const pkgDir of packages) {
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
74
|
+
const results = await generateForPackage(pkgDir, config, dryRun);
|
|
75
|
+
for (const result of results) {
|
|
76
|
+
if (result.error) {
|
|
77
|
+
console.error(chalk.red(`✗ ${pkgDir}: ${result.error}`));
|
|
78
|
+
errorCount++;
|
|
79
|
+
} else {
|
|
80
|
+
const suffix = dryRun ? " (dry run)" : "";
|
|
81
|
+
console.log(chalk.green(`✓ ${pkgDir}/${result.outputFile} (${result.content.length} bytes${suffix})`));
|
|
82
|
+
}
|
|
81
83
|
}
|
|
82
84
|
}
|
|
83
85
|
|
|
@@ -94,14 +96,19 @@ export async function generateCommand(options: GenerateCommandOptions): Promise<
|
|
|
94
96
|
if (dryRun) {
|
|
95
97
|
console.log(chalk.yellow("⚠ Dry run — no files will be written"));
|
|
96
98
|
}
|
|
97
|
-
console.log(chalk.blue(`→ Generating
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
console.log(chalk.blue(`→ Generating agent files for package: ${options.package}`));
|
|
100
|
+
const pkgResults = await generateForPackage(packageDir, config, dryRun);
|
|
101
|
+
let pkgHasError = false;
|
|
102
|
+
for (const result of pkgResults) {
|
|
103
|
+
if (result.error) {
|
|
104
|
+
console.error(chalk.red(`✗ ${result.error}`));
|
|
105
|
+
pkgHasError = true;
|
|
106
|
+
} else {
|
|
107
|
+
const suffix = dryRun ? " (dry run)" : "";
|
|
108
|
+
console.log(chalk.green(`✓ ${options.package}/${result.outputFile} (${result.content.length} bytes${suffix})`));
|
|
109
|
+
}
|
|
102
110
|
}
|
|
103
|
-
|
|
104
|
-
console.log(chalk.green(`✓ ${options.package}/${result.outputFile} (${result.content.length} bytes${suffix})`));
|
|
111
|
+
if (pkgHasError) process.exit(1);
|
|
105
112
|
return;
|
|
106
113
|
}
|
|
107
114
|
|
|
@@ -183,8 +190,8 @@ export async function generateCommand(options: GenerateCommandOptions): Promise<
|
|
|
183
190
|
console.log(chalk.blue("→ Generating configs for all agents..."));
|
|
184
191
|
}
|
|
185
192
|
|
|
186
|
-
|
|
187
|
-
const results =
|
|
193
|
+
// Pass agentFilter to generateAll so only matching agents are written to disk
|
|
194
|
+
const results = await generateAll(genOptions, config, agentFilter ?? undefined);
|
|
188
195
|
|
|
189
196
|
let errorCount = 0;
|
|
190
197
|
|
|
@@ -205,22 +212,24 @@ export async function generateCommand(options: GenerateCommandOptions): Promise<
|
|
|
205
212
|
process.exit(1);
|
|
206
213
|
}
|
|
207
214
|
|
|
208
|
-
// Auto-generate per-package
|
|
215
|
+
// Auto-generate per-package agent files when packages with nax/context.md are discovered
|
|
209
216
|
const packages = await discoverPackages(workdir);
|
|
210
217
|
if (packages.length > 0) {
|
|
211
218
|
console.log(
|
|
212
|
-
chalk.blue(`\n→ Discovered ${packages.length} package(s) with nax/context.md — generating
|
|
219
|
+
chalk.blue(`\n→ Discovered ${packages.length} package(s) with nax/context.md — generating agent files...`),
|
|
213
220
|
);
|
|
214
221
|
let pkgErrorCount = 0;
|
|
215
222
|
for (const pkgDir of packages) {
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
223
|
+
const pkgResults = await generateForPackage(pkgDir, config, dryRun);
|
|
224
|
+
for (const result of pkgResults) {
|
|
225
|
+
if (result.error) {
|
|
226
|
+
console.error(chalk.red(`✗ ${pkgDir}: ${result.error}`));
|
|
227
|
+
pkgErrorCount++;
|
|
228
|
+
} else {
|
|
229
|
+
const suffix = dryRun ? " (dry run)" : "";
|
|
230
|
+
const rel = pkgDir.startsWith(workdir) ? pkgDir.slice(workdir.length + 1) : pkgDir;
|
|
231
|
+
console.log(chalk.green(`✓ ${rel}/${result.outputFile} (${result.content.length} bytes${suffix})`));
|
|
232
|
+
}
|
|
224
233
|
}
|
|
225
234
|
}
|
|
226
235
|
if (pkgErrorCount > 0) {
|
package/src/cli/init.ts
CHANGED
package/src/context/generator.ts
CHANGED
|
@@ -100,15 +100,26 @@ async function generateFor(agent: AgentType, options: GenerateOptions, config: N
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
/**
|
|
103
|
-
* Generate configs for all agents.
|
|
103
|
+
* Generate configs for all agents (or a filtered subset).
|
|
104
|
+
*
|
|
105
|
+
* @param agentFilter - Optional list of agent names to generate. When provided,
|
|
106
|
+
* only those agents are written to disk. When omitted, all agents are generated.
|
|
104
107
|
*/
|
|
105
|
-
async function generateAll(
|
|
108
|
+
async function generateAll(
|
|
109
|
+
options: GenerateOptions,
|
|
110
|
+
config: NaxConfig,
|
|
111
|
+
agentFilter?: AgentType[],
|
|
112
|
+
): Promise<GenerationResult[]> {
|
|
106
113
|
// Load context once and share across generators
|
|
107
114
|
const context = await loadContextContent(options, config);
|
|
108
115
|
|
|
109
116
|
const results: GenerationResult[] = [];
|
|
110
117
|
|
|
111
|
-
|
|
118
|
+
const entries = (Object.entries(GENERATORS) as [AgentType, AgentContextGenerator][]).filter(
|
|
119
|
+
([agentKey]) => !agentFilter || agentFilter.length === 0 || agentFilter.includes(agentKey),
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
for (const [agentKey, generator] of entries) {
|
|
112
123
|
try {
|
|
113
124
|
const content = generator.generate(context);
|
|
114
125
|
const outputPath = join(options.outputDir, generator.outputFile);
|
|
@@ -262,51 +273,70 @@ export async function discoverWorkspacePackages(repoRoot: string): Promise<strin
|
|
|
262
273
|
}
|
|
263
274
|
|
|
264
275
|
/**
|
|
265
|
-
* Generate
|
|
276
|
+
* Generate agent config file(s) for a specific package.
|
|
277
|
+
*
|
|
278
|
+
* Reads `<packageDir>/nax/context.md` and writes agent files (e.g. CLAUDE.md,
|
|
279
|
+
* AGENTS.md) into the package directory. Respects `config.generate.agents` — when
|
|
280
|
+
* set, only generates for those agents; defaults to `["claude"]` when unset.
|
|
281
|
+
*
|
|
282
|
+
* Per-package files contain only package-specific content — Claude Code's native
|
|
283
|
+
* directory hierarchy merges root CLAUDE.md + package CLAUDE.md at runtime.
|
|
266
284
|
*
|
|
267
|
-
*
|
|
268
|
-
* Per-package CLAUDE.md contains only package-specific content — Claude Code's
|
|
269
|
-
* native directory hierarchy merges root CLAUDE.md + package CLAUDE.md at runtime.
|
|
285
|
+
* Returns one result per generated agent.
|
|
270
286
|
*/
|
|
271
287
|
export async function generateForPackage(
|
|
272
288
|
packageDir: string,
|
|
273
289
|
config: NaxConfig,
|
|
274
290
|
dryRun = false,
|
|
275
|
-
): Promise<PackageGenerationResult> {
|
|
291
|
+
): Promise<PackageGenerationResult[]> {
|
|
276
292
|
const contextPath = join(packageDir, "nax", "context.md");
|
|
277
293
|
|
|
278
294
|
if (!existsSync(contextPath)) {
|
|
279
|
-
return
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
295
|
+
return [
|
|
296
|
+
{
|
|
297
|
+
packageDir,
|
|
298
|
+
outputFile: "CLAUDE.md",
|
|
299
|
+
content: "",
|
|
300
|
+
written: false,
|
|
301
|
+
error: `context.md not found: ${contextPath}`,
|
|
302
|
+
},
|
|
303
|
+
];
|
|
286
304
|
}
|
|
287
305
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
306
|
+
// Respect config.generate.agents; default to ["claude"] when unset
|
|
307
|
+
const agentsToGenerate: AgentType[] =
|
|
308
|
+
config?.generate?.agents && config.generate.agents.length > 0
|
|
309
|
+
? (config.generate.agents as AgentType[])
|
|
310
|
+
: ["claude"];
|
|
311
|
+
|
|
312
|
+
const options: GenerateOptions = {
|
|
313
|
+
contextPath,
|
|
314
|
+
outputDir: packageDir,
|
|
315
|
+
workdir: packageDir,
|
|
316
|
+
dryRun,
|
|
317
|
+
autoInject: true,
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
const results: PackageGenerationResult[] = [];
|
|
321
|
+
|
|
322
|
+
for (const agent of agentsToGenerate) {
|
|
323
|
+
try {
|
|
324
|
+
const result = await generateFor(agent, options, config);
|
|
325
|
+
results.push({
|
|
326
|
+
packageDir,
|
|
327
|
+
outputFile: result.outputFile,
|
|
328
|
+
content: result.content,
|
|
329
|
+
written: result.written,
|
|
330
|
+
error: result.error,
|
|
331
|
+
});
|
|
332
|
+
} catch (err) {
|
|
333
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
334
|
+
const fallbackFile = GENERATORS[agent]?.outputFile ?? `${agent}.md`;
|
|
335
|
+
results.push({ packageDir, outputFile: fallbackFile, content: "", written: false, error });
|
|
336
|
+
}
|
|
309
337
|
}
|
|
338
|
+
|
|
339
|
+
return results;
|
|
310
340
|
}
|
|
311
341
|
|
|
312
342
|
export { generateFor, generateAll };
|
|
@@ -45,6 +45,8 @@ export interface CrashRecoveryContext {
|
|
|
45
45
|
getTotalStories?: () => number;
|
|
46
46
|
getStoriesCompleted?: () => number;
|
|
47
47
|
emitError?: (reason: string) => void;
|
|
48
|
+
/** Called during graceful shutdown before process.exit — use to close ACP sessions etc. */
|
|
49
|
+
onShutdown?: () => Promise<void>;
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
let handlersInstalled = false;
|
|
@@ -15,6 +15,8 @@ export interface SignalHandlerContext extends RunCompleteContext {
|
|
|
15
15
|
pidRegistry?: PidRegistry;
|
|
16
16
|
featureDir?: string;
|
|
17
17
|
emitError?: (reason: string) => void;
|
|
18
|
+
/** Called during graceful shutdown (signal/exception) before process.exit — use to close ACP sessions etc. */
|
|
19
|
+
onShutdown?: () => Promise<void>;
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
/**
|
|
@@ -46,6 +48,11 @@ function createSignalHandler(ctx: SignalHandlerContext): (signal: NodeJS.Signals
|
|
|
46
48
|
await ctx.pidRegistry.killAll();
|
|
47
49
|
}
|
|
48
50
|
|
|
51
|
+
// Close any open ACP sessions before exiting (prevents orphaned acpx processes)
|
|
52
|
+
if (ctx.onShutdown) {
|
|
53
|
+
await ctx.onShutdown().catch(() => {});
|
|
54
|
+
}
|
|
55
|
+
|
|
49
56
|
ctx.emitError?.(signal.toLowerCase());
|
|
50
57
|
|
|
51
58
|
await writeFatalLog(ctx.jsonlFilePath, signal);
|
|
@@ -72,6 +79,10 @@ function createUncaughtExceptionHandler(ctx: SignalHandlerContext): (error: Erro
|
|
|
72
79
|
await ctx.pidRegistry.killAll();
|
|
73
80
|
}
|
|
74
81
|
|
|
82
|
+
if (ctx.onShutdown) {
|
|
83
|
+
await ctx.onShutdown().catch(() => {});
|
|
84
|
+
}
|
|
85
|
+
|
|
75
86
|
ctx.emitError?.("uncaughtException");
|
|
76
87
|
await writeFatalLog(ctx.jsonlFilePath, "uncaughtException", error);
|
|
77
88
|
await updateStatusToCrashed(
|
|
@@ -102,6 +113,10 @@ function createUnhandledRejectionHandler(ctx: SignalHandlerContext): (reason: un
|
|
|
102
113
|
await ctx.pidRegistry.killAll();
|
|
103
114
|
}
|
|
104
115
|
|
|
116
|
+
if (ctx.onShutdown) {
|
|
117
|
+
await ctx.onShutdown().catch(() => {});
|
|
118
|
+
}
|
|
119
|
+
|
|
105
120
|
ctx.emitError?.("unhandledRejection");
|
|
106
121
|
await writeFatalLog(ctx.jsonlFilePath, "unhandledRejection", error);
|
|
107
122
|
await updateStatusToCrashed(
|
|
@@ -130,6 +130,11 @@ export async function setupRun(options: RunSetupOptions): Promise<RunSetupResult
|
|
|
130
130
|
emitError: (reason: string) => {
|
|
131
131
|
pipelineEventBus.emit({ type: "run:errored", reason, feature: options.feature });
|
|
132
132
|
},
|
|
133
|
+
// Close open ACP sessions on SIGINT/SIGTERM so acpx processes don't stay alive
|
|
134
|
+
onShutdown: async () => {
|
|
135
|
+
const { sweepFeatureSessions } = await import("../../agents/acp/adapter");
|
|
136
|
+
await sweepFeatureSessions(workdir, feature).catch(() => {});
|
|
137
|
+
},
|
|
133
138
|
});
|
|
134
139
|
|
|
135
140
|
// Load PRD (before try block so it's accessible in finally for onRunEnd)
|
|
@@ -40,6 +40,7 @@ const NAX_RUNTIME_PATTERNS = [
|
|
|
40
40
|
/^.{2} nax\.lock$/,
|
|
41
41
|
/^.{2} nax\/metrics\.json$/,
|
|
42
42
|
/^.{2} nax\/features\/[^/]+\/status\.json$/,
|
|
43
|
+
/^.{2} nax\/features\/[^/]+\/prd\.json$/,
|
|
43
44
|
/^.{2} nax\/features\/[^/]+\/runs\//,
|
|
44
45
|
/^.{2} nax\/features\/[^/]+\/plan\//,
|
|
45
46
|
/^.{2} nax\/features\/[^/]+\/acp-sessions\.json$/,
|