@c-d-cc/reap 0.1.3 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +15 -5
- package/README.ko.md +15 -5
- package/README.md +15 -5
- package/README.zh-CN.md +15 -5
- package/dist/cli.js +545 -259
- package/dist/templates/commands/reap.help.md +70 -45
- package/dist/templates/commands/reap.status.md +12 -2
- package/dist/templates/commands/reap.update.md +40 -0
- package/dist/templates/hooks/opencode-session-start.js +206 -0
- package/dist/templates/hooks/reap-guide.md +4 -0
- package/dist/templates/hooks/session-start.sh +46 -9
- package/package.json +2 -2
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
|
|
8844
|
-
import { join as
|
|
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/
|
|
9004
|
+
// src/core/agents/claude-code.ts
|
|
9005
9005
|
import { join as join2 } from "path";
|
|
9006
|
-
import {
|
|
9007
|
-
|
|
9008
|
-
|
|
9009
|
-
|
|
9010
|
-
|
|
9011
|
-
|
|
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
|
-
|
|
9030
|
-
|
|
9031
|
-
|
|
9032
|
-
|
|
9033
|
-
}
|
|
9034
|
-
|
|
9035
|
-
|
|
9036
|
-
|
|
9037
|
-
|
|
9038
|
-
|
|
9039
|
-
|
|
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
|
-
|
|
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
|
-
|
|
9069
|
-
|
|
9070
|
-
|
|
9071
|
-
|
|
9072
|
-
|
|
9073
|
-
|
|
9074
|
-
|
|
9075
|
-
|
|
9076
|
-
|
|
9077
|
-
|
|
9078
|
-
|
|
9079
|
-
|
|
9080
|
-
|
|
9081
|
-
|
|
9082
|
-
|
|
9083
|
-
|
|
9084
|
-
|
|
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 (
|
|
9093
|
-
|
|
9094
|
-
|
|
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
|
|
9101
|
-
});
|
|
9102
|
-
if (changed && !dryRun) {
|
|
9103
|
-
settings["hooks"] = { ...hooks, SessionStart: updated };
|
|
9104
|
-
await writeSettingsJson(settings);
|
|
9110
|
+
return { action: changed ? "updated" : "skipped" };
|
|
9105
9111
|
}
|
|
9106
|
-
|
|
9107
|
-
|
|
9108
|
-
|
|
9109
|
-
|
|
9110
|
-
|
|
9111
|
-
|
|
9112
|
-
|
|
9113
|
-
|
|
9114
|
-
|
|
9115
|
-
|
|
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
|
-
|
|
9120
|
-
|
|
9121
|
-
|
|
9122
|
-
|
|
9123
|
-
|
|
9124
|
-
|
|
9125
|
-
|
|
9126
|
-
|
|
9127
|
-
return
|
|
9128
|
-
|
|
9129
|
-
|
|
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
|
|
9132
|
-
|
|
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
|
-
|
|
9136
|
-
|
|
9137
|
-
|
|
9138
|
-
|
|
9139
|
-
|
|
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
|
|
9190
|
+
return false;
|
|
9142
9191
|
const hooks = entry["hooks"];
|
|
9143
9192
|
if (!Array.isArray(hooks))
|
|
9144
|
-
return
|
|
9145
|
-
return
|
|
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
|
-
|
|
9153
|
-
|
|
9154
|
-
|
|
9155
|
-
|
|
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
|
-
|
|
9158
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
9189
|
-
const presetExists = await fileExists(
|
|
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
|
-
|
|
9195
|
-
await
|
|
9196
|
-
await
|
|
9197
|
-
await
|
|
9198
|
-
await
|
|
9199
|
-
await
|
|
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 ?
|
|
9454
|
+
const genomeSourceDir = preset ? join4(ReapPaths.packageTemplatesDir, "presets", preset) : ReapPaths.packageGenomeDir;
|
|
9209
9455
|
for (const file of genomeTemplates) {
|
|
9210
|
-
const src =
|
|
9211
|
-
const dest =
|
|
9456
|
+
const src = join4(genomeSourceDir, file);
|
|
9457
|
+
const dest = join4(paths.genome, file);
|
|
9212
9458
|
await writeTextFile(dest, await readTextFileOrThrow(src));
|
|
9213
9459
|
}
|
|
9214
|
-
|
|
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 =
|
|
9218
|
-
const dest =
|
|
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 =
|
|
9222
|
-
const domainGuideDest =
|
|
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
|
-
|
|
9225
|
-
|
|
9226
|
-
|
|
9227
|
-
|
|
9228
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
9245
|
-
for (const
|
|
9246
|
-
|
|
9247
|
-
|
|
9248
|
-
const
|
|
9249
|
-
|
|
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
|
-
|
|
9266
|
-
|
|
9267
|
-
|
|
9268
|
-
|
|
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
|
-
|
|
9272
|
-
|
|
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(
|
|
9276
|
-
const dest =
|
|
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(
|
|
9287
|
-
const domainGuideDest =
|
|
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
|
|
9297
|
-
|
|
9298
|
-
|
|
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
|
|
9301
|
-
|
|
9302
|
-
|
|
9303
|
-
|
|
9304
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
9375
|
-
import { join as
|
|
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
|
|
9430
|
-
import { join as
|
|
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
|
|
9716
|
+
const entries = await readdir4(dirPath, { withFileTypes: true });
|
|
9447
9717
|
for (const entry of entries) {
|
|
9448
|
-
const fullPath =
|
|
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
|
|
9731
|
+
const items = await readdir4(paths.lineage, { withFileTypes: true });
|
|
9462
9732
|
for (const item of items) {
|
|
9463
|
-
const fullPath =
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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:
|
|
9936
|
+
path: join6(paths.lineage, e.name)
|
|
9667
9937
|
}));
|
|
9668
9938
|
const compressed = await compressLevel2(files, epochNum);
|
|
9669
|
-
const outPath =
|
|
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
|
|
9731
|
-
const lifeEntries = await
|
|
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(
|
|
10004
|
+
await rename(join7(this.paths.life, entry), join7(genDir, entry));
|
|
9735
10005
|
}
|
|
9736
10006
|
}
|
|
9737
|
-
const backlogDir =
|
|
9738
|
-
await
|
|
10007
|
+
const backlogDir = join7(genDir, "backlog");
|
|
10008
|
+
await mkdir5(backlogDir, { recursive: true });
|
|
9739
10009
|
try {
|
|
9740
|
-
const backlogEntries = await
|
|
10010
|
+
const backlogEntries = await readdir5(this.paths.backlog);
|
|
9741
10011
|
for (const entry of backlogEntries) {
|
|
9742
|
-
await rename(
|
|
10012
|
+
await rename(join7(this.paths.backlog, entry), join7(backlogDir, entry));
|
|
9743
10013
|
}
|
|
9744
10014
|
} catch {}
|
|
9745
10015
|
try {
|
|
9746
|
-
const mutEntries = await
|
|
10016
|
+
const mutEntries = await readdir5(this.paths.mutations);
|
|
9747
10017
|
if (mutEntries.length > 0) {
|
|
9748
|
-
const mutDir =
|
|
9749
|
-
await
|
|
10018
|
+
const mutDir = join7(genDir, "mutations");
|
|
10019
|
+
await mkdir5(mutDir, { recursive: true });
|
|
9750
10020
|
for (const entry of mutEntries) {
|
|
9751
|
-
await rename(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
9866
|
-
program.name("reap").description("REAP — Recursive Evolutionary Autonomous Pipeline").version("0.
|
|
10135
|
+
import { join as join8 } from "path";
|
|
10136
|
+
program.name("reap").description("REAP — Recursive Evolutionary Autonomous Pipeline").version("0.2.2");
|
|
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
|
-
|
|
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
|
|
9962
|
-
if (
|
|
9963
|
-
|
|
9964
|
-
|
|
9965
|
-
|
|
9966
|
-
|
|
9967
|
-
|
|
9968
|
-
|
|
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(
|
|
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;
|