@pennixrv/trellis 0.7.1-beta.4 → 0.7.1-beta.6

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 (49) hide show
  1. package/dist/commands/init.d.ts.map +1 -1
  2. package/dist/commands/init.js +14 -6
  3. package/dist/commands/init.js.map +1 -1
  4. package/dist/commands/task-progress.d.ts +1 -0
  5. package/dist/commands/task-progress.d.ts.map +1 -1
  6. package/dist/commands/task-progress.js +67 -25
  7. package/dist/commands/task-progress.js.map +1 -1
  8. package/dist/commands/update.js +2 -2
  9. package/dist/commands/update.js.map +1 -1
  10. package/dist/constants/paths.d.ts +3 -10
  11. package/dist/constants/paths.d.ts.map +1 -1
  12. package/dist/constants/paths.js +4 -13
  13. package/dist/constants/paths.js.map +1 -1
  14. package/dist/migrations/manifests/0.7.1-beta.5.json +9 -0
  15. package/dist/migrations/manifests/0.7.1-beta.6.json +10 -0
  16. package/dist/templates/claude/hooks/statusline.py +9 -4
  17. package/dist/templates/common/bundled-skills/trellis-meta/SKILL.md +1 -1
  18. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/add-project-local-conventions.md +1 -1
  19. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-context-loading.md +5 -5
  20. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-task-lifecycle.md +3 -2
  21. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/overview.md +1 -1
  22. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/context-injection.md +1 -1
  23. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/task-system.md +8 -2
  24. package/dist/templates/common/bundled-skills/trellis-teamwork/references/teammate-processes.md +2 -2
  25. package/dist/templates/common/bundled-skills/trellis-teamwork/references/teamwork-plan-construction.md +1 -1
  26. package/dist/templates/omp/extensions/trellis/index.ts.txt +1 -1
  27. package/dist/templates/opencode/lib/session-utils.js +301 -209
  28. package/dist/templates/snow/SNOW.md +1 -1
  29. package/dist/templates/trellis/agents/teammate.md +5 -5
  30. package/dist/templates/trellis/index.d.ts +3 -0
  31. package/dist/templates/trellis/index.d.ts.map +1 -1
  32. package/dist/templates/trellis/index.js +6 -0
  33. package/dist/templates/trellis/index.js.map +1 -1
  34. package/dist/templates/trellis/scripts/add_session.py +1 -1
  35. package/dist/templates/trellis/scripts/common/active_task.py +3 -47
  36. package/dist/templates/trellis/scripts/common/paths.py +9 -25
  37. package/dist/templates/trellis/scripts/common/safe_commit.py +8 -8
  38. package/dist/templates/trellis/scripts/common/task_context.py +11 -51
  39. package/dist/templates/trellis/scripts/common/task_continuity.py +53 -45
  40. package/dist/templates/trellis/scripts/common/task_layout.py +80 -0
  41. package/dist/templates/trellis/scripts/common/task_move.py +345 -0
  42. package/dist/templates/trellis/scripts/common/task_store.py +28 -55
  43. package/dist/templates/trellis/scripts/common/task_utils.py +23 -19
  44. package/dist/templates/trellis/scripts/common/task_workflow.py +28 -20
  45. package/dist/templates/trellis/scripts/common/tasks.py +6 -5
  46. package/dist/templates/trellis/scripts/hooks/linear_sync.py +13 -3
  47. package/dist/templates/trellis/scripts/task.py +41 -23
  48. package/dist/templates/trellis/workflow.md +4 -2
  49. package/package.json +2 -2
@@ -19,6 +19,7 @@ from pathlib import Path
19
19
  from .io import describe_json_read_failure, read_json_checked
20
20
  from .paths import FILE_TASK_JSON
21
21
  from .types import TaskInfo
22
+ from .task_layout import iter_task_dirs, task_status_dir
22
23
 
23
24
 
24
25
  def load_task(task_dir: Path) -> TaskInfo | None:
@@ -72,14 +73,14 @@ def iter_active_tasks(tasks_dir: Path) -> Iterator[TaskInfo]:
72
73
  Yields:
73
74
  TaskInfo for each valid task.
