jamdesk 1.1.149 → 1.1.150
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 +28 -0
- package/dist/__tests__/integration/fix-scan-integration.test.d.ts +2 -0
- package/dist/__tests__/integration/fix-scan-integration.test.d.ts.map +1 -0
- package/dist/__tests__/integration/fix-scan-integration.test.js +77 -0
- package/dist/__tests__/integration/fix-scan-integration.test.js.map +1 -0
- package/dist/__tests__/unit/ai-fix-client.test.d.ts +2 -0
- package/dist/__tests__/unit/ai-fix-client.test.d.ts.map +1 -0
- package/dist/__tests__/unit/ai-fix-client.test.js +64 -0
- package/dist/__tests__/unit/ai-fix-client.test.js.map +1 -0
- package/dist/__tests__/unit/docs-config.test.js +66 -0
- package/dist/__tests__/unit/docs-config.test.js.map +1 -1
- package/dist/__tests__/unit/fix-ai-fallback.test.d.ts +2 -0
- package/dist/__tests__/unit/fix-ai-fallback.test.d.ts.map +1 -0
- package/dist/__tests__/unit/fix-ai-fallback.test.js +82 -0
- package/dist/__tests__/unit/fix-ai-fallback.test.js.map +1 -0
- package/dist/__tests__/unit/fix-engine-sync.test.d.ts +2 -0
- package/dist/__tests__/unit/fix-engine-sync.test.d.ts.map +1 -0
- package/dist/__tests__/unit/fix-engine-sync.test.js +16 -0
- package/dist/__tests__/unit/fix-engine-sync.test.js.map +1 -0
- package/dist/__tests__/unit/fix.test.d.ts +2 -0
- package/dist/__tests__/unit/fix.test.d.ts.map +1 -0
- package/dist/__tests__/unit/fix.test.js +61 -0
- package/dist/__tests__/unit/fix.test.js.map +1 -0
- package/dist/__tests__/unit/frontmatter-write.test.d.ts +2 -0
- package/dist/__tests__/unit/frontmatter-write.test.d.ts.map +1 -0
- package/dist/__tests__/unit/frontmatter-write.test.js +31 -0
- package/dist/__tests__/unit/frontmatter-write.test.js.map +1 -0
- package/dist/__tests__/unit/heading-extractor-sync.test.d.ts +2 -0
- package/dist/__tests__/unit/heading-extractor-sync.test.d.ts.map +1 -0
- package/dist/__tests__/unit/heading-extractor-sync.test.js +16 -0
- package/dist/__tests__/unit/heading-extractor-sync.test.js.map +1 -0
- package/dist/__tests__/unit/missing-description-scan.test.d.ts +2 -0
- package/dist/__tests__/unit/missing-description-scan.test.d.ts.map +1 -0
- package/dist/__tests__/unit/missing-description-scan.test.js +36 -0
- package/dist/__tests__/unit/missing-description-scan.test.js.map +1 -0
- package/dist/commands/fix.d.ts +97 -0
- package/dist/commands/fix.d.ts.map +1 -0
- package/dist/commands/fix.js +331 -0
- package/dist/commands/fix.js.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/ai-fix-client.d.ts +27 -0
- package/dist/lib/ai-fix-client.d.ts.map +1 -0
- package/dist/lib/ai-fix-client.js +45 -0
- package/dist/lib/ai-fix-client.js.map +1 -0
- package/dist/lib/deps.js +1 -1
- package/dist/lib/deps.js.map +1 -1
- package/dist/lib/docs-config.d.ts +1 -1
- package/dist/lib/docs-config.d.ts.map +1 -1
- package/dist/lib/docs-config.js +59 -4
- package/dist/lib/docs-config.js.map +1 -1
- package/dist/lib/fix-engine.d.ts +89 -0
- package/dist/lib/fix-engine.d.ts.map +1 -0
- package/dist/lib/fix-engine.js +236 -0
- package/dist/lib/fix-engine.js.map +1 -0
- package/dist/lib/frontmatter-write.d.ts +4 -0
- package/dist/lib/frontmatter-write.d.ts.map +1 -0
- package/dist/lib/frontmatter-write.js +10 -0
- package/dist/lib/frontmatter-write.js.map +1 -0
- package/dist/lib/heading-extractor.d.ts +63 -0
- package/dist/lib/heading-extractor.d.ts.map +1 -0
- package/dist/lib/heading-extractor.js +171 -0
- package/dist/lib/heading-extractor.js.map +1 -0
- package/dist/lib/missing-description-scan.d.ts +7 -0
- package/dist/lib/missing-description-scan.d.ts.map +1 -0
- package/dist/lib/missing-description-scan.js +20 -0
- package/dist/lib/missing-description-scan.js.map +1 -0
- package/package.json +3 -3
- package/vendored/components/mdx/ApiEndpoint.tsx +1 -1
- package/vendored/components/mdx/OpenApiEndpoint.tsx +7 -2
- package/vendored/components/navigation/SocialFooter.tsx +9 -1
- package/vendored/lib/build/error-parser.ts +6 -3
- package/vendored/lib/firestore-helpers.ts +2 -3
- package/vendored/lib/fix-engine.ts +293 -0
- package/vendored/lib/health-checks.ts +2 -2
- package/vendored/lib/json-ld.ts +98 -6
- package/vendored/lib/render-doc-page.tsx +8 -2
- package/vendored/lib/search.ts +2 -2
- package/vendored/lib/seo.ts +29 -13
- package/vendored/lib/static-artifacts.ts +2 -2
- package/vendored/lib/ui-strings.ts +6 -0
- package/vendored/lib/validate-config.ts +63 -4
- package/vendored/scripts/build-search-index.cjs +2 -2
- package/vendored/shared/types.ts +1 -0
- 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 @@
|
|
|
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 @@
|
|
|
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,36 @@
|
|
|
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
|
+
});
|
|
36
|
+
//# 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":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,uBAAuB,EAAE,MAAM,uCAAuC,CAAC;AAEhF,MAAM,KAAK,GAA2B;IACpC,OAAO,EAAE,yDAAyD;IAClE,OAAO,EAAE,iEAAiE;CAC3E,CAAC;AACF,MAAM,QAAQ,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;AAEjD,QAAQ,CAAC,yBAAyB,EAAE,GAAG,EAAE;IACvC,EAAE,CAAC,kEAAkE,EAAE,GAAG,EAAE;QAC1E,MAAM,GAAG,GAAG,uBAAuB,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC;QAClE,MAAM,CAAC,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC5B,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACtC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACnC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,wBAAwB,EAAE,GAAG,EAAE;QAChC,MAAM,CAAC,uBAAuB,CAAC,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,IAAI,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,uDAAuD,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC7G,MAAM,GAAG,GAAG,uBAAuB,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,CAAC;QACrD,MAAM,CAAC,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC5B,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,MAAM,IAAI,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,iCAAiC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACvF,MAAM,GAAG,GAAG,uBAAuB,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,CAAC;QACrD,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,yEAAyE,EAAE,GAAG,EAAE;QACjF,MAAM,IAAI,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,kBAAkB,CAAC,CAAC,CAAC,2BAA2B,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC5F,MAAM,GAAG,GAAG,uBAAuB,CAAC,CAAC,kBAAkB,CAAC,EAAE,IAAI,CAAC,CAAC;QAChE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -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,331 @@
|
|
|
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
|
+
async function confirm(question) {
|
|
172
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
173
|
+
const answer = await new Promise((res) => rl.question(`${question} (y/N) `, res));
|
|
174
|
+
rl.close();
|
|
175
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
176
|
+
}
|
|
177
|
+
export async function runFix(options) {
|
|
178
|
+
const projectDir = process.cwd();
|
|
179
|
+
const config = await loadDocsConfig('docs.json');
|
|
180
|
+
if (!config) {
|
|
181
|
+
output.error('Could not load docs.json');
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
const hostAtDocs = config.hostAtDocs === true;
|
|
185
|
+
const locales = localesFromConfig(config);
|
|
186
|
+
const readPage = readPageFactory(projectDir);
|
|
187
|
+
const headingsOf = (p) => {
|
|
188
|
+
const c = readPage(p);
|
|
189
|
+
return c == null ? null : extractHeadings(c);
|
|
190
|
+
};
|
|
191
|
+
const pageExists = (p) => readPage(p) !== null;
|
|
192
|
+
const spin = spinner('Scanning for fixable warnings...');
|
|
193
|
+
const validateScript = path.join(__dirname, '../../vendored/scripts/validate-links.cjs');
|
|
194
|
+
let warnings = [];
|
|
195
|
+
try {
|
|
196
|
+
const out = execFileSync('node', [validateScript, '--json'], {
|
|
197
|
+
cwd: projectDir, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
198
|
+
env: { ...process.env, CONTENT_DIR: projectDir },
|
|
199
|
+
});
|
|
200
|
+
warnings = JSON.parse(out);
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
// validate-links.cjs may exit non-zero while still printing JSON to stdout
|
|
204
|
+
// (mirrors broken-links.ts). Guard the parse so non-JSON stdout fails cleanly.
|
|
205
|
+
const e = error;
|
|
206
|
+
if (e.stdout) {
|
|
207
|
+
try {
|
|
208
|
+
warnings = JSON.parse(e.stdout);
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
spin.fail('Scan failed');
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
spin.fail('Scan failed');
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
spin.stop();
|
|
221
|
+
if (options.types?.length)
|
|
222
|
+
warnings = warnings.filter((wn) => options.types.includes(wn.type));
|
|
223
|
+
const plan = planFixes(warnings, { hostAtDocs, locales, headingsOf, pageExists });
|
|
224
|
+
// ---- AI fallback tier -------------------------------------------------------
|
|
225
|
+
if (!options.noAi) {
|
|
226
|
+
const hasNeedsAiSkips = plan.skipped.some((s) => s.reason.includes(NEEDS_AI_MARKER));
|
|
227
|
+
const missing = scanMissingDescriptions(getAllPagePaths(config), (p) => readPage(p));
|
|
228
|
+
if (hasNeedsAiSkips || missing.length > 0) {
|
|
229
|
+
const projectId = options.project ?? config.projectId ?? '';
|
|
230
|
+
if (!projectId) {
|
|
231
|
+
output.hint('AI fixes need a project — pass --project <id> or set "projectId" in docs.json. Skipping the AI tier.');
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
const resolveTarget = (href) => {
|
|
235
|
+
const lp = parseHref(href).path;
|
|
236
|
+
if (!lp)
|
|
237
|
+
return null;
|
|
238
|
+
const norm = normalizeLinkPath(lp, hostAtDocs);
|
|
239
|
+
return pageExists(norm) ? norm : null;
|
|
240
|
+
};
|
|
241
|
+
const { request, anchorMeta } = buildAiRequest({ skipped: plan.skipped, missing, headingsOf, resolveTarget });
|
|
242
|
+
if (request.anchors.length || request.descriptions.length) {
|
|
243
|
+
request.projectId = projectId;
|
|
244
|
+
const spin2 = spinner('Asking AI to resolve the remaining warnings...');
|
|
245
|
+
const resp = await requestAiFixes(request);
|
|
246
|
+
spin2.stop();
|
|
247
|
+
const aiAnchorEdits = anchorEditsFromResponse(resp, anchorMeta, headingsOf);
|
|
248
|
+
if (aiAnchorEdits.length) {
|
|
249
|
+
console.log('\nAI-resolved anchors:');
|
|
250
|
+
for (const f of aiAnchorEdits)
|
|
251
|
+
for (const e of f.edits) {
|
|
252
|
+
console.log(` ${f.warning.file}:${f.warning.line} ${e.oldText} → ${e.newText}`);
|
|
253
|
+
}
|
|
254
|
+
plan.fixed.push(...aiAnchorEdits);
|
|
255
|
+
plan.skipped = dropResolvedSkips(plan.skipped, aiAnchorEdits); // no double-count
|
|
256
|
+
}
|
|
257
|
+
// Descriptions: ALWAYS interactive, per-item confirm — the decision is the
|
|
258
|
+
// pure planDescriptionWrites() (no `yes` param, so --yes cannot bypass it).
|
|
259
|
+
// An AI-generated description is a content judgement a human must approve;
|
|
260
|
+
// `--yes` governs only the validated link rewrites above.
|
|
261
|
+
const { toReview, overflow, skippedNoTty } = planDescriptionWrites({
|
|
262
|
+
descriptions: resp.descriptions, missing, cap: DESC_WRITE_CAP,
|
|
263
|
+
isTTY: !!process.stdin.isTTY, dryRun: !!options.dryRun,
|
|
264
|
+
});
|
|
265
|
+
if (overflow > 0)
|
|
266
|
+
output.hint(`AI proposed ${overflow} more description(s); reviewing the first ${DESC_WRITE_CAP} this run — re-run jamdesk fix for the rest.`);
|
|
267
|
+
if (skippedNoTty) {
|
|
268
|
+
output.hint('AI description suggestions need interactive confirmation — re-run jamdesk fix in a terminal to review them. Skipped (descriptions are never written unattended).');
|
|
269
|
+
}
|
|
270
|
+
for (const d of toReview) {
|
|
271
|
+
console.log(`\nProposed description for ${d.file}:\n "${d.text}"`);
|
|
272
|
+
if (options.dryRun) {
|
|
273
|
+
console.log(' (dry run — not written)');
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
if (await confirm(`Write this description to ${d.file}?`)) {
|
|
277
|
+
const abs = path.join(projectDir, d.file);
|
|
278
|
+
fs.writeFileSync(abs, setFrontmatterField(fs.readFileSync(abs, 'utf-8'), 'description', d.text), 'utf-8');
|
|
279
|
+
output.success(`Wrote description to ${d.file}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// ---- end AI fallback tier ---------------------------------------------------
|
|
287
|
+
if (plan.fixed.length === 0) {
|
|
288
|
+
output.info(`Nothing to auto-fix. (${plan.skipped.length} warning(s) left for manual review.)`);
|
|
289
|
+
printSkips(plan);
|
|
290
|
+
process.exit(0);
|
|
291
|
+
}
|
|
292
|
+
console.log('\nPlanned fixes:\n');
|
|
293
|
+
for (const f of plan.fixed) {
|
|
294
|
+
console.log(` ${f.warning.file}:${f.warning.line ?? '?'}`);
|
|
295
|
+
for (const e of f.edits)
|
|
296
|
+
console.log(` ${e.oldText} → ${e.newText}`);
|
|
297
|
+
}
|
|
298
|
+
if (options.dryRun) {
|
|
299
|
+
console.log('\n(dry run — no files written)');
|
|
300
|
+
printSkips(plan);
|
|
301
|
+
process.exit(0);
|
|
302
|
+
}
|
|
303
|
+
if (!options.yes && !(await confirm(`\nApply ${plan.fixed.length} fix(es) to local files?`))) {
|
|
304
|
+
output.info('Aborted — no files changed.');
|
|
305
|
+
process.exit(0);
|
|
306
|
+
}
|
|
307
|
+
const io = {
|
|
308
|
+
readFile: (p) => fs.readFileSync(p, 'utf-8'),
|
|
309
|
+
writeFile: (p, c) => fs.writeFileSync(p, c, 'utf-8'),
|
|
310
|
+
};
|
|
311
|
+
const outcome = applyPlanToFiles(plan, projectDir, io);
|
|
312
|
+
for (const f of outcome.failed)
|
|
313
|
+
output.error(`Could not fix ${f.file}: ${f.error}`);
|
|
314
|
+
if (outcome.failed.length > 0) {
|
|
315
|
+
output.warn(`Fixed ${outcome.written.length} file(s), ${outcome.failed.length} failed; ${plan.skipped.length} warning(s) left for manual review.`);
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
output.success(`Fixed ${outcome.written.length} file(s); ${plan.skipped.length} warning(s) left for manual review.`);
|
|
319
|
+
}
|
|
320
|
+
printSkips(plan);
|
|
321
|
+
if (outcome.failed.length > 0)
|
|
322
|
+
process.exit(1);
|
|
323
|
+
}
|
|
324
|
+
function printSkips(plan) {
|
|
325
|
+
if (plan.skipped.length === 0)
|
|
326
|
+
return;
|
|
327
|
+
console.log('\nNot auto-fixed (manual review):');
|
|
328
|
+
for (const s of plan.skipped)
|
|
329
|
+
console.log(` ${s.warning.file}:${s.warning.line ?? '?'} — ${s.reason}`);
|
|
330
|
+
}
|
|
331
|
+
//# 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')
|