@yan-geroge/omg 0.1.0
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/.claude/agents/trellis-check.md +109 -0
- package/.claude/agents/trellis-implement.md +109 -0
- package/.claude/agents/trellis-research.md +137 -0
- package/.claude/commands/trellis/continue.md +55 -0
- package/.claude/commands/trellis/finish-work.md +66 -0
- package/.claude/hooks/inject-subagent-context.py +749 -0
- package/.claude/hooks/inject-workflow-state.py +387 -0
- package/.claude/hooks/session-start.py +797 -0
- package/.claude/settings.json +73 -0
- package/.claude/skills/trellis-before-dev/SKILL.md +34 -0
- package/.claude/skills/trellis-brainstorm/SKILL.md +548 -0
- package/.claude/skills/trellis-break-loop/SKILL.md +130 -0
- package/.claude/skills/trellis-check/SKILL.md +92 -0
- package/.claude/skills/trellis-meta/SKILL.md +73 -0
- package/.claude/skills/trellis-meta/references/customize-local/add-project-local-conventions.md +83 -0
- package/.claude/skills/trellis-meta/references/customize-local/change-agents.md +54 -0
- package/.claude/skills/trellis-meta/references/customize-local/change-context-loading.md +81 -0
- package/.claude/skills/trellis-meta/references/customize-local/change-hooks.md +57 -0
- package/.claude/skills/trellis-meta/references/customize-local/change-skills-or-commands.md +78 -0
- package/.claude/skills/trellis-meta/references/customize-local/change-spec-structure.md +83 -0
- package/.claude/skills/trellis-meta/references/customize-local/change-task-lifecycle.md +90 -0
- package/.claude/skills/trellis-meta/references/customize-local/change-workflow.md +64 -0
- package/.claude/skills/trellis-meta/references/customize-local/overview.md +55 -0
- package/.claude/skills/trellis-meta/references/local-architecture/context-injection.md +68 -0
- package/.claude/skills/trellis-meta/references/local-architecture/generated-files.md +80 -0
- package/.claude/skills/trellis-meta/references/local-architecture/overview.md +51 -0
- package/.claude/skills/trellis-meta/references/local-architecture/spec-system.md +102 -0
- package/.claude/skills/trellis-meta/references/local-architecture/task-system.md +101 -0
- package/.claude/skills/trellis-meta/references/local-architecture/workflow.md +75 -0
- package/.claude/skills/trellis-meta/references/local-architecture/workspace-memory.md +71 -0
- package/.claude/skills/trellis-meta/references/platform-files/agents.md +79 -0
- package/.claude/skills/trellis-meta/references/platform-files/hooks-and-settings.md +69 -0
- package/.claude/skills/trellis-meta/references/platform-files/overview.md +59 -0
- package/.claude/skills/trellis-meta/references/platform-files/platform-map.md +74 -0
- package/.claude/skills/trellis-meta/references/platform-files/skills-and-commands.md +83 -0
- package/.claude/skills/trellis-spec-bootstarp/SKILL.md +41 -0
- package/.claude/skills/trellis-spec-bootstarp/references/mcp-setup.md +90 -0
- package/.claude/skills/trellis-spec-bootstarp/references/repository-analysis.md +59 -0
- package/.claude/skills/trellis-spec-bootstarp/references/spec-task-planning.md +61 -0
- package/.claude/skills/trellis-spec-bootstarp/references/spec-writing.md +70 -0
- package/.claude/skills/trellis-update-spec/SKILL.md +356 -0
- package/CLAUDE.md +88 -0
- package/agents/architect.md +80 -0
- package/agents/executor.md +79 -0
- package/agents/planner.md +76 -0
- package/agents/reviewer.md +81 -0
- package/agents/tdd-guide.md +95 -0
- package/agents/verifier.md +81 -0
- package/hooks/keyword-detector.mjs +115 -0
- package/hooks/lib/keyword-detector.js +89 -0
- package/hooks/session-start.mjs +104 -0
- package/marketplace.json +12 -0
- package/package.json +34 -0
- package/plugin.json +14 -0
- package/scripts/diagnose-marketplace.js +84 -0
- package/scripts/e2e-diagnose.mjs +118 -0
- package/scripts/validate-with-csc-schema.mjs +87 -0
- package/skills/costrict-autopilot/SKILL.md +53 -0
- package/skills/costrict-team/SKILL.md +65 -0
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Trellis per-turn breadcrumb hook (UserPromptSubmit / BeforeAgent equivalent).
|
|
3
|
+
|
|
4
|
+
Runs on every user prompt. Resolves the active task through Trellis'
|
|
5
|
+
session-aware active task resolver and emits a short <workflow-state>
|
|
6
|
+
block reminding the main AI what task is active and its expected flow.
|
|
7
|
+
|
|
8
|
+
The emitted ``hookEventName`` field is platform-aware: most hosts expect
|
|
9
|
+
``UserPromptSubmit`` (Claude Code naming, also accepted by Cursor / Qoder /
|
|
10
|
+
CodeBuddy / Droid / Codex / Copilot wiring), but Gemini CLI 0.40.x renamed
|
|
11
|
+
its per-turn event to ``BeforeAgent`` and its schema validator rejects the
|
|
12
|
+
legacy name. ``_detect_platform`` picks the right value at runtime.
|
|
13
|
+
Breadcrumb text is pulled exclusively from workflow.md
|
|
14
|
+
[workflow-state:STATUS] tag blocks — workflow.md is the single source of
|
|
15
|
+
truth. There are no fallback dicts in this script: when workflow.md is
|
|
16
|
+
missing or a tag is absent, the breadcrumb degrades to a generic
|
|
17
|
+
"Refer to workflow.md for current step." line so users see (and fix)
|
|
18
|
+
the broken state instead of the hook silently masking it.
|
|
19
|
+
|
|
20
|
+
Shared across all hook-capable platforms (Claude, Cursor, Codex, Qoder,
|
|
21
|
+
CodeBuddy, Droid, Gemini, Copilot). Kiro is not wired (no per-turn
|
|
22
|
+
hook entry point). Written to each platform's hooks directory via
|
|
23
|
+
writeSharedHooks() at init time.
|
|
24
|
+
|
|
25
|
+
Silent exit 0 cases (no output):
|
|
26
|
+
- No .trellis/ directory found (not a Trellis project)
|
|
27
|
+
- task.json malformed or missing status
|
|
28
|
+
"""
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import json
|
|
32
|
+
import os
|
|
33
|
+
import re
|
|
34
|
+
import sys
|
|
35
|
+
from pathlib import Path
|
|
36
|
+
|
|
37
|
+
# Force UTF-8 on stdin/stdout/stderr on Windows. Default codepage there is
|
|
38
|
+
# cp936 / cp1252 / etc. — non-ASCII content (Chinese task names, prd snippets)
|
|
39
|
+
# both in stdin (hook payload from host CLI) and stdout (our emitted blocks)
|
|
40
|
+
# raises UnicodeDecodeError / UnicodeEncodeError. Equivalent to `python -X utf8`
|
|
41
|
+
# but applied per-stream so we don't depend on host CLI's command wiring.
|
|
42
|
+
if sys.platform.startswith("win"):
|
|
43
|
+
import io as _io
|
|
44
|
+
for _stream_name in ("stdin", "stdout", "stderr"):
|
|
45
|
+
_stream = getattr(sys, _stream_name, None)
|
|
46
|
+
if _stream is None:
|
|
47
|
+
continue
|
|
48
|
+
if hasattr(_stream, "reconfigure"):
|
|
49
|
+
try:
|
|
50
|
+
_stream.reconfigure(encoding="utf-8", errors="replace") # type: ignore[union-attr]
|
|
51
|
+
except Exception:
|
|
52
|
+
pass
|
|
53
|
+
elif hasattr(_stream, "detach"):
|
|
54
|
+
try:
|
|
55
|
+
setattr(sys, _stream_name, _io.TextIOWrapper(_stream.detach(), encoding="utf-8", errors="replace"))
|
|
56
|
+
except Exception:
|
|
57
|
+
pass
|
|
58
|
+
from typing import Optional
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
CODEX_SUB_AGENT_NOTICE = """<sub-agent-notice>
|
|
62
|
+
SUB-AGENT NOTICE - READ FIRST IF SPAWNED VIA spawn_agent
|
|
63
|
+
|
|
64
|
+
If your parent session spawned you via spawn_agent with an explicit task
|
|
65
|
+
message above this hook output, that message is your only job.
|
|
66
|
+
- Execute the parent message exactly as written, then return.
|
|
67
|
+
- Ignore all Trellis workflow guidance below this notice.
|
|
68
|
+
- Do NOT call task.py start, task.py add-context, or task.py archive.
|
|
69
|
+
- Do NOT call wait_agent or spawn_agent.
|
|
70
|
+
- Do NOT modify .trellis/tasks/* or any other file unless the parent message
|
|
71
|
+
explicitly asks for that.
|
|
72
|
+
|
|
73
|
+
If you are the main interactive Codex session and the user is typing at the
|
|
74
|
+
terminal with no parent agent, use the workflow guidance below normally.
|
|
75
|
+
</sub-agent-notice>"""
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# Bootstrap notice for Codex while the session has no active task. Replaces the
|
|
79
|
+
# heavyweight SessionStart context injection — instead of pushing 9.5 KB of
|
|
80
|
+
# workflow text up front, we just nudge the AI to read the `trellis-start` skill once.
|
|
81
|
+
# The nudge keeps showing up while status == "no_task" (cheap text, AI won't
|
|
82
|
+
# re-read after the first time). Once a task is created the breadcrumb status
|
|
83
|
+
# flips and this notice stops appearing automatically. Sub-agents are warded
|
|
84
|
+
# off by the <sub-agent-notice> above plus the explicit exemption below.
|
|
85
|
+
CODEX_NO_TASK_BOOTSTRAP_NOTICE = """<trellis-bootstrap>
|
|
86
|
+
You are running in a Trellis-managed Codex session and there is no active task yet.
|
|
87
|
+
If you have not already loaded Trellis context this session, read the `trellis-start` skill once:
|
|
88
|
+
|
|
89
|
+
$trellis-start
|
|
90
|
+
|
|
91
|
+
(equivalent to reading `.agents/skills/trellis-start/SKILL.md` and following its Steps 1-3)
|
|
92
|
+
|
|
93
|
+
The skill walks you through workflow.md, dev profile, git status, active tasks, and spec
|
|
94
|
+
indexes. Then route the user's request per the <workflow-state> A/B/C rules below.
|
|
95
|
+
|
|
96
|
+
Sub-agent exemption: if you are a sub-agent (spawned via spawn_agent with a parent task
|
|
97
|
+
message), DO NOT read `$trellis-start`. Execute the parent message directly as instructed by the
|
|
98
|
+
<sub-agent-notice> above.
|
|
99
|
+
</trellis-bootstrap>"""
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# ---------------------------------------------------------------------------
|
|
103
|
+
# CWD-robust Trellis root discovery (fixes hook-path-robustness for this hook)
|
|
104
|
+
# ---------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
def find_trellis_root(start: Path) -> Optional[Path]:
|
|
107
|
+
"""Walk up from start to find directory containing .trellis/.
|
|
108
|
+
|
|
109
|
+
Handles CWD drift: subdirectory launches, monorepo packages, etc.
|
|
110
|
+
Returns None if no .trellis/ found (silent no-op).
|
|
111
|
+
"""
|
|
112
|
+
cur = start.resolve()
|
|
113
|
+
while cur != cur.parent:
|
|
114
|
+
if (cur / ".trellis").is_dir():
|
|
115
|
+
return cur
|
|
116
|
+
cur = cur.parent
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# ---------------------------------------------------------------------------
|
|
121
|
+
# Active task discovery
|
|
122
|
+
# ---------------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
def _detect_platform(input_data: dict) -> str | None:
|
|
125
|
+
if isinstance(input_data.get("cursor_version"), str):
|
|
126
|
+
return "cursor"
|
|
127
|
+
env_map = {
|
|
128
|
+
"CLAUDE_PROJECT_DIR": "claude",
|
|
129
|
+
"CURSOR_PROJECT_DIR": "cursor",
|
|
130
|
+
"CODEBUDDY_PROJECT_DIR": "codebuddy",
|
|
131
|
+
"FACTORY_PROJECT_DIR": "droid",
|
|
132
|
+
"GEMINI_PROJECT_DIR": "gemini",
|
|
133
|
+
"QODER_PROJECT_DIR": "qoder",
|
|
134
|
+
"KIRO_PROJECT_DIR": "kiro",
|
|
135
|
+
"COPILOT_PROJECT_DIR": "copilot",
|
|
136
|
+
}
|
|
137
|
+
for env_name, platform in env_map.items():
|
|
138
|
+
if os.environ.get(env_name):
|
|
139
|
+
return platform
|
|
140
|
+
script_parts = set(Path(sys.argv[0]).parts)
|
|
141
|
+
if ".claude" in script_parts:
|
|
142
|
+
return "claude"
|
|
143
|
+
if ".cursor" in script_parts:
|
|
144
|
+
return "cursor"
|
|
145
|
+
if ".codex" in script_parts:
|
|
146
|
+
return "codex"
|
|
147
|
+
if ".gemini" in script_parts:
|
|
148
|
+
return "gemini"
|
|
149
|
+
if ".qoder" in script_parts:
|
|
150
|
+
return "qoder"
|
|
151
|
+
if ".codebuddy" in script_parts:
|
|
152
|
+
return "codebuddy"
|
|
153
|
+
if ".factory" in script_parts:
|
|
154
|
+
return "droid"
|
|
155
|
+
if ".kiro" in script_parts:
|
|
156
|
+
return "kiro"
|
|
157
|
+
return None
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _resolve_active_task(root: Path, input_data: dict):
|
|
161
|
+
scripts_dir = root / ".trellis" / "scripts"
|
|
162
|
+
if str(scripts_dir) not in sys.path:
|
|
163
|
+
sys.path.insert(0, str(scripts_dir))
|
|
164
|
+
from common.active_task import resolve_active_task # type: ignore[import-not-found]
|
|
165
|
+
|
|
166
|
+
return resolve_active_task(root, input_data, platform=_detect_platform(input_data))
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def get_active_task(root: Path, input_data: dict) -> Optional[tuple[str, str, str]]:
|
|
170
|
+
"""Return (task_id, status, source) from the current active task."""
|
|
171
|
+
active = _resolve_active_task(root, input_data)
|
|
172
|
+
if not active.task_path:
|
|
173
|
+
return None
|
|
174
|
+
|
|
175
|
+
task_dir = Path(active.task_path)
|
|
176
|
+
if not task_dir.is_absolute():
|
|
177
|
+
task_dir = root / task_dir
|
|
178
|
+
if active.stale:
|
|
179
|
+
return task_dir.name, f"stale_{active.source_type}", active.source
|
|
180
|
+
|
|
181
|
+
task_json = task_dir / "task.json"
|
|
182
|
+
if not task_json.is_file():
|
|
183
|
+
return None
|
|
184
|
+
try:
|
|
185
|
+
data = json.loads(task_json.read_text(encoding="utf-8"))
|
|
186
|
+
except (json.JSONDecodeError, OSError):
|
|
187
|
+
return None
|
|
188
|
+
|
|
189
|
+
task_id = data.get("id") or task_dir.name
|
|
190
|
+
status = data.get("status", "")
|
|
191
|
+
if not isinstance(status, str) or not status:
|
|
192
|
+
return None
|
|
193
|
+
return task_id, status, active.source
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
# ---------------------------------------------------------------------------
|
|
197
|
+
# Breadcrumb loading: parse workflow.md, fall back to hardcoded defaults
|
|
198
|
+
# ---------------------------------------------------------------------------
|
|
199
|
+
|
|
200
|
+
# Supports STATUS values with letters, digits, underscores, hyphens
|
|
201
|
+
# (so "in-review" / "blocked-by-team" work alongside "in_progress").
|
|
202
|
+
_TAG_RE = re.compile(
|
|
203
|
+
r"\[workflow-state:([A-Za-z0-9_-]+)\]\s*\n(.*?)\n\s*\[/workflow-state:\1\]",
|
|
204
|
+
re.DOTALL,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
def load_breadcrumbs(root: Path) -> dict[str, str]:
|
|
208
|
+
"""Parse workflow.md for [workflow-state:STATUS] blocks.
|
|
209
|
+
|
|
210
|
+
Returns {status: body_text}. workflow.md is the single source of
|
|
211
|
+
truth — there are no fallback dicts in this script. Missing tags
|
|
212
|
+
(or a missing/unreadable workflow.md) fall back to a generic line
|
|
213
|
+
in build_breadcrumb so users see the broken state and fix
|
|
214
|
+
workflow.md, rather than the hook silently masking the issue.
|
|
215
|
+
"""
|
|
216
|
+
workflow = root / ".trellis" / "workflow.md"
|
|
217
|
+
if not workflow.is_file():
|
|
218
|
+
return {}
|
|
219
|
+
try:
|
|
220
|
+
content = workflow.read_text(encoding="utf-8")
|
|
221
|
+
except OSError:
|
|
222
|
+
return {}
|
|
223
|
+
|
|
224
|
+
result: dict[str, str] = {}
|
|
225
|
+
for match in _TAG_RE.finditer(content):
|
|
226
|
+
status = match.group(1)
|
|
227
|
+
body = match.group(2).strip()
|
|
228
|
+
if body:
|
|
229
|
+
result[status] = body
|
|
230
|
+
return result
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _read_trellis_config(root: Path) -> dict:
|
|
234
|
+
"""Load .trellis/config.yaml via the bundled trellis_config helper.
|
|
235
|
+
|
|
236
|
+
The helper lives in .trellis/scripts/common; the hook lives outside the
|
|
237
|
+
scripts tree, so we extend sys.path before importing.
|
|
238
|
+
"""
|
|
239
|
+
scripts_dir = root / ".trellis" / "scripts"
|
|
240
|
+
if str(scripts_dir) not in sys.path:
|
|
241
|
+
sys.path.insert(0, str(scripts_dir))
|
|
242
|
+
try:
|
|
243
|
+
from common.trellis_config import read_trellis_config # type: ignore[import-not-found]
|
|
244
|
+
except Exception:
|
|
245
|
+
return {}
|
|
246
|
+
try:
|
|
247
|
+
return read_trellis_config(root)
|
|
248
|
+
except Exception:
|
|
249
|
+
return {}
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _codex_mode_banner(config: dict) -> str:
|
|
253
|
+
"""Emit a `<codex-mode>` banner for the additionalContext payload.
|
|
254
|
+
|
|
255
|
+
Reads `codex.dispatch_mode` from .trellis/config.yaml; defaults to
|
|
256
|
+
`inline` when missing or invalid because Codex sub-agents run with
|
|
257
|
+
`fork_turns="none"` isolation and can't inherit the parent session's
|
|
258
|
+
task context. The banner makes the active mode explicit to Codex AI
|
|
259
|
+
per turn, complementing the workflow-state body which is per-status.
|
|
260
|
+
Mode tells AI which dispatch protocol to follow; workflow-state tells
|
|
261
|
+
AI what step it's at.
|
|
262
|
+
"""
|
|
263
|
+
mode = "inline"
|
|
264
|
+
if isinstance(config, dict):
|
|
265
|
+
codex_cfg = config.get("codex")
|
|
266
|
+
if isinstance(codex_cfg, dict):
|
|
267
|
+
cfg_mode = codex_cfg.get("dispatch_mode")
|
|
268
|
+
if cfg_mode in ("inline", "sub-agent"):
|
|
269
|
+
mode = cfg_mode
|
|
270
|
+
return f"<codex-mode>{mode}</codex-mode>"
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def resolve_breadcrumb_key(
|
|
274
|
+
status: str, platform: str | None, config: dict
|
|
275
|
+
) -> str:
|
|
276
|
+
"""Pick the breadcrumb tag key based on Codex dispatch_mode.
|
|
277
|
+
|
|
278
|
+
Codex defaults to ``inline`` because sub-agents run with ``fork_turns="none"``
|
|
279
|
+
isolation and can't inherit the parent session's task context. Users can
|
|
280
|
+
opt into ``codex.dispatch_mode: sub-agent`` in ``.trellis/config.yaml``
|
|
281
|
+
to use the parallel ``<status>-inline`` tag → ``<status>`` flip. Invalid
|
|
282
|
+
or missing values fall back to inline.
|
|
283
|
+
|
|
284
|
+
Non-codex platforms return the plain status unchanged.
|
|
285
|
+
"""
|
|
286
|
+
if platform == "codex":
|
|
287
|
+
mode = "inline"
|
|
288
|
+
if isinstance(config, dict):
|
|
289
|
+
codex_cfg = config.get("codex")
|
|
290
|
+
if isinstance(codex_cfg, dict):
|
|
291
|
+
cfg_mode = codex_cfg.get("dispatch_mode")
|
|
292
|
+
if cfg_mode in ("inline", "sub-agent"):
|
|
293
|
+
mode = cfg_mode
|
|
294
|
+
return f"{status}-inline" if mode == "inline" else status
|
|
295
|
+
return status
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def build_breadcrumb(
|
|
299
|
+
task_id: Optional[str],
|
|
300
|
+
status: str,
|
|
301
|
+
templates: dict[str, str],
|
|
302
|
+
source: str | None = None,
|
|
303
|
+
breadcrumb_key: str | None = None,
|
|
304
|
+
) -> str:
|
|
305
|
+
"""Build the <workflow-state>...</workflow-state> block.
|
|
306
|
+
|
|
307
|
+
- Known status (tag present in workflow.md) → detailed template body
|
|
308
|
+
- Unknown status (no tag, or workflow.md missing) → generic
|
|
309
|
+
"Refer to workflow.md for current step." line
|
|
310
|
+
- `no_task` pseudo-status (task_id is None) → header omits task info
|
|
311
|
+
"""
|
|
312
|
+
lookup_key = breadcrumb_key or status
|
|
313
|
+
body = templates.get(lookup_key)
|
|
314
|
+
if body is None and lookup_key != status:
|
|
315
|
+
body = templates.get(status)
|
|
316
|
+
if body is None:
|
|
317
|
+
body = "Refer to workflow.md for current step."
|
|
318
|
+
header = f"Status: {status}" if task_id is None else f"Task: {task_id} ({status})"
|
|
319
|
+
if source:
|
|
320
|
+
header = f"{header}\nSource: {source}"
|
|
321
|
+
return f"<workflow-state>\n{header}\n{body}\n</workflow-state>"
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
# ---------------------------------------------------------------------------
|
|
325
|
+
# Entry
|
|
326
|
+
# ---------------------------------------------------------------------------
|
|
327
|
+
|
|
328
|
+
def main() -> int:
|
|
329
|
+
if os.environ.get("TRELLIS_HOOKS") == "0" or os.environ.get("TRELLIS_DISABLE_HOOKS") == "1":
|
|
330
|
+
return 0
|
|
331
|
+
|
|
332
|
+
try:
|
|
333
|
+
data = json.load(sys.stdin)
|
|
334
|
+
except (json.JSONDecodeError, ValueError):
|
|
335
|
+
data = {}
|
|
336
|
+
|
|
337
|
+
cwd_str = data.get("cwd") or os.getcwd()
|
|
338
|
+
cwd = Path(cwd_str)
|
|
339
|
+
|
|
340
|
+
root = find_trellis_root(cwd)
|
|
341
|
+
if root is None:
|
|
342
|
+
return 0 # not a Trellis project
|
|
343
|
+
|
|
344
|
+
templates = load_breadcrumbs(root)
|
|
345
|
+
platform = _detect_platform(data)
|
|
346
|
+
config = _read_trellis_config(root)
|
|
347
|
+
task = get_active_task(root, data)
|
|
348
|
+
if task is None:
|
|
349
|
+
# No active task — still emit a breadcrumb nudging AI toward
|
|
350
|
+
# trellis-brainstorm + task.py create when user describes real work.
|
|
351
|
+
no_task_key = resolve_breadcrumb_key("no_task", platform, config)
|
|
352
|
+
breadcrumb = build_breadcrumb(
|
|
353
|
+
None, "no_task", templates, breadcrumb_key=no_task_key
|
|
354
|
+
)
|
|
355
|
+
else:
|
|
356
|
+
task_id, status, source = task
|
|
357
|
+
status_key = resolve_breadcrumb_key(status, platform, config)
|
|
358
|
+
breadcrumb = build_breadcrumb(
|
|
359
|
+
task_id, status, templates, source, breadcrumb_key=status_key
|
|
360
|
+
)
|
|
361
|
+
if platform == "codex":
|
|
362
|
+
parts: list[str] = [CODEX_SUB_AGENT_NOTICE]
|
|
363
|
+
if task is None:
|
|
364
|
+
parts.append(CODEX_NO_TASK_BOOTSTRAP_NOTICE)
|
|
365
|
+
parts.append(_codex_mode_banner(config))
|
|
366
|
+
parts.append(breadcrumb)
|
|
367
|
+
breadcrumb = "\n\n".join(parts)
|
|
368
|
+
|
|
369
|
+
# Gemini CLI 0.40.x rejects "UserPromptSubmit" — its per-turn event is
|
|
370
|
+
# named "BeforeAgent". Other platforms (Claude/Cursor/Qoder/CodeBuddy/
|
|
371
|
+
# Droid/Codex/Copilot) accept the original Claude-style name.
|
|
372
|
+
hook_event_name = (
|
|
373
|
+
"BeforeAgent" if platform == "gemini" else "UserPromptSubmit"
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
output = {
|
|
377
|
+
"hookSpecificOutput": {
|
|
378
|
+
"hookEventName": hook_event_name,
|
|
379
|
+
"additionalContext": breadcrumb,
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
print(json.dumps(output))
|
|
383
|
+
return 0
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
if __name__ == "__main__":
|
|
387
|
+
sys.exit(main())
|