@pennixrv/trellis 0.7.1-beta.16 → 0.7.1-beta.18

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 (83) hide show
  1. package/dist/commands/teamwork/adapters/acp.d.ts +5 -0
  2. package/dist/commands/teamwork/adapters/acp.d.ts.map +1 -1
  3. package/dist/commands/teamwork/adapters/acp.js +19 -12
  4. package/dist/commands/teamwork/adapters/acp.js.map +1 -1
  5. package/dist/commands/teamwork/adapters/codex.d.ts +1 -1
  6. package/dist/commands/teamwork/adapters/codex.d.ts.map +1 -1
  7. package/dist/commands/teamwork/adapters/codex.js +4 -13
  8. package/dist/commands/teamwork/adapters/codex.js.map +1 -1
  9. package/dist/commands/teamwork/artifact.d.ts +4 -1
  10. package/dist/commands/teamwork/artifact.d.ts.map +1 -1
  11. package/dist/commands/teamwork/artifact.js +35 -4
  12. package/dist/commands/teamwork/artifact.js.map +1 -1
  13. package/dist/commands/teamwork/batch.d.ts +6 -0
  14. package/dist/commands/teamwork/batch.d.ts.map +1 -1
  15. package/dist/commands/teamwork/batch.js +114 -2
  16. package/dist/commands/teamwork/batch.js.map +1 -1
  17. package/dist/commands/teamwork/context-loader.d.ts.map +1 -1
  18. package/dist/commands/teamwork/context-loader.js +15 -3
  19. package/dist/commands/teamwork/context-loader.js.map +1 -1
  20. package/dist/commands/teamwork/coordinator.d.ts.map +1 -1
  21. package/dist/commands/teamwork/coordinator.js +4 -0
  22. package/dist/commands/teamwork/coordinator.js.map +1 -1
  23. package/dist/commands/teamwork/create.d.ts +1 -0
  24. package/dist/commands/teamwork/create.d.ts.map +1 -1
  25. package/dist/commands/teamwork/create.js +2 -1
  26. package/dist/commands/teamwork/create.js.map +1 -1
  27. package/dist/commands/teamwork/index.d.ts.map +1 -1
  28. package/dist/commands/teamwork/index.js +25 -4
  29. package/dist/commands/teamwork/index.js.map +1 -1
  30. package/dist/commands/teamwork/prompt.d.ts +4 -0
  31. package/dist/commands/teamwork/prompt.d.ts.map +1 -0
  32. package/dist/commands/teamwork/prompt.js +33 -0
  33. package/dist/commands/teamwork/prompt.js.map +1 -0
  34. package/dist/commands/teamwork/result.d.ts +52 -0
  35. package/dist/commands/teamwork/result.d.ts.map +1 -0
  36. package/dist/commands/teamwork/result.js +94 -0
  37. package/dist/commands/teamwork/result.js.map +1 -0
  38. package/dist/commands/teamwork/run.d.ts +1 -0
  39. package/dist/commands/teamwork/run.d.ts.map +1 -1
  40. package/dist/commands/teamwork/run.js +111 -21
  41. package/dist/commands/teamwork/run.js.map +1 -1
  42. package/dist/commands/teamwork/runtime.d.ts.map +1 -1
  43. package/dist/commands/teamwork/runtime.js +49 -2
  44. package/dist/commands/teamwork/runtime.js.map +1 -1
  45. package/dist/commands/teamwork/send.d.ts +1 -0
  46. package/dist/commands/teamwork/send.d.ts.map +1 -1
  47. package/dist/commands/teamwork/send.js +2 -1
  48. package/dist/commands/teamwork/send.js.map +1 -1
  49. package/dist/commands/teamwork/spawn.d.ts +6 -3
  50. package/dist/commands/teamwork/spawn.d.ts.map +1 -1
  51. package/dist/commands/teamwork/spawn.js +12 -28
  52. package/dist/commands/teamwork/spawn.js.map +1 -1
  53. package/dist/commands/teamwork/supervisor/inbox.d.ts +1 -0
  54. package/dist/commands/teamwork/supervisor/inbox.d.ts.map +1 -1
  55. package/dist/commands/teamwork/supervisor/inbox.js +25 -9
  56. package/dist/commands/teamwork/supervisor/inbox.js.map +1 -1
  57. package/dist/commands/teamwork/supervisor.d.ts +2 -0
  58. package/dist/commands/teamwork/supervisor.d.ts.map +1 -1
  59. package/dist/commands/teamwork/supervisor.js +1 -0
  60. package/dist/commands/teamwork/supervisor.js.map +1 -1
  61. package/dist/commands/teamwork/teammate-launch.d.ts +6 -0
  62. package/dist/commands/teamwork/teammate-launch.d.ts.map +1 -1
  63. package/dist/commands/teamwork/teammate-launch.js +24 -3
  64. package/dist/commands/teamwork/teammate-launch.js.map +1 -1
  65. package/dist/commands/teamwork/wait.d.ts +2 -0
  66. package/dist/commands/teamwork/wait.d.ts.map +1 -1
  67. package/dist/commands/teamwork/wait.js +44 -3
  68. package/dist/commands/teamwork/wait.js.map +1 -1
  69. package/dist/migrations/manifests/0.7.1-beta.17.json +9 -0
  70. package/dist/migrations/manifests/0.7.1-beta.18.json +9 -0
  71. package/dist/templates/common/bundled-skills/trellis-solution-design-and-convergence/workflow.md +2 -0
  72. package/dist/templates/common/bundled-skills/trellis-task-implementation/workflow.md +2 -0
  73. package/dist/templates/common/bundled-skills/trellis-teamwork/SKILL.md +5 -2
  74. package/dist/templates/common/bundled-skills/trellis-teamwork/references/coordination-modes.md +4 -0
  75. package/dist/templates/common/bundled-skills/trellis-teamwork/references/teammate-configuration-matrix.md +4 -2
  76. package/dist/templates/common/bundled-skills/trellis-teamwork/references/teammate-processes.md +31 -6
  77. package/dist/templates/common/bundled-skills/trellis-teamwork/references/teamwork-plan-construction.md +28 -3
  78. package/dist/templates/trellis/agents/profiles/research.md +4 -4
  79. package/dist/templates/trellis/agents/teammate.md +4 -75
  80. package/dist/templates/trellis/scripts/common/task_workflow.py +102 -2
  81. package/dist/templates/trellis/scripts/task.py +9 -0
  82. package/dist/templates/trellis/workflow.md +7 -0
  83. package/package.json +2 -2
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import argparse
6
6
  import contextlib
