codecartographer-pi 0.17.1 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -17,7 +17,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
17
17
  import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from "@modelcontextprotocol/sdk/types.js";
18
18
  import { mkdir, readFile, readdir, rename, writeFile } from "node:fs/promises";
19
19
  import { basename, isAbsolute, join } from "node:path";
20
- import { buildPhasePrompt, buildSkillPrompt, buildValidationSummary, BROADSIDE_DIR, broadsideDirFor, BROADSIDE_LENS_IDS, BROADSIDE_SKILL_NAME, canonicalPath, copyPackagedWorkspace, collectResultText, completeValidatedPhase, computePerPhaseTotals, computeTotals, createEmptyStatus, DEFAULT_PIPELINE_PATH, deriveSlug, discoverLibrary, describeScaffoldStaleness, detectProvenanceConflicts, estimateSubmitText, getLens, getNextEligiblePhase, getPipelineLabel, getWorkspaceState, isValidSlug, isWithinPathResolved, listBatchModels, listEntries, listGuideTopics, loadBroadsideConfig, modelsText, readGuide, listSkillNames, loadCodecartoConfig, loadUsage, loadYamlFile, normalizeForComparison, PACKAGE_VERSION, packagedWorkspaceDir, pathExists, PhasePreflightError, publishEntry, reindex as libraryReindex, refreshScaffold, resolvePhase, resolvePipelineChoice, readBroadsideSkill, runBroadsideCollect, runBroadsideStatus, runBroadsideSubmit, seedOrchestratorFiles, statusText, stringifySimpleYaml, switchPipeline, validatePhaseOutput, writeLibraryConfig, } from "../core/index.js";
20
+ import { buildPhasePrompt, buildSkillPrompt, buildValidationSummary, BROADSIDE_DIR, broadsideDirFor, BROADSIDE_LENS_IDS, BROADSIDE_SKILL_NAME, canonicalPath, copyPackagedWorkspace, collectResultText, completeValidatedPhase, computePerPhaseTotals, computeTotals, createEmptyStatus, DEFAULT_PIPELINE_PATH, deriveSlug, discoverLibrary, describeScaffoldStaleness, detectProvenanceConflicts, estimateSubmitText, getLens, getNextEligiblePhase, getPipelineLabel, getWorkspaceState, isValidSlug, isWithinPathResolved, listBatchModels, listEntries, listGuideTopics, loadBroadsideConfig, modelsText, readGuide, listSkillNames, loadCodecartoConfig, loadUsage, loadYamlFile, normalizeForComparison, PACKAGE_VERSION, packagedWorkspaceDir, pathExists, PhasePreflightError, previewPublishVersion, publishEntry, reindex as libraryReindex, refreshScaffold, resolvePhase, resolvePipelineChoice, readBroadsideSkill, runBroadsideCollect, runBroadsideStatus, runBroadsideSubmit, seedOrchestratorFiles, statusText, stringifySimpleYaml, switchPipeline, validatePhaseOutput, writeLibraryConfig, } from "../core/index.js";
21
21
  import { applyAmendment } from "../core/amendment.js";
22
22
  import { appendUsageRun } from "../core/usage.js";
23
23
  import { initLibrary } from "../core/library.js";
@@ -370,6 +370,17 @@ async function resolveLibraryPath(args) {
370
370
  }
371
371
  throw new McpError(ErrorCode.InvalidParams, "library_path is required (pass it explicitly, or pass cwd and configure library.path in ~/.codecarto/config.yaml or .codecarto/workflow/config.yaml).");
372
372
  }
373
+ /**
374
+ * The configuration `codecarto_config` reports for the same `cwd`: workspace
375
+ * over user-global when a cwd is given, user-global alone otherwise. Publish
376
+ * reads its gate from here so the value the config tool displays is the value
377
+ * the publish tool enforces.
378
+ */
379
+ async function loadEffectiveConfig(cwd) {
380
+ return typeof cwd === "string" && cwd.trim() !== ""
381
+ ? loadCodecartoConfig(join(cwd.trim(), ".codecarto"))
382
+ : loadUserConfig();
383
+ }
373
384
  function asStringArray(value, fieldName) {
374
385
  if (!Array.isArray(value)) {
375
386
  throw new McpError(ErrorCode.InvalidParams, `${fieldName} must be an array of strings`);
@@ -550,6 +561,50 @@ export async function handlePublish(args) {
550
561
  const analyzedAt = typeof args.analyzed_at === "string" && args.analyzed_at !== ""
551
562
  ? args.analyzed_at
552
563
  : new Date().toISOString();
564
+ const headline = args.headline.trim();
565
+ const forceNewVersion = args.force_new_version === true;
566
+ // publish_confirm gate (#162). Pi shows a preview and asks before writing.
567
+ // An MCP server has no one to ask, so — as codecarto_broadside does for its
568
+ // spend limit — it refuses with the preview and lets the caller re-invoke
569
+ // with confirm: true. The gate applies only when a config layer actually
570
+ // set the key: the loader's default (true) exists to make Pi's dialog
571
+ // opt-out, and a host that never configured the key keeps the behavior it
572
+ // had. Runs after every argument check so the preview names the resolved
573
+ // slug and namespace, and before publishEntry so nothing is written.
574
+ const config = await loadEffectiveConfig(args.cwd);
575
+ if (config.library.publish_confirm && config.library.publish_confirm_configured && args.confirm !== true) {
576
+ const preview = await previewPublishVersion(libraryPath, spec, { slug, namespace }, { forceNewVersion });
577
+ const label = `${namespace ? `${namespace}/` : ""}${slug}`;
578
+ const versionLine = preview.latestVersion === 0
579
+ ? "v1 (first version of a new entry)"
580
+ : preview.isNewVersion
581
+ ? `v${preview.version} (new content version; the newest is v${preview.latestVersion})`
582
+ : `v${preview.version} (metadata-only update; the content hash matches the newest version)`;
583
+ const specSource = typeof args.spec_path === "string" && args.spec_path.trim() !== ""
584
+ ? args.spec_path.trim()
585
+ : `inline (${spec.length} characters)`;
586
+ throw new McpError(ErrorCode.InvalidRequest, [
587
+ "Publish not performed: library.publish_confirm is set and this call did not carry confirm: true. Nothing was written.",
588
+ `Would publish ${label} to ${libraryPath}`,
589
+ ` Version: ${versionLine}`,
590
+ ` Source repo: ${sourceRepo}`,
591
+ ` Headline: ${headline}`,
592
+ ` Confidentiality: ${confidentiality ?? "internal (the default; none declared)"}`,
593
+ ` Spec: ${specSource}`,
594
+ "Re-invoke codecarto_publish with the same arguments plus confirm: true to publish, or set library.publish_confirm: false in config to drop this gate.",
595
+ ].join("\n"), {
596
+ refused: "publish_confirm",
597
+ libraryPath,
598
+ namespace: namespace ?? null,
599
+ slug,
600
+ version: preview.version,
601
+ isNewVersion: preview.isNewVersion,
602
+ latestVersion: preview.latestVersion,
603
+ source_repo: sourceRepo,
604
+ headline,
605
+ confidentiality: confidentiality ?? null,
606
+ });
607
+ }
553
608
  const result = await publishEntry(libraryPath, spec, {
554
609
  slug,
555
610
  namespace: namespace ?? undefined,
@@ -560,13 +615,13 @@ export async function handlePublish(args) {
560
615
  analyzed_at: analyzedAt,
561
616
  pipeline: defaults.pipeline,
562
617
  codecarto_version: PACKAGE_VERSION,
563
- headline: args.headline.trim(),
618
+ headline,
564
619
  tags,
565
620
  capabilities,
566
621
  confidentiality,
567
622
  generation,
568
623
  }, {
569
- forceNewVersion: args.force_new_version === true,
624
+ forceNewVersion,
570
625
  allowSourceRepoChange: args.allow_source_repo_change === true,
571
626
  allowConfidentialityMismatch: args.allow_confidentiality_mismatch === true,
572
627
  });
@@ -1060,7 +1115,7 @@ const TOOLS = [
1060
1115
  },
1061
1116
  {
1062
1117
  name: "codecarto_publish",
1063
- description: "Publish a reimplementation-spec to a CodeCartographer library. Identified by library_path (absolute) or cwd's config.yaml. Content-hash idempotent — re-publishing identical spec bytes updates metadata in place rather than bumping the version. Required: source_repo, headline, and either spec (inline) or spec_path (absolute file). Slug derives from source_repo if not provided. If the library is namespaced, namespace is required (or pass cwd to inherit from config). Generation context (agent, model, vendor, reasoning) is passed via model_metadata so the host can record provenance; omitted fields default to 'unknown'.",
1118
+ description: "Publish a reimplementation-spec to a CodeCartographer library. Identified by library_path (absolute) or cwd's config.yaml. Content-hash idempotent — re-publishing identical spec bytes updates metadata in place rather than bumping the version. Required: source_repo, headline, and either spec (inline) or spec_path (absolute file). Slug derives from source_repo if not provided. If the library is namespaced, namespace is required (or pass cwd to inherit from config). Generation context (agent, model, vendor, reasoning) is passed via model_metadata so the host can record provenance; omitted fields default to 'unknown'. When library.publish_confirm is set in config, a call without confirm: true is refused with a preview of what would be published (nothing is written); re-invoke with confirm: true to publish.",
1064
1119
  inputSchema: {
1065
1120
  type: "object",
1066
1121
  properties: {
@@ -1104,6 +1159,10 @@ const TOOLS = [
1104
1159
  type: "boolean",
1105
1160
  description: "Permit publishing when the entry's confidentiality is more restricted than the library's visibility (internal < shared < public: an internal entry into a shared or public library, a shared entry into a public one). Off by default, because that direction exposes the spec to everyone the library reaches. An omitted confidentiality counts as internal. Set only when the exposure is intended; the recorded confidentiality is not changed.",
1106
1161
  },
1162
+ confirm: {
1163
+ type: "boolean",
1164
+ description: "Acknowledge the publish_confirm gate. When library.publish_confirm is set in config (user-global ~/.codecarto/config.yaml, or the workspace's .codecarto/workflow/config.yaml when cwd is passed), a call without confirm: true is refused and the refusal previews what would be published — library, entry, whether a new version or a metadata-only update, source_repo, headline, confidentiality — so the caller can show it before committing to the write. Pass true to publish. No effect when publish_confirm is unset or false.",
1165
+ },
1107
1166
  },
1108
1167
  required: ["source_repo", "headline"],
1109
1168
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codecartographer-pi",
3
- "version": "0.17.1",
3
+ "version": "0.18.0",
4
4
  "mcpName": "io.github.HuginnIndustries/codecartographer",
5
5
  "description": "Turn an unfamiliar codebase into a validated reimplementation spec, then synthesize confirmed specs and a product vision into a traceable plan.",
6
6
  "type": "module",