2 Commits

Author SHA1 Message Date
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
lukaszraczylo 2b91db6bf3 rubric: lower single-session struggle threshold to >=1 entry
The hook emits struggle signals only after crossing internal thresholds
(3 retries, 8 tools no-prompt, 4 edits to one file, 2 build failures, etc.).
Each journal entry is therefore meaningful evidence on its own. Old rule
required >=3 entries within single session, which the once-per-thing
emission design rarely produces. New rule: >=1 struggle entry qualifies
for proposal at +2 weight (cross-session bonus does not stack).

Auto-apply still requires cross_session_evidence; single-session-only
proposals always queue for review.
2026-05-10 03:08:02 +01:00
6 changed files with 262 additions and 33 deletions
+15 -6
View File
@@ -36,15 +36,16 @@ LLM coding sessions reveal repeated friction the moment you stop and look. ADAM
├── skills/adam-self-improvement/SKILL.md # /reflect protocol ├── skills/adam-self-improvement/SKILL.md # /reflect protocol
├── commands/reflect.md # /reflect slash command ├── commands/reflect.md # /reflect slash command
└── adam/ └── adam/
├── journal.jsonl # append-only signal log ├── journal.jsonl # append-only signal log (active observations)
├── journal/ # rotated daily logs (>5 MB threshold) ├── journal/ # rotated daily logs + actioned-<id>.jsonl per applied/rejected proposal
├── state.json # cursor + per-session counters ├── state.json # per-session counters (cursor field is vestigial as of v0.2.0)
├── usage.json # skill/agent invocation tallies ├── usage.json # skill/agent invocation tallies + payload visibility counters
├── proposals/ # queued, awaiting review ├── proposals/ # queued, awaiting review
├── applied/ # approved + auto-applied archive ├── applied/ # approved + auto-applied archive
├── rejected/ # rejected (with reason) ├── rejected/ # rejected (with reason)
├── trash/ # soft-deleted artifacts (recoverable) ├── trash/ # soft-deleted artifacts (recoverable)
── tests/run-tests.sh # 18 verification tests ── scripts/ # adam-archive.mjs (called by skill on apply/reject)
└── tests/run-tests.sh # 21 verification tests
``` ```
## Install ## Install
@@ -71,7 +72,7 @@ After install:
``` ```
Sum: Sum:
+2 Signal repeated ≥3× across ≥2 sessions +2 Signal repeated ≥3× across ≥2 sessions
+2 Struggle signal repeated3× within a single session (does not stack with above) +2 Struggle signal appearing1× within a single session (does not stack)
+2 Transcript contains positive endorsement near related action +2 Transcript contains positive endorsement near related action
+1 Multi-axis cluster (≥2 distinct struggle types in same session) +1 Multi-axis cluster (≥2 distinct struggle types in same session)
-1 Type-bias penalty (≥3 rejections, applied:rejected <1:2) -1 Type-bias penalty (≥3 rejections, applied:rejected <1:2)
@@ -88,6 +89,14 @@ auto_apply_eligible requires ALL:
cross_session_evidence == true (single-session-only proposals always queue) cross_session_evidence == true (single-session-only proposals always queue)
``` ```
## Lifecycle: how proposals become permanent
Every proposal records the journal entry timestamps that fed its cluster (`source_entries` in frontmatter). When you apply or reject a proposal, the skill calls `adam/scripts/adam-archive.mjs` which moves matching entries from `journal.jsonl` to `journal/actioned-<id>.jsonl`. Effects:
- The `journal.jsonl` stays bounded by **active** observations only.
- The next `/reflect` reads applied/ + rejected/ frontmatter, builds an excluded-timestamps set, and skips any leftover journal entries that were already actioned.
- Rule changes (e.g. lowering a threshold) immediately re-evaluate the remaining active observations — no manual cursor rewind needed.
## What it will not do ## What it will not do
- No background LLM spend. The analyst runs only when you invoke `/reflect`. - No background LLM spend. The analyst runs only when you invoke `/reflect`.
+117
View File
@@ -0,0 +1,117 @@
#!/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);
}
+64
View File
@@ -215,6 +215,70 @@ else
echo " PASS: build_loop correctly ignored non-build command"; PASS=$((PASS+1)) echo " PASS: build_loop correctly ignored non-build command"; PASS=$((PASS+1))
fi fi
# --- Test 17: adam-archive moves matching entries to actioned file ---
echo "Test 17: adam-archive moves matching journal entries"
ARCHIVE="$HOME/.claude/adam/scripts/adam-archive.mjs"
reset_state
rm -f "$ROOT/journal/actioned-test-archive-001.jsonl"
cat > "$ROOT/journal.jsonl" <<EOF
{"ts":"2026-01-01T00:00:00Z","session":"sX","type":"correction"}
{"ts":"2026-01-02T00:00:00Z","session":"sX","type":"correction"}
{"ts":"2026-01-03T00:00:00Z","session":"sX","type":"dead_end"}
EOF
mkdir -p /tmp/adam-test-17
cat > /tmp/adam-test-17/proposal.md <<EOF
---
id: test-archive-001
type: memory
target: /tmp/test
confidence: 5
blast_radius: low
auto_apply_eligible: false
status: applied
source_entries:
- "2026-01-01T00:00:00Z"
- "2026-01-02T00:00:00Z"
---
# Why
test
EOF
node "$ARCHIVE" /tmp/adam-test-17/proposal.md >/dev/null 2>&1 || true
remaining=$(wc -l < "$ROOT/journal.jsonl" | tr -d ' ')
archived=$(wc -l < "$ROOT/journal/actioned-test-archive-001.jsonl" 2>/dev/null | tr -d ' ' || echo 0)
if [ "$remaining" = "1" ] && [ "$archived" = "2" ]; then
echo " PASS: archive moved 2 matching, kept 1 unmatched"; PASS=$((PASS+1))
else
echo " FAIL: expected 1 remaining + 2 archived, got $remaining + $archived"; FAIL=$((FAIL+1))
fi
rm -rf /tmp/adam-test-17 "$ROOT/journal/actioned-test-archive-001.jsonl"
# --- Test 18: adam-archive no-op when source_entries missing ---
echo "Test 18: adam-archive no-op when source_entries missing"
reset_state
echo '{"ts":"2026-01-01T00:00:00Z","type":"correction"}' > "$ROOT/journal.jsonl"
mkdir -p /tmp/adam-test-18
cat > /tmp/adam-test-18/proposal.md <<EOF
---
id: test-noop-002
type: memory
---
# Why
no source_entries
EOF
node "$ARCHIVE" /tmp/adam-test-18/proposal.md >/dev/null 2>&1 || true
if [ -f "$ROOT/journal/actioned-test-noop-002.jsonl" ]; then
echo " FAIL: archive file created when no source_entries"; FAIL=$((FAIL+1))
else
echo " PASS: no archive file created"; PASS=$((PASS+1))
fi
remaining=$(wc -l < "$ROOT/journal.jsonl" | tr -d ' ')
if [ "$remaining" = "1" ]; then
echo " PASS: journal unchanged"; PASS=$((PASS+1))
else
echo " FAIL: journal modified ($remaining lines, expected 1)"; FAIL=$((FAIL+1))
fi
rm -rf /tmp/adam-test-18
echo echo
echo "Results: $PASS passed, $FAIL failed" echo "Results: $PASS passed, $FAIL failed"
[ "$FAIL" = "0" ] [ "$FAIL" = "0" ]
+56 -23
View File
@@ -46,17 +46,18 @@ The hook emits these `type` values into the journal:
## Process ## Process
1. Read `state.json``cursor` (number of journal lines already processed). 1. **Build feedback context** (run once per `/reflect`):
2. Read `journal.jsonl`. New observations = lines after `cursor`. a. List `rejected_dir/` filenames. Parse each frontmatter `source_entries` (if present), `# Why` and `# Reason` sections.
3. If 0 new lines, emit punch list `{"new":0}` and stop. b. List `applied_dir/` filenames. Parse each frontmatter `type`, `target`, `source_entries`. Tally `applied_by_type[type]`.
4. **Build feedback context** (run once per `/reflect`): c. Compute the **excluded-timestamps set**: union of all `source_entries` arrays across `applied_dir/` + `rejected_dir/`. Journal entries with these `ts` values have already been actioned and MUST NOT be re-clustered.
a. List `rejected_dir/` filenames. Parse each `# Why` and `# Reason` sections. Build a set of rejected ideas (token-tokenized for similarity matching). d. Build the **rejected-ideas set** (token-tokenized `# Why` content) for fuzzy fallback matching when a new cluster topic resembles a rejected one but doesn't share `source_entries` (handles legacy proposals without `source_entries`).
b. List `applied_dir/` filenames. Parse frontmatter `type` and `target`. Tally `applied_by_type[type]` and `applied_by_target[basename(target)]`. e. Compute **type biases**:
c. From these, compute **type biases**:
- Types with applied:rejected ratio >2:1 (over ≥3 total): neutral, no bonus. - Types with applied:rejected ratio >2:1 (over ≥3 total): neutral, no bonus.
- Types with applied:rejected ratio <1:2 (over ≥3 rejections): **-1 confidence penalty**, recorded in proposal `# Why` as "type-bias-penalty: <reason>". - Types with applied:rejected ratio <1:2 (over ≥3 rejections): **-1 confidence penalty**, recorded in proposal `# Why` as "type-bias-penalty: <reason>".
5. Cluster new observations: 2. Read `journal.jsonl`. Filter out entries whose `ts` is in the excluded-timestamps set. The result = **active observations**.
- `correction`: tokenize phrase (drop stopwords, keep content tokens). Phrases sharing ≥2 content tokens collapse into one cluster — regardless of `prev_tool` or `cwd`. Record distinct cwds in cluster (used for CLAUDE.md eligibility). 3. If 0 active observations, emit punch list `{"new":0}` and stop.
4. Cluster active observations:
- `correction`: tokenize phrase (drop stopwords, keep content tokens). Phrases sharing ≥2 content tokens collapse into one cluster — regardless of `prev_tool` or `cwd`. Record distinct cwds (used for CLAUDE.md eligibility).
- `retry_loop`: cluster by `tool`. - `retry_loop`: cluster by `tool`.
- `weak_agent`: cluster by `subagent_type`. - `weak_agent`: cluster by `subagent_type`.
- `tool_error_loop`: cluster by `fp`. - `tool_error_loop`: cluster by `fp`.
@@ -64,26 +65,26 @@ The hook emits these `type` values into the journal:
- `edit_churn`: cluster by file basename pattern (e.g. `*.test.ts`). - `edit_churn`: cluster by file basename pattern (e.g. `*.test.ts`).
- `build_loop`: cluster by `session`. - `build_loop`: cluster by `session`.
- `subagent_dispatch_pattern`: cluster by `subagent_type`. - `subagent_dispatch_pattern`: cluster by `subagent_type`.
6. **Multi-axis correlation**: for each session that produced ≥2 distinct struggle types (`tool_error_loop`, `dead_end`, `weak_agent`, `retry_loop`, `edit_churn`, `build_loop`), tag clusters from that session as `multi_axis: true`. This grants +1 confidence at scoring. 5. **Multi-axis correlation**: for each session that produced ≥2 distinct struggle types (`tool_error_loop`, `dead_end`, `weak_agent`, `retry_loop`, `edit_churn`, `build_loop`), tag clusters from that session as `multi_axis: true`. This grants +1 confidence at scoring.
7. For each cluster qualifying under the rubric — ≥3× across ≥2 sessions, OR ≥3× within a single session for struggle types, OR (for `correction`) ≥3 occurrences across ≥2 cwds: 6. For each cluster qualifying under the rubric — ≥3 occurrences across ≥2 sessions, OR (for struggle types) ≥1 entry within a single session, OR (for `correction`) ≥3 occurrences across ≥2 cwds:
a. If cluster topic matches a rejected idea (≥2 token overlap with rejection's `# Why`), skip with reason `"rejected-similar"`. a. If cluster topic matches a rejected idea via the rejected-ideas fuzzy set (≥2 token overlap with rejection's `# Why`), skip with reason `"rejected-similar"`.
b. Pull ~20 messages of transcript context from `transcripts_root` to enrich. Never read full transcripts. b. Pull ~20 messages of transcript context from `transcripts_root` to enrich. Never read full transcripts.
c. **Solution synthesis** (when type would be `skill_new` AND cluster qualifies for proposal): pull additional ~30 messages of transcript window around the friction events (~50 messages total). Extract: c. **Solution synthesis** (when candidate type is `skill_new` AND cluster qualifies): pull additional ~30 messages around friction events (~50 messages total). Extract:
- Concrete trigger phrases the user says verbatim. - Concrete trigger phrases the user says verbatim.
- Tools / files involved. - Tools / files involved.
- Successful resolution patterns later in transcript (positive endorsement). - Successful resolution patterns later in transcript (positive endorsement).
- Counterexamples (false-positive triggers to exclude). - Counterexamples (false-positive triggers to exclude).
d. **Skill overlap check** (skill_new candidates only): see "Skill overlap rule" below. If overlap qualifies, switch type to `skill_edit` targeting the matched SKILL.md. d. **Skill overlap check** (`skill_new` only): see "Skill overlap rule". If overlap qualifies, switch type to `skill_edit` targeting matched SKILL.md.
e. **Draft full content**: e. **Draft full content**:
- `skill_new`: draft the complete SKILL.md per "Skill drafting protocol" below. `# Proposed change` contains the full file body. - `skill_new`: complete SKILL.md per "Skill drafting protocol".
- `skill_edit`: draft an append-only unified diff per "Skill overlap rule". - `skill_edit`: append-only unified diff per "Skill overlap rule".
- `memory`: draft full memory file content (frontmatter + body). - `memory`: complete memory file per "Memory drafting protocol".
- Other types: per existing rules (unified diff or full content). - Other: per existing rules (unified diff or full content).
f. Score against rubric → `confidence`, `blast_radius`, `cross_session_evidence`, `multi_axis`, `auto_apply_eligible`. f. Score against rubric → `confidence`, `blast_radius`, `cross_session_evidence`, `multi_axis`, `auto_apply_eligible`.
g. Apply feedback bias (step 4c) and multi-axis bonus. g. Apply feedback bias (step 1e) and multi-axis bonus.
h. Emit proposal file to `proposals_dir/`. h. **Record `source_entries`**: list every journal entry timestamp that fed this cluster. Goes in proposal frontmatter as a YAML block-form array (one `- "<ts>"` per line). The skill consumes this on apply/reject to archive matching entries out of `journal.jsonl` and into `journal/actioned-<id>.jsonl`.
8. Update `cursor` in `state.json` to new line count. i. Emit proposal file to `proposals_dir/`.
9. Emit punch list to stdout (last message): `{"new":N, "high_confidence":[...], "queued":[...], "skipped":[...]}`. 7. Emit punch list to stdout (last message): `{"new":N, "high_confidence":[...], "queued":[...], "skipped":[...]}`. The `cursor` field in `state.json` is vestigial as of v0.2.0 — do not read or write it.
## Skill overlap rule ## Skill overlap rule
@@ -143,11 +144,39 @@ When the main thread applies a `skill_new` proposal:
2. Writes the `# Proposed change` body to `<slug>/SKILL.md`. 2. Writes the `# Proposed change` body to `<slug>/SKILL.md`.
3. Tells the user: "skill `<slug>` written. Activates immediately on next user turn (CC v2.1.0+ auto-hot-reload)." 3. Tells the user: "skill `<slug>` written. Activates immediately on next user turn (CC v2.1.0+ auto-hot-reload)."
## Memory drafting protocol (for `memory` proposals)
Every `memory` proposal's `# Proposed change` section MUST contain the COMPLETE memory file body — frontmatter + content — that will be written to the target path under `~/.claude/projects/<encoded-home>/memory/<slug>.md`.
Required structure:
```markdown
---
name: <human-readable name, ≤80 chars>
description: <one-line description used to decide future relevance — be specific, ≤200 chars>
type: user | feedback | project | reference
originSessionId: <session_id from journal entries that fed this cluster>
---
<Body content per type, see CLAUDE.md memory schema:
- feedback: lead with the rule, then **Why:** line, then **How to apply:** line.
- project: lead with fact/decision, then **Why:** and **How to apply:** lines.
- user: brief description of role/preference/knowledge.
- reference: pointer to external system + what's there.>
```
Constraints:
- Frontmatter fields `name`, `description`, `type` are **required**. Skill enforces this at apply time.
- `originSessionId` is required — must be a `session` value from one of the cluster's journal entries.
- ≤50 LOC of body content. Surgical.
- Slug (used in `target` path filename) must not collide with any existing memory file.
- For `type=feedback` and `type=project`, body MUST contain `**Why:**` and `**How to apply:**` lines (CLAUDE.md memory schema).
## Confidence rubric (deterministic — do NOT vibe) ## Confidence rubric (deterministic — do NOT vibe)
Sum: Sum:
- Signal repeated ≥3× across ≥2 sessions: **+2** - Signal repeated ≥3× across ≥2 sessions: **+2**
- Struggle signal (`tool_error_loop`, `dead_end`, `weak_agent`, `retry_loop`, `edit_churn`, `build_loop`) repeated3× within a single session: **+2** *(does not stack with the cross-session bonus — pick whichever applies, never both)* - Struggle signal (`tool_error_loop`, `dead_end`, `weak_agent`, `retry_loop`, `edit_churn`, `build_loop`) appearing1× within a single session: **+2** *(each struggle entry already represents a hook-side threshold crossing — e.g. 8 tools without a prompt, 3 same-args retries, 4 edits to one file. Treat each entry as one piece of evidence. Does not stack with the cross-session bonus.)*
- Transcript contains positive endorsement (`yes`, `exactly`, `do that`, `keep doing`) within 2 messages of related action: **+2** - Transcript contains positive endorsement (`yes`, `exactly`, `do that`, `keep doing`) within 2 messages of related action: **+2**
- Multi-axis cluster (≥2 distinct struggle types in same session): **+1** - Multi-axis cluster (≥2 distinct struggle types in same session): **+1**
- Type-bias penalty from feedback loop (≥3 rejections, applied:rejected ratio <1:2 for this `type`): **-1** - Type-bias penalty from feedback loop (≥3 rejections, applied:rejected ratio <1:2 for this `type`): **-1**
@@ -210,6 +239,10 @@ cross_session_evidence: true | false
multi_axis: true | false multi_axis: true | false
auto_apply_eligible: true | false auto_apply_eligible: true | false
status: queued status: queued
source_entries:
- "<journal entry ts that fed this cluster>"
- "<another ts>"
- "..."
--- ---
# Why # Why
+3 -1
View File
@@ -24,6 +24,7 @@ mkdir -p \
"$DEST/adam/rejected" \ "$DEST/adam/rejected" \
"$DEST/adam/trash" \ "$DEST/adam/trash" \
"$DEST/adam/journal" \ "$DEST/adam/journal" \
"$DEST/adam/scripts" \
"$DEST/adam/tests/fixtures" "$DEST/adam/tests/fixtures"
cp "$SRC/hooks/adam-observe.mjs" "$DEST/hooks/" cp "$SRC/hooks/adam-observe.mjs" "$DEST/hooks/"
@@ -31,6 +32,7 @@ cp "$SRC/hooks/adam-nudge.mjs" "$DEST/hoo
cp "$SRC/agents/adam.md" "$DEST/agents/" cp "$SRC/agents/adam.md" "$DEST/agents/"
cp "$SRC/skills/adam-self-improvement/SKILL.md" "$DEST/skills/adam-self-improvement/" cp "$SRC/skills/adam-self-improvement/SKILL.md" "$DEST/skills/adam-self-improvement/"
cp "$SRC/commands/reflect.md" "$DEST/commands/" cp "$SRC/commands/reflect.md" "$DEST/commands/"
cp "$SRC/adam/scripts/adam-archive.mjs" "$DEST/adam/scripts/"
cp "$SRC/adam/tests/run-tests.sh" "$DEST/adam/tests/" cp "$SRC/adam/tests/run-tests.sh" "$DEST/adam/tests/"
cp "$SRC/adam/tests/fixtures/seed-corrections.jsonl" "$DEST/adam/tests/fixtures/" cp "$SRC/adam/tests/fixtures/seed-corrections.jsonl" "$DEST/adam/tests/fixtures/"
@@ -41,7 +43,7 @@ cp "$SRC/adam/tests/fixtures/seed-corrections.jsonl" "$DEST/ada
echo " files installed." echo " files installed."
echo echo
echo " next steps:" echo " next steps:"
echo " 1. bash $DEST/adam/tests/run-tests.sh # must show: 18 passed, 0 failed" echo " 1. bash $DEST/adam/tests/run-tests.sh # must show: 21 passed, 0 failed"
echo " 2. merge settings.json.example into $DEST/settings.json" echo " 2. merge settings.json.example into $DEST/settings.json"
echo " 3. start a fresh Claude Code session, then run /reflect" echo " 3. start a fresh Claude Code session, then run /reflect"
echo echo
+7 -3
View File
@@ -44,9 +44,10 @@ For each id in `high_confidence`:
- Verify in front of the user: print `id`, `target`, `confidence`, `blast_radius`, `cross_session_evidence`, `auto_apply_eligible`. - Verify in front of the user: print `id`, `target`, `confidence`, `blast_radius`, `cross_session_evidence`, `auto_apply_eligible`.
- Apply the change: - Apply the change:
- **For `skill_new`**: `mkdir -p ~/.claude/skills/<slug>/`, then `Write` the proposal's `# Proposed change` body to `~/.claude/skills/<slug>/SKILL.md`. After write, print: "skill `<slug>` written to `~/.claude/skills/<slug>/SKILL.md` — activates immediately — Claude Code v2.1.0+ auto-hot-reloads user-level skills, no restart needed." - **For `skill_new`**: `mkdir -p ~/.claude/skills/<slug>/`, then `Write` the proposal's `# Proposed change` body to `~/.claude/skills/<slug>/SKILL.md`. After write, print: "skill `<slug>` written to `~/.claude/skills/<slug>/SKILL.md` — activates immediately — Claude Code v2.1.0+ auto-hot-reloads user-level skills, no restart needed."
- **For `memory`**: `Write` the proposal's `# Proposed change` body to the path in `target` (under `~/.claude/projects/<encoded-home>/memory/`, where `<encoded-home>` is the user's home dir with `/` replaced by `-`, e.g. `-Users-alice` on macOS). Then update `MEMORY.md` index with a one-line pointer. - **For `memory`**: `Write` the proposal's `# Proposed change` body (which MUST include the auto-memory frontmatter — see "Memory drafting protocol" in `agents/adam.md`) to the path in `target` (under `~/.claude/projects/<encoded-home>/memory/`, where `<encoded-home>` is the user's home dir with `/` replaced by `-`, e.g. `-Users-alice` on macOS). Then update `MEMORY.md` index with a one-line pointer.
- **For other types under auto-apply**: apply via Write/Edit per `# Proposed change`. (Note: only `memory` and `skill_new` qualify for auto-apply per the rubric.) - **For other types under auto-apply**: apply via Write/Edit per `# Proposed change`. (Note: only `memory` and `skill_new` qualify for auto-apply per the rubric.)
- Move proposal to `~/.claude/adam/applied/<UTC-ts>-<id>.md`. - Move proposal to `~/.claude/adam/applied/<UTC-ts>-<id>.md`.
- **Archive consumed journal entries**: `node ~/.claude/adam/scripts/adam-archive.mjs ~/.claude/adam/applied/<UTC-ts>-<id>.md` — moves entries listed in proposal's `source_entries` from `journal.jsonl` to `journal/actioned-<id>.jsonl` so subsequent `/reflect` runs do not re-cluster them.
Print: `auto-applied N proposals: [ids]`. Print: `auto-applied N proposals: [ids]`.
@@ -61,10 +62,11 @@ c. On **approve**:
- For `deletion`: `mkdir -p ~/.claude/adam/trash/<ts>` then `mv` the artifact into it. Print restoration command. - For `deletion`: `mkdir -p ~/.claude/adam/trash/<ts>` then `mv` the artifact into it. Print restoration command.
- For `skill_new`: `mkdir -p ~/.claude/skills/<slug>/`, then write `# Proposed change` body to `<slug>/SKILL.md`. Tell user: "skill `<slug>` written — activates immediately (CC v2.1.0+ auto-hot-reload)." - For `skill_new`: `mkdir -p ~/.claude/skills/<slug>/`, then write `# Proposed change` body to `<slug>/SKILL.md`. Tell user: "skill `<slug>` written — activates immediately (CC v2.1.0+ auto-hot-reload)."
- For `skill_edit`: apply the unified diff in `# Proposed change` to the existing SKILL.md at `target` (append-only — never replace existing content). - For `skill_edit`: apply the unified diff in `# Proposed change` to the existing SKILL.md at `target` (append-only — never replace existing content).
- For `memory`: write to `target` and update `MEMORY.md` index. - For `memory`: write `# Proposed change` body (must include auto-memory frontmatter) to `target` and update `MEMORY.md` index with a one-line pointer.
- For all others: apply via Write/Edit per the proposal's `# Proposed change`. - For all others: apply via Write/Edit per the proposal's `# Proposed change`.
- Move proposal to `~/.claude/adam/applied/<ts>-<id>.md`. - Move proposal to `~/.claude/adam/applied/<ts>-<id>.md`.
d. On **reject**: ask for reason in one line. Append `# Reason\n<reason>` to proposal body. Move to `~/.claude/adam/rejected/<id>.md`. - Archive: `node ~/.claude/adam/scripts/adam-archive.mjs ~/.claude/adam/applied/<ts>-<id>.md`.
d. On **reject**: ask for reason in one line. Append `# Reason\n<reason>` to proposal body. Move to `~/.claude/adam/rejected/<id>.md`. Archive: `node ~/.claude/adam/scripts/adam-archive.mjs ~/.claude/adam/rejected/<id>.md`.
e. On **edit**: ask the user for the change, edit the proposal in place, then loop back to step 3a for that same id. e. On **edit**: ask the user for the change, edit the proposal in place, then loop back to step 3a for that same id.
### 4. Handle failures ### 4. Handle failures
@@ -95,6 +97,8 @@ Before writing any proposal:
- For `deletion`: confirm both criteria (a) and (b) from the agent's special handling are documented in the proposal. - For `deletion`: confirm both criteria (a) and (b) from the agent's special handling are documented in the proposal.
- For `skill_new`: confirm the slug doesn't collide with any existing skill in `~/.claude/skills/`. If it does, refuse and ask user to rename. - For `skill_new`: confirm the slug doesn't collide with any existing skill in `~/.claude/skills/`. If it does, refuse and ask user to rename.
- For `skill_edit`: confirm the diff is append-only (no `-` lines that remove existing content) and that target SKILL.md exists. - For `skill_edit`: confirm the diff is append-only (no `-` lines that remove existing content) and that target SKILL.md exists.
- For `memory`: confirm `# Proposed change` body starts with `---` frontmatter containing required fields `name`, `description`, `type`, `originSessionId`. Refuse if frontmatter missing — agent must redraft per the Memory drafting protocol.
- Confirm `source_entries` is present in proposal frontmatter as a non-empty list (used for archive). Warn (do not refuse) if missing — legacy proposals from before v0.2.0 won't have it.
If any check fails, refuse to apply and ask the user how to proceed. If any check fails, refuse to apply and ask the user how to proceed.