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.
Files changed (190) hide show
  1. package/README.md +719 -0
  2. package/VERSION +1 -0
  3. package/docs/ANVIL-REPO-IMPLEMENTATION-PLAN.md +441 -0
  4. package/docs/FIRST-SKILL-TUTORIAL.md +408 -0
  5. package/docs/INSTALLATION-RETRO-NOTES.md +458 -0
  6. package/docs/INSTALLATION.md +984 -0
  7. package/docs/anvil-hud.md +469 -0
  8. package/docs/anvil-init.md +255 -0
  9. package/docs/anvil-state.md +210 -0
  10. package/docs/boris-cherny-ralph-wiggum-insights.md +608 -0
  11. package/docs/command-reference.md +2022 -0
  12. package/docs/hooks-tts.md +368 -0
  13. package/docs/implementation-guide.md +810 -0
  14. package/docs/linear-github-integration.md +247 -0
  15. package/docs/local-issues.md +677 -0
  16. package/docs/patterns/README.md +419 -0
  17. package/docs/planning-responsibilities.md +139 -0
  18. package/docs/session-workflow.md +573 -0
  19. package/docs/simplification-plan-template.md +297 -0
  20. package/docs/simplification-principles.md +129 -0
  21. package/docs/specifications/CCS-RALPH-INTEGRATION-DESIGN.md +633 -0
  22. package/docs/specifications/CCS-RESEARCH-REPORT.md +169 -0
  23. package/docs/specifications/PLAN-ANV-verification-ralph-wiggum.md +403 -0
  24. package/docs/specifications/PLAN-parallel-tracks-anvil-memory-ccs.md +494 -0
  25. package/docs/specifications/SPEC-ANV-VRW/component-01-verify.md +208 -0
  26. package/docs/specifications/SPEC-ANV-VRW/component-02-stop-gate.md +226 -0
  27. package/docs/specifications/SPEC-ANV-VRW/component-03-posttooluse.md +209 -0
  28. package/docs/specifications/SPEC-ANV-VRW/component-04-ralph-wiggum.md +604 -0
  29. package/docs/specifications/SPEC-ANV-VRW/component-05-atomic-actions.md +311 -0
  30. package/docs/specifications/SPEC-ANV-VRW/component-06-verify-subagent.md +264 -0
  31. package/docs/specifications/SPEC-ANV-VRW/component-07-claude-md.md +363 -0
  32. package/docs/specifications/SPEC-ANV-VRW/index.md +182 -0
  33. package/docs/specifications/SPEC-ANV-anvil-memory.md +573 -0
  34. package/docs/specifications/SPEC-ANV-context-checkpoints.md +781 -0
  35. package/docs/specifications/SPEC-ANV-verification-ralph-wiggum.md +789 -0
  36. package/docs/sync.md +122 -0
  37. package/global/CLAUDE.md +140 -0
  38. package/global/agents/verify-app.md +164 -0
  39. package/global/commands/anvil-settings.md +527 -0
  40. package/global/commands/anvil-sync.md +121 -0
  41. package/global/commands/change.md +197 -0
  42. package/global/commands/clarify.md +252 -0
  43. package/global/commands/cleanup.md +292 -0
  44. package/global/commands/commit-push-pr.md +207 -0
  45. package/global/commands/decay-review.md +127 -0
  46. package/global/commands/discover.md +158 -0
  47. package/global/commands/doc-coverage.md +122 -0
  48. package/global/commands/evidence.md +307 -0
  49. package/global/commands/explore.md +121 -0
  50. package/global/commands/force-exit.md +135 -0
  51. package/global/commands/handoff.md +191 -0
  52. package/global/commands/healthcheck.md +302 -0
  53. package/global/commands/hud.md +84 -0
  54. package/global/commands/insights.md +319 -0
  55. package/global/commands/linear-setup.md +184 -0
  56. package/global/commands/lint-fix.md +198 -0
  57. package/global/commands/orient.md +510 -0
  58. package/global/commands/plan.md +228 -0
  59. package/global/commands/ralph.md +346 -0
  60. package/global/commands/ready.md +182 -0
  61. package/global/commands/release.md +305 -0
  62. package/global/commands/retro.md +96 -0
  63. package/global/commands/shard.md +166 -0
  64. package/global/commands/spec.md +227 -0
  65. package/global/commands/sprint.md +184 -0
  66. package/global/commands/tasks.md +228 -0
  67. package/global/commands/test-and-commit.md +151 -0
  68. package/global/commands/validate.md +132 -0
  69. package/global/commands/verify.md +251 -0
  70. package/global/commands/weekly-review.md +156 -0
  71. package/global/hooks/__pycache__/ralph_context_monitor.cpython-314.pyc +0 -0
  72. package/global/hooks/__pycache__/statusline_agent_sync.cpython-314.pyc +0 -0
  73. package/global/hooks/anvil_memory_observe.ts +322 -0
  74. package/global/hooks/anvil_memory_session.ts +166 -0
  75. package/global/hooks/anvil_memory_stop.ts +187 -0
  76. package/global/hooks/parse_transcript.py +116 -0
  77. package/global/hooks/post_merge_cleanup.sh +132 -0
  78. package/global/hooks/post_tool_format.sh +215 -0
  79. package/global/hooks/ralph_context_monitor.py +240 -0
  80. package/global/hooks/ralph_stop.sh +502 -0
  81. package/global/hooks/statusline.sh +1110 -0
  82. package/global/hooks/statusline_agent_sync.py +224 -0
  83. package/global/hooks/stop_gate.sh +250 -0
  84. package/global/lib/.claude/anvil-state.json +21 -0
  85. package/global/lib/__pycache__/agent_registry.cpython-314.pyc +0 -0
  86. package/global/lib/__pycache__/claim_service.cpython-314.pyc +0 -0
  87. package/global/lib/__pycache__/coderabbit_service.cpython-314.pyc +0 -0
  88. package/global/lib/__pycache__/config_service.cpython-314.pyc +0 -0
  89. package/global/lib/__pycache__/coordination_service.cpython-314.pyc +0 -0
  90. package/global/lib/__pycache__/doc_coverage_service.cpython-314.pyc +0 -0
  91. package/global/lib/__pycache__/gate_logger.cpython-314.pyc +0 -0
  92. package/global/lib/__pycache__/github_service.cpython-314.pyc +0 -0
  93. package/global/lib/__pycache__/hygiene_service.cpython-314.pyc +0 -0
  94. package/global/lib/__pycache__/issue_models.cpython-314.pyc +0 -0
  95. package/global/lib/__pycache__/issue_provider.cpython-314.pyc +0 -0
  96. package/global/lib/__pycache__/linear_data_service.cpython-314.pyc +0 -0
  97. package/global/lib/__pycache__/linear_provider.cpython-314.pyc +0 -0
  98. package/global/lib/__pycache__/local_provider.cpython-314.pyc +0 -0
  99. package/global/lib/__pycache__/quality_service.cpython-314.pyc +0 -0
  100. package/global/lib/__pycache__/ralph_state.cpython-314.pyc +0 -0
  101. package/global/lib/__pycache__/state_manager.cpython-314.pyc +0 -0
  102. package/global/lib/__pycache__/transcript_parser.cpython-314.pyc +0 -0
  103. package/global/lib/__pycache__/verification_runner.cpython-314.pyc +0 -0
  104. package/global/lib/__pycache__/verify_iteration.cpython-314.pyc +0 -0
  105. package/global/lib/__pycache__/verify_subagent.cpython-314.pyc +0 -0
  106. package/global/lib/agent_registry.py +995 -0
  107. package/global/lib/anvil-state.sh +435 -0
  108. package/global/lib/claim_service.py +515 -0
  109. package/global/lib/coderabbit_service.py +314 -0
  110. package/global/lib/config_service.py +423 -0
  111. package/global/lib/coordination_service.py +331 -0
  112. package/global/lib/doc_coverage_service.py +1305 -0
  113. package/global/lib/gate_logger.py +316 -0
  114. package/global/lib/github_service.py +310 -0
  115. package/global/lib/handoff_generator.py +775 -0
  116. package/global/lib/hygiene_service.py +712 -0
  117. package/global/lib/issue_models.py +257 -0
  118. package/global/lib/issue_provider.py +339 -0
  119. package/global/lib/linear_data_service.py +210 -0
  120. package/global/lib/linear_provider.py +987 -0
  121. package/global/lib/linear_provider.py.backup +671 -0
  122. package/global/lib/local_provider.py +486 -0
  123. package/global/lib/orient_fast.py +457 -0
  124. package/global/lib/quality_service.py +470 -0
  125. package/global/lib/ralph_prompt_generator.py +563 -0
  126. package/global/lib/ralph_state.py +1202 -0
  127. package/global/lib/state_manager.py +417 -0
  128. package/global/lib/transcript_parser.py +597 -0
  129. package/global/lib/verification_runner.py +557 -0
  130. package/global/lib/verify_iteration.py +490 -0
  131. package/global/lib/verify_subagent.py +250 -0
  132. package/global/skills/README.md +155 -0
  133. package/global/skills/quality-gates/SKILL.md +252 -0
  134. package/global/skills/skill-template/SKILL.md +109 -0
  135. package/global/skills/testing-strategies/SKILL.md +337 -0
  136. package/global/templates/CHANGE-template.md +105 -0
  137. package/global/templates/HANDOFF-template.md +63 -0
  138. package/global/templates/PLAN-template.md +111 -0
  139. package/global/templates/SPEC-template.md +93 -0
  140. package/global/templates/ralph/PROMPT.md.template +89 -0
  141. package/global/templates/ralph/fix_plan.md.template +31 -0
  142. package/global/templates/ralph/progress.txt.template +23 -0
  143. package/global/tests/__pycache__/test_doc_coverage.cpython-314.pyc +0 -0
  144. package/global/tests/test_doc_coverage.py +520 -0
  145. package/global/tests/test_issue_models.py +299 -0
  146. package/global/tests/test_local_provider.py +323 -0
  147. package/global/tools/README.md +178 -0
  148. package/global/tools/__pycache__/anvil-hud.cpython-314.pyc +0 -0
  149. package/global/tools/anvil-hud.py +3622 -0
  150. package/global/tools/anvil-hud.py.bak +3318 -0
  151. package/global/tools/anvil-issue.py +432 -0
  152. package/global/tools/anvil-memory/CLAUDE.md +49 -0
  153. package/global/tools/anvil-memory/README.md +42 -0
  154. package/global/tools/anvil-memory/bun.lock +25 -0
  155. package/global/tools/anvil-memory/bunfig.toml +9 -0
  156. package/global/tools/anvil-memory/package.json +23 -0
  157. package/global/tools/anvil-memory/src/__tests__/ccs/context-monitor.test.ts +535 -0
  158. package/global/tools/anvil-memory/src/__tests__/ccs/edge-cases.test.ts +645 -0
  159. package/global/tools/anvil-memory/src/__tests__/ccs/fixtures.ts +363 -0
  160. package/global/tools/anvil-memory/src/__tests__/ccs/index.ts +8 -0
  161. package/global/tools/anvil-memory/src/__tests__/ccs/integration.test.ts +417 -0
  162. package/global/tools/anvil-memory/src/__tests__/ccs/prompt-generator.test.ts +571 -0
  163. package/global/tools/anvil-memory/src/__tests__/ccs/ralph-stop.test.ts +440 -0
  164. package/global/tools/anvil-memory/src/__tests__/ccs/test-utils.ts +252 -0
  165. package/global/tools/anvil-memory/src/__tests__/commands.test.ts +657 -0
  166. package/global/tools/anvil-memory/src/__tests__/db.test.ts +641 -0
  167. package/global/tools/anvil-memory/src/__tests__/hooks.test.ts +272 -0
  168. package/global/tools/anvil-memory/src/__tests__/performance.test.ts +427 -0
  169. package/global/tools/anvil-memory/src/__tests__/test-utils.ts +113 -0
  170. package/global/tools/anvil-memory/src/commands/checkpoint.ts +197 -0
  171. package/global/tools/anvil-memory/src/commands/get.ts +115 -0
  172. package/global/tools/anvil-memory/src/commands/init.ts +94 -0
  173. package/global/tools/anvil-memory/src/commands/observe.ts +163 -0
  174. package/global/tools/anvil-memory/src/commands/search.ts +112 -0
  175. package/global/tools/anvil-memory/src/db.ts +638 -0
  176. package/global/tools/anvil-memory/src/index.ts +205 -0
  177. package/global/tools/anvil-memory/src/types.ts +122 -0
  178. package/global/tools/anvil-memory/tsconfig.json +29 -0
  179. package/global/tools/ralph-loop.sh +359 -0
  180. package/package.json +45 -0
  181. package/scripts/anvil +822 -0
  182. package/scripts/extract_patterns.py +222 -0
  183. package/scripts/init-project.sh +541 -0
  184. package/scripts/install.sh +229 -0
  185. package/scripts/postinstall.js +41 -0
  186. package/scripts/rollback.sh +188 -0
  187. package/scripts/sync.sh +623 -0
  188. package/scripts/test-statusline.sh +248 -0
  189. package/scripts/update_claude_md.py +224 -0
  190. package/scripts/verify.sh +255 -0
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Shared test utilities for Anvil Memory tests
3
+ *
4
+ * Provides common helpers for database cleanup, path generation,
5
+ * and timing measurements used across all test files.
6
+ */
7
+
8
+ import { unlinkSync, existsSync, mkdirSync } from 'fs';
9
+ import { join } from 'path';
10
+ import { tmpdir } from 'os';
11
+
12
+ /**
13
+ * Base directory for all test databases.
14
+ * Uses system temp directory to avoid polluting the project.
15
+ */
16
+ export const TEST_DB_BASE_DIR = join(tmpdir(), 'anvil-memory-tests');
17
+
18
+ /**
19
+ * Generates a unique test database path with timestamp and random suffix.
20
+ * Ensures test isolation by preventing path collisions.
21
+ *
22
+ * @param prefix - Optional prefix for the database filename (default: 'test')
23
+ * @returns Absolute path to a unique test database file
24
+ */
25
+ export function getTestDbPath(prefix = 'test'): string {
26
+ return join(
27
+ TEST_DB_BASE_DIR,
28
+ `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}.db`
29
+ );
30
+ }
31
+
32
+ /**
33
+ * Ensures the test database directory exists.
34
+ * Should be called in beforeAll hooks.
35
+ */
36
+ export function ensureTestDir(): void {
37
+ if (!existsSync(TEST_DB_BASE_DIR)) {
38
+ mkdirSync(TEST_DB_BASE_DIR, { recursive: true });
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Cleans up a SQLite database and its associated WAL/SHM files.
44
+ * Silently ignores errors if files don't exist or can't be deleted.
45
+ *
46
+ * @param path - Path to the database file to clean up
47
+ */
48
+ export function cleanupDb(path: string): void {
49
+ try {
50
+ if (existsSync(path)) unlinkSync(path);
51
+ if (existsSync(`${path}-wal`)) unlinkSync(`${path}-wal`);
52
+ if (existsSync(`${path}-shm`)) unlinkSync(`${path}-shm`);
53
+ } catch {
54
+ // Ignore cleanup errors - files may already be deleted or locked
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Measures the execution time of a synchronous function.
60
+ *
61
+ * @param fn - Function to measure
62
+ * @returns Object containing the function result and execution time in milliseconds
63
+ */
64
+ export function measureTime<T>(fn: () => T): { result: T; timeMs: number } {
65
+ const start = performance.now();
66
+ const result = fn();
67
+ const timeMs = performance.now() - start;
68
+ return { result, timeMs };
69
+ }
70
+
71
+ /**
72
+ * Valid observation types for test data generation.
73
+ */
74
+ export const OBSERVATION_TYPES = [
75
+ 'bugfix',
76
+ 'feature',
77
+ 'refactor',
78
+ 'discovery',
79
+ 'decision',
80
+ 'change',
81
+ 'checkpoint',
82
+ 'ralph_iteration',
83
+ 'handoff',
84
+ 'shard',
85
+ 'linear_sync',
86
+ 'session_request',
87
+ ] as const;
88
+
89
+ /**
90
+ * Returns a random observation type for test data generation.
91
+ */
92
+ export function randomType(): (typeof OBSERVATION_TYPES)[number] {
93
+ return OBSERVATION_TYPES[Math.floor(Math.random() * OBSERVATION_TYPES.length)]!;
94
+ }
95
+
96
+ /**
97
+ * Generates random words for test content.
98
+ *
99
+ * @param count - Number of words to generate
100
+ * @returns Space-separated string of random words
101
+ */
102
+ export function randomWords(count: number): string {
103
+ const words = [
104
+ 'database', 'query', 'index', 'table', 'schema', 'migration',
105
+ 'refactor', 'bugfix', 'feature', 'test', 'performance', 'memory',
106
+ 'cache', 'session', 'observation', 'checkpoint', 'iteration',
107
+ 'component', 'module', 'service', 'handler', 'controller',
108
+ ];
109
+ return Array.from(
110
+ { length: count },
111
+ () => words[Math.floor(Math.random() * words.length)]
112
+ ).join(' ');
113
+ }
@@ -0,0 +1,197 @@
1
+ /**
2
+ * checkpoint command - Record a CCS checkpoint event
3
+ *
4
+ * Usage:
5
+ * anvil-memory checkpoint --level <level> --percent <percent> [options]
6
+ *
7
+ * Options:
8
+ * --level <level> Checkpoint level (L1, L2, L3) (required)
9
+ * --percent <percent> Context percentage at checkpoint (required)
10
+ * --handoff <path> Path to handoff file
11
+ * --session <id> Session ID
12
+ * --task <desc> Current task description (creates linked observation)
13
+ * --progress <desc> Progress description
14
+ * --linear <issue> Linear issue ID (e.g., ANV-123)
15
+ * --path <path> Custom database path
16
+ */
17
+
18
+ import { parseArgs } from 'util';
19
+ import { AnvilMemoryDb, getDefaultDbPath, dbExists } from '../db.ts';
20
+ import type { Checkpoint, CommandResult } from '../types.ts';
21
+
22
+ // Valid checkpoint levels
23
+ const VALID_LEVELS = ['L1', 'L2', 'L3'] as const;
24
+ type CheckpointLevel = (typeof VALID_LEVELS)[number];
25
+
26
+ interface CheckpointOptions {
27
+ level?: string;
28
+ percent?: string;
29
+ handoff?: string;
30
+ session?: string;
31
+ task?: string;
32
+ progress?: string;
33
+ linear?: string;
34
+ path?: string;
35
+ }
36
+
37
+ export function parseCheckpointArgs(args: string[]): CheckpointOptions {
38
+ // Filter out --json which is handled by main
39
+ const filteredArgs = args.filter((arg) => arg !== '--json');
40
+
41
+ const { values } = parseArgs({
42
+ args: filteredArgs,
43
+ options: {
44
+ level: { type: 'string', short: 'l' },
45
+ percent: { type: 'string', short: 'p' },
46
+ handoff: { type: 'string', short: 'h' },
47
+ session: { type: 'string', short: 's' },
48
+ task: { type: 'string', short: 't' },
49
+ progress: { type: 'string' },
50
+ linear: { type: 'string', short: 'i' },
51
+ path: { type: 'string' },
52
+ },
53
+ allowPositionals: false,
54
+ strict: false,
55
+ });
56
+
57
+ return {
58
+ level: typeof values.level === 'string' ? values.level : undefined,
59
+ percent: typeof values.percent === 'string' ? values.percent : undefined,
60
+ handoff: typeof values.handoff === 'string' ? values.handoff : undefined,
61
+ session: typeof values.session === 'string' ? values.session : undefined,
62
+ task: typeof values.task === 'string' ? values.task : undefined,
63
+ progress: typeof values.progress === 'string' ? values.progress : undefined,
64
+ linear: typeof values.linear === 'string' ? values.linear : undefined,
65
+ path: typeof values.path === 'string' ? values.path : undefined,
66
+ };
67
+ }
68
+
69
+ export async function handleCheckpoint(args: string[]): Promise<CommandResult> {
70
+ const options = parseCheckpointArgs(args);
71
+
72
+ // Validate required fields
73
+ if (!options.level) {
74
+ return {
75
+ success: false,
76
+ error: 'Missing required option: --level (L1, L2, or L3)',
77
+ };
78
+ }
79
+
80
+ if (!options.percent) {
81
+ return {
82
+ success: false,
83
+ error: 'Missing required option: --percent',
84
+ };
85
+ }
86
+
87
+ // Validate level
88
+ const level = options.level.toUpperCase();
89
+ if (!VALID_LEVELS.includes(level as CheckpointLevel)) {
90
+ return {
91
+ success: false,
92
+ error: `Invalid level '${options.level}'. Valid levels: ${VALID_LEVELS.join(', ')}`,
93
+ };
94
+ }
95
+
96
+ // Validate percent
97
+ const percent = parseInt(options.percent, 10);
98
+ if (isNaN(percent) || percent < 0 || percent > 100) {
99
+ return {
100
+ success: false,
101
+ error: `Invalid percent '${options.percent}'. Must be 0-100.`,
102
+ };
103
+ }
104
+
105
+ // Check database exists
106
+ const dbPath = options.path ?? getDefaultDbPath();
107
+ if (!dbExists(dbPath)) {
108
+ return {
109
+ success: false,
110
+ error: `Database not found at ${dbPath}. Run 'anvil-memory init' first.`,
111
+ };
112
+ }
113
+
114
+ try {
115
+ const db = new AnvilMemoryDb(dbPath);
116
+
117
+ // Parse session_id once and validate
118
+ const parsedSessionId = options.session ? Number.parseInt(options.session, 10) : undefined;
119
+ const sessionId = parsedSessionId !== undefined && Number.isFinite(parsedSessionId)
120
+ ? parsedSessionId
121
+ : undefined;
122
+
123
+ // Use single timestamp for consistency
124
+ const now = new Date().toISOString();
125
+
126
+ // Create checkpoint record
127
+ const checkpoint = db.createCheckpoint({
128
+ level: level as Checkpoint['level'],
129
+ context_percent: percent,
130
+ handoff_file: options.handoff,
131
+ session_id: sessionId,
132
+ timestamp: now,
133
+ });
134
+
135
+ // Optionally create linked observation with task context
136
+ let observation = null;
137
+ if (options.task) {
138
+ const content = buildCheckpointContent(level as CheckpointLevel, percent, options);
139
+ observation = db.createObservation({
140
+ type: 'checkpoint',
141
+ title: `${level} Checkpoint at ${percent}%`,
142
+ content,
143
+ linear_issue: options.linear,
144
+ session_id: sessionId,
145
+ timestamp: now,
146
+ });
147
+ }
148
+
149
+ db.close();
150
+
151
+ return {
152
+ success: true,
153
+ message: `Recorded ${level} checkpoint at ${percent}%${observation ? ` with observation #${observation.id}` : ''}`,
154
+ data: {
155
+ checkpoint,
156
+ observation,
157
+ },
158
+ };
159
+ } catch (error) {
160
+ return {
161
+ success: false,
162
+ error: error instanceof Error ? error.message : String(error),
163
+ };
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Build checkpoint observation content
169
+ */
170
+ function buildCheckpointContent(
171
+ level: CheckpointLevel,
172
+ percent: number,
173
+ options: CheckpointOptions
174
+ ): string {
175
+ const lines: string[] = [];
176
+
177
+ lines.push(`Context checkpoint triggered at ${level} threshold (${percent}%).`);
178
+ lines.push('');
179
+
180
+ if (options.task) {
181
+ lines.push(`**Current Task**: ${options.task}`);
182
+ }
183
+
184
+ if (options.progress) {
185
+ lines.push(`**Progress**: ${options.progress}`);
186
+ }
187
+
188
+ if (options.handoff) {
189
+ lines.push(`**Handoff File**: ${options.handoff}`);
190
+ }
191
+
192
+ if (options.linear) {
193
+ lines.push(`**Linear Issue**: ${options.linear}`);
194
+ }
195
+
196
+ return lines.join('\n');
197
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * get command - Fetch observation/session/checkpoint by ID
3
+ *
4
+ * Usage:
5
+ * anvil-memory get <id> [options]
6
+ *
7
+ * Options:
8
+ * --type <type> Record type: observation, session, checkpoint (default: observation)
9
+ */
10
+
11
+ import { parseArgs } from 'util';
12
+ import { AnvilMemoryDb, getDefaultDbPath, dbExists } from '../db.ts';
13
+ import type { CommandResult } from '../types.ts';
14
+
15
+ type RecordType = 'observation' | 'session' | 'checkpoint';
16
+
17
+ interface GetOptions {
18
+ id: number;
19
+ recordType: RecordType;
20
+ path?: string;
21
+ }
22
+
23
+ export function parseGetArgs(args: string[]): GetOptions | { error: string } {
24
+ // Filter out --json which is handled by main
25
+ const filteredArgs = args.filter((arg) => arg !== '--json');
26
+
27
+ const { values, positionals } = parseArgs({
28
+ args: filteredArgs,
29
+ options: {
30
+ type: { type: 'string', short: 't' },
31
+ path: { type: 'string' },
32
+ },
33
+ allowPositionals: true,
34
+ strict: false,
35
+ });
36
+
37
+ // Parse ID from positional
38
+ if (positionals.length === 0) {
39
+ return { error: 'Missing ID. Usage: anvil-memory get <id>' };
40
+ }
41
+
42
+ const id = parseInt(positionals[0] ?? '', 10);
43
+ if (isNaN(id)) {
44
+ return { error: `Invalid ID: ${positionals[0]}` };
45
+ }
46
+
47
+ // Validate record type
48
+ const typeVal = typeof values.type === 'string' ? values.type : 'observation';
49
+ const recordType = typeVal as RecordType;
50
+ if (!['observation', 'session', 'checkpoint'].includes(recordType)) {
51
+ return { error: `Invalid type '${recordType}'. Valid types: observation, session, checkpoint` };
52
+ }
53
+
54
+ return {
55
+ id,
56
+ recordType,
57
+ path: typeof values.path === 'string' ? values.path : undefined,
58
+ };
59
+ }
60
+
61
+ export async function handleGet(args: string[]): Promise<CommandResult> {
62
+ const options = parseGetArgs(args);
63
+
64
+ if ('error' in options) {
65
+ return {
66
+ success: false,
67
+ error: options.error,
68
+ };
69
+ }
70
+
71
+ // Check database exists
72
+ const dbPath = options.path ?? getDefaultDbPath();
73
+ if (!dbExists(dbPath)) {
74
+ return {
75
+ success: false,
76
+ error: `Database not found at ${dbPath}. Run 'anvil-memory init' first.`,
77
+ };
78
+ }
79
+
80
+ try {
81
+ const db = new AnvilMemoryDb(dbPath);
82
+ let result: unknown = null;
83
+
84
+ switch (options.recordType) {
85
+ case 'observation':
86
+ result = db.getObservation(options.id);
87
+ break;
88
+ case 'session':
89
+ result = db.getSession(options.id);
90
+ break;
91
+ case 'checkpoint':
92
+ result = db.getCheckpoint(options.id);
93
+ break;
94
+ }
95
+
96
+ db.close();
97
+
98
+ if (!result) {
99
+ return {
100
+ success: false,
101
+ error: `${options.recordType} #${options.id} not found`,
102
+ };
103
+ }
104
+
105
+ return {
106
+ success: true,
107
+ data: result,
108
+ };
109
+ } catch (error) {
110
+ return {
111
+ success: false,
112
+ error: error instanceof Error ? error.message : String(error),
113
+ };
114
+ }
115
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * init command - Initialize the Anvil Memory database
3
+ *
4
+ * Usage:
5
+ * anvil-memory init [--path <path>]
6
+ *
7
+ * Options:
8
+ * --path <path> Custom database path (default: ~/.anvil/memory.db)
9
+ * --force Reinitialize even if database exists
10
+ */
11
+
12
+ import { parseArgs } from 'util';
13
+ import { AnvilMemoryDb, getDefaultDbPath, dbExists } from '../db.ts';
14
+ import type { CommandResult } from '../types.ts';
15
+
16
+ interface InitOptions {
17
+ path?: string;
18
+ force?: boolean;
19
+ }
20
+
21
+ export function parseInitArgs(args: string[]): InitOptions {
22
+ // Filter out --json which is handled by main
23
+ const filteredArgs = args.filter((arg) => arg !== '--json');
24
+
25
+ const { values } = parseArgs({
26
+ args: filteredArgs,
27
+ options: {
28
+ path: { type: 'string', short: 'p' },
29
+ force: { type: 'boolean', short: 'f' },
30
+ },
31
+ allowPositionals: true,
32
+ strict: false, // Allow unknown options
33
+ });
34
+
35
+ return {
36
+ path: typeof values.path === 'string' ? values.path : undefined,
37
+ force: values.force === true,
38
+ };
39
+ }
40
+
41
+ export async function handleInit(args: string[]): Promise<CommandResult> {
42
+ const options = parseInitArgs(args);
43
+ const dbPath = options.path ?? getDefaultDbPath();
44
+
45
+ // Check if database already exists
46
+ if (dbExists(dbPath) && !options.force) {
47
+ const db = new AnvilMemoryDb(dbPath);
48
+ const config = db.getConfig();
49
+ db.close();
50
+
51
+ return {
52
+ success: true,
53
+ message: `Database already exists at ${dbPath} (version ${config.version})`,
54
+ data: config,
55
+ };
56
+ }
57
+
58
+ try {
59
+ // Initialize database
60
+ const db = new AnvilMemoryDb(dbPath);
61
+ const result = db.init();
62
+
63
+ // Run integrity check
64
+ const integrity = db.integrityCheck();
65
+
66
+ if (!integrity.ok) {
67
+ db.close();
68
+ return {
69
+ success: false,
70
+ error: `Database integrity check failed: ${integrity.errors.join(', ')}`,
71
+ };
72
+ }
73
+
74
+ const config = db.getConfig();
75
+ db.close();
76
+
77
+ return {
78
+ success: true,
79
+ message: result.created
80
+ ? `Created Anvil Memory database at ${dbPath} (version ${config.version})`
81
+ : `Initialized database at ${dbPath} (version ${config.version})`,
82
+ data: {
83
+ ...config,
84
+ created: result.created,
85
+ integrityOk: integrity.ok,
86
+ },
87
+ };
88
+ } catch (error) {
89
+ return {
90
+ success: false,
91
+ error: error instanceof Error ? error.message : String(error),
92
+ };
93
+ }
94
+ }
@@ -0,0 +1,163 @@
1
+ /**
2
+ * observe command - Create a new observation
3
+ *
4
+ * Usage:
5
+ * anvil-memory observe --type <type> --title <title> [options]
6
+ *
7
+ * Options:
8
+ * --type <type> Observation type (required)
9
+ * --title <title> Short title (required)
10
+ * --content <content> Full content/description
11
+ * --project <project> Project identifier
12
+ * --files <files> Comma-separated file paths
13
+ * --concepts <tags> Comma-separated concept tags
14
+ * --tokens <n> Work tokens spent
15
+ * --session <id> Session ID
16
+ * --issue <id> Linear issue ID
17
+ */
18
+
19
+ import { parseArgs } from 'util';
20
+ import { AnvilMemoryDb, getDefaultDbPath, dbExists } from '../db.ts';
21
+ import type { CommandResult, ObservationType } from '../types.ts';
22
+
23
+ // Valid observation types
24
+ const VALID_TYPES: ObservationType[] = [
25
+ 'bugfix',
26
+ 'feature',
27
+ 'refactor',
28
+ 'discovery',
29
+ 'decision',
30
+ 'change',
31
+ 'checkpoint',
32
+ 'ralph_iteration',
33
+ 'handoff',
34
+ 'shard',
35
+ 'linear_sync',
36
+ 'session_request',
37
+ ];
38
+
39
+ interface ObserveOptions {
40
+ type?: string;
41
+ title?: string;
42
+ content?: string;
43
+ project?: string;
44
+ files?: string;
45
+ concepts?: string;
46
+ tokens?: string;
47
+ session?: string;
48
+ issue?: string;
49
+ path?: string;
50
+ }
51
+
52
+ export function parseObserveArgs(args: string[]): ObserveOptions {
53
+ // Filter out --json which is handled by main
54
+ const filteredArgs = args.filter((arg) => arg !== '--json');
55
+
56
+ const { values, positionals } = parseArgs({
57
+ args: filteredArgs,
58
+ options: {
59
+ type: { type: 'string', short: 't' },
60
+ title: { type: 'string' },
61
+ content: { type: 'string', short: 'c' },
62
+ project: { type: 'string', short: 'p' },
63
+ files: { type: 'string', short: 'f' },
64
+ concepts: { type: 'string' },
65
+ tokens: { type: 'string' },
66
+ session: { type: 'string', short: 's' },
67
+ issue: { type: 'string', short: 'i' },
68
+ path: { type: 'string' },
69
+ },
70
+ allowPositionals: true,
71
+ strict: false,
72
+ });
73
+
74
+ // Allow content as positional argument
75
+ const contentVal = values.content;
76
+ const content = typeof contentVal === 'string'
77
+ ? contentVal
78
+ : (positionals.join(' ') || undefined);
79
+
80
+ return {
81
+ type: typeof values.type === 'string' ? values.type : undefined,
82
+ title: typeof values.title === 'string' ? values.title : undefined,
83
+ content,
84
+ project: typeof values.project === 'string' ? values.project : undefined,
85
+ files: typeof values.files === 'string' ? values.files : undefined,
86
+ concepts: typeof values.concepts === 'string' ? values.concepts : undefined,
87
+ tokens: typeof values.tokens === 'string' ? values.tokens : undefined,
88
+ session: typeof values.session === 'string' ? values.session : undefined,
89
+ issue: typeof values.issue === 'string' ? values.issue : undefined,
90
+ path: typeof values.path === 'string' ? values.path : undefined,
91
+ };
92
+ }
93
+
94
+ export async function handleObserve(args: string[]): Promise<CommandResult> {
95
+ const options = parseObserveArgs(args);
96
+
97
+ // Validate required fields
98
+ if (!options.type) {
99
+ return {
100
+ success: false,
101
+ error: 'Missing required option: --type',
102
+ };
103
+ }
104
+
105
+ if (!options.title) {
106
+ return {
107
+ success: false,
108
+ error: 'Missing required option: --title',
109
+ };
110
+ }
111
+
112
+ // Validate type
113
+ if (!VALID_TYPES.includes(options.type as ObservationType)) {
114
+ return {
115
+ success: false,
116
+ error: `Invalid type '${options.type}'. Valid types: ${VALID_TYPES.join(', ')}`,
117
+ };
118
+ }
119
+
120
+ // Check database exists
121
+ const dbPath = options.path ?? getDefaultDbPath();
122
+ if (!dbExists(dbPath)) {
123
+ return {
124
+ success: false,
125
+ error: `Database not found at ${dbPath}. Run 'anvil-memory init' first.`,
126
+ };
127
+ }
128
+
129
+ try {
130
+ const db = new AnvilMemoryDb(dbPath);
131
+
132
+ // Parse arrays
133
+ const files = options.files?.split(',').map((f) => f.trim()).filter(Boolean);
134
+ const concepts = options.concepts?.split(',').map((c) => c.trim()).filter(Boolean);
135
+
136
+ // Create observation
137
+ const observation = db.createObservation({
138
+ type: options.type as ObservationType,
139
+ title: options.title,
140
+ content: options.content ?? '',
141
+ project: options.project,
142
+ files,
143
+ concepts,
144
+ work_tokens: options.tokens ? parseInt(options.tokens, 10) : undefined,
145
+ session_id: options.session ? parseInt(options.session, 10) : undefined,
146
+ linear_issue: options.issue,
147
+ timestamp: new Date().toISOString(),
148
+ });
149
+
150
+ db.close();
151
+
152
+ return {
153
+ success: true,
154
+ message: `Created observation #${observation.id}: ${observation.title}`,
155
+ data: observation,
156
+ };
157
+ } catch (error) {
158
+ return {
159
+ success: false,
160
+ error: error instanceof Error ? error.message : String(error),
161
+ };
162
+ }
163
+ }