@mindfoldhq/trellis 0.5.0-beta.13 → 0.5.0-beta.15

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 (119) hide show
  1. package/README.md +5 -5
  2. package/dist/cli/index.js +1 -0
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/commands/init.d.ts +1 -0
  5. package/dist/commands/init.d.ts.map +1 -1
  6. package/dist/commands/init.js +24 -20
  7. package/dist/commands/init.js.map +1 -1
  8. package/dist/commands/update.d.ts.map +1 -1
  9. package/dist/commands/update.js +15 -12
  10. package/dist/commands/update.js.map +1 -1
  11. package/dist/configurators/claude.js +1 -1
  12. package/dist/configurators/claude.js.map +1 -1
  13. package/dist/configurators/codebuddy.js +1 -1
  14. package/dist/configurators/codebuddy.js.map +1 -1
  15. package/dist/configurators/codex.d.ts.map +1 -1
  16. package/dist/configurators/codex.js +3 -6
  17. package/dist/configurators/codex.js.map +1 -1
  18. package/dist/configurators/copilot.d.ts.map +1 -1
  19. package/dist/configurators/copilot.js +4 -11
  20. package/dist/configurators/copilot.js.map +1 -1
  21. package/dist/configurators/cursor.js +1 -1
  22. package/dist/configurators/cursor.js.map +1 -1
  23. package/dist/configurators/droid.js +1 -1
  24. package/dist/configurators/droid.js.map +1 -1
  25. package/dist/configurators/gemini.d.ts.map +1 -1
  26. package/dist/configurators/gemini.js +1 -3
  27. package/dist/configurators/gemini.js.map +1 -1
  28. package/dist/configurators/index.d.ts.map +1 -1
  29. package/dist/configurators/index.js +24 -38
  30. package/dist/configurators/index.js.map +1 -1
  31. package/dist/configurators/kiro.js +1 -1
  32. package/dist/configurators/kiro.js.map +1 -1
  33. package/dist/configurators/pi.d.ts +3 -0
  34. package/dist/configurators/pi.d.ts.map +1 -0
  35. package/dist/configurators/pi.js +39 -0
  36. package/dist/configurators/pi.js.map +1 -0
  37. package/dist/configurators/qoder.d.ts.map +1 -1
  38. package/dist/configurators/qoder.js +1 -3
  39. package/dist/configurators/qoder.js.map +1 -1
  40. package/dist/configurators/shared.d.ts +2 -4
  41. package/dist/configurators/shared.d.ts.map +1 -1
  42. package/dist/configurators/shared.js +6 -9
  43. package/dist/configurators/shared.js.map +1 -1
  44. package/dist/migrations/manifests/0.5.0-beta.14.json +9 -0
  45. package/dist/migrations/manifests/0.5.0-beta.15.json +126 -0
  46. package/dist/templates/claude/agents/trellis-research.md +1 -1
  47. package/dist/templates/claude/settings.json +0 -4
  48. package/dist/templates/codebuddy/agents/trellis-research.md +1 -1
  49. package/dist/templates/codex/agents/trellis-check.toml +0 -16
  50. package/dist/templates/codex/agents/trellis-implement.toml +0 -16
  51. package/dist/templates/codex/agents/trellis-research.toml +3 -2
  52. package/dist/templates/codex/hooks/session-start.py +82 -22
  53. package/dist/templates/codex/skills/start/SKILL.md +1 -1
  54. package/dist/templates/copilot/hooks/session-start.py +84 -26
  55. package/dist/templates/copilot/prompts/start.prompt.md +1 -1
  56. package/dist/templates/cursor/agents/trellis-check.md +1 -1
  57. package/dist/templates/cursor/agents/trellis-implement.md +1 -1
  58. package/dist/templates/cursor/agents/trellis-research.md +2 -2
  59. package/dist/templates/cursor/hooks.json +7 -1
  60. package/dist/templates/droid/droids/trellis-research.md +1 -1
  61. package/dist/templates/extract.d.ts +6 -0
  62. package/dist/templates/extract.d.ts.map +1 -1
  63. package/dist/templates/extract.js +14 -0
  64. package/dist/templates/extract.js.map +1 -1
  65. package/dist/templates/gemini/agents/trellis-research.md +1 -1
  66. package/dist/templates/kiro/agents/trellis-research.json +1 -1
  67. package/dist/templates/markdown/agents.md +11 -12
  68. package/dist/templates/markdown/gitignore.txt +3 -0
  69. package/dist/templates/opencode/agents/trellis-check.md +1 -1
  70. package/dist/templates/opencode/agents/trellis-implement.md +1 -1
  71. package/dist/templates/opencode/agents/trellis-research.md +2 -2
  72. package/dist/templates/opencode/lib/trellis-context.js +100 -13
  73. package/dist/templates/opencode/plugins/inject-subagent-context.js +54 -4
  74. package/dist/templates/opencode/plugins/inject-workflow-state.js +50 -23
  75. package/dist/templates/opencode/plugins/session-start.js +46 -21
  76. package/dist/templates/pi/agents/trellis-check.md +28 -0
  77. package/dist/templates/pi/agents/trellis-implement.md +33 -0
  78. package/dist/templates/pi/agents/trellis-research.md +25 -0
  79. package/dist/templates/pi/extensions/trellis/index.ts.txt +549 -0
  80. package/dist/templates/pi/index.d.ts +5 -0
  81. package/dist/templates/pi/index.d.ts.map +1 -0
  82. package/dist/templates/pi/index.js +12 -0
  83. package/dist/templates/pi/index.js.map +1 -0
  84. package/dist/templates/pi/settings.json +12 -0
  85. package/dist/templates/qoder/agents/trellis-research.md +1 -1
  86. package/dist/templates/shared-hooks/index.d.ts +31 -0
  87. package/dist/templates/shared-hooks/index.d.ts.map +1 -1
  88. package/dist/templates/shared-hooks/index.js +59 -0
  89. package/dist/templates/shared-hooks/index.js.map +1 -1
  90. package/dist/templates/shared-hooks/inject-shell-session-context.py +180 -0
  91. package/dist/templates/shared-hooks/inject-subagent-context.py +128 -26
  92. package/dist/templates/shared-hooks/inject-workflow-state.py +101 -61
  93. package/dist/templates/shared-hooks/session-start.py +151 -28
  94. package/dist/templates/trellis/gitignore.txt +3 -0
  95. package/dist/templates/trellis/index.d.ts +1 -0
  96. package/dist/templates/trellis/index.d.ts.map +1 -1
  97. package/dist/templates/trellis/index.js +2 -0
  98. package/dist/templates/trellis/index.js.map +1 -1
  99. package/dist/templates/trellis/scripts/common/__init__.py +8 -0
  100. package/dist/templates/trellis/scripts/common/active_task.py +593 -0
  101. package/dist/templates/trellis/scripts/common/cli_adapter.py +43 -8
  102. package/dist/templates/trellis/scripts/common/paths.py +61 -58
  103. package/dist/templates/trellis/scripts/common/session_context.py +12 -0
  104. package/dist/templates/trellis/scripts/common/task_store.py +4 -6
  105. package/dist/templates/trellis/scripts/task.py +56 -14
  106. package/dist/templates/trellis/workflow.md +31 -26
  107. package/dist/types/ai-tools.d.ts +3 -3
  108. package/dist/types/ai-tools.d.ts.map +1 -1
  109. package/dist/types/ai-tools.js +16 -0
  110. package/dist/types/ai-tools.js.map +1 -1
  111. package/dist/utils/template-fetcher.d.ts +22 -6
  112. package/dist/utils/template-fetcher.d.ts.map +1 -1
  113. package/dist/utils/template-fetcher.js +405 -27
  114. package/dist/utils/template-fetcher.js.map +1 -1
  115. package/dist/utils/template-hash.d.ts.map +1 -1
  116. package/dist/utils/template-hash.js +3 -2
  117. package/dist/utils/template-hash.js.map +1 -1
  118. package/package.json +1 -1
  119. package/dist/templates/shared-hooks/statusline.py +0 -218
