@mindfoldhq/trellis 0.6.6 → 0.6.8

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 (152) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/index.d.ts.map +1 -1
  3. package/dist/cli/index.js +37 -0
  4. package/dist/cli/index.js.map +1 -1
  5. package/dist/commands/channel/adapters/codex.d.ts +7 -1
  6. package/dist/commands/channel/adapters/codex.d.ts.map +1 -1
  7. package/dist/commands/channel/adapters/codex.js +19 -2
  8. package/dist/commands/channel/adapters/codex.js.map +1 -1
  9. package/dist/commands/channel/adapters/index.d.ts +3 -1
  10. package/dist/commands/channel/adapters/index.d.ts.map +1 -1
  11. package/dist/commands/channel/adapters/index.js +1 -1
  12. package/dist/commands/channel/adapters/index.js.map +1 -1
  13. package/dist/commands/channel/guard.d.ts.map +1 -1
  14. package/dist/commands/channel/guard.js +7 -2
  15. package/dist/commands/channel/guard.js.map +1 -1
  16. package/dist/commands/channel/index.d.ts.map +1 -1
  17. package/dist/commands/channel/index.js +4 -0
  18. package/dist/commands/channel/index.js.map +1 -1
  19. package/dist/commands/channel/spawn.d.ts +3 -0
  20. package/dist/commands/channel/spawn.d.ts.map +1 -1
  21. package/dist/commands/channel/spawn.js +1 -0
  22. package/dist/commands/channel/spawn.js.map +1 -1
  23. package/dist/commands/channel/store/paths.d.ts +14 -0
  24. package/dist/commands/channel/store/paths.d.ts.map +1 -1
  25. package/dist/commands/channel/store/paths.js +26 -0
  26. package/dist/commands/channel/store/paths.js.map +1 -1
  27. package/dist/commands/channel/supervisor.d.ts +3 -0
  28. package/dist/commands/channel/supervisor.d.ts.map +1 -1
  29. package/dist/commands/channel/supervisor.js +1 -0
  30. package/dist/commands/channel/supervisor.js.map +1 -1
  31. package/dist/commands/init.d.ts +2 -0
  32. package/dist/commands/init.d.ts.map +1 -1
  33. package/dist/commands/init.js.map +1 -1
  34. package/dist/commands/mem.d.ts.map +1 -1
  35. package/dist/commands/mem.js +30 -9
  36. package/dist/commands/mem.js.map +1 -1
  37. package/dist/commands/uninstall.d.ts +9 -0
  38. package/dist/commands/uninstall.d.ts.map +1 -1
  39. package/dist/commands/uninstall.js +67 -3
  40. package/dist/commands/uninstall.js.map +1 -1
  41. package/dist/commands/update.d.ts +40 -1
  42. package/dist/commands/update.d.ts.map +1 -1
  43. package/dist/commands/update.js +126 -34
  44. package/dist/commands/update.js.map +1 -1
  45. package/dist/configurators/codex.d.ts.map +1 -1
  46. package/dist/configurators/codex.js +11 -12
  47. package/dist/configurators/codex.js.map +1 -1
  48. package/dist/configurators/grok.d.ts +22 -0
  49. package/dist/configurators/grok.d.ts.map +1 -0
  50. package/dist/configurators/grok.js +58 -0
  51. package/dist/configurators/grok.js.map +1 -0
  52. package/dist/configurators/index.d.ts.map +1 -1
  53. package/dist/configurators/index.js +12 -2
  54. package/dist/configurators/index.js.map +1 -1
  55. package/dist/configurators/kimi.d.ts +29 -0
  56. package/dist/configurators/kimi.d.ts.map +1 -0
  57. package/dist/configurators/kimi.js +81 -0
  58. package/dist/configurators/kimi.js.map +1 -0
  59. package/dist/configurators/pi.js +9 -9
  60. package/dist/configurators/pi.js.map +1 -1
  61. package/dist/configurators/shared.d.ts.map +1 -1
  62. package/dist/configurators/shared.js +11 -4
  63. package/dist/configurators/shared.js.map +1 -1
  64. package/dist/configurators/zcode.d.ts +9 -4
  65. package/dist/configurators/zcode.d.ts.map +1 -1
  66. package/dist/configurators/zcode.js +41 -12
  67. package/dist/configurators/zcode.js.map +1 -1
  68. package/dist/migrations/manifests/0.5.0-beta.0.json +1 -1
  69. package/dist/migrations/manifests/0.6.7.json +9 -0
  70. package/dist/migrations/manifests/0.6.8.json +17 -0
  71. package/dist/templates/codex/agents/trellis-check.toml +5 -12
  72. package/dist/templates/codex/agents/trellis-implement.toml +5 -12
  73. package/dist/templates/codex/agents/trellis-research.toml +13 -15
  74. package/dist/templates/codex/config.toml +11 -0
  75. package/dist/templates/codex/hooks/session-start.py +9 -3
  76. package/dist/templates/codex/hooks.json +12 -0
  77. package/dist/templates/codex/skills/brainstorm/SKILL.md +40 -13
  78. package/dist/templates/common/bundled-skills/trellis-meta/SKILL.md +2 -2
  79. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-skills-or-commands.md +1 -1
  80. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/agents.md +1 -0
  81. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/hooks-and-settings.md +1 -1
  82. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/overview.md +1 -1
  83. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/platform-map.md +8 -4
  84. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/skills-and-commands.md +2 -1
  85. package/dist/templates/common/bundled-skills/trellis-session-insight/SKILL.md +1 -1
  86. package/dist/templates/common/skills/brainstorm.md +40 -13
  87. package/dist/templates/copilot/prompts/brainstorm.prompt.md +40 -13
  88. package/dist/templates/grok/agents/trellis-check.md +105 -0
  89. package/dist/templates/grok/agents/trellis-implement.md +116 -0
  90. package/dist/templates/grok/agents/trellis-research.md +146 -0
  91. package/dist/templates/grok/index.d.ts +16 -0
  92. package/dist/templates/grok/index.d.ts.map +1 -0
  93. package/dist/templates/grok/index.js +19 -0
  94. package/dist/templates/grok/index.js.map +1 -0
  95. package/dist/templates/kimi/agents/trellis-check.md +103 -0
  96. package/dist/templates/kimi/agents/trellis-implement.md +114 -0
  97. package/dist/templates/kimi/agents/trellis-research.md +140 -0
  98. package/dist/templates/kimi/index.d.ts +19 -0
  99. package/dist/templates/kimi/index.d.ts.map +1 -0
  100. package/dist/templates/kimi/index.js +22 -0
  101. package/dist/templates/kimi/index.js.map +1 -0
  102. package/dist/templates/omp/extensions/trellis/index.ts.txt +19 -5
  103. package/dist/templates/opencode/lib/session-utils.js +8 -2
  104. package/dist/templates/pi/extensions/trellis/index.ts.txt +8 -2
  105. package/dist/templates/pi/settings.json +0 -3
  106. package/dist/templates/shared-hooks/index.d.ts +11 -5
  107. package/dist/templates/shared-hooks/index.d.ts.map +1 -1
  108. package/dist/templates/shared-hooks/index.js +16 -5
  109. package/dist/templates/shared-hooks/index.js.map +1 -1
  110. package/dist/templates/shared-hooks/inject-subagent-context.py +151 -17
  111. package/dist/templates/shared-hooks/inject-workflow-state.py +50 -28
  112. package/dist/templates/shared-hooks/session-start.py +26 -6
  113. package/dist/templates/trellis/config.yaml +9 -7
  114. package/dist/templates/trellis/scripts/common/active_task.py +40 -13
  115. package/dist/templates/trellis/scripts/common/cli_adapter.py +69 -5
  116. package/dist/templates/trellis/scripts/common/config.py +16 -8
  117. package/dist/templates/trellis/scripts/common/git.py +36 -0
  118. package/dist/templates/trellis/scripts/common/io.py +28 -4
  119. package/dist/templates/trellis/scripts/common/task_context.py +19 -1
  120. package/dist/templates/trellis/scripts/common/task_store.py +98 -21
  121. package/dist/templates/trellis/scripts/common/task_utils.py +24 -0
  122. package/dist/templates/trellis/scripts/common/workflow_phase.py +19 -12
  123. package/dist/templates/trellis/scripts/task.py +85 -8
  124. package/dist/templates/trellis/workflow.md +20 -19
  125. package/dist/templates/zcode/agents/trellis-check.md +7 -0
  126. package/dist/templates/zcode/agents/trellis-implement.md +13 -4
  127. package/dist/templates/zcode/config.json +42 -0
  128. package/dist/templates/zcode/index.d.ts +10 -1
  129. package/dist/templates/zcode/index.d.ts.map +1 -1
  130. package/dist/templates/zcode/index.js +12 -2
  131. package/dist/templates/zcode/index.js.map +1 -1
  132. package/dist/types/ai-tools.d.ts +4 -4
  133. package/dist/types/ai-tools.d.ts.map +1 -1
  134. package/dist/types/ai-tools.js +76 -2
  135. package/dist/types/ai-tools.js.map +1 -1
  136. package/dist/utils/atomic-write.d.ts +12 -0
  137. package/dist/utils/atomic-write.d.ts.map +1 -0
  138. package/dist/utils/atomic-write.js +30 -0
  139. package/dist/utils/atomic-write.js.map +1 -0
  140. package/dist/utils/file-writer.d.ts.map +1 -1
  141. package/dist/utils/file-writer.js +6 -5
  142. package/dist/utils/file-writer.js.map +1 -1
  143. package/dist/utils/registry-config.d.ts.map +1 -1
  144. package/dist/utils/registry-config.js +3 -2
  145. package/dist/utils/registry-config.js.map +1 -1
  146. package/dist/utils/template-fetcher.d.ts.map +1 -1
  147. package/dist/utils/template-fetcher.js +35 -6
  148. package/dist/utils/template-fetcher.js.map +1 -1
  149. package/dist/utils/template-hash.d.ts.map +1 -1
  150. package/dist/utils/template-hash.js +2 -1
  151. package/dist/utils/template-hash.js.map +1 -1
  152. package/package.json +2 -2
