@andyqiu/codeforge 0.8.15 → 0.8.17

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 +78 -11
  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
+ }
28231
+ }
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(() => {});
28171
28238
  }
28172
- return { decision: parsed.token, summary: r.text };
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.15";
32760
+ const v = "0.8.17";
32694
32761
  if (typeof v === "string" && /^\d+\.\d+\.\d+/.test(v)) {
32695
32762
  return v;
32696
32763
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andyqiu/codeforge",
3
- "version": "0.8.15",
3
+ "version": "0.8.17",
4
4
  "description": "CodeForge — opencode 的零侵入扩展包",
5
5
  "type": "module",
6
6
  "private": false,