Skip to content

chore: sync develop → main#9

Merged
samuelds merged 7 commits into
mainfrom
develop
Apr 20, 2026
Merged

chore: sync develop → main#9
samuelds merged 7 commits into
mainfrom
develop

Conversation

@samuelds
Copy link
Copy Markdown
Contributor

Sync develop to main for CLI CI compatibility.

🤖 Generated with Claude Code

samuelds and others added 7 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>
Copilot AI review requested due to automatic review settings April 20, 2026 13:49
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Syncs develop into main to keep the codebase aligned with current CLI/CI expectations, including repository-wide formatting normalization plus new @focusmcp/core capabilities (marketplace catalog resolver + brick loader) exported from the public entrypoint.

Changes:

  • Standardize formatting across the repo (4-space indentation via EditorConfig/Biome) and reformat TS/JSON/config files accordingly.
  • Add @focusmcp/core marketplace catalog parsing/query utilities (semver compare, update listing) with tests and public exports.
  • Add @focusmcp/core brick loader abstraction (BrickSource + loadBricks) with tests and public exports; update package publishing metadata and CI config.

Reviewed changes

Copilot reviewed 23 out of 61 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tsconfig.json Re-indented root TS config (format normalization).
packages/validator/tsconfig.json Re-indented package TS config.
packages/validator/src/validate-brick.ts Formatting-only changes (indent normalization).
packages/validator/src/validate-brick.test.ts Formatting-only changes (indent normalization).
packages/validator/src/index.ts Formatting-only changes (indent normalization).
packages/validator/package.json Adds publishing metadata + reformats JSON.
packages/sdk/tsconfig.json Re-indented package TS config.
packages/sdk/src/index.ts Formatting-only changes (indent normalization).
packages/sdk/src/define-brick.ts Formatting-only changes (indent normalization).
packages/sdk/src/define-brick.test.ts Formatting-only changes (indent normalization).
packages/sdk/package.json Adds publishing metadata + reformats JSON.
packages/core/tsconfig.json Re-indented package TS config.
packages/core/src/types/tool.ts Formatting-only changes (indent normalization).
packages/core/src/types/router.ts Formatting-only changes (indent normalization).
packages/core/src/types/registry.ts Formatting-only changes (indent normalization).
packages/core/src/types/manifest.ts Formatting-only changes (indent normalization).
packages/core/src/types/index.ts Formatting-only changes (indent normalization).
packages/core/src/types/event-bus.ts Formatting-only changes (indent normalization).
packages/core/src/types/brick.ts Formatting-only changes (indent normalization).
packages/core/src/router/router.ts Formatting-only changes (indent normalization).
packages/core/src/router/router.test.ts Formatting-only changes (indent normalization).
packages/core/src/registry/registry.ts Formatting-only changes (indent normalization).
packages/core/src/registry/registry.test.ts Formatting-only changes (indent normalization).
packages/core/src/registry/permission-provider.ts Formatting-only changes (indent normalization).
packages/core/src/registry/permission-provider.test.ts Formatting-only changes (indent normalization).
packages/core/src/observability/tracing.ts Formatting-only changes (indent normalization).
packages/core/src/observability/logger.ts Formatting-only changes (indent normalization).
packages/core/src/observability/async-storage.ts Formatting-only changes (indent normalization).
packages/core/src/marketplace/resolver.ts New marketplace catalog resolver (parse/query/semver compare).
packages/core/src/marketplace/resolver.test.ts Tests for marketplace catalog resolver.
packages/core/src/manifest/manifest.ts Formatting-only changes (indent normalization).
packages/core/src/manifest/manifest.test.ts Formatting-only changes (indent normalization).
packages/core/src/loader/brick-loader.ts New brick loader (BrickSource + loadBricks) and manifest integrity check.
packages/core/src/loader/brick-loader.test.ts Tests for brick loader.
packages/core/src/index.ts Exports new loader + marketplace resolver APIs from @focusmcp/core.
packages/core/src/event-bus/event-bus.ts Formatting-only changes (indent normalization).
packages/core/src/event-bus/event-bus.test.ts Formatting-only changes (indent normalization).
packages/core/src/bootstrap/create-focus-mcp.ts Formatting-only changes (indent normalization).
packages/core/src/bootstrap/create-focus-mcp.test.ts Formatting-only changes (indent normalization).
packages/core/package.json Adds publishing metadata (registry/repository) + reformats JSON.
packages/cli/tsup.config.ts Formatting-only changes (indent normalization).
packages/cli/tsconfig.json Re-indented package TS config.
packages/cli/package.json Adds publishing metadata + reformats JSON.
package.json Re-indented root package metadata/scripts/config.
config/vitest.config.ts Formatting-only changes (indent normalization).
config/tsup.preset.ts Formatting-only changes (indent normalization).
config/tsconfig.base.json Formatting-only changes (indent normalization).
config/stryker.config.json Formatting-only changes (indent normalization).
config/playwright.config.ts Formatting-only changes (indent normalization).
config/lint-staged.config.js Formatting-only changes (indent normalization).
config/knip.json Formatting-only changes (indent normalization).
config/jscpd.json Formatting-only changes (indent normalization).
config/commitlint.config.js Formatting-only changes (indent normalization).
biome.json Updates Biome schema + switches formatter indentation defaults to 4 spaces.
CLAUDE.md Adds repo agent guidance file (project/process documentation).
.npmrc Adds GitHub Packages registry configuration for @focusmcp scope.
.github/workflows/codeql.yml Runs CodeQL on develop as well as main.
.github/renovate.json Formatting-only changes (indent normalization).
.editorconfig Switches default indentation to 4 spaces.
.changeset/config.json Formatting-only changes (indent normalization).

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

Comment thread packages/core/package.json
Comment thread packages/core/src/loader/brick-loader.ts
Comment thread packages/core/src/marketplace/resolver.ts
@samuelds samuelds closed this Apr 20, 2026
@samuelds samuelds reopened this Apr 20, 2026
@samuelds samuelds merged commit 2fa3860 into main Apr 20, 2026
38 checks passed
samuelds added a commit that referenced this pull request Apr 24, 2026
* 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>
samuelds added a commit that referenced this pull request Apr 24, 2026
* 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>

* 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".

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

* 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.

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) o…
samuelds added a commit that referenced this pull request Apr 28, 2026
…49)

* 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>

* 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".

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

* 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.

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 (de…
samuelds added a commit that referenced this pull request Apr 29, 2026
* 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>

* 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".

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

* 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.

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, ke…
samuelds added a commit that referenced this pull request Apr 29, 2026
* 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>

* 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".

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

* 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.

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, ke…
samuelds added a commit that referenced this pull request Apr 29, 2026
* 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>

* 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".

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

* 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.

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, ke…
samuelds added a commit that referenced this pull request Apr 29, 2026
* 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>

* 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".

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

* 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.

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, ke…
samuelds added a commit that referenced this pull request Apr 29, 2026
* 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…
samuelds added a commit that referenced this pull request Apr 29, 2026
* 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 …
samuelds added a commit that referenced this pull request Apr 30, 2026
* 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>

* 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".

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

* 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.

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, ke…
samuelds added a commit that referenced this pull request Apr 30, 2026
* 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 added a commit that referenced this pull request Apr 30, 2026
* 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>

* 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".

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

* 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.

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, ke…
samuelds added a commit that referenced this pull request Apr 30, 2026
* 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>

* 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".

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

* 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.

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, key…
samuelds added a commit that referenced this pull request Apr 30, 2026
* 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>

* 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".

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

* 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.

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 (descripti…
samuelds added a commit that referenced this pull request Apr 30, 2026
…92)

* 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>

* 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".

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

* 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.

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 (de…
samuelds added a commit that referenced this pull request May 1, 2026
…stic guard) (#96)

* 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>

* 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".

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

* 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.

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 …
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.

2 participants