@contractspec/bundle.workspace 4.1.3 → 4.2.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.
package/dist/index.js CHANGED
@@ -9763,7 +9763,7 @@ function generateWorkflowSpec(data) {
9763
9763
  ${transition.condition ? ` condition: '${escapeString4(transition.condition)}',` : ""}
9764
9764
  }`).join(`,
9765
9765
  `);
9766
- return `import type { WorkflowSpec } from '@contractspec/lib.contracts-spec/workflow';
9766
+ return `import type { WorkflowSpec } from '@contractspec/lib.contracts-spec/workflow/spec';
9767
9767
 
9768
9768
  /**
9769
9769
  * Workflow generated via contractspec CLI.
@@ -9836,11 +9836,9 @@ function generateWorkflowRunnerTemplate({
9836
9836
  runnerName,
9837
9837
  workflowName
9838
9838
  }) {
9839
- return `import {
9840
- InMemoryStateStore,
9841
- WorkflowRegistry,
9842
- WorkflowRunner,
9843
- } from '@contractspec/lib.contracts-spec/workflow';
9839
+ return `import { InMemoryStateStore } from '@contractspec/lib.contracts-spec/workflow/adapters';
9840
+ import { WorkflowRunner } from '@contractspec/lib.contracts-spec/workflow/runner';
9841
+ import { WorkflowRegistry } from '@contractspec/lib.contracts-spec/workflow/spec';
9844
9842
  import { ${exportName} } from '${specImportPath}';
9845
9843
 
9846
9844
  /**
@@ -14667,8 +14665,10 @@ __export(exports_upgrade, {
14667
14665
  getPackageUpgradeCommand: () => getPackageUpgradeCommand,
14668
14666
  getDefaultVersioningConfig: () => getDefaultVersioningConfig,
14669
14667
  getDefaultHooksConfig: () => getDefaultHooksConfig,
14668
+ applyGuidedUpgrade: () => applyGuidedUpgrade,
14670
14669
  applyConfigUpgrades: () => applyConfigUpgrades,
14671
- analyzeUpgrades: () => analyzeUpgrades
14670
+ analyzeUpgrades: () => analyzeUpgrades,
14671
+ analyzeGuidedUpgrade: () => analyzeGuidedUpgrade
14672
14672
  });
14673
14673
 
14674
14674
  // src/services/upgrade/upgrade-service.ts
@@ -14676,14 +14676,16 @@ var API_HOST = process.env["CONTRACTSPEC_API_HOST"] ?? "api.contractspec.io";
14676
14676
  var LATEST_SCHEMA_URL = `https://${API_HOST}/schemas/contractsrc.json`;
14677
14677
  async function analyzeUpgrades(adapters, options) {
14678
14678
  const { fs: fs5, logger: logger3 } = adapters;
14679
+ const packageRoot = findPackageRoot(options.workspaceRoot);
14679
14680
  const workspaceRoot = findWorkspaceRoot(options.workspaceRoot);
14680
14681
  const packageManager = detectPackageManager(workspaceRoot);
14681
14682
  logger3.info("Analyzing available upgrades...", {
14682
14683
  workspaceRoot,
14684
+ packageRoot,
14683
14685
  packageManager
14684
14686
  });
14685
- const packages = await analyzePackages(fs5, workspaceRoot);
14686
- const configUpgrades = await analyzeConfig(fs5, workspaceRoot);
14687
+ const packages = await analyzePackages(fs5, packageRoot);
14688
+ const configUpgrades = await analyzeConfig(fs5, packageRoot);
14687
14689
  const hasUpgrades = packages.length > 0 || configUpgrades.length > 0;
14688
14690
  return {
14689
14691
  packageManager,
@@ -14762,6 +14764,22 @@ async function analyzeConfig(fs5, workspaceRoot) {
14762
14764
  isNew: true
14763
14765
  });
14764
14766
  }
14767
+ if (!config["release"]) {
14768
+ upgrades.push({
14769
+ key: "release",
14770
+ currentValue: undefined,
14771
+ suggestedValue: getDefaultReleaseConfig(),
14772
+ isNew: true
14773
+ });
14774
+ }
14775
+ if (!config["upgrade"]) {
14776
+ upgrades.push({
14777
+ key: "upgrade",
14778
+ currentValue: undefined,
14779
+ suggestedValue: getDefaultUpgradeConfig(),
14780
+ isNew: true
14781
+ });
14782
+ }
14765
14783
  return upgrades;
14766
14784
  } catch {
14767
14785
  return [];
@@ -14769,11 +14787,11 @@ async function analyzeConfig(fs5, workspaceRoot) {
14769
14787
  }
14770
14788
  async function applyConfigUpgrades(adapters, options) {
14771
14789
  const { fs: fs5, logger: logger3 } = adapters;
14772
- const workspaceRoot = findWorkspaceRoot(options.workspaceRoot);
14790
+ const packageRoot = findPackageRoot(options.workspaceRoot);
14773
14791
  if (options.dryRun) {
14774
14792
  logger3.info("Dry run - no changes will be made");
14775
14793
  }
14776
- const configPath = fs5.join(workspaceRoot, ".contractsrc.json");
14794
+ const configPath = fs5.join(packageRoot, ".contractsrc.json");
14777
14795
  if (!await fs5.exists(configPath)) {
14778
14796
  return {
14779
14797
  success: false,
@@ -14806,6 +14824,14 @@ async function applyConfigUpgrades(adapters, options) {
14806
14824
  config["hooks"] = getDefaultHooksConfig();
14807
14825
  sectionsUpgraded++;
14808
14826
  }
14827
+ if (!config["release"]) {
14828
+ config["release"] = getDefaultReleaseConfig();
14829
+ sectionsUpgraded++;
14830
+ }
14831
+ if (!config["upgrade"]) {
14832
+ config["upgrade"] = getDefaultUpgradeConfig();
14833
+ sectionsUpgraded++;
14834
+ }
14809
14835
  if (sectionsUpgraded === 0) {
14810
14836
  return {
14811
14837
  success: true,
@@ -14865,6 +14891,446 @@ function getDefaultHooksConfig() {
14865
14891
  "pre-commit": ["contractspec validate", "contractspec integrity check"]
14866
14892
  };
14867
14893
  }
14894
+ function getDefaultReleaseConfig() {
14895
+ return {
14896
+ enforceOn: "release-branch",
14897
+ requireChangesetForPublished: true,
14898
+ requireReleaseCapsule: true,
14899
+ publishArtifacts: [
14900
+ "manifest.json",
14901
+ "patch-notes.md",
14902
+ "customer-guide.md",
14903
+ "upgrade-manifest.json"
14904
+ ],
14905
+ agentTargets: ["codex"]
14906
+ };
14907
+ }
14908
+ function getDefaultUpgradeConfig() {
14909
+ return {
14910
+ manifestPaths: ["generated/releases/upgrade-manifest.json"],
14911
+ defaultAgentTarget: "codex",
14912
+ enableInteractiveGuidance: true,
14913
+ applyCodemods: true
14914
+ };
14915
+ }
14916
+ // src/services/upgrade/guided-upgrade.ts
14917
+ import {
14918
+ GeneratedReleaseManifestSchema
14919
+ } from "@contractspec/lib.contracts-spec";
14920
+ import {
14921
+ ContractsrcSchema as ContractsrcSchema3,
14922
+ DEFAULT_CONTRACTSRC as DEFAULT_CONTRACTSRC4
14923
+ } from "@contractspec/lib.contracts-spec/workspace-config/contractsrc-schema";
14924
+
14925
+ // src/services/versioning/release-plan.ts
14926
+ import {
14927
+ compareVersions,
14928
+ countUpgradePlanStepLevels,
14929
+ createAgentPromptBundles,
14930
+ dedupeUpgradePlanSteps,
14931
+ sortReleaseManifest
14932
+ } from "@contractspec/lib.contracts-spec";
14933
+ function selectUpgradeReleases(manifest, packages) {
14934
+ if (packages.length === 0) {
14935
+ return sortReleaseManifest(manifest);
14936
+ }
14937
+ const packageVersions = new Map(packages.map((pkg) => [pkg.name, pkg.currentVersion ?? "0.0.0"]));
14938
+ return sortReleaseManifest({
14939
+ ...manifest,
14940
+ releases: manifest.releases.filter((release) => release.packages.some((pkg) => {
14941
+ const currentVersion = packageVersions.get(pkg.name);
14942
+ if (!currentVersion || !pkg.version) {
14943
+ return false;
14944
+ }
14945
+ return compareVersions(pkg.version, currentVersion) === 1;
14946
+ }))
14947
+ });
14948
+ }
14949
+ function createUpgradePlan(manifest, packages, agentTargets, renderPrompt) {
14950
+ const releases = selectUpgradeReleases(manifest, packages);
14951
+ const targetPackages = buildUpgradeTargets(releases, packages);
14952
+ const steps = dedupeUpgradePlanSteps(releases.flatMap((release) => release.upgradeSteps));
14953
+ const counts = countUpgradePlanStepLevels(steps);
14954
+ const basePlan = {
14955
+ generatedAt: new Date().toISOString(),
14956
+ targetPackages,
14957
+ releases,
14958
+ steps,
14959
+ autofixCount: counts.auto,
14960
+ manualCount: counts.manual,
14961
+ assistedCount: counts.assisted,
14962
+ agentPrompts: []
14963
+ };
14964
+ const agentPrompts = createAgentPromptBundles(basePlan, agentTargets, renderPrompt);
14965
+ return {
14966
+ ...basePlan,
14967
+ agentPrompts
14968
+ };
14969
+ }
14970
+ function buildUpgradeTargets(releases, packages) {
14971
+ const currentVersions = new Map(packages.map((pkg) => [pkg.name, pkg.currentVersion]));
14972
+ const targets = new Map;
14973
+ for (const release of releases) {
14974
+ for (const pkg of release.packages) {
14975
+ const current = currentVersions.get(pkg.name);
14976
+ const existing = targets.get(pkg.name);
14977
+ if (!existing) {
14978
+ targets.set(pkg.name, {
14979
+ name: pkg.name,
14980
+ currentVersion: current,
14981
+ targetVersion: pkg.version
14982
+ });
14983
+ continue;
14984
+ }
14985
+ if (pkg.version && existing.targetVersion && compareVersions(pkg.version, existing.targetVersion) === 1) {
14986
+ existing.targetVersion = pkg.version;
14987
+ }
14988
+ }
14989
+ }
14990
+ return Array.from(targets.values()).sort((left, right) => left.name.localeCompare(right.name));
14991
+ }
14992
+
14993
+ // src/services/versioning/release-formatters.ts
14994
+ function formatPackages(entry) {
14995
+ return entry.packages.map((pkg) => {
14996
+ const version = pkg.version ? `@${pkg.version}` : "";
14997
+ return `- ${pkg.name}${version} (${pkg.releaseType})`;
14998
+ });
14999
+ }
15000
+ function formatSteps(steps) {
15001
+ return steps.map((step) => ` - ${step}`);
15002
+ }
15003
+ function renderMaintainerSummary(entry) {
15004
+ const lines = [
15005
+ `### ${entry.summary}`,
15006
+ `- Slug: ${entry.slug}`,
15007
+ `- Date: ${entry.date}`,
15008
+ `- Breaking: ${entry.isBreaking ? "yes" : "no"}`,
15009
+ ...formatPackages(entry)
15010
+ ];
15011
+ if (entry.deprecations.length > 0) {
15012
+ lines.push("- Deprecations:");
15013
+ lines.push(...entry.deprecations.map((item) => ` - ${item}`));
15014
+ }
15015
+ return lines.join(`
15016
+ `);
15017
+ }
15018
+ function renderCustomerPatchNote(entry) {
15019
+ const lines = [`### ${entry.summary}`, ...formatPackages(entry)];
15020
+ for (const instruction of entry.migrationInstructions) {
15021
+ lines.push(`- ${instruction.title}: ${instruction.summary}`);
15022
+ }
15023
+ return lines.join(`
15024
+ `);
15025
+ }
15026
+ function renderMigrationGuide(entry) {
15027
+ const lines = [`### ${entry.summary}`];
15028
+ if (entry.migrationInstructions.length === 0) {
15029
+ lines.push("- No manual migration steps recorded.");
15030
+ return lines.join(`
15031
+ `);
15032
+ }
15033
+ for (const instruction of entry.migrationInstructions) {
15034
+ lines.push(`- ${instruction.title}: ${instruction.summary}`);
15035
+ lines.push(...formatSteps(instruction.steps));
15036
+ }
15037
+ return lines.join(`
15038
+ `);
15039
+ }
15040
+ function renderPatchNotes(manifest) {
15041
+ return ["# Patch Notes", "", ...manifest.releases.map(renderMaintainerSummary)].join(`
15042
+
15043
+ `);
15044
+ }
15045
+ function renderCustomerGuide(manifest) {
15046
+ return [
15047
+ "# Customer Upgrade Guide",
15048
+ "",
15049
+ ...manifest.releases.map(renderMigrationGuide)
15050
+ ].join(`
15051
+
15052
+ `);
15053
+ }
15054
+ function renderUpgradeChecklist(plan) {
15055
+ if (plan.steps.length === 0) {
15056
+ return "- No release-managed upgrade steps were found.";
15057
+ }
15058
+ return plan.steps.map((step) => [`- [${step.level}] ${step.title}: ${step.summary}`, ...formatSteps(step.instructions)].join(`
15059
+ `)).join(`
15060
+ `);
15061
+ }
15062
+ function renderUpgradePrompt(agent, plan) {
15063
+ const targets = plan.targetPackages.map((pkg) => `${pkg.name}: ${pkg.currentVersion ?? "unknown"} -> ${pkg.targetVersion ?? "latest"}`).join(`
15064
+ `);
15065
+ return [
15066
+ `Apply the ContractSpec upgrade plan in this workspace using ${agent}.`,
15067
+ "",
15068
+ "Target packages:",
15069
+ targets || "- No package version targets were inferred.",
15070
+ "",
15071
+ "Required steps:",
15072
+ renderUpgradeChecklist(plan)
15073
+ ].join(`
15074
+ `);
15075
+ }
15076
+
15077
+ // src/services/upgrade/guided-upgrade.ts
15078
+ var IMPORT_REWRITE_GLOB = "**/*.{ts,tsx,js,jsx,mjs,cjs}";
15079
+ var DEFAULT_UPGRADE_CONFIG = DEFAULT_CONTRACTSRC4.upgrade ?? {
15080
+ manifestPaths: ["generated/releases/upgrade-manifest.json"],
15081
+ defaultAgentTarget: "codex",
15082
+ enableInteractiveGuidance: true,
15083
+ applyCodemods: true
15084
+ };
15085
+ async function analyzeGuidedUpgrade(adapters, options) {
15086
+ const { fs: fs5 } = adapters;
15087
+ const packageRoot = findPackageRoot(options.workspaceRoot);
15088
+ const workspaceRoot = findWorkspaceRoot(options.workspaceRoot);
15089
+ const config = await readWorkspaceUpgradeConfig(fs5, packageRoot, workspaceRoot);
15090
+ const manifestResolution = await resolveUpgradeManifest(fs5, packageRoot, workspaceRoot, options.manifestPaths ?? config.manifestPaths);
15091
+ const analysis = await analyzeUpgrades(adapters, {
15092
+ workspaceRoot: packageRoot,
15093
+ dryRun: options.dryRun
15094
+ });
15095
+ const agentTargets = [
15096
+ options.agentTarget ?? config.defaultAgentTarget ?? "codex"
15097
+ ];
15098
+ const plan = createUpgradePlan(manifestResolution.manifest, analysis.packages.map((pkg) => ({
15099
+ name: pkg.name,
15100
+ currentVersion: pkg.currentVersion
15101
+ })), agentTargets, renderUpgradePrompt);
15102
+ const augmentedPlan = augmentUpgradePlan(plan, analysis.packages, analysis.configUpgrades);
15103
+ return {
15104
+ packageManager: analysis.packageManager,
15105
+ manifestPath: manifestResolution.path,
15106
+ plan: augmentedPlan,
15107
+ humanChecklist: buildChecklist(augmentedPlan)
15108
+ };
15109
+ }
15110
+ async function applyGuidedUpgrade(adapters, options) {
15111
+ const { fs: fs5, logger: logger3 } = adapters;
15112
+ const packageRoot = findPackageRoot(options.workspaceRoot);
15113
+ const analysis = await analyzeGuidedUpgrade(adapters, options);
15114
+ const appliedAutofixes = [];
15115
+ if (!options.dryRun) {
15116
+ for (const step of analysis.plan.steps) {
15117
+ if (step.level !== "auto") {
15118
+ continue;
15119
+ }
15120
+ for (const fix of step.autofixes ?? []) {
15121
+ const applied = await applyAutofix(fs5, packageRoot, fix);
15122
+ if (applied) {
15123
+ appliedAutofixes.push(fix.id);
15124
+ }
15125
+ }
15126
+ }
15127
+ logger3.info("Applied guided upgrade autofixes", {
15128
+ count: appliedAutofixes.length,
15129
+ workspaceRoot: packageRoot
15130
+ });
15131
+ }
15132
+ const remainingSteps = analysis.plan.steps.filter((step) => step.level !== "auto");
15133
+ return {
15134
+ success: true,
15135
+ packagesUpgraded: appliedAutofixes.filter((id) => id.startsWith("pkg:")).length,
15136
+ configSectionsUpgraded: appliedAutofixes.filter((id) => id.startsWith("config:")).length,
15137
+ summary: options.dryRun ? `Would apply ${analysis.plan.autofixCount} deterministic upgrade step(s)` : `Applied ${appliedAutofixes.length} deterministic upgrade autofix(es)`,
15138
+ appliedAutofixes,
15139
+ remainingSteps,
15140
+ humanChecklist: analysis.humanChecklist,
15141
+ promptBundle: analysis.plan.agentPrompts[0],
15142
+ plan: analysis.plan,
15143
+ manifestPath: analysis.manifestPath
15144
+ };
15145
+ }
15146
+ async function resolveUpgradeManifest(fs5, packageRoot, workspaceRoot, manifestPaths) {
15147
+ const candidatePaths = manifestPaths ?? ["generated/releases/upgrade-manifest.json"];
15148
+ const searchRoots = Array.from(new Set([packageRoot, workspaceRoot]));
15149
+ for (const candidatePath of candidatePaths) {
15150
+ const resolvedPaths = candidatePath.startsWith("/") ? [candidatePath] : searchRoots.map((root) => fs5.join(root, candidatePath));
15151
+ for (const resolvedPath of resolvedPaths) {
15152
+ if (!await fs5.exists(resolvedPath)) {
15153
+ continue;
15154
+ }
15155
+ const manifest = GeneratedReleaseManifestSchema.parse(JSON.parse(await fs5.readFile(resolvedPath)));
15156
+ return { path: resolvedPath, manifest };
15157
+ }
15158
+ }
15159
+ return {
15160
+ manifest: GeneratedReleaseManifestSchema.parse({
15161
+ generatedAt: new Date().toISOString(),
15162
+ releases: []
15163
+ })
15164
+ };
15165
+ }
15166
+ async function readWorkspaceUpgradeConfig(fs5, packageRoot, workspaceRoot) {
15167
+ for (const configPath of Array.from(new Set([
15168
+ fs5.join(packageRoot, ".contractsrc.json"),
15169
+ fs5.join(workspaceRoot, ".contractsrc.json")
15170
+ ]))) {
15171
+ if (!await fs5.exists(configPath)) {
15172
+ continue;
15173
+ }
15174
+ try {
15175
+ const parsed = JSON.parse(await fs5.readFile(configPath));
15176
+ const validated = ContractsrcSchema3.safeParse(parsed);
15177
+ if (validated.success) {
15178
+ return {
15179
+ ...DEFAULT_UPGRADE_CONFIG,
15180
+ ...validated.data.upgrade
15181
+ };
15182
+ }
15183
+ } catch {
15184
+ continue;
15185
+ }
15186
+ }
15187
+ return DEFAULT_UPGRADE_CONFIG;
15188
+ }
15189
+ function augmentUpgradePlan(plan, packages, configUpgrades) {
15190
+ const packageFixes = [];
15191
+ for (const pkg of packages) {
15192
+ const target = plan.targetPackages.find((entry) => entry.name === pkg.name);
15193
+ if (!target?.targetVersion || target.targetVersion === pkg.currentVersion) {
15194
+ continue;
15195
+ }
15196
+ packageFixes.push({
15197
+ id: `pkg:${pkg.name}`,
15198
+ kind: "package-json",
15199
+ title: `Update ${pkg.name}`,
15200
+ summary: `Update ${pkg.name} to ${target.targetVersion}`,
15201
+ packageName: pkg.name,
15202
+ dependencyType: pkg.isDevDependency ? "devDependencies" : "dependencies",
15203
+ from: pkg.currentVersion,
15204
+ to: target.targetVersion
15205
+ });
15206
+ }
15207
+ const configFixes = configUpgrades.map((upgrade) => ({
15208
+ id: `config:${upgrade.key}`,
15209
+ kind: "contractsrc",
15210
+ title: `Update ${upgrade.key}`,
15211
+ summary: `Update .contractsrc.json at ${upgrade.key}`,
15212
+ configPath: upgrade.key,
15213
+ value: upgrade.suggestedValue
15214
+ }));
15215
+ const implicitSteps = [];
15216
+ if (packageFixes.length > 0) {
15217
+ implicitSteps.push({
15218
+ id: "upgrade-contractspec-packages",
15219
+ title: "Update ContractSpec packages",
15220
+ summary: "Apply package version upgrades from the release manifest.",
15221
+ level: "auto",
15222
+ instructions: packageFixes.map((fix) => `${fix.packageName}: ${fix.from ?? "current"} -> ${fix.to ?? "latest"}`),
15223
+ packages: packageFixes.map((fix) => fix.packageName ?? ""),
15224
+ autofixes: packageFixes
15225
+ });
15226
+ }
15227
+ if (configFixes.length > 0) {
15228
+ implicitSteps.push({
15229
+ id: "upgrade-contractsrc-config",
15230
+ title: "Refresh .contractsrc.json defaults",
15231
+ summary: "Bring workspace release and upgrade config in line with current defaults.",
15232
+ level: "auto",
15233
+ instructions: configFixes.map((fix) => `${fix.configPath} -> updated`),
15234
+ autofixes: configFixes
15235
+ });
15236
+ }
15237
+ const steps = [...implicitSteps, ...plan.steps];
15238
+ const autoCount = steps.filter((step) => step.level === "auto").length;
15239
+ const assistedCount = steps.filter((step) => step.level === "assisted").length;
15240
+ const manualCount = steps.filter((step) => step.level === "manual").length;
15241
+ return {
15242
+ ...plan,
15243
+ steps,
15244
+ autofixCount: autoCount,
15245
+ assistedCount,
15246
+ manualCount,
15247
+ agentPrompts: createUpgradePlan({
15248
+ generatedAt: plan.generatedAt,
15249
+ releases: plan.releases
15250
+ }, plan.targetPackages.map((pkg) => ({
15251
+ name: pkg.name,
15252
+ currentVersion: pkg.currentVersion
15253
+ })), plan.agentPrompts.map((prompt) => prompt.agent), renderUpgradePrompt).agentPrompts
15254
+ };
15255
+ }
15256
+ function buildChecklist(plan) {
15257
+ return plan.steps.map((step) => `${step.title}: ${step.summary}`);
15258
+ }
15259
+ async function applyAutofix(fs5, workspaceRoot, fix) {
15260
+ switch (fix.kind) {
15261
+ case "package-json":
15262
+ return applyPackageJsonAutofix(fs5, workspaceRoot, fix);
15263
+ case "contractsrc":
15264
+ return applyContractsrcAutofix(fs5, workspaceRoot, fix);
15265
+ case "import-rewrite":
15266
+ return applyImportRewriteAutofix(fs5, workspaceRoot, fix);
15267
+ case "codemod":
15268
+ return false;
15269
+ }
15270
+ }
15271
+ async function applyPackageJsonAutofix(fs5, workspaceRoot, fix) {
15272
+ const packageJsonPath = fs5.join(workspaceRoot, "package.json");
15273
+ if (!await fs5.exists(packageJsonPath)) {
15274
+ return false;
15275
+ }
15276
+ const packageJson = JSON.parse(await fs5.readFile(packageJsonPath));
15277
+ const dependencyType = fix.dependencyType ?? "dependencies";
15278
+ const dependencies2 = packageJson[dependencyType] ?? {};
15279
+ if (!fix.packageName || !fix.to || !dependencies2[fix.packageName]) {
15280
+ return false;
15281
+ }
15282
+ dependencies2[fix.packageName] = fix.to;
15283
+ packageJson[dependencyType] = dependencies2;
15284
+ await fs5.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2) + `
15285
+ `);
15286
+ return true;
15287
+ }
15288
+ async function applyContractsrcAutofix(fs5, workspaceRoot, fix) {
15289
+ const configPath = fs5.join(workspaceRoot, ".contractsrc.json");
15290
+ const config = await fs5.exists(configPath) ? JSON.parse(await fs5.readFile(configPath)) : {};
15291
+ if (!fix.configPath) {
15292
+ return false;
15293
+ }
15294
+ setNestedValue(config, fix.configPath, fix.value);
15295
+ await fs5.writeFile(configPath, JSON.stringify(config, null, 2) + `
15296
+ `);
15297
+ return true;
15298
+ }
15299
+ async function applyImportRewriteAutofix(fs5, workspaceRoot, fix) {
15300
+ if (!fix.from || !fix.to) {
15301
+ return false;
15302
+ }
15303
+ const files = await fs5.glob({
15304
+ pattern: fix.path ?? IMPORT_REWRITE_GLOB,
15305
+ cwd: workspaceRoot,
15306
+ absolute: true
15307
+ });
15308
+ let changed = false;
15309
+ for (const filePath of files) {
15310
+ const content = await fs5.readFile(filePath);
15311
+ if (!content.includes(fix.from)) {
15312
+ continue;
15313
+ }
15314
+ await fs5.writeFile(filePath, content.split(fix.from).join(fix.to));
15315
+ changed = true;
15316
+ }
15317
+ return changed;
15318
+ }
15319
+ function setNestedValue(target, path9, value) {
15320
+ const segments = path9.split(".");
15321
+ let current = target;
15322
+ for (const segment of segments.slice(0, -1)) {
15323
+ const next = current[segment];
15324
+ if (!next || typeof next !== "object" || Array.isArray(next)) {
15325
+ current[segment] = {};
15326
+ }
15327
+ current = current[segment];
15328
+ }
15329
+ const lastSegment = segments.at(-1);
15330
+ if (lastSegment) {
15331
+ current[lastSegment] = value;
15332
+ }
15333
+ }
14868
15334
  // src/services/verification-cache/adapters/filesystem.ts
14869
15335
  import {
14870
15336
  existsSync as existsSync3,
@@ -16233,8 +16699,16 @@ var verifyService = new VerifyService;
16233
16699
  // src/services/versioning/index.ts
16234
16700
  var exports_versioning = {};
16235
16701
  __export(exports_versioning, {
16702
+ renderUpgradePrompt: () => renderUpgradePrompt,
16703
+ renderUpgradeChecklist: () => renderUpgradeChecklist,
16704
+ renderPatchNotes: () => renderPatchNotes,
16705
+ renderMigrationGuide: () => renderMigrationGuide,
16706
+ renderMaintainerSummary: () => renderMaintainerSummary,
16707
+ renderCustomerPatchNote: () => renderCustomerPatchNote,
16708
+ renderCustomerGuide: () => renderCustomerGuide,
16236
16709
  parseConventionalCommit: () => parseConventionalCommit,
16237
16710
  isConventionalCommit: () => isConventionalCommit,
16711
+ initReleaseArtifacts: () => initReleaseArtifacts,
16238
16712
  getHighestBumpType: () => getHighestBumpType,
16239
16713
  getBumpTypeFromCommit: () => getBumpTypeFromCommit,
16240
16714
  generateChangelogs: () => generateChangelogs,
@@ -16245,6 +16719,8 @@ __export(exports_versioning, {
16245
16719
  filterBumpableCommits: () => filterBumpableCommits,
16246
16720
  commitsToChangeEntries: () => commitsToChangeEntries,
16247
16721
  commitToChangeEntry: () => commitToChangeEntry,
16722
+ checkReleaseArtifacts: () => checkReleaseArtifacts,
16723
+ buildReleaseArtifacts: () => buildReleaseArtifacts,
16248
16724
  applyVersionBump: () => applyVersionBump,
16249
16725
  analyzeVersionsFromCommits: () => analyzeVersionsFromCommits,
16250
16726
  analyzeVersions: () => analyzeVersions,
@@ -17021,6 +17497,390 @@ function generateRandomName() {
17021
17497
  const random = Math.floor(Math.random() * 1000);
17022
17498
  return `${adj}-${noun}-${verb}-${random}`;
17023
17499
  }
17500
+ // src/services/versioning/release-service.ts
17501
+ import {
17502
+ GeneratedReleaseManifestSchema as GeneratedReleaseManifestSchema2
17503
+ } from "@contractspec/lib.contracts-spec";
17504
+ import {
17505
+ ContractsrcSchema as ContractsrcSchema4,
17506
+ DEFAULT_CONTRACTSRC as DEFAULT_CONTRACTSRC5
17507
+ } from "@contractspec/lib.contracts-spec/workspace-config/contractsrc-schema";
17508
+
17509
+ // src/services/versioning/release-files.ts
17510
+ import { ReleaseCapsuleSchema } from "@contractspec/lib.contracts-spec";
17511
+ import { dump, load } from "js-yaml";
17512
+ var CHANGESET_PATTERN = ".changeset/*.md";
17513
+ var RELEASE_CAPSULE_PATTERN = ".changeset/*.release.yaml";
17514
+ var CHANGESET_FRONTMATTER = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/;
17515
+ var CHANGESET_RELEASE_LINE = /^["']?([^"':]+(?:\/[^"':]+)?)["']?\s*:\s*(major|minor|patch|none)\s*$/;
17516
+ async function listChangesetFiles(fs5, workspaceRoot) {
17517
+ const files = await fs5.glob({
17518
+ pattern: CHANGESET_PATTERN,
17519
+ cwd: workspaceRoot,
17520
+ absolute: true
17521
+ });
17522
+ return files.filter((file) => fs5.basename(file) !== "README.md");
17523
+ }
17524
+ async function readChangesets(fs5, workspaceRoot) {
17525
+ const files = await listChangesetFiles(fs5, workspaceRoot);
17526
+ const changesets = [];
17527
+ for (const filePath of files) {
17528
+ const content = await fs5.readFile(filePath);
17529
+ changesets.push(parseChangesetContent(fs5.basename(filePath), content));
17530
+ }
17531
+ return changesets;
17532
+ }
17533
+ async function readReleaseCapsules(fs5, workspaceRoot, changesets) {
17534
+ const files = await fs5.glob({
17535
+ pattern: RELEASE_CAPSULE_PATTERN,
17536
+ cwd: workspaceRoot,
17537
+ absolute: true
17538
+ });
17539
+ const fallbackPackages = new Map(changesets.map((changeset) => [changeset.slug, changeset.packages]));
17540
+ const capsules = new Map;
17541
+ for (const filePath of files) {
17542
+ const slug = fs5.basename(filePath).replace(/\.release\.yaml$/, "");
17543
+ const parsed = load(await fs5.readFile(filePath));
17544
+ const capsule = normalizeCapsule(parsed, slug, fallbackPackages.get(slug) ?? []);
17545
+ capsules.set(slug, capsule);
17546
+ }
17547
+ return capsules;
17548
+ }
17549
+ async function discoverWorkspacePackages(fs5, workspaceRoot) {
17550
+ const packageJsonFiles = await fs5.glob({
17551
+ patterns: ["package.json", "packages/*/*/package.json"],
17552
+ cwd: workspaceRoot,
17553
+ absolute: true
17554
+ });
17555
+ const packages = [];
17556
+ for (const filePath of packageJsonFiles) {
17557
+ const manifest = JSON.parse(await fs5.readFile(filePath));
17558
+ if (!manifest.name || !manifest.version || manifest.private === true) {
17559
+ continue;
17560
+ }
17561
+ const dir = fs5.relative(workspaceRoot, fs5.dirname(filePath)) || ".";
17562
+ packages.push({ name: manifest.name, dir, version: manifest.version });
17563
+ }
17564
+ return packages.sort((left, right) => left.dir.localeCompare(right.dir));
17565
+ }
17566
+ function matchChangedFilesToPackages(changedFiles, packages) {
17567
+ const names = new Set;
17568
+ for (const changedFile of changedFiles) {
17569
+ const match = packages.find((pkg) => pkg.dir === "." ? !changedFile.startsWith("packages/") : changedFile === pkg.dir || changedFile.startsWith(`${pkg.dir}/`));
17570
+ if (match) {
17571
+ names.add(match.name);
17572
+ }
17573
+ }
17574
+ return Array.from(names).sort((left, right) => left.localeCompare(right));
17575
+ }
17576
+ function renderChangesetMarkdown(summary, packages) {
17577
+ const header = packages.map((pkg) => `"${pkg.name}": ${pkg.releaseType}`).join(`
17578
+ `);
17579
+ return `---
17580
+ ${header}
17581
+ ---
17582
+
17583
+ ${summary}
17584
+ `;
17585
+ }
17586
+ function renderReleaseCapsuleYaml(capsule) {
17587
+ return dump(ReleaseCapsuleSchema.parse(capsule), {
17588
+ noRefs: true,
17589
+ lineWidth: 100
17590
+ });
17591
+ }
17592
+ function parseChangesetContent(fileName, content) {
17593
+ const slug = fileName.replace(/\.md$/, "");
17594
+ const match = content.match(CHANGESET_FRONTMATTER);
17595
+ if (!match) {
17596
+ return { slug, summary: content.trim(), packages: [] };
17597
+ }
17598
+ const header = match[1] ?? "";
17599
+ const summary = (match[2] ?? "").trim();
17600
+ const packages = [];
17601
+ for (const line of header.split(`
17602
+ `)) {
17603
+ const parsed = line.trim().match(CHANGESET_RELEASE_LINE);
17604
+ if (!parsed) {
17605
+ continue;
17606
+ }
17607
+ const packageName = parsed[1];
17608
+ const releaseType = parsed[2];
17609
+ if (!packageName || releaseType === "none") {
17610
+ continue;
17611
+ }
17612
+ packages.push({ name: packageName, releaseType });
17613
+ }
17614
+ return { slug, summary, packages };
17615
+ }
17616
+ function normalizeCapsule(value, slug, fallbackPackages) {
17617
+ const record = typeof value === "object" && value !== null ? value : {};
17618
+ const legacyPackageNames = Array.isArray(record["packageNames"]) ? record["packageNames"].filter((entry) => typeof entry === "string").map((name) => ({
17619
+ name,
17620
+ releaseType: record["releaseType"] ?? "patch"
17621
+ })) : [];
17622
+ return ReleaseCapsuleSchema.parse({
17623
+ ...record,
17624
+ slug,
17625
+ packages: Array.isArray(record["packages"]) && record["packages"].length > 0 ? record["packages"] : legacyPackageNames.length > 0 ? legacyPackageNames : fallbackPackages
17626
+ });
17627
+ }
17628
+
17629
+ // src/services/versioning/release-service.ts
17630
+ var DEFAULT_OUTPUT_DIR = "generated/releases";
17631
+ var DEFAULT_RELEASE_CONFIG = DEFAULT_CONTRACTSRC5.release ?? {
17632
+ enforceOn: "release-branch",
17633
+ requireChangesetForPublished: true,
17634
+ requireReleaseCapsule: true,
17635
+ publishArtifacts: [],
17636
+ agentTargets: ["codex"]
17637
+ };
17638
+ async function initReleaseArtifacts(adapters2, options = {}) {
17639
+ const { fs: fs5, git: git3, logger: logger3 } = adapters2;
17640
+ const workspaceRoot = findWorkspaceRoot(options.workspaceRoot);
17641
+ const workspacePackages = await discoverWorkspacePackages(fs5, workspaceRoot);
17642
+ const changedFiles = options.baseline ? await git3.diffFiles(options.baseline) : [];
17643
+ const packageNames = options.packages && options.packages.length > 0 ? options.packages : matchChangedFilesToPackages(changedFiles, workspacePackages);
17644
+ const commitAnalysis = options.baseline ? await analyzeVersionsFromCommits(adapters2, {
17645
+ baseline: options.baseline,
17646
+ workspaceRoot
17647
+ }) : null;
17648
+ const impact = options.baseline ? await detectImpact(adapters2, {
17649
+ baseline: options.baseline,
17650
+ workspaceRoot
17651
+ }) : null;
17652
+ const inferredReleaseType = (impact?.summary.breaking ?? 0) > 0 ? "major" : commitAnalysis?.suggestedBumpType ?? inferReleaseType(packageNames);
17653
+ const releaseType = options.releaseType ?? inferredReleaseType;
17654
+ const summary = options.summary ?? `Describe the ${releaseType} release for ${packageNames[0] ?? "the workspace"}`;
17655
+ const slug = options.slug ?? slugify(summary);
17656
+ const packages = packageNames.map((name) => ({
17657
+ name,
17658
+ releaseType,
17659
+ version: workspacePackages.find((pkg) => pkg.name === name)?.version
17660
+ }));
17661
+ const capsule = {
17662
+ schemaVersion: "1",
17663
+ slug,
17664
+ summary,
17665
+ isBreaking: releaseType === "major" || (impact?.summary.breaking ?? 0) > 0,
17666
+ packages,
17667
+ affectedRuntimes: [],
17668
+ affectedFrameworks: [],
17669
+ audiences: [
17670
+ { kind: "maintainer", summary },
17671
+ { kind: "customer", summary }
17672
+ ],
17673
+ deprecations: [],
17674
+ migrationInstructions: [],
17675
+ upgradeSteps: [],
17676
+ validation: {
17677
+ commands: [
17678
+ "contractspec impact --baseline main --format markdown",
17679
+ "contractspec version analyze --baseline main"
17680
+ ],
17681
+ evidence: []
17682
+ }
17683
+ };
17684
+ const changesetPath = fs5.join(workspaceRoot, ".changeset", `${slug}.md`);
17685
+ const capsulePath = fs5.join(workspaceRoot, ".changeset", `${slug}.release.yaml`);
17686
+ if (!options.force) {
17687
+ if (await fs5.exists(changesetPath)) {
17688
+ throw new Error(`Changeset already exists: ${changesetPath}`);
17689
+ }
17690
+ if (await fs5.exists(capsulePath)) {
17691
+ throw new Error(`Release capsule already exists: ${capsulePath}`);
17692
+ }
17693
+ }
17694
+ const changesetContent = renderChangesetMarkdown(summary, packages);
17695
+ const capsuleContent = renderReleaseCapsuleYaml(capsule);
17696
+ if (!options.dryRun) {
17697
+ await fs5.writeFile(changesetPath, changesetContent);
17698
+ await fs5.writeFile(capsulePath, capsuleContent);
17699
+ }
17700
+ logger3.info("Initialized release artifacts", { slug, workspaceRoot });
17701
+ return {
17702
+ slug,
17703
+ changesetPath,
17704
+ capsulePath,
17705
+ changesetContent,
17706
+ capsuleContent,
17707
+ packages,
17708
+ releaseType,
17709
+ isBreaking: capsule.isBreaking
17710
+ };
17711
+ }
17712
+ async function buildReleaseArtifacts(adapters2, options = {}) {
17713
+ const { fs: fs5, logger: logger3 } = adapters2;
17714
+ const workspaceRoot = findWorkspaceRoot(options.workspaceRoot);
17715
+ const config = await readWorkspaceReleaseConfig(fs5, workspaceRoot);
17716
+ const outputDir = fs5.join(workspaceRoot, options.outputDir ?? DEFAULT_OUTPUT_DIR);
17717
+ const changesets = await readChangesets(fs5, workspaceRoot);
17718
+ const capsules = await readReleaseCapsules(fs5, workspaceRoot, changesets);
17719
+ const workspacePackages = await discoverWorkspacePackages(fs5, workspaceRoot);
17720
+ const releases = await Promise.all(Array.from(capsules.entries()).map(async ([slug, capsule]) => {
17721
+ const filePath = fs5.join(workspaceRoot, ".changeset", `${slug}.release.yaml`);
17722
+ const stats = await fs5.stat(filePath);
17723
+ return {
17724
+ slug,
17725
+ version: resolveReleaseVersion(capsule, workspacePackages),
17726
+ summary: capsule.summary,
17727
+ date: stats.mtime.toISOString().split("T")[0] ?? new Date().toISOString(),
17728
+ isBreaking: capsule.isBreaking,
17729
+ packages: capsule.packages.map((pkg) => ({
17730
+ ...pkg,
17731
+ version: workspacePackages.find((candidate) => candidate.name === pkg.name)?.version ?? pkg.version
17732
+ })),
17733
+ affectedRuntimes: [...capsule.affectedRuntimes],
17734
+ affectedFrameworks: [...capsule.affectedFrameworks],
17735
+ audiences: [...capsule.audiences],
17736
+ deprecations: [...capsule.deprecations],
17737
+ migrationInstructions: [...capsule.migrationInstructions],
17738
+ upgradeSteps: [...capsule.upgradeSteps],
17739
+ validation: capsule.validation
17740
+ };
17741
+ }));
17742
+ const manifest = GeneratedReleaseManifestSchema2.parse({
17743
+ generatedAt: new Date().toISOString(),
17744
+ releases
17745
+ });
17746
+ const agentTargets = options.agentTargets ?? config.agentTargets ?? ["codex"];
17747
+ const upgradePlan = createUpgradePlan(manifest, [], agentTargets, renderUpgradePrompt);
17748
+ const manifestPath = fs5.join(outputDir, "manifest.json");
17749
+ const upgradeManifestPath = fs5.join(outputDir, "upgrade-manifest.json");
17750
+ const patchNotesPath = fs5.join(outputDir, "patch-notes.md");
17751
+ const customerGuidePath = fs5.join(outputDir, "customer-guide.md");
17752
+ const promptPaths = Object.fromEntries(upgradePlan.agentPrompts.map((prompt) => [
17753
+ prompt.agent,
17754
+ fs5.join(outputDir, "prompts", `${prompt.agent}.md`)
17755
+ ]));
17756
+ if (!options.dryRun) {
17757
+ await fs5.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
17758
+ await fs5.writeFile(upgradeManifestPath, JSON.stringify(manifest, null, 2));
17759
+ await fs5.writeFile(patchNotesPath, renderPatchNotes(manifest));
17760
+ await fs5.writeFile(customerGuidePath, renderCustomerGuide(manifest));
17761
+ for (const prompt of upgradePlan.agentPrompts) {
17762
+ await fs5.writeFile(promptPaths[prompt.agent] ?? "", prompt.prompt);
17763
+ }
17764
+ }
17765
+ logger3.info("Built release artifacts", {
17766
+ outputDir,
17767
+ releasesBuilt: manifest.releases.length
17768
+ });
17769
+ return {
17770
+ outputDir,
17771
+ manifestPath,
17772
+ upgradeManifestPath,
17773
+ patchNotesPath,
17774
+ customerGuidePath,
17775
+ promptPaths,
17776
+ manifest,
17777
+ upgradePlan,
17778
+ releasesBuilt: manifest.releases.length
17779
+ };
17780
+ }
17781
+ async function checkReleaseArtifacts(adapters2, options = {}) {
17782
+ const { fs: fs5 } = adapters2;
17783
+ const workspaceRoot = findWorkspaceRoot(options.workspaceRoot);
17784
+ const config = await readWorkspaceReleaseConfig(fs5, workspaceRoot);
17785
+ const outputDir = fs5.join(workspaceRoot, options.outputDir ?? DEFAULT_OUTPUT_DIR);
17786
+ const checks = [];
17787
+ const warnings = [];
17788
+ const errors = [];
17789
+ const changesets = await readChangesets(fs5, workspaceRoot);
17790
+ const capsules = await readReleaseCapsules(fs5, workspaceRoot, changesets);
17791
+ recordCheck(checks, changesets.length > 0, "changesets", changesets.length > 0 ? `Found ${changesets.length} release changeset(s).` : "No release changesets found.");
17792
+ for (const changeset of changesets) {
17793
+ if (!capsules.has(changeset.slug) && config.requireReleaseCapsule) {
17794
+ errors.push(`Missing release capsule for changeset ${changeset.slug}.`);
17795
+ }
17796
+ }
17797
+ for (const [slug, capsule] of capsules) {
17798
+ if (capsule.isBreaking && capsule.migrationInstructions.length === 0) {
17799
+ errors.push(`Breaking release ${slug} is missing migration instructions.`);
17800
+ }
17801
+ if (capsule.validation.commands.length === 0) {
17802
+ errors.push(`Release capsule ${slug} is missing validation commands.`);
17803
+ }
17804
+ if (capsule.validation.evidence.length === 0) {
17805
+ errors.push(`Release capsule ${slug} is missing validation evidence.`);
17806
+ }
17807
+ }
17808
+ if (options.baseline) {
17809
+ try {
17810
+ const impact = await detectImpact(adapters2, {
17811
+ baseline: options.baseline,
17812
+ workspaceRoot
17813
+ });
17814
+ recordCheck(checks, true, "impact", `Impact status: ${impact.status}`);
17815
+ if (impact.summary.breaking > 0 && !Array.from(capsules.values()).some((capsule) => capsule.isBreaking)) {
17816
+ errors.push("Breaking impact detected without a breaking release capsule.");
17817
+ }
17818
+ } catch (error) {
17819
+ recordCheck(checks, false, "impact", failureMessage(error));
17820
+ errors.push(`Impact detection failed: ${failureMessage(error)}`);
17821
+ }
17822
+ try {
17823
+ const analysis = await analyzeVersions(adapters2, {
17824
+ baseline: options.baseline,
17825
+ workspaceRoot
17826
+ });
17827
+ recordCheck(checks, true, "versioning", `${analysis.specsNeedingBump} spec(s) need version review.`);
17828
+ } catch (error) {
17829
+ recordCheck(checks, false, "versioning", failureMessage(error));
17830
+ errors.push(`Version analysis failed: ${failureMessage(error)}`);
17831
+ }
17832
+ }
17833
+ if (options.strict) {
17834
+ for (const artifact of config.publishArtifacts ?? []) {
17835
+ const artifactPath = fs5.join(outputDir, artifact);
17836
+ if (!await fs5.exists(artifactPath)) {
17837
+ errors.push(`Missing generated release artifact: ${artifactPath}`);
17838
+ }
17839
+ }
17840
+ }
17841
+ recordCheck(checks, errors.length === 0, "capsules", errors.length === 0 ? "All release capsules are complete." : errors[0] ?? "");
17842
+ if (changesets.length === 0) {
17843
+ warnings.push("No pending release changesets were found.");
17844
+ }
17845
+ return {
17846
+ success: errors.length === 0,
17847
+ errors,
17848
+ warnings,
17849
+ checks
17850
+ };
17851
+ }
17852
+ async function readWorkspaceReleaseConfig(fs5, workspaceRoot) {
17853
+ const configPath = fs5.join(workspaceRoot, ".contractsrc.json");
17854
+ if (!await fs5.exists(configPath)) {
17855
+ return DEFAULT_RELEASE_CONFIG;
17856
+ }
17857
+ try {
17858
+ const parsed = JSON.parse(await fs5.readFile(configPath));
17859
+ const validated = ContractsrcSchema4.safeParse(parsed);
17860
+ return validated.success ? {
17861
+ ...DEFAULT_RELEASE_CONFIG,
17862
+ ...validated.data.release
17863
+ } : DEFAULT_RELEASE_CONFIG;
17864
+ } catch {
17865
+ return DEFAULT_RELEASE_CONFIG;
17866
+ }
17867
+ }
17868
+ function inferReleaseType(packageNames) {
17869
+ return packageNames.length > 0 ? "minor" : "patch";
17870
+ }
17871
+ function resolveReleaseVersion(capsule, workspacePackages) {
17872
+ const version = capsule.packages.map((pkg) => workspacePackages.find((candidate) => candidate.name === pkg.name)?.version ?? pkg.version).find((value) => typeof value === "string");
17873
+ return version ?? "0.0.0";
17874
+ }
17875
+ function slugify(value) {
17876
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
17877
+ }
17878
+ function recordCheck(checks, ok, name, message) {
17879
+ checks.push({ name, ok, message });
17880
+ }
17881
+ function failureMessage(error) {
17882
+ return error instanceof Error ? error.message : String(error);
17883
+ }
17024
17884
  // src/services/vibe/index.ts
17025
17885
  var exports_vibe = {};
17026
17886
  __export(exports_vibe, {