easy-coding-harness 0.10.0-beta.7 → 0.10.0-beta.9

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 CHANGED
@@ -6,6 +6,39 @@
6
6
  - `y`:常规功能升级
7
7
  - `z`:日常 bug 修复
8
8
 
9
+ ## 0.10.0-beta.9
10
+
11
+ - 工作流 owner 改为规范平台身份边界:每份安装后的状态脚本固化宿主身份,新写入只接受
12
+ `claude-code` / `codex` / `qoder`,脚本身份、`--agent` 与已注入 session 命名空间三方
13
+ 不一致时在落盘前拒绝;`Codex with Easy Coding` 等作者/Canonical 展示归属不再允许进入
14
+ `last_agent`。
15
+ - Codex 与 Qoder 共用的 `AGENTS.md` 生成区改为安装顺序无关的双平台路径合同,
16
+ 明确当前宿主只能调用自己的状态脚本,防止 Qoder 最后写入后误导 Codex 执行
17
+ `.qoder/hooks/easy_coding_state.py`。
18
+ - handoff 提示改为显式协调事件驱动:只有未消费的 `handoff` 记录才显示交接,
19
+ `claim-task` 新增 immutable `claim` 审计记录并关闭交接;单纯 owner 差异不再被伪造成
20
+ handoff。
21
+ - upgrade 与首次 SessionStart 幂等迁移旧任务/session 中的 `root` 系列和误写展示名,
22
+ 即使版本号已经更新也能继续检测并自愈,但保留 `execution.jsonl` 及 Canonical Spec 的
23
+ 历史审计展示;新增生产形态、跨平台路径冲突、安装顺序和真实 handoff/claim 回归测试。
24
+
25
+ ## 0.10.0-beta.8
26
+
27
+ - VERIFICATION 绿色后新增可审计验收检查点。无漂移时 `confirm` / `auto` 继续按原审批模式
28
+ 自动流转;检查点后出现代码变化时,所有模式只临时暂停一次,返回完整文本 diff、二进制/
29
+ mode 变化、逐文件 old/new SHA-256 和稳定 `diff_sha256`,防止把同一次外部保存误判成必须
30
+ 重走完整流程;执行计划中的非 Git 文件同样支持精确差异确认。
31
+ - 用户确认精确差异后保留原 REVIEW 结论,不回退 IMPLEMENT 或重复审查;非执行差异可
32
+ `carry-forward` 原验证,可执行差异必须补当前指纹的 `targeted` 验证,`waived` 仅用于用户
33
+ 显式接受未验证风险;Canonical 多任务只要求实际受影响 source task 留下当前指纹的定向
34
+ 验证。配置、计划、Workflow、Canonical 设计或嵌套仓状态变化仍按正常门禁返回相应阶段。
35
+ - Canonical task 在 Harness 本地验证完成后保持 `implemented`,仅在
36
+ VERIFICATION → MEMORY 边界按显式确认或既有 `confirm` / `auto` 授权真正应用时写为
37
+ `verified`;共享事件包含验收摘要,MEMORY → COMPLETE 仍负责写回 `completed`。
38
+ - `execution.jsonl` 新增 immutable acceptance 记录,短期记忆门禁会校验用户接受的授权来源、
39
+ 差异摘要、Review/验证策略、变更文件和完整 digest;状态 API 新增检查点、差异检查及精确
40
+ 确认参数,并补齐 Auto、定向验证、Canonical 边界与无重复 REVIEW 的回归测试。
41
+
9
42
  ## 0.10.0-beta.7
10
43
 
11
44
  - Workflow floor 改为 Standard 居中的复合判定:单仓、单 Unit、非并行且最多 5 个文件的
package/README.md CHANGED
@@ -84,7 +84,8 @@ any stage --[user abort via ec-task-close]--> CLOSED
84
84
  - 审批模式优先级为 session 覆盖 > 项目 `behavior.approval_mode` > `guard`;`approve`
85
85
  逐边确认,`guard` 确认 ANALYSIS → IMPLEMENT 与 VERIFICATION → MEMORY,`confirm` 只在
86
86
  ANALYSIS → IMPLEMENT 确认一次,随后各阶段在质量门禁通过后自动推进,`auto` 从开始即
87
- 自动推进。
87
+ 自动推进。所有模式仅在 VERIFICATION 绿色检查点之后又出现新代码差异时临时暂停:展示
88
+ 精确 diff 与摘要,由用户确认该摘要后继续;这不会把 `auto` 永久降级为人工审批。
88
89
  - 工作流模式优先级为 session 覆盖 > 项目 `behavior.workflow_mode` > `adaptive`。Adaptive
89
90
  以 Standard 作为普通业务默认:单仓单 Unit、非并行且不超过 5 个文件的低风险局部修改
90
91
  优先 Fast;只有明确高风险与真实复杂度/大影响面同时存在才进入 Strict。仓库数只按当前
@@ -97,7 +98,10 @@ any stage --[user abort via ec-task-close]--> CLOSED
97
98
  - 显式 `doc` / `analysis` / `report` 只读任务不生成 `test-strategy.md`;展示完整报告后按生效模式进入 COMPLETE,不执行 REVIEW、VERIFICATION 或 MEMORY,也不写任务记忆。
98
99
  - `VERIFICATION` 是验证硬门控:Fast 运行最小充分检查,Standard 运行受影响范围检查,
99
100
  Strict 运行项目适用的完整 lint/typecheck/test/build;所选模式要求的检查未真实执行
100
- 并留下当前指纹下的绿色证据,就不算通过。
101
+ 并留下当前指纹下的绿色证据,就不算通过。绿色后会冻结验收检查点;若代码随后变化,
102
+ Harness 展示完整差异并绑定 `diff_sha256`。用户确认后不重跑 REVIEW:纯非执行差异可沿用
103
+ 原验证,可执行差异补定向验证,显式风险豁免单独记录。配置、方案或 Canonical 设计漂移
104
+ 不能走这条例外。
101
105
  - `MEMORY` 先写入本次任务短期记忆,再执行长期记忆阈值门禁;未超过阈值时长期沉淀为 no-op。
102
106
 
103
107
  ## Canonical Dev Spec
@@ -123,7 +127,10 @@ Canonical Spec 的静态设计由 design revision + `design_sha256` 冻结;共
123
127
  `execution_revision` CAS、幂等键和断点对账,执行区变化不会使本地 plan/review/verify
124
128
  指纹失效,设计变化或 revision 回滚仍会阻塞。显式项目外路径受支持,迁移后只能通过
125
129
  身份一致的 rebind 修复定位。静态设计调整必须 revision +1、READY 并执行 `sync-design`;
126
- 机器执行区禁止手工编辑。无 Canonical manifest 的历史 Dev-Spec 继续走原有整文分析流程。
130
+ 机器执行区禁止手工编辑。Canonical task Harness 本地校验完成后仍保持 `implemented`,
131
+ 只有 VERIFICATION → MEMORY 边界按显式确认或既有审批模式真正应用时才写为 `verified`,
132
+ 并携带验收差异摘要;MEMORY 完成后再写为 `completed`。无 Canonical manifest 的历史
133
+ Dev-Spec 继续走原有整文分析流程。
127
134
 
128
135
  ## Supermodule 模型
129
136
 
package/dist/cli.js CHANGED
@@ -311,6 +311,7 @@ var PLATFORM_META = {
311
311
  stateInjectEvent: ["SessionStart", "UserPromptSubmit"],
312
312
  hasSubagentContext: true,
313
313
  templateContext: {
314
+ workflow_agent_id: "claude-code",
314
315
  sub_agent_dispatch: "Agent tool",
315
316
  platform_spawn_instruction: 'Use the Agent tool with run_in_background when useful; use isolation: "worktree" for parallel file edits.',
316
317
  skill_trigger: "/",
@@ -334,6 +335,7 @@ var PLATFORM_META = {
334
335
  stateInjectEvent: ["SessionStart", "UserPromptSubmit"],
335
336
  hasSubagentContext: false,
336
337
  templateContext: {
338
+ workflow_agent_id: "codex",
337
339
  sub_agent_dispatch: "Codex sub-agent dispatch",
338
340
  platform_spawn_instruction: "Use Codex sub-agent delegation where available; pass the full task card in the prompt.",
339
341
  skill_trigger: "$",
@@ -358,6 +360,7 @@ var PLATFORM_META = {
358
360
  hasSubagentContext: true,
359
361
  cnVariant: ".qodercn",
360
362
  templateContext: {
363
+ workflow_agent_id: "qoder",
361
364
  sub_agent_dispatch: "Agent tool",
362
365
  platform_spawn_instruction: "Use the Agent tool with worktree isolation for parallel file edits.",
363
366
  skill_trigger: "/",
@@ -776,6 +779,87 @@ function isLegacyStage(value) {
776
779
  function migrateStage(value) {
777
780
  return isLegacyStage(value) ? LEGACY_STAGE_MAP[value] : value;
778
781
  }
782
+ var LEGACY_DISPLAY_AGENT_IDENTITIES = {
783
+ "claude with easy coding": "claude-code",
784
+ "claude-code with easy coding": "claude-code",
785
+ "claude code with easy coding": "claude-code",
786
+ "codex with easy coding": "codex",
787
+ "qoder with easy coding": "qoder"
788
+ };
789
+ function migratedAgentIdentity(value) {
790
+ if (typeof value !== "string") return void 0;
791
+ const normalized = value.trim().toLowerCase();
792
+ if (normalized === "claude-code" || normalized === "codex" || normalized === "qoder") {
793
+ return normalized;
794
+ }
795
+ if (/^\/?root(?:\/[a-z0-9._-]+)*$/.test(normalized)) return "codex";
796
+ return LEGACY_DISPLAY_AGENT_IDENTITIES[normalized];
797
+ }
798
+ function migrateAgentFields(record, fields) {
799
+ let changed = false;
800
+ for (const field of fields) {
801
+ const migrated = migratedAgentIdentity(record[field]);
802
+ if (migrated && migrated !== record[field]) {
803
+ record[field] = migrated;
804
+ changed = true;
805
+ }
806
+ }
807
+ return changed;
808
+ }
809
+ function taskAgentFieldGroups(task) {
810
+ const groups = [
811
+ {
812
+ record: task,
813
+ fields: ["created_by", "last_agent", "workflow_mode_confirmed_by", "tdd_confirmed_by"]
814
+ }
815
+ ];
816
+ for (const field of ["pending_transition", "workflow_mode_proposal", "verification_checkpoint"]) {
817
+ const value = task[field];
818
+ if (!value || typeof value !== "object" || Array.isArray(value)) continue;
819
+ const identityField = field === "pending_transition" ? "requested_by" : field === "workflow_mode_proposal" ? "proposed_by" : "recorded_by";
820
+ groups.push({ record: value, fields: [identityField] });
821
+ }
822
+ if (Array.isArray(task.workflow_mode_escalations)) {
823
+ for (const escalation of task.workflow_mode_escalations) {
824
+ if (!escalation || typeof escalation !== "object" || Array.isArray(escalation)) continue;
825
+ groups.push({ record: escalation, fields: ["raised_by"] });
826
+ }
827
+ }
828
+ const memoryProgress = task.memory_progress;
829
+ if (memoryProgress && typeof memoryProgress === "object" && !Array.isArray(memoryProgress)) {
830
+ const assessment = memoryProgress.architecture_assessment;
831
+ if (assessment && typeof assessment === "object" && !Array.isArray(assessment)) {
832
+ groups.push({ record: assessment, fields: ["recorded_by"] });
833
+ }
834
+ }
835
+ if (Array.isArray(task.spec_dependency_evidence)) {
836
+ for (const evidence of task.spec_dependency_evidence) {
837
+ if (!evidence || typeof evidence !== "object" || Array.isArray(evidence)) continue;
838
+ groups.push({ record: evidence, fields: ["satisfied_by"] });
839
+ }
840
+ }
841
+ if (Array.isArray(task.stage_history)) {
842
+ for (const entry of task.stage_history) {
843
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
844
+ groups.push({ record: entry, fields: ["agent"] });
845
+ }
846
+ }
847
+ return groups;
848
+ }
849
+ function migrateTaskAgentIdentities(task) {
850
+ return taskAgentFieldGroups(task).reduce(
851
+ (changed, group) => migrateAgentFields(group.record, group.fields) || changed,
852
+ false
853
+ );
854
+ }
855
+ function hasLegacyTaskAgentIdentities(task) {
856
+ return taskAgentFieldGroups(task).some(
857
+ ({ record, fields }) => fields.some((field) => {
858
+ const migrated = migratedAgentIdentity(record[field]);
859
+ return migrated !== void 0 && migrated !== record[field];
860
+ })
861
+ );
862
+ }
779
863
  function migrateStageHistory(task) {
780
864
  if (!Array.isArray(task.stage_history)) return false;
781
865
  let changed = false;
@@ -806,7 +890,8 @@ function migrateTaskWorkflowState(task) {
806
890
  const legacyRequestedAt = Array.isArray(task.stage_history) ? [...task.stage_history].reverse().find(
807
891
  (entry) => entry && typeof entry === "object" && !Array.isArray(entry) && entry.stage === "WAITING_CONFIRM"
808
892
  ) : null;
809
- let changed = migrateStageHistory(task);
893
+ let changed = migrateTaskAgentIdentities(task);
894
+ changed = migrateStageHistory(task) || changed;
810
895
  if (legacyStatus) {
811
896
  task.status = LEGACY_STAGE_MAP[legacyStatus];
812
897
  changed = true;
@@ -858,7 +943,7 @@ function migrateTaskWorkflowState(task) {
858
943
  return changed;
859
944
  }
860
945
  function migrateSessionBehavior(session) {
861
- let changed = false;
946
+ let changed = migrateAgentFields(session, ["agent", "last_agent"]);
862
947
  const legacyMode = session.confirm_mode;
863
948
  const legacyLite = legacyMode === "lite";
864
949
  if (!["approve", "guard", "confirm", "auto"].includes(String(session.approval_mode ?? ""))) {
@@ -969,7 +1054,7 @@ async function hasLegacyWorkflowState(cwd) {
969
1054
  if (!await pathExists(filePath)) continue;
970
1055
  const task = await readJsonRecord(filePath);
971
1056
  if (!task) continue;
972
- 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(
1057
+ if (hasLegacyTaskAgentIdentities(task) || 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(
973
1058
  (entry) => entry && typeof entry === "object" && !Array.isArray(entry) && isLegacyStage(entry.stage)
974
1059
  )) {
975
1060
  return true;
@@ -978,7 +1063,12 @@ async function hasLegacyWorkflowState(cwd) {
978
1063
  for (const filePath of await sessionFiles(cwd)) {
979
1064
  const session = await readJsonRecord(filePath);
980
1065
  if (!session) continue;
981
- if (isLegacyStage(session.last_seen_stage) || "confirm_mode" in session) return true;
1066
+ if (["agent", "last_agent"].some((field) => {
1067
+ const migrated = migratedAgentIdentity(session[field]);
1068
+ return migrated !== void 0 && migrated !== session[field];
1069
+ }) || isLegacyStage(session.last_seen_stage) || "confirm_mode" in session) {
1070
+ return true;
1071
+ }
982
1072
  }
983
1073
  return false;
984
1074
  }
@@ -3062,7 +3152,7 @@ async function config() {
3062
3152
  {
3063
3153
  value: "auto",
3064
3154
  label: "auto \u2014 advance workflow stages automatically",
3065
- hint: "task closure remains explicit"
3155
+ hint: "only new post-verification code drift pauses for exact acceptance"
3066
3156
  }
3067
3157
  ]
3068
3158
  });