easy-coding-harness 0.10.0-beta.0 → 0.10.0-beta.2
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 +31 -0
- package/README.md +9 -3
- package/dist/cli.js +331 -46
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/templates/common/bundled-skills/ec-init/SKILL.md +8 -0
- package/templates/common/bundled-skills/ec-meta/references/local-architecture/README.md +9 -2
- package/templates/common/bundled-skills/ec-meta/references/platform-files/README.md +1 -1
- package/templates/common/skills/ec-analysis/SKILL.md +38 -0
- package/templates/common/skills/ec-config/SKILL.md +68 -0
- package/templates/common/skills/ec-implementing/SKILL.md +11 -0
- package/templates/common/skills/ec-memory/SKILL.md +4 -0
- package/templates/common/skills/ec-reviewing/SKILL.md +6 -0
- package/templates/common/skills/ec-task-management/SKILL.md +6 -31
- package/templates/common/skills/ec-tdd-init/SKILL.md +99 -0
- package/templates/common/skills/ec-verification/SKILL.md +53 -1
- package/templates/common/skills/ec-workflow/SKILL.md +16 -4
- package/templates/main-constraint/AGENTS.md.tpl +13 -3
- package/templates/main-constraint/CLAUDE.md.tpl +13 -3
- package/templates/runtime/tools/easy_coding_java_coverage.py +317 -0
- package/templates/runtime/tools/easy_coding_tdd_readiness.py +306 -0
- package/templates/shared-hooks/easy_coding_state.py +846 -13
- package/templates/shared-hooks/inject-subagent-context.py +5 -0
package/dist/cli.js
CHANGED
|
@@ -103,7 +103,8 @@ async function isDirectory(filePath) {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
// src/utils/config-yaml.ts
|
|
106
|
-
var CONFIG_SCHEMA_VERSION =
|
|
106
|
+
var CONFIG_SCHEMA_VERSION = 5;
|
|
107
|
+
var DEFAULT_TDD_COVERAGE_THRESHOLD = 90;
|
|
107
108
|
var APPROVAL_MODES = ["approve", "guard", "confirm", "auto"];
|
|
108
109
|
var CONFIGURED_WORKFLOW_MODES = ["adaptive", "fast", "standard", "strict"];
|
|
109
110
|
function createDefaultConfig(params) {
|
|
@@ -125,7 +126,9 @@ function createDefaultConfig(params) {
|
|
|
125
126
|
},
|
|
126
127
|
behavior: {
|
|
127
128
|
approval_mode: "guard",
|
|
128
|
-
workflow_mode: "adaptive"
|
|
129
|
+
workflow_mode: "adaptive",
|
|
130
|
+
tdd_enabled: false,
|
|
131
|
+
tdd_coverage_threshold: DEFAULT_TDD_COVERAGE_THRESHOLD
|
|
129
132
|
}
|
|
130
133
|
};
|
|
131
134
|
if (params.supermodule) {
|
|
@@ -179,31 +182,44 @@ function isApprovalMode(value) {
|
|
|
179
182
|
function isConfiguredWorkflowMode(value) {
|
|
180
183
|
return typeof value === "string" && CONFIGURED_WORKFLOW_MODES.includes(value);
|
|
181
184
|
}
|
|
185
|
+
function isTddCoverageThreshold(value) {
|
|
186
|
+
return Number.isInteger(value) && Number(value) >= 1 && Number(value) <= 100;
|
|
187
|
+
}
|
|
182
188
|
function resolveLegacyBehavior(config2) {
|
|
183
189
|
const behavior = config2.behavior ?? {};
|
|
184
190
|
const legacyLite = behavior.confirm_mode === "lite";
|
|
185
191
|
const approvalMode = isApprovalMode(behavior.approval_mode) ? behavior.approval_mode : isApprovalMode(behavior.confirm_mode) ? behavior.confirm_mode : behavior.auto_mode === true ? "auto" : behavior.strict_confirm === true ? "approve" : "guard";
|
|
186
192
|
const workflowMode = isConfiguredWorkflowMode(behavior.workflow_mode) ? behavior.workflow_mode : legacyLite ? "fast" : "adaptive";
|
|
187
|
-
|
|
193
|
+
const supportsTddThreshold = Number(config2.version) >= 4;
|
|
194
|
+
const supportsReadyTdd = Number(config2.version) >= CONFIG_SCHEMA_VERSION;
|
|
195
|
+
const tddEnabled = supportsReadyTdd && behavior.tdd_enabled === true;
|
|
196
|
+
const tddCoverageThreshold = supportsTddThreshold && isTddCoverageThreshold(behavior.tdd_coverage_threshold) ? behavior.tdd_coverage_threshold : DEFAULT_TDD_COVERAGE_THRESHOLD;
|
|
197
|
+
return { approvalMode, workflowMode, tddEnabled, tddCoverageThreshold };
|
|
188
198
|
}
|
|
189
|
-
async function setBehaviorModes(filePath, approvalMode, workflowMode) {
|
|
199
|
+
async function setBehaviorModes(filePath, approvalMode, workflowMode, tddEnabled, tddCoverageThreshold) {
|
|
200
|
+
if (tddCoverageThreshold !== void 0 && !isTddCoverageThreshold(tddCoverageThreshold)) {
|
|
201
|
+
throw new Error("TDD coverage threshold must be an integer from 1 to 100.");
|
|
202
|
+
}
|
|
190
203
|
return updateConfigYaml(filePath, (config2) => {
|
|
191
204
|
const legacyBehavior = config2.behavior ?? {};
|
|
205
|
+
const resolvedBehavior = resolveLegacyBehavior(config2);
|
|
192
206
|
const behavior = Object.fromEntries(
|
|
193
207
|
Object.entries(legacyBehavior).filter(
|
|
194
|
-
([key]) => key !== "strict_confirm" && key !== "auto_mode" && key !== "confirm_mode" && key !== "approval_mode" && key !== "workflow_mode"
|
|
208
|
+
([key]) => key !== "strict_confirm" && key !== "auto_mode" && key !== "confirm_mode" && key !== "approval_mode" && key !== "workflow_mode" && key !== "tdd_enabled" && key !== "tdd_coverage_threshold"
|
|
195
209
|
)
|
|
196
210
|
);
|
|
197
211
|
behavior.approval_mode = approvalMode;
|
|
198
212
|
behavior.workflow_mode = workflowMode;
|
|
213
|
+
behavior.tdd_enabled = tddEnabled ?? resolvedBehavior.tddEnabled;
|
|
214
|
+
behavior.tdd_coverage_threshold = tddCoverageThreshold ?? resolvedBehavior.tddCoverageThreshold;
|
|
199
215
|
config2.behavior = behavior;
|
|
200
216
|
config2.version = CONFIG_SCHEMA_VERSION;
|
|
201
217
|
});
|
|
202
218
|
}
|
|
203
219
|
async function migrateBehaviorConfig(filePath) {
|
|
204
220
|
const config2 = await readConfigYaml(filePath);
|
|
205
|
-
const { approvalMode, workflowMode } = resolveLegacyBehavior(config2);
|
|
206
|
-
return setBehaviorModes(filePath, approvalMode, workflowMode);
|
|
221
|
+
const { approvalMode, workflowMode, tddEnabled, tddCoverageThreshold } = resolveLegacyBehavior(config2);
|
|
222
|
+
return setBehaviorModes(filePath, approvalMode, workflowMode, tddEnabled, tddCoverageThreshold);
|
|
207
223
|
}
|
|
208
224
|
async function ensureProjectId(filePath) {
|
|
209
225
|
let projectId = "";
|
|
@@ -242,6 +258,9 @@ var SPEC_DIR = "spec";
|
|
|
242
258
|
var MAIN_SPEC_DIR = "main";
|
|
243
259
|
var DEV_SPEC_DIR = "dev";
|
|
244
260
|
var TEMPLATES_DIR = "templates";
|
|
261
|
+
var TOOLS_DIR = "tools";
|
|
262
|
+
var TDD_DIR = "tdd";
|
|
263
|
+
var TDD_READINESS_FILE = "readiness.json";
|
|
245
264
|
var SESSIONS_GITIGNORE_ENTRY = ".easy-coding/sessions/";
|
|
246
265
|
var HOOK_BYTECODE_GITIGNORE_ENTRY = "__pycache__/";
|
|
247
266
|
var GENERATED_REGION_START = "<!-- \u2550\u2550\u2550 easy-coding-harness generated (DO NOT EDIT BETWEEN MARKERS) \u2550\u2550\u2550 -->";
|
|
@@ -659,8 +678,17 @@ async function writeRuntimeScaffold(cwd, agents, opts = {}) {
|
|
|
659
678
|
await ensureDir(path6.join(easyCodingDir, SPEC_DIR, DEV_SPEC_DIR));
|
|
660
679
|
await writeMemoryScaffold(easyCodingDir);
|
|
661
680
|
await writeTemplatesScaffold(easyCodingDir);
|
|
681
|
+
await writeToolsScaffold(easyCodingDir);
|
|
662
682
|
return projectId;
|
|
663
683
|
}
|
|
684
|
+
async function writeToolsScaffold(easyCodingDir) {
|
|
685
|
+
const toolsDir = path6.join(easyCodingDir, TOOLS_DIR);
|
|
686
|
+
await ensureDir(toolsDir);
|
|
687
|
+
for (const file of ["easy_coding_java_coverage.py", "easy_coding_tdd_readiness.py"]) {
|
|
688
|
+
const src = getTemplatePath("runtime", "tools", file);
|
|
689
|
+
await writeTextFile(path6.join(toolsDir, file), await readTextFile(src));
|
|
690
|
+
}
|
|
691
|
+
}
|
|
664
692
|
async function writeTemplatesScaffold(easyCodingDir) {
|
|
665
693
|
const templatesDir = path6.join(easyCodingDir, TEMPLATES_DIR);
|
|
666
694
|
await ensureDir(templatesDir);
|
|
@@ -816,6 +844,17 @@ function migrateTaskWorkflowState(task) {
|
|
|
816
844
|
task.workflow_mode_legacy = true;
|
|
817
845
|
changed = true;
|
|
818
846
|
}
|
|
847
|
+
if (isActive && taskType !== "project-init" && typeof task.tdd_enabled !== "boolean") {
|
|
848
|
+
task.tdd_enabled = false;
|
|
849
|
+
task.tdd_coverage_threshold = DEFAULT_TDD_COVERAGE_THRESHOLD;
|
|
850
|
+
task.tdd_confirmed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
851
|
+
task.tdd_confirmed_by = "upgrade-migration";
|
|
852
|
+
changed = true;
|
|
853
|
+
}
|
|
854
|
+
if (task.tdd_enabled === false && "tdd_baselines" in task) {
|
|
855
|
+
task.tdd_baselines = void 0;
|
|
856
|
+
changed = true;
|
|
857
|
+
}
|
|
819
858
|
return changed;
|
|
820
859
|
}
|
|
821
860
|
function migrateSessionBehavior(session) {
|
|
@@ -2515,6 +2554,7 @@ function addRuntimeClearEntries(plan, cwd) {
|
|
|
2515
2554
|
path16.join(cwd, EASY_CODING_DIR, CONFIG_FILE),
|
|
2516
2555
|
path16.join(cwd, EASY_CODING_DIR, SESSIONS_DIR),
|
|
2517
2556
|
path16.join(cwd, EASY_CODING_DIR, TEMPLATES_DIR),
|
|
2557
|
+
path16.join(cwd, EASY_CODING_DIR, TOOLS_DIR),
|
|
2518
2558
|
path16.join(cwd, EASY_CODING_DIR, INSTALL_MANIFEST_FILE)
|
|
2519
2559
|
])
|
|
2520
2560
|
];
|
|
@@ -2737,8 +2777,8 @@ function renderTargetPlans(targetPlans) {
|
|
|
2737
2777
|
}
|
|
2738
2778
|
|
|
2739
2779
|
// src/commands/config.ts
|
|
2740
|
-
import
|
|
2741
|
-
import { cancel as cancel4, confirm as confirm3, outro as outro3, select } from "@clack/prompts";
|
|
2780
|
+
import path19 from "path";
|
|
2781
|
+
import { cancel as cancel4, confirm as confirm3, outro as outro3, select, text } from "@clack/prompts";
|
|
2742
2782
|
import chalk4 from "chalk";
|
|
2743
2783
|
|
|
2744
2784
|
// src/utils/compare-versions.ts
|
|
@@ -2816,10 +2856,169 @@ async function checkForUpgrade(cwd) {
|
|
|
2816
2856
|
}
|
|
2817
2857
|
}
|
|
2818
2858
|
|
|
2859
|
+
// src/utils/tdd-readiness.ts
|
|
2860
|
+
import { createHash as createHash2 } from "crypto";
|
|
2861
|
+
import { readFile as readFile4, readdir as readdir5, realpath as realpath2 } from "fs/promises";
|
|
2862
|
+
import path18 from "path";
|
|
2863
|
+
var TDD_READINESS_SCHEMA = "easy-coding/tdd-readiness-v1";
|
|
2864
|
+
var TDD_READINESS_SCOPE = "changed-production-lines";
|
|
2865
|
+
var TDD_BASE_VARIABLE = "EASY_CODING_TDD_BASE_SHA";
|
|
2866
|
+
var TDD_THRESHOLD_VARIABLE = "EASY_CODING_TDD_THRESHOLD";
|
|
2867
|
+
var COVERAGE_TOOL_PATH = ".easy-coding/tools/easy_coding_java_coverage.py";
|
|
2868
|
+
var JAVA_BUILD_FILE_NAMES = /* @__PURE__ */ new Set(["pom.xml", "build.gradle", "build.gradle.kts"]);
|
|
2869
|
+
var GITLAB_CI_ENTRY_FILES = /* @__PURE__ */ new Set([".gitlab-ci.yml", ".gitlab-ci.yaml"]);
|
|
2870
|
+
function readinessPath(root) {
|
|
2871
|
+
return path18.join(root, EASY_CODING_DIR, TDD_DIR, TDD_READINESS_FILE);
|
|
2872
|
+
}
|
|
2873
|
+
function parseFileRecords(value, field, reasons) {
|
|
2874
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
2875
|
+
reasons.push(`${field} must contain at least one file`);
|
|
2876
|
+
return [];
|
|
2877
|
+
}
|
|
2878
|
+
const records = [];
|
|
2879
|
+
for (const item of value) {
|
|
2880
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) {
|
|
2881
|
+
reasons.push(`${field} contains an invalid record`);
|
|
2882
|
+
continue;
|
|
2883
|
+
}
|
|
2884
|
+
const record = item;
|
|
2885
|
+
if (typeof record.path !== "string" || !record.path.trim() || path18.isAbsolute(record.path) || typeof record.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(record.sha256)) {
|
|
2886
|
+
reasons.push(`${field} contains an invalid path or SHA-256`);
|
|
2887
|
+
continue;
|
|
2888
|
+
}
|
|
2889
|
+
records.push({ path: record.path, sha256: record.sha256 });
|
|
2890
|
+
}
|
|
2891
|
+
return records;
|
|
2892
|
+
}
|
|
2893
|
+
function usesRequiredGateVariables(command) {
|
|
2894
|
+
const normalized = command.replaceAll(`\${${TDD_BASE_VARIABLE}}`, `$${TDD_BASE_VARIABLE}`).replaceAll(`\${${TDD_THRESHOLD_VARIABLE}}`, `$${TDD_THRESHOLD_VARIABLE}`);
|
|
2895
|
+
return new RegExp(`--base\\s+['"]?\\$${TDD_BASE_VARIABLE}(?:['"]|\\s|$)`).test(normalized) && new RegExp(`--threshold\\s+['"]?\\$${TDD_THRESHOLD_VARIABLE}(?:['"]|\\s|$)`).test(normalized);
|
|
2896
|
+
}
|
|
2897
|
+
function isSafeReportPattern(value) {
|
|
2898
|
+
const normalized = value.replaceAll("\\", "/");
|
|
2899
|
+
return !path18.isAbsolute(value) && !normalized.split("/").includes("..");
|
|
2900
|
+
}
|
|
2901
|
+
function activeCiContent(contents) {
|
|
2902
|
+
return contents.join("\n").split("\n").map((line) => line.replace(/^\s*#.*$/, "").replace(/\s+#.*$/, "")).join("\n");
|
|
2903
|
+
}
|
|
2904
|
+
async function validateFiles(root, records, reasons) {
|
|
2905
|
+
const contents = [];
|
|
2906
|
+
const resolvedRoot = await realpath2(root);
|
|
2907
|
+
for (const record of records) {
|
|
2908
|
+
const absolute = path18.resolve(root, record.path);
|
|
2909
|
+
try {
|
|
2910
|
+
const resolved = await realpath2(absolute);
|
|
2911
|
+
if (resolved !== resolvedRoot && !resolved.startsWith(`${resolvedRoot}${path18.sep}`)) {
|
|
2912
|
+
reasons.push(`readiness file escapes project root: ${record.path}`);
|
|
2913
|
+
continue;
|
|
2914
|
+
}
|
|
2915
|
+
const content = await readFile4(resolved);
|
|
2916
|
+
const digest = createHash2("sha256").update(content).digest("hex");
|
|
2917
|
+
if (digest !== record.sha256) reasons.push(`readiness file changed: ${record.path}`);
|
|
2918
|
+
contents.push(content.toString("utf8"));
|
|
2919
|
+
} catch {
|
|
2920
|
+
reasons.push(`readiness file is missing or unreadable: ${record.path}`);
|
|
2921
|
+
}
|
|
2922
|
+
}
|
|
2923
|
+
return contents;
|
|
2924
|
+
}
|
|
2925
|
+
async function inspectTddReadiness(root) {
|
|
2926
|
+
const manifestPath2 = readinessPath(root);
|
|
2927
|
+
if (!await pathExists(manifestPath2)) {
|
|
2928
|
+
return { status: "needs_init", reasons: ["TDD readiness receipt is missing"], manifestPath: manifestPath2 };
|
|
2929
|
+
}
|
|
2930
|
+
let manifest;
|
|
2931
|
+
try {
|
|
2932
|
+
manifest = JSON.parse(await readFile4(manifestPath2, "utf8"));
|
|
2933
|
+
} catch {
|
|
2934
|
+
return { status: "needs_init", reasons: ["TDD readiness receipt is invalid"], manifestPath: manifestPath2 };
|
|
2935
|
+
}
|
|
2936
|
+
const reasons = [];
|
|
2937
|
+
if (manifest.schema !== TDD_READINESS_SCHEMA) reasons.push("unsupported readiness schema");
|
|
2938
|
+
if (manifest.provider !== "gitlab") reasons.push("readiness provider must be gitlab");
|
|
2939
|
+
if (manifest.coverage_scope !== TDD_READINESS_SCOPE) {
|
|
2940
|
+
reasons.push("coverage scope must be changed-production-lines");
|
|
2941
|
+
}
|
|
2942
|
+
if (manifest.historical_coverage_required !== false) {
|
|
2943
|
+
reasons.push("historical coverage must remain disabled");
|
|
2944
|
+
}
|
|
2945
|
+
if (!Array.isArray(manifest.coverage_report_patterns) || manifest.coverage_report_patterns.length === 0 || manifest.coverage_report_patterns.some(
|
|
2946
|
+
(item) => typeof item !== "string" || !item.trim() || !isSafeReportPattern(item)
|
|
2947
|
+
)) {
|
|
2948
|
+
reasons.push("coverage_report_patterns must contain safe project-relative report patterns");
|
|
2949
|
+
}
|
|
2950
|
+
if (typeof manifest.changed_line_gate_command !== "string" || !manifest.changed_line_gate_command.includes(COVERAGE_TOOL_PATH)) {
|
|
2951
|
+
reasons.push("changed-line coverage gate command is missing");
|
|
2952
|
+
} else if (!usesRequiredGateVariables(manifest.changed_line_gate_command)) {
|
|
2953
|
+
reasons.push("changed-line coverage gate must use the task baseline and threshold variables");
|
|
2954
|
+
}
|
|
2955
|
+
const buildFiles = parseFileRecords(manifest.build_files, "build_files", reasons);
|
|
2956
|
+
const ciFiles = parseFileRecords(manifest.ci_files, "ci_files", reasons);
|
|
2957
|
+
const toolFiles = parseFileRecords(manifest.tool_files, "tool_files", reasons);
|
|
2958
|
+
if (!buildFiles.some((record) => JAVA_BUILD_FILE_NAMES.has(path18.basename(record.path)))) {
|
|
2959
|
+
reasons.push("build_files must include a Maven or Gradle Java build file");
|
|
2960
|
+
}
|
|
2961
|
+
if (!ciFiles.some((record) => GITLAB_CI_ENTRY_FILES.has(record.path.replaceAll("\\", "/")))) {
|
|
2962
|
+
reasons.push("ci_files must include the project-root GitLab CI entry file");
|
|
2963
|
+
}
|
|
2964
|
+
if (!toolFiles.some((record) => record.path.replaceAll("\\", "/") === COVERAGE_TOOL_PATH)) {
|
|
2965
|
+
reasons.push(`tool_files must include ${COVERAGE_TOOL_PATH}`);
|
|
2966
|
+
}
|
|
2967
|
+
const buildContents = await validateFiles(root, buildFiles, reasons);
|
|
2968
|
+
const ciContents = await validateFiles(root, ciFiles, reasons);
|
|
2969
|
+
await validateFiles(root, toolFiles, reasons);
|
|
2970
|
+
if (!buildContents.some((content) => /jacoco/i.test(content))) {
|
|
2971
|
+
reasons.push("build files do not configure JaCoCo");
|
|
2972
|
+
}
|
|
2973
|
+
const combinedCi = activeCiContent(ciContents);
|
|
2974
|
+
for (const marker of [
|
|
2975
|
+
"jacoco",
|
|
2976
|
+
"artifacts",
|
|
2977
|
+
COVERAGE_TOOL_PATH,
|
|
2978
|
+
TDD_BASE_VARIABLE,
|
|
2979
|
+
TDD_THRESHOLD_VARIABLE
|
|
2980
|
+
]) {
|
|
2981
|
+
if (!combinedCi.toLowerCase().includes(marker.toLowerCase())) {
|
|
2982
|
+
reasons.push(`CI files do not contain required marker: ${marker}`);
|
|
2983
|
+
}
|
|
2984
|
+
}
|
|
2985
|
+
if (!usesRequiredGateVariables(combinedCi)) {
|
|
2986
|
+
reasons.push("CI changed-line gate must use the task baseline and threshold variables");
|
|
2987
|
+
}
|
|
2988
|
+
if (!/(?:^|\n)\s*stage\s*:\s*['"]?test['"]?\s*(?:#.*)?(?:\n|$)/i.test(combinedCi)) {
|
|
2989
|
+
reasons.push("CI files do not declare a TEST-stage job");
|
|
2990
|
+
}
|
|
2991
|
+
return {
|
|
2992
|
+
status: reasons.length === 0 ? "ready" : "needs_init",
|
|
2993
|
+
reasons: [...new Set(reasons)],
|
|
2994
|
+
manifestPath: manifestPath2
|
|
2995
|
+
};
|
|
2996
|
+
}
|
|
2997
|
+
async function disableUnreadySessionTddOverrides(root) {
|
|
2998
|
+
if ((await inspectTddReadiness(root)).status === "ready") return 0;
|
|
2999
|
+
const sessionsDir = path18.join(root, EASY_CODING_DIR, SESSIONS_DIR);
|
|
3000
|
+
if (!await pathExists(sessionsDir)) return 0;
|
|
3001
|
+
let updated = 0;
|
|
3002
|
+
for (const entry of await readdir5(sessionsDir, { withFileTypes: true })) {
|
|
3003
|
+
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
3004
|
+
const filePath = path18.join(sessionsDir, entry.name);
|
|
3005
|
+
try {
|
|
3006
|
+
const session = JSON.parse(await readFile4(filePath, "utf8"));
|
|
3007
|
+
if (session.tdd_enabled !== true) continue;
|
|
3008
|
+
session.tdd_enabled = false;
|
|
3009
|
+
await writeTextFile(filePath, `${JSON.stringify(session, null, 2)}
|
|
3010
|
+
`);
|
|
3011
|
+
updated += 1;
|
|
3012
|
+
} catch {
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
return updated;
|
|
3016
|
+
}
|
|
3017
|
+
|
|
2819
3018
|
// src/commands/config.ts
|
|
2820
3019
|
async function config() {
|
|
2821
3020
|
renderBanner();
|
|
2822
|
-
const configPath2 =
|
|
3021
|
+
const configPath2 = path19.join(process.cwd(), EASY_CODING_DIR, CONFIG_FILE);
|
|
2823
3022
|
if (!await pathExists(configPath2)) {
|
|
2824
3023
|
throw new Error("No easy-coding harness found in this project.");
|
|
2825
3024
|
}
|
|
@@ -2901,33 +3100,83 @@ async function config() {
|
|
|
2901
3100
|
cancel4("Configuration cancelled.");
|
|
2902
3101
|
return;
|
|
2903
3102
|
}
|
|
3103
|
+
const tddEnabled = await select({
|
|
3104
|
+
message: `Enable Java TDD for this project (current: ${current.tddEnabled ? "enabled" : "disabled"})`,
|
|
3105
|
+
initialValue: current.tddEnabled,
|
|
3106
|
+
options: [
|
|
3107
|
+
{ value: false, label: "disabled \u2014 preserve current test depth (default)" },
|
|
3108
|
+
{ value: true, label: "enabled \u2014 require TDD evidence and changed-line coverage" }
|
|
3109
|
+
]
|
|
3110
|
+
});
|
|
3111
|
+
if (typeof tddEnabled === "symbol") {
|
|
3112
|
+
cancel4("Configuration cancelled.");
|
|
3113
|
+
return;
|
|
3114
|
+
}
|
|
3115
|
+
if (tddEnabled) {
|
|
3116
|
+
const readiness = await inspectTddReadiness(process.cwd());
|
|
3117
|
+
if (readiness.status !== "ready") {
|
|
3118
|
+
cancel4(
|
|
3119
|
+
`TDD was not enabled. Run ec-tdd-init first: ${readiness.reasons.join("; ")}. No project modes were changed.`
|
|
3120
|
+
);
|
|
3121
|
+
return;
|
|
3122
|
+
}
|
|
3123
|
+
}
|
|
3124
|
+
let tddCoverageThreshold = current.tddCoverageThreshold;
|
|
3125
|
+
if (tddEnabled) {
|
|
3126
|
+
const thresholdInput = await text({
|
|
3127
|
+
message: "Minimum changed-production-line coverage percentage",
|
|
3128
|
+
initialValue: String(current.tddCoverageThreshold),
|
|
3129
|
+
validate(value) {
|
|
3130
|
+
const parsed = Number(value);
|
|
3131
|
+
return isTddCoverageThreshold(parsed) ? void 0 : "Enter an integer from 1 to 100.";
|
|
3132
|
+
}
|
|
3133
|
+
});
|
|
3134
|
+
if (typeof thresholdInput === "symbol") {
|
|
3135
|
+
cancel4("Configuration cancelled.");
|
|
3136
|
+
return;
|
|
3137
|
+
}
|
|
3138
|
+
tddCoverageThreshold = Number(thresholdInput);
|
|
3139
|
+
}
|
|
2904
3140
|
const shouldSave = await confirm3({
|
|
2905
|
-
message: `Set
|
|
3141
|
+
message: `Set approval=${approvalMode}, workflow=${workflowMode}, TDD=${tddEnabled ? `enabled (${tddCoverageThreshold}%)` : "disabled"}?`,
|
|
2906
3142
|
initialValue: true
|
|
2907
3143
|
});
|
|
2908
3144
|
if (typeof shouldSave === "symbol" || !shouldSave) {
|
|
2909
3145
|
cancel4("Configuration cancelled.");
|
|
2910
3146
|
return;
|
|
2911
3147
|
}
|
|
2912
|
-
|
|
2913
|
-
|
|
3148
|
+
if (tddEnabled) {
|
|
3149
|
+
const readiness = await inspectTddReadiness(process.cwd());
|
|
3150
|
+
if (readiness.status !== "ready") {
|
|
3151
|
+
cancel4(
|
|
3152
|
+
`TDD was not enabled because readiness changed before save: ${readiness.reasons.join("; ")}. No project modes were changed.`
|
|
3153
|
+
);
|
|
3154
|
+
return;
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
await setBehaviorModes(configPath2, approvalMode, workflowMode, tddEnabled, tddCoverageThreshold);
|
|
3158
|
+
outro3(
|
|
3159
|
+
chalk4.green(
|
|
3160
|
+
`Project modes updated: approval=${approvalMode}, workflow=${workflowMode}, TDD=${tddEnabled ? `${tddCoverageThreshold}%` : "off"}.`
|
|
3161
|
+
)
|
|
3162
|
+
);
|
|
2914
3163
|
}
|
|
2915
3164
|
|
|
2916
3165
|
// src/commands/init.ts
|
|
2917
|
-
import
|
|
3166
|
+
import path21 from "path";
|
|
2918
3167
|
import { note, outro as outro4 } from "@clack/prompts";
|
|
2919
3168
|
import chalk5 from "chalk";
|
|
2920
3169
|
|
|
2921
3170
|
// src/utils/install-state.ts
|
|
2922
|
-
import { readdir as
|
|
2923
|
-
import
|
|
3171
|
+
import { readdir as readdir6 } from "fs/promises";
|
|
3172
|
+
import path20 from "path";
|
|
2924
3173
|
var LEGACY_ROOT_FILES = ["SOUL.md", "RULES.md", "ABSTRACT.md"];
|
|
2925
3174
|
async function detectEasyCodingInstallState(cwd) {
|
|
2926
|
-
const easyCodingDir =
|
|
3175
|
+
const easyCodingDir = path20.join(cwd, EASY_CODING_DIR);
|
|
2927
3176
|
if (!await pathExists(easyCodingDir)) {
|
|
2928
3177
|
return { kind: "fresh", easyCodingDir };
|
|
2929
3178
|
}
|
|
2930
|
-
const configPath2 =
|
|
3179
|
+
const configPath2 = path20.join(easyCodingDir, CONFIG_FILE);
|
|
2931
3180
|
if (await pathExists(configPath2)) {
|
|
2932
3181
|
return { kind: "installed", easyCodingDir, configPath: configPath2 };
|
|
2933
3182
|
}
|
|
@@ -2945,17 +3194,17 @@ async function detectEasyCodingInstallState(cwd) {
|
|
|
2945
3194
|
async function detectLegacyAssets(easyCodingDir) {
|
|
2946
3195
|
const assets = [];
|
|
2947
3196
|
for (const file of LEGACY_ROOT_FILES) {
|
|
2948
|
-
if (await pathExists(
|
|
3197
|
+
if (await pathExists(path20.join(easyCodingDir, file))) {
|
|
2949
3198
|
assets.push(relativeEasyCodingPath(file));
|
|
2950
3199
|
}
|
|
2951
3200
|
}
|
|
2952
|
-
if (await pathExists(
|
|
3201
|
+
if (await pathExists(path20.join(easyCodingDir, "memory", "long", "MEMORY.md"))) {
|
|
2953
3202
|
assets.push(relativeEasyCodingPath("memory", "long", "MEMORY.md"));
|
|
2954
3203
|
}
|
|
2955
|
-
const shortMemoryFiles = await listMarkdownFiles(
|
|
3204
|
+
const shortMemoryFiles = await listMarkdownFiles(path20.join(easyCodingDir, "memory", "short"));
|
|
2956
3205
|
assets.push(...shortMemoryFiles.map((file) => relativeEasyCodingPath("memory", "short", file)));
|
|
2957
3206
|
for (const dir of ["spec", "prototype"]) {
|
|
2958
|
-
if (await hasAnyDirectoryEntry(
|
|
3207
|
+
if (await hasAnyDirectoryEntry(path20.join(easyCodingDir, dir))) {
|
|
2959
3208
|
assets.push(relativeEasyCodingPath(dir));
|
|
2960
3209
|
}
|
|
2961
3210
|
}
|
|
@@ -2965,23 +3214,23 @@ async function listMarkdownFiles(dir) {
|
|
|
2965
3214
|
if (!await isDirectory(dir)) {
|
|
2966
3215
|
return [];
|
|
2967
3216
|
}
|
|
2968
|
-
const entries = await
|
|
3217
|
+
const entries = await readdir6(dir, { withFileTypes: true });
|
|
2969
3218
|
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => entry.name).sort();
|
|
2970
3219
|
}
|
|
2971
3220
|
async function hasAnyDirectoryEntry(dir) {
|
|
2972
3221
|
if (!await isDirectory(dir)) {
|
|
2973
3222
|
return false;
|
|
2974
3223
|
}
|
|
2975
|
-
return (await
|
|
3224
|
+
return (await readdir6(dir)).length > 0;
|
|
2976
3225
|
}
|
|
2977
3226
|
function relativeEasyCodingPath(...segments) {
|
|
2978
|
-
return
|
|
3227
|
+
return path20.posix.join(EASY_CODING_DIR, ...segments);
|
|
2979
3228
|
}
|
|
2980
3229
|
function relativeConfigPath() {
|
|
2981
|
-
return
|
|
3230
|
+
return path20.posix.join(EASY_CODING_DIR, CONFIG_FILE);
|
|
2982
3231
|
}
|
|
2983
3232
|
function relativeProjectInitTaskPath() {
|
|
2984
|
-
return
|
|
3233
|
+
return path20.posix.join(EASY_CODING_DIR, TASKS_DIR, PROJECT_INIT_TASK_ID, "task.json");
|
|
2985
3234
|
}
|
|
2986
3235
|
|
|
2987
3236
|
// src/commands/init.ts
|
|
@@ -3062,7 +3311,7 @@ async function supermoduleTargets(cwd, opts, submodules) {
|
|
|
3062
3311
|
if (!targetSubmodulePaths.has(entry.path)) {
|
|
3063
3312
|
continue;
|
|
3064
3313
|
}
|
|
3065
|
-
const dir =
|
|
3314
|
+
const dir = path21.join(cwd, entry.path);
|
|
3066
3315
|
targets.push(
|
|
3067
3316
|
await targetFromState(dir, entry.path, "submodule-child", {
|
|
3068
3317
|
parent: toPosixRelative2(dir, cwd)
|
|
@@ -3109,24 +3358,24 @@ function contextFromState(role, installState) {
|
|
|
3109
3358
|
};
|
|
3110
3359
|
}
|
|
3111
3360
|
function toPosixRelative2(from, to) {
|
|
3112
|
-
const relative =
|
|
3113
|
-
return relative ? relative.split(
|
|
3361
|
+
const relative = path21.relative(from, to);
|
|
3362
|
+
return relative ? relative.split(path21.sep).join("/") : ".";
|
|
3114
3363
|
}
|
|
3115
3364
|
async function resolveInitPlatforms(cwd, opts, parentInstalled) {
|
|
3116
3365
|
if (opts.agent || !parentInstalled) {
|
|
3117
3366
|
return resolvePlatforms(opts, ["claude-code"]);
|
|
3118
3367
|
}
|
|
3119
|
-
const config2 = await readConfigYaml(
|
|
3368
|
+
const config2 = await readConfigYaml(path21.join(cwd, EASY_CODING_DIR, CONFIG_FILE));
|
|
3120
3369
|
if (Array.isArray(config2.agents) && config2.agents.length > 0) {
|
|
3121
3370
|
return config2.agents;
|
|
3122
3371
|
}
|
|
3123
3372
|
return resolvePlatforms(opts, ["claude-code"]);
|
|
3124
3373
|
}
|
|
3125
3374
|
async function refreshParentTopologyIfNeeded(cwd, parentTarget2, installPlatforms) {
|
|
3126
|
-
if (!await pathExists(
|
|
3375
|
+
if (!await pathExists(path21.join(cwd, EASY_CODING_DIR, CONFIG_FILE))) {
|
|
3127
3376
|
return;
|
|
3128
3377
|
}
|
|
3129
|
-
const config2 = parentTarget2.installed ? await readConfigYaml(
|
|
3378
|
+
const config2 = parentTarget2.installed ? await readConfigYaml(path21.join(cwd, EASY_CODING_DIR, CONFIG_FILE)) : { agents: installPlatforms };
|
|
3130
3379
|
const platforms = Array.isArray(config2.agents) && config2.agents.length > 0 ? config2.agents : installPlatforms;
|
|
3131
3380
|
await refreshSupermoduleParent(cwd, platforms, parentTarget2.context.submodulePaths ?? []);
|
|
3132
3381
|
}
|
|
@@ -3135,7 +3384,7 @@ async function refreshInstalledChildTopologies(targets) {
|
|
|
3135
3384
|
if (!target.installed || target.context.role !== "submodule-child") {
|
|
3136
3385
|
continue;
|
|
3137
3386
|
}
|
|
3138
|
-
const configPath2 =
|
|
3387
|
+
const configPath2 = path21.join(target.dir, EASY_CODING_DIR, CONFIG_FILE);
|
|
3139
3388
|
if (!await pathExists(configPath2)) {
|
|
3140
3389
|
continue;
|
|
3141
3390
|
}
|
|
@@ -3144,12 +3393,12 @@ async function refreshInstalledChildTopologies(targets) {
|
|
|
3144
3393
|
}
|
|
3145
3394
|
|
|
3146
3395
|
// src/commands/status.ts
|
|
3147
|
-
import
|
|
3396
|
+
import path23 from "path";
|
|
3148
3397
|
import chalk6 from "chalk";
|
|
3149
3398
|
|
|
3150
3399
|
// src/utils/session.ts
|
|
3151
|
-
import { readdir as
|
|
3152
|
-
import
|
|
3400
|
+
import { readdir as readdir7, unlink } from "fs/promises";
|
|
3401
|
+
import path22 from "path";
|
|
3153
3402
|
var STALE_THRESHOLD_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
3154
3403
|
function parseSessionFile(content) {
|
|
3155
3404
|
try {
|
|
@@ -3159,7 +3408,7 @@ function parseSessionFile(content) {
|
|
|
3159
3408
|
}
|
|
3160
3409
|
}
|
|
3161
3410
|
function getSessionDir(cwd) {
|
|
3162
|
-
return
|
|
3411
|
+
return path22.join(cwd, EASY_CODING_DIR, SESSIONS_DIR);
|
|
3163
3412
|
}
|
|
3164
3413
|
async function listSessionFiles(cwd) {
|
|
3165
3414
|
const dir = getSessionDir(cwd);
|
|
@@ -3167,11 +3416,11 @@ async function listSessionFiles(cwd) {
|
|
|
3167
3416
|
return [];
|
|
3168
3417
|
}
|
|
3169
3418
|
const entries = [];
|
|
3170
|
-
for (const name of await
|
|
3419
|
+
for (const name of await readdir7(dir)) {
|
|
3171
3420
|
if (!name.endsWith(".json")) {
|
|
3172
3421
|
continue;
|
|
3173
3422
|
}
|
|
3174
|
-
const filePath =
|
|
3423
|
+
const filePath = path22.join(dir, name);
|
|
3175
3424
|
const content = await readTextIfExists(filePath);
|
|
3176
3425
|
if (!content) {
|
|
3177
3426
|
continue;
|
|
@@ -3193,7 +3442,7 @@ async function listSessionFiles(cwd) {
|
|
|
3193
3442
|
async function status() {
|
|
3194
3443
|
renderBanner();
|
|
3195
3444
|
const cwd = process.cwd();
|
|
3196
|
-
const configPath2 =
|
|
3445
|
+
const configPath2 = path23.join(cwd, EASY_CODING_DIR, CONFIG_FILE);
|
|
3197
3446
|
if (!await pathExists(configPath2)) {
|
|
3198
3447
|
throw new Error("No easy-coding harness found in this project.");
|
|
3199
3448
|
}
|
|
@@ -3203,6 +3452,7 @@ async function status() {
|
|
|
3203
3452
|
const activeTasks = tasks.filter((item) => isActiveTask(item.task));
|
|
3204
3453
|
const sessions = await listSessionFiles(cwd);
|
|
3205
3454
|
const versionRelation = compareVersions(config2.harness_version, VERSION);
|
|
3455
|
+
const tddReadiness = await inspectTddReadiness(cwd);
|
|
3206
3456
|
console.log(chalk6.bold("Harness"));
|
|
3207
3457
|
console.log(` version: ${config2.harness_version}`);
|
|
3208
3458
|
console.log(` cli: ${VERSION}`);
|
|
@@ -3218,14 +3468,28 @@ async function status() {
|
|
|
3218
3468
|
const migratedBehavior = resolveLegacyBehavior(config2);
|
|
3219
3469
|
const projectApprovalMode = isApprovalMode(config2.behavior?.approval_mode) ? config2.behavior.approval_mode : migratedBehavior.approvalMode;
|
|
3220
3470
|
const projectWorkflowMode = isConfiguredWorkflowMode(config2.behavior?.workflow_mode) ? config2.behavior.workflow_mode : migratedBehavior.workflowMode;
|
|
3471
|
+
const projectTddEnabled = migratedBehavior.tddEnabled;
|
|
3472
|
+
const projectTddCoverageThreshold = migratedBehavior.tddCoverageThreshold;
|
|
3221
3473
|
console.log(` approval_mode: ${projectApprovalMode}`);
|
|
3222
3474
|
console.log(` workflow_mode: ${projectWorkflowMode}`);
|
|
3475
|
+
console.log(` tdd_enabled: ${projectTddEnabled}`);
|
|
3476
|
+
console.log(` tdd_coverage_threshold: ${projectTddCoverageThreshold}`);
|
|
3477
|
+
console.log(` tdd_readiness: ${tddReadiness.status}`);
|
|
3478
|
+
if (tddReadiness.status !== "ready") {
|
|
3479
|
+
console.log(` tdd_readiness_reasons: ${tddReadiness.reasons.join("; ")}`);
|
|
3480
|
+
}
|
|
3223
3481
|
console.log("");
|
|
3224
3482
|
console.log(chalk6.bold("Sessions"));
|
|
3225
3483
|
console.log(` project_approval_mode: ${projectApprovalMode}`);
|
|
3226
3484
|
console.log(` project_workflow_mode: ${projectWorkflowMode}`);
|
|
3485
|
+
console.log(` project_tdd_enabled: ${projectTddEnabled}`);
|
|
3486
|
+
console.log(` project_tdd_coverage_threshold: ${projectTddCoverageThreshold}`);
|
|
3227
3487
|
console.log(` effective_approval_mode: ${projectApprovalMode} (without a session override)`);
|
|
3228
3488
|
console.log(` configured_workflow_mode: ${projectWorkflowMode} (without a session override)`);
|
|
3489
|
+
console.log(` effective_tdd_enabled: ${projectTddEnabled} (without a session override)`);
|
|
3490
|
+
console.log(
|
|
3491
|
+
` effective_tdd_coverage_threshold: ${projectTddCoverageThreshold} (without a session override)`
|
|
3492
|
+
);
|
|
3229
3493
|
if (sessions.length === 0) {
|
|
3230
3494
|
console.log(" no session files");
|
|
3231
3495
|
}
|
|
@@ -3236,13 +3500,21 @@ async function status() {
|
|
|
3236
3500
|
);
|
|
3237
3501
|
const sessionApprovalMode = session.approval_mode ?? (legacySessionMode === "lite" ? "guard" : legacySessionMode);
|
|
3238
3502
|
const sessionWorkflowMode = session.workflow_mode ?? (legacySessionMode === "lite" ? "fast" : hasLegacySessionMode ? "adaptive" : void 0);
|
|
3503
|
+
const sessionTddEnabled = session.tdd_enabled;
|
|
3504
|
+
const sessionTddCoverageThreshold = session.tdd_coverage_threshold;
|
|
3239
3505
|
console.log(` - ${key}`);
|
|
3240
3506
|
console.log(` agent: ${session.agent ?? "legacy/unknown"}`);
|
|
3241
3507
|
console.log(` source: ${session.session_source ?? "legacy"}`);
|
|
3242
3508
|
console.log(` approval_mode: ${sessionApprovalMode ?? "project default"}`);
|
|
3243
3509
|
console.log(` workflow_mode: ${sessionWorkflowMode ?? "project default"}`);
|
|
3510
|
+
console.log(` tdd_enabled: ${sessionTddEnabled ?? "project default"}`);
|
|
3511
|
+
console.log(` tdd_coverage_threshold: ${sessionTddCoverageThreshold ?? "project default"}`);
|
|
3244
3512
|
console.log(` effective_approval_mode: ${sessionApprovalMode ?? projectApprovalMode}`);
|
|
3245
3513
|
console.log(` configured_workflow_mode: ${sessionWorkflowMode ?? projectWorkflowMode}`);
|
|
3514
|
+
console.log(` effective_tdd_enabled: ${sessionTddEnabled ?? projectTddEnabled}`);
|
|
3515
|
+
console.log(
|
|
3516
|
+
` effective_tdd_coverage_threshold: ${sessionTddCoverageThreshold ?? projectTddCoverageThreshold}`
|
|
3517
|
+
);
|
|
3246
3518
|
console.log(
|
|
3247
3519
|
` harness: ${session.harness_disabled ? "disabled for this session" : "enabled"}`
|
|
3248
3520
|
);
|
|
@@ -3258,6 +3530,10 @@ async function status() {
|
|
|
3258
3530
|
console.log(
|
|
3259
3531
|
` task_workflow_mode: ${task.workflow_mode ?? task.workflow_mode_proposal?.selected_mode ?? "not resolved"}`
|
|
3260
3532
|
);
|
|
3533
|
+
console.log(` task_tdd_enabled: ${task.tdd_enabled ?? "not frozen"}`);
|
|
3534
|
+
console.log(
|
|
3535
|
+
` task_tdd_coverage_threshold: ${task.tdd_coverage_threshold ?? "not frozen"}`
|
|
3536
|
+
);
|
|
3261
3537
|
console.log(` last_agent: ${task.last_agent}`);
|
|
3262
3538
|
} else {
|
|
3263
3539
|
console.log(` current_task: ${session.current_task} (task.json missing)`);
|
|
@@ -3309,7 +3585,7 @@ async function update(opts) {
|
|
|
3309
3585
|
}
|
|
3310
3586
|
|
|
3311
3587
|
// src/commands/upgrade.ts
|
|
3312
|
-
import
|
|
3588
|
+
import path24 from "path";
|
|
3313
3589
|
import { cancel as cancel6, confirm as confirm5, outro as outro6 } from "@clack/prompts";
|
|
3314
3590
|
import chalk8 from "chalk";
|
|
3315
3591
|
var EXPECTED_HOOK_REGISTRATION_SCRIPTS = {
|
|
@@ -3371,8 +3647,8 @@ async function upgrade(opts) {
|
|
|
3371
3647
|
].filter(Boolean) : [],
|
|
3372
3648
|
"Will overwrite managed skills, hooks, agents, templates, and generated main-constraint regions.",
|
|
3373
3649
|
"Will update project-init task to recommend ec-init re-run for version adaptation.",
|
|
3374
|
-
"Will migrate
|
|
3375
|
-
"Will migrate legacy workflow
|
|
3650
|
+
"Will migrate behavior config to schema 5 and disable unready project/session TDD settings.",
|
|
3651
|
+
"Will migrate legacy workflow/TDD task metadata; memory content, spec, and project knowledge files remain untouched."
|
|
3376
3652
|
].join("\n");
|
|
3377
3653
|
if (opts.dryRun) {
|
|
3378
3654
|
console.log(summary);
|
|
@@ -3389,6 +3665,7 @@ async function upgrade(opts) {
|
|
|
3389
3665
|
}
|
|
3390
3666
|
}
|
|
3391
3667
|
for (const { target, config: config2 } of pending) {
|
|
3668
|
+
const beta1ProjectTddRequested = Number(config2.version) === 4 && config2.behavior?.tdd_enabled === true;
|
|
3392
3669
|
const projectId = await writeRuntimeScaffold(target.dir, config2.agents, {
|
|
3393
3670
|
supermodule: target.supermodule
|
|
3394
3671
|
});
|
|
@@ -3405,6 +3682,14 @@ async function upgrade(opts) {
|
|
|
3405
3682
|
await ensureHookBytecodeIgnored(target.dir);
|
|
3406
3683
|
await migrateLegacyWorkflowState(target.dir);
|
|
3407
3684
|
await migrateBehaviorConfig(target.configPath);
|
|
3685
|
+
const disabledSessionTddOverrides = await disableUnreadySessionTddOverrides(target.dir);
|
|
3686
|
+
if (beta1ProjectTddRequested || disabledSessionTddOverrides > 0) {
|
|
3687
|
+
console.log(
|
|
3688
|
+
chalk8.yellow(
|
|
3689
|
+
`${target.label}: TDD remains off until ec-tdd-init succeeds (project=${beta1ProjectTddRequested ? "disabled" : "unchanged"}, sessions_disabled=${disabledSessionTddOverrides}).`
|
|
3690
|
+
)
|
|
3691
|
+
);
|
|
3692
|
+
}
|
|
3408
3693
|
await updateHarnessVersion(target.configPath, VERSION);
|
|
3409
3694
|
await updateSupermoduleConfig(target.configPath, target.supermodule);
|
|
3410
3695
|
await setPendingInitSince(target.dir, VERSION);
|
|
@@ -3460,7 +3745,7 @@ async function needsHookConfigRefresh(target, config2) {
|
|
|
3460
3745
|
const manifest = await readInstallManifest(target.dir);
|
|
3461
3746
|
for (const agent of config2.agents) {
|
|
3462
3747
|
const meta = resolvePlatformMeta(target.dir, agent);
|
|
3463
|
-
const configPath2 =
|
|
3748
|
+
const configPath2 = path24.join(target.dir, meta.hookConfigFile);
|
|
3464
3749
|
const content = await readTextIfExists(configPath2);
|
|
3465
3750
|
if (content === null) {
|
|
3466
3751
|
return true;
|
|
@@ -3600,7 +3885,7 @@ function isCurrentProjectManagedHookPath(cwd, hookPath, meta, platform) {
|
|
|
3600
3885
|
return true;
|
|
3601
3886
|
}
|
|
3602
3887
|
return pathAliases(
|
|
3603
|
-
normalizePathForHookComparison(
|
|
3888
|
+
normalizePathForHookComparison(path24.resolve(cwd, relativeHookPath))
|
|
3604
3889
|
).includes(normalizedHookPath);
|
|
3605
3890
|
});
|
|
3606
3891
|
}
|