@contractspec/bundle.workspace 1.44.1 → 1.45.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/_virtual/rolldown_runtime.js +1 -19
- package/dist/ai/agents/cursor-agent.js +4 -4
- package/dist/ai/agents/cursor-agent.js.map +1 -1
- package/dist/ai/client.d.ts +14 -0
- package/dist/ai/client.d.ts.map +1 -1
- package/dist/ai/client.js +27 -1
- package/dist/ai/client.js.map +1 -1
- package/dist/ai/index.d.ts +1 -9
- package/dist/ai/index.js +1 -20
- package/dist/ai/prompts/index.js +1 -1
- package/dist/index.d.ts +42 -19
- package/dist/index.js +39 -150
- package/dist/services/agent-guide/agent-guide-service.js +3 -3
- package/dist/services/agent-guide/agent-guide-service.js.map +1 -1
- package/dist/services/ci-check/ci-check-service.d.ts.map +1 -1
- package/dist/services/ci-check/ci-check-service.js +69 -2
- package/dist/services/ci-check/ci-check-service.js.map +1 -1
- package/dist/services/ci-check/types.d.ts +1 -1
- package/dist/services/ci-check/types.d.ts.map +1 -1
- package/dist/services/ci-check/types.js +4 -2
- package/dist/services/ci-check/types.js.map +1 -1
- package/dist/services/config.d.ts +1 -11
- package/dist/services/config.d.ts.map +1 -1
- package/dist/services/config.js +2 -16
- package/dist/services/config.js.map +1 -1
- package/dist/services/create/ai-generator.d.ts +84 -0
- package/dist/services/create/ai-generator.d.ts.map +1 -0
- package/dist/services/create/ai-generator.js +178 -0
- package/dist/services/create/ai-generator.js.map +1 -0
- package/dist/services/create/index.d.ts +27 -0
- package/dist/services/create/index.d.ts.map +1 -0
- package/dist/services/create/index.js +36 -0
- package/dist/services/create/index.js.map +1 -0
- package/dist/services/create/templates.d.ts +21 -0
- package/dist/services/create/templates.d.ts.map +1 -0
- package/dist/services/create/templates.js +37 -0
- package/dist/services/create/templates.js.map +1 -0
- package/dist/services/docs/docs-service.d.ts +19 -0
- package/dist/services/docs/docs-service.d.ts.map +1 -0
- package/dist/services/docs/docs-service.js +41 -0
- package/dist/services/docs/docs-service.js.map +1 -0
- package/dist/services/docs/index.d.ts +1 -0
- package/dist/services/docs/index.js +1 -0
- package/dist/services/doctor/checks/cli.js +3 -3
- package/dist/services/doctor/checks/cli.js.map +1 -1
- package/dist/services/doctor/checks/index.js +1 -0
- package/dist/services/doctor/checks/layers.js +139 -0
- package/dist/services/doctor/checks/layers.js.map +1 -0
- package/dist/services/doctor/doctor-service.d.ts.map +1 -1
- package/dist/services/doctor/doctor-service.js +2 -0
- package/dist/services/doctor/doctor-service.js.map +1 -1
- package/dist/services/doctor/types.d.ts +1 -1
- package/dist/services/doctor/types.d.ts.map +1 -1
- package/dist/services/doctor/types.js +4 -2
- package/dist/services/doctor/types.js.map +1 -1
- package/dist/services/formatter.d.ts +15 -0
- package/dist/services/formatter.d.ts.map +1 -0
- package/dist/services/formatter.js +26 -0
- package/dist/services/formatter.js.map +1 -0
- package/dist/services/impact/formatters.d.ts +5 -5
- package/dist/services/impact/formatters.d.ts.map +1 -1
- package/dist/services/impact/formatters.js.map +1 -1
- package/dist/services/impact/impact-detection-service.js +6 -6
- package/dist/services/impact/impact-detection-service.js.map +1 -1
- package/dist/services/impact/types.d.ts +3 -3
- package/dist/services/implementation/resolver.js +1 -1
- package/dist/services/implementation/resolver.js.map +1 -1
- package/dist/services/implementation/types.d.ts +1 -1
- package/dist/services/index.d.ts +31 -5
- package/dist/services/index.js +30 -4
- package/dist/services/integrity-diagram.js +1 -1
- package/dist/services/integrity-diagram.js.map +1 -1
- package/dist/services/integrity.d.ts +1 -1
- package/dist/services/integrity.js.map +1 -1
- package/dist/services/layer-discovery.d.ts +77 -0
- package/dist/services/layer-discovery.d.ts.map +1 -0
- package/dist/services/layer-discovery.js +121 -0
- package/dist/services/layer-discovery.js.map +1 -0
- package/dist/services/llm/index.d.ts +28 -0
- package/dist/services/llm/index.d.ts.map +1 -0
- package/dist/services/llm/index.js +187 -0
- package/dist/services/llm/index.js.map +1 -0
- package/dist/services/llm/verify-static.d.ts +26 -0
- package/dist/services/llm/verify-static.d.ts.map +1 -0
- package/dist/services/llm/verify-static.js +82 -0
- package/dist/services/llm/verify-static.js.map +1 -0
- package/dist/services/openapi/import-service.d.ts.map +1 -1
- package/dist/services/openapi/import-service.js +98 -4
- package/dist/services/openapi/import-service.js.map +1 -1
- package/dist/services/openapi/sync-service.js +1 -1
- package/dist/services/setup/config-generators.js +1 -1
- package/dist/services/setup/config-generators.js.map +1 -1
- package/dist/services/sync.d.ts +2 -1
- package/dist/services/sync.d.ts.map +1 -1
- package/dist/services/sync.js +2 -1
- package/dist/services/sync.js.map +1 -1
- package/dist/services/test/index.d.ts +1 -0
- package/dist/services/test/index.js +1 -0
- package/dist/services/test/test-service.d.ts +22 -0
- package/dist/services/test/test-service.d.ts.map +1 -0
- package/dist/services/test/test-service.js +81 -0
- package/dist/services/test/test-service.js.map +1 -0
- package/dist/services/validate/blueprint-validator.d.ts +23 -0
- package/dist/services/validate/blueprint-validator.d.ts.map +1 -0
- package/dist/services/validate/blueprint-validator.js +50 -0
- package/dist/services/validate/blueprint-validator.js.map +1 -0
- package/dist/services/validate/implementation-agent-validator.d.ts +20 -0
- package/dist/services/validate/implementation-agent-validator.d.ts.map +1 -0
- package/dist/services/validate/implementation-agent-validator.js +42 -0
- package/dist/services/validate/implementation-agent-validator.js.map +1 -0
- package/dist/services/{validate-implementation.d.ts → validate/implementation-validator.d.ts} +3 -3
- package/dist/services/validate/implementation-validator.d.ts.map +1 -0
- package/dist/services/{validate-implementation.js → validate/implementation-validator.js} +2 -2
- package/dist/services/validate/implementation-validator.js.map +1 -0
- package/dist/services/validate/index.d.ts +5 -0
- package/dist/services/validate/index.js +5 -0
- package/dist/services/{validate.d.ts → validate/spec-validator.d.ts} +5 -4
- package/dist/services/validate/spec-validator.d.ts.map +1 -0
- package/dist/services/{validate.js → validate/spec-validator.js} +6 -4
- package/dist/services/validate/spec-validator.js.map +1 -0
- package/dist/services/validate/tenant-validator.d.ts +21 -0
- package/dist/services/validate/tenant-validator.d.ts.map +1 -0
- package/dist/services/validate/tenant-validator.js +165 -0
- package/dist/services/validate/tenant-validator.js.map +1 -0
- package/dist/services/watch.js +2 -1
- package/dist/services/watch.js.map +1 -1
- package/dist/templates/data-view.template.js +3 -3
- package/dist/templates/data-view.template.js.map +1 -1
- package/dist/templates/event.template.js +3 -1
- package/dist/templates/event.template.js.map +1 -1
- package/dist/templates/operation.template.js +3 -1
- package/dist/templates/operation.template.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/utils/module-loader.js +41 -0
- package/dist/utils/module-loader.js.map +1 -0
- package/package.json +9 -9
- package/dist/ai/index.d.ts.map +0 -1
- package/dist/ai/index.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/services/test.d.ts +0 -15
- package/dist/services/test.d.ts.map +0 -1
- package/dist/services/test.js +0 -30
- package/dist/services/test.js.map +0 -1
- package/dist/services/validate-implementation.d.ts.map +0 -1
- package/dist/services/validate-implementation.js.map +0 -1
- package/dist/services/validate.d.ts.map +0 -1
- package/dist/services/validate.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","names":["results: CheckResult[]"],"sources":["../../../../src/services/doctor/checks/cli.ts"],"sourcesContent":["/**\n * CLI installation health checks.\n */\n\nimport { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport type { FsAdapter } from '../../../ports/fs';\nimport type {
|
|
1
|
+
{"version":3,"file":"cli.js","names":["results: CheckResult[]"],"sources":["../../../../src/services/doctor/checks/cli.ts"],"sourcesContent":["/**\n * CLI installation health checks.\n */\n\nimport { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport type { FsAdapter } from '../../../ports/fs';\nimport type { CheckContext, CheckResult, FixResult } from '../types';\n\nconst execAsync = promisify(exec);\n\n/**\n * Run CLI-related health checks.\n */\nexport async function runCliChecks(\n fs: FsAdapter,\n ctx: CheckContext\n): Promise<CheckResult[]> {\n const results: CheckResult[] = [];\n\n // Check if CLI is accessible\n results.push(await checkCliAccessible(ctx));\n\n // Check CLI version\n results.push(await checkCliVersion(ctx));\n\n // Check if CLI is installed globally or locally\n results.push(await checkCliInstallLocation(fs, ctx));\n\n return results;\n}\n\n/**\n * Check if the CLI is accessible.\n */\nasync function checkCliAccessible(ctx: CheckContext): Promise<CheckResult> {\n try {\n await execAsync('bunx contractspec --version', {\n cwd: ctx.workspaceRoot,\n timeout: 10000,\n });\n\n return {\n category: 'cli',\n name: 'CLI Accessible',\n status: 'pass',\n message: 'ContractSpec CLI is accessible',\n };\n } catch {\n return {\n category: 'cli',\n name: 'CLI Accessible',\n status: 'fail',\n message: 'ContractSpec CLI is not accessible',\n details: 'Could not run \"bunx contractspec --version\"',\n fix: {\n description: 'Install ContractSpec CLI globally',\n apply: async (): Promise<FixResult> => {\n try {\n await execAsync(\n 'npm install -g @contractspec/app.cli-contractspec',\n {\n cwd: ctx.workspaceRoot,\n timeout: 60000,\n }\n );\n return { success: true, message: 'CLI installed globally' };\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n return { success: false, message: `Failed to install: ${msg}` };\n }\n },\n },\n };\n }\n}\n\n/**\n * Check CLI version.\n */\nasync function checkCliVersion(ctx: CheckContext): Promise<CheckResult> {\n try {\n const { stdout } = await execAsync('bunx contractspec --version', {\n cwd: ctx.workspaceRoot,\n timeout: 10000,\n });\n\n const version = stdout.trim();\n\n return {\n category: 'cli',\n name: 'CLI Version',\n status: 'pass',\n message: `CLI version: ${version}`,\n };\n } catch {\n return {\n category: 'cli',\n name: 'CLI Version',\n status: 'skip',\n message: 'Could not determine CLI version',\n };\n }\n}\n\n/**\n * Check if CLI is installed locally in the project.\n */\nasync function checkCliInstallLocation(\n fs: FsAdapter,\n ctx: CheckContext\n): Promise<CheckResult> {\n const packageJsonPath = fs.join(ctx.workspaceRoot, 'package.json');\n\n try {\n const exists = await fs.exists(packageJsonPath);\n if (!exists) {\n return {\n category: 'cli',\n name: 'Local Installation',\n status: 'skip',\n message: 'No package.json found',\n };\n }\n\n const content = await fs.readFile(packageJsonPath);\n const pkg = JSON.parse(content) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n\n const hasDep =\n pkg.dependencies?.['@contractspec/app.cli-contractspec'] !== undefined ||\n pkg.devDependencies?.['@contractspec/app.cli-contractspec'] !== undefined;\n\n if (hasDep) {\n return {\n category: 'cli',\n name: 'Local Installation',\n status: 'pass',\n message: 'CLI is installed as a project dependency',\n };\n }\n\n return {\n category: 'cli',\n name: 'Local Installation',\n status: 'warn',\n message: 'CLI is not installed as a project dependency',\n details:\n 'Consider adding @contractspec/app.cli-contractspec to devDependencies',\n fix: {\n description: 'Add CLI as a dev dependency',\n apply: async (): Promise<FixResult> => {\n try {\n await execAsync(\n 'npm install -D @contractspec/app.cli-contractspec',\n {\n cwd: ctx.workspaceRoot,\n timeout: 60000,\n }\n );\n return { success: true, message: 'CLI added as dev dependency' };\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n return { success: false, message: `Failed to install: ${msg}` };\n }\n },\n },\n };\n } catch {\n return {\n category: 'cli',\n name: 'Local Installation',\n status: 'skip',\n message: 'Could not check local installation',\n };\n }\n}\n"],"mappings":";;;;;;;AASA,MAAM,YAAY,UAAU,KAAK;;;;AAKjC,eAAsB,aACpB,IACA,KACwB;CACxB,MAAMA,UAAyB,EAAE;AAGjC,SAAQ,KAAK,MAAM,mBAAmB,IAAI,CAAC;AAG3C,SAAQ,KAAK,MAAM,gBAAgB,IAAI,CAAC;AAGxC,SAAQ,KAAK,MAAM,wBAAwB,IAAI,IAAI,CAAC;AAEpD,QAAO;;;;;AAMT,eAAe,mBAAmB,KAAyC;AACzE,KAAI;AACF,QAAM,UAAU,+BAA+B;GAC7C,KAAK,IAAI;GACT,SAAS;GACV,CAAC;AAEF,SAAO;GACL,UAAU;GACV,MAAM;GACN,QAAQ;GACR,SAAS;GACV;SACK;AACN,SAAO;GACL,UAAU;GACV,MAAM;GACN,QAAQ;GACR,SAAS;GACT,SAAS;GACT,KAAK;IACH,aAAa;IACb,OAAO,YAAgC;AACrC,SAAI;AACF,YAAM,UACJ,qDACA;OACE,KAAK,IAAI;OACT,SAAS;OACV,CACF;AACD,aAAO;OAAE,SAAS;OAAM,SAAS;OAA0B;cACpD,OAAO;AAEd,aAAO;OAAE,SAAS;OAAO,SAAS,sBADtB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;OACH;;;IAGpE;GACF;;;;;;AAOL,eAAe,gBAAgB,KAAyC;AACtE,KAAI;EACF,MAAM,EAAE,WAAW,MAAM,UAAU,+BAA+B;GAChE,KAAK,IAAI;GACT,SAAS;GACV,CAAC;AAIF,SAAO;GACL,UAAU;GACV,MAAM;GACN,QAAQ;GACR,SAAS,gBANK,OAAO,MAAM;GAO5B;SACK;AACN,SAAO;GACL,UAAU;GACV,MAAM;GACN,QAAQ;GACR,SAAS;GACV;;;;;;AAOL,eAAe,wBACb,IACA,KACsB;CACtB,MAAM,kBAAkB,GAAG,KAAK,IAAI,eAAe,eAAe;AAElE,KAAI;AAEF,MAAI,CADW,MAAM,GAAG,OAAO,gBAAgB,CAE7C,QAAO;GACL,UAAU;GACV,MAAM;GACN,QAAQ;GACR,SAAS;GACV;EAGH,MAAM,UAAU,MAAM,GAAG,SAAS,gBAAgB;EAClD,MAAM,MAAM,KAAK,MAAM,QAAQ;AAS/B,MAHE,IAAI,eAAe,0CAA0C,UAC7D,IAAI,kBAAkB,0CAA0C,OAGhE,QAAO;GACL,UAAU;GACV,MAAM;GACN,QAAQ;GACR,SAAS;GACV;AAGH,SAAO;GACL,UAAU;GACV,MAAM;GACN,QAAQ;GACR,SAAS;GACT,SACE;GACF,KAAK;IACH,aAAa;IACb,OAAO,YAAgC;AACrC,SAAI;AACF,YAAM,UACJ,qDACA;OACE,KAAK,IAAI;OACT,SAAS;OACV,CACF;AACD,aAAO;OAAE,SAAS;OAAM,SAAS;OAA+B;cACzD,OAAO;AAEd,aAAO;OAAE,SAAS;OAAO,SAAS,sBADtB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;OACH;;;IAGpE;GACF;SACK;AACN,SAAO;GACL,UAAU;GACV,MAAM;GACN,QAAQ;GACR,SAAS;GACV"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { discoverLayers } from "../../layer-discovery.js";
|
|
2
|
+
|
|
3
|
+
//#region src/services/doctor/checks/layers.ts
|
|
4
|
+
/**
|
|
5
|
+
* Run all layer health checks.
|
|
6
|
+
*/
|
|
7
|
+
async function runLayerChecks(fs, _ctx) {
|
|
8
|
+
const results = [];
|
|
9
|
+
const discovery = await discoverLayers({
|
|
10
|
+
fs,
|
|
11
|
+
logger: {
|
|
12
|
+
info: () => {},
|
|
13
|
+
warn: () => {},
|
|
14
|
+
error: () => {},
|
|
15
|
+
debug: () => {},
|
|
16
|
+
createProgress: () => ({
|
|
17
|
+
start: () => {},
|
|
18
|
+
update: () => {},
|
|
19
|
+
succeed: () => {},
|
|
20
|
+
fail: () => {},
|
|
21
|
+
warn: () => {},
|
|
22
|
+
stop: () => {},
|
|
23
|
+
finish: () => {}
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
}, {});
|
|
27
|
+
results.push(checkHasFeatures(discovery.stats.features));
|
|
28
|
+
results.push(checkHasExamples(discovery.stats.examples));
|
|
29
|
+
results.push(checkFeatureOwners(discovery.inventory.features));
|
|
30
|
+
results.push(checkExampleEntrypoints(discovery.inventory.examples));
|
|
31
|
+
results.push(checkWorkspaceConfigs(discovery.inventory.workspaceConfigs));
|
|
32
|
+
return results;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Check if workspace has features defined.
|
|
36
|
+
*/
|
|
37
|
+
function checkHasFeatures(count) {
|
|
38
|
+
if (count > 0) return {
|
|
39
|
+
category: "layers",
|
|
40
|
+
name: "Features Defined",
|
|
41
|
+
status: "pass",
|
|
42
|
+
message: `Found ${count} feature module(s)`
|
|
43
|
+
};
|
|
44
|
+
return {
|
|
45
|
+
category: "layers",
|
|
46
|
+
name: "Features Defined",
|
|
47
|
+
status: "warn",
|
|
48
|
+
message: "No feature modules found",
|
|
49
|
+
details: "Create a .feature.ts file to organize your specs into features"
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Check if workspace has examples defined.
|
|
54
|
+
*/
|
|
55
|
+
function checkHasExamples(count) {
|
|
56
|
+
if (count > 0) return {
|
|
57
|
+
category: "layers",
|
|
58
|
+
name: "Examples Defined",
|
|
59
|
+
status: "pass",
|
|
60
|
+
message: `Found ${count} example(s)`
|
|
61
|
+
};
|
|
62
|
+
return {
|
|
63
|
+
category: "layers",
|
|
64
|
+
name: "Examples Defined",
|
|
65
|
+
status: "skip",
|
|
66
|
+
message: "No examples found (optional)",
|
|
67
|
+
details: "Create an example.ts file to package reusable templates"
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Check if features have owners defined.
|
|
72
|
+
*/
|
|
73
|
+
function checkFeatureOwners(features) {
|
|
74
|
+
const missingOwners = [];
|
|
75
|
+
for (const [key, feature] of features) if (!feature.owners?.length) missingOwners.push(key);
|
|
76
|
+
if (missingOwners.length === 0) return {
|
|
77
|
+
category: "layers",
|
|
78
|
+
name: "Feature Owners",
|
|
79
|
+
status: features.size > 0 ? "pass" : "skip",
|
|
80
|
+
message: features.size > 0 ? "All features have owners defined" : "No features to check"
|
|
81
|
+
};
|
|
82
|
+
return {
|
|
83
|
+
category: "layers",
|
|
84
|
+
name: "Feature Owners",
|
|
85
|
+
status: "warn",
|
|
86
|
+
message: `${missingOwners.length} feature(s) missing owners`,
|
|
87
|
+
details: `Features: ${missingOwners.slice(0, 3).join(", ")}${missingOwners.length > 3 ? "..." : ""}`
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Check if examples have valid entrypoints.
|
|
92
|
+
*/
|
|
93
|
+
function checkExampleEntrypoints(examples) {
|
|
94
|
+
const missingPackage = [];
|
|
95
|
+
for (const [key, example] of examples) if (!example.entrypoints.packageName) missingPackage.push(key);
|
|
96
|
+
if (missingPackage.length === 0) return {
|
|
97
|
+
category: "layers",
|
|
98
|
+
name: "Example Entrypoints",
|
|
99
|
+
status: examples.size > 0 ? "pass" : "skip",
|
|
100
|
+
message: examples.size > 0 ? "All examples have valid entrypoints" : "No examples to check"
|
|
101
|
+
};
|
|
102
|
+
return {
|
|
103
|
+
category: "layers",
|
|
104
|
+
name: "Example Entrypoints",
|
|
105
|
+
status: "fail",
|
|
106
|
+
message: `${missingPackage.length} example(s) missing packageName`,
|
|
107
|
+
details: `Examples: ${missingPackage.join(", ")}`
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Check if workspace configs are valid.
|
|
112
|
+
*/
|
|
113
|
+
function checkWorkspaceConfigs(configs) {
|
|
114
|
+
const invalid = [];
|
|
115
|
+
for (const [, config] of configs) if (!config.valid) invalid.push(config.file);
|
|
116
|
+
if (configs.size === 0) return {
|
|
117
|
+
category: "layers",
|
|
118
|
+
name: "Workspace Configs",
|
|
119
|
+
status: "skip",
|
|
120
|
+
message: "No .contractsrc.json files found"
|
|
121
|
+
};
|
|
122
|
+
if (invalid.length === 0) return {
|
|
123
|
+
category: "layers",
|
|
124
|
+
name: "Workspace Configs",
|
|
125
|
+
status: "pass",
|
|
126
|
+
message: `All ${configs.size} workspace config(s) are valid`
|
|
127
|
+
};
|
|
128
|
+
return {
|
|
129
|
+
category: "layers",
|
|
130
|
+
name: "Workspace Configs",
|
|
131
|
+
status: "fail",
|
|
132
|
+
message: `${invalid.length} workspace config(s) invalid`,
|
|
133
|
+
details: `Files: ${invalid.join(", ")}`
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
//#endregion
|
|
138
|
+
export { runLayerChecks };
|
|
139
|
+
//# sourceMappingURL=layers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layers.js","names":["results: CheckResult[]","missingOwners: string[]","missingPackage: string[]","invalid: string[]"],"sources":["../../../../src/services/doctor/checks/layers.ts"],"sourcesContent":["/**\n * Layer health checks.\n *\n * Validates contract layers (features, examples, app-configs, workspace-configs).\n */\n\nimport type { FsAdapter } from '../../../ports/fs';\nimport type { CheckContext, CheckResult } from '../types';\nimport { discoverLayers } from '../../layer-discovery';\n\n/**\n * Run all layer health checks.\n */\nexport async function runLayerChecks(\n fs: FsAdapter,\n _ctx: CheckContext\n): Promise<CheckResult[]> {\n const results: CheckResult[] = [];\n\n // Discover layers\n const discovery = await discoverLayers(\n {\n fs,\n logger: {\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n info: () => {},\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n warn: () => {},\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n error: () => {},\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n debug: () => {},\n createProgress: () => ({\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n start: () => {},\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n update: () => {},\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n succeed: () => {},\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n fail: () => {},\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n warn: () => {},\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n stop: () => {},\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n finish: () => {},\n }),\n },\n },\n {}\n );\n\n // Check: Has features\n results.push(checkHasFeatures(discovery.stats.features));\n\n // Check: Has examples\n results.push(checkHasExamples(discovery.stats.examples));\n\n // Check: Features have owners\n results.push(checkFeatureOwners(discovery.inventory.features));\n\n // Check: Examples have valid entrypoints\n results.push(checkExampleEntrypoints(discovery.inventory.examples));\n\n // Check: Workspace configs are valid\n results.push(checkWorkspaceConfigs(discovery.inventory.workspaceConfigs));\n\n return results;\n}\n\n/**\n * Check if workspace has features defined.\n */\nfunction checkHasFeatures(count: number): CheckResult {\n if (count > 0) {\n return {\n category: 'layers',\n name: 'Features Defined',\n status: 'pass',\n message: `Found ${count} feature module(s)`,\n };\n }\n\n return {\n category: 'layers',\n name: 'Features Defined',\n status: 'warn',\n message: 'No feature modules found',\n details: 'Create a .feature.ts file to organize your specs into features',\n };\n}\n\n/**\n * Check if workspace has examples defined.\n */\nfunction checkHasExamples(count: number): CheckResult {\n if (count > 0) {\n return {\n category: 'layers',\n name: 'Examples Defined',\n status: 'pass',\n message: `Found ${count} example(s)`,\n };\n }\n\n return {\n category: 'layers',\n name: 'Examples Defined',\n status: 'skip',\n message: 'No examples found (optional)',\n details: 'Create an example.ts file to package reusable templates',\n };\n}\n\n/**\n * Check if features have owners defined.\n */\nfunction checkFeatureOwners(\n features: Map<string, { owners?: string[]; filePath: string }>\n): CheckResult {\n const missingOwners: string[] = [];\n\n for (const [key, feature] of features) {\n if (!feature.owners?.length) {\n missingOwners.push(key);\n }\n }\n\n if (missingOwners.length === 0) {\n return {\n category: 'layers',\n name: 'Feature Owners',\n status: features.size > 0 ? 'pass' : 'skip',\n message:\n features.size > 0\n ? 'All features have owners defined'\n : 'No features to check',\n };\n }\n\n return {\n category: 'layers',\n name: 'Feature Owners',\n status: 'warn',\n message: `${missingOwners.length} feature(s) missing owners`,\n details: `Features: ${missingOwners.slice(0, 3).join(', ')}${missingOwners.length > 3 ? '...' : ''}`,\n };\n}\n\n/**\n * Check if examples have valid entrypoints.\n */\nfunction checkExampleEntrypoints(\n examples: Map<\n string,\n { entrypoints: { packageName: string }; filePath: string }\n >\n): CheckResult {\n const missingPackage: string[] = [];\n\n for (const [key, example] of examples) {\n if (!example.entrypoints.packageName) {\n missingPackage.push(key);\n }\n }\n\n if (missingPackage.length === 0) {\n return {\n category: 'layers',\n name: 'Example Entrypoints',\n status: examples.size > 0 ? 'pass' : 'skip',\n message:\n examples.size > 0\n ? 'All examples have valid entrypoints'\n : 'No examples to check',\n };\n }\n\n return {\n category: 'layers',\n name: 'Example Entrypoints',\n status: 'fail',\n message: `${missingPackage.length} example(s) missing packageName`,\n details: `Examples: ${missingPackage.join(', ')}`,\n };\n}\n\n/**\n * Check if workspace configs are valid.\n */\nfunction checkWorkspaceConfigs(\n configs: Map<string, { valid: boolean; errors: string[]; file: string }>\n): CheckResult {\n const invalid: string[] = [];\n\n for (const [, config] of configs) {\n if (!config.valid) {\n invalid.push(config.file);\n }\n }\n\n if (configs.size === 0) {\n return {\n category: 'layers',\n name: 'Workspace Configs',\n status: 'skip',\n message: 'No .contractsrc.json files found',\n };\n }\n\n if (invalid.length === 0) {\n return {\n category: 'layers',\n name: 'Workspace Configs',\n status: 'pass',\n message: `All ${configs.size} workspace config(s) are valid`,\n };\n }\n\n return {\n category: 'layers',\n name: 'Workspace Configs',\n status: 'fail',\n message: `${invalid.length} workspace config(s) invalid`,\n details: `Files: ${invalid.join(', ')}`,\n };\n}\n"],"mappings":";;;;;;AAaA,eAAsB,eACpB,IACA,MACwB;CACxB,MAAMA,UAAyB,EAAE;CAGjC,MAAM,YAAY,MAAM,eACtB;EACE;EACA,QAAQ;GAEN,YAAY;GAEZ,YAAY;GAEZ,aAAa;GAEb,aAAa;GACb,uBAAuB;IAErB,aAAa;IAEb,cAAc;IAEd,eAAe;IAEf,YAAY;IAEZ,YAAY;IAEZ,YAAY;IAEZ,cAAc;IACf;GACF;EACF,EACD,EAAE,CACH;AAGD,SAAQ,KAAK,iBAAiB,UAAU,MAAM,SAAS,CAAC;AAGxD,SAAQ,KAAK,iBAAiB,UAAU,MAAM,SAAS,CAAC;AAGxD,SAAQ,KAAK,mBAAmB,UAAU,UAAU,SAAS,CAAC;AAG9D,SAAQ,KAAK,wBAAwB,UAAU,UAAU,SAAS,CAAC;AAGnE,SAAQ,KAAK,sBAAsB,UAAU,UAAU,iBAAiB,CAAC;AAEzE,QAAO;;;;;AAMT,SAAS,iBAAiB,OAA4B;AACpD,KAAI,QAAQ,EACV,QAAO;EACL,UAAU;EACV,MAAM;EACN,QAAQ;EACR,SAAS,SAAS,MAAM;EACzB;AAGH,QAAO;EACL,UAAU;EACV,MAAM;EACN,QAAQ;EACR,SAAS;EACT,SAAS;EACV;;;;;AAMH,SAAS,iBAAiB,OAA4B;AACpD,KAAI,QAAQ,EACV,QAAO;EACL,UAAU;EACV,MAAM;EACN,QAAQ;EACR,SAAS,SAAS,MAAM;EACzB;AAGH,QAAO;EACL,UAAU;EACV,MAAM;EACN,QAAQ;EACR,SAAS;EACT,SAAS;EACV;;;;;AAMH,SAAS,mBACP,UACa;CACb,MAAMC,gBAA0B,EAAE;AAElC,MAAK,MAAM,CAAC,KAAK,YAAY,SAC3B,KAAI,CAAC,QAAQ,QAAQ,OACnB,eAAc,KAAK,IAAI;AAI3B,KAAI,cAAc,WAAW,EAC3B,QAAO;EACL,UAAU;EACV,MAAM;EACN,QAAQ,SAAS,OAAO,IAAI,SAAS;EACrC,SACE,SAAS,OAAO,IACZ,qCACA;EACP;AAGH,QAAO;EACL,UAAU;EACV,MAAM;EACN,QAAQ;EACR,SAAS,GAAG,cAAc,OAAO;EACjC,SAAS,aAAa,cAAc,MAAM,GAAG,EAAE,CAAC,KAAK,KAAK,GAAG,cAAc,SAAS,IAAI,QAAQ;EACjG;;;;;AAMH,SAAS,wBACP,UAIa;CACb,MAAMC,iBAA2B,EAAE;AAEnC,MAAK,MAAM,CAAC,KAAK,YAAY,SAC3B,KAAI,CAAC,QAAQ,YAAY,YACvB,gBAAe,KAAK,IAAI;AAI5B,KAAI,eAAe,WAAW,EAC5B,QAAO;EACL,UAAU;EACV,MAAM;EACN,QAAQ,SAAS,OAAO,IAAI,SAAS;EACrC,SACE,SAAS,OAAO,IACZ,wCACA;EACP;AAGH,QAAO;EACL,UAAU;EACV,MAAM;EACN,QAAQ;EACR,SAAS,GAAG,eAAe,OAAO;EAClC,SAAS,aAAa,eAAe,KAAK,KAAK;EAChD;;;;;AAMH,SAAS,sBACP,SACa;CACb,MAAMC,UAAoB,EAAE;AAE5B,MAAK,MAAM,GAAG,WAAW,QACvB,KAAI,CAAC,OAAO,MACV,SAAQ,KAAK,OAAO,KAAK;AAI7B,KAAI,QAAQ,SAAS,EACnB,QAAO;EACL,UAAU;EACV,MAAM;EACN,QAAQ;EACR,SAAS;EACV;AAGH,KAAI,QAAQ,WAAW,EACrB,QAAO;EACL,UAAU;EACV,MAAM;EACN,QAAQ;EACR,SAAS,OAAO,QAAQ,KAAK;EAC9B;AAGH,QAAO;EACL,UAAU;EACV,MAAM;EACN,QAAQ;EACR,SAAS,GAAG,QAAQ,OAAO;EAC3B,SAAS,UAAU,QAAQ,KAAK,KAAK;EACtC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor-service.d.ts","names":[],"sources":["../../../src/services/doctor/doctor-service.ts"],"sourcesContent":[],"mappings":";;;;;;;;;
|
|
1
|
+
{"version":3,"file":"doctor-service.d.ts","names":[],"sources":["../../../src/services/doctor/doctor-service.ts"],"sourcesContent":[],"mappings":";;;;;;;;;AAgDG,iBAJmB,SAAA,CAInB,QAAA,EAAA;EAAO,EAAA,EAHQ,SAGR;EAoHM,MAAA,EAvHqB,aAuHrB;AAyBhB,CAAA,EAAA,OAAgB,EA/IL,aA+IsB,EAAA,OAAoB,CAApB,EA9ItB,qBA8I0C,CAAA,EA7IlD,OA6IkD,CA7I1C,YA6I0C,CAAA;;;;iBAzBrC,mBAAA,SAA4B;;;;iBAyB5B,iBAAA,SAA0B"}
|
|
@@ -6,6 +6,7 @@ import { runMcpChecks } from "./checks/mcp.js";
|
|
|
6
6
|
import { runDepsChecks } from "./checks/deps.js";
|
|
7
7
|
import { runWorkspaceChecks } from "./checks/workspace.js";
|
|
8
8
|
import { runAiChecks } from "./checks/ai.js";
|
|
9
|
+
import { runLayerChecks } from "./checks/layers.js";
|
|
9
10
|
import "./checks/index.js";
|
|
10
11
|
|
|
11
12
|
//#region src/services/doctor/doctor-service.ts
|
|
@@ -81,6 +82,7 @@ async function runCategoryChecks(category, fs, ctx, prompts) {
|
|
|
81
82
|
case "deps": return runDepsChecks(fs, ctx);
|
|
82
83
|
case "workspace": return runWorkspaceChecks(fs, ctx);
|
|
83
84
|
case "ai": return runAiChecks(fs, ctx, prompts);
|
|
85
|
+
case "layers": return runLayerChecks(fs, ctx);
|
|
84
86
|
default: return [];
|
|
85
87
|
}
|
|
86
88
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor-service.js","names":["defaultPrompts: DoctorPromptCallbacks","ctx: CheckContext","allResults: CheckResult[]","lines: string[]"],"sources":["../../../src/services/doctor/doctor-service.ts"],"sourcesContent":["/**\n * Doctor service.\n *\n * Orchestrates health checks and applies fixes.\n */\n\nimport type { FsAdapter } from '../../ports/fs';\nimport type { LoggerAdapter } from '../../ports/logger';\nimport type {\n CheckCategory,\n CheckResult,\n CheckContext,\n DoctorOptions,\n DoctorResult,\n DoctorPromptCallbacks,\n} from './types';\nimport { ALL_CHECK_CATEGORIES, CHECK_CATEGORY_LABELS } from './types';\nimport {\n runCliChecks,\n runConfigChecks,\n runMcpChecks,\n runDepsChecks,\n runWorkspaceChecks,\n runAiChecks,\n} from './checks/index';\nimport {\n findPackageRoot,\n findWorkspaceRoot,\n isMonorepo,\n getPackageName,\n} from '../../adapters/workspace';\n\n/**\n * Default prompt callbacks that always decline fixes.\n */\nconst defaultPrompts: DoctorPromptCallbacks = {\n confirm: async () => false,\n input: async () => '',\n};\n\n/**\n * Run all health checks and optionally apply fixes.\n */\nexport async function runDoctor(\n adapters: { fs: FsAdapter; logger: LoggerAdapter },\n options: DoctorOptions,\n prompts: DoctorPromptCallbacks = defaultPrompts\n): Promise<DoctorResult> {\n const { fs, logger } = adapters;\n const categories = options.categories ?? ALL_CHECK_CATEGORIES;\n\n // Detect monorepo context\n const workspaceRoot = findWorkspaceRoot(options.workspaceRoot);\n const packageRoot = findPackageRoot(options.workspaceRoot);\n const monorepo = isMonorepo(workspaceRoot);\n const packageName = monorepo ? getPackageName(packageRoot) : undefined;\n\n const ctx: CheckContext = {\n workspaceRoot,\n packageRoot,\n isMonorepo: monorepo,\n packageName,\n verbose: options.verbose ?? false,\n };\n\n // Log monorepo context if detected\n if (monorepo) {\n const pkgInfo = packageName ? ` (package: ${packageName})` : '';\n logger.info(`Detected monorepo${pkgInfo}`);\n }\n\n const allResults: CheckResult[] = [];\n\n // Run checks for each category\n for (const category of categories) {\n if (options.skipAi && category === 'ai') {\n continue;\n }\n\n logger.info(`Checking ${CHECK_CATEGORY_LABELS[category]}...`);\n\n const categoryResults = await runCategoryChecks(category, fs, ctx, prompts);\n\n // Apply fixes if enabled\n for (const result of categoryResults) {\n if (\n result.fix &&\n (result.status === 'fail' || result.status === 'warn')\n ) {\n const shouldFix = options.autoFix\n ? true\n : await prompts.confirm(\n `Fix \"${result.name}\"? ${result.fix.description}`\n );\n\n if (shouldFix) {\n logger.info(`Applying fix: ${result.fix.description}`);\n const fixResult = await result.fix.apply();\n\n if (fixResult.success) {\n logger.info(`✓ ${fixResult.message}`);\n // Update status to pass after successful fix\n result.status = 'pass';\n result.message = `Fixed: ${fixResult.message}`;\n result.fix = undefined;\n } else {\n logger.warn(`✗ ${fixResult.message}`);\n }\n }\n }\n\n allResults.push(result);\n }\n }\n\n // Calculate summary\n const passed = allResults.filter((r) => r.status === 'pass').length;\n const warnings = allResults.filter((r) => r.status === 'warn').length;\n const failures = allResults.filter((r) => r.status === 'fail').length;\n const skipped = allResults.filter((r) => r.status === 'skip').length;\n\n return {\n checks: allResults,\n passed,\n warnings,\n failures,\n skipped,\n healthy: failures === 0,\n };\n}\n\n/**\n * Run checks for a specific category.\n */\nasync function runCategoryChecks(\n category: CheckCategory,\n fs: FsAdapter,\n ctx: CheckContext,\n prompts: DoctorPromptCallbacks\n): Promise<CheckResult[]> {\n switch (category) {\n case 'cli':\n return runCliChecks(fs, ctx);\n case 'config':\n return runConfigChecks(fs, ctx);\n case 'mcp':\n return runMcpChecks(fs, ctx);\n case 'deps':\n return runDepsChecks(fs, ctx);\n case 'workspace':\n return runWorkspaceChecks(fs, ctx);\n case 'ai':\n return runAiChecks(fs, ctx, prompts);\n default:\n return [];\n }\n}\n\n/**\n * Get a summary string for the doctor result.\n */\nexport function formatDoctorSummary(result: DoctorResult): string {\n const lines: string[] = [];\n\n lines.push('');\n lines.push('=== Health Check Summary ===');\n lines.push('');\n\n if (result.healthy) {\n lines.push('✓ All checks passed!');\n } else {\n lines.push('✗ Some issues found');\n }\n\n lines.push('');\n lines.push(` Passed: ${result.passed}`);\n lines.push(` Warnings: ${result.warnings}`);\n lines.push(` Failures: ${result.failures}`);\n lines.push(` Skipped: ${result.skipped}`);\n\n return lines.join('\\n');\n}\n\n/**\n * Format a single check result for display.\n */\nexport function formatCheckResult(result: CheckResult): string {\n const icon =\n result.status === 'pass'\n ? '✓'\n : result.status === 'warn'\n ? '⚠'\n : result.status === 'fail'\n ? '✗'\n : '○';\n\n let line = `${icon} ${result.name}: ${result.message}`;\n\n if (result.details) {\n line += `\\n ${result.details}`;\n }\n\n if (result.fix) {\n line += `\\n Fix available: ${result.fix.description}`;\n }\n\n return line;\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"doctor-service.js","names":["defaultPrompts: DoctorPromptCallbacks","ctx: CheckContext","allResults: CheckResult[]","lines: string[]"],"sources":["../../../src/services/doctor/doctor-service.ts"],"sourcesContent":["/**\n * Doctor service.\n *\n * Orchestrates health checks and applies fixes.\n */\n\nimport type { FsAdapter } from '../../ports/fs';\nimport type { LoggerAdapter } from '../../ports/logger';\nimport type {\n CheckCategory,\n CheckResult,\n CheckContext,\n DoctorOptions,\n DoctorResult,\n DoctorPromptCallbacks,\n} from './types';\nimport { ALL_CHECK_CATEGORIES, CHECK_CATEGORY_LABELS } from './types';\nimport {\n runCliChecks,\n runConfigChecks,\n runMcpChecks,\n runDepsChecks,\n runWorkspaceChecks,\n runAiChecks,\n runLayerChecks,\n} from './checks/index';\nimport {\n findPackageRoot,\n findWorkspaceRoot,\n isMonorepo,\n getPackageName,\n} from '../../adapters/workspace';\n\n/**\n * Default prompt callbacks that always decline fixes.\n */\nconst defaultPrompts: DoctorPromptCallbacks = {\n confirm: async () => false,\n input: async () => '',\n};\n\n/**\n * Run all health checks and optionally apply fixes.\n */\nexport async function runDoctor(\n adapters: { fs: FsAdapter; logger: LoggerAdapter },\n options: DoctorOptions,\n prompts: DoctorPromptCallbacks = defaultPrompts\n): Promise<DoctorResult> {\n const { fs, logger } = adapters;\n const categories = options.categories ?? ALL_CHECK_CATEGORIES;\n\n // Detect monorepo context\n const workspaceRoot = findWorkspaceRoot(options.workspaceRoot);\n const packageRoot = findPackageRoot(options.workspaceRoot);\n const monorepo = isMonorepo(workspaceRoot);\n const packageName = monorepo ? getPackageName(packageRoot) : undefined;\n\n const ctx: CheckContext = {\n workspaceRoot,\n packageRoot,\n isMonorepo: monorepo,\n packageName,\n verbose: options.verbose ?? false,\n };\n\n // Log monorepo context if detected\n if (monorepo) {\n const pkgInfo = packageName ? ` (package: ${packageName})` : '';\n logger.info(`Detected monorepo${pkgInfo}`);\n }\n\n const allResults: CheckResult[] = [];\n\n // Run checks for each category\n for (const category of categories) {\n if (options.skipAi && category === 'ai') {\n continue;\n }\n\n logger.info(`Checking ${CHECK_CATEGORY_LABELS[category]}...`);\n\n const categoryResults = await runCategoryChecks(category, fs, ctx, prompts);\n\n // Apply fixes if enabled\n for (const result of categoryResults) {\n if (\n result.fix &&\n (result.status === 'fail' || result.status === 'warn')\n ) {\n const shouldFix = options.autoFix\n ? true\n : await prompts.confirm(\n `Fix \"${result.name}\"? ${result.fix.description}`\n );\n\n if (shouldFix) {\n logger.info(`Applying fix: ${result.fix.description}`);\n const fixResult = await result.fix.apply();\n\n if (fixResult.success) {\n logger.info(`✓ ${fixResult.message}`);\n // Update status to pass after successful fix\n result.status = 'pass';\n result.message = `Fixed: ${fixResult.message}`;\n result.fix = undefined;\n } else {\n logger.warn(`✗ ${fixResult.message}`);\n }\n }\n }\n\n allResults.push(result);\n }\n }\n\n // Calculate summary\n const passed = allResults.filter((r) => r.status === 'pass').length;\n const warnings = allResults.filter((r) => r.status === 'warn').length;\n const failures = allResults.filter((r) => r.status === 'fail').length;\n const skipped = allResults.filter((r) => r.status === 'skip').length;\n\n return {\n checks: allResults,\n passed,\n warnings,\n failures,\n skipped,\n healthy: failures === 0,\n };\n}\n\n/**\n * Run checks for a specific category.\n */\nasync function runCategoryChecks(\n category: CheckCategory,\n fs: FsAdapter,\n ctx: CheckContext,\n prompts: DoctorPromptCallbacks\n): Promise<CheckResult[]> {\n switch (category) {\n case 'cli':\n return runCliChecks(fs, ctx);\n case 'config':\n return runConfigChecks(fs, ctx);\n case 'mcp':\n return runMcpChecks(fs, ctx);\n case 'deps':\n return runDepsChecks(fs, ctx);\n case 'workspace':\n return runWorkspaceChecks(fs, ctx);\n case 'ai':\n return runAiChecks(fs, ctx, prompts);\n case 'layers':\n return runLayerChecks(fs, ctx);\n default:\n return [];\n }\n}\n\n/**\n * Get a summary string for the doctor result.\n */\nexport function formatDoctorSummary(result: DoctorResult): string {\n const lines: string[] = [];\n\n lines.push('');\n lines.push('=== Health Check Summary ===');\n lines.push('');\n\n if (result.healthy) {\n lines.push('✓ All checks passed!');\n } else {\n lines.push('✗ Some issues found');\n }\n\n lines.push('');\n lines.push(` Passed: ${result.passed}`);\n lines.push(` Warnings: ${result.warnings}`);\n lines.push(` Failures: ${result.failures}`);\n lines.push(` Skipped: ${result.skipped}`);\n\n return lines.join('\\n');\n}\n\n/**\n * Format a single check result for display.\n */\nexport function formatCheckResult(result: CheckResult): string {\n const icon =\n result.status === 'pass'\n ? '✓'\n : result.status === 'warn'\n ? '⚠'\n : result.status === 'fail'\n ? '✗'\n : '○';\n\n let line = `${icon} ${result.name}: ${result.message}`;\n\n if (result.details) {\n line += `\\n ${result.details}`;\n }\n\n if (result.fix) {\n line += `\\n Fix available: ${result.fix.description}`;\n }\n\n return line;\n}\n"],"mappings":";;;;;;;;;;;;;;;AAoCA,MAAMA,iBAAwC;CAC5C,SAAS,YAAY;CACrB,OAAO,YAAY;CACpB;;;;AAKD,eAAsB,UACpB,UACA,SACA,UAAiC,gBACV;CACvB,MAAM,EAAE,IAAI,WAAW;CACvB,MAAM,aAAa,QAAQ,cAAc;CAGzC,MAAM,gBAAgB,kBAAkB,QAAQ,cAAc;CAC9D,MAAM,cAAc,gBAAgB,QAAQ,cAAc;CAC1D,MAAM,WAAW,WAAW,cAAc;CAC1C,MAAM,cAAc,WAAW,eAAe,YAAY,GAAG;CAE7D,MAAMC,MAAoB;EACxB;EACA;EACA,YAAY;EACZ;EACA,SAAS,QAAQ,WAAW;EAC7B;AAGD,KAAI,UAAU;EACZ,MAAM,UAAU,cAAc,cAAc,YAAY,KAAK;AAC7D,SAAO,KAAK,oBAAoB,UAAU;;CAG5C,MAAMC,aAA4B,EAAE;AAGpC,MAAK,MAAM,YAAY,YAAY;AACjC,MAAI,QAAQ,UAAU,aAAa,KACjC;AAGF,SAAO,KAAK,YAAY,sBAAsB,UAAU,KAAK;EAE7D,MAAM,kBAAkB,MAAM,kBAAkB,UAAU,IAAI,KAAK,QAAQ;AAG3E,OAAK,MAAM,UAAU,iBAAiB;AACpC,OACE,OAAO,QACN,OAAO,WAAW,UAAU,OAAO,WAAW,SAQ/C;QANkB,QAAQ,UACtB,OACA,MAAM,QAAQ,QACZ,QAAQ,OAAO,KAAK,KAAK,OAAO,IAAI,cACrC,EAEU;AACb,YAAO,KAAK,iBAAiB,OAAO,IAAI,cAAc;KACtD,MAAM,YAAY,MAAM,OAAO,IAAI,OAAO;AAE1C,SAAI,UAAU,SAAS;AACrB,aAAO,KAAK,KAAK,UAAU,UAAU;AAErC,aAAO,SAAS;AAChB,aAAO,UAAU,UAAU,UAAU;AACrC,aAAO,MAAM;WAEb,QAAO,KAAK,KAAK,UAAU,UAAU;;;AAK3C,cAAW,KAAK,OAAO;;;CAK3B,MAAM,SAAS,WAAW,QAAQ,MAAM,EAAE,WAAW,OAAO,CAAC;CAC7D,MAAM,WAAW,WAAW,QAAQ,MAAM,EAAE,WAAW,OAAO,CAAC;CAC/D,MAAM,WAAW,WAAW,QAAQ,MAAM,EAAE,WAAW,OAAO,CAAC;AAG/D,QAAO;EACL,QAAQ;EACR;EACA;EACA;EACA,SAPc,WAAW,QAAQ,MAAM,EAAE,WAAW,OAAO,CAAC;EAQ5D,SAAS,aAAa;EACvB;;;;;AAMH,eAAe,kBACb,UACA,IACA,KACA,SACwB;AACxB,SAAQ,UAAR;EACE,KAAK,MACH,QAAO,aAAa,IAAI,IAAI;EAC9B,KAAK,SACH,QAAO,gBAAgB,IAAI,IAAI;EACjC,KAAK,MACH,QAAO,aAAa,IAAI,IAAI;EAC9B,KAAK,OACH,QAAO,cAAc,IAAI,IAAI;EAC/B,KAAK,YACH,QAAO,mBAAmB,IAAI,IAAI;EACpC,KAAK,KACH,QAAO,YAAY,IAAI,KAAK,QAAQ;EACtC,KAAK,SACH,QAAO,eAAe,IAAI,IAAI;EAChC,QACE,QAAO,EAAE;;;;;;AAOf,SAAgB,oBAAoB,QAA8B;CAChE,MAAMC,QAAkB,EAAE;AAE1B,OAAM,KAAK,GAAG;AACd,OAAM,KAAK,+BAA+B;AAC1C,OAAM,KAAK,GAAG;AAEd,KAAI,OAAO,QACT,OAAM,KAAK,uBAAuB;KAElC,OAAM,KAAK,sBAAsB;AAGnC,OAAM,KAAK,GAAG;AACd,OAAM,KAAK,eAAe,OAAO,SAAS;AAC1C,OAAM,KAAK,eAAe,OAAO,WAAW;AAC5C,OAAM,KAAK,eAAe,OAAO,WAAW;AAC5C,OAAM,KAAK,eAAe,OAAO,UAAU;AAE3C,QAAO,MAAM,KAAK,KAAK;;;;;AAMzB,SAAgB,kBAAkB,QAA6B;CAU7D,IAAI,OAAO,GART,OAAO,WAAW,SACd,MACA,OAAO,WAAW,SAChB,MACA,OAAO,WAAW,SAChB,MACA,IAES,GAAG,OAAO,KAAK,IAAI,OAAO;AAE7C,KAAI,OAAO,QACT,SAAQ,SAAS,OAAO;AAG1B,KAAI,OAAO,IACT,SAAQ,wBAAwB,OAAO,IAAI;AAG7C,QAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/services/doctor/types.ts"],"sourcesContent":[],"mappings":";;AASA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/services/doctor/types.ts"],"sourcesContent":[],"mappings":";;AASA;AAYA;AAaA;AAaA;AAKA;AAUA;AAUA;AAEY,KAjEA,aAAA,GAiEA,KAAA,GAAA,QAAA,GAAA,KAAA,GAAA,MAAA,GAAA,WAAA,GAAA,IAAA,GAAA,QAAA;;;;AAgBK,cArEJ,oBAyEE,EAzEoB,aAyEP,EAAA;AAY5B;AAkBA;AAUA;cApGa,uBAAuB,OAAO;;;;KAa/B,WAAA;;;;UAKK,SAAA;;;;;;;;;UAUA,SAAA;;;;eAIF,QAAQ;;;;;UAMN,WAAA;;YAEL;;;;UAIF;;;;QAIF;;;;;;;UAQS,aAAA;;;;eAIF;;;;;;;;;;;UAYE,YAAA;;UAEP;;;;;;;;;;;;;;;UAgBO,qBAAA;;gCAEe;;;;QAEgC;;;;;UAM/C,YAAA"}
|
|
@@ -8,7 +8,8 @@ const ALL_CHECK_CATEGORIES = [
|
|
|
8
8
|
"mcp",
|
|
9
9
|
"deps",
|
|
10
10
|
"workspace",
|
|
11
|
-
"ai"
|
|
11
|
+
"ai",
|
|
12
|
+
"layers"
|
|
12
13
|
];
|
|
13
14
|
/**
|
|
14
15
|
* Human-readable labels for check categories.
|
|
@@ -19,7 +20,8 @@ const CHECK_CATEGORY_LABELS = {
|
|
|
19
20
|
mcp: "MCP Server",
|
|
20
21
|
deps: "Dependencies",
|
|
21
22
|
workspace: "Workspace Structure",
|
|
22
|
-
ai: "AI Provider"
|
|
23
|
+
ai: "AI Provider",
|
|
24
|
+
layers: "Contract Layers"
|
|
23
25
|
};
|
|
24
26
|
|
|
25
27
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":["ALL_CHECK_CATEGORIES: CheckCategory[]","CHECK_CATEGORY_LABELS: Record<CheckCategory, string>"],"sources":["../../../src/services/doctor/types.ts"],"sourcesContent":["/**\n * Doctor service types.\n *\n * Types for health checks and auto-fix functionality.\n */\n\n/**\n * Categories of health checks.\n */\nexport type CheckCategory =\n | 'cli'\n | 'config'\n | 'mcp'\n | 'deps'\n | 'workspace'\n | 'ai';\n\n/**\n * All available check categories.\n */\nexport const ALL_CHECK_CATEGORIES: CheckCategory[] = [\n 'cli',\n 'config',\n 'mcp',\n 'deps',\n 'workspace',\n 'ai',\n];\n\n/**\n * Human-readable labels for check categories.\n */\nexport const CHECK_CATEGORY_LABELS: Record<CheckCategory, string> = {\n cli: 'CLI Installation',\n config: 'Configuration Files',\n mcp: 'MCP Server',\n deps: 'Dependencies',\n workspace: 'Workspace Structure',\n ai: 'AI Provider',\n};\n\n/**\n * Status of a health check.\n */\nexport type CheckStatus = 'pass' | 'warn' | 'fail' | 'skip';\n\n/**\n * Result of applying a fix.\n */\nexport interface FixResult {\n /** Whether the fix was successful. */\n success: boolean;\n /** Message describing the result. */\n message: string;\n}\n\n/**\n * An action that can fix a failed check.\n */\nexport interface FixAction {\n /** Description of what the fix will do. */\n description: string;\n /** Function to apply the fix. */\n apply: () => Promise<FixResult>;\n}\n\n/**\n * Result of a single health check.\n */\nexport interface CheckResult {\n /** Category of the check. */\n category: CheckCategory;\n /** Name of the specific check. */\n name: string;\n /** Status of the check. */\n status: CheckStatus;\n /** Human-readable message. */\n message: string;\n /** Optional fix action if status is 'fail' or 'warn'. */\n fix?: FixAction;\n /** Additional details for debugging. */\n details?: string;\n}\n\n/**\n * Options for running the doctor.\n */\nexport interface DoctorOptions {\n /** Root directory of the workspace. */\n workspaceRoot: string;\n /** Categories to check (defaults to all). */\n categories?: CheckCategory[];\n /** If true, auto-apply fixes without prompting. */\n autoFix?: boolean;\n /** Skip AI provider checks. */\n skipAi?: boolean;\n /** Verbose output. */\n verbose?: boolean;\n}\n\n/**\n * Summary of doctor results.\n */\nexport interface DoctorResult {\n /** All check results. */\n checks: CheckResult[];\n /** Number of passing checks. */\n passed: number;\n /** Number of warnings. */\n warnings: number;\n /** Number of failures. */\n failures: number;\n /** Number of skipped checks. */\n skipped: number;\n /** Overall health status. */\n healthy: boolean;\n}\n\n/**\n * Callback for interactive prompts during doctor.\n */\nexport interface DoctorPromptCallbacks {\n /** Confirm a fix action. */\n confirm: (message: string) => Promise<boolean>;\n /** Input a value (e.g., API key). */\n input: (message: string, options?: { password?: boolean }) => Promise<string>;\n}\n\n/**\n * Context passed to check functions.\n */\nexport interface CheckContext {\n /** Workspace root path (monorepo root or single project root). */\n workspaceRoot: string;\n /** Current package root (may differ from workspaceRoot in monorepos). */\n packageRoot: string;\n /** Whether this is a monorepo. */\n isMonorepo: boolean;\n /** Current package name (if in a monorepo package). */\n packageName?: string;\n /** Whether verbose output is enabled. */\n verbose: boolean;\n}\n"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"types.js","names":["ALL_CHECK_CATEGORIES: CheckCategory[]","CHECK_CATEGORY_LABELS: Record<CheckCategory, string>"],"sources":["../../../src/services/doctor/types.ts"],"sourcesContent":["/**\n * Doctor service types.\n *\n * Types for health checks and auto-fix functionality.\n */\n\n/**\n * Categories of health checks.\n */\nexport type CheckCategory =\n | 'cli'\n | 'config'\n | 'mcp'\n | 'deps'\n | 'workspace'\n | 'ai'\n | 'layers';\n\n/**\n * All available check categories.\n */\nexport const ALL_CHECK_CATEGORIES: CheckCategory[] = [\n 'cli',\n 'config',\n 'mcp',\n 'deps',\n 'workspace',\n 'ai',\n 'layers',\n];\n\n/**\n * Human-readable labels for check categories.\n */\nexport const CHECK_CATEGORY_LABELS: Record<CheckCategory, string> = {\n cli: 'CLI Installation',\n config: 'Configuration Files',\n mcp: 'MCP Server',\n deps: 'Dependencies',\n workspace: 'Workspace Structure',\n ai: 'AI Provider',\n layers: 'Contract Layers',\n};\n\n/**\n * Status of a health check.\n */\nexport type CheckStatus = 'pass' | 'warn' | 'fail' | 'skip';\n\n/**\n * Result of applying a fix.\n */\nexport interface FixResult {\n /** Whether the fix was successful. */\n success: boolean;\n /** Message describing the result. */\n message: string;\n}\n\n/**\n * An action that can fix a failed check.\n */\nexport interface FixAction {\n /** Description of what the fix will do. */\n description: string;\n /** Function to apply the fix. */\n apply: () => Promise<FixResult>;\n}\n\n/**\n * Result of a single health check.\n */\nexport interface CheckResult {\n /** Category of the check. */\n category: CheckCategory;\n /** Name of the specific check. */\n name: string;\n /** Status of the check. */\n status: CheckStatus;\n /** Human-readable message. */\n message: string;\n /** Optional fix action if status is 'fail' or 'warn'. */\n fix?: FixAction;\n /** Additional details for debugging. */\n details?: string;\n}\n\n/**\n * Options for running the doctor.\n */\nexport interface DoctorOptions {\n /** Root directory of the workspace. */\n workspaceRoot: string;\n /** Categories to check (defaults to all). */\n categories?: CheckCategory[];\n /** If true, auto-apply fixes without prompting. */\n autoFix?: boolean;\n /** Skip AI provider checks. */\n skipAi?: boolean;\n /** Verbose output. */\n verbose?: boolean;\n}\n\n/**\n * Summary of doctor results.\n */\nexport interface DoctorResult {\n /** All check results. */\n checks: CheckResult[];\n /** Number of passing checks. */\n passed: number;\n /** Number of warnings. */\n warnings: number;\n /** Number of failures. */\n failures: number;\n /** Number of skipped checks. */\n skipped: number;\n /** Overall health status. */\n healthy: boolean;\n}\n\n/**\n * Callback for interactive prompts during doctor.\n */\nexport interface DoctorPromptCallbacks {\n /** Confirm a fix action. */\n confirm: (message: string) => Promise<boolean>;\n /** Input a value (e.g., API key). */\n input: (message: string, options?: { password?: boolean }) => Promise<string>;\n}\n\n/**\n * Context passed to check functions.\n */\nexport interface CheckContext {\n /** Workspace root path (monorepo root or single project root). */\n workspaceRoot: string;\n /** Current package root (may differ from workspaceRoot in monorepos). */\n packageRoot: string;\n /** Whether this is a monorepo. */\n isMonorepo: boolean;\n /** Current package name (if in a monorepo package). */\n packageName?: string;\n /** Whether verbose output is enabled. */\n verbose: boolean;\n}\n"],"mappings":";;;;AAqBA,MAAaA,uBAAwC;CACnD;CACA;CACA;CACA;CACA;CACA;CACA;CACD;;;;AAKD,MAAaC,wBAAuD;CAClE,KAAK;CACL,QAAQ;CACR,KAAK;CACL,MAAM;CACN,WAAW;CACX,IAAI;CACJ,QAAQ;CACT"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region src/services/formatter.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Formatter service.
|
|
4
|
+
*/
|
|
5
|
+
interface FormatterOptions {
|
|
6
|
+
type?: string;
|
|
7
|
+
cwd?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Format files using the configured formatter (defaulting to prettier).
|
|
11
|
+
*/
|
|
12
|
+
declare function formatFiles(files: string[], _configResolver?: unknown, options?: FormatterOptions): Promise<void>;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { FormatterOptions, formatFiles };
|
|
15
|
+
//# sourceMappingURL=formatter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formatter.d.ts","names":[],"sources":["../../src/services/formatter.ts"],"sourcesContent":[],"mappings":";;AASA;AAQA;UARiB,gBAAA;;;;;;;iBAQK,WAAA,uDAGX,mBACR"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { exec } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
|
|
4
|
+
//#region src/services/formatter.ts
|
|
5
|
+
/**
|
|
6
|
+
* Formatter service.
|
|
7
|
+
*/
|
|
8
|
+
const execAsync = promisify(exec);
|
|
9
|
+
/**
|
|
10
|
+
* Format files using the configured formatter (defaulting to prettier).
|
|
11
|
+
*/
|
|
12
|
+
async function formatFiles(files, _configResolver, options = {}) {
|
|
13
|
+
if (files.length === 0) return;
|
|
14
|
+
const cwd = options.cwd ?? process.cwd();
|
|
15
|
+
const FILE_CHUNK_SIZE = 50;
|
|
16
|
+
for (let i = 0; i < files.length; i += FILE_CHUNK_SIZE) {
|
|
17
|
+
const fileArgs = files.slice(i, i + FILE_CHUNK_SIZE).map((f) => `"${f}"`).join(" ");
|
|
18
|
+
try {
|
|
19
|
+
await execAsync(`npx prettier --write ${fileArgs}`, { cwd });
|
|
20
|
+
} catch (_error) {}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
//#endregion
|
|
25
|
+
export { formatFiles };
|
|
26
|
+
//# sourceMappingURL=formatter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formatter.js","names":[],"sources":["../../src/services/formatter.ts"],"sourcesContent":["/**\n * Formatter service.\n */\n\nimport { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\n\nconst execAsync = promisify(exec);\n\nexport interface FormatterOptions {\n type?: string;\n cwd?: string;\n}\n\n/**\n * Format files using the configured formatter (defaulting to prettier).\n */\nexport async function formatFiles(\n files: string[],\n _configResolver?: unknown,\n options: FormatterOptions = {}\n): Promise<void> {\n if (files.length === 0) return;\n\n const cwd = options.cwd ?? process.cwd();\n\n // Basic implementation: run prettier on the files\n // We assume prettier is available in the project\n\n // Group files by chunks to avoid command line length limits\n const FILE_CHUNK_SIZE = 50;\n\n for (let i = 0; i < files.length; i += FILE_CHUNK_SIZE) {\n const chunk = files.slice(i, i + FILE_CHUNK_SIZE);\n // Quote paths\n const fileArgs = chunk.map((f) => `\"${f}\"`).join(' ');\n\n try {\n // Try npx prettier first\n await execAsync(`npx prettier --write ${fileArgs}`, { cwd });\n } catch (_error) {\n // Fallback or ignore\n // console.warn('Prettier formatting failed:', error);\n }\n }\n}\n"],"mappings":";;;;;;;AAOA,MAAM,YAAY,UAAU,KAAK;;;;AAUjC,eAAsB,YACpB,OACA,iBACA,UAA4B,EAAE,EACf;AACf,KAAI,MAAM,WAAW,EAAG;CAExB,MAAM,MAAM,QAAQ,OAAO,QAAQ,KAAK;CAMxC,MAAM,kBAAkB;AAExB,MAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,iBAAiB;EAGtD,MAAM,WAFQ,MAAM,MAAM,GAAG,IAAI,gBAAgB,CAE1B,KAAK,MAAM,IAAI,EAAE,GAAG,CAAC,KAAK,IAAI;AAErD,MAAI;AAEF,SAAM,UAAU,wBAAwB,YAAY,EAAE,KAAK,CAAC;WACrD,QAAQ"}
|
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
import { CheckRunPayload, PrCommentOptions } from "./types.js";
|
|
2
|
-
import {
|
|
2
|
+
import { ImpactResult } from "@contractspec/module.workspace";
|
|
3
3
|
|
|
4
4
|
//#region src/services/impact/formatters.d.ts
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Format impact result as a PR comment (markdown).
|
|
8
8
|
*/
|
|
9
|
-
declare function formatPrComment(result:
|
|
9
|
+
declare function formatPrComment(result: ImpactResult, options?: PrCommentOptions): string;
|
|
10
10
|
/**
|
|
11
11
|
* Format impact result for minimal PR comment.
|
|
12
12
|
*/
|
|
13
|
-
declare function formatMinimalComment(result:
|
|
13
|
+
declare function formatMinimalComment(result: ImpactResult): string;
|
|
14
14
|
/**
|
|
15
15
|
* Format impact result as GitHub check run payload.
|
|
16
16
|
*/
|
|
17
|
-
declare function formatCheckRun(result:
|
|
17
|
+
declare function formatCheckRun(result: ImpactResult, headSha: string, options?: {
|
|
18
18
|
key?: string;
|
|
19
19
|
failOnBreaking?: boolean;
|
|
20
20
|
}): CheckRunPayload;
|
|
21
21
|
/**
|
|
22
22
|
* Format impact result as JSON.
|
|
23
23
|
*/
|
|
24
|
-
declare function formatJson(result:
|
|
24
|
+
declare function formatJson(result: ImpactResult): string;
|
|
25
25
|
//#endregion
|
|
26
26
|
export { formatCheckRun, formatJson, formatMinimalComment, formatPrComment };
|
|
27
27
|
//# sourceMappingURL=formatters.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formatters.d.ts","names":[],"sources":["../../../src/services/impact/formatters.ts"],"sourcesContent":[],"mappings":";;;;;;;;iBAegB,eAAA,SACN,
|
|
1
|
+
{"version":3,"file":"formatters.d.ts","names":[],"sources":["../../../src/services/impact/formatters.ts"],"sourcesContent":[],"mappings":";;;;;;;;iBAegB,eAAA,SACN,wBACC;;;;iBAyGK,oBAAA,SAA6B;;;;iBAa7B,cAAA,SACN;;;IAGP;;;;iBA2Ca,UAAA,SAAmB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formatters.js","names":["lines: string[]","conclusion: 'success' | 'failure' | 'neutral'","title: string"],"sources":["../../../src/services/impact/formatters.ts"],"sourcesContent":["/**\n * Impact result formatters.\n *\n * Formats impact detection results for various outputs:\n * - PR comments (markdown)\n * - GitHub check runs\n * - JSON output\n */\n\nimport type { ImpactResult } from '
|
|
1
|
+
{"version":3,"file":"formatters.js","names":["lines: string[]","conclusion: 'success' | 'failure' | 'neutral'","title: string"],"sources":["../../../src/services/impact/formatters.ts"],"sourcesContent":["/**\n * Impact result formatters.\n *\n * Formats impact detection results for various outputs:\n * - PR comments (markdown)\n * - GitHub check runs\n * - JSON output\n */\n\nimport type { ImpactResult } from '@contractspec/module.workspace';\nimport type { CheckRunPayload, PrCommentOptions } from './types';\n\n/**\n * Format impact result as a PR comment (markdown).\n */\nexport function formatPrComment(\n result: ImpactResult,\n options: PrCommentOptions = { template: 'detailed' }\n): string {\n const lines: string[] = [];\n\n // Header with status\n lines.push('## 📋 ContractSpec Impact Analysis');\n lines.push('');\n\n // Status badge\n if (result.hasBreaking) {\n lines.push('❌ **Breaking changes detected**');\n } else if (result.hasNonBreaking) {\n lines.push('⚠️ **Contract changed (non-breaking)**');\n } else {\n lines.push('✅ **No contract impact**');\n }\n lines.push('');\n\n // Summary\n if (\n result.summary.breaking > 0 ||\n result.summary.nonBreaking > 0 ||\n result.summary.info > 0\n ) {\n lines.push('### Summary');\n lines.push('');\n lines.push(`| Type | Count |`);\n lines.push(`|------|-------|`);\n if (result.summary.breaking > 0) {\n lines.push(`| 🔴 Breaking | ${result.summary.breaking} |`);\n }\n if (result.summary.nonBreaking > 0) {\n lines.push(`| 🟡 Non-breaking | ${result.summary.nonBreaking} |`);\n }\n if (result.summary.info > 0) {\n lines.push(`| 🔵 Info | ${result.summary.info} |`);\n }\n if (result.summary.added > 0) {\n lines.push(`| ➕ Added | ${result.summary.added} |`);\n }\n if (result.summary.removed > 0) {\n lines.push(`| ➖ Removed | ${result.summary.removed} |`);\n }\n lines.push('');\n }\n\n // Detailed changes (if detailed template)\n if (options.template === 'detailed' && result.deltas.length > 0) {\n lines.push('### Changes');\n lines.push('');\n\n // Group by severity\n const breaking = result.deltas.filter((d) => d.severity === 'breaking');\n const nonBreaking = result.deltas.filter(\n (d) => d.severity === 'non_breaking'\n );\n\n if (breaking.length > 0) {\n lines.push('#### 🔴 Breaking Changes');\n lines.push('');\n for (const delta of breaking) {\n lines.push(`- **${delta.specKey}**: ${delta.description}`);\n }\n lines.push('');\n }\n\n if (nonBreaking.length > 0) {\n lines.push('#### 🟡 Non-breaking Changes');\n lines.push('');\n for (const delta of nonBreaking) {\n lines.push(`- **${delta.specKey}**: ${delta.description}`);\n }\n lines.push('');\n }\n }\n\n // Added/removed specs\n if (result.addedSpecs.length > 0) {\n lines.push('### Added Specs');\n lines.push('');\n for (const spec of result.addedSpecs) {\n lines.push(`- \\`${spec.key}\\` v${spec.version} (${spec.type})`);\n }\n lines.push('');\n }\n\n if (result.removedSpecs.length > 0) {\n lines.push('### Removed Specs');\n lines.push('');\n for (const spec of result.removedSpecs) {\n lines.push(`- \\`${spec.key}\\` v${spec.version} (${spec.type})`);\n }\n lines.push('');\n }\n\n // Footer\n lines.push('---');\n lines.push(`*Generated by ContractSpec at ${result.timestamp}*`);\n\n return lines.join('\\n');\n}\n\n/**\n * Format impact result for minimal PR comment.\n */\nexport function formatMinimalComment(result: ImpactResult): string {\n if (result.hasBreaking) {\n return `❌ **Breaking changes detected** (${result.summary.breaking} breaking, ${result.summary.nonBreaking} non-breaking)`;\n }\n if (result.hasNonBreaking) {\n return `⚠️ **Contract changed** (${result.summary.nonBreaking} non-breaking changes)`;\n }\n return '✅ **No contract impact**';\n}\n\n/**\n * Format impact result as GitHub check run payload.\n */\nexport function formatCheckRun(\n result: ImpactResult,\n headSha: string,\n options: { key?: string; failOnBreaking?: boolean } = {}\n): CheckRunPayload {\n const name = options.key ?? 'ContractSpec Impact';\n const failOnBreaking = options.failOnBreaking ?? true;\n\n let conclusion: 'success' | 'failure' | 'neutral';\n let title: string;\n\n if (result.hasBreaking) {\n conclusion = failOnBreaking ? 'failure' : 'neutral';\n title = `Breaking changes detected (${result.summary.breaking})`;\n } else if (result.hasNonBreaking) {\n conclusion = 'success';\n title = `Non-breaking changes (${result.summary.nonBreaking})`;\n } else {\n conclusion = 'success';\n title = 'No contract impact';\n }\n\n const summary = formatMinimalComment(result);\n\n return {\n name,\n headSha,\n conclusion,\n title,\n summary,\n annotations: result.deltas\n .filter((d) => d.severity === 'breaking')\n .slice(0, 50) // GitHub limit\n .map((d) => ({\n path: d.path,\n startLine: 1,\n endLine: 1,\n annotationLevel: 'failure' as const,\n message: d.description,\n title: `Breaking: ${d.rule}`,\n })),\n };\n}\n\n/**\n * Format impact result as JSON.\n */\nexport function formatJson(result: ImpactResult): string {\n return JSON.stringify(result, null, 2);\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { classifyImpact, computeIoDiff, generateSnapshot } from "@contractspec/module.workspace";
|
|
2
2
|
|
|
3
3
|
//#region src/services/impact/impact-detection-service.ts
|
|
4
4
|
/**
|
|
@@ -23,17 +23,17 @@ async function detectImpact(adapters, options = {}) {
|
|
|
23
23
|
cwd: workspaceRoot
|
|
24
24
|
})).filter((f) => !f.includes(".test.") && !f.includes(".spec.") && !f.includes("node_modules"));
|
|
25
25
|
logger.debug(`Found ${specFiles.length} spec files`);
|
|
26
|
-
const headSnapshot =
|
|
26
|
+
const headSnapshot = generateSnapshot(await loadSpecs(fs, specFiles, workspaceRoot));
|
|
27
27
|
let baseSnapshot;
|
|
28
|
-
if (options.baseline) baseSnapshot =
|
|
28
|
+
if (options.baseline) baseSnapshot = generateSnapshot(await loadBaselineSpecs(fs, git, specFiles, options.baseline, workspaceRoot));
|
|
29
29
|
else baseSnapshot = {
|
|
30
|
-
version: 1,
|
|
30
|
+
version: "1.0.0",
|
|
31
31
|
generatedAt: "",
|
|
32
32
|
specs: [],
|
|
33
33
|
hash: ""
|
|
34
34
|
};
|
|
35
35
|
const diffs = computeSnapshotDiffs(baseSnapshot.specs, headSnapshot.specs);
|
|
36
|
-
const impactResult =
|
|
36
|
+
const impactResult = classifyImpact(baseSnapshot.specs, headSnapshot.specs, diffs);
|
|
37
37
|
logger.info("Impact detection complete", {
|
|
38
38
|
status: impactResult.status,
|
|
39
39
|
breaking: impactResult.summary.breaking,
|
|
@@ -84,7 +84,7 @@ function computeSnapshotDiffs(baseSpecs, headSpecs) {
|
|
|
84
84
|
for (const [key, headSpec] of headMap) {
|
|
85
85
|
const baseSpec = baseMap.get(key);
|
|
86
86
|
if (baseSpec && headSpec.type === "operation" && baseSpec.type === "operation") {
|
|
87
|
-
const ioDiffs =
|
|
87
|
+
const ioDiffs = computeIoDiff(baseSpec.io, headSpec.io);
|
|
88
88
|
diffs.push(...ioDiffs);
|
|
89
89
|
}
|
|
90
90
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"impact-detection-service.js","names":["baseSnapshot: ContractSnapshot","specs: { path: string; content: string }[]","diffs: ReturnType<typeof computeIoDiff>"],"sources":["../../../src/services/impact/impact-detection-service.ts"],"sourcesContent":["/**\n * Impact detection service.\n *\n * Orchestrates contract snapshot generation, diff computation,\n * and impact classification for CI/CD pipelines.\n */\n\n// Note: Using internal imports from the bundle which re-exports from module\nimport {\n classifyImpact,\n computeIoDiff,\n generateSnapshot,\n type ContractSnapshot,\n type SpecSnapshot,\n} from '
|
|
1
|
+
{"version":3,"file":"impact-detection-service.js","names":["baseSnapshot: ContractSnapshot","specs: { path: string; content: string }[]","diffs: ReturnType<typeof computeIoDiff>"],"sources":["../../../src/services/impact/impact-detection-service.ts"],"sourcesContent":["/**\n * Impact detection service.\n *\n * Orchestrates contract snapshot generation, diff computation,\n * and impact classification for CI/CD pipelines.\n */\n\n// Note: Using internal imports from the bundle which re-exports from module\nimport {\n classifyImpact,\n computeIoDiff,\n generateSnapshot,\n type ContractSnapshot,\n type SpecSnapshot,\n} from '@contractspec/module.workspace';\nimport type { FsAdapter } from '../../ports/fs';\nimport type { GitAdapter } from '../../ports/git';\nimport type { LoggerAdapter } from '../../ports/logger';\nimport type { ImpactDetectionOptions, ImpactDetectionResult } from './types';\n\n/**\n * Detect the impact of contract changes between baseline and current state.\n *\n * @param adapters - Required adapters (fs, git, logger)\n * @param options - Detection options\n * @returns Impact detection result\n */\nexport async function detectImpact(\n adapters: { fs: FsAdapter; git: GitAdapter; logger: LoggerAdapter },\n options: ImpactDetectionOptions = {}\n): Promise<ImpactDetectionResult> {\n const { fs, git, logger } = adapters;\n const workspaceRoot = options.workspaceRoot ?? process.cwd();\n\n logger.info('Starting impact detection...', { baseline: options.baseline });\n\n // Discover spec files\n const files = await fs.glob({\n pattern: options.pattern ?? '**/*.{operation,event}.ts',\n cwd: workspaceRoot,\n });\n\n const specFiles = files.filter(\n (f) =>\n !f.includes('.test.') &&\n !f.includes('.spec.') &&\n !f.includes('node_modules')\n );\n\n logger.debug(`Found ${specFiles.length} spec files`);\n\n // Load current (head) specs\n const headSpecs = await loadSpecs(fs, specFiles, workspaceRoot);\n const headSnapshot = generateSnapshot(headSpecs);\n\n // Load baseline specs if specified\n let baseSnapshot: ContractSnapshot;\n if (options.baseline) {\n const baseSpecs = await loadBaselineSpecs(\n fs,\n git,\n specFiles,\n options.baseline,\n workspaceRoot\n );\n baseSnapshot = generateSnapshot(baseSpecs);\n } else {\n // No baseline means all specs are \"new\"\n baseSnapshot = { version: '1.0.0', generatedAt: '', specs: [], hash: '' };\n }\n\n // Compute diffs between snapshots\n const diffs = computeSnapshotDiffs(baseSnapshot.specs, headSnapshot.specs);\n\n // Classify the impact\n const impactResult = classifyImpact(\n baseSnapshot.specs,\n headSnapshot.specs,\n diffs\n );\n\n logger.info('Impact detection complete', {\n status: impactResult.status,\n breaking: impactResult.summary.breaking,\n nonBreaking: impactResult.summary.nonBreaking,\n });\n\n return {\n ...impactResult,\n workspaceRoot,\n specsAnalyzed: specFiles.length,\n baseRef: options.baseline,\n };\n}\n\n/**\n * Load specs from current filesystem.\n */\nasync function loadSpecs(\n fs: FsAdapter,\n files: string[],\n _workspaceRoot: string\n): Promise<{ path: string; content: string }[]> {\n const specs: { path: string; content: string }[] = [];\n\n for (const file of files) {\n const content = await fs.readFile(file);\n specs.push({ path: file, content });\n }\n\n return specs;\n}\n\n/**\n * Load specs from git baseline.\n */\nasync function loadBaselineSpecs(\n _fs: FsAdapter,\n git: GitAdapter,\n files: string[],\n baseline: string,\n _workspaceRoot: string\n): Promise<{ path: string; content: string }[]> {\n const specs: { path: string; content: string }[] = [];\n\n for (const file of files) {\n try {\n const content = await git.showFile(baseline, file);\n specs.push({ path: file, content });\n } catch {\n // File doesn't exist in baseline - skip (it's new)\n }\n }\n\n return specs;\n}\n\n/**\n * Compute diffs between two sets of spec snapshots.\n */\nfunction computeSnapshotDiffs(\n baseSpecs: SpecSnapshot[],\n headSpecs: SpecSnapshot[]\n): ReturnType<typeof computeIoDiff> {\n const diffs: ReturnType<typeof computeIoDiff> = [];\n\n const baseMap = new Map(baseSpecs.map((s) => [`${s.key}@${s.version}`, s]));\n const headMap = new Map(headSpecs.map((s) => [`${s.key}@${s.version}`, s]));\n\n // Compare specs that exist in both\n for (const [key, headSpec] of headMap) {\n const baseSpec = baseMap.get(key);\n if (\n baseSpec &&\n headSpec.type === 'operation' &&\n baseSpec.type === 'operation'\n ) {\n const ioDiffs = computeIoDiff(baseSpec.io, headSpec.io);\n diffs.push(...ioDiffs);\n }\n }\n\n return diffs;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA2BA,eAAsB,aACpB,UACA,UAAkC,EAAE,EACJ;CAChC,MAAM,EAAE,IAAI,KAAK,WAAW;CAC5B,MAAM,gBAAgB,QAAQ,iBAAiB,QAAQ,KAAK;AAE5D,QAAO,KAAK,gCAAgC,EAAE,UAAU,QAAQ,UAAU,CAAC;CAQ3E,MAAM,aALQ,MAAM,GAAG,KAAK;EAC1B,SAAS,QAAQ,WAAW;EAC5B,KAAK;EACN,CAAC,EAEsB,QACrB,MACC,CAAC,EAAE,SAAS,SAAS,IACrB,CAAC,EAAE,SAAS,SAAS,IACrB,CAAC,EAAE,SAAS,eAAe,CAC9B;AAED,QAAO,MAAM,SAAS,UAAU,OAAO,aAAa;CAIpD,MAAM,eAAe,iBADH,MAAM,UAAU,IAAI,WAAW,cAAc,CACf;CAGhD,IAAIA;AACJ,KAAI,QAAQ,SAQV,gBAAe,iBAPG,MAAM,kBACtB,IACA,KACA,WACA,QAAQ,UACR,cACD,CACyC;KAG1C,gBAAe;EAAE,SAAS;EAAS,aAAa;EAAI,OAAO,EAAE;EAAE,MAAM;EAAI;CAI3E,MAAM,QAAQ,qBAAqB,aAAa,OAAO,aAAa,MAAM;CAG1E,MAAM,eAAe,eACnB,aAAa,OACb,aAAa,OACb,MACD;AAED,QAAO,KAAK,6BAA6B;EACvC,QAAQ,aAAa;EACrB,UAAU,aAAa,QAAQ;EAC/B,aAAa,aAAa,QAAQ;EACnC,CAAC;AAEF,QAAO;EACL,GAAG;EACH;EACA,eAAe,UAAU;EACzB,SAAS,QAAQ;EAClB;;;;;AAMH,eAAe,UACb,IACA,OACA,gBAC8C;CAC9C,MAAMC,QAA6C,EAAE;AAErD,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,UAAU,MAAM,GAAG,SAAS,KAAK;AACvC,QAAM,KAAK;GAAE,MAAM;GAAM;GAAS,CAAC;;AAGrC,QAAO;;;;;AAMT,eAAe,kBACb,KACA,KACA,OACA,UACA,gBAC8C;CAC9C,MAAMA,QAA6C,EAAE;AAErD,MAAK,MAAM,QAAQ,MACjB,KAAI;EACF,MAAM,UAAU,MAAM,IAAI,SAAS,UAAU,KAAK;AAClD,QAAM,KAAK;GAAE,MAAM;GAAM;GAAS,CAAC;SAC7B;AAKV,QAAO;;;;;AAMT,SAAS,qBACP,WACA,WACkC;CAClC,MAAMC,QAA0C,EAAE;CAElD,MAAM,UAAU,IAAI,IAAI,UAAU,KAAK,MAAM,CAAC,GAAG,EAAE,IAAI,GAAG,EAAE,WAAW,EAAE,CAAC,CAAC;CAC3E,MAAM,UAAU,IAAI,IAAI,UAAU,KAAK,MAAM,CAAC,GAAG,EAAE,IAAI,GAAG,EAAE,WAAW,EAAE,CAAC,CAAC;AAG3E,MAAK,MAAM,CAAC,KAAK,aAAa,SAAS;EACrC,MAAM,WAAW,QAAQ,IAAI,IAAI;AACjC,MACE,YACA,SAAS,SAAS,eAClB,SAAS,SAAS,aAClB;GACA,MAAM,UAAU,cAAc,SAAS,IAAI,SAAS,GAAG;AACvD,SAAM,KAAK,GAAG,QAAQ;;;AAI1B,QAAO"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ImpactResult } from "@contractspec/module.workspace";
|
|
1
|
+
import { ImpactResult as ImpactResult$1 } from "@contractspec/module.workspace";
|
|
2
2
|
|
|
3
3
|
//#region src/services/impact/types.d.ts
|
|
4
4
|
|
|
@@ -12,7 +12,7 @@ interface ImpactDetectionOptions {
|
|
|
12
12
|
workspaceRoot?: string;
|
|
13
13
|
}
|
|
14
14
|
/** Impact detection result with additional context */
|
|
15
|
-
interface ImpactDetectionResult extends ImpactResult {
|
|
15
|
+
interface ImpactDetectionResult extends ImpactResult$1 {
|
|
16
16
|
/** Working directory */
|
|
17
17
|
workspaceRoot: string;
|
|
18
18
|
/** Number of specs analyzed */
|
|
@@ -54,5 +54,5 @@ interface CheckRunAnnotation {
|
|
|
54
54
|
title?: string;
|
|
55
55
|
}
|
|
56
56
|
//#endregion
|
|
57
|
-
export { CheckRunAnnotation, CheckRunPayload, ImpactDetectionOptions, ImpactDetectionResult, type ImpactResult, PrCommentOptions };
|
|
57
|
+
export { CheckRunAnnotation, CheckRunPayload, ImpactDetectionOptions, ImpactDetectionResult, type ImpactResult$1 as ImpactResult, PrCommentOptions };
|
|
58
58
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -102,7 +102,7 @@ async function resolveImplementations(specFile, adapters, config, options = {})
|
|
|
102
102
|
const specHash = opts.computeHashes ? computeHash(specContent) : void 0;
|
|
103
103
|
const scan = scanSpecSource(specContent, specFile);
|
|
104
104
|
const specKey = scan.key ?? fs.basename(specFile).replace(/\.[jt]s$/, "");
|
|
105
|
-
const specVersion = scan.version ?? 1;
|
|
105
|
+
const specVersion = scan.version ?? "1.0.0";
|
|
106
106
|
const specType = scan.specType ?? "operation";
|
|
107
107
|
const implementations = [];
|
|
108
108
|
const seenPaths = /* @__PURE__ */ new Set();
|