Skip to content

refactor: migrate ShardMind hooks to split bootstrap/personalize lifecycle (#75)#96

Merged
breferrari merged 2 commits into
mainfrom
feat/hook-lifecycle-split
Jun 1, 2026
Merged

refactor: migrate ShardMind hooks to split bootstrap/personalize lifecycle (#75)#96
breferrari merged 2 commits into
mainfrom
feat/hook-lifecycle-split

Conversation

@breferrari

Copy link
Copy Markdown
Owner

Summary

Migrates obsidian-mind off ShardMind's deprecated single post-install hook 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.shardmind check in 0.1.3 (both currently in its [Unreleased]); this PR migrates the shard side in lockstep so users upgrading both don't hit the HOOK_POST_INSTALL_DEPRECATED warning.

What changed

Hooks (.shardmind/hooks/)

  • bootstrap.ts (new) — ensureGitRepo + bootstrapQmd (gated on qmd_enabled). Runs always; unmanaged writes only.
  • personalize.ts (new) — personalizeNorthStar. First-install only. The if (!ctx.valuesAreDefaults) gate is removed — Invariant 2 is now engine-enforced (the engine declines to call personalize on an all-defaults install) — while the empty-user_name guard stays.
  • post-install.ts deleted; post-update.ts retyped to PostUpdateContext and re-pointed at bootstrap.fingerprint as the preferred re-bootstrap mechanism.
  • Per-slot inlined context types (BootstrapContext / PersonalizeContext / PostUpdateContext) replace the shared HookCtx, matching ShardMind docs/AUTHORING.md.

Manifest (.shardmind/shard.yaml)

  • Nested 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.yml now triggers on .shardmind/** — the hook implementations live there but were outside the test path filter, so hook-only changes ran no tests/typecheck.
  • New shard-contract.test.ts (dependency-free, mirrors hook-config.test.ts): asserts the three native slots, the absence of post-install (dual-fire guard), and requires.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 no release.yml edit.

DocsARCHITECTURE.md (install paths, Invariant 2, contract surface) + install-flow prose and folder tour in all four READMEs.

Not in scope

Test plan

  • npm run typecheck clean; npm test green — 527 pass (was 523)
  • Tests pass from both CWDs: npm test (cwd .claude/scripts) and node --experimental-strip-types --test '.claude/scripts/tests/'*.test.ts (repo root, as release.yml invokes it)
  • Contract regexes sanity-checked against the real shard.yaml (floor 0.1.3, all slots present, post-install absent, fingerprint present)

Before merge / release

🤖 Generated with Claude Code

…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>
Copilot AI review requested due to automatic review settings June 1, 2026 23:35
@breferrari breferrari self-assigned this Jun 1, 2026

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 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-install responsibilities into bootstrap (git + optional QMD bootstrap; unmanaged writes only) and personalize (North Star managed-file edit; first-install only).
  • Update .shardmind/shard.yaml to declare native slots, add hooks.bootstrap.fingerprint, and require shardmind >= 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.

Comment on lines +54 to +65
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)",
);
});

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

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>
@breferrari breferrari merged commit 78484b1 into main Jun 1, 2026
8 checks passed
@breferrari breferrari deleted the feat/hook-lifecycle-split branch June 1, 2026 23:59
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.

Migrate post-install hook to new lifecycle (depends on shardmind#102)

2 participants