anvil-dev-framework 0.1.6
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 +719 -0
- package/VERSION +1 -0
- package/docs/ANVIL-REPO-IMPLEMENTATION-PLAN.md +441 -0
- package/docs/FIRST-SKILL-TUTORIAL.md +408 -0
- package/docs/INSTALLATION-RETRO-NOTES.md +458 -0
- package/docs/INSTALLATION.md +984 -0
- package/docs/anvil-hud.md +469 -0
- package/docs/anvil-init.md +255 -0
- package/docs/anvil-state.md +210 -0
- package/docs/boris-cherny-ralph-wiggum-insights.md +608 -0
- package/docs/command-reference.md +2022 -0
- package/docs/hooks-tts.md +368 -0
- package/docs/implementation-guide.md +810 -0
- package/docs/linear-github-integration.md +247 -0
- package/docs/local-issues.md +677 -0
- package/docs/patterns/README.md +419 -0
- package/docs/planning-responsibilities.md +139 -0
- package/docs/session-workflow.md +573 -0
- package/docs/simplification-plan-template.md +297 -0
- package/docs/simplification-principles.md +129 -0
- package/docs/specifications/CCS-RALPH-INTEGRATION-DESIGN.md +633 -0
- package/docs/specifications/CCS-RESEARCH-REPORT.md +169 -0
- package/docs/specifications/PLAN-ANV-verification-ralph-wiggum.md +403 -0
- package/docs/specifications/PLAN-parallel-tracks-anvil-memory-ccs.md +494 -0
- package/docs/specifications/SPEC-ANV-VRW/component-01-verify.md +208 -0
- package/docs/specifications/SPEC-ANV-VRW/component-02-stop-gate.md +226 -0
- package/docs/specifications/SPEC-ANV-VRW/component-03-posttooluse.md +209 -0
- package/docs/specifications/SPEC-ANV-VRW/component-04-ralph-wiggum.md +604 -0
- package/docs/specifications/SPEC-ANV-VRW/component-05-atomic-actions.md +311 -0
- package/docs/specifications/SPEC-ANV-VRW/component-06-verify-subagent.md +264 -0
- package/docs/specifications/SPEC-ANV-VRW/component-07-claude-md.md +363 -0
- package/docs/specifications/SPEC-ANV-VRW/index.md +182 -0
- package/docs/specifications/SPEC-ANV-anvil-memory.md +573 -0
- package/docs/specifications/SPEC-ANV-context-checkpoints.md +781 -0
- package/docs/specifications/SPEC-ANV-verification-ralph-wiggum.md +789 -0
- package/docs/sync.md +122 -0
- package/global/CLAUDE.md +140 -0
- package/global/agents/verify-app.md +164 -0
- package/global/commands/anvil-settings.md +527 -0
- package/global/commands/anvil-sync.md +121 -0
- package/global/commands/change.md +197 -0
- package/global/commands/clarify.md +252 -0
- package/global/commands/cleanup.md +292 -0
- package/global/commands/commit-push-pr.md +207 -0
- package/global/commands/decay-review.md +127 -0
- package/global/commands/discover.md +158 -0
- package/global/commands/doc-coverage.md +122 -0
- package/global/commands/evidence.md +307 -0
- package/global/commands/explore.md +121 -0
- package/global/commands/force-exit.md +135 -0
- package/global/commands/handoff.md +191 -0
- package/global/commands/healthcheck.md +302 -0
- package/global/commands/hud.md +84 -0
- package/global/commands/insights.md +319 -0
- package/global/commands/linear-setup.md +184 -0
- package/global/commands/lint-fix.md +198 -0
- package/global/commands/orient.md +510 -0
- package/global/commands/plan.md +228 -0
- package/global/commands/ralph.md +346 -0
- package/global/commands/ready.md +182 -0
- package/global/commands/release.md +305 -0
- package/global/commands/retro.md +96 -0
- package/global/commands/shard.md +166 -0
- package/global/commands/spec.md +227 -0
- package/global/commands/sprint.md +184 -0
- package/global/commands/tasks.md +228 -0
- package/global/commands/test-and-commit.md +151 -0
- package/global/commands/validate.md +132 -0
- package/global/commands/verify.md +251 -0
- package/global/commands/weekly-review.md +156 -0
- package/global/hooks/__pycache__/ralph_context_monitor.cpython-314.pyc +0 -0
- package/global/hooks/__pycache__/statusline_agent_sync.cpython-314.pyc +0 -0
- package/global/hooks/anvil_memory_observe.ts +322 -0
- package/global/hooks/anvil_memory_session.ts +166 -0
- package/global/hooks/anvil_memory_stop.ts +187 -0
- package/global/hooks/parse_transcript.py +116 -0
- package/global/hooks/post_merge_cleanup.sh +132 -0
- package/global/hooks/post_tool_format.sh +215 -0
- package/global/hooks/ralph_context_monitor.py +240 -0
- package/global/hooks/ralph_stop.sh +502 -0
- package/global/hooks/statusline.sh +1110 -0
- package/global/hooks/statusline_agent_sync.py +224 -0
- package/global/hooks/stop_gate.sh +250 -0
- package/global/lib/.claude/anvil-state.json +21 -0
- package/global/lib/__pycache__/agent_registry.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/claim_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/coderabbit_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/config_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/coordination_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/doc_coverage_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/gate_logger.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/github_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/hygiene_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/issue_models.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/issue_provider.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/linear_data_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/linear_provider.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/local_provider.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/quality_service.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/ralph_state.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/state_manager.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/transcript_parser.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/verification_runner.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/verify_iteration.cpython-314.pyc +0 -0
- package/global/lib/__pycache__/verify_subagent.cpython-314.pyc +0 -0
- package/global/lib/agent_registry.py +995 -0
- package/global/lib/anvil-state.sh +435 -0
- package/global/lib/claim_service.py +515 -0
- package/global/lib/coderabbit_service.py +314 -0
- package/global/lib/config_service.py +423 -0
- package/global/lib/coordination_service.py +331 -0
- package/global/lib/doc_coverage_service.py +1305 -0
- package/global/lib/gate_logger.py +316 -0
- package/global/lib/github_service.py +310 -0
- package/global/lib/handoff_generator.py +775 -0
- package/global/lib/hygiene_service.py +712 -0
- package/global/lib/issue_models.py +257 -0
- package/global/lib/issue_provider.py +339 -0
- package/global/lib/linear_data_service.py +210 -0
- package/global/lib/linear_provider.py +987 -0
- package/global/lib/linear_provider.py.backup +671 -0
- package/global/lib/local_provider.py +486 -0
- package/global/lib/orient_fast.py +457 -0
- package/global/lib/quality_service.py +470 -0
- package/global/lib/ralph_prompt_generator.py +563 -0
- package/global/lib/ralph_state.py +1202 -0
- package/global/lib/state_manager.py +417 -0
- package/global/lib/transcript_parser.py +597 -0
- package/global/lib/verification_runner.py +557 -0
- package/global/lib/verify_iteration.py +490 -0
- package/global/lib/verify_subagent.py +250 -0
- package/global/skills/README.md +155 -0
- package/global/skills/quality-gates/SKILL.md +252 -0
- package/global/skills/skill-template/SKILL.md +109 -0
- package/global/skills/testing-strategies/SKILL.md +337 -0
- package/global/templates/CHANGE-template.md +105 -0
- package/global/templates/HANDOFF-template.md +63 -0
- package/global/templates/PLAN-template.md +111 -0
- package/global/templates/SPEC-template.md +93 -0
- package/global/templates/ralph/PROMPT.md.template +89 -0
- package/global/templates/ralph/fix_plan.md.template +31 -0
- package/global/templates/ralph/progress.txt.template +23 -0
- package/global/tests/__pycache__/test_doc_coverage.cpython-314.pyc +0 -0
- package/global/tests/test_doc_coverage.py +520 -0
- package/global/tests/test_issue_models.py +299 -0
- package/global/tests/test_local_provider.py +323 -0
- package/global/tools/README.md +178 -0
- package/global/tools/__pycache__/anvil-hud.cpython-314.pyc +0 -0
- package/global/tools/anvil-hud.py +3622 -0
- package/global/tools/anvil-hud.py.bak +3318 -0
- package/global/tools/anvil-issue.py +432 -0
- package/global/tools/anvil-memory/CLAUDE.md +49 -0
- package/global/tools/anvil-memory/README.md +42 -0
- package/global/tools/anvil-memory/bun.lock +25 -0
- package/global/tools/anvil-memory/bunfig.toml +9 -0
- package/global/tools/anvil-memory/package.json +23 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/context-monitor.test.ts +535 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/edge-cases.test.ts +645 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/fixtures.ts +363 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/index.ts +8 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/integration.test.ts +417 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/prompt-generator.test.ts +571 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/ralph-stop.test.ts +440 -0
- package/global/tools/anvil-memory/src/__tests__/ccs/test-utils.ts +252 -0
- package/global/tools/anvil-memory/src/__tests__/commands.test.ts +657 -0
- package/global/tools/anvil-memory/src/__tests__/db.test.ts +641 -0
- package/global/tools/anvil-memory/src/__tests__/hooks.test.ts +272 -0
- package/global/tools/anvil-memory/src/__tests__/performance.test.ts +427 -0
- package/global/tools/anvil-memory/src/__tests__/test-utils.ts +113 -0
- package/global/tools/anvil-memory/src/commands/checkpoint.ts +197 -0
- package/global/tools/anvil-memory/src/commands/get.ts +115 -0
- package/global/tools/anvil-memory/src/commands/init.ts +94 -0
- package/global/tools/anvil-memory/src/commands/observe.ts +163 -0
- package/global/tools/anvil-memory/src/commands/search.ts +112 -0
- package/global/tools/anvil-memory/src/db.ts +638 -0
- package/global/tools/anvil-memory/src/index.ts +205 -0
- package/global/tools/anvil-memory/src/types.ts +122 -0
- package/global/tools/anvil-memory/tsconfig.json +29 -0
- package/global/tools/ralph-loop.sh +359 -0
- package/package.json +45 -0
- package/scripts/anvil +822 -0
- package/scripts/extract_patterns.py +222 -0
- package/scripts/init-project.sh +541 -0
- package/scripts/install.sh +229 -0
- package/scripts/postinstall.js +41 -0
- package/scripts/rollback.sh +188 -0
- package/scripts/sync.sh +623 -0
- package/scripts/test-statusline.sh +248 -0
- package/scripts/update_claude_md.py +224 -0
- package/scripts/verify.sh +255 -0
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Anvil Issue CLI - Local issue tracking for Anvil Framework.
|
|
4
|
+
|
|
5
|
+
Commands:
|
|
6
|
+
list - List issues (optionally filtered by status)
|
|
7
|
+
create - Create a new issue
|
|
8
|
+
show - Show issue details
|
|
9
|
+
update - Update an existing issue
|
|
10
|
+
move - Change issue status
|
|
11
|
+
assign - Assign issue to an agent
|
|
12
|
+
stats - Show issue statistics
|
|
13
|
+
|
|
14
|
+
Examples:
|
|
15
|
+
anvil-issue list
|
|
16
|
+
anvil-issue list --status todo
|
|
17
|
+
anvil-issue create --title "Fix bug" --priority high
|
|
18
|
+
anvil-issue show LOCAL-001
|
|
19
|
+
anvil-issue move LOCAL-001 --to in_progress
|
|
20
|
+
anvil-issue assign LOCAL-001 --agent agent-xyz
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import argparse
|
|
24
|
+
import sys
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
# Add lib directory to path
|
|
28
|
+
sys.path.insert(0, str(Path(__file__).parent.parent / "lib"))
|
|
29
|
+
|
|
30
|
+
from issue_models import IssueStatus, Priority
|
|
31
|
+
from local_provider import LocalJsonProvider
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_provider() -> LocalJsonProvider:
|
|
35
|
+
"""Get the local provider instance."""
|
|
36
|
+
return LocalJsonProvider()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def format_priority(priority: Priority) -> str:
|
|
40
|
+
"""Format priority with color indicators."""
|
|
41
|
+
icons = {
|
|
42
|
+
Priority.URGENT: "P0",
|
|
43
|
+
Priority.HIGH: "P1",
|
|
44
|
+
Priority.MEDIUM: "P2",
|
|
45
|
+
Priority.LOW: "P3",
|
|
46
|
+
Priority.NONE: "P4"
|
|
47
|
+
}
|
|
48
|
+
return icons.get(priority, "P?")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def format_status(status: IssueStatus) -> str:
|
|
52
|
+
"""Format status for display."""
|
|
53
|
+
icons = {
|
|
54
|
+
IssueStatus.BACKLOG: "[ ]",
|
|
55
|
+
IssueStatus.TODO: "[·]",
|
|
56
|
+
IssueStatus.IN_PROGRESS: "[→]",
|
|
57
|
+
IssueStatus.IN_REVIEW: "[?]",
|
|
58
|
+
IssueStatus.DONE: "[✓]",
|
|
59
|
+
IssueStatus.CANCELLED: "[✗]"
|
|
60
|
+
}
|
|
61
|
+
return icons.get(status, "[?]")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def cmd_list(args):
|
|
65
|
+
"""List issues."""
|
|
66
|
+
provider = get_provider()
|
|
67
|
+
|
|
68
|
+
# Parse status filter
|
|
69
|
+
status_filter = None
|
|
70
|
+
if args.status:
|
|
71
|
+
try:
|
|
72
|
+
status_filter = IssueStatus(args.status.lower().replace("-", "_"))
|
|
73
|
+
except ValueError:
|
|
74
|
+
print(f"Unknown status: {args.status}")
|
|
75
|
+
print(f"Valid: {', '.join(s.value for s in IssueStatus)}")
|
|
76
|
+
return 1
|
|
77
|
+
|
|
78
|
+
issues = provider.list_issues(status=status_filter, limit=args.limit)
|
|
79
|
+
|
|
80
|
+
if not issues:
|
|
81
|
+
print("No issues found.")
|
|
82
|
+
return 0
|
|
83
|
+
|
|
84
|
+
# Print header
|
|
85
|
+
print(f"\n{'ID':<12} {'Pri':<4} {'Status':<12} {'Title':<40}")
|
|
86
|
+
print("-" * 72)
|
|
87
|
+
|
|
88
|
+
for issue in issues:
|
|
89
|
+
status_str = issue.status.value.replace("_", " ").title()
|
|
90
|
+
title = issue.title[:38] + ".." if len(issue.title) > 40 else issue.title
|
|
91
|
+
print(f"{issue.identifier:<12} {format_priority(issue.priority):<4} {status_str:<12} {title:<40}")
|
|
92
|
+
|
|
93
|
+
print(f"\nTotal: {len(issues)} issue(s)")
|
|
94
|
+
return 0
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def cmd_create(args):
|
|
98
|
+
"""Create a new issue."""
|
|
99
|
+
provider = get_provider()
|
|
100
|
+
|
|
101
|
+
# Parse priority
|
|
102
|
+
priority = Priority.MEDIUM
|
|
103
|
+
if args.priority:
|
|
104
|
+
priority_map = {
|
|
105
|
+
"urgent": Priority.URGENT, "p0": Priority.URGENT,
|
|
106
|
+
"high": Priority.HIGH, "p1": Priority.HIGH,
|
|
107
|
+
"medium": Priority.MEDIUM, "p2": Priority.MEDIUM,
|
|
108
|
+
"low": Priority.LOW, "p3": Priority.LOW,
|
|
109
|
+
"none": Priority.NONE, "p4": Priority.NONE
|
|
110
|
+
}
|
|
111
|
+
priority = priority_map.get(args.priority.lower(), Priority.MEDIUM)
|
|
112
|
+
|
|
113
|
+
# Parse labels
|
|
114
|
+
labels = []
|
|
115
|
+
if args.labels:
|
|
116
|
+
labels = [lbl.strip() for lbl in args.labels.split(",")]
|
|
117
|
+
|
|
118
|
+
issue = provider.create_issue(
|
|
119
|
+
title=args.title,
|
|
120
|
+
description=args.description or "",
|
|
121
|
+
priority=priority,
|
|
122
|
+
labels=labels,
|
|
123
|
+
parent_id=args.parent
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
print(f"\nCreated: {issue.identifier}")
|
|
127
|
+
print(f" Title: {issue.title}")
|
|
128
|
+
print(f" Priority: {format_priority(issue.priority)}")
|
|
129
|
+
print(f" Status: {issue.status.value}")
|
|
130
|
+
if issue.labels:
|
|
131
|
+
print(f" Labels: {', '.join(issue.labels)}")
|
|
132
|
+
|
|
133
|
+
return 0
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def cmd_show(args):
|
|
137
|
+
"""Show issue details."""
|
|
138
|
+
provider = get_provider()
|
|
139
|
+
issue = provider.get_issue(args.identifier)
|
|
140
|
+
|
|
141
|
+
if not issue:
|
|
142
|
+
print(f"Issue not found: {args.identifier}")
|
|
143
|
+
return 1
|
|
144
|
+
|
|
145
|
+
print(f"\n{issue.identifier}: {issue.title}")
|
|
146
|
+
print("-" * 50)
|
|
147
|
+
print(f"Status: {issue.status.value.replace('_', ' ').title()}")
|
|
148
|
+
print(f"Priority: {format_priority(issue.priority)} ({issue.priority.to_display()})")
|
|
149
|
+
|
|
150
|
+
if issue.description:
|
|
151
|
+
print(f"\nDescription:\n{issue.description}")
|
|
152
|
+
|
|
153
|
+
if issue.labels:
|
|
154
|
+
print(f"\nLabels: {', '.join(issue.labels)}")
|
|
155
|
+
|
|
156
|
+
if issue.assigned_agent:
|
|
157
|
+
print(f"Assigned: {issue.assigned_agent}")
|
|
158
|
+
|
|
159
|
+
if issue.parent_id:
|
|
160
|
+
print(f"Parent: {issue.parent_id}")
|
|
161
|
+
|
|
162
|
+
print(f"\nCreated: {issue.created_at.strftime('%Y-%m-%d %H:%M') if issue.created_at else 'N/A'}")
|
|
163
|
+
print(f"Updated: {issue.updated_at.strftime('%Y-%m-%d %H:%M') if issue.updated_at else 'N/A'}")
|
|
164
|
+
|
|
165
|
+
if issue.completed_at:
|
|
166
|
+
print(f"Completed: {issue.completed_at.strftime('%Y-%m-%d %H:%M')}")
|
|
167
|
+
|
|
168
|
+
return 0
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def cmd_update(args):
|
|
172
|
+
"""Update an existing issue."""
|
|
173
|
+
provider = get_provider()
|
|
174
|
+
|
|
175
|
+
# Check issue exists
|
|
176
|
+
existing = provider.get_issue(args.identifier)
|
|
177
|
+
if not existing:
|
|
178
|
+
print(f"Issue not found: {args.identifier}")
|
|
179
|
+
return 1
|
|
180
|
+
|
|
181
|
+
# Parse priority if provided
|
|
182
|
+
priority = None
|
|
183
|
+
if args.priority:
|
|
184
|
+
priority_map = {
|
|
185
|
+
"urgent": Priority.URGENT, "p0": Priority.URGENT,
|
|
186
|
+
"high": Priority.HIGH, "p1": Priority.HIGH,
|
|
187
|
+
"medium": Priority.MEDIUM, "p2": Priority.MEDIUM,
|
|
188
|
+
"low": Priority.LOW, "p3": Priority.LOW,
|
|
189
|
+
"none": Priority.NONE, "p4": Priority.NONE
|
|
190
|
+
}
|
|
191
|
+
priority = priority_map.get(args.priority.lower())
|
|
192
|
+
|
|
193
|
+
# Parse labels if provided
|
|
194
|
+
labels = None
|
|
195
|
+
if args.labels:
|
|
196
|
+
labels = [lbl.strip() for lbl in args.labels.split(",")]
|
|
197
|
+
|
|
198
|
+
issue = provider.update_issue(
|
|
199
|
+
identifier=args.identifier,
|
|
200
|
+
title=args.title,
|
|
201
|
+
description=args.description,
|
|
202
|
+
priority=priority,
|
|
203
|
+
labels=labels
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
print(f"\nUpdated: {issue.identifier}")
|
|
207
|
+
print(f" Title: {issue.title}")
|
|
208
|
+
print(f" Priority: {format_priority(issue.priority)}")
|
|
209
|
+
print(f" Status: {issue.status.value}")
|
|
210
|
+
|
|
211
|
+
return 0
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def cmd_move(args):
|
|
215
|
+
"""Change issue status."""
|
|
216
|
+
provider = get_provider()
|
|
217
|
+
|
|
218
|
+
# Check issue exists
|
|
219
|
+
existing = provider.get_issue(args.identifier)
|
|
220
|
+
if not existing:
|
|
221
|
+
print(f"Issue not found: {args.identifier}")
|
|
222
|
+
return 1
|
|
223
|
+
|
|
224
|
+
# Parse target status
|
|
225
|
+
status_map = {
|
|
226
|
+
"backlog": IssueStatus.BACKLOG,
|
|
227
|
+
"todo": IssueStatus.TODO,
|
|
228
|
+
"in-progress": IssueStatus.IN_PROGRESS,
|
|
229
|
+
"in_progress": IssueStatus.IN_PROGRESS,
|
|
230
|
+
"inprogress": IssueStatus.IN_PROGRESS,
|
|
231
|
+
"in-review": IssueStatus.IN_REVIEW,
|
|
232
|
+
"in_review": IssueStatus.IN_REVIEW,
|
|
233
|
+
"inreview": IssueStatus.IN_REVIEW,
|
|
234
|
+
"done": IssueStatus.DONE,
|
|
235
|
+
"cancelled": IssueStatus.CANCELLED,
|
|
236
|
+
"canceled": IssueStatus.CANCELLED
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
target_status = status_map.get(args.to.lower())
|
|
240
|
+
if not target_status:
|
|
241
|
+
print(f"Unknown status: {args.to}")
|
|
242
|
+
print("Valid: backlog, todo, in-progress, in-review, done, cancelled")
|
|
243
|
+
return 1
|
|
244
|
+
|
|
245
|
+
old_status = existing.status.value
|
|
246
|
+
issue = provider.update_issue(args.identifier, status=target_status)
|
|
247
|
+
|
|
248
|
+
print(f"\n{issue.identifier}: {old_status} → {issue.status.value}")
|
|
249
|
+
|
|
250
|
+
return 0
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def cmd_assign(args):
|
|
254
|
+
"""Assign issue to an agent."""
|
|
255
|
+
provider = get_provider()
|
|
256
|
+
|
|
257
|
+
# Check issue exists
|
|
258
|
+
existing = provider.get_issue(args.identifier)
|
|
259
|
+
if not existing:
|
|
260
|
+
print(f"Issue not found: {args.identifier}")
|
|
261
|
+
return 1
|
|
262
|
+
|
|
263
|
+
if args.unassign:
|
|
264
|
+
issue = provider.unassign_agent(args.identifier)
|
|
265
|
+
print(f"\n{issue.identifier}: Unassigned")
|
|
266
|
+
else:
|
|
267
|
+
if not args.agent:
|
|
268
|
+
print("Error: --agent required (or use --unassign)")
|
|
269
|
+
return 1
|
|
270
|
+
issue = provider.assign_to_agent(args.identifier, args.agent)
|
|
271
|
+
print(f"\n{issue.identifier}: Assigned to {args.agent}")
|
|
272
|
+
|
|
273
|
+
return 0
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def cmd_stats(args):
|
|
277
|
+
"""Show issue statistics."""
|
|
278
|
+
provider = get_provider()
|
|
279
|
+
stats = provider.get_statistics()
|
|
280
|
+
|
|
281
|
+
print("\n=== Issue Statistics ===\n")
|
|
282
|
+
print(f"Total Issues: {stats['total']}")
|
|
283
|
+
print(f"Assigned: {stats['assigned']}")
|
|
284
|
+
print(f"Unassigned: {stats['unassigned']}")
|
|
285
|
+
|
|
286
|
+
print("\n--- By Status ---")
|
|
287
|
+
for status, count in sorted(stats['by_status'].items()):
|
|
288
|
+
print(f" {status.replace('_', ' ').title():<15} {count}")
|
|
289
|
+
|
|
290
|
+
print("\n--- By Priority ---")
|
|
291
|
+
for priority, count in sorted(stats['by_priority'].items()):
|
|
292
|
+
print(f" {priority:<15} {count}")
|
|
293
|
+
|
|
294
|
+
return 0
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def cmd_ready(args):
|
|
298
|
+
"""Show issues ready to work on."""
|
|
299
|
+
provider = get_provider()
|
|
300
|
+
issues = provider.get_ready_issues()
|
|
301
|
+
|
|
302
|
+
if not issues:
|
|
303
|
+
print("\nNo issues ready to work on.")
|
|
304
|
+
return 0
|
|
305
|
+
|
|
306
|
+
print("\n=== Ready to Work ===\n")
|
|
307
|
+
print(f"{'ID':<12} {'Pri':<4} {'Title':<50}")
|
|
308
|
+
print("-" * 68)
|
|
309
|
+
|
|
310
|
+
for issue in issues[:args.limit]:
|
|
311
|
+
title = issue.title[:48] + ".." if len(issue.title) > 50 else issue.title
|
|
312
|
+
print(f"{issue.identifier:<12} {format_priority(issue.priority):<4} {title:<50}")
|
|
313
|
+
|
|
314
|
+
print(f"\nShowing {min(len(issues), args.limit)} of {len(issues)} ready issue(s)")
|
|
315
|
+
return 0
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def cmd_delete(args):
|
|
319
|
+
"""Delete an issue."""
|
|
320
|
+
provider = get_provider()
|
|
321
|
+
|
|
322
|
+
# Check issue exists
|
|
323
|
+
existing = provider.get_issue(args.identifier)
|
|
324
|
+
if not existing:
|
|
325
|
+
print(f"Issue not found: {args.identifier}")
|
|
326
|
+
return 1
|
|
327
|
+
|
|
328
|
+
if args.hard:
|
|
329
|
+
success = provider.hard_delete_issue(args.identifier)
|
|
330
|
+
action = "Deleted permanently"
|
|
331
|
+
else:
|
|
332
|
+
success = provider.delete_issue(args.identifier)
|
|
333
|
+
action = "Cancelled"
|
|
334
|
+
|
|
335
|
+
if success:
|
|
336
|
+
print(f"\n{args.identifier}: {action}")
|
|
337
|
+
return 0
|
|
338
|
+
else:
|
|
339
|
+
print(f"\nFailed to delete {args.identifier}")
|
|
340
|
+
return 1
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def main():
|
|
344
|
+
parser = argparse.ArgumentParser(
|
|
345
|
+
description="Anvil Issue CLI - Local issue tracking",
|
|
346
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
347
|
+
epilog="""
|
|
348
|
+
Examples:
|
|
349
|
+
anvil-issue list List all issues
|
|
350
|
+
anvil-issue list --status todo List todo issues
|
|
351
|
+
anvil-issue create --title "Fix bug" Create new issue
|
|
352
|
+
anvil-issue show LOCAL-001 Show issue details
|
|
353
|
+
anvil-issue move LOCAL-001 --to done Change status
|
|
354
|
+
anvil-issue assign LOCAL-001 --agent xyz Assign to agent
|
|
355
|
+
anvil-issue stats Show statistics
|
|
356
|
+
anvil-issue ready Show ready work
|
|
357
|
+
"""
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
subparsers = parser.add_subparsers(dest="command", help="Command to run")
|
|
361
|
+
|
|
362
|
+
# list command
|
|
363
|
+
list_parser = subparsers.add_parser("list", help="List issues")
|
|
364
|
+
list_parser.add_argument("--status", "-s", help="Filter by status")
|
|
365
|
+
list_parser.add_argument("--limit", "-n", type=int, default=50, help="Max issues to show")
|
|
366
|
+
|
|
367
|
+
# create command
|
|
368
|
+
create_parser = subparsers.add_parser("create", help="Create an issue")
|
|
369
|
+
create_parser.add_argument("--title", "-t", required=True, help="Issue title")
|
|
370
|
+
create_parser.add_argument("--description", "-d", help="Issue description")
|
|
371
|
+
create_parser.add_argument("--priority", "-p", help="Priority (urgent/high/medium/low)")
|
|
372
|
+
create_parser.add_argument("--labels", "-l", help="Comma-separated labels")
|
|
373
|
+
create_parser.add_argument("--parent", help="Parent issue identifier")
|
|
374
|
+
|
|
375
|
+
# show command
|
|
376
|
+
show_parser = subparsers.add_parser("show", help="Show issue details")
|
|
377
|
+
show_parser.add_argument("identifier", help="Issue identifier (e.g., LOCAL-001)")
|
|
378
|
+
|
|
379
|
+
# update command
|
|
380
|
+
update_parser = subparsers.add_parser("update", help="Update an issue")
|
|
381
|
+
update_parser.add_argument("identifier", help="Issue identifier")
|
|
382
|
+
update_parser.add_argument("--title", "-t", help="New title")
|
|
383
|
+
update_parser.add_argument("--description", "-d", help="New description")
|
|
384
|
+
update_parser.add_argument("--priority", "-p", help="New priority")
|
|
385
|
+
update_parser.add_argument("--labels", "-l", help="New labels (comma-separated)")
|
|
386
|
+
|
|
387
|
+
# move command
|
|
388
|
+
move_parser = subparsers.add_parser("move", help="Change issue status")
|
|
389
|
+
move_parser.add_argument("identifier", help="Issue identifier")
|
|
390
|
+
move_parser.add_argument("--to", required=True, help="Target status")
|
|
391
|
+
|
|
392
|
+
# assign command
|
|
393
|
+
assign_parser = subparsers.add_parser("assign", help="Assign issue to agent")
|
|
394
|
+
assign_parser.add_argument("identifier", help="Issue identifier")
|
|
395
|
+
assign_parser.add_argument("--agent", "-a", help="Agent ID to assign")
|
|
396
|
+
assign_parser.add_argument("--unassign", "-u", action="store_true", help="Remove assignment")
|
|
397
|
+
|
|
398
|
+
# stats command
|
|
399
|
+
subparsers.add_parser("stats", help="Show statistics")
|
|
400
|
+
|
|
401
|
+
# ready command
|
|
402
|
+
ready_parser = subparsers.add_parser("ready", help="Show ready work")
|
|
403
|
+
ready_parser.add_argument("--limit", "-n", type=int, default=10, help="Max issues")
|
|
404
|
+
|
|
405
|
+
# delete command
|
|
406
|
+
delete_parser = subparsers.add_parser("delete", help="Delete an issue")
|
|
407
|
+
delete_parser.add_argument("identifier", help="Issue identifier")
|
|
408
|
+
delete_parser.add_argument("--hard", action="store_true", help="Permanently delete")
|
|
409
|
+
|
|
410
|
+
args = parser.parse_args()
|
|
411
|
+
|
|
412
|
+
if not args.command:
|
|
413
|
+
parser.print_help()
|
|
414
|
+
return 0
|
|
415
|
+
|
|
416
|
+
commands = {
|
|
417
|
+
"list": cmd_list,
|
|
418
|
+
"create": cmd_create,
|
|
419
|
+
"show": cmd_show,
|
|
420
|
+
"update": cmd_update,
|
|
421
|
+
"move": cmd_move,
|
|
422
|
+
"assign": cmd_assign,
|
|
423
|
+
"stats": cmd_stats,
|
|
424
|
+
"ready": cmd_ready,
|
|
425
|
+
"delete": cmd_delete
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return commands[args.command](args)
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
if __name__ == "__main__":
|
|
432
|
+
sys.exit(main())
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Anvil Memory
|
|
2
|
+
|
|
3
|
+
> Persistent memory system for the Anvil framework.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Anvil Memory provides SQLite-backed persistent storage for observations, sessions, checkpoints, and Ralph iterations. It replaces Claude-Mem with a proprietary solution integrated into the Anvil framework.
|
|
8
|
+
|
|
9
|
+
## Tech Stack
|
|
10
|
+
|
|
11
|
+
- **Runtime**: Bun (use `bun` instead of `node`)
|
|
12
|
+
- **Database**: SQLite via `bun:sqlite`
|
|
13
|
+
- **Language**: TypeScript (strict mode)
|
|
14
|
+
|
|
15
|
+
## Commands
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
bun run dev # Run from source
|
|
19
|
+
bun run build # Compile to dist/anvil-memory
|
|
20
|
+
bun run test # Run tests
|
|
21
|
+
bun run typecheck # Check types
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Architecture
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
src/
|
|
28
|
+
├── index.ts # CLI entry point
|
|
29
|
+
├── types.ts # Type definitions
|
|
30
|
+
├── db.ts # Database operations
|
|
31
|
+
└── commands/ # Command handlers
|
|
32
|
+
├── init.ts
|
|
33
|
+
├── observe.ts
|
|
34
|
+
├── search.ts
|
|
35
|
+
└── ...
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Database
|
|
39
|
+
|
|
40
|
+
- Location: `~/.anvil/memory.db`
|
|
41
|
+
- Schema: See `src/db.ts` for migrations
|
|
42
|
+
- Uses FTS5 for full-text search
|
|
43
|
+
|
|
44
|
+
## Key Patterns
|
|
45
|
+
|
|
46
|
+
1. **Use bun:sqlite** - Don't use better-sqlite3 or other libraries
|
|
47
|
+
2. **FTS5 sync** - Triggers keep FTS tables in sync
|
|
48
|
+
3. **JSON for arrays** - Store concepts/files as JSON arrays
|
|
49
|
+
4. **ISO timestamps** - All timestamps in ISO 8601 format
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Anvil Memory
|
|
2
|
+
|
|
3
|
+
Persistent memory system for the Anvil framework.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
cd global/tools/anvil-memory
|
|
9
|
+
bun install
|
|
10
|
+
bun run build
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Initialize database
|
|
17
|
+
anvil-memory init
|
|
18
|
+
|
|
19
|
+
# Create observation
|
|
20
|
+
anvil-memory observe --type feature --title "Added auth" --content "Details..."
|
|
21
|
+
|
|
22
|
+
# Search
|
|
23
|
+
anvil-memory search "authentication" --limit 10
|
|
24
|
+
|
|
25
|
+
# Get by ID
|
|
26
|
+
anvil-memory get 42
|
|
27
|
+
|
|
28
|
+
# Generate context
|
|
29
|
+
anvil-memory context --limit 20 --format inject
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Development
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
bun run dev # Run from source
|
|
36
|
+
bun run test # Run tests
|
|
37
|
+
bun run typecheck # Type check
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## License
|
|
41
|
+
|
|
42
|
+
MIT
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lockfileVersion": 1,
|
|
3
|
+
"workspaces": {
|
|
4
|
+
"": {
|
|
5
|
+
"name": "anvil-memory",
|
|
6
|
+
"devDependencies": {
|
|
7
|
+
"@types/bun": "latest",
|
|
8
|
+
},
|
|
9
|
+
"peerDependencies": {
|
|
10
|
+
"typescript": "^5",
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
"packages": {
|
|
15
|
+
"@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
|
|
16
|
+
|
|
17
|
+
"@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="],
|
|
18
|
+
|
|
19
|
+
"bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
|
|
20
|
+
|
|
21
|
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
22
|
+
|
|
23
|
+
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "anvil-memory",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Persistent memory system for Anvil framework",
|
|
5
|
+
"module": "src/index.ts",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"private": true,
|
|
8
|
+
"bin": {
|
|
9
|
+
"anvil-memory": "./dist/anvil-memory"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"dev": "bun run src/index.ts",
|
|
13
|
+
"build": "bun build src/index.ts --compile --outfile dist/anvil-memory",
|
|
14
|
+
"test": "bun test",
|
|
15
|
+
"typecheck": "tsc --noEmit"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/bun": "latest"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"typescript": "^5"
|
|
22
|
+
}
|
|
23
|
+
}
|