7
+ import copy
7
8
  from datetime import datetime, timezone
8
9
  import hashlib
9
10
  import json
@@ -445,6 +446,31 @@ def _show(state: dict) -> int:
445
446
  return 0
446
447
 
447
448
 
449
+ def _require_acceptance_coverage(task_dir: Path, contract_ref: str) -> None:
450
+ _artifact_digest(task_dir, [contract_ref])
451
+ lines = (task_dir / contract_ref).read_text(encoding="utf-8").splitlines()
452
+ heading = next((index for index, line in enumerate(lines) if line.strip() == "## 验收覆盖"), None)
453
+ headers = ["接受条件", "工作包", "Owner", "验证入口", "交付证据", "停止条件"]
454
+ if heading is not None:
455
+ section = []
456
+ for line in lines[heading + 1:]:
457
+ if line.startswith("#"):
458
+ break
459
+ if line.strip():
460
+ section.append(line.strip())
461
+ rows = [line.strip("|").split("|") for line in section if line.startswith("|") and line.endswith("|")]
462
+ rows = [[cell.strip() for cell in row] for row in rows]
463
+ if (
464
+ len(rows) >= 3
465
+ and rows[0] == headers
466
+ and len(rows[1]) == len(headers)
467
+ and all(re.fullmatch(r":?-{3,}:?", cell) for cell in rows[1])
468
+ and all(len(row) == len(headers) and all(row) for row in rows[2:])
469
+ ):
470
+ return
471
+ raise TaskWorkflowError("TASK_PHASE_COVERAGE_REQUIRED", "implementation contract requires ## 验收覆盖 with nonempty rows: " + " | ".join(headers))
472
+
473
+
448
474
  def _update(args: argparse.Namespace, task_dir: Path, data: dict, state: dict) -> int:
449
475
  action = args.phase_action
450
476
  child_dispositions: dict[str, str] = {}
@@ -531,6 +557,8 @@ def _update(args: argparse.Namespace, task_dir: Path, data: dict, state: dict) -
531
557
  raise TaskWorkflowError("TASK_PHASE_INVALID_TRANSITION", "Solution Design and Convergence Phase must remain active for pending_user_decision or deferred; accept implementation_contract, analysis_conclusion, or no_go")
532
558
  if result_ref not in record["artifact_refs"]:
533
559
  raise TaskWorkflowError("TASK_PHASE_RESULT_NOT_BOUND", "--result-ref must be included in the current phase artifact refs")
560
+ if current == PHASE_IDS[2] and outcome == "implementation_contract":
561
+ _require_acceptance_coverage(task_dir, result_ref)
534
562
  if current == PHASE_IDS[3]:
535
563
  authorization = state.get("implementation_authorization")
536
564
  if not isinstance(authorization, dict) or authorization.get("task_route_revision") != state["task_route_revision"]:
@@ -568,17 +596,38 @@ def _update(args: argparse.Namespace, task_dir: Path, data: dict, state: dict) -
568
596
  _advance(state, current)
569
597
  elif action == "return":
570
598
  target = _required_text(args.target_phase, "--target-phase")
599
+ return_kind = args.return_kind
600
+ execution_rework = return_kind == "execution_rework"
571
601
  if target not in state["phase_ids"] or (
572
602
  state["phase_ids"].index(target) >= state["phase_ids"].index(current)
573
- and not ((reviewed_delivery or accepted_design_return) and target == current)
603
+ and not ((reviewed_delivery or accepted_design_return or execution_rework) and target == current)
574
604
  ):
575
605
  raise TaskWorkflowError("TASK_PHASE_INVALID_TRANSITION", "return target must be an earlier phase in the current route")
576
606
  reason = _required_text(args.reason, "--reason")
577
607
  evidence = _refs(args.evidence_ref, "--evidence-ref", required=True)
608
+ authorization = state.get("implementation_authorization")
609
+ admission = state.get("route_dependency_admission")
610
+ if execution_rework:
611
+ if current not in PHASE_IDS[3:] or target not in PHASE_IDS[3:]:
612
+ raise TaskWorkflowError("TASK_PHASE_INVALID_TRANSITION", "execution_rework is limited to implementation and delivery phases")
613
+ candidate = copy.deepcopy(data)
614
+ candidate_state = candidate["meta"]["task_workflow"]
615
+ _invalidate_from(candidate_state, target, keep_first=True)
616
+ candidate_state["implementation_authorization"] = authorization
617
+ _require_current_accepted_artifacts(task_dir, candidate_state)
618
+ candidate_state["current_phase_id"] = PHASE_IDS[3]
619
+ candidate_state["phases"][PHASE_IDS[3]]["status"] = "active"
620
+ if not implementation_start_ready(candidate, task_dir):
621
+ raise TaskWorkflowError("TASK_PHASE_IMPLEMENTATION_NOT_AUTHORIZED", "execution_rework requires the unchanged authorized implementation contract")
622
+ if admission is not None and (admission.get("admission_state") != "ready" or admission.get("task_route_revision") != state["task_route_revision"]):
623
+ raise TaskWorkflowError("TASK_PHASE_ADMISSION_REQUIRED", "execution_rework requires current ready dependency admission")
578
624
  state["input_revision"] = _increment(state["input_revision"])
579
625
  _invalidate_from(state, target, keep_first=True)
626
+ if execution_rework:
627
+ state["implementation_authorization"] = authorization
628
+ state["route_dependency_admission"] = admission
580
629
  state["phases"][target]["input_revision"] = state["input_revision"]
