Skip to content

release: @focus-mcp/core v1.5.1 — sync develop → main#84

Merged
samuelds merged 59 commits into
mainfrom
sync/develop-to-main-1.5.1
Apr 30, 2026
Merged

release: @focus-mcp/core v1.5.1 — sync develop → main#84
samuelds merged 59 commits into
mainfrom
sync/develop-to-main-1.5.1

Conversation

@samuelds
Copy link
Copy Markdown
Contributor

Patch release sync. Bumps: @focus-mcp/core 1.5.0→1.5.1, @focus-mcp/sdk 1.5.0→1.5.1 (linked), @focus-mcp/validator 1.0.6→1.0.7. Branch includes changeset version bump commit and back-merge.

samuelds and others added 30 commits April 15, 2026 19:22
The develop ruleset requires CodeQL results before merge, but the
workflow only triggered on main. PRs targeting develop were
deadlocked. Add develop to push and pull_request triggers, mirroring
the client repo's setup.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(prd): split monolithic PRD into per-repo PRDs

Rewrite core/PRD.md to focus solely on @focusmcp/core (lib TS):
3 piliers, manifest, SDK, validator, CLI, marketplace client, brick loader.
Reflects current architecture (core in WebView, Tauri sole HTTP gateway).

Companion PRDs added in client/ and marketplace/ repos.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): address Copilot review feedback

- Replace cross-repo relative links with absolute GitHub URLs
- Clarify monorepo layout: package `packages/core`, not `core/`
- Manifest naming: parser only enforces kebab-case; the
  `focus-` prefix is a marketplace convention
- Validator section: list only checks actually implemented; defer
  namespace/dependency/bypass checks to P1
- Stack and Decisions tables: replace "Zod / JSON Schema" with
  "custom validator (parseManifest)"; JSON Schema is used only
  for tools[].inputSchema

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): add SPDX headers for REUSE compliance

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(core): add brick loader with abstract source

`loadBricks({ source })` reads installed bricks via an abstract `BrickSource`
(list/readManifest/loadModule), validates manifests with `parseManifest`,
ensures the loaded module exports a Brick whose manifest matches the source
declaration, and collects per-brick failures without aborting the load.

Browser-compatible: no direct FS access — the source is injected by the host
(Tauri commands for desktop, in-memory for tests).

11 tests, 100% line / 94.4% branch coverage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(core): tighten brick-loader validation per review

- Parse the module-provided brick.manifest with `parseManifest` and
  enforce strict equality against the source manifest (canonical JSON
  comparison). Catches divergence in deps/tools, not just name.
- Split default-export check into two messages: missing default vs
  default-not-an-object (clearer diagnostics for `default: 42` etc.).
- Move the manifest-shape check out of the Brick contract assertion so
  malformed `brick.manifest` produces an INVALID_MANIFEST error rather
  than a misleading "does not implement Brick contract".

3 new tests (divergence, malformed module manifest, default-not-object).
14 tests total, 100% line / 96.3% branch coverage on the loader.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(core): add marketplace resolver (parse + find + semver + updates)

Pure, browser-compatible module that consumes a catalog.json as
published by the FocusMCP marketplace. Does no I/O — the host injects
raw JSON and this module validates, normalizes and queries it.

Public API:
- parseCatalog(raw): Catalog — structural validation aligned with the
  published JSON Schema (kebab-case names, semver versions, typed
  source variants).
- findBrick(catalog, name): CatalogBrick | undefined
- compareSemver(a, b): -1 | 0 | 1 — minimal inline implementation
  (core + optional pre-release; no build metadata, no range matching
  yet — added at need).
- listUpdates(installed, catalog): UpdateInfo[]

20 unit tests (parseCatalog, findBrick, compareSemver incl. pre-release
ordering per semver §11, listUpdates). Full suite: 127 passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(core): address Copilot review on marketplace resolver

- Thread a full `loc` path into requireString/optionalString and the
  array variants so validation errors now produce
  "bricks[3].owner.email must be a string" instead of just
  "email must be a string". Much easier to diagnose.
- Tighten SEMVER regex: reject numeric pre-release identifiers with
  leading zeros (per semver 2.0 §9).
- Extend SEMVER regex to accept optional build metadata ("+..."),
  matching the manifest parser and semver 2.0 §10. Build metadata is
  captured but discarded for precedence comparisons, as required.
- parseTool: use conditional spread for `inputSchema` instead of
  emitting `inputSchema: undefined` when the field is missing.
  Consistent with how other optionals are handled in this file and
  compatible with `exactOptionalPropertyTypes`.
- Add 2 tests: build metadata is ignored when comparing; compareSemver
  rejects pre-release identifiers with leading zeros.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style: migrate indent from 2 to 4 spaces (Biome config)

Standardize indentation to 4 spaces across all projects.
Biome formatter config updated accordingly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: update .editorconfig indent_size to 4

Aligns with Biome formatter config to prevent editor/formatter conflicts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: align biome schema to 2.4.11 and format new develop files

Update $schema version to match installed Biome CLI.
Reformat brick-loader and marketplace resolver (merged from develop).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: add CLAUDE.md capturing the post-pivot agent guidance

Replaces the former personal memory system under
~/.claude/projects/**/memory/ with an in-repo, version-controlled
file that is auto-loaded by Claude Code (and any agents.md-compatible
tool).

Covers: project overview, the 4-repo ecosystem post CLI-first pivot
(2026-04-16), the 8 non-negotiable conventions (TDD, strict scope, pro
standards, English public-facing, gitflow, npm orgs, rulesets
checklist), this repo's specifics (lib-only, packages/core, cli moved
out, browser-compatible, no HTTP transport), and the standard feature
workflow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(claude.md): fix repo count and clarify English rule exceptions

Heading: 3 repos actifs + 1 archivé (table listed 3 not 4). Rule #5
(English public-facing): reframe as from-now-on plus list explicit
exceptions (PRD.md and CLAUDE.md stay French, existing docs stay
French until substantial rewrite) so the rule no longer contradicts
the current state of the repo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: configure GitHub Packages registry for @focusmcp/* packages

Add publishConfig with npm.pkg.github.com registry and .npmrc for
scoped package resolution. Preparation for dev package publishing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add SPDX header to .npmrc

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add mandatory prefix field to brick manifest

Tools are exposed as {prefix}_{toolName} to prevent collisions
between bricks and protect internal tools (no prefix).
Prefix must be unique per registry, lowercase alphanumeric.
Reserved prefixes: focus, focusmcp, mcp, internal, system.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: add edge case tests for prefix coverage (registry 98%+)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* ci: add Claude Code Review action

* ci: use Claude Max OAuth instead of API key

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): add id-token permission for OIDC auth

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- actions/checkout v4 → v5
- actions/setup-node v4 → v5
- actions/upload-artifact v4 → v5
- github/codeql-action/init v3 → v4
- github/codeql-action/analyze v3 → v4

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…#15)

Tool names in mcp-brick.json must now be bare alphanumeric (e.g. "search"
not "indexer_search"). The prefix is added by the runtime when exposing
tools via MCP (prefix_toolname).

- manifest.ts: reject tool names containing non-alphanumeric chars
- tool.ts: update JSDoc to reflect new convention
- Tests updated to use bare tool names throughout

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…les (#16)

* feat(marketplace): add catalog-store, catalog-fetcher, installer modules

Pure, browser-compatible marketplace management for FocusMCP core:

- catalog-store: manage catalog source URLs (CRUD, multi-marketplace)
- catalog-fetcher: fetch + aggregate catalogs from multiple sources
- installer: plan + execute brick install/remove via npm
- resolver: extend CatalogBrickSource with npm source type

All modules use dependency injection (IO interfaces) — no direct
node: imports. The CLI provides concrete implementations.

257 tests passing, 0 typecheck errors, 0 lint errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: export marketplace modules from core package entry point

Add catalog-store, catalog-fetcher, and installer exports to index.ts
so they are part of the public API and pass knip unused-export checks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: clean up knip config — remove stale entries

Remove deprecated packages/cli workspace, redundant entry patterns
(vitest.config, playwright.config), and unused playwright binary ignore.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: extract shared validation helpers to reduce duplication

Move requireObject, requireString, optionalString, requireArray,
requireStringArray, optionalStringArray, requireBoolean into a shared
helpers.ts module. Imported by resolver, catalog-store, and installer.

Reduces jscpd duplication from 1.85% to 0.36% (threshold: 1%).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Switch from gh-pages to raw GitHub content serving for the catalog.
URL: https://raw.githubusercontent.com/focus-mcp/marketplace/develop/publish/catalog.json

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add .github/workflows/publish-dev.yml: publishes @focusmcp/* to
  GitHub Packages on push to develop (skips packages with private:true)
- Set "private": false in packages/core, sdk, validator package.json
- Add direct_prompt to claude-review.yml for inline PR review guidance

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Rename all package names, workflow scopes, .npmrc registry bindings,
and source/test file references from @focusmcp/* to @focus-mcp/*.
Email addresses unchanged.

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(ci): add dev publish workflow with auto-versioning

- dev-publish.yml: publish @focus-mcp/{core,sdk,validator} to npmjs.org with --tag dev
- Auto-computed version: <base>-dev.<N> (N = commits since last tag)
- Mark packages/cli stub as private to prevent accidental publish

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: rename scope @focus-mcp@focusmcp + dev publish workflow

- Rename @focus-mcp/{core,sdk,validator} → @focusmcp/{core,sdk,validator}
- Add dev-publish.yml for auto-versioned dev releases
- Mark packages/cli stub as private
- Update all docs, workflows, configs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: regenerate pnpm-lock.yaml after scope rename

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(ci): use GitHub Packages registry instead of npmjs.org

- registry-url → npm.pkg.github.com
- NODE_AUTH_TOKEN → GITHUB_TOKEN (no separate secret needed)
- Add packages: write permission

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: rename scope to @focus-mcp + npmjs.org for dev publish

- All packages: @focus-mcp/{core,sdk,validator}
- dev-publish.yml: registry.npmjs.org + NPM_TOKEN
- Regenerated lockfile

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: regenerate lockfile

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: update scope references to @focus-mcp in docs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
)

- Add id-token:write to dev-publish.yml (required for npm provenance)
- Remove publish-dev.yml (duplicate, was targeting GitHub Packages)

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore(release): bump @focus-mcp/{core,sdk,validator} to 1.0.0

- Bump all 3 public packages from 0.0.0 to 1.0.0
- Add stable-publish.yml workflow (triggers on push to main → npm @latest)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(ci): remove release.yml — stable-publish.yml handles releases

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(ci): remove claude-review.yml (fails on workflow changes)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* revert: restore claude-review.yml — will work after main merge

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: v1 cleanup — rewrite README, add VISION.md and ARCHITECTURE.md

- README.md: English public-facing with install, quick start, architecture
- VISION.md: short "why" doc (1 page)
- ARCHITECTURE.md: technical doc for contributors
- AGENTS.md, CONTRIBUTING.md, ROADMAP.md: updated for v1.0.0 state
- Archived pre-v1 PRD (internal French planning doc)
- npm metadata (description, keywords, author, homepage) on sdk and validator

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: consolidate AGENTS.md + AI transparency

- merge CLAUDE.md into AGENTS.md (single source of truth per agents.md spec)
- add AI-assisted development section to README
- add AI-assisted contributions section to CONTRIBUTING

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(ci): allow `release` commit type in commitlint

Release commits (e.g. `release: v1.0.0`) were rejected by the type-enum.
Add `release` as a valid type since we already use it on main branches.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(deps): pin uuid to ^14.0.0 via pnpm override (GHSA-w5hq-g745-h8pq)

Transitive from @cyclonedx/cdxgen (dev only). Not in runtime bundle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…31)

The v1 action deprecated direct_prompt. Without a valid trigger input,
PRs got a green check but Claude never posted any review.

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The claude-code-action needs full git history to fetch base branches.
Without a preceding actions/checkout@v5 with fetch-depth: 0, the action
fails with "git fetch origin develop --depth=1: exit 128".

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: sync develop → main (#9)

* ci: run CodeQL on develop branch as well (#4)

The develop ruleset requires CodeQL results before merge, but the
workflow only triggered on main. PRs targeting develop were
deadlocked. Add develop to push and pull_request triggers, mirroring
the client repo's setup.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): split PRD into per-repo focused docs (#2)

* docs(prd): split monolithic PRD into per-repo PRDs

Rewrite core/PRD.md to focus solely on @focusmcp/core (lib TS):
3 piliers, manifest, SDK, validator, CLI, marketplace client, brick loader.
Reflects current architecture (core in WebView, Tauri sole HTTP gateway).

Companion PRDs added in client/ and marketplace/ repos.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): address Copilot review feedback

- Replace cross-repo relative links with absolute GitHub URLs
- Clarify monorepo layout: package `packages/core`, not `core/`
- Manifest naming: parser only enforces kebab-case; the
  `focus-` prefix is a marketplace convention
- Validator section: list only checks actually implemented; defer
  namespace/dependency/bypass checks to P1
- Stack and Decisions tables: replace "Zod / JSON Schema" with
  "custom validator (parseManifest)"; JSON Schema is used only
  for tools[].inputSchema

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): add SPDX headers for REUSE compliance

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(core): add brick loader with abstract source (#3)

* feat(core): add brick loader with abstract source

`loadBricks({ source })` reads installed bricks via an abstract `BrickSource`
(list/readManifest/loadModule), validates manifests with `parseManifest`,
ensures the loaded module exports a Brick whose manifest matches the source
declaration, and collects per-brick failures without aborting the load.

Browser-compatible: no direct FS access — the source is injected by the host
(Tauri commands for desktop, in-memory for tests).

11 tests, 100% line / 94.4% branch coverage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(core): tighten brick-loader validation per review

- Parse the module-provided brick.manifest with `parseManifest` and
  enforce strict equality against the source manifest (canonical JSON
  comparison). Catches divergence in deps/tools, not just name.
- Split default-export check into two messages: missing default vs
  default-not-an-object (clearer diagnostics for `default: 42` etc.).
- Move the manifest-shape check out of the Brick contract assertion so
  malformed `brick.manifest` produces an INVALID_MANIFEST error rather
  than a misleading "does not implement Brick contract".

3 new tests (divergence, malformed module manifest, default-not-object).
14 tests total, 100% line / 96.3% branch coverage on the loader.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(core): marketplace resolver (parse + find + semver + updates) (#5)

* feat(core): add marketplace resolver (parse + find + semver + updates)

Pure, browser-compatible module that consumes a catalog.json as
published by the FocusMCP marketplace. Does no I/O — the host injects
raw JSON and this module validates, normalizes and queries it.

Public API:
- parseCatalog(raw): Catalog — structural validation aligned with the
  published JSON Schema (kebab-case names, semver versions, typed
  source variants).
- findBrick(catalog, name): CatalogBrick | undefined
- compareSemver(a, b): -1 | 0 | 1 — minimal inline implementation
  (core + optional pre-release; no build metadata, no range matching
  yet — added at need).
- listUpdates(installed, catalog): UpdateInfo[]

20 unit tests (parseCatalog, findBrick, compareSemver incl. pre-release
ordering per semver §11, listUpdates). Full suite: 127 passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(core): address Copilot review on marketplace resolver

- Thread a full `loc` path into requireString/optionalString and the
  array variants so validation errors now produce
  "bricks[3].owner.email must be a string" instead of just
  "email must be a string". Much easier to diagnose.
- Tighten SEMVER regex: reject numeric pre-release identifiers with
  leading zeros (per semver 2.0 §9).
- Extend SEMVER regex to accept optional build metadata ("+..."),
  matching the manifest parser and semver 2.0 §10. Build metadata is
  captured but discarded for precedence comparisons, as required.
- parseTool: use conditional spread for `inputSchema` instead of
  emitting `inputSchema: undefined` when the field is missing.
  Consistent with how other optionals are handled in this file and
  compatible with `exactOptionalPropertyTypes`.
- Add 2 tests: build metadata is ignored when comparing; compareSemver
  rejects pre-release identifiers with leading zeros.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style: migrate indent from 2 to 4 spaces (#6)

* style: migrate indent from 2 to 4 spaces (Biome config)

Standardize indentation to 4 spaces across all projects.
Biome formatter config updated accordingly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: update .editorconfig indent_size to 4

Aligns with Biome formatter config to prevent editor/formatter conflicts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: align biome schema to 2.4.11 and format new develop files

Update $schema version to match installed Biome CLI.
Reformat brick-loader and marketplace resolver (merged from develop).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add CLAUDE.md (agent guidance, replaces ~/.claude memory) (#7)

* docs: add CLAUDE.md capturing the post-pivot agent guidance

Replaces the former personal memory system under
~/.claude/projects/**/memory/ with an in-repo, version-controlled
file that is auto-loaded by Claude Code (and any agents.md-compatible
tool).

Covers: project overview, the 4-repo ecosystem post CLI-first pivot
(2026-04-16), the 8 non-negotiable conventions (TDD, strict scope, pro
standards, English public-facing, gitflow, npm orgs, rulesets
checklist), this repo's specifics (lib-only, packages/core, cli moved
out, browser-compatible, no HTTP transport), and the standard feature
workflow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(claude.md): fix repo count and clarify English rule exceptions

Heading: 3 repos actifs + 1 archivé (table listed 3 not 4). Rule #5
(English public-facing): reframe as from-now-on plus list explicit
exceptions (PRD.md and CLAUDE.md stay French, existing docs stay
French until substantial rewrite) so the rule no longer contradicts
the current state of the repo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: configure GitHub Packages for @focusmcp/* packages (#8)

* chore: configure GitHub Packages registry for @focusmcp/* packages

Add publishConfig with npm.pkg.github.com registry and .npmrc for
scoped package resolution. Preparation for dev package publishing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add SPDX header to .npmrc

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: claude <claude@localhost>

* chore: sync develop → main (conflict resolution) (#13)

* feat(core): add brick loader with abstract source (#3)

* feat(core): add brick loader with abstract source

`loadBricks({ source })` reads installed bricks via an abstract `BrickSource`
(list/readManifest/loadModule), validates manifests with `parseManifest`,
ensures the loaded module exports a Brick whose manifest matches the source
declaration, and collects per-brick failures without aborting the load.

Browser-compatible: no direct FS access — the source is injected by the host
(Tauri commands for desktop, in-memory for tests).

11 tests, 100% line / 94.4% branch coverage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(core): tighten brick-loader validation per review

- Parse the module-provided brick.manifest with `parseManifest` and
  enforce strict equality against the source manifest (canonical JSON
  comparison). Catches divergence in deps/tools, not just name.
- Split default-export check into two messages: missing default vs
  default-not-an-object (clearer diagnostics for `default: 42` etc.).
- Move the manifest-shape check out of the Brick contract assertion so
  malformed `brick.manifest` produces an INVALID_MANIFEST error rather
  than a misleading "does not implement Brick contract".

3 new tests (divergence, malformed module manifest, default-not-object).
14 tests total, 100% line / 96.3% branch coverage on the loader.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(core): marketplace resolver (parse + find + semver + updates) (#5)

* feat(core): add marketplace resolver (parse + find + semver + updates)

Pure, browser-compatible module that consumes a catalog.json as
published by the FocusMCP marketplace. Does no I/O — the host injects
raw JSON and this module validates, normalizes and queries it.

Public API:
- parseCatalog(raw): Catalog — structural validation aligned with the
  published JSON Schema (kebab-case names, semver versions, typed
  source variants).
- findBrick(catalog, name): CatalogBrick | undefined
- compareSemver(a, b): -1 | 0 | 1 — minimal inline implementation
  (core + optional pre-release; no build metadata, no range matching
  yet — added at need).
- listUpdates(installed, catalog): UpdateInfo[]

20 unit tests (parseCatalog, findBrick, compareSemver incl. pre-release
ordering per semver §11, listUpdates). Full suite: 127 passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(core): address Copilot review on marketplace resolver

- Thread a full `loc` path into requireString/optionalString and the
  array variants so validation errors now produce
  "bricks[3].owner.email must be a string" instead of just
  "email must be a string". Much easier to diagnose.
- Tighten SEMVER regex: reject numeric pre-release identifiers with
  leading zeros (per semver 2.0 §9).
- Extend SEMVER regex to accept optional build metadata ("+..."),
  matching the manifest parser and semver 2.0 §10. Build metadata is
  captured but discarded for precedence comparisons, as required.
- parseTool: use conditional spread for `inputSchema` instead of
  emitting `inputSchema: undefined` when the field is missing.
  Consistent with how other optionals are handled in this file and
  compatible with `exactOptionalPropertyTypes`.
- Add 2 tests: build metadata is ignored when comparing; compareSemver
  rejects pre-release identifiers with leading zeros.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style: migrate indent from 2 to 4 spaces (#6)

* style: migrate indent from 2 to 4 spaces (Biome config)

Standardize indentation to 4 spaces across all projects.
Biome formatter config updated accordingly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: update .editorconfig indent_size to 4

Aligns with Biome formatter config to prevent editor/formatter conflicts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: align biome schema to 2.4.11 and format new develop files

Update $schema version to match installed Biome CLI.
Reformat brick-loader and marketplace resolver (merged from develop).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: configure GitHub Packages for @focusmcp/* packages (#8)

* chore: configure GitHub Packages registry for @focusmcp/* packages

Add publishConfig with npm.pkg.github.com registry and .npmrc for
scoped package resolution. Preparation for dev package publishing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add SPDX header to .npmrc

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: mandatory tool prefix in brick manifest (#10)

* feat: add mandatory prefix field to brick manifest

Tools are exposed as {prefix}_{toolName} to prevent collisions
between bricks and protect internal tools (no prefix).
Prefix must be unique per registry, lowercase alphanumeric.
Reserved prefixes: focus, focusmcp, mcp, internal, system.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: add edge case tests for prefix coverage (registry 98%+)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* ci: add Claude Code Review action (#11)

* ci: add Claude Code Review action

* ci: use Claude Max OAuth instead of API key

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): add id-token permission for OIDC auth

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: claude <claude@localhost>

* release: v1.0.0 — sync develop → main (#29)

* ci: run CodeQL on develop branch as well (#4)

The develop ruleset requires CodeQL results before merge, but the
workflow only triggered on main. PRs targeting develop were
deadlocked. Add develop to push and pull_request triggers, mirroring
the client repo's setup.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): split PRD into per-repo focused docs (#2)

* docs(prd): split monolithic PRD into per-repo PRDs

Rewrite core/PRD.md to focus solely on @focusmcp/core (lib TS):
3 piliers, manifest, SDK, validator, CLI, marketplace client, brick loader.
Reflects current architecture (core in WebView, Tauri sole HTTP gateway).

Companion PRDs added in client/ and marketplace/ repos.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): address Copilot review feedback

- Replace cross-repo relative links with absolute GitHub URLs
- Clarify monorepo layout: package `packages/core`, not `core/`
- Manifest naming: parser only enforces kebab-case; the
  `focus-` prefix is a marketplace convention
- Validator section: list only checks actually implemented; defer
  namespace/dependency/bypass checks to P1
- Stack and Decisions tables: replace "Zod / JSON Schema" with
  "custom validator (parseManifest)"; JSON Schema is used only
  for tools[].inputSchema

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): add SPDX headers for REUSE compliance

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(core): add brick loader with abstract source (#3)

* feat(core): add brick loader with abstract source

`loadBricks({ source })` reads installed bricks via an abstract `BrickSource`
(list/readManifest/loadModule), validates manifests with `parseManifest`,
ensures the loaded module exports a Brick whose manifest matches the source
declaration, and collects per-brick failures without aborting the load.

Browser-compatible: no direct FS access — the source is injected by the host
(Tauri commands for desktop, in-memory for tests).

11 tests, 100% line / 94.4% branch coverage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(core): tighten brick-loader validation per review

- Parse the module-provided brick.manifest with `parseManifest` and
  enforce strict equality against the source manifest (canonical JSON
  comparison). Catches divergence in deps/tools, not just name.
- Split default-export check into two messages: missing default vs
  default-not-an-object (clearer diagnostics for `default: 42` etc.).
- Move the manifest-shape check out of the Brick contract assertion so
  malformed `brick.manifest` produces an INVALID_MANIFEST error rather
  than a misleading "does not implement Brick contract".

3 new tests (divergence, malformed module manifest, default-not-object).
14 tests total, 100% line / 96.3% branch coverage on the loader.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(core): marketplace resolver (parse + find + semver + updates) (#5)

* feat(core): add marketplace resolver (parse + find + semver + updates)

Pure, browser-compatible module that consumes a catalog.json as
published by the FocusMCP marketplace. Does no I/O — the host injects
raw JSON and this module validates, normalizes and queries it.

Public API:
- parseCatalog(raw): Catalog — structural validation aligned with the
  published JSON Schema (kebab-case names, semver versions, typed
  source variants).
- findBrick(catalog, name): CatalogBrick | undefined
- compareSemver(a, b): -1 | 0 | 1 — minimal inline implementation
  (core + optional pre-release; no build metadata, no range matching
  yet — added at need).
- listUpdates(installed, catalog): UpdateInfo[]

20 unit tests (parseCatalog, findBrick, compareSemver incl. pre-release
ordering per semver §11, listUpdates). Full suite: 127 passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(core): address Copilot review on marketplace resolver

- Thread a full `loc` path into requireString/optionalString and the
  array variants so validation errors now produce
  "bricks[3].owner.email must be a string" instead of just
  "email must be a string". Much easier to diagnose.
- Tighten SEMVER regex: reject numeric pre-release identifiers with
  leading zeros (per semver 2.0 §9).
- Extend SEMVER regex to accept optional build metadata ("+..."),
  matching the manifest parser and semver 2.0 §10. Build metadata is
  captured but discarded for precedence comparisons, as required.
- parseTool: use conditional spread for `inputSchema` instead of
  emitting `inputSchema: undefined` when the field is missing.
  Consistent with how other optionals are handled in this file and
  compatible with `exactOptionalPropertyTypes`.
- Add 2 tests: build metadata is ignored when comparing; compareSemver
  rejects pre-release identifiers with leading zeros.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style: migrate indent from 2 to 4 spaces (#6)

* style: migrate indent from 2 to 4 spaces (Biome config)

Standardize indentation to 4 spaces across all projects.
Biome formatter config updated accordingly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: update .editorconfig indent_size to 4

Aligns with Biome formatter config to prevent editor/formatter conflicts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: align biome schema to 2.4.11 and format new develop files

Update $schema version to match installed Biome CLI.
Reformat brick-loader and marketplace resolver (merged from develop).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add CLAUDE.md (agent guidance, replaces ~/.claude memory) (#7)

* docs: add CLAUDE.md capturing the post-pivot agent guidance

Replaces the former personal memory system under
~/.claude/projects/**/memory/ with an in-repo, version-controlled
file that is auto-loaded by Claude Code (and any agents.md-compatible
tool).

