Skip to content

Replace migrate slash command with a harness-independent skill - #7

Open
jfrancoa wants to merge 10 commits into
mainfrom
jose/migrate-memories-skill
Open

Replace migrate slash command with a harness-independent skill#7
jfrancoa wants to merge 10 commits into
mainfrom
jose/migrate-memories-skill

Conversation

@jfrancoa

@jfrancoa jfrancoa commented Aug 5, 2026

Copy link
Copy Markdown

Motivation

Anyone switching to Engram from another local memory system arrives with an existing
store of accumulated memories and no way to bring it along. This PR adds a one-shot
importer with claude-mem as the first supported source, exposed as a skill rather
than a slash command: commands are a Claude-Code-only component type, while the Codex
plugin manifest carries skills, mcpServers, and hooks — a skill keeps the guided
flow portable. Bumps the plugin to 1.1.0.

Supersedes #6 (same feature with a slash command; only one of the two should merge).
Shaped by review: Engram classifies topics itself (@augustas1), no per-run waiting and
shared scope resolution (@danmichaeljones), no source-side curation (@danmichaeljones).

How it works

  • Adapters (plugin/core/migrate/) stream plain records — uid, content, original
    timestamp, project hint. Every observation row migrates; Engram's extraction decides
    what each memory becomes and what to keep. A new source is one adapter module plus a
    registry entry.
  • Engram owns topics. Records group into chronological conversations (one per
    project and day) submitted through the extraction pipeline with their original dates
    as context. The migration never names a topic, so any topic setup works.
  • Scope matches the hooks. Per-project properties come from
    core.scope.resolve_scope on the project's directory — the same function, config
    files, and source cascades realtime adds use. The group schema is fetched only to
    learn which properties are required; a project whose required properties can't be
    resolved is skipped and reported (--map/--property recover it). A resolution error
    in one project (broken .engram.json, offline schema fetch) skips that project
    instead of aborting the run.
  • Directory discovery works for any layout. Project names are located through
    Claude Code's own session registry (~/.claude/projects/), whose munged entries
    decode to every directory the user ever worked in — no workspace convention assumed.
    Explicit --repos-dir dirs win; cwd and its parent are a last resort for stores
    copied from another machine.
  • Submit, then settle. Conversations are submitted back-to-back in chronological
    order (Engram queues internally); a settle pass polls the checkpointed run ids to
    report what committed. Exit codes: 0 done, 1 failures, 3 still in the pipeline.
  • Idempotent and reversible. A per-source checkpoint makes re-runs send only what's
    missing; --rollback deletes via the server's per-run commit manifests, so organic
    memories are untouchable, and refuses to reset state it cannot verify.
  • Skill (plugin/skills/migrate-memories/): dry-run stripped of
    --execute/--rollback → report → fresh confirmation → execute → summary, invoked
    through a self-locating scripts/migrate.sh with no host env dependencies.

Key areas for review

  • engine.py execute/settle/reconcile_pending — checkpoint discipline around
    no-wait submission; failed runs release their items for resubmission
  • __main__.py _props_builder — hook-parity resolution, per-project error handling,
    --map/--property precedence
  • claude_projects.py — the munged-name decoder (listing-pruned, iterative) and the
    registry index
  • engine.py rollback — manifest-driven deletion; still-running runs and non-404
    delete failures keep the checkpoint

Testing

  • 27 unit tests (plugin venv; the SDK is assumed per review): adapter selection and
    composition, sqlite read-only enforcement including URI-reserved path characters,
    chronological planning, skip/undated/skip_uids handling, discovery layers, props
    building (error-skip, map/extra merge), checkpoint roundtrip and corruption handling,
    reconcile, submit-without-wait, submit-error stop, settle including the timeout path,
    manifest rollback. ruff clean.
  • Live against a real store (10k observations, 1.2k summaries, 33 projects): registry
    discovery resolved every project whose directory still exists (skips dropped from 17
    projects to 5), dry-run offline and with a corrupted per-project .engram.json
    degrade to per-project skips (both previously crashed — reproduced first), and a full
    production migration ran earlier in the PR's history with zero failures.

🤖 Generated with Claude Code

@jfrancoa
jfrancoa requested a lite review from Copilot August 5, 2026 09:53

