@jahanxu/trellis 0.4.1 → 0.5.0

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 (65) hide show
  1. package/dist/configurators/workflow.d.ts.map +1 -1
  2. package/dist/configurators/workflow.js +58 -1
  3. package/dist/configurators/workflow.js.map +1 -1
  4. package/dist/constants/paths.d.ts +17 -0
  5. package/dist/constants/paths.d.ts.map +1 -1
  6. package/dist/constants/paths.js +19 -0
  7. package/dist/constants/paths.js.map +1 -1
  8. package/dist/templates/claude/commands/trellis/handoff.md +90 -387
  9. package/dist/templates/claude/commands/trellis/pick-task.md +74 -444
  10. package/dist/templates/claude/hooks/inject-subagent-context.py +17 -101
  11. package/dist/templates/claude/hooks/ralph-loop.py +1 -0
  12. package/dist/templates/claude/hooks/session-start.py +170 -54
  13. package/dist/templates/iflow/commands/trellis/handoff.md +148 -0
  14. package/dist/templates/iflow/commands/trellis/pick-task.md +145 -0
  15. package/dist/templates/iflow/hooks/inject-subagent-context.py +1 -0
  16. package/dist/templates/iflow/hooks/ralph-loop.py +1 -0
  17. package/dist/templates/iflow/hooks/session-start.py +171 -0
  18. package/dist/templates/markdown/index.d.ts +9 -0
  19. package/dist/templates/markdown/index.d.ts.map +1 -1
  20. package/dist/templates/markdown/index.js +10 -0
  21. package/dist/templates/markdown/index.js.map +1 -1
  22. package/dist/templates/markdown/spec/roles/designer/index.md.txt +57 -0
  23. package/dist/templates/markdown/spec/roles/designer/mock-data-standards.md.txt +63 -0
  24. package/dist/templates/markdown/spec/roles/designer/prototype-guidelines.md.txt +49 -0
  25. package/dist/templates/markdown/spec/roles/frontend-impl/api-integration.md.txt +63 -0
  26. package/dist/templates/markdown/spec/roles/frontend-impl/index.md.txt +57 -0
  27. package/dist/templates/markdown/spec/roles/frontend-impl/prototype-to-production.md.txt +57 -0
  28. package/dist/templates/markdown/spec/roles/pm/index.md.txt +45 -0
  29. package/dist/templates/markdown/spec/roles/pm/prd-template.md.txt +64 -0
  30. package/dist/templates/markdown/spec/roles/pm/requirement-checklist.md.txt +43 -0
  31. package/dist/templates/trellis/index.d.ts +1 -0
  32. package/dist/templates/trellis/index.d.ts.map +1 -1
  33. package/dist/templates/trellis/index.js +2 -0
  34. package/dist/templates/trellis/index.js.map +1 -1
  35. package/dist/templates/trellis/scripts/add_session.py +3 -2
  36. package/dist/templates/trellis/scripts/common/cli_adapter.py +4 -3
  37. package/dist/templates/trellis/scripts/common/developer.py +4 -3
  38. package/dist/templates/trellis/scripts/common/git_context.py +7 -7
  39. package/dist/templates/trellis/scripts/common/paths.py +64 -14
  40. package/dist/templates/trellis/scripts/common/phase.py +2 -2
  41. package/dist/templates/trellis/scripts/common/registry.py +16 -15
  42. package/dist/templates/trellis/scripts/common/task_queue.py +10 -10
  43. package/dist/templates/trellis/scripts/common/task_utils.py +5 -4
  44. package/dist/templates/trellis/scripts/common/worktree.py +8 -7
  45. package/dist/templates/trellis/scripts/pool.py +214 -265
  46. package/dist/templates/trellis/scripts/task.py +3 -116
  47. package/package.json +3 -3
  48. package/dist/templates/claude/commands/trellis/before-role-work.md +0 -364
  49. package/dist/templates/trellis/VERSION +0 -1
  50. package/dist/templates/trellis/deliverables/README.md +0 -51
  51. package/dist/templates/trellis/paths.README.md +0 -277
  52. package/dist/templates/trellis/paths.yaml +0 -41
  53. package/dist/templates/trellis/pool/implementations.json +0 -5
  54. package/dist/templates/trellis/pool/prototypes.json +0 -5
  55. package/dist/templates/trellis/pool/requirements.json +0 -5
  56. package/dist/templates/trellis/scripts/common/project_paths.py +0 -189
  57. package/dist/templates/trellis/scripts/handoff_generator.py +0 -380
  58. package/dist/templates/trellis/spec/roles/designer/index.md +0 -243
  59. package/dist/templates/trellis/spec/roles/designer/mock-data-standards.md +0 -481
  60. package/dist/templates/trellis/spec/roles/designer/prototype-guidelines.md +0 -429
  61. package/dist/templates/trellis/spec/roles/frontend-impl/api-integration.md +0 -565
  62. package/dist/templates/trellis/spec/roles/frontend-impl/index.md +0 -321
  63. package/dist/templates/trellis/spec/roles/frontend-impl/state-management.md +0 -599
  64. package/dist/templates/trellis/spec/roles/pm/index.md +0 -112
  65. package/dist/templates/trellis/spec/roles/pm/prd-template.md +0 -124