@@ -1,7 +1,7 @@
1
1
  """
2
2
  CLI Adapter for Multi-Platform Support.
3
3
 
4
- Abstracts differences between Claude Code, OpenCode, Cursor, iFlow, Codex, Kilo, Kiro Code, Gemini CLI, Antigravity, Windsurf, Qoder, CodeBuddy, GitHub Copilot, and Factory Droid interfaces.
4
+ Abstracts differences between Claude Code, OpenCode, Cursor, iFlow, Codex, Kilo, Kiro Code, Gemini CLI, Antigravity, Windsurf, Qoder, CodeBuddy, GitHub Copilot, Factory Droid, and Pi Agent interfaces.
5
5
 
6
6
  Supported platforms:
7
7
  - claude: Claude Code (default)
@@ -18,6 +18,7 @@ Supported platforms:
18
18
  - codebuddy: CodeBuddy
19
19
  - copilot: GitHub Copilot (VS Code)
20
20
  - droid: Factory Droid (commands-based)
21
+ - pi: Pi Agent (extension-backed)
21
22
 
22
23
  Usage:
23
24
  from common.cli_adapter import CLIAdapter
@@ -51,6 +52,7 @@ Platform = Literal[
51
52
  "codebuddy",
52
53
  "copilot",
53
54
  "droid",
55
+ "pi",
54
56
  ]
55
57
 
56
58
 
@@ -95,7 +97,7 @@ class CLIAdapter:
95
97
  """Get platform-specific config directory name.
