easy-coding-harness 1.1.0-beta.0 → 1.1.0-beta.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/dist/cli.js CHANGED
@@ -103,8 +103,9 @@ async function isDirectory(filePath) {
103
103
  }
104
104
 
105
105
  // src/utils/config-yaml.ts
106
- var CONFIG_SCHEMA_VERSION = 5;
107
- var DEFAULT_TDD_COVERAGE_THRESHOLD = 90;
106
+ var CONFIG_SCHEMA_VERSION = 6;
107
+ var DEFAULT_UT_COVERAGE_THRESHOLD = 90;
108
+ var UNIT_TEST_MODES = ["none", "ut", "tdd"];
108
109
  var APPROVAL_MODES = ["approve", "guard", "confirm", "auto"];
109
110
  var CONFIGURED_WORKFLOW_MODES = ["adaptive", "fast", "standard", "strict"];
110
111
  function createDefaultConfig(params) {
@@ -127,8 +128,8 @@ function createDefaultConfig(params) {
127
128
  behavior: {
128
129
  approval_mode: "guard",
129
130
  workflow_mode: "adaptive",
130
- tdd_enabled: false,
131
- tdd_coverage_threshold: DEFAULT_TDD_COVERAGE_THRESHOLD
131
+ unit_test_mode: "none",
132
+ ut_coverage_threshold: DEFAULT_UT_COVERAGE_THRESHOLD
132
133
  }
133
134
  };
134
135
  if (params.supermodule) {
@@ -182,43 +183,65 @@ function isApprovalMode(value) {
182
183
  function isConfiguredWorkflowMode(value) {
183
184
  return typeof value === "string" && CONFIGURED_WORKFLOW_MODES.includes(value);
184
185
  }
185
- function isTddCoverageThreshold(value) {
186
+ function isUtCoverageThreshold(value) {
186
187
  return Number.isInteger(value) && Number(value) >= 1 && Number(value) <= 100;
187
188
  }
189
+ function isUnitTestMode(value) {
190
+ return typeof value === "string" && UNIT_TEST_MODES.includes(value);
191
+ }
192
+ function migrateUnitTestSettings(record) {
193
+ let changed = false;
194
+ if ("tdd_enabled" in record) {
195
+ if (!("unit_test_mode" in record) && typeof record.tdd_enabled === "boolean") {
196
+ record.unit_test_mode = record.tdd_enabled ? "tdd" : "none";
197
+ }
198
+ Reflect.deleteProperty(record, "tdd_enabled");
199
+ changed = true;
200
+ }
201
+ if ("tdd_coverage_threshold" in record) {
202
+ record.ut_coverage_threshold ??= record.tdd_coverage_threshold;
203
+ Reflect.deleteProperty(record, "tdd_coverage_threshold");
204
+ changed = true;
205
+ }
206
+ return changed;
207
+ }
188
208
  function resolveLegacyBehavior(config2) {
189
209
  const behavior = config2.behavior ?? {};
190
210
  const legacyLite = behavior.confirm_mode === "lite";
191
211
  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
212
  const workflowMode = isConfiguredWorkflowMode(behavior.workflow_mode) ? behavior.workflow_mode : legacyLite ? "fast" : "adaptive";
193
- const supportsTddThreshold = Number(config2.version) >= 4;
194
- const tddEnabled = supportsTddThreshold && behavior.tdd_enabled === true;
195
- const tddCoverageThreshold = supportsTddThreshold && isTddCoverageThreshold(behavior.tdd_coverage_threshold) ? behavior.tdd_coverage_threshold : DEFAULT_TDD_COVERAGE_THRESHOLD;
196
- return { approvalMode, workflowMode, tddEnabled, tddCoverageThreshold };
213
+ const unitTestMode = isUnitTestMode(behavior.unit_test_mode) ? behavior.unit_test_mode : Number(config2.version) >= 4 && behavior.tdd_enabled === true ? "tdd" : "none";
214
+ const configuredThreshold = behavior.ut_coverage_threshold ?? (Number(config2.version) >= 4 ? behavior.tdd_coverage_threshold : void 0);
215
+ const utCoverageThreshold = isUtCoverageThreshold(configuredThreshold) ? configuredThreshold : DEFAULT_UT_COVERAGE_THRESHOLD;
216
+ return { approvalMode, workflowMode, unitTestMode, utCoverageThreshold };
197
217
  }
198
- async function setBehaviorModes(filePath, approvalMode, workflowMode, tddEnabled, tddCoverageThreshold) {
199
- if (tddCoverageThreshold !== void 0 && !isTddCoverageThreshold(tddCoverageThreshold)) {
200
- throw new Error("TDD coverage threshold must be an integer from 1 to 100.");
218
+ async function setBehaviorModes(filePath, approvalMode, workflowMode, unitTestMode, utCoverageThreshold) {
219
+ if (unitTestMode !== void 0 && !isUnitTestMode(unitTestMode)) {
220
+ throw new Error("Unit test mode must be none, ut, or tdd.");
221
+ }
222
+ if (utCoverageThreshold !== void 0 && !isUtCoverageThreshold(utCoverageThreshold)) {
223
+ throw new Error("Unit test coverage threshold must be an integer from 1 to 100.");
201
224
  }
202
225
  return updateConfigYaml(filePath, (config2) => {
203
226
  const legacyBehavior = config2.behavior ?? {};
204
227
  const resolvedBehavior = resolveLegacyBehavior(config2);
205
228
  const behavior = Object.fromEntries(
206
229
  Object.entries(legacyBehavior).filter(
207
- ([key]) => key !== "strict_confirm" && key !== "auto_mode" && key !== "confirm_mode" && key !== "approval_mode" && key !== "workflow_mode" && key !== "tdd_enabled" && key !== "tdd_coverage_threshold"
230
+ ([key]) => key !== "strict_confirm" && key !== "auto_mode" && key !== "confirm_mode" && key !== "approval_mode" && key !== "workflow_mode" && key !== "tdd_enabled" && key !== "tdd_coverage_threshold" && key !== "unit_test_mode" && key !== "ut_coverage_threshold"
208
231
  )
209
232
  );
210
233
  behavior.approval_mode = approvalMode;
211
234
  behavior.workflow_mode = workflowMode;
212
- behavior.tdd_enabled = tddEnabled ?? resolvedBehavior.tddEnabled;
213
- behavior.tdd_coverage_threshold = tddCoverageThreshold ?? resolvedBehavior.tddCoverageThreshold;
235
+ behavior.unit_test_mode = unitTestMode ?? resolvedBehavior.unitTestMode;
236
+ behavior.ut_coverage_threshold = utCoverageThreshold ?? resolvedBehavior.utCoverageThreshold;
214
237
  config2.behavior = behavior;
215
238
  config2.version = CONFIG_SCHEMA_VERSION;
216
239
  });
217
240
  }
218
241
  async function migrateBehaviorConfig(filePath) {
219
242
  const config2 = await readConfigYaml(filePath);
220
- const { approvalMode, workflowMode, tddEnabled, tddCoverageThreshold } = resolveLegacyBehavior(config2);
221
- return setBehaviorModes(filePath, approvalMode, workflowMode, tddEnabled, tddCoverageThreshold);
243
+ const { approvalMode, workflowMode, unitTestMode, utCoverageThreshold } = resolveLegacyBehavior(config2);
244
+ return setBehaviorModes(filePath, approvalMode, workflowMode, unitTestMode, utCoverageThreshold);
222
245
  }
223
246
  async function ensureProjectId(filePath) {
224
247
  let projectId = "";
@@ -664,97 +687,11 @@ function byPath(a, b) {
664
687
  }
665
688
 
666
689
  // src/utils/runtime-scaffold.ts
667
- import path6 from "path";
668
-
669
- // src/utils/template-paths.ts
670
- import { existsSync } from "fs";
671
- import path5 from "path";
672
- import { fileURLToPath as fileURLToPath2 } from "url";
673
- function getTemplateRoot() {
674
- const here = path5.dirname(fileURLToPath2(import.meta.url));
675
- const candidates = [
676
- path5.resolve(here, "../templates"),
677
- path5.resolve(here, "../../templates"),
678
- path5.resolve(process.cwd(), "src/templates"),
679
- path5.resolve(process.cwd(), "templates")
680
- ];
681
- const found = candidates.find((candidate) => existsSync(candidate));
682
- if (!found) {
683
- throw new Error(`Unable to locate templates directory. Tried: ${candidates.join(", ")}`);
684
- }
685
- return found;
686
- }
687
- function getTemplatePath(...segments) {
688
- return path5.join(getTemplateRoot(), ...segments);
689
- }
690
-
691
- // src/utils/runtime-scaffold.ts
692
- async function writeRuntimeScaffold(cwd, agents, opts = {}) {
693
- const easyCodingDir = path6.join(cwd, EASY_CODING_DIR);
694
- await ensureDir(easyCodingDir);
695
- const configPath2 = path6.join(easyCodingDir, CONFIG_FILE);
696
- let projectId = opts.projectId ?? createProjectId();
697
- if (!await pathExists(configPath2)) {
698
- const projectName = path6.basename(cwd);
699
- await writeConfigYaml(
700
- configPath2,
701
- createDefaultConfig({
702
- projectName,
703
- projectId,
704
- harnessVersion: VERSION,
705
- agents,
706
- supermodule: opts.supermodule
707
- })
708
- );
709
- } else {
710
- projectId = await ensureProjectId(configPath2);
711
- }
712
- await ensureDir(path6.join(easyCodingDir, "tasks"));
713
- await ensureDir(path6.join(easyCodingDir, SESSIONS_DIR));
714
- await ensureDir(path6.join(easyCodingDir, SPEC_DIR, MAIN_SPEC_DIR));
715
- await ensureDir(path6.join(easyCodingDir, SPEC_DIR, DEV_SPEC_DIR));
716
- await writeMemoryScaffold(easyCodingDir);
717
- await writeTemplatesScaffold(easyCodingDir);
718
- await writeToolsScaffold(easyCodingDir);
719
- return projectId;
720
- }
721
- async function writeToolsScaffold(easyCodingDir) {
722
- const toolsDir = path6.join(easyCodingDir, TOOLS_DIR);
723
- await ensureDir(toolsDir);
724
- for (const file of ["easy_coding_java_coverage.py", "easy_coding_tdd_readiness.py"]) {
725
- const src = getTemplatePath("runtime", "tools", file);
726
- await writeTextFile(path6.join(toolsDir, file), await readTextFile(src));
727
- }
728
- }
729
- async function writeTemplatesScaffold(easyCodingDir) {
730
- const templatesDir = path6.join(easyCodingDir, TEMPLATES_DIR);
731
- await ensureDir(templatesDir);
732
- const src = getTemplatePath("runtime", "templates", "dev-spec-skeleton.md");
733
- const dest = path6.join(templatesDir, "dev-spec-skeleton.md");
734
- await writeTextFile(dest, await readTextFile(src));
735
- }
736
- async function writeMemoryScaffold(easyCodingDir) {
737
- const memoryDir = path6.join(easyCodingDir, MEMORY_DIR);
738
- await ensureDir(path6.join(memoryDir, "short"));
739
- await ensureDir(path6.join(memoryDir, "long"));
740
- for (const file of ["MEMORY.md", "BUSINESS.md", "TECHNICAL.md"]) {
741
- const destination = path6.join(memoryDir, "long", file);
742
- if (await pathExists(destination)) {
743
- continue;
744
- }
745
- const templatePath = getTemplatePath("runtime", "memory", "long", file);
746
- await writeTextFile(destination, await readTextFile(templatePath));
747
- }
748
- const shortTemplateDest = path6.join(memoryDir, "SHORT_MEMORY_TEMPLATE.md");
749
- if (!await pathExists(shortTemplateDest)) {
750
- const templatePath = getTemplatePath("runtime", "memory", "SHORT_MEMORY_TEMPLATE.md");
751
- await writeTextFile(shortTemplateDest, await readTextFile(templatePath));
752
- }
753
- }
690
+ import path7 from "path";
754
691
 
755
692
  // src/utils/task-json.ts
756
693
  import { readdir } from "fs/promises";
757
- import path7 from "path";
694
+ import path5 from "path";
758
695
  var LEGACY_STAGE_MAP = {
759
696
  WAITING_CONFIRM: "ANALYSIS",
760
697
  MEMORY_SHORT: "MEMORY",
@@ -781,7 +718,7 @@ function createProjectInitTask(params) {
781
718
  };
782
719
  }
783
720
  function getTaskJsonPath(cwd, taskId) {
784
- return path7.join(cwd, EASY_CODING_DIR, TASKS_DIR, taskId, "task.json");
721
+ return path5.join(cwd, EASY_CODING_DIR, TASKS_DIR, taskId, "task.json");
785
722
  }
786
723
  async function writeTaskJson(filePath, task) {
787
724
  await writeTextFile(filePath, JSON.stringify(task, null, 2));
@@ -1006,9 +943,10 @@ function migrateTaskWorkflowState(task) {
1006
943
  task.workflow_mode_legacy = true;
1007
944
  changed = true;
1008
945
  }
1009
- if (isActive && taskType !== "project-init" && typeof task.tdd_enabled !== "boolean") {
1010
- task.tdd_enabled = false;
1011
- task.tdd_coverage_threshold ??= DEFAULT_TDD_COVERAGE_THRESHOLD;
946
+ changed = migrateUnitTestSettings(task) || changed;
947
+ if (isActive && taskType !== "project-init" && task.unit_test_mode === void 0) {
948
+ task.unit_test_mode = "none";
949
+ task.ut_coverage_threshold ??= DEFAULT_UT_COVERAGE_THRESHOLD;
1012
950
  task.tdd_confirmed_at = (/* @__PURE__ */ new Date()).toISOString();
1013
951
  task.tdd_confirmed_by = "upgrade-migration";
1014
952
  changed = true;
@@ -1017,6 +955,7 @@ function migrateTaskWorkflowState(task) {
1017
955
  }
1018
956
  function migrateSessionBehavior(session) {
1019
957
  let changed = migrateAgentFields(session, ["agent", "last_agent"]);
958
+ changed = migrateUnitTestSettings(session) || changed;
1020
959
  const legacyMode = session.confirm_mode;
1021
960
  const legacyLite = legacyMode === "lite";
1022
961
  if (!["approve", "guard", "confirm", "auto"].includes(String(session.approval_mode ?? ""))) {
@@ -1092,16 +1031,16 @@ function migrationWorkflowMode(task, candidates, projectWorkflowMode) {
1092
1031
  );
1093
1032
  }
1094
1033
  async function taskFiles(cwd) {
1095
- const tasksDir = path7.join(cwd, EASY_CODING_DIR, TASKS_DIR);
1034
+ const tasksDir = path5.join(cwd, EASY_CODING_DIR, TASKS_DIR);
1096
1035
  if (!await pathExists(tasksDir)) return [];
1097
1036
  const entries = await readdir(tasksDir, { withFileTypes: true });
1098
- return entries.filter((entry) => entry.isDirectory()).map((entry) => path7.join(tasksDir, entry.name, "task.json"));
1037
+ return entries.filter((entry) => entry.isDirectory()).map((entry) => path5.join(tasksDir, entry.name, "task.json"));
1099
1038
  }
1100
1039
  async function sessionFiles(cwd) {
1101
- const sessionsDir = path7.join(cwd, EASY_CODING_DIR, SESSIONS_DIR);
1040
+ const sessionsDir = path5.join(cwd, EASY_CODING_DIR, SESSIONS_DIR);
1102
1041
  if (!await pathExists(sessionsDir)) return [];
1103
1042
  const entries = await readdir(sessionsDir, { withFileTypes: true });
1104
- return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => path7.join(sessionsDir, entry.name));
1043
+ return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => path5.join(sessionsDir, entry.name));
1105
1044
  }
1106
1045
  async function readJsonRecord(filePath) {
1107
1046
  try {
@@ -1116,7 +1055,7 @@ async function hasLegacyWorkflowState(cwd) {
1116
1055
  if (!await pathExists(filePath)) continue;
1117
1056
  const task = await readJsonRecord(filePath);
1118
1057
  if (!task) continue;
1119
- if (hasLegacyTaskAgentIdentities(task) || isLegacyStage(task.status) || "verification_checkpoint" in task || !["PENDING", "COMPLETE", "CLOSED"].includes(String(task.status ?? "")) && ["analysis", "doc", "report"].includes(String(task.type ?? "").toLowerCase()) || !["PENDING", "COMPLETE", "CLOSED"].includes(String(task.status ?? "")) && String(task.type ?? "") !== "project-init" && !["fast", "standard", "strict"].includes(String(task.workflow_mode ?? "")) || Array.isArray(task.stage_history) && task.stage_history.some(
1058
+ if (hasLegacyTaskAgentIdentities(task) || isLegacyStage(task.status) || "verification_checkpoint" in task || "tdd_enabled" in task || "tdd_coverage_threshold" in task || !["PENDING", "COMPLETE", "CLOSED"].includes(String(task.status ?? "")) && ["analysis", "doc", "report"].includes(String(task.type ?? "").toLowerCase()) || !["PENDING", "COMPLETE", "CLOSED"].includes(String(task.status ?? "")) && String(task.type ?? "") !== "project-init" && !["fast", "standard", "strict"].includes(String(task.workflow_mode ?? "")) || Array.isArray(task.stage_history) && task.stage_history.some(
1120
1059
  (entry) => entry && typeof entry === "object" && !Array.isArray(entry) && isLegacyStage(entry.stage)
1121
1060
  )) {
1122
1061
  return true;
@@ -1128,7 +1067,7 @@ async function hasLegacyWorkflowState(cwd) {
1128
1067
  if (["agent", "last_agent"].some((field) => {
1129
1068
  const migrated = migratedAgentIdentity(session[field]);
1130
1069
  return migrated !== void 0 && migrated !== session[field];
1131
- }) || isLegacyStage(session.last_seen_stage) || "confirm_mode" in session) {
1070
+ }) || isLegacyStage(session.last_seen_stage) || "confirm_mode" in session || "tdd_enabled" in session || "tdd_coverage_threshold" in session) {
1132
1071
  return true;
1133
1072
  }
1134
1073
  }
@@ -1139,7 +1078,7 @@ async function migrateLegacyWorkflowState(cwd) {
1139
1078
  let sessionsUpdated = 0;
1140
1079
  const updatedTaskPaths = /* @__PURE__ */ new Set();
1141
1080
  let projectWorkflowMode = "adaptive";
1142
- const configPath2 = path7.join(cwd, EASY_CODING_DIR, "config.yaml");
1081
+ const configPath2 = path5.join(cwd, EASY_CODING_DIR, "config.yaml");
1143
1082
  if (await pathExists(configPath2)) {
1144
1083
  try {
1145
1084
  const config2 = await readConfigYaml(configPath2);
@@ -1194,7 +1133,7 @@ async function migrateLegacyWorkflowState(cwd) {
1194
1133
  if (!task) continue;
1195
1134
  const migrationOwned = task.workflow_mode_legacy === true && task.workflow_mode_confirmed_by === "upgrade-migration";
1196
1135
  if (!migrationOwned) continue;
1197
- const taskId = path7.basename(path7.dirname(filePath));
1136
+ const taskId = path5.basename(path5.dirname(filePath));
1198
1137
  const mode = migrationWorkflowMode(
1199
1138
  task,
1200
1139
  candidatesByTask.get(taskId) ?? [],
@@ -1229,7 +1168,7 @@ async function setPendingInitSince(cwd, version) {
1229
1168
  await writeTaskJson(filePath, task);
1230
1169
  }
1231
1170
  async function listTasks(cwd) {
1232
- const tasksDir = path7.join(cwd, EASY_CODING_DIR, TASKS_DIR);
1171
+ const tasksDir = path5.join(cwd, EASY_CODING_DIR, TASKS_DIR);
1233
1172
  if (!await pathExists(tasksDir)) {
1234
1173
  return [];
1235
1174
  }
@@ -1260,6 +1199,105 @@ function isActiveTask(task) {
1260
1199
  return task.status !== "COMPLETE" && task.status !== "CLOSED";
1261
1200
  }
1262
1201
 
1202
+ // src/utils/template-paths.ts
1203
+ import { existsSync } from "fs";
1204
+ import path6 from "path";
1205
+ import { fileURLToPath as fileURLToPath2 } from "url";
1206
+ function getTemplateRoot() {
1207
+ const here = path6.dirname(fileURLToPath2(import.meta.url));
1208
+ const candidates = [
1209
+ path6.resolve(here, "../templates"),
1210
+ path6.resolve(here, "../../templates"),
1211
+ path6.resolve(process.cwd(), "src/templates"),
1212
+ path6.resolve(process.cwd(), "templates")
1213
+ ];
1214
+ const found = candidates.find((candidate) => existsSync(candidate));
1215
+ if (!found) {
1216
+ throw new Error(`Unable to locate templates directory. Tried: ${candidates.join(", ")}`);
1217
+ }
1218
+ return found;
1219
+ }
1220
+ function getTemplatePath(...segments) {
1221
+ return path6.join(getTemplateRoot(), ...segments);
1222
+ }
1223
+
1224
+ // src/utils/runtime-scaffold.ts
1225
+ async function writeRuntimeScaffold(cwd, agents, opts = {}) {
1226
+ const easyCodingDir = path7.join(cwd, EASY_CODING_DIR);
1227
+ await ensureDir(easyCodingDir);
1228
+ const configPath2 = path7.join(easyCodingDir, CONFIG_FILE);
1229
+ let projectId = opts.projectId ?? createProjectId();
1230
+ if (!await pathExists(configPath2)) {
1231
+ const projectName = path7.basename(cwd);
1232
+ await writeConfigYaml(
1233
+ configPath2,
1234
+ createDefaultConfig({
1235
+ projectName,
1236
+ projectId,
1237
+ harnessVersion: VERSION,
1238
+ agents,
1239
+ supermodule: opts.supermodule
1240
+ })
1241
+ );
1242
+ } else {
1243
+ projectId = await ensureProjectId(configPath2);
1244
+ }
1245
+ await ensureDir(path7.join(easyCodingDir, "tasks"));
1246
+ await ensureDir(path7.join(easyCodingDir, SESSIONS_DIR));
1247
+ await ensureDir(path7.join(easyCodingDir, SPEC_DIR, MAIN_SPEC_DIR));
1248
+ await ensureDir(path7.join(easyCodingDir, SPEC_DIR, DEV_SPEC_DIR));
1249
+ await writeMemoryScaffold(easyCodingDir);
1250
+ await writeTemplatesScaffold(easyCodingDir);
1251
+ await writeToolsScaffold(cwd);
1252
+ return projectId;
1253
+ }
1254
+ async function runtimeToolUpdates(cwd) {
1255
+ const frozen = (await listTasks(cwd)).some(
1256
+ ({ task }) => isActiveTask(task) && (task.unit_test_mode === "ut" || task.unit_test_mode === "tdd" || task.tdd_enabled === true)
1257
+ );
1258
+ const updates = [];
1259
+ for (const file of ["easy_coding_java_coverage.py", "easy_coding_tdd_readiness.py"]) {
1260
+ const target = path7.join(cwd, EASY_CODING_DIR, TOOLS_DIR, file);
1261
+ const current = await readTextIfExists(target);
1262
+ if (frozen && current !== null) continue;
1263
+ const content = await readTextFile(getTemplatePath("runtime", "tools", file));
1264
+ if (content !== current) updates.push({ path: target, content });
1265
+ }
1266
+ return updates;
1267
+ }
1268
+ async function writeToolsScaffold(cwd) {
1269
+ const toolsDir = path7.join(cwd, EASY_CODING_DIR, TOOLS_DIR);
1270
+ await ensureDir(toolsDir);
1271
+ for (const update2 of await runtimeToolUpdates(cwd)) {
1272
+ await writeTextFile(update2.path, update2.content);
1273
+ }
1274
+ }
1275
+ async function writeTemplatesScaffold(easyCodingDir) {
1276
+ const templatesDir = path7.join(easyCodingDir, TEMPLATES_DIR);
1277
+ await ensureDir(templatesDir);
1278
+ const src = getTemplatePath("runtime", "templates", "dev-spec-skeleton.md");
1279
+ const dest = path7.join(templatesDir, "dev-spec-skeleton.md");
1280
+ await writeTextFile(dest, await readTextFile(src));
1281
+ }
1282
+ async function writeMemoryScaffold(easyCodingDir) {
1283
+ const memoryDir = path7.join(easyCodingDir, MEMORY_DIR);
1284
+ await ensureDir(path7.join(memoryDir, "short"));
1285
+ await ensureDir(path7.join(memoryDir, "long"));
1286
+ for (const file of ["MEMORY.md", "BUSINESS.md", "TECHNICAL.md"]) {
1287
+ const destination = path7.join(memoryDir, "long", file);
1288
+ if (await pathExists(destination)) {
1289
+ continue;
1290
+ }
1291
+ const templatePath = getTemplatePath("runtime", "memory", "long", file);
1292
+ await writeTextFile(destination, await readTextFile(templatePath));
1293
+ }
1294
+ const shortTemplateDest = path7.join(memoryDir, "SHORT_MEMORY_TEMPLATE.md");
1295
+ if (!await pathExists(shortTemplateDest)) {
1296
+ const templatePath = getTemplatePath("runtime", "memory", "SHORT_MEMORY_TEMPLATE.md");
1297
+ await writeTextFile(shortTemplateDest, await readTextFile(templatePath));
1298
+ }
1299
+ }
1300
+
1263
1301
  // src/commands/install-harness.ts
1264
1302
  import path13 from "path";
1265
1303
 
@@ -3182,64 +3220,65 @@ async function config() {
3182
3220
  return;
3183
3221
  }
3184
3222
  const workflowMode = "adaptive";
3185
- const tddEnabled = await select({
3186
- message: `Enable Java TDD for this project (current: ${current.tddEnabled ? "enabled" : "disabled"})`,
3187
- initialValue: current.tddEnabled,
3223
+ const unitTestMode = await select({
3224
+ message: `Select Java unit test strategy (current: ${current.unitTestMode})`,
3225
+ initialValue: current.unitTestMode,
3188
3226
  options: [
3189
- { value: false, label: "disabled \u2014 preserve current test depth (default)" },
3190
- { value: true, label: "enabled \u2014 require TDD evidence and changed-line coverage" }
3227
+ { value: "none", label: "none \u2014 preserve task-required verification (default)" },
3228
+ { value: "ut", label: "UT \u2014 passing unit tests and changed-line coverage" },
3229
+ { value: "tdd", label: "TDD \u2014 test-first development and changed-line coverage" }
3191
3230
  ]
3192
3231
  });
3193
- if (typeof tddEnabled === "symbol") {
3232
+ if (typeof unitTestMode === "symbol") {
3194
3233
  cancel4("Configuration cancelled.");
3195
3234
  return;
3196
3235
  }
3197
- if (tddEnabled) {
3236
+ if (unitTestMode !== "none") {
3198
3237
  const readiness = await inspectTddReadiness(process.cwd());
3199
3238
  if (readiness.status !== "ready") {
3200
3239
  cancel4(
3201
- `TDD was not enabled. ${readiness.status === "needs_init" ? "Run ec-tdd-init first" : "Repair TDD readiness"}: ${readiness.reasons.join("; ")}. No project modes were changed.`
3240
+ `Unit test strategy was not enabled. ${readiness.status === "needs_init" ? "Run ec-tdd-init first" : "Repair coverage readiness"}: ${readiness.reasons.join("; ")}. No project modes were changed.`
3202
3241
  );
3203
3242
  return;
3204
3243
  }
3205
3244
  }
3206
- let tddCoverageThreshold = current.tddCoverageThreshold;
3207
- if (tddEnabled) {
3245
+ let utCoverageThreshold = current.utCoverageThreshold;
3246
+ if (unitTestMode !== "none") {
3208
3247
  const thresholdInput = await text({
3209
3248
  message: "Minimum changed-production-line coverage percentage",
3210
- initialValue: String(current.tddCoverageThreshold),
3249
+ initialValue: String(current.utCoverageThreshold),
3211
3250
  validate(value) {
3212
3251
  const parsed = Number(value);
3213
- return isTddCoverageThreshold(parsed) ? void 0 : "Enter an integer from 1 to 100.";
3252
+ return isUtCoverageThreshold(parsed) ? void 0 : "Enter an integer from 1 to 100.";
3214
3253
  }
3215
3254
  });
3216
3255
  if (typeof thresholdInput === "symbol") {
3217
3256
  cancel4("Configuration cancelled.");
3218
3257
  return;
3219
3258
  }
3220
- tddCoverageThreshold = Number(thresholdInput);
3259
+ utCoverageThreshold = Number(thresholdInput);
3221
3260
  }
3222
3261
  const shouldSave = await confirm3({
3223
- message: `Set approval=${approvalMode}, workflow=${workflowMode}, TDD=${tddEnabled ? `enabled (${tddCoverageThreshold}%)` : "disabled"}?`,
3262
+ message: `Set approval=${approvalMode}, workflow=${workflowMode}, unit-test=${unitTestMode}${unitTestMode === "none" ? "" : ` (${utCoverageThreshold}%)`}?`,
3224
3263
  initialValue: true
3225
3264
  });
3226
3265
  if (typeof shouldSave === "symbol" || !shouldSave) {
3227
3266
  cancel4("Configuration cancelled.");
3228
3267
  return;
3229
3268
  }
3230
- if (tddEnabled) {
3269
+ if (unitTestMode !== "none") {
3231
3270
  const readiness = await inspectTddReadiness(process.cwd());
3232
3271
  if (readiness.status !== "ready") {
3233
3272
  cancel4(
3234
- `TDD was not enabled because readiness changed before save: ${readiness.reasons.join("; ")}. No project modes were changed.`
3273
+ `Unit test strategy was not enabled because readiness changed before save: ${readiness.reasons.join("; ")}. No project modes were changed.`
3235
3274
  );
3236
3275
  return;
3237
3276
  }
3238
3277
  }
3239
- await setBehaviorModes(configPath2, approvalMode, workflowMode, tddEnabled, tddCoverageThreshold);
3278
+ await setBehaviorModes(configPath2, approvalMode, workflowMode, unitTestMode, utCoverageThreshold);
3240
3279
  outro3(
3241
3280
  chalk4.green(
3242
- `Project modes updated: approval=${approvalMode}, workflow=${workflowMode}, TDD=${tddEnabled ? `${tddCoverageThreshold}%` : "off"}.`
3281
+ `Project modes updated: approval=${approvalMode}, workflow=${workflowMode}, unit-test=${unitTestMode}${unitTestMode === "none" ? "" : ` (${utCoverageThreshold}%)`}.`
3243
3282
  )
3244
3283
  );
3245
3284
  }
@@ -3530,9 +3569,11 @@ async function cleanSessionRuntime(cwd, options = {}) {
3530
3569
  const maxSessions = options.maxSessions ?? MAX_SESSION_FILES;
3531
3570
  const reserveSlots = options.reserveSlots ?? 0;
3532
3571
  const candidates = (await listSessionCleanupCandidates(cwd)).filter((candidate) => {
3533
- if (!options.preserveTddSettings) return true;
3572
+ if (!options.preserveUnitTestSettings) return true;
3534
3573
  const session = parseSessionFile(candidate.content);
3535
- return !session || !("tdd_enabled" in session || "tdd_coverage_threshold" in session);
3574
+ return !session || !["unit_test_mode", "ut_coverage_threshold", "tdd_enabled", "tdd_coverage_threshold"].some(
3575
+ (key) => key in session
3576
+ );
3536
3577
  });
3537
3578
  const removed = /* @__PURE__ */ new Set();
3538
3579
  for (const candidate of candidates) {
@@ -3674,7 +3715,6 @@ async function status() {
3674
3715
  const activeTasks = tasks.filter((item) => isActiveTask(item.task));
3675
3716
  const sessions = await listSessionFiles(cwd);
3676
3717
  const versionRelation = compareVersions(config2.harness_version, VERSION);
3677
- const tddReadiness = await inspectTddReadiness(cwd);
3678
3718
  console.log(chalk6.bold("Harness"));
3679
3719
  console.log(` version: ${config2.harness_version}`);
3680
3720
  console.log(` cli: ${VERSION}`);
@@ -3690,27 +3730,29 @@ async function status() {
3690
3730
  const migratedBehavior = resolveLegacyBehavior(config2);
3691
3731
  const projectApprovalMode = isApprovalMode(config2.behavior?.approval_mode) ? config2.behavior.approval_mode : migratedBehavior.approvalMode;
3692
3732
  const projectWorkflowMode = isConfiguredWorkflowMode(config2.behavior?.workflow_mode) ? config2.behavior.workflow_mode : migratedBehavior.workflowMode;
3693
- const projectTddEnabled = migratedBehavior.tddEnabled;
3694
- const projectTddCoverageThreshold = migratedBehavior.tddCoverageThreshold;
3733
+ const projectUnitTestMode = migratedBehavior.unitTestMode;
3734
+ const projectUtCoverageThreshold = migratedBehavior.utCoverageThreshold;
3735
+ const needsCoverage = projectUnitTestMode !== "none" || sessions.some(({ session }) => ["ut", "tdd"].includes(session.unit_test_mode ?? "none")) || activeTasks.some(({ task }) => ["ut", "tdd"].includes(task.unit_test_mode ?? "none"));
3736
+ const readiness = needsCoverage ? await inspectTddReadiness(cwd) : { status: "not_checked", reasons: [] };
3695
3737
  console.log(` approval_mode: ${projectApprovalMode}`);
3696
3738
  console.log(` workflow_mode: ${projectWorkflowMode}`);
3697
- console.log(` tdd_enabled: ${projectTddEnabled}`);
3698
- console.log(` tdd_coverage_threshold: ${projectTddCoverageThreshold}`);
3699
- console.log(` tdd_readiness: ${tddReadiness.status}`);
3700
- if (tddReadiness.status !== "ready") {
3701
- console.log(` tdd_readiness_reasons: ${tddReadiness.reasons.join("; ")}`);
3739
+ console.log(` unit_test_mode: ${projectUnitTestMode}`);
3740
+ console.log(` ut_coverage_threshold: ${projectUtCoverageThreshold}`);
3741
+ console.log(` unit_test_readiness: ${readiness.status}`);
3742
+ if (readiness.reasons.length > 0) {
3743
+ console.log(` unit_test_readiness_reasons: ${readiness.reasons.join("; ")}`);
3702
3744
  }
3703
3745
  console.log("");
3704
3746
  console.log(chalk6.bold("Sessions"));
3705
3747
  console.log(` project_approval_mode: ${projectApprovalMode}`);
3706
3748
  console.log(` project_workflow_mode: ${projectWorkflowMode}`);
3707
- console.log(` project_tdd_enabled: ${projectTddEnabled}`);
3708
- console.log(` project_tdd_coverage_threshold: ${projectTddCoverageThreshold}`);
3749
+ console.log(` project_unit_test_mode: ${projectUnitTestMode}`);
3750
+ console.log(` project_ut_coverage_threshold: ${projectUtCoverageThreshold}`);
3709
3751
  console.log(` effective_approval_mode: ${projectApprovalMode} (without a session override)`);
3710
3752
  console.log(` configured_workflow_mode: ${projectWorkflowMode} (without a session override)`);
3711
- console.log(` effective_tdd_enabled: ${projectTddEnabled} (without a session override)`);
3753
+ console.log(` effective_unit_test_mode: ${projectUnitTestMode} (without a session override)`);
3712
3754
  console.log(
3713
- ` effective_tdd_coverage_threshold: ${projectTddCoverageThreshold} (without a session override)`
3755
+ ` effective_ut_coverage_threshold: ${projectUtCoverageThreshold} (without a session override)`
3714
3756
  );
3715
3757
  if (sessions.length === 0) {
3716
3758
  console.log(" no session files");
@@ -3722,20 +3764,20 @@ async function status() {
3722
3764
  );
3723
3765
  const sessionApprovalMode = session.approval_mode ?? (legacySessionMode === "lite" ? "guard" : legacySessionMode);
3724
3766
  const sessionWorkflowMode = session.workflow_mode ?? (legacySessionMode === "lite" ? "fast" : hasLegacySessionMode ? "adaptive" : void 0);
3725
- const sessionTddEnabled = session.tdd_enabled;
3726
- const sessionTddCoverageThreshold = session.tdd_coverage_threshold;
3767
+ const sessionUnitTestMode = session.unit_test_mode;
3768
+ const sessionUtCoverageThreshold = session.ut_coverage_threshold;
3727
3769
  console.log(` - ${key}`);
3728
3770
  console.log(` agent: ${session.agent ?? "legacy/unknown"}`);
3729
3771
  console.log(` source: ${session.session_source ?? "legacy"}`);
3730
3772
  console.log(` approval_mode: ${sessionApprovalMode ?? "project default"}`);
3731
3773
  console.log(` workflow_mode: ${sessionWorkflowMode ?? "project default"}`);
3732
- console.log(` tdd_enabled: ${sessionTddEnabled ?? "project default"}`);
3733
- console.log(` tdd_coverage_threshold: ${sessionTddCoverageThreshold ?? "project default"}`);
3774
+ console.log(` unit_test_mode: ${sessionUnitTestMode ?? "project default"}`);
3775
+ console.log(` ut_coverage_threshold: ${sessionUtCoverageThreshold ?? "project default"}`);
3734
3776
  console.log(` effective_approval_mode: ${sessionApprovalMode ?? projectApprovalMode}`);
3735
3777
  console.log(` configured_workflow_mode: ${sessionWorkflowMode ?? projectWorkflowMode}`);
3736
- console.log(` effective_tdd_enabled: ${sessionTddEnabled ?? projectTddEnabled}`);
3778
+ console.log(` effective_unit_test_mode: ${sessionUnitTestMode ?? projectUnitTestMode}`);
3737
3779
  console.log(
3738
- ` effective_tdd_coverage_threshold: ${sessionTddCoverageThreshold ?? projectTddCoverageThreshold}`
3780
+ ` effective_ut_coverage_threshold: ${sessionUtCoverageThreshold ?? projectUtCoverageThreshold}`
3739
3781
  );
3740
3782
  console.log(
3741
3783
  ` harness: ${session.harness_disabled ? "disabled for this session" : "enabled"}`
@@ -3756,10 +3798,8 @@ async function status() {
3756
3798
  console.log(
3757
3799
  ` task_workflow_mode: ${task.workflow_mode ?? task.workflow_mode_proposal?.selected_mode ?? "not resolved"}`
3758
3800
  );
3759
- console.log(` task_tdd_enabled: ${task.tdd_enabled ?? "not frozen"}`);
3760
- console.log(
3761
- ` task_tdd_coverage_threshold: ${task.tdd_coverage_threshold ?? "not frozen"}`
3762
- );
3801
+ console.log(` task_unit_test_mode: ${task.unit_test_mode ?? "not frozen"}`);
3802
+ console.log(` task_ut_coverage_threshold: ${task.ut_coverage_threshold ?? "not frozen"}`);
3763
3803
  console.log(` last_agent: ${task.last_agent}`);
3764
3804
  } else {
3765
3805
  console.log(` current_task: ${session.current_task} (task.json missing)`);
@@ -3874,10 +3914,11 @@ async function upgrade(opts) {
3874
3914
  "Will overwrite managed skills, hooks, agents, templates, and generated main-constraint regions.",
3875
3915
  "Will remove retired files that still match the previous install manifest and preserve locally modified copies.",
3876
3916
  "Will update project-init task to recommend ec-init re-run for version adaptation.",
3877
- "Will migrate behavior config to schema 5 while preserving project/session TDD settings and frozen task baselines.",
3878
- "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.",
3917
+ "Will migrate behavior config to schema 6 while preserving project/session unit test settings and frozen task baselines.",
3918
+ "Will preserve runtime tools used by active UT/TDD tasks; run upgrade after those tasks finish to refresh the tools.",
3919
+ "Will prune expired session bindings without explicit unit test settings and orphan acceptance snapshots in each upgraded target while preserving tasks, memory, spec, and project knowledge.",
3879
3920
  "Will migrate active REVIEW/VERIFICATION tasks to QUALITY, retire active read-only task types as CLOSED, and preserve their artifacts and history.",
3880
- "Will migrate legacy workflow/TDD task metadata; memory content, spec, and project knowledge files remain untouched."
3921
+ "Will migrate legacy workflow/unit test task metadata; memory content, spec, and project knowledge files remain untouched."
3881
3922
  ].join("\n");
3882
3923
  if (opts.dryRun) {
3883
3924
  console.log(summary);
@@ -3895,7 +3936,9 @@ async function upgrade(opts) {
3895
3936
  }
3896
3937
  for (const { target, config: config2 } of pending) {
3897
3938
  const previousManifest = await readInstallManifest(target.dir);
3898
- const sessionCleanup = await cleanSessionRuntime(target.dir, { preserveTddSettings: true });
3939
+ const sessionCleanup = await cleanSessionRuntime(target.dir, {
3940
+ preserveUnitTestSettings: true
3941
+ });
3899
3942
  if (sessionCleanup.sessionsRemoved > 0 || sessionCleanup.acceptanceSnapshotsRemoved > 0) {
3900
3943
  console.log(
3901
3944
  chalk8.yellow(
@@ -3968,7 +4011,7 @@ async function resolvePendingUpgradeTargets(targets) {
3968
4011
  `${target.label} harness version ${installedVersion} is newer than CLI ${VERSION}. Update the CLI first.`
3969
4012
  );
3970
4013
  }
3971
- if (relation === -1 || relation === 0 && (installedVersion !== VERSION || await needsHookConfigRefresh(target, config2) || await hasLegacyWorkflowState(target.dir))) {
4014
+ if (relation === -1 || relation === 0 && (installedVersion !== VERSION || await needsHookConfigRefresh(target, config2) || await hasLegacyWorkflowState(target.dir) || (await runtimeToolUpdates(target.dir)).length > 0)) {
3972
4015
  pending.push({ target, config: config2 });
3973
4016
  }
3974
4017
  }