@c-d-cc/reap 0.1.2 → 0.2.1

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/cli.js CHANGED
@@ -8840,8 +8840,8 @@ var {
8840
8840
  } = import__.default;
8841
8841
 
8842
8842
  // src/cli/commands/init.ts
8843
- import { mkdir as mkdir2 } from "fs/promises";
8844
- import { join as join3 } from "path";
8843
+ import { mkdir as mkdir3 } from "fs/promises";
8844
+ import { join as join4 } from "path";
8845
8845
 
8846
8846
  // src/core/paths.ts
8847
8847
  import { join, dirname } from "path";
@@ -9001,167 +9001,408 @@ class ConfigManager {
9001
9001
  }
9002
9002
  }
9003
9003
 
9004
- // src/core/hooks.ts
9004
+ // src/core/agents/claude-code.ts
9005
9005
  import { join as join2 } from "path";
9006
- import { mkdir, unlink } from "fs/promises";
9007
- function getReapHookEntry() {
9008
- const sessionStartPath = join2(ReapPaths.packageHooksDir, "session-start.sh");
9009
- return {
9010
- matcher: "",
9011
- hooks: [
9012
- {
9013
- type: "command",
9014
- command: `bash "${sessionStartPath}"`
9015
- }
9016
- ]
9017
- };
9018
- }
9019
- async function readSettingsJson() {
9020
- const fileContent = await readTextFile(ReapPaths.userClaudeSettingsJson);
9021
- if (fileContent === null)
9022
- return {};
9023
- try {
9024
- return JSON.parse(fileContent);
9025
- } catch {
9026
- return {};
9006
+ import { homedir as homedir2 } from "os";
9007
+ import { mkdir, readdir, unlink } from "fs/promises";
9008
+ class ClaudeCodeAdapter {
9009
+ name = "claude-code";
9010
+ displayName = "Claude Code";
9011
+ get userDir() {
9012
+ return join2(homedir2(), ".claude");
9027
9013
  }
9028
- }
9029
- async function writeSettingsJson(settings) {
9030
- await mkdir(ReapPaths.userClaudeDir, { recursive: true });
9031
- await writeTextFile(ReapPaths.userClaudeSettingsJson, JSON.stringify(settings, null, 2) + `
9032
- `);
9033
- }
9034
- function hasReapHookInArray(sessionStartHooks) {
9035
- return sessionStartHooks.some((entry) => {
9036
- if (typeof entry !== "object" || entry === null)
9037
- return false;
9038
- const hooks = entry["hooks"];
9039
- if (!Array.isArray(hooks))
9014
+ get commandsDir() {
9015
+ return join2(this.userDir, "commands");
9016
+ }
9017
+ get settingsPath() {
9018
+ return join2(this.userDir, "settings.json");
9019
+ }
9020
+ get hooksJsonPath() {
9021
+ return join2(this.userDir, "hooks.json");
9022
+ }
9023
+ async detect() {
9024
+ try {
9025
+ const { execSync } = await import("child_process");
9026
+ execSync("which claude", { stdio: "ignore" });
9027
+ return true;
9028
+ } catch {
9040
9029
  return false;
9041
- return hooks.some((h) => {
9042
- if (typeof h !== "object" || h === null)
9043
- return false;
9044
- const cmd = h["command"];
9045
- return typeof cmd === "string" && cmd.includes("session-start");
9046
- });
9047
- });
9048
- }
9049
- async function registerClaudeHook(dryRun = false) {
9050
- await mkdir(ReapPaths.userClaudeDir, { recursive: true });
9051
- const settings = await readSettingsJson();
9052
- const hooks = settings["hooks"] ?? {};
9053
- const sessionStartHooks = hooks["SessionStart"] ?? [];
9054
- if (Array.isArray(sessionStartHooks) && hasReapHookInArray(sessionStartHooks)) {
9055
- return { action: "skipped" };
9056
- }
9057
- const hadHooksSection = "hooks" in settings;
9058
- settings["hooks"] = {
9059
- ...hooks,
9060
- SessionStart: [
9061
- ...Array.isArray(sessionStartHooks) ? sessionStartHooks : [],
9062
- getReapHookEntry()
9063
- ]
9064
- };
9065
- if (!dryRun) {
9066
- await writeSettingsJson(settings);
9030
+ }
9067
9031
  }
9068
- return { action: hadHooksSection ? "updated" : "created" };
9069
- }
9070
- async function syncHookRegistration(dryRun = false) {
9071
- const settings = await readSettingsJson();
9072
- const hooks = settings["hooks"] ?? {};
9073
- const sessionStartHooks = hooks["SessionStart"] ?? [];
9074
- if (!Array.isArray(sessionStartHooks) || !hasReapHookInArray(sessionStartHooks)) {
9075
- await registerClaudeHook(dryRun);
9076
- return { action: "updated" };
9077
- }
9078
- const expectedEntry = getReapHookEntry();
9079
- let changed = false;
9080
- const updated = sessionStartHooks.map((entry) => {
9081
- if (typeof entry !== "object" || entry === null)
9082
- return entry;
9083
- const entryHooks = entry["hooks"];
9084
- if (!Array.isArray(entryHooks))
9032
+ getCommandsDir() {
9033
+ return this.commandsDir;
9034
+ }
9035
+ async installCommands(commandNames, sourceDir) {
9036
+ await mkdir(this.commandsDir, { recursive: true });
9037
+ for (const cmd of commandNames) {
9038
+ const src = join2(sourceDir, `${cmd}.md`);
9039
+ const dest = join2(this.commandsDir, `${cmd}.md`);
9040
+ await writeTextFile(dest, await readTextFileOrThrow(src));
9041
+ }
9042
+ }
9043
+ async removeStaleCommands(validNames) {
9044
+ try {
9045
+ const existing = await readdir(this.commandsDir);
9046
+ for (const file of existing) {
9047
+ if (!file.startsWith("reap.") || !file.endsWith(".md"))
9048
+ continue;
9049
+ if (!validNames.has(file)) {
9050
+ await unlink(join2(this.commandsDir, file));
9051
+ }
9052
+ }
9053
+ } catch {}
9054
+ }
9055
+ async registerSessionHook(dryRun = false) {
9056
+ await mkdir(this.userDir, { recursive: true });
9057
+ const settings = await this.readSettings();
9058
+ const hooks = settings["hooks"] ?? {};
9059
+ const sessionStartHooks = hooks["SessionStart"] ?? [];
9060
+ if (Array.isArray(sessionStartHooks) && this.hasReapHook(sessionStartHooks)) {
9061
+ return { action: "skipped" };
9062
+ }
9063
+ const hadHooksSection = "hooks" in settings;
9064
+ settings["hooks"] = {
9065
+ ...hooks,
9066
+ SessionStart: [
9067
+ ...Array.isArray(sessionStartHooks) ? sessionStartHooks : [],
9068
+ this.getHookEntry()
9069
+ ]
9070
+ };
9071
+ if (!dryRun) {
9072
+ await this.writeSettings(settings);
9073
+ }
9074
+ return { action: hadHooksSection ? "updated" : "created" };
9075
+ }
9076
+ async syncSessionHook(dryRun = false) {
9077
+ const settings = await this.readSettings();
9078
+ const hooks = settings["hooks"] ?? {};
9079
+ const sessionStartHooks = hooks["SessionStart"] ?? [];
9080
+ if (!Array.isArray(sessionStartHooks) || !this.hasReapHook(sessionStartHooks)) {
9081
+ await this.registerSessionHook(dryRun);
9082
+ return { action: "updated" };
9083
+ }
9084
+ const expectedEntry = this.getHookEntry();
9085
+ let changed = false;
9086
+ const updated = sessionStartHooks.map((entry) => {
9087
+ if (typeof entry !== "object" || entry === null)
9088
+ return entry;
9089
+ const entryHooks = entry["hooks"];
9090
+ if (!Array.isArray(entryHooks))
9091
+ return entry;
9092
+ const isReap = entryHooks.some((h) => {
9093
+ if (typeof h !== "object" || h === null)
9094
+ return false;
9095
+ const cmd = h["command"];
9096
+ return typeof cmd === "string" && cmd.includes("session-start");
9097
+ });
9098
+ if (isReap) {
9099
+ if (JSON.stringify(entry) !== JSON.stringify(expectedEntry)) {
9100
+ changed = true;
9101
+ return expectedEntry;
9102
+ }
9103
+ }
9085
9104
  return entry;
9086
- const isReap = entryHooks.some((h) => {
9087
- if (typeof h !== "object" || h === null)
9088
- return false;
9089
- const cmd = h["command"];
9090
- return typeof cmd === "string" && cmd.includes("session-start");
9091
9105
  });
9092
- if (isReap) {
9093
- const currentJson = JSON.stringify(entry);
9094
- const expectedJson = JSON.stringify(expectedEntry);
9095
- if (currentJson !== expectedJson) {
9096
- changed = true;
9097
- return expectedEntry;
9098
- }
9106
+ if (changed && !dryRun) {
9107
+ settings["hooks"] = { ...hooks, SessionStart: updated };
9108
+ await this.writeSettings(settings);
9099
9109
  }
9100
- return entry;
9101
- });
9102
- if (changed && !dryRun) {
9103
- settings["hooks"] = { ...hooks, SessionStart: updated };
9104
- await writeSettingsJson(settings);
9110
+ return { action: changed ? "updated" : "skipped" };
9105
9111
  }
9106
- return { action: changed ? "updated" : "skipped" };
9107
- }
9108
- async function migrateHooksJsonToSettings(dryRun = false) {
9109
- const hooksJsonPath = ReapPaths.userClaudeHooksJson;
9110
- const fileContent = await readTextFile(hooksJsonPath);
9111
- if (fileContent === null)
9112
- return { action: "skipped" };
9113
- let hooksJson;
9114
- try {
9115
- hooksJson = JSON.parse(fileContent);
9116
- } catch {
9117
- return { action: "skipped" };
9112
+ async readLanguage() {
9113
+ const content = await readTextFile(this.settingsPath);
9114
+ if (!content)
9115
+ return null;
9116
+ try {
9117
+ const settings = JSON.parse(content);
9118
+ return settings.language ?? null;
9119
+ } catch {
9120
+ return null;
9121
+ }
9118
9122
  }
9119
- const sessionStartHooks = hooksJson["SessionStart"];
9120
- if (!Array.isArray(sessionStartHooks))
9121
- return { action: "skipped" };
9122
- const reapEntries = sessionStartHooks.filter((entry) => {
9123
- if (typeof entry !== "object" || entry === null)
9124
- return false;
9125
- const hooks = entry["hooks"];
9126
- if (!Array.isArray(hooks))
9127
- return false;
9128
- return hooks.some((h) => {
9129
- if (typeof h !== "object" || h === null)
9123
+ async migrate(dryRun = false) {
9124
+ const fileContent = await readTextFile(this.hooksJsonPath);
9125
+ if (fileContent === null)
9126
+ return { action: "skipped" };
9127
+ let hooksJson;
9128
+ try {
9129
+ hooksJson = JSON.parse(fileContent);
9130
+ } catch {
9131
+ return { action: "skipped" };
9132
+ }
9133
+ const sessionStartHooks = hooksJson["SessionStart"];
9134
+ if (!Array.isArray(sessionStartHooks))
9135
+ return { action: "skipped" };
9136
+ const reapEntries = sessionStartHooks.filter((entry) => {
9137
+ if (typeof entry !== "object" || entry === null)
9130
9138
  return false;
9131
- const cmd = h["command"];
9132
- return typeof cmd === "string" && cmd.includes("session-start");
9139
+ const hooks = entry["hooks"];
9140
+ if (!Array.isArray(hooks))
9141
+ return false;
9142
+ return hooks.some((h) => {
9143
+ if (typeof h !== "object" || h === null)
9144
+ return false;
9145
+ const cmd = h["command"];
9146
+ return typeof cmd === "string" && cmd.includes("session-start");
9147
+ });
9133
9148
  });
9134
- });
9135
- if (reapEntries.length === 0)
9136
- return { action: "skipped" };
9137
- if (!dryRun) {
9138
- await registerClaudeHook(false);
9139
- const filtered = sessionStartHooks.filter((entry) => {
9149
+ if (reapEntries.length === 0)
9150
+ return { action: "skipped" };
9151
+ if (!dryRun) {
9152
+ await this.registerSessionHook(false);
9153
+ const filtered = sessionStartHooks.filter((entry) => {
9154
+ if (typeof entry !== "object" || entry === null)
9155
+ return true;
9156
+ const hooks = entry["hooks"];
9157
+ if (!Array.isArray(hooks))
9158
+ return true;
9159
+ return !hooks.some((h) => {
9160
+ if (typeof h !== "object" || h === null)
9161
+ return false;
9162
+ const cmd = h["command"];
9163
+ return typeof cmd === "string" && cmd.includes("session-start");
9164
+ });
9165
+ });
9166
+ if (filtered.length === 0) {
9167
+ delete hooksJson["SessionStart"];
9168
+ } else {
9169
+ hooksJson["SessionStart"] = filtered;
9170
+ }
9171
+ if (Object.keys(hooksJson).length === 0) {
9172
+ await unlink(this.hooksJsonPath);
9173
+ } else {
9174
+ await writeTextFile(this.hooksJsonPath, JSON.stringify(hooksJson, null, 2) + `
9175
+ `);
9176
+ }
9177
+ }
9178
+ return { action: "migrated" };
9179
+ }
9180
+ getHookEntry() {
9181
+ const sessionStartPath = join2(ReapPaths.packageHooksDir, "session-start.sh");
9182
+ return {
9183
+ matcher: "",
9184
+ hooks: [{ type: "command", command: `bash "${sessionStartPath}"` }]
9185
+ };
9186
+ }
9187
+ hasReapHook(sessionStartHooks) {
9188
+ return sessionStartHooks.some((entry) => {
9140
9189
  if (typeof entry !== "object" || entry === null)
9141
- return true;
9190
+ return false;
9142
9191
  const hooks = entry["hooks"];
9143
9192
  if (!Array.isArray(hooks))
9144
- return true;
9145
- return !hooks.some((h) => {
9193
+ return false;
9194
+ return hooks.some((h) => {
9146
9195
  if (typeof h !== "object" || h === null)
9147
9196
  return false;
9148
9197
  const cmd = h["command"];
9149
9198
  return typeof cmd === "string" && cmd.includes("session-start");
9150
9199
  });
9151
9200
  });
9152
- if (filtered.length === 0) {
9153
- delete hooksJson["SessionStart"];
9154
- } else {
9155
- hooksJson["SessionStart"] = filtered;
9201
+ }
9202
+ async readSettings() {
9203
+ const content = await readTextFile(this.settingsPath);
9204
+ if (content === null)
9205
+ return {};
9206
+ try {
9207
+ return JSON.parse(content);
9208
+ } catch {
9209
+ return {};
9210
+ }
9211
+ }
9212
+ async writeSettings(settings) {
9213
+ await mkdir(this.userDir, { recursive: true });
9214
+ await writeTextFile(this.settingsPath, JSON.stringify(settings, null, 2) + `
9215
+ `);
9216
+ }
9217
+ }
9218
+
9219
+ // src/core/agents/opencode.ts
9220
+ import { join as join3 } from "path";
9221
+ import { homedir as homedir3 } from "os";
9222
+ import { mkdir as mkdir2, readdir as readdir2, unlink as unlink2 } from "fs/promises";
9223
+ var OPENCODE_STATE_DIR = join3(homedir3(), ".local", "state", "opencode");
9224
+
9225
+ class OpenCodeAdapter {
9226
+ name = "opencode";
9227
+ displayName = "OpenCode";
9228
+ get configDir() {
9229
+ return join3(homedir3(), ".config", "opencode");
9230
+ }
9231
+ get commandsDir() {
9232
+ return join3(this.configDir, "commands");
9233
+ }
9234
+ get pluginsDir() {
9235
+ return join3(this.configDir, "plugins");
9236
+ }
9237
+ get settingsPath() {
9238
+ return join3(this.configDir, "opencode.json");
9239
+ }
9240
+ get tuiConfigPath() {
9241
+ return join3(this.configDir, "tui.json");
9242
+ }
9243
+ async detect() {
9244
+ try {
9245
+ const { execSync } = await import("child_process");
9246
+ execSync("which opencode", { stdio: "ignore" });
9247
+ return true;
9248
+ } catch {
9249
+ return false;
9250
+ }
9251
+ }
9252
+ getCommandsDir() {
9253
+ return this.commandsDir;
9254
+ }
9255
+ async installCommands(commandNames, sourceDir) {
9256
+ await mkdir2(this.commandsDir, { recursive: true });
9257
+ for (const cmd of commandNames) {
9258
+ const src = join3(sourceDir, `${cmd}.md`);
9259
+ const dest = join3(this.commandsDir, `${cmd}.md`);
9260
+ await writeTextFile(dest, await readTextFileOrThrow(src));
9156
9261
  }
9157
- if (Object.keys(hooksJson).length === 0) {
9158
- await unlink(hooksJsonPath);
9262
+ }
9263
+ async removeStaleCommands(validNames) {
9264
+ try {
9265
+ const existing = await readdir2(this.commandsDir);
9266
+ for (const file of existing) {
9267
+ if (!file.startsWith("reap.") || !file.endsWith(".md"))
9268
+ continue;
9269
+ if (!validNames.has(file)) {
9270
+ await unlink2(join3(this.commandsDir, file));
9271
+ }
9272
+ }
9273
+ } catch {}
9274
+ }
9275
+ async registerSessionHook(dryRun = false) {
9276
+ const pluginPath = join3(this.pluginsDir, "reap-session-start.js");
9277
+ const exists = await fileExists(pluginPath);
9278
+ let pluginAction = "skipped";
9279
+ if (exists) {
9280
+ const current = await readTextFile(pluginPath);
9281
+ const expected = await this.getPluginContent();
9282
+ if (current !== expected) {
9283
+ if (!dryRun)
9284
+ await writeTextFile(pluginPath, expected);
9285
+ pluginAction = "updated";
9286
+ }
9159
9287
  } else {
9160
- await writeTextFile(hooksJsonPath, JSON.stringify(hooksJson, null, 2) + `
9288
+ if (!dryRun) {
9289
+ await mkdir2(this.pluginsDir, { recursive: true });
9290
+ await writeTextFile(pluginPath, await this.getPluginContent());
9291
+ }
9292
+ pluginAction = "created";
9293
+ }
9294
+ if (!dryRun) {
9295
+ await this.ensureTuiConfig();
9296
+ await this.ensureDefaultVisibility();
9297
+ }
9298
+ return { action: pluginAction };
9299
+ }
9300
+ async syncSessionHook(dryRun = false) {
9301
+ const result = await this.registerSessionHook(dryRun);
9302
+ return { action: result.action === "skipped" ? "skipped" : "updated" };
9303
+ }
9304
+ async readLanguage() {
9305
+ const content = await readTextFile(this.settingsPath);
9306
+ if (!content)
9307
+ return null;
9308
+ try {
9309
+ const settings = JSON.parse(content);
9310
+ return settings.language ?? null;
9311
+ } catch {
9312
+ return null;
9313
+ }
9314
+ }
9315
+ async getPluginContent() {
9316
+ const templatePath = join3(ReapPaths.packageHooksDir, "opencode-session-start.js");
9317
+ return readTextFileOrThrow(templatePath);
9318
+ }
9319
+ async ensureDefaultVisibility() {
9320
+ const kvPath = join3(OPENCODE_STATE_DIR, "kv.json");
9321
+ await mkdir2(OPENCODE_STATE_DIR, { recursive: true });
9322
+ let kv = {};
9323
+ const existing = await readTextFile(kvPath);
9324
+ if (existing) {
9325
+ try {
9326
+ kv = JSON.parse(existing);
9327
+ } catch {}
9328
+ }
9329
+ let changed = false;
9330
+ if (kv["tool_details_visibility"] === undefined) {
9331
+ kv["tool_details_visibility"] = false;
9332
+ changed = true;
9333
+ }
9334
+ if (changed) {
9335
+ await writeTextFile(kvPath, JSON.stringify(kv, null, 2) + `
9336
+ `);
9337
+ }
9338
+ }
9339
+ async ensureTuiConfig() {
9340
+ await mkdir2(this.configDir, { recursive: true });
9341
+ const reapKeybinds = {
9342
+ display_thinking: "<leader>t",
9343
+ tool_details: "<leader>d"
9344
+ };
9345
+ let config = {};
9346
+ const existing = await readTextFile(this.tuiConfigPath);
9347
+ if (existing) {
9348
+ try {
9349
+ config = JSON.parse(existing);
9350
+ } catch {}
9351
+ }
9352
+ const keybinds = config["keybinds"] ?? {};
9353
+ let changed = false;
9354
+ for (const [key, value] of Object.entries(reapKeybinds)) {
9355
+ if (!keybinds[key] || keybinds[key] === "none") {
9356
+ keybinds[key] = value;
9357
+ changed = true;
9358
+ }
9359
+ }
9360
+ if (changed) {
9361
+ config["keybinds"] = keybinds;
9362
+ if (!config["$schema"]) {
9363
+ config = { $schema: "https://opencode.ai/tui.json", ...config };
9364
+ }
9365
+ await writeTextFile(this.tuiConfigPath, JSON.stringify(config, null, 2) + `
9161
9366
  `);
9162
9367
  }
9163
9368
  }
9164
- return { action: "migrated" };
9369
+ }
9370
+
9371
+ // src/core/agents/index.ts
9372
+ var ALL_ADAPTERS = [
9373
+ new ClaudeCodeAdapter,
9374
+ new OpenCodeAdapter
9375
+ ];
9376
+
9377
+ class AgentRegistry {
9378
+ static allAdapters() {
9379
+ return ALL_ADAPTERS;
9380
+ }
9381
+ static getAdapter(name) {
9382
+ return ALL_ADAPTERS.find((a) => a.name === name);
9383
+ }
9384
+ static async detectInstalled() {
9385
+ const results = await Promise.all(ALL_ADAPTERS.map(async (adapter) => ({
9386
+ adapter,
9387
+ installed: await adapter.detect()
9388
+ })));
9389
+ return results.filter((r) => r.installed).map((r) => r.adapter);
9390
+ }
9391
+ static async getActiveAdapters(config) {
9392
+ if (config?.agents && config.agents.length > 0) {
9393
+ return config.agents.map((name) => AgentRegistry.getAdapter(name)).filter((a) => a !== undefined);
9394
+ }
9395
+ return AgentRegistry.detectInstalled();
9396
+ }
9397
+ static async readLanguage(adapters) {
9398
+ const list = adapters ?? ALL_ADAPTERS;
9399
+ for (const adapter of list) {
9400
+ const lang = await adapter.readLanguage();
9401
+ if (lang)
9402
+ return lang;
9403
+ }
9404
+ return null;
9405
+ }
9165
9406
  }
9166
9407
 
9167
9408
  // src/cli/commands/init.ts
@@ -9177,26 +9418,30 @@ var COMMAND_NAMES = [
9177
9418
  "reap.back",
9178
9419
  "reap.status",
9179
9420
  "reap.sync",
9180
- "reap.help"
9421
+ "reap.help",
9422
+ "reap.update"
9181
9423
  ];
9182
- async function initProject(projectRoot, projectName, entryMode, preset) {
9424
+ async function initProject(projectRoot, projectName, entryMode, preset, onProgress) {
9425
+ const log = onProgress ?? (() => {});
9183
9426
  const paths = new ReapPaths(projectRoot);
9184
9427
  if (await paths.isReapProject()) {
9185
9428
  throw new Error(".reap/ already exists. This is already a REAP project.");
9186
9429
  }
9187
9430
  if (preset) {
9188
- const presetDir = join3(ReapPaths.packageTemplatesDir, "presets", preset);
9189
- const presetExists = await fileExists(join3(presetDir, "principles.md"));
9431
+ const presetDir = join4(ReapPaths.packageTemplatesDir, "presets", preset);
9432
+ const presetExists = await fileExists(join4(presetDir, "principles.md"));
9190
9433
  if (!presetExists) {
9191
9434
  throw new Error(`Unknown preset: "${preset}". Available presets: bun-hono-react`);
9192
9435
  }
9193
9436
  }
9194
- await mkdir2(paths.genome, { recursive: true });
9195
- await mkdir2(paths.domain, { recursive: true });
9196
- await mkdir2(paths.environment, { recursive: true });
9197
- await mkdir2(paths.life, { recursive: true });
9198
- await mkdir2(paths.backlog, { recursive: true });
9199
- await mkdir2(paths.lineage, { recursive: true });
9437
+ log("Creating .reap/ directory structure...");
9438
+ await mkdir3(paths.genome, { recursive: true });
9439
+ await mkdir3(paths.domain, { recursive: true });
9440
+ await mkdir3(paths.environment, { recursive: true });
9441
+ await mkdir3(paths.life, { recursive: true });
9442
+ await mkdir3(paths.backlog, { recursive: true });
9443
+ await mkdir3(paths.lineage, { recursive: true });
9444
+ log("Writing config.yml...");
9200
9445
  const config = {
9201
9446
  version: "0.1.0",
9202
9447
  project: projectName,
@@ -9204,76 +9449,97 @@ async function initProject(projectRoot, projectName, entryMode, preset) {
9204
9449
  ...preset && { preset }
9205
9450
  };
9206
9451
  await ConfigManager.write(paths, config);
9452
+ log("Setting up Genome templates...");
9207
9453
  const genomeTemplates = ["principles.md", "conventions.md", "constraints.md"];
9208
- const genomeSourceDir = preset ? join3(ReapPaths.packageTemplatesDir, "presets", preset) : ReapPaths.packageGenomeDir;
9454
+ const genomeSourceDir = preset ? join4(ReapPaths.packageTemplatesDir, "presets", preset) : ReapPaths.packageGenomeDir;
9209
9455
  for (const file of genomeTemplates) {
9210
- const src = join3(genomeSourceDir, file);
9211
- const dest = join3(paths.genome, file);
9456
+ const src = join4(genomeSourceDir, file);
9457
+ const dest = join4(paths.genome, file);
9212
9458
  await writeTextFile(dest, await readTextFileOrThrow(src));
9213
9459
  }
9214
- await mkdir2(ReapPaths.userReapTemplates, { recursive: true });
9460
+ log("Installing artifact templates...");
9461
+ await mkdir3(ReapPaths.userReapTemplates, { recursive: true });
9215
9462
  const artifactFiles = ["01-objective.md", "02-planning.md", "03-implementation.md", "04-validation.md", "05-completion.md"];
9216
9463
  for (const file of artifactFiles) {
9217
- const src = join3(ReapPaths.packageArtifactsDir, file);
9218
- const dest = join3(ReapPaths.userReapTemplates, file);
9464
+ const src = join4(ReapPaths.packageArtifactsDir, file);
9465
+ const dest = join4(ReapPaths.userReapTemplates, file);
9219
9466
  await writeTextFile(dest, await readTextFileOrThrow(src));
9220
9467
  }
9221
- const domainGuideSrc = join3(ReapPaths.packageGenomeDir, "domain/README.md");
9222
- const domainGuideDest = join3(ReapPaths.userReapTemplates, "domain-guide.md");
9468
+ const domainGuideSrc = join4(ReapPaths.packageGenomeDir, "domain/README.md");
9469
+ const domainGuideDest = join4(ReapPaths.userReapTemplates, "domain-guide.md");
9223
9470
  await writeTextFile(domainGuideDest, await readTextFileOrThrow(domainGuideSrc));
9224
- await mkdir2(ReapPaths.userClaudeCommands, { recursive: true });
9225
- for (const cmd of COMMAND_NAMES) {
9226
- const src = join3(ReapPaths.packageCommandsDir, `${cmd}.md`);
9227
- const dest = join3(ReapPaths.userClaudeCommands, `${cmd}.md`);
9228
- await writeTextFile(dest, await readTextFileOrThrow(src));
9471
+ log("Detecting AI agents...");
9472
+ const detectedAgents = await AgentRegistry.detectInstalled();
9473
+ const sourceDir = ReapPaths.packageCommandsDir;
9474
+ for (const adapter of detectedAgents) {
9475
+ log(` Installing commands for ${adapter.displayName}...`);
9476
+ await adapter.installCommands(COMMAND_NAMES, sourceDir);
9477
+ log(` Registering session hook for ${adapter.displayName}...`);
9478
+ await adapter.registerSessionHook();
9479
+ }
9480
+ if (detectedAgents.length === 0) {
9481
+ log(" No AI agents detected.");
9482
+ }
9483
+ return { agents: detectedAgents.map((a) => a.displayName) };
9484
+ }
9485
+
9486
+ // src/cli/commands/update.ts
9487
+ import { readdir as readdir3, unlink as unlink3, rm, mkdir as mkdir4 } from "fs/promises";
9488
+ import { join as join5 } from "path";
9489
+
9490
+ // src/core/hooks.ts
9491
+ async function migrateHooks(dryRun = false) {
9492
+ const adapters = AgentRegistry.allAdapters();
9493
+ const results = [];
9494
+ for (const adapter of adapters) {
9495
+ if (adapter.migrate) {
9496
+ const result = await adapter.migrate(dryRun);
9497
+ results.push({ agent: adapter.displayName, action: result.action });
9498
+ }
9229
9499
  }
9230
- await registerClaudeHook();
9500
+ return { results };
9231
9501
  }
9232
9502
 
9233
9503
  // src/cli/commands/update.ts
9234
- import { readdir, unlink as unlink2, rm, mkdir as mkdir3 } from "fs/promises";
9235
- import { join as join4 } from "path";
9236
9504
  async function updateProject(projectRoot, dryRun = false) {
9237
9505
  const paths = new ReapPaths(projectRoot);
9238
9506
  if (!await paths.isReapProject()) {
9239
9507
  throw new Error("Not a REAP project. Run 'reap init' first.");
9240
9508
  }
9241
9509
  const result = { updated: [], skipped: [], removed: [] };
9242
- await mkdir3(ReapPaths.userClaudeCommands, { recursive: true });
9510
+ const config = await ConfigManager.read(paths);
9511
+ const adapters = await AgentRegistry.getActiveAdapters(config ?? undefined);
9243
9512
  const commandsDir = ReapPaths.packageCommandsDir;
9244
- const commandFiles = await readdir(commandsDir);
9245
- for (const file of commandFiles) {
9246
- if (!file.endsWith(".md"))
9247
- continue;
9248
- const src = await readTextFileOrThrow(join4(commandsDir, file));
9249
- const dest = join4(ReapPaths.userClaudeCommands, file);
9250
- const existingContent = await readTextFile(dest);
9251
- if (existingContent !== null && existingContent === src) {
9252
- result.skipped.push(`~/.claude/commands/${file}`);
9253
- } else {
9254
- if (!dryRun)
9255
- await writeTextFile(dest, src);
9256
- result.updated.push(`~/.claude/commands/${file}`);
9257
- }
9258
- }
9259
- const validCommandFiles = new Set(commandFiles);
9260
- try {
9261
- const existing = await readdir(ReapPaths.userClaudeCommands);
9262
- for (const file of existing) {
9263
- if (!file.startsWith("reap.") || !file.endsWith(".md"))
9513
+ const commandFiles = await readdir3(commandsDir);
9514
+ for (const adapter of adapters) {
9515
+ const agentCmdDir = adapter.getCommandsDir();
9516
+ const label = `${adapter.displayName}`;
9517
+ for (const file of commandFiles) {
9518
+ if (!file.endsWith(".md"))
9264
9519
  continue;
9265
- if (!validCommandFiles.has(file)) {
9266
- if (!dryRun)
9267
- await unlink2(join4(ReapPaths.userClaudeCommands, file));
9268
- result.removed.push(`~/.claude/commands/${file}`);
9520
+ const src = await readTextFileOrThrow(join5(commandsDir, file));
9521
+ const dest = join5(agentCmdDir, file);
9522
+ const existingContent = await readTextFile(dest);
9523
+ if (existingContent !== null && existingContent === src) {
9524
+ result.skipped.push(`[${label}] commands/${file}`);
9525
+ } else {
9526
+ if (!dryRun) {
9527
+ await mkdir4(agentCmdDir, { recursive: true });
9528
+ await writeTextFile(dest, src);
9529
+ }
9530
+ result.updated.push(`[${label}] commands/${file}`);
9269
9531
  }
9270
9532
  }
9271
- } catch {}
9272
- await mkdir3(ReapPaths.userReapTemplates, { recursive: true });
9533
+ const validCommandFiles = new Set(commandFiles);
9534
+ if (!dryRun) {
9535
+ await adapter.removeStaleCommands(validCommandFiles);
9536
+ }
9537
+ }
9538
+ await mkdir4(ReapPaths.userReapTemplates, { recursive: true });
9273
9539
  const artifactFiles = ["01-objective.md", "02-planning.md", "03-implementation.md", "04-validation.md", "05-completion.md"];
9274
9540
  for (const file of artifactFiles) {
9275
- const src = await readTextFileOrThrow(join4(ReapPaths.packageArtifactsDir, file));
9276
- const dest = join4(ReapPaths.userReapTemplates, file);
9541
+ const src = await readTextFileOrThrow(join5(ReapPaths.packageArtifactsDir, file));
9542
+ const dest = join5(ReapPaths.userReapTemplates, file);
9277
9543
  const existingContent = await readTextFile(dest);
9278
9544
  if (existingContent !== null && existingContent === src) {
9279
9545
  result.skipped.push(`~/.reap/templates/${file}`);
@@ -9283,8 +9549,8 @@ async function updateProject(projectRoot, dryRun = false) {
9283
9549
  result.updated.push(`~/.reap/templates/${file}`);
9284
9550
  }
9285
9551
  }
9286
- const domainGuideSrc = await readTextFileOrThrow(join4(ReapPaths.packageGenomeDir, "domain/README.md"));
9287
- const domainGuideDest = join4(ReapPaths.userReapTemplates, "domain-guide.md");
9552
+ const domainGuideSrc = await readTextFileOrThrow(join5(ReapPaths.packageGenomeDir, "domain/README.md"));
9553
+ const domainGuideDest = join5(ReapPaths.userReapTemplates, "domain-guide.md");
9288
9554
  const domainExistingContent = await readTextFile(domainGuideDest);
9289
9555
  if (domainExistingContent !== null && domainExistingContent === domainGuideSrc) {
9290
9556
  result.skipped.push(`~/.reap/templates/domain-guide.md`);
@@ -9293,15 +9559,19 @@ async function updateProject(projectRoot, dryRun = false) {
9293
9559
  await writeTextFile(domainGuideDest, domainGuideSrc);
9294
9560
  result.updated.push(`~/.reap/templates/domain-guide.md`);
9295
9561
  }
9296
- const migration = await migrateHooksJsonToSettings(dryRun);
9297
- if (migration.action === "migrated") {
9298
- result.updated.push(`~/.claude/settings.json (migrated from hooks.json)`);
9562
+ const migrations = await migrateHooks(dryRun);
9563
+ for (const m of migrations.results) {
9564
+ if (m.action === "migrated") {
9565
+ result.updated.push(`[${m.agent}] hooks (migrated)`);
9566
+ }
9299
9567
  }
9300
- const hookReg = await syncHookRegistration(dryRun);
9301
- if (hookReg.action === "updated") {
9302
- result.updated.push(`~/.claude/settings.json (hooks)`);
9303
- } else {
9304
- result.skipped.push(`~/.claude/settings.json (hooks)`);
9568
+ for (const adapter of adapters) {
9569
+ const hookResult = await adapter.syncSessionHook(dryRun);
9570
+ if (hookResult.action === "updated") {
9571
+ result.updated.push(`[${adapter.displayName}] session hook`);
9572
+ } else {
9573
+ result.skipped.push(`[${adapter.displayName}] session hook`);
9574
+ }
9305
9575
  }
9306
9576
  await migrateLegacyFiles(paths, dryRun, result);
9307
9577
  return result;
@@ -9312,11 +9582,11 @@ async function migrateLegacyFiles(paths, dryRun, result) {
9312
9582
  await removeDirIfExists(paths.legacyHooks, ".reap/hooks/", dryRun, result);
9313
9583
  try {
9314
9584
  const claudeCmdDir = paths.legacyClaudeCommands;
9315
- const files = await readdir(claudeCmdDir);
9585
+ const files = await readdir3(claudeCmdDir);
9316
9586
  for (const file of files) {
9317
9587
  if (file.startsWith("reap.") && file.endsWith(".md")) {
9318
9588
  if (!dryRun)
9319
- await unlink2(join4(claudeCmdDir, file));
9589
+ await unlink3(join5(claudeCmdDir, file));
9320
9590
  result.removed.push(`.claude/commands/${file}`);
9321
9591
  }
9322
9592
  }
@@ -9344,7 +9614,7 @@ async function migrateLegacyFiles(paths, dryRun, result) {
9344
9614
  if (filtered.length !== sessionStart.length) {
9345
9615
  if (!dryRun) {
9346
9616
  if (filtered.length === 0 && Object.keys(content).length === 1) {
9347
- await unlink2(legacyHooksJson);
9617
+ await unlink3(legacyHooksJson);
9348
9618
  result.removed.push(`.claude/hooks.json (legacy)`);
9349
9619
  } else {
9350
9620
  content["SessionStart"] = filtered;
@@ -9360,7 +9630,7 @@ async function migrateLegacyFiles(paths, dryRun, result) {
9360
9630
  }
9361
9631
  async function removeDirIfExists(dirPath, label, dryRun, result) {
9362
9632
  try {
9363
- const entries = await readdir(dirPath);
9633
+ const entries = await readdir3(dirPath);
9364
9634
  if (entries.length > 0 || true) {
9365
9635
  if (!dryRun)
9366
9636
  await rm(dirPath, { recursive: true });
@@ -9371,8 +9641,8 @@ async function removeDirIfExists(dirPath, label, dryRun, result) {
9371
9641
 
9372
9642
  // src/core/generation.ts
9373
9643
  var import_yaml2 = __toESM(require_dist(), 1);
9374
- import { readdir as readdir3, mkdir as mkdir4, rename } from "fs/promises";
9375
- import { join as join6 } from "path";
9644
+ import { readdir as readdir5, mkdir as mkdir5, rename } from "fs/promises";
9645
+ import { join as join7 } from "path";
9376
9646
 
9377
9647
  // src/types/index.ts
9378
9648
  var LIFECYCLE_ORDER = [
@@ -9426,8 +9696,8 @@ class LifeCycle {
9426
9696
  }
9427
9697
 
9428
9698
  // src/core/compression.ts
9429
- import { readdir as readdir2, rm as rm2 } from "fs/promises";
9430
- import { join as join5 } from "path";
9699
+ import { readdir as readdir4, rm as rm2 } from "fs/promises";
9700
+ import { join as join6 } from "path";
9431
9701
  var LINEAGE_MAX_LINES = 1e4;
9432
9702
  var MIN_GENERATIONS_FOR_COMPRESSION = 5;
9433
9703
  var LEVEL1_MAX_LINES = 40;
@@ -9443,9 +9713,9 @@ async function countLines(filePath) {
9443
9713
  async function countDirLines(dirPath) {
9444
9714
  let total = 0;
9445
9715
  try {
9446
- const entries = await readdir2(dirPath, { withFileTypes: true });
9716
+ const entries = await readdir4(dirPath, { withFileTypes: true });
9447
9717
  for (const entry of entries) {
9448
- const fullPath = join5(dirPath, entry.name);
9718
+ const fullPath = join6(dirPath, entry.name);
9449
9719
  if (entry.isFile() && entry.name.endsWith(".md")) {
9450
9720
  total += await countLines(fullPath);
9451
9721
  } else if (entry.isDirectory()) {
@@ -9458,9 +9728,9 @@ async function countDirLines(dirPath) {
9458
9728
  async function scanLineage(paths) {
9459
9729
  const entries = [];
9460
9730
  try {
9461
- const items = await readdir2(paths.lineage, { withFileTypes: true });
9731
+ const items = await readdir4(paths.lineage, { withFileTypes: true });
9462
9732
  for (const item of items) {
9463
- const fullPath = join5(paths.lineage, item.name);
9733
+ const fullPath = join6(paths.lineage, item.name);
9464
9734
  if (item.isDirectory() && item.name.startsWith("gen-")) {
9465
9735
  const genNum = parseInt(item.name.replace("gen-", ""), 10);
9466
9736
  const lines = await countDirLines(fullPath);
@@ -9481,7 +9751,7 @@ async function compressLevel1(genDir, genName) {
9481
9751
  const lines = [];
9482
9752
  let goal = "", completionConditions = "";
9483
9753
  {
9484
- const objective = await readTextFile(join5(genDir, "01-objective.md"));
9754
+ const objective = await readTextFile(join6(genDir, "01-objective.md"));
9485
9755
  if (objective) {
9486
9756
  const goalMatch = objective.match(/## Goal\n([\s\S]*?)(?=\n##)/);
9487
9757
  if (goalMatch)
@@ -9493,7 +9763,7 @@ async function compressLevel1(genDir, genName) {
9493
9763
  }
9494
9764
  let lessons = "", genomeChanges = "", nextBacklog = "";
9495
9765
  {
9496
- const completion = await readTextFile(join5(genDir, "05-completion.md"));
9766
+ const completion = await readTextFile(join6(genDir, "05-completion.md"));
9497
9767
  if (completion) {
9498
9768
  const lessonsMatch = completion.match(/### Lessons Learned\n([\s\S]*?)(?=\n###)/);
9499
9769
  if (lessonsMatch)
@@ -9508,7 +9778,7 @@ async function compressLevel1(genDir, genName) {
9508
9778
  }
9509
9779
  let metadata = "";
9510
9780
  {
9511
- const completion = await readTextFile(join5(genDir, "05-completion.md"));
9781
+ const completion = await readTextFile(join6(genDir, "05-completion.md"));
9512
9782
  if (completion) {
9513
9783
  const summaryMatch = completion.match(/## Summary\n([\s\S]*?)(?=\n##)/);
9514
9784
  if (summaryMatch)
@@ -9517,7 +9787,7 @@ async function compressLevel1(genDir, genName) {
9517
9787
  }
9518
9788
  let validationResult = "";
9519
9789
  {
9520
- const validation = await readTextFile(join5(genDir, "04-validation.md"));
9790
+ const validation = await readTextFile(join6(genDir, "04-validation.md"));
9521
9791
  if (validation) {
9522
9792
  const resultMatch = validation.match(/## Result: (.+)/);
9523
9793
  if (resultMatch)
@@ -9526,7 +9796,7 @@ async function compressLevel1(genDir, genName) {
9526
9796
  }
9527
9797
  let deferred = "";
9528
9798
  {
9529
- const impl = await readTextFile(join5(genDir, "03-implementation.md"));
9799
+ const impl = await readTextFile(join6(genDir, "03-implementation.md"));
9530
9800
  if (impl) {
9531
9801
  const deferredMatch = impl.match(/## Deferred Tasks\n([\s\S]*?)(?=\n##)/);
9532
9802
  if (deferredMatch) {
@@ -9646,10 +9916,10 @@ async function compressLineageIfNeeded(paths) {
9646
9916
  const currentTotal = await countDirLines(paths.lineage);
9647
9917
  if (currentTotal <= LINEAGE_MAX_LINES)
9648
9918
  break;
9649
- const dirPath = join5(paths.lineage, dir.name);
9919
+ const dirPath = join6(paths.lineage, dir.name);
9650
9920
  const compressed = await compressLevel1(dirPath, dir.name);
9651
9921
  const genId = dir.name.match(/^gen-\d+/)?.[0] ?? dir.name;
9652
- const outPath = join5(paths.lineage, `${genId}.md`);
9922
+ const outPath = join6(paths.lineage, `${genId}.md`);
9653
9923
  await writeTextFile(outPath, compressed);
9654
9924
  await rm2(dirPath, { recursive: true });
9655
9925
  result.level1.push(genId);
@@ -9663,10 +9933,10 @@ async function compressLineageIfNeeded(paths) {
9663
9933
  const batch = level1s.slice(i * LEVEL2_BATCH_SIZE, (i + 1) * LEVEL2_BATCH_SIZE);
9664
9934
  const files = batch.map((e) => ({
9665
9935
  name: e.name,
9666
- path: join5(paths.lineage, e.name)
9936
+ path: join6(paths.lineage, e.name)
9667
9937
  }));
9668
9938
  const compressed = await compressLevel2(files, epochNum);
9669
- const outPath = join5(paths.lineage, `epoch-${String(epochNum).padStart(3, "0")}.md`);
9939
+ const outPath = join6(paths.lineage, `epoch-${String(epochNum).padStart(3, "0")}.md`);
9670
9940
  await writeTextFile(outPath, compressed);
9671
9941
  for (const file of files) {
9672
9942
  await rm2(file.path);
@@ -9727,28 +9997,28 @@ class GenerationManager {
9727
9997
  const goalSlug = state.goal.toLowerCase().replace(/[^a-z0-9가-힣]+/g, "-").replace(/^-|-$/g, "").slice(0, 30);
9728
9998
  const genDirName = `${state.id}-${goalSlug}`;
9729
9999
  const genDir = this.paths.generationDir(genDirName);
9730
- await mkdir4(genDir, { recursive: true });
9731
- const lifeEntries = await readdir3(this.paths.life);
10000
+ await mkdir5(genDir, { recursive: true });
10001
+ const lifeEntries = await readdir5(this.paths.life);
9732
10002
  for (const entry of lifeEntries) {
9733
10003
  if (/^\d{2}-[a-z]+(?:-[a-z]+)*\.md$/.test(entry)) {
9734
- await rename(join6(this.paths.life, entry), join6(genDir, entry));
10004
+ await rename(join7(this.paths.life, entry), join7(genDir, entry));
9735
10005
  }
9736
10006
  }
9737
- const backlogDir = join6(genDir, "backlog");
9738
- await mkdir4(backlogDir, { recursive: true });
10007
+ const backlogDir = join7(genDir, "backlog");
10008
+ await mkdir5(backlogDir, { recursive: true });
9739
10009
  try {
9740
- const backlogEntries = await readdir3(this.paths.backlog);
10010
+ const backlogEntries = await readdir5(this.paths.backlog);
9741
10011
  for (const entry of backlogEntries) {
9742
- await rename(join6(this.paths.backlog, entry), join6(backlogDir, entry));
10012
+ await rename(join7(this.paths.backlog, entry), join7(backlogDir, entry));
9743
10013
  }
9744
10014
  } catch {}
9745
10015
  try {
9746
- const mutEntries = await readdir3(this.paths.mutations);
10016
+ const mutEntries = await readdir5(this.paths.mutations);
9747
10017
  if (mutEntries.length > 0) {
9748
- const mutDir = join6(genDir, "mutations");
9749
- await mkdir4(mutDir, { recursive: true });
10018
+ const mutDir = join7(genDir, "mutations");
10019
+ await mkdir5(mutDir, { recursive: true });
9750
10020
  for (const entry of mutEntries) {
9751
- await rename(join6(this.paths.mutations, entry), join6(mutDir, entry));
10021
+ await rename(join7(this.paths.mutations, entry), join7(mutDir, entry));
9752
10022
  }
9753
10023
  }
9754
10024
  } catch {}
@@ -9761,7 +10031,7 @@ class GenerationManager {
9761
10031
  }
9762
10032
  async listCompleted() {
9763
10033
  try {
9764
- const entries = await readdir3(this.paths.lineage);
10034
+ const entries = await readdir5(this.paths.lineage);
9765
10035
  return entries.filter((e) => e.startsWith("gen-")).sort();
9766
10036
  } catch {
9767
10037
  return [];
@@ -9806,7 +10076,7 @@ async function getStatus(projectRoot) {
9806
10076
 
9807
10077
  // src/cli/commands/fix.ts
9808
10078
  var import_yaml3 = __toESM(require_dist(), 1);
9809
- import { mkdir as mkdir5, stat as stat2 } from "fs/promises";
10079
+ import { mkdir as mkdir6, stat as stat2 } from "fs/promises";
9810
10080
  async function dirExists(path) {
9811
10081
  try {
9812
10082
  const s = await stat2(path);
@@ -9829,7 +10099,7 @@ async function fixProject(projectRoot) {
9829
10099
  ];
9830
10100
  for (const dir of requiredDirs) {
9831
10101
  if (!await dirExists(dir.path)) {
9832
- await mkdir5(dir.path, { recursive: true });
10102
+ await mkdir6(dir.path, { recursive: true });
9833
10103
  fixed.push(`Recreated missing directory: ${dir.name}/`);
9834
10104
  }
9835
10105
  }
@@ -9849,7 +10119,7 @@ async function fixProject(projectRoot) {
9849
10119
  if (!state.goal)
9850
10120
  issues.push("current.yml is missing 'goal' field. Manual correction required.");
9851
10121
  if (!await dirExists(paths.backlog)) {
9852
- await mkdir5(paths.backlog, { recursive: true });
10122
+ await mkdir6(paths.backlog, { recursive: true });
9853
10123
  fixed.push("Recreated missing backlog/ directory for active generation");
9854
10124
  }
9855
10125
  } catch {
@@ -9862,8 +10132,8 @@ async function fixProject(projectRoot) {
9862
10132
  }
9863
10133
 
9864
10134
  // src/cli/index.ts
9865
- import { join as join7 } from "path";
9866
- program.name("reap").description("REAP — Recursive Evolutionary Autonomous Pipeline").version("0.1.2");
10135
+ import { join as join8 } from "path";
10136
+ program.name("reap").description("REAP — Recursive Evolutionary Autonomous Pipeline").version("0.2.1");
9867
10137
  program.command("init").description("Initialize a new REAP project (Genesis)").argument("[project-name]", "Project name (defaults to current directory name)").option("-m, --mode <mode>", "Entry mode: greenfield, migration, adoption", "greenfield").option("-p, --preset <preset>", "Bootstrap with a genome preset (e.g., bun-hono-react)").action(async (projectName, options) => {
9868
10138
  try {
9869
10139
  const cwd = process.cwd();
@@ -9880,11 +10150,32 @@ program.command("init").description("Initialize a new REAP project (Genesis)").a
9880
10150
  `);
9881
10151
  }
9882
10152
  }
9883
- await initProject(cwd, name, mode, options.preset);
9884
- console.log(`✓ REAP project "${name}" initialized (${mode} mode)`);
9885
- console.log(` .reap/ directory created with genome, environment, life, lineage`);
9886
10153
  console.log(`
9887
- Next: run '/reap.start' to start your first Generation`);
10154
+ Initializing REAP project "${name}" (${mode} mode)...
10155
+ `);
10156
+ const initResult = await initProject(cwd, name, mode, options.preset, (msg) => {
10157
+ console.log(` ${msg}`);
10158
+ });
10159
+ console.log(`
10160
+ ✓ REAP project "${name}" initialized successfully!
10161
+ `);
10162
+ console.log(` Project: ${name} (${mode})`);
10163
+ if (initResult.agents.length > 0) {
10164
+ console.log(` Agents: ${initResult.agents.join(", ")}`);
10165
+ } else {
10166
+ console.log(` Agents: None detected`);
10167
+ }
10168
+ console.log(` Config: .reap/config.yml`);
10169
+ console.log(` Genome: .reap/genome/ (principles, conventions, constraints)`);
10170
+ console.log(`
10171
+ Getting started:`);
10172
+ console.log(` 1. Open your AI agent (${initResult.agents[0] || "Claude Code or OpenCode"})`);
10173
+ console.log(` 2. Run /reap.start to begin your first Generation`);
10174
+ console.log(` 3. Or run /reap.evolve for autonomous execution`);
10175
+ if (initResult.agents.length === 0) {
10176
+ console.log(`
10177
+ ⚠ No AI agents detected. Install Claude Code or OpenCode, then run 'reap update'.`);
10178
+ }
9888
10179
  } catch (e) {
9889
10180
  console.error(`Error: ${e.message}`);
9890
10181
  process.exit(1);
@@ -9958,21 +10249,16 @@ program.command("update").description("Sync slash commands, templates, and hooks
9958
10249
  });
9959
10250
  program.command("help").description("Show REAP commands, slash commands, and workflow overview").action(async () => {
9960
10251
  let lang = "en";
9961
- const settingsContent = await readTextFile(ReapPaths.userClaudeSettingsJson);
9962
- if (settingsContent) {
9963
- try {
9964
- const settings = JSON.parse(settingsContent);
9965
- if (settings.language) {
9966
- const l = settings.language.toLowerCase();
9967
- if (l === "korean" || l === "ko")
9968
- lang = "ko";
9969
- }
9970
- } catch {}
9971
- }
9972
- const helpDir = join7(ReapPaths.packageTemplatesDir, "help");
9973
- let helpText = await readTextFile(join7(helpDir, `${lang}.txt`));
10252
+ const detectedLang = await AgentRegistry.readLanguage();
10253
+ if (detectedLang) {
10254
+ const l = detectedLang.toLowerCase();
10255
+ if (l === "korean" || l === "ko")
10256
+ lang = "ko";
10257
+ }
10258
+ const helpDir = join8(ReapPaths.packageTemplatesDir, "help");
10259
+ let helpText = await readTextFile(join8(helpDir, `${lang}.txt`));
9974
10260
  if (!helpText)
9975
- helpText = await readTextFile(join7(helpDir, "en.txt"));
10261
+ helpText = await readTextFile(join8(helpDir, "en.txt"));
9976
10262
  if (!helpText) {
9977
10263
  console.log("Help file not found. Run 'reap update' to install templates.");
9978
10264
  return;