96
98
 
97
99
  Returns:
98
- Directory name ('.claude', '.opencode', '.cursor', '.iflow', '.codex', '.kilocode', '.kiro', '.gemini', '.agent', '.windsurf', '.qoder', or '.codebuddy')
100
+ Directory name ('.claude', '.opencode', '.cursor', '.iflow', '.codex', '.kilocode', '.kiro', '.gemini', '.agent', '.windsurf', '.qoder', '.codebuddy', '.github/copilot', '.factory', or '.pi')
99
101
  """
100
102
  if self.platform == "opencode":
101
103
  return ".opencode"
@@ -123,6 +125,8 @@ class CLIAdapter:
123
125
  return ".github/copilot"
124
126
  elif self.platform == "droid":
125
127
  return ".factory"
128
+ elif self.platform == "pi":
129
+ return ".pi"
126
130
  else:
127
131
  return ".claude"
128
132
 
@@ -133,7 +137,7 @@ class CLIAdapter:
133
137
  project_root: Project root directory
134
138
 
135
139
  Returns:
136
- Path to config directory (.claude, .opencode, .cursor, .iflow, .codex, .kilocode, .kiro, .gemini, .agent, .windsurf, .qoder, or .codebuddy)
140
+ Path to config directory (.claude, .opencode, .cursor, .iflow, .codex, .kilocode, .kiro, .gemini, .agent, .windsurf, .qoder, .codebuddy, .github/copilot, .factory, or .pi)
137
141
  """
138
142
  return project_root / self.config_dir_name
139
143
 
@@ -167,8 +171,20 @@ class CLIAdapter:
167
171
  Antigravity uses workflow directory: .agent/workflows/<name>.md
168
172
  Windsurf uses workflow directory: .windsurf/workflows/trellis-<name>.md
169
173
  Copilot uses prompt files: .github/prompts/<name>.prompt.md
174
+ Pi uses prompt templates: .pi/prompts/trellis-<name>.md
170
175
  Claude/OpenCode use subdirectory: .claude/commands/trellis/<name>.md
171
176
  """
177
+ if self.platform == "pi":
178
+ prompts_dir = self.get_config_dir(project_root) / "prompts"
179
+ if not parts:
180
+ return prompts_dir
181
+ if len(parts) >= 2 and parts[0] == "trellis":
182
+ filename = parts[-1]
183
+ if filename.endswith(".md"):
184
+ filename = filename[:-3]
185
+ return prompts_dir / f"trellis-{filename}.md"
186
+ return prompts_dir / Path(*parts)
187
+
172
188
  if self.platform == "windsurf":
173
189
  workflow_dir = self.get_config_dir(project_root) / "workflows"
174
190
  if not parts:
@@ -227,6 +243,7 @@ class CLIAdapter:
227
243
  Gemini: .gemini/commands/trellis/<name>.toml
228
244
  Antigravity: .agent/workflows/<name>.md
229
245
  Windsurf: .windsurf/workflows/trellis-<name>.md
246
+ Pi: .pi/prompts/trellis-<name>.md
230
247
  Others: .{platform}/commands/trellis/<name>.md
231
248
  """
232
249
  if self.platform == "cursor":
@@ -249,6 +266,8 @@ class CLIAdapter:
249
266
  return f".github/prompts/{name}.prompt.md"
250
267
  elif self.platform == "droid":
251
268
  return f".factory/commands/trellis/{name}.md"
269
+ elif self.platform == "pi":
270
+ return f".pi/prompts/trellis-{name}.md"
252
271
  else:
253
272
  return f"{self.config_dir_name}/commands/trellis/{name}.md"
254
273
 
@@ -284,6 +303,8 @@ class CLIAdapter:
284
303
  return {}
285
304
  elif self.platform == "droid":
286
305
  return {}
306
+ elif self.platform == "pi":
307
+ return {}
287
308
  else:
288
309
  return {"CLAUDE_NON_INTERACTIVE": "1"}
289
310
 
