@open-agent-toolkit/cli 0.3.32 → 0.3.34
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/assets/bundle-metadata.json +1 -1
- package/assets/docs/contributing/documentation.md +1 -0
- package/assets/docs/docs-tooling/check.md +35 -11
- package/assets/docs/docs-tooling/commands.md +20 -2
- package/assets/docs/reference/cli-reference.md +2 -2
- package/assets/docs/reference/config-and-local-state.md +1 -1
- package/assets/docs/reference/configuration.md +1 -1
- package/assets/docs/reference/docs-index-contract.md +3 -2
- package/assets/docs/reference/oat-directory-structure.md +1 -1
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/oat-docs-analyze/SKILL.md +35 -11
- package/assets/skills/oat-docs-analyze/references/analysis-artifact-template.md +2 -2
- package/assets/skills/oat-docs-analyze/references/quality-checklist.md +3 -1
- package/assets/skills/oat-docs-analyze/scripts/package-drift.mjs +303 -30
- package/assets/skills/oat-docs-apply/SKILL.md +3 -1
- package/assets/skills/oat-docs-authoring/SKILL.md +1 -1
- package/assets/skills/oat-docs-authoring/references/oat-fumadocs-contract.md +6 -1
- package/dist/commands/config/index.d.ts.map +1 -1
- package/dist/commands/config/index.js +42 -9
- package/dist/commands/docs/check/check.d.ts.map +1 -1
- package/dist/commands/docs/check/check.js +1 -0
- package/dist/commands/docs/check/checker.d.ts +2 -0
- package/dist/commands/docs/check/checker.d.ts.map +1 -1
- package/dist/commands/docs/check/checker.js +6 -1
- package/dist/commands/docs/check/commands.d.ts.map +1 -1
- package/dist/commands/docs/check/commands.js +74 -30
- package/dist/commands/docs/check/files.d.ts +2 -0
- package/dist/commands/docs/check/files.d.ts.map +1 -1
- package/dist/commands/docs/check/files.js +16 -0
- package/dist/commands/docs/check/navigation.d.ts +11 -1
- package/dist/commands/docs/check/navigation.d.ts.map +1 -1
- package/dist/commands/docs/check/navigation.js +40 -69
- package/dist/commands/docs/check/reader-rules.d.ts.map +1 -1
- package/dist/commands/docs/check/reader-rules.js +12 -9
- package/dist/commands/docs/nav/contents.d.ts +50 -2
- package/dist/commands/docs/nav/contents.d.ts.map +1 -1
- package/dist/commands/docs/nav/contents.js +137 -14
- package/dist/commands/docs/nav/fumadocs.d.ts +29 -0
- package/dist/commands/docs/nav/fumadocs.d.ts.map +1 -1
- package/dist/commands/docs/nav/fumadocs.js +16 -41
- package/dist/commands/docs/nav/membership.d.ts +37 -0
- package/dist/commands/docs/nav/membership.d.ts.map +1 -0
- package/dist/commands/docs/nav/membership.js +474 -0
- package/dist/commands/docs/nav/sync.d.ts +26 -2
- package/dist/commands/docs/nav/sync.d.ts.map +1 -1
- package/dist/commands/docs/nav/sync.js +160 -43
- package/package.json +3 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
|
-
import { join, resolve } from 'node:path';
|
|
1
|
+
import { readFile, realpath, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { join, relative, resolve } from 'node:path';
|
|
3
3
|
import { buildCommandContext, } from '../../../app/command-context.js';
|
|
4
4
|
import { readGlobalOptions } from '../../shared/shared.utils.js';
|
|
5
5
|
import { readOatConfig } from '../../../config/oat-config.js';
|
|
@@ -9,6 +9,7 @@ import { Command, Option } from 'commander';
|
|
|
9
9
|
import YAML from 'yaml';
|
|
10
10
|
import { buildDocsNavTree } from './contents.js';
|
|
11
11
|
import { syncFumadocsNavigation } from './fumadocs.js';
|
|
12
|
+
import { compareFumadocsMembership, compareMkDocsMembership, } from './membership.js';
|
|
12
13
|
const MKDOCS_MARKERS = ['mkdocs.yml'];
|
|
13
14
|
const FUMADOCS_MARKERS = [
|
|
14
15
|
'source.config.ts',
|
|
@@ -44,39 +45,64 @@ export async function detectDocsNavFramework(appRoot, configuredTooling) {
|
|
|
44
45
|
throw new Error(`Could not detect the docs framework in ${appRoot}: expected mkdocs.yml (MkDocs) ` +
|
|
45
46
|
'or source.config.ts (Fumadocs).');
|
|
46
47
|
}
|
|
47
|
-
async function
|
|
48
|
-
try {
|
|
49
|
-
const repoRoot = await resolveProjectRoot(cwd);
|
|
50
|
-
const config = await readOatConfig(repoRoot);
|
|
51
|
-
const root = config.documentation?.root?.trim();
|
|
52
|
-
if (!root || resolve(repoRoot, root) !== appRoot) {
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
return config.documentation?.tooling?.trim() || null;
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
async function resolveNonReaderPaths(cwd, appRoot) {
|
|
48
|
+
async function resolveConfiguredDocumentation(cwd) {
|
|
62
49
|
let repoRoot;
|
|
63
50
|
try {
|
|
64
51
|
repoRoot = await resolveProjectRoot(cwd);
|
|
65
52
|
}
|
|
66
53
|
catch {
|
|
67
|
-
return
|
|
54
|
+
return null;
|
|
68
55
|
} // Explicit app targets also work outside a configured repository.
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
56
|
+
const documentation = (await readOatConfig(repoRoot)).documentation;
|
|
57
|
+
const configuredRoot = documentation?.root?.trim();
|
|
58
|
+
if (!configuredRoot)
|
|
59
|
+
return null;
|
|
60
|
+
return {
|
|
61
|
+
root: resolve(repoRoot, configuredRoot),
|
|
62
|
+
configuredRoot,
|
|
63
|
+
tooling: documentation?.tooling?.trim() || null,
|
|
64
|
+
navOwner: documentation?.navOwner,
|
|
65
|
+
nonReaderPaths: documentation?.nonReaderPaths ?? [],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Read-only comparison for `navOwner: hand-authored`: the same
|
|
70
|
+
* membership-and-order semantics as the `oat docs check` navigation rule,
|
|
71
|
+
* never strict generator output.
|
|
72
|
+
*/
|
|
73
|
+
export async function checkHandAuthoredNavigation(options) {
|
|
74
|
+
const docsRoot = join(options.appRoot, 'docs');
|
|
75
|
+
const framework = await detectDocsNavFramework(options.appRoot, options.configuredTooling);
|
|
76
|
+
if (framework === 'mkdocs')
|
|
77
|
+
return {
|
|
78
|
+
framework,
|
|
79
|
+
appRoot: options.appRoot,
|
|
80
|
+
docsRoot,
|
|
81
|
+
issues: await compareMkDocsMembership({
|
|
82
|
+
appRoot: options.appRoot,
|
|
83
|
+
docsRoot,
|
|
84
|
+
nonReaderPaths: options.nonReaderPaths,
|
|
85
|
+
}),
|
|
86
|
+
unlisted: [],
|
|
87
|
+
};
|
|
88
|
+
const generated = await syncFumadocsNavigation({
|
|
89
|
+
docsRoot,
|
|
90
|
+
check: true,
|
|
91
|
+
nonReaderPaths: options.nonReaderPaths,
|
|
92
|
+
});
|
|
93
|
+
return {
|
|
94
|
+
framework,
|
|
95
|
+
appRoot: options.appRoot,
|
|
96
|
+
docsRoot,
|
|
97
|
+
issues: await compareFumadocsMembership(docsRoot, generated.metaFiles),
|
|
98
|
+
unlisted: generated.unlisted,
|
|
99
|
+
};
|
|
74
100
|
}
|
|
75
101
|
const DEFAULT_DEPENDENCIES = {
|
|
76
102
|
buildCommandContext,
|
|
77
|
-
|
|
78
|
-
resolveNonReaderPaths,
|
|
103
|
+
resolveConfiguredDocumentation,
|
|
79
104
|
syncDocsNavigation,
|
|
105
|
+
checkHandAuthoredNavigation,
|
|
80
106
|
};
|
|
81
107
|
function replaceTopLevelYamlSection(source, key, replacement) {
|
|
82
108
|
const lines = source.split(/\r?\n/);
|
|
@@ -135,7 +161,7 @@ export async function syncDocsNavigation(options) {
|
|
|
135
161
|
stale: nextMkdocsSource === mkdocsSource ? [] : ['mkdocs.yml'],
|
|
136
162
|
};
|
|
137
163
|
}
|
|
138
|
-
function reportFumadocsResult(context, targetDir, result) {
|
|
164
|
+
function reportFumadocsResult(context, targetDir, result, warnings) {
|
|
139
165
|
if (context.json) {
|
|
140
166
|
context.logger.json({
|
|
141
167
|
status: 'ok',
|
|
@@ -145,6 +171,7 @@ function reportFumadocsResult(context, targetDir, result) {
|
|
|
145
171
|
metaFiles: result.metaFiles,
|
|
146
172
|
written: result.written,
|
|
147
173
|
unlisted: result.unlisted,
|
|
174
|
+
...(warnings.length ? { warnings } : {}),
|
|
148
175
|
});
|
|
149
176
|
return;
|
|
150
177
|
}
|
|
@@ -167,15 +194,18 @@ function reportFumadocsResult(context, targetDir, result) {
|
|
|
167
194
|
context.logger.warn(` ${path}`);
|
|
168
195
|
}
|
|
169
196
|
}
|
|
197
|
+
for (const warning of warnings)
|
|
198
|
+
context.logger.warn(warning);
|
|
170
199
|
}
|
|
171
200
|
/**
|
|
172
201
|
* `--check`: report drift without writing. Fails (exit 1) when any generated
|
|
173
|
-
* navigation file would change or, for Fumadocs, any page or
|
|
174
|
-
* unlisted, since strict navigation hides it from the sidebar.
|
|
202
|
+
* navigation file is missing or would change or, for Fumadocs, any page or
|
|
203
|
+
* folder is unlisted, since strict navigation hides it from the sidebar.
|
|
175
204
|
*/
|
|
176
|
-
function reportCheckResult(context, targetDir, result) {
|
|
205
|
+
function reportCheckResult(context, targetDir, result, missing, warnings) {
|
|
177
206
|
const unlisted = result.framework === 'fumadocs' ? result.unlisted : [];
|
|
178
207
|
const current = result.stale.length === 0 && unlisted.length === 0;
|
|
208
|
+
const differing = result.stale.filter((path) => !missing.includes(path));
|
|
179
209
|
if (context.json) {
|
|
180
210
|
context.logger.json({
|
|
181
211
|
status: current ? 'ok' : 'drift',
|
|
@@ -184,20 +214,28 @@ function reportCheckResult(context, targetDir, result) {
|
|
|
184
214
|
appRoot: result.appRoot,
|
|
185
215
|
docsRoot: result.docsRoot,
|
|
186
216
|
stale: result.stale,
|
|
217
|
+
missing,
|
|
187
218
|
unlisted,
|
|
219
|
+
...(warnings.length ? { warnings } : {}),
|
|
188
220
|
});
|
|
189
221
|
return current;
|
|
190
222
|
}
|
|
223
|
+
for (const warning of warnings)
|
|
224
|
+
context.logger.warn(warning);
|
|
191
225
|
if (current) {
|
|
192
226
|
context.logger.info(`Docs navigation is up to date in ${targetDir}`);
|
|
193
227
|
return current;
|
|
194
228
|
}
|
|
195
229
|
context.logger.error(`Docs navigation is out of date in ${targetDir}`);
|
|
196
|
-
if (
|
|
197
|
-
context.logger.error(` ${
|
|
198
|
-
for (const path of
|
|
230
|
+
if (missing.length > 0) {
|
|
231
|
+
context.logger.error(` ${missing.length} generated file(s) are missing:`);
|
|
232
|
+
for (const path of missing)
|
|
233
|
+
context.logger.error(` ${path}`);
|
|
234
|
+
}
|
|
235
|
+
if (differing.length > 0) {
|
|
236
|
+
context.logger.error(` ${differing.length} generated file(s) differ from the index.md Contents maps:`);
|
|
237
|
+
for (const path of differing)
|
|
199
238
|
context.logger.error(` ${path}`);
|
|
200
|
-
}
|
|
201
239
|
}
|
|
202
240
|
if (unlisted.length > 0) {
|
|
203
241
|
context.logger.error(` ${unlisted.length} path(s) are not listed in any index.md Contents map ` +
|
|
@@ -205,26 +243,102 @@ function reportCheckResult(context, targetDir, result) {
|
|
|
205
243
|
for (const path of unlisted) {
|
|
206
244
|
context.logger.error(` ${path}`);
|
|
207
245
|
}
|
|
246
|
+
context.logger.error(` List every page in its directory's ## Contents, then run \`oat docs nav sync --target-dir ${targetDir}\`.`);
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
context.logger.error(` Run \`oat docs nav sync --target-dir ${targetDir}\` to regenerate the navigation.`);
|
|
250
|
+
}
|
|
251
|
+
return current;
|
|
252
|
+
}
|
|
253
|
+
function reportHandAuthoredCheck(context, targetDir, result) {
|
|
254
|
+
const current = result.issues.length === 0 && result.unlisted.length === 0;
|
|
255
|
+
if (context.json) {
|
|
256
|
+
context.logger.json({
|
|
257
|
+
status: current ? 'ok' : 'drift',
|
|
258
|
+
check: true,
|
|
259
|
+
navOwner: 'hand-authored',
|
|
260
|
+
framework: result.framework,
|
|
261
|
+
appRoot: result.appRoot,
|
|
262
|
+
docsRoot: result.docsRoot,
|
|
263
|
+
stale: [...new Set(result.issues.map((issue) => issue.path))].sort(),
|
|
264
|
+
missing: [],
|
|
265
|
+
unlisted: result.unlisted,
|
|
266
|
+
issues: result.issues,
|
|
267
|
+
});
|
|
268
|
+
return current;
|
|
269
|
+
}
|
|
270
|
+
if (current) {
|
|
271
|
+
context.logger.info(`Hand-authored docs navigation matches the index.md Contents maps in ${targetDir} (membership and order).`);
|
|
272
|
+
return current;
|
|
208
273
|
}
|
|
209
|
-
context.logger.error(`
|
|
274
|
+
context.logger.error(`Hand-authored docs navigation differs from the index.md Contents maps in ${targetDir}:`);
|
|
275
|
+
for (const issue of result.issues)
|
|
276
|
+
context.logger.error(` ${issue.path}${issue.line === undefined ? '' : `:${issue.line}`} ${issue.message}`);
|
|
277
|
+
if (result.unlisted.length > 0) {
|
|
278
|
+
context.logger.error(` ${result.unlisted.length} path(s) are not listed in any index.md Contents map:`);
|
|
279
|
+
for (const path of result.unlisted)
|
|
280
|
+
context.logger.error(` ${path}`);
|
|
281
|
+
}
|
|
282
|
+
context.logger.error(' Edit the hand-authored navigation or the Contents maps; nav sync does not regenerate hand-authored navigation.');
|
|
210
283
|
return current;
|
|
211
284
|
}
|
|
285
|
+
const MARKDOWN_NOT_APPLICABLE = 'oat docs nav sync is not applicable to Markdown docs (documentation.tooling: markdown): the index.md Contents maps are the navigation and nothing is generated. Use oat docs check to validate them.';
|
|
212
286
|
async function runDocsNavSyncCommand(context, options, dependencies) {
|
|
213
287
|
try {
|
|
214
|
-
const
|
|
288
|
+
const configured = await dependencies.resolveConfiguredDocumentation(context.cwd);
|
|
289
|
+
// Like `oat docs check`, an omitted target is the configured root.
|
|
290
|
+
const targetDir = options.targetDir ??
|
|
291
|
+
(configured ? relative(context.cwd, configured.root) || '.' : '.');
|
|
215
292
|
const appRoot = resolve(context.cwd, targetDir);
|
|
216
|
-
|
|
293
|
+
// Compare filesystem identity so a symlinked alias of the configured
|
|
294
|
+
// app keeps its ownership and exclusions.
|
|
295
|
+
const canonical = async (path) => realpath(path).catch(() => resolve(path));
|
|
296
|
+
const documentation = configured &&
|
|
297
|
+
(await canonical(configured.root)) === (await canonical(appRoot))
|
|
298
|
+
? configured
|
|
299
|
+
: null;
|
|
300
|
+
if (documentation?.tooling?.toLowerCase() === 'markdown')
|
|
301
|
+
throw new Error(MARKDOWN_NOT_APPLICABLE);
|
|
302
|
+
const navOwner = documentation?.navOwner;
|
|
303
|
+
const syncOptions = {
|
|
217
304
|
appRoot,
|
|
218
|
-
configuredTooling:
|
|
305
|
+
configuredTooling: documentation?.tooling ?? null,
|
|
219
306
|
check: options.check === true,
|
|
220
|
-
nonReaderPaths:
|
|
221
|
-
}
|
|
307
|
+
nonReaderPaths: documentation?.nonReaderPaths ?? [],
|
|
308
|
+
};
|
|
309
|
+
if (navOwner === 'hand-authored') {
|
|
310
|
+
if (!options.check)
|
|
311
|
+
throw new Error(`documentation.navOwner is hand-authored, so oat docs nav sync will not overwrite the navigation in ${targetDir}. ` +
|
|
312
|
+
'Edit meta.json (Fumadocs) or the mkdocs.yml nav (MkDocs) directly and run oat docs check, ' +
|
|
313
|
+
'or compare it with `oat docs nav sync --check`. To hand navigation to nav sync, first run ' +
|
|
314
|
+
'`oat config set documentation.navOwner nav-sync`.');
|
|
315
|
+
const result = await dependencies.checkHandAuthoredNavigation(syncOptions);
|
|
316
|
+
process.exitCode = reportHandAuthoredCheck(context, targetDir, result)
|
|
317
|
+
? 0
|
|
318
|
+
: 1;
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
const warnings = navOwner === undefined
|
|
322
|
+
? [
|
|
323
|
+
options.check
|
|
324
|
+
? `No documentation.navOwner is declared for ${targetDir}, so this check compares with strict nav sync output. Declare ownership with \`oat config set documentation.navOwner nav-sync\`, or \`hand-authored\` for a membership-and-order comparison.`
|
|
325
|
+
: `No documentation.navOwner is declared for ${targetDir}, so nav sync regenerated the navigation as its owner. Declare ownership with \`oat config set documentation.navOwner nav-sync\`, or \`hand-authored\` to protect hand-written navigation from regeneration.`,
|
|
326
|
+
]
|
|
327
|
+
: [];
|
|
328
|
+
const result = await dependencies.syncDocsNavigation(syncOptions);
|
|
222
329
|
if (options.check) {
|
|
223
|
-
|
|
330
|
+
const missing = [];
|
|
331
|
+
if (result.framework === 'fumadocs')
|
|
332
|
+
for (const path of result.stale)
|
|
333
|
+
if (!(await fileExists(join(result.docsRoot, path))))
|
|
334
|
+
missing.push(path);
|
|
335
|
+
process.exitCode = reportCheckResult(context, targetDir, result, missing, warnings)
|
|
336
|
+
? 0
|
|
337
|
+
: 1;
|
|
224
338
|
return;
|
|
225
339
|
}
|
|
226
340
|
if (result.framework === 'fumadocs') {
|
|
227
|
-
reportFumadocsResult(context, targetDir, result);
|
|
341
|
+
reportFumadocsResult(context, targetDir, result, warnings);
|
|
228
342
|
}
|
|
229
343
|
else if (context.json) {
|
|
230
344
|
context.logger.json({
|
|
@@ -233,12 +347,15 @@ async function runDocsNavSyncCommand(context, options, dependencies) {
|
|
|
233
347
|
docsRoot: result.docsRoot,
|
|
234
348
|
mkdocsPath: result.mkdocsPath,
|
|
235
349
|
nav: result.nav,
|
|
350
|
+
...(warnings.length ? { warnings } : {}),
|
|
236
351
|
});
|
|
237
352
|
}
|
|
238
353
|
else {
|
|
239
354
|
context.logger.info(`Synced docs navigation in ${targetDir}`);
|
|
240
355
|
context.logger.info(` MkDocs config: ${result.mkdocsPath}`);
|
|
241
356
|
context.logger.info(` Docs root: ${result.docsRoot}`);
|
|
357
|
+
for (const warning of warnings)
|
|
358
|
+
context.logger.warn(warning);
|
|
242
359
|
}
|
|
243
360
|
process.exitCode = 0;
|
|
244
361
|
}
|
|
@@ -260,8 +377,8 @@ export function createDocsNavSyncCommand(overrides = {}) {
|
|
|
260
377
|
};
|
|
261
378
|
return new Command('sync')
|
|
262
379
|
.description('Regenerate docs navigation from index.md contents: mkdocs.yml nav (MkDocs) or strict meta.json files (Fumadocs)')
|
|
263
|
-
.addOption(new Option('--target-dir <path>', 'Docs app directory containing mkdocs.yml or source.config.ts'))
|
|
264
|
-
.addOption(new Option('--check', 'Write nothing; exit 1 if any navigation file is stale or any page is unlisted'))
|
|
380
|
+
.addOption(new Option('--target-dir <path>', 'Docs app directory containing mkdocs.yml or source.config.ts (default: documentation.root, else the current directory)'))
|
|
381
|
+
.addOption(new Option('--check', 'Write nothing; exit 1 if any navigation file is missing or stale or any page is unlisted (hand-authored navigation: membership and order only)'))
|
|
265
382
|
.action(async (options, command) => {
|
|
266
383
|
const context = dependencies.buildCommandContext(readGlobalOptions(command));
|
|
267
384
|
await runDocsNavSyncCommand(context, options, dependencies);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-agent-toolkit/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.34",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Open Agent Toolkit CLI",
|
|
6
6
|
"homepage": "https://github.com/voxmedia/open-agent-toolkit/tree/main/packages/cli",
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"unified": "11.0.5",
|
|
43
43
|
"yaml": "2.8.2",
|
|
44
44
|
"zod": "^3.25.76",
|
|
45
|
-
"@open-agent-toolkit/
|
|
46
|
-
"@open-agent-toolkit/
|
|
45
|
+
"@open-agent-toolkit/control-plane": "0.3.34",
|
|
46
|
+
"@open-agent-toolkit/docs-transforms": "0.3.34"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@types/jsdom": "28.0.3",
|