@open-agent-toolkit/cli 0.3.26 → 0.3.28

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 (104) hide show
  1. package/README.md +12 -0
  2. package/assets/bundle-metadata.json +1 -1
  3. package/assets/docs/contributing/documentation.md +48 -6
  4. package/assets/docs/contributing/markdown-features.md +14 -2
  5. package/assets/docs/docs-tooling/add-docs-to-a-repo.md +22 -9
  6. package/assets/docs/docs-tooling/check.md +154 -0
  7. package/assets/docs/docs-tooling/commands.md +15 -10
  8. package/assets/docs/docs-tooling/index.md +1 -0
  9. package/assets/docs/docs-tooling/meta.json +7 -1
  10. package/assets/docs/docs-tooling/workflows.md +18 -10
  11. package/assets/docs/getting-started/tool-packs.md +5 -3
  12. package/assets/docs/reference/cli-reference.md +2 -0
  13. package/assets/docs/reference/configuration.md +1 -0
  14. package/assets/docs/skills/index.md +8 -8
  15. package/assets/public-package-versions.json +4 -4
  16. package/assets/skills/oat-docs-analyze/SKILL.md +14 -3
  17. package/assets/skills/oat-docs-analyze/references/legacy-scaffold-checks.md +4 -0
  18. package/assets/skills/oat-docs-authoring/SKILL.md +1 -1
  19. package/assets/skills/oat-docs-authoring/references/validation.md +4 -0
  20. package/assets/skills/oat-docs-bootstrap/SKILL.md +92 -61
  21. package/assets/skills/oat-docs-bootstrap/references/pr-ci.md +89 -0
  22. package/assets/templates/docs-app-fuma/AGENTS.md +57 -0
  23. package/assets/templates/docs-app-fuma/app/[[...slug]]/page.tsx +7 -3
  24. package/assets/templates/docs-app-fuma/app/llms/[...slug]/route.ts +15 -0
  25. package/assets/templates/docs-app-fuma/app/llms-full.txt/route.ts +7 -0
  26. package/assets/templates/docs-app-fuma/app/llms.txt/route.ts +7 -0
  27. package/assets/templates/docs-app-fuma/docs/contributing/docs-toolchain.md +85 -0
  28. package/assets/templates/docs-app-fuma/docs/contributing/index.md +13 -0
  29. package/assets/templates/docs-app-fuma/docs/contributing.md +83 -41
  30. package/assets/templates/docs-app-fuma/docs/getting-started.md +3 -33
  31. package/assets/templates/docs-app-fuma/docs/index.md +3 -3
  32. package/assets/templates/docs-app-fuma/gitignore.template +12 -0
  33. package/assets/templates/docs-app-fuma/lib/source.ts +5 -0
  34. package/assets/templates/docs-app-fuma/package.json.template +2 -1
  35. package/assets/templates/docs-app-fuma/pnpm-workspace.yaml +5 -0
  36. package/assets/templates/docs-app-mkdocs/AGENTS.md +48 -0
  37. package/assets/templates/docs-app-mkdocs/docs/contributing/docs-toolchain.md +46 -0
  38. package/assets/templates/docs-app-mkdocs/docs/contributing/index.md +13 -0
  39. package/assets/templates/docs-app-mkdocs/docs/contributing.md +111 -21
  40. package/assets/templates/docs-app-mkdocs/docs/getting-started.md +7 -25
  41. package/assets/templates/docs-app-mkdocs/docs/index.md +7 -2
  42. package/assets/templates/docs-app-mkdocs/gitignore.template +8 -0
  43. package/assets/templates/docs-app-mkdocs/mkdocs.yml +1 -0
  44. package/assets/templates/docs-app-mkdocs/package.json.template +3 -2
  45. package/dist/commands/config/index.d.ts.map +1 -1
  46. package/dist/commands/config/index.js +35 -3
  47. package/dist/commands/docs/check/check.d.ts +3 -0
  48. package/dist/commands/docs/check/check.d.ts.map +1 -0
  49. package/dist/commands/docs/check/check.js +48 -0
  50. package/dist/commands/docs/check/check.types.d.ts +17 -0
  51. package/dist/commands/docs/check/check.types.d.ts.map +1 -0
  52. package/dist/commands/docs/check/check.types.js +1 -0
  53. package/dist/commands/docs/check/checker.d.ts +8 -0
  54. package/dist/commands/docs/check/checker.d.ts.map +1 -0
  55. package/dist/commands/docs/check/checker.js +82 -0
  56. package/dist/commands/docs/check/commands.d.ts +5 -0
  57. package/dist/commands/docs/check/commands.d.ts.map +1 -0
  58. package/dist/commands/docs/check/commands.js +232 -0
  59. package/dist/commands/docs/check/diagrams.d.ts +5 -0
  60. package/dist/commands/docs/check/diagrams.d.ts.map +1 -0
  61. package/dist/commands/docs/check/diagrams.js +197 -0
  62. package/dist/commands/docs/check/files.d.ts +21 -0
  63. package/dist/commands/docs/check/files.d.ts.map +1 -0
  64. package/dist/commands/docs/check/files.js +121 -0
  65. package/dist/commands/docs/check/markdown.d.ts +60 -0
  66. package/dist/commands/docs/check/markdown.d.ts.map +1 -0
  67. package/dist/commands/docs/check/markdown.js +125 -0
  68. package/dist/commands/docs/check/mermaid-runtime.d.ts +3 -0
  69. package/dist/commands/docs/check/mermaid-runtime.d.ts.map +1 -0
  70. package/dist/commands/docs/check/mermaid-runtime.js +57 -0
  71. package/dist/commands/docs/check/navigation.d.ts +4 -0
  72. package/dist/commands/docs/check/navigation.d.ts.map +1 -0
  73. package/dist/commands/docs/check/navigation.js +104 -0
  74. package/dist/commands/docs/check/reader-rules.d.ts +6 -0
  75. package/dist/commands/docs/check/reader-rules.d.ts.map +1 -0
  76. package/dist/commands/docs/check/reader-rules.js +214 -0
  77. package/dist/commands/docs/index.d.ts.map +1 -1
  78. package/dist/commands/docs/index.js +2 -0
  79. package/dist/commands/docs/init/docs-commands.d.ts +1 -0
  80. package/dist/commands/docs/init/docs-commands.d.ts.map +1 -1
  81. package/dist/commands/docs/init/docs-commands.js +8 -3
  82. package/dist/commands/docs/init/index.d.ts +1 -0
  83. package/dist/commands/docs/init/index.d.ts.map +1 -1
  84. package/dist/commands/docs/init/index.js +8 -0
  85. package/dist/commands/docs/init/scaffold.d.ts +1 -0
  86. package/dist/commands/docs/init/scaffold.d.ts.map +1 -1
  87. package/dist/commands/docs/init/scaffold.js +61 -13
  88. package/dist/commands/docs/nav/contents.d.ts +4 -0
  89. package/dist/commands/docs/nav/contents.d.ts.map +1 -1
  90. package/dist/commands/docs/nav/contents.js +10 -0
  91. package/dist/commands/docs/nav/fumadocs.d.ts.map +1 -1
  92. package/dist/commands/docs/nav/fumadocs.js +32 -10
  93. package/dist/config/documentation-check.d.ts +38 -0
  94. package/dist/config/documentation-check.d.ts.map +1 -0
  95. package/dist/config/documentation-check.js +132 -0
  96. package/dist/config/oat-config.d.ts +2 -0
  97. package/dist/config/oat-config.d.ts.map +1 -1
  98. package/dist/config/oat-config.js +4 -0
  99. package/dist/config/resolve.d.ts.map +1 -1
  100. package/dist/config/resolve.js +1 -0
  101. package/dist/release/public-package-contract.d.ts.map +1 -1
  102. package/dist/release/public-package-contract.js +3 -0
  103. package/package.json +11 -2
  104. package/assets/skills/oat-docs-bootstrap/assets/AGENTS.md.template +0 -64
