Files
claude-adam/adam/scripts/adam-archive.mjs
T
lukaszraczylo 7962e85578 v0.2.0: drop cursor, add source_entries lifecycle, mandate memory frontmatter
Lifecycle redesign:
- Each proposal records source_entries: [<ts>...] in frontmatter listing
  the journal timestamps that fed its cluster.
- After apply/reject, skill calls adam/scripts/adam-archive.mjs which moves
  matching entries from journal.jsonl to journal/actioned-<id>.jsonl.
- Agent reads applied/ + rejected/ frontmatter on each /reflect, builds an
  excluded-timestamps set, skips any leftover already-actioned entries.
- cursor field in state.json is vestigial; agent ignores it.

Effect: journal stays bounded by active observations. Rule changes
re-evaluate the remainder without manual rewind. Race-safer for parallel
sessions on shared state.json (no cursor write contention).

Memory drafting:
- agents/adam.md adds 'Memory drafting protocol' parallel to Skill drafting.
- Memory proposals MUST contain auto-memory frontmatter (name, description,
  type, originSessionId) in '# Proposed change' body.
- Skill enforces frontmatter check at apply time; refuses if missing.

Tests: 18 -> 21. Two new tests for adam-archive happy path + no-op.

Migration: existing applied proposals lack source_entries. Their backing
journal entries archived as a one-time bulk migration; legacy proposals
annotated with migration note.
2026-05-10 04:29:49 +01:00

118 lines
3.3 KiB
JavaScript
Executable File

#!/usr/bin/env node
// Usage: adam-archive.mjs <proposal-path>
// Reads `source_entries` from proposal frontmatter, moves matching journal
// entries from journal.jsonl to journal/actioned-<id>.jsonl. Used by the
// adam-self-improvement skill after each apply/reject so subsequent /reflect
// runs do not re-cluster already-actioned signals.
import { readFileSync, writeFileSync, appendFileSync, mkdirSync, existsSync } from "node:fs";
import { join } from "node:path";
import { homedir } from "node:os";
const ROOT = join(homedir(), ".claude", "adam");
const JOURNAL = join(ROOT, "journal.jsonl");
const JOURNAL_DIR = join(ROOT, "journal");
function parseFrontmatter(content) {
const m = content.match(/^---\n([\s\S]*?)\n---/);
if (!m) return {};
const fm = {};
const lines = m[1].split("\n");
let i = 0;
while (i < lines.length) {
const line = lines[i];
const idx = line.indexOf(":");
if (idx === -1) { i++; continue; }
const key = line.slice(0, idx).trim();
const value = line.slice(idx + 1).trim();
if (key === "source_entries") {
const arr = [];
if (value.startsWith("[") && value.endsWith("]")) {
const inner = value.slice(1, -1)
.split(",")
.map(s => s.trim().replace(/^['"]|['"]$/g, ""));
arr.push(...inner.filter(Boolean));
fm[key] = arr;
i++;
continue;
}
i++;
while (i < lines.length && /^\s*-\s+/.test(lines[i])) {
const item = lines[i].replace(/^\s*-\s+/, "").trim().replace(/^['"]|['"]$/g, "");
if (item) arr.push(item);
i++;
}
fm[key] = arr;
continue;
}
fm[key] = value;
i++;
}
return fm;
}
function main() {
const proposalPath = process.argv[2];
if (!proposalPath) {
console.error("usage: adam-archive.mjs <proposal-path>");
process.exit(2);
}
let proposal;
try {
proposal = readFileSync(proposalPath, "utf8");
} catch (e) {
console.error(`cannot read ${proposalPath}: ${e.message}`);
process.exit(1);
}
const fm = parseFrontmatter(proposal);
const id = fm.id || "unknown";
const sourceEntries = Array.isArray(fm.source_entries) ? fm.source_entries : [];
if (sourceEntries.length === 0) {
console.log(`${id}: no source_entries in frontmatter — nothing to archive`);
return;
}
if (!existsSync(JOURNAL)) {
console.log(`${id}: journal does not exist at ${JOURNAL}`);
return;
}
const lines = readFileSync(JOURNAL, "utf8").split("\n").filter(Boolean);
const tsSet = new Set(sourceEntries);
const matched = [];
const remaining = [];
for (const line of lines) {
try {
const e = JSON.parse(line);
if (e.ts && tsSet.has(e.ts)) {
matched.push(line);
} else {
remaining.push(line);
}
} catch {
remaining.push(line);
}
}
if (matched.length === 0) {
console.log(`${id}: no matching entries in journal (already archived?)`);
return;
}
mkdirSync(JOURNAL_DIR, { recursive: true });
const archivePath = join(JOURNAL_DIR, `actioned-${id}.jsonl`);
appendFileSync(archivePath, matched.join("\n") + "\n");
writeFileSync(JOURNAL, remaining.length ? remaining.join("\n") + "\n" : "");
console.log(`${id}: archived ${matched.length}/${lines.length} entries → ${archivePath}`);
}
try { main(); } catch (e) {
console.error(`error: ${e.message}`);
process.exit(1);
}