easy-coding-harness 0.5.1 → 0.5.3

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/CHANGELOG.md CHANGED
@@ -6,11 +6,19 @@
6
6
  - `y`:常规功能升级
7
7
  - `z`:日常 bug 修复
8
8
 
9
- ## 0.5.1
9
+ ## 0.5.3
10
10
 
11
- - 修复 Claude Code hook 命令使用相对路径导致在子目录 cwd 下找不到 `.claude/hooks/*.py` 的问题;Claude、Codex、Qoder hook 配置现在安装时渲染为带引号的绝对脚本路径。
12
- - `easy-coding upgrade` 可直接刷新存量 0.5.0 项目的 hook 配置并解决该问题;升级后 `ec-init` 合规扫描会校验 hook 配置是否已使用当前版本要求的绝对路径。
13
- - `easy-coding clear` `install-manifest.json` 兼容新绝对 hook 命令,清理时仍按项目相对 hook 路径识别托管注册项。
11
+ - 根治 `task.json` 绝对路径泄漏:`.easy-coding/tasks/project-init/task.json` 不再写入本机仓库绝对路径。该 `project_path` 字段无任何消费方,直接移除,可提交产物彻底去本地化。
12
+ - `easy-coding upgrade` 会自动剥离存量项目中遗留的 `project_path` 字段,让老项目也随升级变干净。
13
+ - 根治 hook 编译产物:hook launcher 设置 `sys.dont_write_bytecode=True`,运行时不再在 `.claude/hooks/`、`.codex/hooks/`、`.qoder/hooks/` 旁生成 `__pycache__/*.pyc`;同时 `init` / `add-agent` / `upgrade` 会向项目 `.gitignore` 追加 `__pycache__/` 作为兜底防御。launcher 内容变化会触发 `upgrade` 刷新存量 hook 注册。
14
+ - 版本自 `0.5.2-beta.0` 升级为 `0.5.3`,合并 beta.0 的全部 portable hook 修复。跳过 `0.5.2` 正式版:升级检测的 `compareVersions` 会截断预发布后缀,若发 `0.5.2` 会把已安装 `0.5.2-beta.0` 的项目误判为已最新而不提示本次迁移;递增到 `0.5.3` 可让 beta 用户正确检测到升级。
15
+
16
+ ## 0.5.2-beta.0
17
+
18
+ - 修复 Claude Code hook 命令使用直接相对路径导致在子目录 cwd 下找不到 `.claude/hooks/*.py` 的问题;Claude、Codex、Qoder 的 hook 配置现在使用可共享的 portable relative launcher,并绑定 `.easy-coding/config.yaml` 的 `project.id`,避免把本机仓库绝对路径写入可提交配置,同时防止 supermodule 父仓 hook 被子仓 cwd 错路由。
19
+ - `easy-coding upgrade` 可直接刷新存量 0.5.0 项目和已安装 0.5.1 beta 绝对路径配置;同版本也会修复缺失、重复、事件错位或 stale 的托管 hook 注册。
20
+ - `easy-coding clear` 和 `install-manifest.json` 兼容 portable launcher、旧直接相对命令和旧绝对 hook 命令,清理时仍按项目相对 hook 路径识别托管注册项。
21
+ - npm `0.5.1` 已发布不可覆盖,本版本作为 beta 修复包发布,用于替代原 `0.5.1` beta。
14
22
 
15
23
  ## 0.5.0
16
24
 
package/dist/cli.js CHANGED
@@ -61,6 +61,7 @@ function colorizeBanner(art) {
61
61
  }
62
62
 
63
63
  // src/utils/config-yaml.ts
64
+ import { randomUUID } from "crypto";
64
65
  import { readFile as readFile2 } from "fs/promises";
65
66
  import YAML, { isScalar, isSeq, parseDocument } from "yaml";
66
67
 
