Imported from krakozavr/MemContinuum (
skills/memcontinuum/SKILL.md). Install upstream withnpx skills add krakozavr/MemContinuum --skill memcontinuum. Copyright stays with the author.
MemContinuum — per-repository decision
MemContinuum is installed once per machine (memcontinuum-setup.sh). Whether any given
repository keeps a decision store is a separate, per-repo choice, and it is
the human's to make. Running this skill, once per repository, is the
official way that choice gets made — not something that happens around the
human. This skill is the only thing that records that choice.
A hook may report the state. Only this skill, after a human has answered, writes a decision down. A SessionStart detector also classifies a repo and can hand this trigger to the assistant, but that channel is advisory only — it reaches the assistant, never the human directly, and nothing compels an assistant to act on it. If you were triggered by the SessionStart detector and the human has not actually answered yet, stop and ask them first.
1. Read the current state before saying anything
# config.sh is sourceable shell (values are single-quoted by sh_quote) --
# SOURCE it, never parse it with sed/tr: any quote-stripping of your own
# breaks the moment the quoting style changes.
#
# Two steps, because the fixed default path may hold only a POINTER
# (a custom-HOME install also writes a minimal config.sh at
# $HOME/.memcontinuum recording just the real MEMCONTINUUM_HOME -- see
# memcontinuum-setup.sh "3. config"). Source the default/env path first;
# if that just redefined MEMCONTINUUM_HOME to a different directory, it
# was a pointer -- follow through and source the REAL config.sh there too,
# or MEMCONTINUUM_ENGINE stays empty under a custom HOME.
MC_HOME_CONFIG_1="${MEMCONTINUUM_HOME:-$HOME/.memcontinuum}/config.sh"
[ -f "$MC_HOME_CONFIG_1" ] && . "$MC_HOME_CONFIG_1"
if [ -n "${MEMCONTINUUM_HOME:-}" ] && [ "$MEMCONTINUUM_HOME/config.sh" != "$MC_HOME_CONFIG_1" ]; then
. "$MEMCONTINUUM_HOME/config.sh"
fi
ENGINE="$MEMCONTINUUM_ENGINE"
bash "$ENGINE/scripts/memcontinuum-state.sh" REPO # the repo you're deciding for, not $PWD
It prints decision= (wired, declined, or none -- the human's recorded
answer, when there is one) and wiring= (full, partial, or none --
what the repo's .claude settings actually contain right now, plus
missing=<basenames> when partial) as two SEPARATE facts: a hand-edited
settings file or an interrupted install can leave them disagreeing. It also
prints a combined state= line -- wired, declined, partial-wired (no
recorded decision, and only SOME of the always-wired hooks are present --
SessionStart's detector emits its ask into the assistant's context here too,
same as undecided: a half-wired repo with no recorded decision is still an
open question, not a settled one), undecided, not-a-repo, or no-config
-- plus the repo key, store path and project name where they apply. wired
also covers an install wired before the decision registry existed:
decision=none with wiring=full reads as already wired, since the wiring
itself is the evidence. Report decision/wiring plainly when they
disagree; never guess either one from the presence of a directory.
2. Ask — a structured prompt for every state, never destructive by accident
One question, no advocacy: report each option in the words given below it —
its cost where one is named there, otherwise what it does — then stop; never
add a cost or a justification of your own, never argue for one, never read
whether this particular repo deserves it. No
option ever deletes a store (Section 5 makes this absolute, on its own).
Where the repo already has a recorded answer (wired, declined), the
first option offered always keeps it, unchanged; where it does not, an
option that changes nothing — Not now — is always present, and it always
records nothing. All three rules govern every state below, not only
undecided.
Read state= from step 1 and ask through the interface's structured multiple-choice
prompt, never prose. What the human is deciding, underneath
every state's wording below: whether this repo keeps an append-only record of
why its decisions were made — rulings, incidents, rejected alternatives —
indexed and surfaced to agents when they touch related code. It costs a git
repo for the store, hooks wired into the project's .claude settings (the
dry-run's plan prints a code root : line per root for retrieval, and how
many hook groups merge into each event — not the individual hook script
names; read the plan's own lines to the human rather than restating them),
and the habit of writing records.
undecided — four options:
- Yes, with code retrieval — records, plus decisions surfaced automatically when edits touch the named code root
- Yes, rationale only — records, no code retrieval
- No — record the decline; this repo is never asked again
- Not now — nothing is recorded; run
/memcontinuumagain to decide
partial-wired (some but not all write-side hooks already present, no
recorded decision) — three options:
- Complete the wiring — finishes what a prior install left half-done
- Remove what is there
- Not now — leave it half-wired; run
/memcontinuumagain to decide
wired — first report the state as facts, straight out of step 1's own
output (decision, wiring, decided_at — present only when a registry row
exists; a repo whose wired reading comes purely from decision=none +
wiring=full has none, and step 1's output omits it there, not a blank
value — store, project, settings),
then offer:
- Keep as is — nothing changes
- Stop using MemContinuum here — record the decline (the hooks stay wired until removed by hand; say so)
Moving a wired store, or changing its code-root set, is not offered as a
prompt option here: this skill has no read-back of the repo's current
complete parameter set, so it will not guess at what a re-run should ask
for. The underlying tool can still do both, safely — see step 4 for the
supported path (repo-init.sh --adopt-only with the complete desired set)
and how a human finds their current one. If the human wants either, say so
plainly and point at step 4.
Neither option here repairs broken wiring: state=wired also covers
decision=wired with wiring=partial (a hand-edited or interrupted
settings file). "Keep as is" leaves it broken; there is no third PROMPT
option that fixes it, but it is not stranded — step 4's same complete-set
repo-init.sh --adopt-only path repairs it too. Say so plainly if step 1
showed that combination, and point at step 4.
declined — two options:
- Keep declined
- Wire it after all
not-a-repo / no-config — no prompt: state the fact and stop. These
are the only states where a prompt would be theatre.
Act on whichever option the human picks using step 3 below (a fresh
undecided install, or the partial-wired repair) or step 4 (every other
change to an existing answer).
3. Act on the answer
Yes → initialize. Two facts are needed and both are the human's call:
-
--project NAME— the index namespace, also<NAME>.sqlite. Pick whatever name the human wants; ifrepo-init.shrefuses it, its own message names the characters it allows. -
--store DIR— where the store lives. Naming convention: the folder is calledMemContinuum-Store— marked as this tool's, never a genericmemory/(collides with other memory systems) and never bareMemContinuum(reads as the tool itself). Getting it right on an explicit--storeis the human's call.Omit
--storeand letrepo-init.shpick the default. Always dry-run first, and read thestore :line — and anynote:line above it — out of that dry-run's own output, verbatim, to the human. Never predict, describe, or explain where the default will land, on any platform: the rule that computes it lives inscripts/mc-registry-lib.shand can change without this skill knowing, so the only honest answer is whatever the tool just printed. -
--code-root DIR— repeatable; the code checkout(s) whose edits should trigger retrieval. Omit for a rationale-only store.
"Complete the wiring" on a partial-wired repo is a repair, not a fresh
install — but ONLY when this repo has no existing code retrieval: check
<claude-dir>/settings.local.json (and settings.json) yourself for a
PreToolUse entry naming pre-edit-chain.sh. If neither file has one,
re-run scripts/repo-init.sh with the same --store/--project the repo
already has (from step 1's store=/project= lines) plus
--record-decision, so it completes the missing wiring and records it in
one step. If either file DOES have one, do
not run the bare repair: omitting the existing --code-root/--langs/
--never-ext flags would erase or clear part of the existing wiring
instead of completing it, and this skill will not reconstruct that flag
set for you from memory. This is not stranded, though: step 4's "wired →
a different store location, or a different code-root set" bullet gives
the verified recovery route for every field of that complete set —
including the one field, language NAMES, that cannot be read back
verbatim, and what to do about that honestly — and the same
repo-init.sh --adopt-only path, with --record-decision added, completes
the missing hooks and records it, without dropping retrieval.
"Remove what is there" on a partial-wired repo: no decision was ever
recorded (decision=none), so there is no registry row to touch — just the
hooks that already exist. Remove them by hand the same way as README.md
"Uninstall" step 1.
Always dry-run first, show the plan, then run it. With a --code-root,
never invoke repo-init.sh bare and interactive — you (Claude Code's Bash
tool) have no tty, so a bare run against a repo with any supported-language
files hits repo-init.sh's own [ -t 0 ] guard and fails with a clear
message (exit 12) rather than hanging; the message itself names the driven
path below. Run the driven flow instead:
-
Census the code root yourself, with the resolved python and
PYTHONPATH=cleared (same resolutionrepo-init.shuses —$MEMCONTINUUM_PYTHON, else$ENGINE/.venv/bin/python):PY="${MEMCONTINUUM_PYTHON:-$ENGINE/.venv/bin/python}" PYTHONPATH= "$PY" "$ENGINE/memidx.py" code-census --root DIR --json -
Present the three categories to the human in conversation, reading all three straight out of that JSON — no separate list of known languages is needed. Each key maps to
{"files": N, "status": "supported"|"unsupported"}:- proposed —
status: "supported"withfiles > 0(the key is the language name), - supported but not found —
status: "supported"withfiles == 0(every language this engine version knows always appears, at zero when the tree holds none of its files), - unsupported —
status: "unsupported"(the key is the extension, or"(no extension)").
Offer skip, enable all detected, or select a subset. A fourth option: the human can name an extension the new-file reminder should never mention again — pass
--never-ext .cs(comma-separated for several) alongside whichever language choice they made. It does not change which languages are enabled. - proposed —
-
Run
repo-init.shwith the human's answer turned into a flag — never bare, and the flag goes on the--dry-runpreview line too (the census block, unlike the existence check, runs under--dry-runas well — a preview command with no tty and no bypass flag hits the same[ -t 0 ]guard and exit 12 that this whole driven flow exists to avoid):# human chose specific languages, or "enable all detected": cd REPO && bash "$ENGINE/scripts/repo-init.sh" --project NAME --code-root DIR --langs "chosen,langs" --dry-run cd REPO && bash "$ENGINE/scripts/repo-init.sh" --project NAME --code-root DIR --langs "chosen,langs" --record-decision # -- or, for "skip": cd REPO && bash "$ENGINE/scripts/repo-init.sh" --project NAME --code-root DIR --non-interactive --dry-run cd REPO && bash "$ENGINE/scripts/repo-init.sh" --project NAME --code-root DIR --non-interactive --record-decision
For a rationale-only install (no --code-root at all), there is nothing to
census or ask about — the bare two-line dry-run-then-run form is fine:
cd REPO && bash "$ENGINE/scripts/repo-init.sh" --project NAME --dry-run
cd REPO && bash "$ENGINE/scripts/repo-init.sh" --project NAME --record-decision
--record-decision (real run only — a no-op under --dry-run) records
wired the same way running memcontinuum-decide.sh wired by hand would
(store, project, and claude-dir all included) — and, because it goes
through the same shared builder repo-init.sh uses internally
(mc_build_wiring_args, scripts/mc-registry-lib.sh), it also records
this install's --code-root/--langs/--never-ext into the registry
note as code-roots=/langs=/never=. Step 4 depends on that note to
recover this repo's complete parameter set later without asking the human
to remember it — this is exactly the case --record-decision's own help
text asks for: "from a driven flow where a human has already said yes."
When a row already exists for this repo, its recorded claude-dirs/code-
roots are unioned with this install's, never dropped. Use it on every
real (non-dry-run) repo-init.sh run this skill drives — including the
bare partial-wired repair re-run above and step 4's repair and
reconfiguration paths below — not only a fresh install.
Recording wired for a repo that is ALREADY fully wired (step 1 showed
decision=none, wiring=full — wired before the decision registry
existed, nothing to install or change) is the one case that legitimately
records without running repo-init.sh at all: there is no registry row
yet, so step 4's registry-first recovery has nothing to read, and no
install is needed since the wiring is already there. Recover
--code-root/--never-ext from the rendered hooks the same way step 4's
fallback does (MEMCONTINUUM_CODE_ROOTS off a write-side line,
MEMCONTINUUM_NEVER_EXTS off a newfile-nudge.sh line, both filtered by
MEMCONTINUUM_PROJECT); --langs has the same honest gap step 4
describes — ask the human if it is not already known. Pass everything
found:
bash "$ENGINE/scripts/memcontinuum-decide.sh" wired --repo REPO --store DIR --project NAME --code-root ... --langs ... --never-ext ...
No → record the decline. One command, and this repo is never asked again:
bash "$ENGINE/scripts/memcontinuum-decide.sh" declined --repo REPO
"Not now" → record nothing. Say so and move on; the detector stays quiet
for the rest of this session and reads this repo as undecided again at the
next one — the same advisory nudge to the assistant as before, with no
guarantee it reaches the human unless /memcontinuum is run again. An
unrecorded maybe is correct here — do not invent a decision to silence a
prompt.
4. Reversing or changing an earlier answer
Either direction, at any point in a repo's life:
-
declined → wanted ("wire it after all" on a
declinedrepo): firstbash "$ENGINE/scripts/memcontinuum-decide.sh" forget --repo REPO— verified live:--record-decisionREFUSES to record over adeclinedrow ("Only an undecided repo may be recorded as wired by the installer"), installs the wiring anyway, then leaves the row exactly asdeclinedwith nocode-roots=/langs=recorded — the same failure this whole round exists to fix, reintroduced on this one path if skipped. Once forgotten, run thescripts/repo-init.shsteps above (with--record-decision) exactly as for a freshundecidedinstall. -
wired → unwanted ("stop using MemContinuum here" on a
wiredrepo):memcontinuum-decide.sh declined --repo REPOrecords it, but that only stops the asking. The hooks stay wired until they are removed — see README.md "Uninstall". Tell the human which of the two they want; do not delete a store, ever. A store is its own git history, not an installer artifact. -
wired → a different store location, or a different code-root set: this skill does not drive it — it has no read-back it can trust for the repo's complete current parameter set, and handing
repo-init.shan incomplete one silently drops existing wiring (the exact failure mode the removed relocation/add-retrieval options hit). The product itself supports both changes:repo-init.shalways replaces prior wiring with the invocation's COMPLETE set, and--adopt-onlywires an EXISTING store without creating one — including a store already relocated by hand (the store is its own git repo;mvit first, then point--storeat the new path). The honest, human-directed path — every field below was checked against a live install's actualsettings.local.jsonand registry row, not assumed from the template or library that produce them:-
--project/--store/--claude-dir: off step 1's ownproject=/store=/settings=lines (the last names the directory holdingsettings.local.json). -
--code-root/--langs/--never-ext: first check the registry row —$MEMCONTINUUM_HOME/decisions.tsv, keyed by step 1's ownkey=line. When this repo was recorded with--record-decision(step 3 now always uses it), the row's note carriescode-roots=,langs=, andnever=verbatim, semicolon-joined. Read them with the SAME shared library the installer itself uses — never a hand-rolled parse of the TSV or the note:source "$ENGINE/scripts/mc-registry-lib.sh" if mc_registry_lookup "${MEMCONTINUUM_HOME:-$HOME/.memcontinuum}/decisions.tsv" "KEY"; then mc_note_field "$MC_LOOKUP_NOTE" "code-roots"; ROOTS_SEMI="$MC_NOTE_FIELD" mc_note_field "$MC_LOOKUP_NOTE" "langs"; LANGS_SEMI="$MC_NOTE_FIELD" mc_note_field "$MC_LOOKUP_NOTE" "never"; NEVER_SEMI="$MC_NOTE_FIELD" mc_build_wiring_args "$ROOTS_SEMI" "$(printf '%s' "$LANGS_SEMI" | tr ';' ',')" \ "$(printf '%s' "$NEVER_SEMI" | tr ';' ',')" # ${MC_BUILT_ARGS[@]} is now the exact --code-root/--langs/--never-ext # tail for the re-run below -- verified live: a --langs python # --record-decision install round-trips through this to # `--code-root DIR --langs python`. fiAn EMPTY field here is ambiguous by construction (
mc_note_fieldreturns "" both for "recorded as empty" and "never recorded") — do not read an emptyLANGS_SEMIas "no languages wired" without also checkingwiring=fulland a realnewfile-nudge.shline insettings.local.json; if one exists, the row predates--record-decisionand the field is unknown, not empty.These fields can also OVERSTATE the current set, never understate it.
--record-decision(andmemcontinuum-decide.shcalled by hand) UNIONS a new install's code-roots/langs/never into whatever the row already had — verified live: recordingpythonthen laterpython,javascriptafter dropping the python-only root left the row readinglangs=python;javascripteven though onlyjavascriptwas still actually wired. The registry note is reliable for recovering a set you are ADDING to or leaving unchanged (relocating the store, adding a root); it is NOT reliable evidence of what survived a REMOVAL. After removing a root or a language, treat the RENDERED hooks (MEMCONTINUUM_CODE_ROOTS/MEMCONTINUUM_LANG_EXTSinsettings.local.json, per the fallback below) as the one source of truth for what is wired right now, and the registry note only as a hint about what USED to be there. -
If the row lacks these fields (recorded before this round, or by hand):
--code-rootcan still be recovered independently — readMEMCONTINUUM_CODE_ROOTS, a JSON array naming every root already resolved, off any ONE write-side hook line (PostToolUse,SessionStart,SessionEnd,UserPromptSubmit, orPreCompact; not the twoPreToolUsepre-edit-chain.shlines, which carryMEMCONTINUUM_ROOTandMEMCONTINUUM_STRIP_PREFIX, never a code-root token).--never-extlikewise — readMEMCONTINUUM_NEVER_EXTSoff anyPreToolUseline namingnewfile-nudge.sh(identical on every root's copy of it) and turn each glob back into a bare extension (*.cs→.cs). -
--langsis the one field that has no other reliable recovery route today, and saying otherwise is what made this recipe fail before this round. The rendered hooks hold onlyMEMCONTINUUM_LANG_EXTS, EXTENSION GLOBS (e.g.'*.py'), not language names — feeding that straight to--langsfails outright (ERROR: --langs names an unknown language: *.py, exit 11). Inverting a glob set back to names correctly (handling a partial match, an unknown extension, or two languages that could both explain the same glob set) is real logic that already exists —mc_update_recover_from_settingsinscripts/memcontinuum-update.sh— but it is that script's own internal migration helper, not a public interface: the whole file runs its top-level CLI the moment it is sourced, so calling just that one function safely is not possible today, andmemcontinuum-update.sh --repo REPOon its own reports only a migration verdict (ok,migrate-needs-langs, …), never the recovered names themselves. When the registry lackslangs=, this skill has no supported way to recover the language set — say so plainly and ask the human to state it again, rather than guessing from step 3'scode-census(that reports what languages are PRESENT in the tree right now, a different question from what was WIRED, since files can have been added or removed since the original install) or reimplementing the updater's own inversion by hand. -
A
.claudedirectory can be shared by more than one project — every hook line above also carriesMEMCONTINUUM_PROJECT=<name>in the same command string, and two projects' entries use the identical seven basenames. Before reading anything off a matched line, confirm it also carriesMEMCONTINUUM_PROJECT=followed by THIS repo's own project (step 1'sproject=line) — matching on basename alone can read another project's roots, extensions, or never-list instead of this one's.
Then re-run naming that complete set plus whichever root, language, or never-ext is changing, with
--record-decisionso the registry stays complete for next time:bash "$ENGINE/scripts/repo-init.sh" --adopt-only --store DIR --claude-dir DIR --project NAME --code-root ... --langs ... --never-ext ... --record-decision— dry-run first, per step 3 (drop--record-decisionon the dry-run line; it is a no-op there but the dry-run preview should still mirror the real command otherwise). This is a deliberate command run at the human's direction, not a step 2 prompt option.If the new
--langsset drops a language the project had before (removing the only root that used it, say), this re-run's own code-reindex step fails on purpose (verified live:--lang javascript drops python from the project's stored language set python,javascript; ... pass a superset, or --full to change it for every root, exit nonzero) —repo-init.shhas no--fullpassthrough of its own. The hook wiring is still updated correctly at this point; only the code index is left stale. Finish it with one direct call, same code-root, project, and new language set, adding--full:PYTHONPATH= "$PY" "$ENGINE/memidx.py" code-reindex --code-root DIR --project NAME --lang "chosen,langs" --full($PYas step 3 resolves it). Re-runningrepo-init.shagain afterward is not required for the wiring or the index. It will not fix the registry either way: verified live, a SECOND--record-decisionrun (now exiting 0, since the index is already fixed) still leftlangs=python;javascriptin the row —--record-decisionunions, so once a language has been recorded it stays in the note permanently, whether or not it is still wired (see the union caveat under step 4's registry recovery). There is no supported command that removes a field from a registry row's note short ofmemcontinuum-decide.sh forget --repo REPO(which drops the WHOLE row, decision included) and recording it again. Say so plainly if it matters here; do not claim the registry now reads correctly. -
-
never ask in any repo on this machine:
memcontinuum-decide.sh never-ask; undo withmemcontinuum-decide.sh ask-again.
5. Rules
- Never initialize without an explicit yes in this conversation.
- Never delete a store, ever, regardless of what is asked. Never move one on your own judgment either — step 4's relocation path is a deliberate, human-directed command, never something this skill decides or automates by itself.
- Never write a decision the human did not give you.
- One question, no advocacy, governs every prompt in step 2, for every state — never argue for an option, never read whether a repo deserves one.
- Every prompt in step 2 is never destructive by accident: on a repo with a
recorded answer, its first option always keeps that answer; on a repo
with none,
Not nowis always offered and always records nothing. - If
state=no-config, MemContinuum was never bootstrapped on this machine. Point atmemcontinuum-setup.sh; do not run it unasked. - When two
status: activelinks (in the same topic or across topics) genuinely conflict, the citation tiers already decide: the higher tier prevails (CONSTRAINT over HOLD over CONTEXT — SCHEMA section 4), and say so to whoever asked. Equal tier is not yours to pick between: two conflicting agent-level rulings are resolved by the orchestrator writing a new link that reverses one of them (TOP-xxxx Ln,kind: reversed, naming the other as the reason); two conflicting owner-level rulings go back to the owner, and the owner's answer is recorded as a newowner-verbatimlink, not inferred.
6. The new-file nudge's language offer
hooks/newfile-nudge.sh (newlang-nudge) can present its own three-option
decision point directly in a Write hook's additionalContext,
when a new file's extension is one this engine supports but the project has
not wired: wire it now, never mention it here again, or not now.
This is a different question than step 2's ("does this repo keep a store at
all") and the human answers it right there, in that turn — act on whichever
option they pick using this step, not step 3.
-
Wire it now — this is the SAME reconfiguration step 4 already covers for adding a language to an already-wired repo: recover the complete current parameter set (step 4's registry-first recovery, or its rendered-hooks fallback when the registry lacks it), add the new language to
--langs, and re-runrepo-init.sh --adopt-only ... --record-decisionexactly as step 4 describes. Follow step 4's own text; nothing here restates it. -
Never mention it again — the decline is per EXTENSION (
--never-ext/MEMCONTINUUM_NEVER_EXTS, which this hook already honours before any other gate), not per language: a language with more than one known extension (javascript:.js/.jsx/.mjs/.cjs) needs every one of them named to actually go silent — the nudge's own option 2 text names them all. Recording it costs one of two different things, and only this repo's registry row says which: read$MEMCONTINUUM_HOME/decisions.tsv(step 1's ownkey=line) viamc_registry_lookup, the same way step 4's registry-first recovery already does.- The row already records this repo's claude-dirs AND code-roots
(current-format, a
--record-decisioninstall) — one command:scripts/memcontinuum-update.sh --never-ext .EXT[,.EXT...] --repo REPO(dry-run first to preview; it is additive — a never-list already on the row is never dropped, only extended). - The row lacks claude-dirs (refuses, naming the
--apply --repo REPO --claude-dir DIR [...]migration to run first), lacks code-roots (refusesno-code-root:— repo-init ignores--langs/--never-extwith no code-root to wire them into), or there is no row at all, includingdecision=nonewithwiring=full(wired before the registry existed) — refusesno-wired-row:—memcontinuum-update.shrefuses outright in every one of these rather than guessing at a claude-dir. There is no one-command path here: recover the complete current parameter set the way step 4 does, add the extension(s) to--never-ext, and re-run the SAME fullrepo-init.sh --adopt-only ... --record-decisioncommand option 1 uses — not a shortcut, and never implied as one to the human.
- The row already records this repo's claude-dirs AND code-roots
(current-format, a
-
Not now — nothing is recorded; the language stays unwired until decided. The hook's own per-session dedupe (never permanent — see its own comment) means a later session's next matching write re-emits this same decision point into the assistant's own context, the same as the SessionStart detector above — not a promise that a human is re-asked.