@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.
Files changed (122) hide show
  1. package/README.md +757 -135
  2. package/dist/cli/index.d.ts.map +1 -1
  3. package/dist/cli/index.js +38 -3
  4. package/dist/cli/index.js.map +1 -1
  5. package/dist/commands/init.d.ts.map +1 -1
  6. package/dist/commands/init.js +6 -8
  7. package/dist/commands/init.js.map +1 -1
  8. package/dist/commands/update.d.ts +11 -0
  9. package/dist/commands/update.d.ts.map +1 -0
  10. package/dist/commands/update.js +357 -0
  11. package/dist/commands/update.js.map +1 -0
  12. package/dist/configurators/claude.d.ts +2 -5
  13. package/dist/configurators/claude.d.ts.map +1 -1
  14. package/dist/configurators/claude.js +49 -12
  15. package/dist/configurators/claude.js.map +1 -1
  16. package/dist/configurators/cursor.d.ts +1 -4
  17. package/dist/configurators/cursor.d.ts.map +1 -1
  18. package/dist/configurators/cursor.js +42 -11
  19. package/dist/configurators/cursor.js.map +1 -1
  20. package/dist/configurators/templates.d.ts +15 -0
  21. package/dist/configurators/templates.d.ts.map +1 -0
  22. package/dist/configurators/templates.js +30 -0
  23. package/dist/configurators/templates.js.map +1 -0
  24. package/dist/configurators/workflow.d.ts.map +1 -1
  25. package/dist/configurators/workflow.js +10 -8
  26. package/dist/configurators/workflow.js.map +1 -1
  27. package/dist/{.claude → templates/claude}/agents/check.md +32 -8
  28. package/dist/{.claude → templates/claude}/agents/debug.md +6 -9
  29. package/dist/{.claude → templates/claude}/agents/dispatch.md +8 -3
  30. package/dist/{.claude → templates/claude}/agents/implement.md +1 -6
  31. package/dist/{.cursor → templates/claude}/commands/finish-work.md +1 -1
  32. package/dist/{.claude → templates/claude}/commands/onboard-developer.md +3 -3
  33. package/dist/{.claude → templates/claude}/commands/parallel.md +46 -53
  34. package/dist/{.claude → templates/claude}/commands/record-agent-flow.md +1 -1
  35. package/dist/{.claude → templates/claude}/commands/start.md +4 -5
  36. package/dist/{.claude → templates/claude}/hooks/inject-subagent-context.py +82 -4
  37. package/dist/templates/claude/hooks/ralph-loop.py +374 -0
  38. package/dist/templates/claude/index.d.ts +52 -0
  39. package/dist/templates/claude/index.d.ts.map +1 -0
  40. package/dist/templates/claude/index.js +83 -0
  41. package/dist/templates/claude/index.js.map +1 -0
  42. package/dist/{.claude → templates/claude}/settings.json +12 -0
  43. package/dist/{.claude → templates/cursor}/commands/finish-work.md +1 -1
  44. package/dist/{.cursor → templates/cursor}/commands/onboard-developer.md +3 -3
  45. package/dist/{.cursor → templates/cursor}/commands/record-agent-flow.md +1 -1
  46. package/dist/{.cursor → templates/cursor}/commands/start.md +4 -5
  47. package/dist/templates/cursor/index.d.ts +22 -0
  48. package/dist/templates/cursor/index.d.ts.map +1 -0
  49. package/dist/templates/cursor/index.js +42 -0
  50. package/dist/templates/cursor/index.js.map +1 -0
  51. package/dist/templates/extract.d.ts +22 -24
  52. package/dist/templates/extract.d.ts.map +1 -1
  53. package/dist/templates/extract.js +44 -60
  54. package/dist/templates/extract.js.map +1 -1
  55. package/dist/templates/markdown/index.d.ts +3 -7
  56. package/dist/templates/markdown/index.d.ts.map +1 -1
  57. package/dist/templates/markdown/index.js +6 -17
  58. package/dist/templates/markdown/index.js.map +1 -1
  59. package/dist/templates/trellis/backlog/.gitkeep +0 -0
  60. package/dist/templates/trellis/gitignore.txt +5 -0
  61. package/dist/templates/trellis/index.d.ts +37 -0
  62. package/dist/templates/trellis/index.d.ts.map +1 -0
  63. package/dist/templates/trellis/index.js +68 -0
  64. package/dist/templates/trellis/index.js.map +1 -0
  65. package/dist/templates/trellis/scripts/common/backlog.sh +220 -0
  66. package/dist/templates/trellis/scripts/common/feature-utils.sh +194 -0
  67. package/dist/{.trellis → templates/trellis}/scripts/common/git-context.sh +51 -0
  68. package/dist/{.trellis → templates/trellis}/scripts/common/paths.sh +17 -0
  69. package/dist/templates/trellis/scripts/common/registry.sh +247 -0
  70. package/dist/{.trellis → templates/trellis}/scripts/common/worktree.sh +0 -10
  71. package/dist/{.trellis → templates/trellis}/scripts/feature.sh +113 -42
  72. package/dist/{.trellis → templates/trellis}/scripts/multi-agent/cleanup.sh +56 -76
  73. package/dist/{.trellis → templates/trellis}/scripts/multi-agent/plan.sh +2 -1
  74. package/dist/{.trellis → templates/trellis}/scripts/multi-agent/start.sh +4 -35
  75. package/dist/{.trellis → templates/trellis}/scripts/multi-agent/status.sh +35 -9
  76. package/dist/{.trellis → templates/trellis}/workflow.md +85 -10
  77. package/dist/{.trellis → templates/trellis}/worktree.yaml +6 -8
  78. package/package.json +1 -1
  79. package/dist/.trellis/structure/backend/database-guidelines.md +0 -51
  80. package/dist/.trellis/structure/backend/directory-structure.md +0 -209
  81. package/dist/.trellis/structure/backend/error-handling.md +0 -278
  82. package/dist/.trellis/structure/backend/index.md +0 -38
  83. package/dist/.trellis/structure/backend/logging-guidelines.md +0 -266
  84. package/dist/.trellis/structure/backend/quality-guidelines.md +0 -313
  85. package/dist/.trellis/structure/frontend/component-guidelines.md +0 -59
  86. package/dist/.trellis/structure/frontend/directory-structure.md +0 -54
  87. package/dist/.trellis/structure/frontend/hook-guidelines.md +0 -51
  88. package/dist/.trellis/structure/frontend/index.md +0 -39
  89. package/dist/.trellis/structure/frontend/quality-guidelines.md +0 -51
  90. package/dist/.trellis/structure/frontend/state-management.md +0 -51
  91. package/dist/.trellis/structure/frontend/type-safety.md +0 -51
  92. package/dist/.trellis/structure/guides/code-reuse-thinking-guide.md +0 -92
  93. package/dist/.trellis/structure/guides/cross-layer-thinking-guide.md +0 -94
  94. package/dist/.trellis/structure/guides/index.md +0 -79
  95. package/dist/templates/markdown/init-agent.md +0 -315
  96. /package/dist/{.claude → templates/claude}/agents/plan.md +0 -0
  97. /package/dist/{.claude → templates/claude}/agents/research.md +0 -0
  98. /package/dist/{.claude → templates/claude}/commands/before-backend-dev.md +0 -0
  99. /package/dist/{.claude → templates/claude}/commands/before-frontend-dev.md +0 -0
  100. /package/dist/{.claude → templates/claude}/commands/break-loop.md +0 -0
  101. /package/dist/{.claude → templates/claude}/commands/check-backend.md +0 -0
  102. /package/dist/{.claude → templates/claude}/commands/check-cross-layer.md +0 -0
  103. /package/dist/{.claude → templates/claude}/commands/check-frontend.md +0 -0
  104. /package/dist/{.claude → templates/claude}/commands/create-command.md +0 -0
  105. /package/dist/{.claude → templates/claude}/commands/integrate-skill.md +0 -0
  106. /package/dist/{.cursor → templates/cursor}/commands/before-backend-dev.md +0 -0
  107. /package/dist/{.cursor → templates/cursor}/commands/before-frontend-dev.md +0 -0
  108. /package/dist/{.cursor → templates/cursor}/commands/break-loop.md +0 -0
  109. /package/dist/{.cursor → templates/cursor}/commands/check-backend.md +0 -0
  110. /package/dist/{.cursor → templates/cursor}/commands/check-cross-layer.md +0 -0
  111. /package/dist/{.cursor → templates/cursor}/commands/check-frontend.md +0 -0
  112. /package/dist/{.cursor → templates/cursor}/commands/create-command.md +0 -0
  113. /package/dist/{.cursor → templates/cursor}/commands/integrate-skill.md +0 -0
  114. /package/dist/{.trellis/agent-traces/index.md → templates/markdown/agent-traces-index.md} +0 -0
  115. /package/dist/{.trellis → templates/trellis}/scripts/add-session.sh +0 -0
  116. /package/dist/{.trellis → templates/trellis}/scripts/common/developer.sh +0 -0
  117. /package/dist/{.trellis → templates/trellis}/scripts/common/phase.sh +0 -0
  118. /package/dist/{.trellis → templates/trellis}/scripts/create-bootstrap.sh +0 -0
  119. /package/dist/{.trellis → templates/trellis}/scripts/get-context.sh +0 -0
  120. /package/dist/{.trellis → templates/trellis}/scripts/get-developer.sh +0 -0
  121. /package/dist/{.trellis → templates/trellis}/scripts/init-developer.sh +0 -0
  122. /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** - Reference .husky/pre-commit for typecheck and lint
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 finish-work.md
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
- context = get_check_context(repo_root, feature_dir)
654
- new_prompt = build_check_prompt(original_prompt, context)
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"}
@@ -11,6 +11,18 @@
11
11
  }
12
12
  ]
13
13
  }
14
+ ],
15
+ "SubagentStop": [
16
+ {
17
+ "matcher": "check",
18
+ "hooks": [
19
+ {
20
+ "type": "command",
21
+ "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/ralph-loop.py\"",
22
+ "timeout": 10
23
+ }
24
+ ]
25
+ }
14
26
  ]
15
27
  }
16
28
  }
@@ -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