74
75
  """
75
- if not tasks_dir.is_dir():
76
- return
76
+ from .task_move import require_settled_layout
77
77
 
78
- for d in sorted(tasks_dir.iterdir()):
79
- if not d.is_dir():
80
- continue
78
+ require_settled_layout(tasks_dir.parent.parent)
79
+ for d in iter_task_dirs(tasks_dir):
81
80
  info = load_task(d)
82
81
  if info is not None:
82
+ if info.status != task_status_dir(d, tasks_dir):
83
+ print(f"[WARN] Task status/directory mismatch: {d}", file=sys.stderr)
83
84
  yield info
84
85
 
85
86
 
@@ -9,7 +9,7 @@ Usage (called automatically by task.py hooks):
9
9
  python3 .trellis/scripts/hooks/linear_sync.py complete
10
10
 
11
11
  Manual usage:
12
- TASK_JSON_PATH=.trellis/tasks/<name>/task.json python3 .trellis/scripts/hooks/linear_sync.py sync
12
+ TASK_JSON_PATH=.trellis/tasks/<task-dir>/task.json python3 .trellis/scripts/hooks/linear_sync.py sync
13
13
 
14
14
  Environment:
15
15
  TASK_JSON_PATH - Absolute path to task.json (set by task.py)
@@ -211,8 +211,18 @@ def _resolve_parent_linear_issue(task: dict) -> str | None:
211
211
  return None
212
212
 
213
213
  current_task_dir = Path(task_json_path).parent
214
- tasks_dir = current_task_dir.parent
215
- parent_json = tasks_dir / parent_name / "task.json"
214
+ scripts_dir = Path(__file__).resolve().parents[1]
215
+ if str(scripts_dir) not in sys.path:
216
+ sys.path.insert(0, str(scripts_dir))
217
+ from common.paths import get_tasks_dir
218
+ from common.task_utils import find_task_by_name
219
+ workflow_dir = next((parent for parent in current_task_dir.parents if parent.name == ".trellis"), None)
220
+ if workflow_dir is None:
221
+ return None
222
+ parent_dir = find_task_by_name(parent_name, get_tasks_dir(workflow_dir.parent))
223
+ if parent_dir is None:
224
+ return None
225
+ parent_json = parent_dir / "task.json"
216
226
 
217
227
  if parent_json.exists():
218
228
  try:
@@ -101,7 +101,7 @@ def _record_start_state(
101
101
  task_json_path: Path,
102
102
  repo_root: Path,
103
103
  label: str = "",
104
- ) -> None:
104
+ ) -> Path:
105
105
  """Enter implementation and record its branch after current authorization.
106
106
 
107
107
  Planning and analysis tasks keep their lifecycle status while the active
@@ -121,7 +121,7 @@ def _record_start_state(
121
121
  file=sys.stderr,
122
122
  )
123
123
  print(hint, file=sys.stderr)
124
- return
124
+ return task_json_path
125
125
 
126
126
  applied: list[str] = []
127
127
 
@@ -132,7 +132,7 @@ def _record_start_state(
132
132
  colored(f"Warning: Implementation start withheld; task.json not updated: {error.code}: {error}", Colors.YELLOW),
133
133
  file=sys.stderr,
134
134
  )
135
- return
135
+ return task_json_path
136
136
  if data.get("status") == "planning" and implementation_ready:
137
137
  data["status"] = "in_progress"
138
138
  applied.append(f"✓ Status: planning → in_progress{label}")
@@ -158,15 +158,18 @@ def _record_start_state(
158
158
  )
159
159
 
160
160
  if not applied:
161
- return
161
+ return task_json_path
162
162
 
163
163
  try:
164
164
  assert_task_mutation_allowed(repo_root, task_json_path.parent, data)
165
165
  except TaskWorkflowError as error:
166
166
  print(colored(f"{error.code}: task metadata update withheld: {error}", Colors.YELLOW), file=sys.stderr)
167
- return
167
+ return task_json_path
168
168
 
169
- if not write_json(task_json_path, data):
169
+ from common.task_move import relocate_task
170
+ relocated = relocate_task(repo_root, task_json_path.parent, data)
171
+ task_json_path = relocated / FILE_TASK_JSON
172
+ if not task_json_path.is_file():
170
173
  print(
171
174
  colored(
172
175
  f"Warning: Failed to write {task_json_path}; "
@@ -175,7 +178,7 @@ def _record_start_state(
175
178
  ),
176
179
  file=sys.stderr,
177
180
  )
178
- return
181
+ return task_json_path
179
182
 
180
183
  for line in applied:
181
184
  print(colored(line, Colors.GREEN))
@@ -190,12 +193,15 @@ def _record_start_state(
190
193
  ),
191
194
  file=sys.stderr,
192
195
  )
196
+
193
197
  print(
194
198
  f"Once you branch off, run: python3 {DIR_WORKFLOW}/scripts/task.py "
195
199
  "set-branch <task> <feature-branch>",
196
200
  file=sys.stderr,
197
201
  )
198
202
 
203
+ return task_json_path
204
+
199
205
 
200
206
  def cmd_start(args: argparse.Namespace) -> int:
201
207
  """Set active task."""
@@ -256,18 +262,17 @@ def cmd_start(args: argparse.Namespace) -> int:
256
262
 
257
263
  # Enter implementation only when the accepted phase contract authorizes it.
258
264
  if task_json_path.is_file():
259
- _record_start_state(task_json_path, repo_root, " (degraded)")
265
+ task_json_path = _record_start_state(task_json_path, repo_root, " (degraded)")
260
266
  run_task_hooks("after_start", task_json_path, repo_root)
261
267
  return 0
262
268
 
263
269
  active = set_active_task(task_dir, repo_root)
264
270
  if active:
265
- print(colored(f"✓ Current task set to: {task_dir}", Colors.GREEN))
266
- print(f"Source: {active.source}")
267
-
268
271
  if task_json_path.is_file():
269
- _record_start_state(task_json_path, repo_root)
272
+ task_json_path = _record_start_state(task_json_path, repo_root)
270
273
 
274
+ print(colored(f"✓ Current task set to: {task_json_path.parent.relative_to(repo_root).as_posix()}", Colors.GREEN))
275
+ print(f"Source: {active.source}")
271
276
  print()
272
277
  print(colored("The hook will now inject context from this task's jsonl files.", Colors.BLUE))
273
278
 
@@ -499,7 +504,7 @@ def cmd_list(args: argparse.Namespace) -> int:
499
504
  if filter_status and t.status != filter_status:
500
505
  continue
501
506
  items.append({
502
- "dir": f"{DIR_WORKFLOW}/{DIR_TASKS}/{dir_name}",
507
+ "dir": t.directory.relative_to(repo_root).as_posix(),
503
508
  "id": t.raw.get("id") or dir_name,
504
509
  "title": t.title,
505
510
  "status": t.status,
@@ -538,7 +543,7 @@ def cmd_list(args: argparse.Namespace) -> int:
538
543
  if filter_status and t.status != filter_status:
539
544
  return
540
545
 
541
- relative_path = f"{DIR_WORKFLOW}/{DIR_TASKS}/{dir_name}"
546
+ relative_path = t.directory.relative_to(repo_root).as_posix()
542
547
  marker = ""
543
548
  if relative_path == current_task:
544
549
  marker = f" {colored('<- current', Colors.GREEN)}"
@@ -622,7 +627,7 @@ Rename options:
622
627
 
623
628
  List options:
624
629
  --mine, -m Show only tasks assigned to current developer
625
- --status, -s <s> Filter by status (planning, in_progress, review, completed)
630
+ --status, -s <s> Filter by status (planning, in_progress, completed, cancelled)
626
631
  --json Output machine-readable JSON (also available on `current`)
627
632
 
628
633
  Examples:
@@ -849,16 +854,18 @@ def main() -> int:
849
854
  p_complete = subparsers.add_parser("complete", help="Complete after Delivery Review and On-Site Closeout Phase acceptance")
850
855
  p_complete.add_argument("task_dir", help="Task directory or task name")
851
856
 
857
+ p_migrate = subparsers.add_parser("migrate-layout", help="Preview or explicitly apply task status directory migration")
858
+ migration_mode = p_migrate.add_mutually_exclusive_group()
859
+ migration_mode.add_argument("--apply", action="store_true", help="Recheck and apply the migration")
860
+ migration_mode.add_argument("--recover", action="store_true", help="Roll back an interrupted relocation")
861
+
852
862
  args = parser.parse_args()
853
863
 
854
864
  if not args.command:
855
865
  show_usage()
856
866
  return 1
857
867
 
858
- if args.command == "continuity":
859
- return continuity_show(args) if args.continuity_action == "show" else continuity_capture(args)
860
- if args.command == "transfer":
861
- return transfer_task_ownership(args)
868
+ from common.task_move import cmd_migrate_layout, layout_lock, require_settled_layout
862
869
 
863
870
  commands = {
864
871
  "create": cmd_create,
@@ -881,12 +888,23 @@ def main() -> int:
881
888
  "add-subtask": cmd_add_subtask,
882
889
  "remove-subtask": cmd_remove_subtask,
883
890
  "list": cmd_list,
891
+ "migrate-layout": cmd_migrate_layout,
884
892
  }
885
893
 
886
- if args.command in commands:
887
- return commands[args.command](args)
888
- else:
889
- show_usage()
894
+ try:
895
+ with layout_lock(get_repo_root()):
896
+ if args.command != "migrate-layout":
897
+ require_settled_layout(get_repo_root())
898
+ if args.command == "continuity":
899
+ return continuity_show(args) if args.continuity_action == "show" else continuity_capture(args)
900
+ if args.command == "transfer":
901
+ return transfer_task_ownership(args)
902
+ if args.command in commands:
903
+ return commands[args.command](args)
904
+ show_usage()
905
+ return 1
906
+ except (ValueError, OSError) as error:
907
+ print(f"TASK_LAYOUT_WITHHELD: {error}", file=sys.stderr)
890
908
  return 1
891
909
 
892
910
 
@@ -20,7 +20,7 @@ python3 ./.trellis/scripts/get_context.py --mode spec --file <path>
20
20
 
21
21
  ### 任务记录
22
22
 
23
- Trellis task 位于 `.trellis/tasks/`,包含 `task.json`、需求以及任务所需的设计、实施、研究或上下文资产。使用 Trellis CLI 创建和启动任务、初始化和推进阶段路线,以及完成或取消任务。不得手工修改任务状态或阶段状态。
23
+ Trellis task 按 `task.json.status` 存放在 `.trellis/tasks/planning/<task>`、`in_progress/<task>`、`completed/YYYY-MM/<task>` 或 `cancelled/<task>`。创建进入 `planning`,获准实施后启动进入 `in_progress`;目录仅是状态的投影。每个任务包含 `task.json`、需求以及任务所需的设计、实施、研究或上下文资产。使用 Trellis CLI 创建和启动任务、初始化和推进阶段路线,以及完成或取消任务。不得手工修改任务状态或阶段状态。
24
24
 
25
25
  ```bash
26
26
  python3 ./.trellis/scripts/task.py create "<title>" [--slug <name>] [--parent <task>]
@@ -34,7 +34,9 @@ python3 ./.trellis/scripts/task.py cancel <task> --reason "<reason>" --evidence-
34
34
 
35
35
  使用 `phase artifact`、`accept`、`skip`、`wait`、`block`、`resume`、`return`、`route` 和 `authorize` 执行对应的阶段操作。准确参数以 `task.py --help` 为准。跳过阶段必须提供理由和证据;目标探索与需求核对阶段不可省略。
36
36
 
37
- 只有阶段路线已到达获准的实施入口时,`task.py start` 才会将任务从 `planning` 改为 `in_progress`。分析任务可在交付审查与现场收尾阶段后仍处于 `planning` 时完成。`complete` 和 `cancel` 会清除任务的运行时会话指针;任务记录保留在原位置。
37
+ 只有阶段路线已到达获准的实施入口时,`task.py start` 才会将任务从 `planning` 改为 `in_progress`。分析任务可在交付审查与现场收尾阶段后仍处于 `planning` 时完成。`complete` 将任务移入 `completed/YYYY-MM/<task>`,`cancel` 将任务移入 `cancelled/<task>`,并在同一事务中清除任务的运行时会话指针。
38
+
39
+ 旧布局须先预览 `task.py migrate-layout`,核对后执行 `task.py migrate-layout --apply`;旧 `archive/YYYY-MM` 中已完成的任务保留原月份,其他任务按真实状态放置。迁移会同步 JSON/JSONL 机器引用、会话指针和运行态索引,保留任务身份与父子关系,不重授历史授权。未完成事务会阻止普通任务命令,须用 `task.py migrate-layout --recover` 恢复;输入被外部改动时停止并保留现场。路径或产物内容改变导致接受证据失效时,须经原生阶段接受或 continuity 核对重新收敛。
38
40
 
39
41
  在支持原生实施或检查 agent 的平台上,任务上下文清单(`implement.jsonl`、`check.jsonl`)用于提供相关规范和研究资料。只有这些 agent 会读取清单时才需要维护;清单不能替代任务资产或阶段证据。
40
42
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pennixrv/trellis",
3
- "version": "0.7.1-beta.4",
3
+ "version": "0.7.1-beta.6",
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.4"
37
+ "@pennixrv/trellis-core": "0.7.1-beta.6"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@eslint/js": "^9.18.0",