@mindfoldhq/trellis 0.3.3 → 0.3.4

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 (81) hide show
  1. package/README.md +4 -4
  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.map +1 -1
  7. package/dist/commands/update.d.ts.map +1 -1
  8. package/dist/commands/update.js +2 -7
  9. package/dist/commands/update.js.map +1 -1
  10. package/dist/configurators/index.d.ts.map +1 -1
  11. package/dist/configurators/index.js +15 -3
  12. package/dist/configurators/index.js.map +1 -1
  13. package/dist/configurators/kilo.d.ts +1 -1
  14. package/dist/configurators/kilo.d.ts.map +1 -1
  15. package/dist/configurators/kilo.js +2 -1
  16. package/dist/configurators/kilo.js.map +1 -1
  17. package/dist/configurators/qoder.d.ts +8 -0
  18. package/dist/configurators/qoder.d.ts.map +1 -0
  19. package/dist/configurators/qoder.js +52 -0
  20. package/dist/configurators/qoder.js.map +1 -0
  21. package/dist/migrations/manifests/0.3.4.json +21 -0
  22. package/dist/templates/claude/commands/trellis/record-session.md +12 -16
  23. package/dist/templates/codex/skills/record-session/SKILL.md +13 -17
  24. package/dist/templates/cursor/commands/trellis-record-session.md +12 -16
  25. package/dist/templates/extract.d.ts +7 -0
  26. package/dist/templates/extract.d.ts.map +1 -1
  27. package/dist/templates/extract.js +13 -0
  28. package/dist/templates/extract.js.map +1 -1
  29. package/dist/templates/gemini/commands/trellis/record-session.toml +12 -16
  30. package/dist/templates/iflow/commands/trellis/record-session.md +12 -16
  31. package/dist/templates/iflow/hooks/session-start.py +1 -0
  32. package/dist/templates/kilo/index.d.ts +3 -3
  33. package/dist/templates/kilo/index.d.ts.map +1 -1
  34. package/dist/templates/kilo/index.js +7 -7
  35. package/dist/templates/kilo/index.js.map +1 -1
  36. package/dist/templates/kilo/{commands/trellis → workflows}/record-session.md +12 -16
  37. package/dist/templates/kiro/skills/record-session/SKILL.md +13 -17
  38. package/dist/templates/opencode/commands/trellis/record-session.md +12 -16
  39. package/dist/templates/qoder/index.d.ts +18 -0
  40. package/dist/templates/qoder/index.d.ts.map +1 -0
  41. package/dist/templates/qoder/index.js +40 -0
  42. package/dist/templates/qoder/index.js.map +1 -0
  43. package/dist/templates/qoder/skills/before-backend-dev/SKILL.md +18 -0
  44. package/dist/templates/qoder/skills/before-frontend-dev/SKILL.md +18 -0
  45. package/dist/templates/qoder/skills/brainstorm/SKILL.md +479 -0
  46. package/dist/templates/qoder/skills/break-loop/SKILL.md +130 -0
  47. package/dist/templates/qoder/skills/check-backend/SKILL.md +18 -0
  48. package/dist/templates/qoder/skills/check-cross-layer/SKILL.md +158 -0
  49. package/dist/templates/qoder/skills/check-frontend/SKILL.md +18 -0
  50. package/dist/templates/qoder/skills/create-command/SKILL.md +101 -0
  51. package/dist/templates/qoder/skills/finish-work/SKILL.md +134 -0
  52. package/dist/templates/qoder/skills/integrate-skill/SKILL.md +221 -0
  53. package/dist/templates/qoder/skills/onboard/SKILL.md +363 -0
  54. package/dist/templates/qoder/skills/record-session/SKILL.md +63 -0
  55. package/dist/templates/qoder/skills/start/SKILL.md +326 -0
  56. package/dist/templates/qoder/skills/update-spec/SKILL.md +290 -0
  57. package/dist/templates/trellis/scripts/add_session.py +3 -3
  58. package/dist/templates/trellis/scripts/common/cli_adapter.py +125 -20
  59. package/dist/templates/trellis/scripts/common/git_context.py +120 -0
  60. package/dist/templates/trellis/scripts/multi_agent/plan.py +4 -1
  61. package/dist/templates/trellis/scripts/multi_agent/start.py +5 -1
  62. package/dist/templates/trellis/scripts/task.py +26 -0
  63. package/dist/types/ai-tools.d.ts +3 -3
  64. package/dist/types/ai-tools.d.ts.map +1 -1
  65. package/dist/types/ai-tools.js +8 -0
  66. package/dist/types/ai-tools.js.map +1 -1
  67. package/package.json +1 -1
  68. /package/dist/templates/kilo/{commands/trellis → workflows}/before-backend-dev.md +0 -0
  69. /package/dist/templates/kilo/{commands/trellis → workflows}/before-frontend-dev.md +0 -0
  70. /package/dist/templates/kilo/{commands/trellis → workflows}/brainstorm.md +0 -0
  71. /package/dist/templates/kilo/{commands/trellis → workflows}/break-loop.md +0 -0
  72. /package/dist/templates/kilo/{commands/trellis → workflows}/check-backend.md +0 -0
  73. /package/dist/templates/kilo/{commands/trellis → workflows}/check-cross-layer.md +0 -0
  74. /package/dist/templates/kilo/{commands/trellis → workflows}/check-frontend.md +0 -0
  75. /package/dist/templates/kilo/{commands/trellis → workflows}/create-command.md +0 -0
  76. /package/dist/templates/kilo/{commands/trellis → workflows}/finish-work.md +0 -0
  77. /package/dist/templates/kilo/{commands/trellis → workflows}/integrate-skill.md +0 -0
  78. /package/dist/templates/kilo/{commands/trellis → workflows}/onboard.md +0 -0
  79. /package/dist/templates/kilo/{commands/trellis → workflows}/parallel.md +0 -0
  80. /package/dist/templates/kilo/{commands/trellis → workflows}/start.md +0 -0
  81. /package/dist/templates/kilo/{commands/trellis → workflows}/update-spec.md +0 -0
