@c-d-cc/reap 0.12.0 → 0.13.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/README.ja.md +21 -3
- package/README.ko.md +19 -1
- package/README.md +23 -5
- package/README.zh-CN.md +21 -3
- package/dist/cli.js +151 -56
- package/dist/templates/commands/reap.abort.md +1 -1
- package/dist/templates/commands/reap.back.md +1 -1
- package/dist/templates/commands/reap.help.md +1 -1
- package/dist/templates/commands/reap.merge.md +1 -1
- package/dist/templates/commands/reap.merge.start.md +1 -1
- package/dist/templates/commands/reap.next.md +1 -1
- package/dist/templates/commands/reap.pull.md +1 -1
- package/dist/templates/commands/reap.start.md +1 -1
- package/package.json +2 -1
- package/scripts/postinstall.cjs +8 -0
package/README.ja.md
CHANGED
|
@@ -28,10 +28,10 @@ REAPはアプリケーションの設計知識 — Genome(アーキテクチ
|
|
|
28
28
|
- [なぜREAPか?](#なぜreapか)
|
|
29
29
|
- [インストール](#インストール)
|
|
30
30
|
- [クイックスタート](#クイックスタート)
|
|
31
|
-
- [ライフサイクル](
|
|
32
|
-
- [コアコンセプト](
|
|
31
|
+
- [ライフサイクル](#ライフサイクル-)
|
|
32
|
+
- [コアコンセプト](#コアコンセプト-)
|
|
33
33
|
- [分散ワークフロー — 並行開発](#分散ワークフロー--並行開発)
|
|
34
|
-
- [CLIコマンド](#cli
|
|
34
|
+
- [CLIコマンド](#cliコマンド-)
|
|
35
35
|
- [エージェント連携](#エージェント連携)
|
|
36
36
|
- [`reap init`後のプロジェクト構造](#reap-init後のプロジェクト構造)
|
|
37
37
|
- [系譜圧縮(Lineage Compression)](#系譜圧縮lineage-compression)
|
|
@@ -240,6 +240,24 @@ REAPはスラッシュコマンドとセッションフックを通じてAIエ
|
|
|
240
240
|
|
|
241
241
|
v0.11.0より、28個のスラッシュコマンドが**1行`.md`ラッパー + TypeScriptスクリプト**構造に移行しました。各`.md`ファイルは`reap run <cmd>`を呼び出し、TSスクリプト(`src/cli/commands/run/`)がすべての決定論的ロジックを処理して、AIエージェントにstructured JSONで指示します。一貫性とテスト容易性が大幅に向上しました。
|
|
242
242
|
|
|
243
|
+
### 署名ベースロック(Signature-Based Locking) [↗](https://reap.cc/docs/advanced)
|
|
244
|
+
|
|
245
|
+
REAPは暗号学的nonceチェーンを使用してステージの順序を強制します。ステージコマンドが実行されると、スクリプトがワンタイムnonceを生成し、そのハッシュを`current.yml`に保存して、nonceをAIエージェントに返します。`/reap.next`はこのnonceがなければ進行できず、なければ拒否されます。
|
|
246
|
+
|
|
247
|
+
```
|
|
248
|
+
Stage Command current.yml /reap.next
|
|
249
|
+
───────────── ─────────── ──────────
|
|
250
|
+
nonce生成 ────────────→ hash(nonce)を保存
|
|
251
|
+
AIにnonce返却 ←── AIがnonceを渡す
|
|
252
|
+
hash(nonce)を検証
|
|
253
|
+
✓ ステージ前進
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
これにより以下を防止します:
|
|
257
|
+
- **ステージのスキップ** — 実行されていないステージには有効なnonceが存在しない
|
|
258
|
+
- **トークンの偽造** — ハッシュは一方向であり、ハッシュからnonceを推測することは不可能
|
|
259
|
+
- **古いnonceの再利用** — 各nonceはワンタイムで、現在のステージにバインドされている
|
|
260
|
+
|
|
243
261
|
### autoSubagentモード
|
|
244
262
|
|
|
245
263
|
`/reap.evolve`実行時、自動的にsubagentにGenerationライフサイクル全体を委任できます:
|
package/README.ko.md
CHANGED
|
@@ -29,7 +29,7 @@ REAP은 Application의 설계 지식 — Genome(아키텍처, 컨벤션, 제약
|
|
|
29
29
|
- [설치](#설치)
|
|
30
30
|
- [빠른 시작](#빠른-시작)
|
|
31
31
|
- [생애주기 (Life Cycle)](#생애주기-life-cycle)
|
|
32
|
-
- [핵심 개념](
|
|
32
|
+
- [핵심 개념](#핵심-개념-)
|
|
33
33
|
- [분산 워크플로우 — 병렬 개발](#분산-워크플로우--병렬-개발)
|
|
34
34
|
- [CLI 명령어](#cli-명령어)
|
|
35
35
|
- [에이전트 연동](#에이전트-연동)
|
|
@@ -240,6 +240,24 @@ REAP은 슬래시 커맨드와 세션 훅을 통해 AI 에이전트와 통합됩
|
|
|
240
240
|
|
|
241
241
|
v0.11.0부터 28개 슬래시 커맨드가 **1줄 `.md` wrapper + TypeScript 스크립트** 구조로 전환되었습니다. 각 `.md` 파일은 `reap run <cmd>`를 호출하고, TS 스크립트(`src/cli/commands/run/`)가 모든 결정적 로직을 처리하여 AI에게 structured JSON으로 지시합니다. 일관성과 테스트 용이성이 크게 향상되었습니다.
|
|
242
242
|
|
|
243
|
+
### 서명 기반 잠금 (Signature-Based Locking) [↗](https://reap.cc/docs/advanced)
|
|
244
|
+
|
|
245
|
+
REAP은 암호학적 nonce 체인을 사용하여 stage 순서를 강제합니다. stage 커맨드가 실행되면 스크립트가 일회용 nonce를 생성하여 해시를 `current.yml`에 저장하고, nonce를 AI 에이전트에게 반환합니다. `/reap.next`는 이 nonce가 있어야 다음 단계로 진행할 수 있으며, 없으면 진행이 거부됩니다.
|
|
246
|
+
|
|
247
|
+
```
|
|
248
|
+
Stage Command current.yml /reap.next
|
|
249
|
+
───────────── ─────────── ──────────
|
|
250
|
+
nonce 생성 ──────────→ hash(nonce) 저장
|
|
251
|
+
AI에게 nonce 반환 ←── AI가 nonce 전달
|
|
252
|
+
hash(nonce) 검증
|
|
253
|
+
✓ stage 전진
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
이를 통해 방지하는 것:
|
|
257
|
+
- **Stage 건너뛰기** — 실행되지 않은 stage에는 유효한 nonce가 존재하지 않음
|
|
258
|
+
- **토큰 위조** — 해시는 단방향이므로 해시에서 nonce를 추측할 수 없음
|
|
259
|
+
- **이전 nonce 재사용** — 각 nonce는 일회용이며 현재 stage에 바인딩됨
|
|
260
|
+
|
|
243
261
|
### autoSubagent 모드
|
|
244
262
|
|
|
245
263
|
`/reap.evolve` 실행 시 자동으로 subagent에게 Generation lifecycle 전체를 위임할 수 있습니다:
|
package/README.md
CHANGED
|
@@ -28,13 +28,13 @@ REAP captures an application's design knowledge — the Genome (architecture, co
|
|
|
28
28
|
- [Why REAP?](#why-reap)
|
|
29
29
|
- [Installation](#installation)
|
|
30
30
|
- [Quick Start](#quick-start)
|
|
31
|
-
- [Life Cycle](#life-cycle)
|
|
32
|
-
- [Core Concepts](#core-concepts)
|
|
33
|
-
- [Distributed Workflow
|
|
34
|
-
- [CLI Commands](#cli-commands)
|
|
31
|
+
- [Life Cycle](#life-cycle-)
|
|
32
|
+
- [Core Concepts](#core-concepts-)
|
|
33
|
+
- [Distributed Workflow](#distributed-workflow-)
|
|
34
|
+
- [CLI Commands](#cli-commands-)
|
|
35
35
|
- [Agent Integration](#agent-integration)
|
|
36
36
|
- [Project Structure](#project-structure-after-reap-init)
|
|
37
|
-
- [Lineage Compression](#lineage-compression)
|
|
37
|
+
- [Lineage Compression](#lineage-compression-)
|
|
38
38
|
- [Evolution Flow](#evolution-flow)
|
|
39
39
|
- [Presets](#presets)
|
|
40
40
|
- [Entry Modes](#entry-modes)
|
|
@@ -239,6 +239,24 @@ REAP integrates with AI agents through slash commands and session hooks. Current
|
|
|
239
239
|
|
|
240
240
|
Since v0.11.0, all 28 slash commands follow a **1-line `.md` wrapper + TypeScript script** pattern. Each `.md` file simply calls `reap run <cmd>`, and the TS script (`src/cli/commands/run/`) handles all deterministic logic — returning structured JSON instructions for the AI agent. This ensures consistency and testability.
|
|
241
241
|
|
|
242
|
+
### Signature-Based Locking [↗](https://reap.cc/docs/advanced)
|
|
243
|
+
|
|
244
|
+
REAP uses a cryptographic nonce chain to enforce stage ordering. When a stage command runs, the script generates a one-time nonce, stores its hash in `current.yml`, and returns the nonce to the AI agent. `/reap.next` requires this nonce to advance — without it, progression is rejected.
|
|
245
|
+
|
|
246
|
+
```
|
|
247
|
+
Stage Command current.yml /reap.next
|
|
248
|
+
───────────── ─────────── ──────────
|
|
249
|
+
generate nonce ──────→ store hash(nonce)
|
|
250
|
+
return nonce to AI ←── AI passes nonce
|
|
251
|
+
verify hash(nonce)
|
|
252
|
+
✓ advance stage
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
This prevents:
|
|
256
|
+
- **Skipping stages** — no valid nonce exists for stages that were not executed
|
|
257
|
+
- **Forging tokens** — the hash is one-way; guessing the nonce from the hash is infeasible
|
|
258
|
+
- **Replaying old nonces** — each nonce is single-use and bound to the current stage
|
|
259
|
+
|
|
242
260
|
### autoSubagent Mode
|
|
243
261
|
|
|
244
262
|
When `/reap.evolve` is run, REAP can automatically delegate the entire generation lifecycle to a subagent. This is controlled by:
|
package/README.zh-CN.md
CHANGED
|
@@ -28,10 +28,10 @@ REAP记录应用程序的设计知识 — Genome(架构、约定、约束)
|
|
|
28
28
|
- [为什么选择REAP?](#为什么选择reap)
|
|
29
29
|
- [安装](#安装)
|
|
30
30
|
- [快速开始](#快速开始)
|
|
31
|
-
- [生命周期](
|
|
32
|
-
- [核心概念](
|
|
31
|
+
- [生命周期](#生命周期-)
|
|
32
|
+
- [核心概念](#核心概念-)
|
|
33
33
|
- [分布式工作流 — 并行开发](#分布式工作流--并行开发)
|
|
34
|
-
- [CLI命令](#cli
|
|
34
|
+
- [CLI命令](#cli命令-)
|
|
35
35
|
- [代理集成](#代理集成)
|
|
36
36
|
- [`reap init`后的项目结构](#reap-init后的项目结构)
|
|
37
37
|
- [谱系压缩(Lineage Compression)](#谱系压缩lineage-compression)
|
|
@@ -240,6 +240,24 @@ REAP通过斜杠命令和会话钩子与AI代理集成。当前支持的代理
|
|
|
240
240
|
|
|
241
241
|
从v0.11.0开始,28个斜杠命令全部采用**1行`.md` wrapper + TypeScript脚本**模式。每个`.md`文件仅调用`reap run <cmd>`,TS脚本(`src/cli/commands/run/`)处理所有确定性逻辑,以structured JSON形式指示AI代理。大幅提升了一致性和可测试性。
|
|
242
242
|
|
|
243
|
+
### 签名锁定(Signature-Based Locking) [↗](https://reap.cc/docs/advanced)
|
|
244
|
+
|
|
245
|
+
REAP使用加密nonce链来强制执行阶段顺序。当阶段命令运行时,脚本生成一次性nonce,将其哈希存储在`current.yml`中,并将nonce返回给AI代理。`/reap.next`需要此nonce才能推进 — 没有它,推进将被拒绝。
|
|
246
|
+
|
|
247
|
+
```
|
|
248
|
+
Stage Command current.yml /reap.next
|
|
249
|
+
───────────── ─────────── ──────────
|
|
250
|
+
生成nonce ────────────→ 存储hash(nonce)
|
|
251
|
+
将nonce返回给AI ←── AI传递nonce
|
|
252
|
+
验证hash(nonce)
|
|
253
|
+
✓ 阶段推进
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
这可以防止:
|
|
257
|
+
- **跳过阶段** — 未执行的阶段不存在有效的nonce
|
|
258
|
+
- **伪造令牌** — 哈希是单向的,从哈希推测nonce是不可行的
|
|
259
|
+
- **重放旧nonce** — 每个nonce是一次性的,绑定到当前阶段
|
|
260
|
+
|
|
243
261
|
### autoSubagent模式
|
|
244
262
|
|
|
245
263
|
执行`/reap.evolve`时,可自动将整个Generation生命周期委托给subagent:
|
package/dist/cli.js
CHANGED
|
@@ -9947,10 +9947,19 @@ var init_lineage = __esm(() => {
|
|
|
9947
9947
|
});
|
|
9948
9948
|
|
|
9949
9949
|
// src/core/generation.ts
|
|
9950
|
-
import { createHash } from "crypto";
|
|
9950
|
+
import { createHash, randomBytes } from "crypto";
|
|
9951
9951
|
import { hostname } from "os";
|
|
9952
9952
|
import { readdir as readdir7, mkdir as mkdir4, rename, unlink as unlink3 } from "fs/promises";
|
|
9953
9953
|
import { join as join8 } from "path";
|
|
9954
|
+
function generateStageToken(genId, stage) {
|
|
9955
|
+
const nonce = randomBytes(16).toString("hex");
|
|
9956
|
+
const hash = createHash("sha256").update(nonce + genId + stage).digest("hex");
|
|
9957
|
+
return { nonce, hash };
|
|
9958
|
+
}
|
|
9959
|
+
function verifyStageToken(token, genId, stage, expectedHash) {
|
|
9960
|
+
const computed = createHash("sha256").update(token + genId + stage).digest("hex");
|
|
9961
|
+
return computed === expectedHash;
|
|
9962
|
+
}
|
|
9954
9963
|
function generateGenHash(parents, goal, genomeHash, machineId, startedAt) {
|
|
9955
9964
|
const input = JSON.stringify({ parents, goal, genomeHash, machineId, startedAt });
|
|
9956
9965
|
return createHash("sha256").update(input).digest("hex").slice(0, 6);
|
|
@@ -10391,6 +10400,16 @@ async function execute(paths, _phase) {
|
|
|
10391
10400
|
if (!state) {
|
|
10392
10401
|
emitError("next", "No active Generation. Run /reap.start first.");
|
|
10393
10402
|
}
|
|
10403
|
+
if (state.expectedTokenHash) {
|
|
10404
|
+
const args = process.argv.slice(2);
|
|
10405
|
+
const nonce = args.find((a) => !a.startsWith("-") && a !== "run" && a !== "next");
|
|
10406
|
+
if (!nonce) {
|
|
10407
|
+
emitError("next", `Stage transition blocked: no token provided. The stage command outputs a nonce that must be passed to /reap.next. Example: /reap.next <nonce>. This ensures the stage command was actually executed — you cannot skip stages.`);
|
|
10408
|
+
}
|
|
10409
|
+
if (!verifyStageToken(nonce, state.id, state.stage, state.expectedTokenHash)) {
|
|
10410
|
+
emitError("next", `Token verification failed. The provided nonce does not match. Re-run the current stage command (reap run ${state.stage}) to get a valid token. You cannot forge or guess the token.`);
|
|
10411
|
+
}
|
|
10412
|
+
}
|
|
10394
10413
|
const isMerge = state.type === "merge";
|
|
10395
10414
|
let nextStage;
|
|
10396
10415
|
if (isMerge) {
|
|
@@ -10405,6 +10424,7 @@ async function execute(paths, _phase) {
|
|
|
10405
10424
|
if (!state.timeline)
|
|
10406
10425
|
state.timeline = [];
|
|
10407
10426
|
state.timeline.push({ stage: nextStage, at: new Date().toISOString() });
|
|
10427
|
+
state.expectedTokenHash = undefined;
|
|
10408
10428
|
await gm.save(state);
|
|
10409
10429
|
const artifactFile = isMerge ? MERGE_ARTIFACT[nextStage] : NORMAL_ARTIFACT[nextStage];
|
|
10410
10430
|
if (artifactFile) {
|
|
@@ -10439,7 +10459,7 @@ async function execute(paths, _phase) {
|
|
|
10439
10459
|
status: "ok",
|
|
10440
10460
|
command: "next",
|
|
10441
10461
|
phase: "done",
|
|
10442
|
-
completed: ["gate", "advance-stage", "create-artifact", "hooks"],
|
|
10462
|
+
completed: ["gate", "nonce-verify", "advance-stage", "create-artifact", "hooks"],
|
|
10443
10463
|
context: {
|
|
10444
10464
|
generationId: state.id,
|
|
10445
10465
|
previousStage,
|
|
@@ -10478,7 +10498,24 @@ var exports_back = {};
|
|
|
10478
10498
|
__export(exports_back, {
|
|
10479
10499
|
execute: () => execute2
|
|
10480
10500
|
});
|
|
10481
|
-
|
|
10501
|
+
function getFlag(args, name) {
|
|
10502
|
+
const idx = args.indexOf(`--${name}`);
|
|
10503
|
+
return idx !== -1 && args[idx + 1] ? args[idx + 1] : undefined;
|
|
10504
|
+
}
|
|
10505
|
+
function getPositionals(args, valueFlags) {
|
|
10506
|
+
const result = [];
|
|
10507
|
+
for (let i = 0;i < args.length; i++) {
|
|
10508
|
+
if (args[i].startsWith("--")) {
|
|
10509
|
+
const flagName = args[i].slice(2);
|
|
10510
|
+
if (valueFlags.includes(flagName) && i + 1 < args.length)
|
|
10511
|
+
i++;
|
|
10512
|
+
continue;
|
|
10513
|
+
}
|
|
10514
|
+
result.push(args[i]);
|
|
10515
|
+
}
|
|
10516
|
+
return result;
|
|
10517
|
+
}
|
|
10518
|
+
async function execute2(paths, phase, argv = []) {
|
|
10482
10519
|
const gm = new GenerationManager(paths);
|
|
10483
10520
|
const state = await gm.current();
|
|
10484
10521
|
if (!state) {
|
|
@@ -10500,16 +10537,17 @@ async function execute2(paths, phase) {
|
|
|
10500
10537
|
type: state.type,
|
|
10501
10538
|
id: state.id
|
|
10502
10539
|
},
|
|
10503
|
-
prompt:
|
|
10540
|
+
prompt: 'Ask the human: (1) target stage (default: previous stage), (2) reason for regression, (3) related refs. Then run: reap run back --phase apply <target-stage> --reason "<reason>" --refs "<a,b,c>"',
|
|
10504
10541
|
nextCommand: "reap run back --phase apply"
|
|
10505
10542
|
});
|
|
10506
10543
|
}
|
|
10507
10544
|
if (phase === "apply") {
|
|
10508
10545
|
const originalStage = state.stage;
|
|
10509
|
-
const
|
|
10546
|
+
const positionals = getPositionals(argv, ["reason", "refs"]);
|
|
10547
|
+
const targetArg = positionals[0];
|
|
10510
10548
|
let target;
|
|
10511
|
-
if (
|
|
10512
|
-
target =
|
|
10549
|
+
if (targetArg) {
|
|
10550
|
+
target = targetArg;
|
|
10513
10551
|
} else if (isMerge) {
|
|
10514
10552
|
target = MergeLifeCycle.prev(state.stage);
|
|
10515
10553
|
} else {
|
|
@@ -10519,8 +10557,8 @@ async function execute2(paths, phase) {
|
|
|
10519
10557
|
if (!canTransition) {
|
|
10520
10558
|
emitError("back", `Cannot regress from '${originalStage}' to '${target}'.`);
|
|
10521
10559
|
}
|
|
10522
|
-
const reason =
|
|
10523
|
-
const refs = (
|
|
10560
|
+
const reason = getFlag(argv, "reason") ?? "No reason provided";
|
|
10561
|
+
const refs = (getFlag(argv, "refs") ?? "").split(",").filter(Boolean);
|
|
10524
10562
|
state.stage = target;
|
|
10525
10563
|
state.timeline.push({
|
|
10526
10564
|
stage: target,
|
|
@@ -10652,7 +10690,27 @@ __export(exports_start, {
|
|
|
10652
10690
|
});
|
|
10653
10691
|
import { join as join15 } from "path";
|
|
10654
10692
|
import { readdir as readdir12 } from "fs/promises";
|
|
10655
|
-
|
|
10693
|
+
function getFlag2(args, name) {
|
|
10694
|
+
const idx = args.indexOf(`--${name}`);
|
|
10695
|
+
return idx !== -1 && args[idx + 1] ? args[idx + 1] : undefined;
|
|
10696
|
+
}
|
|
10697
|
+
function getPositionals2(args, valueFlags) {
|
|
10698
|
+
const result = [];
|
|
10699
|
+
for (let i = 0;i < args.length; i++) {
|
|
10700
|
+
if (args[i].startsWith("--")) {
|
|
10701
|
+
const flagName = args[i].slice(2);
|
|
10702
|
+
if (valueFlags.includes(flagName) && i + 1 < args.length)
|
|
10703
|
+
i++;
|
|
10704
|
+
continue;
|
|
10705
|
+
}
|
|
10706
|
+
result.push(args[i]);
|
|
10707
|
+
}
|
|
10708
|
+
return result;
|
|
10709
|
+
}
|
|
10710
|
+
async function execute3(paths, phase, argv = []) {
|
|
10711
|
+
const positionals = getPositionals2(argv, ["backlog"]);
|
|
10712
|
+
const goal = positionals.join(" ") || undefined;
|
|
10713
|
+
const backlogFile = getFlag2(argv, "backlog");
|
|
10656
10714
|
const gm = new GenerationManager(paths);
|
|
10657
10715
|
if (!phase || phase === "scan") {
|
|
10658
10716
|
const state = await gm.current();
|
|
@@ -10666,14 +10724,13 @@ async function execute3(paths, phase) {
|
|
|
10666
10724
|
phase: "collect-goal",
|
|
10667
10725
|
completed: ["gate", "backlog-scan"],
|
|
10668
10726
|
context: { backlogItems },
|
|
10669
|
-
prompt: backlogItems.length > 0 ?
|
|
10727
|
+
prompt: backlogItems.length > 0 ? 'Present the backlog items to the human. Ask: select one or enter a new goal. Then run: reap run start --phase create "<goal>" (add --backlog <filename> if selected from backlog)' : 'Ask the human for the goal of this generation. Then run: reap run start --phase create "<goal>"',
|
|
10670
10728
|
nextCommand: "reap run start --phase create"
|
|
10671
10729
|
});
|
|
10672
10730
|
}
|
|
10673
10731
|
if (phase === "create") {
|
|
10674
|
-
const goal = process.env.REAP_START_GOAL;
|
|
10675
10732
|
if (!goal) {
|
|
10676
|
-
emitError("start",
|
|
10733
|
+
emitError("start", 'Goal is required. Usage: reap run start --phase create "<goal>"');
|
|
10677
10734
|
}
|
|
10678
10735
|
const existing = await gm.current();
|
|
10679
10736
|
if (existing && existing.id) {
|
|
@@ -10685,7 +10742,9 @@ async function execute3(paths, phase) {
|
|
|
10685
10742
|
genomeVersion = lineageEntries.filter((e) => e.startsWith("gen-")).length + 1;
|
|
10686
10743
|
} catch {}
|
|
10687
10744
|
const state = await gm.create(goal, genomeVersion);
|
|
10688
|
-
const
|
|
10745
|
+
const { nonce, hash } = generateStageToken(state.id, state.stage);
|
|
10746
|
+
state.expectedTokenHash = hash;
|
|
10747
|
+
await gm.save(state);
|
|
10689
10748
|
if (backlogFile) {
|
|
10690
10749
|
await markBacklogConsumed(paths.backlog, backlogFile, state.id);
|
|
10691
10750
|
}
|
|
@@ -10897,7 +10956,14 @@ __export(exports_abort, {
|
|
|
10897
10956
|
});
|
|
10898
10957
|
import { join as join17 } from "path";
|
|
10899
10958
|
import { readdir as readdir13, unlink as unlink5 } from "fs/promises";
|
|
10900
|
-
|
|
10959
|
+
function getFlag3(args, name) {
|
|
10960
|
+
const idx = args.indexOf(`--${name}`);
|
|
10961
|
+
return idx !== -1 && args[idx + 1] ? args[idx + 1] : undefined;
|
|
10962
|
+
}
|
|
10963
|
+
function hasFlag(args, name) {
|
|
10964
|
+
return args.includes(`--${name}`);
|
|
10965
|
+
}
|
|
10966
|
+
async function execute5(paths, phase, argv = []) {
|
|
10901
10967
|
const gm = new GenerationManager(paths);
|
|
10902
10968
|
const state = await gm.current();
|
|
10903
10969
|
if (!state || !state.id) {
|
|
@@ -10923,17 +10989,16 @@ async function execute5(paths, phase) {
|
|
|
10923
10989
|
" - If changes: offer rollback / stash / hold.",
|
|
10924
10990
|
" - If no changes: skip.",
|
|
10925
10991
|
"Ask: 'Goal과 진행 상황을 backlog에 저장할까요? (yes/no)'",
|
|
10926
|
-
|
|
10927
|
-
"Then run: reap run abort --phase execute"
|
|
10992
|
+
'Then run: reap run abort --phase execute --reason "<reason>" --source-action <rollback|stash|hold|none> [--save-backlog]'
|
|
10928
10993
|
].join(`
|
|
10929
10994
|
`),
|
|
10930
10995
|
nextCommand: "reap run abort --phase execute"
|
|
10931
10996
|
});
|
|
10932
10997
|
}
|
|
10933
10998
|
if (phase === "execute") {
|
|
10934
|
-
const reason =
|
|
10935
|
-
const sourceAction =
|
|
10936
|
-
const saveBacklog =
|
|
10999
|
+
const reason = getFlag3(argv, "reason") ?? "No reason provided";
|
|
11000
|
+
const sourceAction = getFlag3(argv, "source-action") ?? "none";
|
|
11001
|
+
const saveBacklog = hasFlag(argv, "save-backlog");
|
|
10937
11002
|
let backlogSaved = false;
|
|
10938
11003
|
if (saveBacklog) {
|
|
10939
11004
|
const objectiveContent = await readTextFile(paths.artifact("01-objective.md"));
|
|
@@ -11152,6 +11217,9 @@ async function execute7(paths, phase) {
|
|
|
11152
11217
|
if (!content || content.length < 50) {
|
|
11153
11218
|
emitError("objective", "01-objective.md appears incomplete (too short). Fill in the objective before completing.");
|
|
11154
11219
|
}
|
|
11220
|
+
const { nonce, hash } = generateStageToken(state.id, state.stage);
|
|
11221
|
+
state.expectedTokenHash = hash;
|
|
11222
|
+
await gm.save(state);
|
|
11155
11223
|
const hookResults = await executeHooks(paths.hooks, "onLifeObjected", paths.projectRoot);
|
|
11156
11224
|
emitOutput({
|
|
11157
11225
|
status: "ok",
|
|
@@ -11162,7 +11230,7 @@ async function execute7(paths, phase) {
|
|
|
11162
11230
|
id: state.id,
|
|
11163
11231
|
hookResults
|
|
11164
11232
|
},
|
|
11165
|
-
message:
|
|
11233
|
+
message: `Objective stage complete. Advance with: /reap.next ${nonce}`
|
|
11166
11234
|
});
|
|
11167
11235
|
}
|
|
11168
11236
|
}
|
|
@@ -11274,6 +11342,9 @@ async function execute8(paths, phase) {
|
|
|
11274
11342
|
if (!content || content.length < 50) {
|
|
11275
11343
|
emitError("planning", "02-planning.md appears incomplete. Fill in the plan before completing.");
|
|
11276
11344
|
}
|
|
11345
|
+
const { nonce, hash } = generateStageToken(state.id, state.stage);
|
|
11346
|
+
state.expectedTokenHash = hash;
|
|
11347
|
+
await gm.save(state);
|
|
11277
11348
|
const hookResults = await executeHooks(paths.hooks, "onLifePlanned", paths.projectRoot);
|
|
11278
11349
|
emitOutput({
|
|
11279
11350
|
status: "ok",
|
|
@@ -11284,7 +11355,7 @@ async function execute8(paths, phase) {
|
|
|
11284
11355
|
id: state.id,
|
|
11285
11356
|
hookResults
|
|
11286
11357
|
},
|
|
11287
|
-
message:
|
|
11358
|
+
message: `Planning stage complete. Advance with: /reap.next ${nonce}`
|
|
11288
11359
|
});
|
|
11289
11360
|
}
|
|
11290
11361
|
}
|
|
@@ -11392,6 +11463,9 @@ async function execute9(paths, phase) {
|
|
|
11392
11463
|
if (!await fileExists(artifactPath)) {
|
|
11393
11464
|
emitError("implementation", "03-implementation.md does not exist. Complete the implementation work first.");
|
|
11394
11465
|
}
|
|
11466
|
+
const { nonce, hash } = generateStageToken(state.id, state.stage);
|
|
11467
|
+
state.expectedTokenHash = hash;
|
|
11468
|
+
await gm.save(state);
|
|
11395
11469
|
const hookResults = await executeHooks(paths.hooks, "onLifeImplemented", paths.projectRoot);
|
|
11396
11470
|
emitOutput({
|
|
11397
11471
|
status: "ok",
|
|
@@ -11402,7 +11476,7 @@ async function execute9(paths, phase) {
|
|
|
11402
11476
|
id: state.id,
|
|
11403
11477
|
hookResults
|
|
11404
11478
|
},
|
|
11405
|
-
message:
|
|
11479
|
+
message: `Implementation stage complete. Advance with: /reap.next ${nonce}`
|
|
11406
11480
|
});
|
|
11407
11481
|
}
|
|
11408
11482
|
}
|
|
@@ -11521,6 +11595,9 @@ async function execute10(paths, phase) {
|
|
|
11521
11595
|
if (!await fileExists(artifactPath)) {
|
|
11522
11596
|
emitError("validation", "04-validation.md does not exist. Complete the validation work first.");
|
|
11523
11597
|
}
|
|
11598
|
+
const { nonce, hash } = generateStageToken(state.id, state.stage);
|
|
11599
|
+
state.expectedTokenHash = hash;
|
|
11600
|
+
await gm.save(state);
|
|
11524
11601
|
const hookResults = await executeHooks(paths.hooks, "onLifeValidated", paths.projectRoot);
|
|
11525
11602
|
emitOutput({
|
|
11526
11603
|
status: "ok",
|
|
@@ -11531,7 +11608,7 @@ async function execute10(paths, phase) {
|
|
|
11531
11608
|
id: state.id,
|
|
11532
11609
|
hookResults
|
|
11533
11610
|
},
|
|
11534
|
-
message:
|
|
11611
|
+
message: `Validation stage complete. Advance with: /reap.next ${nonce}`
|
|
11535
11612
|
});
|
|
11536
11613
|
}
|
|
11537
11614
|
}
|
|
@@ -11606,6 +11683,12 @@ function buildSubagentPrompt(paths, state, genomeSummaries, backlogSummary) {
|
|
|
11606
11683
|
lines.push("## Project");
|
|
11607
11684
|
lines.push(`- Path: ${paths.projectRoot}`);
|
|
11608
11685
|
lines.push("");
|
|
11686
|
+
lines.push("## Stage Chain Token");
|
|
11687
|
+
lines.push("- Each stage command returns a `stageToken` in its output context.");
|
|
11688
|
+
lines.push("- You MUST pass this token to `/reap.next --token <TOKEN>` (or set `REAP_STAGE_TOKEN` env var).");
|
|
11689
|
+
lines.push("- Without a valid token, stage transition will be REJECTED.");
|
|
11690
|
+
lines.push("- If token is missing or mismatched, re-run the current stage command to obtain a new token.");
|
|
11691
|
+
lines.push("");
|
|
11609
11692
|
lines.push("## Commit Rules");
|
|
11610
11693
|
lines.push("- Create a git commit after implementation and after completion.");
|
|
11611
11694
|
lines.push("- Use conventional commit format: `feat|fix|chore(scope): description`");
|
|
@@ -12003,7 +12086,7 @@ function buildLines(versionDisplay, lang, stateDisplay) {
|
|
|
12003
12086
|
...buildCommandTable(lang),
|
|
12004
12087
|
"",
|
|
12005
12088
|
TOPICS_LINE[lang],
|
|
12006
|
-
"Usage:
|
|
12089
|
+
"Usage: /reap.help <topic>",
|
|
12007
12090
|
CONFIG_LINE[lang]
|
|
12008
12091
|
];
|
|
12009
12092
|
}
|
|
@@ -12017,7 +12100,8 @@ async function execute15(paths) {
|
|
|
12017
12100
|
const rawLang = detectLanguage(configContent);
|
|
12018
12101
|
const supported = isSupportedLanguage(rawLang);
|
|
12019
12102
|
const lang = supported ? rawLang : "en";
|
|
12020
|
-
const
|
|
12103
|
+
const args = process.argv.slice(2).filter((a) => !a.startsWith("-") && a !== "run" && a !== "help");
|
|
12104
|
+
const topic = args[0] || undefined;
|
|
12021
12105
|
const stateDisplay = state?.id ? `Active: **${state.id}** — ${state.goal} (Stage: ${state.stage})` : "No active Generation → `/reap.start` or `/reap.evolve`";
|
|
12022
12106
|
const lines = buildLines(versionDisplay, lang, stateDisplay);
|
|
12023
12107
|
if (topic) {
|
|
@@ -12613,7 +12697,9 @@ var exports_merge_start = {};
|
|
|
12613
12697
|
__export(exports_merge_start, {
|
|
12614
12698
|
execute: () => execute17
|
|
12615
12699
|
});
|
|
12616
|
-
async function execute17(paths, phase) {
|
|
12700
|
+
async function execute17(paths, phase, argv = []) {
|
|
12701
|
+
const positionals = argv.filter((a) => !a.startsWith("--"));
|
|
12702
|
+
const targetBranchArg = positionals[0];
|
|
12617
12703
|
const gm = new GenerationManager(paths);
|
|
12618
12704
|
if (!phase || phase === "collect") {
|
|
12619
12705
|
const state = await gm.current();
|
|
@@ -12629,14 +12715,14 @@ async function execute17(paths, phase) {
|
|
|
12629
12715
|
context: {
|
|
12630
12716
|
currentBranch
|
|
12631
12717
|
},
|
|
12632
|
-
prompt: "Ask the human for the target branch to merge.
|
|
12718
|
+
prompt: "Ask the human for the target branch to merge. Then run: reap run merge-start --phase create <branch-name>",
|
|
12633
12719
|
nextCommand: "reap run merge-start --phase create"
|
|
12634
12720
|
});
|
|
12635
12721
|
}
|
|
12636
12722
|
if (phase === "create") {
|
|
12637
|
-
const targetBranch =
|
|
12723
|
+
const targetBranch = targetBranchArg;
|
|
12638
12724
|
if (!targetBranch) {
|
|
12639
|
-
emitError("merge-start", "
|
|
12725
|
+
emitError("merge-start", "Target branch is required. Usage: reap run merge-start --phase create <branch>");
|
|
12640
12726
|
}
|
|
12641
12727
|
const existing = await gm.current();
|
|
12642
12728
|
if (existing && existing.id) {
|
|
@@ -13269,7 +13355,9 @@ var exports_merge = {};
|
|
|
13269
13355
|
__export(exports_merge, {
|
|
13270
13356
|
execute: () => execute25
|
|
13271
13357
|
});
|
|
13272
|
-
async function execute25(paths, phase) {
|
|
13358
|
+
async function execute25(paths, phase, argv = []) {
|
|
13359
|
+
const positionals = argv.filter((a) => !a.startsWith("--"));
|
|
13360
|
+
const targetBranchArg = positionals[0];
|
|
13273
13361
|
const gm = new GenerationManager(paths);
|
|
13274
13362
|
if (!phase || phase === "detect") {
|
|
13275
13363
|
const state = await gm.current();
|
|
@@ -13288,17 +13376,16 @@ async function execute25(paths, phase) {
|
|
|
13288
13376
|
"## Merge -- Full Merge Generation for a Local Branch",
|
|
13289
13377
|
"",
|
|
13290
13378
|
"Ask the human for the target branch to merge.",
|
|
13291
|
-
"
|
|
13292
|
-
"Then run: reap run merge --phase check"
|
|
13379
|
+
"Then run: reap run merge --phase check <branch-name>"
|
|
13293
13380
|
].join(`
|
|
13294
13381
|
`),
|
|
13295
13382
|
nextCommand: "reap run merge --phase check"
|
|
13296
13383
|
});
|
|
13297
13384
|
}
|
|
13298
13385
|
if (phase === "check") {
|
|
13299
|
-
const targetBranch =
|
|
13386
|
+
const targetBranch = targetBranchArg;
|
|
13300
13387
|
if (!targetBranch) {
|
|
13301
|
-
emitError("merge", "
|
|
13388
|
+
emitError("merge", "Target branch is required. Usage: reap run merge --phase check <branch>");
|
|
13302
13389
|
}
|
|
13303
13390
|
if (!gitRefExists(targetBranch, paths.projectRoot)) {
|
|
13304
13391
|
emitError("merge", `Branch "${targetBranch}" does not exist.`);
|
|
@@ -13313,7 +13400,7 @@ async function execute25(paths, phase) {
|
|
|
13313
13400
|
phase: "start-merge",
|
|
13314
13401
|
completed: ["gate", "branch-verify"],
|
|
13315
13402
|
context: { targetBranch, currentBranch },
|
|
13316
|
-
prompt: `No lineage found. Run /reap.merge.start
|
|
13403
|
+
prompt: `No lineage found. Run /reap.merge.start ${targetBranch} to begin the merge generation, then /reap.merge.evolve.`,
|
|
13317
13404
|
nextCommand: `reap run merge-start --phase create`
|
|
13318
13405
|
});
|
|
13319
13406
|
return;
|
|
@@ -13336,9 +13423,8 @@ async function execute25(paths, phase) {
|
|
|
13336
13423
|
`Current branch: ${currentBranch}`,
|
|
13337
13424
|
"",
|
|
13338
13425
|
"Execute the following sequence:",
|
|
13339
|
-
`1.
|
|
13340
|
-
"2. Run /reap.merge.
|
|
13341
|
-
"3. Run /reap.merge.evolve (runs detect -> mate -> merge -> sync -> validation -> completion)",
|
|
13426
|
+
`1. Run /reap.merge.start ${targetBranch} (creates merge generation + detect report)`,
|
|
13427
|
+
"2. Run /reap.merge.evolve (runs detect -> mate -> merge -> sync -> validation -> completion)",
|
|
13342
13428
|
"",
|
|
13343
13429
|
"The merge generation will be archived upon completion."
|
|
13344
13430
|
].join(`
|
|
@@ -13358,7 +13444,9 @@ var exports_pull = {};
|
|
|
13358
13444
|
__export(exports_pull, {
|
|
13359
13445
|
execute: () => execute26
|
|
13360
13446
|
});
|
|
13361
|
-
async function execute26(paths, phase) {
|
|
13447
|
+
async function execute26(paths, phase, argv = []) {
|
|
13448
|
+
const positionals = argv.filter((a) => !a.startsWith("--"));
|
|
13449
|
+
const targetBranchArg = positionals[0];
|
|
13362
13450
|
const gm = new GenerationManager(paths);
|
|
13363
13451
|
if (!phase || phase === "fetch") {
|
|
13364
13452
|
const state = await gm.current();
|
|
@@ -13378,17 +13466,16 @@ async function execute26(paths, phase) {
|
|
|
13378
13466
|
"",
|
|
13379
13467
|
"1. Run `git fetch origin`",
|
|
13380
13468
|
"2. Ask the human for the target remote branch (e.g., `origin/main`)",
|
|
13381
|
-
"3.
|
|
13382
|
-
"4. Then run: reap run pull --phase check"
|
|
13469
|
+
"3. Then run: reap run pull --phase check <branch-name>"
|
|
13383
13470
|
].join(`
|
|
13384
13471
|
`),
|
|
13385
13472
|
nextCommand: "reap run pull --phase check"
|
|
13386
13473
|
});
|
|
13387
13474
|
}
|
|
13388
13475
|
if (phase === "check") {
|
|
13389
|
-
const targetBranch =
|
|
13476
|
+
const targetBranch = targetBranchArg;
|
|
13390
13477
|
if (!targetBranch) {
|
|
13391
|
-
emitError("pull", "
|
|
13478
|
+
emitError("pull", "Target branch is required. Usage: reap run pull --phase check <branch>");
|
|
13392
13479
|
}
|
|
13393
13480
|
if (!gitRefExists(targetBranch, paths.projectRoot)) {
|
|
13394
13481
|
emitError("pull", `Branch "${targetBranch}" does not exist. Run \`git fetch\` first.`);
|
|
@@ -13459,10 +13546,9 @@ async function execute26(paths, phase) {
|
|
|
13459
13546
|
"## Branches have diverged -- full merge required",
|
|
13460
13547
|
"",
|
|
13461
13548
|
"Execute the following sequence:",
|
|
13462
|
-
`1.
|
|
13463
|
-
"2. Run /reap.merge.
|
|
13464
|
-
"3. Run
|
|
13465
|
-
"4. Run `git submodule update --init` after merge completes"
|
|
13549
|
+
`1. Run /reap.merge.start ${targetBranch} (creates merge generation + detect report)`,
|
|
13550
|
+
"2. Run /reap.merge.evolve (runs detect -> mate -> merge -> sync -> validation -> completion)",
|
|
13551
|
+
"3. Run `git submodule update --init` after merge completes"
|
|
13466
13552
|
].join(`
|
|
13467
13553
|
`)
|
|
13468
13554
|
});
|
|
@@ -13516,7 +13602,7 @@ __export(exports_run, {
|
|
|
13516
13602
|
runCommand: () => runCommand
|
|
13517
13603
|
});
|
|
13518
13604
|
import { execSync as execSync6 } from "child_process";
|
|
13519
|
-
async function runCommand(command, phase) {
|
|
13605
|
+
async function runCommand(command, phase, argv = []) {
|
|
13520
13606
|
const cwd = process.cwd();
|
|
13521
13607
|
const paths = new ReapPaths(cwd);
|
|
13522
13608
|
if (!await paths.isReapProject()) {
|
|
@@ -13528,12 +13614,12 @@ async function runCommand(command, phase) {
|
|
|
13528
13614
|
}
|
|
13529
13615
|
try {
|
|
13530
13616
|
const mod = await loader();
|
|
13531
|
-
await mod.execute(paths, phase);
|
|
13617
|
+
await mod.execute(paths, phase, argv);
|
|
13532
13618
|
} catch (err) {
|
|
13533
13619
|
try {
|
|
13534
13620
|
const config = await ConfigManager.read(paths);
|
|
13535
13621
|
if (config.autoIssueReport) {
|
|
13536
|
-
const version = "0.
|
|
13622
|
+
const version = "0.13.1";
|
|
13537
13623
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
13538
13624
|
const title = `[auto] reap run ${command}: ${errMsg.slice(0, 80)}`;
|
|
13539
13625
|
const body = [
|
|
@@ -14119,7 +14205,7 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
|
|
|
14119
14205
|
}
|
|
14120
14206
|
const detectedLanguage = await AgentRegistry.readLanguage();
|
|
14121
14207
|
const config = {
|
|
14122
|
-
version: "0.
|
|
14208
|
+
version: "0.13.1",
|
|
14123
14209
|
project: projectName,
|
|
14124
14210
|
entryMode,
|
|
14125
14211
|
strict: false,
|
|
@@ -14718,7 +14804,7 @@ async function updateProject(projectRoot, dryRun = false) {
|
|
|
14718
14804
|
result.skipped.push(`.claude/commands/ (${reapCmdFiles.length} unchanged)`);
|
|
14719
14805
|
}
|
|
14720
14806
|
await migrateLegacyFiles(paths, dryRun, result);
|
|
14721
|
-
const currentVersion = "0.
|
|
14807
|
+
const currentVersion = "0.13.1";
|
|
14722
14808
|
const migrationResult = await MigrationRunner.run(paths, currentVersion, dryRun);
|
|
14723
14809
|
for (const m of migrationResult.migrated) {
|
|
14724
14810
|
result.updated.push(`[migration] ${m}`);
|
|
@@ -14916,7 +15002,7 @@ init_fs();
|
|
|
14916
15002
|
init_version();
|
|
14917
15003
|
init_config();
|
|
14918
15004
|
import { join as join26 } from "path";
|
|
14919
|
-
program.name("reap").description("REAP — Recursive Evolutionary Autonomous Pipeline").version("0.
|
|
15005
|
+
program.name("reap").description("REAP — Recursive Evolutionary Autonomous Pipeline").version("0.13.1");
|
|
14920
15006
|
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) => {
|
|
14921
15007
|
try {
|
|
14922
15008
|
const cwd = process.cwd();
|
|
@@ -15060,8 +15146,17 @@ program.command("help").description("Show REAP commands, slash commands, and wor
|
|
|
15060
15146
|
}
|
|
15061
15147
|
console.log(helpText);
|
|
15062
15148
|
});
|
|
15063
|
-
program.command("run <command>").description("Run a REAP command script (internal, used by slash commands)").option("--phase <phase>", "Start from a specific phase").action(async (command, options) => {
|
|
15149
|
+
program.command("run <command>").description("Run a REAP command script (internal, used by slash commands)").option("--phase <phase>", "Start from a specific phase").allowUnknownOption().action(async (command, options, cmd) => {
|
|
15150
|
+
const rawArgs = cmd.args.slice(1);
|
|
15151
|
+
const passArgs = [];
|
|
15152
|
+
for (let i = 0;i < rawArgs.length; i++) {
|
|
15153
|
+
if (rawArgs[i] === "--phase") {
|
|
15154
|
+
i++;
|
|
15155
|
+
continue;
|
|
15156
|
+
}
|
|
15157
|
+
passArgs.push(rawArgs[i]);
|
|
15158
|
+
}
|
|
15064
15159
|
const { runCommand: runCommand2 } = await Promise.resolve().then(() => (init_run(), exports_run));
|
|
15065
|
-
await runCommand2(command, options.phase);
|
|
15160
|
+
await runCommand2(command, options.phase, passArgs);
|
|
15066
15161
|
});
|
|
15067
15162
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c-d-cc/reap",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.1",
|
|
4
4
|
"description": "Recursive Evolutionary Autonomous Pipeline — AI and humans evolve software across generations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"dist/",
|
|
27
27
|
"scripts/postinstall.cjs"
|
|
28
28
|
],
|
|
29
|
+
"preferGlobal": true,
|
|
29
30
|
"engines": {
|
|
30
31
|
"node": ">=18"
|
|
31
32
|
},
|
package/scripts/postinstall.cjs
CHANGED
|
@@ -10,6 +10,14 @@ const { join, dirname } = require("path");
|
|
|
10
10
|
const { homedir } = require("os");
|
|
11
11
|
|
|
12
12
|
try {
|
|
13
|
+
// Warn if installed locally instead of globally
|
|
14
|
+
const isGlobal = process.env.npm_config_global === "true"
|
|
15
|
+
|| (process.env.npm_config_prefix && !process.env.npm_config_prefix.includes("node_modules"));
|
|
16
|
+
if (!isGlobal) {
|
|
17
|
+
console.warn("\n ⚠ @c-d-cc/reap is a CLI tool and should be installed globally:");
|
|
18
|
+
console.warn(" npm install -g @c-d-cc/reap\n");
|
|
19
|
+
}
|
|
20
|
+
|
|
13
21
|
// Resolve commands source: dist/templates/commands/ relative to this script
|
|
14
22
|
const commandsSource = join(dirname(__dirname), "dist", "templates", "commands");
|
|
15
23
|
if (!existsSync(commandsSource)) {
|