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,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
|
+
}
|