@@ -108,6 +109,7 @@ function createDefaultConfig(params) {
108
109
  harness_version: params.harnessVersion,
109
110
  agents: params.agents,
110
111
  project: {
112
+ id: params.projectId ?? createProjectId(),
111
113
  name: params.projectName
112
114
  },
113
115
  memory: {
@@ -138,6 +140,14 @@ async function readConfigYaml(filePath) {
138
140
  const content = await readFile2(filePath, "utf8");
139
141
  return YAML.parse(content);
140
142
  }
143
+ async function readProjectIdIfExists(filePath) {
144
+ try {
145
+ const config = await readConfigYaml(filePath);
146
+ return typeof config.project?.id === "string" && config.project.id.trim() ? config.project.id : null;
147
+ } catch {
148
+ return null;
149
+ }
150
+ }
141
151
  async function updateConfigYaml(filePath, updater) {
142
152
  const content = await readFile2(filePath, "utf8");
143
153
  const document = parseDocument(content);
@@ -160,11 +170,27 @@ async function updateHarnessVersion(filePath, version) {
160
170
  config.harness_version = version;
161
171
  });
162
172
  }
173
+ async function ensureProjectId(filePath) {
174
+ let projectId = "";
175
+ await updateConfigYaml(filePath, (config) => {
176
+ if (!config.project || typeof config.project !== "object") {
177
+ config.project = { id: createProjectId(), name: "" };
178
+ }
179
+ if (typeof config.project.id !== "string" || !config.project.id.trim()) {
180
+ config.project.id = createProjectId();
181
+ }
182
+ projectId = config.project.id;
183
+ });
184
+ return projectId;
185
+ }
163
186
  async function updateSupermoduleConfig(filePath, supermodule) {
164
187
  return updateConfigYaml(filePath, (config) => {
165
188
  config.supermodule = supermodule;
166
189
  });
167
190
  }
191
+ function createProjectId() {
192
+ return `ec-${randomUUID()}`;
193
+ }
168
194
 
169
195
  // src/utils/gitignore.ts
170
196
  import path3 from "path";
@@ -182,6 +208,7 @@ var MAIN_SPEC_DIR = "main";
182
208
  var DEV_SPEC_DIR = "dev";
183
209
  var TEMPLATES_DIR = "templates";
184
210
  var SESSIONS_GITIGNORE_ENTRY = ".easy-coding/sessions/";
211
+ var HOOK_BYTECODE_GITIGNORE_ENTRY = "__pycache__/";
185
212
  var GENERATED_REGION_START = "<!-- \u2550\u2550\u2550 easy-coding-harness generated (DO NOT EDIT BETWEEN MARKERS) \u2550\u2550\u2550 -->";
186
213
  var GENERATED_REGION_END = "<!-- \u2550\u2550\u2550 end easy-coding-harness generated \u2550\u2550\u2550 -->";
187
214
 
@@ -201,6 +228,13 @@ async function ensureGitignoreEntry(cwd, entry, heading = "# \u2550\u2550\u2550
201
228
  async function ensureEasyCodingSessionsIgnored(cwd) {
202
229
  return ensureGitignoreEntry(cwd, SESSIONS_GITIGNORE_ENTRY);
203
230
  }
231
+ async function ensureHookBytecodeIgnored(cwd) {
232
+ return ensureGitignoreEntry(
233
+ cwd,
234
+ HOOK_BYTECODE_GITIGNORE_ENTRY,
235
+ "# \u2550\u2550\u2550 easy-coding-harness (auto-generated) \u2550\u2550\u2550\n# Python bytecode from hook scripts; do not commit"
236
+ );
237
+ }
204
238
 
205
239
  // src/utils/install-manifest.ts
206
240
  import { createHash } from "crypto";
@@ -568,17 +602,21 @@ async function writeRuntimeScaffold(cwd, agents, opts = {}) {
568
602
  const easyCodingDir = path6.join(cwd, EASY_CODING_DIR);
569
603
  await ensureDir(easyCodingDir);
570
604
  const configPath2 = path6.join(easyCodingDir, CONFIG_FILE);
605
+ let projectId = opts.projectId ?? createProjectId();
571
606
  if (!await pathExists(configPath2)) {
572
607
  const projectName = path6.basename(cwd);
573
608
  await writeConfigYaml(
574
609
  configPath2,
575
610
  createDefaultConfig({
576
611
  projectName,
612
+ projectId,
577
613
  harnessVersion: VERSION,
578
614
  agents,
579
615
  supermodule: opts.supermodule
580
616
  })
581
617
  );
618
+ } else {
619
+ projectId = await ensureProjectId(configPath2);
582
620
  }
583
621
  await ensureDir(path6.join(easyCodingDir, "tasks"));
584
622
  await ensureDir(path6.join(easyCodingDir, SESSIONS_DIR));
@@ -586,6 +624,7 @@ async function writeRuntimeScaffold(cwd, agents, opts = {}) {
586
624
  await ensureDir(path6.join(easyCodingDir, SPEC_DIR, DEV_SPEC_DIR));
587
625
  await writeMemoryScaffold(easyCodingDir);
588
626
  await writeTemplatesScaffold(easyCodingDir);
627
+ return projectId;
589
628
  }
590
629
  async function writeTemplatesScaffold(easyCodingDir) {
591
630
  const templatesDir = path6.join(easyCodingDir, TEMPLATES_DIR);
@@ -627,7 +666,6 @@ function createProjectInitTask(params) {
627
666
  context: {
628
667
  agents_installed: params.agents,
629
668
  cli_version: VERSION,
630
- project_path: params.cwd,
631
669
  init_source: params.initSource ?? "fresh",
632
670
  ...params.legacyAssets ? { legacy_assets: params.legacyAssets } : {},
633
671
  ...params.legacyMissingHarnessFiles ? { legacy_missing_harness_files: params.legacyMissingHarnessFiles } : {}
@@ -648,7 +686,6 @@ async function writeProjectInitTask(cwd, agents, options = {}) {
648
686
  await writeTaskJson(
649
687
  getTaskJsonPath(cwd, PROJECT_INIT_TASK_ID),
650
688
  createProjectInitTask({
651
- cwd,
652
689
  agents,
653
690
  initSource: options.initSource,
654
691
  legacyAssets: options.legacyAssets,
@@ -656,6 +693,15 @@ async function writeProjectInitTask(cwd, agents, options = {}) {
656
693
  })
657
694
  );
658
695
  }
696
+ async function stripInitTaskProjectPath(cwd) {
697
+ const filePath = getTaskJsonPath(cwd, PROJECT_INIT_TASK_ID);
698
+ if (!await pathExists(filePath)) return false;
699
+ const task = await readTaskJson(filePath);
700
+ if (!task.context || !("project_path" in task.context)) return false;
701
+ task.context.project_path = void 0;
702
+ await writeTaskJson(filePath, task);
703
+ return true;
704
+ }
659
705
  async function setPendingInitSince(cwd, version) {
660
706
  const filePath = getTaskJsonPath(cwd, PROJECT_INIT_TASK_ID);
661
707
  if (!await pathExists(filePath)) return;
@@ -762,17 +808,28 @@ function resolvePlaceholders(content, ctx, contentName = "template") {
762
808
  }
763
809
  return resolved;
764
810
  }
765
- function withInstallPaths(cwd, ctx) {
766
- const hooksDir = toPosixPath(path8.resolve(cwd, ctx.platform_config_dir, "hooks"));
811
+ async function withProjectInstallPaths(cwd, ctx, projectId) {
812
+ const resolvedProjectId = projectId ?? await readProjectIdIfExists(path8.join(cwd, EASY_CODING_DIR, CONFIG_FILE));
813
+ return withInstallPaths(cwd, ctx, resolvedProjectId ?? void 0);
814
+ }
815
+ function withInstallPaths(cwd, ctx, projectId) {
767
816
  return {
768
817
  ...ctx,
769
- platform_hooks_dir_abs: hooksDir,
770
- platform_hooks_dir_shell: jsonStringContent(shellDoubleQuoteArg(hooksDir))
818
+ platform_hook_session_start_command: jsonStringContent(
819
+ renderHookCommand(cwd, ctx, "session-start.py", process.platform, projectId)
820
+ ),
821
+ platform_hook_inject_workflow_state_command: jsonStringContent(
822
+ renderHookCommand(cwd, ctx, "inject-workflow-state.py", process.platform, projectId)
823
+ ),
824
+ platform_hook_inject_subagent_context_command: jsonStringContent(
825
+ renderHookCommand(cwd, ctx, "inject-subagent-context.py", process.platform, projectId)
826
+ )
771
827
  };
772
828
  }
773
- function renderHookCommand(cwd, ctx, scriptName, platform = process.platform) {
774
- const hooksDir = toPosixPath(path8.resolve(cwd, ctx.platform_config_dir, "hooks"));
775
- return `${ctx.python_cmd} ${shellDoubleQuoteArg(hooksDir, platform)}/${scriptName}`;
829
+ function renderHookCommand(_cwd, ctx, scriptName, platform = process.platform, projectId) {
830
+ const launcher = `import base64;exec(base64.b64decode('${Buffer.from(HOOK_LAUNCHER_PYTHON).toString("base64")}').decode())`;
831
+ const rootIdArg = projectId ? ` ${shellDoubleQuoteArg(projectId, platform)}` : "";
832
+ return `${ctx.python_cmd} -c ${shellDoubleQuoteArg(launcher, platform)}${rootIdArg} ${ctx.platform_config_dir}/hooks/${scriptName}`;
776
833
  }
777
834
  function shellDoubleQuoteArg(value, platform = process.platform) {
778
835
  if (platform === "win32") {
@@ -783,12 +840,58 @@ function shellDoubleQuoteArg(value, platform = process.platform) {
783
840
  }
784
841
  return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
785
842
  }
786
- function toPosixPath(filePath) {
787
- return filePath.split(path8.sep).join("/");
788
- }
789
843
  function jsonStringContent(value) {
790
844
  return JSON.stringify(value).slice(1, -1);
791
845
  }
846
+ var HOOK_LAUNCHER_PYTHON = [
847
+ "import io,json,os,re,runpy,sys",
848
+ "sys.dont_write_bytecode=True",
849
+ "from pathlib import Path",
850
+ "payload=sys.stdin.read()",
851
+ "try:",
852
+ " data=json.loads(payload or '{}')",
853
+ "except Exception:",
854
+ " data={}",
855
+ "expected_id=sys.argv[1] if len(sys.argv) > 2 else None",
856
+ "script_arg=sys.argv[2] if len(sys.argv) > 2 else (sys.argv[1] if len(sys.argv) > 1 else None)",
857
+ "def project_id(root):",
858
+ " try:",
859
+ " lines=(root / '.easy-coding' / 'config.yaml').read_text(encoding='utf-8').splitlines()",
860
+ " except Exception:",
861
+ " return None",
862
+ " in_project=False",
863
+ " for line in lines:",
864
+ " if not in_project:",
865
+ " if re.match(r'^project\\s*:\\s*(?:#.*)?$', line):",
866
+ " in_project=True",
867
+ " continue",
868
+ " if not line.strip() or line.lstrip().startswith('#'):",
869
+ " continue",
870
+ " if not line.startswith((' ', '\\t')):",
871
+ " return None",
872
+ ` match=re.match(r"\\s+id\\s*:\\s*['\\"]?([^'\\"#\\s]+)", line)`,
873
+ " if match:",
874
+ " return match.group(1)",
875
+ " return None",
876
+ "def roots_from(raw):",
877
+ " if not raw:",
878
+ " return []",
879
+ " start=Path(raw).resolve()",
880
+ " return [candidate for candidate in (start, *start.parents) if (candidate / '.easy-coding').is_dir()]",
881
+ "sources=[os.environ.get('CLAUDE_PROJECT_DIR'), os.environ.get('QODER_PROJECT_DIR'), data.get('cwd'), os.getcwd()]",
882
+ "root=None",
883
+ "if expected_id:",
884
+ " root=next((candidate for raw in sources for candidate in roots_from(raw) if project_id(candidate) == expected_id), None)",
885
+ "else:",
886
+ " root=next((candidate for raw in sources for candidate in roots_from(raw)), None)",
887
+ "target=(root / script_arg) if root and script_arg else None",
888
+ "if target and target.is_file():",
889
+ " os.chdir(root)",
890
+ " data['cwd']=str(root)",
891
+ " sys.stdin=io.StringIO(json.dumps(data))",
892
+ " sys.path.insert(0, str(target.parent))",
893
+ " runpy.run_path(str(target), run_name='__main__')"
894
+ ].join("\n");
792
895
  async function resolveSkills(ctx) {
793
896
  const skillsRoot = getTemplatePath("common", "skills");
794
897
  const entries = await readdir2(skillsRoot);
@@ -944,7 +1047,7 @@ function stripTemplateExtension(fileName) {
944
1047
  async function configureClaude(cwd, opts = {}) {
945
1048
  const platform = "claude-code";
946
1049
  const meta = PLATFORM_META[platform];
947
- const ctx = withInstallPaths(cwd, meta.templateContext);
1050
+ const ctx = await withProjectInstallPaths(cwd, meta.templateContext, opts.projectId);
948
1051
  const dest = path9.join(cwd, ".claude");
949
1052
  const hookConfigPath = path9.join(cwd, meta.hookConfigFile);
950
1053
  const artifacts = [];
@@ -982,7 +1085,7 @@ import path10 from "path";
982
1085
  async function configureCodex(cwd, opts = {}) {
983
1086
  const platform = "codex";
984
1087
  const meta = PLATFORM_META[platform];
985
- const ctx = withInstallPaths(cwd, meta.templateContext);
1088
+ const ctx = await withProjectInstallPaths(cwd, meta.templateContext, opts.projectId);
986
1089
  const hookConfigPath = path10.join(cwd, meta.hookConfigFile);
987
1090
  const artifacts = [];
988
1091
  artifacts.push(
@@ -1098,7 +1201,7 @@ async function existingManagedSkillArtifacts(skillsDir, skillNames, platform) {
1098
1201
  async function configureQoder(cwd, opts = {}) {
1099
1202
  const platform = "qoder";
1100
1203
  const meta = resolvePlatformMeta(cwd, platform);
1101
- const ctx = withInstallPaths(cwd, meta.templateContext);
1204
+ const ctx = await withProjectInstallPaths(cwd, meta.templateContext, opts.projectId);
1102
1205
  const dest = path12.join(cwd, ctx.platform_config_dir);
1103
1206
  const hookConfigPath = path12.join(cwd, meta.hookConfigFile);
1104
1207
  const artifacts = [];
@@ -1149,17 +1252,22 @@ var CONFIGURATORS = {
1149
1252
  };
1150
1253
 
1151
1254
  // src/commands/install-harness.ts
1152
- async function configurePlatformsForDir(targetDir, platforms, boundary) {
1255
+ async function configurePlatformsForDir(targetDir, platforms, opts = {}) {
1153
1256
  const artifacts = [];
1154
1257
  for (const platform of platforms) {
1155
- artifacts.push(...await CONFIGURATORS[platform](targetDir, { supermodule: boundary }));
1258
+ artifacts.push(...await CONFIGURATORS[platform](targetDir, opts));
1156
1259
  }
1157
1260
  return artifacts;
1158
1261
  }
1159
1262
  async function installHarnessToDir(targetDir, platforms, ctx) {
1160
- const artifacts = await configurePlatformsForDir(targetDir, platforms, boundaryFromContext(ctx));
1263
+ const projectId = createProjectId();
1264
+ const artifacts = await configurePlatformsForDir(targetDir, platforms, {
1265
+ supermodule: boundaryFromContext(ctx),
1266
+ projectId
1267
+ });
1161
1268
  await writeRuntimeScaffold(targetDir, platforms, {
1162
- supermodule: supermoduleConfigFromContext(ctx)
1269
+ supermodule: supermoduleConfigFromContext(ctx),
1270
+ projectId
1163
1271
  });
1164
1272
  await writeInstallManifest(targetDir, {
1165
1273
  harnessVersion: VERSION,
@@ -1172,6 +1280,7 @@ async function installHarnessToDir(targetDir, platforms, ctx) {
1172
1280
  legacyMissingHarnessFiles: ctx.legacyMissingHarnessFiles
1173
1281
  });
1174
1282
  await ensureEasyCodingSessionsIgnored(targetDir);
1283
+ await ensureHookBytecodeIgnored(targetDir);
1175
1284
  return artifacts;
1176
1285
  }
1177
1286
  function supermoduleConfigFromContext(ctx) {
@@ -1653,14 +1762,13 @@ async function addAgent(opts) {
1653
1762
  const toInstall = platforms.filter((platform) => !config.agents.includes(platform));
1654
1763
  const agents = [...config.agents, ...toInstall];
1655
1764
  if (toInstall.length > 0) {
1656
- const artifacts = await configurePlatformsForDir(
1657
- target.dir,
1658
- toInstall,
1659
- target.boundary
1660
- );
1661
- await writeRuntimeScaffold(target.dir, agents, {
1765
+ const projectId = await writeRuntimeScaffold(target.dir, agents, {
1662
1766
  supermodule: target.supermodule
1663
1767
  });
1768
+ const artifacts = await configurePlatformsForDir(target.dir, toInstall, {
1769
+ supermodule: target.boundary,
1770
+ projectId
1771
+ });
1664
1772
  await writeInstallManifest(target.dir, {
1665
1773
  harnessVersion: VERSION,
1666
1774
  agents: toInstall,
@@ -1668,6 +1776,7 @@ async function addAgent(opts) {
1668
1776
  mode: "merge"
1669
1777
  });
1670
1778
  await ensureEasyCodingSessionsIgnored(target.dir);
1779
+ await ensureHookBytecodeIgnored(target.dir);
1671
1780
  await addAgentsToConfig(target.configPath, toInstall);
1672
1781
  await setPendingInitSince(target.dir, VERSION);
1673
1782
  installedLabels.push(`${target.label}: ${toInstall.join(", ")}`);
@@ -2757,23 +2866,24 @@ async function upgrade(opts) {
2757
2866
  }
2758
2867
  }
2759
2868
  for (const { target, config } of pending) {
2760
- const artifacts = await configurePlatformsForDir(
2761
- target.dir,
2762
- config.agents,
2763
- target.boundary
2764
- );
2765
- await writeRuntimeScaffold(target.dir, config.agents, {
2869
+ const projectId = await writeRuntimeScaffold(target.dir, config.agents, {
2766
2870
  supermodule: target.supermodule
2767
2871
  });
2872
+ const artifacts = await configurePlatformsForDir(target.dir, config.agents, {
2873
+ supermodule: target.boundary,
2874
+ projectId
2875
+ });
2768
2876
  await writeInstallManifest(target.dir, {
2769
2877
  harnessVersion: VERSION,
2770
2878
  agents: config.agents,
2771
2879
  artifacts
2772
2880
  });
2773
2881
  await ensureEasyCodingSessionsIgnored(target.dir);
2882
+ await ensureHookBytecodeIgnored(target.dir);
2774
2883
  await updateHarnessVersion(target.configPath, VERSION);
2775
2884
  await updateSupermoduleConfig(target.configPath, target.supermodule);
2776
2885
  await setPendingInitSince(target.dir, VERSION);
2886
+ await stripInitTaskProjectPath(target.dir);
2777
2887
  }
2778
2888
  if (parentTopologyRefresh) {
2779
2889
  await refreshSupermoduleParent(
@@ -2811,15 +2921,19 @@ async function resolvePendingUpgradeTargets(targets) {
2811
2921
  `${target.label} harness version ${installedVersion} is newer than CLI ${VERSION}. Update the CLI first.`
2812
2922
  );
2813
2923
  }
2814
- if (relation === -1 || relation === 0 && await needsHookConfigRefresh(target, config.agents)) {
2924
+ if (relation === -1 || relation === 0 && await needsHookConfigRefresh(target, config)) {
2815
2925
  pending.push({ target, config });
2816
2926
  }
2817
2927
  }
2818
2928
  return pending;
2819
2929
  }
2820
- async function needsHookConfigRefresh(target, agents) {
2930
+ async function needsHookConfigRefresh(target, config) {
2931
+ const projectId = typeof config.project?.id === "string" ? config.project.id.trim() : "";
2932
+ if (!projectId) {
2933
+ return true;
2934
+ }
2821
2935
  const manifest = await readInstallManifest(target.dir);
2822
- for (const agent of agents) {
2936
+ for (const agent of config.agents) {
2823
2937
  const meta = resolvePlatformMeta(target.dir, agent);
2824
2938
  const configPath2 = path22.join(target.dir, meta.hookConfigFile);
2825
2939
  const content = await readTextIfExists(configPath2);
@@ -2834,7 +2948,7 @@ async function needsHookConfigRefresh(target, agents) {
2834
2948
  }
2835
2949
  const commandsByEvent = collectHookCommandsByEvent(parsed.hooks);
2836
2950
  const commands = [...commandsByEvent.values()].flat();
2837
- const expectedRegistrations = expectedHookRegistrations(target.dir, meta, agent);
2951
+ const expectedRegistrations = expectedHookRegistrations(target.dir, meta, agent, projectId);
2838
2952
  const expectedCommandSet = new Set(
2839
2953
  expectedRegistrations.map((registration) => registration.command)
2840
2954
  );
@@ -2859,10 +2973,10 @@ async function needsHookConfigRefresh(target, agents) {
2859
2973
  }
2860
2974
  return false;
2861
2975
  }
2862
- function expectedHookRegistrations(cwd, meta, platform) {
2976
+ function expectedHookRegistrations(cwd, meta, platform, projectId) {
2863
2977
  return EXPECTED_HOOK_REGISTRATION_SCRIPTS[platform].map(({ event, scriptName }) => ({
2864
2978
  event,
2865
- command: renderHookCommand(cwd, meta.templateContext, scriptName)
2979
+ command: renderHookCommand(cwd, meta.templateContext, scriptName, process.platform, projectId)
2866
2980
  }));
2867
2981
  }
2868
2982
  function hasExpectedHookRegistrations(commandsByEvent, expectedRegistrations) {