@contractspec/bundle.workspace 4.0.0 → 4.1.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.
@@ -2,8 +2,8 @@
2
2
  * Adapter for UnifiedAgent to work with Workspace AgentOrchestrator.
3
3
  */
4
4
  import { type UnifiedAgentConfig } from '@contractspec/lib.ai-agent/agent/unified-agent';
5
- import type { AgentSpec } from '@contractspec/lib.ai-agent/spec';
6
5
  import type { AgentMode } from '@contractspec/lib.contracts-spec';
6
+ import type { AgentSpec } from '@contractspec/lib.contracts-spec/agent';
7
7
  import type { AgentProvider, AgentResult, AgentTask } from './types';
8
8
  export declare class UnifiedAgentAdapter implements AgentProvider {
9
9
  readonly name: AgentMode;
package/dist/index.js CHANGED
@@ -397,7 +397,6 @@ var DEFAULT_FS_IGNORES = [
397
397
  "**/*.d.ts",
398
398
  "**/importer/**",
399
399
  "**/exporter/**",
400
- "**/docs/**/*.docblock.ts",
401
400
  "**/docs/presentations.ts"
402
401
  ];
403
402
 
@@ -4349,6 +4348,93 @@ async function runDepsChecks(adapters, options) {
4349
4348
  }
4350
4349
  return issues;
4351
4350
  }
4351
+ // src/services/docs/docblock-audit.ts
4352
+ import {
4353
+ analyzePackageDocBlocks
4354
+ } from "@contractspec/module.workspace";
4355
+ var PACKAGE_GLOB_IGNORES = [
4356
+ "**/node_modules/**",
4357
+ "**/dist/**",
4358
+ "**/.turbo/**",
4359
+ "**/.next/**"
4360
+ ];
4361
+ async function discoverWorkspaceDocPackages(fs5, workspaceRoot) {
4362
+ const packageJsonFiles = await fs5.glob({
4363
+ pattern: "packages/**/package.json",
4364
+ cwd: workspaceRoot,
4365
+ ignore: PACKAGE_GLOB_IGNORES
4366
+ });
4367
+ const discovered = new Map;
4368
+ for (const packageJsonFile of packageJsonFiles) {
4369
+ const packageRoot = fs5.dirname(packageJsonFile);
4370
+ const srcRoot = fs5.join(packageRoot, "src");
4371
+ if (!await fs5.exists(srcRoot)) {
4372
+ continue;
4373
+ }
4374
+ try {
4375
+ const packageJson = JSON.parse(await fs5.readFile(packageJsonFile));
4376
+ discovered.set(packageRoot, {
4377
+ packageName: packageJson.name ?? fs5.relative(workspaceRoot, packageRoot),
4378
+ packageRoot,
4379
+ srcRoot
4380
+ });
4381
+ } catch {
4382
+ continue;
4383
+ }
4384
+ }
4385
+ const rootPackageJson = fs5.join(workspaceRoot, "package.json");
4386
+ const rootSrc = fs5.join(workspaceRoot, "src");
4387
+ if (await fs5.exists(rootPackageJson) && await fs5.exists(rootSrc)) {
4388
+ try {
4389
+ const packageJson = JSON.parse(await fs5.readFile(rootPackageJson));
4390
+ discovered.set(workspaceRoot, {
4391
+ packageName: packageJson.name ?? workspaceRoot,
4392
+ packageRoot: workspaceRoot,
4393
+ srcRoot: rootSrc
4394
+ });
4395
+ } catch {}
4396
+ }
4397
+ return [...discovered.values()].sort((left, right) => left.packageRoot.localeCompare(right.packageRoot));
4398
+ }
4399
+ async function analyzeWorkspaceDocBlocks(fs5, workspaceRoot) {
4400
+ const diagnostics = [];
4401
+ const packages = await discoverWorkspaceDocPackages(fs5, workspaceRoot);
4402
+ for (const pkg of packages) {
4403
+ const result = analyzePackageDocBlocks({
4404
+ packageName: pkg.packageName,
4405
+ srcRoot: pkg.srcRoot
4406
+ });
4407
+ for (const diagnostic of result.diagnostics) {
4408
+ diagnostics.push({
4409
+ ...diagnostic,
4410
+ packageName: pkg.packageName,
4411
+ packageRoot: pkg.packageRoot,
4412
+ srcRoot: pkg.srcRoot
4413
+ });
4414
+ }
4415
+ }
4416
+ return diagnostics;
4417
+ }
4418
+
4419
+ // src/services/ci-check/checks/docs.ts
4420
+ async function runDocsChecks(adapters, options) {
4421
+ const workspaceRoot = options.workspaceRoot ?? process.cwd();
4422
+ const diagnostics = await analyzeWorkspaceDocBlocks(adapters.fs, workspaceRoot);
4423
+ return diagnostics.map((diagnostic) => ({
4424
+ ruleId: diagnostic.ruleId,
4425
+ severity: diagnostic.severity === "warning" ? "warning" : "error",
4426
+ message: diagnostic.message,
4427
+ category: "docs",
4428
+ file: diagnostic.file,
4429
+ line: diagnostic.line,
4430
+ column: diagnostic.column,
4431
+ context: {
4432
+ packageName: diagnostic.packageName,
4433
+ packageRoot: diagnostic.packageRoot,
4434
+ ...diagnostic.context
4435
+ }
4436
+ }));
4437
+ }
4352
4438
  // src/services/doctor/checks/ai.ts
4353
4439
  async function runAiChecks(fs5, ctx, prompts) {
4354
4440
  const results = [];
@@ -4627,7 +4713,7 @@ function generateContractsrcConfig(options) {
4627
4713
  type: "biome"
4628
4714
  },
4629
4715
  ci: {
4630
- checks: ["structure", "integrity", "deps", "doctor", "policy"],
4716
+ checks: ["structure", "integrity", "deps", "doctor", "docs", "policy"],
4631
4717
  failOnWarnings: false,
4632
4718
  uploadSarif: true
4633
4719
  },
@@ -5437,6 +5523,34 @@ async function checkContractsLibrary(fs5, ctx) {
5437
5523
  };
5438
5524
  }
5439
5525
  }
5526
+ // src/services/doctor/checks/docs.ts
5527
+ async function runDocChecks(fs5, ctx) {
5528
+ const diagnostics = await analyzeWorkspaceDocBlocks(fs5, ctx.workspaceRoot);
5529
+ if (diagnostics.length === 0) {
5530
+ return [
5531
+ {
5532
+ category: "docs",
5533
+ name: "Same-File DocBlocks",
5534
+ status: "pass",
5535
+ message: "All analyzed packages follow the same-file DocBlock rules."
5536
+ }
5537
+ ];
5538
+ }
5539
+ return diagnostics.map((diagnostic) => ({
5540
+ category: "docs",
5541
+ name: `Same-File DocBlocks (${diagnostic.packageName})`,
5542
+ status: diagnostic.severity === "warning" ? "warn" : "fail",
5543
+ message: diagnostic.message,
5544
+ details: fs5.relative(ctx.workspaceRoot, diagnostic.file),
5545
+ context: {
5546
+ ruleId: diagnostic.ruleId,
5547
+ file: diagnostic.file,
5548
+ line: diagnostic.line,
5549
+ column: diagnostic.column,
5550
+ packageName: diagnostic.packageName
5551
+ }
5552
+ }));
5553
+ }
5440
5554
  // src/services/layer-discovery.ts
