Skip to content

feat: close the three publish-surface gaps between Pi and MCP (#162, #146, #147) - #179

Merged
TheAmericanMaker merged 1 commit into
mainfrom
feat/publish-parity
Sep 7, 2026
Merged

feat: close the three publish-surface gaps between Pi and MCP (#162, #146, #147)#179
TheAmericanMaker merged 1 commit into
mainfrom
feat/publish-parity

Conversation

@TheAmericanMaker

Copy link
Copy Markdown
Member

Three publish-surface gaps between Pi and MCP, closed together because #147's upgrade path is exactly the case #146's confirm exists for.

#162 — MCP honors library.publish_confirm

handlePublish never read the key; only codecarto_config displayed it. MCP cannot ask a human, so it now does what codecarto_broadside does for its spend limit: when the key is set and the call lacks confirm: true, it throws McpError(InvalidRequest) whose text is the preview Pi would have shown (library, entry, new version vs metadata-only update, source_repo, headline, confidentiality, spec source) and says to re-invoke with confirm: true; the same fields ride along as error.data. Nothing is written by the refusal.

  • The version outcome is computed read-only by the new previewPublishVersion, which publishEntry itself now calls — so the preview and the real publish cannot drift.
  • The gate applies only when a config layer actually set the key (new library.publish_confirm_configured on the loaded config). The loader defaults publish_confirm to true; honoring that default would have gated every MCP host that never touched the key. codecarto_library_init writes the key explicitly, so libraries initialized through the tooling get the gate. Hosts that never configured it see no change.
  • The gate runs after all argument validation (so the preview names the resolved slug/namespace) and reads the same layered config codecarto_config reports for the same cwd.
  • confirm (boolean) added to the codecarto_publish inputSchema.

#146 — Pi: the source-repo collision guard gets an escape hatch

The guard now throws a typed SourceRepoMismatchError { recorded, incoming } (message unchanged), mirroring ConfidentialityMismatchError. /codecarto-publish catches it and asks a second confirm showing both values — "did the repository move?" Yes retries with allowSourceRepoChange: true; no notifies "Publish cancelled. Nothing was written." The publish loop accumulates overrides, so a publish that trips both guards is asked both questions in turn.

#147 — Pi records the git remote as source_repo

New resolvePublishSourceRepo(cwd) in core (next to commitPublish, reusing runGit): origin's fetch URL, else the fetch URL of the remote the current branch tracks, else cwd. Stored verbatim; normalization stays at comparison time (#127). Slug derives from the same value; deriveSlug now also handles the slash-less git@host:name form so every spelling of a remote lands on the slug the directory did. The preview prints Source: <value> (git remote origin).

  • The remote is consulted only when cwd is the root of its work tree (realpath-compared). A monorepo subdirectory keeps recording its path — otherwise every subdirectory would resolve to one URL and one slug and the second published would silently land as v2 of the first, with no guard able to tell.
  • Upgrade note: entries an earlier Pi published hold the local path. The first publish after upgrading carries a URL against a recorded path, so the collision guard fires — and the Pi: the source_repo collision guard has no escape hatch on the primary surface #146 confirm is the remedy. Documented in the CHANGELOG bullet and in docs/library-format.md (source_repo row and "Source repo conflicts").

Tests

535 passing (516 before; 19 added): previewPublishVersion, SourceRepoMismatchError fields, deriveSlug remote/directory agreement, the resolver against real temporary git repos (origin, upstream-only, no remote, . remote, non-git, subdirectory, symlinked root), the MCP gate (refusal + preview + nothing written, confirm: true, unset/false unchanged, workspace-over-user layering, === true only, gate runs after validation), and a new tests/pi-publish.test.mjs harness (records the remote and derives the slug from it, non-git path fallback, the upgrade collision answered yes/no, preview decline).

tests/mcp-library.test.mjs and tests/publish-path-containment.test.mjs now pin CODECARTO_USER_CONFIG_PATH to a nonexistent file: with the gate live, a developer's real ~/.codecarto/config.yaml (publish_confirm: true, as library-init writes) would otherwise refuse every existing handlePublish test locally while CI stayed green.

Closes #162
Closes #146
Closes #147

🤖 Generated with Claude Code

human, so when the key is set in config and the call lacks confirm: true
it refuses with an InvalidRequest whose text previews the publish
(library, entry, new version vs metadata-only update via the new
read-only previewPublishVersion that publishEntry now also uses,
source_repo, headline, confidentiality) and says to re-invoke with
confirm: true. Nothing is written. The gate applies only when a config
layer actually set the key (library.publish_confirm_configured), since
the loader's default of true exists for Pi's dialog; hosts that never
configured it see no change.

carrying recorded and incoming (message unchanged), mirroring
ConfidentialityMismatchError, and /codecarto-publish catches it to ask
whether the repository moved — yes retries with allowSourceRepoChange,
no writes nothing. Overrides accumulate so a publish tripping both
guards is asked both questions.

the new resolvePublishSourceRepo (origin's fetch URL, else the remote
the current branch tracks, else the directory; only at the work tree
root so monorepo subdirectories keep distinct slugs). Stored verbatim,
slug derived from the same value, and deriveSlug handles the slash-less
git@host:name form. Entries an earlier Pi published hold a path, so the
first publish after upgrading trips the guard — #146's question is the
remedy, and the docs say so.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@TheAmericanMaker
TheAmericanMaker merged commit 9a0fb17 into main Sep 7, 2026
5 checks passed
@TheAmericanMaker
TheAmericanMaker deleted the feat/publish-parity branch September 7, 2026 15:04
@TheAmericanMaker TheAmericanMaker mentioned this pull request Sep 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant