@pennixrv/trellis 0.7.1-beta.12 → 0.7.1-beta.13

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.
@@ -64,15 +64,39 @@ export async function withLock(lockFile, fn, opts) {
64
64
  }
65
65
  }
66
66
  async function checkAndStealStale(lockFile) {
67
+ const recoveryFile = `${lockFile}.recovery`;
68
+ let recoveryFd;
69
+ try {
70
+ recoveryFd = fs.openSync(recoveryFile, "wx");
71
+ }
72
+ catch (error) {
73
+ if (error.code === "EEXIST")
74
+ return false;
75
+ throw error;
76
+ }
77
+ // Serialize stale recovery so a second reaper cannot unlink a new holder.
78
+ try {
79
+ return stealVerifiedDeadLock(lockFile);
80
+ }
81
+ finally {
82
+ fs.closeSync(recoveryFd);
83
+ fs.unlinkSync(recoveryFile);
84
+ }
85
+ }
86
+ function stealVerifiedDeadLock(lockFile) {
67
87
  let holderPid = 0;
68
88
  try {
69
- holderPid = Number(fs.readFileSync(lockFile, "utf-8").trim());
89
+ const content = fs.readFileSync(lockFile, "utf-8").trim();
90
+ // An empty file may belong to a live holder between creation and PID write.
91
+ if (!/^[1-9][0-9]*$/.test(content))
92
+ return false;
93
+ holderPid = Number(content);
70
94
  }
71
95
  catch {
72
96
  // Lock vanished while we checked — let outer loop retry openSync.
73
97
  return false;
74
98
  }
75
- if (!holderPid || !pidAlive(holderPid)) {
99
+ if (Number.isSafeInteger(holderPid) && !pidAlive(holderPid)) {
76
100
  try {
77
101
  fs.unlinkSync(lockFile);
78
102
  process.stderr.write(`[Teamwork Session lock] stale lock from dead pid ${holderPid} stolen at ${lockFile}\n`);
@@ -89,8 +113,8 @@ function pidAlive(pid) {
89
113
  process.kill(pid, 0);
90
114
  return true;
91
115
  }
92
- catch {
93
- return false;
116
+ catch (error) {
117
+ return error.code !== "ESRCH";
94
118
  }
95
119
  }
96
120
  function sleep(ms) {
@@ -1 +1 @@
1
- {"version":3,"file":"lock.js","sourceRoot":"","sources":["../../../../src/commands/teamwork/store/lock.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"lock.js","sourceRoot":"","sources":["../../../../src/commands/teamwork/store/lock.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ {
2
+ "version": "0.7.1-beta.13",
3
+ "description": "补齐阶段返修、委派准入与 Teamwork 锁竞争保护",
4
+ "breaking": false,
5
+ "recommendMigrate": false,
6
+ "changelog": "允许待授权的已接受实施合同显式返修;阻止暂停实施、终态子任务及交接围栏内的委派启动;Teamwork CLI/Core 保留未知锁和权限失败,串行核验与回收死锁。",
7
+ "migrations": [],
8
+ "notes": "Run the native update to refresh managed scripts and phase Skills. Task and handoff history are unchanged. Interrupted lock recovery guards are preserved for explicit inspection."
9
+ }
@@ -6,6 +6,8 @@
6
6
 
7
7
  判断当前结果:分析交付形成 `analysis_conclusion`;不继续形成 `no_go`;延期形成带责任方、触发条件和恢复位置的 `deferred`,留在解决方案设计与收敛阶段;实施路线形成 `implementation_contract`,明确目标、范围和接受条件,并按路线需要补足实施与核验信息。只有交付物具有独立实施或集成边界时才拆分工作包,并说明推进和协作所需的依赖、顺序、责任或集成信息;单一交付不人为拆分工作包。解决方案设计与收敛阶段的结果接受与实施授权分开记录;只有合同及明确批准均有效时,Trellis CLI 才允许进入任务实施与测试验证阶段。
8
8
 
9
+ 合同结果已接受、实施尚未授权时,发现需要返修,应通过 `task.py phase return` 回到本阶段或更早受影响阶段,记录理由和证据。受影响的接受与授权随之失效;修订后重新接受当前产物,再核验实施授权。
10
+
9
11
  逐项决定回答后,将已决目标、候选依据、接口与数据流、owner、依赖顺序、实施和验证,以及适用的迁移、发布、资源和失败恢复安排组织为一个完整方案。核对需求覆盖、跨产物一致性、可实施性和可验收性;有净收益时使用有界反例或压力测试,先修复可查缺口。向用户展示可识别的整体方案和实质剩余取舍后,再记录当前 revision 的接受及实施授权。逐项答复不自动等于整体批准;用户已充分明确批准整体方案时不重复索取。目标或候选问题回最早受影响阶段,局部方案问题在本阶段解决,不增设外部封口状态。
10
12
 
11
13
  研究、原型和协作证据写回现有阶段产物、research 或 execution 自然落点,注明来源与版本、事实和假设、适用性、限制及决定影响。证据足够即停止扩搜;工具、报告或模板不能替代用户决定和 Trellis 原生接受事实。
@@ -2,6 +2,8 @@
2
2
 
3
3
  开始前核验当前 `implementation_contract`、实施授权、路线 revision、必要依赖和执行基线。使用 `trellis-before-dev` 读取目标仓库实际适用的规范;按组件 owner 合同选择主工作树或已批准的独立写入路线。调用项目任务 `start` 只在实施授权有效时将任务置为 `in_progress`。
4
4
 
5
+ 实施阶段为 `blocked` 或 `waiting_user` 时,先解决阻塞并经原生 `phase resume` 恢复,再重新核验准入。`delegate-start` 同时核验父任务会话归属和子任务状态;父任务交接尚未消费、当前会话不持有父任务,或子任务已完成、取消时,不启动或复用委派执行。
6
+
5
7
  按合同的依赖和优先级选取当前工作包,实施满足目标的最小变更,并运行与风险和影响相称的局部验证。主工作树主代理核对来源、revision、Git 基线、差异和结果后接受工作包。多包任务按合同集成;单一交付不人为拆分。Trellis Teamwork 仅用于冻结计划下的只读比较或评议,不能改写任务状态或接受实施结果。实际写入委派依父任务规定使用 Trellis 受控入口与 Paseo;各组件源码仍在 owner 仓库内修改。
6
8
 
7
9
  局部失败、暂时不可用或当前状态已满足接受条件时,先在任务实施与测试验证阶段中处理。依赖或资源暂不可用且现场安全时记录恢复条件;未授权不产生外部副作用;未知现场先停止并核验。合同允许未运行验证且风险可接受时,记录原因、影响和替代证据。替代路线若改变授权、成本、隐私或接受条件,回到解决方案设计与收敛阶段;目标、范围变化按影响回到目标探索与需求核对阶段或决策空间构造与归并阶段。只有合同范围内工作包均已接受、适用必需验证完成或有有效未运行处置、按范围和风险需要的集成验证通过,才将交付候选交由交付审查与现场收尾阶段;仅当路线实际创建或委派资源时,才核验其处置事实。
@@ -133,6 +133,10 @@ def implementation_start_ready(data: dict, task_dir: Path) -> bool:
133
133
  phase_ids = state.get("phase_ids")
134
134
  if not isinstance(phase_ids, list) or PHASE_IDS[3] not in phase_ids:
135
135
  return False
136
+ phases = state.get("phases")
137
+ implementation = phases.get(PHASE_IDS[3]) if isinstance(phases, dict) else None
138
+ if not isinstance(implementation, dict) or implementation.get("status") != "active":
139
+ return False
136
140
  if state.get("task_kind") not in {"implementation", "combined"}:
137
141
  return False
138
142
  authorization = state.get("implementation_authorization")
@@ -460,11 +464,12 @@ def _update(args: argparse.Namespace, task_dir: Path, data: dict, state: dict) -
460
464
  current, record = PHASE_IDS[4], state["phases"][PHASE_IDS[4]]
461
465
  else:
462
466
  current, record = _phase(state)
467
+ accepted_design_return = action == "return" and current == PHASE_IDS[2] and record.get("status") == "accepted"
463
468
  returning_to_requirements = action == "return" and args.target_phase == PHASE_IDS[0]
464
469
  rerouting_to_requirements = action == "route" and args.target_phase == PHASE_IDS[0]
465
470
  if current != PHASE_IDS[0] and not returning_to_requirements and not rerouting_to_requirements:
466
471
  _require_current_requirements_confirmation(task_dir, state)
467
- if action not in {"show", "resume", "route", "authorize", "admission"} and record.get("status") != "active" and not reviewed_delivery:
472
+ if action not in {"show", "resume", "route", "authorize", "admission"} and record.get("status") != "active" and not reviewed_delivery and not accepted_design_return:
468
473
  raise TaskWorkflowError("TASK_PHASE_INVALID_TRANSITION", "only the active phase can be changed")
469
474
 
470
475
  if action == "artifact":
@@ -565,7 +570,7 @@ def _update(args: argparse.Namespace, task_dir: Path, data: dict, state: dict) -
565
570
  target = _required_text(args.target_phase, "--target-phase")
566
571
  if target not in state["phase_ids"] or (
567
572
  state["phase_ids"].index(target) >= state["phase_ids"].index(current)
568
- and not (reviewed_delivery and target == current)
573
+ and not ((reviewed_delivery or accepted_design_return) and target == current)
569
574
  ):
570
575
  raise TaskWorkflowError("TASK_PHASE_INVALID_TRANSITION", "return target must be an earlier phase in the current route")
571
576
  reason = _required_text(args.reason, "--reason")
@@ -760,6 +765,11 @@ def cmd_delegate_start(args: argparse.Namespace) -> int:
760
765
  child_data, child_meta = _load(child_dir)
761
766
  if child_data.get("parent") != parent_dir.name or child_dir.name not in parent_data.get("children", []):
762
767
  raise TaskWorkflowError("TASK_DELEGATE_PARENT_LINK_INVALID", "parent and child task links do not match")
768
+ from .task_continuity import assert_task_mutation_allowed
769
+
770
+ assert_task_mutation_allowed(repo_root, parent_dir, parent_data)
771
+ if child_data.get("status") not in {"planning", "in_progress"}:
772
+ raise TaskWorkflowError("TASK_DELEGATE_CHILD_TERMINAL", "only a planning or in-progress child task can be delegated")
763
773
  if parent_data.get("status") != "in_progress" or not implementation_start_ready(parent_data, parent_dir):
764
774
  raise TaskWorkflowError("TASK_DELEGATE_IMPLEMENTATION_NOT_AUTHORIZED", "parent task is not at an authorized Task Implementation and Test Verification Phase entry")
765
775
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pennixrv/trellis",
3
- "version": "0.7.1-beta.12",
3
+ "version": "0.7.1-beta.13",
4
4
  "description": "AI capabilities grow like ivy — Trellis provides the structure to guide them along a disciplined path",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -34,7 +34,7 @@
34
34
  "inquirer": "^9.3.7",
35
35
  "undici": "^6.21.0",
36
36
  "zod": "^4.4.2",
37
- "@pennixrv/trellis-core": "0.7.1-beta.12"
37
+ "@pennixrv/trellis-core": "0.7.1-beta.13"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@eslint/js": "^9.18.0",