refactor: migrate ShardMind hooks to split bootstrap/personalize lifecycle (#75)#96
Conversation
…cycle (#75) Replace the single post-install hook with ShardMind's native bootstrap (git init + QMD index, runs always, unmanaged writes only) and personalize (North Star, first-install only, managed writes only) slots. Invariant 2 is now engine-enforced: the personalize hook drops its valuesAreDefaults check (the engine refuses to call it on an all-defaults install) but keeps the empty-user_name guard. Per-slot context types replace the shared HookCtx. Declare requires.shardmind ">=0.1.3" so engines that predate the split refuse the install instead of silently skipping the unknown slots. Add hooks.bootstrap.fingerprint so a future QMD index-schema change can trigger a re-bootstrap on update. Harden CI: test.yml now triggers on .shardmind/** (the hooks live there but were outside the test path filter), and a dependency-free shard-contract test pins the slot set, the absence of post-install (dual-fire guard), and the requires.shardmind floor, enforced at both test- and release-time. Docs updated (ARCHITECTURE.md + all four READMEs). Versions, CHANGELOG, and fingerprints intentionally untouched: the tag-driven release workflow owns them. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR migrates the shard’s hook implementation from ShardMind’s deprecated single post-install hook to the split native lifecycle (bootstrap / personalize / post-update), and pins the minimum ShardMind engine version required to run those slots. It also updates documentation and CI/tests to ensure hook-only changes are continuously validated.
Changes:
- Split former
post-installresponsibilities intobootstrap(git + optional QMD bootstrap; unmanaged writes only) andpersonalize(North Star managed-file edit; first-install only). - Update
.shardmind/shard.yamlto declare native slots, addhooks.bootstrap.fingerprint, and requireshardmind >= 0.1.3. - Expand CI path filters and add contract/unit tests to guard the shard/engine coupling and new hook behaviors; refresh docs/READMEs to reflect the new lifecycle.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| README.md | Updates install narrative and folder tour to reflect bootstrap/personalize lifecycle. |
| README.zh-CN.md | Same as README.md (Chinese translation). |
| README.ko.md | Same as README.md (Korean translation). |
| README.ja.md | Same as README.md (Japanese translation). |
| ARCHITECTURE.md | Updates v6 install path explanation + invariants to match engine-enforced personalize gating. |
| .shardmind/shard.yaml | Declares native lifecycle hooks, adds bootstrap fingerprint, and pins requires.shardmind >= 0.1.3. |
| .shardmind/shard-schema.yaml | Updates invariant commentary to reflect engine-enforced personalize gating + empty-name secondary guard. |
| .shardmind/hooks/bootstrap.ts | Implements bootstrap hook (git init + optional QMD bootstrap), unmanaged writes only. |
| .shardmind/hooks/personalize.ts | Implements personalize hook for North Star managed-file personalization + idempotency/ENOENT tolerance. |
| .shardmind/hooks/post-update.ts | Retypes to PostUpdateContext and updates guidance to prefer bootstrap fingerprint bumps for re-bootstrap. |
| .github/workflows/test.yml | Ensures CI runs when .shardmind/** changes. |
| .claude/scripts/tests/shard-contract.test.ts | Adds contract test pinning lifecycle slots, absence of post-install, and engine floor. |
| .claude/scripts/tests/personalize-hook.test.ts | Updates unit tests to target personalize hook and its empty-name guard/idempotency. |
| .claude/scripts/tests/bootstrap-hook.test.ts | Adds unit tests for bootstrap gating/skip behavior without spawning subprocesses. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| test("bootstrap declares a script and a fingerprint", () => { | ||
| assert.match( | ||
| shardYaml, | ||
| /^\s+script:\s*\.shardmind\/hooks\/bootstrap\.ts\s*$/m, | ||
| "bootstrap.script must point at .shardmind/hooks/bootstrap.ts", | ||
| ); | ||
| assert.match( | ||
| shardYaml, | ||
| /^\s+fingerprint:\s*"[^"]+"\s*$/m, | ||
| "bootstrap.fingerprint must be set (re-bootstrap-on-update marker)", | ||
| ); | ||
| }); |
There was a problem hiding this comment.
Good catch — fixed in 60e0e0a. Added a dependency-free blockUnder() helper that extracts the indented sub-block under a mapping key, and scoped both the script: and fingerprint: assertions to the hooks.bootstrap block. A stray key elsewhere can no longer produce a false pass.
…p block Address Copilot review on #96: the script/fingerprint assertions scanned the whole shard.yaml, so a stray `script:`/`fingerprint:` key elsewhere could mask bootstrap losing its own. Add a dependency-free blockUnder() helper that extracts the indented sub-block under a mapping key and scope both assertions to it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Migrates obsidian-mind off ShardMind's deprecated single
post-installhook onto the native split lifecycle —bootstrap(unmanaged artifacts) /personalize(managed-file edits) /post-update— and declares the engine floor that the new slots require. Closes #75.Per #75's "same release window" intent: ShardMind ships the split lifecycle + the
requires.shardmindcheck in 0.1.3 (both currently in its[Unreleased]); this PR migrates the shard side in lockstep so users upgrading both don't hit theHOOK_POST_INSTALL_DEPRECATEDwarning.What changed
Hooks (
.shardmind/hooks/)bootstrap.ts(new) —ensureGitRepo+bootstrapQmd(gated onqmd_enabled). Runs always; unmanaged writes only.personalize.ts(new) —personalizeNorthStar. First-install only. Theif (!ctx.valuesAreDefaults)gate is removed — Invariant 2 is now engine-enforced (the engine declines to callpersonalizeon an all-defaults install) — while the empty-user_nameguard stays.post-install.tsdeleted;post-update.tsretyped toPostUpdateContextand re-pointed atbootstrap.fingerprintas the preferred re-bootstrap mechanism.BootstrapContext/PersonalizeContext/PostUpdateContext) replace the sharedHookCtx, matching ShardMinddocs/AUTHORING.md.Manifest (
.shardmind/shard.yaml)hooks:block:bootstrap(script+fingerprint: "qmd-v1"),personalize,post-update.requires.shardmind: ">=0.1.3"— older engines refuse the install (SHARDMIND_VERSION_MISMATCH) instead of silently skipping the unknown slots.CI / contract
test.ymlnow triggers on.shardmind/**— the hook implementations live there but were outside the test path filter, so hook-only changes ran no tests/typecheck.shard-contract.test.ts(dependency-free, mirrorshook-config.test.ts): asserts the three native slots, the absence ofpost-install(dual-fire guard), andrequires.shardmind ≥ 0.1.3. Runs in both the test job and the release job (which already runs that test dir), so the coupling is guarded at release time with norelease.ymledit.Docs —
ARCHITECTURE.md(install paths, Invariant 2, contract surface) + install-flow prose and folder tour in all four READMEs.Not in scope
.scripts/consolidation (Untie hook scripts from .claude/ and consolidate under .scripts/ #71) — blocked on shardmind#88 (rename migrations, v0.2).version_fingerprintsare intentionally untouched: the tag-driven release workflow (generate-changelog.ts) owns them.Test plan
npm run typecheckclean;npm testgreen — 527 pass (was 523)npm test(cwd.claude/scripts) andnode --experimental-strip-types --test '.claude/scripts/tests/'*.test.ts(repo root, asrelease.ymlinvokes it)shard.yaml(floor0.1.3, all slots present,post-installabsent, fingerprint present)Before merge / release
shard.yamlandMIN_SHARDMINDinshard-contract.test.ts.shardmind_versionand Migrate post-install hook to new lifecycle (depends on shardmind#102) #75 cites #119 as the source — the actual field isrequires.shardmindand the source issue is #121.🤖 Generated with Claude Code