jamdesk 1.1.149 → 1.1.151

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 (85) hide show
  1. package/README.md +28 -0
  2. package/dist/__tests__/integration/fix-scan-integration.test.d.ts +2 -0
  3. package/dist/__tests__/integration/fix-scan-integration.test.d.ts.map +1 -0
  4. package/dist/__tests__/integration/fix-scan-integration.test.js +77 -0
  5. package/dist/__tests__/integration/fix-scan-integration.test.js.map +1 -0
  6. package/dist/__tests__/unit/ai-fix-client.test.d.ts +2 -0
  7. package/dist/__tests__/unit/ai-fix-client.test.d.ts.map +1 -0
  8. package/dist/__tests__/unit/ai-fix-client.test.js +64 -0
  9. package/dist/__tests__/unit/ai-fix-client.test.js.map +1 -0
  10. package/dist/__tests__/unit/docs-config.test.js +66 -0
  11. package/dist/__tests__/unit/docs-config.test.js.map +1 -1
  12. package/dist/__tests__/unit/fix-ai-fallback.test.d.ts +2 -0
  13. package/dist/__tests__/unit/fix-ai-fallback.test.d.ts.map +1 -0
  14. package/dist/__tests__/unit/fix-ai-fallback.test.js +82 -0
  15. package/dist/__tests__/unit/fix-ai-fallback.test.js.map +1 -0
  16. package/dist/__tests__/unit/fix-engine-sync.test.d.ts +2 -0
  17. package/dist/__tests__/unit/fix-engine-sync.test.d.ts.map +1 -0
  18. package/dist/__tests__/unit/fix-engine-sync.test.js +16 -0
  19. package/dist/__tests__/unit/fix-engine-sync.test.js.map +1 -0
  20. package/dist/__tests__/unit/fix.test.d.ts +2 -0
  21. package/dist/__tests__/unit/fix.test.d.ts.map +1 -0
  22. package/dist/__tests__/unit/fix.test.js +61 -0
  23. package/dist/__tests__/unit/fix.test.js.map +1 -0
  24. package/dist/__tests__/unit/frontmatter-write.test.d.ts +2 -0
  25. package/dist/__tests__/unit/frontmatter-write.test.d.ts.map +1 -0
  26. package/dist/__tests__/unit/frontmatter-write.test.js +31 -0
  27. package/dist/__tests__/unit/frontmatter-write.test.js.map +1 -0
  28. package/dist/__tests__/unit/heading-extractor-sync.test.d.ts +2 -0
  29. package/dist/__tests__/unit/heading-extractor-sync.test.d.ts.map +1 -0
  30. package/dist/__tests__/unit/heading-extractor-sync.test.js +16 -0
  31. package/dist/__tests__/unit/heading-extractor-sync.test.js.map +1 -0
  32. package/dist/__tests__/unit/missing-description-scan.test.d.ts +2 -0
  33. package/dist/__tests__/unit/missing-description-scan.test.d.ts.map +1 -0
  34. package/dist/__tests__/unit/missing-description-scan.test.js +50 -0
  35. package/dist/__tests__/unit/missing-description-scan.test.js.map +1 -0
  36. package/dist/commands/fix.d.ts +97 -0
  37. package/dist/commands/fix.d.ts.map +1 -0
  38. package/dist/commands/fix.js +348 -0
  39. package/dist/commands/fix.js.map +1 -0
  40. package/dist/index.js +19 -0
  41. package/dist/index.js.map +1 -1
  42. package/dist/lib/ai-fix-client.d.ts +27 -0
  43. package/dist/lib/ai-fix-client.d.ts.map +1 -0
  44. package/dist/lib/ai-fix-client.js +45 -0
  45. package/dist/lib/ai-fix-client.js.map +1 -0
  46. package/dist/lib/deps.js +1 -1
  47. package/dist/lib/deps.js.map +1 -1
  48. package/dist/lib/docs-config.d.ts +1 -1
  49. package/dist/lib/docs-config.d.ts.map +1 -1
  50. package/dist/lib/docs-config.js +59 -4
  51. package/dist/lib/docs-config.js.map +1 -1
  52. package/dist/lib/fix-engine.d.ts +89 -0
  53. package/dist/lib/fix-engine.d.ts.map +1 -0
  54. package/dist/lib/fix-engine.js +236 -0
  55. package/dist/lib/fix-engine.js.map +1 -0
  56. package/dist/lib/frontmatter-write.d.ts +4 -0
  57. package/dist/lib/frontmatter-write.d.ts.map +1 -0
  58. package/dist/lib/frontmatter-write.js +10 -0
  59. package/dist/lib/frontmatter-write.js.map +1 -0
  60. package/dist/lib/heading-extractor.d.ts +63 -0
  61. package/dist/lib/heading-extractor.d.ts.map +1 -0
  62. package/dist/lib/heading-extractor.js +171 -0
  63. package/dist/lib/heading-extractor.js.map +1 -0
  64. package/dist/lib/missing-description-scan.d.ts +7 -0
  65. package/dist/lib/missing-description-scan.d.ts.map +1 -0
  66. package/dist/lib/missing-description-scan.js +28 -0
  67. package/dist/lib/missing-description-scan.js.map +1 -0
  68. package/package.json +3 -3
  69. package/vendored/components/mdx/ApiEndpoint.tsx +1 -1
  70. package/vendored/components/mdx/OpenApiEndpoint.tsx +7 -2
  71. package/vendored/components/navigation/SocialFooter.tsx +9 -1
  72. package/vendored/lib/build/error-parser.ts +6 -3
  73. package/vendored/lib/firestore-helpers.ts +2 -3
  74. package/vendored/lib/fix-engine.ts +293 -0
  75. package/vendored/lib/health-checks.ts +2 -2
  76. package/vendored/lib/json-ld.ts +98 -6
  77. package/vendored/lib/render-doc-page.tsx +8 -2
  78. package/vendored/lib/search.ts +2 -2
  79. package/vendored/lib/seo.ts +29 -13
  80. package/vendored/lib/static-artifacts.ts +2 -2
  81. package/vendored/lib/ui-strings.ts +6 -0
  82. package/vendored/lib/validate-config.ts +63 -4
  83. package/vendored/scripts/build-search-index.cjs +2 -2
  84. package/vendored/shared/types.ts +1 -0
  85. package/vendored/workspace-package-lock.json +36 -36