@@ -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, and Antigravity interfaces.
4
+ Abstracts differences between Claude Code, OpenCode, Cursor, iFlow, Codex, Kilo, Kiro Code, Gemini CLI, Antigravity, and Qoder interfaces.
5
5
 
6
6
  Supported platforms:
7
7
  - claude: Claude Code (default)
@@ -13,6 +13,7 @@ Supported platforms:
13
13
  - kiro: Kiro Code (skills-based)
14
14
  - gemini: Gemini CLI
15
15
  - antigravity: Antigravity (workflow-based)
16
+ - qoder: Qoder
16
17
 
17
18
  Usage:
18
19
  from common.cli_adapter import CLIAdapter
@@ -31,7 +32,18 @@ from dataclasses import dataclass
31
32
  from pathlib import Path
32
33
  from typing import ClassVar, Literal
33
34
 
34
- Platform = Literal["claude", "opencode", "cursor", "iflow", "codex", "kilo", "kiro", "gemini", "antigravity"]
35
+ Platform = Literal[
36
+ "claude",
37
+ "opencode",
38
+ "cursor",
39
+ "iflow",
40
+ "codex",
41
+ "kilo",
42
+ "kiro",
43
+ "gemini",
44
+ "antigravity",
45
+ "qoder",
46
+ ]
35
47
 
36
48
 
37
49
  @dataclass
@@ -75,7 +87,7 @@ class CLIAdapter:
75
87
  """Get platform-specific config directory name.
76
88
 
77
89
  Returns:
78
- Directory name ('.claude', '.opencode', '.cursor', '.iflow', '.agents', '.kilocode', '.kiro', '.gemini', or '.agent')
90
+ Directory name ('.claude', '.opencode', '.cursor', '.iflow', '.agents', '.kilocode', '.kiro', '.gemini', '.agent', or '.qoder')
79
91
  """
80
92
  if self.platform == "opencode":
81
93
  return ".opencode"
@@ -93,6 +105,8 @@ class CLIAdapter:
93
105
  return ".gemini"
94
106
  elif self.platform == "antigravity":
95
107
  return ".agent"
108
+ elif self.platform == "qoder":
109
+ return ".qoder"
96
110
  else:
97
111
  return ".claude"
98
112
 
@@ -103,7 +117,7 @@ class CLIAdapter:
103
117
  project_root: Project root directory
104
118
 
105
119
  Returns:
106
- Path to config directory (.claude, .opencode, .cursor, .iflow, .agents, .kilocode, .kiro, or .agent)
120
+ Path to config directory (.claude, .opencode, .cursor, .iflow, .agents, .kilocode, .kiro, .gemini, .agent, or .qoder)
107
121
  """
108
122
  return project_root / self.config_dir_name
109
123
 
@@ -135,7 +149,7 @@ class CLIAdapter:
135
149
  Antigravity uses workflow directory: .agent/workflows/<name>.md
136
150
  Claude/OpenCode use subdirectory: .claude/commands/trellis/<name>.md
137
151
  """
138
- if self.platform == "antigravity":
152
+ if self.platform in ("antigravity", "kilo"):
139
153
  workflow_dir = self.get_config_dir(project_root) / "workflows"
140
154
  if not parts:
141
155
  return workflow_dir
@@ -151,7 +165,9 @@ class CLIAdapter:
151
165
  if self.platform == "cursor" and len(parts) >= 2 and parts[0] == "trellis":
152
166
  # Convert trellis/<name>.md to trellis-<name>.md
153
167
  filename = parts[-1]
154
- return self.get_config_dir(project_root) / "commands" / f"trellis-{filename}"
168
+ return (
169
+ self.get_config_dir(project_root) / "commands" / f"trellis-{filename}"
170
+ )
155
171
 
156
172
  return self.get_config_dir(project_root) / "commands" / Path(*parts)
157
173
 
@@ -182,6 +198,8 @@ class CLIAdapter:
182
198
  return f".gemini/commands/trellis/{name}.toml"
183
199
  elif self.platform == "antigravity":
184
200
  return f".agent/workflows/{name}.md"
201
+ elif self.platform == "kilo":
202
+ return f".kilocode/workflows/{name}.md"
185
203
  else:
186
204
  return f"{self.config_dir_name}/commands/trellis/{name}.md"
187
205
 
