mirror of
https://github.com/lukaszraczylo/claude-adam.git
synced 2026-06-25 02:13:41 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7962e85578 | |||
| 2b91db6bf3 |
@@ -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 repeated ≥3× within a single session (does not stack with above)
|
+2 Struggle signal appearing ≥1× 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`.
|
||||||
|
|||||||
Executable
+117
@@ -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);
|
||||||
|
}
|
||||||
@@ -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
@@ -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`) repeated ≥3× 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`) appearing ≥1× 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
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user