@@ -367,6 +388,8 @@ class CLIAdapter:
367
388
  raise ValueError(
368
389
  "Factory Droid CLI agent run is not yet supported."
369
390
  )
391
+ elif self.platform == "pi":
392
+ cmd = ["pi", "-p", prompt]
370
393
 
371
394
  else: # claude
372
395
  cmd = ["claude", "-p"]
@@ -431,6 +454,8 @@ class CLIAdapter:
431
454
  raise ValueError(
432
455
  "Factory Droid CLI resume is not yet supported."
433
456
  )
457
+ elif self.platform == "pi":
458
+ return ["pi", "-c", session_id]
434
459
  else:
435
460
  return ["claude", "--resume", session_id]
436
461
 
@@ -503,6 +528,8 @@ class CLIAdapter:
503
528
  return "copilot"
504
529
  elif self.platform == "droid":
505
530
  return "droid"
531
+ elif self.platform == "pi":
532
+ return "pi"
506
533
  else:
507
534
  return "claude"
508
535
 
@@ -513,7 +540,7 @@ class CLIAdapter:
513
540
  Claude Code, OpenCode, iFlow, and Codex support CLI agent execution.
514
541
  Cursor is IDE-only and doesn't support CLI agents.
515
542
  """
516
- return self.platform in ("claude", "opencode", "iflow", "codex")
543
+ return self.platform in ("claude", "opencode", "iflow", "codex", "pi")
517
544
 
518
545
  @property
519
546
  def requires_agent_definition_file(self) -> bool:
@@ -567,7 +594,7 @@ def get_cli_adapter(platform: str = "claude") -> CLIAdapter:
567
594
  """Get CLI adapter for the specified platform.
568
595
 
569
596
  Args:
570
- platform: Platform name ('claude', 'opencode', 'cursor', 'iflow', 'codex', 'kilo', 'kiro', 'gemini', 'antigravity', 'windsurf', 'qoder', or 'codebuddy')
597
+ platform: Platform name ('claude', 'opencode', 'cursor', 'iflow', 'codex', 'kilo', 'kiro', 'gemini', 'antigravity', 'windsurf', 'qoder', 'codebuddy', 'copilot', 'droid', or 'pi')
571
598
 
572
599
  Returns:
573
600
  CLIAdapter instance
@@ -590,9 +617,10 @@ def get_cli_adapter(platform: str = "claude") -> CLIAdapter:
590
617
  "codebuddy",
591
618
  "copilot",
592
619
  "droid",
620
+ "pi",
593
621
  ):
594
622
  raise ValueError(
595
- f"Unsupported platform: {platform} (must be 'claude', 'opencode', 'cursor', 'iflow', 'codex', 'kilo', 'kiro', 'gemini', 'antigravity', 'windsurf', 'qoder', 'codebuddy', 'copilot', or 'droid')"
623
+ f"Unsupported platform: {platform} (must be 'claude', 'opencode', 'cursor', 'iflow', 'codex', 'kilo', 'kiro', 'gemini', 'antigravity', 'windsurf', 'qoder', 'codebuddy', 'copilot', 'droid', or 'pi')"
596
624
  )
597
625
 
598
626
  return CLIAdapter(platform=platform) # type: ignore
@@ -613,6 +641,7 @@ _ALL_PLATFORM_CONFIG_DIRS = (
613
641
  ".codebuddy",
614
642
  ".github/copilot",
615
643
  ".factory",
644
+ ".pi",
616
645
  )
617
646
  """Platform-specific config directory names used by detect_platform exclusion
618
647
  checks. `.agents/skills/` is NOT listed here: it is a shared cross-platform
@@ -646,13 +675,14 @@ def detect_platform(project_root: Path) -> Platform:
646
675
  10. .windsurf/workflows exists and no other platform dirs → windsurf
647
676
  11. .codebuddy directory exists → codebuddy
648
677
  12. .qoder directory exists → qoder
649
- 13. Defaultclaude
678
+ 13. .pi directory exists pi
679
+ 14. Default → claude
650
680
 
651
681
  Args:
652
682
  project_root: Project root directory
653
683
 
654
684
  Returns:
655
- Detected platform ('claude', 'opencode', 'cursor', 'iflow', 'codex', 'kilo', 'kiro', 'gemini', 'antigravity', 'windsurf', 'qoder', 'codebuddy', or default 'claude')
685
+ Detected platform ('claude', 'opencode', 'cursor', 'iflow', 'codex', 'kilo', 'kiro', 'gemini', 'antigravity', 'windsurf', 'qoder', 'codebuddy', 'copilot', 'droid', 'pi', or default 'claude')
656
686
  """