@@ -197,6 +215,8 @@ class CLIAdapter:
197
215
  """
198
216
  if self.platform == "opencode":
199
217
  return {"OPENCODE_NON_INTERACTIVE": "1"}
218
+ elif self.platform == "iflow":
219
+ return {"IFLOW_NON_INTERACTIVE": "1"}
200
220
  elif self.platform == "codex":
201
221
  return {"CODEX_NON_INTERACTIVE": "1"}
202
222
  elif self.platform == "kiro":
@@ -205,6 +225,8 @@ class CLIAdapter:
205
225
  return {} # Gemini CLI doesn't have a non-interactive env var
206
226
  elif self.platform == "antigravity":
207
227
  return {}
228
+ elif self.platform == "qoder":
229
+ return {}
208
230
  else:
209
231
  return {"CLAUDE_NON_INTERACTIVE": "1"}
210
232
 
@@ -255,6 +277,13 @@ class CLIAdapter:
255
277
 
256
278
  cmd.append(prompt)
257
279
 
280
+ elif self.platform == "iflow":
281
+ cmd = ["iflow", "-p"]
282
+ cmd.extend(["-y", "--agent", mapped_agent])
283
+ # iFlow doesn't support --session-id on creation
284
+ if verbose:
285
+ cmd.append("--verbose")
286
+ cmd.append(prompt)
258
287
  elif self.platform == "codex":
259
288
  cmd = ["codex", "exec"]
260
289
  cmd.append(prompt)
@@ -267,6 +296,8 @@ class CLIAdapter:
267
296
  raise ValueError(
268
297
  "Antigravity workflows are UI slash commands; CLI agent run is not supported."
269
298
  )
299
+ elif self.platform == "qoder":
300
+ cmd = ["qodercli", "-p", prompt]
270
301
 
271
302
  else: # claude
272
303
  cmd = ["claude", "-p"]
@@ -292,13 +323,17 @@ class CLIAdapter:
292
323
  """Build CLI command for resuming a session.
293
324
 
294
325
  Args:
295
- session_id: Session ID to resume
326
+ session_id: Session ID to resume (ignored for iFlow)
296
327
 
297
328
  Returns:
298
329
  List of command arguments
299
330
  """
300
331
  if self.platform == "opencode":
301
332
  return ["opencode", "run", "--session", session_id]
333
+ elif self.platform == "iflow":
334
+ # iFlow uses -c to continue most recent conversation
335
+ # session_id is ignored as iFlow doesn't support session IDs
336
+ return ["iflow", "-c"]
302
337
  elif self.platform == "codex":
303
338
  return ["codex", "resume", session_id]
304
339
  elif self.platform == "kiro":
@@ -309,6 +344,8 @@ class CLIAdapter:
309
344
  raise ValueError(
310
345
  "Antigravity workflows are UI slash commands; CLI resume is not supported."
311
346
  )
347
+ elif self.platform == "qoder":
348
+ return ["qodercli", "--resume", session_id]
312
349
  else:
313
350
  return ["claude", "--resume", session_id]
314
351
 
@@ -348,6 +385,11 @@ class CLIAdapter:
348
385
  """Check if platform is Cursor."""
349
386
  return self.platform == "cursor"
350
387
 
388
+ @property
389
+ def is_iflow(self) -> bool:
390
+ """Check if platform is iFlow CLI."""
391
+ return self.platform == "iflow"
392
+
351
393
  @property
352
394
  def cli_name(self) -> str:
353
395
  """Get CLI executable name.
@@ -358,12 +400,16 @@ class CLIAdapter:
358
400
  return "opencode"
359
401
  elif self.is_cursor:
360
402
  return "cursor" # Note: Cursor is IDE-only, no CLI
403
+ elif self.platform == "iflow":
404
+ return "iflow"
361
405
  elif self.platform == "kiro":
362
406
  return "kiro"
363
407
  elif self.platform == "gemini":
364
408
  return "gemini"
365
409
  elif self.platform == "antigravity":
366
410
  return "agy"
411
+ elif self.platform == "qoder":
412
+ return "qodercli"
367
413
  else:
368
414
  return "claude"
369
415
 
@@ -371,10 +417,10 @@ class CLIAdapter:
371
417
  def supports_cli_agents(self) -> bool:
372
418
  """Check if platform supports running agents via CLI.
373
419
 
374
- Claude Code and OpenCode support CLI agent execution.
420
+ Claude Code, OpenCode, and iFlow support CLI agent execution.
375
421
  Cursor is IDE-only and doesn't support CLI agents.
376
422
  """
377
- return self.platform in ("claude", "opencode")
423
+ return self.platform in ("claude", "opencode", "iflow")
378
424
 
379
425
  # =========================================================================
380
426
  # Session ID Handling
@@ -386,6 +432,7 @@ class CLIAdapter:
386
432
 
387
433
  Claude Code: Yes (--session-id)
388
434
  OpenCode: No (auto-generated, extract from logs)
435
+ iFlow: No (no session ID support)
389
436
  """
390
437
  return self.platform == "claude"
391
438
 
@@ -418,7 +465,7 @@ def get_cli_adapter(platform: str = "claude") -> CLIAdapter:
418
465
  """Get CLI adapter for the specified platform.
419
466
 
420
467
  Args:
421
- platform: Platform name ('claude', 'opencode', 'cursor', 'iflow', 'codex', 'kilo', 'kiro', or 'antigravity')
468
+ platform: Platform name ('claude', 'opencode', 'cursor', 'iflow', 'codex', 'kilo', 'kiro', 'gemini', 'antigravity', or 'qoder')
422
469
 
423
470
  Returns:
424
471
  CLIAdapter instance
@@ -426,8 +473,21 @@ def get_cli_adapter(platform: str = "claude") -> CLIAdapter:
426
473
  Raises:
427
474
  ValueError: If platform is not supported