@@ -1,38 +1,43 @@
1
1
  #!/usr/bin/env python3
2
2
  # -*- coding: utf-8 -*-
3
3
  """
4
- Task Pool Management Script
5
-
6
- Manages three role-based task pools:
7
- - requirements.json (PM outputs)
8
- - prototypes.json (Designer outputs)
9
- - implementations.json (Frontend outputs)
4
+ Pool Management Script for Three-Role Collaboration Pipeline.
10
5
 
11
6
  Usage:
12
- python3 pool.py add <pool-type> <task-data-json>
13
- python3 pool.py list <pool-type> [--status available|consumed]
14
- python3 pool.py consume <pool-type> <task-id>
15
- python3 pool.py check <pool-type>
7
+ python3 pool.py init [pool-name...] # Create pool JSON files
8
+ python3 pool.py add <pool> <id> <title> <path> [--handoff <path>] # Add deliverable
9
+ python3 pool.py list [pool] # List available items
10
+ python3 pool.py status <pool> <id> # Get item status
11
+ python3 pool.py consume <pool> <id> <consumed-by> # Mark as consumed
16
12
  """
17
13
 
14
+ from __future__ import annotations
15
+
16
+ import sys
17
+
18
+ # IMPORTANT: Force stdout to use UTF-8 on Windows
19
+ # This fixes UnicodeEncodeError when outputting non-ASCII characters
20
+ if sys.platform == "win32":
21
+ import io as _io
22
+ if hasattr(sys.stdout, "reconfigure"):
23
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace") # type: ignore[union-attr]
24
+ elif hasattr(sys.stdout, "detach"):
25
+ sys.stdout = _io.TextIOWrapper(sys.stdout.detach(), encoding="utf-8", errors="replace") # type: ignore[union-attr]
26
+
18
27
  import argparse
19
28
  import json
29
+ import os
20
30
  import sys
21
- from datetime import datetime
31
+ from datetime import datetime, timezone
22
32
  from pathlib import Path
23
- from typing import Optional, Tuple, Dict
24
33
 
25
- # Add parent directory to path for imports
26
- sys.path.insert(0, str(Path(__file__).parent))
27
-
28
- from common.paths import get_repo_root
29
-
30
- # =============================================================================
31
- # Constants
32
- # =============================================================================
34
+ from common.paths import (
35
+ get_repo_root,
36
+ get_developer,
37
+ get_pool_dir,
38
+ get_pool_file,
39
+ )
33
40
 
34
- POOL_DIR = ".trellis/pool"
35
- POOL_TYPES = ["requirements", "prototypes", "implementations"]
36
41
 
37
42
  # =============================================================================
38
43
  # Colors
@@ -48,280 +53,246 @@ class Colors:
48
53
 
49
54
 
50
55
  def colored(text: str, color: str) -> str:
56
+ """Apply color to text."""
51
57
  return f"{color}{text}{Colors.NC}"
52
58
 
53
59
 
54
60
  # =============================================================================
55
- # Helper Functions
61
+ # Constants
56
62
  # =============================================================================
57
63
 
58
- def get_pool_file(pool_type: str, repo_root: Path) -> Path:
59
- """Get pool JSON file path."""
60
- if pool_type not in POOL_TYPES:
61
- raise ValueError(f"Invalid pool type: {pool_type}. Must be one of {POOL_TYPES}")
62
- return repo_root / POOL_DIR / f"{pool_type}.json"
63
-
64
-
65
- def read_pool(pool_file: Path) -> dict:
66
- """Read pool JSON file."""
67
- if not pool_file.exists():
68
- return {"available": [], "consumed": [], "last_updated": None}
69
-
70
- try:
71
- return json.loads(pool_file.read_text(encoding="utf-8"))
72
- except (json.JSONDecodeError, OSError) as e:
73
- print(colored(f"Error reading pool file: {e}", Colors.RED), file=sys.stderr)
74
- return {"available": [], "consumed": [], "last_updated": None}
75
-
76
-
77
- def write_pool_atomic(pool_file: Path, data: dict) -> None:
78
- """Write pool JSON with atomic operation (no complex locking needed).
79
-
80
- Uses temp file + rename for atomicity.
81
- Based on risk analysis: low concurrency risk for most pools.
82
- """
83
- # Update timestamp
84
- data["last_updated"] = datetime.now().isoformat()
85
-
86
- # Write to temp file
87
- tmp_file = pool_file.with_suffix(".tmp")
88
- tmp_file.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8")
64
+ DEFAULT_POOLS = ["requirements", "prototypes", "implementations"]
89
65
 
90
- # Atomic replace
91
- tmp_file.replace(pool_file)
66
+ EMPTY_POOL = {"available": []}
92
67
 
93
68
 
94
- def find_task_in_pool(pool_data: dict, task_id: str) -> Tuple[Optional[str], Optional[dict]]:
95
- """Find task in pool by ID.
96
-
97
- Returns:
98
- (status, task_data) where status is "available" or "consumed"
99
- """
100
- for task in pool_data.get("available", []):
101
- if task.get("id") == task_id:
102
- return ("available", task)
69
+ # =============================================================================
70
+ # Pool I/O (atomic writes)
71
+ # =============================================================================
103
72
 
104
- for task in pool_data.get("consumed", []):
105
- if task.get("id") == task_id:
106
- return ("consumed", task)
73
+ def _read_pool(pool_path: Path) -> dict:
74
+ """Read pool JSON file. Auto-create if missing, backup if corrupted."""
75
+ if not pool_path.is_file():
76
+ return {"available": []}
107
77
 
108
- return (None, None)
78
+ try:
79
+ data = json.loads(pool_path.read_text(encoding="utf-8"))
80
+ if not isinstance(data, dict) or "available" not in data:
81
+ raise ValueError("Invalid pool schema")
82
+ return data
83
+ except (json.JSONDecodeError, ValueError):
84
+ # Backup corrupted file
85
+ bak_path = pool_path.with_suffix(".json.bak")
86
+ try:
87
+ pool_path.rename(bak_path)
88
+ print(colored(f" Warning: Corrupted pool backed up to {bak_path.name}", Colors.YELLOW))
89
+ except OSError:
90
+ pass
91
+ return {"available": []}
92
+
93
+
94
+ def _write_pool(pool_path: Path, data: dict) -> bool:
95
+ """Write pool JSON atomically (write .tmp then os.replace)."""
96
+ tmp_path = pool_path.with_suffix(".json.tmp")
97
+ try:
98
+ pool_path.parent.mkdir(parents=True, exist_ok=True)
99
+ tmp_path.write_text(
100
+ json.dumps(data, indent=2, ensure_ascii=False) + "\n",
101
+ encoding="utf-8",
102
+ )
103
+ os.replace(str(tmp_path), str(pool_path))
104
+ return True
105
+ except (OSError, IOError) as e:
106
+ print(colored(f" Error writing pool: {e}", Colors.RED))
107
+ # Clean up tmp if it exists
108
+ try:
109
+ tmp_path.unlink(missing_ok=True)
110
+ except OSError:
111
+ pass
112
+ return False
109
113
 
110
114
 
111
115
  # =============================================================================
112
- # Command: add
116
+ # Subcommands
113
117
  # =============================================================================
114
118
 
115
- def cmd_add(args: argparse.Namespace) -> int:
116
- """Add task to pool."""
119
+ def cmd_init(args: argparse.Namespace) -> int:
120
+ """Create pool JSON files."""
117
121
  repo_root = get_repo_root()
118
- pool_type = args.pool_type
119
-
120
- # Parse task data
121
- try:
122
- if args.task_data.startswith("{"):
123
- task_data = json.loads(args.task_data)
122
+ pool_dir = get_pool_dir(repo_root)
123
+ pool_dir.mkdir(parents=True, exist_ok=True)
124
+
125
+ pool_names = args.pools if args.pools else DEFAULT_POOLS
126
+
127
+ for name in pool_names:
128
+ pool_path = pool_dir / f"{name}.json"
129
+ if pool_path.is_file():
130
+ print(colored(f" Pool '{name}' already exists, skipping", Colors.YELLOW))
131
+ continue
132
+ if _write_pool(pool_path, EMPTY_POOL):
133
+ print(colored(f" Created pool: {name}", Colors.GREEN))
124
134
  else:
125
- # Load from file
126
- task_file = Path(args.task_data)
127
- if not task_file.exists():
128
- print(colored(f"Error: File not found: {args.task_data}", Colors.RED))
129
- return 1
130
- task_data = json.loads(task_file.read_text(encoding="utf-8"))
131
- except json.JSONDecodeError as e:
132
- print(colored(f"Error: Invalid JSON: {e}", Colors.RED))
133
- return 1
134
-
135
- # Validate required fields
136
- required_fields = ["id", "title", "path", "completed_by", "handoff_doc"]
137
- for field in required_fields:
138
- if field not in task_data:
139
- print(colored(f"Error: Missing required field: {field}", Colors.RED))
135
+ print(colored(f" Failed to create pool: {name}", Colors.RED))
140
136
  return 1
141
137
 
142
- pool_file = get_pool_file(pool_type, repo_root)
143
- pool_data = read_pool(pool_file)
138
+ print(colored("Pool initialization complete.", Colors.GREEN))
139
+ return 0
144
140
 
145
- # Check if task already exists
146
- status, existing = find_task_in_pool(pool_data, task_data["id"])
147
- if status:
148
- print(colored(f"Warning: Task '{task_data['id']}' already exists in {status} list", Colors.YELLOW))
149
- if not args.force:
150
- print("Use --force to overwrite")
151
- return 1
152
141
 
153
- # Remove existing task
154
- if status == "available":
155
- pool_data["available"] = [t for t in pool_data["available"] if t["id"] != task_data["id"]]
156
- else:
157
- pool_data["consumed"] = [t for t in pool_data["consumed"] if t["id"] != task_data["id"]]
142
+ def cmd_add(args: argparse.Namespace) -> int:
143
+ """Add a deliverable to a pool."""
144
+ repo_root = get_repo_root()
145
+ pool_path = get_pool_file(args.pool, repo_root)
146
+
147
+ data = _read_pool(pool_path)
148
+
149
+ # Check for duplicate ID
150
+ for item in data["available"]:
151
+ if item["id"] == args.id:
152
+ print(colored(f" Warning: ID '{args.id}' already exists in pool '{args.pool}', skipping", Colors.YELLOW))
153
+ return 0
154
+
155
+ developer = get_developer(repo_root) or "unknown"
156
+
157
+ entry = {
158
+ "id": args.id,
159
+ "title": args.title,
160
+ "path": args.path,
161
+ "completed_by": developer,
162
+ "completed_at": datetime.now(timezone.utc).isoformat(),
163
+ "handoff_doc": args.handoff if args.handoff else f"{args.path}/HANDOFF.md",
164
+ "status": "available",
165
+ }
158
166
 
159
- # Add to available list
160
- task_data["status"] = "available"
161
- task_data["added_at"] = datetime.now().isoformat()
162
- pool_data["available"].append(task_data)
167
+ data["available"].append(entry)
163
168
 
164
- # Write back
165
- write_pool_atomic(pool_file, pool_data)
169
+ if _write_pool(pool_path, data):
170
+ print(colored(f" Added '{args.id}' to pool '{args.pool}'", Colors.GREEN))
171
+ return 0
172
+ return 1
166
173
 
167
- print(colored(f"✓ Added task '{task_data['id']}' to {pool_type} pool", Colors.GREEN))
168
- print(f" Path: {task_data['path']}")
169
- print(f" HANDOFF: {task_data['handoff_doc']}")
170
174
 
171
- return 0
175
+ def cmd_list(args: argparse.Namespace) -> int:
176
+ """List available items in pools."""
177
+ repo_root = get_repo_root()
178
+ pool_dir = get_pool_dir(repo_root)
172
179
 
180
+ if not pool_dir.is_dir():
181
+ print(colored("No pool directory found. Run 'pool.py init' first.", Colors.YELLOW))
182
+ return 0
173
183
 
174
- # =============================================================================
175
- # Command: list
176
- # =============================================================================
184
+ pools_to_list = [args.pool] if args.pool else [
185
+ p.stem for p in sorted(pool_dir.glob("*.json"))
186
+ ]
177
187
 
178
- def cmd_list(args: argparse.Namespace) -> int:
179
- """List tasks in pool."""
180
- repo_root = get_repo_root()
181
- pool_type = args.pool_type
182
- filter_status = args.status
188
+ if not pools_to_list:
189
+ print(colored("No pools found.", Colors.YELLOW))
190
+ return 0
183
191
 
184
- pool_file = get_pool_file(pool_type, repo_root)
185
- pool_data = read_pool(pool_file)
192
+ for pool_name in pools_to_list:
193
+ pool_path = get_pool_file(pool_name, repo_root)
194
+ data = _read_pool(pool_path)
186
195
 
187
- print(colored(f"=== {pool_type.capitalize()} Pool ===", Colors.BLUE))
188
- print()
196
+ available = [i for i in data["available"] if i.get("status") == "available"]
197
+ consumed = [i for i in data["available"] if i.get("status") == "consumed"]
189
198
 
190
- # Show available tasks
191
- if not filter_status or filter_status == "available":
192
- available = pool_data.get("available", [])
193
- print(colored(f"Available ({len(available)}):", Colors.GREEN))
194
- if available:
195
- for task in available:
196
- print(f" • {colored(task['id'], Colors.CYAN)}: {task['title']}")
197
- print(f" Completed by: {task['completed_by']}")
198
- print(f" Path: {task['path']}")
199
- print()
200
- else:
201
- print(" (none)")
202
- print()
199
+ print(colored(f"\n=== {pool_name} ===", Colors.CYAN))
200
+ print(f" Available: {len(available)} | Consumed: {len(consumed)}")
203
201
 
204
- # Show consumed tasks
205
- if not filter_status or filter_status == "consumed":
206
- consumed = pool_data.get("consumed", [])
207
- print(colored(f"Consumed ({len(consumed)}):", Colors.YELLOW))
202
+ if available:
203
+ print(colored(" Available items:", Colors.GREEN))
204
+ for item in available:
205
+ print(f" - {item['id']}: {item['title']} (by {item.get('completed_by', '?')})")
208
206
  if consumed:
209
- for task in consumed:
210
- print(f" • {colored(task['id'], Colors.CYAN)}: {task['title']}")
211
- print(f" Consumed by: {task.get('consumed_by', 'unknown')}")
212
- print(f" Consumed at: {task.get('consumed_at', 'unknown')}")
213
- print()
214
- else:
215
- print(" (none)")
216
- print()
217
-
218
- # Show summary
219
- last_updated = pool_data.get("last_updated", "never")
220
- print(colored(f"Last updated: {last_updated}", Colors.BLUE))
207
+ print(colored(" Consumed items:", Colors.YELLOW))
208
+ for item in consumed:
209
+ consumed_by = item.get("consumed_by", "?")
210
+ print(f" - {item['id']}: {item['title']} (consumed by {consumed_by})")
221
211
 
212
+ print()
222
213
  return 0
223
214
 
224
215
 
225
- # =============================================================================
226
- # Command: consume
227
- # =============================================================================
228
-
229
- def cmd_consume(args: argparse.Namespace) -> int:
230
- """Mark task as consumed."""
216
+ def cmd_status(args: argparse.Namespace) -> int:
217
+ """Get status of a specific item."""
231
218
  repo_root = get_repo_root()
232
- pool_type = args.pool_type
233
- task_id = args.task_id
234
- consumed_by = args.consumed_by
235
-
236
- pool_file = get_pool_file(pool_type, repo_root)
237
- pool_data = read_pool(pool_file)
238
-
239
- # Find task in available list
240
- task = None
241
- for t in pool_data.get("available", []):
242
- if t["id"] == task_id:
243
- task = t
244
- break
245
-
246
- if not task:
247
- # Check if already consumed
248
- for t in pool_data.get("consumed", []):
249
- if t["id"] == task_id:
250
- print(colored(f"Task '{task_id}' is already consumed", Colors.YELLOW))
251
- print(f" Consumed by: {t.get('consumed_by', 'unknown')}")
252
- print(f" Consumed at: {t.get('consumed_at', 'unknown')}")
253
- return 1
219
+ pool_path = get_pool_file(args.pool, repo_root)
220
+ data = _read_pool(pool_path)
254
221
 
255
- print(colored(f"Error: Task '{task_id}' not found in {pool_type} pool", Colors.RED))
256
- return 1
222
+ for item in data["available"]:
223
+ if item["id"] == args.id:
224
+ print(json.dumps(item, indent=2, ensure_ascii=False))
225
+ return 0
257
226
 
258
- # Move from available to consumed
259
- pool_data["available"] = [t for t in pool_data["available"] if t["id"] != task_id]
227
+ print(colored(f" Item '{args.id}' not found in pool '{args.pool}'", Colors.RED))
228
+ return 1
260
229
 
261
- task["status"] = "consumed"
262
- task["consumed_by"] = consumed_by
263
- task["consumed_at"] = datetime.now().isoformat()
264
230
 
265
- pool_data["consumed"].append(task)
231
+ def cmd_consume(args: argparse.Namespace) -> int:
232
+ """Mark an item as consumed."""
233
+ repo_root = get_repo_root()
234
+ pool_path = get_pool_file(args.pool, repo_root)
235
+ data = _read_pool(pool_path)
236
+
237
+ for item in data["available"]:
238
+ if item["id"] == args.id:
239
+ if item.get("status") == "consumed":
240
+ consumed_by = item.get("consumed_by", "?")
241
+ print(colored(f" Item '{args.id}' already consumed by {consumed_by}", Colors.YELLOW))
242
+ return 1
266
243
 
267
- # Write back
268
- write_pool_atomic(pool_file, pool_data)
244
+ item["status"] = "consumed"
245
+ item["consumed_by"] = args.consumed_by
246
+ item["consumed_at"] = datetime.now(timezone.utc).isoformat()
269
247
 
270
- print(colored(f"✓ Marked task '{task_id}' as consumed", Colors.GREEN))
271
- print(f" Consumed by: {consumed_by}")
248
+ if _write_pool(pool_path, data):
249
+ print(colored(f" Marked '{args.id}' as consumed by {args.consumed_by}", Colors.GREEN))
250
+ return 0
251
+ return 1
272
252
 
273
- return 0
253
+ print(colored(f" Item '{args.id}' not found in pool '{args.pool}'", Colors.RED))
254
+ return 1
274
255
 
275
256
 
276
257
  # =============================================================================
277
- # Command: check
258
+ # CLI Parser
278
259
  # =============================================================================
279
260
 
280
- def cmd_check(args: argparse.Namespace) -> int:
281
- """Check pool integrity."""
282
- repo_root = get_repo_root()
283
- pool_type = args.pool_type
261
+ def build_parser() -> argparse.ArgumentParser:
262
+ """Build argument parser."""
263
+ parser = argparse.ArgumentParser(
264
+ description="Pool management for three-role collaboration pipeline",
265
+ )
266
+ subparsers = parser.add_subparsers(dest="command", help="Available commands")
284
267
 
285
- pool_file = get_pool_file(pool_type, repo_root)
286
- pool_data = read_pool(pool_file)
268
+ # init
269
+ p_init = subparsers.add_parser("init", help="Create pool JSON files")
270
+ p_init.add_argument("pools", nargs="*", help="Pool names (default: requirements, prototypes, implementations)")
287
271
 
288
- print(colored(f"=== Checking {pool_type} Pool ===", Colors.BLUE))
289
- print()
272
+ # add
273
+ p_add = subparsers.add_parser("add", help="Add deliverable to pool")
274
+ p_add.add_argument("pool", help="Pool name (e.g., requirements)")
275
+ p_add.add_argument("id", help="Deliverable ID (e.g., user-login)")
276
+ p_add.add_argument("title", help="Deliverable title")
277
+ p_add.add_argument("path", help="Path to deliverable directory")
278
+ p_add.add_argument("--handoff", help="Path to HANDOFF.md (default: <path>/HANDOFF.md)")
290
279
 
291
- errors = 0
292
- warnings = 0
293
-
294
- # Check available tasks
295
- for task in pool_data.get("available", []):
296
- task_id = task.get("id", "unknown")
297
-
298
- # Check required fields
299
- for field in ["id", "title", "path", "handoff_doc"]:
300
- if field not in task:
301
- print(colored(f"✗ Task '{task_id}': Missing field '{field}'", Colors.RED))
302
- errors += 1
303
-
304
- # Check if path exists
305
- if "path" in task:
306
- path = repo_root / task["path"]
307
- if not path.exists():
308
- print(colored(f"✗ Task '{task_id}': Path not found: {task['path']}", Colors.RED))
309
- errors += 1
310
-
311
- # Check if HANDOFF exists
312
- if "handoff_doc" in task:
313
- handoff = repo_root / task["handoff_doc"]
314
- if not handoff.exists():
315
- print(colored(f"⚠ Task '{task_id}': HANDOFF not found: {task['handoff_doc']}", Colors.YELLOW))
316
- warnings += 1
280
+ # list
281
+ p_list = subparsers.add_parser("list", help="List available items")
282
+ p_list.add_argument("pool", nargs="?", help="Pool name (omit for all)")
317
283
 
318
- print()
319
- if errors == 0 and warnings == 0:
320
- print(colored(" All checks passed", Colors.GREEN))
321
- else:
322
- print(colored(f"✗ {errors} error(s), {warnings} warning(s)", Colors.RED if errors > 0 else Colors.YELLOW))
284
+ # status
285
+ p_status = subparsers.add_parser("status", help="Get item status")
286
+ p_status.add_argument("pool", help="Pool name")
287
+ p_status.add_argument("id", help="Item ID")
323
288
 
324
- return 1 if errors > 0 else 0
289
+ # consume
290
+ p_consume = subparsers.add_parser("consume", help="Mark item as consumed")
291
+ p_consume.add_argument("pool", help="Pool name")
292
+ p_consume.add_argument("id", help="Item ID")
293
+ p_consume.add_argument("consumed_by", help="Consumer identity (e.g., designer-bob)")
294
+
295
+ return parser
325
296
 
326
297
 
327
298
  # =============================================================================
@@ -329,30 +300,7 @@ def cmd_check(args: argparse.Namespace) -> int:
329
300
  # =============================================================================
330
301
 
331
302
  def main() -> int:
332
- parser = argparse.ArgumentParser(description="Task Pool Management")
333
- subparsers = parser.add_subparsers(dest="command", help="Commands")
334
-
335
- # add
336
- p_add = subparsers.add_parser("add", help="Add task to pool")
337
- p_add.add_argument("pool_type", choices=POOL_TYPES, help="Pool type")
338
- p_add.add_argument("task_data", help="Task data JSON or file path")
339
- p_add.add_argument("--force", "-f", action="store_true", help="Overwrite existing task")
340
-
341
- # list
342
- p_list = subparsers.add_parser("list", help="List tasks in pool")
343
- p_list.add_argument("pool_type", choices=POOL_TYPES, help="Pool type")
344
- p_list.add_argument("--status", "-s", choices=["available", "consumed"], help="Filter by status")
345
-
346
- # consume
347
- p_consume = subparsers.add_parser("consume", help="Mark task as consumed")
348
- p_consume.add_argument("pool_type", choices=POOL_TYPES, help="Pool type")
349
- p_consume.add_argument("task_id", help="Task ID")
350
- p_consume.add_argument("consumed_by", help="Developer who consumed the task")
351
-
352
- # check
353
- p_check = subparsers.add_parser("check", help="Check pool integrity")
354
- p_check.add_argument("pool_type", choices=POOL_TYPES, help="Pool type")
355
-
303
+ parser = build_parser()
356
304
  args = parser.parse_args()
357
305
 
358
306
  if not args.command:
@@ -360,10 +308,11 @@ def main() -> int:
360
308
  return 1
361
309
 
362
310
  commands = {
311
+ "init": cmd_init,
363
312
  "add": cmd_add,
364
313
  "list": cmd_list,
314
+ "status": cmd_status,
365
315
  "consume": cmd_consume,
366
- "check": cmd_check,
367
316
  }
368
317
 
369
318
  return commands[args.command](args)