@contractspec/bundle.workspace 3.0.0 → 3.1.1

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.
@@ -3,8 +3,8 @@ export declare const initOperation: import("@contractspec/lib.contracts-spec").O
3
3
  yes: z.ZodDefault<z.ZodBoolean>;
4
4
  targets: z.ZodOptional<z.ZodArray<z.ZodEnum<{
5
5
  cursor: "cursor";
6
- cli: "cli";
7
6
  mcp: "mcp";
7
+ cli: "cli";
8
8
  vscode: "vscode";
9
9
  agents: "agents";
10
10
  github: "github";
package/dist/index.js CHANGED
@@ -16334,6 +16334,177 @@ function generateCliReport(ir, verification) {
16334
16334
  return lines.join(`
16335
16335
  `);
16336
16336
  }
16337
+ // src/services/update.ts
16338
+ import {
16339
+ scanSpecSource as scanSpecSource4
16340
+ } from "@contractspec/module.workspace";
16341
+ function unknownSpecResult(filePath) {
16342
+ return {
16343
+ specType: "unknown",
16344
+ filePath,
16345
+ hasMeta: false,
16346
+ hasIo: false,
16347
+ hasPolicy: false,
16348
+ hasPayload: false,
16349
+ hasContent: false,
16350
+ hasDefinition: false
16351
+ };
16352
+ }
16353
+ async function updateSpec(specPath, adapters2, options = {}) {
16354
+ const { fs: fs5, logger: logger3 } = adapters2;
16355
+ const exists = await fs5.exists(specPath);
16356
+ if (!exists) {
16357
+ return {
16358
+ specPath,
16359
+ specInfo: unknownSpecResult(specPath),
16360
+ updated: false,
16361
+ errors: [`Spec file not found: ${specPath}`],
16362
+ warnings: []
16363
+ };
16364
+ }
16365
+ const originalCode = await fs5.readFile(specPath);
16366
+ let updatedCode;
16367
+ if (options.content !== undefined) {
16368
+ updatedCode = options.content;
16369
+ } else if (options.fields?.length) {
16370
+ updatedCode = applyFieldPatches(originalCode, options.fields);
16371
+ } else {
16372
+ return {
16373
+ specPath,
16374
+ specInfo: scanSpecSource4(originalCode, specPath),
16375
+ updated: false,
16376
+ errors: ["No update provided: supply `content` or `fields`"],
16377
+ warnings: []
16378
+ };
16379
+ }
16380
+ if (!options.skipValidation) {
16381
+ await fs5.writeFile(specPath, updatedCode);
16382
+ const validation3 = await validateSpec(specPath, adapters2);
16383
+ if (validation3.errors.length > 0) {
16384
+ await fs5.writeFile(specPath, originalCode);
16385
+ return {
16386
+ specPath,
16387
+ specInfo: scanSpecSource4(updatedCode, specPath),
16388
+ updated: false,
16389
+ errors: validation3.errors,
16390
+ warnings: validation3.warnings
16391
+ };
16392
+ }
16393
+ if (validation3.warnings.length > 0 && !options.allowWarnings) {
16394
+ await fs5.writeFile(specPath, originalCode);
16395
+ return {
16396
+ specPath,
16397
+ specInfo: scanSpecSource4(updatedCode, specPath),
16398
+ updated: false,
16399
+ errors: [
16400
+ "Validation produced warnings (use allowWarnings to override)"
16401
+ ],
16402
+ warnings: validation3.warnings
16403
+ };
16404
+ }
16405
+ logger3.info(`Updated spec: ${specPath}`);
16406
+ return {
16407
+ specPath,
16408
+ specInfo: scanSpecSource4(updatedCode, specPath),
16409
+ updated: true,
16410
+ errors: [],
16411
+ warnings: validation3.warnings
16412
+ };
16413
+ }
16414
+ await fs5.writeFile(specPath, updatedCode);
16415
+ logger3.info(`Updated spec (validation skipped): ${specPath}`);
16416
+ return {
16417
+ specPath,
16418
+ specInfo: scanSpecSource4(updatedCode, specPath),
16419
+ updated: true,
16420
+ errors: [],
16421
+ warnings: []
16422
+ };
16423
+ }
16424
+ function applyFieldPatches(code, fields) {
16425
+ let result = code;
16426
+ for (const { key, value } of fields) {
16427
+ const fieldName = key.includes(".") ? key.split(".").pop() : key;
16428
+ if (!fieldName)
16429
+ continue;
16430
+ const pattern = new RegExp(`(${escapeRegex2(fieldName)}\\s*:\\s*)(['"\`])([^'"\`]*?)\\2`, "g");
16431
+ const needsQuotes = !/^(true|false|\d+(\.\d+)?)$/.test(value);
16432
+ const replacement = needsQuotes ? `$1"${value}"` : `$1${value}`;
16433
+ result = result.replace(pattern, replacement);
16434
+ }
16435
+ return result;
16436
+ }
16437
+ function escapeRegex2(str) {
16438
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
16439
+ }
16440
+ // src/services/delete.ts
16441
+ import {
16442
+ scanSpecSource as scanSpecSource5
16443
+ } from "@contractspec/module.workspace";
16444
+ function unknownSpecResult2(filePath) {
16445
+ return {
16446
+ specType: "unknown",
16447
+ filePath,
16448
+ hasMeta: false,
16449
+ hasIo: false,
16450
+ hasPolicy: false,
16451
+ hasPayload: false,
16452
+ hasContent: false,
16453
+ hasDefinition: false
16454
+ };
16455
+ }
16456
+ async function deleteSpec(specPath, adapters2, options = {}) {
16457
+ const { fs: fs5, logger: logger3 } = adapters2;
16458
+ const exists = await fs5.exists(specPath);
16459
+ if (!exists && !options.force) {
16460
+ return {
16461
+ specPath,
16462
+ specInfo: unknownSpecResult2(specPath),
16463
+ deleted: false,
16464
+ cleanedFiles: [],
16465
+ errors: [`Spec file not found: ${specPath}`]
16466
+ };
16467
+ }
16468
+ let specInfo = unknownSpecResult2(specPath);
16469
+ let specKey2;
16470
+ if (exists) {
16471
+ try {
16472
+ const code = await fs5.readFile(specPath);
16473
+ specInfo = scanSpecSource5(code, specPath);
16474
+ specKey2 = specInfo.key;
16475
+ } catch {}
16476
+ }
16477
+ const cleanedFiles = [];
16478
+ if (options.clean && specKey2) {
16479
+ try {
16480
+ const implementations = await discoverImplementationsForSpec(specKey2, {
16481
+ fs: fs5
16482
+ });
16483
+ for (const impl of implementations) {
16484
+ try {
16485
+ await fs5.remove(impl.filePath);
16486
+ cleanedFiles.push(impl.filePath);
16487
+ logger3.info(`Removed artifact: ${impl.filePath}`);
16488
+ } catch {
16489
+ logger3.warn(`Could not remove artifact: ${impl.filePath}`);
16490
+ }
16491
+ }
16492
+ } catch {
16493
+ logger3.warn("Could not discover implementations for cleanup");
16494
+ }
16495
+ }
16496
+ if (exists) {
16497
+ await fs5.remove(specPath);
16498
+ logger3.info(`Deleted spec: ${specPath}`);
16499
+ }
16500
+ return {
16501
+ specPath,
16502
+ specInfo,
16503
+ deleted: true,
16504
+ cleanedFiles,
16505
+ errors: []
16506
+ };
16507
+ }
16337
16508
  // src/registry.ts
16338
16509
  import { OperationSpecRegistry as OperationSpecRegistry3 } from "@contractspec/lib.contracts-spec/operations";
16339
16510
  import { registerReportContracts } from "@contractspec/lib.contracts-spec/operations/report";
@@ -16836,6 +17007,7 @@ export {
16836
17007
  validateAgainstOpenApiService,
16837
17008
  exports_utils as utils,
16838
17009
  exports_upgrade as upgrade,
17010
+ updateSpec,
16839
17011
  toKebabCase3 as toKebabCase,
16840
17012
  exports_templates as templates,
16841
17013
  syncWithOpenApiService,
@@ -16953,6 +17125,7 @@ export {
16953
17125
  determineStatus,
16954
17126
  detectRepositoryType,
16955
17127
  detectPackageManager,
17128
+ deleteSpec,
16956
17129
  deepMergePreserve,
16957
17130
  deepMergeOverwrite,
16958
17131
  cursorCLIAdapter,
@@ -16334,6 +16334,177 @@ function generateCliReport(ir, verification) {
16334
16334
  return lines.join(`
16335
16335
  `);
16336
16336
  }
16337
+ // src/services/update.ts
16338
+ import {
16339
+ scanSpecSource as scanSpecSource4
16340
+ } from "@contractspec/module.workspace";
16341
+ function unknownSpecResult(filePath) {
16342
+ return {
16343
+ specType: "unknown",
16344
+ filePath,
16345
+ hasMeta: false,
16346
+ hasIo: false,
16347
+ hasPolicy: false,
16348
+ hasPayload: false,
16349
+ hasContent: false,
16350
+ hasDefinition: false
16351
+ };
16352
+ }
16353
+ async function updateSpec(specPath, adapters2, options = {}) {
16354
+ const { fs: fs5, logger: logger3 } = adapters2;
16355
+ const exists = await fs5.exists(specPath);
16356
+ if (!exists) {
16357
+ return {
16358
+ specPath,
16359
+ specInfo: unknownSpecResult(specPath),
16360
+ updated: false,
16361
+ errors: [`Spec file not found: ${specPath}`],
16362
+ warnings: []
16363
+ };
16364
+ }
16365
+ const originalCode = await fs5.readFile(specPath);
16366
+ let updatedCode;
16367
+ if (options.content !== undefined) {
16368
+ updatedCode = options.content;
16369
+ } else if (options.fields?.length) {
16370
+ updatedCode = applyFieldPatches(originalCode, options.fields);
16371
+ } else {
16372
+ return {
16373
+ specPath,
16374
+ specInfo: scanSpecSource4(originalCode, specPath),
16375
+ updated: false,
16376
+ errors: ["No update provided: supply `content` or `fields`"],
16377
+ warnings: []
16378
+ };
16379
+ }
16380
+ if (!options.skipValidation) {
16381
+ await fs5.writeFile(specPath, updatedCode);
16382
+ const validation3 = await validateSpec(specPath, adapters2);
16383
+ if (validation3.errors.length > 0) {
16384
+ await fs5.writeFile(specPath, originalCode);
16385
+ return {
16386
+ specPath,
16387
+ specInfo: scanSpecSource4(updatedCode, specPath),
16388
+ updated: false,
16389
+ errors: validation3.errors,
16390
+ warnings: validation3.warnings
16391
+ };
16392
+ }
16393
+ if (validation3.warnings.length > 0 && !options.allowWarnings) {
16394
+ await fs5.writeFile(specPath, originalCode);
16395
+ return {
16396
+ specPath,
16397
+ specInfo: scanSpecSource4(updatedCode, specPath),
16398
+ updated: false,
16399
+ errors: [
16400
+ "Validation produced warnings (use allowWarnings to override)"
16401
+ ],
16402
+ warnings: validation3.warnings
16403
+ };
16404
+ }
16405
+ logger3.info(`Updated spec: ${specPath}`);
16406
+ return {
16407
+ specPath,
16408
+ specInfo: scanSpecSource4(updatedCode, specPath),
16409
+ updated: true,
16410
+ errors: [],
16411
+ warnings: validation3.warnings
16412
+ };
16413
+ }
16414
+ await fs5.writeFile(specPath, updatedCode);
16415
+ logger3.info(`Updated spec (validation skipped): ${specPath}`);
16416
+ return {
16417
+ specPath,
16418
+ specInfo: scanSpecSource4(updatedCode, specPath),
16419
+ updated: true,
16420
+ errors: [],
16421
+ warnings: []
16422
+ };
16423
+ }
16424
+ function applyFieldPatches(code, fields) {
16425
+ let result = code;
16426
+ for (const { key, value } of fields) {
16427
+ const fieldName = key.includes(".") ? key.split(".").pop() : key;
16428
+ if (!fieldName)
16429
+ continue;
16430
+ const pattern = new RegExp(`(${escapeRegex2(fieldName)}\\s*:\\s*)(['"\`])([^'"\`]*?)\\2`, "g");
16431
+ const needsQuotes = !/^(true|false|\d+(\.\d+)?)$/.test(value);
16432
+ const replacement = needsQuotes ? `$1"${value}"` : `$1${value}`;
16433
+ result = result.replace(pattern, replacement);
16434
+ }
16435
+ return result;
16436
+ }
16437
+ function escapeRegex2(str) {
16438
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
16439
+ }
16440
+ // src/services/delete.ts
16441
+ import {
16442
+ scanSpecSource as scanSpecSource5
16443
+ } from "@contractspec/module.workspace";
16444
+ function unknownSpecResult2(filePath) {
16445
+ return {
16446
+ specType: "unknown",
16447
+ filePath,
16448
+ hasMeta: false,
16449
+ hasIo: false,
16450
+ hasPolicy: false,
16451
+ hasPayload: false,
16452
+ hasContent: false,
16453
+ hasDefinition: false
16454
+ };
16455
+ }
16456
+ async function deleteSpec(specPath, adapters2, options = {}) {
16457
+ const { fs: fs5, logger: logger3 } = adapters2;
16458
+ const exists = await fs5.exists(specPath);
16459
+ if (!exists && !options.force) {
16460
+ return {
16461
+ specPath,
16462
+ specInfo: unknownSpecResult2(specPath),
16463
+ deleted: false,
16464
+ cleanedFiles: [],
16465
+ errors: [`Spec file not found: ${specPath}`]
16466
+ };
16467
+ }
16468
+ let specInfo = unknownSpecResult2(specPath);
16469
+ let specKey2;
16470
+ if (exists) {
16471
+ try {
16472
+ const code = await fs5.readFile(specPath);
16473
+ specInfo = scanSpecSource5(code, specPath);
16474
+ specKey2 = specInfo.key;
16475
+ } catch {}
16476
+ }
16477
+ const cleanedFiles = [];
16478
+ if (options.clean && specKey2) {
16479
+ try {
16480
+ const implementations = await discoverImplementationsForSpec(specKey2, {
16481
+ fs: fs5
16482
+ });
16483
+ for (const impl of implementations) {
16484
+ try {
16485
+ await fs5.remove(impl.filePath);
16486
+ cleanedFiles.push(impl.filePath);
16487
+ logger3.info(`Removed artifact: ${impl.filePath}`);
16488
+ } catch {
16489
+ logger3.warn(`Could not remove artifact: ${impl.filePath}`);
16490
+ }
16491
+ }
16492
+ } catch {
16493
+ logger3.warn("Could not discover implementations for cleanup");
16494
+ }
16495
+ }
16496
+ if (exists) {
16497
+ await fs5.remove(specPath);
16498
+ logger3.info(`Deleted spec: ${specPath}`);
16499
+ }
16500
+ return {
16501
+ specPath,
16502
+ specInfo,
16503
+ deleted: true,
16504
+ cleanedFiles,
16505
+ errors: []
16506
+ };
16507
+ }
16337
16508
  // src/registry.ts
16338
16509
  import { OperationSpecRegistry as OperationSpecRegistry3 } from "@contractspec/lib.contracts-spec/operations";
16339
16510
  import { registerReportContracts } from "@contractspec/lib.contracts-spec/operations/report";
@@ -16836,6 +17007,7 @@ export {
16836
17007
  validateAgainstOpenApiService,
16837
17008
  exports_utils as utils,
16838
17009
  exports_upgrade as upgrade,
17010
+ updateSpec,
16839
17011
  toKebabCase3 as toKebabCase,
16840
17012
  exports_templates as templates,
16841
17013
  syncWithOpenApiService,
@@ -16953,6 +17125,7 @@ export {
16953
17125
  determineStatus,
16954
17126
  detectRepositoryType,
16955
17127
  detectPackageManager,
17128
+ deleteSpec,
16956
17129
  deepMergePreserve,
16957
17130
  deepMergeOverwrite,
16958
17131
  cursorCLIAdapter,
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Delete spec service.
3
+ *
4
+ * Removes a spec file and optionally cleans up generated artifacts
5
+ * (handlers, tests, components) that reference it.
6
+ */
7
+ import { type SpecScanResult } from '@contractspec/module.workspace';
8
+ import type { FsAdapter } from '../ports/fs';
9
+ import type { LoggerAdapter } from '../ports/logger';
10
+ export interface DeleteSpecOptions {
11
+ /** Also remove generated artifacts that reference this spec. */
12
+ clean?: boolean;
13
+ /** Skip the existence check (caller already confirmed). */
14
+ force?: boolean;
15
+ }
16
+ export interface DeleteSpecResult {
17
+ specPath: string;
18
+ specInfo: SpecScanResult;
19
+ deleted: boolean;
20
+ cleanedFiles: string[];
21
+ errors: string[];
22
+ }
23
+ /**
24
+ * Delete a spec file and optionally its generated artifacts.
25
+ */
26
+ export declare function deleteSpec(specPath: string, adapters: {
27
+ fs: FsAdapter;
28
+ logger: LoggerAdapter;
29
+ }, options?: DeleteSpecOptions): Promise<DeleteSpecResult>;
@@ -46,3 +46,5 @@ export * from './generate-artifacts';
46
46
  export * from './extract';
47
47
  export * as vibe from './vibe/index';
48
48
  export * from './import/index';
49
+ export * from './update';
50
+ export * from './delete';
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Update spec service.
3
+ *
4
+ * Reads an existing spec file, applies field-level patches or
5
+ * full-content replacement, re-validates, and writes back.
6
+ */
7
+ import { type SpecScanResult } from '@contractspec/module.workspace';
8
+ import type { FsAdapter } from '../ports/fs';
9
+ import type { LoggerAdapter } from '../ports/logger';
10
+ /**
11
+ * A single field patch: dot-path key and the replacement source text.
12
+ *
13
+ * Example: `{ key: "meta.stability", value: "stable" }`
14
+ * replaces the `stability: '...'` value in the `meta` block.
15
+ */
16
+ export interface SpecFieldPatch {
17
+ key: string;
18
+ value: string;
19
+ }
20
+ export interface UpdateSpecOptions {
21
+ /** Replace spec file content entirely. */
22
+ content?: string;
23
+ /** Apply individual field patches (ignored when `content` is set). */
24
+ fields?: SpecFieldPatch[];
25
+ /** Skip post-update validation. */
26
+ skipValidation?: boolean;
27
+ /** Write even when validation produces warnings. */
28
+ allowWarnings?: boolean;
29
+ }
30
+ export interface UpdateSpecResult {
31
+ specPath: string;
32
+ specInfo: SpecScanResult;
33
+ updated: boolean;
34
+ errors: string[];
35
+ warnings: string[];
36
+ }
37
+ /**
38
+ * Update an existing spec file.
39
+ */
40
+ export declare function updateSpec(specPath: string, adapters: {
41
+ fs: FsAdapter;
42
+ logger: LoggerAdapter;
43
+ }, options?: UpdateSpecOptions): Promise<UpdateSpecResult>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contractspec/bundle.workspace",
3
- "version": "3.0.0",
3
+ "version": "3.1.1",
4
4
  "description": "Workspace utilities for monorepo development",
5
5
  "keywords": [
6
6
  "contractspec",
@@ -31,17 +31,17 @@
31
31
  "typecheck": "tsc --noEmit"
32
32
  },
33
33
  "dependencies": {
34
- "@ai-sdk/anthropic": "3.0.48",
35
- "@ai-sdk/openai": "3.0.36",
36
- "@contractspec/lib.ai-agent": "3.0.0",
37
- "@contractspec/lib.ai-providers": "3.0.0",
38
- "@contractspec/lib.contracts-spec": "3.0.0",
39
- "@contractspec/lib.contracts-integrations": "3.0.0",
40
- "@contractspec/lib.contracts-transformers": "3.0.0",
41
- "@contractspec/lib.source-extractors": "2.0.0",
42
- "@contractspec/module.workspace": "3.0.0",
43
- "@contractspec/lib.utils-typescript": "3.0.0",
44
- "ai": "6.0.103",
34
+ "@ai-sdk/anthropic": "3.0.58",
35
+ "@ai-sdk/openai": "3.0.41",
36
+ "@contractspec/lib.ai-agent": "3.1.1",
37
+ "@contractspec/lib.ai-providers": "3.1.1",
38
+ "@contractspec/lib.contracts-spec": "3.1.1",
39
+ "@contractspec/lib.contracts-integrations": "3.1.1",
40
+ "@contractspec/lib.contracts-transformers": "3.1.1",
41
+ "@contractspec/lib.source-extractors": "2.1.1",
42
+ "@contractspec/module.workspace": "3.1.1",
43
+ "@contractspec/lib.utils-typescript": "3.1.0",
44
+ "ai": "6.0.116",
45
45
  "chalk": "^5.6.2",
46
46
  "chokidar": "^5.0.0",
47
47
  "glob": "^13.0.6",
@@ -52,12 +52,12 @@
52
52
  "zod": "^4.3.5"
53
53
  },
54
54
  "devDependencies": {
55
- "@contractspec/tool.typescript": "3.0.0",
56
- "@types/bun": "^1.3.9",
55
+ "@contractspec/tool.typescript": "3.1.0",
56
+ "@types/bun": "^1.3.10",
57
57
  "@types/micromatch": "^4.0.10",
58
- "@types/node": "^25.3.2",
58
+ "@types/node": "^25.3.5",
59
59
  "typescript": "^5.9.3",
60
- "@contractspec/tool.bun": "3.0.0"
60
+ "@contractspec/tool.bun": "3.1.0"
61
61
  },
62
62
  "exports": {
63
63
  ".": {