657
687
  import os
658
688
 
@@ -673,6 +703,7 @@ def detect_platform(project_root: Path) -> Platform:
673
703
  "codebuddy",
674
704
  "copilot",
675
705
  "droid",
706
+ "pi",
676
707
  ):
677
708
  return env_platform # type: ignore
678
709
 
@@ -742,6 +773,10 @@ def detect_platform(project_root: Path) -> Platform:
742
773
  if (project_root / ".factory").is_dir():
743
774
  return "droid"
744
775
 
776
+ # Check for .pi directory (Pi Agent-specific)
777
+ if (project_root / ".pi").is_dir():
778
+ return "pi"
779
+
745
780
  # Fallback: checkout only has the Codex shared-skills layer
746
781
  # (.agents/skills/trellis-* dirs) and no explicit platform config dir.
747
782
  # Happens on fresh clones where .codex/ is gitignored/absent but the
@@ -207,22 +207,8 @@ def count_lines(file_path: Path) -> int:
207
207
  # Current Task Management
208
208
  # =============================================================================
209
209
 
210
- def _get_current_task_file(repo_root: Path | None = None) -> Path:
211
- """Get .current-task file path.
212
-
213
- Args:
214
- repo_root: Repository root path. Defaults to auto-detected.
215
-
216
- Returns:
217
- Path to .current-task file.
218
- """
219
- if repo_root is None:
220
- repo_root = get_repo_root()
221
- return repo_root / DIR_WORKFLOW / FILE_CURRENT_TASK
222
-
223
-
224
210
  def normalize_task_ref(task_ref: str) -> str:
225
- """Normalize a task ref for stable storage in .current-task.
211
+ """Normalize a task ref for stable runtime storage.
226
212
 
227
213
  Stored refs should prefer repo-relative POSIX paths like
228
214
  `.trellis/tasks/03-27-my-task`, even on Windows. Absolute paths are preserved
@@ -247,7 +233,7 @@ def normalize_task_ref(task_ref: str) -> str:
247
233
 
248
234
 
249
235
  def resolve_task_ref(task_ref: str, repo_root: Path | None = None) -> Path | None:
250
- """Resolve a task ref from .current-task to an absolute task directory path."""
236
+ """Resolve a task ref to an absolute task directory path."""
251
237
  if repo_root is None:
252
238
  repo_root = get_repo_root()
253
239
 
@@ -265,7 +251,11 @@ def resolve_task_ref(task_ref: str, repo_root: Path | None = None) -> Path | Non
265
251
  return repo_root / DIR_WORKFLOW / DIR_TASKS / path_obj
266
252
 
267
253
 
268
- def get_current_task(repo_root: Path | None = None) -> str | None:
254
+ def get_current_task(
255
+ repo_root: Path | None = None,
256
+ platform_input: dict | None = None,
257
+ platform: str | None = None,
258
+ ) -> str | None:
269
259
  """Get current task directory path (relative to repo_root).
270
260
 
271
261
  Args:
@@ -274,19 +264,19 @@ def get_current_task(repo_root: Path | None = None) -> str | None:
274
264
  Returns:
275
265
  Relative path to current task directory or None.
276
266
  """
277
- current_file = _get_current_task_file(repo_root)
267
+ if repo_root is None:
268
+ repo_root = get_repo_root()
278
269
 
279
- if not current_file.is_file():
280
- return None
270
+ from .active_task import resolve_active_task
281
271
 
282
- try:
283
- content = current_file.read_text(encoding="utf-8").strip()
284
- return normalize_task_ref(content) if content else None
285
- except (OSError, IOError):
286
- return None
272
+ return resolve_active_task(repo_root, platform_input, platform).task_path
287
273
 
288
274
 