@@ -0,0 +1,31 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { setFrontmatterField } from '../../lib/frontmatter-write.js';
3
+ describe('setFrontmatterField', () => {
4
+ it('adds a description to existing frontmatter, body intact', () => {
5
+ const src = '---\ntitle: Setup\n---\n\n## Introduction\n';
6
+ const out = setFrontmatterField(src, 'description', 'A concise summary.');
7
+ expect(out).toMatch(/title: Setup/);
8
+ expect(out).toMatch(/description: A concise summary\./);
9
+ expect(out).toMatch(/## Introduction/);
10
+ });
11
+ it('creates frontmatter when none exists', () => {
12
+ const out = setFrontmatterField('# Hello\n', 'description', 'Hi');
13
+ expect(out.startsWith('---\n')).toBe(true);
14
+ expect(out).toMatch(/description: Hi/);
15
+ expect(out).toMatch(/# Hello/);
16
+ });
17
+ it('does not corrupt a body that contains its own --- divider', () => {
18
+ const src = '---\ntitle: Setup\n---\n\nIntro\n\n---\n\nMore prose\n';
19
+ const out = setFrontmatterField(src, 'description', 'A summary.');
20
+ expect(out).toMatch(/description: A summary\./);
21
+ // both the intro and the post-divider prose survive
22
+ expect(out).toMatch(/Intro/);
23
+ expect(out).toMatch(/More prose/);
24
+ });
25
+ it('round-trips an all-frontmatter source with an empty body', () => {
26
+ const out = setFrontmatterField('---\ntitle: Setup\n---\n', 'description', 'A summary.');
27
+ expect(out).toMatch(/title: Setup/);
28
+ expect(out).toMatch(/description: A summary\./);
29
+ });
30
+ });
31
+ //# sourceMappingURL=frontmatter-write.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontmatter-write.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/frontmatter-write.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAErE,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;QACjE,MAAM,GAAG,GAAG,6CAA6C,CAAC;QAC1D,MAAM,GAAG,GAAG,mBAAmB,CAAC,GAAG,EAAE,aAAa,EAAE,oBAAoB,CAAC,CAAC;QAC1E,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QACpC,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,kCAAkC,CAAC,CAAC;QACxD,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,GAAG,GAAG,mBAAmB,CAAC,WAAW,EAAE,aAAa,EAAE,IAAI,CAAC,CAAC;QAClE,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3C,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;QACvC,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACnE,MAAM,GAAG,GAAG,wDAAwD,CAAC;QACrE,MAAM,GAAG,GAAG,mBAAmB,CAAC,GAAG,EAAE,aAAa,EAAE,YAAY,CAAC,CAAC;QAClE,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,0BAA0B,CAAC,CAAC;QAChD,oDAAoD;QACpD,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAC7B,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,MAAM,GAAG,GAAG,mBAAmB,CAAC,0BAA0B,EAAE,aAAa,EAAE,YAAY,CAAC,CAAC;QACzF,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QACpC,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,0BAA0B,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=heading-extractor-sync.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heading-extractor-sync.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/heading-extractor-sync.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,16 @@
1
+ // builder/cli/src/__tests__/unit/heading-extractor-sync.test.ts
2
+ import { describe, it, expect } from 'vitest';
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+ const stripHeader = (s) => s.replace(/^\/\*\*[\s\S]*?\*\/\s*\n/, '');
8
+ describe('heading-extractor sync', () => {
9
+ it('CLI body matches build-service canonical (excluding header)', () => {
10
+ const cli = stripHeader(fs.readFileSync(path.resolve(__dirname, '../../lib/heading-extractor.ts'), 'utf-8'));
11
+ const canonical = stripHeader(fs.readFileSync(path.resolve(__dirname, '../../../../build-service/lib/heading-extractor.ts'), 'utf-8'));
12
+ expect(cli).not.toBe('');
13
+ expect(cli).toBe(canonical);
14
+ });
15
+ });
16
+ //# sourceMappingURL=heading-extractor-sync.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heading-extractor-sync.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/heading-extractor-sync.test.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAEpC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC/D,MAAM,WAAW,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,0BAA0B,EAAE,EAAE,CAAC,CAAC;AAE7E,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;IACtC,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACrE,MAAM,GAAG,GAAG,WAAW,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,gCAAgC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;QAC7G,MAAM,SAAS,GAAG,WAAW,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,oDAAoD,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;QACvI,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACzB,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=missing-description-scan.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"missing-description-scan.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/missing-description-scan.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,50 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { scanMissingDescriptions } from '../../lib/missing-description-scan.js';
3
+ const pages = {
4
+ 'setup': '---\ntitle: Setup\n---\n\nInstall the CLI and run it.\n',
5
+ 'about': '---\ntitle: About\ndescription: Already has one\n---\n\nBody.\n',
6
+ };
7
+ const readPage = (p) => pages[p] ?? null;
8
+ describe('scanMissingDescriptions', () => {
9
+ it('returns only pages missing a description, with title + body text', () => {
10
+ const out = scanMissingDescriptions(['setup', 'about'], readPage);
11
+ expect(out).toHaveLength(1);
12
+ expect(out[0].file).toBe('setup.mdx');
13
+ expect(out[0].title).toBe('Setup');
14
+ expect(out[0].pageText).toContain('Install the CLI');
15
+ });
16
+ it('skips unreadable pages', () => {
17
+ expect(scanMissingDescriptions(['missing'], readPage)).toEqual([]);
18
+ });
19
+ it('treats a whitespace-only description as missing', () => {
20
+ const read = (p) => (p === 'blank' ? '---\ntitle: Blank\ndescription: " "\n---\n\nBody.\n' : null);
21
+ const out = scanMissingDescriptions(['blank'], read);
22
+ expect(out).toHaveLength(1);
23
+ expect(out[0].file).toBe('blank.mdx');
24
+ });
25
+ it('falls back to the first H1 when frontmatter has no title', () => {
26
+ const read = (p) => (p === 'guide' ? '# Getting Started\n\nWelcome.\n' : null);
27
+ const out = scanMissingDescriptions(['guide'], read);
28
+ expect(out[0].title).toBe('Getting Started');
29
+ });
30
+ it('falls back to the slug (last path segment) when there is no title or H1', () => {
31
+ const read = (p) => (p === 'docs/nested/page' ? 'Just prose, no heading.\n' : null);
32
+ const out = scanMissingDescriptions(['docs/nested/page'], read);
33
+ expect(out[0].title).toBe('page');
34
+ });
35
+ it('emits the resolver-supplied on-disk path for a dir-index page', () => {
36
+ // "api" is backed by api/index.mdx, so the write target must be that real file,
37
+ // not a fabricated api.mdx (which would crash the description write with ENOENT).
38
+ const read = (p) => (p === 'api' ? '---\ntitle: API\n---\n\nReference.\n' : null);
39
+ const resolveFile = (p) => (p === 'api' ? 'api/index.mdx' : null);
40
+ const out = scanMissingDescriptions(['api'], read, resolveFile);
41
+ expect(out).toHaveLength(1);
42
+ expect(out[0].file).toBe('api/index.mdx');
43
+ });
44
+ it('skips a page the resolver cannot place on disk', () => {
45
+ const read = (p) => (p === 'ghost' ? '---\ntitle: Ghost\n---\n\nBody.\n' : null);
46
+ const resolveFile = () => null;
47
+ expect(scanMissingDescriptions(['ghost'], read, resolveFile)).toEqual([]);
48
+ });
49
+ });
50
+ //# sourceMappingURL=missing-description-scan.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"missing-description-scan.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/missing-description-scan.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,97 @@
1
+ import { type FixWarning, type FixResult, type LocaleInfo, type EngineHeading } from '../lib/fix-engine.js';
2
+ import { type MissingDescription } from '../lib/missing-description-scan.js';
3
+ import { type AiFixRequest, type AiFixResponse } from '../lib/ai-fix-client.js';
4
+ export interface AiAnchorMeta {
5
+ warning: FixWarning;
6
+ targetPage: string;
7
+ linkPath: string;
8
+ fragment: string;
9
+ }
10
+ export declare function buildAiRequest(input: {
11
+ skipped: Array<{
12
+ warning: FixWarning;
13
+ reason: string;
14
+ }>;
15
+ missing: MissingDescription[];
16
+ headingsOf: (p: string) => EngineHeading[] | null;
17
+ resolveTarget: (href: string) => string | null;
18
+ }): {
19
+ request: AiFixRequest;
20
+ anchorMeta: AiAnchorMeta[];
21
+ };
22
+ export declare function anchorEditsFromResponse(resp: AiFixResponse, anchorMeta: AiAnchorMeta[], headingsOf: (p: string) => EngineHeading[] | null): FixPlan['fixed'];
23
+ /** Remove from `skipped` any entry whose warning the AI resolved. Object-reference
24
+ * equality holds: anchorMeta carries the same `warning` object that came from
25
+ * plan.skipped, so an AI-fixed warning is never double-counted (fixed AND skipped). */
26
+ export declare function dropResolvedSkips(skipped: FixPlan['skipped'], resolved: FixPlan['fixed']): FixPlan['skipped'];
27
+ export interface DescriptionWrite {
28
+ index: number;
29
+ text: string;
30
+ file: string;
31
+ }
32
+ /** Decide which AI descriptions to surface for confirmation. PURE so the safety
33
+ * rules are provable without driving the interactive shell. Note there is NO
34
+ * `yes` parameter: --yes CANNOT bypass the description confirm by construction —
35
+ * an AI-written description is a content judgement that always needs a human y/N.
36
+ * - filters to descriptions with text AND a matching `missing` page
37
+ * - caps at `cap` per run (`overflow` = how many were deferred to a later run)
38
+ * - `skippedNoTty`: in a non-TTY run (CI/pipe) `confirm` can't read a keypress, so
39
+ * the caller skips writing entirely rather than auto-write or hang. Dry-run is
40
+ * NOT a skip — it still lists proposals (the caller just doesn't write). */
41
+ export declare function planDescriptionWrites(input: {
42
+ descriptions: AiFixResponse['descriptions'];
43
+ missing: MissingDescription[];
44
+ cap: number;
45
+ isTTY: boolean;
46
+ dryRun: boolean;
47
+ }): {
48
+ toReview: DescriptionWrite[];
49
+ overflow: number;
50
+ skippedNoTty: boolean;
51
+ };
52
+ export interface FixPlan {
53
+ fixed: Array<{
54
+ warning: FixWarning;
55
+ edits: FixResult['edits'];
56
+ }>;
57
+ skipped: Array<{
58
+ warning: FixWarning;
59
+ reason: string;
60
+ }>;
61
+ }
62
+ interface PlanDeps {
63
+ hostAtDocs: boolean;
64
+ locales: LocaleInfo;
65
+ headingsOf: (pagePath: string) => EngineHeading[] | null;
66
+ pageExists: (pagePath: string) => boolean;
67
+ }
68
+ /** Pure: turn warnings into a fix plan. No filesystem writes. */
69
+ export declare function planFixes(warnings: FixWarning[], deps: PlanDeps): FixPlan;
70
+ export interface FileIO {
71
+ readFile: (absPath: string) => string;
72
+ writeFile: (absPath: string, content: string) => void;
73
+ }
74
+ export interface ApplyOutcome {
75
+ written: string[];
76
+ failed: Array<{
77
+ file: string;
78
+ error: string;
79
+ }>;
80
+ }
81
+ /**
82
+ * Apply a fix plan to files. Groups edits by file; a file whose source no longer
83
+ * matches an edit (changed since the scan) is recorded as failed and skipped —
84
+ * the others are still written. NEVER writes a partial/wrong edit. Pure except
85
+ * for the injected FileIO, so the failure mode is unit-testable without disk.
86
+ */
87
+ export declare function applyPlanToFiles(plan: FixPlan, projectDir: string, io: FileIO): ApplyOutcome;
88
+ export interface FixOptions {
89
+ dryRun: boolean;
90
+ yes: boolean;
91
+ types?: string[];
92
+ noAi: boolean;
93
+ project?: string;
94
+ }
95
+ export declare function runFix(options: FixOptions): Promise<void>;
96
+ export {};
97
+ //# sourceMappingURL=fix.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fix.d.ts","sourceRoot":"","sources":["../../src/commands/fix.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,348 @@
1
+ // builder/cli/src/commands/fix.ts
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
4
+ import { execFileSync } from 'child_process';
5
+ import { fileURLToPath } from 'url';
6
+ import { createInterface } from 'readline';
7
+ import { output } from '../lib/output.js';
8
+ import { spinner } from '../lib/spinner.js';
9
+ import { loadDocsConfig, getAllPagePaths } from '../lib/docs-config.js';
10
+ import { extractHeadings } from '../lib/heading-extractor.js';
11
+ import { runFixer, applyEdits, normalizeLinkPath, parseHref, isRealAnchor, NEEDS_AI_MARKER, } from '../lib/fix-engine.js';
12
+ import { scanMissingDescriptions } from '../lib/missing-description-scan.js';
13
+ import { setFrontmatterField } from '../lib/frontmatter-write.js';
14
+ import { requestAiFixes } from '../lib/ai-fix-client.js';
15
+ // The engine tags AI-deferrable skips by appending NEEDS_AI_MARKER (imported from
16
+ // fix-engine) to the skip reason — we match on it as a substring so the engine and the
17
+ // CLI can never drift on the exact marker text.
18
+ // Per-run cap on AI descriptions surfaced for confirmation. Bounds review fatigue and
19
+ // cost on a first run over a large docs set; the rest are picked up on the next run.
20
+ const DESC_WRITE_CAP = 25;
21
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
22
+ export function buildAiRequest(input) {
23
+ const anchorMeta = [];
24
+ const anchors = [];
25
+ for (const s of input.skipped) {
26
+ if (s.warning.type !== 'broken_link' || !s.reason.includes(NEEDS_AI_MARKER))
27
+ continue;
28
+ // An anchor edit needs a line to place; a null-line warning can't be safely
29
+ // edited (mirrors the guard in collectAiAnchorSuggestions). Unreachable today —
30
+ // the engine emits a distinct null-line skip reason that never carries NEEDS_AI —
31
+ // but kept for defense-in-depth symmetry so a future engine change can't slip a
32
+ // `line: null as number` edit through applyEdits.
33
+ if (s.warning.line == null)
34
+ continue;
35
+ const { path: linkPath, fragment } = parseHref(s.warning.link);
36
+ if (!linkPath || !fragment)
37
+ continue;
38
+ const targetPage = input.resolveTarget(s.warning.link);
39
+ if (!targetPage)
40
+ continue;
41
+ const headings = input.headingsOf(targetPage);
42
+ if (!headings || headings.length === 0)
43
+ continue;
44
+ anchors.push({ brokenFragment: fragment, targetHeadingIds: headings.map((h) => h.id) });
45
+ anchorMeta.push({ warning: s.warning, targetPage, linkPath, fragment });
46
+ }
47
+ const descriptions = input.missing.map((m) => ({ title: m.title, pageText: m.pageText }));
48
+ return { request: { projectId: '', anchors, descriptions }, anchorMeta };
49
+ }
50
+ export function anchorEditsFromResponse(resp, anchorMeta, headingsOf) {
51
+ const fixed = [];
52
+ for (const a of resp.anchors) {
53
+ const meta = anchorMeta[a.index];
54
+ if (!a.id || !meta)
55
+ continue;
56
+ const headings = headingsOf(meta.targetPage);
57
+ if (!headings || !isRealAnchor(a.id, headings))
58
+ continue; // hallucination guard
59
+ fixed.push({ warning: meta.warning, edits: [{ file: meta.warning.file, line: meta.warning.line, oldText: meta.warning.link, newText: `${meta.linkPath}#${a.id}` }] });
60
+ }
61
+ return fixed;
62
+ }
63
+ /** Remove from `skipped` any entry whose warning the AI resolved. Object-reference
64
+ * equality holds: anchorMeta carries the same `warning` object that came from
65
+ * plan.skipped, so an AI-fixed warning is never double-counted (fixed AND skipped). */
66
+ export function dropResolvedSkips(skipped, resolved) {
67
+ const resolvedWarnings = new Set(resolved.map((f) => f.warning));
68
+ return skipped.filter((s) => !resolvedWarnings.has(s.warning));
69
+ }
70
+ /** Decide which AI descriptions to surface for confirmation. PURE so the safety
71
+ * rules are provable without driving the interactive shell. Note there is NO
72
+ * `yes` parameter: --yes CANNOT bypass the description confirm by construction —
73
+ * an AI-written description is a content judgement that always needs a human y/N.
74
+ * - filters to descriptions with text AND a matching `missing` page
75
+ * - caps at `cap` per run (`overflow` = how many were deferred to a later run)
76
+ * - `skippedNoTty`: in a non-TTY run (CI/pipe) `confirm` can't read a keypress, so
77
+ * the caller skips writing entirely rather than auto-write or hang. Dry-run is
78
+ * NOT a skip — it still lists proposals (the caller just doesn't write). */
79
+ export function planDescriptionWrites(input) {
80
+ const writable = [];
81
+ for (const d of input.descriptions) {
82
+ const m = input.missing[d.index];
83
+ if (d.text && m)
84
+ writable.push({ index: d.index, text: d.text, file: m.file });
85
+ }
86
+ const toReview = writable.slice(0, input.cap);
87
+ const overflow = writable.length - toReview.length;
88
+ const skippedNoTty = toReview.length > 0 && !input.dryRun && !input.isTTY;
89
+ return { toReview: skippedNoTty ? [] : toReview, overflow, skippedNoTty };
90
+ }
91
+ /** Pure: turn warnings into a fix plan. No filesystem writes. */
92
+ export function planFixes(warnings, deps) {
93
+ const plan = { fixed: [], skipped: [] };
94
+ for (const w of warnings) {
95
+ const result = runFixer(w, {
96
+ currentPagePath: w.file.replace(/\.mdx$/, ''),
97
+ locales: deps.locales,
98
+ resolveLinkPagePath: (href) => {
99
+ const linkPath = parseHref(href).path;
100
+ if (!linkPath)
101
+ return null;
102
+ const p = normalizeLinkPath(linkPath, deps.hostAtDocs);
103
+ return deps.pageExists(p) ? p : null;
104
+ },
105
+ headingsOf: deps.headingsOf,
106
+ });
107
+ if (result.status === 'fixed')
108
+ plan.fixed.push({ warning: w, edits: result.edits });
109
+ else
110
+ plan.skipped.push({ warning: w, reason: result.reason });
111
+ }
112
+ return plan;
113
+ }
114
+ /**
115
+ * Apply a fix plan to files. Groups edits by file; a file whose source no longer
116
+ * matches an edit (changed since the scan) is recorded as failed and skipped —
117
+ * the others are still written. NEVER writes a partial/wrong edit. Pure except
118
+ * for the injected FileIO, so the failure mode is unit-testable without disk.
119
+ */
120
+ export function applyPlanToFiles(plan, projectDir, io) {
121
+ const byFile = new Map();
122
+ for (const f of plan.fixed) {
123
+ const list = byFile.get(f.warning.file);
124
+ if (list)
125
+ list.push(...f.edits);
126
+ else
127
+ byFile.set(f.warning.file, [...f.edits]);
128
+ }
129
+ const outcome = { written: [], failed: [] };
130
+ for (const [file, edits] of byFile) {
131
+ const abs = path.join(projectDir, file);
132
+ try {
133
+ io.writeFile(abs, applyEdits(io.readFile(abs), edits));
134
+ outcome.written.push(file);
135
+ }
136
+ catch (err) {
137
+ outcome.failed.push({ file, error: err.message });
138
+ }
139
+ }
140
+ return outcome;
141
+ }
142
+ function localesFromConfig(config) {
143
+ const nav = (config.navigation ?? {});
144
+ const langs = nav.languages ?? [];
145
+ if (langs.length === 0)
146
+ return { codes: [], defaultCode: '' };
147
+ const def = langs.find((l) => l.default) ?? langs[0];
148
+ return { codes: langs.map((l) => l.language), defaultCode: def.language };
149
+ }
150
+ function readPageFactory(projectDir) {
151
+ // Memoize across the whole runFix invocation: pageExists + headingsOf both call
152
+ // this, and many warnings can target the same page — without the cache each page
153
+ // is re-read (and re-parsed) once per warning that links to it.
154
+ const cache = new Map();
155
+ return (pagePath) => {
156
+ if (cache.has(pagePath))
157
+ return cache.get(pagePath);
158
+ let result = null;
159
+ const mdx = path.join(projectDir, pagePath + '.mdx');
160
+ if (fs.existsSync(mdx))
161
+ result = fs.readFileSync(mdx, 'utf-8');
162
+ else {
163
+ const idx = path.join(projectDir, pagePath, 'index.mdx');
164
+ if (fs.existsSync(idx))
165
+ result = fs.readFileSync(idx, 'utf-8');
166
+ }
167
+ cache.set(pagePath, result);
168
+ return result;
169
+ };
170
+ }
171
+ // Resolve a nav page key to its actual on-disk relative path, mirroring
172
+ // readPageFactory's mdx-then-index lookup. Used as the write target for AI
173
+ // descriptions so a dir-index page ("api" → "api/index.mdx") is edited in place
174
+ // instead of fabricating a stray "api.mdx".
175
+ function resolvePageFile(projectDir, pagePath) {
176
+ if (fs.existsSync(path.join(projectDir, `${pagePath}.mdx`)))
177
+ return `${pagePath}.mdx`;
178
+ if (fs.existsSync(path.join(projectDir, pagePath, 'index.mdx')))
179
+ return `${pagePath}/index.mdx`;
180
+ return null;
181
+ }
182
+ async function confirm(question) {
183
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
184
+ const answer = await new Promise((res) => rl.question(`${question} (y/N) `, res));
185
+ rl.close();
186
+ return /^y(es)?$/i.test(answer.trim());
187
+ }
188
+ export async function runFix(options) {
189
+ const projectDir = process.cwd();
190
+ const config = await loadDocsConfig('docs.json');
191
+ if (!config) {
192
+ output.error('Could not load docs.json');
193
+ process.exit(1);
194
+ }
195
+ const hostAtDocs = config.hostAtDocs === true;
196
+ const locales = localesFromConfig(config);
197
+ const readPage = readPageFactory(projectDir);
198
+ const headingsOf = (p) => {
199
+ const c = readPage(p);
200
+ return c == null ? null : extractHeadings(c);
201
+ };
202
+ const pageExists = (p) => readPage(p) !== null;
203
+ const spin = spinner('Scanning for fixable warnings...');
204
+ const validateScript = path.join(__dirname, '../../vendored/scripts/validate-links.cjs');
205
+ let warnings = [];
206
+ try {
207
+ const out = execFileSync('node', [validateScript, '--json'], {
208
+ cwd: projectDir, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'],
209
+ env: { ...process.env, CONTENT_DIR: projectDir },
210
+ });
211
+ warnings = JSON.parse(out);
212
+ }
213
+ catch (error) {
214
+ // validate-links.cjs may exit non-zero while still printing JSON to stdout
215
+ // (mirrors broken-links.ts). Guard the parse so non-JSON stdout fails cleanly.
216
+ const e = error;
217
+ if (e.stdout) {
218
+ try {
219
+ warnings = JSON.parse(e.stdout);
220
+ }
221
+ catch {
222
+ spin.fail('Scan failed');
223
+ process.exit(1);
224
+ }
225
+ }
226
+ else {
227
+ spin.fail('Scan failed');
228
+ process.exit(1);
229
+ }
230
+ }
231
+ spin.stop();
232
+ if (options.types?.length)
233
+ warnings = warnings.filter((wn) => options.types.includes(wn.type));
234
+ const plan = planFixes(warnings, { hostAtDocs, locales, headingsOf, pageExists });
235
+ // ---- AI fallback tier -------------------------------------------------------
236
+ if (!options.noAi) {
237
+ const hasNeedsAiSkips = plan.skipped.some((s) => s.reason.includes(NEEDS_AI_MARKER));
238
+ const missing = scanMissingDescriptions(getAllPagePaths(config), (p) => readPage(p), (p) => resolvePageFile(projectDir, p));
239
+ if (hasNeedsAiSkips || missing.length > 0) {
240
+ const projectId = options.project ?? config.projectId ?? '';
241
+ if (!projectId) {
242
+ output.hint('AI fixes need a project — pass --project <id> or set "projectId" in docs.json. Skipping the AI tier.');
243
+ }
244
+ else {
245
+ const resolveTarget = (href) => {
246
+ const lp = parseHref(href).path;
247
+ if (!lp)
248
+ return null;
249
+ const norm = normalizeLinkPath(lp, hostAtDocs);
250
+ return pageExists(norm) ? norm : null;
251
+ };
252
+ const { request, anchorMeta } = buildAiRequest({ skipped: plan.skipped, missing, headingsOf, resolveTarget });
253
+ if (request.anchors.length || request.descriptions.length) {
254
+ request.projectId = projectId;
255
+ const spin2 = spinner('Asking AI to resolve the remaining warnings...');
256
+ const resp = await requestAiFixes(request);
257
+ spin2.stop();
258
+ const aiAnchorEdits = anchorEditsFromResponse(resp, anchorMeta, headingsOf);
259
+ if (aiAnchorEdits.length) {
260
+ console.log('\nAI-resolved anchors:');
261
+ for (const f of aiAnchorEdits)
262
+ for (const e of f.edits) {
263
+ console.log(` ${f.warning.file}:${f.warning.line} ${e.oldText} → ${e.newText}`);
264
+ }
265
+ plan.fixed.push(...aiAnchorEdits);
266
+ plan.skipped = dropResolvedSkips(plan.skipped, aiAnchorEdits); // no double-count
267
+ }
268
+ // Descriptions: ALWAYS interactive, per-item confirm — the decision is the
269
+ // pure planDescriptionWrites() (no `yes` param, so --yes cannot bypass it).
270
+ // An AI-generated description is a content judgement a human must approve;
271
+ // `--yes` governs only the validated link rewrites above.
272
+ const { toReview, overflow, skippedNoTty } = planDescriptionWrites({
273
+ descriptions: resp.descriptions, missing, cap: DESC_WRITE_CAP,
274
+ isTTY: !!process.stdin.isTTY, dryRun: !!options.dryRun,
275
+ });
276
+ if (overflow > 0)
277
+ output.hint(`AI proposed ${overflow} more description(s); reviewing the first ${DESC_WRITE_CAP} this run — re-run jamdesk fix for the rest.`);
278
+ if (skippedNoTty) {
279
+ output.hint('AI description suggestions need interactive confirmation — re-run jamdesk fix in a terminal to review them. Skipped (descriptions are never written unattended).');
280
+ }
281
+ for (const d of toReview) {
282
+ console.log(`\nProposed description for ${d.file}:\n "${d.text}"`);
283
+ if (options.dryRun) {
284
+ console.log(' (dry run — not written)');
285
+ continue;
286
+ }
287
+ const abs = path.join(projectDir, d.file);
288
+ // Defense in depth: never read/write a target that isn't on disk (an
289
+ // unresolved page would otherwise throw ENOENT mid-write).
290
+ if (!fs.existsSync(abs)) {
291
+ output.hint(`Skipped ${d.file} — file not found on disk.`);
292
+ continue;
293
+ }
294
+ if (await confirm(`Write this description to ${d.file}?`)) {
295
+ fs.writeFileSync(abs, setFrontmatterField(fs.readFileSync(abs, 'utf-8'), 'description', d.text), 'utf-8');
296
+ output.success(`Wrote description to ${d.file}`);
297
+ }
298
+ }
299
+ }
300
+ }
301
+ }
302
+ }
303
+ // ---- end AI fallback tier ---------------------------------------------------
304
+ if (plan.fixed.length === 0) {
305
+ output.info(`Nothing to auto-fix. (${plan.skipped.length} warning(s) left for manual review.)`);
306
+ printSkips(plan);
307
+ process.exit(0);
308
+ }
309
+ console.log('\nPlanned fixes:\n');
310
+ for (const f of plan.fixed) {
311
+ console.log(` ${f.warning.file}:${f.warning.line ?? '?'}`);
312
+ for (const e of f.edits)
313
+ console.log(` ${e.oldText} → ${e.newText}`);
314
+ }
315
+ if (options.dryRun) {
316
+ console.log('\n(dry run — no files written)');
317
+ printSkips(plan);
318
+ process.exit(0);
319
+ }
320
+ if (!options.yes && !(await confirm(`\nApply ${plan.fixed.length} fix(es) to local files?`))) {
321
+ output.info('Aborted — no files changed.');
322
+ process.exit(0);
323
+ }
324
+ const io = {
325
+ readFile: (p) => fs.readFileSync(p, 'utf-8'),
326
+ writeFile: (p, c) => fs.writeFileSync(p, c, 'utf-8'),
327
+ };
328
+ const outcome = applyPlanToFiles(plan, projectDir, io);
329
+ for (const f of outcome.failed)
330
+ output.error(`Could not fix ${f.file}: ${f.error}`);
331
+ if (outcome.failed.length > 0) {
332
+ output.warn(`Fixed ${outcome.written.length} file(s), ${outcome.failed.length} failed; ${plan.skipped.length} warning(s) left for manual review.`);
333
+ }
334
+ else {
335
+ output.success(`Fixed ${outcome.written.length} file(s); ${plan.skipped.length} warning(s) left for manual review.`);
336
+ }
337
+ printSkips(plan);
338
+ if (outcome.failed.length > 0)
339
+ process.exit(1);
340
+ }
341
+ function printSkips(plan) {
342
+ if (plan.skipped.length === 0)
343
+ return;
344
+ console.log('\nNot auto-fixed (manual review):');
345
+ for (const s of plan.skipped)
346
+ console.log(` ${s.warning.file}:${s.warning.line ?? '?'} — ${s.reason}`);
347
+ }
348
+ //# sourceMappingURL=fix.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fix.js","sourceRoot":"","sources":["../../src/commands/fix.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -185,6 +185,25 @@ Output shows the file, line number, and suggests corrections:
185
185
  const { brokenLinks } = await import('./commands/broken-links.js');
186
186
  await brokenLinks({ verbose });
187
187
  });
188
+ // Fix command
189
+ program
190
+ .command('fix')
191
+ .description('Auto-fix doc warnings where the fix is known (broken internal links). Edits local files.')
192
+ .option('--dry-run', 'Show planned fixes without writing files', false)
193
+ .option('-y, --yes', 'Apply fixes without confirmation', false)
194
+ .option('--types <types>', 'Comma-separated warning types to fix (default: all supported)')
195
+ .option('--no-ai', 'Skip the AI fallback tier (deterministic only)')
196
+ .option('--project <id>', 'Project id for AI fixes (defaults to docs.json projectId)')
197
+ .action(async (opts) => {
198
+ const { runFix } = await import('./commands/fix.js');
199
+ await runFix({
200
+ dryRun: !!opts.dryRun,
201
+ yes: !!opts.yes,
202
+ types: opts.types ? String(opts.types).split(',').map((s) => s.trim()) : undefined,
203
+ noAi: !opts.ai,
204
+ project: opts.project,
205
+ });
206
+ });
188
207
  // Spellcheck command
189
208
  program
190
209
  .command('spellcheck')