easy-coding-harness 0.8.3 → 0.9.0-beta.0
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 +20 -0
- package/README.md +16 -10
- package/dist/cli.js +256 -47
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/templates/claude/agents/ec-fixer.md +3 -2
- package/templates/claude/agents/ec-implementer.md +4 -1
- package/templates/claude/agents/ec-reviewer.md +4 -2
- package/templates/claude/agents/ec-verifier.md +6 -3
- package/templates/codex/agents/ec-fixer.toml +3 -2
- package/templates/codex/agents/ec-implementer.toml +4 -1
- package/templates/codex/agents/ec-reviewer.toml +4 -2
- package/templates/codex/agents/ec-verifier.toml +6 -3
- package/templates/common/bundled-skills/ec-meta/references/local-architecture/README.md +9 -7
- package/templates/common/skills/ec-analysis/SKILL.md +92 -266
- package/templates/common/skills/ec-implementing/SKILL.md +82 -131
- package/templates/common/skills/ec-memory/SKILL.md +23 -149
- package/templates/common/skills/ec-reviewing/SKILL.md +54 -73
- package/templates/common/skills/ec-task-management/SKILL.md +34 -94
- package/templates/common/skills/ec-verification/SKILL.md +54 -79
- package/templates/common/skills/ec-workflow/SKILL.md +109 -302
- package/templates/main-constraint/AGENTS.md.tpl +19 -17
- package/templates/main-constraint/CLAUDE.md.tpl +19 -17
- package/templates/qoder/agents/ec-fixer.md +3 -2
- package/templates/qoder/agents/ec-implementer.md +4 -1
- package/templates/qoder/agents/ec-reviewer.md +4 -2
- package/templates/qoder/agents/ec-verifier.md +6 -3
- package/templates/runtime/memory/SHORT_MEMORY_TEMPLATE.md +2 -0
- package/templates/runtime/templates/dev-spec-skeleton.md +14 -6
- package/templates/shared-hooks/easy_coding_state.py +1240 -89
- package/templates/shared-hooks/inject-subagent-context.py +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,26 @@
|
|
|
6
6
|
- `y`:常规功能升级
|
|
7
7
|
- `z`:日常 bug 修复
|
|
8
8
|
|
|
9
|
+
## 0.9.0-beta.0
|
|
10
|
+
|
|
11
|
+
- 将单一 `behavior.confirm_mode` 拆分为 `behavior.approval_mode` 与
|
|
12
|
+
`behavior.workflow_mode`:审批等待和执行深度不再耦合;工作流默认使用 `adaptive`,
|
|
13
|
+
并在 ANALYSIS 结束时解析、展示并冻结为 `fast`、`standard` 或 `strict`。
|
|
14
|
+
- `approval_mode` 新增 `confirm`:仅在 ANALYSIS → IMPLEMENT 等待一次方案确认,确认后
|
|
15
|
+
REVIEW、VERIFICATION、MEMORY、COMPLETE 在各自机械质量门禁通过后自动推进。
|
|
16
|
+
- 保留完整代码任务状态链,Fast 也必须进入 REVIEW;按模式调整每个状态内部的上下文
|
|
17
|
+
加载、主 Agent/子 Agent 分工、审查独立性、验证范围和记忆深度,减少无效 Token、
|
|
18
|
+
固定子 Agent 调度和重复全量检查。
|
|
19
|
+
- 新增工作流模式提案、机械风险下限、原子冻结和只升不降机制;REVIEW 证据绑定最终
|
|
20
|
+
实现指纹,VERIFICATION 证据绑定实现与配置指纹,相关内容未变化时可复用、变化后
|
|
21
|
+
自动失效。
|
|
22
|
+
- REVIEW 返工改为按语义单元合并,明确 error/warning/info 阻断语义;同类问题连续两轮
|
|
23
|
+
未解决时停止盲目返工并回到重新分析。
|
|
24
|
+
- 配置 Schema 升至 3;旧 `lite` 自动迁移为 `approval_mode: guard` 与
|
|
25
|
+
`workflow_mode: fast`,旧 approve/guard/auto 保留审批语义并默认使用 adaptive;
|
|
26
|
+
在途任务通过 legacy 标记兼容已有提案与 REVIEW 证据,仅旧 lite 或已持久化直通边
|
|
27
|
+
保留一次 IMPLEMENT → VERIFICATION;已处于 VERIFICATION 的旧任务仍需新鲜验证证据。
|
|
28
|
+
|
|
9
29
|
## 0.8.3
|
|
10
30
|
|
|
11
31
|
- 正式发布 Codex App thread 级 session 隔离:标准 hook `session_id` 缺失时使用 `CODEX_THREAD_ID`,避免同一 App 进程内多个逻辑会话共享 PPID session。
|
package/README.md
CHANGED
|
@@ -70,21 +70,26 @@ easy-coding init --submodules packages/a,packages/b
|
|
|
70
70
|
|
|
71
71
|
```text
|
|
72
72
|
INIT --[always auto]--> ANALYSIS -> IMPLEMENT -> REVIEW -> VERIFICATION -> MEMORY --[always auto]--> COMPLETE
|
|
73
|
-
\----------------> VERIFICATION
|
|
74
73
|
\--[read-only, mode-aware]-----------------------> COMPLETE
|
|
75
74
|
^ ^ | |
|
|
76
75
|
+-- replan ---+ +--- fix -----+
|
|
77
76
|
^ |
|
|
78
77
|
+------- repair ----------+
|
|
79
|
-
|
|
78
|
+
approval --[approve / guard / confirm / auto]--> transition wait policy
|
|
79
|
+
workflow --[adaptive => fast / standard / strict]--> stage execution depth
|
|
80
80
|
any stage --[user abort via ec-task-close]--> CLOSED
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
-
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
- 审批模式优先级为 session 覆盖 > 项目 `behavior.approval_mode` > `guard`;`approve`
|
|
84
|
+
逐边确认,`guard` 确认 ANALYSIS → IMPLEMENT 与 VERIFICATION → MEMORY,`confirm` 只在
|
|
85
|
+
ANALYSIS → IMPLEMENT 确认一次,随后各阶段在质量门禁通过后自动推进,`auto` 从开始即
|
|
86
|
+
自动推进。
|
|
87
|
+
- 工作流模式优先级为 session 覆盖 > 项目 `behavior.workflow_mode` > `adaptive`。Adaptive 在 ANALYSIS 结束时根据风险解析、展示并冻结为 `fast`、`standard` 或 `strict`,用户可在风险下限之上调整。
|
|
88
|
+
- 所有新代码任务都完整进入 REVIEW;不同工作流模式只调整各状态内部的上下文加载、执行主体、审查独立性、验证范围和记忆深度,不绕过状态或证据门禁。
|
|
86
89
|
- 显式 `doc` / `analysis` / `report` 只读任务不生成 `test-strategy.md`;展示完整报告后按生效模式进入 COMPLETE,不执行 REVIEW、VERIFICATION 或 MEMORY,也不写任务记忆。
|
|
87
|
-
- `VERIFICATION`
|
|
90
|
+
- `VERIFICATION` 是验证硬门控:Fast 运行最小充分检查,Standard 运行受影响范围检查,
|
|
91
|
+
Strict 运行项目适用的完整 lint/typecheck/test/build;所选模式要求的检查未真实执行
|
|
92
|
+
并留下当前指纹下的绿色证据,就不算通过。
|
|
88
93
|
- `MEMORY` 先写入本次任务短期记忆,再执行长期记忆阈值门禁;未超过阈值时长期沉淀为 no-op。
|
|
89
94
|
|
|
90
95
|
## Supermodule 模型
|
|
@@ -108,7 +113,7 @@ any stage --[user abort via ec-task-close]--> CLOSED
|
|
|
108
113
|
| `easy-coding add-agent` | 给已接入项目追加 Claude Code、Codex 或 Qoder 支持;supermodule 父仓可按已初始化子仓分层追加 |
|
|
109
114
|
| `easy-coding upgrade` | CLI 升级后同步项目内生成文件,生成区覆盖,用户资产保留;supermodule 父仓会同步升级已初始化子仓 |
|
|
110
115
|
| `easy-coding update` | 更新全局 CLI 到最新发布版 |
|
|
111
|
-
| `easy-coding config` | 交互修改当前项目的 `
|
|
116
|
+
| `easy-coding config` | 交互修改当前项目的 `approval_mode` 与 `workflow_mode` |
|
|
112
117
|
| `easy-coding status` | 查看已安装平台、harness 版本、当前任务状态 |
|
|
113
118
|
| `easy-coding clear` | 移除 harness 安装物,保留 tasks、spec、memory、project.yaml 等用户资产;supermodule 父仓支持交互选择、`--submodules` 和 `--no-submodules` |
|
|
114
119
|
|
|
@@ -158,10 +163,11 @@ easy-coding upgrade
|
|
|
158
163
|
|
|
159
164
|
`upgrade` 会刷新生成区内的 skills、hooks、agents、主约束模板和运行时模板,不会删除已有任务、spec、memory、project.yaml 或项目知识文件。
|
|
160
165
|
|
|
161
|
-
升级到 0.
|
|
162
|
-
`behavior.
|
|
166
|
+
升级到 0.9.0 时,旧 `strict_confirm` / `auto_mode` / `confirm_mode` 会一次性迁移为
|
|
167
|
+
`behavior.approval_mode` 与 `behavior.workflow_mode`;旧 `lite` 映射为 `guard + fast`。
|
|
168
|
+
项目级模式用 `easy-coding config` 修改(要求
|
|
163
169
|
项目 Harness 与 CLI 版本完全一致,否则先执行 `easy-coding upgrade` 或更新 CLI);当前
|
|
164
|
-
session
|
|
170
|
+
session 的两类临时覆盖通过 `ec-task-management` 对话修改。
|
|
165
171
|
|
|
166
172
|
若当前会话不希望 Harness 接管,显式调用 `/ec-no-harness`(Codex 使用
|
|
167
173
|
`$ec-no-harness`)。它只旁路 Easy Coding,不关闭其他 hooks,也不忽略其他 skills;
|
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 = 3;
|
|
107
|
+
var APPROVAL_MODES = ["approve", "guard", "confirm", "auto"];
|
|
108
|
+
var CONFIGURED_WORKFLOW_MODES = ["adaptive", "fast", "standard", "strict"];
|
|
108
109
|
function createDefaultConfig(params) {
|
|
109
110
|
const config2 = {
|
|
110
111
|
version: CONFIG_SCHEMA_VERSION,
|
|
@@ -123,7 +124,8 @@ function createDefaultConfig(params) {
|
|
|
123
124
|
auto_archive_days: 30
|
|
124
125
|
},
|
|
125
126
|
behavior: {
|
|
126
|
-
|
|
127
|
+
approval_mode: "guard",
|
|
128
|
+
workflow_mode: "adaptive"
|
|
127
129
|
}
|
|
128
130
|
};
|
|
129
131
|
if (params.supermodule) {
|
|
@@ -171,38 +173,37 @@ async function updateHarnessVersion(filePath, version) {
|
|
|
171
173
|
config2.harness_version = version;
|
|
172
174
|
});
|
|
173
175
|
}
|
|
174
|
-
function
|
|
175
|
-
return typeof value === "string" &&
|
|
176
|
+
function isApprovalMode(value) {
|
|
177
|
+
return typeof value === "string" && APPROVAL_MODES.includes(value);
|
|
176
178
|
}
|
|
177
|
-
function
|
|
179
|
+
function isConfiguredWorkflowMode(value) {
|
|
180
|
+
return typeof value === "string" && CONFIGURED_WORKFLOW_MODES.includes(value);
|
|
181
|
+
}
|
|
182
|
+
function resolveLegacyBehavior(config2) {
|
|
178
183
|
const behavior = config2.behavior ?? {};
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
return "auto";
|
|
184
|
-
}
|
|
185
|
-
if (behavior.strict_confirm === true) {
|
|
186
|
-
return "approve";
|
|
187
|
-
}
|
|
188
|
-
return "guard";
|
|
184
|
+
const legacyLite = behavior.confirm_mode === "lite";
|
|
185
|
+
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
|
+
const workflowMode = isConfiguredWorkflowMode(behavior.workflow_mode) ? behavior.workflow_mode : legacyLite ? "fast" : "adaptive";
|
|
187
|
+
return { approvalMode, workflowMode };
|
|
189
188
|
}
|
|
190
|
-
async function
|
|
189
|
+
async function setBehaviorModes(filePath, approvalMode, workflowMode) {
|
|
191
190
|
return updateConfigYaml(filePath, (config2) => {
|
|
192
191
|
const legacyBehavior = config2.behavior ?? {};
|
|
193
192
|
const behavior = Object.fromEntries(
|
|
194
193
|
Object.entries(legacyBehavior).filter(
|
|
195
|
-
([key]) => key !== "strict_confirm" && key !== "auto_mode"
|
|
194
|
+
([key]) => key !== "strict_confirm" && key !== "auto_mode" && key !== "confirm_mode" && key !== "approval_mode" && key !== "workflow_mode"
|
|
196
195
|
)
|
|
197
196
|
);
|
|
198
|
-
behavior.
|
|
197
|
+
behavior.approval_mode = approvalMode;
|
|
198
|
+
behavior.workflow_mode = workflowMode;
|
|
199
199
|
config2.behavior = behavior;
|
|
200
200
|
config2.version = CONFIG_SCHEMA_VERSION;
|
|
201
201
|
});
|
|
202
202
|
}
|
|
203
|
-
async function
|
|
203
|
+
async function migrateBehaviorConfig(filePath) {
|
|
204
204
|
const config2 = await readConfigYaml(filePath);
|
|
205
|
-
|
|
205
|
+
const { approvalMode, workflowMode } = resolveLegacyBehavior(config2);
|
|
206
|
+
return setBehaviorModes(filePath, approvalMode, workflowMode);
|
|
206
207
|
}
|
|
207
208
|
async function ensureProjectId(filePath) {
|
|
208
209
|
let projectId = "";
|
|
@@ -805,8 +806,105 @@ function migrateTaskWorkflowState(task) {
|
|
|
805
806
|
task.memory_progress = progress;
|
|
806
807
|
changed = true;
|
|
807
808
|
}
|
|
809
|
+
const status2 = String(task.status ?? "");
|
|
810
|
+
const taskType = String(task.type ?? "").toLowerCase();
|
|
811
|
+
const isActive = !["", "PENDING", "COMPLETE", "CLOSED"].includes(status2);
|
|
812
|
+
if (isActive && taskType !== "project-init" && !["fast", "standard", "strict"].includes(String(task.workflow_mode ?? ""))) {
|
|
813
|
+
task.workflow_mode = ["analysis", "doc", "report"].includes(taskType) ? "fast" : "strict";
|
|
814
|
+
task.workflow_mode_confirmed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
815
|
+
task.workflow_mode_confirmed_by = "upgrade-migration";
|
|
816
|
+
task.workflow_mode_legacy = true;
|
|
817
|
+
changed = true;
|
|
818
|
+
}
|
|
819
|
+
return changed;
|
|
820
|
+
}
|
|
821
|
+
function migrateSessionBehavior(session) {
|
|
822
|
+
let changed = false;
|
|
823
|
+
const legacyMode = session.confirm_mode;
|
|
824
|
+
const legacyLite = legacyMode === "lite";
|
|
825
|
+
if (!["approve", "guard", "confirm", "auto"].includes(String(session.approval_mode ?? ""))) {
|
|
826
|
+
if (legacyLite) {
|
|
827
|
+
session.approval_mode = "guard";
|
|
828
|
+
changed = true;
|
|
829
|
+
} else if (["approve", "guard", "confirm", "auto"].includes(String(legacyMode ?? ""))) {
|
|
830
|
+
session.approval_mode = legacyMode;
|
|
831
|
+
changed = true;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
if (!["adaptive", "fast", "standard", "strict"].includes(String(session.workflow_mode ?? ""))) {
|
|
835
|
+
if (legacyLite) {
|
|
836
|
+
session.workflow_mode = "fast";
|
|
837
|
+
session.workflow_mode_legacy_confirm_override = true;
|
|
838
|
+
session.workflow_mode_legacy_alias_override = void 0;
|
|
839
|
+
changed = true;
|
|
840
|
+
} else if (["approve", "guard", "confirm", "auto"].includes(String(legacyMode ?? ""))) {
|
|
841
|
+
session.workflow_mode = "adaptive";
|
|
842
|
+
session.workflow_mode_legacy_alias_override = true;
|
|
843
|
+
session.workflow_mode_legacy_confirm_override = void 0;
|
|
844
|
+
changed = true;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
if (legacyLite && session.workflow_mode_legacy_alias_override === true) {
|
|
848
|
+
session.workflow_mode_legacy_alias_override = void 0;
|
|
849
|
+
changed = true;
|
|
850
|
+
} else if (["approve", "guard", "confirm", "auto"].includes(String(legacyMode ?? "")) && session.workflow_mode_legacy_confirm_override === true) {
|
|
851
|
+
session.workflow_mode_legacy_confirm_override = void 0;
|
|
852
|
+
changed = true;
|
|
853
|
+
}
|
|
854
|
+
if ("confirm_mode" in session) {
|
|
855
|
+
session.confirm_mode = void 0;
|
|
856
|
+
changed = true;
|
|
857
|
+
}
|
|
808
858
|
return changed;
|
|
809
859
|
}
|
|
860
|
+
var WORKFLOW_MODE_RANK = {
|
|
861
|
+
fast: 0,
|
|
862
|
+
standard: 1,
|
|
863
|
+
strict: 2
|
|
864
|
+
};
|
|
865
|
+
function sessionMigrationCandidate(session) {
|
|
866
|
+
if (typeof session.current_task !== "string") return null;
|
|
867
|
+
const workflowMode = ["adaptive", "fast", "standard", "strict"].includes(
|
|
868
|
+
String(session.workflow_mode ?? "")
|
|
869
|
+
) ? session.workflow_mode : void 0;
|
|
870
|
+
return {
|
|
871
|
+
currentTask: session.current_task,
|
|
872
|
+
agent: typeof session.agent === "string" ? session.agent : void 0,
|
|
873
|
+
lastAgent: typeof session.last_agent === "string" ? session.last_agent : void 0,
|
|
874
|
+
workflowMode,
|
|
875
|
+
legacyLite: session.workflow_mode_legacy_confirm_override === true
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
function relevantSessionMigrationCandidates(task, candidates) {
|
|
879
|
+
const taskAgent = String(task.last_agent ?? "");
|
|
880
|
+
const ownerCandidates = candidates.filter(
|
|
881
|
+
(candidate) => candidate.agent === taskAgent || candidate.lastAgent === taskAgent
|
|
882
|
+
);
|
|
883
|
+
return ownerCandidates.length > 0 ? ownerCandidates : candidates;
|
|
884
|
+
}
|
|
885
|
+
function migrationWorkflowMode(task, candidates, projectWorkflowMode) {
|
|
886
|
+
const taskType = String(task.type ?? "").toLowerCase();
|
|
887
|
+
if (["analysis", "doc", "report"].includes(taskType)) return "fast";
|
|
888
|
+
const relevantCandidates = relevantSessionMigrationCandidates(task, candidates);
|
|
889
|
+
const fallbackMode = projectWorkflowMode === "adaptive" ? "strict" : projectWorkflowMode;
|
|
890
|
+
const concreteModes = relevantCandidates.map(
|
|
891
|
+
(candidate) => candidate.workflowMode === "adaptive" ? "strict" : candidate.workflowMode ?? fallbackMode
|
|
892
|
+
);
|
|
893
|
+
if (concreteModes.length === 0) return fallbackMode;
|
|
894
|
+
return concreteModes.reduce(
|
|
895
|
+
(highest, mode) => WORKFLOW_MODE_RANK[mode] > WORKFLOW_MODE_RANK[highest] ? mode : highest
|
|
896
|
+
);
|
|
897
|
+
}
|
|
898
|
+
function preserveLegacyDirectEdge(task, candidates, projectLegacyLite) {
|
|
899
|
+
if (task.status !== "IMPLEMENT") return false;
|
|
900
|
+
const pending = task.pending_transition && typeof task.pending_transition === "object" && !Array.isArray(task.pending_transition) ? task.pending_transition : null;
|
|
901
|
+
if (pending?.from === "IMPLEMENT" && pending.to === "VERIFICATION") return true;
|
|
902
|
+
const relevantCandidates = relevantSessionMigrationCandidates(task, candidates);
|
|
903
|
+
if (relevantCandidates.length > 0) {
|
|
904
|
+
return relevantCandidates.every((candidate) => candidate.legacyLite);
|
|
905
|
+
}
|
|
906
|
+
return projectLegacyLite;
|
|
907
|
+
}
|
|
810
908
|
async function taskFiles(cwd) {
|
|
811
909
|
const tasksDir = path7.join(cwd, EASY_CODING_DIR, TASKS_DIR);
|
|
812
910
|
if (!await pathExists(tasksDir)) return [];
|
|
@@ -832,7 +930,7 @@ async function hasLegacyWorkflowState(cwd) {
|
|
|
832
930
|
if (!await pathExists(filePath)) continue;
|
|
833
931
|
const task = await readJsonRecord(filePath);
|
|
834
932
|
if (!task) continue;
|
|
835
|
-
if (isLegacyStage(task.status) || Array.isArray(task.stage_history) && task.stage_history.some(
|
|
933
|
+
if (isLegacyStage(task.status) || !["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(
|
|
836
934
|
(entry) => entry && typeof entry === "object" && !Array.isArray(entry) && isLegacyStage(entry.stage)
|
|
837
935
|
)) {
|
|
838
936
|
return true;
|
|
@@ -841,13 +939,26 @@ async function hasLegacyWorkflowState(cwd) {
|
|
|
841
939
|
for (const filePath of await sessionFiles(cwd)) {
|
|
842
940
|
const session = await readJsonRecord(filePath);
|
|
843
941
|
if (!session) continue;
|
|
844
|
-
if (isLegacyStage(session.last_seen_stage)) return true;
|
|
942
|
+
if (isLegacyStage(session.last_seen_stage) || "confirm_mode" in session) return true;
|
|
845
943
|
}
|
|
846
944
|
return false;
|
|
847
945
|
}
|
|
848
946
|
async function migrateLegacyWorkflowState(cwd) {
|
|
849
947
|
let tasksUpdated = 0;
|
|
850
948
|
let sessionsUpdated = 0;
|
|
949
|
+
const updatedTaskPaths = /* @__PURE__ */ new Set();
|
|
950
|
+
let projectWorkflowMode = "adaptive";
|
|
951
|
+
let projectLegacyLite = false;
|
|
952
|
+
const configPath2 = path7.join(cwd, EASY_CODING_DIR, "config.yaml");
|
|
953
|
+
if (await pathExists(configPath2)) {
|
|
954
|
+
try {
|
|
955
|
+
const config2 = await readConfigYaml(configPath2);
|
|
956
|
+
projectWorkflowMode = resolveLegacyBehavior(config2).workflowMode;
|
|
957
|
+
const behavior = config2.behavior ?? {};
|
|
958
|
+
projectLegacyLite = behavior.confirm_mode === "lite";
|
|
959
|
+
} catch {
|
|
960
|
+
}
|
|
961
|
+
}
|
|
851
962
|
for (const filePath of await taskFiles(cwd)) {
|
|
852
963
|
if (!await pathExists(filePath)) continue;
|
|
853
964
|
const task = await readJsonRecord(filePath);
|
|
@@ -855,15 +966,68 @@ async function migrateLegacyWorkflowState(cwd) {
|
|
|
855
966
|
if (!migrateTaskWorkflowState(task)) continue;
|
|
856
967
|
await writeTextFile(filePath, JSON.stringify(task, null, 2));
|
|
857
968
|
tasksUpdated += 1;
|
|
969
|
+
updatedTaskPaths.add(filePath);
|
|
858
970
|
}
|
|
971
|
+
const sessionCandidates = [];
|
|
859
972
|
for (const filePath of await sessionFiles(cwd)) {
|
|
860
973
|
const session = await readJsonRecord(filePath);
|
|
861
974
|
if (!session) continue;
|
|
862
975
|
const mappedStage = migrateStage(session.last_seen_stage);
|
|
863
|
-
|
|
864
|
-
session.last_seen_stage
|
|
865
|
-
|
|
866
|
-
|
|
976
|
+
let changed = false;
|
|
977
|
+
if (mappedStage !== session.last_seen_stage) {
|
|
978
|
+
session.last_seen_stage = mappedStage;
|
|
979
|
+
changed = true;
|
|
980
|
+
}
|
|
981
|
+
changed = migrateSessionBehavior(session) || changed;
|
|
982
|
+
const candidate = sessionMigrationCandidate(session);
|
|
983
|
+
if (candidate) sessionCandidates.push(candidate);
|
|
984
|
+
if (changed) {
|
|
985
|
+
await writeTextFile(filePath, JSON.stringify(session, null, 2));
|
|
986
|
+
sessionsUpdated += 1;
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
const candidatesByTask = /* @__PURE__ */ new Map();
|
|
990
|
+
for (const candidate of sessionCandidates) {
|
|
991
|
+
const candidates = candidatesByTask.get(candidate.currentTask) ?? [];
|
|
992
|
+
candidates.push(candidate);
|
|
993
|
+
candidatesByTask.set(candidate.currentTask, candidates);
|
|
994
|
+
}
|
|
995
|
+
for (const filePath of await taskFiles(cwd)) {
|
|
996
|
+
const task = await readJsonRecord(filePath);
|
|
997
|
+
if (!task) continue;
|
|
998
|
+
const migrationOwned = task.workflow_mode_legacy === true && task.workflow_mode_confirmed_by === "upgrade-migration";
|
|
999
|
+
if (!migrationOwned) continue;
|
|
1000
|
+
const taskId = path7.basename(path7.dirname(filePath));
|
|
1001
|
+
const mode = migrationWorkflowMode(
|
|
1002
|
+
task,
|
|
1003
|
+
candidatesByTask.get(taskId) ?? [],
|
|
1004
|
+
projectWorkflowMode
|
|
1005
|
+
);
|
|
1006
|
+
let changed = false;
|
|
1007
|
+
if (task.workflow_mode !== mode) {
|
|
1008
|
+
task.workflow_mode = mode;
|
|
1009
|
+
task.workflow_mode_confirmed_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
1010
|
+
changed = true;
|
|
1011
|
+
}
|
|
1012
|
+
const keepDirectEdge = preserveLegacyDirectEdge(
|
|
1013
|
+
task,
|
|
1014
|
+
candidatesByTask.get(taskId) ?? [],
|
|
1015
|
+
projectLegacyLite
|
|
1016
|
+
);
|
|
1017
|
+
if (keepDirectEdge && task.workflow_mode_legacy_direct_edge !== true) {
|
|
1018
|
+
task.workflow_mode_legacy_direct_edge = true;
|
|
1019
|
+
changed = true;
|
|
1020
|
+
} else if (!keepDirectEdge && "workflow_mode_legacy_direct_edge" in task) {
|
|
1021
|
+
task.workflow_mode_legacy_direct_edge = void 0;
|
|
1022
|
+
changed = true;
|
|
1023
|
+
}
|
|
1024
|
+
if (changed) {
|
|
1025
|
+
await writeTextFile(filePath, JSON.stringify(task, null, 2));
|
|
1026
|
+
if (!updatedTaskPaths.has(filePath)) {
|
|
1027
|
+
tasksUpdated += 1;
|
|
1028
|
+
updatedTaskPaths.add(filePath);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
867
1031
|
}
|
|
868
1032
|
return { tasksUpdated, sessionsUpdated };
|
|
869
1033
|
}
|
|
@@ -2676,10 +2840,10 @@ async function config() {
|
|
|
2676
2840
|
`Project harness ${projectConfig.harness_version} does not exactly match CLI ${VERSION}. Upgrade the harness or update the CLI before changing config.`
|
|
2677
2841
|
);
|
|
2678
2842
|
}
|
|
2679
|
-
const current =
|
|
2680
|
-
const
|
|
2681
|
-
message: `Select project
|
|
2682
|
-
initialValue: current,
|
|
2843
|
+
const current = resolveLegacyBehavior(projectConfig);
|
|
2844
|
+
const approvalMode = await select({
|
|
2845
|
+
message: `Select project approval mode (current: ${current.approvalMode})`,
|
|
2846
|
+
initialValue: current.approvalMode,
|
|
2683
2847
|
options: [
|
|
2684
2848
|
{
|
|
2685
2849
|
value: "approve",
|
|
@@ -2692,9 +2856,9 @@ async function config() {
|
|
|
2692
2856
|
hint: "ANALYSIS -> IMPLEMENT and VERIFICATION -> MEMORY"
|
|
2693
2857
|
},
|
|
2694
2858
|
{
|
|
2695
|
-
value: "
|
|
2696
|
-
label: "
|
|
2697
|
-
hint: "
|
|
2859
|
+
value: "confirm",
|
|
2860
|
+
label: "confirm \u2014 confirm the plan once",
|
|
2861
|
+
hint: "only ANALYSIS -> IMPLEMENT; later stages advance after quality gates"
|
|
2698
2862
|
},
|
|
2699
2863
|
{
|
|
2700
2864
|
value: "auto",
|
|
@@ -2703,20 +2867,50 @@ async function config() {
|
|
|
2703
2867
|
}
|
|
2704
2868
|
]
|
|
2705
2869
|
});
|
|
2706
|
-
if (typeof
|
|
2870
|
+
if (typeof approvalMode === "symbol") {
|
|
2871
|
+
cancel4("Configuration cancelled.");
|
|
2872
|
+
return;
|
|
2873
|
+
}
|
|
2874
|
+
const workflowMode = await select({
|
|
2875
|
+
message: `Select project workflow mode (current: ${current.workflowMode})`,
|
|
2876
|
+
initialValue: current.workflowMode,
|
|
2877
|
+
options: [
|
|
2878
|
+
{
|
|
2879
|
+
value: "adaptive",
|
|
2880
|
+
label: "adaptive \u2014 choose by task risk (default)",
|
|
2881
|
+
hint: "freezes to fast, standard, or strict after ANALYSIS"
|
|
2882
|
+
},
|
|
2883
|
+
{
|
|
2884
|
+
value: "fast",
|
|
2885
|
+
label: "fast \u2014 compact execution for low-risk tasks",
|
|
2886
|
+
hint: "all workflow stages still run"
|
|
2887
|
+
},
|
|
2888
|
+
{
|
|
2889
|
+
value: "standard",
|
|
2890
|
+
label: "standard \u2014 balanced execution",
|
|
2891
|
+
hint: "independent review and impacted verification"
|
|
2892
|
+
},
|
|
2893
|
+
{
|
|
2894
|
+
value: "strict",
|
|
2895
|
+
label: "strict \u2014 maximum assurance",
|
|
2896
|
+
hint: "multi-dimensional review and full verification"
|
|
2897
|
+
}
|
|
2898
|
+
]
|
|
2899
|
+
});
|
|
2900
|
+
if (typeof workflowMode === "symbol") {
|
|
2707
2901
|
cancel4("Configuration cancelled.");
|
|
2708
2902
|
return;
|
|
2709
2903
|
}
|
|
2710
2904
|
const shouldSave = await confirm3({
|
|
2711
|
-
message: `Set behavior.
|
|
2905
|
+
message: `Set behavior.approval_mode to ${approvalMode} and behavior.workflow_mode to ${workflowMode}?`,
|
|
2712
2906
|
initialValue: true
|
|
2713
2907
|
});
|
|
2714
2908
|
if (typeof shouldSave === "symbol" || !shouldSave) {
|
|
2715
2909
|
cancel4("Configuration cancelled.");
|
|
2716
2910
|
return;
|
|
2717
2911
|
}
|
|
2718
|
-
await
|
|
2719
|
-
outro3(chalk4.green(`Project
|
|
2912
|
+
await setBehaviorModes(configPath2, approvalMode, workflowMode);
|
|
2913
|
+
outro3(chalk4.green(`Project modes updated: approval=${approvalMode}, workflow=${workflowMode}.`));
|
|
2720
2914
|
}
|
|
2721
2915
|
|
|
2722
2916
|
// src/commands/init.ts
|
|
@@ -3021,22 +3215,34 @@ async function status() {
|
|
|
3021
3215
|
}
|
|
3022
3216
|
console.log(` agents: ${config2.agents.join(", ") || "(none)"}`);
|
|
3023
3217
|
console.log(` project: ${config2.project.name}`);
|
|
3024
|
-
const
|
|
3025
|
-
|
|
3218
|
+
const migratedBehavior = resolveLegacyBehavior(config2);
|
|
3219
|
+
const projectApprovalMode = isApprovalMode(config2.behavior?.approval_mode) ? config2.behavior.approval_mode : migratedBehavior.approvalMode;
|
|
3220
|
+
const projectWorkflowMode = isConfiguredWorkflowMode(config2.behavior?.workflow_mode) ? config2.behavior.workflow_mode : migratedBehavior.workflowMode;
|
|
3221
|
+
console.log(` approval_mode: ${projectApprovalMode}`);
|
|
3222
|
+
console.log(` workflow_mode: ${projectWorkflowMode}`);
|
|
3026
3223
|
console.log("");
|
|
3027
3224
|
console.log(chalk6.bold("Sessions"));
|
|
3028
|
-
console.log(`
|
|
3029
|
-
console.log(`
|
|
3225
|
+
console.log(` project_approval_mode: ${projectApprovalMode}`);
|
|
3226
|
+
console.log(` project_workflow_mode: ${projectWorkflowMode}`);
|
|
3227
|
+
console.log(` effective_approval_mode: ${projectApprovalMode} (without a session override)`);
|
|
3228
|
+
console.log(` configured_workflow_mode: ${projectWorkflowMode} (without a session override)`);
|
|
3030
3229
|
if (sessions.length === 0) {
|
|
3031
3230
|
console.log(" no session files");
|
|
3032
3231
|
}
|
|
3033
3232
|
for (const { key, session } of sessions) {
|
|
3034
|
-
const
|
|
3233
|
+
const legacySessionMode = session.confirm_mode;
|
|
3234
|
+
const hasLegacySessionMode = ["approve", "guard", "confirm", "auto"].includes(
|
|
3235
|
+
String(legacySessionMode ?? "")
|
|
3236
|
+
);
|
|
3237
|
+
const sessionApprovalMode = session.approval_mode ?? (legacySessionMode === "lite" ? "guard" : legacySessionMode);
|
|
3238
|
+
const sessionWorkflowMode = session.workflow_mode ?? (legacySessionMode === "lite" ? "fast" : hasLegacySessionMode ? "adaptive" : void 0);
|
|
3035
3239
|
console.log(` - ${key}`);
|
|
3036
3240
|
console.log(` agent: ${session.agent ?? "legacy/unknown"}`);
|
|
3037
3241
|
console.log(` source: ${session.session_source ?? "legacy"}`);
|
|
3038
|
-
console.log(`
|
|
3039
|
-
console.log(`
|
|
3242
|
+
console.log(` approval_mode: ${sessionApprovalMode ?? "project default"}`);
|
|
3243
|
+
console.log(` workflow_mode: ${sessionWorkflowMode ?? "project default"}`);
|
|
3244
|
+
console.log(` effective_approval_mode: ${sessionApprovalMode ?? projectApprovalMode}`);
|
|
3245
|
+
console.log(` configured_workflow_mode: ${sessionWorkflowMode ?? projectWorkflowMode}`);
|
|
3040
3246
|
console.log(
|
|
3041
3247
|
` harness: ${session.harness_disabled ? "disabled for this session" : "enabled"}`
|
|
3042
3248
|
);
|
|
@@ -3049,6 +3255,9 @@ async function status() {
|
|
|
3049
3255
|
const task = await readTaskJson(taskPath);
|
|
3050
3256
|
console.log(` current_task: ${session.current_task}`);
|
|
3051
3257
|
console.log(` current_stage: ${task.status}`);
|
|
3258
|
+
console.log(
|
|
3259
|
+
` task_workflow_mode: ${task.workflow_mode ?? task.workflow_mode_proposal?.selected_mode ?? "not resolved"}`
|
|
3260
|
+
);
|
|
3052
3261
|
console.log(` last_agent: ${task.last_agent}`);
|
|
3053
3262
|
} else {
|
|
3054
3263
|
console.log(` current_task: ${session.current_task} (task.json missing)`);
|
|
@@ -3162,7 +3371,7 @@ async function upgrade(opts) {
|
|
|
3162
3371
|
].filter(Boolean) : [],
|
|
3163
3372
|
"Will overwrite managed skills, hooks, agents, templates, and generated main-constraint regions.",
|
|
3164
3373
|
"Will update project-init task to recommend ec-init re-run for version adaptation.",
|
|
3165
|
-
"Will migrate
|
|
3374
|
+
"Will migrate legacy confirmation settings to behavior.approval_mode and behavior.workflow_mode.",
|
|
3166
3375
|
"Will migrate legacy workflow stage metadata; memory content, spec, and project knowledge files remain untouched."
|
|
3167
3376
|
].join("\n");
|
|
3168
3377
|
if (opts.dryRun) {
|
|
@@ -3195,7 +3404,7 @@ async function upgrade(opts) {
|
|
|
3195
3404
|
await ensureEasyCodingSessionsIgnored(target.dir);
|
|
3196
3405
|
await ensureHookBytecodeIgnored(target.dir);
|
|
3197
3406
|
await migrateLegacyWorkflowState(target.dir);
|
|
3198
|
-
await
|
|
3407
|
+
await migrateBehaviorConfig(target.configPath);
|
|
3199
3408
|
await updateHarnessVersion(target.configPath, VERSION);
|
|
3200
3409
|
await updateSupermoduleConfig(target.configPath, target.supermodule);
|
|
3201
3410
|
await setPendingInitSince(target.dir, VERSION);
|