@ankhorage/doctor 0.10.13 → 0.10.15

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.10.15
4
+
5
+ ### Patch Changes
6
+
7
+ - 65d2fe5: Update Ankhorage dependencies: `@ankhorage/contracts`, `@ankhorage/paradox`, `@ankhorage/supabase-auth`.
8
+
9
+ ## 0.10.14
10
+
11
+ ### Patch Changes
12
+
13
+ - e6c0f2d: Remove the obsolete Ankh-specific Devtools runtime dependency placement rule now that Ankh dynamically discovers providers instead of bundling core providers.
14
+
3
15
  ## 0.10.13
4
16
 
5
17
  ### Patch Changes
@@ -1,4 +1,10 @@
1
1
  import { type DoctorAnalysisRequest, type DoctorAnalysisResult } from './analysis.js';
2
+ /***
3
+ * Analyze a Doctor target and apply repository-level dependency and runtime policies.
4
+ */
2
5
  export declare function analyzeDoctorTarget(request: DoctorAnalysisRequest): Promise<DoctorAnalysisResult>;
6
+ /***
7
+ * Analyze a Doctor target with CLI layout validation and repository-level policies.
8
+ */
3
9
  export declare function analyzeDoctorTargetWithCliLayout(request: DoctorAnalysisRequest): Promise<DoctorAnalysisResult>;
4
10
  //# sourceMappingURL=dependencyPolicyAnalysis.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dependencyPolicyAnalysis.d.ts","sourceRoot":"","sources":["../src/dependencyPolicyAnalysis.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EAC1B,MAAM,eAAe,CAAC;AASvB,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAE/B;AAED,wBAAsB,gCAAgC,CACpD,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAE/B"}
