@andyqiu/codeforge 0.5.13 → 0.5.15

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/bin/codeforge.mjs CHANGED
@@ -115,7 +115,7 @@ function installOpencode({ scope, dryRun, extraArgs, quiet = false }) {
115
115
  if (dryRun) flagArgs.push("--dry-run")
116
116
  }
117
117
  const all = [...baseArgs, ...flagArgs, ...(extraArgs ?? [])]
118
- log(`opencode: ${cmd} ${all.join(" ")}`)
118
+ if (!quiet) log(`opencode: ${cmd} ${all.join(" ")}`)
119
119
  const r = spawnSync(cmd, all, { stdio: quiet ? "pipe" : "inherit", cwd: REPO_ROOT })
120
120
  if (quiet && r.status !== 0) {
121
121
  if (r.stderr) process.stderr.write(r.stderr)
package/dist/index.js CHANGED
@@ -13269,8 +13269,8 @@ async function mergeSessionBack(opts) {
13269
13269
  });
13270
13270
  throw new Error(`mergeSessionBack: squash merge 失败(已 reset 主仓兜底): ${err.message}`);
13271
13271
  }
13272
- const hasDevOnce = await packageHasScript(mainRoot, "dev:once");
13273
- if (hasDevOnce) {
13272
+ const buildScript = await getBuildScript(mainRoot);
13273
+ if (buildScript) {
13274
13274
  const stagedRaw = await runGit2(mainRoot, [
13275
13275
  "diff",
13276
13276
  "--cached",
@@ -13281,18 +13281,18 @@ async function mergeSessionBack(opts) {
13281
13281
  const canSkipDevOnce = await shouldSkipDevOnce(mainRoot, stagedPaths);
13282
13282
  if (canSkipDevOnce) {
13283
13283
  const sourceCount = stagedPaths.filter((p) => /^(plugins|lib|src)\//.test(p) && !/\.(md|test\.ts)$/.test(p)).length;
13284
- console.log(`[session-worktree] skip dev:once: dist 已是最新(${sourceCount} staged 源文件 mtime <= dist mtime)`);
13284
+ console.log(`[session-worktree] skip ${buildScript}: dist 已是最新(${sourceCount} staged 源文件 mtime <= dist mtime)`);
13285
13285
  } else {
13286
13286
  try {
13287
- await runCmd("npm", ["run", "dev:once"], mainRoot);
13287
+ await runCmd("npm", ["run", buildScript], mainRoot);
13288
13288
  } catch (err) {
13289
13289
  await runGit2(mainRoot, ["reset", "--hard", "HEAD"]).catch(() => {});
13290
13290
  const msg = err instanceof Error ? err.message : String(err);
13291
- throw new Error(`dev:once 失败已 reset 主仓: ${msg}`);
13291
+ throw new Error(`${buildScript} 失败已 reset 主仓: ${msg}`);
13292
13292
  }
13293
13293
  }
13294
13294
  } else {
13295
- console.log(`[session-worktree] skip dev:once: not configured in ${mainRoot}/package.json`);
13295
+ console.warn(`[session-worktree] skip build step: neither build:dev nor dev:once configured in ${mainRoot}/package.json`);
13296
13296
  }
13297
13297
  const squashedRaw = await runGit2(wt, ["log", "--format=%s", `${baseSha}..HEAD`]);
13298
13298
  const squashedCommits = squashedRaw.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
@@ -13430,6 +13430,13 @@ async function packageHasScript(mainRoot, scriptName) {
13430
13430
  return false;
13431
13431
  }
13432
13432
  }
13433
+ async function getBuildScript(mainRoot) {
13434
+ if (await packageHasScript(mainRoot, "build:dev"))
13435
+ return "build:dev";
13436
+ if (await packageHasScript(mainRoot, "dev:once"))
13437
+ return "dev:once";
13438
+ return null;
13439
+ }
13433
13440
  async function shouldSkipDevOnce(mainRoot, stagedPaths) {
13434
13441
  let distMtimeSec;
13435
13442
  try {
@@ -21355,23 +21362,27 @@ var RISK_PATTERNS = [
21355
21362
  },
21356
21363
  {
21357
21364
  tag: "write_secrets",
21358
- re: /(\.env(?:\.\w+)?|id_[edr]sa|\.ssh\/id_|\.pem|\.p12|secret\.json)/i,
21359
- matchOn: ["command", "filePath", "path"]
21365
+ kinds: ["bash", "edit"],
21366
+ re: /(?:^|[\s/"'])(id_[edr]sa|secret\.json|\.env(?:\.\w+)?)(?:$|[\s"'/])|[\w./-]+\.(?:pem|p12)(?:$|[\s"'])|\.ssh\/id_/i,
21367
+ matchOn: ["command", "filePath", "path", "target"]
21360
21368
  },
21361
21369
  {
21362
21370
  tag: "write_etc",
21363
21371
  kinds: ["bash", "edit"],
21364
- re: /(?:^|\s|"|')\/etc\//
21372
+ re: /(?:^|\s|"|')\/etc\//,
21373
+ matchOn: ["command", "filePath", "path"]
21365
21374
  },
21366
21375
  {
21367
21376
  tag: "write_usr",
21368
21377
  kinds: ["bash", "edit"],
21369
- re: /(?:^|\s|"|')\/usr\//
21378
+ re: /(?:^|\s|"|')\/usr\//,
21379
+ matchOn: ["command", "filePath", "path"]
21370
21380
  },
21371
21381
  {
21372
21382
  tag: "write_root_home",
21373
21383
  kinds: ["bash", "edit"],
21374
- re: /(?:^|\s|"|')(\/root|\/home\/root)\//
21384
+ re: /(?:^|\s|"|')(\/root|\/home\/root)\//,
21385
+ matchOn: ["command", "filePath", "path"]
21375
21386
  },
21376
21387
  {
21377
21388
  tag: "internal_url",
@@ -21774,7 +21785,7 @@ import * as zlib from "node:zlib";
21774
21785
  // lib/version-injected.ts
21775
21786
  function getInjectedVersion() {
21776
21787
  try {
21777
- const v = "0.5.13";
21788
+ const v = "0.5.15";
21778
21789
  if (typeof v === "string" && /^\d+\.\d+\.\d+/.test(v)) {
21779
21790
  return v;
21780
21791
  }
@@ -23054,14 +23065,18 @@ var handler24 = workflowEngineServer;
23054
23065
  import path27 from "node:path";
23055
23066
  var PLUGIN_NAME25 = "session-worktree-guard";
23056
23067
  logLifecycle(PLUGIN_NAME25, "import", {});
23057
- var WRITE_INTENT_RE = />(?![=&])|\btee\b|\brm\b|\bmv\b|\bcp\b|\bmkdir\b|\btouch\b|\bchmod\b|\bchown\b|\bln\b/;
23058
- var READ_ONLY_COMMANDS = /^\s*(?:ls|cat|head|tail|grep|rg|find|fd|wc|stat|file|which|whereis|echo|pwd|cd|pushd|popd|env|printenv|type|less|more|sort|uniq|awk|tr|cut|jq|date|whoami|id|uname|git(?:\s+-C\s+\S+)?\s+(?:log|show|diff|status|branch|tag|remote|config\s+--get|rev-parse|rev-list|ls-files|ls-tree|cat-file|describe|reflog|blame|shortlog|name-rev|symbolic-ref|merge-base|worktree\s+list|stash\s+list|stash\s+show))\b/;
23059
- var SIDE_EFFECT_TOKEN_RE = />(?![=&])|\|\s*tee\b|\btee\b/;
23068
+ var WRITE_INTENT_RE = />(?![=&])(?!\s*\/dev\/(?:null|stdout|stderr|fd\/\d+)\b)|\btee\b|\brm\b|\bmv\b|\bcp\b|\bmkdir\b|\btouch\b|\bchmod\b|\bchown\b|\bln\b/;
23069
+ var READ_ONLY_COMMANDS = /^\s*(?:ls|cat|head|tail|grep|rg|find|fd|wc|stat|file|which|whereis|echo|pwd|cd|pushd|popd|env|printenv|type|less|more|sort|uniq|awk|tr|cut|jq|date|whoami|id|uname|node|npx|tsc|diff|python3?|git(?:\s+-C\s+\S+)?\s+(?:log|show|diff|status|branch|tag|remote|config\s+--get|rev-parse|rev-list|ls-files|ls-tree|cat-file|describe|reflog|blame|shortlog|name-rev|symbolic-ref|merge-base|worktree\s+list|stash\s+list|stash\s+show))\b/;
23070
+ var SIDE_EFFECT_TOKEN_RE = />(?![=&])(?!\s*\/dev\/(?:null|stdout|stderr|fd\/\d+)\b)|\|\s*tee\b|\btee\b/;
23060
23071
  function isReadOnlyBashCommand(command) {
23061
23072
  if (!READ_ONLY_COMMANDS.test(command))
23062
23073
  return false;
23063
23074
  if (SIDE_EFFECT_TOKEN_RE.test(command))
23064
23075
  return false;
23076
+ for (const re of INTERPRETER_WRITE_RES) {
23077
+ if (re.test(command))
23078
+ return false;
23079
+ }
23065
23080
  return true;
23066
23081
  }
23067
23082
  var INTERPRETER_WRITE_RES = [
@@ -23069,6 +23084,9 @@ var INTERPRETER_WRITE_RES = [
23069
23084
  /node.*writeFile/,
23070
23085
  /perl.*>/
23071
23086
  ];
23087
+ function stripCommitMessageArgs(command) {
23088
+ return command.replace(/(?:^|\s)(?:-m|--message|-F|--file)(?:=|\s+)("[^"]*"|'[^']*')/g, " ");
23089
+ }
23072
23090
  function escapeRegex2(s) {
23073
23091
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
23074
23092
  }
@@ -23157,13 +23175,14 @@ function commandContainsMainRootExcludingWorktree(command, mainRoot, worktreePat
23157
23175
  function detectBashWriteIntent(command, mainRoot) {
23158
23176
  if (isReadOnlyBashCommand(command))
23159
23177
  return false;
23160
- if (WRITE_INTENT_RE.test(command))
23178
+ const sanitized = stripCommitMessageArgs(command);
23179
+ if (WRITE_INTENT_RE.test(sanitized))
23161
23180
  return true;
23162
23181
  for (const re of INTERPRETER_WRITE_RES) {
23163
- if (re.test(command))
23182
+ if (re.test(sanitized))
23164
23183
  return true;
23165
23184
  }
23166
- if (buildGitVcsWriteRegex(mainRoot).test(command))
23185
+ if (buildGitVcsWriteRegex(mainRoot).test(sanitized))
23167
23186
  return true;
23168
23187
  return false;
23169
23188
  }
@@ -23177,13 +23196,14 @@ function isWriteOperation(toolName, argsObj, mainRoot) {
23177
23196
  return false;
23178
23197
  if (isReadOnlyBashCommand(command))
23179
23198
  return false;
23180
- if (WRITE_INTENT_RE.test(command))
23199
+ const sanitized = stripCommitMessageArgs(command);
23200
+ if (WRITE_INTENT_RE.test(sanitized))
23181
23201
  return true;
23182
23202
  for (const re of INTERPRETER_WRITE_RES) {
23183
- if (re.test(command))
23203
+ if (re.test(sanitized))
23184
23204
  return true;
23185
23205
  }
23186
- if (mainRoot && buildGitVcsWriteRegex(mainRoot).test(command))
23206
+ if (mainRoot && buildGitVcsWriteRegex(mainRoot).test(sanitized))
23187
23207
  return true;
23188
23208
  return false;
23189
23209
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andyqiu/codeforge",
3
- "version": "0.5.13",
3
+ "version": "0.5.15",
4
4
  "description": "CodeForge — opencode 的零侵入扩展包",
5
5
  "type": "module",
6
6
  "private": false,
@@ -33,7 +33,7 @@
33
33
  "build:types": "tsc -p tsconfig.build.json --emitDeclarationOnly",
34
34
  "build:watch": "node ./scripts/build-with-version.mjs --watch",
35
35
  "dev": "node ./scripts/dev-sync.mjs --watch",
36
- "dev:once": "node ./scripts/dev-sync.mjs",
36
+ "build:dev": "node ./scripts/dev-sync.mjs",
37
37
  "dev-sync": "node ./scripts/dev-sync.mjs",
38
38
  "dev-sync:watch": "node ./scripts/dev-sync.mjs --watch",
39
39
  "typecheck": "tsc -p tsconfig.json --noEmit",
@@ -72,14 +72,9 @@
72
72
  "replay": "node ./scripts/codeforge-replay.mjs",
73
73
  "release": "node ./scripts/release.mjs",
74
74
  "release:dry": "node ./scripts/release.mjs --dry-run",
75
- "publish:patch": "bash ./scripts/publish.sh patch",
76
- "publish:minor": "bash ./scripts/publish.sh minor",
77
- "publish:major": "bash ./scripts/publish.sh major",
78
- "publish:dry": "bash ./scripts/publish.sh --dry-run",
79
- "publish:win:patch": "powershell -NoProfile -ExecutionPolicy Bypass -File ./scripts/publish.ps1",
80
- "publish:win:minor": "powershell -NoProfile -ExecutionPolicy Bypass -File ./scripts/publish.ps1 -Minor",
81
- "publish:win:major": "powershell -NoProfile -ExecutionPolicy Bypass -File ./scripts/publish.ps1 -Major",
82
- "publish:win:dry": "powershell -NoProfile -ExecutionPolicy Bypass -File ./scripts/publish.ps1 -DryRun",
75
+ "release:patch": "node ./scripts/release.mjs patch",
76
+ "release:minor": "node ./scripts/release.mjs minor",
77
+ "release:major": "node ./scripts/release.mjs major",
83
78
  "prepublishOnly": "npm run build && npm run typecheck && npm test",
84
79
  "prepare": "husky"
85
80
  },
@@ -25,6 +25,7 @@
25
25
  import { promises as fs } from "node:fs"
26
26
  import * as path from "node:path"
27
27
  import * as url from "node:url"
28
+ import { syncToGlobalOpencode } from "./dev-sync.mjs"
28
29
 
29
30
  const args = new Set(process.argv.slice(2))
30
31
  const CHECK = args.has("--check")
@@ -99,6 +100,13 @@ async function main() {
99
100
  process.exit(1)
100
101
  }
101
102
  }
103
+
104
+ // 写盘完成后,把更新后的 agents/*.md 同步到 ~/.config/opencode/agents/
105
+ // 仅在非 DRY/CHECK 模式且有 .codeforge/.dev-marker 时生效(dev-sync.mjs 内部检查 marker)
106
+ if (!DRY && summary.changed.length > 0) {
107
+ console.log("[sync-models] 同步已更新 agents/*.md -> 全局 opencode (ADR:dev-sync-to-global)")
108
+ await syncToGlobalOpencode({ projectRoot: ROOT, dryRun: false })
109
+ }
102
110
  }
103
111
 
104
112
  /**