Covers: project overview, the 4-repo ecosystem post CLI-first pivot
(2026-04-16), the 8 non-negotiable conventions (TDD, strict scope, pro
standards, English public-facing, gitflow, npm orgs, rulesets
checklist), this repo's specifics (lib-only, packages/core, cli moved
out, browser-compatible, no HTTP transport), and the standard feature
workflow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(claude.md): fix repo count and clarify English rule exceptions

Heading: 3 repos actifs + 1 archivé (table listed 3 not 4). Rule #5
(English public-facing): reframe as from-now-on plus list explicit
exceptions (PRD.md and CLAUDE.md stay French, existing docs stay
French until substantial rewrite) so the rule no longer contradicts
the current state of the repo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: configure GitHub Packages for @focusmcp/* packages (#8)

* chore: configure GitHub Packages registry for @focusmcp/* packages

Add publishConfig with npm.pkg.github.com registry and .npmrc for
scoped package resolution. Preparation for dev package publishing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add SPDX header to .npmrc

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: mandatory tool prefix in brick manifest (#10)

* feat: add mandatory prefix field to brick manifest

Tools are exposed as {prefix}_{toolName} to prevent collisions
between bricks and protect internal tools (no prefix).
Prefix must be unique per registry, lowercase alphanumeric.
Reserved prefixes: focus, focusmcp, mcp, internal, system.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: add edge case tests for prefix coverage (registry 98%+)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* ci: add Claude Code Review action (#11)

* ci: add Claude Code Review action

* ci: use Claude Max OAuth instead of API key

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): add id-token permission for OIDC auth

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(ci): bump GitHub Actions to v5 (Node.js 24) (#14)

- actions/checkout v4 → v5
- actions/setup-node v4 → v5
- actions/upload-artifact v4 → v5
- github/codeql-action/init v3 → v4
- github/codeql-action/analyze v3 → v4

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: enforce bare tool names in manifest — prefix applied at runtime (#15)

Tool names in mcp-brick.json must now be bare alphanumeric (e.g. "search"
not "indexer_search"). The prefix is added by the runtime when exposing
tools via MCP (prefix_toolname).

- manifest.ts: reject tool names containing non-alphanumeric chars
- tool.ts: update JSDoc to reflect new convention
- Tests updated to use bare tool names throughout

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(marketplace): add catalog-store, catalog-fetcher, installer modules (#16)

* feat(marketplace): add catalog-store, catalog-fetcher, installer modules

Pure, browser-compatible marketplace management for FocusMCP core:

- catalog-store: manage catalog source URLs (CRUD, multi-marketplace)
- catalog-fetcher: fetch + aggregate catalogs from multiple sources
- installer: plan + execute brick install/remove via npm
- resolver: extend CatalogBrickSource with npm source type

All modules use dependency injection (IO interfaces) — no direct
node: imports. The CLI provides concrete implementations.

257 tests passing, 0 typecheck errors, 0 lint errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: export marketplace modules from core package entry point

Add catalog-store, catalog-fetcher, and installer exports to index.ts
so they are part of the public API and pass knip unused-export checks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: clean up knip config — remove stale entries

Remove deprecated packages/cli workspace, redundant entry patterns
(vitest.config, playwright.config), and unused playwright binary ignore.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: extract shared validation helpers to reduce duplication

Move requireObject, requireString, optionalString, requireArray,
requireStringArray, optionalStringArray, requireBoolean into a shared
helpers.ts module. Imported by resolver, catalog-store, and installer.

Reduces jscpd duplication from 1.85% to 0.36% (threshold: 1%).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: update default catalog URL to raw.githubusercontent.com (#17)

Switch from gh-pages to raw GitHub content serving for the catalog.
URL: https://raw.githubusercontent.com/focus-mcp/marketplace/develop/publish/catalog.json

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* ci: add GitHub Packages publish workflow for dev channel (#18)

- Add .github/workflows/publish-dev.yml: publishes @focusmcp/* to
  GitHub Packages on push to develop (skips packages with private:true)
- Set "private": false in packages/core, sdk, validator package.json
- Add direct_prompt to claude-review.yml for inline PR review guidance

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: rename npm scope from @focusmcp to @focus-mcp (#21)

Rename all package names, workflow scopes, .npmrc registry bindings,
and source/test file references from @focusmcp/* to @focus-mcp/*.
Email addresses unchanged.

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): add id-token permission for npm provenance (#22)

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(ci): dev publish workflow (#24)

* feat(ci): add dev publish workflow with auto-versioning

- dev-publish.yml: publish @focus-mcp/{core,sdk,validator} to npmjs.org with --tag dev
- Auto-computed version: <base>-dev.<N> (N = commits since last tag)
- Mark packages/cli stub as private to prevent accidental publish

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: rename scope @focus-mcp@focusmcp + dev publish workflow

- Rename @focus-mcp/{core,sdk,validator} → @focusmcp/{core,sdk,validator}
- Add dev-publish.yml for auto-versioned dev releases
- Mark packages/cli stub as private
- Update all docs, workflows, configs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: regenerate pnpm-lock.yaml after scope rename

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): use GitHub Packages for dev publish (#25)

* fix(ci): use GitHub Packages registry instead of npmjs.org

- registry-url → npm.pkg.github.com
- NODE_AUTH_TOKEN → GITHUB_TOKEN (no separate secret needed)
- Add packages: write permission

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: rename scope to @focus-mcp + npmjs.org for dev publish

- All packages: @focus-mcp/{core,sdk,validator}
- dev-publish.yml: registry.npmjs.org + NPM_TOKEN
- Regenerated lockfile

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: regenerate lockfile

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: update scope references to @focus-mcp in docs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): add id-token:write permission + remove duplicate workflows (#26)

- Add id-token:write to dev-publish.yml (required for npm provenance)
- Remove publish-dev.yml (duplicate, was targeting GitHub Packages)

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(release): v1.0.0 + stable-publish workflow (#27)

* chore(release): bump @focus-mcp/{core,sdk,validator} to 1.0.0

- Bump all 3 public packages from 0.0.0 to 1.0.0
- Add stable-publish.yml workflow (triggers on push to main → npm @latest)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(ci): remove release.yml — stable-publish.yml handles releases

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(ci): remove claude-review.yml (fails on workflow changes)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* revert: restore claude-review.yml — will work after main merge

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: claude <claude@localhost>

* release: sync develop → main (workflow fix + v1 cleanup) (#33)

* ci: run CodeQL on develop branch as well (#4)

The develop ruleset requires CodeQL results before merge, but the
workflow only triggered on main. PRs targeting develop were
deadlocked. Add develop to push and pull_request triggers, mirroring
the client repo's setup.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): split PRD into per-repo focused docs (#2)

* docs(prd): split monolithic PRD into per-repo PRDs

Rewrite core/PRD.md to focus solely on @focusmcp/core (lib TS):
3 piliers, manifest, SDK, validator, CLI, marketplace client, brick loader.
Reflects current architecture (core in WebView, Tauri sole HTTP gateway).

Companion PRDs added in client/ and marketplace/ repos.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): address Copilot review feedback

- Replace cross-repo relative links with absolute GitHub URLs
- Clarify monorepo layout: package `packages/core`, not `core/`
- Manifest naming: parser only enforces kebab-case; the
  `focus-` prefix is a marketplace convention
- Validator section: list only checks actually implemented; defer
  namespace/dependency/bypass checks to P1
- Stack and Decisions tables: replace "Zod / JSON Schema" with
  "custom validator (parseManifest)"; JSON Schema is used only
  for tools[].inputSchema

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): add SPDX headers for REUSE compliance

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(core): add brick loader with abstract source (#3)

* feat(core): add brick loader with abstract source

`loadBricks({ source })` reads installed bricks via an abstract `BrickSource`
(list/readManifest/loadModule), validates manifests with `parseManifest`,
ensures the loaded module exports a Brick whose manifest matches the source
declaration, and collects per-brick failures without aborting the load.

Browser-compatible: no direct FS access — the source is injected by the host
(Tauri commands for desktop, in-memory for tests).

11 tests, 100% line / 94.4% branch coverage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(core): tighten brick-loader validation per review

- Parse the module-provided brick.manifest with `parseManifest` and
  enforce strict equality against the source manifest (canonical JSON
  comparison). Catches divergence in deps/tools, not just name.
- Split default-export check into two messages: missing default vs
  default-not-an-object (clearer diagnostics for `default: 42` etc.).
- Move the manifest-shape check out of the Brick contract assertion so
  malformed `brick.manifest` produces an INVALID_MANIFEST error rather
  than a misleading "does not implement Brick contract".

3 new tests (divergence, malformed module manifest, default-not-object).
14 tests total, 100% line / 96.3% branch coverage on the loader.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(core): marketplace resolver (parse + find + semver + updates) (#5)

* feat(core): add marketplace resolver (parse + find + semver + updates)

Pure, browser-compatible module that consumes a catalog.json as
published by the FocusMCP marketplace. Does no I/O — the host injects
raw JSON and this module validates, normalizes and queries it.

Public API:
- parseCatalog(raw): Catalog — structural validation aligned with the
  published JSON Schema (kebab-case names, semver versions, typed
  source variants).
- findBrick(catalog, name): CatalogBrick | undefined
- compareSemver(a, b): -1 | 0 | 1 — minimal inline implementation
  (core + optional pre-release; no build metadata, no range matching
  yet — added at need).
- listUpdates(installed, catalog): UpdateInfo[]

20 unit tests (parseCatalog, findBrick, compareSemver incl. pre-release
ordering per semver §11, listUpdates). Full suite: 127 passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(core): address Copilot review on marketplace resolver

- Thread a full `loc` path into requireString/optionalString and the
  array variants so validation errors now produce
  "bricks[3].owner.email must be a string" instead of just
  "email must be a string". Much easier to diagnose.
- Tighten SEMVER regex: reject numeric pre-release identifiers with
  leading zeros (per semver 2.0 §9).
- Extend SEMVER regex to accept optional build metadata ("+..."),
  matching the manifest parser and semver 2.0 §10. Build metadata is
  captured but discarded for precedence comparisons, as required.
- parseTool: use conditional spread for `inputSchema` instead of
  emitting `inputSchema: undefined` when the field is missing.
  Consistent with how other optionals are handled in this file and
  compatible with `exactOptionalPropertyTypes`.
- Add 2 tests: build metadata is ignored when comparing; compareSemver
  rejects pre-release identifiers with leading zeros.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style: migrate indent from 2 to 4 spaces (#6)

* style: migrate indent from 2 to 4 spaces (Biome config)

Standardize indentation to 4 spaces across all projects.
Biome formatter config updated accordingly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: update .editorconfig indent_size to 4

Aligns with Biome formatter config to prevent editor/formatter conflicts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: align biome schema to 2.4.11 and format new develop files

Update $schema version to match installed Biome CLI.
Reformat brick-loader and marketplace resolver (merged from develop).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add CLAUDE.md (agent guidance, replaces ~/.claude memory) (#7)

* docs: add CLAUDE.md capturing the post-pivot agent guidance

Replaces the former personal memory system under
~/.claude/projects/**/memory/ with an in-repo, version-controlled
file that is auto-loaded by Claude Code (and any agents.md-compatible
tool).

Covers: project overview, the 4-repo ecosystem post CLI-first pivot
(2026-04-16), the 8 non-negotiable conventions (TDD, strict scope, pro
standards, English public-facing, gitflow, npm orgs, rulesets
checklist), this repo's specifics (lib-only, packages/core, cli moved
out, browser-compatible, no HTTP transport), and the standard feature
workflow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(claude.md): fix repo count and clarify English rule exceptions

Heading: 3 repos actifs + 1 archivé (table listed 3 not 4). Rule #5
(English public-facing): reframe as from-now-on plus list explicit
exceptions (PRD.md and CLAUDE.md stay French, existing docs stay
French until substantial rewrite) so the rule no longer contradicts
the current state of the repo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: configure GitHub Packages for @focusmcp/* packages (#8)

* chore: configure GitHub Packages registry for @focusmcp/* packages

Add publishConfig with npm.pkg.github.com registry and .npmrc for
scoped package resolution. Preparation for dev package publishing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add SPDX header to .npmrc

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: mandatory tool prefix in brick manifest (#10)

* feat: add mandatory prefix field to brick manifest

Tools are exposed as {prefix}_{toolName} to prevent collisions
between bricks and protect internal tools (no prefix).
Prefix must be unique per registry, lowercase alphanumeric.
Reserved prefixes: focus, focusmcp, mcp, internal, system.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: add edge case tests for prefix coverage (registry 98%+)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* ci: add Claude Code Review action (#11)

* ci: add Claude Code Review action

* ci: use Claude Max OAuth instead of API key

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): add id-token permission for OIDC auth

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(ci): bump GitHub Actions to v5 (Node.js 24) (#14)

- actions/checkout v4 → v5
- actions/setup-node v4 → v5
- actions/upload-artifact v4 → v5
- github/codeql-action/init v3 → v4
- github/codeql-action/analyze v3 → v4

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: enforce bare tool names in manifest — prefix applied at runtime (#15)

Tool names in mcp-brick.json must now be bare alphanumeric (e.g. "search"
not "indexer_search"). The prefix is added by the runtime when exposing
tools via MCP (prefix_toolname).

- manifest.ts: reject tool names containing non-alphanumeric chars
- tool.ts: update JSDoc to reflect new convention
- Tests updated to use bare tool names throughout

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(marketplace): add catalog-store, catalog-fetcher, installer modules (#16)

* feat(marketplace): add catalog-store, catalog-fetcher, installer modules

Pure, browser-compatible marketplace management for FocusMCP core:

- catalog-store: manage catalog source URLs (CRUD, multi-marketplace)
- catalog-fetcher: fetch + aggregate catalogs from multiple sources
- installer: plan + execute brick install/remove via npm
- resolver: extend CatalogBrickSource with npm source type

All modules use dependency injection (IO interfaces) — no direct
node: imports. The CLI provides concrete implementations.

257 tests passing, 0 typecheck errors, 0 lint errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: export marketplace modules from core package entry point

Add catalog-store, catalog-fetcher, and installer exports to index.ts
so they are part of the public API and pass knip unused-export checks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: clean up knip config — remove stale entries

Remove deprecated packages/cli workspace, redundant entry patterns
(vitest.config, playwright.config), and unused playwright binary ignore.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: extract shared validation helpers to reduce duplication

Move requireObject, requireString, optionalString, requireArray,
requireStringArray, optionalStringArray, requireBoolean into a shared
helpers.ts module. Imported by resolver, catalog-store, and installer.

Reduces jscpd duplication from 1.85% to 0.36% (threshold: 1%).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: update default catalog URL to raw.githubusercontent.com (#17)

Switch from gh-pages to raw GitHub content serving for the catalog.
URL: https://raw.githubusercontent.com/focus-mcp/marketplace/develop/publish/catalog.json

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* ci: add GitHub Packages publish workflow for dev channel (#18)

- Add .github/workflows/publish-dev.yml: publishes @focusmcp/* to
  GitHub Packages on push to develop (skips packages with private:true)
- Set "private": false in packages/core, sdk, validator package.json
- Add direct_prompt to claude-review.yml for inline PR review guidance

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: rename npm scope from @focusmcp to @focus-mcp (#21)

Rename all package names, workflow scopes, .npmrc registry bindings,
and source/test file references from @focusmcp/* to @focus-mcp/*.
Email addresses unchanged.

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): add id-token permission for npm provenance (#22)

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(ci): dev publish workflow (#24)

* feat(ci): add dev publish workflow with auto-versioning

- dev-publish.yml: publish @focus-mcp/{core,sdk,validator} to npmjs.org with --tag dev
- Auto-computed version: <base>-dev.<N> (N = commits since last tag)
- Mark packages/cli stub as private to prevent accidental publish

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: rename scope @focus-mcp@focusmcp + dev publish workflow

- Rename @focus-mcp/{core,sdk,validator} → @focusmcp/{core,sdk,validator}
- Add dev-publish.yml for auto-versioned dev releases
- Mark packages/cli stub as private
- Update all docs, workflows, configs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: regenerate pnpm-lock.yaml after scope rename

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): use GitHub Packages for dev publish (#25)

* fix(ci): use GitHub Packages registry instead of npmjs.org

- registry-url → npm.pkg.github.com
- NODE_AUTH_TOKEN → GITHUB_TOKEN (no separate secret needed)
- Add packages: write permission

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: rename scope to @focus-mcp + npmjs.org for dev publish

- All packages: @focus-mcp/{core,sdk,validator}
- dev-publish.yml: registry.npmjs.org + NPM_TOKEN
- Regenerated lockfile

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: regenerate lockfile

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: update scope references to @focus-mcp in docs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): add id-token:write permission + remove duplicate workflows (#26)

- Add id-token:write to dev-publish.yml (required for npm provenance)
- Remove publish-dev.yml (duplicate, was targeting GitHub Packages)

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(release): v1.0.0 + stable-publish workflow (#27)

* chore(release): bump @focus-mcp/{core,sdk,validator} to 1.0.0

- Bump all 3 public packages from 0.0.0 to 1.0.0
- Add stable-publish.yml workflow (triggers on push to main → npm @latest)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(ci): remove release.yml — stable-publish.yml handles releases

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(ci): remove claude-review.yml (fails on workflow changes)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* revert: restore claude-review.yml — will work after main merge

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: v1 cleanup — README, VISION, ARCHITECTURE (#30)

* docs: v1 cleanup — rewrite README, add VISION.md and ARCHITECTURE.md

- README.md: English public-facing with install, quick start, architecture
- VISION.md: short "why" doc (1 page)
- ARCHITECTURE.md: technical doc for contributors
- AGENTS.md, CONTRIBUTING.md, ROADMAP.md: updated for v1.0.0 state
- Archived pre-v1 PRD (internal French planning doc)
- npm metadata (description, keywords, author, homepage) on sdk and validator

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: consolidate AGENTS.md + AI transparency

- merge CLAUDE.md into AGENTS.md (single source of truth per agents.md spec)
- add AI-assisted development section to README
- add AI-assisted contributions section to CONTRIBUTING

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(ci): allow `release` commit type in commitlint

Release commits (e.g. `release: v1.0.0`) were rejected by the type-enum.
Add `release` as a valid type since we already use it on main branches.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(deps): pin uuid to ^14.0.0 via pnpm override (GHSA-w5hq-g745-h8pq)

Transitive from @cyclonedx/cdxgen (dev only). Not in runtime bundle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(ci): rename \`direct_prompt\` → \`prompt\` in claude-code-action (#31)

The v1 action deprecated direct_prompt. Without a valid trigger input,
PRs got a green check but Claude never posted any review.

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: claude <claude@localhost>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: claude <claude@localhost>
Adds optional `{ force: true }` third argument to `removeSource` that
bypasses the default-source protection. Without force the existing guard
is preserved. Exports new `RemoveSourceOptions` type from the package.

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Bump @focus-mcp/{core,sdk} to 1.1.0 (minor). Ships PR #38 removeSource
--force option and PR #34 CI checkout fix. No API breakage.

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove "200k to 2k" unverified figures; replace with real benchmark
data from equivalence-report.md (sr_summary -99.8%, sr_signatures -99.5%).

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(ci): add back-merge workflow to auto-sync main → develop

* chore(reuse): fix SPDX headers in CHANGELOGs and *.md

---------

Co-authored-by: claude <claude@localhost>
Extracts upgrade logic from CLI into core/marketplace/upgrader.ts.
Pure planUpgrade() + async executeUpgrade() with injected UpgradeIO.
12 tests cover up-to-date, upgrade, missing-catalog, dry-run and
disabled-state preservation cases.

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…preview tags (#46)

Replace custom 0.0.0-dev.X versioning with `pnpm changeset version --snapshot dev`
so that dev dist-tags reflect the actual next stable (e.g. 1.2.0-dev-DATE-SHA)
instead of a frozen 0.0.0 base.

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
samuelds and others added 28 commits April 28, 2026 22:51
…ets (#50)

- Replace GitHub Packages .npmrc config with npmjs.org (was causing E401
  because GITHUB_TOKEN was never resolved, while workflow passes NODE_AUTH_TOKEN)
- Add explicit guard: skip all publish steps when no .changeset/*.md pending
- Remove || true so failures are visible instead of silently masked

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Pure, browser-compatible module for managing the tools.hidden and
tools.alwaysLoad lists in ~/.focus/config.json.

Exports:
- parseToolConfig / ToolConfigData / ToolConfigIO
- matchesToolPattern — trailing glob support
- isToolHidden — hidden check with focus_tools immunity
- shouldAlwaysLoad — user-pin + server-defaults precedence
- hideTool / showTool / pinTool / unpinTool — mutations
- listToolsConfig / clearToolsConfig — bulk ops

IO is injected (ToolConfigIO) — no filesystem dependency in core.
32 unit tests, 100% branch coverage.

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…v tags) (#54)

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(core): add update-checker module for cli + bricks update detection

Adds `checkForUpdates()` with injectable IO (fully testable), 24h throttle
cache in ~/.focus/update-cache.json, npm registry fetch for cli version,
and catalog comparison for installed bricks. Network errors are swallowed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(update-checker): cover makeNodeIO without v8 ignore

Remove /* v8 ignore next 90 */ from makeNodeIO. Add 17 tests in
update-checker-node-io.test.ts that mock node:fs/promises and node:os
via vi.mock to exercise the Node I/O adapter. Add 5 tests in
update-checker.test.ts for buildCliCommand (pnpm/yarn/npm branches)
and parseCacheFile edge cases. Coverage: 94% → 100% stmts/lines/funcs,
81% → 93% branches. Zero v8/istanbul ignore in the codebase.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(core): make update-checker browser-compatible, remove makeNodeIO

Remove makeNodeIO() from core: size-limit CI runs esbuild in browser mode
(no platform:node) so node built-ins cannot resolve. io is now required
in UpdateCheckOptions. Refactored checkForUpdates helpers (complexity 41->10).
Fixed Biome lint. Removed update-checker-node-io.test.ts (makeNodeIO to cli).

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…#59)

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace direct `git push origin develop` with a PR-based approach
to comply with the develop branch ruleset (GH013 error). Now creates
a `chore/back-merge-<sha>` branch, pushes it, opens a PR, and
enables auto-merge (squash) so it merges once CI passes.

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: claude <claude@localhost>
Extend CatalogBrick, BrickManifest and parseBrick to support two new
optional fields: keywords (free-form tags) and recommendedFor
(stack/framework hints). searchBricks now matches on both fields in
addition to name, description and tags.

Co-authored-by: claude <claude@localhost>
…mendedFor (#64)

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: sync develop → main (#9)

* ci: run CodeQL on develop branch as well (#4)

The develop ruleset requires CodeQL results before merge, but the
workflow only triggered on main. PRs targeting develop were
deadlocked. Add develop to push and pull_request triggers, mirroring
the client repo's setup.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): split PRD into per-repo focused docs (#2)

* docs(prd): split monolithic PRD into per-repo PRDs

Rewrite core/PRD.md to focus solely on @focusmcp/core (lib TS):
3 piliers, manifest, SDK, validator, CLI, marketplace client, brick loader.
Reflects current architecture (core in WebView, Tauri sole HTTP gateway).

Companion PRDs added in client/ and marketplace/ repos.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): address Copilot review feedback

- Replace cross-repo relative links with absolute GitHub URLs
- Clarify monorepo layout: package `packages/core`, not `core/`
- Manifest naming: parser only enforces kebab-case; the
  `focus-` prefix is a marketplace convention
- Validator section: list only checks actually implemented; defer
  namespace/dependency/bypass checks to P1
- Stack and Decisions tables: replace "Zod / JSON Schema" with
  "custom validator (parseManifest)"; JSON Schema is used only
  for tools[].inputSchema

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): add SPDX headers for REUSE compliance

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(core): add brick loader with abstract source (#3)

* feat(core): add brick loader with abstract source

`loadBricks({ source })` reads installed bricks via an abstract `BrickSource`
(list/readManifest/loadModule), validates manifests with `parseManifest`,
ensures the loaded module exports a Brick whose manifest matches the source
declaration, and collects per-brick failures without aborting the load.

Browser-compatible: no direct FS access — the source is injected by the host
(Tauri commands for desktop, in-memory for tests).

11 tests, 100% line / 94.4% branch coverage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(core): tighten brick-loader validation per review

- Parse the module-provided brick.manifest with `parseManifest` and
  enforce strict equality against the source manifest (canonical JSON
  comparison). Catches divergence in deps/tools, not just name.
- Split default-export check into two messages: missing default vs
  default-not-an-object (clearer diagnostics for `default: 42` etc.).
- Move the manifest-shape check out of the Brick contract assertion so
  malformed `brick.manifest` produces an INVALID_MANIFEST error rather
  than a misleading "does not implement Brick contract".

3 new tests (divergence, malformed module manifest, default-not-object).
14 tests total, 100% line / 96.3% branch coverage on the loader.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(core): marketplace resolver (parse + find + semver + updates) (#5)

* feat(core): add marketplace resolver (parse + find + semver + updates)

Pure, browser-compatible module that consumes a catalog.json as
published by the FocusMCP marketplace. Does no I/O — the host injects
raw JSON and this module validates, normalizes and queries it.

Public API:
- parseCatalog(raw): Catalog — structural validation aligned with the
  published JSON Schema (kebab-case names, semver versions, typed
  source variants).
- findBrick(catalog, name): CatalogBrick | undefined
- compareSemver(a, b): -1 | 0 | 1 — minimal inline implementation
  (core + optional pre-release; no build metadata, no range matching
  yet — added at need).
- listUpdates(installed, catalog): UpdateInfo[]

20 unit tests (parseCatalog, findBrick, compareSemver incl. pre-release
ordering per semver §11, listUpdates). Full suite: 127 passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(core): address Copilot review on marketplace resolver

- Thread a full `loc` path into requireString/optionalString and the
  array variants so validation errors now produce
  "bricks[3].owner.email must be a string" instead of just
  "email must be a string". Much easier to diagnose.
- Tighten SEMVER regex: reject numeric pre-release identifiers with
  leading zeros (per semver 2.0 §9).
- Extend SEMVER regex to accept optional build metadata ("+..."),
  matching the manifest parser and semver 2.0 §10. Build metadata is
  captured but discarded for precedence comparisons, as required.
- parseTool: use conditional spread for `inputSchema` instead of
  emitting `inputSchema: undefined` when the field is missing.
  Consistent with how other optionals are handled in this file and
  compatible with `exactOptionalPropertyTypes`.
- Add 2 tests: build metadata is ignored when comparing; compareSemver
  rejects pre-release identifiers with leading zeros.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style: migrate indent from 2 to 4 spaces (#6)

* style: migrate indent from 2 to 4 spaces (Biome config)

Standardize indentation to 4 spaces across all projects.
Biome formatter config updated accordingly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: update .editorconfig indent_size to 4

Aligns with Biome formatter config to prevent editor/formatter conflicts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: align biome schema to 2.4.11 and format new develop files

Update $schema version to match installed Biome CLI.
Reformat brick-loader and marketplace resolver (merged from develop).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add CLAUDE.md (agent guidance, replaces ~/.claude memory) (#7)

* docs: add CLAUDE.md capturing the post-pivot agent guidance

Replaces the former personal memory system under
~/.claude/projects/**/memory/ with an in-repo, version-controlled
file that is auto-loaded by Claude Code (and any agents.md-compatible
tool).

Covers: project overview, the 4-repo ecosystem post CLI-first pivot
(2026-04-16), the 8 non-negotiable conventions (TDD, strict scope, pro
standards, English public-facing, gitflow, npm orgs, rulesets
checklist), this repo's specifics (lib-only, packages/core, cli moved
out, browser-compatible, no HTTP transport), and the standard feature
workflow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(claude.md): fix repo count and clarify English rule exceptions

Heading: 3 repos actifs + 1 archivé (table listed 3 not 4). Rule #5
(English public-facing): reframe as from-now-on plus list explicit
exceptions (PRD.md and CLAUDE.md stay French, existing docs stay
French until substantial rewrite) so the rule no longer contradicts
the current state of the repo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: configure GitHub Packages for @focusmcp/* packages (#8)

* chore: configure GitHub Packages registry for @focusmcp/* packages

Add publishConfig with npm.pkg.github.com registry and .npmrc for
scoped package resolution. Preparation for dev package publishing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add SPDX header to .npmrc

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: claude <claude@localhost>

* chore: sync develop → main (conflict resolution) (#13)

* feat(core): add brick loader with abstract source (#3)

* feat(core): add brick loader with abstract source

`loadBricks({ source })` reads installed bricks via an abstract `BrickSource`
(list/readManifest/loadModule), validates manifests with `parseManifest`,
ensures the loaded module exports a Brick whose manifest matches the source
declaration, and collects per-brick failures without aborting the load.

Browser-compatible: no direct FS access — the source is injected by the host
(Tauri commands for desktop, in-memory for tests).

11 tests, 100% line / 94.4% branch coverage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(core): tighten brick-loader validation per review

- Parse the module-provided brick.manifest with `parseManifest` and
  enforce strict equality against the source manifest (canonical JSON
  comparison). Catches divergence in deps/tools, not just name.
- Split default-export check into two messages: missing default vs
  default-not-an-object (clearer diagnostics for `default: 42` etc.).
- Move the manifest-shape check out of the Brick contract assertion so
  malformed `brick.manifest` produces an INVALID_MANIFEST error rather
  than a misleading "does not implement Brick contract".

3 new tests (divergence, malformed module manifest, default-not-object).
14 tests total, 100% line / 96.3% branch coverage on the loader.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(core): marketplace resolver (parse + find + semver + updates) (#5)

* feat(core): add marketplace resolver (parse + find + semver + updates)

Pure, browser-compatible module that consumes a catalog.json as
published by the FocusMCP marketplace. Does no I/O — the host injects
raw JSON and this module validates, normalizes and queries it.

Public API:
- parseCatalog(raw): Catalog — structural validation aligned with the
  published JSON Schema (kebab-case names, semver versions, typed
  source variants).
- findBrick(catalog, name): CatalogBrick | undefined
- compareSemver(a, b): -1 | 0 | 1 — minimal inline implementation
  (core + optional pre-release; no build metadata, no range matching
  yet — added at need).
- listUpdates(installed, catalog): UpdateInfo[]

20 unit tests (parseCatalog, findBrick, compareSemver incl. pre-release
ordering per semver §11, listUpdates). Full suite: 127 passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(core): address Copilot review on marketplace resolver

- Thread a full `loc` path into requireString/optionalString and the
  array variants so validation errors now produce
  "bricks[3].owner.email must be a string" instead of just
  "email must be a string". Much easier to diagnose.
- Tighten SEMVER regex: reject numeric pre-release identifiers with
  leading zeros (per semver 2.0 §9).
- Extend SEMVER regex to accept optional build metadata ("+..."),
  matching the manifest parser and semver 2.0 §10. Build metadata is
  captured but discarded for precedence comparisons, as required.
- parseTool: use conditional spread for `inputSchema` instead of
  emitting `inputSchema: undefined` when the field is missing.
  Consistent with how other optionals are handled in this file and
  compatible with `exactOptionalPropertyTypes`.
- Add 2 tests: build metadata is ignored when comparing; compareSemver
  rejects pre-release identifiers with leading zeros.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style: migrate indent from 2 to 4 spaces (#6)

* style: migrate indent from 2 to 4 spaces (Biome config)

Standardize indentation to 4 spaces across all projects.
Biome formatter config updated accordingly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: update .editorconfig indent_size to 4

Aligns with Biome formatter config to prevent editor/formatter conflicts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: align biome schema to 2.4.11 and format new develop files

Update $schema version to match installed Biome CLI.
Reformat brick-loader and marketplace resolver (merged from develop).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: configure GitHub Packages for @focusmcp/* packages (#8)

* chore: configure GitHub Packages registry for @focusmcp/* packages

Add publishConfig with npm.pkg.github.com registry and .npmrc for
scoped package resolution. Preparation for dev package publishing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add SPDX header to .npmrc

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: mandatory tool prefix in brick manifest (#10)

* feat: add mandatory prefix field to brick manifest

Tools are exposed as {prefix}_{toolName} to prevent collisions
between bricks and protect internal tools (no prefix).
Prefix must be unique per registry, lowercase alphanumeric.
Reserved prefixes: focus, focusmcp, mcp, internal, system.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: add edge case tests for prefix coverage (registry 98%+)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* ci: add Claude Code Review action (#11)

* ci: add Claude Code Review action

* ci: use Claude Max OAuth instead of API key

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): add id-token permission for OIDC auth

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: claude <claude@localhost>

* release: v1.0.0 — sync develop → main (#29)

* ci: run CodeQL on develop branch as well (#4)

The develop ruleset requires CodeQL results before merge, but the
workflow only triggered on main. PRs targeting develop were
deadlocked. Add develop to push and pull_request triggers, mirroring
the client repo's setup.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): split PRD into per-repo focused docs (#2)

* docs(prd): split monolithic PRD into per-repo PRDs

Rewrite core/PRD.md to focus solely on @focusmcp/core (lib TS):
3 piliers, manifest, SDK, validator, CLI, marketplace client, brick loader.
Reflects current architecture (core in WebView, Tauri sole HTTP gateway).

Companion PRDs added in client/ and marketplace/ repos.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): address Copilot review feedback

- Replace cross-repo relative links with absolute GitHub URLs
- Clarify monorepo layout: package `packages/core`, not `core/`
- Manifest naming: parser only enforces kebab-case; the
  `focus-` prefix is a marketplace convention
- Validator section: list only checks actually implemented; defer
  namespace/dependency/bypass checks to P1
- Stack and Decisions tables: replace "Zod / JSON Schema" with
  "custom validator (parseManifest)"; JSON Schema is used only
  for tools[].inputSchema

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): add SPDX headers for REUSE compliance

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(core): add brick loader with abstract source (#3)

* feat(core): add brick loader with abstract source

`loadBricks({ source })` reads installed bricks via an abstract `BrickSource`
(list/readManifest/loadModule), validates manifests with `parseManifest`,
ensures the loaded module exports a Brick whose manifest matches the source
declaration, and collects per-brick failures without aborting the load.

Browser-compatible: no direct FS access — the source is injected by the host
(Tauri commands for desktop, in-memory for tests).

11 tests, 100% line / 94.4% branch coverage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(core): tighten brick-loader validation per review

- Parse the module-provided brick.manifest with `parseManifest` and
  enforce strict equality against the source manifest (canonical JSON
  comparison). Catches divergence in deps/tools, not just name.
- Split default-export check into two messages: missing default vs
  default-not-an-object (clearer diagnostics for `default: 42` etc.).
- Move the manifest-shape check out of the Brick contract assertion so
  malformed `brick.manifest` produces an INVALID_MANIFEST error rather
  than a misleading "does not implement Brick contract".

3 new tests (divergence, malformed module manifest, default-not-object).
14 tests total, 100% line / 96.3% branch coverage on the loader.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(core): marketplace resolver (parse + find + semver + updates) (#5)

* feat(core): add marketplace resolver (parse + find + semver + updates)

Pure, browser-compatible module that consumes a catalog.json as
published by the FocusMCP marketplace. Does no I/O — the host injects
raw JSON and this module validates, normalizes and queries it.

Public API:
- parseCatalog(raw): Catalog — structural validation aligned with the
  published JSON Schema (kebab-case names, semver versions, typed
  source variants).
- findBrick(catalog, name): CatalogBrick | undefined
- compareSemver(a, b): -1 | 0 | 1 — minimal inline implementation
  (core + optional pre-release; no build metadata, no range matching
  yet — added at need).
- listUpdates(installed, catalog): UpdateInfo[]

20 unit tests (parseCatalog, findBrick, compareSemver incl. pre-release
ordering per semver §11, listUpdates). Full suite: 127 passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(core): address Copilot review on marketplace resolver

- Thread a full `loc` path into requireString/optionalString and the
  array variants so validation errors now produce
  "bricks[3].owner.email must be a string" instead of just
  "email must be a string". Much easier to diagnose.
- Tighten SEMVER regex: reject numeric pre-release identifiers with
  leading zeros (per semver 2.0 §9).
- Extend SEMVER regex to accept optional build metadata ("+..."),
  matching the manifest parser and semver 2.0 §10. Build metadata is
  captured but discarded for precedence comparisons, as required.
- parseTool: use conditional spread for `inputSchema` instead of
  emitting `inputSchema: undefined` when the field is missing.
  Consistent with how other optionals are handled in this file and
  compatible with `exactOptionalPropertyTypes`.
- Add 2 tests: build metadata is ignored when comparing; compareSemver
  rejects pre-release identifiers with leading zeros.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style: migrate indent from 2 to 4 spaces (#6)

* style: migrate indent from 2 to 4 spaces (Biome config)

Standardize indentation to 4 spaces across all projects.
Biome formatter config updated accordingly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: update .editorconfig indent_size to 4

Aligns with Biome formatter config to prevent editor/formatter conflicts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: align biome schema to 2.4.11 and format new develop files

Update $schema version to match installed Biome CLI.
Reformat brick-loader and marketplace resolver (merged from develop).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add CLAUDE.md (agent guidance, replaces ~/.claude memory) (#7)

* docs: add CLAUDE.md capturing the post-pivot agent guidance

Replaces the former personal memory system under
~/.claude/projects/**/memory/ with an in-repo, version-controlled
file that is auto-loaded by Claude Code (and any agents.md-compatible
tool).

Covers: project overview, the 4-repo ecosystem post CLI-first pivot
(2026-04-16), the 8 non-negotiable conventions (TDD, strict scope, pro
standards, English public-facing, gitflow, npm orgs, rulesets
checklist), this repo's specifics (lib-only, packages/core, cli moved
out, browser-compatible, no HTTP transport), and the standard feature
workflow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(claude.md): fix repo count and clarify English rule exceptions

Heading: 3 repos actifs + 1 archivé (table listed 3 not 4). Rule #5
(English public-facing): reframe as from-now-on plus list explicit
exceptions (PRD.md and CLAUDE.md stay French, existing docs stay
French until substantial rewrite) so the rule no longer contradicts
the current state of the repo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: configure GitHub Packages for @focusmcp/* packages (#8)

* chore: configure GitHub Packages registry for @focusmcp/* packages

Add publishConfig with npm.pkg.github.com registry and .npmrc for
scoped package resolution. Preparation for dev package publishing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add SPDX header to .npmrc

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: mandatory tool prefix in brick manifest (#10)

* feat: add mandatory prefix field to brick manifest

Tools are exposed as {prefix}_{toolName} to prevent collisions
between bricks and protect internal tools (no prefix).
Prefix must be unique per registry, lowercase alphanumeric.
Reserved prefixes: focus, focusmcp, mcp, internal, system.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: add edge case tests for prefix coverage (registry 98%+)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* ci: add Claude Code Review action (#11)

* ci: add Claude Code Review action

* ci: use Claude Max OAuth instead of API key

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): add id-token permission for OIDC auth

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(ci): bump GitHub Actions to v5 (Node.js 24) (#14)

- actions/checkout v4 → v5
- actions/setup-node v4 → v5
- actions/upload-artifact v4 → v5
- github/codeql-action/init v3 → v4
- github/codeql-action/analyze v3 → v4

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: enforce bare tool names in manifest — prefix applied at runtime (#15)

Tool names in mcp-brick.json must now be bare alphanumeric (e.g. "search"
not "indexer_search"). The prefix is added by the runtime when exposing
tools via MCP (prefix_toolname).

- manifest.ts: reject tool names containing non-alphanumeric chars
- tool.ts: update JSDoc to reflect new convention
- Tests updated to use bare tool names throughout

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(marketplace): add catalog-store, catalog-fetcher, installer modules (#16)

* feat(marketplace): add catalog-store, catalog-fetcher, installer modules

Pure, browser-compatible marketplace management for FocusMCP core:

- catalog-store: manage catalog source URLs (CRUD, multi-marketplace)
- catalog-fetcher: fetch + aggregate catalogs from multiple sources
- installer: plan + execute brick install/remove via npm
- resolver: extend CatalogBrickSource with npm source type

All modules use dependency injection (IO interfaces) — no direct
node: imports. The CLI provides concrete implementations.

257 tests passing, 0 typecheck errors, 0 lint errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: export marketplace modules from core package entry point

Add catalog-store, catalog-fetcher, and installer exports to index.ts
so they are part of the public API and pass knip unused-export checks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: clean up knip config — remove stale entries

Remove deprecated packages/cli workspace, redundant entry patterns
(vitest.config, playwright.config), and unused playwright binary ignore.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: extract shared validation helpers to reduce duplication

Move requireObject, requireString, optionalString, requireArray,
requireStringArray, optionalStringArray, requireBoolean into a shared
helpers.ts module. Imported by resolver, catalog-store, and installer.

Reduces jscpd duplication from 1.85% to 0.36% (threshold: 1%).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: update default catalog URL to raw.githubusercontent.com (#17)

Switch from gh-pages to raw GitHub content serving for the catalog.
URL: https://raw.githubusercontent.com/focus-mcp/marketplace/develop/publish/catalog.json

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* ci: add GitHub Packages publish workflow for dev channel (#18)

- Add .github/workflows/publish-dev.yml: publishes @focusmcp/* to
  GitHub Packages on push to develop (skips packages with private:true)
- Set "private": false in packages/core, sdk, validator package.json
- Add direct_prompt to claude-review.yml for inline PR review guidance

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: rename npm scope from @focusmcp to @focus-mcp (#21)

Rename all package names, workflow scopes, .npmrc registry bindings,
and source/test file references from @focusmcp/* to @focus-mcp/*.
Email addresses unchanged.

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): add id-token permission for npm provenance (#22)

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(ci): dev publish workflow (#24)

* feat(ci): add dev publish workflow with auto-versioning

- dev-publish.yml: publish @focus-mcp/{core,sdk,validator} to npmjs.org with --tag dev
- Auto-computed version: <base>-dev.<N> (N = commits since last tag)
- Mark packages/cli stub as private to prevent accidental publish

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: rename scope @focus-mcp → @focusmcp + dev publish workflow

- Rename @focus-mcp/{core,sdk,validator} → @focusmcp/{core,sdk,validator}
- Add dev-publish.yml for auto-versioned dev releases
- Mark packages/cli stub as private
- Update all docs, workflows, configs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: regenerate pnpm-lock.yaml after scope rename

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): use GitHub Packages for dev publish (#25)

* fix(ci): use GitHub Packages registry instead of npmjs.org

- registry-url → npm.pkg.github.com
- NODE_AUTH_TOKEN → GITHUB_TOKEN (no separate secret needed)
- Add packages: write permission

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: rename scope to @focus-mcp + npmjs.org for dev publish

- All packages: @focus-mcp/{core,sdk,validator}
- dev-publish.yml: registry.npmjs.org + NPM_TOKEN
- Regenerated lockfile

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: regenerate lockfile

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: update scope references to @focus-mcp in docs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): add id-token:write permission + remove duplicate workflows (#26)

- Add id-token:write to dev-publish.yml (required for npm provenance)
- Remove publish-dev.yml (duplicate, was targeting GitHub Packages)

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(release): v1.0.0 + stable-publish workflow (#27)

* chore(release): bump @focus-mcp/{core,sdk,validator} to 1.0.0

- Bump all 3 public packages from 0.0.0 to 1.0.0
- Add stable-publish.yml workflow (triggers on push to main → npm @latest)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(ci): remove release.yml — stable-publish.yml handles releases

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(ci): remove claude-review.yml (fails on workflow changes)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* revert: restore claude-review.yml — will work after main merge

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: claude <claude@localhost>

* release: sync develop → main (workflow fix + v1 cleanup) (#33)

* ci: run CodeQL on develop branch as well (#4)

The develop ruleset requires CodeQL results before merge, but the
workflow only triggered on main. PRs targeting develop were
deadlocked. Add develop to push and pull_request triggers, mirroring
the client repo's setup.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): split PRD into per-repo focused docs (#2)

* docs(prd): split monolithic PRD into per-repo PRDs

Rewrite core/PRD.md to focus solely on @focusmcp/core (lib TS):
3 piliers, manifest, SDK, validator, CLI, marketplace client, brick loader.
Reflects current architecture (core in WebView, Tauri sole HTTP gateway).

Companion PRDs added in client/ and marketplace/ repos.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): address Copilot review feedback

- Replace cross-repo relative links with absolute GitHub URLs
- Clarify monorepo layout: package `packages/core`, not `core/`
- Manifest naming: parser only enforces kebab-case; the
  `focus-` prefix is a marketplace convention
- Validator section: list only checks actually implemented; defer
  namespace/dependency/bypass checks to P1
- Stack and Decisions tables: replace "Zod / JSON Schema" with
  "custom validator (parseManifest)"; JSON Schema is used only
  for tools[].inputSchema

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): add SPDX headers for REUSE compliance

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(core): add brick loader with abstract source (#3)

* feat(core): add brick loader with abstract source

`loadBricks({ source })` reads installed bricks via an abstract `BrickSource`
(list/readManifest/loadModule), validates manifests with `parseManifest`,
ensures the loaded module exports a Brick whose manifest matches the source
declaration, and collects per-brick failures without aborting the load.

Browser-compatible: no direct FS access — the source is injected by the host
(Tauri commands for desktop, in-memory for tests).

11 tests, 100% line / 94.4% branch coverage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(core): tighten brick-loader validation per review

- Parse the module-provided brick.manifest with `parseManifest` and
  enforce strict equality against the source manifest (canonical JSON
  comparison). Catches divergence in deps/tools, not just name.
- Split default-export check into two messages: missing default vs
  default-not-an-object (clearer diagnostics for `default: 42` etc.).
- Move the manifest-shape check out of the Brick contract assertion so
  malformed `brick.manifest` produces an INVALID_MANIFEST error rather
  than a misleading "does not implement Brick contract".

3 new tests (divergence, malformed module manifest, default-not-object).
14 tests total, 100% line / 96.3% branch coverage on the loader.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(core): marketplace resolver (parse + find + semver + updates) (#5)

* feat(core): add marketplace resolver (parse + find + semver + updates)

Pure, browser-compatible module that consumes a catalog.json as
published by the FocusMCP marketplace. Does no I/O — the host injects
raw JSON and this module validates, normalizes and queries it.

Public API:
- parseCatalog(raw): Catalog — structural validation aligned with the
  published JSON Schema (kebab-case names, semver versions, typed
  source variants).
- findBrick(catalog, name): CatalogBrick | undefined
- compareSemver(a, b): -1 | 0 | 1 — minimal inline implementation
  (core + optional pre-release; no build metadata, no range matching
  yet — added at need).
- listUpdates(installed, catalog): UpdateInfo[]

20 unit tests (parseCatalog, findBrick, compareSemver incl. pre-release
ordering per semver §11, listUpdates). Full suite: 127 passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(core): address Copilot review on marketplace resolver

- Thread a full `loc` path into requireString/optionalString and the
  array variants so validation errors now produce
  "bricks[3].owner.email must be a string" instead of just
  "email must be a string". Much easier to diagnose.
- Tighten SEMVER regex: reject numeric pre-release identifiers with
  leading zeros (per semver 2.0 §9).
- Extend SEMVER regex to accept optional build metadata ("+..."),
  matching the manifest parser and semver 2.0 §10. Build metadata is
  captured but discarded for precedence comparisons, as required.
- parseTool: use conditional spread for `inputSchema` instead of
  emitting `inputSchema: undefined` when the field is missing.
  Consistent with how other optionals are handled in this file and
  compatible with `exactOptionalPropertyTypes`.
- Add 2 tests: build metadata is ignored when comparing; compareSemver
  rejects pre-release identifiers with leading zeros.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style: migrate indent from 2 to 4 spaces (#6)

* style: migrate indent from 2 to 4 spaces (Biome config)

Standardize indentation to 4 spaces across all projects.
Biome formatter config updated accordingly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: update .editorconfig indent_size to 4

Aligns with Biome formatter config to prevent editor/formatter conflicts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: align biome schema to 2.4.11 and format new develop files

Update $schema version to match installed Biome CLI.
Reformat brick-loader and marketplace resolver (merged from develop).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add CLAUDE.md (agent guidance, replaces ~/.claude memory) (#7)

* docs: add CLAUDE.md capturing the post-pivot agent guidance

Replaces the former personal memory system under
~/.claude/projects/**/memory/ with an in-repo, version-controlled
file that is auto-loaded by Claude Code (and any agents.md-compatible
tool).

Covers: project overview, the 4-repo ecosystem post CLI-first pivot
(2026-04-16), the 8 non-negotiable conventions (TDD, strict scope, pro
standards, English public-facing, gitflow, npm orgs, rulesets
checklist), this repo's specifics (lib-only, packages/core, cli moved
out, browser-compatible, no HTTP transport), and the standard feature
workflow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(claude.md): fix repo count and clarify English rule exceptions

Heading: 3 repos actifs + 1 archivé (table listed 3 not 4). Rule #5
(English public-facing): reframe as from-now-on plus list explicit
exceptions (PRD.md and CLAUDE.md stay French, existing docs stay
French until substantial rewrite) so the rule no longer contradicts
the current state of the repo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: configure GitHub Packages for @focusmcp/* packages (#8)

* chore: configure GitHub Packages registry for @focusmcp/* packages

Add publishConfig with npm.pkg.github.com registry and .npmrc for
scoped package resolution. Preparation for dev package publishing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add SPDX header to .npmrc

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: mandatory tool prefix in brick manifest (#10)

* feat: add mandatory prefix field to brick manifest

Tools are exposed as {prefix}_{toolName} to prevent collisions
between bricks and protect internal tools (no prefix).
Prefix must be unique per registry, lowercase alphanumeric.
Reserved prefixes: focus, focusmcp, mcp, internal, system.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: add edge case tests for prefix coverage (registry 98%+)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* ci: add Claude Code Review action (#11)

* ci: add Claude Code Review action

* ci: use Claude Max OAuth instead of API key

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): add id-token permission for OIDC auth

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(ci): bump GitHub Actions to v5 (Node.js 24) (#14)

- actions/checkout v4 → v5
- actions/setup-node v4 → v5
- actions/upload-artifact v4 → v5
- github/codeql-action/init v3 → v4
- github/codeql-action/analyze v3 → v4

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: enforce bare tool names in manifest — prefix applied at runtime (#15)

Tool names in mcp-brick.json must now be bare alphanumeric (e.g. "search"
not "indexer_search"). The prefix is added by the runtime when exposing
tools via MCP (prefix_toolname).

- manifest.ts: reject tool names containing non-alphanumeric chars
- tool.ts: update JSDoc to reflect new convention
- Tests updated to use bare tool names throughout

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(marketplace): add catalog-store, catalog-fetcher, installer modules (#16)

* feat(marketplace): add catalog-store, catalog-fetcher, installer modules

Pure, browser-compatible marketplace management for FocusMCP core:

- catalog-store: manage catalog source URLs (CRUD, multi-marketplace)
- catalog-fetcher: fetch + aggregate catalogs from multiple sources
- installer: plan + execute brick install/remove via npm
- resolver: extend CatalogBrickSource with npm source type

All modules use dependency injection (IO interfaces) — no direct
node: imports. The CLI provides concrete implementations.

257 tests passing, 0 typecheck errors, 0 lint errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: export marketplace modules from core package entry point

Add catalog-store, catalog-fetcher, and installer exports to index.ts
so they are part of the public API and pass knip unused-export checks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: clean up knip config — remove stale entries

Remove deprecated packages/cli workspace, redundant entry patterns
(vitest.config, playwright.config), and unused playwright binary ignore.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: extract shared validation helpers to reduce duplication

Move requireObject, requireString, optionalString, requireArray,
requireStringArray, optionalStringArray, requireBoolean into a shared
helpers.ts module. Imported by resolver, catalog-store, and installer.

Reduces jscpd duplication from 1.85% to 0.36% (threshold: 1%).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: update default catalog URL to raw.githubusercontent.com (#17)

Switch from gh-pages to raw GitHub content serving for the catalog.
URL: https://raw.githubusercontent.com/focus-mcp/marketplace/develop/publish/catalog.json

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* ci: add GitHub Packages publish workflow for dev channel (#18)

- Add .github/workflows/publish-dev.yml: publishes @focusmcp/* to
  GitHub Packages on push to develop (skips packages with private:true)
- Set "private": false in packages/core, sdk, validator package.json
- Add direct_prompt to claude-review.yml for inline PR review guidance

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: rename npm scope from @focusmcp to @focus-mcp (#21)

Rename all package names, workflow scopes, .npmrc registry bindings,
and source/test file references from @focusmcp/* to @focus-mcp/*.
Email addresses unchanged.

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): add id-token permission for npm provenance (#22)

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(ci): dev publish workflow (#24)

* feat(ci): add dev publish workflow with auto-versioning

- dev-publish.yml: publish @focus-mcp/{core,sdk,validator} to npmjs.org with --tag dev
- Auto-computed version: <base>-dev.<N> (N = commits since last tag)
- Mark packages/cli stub as private to prevent accidental publish

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: rename scope @focus-mcp → @focusmcp + dev publish workflow

- Rename @focus-mcp/{core,sdk,validator} → @focusmcp/{core,sdk,validator}
- Add dev-publish.yml for auto-versioned dev releases
- Mark packages/cli stub as private
- Update all docs, workflows, configs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: regenerate pnpm-lock.yaml after scope rename

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): use GitHub Packages for dev publish (#25)

* fix(ci): use GitHub Packages registry instead of npmjs.org

- registry-url → npm.pkg.github.com
- NODE_AUTH_TOKEN → GITHUB_TOKEN (no separate secret needed)
- Add packages: write permission

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: rename scope to @focus-mcp + npmjs.org for dev publish

- All packages: @focus-mcp/{core,sdk,validator}
- dev-publish.yml: registry.npmjs.org + NPM_TOKEN
- Regenerated lockfile

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: regenerate lockfile

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: update scope references to @focus-mcp in docs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): add id-token:write permission + remove duplicate workflows (#26)

- Add id-token:write to dev-publish.yml (required for npm provenance)
- Remove publish-dev.yml (duplicate, was targeting GitHub Packages)

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(release): v1.0.0 + stable-publish workflow (#27)

* chore(release): bump @focus-mcp/{core,sdk,validator} to 1.0.0

- Bump all 3 public packages from 0.0.0 to 1.0.0
- Add stable-publish.yml workflow (triggers on push to main → npm @latest)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(ci): remove release.yml — stable-publish.yml handles releases

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(ci): remove claude-review.yml (fails on workflow changes)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* revert: restore claude-review.yml — will work after main merge

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: v1 cleanup — README, VISION, ARCHITECTURE (#30)

* docs: v1 cleanup — rewrite README, add VISION.md and ARCHITECTURE.md

- README.md: English public-facing with install, quick start, architecture
- VISION.md: short "why" doc (1 page)
- ARCHITECTURE.md: technical doc for contributors
- AGENTS.md, CONTRIBUTING.md, ROADMAP.md: updated for v1.0.0 state
- Archived pre-v1 PRD (internal French planning doc)
- npm metadata (description, keywords, author, homepage) on sdk and validator

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: consolidate AGENTS.md + AI transparency

- merge CLAUDE.md into AGENTS.md (single source of truth per agents.md spec)
- add AI-assisted development section to README
- add AI-assisted contributions section to CONTRIBUTING

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(ci): allow `release` commit type in commitlint

Release commits (e.g. `release: v1.0.0`) were rejected by the type-enum.
Add `release` as a valid type since we already use it on main branches.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(deps): pin uuid to ^14.0.0 via pnpm override (GHSA-w5hq-g745-h8pq)

Transitive from @cyclonedx/cdxgen (dev only). Not in runtime bundle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(ci): rename \`direct_prompt\` → \`prompt\` in claude-code-action (#31)

The v1 action deprecated direct_prompt. Without a valid trigger input,
PRs got a green check but Claude never posted any review.

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: claude <claude@localhost>

* release: sync develop → main (#37)

* ci: run CodeQL on develop branch as well (#4)

The develop ruleset requires CodeQL results before merge, but the
workflow only triggered on main. PRs targeting develop were
deadlocked. Add develop to push and pull_request triggers, mirroring
the client repo's setup.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): split PRD into per-repo focused docs (#2)

* docs(prd): split monolithic PRD into per-repo PRDs

Rewrite core/PRD.md to focus solely on @focusmcp/core (lib TS):
3 piliers, manifest, SDK, validator, CLI, marketplace client, brick loader.
Reflects current architecture (core in WebView, Tauri sole HTTP gateway).

Companion PRDs added in client/ and marketplace/ repos.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): address Copilot review feedback

- Replace cross-repo relative links with absolute GitHub URLs
- Clarify monorepo layout: package `packages/core`, not `core/`
- Manifest naming: parser only enforces kebab-case; the
  `focus-` prefix is a marketplace convention
- Validator section: list only checks actually implemented; defer
  namespace/dependency/bypass checks to P1
- Stack and Decisions tables: replace "Zod / JSON Schema" with
  "custom validator (parseManifest)"; JSON Schema is used only
  for tools[].inputSchema

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): add SPDX headers for REUSE compliance

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(core): add brick loader with abstract source (#3)

* feat(core): add brick loader with abstract source

`loadBricks({ source })` reads installed bricks via an abstract `BrickSource`
(list/readManifest/loadModule), validates manifests with `parseManifest`,
ensures the loaded module exports a Brick whose manifest matches the source
declaration, and collects per-brick failures without aborting the load.

Browser-compatible: no direct FS access — the source is injected by the host
(Tauri commands for desktop, in-memory for tests).

11 tests, 100% line / 94.4% branch coverage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(core): tighten brick-loader validation per review

- Parse the module-provided brick.manifest with `parseManifest` and
  enforce strict equality against the source manifest (canonical JSON
  comparison). Catches divergence in deps/tools, not just name.
- Split default-export check into two messages: missing default vs
  default-not-an-object (clearer diagnostics for `default: 42` etc.).
- Move the manifest-shape check out of the Brick contract assertion so
  malformed `brick.manifest` produces an INVALID_MANIFEST error rather
  than a misleading "does not implement Brick contract".

3 new tests (divergence, malformed module manifest, default-not-object).
14 tests total, 100% line / 96.3% branch coverage on the loader.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(core): marketplace resolver (parse + find + semver + updates) (#5)

* feat(core): add marketplace resolver (parse + find + semver + updates)

Pure, browser-compatible module that consumes a catalog.json as
published by the FocusMCP marketplace. Does no I/O — the host injects
raw JSON and this module validates, normalizes and queries it.

Public API:
- parseCatalog(raw): Catalog — structural validation aligned with the
  published JSON Schema (kebab-case names, semver versions, typed
  source variants).
- findBrick(catalog, name): CatalogBrick | undefined
- compareSemver(a, b): -1 | 0 | 1 — minimal inline implementation
  (core + optional pre-release; no build metadata, no range matching
  yet — added at need).
- listUpdates(installed, catalog): UpdateInfo[]

20 unit tests (parseCatalog, findBrick, compareSemver incl. pre-release
ordering per semver §11, listUpdates). Full suite: 127 passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(core): address Copilot review on marketplace resolver

- Thread a full `loc` path into requireString/optionalString and the
  array variants so validation errors now produce
  "bricks[3].owner.email must be a string" instead of just
  "email must be a string". Much easier to diagnose.
- Tighten SEMVER regex: reject numeric pre-release identifiers with
  leading zeros (per semver 2.0 §9).
- Extend SEMVER regex to accept optional build metadata ("+..."),
  matching the manifest parser and semver 2.0 §10. Build metadata is
  captured but discarded for precedence comparisons, as required.
- parseTool: use conditional spread for `inputSchema` instead of
  emitting `inputSchema: undefined` when the field is missing.
  Consistent with how other optionals are handled in this file and
  compatible with `exactOptionalPropertyTypes`.
- Add 2 tests: build metadata is ignored when comparing; compareSemver
  rejects pre-release identifiers with leading zeros.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style: migrate indent from 2 to 4 spaces (#6)

* style: migrate indent from 2 to 4 spaces (Biome config)

Standardize indentation to 4 spaces across all projects.
Biome formatter config updated accordingly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: update .editorconfig indent_size to 4

Aligns with Biome formatter config to prevent editor/formatter conflicts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: align biome schema to 2.4.11 and format new develop files

Update $schema version to match installed Biome CLI.
Reformat brick-loader and marketplace resolver (merged from develop).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add CLAUDE.md (agent guidance, replaces ~/.claude memory) (#7)

* docs: add CLAUDE.md capturing the post-pivot agent guidance

Replaces the former personal memory system under
~/.claude/projects/**/memory/ with an in-repo, version-controlled
file that is auto-loaded by Claude Code (and any agents.md-compatible
tool).

Covers: project overview, the 4-repo ecosystem post CLI-first pivot
(2026-04-16), the 8 non-negotiable conventions (TDD, strict scope, pro
standards, English public-facing, gitflow, npm orgs, rulesets
checklist), this repo's specifics (lib-only, packages/core, cli moved
out, browser-compatible, no HTTP transport), and the standard feature
workflow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(claude.md): fix repo count and clarify English rule exceptions

Heading: 3 repos actifs + 1 archivé (table listed 3 not 4). Rule #5
(English public-facing): reframe as from-now-on plus list explicit
exceptions (PRD.md and CLAUDE.md stay French, existing docs stay
French until substantial rewrite) so the rule no longer contradicts
the current state of the repo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: configure GitHub Packages for @focusmcp/* packages (#8)

* chore: configure GitHub Packages registry for @focusmcp/* packages

Add publishConfig with npm.pkg.github.com registry and .npmrc for
scoped package resolution. Preparation for dev package publishing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add SPDX header to .npmrc

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: mandatory tool prefix in brick manifest (#10)

* feat: add mandatory prefix field to brick manifest

Tools are exposed as {prefix}_{toolName} to prevent collisions
between bricks and protect internal tools (no prefix).
Prefix must be unique per registry, lowercase alphanumeric.
Reserved prefixes: focus, focusmcp, mcp, internal, system.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: add edge case tests for prefix coverage (registry 98%+)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* ci: add Claude Code Review action (#11)

* ci: add Claude Code Review action

* ci: use Claude Max OAuth instead of API key

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): add id-token permission for OIDC auth

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(ci): bump GitHub Actions to v5 (Node.js 24) (#14)

- actions/checkout v4 → v5
- actions/setup-node v4 → v5
- actions/upload-artifact v4 → v5
- github/codeql-action/init v3 → v4
- github/codeql-action/analyze v3 → v4

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: enforce bare tool names in manifest — prefix applied at runtime (#15)

Tool names in mcp-brick.json must now be bare alphanumeric (e.g. "search"
not "indexer_search"). The prefix is added by the runtime when exposing
tools via MCP (prefix_toolname).

- manifest.ts: reject tool names containing non-alphanumeric chars
- tool.ts: update JSDoc to reflect new convention
- Tests updated to use bare tool names throughout

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(marketplace): add catalog-store, catalog-fetcher, installer modules (#16)

* feat(marketplace): add catalog-store, catalog-fetcher, installer modules

Pure, browser-compatible marketplace management for FocusMCP core:

- catalog-store: manage catalog source URLs (CRUD, multi-marketplace)
- catalog-fetcher: fetch + aggregate catalogs from multiple sources
- installer: plan + execute brick install/remove via npm
- resolver: extend CatalogBrickSource with npm source type

All modules use dependency injection (IO interfaces) — no direct
node: imports. The CLI provides concrete implementations.

257 tests passing, 0 typecheck errors, 0 lint errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: export marketplace modules from core package entry point

Add catalog-store, catalog-fetcher, and installer exports to index.ts
so they are part of the public API and pass knip unused-export checks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: clean up knip config — remove stale entries

Remove deprecated packages/cli workspace, redundant entry patterns
(vitest.config, playwright.config), and unused playwright binary ignore.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: extract shared validation helpers to reduce duplication

Move requireObject, requireString, optionalString, requireArray,
requireStringArray, optionalStringArray, requireBoolean into a shared
helpers.ts module. Imported by resolver, catalog-store, and installer.

Reduces jscpd duplication from 1.85% to 0.36% (threshold: 1%).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: update default catalog URL to raw.githubusercontent.com (#17)

Switch from gh-pages to raw GitHub content serving for the catalog.
URL: https://raw.githubusercontent.com/focus-mcp/marketplace/develop/publish/catalog.json

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* ci: add GitHub Packages publish workflow for dev channel (#18)

- Add .github/workflows/publish-dev.yml: publishes @focusmcp/* to
  GitHub Packages on push to develop (skips packages with private:true)
- Set "private": false in packages/core, sdk, validator package.json
- Add direct_prompt to claude-review.yml for inline PR review guidance

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: rename npm scope from @focusmcp to @focus-mcp (#21)

Rename all package names, workflow scopes, .npmrc registry bindings,
and source/test file references from @focusmcp/* to @focus-mcp/*.
Email addresses unchanged.

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): add id-token permission for npm provenance (#22)

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(ci): dev publish workflow (#24)

* feat(ci): add dev publish workflow with auto-versioning

- dev-publish.yml: publish @focus-mcp/{core,sdk,validator} to npmjs.org with --tag dev
- Auto-computed version: <base>-dev.<N> (N = commits since last tag)
- Mark packages/cli stub as private to prevent accidental publish

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: rename scope @focus-mcp → @focusmcp + dev publish workflow

- Rename @focus-mcp/{core,sdk,validator} → @focusmcp/{core,sdk,validator}
- Add dev-publish.yml for auto-versioned dev releases
- Mark packages/cli stub as private
- Update all docs, workflows, configs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: regenerate pnpm-lock.yaml after scope rename

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): use GitHub Packages for dev publish (#25)

* fix(ci): use GitHub Packages registry instead of npmjs.org

- registry-url → npm.pkg.github.com
- NODE_AUTH_TOKEN → GITHUB_TOKEN (no separate secret needed)
- Add packages: write permission

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: rename scope to @focus-mcp + npmjs.org for dev publish

- All packages: @focus-mcp/{core,sdk,validator}
- dev-publish.yml: registry.npmjs.org + NPM_TOKEN
- Regenerated lockfile

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: regenerate lockfile

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: update scope references to @focus-mcp in docs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): add id-token:write permission + remove duplicate workflows (#26)

- Add id-token:write to dev-publish.yml (required for npm provenance)
- Remove publish-dev.yml (duplicate, was targeting GitHub Packages)

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(release): v1.0.0 + stable-publish workflow (#27)

* chore(release): bump @focus-mcp/{core,sdk,validator} to 1.0.0

- Bump all 3 public packages from 0.0.0 to 1.0.0
- Add stable-publish.yml workflow (triggers on push to main → npm @latest)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(ci): remove release.yml — stable-publish.yml handles releases

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(ci): remove claude-review.yml (fails on workflow changes)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* revert: restore claude-review.yml — will work after main merge

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: v1 cleanup — README, VISION, ARCHITECTURE (#30)

* docs: v1 cleanup — rewrite README, add VISION.md and ARCHITECTURE.md

- README.md: English public-facing with install, quick start, architecture
- VISION.md: short "why" doc (1 page)
- ARCHITECTURE.md: technical doc for contributors
- AGENTS.md, CONTRIBUTING.md, ROADMAP.md: updated for v1.0.0 state
- Archived pre-v1 PRD (internal French planning doc)
- npm metadata (description, keywords, author, homepage) on sdk and validator

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: consolidate AGENTS.md + AI transparency

- merge CLAUDE.md into AGENTS.md (single source of truth per agents.md spec)
- add AI-assisted development section to README
- ad…
* chore: sync develop → main (#9)

* ci: run CodeQL on develop branch as well (#4)

The develop ruleset requires CodeQL results before merge, but the
workflow only triggered on main. PRs targeting develop were
deadlocked. Add develop to push and pull_request triggers, mirroring
the client repo's setup.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): split PRD into per-repo focused docs (#2)

* docs(prd): split monolithic PRD into per-repo PRDs

Rewrite core/PRD.md to focus solely on @focusmcp/core (lib TS):
3 piliers, manifest, SDK, validator, CLI, marketplace client, brick loader.
Reflects current architecture (core in WebView, Tauri sole HTTP gateway).

Companion PRDs added in client/ and marketplace/ repos.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): address Copilot review feedback

- Replace cross-repo relative links with absolute GitHub URLs
- Clarify monorepo layout: package `packages/core`, not `core/`
- Manifest naming: parser only enforces kebab-case; the
  `focus-` prefix is a marketplace convention
- Validator section: list only checks actually implemented; defer
  namespace/dependency/bypass checks to P1
- Stack and Decisions tables: replace "Zod / JSON Schema" with
  "custom validator (parseManifest)"; JSON Schema is used only
  for tools[].inputSchema

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): add SPDX headers for REUSE compliance

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(core): add brick loader with abstract source (#3)

* feat(core): add brick loader with abstract source

`loadBricks({ source })` reads installed bricks via an abstract `BrickSource`
(list/readManifest/loadModule), validates manifests with `parseManifest`,
ensures the loaded module exports a Brick whose manifest matches the source
declaration, and collects per-brick failures without aborting the load.

Browser-compatible: no direct FS access — the source is injected by the host
(Tauri commands for desktop, in-memory for tests).

11 tests, 100% line / 94.4% branch coverage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(core): tighten brick-loader validation per review

- Parse the module-provided brick.manifest with `parseManifest` and
  enforce strict equality against the source manifest (canonical JSON
  comparison). Catches divergence in deps/tools, not just name.
- Split default-export check into two messages: missing default vs
  default-not-an-object (clearer diagnostics for `default: 42` etc.).
- Move the manifest-shape check out of the Brick contract assertion so
  malformed `brick.manifest` produces an INVALID_MANIFEST error rather
  than a misleading "does not implement Brick contract".

3 new tests (divergence, malformed module manifest, default-not-object).
14 tests total, 100% line / 96.3% branch coverage on the loader.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(core): marketplace resolver (parse + find + semver + updates) (#5)

* feat(core): add marketplace resolver (parse + find + semver + updates)

Pure, browser-compatible module that consumes a catalog.json as
published by the FocusMCP marketplace. Does no I/O — the host injects
raw JSON and this module validates, normalizes and queries it.

Public API:
- parseCatalog(raw): Catalog — structural validation aligned with the
  published JSON Schema (kebab-case names, semver versions, typed
  source variants).
- findBrick(catalog, name): CatalogBrick | undefined
- compareSemver(a, b): -1 | 0 | 1 — minimal inline implementation
  (core + optional pre-release; no build metadata, no range matching
  yet — added at need).
- listUpdates(installed, catalog): UpdateInfo[]

20 unit tests (parseCatalog, findBrick, compareSemver incl. pre-release
ordering per semver §11, listUpdates). Full suite: 127 passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(core): address Copilot review on marketplace resolver

- Thread a full `loc` path into requireString/optionalString and the
  array variants so validation errors now produce
  "bricks[3].owner.email must be a string" instead of just
  "email must be a string". Much easier to diagnose.
- Tighten SEMVER regex: reject numeric pre-release identifiers with
  leading zeros (per semver 2.0 §9).
- Extend SEMVER regex to accept optional build metadata ("+..."),
  matching the manifest parser and semver 2.0 §10. Build metadata is
  captured but discarded for precedence comparisons, as required.
- parseTool: use conditional spread for `inputSchema` instead of
  emitting `inputSchema: undefined` when the field is missing.
  Consistent with how other optionals are handled in this file and
  compatible with `exactOptionalPropertyTypes`.
- Add 2 tests: build metadata is ignored when comparing; compareSemver
  rejects pre-release identifiers with leading zeros.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style: migrate indent from 2 to 4 spaces (#6)

* style: migrate indent from 2 to 4 spaces (Biome config)

Standardize indentation to 4 spaces across all projects.
Biome formatter config updated accordingly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: update .editorconfig indent_size to 4

Aligns with Biome formatter config to prevent editor/formatter conflicts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: align biome schema to 2.4.11 and format new develop files

Update $schema version to match installed Biome CLI.
Reformat brick-loader and marketplace resolver (merged from develop).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add CLAUDE.md (agent guidance, replaces ~/.claude memory) (#7)

* docs: add CLAUDE.md capturing the post-pivot agent guidance

Replaces the former personal memory system under
~/.claude/projects/**/memory/ with an in-repo, version-controlled
file that is auto-loaded by Claude Code (and any agents.md-compatible
tool).

Covers: project overview, the 4-repo ecosystem post CLI-first pivot
(2026-04-16), the 8 non-negotiable conventions (TDD, strict scope, pro
standards, English public-facing, gitflow, npm orgs, rulesets
checklist), this repo's specifics (lib-only, packages/core, cli moved
out, browser-compatible, no HTTP transport), and the standard feature
workflow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(claude.md): fix repo count and clarify English rule exceptions

Heading: 3 repos actifs + 1 archivé (table listed 3 not 4). Rule #5
(English public-facing): reframe as from-now-on plus list explicit
exceptions (PRD.md and CLAUDE.md stay French, existing docs stay
French until substantial rewrite) so the rule no longer contradicts
the current state of the repo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: configure GitHub Packages for @focusmcp/* packages (#8)

* chore: configure GitHub Packages registry for @focusmcp/* packages

Add publishConfig with npm.pkg.github.com registry and .npmrc for
scoped package resolution. Preparation for dev package publishing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add SPDX header to .npmrc

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: claude <claude@localhost>

* chore: sync develop → main (conflict resolution) (#13)

* feat(core): add brick loader with abstract source (#3)

* feat(core): add brick loader with abstract source

`loadBricks({ source })` reads installed bricks via an abstract `BrickSource`
(list/readManifest/loadModule), validates manifests with `parseManifest`,
ensures the loaded module exports a Brick whose manifest matches the source
declaration, and collects per-brick failures without aborting the load.

Browser-compatible: no direct FS access — the source is injected by the host
(Tauri commands for desktop, in-memory for tests).

11 tests, 100% line / 94.4% branch coverage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(core): tighten brick-loader validation per review

- Parse the module-provided brick.manifest with `parseManifest` and
  enforce strict equality against the source manifest (canonical JSON
  comparison). Catches divergence in deps/tools, not just name.
- Split default-export check into two messages: missing default vs
  default-not-an-object (clearer diagnostics for `default: 42` etc.).
- Move the manifest-shape check out of the Brick contract assertion so
  malformed `brick.manifest` produces an INVALID_MANIFEST error rather
  than a misleading "does not implement Brick contract".

3 new tests (divergence, malformed module manifest, default-not-object).
14 tests total, 100% line / 96.3% branch coverage on the loader.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(core): marketplace resolver (parse + find + semver + updates) (#5)

* feat(core): add marketplace resolver (parse + find + semver + updates)

Pure, browser-compatible module that consumes a catalog.json as
published by the FocusMCP marketplace. Does no I/O — the host injects
raw JSON and this module validates, normalizes and queries it.

Public API:
- parseCatalog(raw): Catalog — structural validation aligned with the
  published JSON Schema (kebab-case names, semver versions, typed
  source variants).
- findBrick(catalog, name): CatalogBrick | undefined
- compareSemver(a, b): -1 | 0 | 1 — minimal inline implementation
  (core + optional pre-release; no build metadata, no range matching
  yet — added at need).
- listUpdates(installed, catalog): UpdateInfo[]

20 unit tests (parseCatalog, findBrick, compareSemver incl. pre-release
ordering per semver §11, listUpdates). Full suite: 127 passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(core): address Copilot review on marketplace resolver

- Thread a full `loc` path into requireString/optionalString and the
  array variants so validation errors now produce
  "bricks[3].owner.email must be a string" instead of just
  "email must be a string". Much easier to diagnose.
- Tighten SEMVER regex: reject numeric pre-release identifiers with
  leading zeros (per semver 2.0 §9).
- Extend SEMVER regex to accept optional build metadata ("+..."),
  matching the manifest parser and semver 2.0 §10. Build metadata is
  captured but discarded for precedence comparisons, as required.
- parseTool: use conditional spread for `inputSchema` instead of
  emitting `inputSchema: undefined` when the field is missing.
  Consistent with how other optionals are handled in this file and
  compatible with `exactOptionalPropertyTypes`.
- Add 2 tests: build metadata is ignored when comparing; compareSemver
  rejects pre-release identifiers with leading zeros.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style: migrate indent from 2 to 4 spaces (#6)

* style: migrate indent from 2 to 4 spaces (Biome config)

Standardize indentation to 4 spaces across all projects.
Biome formatter config updated accordingly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: update .editorconfig indent_size to 4

Aligns with Biome formatter config to prevent editor/formatter conflicts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: align biome schema to 2.4.11 and format new develop files

Update $schema version to match installed Biome CLI.
Reformat brick-loader and marketplace resolver (merged from develop).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: configure GitHub Packages for @focusmcp/* packages (#8)

* chore: configure GitHub Packages registry for @focusmcp/* packages

Add publishConfig with npm.pkg.github.com registry and .npmrc for
scoped package resolution. Preparation for dev package publishing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add SPDX header to .npmrc

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: mandatory tool prefix in brick manifest (#10)

* feat: add mandatory prefix field to brick manifest

Tools are exposed as {prefix}_{toolName} to prevent collisions
between bricks and protect internal tools (no prefix).
Prefix must be unique per registry, lowercase alphanumeric.
Reserved prefixes: focus, focusmcp, mcp, internal, system.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: add edge case tests for prefix coverage (registry 98%+)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* ci: add Claude Code Review action (#11)

* ci: add Claude Code Review action

* ci: use Claude Max OAuth instead of API key

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): add id-token permission for OIDC auth

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: claude <claude@localhost>

* release: v1.0.0 — sync develop → main (#29)

* ci: run CodeQL on develop branch as well (#4)

The develop ruleset requires CodeQL results before merge, but the
workflow only triggered on main. PRs targeting develop were
deadlocked. Add develop to push and pull_request triggers, mirroring
the client repo's setup.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): split PRD into per-repo focused docs (#2)

* docs(prd): split monolithic PRD into per-repo PRDs

Rewrite core/PRD.md to focus solely on @focusmcp/core (lib TS):
3 piliers, manifest, SDK, validator, CLI, marketplace client, brick loader.
Reflects current architecture (core in WebView, Tauri sole HTTP gateway).

Companion PRDs added in client/ and marketplace/ repos.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): address Copilot review feedback

- Replace cross-repo relative links with absolute GitHub URLs
- Clarify monorepo layout: package `packages/core`, not `core/`
- Manifest naming: parser only enforces kebab-case; the
  `focus-` prefix is a marketplace convention
- Validator section: list only checks actually implemented; defer
  namespace/dependency/bypass checks to P1
- Stack and Decisions tables: replace "Zod / JSON Schema" with
  "custom validator (parseManifest)"; JSON Schema is used only
  for tools[].inputSchema

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): add SPDX headers for REUSE compliance

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(core): add brick loader with abstract source (#3)

* feat(core): add brick loader with abstract source

`loadBricks({ source })` reads installed bricks via an abstract `BrickSource`
(list/readManifest/loadModule), validates manifests with `parseManifest`,
ensures the loaded module exports a Brick whose manifest matches the source
declaration, and collects per-brick failures without aborting the load.

Browser-compatible: no direct FS access — the source is injected by the host
(Tauri commands for desktop, in-memory for tests).

11 tests, 100% line / 94.4% branch coverage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(core): tighten brick-loader validation per review

- Parse the module-provided brick.manifest with `parseManifest` and
  enforce strict equality against the source manifest (canonical JSON
  comparison). Catches divergence in deps/tools, not just name.
- Split default-export check into two messages: missing default vs
  default-not-an-object (clearer diagnostics for `default: 42` etc.).
- Move the manifest-shape check out of the Brick contract assertion so
  malformed `brick.manifest` produces an INVALID_MANIFEST error rather
  than a misleading "does not implement Brick contract".

3 new tests (divergence, malformed module manifest, default-not-object).
14 tests total, 100% line / 96.3% branch coverage on the loader.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(core): marketplace resolver (parse + find + semver + updates) (#5)

* feat(core): add marketplace resolver (parse + find + semver + updates)

Pure, browser-compatible module that consumes a catalog.json as
published by the FocusMCP marketplace. Does no I/O — the host injects
raw JSON and this module validates, normalizes and queries it.

Public API:
- parseCatalog(raw): Catalog — structural validation aligned with the
  published JSON Schema (kebab-case names, semver versions, typed
  source variants).
- findBrick(catalog, name): CatalogBrick | undefined
- compareSemver(a, b): -1 | 0 | 1 — minimal inline implementation
  (core + optional pre-release; no build metadata, no range matching
  yet — added at need).
- listUpdates(installed, catalog): UpdateInfo[]

20 unit tests (parseCatalog, findBrick, compareSemver incl. pre-release
ordering per semver §11, listUpdates). Full suite: 127 passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(core): address Copilot review on marketplace resolver

- Thread a full `loc` path into requireString/optionalString and the
  array variants so validation errors now produce
  "bricks[3].owner.email must be a string" instead of just
  "email must be a string". Much easier to diagnose.
- Tighten SEMVER regex: reject numeric pre-release identifiers with
  leading zeros (per semver 2.0 §9).
- Extend SEMVER regex to accept optional build metadata ("+..."),
  matching the manifest parser and semver 2.0 §10. Build metadata is
  captured but discarded for precedence comparisons, as required.
- parseTool: use conditional spread for `inputSchema` instead of
  emitting `inputSchema: undefined` when the field is missing.
  Consistent with how other optionals are handled in this file and
  compatible with `exactOptionalPropertyTypes`.
- Add 2 tests: build metadata is ignored when comparing; compareSemver
  rejects pre-release identifiers with leading zeros.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style: migrate indent from 2 to 4 spaces (#6)

* style: migrate indent from 2 to 4 spaces (Biome config)

Standardize indentation to 4 spaces across all projects.
Biome formatter config updated accordingly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: update .editorconfig indent_size to 4

Aligns with Biome formatter config to prevent editor/formatter conflicts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: align biome schema to 2.4.11 and format new develop files

Update $schema version to match installed Biome CLI.
Reformat brick-loader and marketplace resolver (merged from develop).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add CLAUDE.md (agent guidance, replaces ~/.claude memory) (#7)

* docs: add CLAUDE.md capturing the post-pivot agent guidance

Replaces the former personal memory system under
~/.claude/projects/**/memory/ with an in-repo, version-controlled
file that is auto-loaded by Claude Code (and any agents.md-compatible
tool).

Covers: project overview, the 4-repo ecosystem post CLI-first pivot
(2026-04-16), the 8 non-negotiable conventions (TDD, strict scope, pro
standards, English public-facing, gitflow, npm orgs, rulesets
checklist), this repo's specifics (lib-only, packages/core, cli moved
out, browser-compatible, no HTTP transport), and the standard feature
workflow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(claude.md): fix repo count and clarify English rule exceptions

Heading: 3 repos actifs + 1 archivé (table listed 3 not 4). Rule #5
(English public-facing): reframe as from-now-on plus list explicit
exceptions (PRD.md and CLAUDE.md stay French, existing docs stay
French until substantial rewrite) so the rule no longer contradicts
the current state of the repo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: configure GitHub Packages for @focusmcp/* packages (#8)

* chore: configure GitHub Packages registry for @focusmcp/* packages

Add publishConfig with npm.pkg.github.com registry and .npmrc for
scoped package resolution. Preparation for dev package publishing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add SPDX header to .npmrc

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: mandatory tool prefix in brick manifest (#10)

* feat: add mandatory prefix field to brick manifest

Tools are exposed as {prefix}_{toolName} to prevent collisions
between bricks and protect internal tools (no prefix).
Prefix must be unique per registry, lowercase alphanumeric.
Reserved prefixes: focus, focusmcp, mcp, internal, system.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: add edge case tests for prefix coverage (registry 98%+)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* ci: add Claude Code Review action (#11)

* ci: add Claude Code Review action

* ci: use Claude Max OAuth instead of API key

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): add id-token permission for OIDC auth

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(ci): bump GitHub Actions to v5 (Node.js 24) (#14)

- actions/checkout v4 → v5
- actions/setup-node v4 → v5
- actions/upload-artifact v4 → v5
- github/codeql-action/init v3 → v4
- github/codeql-action/analyze v3 → v4

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: enforce bare tool names in manifest — prefix applied at runtime (#15)

Tool names in mcp-brick.json must now be bare alphanumeric (e.g. "search"
not "indexer_search"). The prefix is added by the runtime when exposing
tools via MCP (prefix_toolname).

- manifest.ts: reject tool names containing non-alphanumeric chars
- tool.ts: update JSDoc to reflect new convention
- Tests updated to use bare tool names throughout

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(marketplace): add catalog-store, catalog-fetcher, installer modules (#16)

* feat(marketplace): add catalog-store, catalog-fetcher, installer modules

Pure, browser-compatible marketplace management for FocusMCP core:

- catalog-store: manage catalog source URLs (CRUD, multi-marketplace)
- catalog-fetcher: fetch + aggregate catalogs from multiple sources
- installer: plan + execute brick install/remove via npm
- resolver: extend CatalogBrickSource with npm source type

All modules use dependency injection (IO interfaces) — no direct
node: imports. The CLI provides concrete implementations.

257 tests passing, 0 typecheck errors, 0 lint errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: export marketplace modules from core package entry point

Add catalog-store, catalog-fetcher, and installer exports to index.ts
so they are part of the public API and pass knip unused-export checks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: clean up knip config — remove stale entries

Remove deprecated packages/cli workspace, redundant entry patterns
(vitest.config, playwright.config), and unused playwright binary ignore.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: extract shared validation helpers to reduce duplication

Move requireObject, requireString, optionalString, requireArray,
requireStringArray, optionalStringArray, requireBoolean into a shared
helpers.ts module. Imported by resolver, catalog-store, and installer.

Reduces jscpd duplication from 1.85% to 0.36% (threshold: 1%).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: update default catalog URL to raw.githubusercontent.com (#17)

Switch from gh-pages to raw GitHub content serving for the catalog.
URL: https://raw.githubusercontent.com/focus-mcp/marketplace/develop/publish/catalog.json

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* ci: add GitHub Packages publish workflow for dev channel (#18)

- Add .github/workflows/publish-dev.yml: publishes @focusmcp/* to
  GitHub Packages on push to develop (skips packages with private:true)
- Set "private": false in packages/core, sdk, validator package.json
- Add direct_prompt to claude-review.yml for inline PR review guidance

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: rename npm scope from @focusmcp to @focus-mcp (#21)

Rename all package names, workflow scopes, .npmrc registry bindings,
and source/test file references from @focusmcp/* to @focus-mcp/*.
Email addresses unchanged.

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): add id-token permission for npm provenance (#22)

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(ci): dev publish workflow (#24)

* feat(ci): add dev publish workflow with auto-versioning

- dev-publish.yml: publish @focus-mcp/{core,sdk,validator} to npmjs.org with --tag dev
- Auto-computed version: <base>-dev.<N> (N = commits since last tag)
- Mark packages/cli stub as private to prevent accidental publish

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: rename scope @focus-mcp → @focusmcp + dev publish workflow

- Rename @focus-mcp/{core,sdk,validator} → @focusmcp/{core,sdk,validator}
- Add dev-publish.yml for auto-versioned dev releases
- Mark packages/cli stub as private
- Update all docs, workflows, configs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: regenerate pnpm-lock.yaml after scope rename

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): use GitHub Packages for dev publish (#25)

* fix(ci): use GitHub Packages registry instead of npmjs.org

- registry-url → npm.pkg.github.com
- NODE_AUTH_TOKEN → GITHUB_TOKEN (no separate secret needed)
- Add packages: write permission

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: rename scope to @focus-mcp + npmjs.org for dev publish

- All packages: @focus-mcp/{core,sdk,validator}
- dev-publish.yml: registry.npmjs.org + NPM_TOKEN
- Regenerated lockfile

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: regenerate lockfile

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: update scope references to @focus-mcp in docs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): add id-token:write permission + remove duplicate workflows (#26)

- Add id-token:write to dev-publish.yml (required for npm provenance)
- Remove publish-dev.yml (duplicate, was targeting GitHub Packages)

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(release): v1.0.0 + stable-publish workflow (#27)

* chore(release): bump @focus-mcp/{core,sdk,validator} to 1.0.0

- Bump all 3 public packages from 0.0.0 to 1.0.0
- Add stable-publish.yml workflow (triggers on push to main → npm @latest)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(ci): remove release.yml — stable-publish.yml handles releases

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(ci): remove claude-review.yml (fails on workflow changes)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* revert: restore claude-review.yml — will work after main merge

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: claude <claude@localhost>

* release: sync develop → main (workflow fix + v1 cleanup) (#33)

* ci: run CodeQL on develop branch as well (#4)

The develop ruleset requires CodeQL results before merge, but the
workflow only triggered on main. PRs targeting develop were
deadlocked. Add develop to push and pull_request triggers, mirroring
the client repo's setup.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): split PRD into per-repo focused docs (#2)

* docs(prd): split monolithic PRD into per-repo PRDs

Rewrite core/PRD.md to focus solely on @focusmcp/core (lib TS):
3 piliers, manifest, SDK, validator, CLI, marketplace client, brick loader.
Reflects current architecture (core in WebView, Tauri sole HTTP gateway).

Companion PRDs added in client/ and marketplace/ repos.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): address Copilot review feedback

- Replace cross-repo relative links with absolute GitHub URLs
- Clarify monorepo layout: package `packages/core`, not `core/`
- Manifest naming: parser only enforces kebab-case; the
  `focus-` prefix is a marketplace convention
- Validator section: list only checks actually implemented; defer
  namespace/dependency/bypass checks to P1
- Stack and Decisions tables: replace "Zod / JSON Schema" with
  "custom validator (parseManifest)"; JSON Schema is used only
  for tools[].inputSchema

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): add SPDX headers for REUSE compliance

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(core): add brick loader with abstract source (#3)

* feat(core): add brick loader with abstract source

`loadBricks({ source })` reads installed bricks via an abstract `BrickSource`
(list/readManifest/loadModule), validates manifests with `parseManifest`,
ensures the loaded module exports a Brick whose manifest matches the source
declaration, and collects per-brick failures without aborting the load.

Browser-compatible: no direct FS access — the source is injected by the host
(Tauri commands for desktop, in-memory for tests).

11 tests, 100% line / 94.4% branch coverage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(core): tighten brick-loader validation per review

- Parse the module-provided brick.manifest with `parseManifest` and
  enforce strict equality against the source manifest (canonical JSON
  comparison). Catches divergence in deps/tools, not just name.
- Split default-export check into two messages: missing default vs
  default-not-an-object (clearer diagnostics for `default: 42` etc.).
- Move the manifest-shape check out of the Brick contract assertion so
  malformed `brick.manifest` produces an INVALID_MANIFEST error rather
  than a misleading "does not implement Brick contract".

3 new tests (divergence, malformed module manifest, default-not-object).
14 tests total, 100% line / 96.3% branch coverage on the loader.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(core): marketplace resolver (parse + find + semver + updates) (#5)

* feat(core): add marketplace resolver (parse + find + semver + updates)

Pure, browser-compatible module that consumes a catalog.json as
published by the FocusMCP marketplace. Does no I/O — the host injects
raw JSON and this module validates, normalizes and queries it.

Public API:
- parseCatalog(raw): Catalog — structural validation aligned with the
  published JSON Schema (kebab-case names, semver versions, typed
  source variants).
- findBrick(catalog, name): CatalogBrick | undefined
- compareSemver(a, b): -1 | 0 | 1 — minimal inline implementation
  (core + optional pre-release; no build metadata, no range matching
  yet — added at need).
- listUpdates(installed, catalog): UpdateInfo[]

20 unit tests (parseCatalog, findBrick, compareSemver incl. pre-release
ordering per semver §11, listUpdates). Full suite: 127 passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(core): address Copilot review on marketplace resolver

- Thread a full `loc` path into requireString/optionalString and the
  array variants so validation errors now produce
  "bricks[3].owner.email must be a string" instead of just
  "email must be a string". Much easier to diagnose.
- Tighten SEMVER regex: reject numeric pre-release identifiers with
  leading zeros (per semver 2.0 §9).
- Extend SEMVER regex to accept optional build metadata ("+..."),
  matching the manifest parser and semver 2.0 §10. Build metadata is
  captured but discarded for precedence comparisons, as required.
- parseTool: use conditional spread for `inputSchema` instead of
  emitting `inputSchema: undefined` when the field is missing.
  Consistent with how other optionals are handled in this file and
  compatible with `exactOptionalPropertyTypes`.
- Add 2 tests: build metadata is ignored when comparing; compareSemver
  rejects pre-release identifiers with leading zeros.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style: migrate indent from 2 to 4 spaces (#6)

* style: migrate indent from 2 to 4 spaces (Biome config)

Standardize indentation to 4 spaces across all projects.
Biome formatter config updated accordingly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: update .editorconfig indent_size to 4

Aligns with Biome formatter config to prevent editor/formatter conflicts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: align biome schema to 2.4.11 and format new develop files

Update $schema version to match installed Biome CLI.
Reformat brick-loader and marketplace resolver (merged from develop).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add CLAUDE.md (agent guidance, replaces ~/.claude memory) (#7)

* docs: add CLAUDE.md capturing the post-pivot agent guidance

Replaces the former personal memory system under
~/.claude/projects/**/memory/ with an in-repo, version-controlled
file that is auto-loaded by Claude Code (and any agents.md-compatible
tool).

