@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.
- package/README.md +4 -4
- package/dist/cli/index.js +1 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +2 -7
- package/dist/commands/update.js.map +1 -1
- package/dist/configurators/index.d.ts.map +1 -1
- package/dist/configurators/index.js +15 -3
- package/dist/configurators/index.js.map +1 -1
- package/dist/configurators/kilo.d.ts +1 -1
- package/dist/configurators/kilo.d.ts.map +1 -1
- package/dist/configurators/kilo.js +2 -1
- package/dist/configurators/kilo.js.map +1 -1
- package/dist/configurators/qoder.d.ts +8 -0
- package/dist/configurators/qoder.d.ts.map +1 -0
- package/dist/configurators/qoder.js +52 -0
- package/dist/configurators/qoder.js.map +1 -0
- package/dist/migrations/manifests/0.3.4.json +21 -0
- package/dist/templates/claude/commands/trellis/record-session.md +12 -16
- package/dist/templates/codex/skills/record-session/SKILL.md +13 -17
- package/dist/templates/cursor/commands/trellis-record-session.md +12 -16
- package/dist/templates/extract.d.ts +7 -0
- package/dist/templates/extract.d.ts.map +1 -1
- package/dist/templates/extract.js +13 -0
- package/dist/templates/extract.js.map +1 -1
- package/dist/templates/gemini/commands/trellis/record-session.toml +12 -16
- package/dist/templates/iflow/commands/trellis/record-session.md +12 -16
- package/dist/templates/iflow/hooks/session-start.py +1 -0
- package/dist/templates/kilo/index.d.ts +3 -3
- package/dist/templates/kilo/index.d.ts.map +1 -1
- package/dist/templates/kilo/index.js +7 -7
- package/dist/templates/kilo/index.js.map +1 -1
- package/dist/templates/kilo/{commands/trellis → workflows}/record-session.md +12 -16
- package/dist/templates/kiro/skills/record-session/SKILL.md +13 -17
- package/dist/templates/opencode/commands/trellis/record-session.md +12 -16
- package/dist/templates/qoder/index.d.ts +18 -0
- package/dist/templates/qoder/index.d.ts.map +1 -0
- package/dist/templates/qoder/index.js +40 -0
- package/dist/templates/qoder/index.js.map +1 -0
- package/dist/templates/qoder/skills/before-backend-dev/SKILL.md +18 -0
- package/dist/templates/qoder/skills/before-frontend-dev/SKILL.md +18 -0
- package/dist/templates/qoder/skills/brainstorm/SKILL.md +479 -0
- package/dist/templates/qoder/skills/break-loop/SKILL.md +130 -0
- package/dist/templates/qoder/skills/check-backend/SKILL.md +18 -0
- package/dist/templates/qoder/skills/check-cross-layer/SKILL.md +158 -0
- package/dist/templates/qoder/skills/check-frontend/SKILL.md +18 -0
- package/dist/templates/qoder/skills/create-command/SKILL.md +101 -0
- package/dist/templates/qoder/skills/finish-work/SKILL.md +134 -0
- package/dist/templates/qoder/skills/integrate-skill/SKILL.md +221 -0
- package/dist/templates/qoder/skills/onboard/SKILL.md +363 -0
- package/dist/templates/qoder/skills/record-session/SKILL.md +63 -0
- package/dist/templates/qoder/skills/start/SKILL.md +326 -0
- package/dist/templates/qoder/skills/update-spec/SKILL.md +290 -0
- package/dist/templates/trellis/scripts/add_session.py +3 -3
- package/dist/templates/trellis/scripts/common/cli_adapter.py +125 -20
- package/dist/templates/trellis/scripts/common/git_context.py +120 -0
- package/dist/templates/trellis/scripts/multi_agent/plan.py +4 -1
- package/dist/templates/trellis/scripts/multi_agent/start.py +5 -1
- package/dist/templates/trellis/scripts/task.py +26 -0
- package/dist/types/ai-tools.d.ts +3 -3
- package/dist/types/ai-tools.d.ts.map +1 -1
- package/dist/types/ai-tools.js +8 -0
- package/dist/types/ai-tools.js.map +1 -1
- package/package.json +1 -1
- /package/dist/templates/kilo/{commands/trellis → workflows}/before-backend-dev.md +0 -0
- /package/dist/templates/kilo/{commands/trellis → workflows}/before-frontend-dev.md +0 -0
- /package/dist/templates/kilo/{commands/trellis → workflows}/brainstorm.md +0 -0
- /package/dist/templates/kilo/{commands/trellis → workflows}/break-loop.md +0 -0
- /package/dist/templates/kilo/{commands/trellis → workflows}/check-backend.md +0 -0
- /package/dist/templates/kilo/{commands/trellis → workflows}/check-cross-layer.md +0 -0
- /package/dist/templates/kilo/{commands/trellis → workflows}/check-frontend.md +0 -0
- /package/dist/templates/kilo/{commands/trellis → workflows}/create-command.md +0 -0
- /package/dist/templates/kilo/{commands/trellis → workflows}/finish-work.md +0 -0
- /package/dist/templates/kilo/{commands/trellis → workflows}/integrate-skill.md +0 -0
- /package/dist/templates/kilo/{commands/trellis → workflows}/onboard.md +0 -0
- /package/dist/templates/kilo/{commands/trellis → workflows}/parallel.md +0 -0
- /package/dist/templates/kilo/{commands/trellis → workflows}/start.md +0 -0
- /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
|
|
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[
|
|
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 '.
|
|
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 .
|
|
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
|
|
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
|
|
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
|
|
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',
|
|
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 (
|
|
430
|
-
|
|
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.
|
|
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 '
|
|
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 (
|
|
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 = (
|
|
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 = (
|
|
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 = (
|
|
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()
|
|
603
|
+
(project_root / directory).is_dir()
|
|
604
|
+
for directory in other_platform_dirs_antigravity
|
|
506
605
|
)
|
|
507
|
-
if (
|
|
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")
|
package/dist/types/ai-tools.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/types/ai-tools.js
CHANGED
|
@@ -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;
|
|
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
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|