428
475
  """
429
- if platform not in ("claude", "opencode", "cursor", "iflow", "codex", "kilo", "kiro", "gemini", "antigravity"):
430
- raise ValueError(f"Unsupported platform: {platform} (must be 'claude', 'opencode', 'cursor', 'iflow', 'codex', 'kilo', 'kiro', 'gemini', or 'antigravity')")
476
+ if platform not in (
477
+ "claude",
478
+ "opencode",
479
+ "cursor",
480
+ "iflow",
481
+ "codex",
482
+ "kilo",
483
+ "kiro",
484
+ "gemini",
485
+ "antigravity",
486
+ "qoder",
487
+ ):
488
+ raise ValueError(
489
+ f"Unsupported platform: {platform} (must be 'claude', 'opencode', 'cursor', 'iflow', 'codex', 'kilo', 'kiro', 'gemini', 'antigravity', or 'qoder')"
490
+ )
431
491
 
432
492
  return CLIAdapter(platform=platform) # type: ignore
433
493
 
@@ -445,19 +505,31 @@ def detect_platform(project_root: Path) -> Platform:
445
505
  7. .kiro/skills exists and no other platform dirs → kiro
446
506
  8. .gemini directory exists → gemini
447
507
  9. .agent/workflows exists and no other platform dirs → antigravity
448
- 10. Defaultclaude
508
+ 10. .qoder directory exists qoder
509
+ 11. Default → claude
449
510
 
450
511
  Args:
451
512
  project_root: Project root directory
452
513
 
453
514
  Returns:
454
- Detected platform ('claude', 'opencode', 'cursor', 'iflow', 'codex', 'kilo', 'kiro', 'gemini', or 'antigravity')
515
+ Detected platform ('claude', 'opencode', 'cursor', 'iflow', 'codex', 'kilo', 'kiro', 'gemini', 'antigravity', or 'qoder')
455
516
  """
456
517
  import os
457
518
 
458
519
  # Check environment variable first
459
520
  env_platform = os.environ.get("TRELLIS_PLATFORM", "").lower()
460
- if env_platform in ("claude", "opencode", "cursor", "iflow", "codex", "kilo", "kiro", "gemini", "antigravity"):
521
+ if env_platform in (
522
+ "claude",
523
+ "opencode",
524
+ "cursor",
525
+ "iflow",
526
+ "codex",
527
+ "kilo",
528
+ "kiro",
529
+ "gemini",
530
+ "antigravity",
531
+ "qoder",
532
+ ):
461
533
  return env_platform # type: ignore
462
534
 
463
535
  # Check for .opencode directory (OpenCode-specific)
@@ -480,7 +552,16 @@ def detect_platform(project_root: Path) -> Platform:
480
552
  return "gemini"
481
553
 
482
554
  # Check for Codex skills directory only when no other platform config exists
483
- other_platform_dirs_codex = (".claude", ".cursor", ".iflow", ".opencode", ".kilocode", ".kiro", ".gemini", ".agent")
555
+ other_platform_dirs_codex = (
556
+ ".claude",
557
+ ".cursor",
558
+ ".iflow",
559
+ ".opencode",
560
+ ".kilocode",
561
+ ".kiro",
562
+ ".gemini",
563
+ ".agent",
564
+ )
484
565
  has_other_platform_config = any(
485
566
  (project_root / directory).is_dir() for directory in other_platform_dirs_codex
486
567
  )
@@ -492,7 +573,16 @@ def detect_platform(project_root: Path) -> Platform:
492
573
  return "kilo"
493
574
 
494
575
  # Check for Kiro skills directory only when no other platform config exists
495
- other_platform_dirs_kiro = (".claude", ".cursor", ".iflow", ".opencode", ".agents", ".kilocode", ".gemini", ".agent")
576
+ other_platform_dirs_kiro = (
577
+ ".claude",
578
+ ".cursor",
579
+ ".iflow",
580
+ ".opencode",
581
+ ".agents",
582
+ ".kilocode",
583
+ ".gemini",
584
+ ".agent",
585
+ )
496
586
  has_other_platform_config = any(
497
587
  (project_root / directory).is_dir() for directory in other_platform_dirs_kiro
498
588
  )
@@ -500,13 +590,28 @@ def detect_platform(project_root: Path) -> Platform:
500
590
  return "kiro"
501
591
 
502
592
  # Check for Antigravity workflow directory only when no other platform config exists
503
- other_platform_dirs_antigravity = (".claude", ".cursor", ".iflow", ".opencode", ".agents", ".kilocode", ".kiro")
593
+ other_platform_dirs_antigravity = (
594
+ ".claude",
595
+ ".cursor",
596
+ ".iflow",
597
+ ".opencode",
598
+ ".agents",
599
+ ".kilocode",
600
+ ".kiro",
601
+ )
504
602
  has_other_platform_config = any(
505
- (project_root / directory).is_dir() for directory in other_platform_dirs_antigravity
603
+ (project_root / directory).is_dir()
604
+ for directory in other_platform_dirs_antigravity
506
605
  )
507
- if (project_root / ".agent" / "workflows").is_dir() and not has_other_platform_config:
606
+ if (
607
+ project_root / ".agent" / "workflows"
608
+ ).is_dir() and not has_other_platform_config:
508
609
  return "antigravity"
509
610
 
611
+ # Check for .qoder directory (Qoder-specific)
612
+ if (project_root / ".qoder").is_dir():
613
+ return "qoder"
614
+
510
615
  return "claude"
511
616
 
512
617
 
@@ -335,6 +335,117 @@ def get_context_text(repo_root: Path | None = None) -> str:
335
335
  return "\n".join(lines)
336
336
 
337
337
 
