@mindfoldhq/trellis 0.1.3 → 0.1.5
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 +757 -135
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +38 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +6 -8
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/update.d.ts +11 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +357 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/configurators/claude.d.ts +2 -5
- package/dist/configurators/claude.d.ts.map +1 -1
- package/dist/configurators/claude.js +49 -12
- package/dist/configurators/claude.js.map +1 -1
- package/dist/configurators/cursor.d.ts +1 -4
- package/dist/configurators/cursor.d.ts.map +1 -1
- package/dist/configurators/cursor.js +42 -11
- package/dist/configurators/cursor.js.map +1 -1
- package/dist/configurators/templates.d.ts +15 -0
- package/dist/configurators/templates.d.ts.map +1 -0
- package/dist/configurators/templates.js +30 -0
- package/dist/configurators/templates.js.map +1 -0
- package/dist/configurators/workflow.d.ts.map +1 -1
- package/dist/configurators/workflow.js +10 -8
- package/dist/configurators/workflow.js.map +1 -1
- package/dist/{.claude → templates/claude}/agents/check.md +32 -8
- package/dist/{.claude → templates/claude}/agents/debug.md +6 -9
- package/dist/{.claude → templates/claude}/agents/dispatch.md +8 -3
- package/dist/{.claude → templates/claude}/agents/implement.md +1 -6
- package/dist/{.cursor → templates/claude}/commands/finish-work.md +1 -1
- package/dist/{.claude → templates/claude}/commands/onboard-developer.md +3 -3
- package/dist/{.claude → templates/claude}/commands/parallel.md +46 -53
- package/dist/{.claude → templates/claude}/commands/record-agent-flow.md +1 -1
- package/dist/{.claude → templates/claude}/commands/start.md +4 -5
- package/dist/{.claude → templates/claude}/hooks/inject-subagent-context.py +82 -4
- package/dist/templates/claude/hooks/ralph-loop.py +374 -0
- package/dist/templates/claude/index.d.ts +52 -0
- package/dist/templates/claude/index.d.ts.map +1 -0
- package/dist/templates/claude/index.js +83 -0
- package/dist/templates/claude/index.js.map +1 -0
- package/dist/{.claude → templates/claude}/settings.json +12 -0
- package/dist/{.claude → templates/cursor}/commands/finish-work.md +1 -1
- package/dist/{.cursor → templates/cursor}/commands/onboard-developer.md +3 -3
- package/dist/{.cursor → templates/cursor}/commands/record-agent-flow.md +1 -1
- package/dist/{.cursor → templates/cursor}/commands/start.md +4 -5
- package/dist/templates/cursor/index.d.ts +22 -0
- package/dist/templates/cursor/index.d.ts.map +1 -0
- package/dist/templates/cursor/index.js +42 -0
- package/dist/templates/cursor/index.js.map +1 -0
- package/dist/templates/extract.d.ts +22 -24
- package/dist/templates/extract.d.ts.map +1 -1
- package/dist/templates/extract.js +44 -60
- package/dist/templates/extract.js.map +1 -1
- package/dist/templates/markdown/index.d.ts +3 -7
- package/dist/templates/markdown/index.d.ts.map +1 -1
- package/dist/templates/markdown/index.js +6 -17
- package/dist/templates/markdown/index.js.map +1 -1
- package/dist/templates/trellis/backlog/.gitkeep +0 -0
- package/dist/templates/trellis/gitignore.txt +5 -0
- package/dist/templates/trellis/index.d.ts +37 -0
- package/dist/templates/trellis/index.d.ts.map +1 -0
- package/dist/templates/trellis/index.js +68 -0
- package/dist/templates/trellis/index.js.map +1 -0
- package/dist/templates/trellis/scripts/common/backlog.sh +220 -0
- package/dist/templates/trellis/scripts/common/feature-utils.sh +194 -0
- package/dist/{.trellis → templates/trellis}/scripts/common/git-context.sh +51 -0
- package/dist/{.trellis → templates/trellis}/scripts/common/paths.sh +17 -0
- package/dist/templates/trellis/scripts/common/registry.sh +247 -0
- package/dist/{.trellis → templates/trellis}/scripts/common/worktree.sh +0 -10
- package/dist/{.trellis → templates/trellis}/scripts/feature.sh +113 -42
- package/dist/{.trellis → templates/trellis}/scripts/multi-agent/cleanup.sh +56 -76
- package/dist/{.trellis → templates/trellis}/scripts/multi-agent/plan.sh +2 -1
- package/dist/{.trellis → templates/trellis}/scripts/multi-agent/start.sh +4 -35
- package/dist/{.trellis → templates/trellis}/scripts/multi-agent/status.sh +35 -9
- package/dist/{.trellis → templates/trellis}/workflow.md +85 -10
- package/dist/{.trellis → templates/trellis}/worktree.yaml +6 -8
- package/package.json +1 -1
- package/dist/.trellis/structure/backend/database-guidelines.md +0 -51
- package/dist/.trellis/structure/backend/directory-structure.md +0 -209
- package/dist/.trellis/structure/backend/error-handling.md +0 -278
- package/dist/.trellis/structure/backend/index.md +0 -38
- package/dist/.trellis/structure/backend/logging-guidelines.md +0 -266
- package/dist/.trellis/structure/backend/quality-guidelines.md +0 -313
- package/dist/.trellis/structure/frontend/component-guidelines.md +0 -59
- package/dist/.trellis/structure/frontend/directory-structure.md +0 -54
- package/dist/.trellis/structure/frontend/hook-guidelines.md +0 -51
- package/dist/.trellis/structure/frontend/index.md +0 -39
- package/dist/.trellis/structure/frontend/quality-guidelines.md +0 -51
- package/dist/.trellis/structure/frontend/state-management.md +0 -51
- package/dist/.trellis/structure/frontend/type-safety.md +0 -51
- package/dist/.trellis/structure/guides/code-reuse-thinking-guide.md +0 -92
- package/dist/.trellis/structure/guides/cross-layer-thinking-guide.md +0 -94
- package/dist/.trellis/structure/guides/index.md +0 -79
- package/dist/templates/markdown/init-agent.md +0 -315
- /package/dist/{.claude → templates/claude}/agents/plan.md +0 -0
- /package/dist/{.claude → templates/claude}/agents/research.md +0 -0
- /package/dist/{.claude → templates/claude}/commands/before-backend-dev.md +0 -0
- /package/dist/{.claude → templates/claude}/commands/before-frontend-dev.md +0 -0
- /package/dist/{.claude → templates/claude}/commands/break-loop.md +0 -0
- /package/dist/{.claude → templates/claude}/commands/check-backend.md +0 -0
- /package/dist/{.claude → templates/claude}/commands/check-cross-layer.md +0 -0
- /package/dist/{.claude → templates/claude}/commands/check-frontend.md +0 -0
- /package/dist/{.claude → templates/claude}/commands/create-command.md +0 -0
- /package/dist/{.claude → templates/claude}/commands/integrate-skill.md +0 -0
- /package/dist/{.cursor → templates/cursor}/commands/before-backend-dev.md +0 -0
- /package/dist/{.cursor → templates/cursor}/commands/before-frontend-dev.md +0 -0
- /package/dist/{.cursor → templates/cursor}/commands/break-loop.md +0 -0
- /package/dist/{.cursor → templates/cursor}/commands/check-backend.md +0 -0
- /package/dist/{.cursor → templates/cursor}/commands/check-cross-layer.md +0 -0
- /package/dist/{.cursor → templates/cursor}/commands/check-frontend.md +0 -0
- /package/dist/{.cursor → templates/cursor}/commands/create-command.md +0 -0
- /package/dist/{.cursor → templates/cursor}/commands/integrate-skill.md +0 -0
- /package/dist/{.trellis/agent-traces/index.md → templates/markdown/agent-traces-index.md} +0 -0
- /package/dist/{.trellis → templates/trellis}/scripts/add-session.sh +0 -0
- /package/dist/{.trellis → templates/trellis}/scripts/common/developer.sh +0 -0
- /package/dist/{.trellis → templates/trellis}/scripts/common/phase.sh +0 -0
- /package/dist/{.trellis → templates/trellis}/scripts/create-bootstrap.sh +0 -0
- /package/dist/{.trellis → templates/trellis}/scripts/get-context.sh +0 -0
- /package/dist/{.trellis → templates/trellis}/scripts/get-developer.sh +0 -0
- /package/dist/{.trellis → templates/trellis}/scripts/init-developer.sh +0 -0
- /package/dist/{.trellis → templates/trellis}/scripts/multi-agent/create-pr.sh +0 -0
|
@@ -349,6 +349,41 @@ def get_check_context(repo_root: str, feature_dir: str) -> str:
|
|
|
349
349
|
return "\n\n".join(context_parts)
|
|
350
350
|
|
|
351
351
|
|
|
352
|
+
def get_finish_context(repo_root: str, feature_dir: str) -> str:
|
|
353
|
+
"""
|
|
354
|
+
Complete context for Finish phase (final check before PR)
|
|
355
|
+
|
|
356
|
+
Read order:
|
|
357
|
+
1. All files in finish.jsonl (if exists)
|
|
358
|
+
2. Fallback to finish-work.md only (lightweight final check)
|
|
359
|
+
3. prd.md (for verifying requirements are met)
|
|
360
|
+
"""
|
|
361
|
+
context_parts = []
|
|
362
|
+
|
|
363
|
+
# 1. Try finish.jsonl first
|
|
364
|
+
finish_entries = read_jsonl_entries(repo_root, f"{feature_dir}/finish.jsonl")
|
|
365
|
+
|
|
366
|
+
if finish_entries:
|
|
367
|
+
for file_path, content in finish_entries:
|
|
368
|
+
context_parts.append(f"=== {file_path} ===\n{content}")
|
|
369
|
+
else:
|
|
370
|
+
# Fallback: only finish-work.md (lightweight)
|
|
371
|
+
finish_work = read_file_content(repo_root, ".claude/commands/finish-work.md")
|
|
372
|
+
if finish_work:
|
|
373
|
+
context_parts.append(
|
|
374
|
+
f"=== .claude/commands/finish-work.md (Finish checklist) ===\n{finish_work}"
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
# 2. Requirements document (for verifying requirements are met)
|
|
378
|
+
prd_content = read_file_content(repo_root, f"{feature_dir}/prd.md")
|
|
379
|
+
if prd_content:
|
|
380
|
+
context_parts.append(
|
|
381
|
+
f"=== {feature_dir}/prd.md (Requirements - verify all met) ===\n{prd_content}"
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
return "\n\n".join(context_parts)
|
|
385
|
+
|
|
386
|
+
|
|
352
387
|
def get_debug_context(repo_root: str, feature_dir: str) -> str:
|
|
353
388
|
"""
|
|
354
389
|
Complete context for Debug Agent
|
|
@@ -452,15 +487,49 @@ All check specs and dev specs you need:
|
|
|
452
487
|
1. **Get changes** - Run `git diff --name-only` and `git diff` to get code changes
|
|
453
488
|
2. **Check against specs** - Check item by item against specs above
|
|
454
489
|
3. **Self-fix** - Fix issues directly, don't just report
|
|
455
|
-
4. **Run verification** -
|
|
490
|
+
4. **Run verification** - Run project's lint and typecheck commands
|
|
456
491
|
|
|
457
492
|
## Important Constraints
|
|
458
493
|
|
|
459
494
|
- Fix issues yourself, don't just report
|
|
460
|
-
- Must execute complete checklist in
|
|
495
|
+
- Must execute complete checklist in check specs
|
|
461
496
|
- Pay special attention to impact radius analysis (L1-L5)"""
|
|
462
497
|
|
|
463
498
|
|
|
499
|
+
def build_finish_prompt(original_prompt: str, context: str) -> str:
|
|
500
|
+
"""Build complete prompt for Finish (final check before PR)"""
|
|
501
|
+
return f"""# Finish Agent Task
|
|
502
|
+
|
|
503
|
+
You are performing the final check before creating a PR.
|
|
504
|
+
|
|
505
|
+
## Your Context
|
|
506
|
+
|
|
507
|
+
Finish checklist and requirements:
|
|
508
|
+
|
|
509
|
+
{context}
|
|
510
|
+
|
|
511
|
+
---
|
|
512
|
+
|
|
513
|
+
## Your Task
|
|
514
|
+
|
|
515
|
+
{original_prompt}
|
|
516
|
+
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
## Workflow
|
|
520
|
+
|
|
521
|
+
1. **Review changes** - Run `git diff --name-only` to see all changed files
|
|
522
|
+
2. **Verify requirements** - Check each requirement in prd.md is implemented
|
|
523
|
+
3. **Run final checks** - Execute finish-work.md checklist
|
|
524
|
+
4. **Confirm ready** - Ensure code is ready for PR
|
|
525
|
+
|
|
526
|
+
## Important Constraints
|
|
527
|
+
|
|
528
|
+
- This is a final verification, not a fix phase
|
|
529
|
+
- If critical issues found, report them clearly
|
|
530
|
+
- Verify all acceptance criteria in prd.md are met"""
|
|
531
|
+
|
|
532
|
+
|
|
464
533
|
def build_debug_prompt(original_prompt: str, context: str) -> str:
|
|
465
534
|
"""Build complete prompt for Debug"""
|
|
466
535
|
return f"""# Debug Agent Task
|
|
@@ -643,6 +712,9 @@ def main():
|
|
|
643
712
|
# Update current_phase in feature.json (system-level enforcement)
|
|
644
713
|
update_current_phase(repo_root, feature_dir, subagent_type)
|
|
645
714
|
|
|
715
|
+
# Check for [finish] marker in prompt (check agent with finish context)
|
|
716
|
+
is_finish_phase = "[finish]" in original_prompt.lower()
|
|
717
|
+
|
|
646
718
|
# Get context and build prompt based on subagent type
|
|
647
719
|
if subagent_type == AGENT_IMPLEMENT:
|
|
648
720
|
assert feature_dir is not None # validated above
|
|
@@ -650,8 +722,14 @@ def main():
|
|
|
650
722
|
new_prompt = build_implement_prompt(original_prompt, context)
|
|
651
723
|
elif subagent_type == AGENT_CHECK:
|
|
652
724
|
assert feature_dir is not None # validated above
|
|
653
|
-
|
|
654
|
-
|
|
725
|
+
if is_finish_phase:
|
|
726
|
+
# Finish phase: use finish context (lighter, focused on final verification)
|
|
727
|
+
context = get_finish_context(repo_root, feature_dir)
|
|
728
|
+
new_prompt = build_finish_prompt(original_prompt, context)
|
|
729
|
+
else:
|
|
730
|
+
# Regular check phase: use check context (full specs for self-fix loop)
|
|
731
|
+
context = get_check_context(repo_root, feature_dir)
|
|
732
|
+
new_prompt = build_check_prompt(original_prompt, context)
|
|
655
733
|
elif subagent_type == AGENT_DEBUG:
|
|
656
734
|
assert feature_dir is not None # validated above
|
|
657
735
|
context = get_debug_context(repo_root, feature_dir)
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Ralph Loop - SubagentStop Hook for Check Agent Loop Control
|
|
4
|
+
|
|
5
|
+
Based on the Ralph Wiggum technique for autonomous agent loops.
|
|
6
|
+
Uses completion promises to control when the check agent can stop.
|
|
7
|
+
|
|
8
|
+
Mechanism:
|
|
9
|
+
- Intercepts when check subagent tries to stop (SubagentStop event)
|
|
10
|
+
- If verify commands configured in worktree.yaml, runs them to verify
|
|
11
|
+
- Otherwise, reads check.jsonl to get dynamic completion markers ({reason}_FINISH)
|
|
12
|
+
- Blocks stopping until verification passes or all markers found
|
|
13
|
+
- Has max iterations as safety limit
|
|
14
|
+
|
|
15
|
+
State file: .trellis/.ralph-state.json
|
|
16
|
+
- Tracks current iteration count per session
|
|
17
|
+
- Resets when feature changes
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import os
|
|
22
|
+
import subprocess
|
|
23
|
+
import sys
|
|
24
|
+
from datetime import datetime
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
# =============================================================================
|
|
28
|
+
# Configuration
|
|
29
|
+
# =============================================================================
|
|
30
|
+
|
|
31
|
+
MAX_ITERATIONS = 5 # Safety limit to prevent infinite loops
|
|
32
|
+
STATE_TIMEOUT_MINUTES = 30 # Reset state if older than this
|
|
33
|
+
STATE_FILE = ".trellis/.ralph-state.json"
|
|
34
|
+
WORKTREE_YAML = ".trellis/worktree.yaml"
|
|
35
|
+
DIR_WORKFLOW = ".trellis"
|
|
36
|
+
FILE_CURRENT_FEATURE = ".current-feature"
|
|
37
|
+
|
|
38
|
+
# Only control loop for check agent
|
|
39
|
+
TARGET_AGENT = "check"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def find_repo_root(start_path: str) -> str | None:
|
|
43
|
+
"""Find git repo root from start_path upwards"""
|
|
44
|
+
current = Path(start_path).resolve()
|
|
45
|
+
while current != current.parent:
|
|
46
|
+
if (current / ".git").exists():
|
|
47
|
+
return str(current)
|
|
48
|
+
current = current.parent
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_current_feature(repo_root: str) -> str | None:
|
|
53
|
+
"""Read current feature directory path"""
|
|
54
|
+
current_feature_file = os.path.join(repo_root, DIR_WORKFLOW, FILE_CURRENT_FEATURE)
|
|
55
|
+
if not os.path.exists(current_feature_file):
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
with open(current_feature_file, "r", encoding="utf-8") as f:
|
|
60
|
+
content = f.read().strip()
|
|
61
|
+
return content if content else None
|
|
62
|
+
except Exception:
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def get_verify_commands(repo_root: str) -> list[str]:
|
|
67
|
+
"""
|
|
68
|
+
Read verify commands from worktree.yaml.
|
|
69
|
+
|
|
70
|
+
Returns list of commands to run, or empty list if not configured.
|
|
71
|
+
Uses simple YAML parsing without external dependencies.
|
|
72
|
+
"""
|
|
73
|
+
yaml_path = os.path.join(repo_root, WORKTREE_YAML)
|
|
74
|
+
if not os.path.exists(yaml_path):
|
|
75
|
+
return []
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
with open(yaml_path, "r", encoding="utf-8") as f:
|
|
79
|
+
content = f.read()
|
|
80
|
+
|
|
81
|
+
# Simple YAML parsing for verify section
|
|
82
|
+
# Look for "verify:" followed by list items
|
|
83
|
+
lines = content.split("\n")
|
|
84
|
+
in_verify_section = False
|
|
85
|
+
commands = []
|
|
86
|
+
|
|
87
|
+
for line in lines:
|
|
88
|
+
stripped = line.strip()
|
|
89
|
+
|
|
90
|
+
# Check for section start
|
|
91
|
+
if stripped.startswith("verify:"):
|
|
92
|
+
in_verify_section = True
|
|
93
|
+
continue
|
|
94
|
+
|
|
95
|
+
# Check for new section (not indented, ends with :)
|
|
96
|
+
if (
|
|
97
|
+
not line.startswith(" ")
|
|
98
|
+
and not line.startswith("\t")
|
|
99
|
+
and stripped.endswith(":")
|
|
100
|
+
and stripped != ""
|
|
101
|
+
):
|
|
102
|
+
in_verify_section = False
|
|
103
|
+
continue
|
|
104
|
+
|
|
105
|
+
# If in verify section, look for list items
|
|
106
|
+
if in_verify_section:
|
|
107
|
+
# Skip comments and empty lines
|
|
108
|
+
if stripped.startswith("#") or stripped == "":
|
|
109
|
+
continue
|
|
110
|
+
# Parse list item (- command)
|
|
111
|
+
if stripped.startswith("- "):
|
|
112
|
+
cmd = stripped[2:].strip()
|
|
113
|
+
if cmd:
|
|
114
|
+
commands.append(cmd)
|
|
115
|
+
|
|
116
|
+
return commands
|
|
117
|
+
except Exception:
|
|
118
|
+
return []
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def run_verify_commands(repo_root: str, commands: list[str]) -> tuple[bool, str]:
|
|
122
|
+
"""
|
|
123
|
+
Run verify commands and return (success, message).
|
|
124
|
+
|
|
125
|
+
All commands must pass for success.
|
|
126
|
+
"""
|
|
127
|
+
for cmd in commands:
|
|
128
|
+
try:
|
|
129
|
+
result = subprocess.run(
|
|
130
|
+
cmd,
|
|
131
|
+
shell=True,
|
|
132
|
+
cwd=repo_root,
|
|
133
|
+
capture_output=True,
|
|
134
|
+
timeout=120, # 2 minute timeout per command
|
|
135
|
+
)
|
|
136
|
+
if result.returncode != 0:
|
|
137
|
+
stderr = result.stderr.decode("utf-8", errors="replace")
|
|
138
|
+
stdout = result.stdout.decode("utf-8", errors="replace")
|
|
139
|
+
error_output = stderr or stdout
|
|
140
|
+
# Truncate long output
|
|
141
|
+
if len(error_output) > 500:
|
|
142
|
+
error_output = error_output[:500] + "..."
|
|
143
|
+
return False, f"Command failed: {cmd}\n{error_output}"
|
|
144
|
+
except subprocess.TimeoutExpired:
|
|
145
|
+
return False, f"Command timed out: {cmd}"
|
|
146
|
+
except Exception as e:
|
|
147
|
+
return False, f"Command error: {cmd} - {str(e)}"
|
|
148
|
+
|
|
149
|
+
return True, "All verify commands passed"
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def get_completion_markers(repo_root: str, feature_dir: str) -> list[str]:
|
|
153
|
+
"""
|
|
154
|
+
Read check.jsonl and generate completion markers from reasons.
|
|
155
|
+
|
|
156
|
+
Each entry's "reason" field becomes {REASON}_FINISH marker.
|
|
157
|
+
Example: {"file": "...", "reason": "TypeCheck"} -> "TYPECHECK_FINISH"
|
|
158
|
+
"""
|
|
159
|
+
check_jsonl_path = os.path.join(repo_root, feature_dir, "check.jsonl")
|
|
160
|
+
markers = []
|
|
161
|
+
|
|
162
|
+
if not os.path.exists(check_jsonl_path):
|
|
163
|
+
# Fallback: if no check.jsonl, use default marker
|
|
164
|
+
return ["ALL_CHECKS_FINISH"]
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
with open(check_jsonl_path, "r", encoding="utf-8") as f:
|
|
168
|
+
for line in f:
|
|
169
|
+
line = line.strip()
|
|
170
|
+
if not line:
|
|
171
|
+
continue
|
|
172
|
+
try:
|
|
173
|
+
item = json.loads(line)
|
|
174
|
+
reason = item.get("reason", "")
|
|
175
|
+
if reason:
|
|
176
|
+
# Convert to uppercase and add _FINISH suffix
|
|
177
|
+
marker = f"{reason.upper().replace(' ', '_')}_FINISH"
|
|
178
|
+
if marker not in markers:
|
|
179
|
+
markers.append(marker)
|
|
180
|
+
except json.JSONDecodeError:
|
|
181
|
+
continue
|
|
182
|
+
except Exception:
|
|
183
|
+
pass
|
|
184
|
+
|
|
185
|
+
# If no markers found, use default
|
|
186
|
+
if not markers:
|
|
187
|
+
markers = ["ALL_CHECKS_FINISH"]
|
|
188
|
+
|
|
189
|
+
return markers
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def load_state(repo_root: str) -> dict:
|
|
193
|
+
"""Load Ralph Loop state from file"""
|
|
194
|
+
state_path = os.path.join(repo_root, STATE_FILE)
|
|
195
|
+
if not os.path.exists(state_path):
|
|
196
|
+
return {"feature": None, "iteration": 0, "started_at": None}
|
|
197
|
+
|
|
198
|
+
try:
|
|
199
|
+
with open(state_path, "r", encoding="utf-8") as f:
|
|
200
|
+
return json.load(f)
|
|
201
|
+
except Exception:
|
|
202
|
+
return {"feature": None, "iteration": 0, "started_at": None}
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def save_state(repo_root: str, state: dict) -> None:
|
|
206
|
+
"""Save Ralph Loop state to file"""
|
|
207
|
+
state_path = os.path.join(repo_root, STATE_FILE)
|
|
208
|
+
try:
|
|
209
|
+
# Ensure directory exists
|
|
210
|
+
os.makedirs(os.path.dirname(state_path), exist_ok=True)
|
|
211
|
+
with open(state_path, "w", encoding="utf-8") as f:
|
|
212
|
+
json.dump(state, f, indent=2, ensure_ascii=False)
|
|
213
|
+
except Exception:
|
|
214
|
+
pass
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def check_completion(agent_output: str, markers: list[str]) -> tuple[bool, list[str]]:
|
|
218
|
+
"""
|
|
219
|
+
Check if all completion markers are present in agent output.
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
(all_complete, missing_markers)
|
|
223
|
+
"""
|
|
224
|
+
missing = []
|
|
225
|
+
for marker in markers:
|
|
226
|
+
if marker not in agent_output:
|
|
227
|
+
missing.append(marker)
|
|
228
|
+
|
|
229
|
+
return len(missing) == 0, missing
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def main():
|
|
233
|
+
try:
|
|
234
|
+
input_data = json.load(sys.stdin)
|
|
235
|
+
except json.JSONDecodeError:
|
|
236
|
+
# If can't parse input, allow stop
|
|
237
|
+
sys.exit(0)
|
|
238
|
+
|
|
239
|
+
# Get event info
|
|
240
|
+
hook_event = input_data.get("hook_event_name", "")
|
|
241
|
+
|
|
242
|
+
# Only handle SubagentStop event
|
|
243
|
+
if hook_event != "SubagentStop":
|
|
244
|
+
sys.exit(0)
|
|
245
|
+
|
|
246
|
+
# Get subagent info
|
|
247
|
+
subagent_type = input_data.get("subagent_type", "")
|
|
248
|
+
agent_output = input_data.get("agent_output", "")
|
|
249
|
+
original_prompt = input_data.get("prompt", "")
|
|
250
|
+
cwd = input_data.get("cwd", os.getcwd())
|
|
251
|
+
|
|
252
|
+
# Only control check agent
|
|
253
|
+
if subagent_type != TARGET_AGENT:
|
|
254
|
+
sys.exit(0)
|
|
255
|
+
|
|
256
|
+
# Skip Ralph Loop for finish phase (already verified in check phase)
|
|
257
|
+
if "[finish]" in original_prompt.lower():
|
|
258
|
+
sys.exit(0)
|
|
259
|
+
|
|
260
|
+
# Find repo root
|
|
261
|
+
repo_root = find_repo_root(cwd)
|
|
262
|
+
if not repo_root:
|
|
263
|
+
sys.exit(0)
|
|
264
|
+
|
|
265
|
+
# Get current feature
|
|
266
|
+
feature_dir = get_current_feature(repo_root)
|
|
267
|
+
if not feature_dir:
|
|
268
|
+
sys.exit(0)
|
|
269
|
+
|
|
270
|
+
# Load state
|
|
271
|
+
state = load_state(repo_root)
|
|
272
|
+
|
|
273
|
+
# Reset state if feature changed or state is too old
|
|
274
|
+
should_reset = False
|
|
275
|
+
if state.get("feature") != feature_dir:
|
|
276
|
+
should_reset = True
|
|
277
|
+
elif state.get("started_at"):
|
|
278
|
+
try:
|
|
279
|
+
started = datetime.fromisoformat(state["started_at"])
|
|
280
|
+
if (datetime.now() - started).total_seconds() > STATE_TIMEOUT_MINUTES * 60:
|
|
281
|
+
should_reset = True
|
|
282
|
+
except (ValueError, TypeError):
|
|
283
|
+
should_reset = True
|
|
284
|
+
|
|
285
|
+
if should_reset:
|
|
286
|
+
state = {
|
|
287
|
+
"feature": feature_dir,
|
|
288
|
+
"iteration": 0,
|
|
289
|
+
"started_at": datetime.now().isoformat(),
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
# Increment iteration
|
|
293
|
+
state["iteration"] = state.get("iteration", 0) + 1
|
|
294
|
+
current_iteration = state["iteration"]
|
|
295
|
+
|
|
296
|
+
# Save state
|
|
297
|
+
save_state(repo_root, state)
|
|
298
|
+
|
|
299
|
+
# Safety check: max iterations
|
|
300
|
+
if current_iteration >= MAX_ITERATIONS:
|
|
301
|
+
# Allow stop, reset state for next run
|
|
302
|
+
state["iteration"] = 0
|
|
303
|
+
save_state(repo_root, state)
|
|
304
|
+
output = {
|
|
305
|
+
"decision": "allow",
|
|
306
|
+
"reason": f"Max iterations ({MAX_ITERATIONS}) reached. Stopping to prevent infinite loop.",
|
|
307
|
+
}
|
|
308
|
+
print(json.dumps(output, ensure_ascii=False))
|
|
309
|
+
sys.exit(0)
|
|
310
|
+
|
|
311
|
+
# Check if verify commands are configured
|
|
312
|
+
verify_commands = get_verify_commands(repo_root)
|
|
313
|
+
|
|
314
|
+
if verify_commands:
|
|
315
|
+
# Use programmatic verification
|
|
316
|
+
passed, message = run_verify_commands(repo_root, verify_commands)
|
|
317
|
+
|
|
318
|
+
if passed:
|
|
319
|
+
# All verify commands passed, allow stop
|
|
320
|
+
state["iteration"] = 0
|
|
321
|
+
save_state(repo_root, state)
|
|
322
|
+
output = {
|
|
323
|
+
"decision": "allow",
|
|
324
|
+
"reason": "All verify commands passed. Check phase complete.",
|
|
325
|
+
}
|
|
326
|
+
print(json.dumps(output, ensure_ascii=False))
|
|
327
|
+
sys.exit(0)
|
|
328
|
+
else:
|
|
329
|
+
# Verification failed, block stop
|
|
330
|
+
output = {
|
|
331
|
+
"decision": "block",
|
|
332
|
+
"reason": f"Iteration {current_iteration}/{MAX_ITERATIONS}. Verification failed:\n{message}\n\nPlease fix the issues and try again.",
|
|
333
|
+
}
|
|
334
|
+
print(json.dumps(output, ensure_ascii=False))
|
|
335
|
+
sys.exit(0)
|
|
336
|
+
else:
|
|
337
|
+
# No verify commands, fall back to completion markers
|
|
338
|
+
markers = get_completion_markers(repo_root, feature_dir)
|
|
339
|
+
all_complete, missing = check_completion(agent_output, markers)
|
|
340
|
+
|
|
341
|
+
if all_complete:
|
|
342
|
+
# All checks complete, allow stop
|
|
343
|
+
state["iteration"] = 0
|
|
344
|
+
save_state(repo_root, state)
|
|
345
|
+
output = {
|
|
346
|
+
"decision": "allow",
|
|
347
|
+
"reason": "All completion markers found. Check phase complete.",
|
|
348
|
+
}
|
|
349
|
+
print(json.dumps(output, ensure_ascii=False))
|
|
350
|
+
sys.exit(0)
|
|
351
|
+
else:
|
|
352
|
+
# Missing markers, block stop and continue
|
|
353
|
+
output = {
|
|
354
|
+
"decision": "block",
|
|
355
|
+
"reason": f"""Iteration {current_iteration}/{MAX_ITERATIONS}. Missing completion markers: {", ".join(missing)}.
|
|
356
|
+
|
|
357
|
+
IMPORTANT: You must ACTUALLY run the checks, not just output the markers.
|
|
358
|
+
- Did you run lint? What was the output?
|
|
359
|
+
- Did you run typecheck? What was the output?
|
|
360
|
+
- Did they actually pass with zero errors?
|
|
361
|
+
|
|
362
|
+
Only output a marker (e.g., LINT_FINISH) AFTER:
|
|
363
|
+
1. You have executed the corresponding command
|
|
364
|
+
2. The command completed with zero errors
|
|
365
|
+
3. You have shown the command output in your response
|
|
366
|
+
|
|
367
|
+
Do NOT output markers just to escape the loop. The loop exists to ensure quality.""",
|
|
368
|
+
}
|
|
369
|
+
print(json.dumps(output, ensure_ascii=False))
|
|
370
|
+
sys.exit(0)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
if __name__ == "__main__":
|
|
374
|
+
main()
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code templates
|
|
3
|
+
*
|
|
4
|
+
* These are GENERIC templates for user projects.
|
|
5
|
+
* Do NOT use Trellis project's own .claude/ directory (which may be customized).
|
|
6
|
+
*
|
|
7
|
+
* Directory structure:
|
|
8
|
+
* claude/
|
|
9
|
+
* ├── commands/ # Slash commands
|
|
10
|
+
* ├── agents/ # Multi-agent pipeline agents
|
|
11
|
+
* ├── hooks/ # Context injection hooks
|
|
12
|
+
* └── settings.json # Settings configuration
|
|
13
|
+
*/
|
|
14
|
+
export declare const settingsTemplate: string;
|
|
15
|
+
/**
|
|
16
|
+
* Command template with name and content
|
|
17
|
+
*/
|
|
18
|
+
export interface CommandTemplate {
|
|
19
|
+
name: string;
|
|
20
|
+
content: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Agent template with name and content
|
|
24
|
+
*/
|
|
25
|
+
export interface AgentTemplate {
|
|
26
|
+
name: string;
|
|
27
|
+
content: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Hook template with target path and content
|
|
31
|
+
*/
|
|
32
|
+
export interface HookTemplate {
|
|
33
|
+
targetPath: string;
|
|
34
|
+
content: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Get all command templates
|
|
38
|
+
*/
|
|
39
|
+
export declare function getAllCommands(): CommandTemplate[];
|
|
40
|
+
/**
|
|
41
|
+
* Get all agent templates
|
|
42
|
+
*/
|
|
43
|
+
export declare function getAllAgents(): AgentTemplate[];
|
|
44
|
+
/**
|
|
45
|
+
* Get all hook templates
|
|
46
|
+
*/
|
|
47
|
+
export declare function getAllHooks(): HookTemplate[];
|
|
48
|
+
/**
|
|
49
|
+
* Get settings template
|
|
50
|
+
*/
|
|
51
|
+
export declare function getSettingsTemplate(): HookTemplate;
|
|
52
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/templates/claude/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAsBH,eAAO,MAAM,gBAAgB,QAAgC,CAAC;AAE9D;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,eAAe,EAAE,CAalD;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,aAAa,EAAE,CAa9C;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,YAAY,EAAE,CAU5C;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,YAAY,CAKlD"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code templates
|
|
3
|
+
*
|
|
4
|
+
* These are GENERIC templates for user projects.
|
|
5
|
+
* Do NOT use Trellis project's own .claude/ directory (which may be customized).
|
|
6
|
+
*
|
|
7
|
+
* Directory structure:
|
|
8
|
+
* claude/
|
|
9
|
+
* ├── commands/ # Slash commands
|
|
10
|
+
* ├── agents/ # Multi-agent pipeline agents
|
|
11
|
+
* ├── hooks/ # Context injection hooks
|
|
12
|
+
* └── settings.json # Settings configuration
|
|
13
|
+
*/
|
|
14
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
15
|
+
import { dirname, join } from "node:path";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
17
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
18
|
+
const __dirname = dirname(__filename);
|
|
19
|
+
function readTemplate(relativePath) {
|
|
20
|
+
return readFileSync(join(__dirname, relativePath), "utf-8");
|
|
21
|
+
}
|
|
22
|
+
function listFiles(dir) {
|
|
23
|
+
try {
|
|
24
|
+
return readdirSync(join(__dirname, dir));
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// Settings
|
|
31
|
+
export const settingsTemplate = readTemplate("settings.json");
|
|
32
|
+
/**
|
|
33
|
+
* Get all command templates
|
|
34
|
+
*/
|
|
35
|
+
export function getAllCommands() {
|
|
36
|
+
const commands = [];
|
|
37
|
+
const files = listFiles("commands");
|
|
38
|
+
for (const file of files) {
|
|
39
|
+
if (file.endsWith(".md")) {
|
|
40
|
+
const name = file.replace(".md", "");
|
|
41
|
+
const content = readTemplate(`commands/${file}`);
|
|
42
|
+
commands.push({ name, content });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return commands;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get all agent templates
|
|
49
|
+
*/
|
|
50
|
+
export function getAllAgents() {
|
|
51
|
+
const agents = [];
|
|
52
|
+
const files = listFiles("agents");
|
|
53
|
+
for (const file of files) {
|
|
54
|
+
if (file.endsWith(".md")) {
|
|
55
|
+
const name = file.replace(".md", "");
|
|
56
|
+
const content = readTemplate(`agents/${file}`);
|
|
57
|
+
agents.push({ name, content });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return agents;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Get all hook templates
|
|
64
|
+
*/
|
|
65
|
+
export function getAllHooks() {
|
|
66
|
+
const hooks = [];
|
|
67
|
+
const files = listFiles("hooks");
|
|
68
|
+
for (const file of files) {
|
|
69
|
+
const content = readTemplate(`hooks/${file}`);
|
|
70
|
+
hooks.push({ targetPath: `hooks/${file}`, content });
|
|
71
|
+
}
|
|
72
|
+
return hooks;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Get settings template
|
|
76
|
+
*/
|
|
77
|
+
export function getSettingsTemplate() {
|
|
78
|
+
return {
|
|
79
|
+
targetPath: "settings.json",
|
|
80
|
+
content: settingsTemplate,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/templates/claude/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAEtC,SAAS,YAAY,CAAC,YAAoB;IACxC,OAAO,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,EAAE,OAAO,CAAC,CAAC;AAC9D,CAAC;AAED,SAAS,SAAS,CAAC,GAAW;IAC5B,IAAI,CAAC;QACH,OAAO,WAAW,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,WAAW;AACX,MAAM,CAAC,MAAM,gBAAgB,GAAG,YAAY,CAAC,eAAe,CAAC,CAAC;AA0B9D;;GAEG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,QAAQ,GAAsB,EAAE,CAAC;IACvC,MAAM,KAAK,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC;IAEpC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACrC,MAAM,OAAO,GAAG,YAAY,CAAC,YAAY,IAAI,EAAE,CAAC,CAAC;YACjD,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY;IAC1B,MAAM,MAAM,GAAoB,EAAE,CAAC;IACnC,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;IAElC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACrC,MAAM,OAAO,GAAG,YAAY,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC;YAC/C,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW;IACzB,MAAM,KAAK,GAAmB,EAAE,CAAC;IACjC,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;IAEjC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,YAAY,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;QAC9C,KAAK,CAAC,IAAI,CAAC,EAAE,UAAU,EAAE,SAAS,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IACvD,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB;IACjC,OAAO;QACL,UAAU,EAAE,eAAe;QAC3B,OAAO,EAAE,gBAAgB;KAC1B,CAAC;AACJ,CAAC"}
|
|
@@ -21,7 +21,7 @@ pnpm test
|
|
|
21
21
|
- [ ] `pnpm type-check` passes with no type errors?
|
|
22
22
|
- [ ] Tests pass?
|
|
23
23
|
- [ ] No `console.log` statements (use logger)?
|
|
24
|
-
- [ ] No non-null assertions
|
|
24
|
+
- [ ] No non-null assertions (the `x!` operator)?
|
|
25
25
|
- [ ] No `any` types?
|
|
26
26
|
|
|
27
27
|
### 2. Documentation Sync
|