@mindfoldhq/trellis 0.6.4 → 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.
Files changed (116) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/index.js +2 -0
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/commands/channel/messages.js +1 -1
  5. package/dist/commands/channel/supervisor/stdout.d.ts +7 -5
  6. package/dist/commands/channel/supervisor/stdout.d.ts.map +1 -1
  7. package/dist/commands/channel/supervisor/stdout.js +65 -18
  8. package/dist/commands/channel/supervisor/stdout.js.map +1 -1
  9. package/dist/commands/channel/supervisor.d.ts +13 -0
  10. package/dist/commands/channel/supervisor.d.ts.map +1 -1
  11. package/dist/commands/channel/supervisor.js +54 -2
  12. package/dist/commands/channel/supervisor.js.map +1 -1
  13. package/dist/commands/init.d.ts +2 -0
  14. package/dist/commands/init.d.ts.map +1 -1
  15. package/dist/commands/init.js.map +1 -1
  16. package/dist/commands/uninstall.d.ts.map +1 -1
  17. package/dist/commands/uninstall.js +8 -1
  18. package/dist/commands/uninstall.js.map +1 -1
  19. package/dist/commands/update.d.ts.map +1 -1
  20. package/dist/commands/update.js +83 -42
  21. package/dist/commands/update.js.map +1 -1
  22. package/dist/configurators/copilot.d.ts +1 -0
  23. package/dist/configurators/copilot.d.ts.map +1 -1
  24. package/dist/configurators/copilot.js +4 -1
  25. package/dist/configurators/copilot.js.map +1 -1
  26. package/dist/configurators/index.d.ts.map +1 -1
  27. package/dist/configurators/index.js +27 -1
  28. package/dist/configurators/index.js.map +1 -1
  29. package/dist/configurators/omp.d.ts +11 -0
  30. package/dist/configurators/omp.d.ts.map +1 -0
  31. package/dist/configurators/omp.js +51 -0
  32. package/dist/configurators/omp.js.map +1 -0
  33. package/dist/configurators/pi.d.ts.map +1 -1
  34. package/dist/configurators/pi.js +14 -4
  35. package/dist/configurators/pi.js.map +1 -1
  36. package/dist/configurators/shared.d.ts +7 -0
  37. package/dist/configurators/shared.d.ts.map +1 -1
  38. package/dist/configurators/shared.js +27 -0
  39. package/dist/configurators/shared.js.map +1 -1
  40. package/dist/configurators/trae.d.ts +17 -0
  41. package/dist/configurators/trae.d.ts.map +1 -0
  42. package/dist/configurators/trae.js +42 -0
  43. package/dist/configurators/trae.js.map +1 -0
  44. package/dist/configurators/zcode.d.ts +3 -3
  45. package/dist/configurators/zcode.js +12 -12
  46. package/dist/configurators/zcode.js.map +1 -1
  47. package/dist/migrations/manifests/0.6.5.json +9 -0
  48. package/dist/migrations/manifests/0.6.6.json +17 -0
  49. package/dist/templates/codex/hooks/session-start.py +6 -6
  50. package/dist/templates/codex/skills/brainstorm/SKILL.md +19 -1
  51. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-agents.md +1 -1
  52. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-hooks.md +1 -1
  53. package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-skills-or-commands.md +1 -1
  54. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/bundled-skills.md +2 -1
  55. package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/generated-files.md +3 -3
  56. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/agents.md +1 -1
  57. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/hooks-and-settings.md +1 -0
  58. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/overview.md +5 -5
  59. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/platform-map.md +21 -2
  60. package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/skills-and-commands.md +1 -1
  61. package/dist/templates/common/skills/brainstorm.md +19 -1
  62. package/dist/templates/copilot/copilot-instructions.md +11 -0
  63. package/dist/templates/copilot/hooks/session-start.py +6 -6
  64. package/dist/templates/copilot/index.d.ts +6 -1
  65. package/dist/templates/copilot/index.d.ts.map +1 -1
  66. package/dist/templates/copilot/index.js +8 -1
  67. package/dist/templates/copilot/index.js.map +1 -1
  68. package/dist/templates/copilot/prompts/brainstorm.prompt.md +19 -1
  69. package/dist/templates/omp/agents/trellis-check.md +41 -0
  70. package/dist/templates/omp/agents/trellis-implement.md +45 -0
  71. package/dist/templates/omp/agents/trellis-research.md +30 -0
  72. package/dist/templates/omp/extensions/trellis/index.ts.txt +462 -0
  73. package/dist/templates/omp/index.d.ts +4 -0
  74. package/dist/templates/omp/index.d.ts.map +1 -0
  75. package/dist/templates/omp/index.js +9 -0
  76. package/dist/templates/omp/index.js.map +1 -0
  77. package/dist/templates/pi/agents/trellis-check.md +1 -1
  78. package/dist/templates/pi/agents/trellis-implement.md +1 -1
  79. package/dist/templates/pi/agents/trellis-research.md +1 -1
  80. package/dist/templates/pi/extensions/trellis/index.ts.txt +111 -9
  81. package/dist/templates/shared-hooks/index.d.ts +1 -1
  82. package/dist/templates/shared-hooks/index.d.ts.map +1 -1
  83. package/dist/templates/shared-hooks/index.js +1 -0
  84. package/dist/templates/shared-hooks/index.js.map +1 -1
  85. package/dist/templates/shared-hooks/inject-workflow-state.py +41 -6
  86. package/dist/templates/shared-hooks/session-start.py +12 -7
  87. package/dist/templates/trae/agents/trellis-check.md +108 -0
  88. package/dist/templates/trae/agents/trellis-implement.md +103 -0
  89. package/dist/templates/trae/agents/trellis-research.md +137 -0
  90. package/dist/templates/trae/hooks.json +27 -0
  91. package/dist/templates/trae/index.d.ts +15 -0
  92. package/dist/templates/trae/index.d.ts.map +1 -0
  93. package/dist/templates/trae/index.js +15 -0
  94. package/dist/templates/trae/index.js.map +1 -0
  95. package/dist/templates/trellis/scripts/add_session.py +65 -17
  96. package/dist/templates/trellis/scripts/common/active_task.py +2 -0
  97. package/dist/templates/trellis/scripts/common/cli_adapter.py +72 -11
  98. package/dist/templates/trellis/scripts/common/config.py +29 -0
  99. package/dist/templates/trellis/scripts/common/session_context.py +1 -1
  100. package/dist/templates/trellis/scripts/common/task_store.py +79 -19
  101. package/dist/templates/trellis/scripts/task.py +8 -2
  102. package/dist/templates/trellis/workflow.md +22 -20
  103. package/dist/templates/zcode/agents/trellis-research.md +133 -0
  104. package/dist/templates/zcode/index.d.ts +6 -5
  105. package/dist/templates/zcode/index.d.ts.map +1 -1
  106. package/dist/templates/zcode/index.js +6 -5
  107. package/dist/templates/zcode/index.js.map +1 -1
  108. package/dist/types/ai-tools.d.ts +4 -4
  109. package/dist/types/ai-tools.d.ts.map +1 -1
  110. package/dist/types/ai-tools.js +45 -2
  111. package/dist/types/ai-tools.js.map +1 -1
  112. package/dist/utils/uninstall-scrubbers.d.ts +1 -0
  113. package/dist/utils/uninstall-scrubbers.d.ts.map +1 -1
  114. package/dist/utils/uninstall-scrubbers.js +19 -0
  115. package/dist/utils/uninstall-scrubbers.js.map +1 -1
  116. package/package.json +2 -2
@@ -0,0 +1,462 @@
1
+ import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
2
+ import { existsSync, readFileSync, readdirSync, realpathSync } from "node:fs";
3
+ import { join, dirname, isAbsolute, relative, resolve } from "node:path";
4
+ import { spawnSync } from "node:child_process";
5
+ import { createHash } from "node:crypto";
6
+
7
+ // ---------------------------------------------------------------------------
8
+ // Project root detection
9
+ // ---------------------------------------------------------------------------
10
+
11
+ function findProjectRoot(startDir: string): string | null {
12
+ let current = startDir;
13
+ while (true) {
14
+ if (existsSync(join(current, ".trellis"))) return current;
15
+ const parent = dirname(current);
16
+ if (parent === current) break;
17
+ current = parent;
18
+ }
19
+ return null;
20
+ }
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Session identity helpers (mirrors Python _sanitize_key / _hash_value / _context_key)
24
+ // ---------------------------------------------------------------------------
25
+
26
+ function sanitizeKey(raw: string): string {
27
+ const safe = raw.trim().replace(/[^A-Za-z0-9._-]+/g, "_").replace(/^[._-]+|[._-]+$/g, "");
28
+ return safe ? safe.slice(0, 160) : "";
29
+ }
30
+
31
+ function hashValue(raw: string): string {
32
+ return createHash("sha256").update(raw).digest("hex").slice(0, 24);
33
+ }
34
+
35
+ function buildContextKey(platformName: string, kind: string, value: string): string {
36
+ if (kind === "transcript") {
37
+ return `${platformName}_transcript_${hashValue(value)}`;
38
+ }
39
+ const safeValue = sanitizeKey(value);
40
+ return safeValue ? `${platformName}_${safeValue}` : `${platformName}_${hashValue(value)}`;
41
+ }
42
+
43
+ function deriveContextKey(ctx?: { sessionManager?: { getSessionId?: () => string; getSessionFile?: () => string } }): string | null {
44
+ const sessionId = ctx?.sessionManager?.getSessionId?.();
45
+ if (sessionId) {
46
+ return buildContextKey("omp", "session", sessionId);
47
+ }
48
+ const sessionFile = ctx?.sessionManager?.getSessionFile?.();
49
+ if (sessionFile) {
50
+ return buildContextKey("omp", "transcript", sessionFile);
51
+ }
52
+ const override = process.env.TRELLIS_CONTEXT_ID?.trim();
53
+ return override ? sanitizeKey(override) || hashValue(override) : null;
54
+ }
55
+
56
+ function isInsideRoot(root: string, candidate: string): boolean {
57
+ const rel = relative(root, candidate);
58
+ return rel === "" || (rel !== ".." && !rel.startsWith("../") && !rel.startsWith("..\\") && !isAbsolute(rel));
59
+ }
60
+
61
+ function resolveProjectFile(projectRoot: string, file: string): string | null {
62
+ try {
63
+ const rootReal = realpathSync(projectRoot);
64
+ const targetReal = realpathSync(resolve(projectRoot, file));
65
+ return isInsideRoot(rootReal, targetReal) ? targetReal : null;
66
+ } catch {
67
+ return null;
68
+ }
69
+ }
70
+
71
+ // ---------------------------------------------------------------------------
72
+ // Active task resolution
73
+ // ---------------------------------------------------------------------------
74
+
75
+ function resolveActiveTaskStatus(
76
+ projectRoot: string,
77
+ contextKey: string | null,
78
+ ): { status: string; taskDir: string | null; taskTitle: string | null } {
79
+ const sessionsDir = join(projectRoot, ".trellis", ".runtime", "sessions");
80
+ if (!existsSync(sessionsDir)) return { status: "no_task", taskDir: null, taskTitle: null };
81
+
82
+ // --- 通过 context key 解析 session 文件 ---
83
+ let sessionFilePath: string | null = null;
84
+
85
+ if (contextKey) {
86
+ const candidate = join(sessionsDir, `${contextKey}.json`);
87
+ if (existsSync(candidate)) {
88
+ sessionFilePath = candidate;
89
+ } else {
90
+ return { status: "no_task", taskDir: null, taskTitle: null };
91
+ }
92
+ } else {
93
+ // No identity: use single-session fallback only when there is exactly one session file.
94
+ let sessionFiles: string[];
95
+ try {
96
+ sessionFiles = readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
97
+ } catch {
98
+ return { status: "no_task", taskDir: null, taskTitle: null };
99
+ }
100
+ if (sessionFiles.length === 1) {
101
+ sessionFilePath = join(sessionsDir, sessionFiles[0]);
102
+ } else {
103
+ return { status: "no_task", taskDir: null, taskTitle: null };
104
+ }
105
+ }
106
+
107
+ // --- 读取 session 数据 ---
108
+ let sessionData: Record<string, unknown>;
109
+ try {
110
+ sessionData = JSON.parse(readFileSync(sessionFilePath, "utf-8"));
111
+ } catch {
112
+ return { status: "no_task", taskDir: null, taskTitle: null };
113
+ }
114
+
115
+ const currentTask = sessionData.current_task;
116
+ if (typeof currentTask !== "string" || !currentTask)
117
+ return { status: "no_task", taskDir: null, taskTitle: null };
118
+
119
+ const taskDir = join(projectRoot, currentTask);
120
+ const taskJsonPath = join(taskDir, "task.json");
121
+ if (!existsSync(taskJsonPath)) return { status: "no_task", taskDir: null, taskTitle: null };
122
+
123
+ let taskData: Record<string, unknown>;
124
+ try {
125
+ taskData = JSON.parse(readFileSync(taskJsonPath, "utf-8"));
126
+ } catch {
127
+ return { status: "no_task", taskDir: null, taskTitle: null };
128
+ }
129
+
130
+ return {
131
+ status: typeof taskData.status === "string" ? taskData.status : "planning",
132
+ taskDir,
133
+ taskTitle: typeof taskData.title === "string" ? taskData.title : null,
134
+ };
135
+ }
136
+
137
+ // ---------------------------------------------------------------------------
138
+ // Session context — spawns get_context.py default mode (same as Claude hook)
139
+ // ---------------------------------------------------------------------------
140
+
141
+ const SESSION_CONTEXT_TIMEOUT_MS = 5000;
142
+
143
+ function buildSessionContext(projectRoot: string, contextKey: string | null): string {
144
+ const script = join(projectRoot, ".trellis", "scripts", "get_context.py");
145
+ if (!existsSync(script)) return "";
146
+
147
+ try {
148
+ const result = spawnSync("python3", [script], {
149
+ cwd: projectRoot,
150
+ encoding: "utf-8",
151
+ env: contextKey
152
+ ? { ...process.env, TRELLIS_CONTEXT_ID: contextKey }
153
+ : process.env,
154
+ timeout: SESSION_CONTEXT_TIMEOUT_MS,
155
+ windowsHide: true,
156
+ });
157
+ if (result.status !== 0 || !result.stdout?.trim()) {
158
+ return "";
159
+ }
160
+ return `<session-context>\n${result.stdout.trim()}\n</session-context>`;
161
+ } catch {
162
+ return "";
163
+ }
164
+ }
165
+
166
+ // ---------------------------------------------------------------------------
167
+ // Task context — prd.md, info.md, and jsonl-referenced spec/research files
168
+ // ---------------------------------------------------------------------------
169
+
170
+ type AgentType = "trellis-implement" | "trellis-check" | "trellis-research" | null;
171
+
172
+ function buildTaskContext(projectRoot: string, taskDir: string, agentType?: AgentType): string {
173
+ const parts: string[] = [];
174
+
175
+ // prd.md and info.md — always included
176
+ let prd = "";
177
+ try { prd = readFileSync(join(taskDir, "prd.md"), "utf-8"); } catch { }
178
+ if (prd.trim()) parts.push(`## PRD\n\n${prd.trim()}`);
179
+
180
+ let info = "";
181
+ try { info = readFileSync(join(taskDir, "info.md"), "utf-8"); } catch { }
182
+ if (info.trim()) parts.push(`## Info\n\n${info.trim()}`);
183
+
184
+ // Determine which jsonl files to read based on agent type
185
+ let jsonlNames: string[];
186
+ if (agentType === "trellis-implement") {
187
+ jsonlNames = ["implement.jsonl"];
188
+ } else if (agentType === "trellis-check") {
189
+ jsonlNames = ["check.jsonl"];
190
+ } else if (agentType === "trellis-research") {
191
+ jsonlNames = []; // research agent gets only prd + info
192
+ } else {
193
+ jsonlNames = ["implement.jsonl", "check.jsonl"]; // main session: all
194
+ }
195
+
196
+ for (const jsonlName of jsonlNames) {
197
+ const jsonlPath = join(taskDir, jsonlName);
198
+ if (!existsSync(jsonlPath)) continue;
199
+
200
+ let lines: string[];
201
+ try {
202
+ lines = readFileSync(jsonlPath, "utf-8").split(/\r?\n/);
203
+ } catch {
204
+ continue;
205
+ }
206
+
207
+ const fileChunks: string[] = [];
208
+ for (const line of lines) {
209
+ const trimmed = line.trim();
210
+ if (!trimmed) continue;
211
+ try {
212
+ const row = JSON.parse(trimmed) as Record<string, unknown>;
213
+ const file = typeof row.file === "string" ? row.file.trim() : "";
214
+ if (!file) continue;
215
+ const targetPath = resolveProjectFile(projectRoot, file);
216
+ if (!targetPath) continue;
217
+ let content = "";
218
+ try { content = readFileSync(targetPath, "utf-8"); } catch { }
219
+ if (content.trim()) {
220
+ fileChunks.push(`### ${file}\n\n${content.trim()}`);
221
+ }
222
+ } catch {
223
+ // seed rows and malformed lines are non-fatal
224
+ }
225
+ }
226
+
227
+ if (fileChunks.length > 0) {
228
+ parts.push(`## ${jsonlName}\n\n${fileChunks.join("\n\n---\n\n")}`);
229
+ }
230
+ }
231
+
232
+ return parts.length > 0
233
+ ? `<task-context>\n${parts.join("\n\n")}\n</task-context>`
234
+ : "";
235
+ }
236
+
237
+ // ---------------------------------------------------------------------------
238
+ // Per-turn cache — prevents redundant workflow-state resolution within a
239
+ // single event cascade (input, before_agent_start, and context fire closely)
240
+ // ---------------------------------------------------------------------------
241
+
242
+ const SESSION_OVERVIEW_TEXT =
243
+ "Trellis workflow system active. Use skills and agents as directed by the workflow state.";
244
+
245
+ class TurnContextCache {
246
+ private key: string | null = null;
247
+ private timestamp = 0;
248
+ private workflowMsg = "";
249
+ private static readonly TTL_MS = 1500;
250
+
251
+ get(projectRoot: string, contextKey: string | null): { workflowMsg: string } {
252
+ const now = Date.now();
253
+ const cacheKey = `${projectRoot}:${contextKey ?? ""}`;
254
+ if (
255
+ this.key === cacheKey &&
256
+ now - this.timestamp < TurnContextCache.TTL_MS
257
+ ) {
258
+ return { workflowMsg: this.workflowMsg };
259
+ }
260
+
261
+ const { status } = resolveActiveTaskStatus(projectRoot, contextKey);
262
+
263
+ const workflowPath = join(projectRoot, ".trellis", "workflow.md");
264
+ let workflowMd = "";
265
+ try { workflowMd = readFileSync(workflowPath, "utf-8"); } catch { }
266
+
267
+ let workflowBody = "";
268
+ if (workflowMd) {
269
+ const blocks = parseWorkflowStateBlocks(workflowMd);
270
+ const activeBlock = blocks.find((b) => b.status === status);
271
+ if (activeBlock) {
272
+ workflowBody = `[workflow-state:${activeBlock.status}]\n${activeBlock.content}\n[/workflow-state:${activeBlock.status}]`;
273
+ }
274
+ }
275
+ if (!workflowBody) {
276
+ workflowBody = "Refer to workflow.md for current step.";
277
+ }
278
+
279
+ this.workflowMsg = `<workflow-state>\n${workflowBody}\n</workflow-state>\n\n<session-overview>\n${SESSION_OVERVIEW_TEXT}\n</session-overview>`;
280
+
281
+ this.key = cacheKey;
282
+ this.timestamp = now;
283
+ return { workflowMsg: this.workflowMsg };
284
+ }
285
+ }
286
+
287
+ // ---------------------------------------------------------------------------
288
+ // Workflow-state tag parsing
289
+ // ---------------------------------------------------------------------------
290
+
291
+ const WORKFLOW_STATE_RE =
292
+ /\[workflow-state:([A-Za-z0-9_-]+)\]\s*\n([\s\S]*?)\n\s*\[\/workflow-state:\1\]/g;
293
+
294
+ interface WorkflowStateBlock {
295
+ status: string;
296
+ content: string;
297
+ }
298
+
299
+ function parseWorkflowStateBlocks(markdown: string): WorkflowStateBlock[] {
300
+ const blocks: WorkflowStateBlock[] = [];
301
+ for (const match of markdown.matchAll(WORKFLOW_STATE_RE)) {
302
+ blocks.push({
303
+ status: match[1],
304
+ content: match[2].trim(),
305
+ });
306
+ }
307
+ return blocks;
308
+ }
309
+
310
+ // ---------------------------------------------------------------------------
311
+ // Sub-agent detection
312
+ // ---------------------------------------------------------------------------
313
+
314
+ const TRELLIS_AGENTS = new Set(["trellis-implement", "trellis-check", "trellis-research"]);
315
+
316
+ function detectAgentType(): AgentType {
317
+ const blocked = process.env.PI_BLOCKED_AGENT;
318
+ if (blocked && TRELLIS_AGENTS.has(blocked)) {
319
+ return blocked as AgentType;
320
+ }
321
+ return null;
322
+ }
323
+
324
+ // ---------------------------------------------------------------------------
325
+ // Extension entry point
326
+ // ---------------------------------------------------------------------------
327
+
328
+ export default function(pi: ExtensionAPI): void {
329
+ let projectRoot: string | null = null;
330
+ const turnCache = new TurnContextCache();
331
+ const agentType = detectAgentType();
332
+ const isSubAgent = agentType !== null;
333
+
334
+ // Tracks compaction boundaries — context handler skips scanning when no
335
+ // compaction has occurred since last injection.
336
+ let lastCompactionTs = 0;
337
+ let lastInjectionTs = 0;
338
+
339
+ const rememberContextKey = (ctx?: { sessionManager?: { getSessionId?: () => string; getSessionFile?: () => string } }): string | null => {
340
+ const key = deriveContextKey(ctx);
341
+ if (!key) return null;
342
+ return key;
343
+ };
344
+
345
+ pi.on("session_start", async (_event, ctx) => {
346
+ projectRoot = findProjectRoot(ctx.cwd);
347
+ const contextKey = rememberContextKey(ctx);
348
+
349
+ if (!projectRoot) return;
350
+
351
+ if (isSubAgent) {
352
+ // Sub-agent: inject precise task context once
353
+ const { taskDir } = resolveActiveTaskStatus(projectRoot, contextKey);
354
+ if (taskDir) {
355
+ const taskContext = buildTaskContext(projectRoot, taskDir, agentType);
356
+ if (taskContext) {
357
+ await pi.sendMessage({
358
+ customType: "trellis-task-context",
359
+ content: taskContext,
360
+ display: false,
361
+ });
362
+ }
363
+ }
364
+ } else {
365
+ // Main session: inject session context (global map) + task context
366
+ const sessionContext = buildSessionContext(projectRoot, contextKey);
367
+ if (sessionContext) {
368
+ await pi.sendMessage({
369
+ customType: "trellis-session-context",
370
+ content: sessionContext,
371
+ display: false,
372
+ });
373
+ }
374
+
375
+ const { taskDir } = resolveActiveTaskStatus(projectRoot, contextKey);
376
+ if (taskDir) {
377
+ const taskContext = buildTaskContext(projectRoot, taskDir);
378
+ if (taskContext) {
379
+ await pi.sendMessage({
380
+ customType: "trellis-task-context",
381
+ content: taskContext,
382
+ display: false,
383
+ });
384
+ }
385
+ }
386
+
387
+ ctx.ui.notify("Trellis workflow system available", "info");
388
+ }
389
+ });
390
+
391
+ pi.on("session_before_compact", async () => {
392
+ lastCompactionTs = Date.now();
393
+ });
394
+
395
+ pi.on("before_agent_start", async (_event, ctx) => {
396
+ if (!projectRoot) {
397
+ projectRoot = findProjectRoot(ctx.cwd);
398
+ }
399
+ if (!projectRoot) return;
400
+ const contextKey = rememberContextKey(ctx);
401
+
402
+ // Persistent injection: workflow state for this turn
403
+ const cached = turnCache.get(projectRoot, contextKey);
404
+ lastInjectionTs = Date.now();
405
+
406
+ return {
407
+ message: {
408
+ customType: "trellis-workflow-state",
409
+ content: cached.workflowMsg,
410
+ display: false,
411
+ },
412
+ };
413
+ });
414
+
415
+ // context fires before EVERY LLM API call (including tool-use continuations
416
+ // and post-compaction agent.continue() paths). Acts as a safety net when
417
+ // before_agent_start's persisted message was removed by compaction.
418
+ pi.on("context", async (event, ctx) => {
419
+ if (!projectRoot) return;
420
+ const contextKey = rememberContextKey(ctx);
421
+
422
+ // Fast path: no compaction since last injection — message is still present
423
+ if (lastInjectionTs > lastCompactionTs) return;
424
+
425
+ const cached = turnCache.get(projectRoot, contextKey);
426
+ if (!cached.workflowMsg) return;
427
+
428
+ // Post-compaction: reverse-scan to confirm absence before injecting
429
+ const messages = event.messages as { role?: string; customType?: string }[];
430
+ for (let i = messages.length - 1; i >= 0; i--) {
431
+ if (messages[i].role === "custom" && messages[i].customType === "trellis-workflow-state") {
432
+ lastInjectionTs = Date.now();
433
+ return;
434
+ }
435
+ }
436
+
437
+ lastInjectionTs = Date.now();
438
+ return {
439
+ messages: [
440
+ ...event.messages,
441
+ {
442
+ role: "custom",
443
+ customType: "trellis-workflow-state",
444
+ content: cached.workflowMsg,
445
+ timestamp: Date.now(),
446
+ },
447
+ ],
448
+ };
449
+ });
450
+
451
+ pi.on("input", async (_event, ctx) => {
452
+ if (!projectRoot) {
453
+ projectRoot = findProjectRoot(ctx.cwd);
454
+ }
455
+ // Resolve projectRoot on first input if session_start missed it
456
+ if (!projectRoot) return { action: "continue" };
457
+ const contextKey = rememberContextKey(ctx);
458
+ // Pre-warm the cache so before_agent_start and context can use it
459
+ turnCache.get(projectRoot, contextKey);
460
+ return { action: "continue" };
461
+ });
462
+ }
@@ -0,0 +1,4 @@
1
+ import type { AgentTemplate } from "../template-utils.js";
2
+ export declare function getAllAgents(): AgentTemplate[];
3
+ export declare function getExtensionTemplate(): string;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/templates/omp/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAI1D,wBAAgB,YAAY,IAAI,aAAa,EAAE,CAE9C;AAED,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C"}
@@ -0,0 +1,9 @@
1
+ import { createTemplateReader } from "../template-utils.js";
2
+ const { listMdAgents, readTemplate } = createTemplateReader(import.meta.url);
3
+ export function getAllAgents() {
4
+ return listMdAgents();
5
+ }
6
+ export function getExtensionTemplate() {
7
+ return readTemplate("extensions/trellis/index.ts.txt");
8
+ }
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/templates/omp/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAG5D,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE,GAAG,oBAAoB,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAE7E,MAAM,UAAU,YAAY;IAC1B,OAAO,YAAY,EAAE,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,oBAAoB;IAClC,OAAO,YAAY,CAAC,iCAAiC,CAAC,CAAC;AACzD,CAAC"}
@@ -2,7 +2,7 @@
2
2
  name: trellis-check
3
3
  description: |
4
4
  Code quality check expert. Reviews changes against Trellis specs, fixes issues directly, and verifies quality gates.
5
- tools: Read, Write, Edit, Bash, Glob, Grep
5
+ tools: read, write, edit, bash, find, grep
6
6
  ---
7
7
  # Check Agent
8
8
 
@@ -2,7 +2,7 @@
2
2
  name: trellis-implement
3
3
  description: |
4
4
  Code implementation expert. Understands Trellis specs and requirements, then implements features. No git commit allowed.
5
- tools: Read, Write, Edit, Bash, Glob, Grep
5
+ tools: read, write, edit, bash, find, grep
6
6
  ---
7
7
  # Implement Agent
8
8
 
@@ -2,7 +2,7 @@
2
2
  name: trellis-research
3
3
  description: |
4
4
  Code and technical research expert. Finds relevant files, patterns, docs, and persists findings to the current task's research/ directory.
5
- tools: Read, Write, Bash, Glob, Grep
5
+ tools: read, write, bash, find, grep
6
6
  ---
7
7
  # Research Agent
8
8