@orca-security-eu orca-security-eu Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Orca Security Scan Summary

Status Check Issues by priority
Passed Passed Secrets high 0   medium 0   low 0   info 0 View in Orca

@jfrancoa
jfrancoa changed the base branch from jose/migrate-from-claude-mem to main August 5, 2026 09:55

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR replaces the previous Claude-Code-only /engram:migrate guided migration slash command with a harness-independent migrate-memories skill, so the migration flow can work across hosts that support skills (e.g., future Codex dual-host manifests).

Changes:

  • Added a new migrate-memories skill that documents a safe, confirmation-gated migration flow (dry-run → review → confirm → execute → summarize).
  • Added a self-locating scripts/migrate.sh wrapper that resolves the plugin root from its own path and delegates to plugin/bin/engram-migrate without relying on host env vars.
  • Updated README migration instructions and removed the old plugin/commands/migrate.md slash-command implementation; added eval prompts for future iteration.

Reviewed changes

Copilot reviewed 12 out of 14 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
README.md Updates user-facing migration guidance to point to the new skill and CLI usage.
plugin/skills/migrate-memories/SKILL.md Introduces the skill definition and step-by-step migration flow (dry-run + explicit confirmation gates).
plugin/skills/migrate-memories/scripts/migrate.sh Adds a harness-independent entrypoint that locates and executes the migration CLI.
plugin/skills/migrate-memories/evals/evals.json Adds behavioral eval prompts/assertions for the skill-driven migration flow.
plugin/commands/migrate.md Removes the old /engram:migrate slash command guidance/implementation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread README.md
@jfrancoa
jfrancoa requested a lite review from Copilot August 5, 2026 10:02

@orca-security-eu orca-security-eu Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Orca Security Scan Summary

Status Check Issues by priority
Passed Passed Infrastructure as Code high 0   medium 0   low 0   info 0 View in Orca
Passed Passed SAST high 0   medium 0   low 0   info 0 View in Orca
Passed Passed Secrets high 0   medium 0   low 0   info 0 View in Orca
Passed Passed Vulnerabilities high 0   medium 0   low 0   info 0 View in Orca

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 14 changed files in this pull request and generated no new comments.

Suppressed comments (3)

plugin/core/migrate/main.py:52

  • Same as above: sys.exit(<message>) exits with status 1 here; for malformed NAME=VALUE pairs this should be treated as a usage error (exit 2) and written to stderr.
        # an empty half silently doing nothing (e.g. --map proj= falling through to the
        # git probe) is worse than an error
        if not k.strip() or not v.strip():
            sys.exit(f"--{flag} {p!r}: name and value must be non-empty")
        out[k.strip()] = v.strip()

plugin/core/migrate/engine.py:373

  • In rollback(), iterating ops.created assumes both committed_operations and its created list are always present and non-null. If the API/SDK returns committed_operations.created = null (or omits it) for runs with no creates, this will raise and prevent rollback from completing.
        ops = rs.committed_operations
        ids = [op.memory_id for op in (ops.created if ops else [])]

plugin/core/migrate/main.py:47

  • The module docstring says exit code 2 is for usage errors, but _parse_kv uses sys.exit(<message>), which exits with status 1. Prefer emitting the message to stderr and exiting with code 2 for invalid --map/--topic-map/--property values.

This issue also appears on line 48 of the same file.

        if "=" not in p:
            sys.exit(f"--{flag} expects NAME=VALUE, got {p!r}")
        k, v = p.split("=", 1)

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 14 changed files in this pull request and generated no new comments.

Suppressed comments (2)

plugin/core/migrate/engine.py:373

  • rollback() assumes client.runs.get() always returns an object with a committed_operations attribute and that it always has a created iterable. If the SDK omits committed_operations for some terminal run states (or created is None), this will raise AttributeError/TypeError and abort rollback, leaving the checkpoint and partially deleted state.
        ops = rs.committed_operations
        ids = [op.memory_id for op in (ops.created if ops else [])]

plugin/skills/migrate-memories/SKILL.md:46

  • This option suggests running --execute directly, but the flow above explicitly requires always doing a dry-run first and only appending --execute after a fresh in-conversation confirmation. As written, this conflicts with the skill’s safety gate.
- `--limit N --execute` — a small smoke run before committing to a full migration.