Covers: project overview, the 4-repo ecosystem post CLI-first pivot
(2026-04-16), the 8 non-negotiable conventions (TDD, strict scope, pro
standards, English public-facing, gitflow, npm orgs, rulesets
checklist), this repo's specifics (lib-only, packages/core, cli moved
out, browser-compatible, no HTTP transport), and the standard feature
workflow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(claude.md): fix repo count and clarify English rule exceptions

Heading: 3 repos actifs + 1 archivé (table listed 3 not 4). Rule #5
(English public-facing): reframe as from-now-on plus list explicit
exceptions (PRD.md and CLAUDE.md stay French, existing docs stay
French until substantial rewrite) so the rule no longer contradicts
the current state of the repo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: configure GitHub Packages for @focusmcp/* packages (#8)

* chore: configure GitHub Packages registry for @focusmcp/* packages

Add publishConfig with npm.pkg.github.com registry and .npmrc for
scoped package resolution. Preparation for dev package publishing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add SPDX header to .npmrc

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: mandatory tool prefix in brick manifest (#10)

* feat: add mandatory prefix field to brick manifest

Tools are exposed as {prefix}_{toolName} to prevent collisions
between bricks and protect internal tools (no prefix).
Prefix must be unique per registry, lowercase alphanumeric.
Reserved prefixes: focus, focusmcp, mcp, internal, system.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: add edge case tests for prefix coverage (registry 98%+)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* ci: add Claude Code Review action (#11)

* ci: add Claude Code Review action

* ci: use Claude Max OAuth instead of API key

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): add id-token permission for OIDC auth

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(ci): bump GitHub Actions to v5 (Node.js 24) (#14)

- actions/checkout v4 → v5
- actions/setup-node v4 → v5
- actions/upload-artifact v4 → v5
- github/codeql-action/init v3 → v4
- github/codeql-action/analyze v3 → v4

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: enforce bare tool names in manifest — prefix applied at runtime (#15)

Tool names in mcp-brick.json must now be bare alphanumeric (e.g. "search"
not "indexer_search"). The prefix is added by the runtime when exposing
tools via MCP (prefix_toolname).

- manifest.ts: reject tool names containing non-alphanumeric chars
- tool.ts: update JSDoc to reflect new convention
- Tests updated to use bare tool names throughout

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(marketplace): add catalog-store, catalog-fetcher, installer modules (#16)

* feat(marketplace): add catalog-store, catalog-fetcher, installer modules

Pure, browser-compatible marketplace management for FocusMCP core:

- catalog-store: manage catalog source URLs (CRUD, multi-marketplace)
- catalog-fetcher: fetch + aggregate catalogs from multiple sources
- installer: plan + execute brick install/remove via npm
- resolver: extend CatalogBrickSource with npm source type

All modules use dependency injection (IO interfaces) — no direct
node: imports. The CLI provides concrete implementations.

257 tests passing, 0 typecheck errors, 0 lint errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: export marketplace modules from core package entry point

Add catalog-store, catalog-fetcher, and installer exports to index.ts
so they are part of the public API and pass knip unused-export checks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: clean up knip config — remove stale entries

Remove deprecated packages/cli workspace, redundant entry patterns
(vitest.config, playwright.config), and unused playwright binary ignore.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: extract shared validation helpers to reduce duplication

Move requireObject, requireString, optionalString, requireArray,
requireStringArray, optionalStringArray, requireBoolean into a shared
helpers.ts module. Imported by resolver, catalog-store, and installer.

Reduces jscpd duplication from 1.85% to 0.36% (threshold: 1%).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: update default catalog URL to raw.githubusercontent.com (#17)

Switch from gh-pages to raw GitHub content serving for the catalog.
URL: https://raw.githubusercontent.com/focus-mcp/marketplace/develop/publish/catalog.json

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* ci: add GitHub Packages publish workflow for dev channel (#18)

- Add .github/workflows/publish-dev.yml: publishes @focusmcp/* to
  GitHub Packages on push to develop (skips packages with private:true)
- Set "private": false in packages/core, sdk, validator package.json
- Add direct_prompt to claude-review.yml for inline PR review guidance

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: rename npm scope from @focusmcp to @focus-mcp (#21)

Rename all package names, workflow scopes, .npmrc registry bindings,
and source/test file references from @focusmcp/* to @focus-mcp/*.
Email addresses unchanged.

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): add id-token permission for npm provenance (#22)

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(ci): dev publish workflow (#24)

* feat(ci): add dev publish workflow with auto-versioning

- dev-publish.yml: publish @focus-mcp/{core,sdk,validator} to npmjs.org with --tag dev
- Auto-computed version: <base>-dev.<N> (N = commits since last tag)
- Mark packages/cli stub as private to prevent accidental publish

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: rename scope @focus-mcp → @focusmcp + dev publish workflow

- Rename @focus-mcp/{core,sdk,validator} → @focusmcp/{core,sdk,validator}
- Add dev-publish.yml for auto-versioned dev releases
- Mark packages/cli stub as private
- Update all docs, workflows, configs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: regenerate pnpm-lock.yaml after scope rename

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): use GitHub Packages for dev publish (#25)

* fix(ci): use GitHub Packages registry instead of npmjs.org

- registry-url → npm.pkg.github.com
- NODE_AUTH_TOKEN → GITHUB_TOKEN (no separate secret needed)
- Add packages: write permission

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: rename scope to @focus-mcp + npmjs.org for dev publish

- All packages: @focus-mcp/{core,sdk,validator}
- dev-publish.yml: registry.npmjs.org + NPM_TOKEN
- Regenerated lockfile

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: regenerate lockfile

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: update scope references to @focus-mcp in docs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): add id-token:write permission + remove duplicate workflows (#26)

- Add id-token:write to dev-publish.yml (required for npm provenance)
- Remove publish-dev.yml (duplicate, was targeting GitHub Packages)

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(release): v1.0.0 + stable-publish workflow (#27)

* chore(release): bump @focus-mcp/{core,sdk,validator} to 1.0.0

- Bump all 3 public packages from 0.0.0 to 1.0.0
- Add stable-publish.yml workflow (triggers on push to main → npm @latest)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(ci): remove release.yml — stable-publish.yml handles releases

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(ci): remove claude-review.yml (fails on workflow changes)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* revert: restore claude-review.yml — will work after main merge

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: v1 cleanup — README, VISION, ARCHITECTURE (#30)

* docs: v1 cleanup — rewrite README, add VISION.md and ARCHITECTURE.md

- README.md: English public-facing with install, quick start, architecture
- VISION.md: short "why" doc (1 page)
- ARCHITECTURE.md: technical doc for contributors
- AGENTS.md, CONTRIBUTING.md, ROADMAP.md: updated for v1.0.0 state
- Archived pre-v1 PRD (internal French planning doc)
- npm metadata (description, keywords, author, homepage) on sdk and validator

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: consolidate AGENTS.md + AI transparency

- merge CLAUDE.md into AGENTS.md (single source of truth per agents.md spec)
- add AI-assisted development section to README
- add AI-assisted contributions section to CONTRIBUTING

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(ci): allow `release` commit type in commitlint

Release commits (e.g. `release: v1.0.0`) were rejected by the type-enum.
Add `release` as a valid type since we already use it on main branches.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(deps): pin uuid to ^14.0.0 via pnpm override (GHSA-w5hq-g745-h8pq)

Transitive from @cyclonedx/cdxgen (dev only). Not in runtime bundle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(ci): rename \`direct_prompt\` → \`prompt\` in claude-code-action (#31)

The v1 action deprecated direct_prompt. Without a valid trigger input,
PRs got a green check but Claude never posted any review.

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: claude <claude@localhost>

* release: sync develop → main (#37)

* ci: run CodeQL on develop branch as well (#4)

The develop ruleset requires CodeQL results before merge, but the
workflow only triggered on main. PRs targeting develop were
deadlocked. Add develop to push and pull_request triggers, mirroring
the client repo's setup.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): split PRD into per-repo focused docs (#2)

* docs(prd): split monolithic PRD into per-repo PRDs

Rewrite core/PRD.md to focus solely on @focusmcp/core (lib TS):
3 piliers, manifest, SDK, validator, CLI, marketplace client, brick loader.
Reflects current architecture (core in WebView, Tauri sole HTTP gateway).

Companion PRDs added in client/ and marketplace/ repos.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): address Copilot review feedback

- Replace cross-repo relative links with absolute GitHub URLs
- Clarify monorepo layout: package `packages/core`, not `core/`
- Manifest naming: parser only enforces kebab-case; the
  `focus-` prefix is a marketplace convention
- Validator section: list only checks actually implemented; defer
  namespace/dependency/bypass checks to P1
- Stack and Decisions tables: replace "Zod / JSON Schema" with
  "custom validator (parseManifest)"; JSON Schema is used only
  for tools[].inputSchema

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(prd): add SPDX headers for REUSE compliance

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(core): add brick loader with abstract source (#3)

* feat(core): add brick loader with abstract source

`loadBricks({ source })` reads installed bricks via an abstract `BrickSource`
(list/readManifest/loadModule), validates manifests with `parseManifest`,
ensures the loaded module exports a Brick whose manifest matches the source
declaration, and collects per-brick failures without aborting the load.

Browser-compatible: no direct FS access — the source is injected by the host
(Tauri commands for desktop, in-memory for tests).

11 tests, 100% line / 94.4% branch coverage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(core): tighten brick-loader validation per review

- Parse the module-provided brick.manifest with `parseManifest` and
  enforce strict equality against the source manifest (canonical JSON
  comparison). Catches divergence in deps/tools, not just name.
- Split default-export check into two messages: missing default vs
  default-not-an-object (clearer diagnostics for `default: 42` etc.).
- Move the manifest-shape check out of the Brick contract assertion so
  malformed `brick.manifest` produces an INVALID_MANIFEST error rather
  than a misleading "does not implement Brick contract".

3 new tests (divergence, malformed module manifest, default-not-object).
14 tests total, 100% line / 96.3% branch coverage on the loader.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(core): marketplace resolver (parse + find + semver + updates) (#5)

* feat(core): add marketplace resolver (parse + find + semver + updates)

Pure, browser-compatible module that consumes a catalog.json as
published by the FocusMCP marketplace. Does no I/O — the host injects
raw JSON and this module validates, normalizes and queries it.

Public API:
- parseCatalog(raw): Catalog — structural validation aligned with the
  published JSON Schema (kebab-case names, semver versions, typed
  source variants).
- findBrick(catalog, name): CatalogBrick | undefined
- compareSemver(a, b): -1 | 0 | 1 — minimal inline implementation
  (core + optional pre-release; no build metadata, no range matching
  yet — added at need).
- listUpdates(installed, catalog): UpdateInfo[]

20 unit tests (parseCatalog, findBrick, compareSemver incl. pre-release
ordering per semver §11, listUpdates). Full suite: 127 passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor(core): address Copilot review on marketplace resolver

- Thread a full `loc` path into requireString/optionalString and the
  array variants so validation errors now produce
  "bricks[3].owner.email must be a string" instead of just
  "email must be a string". Much easier to diagnose.
- Tighten SEMVER regex: reject numeric pre-release identifiers with
  leading zeros (per semver 2.0 §9).
- Extend SEMVER regex to accept optional build metadata ("+..."),
  matching the manifest parser and semver 2.0 §10. Build metadata is
  captured but discarded for precedence comparisons, as required.
- parseTool: use conditional spread for `inputSchema` instead of
  emitting `inputSchema: undefined` when the field is missing.
  Consistent with how other optionals are handled in this file and
  compatible with `exactOptionalPropertyTypes`.
- Add 2 tests: build metadata is ignored when comparing; compareSemver
  rejects pre-release identifiers with leading zeros.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style: migrate indent from 2 to 4 spaces (#6)

* style: migrate indent from 2 to 4 spaces (Biome config)

Standardize indentation to 4 spaces across all projects.
Biome formatter config updated accordingly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: update .editorconfig indent_size to 4

Aligns with Biome formatter config to prevent editor/formatter conflicts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: align biome schema to 2.4.11 and format new develop files

Update $schema version to match installed Biome CLI.
Reformat brick-loader and marketplace resolver (merged from develop).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: add CLAUDE.md (agent guidance, replaces ~/.claude memory) (#7)

* docs: add CLAUDE.md capturing the post-pivot agent guidance

Replaces the former personal memory system under
~/.claude/projects/**/memory/ with an in-repo, version-controlled
file that is auto-loaded by Claude Code (and any agents.md-compatible
tool).

Covers: project overview, the 4-repo ecosystem post CLI-first pivot
(2026-04-16), the 8 non-negotiable conventions (TDD, strict scope, pro
standards, English public-facing, gitflow, npm orgs, rulesets
checklist), this repo's specifics (lib-only, packages/core, cli moved
out, browser-compatible, no HTTP transport), and the standard feature
workflow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(claude.md): fix repo count and clarify English rule exceptions

Heading: 3 repos actifs + 1 archivé (table listed 3 not 4). Rule #5
(English public-facing): reframe as from-now-on plus list explicit
exceptions (PRD.md and CLAUDE.md stay French, existing docs stay
French until substantial rewrite) so the rule no longer contradicts
the current state of the repo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: configure GitHub Packages for @focusmcp/* packages (#8)

* chore: configure GitHub Packages registry for @focusmcp/* packages

Add publishConfig with npm.pkg.github.com registry and .npmrc for
scoped package resolution. Preparation for dev package publishing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: add SPDX header to .npmrc

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: mandatory tool prefix in brick manifest (#10)

* feat: add mandatory prefix field to brick manifest

Tools are exposed as {prefix}_{toolName} to prevent collisions
between bricks and protect internal tools (no prefix).
Prefix must be unique per registry, lowercase alphanumeric.
Reserved prefixes: focus, focusmcp, mcp, internal, system.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: add edge case tests for prefix coverage (registry 98%+)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* ci: add Claude Code Review action (#11)

* ci: add Claude Code Review action

* ci: use Claude Max OAuth instead of API key

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): add id-token permission for OIDC auth

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(ci): bump GitHub Actions to v5 (Node.js 24) (#14)

- actions/checkout v4 → v5
- actions/setup-node v4 → v5
- actions/upload-artifact v4 → v5
- github/codeql-action/init v3 → v4
- github/codeql-action/analyze v3 → v4

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: enforce bare tool names in manifest — prefix applied at runtime (#15)

Tool names in mcp-brick.json must now be bare alphanumeric (e.g. "search"
not "indexer_search"). The prefix is added by the runtime when exposing
tools via MCP (prefix_toolname).

- manifest.ts: reject tool names containing non-alphanumeric chars
- tool.ts: update JSDoc to reflect new convention
- Tests updated to use bare tool names throughout

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(marketplace): add catalog-store, catalog-fetcher, installer modules (#16)

* feat(marketplace): add catalog-store, catalog-fetcher, installer modules

Pure, browser-compatible marketplace management for FocusMCP core:

- catalog-store: manage catalog source URLs (CRUD, multi-marketplace)
- catalog-fetcher: fetch + aggregate catalogs from multiple sources
- installer: plan + execute brick install/remove via npm
- resolver: extend CatalogBrickSource with npm source type

All modules use dependency injection (IO interfaces) — no direct
node: imports. The CLI provides concrete implementations.

257 tests passing, 0 typecheck errors, 0 lint errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: export marketplace modules from core package entry point

Add catalog-store, catalog-fetcher, and installer exports to index.ts
so they are part of the public API and pass knip unused-export checks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: clean up knip config — remove stale entries

Remove deprecated packages/cli workspace, redundant entry patterns
(vitest.config, playwright.config), and unused playwright binary ignore.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: extract shared validation helpers to reduce duplication

Move requireObject, requireString, optionalString, requireArray,
requireStringArray, optionalStringArray, requireBoolean into a shared
helpers.ts module. Imported by resolver, catalog-store, and installer.

Reduces jscpd duplication from 1.85% to 0.36% (threshold: 1%).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: update default catalog URL to raw.githubusercontent.com (#17)

Switch from gh-pages to raw GitHub content serving for the catalog.
URL: https://raw.githubusercontent.com/focus-mcp/marketplace/develop/publish/catalog.json

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* ci: add GitHub Packages publish workflow for dev channel (#18)

- Add .github/workflows/publish-dev.yml: publishes @focusmcp/* to
  GitHub Packages on push to develop (skips packages with private:true)
- Set "private": false in packages/core, sdk, validator package.json
- Add direct_prompt to claude-review.yml for inline PR review guidance

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: rename npm scope from @focusmcp to @focus-mcp (#21)

Rename all package names, workflow scopes, .npmrc registry bindings,
and source/test file references from @focusmcp/* to @focus-mcp/*.
Email addresses unchanged.

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci): add id-token permission for npm provenance (#22)

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(ci): dev publish workflow (#24)

* feat(ci): add dev publish workflow with auto-versioning

- dev-publish.yml: publish @focus-mcp/{core,sdk,validator} to npmjs.org with --tag dev
- Auto-computed version: <base>-dev.<N> (N = commits since last tag)
- Mark packages/cli stub as private to prevent accidental publish

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: rename scope @focus-mcp → @focusmcp + dev publish workflow

- Rename @focus-mcp/{core,sdk,validator} → @focusmcp/{core,sdk,validator}
- Add dev-publish.yml for auto-versioned dev releases
- Mark packages/cli stub as private
- Update all docs, workflows, configs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: regenerate pnpm-lock.yaml after scope rename

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): use GitHub Packages for dev publish (#25)

* fix(ci): use GitHub Packages registry instead of npmjs.org

- registry-url → npm.pkg.github.com
- NODE_AUTH_TOKEN → GITHUB_TOKEN (no separate secret needed)
- Add packages: write permission

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: rename scope to @focus-mcp + npmjs.org for dev publish

- All packages: @focus-mcp/{core,sdk,validator}
- dev-publish.yml: registry.npmjs.org + NPM_TOKEN
- Regenerated lockfile

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: regenerate lockfile

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: update scope references to @focus-mcp in docs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(ci): add id-token:write permission + remove duplicate workflows (#26)

- Add id-token:write to dev-publish.yml (required for npm provenance)
- Remove publish-dev.yml (duplicate, was targeting GitHub Packages)

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(release): v1.0.0 + stable-publish workflow (#27)

* chore(release): bump @focus-mcp/{core,sdk,validator} to 1.0.0

- Bump all 3 public packages from 0.0.0 to 1.0.0
- Add stable-publish.yml workflow (triggers on push to main → npm @latest)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(ci): remove release.yml — stable-publish.yml handles releases

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore(ci): remove claude-review.yml (fails on workflow changes)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* revert: restore claude-review.yml — will work after main merge

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs: v1 cleanup — README, VISION, ARCHITECTURE (#30)

* docs: v1 cleanup — rewrite README, add VISION.md and ARCHITECTURE.md

- README.md: English public-facing with install, quick start, architecture
- VISION.md: short "why" doc (1 page)
- ARCHITECTURE.md: technical doc for contributors
- AGENTS.md, CONTRIBUTING.md, ROADMAP.md: updated for v1.0.0 state
- Archived pre-v1 PRD (internal French planning doc)
- npm metadata (description, keywords, author, homepage) on sdk and validator

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: consolidate AGENTS.md + AI transparency

- merge CLAUDE.md into AGENTS.md (single source of truth per agents.md spec)
- add AI-assisted development …
Merge commit (not squash) to reconcile develop with main and preserve
original commit authors from previous release merges.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
….5.0

chore: back-merge main → develop with merge commit (preserve history)
Add npm upgrade step (npm@latest >= 11.5.1) before each publish step to
enable OIDC token-based auth. Comment out .npmrc token line. NODE_AUTH_TOKEN
kept as fallback during transition until Trusted Publishers are configured on npmjs.com.

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolves CVE in postcss < 8.5.10 — transitive dep via tsup.
Updated from 8.5.9 to 8.5.12.

Co-authored-by: claude <claude@localhost>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Add git branching rules, commit convention details, browser-compat golden
rule (core=brains, no Node.js-only imports in packages/core or
packages/sdk), update-checker pattern reference, EventBus/Registry
coverage >= 95% threshold, common pitfalls (browser-compat breakage,
coverage drop, develop↔main divergence, ADR missing) to CONTRIBUTING.md.

Add docs/RELEASE.md covering the 3-package monorepo release process
(core → cli → marketplace order), manual fallback, back-merge recovery,
post-release verification for all 3 packages, and OIDC publishing.

Co-authored-by: claude <claude@localhost>
Co-authored-by: claude <claude@localhost>
Co-authored-by: claude <claude@localhost>
Co-authored-by: claude <claude@localhost>
Co-authored-by: claude <claude@localhost>
…s-mcp/validator@1.0.7 (#80)

Co-authored-by: claude <claude@localhost>
* chore(release): bump @focus-mcp/core@1.5.1 @focus-mcp/sdk@1.5.1 @focus-mcp/validator@1.0.7

* fix(ci): remove broken npm upgrade step in stable-publish

---------

Co-authored-by: claude <claude@localhost>
* ci: run CodeQL on develop branch as well (#4)

The develop ruleset requires CodeQL results before merge, but the
workflow only triggered on main. PRs targeting develop were
deadlocked. Add develop to push and pull_request triggers, mirroring
the client repo's setup.



* docs(prd): split PRD into per-repo focused docs (#2)

* docs(prd): split monolithic PRD into per-repo PRDs

Rewrite core/PRD.md to focus solely on @focusmcp/core (lib TS):
3 piliers, manifest, SDK, validator, CLI, marketplace client, brick loader.
Reflects current architecture (core in WebView, Tauri sole HTTP gateway).

Companion PRDs added in client/ and marketplace/ repos.



* docs(prd): address Copilot review feedback

- Replace cross-repo relative links with absolute GitHub URLs
- Clarify monorepo layout: package `packages/core`, not `core/`
- Manifest naming: parser only enforces kebab-case; the
  `focus-` prefix is a marketplace convention
- Validator section: list only checks actually implemented; defer
  namespace/dependency/bypass checks to P1
- Stack and Decisions tables: replace "Zod / JSON Schema" with
  "custom validator (parseManifest)"; JSON Schema is used only
  for tools[].inputSchema



* docs(prd): add SPDX headers for REUSE compliance



---------



* feat(core): add brick loader with abstract source (#3)

* feat(core): add brick loader with abstract source

`loadBricks({ source })` reads installed bricks via an abstract `BrickSource`
(list/readManifest/loadModule), validates manifests with `parseManifest`,
ensures the loaded module exports a Brick whose manifest matches the source
declaration, and collects per-brick failures without aborting the load.

Browser-compatible: no direct FS access — the source is injected by the host
(Tauri commands for desktop, in-memory for tests).

11 tests, 100% line / 94.4% branch coverage.



* refactor(core): tighten brick-loader validation per review

- Parse the module-provided brick.manifest with `parseManifest` and
  enforce strict equality against the source manifest (canonical JSON
  comparison). Catches divergence in deps/tools, not just name.
- Split default-export check into two messages: missing default vs
  default-not-an-object (clearer diagnostics for `default: 42` etc.).
- Move the manifest-shape check out of the Brick contract assertion so
  malformed `brick.manifest` produces an INVALID_MANIFEST error rather
  than a misleading "does not implement Brick contract".

3 new tests (divergence, malformed module manifest, default-not-object).
14 tests total, 100% line / 96.3% branch coverage on the loader.



---------



* feat(core): marketplace resolver (parse + find + semver + updates) (#5)

* feat(core): add marketplace resolver (parse + find + semver + updates)

Pure, browser-compatible module that consumes a catalog.json as
published by the FocusMCP marketplace. Does no I/O — the host injects
raw JSON and this module validates, normalizes and queries it.

Public API:
- parseCatalog(raw): Catalog — structural validation aligned with the
  published JSON Schema (kebab-case names, semver versions, typed
  source variants).
- findBrick(catalog, name): CatalogBrick | undefined
- compareSemver(a, b): -1 | 0 | 1 — minimal inline implementation
  (core + optional pre-release; no build metadata, no range matching
  yet — added at need).
- listUpdates(installed, catalog): UpdateInfo[]

20 unit tests (parseCatalog, findBrick, compareSemver incl. pre-release
ordering per semver §11, listUpdates). Full suite: 127 passing.



* refactor(core): address Copilot review on marketplace resolver

- Thread a full `loc` path into requireString/optionalString and the
  array variants so validation errors now produce
  "bricks[3].owner.email must be a string" instead of just
  "email must be a string". Much easier to diagnose.
- Tighten SEMVER regex: reject numeric pre-release identifiers with
  leading zeros (per semver 2.0 §9).
- Extend SEMVER regex to accept optional build metadata ("+..."),
  matching the manifest parser and semver 2.0 §10. Build metadata is
  captured but discarded for precedence comparisons, as required.
- parseTool: use conditional spread for `inputSchema` instead of
  emitting `inputSchema: undefined` when the field is missing.
  Consistent with how other optionals are handled in this file and
  compatible with `exactOptionalPropertyTypes`.
- Add 2 tests: build metadata is ignored when comparing; compareSemver
  rejects pre-release identifiers with leading zeros.



---------



* style: migrate indent from 2 to 4 spaces (#6)

* style: migrate indent from 2 to 4 spaces (Biome config)

Standardize indentation to 4 spaces across all projects.
Biome formatter config updated accordingly.



* fix: update .editorconfig indent_size to 4

Aligns with Biome formatter config to prevent editor/formatter conflicts.



* fix: align biome schema to 2.4.11 and format new develop files

Update $schema version to match installed Biome CLI.
Reformat brick-loader and marketplace resolver (merged from develop).



---------




* docs: add CLAUDE.md (agent guidance, replaces ~/.claude memory) (#7)

* docs: add CLAUDE.md capturing the post-pivot agent guidance

Replaces the former personal memory system under
~/.claude/projects/**/memory/ with an in-repo, version-controlled
file that is auto-loaded by Claude Code (and any agents.md-compatible
tool).

Covers: project overview, the 4-repo ecosystem post CLI-first pivot
(2026-04-16), the 8 non-negotiable conventions (TDD, strict scope, pro
standards, English public-facing, gitflow, npm orgs, rulesets
checklist), this repo's specifics (lib-only, packages/core, cli moved
out, browser-compatible, no HTTP transport), and the standard feature
workflow.



* docs(claude.md): fix repo count and clarify English rule exceptions

Heading: 3 repos actifs + 1 archivé (table listed 3 not 4). Rule #5
(English public-facing): reframe as from-now-on plus list explicit
exceptions (PRD.md and CLAUDE.md stay French, existing docs stay
French until substantial rewrite) so the rule no longer contradicts
the current state of the repo.



---------



* chore: configure GitHub Packages for @focusmcp/* packages (#8)

* chore: configure GitHub Packages registry for @focusmcp/* packages

Add publishConfig with npm.pkg.github.com registry and .npmrc for
scoped package resolution. Preparation for dev package publishing.



* fix: add SPDX header to .npmrc



---------




* feat: mandatory tool prefix in brick manifest (#10)

* feat: add mandatory prefix field to brick manifest

Tools are exposed as {prefix}_{toolName} to prevent collisions
between bricks and protect internal tools (no prefix).
Prefix must be unique per registry, lowercase alphanumeric.
Reserved prefixes: focus, focusmcp, mcp, internal, system.



* test: add edge case tests for prefix coverage (registry 98%+)



---------




* ci: add Claude Code Review action (#11)

* ci: add Claude Code Review action

* ci: use Claude Max OAuth instead of API key



* fix(ci): add id-token permission for OIDC auth

---------




* chore(ci): bump GitHub Actions to v5 (Node.js 24) (#14)

- actions/checkout v4 → v5
- actions/setup-node v4 → v5
- actions/upload-artifact v4 → v5
- github/codeql-action/init v3 → v4
- github/codeql-action/analyze v3 → v4




* feat: enforce bare tool names in manifest — prefix applied at runtime (#15)

Tool names in mcp-brick.json must now be bare alphanumeric (e.g. "search"
not "indexer_search"). The prefix is added by the runtime when exposing
tools via MCP (prefix_toolname).

- manifest.ts: reject tool names containing non-alphanumeric chars
- tool.ts: update JSDoc to reflect new convention
- Tests updated to use bare tool names throughout




* feat(marketplace): add catalog-store, catalog-fetcher, installer modules (#16)

* feat(marketplace): add catalog-store, catalog-fetcher, installer modules

Pure, browser-compatible marketplace management for FocusMCP core:

- catalog-store: manage catalog source URLs (CRUD, multi-marketplace)
- catalog-fetcher: fetch + aggregate catalogs from multiple sources
- installer: plan + execute brick install/remove via npm
- resolver: extend CatalogBrickSource with npm source type

All modules use dependency injection (IO interfaces) — no direct
node: imports. The CLI provides concrete implementations.

257 tests passing, 0 typecheck errors, 0 lint errors.



* fix: export marketplace modules from core package entry point

Add catalog-store, catalog-fetcher, and installer exports to index.ts
so they are part of the public API and pass knip unused-export checks.



* chore: clean up knip config — remove stale entries

Remove deprecated packages/cli workspace, redundant entry patterns
(vitest.config, playwright.config), and unused playwright binary ignore.



* refactor: extract shared validation helpers to reduce duplication

Move requireObject, requireString, optionalString, requireArray,
requireStringArray, optionalStringArray, requireBoolean into a shared
helpers.ts module. Imported by resolver, catalog-store, and installer.

Reduces jscpd duplication from 1.85% to 0.36% (threshold: 1%).



---------




* feat: update default catalog URL to raw.githubusercontent.com (#17)

Switch from gh-pages to raw GitHub content serving for the catalog.
URL: https://raw.githubusercontent.com/focus-mcp/marketplace/develop/publish/catalog.json




* ci: add GitHub Packages publish workflow for dev channel (#18)

- Add .github/workflows/publish-dev.yml: publishes @focusmcp/* to
  GitHub Packages on push to develop (skips packages with private:true)
- Set "private": false in packages/core, sdk, validator package.json
- Add direct_prompt to claude-review.yml for inline PR review guidance




* feat: rename npm scope from @focusmcp to @focus-mcp (#21)

Rename all package names, workflow scopes, .npmrc registry bindings,
and source/test file references from @focusmcp/* to @focus-mcp/*.
Email addresses unchanged.




* fix(ci): add id-token permission for npm provenance (#22)




* feat(ci): dev publish workflow (#24)

* feat(ci): add dev publish workflow with auto-versioning

- dev-publish.yml: publish @focus-mcp/{core,sdk,validator} to npmjs.org with --tag dev
- Auto-computed version: <base>-dev.<N> (N = commits since last tag)
- Mark packages/cli stub as private to prevent accidental publish



* feat: rename scope @focus-mcp@focusmcp + dev publish workflow

- Rename @focus-mcp/{core,sdk,validator} → @focusmcp/{core,sdk,validator}
- Add dev-publish.yml for auto-versioned dev releases
- Mark packages/cli stub as private
- Update all docs, workflows, configs



* chore: regenerate pnpm-lock.yaml after scope rename



---------




* fix(ci): use GitHub Packages for dev publish (#25)

* fix(ci): use GitHub Packages registry instead of npmjs.org

- registry-url → npm.pkg.github.com
- NODE_AUTH_TOKEN → GITHUB_TOKEN (no separate secret needed)
- Add packages: write permission



* fix: rename scope to @focus-mcp + npmjs.org for dev publish

- All packages: @focus-mcp/{core,sdk,validator}
- dev-publish.yml: registry.npmjs.org + NPM_TOKEN
- Regenerated lockfile



* chore: regenerate lockfile



* docs: update scope references to @focus-mcp in docs



---------




* fix(ci): add id-token:write permission + remove duplicate workflows (#26)

- Add id-token:write to dev-publish.yml (required for npm provenance)
- Remove publish-dev.yml (duplicate, was targeting GitHub Packages)




* chore(release): v1.0.0 + stable-publish workflow (#27)

* chore(release): bump @focus-mcp/{core,sdk,validator} to 1.0.0

- Bump all 3 public packages from 0.0.0 to 1.0.0
- Add stable-publish.yml workflow (triggers on push to main → npm @latest)



* chore(ci): remove release.yml — stable-publish.yml handles releases



* chore(ci): remove claude-review.yml (fails on workflow changes)



* revert: restore claude-review.yml — will work after main merge



---------




* docs: v1 cleanup — README, VISION, ARCHITECTURE (#30)

* docs: v1 cleanup — rewrite README, add VISION.md and ARCHITECTURE.md

- README.md: English public-facing with install, quick start, architecture
- VISION.md: short "why" doc (1 page)
- ARCHITECTURE.md: technical doc for contributors
- AGENTS.md, CONTRIBUTING.md, ROADMAP.md: updated for v1.0.0 state
- Archived pre-v1 PRD (internal French planning doc)
- npm metadata (description, keywords, author, homepage) on sdk and validator



* docs: consolidate AGENTS.md + AI transparency

- merge CLAUDE.md into AGENTS.md (single source of truth per agents.md spec)
- add AI-assisted development section to README
- add AI-assisted contributions section to CONTRIBUTING



* chore(ci): allow `release` commit type in commitlint

Release commits (e.g. `release: v1.0.0`) were rejected by the type-enum.
Add `release` as a valid type since we already use it on main branches.



* fix(deps): pin uuid to ^14.0.0 via pnpm override (GHSA-w5hq-g745-h8pq)

Transitive from @cyclonedx/cdxgen (dev only). Not in runtime bundle.



---------




* fix(ci): rename \`direct_prompt\` → \`prompt\` in claude-code-action (#31)

The v1 action deprecated direct_prompt. Without a valid trigger input,
PRs got a green check but Claude never posted any review.




* fix(ci): add checkout step to claude-review workflow (#34)

The claude-code-action needs full git history to fetch base branches.
Without a preceding actions/checkout@v5 with fetch-depth: 0, the action
fails with "git fetch origin develop --depth=1: exit 128".




* chore: sync main back into develop (release bumps) (#35)

* chore: sync develop → main (#9)

* ci: run CodeQL on develop branch as well (#4)

The develop ruleset requires CodeQL results before merge, but the
workflow only triggered on main. PRs targeting develop were
deadlocked. Add develop to push and pull_request triggers, mirroring
the client repo's setup.



* docs(prd): split PRD into per-repo focused docs (#2)

* docs(prd): split monolithic PRD into per-repo PRDs

Rewrite core/PRD.md to focus solely on @focusmcp/core (lib TS):
3 piliers, manifest, SDK, validator, CLI, marketplace client, brick loader.
Reflects current architecture (core in WebView, Tauri sole HTTP gateway).

Companion PRDs added in client/ and marketplace/ repos.



* docs(prd): address Copilot review feedback

- Replace cross-repo relative links with absolute GitHub URLs
- Clarify monorepo layout: package `packages/core`, not `core/`
- Manifest naming: parser only enforces kebab-case; the
  `focus-` prefix is a marketplace convention
- Validator section: list only checks actually implemented; defer
  namespace/dependency/bypass checks to P1
- Stack and Decisions tables: replace "Zod / JSON Schema" with
  "custom validator (parseManifest)"; JSON Schema is used only
  for tools[].inputSchema



* docs(prd): add SPDX headers for REUSE compliance



---------



* feat(core): add brick loader with abstract source (#3)

* feat(core): add brick loader with abstract source

`loadBricks({ source })` reads installed bricks via an abstract `BrickSource`
(list/readManifest/loadModule), validates manifests with `parseManifest`,
ensures the loaded module exports a Brick whose manifest matches the source
declaration, and collects per-brick failures without aborting the load.

Browser-compatible: no direct FS access — the source is injected by the host
(Tauri commands for desktop, in-memory for tests).

11 tests, 100% line / 94.4% branch coverage.



* refactor(core): tighten brick-loader validation per review

- Parse the module-provided brick.manifest with `parseManifest` and
  enforce strict equality against the source manifest (canonical JSON
  comparison). Catches divergence in deps/tools, not just name.
- Split default-export check into two messages: missing default vs
  default-not-an-object (clearer diagnostics for `default: 42` etc.).
- Move the manifest-shape check out of the Brick contract assertion so
  malformed `brick.manifest` produces an INVALID_MANIFEST error rather
  than a misleading "does not implement Brick contract".

3 new tests (divergence, malformed module manifest, default-not-object).
14 tests total, 100% line / 96.3% branch coverage on the loader.



---------



* feat(core): marketplace resolver (parse + find + semver + updates) (#5)

* feat(core): add marketplace resolver (parse + find + semver + updates)

Pure, browser-compatible module that consumes a catalog.json as
published by the FocusMCP marketplace. Does no I/O — the host injects
raw JSON and this module validates, normalizes and queries it.

Public API:
- parseCatalog(raw): Catalog — structural validation aligned with the
  published JSON Schema (kebab-case names, semver versions, typed
  source variants).
- findBrick(catalog, name): CatalogBrick | undefined
- compareSemver(a, b): -1 | 0 | 1 — minimal inline implementation
  (core + optional pre-release; no build metadata, no range matching
  yet — added at need).
- listUpdates(installed, catalog): UpdateInfo[]

20 unit tests (parseCatalog, findBrick, compareSemver incl. pre-release
ordering per semver §11, listUpdates). Full suite: 127 passing.



* refactor(core): address Copilot review on marketplace resolver

- Thread a full `loc` path into requireString/optionalString and the
  array variants so validation errors now produce
  "bricks[3].owner.email must be a string" instead of just
  "email must be a string". Much easier to diagnose.
- Tighten SEMVER regex: reject numeric pre-release identifiers with
  leading zeros (per semver 2.0 §9).
- Extend SEMVER regex to accept optional build metadata ("+..."),
  matching the manifest parser and semver 2.0 §10. Build metadata is
  captured but discarded for precedence comparisons, as required.
- parseTool: use conditional spread for `inputSchema` instead of
  emitting `inputSchema: undefined` when the field is missing.
  Consistent with how other optionals are handled in this file and
  compatible with `exactOptionalPropertyTypes`.
- Add 2 tests: build metadata is ignored when comparing; compareSemver
  rejects pre-release identifiers with leading zeros.



---------



* style: migrate indent from 2 to 4 spaces (#6)

* style: migrate indent from 2 to 4 spaces (Biome config)

Standardize indentation to 4 spaces across all projects.
Biome formatter config updated accordingly.



* fix: update .editorconfig indent_size to 4

Aligns with Biome formatter config to prevent editor/formatter conflicts.



* fix: align biome schema to 2.4.11 and format new develop files

Update $schema version to match installed Biome CLI.
Reformat brick-loader and marketplace resolver (merged from develop).



---------




* docs: add CLAUDE.md (agent guidance, replaces ~/.claude memory) (#7)

* docs: add CLAUDE.md capturing the post-pivot agent guidance

Replaces the former personal memory system under
~/.claude/projects/**/memory/ with an in-repo, version-controlled
file that is auto-loaded by Claude Code (and any agents.md-compatible
tool).

Covers: project overview, the 4-repo ecosystem post CLI-first pivot
(2026-04-16), the 8 non-negotiable conventions (TDD, strict scope, pro
standards, English public-facing, gitflow, npm orgs, rulesets
checklist), this repo's specifics (lib-only, packages/core, cli moved
out, browser-compatible, no HTTP transport), and the standard feature
workflow.



* docs(claude.md): fix repo count and clarify English rule exceptions

Heading: 3 repos actifs + 1 archivé (table listed 3 not 4). Rule #5
(English public-facing): reframe as from-now-on plus list explicit
exceptions (PRD.md and CLAUDE.md stay French, existing docs stay
French until substantial rewrite) so the rule no longer contradicts
the current state of the repo.



---------



* chore: configure GitHub Packages for @focusmcp/* packages (#8)

* chore: configure GitHub Packages registry for @focusmcp/* packages

Add publishConfig with npm.pkg.github.com registry and .npmrc for
scoped package resolution. Preparation for dev package publishing.



* fix: add SPDX header to .npmrc



---------




---------




* chore: sync develop → main (conflict resolution) (#13)

* feat(core): add brick loader with abstract source (#3)

* feat(core): add brick loader with abstract source

`loadBricks({ source })` reads installed bricks via an abstract `BrickSource`
(list/readManifest/loadModule), validates manifests with `parseManifest`,
ensures the loaded module exports a Brick whose manifest matches the source
declaration, and collects per-brick failures without aborting the load.

Browser-compatible: no direct FS access — the source is injected by the host
(Tauri commands for desktop, in-memory for tests).

11 tests, 100% line / 94.4% branch coverage.



* refactor(core): tighten brick-loader validation per review

- Parse the module-provided brick.manifest with `parseManifest` and
  enforce strict equality against the source manifest (canonical JSON
  comparison). Catches divergence in deps/tools, not just name.
- Split default-export check into two messages: missing default vs
  default-not-an-object (clearer diagnostics for `default: 42` etc.).
- Move the manifest-shape check out of the Brick contract assertion so
  malformed `brick.manifest` produces an INVALID_MANIFEST error rather
  than a misleading "does not implement Brick contract".

3 new tests (divergence, malformed module manifest, default-not-object).
14 tests total, 100% line / 96.3% branch coverage on the loader.



---------



* feat(core): marketplace resolver (parse + find + semver + updates) (#5)

* feat(core): add marketplace resolver (parse + find + semver + updates)

Pure, browser-compatible module that consumes a catalog.json as
published by the FocusMCP marketplace. Does no I/O — the host injects
raw JSON and this module validates, normalizes and queries it.

Public API:
- parseCatalog(raw): Catalog — structural validation aligned with the
  published JSON Schema (kebab-case names, semver versions, typed
  source variants).
- findBrick(catalog, name): CatalogBrick | undefined
- compareSemver(a, b): -1 | 0 | 1 — minimal inline implementation
  (core + optional pre-release; no build metadata, no range matching
  yet — added at need).
- listUpdates(installed, catalog): UpdateInfo[]

20 unit tests (parseCatalog, findBrick, compareSemver incl. pre-release
ordering per semver §11, listUpdates). Full suite: 127 passing.



* refactor(core): address Copilot review on marketplace resolver

- Thread a full `loc` path into requireString/optionalString and the
  array variants so validation errors now produce
  "bricks[3].owner.email must be a string" instead of just
  "email must be a string". Much easier to diagnose.
- Tighten SEMVER regex: reject numeric pre-release identifiers with
  leading zeros (per semver 2.0 §9).
- Extend SEMVER regex to accept optional build metadata ("+..."),
  matching the manifest parser and semver 2.0 §10. Build metadata is
  captured but discarded for precedence comparisons, as required.
- parseTool: use conditional spread for `inputSchema` instead of
  emitting `inputSchema: undefined` when the field is missing.
  Consistent with how other optionals are handled in this file and
  compatible with `exactOptionalPropertyTypes`.
- Add 2 tests: build metadata is ignored when comparing; compareSemver
  rejects pre-release identifiers with leading zeros.



---------



* style: migrate indent from 2 to 4 spaces (#6)

* style: migrate indent from 2 to 4 spaces (Biome config)

Standardize indentation to 4 spaces across all projects.
Biome formatter config updated accordingly.



* fix: update .editorconfig indent_size to 4

Aligns with Biome formatter config to prevent editor/formatter conflicts.



* fix: align biome schema to 2.4.11 and format new develop files

Update $schema version to match installed Biome CLI.
Reformat brick-loader and marketplace resolver (merged from develop).



---------




* chore: configure GitHub Packages for @focusmcp/* packages (#8)

* chore: configure GitHub Packages registry for @focusmcp/* packages

Add publishConfig with npm.pkg.github.com registry and .npmrc for
scoped package resolution. Preparation for dev package publishing.



* fix: add SPDX header to .npmrc



---------




* feat: mandatory tool prefix in brick manifest (#10)

* feat: add mandatory prefix field to brick manifest

Tools are exposed as {prefix}_{toolName} to prevent collisions
between bricks and protect internal tools (no prefix).
Prefix must be unique per registry, lowercase alphanumeric.
Reserved prefixes: focus, focusmcp, mcp, internal, system.



* test: add edge case tests for prefix coverage (registry 98%+)



---------




* ci: add Claude Code Review action (#11)

* ci: add Claude Code Review action

* ci: use Claude Max OAuth instead of API key



* fix(ci): add id-token permission for OIDC auth

---------




---------




* release: v1.0.0 — sync develop → main (#29)

* ci: run CodeQL on develop branch as well (#4)

The develop ruleset requires CodeQL results before merge, but the
workflow only triggered on main. PRs targeting develop were
deadlocked. Add develop to push and pull_request triggers, mirroring
the client repo's setup.



* docs(prd): split PRD into per-repo focused docs (#2)

* docs(prd): split monolithic PRD into per-repo PRDs

Rewrite core/PRD.md to focus solely on @focusmcp/core (lib TS):
3 piliers, manifest, SDK, validator, CLI, marketplace client, brick loader.
Reflects current architecture (core in WebView, Tauri sole HTTP gateway).

Companion PRDs added in client/ and marketplace/ repos.



* docs(prd): address Copilot review feedback

- Replace cross-repo relative links with absolute GitHub URLs
- Clarify monorepo layout: package `packages/core`, not `core/`
- Manifest naming: parser only enforces kebab-case; the
  `focus-` prefix is a marketplace convention
- Validator section: list only checks actually implemented; defer
  namespace/dependency/bypass checks to P1
- Stack and Decisions tables: replace "Zod / JSON Schema" with
  "custom validator (parseManifest)"; JSON Schema is used only
  for tools[].inputSchema



* docs(prd): add SPDX headers for REUSE compliance



---------



* feat(core): add brick loader with abstract source (#3)

* feat(core): add brick loader with abstract source

`loadBricks({ source })` reads installed bricks via an abstract `BrickSource`
(list/readManifest/loadModule), validates manifests with `parseManifest`,
ensures the loaded module exports a Brick whose manifest matches the source
declaration, and collects per-brick failures without aborting the load.

Browser-compatible: no direct FS access — the source is injected by the host
(Tauri commands for desktop, in-memory for tests).

11 tests, 100% line / 94.4% branch coverage.



* refactor(core): tighten brick-loader validation per review

- Parse the module-provided brick.manifest with `parseManifest` and
  enforce strict equality against the source manifest (canonical JSON
  comparison). Catches divergence in deps/tools, not just name.
- Split default-export check into two messages: missing default vs
  default-not-an-object (clearer diagnostics for `default: 42` etc.).
- Move the manifest-shape check out of the Brick contract assertion so
  malformed `brick.manifest` produces an INVALID_MANIFEST error rather
  than a misleading "does not implement Brick contract".

3 new tests (divergence, malformed module manifest, default-not-object).
14 tests total, 100% line / 96.3% branch coverage on the loader.



---------



* feat(core): marketplace resolver (parse + find + semver + updates) (#5)

* feat(core): add marketplace resolver (parse + find + semver + updates)

Pure, browser-compatible module that consumes a catalog.json as
published by the FocusMCP marketplace. Does no I/O — the host injects
raw JSON and this module validates, normalizes and queries it.

Public API:
- parseCatalog(raw): Catalog — structural validation aligned with the
  published JSON Schema (kebab-case names, semver versions, typed
  source variants).
- findBrick(catalog, name): CatalogBrick | undefined
- compareSemver(a, b): -1 | 0 | 1 — minimal inline implementation
  (core + optional pre-release; no build metadata, no range matching
  yet — added at need).
- listUpdates(installed, catalog): UpdateInfo[]

20 unit tests (parseCatalog, findBrick, compareSemver incl. pre-release
ordering per semver §11, listUpdates). Full suite: 127 passing.



* refactor(core): address Copilot review on marketplace resolver

- Thread a full `loc` path into requireString/optionalString and the
  array variants so validation errors now produce
  "bricks[3].owner.email must be a string" instead of just
  "email must be a string". Much easier to diagnose.
- Tighten SEMVER regex: reject numeric pre-release identifiers with
  leading zeros (per semver 2.0 §9).
- Extend SEMVER regex to accept optional build metadata ("+..."),
  matching the manifest parser and semver 2.0 §10. Build metadata is
  captured but discarded for precedence comparisons, as required.
- parseTool: use conditional spread for `inputSchema` instead of
  emitting `inputSchema: undefined` when the field is missing.
  Consistent with how other optionals are handled in this file and
  compatible with `exactOptionalPropertyTypes`.
- Add 2 tests: build metadata is ignored when comparing; compareSemver
  rejects pre-release identifiers with leading zeros.



---------



* style: migrate indent from 2 to 4 spaces (#6)

* style: migrate indent from 2 to 4 spaces (Biome config)

Standardize indentation to 4 spaces across all projects.
Biome formatter config updated accordingly.



* fix: update .editorconfig indent_size to 4

Aligns with Biome formatter config to prevent editor/formatter conflicts.



* fix: align biome schema to 2.4.11 and format new develop files

Update $schema version to match installed Biome CLI.
Reformat brick-loader and marketplace resolver (merged from develop).



---------




* docs: add CLAUDE.md (agent guidance, replaces ~/.claude memory) (#7)

* docs: add CLAUDE.md capturing the post-pivot agent guidance

Replaces the former personal memory system under
~/.claude/projects/**/memory/ with an in-repo, version-controlled
file that is auto-loaded by Claude Code (and any agents.md-compatible
tool).

Covers: project overview, the 4-repo ecosystem post CLI-first pivot
(2026-04-16), the 8 non-negotiable conventions (TDD, strict scope, pro
standards, English public-facing, gitflow, npm orgs, rulesets
checklist), this repo's specifics (lib-only, packages/core, cli moved
out, browser-compatible, no HTTP transport), and the standard feature
workflow.



* docs(claude.md): fix repo count and clarify English rule exceptions

Heading: 3 repos actifs + 1 archivé (table listed 3 not 4). Rule #5
(English public-facing): reframe as from-now-on plus list explicit
exceptions (PRD.md and CLAUDE.md stay French, existing docs stay
French until substantial rewrite) so the rule no longer contradicts
the current state of the repo.



---------



* chore: configure GitHub Packages for @focusmcp/* packages (#8)

* chore: configure GitHub Packages registry for @focusmcp/* packages

Add publishConfig with npm.pkg.github.com registry and .npmrc for
scoped package resolution. Preparation for dev package publishing.



* fix: add SPDX header to .npmrc



---------




* feat: mandatory tool prefix in brick manifest (#10)

* feat: add mandatory prefix field to brick manifest

Tools are exposed as {prefix}_{toolName} to prevent collisions
between bricks and protect internal tools (no prefix).
Prefix must be unique per registry, lowercase alphanumeric.
Reserved prefixes: focus, focusmcp, mcp, internal, system.



* test: add edge case tests for prefix coverage (registry 98%+)



---------




* ci: add Claude Code Review action (#11)

* ci: add Claude Code Review action

* ci: use Claude Max OAuth instead of API key



* fix(ci): add id-token permission for OIDC auth

---------




* chore(ci): bump GitHub Actions to v5 (Node.js 24) (#14)

- actions/checkout v4 → v5
- actions/setup-node v4 → v5
- actions/upload-artifact v4 → v5
- github/codeql-action/init v3 → v4
- github/codeql-action/analyze v3 → v4




* feat: enforce bare tool names in manifest — prefix applied at runtime (#15)

Tool names in mcp-brick.json must now be bare alphanumeric (e.g. "search"
not "indexer_search"). The prefix is added by the runtime when exposing
tools via MCP (prefix_toolname).

- manifest.ts: reject tool names containing non-alphanumeric chars
- tool.ts: update JSDoc to reflect new convention
- Tests updated to use bare tool names throughout




* feat(marketplace): add catalog-store, catalog-fetcher, installer modules (#16)

* feat(marketplace): add catalog-store, catalog-fetcher, installer modules

Pure, browser-compatible marketplace management for FocusMCP core:

- catalog-store: manage catalog source URLs (CRUD, multi-marketplace)
- catalog-fetcher: fetch + aggregate catalogs from multiple sources
- installer: plan + execute brick install/remove via npm
- resolver: extend CatalogBrickSource with npm source type

All modules use dependency injection (IO interfaces) — no direct
node: imports. The CLI provides concrete implementations.

257 tests passing, 0 typecheck errors, 0 lint errors.



* fix: export marketplace modules from core package entry point

Add catalog-store, catalog-fetcher, and installer exports to index.ts
so they are part of the public API and pass knip unused-export checks.



* chore: clean up knip config — remove stale entries

Remove deprecated packages/cli workspace, redundant entry patterns
(vitest.config, playwright.config), and unused playwright binary ignore.



* refactor: extract shared validation helpers to reduce duplication

Move requireObject, requireString, optionalString, requireArray,
requireStringArray, optionalStringArray, requireBoolean into a shared
helpers.ts module. Imported by resolver, catalog-store, and installer.

Reduces jscpd duplication from 1.85% to 0.36% (threshold: 1%).



---------




* feat: update default catalog URL to raw.githubusercontent.com (#17)

Switch from gh-pages to raw GitHub content serving for the catalog.
URL: https://raw.githubusercontent.com/focus-mcp/marketplace/develop/publish/catalog.json




* ci: add GitHub Packages publish workflow for dev channel (#18)

- Add .github/workflows/publish-dev.yml: publishes @focusmcp/* to
  GitHub Packages on push to develop (skips packages with private:true)
- Set "private": false in packages/core, sdk, validator package.json
- Add direct_prompt to claude-review.yml for inline PR review guidance




* feat: rename npm scope from @focusmcp to @focus-mcp (#21)

Rename all package names, workflow scopes, .npmrc registry bindings,
and source/test file references from @focusmcp/* to @focus-mcp/*.
Email addresses unchanged.




* fix(ci): add id-token permission for npm provenance (#22)




* feat(ci): dev publish workflow (#24)

* feat(ci): add dev publish workflow with auto-versioning

- dev-publish.yml: publish @focus-mcp/{core,sdk,validator} to npmjs.org with --tag dev
- Auto-computed version: <base>-dev.<N> (N = commits since last tag)
- Mark packages/cli stub as private to prevent accidental publish



* feat: rename scope @focus-mcp@focusmcp + dev publish workflow

- Rename @focus-mcp/{core,sdk,validator} → @focusmcp/{core,sdk,validator}
- Add dev-publish.yml for auto-versioned dev releases
- Mark packages/cli stub as private
- Update all docs, workflows, configs



* chore: regenerate pnpm-lock.yaml after scope rename



---------




* fix(ci): use GitHub Packages for dev publish (#25)

* fix(ci): use GitHub Packages registry instead of npmjs.org

- registry-url → npm.pkg.github.com
- NODE_AUTH_TOKEN → GITHUB_TOKEN (no separate secret needed)
- Add packages: write permission



* fix: rename scope to @focus-mcp + npmjs.org for dev publish

- All packages: @focus-mcp/{core,sdk,validator}
- dev-publish.yml: registry.npmjs.org + NPM_TOKEN
- Regenerated lockfile



* chore: regenerate lockfile



* docs: update scope references to @focus-mcp in docs



---------




* fix(ci): add id-token:write permission + remove duplicate workflows (#26)

- Add id-token:write to dev-publish.yml (required for npm provenance)
- Remove publish-dev.yml (duplicate, was targeting GitHub Packages)




* chore(release): v1.0.0 + stable-publish workflow (#27)

* chore(release): bump @focus-mcp/{core,sdk,validator} to 1.0.0

- Bump all 3 public packages from 0.0.0 to 1.0.0
- Add stable-publish.yml workflow (triggers on push to main → npm @latest)



* chore(ci): remove release.yml — stable-publish.yml handles releases



* chore(ci): remove claude-review.yml (fails on workflow changes)



* revert: restore claude-review.yml — will work after main merge



---------




---------




* release: sync develop → main (workflow fix + v1 cleanup) (#33)

* ci: run CodeQL on develop branch as well (#4)

The develop ruleset requires CodeQL results before merge, but the
workflow only triggered on main. PRs targeting develop were
deadlocked. Add develop to push and pull_request triggers, mirroring
the client repo's setup.



* docs(prd): split PRD into per-repo focused docs (#2)

* docs(prd): split monolithic PRD into per-repo PRDs

Rewrite core/PRD.md to focus solely on @focusmcp/core (lib TS):
3 piliers, manifest, SDK, validator, CLI, marketplace client, brick loader.
Reflects current architecture (core in WebView, Tauri sole HTTP gateway).

Companion PRDs added in client/ and marketplace/ repos.



* docs(prd): address Copilot review feedback

- Replace cross-repo relative links with absolute GitHub URLs
- Clarify monorepo layout: package `packages/core`, not `core/`
- Manifest naming: parser only enforces kebab-case; the
  `focus-` prefix is a marketplace convention
- Validator section: list only checks actually implemented; defer
  namespace/dependency/bypass checks to P1
- Stack and Decisions tables: replace "Zod / JSON Schema" with
  "custom validator (parseManifest)"; JSON Schema is used only
  for tools[].inputSchema



* docs(prd): add SPDX headers for REUSE compliance



---------



* feat(core): add brick loader with abstract source (#3)

* feat(core): add brick loader with abstract source

`loadBricks({ source })` reads installed bricks via an abstract `BrickSource`
(list/readManifest/loadModule), validates manifests with `parseManifest`,
ensures the loaded module exports a Brick whose manifest matches the source
declaration, and collects per-brick failures without aborting the load.

Browser-compatible: no direct FS access — the source is injected by the host
(Tauri commands for desktop, in-memory for tests).

11 tests, 100% line / 94.4% branch coverage.



* refactor(core): tighten brick-loader validation per review

- Parse the module-provided brick.manifest with `parseManifest` and
  enforce strict equality against the source manifest (canonical JSON
  comparison). Catches divergence in deps/tools, not just name.
- Split default-export check into two messages: missing default vs
  default-not-an-object (clearer diagnostics for `default: 42` etc.).
- Move the manifest-shape check out of the Brick contract assertion so
  malformed `brick.manifest` produces an INVALID_MANIFEST error rather
  than a misleading "does not implement Brick contract".

3 new tests (divergence, malformed module manifest, default-not-object).
14 tests total, 100% line / 96.3% branch coverage on the loader.



---------



* feat(core): marketplace resolver (parse + find + semver + updates) (#5)

* feat(core): add marketplace resolver (parse + find + semver + updates)

Pure, browser-compatible module that consumes a catalog.json as
published by the FocusMCP marketplace. Does no I/O — the host injects
raw JSON and this module validates, normalizes and queries it.

Public API:
- parseCatalog(raw): Catalog — structural validation aligned with the
  published JSON Schema (kebab-case names, semver versions, typed
  source variants).
- findBrick(catalog, name): CatalogBrick | undefined
- compareSemver(a, b): -1 | 0 | 1 — minimal inline implementation
  (core + optional pre-release; no build metadata, no range matching
  yet — added at need).
- listUpdates(installed, catalog): UpdateInfo[]

20 unit tests (parseCatalog, findBrick, compareSemver incl. pre-release
ordering per semver §11, listUpdates). Full suite: 127 passing.



* refactor(core): address Copilot review on marketplace resolver

- Thread a full `loc` path into requireString/optionalString and the
  array variants so validation errors now produce
  "bricks[3].owner.email must be a string" instead of just
  "email must be a string". Much easier to diagnose.
- Tighten SEMVER regex: reject numeric pre-release identifiers with
  leading zeros (per semver 2.0 §9).
- Extend SEMVER regex to accept optional build metadata ("+..."),
  matching the manifest parser and semver 2.0 §10. Build metadata is
  captured but discarded for precedence comparisons, as required.
- parseTool: use conditional spread for `inputSchema` instead of
  emitting `inputSchema: undefined` when the field is missing.
  Consistent with how other optionals are handled in this file and
  compatible with `exactOptionalPropertyTypes`.
- Add 2 tests: build metadata is ignored when comparing; compareSemver
  rejects pre-release identifiers with leading zeros.



---------



* style: migrate indent from 2 to 4 spaces (#6)

* style: migrate indent from 2 to 4 spaces (Biome config)

Standardize indentation to 4 spaces across all projects.
Biome formatter config updated accordingly.



* fix: update .editorconfig indent_size to 4

Aligns with Biome formatter config to prevent editor/formatter conflicts.



* fix: align biome schema to 2.4.11 and format new develop files

Update $schema version to match installed Biome CLI.
Reformat brick-loader and marketplace resolver (merged from develop).



---------




* docs: add CLAUDE.md (agent guidance, replaces ~/.claude memory) (#7)

* docs: add CLAUDE.md capturing the post-pivot agent guidance

Replaces the former personal memory system under
~/.claude/projects/**/memory/ with an in-repo, version-controlled
file that is auto-loaded by Claude Code (and any agents.md-compatible
tool).

Covers: project overview, the 4-repo ecosystem post CLI-first pivot
(2026-04-16), the 8 non-negotiable conventions (TDD, strict scope, pro
standards, English public-facing, gitflow, npm orgs, rulesets
checklist), this repo's specifics (lib-only, packages/core, cli moved
out, browser-compatible, no HTTP transport), and the standard feature
workflow.



* docs(claude.md): fix repo count and clarify English rule exceptions

Heading: 3 repos actifs + 1 archivé (table listed 3 not 4). Rule #5
(English public-facing): reframe as from-now-on plus list explicit
exceptions (PRD.md and CLAUDE.md stay French, existing docs stay
French until substantial rewrite) so the rule no longer contradicts
the current state of the repo.



---------



* chore: configure GitHub Packages for @focusmcp/* packages (#8)

* chore: configure GitHub Packages registry for @focusmcp/* packages

Add publishConfig with npm.pkg.github.com registry and .npmrc for
scoped package resolution. Preparation for dev package publishing.



* fix: add SPDX header to .npmrc



---------




* feat: mandatory tool prefix in brick manifest (#10)

* feat: add mandatory prefix field to brick manifest

Tools are exposed as {prefix}_{toolName} to prevent collisions
between bricks and protect internal tools (no prefix).
Prefix must be unique per registry, lowercase alphanumeric.
Reserved prefixes: focus, focusmcp, mcp, internal, system.



* test: add edge case tests for prefix coverage (registry 98%+)



---------




* ci: add Claude Code Review action (#11)

* ci: add Claude Code Review action

* ci: use Claude Max OAuth instead of API key



* fix(ci): add id-token permission for OIDC auth

---------




* chore(ci): bump GitHub Actions to v5 (Node.js 24) (#14)

- actions/checkout v4 → v5
- actions/setup-node v4 → v5
- actions/upload-artifact v4 → v5
- github/codeql-action/init v3 → v4
- github/codeql-action/analyze v3 → v4




* feat: enforce bare tool names in manifest — prefix applied at runtime (#15)

Tool names in mcp-brick.json must now be bare alphanumeric (e.g. "search"
not "indexer_search"). The prefix is added by the runtime when exposing
tools via MCP (prefix_toolname).

- manifest.ts: reject tool names containing non-alphanumeric chars
- tool.ts: update JSDoc to reflect new convention
- Tests updated to use bare tool names throughout




* feat(marketplace): add catalog-store, catalog-fetcher, installer modules (#16)

* feat(marketplace): add catalog-store, catalog-fetcher, installer modules

Pure, browser-compatible marketplace management for FocusMCP core:

- catalog-store: manage catalog source URLs (CRUD, multi-marketplace)
- catalog-fetcher: fetch + aggregate catalogs from multiple sources
- installer: plan + execute brick install/remove via npm
- resolver: extend CatalogBrickSource with npm source type

All modules use dependency injection (IO interfaces) — no direct
node: imports. The CLI provides concrete implementations.

257 tests passing, 0 typecheck errors, 0 lint errors.



* fix: export marketplace modules from core package entry point

Add catalog-store, catalog-fetcher, and installer exports to index.ts
so they are part of the public API and pass knip unused-export checks.



* chore: clean up knip config — remove stale entries

Remove deprecated packages/cli workspace, redundant entry patterns
(vitest.config, playwright.config), and unused playwright binary ignore.



* refactor: extract shared validation helpers to reduce duplication

Move requireObject, requireString, optionalString, requireArray,
requireStringArray, optionalStringArray, requireBoolean into a shared
helpers.ts module. Imported by resolver, catalog-store, and installer.

Reduces jscpd duplication from 1.85% to 0.36% (threshold: 1%).



---------




* feat: update default catalog URL to raw.githubusercontent.com (#17)

Switch from gh-pages to raw GitHub content serving for the catalog.
URL: https://raw.githubusercontent.com/focus-mcp/marketplace/develop/publish/catalog.json




* ci: add GitHub Packages publish workflow for dev channel (#18)

- Add .github/workflows/publish-dev.yml: publishes @focusmcp/* to
  GitHub Packages on push to develop (skips packages with private:true)
- Set "private": false in packages/core, sdk, validator package.json
- Add direct_prompt to claude-review.yml for inline PR review guidance




* feat: rename npm scope from @focusmcp to @focus-mcp (#21)

Rename all package names, workflow scopes, .npmrc registry bindings,
and source/test file references from @focusmcp/* to @focus-mcp/*.
Email addresses unchanged.




* fix(ci): add id-token permission for npm provenance (#22)




* feat(ci): dev publish workflow (#24)

* feat(ci): add dev publish workflow with auto-versioning

- dev-publish.yml: publish @focus-mcp/{core,sdk,validator} to npmjs.org with --tag dev
- Auto-computed version: <base>-dev.<N> (N = commits since last tag)
- Mark packages/cli stub as private to prevent accidental publish



* feat: rename scope @focus-mcp@focusmcp + dev publish workflow

- Rename @focus-mcp/{core,sdk,validator} → @focusmcp/{core,sdk,validator}
- Add dev-publish.yml for auto-versioned dev releases
- Mark packages/cli stub as private
- Update all docs, workflows, configs



* chore: regenerate pnpm-lock.yaml after scope rename



---------




* fix(ci): use GitHub Packages for dev publish (#25)

* fix(ci): use GitHub Packages registry instead of npmjs.org

- registry-url → npm.pkg.github.com
- NODE_AUTH_TOKEN → GITHUB_TOKEN (no separate secret needed)
- Add packages: write permission



* fix: rename scope to @focus-mcp + npmjs.org for dev publish

- All packages: @focus-mcp/{core,sdk,validator}
- dev-publish.yml: registry.npmjs.org + NPM_TOKEN
- Regenerated lockfile



* chore: regenerate lockfile



* docs: update scope references to @focus-mcp in docs



---------




* fix(ci): add id-token:write permission + remove duplicate workflows (#26)

- Add id-token:write to dev-publish.yml (required for npm provenance)
- Remove publish-dev.yml (duplicate, was targeting GitHub Packages)




* chore(release): v1.0.0 + stable-publish workflow (#27)

* chore(release): bump @focus-mcp/{core,sdk,validator} to 1.0.0

- Bump all 3 public packages from 0.0.0 to 1.0.0
- Add stable-publish.yml workflow (triggers on push to main → npm @latest)



* chore(ci): remove release.yml — stable-publish.yml handles releases



* chore(ci): remove claude-review.yml (fails on workflow changes)



* revert: restore claude-review.yml — will work after main merge



---------




* docs: v1 cleanup — README, VISION, ARCHITECTURE (#30)

* docs: v1 cleanup — rewrite README, add VISION.md and ARCHITECTURE.md

- README.md: English public-facing with install, quick start, architecture
- VISION.md: short "why" doc (1 page)
- ARCHITECTURE.md: technical doc for contributors
- AGENTS.md, CONTRIBUTING.md, ROADMAP.md: updated for v1.0.0 state
- Archived pre-v1 PRD (internal French planning doc)
- npm metadata (description, ke…

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: claude <claude@localhost>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
@samuelds samuelds enabled auto-merge (squash) April 30, 2026 19:39
@samuelds samuelds merged commit 800509a into main Apr 30, 2026
39 checks passed
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.

1 participant