@andyqiu/codeforge 0.8.16 → 0.8.18

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.
Files changed (2) hide show
  1. package/dist/index.js +125 -16
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -22873,7 +22873,7 @@ class ApprovalStore {
22873
22873
  await fs6.writeFile(file2, JSON.stringify(meta, null, 2), "utf8");
22874
22874
  return file2;
22875
22875
  }
22876
- async getLatest(pendingId) {
22876
+ async getLatestRaw(pendingId) {
22877
22877
  const dir = path8.join(this.base, pendingId);
22878
22878
  const files = await this.safeReaddir(dir);
22879
22879
  const metaFiles = files.filter((f) => /^meta(?:-\d+)?\.json$/.test(f));
@@ -22890,6 +22890,33 @@ class ApprovalStore {
22890
22890
  all.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
22891
22891
  return all[0];
22892
22892
  }
22893
+ async getLatest(pendingId) {
22894
+ const raw = await this.getLatestRaw(pendingId);
22895
+ if (!raw)
22896
+ return;
22897
+ if (raw.invalidatedAt)
22898
+ return;
22899
+ return raw;
22900
+ }
22901
+ async invalidateLatest(pendingId, reason) {
22902
+ const raw = await this.getLatestRaw(pendingId);
22903
+ if (!raw)
22904
+ return { ok: false, skipped: "no-record" };
22905
+ if (raw.invalidatedAt)
22906
+ return { ok: false, skipped: "already-invalidated" };
22907
+ const invalidatedRecord = {
22908
+ ...raw,
22909
+ invalidatedAt: new Date().toISOString(),
22910
+ invalidatedReason: reason,
22911
+ createdAt: new Date().toISOString()
22912
+ };
22913
+ const dir = path8.join(this.base, pendingId);
22914
+ await fs6.mkdir(dir, { recursive: true });
22915
+ const filename = await this.pickRecordFilename(dir);
22916
+ const file2 = path8.join(dir, filename);
22917
+ await fs6.writeFile(file2, JSON.stringify(invalidatedRecord, null, 2), "utf8");
22918
+ return { ok: true, file: file2 };
22919
+ }
22893
22920
  async findAliasApprovals(ownerSessionId) {
22894
22921
  if (!ownerSessionId)
22895
22922
  return [];
@@ -28116,6 +28143,35 @@ async function sendParentNotice(client, sessionID, text, opts = {}) {
28116
28143
 
28117
28144
  // lib/spawner-production.ts
28118
28145
  init_decision_parser();
28146
+
28147
+ // lib/approval-invalidation.ts
28148
+ async function invalidateLatestIfStale(store, pendingIds, reason, log5) {
28149
+ for (const pendingId of pendingIds) {
28150
+ try {
28151
+ const result = await store.invalidateLatest(pendingId, reason);
28152
+ if (result.ok) {
28153
+ log5?.("info", `[approval-invalidation] stale approval 已作废`, {
28154
+ pendingId,
28155
+ reason,
28156
+ file: result.file
28157
+ });
28158
+ } else {
28159
+ log5?.("info", `[approval-invalidation] 无需作废`, {
28160
+ pendingId,
28161
+ skipped: result.skipped
28162
+ });
28163
+ }
28164
+ } catch (err) {
28165
+ log5?.("warn", `[approval-invalidation] 作废失败(fail-open),stale 可能残留`, {
28166
+ pendingId,
28167
+ reason,
28168
+ err: err instanceof Error ? err.message : String(err)
28169
+ });
28170
+ }
28171
+ }
28172
+ }
28173
+
28174
+ // lib/spawner-production.ts
28119
28175
  class ProductionSpawner {
28120
28176
  opts;
28121
28177
  constructor(opts) {
@@ -28153,23 +28209,34 @@ class ProductionSpawner {
28153
28209
  } catch (err) {
28154
28210
  throw err;
28155
28211
  }
28212
+ let result;
28156
28213
  if (r.llmError) {
28157
- return {
28214
+ result = {
28158
28215
  decision: "REQUEST_CHANGES",
28159
28216
  summary: `reviewer LLM error: ${describe4(r.llmError)}`
28160
28217
  };
28161
- }
28162
- const parsed = parseDecision(r.text);
28163
- if (parsed.token === null) {
28164
- return {
28165
- decision: "REQUEST_CHANGES",
28166
- summary: `decision parse failed: ${parsed.reason}
28218
+ } else {
28219
+ const parsed = parseDecision(r.text);
28220
+ if (parsed.token === null) {
28221
+ result = {
28222
+ decision: "REQUEST_CHANGES",
28223
+ summary: `decision parse failed: ${parsed.reason}
28167
28224
 
28168
28225
  ---
28169
28226
  ${r.text.slice(0, 800)}`
28170
- };
28227
+ };
28228
+ } else {
28229
+ result = { decision: parsed.token, summary: r.text };
28230
+ }
28171
28231
  }
28172
- return { decision: parsed.token, summary: r.text };
28232
+ if (result.decision !== "APPROVE" && ownerRoot) {
28233
+ const pendingIds = [`session:${ownerSessionId}`];
28234
+ if (args.sessionId && args.sessionId !== ownerSessionId) {
28235
+ pendingIds.push(`session:${args.sessionId}`);
28236
+ }
28237
+ await invalidateLatestIfStale(ApprovalStore.forProject(ownerRoot), pendingIds, `spawnReviewer round ${args.round}: decision=${result.decision}, invalidating stale approval`, this.opts.log).catch(() => {});
28238
+ }
28239
+ return result;
28173
28240
  }
28174
28241
  async spawnCoder(args) {
28175
28242
  const prompt = buildCoderPrompt(args);
@@ -32690,7 +32757,7 @@ import * as https from "node:https";
32690
32757
  // lib/version-injected.ts
32691
32758
  function getInjectedVersion() {
32692
32759
  try {
32693
- const v = "0.8.16";
32760
+ const v = "0.8.18";
32694
32761
  if (typeof v === "string" && /^\d+\.\d+\.\d+/.test(v)) {
32695
32762
  return v;
32696
32763
  }
@@ -32815,7 +32882,7 @@ function defaultHttpFetcher(url3, timeoutMs) {
32815
32882
 
32816
32883
  // plugins/update-checker.ts
32817
32884
  var PLUGIN_NAME19 = "update-checker";
32818
- var PLUGIN_VERSION = "3.0.0";
32885
+ var PLUGIN_VERSION = "4.0.0";
32819
32886
  var _updateCheckStarted = false;
32820
32887
  function getCacheFile() {
32821
32888
  return join27(process.env["CODEFORGE_CACHE_DIR"] ?? join27(homedir9(), ".cache", "codeforge"), "update-check.json");
@@ -32903,12 +32970,32 @@ function spawnAsync(cmd, args, opts = {}) {
32903
32970
  }
32904
32971
  async function resolveNodeBin() {
32905
32972
  const w = process.platform === "win32";
32973
+ if (w) {
32974
+ try {
32975
+ const r = await spawnAsync("where", ["node.exe"], { timeout: 3000, shell: true });
32976
+ if (r.status === 0 && r.stdout.trim())
32977
+ return r.stdout.trim().split(/\r?\n/)[0].trim();
32978
+ } catch {}
32979
+ try {
32980
+ const r = await spawnAsync("where", ["node"], { timeout: 3000, shell: true });
32981
+ if (r.status === 0 && r.stdout.trim())
32982
+ return r.stdout.trim().split(/\r?\n/)[0].trim();
32983
+ } catch {}
32984
+ for (const c of ["C:\\Program Files\\nodejs\\node.exe", "C:\\Program Files (x86)\\nodejs\\node.exe"]) {
32985
+ try {
32986
+ const r = await spawnAsync(c, ["--version"], { timeout: 2000, shell: false });
32987
+ if (r.status === 0)
32988
+ return c;
32989
+ } catch {}
32990
+ }
32991
+ return process.execPath;
32992
+ }
32906
32993
  try {
32907
- const r = await spawnAsync(w ? "where" : "which", ["node"], { timeout: 3000 });
32994
+ const r = await spawnAsync("which", ["node"], { timeout: 3000 });
32908
32995
  if (r.status === 0 && r.stdout.trim())
32909
32996
  return r.stdout.trim().split(/\r?\n/)[0].trim();
32910
32997
  } catch {}
32911
- for (const c of w ? ["C:\\Program Files\\nodejs\\node.exe"] : ["/usr/local/bin/node", "/usr/bin/node", "/opt/homebrew/bin/node", "/opt/homebrew/opt/node/bin/node"]) {
32998
+ for (const c of ["/usr/local/bin/node", "/usr/bin/node", "/opt/homebrew/bin/node", "/opt/homebrew/opt/node/bin/node"]) {
32912
32999
  try {
32913
33000
  const r = await spawnAsync(c, ["--version"], { timeout: 2000 });
32914
33001
  if (r.status === 0)
@@ -32919,12 +33006,34 @@ async function resolveNodeBin() {
32919
33006
  }
32920
33007
  async function resolveNpmBin() {
32921
33008
  const w = process.platform === "win32";
33009
+ if (w) {
33010
+ try {
33011
+ const r = await spawnAsync("where", ["npm.cmd"], { timeout: 3000, shell: true });
33012
+ if (r.status === 0 && r.stdout.trim())
33013
+ return r.stdout.trim().split(/\r?\n/)[0].trim();
33014
+ } catch {}
33015
+ try {
33016
+ const r = await spawnAsync("where", ["npm"], { timeout: 3000, shell: true });
33017
+ if (r.status === 0 && r.stdout.trim()) {
33018
+ const p = r.stdout.trim().split(/\r?\n/)[0].trim();
33019
+ return p.replace(/\.cmd$/i, "") + ".cmd";
33020
+ }
33021
+ } catch {}
33022
+ for (const c of ["C:\\Program Files\\nodejs\\npm.cmd", "C:\\Program Files (x86)\\nodejs\\npm.cmd"]) {
33023
+ try {
33024
+ const r = await spawnAsync(c, ["--version"], { timeout: 2000, shell: true });
33025
+ if (r.status === 0)
33026
+ return c;
33027
+ } catch {}
33028
+ }
33029
+ return "npm.cmd";
33030
+ }
32922
33031
  try {
32923
- const r = await spawnAsync(w ? "where" : "which", ["npm"], { timeout: 3000 });
33032
+ const r = await spawnAsync("which", ["npm"], { timeout: 3000 });
32924
33033
  if (r.status === 0 && r.stdout.trim())
32925
33034
  return r.stdout.trim().split(/\r?\n/)[0].trim();
32926
33035
  } catch {}
32927
- for (const c of w ? ["C:\\Program Files\\nodejs\\npm.cmd"] : ["/usr/local/bin/npm", "/usr/bin/npm", "/opt/homebrew/bin/npm"]) {
33036
+ for (const c of ["/usr/local/bin/npm", "/usr/bin/npm", "/opt/homebrew/bin/npm"]) {
32928
33037
  try {
32929
33038
  const r = await spawnAsync(c, ["--version"], { timeout: 2000 });
32930
33039
  if (r.status === 0)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andyqiu/codeforge",
3
- "version": "0.8.16",
3
+ "version": "0.8.18",
4
4
  "description": "CodeForge — opencode 的零侵入扩展包",
5
5
  "type": "module",
6
6
  "private": false,