Comment thread plugin/core/migrate/claude_mem.py Outdated
# claude-mem observation `type` → our kind. Everything describing the codebase and its
# decisions maps to `architecture`; work items map to `task`. A type missing here (from a
# newer claude-mem) is skipped and reported by describe_selection rather than mis-filed.
KIND_BY_TYPE = {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since topics are Engram's strength, we might want to make it more flexible and closer to the plugin behaviour.
Plugin checks configured topics of the live project for the only reason that there might be scope properties (which would be required at input).

I am thinking maybe it's better to let Engram extract and classify memories (instead of using pre-extracted mode here) so this migrations works regardless of topic setup (e.g. currently it fails if project has topics with no properties). We might still need to fetch topics from project just to see what properties are configured, just to figure out property setup. The plugin does it this way:

  1. Fetches project's group to see what properties are configured on topics
  2. Pass required properties (default repo_name, session_id if configured) when adding memories
    OR pass no properties if none are configured.
  3. Plugins also let's configure custom properties (other than repo_name, session_id) but maybe we don't need to build this for the migration skill.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-implemented in 54e9ddf exactly along these lines: conversation input is now the only ingestion path, so Engram's extraction classifies every memory itself — the whole kind→topic mapping (and --topic-map, --input, the pre-extracted planner) is gone. The group schema is fetched only to learn the configured scope properties: repo_name is attached per batch only when the group configures it, and when it doesn't, unresolvable projects migrate too since nothing can be mis-filed. session_id keeps its migration marker when configured; custom --property stays since it was already built and small.

jfrancoa added a commit that referenced this pull request Aug 10, 2026
Review feedback (augustas1 on #7): topic classification is Engram's
strength, and a client-side kind→topic mapping breaks on any group
whose topics differ from the author's (TaskStatus is not in the default
group). The group schema also only matters for scope properties, not
topics.

- Conversation input is now the only ingestion path: memories go
  through the extraction pipeline with their original dates as context
  and Engram routes each one into the group's topics itself
- Removed: Record.kind, the KINDS vocabulary, KIND_TO_TOPIC, the
  pre-extracted planner, --input, --topic-map, --batch-size, and the
  [date] content prefix
- The schema fetch now serves property setup only: repo_name is
  attached per batch only when the group configures it; when the group
  has no repo_name property nothing can be mis-filed, so unresolvable
  projects migrate too instead of being skipped
- session_id auto-fill and --property behavior unchanged

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jfrancoa
jfrancoa requested a lite review from Copilot August 10, 2026 10:15

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 14 changed files in this pull request and generated no new comments.

Suppressed comments (1)

plugin/core/client.py:92

  • get_client() imports the Engram SDK unconditionally; when with-venv.sh falls back to system python (e.g., first run before deps are installed), this will raise ImportError and crash the hook/CLI instead of failing open as intended. Consider importing only after confirming an API key is present and catching ImportError to return None.
def get_client():
    # SDK import stays local: everything else in this module (key/identity resolution, the
    # REST helper) is stdlib-only and must keep working where the SDK isn't installed.
    from engram import EngramClient

Comment thread plugin/core/migrate/claude_mem.py Outdated
Comment thread plugin/core/migrate/engine.py Outdated
Comment thread plugin/core/migrate/engine.py Outdated
Comment thread plugin/core/__init__.py Outdated
jfrancoa and others added 7 commits August 11, 2026 12:24
One-shot migration of an existing local memory store into Engram, built
around a small source-adapter contract so new systems are one module +
one registry entry. First source: claude-mem (SQLite, read-only).

- Two ingestion paths: pre-extracted (verbatim, [date]-prefixed, explicit
  topic mapping validated against the live group schema) and conversation
  (extraction pipeline with created_at date context, submitted strictly
  earliest-to-latest; a slow run aborts resumably instead of skipping
  ahead to preserve chronology)
- Checkpoint in ~/.engram/migrate/<source>.json makes every run
  idempotent and resumable; --rollback deletes exactly what the migration
  created via the server's per-run commit manifests
- Dry-run by default; --execute writes. Repo scoping via git-remote
  probing with --map overrides; unmappable projects are skipped, never
  mis-filed. Group-required scope properties are checked up front
  (session_id auto-filled with a migration marker)
- Ships as bin/engram-migrate (self-locating, plugin bin/ is on PATH)
  and the /engram:migrate command; stdlib-only unit tests included
- core/__init__.py re-exports are now lazy and the SDK import moved
  inside get_client(), so dry-run and tests work without the venv

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review findings (F1-F14) against the migrate feature, fixed:

- rollback: refuse manifests of still-running runs, record non-404 delete
  failures instead of crashing, require a stable identity, detect an
  identity change via a user_id stamp in the checkpoint, and keep the
  checkpoint when every delete reports already-gone (possible mismatch)
- conversation chronology: abort on a failed batch (not just a timed-out
  one), refuse to submit while earlier runs are still in flight, and cap
  one conversation at MAX_CONVERSATION_MESSAGES
- validation: --execute refuses to run without the group schema instead
  of failing open per batch; --property repo_name is rejected; --topic-map
  and --batch-size error in conversation mode instead of being ignored
- robustness: submit failures are recorded per batch instead of killing
  the CLI; _wait and reconcile surface the real error and release runs
  the server 404s; corrupt checkpoints exit cleanly with the file named
- CLI args: --limit/--batch-size must be positive, --limit counts fresh
  (post-checkpoint) records, --repos-dir is expanduser'd, empty KEY=VALUE
  halves are rejected; exit code 3 marks an incomplete (pending) run
- adapter: NULL observation types no longer crash the report; non-array
  facts JSON is kept verbatim instead of mangled
- report: per-project counts, honest checkpoint label, mode-agnostic
  sample line, no --map advice for unmappable "(none)" records
- docs: /engram:migrate strips --execute/--rollback from the dry-run
  step; README clarifies where engram-migrate is on PATH; stale
  docstrings and the launcher's env-precedence comment corrected

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Percent-encode the sqlite URI path so ?/#/% in --db can't smuggle
  URI params past mode=ro
- Conversation mode excludes records without a usable created_at and
  reports the count (they can't be placed chronologically; pre-extracted
  mode carries them), instead of silently importing them first

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Slash commands are a Claude-Code-only component: the Codex plugin
manifest (as shipped by dual-host plugins today) carries skills, MCP
servers, and hooks — no commands. Shipping the guided migration flow as
a command would block a future Codex release of this plugin.

The flow now lives in skills/migrate-memories/: same five steps
(dry-run stripped of --execute/--rollback, present report, fresh
confirmation, execute, summarize), invoked through a self-locating
scripts/migrate.sh that resolves the plugin root from its own path —
no CLAUDE_PLUGIN_ROOT or any host env — and delegates to the verified
bin/engram-migrate launcher. Works unchanged wherever skills load.

Skill authored with skill-creator; behavioral checks (dry-run-first and
the confirmation gate under "just run it with --execute" pressure) pass,
and the skill run used ~40% fewer tokens than exploration-based
baselines. Caveat: baselines could see the skill files in the tree, so
the comparison understates the no-skill gap. Eval prompts kept in
skills/migrate-memories/evals/ for future iteration.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Drop the stale /engram:migrate reference from the CLI module docstring;
  point at the migrate-memories skill wrapper instead

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Eval 1 tolerated executing on the prompt's blanket consent, which
SKILL.md step 3 forbids — a future iteration graded against it could
pass behavior the skill prohibits. The expected output and assertions
now encode the fresh-in-conversation-confirmation contract.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review feedback (augustas1 on #7): topic classification is Engram's
strength, and a client-side kind→topic mapping breaks on any group
whose topics differ from the author's (TaskStatus is not in the default
group). The group schema also only matters for scope properties, not
topics.

- Conversation input is now the only ingestion path: memories go
  through the extraction pipeline with their original dates as context
  and Engram routes each one into the group's topics itself
- Removed: Record.kind, the KINDS vocabulary, KIND_TO_TOPIC, the
  pre-extracted planner, --input, --topic-map, --batch-size, and the
  [date] content prefix
- The schema fetch now serves property setup only: repo_name is
  attached per batch only when the group configures it; when the group
  has no repo_name property nothing can be mis-filed, so unresolvable
  projects migrate too instead of being skipped
- session_id auto-fill and --property behavior unchanged

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jfrancoa
jfrancoa force-pushed the jose/migrate-memories-skill branch from 54e9ddf to 8fb33c6 Compare August 11, 2026 10:25
- Submit without waiting on each pipeline run: Engram queues internally,
  so conversations are submitted back-to-back in chronological order and
  only an immediate add error stops the loop. A settle pass then polls
  the checkpointed run ids to record what committed; leftovers reconcile
  on the next invocation (exit 3)
- Share scope resolution with the plugin core: per-project properties
  now come from core.scope.resolve_scope on the project's directory —
  the same function, configuration files, and source cascades the store
  hook uses, including the repo_name git-repo → cwd fallback. The
  migration-only resolver and _batch_properties are gone; --map and
  --property remain as overrides; projects whose required properties
  can't be resolved are still skipped and reported
- Assume the SDK everywhere: revert the lazy re-exports in
  core/__init__.py and the lazy imports in client.py and the engine
  (core/__init__.py and client.py now match main exactly); tests run
  under the plugin venv

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jfrancoa
jfrancoa requested a lite review from Copilot August 11, 2026 10:40

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 12 changed files in this pull request and generated no new comments.

Suppressed comments (4)

plugin/core/migrate/engine.py:84

  • Records with rec.project == None are always treated as unresolvable and skipped because props_for() is never called in that case (props_by_project is set to None). _props_builder(props_for) explicitly supports a missing project by returning only trustworthy required properties (e.g., session_id marker) + explicit overrides, so these records can migrate safely when the group doesn’t require repo_name.
        project = rec.project or "(none)"
        if project not in props_by_project:
            props_by_project[project] = props_for(rec.project) if rec.project else None
        props = props_by_project[project]
        if props is None:
            mapping[project] = None
            skipped[project] = skipped.get(project, 0) + 1
            continue

plugin/core/migrate/engine.py:25

  • engine.py imports the Engram SDK (ConversationInput/MessageInput) at module import time. That makes even dry-run/report-only code paths fail in environments without the plugin venv, contradicting the stated goal that dry-run/tests can run SDK-free. Consider lazy-importing the SDK only when building/submitting ConversationInput (e.g., inside _build_input/execute) so planning/reporting works without the SDK installed.
from engram import ConversationInput, MessageInput

plugin/core/migrate/engine.py:126

  • The skipped-projects note implies --map is the remedy for required-scope resolution failures, but projects can also be skipped due to other missing required properties (which are instead addressable via --property). This message can mislead users into trying only --map when they need to supply other required properties.
        lines.append(
            "  skipped — the group's required scope properties could not be resolved "
            "(--map=NAME=owner/repo supplies repo_name):"
        )

plugin/.claude-plugin/plugin.json:5

  • PR description says the plugin is bumped to 0.2.0, but the manifest version is set to 1.1.0 (from 1.0.0). Please align the PR description and the shipped manifest version so downstream tooling (e.g., client_origin_header tests and plugin distribution) reflects the intended release.
  "description": "Persistent cross-session memory backed by Weaviate Engram. Stores each conversation turn and recalls relevant memories before Claude answers.",
  "version": "1.1.0",
  "author": { "name": "Weaviate" },

Review feedback (danmichaeljones): the plugin should not hardcode what
is relevant — insert everything and let Engram's extraction decide what
to keep. The type list was a leftover from the pre-extracted design,
where whatever was sent got stored verbatim; with extraction as the
filter there is nothing left for the adapter to curate, and unknown or
NULL types are just text to the extractor.

- claude-mem adapter reads every observation row (no type WHERE clause);
  describe_selection reports counts per type, nothing excluded
- --all removed; adapter contract simplifies to records() /
  describe_selection() with no include_all parameter

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 12 changed files in this pull request and generated no new comments.

Suppressed comments (6)

plugin/tests/test_migrate.py:54

  • This comment is now inaccurate: the adapter/test intentionally includes unknown/new observation types (see test_selection_and_composition expecting obs:5). Update the comment to reflect that type is not used as a filter.
            # unknown type → never yielded

plugin/core/migrate/engine.py:80

  • Records with an empty/None project are always skipped because props_for() is never called (line 79 sets props to None). This contradicts the intended behavior where migrations should still proceed when required scope properties can be satisfied without a project directory (e.g., groups without repo_name, or only requiring session_id). Call props_for() even when rec.project is falsy and let it decide whether required properties are satisfiable.
        project = rec.project or "(none)"
        if project not in props_by_project:
            props_by_project[project] = props_for(rec.project) if rec.project else None
        props = props_by_project[project]

plugin/tests/test_migrate.py:51

  • This comment is now inaccurate: the adapter/test intentionally includes "discovery" observations (see test_selection_and_composition expecting obs:4). Update the comment so it doesn't imply these rows are excluded.

This issue also appears on line 54 of the same file.

            # excluded by default

plugin/skills/migrate-memories/SKILL.md:31

  • This flow text says the report is "per repo and day", but the current implementation groups by (day, source project) (plugin/core/migrate/engine.py:77-99). Adjust the wording so the skill guidance matches what the CLI actually reports.
2. **Present the report**: how many memories per repo and day; which source projects were
   skipped because no git remote was found (offer `--map NAME=owner/repo` for ones worth

plugin/.claude-plugin/plugin.json:5

  • PR description mentions bumping the plugin to 0.2.0, but this manifest changes the version from 1.0.0 to 1.1.0. Please align the PR description and the actual versioning scheme (either update the version here or adjust the stated version bump).
  "description": "Persistent cross-session memory backed by Weaviate Engram. Stores each conversation turn and recalls relevant memories before Claude answers.",
  "version": "1.1.0",
  "author": { "name": "Weaviate" },

README.md:131

  • The migration engine groups records by (day, source project) (plugin/core/migrate/engine.py:77-99), not strictly by resolved repo_name. This README sentence says "per repo and day", which can be misleading if multiple source project names map to the same repo_name (via --map or source-side merges). Update wording to match the implementation.
so re-runs only send what's missing. Memories are grouped per repo and day into
chronological conversations and imported through Engram's extraction pipeline, with each
conversation's `created_at` telling the extractor when the data is from — so memory

Review round: F1, F3, F10, F12, F13, plus making directory discovery
work for any workspace layout instead of assuming projects are siblings
of the current directory.

- claude_projects.py: ~/.claude/projects/ holds one munged name per
  directory the user ever ran a session in; decoding them reconstructs
  the user's real layout. index_by_basename() maps basenames (what
  claude-mem records) to those directories, newest session first.
  The hardened munged-name decoder now lives here
- project_dir_finder is layered: explicit --repos-dir dirs, then the
  registry index (a candidate with a git remote wins, then the most
  recent session), then cwd and its parent as a last resort for stores
  copied from another machine. Live result on a real store: skipped
  projects dropped from 17 to the handful whose directories no longer
  exist anywhere
- F1: props_for wraps resolve_scope — one project's broken .engram.json
  or an uncached offline dry-run skips that project with a note instead
  of aborting the whole migration with a traceback (both crashes were
  reproduced first)
- F3: the all-gone rollback branch resets the checkpoint when its
  recorded identity matched; only unstamped legacy checkpoints keep it,
  and the message now names the file to delete
- F10: runs reconciled at startup are counted in the final summary
- F12: skip reason and hint corrected (missing required properties, not
  "no git remote"; --property mentioned), --repos-dir documented,
  --project marked repeatable, exit-code docstring made truthful,
  describe_selection labeled as row counts, report says "scopes"
- F13: stale fixture comments fixed; tests added for the discovery
  layers, _props_builder (error-skip, map/extra merge, dir-less
  marker), and the settle timeout path

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
jfrancoa added a commit that referenced this pull request Aug 11, 2026
The munged-name decoder moved to core.migrate.claude_projects on #7
(the session-registry index uses it for directory discovery); the
adapter now imports it instead of carrying its own copy. Finder tests
updated for the layered signature.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jfrancoa
jfrancoa requested a lite review from Copilot August 11, 2026 13:29

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 13 changed files in this pull request and generated no new comments.

Suppressed comments (1)

plugin/core/migrate/claude_mem.py:121

  • describe_selection() says rows with no usable content are dropped "at planning", but the adapter actually drops those rows while iterating the source DB (in _observations() / _summaries() it continues when content is empty). This is a small but concrete mismatch in the dry-run report text.
        return [
            f"observation rows: {by_type} — rows with no usable content are dropped "
            "at planning",

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants