easy-coding-harness 1.0.0 → 1.0.1
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 +18 -0
- package/README.md +15 -4
- package/dist/cli.js +33 -92
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/templates/common/skills/ec-analysis/SKILL.md +13 -14
- package/templates/common/skills/ec-config/SKILL.md +3 -1
- package/templates/common/skills/ec-implementing/SKILL.md +11 -2
- package/templates/common/skills/ec-quality/SKILL.md +5 -0
- package/templates/common/skills/ec-task-management/SKILL.md +9 -2
- package/templates/common/skills/ec-tdd-init/SKILL.md +13 -9
- package/templates/common/skills/ec-workflow/SKILL.md +12 -3
- package/templates/main-constraint/AGENTS.md.tpl +7 -1
- package/templates/main-constraint/CLAUDE.md.tpl +7 -1
- package/templates/runtime/tools/easy_coding_tdd_readiness.py +18 -18
- package/templates/shared-hooks/easy_coding_state.py +341 -55
- package/templates/shared-hooks/easy_dev_spec.py +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,24 @@
|
|
|
6
6
|
- `y`:常规功能升级
|
|
7
7
|
- `z`:日常 bug 修复
|
|
8
8
|
|
|
9
|
+
## 1.0.1
|
|
10
|
+
|
|
11
|
+
- 将 `1.0.1-beta.0` 晋升为正式版本,包含 TDD 日常 readiness、升级配置保护,以及
|
|
12
|
+
Canonical Spec 跨 Agent 上下文恢复、需求同步和中断恢复修复。
|
|
13
|
+
- 默认 npm `latest` 安装获取 `1.0.1`;预发布用户可直接升级到本版本。
|
|
14
|
+
|
|
15
|
+
## 1.0.1-beta.0
|
|
16
|
+
|
|
17
|
+
- TDD 日常 readiness 检查保留初始化入口和参数契约校验,取消构建文件、CI 和工具文件与
|
|
18
|
+
初始化快照必须逐字一致的要求。Maven 版本、依赖和格式变更不再要求重新初始化;缺少
|
|
19
|
+
凭据提示首次初始化,已有入口损坏提示具体修复,本地测试和差异覆盖率仍负责最终验收。
|
|
20
|
+
- 升级保留项目/session TDD 开关、覆盖率阈值与进行中任务的冻结配置;readiness 异常和
|
|
21
|
+
工具更新不再自动关闭 TDD,升级清理保留带有显式 TDD 配置的 session。
|
|
22
|
+
- Canonical Spec 接手恢复绑定原稿的选中消费范围,核对设计版本并公开待同步状态;确认的
|
|
23
|
+
需求变更先登记到任务,原稿修订与设计同步完成后才能继续实施和验收。
|
|
24
|
+
- 修复跨 Agent 恢复设计同步时把任务负责人改回原事件作者的问题;重放保持原事件归属与
|
|
25
|
+
幂等键,当前负责人和会话保持为实际接手者。
|
|
26
|
+
|
|
9
27
|
## 1.0.0
|
|
10
28
|
|
|
11
29
|
- 首个正式版本,固化统一的 `INIT → ANALYSIS → IMPLEMENT → QUALITY → MEMORY → COMPLETE`
|
package/README.md
CHANGED
|
@@ -213,16 +213,27 @@ easy-coding upgrade
|
|
|
213
213
|
`behavior.approval_mode` 与 `behavior.workflow_mode`;旧 `lite` 映射为 `guard + fast`。
|
|
214
214
|
项目级模式用 `easy-coding config` 修改(要求
|
|
215
215
|
项目 Harness 与 CLI 版本完全一致,否则先执行 `easy-coding upgrade` 或更新 CLI);当前
|
|
216
|
-
session 临时覆盖统一通过 `ec-config`
|
|
217
|
-
|
|
218
|
-
|
|
216
|
+
session 临时覆盖统一通过 `ec-config` 对话修改。当前配置 schema 为 5;升级保留项目和
|
|
217
|
+
session 的 TDD 开关、阈值及继承关系,也保留任务已经冻结的基线。支持 TDD 的 schema 4
|
|
218
|
+
迁移保留用户配置;readiness 缺失或损坏只报告问题,不自动关闭 TDD。TDD 业务任务只依赖本地
|
|
219
219
|
单测与本地差异覆盖率,历史远程 CI 证据保留但不再参与验收。已经冻结的活动任务合同
|
|
220
220
|
不会被静默改写。0.10.0-beta.4 起,仍停在 ANALYSIS 的旧任务必须补齐决策闭环后才能
|
|
221
221
|
进入 IMPLEMENT;已经进入后续阶段的任务不受影响。
|
|
222
222
|
|
|
223
223
|
session GC 只在创建新逻辑会话前和实际升级时触发:无任务绑定的会话保留 7 天、仍绑定
|
|
224
224
|
任务的会话保留 30 天,并按最近活动时间将根目录 JSON 控制在 100 个以内。活动任务仍在
|
|
225
|
-
引用的 acceptance
|
|
225
|
+
引用的 acceptance 验收快照会被保留;升级时带有显式 TDD 配置的 session 不参与清理。
|
|
226
|
+
任务、记忆、Spec 和项目知识不参与清理。
|
|
227
|
+
|
|
228
|
+
TDD 凭据中的文件摘要是初始化历史。POM 版本、依赖、插件、CI 或托管工具变化无需重新
|
|
229
|
+
初始化;本轮构建与工具变化会使旧验收证据失效。日常 readiness 只检查必要本地入口和
|
|
230
|
+
参数契约;缺少凭据为 `needs_init`,已有凭据或入口损坏为 `needs_repair`。实际单测与
|
|
231
|
+
增量覆盖率仍须通过。`ec-tdd-init` 的完整校验包含 GitLab CI。
|
|
232
|
+
|
|
233
|
+
正式绑定的外部 Canonical Spec 在创建和接手时返回选中内容,恢复会话使用
|
|
234
|
+
`resume-spec-context`。需求确认后先用 `begin-spec-change` 登记,再更新绑定原稿、递增
|
|
235
|
+
revision、验证 READY 并执行 `sync-spec-design`;随后恢复上下文并更新派生计划。待同步
|
|
236
|
+
变更会跨 Agent 保留并阻止实施和验收,交接摘要不能代替原稿。
|
|
226
237
|
|
|
227
238
|
若当前会话不希望 Harness 接管,显式调用 `/ec-no-harness`(Codex 使用
|
|
228
239
|
`$ec-no-harness`)。它只旁路 Easy Coding,不关闭其他 hooks,也不忽略其他 skills;
|
package/dist/cli.js
CHANGED
|
@@ -191,8 +191,7 @@ function resolveLegacyBehavior(config2) {
|
|
|
191
191
|
const approvalMode = isApprovalMode(behavior.approval_mode) ? behavior.approval_mode : isApprovalMode(behavior.confirm_mode) ? behavior.confirm_mode : behavior.auto_mode === true ? "auto" : behavior.strict_confirm === true ? "approve" : "guard";
|
|
192
192
|
const workflowMode = isConfiguredWorkflowMode(behavior.workflow_mode) ? behavior.workflow_mode : legacyLite ? "fast" : "adaptive";
|
|
193
193
|
const supportsTddThreshold = Number(config2.version) >= 4;
|
|
194
|
-
const
|
|
195
|
-
const tddEnabled = supportsReadyTdd && behavior.tdd_enabled === true;
|
|
194
|
+
const tddEnabled = supportsTddThreshold && behavior.tdd_enabled === true;
|
|
196
195
|
const tddCoverageThreshold = supportsTddThreshold && isTddCoverageThreshold(behavior.tdd_coverage_threshold) ? behavior.tdd_coverage_threshold : DEFAULT_TDD_COVERAGE_THRESHOLD;
|
|
197
196
|
return { approvalMode, workflowMode, tddEnabled, tddCoverageThreshold };
|
|
198
197
|
}
|
|
@@ -1009,15 +1008,11 @@ function migrateTaskWorkflowState(task) {
|
|
|
1009
1008
|
}
|
|
1010
1009
|
if (isActive && taskType !== "project-init" && typeof task.tdd_enabled !== "boolean") {
|
|
1011
1010
|
task.tdd_enabled = false;
|
|
1012
|
-
task.tdd_coverage_threshold
|
|
1011
|
+
task.tdd_coverage_threshold ??= DEFAULT_TDD_COVERAGE_THRESHOLD;
|
|
1013
1012
|
task.tdd_confirmed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1014
1013
|
task.tdd_confirmed_by = "upgrade-migration";
|
|
1015
1014
|
changed = true;
|
|
1016
1015
|
}
|
|
1017
|
-
if (task.tdd_enabled === false && "tdd_baselines" in task) {
|
|
1018
|
-
task.tdd_baselines = void 0;
|
|
1019
|
-
changed = true;
|
|
1020
|
-
}
|
|
1021
1016
|
return changed;
|
|
1022
1017
|
}
|
|
1023
1018
|
function migrateSessionBehavior(session) {
|
|
@@ -3022,8 +3017,7 @@ async function checkForUpgrade(cwd) {
|
|
|
3022
3017
|
}
|
|
3023
3018
|
|
|
3024
3019
|
// src/utils/tdd-readiness.ts
|
|
3025
|
-
import {
|
|
3026
|
-
import { readFile as readFile4, readdir as readdir5, realpath as realpath2 } from "fs/promises";
|
|
3020
|
+
import { readFile as readFile4, realpath as realpath2 } from "fs/promises";
|
|
3027
3021
|
import path18 from "path";
|
|
3028
3022
|
var TDD_READINESS_SCHEMA = "easy-coding/tdd-readiness-v1";
|
|
3029
3023
|
var TDD_READINESS_SCOPE = "changed-production-lines";
|
|
@@ -3031,7 +3025,6 @@ var TDD_BASE_VARIABLE = "EASY_CODING_TDD_BASE_SHA";
|
|
|
3031
3025
|
var TDD_THRESHOLD_VARIABLE = "EASY_CODING_TDD_THRESHOLD";
|
|
3032
3026
|
var COVERAGE_TOOL_PATH = ".easy-coding/tools/easy_coding_java_coverage.py";
|
|
3033
3027
|
var JAVA_BUILD_FILE_NAMES = /* @__PURE__ */ new Set(["pom.xml", "build.gradle", "build.gradle.kts"]);
|
|
3034
|
-
var GITLAB_CI_ENTRY_FILES = /* @__PURE__ */ new Set([".gitlab-ci.yml", ".gitlab-ci.yaml"]);
|
|
3035
3028
|
function readinessPath(root) {
|
|
3036
3029
|
return path18.join(root, EASY_CODING_DIR, TDD_DIR, TDD_READINESS_FILE);
|
|
3037
3030
|
}
|
|
@@ -3047,11 +3040,11 @@ function parseFileRecords(value, field, reasons) {
|
|
|
3047
3040
|
continue;
|
|
3048
3041
|
}
|
|
3049
3042
|
const record = item;
|
|
3050
|
-
if (typeof record.path !== "string" || !record.path.trim() || path18.isAbsolute(record.path)
|
|
3051
|
-
reasons.push(`${field} contains an invalid path
|
|
3043
|
+
if (typeof record.path !== "string" || !record.path.trim() || path18.isAbsolute(record.path)) {
|
|
3044
|
+
reasons.push(`${field} contains an invalid path`);
|
|
3052
3045
|
continue;
|
|
3053
3046
|
}
|
|
3054
|
-
records.push({ path: record.path
|
|
3047
|
+
records.push({ path: record.path });
|
|
3055
3048
|
}
|
|
3056
3049
|
return records;
|
|
3057
3050
|
}
|
|
@@ -3063,11 +3056,7 @@ function isSafeReportPattern(value) {
|
|
|
3063
3056
|
const normalized = value.replaceAll("\\", "/");
|
|
3064
3057
|
return !path18.isAbsolute(value) && !normalized.split("/").includes("..");
|
|
3065
3058
|
}
|
|
3066
|
-
function activeCiContent(contents) {
|
|
3067
|
-
return contents.join("\n").split("\n").map((line) => line.replace(/^\s*#.*$/, "").replace(/\s+#.*$/, "")).join("\n");
|
|
3068
|
-
}
|
|
3069
3059
|
async function validateFiles(root, records, reasons) {
|
|
3070
|
-
const contents = [];
|
|
3071
3060
|
const resolvedRoot = await realpath2(root);
|
|
3072
3061
|
for (const record of records) {
|
|
3073
3062
|
const absolute = path18.resolve(root, record.path);
|
|
@@ -3077,15 +3066,11 @@ async function validateFiles(root, records, reasons) {
|
|
|
3077
3066
|
reasons.push(`readiness file escapes project root: ${record.path}`);
|
|
3078
3067
|
continue;
|
|
3079
3068
|
}
|
|
3080
|
-
|
|
3081
|
-
const digest = createHash2("sha256").update(content).digest("hex");
|
|
3082
|
-
if (digest !== record.sha256) reasons.push(`readiness file changed: ${record.path}`);
|
|
3083
|
-
contents.push(content.toString("utf8"));
|
|
3069
|
+
await readFile4(resolved);
|
|
3084
3070
|
} catch {
|
|
3085
3071
|
reasons.push(`readiness file is missing or unreadable: ${record.path}`);
|
|
3086
3072
|
}
|
|
3087
3073
|
}
|
|
3088
|
-
return contents;
|
|
3089
3074
|
}
|
|
3090
3075
|
async function inspectTddReadiness(root) {
|
|
3091
3076
|
const manifestPath2 = readinessPath(root);
|
|
@@ -3096,7 +3081,14 @@ async function inspectTddReadiness(root) {
|
|
|
3096
3081
|
try {
|
|
3097
3082
|
manifest = JSON.parse(await readFile4(manifestPath2, "utf8"));
|
|
3098
3083
|
} catch {
|
|
3099
|
-
return { status: "
|
|
3084
|
+
return { status: "needs_repair", reasons: ["TDD readiness receipt is invalid"], manifestPath: manifestPath2 };
|
|
3085
|
+
}
|
|
3086
|
+
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
|
|
3087
|
+
return {
|
|
3088
|
+
status: "needs_repair",
|
|
3089
|
+
reasons: ["TDD readiness receipt must be a JSON object"],
|
|
3090
|
+
manifestPath: manifestPath2
|
|
3091
|
+
};
|
|
3100
3092
|
}
|
|
3101
3093
|
const reasons = [];
|
|
3102
3094
|
if (manifest.schema !== TDD_READINESS_SCHEMA) reasons.push("unsupported readiness schema");
|
|
@@ -3118,67 +3110,21 @@ async function inspectTddReadiness(root) {
|
|
|
3118
3110
|
reasons.push("changed-line coverage gate must use the task baseline and threshold variables");
|
|
3119
3111
|
}
|
|
3120
3112
|
const buildFiles = parseFileRecords(manifest.build_files, "build_files", reasons);
|
|
3121
|
-
const ciFiles = parseFileRecords(manifest.ci_files, "ci_files", reasons);
|
|
3122
3113
|
const toolFiles = parseFileRecords(manifest.tool_files, "tool_files", reasons);
|
|
3123
3114
|
if (!buildFiles.some((record) => JAVA_BUILD_FILE_NAMES.has(path18.basename(record.path)))) {
|
|
3124
3115
|
reasons.push("build_files must include a Maven or Gradle Java build file");
|
|
3125
3116
|
}
|
|
3126
|
-
if (!ciFiles.some((record) => GITLAB_CI_ENTRY_FILES.has(record.path.replaceAll("\\", "/")))) {
|
|
3127
|
-
reasons.push("ci_files must include the project-root GitLab CI entry file");
|
|
3128
|
-
}
|
|
3129
3117
|
if (!toolFiles.some((record) => record.path.replaceAll("\\", "/") === COVERAGE_TOOL_PATH)) {
|
|
3130
3118
|
reasons.push(`tool_files must include ${COVERAGE_TOOL_PATH}`);
|
|
3131
3119
|
}
|
|
3132
|
-
|
|
3133
|
-
const ciContents = await validateFiles(root, ciFiles, reasons);
|
|
3120
|
+
await validateFiles(root, buildFiles, reasons);
|
|
3134
3121
|
await validateFiles(root, toolFiles, reasons);
|
|
3135
|
-
if (!buildContents.some((content) => /jacoco/i.test(content))) {
|
|
3136
|
-
reasons.push("build files do not configure JaCoCo");
|
|
3137
|
-
}
|
|
3138
|
-
const combinedCi = activeCiContent(ciContents);
|
|
3139
|
-
for (const marker of [
|
|
3140
|
-
"jacoco",
|
|
3141
|
-
"artifacts",
|
|
3142
|
-
COVERAGE_TOOL_PATH,
|
|
3143
|
-
TDD_BASE_VARIABLE,
|
|
3144
|
-
TDD_THRESHOLD_VARIABLE
|
|
3145
|
-
]) {
|
|
3146
|
-
if (!combinedCi.toLowerCase().includes(marker.toLowerCase())) {
|
|
3147
|
-
reasons.push(`CI files do not contain required marker: ${marker}`);
|
|
3148
|
-
}
|
|
3149
|
-
}
|
|
3150
|
-
if (!usesRequiredGateVariables(combinedCi)) {
|
|
3151
|
-
reasons.push("CI changed-line gate must use the task baseline and threshold variables");
|
|
3152
|
-
}
|
|
3153
|
-
if (!/(?:^|\n)\s*stage\s*:\s*['"]?test['"]?\s*(?:#.*)?(?:\n|$)/i.test(combinedCi)) {
|
|
3154
|
-
reasons.push("CI files do not declare a TEST-stage job");
|
|
3155
|
-
}
|
|
3156
3122
|
return {
|
|
3157
|
-
status: reasons.length === 0 ? "ready" : "
|
|
3123
|
+
status: reasons.length === 0 ? "ready" : "needs_repair",
|
|
3158
3124
|
reasons: [...new Set(reasons)],
|
|
3159
3125
|
manifestPath: manifestPath2
|
|
3160
3126
|
};
|
|
3161
3127
|
}
|
|
3162
|
-
async function disableUnreadySessionTddOverrides(root) {
|
|
3163
|
-
if ((await inspectTddReadiness(root)).status === "ready") return 0;
|
|
3164
|
-
const sessionsDir = path18.join(root, EASY_CODING_DIR, SESSIONS_DIR);
|
|
3165
|
-
if (!await pathExists(sessionsDir)) return 0;
|
|
3166
|
-
let updated = 0;
|
|
3167
|
-
for (const entry of await readdir5(sessionsDir, { withFileTypes: true })) {
|
|
3168
|
-
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
3169
|
-
const filePath = path18.join(sessionsDir, entry.name);
|
|
3170
|
-
try {
|
|
3171
|
-
const session = JSON.parse(await readFile4(filePath, "utf8"));
|
|
3172
|
-
if (session.tdd_enabled !== true) continue;
|
|
3173
|
-
session.tdd_enabled = false;
|
|
3174
|
-
await writeTextFile(filePath, `${JSON.stringify(session, null, 2)}
|
|
3175
|
-
`);
|
|
3176
|
-
updated += 1;
|
|
3177
|
-
} catch {
|
|
3178
|
-
}
|
|
3179
|
-
}
|
|
3180
|
-
return updated;
|
|
3181
|
-
}
|
|
3182
3128
|
|
|
3183
3129
|
// src/commands/config.ts
|
|
3184
3130
|
async function config() {
|
|
@@ -3281,7 +3227,7 @@ async function config() {
|
|
|
3281
3227
|
const readiness = await inspectTddReadiness(process.cwd());
|
|
3282
3228
|
if (readiness.status !== "ready") {
|
|
3283
3229
|
cancel4(
|
|
3284
|
-
`TDD was not enabled. Run ec-tdd-init first: ${readiness.reasons.join("; ")}. No project modes were changed.`
|
|
3230
|
+
`TDD was not enabled. ${readiness.status === "needs_init" ? "Run ec-tdd-init first" : "Repair TDD readiness"}: ${readiness.reasons.join("; ")}. No project modes were changed.`
|
|
3285
3231
|
);
|
|
3286
3232
|
return;
|
|
3287
3233
|
}
|
|
@@ -3333,7 +3279,7 @@ import { note, outro as outro4 } from "@clack/prompts";
|
|
|
3333
3279
|
import chalk5 from "chalk";
|
|
3334
3280
|
|
|
3335
3281
|
// src/utils/install-state.ts
|
|
3336
|
-
import { readdir as
|
|
3282
|
+
import { readdir as readdir5 } from "fs/promises";
|
|
3337
3283
|
import path20 from "path";
|
|
3338
3284
|
var LEGACY_ROOT_FILES = ["SOUL.md", "RULES.md", "ABSTRACT.md"];
|
|
3339
3285
|
async function detectEasyCodingInstallState(cwd) {
|
|
@@ -3379,14 +3325,14 @@ async function listMarkdownFiles(dir) {
|
|
|
3379
3325
|
if (!await isDirectory(dir)) {
|
|
3380
3326
|
return [];
|
|
3381
3327
|
}
|
|
3382
|
-
const entries = await
|
|
3328
|
+
const entries = await readdir5(dir, { withFileTypes: true });
|
|
3383
3329
|
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => entry.name).sort();
|
|
3384
3330
|
}
|
|
3385
3331
|
async function hasAnyDirectoryEntry(dir) {
|
|
3386
3332
|
if (!await isDirectory(dir)) {
|
|
3387
3333
|
return false;
|
|
3388
3334
|
}
|
|
3389
|
-
return (await
|
|
3335
|
+
return (await readdir5(dir)).length > 0;
|
|
3390
3336
|
}
|
|
3391
3337
|
function relativeEasyCodingPath(...segments) {
|
|
3392
3338
|
return path20.posix.join(EASY_CODING_DIR, ...segments);
|
|
@@ -3562,7 +3508,7 @@ import path23 from "path";
|
|
|
3562
3508
|
import chalk6 from "chalk";
|
|
3563
3509
|
|
|
3564
3510
|
// src/utils/session.ts
|
|
3565
|
-
import { readdir as
|
|
3511
|
+
import { readdir as readdir6, stat as stat3, unlink as unlink2 } from "fs/promises";
|
|
3566
3512
|
import path22 from "path";
|
|
3567
3513
|
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
3568
3514
|
var IDLE_SESSION_RETENTION_MS = 7 * DAY_MS;
|
|
@@ -3585,7 +3531,7 @@ async function listSessionFiles(cwd) {
|
|
|
3585
3531
|
return [];
|
|
3586
3532
|
}
|
|
3587
3533
|
const entries = [];
|
|
3588
|
-
for (const name of await
|
|
3534
|
+
for (const name of await readdir6(dir)) {
|
|
3589
3535
|
if (!name.endsWith(".json")) {
|
|
3590
3536
|
continue;
|
|
3591
3537
|
}
|
|
@@ -3612,7 +3558,11 @@ async function cleanSessionRuntime(cwd, options = {}) {
|
|
|
3612
3558
|
const attachedRetentionMs = options.attachedRetentionMs ?? ATTACHED_SESSION_RETENTION_MS;
|
|
3613
3559
|
const maxSessions = options.maxSessions ?? MAX_SESSION_FILES;
|
|
3614
3560
|
const reserveSlots = options.reserveSlots ?? 0;
|
|
3615
|
-
const candidates = await listSessionCleanupCandidates(cwd)
|
|
3561
|
+
const candidates = (await listSessionCleanupCandidates(cwd)).filter((candidate) => {
|
|
3562
|
+
if (!options.preserveTddSettings) return true;
|
|
3563
|
+
const session = parseSessionFile(candidate.content);
|
|
3564
|
+
return !session || !("tdd_enabled" in session || "tdd_coverage_threshold" in session);
|
|
3565
|
+
});
|
|
3616
3566
|
const removed = /* @__PURE__ */ new Set();
|
|
3617
3567
|
for (const candidate of candidates) {
|
|
3618
3568
|
const retentionMs = candidate.attached ? attachedRetentionMs : idleRetentionMs;
|
|
@@ -3644,7 +3594,7 @@ async function listSessionCleanupCandidates(cwd) {
|
|
|
3644
3594
|
return [];
|
|
3645
3595
|
}
|
|
3646
3596
|
const candidates = [];
|
|
3647
|
-
for (const entry of await
|
|
3597
|
+
for (const entry of await readdir6(dir, { withFileTypes: true })) {
|
|
3648
3598
|
if (!entry.isFile() || !entry.name.endsWith(".json")) {
|
|
3649
3599
|
continue;
|
|
3650
3600
|
}
|
|
@@ -3688,7 +3638,7 @@ async function cleanOrphanAcceptanceSnapshots(cwd) {
|
|
|
3688
3638
|
return 0;
|
|
3689
3639
|
}
|
|
3690
3640
|
let removed = 0;
|
|
3691
|
-
for (const entry of await
|
|
3641
|
+
for (const entry of await readdir6(acceptanceDir, { withFileTypes: true })) {
|
|
3692
3642
|
if (!entry.isFile() || !entry.name.endsWith(".json")) {
|
|
3693
3643
|
continue;
|
|
3694
3644
|
}
|
|
@@ -3953,8 +3903,8 @@ async function upgrade(opts) {
|
|
|
3953
3903
|
"Will overwrite managed skills, hooks, agents, templates, and generated main-constraint regions.",
|
|
3954
3904
|
"Will remove retired files that still match the previous install manifest and preserve locally modified copies.",
|
|
3955
3905
|
"Will update project-init task to recommend ec-init re-run for version adaptation.",
|
|
3956
|
-
"Will migrate behavior config to schema 5
|
|
3957
|
-
"Will prune expired session bindings and orphan acceptance snapshots in each upgraded target while preserving tasks, memory, spec, and project knowledge.",
|
|
3906
|
+
"Will migrate behavior config to schema 5 while preserving project/session TDD settings and frozen task baselines.",
|
|
3907
|
+
"Will prune expired session bindings without explicit TDD settings and orphan acceptance snapshots in each upgraded target while preserving tasks, memory, spec, and project knowledge.",
|
|
3958
3908
|
"Will migrate active REVIEW/VERIFICATION tasks to QUALITY, retire active read-only task types as CLOSED, and preserve their artifacts and history.",
|
|
3959
3909
|
"Will migrate legacy workflow/TDD task metadata; memory content, spec, and project knowledge files remain untouched."
|
|
3960
3910
|
].join("\n");
|
|
@@ -3974,7 +3924,7 @@ async function upgrade(opts) {
|
|
|
3974
3924
|
}
|
|
3975
3925
|
for (const { target, config: config2 } of pending) {
|
|
3976
3926
|
const previousManifest = await readInstallManifest(target.dir);
|
|
3977
|
-
const sessionCleanup = await cleanSessionRuntime(target.dir);
|
|
3927
|
+
const sessionCleanup = await cleanSessionRuntime(target.dir, { preserveTddSettings: true });
|
|
3978
3928
|
if (sessionCleanup.sessionsRemoved > 0 || sessionCleanup.acceptanceSnapshotsRemoved > 0) {
|
|
3979
3929
|
console.log(
|
|
3980
3930
|
chalk8.yellow(
|
|
@@ -3982,7 +3932,6 @@ async function upgrade(opts) {
|
|
|
3982
3932
|
)
|
|
3983
3933
|
);
|
|
3984
3934
|
}
|
|
3985
|
-
const beta1ProjectTddRequested = Number(config2.version) === 4 && config2.behavior?.tdd_enabled === true;
|
|
3986
3935
|
const projectId = await writeRuntimeScaffold(target.dir, config2.agents, {
|
|
3987
3936
|
supermodule: target.supermodule
|
|
3988
3937
|
});
|
|
@@ -4007,14 +3956,6 @@ async function upgrade(opts) {
|
|
|
4007
3956
|
await ensureHookBytecodeIgnored(target.dir);
|
|
4008
3957
|
await migrateLegacyWorkflowState(target.dir);
|
|
4009
3958
|
await migrateBehaviorConfig(target.configPath);
|
|
4010
|
-
const disabledSessionTddOverrides = await disableUnreadySessionTddOverrides(target.dir);
|
|
4011
|
-
if (beta1ProjectTddRequested || disabledSessionTddOverrides > 0) {
|
|
4012
|
-
console.log(
|
|
4013
|
-
chalk8.yellow(
|
|
4014
|
-
`${target.label}: TDD remains off until ec-tdd-init succeeds (project=${beta1ProjectTddRequested ? "disabled" : "unchanged"}, sessions_disabled=${disabledSessionTddOverrides}).`
|
|
4015
|
-
)
|
|
4016
|
-
);
|
|
4017
|
-
}
|
|
4018
3959
|
await updateHarnessVersion(target.configPath, VERSION);
|
|
4019
3960
|
await updateSupermoduleConfig(target.configPath, target.supermodule);
|
|
4020
3961
|
await setPendingInitSince(target.dir, VERSION);
|