581
- state["return_record"] = {"from": current, "to": target, "reason": reason, "evidence_refs": evidence, "task_route_revision": state["task_route_revision"]}
630
+ state["return_record"] = {"from": current, "to": target, "return_kind": return_kind, "authorization_retained": execution_rework, "reason": reason, "evidence_refs": evidence, "task_route_revision": state["task_route_revision"]}
582
631
  elif action == "route":
583
632
  phases = _validate_route(args.phase_ids)
584
633
  task_kind = args.task_kind if args.task_kind is not None else state["task_kind"]
@@ -690,6 +739,7 @@ def _update(args: argparse.Namespace, task_dir: Path, data: dict, state: dict) -
690
739
  raise TaskWorkflowError("TASK_PHASE_STALE_ARTIFACT", "Solution Design and Convergence Phase implementation contract changed after acceptance; refresh and accept the current artifact before authorization")
691
740
  elif contract_ref not in state.get("skip_records", {}).get(PHASE_IDS[2], {}).get("evidence_refs", []):
692
741
  raise TaskWorkflowError("TASK_PHASE_CONTRACT_NOT_ACCEPTED", "when Solution Design and Convergence Phase is omitted, the implementation contract must be among its route evidence")
742
+ _require_acceptance_coverage(task_dir, contract_ref)
693
743
  state["implementation_authorization"] = {"contract_ref": contract_ref, "contract_revision": _artifact_digest(task_dir, [contract_ref]), "approval_ref": approval_ref, "task_route_revision": state["task_route_revision"]}
694
744
  if current == PHASE_IDS[2]:
695
745
  _advance(state, current)
@@ -1011,6 +1061,56 @@ def cmd_cancel_task(args: argparse.Namespace) -> int:
1011
1061
  return 1
1012
1062
 
1013
1063
 
1064
+ def cmd_reopen_task(args: argparse.Namespace) -> int:
1065
+ try:
1066
+ task_dir = _task_dir(args.task_dir)
1067
+ with _task_lock(task_dir):
1068
+ data, meta = _load(task_dir)
1069
+ previous_status = data.get("status")
1070
+ if previous_status not in {"completed", "cancelled"}:
1071
+ raise TaskWorkflowError("TASK_PHASE_REOPEN_INVALID_STATUS", "only completed or cancelled tasks can be reopened")
1072
+ reason = _required_text(args.reason, "--reason")
1073
+ evidence = _refs(args.evidence_ref, "--evidence-ref", required=True)
1074
+ _assert_delegated_resources_closed(task_dir, data)
1075
+ recoveries = meta.setdefault("task_lifecycle_recoveries", [])
1076
+ if not isinstance(recoveries, list) or any(not isinstance(record, dict) for record in recoveries):
1077
+ raise TaskWorkflowError("TASK_PHASE_TASK_INVALID", "task lifecycle recovery history is malformed")
1078
+ recovery = {
1079
+ "from_status": previous_status,
1080
+ "to_status": "planning",
1081
+ "reason": reason,
1082
+ "evidence_refs": evidence,
1083
+ "reopened_at": datetime.now(timezone.utc).isoformat(),
1084
+ "terminal_completed_at": data.get("completedAt"),
1085
+ "terminal_cancellation": copy.deepcopy(meta.get("task_cancellation")),
1086
+ }
1087
+ state = meta.get("task_workflow")
1088
+ if state is not None:
1089
+ state = _state(meta)
1090
+ state["implementation_authorization"] = None
1091
+ state["route_dependency_admission"] = None
1092
+ state["closeout_disposition"] = None
1093
+ recoveries.append(recovery)
1094
+ data["status"] = "planning"
1095
+ data["completedAt"] = None
1096
+ from .task_move import relocate_task
1097
+ from .active_task import clear_task_from_sessions
1098
+ from .task_continuity import clear_task_runtime_records
1099
+
1100
+ repo_root = get_repo_root()
1101
+ # Relocation rewrites runtime paths; terminal runtime must be discarded first.
1102
+ for runtime_dir in (task_dir, repo_root / ".trellis/tasks/planning" / task_dir.name):
1103
+ clear_task_from_sessions(str(runtime_dir), repo_root)
1104
+ if not clear_task_runtime_records(repo_root, runtime_dir.relative_to(repo_root).as_posix()):
1105
+ raise TaskWorkflowError("TASK_PHASE_RUNTIME_CLEANUP_FAILED", "could not discard terminal task runtime; task remains terminal")
1106
+ task_dir = relocate_task(repo_root, task_dir, data)
1107
+ print(json.dumps({"status": "planning", "task_lifecycle_recovery": recovery, "runtime_records_cleared": True}, ensure_ascii=False))
1108
+ return 0
1109
+ except TaskWorkflowError as error:
1110
+ print(f"{error.code}: {error}", file=sys.stderr)
1111
+ return 1
1112
+
1113
+
1014
1114
  def task_closeout_readiness(data: dict, disposition: str) -> str | None:
1015
1115
  if "meta" in data and not isinstance(data["meta"], dict):
1016
1116
  return "task metadata is malformed"
@@ -14,6 +14,7 @@ Usage:
14
14
  python3 task.py workflow <id>|--clear # Set/clear per-task workflow selection
15
15
  python3 task.py phase <action> <task-dir> # Read/update task phase workflow state
16
16
  python3 task.py cancel <task-dir> --reason <text> --evidence-ref <ref> # Cancel task lifecycle
17
+ python3 task.py reopen <task-dir> --reason <text> --evidence-ref <ref> # Restore terminal task to planning
17
18
  python3 task.py set-branch <dir> <branch> # Set git branch
18
19
  python3 task.py set-base-branch <dir> <branch> # Set PR target branch
19
20
  python3 task.py set-scope <dir> <scope> # Set scope for PR title
@@ -79,6 +80,7 @@ from common.task_context import (
79
80
  from common.task_workflow import (
80
81
  TaskWorkflowError,
81
82
  cmd_cancel_task,
83
+ cmd_reopen_task,
82
84
  cmd_complete_task,
83
85
  cmd_delegate_start,
84
86
  cmd_phase,
@@ -779,6 +781,7 @@ def main() -> int:
779
781
  p_phase.add_argument("--route-reason", help="Reason when initialization omits one or more phases")
780
782
  p_phase.add_argument("--route-evidence-ref", action="append", help="Evidence supporting an abbreviated initial route")
781
783
  p_phase.add_argument("--target-phase", help="Earlier phase to revisit, or first affected phase for route change")
784
+ p_phase.add_argument("--return-kind", choices=("contract_change", "execution_rework"), default="contract_change", help="Contract change invalidates authorization; execution rework retains a verified unchanged contract")
782
785
  p_phase.add_argument("--artifact-ref", action="append", help="Task-relative file in the current phase output")
783
786
  p_phase.add_argument("--artifact-revision", help="Current artifact_revision required for acceptance")
784
787
  p_phase.add_argument("--result-ref", help="Task result artifact reference")
@@ -803,6 +806,11 @@ def main() -> int:
803
806
  p_cancel.add_argument("--reason", required=True, help="Why the task is cancelled")
804
807
  p_cancel.add_argument("--evidence-ref", action="append", required=True, help="Task-relative cancellation evidence")
805
808
 
809
+ p_reopen = subparsers.add_parser("reopen", help="Recover a terminal task to planning without implementation authorization")
810
+ p_reopen.add_argument("task_dir", help="Task directory or task name")
811
+ p_reopen.add_argument("--reason", required=True, help="Why the same task must continue")
812
+ p_reopen.add_argument("--evidence-ref", action="append", required=True, help="Recovery decision evidence")
813
+
806
814
  # set-branch
807
815
  p_branch = subparsers.add_parser("set-branch", help="Set git branch")
808
816
  p_branch.add_argument("dir", help="Task directory")
@@ -880,6 +888,7 @@ def main() -> int:
880
888
  "delegate-start": cmd_delegate_start,
881
889
  "complete": cmd_complete_task,
882
890
  "cancel": cmd_cancel_task,
891
+ "reopen": cmd_reopen_task,
883
892
  "set-branch": cmd_set_branch,
884
893
  "set-base-branch": cmd_set_base_branch,
885
894
  "set-scope": cmd_set_scope,
@@ -30,12 +30,19 @@ python3 ./.trellis/scripts/task.py validate <task>
30
30
  python3 ./.trellis/scripts/task.py start <task>
31
31
  python3 ./.trellis/scripts/task.py complete <task>
32
32
  python3 ./.trellis/scripts/task.py cancel <task> --reason "<reason>" --evidence-ref <reference>
33
+ python3 ./.trellis/scripts/task.py reopen <task> --reason "<reason>" --evidence-ref <reference>
33
34
  ```
34
35
 
35
36
  使用 `phase artifact`、`accept`、`skip`、`wait`、`block`、`resume`、`return`、`route` 和 `authorize` 执行对应的阶段操作。准确参数以 `task.py --help` 为准。跳过阶段必须提供理由和证据;目标探索与需求核对阶段不可省略。
36
37
 
37
38
  只有阶段路线已到达获准的实施入口时,`task.py start` 才会将任务从 `planning` 改为 `in_progress`。分析任务可在交付审查与现场收尾阶段后仍处于 `planning` 时完成。`complete` 将任务移入 `completed/YYYY-MM/<task>`,`cancel` 将任务移入 `cancelled/<task>`,并在同一事务中清除任务的运行时会话指针。
38
39
 
40
+ 工作流阶段回流与原生任务生命周期恢复分别操作。`completed` 或 `cancelled` 任务需要在原线程继续时,先用 `reopen` 恢复到 `planning`,保留终态事实、取消原因和恢复证据,清除旧运行态、实施授权和依赖准入;工作流阶段保持原记录。随后用 `phase return` 回到最早受影响阶段。实现任务重新接受合同、取得新的实施授权并核验准入后,才由 `start` 进入 `in_progress`。`phase return` 不能直接修改终态任务。
41
+
42
+ `phase return` 默认采用 `contract_change`,使受影响阶段接受和实施授权失效。合同不变的局部实施或证据返工可显式使用 `--return-kind execution_rework`,目标限于实施或交付阶段,可重入当前阶段;CLI 核验需求、已接受合同、授权摘要、路线 revision 和已声明依赖准入仍有效后,保留授权,清理目标阶段及下游的接受事实。不能以此修改目标、合同或绕过待决依赖。普通局部修复可直接在当前活动阶段完成,无需为了记录进展而回流。
43
+
44
+ 新接受的 `implementation_contract`(以及省略设计阶段时新授权的合同)必须包含 `## 验收覆盖` 表,表头固定为 `接受条件 | 工作包 | Owner | 验证入口 | 交付证据 | 停止条件`,至少一行且各单元格非空。CLI 负责结构检查,阶段 Skill 负责逐条核对已接受条件的语义覆盖。既有接受和授权不会因本次升级自动失效;再次接受或授权时执行新检查。
45
+
39
46
  旧布局须先预览 `task.py migrate-layout`,核对后执行 `task.py migrate-layout --apply`;旧 `archive/YYYY-MM` 中已完成的任务保留原月份,其他任务按真实状态放置。迁移会同步 JSON/JSONL 机器引用、会话指针和运行态索引,保留任务身份与父子关系,不重授历史授权。未完成事务会阻止普通任务命令,须用 `task.py migrate-layout --recover` 恢复;输入被外部改动时停止并保留现场。路径或产物内容改变导致接受证据失效时,须经原生阶段接受或 continuity 核对重新收敛。
40
47
 
41
48
  在支持原生实施或检查 agent 的平台上,任务上下文清单(`implement.jsonl`、`check.jsonl`)用于提供相关规范和研究资料。只有这些 agent 会读取清单时才需要维护;清单不能替代任务资产或阶段证据。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pennixrv/trellis",
3
- "version": "0.7.1-beta.16",
3
+ "version": "0.7.1-beta.18",
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",
@@ -35,7 +35,7 @@
35
35
  "inquirer": "^9.3.7",
36
36
  "undici": "^6.21.0",
37
37
  "zod": "^4.4.2",
38
- "@pennixrv/trellis-core": "0.7.1-beta.16"
38
+ "@pennixrv/trellis-core": "0.7.1-beta.18"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@eslint/js": "^9.18.0",