@@ -21,8 +21,10 @@ import argparse
21
21
  import json
22
22
  from pathlib import Path
23
23
 
24
+ from .git import branch_exists_locally
25
+ from .io import read_json
24
26
  from .log import Colors, colored
25
- from .paths import get_repo_root
27
+ from .paths import FILE_TASK_JSON, get_repo_root
26
28
  from .task_utils import resolve_task_dir
27
29
 
28
30
 
@@ -97,6 +99,22 @@ def cmd_validate(args: argparse.Namespace) -> int:
97
99
  print(f"Target dir: {target_dir}")
98
100
  print()
99
101
 
102
+ # Warn (don't fail validation) when the recorded branch is stale — it
103
+ # was likely already merged and deleted (#399 item 2).
104
+ task_json_path = target_dir / FILE_TASK_JSON
105
+ if task_json_path.is_file():
106
+ task_data = read_json(task_json_path)
107
+ stored_branch = task_data.get("branch") if task_data else None
108
+ if stored_branch and not branch_exists_locally(stored_branch, repo_root):
109
+ print(
110
+ colored(
111
+ f"Warning: recorded branch '{stored_branch}' no longer exists locally "
112
+ "(likely merged and deleted).",
113
+ Colors.YELLOW,
114
+ )
115
+ )
116
+ print()
117
+
100
118
  total_errors = 0
101
119
  for jsonl_name in ["implement.jsonl", "check.jsonl"]:
102
120
  jsonl_file = target_dir / jsonl_name
@@ -30,7 +30,7 @@ from .config import (
30
30
  resolve_package,
31
31
  validate_package,
32
32
  )
33
- from .git import run_git
33
+ from .git import branch_exists_locally, resolve_default_branch, run_git
34
34
  from .io import read_json, write_json
35
35
  from .log import Colors, colored
36
36
  from .paths import (
@@ -51,6 +51,7 @@ from .safe_commit import (
51
51
  from .task_utils import (
52
52
  archive_task_complete,
53
53
  find_task_by_name,
54
+ is_within_tasks_dir,
54
55
  resolve_task_dir,
55
56
  run_task_hooks,
56
57
  )
@@ -115,7 +116,7 @@ def _repo_relative_path(path: Path, repo_root: Path) -> str:
115
116
  # Config directories of platforms that consume implement.jsonl / check.jsonl.
116
117
  # Keep in sync with src/types/ai-tools.ts AI_TOOLS entries — these are the
117
118
  # platforms listed in workflow.md's "agent-capable" Skill Routing block.
118
- # Codex is checked separately because default inline mode does not consume
119
+ # Codex is checked separately because explicit inline mode does not consume
119
120
  # JSONL. Kilo / Antigravity / Devin are NOT in this list either: they load
120
121
  # specs through skills instead of JSONL.
121
122
  _SUBAGENT_CONFIG_DIRS: tuple[str, ...] = (
@@ -132,6 +133,8 @@ _SUBAGENT_CONFIG_DIRS: tuple[str, ...] = (
132
133
  ".trae", # Trae IDE
133
134
  ".omp", # Oh My Pi
134
135
  ".zcode", # ZCode
136
+ ".grok", # Grok Build
137
+ ".kimi-code", # Kimi Code
135
138
  )
136
139
  _CODEX_CONFIG_DIR = ".codex"
137
140
 
@@ -147,14 +150,15 @@ def _has_subagent_platform(repo_root: Path) -> bool:
147
150
  """Return True if any sub-agent-capable platform is configured.
148
151
 
149
152
  Detected by probing well-known config directories at the repo root. Codex
150
- only counts when ``codex.dispatch_mode`` explicitly opts into
151
- ``sub-agent``; inline mode loads context through skills, not JSONL.
153
+ counts by default through ``codex.dispatch_mode: auto`` (including the
154
+ legacy ``sub-agent`` alias); explicit inline mode loads context through
155
+ skills, not JSONL.
152
156
  """
153
157
  for config_dir in _SUBAGENT_CONFIG_DIRS:
154
158
  if (repo_root / config_dir).is_dir():
155
159
  return True
156
160
  if (repo_root / _CODEX_CONFIG_DIR).is_dir():
157
- return get_codex_dispatch_mode(repo_root) == "sub-agent"
161
+ return get_codex_dispatch_mode(repo_root) == "auto"
158
162
  return False
159
163
 
160
164
 
@@ -292,9 +296,32 @@ def cmd_create(args: argparse.Namespace) -> int:
292
296
 
293
297
  today = datetime.now().strftime("%Y-%m-%d")
294
298
 
295
- # Record current branch as base_branch (PR target)
299
+ # Record the PR target branch. Prefer the repo's actual default branch
300
+ # (origin/HEAD) so creating a task from a feature branch doesn't
301
+ # mis-stamp that feature branch as the PR target (#399 item 1). Falls
302
+ # back to the checked-out branch when the default can't be resolved
303
+ # (no remote configured, offline, etc.) — the pre-existing behavior.
304
+ # --base-branch lets the caller override both when neither is correct.
296
305
  _, branch_out, _ = run_git(["branch", "--show-current"], cwd=repo_root)
297
306
  current_branch = branch_out.strip() or "main"
307
+ explicit_base_branch: str | None = getattr(args, "base_branch", None)
308
+ if explicit_base_branch:
309
+ base_branch = explicit_base_branch
310
+ else:
311
+ resolved_base_branch = resolve_default_branch(repo_root)
312
+ if resolved_base_branch:
313
+ base_branch = resolved_base_branch
314
+ else:
315
+ base_branch = current_branch
316
+ print(
317
+ colored(
318
+ f"warning: could not resolve the repository's default branch "
319
+ f"(no remote configured, offline, etc.); stamping base_branch as "
320
+ f"the checked-out branch '{base_branch}'. Pass --base-branch to override.",
321
+ Colors.YELLOW,
322
+ ),
323
+ file=sys.stderr,
324
+ )
298
325
 
299
326
  description = (args.description or "").strip()
300
327
  if not description.strip():
@@ -321,7 +348,7 @@ def cmd_create(args: argparse.Namespace) -> int:
321
348
  "createdAt": today,
322
349
  "completedAt": None,
323
350
  "branch": None,
324
- "base_branch": current_branch,
351
+ "base_branch": base_branch,
325
352
  "worktree_path": None,
326
353
  "commit": None,
327
354
  "pr_url": None,
@@ -392,20 +419,46 @@ def cmd_create(args: argparse.Namespace) -> int:
392
419
  else:
393
420
  try:
394
421
  from .active_task import resolve_context_key, set_active_task
395
- if resolve_context_key():
396
- try:
397
- rel_dir = task_dir.relative_to(repo_root).as_posix()
398
- except ValueError:
399
- rel_dir = str(task_dir)
400
- active = set_active_task(rel_dir, repo_root)
401
- if active:
402
- print(
403
- colored(f"Activated task for this session: {active.task_path}", Colors.GREEN),
404
- file=sys.stderr,
405
- )
406
- print(f"Source: {active.source}", file=sys.stderr)
407
- except Exception:
408
- pass
422
+ except Exception as exc:
423
+ print(
424
+ colored(f"Warning: session activation unavailable (import failed: {exc})", Colors.YELLOW),
425
+ file=sys.stderr,
426
+ )
427
+ else:
428
+ try:
429
+ context_key = resolve_context_key()
430
+ except Exception as exc:
431
+ print(
432
+ colored(f"Warning: session activation failed (context resolution: {exc})", Colors.YELLOW),
433
+ file=sys.stderr,
434
+ )
435
+ else:
436
+ # No session identity is the normal CLI-outside-an-AI-session
437
+ # case (see comment above) — stay silent, not a failure.
438
+ if context_key:
439
+ try:
440
+ rel_dir = task_dir.relative_to(repo_root).as_posix()
441
+ except ValueError:
442
+ rel_dir = str(task_dir)
443
+ try:
444
+ active = set_active_task(rel_dir, repo_root)
445
+ except Exception as exc:
446
+ print(
447
+ colored(f"Warning: session activation failed (pointer persistence: {exc})", Colors.YELLOW),
448
+ file=sys.stderr,
449
+ )
450
+ else:
451
+ if active:
452
+ print(
453
+ colored(f"Activated task for this session: {active.task_path}", Colors.GREEN),
454
+ file=sys.stderr,
455
+ )
456
+ print(f"Source: {active.source}", file=sys.stderr)
457
+ else:
458
+ print(
459
+ colored("Warning: session activation failed (no pointer returned)", Colors.YELLOW),
460
+ file=sys.stderr,
461
+ )
409
462
 
410
463
  print(colored(f"Created task: {dir_name}", Colors.GREEN), file=sys.stderr)
411
464
  print("", file=sys.stderr)
@@ -455,6 +508,17 @@ def cmd_archive(args: argparse.Namespace) -> int:
455
508
  print(f" - {t.dir_name}/", file=sys.stderr)
456
509
  return 1
457
510
 
511
+ # Refuse to archive anything that isn't a real task directly under
512
+ # .trellis/tasks/. A mistyped name (e.g. "src") resolves to repo_root/src,
513
+ # which is a dir but not a task — without this guard archive would move the
514
+ # user's source directory out of the repo.
515
+ if not is_within_tasks_dir(task_dir, repo_root):
516
+ print(colored(
517
+ f"Error: refusing to archive '{task_name}': "
518
+ f"{task_dir} is not a task under {tasks_dir}",
519
+ Colors.RED), file=sys.stderr)
520
+ return 1
521
+
458
522
  dir_name = task_dir.name
459
523
  task_json_path = task_dir / FILE_TASK_JSON
460
524
 
@@ -466,6 +530,19 @@ def cmd_archive(args: argparse.Namespace) -> int:
466
530
  if task_json_path.is_file():
467
531
  data = read_json(task_json_path)
468
532
  if data:
533
+ # Warn (don't block) when the recorded branch is stale — it was
534
+ # likely already merged and deleted (#399 item 2).
535
+ stored_branch = data.get("branch")
536
+ if stored_branch and not branch_exists_locally(stored_branch, repo_root):
537
+ print(
538
+ colored(
539
+ f"Warning: recorded branch '{stored_branch}' no longer exists locally "
540
+ "(likely merged and deleted).",
541
+ Colors.YELLOW,
542
+ ),
543
+ file=sys.stderr,
544
+ )
545
+
469
546
  data["status"] = "completed"
470
547
  data["completedAt"] = today
471
548
  write_json(task_json_path, data)
@@ -69,6 +69,30 @@ def is_safe_task_path(task_path: str, repo_root: Path | None = None) -> bool:
69
69
  return True
70
70
 
71
71
 
72
+ def is_within_tasks_dir(task_dir_abs: Path, repo_root: Path | None = None) -> bool:
73
+ """Check that a resolved task directory really is a task under the tasks dir.
74
+
75
+ A real task lives directly at ``.trellis/tasks/<name>``. This returns True
76
+ only when ``task_dir_abs`` is an immediate child of the tasks directory.
77
+
78
+ Guards archive: ``resolve_task_dir`` falls back to ``repo_root/<name>`` for
79
+ an unknown name, so a mistyped ``task.py archive src`` resolves to the real
80
+ ``src/`` source directory. Without this check archive would ``shutil.move``
81
+ it out of the repo. Also rejects the tasks dir itself and anything nested
82
+ under ``archive/`` (already-archived tasks).
83
+ """
84
+ if repo_root is None:
85
+ repo_root = get_repo_root()
86
+ try:
87
+ resolved = task_dir_abs.resolve()
88
+ tasks_resolved = get_tasks_dir(repo_root).resolve()
89
+ except (OSError, RuntimeError):
90
+ return False
91
+ if resolved.parent != tasks_resolved:
92
+ return False
93
+ return resolved.name != "archive"
94
+
95
+
72
96
  # =============================================================================
73
97
  # Task Lookup
74
98
  # =============================================================================
@@ -144,27 +144,34 @@ def _platform_matches(platform: str, block_names: list[str]) -> bool:
144
144
  def resolve_effective_platform(platform: str, config: dict) -> str:
145
145
  """Map ``codex`` to a dispatch-mode-namespaced virtual platform name.
146
146
 
147
- When ``--platform codex`` is passed, return ``"codex-inline"`` (default)
148
- or ``"codex-sub-agent"`` based on ``.trellis/config.yaml`` ``codex.dispatch_mode``.
147
+ When ``--platform codex`` is passed, return ``"codex-sub-agent"`` by
148
+ default or ``"codex-inline"`` when explicitly configured in
149
+ ``.trellis/config.yaml``. ``sub-agent`` remains an alias for ``auto``.
149
150
  ``filter_platform`` then surfaces blocks whose marker lists include the
150
151
  namespaced name (e.g. ``[codex-sub-agent, ...]`` or ``[codex-inline, Kilo,
151
152
  Antigravity, Devin]``).
152
153
 
153
- Default is ``inline`` because Codex sub-agents run with ``fork_turns="none"``
154
- isolation and can't inherit the parent session's task context — inline
155
- keeps the main agent in charge so context isn't lost. Invalid / missing
156
- values also fall back to inline.
154
+ Native Codex context injection supports the ``auto`` default. Invalid
155
+ explicit values fall back to ``inline`` safely; this renderer deliberately
156
+ does not warn because it can run in normal CLI output flows.
157
157
 
158
158
  Other platforms are returned unchanged.
159
159
  """
160
160
  if platform == "codex":
161
- mode = "inline"
161
+ mode = "auto"
162
162
  codex_cfg = config.get("codex") if isinstance(config, dict) else None
163
- if isinstance(codex_cfg, dict):
164
- cfg_mode = codex_cfg.get("dispatch_mode")
165
- if cfg_mode in ("inline", "sub-agent"):
166
- mode = cfg_mode
167
- return f"codex-{mode}"
163
+ if codex_cfg is not None:
164
+ if not isinstance(codex_cfg, dict):
165
+ mode = "inline"
166
+ else:
167
+ cfg_mode = str(codex_cfg.get("dispatch_mode", mode)).strip().lower()
168
+ if cfg_mode == "inline":
169
+ mode = "inline"
170
+ elif cfg_mode in ("auto", "sub-agent"):
171
+ mode = "auto"
172
+ else:
173
+ mode = "inline"
174
+ return "codex-sub-agent" if mode == "auto" else "codex-inline"
168
175
  return platform
169
176
 
170
177
 
@@ -9,7 +9,7 @@ Usage:
9
9
  python3 task.py validate <dir> # Validate jsonl files
10
10
  python3 task.py list-context <dir> # List jsonl entries
11
11
  python3 task.py start <dir> # Set active task
12
- python3 task.py current [--source] # Show active task
12
+ python3 task.py current [--source] [--json] # Show active task
13
13
  python3 task.py finish # Clear active task
14
14
  python3 task.py set-branch <dir> <branch> # Set git branch
15
15
  python3 task.py set-base-branch <dir> <branch> # Set PR target branch
@@ -24,6 +24,7 @@ Usage:
24
24
  from __future__ import annotations
25
25
 
26
26
  import argparse
27
+ import json
27
28
  import sys
28
29
 
29
30
  from common.log import Colors, colored
@@ -166,6 +167,27 @@ def cmd_current(args: argparse.Namespace) -> int:
166
167
  repo_root = get_repo_root()
167
168
  active = resolve_active_task(repo_root)
168
169
 
170
+ if getattr(args, "json", False):
171
+ task_obj = None
172
+ if active.task_path:
173
+ data = read_json(repo_root / active.task_path / FILE_TASK_JSON) or {}
174
+ task_obj = {
175
+ "dir": active.task_path,
176
+ "id": data.get("id") or data.get("name"),
177
+ "title": data.get("title"),
178
+ "status": data.get("status"),
179
+ "parent": data.get("parent"),
180
+ "children": data.get("children", []),
181
+ "branch": data.get("branch"),
182
+ "base_branch": data.get("base_branch"),
183
+ }
184
+ print(json.dumps({
185
+ "current_task": task_obj,
186
+ "source": active.source,
187
+ "stale": active.stale,
188
+ }, ensure_ascii=False))
189
+ return 0 if active.task_path else 1
190
+
169
191
  if args.source:
170
192
  print(f"Current task: {active.task_path or '(none)'}")
171
193
  print(f"Source: {active.source}")
@@ -184,6 +206,24 @@ def cmd_current(args: argparse.Namespace) -> int:
184
206
  # Command: list
185
207
  # =============================================================================
186
208
 
209
+ def _display_status(t, all_statuses: dict) -> str:
210
+ """Return the status label to show for a task in `list` output.
211
+
212
+ A parent task's stored status stays "planning" until someone runs
213
+ `task.py start` on the parent directly, even while its children are
214
+ actively being worked — a misleading label for anyone scanning the
215
+ list (#399 item 3). Show "active" instead when at least one child is
216
+ past planning; the stored status.json value is left untouched.
217
+ """
218
+ if t.status == "planning" and t.children:
219
+ child_in_flight = any(
220
+ all_statuses.get(c) not in (None, "planning") for c in t.children
221
+ )
222
+ if child_in_flight:
223
+ return "active"
224
+ return t.status
225
+
226
+
187
227
  def cmd_list(args: argparse.Namespace) -> int:
188
228
  """List active tasks."""
189
229
  repo_root = get_repo_root()
@@ -192,6 +232,38 @@ def cmd_list(args: argparse.Namespace) -> int:
192
232
  developer = get_developer(repo_root)
193
233
  filter_mine = args.mine
194
234
  filter_status = args.status
235
+ as_json = getattr(args, "json", False)
236
+
237
+ # Single pass: collect all tasks via shared iterator
238
+ all_tasks = {t.dir_name: t for t in iter_active_tasks(tasks_dir)}
239
+ all_statuses = {name: t.status for name, t in all_tasks.items()}
240
+
241
+ if as_json:
242
+ if filter_mine and not developer:
243
+ print(json.dumps({"error": "No developer set"}), file=sys.stderr)
244
+ return 1
245
+
246
+ items = []
247
+ for dir_name in sorted(all_tasks.keys()):
248
+ t = all_tasks[dir_name]
249
+ if filter_mine and (t.assignee or "-") != developer:
250
+ continue
251
+ if filter_status and t.status != filter_status:
252
+ continue
253
+ items.append({
254
+ "dir": f"{DIR_WORKFLOW}/{DIR_TASKS}/{dir_name}",
255
+ "id": t.raw.get("id") or dir_name,
256
+ "title": t.title,
257
+ "status": t.status,
258
+ "display_status": _display_status(t, all_statuses),
259
+ "priority": t.priority,
260
+ "assignee": t.assignee or None,
261
+ "parent": t.parent,
262
+ "children": list(t.children),
263
+ "package": t.package,
264
+ })
265
+ print(json.dumps({"tasks": items}, ensure_ascii=False))
266
+ return 0
195
267
 
196
268
  if filter_mine:
197
269
  if not developer:
@@ -202,10 +274,6 @@ def cmd_list(args: argparse.Namespace) -> int:
202
274
  print(colored("All active tasks:", Colors.BLUE))
203
275
  print()
204
276
 
205
- # Single pass: collect all tasks via shared iterator
206
- all_tasks = {t.dir_name: t for t in iter_active_tasks(tasks_dir)}
207
- all_statuses = {name: t.status for name, t in all_tasks.items()}
208
-
209
277
  # Display tasks hierarchically
210
278
  count = 0
211
279
 
@@ -228,6 +296,7 @@ def cmd_list(args: argparse.Namespace) -> int:
228
296
 
229
297
  # Children progress
230
298
  progress = children_progress(t.children, all_statuses)
299
+ status_label = _display_status(t, all_statuses)
231
300
 
232
301
  # Package tag
233
302
  pkg_tag = f" @{t.package}" if t.package else ""
@@ -235,9 +304,9 @@ def cmd_list(args: argparse.Namespace) -> int:
235
304
  prefix = " " * indent + " - "
236
305
 
237
306
  if filter_mine:
238
- print(f"{prefix}{dir_name}/ ({t.status}){pkg_tag}{progress}{marker}")
307
+ print(f"{prefix}{dir_name}/ ({status_label}){pkg_tag}{progress}{marker}")
239
308
  else:
240
- print(f"{prefix}{dir_name}/ ({t.status}){pkg_tag}{progress} [{colored(t.assignee or '-', Colors.CYAN)}]{marker}")
309
+ print(f"{prefix}{dir_name}/ ({status_label}){pkg_tag}{progress} [{colored(t.assignee or '-', Colors.CYAN)}]{marker}")
241
310
  count += 1
242
311
 
243
312
  # Print children indented
@@ -320,7 +389,7 @@ Usage:
320
389
  python3 task.py archive <task-dir> Archive completed task
321
390
  python3 task.py add-subtask <parent> <child> Link child task to parent
322
391
  python3 task.py remove-subtask <parent> <child> Unlink child from parent
323
- python3 task.py list [--mine] [--status <status>] List tasks
392
+ python3 task.py list [--mine] [--status <status>] [--json] List tasks
324
393
  python3 task.py list-archive [YYYY-MM] List archived tasks
325
394
 
326
395
  Monorepo options:
@@ -329,6 +398,7 @@ Monorepo options:
329
398
  List options:
330
399
  --mine, -m Show only tasks assigned to current developer
331
400
  --status, -s <s> Filter by status (planning, in_progress, review, completed)
401
+ --json Output machine-readable JSON (also available on `current`)
332
402
 
333
403
  Examples:
334
404
  python3 task.py create "Add login feature" --slug add-login
@@ -399,6 +469,10 @@ def main() -> int:
399
469
  p_create.add_argument("--description", "-d", help="Task description")
400
470
  p_create.add_argument("--parent", help="Parent task directory (establishes subtask link)")
401
471
  p_create.add_argument("--package", help="Package name for monorepo projects")
472
+ p_create.add_argument(
473
+ "--base-branch",
474
+ help="PR target branch (overrides origin/HEAD detection and the checked-out-branch fallback)",
475
+ )
402
476
  p_create.add_argument(
403
477
  "--no-start",
404
478
  action="store_true",
@@ -428,6 +502,8 @@ def main() -> int:
428
502
  p_current = subparsers.add_parser("current", help="Show active task")
429
503
  p_current.add_argument("--source", action="store_true",
430
504
  help="Show active task source")
505
+ p_current.add_argument("--json", action="store_true",
506
+ help="Output machine-readable JSON")
431
507
 
432
508
  # finish
433
509
  subparsers.add_parser("finish", help="Clear active task")
@@ -456,6 +532,7 @@ def main() -> int:
456
532
  p_list = subparsers.add_parser("list", help="List tasks")
457
533
  p_list.add_argument("--mine", "-m", action="store_true", help="My tasks only")
458
534
  p_list.add_argument("--status", "-s", help="Filter by status")
535
+ p_list.add_argument("--json", action="store_true", help="Output machine-readable JSON")
459
536
 
460
537
  # add-subtask
461
538
  p_addsub = subparsers.add_parser("add-subtask", help="Link child task to parent")
@@ -183,7 +183,7 @@ Complex task: ask the user if you can create a Trellis task and enter the planni
183
183
  - 1.0 Create task `[required · once]` (only after task-creation consent)
184
184
  - 1.1 Requirement exploration `[required · repeatable]` (`prd.md`; complex tasks also need `design.md` + `implement.md`)
185
185
  - 1.2 Research `[optional · repeatable]`
186
- - 1.3 Configure context `[required · once]` — Claude Code, Cursor, OpenCode, Codex, Kiro, Gemini, Qoder, CodeBuddy, Copilot, Droid, Pi, Oh My Pi, ZCode, Reasonix (sub-agent-dispatch platforms only; inline platforms skip)
186
+ - 1.3 Configure context `[required · once]` — Claude Code, Cursor, OpenCode, Codex, Kiro, Gemini, Qoder, CodeBuddy, Copilot, Droid, Pi, Oh My Pi, ZCode, Reasonix, Grok, Kimi Code (sub-agent-dispatch platforms only; inline platforms skip)
187
187
  - 1.4 Activate task `[required · once]` (review gate, then `task.py start`; status → in_progress)
188
188
  - 1.5 Completion criteria
189
189
 
@@ -220,7 +220,7 @@ Inline mode: skip jsonl curation; Phase 2 reads artifacts/specs via `trellis-bef
220
220
  therefore must cover every required step from implementation through
221
221
  commit, including Phase 3.3 spec update and Phase 3.4 commit. -->
222
222
 
223
- Sub-agent dispatch protocol applies to all platforms and all sub-agents, including class-2 Codex/Gemini/Qoder/Copilot/ZCode/Reasonix/Trae and `trellis-research`: every dispatch prompt starts with `Active task: <task path from task.py current>` before role-specific instructions.
223
+ Sub-agent dispatch protocol applies to all platforms and all sub-agents, including native Codex `SubagentStart` context injection with child-side pull fallback, class-2 Gemini/Qoder/Copilot/Reasonix/Trae/Grok/Kimi Code, hook-backed ZCode, and `trellis-research`: every dispatch prompt starts with `Active task: <task path from task.py current>` before role-specific instructions. On Grok Build, use `spawn_subagent` with `subagent_type` set to the Trellis agent name (e.g. `trellis-implement`). On Kimi Code, dispatch the built-in `coder` / `explore` sub-agent with the matching `.kimi-code/skills/trellis-<role>/SKILL.md` instructions.
224
224
 
225
225
  [workflow-state:in_progress]
226
226
  Tools: `trellis-implement` / `trellis-research` are sub-agent types only (Task/Agent tool, NOT Skill; there is no skill by these names). `trellis-update-spec` is a skill. `trellis-check` exists as both; prefer the Agent form when verifying after code changes.
@@ -272,13 +272,13 @@ Code committed. Run `/trellis:finish-work`; if dirty, return to Phase 3.4 first.
272
272
 
273
273
  When a user request matches one of these intents inside an active task, route first, then load the detailed phase step if needed.
274
274
 
275
- [Claude Code, Cursor, OpenCode, codex-sub-agent, Kiro, Gemini, Qoder, CodeBuddy, Copilot, Droid, Pi, Oh My Pi, ZCode, Reasonix, Trae]
275
+ [Claude Code, Cursor, OpenCode, codex-sub-agent, Kiro, Gemini, Qoder, CodeBuddy, Copilot, Droid, Pi, Oh My Pi, ZCode, Reasonix, Trae, Grok, Kimi Code]
276
276
 
277
277
  - Planning or unclear requirements -> `trellis-brainstorm`.
278
278
  - `in_progress` implementation/check -> dispatch `trellis-implement` / `trellis-check`.
279
279
  - Repeated debugging -> `trellis-break-loop`; spec updates -> `trellis-update-spec`.
280
280
 
281
- [/Claude Code, Cursor, OpenCode, codex-sub-agent, Kiro, Gemini, Qoder, CodeBuddy, Copilot, Droid, Pi, Oh My Pi, ZCode, Reasonix, Trae]
281
+ [/Claude Code, Cursor, OpenCode, codex-sub-agent, Kiro, Gemini, Qoder, CodeBuddy, Copilot, Droid, Pi, Oh My Pi, ZCode, Reasonix, Trae, Grok, Kimi Code]
282
282
 
283
283
  [codex-inline, Kilo, Antigravity, Devin]
284
284
 
@@ -353,7 +353,7 @@ Return to this step whenever requirements change and revise the relevant artifac
353
353
 
354
354
  Research can happen at any time during requirement exploration. It isn't limited to local code — you can use any available tool (MCP servers, skills, web search, etc.) to look up external information, including third-party library docs, industry practices, API references, etc.
355
355
 
356
- [Claude Code, Cursor, OpenCode, codex-sub-agent, Kiro, Gemini, Qoder, CodeBuddy, Copilot, Droid, Pi, Oh My Pi, ZCode, Reasonix, Trae]
356
+ [Claude Code, Cursor, OpenCode, codex-sub-agent, Kiro, Gemini, Qoder, CodeBuddy, Copilot, Droid, Pi, Oh My Pi, ZCode, Reasonix, Trae, Grok, Kimi Code]
357
357
 
358
358
  Spawn the research sub-agent:
359
359
 
@@ -361,11 +361,11 @@ Spawn the research sub-agent:
361
361
  - **Task description**: Research <specific question>
362
362
  - **Key requirement**: Research output MUST be persisted to `{TASK_DIR}/research/`
363
363
 
364
- [/Claude Code, Cursor, OpenCode, codex-sub-agent, Kiro, Gemini, Qoder, CodeBuddy, Copilot, Droid, Pi, Oh My Pi, ZCode, Reasonix, Trae]
364
+ [/Claude Code, Cursor, OpenCode, codex-sub-agent, Kiro, Gemini, Qoder, CodeBuddy, Copilot, Droid, Pi, Oh My Pi, ZCode, Reasonix, Trae, Grok, Kimi Code]
365
365
 
366
366
  [codex-inline, Kilo, Antigravity, Devin]
367
367
 
368
- Do the research in the main session directly and write findings into `{TASK_DIR}/research/`. (For `codex-inline` this avoids the `fork_turns="none"` isolation that prevents `trellis-research` sub-agents from resolving the active task path.)
368
+ Do the research in the main session directly and write findings into `{TASK_DIR}/research/`. `codex-inline` is the explicit mode that keeps work in the main session.
369
369
 
370
370
  [/codex-inline, Kilo, Antigravity, Devin]
371
371
 
@@ -380,7 +380,7 @@ Brainstorm and research can interleave freely — pause to research a technical
380
380
 
381
381
  #### 1.3 Configure context `[required · once]`
382
382
 
383
- [Claude Code, Cursor, OpenCode, codex-sub-agent, Kiro, Gemini, Qoder, CodeBuddy, Copilot, Droid, Pi, Oh My Pi, ZCode, Reasonix, Trae]
383
+ [Claude Code, Cursor, OpenCode, codex-sub-agent, Kiro, Gemini, Qoder, CodeBuddy, Copilot, Droid, Pi, Oh My Pi, ZCode, Reasonix, Trae, Grok, Kimi Code]
384
384
 
385
385
  Curate `implement.jsonl` and `check.jsonl` so the Phase 2 sub-agents get the right spec/research context. These files were seeded on `task create` with a single self-describing `_example` line; your job here is to fill in real entries.
386
386
 
@@ -425,7 +425,7 @@ Ready gate: both `implement.jsonl` and `check.jsonl` must contain at least one r
425
425
 
426
426
  Skip this step only when both files already have real curated entries.
427
427
 
428
- [/Claude Code, Cursor, OpenCode, codex-sub-agent, Kiro, Gemini, Qoder, CodeBuddy, Copilot, Droid, Pi, Oh My Pi, ZCode, Reasonix, Trae]
428
+ [/Claude Code, Cursor, OpenCode, codex-sub-agent, Kiro, Gemini, Qoder, CodeBuddy, Copilot, Droid, Pi, Oh My Pi, ZCode, Reasonix, Trae, Grok, Kimi Code]
429
429
 
430
430
  [codex-inline, Kilo, Antigravity, Devin]
431
431
 
@@ -458,11 +458,11 @@ If `task.py start` errors with a session-identity message (no context key from h
458
458
  | `design.md` exists (complex tasks) | ✅ |
459
459
  | `implement.md` exists (complex tasks) | ✅ |
460
460
 
461
- [Claude Code, Cursor, OpenCode, codex-sub-agent, Kiro, Gemini, Qoder, CodeBuddy, Copilot, Droid, Pi, Oh My Pi, ZCode, Reasonix, Trae]
461
+ [Claude Code, Cursor, OpenCode, codex-sub-agent, Kiro, Gemini, Qoder, CodeBuddy, Copilot, Droid, Pi, Oh My Pi, ZCode, Reasonix, Trae, Grok, Kimi Code]
462
462
 
463
463
  | `implement.jsonl` and `check.jsonl` each contain at least one real curated entry (seed row does not count) | ✅ |
464
464
 
465
- [/Claude Code, Cursor, OpenCode, codex-sub-agent, Kiro, Gemini, Qoder, CodeBuddy, Copilot, Droid, Pi, Oh My Pi, ZCode, Reasonix, Trae]
465
+ [/Claude Code, Cursor, OpenCode, codex-sub-agent, Kiro, Gemini, Qoder, CodeBuddy, Copilot, Droid, Pi, Oh My Pi, ZCode, Reasonix, Trae, Grok, Kimi Code]
466
466
 
467
467
  ---
468
468
 
@@ -472,21 +472,22 @@ Goal: turn reviewed planning artifacts into code that passes quality checks.
472
472
 
473
473
  #### 2.1 Implement `[required · repeatable]`
474
474
 
475
- [Claude Code, Cursor, OpenCode, CodeBuddy, Droid, Pi, Oh My Pi]
475
+ [Claude Code, Cursor, OpenCode, codex-sub-agent, CodeBuddy, Droid, Pi, ZCode, Oh My Pi]
476
476
 
477
477
  Spawn the implement sub-agent:
478
478
 
479
479
  - **Agent type**: `trellis-implement`
480
480
  - **Task description**: Implement the reviewed task artifacts, consulting materials under `{TASK_DIR}/research/`; finish by running project lint and type-check
481
- - **Dispatch prompt guard**: Tell the spawned agent it is already the `trellis-implement` sub-agent and must implement directly, not spawn another `trellis-implement` / `trellis-check`.
481
+ - **Dispatch prompt guard**: The prompt MUST start with `Active task: <task path>`, then tell the spawned agent it is already the `trellis-implement` sub-agent and must implement directly, not spawn another `trellis-implement` / `trellis-check`.
482
482
 
483
483
  The platform hook/plugin auto-handles:
484
484
  - Reads `implement.jsonl` and injects referenced spec/research files into the agent prompt
485
485
  - Injects `prd.md`, `design.md` if present, and `implement.md` if present
486
+ - For Codex, `SubagentStart` supplies native context injection; the agent profile keeps child-side loading as the fallback
486
487
 
487
- [/Claude Code, Cursor, OpenCode, CodeBuddy, Droid, Pi, Oh My Pi]
488
+ [/Claude Code, Cursor, OpenCode, codex-sub-agent, CodeBuddy, Droid, Pi, ZCode, Oh My Pi]
488
489
 
489
- [codex-sub-agent, Gemini, Qoder, Copilot, ZCode, Reasonix, Trae]
490
+ [Gemini, Qoder, Copilot, Reasonix, Trae, Grok, Kimi Code]
490
491
 
491
492
  Spawn the implement sub-agent:
492
493
 
@@ -498,7 +499,7 @@ The pull-based sub-agent definition auto-handles the context load requirement:
498
499
  - Resolves the active task with `task.py current --source`, then reads `prd.md`, `design.md` if present, and `implement.md` if present
499
500
  - Reads `implement.jsonl` and requires the agent to load each referenced spec/research file before coding
500
501
 
501
- [/codex-sub-agent, Gemini, Qoder, Copilot, ZCode, Reasonix, Trae]
502
+ [/Gemini, Qoder, Copilot, Reasonix, Trae, Grok, Kimi Code]
502
503
 
503
504
  [Kiro]
504
505
 
@@ -526,13 +527,13 @@ The platform prelude auto-handles the context load requirement:
526
527
 
527
528
  #### 2.2 Quality check `[required · repeatable]`
528
529
 
529
- [Claude Code, Cursor, OpenCode, codex-sub-agent, Kiro, Gemini, Qoder, CodeBuddy, Copilot, Droid, Pi, Oh My Pi, ZCode, Reasonix, Trae]
530
+ [Claude Code, Cursor, OpenCode, codex-sub-agent, Kiro, Gemini, Qoder, CodeBuddy, Copilot, Droid, Pi, Oh My Pi, ZCode, Reasonix, Trae, Grok, Kimi Code]
530
531
 
531
532
  Spawn the check sub-agent:
532
533
 
533
534
  - **Agent type**: `trellis-check`
534
535
  - **Task description**: Review all code changes against specs and task artifacts; fix any findings directly; ensure lint and type-check pass
535
- - **Dispatch prompt guard**: Tell the spawned agent it is already the `trellis-check` sub-agent and must review/fix directly, not spawn another `trellis-check` / `trellis-implement`.
536
+ - **Dispatch prompt guard**: The prompt MUST start with `Active task: <task path>`, then tell the spawned agent it is already the `trellis-check` sub-agent and must review/fix directly, not spawn another `trellis-check` / `trellis-implement`.
536
537
 
537
538
  The check agent's job:
538
539
  - Review code changes against specs
@@ -540,7 +541,7 @@ The check agent's job:
540
541
  - Auto-fix issues it finds
541
542
  - Run lint and typecheck to verify
542
543
 
543
- [/Claude Code, Cursor, OpenCode, codex-sub-agent, Kiro, Gemini, Qoder, CodeBuddy, Copilot, Droid, Pi, Oh My Pi, ZCode, Reasonix, Trae]
544
+ [/Claude Code, Cursor, OpenCode, codex-sub-agent, Kiro, Gemini, Qoder, CodeBuddy, Copilot, Droid, Pi, Oh My Pi, ZCode, Reasonix, Trae, Grok, Kimi Code]
544
545
 
545
546
  [codex-inline, Kilo, Antigravity, Devin]
546
547
 
@@ -16,6 +16,13 @@ You are already the `trellis-check` sub-agent that the main session dispatched.
16
16
  - If SessionStart context, workflow-state breadcrumbs, or workflow.md say to dispatch `trellis-implement` / `trellis-check`, treat that as a main-session instruction that is already satisfied by your current role.
17
17
  - Only the main session may dispatch Trellis implement/check agents. If more implementation work is needed, report that recommendation instead of spawning.
18
18
 
19
+ ## Trellis Context Loading Protocol
20
+
21
+ Look for the `<!-- trellis-hook-injected -->` marker in your input above.
22
+
23
+ - **If the marker is present**: prd / spec / research files have already been auto-loaded for you above. Proceed with the check work directly.
24
+ - **If the marker is absent**: hook injection didn't fire. Find the active task path from your dispatch prompt's first line `Active task: <path>`, then Read `<task-path>/check.jsonl`, each listed file, `<task-path>/prd.md`, `<task-path>/design.md` if present, and `<task-path>/implement.md` if present before checking the work.
25
+
19
26
  ## Context
20
27
 
21
28
  Before checking, read: