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/CHANGELOG.md +11 -0
- package/README.md +11 -10
- package/dist/cli.js +208 -165
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/templates/common/bundled-skills/ec-init/SKILL.md +2 -2
- package/templates/common/bundled-skills/ec-meta/references/local-architecture/README.md +6 -6
- package/templates/common/skills/ec-analysis/SKILL.md +30 -41
- package/templates/common/skills/ec-config/SKILL.md +37 -46
- package/templates/common/skills/ec-implementing/SKILL.md +4 -3
- package/templates/common/skills/ec-lite/SKILL.md +1 -1
- package/templates/common/skills/ec-memory/SKILL.md +3 -4
- package/templates/common/skills/ec-quality/SKILL.md +9 -4
- package/templates/common/skills/ec-task-management/SKILL.md +2 -2
- package/templates/common/skills/ec-tdd-init/SKILL.md +7 -7
- package/templates/common/skills/ec-workflow/SKILL.md +9 -9
- package/templates/main-constraint/AGENTS.md.tpl +15 -14
- package/templates/main-constraint/CLAUDE.md.tpl +15 -14
- package/templates/runtime/tools/easy_coding_java_coverage.py +5 -5
- package/templates/shared-hooks/easy_coding_inputs.py +1 -1
- package/templates/shared-hooks/easy_coding_state.py +173 -138
- 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 =
|
|
107
|
-
var
|
|
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
|
-
|
|
131
|
-
|
|
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
|
|
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
|
|
194
|
-
const
|
|
195
|
-
const
|
|
196
|
-
return { approvalMode, workflowMode,
|
|
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,
|
|
199
|
-
if (
|
|
200
|
-
throw new Error("
|
|
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.
|
|
213
|
-
behavior.
|
|
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,
|
|
221
|
-
return setBehaviorModes(filePath, approvalMode, workflowMode,
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
1010
|
-
|
|
1011
|
-
task.
|
|
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 =
|
|
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) =>
|
|
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 =
|
|
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) =>
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
3186
|
-
message: `
|
|
3187
|
-
initialValue: current.
|
|
3223
|
+
const unitTestMode = await select({
|
|
3224
|
+
message: `Select Java unit test strategy (current: ${current.unitTestMode})`,
|
|
3225
|
+
initialValue: current.unitTestMode,
|
|
3188
3226
|
options: [
|
|
3189
|
-
{ value:
|
|
3190
|
-
{ value:
|
|
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
|
|
3232
|
+
if (typeof unitTestMode === "symbol") {
|
|
3194
3233
|
cancel4("Configuration cancelled.");
|
|
3195
3234
|
return;
|
|
3196
3235
|
}
|
|
3197
|
-
if (
|
|
3236
|
+
if (unitTestMode !== "none") {
|
|
3198
3237
|
const readiness = await inspectTddReadiness(process.cwd());
|
|
3199
3238
|
if (readiness.status !== "ready") {
|
|
3200
3239
|
cancel4(
|
|
3201
|
-
`
|
|
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
|
|
3207
|
-
if (
|
|
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.
|
|
3249
|
+
initialValue: String(current.utCoverageThreshold),
|
|
3211
3250
|
validate(value) {
|
|
3212
3251
|
const parsed = Number(value);
|
|
3213
|
-
return
|
|
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
|
-
|
|
3259
|
+
utCoverageThreshold = Number(thresholdInput);
|
|
3221
3260
|
}
|
|
3222
3261
|
const shouldSave = await confirm3({
|
|
3223
|
-
message: `Set approval=${approvalMode}, workflow=${workflowMode},
|
|
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 (
|
|
3269
|
+
if (unitTestMode !== "none") {
|
|
3231
3270
|
const readiness = await inspectTddReadiness(process.cwd());
|
|
3232
3271
|
if (readiness.status !== "ready") {
|
|
3233
3272
|
cancel4(
|
|
3234
|
-
`
|
|
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,
|
|
3278
|
+
await setBehaviorModes(configPath2, approvalMode, workflowMode, unitTestMode, utCoverageThreshold);
|
|
3240
3279
|
outro3(
|
|
3241
3280
|
chalk4.green(
|
|
3242
|
-
`Project modes updated: approval=${approvalMode}, workflow=${workflowMode},
|
|
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.
|
|
3572
|
+
if (!options.preserveUnitTestSettings) return true;
|
|
3534
3573
|
const session = parseSessionFile(candidate.content);
|
|
3535
|
-
return !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
|
|
3694
|
-
const
|
|
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(`
|
|
3698
|
-
console.log(`
|
|
3699
|
-
console.log(`
|
|
3700
|
-
if (
|
|
3701
|
-
console.log(`
|
|
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(`
|
|
3708
|
-
console.log(`
|
|
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(`
|
|
3753
|
+
console.log(` effective_unit_test_mode: ${projectUnitTestMode} (without a session override)`);
|
|
3712
3754
|
console.log(
|
|
3713
|
-
`
|
|
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
|
|
3726
|
-
const
|
|
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(`
|
|
3733
|
-
console.log(`
|
|
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(`
|
|
3778
|
+
console.log(` effective_unit_test_mode: ${sessionUnitTestMode ?? projectUnitTestMode}`);
|
|
3737
3779
|
console.log(
|
|
3738
|
-
`
|
|
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(`
|
|
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
|
|
3878
|
-
"Will
|
|
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/
|
|
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, {
|
|
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
|
}
|