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 +12 -4
- package/dist/cli.js +151 -37
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/templates/claude/settings.json +4 -4
- package/templates/codex/hooks.json +2 -2
- package/templates/common/bundled-skills/ec-init/SKILL.md +6 -4
- package/templates/qoder/settings.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -6,11 +6,19 @@
|
|
|
6
6
|
- `y`:常规功能升级
|
|
7
7
|
- `z`:日常 bug 修复
|
|
8
8
|
|
|
9
|
-
## 0.5.
|
|
9
|
+
## 0.5.3
|
|
10
10
|
|
|
11
|
-
-
|
|
12
|
-
- `easy-coding upgrade`
|
|
13
|
-
- `
|
|
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
|
|
766
|
-
const
|
|
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
|
-
|
|
770
|
-
|
|
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(
|
|
774
|
-
const
|
|
775
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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) {
|