@contractspec/bundle.workspace 3.7.1 → 3.7.3

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.
@@ -26,7 +26,17 @@ export interface JsonFormatOptions {
26
26
  * CI JSON output structure (v1.0).
27
27
  */
28
28
  export interface CiJsonOutput extends BaseJsonOutput {
29
+ success: boolean;
29
30
  checks: CiCheckJson[];
31
+ categories: {
32
+ category: string;
33
+ label: string;
34
+ passed: boolean;
35
+ errors: number;
36
+ warnings: number;
37
+ notes: number;
38
+ durationMs: number;
39
+ }[];
30
40
  drift: {
31
41
  status: 'none' | 'detected';
32
42
  files: string[];
@@ -35,7 +45,11 @@ export interface CiJsonOutput extends BaseJsonOutput {
35
45
  pass: number;
36
46
  fail: number;
37
47
  warn: number;
48
+ note: number;
38
49
  total: number;
50
+ totalErrors: number;
51
+ totalWarnings: number;
52
+ totalNotes: number;
39
53
  durationMs: number;
40
54
  timestamp: string;
41
55
  };
package/dist/index.js CHANGED
@@ -1,12 +1,16 @@
1
1
  // @bun
2
2
  var __defProp = Object.defineProperty;
3
+ var __returnValue = (v) => v;
4
+ function __exportSetter(name, newValue) {
5
+ this[name] = __returnValue.bind(null, newValue);
6
+ }
3
7
  var __export = (target, all) => {
4
8
  for (var name in all)
5
9
  __defProp(target, name, {
6
10
  get: all[name],
7
11
  enumerable: true,
8
12
  configurable: true,
9
- set: (newValue) => all[name] = () => newValue
13
+ set: __exportSetter.bind(all, name)
10
14
  });
11
15
  };
12
16
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -244,6 +248,8 @@ import { glob as globFn } from "glob";
244
248
 
245
249
  // src/adapters/fs.ts
246
250
  var DEFAULT_SPEC_PATTERNS = [
251
+ "**/*.command.ts",
252
+ "**/*.query.ts",
247
253
  "**/*.operation.ts",
248
254
  "**/*.operations.ts",
249
255
  "**/*.event.ts",
@@ -263,6 +269,10 @@ var DEFAULT_SPEC_PATTERNS = [
263
269
  "**/*.test-spec.ts",
264
270
  "**/contracts/*.ts",
265
271
  "**/contracts/index.ts",
272
+ "**/commands/*.ts",
273
+ "**/commands/index.ts",
274
+ "**/queries/*.ts",
275
+ "**/queries/index.ts",
266
276
  "**/operations/*.ts",
267
277
  "**/operations/index.ts",
268
278
  "**/operations.ts",
@@ -3088,9 +3098,7 @@ async function analyzeDeps(adapters, options = {}) {
3088
3098
  for (const file of files) {
3089
3099
  const content = await fs5.readFile(file);
3090
3100
  const relativePath = fs5.relative(".", file);
3091
- const nameMatch = content.match(/name:\s*['"]([^'"]+)['"]/);
3092
- const inferredName = nameMatch?.[1] ? nameMatch[1] : fs5.basename(file).replace(/\.[jt]s$/, "").replace(/\.(contracts|contract|operation|operations|event|presentation|workflow|data-view|migration|telemetry|experiment|app-config|integration|knowledge)$/, "");
3093
- const finalName = inferredName || "unknown";
3101
+ const finalName = fs5.basename(file).replace(/\.[jt]s$/, "").replace(/\.(contracts|contract|command|query|operation|operations|event|presentation|workflow|data-view|migration|telemetry|experiment|app-config|integration|knowledge)$/, "") || "unknown";
3094
3102
  const dependencies = parseImportedSpecNames(content, file);
3095
3103
  addContractNode(graph, finalName, relativePath, dependencies);
3096
3104
  }
@@ -6670,6 +6678,271 @@ async function checkContractsLibrary(fs5, ctx) {
6670
6678
  };
6671
6679
  }
6672
6680
  }
6681
+ // src/services/stability/policy.ts
6682
+ var STABILITY_POLICY_PATH = "config/stability-policy.json";
6683
+ function normalizePath(value) {
6684
+ return value.replace(/\\/g, "/").replace(/\/+$/, "");
6685
+ }
6686
+ function toStringArray(value) {
6687
+ if (!Array.isArray(value))
6688
+ return [];
6689
+ return value.filter((entry) => typeof entry === "string").map((entry) => entry.trim()).filter(Boolean);
6690
+ }
6691
+ async function loadStabilityPolicy(fs5, workspaceRoot) {
6692
+ const policyPath = fs5.join(workspaceRoot, STABILITY_POLICY_PATH);
6693
+ if (!await fs5.exists(policyPath)) {
6694
+ return;
6695
+ }
6696
+ try {
6697
+ const content = await fs5.readFile(policyPath);
6698
+ const parsed = JSON.parse(content);
6699
+ return {
6700
+ version: typeof parsed.version === "number" && Number.isFinite(parsed.version) ? parsed.version : 1,
6701
+ criticalPackages: toStringArray(parsed.criticalPackages).map(normalizePath),
6702
+ criticalFeatureKeys: toStringArray(parsed.criticalFeatureKeys),
6703
+ smokePackages: toStringArray(parsed.smokePackages)
6704
+ };
6705
+ } catch {
6706
+ return;
6707
+ }
6708
+ }
6709
+ function getPackageTier(relativePackagePath, policy) {
6710
+ const normalizedPath = normalizePath(relativePackagePath);
6711
+ if (policy?.criticalPackages.includes(normalizedPath)) {
6712
+ return "critical";
6713
+ }
6714
+ return "non-critical";
6715
+ }
6716
+ function isCriticalFeatureKey(featureKey, policy) {
6717
+ return Boolean(featureKey && policy?.criticalFeatureKeys.includes(featureKey));
6718
+ }
6719
+ function getStabilityPolicyPath(workspaceRoot) {
6720
+ return normalizePath(`${normalizePath(workspaceRoot)}/${STABILITY_POLICY_PATH}`);
6721
+ }
6722
+
6723
+ // src/services/stability/package-audit.ts
6724
+ var TEST_FILE_PATTERNS = ["**/*.{test,spec}.{ts,tsx,js,jsx,mts,cts}"];
6725
+ var TEST_IGNORES = [
6726
+ "**/node_modules/**",
6727
+ "**/dist/**",
6728
+ "**/.next/**",
6729
+ "**/.turbo/**",
6730
+ "**/coverage/**"
6731
+ ];
6732
+ function toRecord(value) {
6733
+ if (!value || typeof value !== "object") {
6734
+ return {};
6735
+ }
6736
+ return Object.entries(value).reduce((acc, [key, entry]) => {
6737
+ if (typeof entry === "string") {
6738
+ acc[key] = entry;
6739
+ }
6740
+ return acc;
6741
+ }, {});
6742
+ }
6743
+ function usesPassWithNoTests(scripts) {
6744
+ return Object.entries(scripts).some(([name, command]) => {
6745
+ return name.startsWith("test") && /pass[- ]with[- ]no[- ]tests/i.test(command);
6746
+ });
6747
+ }
6748
+ async function discoverPackages(fs5, workspaceRoot, policy) {
6749
+ const packageJsonFiles = await fs5.glob({
6750
+ pattern: "packages/**/package.json",
6751
+ cwd: workspaceRoot,
6752
+ ignore: TEST_IGNORES
6753
+ });
6754
+ const descriptors = [];
6755
+ for (const packageJsonFile of packageJsonFiles) {
6756
+ const packagePath = fs5.dirname(packageJsonFile);
6757
+ const relativePackagePath = fs5.relative(workspaceRoot, packagePath);
6758
+ try {
6759
+ const packageJson = JSON.parse(await fs5.readFile(packageJsonFile));
6760
+ const scripts = toRecord(packageJson.scripts);
6761
+ const testFiles = await fs5.glob({
6762
+ patterns: TEST_FILE_PATTERNS,
6763
+ cwd: packagePath,
6764
+ ignore: TEST_IGNORES
6765
+ });
6766
+ descriptors.push({
6767
+ packageName: packageJson.name ?? relativePackagePath,
6768
+ packagePath: relativePackagePath.replace(/\\/g, "/"),
6769
+ hasBuildScript: typeof scripts.build === "string",
6770
+ hasTypecheckScript: typeof scripts.typecheck === "string",
6771
+ hasLintScript: typeof scripts.lint === "string" || typeof scripts["lint:check"] === "string",
6772
+ hasTestScript: typeof scripts.test === "string",
6773
+ usesPassWithNoTests: usesPassWithNoTests(scripts),
6774
+ testFileCount: testFiles.length,
6775
+ tier: getPackageTier(relativePackagePath, policy)
6776
+ });
6777
+ } catch {
6778
+ continue;
6779
+ }
6780
+ }
6781
+ return descriptors;
6782
+ }
6783
+ function createCriticalFindings(descriptor) {
6784
+ const findings = [];
6785
+ if (!descriptor.hasBuildScript) {
6786
+ findings.push({
6787
+ code: "critical-missing-build-script",
6788
+ tier: descriptor.tier,
6789
+ packageName: descriptor.packageName,
6790
+ packagePath: descriptor.packagePath,
6791
+ message: "Missing build script"
6792
+ });
6793
+ }
6794
+ if (!descriptor.hasTypecheckScript) {
6795
+ findings.push({
6796
+ code: "critical-missing-typecheck-script",
6797
+ tier: descriptor.tier,
6798
+ packageName: descriptor.packageName,
6799
+ packagePath: descriptor.packagePath,
6800
+ message: "Missing typecheck script"
6801
+ });
6802
+ }
6803
+ if (!descriptor.hasLintScript) {
6804
+ findings.push({
6805
+ code: "critical-missing-lint-script",
6806
+ tier: descriptor.tier,
6807
+ packageName: descriptor.packageName,
6808
+ packagePath: descriptor.packagePath,
6809
+ message: "Missing lint or lint:check script"
6810
+ });
6811
+ }
6812
+ if (!descriptor.hasTestScript) {
6813
+ findings.push({
6814
+ code: "critical-missing-test-script",
6815
+ tier: descriptor.tier,
6816
+ packageName: descriptor.packageName,
6817
+ packagePath: descriptor.packagePath,
6818
+ message: "Missing test script"
6819
+ });
6820
+ }
6821
+ if (descriptor.testFileCount === 0) {
6822
+ findings.push({
6823
+ code: "critical-missing-test-files",
6824
+ tier: descriptor.tier,
6825
+ packageName: descriptor.packageName,
6826
+ packagePath: descriptor.packagePath,
6827
+ message: "No real test files found"
6828
+ });
6829
+ }
6830
+ if (descriptor.usesPassWithNoTests) {
6831
+ findings.push({
6832
+ code: "critical-pass-with-no-tests",
6833
+ tier: descriptor.tier,
6834
+ packageName: descriptor.packageName,
6835
+ packagePath: descriptor.packagePath,
6836
+ message: "Uses pass-with-no-tests in a critical package"
6837
+ });
6838
+ }
6839
+ return findings;
6840
+ }
6841
+ function createGeneralFindings(descriptor) {
6842
+ const findings = [];
6843
+ if (descriptor.testFileCount > 0 && !descriptor.hasTestScript) {
6844
+ findings.push({
6845
+ code: "tests-without-test-script",
6846
+ tier: descriptor.tier,
6847
+ packageName: descriptor.packageName,
6848
+ packagePath: descriptor.packagePath,
6849
+ message: "Has test files on disk but no test script"
6850
+ });
6851
+ }
6852
+ if (descriptor.hasBuildScript && descriptor.testFileCount === 0 && !descriptor.hasTestScript) {
6853
+ findings.push({
6854
+ code: "build-without-tests",
6855
+ tier: descriptor.tier,
6856
+ packageName: descriptor.packageName,
6857
+ packagePath: descriptor.packagePath,
6858
+ message: "Has a build script but no test script or test files"
6859
+ });
6860
+ }
6861
+ return findings;
6862
+ }
6863
+ async function auditWorkspacePackages(fs5, workspaceRoot, policy) {
6864
+ const packages = await discoverPackages(fs5, workspaceRoot, policy);
6865
+ const findings = [];
6866
+ for (const descriptor of packages) {
6867
+ if (descriptor.tier === "critical") {
6868
+ findings.push(...createCriticalFindings(descriptor));
6869
+ }
6870
+ findings.push(...createGeneralFindings(descriptor));
6871
+ }
6872
+ const criticalPackages = packages.filter((descriptor) => descriptor.tier === "critical").map((descriptor) => ({
6873
+ packageName: descriptor.packageName,
6874
+ packagePath: descriptor.packagePath,
6875
+ hasBuildScript: descriptor.hasBuildScript,
6876
+ hasTypecheckScript: descriptor.hasTypecheckScript,
6877
+ hasLintScript: descriptor.hasLintScript,
6878
+ hasTestScript: descriptor.hasTestScript,
6879
+ usesPassWithNoTests: descriptor.usesPassWithNoTests,
6880
+ testFileCount: descriptor.testFileCount
6881
+ }));
6882
+ return { findings, criticalPackages };
6883
+ }
6884
+
6885
+ // src/services/doctor/checks/package-stability.ts
6886
+ function summarizeFindings(findings) {
6887
+ return findings.slice(0, 5).map((finding) => `${finding.packagePath}: ${finding.message}`).join("; ");
6888
+ }
6889
+ function createCheckResult(name, findings, failureCodes, successMessage) {
6890
+ if (findings.length === 0) {
6891
+ return {
6892
+ category: "workspace",
6893
+ name,
6894
+ status: "pass",
6895
+ message: successMessage,
6896
+ context: { findings: [] }
6897
+ };
6898
+ }
6899
+ const failingFindings = findings.filter((finding) => {
6900
+ return finding.tier === "critical" || failureCodes.has(finding.code);
6901
+ });
6902
+ return {
6903
+ category: "workspace",
6904
+ name,
6905
+ status: failingFindings.length > 0 ? "fail" : "warn",
6906
+ message: `${findings.length} package issue(s) found`,
6907
+ details: summarizeFindings(findings),
6908
+ context: { findings }
6909
+ };
6910
+ }
6911
+ async function runPackageStabilityChecks(fs5, ctx) {
6912
+ const policy = await loadStabilityPolicy(fs5, ctx.workspaceRoot);
6913
+ if (!policy) {
6914
+ return [];
6915
+ }
6916
+ const report = await auditWorkspacePackages(fs5, ctx.workspaceRoot, policy);
6917
+ const criticalPackageCodes = new Set([
6918
+ "critical-missing-build-script",
6919
+ "critical-missing-typecheck-script",
6920
+ "critical-missing-lint-script",
6921
+ "critical-missing-test-script",
6922
+ "critical-missing-test-files",
6923
+ "critical-pass-with-no-tests"
6924
+ ]);
6925
+ const qualityGateFindings = report.findings.filter((finding) => criticalPackageCodes.has(finding.code));
6926
+ const testsWithoutScript = report.findings.filter((finding) => finding.code === "tests-without-test-script");
6927
+ const buildWithoutTests = report.findings.filter((finding) => finding.code === "build-without-tests");
6928
+ return [
6929
+ {
6930
+ category: "workspace",
6931
+ name: "Critical Package Gates",
6932
+ status: qualityGateFindings.length > 0 ? "fail" : "pass",
6933
+ message: qualityGateFindings.length > 0 ? `${qualityGateFindings.length} critical package gate failure(s)` : `All ${report.criticalPackages.length} critical packages meet build, lint, typecheck, and test requirements`,
6934
+ details: qualityGateFindings.length > 0 ? summarizeFindings(qualityGateFindings) : ctx.verbose ? `Policy: ${getStabilityPolicyPath(ctx.workspaceRoot)}` : undefined,
6935
+ context: {
6936
+ policyPath: getStabilityPolicyPath(ctx.workspaceRoot),
6937
+ criticalPackages: report.criticalPackages,
6938
+ findings: qualityGateFindings
6939
+ }
6940
+ },
6941
+ createCheckResult("Package Test Scripts", testsWithoutScript, new Set, "All tested packages expose a test script"),
6942
+ createCheckResult("Buildable Packages Without Tests", buildWithoutTests, new Set, "All buildable packages have tests or explicit test scripts")
6943
+ ];
6944
+ }
6945
+
6673
6946
  // src/services/doctor/checks/workspace.ts
6674
6947
  var CONTRACT_PATHS = ["src/contracts", "contracts", "src/specs", "specs"];
6675
6948
  async function runWorkspaceChecks(fs5, ctx) {
@@ -6679,6 +6952,7 @@ async function runWorkspaceChecks(fs5, ctx) {
6679
6952
  results.push(await checkContractsDirectory(fs5, ctx));
6680
6953
  results.push(await checkContractFiles(fs5, ctx));
6681
6954
  results.push(await checkOutputDirectory(fs5, ctx));
6955
+ results.push(...await runPackageStabilityChecks(fs5, ctx));
6682
6956
  return results;
6683
6957
  }
6684
6958
  function checkMonorepoStatus(ctx) {
@@ -6860,12 +7134,15 @@ async function checkOutputDirectory(fs5, ctx) {
6860
7134
  details: ctx.verbose ? `Resolved to: ${outputPath}` : undefined
6861
7135
  };
6862
7136
  }
6863
- if (ctx.isMonorepo && ctx.packageRoot === ctx.workspaceRoot && !config.outputDir) {
7137
+ const isDefaultWorkspaceOutput = outputDir === "./src" || outputDir === "src";
7138
+ const usesPackageScopedConfig = Array.isArray(config.packages) && config.packages.length > 0;
7139
+ if (ctx.isMonorepo && ctx.packageRoot === ctx.workspaceRoot && isDefaultWorkspaceOutput && (usesPackageScopedConfig || configInfo.level === "workspace")) {
6864
7140
  return {
6865
7141
  category: "workspace",
6866
7142
  name: "Output Directory",
6867
7143
  status: "pass",
6868
- message: "Monorepo root detected (using package directories)"
7144
+ message: "Monorepo root detected (using package directories)",
7145
+ details: ctx.verbose ? `Resolved default output to packages via ${configInfo.path}` : undefined
6869
7146
  };
6870
7147
  }
6871
7148
  return {
@@ -7018,8 +7295,9 @@ async function checkApiKey(fs5, ctx, prompts) {
7018
7295
  }
7019
7296
  }
7020
7297
  // src/services/doctor/checks/layers.ts
7021
- async function runLayerChecks(fs5, _ctx) {
7298
+ async function runLayerChecks(fs5, ctx) {
7022
7299
  const results = [];
7300
+ const policy = await loadStabilityPolicy(fs5, ctx.workspaceRoot);
7023
7301
  const discovery = await discoverLayers({
7024
7302
  fs: fs5,
7025
7303
  logger: {
@@ -7040,7 +7318,7 @@ async function runLayerChecks(fs5, _ctx) {
7040
7318
  }, {});
7041
7319
  results.push(checkHasFeatures(discovery.stats.features));
7042
7320
  results.push(checkHasExamples(discovery.stats.examples));
7043
- results.push(checkFeatureOwners(discovery.inventory.features));
7321
+ results.push(checkFeatureOwners(discovery.inventory.features, policy, ctx));
7044
7322
  results.push(checkExampleEntrypoints(discovery.inventory.examples));
7045
7323
  results.push(checkWorkspaceConfigs(discovery.inventory.workspaceConfigs));
7046
7324
  return results;
@@ -7079,27 +7357,43 @@ function checkHasExamples(count) {
7079
7357
  details: "Create an example.ts file to package reusable templates"
7080
7358
  };
7081
7359
  }
7082
- function checkFeatureOwners(features) {
7360
+ function checkFeatureOwners(features, policy, ctx) {
7083
7361
  const missingOwners = [];
7362
+ const criticalMissingOwners = [];
7084
7363
  for (const [key, feature] of features) {
7085
- if (!feature.owners?.length) {
7086
- missingOwners.push(key);
7364
+ const hasOwnerMetadata = Boolean(feature.owners?.length) || /owners\s*:\s*(?!\[\s*\])/.test(feature.sourceBlock ?? "");
7365
+ if (!hasOwnerMetadata) {
7366
+ if (isCriticalFeatureKey(key, policy)) {
7367
+ criticalMissingOwners.push(key);
7368
+ } else {
7369
+ missingOwners.push(key);
7370
+ }
7087
7371
  }
7088
7372
  }
7089
- if (missingOwners.length === 0) {
7373
+ if (criticalMissingOwners.length === 0 && missingOwners.length === 0) {
7090
7374
  return {
7091
7375
  category: "layers",
7092
7376
  name: "Feature Owners",
7093
7377
  status: features.size > 0 ? "pass" : "skip",
7094
- message: features.size > 0 ? "All features have owners defined" : "No features to check"
7378
+ message: features.size > 0 ? "All features have owners defined" : "No features to check",
7379
+ context: features.size > 0 ? {
7380
+ policyPath: policy ? getStabilityPolicyPath(ctx.workspaceRoot) : undefined,
7381
+ criticalMissingFeatures: [],
7382
+ missingFeatures: []
7383
+ } : undefined
7095
7384
  };
7096
7385
  }
7097
7386
  return {
7098
7387
  category: "layers",
7099
7388
  name: "Feature Owners",
7100
- status: "warn",
7101
- message: `${missingOwners.length} feature(s) missing owners`,
7102
- details: `Features: ${missingOwners.slice(0, 3).join(", ")}${missingOwners.length > 3 ? "..." : ""}`
7389
+ status: criticalMissingOwners.length > 0 ? "fail" : "warn",
7390
+ message: criticalMissingOwners.length > 0 ? `${criticalMissingOwners.length} critical feature(s) missing owners` : `${missingOwners.length} feature(s) missing owners`,
7391
+ details: criticalMissingOwners.length > 0 ? `Critical features: ${criticalMissingOwners.join(", ")}` : `Features: ${missingOwners.slice(0, 3).join(", ")}${missingOwners.length > 3 ? "..." : ""}`,
7392
+ context: {
7393
+ policyPath: policy ? getStabilityPolicyPath(ctx.workspaceRoot) : undefined,
7394
+ criticalMissingFeatures: criticalMissingOwners,
7395
+ missingFeatures: missingOwners
7396
+ }
7103
7397
  };
7104
7398
  }
7105
7399
  function checkExampleEntrypoints(examples) {
@@ -7636,7 +7930,7 @@ async function runDoctorChecks(adapters, options) {
7636
7930
  const result = await runDoctor(adapters, {
7637
7931
  workspaceRoot,
7638
7932
  skipAi: true,
7639
- categories: ["cli", "config", "deps", "workspace"]
7933
+ categories: ["cli", "config", "deps", "workspace", "layers"]
7640
7934
  });
7641
7935
  for (const check of result.checks) {
7642
7936
  if (check.status === "fail") {
@@ -7645,7 +7939,7 @@ async function runDoctorChecks(adapters, options) {
7645
7939
  severity: "error",
7646
7940
  message: `${check.name}: ${check.message}`,
7647
7941
  category: "doctor",
7648
- context: { details: check.details }
7942
+ context: { details: check.details, ...check.context ?? {} }
7649
7943
  });
7650
7944
  } else if (check.status === "warn") {
7651
7945
  issues.push({
@@ -7653,7 +7947,7 @@ async function runDoctorChecks(adapters, options) {
7653
7947
  severity: "warning",
7654
7948
  message: `${check.name}: ${check.message}`,
7655
7949
  category: "doctor",
7656
- context: { details: check.details }
7950
+ context: { details: check.details, ...check.context ?? {} }
7657
7951
  });
7658
7952
  }
7659
7953
  }
@@ -16851,20 +17145,36 @@ function formatAsJson(result, options = {}) {
16851
17145
  line: issue.line,
16852
17146
  details: issue.context
16853
17147
  }));
17148
+ const pass = result.categories.filter((category) => category.passed).length;
16854
17149
  const fail = result.totalErrors;
16855
17150
  const warn = result.totalWarnings;
17151
+ const note = result.totalNotes;
16856
17152
  const output = {
16857
17153
  schemaVersion: "1.0",
17154
+ success: result.success,
16858
17155
  checks,
17156
+ categories: result.categories.map((category) => ({
17157
+ category: category.category,
17158
+ label: category.label,
17159
+ passed: category.passed,
17160
+ errors: category.errors,
17161
+ warnings: category.warnings,
17162
+ notes: category.notes,
17163
+ durationMs: category.durationMs
17164
+ })),
16859
17165
  drift: {
16860
17166
  status: driftResult?.hasDrift ? "detected" : "none",
16861
17167
  files: driftResult?.files ?? []
16862
17168
  },
16863
17169
  summary: {
16864
- pass: 0,
17170
+ pass,
16865
17171
  fail,
16866
17172
  warn,
16867
- total: fail + warn,
17173
+ note,
17174
+ total: pass + fail + warn + note,
17175
+ totalErrors: result.totalErrors,
17176
+ totalWarnings: result.totalWarnings,
17177
+ totalNotes: result.totalNotes,
16868
17178
  durationMs: result.durationMs,
16869
17179
  timestamp: result.timestamp
16870
17180
  },
@@ -1,12 +1,16 @@
1
1
  import { createRequire } from "node:module";
2
2
  var __defProp = Object.defineProperty;
3
+ var __returnValue = (v) => v;
4
+ function __exportSetter(name, newValue) {
5
+ this[name] = __returnValue.bind(null, newValue);
6
+ }
3
7
  var __export = (target, all) => {
4
8
  for (var name in all)
5
9
  __defProp(target, name, {
6
10
  get: all[name],
7
11
  enumerable: true,
8
12
  configurable: true,
9
- set: (newValue) => all[name] = () => newValue
13
+ set: __exportSetter.bind(all, name)
10
14
  });
11
15
  };
12
16
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -244,6 +248,8 @@ import { glob as globFn } from "glob";
244
248
 
245
249
  // src/adapters/fs.ts
246
250
  var DEFAULT_SPEC_PATTERNS = [
251
+ "**/*.command.ts",
252
+ "**/*.query.ts",
247
253
  "**/*.operation.ts",
248
254
  "**/*.operations.ts",
249
255
  "**/*.event.ts",
@@ -263,6 +269,10 @@ var DEFAULT_SPEC_PATTERNS = [
263
269
  "**/*.test-spec.ts",
264
270
  "**/contracts/*.ts",
265
271
  "**/contracts/index.ts",
272
+ "**/commands/*.ts",
273
+ "**/commands/index.ts",
274
+ "**/queries/*.ts",
275
+ "**/queries/index.ts",
266
276
  "**/operations/*.ts",
267
277
  "**/operations/index.ts",
268
278
  "**/operations.ts",
@@ -3088,9 +3098,7 @@ async function analyzeDeps(adapters, options = {}) {
3088
3098
  for (const file of files) {
3089
3099
  const content = await fs5.readFile(file);
3090
3100
  const relativePath = fs5.relative(".", file);
3091
- const nameMatch = content.match(/name:\s*['"]([^'"]+)['"]/);
3092
- const inferredName = nameMatch?.[1] ? nameMatch[1] : fs5.basename(file).replace(/\.[jt]s$/, "").replace(/\.(contracts|contract|operation|operations|event|presentation|workflow|data-view|migration|telemetry|experiment|app-config|integration|knowledge)$/, "");
3093
- const finalName = inferredName || "unknown";
3101
+ const finalName = fs5.basename(file).replace(/\.[jt]s$/, "").replace(/\.(contracts|contract|command|query|operation|operations|event|presentation|workflow|data-view|migration|telemetry|experiment|app-config|integration|knowledge)$/, "") || "unknown";
3094
3102
  const dependencies = parseImportedSpecNames(content, file);
3095
3103
  addContractNode(graph, finalName, relativePath, dependencies);
3096
3104
  }
@@ -6670,6 +6678,271 @@ async function checkContractsLibrary(fs5, ctx) {
6670
6678
  };
6671
6679
  }
6672
6680
  }
6681
+ // src/services/stability/policy.ts
6682
+ var STABILITY_POLICY_PATH = "config/stability-policy.json";
6683
+ function normalizePath(value) {
6684
+ return value.replace(/\\/g, "/").replace(/\/+$/, "");
6685
+ }
6686
+ function toStringArray(value) {
6687
+ if (!Array.isArray(value))
6688
+ return [];
6689
+ return value.filter((entry) => typeof entry === "string").map((entry) => entry.trim()).filter(Boolean);
6690
+ }
6691
+ async function loadStabilityPolicy(fs5, workspaceRoot) {
6692
+ const policyPath = fs5.join(workspaceRoot, STABILITY_POLICY_PATH);
6693
+ if (!await fs5.exists(policyPath)) {
6694
+ return;
6695
+ }
6696
+ try {
6697
+ const content = await fs5.readFile(policyPath);
6698
+ const parsed = JSON.parse(content);
6699
+ return {
6700
+ version: typeof parsed.version === "number" && Number.isFinite(parsed.version) ? parsed.version : 1,
6701
+ criticalPackages: toStringArray(parsed.criticalPackages).map(normalizePath),
6702
+ criticalFeatureKeys: toStringArray(parsed.criticalFeatureKeys),
6703
+ smokePackages: toStringArray(parsed.smokePackages)
6704
+ };
6705
+ } catch {
6706
+ return;
6707
+ }
6708
+ }
6709
+ function getPackageTier(relativePackagePath, policy) {
6710
+ const normalizedPath = normalizePath(relativePackagePath);
6711
+ if (policy?.criticalPackages.includes(normalizedPath)) {
6712
+ return "critical";
6713
+ }
6714
+ return "non-critical";
6715
+ }
6716
+ function isCriticalFeatureKey(featureKey, policy) {
6717
+ return Boolean(featureKey && policy?.criticalFeatureKeys.includes(featureKey));
6718
+ }
6719
+ function getStabilityPolicyPath(workspaceRoot) {
6720
+ return normalizePath(`${normalizePath(workspaceRoot)}/${STABILITY_POLICY_PATH}`);
6721
+ }
6722
+
6723
+ // src/services/stability/package-audit.ts
6724
+ var TEST_FILE_PATTERNS = ["**/*.{test,spec}.{ts,tsx,js,jsx,mts,cts}"];
6725
+ var TEST_IGNORES = [
6726
+ "**/node_modules/**",
6727
+ "**/dist/**",
6728
+ "**/.next/**",
6729
+ "**/.turbo/**",
6730
+ "**/coverage/**"
6731
+ ];
6732
+ function toRecord(value) {
6733
+ if (!value || typeof value !== "object") {
6734
+ return {};
6735
+ }
6736
+ return Object.entries(value).reduce((acc, [key, entry]) => {
6737
+ if (typeof entry === "string") {
6738
+ acc[key] = entry;
6739
+ }
6740
+ return acc;
6741
+ }, {});
6742
+ }
6743
+ function usesPassWithNoTests(scripts) {
6744
+ return Object.entries(scripts).some(([name, command]) => {
6745
+ return name.startsWith("test") && /pass[- ]with[- ]no[- ]tests/i.test(command);
6746
+ });
6747
+ }
6748
+ async function discoverPackages(fs5, workspaceRoot, policy) {
6749
+ const packageJsonFiles = await fs5.glob({
6750
+ pattern: "packages/**/package.json",
6751
+ cwd: workspaceRoot,
6752
+ ignore: TEST_IGNORES
6753
+ });
6754
+ const descriptors = [];
6755
+ for (const packageJsonFile of packageJsonFiles) {
6756
+ const packagePath = fs5.dirname(packageJsonFile);
6757
+ const relativePackagePath = fs5.relative(workspaceRoot, packagePath);
6758
+ try {
6759
+ const packageJson = JSON.parse(await fs5.readFile(packageJsonFile));
6760
+ const scripts = toRecord(packageJson.scripts);
6761
+ const testFiles = await fs5.glob({
6762
+ patterns: TEST_FILE_PATTERNS,
6763
+ cwd: packagePath,
6764
+ ignore: TEST_IGNORES
6765
+ });
6766
+ descriptors.push({
6767
+ packageName: packageJson.name ?? relativePackagePath,
6768
+ packagePath: relativePackagePath.replace(/\\/g, "/"),
6769
+ hasBuildScript: typeof scripts.build === "string",
6770
+ hasTypecheckScript: typeof scripts.typecheck === "string",
6771
+ hasLintScript: typeof scripts.lint === "string" || typeof scripts["lint:check"] === "string",
6772
+ hasTestScript: typeof scripts.test === "string",
6773
+ usesPassWithNoTests: usesPassWithNoTests(scripts),
6774
+ testFileCount: testFiles.length,
6775
+ tier: getPackageTier(relativePackagePath, policy)
6776
+ });
6777
+ } catch {
6778
+ continue;
6779
+ }
6780
+ }
6781
+ return descriptors;
6782
+ }
6783
+ function createCriticalFindings(descriptor) {
6784
+ const findings = [];
6785
+ if (!descriptor.hasBuildScript) {
6786
+ findings.push({
6787
+ code: "critical-missing-build-script",
6788
+ tier: descriptor.tier,
6789
+ packageName: descriptor.packageName,
6790
+ packagePath: descriptor.packagePath,
6791
+ message: "Missing build script"
6792
+ });
6793
+ }
6794
+ if (!descriptor.hasTypecheckScript) {
6795
+ findings.push({
6796
+ code: "critical-missing-typecheck-script",
6797
+ tier: descriptor.tier,
6798
+ packageName: descriptor.packageName,
6799
+ packagePath: descriptor.packagePath,
6800
+ message: "Missing typecheck script"
6801
+ });
6802
+ }
6803
+ if (!descriptor.hasLintScript) {
6804
+ findings.push({
6805
+ code: "critical-missing-lint-script",
6806
+ tier: descriptor.tier,
6807
+ packageName: descriptor.packageName,
6808
+ packagePath: descriptor.packagePath,
6809
+ message: "Missing lint or lint:check script"
6810
+ });
6811
+ }
6812
+ if (!descriptor.hasTestScript) {
6813
+ findings.push({
6814
+ code: "critical-missing-test-script",
6815
+ tier: descriptor.tier,
6816
+ packageName: descriptor.packageName,
6817
+ packagePath: descriptor.packagePath,
6818
+ message: "Missing test script"
6819
+ });
6820
+ }
6821
+ if (descriptor.testFileCount === 0) {
6822
+ findings.push({
6823
+ code: "critical-missing-test-files",
6824
+ tier: descriptor.tier,
6825
+ packageName: descriptor.packageName,
6826
+ packagePath: descriptor.packagePath,
6827
+ message: "No real test files found"
6828
+ });
6829
+ }
6830
+ if (descriptor.usesPassWithNoTests) {
6831
+ findings.push({
6832
+ code: "critical-pass-with-no-tests",
6833
+ tier: descriptor.tier,
6834
+ packageName: descriptor.packageName,
6835
+ packagePath: descriptor.packagePath,
6836
+ message: "Uses pass-with-no-tests in a critical package"
6837
+ });
6838
+ }
6839
+ return findings;
6840
+ }
6841
+ function createGeneralFindings(descriptor) {
6842
+ const findings = [];
6843
+ if (descriptor.testFileCount > 0 && !descriptor.hasTestScript) {
6844
+ findings.push({
6845
+ code: "tests-without-test-script",
6846
+ tier: descriptor.tier,
6847
+ packageName: descriptor.packageName,
6848
+ packagePath: descriptor.packagePath,
6849
+ message: "Has test files on disk but no test script"
6850
+ });
6851
+ }
6852
+ if (descriptor.hasBuildScript && descriptor.testFileCount === 0 && !descriptor.hasTestScript) {
6853
+ findings.push({
6854
+ code: "build-without-tests",
6855
+ tier: descriptor.tier,
6856
+ packageName: descriptor.packageName,
6857
+ packagePath: descriptor.packagePath,
6858
+ message: "Has a build script but no test script or test files"
6859
+ });
6860
+ }
6861
+ return findings;
6862
+ }
6863
+ async function auditWorkspacePackages(fs5, workspaceRoot, policy) {
6864
+ const packages = await discoverPackages(fs5, workspaceRoot, policy);
6865
+ const findings = [];
6866
+ for (const descriptor of packages) {
6867
+ if (descriptor.tier === "critical") {
6868
+ findings.push(...createCriticalFindings(descriptor));
6869
+ }
6870
+ findings.push(...createGeneralFindings(descriptor));
6871
+ }
6872
+ const criticalPackages = packages.filter((descriptor) => descriptor.tier === "critical").map((descriptor) => ({
6873
+ packageName: descriptor.packageName,
6874
+ packagePath: descriptor.packagePath,
6875
+ hasBuildScript: descriptor.hasBuildScript,
6876
+ hasTypecheckScript: descriptor.hasTypecheckScript,
6877
+ hasLintScript: descriptor.hasLintScript,
6878
+ hasTestScript: descriptor.hasTestScript,
6879
+ usesPassWithNoTests: descriptor.usesPassWithNoTests,
6880
+ testFileCount: descriptor.testFileCount
6881
+ }));
6882
+ return { findings, criticalPackages };
6883
+ }
6884
+
6885
+ // src/services/doctor/checks/package-stability.ts
6886
+ function summarizeFindings(findings) {
6887
+ return findings.slice(0, 5).map((finding) => `${finding.packagePath}: ${finding.message}`).join("; ");
6888
+ }
6889
+ function createCheckResult(name, findings, failureCodes, successMessage) {
6890
+ if (findings.length === 0) {
6891
+ return {
6892
+ category: "workspace",
6893
+ name,
6894
+ status: "pass",
6895
+ message: successMessage,
6896
+ context: { findings: [] }
6897
+ };
6898
+ }
6899
+ const failingFindings = findings.filter((finding) => {
6900
+ return finding.tier === "critical" || failureCodes.has(finding.code);
6901
+ });
6902
+ return {
6903
+ category: "workspace",
6904
+ name,
6905
+ status: failingFindings.length > 0 ? "fail" : "warn",
6906
+ message: `${findings.length} package issue(s) found`,
6907
+ details: summarizeFindings(findings),
6908
+ context: { findings }
6909
+ };
6910
+ }
6911
+ async function runPackageStabilityChecks(fs5, ctx) {
6912
+ const policy = await loadStabilityPolicy(fs5, ctx.workspaceRoot);
6913
+ if (!policy) {
6914
+ return [];
6915
+ }
6916
+ const report = await auditWorkspacePackages(fs5, ctx.workspaceRoot, policy);
6917
+ const criticalPackageCodes = new Set([
6918
+ "critical-missing-build-script",
6919
+ "critical-missing-typecheck-script",
6920
+ "critical-missing-lint-script",
6921
+ "critical-missing-test-script",
6922
+ "critical-missing-test-files",
6923
+ "critical-pass-with-no-tests"
6924
+ ]);
6925
+ const qualityGateFindings = report.findings.filter((finding) => criticalPackageCodes.has(finding.code));
6926
+ const testsWithoutScript = report.findings.filter((finding) => finding.code === "tests-without-test-script");
6927
+ const buildWithoutTests = report.findings.filter((finding) => finding.code === "build-without-tests");
6928
+ return [
6929
+ {
6930
+ category: "workspace",
6931
+ name: "Critical Package Gates",
6932
+ status: qualityGateFindings.length > 0 ? "fail" : "pass",
6933
+ message: qualityGateFindings.length > 0 ? `${qualityGateFindings.length} critical package gate failure(s)` : `All ${report.criticalPackages.length} critical packages meet build, lint, typecheck, and test requirements`,
6934
+ details: qualityGateFindings.length > 0 ? summarizeFindings(qualityGateFindings) : ctx.verbose ? `Policy: ${getStabilityPolicyPath(ctx.workspaceRoot)}` : undefined,
6935
+ context: {
6936
+ policyPath: getStabilityPolicyPath(ctx.workspaceRoot),
6937
+ criticalPackages: report.criticalPackages,
6938
+ findings: qualityGateFindings
6939
+ }
6940
+ },
6941
+ createCheckResult("Package Test Scripts", testsWithoutScript, new Set, "All tested packages expose a test script"),
6942
+ createCheckResult("Buildable Packages Without Tests", buildWithoutTests, new Set, "All buildable packages have tests or explicit test scripts")
6943
+ ];
6944
+ }
6945
+
6673
6946
  // src/services/doctor/checks/workspace.ts
6674
6947
  var CONTRACT_PATHS = ["src/contracts", "contracts", "src/specs", "specs"];
6675
6948
  async function runWorkspaceChecks(fs5, ctx) {
@@ -6679,6 +6952,7 @@ async function runWorkspaceChecks(fs5, ctx) {
6679
6952
  results.push(await checkContractsDirectory(fs5, ctx));
6680
6953
  results.push(await checkContractFiles(fs5, ctx));
6681
6954
  results.push(await checkOutputDirectory(fs5, ctx));
6955
+ results.push(...await runPackageStabilityChecks(fs5, ctx));
6682
6956
  return results;
6683
6957
  }
6684
6958
  function checkMonorepoStatus(ctx) {
@@ -6860,12 +7134,15 @@ async function checkOutputDirectory(fs5, ctx) {
6860
7134
  details: ctx.verbose ? `Resolved to: ${outputPath}` : undefined
6861
7135
  };
6862
7136
  }
6863
- if (ctx.isMonorepo && ctx.packageRoot === ctx.workspaceRoot && !config.outputDir) {
7137
+ const isDefaultWorkspaceOutput = outputDir === "./src" || outputDir === "src";
7138
+ const usesPackageScopedConfig = Array.isArray(config.packages) && config.packages.length > 0;
7139
+ if (ctx.isMonorepo && ctx.packageRoot === ctx.workspaceRoot && isDefaultWorkspaceOutput && (usesPackageScopedConfig || configInfo.level === "workspace")) {
6864
7140
  return {
6865
7141
  category: "workspace",
6866
7142
  name: "Output Directory",
6867
7143
  status: "pass",
6868
- message: "Monorepo root detected (using package directories)"
7144
+ message: "Monorepo root detected (using package directories)",
7145
+ details: ctx.verbose ? `Resolved default output to packages via ${configInfo.path}` : undefined
6869
7146
  };
6870
7147
  }
6871
7148
  return {
@@ -7018,8 +7295,9 @@ async function checkApiKey(fs5, ctx, prompts) {
7018
7295
  }
7019
7296
  }
7020
7297
  // src/services/doctor/checks/layers.ts
7021
- async function runLayerChecks(fs5, _ctx) {
7298
+ async function runLayerChecks(fs5, ctx) {
7022
7299
  const results = [];
7300
+ const policy = await loadStabilityPolicy(fs5, ctx.workspaceRoot);
7023
7301
  const discovery = await discoverLayers({
7024
7302
  fs: fs5,
7025
7303
  logger: {
@@ -7040,7 +7318,7 @@ async function runLayerChecks(fs5, _ctx) {
7040
7318
  }, {});
7041
7319
  results.push(checkHasFeatures(discovery.stats.features));
7042
7320
  results.push(checkHasExamples(discovery.stats.examples));
7043
- results.push(checkFeatureOwners(discovery.inventory.features));
7321
+ results.push(checkFeatureOwners(discovery.inventory.features, policy, ctx));
7044
7322
  results.push(checkExampleEntrypoints(discovery.inventory.examples));
7045
7323
  results.push(checkWorkspaceConfigs(discovery.inventory.workspaceConfigs));
7046
7324
  return results;
@@ -7079,27 +7357,43 @@ function checkHasExamples(count) {
7079
7357
  details: "Create an example.ts file to package reusable templates"
7080
7358
  };
7081
7359
  }
7082
- function checkFeatureOwners(features) {
7360
+ function checkFeatureOwners(features, policy, ctx) {
7083
7361
  const missingOwners = [];
7362
+ const criticalMissingOwners = [];
7084
7363
  for (const [key, feature] of features) {
7085
- if (!feature.owners?.length) {
7086
- missingOwners.push(key);
7364
+ const hasOwnerMetadata = Boolean(feature.owners?.length) || /owners\s*:\s*(?!\[\s*\])/.test(feature.sourceBlock ?? "");
7365
+ if (!hasOwnerMetadata) {
7366
+ if (isCriticalFeatureKey(key, policy)) {
7367
+ criticalMissingOwners.push(key);
7368
+ } else {
7369
+ missingOwners.push(key);
7370
+ }
7087
7371
  }
7088
7372
  }
7089
- if (missingOwners.length === 0) {
7373
+ if (criticalMissingOwners.length === 0 && missingOwners.length === 0) {
7090
7374
  return {
7091
7375
  category: "layers",
7092
7376
  name: "Feature Owners",
7093
7377
  status: features.size > 0 ? "pass" : "skip",
7094
- message: features.size > 0 ? "All features have owners defined" : "No features to check"
7378
+ message: features.size > 0 ? "All features have owners defined" : "No features to check",
7379
+ context: features.size > 0 ? {
7380
+ policyPath: policy ? getStabilityPolicyPath(ctx.workspaceRoot) : undefined,
7381
+ criticalMissingFeatures: [],
7382
+ missingFeatures: []
7383
+ } : undefined
7095
7384
  };
7096
7385
  }
7097
7386
  return {
7098
7387
  category: "layers",
7099
7388
  name: "Feature Owners",
7100
- status: "warn",
7101
- message: `${missingOwners.length} feature(s) missing owners`,
7102
- details: `Features: ${missingOwners.slice(0, 3).join(", ")}${missingOwners.length > 3 ? "..." : ""}`
7389
+ status: criticalMissingOwners.length > 0 ? "fail" : "warn",
7390
+ message: criticalMissingOwners.length > 0 ? `${criticalMissingOwners.length} critical feature(s) missing owners` : `${missingOwners.length} feature(s) missing owners`,
7391
+ details: criticalMissingOwners.length > 0 ? `Critical features: ${criticalMissingOwners.join(", ")}` : `Features: ${missingOwners.slice(0, 3).join(", ")}${missingOwners.length > 3 ? "..." : ""}`,
7392
+ context: {
7393
+ policyPath: policy ? getStabilityPolicyPath(ctx.workspaceRoot) : undefined,
7394
+ criticalMissingFeatures: criticalMissingOwners,
7395
+ missingFeatures: missingOwners
7396
+ }
7103
7397
  };
7104
7398
  }
7105
7399
  function checkExampleEntrypoints(examples) {
@@ -7636,7 +7930,7 @@ async function runDoctorChecks(adapters, options) {
7636
7930
  const result = await runDoctor(adapters, {
7637
7931
  workspaceRoot,
7638
7932
  skipAi: true,
7639
- categories: ["cli", "config", "deps", "workspace"]
7933
+ categories: ["cli", "config", "deps", "workspace", "layers"]
7640
7934
  });
7641
7935
  for (const check of result.checks) {
7642
7936
  if (check.status === "fail") {
@@ -7645,7 +7939,7 @@ async function runDoctorChecks(adapters, options) {
7645
7939
  severity: "error",
7646
7940
  message: `${check.name}: ${check.message}`,
7647
7941
  category: "doctor",
7648
- context: { details: check.details }
7942
+ context: { details: check.details, ...check.context ?? {} }
7649
7943
  });
7650
7944
  } else if (check.status === "warn") {
7651
7945
  issues.push({
@@ -7653,7 +7947,7 @@ async function runDoctorChecks(adapters, options) {
7653
7947
  severity: "warning",
7654
7948
  message: `${check.name}: ${check.message}`,
7655
7949
  category: "doctor",
7656
- context: { details: check.details }
7950
+ context: { details: check.details, ...check.context ?? {} }
7657
7951
  });
7658
7952
  }
7659
7953
  }
@@ -16851,20 +17145,36 @@ function formatAsJson(result, options = {}) {
16851
17145
  line: issue.line,
16852
17146
  details: issue.context
16853
17147
  }));
17148
+ const pass = result.categories.filter((category) => category.passed).length;
16854
17149
  const fail = result.totalErrors;
16855
17150
  const warn = result.totalWarnings;
17151
+ const note = result.totalNotes;
16856
17152
  const output = {
16857
17153
  schemaVersion: "1.0",
17154
+ success: result.success,
16858
17155
  checks,
17156
+ categories: result.categories.map((category) => ({
17157
+ category: category.category,
17158
+ label: category.label,
17159
+ passed: category.passed,
17160
+ errors: category.errors,
17161
+ warnings: category.warnings,
17162
+ notes: category.notes,
17163
+ durationMs: category.durationMs
17164
+ })),
16859
17165
  drift: {
16860
17166
  status: driftResult?.hasDrift ? "detected" : "none",
16861
17167
  files: driftResult?.files ?? []
16862
17168
  },
16863
17169
  summary: {
16864
- pass: 0,
17170
+ pass,
16865
17171
  fail,
16866
17172
  warn,
16867
- total: fail + warn,
17173
+ note,
17174
+ total: pass + fail + warn + note,
17175
+ totalErrors: result.totalErrors,
17176
+ totalWarnings: result.totalWarnings,
17177
+ totalNotes: result.totalNotes,
16868
17178
  durationMs: result.durationMs,
16869
17179
  timestamp: result.timestamp
16870
17180
  },
@@ -8,4 +8,4 @@ import type { CheckContext, CheckResult } from '../types';
8
8
  /**
9
9
  * Run all layer health checks.
10
10
  */
11
- export declare function runLayerChecks(fs: FsAdapter, _ctx: CheckContext): Promise<CheckResult[]>;
11
+ export declare function runLayerChecks(fs: FsAdapter, ctx: CheckContext): Promise<CheckResult[]>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ import type { FsAdapter } from '../../../ports/fs';
2
+ import type { CheckContext, CheckResult } from '../types';
3
+ export declare function runPackageStabilityChecks(fs: FsAdapter, ctx: CheckContext): Promise<CheckResult[]>;
@@ -0,0 +1 @@
1
+ export {};
@@ -53,6 +53,8 @@ export interface CheckResult {
53
53
  fix?: FixAction;
54
54
  /** Additional details for debugging. */
55
55
  details?: string;
56
+ /** Structured context for machine-readable consumers. */
57
+ context?: Record<string, unknown>;
56
58
  }
57
59
  /**
58
60
  * Options for running the doctor.
@@ -0,0 +1,25 @@
1
+ import type { FsAdapter } from '../../ports/fs';
2
+ import type { StabilityPolicy, StabilityTier } from './policy';
3
+ export type PackageAuditFindingCode = 'critical-missing-build-script' | 'critical-missing-typecheck-script' | 'critical-missing-lint-script' | 'critical-missing-test-script' | 'critical-missing-test-files' | 'critical-pass-with-no-tests' | 'tests-without-test-script' | 'build-without-tests';
4
+ export interface PackageAuditFinding {
5
+ code: PackageAuditFindingCode;
6
+ tier: StabilityTier;
7
+ packageName: string;
8
+ packagePath: string;
9
+ message: string;
10
+ }
11
+ export interface CriticalPackageStatus {
12
+ packageName: string;
13
+ packagePath: string;
14
+ hasBuildScript: boolean;
15
+ hasTypecheckScript: boolean;
16
+ hasLintScript: boolean;
17
+ hasTestScript: boolean;
18
+ usesPassWithNoTests: boolean;
19
+ testFileCount: number;
20
+ }
21
+ export interface PackageAuditReport {
22
+ findings: PackageAuditFinding[];
23
+ criticalPackages: CriticalPackageStatus[];
24
+ }
25
+ export declare function auditWorkspacePackages(fs: FsAdapter, workspaceRoot: string, policy: StabilityPolicy): Promise<PackageAuditReport>;
@@ -0,0 +1,12 @@
1
+ import type { FsAdapter } from '../../ports/fs';
2
+ export type StabilityTier = 'critical' | 'non-critical';
3
+ export interface StabilityPolicy {
4
+ version: number;
5
+ criticalPackages: string[];
6
+ criticalFeatureKeys: string[];
7
+ smokePackages: string[];
8
+ }
9
+ export declare function loadStabilityPolicy(fs: FsAdapter, workspaceRoot: string): Promise<StabilityPolicy | undefined>;
10
+ export declare function getPackageTier(relativePackagePath: string, policy?: StabilityPolicy): StabilityTier;
11
+ export declare function isCriticalFeatureKey(featureKey: string, policy?: StabilityPolicy): boolean;
12
+ export declare function getStabilityPolicyPath(workspaceRoot: string): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contractspec/bundle.workspace",
3
- "version": "3.7.1",
3
+ "version": "3.7.3",
4
4
  "description": "Workspace utilities for monorepo development",
5
5
  "keywords": [
6
6
  "contractspec",
@@ -33,14 +33,14 @@
33
33
  "dependencies": {
34
34
  "@ai-sdk/anthropic": "3.0.58",
35
35
  "@ai-sdk/openai": "3.0.41",
36
- "@contractspec/lib.ai-agent": "7.0.1",
37
- "@contractspec/lib.ai-providers": "3.7.1",
38
- "@contractspec/lib.contracts-spec": "3.7.1",
39
- "@contractspec/lib.contracts-integrations": "3.7.1",
40
- "@contractspec/lib.contracts-transformers": "3.7.1",
41
- "@contractspec/lib.source-extractors": "2.7.1",
42
- "@contractspec/module.workspace": "3.7.1",
43
- "@contractspec/lib.utils-typescript": "3.7.1",
36
+ "@contractspec/lib.ai-agent": "7.0.3",
37
+ "@contractspec/lib.ai-providers": "3.7.3",
38
+ "@contractspec/lib.contracts-spec": "3.7.3",
39
+ "@contractspec/lib.contracts-integrations": "3.7.3",
40
+ "@contractspec/lib.contracts-transformers": "3.7.3",
41
+ "@contractspec/lib.source-extractors": "2.7.3",
42
+ "@contractspec/module.workspace": "3.7.3",
43
+ "@contractspec/lib.utils-typescript": "3.7.3",
44
44
  "ai": "6.0.116",
45
45
  "chalk": "^5.6.2",
46
46
  "chokidar": "^5.0.0",
@@ -52,12 +52,12 @@
52
52
  "zod": "^4.3.5"
53
53
  },
54
54
  "devDependencies": {
55
- "@contractspec/tool.typescript": "3.7.1",
55
+ "@contractspec/tool.typescript": "3.7.3",
56
56
  "@types/bun": "^1.3.10",
57
57
  "@types/micromatch": "^4.0.10",
58
58
  "@types/node": "^25.3.5",
59
59
  "typescript": "^5.9.3",
60
- "@contractspec/tool.bun": "3.7.1"
60
+ "@contractspec/tool.bun": "3.7.3"
61
61
  },
62
62
  "exports": {
63
63
  ".": {