easy-coding-harness 0.8.3-beta.0 → 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.
Files changed (31) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +16 -10
  3. package/dist/cli.js +256 -47
  4. package/dist/cli.js.map +1 -1
  5. package/package.json +1 -1
  6. package/templates/claude/agents/ec-fixer.md +3 -2
  7. package/templates/claude/agents/ec-implementer.md +4 -1
  8. package/templates/claude/agents/ec-reviewer.md +4 -2
  9. package/templates/claude/agents/ec-verifier.md +6 -3
  10. package/templates/codex/agents/ec-fixer.toml +3 -2
  11. package/templates/codex/agents/ec-implementer.toml +4 -1
  12. package/templates/codex/agents/ec-reviewer.toml +4 -2
  13. package/templates/codex/agents/ec-verifier.toml +6 -3
  14. package/templates/common/bundled-skills/ec-meta/references/local-architecture/README.md +9 -7
  15. package/templates/common/skills/ec-analysis/SKILL.md +92 -266
  16. package/templates/common/skills/ec-implementing/SKILL.md +82 -131
  17. package/templates/common/skills/ec-memory/SKILL.md +23 -149
  18. package/templates/common/skills/ec-reviewing/SKILL.md +54 -73
  19. package/templates/common/skills/ec-task-management/SKILL.md +34 -94
  20. package/templates/common/skills/ec-verification/SKILL.md +54 -79
  21. package/templates/common/skills/ec-workflow/SKILL.md +109 -302
  22. package/templates/main-constraint/AGENTS.md.tpl +19 -17
  23. package/templates/main-constraint/CLAUDE.md.tpl +19 -17
  24. package/templates/qoder/agents/ec-fixer.md +3 -2
  25. package/templates/qoder/agents/ec-implementer.md +4 -1
  26. package/templates/qoder/agents/ec-reviewer.md +4 -2
  27. package/templates/qoder/agents/ec-verifier.md +6 -3
  28. package/templates/runtime/memory/SHORT_MEMORY_TEMPLATE.md +2 -0
  29. package/templates/runtime/templates/dev-spec-skeleton.md +14 -6
  30. package/templates/shared-hooks/easy_coding_state.py +1240 -89
  31. package/templates/shared-hooks/inject-subagent-context.py +3 -0
package/CHANGELOG.md CHANGED
@@ -6,6 +6,32 @@
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
+
29
+ ## 0.8.3
30
+
31
+ - 正式发布 Codex App thread 级 session 隔离:标准 hook `session_id` 缺失时使用 `CODEX_THREAD_ID`,避免同一 App 进程内多个逻辑会话共享 PPID session。
32
+ - 正式发布 Qoder CLI Agent 识别修复:共享 hook 优先按脚本平台目录判断,无法判断时优先使用 Qoder 专属环境变量,避免 Claude 兼容变量造成误识别。
33
+ - 汇总 `0.8.3-beta.0` 的 Codex thread fallback、Qoder/Claude 双环境变量、legacy PPID 回退、设计文档与安装后 hook 回归验证结果。
34
+
9
35
  ## 0.8.3-beta.0
10
36
 
11
37
  - 修复 Codex App 未在 hook payload 中提供 `session_id` 时仍回退到 PPID、导致同一 App 进程内多个逻辑会话共享 Easy Coding session 的问题;Codex 现在会在标准 hook session ID 缺失时使用 `CODEX_THREAD_ID`,仅在两者都不可用时保留 PPID 兼容回退。
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
- edge behavior --[approve / guard / lite / auto]--> target stage
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
- - 生效确认模式优先级为 session 覆盖 > 项目 `behavior.confirm_mode` > `guard` 默认值。
84
- - `approve` `INIT → ANALYSIS`、`MEMORYCOMPLETE` 外逐边确认;`guard` `lite` 只确认 `ANALYSIS → IMPLEMENT`、`VERIFICATION → MEMORY`;`auto` 自动执行全部合法工作流边。任何模式下关闭任务都必须显式执行。
85
- - `guard` / `auto` 的代码主链在 IMPLEMENT 后默认进入 REVIEW;`lite` 不执行 REVIEW,直接进入 VERIFICATION;`approve` 可明确选择 REVIEW 或跳过它。确认模式不绕过状态合法性、方案产物或验证门控。
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` 是验证硬门控,未实际运行的 lint、typecheck、test 不算通过。
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` | 交互修改当前项目的 `confirm_mode` |
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.7.0 时,旧 `strict_confirm` / `auto_mode` 会一次性迁移为
162
- `behavior.confirm_mode` 并从配置中删除。项目级模式用 `easy-coding config` 修改(要求
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 的临时覆盖通过 `ec-task-management` 对话修改。
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 = 2;
107
- var CONFIRM_MODES = ["approve", "guard", "lite", "auto"];
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
- confirm_mode: "guard"
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 isConfirmMode(value) {
175
- return typeof value === "string" && CONFIRM_MODES.includes(value);
176
+ function isApprovalMode(value) {
177
+ return typeof value === "string" && APPROVAL_MODES.includes(value);
176
178
  }
177
- function resolveLegacyConfirmMode(config2) {
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
- if (isConfirmMode(behavior.confirm_mode)) {
180
- return behavior.confirm_mode;
181
- }
182
- if (behavior.auto_mode === true) {
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 setConfirmMode(filePath, mode) {
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.confirm_mode = mode;
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 migrateConfirmModeConfig(filePath) {
203
+ async function migrateBehaviorConfig(filePath) {
204
204
  const config2 = await readConfigYaml(filePath);
205
- return setConfirmMode(filePath, resolveLegacyConfirmMode(config2));
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
- if (mappedStage === session.last_seen_stage) continue;
864
- session.last_seen_stage = mappedStage;
865
- await writeTextFile(filePath, JSON.stringify(session, null, 2));
866
- sessionsUpdated += 1;
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 = resolveLegacyConfirmMode(projectConfig);
2680
- const selected = await select({
2681
- message: `Select project confirm mode (current: ${current})`,
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: "lite",
2696
- label: "lite \u2014 guard gates without code review",
2697
- hint: "IMPLEMENT -> VERIFICATION"
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 selected === "symbol") {
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.confirm_mode to ${selected}?`,
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 setConfirmMode(configPath2, selected);
2719
- outro3(chalk4.green(`Project confirm mode updated to ${selected}.`));
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 projectConfirmMode = isConfirmMode(config2.behavior?.confirm_mode) ? config2.behavior.confirm_mode : "guard";
3025
- console.log(` confirm_mode: ${projectConfirmMode}`);
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(` project_confirm_mode: ${projectConfirmMode}`);
3029
- console.log(` effective_confirm_mode: ${projectConfirmMode} (without a session override)`);
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 sessionConfirmMode = session.confirm_mode;
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(` confirm_mode: ${sessionConfirmMode ?? "project default"}`);
3039
- console.log(` effective_confirm_mode: ${sessionConfirmMode ?? projectConfirmMode}`);
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 behavior.strict_confirm/auto_mode to behavior.confirm_mode and remove the old keys.",
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 migrateConfirmModeConfig(target.configPath);
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);