@technomoron/agent-run 0.99.27 → 0.99.29
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/CHANGES +22 -0
- package/README.md +106 -52
- package/dist/checks.js +4 -0
- package/dist/cli.js +47 -19
- package/dist/commands.js +110 -14
- package/dist/config-tree.js +254 -2
- package/dist/constants.js +1 -1
- package/dist/defaults.js +34 -3
- package/dist/manifest.js +2 -1
- package/dist/project.js +31 -22
- package/dist/renderer.js +30 -4
- package/dist/templates.js +3 -0
- package/dist/tools.js +6 -0
- package/examples/basic-config/README.md +2 -1
- package/examples/basic-config/agent-config/agent-run.defaults.jsonc +2 -1
- package/examples/basic-config/agent-config/global/tool-templates/AGENTS.md.njk +2 -0
- package/examples/basic-config/agent-config/global/tool-templates/CLAUDE.md.njk +1 -0
- package/examples/basic-config/agent-config/global/tool-templates/claude-settings.json.njk +2 -0
- package/examples/basic-config/agent-config/global/tool-templates/codex-config.toml.njk +2 -1
- package/examples/basic-config/agent-config/starter/basic-project/notes/memory/README.md +8 -0
- package/examples/basic-config/agent-config/starter/basic-project/overrides/claude-settings.json.njk +2 -0
- package/examples/basic-config/agent-config/starter/basic-project/overrides/codex-config.toml.njk +2 -1
- package/ops/systemd/ai-tools-update.service +1 -1
- package/package.json +1 -1
- package/scripts/install-systemd-jobs.sh +15 -5
- package/scripts/update-ai-tools.sh +37 -2
package/dist/commands.js
CHANGED
|
@@ -22,7 +22,10 @@ function dispatch(command) {
|
|
|
22
22
|
runCheck(command);
|
|
23
23
|
return;
|
|
24
24
|
case 'init':
|
|
25
|
-
|
|
25
|
+
runGenerate(command);
|
|
26
|
+
return;
|
|
27
|
+
case 'generate':
|
|
28
|
+
runGenerate(command);
|
|
26
29
|
return;
|
|
27
30
|
case 'setup':
|
|
28
31
|
runSetup(command);
|
|
@@ -56,22 +59,21 @@ function runTool(parsed) {
|
|
|
56
59
|
(0, utils_1.fail)(profileResult.reason);
|
|
57
60
|
}
|
|
58
61
|
const profile = profileResult.profile;
|
|
62
|
+
const configRoot = (0, project_1.defaultConfigRoot)(projectRoot);
|
|
63
|
+
const agentDir = path.join(configRoot, profile);
|
|
59
64
|
if (wrapperArgs.show) {
|
|
60
|
-
const configRoot = (0, project_1.defaultConfigRoot)(projectRoot);
|
|
61
|
-
const agentDir = path.join(configRoot, profile);
|
|
62
65
|
(0, utils_1.verbose)(`profile=${profile} configRoot=${configRoot} agentPath=${agentDir}`);
|
|
63
66
|
requireProfile(command, agentDir);
|
|
64
67
|
showToolProfile(command, projectRoot, agentDir);
|
|
65
68
|
return;
|
|
66
69
|
}
|
|
67
70
|
if (wrapperArgs.create) {
|
|
68
|
-
|
|
71
|
+
runGenerate({ command: 'generate', targetPath: projectRoot });
|
|
69
72
|
}
|
|
70
|
-
const configRoot = (0, project_1.defaultConfigRoot)(projectRoot);
|
|
71
|
-
const agentDir = path.join(configRoot, profile);
|
|
72
73
|
(0, utils_1.verbose)(`profile=${profile} configRoot=${configRoot} agentPath=${agentDir}`);
|
|
73
74
|
prepareConfigRoot(configRoot);
|
|
74
75
|
requireProfile(command, agentDir);
|
|
76
|
+
migrateProfileOnStart(configRoot, agentDir, false);
|
|
75
77
|
const preview = (0, renderer_1.renderProfile)(projectRoot, agentDir, true, command);
|
|
76
78
|
ensureToolEnabled(preview.context, command);
|
|
77
79
|
if (wrapperArgs.generate) {
|
|
@@ -110,7 +112,7 @@ function requireProfile(tool, agentDir) {
|
|
|
110
112
|
}
|
|
111
113
|
const label = tool ?? 'agent';
|
|
112
114
|
process.stderr.write(`agent-run: no ${label} profile found for this project: ${agentDir}\n`);
|
|
113
|
-
process.stderr.write('agent-run: run `agent-run
|
|
115
|
+
process.stderr.write('agent-run: run `agent-run generate` or use the tool with `--create`.\n');
|
|
114
116
|
process.exit(1);
|
|
115
117
|
}
|
|
116
118
|
function checkLocalAiFiles(command, projectRoot, agentDir, context, allowLocal) {
|
|
@@ -146,21 +148,87 @@ function ensureToolEnabled(context, command) {
|
|
|
146
148
|
(0, utils_1.fail)(`${command} is disabled for agent-run profile ${context.profile}`);
|
|
147
149
|
}
|
|
148
150
|
}
|
|
149
|
-
function
|
|
150
|
-
|
|
151
|
+
function runGenerate(parsed) {
|
|
152
|
+
if (parsed.command === 'init') {
|
|
153
|
+
process.stderr.write('agent-run: `init` is deprecated; use `agent-run generate [path]` instead.\n');
|
|
154
|
+
}
|
|
155
|
+
const projectRoot = activeProject(parsed.targetPath, 'generate');
|
|
151
156
|
if (projectRoot === null) {
|
|
152
157
|
return;
|
|
153
158
|
}
|
|
154
159
|
const agentDir = initializeProfileSource(projectRoot, true);
|
|
160
|
+
migrateProfileOnStart((0, project_1.defaultConfigRoot)(projectRoot), agentDir, false);
|
|
155
161
|
printUpdateSummary((0, renderer_1.syncAgentProfile)(projectRoot, agentDir));
|
|
156
162
|
}
|
|
157
163
|
function runSetup(parsed) {
|
|
164
|
+
const projectRoot = (0, project_1.findProjectRoot)(process.cwd());
|
|
165
|
+
const detectedProfile = (0, project_1.resolveProfile)(projectRoot);
|
|
166
|
+
const profile = parsed.profile === null ? confirmProfile(detectedProfile) : requireValidProfile(parsed.profile);
|
|
167
|
+
const configRoot = (0, project_1.defaultConfigRoot)(projectRoot);
|
|
158
168
|
const sourcePath = (0, config_tree_1.starterConfigRootPath)();
|
|
159
169
|
if (!fs.existsSync(sourcePath)) {
|
|
160
170
|
(0, utils_1.fail)(`starter config skeleton not found: ${sourcePath}`);
|
|
161
171
|
}
|
|
162
|
-
(0, config_tree_1.copySkeletonTree)(sourcePath,
|
|
163
|
-
|
|
172
|
+
(0, config_tree_1.copySkeletonTree)(sourcePath, configRoot, new Set(['starter']));
|
|
173
|
+
(0, config_tree_1.ensurePortableSystemdFiles)(configRoot);
|
|
174
|
+
const agentDir = path.join(configRoot, profile);
|
|
175
|
+
migrateProfileOnStart(configRoot, agentDir, false);
|
|
176
|
+
(0, config_tree_1.copySkeletonTree)(path.join(sourcePath, 'starter', 'basic-project'), agentDir);
|
|
177
|
+
if (profile !== detectedProfile) {
|
|
178
|
+
writeProjectProfileMapping(projectRoot, profile);
|
|
179
|
+
}
|
|
180
|
+
printUpdateSummary((0, renderer_1.syncAgentProfile)(projectRoot, agentDir, { configRoot, profile }));
|
|
181
|
+
process.stdout.write(`OK installed default config tree at ${configRoot}\n`);
|
|
182
|
+
}
|
|
183
|
+
function requireValidProfile(value) {
|
|
184
|
+
const parsed = (0, project_1.parseProfile)(value, 'setup profile');
|
|
185
|
+
if (parsed.profile === null) {
|
|
186
|
+
(0, utils_1.fail)(parsed.reason);
|
|
187
|
+
}
|
|
188
|
+
return parsed.profile;
|
|
189
|
+
}
|
|
190
|
+
function confirmProfile(detectedProfile) {
|
|
191
|
+
process.stdout.write(`Detected profile: ${detectedProfile}\n`);
|
|
192
|
+
const confirmation = promptLine('Is this correct? [Y/n] ').trim().toLowerCase();
|
|
193
|
+
if (confirmation === '' || confirmation === 'y' || confirmation === 'yes') {
|
|
194
|
+
return detectedProfile;
|
|
195
|
+
}
|
|
196
|
+
return requireValidProfile(promptLine('Profile (org/repo): '));
|
|
197
|
+
}
|
|
198
|
+
function promptLine(prompt, nonInteractiveMessage = 'setup requires a profile argument when input is not interactive') {
|
|
199
|
+
if (!process.stdin.isTTY) {
|
|
200
|
+
(0, utils_1.fail)(nonInteractiveMessage);
|
|
201
|
+
}
|
|
202
|
+
process.stdout.write(prompt);
|
|
203
|
+
const bytes = [];
|
|
204
|
+
const buffer = Buffer.alloc(1);
|
|
205
|
+
while (fs.readSync(process.stdin.fd, buffer, 0, 1, null) === 1) {
|
|
206
|
+
if (buffer[0] === 10) {
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
if (buffer[0] !== 13) {
|
|
210
|
+
bytes.push(buffer[0] ?? 0);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return Buffer.from(bytes).toString('utf8');
|
|
214
|
+
}
|
|
215
|
+
function writeProjectProfileMapping(projectRoot, profile) {
|
|
216
|
+
const envPath = path.join(projectRoot, constants_1.ENV_FILE_NAME);
|
|
217
|
+
const existing = fs.existsSync(envPath) ? fs.readFileSync(envPath, 'utf8').replace(/\r\n/g, '\n') : '';
|
|
218
|
+
const lines = existing.length > 0 ? existing.replace(/\n+$/, '').split('\n') : [];
|
|
219
|
+
const profileLine = `AGENT_RUN_PROFILE=${profile}`;
|
|
220
|
+
const index = lines.findIndex((line) => /^\s*AGENT_RUN_PROFILE\s*=/.test(line));
|
|
221
|
+
if (index >= 0) {
|
|
222
|
+
lines[index] = profileLine;
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
lines.push(profileLine);
|
|
226
|
+
}
|
|
227
|
+
const content = `${lines.join('\n')}\n`;
|
|
228
|
+
if (content !== existing) {
|
|
229
|
+
fs.writeFileSync(envPath, content, 'utf8');
|
|
230
|
+
(0, utils_1.verbose)(`write ${envPath}`);
|
|
231
|
+
}
|
|
164
232
|
}
|
|
165
233
|
function runEdit(parsed) {
|
|
166
234
|
const projectRoot = activeProject(parsed.targetPath, 'edit');
|
|
@@ -168,6 +236,7 @@ function runEdit(parsed) {
|
|
|
168
236
|
return;
|
|
169
237
|
}
|
|
170
238
|
const agentDir = initializeProfileSource(projectRoot, false);
|
|
239
|
+
migrateProfileOnStart((0, project_1.defaultConfigRoot)(projectRoot), agentDir, false);
|
|
171
240
|
const editPath = (0, manifest_1.createDefaultLocalFile)(agentDir);
|
|
172
241
|
(0, renderer_1.syncAgentProfile)(projectRoot, agentDir);
|
|
173
242
|
process.stdout.write(`Edit: ${editPath}\n`);
|
|
@@ -188,6 +257,7 @@ function runUpdate(parsed) {
|
|
|
188
257
|
const agentDir = (0, project_1.resolveAgentDir)(projectRoot);
|
|
189
258
|
prepareConfigRoot(configRoot);
|
|
190
259
|
requireProfile(null, agentDir);
|
|
260
|
+
migrateProfileOnStart(configRoot, agentDir, false);
|
|
191
261
|
printUpdateSummary((0, renderer_1.syncAgentProfile)(projectRoot, agentDir));
|
|
192
262
|
}
|
|
193
263
|
function runUpdateAll(targetPath) {
|
|
@@ -198,6 +268,7 @@ function runUpdateAll(targetPath) {
|
|
|
198
268
|
prepareConfigRoot(configRoot);
|
|
199
269
|
const profileDirs = (0, config_tree_1.findProfileDirs)(configRoot);
|
|
200
270
|
for (const agentDir of profileDirs) {
|
|
271
|
+
migrateProfileOnStart(configRoot, agentDir, false);
|
|
201
272
|
const profile = path.relative(configRoot, agentDir).replace(/\\/g, '/');
|
|
202
273
|
const projectRoot = (0, project_1.projectRootForProfile)(profile, configRoot);
|
|
203
274
|
(0, renderer_1.syncAgentProfile)(projectRoot, agentDir, { configRoot, profile });
|
|
@@ -215,15 +286,16 @@ function runMigrateConfig(parsed) {
|
|
|
215
286
|
(0, config_tree_1.migrateOldTemplates)(configRoot);
|
|
216
287
|
(0, config_tree_1.ensureDefaultGlobalTemplates)(configRoot);
|
|
217
288
|
(0, manifest_1.ensureRootDefaultsFile)(configRoot);
|
|
218
|
-
const profileDirs = (0, config_tree_1.findLegacyProfileDirs)(configRoot);
|
|
289
|
+
const profileDirs = (0, utils_1.uniqueSorted)([...(0, config_tree_1.findLegacyProfileDirs)(configRoot), ...(0, config_tree_1.findProfileDirs)(configRoot)]);
|
|
219
290
|
let createdManifestCount = 0;
|
|
220
291
|
let createdLocalCount = 0;
|
|
221
292
|
let movedRuntimeCount = 0;
|
|
222
293
|
let movedReviewCount = 0;
|
|
223
294
|
let movedMemoryCount = 0;
|
|
224
295
|
for (const agentDir of profileDirs) {
|
|
296
|
+
const layout = migrateProfileOnStart(configRoot, agentDir, parsed.yes);
|
|
225
297
|
const localPath = path.join(agentDir, constants_1.LOCAL_TEMPLATE_FILE_NAME);
|
|
226
|
-
if (!fs.existsSync(localPath)) {
|
|
298
|
+
if (!fs.existsSync(localPath) && fs.existsSync(path.join(agentDir, 'AGENTS-MODS.md'))) {
|
|
227
299
|
fs.writeFileSync(localPath, (0, config_tree_1.convertLegacyTemplateVars)(fs.readFileSync(path.join(agentDir, 'AGENTS-MODS.md'), 'utf8')), 'utf8');
|
|
228
300
|
createdLocalCount += 1;
|
|
229
301
|
}
|
|
@@ -233,7 +305,7 @@ function runMigrateConfig(parsed) {
|
|
|
233
305
|
}
|
|
234
306
|
movedRuntimeCount += (0, config_tree_1.migrateCodexRuntimeFiles)(agentDir);
|
|
235
307
|
movedReviewCount += (0, config_tree_1.migrateLooseFiles)(agentDir, /^REVIEW(?:-.+)?\.md$/, 'reviews');
|
|
236
|
-
movedMemoryCount +=
|
|
308
|
+
movedMemoryCount += layout.projectMemoryMoves;
|
|
237
309
|
}
|
|
238
310
|
process.stdout.write(`OK migrated ${configRoot}\n`);
|
|
239
311
|
process.stdout.write(`Profiles converted: ${profileDirs.length}\n`);
|
|
@@ -243,6 +315,29 @@ function runMigrateConfig(parsed) {
|
|
|
243
315
|
process.stdout.write(`Moved review files: ${movedReviewCount}\n`);
|
|
244
316
|
process.stdout.write(`Moved memory files: ${movedMemoryCount}\n`);
|
|
245
317
|
}
|
|
318
|
+
function migrateProfileOnStart(configRoot, profileDir, assumeYes) {
|
|
319
|
+
const result = (0, config_tree_1.migrateProfileLayout)(configRoot, profileDir, ({ gitRoot, moves }) => {
|
|
320
|
+
process.stdout.write('agent-run: this profile uses an older project-memory layout.\n');
|
|
321
|
+
process.stdout.write(`Git working tree: ${gitRoot}\n`);
|
|
322
|
+
for (const move of moves) {
|
|
323
|
+
process.stdout.write(` ${path.relative(gitRoot, move.source)} -> ${path.relative(gitRoot, move.target)}\n`);
|
|
324
|
+
}
|
|
325
|
+
if (assumeYes) {
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
const migrateCommand = (0, utils_1.formatCommand)('agent-run', ['migrate-config', '--yes', configRoot]);
|
|
329
|
+
const message = `tracked project-memory files need migration; rerun interactively or run \`${migrateCommand}\``;
|
|
330
|
+
const answer = promptLine('Stage these moves with git mv? [Y/n] ', message).trim().toLowerCase();
|
|
331
|
+
return answer === '' || answer === 'y' || answer === 'yes';
|
|
332
|
+
});
|
|
333
|
+
if (result.gitMoves > 0) {
|
|
334
|
+
process.stdout.write(`Staged Git moves: ${result.gitMoves}\n`);
|
|
335
|
+
}
|
|
336
|
+
if (result.runtimeMoves > 0) {
|
|
337
|
+
process.stdout.write(`Moved local Codex runtime entries: ${result.runtimeMoves}\n`);
|
|
338
|
+
}
|
|
339
|
+
return result;
|
|
340
|
+
}
|
|
246
341
|
function runCheck(parsed) {
|
|
247
342
|
if (parsed.all) {
|
|
248
343
|
const report = (0, checks_1.checkSourceTree)(parsed.targetPath);
|
|
@@ -262,6 +357,7 @@ function prepareConfigRoot(configRoot) {
|
|
|
262
357
|
(0, config_tree_1.ensureConfigRootLayout)(configRoot);
|
|
263
358
|
(0, config_tree_1.ensureConfigRootGitignore)(configRoot);
|
|
264
359
|
(0, config_tree_1.ensureDefaultGlobalTemplates)(configRoot);
|
|
360
|
+
(0, config_tree_1.ensurePortableSystemdFiles)(configRoot);
|
|
265
361
|
}
|
|
266
362
|
function activeProject(targetPath, action) {
|
|
267
363
|
const projectRoot = (0, project_1.findProjectRoot)(targetPath);
|
package/dist/config-tree.js
CHANGED
|
@@ -3,14 +3,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ensureConfigRootLayout = ensureConfigRootLayout;
|
|
4
4
|
exports.ensureConfigRootGitignore = ensureConfigRootGitignore;
|
|
5
5
|
exports.ensureDefaultGlobalTemplates = ensureDefaultGlobalTemplates;
|
|
6
|
+
exports.ensurePortableSystemdFiles = ensurePortableSystemdFiles;
|
|
6
7
|
exports.migrateOldTemplates = migrateOldTemplates;
|
|
7
8
|
exports.findLegacyProfileDirs = findLegacyProfileDirs;
|
|
8
9
|
exports.findProfileDirs = findProfileDirs;
|
|
9
10
|
exports.convertLegacyTemplateVars = convertLegacyTemplateVars;
|
|
10
11
|
exports.migrateCodexRuntimeFiles = migrateCodexRuntimeFiles;
|
|
11
12
|
exports.migrateLooseFiles = migrateLooseFiles;
|
|
13
|
+
exports.describeLegacyProfileLayout = describeLegacyProfileLayout;
|
|
14
|
+
exports.migrateProfileLayout = migrateProfileLayout;
|
|
12
15
|
exports.starterConfigRootPath = starterConfigRootPath;
|
|
13
16
|
exports.copySkeletonTree = copySkeletonTree;
|
|
17
|
+
const childProcess = require("child_process");
|
|
14
18
|
const fs = require("fs");
|
|
15
19
|
const path = require("path");
|
|
16
20
|
const constants_1 = require("./constants");
|
|
@@ -68,6 +72,25 @@ function ensureDefaultGlobalTemplates(configRoot) {
|
|
|
68
72
|
}
|
|
69
73
|
}
|
|
70
74
|
}
|
|
75
|
+
function ensurePortableSystemdFiles(configRoot) {
|
|
76
|
+
const packageRoot = path.resolve(__dirname, '..');
|
|
77
|
+
const files = new Map([
|
|
78
|
+
['install-systemd-jobs.sh', { source: 'scripts/install-systemd-jobs.sh', mode: 0o755 }],
|
|
79
|
+
['update-ai-tools.sh', { source: 'scripts/update-ai-tools.sh', mode: 0o755 }],
|
|
80
|
+
['ai-tools-update.service', { source: 'ops/systemd/ai-tools-update.service', mode: 0o644 }],
|
|
81
|
+
['ai-tools-update.timer', { source: 'ops/systemd/ai-tools-update.timer', mode: 0o644 }]
|
|
82
|
+
]);
|
|
83
|
+
for (const [targetName, asset] of files) {
|
|
84
|
+
const sourcePath = path.join(packageRoot, asset.source);
|
|
85
|
+
const targetPath = path.join(configRoot, targetName);
|
|
86
|
+
if (!fs.existsSync(sourcePath)) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
90
|
+
fs.chmodSync(targetPath, asset.mode);
|
|
91
|
+
(0, utils_1.verbose)(`updated ${targetPath}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
71
94
|
function migrateOldTemplates(configRoot) {
|
|
72
95
|
const oldCodeTemplate = path.join(configRoot, 'templates', 'AGENTS-CODE.md');
|
|
73
96
|
const newCodeTemplate = path.join(configRoot, 'global', 'agents', 'code.md.njk');
|
|
@@ -150,17 +173,246 @@ function migrateLooseFiles(agentDir, pattern, targetDirName) {
|
|
|
150
173
|
}
|
|
151
174
|
return moved;
|
|
152
175
|
}
|
|
176
|
+
function describeLegacyProfileLayout(profileDir) {
|
|
177
|
+
const paths = [];
|
|
178
|
+
const legacyMemoryDir = path.join(profileDir, 'memory');
|
|
179
|
+
if (fs.existsSync(legacyMemoryDir)) {
|
|
180
|
+
paths.push(legacyMemoryDir);
|
|
181
|
+
}
|
|
182
|
+
for (const filePath of legacyProjectMemoryFiles(profileDir)) {
|
|
183
|
+
paths.push(filePath);
|
|
184
|
+
}
|
|
185
|
+
const legacyCodexHome = path.join(profileDir, 'memories', 'codex-home');
|
|
186
|
+
if (fs.existsSync(legacyCodexHome) || (0, utils_1.isSymlink)(legacyCodexHome)) {
|
|
187
|
+
paths.push(legacyCodexHome);
|
|
188
|
+
}
|
|
189
|
+
return [...new Set(paths)].sort();
|
|
190
|
+
}
|
|
191
|
+
function migrateProfileLayout(configRoot, profileDir, confirmGitMoves) {
|
|
192
|
+
const projectMoves = planProjectMemoryMoves(profileDir);
|
|
193
|
+
validateMoveTargets(projectMoves);
|
|
194
|
+
const git = findGitWorktree(configRoot);
|
|
195
|
+
const trackedMoves = git === null ? [] : projectMoves.filter((move) => isTrackedByGit(git, move.source));
|
|
196
|
+
if (git !== null && trackedMoves.length > 0 && !confirmGitMoves({ gitRoot: git.root, moves: trackedMoves })) {
|
|
197
|
+
throw new Error('profile layout migration declined; no files were moved');
|
|
198
|
+
}
|
|
199
|
+
let gitMoves = 0;
|
|
200
|
+
for (const move of projectMoves) {
|
|
201
|
+
fs.mkdirSync(path.dirname(move.target), { recursive: true });
|
|
202
|
+
if (git !== null && trackedMoves.includes(move)) {
|
|
203
|
+
runGitMove(git, move);
|
|
204
|
+
gitMoves += 1;
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
fs.renameSync(move.source, move.target);
|
|
208
|
+
}
|
|
209
|
+
removeEmptyParents(path.dirname(move.source), profileDir);
|
|
210
|
+
(0, utils_1.verbose)(`moved project memory ${move.source} -> ${move.target}`);
|
|
211
|
+
}
|
|
212
|
+
removeEmptyParents(path.join(profileDir, 'memory'), profileDir);
|
|
213
|
+
removeEmptyParents(path.join(profileDir, 'memories'), profileDir);
|
|
214
|
+
const runtimeMoves = migrateLegacyCodexHome(profileDir);
|
|
215
|
+
return { gitMoves, projectMemoryMoves: projectMoves.length, runtimeMoves };
|
|
216
|
+
}
|
|
217
|
+
function planProjectMemoryMoves(profileDir) {
|
|
218
|
+
const targetDir = path.join(profileDir, 'notes', 'memory');
|
|
219
|
+
const moves = [];
|
|
220
|
+
const legacyMemoryDir = path.join(profileDir, 'memory');
|
|
221
|
+
for (const source of listTreeFiles(legacyMemoryDir)) {
|
|
222
|
+
moves.push({ source, target: path.join(targetDir, path.relative(legacyMemoryDir, source)) });
|
|
223
|
+
}
|
|
224
|
+
for (const source of legacyProjectMemoryFiles(profileDir)) {
|
|
225
|
+
moves.push({ source, target: path.join(targetDir, path.basename(source)) });
|
|
226
|
+
}
|
|
227
|
+
return moves.sort((left, right) => left.source.localeCompare(right.source));
|
|
228
|
+
}
|
|
229
|
+
function legacyProjectMemoryFiles(profileDir) {
|
|
230
|
+
const files = [];
|
|
231
|
+
for (const entry of safeReadDir(profileDir)) {
|
|
232
|
+
if (entry.isFile() && /^memory.*\.md$/i.test(entry.name)) {
|
|
233
|
+
files.push(path.join(profileDir, entry.name));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const legacyMemoriesDir = path.join(profileDir, 'memories');
|
|
237
|
+
for (const entry of safeReadDir(legacyMemoriesDir)) {
|
|
238
|
+
if (entry.isFile() && /\.md$/i.test(entry.name)) {
|
|
239
|
+
files.push(path.join(legacyMemoriesDir, entry.name));
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return files.sort();
|
|
243
|
+
}
|
|
244
|
+
function safeReadDir(dir) {
|
|
245
|
+
try {
|
|
246
|
+
return fs.readdirSync(dir, { withFileTypes: true });
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
250
|
+
return [];
|
|
251
|
+
}
|
|
252
|
+
throw error;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function listTreeFiles(root) {
|
|
256
|
+
const files = [];
|
|
257
|
+
for (const entry of safeReadDir(root)) {
|
|
258
|
+
const filePath = path.join(root, entry.name);
|
|
259
|
+
if (entry.isDirectory()) {
|
|
260
|
+
files.push(...listTreeFiles(filePath));
|
|
261
|
+
}
|
|
262
|
+
else if (entry.isFile() || entry.isSymbolicLink()) {
|
|
263
|
+
files.push(filePath);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return files;
|
|
267
|
+
}
|
|
268
|
+
function validateMoveTargets(moves) {
|
|
269
|
+
const targets = new Set();
|
|
270
|
+
for (const move of moves) {
|
|
271
|
+
const target = path.resolve(move.target);
|
|
272
|
+
if (targets.has(target) || fs.existsSync(target) || (0, utils_1.isSymlink)(target)) {
|
|
273
|
+
throw new Error(`cannot migrate project memory because the destination already exists: ${move.target}`);
|
|
274
|
+
}
|
|
275
|
+
targets.add(target);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function findGitWorktree(configRoot) {
|
|
279
|
+
const searchPath = process.env.AGENT_RUN_REAL_PATH ?? process.env.PATH ?? '';
|
|
280
|
+
const gitCommand = findCommandOnPath('git', searchPath);
|
|
281
|
+
if (gitCommand === null) {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
const env = { ...process.env, PATH: searchPath };
|
|
285
|
+
const result = childProcess.spawnSync(gitCommand, ['-C', configRoot, 'rev-parse', '--show-toplevel'], {
|
|
286
|
+
encoding: 'utf8',
|
|
287
|
+
env,
|
|
288
|
+
shell: false
|
|
289
|
+
});
|
|
290
|
+
if (result.status !== 0) {
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
const root = result.stdout.trim();
|
|
294
|
+
return root ? { command: gitCommand, env, root: path.resolve(root) } : null;
|
|
295
|
+
}
|
|
296
|
+
function findCommandOnPath(command, searchPath) {
|
|
297
|
+
const extensions = process.platform === 'win32' ? ['.exe', '.cmd', '.bat', ''] : [''];
|
|
298
|
+
for (const dir of searchPath.split(path.delimiter).filter(Boolean)) {
|
|
299
|
+
for (const extension of extensions) {
|
|
300
|
+
const candidate = path.join(dir, `${command}${extension}`);
|
|
301
|
+
try {
|
|
302
|
+
fs.accessSync(candidate, process.platform === 'win32' ? fs.constants.F_OK : fs.constants.X_OK);
|
|
303
|
+
return candidate;
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
function isTrackedByGit(git, filePath) {
|
|
313
|
+
const relativePath = path.relative(git.root, filePath).replace(/\\/g, '/');
|
|
314
|
+
const result = childProcess.spawnSync(git.command, ['-C', git.root, '--literal-pathspecs', 'ls-files', '--error-unmatch', '--', relativePath], { encoding: 'utf8', env: git.env, shell: false });
|
|
315
|
+
return result.status === 0;
|
|
316
|
+
}
|
|
317
|
+
function runGitMove(git, move) {
|
|
318
|
+
const source = path.relative(git.root, move.source).replace(/\\/g, '/');
|
|
319
|
+
const target = path.relative(git.root, move.target).replace(/\\/g, '/');
|
|
320
|
+
const result = childProcess.spawnSync(git.command, ['-C', git.root, '--literal-pathspecs', 'mv', '--', source, target], {
|
|
321
|
+
encoding: 'utf8',
|
|
322
|
+
env: git.env,
|
|
323
|
+
shell: false
|
|
324
|
+
});
|
|
325
|
+
if (result.status !== 0) {
|
|
326
|
+
const detail = result.stderr.trim() || result.stdout.trim() || `git exited with status ${result.status}`;
|
|
327
|
+
throw new Error(`cannot migrate tracked project memory ${move.source}: ${detail}`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
function migrateLegacyCodexHome(profileDir) {
|
|
331
|
+
const source = path.join(profileDir, 'memories', 'codex-home');
|
|
332
|
+
if (!fs.existsSync(source) && !(0, utils_1.isSymlink)(source)) {
|
|
333
|
+
return 0;
|
|
334
|
+
}
|
|
335
|
+
const target = path.join(profileDir, constants_1.LIVE_DIR_NAME, 'memories', 'codex-home');
|
|
336
|
+
if (!fs.existsSync(target) && !(0, utils_1.isSymlink)(target)) {
|
|
337
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
338
|
+
fs.renameSync(source, target);
|
|
339
|
+
removeEmptyParents(path.dirname(source), profileDir);
|
|
340
|
+
(0, utils_1.verbose)(`moved Codex runtime ${source} -> ${target}`);
|
|
341
|
+
return 1;
|
|
342
|
+
}
|
|
343
|
+
const sourceIsDirectory = !(0, utils_1.isSymlink)(source) && fs.statSync(source).isDirectory();
|
|
344
|
+
const targetIsDirectory = !(0, utils_1.isSymlink)(target) && fs.statSync(target).isDirectory();
|
|
345
|
+
if (!sourceIsDirectory || !targetIsDirectory) {
|
|
346
|
+
const backupTarget = (0, utils_1.nextBackupPath)(target);
|
|
347
|
+
fs.renameSync(source, backupTarget);
|
|
348
|
+
removeEmptyParents(path.dirname(source), profileDir);
|
|
349
|
+
(0, utils_1.verbose)(`moved Codex runtime ${source} -> ${backupTarget}`);
|
|
350
|
+
return 1;
|
|
351
|
+
}
|
|
352
|
+
const moved = mergeRuntimeTree(source, target);
|
|
353
|
+
removeEmptyParents(path.dirname(source), profileDir);
|
|
354
|
+
return moved;
|
|
355
|
+
}
|
|
356
|
+
function mergeRuntimeTree(sourceDir, targetDir) {
|
|
357
|
+
let moved = 0;
|
|
358
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
359
|
+
for (const entry of safeReadDir(sourceDir)) {
|
|
360
|
+
const source = path.join(sourceDir, entry.name);
|
|
361
|
+
let target = path.join(targetDir, entry.name);
|
|
362
|
+
if (entry.isDirectory() && !entry.isSymbolicLink()) {
|
|
363
|
+
const targetIsDirectory = fs.existsSync(target) && !(0, utils_1.isSymlink)(target) && fs.statSync(target).isDirectory();
|
|
364
|
+
if ((fs.existsSync(target) || (0, utils_1.isSymlink)(target)) && !targetIsDirectory) {
|
|
365
|
+
target = (0, utils_1.nextBackupPath)(target);
|
|
366
|
+
}
|
|
367
|
+
moved += mergeRuntimeTree(source, target);
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
const resolvedTarget = fs.existsSync(target) || (0, utils_1.isSymlink)(target) ? (0, utils_1.nextBackupPath)(target) : target;
|
|
371
|
+
fs.mkdirSync(path.dirname(resolvedTarget), { recursive: true });
|
|
372
|
+
fs.renameSync(source, resolvedTarget);
|
|
373
|
+
(0, utils_1.verbose)(`moved Codex runtime ${source} -> ${resolvedTarget}`);
|
|
374
|
+
moved += 1;
|
|
375
|
+
}
|
|
376
|
+
try {
|
|
377
|
+
fs.rmdirSync(sourceDir);
|
|
378
|
+
}
|
|
379
|
+
catch (error) {
|
|
380
|
+
if (!(error instanceof Error && 'code' in error && error.code === 'ENOENT')) {
|
|
381
|
+
throw error;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return moved;
|
|
385
|
+
}
|
|
386
|
+
function removeEmptyParents(startDir, stopDir) {
|
|
387
|
+
let dir = path.resolve(startDir);
|
|
388
|
+
const stop = path.resolve(stopDir);
|
|
389
|
+
while (dir !== stop && (0, utils_1.isSamePathOrDescendant)(dir, stop)) {
|
|
390
|
+
try {
|
|
391
|
+
fs.rmdirSync(dir);
|
|
392
|
+
}
|
|
393
|
+
catch (error) {
|
|
394
|
+
if (error instanceof Error && 'code' in error && ['ENOENT', 'ENOTEMPTY'].includes(String(error.code))) {
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
throw error;
|
|
398
|
+
}
|
|
399
|
+
dir = path.dirname(dir);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
153
402
|
function starterConfigRootPath() {
|
|
154
403
|
return path.resolve(__dirname, '..', 'examples', 'basic-config', 'agent-config');
|
|
155
404
|
}
|
|
156
|
-
function copySkeletonTree(sourceDir, targetDir) {
|
|
405
|
+
function copySkeletonTree(sourceDir, targetDir, excludedNames = new Set()) {
|
|
157
406
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
158
407
|
const entries = fs.readdirSync(sourceDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
159
408
|
for (const entry of entries) {
|
|
409
|
+
if (excludedNames.has(entry.name)) {
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
160
412
|
const sourcePath = path.join(sourceDir, entry.name);
|
|
161
413
|
const targetPath = path.join(targetDir, entry.name === 'gitignore' ? '.gitignore' : entry.name);
|
|
162
414
|
if (entry.isDirectory()) {
|
|
163
|
-
copySkeletonTree(sourcePath, targetPath);
|
|
415
|
+
copySkeletonTree(sourcePath, targetPath, excludedNames);
|
|
164
416
|
}
|
|
165
417
|
else if (entry.isFile() && !fs.existsSync(targetPath)) {
|
|
166
418
|
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
package/dist/constants.js
CHANGED
|
@@ -11,7 +11,7 @@ exports.LIVE_DIR_NAME = 'live';
|
|
|
11
11
|
exports.CONFIG_ROOT_OVERRIDE_ENV = 'AGENT_RUN_CONFIG_ROOT_OVERRIDE';
|
|
12
12
|
exports.CONFIG_DIR_ENV = 'AGENT_CONFIG_DIR';
|
|
13
13
|
exports.VERBOSE_ENV = 'AGENT_RUN_VERBOSE';
|
|
14
|
-
exports.PACKAGE_VERSION = '0.99.
|
|
14
|
+
exports.PACKAGE_VERSION = '0.99.29';
|
|
15
15
|
exports.UNEXPANDED_TEMPLATE_RE = /\{\{[^}]+\}\}|\{%[^%]+%\}/;
|
|
16
16
|
exports.LOCAL_AI_FILE_NAMES = new Set([
|
|
17
17
|
'AGENTS.md',
|
package/dist/defaults.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.defaultManifest = defaultManifest;
|
|
4
4
|
exports.stringifyRootDefaults = stringifyRootDefaults;
|
|
5
5
|
exports.defaultLocalTemplate = defaultLocalTemplate;
|
|
6
|
+
exports.defaultProjectMemoryIndex = defaultProjectMemoryIndex;
|
|
6
7
|
exports.defaultGlobalTemplates = defaultGlobalTemplates;
|
|
7
8
|
exports.defaultToolInstructionsTemplate = defaultToolInstructionsTemplate;
|
|
8
9
|
exports.defaultCodexConfigContent = defaultCodexConfigContent;
|
|
@@ -34,7 +35,8 @@ function defaultManifest(profile) {
|
|
|
34
35
|
changesFile: '{{ projectRoot }}/CHANGES',
|
|
35
36
|
reviewDir: '{{ profileDir }}/reviews',
|
|
36
37
|
reviewFile: '{{ profileDir }}/reviews/REVIEW-{{ date }}.md',
|
|
37
|
-
memoriesDir: '{{ agentDir }}/memories'
|
|
38
|
+
memoriesDir: '{{ agentDir }}/memories',
|
|
39
|
+
projectMemoryDir: '{{ profileDir }}/notes/memory'
|
|
38
40
|
}
|
|
39
41
|
};
|
|
40
42
|
}
|
|
@@ -71,6 +73,19 @@ function defaultLocalTemplate() {
|
|
|
71
73
|
''
|
|
72
74
|
].join('\n');
|
|
73
75
|
}
|
|
76
|
+
function defaultProjectMemoryIndex() {
|
|
77
|
+
return [
|
|
78
|
+
'# Project memory',
|
|
79
|
+
'',
|
|
80
|
+
'This directory contains durable notes for this project across agent sessions and machines.',
|
|
81
|
+
'Keep the notes short, factual, and free of secrets. Link additional topic files from this index.',
|
|
82
|
+
'',
|
|
83
|
+
'## Files',
|
|
84
|
+
'',
|
|
85
|
+
'- Add project memory files here as they are needed.',
|
|
86
|
+
''
|
|
87
|
+
].join('\n');
|
|
88
|
+
}
|
|
74
89
|
function defaultGlobalTemplates() {
|
|
75
90
|
return new Map([
|
|
76
91
|
['global/agents/code.md.njk', defaultCodeAgentTemplate()],
|
|
@@ -293,6 +308,18 @@ function defaultToolInstructionsTemplate(isClaude) {
|
|
|
293
308
|
'{{ paths.memoriesDir }}',
|
|
294
309
|
'```',
|
|
295
310
|
'',
|
|
311
|
+
'Project memory directory:',
|
|
312
|
+
'',
|
|
313
|
+
'```text',
|
|
314
|
+
'{{ paths.projectMemoryDir }}',
|
|
315
|
+
'```',
|
|
316
|
+
'',
|
|
317
|
+
'Native Codex memory directory:',
|
|
318
|
+
'',
|
|
319
|
+
'```text',
|
|
320
|
+
'{{ paths.nativeMemoryDir }}',
|
|
321
|
+
'```',
|
|
322
|
+
'',
|
|
296
323
|
'Global memory directory:',
|
|
297
324
|
'',
|
|
298
325
|
'```text',
|
|
@@ -337,7 +364,8 @@ function defaultCodexConfigTemplate() {
|
|
|
337
364
|
'writable_roots = [',
|
|
338
365
|
' {{ projectRoot | dump }},',
|
|
339
366
|
' {{ paths.reviewDir | dump }},',
|
|
340
|
-
' {{ paths.memoriesDir | dump }}',
|
|
367
|
+
' {{ paths.memoriesDir | dump }},',
|
|
368
|
+
' {{ paths.projectMemoryDir | dump }}',
|
|
341
369
|
']',
|
|
342
370
|
'network_access = false',
|
|
343
371
|
''
|
|
@@ -349,6 +377,7 @@ function defaultClaudeSettingsTemplate() {
|
|
|
349
377
|
' "AGENT_DIR": {{ agentDir | dump }},',
|
|
350
378
|
' "AGENT_PROFILE_DIR": {{ profileDir | dump }},',
|
|
351
379
|
' "AGENT_RUN_PROJECT_ROOT": {{ projectRoot | dump }},',
|
|
380
|
+
' "AGENT_PROJECT_MEMORY_DIR": {{ paths.projectMemoryDir | dump }},',
|
|
352
381
|
' "AGENT_GLOBAL_MEMORY_DIR": {{ paths.globalMemoryDir | dump }}',
|
|
353
382
|
' }'
|
|
354
383
|
].join('\n');
|
|
@@ -377,7 +406,8 @@ function defaultCodexConfigContent(context) {
|
|
|
377
406
|
'writable_roots = [',
|
|
378
407
|
` ${JSON.stringify(context.projectRoot)},`,
|
|
379
408
|
` ${JSON.stringify(context.paths.reviewDir)},`,
|
|
380
|
-
` ${JSON.stringify(context.paths.memoriesDir)}
|
|
409
|
+
` ${JSON.stringify(context.paths.memoriesDir)},`,
|
|
410
|
+
` ${JSON.stringify(context.paths.projectMemoryDir)}`,
|
|
381
411
|
']',
|
|
382
412
|
'network_access = false',
|
|
383
413
|
''
|
|
@@ -389,6 +419,7 @@ function defaultClaudeSettingsContent(context) {
|
|
|
389
419
|
AGENT_DIR: context.agentDir,
|
|
390
420
|
AGENT_PROFILE_DIR: context.profileDir,
|
|
391
421
|
AGENT_RUN_PROJECT_ROOT: context.projectRoot,
|
|
422
|
+
AGENT_PROJECT_MEMORY_DIR: context.paths.projectMemoryDir,
|
|
392
423
|
AGENT_GLOBAL_MEMORY_DIR: context.paths.globalMemoryDir
|
|
393
424
|
}
|
|
394
425
|
};
|
package/dist/manifest.js
CHANGED
|
@@ -165,7 +165,8 @@ function normalizeManifest(manifest, profile) {
|
|
|
165
165
|
reviewDir: normalizeLegacyReviewPath(paths.reviewDir ?? fallbackPaths.reviewDir ?? '{{ profileDir }}/reviews'),
|
|
166
166
|
reviewFile: normalizeLegacyReviewPath(paths.reviewFile ?? fallbackPaths.reviewFile ?? '{{ profileDir }}/reviews/REVIEW-{{ date }}.md'),
|
|
167
167
|
reviewConsolidatedFile: paths.reviewConsolidatedFile ?? fallbackPaths.reviewConsolidatedFile,
|
|
168
|
-
memoriesDir: paths.memoriesDir ?? fallbackPaths.memoriesDir ?? '{{ agentDir }}/memories'
|
|
168
|
+
memoriesDir: paths.memoriesDir ?? fallbackPaths.memoriesDir ?? '{{ agentDir }}/memories',
|
|
169
|
+
projectMemoryDir: paths.projectMemoryDir ?? fallbackPaths.projectMemoryDir ?? '{{ profileDir }}/notes/memory'
|
|
169
170
|
}
|
|
170
171
|
};
|
|
171
172
|
}
|