@@ -0,0 +1,125 @@
1
+ import { remarkTabs } from '@open-agent-toolkit/docs-transforms';
2
+ import Slugger from 'github-slugger';
3
+ import { parseFragment } from 'parse5';
4
+ import remarkGfm from 'remark-gfm';
5
+ import remarkParse from 'remark-parse';
6
+ import { unified } from 'unified';
7
+ import YAML from 'yaml';
8
+ export function nodeText(node) {
9
+ return node.children
10
+ ? node.children.map(nodeText).join('')
11
+ : (node.value ?? '');
12
+ }
13
+ export function htmlNodes(html) {
14
+ const nodes = [];
15
+ const walk = (n) => {
16
+ nodes.push(n);
17
+ n.childNodes?.forEach(walk);
18
+ };
19
+ walk(parseFragment(html, { sourceCodeLocationInfo: true }));
20
+ return nodes;
21
+ }
22
+ export function attributes(node) {
23
+ return Object.fromEntries(node.attrs?.map((a) => [a.name, a.value]) ?? []);
24
+ }
25
+ export function parsePage(path, source) {
26
+ let body = source;
27
+ let frontmatter = {};
28
+ let frontmatterError;
29
+ const match = /^(?:\uFEFF)?---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(source);
30
+ if (match) {
31
+ // Preserve physical positions for both AST and Contents diagnostics.
32
+ body = match[0].replace(/[^\r\n]/g, ' ') + source.slice(match[0].length);
33
+ try {
34
+ const doc = YAML.parseDocument(match[1]);
35
+ if (doc.errors.length)
36
+ frontmatterError = `Invalid YAML frontmatter: ${doc.errors[0].message}`;
37
+ else {
38
+ const data = doc.toJS();
39
+ if (typeof data !== 'object' || data === null || Array.isArray(data))
40
+ frontmatterError = 'Frontmatter must be a YAML mapping';
41
+ else
42
+ frontmatter = data;
43
+ }
44
+ }
45
+ catch (error) {
46
+ frontmatterError = `Invalid YAML frontmatter: ${error.message}`;
47
+ }
48
+ }
49
+ const processor = unified().use(remarkParse).use(remarkGfm).use(remarkTabs);
50
+ const tree = processor.runSync(processor.parse(body), {
51
+ value: body,
52
+ });
53
+ const nodes = [];
54
+ const walk = (node) => {
55
+ nodes.push(node);
56
+ node.children?.forEach(walk);
57
+ };
58
+ walk(tree);
59
+ const definitions = new Map();
60
+ for (const n of nodes)
61
+ if (n.type === 'definition' &&
62
+ n.identifier &&
63
+ n.url &&
64
+ !definitions.has(n.identifier))
65
+ definitions.set(n.identifier, n.url);
66
+ const anchors = new Set();
67
+ const slugger = new Slugger();
68
+ const links = [];
69
+ for (const n of nodes) {
70
+ const line = n.position?.start.line ?? 1;
71
+ if (n.type === 'heading') {
72
+ const text = nodeText(n);
73
+ const custom = /\s*(?:\[#([^\]]+)\]|\{#([^}]+)\})\s*$/.exec(text);
74
+ anchors.add(custom?.[1] ?? custom?.[2] ?? slugger.slug(text));
75
+ }
76
+ if (['link', 'image', 'linkReference', 'imageReference'].includes(n.type)) {
77
+ const href = n.url ?? definitions.get(n.identifier ?? '');
78
+ if (href !== undefined)
79
+ links.push({
80
+ href,
81
+ line,
82
+ image: n.type.startsWith('image'),
83
+ alt: n.alt,
84
+ });
85
+ }
86
+ if (n.type === 'html')
87
+ for (const element of htmlNodes(n.value ?? '')) {
88
+ const a = attributes(element);
89
+ if (a.id)
90
+ anchors.add(a.id);
91
+ if (element.nodeName === 'a' && a.name)
92
+ anchors.add(a.name);
93
+ if (element.nodeName === 'a' && a.href)
94
+ links.push({
95
+ href: a.href,
96
+ image: false,
97
+ html: true,
98
+ line: line + (element.sourceCodeLocation?.startLine ?? 1) - 1,
99
+ });
100
+ if (element.nodeName === 'img' && a.src)
101
+ links.push({
102
+ href: a.src,
103
+ alt: a.alt,
104
+ image: true,
105
+ html: true,
106
+ describedBy: a['aria-describedby'],
107
+ line: line + (element.sourceCodeLocation?.startLine ?? 1) - 1,
108
+ });
109
+ }
110
+ }
111
+ return {
112
+ path,
113
+ source,
114
+ body,
115
+ frontmatter,
116
+ ...(frontmatterError ? { frontmatterError } : {}),
117
+ nodes,
118
+ anchors,
119
+ links,
120
+ text: nodes
121
+ .filter((n) => n.type === 'text')
122
+ .map((n) => n.value ?? '')
123
+ .join(' '),
124
+ };
125
+ }
@@ -0,0 +1,3 @@
1
+ /** Lazy, real Node DOM for Mermaid syntax parsing only. Never renders SVG. */
2
+ export declare function parseMermaid(source: string): Promise<void>;
3
+ //# sourceMappingURL=mermaid-runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mermaid-runtime.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/check/mermaid-runtime.ts"],"names":[],"mappings":"AA6CA,8EAA8E;AAC9E,wBAAsB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAehE"}
@@ -0,0 +1,57 @@
1
+ let runtime;
2
+ let queue = Promise.resolve();
3
+ async function withDom(dom, action) {
4
+ const keys = ['window', 'document'];
5
+ const saved = keys.map((key) => Object.getOwnPropertyDescriptor(globalThis, key));
6
+ Object.defineProperty(globalThis, 'window', {
7
+ configurable: true,
8
+ value: dom.window,
9
+ });
10
+ Object.defineProperty(globalThis, 'document', {
11
+ configurable: true,
12
+ value: dom.window.document,
13
+ });
14
+ try {
15
+ return await action();
16
+ }
17
+ finally {
18
+ keys.forEach((key, index) => {
19
+ const descriptor = saved[index];
20
+ if (descriptor)
21
+ Object.defineProperty(globalThis, key, descriptor);
22
+ else
23
+ Reflect.deleteProperty(globalThis, key);
24
+ });
25
+ }
26
+ }
27
+ async function loadRuntime() {
28
+ const { JSDOM } = await import('jsdom');
29
+ // Defaults intentionally disable script execution and external resources.
30
+ const dom = new JSDOM('');
31
+ try {
32
+ const mermaid = await withDom(dom, async () => (await import('mermaid')).default);
33
+ process.once('beforeExit', () => dom.window.close());
34
+ return { dom, mermaid };
35
+ }
36
+ catch (error) {
37
+ dom.window.close();
38
+ throw error;
39
+ }
40
+ }
41
+ /** Lazy, real Node DOM for Mermaid syntax parsing only. Never renders SVG. */
42
+ export async function parseMermaid(source) {
43
+ const result = queue.then(async () => {
44
+ runtime ??= loadRuntime();
45
+ const { dom, mermaid } = await runtime;
46
+ await withDom(dom, async () => {
47
+ mermaid.initialize({
48
+ startOnLoad: false,
49
+ securityLevel: 'strict',
50
+ logLevel: 'fatal',
51
+ });
52
+ await mermaid.parse(source);
53
+ });
54
+ });
55
+ queue = result.catch(() => undefined);
56
+ await result;
57
+ }
@@ -0,0 +1,4 @@
1
+ import type { Report } from './check.types.js';
2
+ import { type CheckTarget } from './files.js';
3
+ export declare function checkNavigation(target: CheckTarget, report: Report): Promise<void>;
4
+ //# sourceMappingURL=navigation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/check/navigation.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAW,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AAgCpD,wBAAsB,eAAe,CACnC,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAmGf"}
@@ -0,0 +1,104 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { isDeepStrictEqual } from 'node:util';
4
+ import { buildDocsNavTree } from '../../docs/nav/contents.js';
5
+ import { syncFumadocsNavigation } from '../../docs/nav/fumadocs.js';
6
+ import { syncDocsNavigation } from '../../docs/nav/sync.js';
7
+ import YAML from 'yaml';
8
+ import { present } from './files.js';
9
+ function pageIdentity(item) {
10
+ // Hand-authored collision links can use their own display labels.
11
+ return item.replace(/^\[[^\]]*\]\((.*)\)$/, '[]($1)').replace(/^\.\//, '');
12
+ }
13
+ function navIdentity(value, expected) {
14
+ if (!Array.isArray(value))
15
+ return value;
16
+ return value.map((item, index) => {
17
+ if (typeof item === 'string')
18
+ return item;
19
+ if (typeof item !== 'object' || item === null)
20
+ return item;
21
+ const entry = Object.entries(item)[0];
22
+ if (!entry)
23
+ return item;
24
+ const expectedItem = Array.isArray(expected) ? expected[index] : undefined;
25
+ const children = expectedItem && typeof expectedItem === 'object'
26
+ ? Object.values(expectedItem)[0]
27
+ : undefined;
28
+ // The Contents-derived tree identifies sections by their direct landing path.
29
+ // Actual landing/page labels are presentation; explicit group labels remain meaningful.
30
+ const section = Array.isArray(children) &&
31
+ children.some((child) => typeof child === 'string' && child.endsWith('/index.md'));
32
+ return Array.isArray(entry[1])
33
+ ? {
34
+ [section ? '$section' : entry[0]]: navIdentity(entry[1], children),
35
+ }
36
+ : entry[1];
37
+ });
38
+ }
39
+ export async function checkNavigation(target, report) {
40
+ if (target.framework === 'markdown')
41
+ return;
42
+ try {
43
+ const generated = target.documentation.navOwner === 'nav-sync';
44
+ const nonReaderPaths = target.documentation.nonReaderPaths;
45
+ if (target.framework === 'fumadocs') {
46
+ const result = await syncFumadocsNavigation({
47
+ docsRoot: target.contentRoot,
48
+ check: true,
49
+ nonReaderPaths,
50
+ });
51
+ for (const file of result.metaFiles) {
52
+ let stale = file.changed;
53
+ if (!generated) {
54
+ if (!(await present(join(target.contentRoot, file.path))))
55
+ continue;
56
+ try {
57
+ const actual = JSON.parse(await readFile(join(target.contentRoot, file.path), 'utf8'));
58
+ if (actual &&
59
+ typeof actual === 'object' &&
60
+ !Array.isArray(actual) &&
61
+ !Object.hasOwn(actual, 'pages'))
62
+ continue;
63
+ stale =
64
+ !Array.isArray(actual.pages) ||
65
+ !isDeepStrictEqual(actual.pages.map((p) => typeof p === 'string' ? pageIdentity(p) : p), file.meta.pages.map(pageIdentity));
66
+ }
67
+ catch {
68
+ report('navigation', 'error', file.path, 'Cannot read or parse meta.json; check file permissions and JSON syntax.');
69
+ continue;
70
+ }
71
+ }
72
+ if (stale)
73
+ report('navigation', 'error', file.path, generated
74
+ ? 'Generated navigation is stale; run oat docs nav sync.'
75
+ : 'Navigation membership/order differs from authored Contents; update pages or Contents.');
76
+ }
77
+ for (const path of result.unlisted)
78
+ report('navigation', 'error', path, 'Reader page/folder is unlisted in authored Contents and would be hidden from navigation.');
79
+ }
80
+ else if (generated) {
81
+ const result = await syncDocsNavigation({
82
+ appRoot: target.appRoot,
83
+ check: true,
84
+ nonReaderPaths,
85
+ });
86
+ for (const path of result.stale)
87
+ report('navigation', 'error', path, 'Generated navigation is stale; run oat docs nav sync.');
88
+ }
89
+ else {
90
+ const expected = await buildDocsNavTree({
91
+ docsRoot: target.contentRoot,
92
+ nonReaderPaths,
93
+ });
94
+ const actual = YAML.parse(await readFile(join(target.appRoot, 'mkdocs.yml'), 'utf8'));
95
+ if (!actual || !Object.hasOwn(actual, 'nav'))
96
+ return;
97
+ if (!isDeepStrictEqual(navIdentity(actual.nav, expected), navIdentity(expected, expected)))
98
+ report('navigation', 'error', 'mkdocs.yml', 'Navigation membership/order differs from authored Contents; update nav or Contents.');
99
+ }
100
+ }
101
+ catch (error) {
102
+ report('navigation', 'error', 'index.md', error.message);
103
+ }
104
+ }
@@ -0,0 +1,6 @@
1
+ import type { Report } from './check.types.js';
2
+ import { type CheckTarget } from './files.js';
3
+ import { type ParsedPage } from './markdown.js';
4
+ export declare function checkReaders(target: CheckTarget, pages: ParsedPage[], report: Report): Promise<void>;
5
+ export declare function checkProvenance(target: CheckTarget, pages: ParsedPage[], report: Report): void;
6
+ //# sourceMappingURL=reader-rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reader-rules.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/check/reader-rules.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAML,KAAK,WAAW,EACjB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAY,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAEvD,wBAAsB,YAAY,CAChC,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,UAAU,EAAE,EACnB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAuVf;AAED,wBAAgB,eAAe,CAC7B,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,UAAU,EAAE,EACnB,MAAM,EAAE,MAAM,GACb,IAAI,CA+CN"}
@@ -0,0 +1,214 @@
1
+ import { dirname, extname, join, resolve } from 'node:path';
2
+ import { parseIndexContents, resolveEntryTarget, } from '../../docs/nav/contents.js';
3
+ import { isExcludedPath } from '../../docs/shared/exclusions.js';
4
+ import { DOCS_CHECK_THRESHOLDS } from '../../../config/documentation-check.js';
5
+ import { containedFile, inside, posix, present, readerDirectories, } from './files.js';
6
+ import { nodeText } from './markdown.js';
7
+ export async function checkReaders(target, pages, report) {
8
+ const t = {
9
+ ...DOCS_CHECK_THRESHOLDS,
10
+ ...target.documentation.check?.thresholds,
11
+ };
12
+ const byPath = new Map(pages.map((p) => [p.path, p]));
13
+ const dirs = readerDirectories(pages.map((p) => p.path));
14
+ const separators = new Map();
15
+ for (const dir of dirs) {
16
+ const path = posix(join(dir, 'index.md'));
17
+ const page = byPath.get(path);
18
+ let grouped = false;
19
+ if (!page)
20
+ report('directory-index', 'error', path, 'Reader-bearing directory needs index.md with a nonempty ## Contents map.');
21
+ else {
22
+ try {
23
+ const entries = parseIndexContents(page.body, path, {
24
+ locations: true,
25
+ });
26
+ const retained = [];
27
+ for (const entry of entries) {
28
+ const lexical = resolve(target.contentRoot, dir, entry.href.split('#')[0] ?? '');
29
+ if (inside(target.contentRoot, lexical) &&
30
+ isExcludedPath(target.matcher, posix(lexical.slice(target.contentRoot.length + 1))))
31
+ continue;
32
+ retained.push(entry);
33
+ if (entry.description && /(?:\.\.\.|…)\s*$/.test(entry.description))
34
+ report('contents-description', 'error', path, 'Contents description is truncated; finish the sentence.', entry.line);
35
+ if (entry.separator)
36
+ separators.set(entry.separator, { path, line: entry.line });
37
+ try {
38
+ // Existing strict Contents resolution owns local map semantics.
39
+ await resolveEntryTarget(target.contentRoot, join(target.contentRoot, dir), dir, entry, ['.md', '.mdx'], target.matcher);
40
+ }
41
+ catch (error) {
42
+ report('directory-index', 'error', path, String(error.message), entry.line);
43
+ }
44
+ }
45
+ grouped = retained.some((e) => e.group !== undefined);
46
+ if (dir === '.' && !grouped) {
47
+ if (retained.length > t.rootError)
48
+ report('root-width', 'error', path, `Flat root has ${retained.length} Contents links; maximum ${t.rootError}.`);
49
+ else if (retained.length > t.rootWarn)
50
+ report('root-width', 'warn', path, `Flat root has ${retained.length} Contents links; group above ${t.rootWarn}.`);
51
+ }
52
+ const catalog = page.frontmatter.catalog === true ||
53
+ page.source.includes('<!-- AUTOGENERATED by `oat docs generate-index`.');
54
+ if (!grouped && !catalog) {
55
+ if (retained.length > t.contentsError)
56
+ report('contents-width', 'error', path, `Ungrouped Contents has ${retained.length} links; maximum ${t.contentsError}.`);
57
+ else if (retained.length > t.contentsWarn)
58
+ report('contents-width', 'warn', path, `Ungrouped Contents has ${retained.length} links; group above ${t.contentsWarn}.`);
59
+ }
60
+ const contentsHeading = page.nodes.find((n) => n.type === 'heading' &&
61
+ n.depth === 2 &&
62
+ nodeText(n).trim() === 'Contents');
63
+ const words = page.nodes
64
+ .filter((n) => n.type === 'paragraph' &&
65
+ (n.position?.end.line ?? Infinity) <
66
+ (contentsHeading?.position?.start.line ?? 0))
67
+ .map(nodeText)
68
+ .join(' ')
69
+ .trim()
70
+ .split(/\s+/)
71
+ .filter(Boolean).length;
72
+ if (words < t.overviewWords)
73
+ report('overview', 'warn', path, `Overview has ${words} words; use at least ${t.overviewWords} before Contents.`);
74
+ }
75
+ catch (error) {
76
+ report('directory-index', 'error', path, error.message);
77
+ }
78
+ }
79
+ const count = pages.filter((p) => dirname(p.path) === dir).length;
80
+ const child = dirs.some((d) => d !== dir && (dir === '.' || d.startsWith(`${dir}/`)));
81
+ if (!child) {
82
+ if (count === 1 && page)
83
+ report('leaf-size', 'error', path, 'Index-only leaf has no reader destination; flatten it or add its reader pages.');
84
+ else if (count === 2)
85
+ report('leaf-size', 'warn', path, 'Leaf has only two direct reader pages including index.md; consider flattening.');
86
+ }
87
+ if (!grouped) {
88
+ if (count > t.folderError)
89
+ report('folder-size', 'error', path, `Folder has ${count} direct reader pages including index; maximum ${t.folderError} without groups.`);
90
+ else if (count > t.folderWarn)
91
+ report('folder-size', 'warn', path, `Folder has ${count} direct reader pages including index; group above ${t.folderWarn}.`);
92
+ }
93
+ const depth = dir === '.' ? 0 : dir.split('/').length;
94
+ if (depth >= t.depthError)
95
+ report('depth', 'error', path, `Directory depth ${depth}; error at ${t.depthError} (content root is 0).`);
96
+ else if (depth >= t.depthWarn)
97
+ report('depth', 'warn', path, `Directory depth ${depth}; warning at ${t.depthWarn} (content root is 0).`);
98
+ }
99
+ if (separators.size > 1)
100
+ for (const { path, line } of separators.values())
101
+ report('contents-separator', 'warn', path, `Contents separators differ within this tree (${[...separators.keys()].sort().join(', ')}).`, line);
102
+ for (const p of pages) {
103
+ if (!p.frontmatterError) {
104
+ const desc = p.frontmatter.description;
105
+ if (typeof desc !== 'string' || !desc.trim())
106
+ report('description', 'error', p.path, 'Frontmatter description is required and must be nonempty.', 1);
107
+ else if (desc.trim().length < t.descriptionMin ||
108
+ desc.trim().length > t.descriptionMax)
109
+ report('description', 'warn', p.path, `Description has ${desc.trim().length} characters; expected ${t.descriptionMin}–${t.descriptionMax}.`, 1);
110
+ }
111
+ const h1 = p.nodes.filter((n) => n.type === 'heading' && n.depth === 1);
112
+ if (h1.length !== 1)
113
+ report('h1', 'error', p.path, `Expected one real H1; found ${h1.length}.`, h1[1]?.position?.start.line ?? 1);
114
+ const reference = p.frontmatter.type === 'reference' ||
115
+ /(?:^|\/)references?(?:\/|$)/.test(p.path);
116
+ const lines = p.source.split(/\r?\n/).length - (p.source.endsWith('\n') ? 1 : 0);
117
+ const limit = reference ? t.referenceLines : t.pageLines;
118
+ if (lines > limit)
119
+ report('page-length', 'warn', p.path, `Page has ${lines} physical source lines; limit ${limit}${reference ? ' for reference pages' : ''}.`);
120
+ const h3 = p.nodes.filter((n) => n.type === 'heading' && n.depth === 3).length;
121
+ if (reference && h3 > t.referenceH3)
122
+ report('page-length', 'warn', p.path, `Reference page has ${h3} real H3 headings; limit ${t.referenceH3}.`);
123
+ for (const link of p.links) {
124
+ const href = link.href;
125
+ if (/^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(href))
126
+ continue;
127
+ let pathname;
128
+ let anchor;
129
+ try {
130
+ const hash = href.indexOf('#');
131
+ pathname = decodeURIComponent((hash < 0 ? href : href.slice(0, hash)).split('?')[0]);
132
+ anchor = hash < 0 ? '' : decodeURIComponent(href.slice(hash + 1));
133
+ }
134
+ catch {
135
+ report('links', 'error', p.path, `Malformed percent encoding in link "${href}".`, link.line);
136
+ continue;
137
+ }
138
+ if (!pathname) {
139
+ if (anchor && !p.anchors.has(anchor))
140
+ report('links', 'error', p.path, `Unresolved anchor "${href}".`, link.line);
141
+ continue;
142
+ }
143
+ if (pathname.startsWith('~/'))
144
+ continue; // provenance reports it, never read home paths
145
+ const file = pathname.startsWith('/')
146
+ ? resolve(target.contentRoot, `.${pathname}`)
147
+ : resolve(target.contentRoot, dirname(p.path), pathname);
148
+ if (!inside(target.contentRoot, file))
149
+ continue; // provenance warns without outside IO
150
+ const relativePath = posix(file.slice(target.contentRoot.length + 1));
151
+ if (isExcludedPath(target.matcher, relativePath))
152
+ continue;
153
+ let resolved = file;
154
+ let isFile = await containedFile(target.contentRoot, file);
155
+ if (!isFile && !extname(pathname)) {
156
+ for (const suffix of ['.md', '.mdx', '/index.md'])
157
+ if (await containedFile(target.contentRoot, file + suffix)) {
158
+ resolved = file + suffix;
159
+ isFile = true;
160
+ break;
161
+ }
162
+ }
163
+ // Framework public assets have a separate explicit allowed root. Rendered
164
+ // absolute routes are validated only when they identify a reader file.
165
+ if (!isFile && pathname.startsWith('/') && link.image) {
166
+ const publicRoot = join(target.appRoot, 'public');
167
+ isFile =
168
+ (await present(publicRoot)) &&
169
+ (await containedFile(publicRoot, resolve(publicRoot, `.${pathname}`)));
170
+ }
171
+ if (!isFile &&
172
+ pathname.startsWith('/') &&
173
+ !link.image &&
174
+ !/\.mdx?$/.test(pathname))
175
+ continue;
176
+ if (!isFile)
177
+ report('links', 'error', p.path, `Unresolved in-root link "${href}".`, link.line);
178
+ else if (anchor && /\.mdx?$/.test(resolved)) {
179
+ const destination = byPath.get(posix(resolved.slice(target.contentRoot.length + 1)));
180
+ if (destination && !destination.anchors.has(anchor))
181
+ report('links', 'error', p.path, `Unresolved anchor "${href}".`, link.line);
182
+ }
183
+ }
184
+ }
185
+ }
186
+ export function checkProvenance(target, pages, report) {
187
+ const patterns = [
188
+ /\.oat\//u,
189
+ /\b[Bb][Ll]-[\w-]+/u,
190
+ /(?:~\/|\/Users\/|\/home\/)/u,
191
+ ...(target.documentation.check?.provenance?.ticketPatterns ?? []).map((p) => new RegExp(p, 'u')),
192
+ ...(target.documentation.check?.provenance?.milestonePatterns ?? []).map((p) => new RegExp(p, 'u')),
193
+ ];
194
+ for (const p of pages) {
195
+ p.source.split(/\r?\n/).forEach((text, line) => {
196
+ if (patterns.some((pattern) => pattern.test(text)))
197
+ report('provenance', 'warn', p.path, 'Reader page contains a planning/backlog/home path or configured ticket/milestone pattern; remove it or record a reason-bearing decision-pointer exception.', line + 1);
198
+ });
199
+ for (const link of p.links) {
200
+ if (/^(?:[a-z][a-z\d+.-]*:|\/)/i.test(link.href))
201
+ continue;
202
+ const href = link.href.split(/[?#]/)[0];
203
+ let decoded;
204
+ try {
205
+ decoded = decodeURIComponent(href);
206
+ }
207
+ catch {
208
+ continue;
209
+ }
210
+ if (!inside(target.contentRoot, resolve(target.contentRoot, dirname(p.path), decoded)))
211
+ report('provenance', 'warn', p.path, `Link "${link.href}" escapes the docs root; target was not read.`, link.line);
212
+ }
213
+ }
214
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/docs/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASpC,wBAAgB,iBAAiB,IAAI,OAAO,CAS3C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/docs/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAUpC,wBAAgB,iBAAiB,IAAI,OAAO,CAU3C"}
@@ -1,6 +1,7 @@
1
1
  import { Command } from 'commander';
2
2
  import { createDocsAnalyzeCommand } from './analyze.js';
3
3
  import { createDocsApplyCommand } from './apply.js';
4
+ import { createDocsCheckCommand } from './check/check.js';
4
5
  import { createDocsGenerateIndexCommand } from './index-generate/index.js';
5
6
  import { createDocsInitCommand } from './init/index.js';
6
7
  import { createDocsMigrateCommand } from './migrate/index.js';
@@ -8,6 +9,7 @@ import { createDocsNavCommand } from './nav/index.js';
8
9
  export function createDocsCommand() {
9
10
  return new Command('docs')
10
11
  .description('OAT documentation bootstrap and maintenance commands')
12
+ .addCommand(createDocsCheckCommand())
11
13
  .addCommand(createDocsAnalyzeCommand())
12
14
  .addCommand(createDocsApplyCommand())
13
15
  .addCommand(createDocsGenerateIndexCommand())
@@ -4,5 +4,6 @@ export interface DocsCommands {
4
4
  dev: string;
5
5
  build: string;
6
6
  }
7
+ export declare function shellQuote(value: string): string;
7
8
  export declare function buildDocsCommands(repoShape: DocsRepoShape, targetDir: string, appName: string): DocsCommands;
8
9
  //# sourceMappingURL=docs-commands.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"docs-commands.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/init/docs-commands.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAMD,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,aAAa,EACxB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GACd,YAAY,CAsBd"}
1
+ {"version":3,"file":"docs-commands.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/init/docs-commands.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIhD;AAMD,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,aAAa,EACxB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GACd,YAAY,CAsBd"}
@@ -1,4 +1,9 @@
1
1
  import { normalize } from 'node:path';
2
+ export function shellQuote(value) {
3
+ return /^[a-zA-Z0-9_./-]+$/.test(value)
4
+ ? value
5
+ : `'${value.replaceAll("'", "'\\''")}'`;
6
+ }
2
7
  function isRootTarget(targetDir) {
3
8
  return targetDir.trim() === '' || normalize(targetDir) === '.';
4
9
  }
@@ -18,8 +23,8 @@ export function buildDocsCommands(repoShape, targetDir, appName) {
18
23
  };
19
24
  }
20
25
  return {
21
- install: `cd ${targetDir} && pnpm install`,
22
- dev: `cd ${targetDir} && pnpm dev`,
23
- build: `cd ${targetDir} && pnpm build`,
26
+ install: `cd ${shellQuote(targetDir)} && pnpm install`,
27
+ dev: `cd ${shellQuote(targetDir)} && pnpm dev`,
28
+ build: `cd ${shellQuote(targetDir)} && pnpm build`,
24
29
  };
25
30
  }
@@ -9,6 +9,7 @@ interface DocsInitExecutionResult {
9
9
  createdFiles: string[];
10
10
  appRoot: string;
11
11
  rootPackagePatch?: RootPackagePatchResult;
12
+ installationAdvice?: string[];
12
13
  dryRun?: boolean;
13
14
  plannedFiles?: string[];
14
15
  preservedFiles?: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/init/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,KAAK,mBAAmB,EAGxB,sBAAsB,EAEvB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAGL,KAAK,aAAa,EAClB,KAAK,YAAY,EAElB,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EACL,KAAK,SAAS,EAGf,MAAM,oBAAoB,CAAC;AAG5B,OAAO,EAAE,OAAO,EAAU,MAAM,WAAW,CAAC;AAU5C,OAAO,EAIL,KAAK,uBAAuB,EAK7B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAEL,KAAK,sBAAsB,EAC5B,MAAM,gBAAgB,CAAC;AAgBxB,UAAU,uBAAuB;IAC/B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,sBAAsB,CAAC;IAC1C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,OAAO,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC;IAC/C,YAAY,CAAC,EACT,SAAS,GACT,SAAS,GACT,WAAW,GACX,QAAQ,GACR,eAAe,CAAC;IACpB,OAAO,CAAC,EAAE;QAAE,KAAK,EAAE,UAAU,GAAG,QAAQ,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7D;AAED,UAAU,oBAAoB;IAC5B,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,iBAAiB,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACzC,eAAe,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,GAAG,gBAAgB,CAAC,CAAC;IAC9E,gBAAgB,EAAE,CAChB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,GAAG,EAAE,aAAa,KACf,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC5B,eAAe,EAAE,CAAC,CAAC,SAAS,MAAM,EAChC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,EAC1B,GAAG,EAAE,aAAa,KACf,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IACvB,WAAW,EAAE,CACX,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE,uBAAuB,EAChC,UAAU,EAAE,MAAM,KACf,OAAO,CAAC,uBAAuB,GAAG,IAAI,CAAC,CAAC;IAC7C,qBAAqB,EAAE,CACrB,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,KACT,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAClC,sBAAsB,EAAE,OAAO,sBAAsB,CAAC;IACtD,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;IACxD,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,aAAa,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1E;AA2MD;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,uBAAuB,GAAG,MAAM,CAoC7E;AA0QD,wBAAgB,qBAAqB,CACnC,SAAS,GAAE,OAAO,CAAC,oBAAoB,CAAM,GAC5C,OAAO,CAiET"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/init/index.ts"],"names":[],"mappings":"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"}
@@ -176,11 +176,16 @@ const DEFAULT_DEPENDENCIES = {
176
176
  context.logger.info(` Format: ${options.format}`);
177
177
  if (rootPackagePatch)
178
178
  logRootPackagePatch(context, rootPackagePatch);
179
+ for (const advice of result.installationAdvice ?? [])
180
+ context.logger.info(advice);
179
181
  }
180
182
  return {
181
183
  createdFiles: result.createdFiles,
182
184
  appRoot: result.appRoot,
183
185
  rootPackagePatch,
186
+ ...(result.installationAdvice
187
+ ? { installationAdvice: result.installationAdvice }
188
+ : {}),
184
189
  };
185
190
  },
186
191
  upsertAgentsMdSection,
@@ -384,6 +389,9 @@ async function runDocsInitCommand(context, options, dependencies) {
384
389
  framework: resolved.framework,
385
390
  },
386
391
  guidance: sectionResult,
392
+ ...(scaffold?.installationAdvice
393
+ ? { installationAdvice: scaffold.installationAdvice }
394
+ : {}),
387
395
  });
388
396
  }
389
397
  else {
@@ -7,6 +7,7 @@ export interface ScaffoldDocsAppResult {
7
7
  appRoot: string;
8
8
  createdFiles: string[];
9
9
  documentationConfig: OatDocumentationConfig;
10
+ installationAdvice?: string[];
10
11
  }
11
12
  export interface OatDepContext {
12
13
  isOatRepo: boolean;