@ankhorage/paradox 0.1.27 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +53 -61
  3. package/dist/analyze/analyze.d.ts +2 -2
  4. package/dist/analyze/analyze.js +51 -35
  5. package/dist/analyze/badges.d.ts +2 -1
  6. package/dist/analyze/badges.js +14 -4
  7. package/dist/analyze/components.js +12 -11
  8. package/dist/analyze/documentation/collectDocumentationCommentsAsync.d.ts +11 -0
  9. package/dist/analyze/documentation/collectDocumentationCommentsAsync.js +72 -0
  10. package/dist/analyze/documentation/findings.d.ts +5 -0
  11. package/dist/analyze/documentation/findings.js +17 -0
  12. package/dist/analyze/documentation/validateDocumentationPolicyAsync.d.ts +13 -0
  13. package/dist/analyze/documentation/validateDocumentationPolicyAsync.js +150 -0
  14. package/dist/analyze/documentation/validateReferencesAsync.d.ts +11 -0
  15. package/dist/analyze/documentation/validateReferencesAsync.js +148 -0
  16. package/dist/analyze/exports.d.ts +4 -0
  17. package/dist/analyze/exports.js +14 -7
  18. package/dist/analyze/readmeConfig.d.ts +1 -3
  19. package/dist/analyze/readmeConfig.js +8 -19
  20. package/dist/analyze/readmeUsage.d.ts +7 -10
  21. package/dist/analyze/readmeUsage.js +124 -48
  22. package/dist/analyze/semantic/docBlocks.js +18 -48
  23. package/dist/analyze/semantic/exports.js +1 -3
  24. package/dist/analyze/semantic/model.d.ts +0 -2
  25. package/dist/analyze/semantic/paradoxComment.d.ts +1 -11
  26. package/dist/analyze/semantic/paradoxComment.js +1 -43
  27. package/dist/analyze/semantic/tagRegistry.js +2 -1
  28. package/dist/analyze/sourceFunctions.js +4 -4
  29. package/dist/analyze/types.d.ts +31 -24
  30. package/dist/analyze/usage.d.ts +2 -2
  31. package/dist/analyze/usage.js +3 -33
  32. package/dist/analyze/utils/getExportMetadata.js +11 -40
  33. package/dist/analyze/utils/parseParadoxComment.d.ts +12 -9
  34. package/dist/analyze/utils/parseParadoxComment.js +66 -78
  35. package/dist/cli/index.d.ts +3 -2
  36. package/dist/cli/index.js +3 -2
  37. package/dist/cli/standalone.js +11 -0
  38. package/dist/config/defineParadoxConfig.d.ts +1 -1
  39. package/dist/doc-tags/registry.d.ts +28 -32
  40. package/dist/doc-tags/registry.js +35 -39
  41. package/dist/index.d.ts +1 -1
  42. package/dist/model/buildModel.d.ts +26 -19
  43. package/dist/model/buildModel.js +33 -99
  44. package/dist/model/types.d.ts +27 -20
  45. package/dist/paths/policy.d.ts +1 -1
  46. package/dist/render/renderers/diagrams.js +1 -7
  47. package/dist/render/renderers/html.js +89 -58
  48. package/dist/render/renderers/markdown.js +139 -86
  49. package/dist/render/toFileStem.d.ts +2 -0
  50. package/dist/render/toFileStem.js +8 -0
  51. package/dist/{config/types.d.ts → types/config.d.ts} +3 -5
  52. package/dist/write/write.d.ts +1 -1
  53. package/package.json +2 -1
  54. package/dist/analyze/readmeCli.d.ts +0 -9
  55. package/dist/analyze/readmeCli.js +0 -33
  56. package/dist/analyze/utils/getLeadingParadoxComment.d.ts +0 -10
  57. package/dist/analyze/utils/getLeadingParadoxComment.js +0 -16
  58. /package/dist/{config/types.js → types/config.js} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 9fe93b9: Adopt the canonical Ankhorage documentation policy: fixed usage and config locations, source-backed
8
+ examples, explicit titles, validated external and security references, policy-derived status
9
+ badges, and hard failures for invalid documentation contracts.
10
+
3
11
  ## 0.1.27
4
12
 
5
13
  ### Patch Changes
package/README.md CHANGED
@@ -3,66 +3,44 @@
3
3
 
4
4
  # @ankhorage/paradox
5
5
 
6
- ![license: MIT](./paradox/badges/license.svg) ![npm: v0.1.25](./paradox/badges/npm.svg) ![runtime: bun](./paradox/badges/runtime.svg) ![typescript: strict](./paradox/badges/typescript.svg) ![eslint: checked](./paradox/badges/eslint.svg) ![prettier: checked](./paradox/badges/prettier.svg) ![build: checked](./paradox/badges/build.svg) ![tests: checked](./paradox/badges/tests.svg) ![docs: paradox](./paradox/badges/docs.svg)
6
+ ![license: MIT](./paradox/badges/license.svg) ![npm: v0.1.27](./paradox/badges/npm.svg) ![runtime: bun](./paradox/badges/runtime.svg) ![typescript: strict](./paradox/badges/typescript.svg) ![eslint: checked](./paradox/badges/eslint.svg) ![prettier: checked](./paradox/badges/prettier.svg) ![build: checked](./paradox/badges/build.svg) ![tests: checked](./paradox/badges/tests.svg) ![paradox: canonical](./paradox/badges/docs.svg)
7
7
 
8
8
  Deterministic documentation generator for TypeScript packages.
9
9
 
10
- ## CLI
10
+ ## Usage
11
11
 
12
- Generates deterministic documentation for a package through the Paradox CLI.
12
+ ### CLI
13
13
 
14
- ```bash
15
- bunx @ankhorage/paradox
16
- ```
14
+ Ankhorage packages expose their command-line interface through `ankh`. Use `ankh --help` to discover available package commands, or run a package command with `--help` for package-specific usage.
17
15
 
18
- <details>
19
- <summary>paradox</summary>
20
-
21
- Runs the Paradox CLI.
22
-
23
- The command discovers the nearest Paradox config, resolves the package and output roots,
24
- analyzes the package, builds the documentation model, renders all documentation artifacts,
25
- and writes them to the configured output directory.
26
-
27
- Diagram: [paradox sequence](./paradox/diagrams/sequences/paradox.mmd)
28
-
29
- ```mermaid
30
- sequenceDiagram
31
- participant participant_analyze as analyze
32
- participant participant_buildModel as buildModel
33
- participant participant_dirname as dirname
34
- participant participant_findParadoxConfigFile as findParadoxConfigFile
35
- participant participant_loadParadoxConfig as loadParadoxConfig
36
- participant participant_main as main
37
- participant participant_render as render
38
- participant participant_resolveOutputRoot as resolveOutputRoot
39
- participant participant_resolvePackageRoot as resolvePackageRoot
40
- participant participant_write as write
41
- participant_main->>participant_findParadoxConfigFile: findParadoxConfigFile()
42
- participant_findParadoxConfigFile-->>participant_main: return
43
- participant_main->>participant_dirname: dirname()
44
- participant_dirname-->>participant_main: return
45
- participant_main->>participant_loadParadoxConfig: loadParadoxConfig()
46
- participant_loadParadoxConfig-->>participant_main: return
47
- participant_main->>participant_resolvePackageRoot: resolvePackageRoot()
48
- participant_resolvePackageRoot-->>participant_main: return
49
- participant_main->>participant_resolveOutputRoot: resolveOutputRoot()
50
- participant_resolveOutputRoot-->>participant_main: return
51
- participant_main->>participant_analyze: analyze()
52
- participant_analyze-->>participant_main: return
53
- participant_main->>participant_buildModel: buildModel()
54
- participant_buildModel-->>participant_main: return
55
- participant_main->>participant_render: render()
56
- participant_render-->>participant_main: return
57
- participant_main->>participant_write: write()
58
- participant_write-->>participant_main: return
16
+ ```zsh
17
+ # Install the Ankhorage CLI
18
+ bun add --global @ankhorage/ankh
19
+
20
+ # Show usage information for paradox
21
+ ankh paradox --help
59
22
  ```
60
23
 
61
- </details>
24
+ ### Basic Usage
25
+
26
+ Paradox generates documentation from the canonical package structure. Usage documentation lives
27
+ only below `examples/**` or `src/cli/**`. A repository may contain multiple `@usage`
28
+ examples, but exactly one example below `examples/**` is promoted into README with `@readme`.
29
+
30
+ README-promoted usage provides an explicit `@title` and non-empty prose. Code always comes from
31
+ real source declarations rather than duplicated code blocks inside Paradox comments.
32
+
33
+ ```ts
34
+ export const basicConfig = defineParadoxConfig({
35
+ mode: 'safe',
36
+ });
37
+ ```
62
38
 
63
39
  ## Configuration
64
40
 
65
- Canonical Paradox configuration for this package.
41
+ Configures Paradox documentation generation for a package.
42
+
43
+ ### Example
66
44
 
67
45
  ```ts
68
46
  import { defineParadoxConfig } from './src/config/defineParadoxConfig.js';
@@ -89,14 +67,14 @@ export default defineParadoxConfig({
89
67
  <details>
90
68
  <summary>Configuration options</summary>
91
69
 
92
- | Field | Type | Required | Default | Description |
93
- | ------------- | ------------------------------------------------------------------------------------------------------------------- | -------- | ------- | ----------- |
94
- | mode | `'safe' \| 'write' \| undefined` | no | — | |
95
- | collaborators | `true \| undefined` | no | — | |
96
- | donation | `{ account: string; } \| undefined` | no | — | |
97
- | docs | `{ title?: string; description?: string; usage?: { description?: string; entrypoints?: string[]; }; } \| undefined` | no | — | |
98
- | package | `{ root?: string; entrypoints?: string[]; } \| undefined` | no | — | |
99
- | output | `{ dir?: string; } \| undefined` | no | — | |
70
+ | Field | Type | Required | Default | Description |
71
+ | ------------- | --------------------------------------------------------- | -------- | ------- | ----------- |
72
+ | mode | `'safe' \| 'write' \| undefined` | no | — | |
73
+ | collaborators | `true \| undefined` | no | — | |
74
+ | donation | `{ account: string; } \| undefined` | no | — | |
75
+ | docs | `{ title?: string; description?: string; } \| undefined` | no | — | |
76
+ | package | `{ root?: string; entrypoints?: string[]; } \| undefined` | no | — | |
77
+ | output | `{ dir?: string; } \| undefined` | no | — | |
100
78
 
101
79
  </details>
102
80
 
@@ -130,13 +108,27 @@ Related symbols: `ParadoxConfig`
130
108
 
131
109
  </details>
132
110
 
111
+ ### Documentation
112
+
113
+ <details>
114
+ <summary>PARADOX_DOC_TAGS</summary>
115
+
116
+ Supported Paradox documentation tags projected from the canonical Ankhorage documentation policy.
117
+
118
+ Module: `src/doc-tags/registry.ts`
119
+ Source: `src/doc-tags/registry.ts:32:14`
120
+
121
+ </details>
122
+
123
+ ### Types
124
+
133
125
  <details>
134
- <summary>ParadoxConfig</summary>
126
+ <summary>Configuration</summary>
135
127
 
136
- Configuration for running Paradox.
128
+ Configures Paradox documentation generation for a package.
137
129
 
138
- Module: `src/config/types.ts`
139
- Source: `src/config/types.ts:7:1`
130
+ Module: `src/types/config.ts`
131
+ Source: `src/types/config.ts:9:1`
140
132
 
141
133
  </details>
142
134
 
@@ -1,7 +1,7 @@
1
- import type { ParadoxConfig } from '../config/types.js';
1
+ import type { ParadoxConfig } from '../types/config.js';
2
2
  import type { AnalysisResult } from './types.js';
3
3
  /***
4
- * Analyzes a package and returns the complete documentation input model.
4
+ * Analyzes a package and returns documentation plus canonical policy findings.
5
5
  */
6
6
  export declare function analyze(config: ParadoxConfig, runtime: {
7
7
  packageRoot: string;
@@ -1,15 +1,17 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { join } from 'node:path';
3
+ import { resolvePolicyStatus } from '@ankhorage/policy/status';
3
4
  import { validateCollaborators } from '../config/utils/validateCollaborators.js';
4
5
  import { validateDonationAccount } from '../config/utils/validateDonationAccount.js';
5
6
  import { analyzeBadges } from './badges.js';
6
7
  import { analyzeComponents } from './components.js';
8
+ import { collectDocumentationCommentsAsync } from './documentation/collectDocumentationCommentsAsync.js';
9
+ import { validateDocumentationPolicyAsync } from './documentation/validateDocumentationPolicyAsync.js';
7
10
  import { analyzeExports } from './exports.js';
8
11
  import { analyzeModules } from './modules.js';
9
12
  import { createProject } from './project.js';
10
- import { analyzeReadmeCli } from './readmeCli.js';
11
13
  import { analyzeReadmeConfig } from './readmeConfig.js';
12
- import { analyzeReadmeUsage } from './readmeUsage.js';
14
+ import { analyzeReadmeUsage, countExampleDirectoriesAsync } from './readmeUsage.js';
13
15
  import { createTypeScriptProgram } from './semantic/createTypeScriptProgram.js';
14
16
  import { collectTypeMembers, resolveTypeReference } from './semantic/exports.js';
15
17
  import { collectCallGraph, collectComponentCompositionGraph, collectImportGraph, } from './semantic/graphs.js';
@@ -17,7 +19,7 @@ import { analyzeSequenceScenarios } from './sequenceScenarios.js';
17
19
  import { analyzeSourceFunctions } from './sourceFunctions.js';
18
20
  import { createUsageFromPackageJson } from './usage.js';
19
21
  /***
20
- * Analyzes a package and returns the complete documentation input model.
22
+ * Analyzes a package and returns documentation plus canonical policy findings.
21
23
  */
22
24
  export async function analyze(config, runtime) {
23
25
  const root = runtime.packageRoot;
@@ -27,38 +29,22 @@ export async function analyze(config, runtime) {
27
29
  ? null
28
30
  : { account: validateDonationAccount(config.donation.account) };
29
31
  const usage = createUsageFromPackageJson(pkg);
30
- const badges = await analyzeBadges(root, pkg);
31
32
  const project = createProject(root);
32
33
  const entrypoints = config.package?.entrypoints ?? ['src/index.ts'];
33
- const readmeUsageDescription = config.docs?.usage?.description ?? null;
34
- const usageEntryPoints = config.docs?.usage?.entrypoints ?? [];
35
- const readmeUsage = await analyzeReadmeUsage({ root, entrypoints: usageEntryPoints });
36
- const readmeCli = await analyzeReadmeCli(root);
37
- const readmeConfig = await analyzeReadmeConfig({
38
- root,
39
- configFilePath: runtime.configFilePath ?? null,
40
- });
41
34
  const program = createTypeScriptProgram({ root, entrypoints, project });
42
35
  const { config: configMetadata, exports } = analyzeExports(project, { root, entrypoints });
43
36
  const components = analyzeComponents(exports, { program });
44
- const modules = analyzeModules(project, {
45
- root,
46
- entrypoints,
47
- excludePaths: usageEntryPoints,
48
- });
37
+ const modules = analyzeModules(project, { root, entrypoints });
49
38
  const sourceFunctions = analyzeSourceFunctions(project, root);
50
39
  const sequenceScenarios = analyzeSequenceScenarios({ project, root, pkg, exports });
51
- const configExport = configMetadata
52
- ? (exports.find((entry) => entry.name === configMetadata.exportName) ?? null)
53
- : null;
54
- const configMembers = configExport && (configExport.kind === 'type' || configExport.kind === 'unknown')
55
- ? collectTypeMembers(program, resolveTypeReference(program, configExport.node) ?? {
56
- type: configExport.node.getType(),
57
- name: configExport.name,
58
- sourcePath: configExport.modulePath,
59
- symbol: configExport.node.getSymbol() ?? null,
60
- })
61
- : [];
40
+ const usageEntries = await analyzeReadmeUsage({ root });
41
+ const exampleCount = await countExampleDirectoriesAsync(root);
42
+ const comments = await collectDocumentationCommentsAsync(root);
43
+ const readmeConfig = await analyzeReadmeConfig({
44
+ root,
45
+ configFilePath: runtime.configFilePath ?? null,
46
+ });
47
+ const configMembers = collectConfigMembers(program, exports, configMetadata);
62
48
  const graphs = {
63
49
  imports: collectImportGraph(program),
64
50
  calls: collectCallGraph(program),
@@ -69,6 +55,14 @@ export async function analyze(config, runtime) {
69
55
  }))),
70
56
  componentComposition: collectComponentCompositionGraph(program),
71
57
  };
58
+ const findings = await validateDocumentationPolicyAsync({
59
+ root,
60
+ project,
61
+ comments,
62
+ exports,
63
+ });
64
+ const documentationStatus = resolvePolicyStatus(findings);
65
+ const badges = await analyzeBadges(root, pkg, documentationStatus.status);
72
66
  return {
73
67
  packageName: config.docs?.title ?? pkg.name,
74
68
  packageId: pkg.name,
@@ -83,20 +77,42 @@ export async function analyze(config, runtime) {
83
77
  badges,
84
78
  sequenceScenarios,
85
79
  usage,
86
- readmeUsageDescription,
87
- readmeUsage,
88
- readmeCli,
80
+ usageEntries,
81
+ exampleCount,
82
+ findings,
89
83
  readmeConfig,
90
- config: configMetadata
91
- ? {
84
+ config: configMetadata === null
85
+ ? null
86
+ : {
92
87
  exportName: configMetadata.exportName,
88
+ title: configMetadata.title,
89
+ description: configMetadata.description,
93
90
  isReadme: configMetadata.isReadme,
91
+ see: configMetadata.see,
92
+ security: configMetadata.security,
94
93
  members: mapTypeMembers(configMembers),
95
- }
96
- : null,
94
+ },
97
95
  graphs,
98
96
  };
99
97
  }
98
+ /***
99
+ * Resolves member metadata for the public configuration root when one exists.
100
+ */
101
+ function collectConfigMembers(program, exports, configMetadata) {
102
+ if (configMetadata === null)
103
+ return [];
104
+ const configExport = exports.find((entry) => entry.name === configMetadata.exportName);
105
+ if (configExport === undefined ||
106
+ (configExport.kind !== 'type' && configExport.kind !== 'unknown')) {
107
+ return [];
108
+ }
109
+ return collectTypeMembers(program, resolveTypeReference(program, configExport.node) ?? {
110
+ type: configExport.node.getType(),
111
+ name: configExport.name,
112
+ sourcePath: configExport.modulePath,
113
+ symbol: configExport.node.getSymbol() ?? null,
114
+ });
115
+ }
100
116
  /***
101
117
  * Converts semantic type members into serializable analysis output.
102
118
  */
@@ -1,6 +1,7 @@
1
+ import type { PolicyStatus } from '@ankhorage/policy/status';
1
2
  import type { AnalysisBadge } from './types.js';
2
3
  import type { PackageJsonModel } from './usage.js';
3
4
  /***
4
5
  * Derives deterministic repository metadata badges from local repository files.
5
6
  */
6
- export declare function analyzeBadges(root: string, pkg: PackageJsonModel): Promise<AnalysisBadge[]>;
7
+ export declare function analyzeBadges(root: string, pkg: PackageJsonModel, documentationStatus: PolicyStatus): Promise<AnalysisBadge[]>;
@@ -39,7 +39,7 @@ const BADGE_ORDER = [
39
39
  /***
40
40
  * Derives deterministic repository metadata badges from local repository files.
41
41
  */
42
- export async function analyzeBadges(root, pkg) {
42
+ export async function analyzeBadges(root, pkg, documentationStatus) {
43
43
  const workflowFiles = await readWorkflowFiles(root);
44
44
  const badges = [];
45
45
  if ((await hasAnyFile(root, ESLINT_CONFIG_FILES)) ||
@@ -126,9 +126,9 @@ export async function analyzeBadges(root, pkg) {
126
126
  }
127
127
  badges.push({
128
128
  id: 'docs',
129
- label: 'docs',
130
- value: 'paradox',
131
- color: '0f766e',
129
+ label: 'paradox',
130
+ value: documentationStatus,
131
+ color: getDocumentationStatusColor(documentationStatus),
132
132
  });
133
133
  return sortBadges(badges);
134
134
  }
@@ -305,3 +305,13 @@ function getBadgeOrder(id) {
305
305
  const index = BADGE_ORDER.indexOf(id);
306
306
  return index === -1 ? BADGE_ORDER.length : index;
307
307
  }
308
+ /***
309
+ * Maps the shared traffic-light status to a deterministic badge color.
310
+ */
311
+ function getDocumentationStatusColor(status) {
312
+ if (status === 'invalid')
313
+ return 'dc2626';
314
+ if (status === 'warnings')
315
+ return 'ca8a04';
316
+ return '0a7f3f';
317
+ }
@@ -7,11 +7,11 @@ import { isReactComponent } from './utils/isReactComponent.js';
7
7
  */
8
8
  export function analyzeComponents(exports, options = {}) {
9
9
  const components = [];
10
- for (const e of exports) {
11
- if (!isReactComponent(e.node))
10
+ for (const entry of exports) {
11
+ if (!isReactComponent(entry.node))
12
12
  continue;
13
13
  const propsFromAnalyzer = options.program
14
- ? collectPropsForExport(options.program, { name: e.name, node: e.node })
14
+ ? collectPropsForExport(options.program, { name: entry.name, node: entry.node })
15
15
  : undefined;
16
16
  const analyzerProps = propsFromAnalyzer?.members.map((member) => ({
17
17
  name: member.name,
@@ -20,17 +20,18 @@ export function analyzeComponents(exports, options = {}) {
20
20
  ...(member.defaultValue !== undefined ? { defaultValue: member.defaultValue } : {}),
21
21
  description: member.description ?? null,
22
22
  })) ?? [];
23
- const propsType = getComponentPropsType(e.node);
23
+ const propsType = getComponentPropsType(entry.node);
24
24
  const legacyProps = propsType != null ? getPropsFromType(propsType, options.program?.root) : [];
25
25
  const props = analyzerProps.length > 0 ? analyzerProps : legacyProps;
26
26
  components.push({
27
- name: e.name,
28
- description: e.description,
29
- isReadme: e.isReadme,
30
- examples: e.examples,
31
- modulePath: e.modulePath,
32
- sourceLocation: e.sourceLocation,
33
- exportPaths: e.exportPaths,
27
+ name: entry.name,
28
+ description: entry.description,
29
+ isReadme: entry.isReadme,
30
+ see: entry.see,
31
+ security: entry.security,
32
+ modulePath: entry.modulePath,
33
+ sourceLocation: entry.sourceLocation,
34
+ exportPaths: entry.exportPaths,
34
35
  props,
35
36
  });
36
37
  }
@@ -0,0 +1,11 @@
1
+ import type { ParsedParadoxComment } from '../utils/parseParadoxComment.js';
2
+ export interface CollectedDocumentationComment {
3
+ readonly sourcePath: string;
4
+ readonly line: number;
5
+ readonly raw: string;
6
+ readonly parsed: ParsedParadoxComment;
7
+ }
8
+ /***
9
+ * Collects real Paradox comments from canonical source roots and source-like repository root files.
10
+ */
11
+ export declare function collectDocumentationCommentsAsync(root: string): Promise<CollectedDocumentationComment[]>;
@@ -0,0 +1,72 @@
1
+ import { readdir, readFile } from 'node:fs/promises';
2
+ import { extname, join, relative } from 'node:path';
3
+ import { parseParadoxComment } from '../utils/parseParadoxComment.js';
4
+ const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.json5']);
5
+ const SCANNED_ROOTS = ['src', 'examples'];
6
+ const COMMENT_PATTERN = /\/\*\*\*[\s\S]*?\*\//g;
7
+ /***
8
+ * Collects real Paradox comments from canonical source roots and source-like repository root files.
9
+ */
10
+ export async function collectDocumentationCommentsAsync(root) {
11
+ const nestedFiles = (await Promise.all(SCANNED_ROOTS.map((name) => collectSourceFilesAsync(join(root, name))))).flat();
12
+ const rootFiles = await collectRootSourceFilesAsync(root);
13
+ const files = [...new Set([...nestedFiles, ...rootFiles])].sort((a, b) => a.localeCompare(b));
14
+ const comments = await Promise.all(files.map((filePath) => collectFileCommentsAsync(root, filePath)));
15
+ return comments.flat();
16
+ }
17
+ /***
18
+ * Collects supported source files recursively while treating missing canonical roots as empty.
19
+ */
20
+ async function collectSourceFilesAsync(root) {
21
+ let entries;
22
+ try {
23
+ entries = await readdir(root, { withFileTypes: true });
24
+ }
25
+ catch (error) {
26
+ if (isMissingPathError(error))
27
+ return [];
28
+ throw error;
29
+ }
30
+ const nested = await Promise.all(entries.map(async (entry) => {
31
+ const path = join(root, entry.name);
32
+ if (entry.isDirectory())
33
+ return collectSourceFilesAsync(path);
34
+ return entry.isFile() && SOURCE_EXTENSIONS.has(extname(entry.name)) ? [path] : [];
35
+ }));
36
+ return nested.flat();
37
+ }
38
+ /***
39
+ * Collects source-like files directly at repository root.
40
+ */
41
+ async function collectRootSourceFilesAsync(root) {
42
+ const entries = await readdir(root, { withFileTypes: true });
43
+ return entries.flatMap((entry) => entry.isFile() && SOURCE_EXTENSIONS.has(extname(entry.name)) ? [join(root, entry.name)] : []);
44
+ }
45
+ /***
46
+ * Collects Paradox comments from one source file with stable line locations.
47
+ */
48
+ async function collectFileCommentsAsync(root, filePath) {
49
+ const source = await readFile(filePath, 'utf-8');
50
+ const sourcePath = toPosixPath(relative(root, filePath));
51
+ return [...source.matchAll(COMMENT_PATTERN)].map((match) => {
52
+ const [raw] = match;
53
+ return {
54
+ sourcePath,
55
+ line: source.slice(0, match.index).split('\n').length,
56
+ raw,
57
+ parsed: parseParadoxComment(raw),
58
+ };
59
+ });
60
+ }
61
+ /***
62
+ * Checks whether a filesystem error reports a missing path.
63
+ */
64
+ function isMissingPathError(error) {
65
+ return error instanceof Error && 'code' in error && error.code === 'ENOENT';
66
+ }
67
+ /***
68
+ * Normalizes filesystem separators for stable documentation paths.
69
+ */
70
+ function toPosixPath(path) {
71
+ return path.replaceAll('\\', '/');
72
+ }
@@ -0,0 +1,5 @@
1
+ import type { AnalysisDocumentationFinding } from '../types.js';
2
+ /***
3
+ * Creates a finding from one canonical documentation policy rule.
4
+ */
5
+ export declare function createDocumentationFinding(ruleId: string, message: string, sourcePath?: string | null, line?: number | null): AnalysisDocumentationFinding;
@@ -0,0 +1,17 @@
1
+ import { DOCUMENTATION_POLICY } from '@ankhorage/policy/documentation';
2
+ /***
3
+ * Creates a finding from one canonical documentation policy rule.
4
+ */
5
+ export function createDocumentationFinding(ruleId, message, sourcePath = null, line = null) {
6
+ const rule = DOCUMENTATION_POLICY.rules.find((candidate) => candidate.id === ruleId);
7
+ if (rule === undefined) {
8
+ throw new Error(`Unknown documentation policy rule: ${ruleId}`);
9
+ }
10
+ return {
11
+ ruleId,
12
+ severity: rule.severity,
13
+ message,
14
+ sourcePath,
15
+ line,
16
+ };
17
+ }
@@ -0,0 +1,13 @@
1
+ import { type Project } from 'ts-morph';
2
+ import type { AnalysisDocumentationFinding, AnalysisExport } from '../types.js';
3
+ import type { CollectedDocumentationComment } from './collectDocumentationCommentsAsync.js';
4
+ /***
5
+ * Evaluates package documentation evidence against the canonical Ankhorage documentation policy.
6
+ */
7
+ export declare function validateDocumentationPolicyAsync(options: {
8
+ root: string;
9
+ project: Project;
10
+ comments: readonly CollectedDocumentationComment[];
11
+ exports: readonly AnalysisExport[];
12
+ validateSeeUrlAsync?: (url: string) => Promise<unknown>;
13
+ }): Promise<AnalysisDocumentationFinding[]>;