@mindfoldhq/trellis 0.6.7 → 0.6.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/README.md +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +38 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/channel/adapters/codex.d.ts +7 -1
- package/dist/commands/channel/adapters/codex.d.ts.map +1 -1
- package/dist/commands/channel/adapters/codex.js +19 -2
- package/dist/commands/channel/adapters/codex.js.map +1 -1
- package/dist/commands/channel/adapters/index.d.ts +3 -1
- package/dist/commands/channel/adapters/index.d.ts.map +1 -1
- package/dist/commands/channel/adapters/index.js +1 -1
- package/dist/commands/channel/adapters/index.js.map +1 -1
- package/dist/commands/channel/agent-loader.d.ts +2 -2
- package/dist/commands/channel/agent-loader.d.ts.map +1 -1
- package/dist/commands/channel/agent-loader.js +9 -5
- package/dist/commands/channel/agent-loader.js.map +1 -1
- package/dist/commands/channel/context-loader.d.ts +1 -1
- package/dist/commands/channel/context-loader.d.ts.map +1 -1
- package/dist/commands/channel/context-loader.js +36 -15
- package/dist/commands/channel/context-loader.js.map +1 -1
- package/dist/commands/channel/context-trust.d.ts +31 -0
- package/dist/commands/channel/context-trust.d.ts.map +1 -0
- package/dist/commands/channel/context-trust.js +139 -0
- package/dist/commands/channel/context-trust.js.map +1 -0
- package/dist/commands/channel/index.d.ts.map +1 -1
- package/dist/commands/channel/index.js +4 -0
- package/dist/commands/channel/index.js.map +1 -1
- package/dist/commands/channel/spawn.d.ts +3 -0
- package/dist/commands/channel/spawn.d.ts.map +1 -1
- package/dist/commands/channel/spawn.js +5 -2
- package/dist/commands/channel/spawn.js.map +1 -1
- package/dist/commands/channel/supervisor.d.ts +3 -0
- package/dist/commands/channel/supervisor.d.ts.map +1 -1
- package/dist/commands/channel/supervisor.js +1 -0
- package/dist/commands/channel/supervisor.js.map +1 -1
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/mem.d.ts.map +1 -1
- package/dist/commands/mem.js +30 -9
- package/dist/commands/mem.js.map +1 -1
- package/dist/commands/update.d.ts +12 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +75 -7
- package/dist/commands/update.js.map +1 -1
- package/dist/configurators/codex.d.ts +36 -0
- package/dist/configurators/codex.d.ts.map +1 -1
- package/dist/configurators/codex.js +111 -13
- package/dist/configurators/codex.js.map +1 -1
- package/dist/configurators/grok.d.ts +22 -0
- package/dist/configurators/grok.d.ts.map +1 -0
- package/dist/configurators/grok.js +58 -0
- package/dist/configurators/grok.js.map +1 -0
- package/dist/configurators/index.d.ts.map +1 -1
- package/dist/configurators/index.js +17 -2
- package/dist/configurators/index.js.map +1 -1
- package/dist/configurators/kimi.d.ts +29 -0
- package/dist/configurators/kimi.d.ts.map +1 -0
- package/dist/configurators/kimi.js +81 -0
- package/dist/configurators/kimi.js.map +1 -0
- package/dist/configurators/pi.js +9 -9
- package/dist/configurators/pi.js.map +1 -1
- package/dist/configurators/shared.d.ts.map +1 -1
- package/dist/configurators/shared.js +12 -4
- package/dist/configurators/shared.js.map +1 -1
- package/dist/configurators/snow.d.ts +31 -0
- package/dist/configurators/snow.d.ts.map +1 -0
- package/dist/configurators/snow.js +95 -0
- package/dist/configurators/snow.js.map +1 -0
- package/dist/configurators/workflow.d.ts +14 -0
- package/dist/configurators/workflow.d.ts.map +1 -1
- package/dist/configurators/workflow.js +37 -1
- package/dist/configurators/workflow.js.map +1 -1
- package/dist/configurators/zcode.d.ts +9 -4
- package/dist/configurators/zcode.d.ts.map +1 -1
- package/dist/configurators/zcode.js +41 -12
- package/dist/configurators/zcode.js.map +1 -1
- package/dist/migrations/manifests/0.5.0-beta.0.json +1 -1
- package/dist/migrations/manifests/0.6.8.json +17 -0
- package/dist/migrations/manifests/0.6.9.json +9 -0
- package/dist/templates/codex/agents/trellis-check.toml +7 -12
- package/dist/templates/codex/agents/trellis-implement.toml +7 -12
- package/dist/templates/codex/agents/trellis-research.toml +15 -15
- package/dist/templates/codex/config.toml +11 -0
- package/dist/templates/codex/hooks/session-start.py +9 -3
- package/dist/templates/codex/hooks.json +12 -0
- package/dist/templates/codex/skills/brainstorm/SKILL.md +40 -13
- package/dist/templates/common/bundled-skills/trellis-meta/SKILL.md +2 -2
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-skills-or-commands.md +1 -1
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/agents.md +1 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/hooks-and-settings.md +1 -1
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/overview.md +1 -1
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/platform-map.md +10 -4
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/skills-and-commands.md +2 -1
- package/dist/templates/common/bundled-skills/trellis-session-insight/SKILL.md +1 -1
- package/dist/templates/common/skills/brainstorm.md +40 -13
- package/dist/templates/copilot/prompts/brainstorm.prompt.md +40 -13
- package/dist/templates/grok/agents/trellis-check.md +105 -0
- package/dist/templates/grok/agents/trellis-implement.md +116 -0
- package/dist/templates/grok/agents/trellis-research.md +146 -0
- package/dist/templates/grok/index.d.ts +16 -0
- package/dist/templates/grok/index.d.ts.map +1 -0
- package/dist/templates/grok/index.js +19 -0
- package/dist/templates/grok/index.js.map +1 -0
- package/dist/templates/kimi/agents/trellis-check.md +103 -0
- package/dist/templates/kimi/agents/trellis-implement.md +114 -0
- package/dist/templates/kimi/agents/trellis-research.md +144 -0
- package/dist/templates/kimi/index.d.ts +19 -0
- package/dist/templates/kimi/index.d.ts.map +1 -0
- package/dist/templates/kimi/index.js +22 -0
- package/dist/templates/kimi/index.js.map +1 -0
- package/dist/templates/omp/extensions/trellis/index.ts.txt +133 -9
- package/dist/templates/opencode/lib/session-utils.js +8 -2
- package/dist/templates/opencode/lib/trellis-context.js +263 -43
- package/dist/templates/opencode/plugins/inject-subagent-context.js +89 -35
- package/dist/templates/opencode/plugins/inject-workflow-state.js +90 -5
- package/dist/templates/pi/extensions/trellis/index.ts.txt +278 -24
- package/dist/templates/pi/settings.json +0 -3
- package/dist/templates/shared-hooks/index.d.ts +11 -5
- package/dist/templates/shared-hooks/index.d.ts.map +1 -1
- package/dist/templates/shared-hooks/index.js +16 -5
- package/dist/templates/shared-hooks/index.js.map +1 -1
- package/dist/templates/shared-hooks/inject-subagent-context.py +466 -104
- package/dist/templates/shared-hooks/inject-workflow-state.py +86 -29
- package/dist/templates/shared-hooks/session-start.py +26 -6
- package/dist/templates/snow/SNOW.md +86 -0
- package/dist/templates/snow/agents/trellis-check.md +135 -0
- package/dist/templates/snow/agents/trellis-implement.md +153 -0
- package/dist/templates/snow/agents/trellis-research.md +114 -0
- package/dist/templates/snow/hooks/beforeSubAgentStart.json +15 -0
- package/dist/templates/snow/hooks/onSessionStart.json +15 -0
- package/dist/templates/snow/hooks/onUserMessage.json +15 -0
- package/dist/templates/snow/hooks/write-trellis-context.py +644 -0
- package/dist/templates/snow/index.d.ts +26 -0
- package/dist/templates/snow/index.d.ts.map +1 -0
- package/dist/templates/snow/index.js +36 -0
- package/dist/templates/snow/index.js.map +1 -0
- package/dist/templates/template-utils.d.ts.map +1 -1
- package/dist/templates/template-utils.js +5 -1
- package/dist/templates/template-utils.js.map +1 -1
- package/dist/templates/trellis/config.yaml +55 -7
- package/dist/templates/trellis/gitattributes.txt +9 -0
- package/dist/templates/trellis/index.d.ts +3 -1
- package/dist/templates/trellis/index.d.ts.map +1 -1
- package/dist/templates/trellis/index.js +3 -1
- package/dist/templates/trellis/index.js.map +1 -1
- package/dist/templates/trellis/scripts/add_session.py +94 -28
- package/dist/templates/trellis/scripts/common/active_task.py +44 -13
- package/dist/templates/trellis/scripts/common/cli_adapter.py +69 -5
- package/dist/templates/trellis/scripts/common/config.py +105 -10
- package/dist/templates/trellis/scripts/common/git.py +36 -0
- package/dist/templates/trellis/scripts/common/io.py +16 -9
- package/dist/templates/trellis/scripts/common/task_context.py +101 -7
- package/dist/templates/trellis/scripts/common/task_store.py +149 -22
- package/dist/templates/trellis/scripts/common/task_utils.py +3 -1
- package/dist/templates/trellis/scripts/common/trellis_config.py +2 -1
- package/dist/templates/trellis/scripts/common/workflow_phase.py +19 -12
- package/dist/templates/trellis/scripts/task.py +106 -10
- package/dist/templates/trellis/workflow.md +20 -19
- package/dist/templates/zcode/agents/trellis-check.md +7 -0
- package/dist/templates/zcode/agents/trellis-implement.md +13 -4
- package/dist/templates/zcode/config.json +42 -0
- package/dist/templates/zcode/index.d.ts +10 -1
- package/dist/templates/zcode/index.d.ts.map +1 -1
- package/dist/templates/zcode/index.js +12 -2
- package/dist/templates/zcode/index.js.map +1 -1
- package/dist/types/ai-tools.d.ts +4 -4
- package/dist/types/ai-tools.d.ts.map +1 -1
- package/dist/types/ai-tools.js +114 -2
- package/dist/types/ai-tools.js.map +1 -1
- package/dist/utils/template-fetcher.d.ts.map +1 -1
- package/dist/utils/template-fetcher.js +0 -3
- package/dist/utils/template-fetcher.js.map +1 -1
- package/package.json +2 -2
|
@@ -9,6 +9,7 @@ Provides:
|
|
|
9
9
|
cmd_set_branch - Set git branch for task
|
|
10
10
|
cmd_set_base_branch - Set PR target branch
|
|
11
11
|
cmd_set_scope - Set scope for PR title
|
|
12
|
+
cmd_set_meta - Set/overwrite a task metadata key
|
|
12
13
|
cmd_add_subtask - Link child task to parent
|
|
13
14
|
cmd_remove_subtask - Unlink child task from parent
|
|
14
15
|
"""
|
|
@@ -30,7 +31,7 @@ from .config import (
|
|
|
30
31
|
resolve_package,
|
|
31
32
|
validate_package,
|
|
32
33
|
)
|
|
33
|
-
from .git import run_git
|
|
34
|
+
from .git import branch_exists_locally, resolve_default_branch, run_git
|
|
34
35
|
from .io import read_json, write_json
|
|
35
36
|
from .log import Colors, colored
|
|
36
37
|
from .paths import (
|
|
@@ -116,7 +117,7 @@ def _repo_relative_path(path: Path, repo_root: Path) -> str:
|
|
|
116
117
|
# Config directories of platforms that consume implement.jsonl / check.jsonl.
|
|
117
118
|
# Keep in sync with src/types/ai-tools.ts AI_TOOLS entries — these are the
|
|
118
119
|
# platforms listed in workflow.md's "agent-capable" Skill Routing block.
|
|
119
|
-
# Codex is checked separately because
|
|
120
|
+
# Codex is checked separately because explicit inline mode does not consume
|
|
120
121
|
# JSONL. Kilo / Antigravity / Devin are NOT in this list either: they load
|
|
121
122
|
# specs through skills instead of JSONL.
|
|
122
123
|
_SUBAGENT_CONFIG_DIRS: tuple[str, ...] = (
|
|
@@ -133,6 +134,8 @@ _SUBAGENT_CONFIG_DIRS: tuple[str, ...] = (
|
|
|
133
134
|
".trae", # Trae IDE
|
|
134
135
|
".omp", # Oh My Pi
|
|
135
136
|
".zcode", # ZCode
|
|
137
|
+
".grok", # Grok Build
|
|
138
|
+
".kimi-code", # Kimi Code
|
|
136
139
|
)
|
|
137
140
|
_CODEX_CONFIG_DIR = ".codex"
|
|
138
141
|
|
|
@@ -148,14 +151,15 @@ def _has_subagent_platform(repo_root: Path) -> bool:
|
|
|
148
151
|
"""Return True if any sub-agent-capable platform is configured.
|
|
149
152
|
|
|
150
153
|
Detected by probing well-known config directories at the repo root. Codex
|
|
151
|
-
|
|
152
|
-
``sub-agent
|
|
154
|
+
counts by default through ``codex.dispatch_mode: auto`` (including the
|
|
155
|
+
legacy ``sub-agent`` alias); explicit inline mode loads context through
|
|
156
|
+
skills, not JSONL.
|
|
153
157
|
"""
|
|
154
158
|
for config_dir in _SUBAGENT_CONFIG_DIRS:
|
|
155
159
|
if (repo_root / config_dir).is_dir():
|
|
156
160
|
return True
|
|
157
161
|
if (repo_root / _CODEX_CONFIG_DIR).is_dir():
|
|
158
|
-
return get_codex_dispatch_mode(repo_root) == "
|
|
162
|
+
return get_codex_dispatch_mode(repo_root) == "auto"
|
|
159
163
|
return False
|
|
160
164
|
|
|
161
165
|
|
|
@@ -170,6 +174,26 @@ def _write_seed_jsonl(path: Path) -> None:
|
|
|
170
174
|
path.write_text(json.dumps(seed, ensure_ascii=False) + "\n", encoding="utf-8")
|
|
171
175
|
|
|
172
176
|
|
|
177
|
+
def _parse_meta_pairs(pairs: list[str] | None) -> dict[str, str] | None:
|
|
178
|
+
"""Parse repeatable ``--meta key=value`` pairs into a dict.
|
|
179
|
+
|
|
180
|
+
Returns ``None`` (after printing an error naming the bad value) on the
|
|
181
|
+
first malformed pair: missing ``=`` or an empty key. Values are stored
|
|
182
|
+
as-is (strings, no nesting, no type coercion).
|
|
183
|
+
"""
|
|
184
|
+
meta: dict[str, str] = {}
|
|
185
|
+
for pair in pairs or []:
|
|
186
|
+
key, sep, value = pair.partition("=")
|
|
187
|
+
if not sep or not key:
|
|
188
|
+
print(
|
|
189
|
+
colored(f"Error: malformed --meta value '{pair}' (expected key=value)", Colors.RED),
|
|
190
|
+
file=sys.stderr,
|
|
191
|
+
)
|
|
192
|
+
return None
|
|
193
|
+
meta[key] = value
|
|
194
|
+
return meta
|
|
195
|
+
|
|
196
|
+
|
|
173
197
|
def _default_prd_content(title: str, description: str | None = None) -> str:
|
|
174
198
|
"""Return the default PRD skeleton created with every task."""
|
|
175
199
|
goal = (description or "").strip() or "TBD."
|
|
@@ -208,6 +232,11 @@ def cmd_create(args: argparse.Namespace) -> int:
|
|
|
208
232
|
print(colored("Error: title is required", Colors.RED), file=sys.stderr)
|
|
209
233
|
return 1
|
|
210
234
|
|
|
235
|
+
# Validate --meta (CLI source: fail-fast, before any directory is created)
|
|
236
|
+
meta = _parse_meta_pairs(getattr(args, "meta", None))
|
|
237
|
+
if meta is None:
|
|
238
|
+
return 1
|
|
239
|
+
|
|
211
240
|
# Validate --package (CLI source: fail-fast)
|
|
212
241
|
package: str | None = getattr(args, "package", None)
|
|
213
242
|
if not is_monorepo(repo_root):
|
|
@@ -293,9 +322,32 @@ def cmd_create(args: argparse.Namespace) -> int:
|
|
|
293
322
|
|
|
294
323
|
today = datetime.now().strftime("%Y-%m-%d")
|
|
295
324
|
|
|
296
|
-
# Record
|
|
325
|
+
# Record the PR target branch. Prefer the repo's actual default branch
|
|
326
|
+
# (origin/HEAD) so creating a task from a feature branch doesn't
|
|
327
|
+
# mis-stamp that feature branch as the PR target (#399 item 1). Falls
|
|
328
|
+
# back to the checked-out branch when the default can't be resolved
|
|
329
|
+
# (no remote configured, offline, etc.) — the pre-existing behavior.
|
|
330
|
+
# --base-branch lets the caller override both when neither is correct.
|
|
297
331
|
_, branch_out, _ = run_git(["branch", "--show-current"], cwd=repo_root)
|
|
298
332
|
current_branch = branch_out.strip() or "main"
|
|
333
|
+
explicit_base_branch: str | None = getattr(args, "base_branch", None)
|
|
334
|
+
if explicit_base_branch:
|
|
335
|
+
base_branch = explicit_base_branch
|
|
336
|
+
else:
|
|
337
|
+
resolved_base_branch = resolve_default_branch(repo_root)
|
|
338
|
+
if resolved_base_branch:
|
|
339
|
+
base_branch = resolved_base_branch
|
|
340
|
+
else:
|
|
341
|
+
base_branch = current_branch
|
|
342
|
+
print(
|
|
343
|
+
colored(
|
|
344
|
+
f"warning: could not resolve the repository's default branch "
|
|
345
|
+
f"(no remote configured, offline, etc.); stamping base_branch as "
|
|
346
|
+
f"the checked-out branch '{base_branch}'. Pass --base-branch to override.",
|
|
347
|
+
Colors.YELLOW,
|
|
348
|
+
),
|
|
349
|
+
file=sys.stderr,
|
|
350
|
+
)
|
|
299
351
|
|
|
300
352
|
description = (args.description or "").strip()
|
|
301
353
|
if not description.strip():
|
|
@@ -322,7 +374,7 @@ def cmd_create(args: argparse.Namespace) -> int:
|
|
|
322
374
|
"createdAt": today,
|
|
323
375
|
"completedAt": None,
|
|
324
376
|
"branch": None,
|
|
325
|
-
"base_branch":
|
|
377
|
+
"base_branch": base_branch,
|
|
326
378
|
"worktree_path": None,
|
|
327
379
|
"commit": None,
|
|
328
380
|
"pr_url": None,
|
|
@@ -331,7 +383,7 @@ def cmd_create(args: argparse.Namespace) -> int:
|
|
|
331
383
|
"parent": None,
|
|
332
384
|
"relatedFiles": [],
|
|
333
385
|
"notes": "",
|
|
334
|
-
"meta":
|
|
386
|
+
"meta": meta,
|
|
335
387
|
}
|
|
336
388
|
|
|
337
389
|
write_json(task_json_path, task_data)
|
|
@@ -393,20 +445,46 @@ def cmd_create(args: argparse.Namespace) -> int:
|
|
|
393
445
|
else:
|
|
394
446
|
try:
|
|
395
447
|
from .active_task import resolve_context_key, set_active_task
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
)
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
448
|
+
except Exception as exc:
|
|
449
|
+
print(
|
|
450
|
+
colored(f"Warning: session activation unavailable (import failed: {exc})", Colors.YELLOW),
|
|
451
|
+
file=sys.stderr,
|
|
452
|
+
)
|
|
453
|
+
else:
|
|
454
|
+
try:
|
|
455
|
+
context_key = resolve_context_key()
|
|
456
|
+
except Exception as exc:
|
|
457
|
+
print(
|
|
458
|
+
colored(f"Warning: session activation failed (context resolution: {exc})", Colors.YELLOW),
|
|
459
|
+
file=sys.stderr,
|
|
460
|
+
)
|
|
461
|
+
else:
|
|
462
|
+
# No session identity is the normal CLI-outside-an-AI-session
|
|
463
|
+
# case (see comment above) — stay silent, not a failure.
|
|
464
|
+
if context_key:
|
|
465
|
+
try:
|
|
466
|
+
rel_dir = task_dir.relative_to(repo_root).as_posix()
|
|
467
|
+
except ValueError:
|
|
468
|
+
rel_dir = str(task_dir)
|
|
469
|
+
try:
|
|
470
|
+
active = set_active_task(rel_dir, repo_root)
|
|
471
|
+
except Exception as exc:
|
|
472
|
+
print(
|
|
473
|
+
colored(f"Warning: session activation failed (pointer persistence: {exc})", Colors.YELLOW),
|
|
474
|
+
file=sys.stderr,
|
|
475
|
+
)
|
|
476
|
+
else:
|
|
477
|
+
if active:
|
|
478
|
+
print(
|
|
479
|
+
colored(f"Activated task for this session: {active.task_path}", Colors.GREEN),
|
|
480
|
+
file=sys.stderr,
|
|
481
|
+
)
|
|
482
|
+
print(f"Source: {active.source}", file=sys.stderr)
|
|
483
|
+
else:
|
|
484
|
+
print(
|
|
485
|
+
colored("Warning: session activation failed (no pointer returned)", Colors.YELLOW),
|
|
486
|
+
file=sys.stderr,
|
|
487
|
+
)
|
|
410
488
|
|
|
411
489
|
print(colored(f"Created task: {dir_name}", Colors.GREEN), file=sys.stderr)
|
|
412
490
|
print("", file=sys.stderr)
|
|
@@ -478,6 +556,19 @@ def cmd_archive(args: argparse.Namespace) -> int:
|
|
|
478
556
|
if task_json_path.is_file():
|
|
479
557
|
data = read_json(task_json_path)
|
|
480
558
|
if data:
|
|
559
|
+
# Warn (don't block) when the recorded branch is stale — it was
|
|
560
|
+
# likely already merged and deleted (#399 item 2).
|
|
561
|
+
stored_branch = data.get("branch")
|
|
562
|
+
if stored_branch and not branch_exists_locally(stored_branch, repo_root):
|
|
563
|
+
print(
|
|
564
|
+
colored(
|
|
565
|
+
f"Warning: recorded branch '{stored_branch}' no longer exists locally "
|
|
566
|
+
"(likely merged and deleted).",
|
|
567
|
+
Colors.YELLOW,
|
|
568
|
+
),
|
|
569
|
+
file=sys.stderr,
|
|
570
|
+
)
|
|
571
|
+
|
|
481
572
|
data["status"] = "completed"
|
|
482
573
|
data["completedAt"] = today
|
|
483
574
|
write_json(task_json_path, data)
|
|
@@ -816,3 +907,39 @@ def cmd_set_scope(args: argparse.Namespace) -> int:
|
|
|
816
907
|
|
|
817
908
|
print(colored(f"✓ Scope set to: {scope}", Colors.GREEN))
|
|
818
909
|
return 0
|
|
910
|
+
|
|
911
|
+
|
|
912
|
+
# =============================================================================
|
|
913
|
+
# Command: set-meta
|
|
914
|
+
# =============================================================================
|
|
915
|
+
|
|
916
|
+
def cmd_set_meta(args: argparse.Namespace) -> int:
|
|
917
|
+
"""Set/overwrite one metadata key on an existing task."""
|
|
918
|
+
repo_root = get_repo_root()
|
|
919
|
+
target_dir = resolve_task_dir(args.dir, repo_root)
|
|
920
|
+
key = args.key
|
|
921
|
+
value = args.value
|
|
922
|
+
|
|
923
|
+
if not key:
|
|
924
|
+
print(colored("Error: Missing arguments", Colors.RED))
|
|
925
|
+
print("Usage: python3 task.py set-meta <task-dir> <key> <value>")
|
|
926
|
+
return 1
|
|
927
|
+
|
|
928
|
+
task_json = target_dir / FILE_TASK_JSON
|
|
929
|
+
if not task_json.is_file():
|
|
930
|
+
print(colored(f"Error: task.json not found at {target_dir}", Colors.RED))
|
|
931
|
+
return 1
|
|
932
|
+
|
|
933
|
+
data = read_json(task_json)
|
|
934
|
+
if not data:
|
|
935
|
+
return 1
|
|
936
|
+
|
|
937
|
+
meta = data.get("meta")
|
|
938
|
+
if not isinstance(meta, dict):
|
|
939
|
+
meta = {}
|
|
940
|
+
meta[key] = value
|
|
941
|
+
data["meta"] = meta
|
|
942
|
+
write_json(task_json, data)
|
|
943
|
+
|
|
944
|
+
print(colored(f"✓ Meta set: {key} = {value}", Colors.GREEN))
|
|
945
|
+
return 0
|
|
@@ -88,7 +88,9 @@ def is_within_tasks_dir(task_dir_abs: Path, repo_root: Path | None = None) -> bo
|
|
|
88
88
|
tasks_resolved = get_tasks_dir(repo_root).resolve()
|
|
89
89
|
except (OSError, RuntimeError):
|
|
90
90
|
return False
|
|
91
|
-
|
|
91
|
+
if resolved.parent != tasks_resolved:
|
|
92
|
+
return False
|
|
93
|
+
return resolved.name != "archive"
|
|
92
94
|
|
|
93
95
|
|
|
94
96
|
# =============================================================================
|
|
@@ -78,10 +78,11 @@ def _parse_yaml_block(
|
|
|
78
78
|
key, _, value = stripped.partition(":")
|
|
79
79
|
key = key.strip()
|
|
80
80
|
value = _strip_inline_comment(value).strip()
|
|
81
|
+
was_quoted = len(value) >= 2 and value[0] == value[-1] and value[0] in ('"', "'")
|
|
81
82
|
value = _unquote(value)
|
|
82
83
|
current_list = None
|
|
83
84
|
|
|
84
|
-
if value:
|
|
85
|
+
if value or was_quoted:
|
|
85
86
|
target[key] = value
|
|
86
87
|
i += 1
|
|
87
88
|
else:
|
|
@@ -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-
|
|
148
|
-
or ``"codex-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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 = "
|
|
161
|
+
mode = "auto"
|
|
162
162
|
codex_cfg = config.get("codex") if isinstance(config, dict) else None
|
|
163
|
-
if
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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,11 +9,12 @@ 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]
|
|
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
|
|
16
16
|
python3 task.py set-scope <dir> <scope> # Set scope for PR title
|
|
17
|
+
python3 task.py set-meta <dir> <key> <value> # Set a task metadata key
|
|
17
18
|
python3 task.py archive <task-dir> # Archive completed task
|
|
18
19
|
python3 task.py list # List active tasks
|
|
19
20
|
python3 task.py list-archive [month] # List archived tasks
|
|
@@ -24,6 +25,7 @@ Usage:
|
|
|
24
25
|
from __future__ import annotations
|
|
25
26
|
|
|
26
27
|
import argparse
|
|
28
|
+
import json
|
|
27
29
|
import sys
|
|
28
30
|
|
|
29
31
|
from common.log import Colors, colored
|
|
@@ -53,6 +55,7 @@ from common.task_store import (
|
|
|
53
55
|
cmd_set_branch,
|
|
54
56
|
cmd_set_base_branch,
|
|
55
57
|
cmd_set_scope,
|
|
58
|
+
cmd_set_meta,
|
|
56
59
|
cmd_add_subtask,
|
|
57
60
|
cmd_remove_subtask,
|
|
58
61
|
)
|
|
@@ -166,6 +169,27 @@ def cmd_current(args: argparse.Namespace) -> int:
|
|
|
166
169
|
repo_root = get_repo_root()
|
|
167
170
|
active = resolve_active_task(repo_root)
|
|
168
171
|
|
|
172
|
+
if getattr(args, "json", False):
|
|
173
|
+
task_obj = None
|
|
174
|
+
if active.task_path:
|
|
175
|
+
data = read_json(repo_root / active.task_path / FILE_TASK_JSON) or {}
|
|
176
|
+
task_obj = {
|
|
177
|
+
"dir": active.task_path,
|
|
178
|
+
"id": data.get("id") or data.get("name"),
|
|
179
|
+
"title": data.get("title"),
|
|
180
|
+
"status": data.get("status"),
|
|
181
|
+
"parent": data.get("parent"),
|
|
182
|
+
"children": data.get("children", []),
|
|
183
|
+
"branch": data.get("branch"),
|
|
184
|
+
"base_branch": data.get("base_branch"),
|
|
185
|
+
}
|
|
186
|
+
print(json.dumps({
|
|
187
|
+
"current_task": task_obj,
|
|
188
|
+
"source": active.source,
|
|
189
|
+
"stale": active.stale,
|
|
190
|
+
}, ensure_ascii=False))
|
|
191
|
+
return 0 if active.task_path else 1
|
|
192
|
+
|
|
169
193
|
if args.source:
|
|
170
194
|
print(f"Current task: {active.task_path or '(none)'}")
|
|
171
195
|
print(f"Source: {active.source}")
|
|
@@ -184,6 +208,24 @@ def cmd_current(args: argparse.Namespace) -> int:
|
|
|
184
208
|
# Command: list
|
|
185
209
|
# =============================================================================
|
|
186
210
|
|
|
211
|
+
def _display_status(t, all_statuses: dict) -> str:
|
|
212
|
+
"""Return the status label to show for a task in `list` output.
|
|
213
|
+
|
|
214
|
+
A parent task's stored status stays "planning" until someone runs
|
|
215
|
+
`task.py start` on the parent directly, even while its children are
|
|
216
|
+
actively being worked — a misleading label for anyone scanning the
|
|
217
|
+
list (#399 item 3). Show "active" instead when at least one child is
|
|
218
|
+
past planning; the stored status.json value is left untouched.
|
|
219
|
+
"""
|
|
220
|
+
if t.status == "planning" and t.children:
|
|
221
|
+
child_in_flight = any(
|
|
222
|
+
all_statuses.get(c) not in (None, "planning") for c in t.children
|
|
223
|
+
)
|
|
224
|
+
if child_in_flight:
|
|
225
|
+
return "active"
|
|
226
|
+
return t.status
|
|
227
|
+
|
|
228
|
+
|
|
187
229
|
def cmd_list(args: argparse.Namespace) -> int:
|
|
188
230
|
"""List active tasks."""
|
|
189
231
|
repo_root = get_repo_root()
|
|
@@ -192,6 +234,38 @@ def cmd_list(args: argparse.Namespace) -> int:
|
|
|
192
234
|
developer = get_developer(repo_root)
|
|
193
235
|
filter_mine = args.mine
|
|
194
236
|
filter_status = args.status
|
|
237
|
+
as_json = getattr(args, "json", False)
|
|
238
|
+
|
|
239
|
+
# Single pass: collect all tasks via shared iterator
|
|
240
|
+
all_tasks = {t.dir_name: t for t in iter_active_tasks(tasks_dir)}
|
|
241
|
+
all_statuses = {name: t.status for name, t in all_tasks.items()}
|
|
242
|
+
|
|
243
|
+
if as_json:
|
|
244
|
+
if filter_mine and not developer:
|
|
245
|
+
print(json.dumps({"error": "No developer set"}), file=sys.stderr)
|
|
246
|
+
return 1
|
|
247
|
+
|
|
248
|
+
items = []
|
|
249
|
+
for dir_name in sorted(all_tasks.keys()):
|
|
250
|
+
t = all_tasks[dir_name]
|
|
251
|
+
if filter_mine and (t.assignee or "-") != developer:
|
|
252
|
+
continue
|
|
253
|
+
if filter_status and t.status != filter_status:
|
|
254
|
+
continue
|
|
255
|
+
items.append({
|
|
256
|
+
"dir": f"{DIR_WORKFLOW}/{DIR_TASKS}/{dir_name}",
|
|
257
|
+
"id": t.raw.get("id") or dir_name,
|
|
258
|
+
"title": t.title,
|
|
259
|
+
"status": t.status,
|
|
260
|
+
"display_status": _display_status(t, all_statuses),
|
|
261
|
+
"priority": t.priority,
|
|
262
|
+
"assignee": t.assignee or None,
|
|
263
|
+
"parent": t.parent,
|
|
264
|
+
"children": list(t.children),
|
|
265
|
+
"package": t.package,
|
|
266
|
+
})
|
|
267
|
+
print(json.dumps({"tasks": items}, ensure_ascii=False))
|
|
268
|
+
return 0
|
|
195
269
|
|
|
196
270
|
if filter_mine:
|
|
197
271
|
if not developer:
|
|
@@ -202,10 +276,6 @@ def cmd_list(args: argparse.Namespace) -> int:
|
|
|
202
276
|
print(colored("All active tasks:", Colors.BLUE))
|
|
203
277
|
print()
|
|
204
278
|
|
|
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
279
|
# Display tasks hierarchically
|
|
210
280
|
count = 0
|
|
211
281
|
|
|
@@ -228,6 +298,7 @@ def cmd_list(args: argparse.Namespace) -> int:
|
|
|
228
298
|
|
|
229
299
|
# Children progress
|
|
230
300
|
progress = children_progress(t.children, all_statuses)
|
|
301
|
+
status_label = _display_status(t, all_statuses)
|
|
231
302
|
|
|
232
303
|
# Package tag
|
|
233
304
|
pkg_tag = f" @{t.package}" if t.package else ""
|
|
@@ -235,9 +306,9 @@ def cmd_list(args: argparse.Namespace) -> int:
|
|
|
235
306
|
prefix = " " * indent + " - "
|
|
236
307
|
|
|
237
308
|
if filter_mine:
|
|
238
|
-
print(f"{prefix}{dir_name}/ ({
|
|
309
|
+
print(f"{prefix}{dir_name}/ ({status_label}){pkg_tag}{progress}{marker}")
|
|
239
310
|
else:
|
|
240
|
-
print(f"{prefix}{dir_name}/ ({
|
|
311
|
+
print(f"{prefix}{dir_name}/ ({status_label}){pkg_tag}{progress} [{colored(t.assignee or '-', Colors.CYAN)}]{marker}")
|
|
241
312
|
count += 1
|
|
242
313
|
|
|
243
314
|
# Print children indented
|
|
@@ -245,9 +316,12 @@ def cmd_list(args: argparse.Namespace) -> int:
|
|
|
245
316
|
if child_name in all_tasks:
|
|
246
317
|
_print_task(child_name, indent + 1)
|
|
247
318
|
|
|
248
|
-
# Display only top-level tasks
|
|
319
|
+
# Display only top-level tasks: those without a parent, plus orphans
|
|
320
|
+
# whose recorded parent is not (or no longer) in the active set — a
|
|
321
|
+
# dangling parent ref must still render flat instead of disappearing.
|
|
249
322
|
for dir_name in sorted(all_tasks.keys()):
|
|
250
|
-
|
|
323
|
+
parent = all_tasks[dir_name].parent
|
|
324
|
+
if not parent or parent not in all_tasks:
|
|
251
325
|
_print_task(dir_name)
|
|
252
326
|
|
|
253
327
|
if count == 0:
|
|
@@ -317,10 +391,11 @@ Usage:
|
|
|
317
391
|
python3 task.py set-branch <dir> <branch> Set git branch
|
|
318
392
|
python3 task.py set-base-branch <dir> <branch> Set PR target branch
|
|
319
393
|
python3 task.py set-scope <dir> <scope> Set scope for PR title
|
|
394
|
+
python3 task.py set-meta <dir> <key> <value> Set/overwrite a task metadata key
|
|
320
395
|
python3 task.py archive <task-dir> Archive completed task
|
|
321
396
|
python3 task.py add-subtask <parent> <child> Link child task to parent
|
|
322
397
|
python3 task.py remove-subtask <parent> <child> Unlink child from parent
|
|
323
|
-
python3 task.py list [--mine] [--status <status>] List tasks
|
|
398
|
+
python3 task.py list [--mine] [--status <status>] [--json] List tasks
|
|
324
399
|
python3 task.py list-archive [YYYY-MM] List archived tasks
|
|
325
400
|
|
|
326
401
|
Monorepo options:
|
|
@@ -329,10 +404,12 @@ Monorepo options:
|
|
|
329
404
|
List options:
|
|
330
405
|
--mine, -m Show only tasks assigned to current developer
|
|
331
406
|
--status, -s <s> Filter by status (planning, in_progress, review, completed)
|
|
407
|
+
--json Output machine-readable JSON (also available on `current`)
|
|
332
408
|
|
|
333
409
|
Examples:
|
|
334
410
|
python3 task.py create "Add login feature" --slug add-login
|
|
335
411
|
python3 task.py create "Add login feature" --slug add-login --package cli
|
|
412
|
+
python3 task.py create "Add login feature" --meta linear=ENG-123 --meta epic=auth
|
|
336
413
|
python3 task.py create "Child task" --slug child --parent .trellis/tasks/01-21-parent
|
|
337
414
|
python3 task.py add-context <dir> implement .trellis/spec/cli/backend/auth.md "Auth guidelines"
|
|
338
415
|
python3 task.py set-branch <dir> task/add-login
|
|
@@ -399,6 +476,15 @@ def main() -> int:
|
|
|
399
476
|
p_create.add_argument("--description", "-d", help="Task description")
|
|
400
477
|
p_create.add_argument("--parent", help="Parent task directory (establishes subtask link)")
|
|
401
478
|
p_create.add_argument("--package", help="Package name for monorepo projects")
|
|
479
|
+
p_create.add_argument(
|
|
480
|
+
"--base-branch",
|
|
481
|
+
help="PR target branch (overrides origin/HEAD detection and the checked-out-branch fallback)",
|
|
482
|
+
)
|
|
483
|
+
p_create.add_argument(
|
|
484
|
+
"--meta",
|
|
485
|
+
action="append",
|
|
486
|
+
help="Task metadata key=value (repeatable)",
|
|
487
|
+
)
|
|
402
488
|
p_create.add_argument(
|
|
403
489
|
"--no-start",
|
|
404
490
|
action="store_true",
|
|
@@ -428,6 +514,8 @@ def main() -> int:
|
|
|
428
514
|
p_current = subparsers.add_parser("current", help="Show active task")
|
|
429
515
|
p_current.add_argument("--source", action="store_true",
|
|
430
516
|
help="Show active task source")
|
|
517
|
+
p_current.add_argument("--json", action="store_true",
|
|
518
|
+
help="Output machine-readable JSON")
|
|
431
519
|
|
|
432
520
|
# finish
|
|
433
521
|
subparsers.add_parser("finish", help="Clear active task")
|
|
@@ -447,6 +535,12 @@ def main() -> int:
|
|
|
447
535
|
p_scope.add_argument("dir", help="Task directory")
|
|
448
536
|
p_scope.add_argument("scope", help="Scope name")
|
|
449
537
|
|
|
538
|
+
# set-meta
|
|
539
|
+
p_setmeta = subparsers.add_parser("set-meta", help="Set/overwrite a task metadata key")
|
|
540
|
+
p_setmeta.add_argument("dir", help="Task directory")
|
|
541
|
+
p_setmeta.add_argument("key", help="Metadata key")
|
|
542
|
+
p_setmeta.add_argument("value", help="Metadata value")
|
|
543
|
+
|
|
450
544
|
# archive
|
|
451
545
|
p_archive = subparsers.add_parser("archive", help="Archive task")
|
|
452
546
|
p_archive.add_argument("name", help="Task directory or name")
|
|
@@ -456,6 +550,7 @@ def main() -> int:
|
|
|
456
550
|
p_list = subparsers.add_parser("list", help="List tasks")
|
|
457
551
|
p_list.add_argument("--mine", "-m", action="store_true", help="My tasks only")
|
|
458
552
|
p_list.add_argument("--status", "-s", help="Filter by status")
|
|
553
|
+
p_list.add_argument("--json", action="store_true", help="Output machine-readable JSON")
|
|
459
554
|
|
|
460
555
|
# add-subtask
|
|
461
556
|
p_addsub = subparsers.add_parser("add-subtask", help="Link child task to parent")
|
|
@@ -488,6 +583,7 @@ def main() -> int:
|
|
|
488
583
|
"set-branch": cmd_set_branch,
|
|
489
584
|
"set-base-branch": cmd_set_base_branch,
|
|
490
585
|
"set-scope": cmd_set_scope,
|
|
586
|
+
"set-meta": cmd_set_meta,
|
|
491
587
|
"archive": cmd_archive,
|
|
492
588
|
"add-subtask": cmd_add_subtask,
|
|
493
589
|
"remove-subtask": cmd_remove_subtask,
|