1
+ {"version":3,"file":"dependencyPolicyAnalysis.d.ts","sourceRoot":"","sources":["../src/dependencyPolicyAnalysis.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EAC1B,MAAM,eAAe,CAAC;AAIvB;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAE/B;AAED;;GAEG;AACH,wBAAsB,gCAAgC,CACpD,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAE/B"}
@@ -1,80 +1,22 @@
1
- import { promises as fs } from 'node:fs';
2
- import path from 'node:path';
3
1
  import { analyzeDoctorTarget as analyzeBaseDoctorTarget, } from './analysis.js';
4
2
  import { applyBunRuntimePolicy } from './bunPolicyAnalysis.js';
5
3
  import { analyzeDoctorTargetWithCliLayout as analyzeBaseDoctorTargetWithCliLayout } from './cliLayoutAnalysis.js';
6
- const ANKH_PACKAGE_NAME = '@ankhorage/ankh';
7
- const DEVTOOLS_PACKAGE_NAME = '@ankhorage/devtools';
8
- const DEVTOOLS_REQUIRED_RULE_ID = 'package.dependencies.devtools.required';
4
+ /***
5
+ * Analyze a Doctor target and apply repository-level dependency and runtime policies.
6
+ */
9
7
  export async function analyzeDoctorTarget(request) {
10
8
  return applyRepositoryPolicies(await analyzeBaseDoctorTarget(request));
11
9
  }
10
+ /***
11
+ * Analyze a Doctor target with CLI layout validation and repository-level policies.
12
+ */
12
13
  export async function analyzeDoctorTargetWithCliLayout(request) {
13
14
  return applyRepositoryPolicies(await analyzeBaseDoctorTargetWithCliLayout(request));
14
15
  }
16
+ /***
17
+ * Apply repository policies that remain independent from concrete package identities.
18
+ */
15
19
  async function applyRepositoryPolicies(result) {
16
- return applyBunRuntimePolicy(await applyDevtoolsDependencyPlacementPolicy(result));
17
- }
18
- async function applyDevtoolsDependencyPlacementPolicy(result) {
19
- if (!result.hasPackageJson) {
20
- return result;
21
- }
22
- const packageJsonPath = path.join(result.targetPath, 'package.json');
23
- const packageJson = await readPackageJson(packageJsonPath);
24
- if (packageJson?.name !== ANKH_PACKAGE_NAME || !expectsDevtools(packageJson)) {
25
- return result;
26
- }
27
- const dependencies = isRecord(packageJson.dependencies) ? packageJson.dependencies : null;
28
- const hasRuntimeDevtools = hasDependency(dependencies, DEVTOOLS_PACKAGE_NAME);
29
- const diagnosticsWithoutPlacementRule = result.diagnostics.filter((diagnostic) => diagnostic.ruleId !== DEVTOOLS_REQUIRED_RULE_ID);
30
- const diagnostics = hasRuntimeDevtools
31
- ? diagnosticsWithoutPlacementRule
32
- : [
33
- ...diagnosticsWithoutPlacementRule,
34
- createMissingRuntimeDevtoolsDiagnostic(packageJsonPath, result),
35
- ];
36
- return {
37
- ...result,
38
- diagnostics,
39
- fixPlan: result.fixPlan === null ? null : { ...result.fixPlan, diagnostics },
40
- };
41
- }
42
- async function readPackageJson(packageJsonPath) {
43
- try {
44
- const parsed = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
45
- return isRecord(parsed) ? parsed : null;
46
- }
47
- catch {
48
- return null;
49
- }
50
- }
51
- function createMissingRuntimeDevtoolsDiagnostic(packageJsonPath, result) {
52
- return {
53
- code: 'missing-dependency',
54
- message: '@ankhorage/ankh bundles @ankhorage/devtools as a runtime core provider and must declare it in dependencies.',
55
- path: packageJsonPath,
56
- profile: result.profile,
57
- ruleId: DEVTOOLS_REQUIRED_RULE_ID,
58
- severity: 'error',
59
- };
60
- }
61
- function expectsDevtools(packageJson) {
62
- const scripts = isRecord(packageJson.scripts) ? packageJson.scripts : null;
63
- if (scripts === null) {
64
- return false;
65
- }
66
- return Object.values(scripts).some((scriptValue) => isNonEmptyString(scriptValue) &&
67
- (scriptValue.includes('ankhorage-eslint') ||
68
- scriptValue.includes('ankhorage-prettier') ||
69
- scriptValue.includes('ankhorage-knip')));
70
- }
71
- function hasDependency(dependencyMap, dependencyName) {
72
- return dependencyMap !== null && isNonEmptyString(dependencyMap[dependencyName]);
73
- }
74
- function isNonEmptyString(value) {
75
- return typeof value === 'string' && value.trim() !== '';
76
- }
77
- function isRecord(value) {
78
- return typeof value === 'object' && value !== null && !Array.isArray(value);
20
+ return applyBunRuntimePolicy(result);
79
21
  }
80
22
  //# sourceMappingURL=dependencyPolicyAnalysis.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"dependencyPolicyAnalysis.js","sourceRoot":"","sources":["../src/dependencyPolicyAnalysis.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EACL,mBAAmB,IAAI,uBAAuB,GAG/C,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,gCAAgC,IAAI,oCAAoC,EAAE,MAAM,wBAAwB,CAAC;AAGlH,MAAM,iBAAiB,GAAG,iBAAiB,CAAC;AAC5C,MAAM,qBAAqB,GAAG,qBAAqB,CAAC;AACpD,MAAM,yBAAyB,GAAG,wCAAwC,CAAC;AAE3E,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,OAA8B;IAE9B,OAAO,uBAAuB,CAAC,MAAM,uBAAuB,CAAC,OAAO,CAAC,CAAC,CAAC;AACzE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gCAAgC,CACpD,OAA8B;IAE9B,OAAO,uBAAuB,CAAC,MAAM,oCAAoC,CAAC,OAAO,CAAC,CAAC,CAAC;AACtF,CAAC;AAED,KAAK,UAAU,uBAAuB,CACpC,MAA4B;IAE5B,OAAO,qBAAqB,CAAC,MAAM,sCAAsC,CAAC,MAAM,CAAC,CAAC,CAAC;AACrF,CAAC;AAED,KAAK,UAAU,sCAAsC,CACnD,MAA4B;IAE5B,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;QAC3B,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;IACrE,MAAM,WAAW,GAAG,MAAM,eAAe,CAAC,eAAe,CAAC,CAAC;IAC3D,IAAI,WAAW,EAAE,IAAI,KAAK,iBAAiB,IAAI,CAAC,eAAe,CAAC,WAAW,CAAC,EAAE,CAAC;QAC7E,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,MAAM,YAAY,GAAG,QAAQ,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC;IAC1F,MAAM,kBAAkB,GAAG,aAAa,CAAC,YAAY,EAAE,qBAAqB,CAAC,CAAC;IAC9E,MAAM,+BAA+B,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAC/D,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,MAAM,KAAK,yBAAyB,CAChE,CAAC;IACF,MAAM,WAAW,GAAG,kBAAkB;QACpC,CAAC,CAAC,+BAA+B;QACjC,CAAC,CAAC;YACE,GAAG,+BAA+B;YAClC,sCAAsC,CAAC,eAAe,EAAE,MAAM,CAAC;SAChE,CAAC;IAEN,OAAO;QACL,GAAG,MAAM;QACT,WAAW;QACX,OAAO,EAAE,MAAM,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,CAAC,OAAO,EAAE,WAAW,EAAE;KAC7E,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,eAAe,CAAC,eAAuB;IACpD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC,CAAY,CAAC;QACjF,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,sCAAsC,CAC7C,eAAuB,EACvB,MAA4B;IAE5B,OAAO;QACL,IAAI,EAAE,oBAAoB;QAC1B,OAAO,EACL,6GAA6G;QAC/G,IAAI,EAAE,eAAe;QACrB,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,MAAM,EAAE,yBAAyB;QACjC,QAAQ,EAAE,OAAO;KAClB,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,WAAoC;IAC3D,MAAM,OAAO,GAAG,QAAQ,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IAC3E,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACrB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAChC,CAAC,WAAW,EAAE,EAAE,CACd,gBAAgB,CAAC,WAAW,CAAC;QAC7B,CAAC,WAAW,CAAC,QAAQ,CAAC,kBAAkB,CAAC;YACvC,WAAW,CAAC,QAAQ,CAAC,oBAAoB,CAAC;YAC1C,WAAW,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC,CAC5C,CAAC;AACJ,CAAC;AAED,SAAS,aAAa,CACpB,aAA6C,EAC7C,cAAsB;IAEtB,OAAO,aAAa,KAAK,IAAI,IAAI,gBAAgB,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC,CAAC;AACnF,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAc;IACtC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;AAC1D,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC","sourcesContent":["import { promises as fs } from 'node:fs';\nimport path from 'node:path';\n\nimport {\n analyzeDoctorTarget as analyzeBaseDoctorTarget,\n type DoctorAnalysisRequest,\n type DoctorAnalysisResult,\n} from './analysis.js';\nimport { applyBunRuntimePolicy } from './bunPolicyAnalysis.js';\nimport { analyzeDoctorTargetWithCliLayout as analyzeBaseDoctorTargetWithCliLayout } from './cliLayoutAnalysis.js';\nimport type { DoctorDiagnostic } from './diagnostics.js';\n\nconst ANKH_PACKAGE_NAME = '@ankhorage/ankh';\nconst DEVTOOLS_PACKAGE_NAME = '@ankhorage/devtools';\nconst DEVTOOLS_REQUIRED_RULE_ID = 'package.dependencies.devtools.required';\n\nexport async function analyzeDoctorTarget(\n request: DoctorAnalysisRequest,\n): Promise<DoctorAnalysisResult> {\n return applyRepositoryPolicies(await analyzeBaseDoctorTarget(request));\n}\n\nexport async function analyzeDoctorTargetWithCliLayout(\n request: DoctorAnalysisRequest,\n): Promise<DoctorAnalysisResult> {\n return applyRepositoryPolicies(await analyzeBaseDoctorTargetWithCliLayout(request));\n}\n\nasync function applyRepositoryPolicies(\n result: DoctorAnalysisResult,\n): Promise<DoctorAnalysisResult> {\n return applyBunRuntimePolicy(await applyDevtoolsDependencyPlacementPolicy(result));\n}\n\nasync function applyDevtoolsDependencyPlacementPolicy(\n result: DoctorAnalysisResult,\n): Promise<DoctorAnalysisResult> {\n if (!result.hasPackageJson) {\n return result;\n }\n\n const packageJsonPath = path.join(result.targetPath, 'package.json');\n const packageJson = await readPackageJson(packageJsonPath);\n if (packageJson?.name !== ANKH_PACKAGE_NAME || !expectsDevtools(packageJson)) {\n return result;\n }\n\n const dependencies = isRecord(packageJson.dependencies) ? packageJson.dependencies : null;\n const hasRuntimeDevtools = hasDependency(dependencies, DEVTOOLS_PACKAGE_NAME);\n const diagnosticsWithoutPlacementRule = result.diagnostics.filter(\n (diagnostic) => diagnostic.ruleId !== DEVTOOLS_REQUIRED_RULE_ID,\n );\n const diagnostics = hasRuntimeDevtools\n ? diagnosticsWithoutPlacementRule\n : [\n ...diagnosticsWithoutPlacementRule,\n createMissingRuntimeDevtoolsDiagnostic(packageJsonPath, result),\n ];\n\n return {\n ...result,\n diagnostics,\n fixPlan: result.fixPlan === null ? null : { ...result.fixPlan, diagnostics },\n };\n}\n\nasync function readPackageJson(packageJsonPath: string): Promise<Record<string, unknown> | null> {\n try {\n const parsed = JSON.parse(await fs.readFile(packageJsonPath, 'utf8')) as unknown;\n return isRecord(parsed) ? parsed : null;\n } catch {\n return null;\n }\n}\n\nfunction createMissingRuntimeDevtoolsDiagnostic(\n packageJsonPath: string,\n result: DoctorAnalysisResult,\n): DoctorDiagnostic {\n return {\n code: 'missing-dependency',\n message:\n '@ankhorage/ankh bundles @ankhorage/devtools as a runtime core provider and must declare it in dependencies.',\n path: packageJsonPath,\n profile: result.profile,\n ruleId: DEVTOOLS_REQUIRED_RULE_ID,\n severity: 'error',\n };\n}\n\nfunction expectsDevtools(packageJson: Record<string, unknown>): boolean {\n const scripts = isRecord(packageJson.scripts) ? packageJson.scripts : null;\n if (scripts === null) {\n return false;\n }\n\n return Object.values(scripts).some(\n (scriptValue) =>\n isNonEmptyString(scriptValue) &&\n (scriptValue.includes('ankhorage-eslint') ||\n scriptValue.includes('ankhorage-prettier') ||\n scriptValue.includes('ankhorage-knip')),\n );\n}\n\nfunction hasDependency(\n dependencyMap: Record<string, unknown> | null,\n dependencyName: string,\n): boolean {\n return dependencyMap !== null && isNonEmptyString(dependencyMap[dependencyName]);\n}\n\nfunction isNonEmptyString(value: unknown): value is string {\n return typeof value === 'string' && value.trim() !== '';\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n"]}
1
+ {"version":3,"file":"dependencyPolicyAnalysis.js","sourceRoot":"","sources":["../src/dependencyPolicyAnalysis.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,IAAI,uBAAuB,GAG/C,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,gCAAgC,IAAI,oCAAoC,EAAE,MAAM,wBAAwB,CAAC;AAElH;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,OAA8B;IAE9B,OAAO,uBAAuB,CAAC,MAAM,uBAAuB,CAAC,OAAO,CAAC,CAAC,CAAC;AACzE,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,gCAAgC,CACpD,OAA8B;IAE9B,OAAO,uBAAuB,CAAC,MAAM,oCAAoC,CAAC,OAAO,CAAC,CAAC,CAAC;AACtF,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,uBAAuB,CACpC,MAA4B;IAE5B,OAAO,qBAAqB,CAAC,MAAM,CAAC,CAAC;AACvC,CAAC","sourcesContent":["import {\n analyzeDoctorTarget as analyzeBaseDoctorTarget,\n type DoctorAnalysisRequest,\n type DoctorAnalysisResult,\n} from './analysis.js';\nimport { applyBunRuntimePolicy } from './bunPolicyAnalysis.js';\nimport { analyzeDoctorTargetWithCliLayout as analyzeBaseDoctorTargetWithCliLayout } from './cliLayoutAnalysis.js';\n\n/***\n * Analyze a Doctor target and apply repository-level dependency and runtime policies.\n */\nexport async function analyzeDoctorTarget(\n request: DoctorAnalysisRequest,\n): Promise<DoctorAnalysisResult> {\n return applyRepositoryPolicies(await analyzeBaseDoctorTarget(request));\n}\n\n/***\n * Analyze a Doctor target with CLI layout validation and repository-level policies.\n */\nexport async function analyzeDoctorTargetWithCliLayout(\n request: DoctorAnalysisRequest,\n): Promise<DoctorAnalysisResult> {\n return applyRepositoryPolicies(await analyzeBaseDoctorTargetWithCliLayout(request));\n}\n\n/***\n * Apply repository policies that remain independent from concrete package identities.\n */\nasync function applyRepositoryPolicies(\n result: DoctorAnalysisResult,\n): Promise<DoctorAnalysisResult> {\n return applyBunRuntimePolicy(result);\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ankhorage/doctor",
3
- "version": "0.10.13",
3
+ "version": "0.10.15",
4
4
  "description": "Executable Ankh provider and standalone CLI for lightweight repo and package compliance diagnostics.",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -77,13 +77,13 @@
77
77
  "knip:check": "ankhorage-knip"
78
78
  },
79
79
  "dependencies": {
80
- "@ankhorage/contracts": "^19.1.0",
81
- "@ankhorage/supabase-auth": "^1.2.11"
80
+ "@ankhorage/contracts": "^19.5.0",
81
+ "@ankhorage/supabase-auth": "^1.2.12"
82
82
  },
83
83
  "devDependencies": {
84
84
  "@ankhorage/ankh": "^0.8.11",
85
85
  "@ankhorage/devtools": "^1.14.0",
86
- "@ankhorage/paradox": "^0.1.24",
86
+ "@ankhorage/paradox": "^0.1.26",
87
87
  "@types/bun": "^1.4.1",
88
88
  "@types/node": "^25.6.0",
89
89
  "typescript": "^5.9.3"
@@ -1,6 +1,3 @@
1
- import { promises as fs } from 'node:fs';
2
- import path from 'node:path';
3
-
4
1
  import {
5
2
  analyzeDoctorTarget as analyzeBaseDoctorTarget,
6
3
  type DoctorAnalysisRequest,
@@ -8,112 +5,30 @@ import {
8
5
  } from './analysis.js';
9
6
  import { applyBunRuntimePolicy } from './bunPolicyAnalysis.js';
10
7
  import { analyzeDoctorTargetWithCliLayout as analyzeBaseDoctorTargetWithCliLayout } from './cliLayoutAnalysis.js';
11
- import type { DoctorDiagnostic } from './diagnostics.js';
12
-
13
- const ANKH_PACKAGE_NAME = '@ankhorage/ankh';
14
- const DEVTOOLS_PACKAGE_NAME = '@ankhorage/devtools';
15
- const DEVTOOLS_REQUIRED_RULE_ID = 'package.dependencies.devtools.required';
16
8
 
9
+ /***
10
+ * Analyze a Doctor target and apply repository-level dependency and runtime policies.
11
+ */
17
12
  export async function analyzeDoctorTarget(
18
13
  request: DoctorAnalysisRequest,
19
14
  ): Promise<DoctorAnalysisResult> {
20
15
  return applyRepositoryPolicies(await analyzeBaseDoctorTarget(request));
21
16
  }
22
17
 
18
+ /***
19
+ * Analyze a Doctor target with CLI layout validation and repository-level policies.
20
+ */
23
21
  export async function analyzeDoctorTargetWithCliLayout(
24
22
  request: DoctorAnalysisRequest,
25
23
  ): Promise<DoctorAnalysisResult> {
26
24
  return applyRepositoryPolicies(await analyzeBaseDoctorTargetWithCliLayout(request));
27
25
  }
28
26
 
27
+ /***
28
+ * Apply repository policies that remain independent from concrete package identities.
29
+ */
29
30
  async function applyRepositoryPolicies(
30
31
  result: DoctorAnalysisResult,
31
32
  ): Promise<DoctorAnalysisResult> {
32
- return applyBunRuntimePolicy(await applyDevtoolsDependencyPlacementPolicy(result));
33
- }
34
-
35
- async function applyDevtoolsDependencyPlacementPolicy(
36
- result: DoctorAnalysisResult,
37
- ): Promise<DoctorAnalysisResult> {
38
- if (!result.hasPackageJson) {
39
- return result;
40
- }
41
-
42
- const packageJsonPath = path.join(result.targetPath, 'package.json');
43
- const packageJson = await readPackageJson(packageJsonPath);
44
- if (packageJson?.name !== ANKH_PACKAGE_NAME || !expectsDevtools(packageJson)) {
45
- return result;
46
- }
47
-
48
- const dependencies = isRecord(packageJson.dependencies) ? packageJson.dependencies : null;
49
- const hasRuntimeDevtools = hasDependency(dependencies, DEVTOOLS_PACKAGE_NAME);
50
- const diagnosticsWithoutPlacementRule = result.diagnostics.filter(
51
- (diagnostic) => diagnostic.ruleId !== DEVTOOLS_REQUIRED_RULE_ID,
52
- );
53
- const diagnostics = hasRuntimeDevtools
54
- ? diagnosticsWithoutPlacementRule
55
- : [
56
- ...diagnosticsWithoutPlacementRule,
57
- createMissingRuntimeDevtoolsDiagnostic(packageJsonPath, result),
58
- ];
59
-
60
- return {
61
- ...result,
62
- diagnostics,
63
- fixPlan: result.fixPlan === null ? null : { ...result.fixPlan, diagnostics },
64
- };
65
- }
66
-
67
- async function readPackageJson(packageJsonPath: string): Promise<Record<string, unknown> | null> {
68
- try {
69
- const parsed = JSON.parse(await fs.readFile(packageJsonPath, 'utf8')) as unknown;
70
- return isRecord(parsed) ? parsed : null;
71
- } catch {
72
- return null;
73
- }
74
- }
75
-
76
- function createMissingRuntimeDevtoolsDiagnostic(
77
- packageJsonPath: string,
78
- result: DoctorAnalysisResult,
79
- ): DoctorDiagnostic {
80
- return {
81
- code: 'missing-dependency',
82
- message:
83
- '@ankhorage/ankh bundles @ankhorage/devtools as a runtime core provider and must declare it in dependencies.',
84
- path: packageJsonPath,
85
- profile: result.profile,
86
- ruleId: DEVTOOLS_REQUIRED_RULE_ID,
87
- severity: 'error',
88
- };
89
- }
90
-
91
- function expectsDevtools(packageJson: Record<string, unknown>): boolean {
92
- const scripts = isRecord(packageJson.scripts) ? packageJson.scripts : null;
93
- if (scripts === null) {
94
- return false;
95
- }
96
-
97
- return Object.values(scripts).some(
98
- (scriptValue) =>
99
- isNonEmptyString(scriptValue) &&
100
- (scriptValue.includes('ankhorage-eslint') ||
101
- scriptValue.includes('ankhorage-prettier') ||
102
- scriptValue.includes('ankhorage-knip')),
103
- );
104
- }
105
-
106
- function hasDependency(
107
- dependencyMap: Record<string, unknown> | null,
108
- dependencyName: string,
109
- ): boolean {
110
- return dependencyMap !== null && isNonEmptyString(dependencyMap[dependencyName]);
111
- }
112
-
113
- function isNonEmptyString(value: unknown): value is string {
114
- return typeof value === 'string' && value.trim() !== '';
115
- }
116
-
117
- function isRecord(value: unknown): value is Record<string, unknown> {
118
- return typeof value === 'object' && value !== null && !Array.isArray(value);
33
+ return applyBunRuntimePolicy(result);
119
34
  }