338
+ def get_context_text_record(repo_root: Path | None = None) -> str:
339
+ """Get context as formatted text for record-session mode.
340
+
341
+ Focused output: MY ACTIVE TASKS first (with [!!!] emphasis),
342
+ then GIT STATUS, RECENT COMMITS, CURRENT TASK.
343
+
344
+ Args:
345
+ repo_root: Repository root path. Defaults to auto-detected.
346
+
347
+ Returns:
348
+ Formatted text output for record-session.
349
+ """
350
+ if repo_root is None:
351
+ repo_root = get_repo_root()
352
+
353
+ lines: list[str] = []
354
+ lines.append("========================================")
355
+ lines.append("SESSION CONTEXT (RECORD MODE)")
356
+ lines.append("========================================")
357
+ lines.append("")
358
+
359
+ developer = get_developer(repo_root)
360
+ if not developer:
361
+ lines.append(
362
+ f"ERROR: Not initialized. Run: python3 ./{DIR_WORKFLOW}/{DIR_SCRIPTS}/init_developer.py <name>"
363
+ )
364
+ return "\n".join(lines)
365
+
366
+ # MY ACTIVE TASKS — first and prominent
367
+ lines.append(f"## [!!!] MY ACTIVE TASKS (Assigned to {developer})")
368
+ lines.append("[!] Review whether any should be archived before recording this session.")
369
+ lines.append("")
370
+
371
+ tasks_dir = get_tasks_dir(repo_root)
372
+ my_task_count = 0
373
+
374
+ if tasks_dir.is_dir():
375
+ for d in sorted(tasks_dir.iterdir()):
376
+ if d.is_dir() and d.name != "archive":
377
+ t_json = d / FILE_TASK_JSON
378
+ if t_json.is_file():
379
+ data = _read_json_file(t_json)
380
+ if data:
381
+ assignee = data.get("assignee", "")
382
+ status = data.get("status", "planning")
383
+
384
+ if assignee == developer:
385
+ title = data.get("title") or data.get("name") or "unknown"
386
+ priority = data.get("priority", "P2")
387
+ lines.append(f"- [{priority}] {title} ({status}) — {d.name}")
388
+ my_task_count += 1
389
+
390
+ if my_task_count == 0:
391
+ lines.append("(no active tasks assigned to you)")
392
+ lines.append("")
393
+
394
+ # GIT STATUS
395
+ lines.append("## GIT STATUS")
396
+ _, branch_out, _ = _run_git_command(["branch", "--show-current"], cwd=repo_root)
397
+ branch = branch_out.strip() or "unknown"
398
+ lines.append(f"Branch: {branch}")
399
+
400
+ _, status_out, _ = _run_git_command(["status", "--porcelain"], cwd=repo_root)
401
+ status_lines = [line for line in status_out.splitlines() if line.strip()]
402
+ status_count = len(status_lines)
403
+
404
+ if status_count == 0:
405
+ lines.append("Working directory: Clean")
406
+ else:
407
+ lines.append(f"Working directory: {status_count} uncommitted change(s)")
408
+ lines.append("")
409
+ lines.append("Changes:")
410
+ _, short_out, _ = _run_git_command(["status", "--short"], cwd=repo_root)
411
+ for line in short_out.splitlines()[:10]:
412
+ lines.append(line)
413
+ lines.append("")
414
+
415
+ # RECENT COMMITS
416
+ lines.append("## RECENT COMMITS")
417
+ _, log_out, _ = _run_git_command(["log", "--oneline", "-5"], cwd=repo_root)
418
+ if log_out.strip():
419
+ for line in log_out.splitlines():
420
+ lines.append(line)
421
+ else:
422
+ lines.append("(no commits)")
423
+ lines.append("")
424
+
425
+ # CURRENT TASK
426
+ lines.append("## CURRENT TASK")
427
+ current_task = get_current_task(repo_root)
428
+ if current_task:
429
+ current_task_dir = repo_root / current_task
430
+ task_json_path = current_task_dir / FILE_TASK_JSON
431
+ lines.append(f"Path: {current_task}")
432
+
433
+ if task_json_path.is_file():
434
+ data = _read_json_file(task_json_path)
435
+ if data:
436
+ t_name = data.get("name") or data.get("id") or "unknown"
437
+ t_status = data.get("status", "unknown")
438
+ lines.append(f"Name: {t_name}")
439
+ lines.append(f"Status: {t_status}")
440
+ else:
441
+ lines.append("(none)")
442
+ lines.append("")
443
+
444
+ lines.append("========================================")
445
+
446
+ return "\n".join(lines)
447
+
448
+
338
449
  def output_text(repo_root: Path | None = None) -> None:
339
450
  """Output context in text format.
340
451
 
@@ -360,11 +471,20 @@ def main() -> None:
360
471
  action="store_true",
361
472
  help="Output context in JSON format",
362
473
  )
474
+ parser.add_argument(
475
+ "--mode",
476
+ "-m",
477
+ choices=["default", "record"],
478
+ default="default",
479
+ help="Output mode: default (full context) or record (for record-session)",
480
+ )
363
481
 
364
482
  args = parser.parse_args()
365
483
 
366
484
  if args.json:
367
485
  output_json()
486
+ elif args.mode == "record":
487
+ print(get_context_text_record())
368
488
  else:
369
489
  output_text()
370
490
 
@@ -77,7 +77,7 @@ def main() -> int:
77
77
  parser.add_argument("--requirement", "-r", required=True, help="Requirement description")
78
78
  parser.add_argument(
79
79
  "--platform", "-p",
80
- choices=["claude", "cursor", "iflow", "opencode"],
80
+ choices=["claude", "cursor", "iflow", "opencode", "qoder"],
81
81
  default=DEFAULT_PLATFORM,
82
82
  help="Platform to use (default: claude)"
83
83
  )
@@ -173,6 +173,9 @@ def main() -> int:
173
173
  env["http_proxy"] = http_proxy
174
174
  env["all_proxy"] = all_proxy
175
175
 
176
+ # Clear nested-session detection so the new CLI process can start
177
+ env.pop("CLAUDECODE", None)
178
+
176
179
  # Set non-interactive env var based on platform
177
180
  env.update(adapter.get_non_interactive_env())
178
181
 
@@ -124,7 +124,7 @@ def main() -> int:
124
124
  parser.add_argument("task_dir", help="Task directory path")
125
125
  parser.add_argument(
126
126
  "--platform", "-p",
127
- choices=["claude", "cursor", "iflow", "opencode"],
127
+ choices=["claude", "cursor", "iflow", "opencode", "qoder"],
128
128
  default=DEFAULT_PLATFORM,
129
129
  help="Platform to use (default: claude)"
130
130
  )
@@ -362,6 +362,10 @@ def main() -> int:
362
362
  env["http_proxy"] = http_proxy
363
363
  env["all_proxy"] = all_proxy
364
364
 
365
+ # Clear nested-session detection so the new CLI process can start
366
+ # (when this script runs inside a Claude Code session, CLAUDECODE=1 is inherited)
367
+ env.pop("CLAUDECODE", None)
368
+
365
369
  # Set non-interactive env var based on platform
366
370
  env.update(adapter.get_non_interactive_env())
367
371
 
@@ -659,6 +659,10 @@ def cmd_archive(args: argparse.Namespace) -> int:
659
659
  year_month = archive_dest.parent.name
660
660
  print(colored(f"Archived: {dir_name} -> archive/{year_month}/", Colors.GREEN), file=sys.stderr)
661
661
 
662
+ # Auto-commit unless --no-commit
663
+ if not getattr(args, "no_commit", False):
664
+ _auto_commit_archive(dir_name, repo_root)
665
+
662
666
  # Return the archive path
663
667
  print(f"{DIR_WORKFLOW}/{DIR_TASKS}/{DIR_ARCHIVE}/{year_month}/{dir_name}")
664
668
  return 0
@@ -666,6 +670,27 @@ def cmd_archive(args: argparse.Namespace) -> int:
666
670
  return 1
667
671
 
668
672
 
673
+ def _auto_commit_archive(task_name: str, repo_root: Path) -> None:
674
+ """Stage .trellis/tasks/ changes and commit after archive."""
675
+ tasks_rel = f"{DIR_WORKFLOW}/{DIR_TASKS}"
676
+ _run_git_command(["add", "-A", tasks_rel], cwd=repo_root)
677
+
678
+ # Check if there are staged changes
679
+ rc, _, _ = _run_git_command(
680
+ ["diff", "--cached", "--quiet", "--", tasks_rel], cwd=repo_root
681
+ )
682
+ if rc == 0:
683
+ print("[OK] No task changes to commit.", file=sys.stderr)
684
+ return
685
+
686
+ commit_msg = f"chore(task): archive {task_name}"
687
+ rc, _, err = _run_git_command(["commit", "-m", commit_msg], cwd=repo_root)
688
+ if rc == 0:
689
+ print(f"[OK] Auto-committed: {commit_msg}", file=sys.stderr)
690
+ else:
691
+ print(f"[WARN] Auto-commit failed: {err.strip()}", file=sys.stderr)
692
+
693
+
669
694
  # =============================================================================
670
695
  # Command: list
671
696
  # =============================================================================
@@ -1005,6 +1030,7 @@ def main() -> int:
1005
1030
  # archive
1006
1031
  p_archive = subparsers.add_parser("archive", help="Archive task")
1007
1032
  p_archive.add_argument("name", help="Task name")
1033
+ p_archive.add_argument("--no-commit", action="store_true", help="Skip auto git commit after archive")
1008
1034
 
1009
1035
  # list
1010
1036
  p_list = subparsers.add_parser("list", help="List tasks")
@@ -6,16 +6,16 @@
6
6
  /**
7
7
  * Supported AI coding tools
8
8
  */
9
- export type AITool = "claude-code" | "cursor" | "opencode" | "iflow" | "codex" | "kilo" | "kiro" | "gemini" | "antigravity";
9
+ export type AITool = "claude-code" | "cursor" | "opencode" | "iflow" | "codex" | "kilo" | "kiro" | "gemini" | "antigravity" | "qoder";
10
10
  /**
11
11
  * Template directory categories
12
12
  */
13
- export type TemplateDir = "common" | "claude" | "cursor" | "opencode" | "iflow" | "codex" | "kilo" | "kiro" | "gemini" | "antigravity";
13
+ export type TemplateDir = "common" | "claude" | "cursor" | "opencode" | "iflow" | "codex" | "kilo" | "kiro" | "gemini" | "antigravity" | "qoder";
14
14
  /**
15
15
  * CLI flag names for platform selection (e.g., --claude, --cursor, --kilo, --kiro, --gemini, --antigravity)
16
16
  * Must match keys in InitOptions (src/commands/init.ts)
17
17
  */
18
- export type CliFlag = "claude" | "cursor" | "opencode" | "iflow" | "codex" | "kilo" | "kiro" | "gemini" | "antigravity";
18
+ export type CliFlag = "claude" | "cursor" | "opencode" | "iflow" | "codex" | "kilo" | "kiro" | "gemini" | "antigravity" | "qoder";
19
19
  /**
20
20
  * Configuration for an AI tool
21
21
  */
@@ -1 +1 @@
1
- {"version":3,"file":"ai-tools.d.ts","sourceRoot":"","sources":["../../src/types/ai-tools.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;GAEG;AACH,MAAM,MAAM,MAAM,GACd,aAAa,GACb,QAAQ,GACR,UAAU,GACV,OAAO,GACP,OAAO,GACP,MAAM,GACN,MAAM,GACN,QAAQ,GACR,aAAa,CAAC;AAElB;;GAEG;AACH,MAAM,MAAM,WAAW,GACnB,QAAQ,GACR,QAAQ,GACR,QAAQ,GACR,UAAU,GACV,OAAO,GACP,OAAO,GACP,MAAM,GACN,MAAM,GACN,QAAQ,GACR,aAAa,CAAC;AAElB;;;GAGG;AACH,MAAM,MAAM,OAAO,GACf,QAAQ,GACR,QAAQ,GACR,UAAU,GACV,OAAO,GACP,OAAO,GACP,MAAM,GACN,MAAM,GACN,QAAQ,GACR,aAAa,CAAC;AAElB;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,OAAO,EAAE,OAAO,CAAC;IACjB,yEAAyE;IACzE,cAAc,EAAE,OAAO,CAAC;IACxB,+EAA+E;IAC/E,cAAc,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAyEjD,CAAC;AAEF;;GAEG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAExD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CAE3D"}
1
+ {"version":3,"file":"ai-tools.d.ts","sourceRoot":"","sources":["../../src/types/ai-tools.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;GAEG;AACH,MAAM,MAAM,MAAM,GACd,aAAa,GACb,QAAQ,GACR,UAAU,GACV,OAAO,GACP,OAAO,GACP,MAAM,GACN,MAAM,GACN,QAAQ,GACR,aAAa,GACb,OAAO,CAAC;AAEZ;;GAEG;AACH,MAAM,MAAM,WAAW,GACnB,QAAQ,GACR,QAAQ,GACR,QAAQ,GACR,UAAU,GACV,OAAO,GACP,OAAO,GACP,MAAM,GACN,MAAM,GACN,QAAQ,GACR,aAAa,GACb,OAAO,CAAC;AAEZ;;;GAGG;AACH,MAAM,MAAM,OAAO,GACf,QAAQ,GACR,QAAQ,GACR,UAAU,GACV,OAAO,GACP,OAAO,GACP,MAAM,GACN,MAAM,GACN,QAAQ,GACR,aAAa,GACb,OAAO,CAAC;AAEZ;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,kEAAkE;IAClE,SAAS,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,OAAO,EAAE,OAAO,CAAC;IACjB,yEAAyE;IACzE,cAAc,EAAE,OAAO,CAAC;IACxB,+EAA+E;IAC/E,cAAc,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAiFjD,CAAC;AAEF;;GAEG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAExD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,EAAE,CAE3D"}
@@ -87,6 +87,14 @@ export const AI_TOOLS = {
87
87
  defaultChecked: false,
88
88
  hasPythonHooks: false,
89
89
  },
90
+ qoder: {
91
+ name: "Qoder",
92
+ templateDirs: ["common", "qoder"],
93
+ configDir: ".qoder",
94
+ cliFlag: "qoder",
95
+ defaultChecked: false,
96
+ hasPythonHooks: false,
97
+ },
90
98
  };
91
99
  /**
92
100
  * Get the configuration for a specific AI tool
@@ -1 +1 @@
1
- {"version":3,"file":"ai-tools.js","sourceRoot":"","sources":["../../src/types/ai-tools.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAgEH;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAiC;IACpD,aAAa,EAAE;QACb,IAAI,EAAE,aAAa;QACnB,YAAY,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC;QAClC,SAAS,EAAE,SAAS;QACpB,OAAO,EAAE,QAAQ;QACjB,cAAc,EAAE,IAAI;QACpB,cAAc,EAAE,IAAI;KACrB;IACD,MAAM,EAAE;QACN,IAAI,EAAE,QAAQ;QACd,YAAY,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC;QAClC,SAAS,EAAE,SAAS;QACpB,OAAO,EAAE,QAAQ;QACjB,cAAc,EAAE,IAAI;QACpB,cAAc,EAAE,KAAK;KACtB;IACD,QAAQ,EAAE;QACR,IAAI,EAAE,UAAU;QAChB,YAAY,EAAE,CAAC,QAAQ,EAAE,UAAU,CAAC;QACpC,SAAS,EAAE,WAAW;QACtB,OAAO,EAAE,UAAU;QACnB,cAAc,EAAE,KAAK;QACrB,cAAc,EAAE,KAAK;KACtB;IACD,KAAK,EAAE;QACL,IAAI,EAAE,WAAW;QACjB,YAAY,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC;QACjC,SAAS,EAAE,QAAQ;QACnB,OAAO,EAAE,OAAO;QAChB,cAAc,EAAE,KAAK;QACrB,cAAc,EAAE,IAAI;KACrB;IACD,KAAK,EAAE;QACL,IAAI,EAAE,OAAO;QACb,YAAY,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC;QACjC,SAAS,EAAE,gBAAgB;QAC3B,OAAO,EAAE,OAAO;QAChB,cAAc,EAAE,KAAK;QACrB,cAAc,EAAE,KAAK;KACtB;IACD,IAAI,EAAE;QACJ,IAAI,EAAE,UAAU;QAChB,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC;QAChC,SAAS,EAAE,WAAW;QACtB,OAAO,EAAE,MAAM;QACf,cAAc,EAAE,KAAK;QACrB,cAAc,EAAE,KAAK;KACtB;IACD,IAAI,EAAE;QACJ,IAAI,EAAE,WAAW;QACjB,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC;QAChC,SAAS,EAAE,cAAc;QACzB,OAAO,EAAE,MAAM;QACf,cAAc,EAAE,KAAK;QACrB,cAAc,EAAE,KAAK;KACtB;IACD,MAAM,EAAE;QACN,IAAI,EAAE,YAAY;QAClB,YAAY,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC;QAClC,SAAS,EAAE,SAAS;QACpB,OAAO,EAAE,QAAQ;QACjB,cAAc,EAAE,KAAK;QACrB,cAAc,EAAE,KAAK;KACtB;IACD,WAAW,EAAE;QACX,IAAI,EAAE,aAAa;QACnB,YAAY,EAAE,CAAC,QAAQ,EAAE,aAAa,CAAC;QACvC,SAAS,EAAE,kBAAkB;QAC7B,OAAO,EAAE,aAAa;QACtB,cAAc,EAAE,KAAK;QACrB,cAAc,EAAE,KAAK;KACtB;CACF,CAAC;AAEF;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC;AACxB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,YAAY,CAAC;AACrC,CAAC"}
1
+ {"version":3,"file":"ai-tools.js","sourceRoot":"","sources":["../../src/types/ai-tools.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAmEH;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAiC;IACpD,aAAa,EAAE;QACb,IAAI,EAAE,aAAa;QACnB,YAAY,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC;QAClC,SAAS,EAAE,SAAS;QACpB,OAAO,EAAE,QAAQ;QACjB,cAAc,EAAE,IAAI;QACpB,cAAc,EAAE,IAAI;KACrB;IACD,MAAM,EAAE;QACN,IAAI,EAAE,QAAQ;QACd,YAAY,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC;QAClC,SAAS,EAAE,SAAS;QACpB,OAAO,EAAE,QAAQ;QACjB,cAAc,EAAE,IAAI;QACpB,cAAc,EAAE,KAAK;KACtB;IACD,QAAQ,EAAE;QACR,IAAI,EAAE,UAAU;QAChB,YAAY,EAAE,CAAC,QAAQ,EAAE,UAAU,CAAC;QACpC,SAAS,EAAE,WAAW;QACtB,OAAO,EAAE,UAAU;QACnB,cAAc,EAAE,KAAK;QACrB,cAAc,EAAE,KAAK;KACtB;IACD,KAAK,EAAE;QACL,IAAI,EAAE,WAAW;QACjB,YAAY,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC;QACjC,SAAS,EAAE,QAAQ;QACnB,OAAO,EAAE,OAAO;QAChB,cAAc,EAAE,KAAK;QACrB,cAAc,EAAE,IAAI;KACrB;IACD,KAAK,EAAE;QACL,IAAI,EAAE,OAAO;QACb,YAAY,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC;QACjC,SAAS,EAAE,gBAAgB;QAC3B,OAAO,EAAE,OAAO;QAChB,cAAc,EAAE,KAAK;QACrB,cAAc,EAAE,KAAK;KACtB;IACD,IAAI,EAAE;QACJ,IAAI,EAAE,UAAU;QAChB,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC;QAChC,SAAS,EAAE,WAAW;QACtB,OAAO,EAAE,MAAM;QACf,cAAc,EAAE,KAAK;QACrB,cAAc,EAAE,KAAK;KACtB;IACD,IAAI,EAAE;QACJ,IAAI,EAAE,WAAW;QACjB,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC;QAChC,SAAS,EAAE,cAAc;QACzB,OAAO,EAAE,MAAM;QACf,cAAc,EAAE,KAAK;QACrB,cAAc,EAAE,KAAK;KACtB;IACD,MAAM,EAAE;QACN,IAAI,EAAE,YAAY;QAClB,YAAY,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC;QAClC,SAAS,EAAE,SAAS;QACpB,OAAO,EAAE,QAAQ;QACjB,cAAc,EAAE,KAAK;QACrB,cAAc,EAAE,KAAK;KACtB;IACD,WAAW,EAAE;QACX,IAAI,EAAE,aAAa;QACnB,YAAY,EAAE,CAAC,QAAQ,EAAE,aAAa,CAAC;QACvC,SAAS,EAAE,kBAAkB;QAC7B,OAAO,EAAE,aAAa;QACtB,cAAc,EAAE,KAAK;QACrB,cAAc,EAAE,KAAK;KACtB;IACD,KAAK,EAAE;QACL,IAAI,EAAE,OAAO;QACb,YAAY,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC;QACjC,SAAS,EAAE,QAAQ;QACnB,OAAO,EAAE,OAAO;QAChB,cAAc,EAAE,KAAK;QACrB,cAAc,EAAE,KAAK;KACtB;CACF,CAAC;AAEF;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC;AACxB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,YAAY,CAAC;AACrC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindfoldhq/trellis",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
4
4
  "description": "AI capabilities grow like ivy — Trellis provides the structure to guide them along a disciplined path",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",