@mindfoldhq/trellis 0.6.5 → 0.6.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.
- package/README.md +1 -1
- package/dist/cli/index.js +1 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/channel/supervisor/stdout.d.ts +7 -5
- package/dist/commands/channel/supervisor/stdout.d.ts.map +1 -1
- package/dist/commands/channel/supervisor/stdout.js +65 -18
- package/dist/commands/channel/supervisor/stdout.js.map +1 -1
- package/dist/commands/channel/supervisor.d.ts +13 -0
- package/dist/commands/channel/supervisor.d.ts.map +1 -1
- package/dist/commands/channel/supervisor.js +36 -16
- package/dist/commands/channel/supervisor.js.map +1 -1
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/uninstall.d.ts.map +1 -1
- package/dist/commands/uninstall.js +7 -1
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +60 -31
- package/dist/commands/update.js.map +1 -1
- package/dist/configurators/copilot.d.ts +1 -0
- package/dist/configurators/copilot.d.ts.map +1 -1
- package/dist/configurators/copilot.js +4 -1
- package/dist/configurators/copilot.js.map +1 -1
- package/dist/configurators/index.d.ts.map +1 -1
- package/dist/configurators/index.js +7 -1
- package/dist/configurators/index.js.map +1 -1
- package/dist/configurators/omp.d.ts +11 -0
- package/dist/configurators/omp.d.ts.map +1 -0
- package/dist/configurators/omp.js +51 -0
- package/dist/configurators/omp.js.map +1 -0
- package/dist/configurators/shared.d.ts +7 -0
- package/dist/configurators/shared.d.ts.map +1 -1
- package/dist/configurators/shared.js +27 -0
- package/dist/configurators/shared.js.map +1 -1
- package/dist/configurators/zcode.d.ts +3 -3
- package/dist/configurators/zcode.js +12 -12
- package/dist/configurators/zcode.js.map +1 -1
- package/dist/migrations/manifests/0.6.6.json +17 -0
- package/dist/templates/codex/hooks/session-start.py +6 -6
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-agents.md +1 -1
- 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/local-architecture/bundled-skills.md +2 -1
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/generated-files.md +2 -2
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/agents.md +1 -1
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/overview.md +3 -3
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/platform-map.md +19 -2
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/skills-and-commands.md +1 -1
- package/dist/templates/copilot/copilot-instructions.md +11 -0
- package/dist/templates/copilot/hooks/session-start.py +6 -6
- package/dist/templates/copilot/index.d.ts +6 -1
- package/dist/templates/copilot/index.d.ts.map +1 -1
- package/dist/templates/copilot/index.js +8 -1
- package/dist/templates/copilot/index.js.map +1 -1
- package/dist/templates/omp/agents/trellis-check.md +41 -0
- package/dist/templates/omp/agents/trellis-implement.md +45 -0
- package/dist/templates/omp/agents/trellis-research.md +30 -0
- package/dist/templates/omp/extensions/trellis/index.ts.txt +462 -0
- package/dist/templates/omp/index.d.ts +4 -0
- package/dist/templates/omp/index.d.ts.map +1 -0
- package/dist/templates/omp/index.js +9 -0
- package/dist/templates/omp/index.js.map +1 -0
- package/dist/templates/pi/extensions/trellis/index.ts.txt +47 -21
- package/dist/templates/shared-hooks/inject-workflow-state.py +5 -5
- package/dist/templates/shared-hooks/session-start.py +7 -7
- package/dist/templates/trellis/scripts/add_session.py +65 -17
- package/dist/templates/trellis/scripts/common/cli_adapter.py +36 -1
- package/dist/templates/trellis/scripts/common/config.py +29 -0
- package/dist/templates/trellis/scripts/common/session_context.py +1 -1
- package/dist/templates/trellis/scripts/common/task_store.py +78 -19
- package/dist/templates/trellis/scripts/task.py +8 -2
- package/dist/templates/trellis/workflow.md +13 -13
- package/dist/templates/zcode/agents/trellis-research.md +133 -0
- package/dist/templates/zcode/index.d.ts +6 -5
- package/dist/templates/zcode/index.d.ts.map +1 -1
- package/dist/templates/zcode/index.js +6 -5
- package/dist/templates/zcode/index.js.map +1 -1
- package/dist/types/ai-tools.d.ts +3 -3
- package/dist/types/ai-tools.d.ts.map +1 -1
- package/dist/types/ai-tools.js +29 -2
- package/dist/types/ai-tools.js.map +1 -1
- package/dist/utils/uninstall-scrubbers.d.ts +1 -0
- package/dist/utils/uninstall-scrubbers.d.ts.map +1 -1
- package/dist/utils/uninstall-scrubbers.js +19 -0
- package/dist/utils/uninstall-scrubbers.js.map +1 -1
- package/package.json +2 -2
|
@@ -87,12 +87,12 @@ if sys.platform.startswith("win"):
|
|
|
87
87
|
try:
|
|
88
88
|
_stream.reconfigure(encoding="utf-8", errors="replace") # type: ignore[union-attr]
|
|
89
89
|
except Exception:
|
|
90
|
-
pass
|
|
90
|
+
pass # Optional Windows stream setup; keep hook startup non-fatal.
|
|
91
91
|
elif hasattr(_stream, "detach"):
|
|
92
92
|
try:
|
|
93
93
|
setattr(sys, _stream_name, _io.TextIOWrapper(_stream.detach(), encoding="utf-8", errors="replace"))
|
|
94
94
|
except Exception:
|
|
95
|
-
pass
|
|
95
|
+
pass # Optional Windows stream setup; keep hook startup non-fatal.
|
|
96
96
|
|
|
97
97
|
|
|
98
98
|
|
|
@@ -249,7 +249,7 @@ def _persist_context_key_for_bash(context_key: str | None) -> None:
|
|
|
249
249
|
with open(env_file, "a", encoding="utf-8") as handle:
|
|
250
250
|
handle.write(f"export TRELLIS_CONTEXT_ID={shlex.quote(context_key)}\n")
|
|
251
251
|
except OSError:
|
|
252
|
-
pass
|
|
252
|
+
pass # Optional shell bridge; keep session-start non-fatal.
|
|
253
253
|
|
|
254
254
|
|
|
255
255
|
def _resolve_active_task(trellis_dir: Path, input_data: dict):
|
|
@@ -351,7 +351,7 @@ def _get_task_status(trellis_dir: Path, input_data: dict) -> str:
|
|
|
351
351
|
try:
|
|
352
352
|
task_data = json.loads(task_json_path.read_text(encoding="utf-8"))
|
|
353
353
|
except (json.JSONDecodeError, PermissionError):
|
|
354
|
-
pass
|
|
354
|
+
pass # Optional task metadata; fall back to generic status.
|
|
355
355
|
|
|
356
356
|
task_title = task_data.get("title", task_ref)
|
|
357
357
|
task_status = task_data.get("status", "unknown")
|
|
@@ -453,7 +453,7 @@ def _load_trellis_config(trellis_dir: Path, input_data: dict) -> tuple:
|
|
|
453
453
|
if isinstance(tp, str) and tp:
|
|
454
454
|
task_pkg = tp
|
|
455
455
|
except (json.JSONDecodeError, OSError):
|
|
456
|
-
pass
|
|
456
|
+
pass # Optional package metadata; fall back to default scope.
|
|
457
457
|
|
|
458
458
|
default_pkg = get_default_package(repo_root)
|
|
459
459
|
return is_mono, packages, scope, task_pkg, default_pkg
|
|
@@ -630,7 +630,7 @@ def _build_compact_current_state(
|
|
|
630
630
|
if isinstance(data, dict):
|
|
631
631
|
status = str(data.get("status") or "unknown")
|
|
632
632
|
except (json.JSONDecodeError, OSError):
|
|
633
|
-
pass
|
|
633
|
+
pass # Optional task metadata; fall back to generic status.
|
|
634
634
|
lines.append(f"Current task: {_repo_relative(repo_root, task_dir)}; status={status}.")
|
|
635
635
|
else:
|
|
636
636
|
lines.append("Current task: none.")
|
|
@@ -642,7 +642,7 @@ def _build_compact_current_state(
|
|
|
642
642
|
f"Active tasks: {task_count} total. Use `python3 ./.trellis/scripts/task.py list --mine` only if needed."
|
|
643
643
|
)
|
|
644
644
|
except Exception:
|
|
645
|
-
pass
|
|
645
|
+
pass # Optional task summary; keep compact state available.
|
|
646
646
|
|
|
647
647
|
if get_active_journal_file and count_lines:
|
|
648
648
|
journal = get_active_journal_file(repo_root)
|
|
@@ -14,7 +14,7 @@ Usage:
|
|
|
14
14
|
|
|
15
15
|
Branch resolution order:
|
|
16
16
|
1. --branch CLI arg (explicit)
|
|
17
|
-
2. task.json branch field (from active task)
|
|
17
|
+
2. task.json branch field (from active task, if still exists)
|
|
18
18
|
3. git branch --show-current (auto-detect)
|
|
19
19
|
4. None (omitted gracefully)
|
|
20
20
|
"""
|
|
@@ -44,6 +44,7 @@ from common.safe_commit import (
|
|
|
44
44
|
safe_trellis_paths_to_add,
|
|
45
45
|
)
|
|
46
46
|
from common.tasks import load_task
|
|
47
|
+
from common.types import TaskInfo
|
|
47
48
|
from common.config import (
|
|
48
49
|
get_packages,
|
|
49
50
|
get_session_auto_commit,
|
|
@@ -55,6 +56,12 @@ from common.config import (
|
|
|
55
56
|
)
|
|
56
57
|
|
|
57
58
|
|
|
59
|
+
DEFAULT_MAIN_CHANGES = (
|
|
60
|
+
"- Detailed change bullets were not supplied; see the summary above."
|
|
61
|
+
)
|
|
62
|
+
DEFAULT_TESTING = "- Validation was not recorded for this session."
|
|
63
|
+
|
|
64
|
+
|
|
58
65
|
# =============================================================================
|
|
59
66
|
# Helper Functions
|
|
60
67
|
# =============================================================================
|
|
@@ -126,6 +133,56 @@ def count_journal_files(dev_dir: Path, active_num: int) -> str:
|
|
|
126
133
|
return "\n".join(result_lines)
|
|
127
134
|
|
|
128
135
|
|
|
136
|
+
def get_current_git_branch(repo_root: Path) -> str | None:
|
|
137
|
+
"""Return the current checkout branch, or None for detached/non-git states."""
|
|
138
|
+
rc, branch_out, _ = run_git(["branch", "--show-current"], cwd=repo_root)
|
|
139
|
+
if rc != 0:
|
|
140
|
+
return None
|
|
141
|
+
detected = branch_out.strip()
|
|
142
|
+
return detected or None
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def branch_ref_exists(repo_root: Path, branch: str) -> bool:
|
|
146
|
+
"""Return True when branch exists locally or as the local origin ref."""
|
|
147
|
+
for ref in (f"refs/heads/{branch}", f"refs/remotes/origin/{branch}"):
|
|
148
|
+
rc, _, _ = run_git(["show-ref", "--verify", "--quiet", ref], cwd=repo_root)
|
|
149
|
+
if rc == 0:
|
|
150
|
+
return True
|
|
151
|
+
return False
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def resolve_session_branch(
|
|
155
|
+
repo_root: Path,
|
|
156
|
+
cli_branch: str | None,
|
|
157
|
+
task_data: TaskInfo | None,
|
|
158
|
+
) -> str | None:
|
|
159
|
+
"""Resolve journal branch without trusting stale task.json branch fields."""
|
|
160
|
+
if cli_branch:
|
|
161
|
+
return cli_branch
|
|
162
|
+
|
|
163
|
+
current_branch = get_current_git_branch(repo_root)
|
|
164
|
+
raw_task_branch = task_data.raw.get("branch") if task_data else None
|
|
165
|
+
task_branch = raw_task_branch.strip() if isinstance(raw_task_branch, str) else ""
|
|
166
|
+
if not task_branch:
|
|
167
|
+
return current_branch
|
|
168
|
+
|
|
169
|
+
if branch_ref_exists(repo_root, task_branch):
|
|
170
|
+
return task_branch
|
|
171
|
+
|
|
172
|
+
if current_branch:
|
|
173
|
+
print(
|
|
174
|
+
f"Warning: task.json branch '{task_branch}' no longer exists locally or as origin/{task_branch}; using current branch '{current_branch}'.",
|
|
175
|
+
file=sys.stderr,
|
|
176
|
+
)
|
|
177
|
+
return current_branch
|
|
178
|
+
|
|
179
|
+
print(
|
|
180
|
+
f"Warning: task.json branch '{task_branch}' no longer exists locally or as origin/{task_branch}; omitting branch.",
|
|
181
|
+
file=sys.stderr,
|
|
182
|
+
)
|
|
183
|
+
return None
|
|
184
|
+
|
|
185
|
+
|
|
129
186
|
def create_new_journal_file(
|
|
130
187
|
dev_dir: Path, num: int, developer: str, today: str, max_lines: int = 2000,
|
|
131
188
|
) -> Path:
|
|
@@ -154,6 +211,7 @@ def generate_session_content(
|
|
|
154
211
|
today: str,
|
|
155
212
|
package: str | None = None,
|
|
156
213
|
branch: str | None = None,
|
|
214
|
+
testing_content: str = DEFAULT_TESTING,
|
|
157
215
|
) -> str:
|
|
158
216
|
"""Generate session content."""
|
|
159
217
|
if commit and commit != "-":
|
|
@@ -189,7 +247,7 @@ def generate_session_content(
|
|
|
189
247
|
|
|
190
248
|
### Testing
|
|
191
249
|
|
|
192
|
-
|
|
250
|
+
{testing_content}
|
|
193
251
|
|
|
194
252
|
### Status
|
|
195
253
|
|
|
@@ -395,8 +453,8 @@ def _auto_commit_workspace(repo_root: Path) -> None:
|
|
|
395
453
|
def add_session(
|
|
396
454
|
title: str,
|
|
397
455
|
commit: str = "-",
|
|
398
|
-
summary: str = "
|
|
399
|
-
extra_content: str =
|
|
456
|
+
summary: str = "Session summary was not supplied.",
|
|
457
|
+
extra_content: str = DEFAULT_MAIN_CHANGES,
|
|
400
458
|
auto_commit: bool = True,
|
|
401
459
|
package: str | None = None,
|
|
402
460
|
branch: str | None = None,
|
|
@@ -503,7 +561,7 @@ def main() -> int:
|
|
|
503
561
|
)
|
|
504
562
|
parser.add_argument("--title", required=True, help="Session title")
|
|
505
563
|
parser.add_argument("--commit", default="-", help="Comma-separated commit hashes")
|
|
506
|
-
parser.add_argument("--summary", default="
|
|
564
|
+
parser.add_argument("--summary", default="Session summary was not supplied.", help="Brief summary")
|
|
507
565
|
parser.add_argument("--content-file", help="Path to file with detailed content")
|
|
508
566
|
parser.add_argument("--package", help="Package name tag (e.g., cli, docs-site)")
|
|
509
567
|
parser.add_argument("--branch", help="Branch name (auto-detected if omitted)")
|
|
@@ -514,7 +572,7 @@ def main() -> int:
|
|
|
514
572
|
|
|
515
573
|
args = parser.parse_args()
|
|
516
574
|
|
|
517
|
-
extra_content =
|
|
575
|
+
extra_content = DEFAULT_MAIN_CHANGES
|
|
518
576
|
if args.content_file:
|
|
519
577
|
content_path = Path(args.content_file)
|
|
520
578
|
if content_path.is_file():
|
|
@@ -543,17 +601,7 @@ def main() -> int:
|
|
|
543
601
|
task_package = task_data.package if task_data else None
|
|
544
602
|
package = resolve_package(task_package, repo_root)
|
|
545
603
|
|
|
546
|
-
|
|
547
|
-
branch = args.branch
|
|
548
|
-
|
|
549
|
-
if not branch:
|
|
550
|
-
if task_data and task_data.raw.get("branch"):
|
|
551
|
-
branch = task_data.raw["branch"]
|
|
552
|
-
else:
|
|
553
|
-
_, branch_out, _ = run_git(["branch", "--show-current"], cwd=repo_root)
|
|
554
|
-
detected = branch_out.strip()
|
|
555
|
-
if detected:
|
|
556
|
-
branch = detected
|
|
604
|
+
branch = resolve_session_branch(repo_root, args.branch, task_data)
|
|
557
605
|
|
|
558
606
|
return add_session(
|
|
559
607
|
args.title, args.commit, args.summary, extra_content,
|
|
@@ -55,6 +55,7 @@ Platform = Literal[
|
|
|
55
55
|
"droid",
|
|
56
56
|
"pi",
|
|
57
57
|
"trae",
|
|
58
|
+
"omp",
|
|
58
59
|
]
|
|
59
60
|
|
|
60
61
|
|
|
@@ -131,6 +132,8 @@ class CLIAdapter:
|
|
|
131
132
|
return ".pi"
|
|
132
133
|
elif self.platform == "trae":
|
|
133
134
|
return ".trae"
|
|
135
|
+
elif self.platform == "omp":
|
|
136
|
+
return ".omp"
|
|
134
137
|
else:
|
|
135
138
|
return ".claude"
|
|
136
139
|
|
|
@@ -188,6 +191,17 @@ class CLIAdapter:
|
|
|
188
191
|
filename = filename[:-3]
|
|
189
192
|
return prompts_dir / f"trellis-{filename}.md"
|
|
190
193
|
return prompts_dir / Path(*parts)
|
|
194
|
+
if self.platform == "omp":
|
|
195
|
+
commands_dir = self.get_config_dir(project_root) / "commands"
|
|
196
|
+
if not parts:
|
|
197
|
+
return commands_dir
|
|
198
|
+
if len(parts) >= 2 and parts[0] == "trellis":
|
|
199
|
+
filename = parts[-1]
|
|
200
|
+
if filename.endswith(".md"):
|
|
201
|
+
filename = filename[:-3]
|
|
202
|
+
return commands_dir / f"trellis-{filename}.md"
|
|
203
|
+
return commands_dir / Path(*parts)
|
|
204
|
+
|
|
191
205
|
|
|
192
206
|
if self.platform == "devin":
|
|
193
207
|
workflow_dir = self.get_config_dir(project_root) / "workflows"
|
|
@@ -272,6 +286,8 @@ class CLIAdapter:
|
|
|
272
286
|
return f".factory/commands/trellis/{name}.md"
|
|
273
287
|
elif self.platform == "pi":
|
|
274
288
|
return f".pi/prompts/trellis-{name}.md"
|
|
289
|
+
elif self.platform == "omp":
|
|
290
|
+
return f".omp/commands/trellis-{name}.md"
|
|
275
291
|
else:
|
|
276
292
|
return f"{self.config_dir_name}/commands/trellis/{name}.md"
|
|
277
293
|
|
|
@@ -311,6 +327,8 @@ class CLIAdapter:
|
|
|
311
327
|
return {}
|
|
312
328
|
elif self.platform == "trae":
|
|
313
329
|
return {}
|
|
330
|
+
elif self.platform == "omp":
|
|
331
|
+
return {}
|
|
314
332
|
else:
|
|
315
333
|
return {"CLAUDE_NON_INTERACTIVE": "1"}
|
|
316
334
|
|
|
@@ -400,6 +418,10 @@ class CLIAdapter:
|
|
|
400
418
|
raise ValueError(
|
|
401
419
|
"Trae is IDE-only; CLI agent run is not supported."
|
|
402
420
|
)
|
|
421
|
+
elif self.platform == "omp":
|
|
422
|
+
raise ValueError(
|
|
423
|
+
"OMP uses native task tool for agent runs; CLI agent run is not supported."
|
|
424
|
+
)
|
|
403
425
|
|
|
404
426
|
else: # claude
|
|
405
427
|
cmd = ["claude", "-p"]
|
|
@@ -470,6 +492,10 @@ class CLIAdapter:
|
|
|
470
492
|
raise ValueError(
|
|
471
493
|
"Trae is IDE-only; CLI resume is not supported."
|
|
472
494
|
)
|
|
495
|
+
elif self.platform == "omp":
|
|
496
|
+
raise ValueError(
|
|
497
|
+
"OMP uses native task tool for agent runs; CLI resume is not supported."
|
|
498
|
+
)
|
|
473
499
|
else:
|
|
474
500
|
return ["claude", "--resume", session_id]
|
|
475
501
|
|
|
@@ -546,6 +572,8 @@ class CLIAdapter:
|
|
|
546
572
|
return "pi"
|
|
547
573
|
elif self.platform == "trae":
|
|
548
574
|
return "trae"
|
|
575
|
+
elif self.platform == "omp":
|
|
576
|
+
return "omp"
|
|
549
577
|
else:
|
|
550
578
|
return "claude"
|
|
551
579
|
|
|
@@ -642,9 +670,10 @@ def get_cli_adapter(platform: str = "claude") -> CLIAdapter:
|
|
|
642
670
|
"droid",
|
|
643
671
|
"pi",
|
|
644
672
|
"trae",
|
|
673
|
+
"omp",
|
|
645
674
|
):
|
|
646
675
|
raise ValueError(
|
|
647
|
-
f"Unsupported platform: {platform} (must be 'claude', 'opencode', 'cursor', 'iflow', 'codex', 'kilo', 'kiro', 'gemini', 'antigravity', 'devin', 'qoder', 'codebuddy', 'copilot', 'droid', 'pi', or '
|
|
676
|
+
f"Unsupported platform: {platform} (must be 'claude', 'opencode', 'cursor', 'iflow', 'codex', 'kilo', 'kiro', 'gemini', 'antigravity', 'devin', 'qoder', 'codebuddy', 'copilot', 'droid', 'pi', 'trae', or 'omp')"
|
|
648
677
|
)
|
|
649
678
|
|
|
650
679
|
return CLIAdapter(platform=platform) # type: ignore
|
|
@@ -668,6 +697,7 @@ _ALL_PLATFORM_CONFIG_DIRS = (
|
|
|
668
697
|
".factory",
|
|
669
698
|
".pi",
|
|
670
699
|
".trae",
|
|
700
|
+
".omp",
|
|
671
701
|
)
|
|
672
702
|
"""Platform-specific config directory names used by detect_platform exclusion
|
|
673
703
|
checks. `.agents/skills/` is NOT listed here: it is a shared cross-platform
|
|
@@ -737,6 +767,7 @@ def detect_platform(project_root: Path) -> Platform:
|
|
|
737
767
|
"droid",
|
|
738
768
|
"pi",
|
|
739
769
|
"trae",
|
|
770
|
+
"omp",
|
|
740
771
|
):
|
|
741
772
|
return env_platform # type: ignore
|
|
742
773
|
|
|
@@ -817,6 +848,10 @@ def detect_platform(project_root: Path) -> Platform:
|
|
|
817
848
|
if (project_root / ".trae").is_dir():
|
|
818
849
|
return "trae"
|
|
819
850
|
|
|
851
|
+
# Check for .omp directory (OMP-specific)
|
|
852
|
+
if (project_root / ".omp").is_dir():
|
|
853
|
+
return "omp"
|
|
854
|
+
|
|
820
855
|
# Fallback: checkout only has the Codex shared-skills layer
|
|
821
856
|
# (.agents/skills/trellis-* dirs) and no explicit platform config dir.
|
|
822
857
|
# Happens on fresh clones where .codex/ is gitignored/absent but the
|
|
@@ -167,6 +167,7 @@ def _next_content_line(lines: list[str], start: int) -> tuple[int, str]:
|
|
|
167
167
|
DEFAULT_SESSION_COMMIT_MESSAGE = "chore: record journal"
|
|
168
168
|
DEFAULT_MAX_JOURNAL_LINES = 2000
|
|
169
169
|
DEFAULT_SESSION_AUTO_COMMIT = True
|
|
170
|
+
DEFAULT_CODEX_DISPATCH_MODE = "inline"
|
|
170
171
|
|
|
171
172
|
CONFIG_FILE = "config.yaml"
|
|
172
173
|
|
|
@@ -243,6 +244,34 @@ def get_session_auto_commit(repo_root: Path | None = None) -> bool:
|
|
|
243
244
|
return DEFAULT_SESSION_AUTO_COMMIT
|
|
244
245
|
|
|
245
246
|
|
|
247
|
+
def get_codex_dispatch_mode(repo_root: Path | None = None) -> str:
|
|
248
|
+
"""Return Codex dispatch mode.
|
|
249
|
+
|
|
250
|
+
Default is ``inline``. ``sub-agent`` is an explicit opt-in because Codex
|
|
251
|
+
sub-agents do not inherit the parent session context.
|
|
252
|
+
"""
|
|
253
|
+
config = _load_config(repo_root)
|
|
254
|
+
codex = config.get("codex")
|
|
255
|
+
if codex is None:
|
|
256
|
+
return DEFAULT_CODEX_DISPATCH_MODE
|
|
257
|
+
if not isinstance(codex, dict):
|
|
258
|
+
print(
|
|
259
|
+
f"[WARN] invalid codex config: {codex!r}; using {DEFAULT_CODEX_DISPATCH_MODE}",
|
|
260
|
+
file=sys.stderr,
|
|
261
|
+
)
|
|
262
|
+
return DEFAULT_CODEX_DISPATCH_MODE
|
|
263
|
+
|
|
264
|
+
raw = codex.get("dispatch_mode", DEFAULT_CODEX_DISPATCH_MODE)
|
|
265
|
+
mode = str(raw).strip().lower()
|
|
266
|
+
if mode in ("inline", "sub-agent"):
|
|
267
|
+
return mode
|
|
268
|
+
print(
|
|
269
|
+
f"[WARN] invalid codex.dispatch_mode value: {raw!r}; using {DEFAULT_CODEX_DISPATCH_MODE}",
|
|
270
|
+
file=sys.stderr,
|
|
271
|
+
)
|
|
272
|
+
return DEFAULT_CODEX_DISPATCH_MODE
|
|
273
|
+
|
|
274
|
+
|
|
246
275
|
def get_hooks(event: str, repo_root: Path | None = None) -> list[str]:
|
|
247
276
|
"""Get hook commands for a lifecycle event.
|
|
248
277
|
|
|
@@ -23,6 +23,7 @@ from datetime import datetime
|
|
|
23
23
|
from pathlib import Path
|
|
24
24
|
|
|
25
25
|
from .config import (
|
|
26
|
+
get_codex_dispatch_mode,
|
|
26
27
|
get_packages,
|
|
27
28
|
get_session_auto_commit,
|
|
28
29
|
is_monorepo,
|
|
@@ -113,13 +114,13 @@ def _repo_relative_path(path: Path, repo_root: Path) -> str:
|
|
|
113
114
|
|
|
114
115
|
# Config directories of platforms that consume implement.jsonl / check.jsonl.
|
|
115
116
|
# Keep in sync with src/types/ai-tools.ts AI_TOOLS entries — these are the
|
|
116
|
-
# platforms listed in workflow.md's "agent-capable" Skill Routing block
|
|
117
|
-
#
|
|
118
|
-
# Devin are NOT in this list: they
|
|
117
|
+
# platforms listed in workflow.md's "agent-capable" Skill Routing block.
|
|
118
|
+
# Codex is checked separately because default inline mode does not consume
|
|
119
|
+
# JSONL. Kilo / Antigravity / Devin are NOT in this list either: they load
|
|
120
|
+
# specs through skills instead of JSONL.
|
|
119
121
|
_SUBAGENT_CONFIG_DIRS: tuple[str, ...] = (
|
|
120
122
|
".claude",
|
|
121
123
|
".cursor",
|
|
122
|
-
".codex",
|
|
123
124
|
".kiro",
|
|
124
125
|
".gemini",
|
|
125
126
|
".opencode",
|
|
@@ -129,7 +130,10 @@ _SUBAGENT_CONFIG_DIRS: tuple[str, ...] = (
|
|
|
129
130
|
".github/copilot",
|
|
130
131
|
".pi", # Pi Agent
|
|
131
132
|
".trae", # Trae IDE
|
|
133
|
+
".omp", # Oh My Pi
|
|
134
|
+
".zcode", # ZCode
|
|
132
135
|
)
|
|
136
|
+
_CODEX_CONFIG_DIR = ".codex"
|
|
133
137
|
|
|
134
138
|
_SEED_EXAMPLE = (
|
|
135
139
|
"Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. "
|
|
@@ -142,13 +146,15 @@ _SEED_EXAMPLE = (
|
|
|
142
146
|
def _has_subagent_platform(repo_root: Path) -> bool:
|
|
143
147
|
"""Return True if any sub-agent-capable platform is configured.
|
|
144
148
|
|
|
145
|
-
Detected by probing well-known config directories at the repo root.
|
|
146
|
-
only
|
|
147
|
-
``
|
|
149
|
+
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.
|
|
148
152
|
"""
|
|
149
153
|
for config_dir in _SUBAGENT_CONFIG_DIRS:
|
|
150
154
|
if (repo_root / config_dir).is_dir():
|
|
151
155
|
return True
|
|
156
|
+
if (repo_root / _CODEX_CONFIG_DIR).is_dir():
|
|
157
|
+
return get_codex_dispatch_mode(repo_root) == "sub-agent"
|
|
152
158
|
return False
|
|
153
159
|
|
|
154
160
|
|
|
@@ -240,6 +246,34 @@ def cmd_create(args: argparse.Namespace) -> int:
|
|
|
240
246
|
# Create task directory with MM-DD-slug format
|
|
241
247
|
tasks_dir = get_tasks_dir(repo_root)
|
|
242
248
|
date_prefix = generate_task_date_prefix()
|
|
249
|
+
|
|
250
|
+
# Guard against date-prefixed --slug (e.g. a full task dir name pasted in),
|
|
251
|
+
# which would otherwise produce MM-DD-MM-DD-slug (issue #377). Only an
|
|
252
|
+
# explicit --slug is guarded; title-derived slugs are left untouched.
|
|
253
|
+
if args.slug:
|
|
254
|
+
m = re.match(r"^(\d{2})-(\d{2})-(.+)$", slug)
|
|
255
|
+
if m and 1 <= int(m.group(1)) <= 12 and 1 <= int(m.group(2)) <= 31:
|
|
256
|
+
slug_prefix = f"{m.group(1)}-{m.group(2)}"
|
|
257
|
+
if slug_prefix == date_prefix:
|
|
258
|
+
slug = m.group(3)
|
|
259
|
+
print(
|
|
260
|
+
colored(
|
|
261
|
+
f'warning: --slug should not include the MM-DD prefix; normalized to "{slug}"',
|
|
262
|
+
Colors.YELLOW,
|
|
263
|
+
),
|
|
264
|
+
file=sys.stderr,
|
|
265
|
+
)
|
|
266
|
+
else:
|
|
267
|
+
print(
|
|
268
|
+
colored(
|
|
269
|
+
f"Error: --slug starts with a date prefix ({slug_prefix}-), but task.py create always uses today's date ({date_prefix}).",
|
|
270
|
+
Colors.RED,
|
|
271
|
+
),
|
|
272
|
+
file=sys.stderr,
|
|
273
|
+
)
|
|
274
|
+
print(f"Pass only the slug body, e.g. --slug {m.group(3)}", file=sys.stderr)
|
|
275
|
+
return 1
|
|
276
|
+
|
|
243
277
|
dir_name = f"{date_prefix}-{slug}"
|
|
244
278
|
task_dir = tasks_dir / dir_name
|
|
245
279
|
task_json_path = task_dir / FILE_TASK_JSON
|
|
@@ -262,11 +296,21 @@ def cmd_create(args: argparse.Namespace) -> int:
|
|
|
262
296
|
_, branch_out, _ = run_git(["branch", "--show-current"], cwd=repo_root)
|
|
263
297
|
current_branch = branch_out.strip() or "main"
|
|
264
298
|
|
|
299
|
+
description = (args.description or "").strip()
|
|
300
|
+
if not description.strip():
|
|
301
|
+
print(
|
|
302
|
+
colored(
|
|
303
|
+
"warning: task description is empty; pass --description to improve search and later audits.",
|
|
304
|
+
Colors.YELLOW,
|
|
305
|
+
),
|
|
306
|
+
file=sys.stderr,
|
|
307
|
+
)
|
|
308
|
+
|
|
265
309
|
task_data = {
|
|
266
310
|
"id": slug,
|
|
267
311
|
"name": slug,
|
|
268
312
|
"title": args.title,
|
|
269
|
-
"description":
|
|
313
|
+
"description": description,
|
|
270
314
|
"status": "planning",
|
|
271
315
|
"dev_type": None,
|
|
272
316
|
"scope": None,
|
|
@@ -294,7 +338,7 @@ def cmd_create(args: argparse.Namespace) -> int:
|
|
|
294
338
|
prd_path = task_dir / "prd.md"
|
|
295
339
|
if not prd_path.exists():
|
|
296
340
|
prd_path.write_text(
|
|
297
|
-
_default_prd_content(args.title,
|
|
341
|
+
_default_prd_content(args.title, description),
|
|
298
342
|
encoding="utf-8",
|
|
299
343
|
)
|
|
300
344
|
|
|
@@ -337,16 +381,31 @@ def cmd_create(args: argparse.Namespace) -> int:
|
|
|
337
381
|
# outside an AI session) — the task is still created, the user can run
|
|
338
382
|
# task.py start later. Pointer is session-scoped so this never affects
|
|
339
383
|
# other AI sessions.
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
384
|
+
if getattr(args, "no_start", False):
|
|
385
|
+
print(
|
|
386
|
+
colored(
|
|
387
|
+
"Skipped session activation (--no-start); run task.py start when ready.",
|
|
388
|
+
Colors.YELLOW,
|
|
389
|
+
),
|
|
390
|
+
file=sys.stderr,
|
|
391
|
+
)
|
|
392
|
+
else:
|
|
393
|
+
try:
|
|
394
|
+
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
|
|
350
409
|
|
|
351
410
|
print(colored(f"Created task: {dir_name}", Colors.GREEN), file=sys.stderr)
|
|
352
411
|
print("", file=sys.stderr)
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
Task Management Script.
|
|
5
5
|
|
|
6
6
|
Usage:
|
|
7
|
-
python3 task.py create "<title>" [--slug <name>] [--assignee <dev>] [--priority P0|P1|P2|P3] [--parent <dir>] [--package <pkg>]
|
|
7
|
+
python3 task.py create "<title>" [--slug <name>] [--assignee <dev>] [--priority P0|P1|P2|P3] [--parent <dir>] [--package <pkg>] [--no-start]
|
|
8
8
|
python3 task.py add-context <dir> <file> <path> [reason] # Add jsonl entry
|
|
9
9
|
python3 task.py validate <dir> # Validate jsonl files
|
|
10
10
|
python3 task.py list-context <dir> # List jsonl entries
|
|
@@ -307,6 +307,7 @@ Usage:
|
|
|
307
307
|
python3 task.py create <title> Create new task directory
|
|
308
308
|
python3 task.py create <title> --package <pkg> Create task for a specific package
|
|
309
309
|
python3 task.py create <title> --parent <dir> Create task as child of parent
|
|
310
|
+
python3 task.py create <title> --no-start Create without making it active in this session
|
|
310
311
|
python3 task.py add-context <dir> <jsonl> <path> [reason] Add entry to jsonl
|
|
311
312
|
python3 task.py validate <dir> Validate jsonl files
|
|
312
313
|
python3 task.py list-context <dir> List jsonl entries
|
|
@@ -392,12 +393,17 @@ def main() -> int:
|
|
|
392
393
|
# create
|
|
393
394
|
p_create = subparsers.add_parser("create", help="Create new task")
|
|
394
395
|
p_create.add_argument("title", help="Task title")
|
|
395
|
-
p_create.add_argument("--slug", "-s", help="Task slug")
|
|
396
|
+
p_create.add_argument("--slug", "-s", help="Task slug without the MM-DD date prefix")
|
|
396
397
|
p_create.add_argument("--assignee", "-a", help="Assignee developer")
|
|
397
398
|
p_create.add_argument("--priority", "-p", default="P2", help="Priority (P0-P3)")
|
|
398
399
|
p_create.add_argument("--description", "-d", help="Task description")
|
|
399
400
|
p_create.add_argument("--parent", help="Parent task directory (establishes subtask link)")
|
|
400
401
|
p_create.add_argument("--package", help="Package name for monorepo projects")
|
|
402
|
+
p_create.add_argument(
|
|
403
|
+
"--no-start",
|
|
404
|
+
action="store_true",
|
|
405
|
+
help="Create the task without making it active in this session",
|
|
406
|
+
)
|
|
401
407
|
|
|
402
408
|
# add-context
|
|
403
409
|
p_add = subparsers.add_parser("add-context", help="Add context entry")
|