5441
5555
  import {
5442
5556
  isExampleFile,
@@ -6346,6 +6460,7 @@ var ALL_CHECK_CATEGORIES = [
6346
6460
  "config",
6347
6461
  "mcp",
6348
6462
  "deps",
6463
+ "docs",
6349
6464
  "workspace",
6350
6465
  "ai",
6351
6466
  "layers"
@@ -6355,6 +6470,7 @@ var CHECK_CATEGORY_LABELS = {
6355
6470
  config: "Configuration Files",
6356
6471
  mcp: "MCP Server",
6357
6472
  deps: "Dependencies",
6473
+ docs: "DocBlock Ownership",
6358
6474
  workspace: "Workspace Structure",
6359
6475
  ai: "AI Provider",
6360
6476
  layers: "Contract Layers"
@@ -6367,6 +6483,7 @@ var defaultDoctorDependencies = {
6367
6483
  runConfigChecks,
6368
6484
  runMcpChecks,
6369
6485
  runDepsChecks: runDepsChecks2,
6486
+ runDocChecks,
6370
6487
  runWorkspaceChecks,
6371
6488
  runAiChecks,
6372
6489
  runLayerChecks
@@ -6450,6 +6567,8 @@ async function runCategoryChecks(category, fs5, ctx, prompts, checks) {
6450
6567
  return checks.runMcpChecks(fs5, ctx);
6451
6568
  case "deps":
6452
6569
  return checks.runDepsChecks(fs5, ctx);
6570
+ case "docs":
6571
+ return checks.runDocChecks(fs5, ctx);
6453
6572
  case "workspace":
6454
6573
  return checks.runWorkspaceChecks(fs5, ctx);
6455
6574
  case "ai":
@@ -6532,13 +6651,14 @@ import path3 from "path";
6532
6651
 
6533
6652
  // src/services/generate-artifacts.ts
6534
6653
  import path2 from "path";
6535
-
6536
6654
  // src/services/docs/docs-service.ts
6537
6655
  import {
6538
- defaultDocRegistry
6656
+ packageDocBlocks
6539
6657
  } from "@contractspec/lib.contracts-spec/docs";
6540
6658
  import {
6659
+ buildPackageDocManifest,
6541
6660
  convertSpecToDocBlock,
6661
+ extractModuleDocData,
6542
6662
  loadSpecFromSource,
6543
6663
  scanAllSpecsFromSource as scanAllSpecsFromSource2,
6544
6664
  scanSpecSource as scanSpecSource3
@@ -6628,6 +6748,14 @@ async function generateDocsFromSpecs(specFiles, options, adapters) {
6628
6748
  } catch (_err) {}
6629
6749
  }
6630
6750
  resolver.initialize(scanResults);
6751
+ const authoredEntries = await loadAuthoredDocEntriesFromSourceFiles(specFiles, adapters);
6752
+ for (const authoredEntry of authoredEntries) {
6753
+ blocks.push(authoredEntry.entry.block);
6754
+ logger3.debug(`Loaded authored doc ${authoredEntry.entry.id}`);
6755
+ if (options.outputDir) {
6756
+ await writeDocBlockFile(authoredEntry.entry.block, authoredEntry.filePath, options.outputDir, resolver, fs5);
6757
+ }
6758
+ }
6631
6759
  for (const file of specFiles) {
6632
6760
  try {
6633
6761
  const parsedList = await loadSpecFromSource(file);
@@ -6639,26 +6767,12 @@ async function generateDocsFromSpecs(specFiles, options, adapters) {
6639
6767
  const block = convertSpecToDocBlock(parsed, {
6640
6768
  rootPath: options.rootPath
6641
6769
  });
6642
- defaultDocRegistry.register(block);
6643
6770
  blocks.push(block);
6644
6771
  logger3.debug(`Generated doc for ${block.id}`);
6645
6772
  if (!options.outputDir) {
6646
6773
  continue;
6647
6774
  }
6648
- const moduleDef = resolver.resolve(file);
6649
- let targetDir = options.outputDir;
6650
- if (moduleDef) {
6651
- targetDir = path.join(options.outputDir, moduleDef.key);
6652
- } else {
6653
- targetDir = path.join(options.outputDir, "_common");
6654
- }
6655
- await fs5.mkdir(targetDir);
6656
- const filename = `${block.id}.md`;
6657
- const filePath = path.join(targetDir, filename);
6658
- const generatedContent = `<!-- @generated - This file was generated by ContractSpec. Do not edit manually. -->
6659
-
6660
- ${block.body}`;
6661
- await fs5.writeFile(filePath, generatedContent);
6775
+ await writeDocBlockFile(block, file, options.outputDir, resolver, fs5);
6662
6776
  }
6663
6777
  } catch (error) {
6664
6778
  logger3.error(`Error processing ${file}: ${error instanceof Error ? error.message : String(error)}`);
@@ -6669,6 +6783,36 @@ ${block.body}`;
6669
6783
  }
6670
6784
  return { blocks, count: blocks.length };
6671
6785
  }
6786
+ async function loadAuthoredDocBlocksFromSourceFiles(sourceFiles, adapters) {
6787
+ const entries = await loadAuthoredDocEntriesFromSourceFiles(sourceFiles, adapters);
6788
+ return entries.map(({ entry }) => entry.block);
6789
+ }
6790
+ function loadPackageAuthoredDocBlocks(packageName, srcRoot) {
6791
+ return packageDocBlocks(buildPackageDocManifest({ packageName, srcRoot }));
6792
+ }
6793
+ async function loadAuthoredDocEntriesFromSourceFiles(sourceFiles, adapters) {
6794
+ const uniqueFiles = [...new Set(sourceFiles)].sort((left, right) => left.localeCompare(right));
6795
+ const entries = [];
6796
+ for (const filePath of uniqueFiles) {
6797
+ const sourceText = await adapters.fs.readFile(filePath);
6798
+ const sourceModule = filePath.replace(/\\/g, "/").replace(/\.[cm]?[jt]sx?$/, "");
6799
+ const moduleData = extractModuleDocData(sourceText, filePath, sourceModule);
6800
+ for (const entry of moduleData.entries) {
6801
+ entries.push({ entry, filePath });
6802
+ }
6803
+ }
6804
+ return entries;
6805
+ }
6806
+ async function writeDocBlockFile(block, file, outputDir, resolver, fs5) {
6807
+ const moduleDef = resolver.resolve(file);
6808
+ const targetDir = moduleDef ? path.join(outputDir, moduleDef.key) : path.join(outputDir, "_common");
6809
+ await fs5.mkdir(targetDir);
6810
+ const filePath = path.join(targetDir, `${block.id}.md`);
6811
+ const generatedContent = `<!-- @generated - This file was generated by ContractSpec. Do not edit manually. -->
6812
+
6813
+ ${block.body}`;
6814
+ await fs5.writeFile(filePath, generatedContent);
6815
+ }
6672
6816
  // src/services/generate-artifacts.ts
6673
6817
  async function generateArtifacts(adapters, contractsDir, generatedDir, rootPath) {
6674
6818
  if (!await adapters.fs.exists(contractsDir)) {}
@@ -7925,7 +8069,12 @@ import {
7925
8069
  DEFAULT_CONTRACTSRC as DEFAULT_CONTRACTSRC3
7926
8070
  } from "@contractspec/lib.contracts-spec/workspace-config";
7927
8071
  var IMPLEMENTATION_PATH_PATTERN = /(^|\/)(handlers?|routes?|controllers?|api)(\/|$)|\.(handler|handlers|route|routes|controller)\.(ts|tsx)$/i;
7928
- var CONTRACT_REFERENCE_PATTERN = /@contractspec\/lib\.contracts(?:-spec|-integrations)?|define(Command|Query|Event|Feature|Presentation|Capability|Form|DataView|Integration)|OperationSpecRegistry|ContractHandler|installOp|contracts\//;
8072
+ var CONTRACT_REFERENCE_PATTERN = /@contractspec\/lib\.contracts(?:-spec|-integrations)?|define(Command|Query|Event|Feature|Presentation|Capability|Form|DataView|Integration)|OperationSpecRegistry|ContractHandler|installOp|contracts\b|['"][^'"]+\.(operation|event|presentation|feature|capability|form|test-spec)(?:\.[tj]sx?)?['"]/;
8073
+ var CONTRACT_CONTEXT_PATTERN = /@contractspec\/(?:lib\.contracts(?:-spec|-integrations)?|module\.ai-chat|bundle\.library\/application\/mcp|example\.)|['"][^'"]+\.(operation|event|presentation|feature|capability|form|test-spec)(?:\.[tj]sx?)?['"]/;
8074
+ var SUPPORT_FILE_PATTERN = /(^|\/)(index|types)\.ts$|\.types\.ts$|\.storage\.ts$|(?:^|\/)[^/]*\.(resolver|scheduler)\.ts$|(?:^|\/)[^/]*(factory|resources|mock-data)\.ts$/i;
8075
+ var FIXTURE_PATH_PATTERN = /(^|\/)(__fixtures__|fixtures)(\/|$)/i;
8076
+ var WORKSPACE_PACKAGE_ROOT_PATTERN = /^(.*\/packages\/(?:apps|apps-registry|bundles|examples|integrations|libs|modules|tools)\/[^/]+)(?:\/|$)/i;
8077
+ var PACKAGE_ROOT_POLICY_CONTEXT_PATTERN = /\/packages\/(?:apps|apps-registry|bundles|modules)\//i;
7929
8078
  function splitConventionPath(value) {
7930
8079
  if (!value) {
7931
8080
  return [];
@@ -7949,7 +8098,7 @@ function isPolicyCandidate(filePath, specFiles, config) {
7949
8098
  if (!/\.(ts|tsx)$/.test(normalized)) {
7950
8099
  return false;
7951
8100
  }
7952
- if (normalized.includes("/node_modules/") || normalized.includes("/dist/") || normalized.endsWith(".d.ts") || normalized.endsWith(".test.ts") || normalized.endsWith(".spec.ts")) {
8101
+ if (normalized.includes("/node_modules/") || normalized.includes("/dist/") || FIXTURE_PATH_PATTERN.test(normalized) || SUPPORT_FILE_PATTERN.test(normalized) || normalized.endsWith(".d.ts") || normalized.endsWith(".test.ts") || normalized.endsWith(".spec.ts")) {
7953
8102
  return false;
7954
8103
  }
7955
8104
  if (specFiles.has(normalized)) {
@@ -7960,6 +8109,16 @@ function isPolicyCandidate(filePath, specFiles, config) {
7960
8109
  }
7961
8110
  return IMPLEMENTATION_PATH_PATTERN.test(normalized);
7962
8111
  }
8112
+ function getWorkspacePackageRoot(filePath) {
8113
+ const normalized = filePath.replaceAll("\\", "/");
8114
+ const match = normalized.match(WORKSPACE_PACKAGE_ROOT_PATTERN);
8115
+ return match?.[1] ?? null;
8116
+ }
8117
+ function isBarrelFile(content) {
8118
+ const meaningfulLines = content.split(`
8119
+ `).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("//") && !line.startsWith("/*") && !line.startsWith("*") && !line.startsWith("*/"));
8120
+ return meaningfulLines.length > 0 && meaningfulLines.every((line) => line.startsWith("import ") || line.startsWith("export *") || line.startsWith("export {") || line.startsWith("export type {") || line === "'use client';" || line === '"use client";' || line === "'use server';" || line === '"use server";');
8121
+ }
7963
8122
  function resolvePolicyConfig(config) {
7964
8123
  if (!config) {
7965
8124
  return DEFAULT_CONTRACTSRC3;
@@ -7985,6 +8144,7 @@ async function runPolicyChecks(adapters, options) {
7985
8144
  const config = options.config ? resolvePolicyConfig(options.config) : await loadWorkspaceConfig(fs5);
7986
8145
  const scannedSpecs = await listSpecs({ fs: fs5 }, { config });
7987
8146
  const specFiles = new Set(scannedSpecs.map((spec) => spec.filePath.replaceAll("\\", "/")));
8147
+ const packageRootsWithSpecs = new Set(scannedSpecs.map((spec) => getWorkspacePackageRoot(spec.filePath)).filter((root) => Boolean(root)));
7988
8148
  const sourceFiles = await fs5.glob({ pattern: "**/*.{ts,tsx}" });
7989
8149
  for (const file of sourceFiles) {
7990
8150
  if (!isPolicyCandidate(file, specFiles, config)) {
@@ -7992,6 +8152,14 @@ async function runPolicyChecks(adapters, options) {
7992
8152
  }
7993
8153
  try {
7994
8154
  const content = await fs5.readFile(file);
8155
+ if (isBarrelFile(content)) {
8156
+ continue;
8157
+ }
8158
+ const packageRoot = getWorkspacePackageRoot(file);
8159
+ const hasLocalSpecContext = packageRoot !== null && PACKAGE_ROOT_POLICY_CONTEXT_PATTERN.test(packageRoot) && packageRootsWithSpecs.has(packageRoot);
8160
+ if (!hasLocalSpecContext && !CONTRACT_CONTEXT_PATTERN.test(content)) {
8161
+ continue;
8162
+ }
7995
8163
  if (CONTRACT_REFERENCE_PATTERN.test(content)) {
7996
8164
  continue;
7997
8165
  }
@@ -8132,6 +8300,7 @@ function createCategorySummary(category, issues, durationMs) {
8132
8300
  integrity: "Contract Integrity Analysis",
8133
8301
  deps: "Dependency Analysis",
8134
8302
  doctor: "Installation Health",
8303
+ docs: "DocBlock Ownership",
8135
8304
  policy: "Contract Policy Enforcement",
8136
8305
  handlers: "Handler Implementation",
8137
8306
  tests: "Test Coverage",
@@ -8179,7 +8348,7 @@ async function getGitInfo(fs5) {
8179
8348
  }
8180
8349
  function getChecksToRun(options) {
8181
8350
  const configuredChecks = options.config?.ci?.checks;
8182
- const allCategories = configuredChecks && configuredChecks.length > 0 ? [...configuredChecks] : ["structure", "integrity", "deps", "doctor"];
8351
+ const allCategories = configuredChecks && configuredChecks.length > 0 ? [...configuredChecks] : ["structure", "integrity", "deps", "doctor", "docs"];
8183
8352
  if (options.checkHandlers) {
8184
8353
  allCategories.push("handlers");
8185
8354
  }
@@ -8237,6 +8406,12 @@ async function runCIChecks(adapters, options = {}) {
8237
8406
  issues.push(...doctorIssues);
8238
8407
  categorySummaries.push(createCategorySummary("doctor", doctorIssues, Date.now() - categoryStart));
8239
8408
  }
8409
+ if (checksToRun.includes("docs")) {
8410
+ const categoryStart = Date.now();
8411
+ const docsIssues = await runDocsChecks(adapters, options);
8412
+ issues.push(...docsIssues);
8413
+ categorySummaries.push(createCategorySummary("docs", docsIssues, Date.now() - categoryStart));
8414
+ }
8240
8415
  if (checksToRun.includes("policy")) {
8241
8416
  const categoryStart = Date.now();
8242
8417
  const policyIssues = await runPolicyChecks(adapters, options);
@@ -8315,6 +8490,7 @@ var ALL_CI_CHECK_CATEGORIES = [
8315
8490
  "integrity",
8316
8491
  "deps",
8317
8492
  "doctor",
8493
+ "docs",
8318
8494
  "policy",
8319
8495
  "handlers",
8320
8496
  "tests",
@@ -8329,6 +8505,7 @@ var CI_CHECK_CATEGORY_LABELS = {
8329
8505
  integrity: "Contract Integrity Analysis",
8330
8506
  deps: "Dependency Analysis",
8331
8507
  doctor: "Installation Health",
8508
+ docs: "DocBlock Ownership",
8332
8509
  policy: "Contract Policy Enforcement",
8333
8510
  handlers: "Handler Implementation",
8334
8511
  tests: "Test Coverage",
@@ -11619,7 +11796,8 @@ __export(exports_impact, {
11619
11796
  formatMinimalComment: () => formatMinimalComment,
11620
11797
  formatJson: () => formatJson2,
11621
11798
  formatCheckRun: () => formatCheckRun,
11622
- detectImpact: () => detectImpact
11799
+ detectImpact: () => detectImpact,
11800
+ ImpactDetectionOverviewDocBlock: () => ImpactDetectionOverviewDocBlock
11623
11801
  });
11624
11802
 
11625
11803
  // src/services/impact/formatters.ts
@@ -11846,6 +12024,63 @@ function computeSnapshotDiffs(baseSpecs, headSpecs) {
11846
12024
  }
11847
12025
  return diffs;
11848
12026
  }
12027
+
12028
+ // src/services/impact/index.ts
12029
+ var ImpactDetectionOverviewDocBlock = {
12030
+ id: "feature.impact-detection.overview",
12031
+ title: "Contract Impact Detection",
12032
+ kind: "goal",
12033
+ visibility: "public",
12034
+ route: "/docs/features/impact-detection",
12035
+ body: `
12036
+ # Contract Impact Detection
12037
+
12038
+ Automated detection and classification of breaking changes in ContractSpec APIs.
12039
+
12040
+ ## Features
12041
+
12042
+ - **Snapshot Generation**: Creates canonical, deterministic representations of contracts
12043
+ - **Deep Diff Engine**: Field-level comparison of input/output schemas
12044
+ - **Breaking Change Classification**: Automatic classification using 16 rules
12045
+ - **Multiple Output Formats**: JSON, Markdown, Text, GitHub Check Run
12046
+ - **GitHub Integration**: PR comments and check runs
12047
+ - **CLI Tool**: \`contractspec impact\` command
12048
+
12049
+ ## Quick Start
12050
+
12051
+ ### CLI Usage
12052
+
12053
+ \`\`\`bash
12054
+ # Basic usage
12055
+ contractspec impact
12056
+
12057
+ # Compare against specific baseline
12058
+ contractspec impact --baseline origin/main
12059
+
12060
+ # Get JSON output
12061
+ contractspec impact --format json
12062
+ \`\`\`
12063
+
12064
+ ### GitHub Action
12065
+
12066
+ \`\`\`yaml
12067
+ - uses: lssm-tech/contractspec-action@v1
12068
+ with:
12069
+ mode: impact
12070
+ pr-comment: true
12071
+ fail-on-breaking: true
12072
+ \`\`\`
12073
+
12074
+ ## Architecture
12075
+
12076
+ The system consists of three layers:
12077
+
12078
+ 1. **Analysis Modules** (module package): Snapshot, Deep Diff, Classifier
12079
+ 2. **Impact Service** (bundle package): Orchestration + Formatters
12080
+ 3. **Integrations**: CLI command + GitHub Action
12081
+ `,
12082
+ tags: ["impact-detection", "breaking-changes", "ci-cd"]
12083
+ };
11849
12084
  // src/services/import/import-service.ts
11850
12085
  import {
11851
12086
  detectFramework,
@@ -17691,6 +17926,8 @@ export {
17691
17926
  module,
17692
17927
  mergeMonorepoConfigs,
17693
17928
  loadWorkspaceConfig,
17929
+ loadPackageAuthoredDocBlocks,
17930
+ loadAuthoredDocBlocksFromSourceFiles,
17694
17931
  listTests,
17695
17932
  listSpecsForView,
17696
17933
  listSpecs,
@@ -17814,6 +18051,7 @@ export {
17814
18051
  claudeCodeAdapter,
17815
18052
  cacheKeyToString,
17816
18053
  buildSpec,
18054
+ analyzeWorkspaceDocBlocks,
17817
18055
  analyzeIntegrity,
17818
18056
  analyzeGap,
17819
18057
  analyzeDeps,
@@ -397,7 +397,6 @@ var DEFAULT_FS_IGNORES = [
397
397
  "**/*.d.ts",
398
398
  "**/importer/**",
399
399
  "**/exporter/**",
400
- "**/docs/**/*.docblock.ts",
401
400
  "**/docs/presentations.ts"
402
401
  ];
403
402
 
@@ -4349,6 +4348,93 @@ async function runDepsChecks(adapters, options) {
4349
4348
  }
4350
4349
  return issues;
4351
4350
  }
4351
+ // src/services/docs/docblock-audit.ts
4352
+ import {
4353
+ analyzePackageDocBlocks
4354
+ } from "@contractspec/module.workspace";
4355
+ var PACKAGE_GLOB_IGNORES = [
4356
+ "**/node_modules/**",
4357
+ "**/dist/**",
4358
+ "**/.turbo/**",
4359
+ "**/.next/**"
4360
+ ];
4361
+ async function discoverWorkspaceDocPackages(fs5, workspaceRoot) {
4362
+ const packageJsonFiles = await fs5.glob({
4363
+ pattern: "packages/**/package.json",
4364
+ cwd: workspaceRoot,
4365
+ ignore: PACKAGE_GLOB_IGNORES
4366
+ });
4367
+ const discovered = new Map;
4368
+ for (const packageJsonFile of packageJsonFiles) {
4369
+ const packageRoot = fs5.dirname(packageJsonFile);
4370
+ const srcRoot = fs5.join(packageRoot, "src");
4371
+ if (!await fs5.exists(srcRoot)) {
4372
+ continue;
4373
+ }
4374
+ try {
4375
+ const packageJson = JSON.parse(await fs5.readFile(packageJsonFile));
4376
+ discovered.set(packageRoot, {
4377
+ packageName: packageJson.name ?? fs5.relative(workspaceRoot, packageRoot),
4378
+ packageRoot,
4379
+ srcRoot
4380
+ });
4381
+ } catch {
4382
+ continue;
4383
+ }
4384
+ }
4385
+ const rootPackageJson = fs5.join(workspaceRoot, "package.json");
4386
+ const rootSrc = fs5.join(workspaceRoot, "src");
4387
+ if (await fs5.exists(rootPackageJson) && await fs5.exists(rootSrc)) {
4388
+ try {
4389
+ const packageJson = JSON.parse(await fs5.readFile(rootPackageJson));
4390
+ discovered.set(workspaceRoot, {
4391
+ packageName: packageJson.name ?? workspaceRoot,
4392
+ packageRoot: workspaceRoot,
4393
+ srcRoot: rootSrc
4394
+ });
4395
+ } catch {}
4396
+ }
4397
+ return [...discovered.values()].sort((left, right) => left.packageRoot.localeCompare(right.packageRoot));
4398
+ }
4399
+ async function analyzeWorkspaceDocBlocks(fs5, workspaceRoot) {
4400
+ const diagnostics = [];
4401
+ const packages = await discoverWorkspaceDocPackages(fs5, workspaceRoot);
4402
+ for (const pkg of packages) {
4403
+ const result = analyzePackageDocBlocks({
4404
+ packageName: pkg.packageName,
4405
+ srcRoot: pkg.srcRoot
4406
+ });
4407
+ for (const diagnostic of result.diagnostics) {
4408
+ diagnostics.push({
4409
+ ...diagnostic,
4410
+ packageName: pkg.packageName,
4411
+ packageRoot: pkg.packageRoot,
4412
+ srcRoot: pkg.srcRoot
4413
+ });
4414
+ }
4415
+ }
4416
+ return diagnostics;
4417
+ }
4418
+
4419
+ // src/services/ci-check/checks/docs.ts
4420
+ async function runDocsChecks(adapters, options) {
4421
+ const workspaceRoot = options.workspaceRoot ?? process.cwd();
4422
+ const diagnostics = await analyzeWorkspaceDocBlocks(adapters.fs, workspaceRoot);
4423
+ return diagnostics.map((diagnostic) => ({
4424
+ ruleId: diagnostic.ruleId,
4425
+ severity: diagnostic.severity === "warning" ? "warning" : "error",
4426
+ message: diagnostic.message,
4427
+ category: "docs",
4428
+ file: diagnostic.file,
4429
+ line: diagnostic.line,
4430
+ column: diagnostic.column,
4431
+ context: {
4432
+ packageName: diagnostic.packageName,
4433
+ packageRoot: diagnostic.packageRoot,
4434
+ ...diagnostic.context
4435
+ }
4436
+ }));
4437
+ }
4352
4438
  // src/services/doctor/checks/ai.ts
4353
4439
  async function runAiChecks(fs5, ctx, prompts) {
4354
4440
  const results = [];
@@ -4627,7 +4713,7 @@ function generateContractsrcConfig(options) {
4627
4713
  type: "biome"
4628
4714
  },
4629
4715
  ci: {
4630
- checks: ["structure", "integrity", "deps", "doctor", "policy"],
4716
+ checks: ["structure", "integrity", "deps", "doctor", "docs", "policy"],
4631
4717
  failOnWarnings: false,
4632
4718
  uploadSarif: true
4633
4719
  },
@@ -5437,6 +5523,34 @@ async function checkContractsLibrary(fs5, ctx) {
5437
5523
  };
5438
5524
  }
5439
5525
  }
5526
+ // src/services/doctor/checks/docs.ts
5527
+ async function runDocChecks(fs5, ctx) {
5528
+ const diagnostics = await analyzeWorkspaceDocBlocks(fs5, ctx.workspaceRoot);
5529
+ if (diagnostics.length === 0) {
5530
+ return [
5531
+ {
5532
+ category: "docs",
5533
+ name: "Same-File DocBlocks",
5534
+ status: "pass",
5535
+ message: "All analyzed packages follow the same-file DocBlock rules."
5536
+ }
5537
+ ];
5538
+ }
5539
+ return diagnostics.map((diagnostic) => ({
5540
+ category: "docs",
5541
+ name: `Same-File DocBlocks (${diagnostic.packageName})`,
5542
+ status: diagnostic.severity === "warning" ? "warn" : "fail",
5543
+ message: diagnostic.message,
5544
+ details: fs5.relative(ctx.workspaceRoot, diagnostic.file),
5545
+ context: {
5546
+ ruleId: diagnostic.ruleId,
5547
+ file: diagnostic.file,
5548
+ line: diagnostic.line,
5549
+ column: diagnostic.column,
5550
+ packageName: diagnostic.packageName
5551
+ }
5552
+ }));
5553
+ }
5440
5554
  // src/services/layer-discovery.ts
5441
5555
  import {
5442
5556
  isExampleFile,
@@ -6346,6 +6460,7 @@ var ALL_CHECK_CATEGORIES = [
6346
6460
  "config",
6347
6461
  "mcp",
6348
6462
  "deps",
6463
+ "docs",
6349
6464
  "workspace",
6350
6465
  "ai",
6351
6466
  "layers"
@@ -6355,6 +6470,7 @@ var CHECK_CATEGORY_LABELS = {
6355
6470
  config: "Configuration Files",
6356
6471
  mcp: "MCP Server",
6357
6472
  deps: "Dependencies",
6473
+ docs: "DocBlock Ownership",
6358
6474
  workspace: "Workspace Structure",
6359
6475
  ai: "AI Provider",
6360
6476
  layers: "Contract Layers"
@@ -6367,6 +6483,7 @@ var defaultDoctorDependencies = {
6367
6483
  runConfigChecks,
6368
6484
  runMcpChecks,
6369
6485
  runDepsChecks: runDepsChecks2,
6486
+ runDocChecks,
6370
6487
  runWorkspaceChecks,
6371
6488
  runAiChecks,
6372
6489
  runLayerChecks
@@ -6450,6 +6567,8 @@ async function runCategoryChecks(category, fs5, ctx, prompts, checks) {
6450
6567
  return checks.runMcpChecks(fs5, ctx);
6451
6568
  case "deps":
6452
6569
  return checks.runDepsChecks(fs5, ctx);
6570
+ case "docs":
6571
+ return checks.runDocChecks(fs5, ctx);
6453
6572
  case "workspace":
6454
6573
  return checks.runWorkspaceChecks(fs5, ctx);
6455
6574
  case "ai":
@@ -6532,13 +6651,14 @@ import path3 from "path";
6532
6651
 
6533
6652
  // src/services/generate-artifacts.ts
6534
6653
  import path2 from "path";
6535
-
6536
6654
  // src/services/docs/docs-service.ts
6537
6655
  import {
6538
- defaultDocRegistry
6656
+ packageDocBlocks
6539
6657
  } from "@contractspec/lib.contracts-spec/docs";
6540
6658
  import {
6659
+ buildPackageDocManifest,
6541
6660
  convertSpecToDocBlock,
6661
+ extractModuleDocData,
6542
6662
  loadSpecFromSource,
6543
6663
  scanAllSpecsFromSource as scanAllSpecsFromSource2,
6544
6664
  scanSpecSource as scanSpecSource3
@@ -6628,6 +6748,14 @@ async function generateDocsFromSpecs(specFiles, options, adapters) {
6628
6748
  } catch (_err) {}
6629
6749
  }
6630
6750
  resolver.initialize(scanResults);
6751
+ const authoredEntries = await loadAuthoredDocEntriesFromSourceFiles(specFiles, adapters);
6752
+ for (const authoredEntry of authoredEntries) {
6753
+ blocks.push(authoredEntry.entry.block);
6754
+ logger3.debug(`Loaded authored doc ${authoredEntry.entry.id}`);
6755
+ if (options.outputDir) {
6756
+ await writeDocBlockFile(authoredEntry.entry.block, authoredEntry.filePath, options.outputDir, resolver, fs5);
6757
+ }
6758
+ }
6631
6759
  for (const file of specFiles) {
6632
6760
  try {
6633
6761
  const parsedList = await loadSpecFromSource(file);
@@ -6639,26 +6767,12 @@ async function generateDocsFromSpecs(specFiles, options, adapters) {
6639
6767
  const block = convertSpecToDocBlock(parsed, {
6640
6768
  rootPath: options.rootPath
6641
6769
  });
6642
- defaultDocRegistry.register(block);
6643
6770
  blocks.push(block);
6644
6771
  logger3.debug(`Generated doc for ${block.id}`);
6645
6772
  if (!options.outputDir) {
6646
6773
  continue;
6647
6774
  }
6648
- const moduleDef = resolver.resolve(file);
6649
- let targetDir = options.outputDir;
6650
- if (moduleDef) {
6651
- targetDir = path.join(options.outputDir, moduleDef.key);
6652
- } else {
6653
- targetDir = path.join(options.outputDir, "_common");
6654
- }
6655
- await fs5.mkdir(targetDir);
6656
- const filename = `${block.id}.md`;
6657
- const filePath = path.join(targetDir, filename);
6658
- const generatedContent = `<!-- @generated - This file was generated by ContractSpec. Do not edit manually. -->
6659
-
6660
- ${block.body}`;
6661
- await fs5.writeFile(filePath, generatedContent);
6775
+ await writeDocBlockFile(block, file, options.outputDir, resolver, fs5);
6662
6776
  }
6663
6777
  } catch (error) {
6664
6778
  logger3.error(`Error processing ${file}: ${error instanceof Error ? error.message : String(error)}`);
@@ -6669,6 +6783,36 @@ ${block.body}`;
6669
6783
  }
6670
6784
  return { blocks, count: blocks.length };
6671
6785
  }
6786
+ async function loadAuthoredDocBlocksFromSourceFiles(sourceFiles, adapters) {
6787
+ const entries = await loadAuthoredDocEntriesFromSourceFiles(sourceFiles, adapters);
6788
+ return entries.map(({ entry }) => entry.block);
6789
+ }
6790
+ function loadPackageAuthoredDocBlocks(packageName, srcRoot) {
6791
+ return packageDocBlocks(buildPackageDocManifest({ packageName, srcRoot }));
6792
+ }
6793
+ async function loadAuthoredDocEntriesFromSourceFiles(sourceFiles, adapters) {
6794
+ const uniqueFiles = [...new Set(sourceFiles)].sort((left, right) => left.localeCompare(right));
6795
+ const entries = [];
6796
+ for (const filePath of uniqueFiles) {
6797
+ const sourceText = await adapters.fs.readFile(filePath);
6798
+ const sourceModule = filePath.replace(/\\/g, "/").replace(/\.[cm]?[jt]sx?$/, "");
6799
+ const moduleData = extractModuleDocData(sourceText, filePath, sourceModule);
6800
+ for (const entry of moduleData.entries) {
6801
+ entries.push({ entry, filePath });
6802
+ }
6803
+ }
6804
+ return entries;
6805
+ }
6806
+ async function writeDocBlockFile(block, file, outputDir, resolver, fs5) {
6807
+ const moduleDef = resolver.resolve(file);
6808
+ const targetDir = moduleDef ? path.join(outputDir, moduleDef.key) : path.join(outputDir, "_common");
6809
+ await fs5.mkdir(targetDir);
6810
+ const filePath = path.join(targetDir, `${block.id}.md`);
6811
+ const generatedContent = `<!-- @generated - This file was generated by ContractSpec. Do not edit manually. -->
6812
+
6813
+ ${block.body}`;
6814
+ await fs5.writeFile(filePath, generatedContent);
6815
+ }
6672
6816
  // src/services/generate-artifacts.ts
6673
6817
  async function generateArtifacts(adapters, contractsDir, generatedDir, rootPath) {
6674
6818
  if (!await adapters.fs.exists(contractsDir)) {}
@@ -7925,7 +8069,12 @@ import {
7925
8069
  DEFAULT_CONTRACTSRC as DEFAULT_CONTRACTSRC3
7926
8070
  } from "@contractspec/lib.contracts-spec/workspace-config";
7927
8071
  var IMPLEMENTATION_PATH_PATTERN = /(^|\/)(handlers?|routes?|controllers?|api)(\/|$)|\.(handler|handlers|route|routes|controller)\.(ts|tsx)$/i;
7928
- var CONTRACT_REFERENCE_PATTERN = /@contractspec\/lib\.contracts(?:-spec|-integrations)?|define(Command|Query|Event|Feature|Presentation|Capability|Form|DataView|Integration)|OperationSpecRegistry|ContractHandler|installOp|contracts\//;
8072
+ var CONTRACT_REFERENCE_PATTERN = /@contractspec\/lib\.contracts(?:-spec|-integrations)?|define(Command|Query|Event|Feature|Presentation|Capability|Form|DataView|Integration)|OperationSpecRegistry|ContractHandler|installOp|contracts\b|['"][^'"]+\.(operation|event|presentation|feature|capability|form|test-spec)(?:\.[tj]sx?)?['"]/;
8073
+ var CONTRACT_CONTEXT_PATTERN = /@contractspec\/(?:lib\.contracts(?:-spec|-integrations)?|module\.ai-chat|bundle\.library\/application\/mcp|example\.)|['"][^'"]+\.(operation|event|presentation|feature|capability|form|test-spec)(?:\.[tj]sx?)?['"]/;
8074
+ var SUPPORT_FILE_PATTERN = /(^|\/)(index|types)\.ts$|\.types\.ts$|\.storage\.ts$|(?:^|\/)[^/]*\.(resolver|scheduler)\.ts$|(?:^|\/)[^/]*(factory|resources|mock-data)\.ts$/i;
8075
+ var FIXTURE_PATH_PATTERN = /(^|\/)(__fixtures__|fixtures)(\/|$)/i;
8076
+ var WORKSPACE_PACKAGE_ROOT_PATTERN = /^(.*\/packages\/(?:apps|apps-registry|bundles|examples|integrations|libs|modules|tools)\/[^/]+)(?:\/|$)/i;
8077
+ var PACKAGE_ROOT_POLICY_CONTEXT_PATTERN = /\/packages\/(?:apps|apps-registry|bundles|modules)\//i;
7929
8078
  function splitConventionPath(value) {
7930
8079
  if (!value) {
7931
8080
  return [];
@@ -7949,7 +8098,7 @@ function isPolicyCandidate(filePath, specFiles, config) {
7949
8098
  if (!/\.(ts|tsx)$/.test(normalized)) {
7950
8099
  return false;
7951
8100
  }
7952
- if (normalized.includes("/node_modules/") || normalized.includes("/dist/") || normalized.endsWith(".d.ts") || normalized.endsWith(".test.ts") || normalized.endsWith(".spec.ts")) {
8101
+ if (normalized.includes("/node_modules/") || normalized.includes("/dist/") || FIXTURE_PATH_PATTERN.test(normalized) || SUPPORT_FILE_PATTERN.test(normalized) || normalized.endsWith(".d.ts") || normalized.endsWith(".test.ts") || normalized.endsWith(".spec.ts")) {
7953
8102
  return false;
7954
8103
  }
7955
8104
  if (specFiles.has(normalized)) {
@@ -7960,6 +8109,16 @@ function isPolicyCandidate(filePath, specFiles, config) {
7960
8109
  }
7961
8110
  return IMPLEMENTATION_PATH_PATTERN.test(normalized);
7962
8111
  }
8112
+ function getWorkspacePackageRoot(filePath) {
8113
+ const normalized = filePath.replaceAll("\\", "/");
8114
+ const match = normalized.match(WORKSPACE_PACKAGE_ROOT_PATTERN);
8115
+ return match?.[1] ?? null;
8116
+ }
8117
+ function isBarrelFile(content) {
8118
+ const meaningfulLines = content.split(`
8119
+ `).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("//") && !line.startsWith("/*") && !line.startsWith("*") && !line.startsWith("*/"));
8120
+ return meaningfulLines.length > 0 && meaningfulLines.every((line) => line.startsWith("import ") || line.startsWith("export *") || line.startsWith("export {") || line.startsWith("export type {") || line === "'use client';" || line === '"use client";' || line === "'use server';" || line === '"use server";');
8121
+ }
7963
8122
  function resolvePolicyConfig(config) {
7964
8123
  if (!config) {
7965
8124
  return DEFAULT_CONTRACTSRC3;
@@ -7985,6 +8144,7 @@ async function runPolicyChecks(adapters, options) {
7985
8144
  const config = options.config ? resolvePolicyConfig(options.config) : await loadWorkspaceConfig(fs5);
7986
8145
  const scannedSpecs = await listSpecs({ fs: fs5 }, { config });
7987
8146
  const specFiles = new Set(scannedSpecs.map((spec) => spec.filePath.replaceAll("\\", "/")));
8147
+ const packageRootsWithSpecs = new Set(scannedSpecs.map((spec) => getWorkspacePackageRoot(spec.filePath)).filter((root) => Boolean(root)));
7988
8148
  const sourceFiles = await fs5.glob({ pattern: "**/*.{ts,tsx}" });
7989
8149
  for (const file of sourceFiles) {
7990
8150
  if (!isPolicyCandidate(file, specFiles, config)) {
@@ -7992,6 +8152,14 @@ async function runPolicyChecks(adapters, options) {
7992
8152
  }
7993
8153
  try {
7994
8154
  const content = await fs5.readFile(file);
8155
+ if (isBarrelFile(content)) {
8156
+ continue;
8157
+ }
8158
+ const packageRoot = getWorkspacePackageRoot(file);
8159
+ const hasLocalSpecContext = packageRoot !== null && PACKAGE_ROOT_POLICY_CONTEXT_PATTERN.test(packageRoot) && packageRootsWithSpecs.has(packageRoot);
8160
+ if (!hasLocalSpecContext && !CONTRACT_CONTEXT_PATTERN.test(content)) {
8161
+ continue;
8162
+ }
7995
8163
  if (CONTRACT_REFERENCE_PATTERN.test(content)) {
7996
8164
  continue;
7997
8165
  }
@@ -8132,6 +8300,7 @@ function createCategorySummary(category, issues, durationMs) {
8132
8300
  integrity: "Contract Integrity Analysis",
8133
8301
  deps: "Dependency Analysis",
8134
8302
  doctor: "Installation Health",
8303
+ docs: "DocBlock Ownership",
8135
8304
  policy: "Contract Policy Enforcement",
8136
8305
  handlers: "Handler Implementation",
8137
8306
  tests: "Test Coverage",
@@ -8179,7 +8348,7 @@ async function getGitInfo(fs5) {
8179
8348
  }
8180
8349
  function getChecksToRun(options) {
8181
8350
  const configuredChecks = options.config?.ci?.checks;
8182
- const allCategories = configuredChecks && configuredChecks.length > 0 ? [...configuredChecks] : ["structure", "integrity", "deps", "doctor"];
8351
+ const allCategories = configuredChecks && configuredChecks.length > 0 ? [...configuredChecks] : ["structure", "integrity", "deps", "doctor", "docs"];
8183
8352
  if (options.checkHandlers) {
8184
8353
  allCategories.push("handlers");
8185
8354
  }
@@ -8237,6 +8406,12 @@ async function runCIChecks(adapters, options = {}) {
8237
8406
  issues.push(...doctorIssues);
8238
8407
  categorySummaries.push(createCategorySummary("doctor", doctorIssues, Date.now() - categoryStart));
8239
8408
  }
8409
+ if (checksToRun.includes("docs")) {
8410
+ const categoryStart = Date.now();
8411
+ const docsIssues = await runDocsChecks(adapters, options);
8412
+ issues.push(...docsIssues);
8413
+ categorySummaries.push(createCategorySummary("docs", docsIssues, Date.now() - categoryStart));
8414
+ }
8240
8415
  if (checksToRun.includes("policy")) {
8241
8416
  const categoryStart = Date.now();
8242
8417
  const policyIssues = await runPolicyChecks(adapters, options);
@@ -8315,6 +8490,7 @@ var ALL_CI_CHECK_CATEGORIES = [
8315
8490
  "integrity",
8316
8491
  "deps",
8317
8492
  "doctor",
8493
+ "docs",
8318
8494
  "policy",
8319
8495
  "handlers",
8320
8496
  "tests",
@@ -8329,6 +8505,7 @@ var CI_CHECK_CATEGORY_LABELS = {
8329
8505
  integrity: "Contract Integrity Analysis",
8330
8506
  deps: "Dependency Analysis",
8331
8507
  doctor: "Installation Health",
8508
+ docs: "DocBlock Ownership",
8332
8509
  policy: "Contract Policy Enforcement",
8333
8510
  handlers: "Handler Implementation",
8334
8511
  tests: "Test Coverage",
@@ -11619,7 +11796,8 @@ __export(exports_impact, {
11619
11796
  formatMinimalComment: () => formatMinimalComment,
11620
11797
  formatJson: () => formatJson2,
11621
11798
  formatCheckRun: () => formatCheckRun,
11622
- detectImpact: () => detectImpact
11799
+ detectImpact: () => detectImpact,
11800
+ ImpactDetectionOverviewDocBlock: () => ImpactDetectionOverviewDocBlock
11623
11801
  });
11624
11802
 
11625
11803
  // src/services/impact/formatters.ts
@@ -11846,6 +12024,63 @@ function computeSnapshotDiffs(baseSpecs, headSpecs) {
11846
12024
  }
11847
12025
  return diffs;
11848
12026
  }
12027
+
12028
+ // src/services/impact/index.ts
12029
+ var ImpactDetectionOverviewDocBlock = {
12030
+ id: "feature.impact-detection.overview",
12031
+ title: "Contract Impact Detection",
12032
+ kind: "goal",
12033
+ visibility: "public",
12034
+ route: "/docs/features/impact-detection",
12035
+ body: `
12036
+ # Contract Impact Detection
12037
+
12038
+ Automated detection and classification of breaking changes in ContractSpec APIs.
12039
+
12040
+ ## Features
12041
+
12042
+ - **Snapshot Generation**: Creates canonical, deterministic representations of contracts
12043
+ - **Deep Diff Engine**: Field-level comparison of input/output schemas
12044
+ - **Breaking Change Classification**: Automatic classification using 16 rules
12045
+ - **Multiple Output Formats**: JSON, Markdown, Text, GitHub Check Run
12046
+ - **GitHub Integration**: PR comments and check runs
12047
+ - **CLI Tool**: \`contractspec impact\` command
12048
+
12049
+ ## Quick Start
12050
+
12051
+ ### CLI Usage
12052
+
12053
+ \`\`\`bash
12054
+ # Basic usage
12055
+ contractspec impact
12056
+
12057
+ # Compare against specific baseline
12058
+ contractspec impact --baseline origin/main
12059
+
12060
+ # Get JSON output
12061
+ contractspec impact --format json
12062
+ \`\`\`
12063
+
12064
+ ### GitHub Action
12065
+
12066
+ \`\`\`yaml
12067
+ - uses: lssm-tech/contractspec-action@v1
12068
+ with:
12069
+ mode: impact
12070
+ pr-comment: true
12071
+ fail-on-breaking: true
12072
+ \`\`\`
12073
+
12074
+ ## Architecture
12075
+
12076
+ The system consists of three layers:
12077
+
12078
+ 1. **Analysis Modules** (module package): Snapshot, Deep Diff, Classifier
12079
+ 2. **Impact Service** (bundle package): Orchestration + Formatters
12080
+ 3. **Integrations**: CLI command + GitHub Action
12081
+ `,
12082
+ tags: ["impact-detection", "breaking-changes", "ci-cd"]
12083
+ };
11849
12084
  // src/services/import/import-service.ts
11850
12085
  import {
11851
12086
  detectFramework,
@@ -17691,6 +17926,8 @@ export {
17691
17926
  module,
17692
17927
  mergeMonorepoConfigs,
17693
17928
  loadWorkspaceConfig,
17929
+ loadPackageAuthoredDocBlocks,
17930
+ loadAuthoredDocBlocksFromSourceFiles,
17694
17931
  listTests,
17695
17932
  listSpecsForView,
17696
17933
  listSpecs,
@@ -17814,6 +18051,7 @@ export {
17814
18051
  claudeCodeAdapter,
17815
18052
  cacheKeyToString,
17816
18053
  buildSpec,
18054
+ analyzeWorkspaceDocBlocks,
17817
18055
  analyzeIntegrity,
17818
18056
  analyzeGap,
17819
18057
  analyzeDeps,
@@ -0,0 +1,7 @@
1
+ import type { FsAdapter } from '../../../ports/fs';
2
+ import type { LoggerAdapter } from '../../../ports/logger';
3
+ import type { CICheckOptions, CIIssue } from '../types';
4
+ export declare function runDocsChecks(adapters: {
5
+ fs: FsAdapter;
6
+ logger: LoggerAdapter;
7
+ }, options: CICheckOptions): Promise<CIIssue[]>;
@@ -0,0 +1 @@
1
+ export {};
@@ -3,6 +3,7 @@
3
3
  */
4
4
  export { runCoverageChecks } from './coverage';
5
5
  export { runDepsChecks } from './deps';
6
+ export { runDocsChecks } from './docs';
6
7
  export { runDoctorChecks } from './doctor';
7
8
  export { runDriftChecks } from './drift';
8
9
  export { runHandlerChecks } from './handlers';
@@ -7,7 +7,7 @@ import type { ContractsrcConfig, ResolvedContractsrcConfig } from '@contractspec
7
7
  /**
8
8
  * Categories of CI checks.
9
9
  */
10
- export type CICheckCategory = 'structure' | 'integrity' | 'deps' | 'doctor' | 'policy' | 'handlers' | 'tests' | 'test-refs' | 'coverage' | 'implementation' | 'layers' | 'drift';
10
+ export type CICheckCategory = 'structure' | 'integrity' | 'deps' | 'doctor' | 'docs' | 'policy' | 'handlers' | 'tests' | 'test-refs' | 'coverage' | 'implementation' | 'layers' | 'drift';
11
11
  /**
12
12
  * All available CI check categories.
13
13
  */
@@ -0,0 +1,8 @@
1
+ import { type DocBlockDiagnostic } from '@contractspec/module.workspace';
2
+ import type { FsAdapter } from '../../ports/fs';
3
+ export interface WorkspaceDocBlockDiagnostic extends DocBlockDiagnostic {
4
+ packageName: string;
5
+ packageRoot: string;
6
+ srcRoot: string;
7
+ }
8
+ export declare function analyzeWorkspaceDocBlocks(fs: FsAdapter, workspaceRoot: string): Promise<WorkspaceDocBlockDiagnostic[]>;
@@ -13,3 +13,5 @@ export interface DocsServiceResult {
13
13
  * Generate documentation from spec files.
14
14
  */
15
15
  export declare function generateDocsFromSpecs(specFiles: string[], options: DocsServiceOptions, adapters: WorkspaceAdapters): Promise<DocsServiceResult>;
16
+ export declare function loadAuthoredDocBlocksFromSourceFiles(sourceFiles: string[], adapters: Pick<WorkspaceAdapters, 'fs'>): Promise<DocBlock[]>;
17
+ export declare function loadPackageAuthoredDocBlocks(packageName: string, srcRoot: string): DocBlock[];
@@ -0,0 +1 @@
1
+ export {};
@@ -1 +1,2 @@
1
+ export * from './docblock-audit';
1
2
  export * from './docs-service';
@@ -0,0 +1,3 @@
1
+ import type { FsAdapter } from '../../../ports/fs';
2
+ import type { CheckContext, CheckResult } from '../types';
3
+ export declare function runDocChecks(fs: FsAdapter, ctx: CheckContext): Promise<CheckResult[]>;
@@ -0,0 +1 @@
1
+ export {};
@@ -5,6 +5,7 @@ export { runAiChecks } from './ai';
5
5
  export { runCliChecks } from './cli';
6
6
  export { runConfigChecks } from './config';
7
7
  export { runDepsChecks } from './deps';
8
+ export { runDocChecks } from './docs';
8
9
  export { runLayerChecks } from './layers';
9
10
  export { runMcpChecks } from './mcp';
10
11
  export { runWorkspaceChecks } from './workspace';
@@ -6,13 +6,14 @@
6
6
  import { findPackageRoot, findWorkspaceRoot, getPackageName, isMonorepo } from '../../adapters/workspace';
7
7
  import type { FsAdapter } from '../../ports/fs';
8
8
  import type { LoggerAdapter } from '../../ports/logger';
9
- import { runAiChecks, runCliChecks, runConfigChecks, runDepsChecks, runLayerChecks, runMcpChecks, runWorkspaceChecks } from './checks/index';
9
+ import { runAiChecks, runCliChecks, runConfigChecks, runDepsChecks, runDocChecks, runLayerChecks, runMcpChecks, runWorkspaceChecks } from './checks/index';
10
10
  import type { CheckResult, DoctorOptions, DoctorPromptCallbacks, DoctorResult } from './types';
11
11
  interface DoctorCheckRunners {
12
12
  runCliChecks: typeof runCliChecks;
13
13
  runConfigChecks: typeof runConfigChecks;
14
14
  runMcpChecks: typeof runMcpChecks;
15
15
  runDepsChecks: typeof runDepsChecks;
16
+ runDocChecks: typeof runDocChecks;
16
17
  runWorkspaceChecks: typeof runWorkspaceChecks;
17
18
  runAiChecks: typeof runAiChecks;
18
19
  runLayerChecks: typeof runLayerChecks;
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Categories of health checks.
8
8
  */
9
- export type CheckCategory = 'cli' | 'config' | 'mcp' | 'deps' | 'workspace' | 'ai' | 'layers';
9
+ export type CheckCategory = 'cli' | 'config' | 'mcp' | 'deps' | 'docs' | 'workspace' | 'ai' | 'layers';
10
10
  /**
11
11
  * All available check categories.
12
12
  */
@@ -1,13 +1,16 @@
1
- /**
2
- * Impact detection service.
3
- *
4
- * Orchestrates contract snapshot generation, diff computation,
5
- * and impact classification for CI/CD pipelines.
6
- */
7
1
  import type { FsAdapter } from '../../ports/fs';
8
2
  import type { GitAdapter } from '../../ports/git';
9
3
  import type { LoggerAdapter } from '../../ports/logger';
10
4
  import type { ImpactDetectionOptions, ImpactDetectionResult } from './types';
5
+ export declare const ImpactDetectionRulesDocBlock: {
6
+ id: string;
7
+ title: string;
8
+ kind: "reference";
9
+ visibility: "public";
10
+ route: string;
11
+ body: string;
12
+ tags: string[];
13
+ };
11
14
  /**
12
15
  * Detect the impact of contract changes between baseline and current state.
13
16
  *
@@ -1,6 +1,12 @@
1
- /**
2
- * Impact detection service module.
3
- */
1
+ export declare const ImpactDetectionOverviewDocBlock: {
2
+ id: string;
3
+ title: string;
4
+ kind: "goal";
5
+ visibility: "public";
6
+ route: string;
7
+ body: string;
8
+ tags: string[];
9
+ };
4
10
  export { formatCheckRun, formatJson, formatMinimalComment, formatPrComment, } from './formatters';
5
11
  export { detectImpact } from './impact-detection-service';
6
12
  export * from './types';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contractspec/bundle.workspace",
3
- "version": "4.0.0",
3
+ "version": "4.1.0",
4
4
  "description": "Workspace utilities for monorepo development",
5
5
  "keywords": [
6
6
  "contractspec",
@@ -33,15 +33,15 @@
33
33
  "dependencies": {
34
34
  "@ai-sdk/anthropic": "3.0.58",
35
35
  "@ai-sdk/openai": "3.0.41",
36
- "@contractspec/biome-config": "3.8.0",
37
- "@contractspec/lib.ai-agent": "7.0.7",
38
- "@contractspec/lib.ai-providers": "3.7.6",
39
- "@contractspec/lib.contracts-spec": "4.0.0",
40
- "@contractspec/lib.contracts-integrations": "3.7.7",
41
- "@contractspec/lib.contracts-transformers": "3.7.7",
42
- "@contractspec/lib.source-extractors": "2.7.7",
43
- "@contractspec/module.workspace": "4.0.0",
44
- "@contractspec/lib.utils-typescript": "3.7.6",
36
+ "@contractspec/biome-config": "3.8.3",
37
+ "@contractspec/lib.ai-agent": "8.0.0",
38
+ "@contractspec/lib.ai-providers": "3.7.9",
39
+ "@contractspec/lib.contracts-spec": "5.0.0",
40
+ "@contractspec/lib.contracts-integrations": "3.8.4",
41
+ "@contractspec/lib.contracts-transformers": "3.7.12",
42
+ "@contractspec/lib.source-extractors": "2.7.12",
43
+ "@contractspec/module.workspace": "4.1.0",
44
+ "@contractspec/lib.utils-typescript": "3.7.9",
45
45
  "ai": "6.0.116",
46
46
  "chalk": "^5.6.2",
47
47
  "chokidar": "^5.0.0",
@@ -54,12 +54,12 @@
54
54
  "zod": "^4.3.5"
55
55
  },
56
56
  "devDependencies": {
57
- "@contractspec/tool.typescript": "3.7.6",
57
+ "@contractspec/tool.typescript": "3.7.9",
58
58
  "@types/bun": "^1.3.11",
59
59
  "@types/micromatch": "^4.0.10",
60
60
  "@types/node": "^25.3.5",
61
61
  "typescript": "^5.9.3",
62
- "@contractspec/tool.bun": "3.7.6"
62
+ "@contractspec/tool.bun": "3.7.9"
63
63
  },
64
64
  "exports": {
65
65
  ".": {
@@ -1,9 +0,0 @@
1
- /**
2
- * DocBlock for Contract Impact Detection feature.
3
- *
4
- * Colocated documentation following the DocBlock pattern.
5
- */
6
- import type { DocBlock } from '@contractspec/lib.contracts-spec/docs/types';
7
- export declare const ImpactDetectionOverviewDoc: DocBlock;
8
- export declare const ImpactDetectionRulesDoc: DocBlock;
9
- export declare const ImpactDetectionUsageDoc: DocBlock;