easy-coding-harness 1.0.1 → 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.
Files changed (28) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +17 -15
  3. package/dist/cli.js +209 -195
  4. package/dist/cli.js.map +1 -1
  5. package/package.json +1 -1
  6. package/templates/claude/agents/ec-implementer.md +4 -0
  7. package/templates/claude/agents/ec-reviewer.md +6 -0
  8. package/templates/codex/agents/ec-implementer.toml +4 -0
  9. package/templates/codex/agents/ec-reviewer.toml +6 -0
  10. package/templates/common/bundled-skills/ec-init/SKILL.md +2 -2
  11. package/templates/common/bundled-skills/ec-meta/references/local-architecture/README.md +6 -6
  12. package/templates/common/skills/ec-analysis/SKILL.md +56 -104
  13. package/templates/common/skills/ec-config/SKILL.md +37 -47
  14. package/templates/common/skills/ec-implementing/SKILL.md +26 -10
  15. package/templates/common/skills/ec-lite/SKILL.md +1 -1
  16. package/templates/common/skills/ec-memory/SKILL.md +3 -4
  17. package/templates/common/skills/ec-quality/SKILL.md +46 -25
  18. package/templates/common/skills/ec-task-management/SKILL.md +2 -2
  19. package/templates/common/skills/ec-tdd-init/SKILL.md +7 -7
  20. package/templates/common/skills/ec-workflow/SKILL.md +24 -22
  21. package/templates/main-constraint/AGENTS.md.tpl +23 -16
  22. package/templates/main-constraint/CLAUDE.md.tpl +23 -16
  23. package/templates/qoder/agents/ec-implementer.md +4 -0
  24. package/templates/qoder/agents/ec-reviewer.md +6 -0
  25. package/templates/runtime/tools/easy_coding_java_coverage.py +5 -5
  26. package/templates/shared-hooks/easy_coding_inputs.py +305 -0
  27. package/templates/shared-hooks/easy_coding_state.py +495 -461
  28. package/templates/shared-hooks/inject-subagent-context.py +3 -3
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
 
@@ -3181,94 +3219,66 @@ async function config() {
3181
3219
  cancel4("Configuration cancelled.");
3182
3220
  return;
3183
3221
  }
3184
- const workflowMode = await select({
3185
- message: `Select project workflow mode (current: ${current.workflowMode})`,
3186
- initialValue: current.workflowMode,
3222
+ const workflowMode = "adaptive";
3223
+ const unitTestMode = await select({
3224
+ message: `Select Java unit test strategy (current: ${current.unitTestMode})`,
3225
+ initialValue: current.unitTestMode,
3187
3226
  options: [
3188
- {
3189
- value: "adaptive",
3190
- label: "adaptive \u2014 choose by task risk (default)",
3191
- hint: "freezes to fast, standard, or strict after ANALYSIS"
3192
- },
3193
- {
3194
- value: "fast",
3195
- label: "fast \u2014 compact execution for low-risk tasks",
3196
- hint: "all workflow stages still run"
3197
- },
3198
- {
3199
- value: "standard",
3200
- label: "standard \u2014 balanced execution",
3201
- hint: "independent review and impacted verification"
3202
- },
3203
- {
3204
- value: "strict",
3205
- label: "strict \u2014 maximum assurance",
3206
- hint: "multi-dimensional review and full verification"
3207
- }
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" }
3208
3230
  ]
3209
3231
  });
3210
- if (typeof workflowMode === "symbol") {
3232
+ if (typeof unitTestMode === "symbol") {
3211
3233
  cancel4("Configuration cancelled.");
3212
3234
  return;
3213
3235
  }
3214
- const tddEnabled = await select({
3215
- message: `Enable Java TDD for this project (current: ${current.tddEnabled ? "enabled" : "disabled"})`,
3216
- initialValue: current.tddEnabled,
3217
- options: [
3218
- { value: false, label: "disabled \u2014 preserve current test depth (default)" },
3219
- { value: true, label: "enabled \u2014 require TDD evidence and changed-line coverage" }
3220
- ]
3221
- });
3222
- if (typeof tddEnabled === "symbol") {
3223
- cancel4("Configuration cancelled.");
3224
- return;
3225
- }
3226
- if (tddEnabled) {
3236
+ if (unitTestMode !== "none") {
3227
3237
  const readiness = await inspectTddReadiness(process.cwd());
3228
3238
  if (readiness.status !== "ready") {
3229
3239
  cancel4(
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.`
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.`
3231
3241
  );
3232
3242
  return;
3233
3243
  }
3234
3244
  }
3235
- let tddCoverageThreshold = current.tddCoverageThreshold;
3236
- if (tddEnabled) {
3245
+ let utCoverageThreshold = current.utCoverageThreshold;
3246
+ if (unitTestMode !== "none") {
3237
3247
  const thresholdInput = await text({
3238
3248
  message: "Minimum changed-production-line coverage percentage",
3239
- initialValue: String(current.tddCoverageThreshold),
3249
+ initialValue: String(current.utCoverageThreshold),
3240
3250
  validate(value) {
3241
3251
  const parsed = Number(value);
3242
- 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.";
3243
3253
  }
3244
3254
  });
3245
3255
  if (typeof thresholdInput === "symbol") {
3246
3256
  cancel4("Configuration cancelled.");
3247
3257
  return;
3248
3258
  }
3249
- tddCoverageThreshold = Number(thresholdInput);
3259
+ utCoverageThreshold = Number(thresholdInput);
3250
3260
  }
3251
3261
  const shouldSave = await confirm3({
3252
- message: `Set approval=${approvalMode}, workflow=${workflowMode}, TDD=${tddEnabled ? `enabled (${tddCoverageThreshold}%)` : "disabled"}?`,
3262
+ message: `Set approval=${approvalMode}, workflow=${workflowMode}, unit-test=${unitTestMode}${unitTestMode === "none" ? "" : ` (${utCoverageThreshold}%)`}?`,
3253
3263
  initialValue: true
3254
3264
  });
3255
3265
  if (typeof shouldSave === "symbol" || !shouldSave) {
3256
3266
  cancel4("Configuration cancelled.");
3257
3267
  return;
3258
3268
  }
3259
- if (tddEnabled) {
3269
+ if (unitTestMode !== "none") {
3260
3270
  const readiness = await inspectTddReadiness(process.cwd());
3261
3271
  if (readiness.status !== "ready") {
3262
3272
  cancel4(
3263
- `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.`
3264
3274
  );
3265
3275
  return;
3266
3276
  }
3267
3277
  }
3268
- await setBehaviorModes(configPath2, approvalMode, workflowMode, tddEnabled, tddCoverageThreshold);
3278
+ await setBehaviorModes(configPath2, approvalMode, workflowMode, unitTestMode, utCoverageThreshold);
3269
3279
  outro3(
3270
3280
  chalk4.green(
3271
- `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}%)`}.`
3272
3282
  )
3273
3283
  );
3274
3284
  }
@@ -3559,9 +3569,11 @@ async function cleanSessionRuntime(cwd, options = {}) {
3559
3569
  const maxSessions = options.maxSessions ?? MAX_SESSION_FILES;
3560
3570
  const reserveSlots = options.reserveSlots ?? 0;
3561
3571
  const candidates = (await listSessionCleanupCandidates(cwd)).filter((candidate) => {
3562
- if (!options.preserveTddSettings) return true;
3572
+ if (!options.preserveUnitTestSettings) return true;
3563
3573
  const session = parseSessionFile(candidate.content);
3564
- 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
+ );
3565
3577
  });
3566
3578
  const removed = /* @__PURE__ */ new Set();
3567
3579
  for (const candidate of candidates) {
@@ -3703,7 +3715,6 @@ async function status() {
3703
3715
  const activeTasks = tasks.filter((item) => isActiveTask(item.task));
3704
3716
  const sessions = await listSessionFiles(cwd);
3705
3717
  const versionRelation = compareVersions(config2.harness_version, VERSION);
3706
- const tddReadiness = await inspectTddReadiness(cwd);
3707
3718
  console.log(chalk6.bold("Harness"));
3708
3719
  console.log(` version: ${config2.harness_version}`);
3709
3720
  console.log(` cli: ${VERSION}`);
@@ -3719,27 +3730,29 @@ async function status() {
3719
3730
  const migratedBehavior = resolveLegacyBehavior(config2);
3720
3731
  const projectApprovalMode = isApprovalMode(config2.behavior?.approval_mode) ? config2.behavior.approval_mode : migratedBehavior.approvalMode;
3721
3732
  const projectWorkflowMode = isConfiguredWorkflowMode(config2.behavior?.workflow_mode) ? config2.behavior.workflow_mode : migratedBehavior.workflowMode;
3722
- const projectTddEnabled = migratedBehavior.tddEnabled;
3723
- 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: [] };
3724
3737
  console.log(` approval_mode: ${projectApprovalMode}`);
3725
3738
  console.log(` workflow_mode: ${projectWorkflowMode}`);
3726
- console.log(` tdd_enabled: ${projectTddEnabled}`);
3727
- console.log(` tdd_coverage_threshold: ${projectTddCoverageThreshold}`);
3728
- console.log(` tdd_readiness: ${tddReadiness.status}`);
3729
- if (tddReadiness.status !== "ready") {
3730
- 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("; ")}`);
3731
3744
  }
3732
3745
  console.log("");
3733
3746
  console.log(chalk6.bold("Sessions"));
3734
3747
  console.log(` project_approval_mode: ${projectApprovalMode}`);
3735
3748
  console.log(` project_workflow_mode: ${projectWorkflowMode}`);
3736
- console.log(` project_tdd_enabled: ${projectTddEnabled}`);
3737
- console.log(` project_tdd_coverage_threshold: ${projectTddCoverageThreshold}`);
3749
+ console.log(` project_unit_test_mode: ${projectUnitTestMode}`);
3750
+ console.log(` project_ut_coverage_threshold: ${projectUtCoverageThreshold}`);
3738
3751
  console.log(` effective_approval_mode: ${projectApprovalMode} (without a session override)`);
3739
3752
  console.log(` configured_workflow_mode: ${projectWorkflowMode} (without a session override)`);
3740
- console.log(` effective_tdd_enabled: ${projectTddEnabled} (without a session override)`);
3753
+ console.log(` effective_unit_test_mode: ${projectUnitTestMode} (without a session override)`);
3741
3754
  console.log(
3742
- ` effective_tdd_coverage_threshold: ${projectTddCoverageThreshold} (without a session override)`
3755
+ ` effective_ut_coverage_threshold: ${projectUtCoverageThreshold} (without a session override)`
3743
3756
  );
3744
3757
  if (sessions.length === 0) {
3745
3758
  console.log(" no session files");
@@ -3751,20 +3764,20 @@ async function status() {
3751
3764
  );
3752
3765
  const sessionApprovalMode = session.approval_mode ?? (legacySessionMode === "lite" ? "guard" : legacySessionMode);
3753
3766
  const sessionWorkflowMode = session.workflow_mode ?? (legacySessionMode === "lite" ? "fast" : hasLegacySessionMode ? "adaptive" : void 0);
3754
- const sessionTddEnabled = session.tdd_enabled;
3755
- const sessionTddCoverageThreshold = session.tdd_coverage_threshold;
3767
+ const sessionUnitTestMode = session.unit_test_mode;
3768
+ const sessionUtCoverageThreshold = session.ut_coverage_threshold;
3756
3769
  console.log(` - ${key}`);
3757
3770
  console.log(` agent: ${session.agent ?? "legacy/unknown"}`);
3758
3771
  console.log(` source: ${session.session_source ?? "legacy"}`);
3759
3772
  console.log(` approval_mode: ${sessionApprovalMode ?? "project default"}`);
3760
3773
  console.log(` workflow_mode: ${sessionWorkflowMode ?? "project default"}`);
3761
- console.log(` tdd_enabled: ${sessionTddEnabled ?? "project default"}`);
3762
- 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"}`);
3763
3776
  console.log(` effective_approval_mode: ${sessionApprovalMode ?? projectApprovalMode}`);
3764
3777
  console.log(` configured_workflow_mode: ${sessionWorkflowMode ?? projectWorkflowMode}`);
3765
- console.log(` effective_tdd_enabled: ${sessionTddEnabled ?? projectTddEnabled}`);
3778
+ console.log(` effective_unit_test_mode: ${sessionUnitTestMode ?? projectUnitTestMode}`);
3766
3779
  console.log(
3767
- ` effective_tdd_coverage_threshold: ${sessionTddCoverageThreshold ?? projectTddCoverageThreshold}`
3780
+ ` effective_ut_coverage_threshold: ${sessionUtCoverageThreshold ?? projectUtCoverageThreshold}`
3768
3781
  );
3769
3782
  console.log(
3770
3783
  ` harness: ${session.harness_disabled ? "disabled for this session" : "enabled"}`
@@ -3785,10 +3798,8 @@ async function status() {
3785
3798
  console.log(
3786
3799
  ` task_workflow_mode: ${task.workflow_mode ?? task.workflow_mode_proposal?.selected_mode ?? "not resolved"}`
3787
3800
  );
3788
- console.log(` task_tdd_enabled: ${task.tdd_enabled ?? "not frozen"}`);
3789
- console.log(
3790
- ` task_tdd_coverage_threshold: ${task.tdd_coverage_threshold ?? "not frozen"}`
3791
- );
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"}`);
3792
3803
  console.log(` last_agent: ${task.last_agent}`);
3793
3804
  } else {
3794
3805
  console.log(` current_task: ${session.current_task} (task.json missing)`);
@@ -3903,10 +3914,11 @@ async function upgrade(opts) {
3903
3914
  "Will overwrite managed skills, hooks, agents, templates, and generated main-constraint regions.",
3904
3915
  "Will remove retired files that still match the previous install manifest and preserve locally modified copies.",
3905
3916
  "Will update project-init task to recommend ec-init re-run for version adaptation.",
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.",
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.",
3908
3920
  "Will migrate active REVIEW/VERIFICATION tasks to QUALITY, retire active read-only task types as CLOSED, and preserve their artifacts and history.",
3909
- "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."
3910
3922
  ].join("\n");
3911
3923
  if (opts.dryRun) {
3912
3924
  console.log(summary);
@@ -3924,7 +3936,9 @@ async function upgrade(opts) {
3924
3936
  }
3925
3937
  for (const { target, config: config2 } of pending) {
3926
3938
  const previousManifest = await readInstallManifest(target.dir);
3927
- const sessionCleanup = await cleanSessionRuntime(target.dir, { preserveTddSettings: true });
3939
+ const sessionCleanup = await cleanSessionRuntime(target.dir, {
3940
+ preserveUnitTestSettings: true
3941
+ });
3928
3942
  if (sessionCleanup.sessionsRemoved > 0 || sessionCleanup.acceptanceSnapshotsRemoved > 0) {
3929
3943
  console.log(
3930
3944
  chalk8.yellow(
@@ -3997,7 +4011,7 @@ async function resolvePendingUpgradeTargets(targets) {
3997
4011
  `${target.label} harness version ${installedVersion} is newer than CLI ${VERSION}. Update the CLI first.`
3998
4012
  );
3999
4013
  }
4000
- 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)) {
4001
4015
  pending.push({ target, config: config2 });
4002
4016
  }
4003
4017
  }