289
- def get_current_task_abs(repo_root: Path | None = None) -> Path | None:
275
+ def get_current_task_abs(
276
+ repo_root: Path | None = None,
277
+ platform_input: dict | None = None,
278
+ platform: str | None = None,
279
+ ) -> Path | None:
290
280
  """Get current task directory absolute path.
291
281
 
292
282
  Args:
@@ -298,14 +288,33 @@ def get_current_task_abs(repo_root: Path | None = None) -> Path | None:
298
288
  if repo_root is None:
299
289
  repo_root = get_repo_root()
300
290
 
301
- relative = get_current_task(repo_root)
291
+ relative = get_current_task(repo_root, platform_input, platform)
302
292
  if relative:
303
293
  return resolve_task_ref(relative, repo_root)
304
294
  return None
305
295
 
306
296
 
307
- def set_current_task(task_path: str, repo_root: Path | None = None) -> bool:
308
- """Set current task.
297
+ def get_current_task_source(
298
+ repo_root: Path | None = None,
299
+ platform_input: dict | None = None,
300
+ platform: str | None = None,
301
+ ) -> tuple[str, str | None, str | None]:
302
+ """Get active task source as (`source`, `context_key`, `task_path`)."""
303
+ if repo_root is None:
304
+ repo_root = get_repo_root()
305
+
306
+ from .active_task import get_current_task_source as _get_source
307
+
308
+ return _get_source(repo_root, platform_input, platform)
309
+
310
+
311
+ def set_current_task(
312
+ task_path: str,
313
+ repo_root: Path | None = None,
314
+ platform_input: dict | None = None,
315
+ platform: str | None = None,
316
+ ) -> bool:
317
+ """Set current task in session scope.
309
318
 
310
319
  Args:
311
320
  task_path: Task directory path (relative to repo_root).
@@ -317,31 +326,22 @@ def set_current_task(task_path: str, repo_root: Path | None = None) -> bool:
317
326
  if repo_root is None:
318
327
  repo_root = get_repo_root()
319
328
 
320
- normalized = normalize_task_ref(task_path)
321
- if not normalized:
322
- return False
329
+ from .active_task import set_active_task
323
330
 
324
- # Verify task directory exists
325
- full_path = resolve_task_ref(normalized, repo_root)
326
- if full_path is None or not full_path.is_dir():
327
- return False
331
+ return set_active_task(
332
+ task_path,
333
+ repo_root,
334
+ platform_input=platform_input,
335
+ platform=platform,
336
+ ) is not None
328
337
 
329
- try:
330
- normalized = full_path.relative_to(repo_root).as_posix()
331
- except ValueError:
332
- normalized = str(full_path)
333
338
 
334
- current_file = _get_current_task_file(repo_root)
335
-
336
- try:
337
- current_file.write_text(normalized, encoding="utf-8")
338
- return True
339
- except (OSError, IOError):
340
- return False
341
-
342
-
343
- def clear_current_task(repo_root: Path | None = None) -> bool:
344
- """Clear current task.
339
+ def clear_current_task(
340
+ repo_root: Path | None = None,
341
+ platform_input: dict | None = None,
342
+ platform: str | None = None,
343
+ ) -> bool:
344
+ """Clear current task in session scope.
345
345
 
346
346
  Args:
347
347
  repo_root: Repository root path. Defaults to auto-detected.
@@ -349,14 +349,17 @@ def clear_current_task(repo_root: Path | None = None) -> bool:
349
349
  Returns:
350
350
  True on success.
351
351
  """
352
- current_file = _get_current_task_file(repo_root)
352
+ if repo_root is None:
353
+ repo_root = get_repo_root()
353
354
 
354
- try:
355
- if current_file.is_file():
356
- current_file.unlink()
357
- return True
358
- except (OSError, IOError):
359
- return False
355
+ from .active_task import clear_active_task
356
+
357
+ clear_active_task(
358
+ repo_root,
359
+ platform_input=platform_input,
360
+ platform=platform,
361
+ )
362
+ return True
360
363
 
361
364
 
362
365
  def has_current_task(repo_root: Path | None = None) -> bool:
@@ -29,6 +29,7 @@ from .paths import (
29
29
  count_lines,
30
30
  get_active_journal_file,
31
31
  get_current_task,
32
+ get_current_task_source,
32
33
  get_developer,
33
34
  get_repo_root,
34
35
  get_tasks_dir,
@@ -279,7 +280,11 @@ def get_context_text(repo_root: Path | None = None) -> str:
279
280
  current_task = get_current_task(repo_root)
280
281
  if current_task:
281
282
  current_task_dir = repo_root / current_task
283
+ source_type, context_key, _ = get_current_task_source(repo_root)
282
284
  lines.append(f"Path: {current_task}")
285
+ lines.append(
286
+ f"Source: {source_type}" + (f":{context_key}" if context_key else "")
287
+ )
283
288
 
284
289
  ct = load_task(current_task_dir)
285
290
  if ct:
@@ -429,12 +434,15 @@ def get_context_record_json(repo_root: Path | None = None) -> dict:
429
434
  current_task_info = None
430
435
  current_task = get_current_task(repo_root)
431
436
  if current_task:
437
+ source_type, context_key, _ = get_current_task_source(repo_root)
432
438
  ct = load_task(repo_root / current_task)
433
439
  if ct:
434
440
  current_task_info = {
435
441
  "path": current_task,
436
442
  "name": ct.name,
437
443
  "status": ct.status,
444
+ "source": source_type,
445
+ "contextKey": context_key,
438
446
  }
439
447
 
440
448
  # Package git repos
@@ -539,7 +547,11 @@ def get_context_text_record(repo_root: Path | None = None) -> str:
539
547
  lines.append("## CURRENT TASK")
540
548
  current_task = get_current_task(repo_root)
541
549
  if current_task:
550
+ source_type, context_key, _ = get_current_task_source(repo_root)
542
551
  lines.append(f"Path: {current_task}")
552
+ lines.append(
553
+ f"Source: {source_type}" + (f":{context_key}" if context_key else "")
554
+ )
543
555
  ct = load_task(repo_root / current_task)
544
556
  if ct:
545
557
  lines.append(f"Name: {ct.name}")
@@ -36,9 +36,7 @@ from .paths import (
36
36
  DIR_TASKS,
37
37
  DIR_WORKFLOW,
38
38
  FILE_TASK_JSON,
39
- clear_current_task,
40
39
  generate_task_date_prefix,
41
- get_current_task,
42
40
  get_developer,
43
41
  get_repo_root,
44
42
  get_tasks_dir,
@@ -99,6 +97,7 @@ _SUBAGENT_CONFIG_DIRS: tuple[str, ...] = (
99
97
  ".codebuddy",
100
98
  ".factory", # Factory Droid
101
99
  ".github/copilot",
100
+ ".pi", # Pi Agent
102
101
  )
103
102
 
104
103
  _SEED_EXAMPLE = (
@@ -353,10 +352,9 @@ def cmd_archive(args: argparse.Namespace) -> int:
353
352
  child_data["parent"] = None
354
353
  write_json(child_json, child_data)
355
354
 
356
- # Clear if current task
357
- current = get_current_task(repo_root)
358
- if current and dir_name in current:
359
- clear_current_task(repo_root)
355
+ # Clear any session that still points at this task before the path moves.
356
+ from .active_task import clear_task_from_sessions
357
+ clear_task_from_sessions(str(task_dir), repo_root)
360
358
 
361
359
  # Archive
362
360
  result = archive_task_complete(task_dir, repo_root)
@@ -8,8 +8,9 @@ Usage:
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
11
- python3 task.py start <dir> # Set as current task
12
- python3 task.py finish # Clear current task
11
+ python3 task.py start <dir> # Set active task
12
+ python3 task.py current [--source] # Show active task
13
+ python3 task.py finish # Clear active task
13
14
  python3 task.py set-branch <dir> <branch> # Set git branch
14
15
  python3 task.py set-base-branch <dir> <branch> # Set PR target branch
15
16
  python3 task.py set-scope <dir> <scope> # Set scope for PR title
@@ -34,8 +35,12 @@ from common.paths import (
34
35
  get_developer,
35
36
  get_tasks_dir,
36
37
  get_current_task,
37
- set_current_task,
38
- clear_current_task,
38
+ )
39
+ from common.active_task import (
40
+ clear_active_task,
41
+ resolve_active_task,
42
+ resolve_context_key,
43
+ set_active_task,
39
44
  )
40
45
  from common.io import read_json, write_json
41
46
  from common.task_utils import resolve_task_dir, run_task_hooks
@@ -63,7 +68,7 @@ from common.task_context import (
63
68
  # =============================================================================
64
69
 
65
70
  def cmd_start(args: argparse.Namespace) -> int:
66
- """Set current task."""
71
+ """Set active task."""
67
72
  repo_root = get_repo_root()
68
73
  task_input = args.dir
69
74
 
@@ -85,8 +90,18 @@ def cmd_start(args: argparse.Namespace) -> int:
85
90
  except ValueError:
86
91
  task_dir = str(full_path)
87
92
 
88
- if set_current_task(task_dir, repo_root):
93
+ if not resolve_context_key():
94
+ print(colored("Error: Cannot set active task without a session identity.", Colors.RED))
95
+ print(
96
+ "Hint: run inside an AI IDE/session that exposes session identity, "
97
+ "or set TRELLIS_CONTEXT_ID before running task.py start."
98
+ )
99
+ return 1
100
+
101
+ active = set_active_task(task_dir, repo_root)
102
+ if active:
89
103
  print(colored(f"✓ Current task set to: {task_dir}", Colors.GREEN))
104
+ print(f"Source: {active.source}")
90
105
 
91
106
  task_json_path = full_path / FILE_TASK_JSON
92
107
  if task_json_path.is_file():
@@ -107,10 +122,10 @@ def cmd_start(args: argparse.Namespace) -> int:
107
122
 
108
123
 
109
124
  def cmd_finish(args: argparse.Namespace) -> int:
110
- """Clear current task."""
111
- _ = args # signature required by argparse dispatcher
125
+ """Clear active task."""
112
126
  repo_root = get_repo_root()
113
- current = get_current_task(repo_root)
127
+ active = clear_active_task(repo_root)
128
+ current = active.task_path
114
129
 
115
130
  if not current:
116
131
  print(colored("No current task set", Colors.YELLOW))
@@ -119,14 +134,33 @@ def cmd_finish(args: argparse.Namespace) -> int:
119
134
  # Resolve task.json path before clearing
120
135
  task_json_path = repo_root / current / FILE_TASK_JSON
121
136
 
122
- clear_current_task(repo_root)
123
137
  print(colored(f"✓ Cleared current task (was: {current})", Colors.GREEN))
138
+ print(f"Source: {active.source}")
124
139
 
125
140
  if task_json_path.is_file():
126
141
  run_task_hooks("after_finish", task_json_path, repo_root)
127
142
  return 0
128
143
 
129
144
 
145
+ def cmd_current(args: argparse.Namespace) -> int:
146
+ """Show active task."""
147
+ repo_root = get_repo_root()
148
+ active = resolve_active_task(repo_root)
149
+
150
+ if args.source:
151
+ print(f"Current task: {active.task_path or '(none)'}")
152
+ print(f"Source: {active.source}")
153
+ if active.stale:
154
+ print("State: stale")
155
+ return 0 if active.task_path else 1
156
+
157
+ if active.task_path:
158
+ print(active.task_path)
159
+ return 0
160
+
161
+ return 1
162
+
163
+
130
164
  # =============================================================================
131
165
  # Command: list
132
166
  # =============================================================================
@@ -257,8 +291,9 @@ Usage:
257
291
  python3 task.py add-context <dir> <jsonl> <path> [reason] Add entry to jsonl
258
292
  python3 task.py validate <dir> Validate jsonl files
259
293
  python3 task.py list-context <dir> List jsonl entries
260
- python3 task.py start <dir> Set as current task
261
- python3 task.py finish Clear current task
294
+ python3 task.py start <dir> Set active task
295
+ python3 task.py current [--source] Show active task
296
+ python3 task.py finish Clear active task
262
297
  python3 task.py set-branch <dir> <branch> Set git branch
263
298
  python3 task.py set-base-branch <dir> <branch> Set PR target branch
264
299
  python3 task.py set-scope <dir> <scope> Set scope for PR title
@@ -282,6 +317,7 @@ Examples:
282
317
  python3 task.py add-context <dir> implement .trellis/spec/cli/backend/auth.md "Auth guidelines"
283
318
  python3 task.py set-branch <dir> task/add-login
284
319
  python3 task.py start .trellis/tasks/01-21-add-login
320
+ python3 task.py current --source
285
321
  python3 task.py finish
286
322
  python3 task.py archive add-login
287
323
  python3 task.py add-subtask parent-task child-task # Link existing tasks
@@ -360,11 +396,16 @@ def main() -> int:
360
396
  p_listctx.add_argument("dir", help="Task directory")
361
397
 
362
398
  # start
363
- p_start = subparsers.add_parser("start", help="Set current task")
399
+ p_start = subparsers.add_parser("start", help="Set active task")
364
400
  p_start.add_argument("dir", help="Task directory")
365
401
 
402
+ # current
403
+ p_current = subparsers.add_parser("current", help="Show active task")
404
+ p_current.add_argument("--source", action="store_true",
405
+ help="Show active task source")
406
+
366
407
  # finish
367
- subparsers.add_parser("finish", help="Clear current task")
408
+ subparsers.add_parser("finish", help="Clear active task")
368
409
 
369
410
  # set-branch
370
411
  p_branch = subparsers.add_parser("set-branch", help="Set git branch")
@@ -417,6 +458,7 @@ def main() -> int:
417
458
  "validate": cmd_validate,
418
459
  "list-context": cmd_list_context,
419
460
  "start": cmd_start,
461
+ "current": cmd_current,
420
462
  "finish": cmd_finish,
421
463
  "set-branch": cmd_set_branch,
422
464
  "set-base-branch": cmd_set_base_branch,