@lmaksym/agent-mem 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/context.md +24 -0
- package/.claude/skills/agent-mem/SKILL.md +66 -0
- package/.claude/skills/agent-mem/references/branching-merging.md +34 -0
- package/.claude/skills/agent-mem/references/coexistence.md +19 -0
- package/.claude/skills/agent-mem/references/collaboration.md +33 -0
- package/.claude/skills/agent-mem/references/reflection-compaction.md +104 -0
- package/.claude/skills/agent-mem/references/sub-agent-patterns.md +60 -0
- package/LICENSE +21 -0
- package/README.md +235 -0
- package/bin/agent-context.js +95 -0
- package/bin/parse-args.js +85 -0
- package/package.json +58 -0
- package/src/commands/branch.js +57 -0
- package/src/commands/branch.test.js +91 -0
- package/src/commands/branches.js +34 -0
- package/src/commands/commit.js +55 -0
- package/src/commands/compact.js +307 -0
- package/src/commands/compact.test.js +110 -0
- package/src/commands/config.js +47 -0
- package/src/commands/core.test.js +166 -0
- package/src/commands/diff.js +157 -0
- package/src/commands/diff.test.js +64 -0
- package/src/commands/forget.js +77 -0
- package/src/commands/forget.test.js +68 -0
- package/src/commands/help.js +99 -0
- package/src/commands/import.js +83 -0
- package/src/commands/init.js +269 -0
- package/src/commands/init.test.js +80 -0
- package/src/commands/lesson.js +95 -0
- package/src/commands/lesson.test.js +93 -0
- package/src/commands/merge.js +105 -0
- package/src/commands/pin.js +34 -0
- package/src/commands/pull.js +80 -0
- package/src/commands/push.js +80 -0
- package/src/commands/read.js +62 -0
- package/src/commands/reflect.js +328 -0
- package/src/commands/remember.js +95 -0
- package/src/commands/resolve.js +230 -0
- package/src/commands/resolve.test.js +167 -0
- package/src/commands/search.js +70 -0
- package/src/commands/share.js +65 -0
- package/src/commands/snapshot.js +106 -0
- package/src/commands/status.js +37 -0
- package/src/commands/switch.js +31 -0
- package/src/commands/sync.js +328 -0
- package/src/commands/track.js +61 -0
- package/src/commands/unpin.js +28 -0
- package/src/commands/write.js +58 -0
- package/src/core/auto-commit.js +22 -0
- package/src/core/config.js +93 -0
- package/src/core/context-root.js +28 -0
- package/src/core/fs.js +137 -0
- package/src/core/git.js +182 -0
- package/src/core/importers.js +210 -0
- package/src/core/lock.js +62 -0
- package/src/core/reflect-defrag.js +287 -0
- package/src/core/reflect-gather.js +360 -0
- package/src/core/reflect-parse.js +168 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* agent-mem â Context management CLI for AI coding agents
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* agent-mem init Bootstrap context from codebase
|
|
8
|
+
* agent-mem snapshot Get context tree (agent's primary view)
|
|
9
|
+
* agent-mem read <path> Read a specific context file
|
|
10
|
+
* agent-mem write <path> Write/update a context file
|
|
11
|
+
* agent-mem commit [msg] Checkpoint progress
|
|
12
|
+
* agent-mem status Quick status overview
|
|
13
|
+
* agent-mem remember Quick-add to memory
|
|
14
|
+
* agent-mem search Search across context
|
|
15
|
+
* agent-mem branch Create exploration branch
|
|
16
|
+
* agent-mem switch Switch active branch
|
|
17
|
+
* agent-mem merge Merge branch back
|
|
18
|
+
* agent-mem branches List branches
|
|
19
|
+
* agent-mem pin Move file to system/ (always loaded)
|
|
20
|
+
* agent-mem unpin Move file out of system/
|
|
21
|
+
* agent-mem config Show/set configuration
|
|
22
|
+
* agent-mem help Show help
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { resolve } from 'node:path';
|
|
26
|
+
import { parseArgs } from './parse-args.js';
|
|
27
|
+
import { findContextRoot } from '../src/core/context-root.js';
|
|
28
|
+
|
|
29
|
+
const COMMANDS = {
|
|
30
|
+
init: () => import('../src/commands/init.js'),
|
|
31
|
+
snapshot: () => import('../src/commands/snapshot.js'),
|
|
32
|
+
read: () => import('../src/commands/read.js'),
|
|
33
|
+
write: () => import('../src/commands/write.js'),
|
|
34
|
+
commit: () => import('../src/commands/commit.js'),
|
|
35
|
+
status: () => import('../src/commands/status.js'),
|
|
36
|
+
remember: () => import('../src/commands/remember.js'),
|
|
37
|
+
search: () => import('../src/commands/search.js'),
|
|
38
|
+
branch: () => import('../src/commands/branch.js'),
|
|
39
|
+
switch: () => import('../src/commands/switch.js'),
|
|
40
|
+
merge: () => import('../src/commands/merge.js'),
|
|
41
|
+
branches: () => import('../src/commands/branches.js'),
|
|
42
|
+
pin: () => import('../src/commands/pin.js'),
|
|
43
|
+
unpin: () => import('../src/commands/unpin.js'),
|
|
44
|
+
reflect: () => import('../src/commands/reflect.js'),
|
|
45
|
+
compact: () => import('../src/commands/compact.js'),
|
|
46
|
+
resolve: () => import('../src/commands/resolve.js'),
|
|
47
|
+
diff: () => import('../src/commands/diff.js'),
|
|
48
|
+
forget: () => import('../src/commands/forget.js'),
|
|
49
|
+
lesson: () => import('../src/commands/lesson.js'),
|
|
50
|
+
sync: () => import('../src/commands/sync.js'),
|
|
51
|
+
track: () => import('../src/commands/track.js'),
|
|
52
|
+
push: () => import('../src/commands/push.js'),
|
|
53
|
+
pull: () => import('../src/commands/pull.js'),
|
|
54
|
+
share: () => import('../src/commands/share.js'),
|
|
55
|
+
import: () => import('../src/commands/import.js'),
|
|
56
|
+
config: () => import('../src/commands/config.js'),
|
|
57
|
+
help: () => import('../src/commands/help.js'),
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
async function main() {
|
|
61
|
+
const { command, args, flags } = parseArgs(process.argv.slice(2));
|
|
62
|
+
|
|
63
|
+
if (!command || command === 'help' || flags.help || flags.h) {
|
|
64
|
+
const mod = await COMMANDS.help();
|
|
65
|
+
await mod.default({ args, flags });
|
|
66
|
+
process.exit(0);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!(command in COMMANDS)) {
|
|
70
|
+
console.error(`â Unknown command: ${command}`);
|
|
71
|
+
console.error(`Run 'agent-mem help' for available commands.`);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// All commands except init, help, pull, import require an existing .context/
|
|
76
|
+
if (!['init', 'help', 'pull', 'import'].includes(command)) {
|
|
77
|
+
const root = findContextRoot(process.cwd());
|
|
78
|
+
if (!root) {
|
|
79
|
+
console.error(`â No .context/ found. Run 'agent-mem init' first.`);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
flags._contextRoot = root;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
const mod = await COMMANDS[command]();
|
|
87
|
+
await mod.default({ args, flags });
|
|
88
|
+
} catch (err) {
|
|
89
|
+
console.error(`â ${err.message}`);
|
|
90
|
+
if (flags.verbose) console.error(err.stack);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
main();
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal argument parser. Zero deps.
|
|
3
|
+
*
|
|
4
|
+
* parseArgs(["init", "--from-claude", "--verbose"])
|
|
5
|
+
* â { command: "init", args: [], flags: { "from-claude": true, verbose: true } }
|
|
6
|
+
*
|
|
7
|
+
* parseArgs(["read", "memory/decisions.md"])
|
|
8
|
+
* â { command: "read", args: ["memory/decisions.md"], flags: {} }
|
|
9
|
+
*
|
|
10
|
+
* parseArgs(["commit", "implemented auth flow"])
|
|
11
|
+
* â { command: "commit", args: ["implemented", "auth", "flow"], flags: {} }
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export function parseArgs(argv) {
|
|
15
|
+
const flags = {};
|
|
16
|
+
const args = [];
|
|
17
|
+
let command = null;
|
|
18
|
+
|
|
19
|
+
for (let i = 0; i < argv.length; i++) {
|
|
20
|
+
const arg = argv[i];
|
|
21
|
+
|
|
22
|
+
if (!command && !arg.startsWith('-')) {
|
|
23
|
+
command = arg;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (arg.startsWith('--')) {
|
|
28
|
+
const key = arg.slice(2);
|
|
29
|
+
const eqIdx = key.indexOf('=');
|
|
30
|
+
if (eqIdx !== -1) {
|
|
31
|
+
flags[key.slice(0, eqIdx)] = key.slice(eqIdx + 1);
|
|
32
|
+
} else {
|
|
33
|
+
// Boolean-only flags (never consume next arg as value)
|
|
34
|
+
const BOOLEAN_FLAGS = new Set([
|
|
35
|
+
'help',
|
|
36
|
+
'h',
|
|
37
|
+
'verbose',
|
|
38
|
+
'force',
|
|
39
|
+
'deep',
|
|
40
|
+
'compare',
|
|
41
|
+
'landscape',
|
|
42
|
+
'from-claude',
|
|
43
|
+
'from-codex',
|
|
44
|
+
'json',
|
|
45
|
+
'no-fetch',
|
|
46
|
+
'decision',
|
|
47
|
+
'pattern',
|
|
48
|
+
'mistake',
|
|
49
|
+
'note',
|
|
50
|
+
'all',
|
|
51
|
+
'claude',
|
|
52
|
+
'gemini',
|
|
53
|
+
'codex',
|
|
54
|
+
'cursor',
|
|
55
|
+
'windsurf',
|
|
56
|
+
'enable',
|
|
57
|
+
'disable',
|
|
58
|
+
'merge',
|
|
59
|
+
'dry-run',
|
|
60
|
+
'dry-run',
|
|
61
|
+
'compaction',
|
|
62
|
+
'hard',
|
|
63
|
+
]);
|
|
64
|
+
if (BOOLEAN_FLAGS.has(key)) {
|
|
65
|
+
flags[key] = true;
|
|
66
|
+
} else {
|
|
67
|
+
// Check if next arg is a value
|
|
68
|
+
const next = argv[i + 1];
|
|
69
|
+
if (next && !next.startsWith('-')) {
|
|
70
|
+
flags[key] = next;
|
|
71
|
+
i++;
|
|
72
|
+
} else {
|
|
73
|
+
flags[key] = true;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
} else if (arg.startsWith('-') && arg.length === 2) {
|
|
78
|
+
flags[arg.slice(1)] = true;
|
|
79
|
+
} else {
|
|
80
|
+
args.push(arg);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return { command, args, flags };
|
|
85
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lmaksym/agent-mem",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Persistent, git-backed memory for AI coding agents.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"agent-mem": "./bin/agent-context.js",
|
|
8
|
+
"amem": "./bin/agent-context.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node bin/agent-context.js",
|
|
12
|
+
"test": "node --test src/**/*.test.js",
|
|
13
|
+
"format": "prettier --write 'src/**/*.js' 'bin/**/*.js'",
|
|
14
|
+
"format:check": "prettier --check 'src/**/*.js' 'bin/**/*.js'",
|
|
15
|
+
"prepare": "husky"
|
|
16
|
+
},
|
|
17
|
+
"lint-staged": {
|
|
18
|
+
"*.js": "prettier --write"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"ai",
|
|
22
|
+
"agent",
|
|
23
|
+
"context",
|
|
24
|
+
"memory",
|
|
25
|
+
"cli",
|
|
26
|
+
"claude-code",
|
|
27
|
+
"codex",
|
|
28
|
+
"cursor",
|
|
29
|
+
"coding-agent",
|
|
30
|
+
"agent-skills"
|
|
31
|
+
],
|
|
32
|
+
"author": "Maksym Lypivskyi",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/lmaksym/agent-context.git"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/lmaksym/agent-context",
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/lmaksym/agent-context/issues"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=20"
|
|
44
|
+
},
|
|
45
|
+
"files": [
|
|
46
|
+
"bin/",
|
|
47
|
+
"src/",
|
|
48
|
+
".claude/commands/",
|
|
49
|
+
".claude/skills/",
|
|
50
|
+
"LICENSE",
|
|
51
|
+
"README.md"
|
|
52
|
+
],
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"husky": "^9.1.7",
|
|
55
|
+
"lint-staged": "^16.2.7",
|
|
56
|
+
"prettier": "^3.8.1"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { contextDir as getContextDir } from '../core/context-root.js';
|
|
2
|
+
import { writeContextFile, readContextFile } from '../core/fs.js';
|
|
3
|
+
import { readConfig, writeConfig } from '../core/config.js';
|
|
4
|
+
import { existsSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
export default async function branch({ args, flags }) {
|
|
8
|
+
const root = flags._contextRoot;
|
|
9
|
+
const ctxDir = getContextDir(root);
|
|
10
|
+
|
|
11
|
+
if (!args.length) {
|
|
12
|
+
console.error('â Usage: agent-mem branch <name> [purpose]');
|
|
13
|
+
console.error('Example: agent-mem branch try-qdrant "evaluate vector search"');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const name = args[0];
|
|
18
|
+
const purpose = args.slice(1).join(' ') || '';
|
|
19
|
+
const branchDir = join(ctxDir, 'branches', name);
|
|
20
|
+
|
|
21
|
+
if (existsSync(branchDir)) {
|
|
22
|
+
console.error(`â Branch "${name}" already exists.`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
27
|
+
|
|
28
|
+
writeContextFile(
|
|
29
|
+
ctxDir,
|
|
30
|
+
`branches/${name}/purpose.md`,
|
|
31
|
+
[`# Branch: ${name}`, '', purpose || 'Purpose not specified.', '', `Created: ${date}`, ''].join(
|
|
32
|
+
'\n',
|
|
33
|
+
),
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
writeContextFile(
|
|
37
|
+
ctxDir,
|
|
38
|
+
`branches/${name}/commits.md`,
|
|
39
|
+
[`# Commits: ${name}`, '', 'Milestone log for this branch.', ''].join('\n'),
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
writeContextFile(
|
|
43
|
+
ctxDir,
|
|
44
|
+
`branches/${name}/trace.md`,
|
|
45
|
+
[`# Trace: ${name}`, '', 'Fine-grained execution log.', ''].join('\n'),
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
// Switch to new branch
|
|
49
|
+
const config = readConfig(ctxDir);
|
|
50
|
+
config.branch = name;
|
|
51
|
+
writeConfig(ctxDir, config);
|
|
52
|
+
|
|
53
|
+
console.log(`â
BRANCHED: ${name}`);
|
|
54
|
+
if (purpose) console.log(`Purpose: ${purpose}`);
|
|
55
|
+
console.log(`Switched to branch: ${name}`);
|
|
56
|
+
console.log(`Files: branches/${name}/{purpose,commits,trace}.md`);
|
|
57
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { describe, it, before, after } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { mkdtempSync, rmSync, writeFileSync, existsSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { execSync } from 'node:child_process';
|
|
6
|
+
import { tmpdir } from 'node:os';
|
|
7
|
+
|
|
8
|
+
const CLI = join(import.meta.dirname, '../../bin/agent-context.js');
|
|
9
|
+
const run = (args, cwd) =>
|
|
10
|
+
execSync(`node ${CLI} ${args}`, { cwd, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
11
|
+
|
|
12
|
+
describe('branch / switch / merge / branches', () => {
|
|
13
|
+
let dir;
|
|
14
|
+
|
|
15
|
+
before(() => {
|
|
16
|
+
dir = mkdtempSync(join(tmpdir(), 'amem-test-branch-'));
|
|
17
|
+
writeFileSync(join(dir, 'package.json'), '{}');
|
|
18
|
+
run('init', dir);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
after(() => rmSync(dir, { recursive: true, force: true }));
|
|
22
|
+
|
|
23
|
+
it('creates a branch with purpose', () => {
|
|
24
|
+
const out = run('branch try-redis "evaluate Redis for caching"', dir);
|
|
25
|
+
assert.ok(out.includes('BRANCHED: try-redis'));
|
|
26
|
+
assert.ok(existsSync(join(dir, '.context/branches/try-redis/purpose.md')));
|
|
27
|
+
assert.ok(existsSync(join(dir, '.context/branches/try-redis/commits.md')));
|
|
28
|
+
assert.ok(existsSync(join(dir, '.context/branches/try-redis/trace.md')));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('switches config to new branch', () => {
|
|
32
|
+
const config = readFileSync(join(dir, '.context/config.yaml'), 'utf-8');
|
|
33
|
+
assert.ok(config.includes('branch: try-redis'));
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('lists branches', () => {
|
|
37
|
+
const out = run('branches', dir);
|
|
38
|
+
assert.ok(out.includes('try-redis'));
|
|
39
|
+
assert.ok(out.includes('evaluate Redis'));
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('switches back to main', () => {
|
|
43
|
+
const out = run('switch main', dir);
|
|
44
|
+
assert.ok(out.includes('SWITCHED'));
|
|
45
|
+
assert.ok(out.includes('main'));
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('refuses duplicate branch', () => {
|
|
49
|
+
assert.throws(() => run('branch try-redis', dir));
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('merges branch back', () => {
|
|
53
|
+
const out = run('merge try-redis "Redis too complex, using in-memory cache"', dir);
|
|
54
|
+
assert.ok(out.includes('MERGED: try-redis'));
|
|
55
|
+
assert.ok(existsSync(join(dir, '.context/memory/decisions.md')));
|
|
56
|
+
const decisions = readFileSync(join(dir, '.context/memory/decisions.md'), 'utf-8');
|
|
57
|
+
assert.ok(decisions.includes('Redis too complex'));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('switches to main after merge', () => {
|
|
61
|
+
const config = readFileSync(join(dir, '.context/config.yaml'), 'utf-8');
|
|
62
|
+
assert.ok(config.includes('branch: main'));
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('pin / unpin', () => {
|
|
67
|
+
let dir;
|
|
68
|
+
|
|
69
|
+
before(() => {
|
|
70
|
+
dir = mkdtempSync(join(tmpdir(), 'amem-test-pin-'));
|
|
71
|
+
writeFileSync(join(dir, 'package.json'), '{}');
|
|
72
|
+
run('init', dir);
|
|
73
|
+
run('remember --decision "test decision"', dir);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
after(() => rmSync(dir, { recursive: true, force: true }));
|
|
77
|
+
|
|
78
|
+
it('pins a memory file to system/', () => {
|
|
79
|
+
const out = run('pin memory/decisions.md', dir);
|
|
80
|
+
assert.ok(out.includes('PINNED'));
|
|
81
|
+
assert.ok(existsSync(join(dir, '.context/system/decisions.md')));
|
|
82
|
+
assert.ok(!existsSync(join(dir, '.context/memory/decisions.md')));
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('unpins a system file to memory/', () => {
|
|
86
|
+
const out = run('unpin system/decisions.md', dir);
|
|
87
|
+
assert.ok(out.includes('UNPINNED'));
|
|
88
|
+
assert.ok(existsSync(join(dir, '.context/memory/decisions.md')));
|
|
89
|
+
assert.ok(!existsSync(join(dir, '.context/system/decisions.md')));
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { contextDir as getContextDir } from '../core/context-root.js';
|
|
2
|
+
import { listFiles, readContextFile } from '../core/fs.js';
|
|
3
|
+
import { readConfig } from '../core/config.js';
|
|
4
|
+
|
|
5
|
+
export default async function branches({ args, flags }) {
|
|
6
|
+
const root = flags._contextRoot;
|
|
7
|
+
const ctxDir = getContextDir(root);
|
|
8
|
+
const config = readConfig(ctxDir);
|
|
9
|
+
const current = config.branch || 'main';
|
|
10
|
+
|
|
11
|
+
const branchNames = listFiles(ctxDir, 'branches');
|
|
12
|
+
|
|
13
|
+
console.log(`đ BRANCHES:`);
|
|
14
|
+
console.log(` ${current === 'main' ? '* ' : ' '}main (default)`);
|
|
15
|
+
|
|
16
|
+
if (!branchNames.length) {
|
|
17
|
+
console.log('\n No exploration branches yet.');
|
|
18
|
+
console.log(' Create one: agent-mem branch <name> "purpose"');
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
for (const name of branchNames) {
|
|
23
|
+
const purpose = readContextFile(ctxDir, `branches/${name}/purpose.md`);
|
|
24
|
+
const purposeLine =
|
|
25
|
+
purpose
|
|
26
|
+
?.split('\n')
|
|
27
|
+
.find(
|
|
28
|
+
(l) =>
|
|
29
|
+
l.trim() && !l.startsWith('#') && !l.startsWith('---') && !l.startsWith('Created:'),
|
|
30
|
+
) || '';
|
|
31
|
+
const marker = current === name ? '* ' : ' ';
|
|
32
|
+
console.log(` ${marker}${name} â ${purposeLine.trim() || '(no purpose)'}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { contextDir as getContextDir } from '../core/context-root.js';
|
|
2
|
+
import { commitContext, hasChanges, commitCount, commitCountSince } from '../core/git.js';
|
|
3
|
+
import { readConfig } from '../core/config.js';
|
|
4
|
+
import { readContextFile } from '../core/fs.js';
|
|
5
|
+
import { acquireLock } from '../core/lock.js';
|
|
6
|
+
|
|
7
|
+
export default async function commit({ args, flags }) {
|
|
8
|
+
const root = flags._contextRoot;
|
|
9
|
+
const ctxDir = getContextDir(root);
|
|
10
|
+
const releaseLock = acquireLock(root);
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
if (!hasChanges(ctxDir)) {
|
|
14
|
+
console.log('âšī¸ No changes to commit.');
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const message = args.length
|
|
19
|
+
? args.join(' ')
|
|
20
|
+
: `checkpoint ${new Date().toISOString().slice(0, 16)}`;
|
|
21
|
+
const hash = commitContext(ctxDir, message);
|
|
22
|
+
|
|
23
|
+
if (!hash) {
|
|
24
|
+
console.log('âšī¸ No changes to commit.');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const count = commitCount(ctxDir);
|
|
29
|
+
console.log(`â
COMMITTED: "${message}"`);
|
|
30
|
+
console.log(`Commit: ${hash} | Total: ${count}`);
|
|
31
|
+
|
|
32
|
+
// Check if reflection is due (auto-commit trigger)
|
|
33
|
+
const config = readConfig(ctxDir);
|
|
34
|
+
if (config.reflection?.trigger === 'auto-commit') {
|
|
35
|
+
const freq = config.reflection?.frequency || 5;
|
|
36
|
+
// Find last reflection's commit hash
|
|
37
|
+
let sinceRef = null;
|
|
38
|
+
const stateRaw = readContextFile(ctxDir, '.reflect-state.json');
|
|
39
|
+
if (stateRaw) {
|
|
40
|
+
try {
|
|
41
|
+
sinceRef = JSON.parse(stateRaw).last_commit_hash;
|
|
42
|
+
} catch {}
|
|
43
|
+
}
|
|
44
|
+
const sinceLast = sinceRef ? commitCountSince(ctxDir, sinceRef) : count;
|
|
45
|
+
if (sinceLast >= freq) {
|
|
46
|
+
console.log('');
|
|
47
|
+
console.log(
|
|
48
|
+
`đ REFLECTION DUE: ${sinceLast} commits since last reflection. Run: amem reflect`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
} finally {
|
|
53
|
+
releaseLock();
|
|
54
|
+
}
|
|
55
|
+
}
|