@c-d-cc/reap 0.15.13 → 0.15.14

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/RELEASE_NOTICE.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Release Notices
2
2
 
3
+ ## v0.15.14
4
+ ### en
5
+ Auto-update guard for breaking changes. New Release Notes docs page with v0.16 pre-announcement.
6
+ ### ko
7
+ Breaking change 대비 자동 업데이트 차단 기능 추가. v0.16 사전 고지가 포함된 릴리스 노트 페이지 신규.
8
+
3
9
  ## v0.15.13
4
10
  ### en
5
11
  Replaced `commander.js` with built-in CLI library. Runtime dependencies: 2 → 1.
package/dist/cli.js CHANGED
@@ -8391,7 +8391,7 @@ function checkLatestVersion() {
8391
8391
  }
8392
8392
  }
8393
8393
  function getCurrentVersion() {
8394
- return "0.15.13";
8394
+ return "0.15.14";
8395
8395
  }
8396
8396
  function formatVersionLine(current, skipCheck) {
8397
8397
  if (skipCheck) {
@@ -10604,7 +10604,7 @@ async function execute17(paths) {
10604
10604
  const gm = new GenerationManager(paths);
10605
10605
  const state = await gm.current();
10606
10606
  const configContent = await readTextFile(paths.config);
10607
- const installedVersion = "0.15.13";
10607
+ const installedVersion = "0.15.14";
10608
10608
  const autoUpdate = configContent?.match(/autoUpdate:\s*(true|false)/)?.[1] === "true";
10609
10609
  const versionDisplay = formatVersionLine(installedVersion, !autoUpdate);
10610
10610
  const rawLang = detectLanguage(configContent);
@@ -12332,7 +12332,7 @@ async function execute30(paths) {
12332
12332
  const lines = [
12333
12333
  `REAP Configuration (${paths.config})`,
12334
12334
  "",
12335
- ` version: ${"0.15.13"} (package)`,
12335
+ ` version: ${"0.15.14"} (package)`,
12336
12336
  ` project: ${config.project}`,
12337
12337
  ` entryMode: ${config.entryMode}`,
12338
12338
  ` strict: ${config.strict ?? false}`,
@@ -12638,7 +12638,7 @@ async function runCommand(command, phase, argv = []) {
12638
12638
  try {
12639
12639
  const config = await ConfigManager.read(paths);
12640
12640
  if (config.autoIssueReport) {
12641
- const version = "0.15.13";
12641
+ const version = "0.15.14";
12642
12642
  const errMsg = err instanceof Error ? err.message : String(err);
12643
12643
  const title = `[auto] reap run ${command}: ${errMsg.slice(0, 80)}`;
12644
12644
  const body = [
@@ -14959,12 +14959,54 @@ function selfUpgrade() {
14959
14959
  if (installed === latest) {
14960
14960
  return { upgraded: false };
14961
14961
  }
14962
+ const minVersion = queryAutoUpdateMinVersion();
14963
+ if (minVersion && !semverGte(installed, minVersion)) {
14964
+ return {
14965
+ upgraded: false,
14966
+ blocked: true,
14967
+ from: installed,
14968
+ to: latest,
14969
+ reason: `Breaking change: v${installed} -> v${latest}. Run '/reap.update' to upgrade manually. See https://reap.cc/docs/release-notes`
14970
+ };
14971
+ }
14962
14972
  execSync3("npm update -g @c-d-cc/reap", { encoding: "utf-8", timeout: 60000, stdio: "pipe" });
14963
14973
  return { upgraded: true, from: installed, to: latest };
14964
14974
  } catch {
14965
14975
  return { upgraded: false };
14966
14976
  }
14967
14977
  }
14978
+ function semverGte(a, b) {
14979
+ const pa = a.split(".").map(Number);
14980
+ const pb = b.split(".").map(Number);
14981
+ for (let i = 0;i < 3; i++) {
14982
+ if ((pa[i] ?? 0) > (pb[i] ?? 0))
14983
+ return true;
14984
+ if ((pa[i] ?? 0) < (pb[i] ?? 0))
14985
+ return false;
14986
+ }
14987
+ return true;
14988
+ }
14989
+ function forceUpgrade(to) {
14990
+ try {
14991
+ const installed = execSync3("reap --version", { encoding: "utf-8", timeout: 5000 }).trim();
14992
+ execSync3(`npm install -g @c-d-cc/reap@${to}`, { encoding: "utf-8", timeout: 60000, stdio: "pipe" });
14993
+ return { upgraded: true, from: installed, to };
14994
+ } catch {
14995
+ return { upgraded: false };
14996
+ }
14997
+ }
14998
+ function queryAutoUpdateMinVersion() {
14999
+ try {
15000
+ const result = execSync3("npm view @c-d-cc/reap reap.autoUpdateMinVersion", {
15001
+ encoding: "utf-8",
15002
+ timeout: 1e4,
15003
+ stdio: ["pipe", "pipe", "pipe"]
15004
+ });
15005
+ return result.trim() || null;
15006
+ } catch {
15007
+ return null;
15008
+ }
15009
+ }
14968
15010
  async function updateProject(projectRoot, dryRun = false) {
14969
15011
  const paths = new ReapPaths(projectRoot);
14970
15012
  const result = { updated: [], skipped: [], removed: [] };
@@ -15259,7 +15301,7 @@ async function getStatus(projectRoot) {
15259
15301
  const totalCompleted = await mgr.countAllCompleted();
15260
15302
  const integrityResult = await checkIntegrity(paths);
15261
15303
  return {
15262
- version: "0.15.13",
15304
+ version: "0.15.14",
15263
15305
  project: config.project,
15264
15306
  entryMode: config.entryMode,
15265
15307
  lastSyncedGeneration: config.lastSyncedGeneration,
@@ -15582,7 +15624,7 @@ init_fs();
15582
15624
  init_version();
15583
15625
  init_config();
15584
15626
  import { join as join34 } from "path";
15585
- program.name("reap").description("REAP — Recursive Evolutionary Autonomous Pipeline").version("0.15.13");
15627
+ program.name("reap").description("REAP — Recursive Evolutionary Autonomous Pipeline").version("0.15.14");
15586
15628
  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) => {
15587
15629
  try {
15588
15630
  const cwd = process.cwd();
@@ -15639,7 +15681,7 @@ program.command("status").description("Show current project and Generation statu
15639
15681
  const paths = new ReapPaths(cwd);
15640
15682
  const config = await ConfigManager.read(paths);
15641
15683
  const skipCheck = config.autoUpdate === false;
15642
- const installedVersion = "0.15.13";
15684
+ const installedVersion = "0.15.14";
15643
15685
  const versionLine = formatVersionLine(installedVersion, skipCheck);
15644
15686
  console.log(`${versionLine} | Project: ${status.project} (${status.entryMode})`);
15645
15687
  console.log(`Completed Generations: ${status.totalGenerations}`);
@@ -15711,8 +15753,29 @@ program.command("fix").description("Diagnose and repair .reap/ directory structu
15711
15753
  });
15712
15754
  program.command("update").description("Upgrade REAP package and sync slash commands, templates, and hooks").option("--dry-run", "Show changes without applying them").action(async (options) => {
15713
15755
  try {
15714
- const upgrade = !options.dryRun ? selfUpgrade() : { upgraded: false };
15715
- if (upgrade.upgraded) {
15756
+ let upgrade = !options.dryRun ? selfUpgrade() : { upgraded: false };
15757
+ if (upgrade.blocked) {
15758
+ console.log(`
15759
+ ⚠ Breaking change detected: v${upgrade.from} → v${upgrade.to}`);
15760
+ console.log(` This update contains breaking changes that may require manual migration.`);
15761
+ console.log(` See release notes: https://reap.cc/docs/release-notes
15762
+ `);
15763
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
15764
+ const answer = await new Promise((resolve) => {
15765
+ rl.question(" Proceed with update? (y/N) ", resolve);
15766
+ });
15767
+ rl.close();
15768
+ if (answer.toLowerCase() === "y" || answer.toLowerCase() === "yes") {
15769
+ upgrade = forceUpgrade(upgrade.to);
15770
+ if (upgrade.upgraded) {
15771
+ console.log(`Upgraded: v${upgrade.from} → v${upgrade.to}`);
15772
+ } else {
15773
+ console.log("Upgrade failed. Try manually: npm install -g @c-d-cc/reap@latest");
15774
+ }
15775
+ } else {
15776
+ console.log("Update skipped.");
15777
+ }
15778
+ } else if (upgrade.upgraded) {
15716
15779
  console.log(`Upgraded: v${upgrade.from} → v${upgrade.to}`);
15717
15780
  }
15718
15781
  const result = await updateProject(process.cwd(), options.dryRun ?? false);
@@ -5,6 +5,17 @@ const { execSync } = require("child_process");
5
5
  const fs = require("fs");
6
6
  const path = require("path");
7
7
 
8
+ // Inline semver comparison (no external deps): returns true if a >= b
9
+ function semverGte(a, b) {
10
+ const pa = a.split(".").map(Number);
11
+ const pb = b.split(".").map(Number);
12
+ for (let i = 0; i < 3; i++) {
13
+ if ((pa[i] || 0) > (pb[i] || 0)) return true;
14
+ if ((pa[i] || 0) < (pb[i] || 0)) return false;
15
+ }
16
+ return true;
17
+ }
18
+
8
19
  module.exports = async (ctx) => {
9
20
  return {
10
21
  "session.start": async ({ trigger }) => {
@@ -50,9 +61,15 @@ module.exports = async (ctx) => {
50
61
  const installed = gl.exec("reap --version 2>/dev/null", execOpts);
51
62
  const latest = gl.exec("npm view @c-d-cc/reap version 2>/dev/null", execOpts);
52
63
  if (installed && latest && installed !== latest) {
53
- execSync("npm update -g @c-d-cc/reap >/dev/null 2>&1", { ...execOpts, stdio: "ignore" });
54
- execSync("reap update >/dev/null 2>&1", { ...execOpts, stdio: "ignore" });
55
- autoUpdateMessage = `REAP auto-updated: v${installed} v${latest}`;
64
+ // Auto-update guard: check autoUpdateMinVersion
65
+ const minVersion = gl.exec("npm view @c-d-cc/reap reap.autoUpdateMinVersion 2>/dev/null", execOpts);
66
+ if (minVersion && !semverGte(installed, minVersion)) {
67
+ autoUpdateMessage = `[BREAKING] Auto-update blocked: v${installed} -> v${latest} contains breaking changes. Inform the user about this breaking change and ask if they want to update via '/reap.update'. Release notes: https://reap.cc/docs/release-notes`;
68
+ } else {
69
+ execSync("npm update -g @c-d-cc/reap >/dev/null 2>&1", { ...execOpts, stdio: "ignore" });
70
+ execSync("reap update >/dev/null 2>&1", { ...execOpts, stdio: "ignore" });
71
+ autoUpdateMessage = `REAP auto-updated: v${installed} → v${latest}`;
72
+ }
56
73
  }
57
74
  } catch { /* update check failed, skip */ }
58
75
  }
@@ -109,7 +126,11 @@ module.exports = async (ctx) => {
109
126
  // Build auto-update section
110
127
  let updateSection = "";
111
128
  if (autoUpdateMessage) {
112
- updateSection = `\n\n## Auto-Update\n${autoUpdateMessage}. Tell the user: "${autoUpdateMessage}"`;
129
+ if (autoUpdateMessage.startsWith("[BREAKING]")) {
130
+ updateSection = `\n\n## Auto-Update (Breaking Change Detected)\n${autoUpdateMessage}\n\nIMPORTANT: On your first response, explain to the user that a new REAP version is available but contains breaking changes. Show them the release notes link and ask for explicit confirmation before they run the manual update command. Do NOT silently skip this.`;
131
+ } else {
132
+ updateSection = `\n\n## Auto-Update\n${autoUpdateMessage}. Tell the user: "${autoUpdateMessage}"`;
133
+ }
113
134
  }
114
135
 
115
136
  const context = `<REAP_WORKFLOW>\n${reapGuide}\n\n---\n\n## Genome (Project Knowledge)\n${genomeContent}\n\n---\n\n## Current State\n${generationContext}${staleSection}${strictSection}${updateSection}${langSection}\n\n## Rules\n1. ALL development work MUST follow the REAP lifecycle.\n2. Before writing any code, check if a Generation is active and what stage it is in.\n3. If a Generation is active, use \`${nextCmd}\` to proceed with the current stage.\n4. If no Generation is active, use \`/reap.start\` to start a new one.\n5. Do NOT implement features outside of the REAP lifecycle unless explicitly asked.\n6. Genome is the authoritative knowledge source.\n</REAP_WORKFLOW>`;
@@ -117,6 +117,17 @@ if (gl.dirExists(userReapCommands)) {
117
117
  }
118
118
  }
119
119
 
120
+ // Inline semver comparison (no external deps): returns true if a >= b
121
+ function semverGte(a, b) {
122
+ const pa = a.split('.').map(Number);
123
+ const pb = b.split('.').map(Number);
124
+ for (let i = 0; i < 3; i++) {
125
+ if ((pa[i] || 0) > (pb[i] || 0)) return true;
126
+ if ((pa[i] || 0) < (pb[i] || 0)) return false;
127
+ }
128
+ return true;
129
+ }
130
+
120
131
  // Step 1: Version check + Auto-update
121
132
  log('Checking for updates...');
122
133
  let autoUpdateMessage = '';
@@ -129,10 +140,17 @@ if (installed && installed.includes('+dev')) {
129
140
  } else if (installed && latest && installed !== latest) {
130
141
  const autoUpdate = configContent ? /^autoUpdate:\s*true/m.test(configContent) : false;
131
142
  if (autoUpdate) {
132
- const updated = gl.exec('npm update -g @c-d-cc/reap');
133
- if (updated !== null) {
134
- gl.exec('reap update');
135
- autoUpdateMessage = `REAP auto-updated: v${installed} v${latest}`;
143
+ // Auto-update guard: check autoUpdateMinVersion before upgrading
144
+ const minVersion = gl.exec('npm view @c-d-cc/reap reap.autoUpdateMinVersion');
145
+ if (minVersion && !semverGte(installed, minVersion)) {
146
+ // Breaking change detected block auto-update
147
+ autoUpdateMessage = `[BREAKING] Auto-update blocked: v${installed} → v${latest} contains breaking changes. Inform the user about this breaking change and ask if they want to update via '/reap.update'. Release notes: https://reap.cc/docs/release-notes`;
148
+ } else {
149
+ const updated = gl.exec('npm update -g @c-d-cc/reap');
150
+ if (updated !== null) {
151
+ gl.exec('reap update');
152
+ autoUpdateMessage = `REAP auto-updated: v${installed} → v${latest}`;
153
+ }
136
154
  }
137
155
  } else {
138
156
  updateAvailableMessage = `update available: v${installed} → v${latest}`;
@@ -178,7 +196,11 @@ if (genomeStaleWarning) {
178
196
  // Build auto-update section
179
197
  let updateSection = '';
180
198
  if (autoUpdateMessage) {
181
- updateSection = `\n\n## Auto-Update\n${autoUpdateMessage}. Tell the user: "${autoUpdateMessage}"`;
199
+ if (autoUpdateMessage.startsWith('[BREAKING]')) {
200
+ updateSection = `\n\n## Auto-Update (Breaking Change Detected)\n${autoUpdateMessage}\n\nIMPORTANT: On your first response, explain to the user that a new REAP version is available but contains breaking changes. Show them the release notes link and ask for explicit confirmation before they run the manual update command. Do NOT silently skip this.`;
201
+ } else {
202
+ updateSection = `\n\n## Auto-Update\n${autoUpdateMessage}. Tell the user: "${autoUpdateMessage}"`;
203
+ }
182
204
  }
183
205
 
184
206
  // Build session init display
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c-d-cc/reap",
3
- "version": "0.15.13",
3
+ "version": "0.15.14",
4
4
  "description": "Recursive Evolutionary Autonomous Pipeline — AI and humans evolve software across generations",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -46,5 +46,8 @@
46
46
  },
47
47
  "dependencies": {
48
48
  "yaml": "^2.0.0"
49
+ },
50
+ "reap": {
51
+ "autoUpdateMinVersion": "0.15.0"
49
52
  }
50
53
  }