@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,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Read-only, offline comparison used by oat-docs-analyze.
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
// Read-only, offline comparison used by oat-docs-analyze. It reads installed
|
|
3
|
+
// package manifests the way Node resolves packages (the app's node_modules,
|
|
4
|
+
// then each ancestor's) and never installs, fetches or executes anything.
|
|
5
|
+
import { access, open, readFile, realpath } from 'node:fs/promises';
|
|
6
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
5
7
|
import { parseArgs } from 'node:util';
|
|
6
8
|
|
|
7
9
|
const shellQuote = (value) =>
|
|
@@ -75,6 +77,248 @@ async function versionsForCli(executable) {
|
|
|
75
77
|
);
|
|
76
78
|
}
|
|
77
79
|
|
|
80
|
+
async function exists(path) {
|
|
81
|
+
try {
|
|
82
|
+
await access(path);
|
|
83
|
+
return true;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if (error.code === 'ENOENT' || error.code === 'ENOTDIR') return false;
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function readJson(path) {
|
|
91
|
+
try {
|
|
92
|
+
return JSON.parse(await readFile(path, 'utf8'));
|
|
93
|
+
} catch (error) {
|
|
94
|
+
if (error.code === 'ENOENT' || error.code === 'ENOTDIR') return null;
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function ancestors(directory) {
|
|
100
|
+
const list = [];
|
|
101
|
+
for (let current = directory; ; current = dirname(current)) {
|
|
102
|
+
list.push(current);
|
|
103
|
+
if (dirname(current) === current) return list;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Node package lookup: <dir>/node_modules/<name> from the app upward, so
|
|
108
|
+
// hoisted installs (pnpm node-linker=hoisted, npm/yarn workspaces) resolve.
|
|
109
|
+
async function installedManifest(appPath, name) {
|
|
110
|
+
for (const directory of ancestors(appPath)) {
|
|
111
|
+
const path = join(directory, 'node_modules', name, 'package.json');
|
|
112
|
+
const manifest = await readJson(path);
|
|
113
|
+
if (manifest !== null) return { path, manifest };
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// The app's own packageManager wins, then the nearest ancestor's.
|
|
119
|
+
async function packageManagerFor(appPath) {
|
|
120
|
+
for (const directory of ancestors(appPath)) {
|
|
121
|
+
const manifest = await readJson(join(directory, 'package.json'));
|
|
122
|
+
if (typeof manifest?.packageManager === 'string')
|
|
123
|
+
return manifest.packageManager.trim();
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Remove a YAML comment: `#` at line start or after whitespace, outside quotes.
|
|
129
|
+
function stripYamlComment(line) {
|
|
130
|
+
let quote = null;
|
|
131
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
132
|
+
const char = line[index];
|
|
133
|
+
if (quote) {
|
|
134
|
+
if (char === quote) quote = null;
|
|
135
|
+
} else if (char === '"' || char === "'") quote = char;
|
|
136
|
+
else if (char === '#' && (index === 0 || /\s/.test(line[index - 1])))
|
|
137
|
+
return line.slice(0, index);
|
|
138
|
+
}
|
|
139
|
+
return line;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function unquoteYaml(value) {
|
|
143
|
+
const text = value.trim();
|
|
144
|
+
if (text.length >= 2 && text.startsWith("'") && text.endsWith("'"))
|
|
145
|
+
return text.slice(1, -1).replaceAll("''", "'");
|
|
146
|
+
if (text.length >= 2 && text.startsWith('"') && text.endsWith('"')) {
|
|
147
|
+
try {
|
|
148
|
+
return JSON.parse(text);
|
|
149
|
+
} catch {
|
|
150
|
+
return text.slice(1, -1);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return text;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Split a flow sequence body on commas outside quotes and braces.
|
|
157
|
+
function splitFlowItems(body) {
|
|
158
|
+
const items = [];
|
|
159
|
+
let quote = null;
|
|
160
|
+
let depth = 0;
|
|
161
|
+
let current = '';
|
|
162
|
+
for (const char of body) {
|
|
163
|
+
if (quote) {
|
|
164
|
+
if (char === quote) quote = null;
|
|
165
|
+
} else if (char === '"' || char === "'") quote = char;
|
|
166
|
+
else if (char === '{') depth += 1;
|
|
167
|
+
else if (char === '}') depth -= 1;
|
|
168
|
+
else if (char === ',' && depth === 0) {
|
|
169
|
+
items.push(current);
|
|
170
|
+
current = '';
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
current += char;
|
|
174
|
+
}
|
|
175
|
+
items.push(current);
|
|
176
|
+
return items;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// The `packages` list of a pnpm-workspace.yaml: an indented or unindented
|
|
180
|
+
// block sequence or a flow sequence, with quotes and comments. A file
|
|
181
|
+
// without `packages` (settings only) declares no member packages.
|
|
182
|
+
function workspacePatterns(text) {
|
|
183
|
+
const lines = text.split(/\r?\n/).map(stripYamlComment);
|
|
184
|
+
const start = lines.findIndex((line) => /^packages\s*:/.test(line));
|
|
185
|
+
if (start < 0) return [];
|
|
186
|
+
const inline = lines[start].replace(/^packages\s*:/, '').trim();
|
|
187
|
+
if (inline.startsWith('[')) {
|
|
188
|
+
let flow = inline;
|
|
189
|
+
for (
|
|
190
|
+
let index = start + 1;
|
|
191
|
+
!flow.includes(']') && index < lines.length;
|
|
192
|
+
index += 1
|
|
193
|
+
)
|
|
194
|
+
flow += ` ${lines[index].trim()}`;
|
|
195
|
+
return splitFlowItems(flow.slice(1, flow.lastIndexOf(']')))
|
|
196
|
+
.map(unquoteYaml)
|
|
197
|
+
.filter((entry) => entry.length > 0);
|
|
198
|
+
}
|
|
199
|
+
const patterns = [];
|
|
200
|
+
for (const line of lines.slice(start + 1)) {
|
|
201
|
+
if (line.trim() === '') continue;
|
|
202
|
+
const item = line.match(/^\s*-\s*(.*)$/);
|
|
203
|
+
if (!item) break;
|
|
204
|
+
patterns.push(unquoteYaml(item[1]));
|
|
205
|
+
}
|
|
206
|
+
return patterns.filter((entry) => entry.length > 0);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Expand `{a,b}` alternatives, innermost first.
|
|
210
|
+
function expandBraces(pattern) {
|
|
211
|
+
const match = pattern.match(/\{([^{}]*)\}/);
|
|
212
|
+
if (!match) return [pattern];
|
|
213
|
+
return match[1]
|
|
214
|
+
.split(',')
|
|
215
|
+
.flatMap((alternative) =>
|
|
216
|
+
expandBraces(
|
|
217
|
+
pattern.slice(0, match.index) +
|
|
218
|
+
alternative +
|
|
219
|
+
pattern.slice(match.index + match[0].length),
|
|
220
|
+
),
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Glob syntax evaluated here: `*`, `**`, `?`, braces (expanded first) and
|
|
225
|
+
// positive character classes. Extglobs, negated classes, escapes and
|
|
226
|
+
// unbalanced brackets are not, and never count as a non-match.
|
|
227
|
+
function unsupportedGlob(pattern) {
|
|
228
|
+
return (
|
|
229
|
+
/[+@!?*]\(/.test(pattern) ||
|
|
230
|
+
/\[[!^]/.test(pattern) ||
|
|
231
|
+
pattern.includes('\\') ||
|
|
232
|
+
/\[(?![^\]]*\])/.test(pattern) ||
|
|
233
|
+
/\[\]/.test(pattern)
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function globToRegExp(pattern) {
|
|
238
|
+
const source = pattern
|
|
239
|
+
.split(/(\*\*\/|\*\*|\*|\?|\[[^\]]+\])/)
|
|
240
|
+
.map((part) =>
|
|
241
|
+
part === '**/'
|
|
242
|
+
? '(?:.*/)?'
|
|
243
|
+
: part === '**'
|
|
244
|
+
? '.*'
|
|
245
|
+
: part === '*'
|
|
246
|
+
? '[^/]*'
|
|
247
|
+
: part === '?'
|
|
248
|
+
? '[^/]'
|
|
249
|
+
: part.startsWith('[') && part.endsWith(']')
|
|
250
|
+
? `(?!/)[${part.slice(1, -1).replace(/[\\\]]/g, '\\$&')}]`
|
|
251
|
+
: part.replace(/[.+^${}()|[\]\\]/g, '\\$&'),
|
|
252
|
+
)
|
|
253
|
+
.join('');
|
|
254
|
+
return new RegExp(`^${source}$`);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// pnpm finds members by globbing `<pattern>/package.json`, so a trailing
|
|
258
|
+
// globstar also matches the directory itself; negations exclude. Returns
|
|
259
|
+
// true or false, or `{ unsupported: pattern }` when a pattern whose result
|
|
260
|
+
// matters cannot be evaluated.
|
|
261
|
+
function workspaceIncludes(patterns, rel) {
|
|
262
|
+
const target = `${rel}/package.json`;
|
|
263
|
+
const evaluate = (pattern) => {
|
|
264
|
+
const expanded = expandBraces(
|
|
265
|
+
pattern.replace(/^\.\//, '').replace(/\/+$/, ''),
|
|
266
|
+
);
|
|
267
|
+
if (expanded.some(unsupportedGlob)) return { unsupported: pattern };
|
|
268
|
+
return expanded.some((glob) =>
|
|
269
|
+
globToRegExp(`${glob}/package.json`).test(target),
|
|
270
|
+
);
|
|
271
|
+
};
|
|
272
|
+
const any = (list) => {
|
|
273
|
+
let unknown = null;
|
|
274
|
+
for (const pattern of list) {
|
|
275
|
+
const result = evaluate(pattern);
|
|
276
|
+
if (result === true) return true;
|
|
277
|
+
if (result !== false && unknown === null) unknown = result;
|
|
278
|
+
}
|
|
279
|
+
return unknown ?? false;
|
|
280
|
+
};
|
|
281
|
+
const included = any(patterns.filter((pattern) => !pattern.startsWith('!')));
|
|
282
|
+
if (included !== true) return included;
|
|
283
|
+
const excluded = any(
|
|
284
|
+
patterns
|
|
285
|
+
.filter((pattern) => pattern.startsWith('!'))
|
|
286
|
+
.map((pattern) => pattern.slice(1)),
|
|
287
|
+
);
|
|
288
|
+
return excluded === true ? false : excluded === false ? true : excluded;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// The app's relation to the nearest pnpm workspace: its own root, a member of
|
|
292
|
+
// an ancestor workspace, standalone (the ancestor's `packages` globs exclude
|
|
293
|
+
// it, so installs need --ignore-workspace), or undetermined when a pattern
|
|
294
|
+
// cannot be evaluated. Lockfile presence is not evidence: a member can keep
|
|
295
|
+
// its own lockfile with shared-workspace-lockfile=false.
|
|
296
|
+
async function workspaceMembership(appPath) {
|
|
297
|
+
if (await exists(join(appPath, 'pnpm-workspace.yaml')))
|
|
298
|
+
return { root: appPath, membership: 'root', reason: null };
|
|
299
|
+
for (const directory of ancestors(dirname(appPath))) {
|
|
300
|
+
const file = join(directory, 'pnpm-workspace.yaml');
|
|
301
|
+
if (!(await exists(file))) continue;
|
|
302
|
+
const rel = relative(directory, appPath).split(sep).join('/');
|
|
303
|
+
const included = workspaceIncludes(
|
|
304
|
+
workspacePatterns(await readFile(file, 'utf8')),
|
|
305
|
+
rel,
|
|
306
|
+
);
|
|
307
|
+
if (typeof included === 'boolean')
|
|
308
|
+
return {
|
|
309
|
+
root: directory,
|
|
310
|
+
membership: included ? 'member' : 'standalone',
|
|
311
|
+
reason: null,
|
|
312
|
+
};
|
|
313
|
+
return {
|
|
314
|
+
root: directory,
|
|
315
|
+
membership: 'undetermined',
|
|
316
|
+
reason: `pnpm-workspace.yaml pattern "${included.unsupported}" uses glob syntax this helper does not evaluate; workspace membership is unknown, so the command omits --ignore-workspace. Confirm membership with pnpm -r ls before running it.`,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
return { root: null, membership: null, reason: null };
|
|
320
|
+
}
|
|
321
|
+
|
|
78
322
|
try {
|
|
79
323
|
const { values } = parseArgs({
|
|
80
324
|
options: {
|
|
@@ -84,14 +328,46 @@ try {
|
|
|
84
328
|
},
|
|
85
329
|
});
|
|
86
330
|
if (!values.app) throw new Error('Required: --app <docs-app-directory>.');
|
|
331
|
+
const appPath = resolve(values.app);
|
|
332
|
+
const manifest = await readJson(join(appPath, 'package.json'));
|
|
333
|
+
if (manifest === null) {
|
|
334
|
+
process.stdout.write(
|
|
335
|
+
`${JSON.stringify(
|
|
336
|
+
{
|
|
337
|
+
versionsPath: null,
|
|
338
|
+
appPath,
|
|
339
|
+
applicable: false,
|
|
340
|
+
reason:
|
|
341
|
+
'No package.json in the docs directory: Markdown docs have no app-package drift check.',
|
|
342
|
+
packages: [],
|
|
343
|
+
},
|
|
344
|
+
null,
|
|
345
|
+
2,
|
|
346
|
+
)}\n`,
|
|
347
|
+
);
|
|
348
|
+
process.exit(0);
|
|
349
|
+
}
|
|
87
350
|
const versionsPath = resolve(
|
|
88
351
|
values.versions ?? (await versionsForCli(values.cli)),
|
|
89
352
|
);
|
|
90
353
|
const versions = JSON.parse(await readFile(versionsPath, 'utf8'));
|
|
91
|
-
const
|
|
92
|
-
const
|
|
93
|
-
|
|
354
|
+
const packageManager = await packageManagerFor(appPath);
|
|
355
|
+
const pinned = packageManager?.match(
|
|
356
|
+
/^pnpm@(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)(?:\+.*)?$/,
|
|
94
357
|
);
|
|
358
|
+
// Run the repository's pinned pnpm rather than whatever global pnpm is on
|
|
359
|
+
// PATH: a different major can rewrite workspace settings or reject the store.
|
|
360
|
+
// npx ships with npm on every Node line; Corepack is absent from Node 25+.
|
|
361
|
+
const pnpm = pinned ? `npx --yes pnpm@${pinned[1]}` : 'pnpm';
|
|
362
|
+
const otherManager =
|
|
363
|
+
packageManager && !packageManager.startsWith('pnpm@')
|
|
364
|
+
? packageManager
|
|
365
|
+
: null;
|
|
366
|
+
// A nested app the ancestor workspace does not include must be installed
|
|
367
|
+
// with --ignore-workspace; an undetermined result never adds the flag.
|
|
368
|
+
const workspace = await workspaceMembership(appPath);
|
|
369
|
+
const ignoreWorkspace =
|
|
370
|
+
workspace.membership === 'standalone' ? ' --ignore-workspace' : '';
|
|
95
371
|
const packages = [];
|
|
96
372
|
for (const section of [
|
|
97
373
|
'dependencies',
|
|
@@ -116,29 +392,22 @@ try {
|
|
|
116
392
|
)
|
|
117
393
|
throw new Error(`Missing/invalid bundled version for ${name}.`);
|
|
118
394
|
// Report installed package bytes when available, not just a declared range.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
join(appPath, 'node_modules', name, 'package.json'),
|
|
124
|
-
'utf8',
|
|
125
|
-
),
|
|
126
|
-
);
|
|
127
|
-
if (typeof installedManifest.version !== 'string')
|
|
128
|
-
throw new Error(`Invalid installed version for ${name}.`);
|
|
129
|
-
installed = installedManifest.version;
|
|
130
|
-
} catch (error) {
|
|
131
|
-
if (error.code !== 'ENOENT') throw error;
|
|
132
|
-
}
|
|
395
|
+
const found = await installedManifest(appPath, name);
|
|
396
|
+
if (found !== null && typeof found.manifest.version !== 'string')
|
|
397
|
+
throw new Error(`Invalid installed version for ${name}.`);
|
|
398
|
+
const installed = found?.manifest.version ?? null;
|
|
133
399
|
// Only stable numeric releases are orderable here; prereleases and other
|
|
134
400
|
// package-manager version forms need manual verification, never an upgrade.
|
|
135
401
|
const stable = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
|
|
136
402
|
let status = 'unverified';
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
403
|
+
let reason = null;
|
|
404
|
+
if (installed === null)
|
|
405
|
+
reason = `not installed: no node_modules/${name} found from the app or its ancestors; run the app install first`;
|
|
406
|
+
else if (!stable.test(installed))
|
|
407
|
+
reason = `installed version ${installed} is not a stable release; verify it manually`;
|
|
408
|
+
else if (!stable.test(bundled))
|
|
409
|
+
reason = `bundled version ${bundled} is a prerelease; verify it manually`;
|
|
410
|
+
else {
|
|
142
411
|
const left = installed.split('.').map(BigInt);
|
|
143
412
|
const right = bundled.split('.').map(BigInt);
|
|
144
413
|
const differing = left.findIndex(
|
|
@@ -151,22 +420,26 @@ try {
|
|
|
151
420
|
? 'drift'
|
|
152
421
|
: 'ahead';
|
|
153
422
|
}
|
|
423
|
+
let upgradeCommand = null;
|
|
424
|
+
if (status === 'drift' && otherManager)
|
|
425
|
+
reason = `packageManager is ${otherManager}; translate the upgrade to its add command, preserving the dependency section and exact version`;
|
|
426
|
+
else if (status === 'drift')
|
|
427
|
+
upgradeCommand = `${pnpm} --dir ${shellQuote(appPath)} add${ignoreWorkspace}${section === 'devDependencies' ? ' --save-dev' : section === 'optionalDependencies' ? ' --save-optional' : ''} ${name}@${bundled}`;
|
|
154
428
|
packages.push({
|
|
155
429
|
name,
|
|
156
430
|
section,
|
|
157
431
|
declared,
|
|
158
432
|
installed,
|
|
433
|
+
installedPath: found?.path ?? null,
|
|
159
434
|
bundled,
|
|
160
435
|
status,
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
? `pnpm --dir ${shellQuote(appPath)} add${section === 'devDependencies' ? ' --save-dev' : section === 'optionalDependencies' ? ' --save-optional' : ''} ${name}@${bundled}`
|
|
164
|
-
: null,
|
|
436
|
+
reason,
|
|
437
|
+
upgradeCommand,
|
|
165
438
|
});
|
|
166
439
|
}
|
|
167
440
|
}
|
|
168
441
|
process.stdout.write(
|
|
169
|
-
`${JSON.stringify({ versionsPath, appPath, packages }, null, 2)}\n`,
|
|
442
|
+
`${JSON.stringify({ versionsPath, appPath, applicable: true, packageManager, workspace, packages }, null, 2)}\n`,
|
|
170
443
|
);
|
|
171
444
|
} catch (error) {
|
|
172
445
|
process.stderr.write(`${error.message}\n`);
|
|
@@ -5,7 +5,7 @@ disable-model-invocation: true
|
|
|
5
5
|
user-invocable: true
|
|
6
6
|
allowed-tools: Read, Write, Edit, Bash(git:*), Bash(gh:*), Bash(oat:*), Bash(pnpm:*), Bash(bash:*), Bash(jq:*), Bash(mkdir:*), Bash(date:*), Bash(echo:*), Bash(cd:*), Bash(pwd:*), Glob, Grep, AskUserQuestion
|
|
7
7
|
metadata:
|
|
8
|
-
version: '1.4.
|
|
8
|
+
version: '1.4.8'
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# Docs Apply
|
|
@@ -324,6 +324,8 @@ approval, its out-of-Markdown allowance is only these four operations:
|
|
|
324
324
|
app's own `@open-agent-toolkit/cli` dependency when the app declares it, to the
|
|
325
325
|
bundled versions using the artifact's exact upgrade commands (preserve each
|
|
326
326
|
dependency section and the package manager; include the resulting lockfile only).
|
|
327
|
+
Run the pinned package-manager form the command gives; a global pnpm of another
|
|
328
|
+
major can rewrite workspace settings and break the store link.
|
|
327
329
|
Never downgrade an installed version: `ahead` or `unverified` requires CLI
|
|
328
330
|
selection/update or manual verification before re-analysis, not a tooling update.
|
|
329
331
|
2. After a CLI bump, regenerate the app's generated manifest (`index.md`) with that
|
|
@@ -29,7 +29,12 @@ Use file-path-friendly relative Markdown links:
|
|
|
29
29
|
- child directory: `[Section](section/index.md)`;
|
|
30
30
|
- avoid extensionless local docs links for new authored navigation entries;
|
|
31
31
|
- preserve anchors only when the target heading exists and the local renderer
|
|
32
|
-
supports the anchor
|
|
32
|
+
supports the anchor;
|
|
33
|
+
- index-only leaf (its `index.md` is the directory's only reader page and it
|
|
34
|
+
has no child sections): may list same-page heading anchors such as `[Setup](#setup)` as an in-page table
|
|
35
|
+
of contents. They add no navigation entry, and `oat docs check` warns
|
|
36
|
+
(`leaf-size`) that the section could be flattened. Anywhere else a same-page
|
|
37
|
+
anchor in Contents is an error.
|
|
33
38
|
|
|
34
39
|
Existing extensionless links are drift, not precedent. Normalize them only when
|
|
35
40
|
the task scope or an approved recommendation covers that change.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/config/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,mBAAmB,EAAE,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAIhF,OAAO,EAEL,KAAK,aAAa,EACnB,MAAM,iCAAiC,CAAC;AAsBzC,OAAO,EAUL,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,cAAc,EAQnB,KAAK,UAAU,EAwBhB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAGL,KAAK,cAAc,EAEpB,MAAM,iBAAiB,CAAC;AAIzB,OAAO,EAGL,KAAK,8BAA8B,EACnC,KAAK,yBAAyB,EAC/B,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EACL,mCAAmC,EACnC,0BAA0B,EAC3B,MAAM,yCAAyC,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/config/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -77,6 +77,7 @@ const KEY_ORDER = [
|
|
|
77
77
|
'documentation.root',
|
|
78
78
|
'documentation.tooling',
|
|
79
79
|
'documentation.config',
|
|
80
|
+
'documentation.index',
|
|
80
81
|
'documentation.excludes',
|
|
81
82
|
'documentation.requireForProjectCompletion',
|
|
82
83
|
'explainers.defaults.style',
|
|
@@ -322,6 +323,17 @@ const CONFIG_CATALOG = [
|
|
|
322
323
|
owningCommand: 'oat config set documentation.config <value>',
|
|
323
324
|
description: 'Repository-relative path to the primary documentation tool config file.',
|
|
324
325
|
},
|
|
326
|
+
{
|
|
327
|
+
key: 'documentation.index',
|
|
328
|
+
group: 'Shared Repo (.oat/config.json)',
|
|
329
|
+
file: '.oat/config.json',
|
|
330
|
+
scope: 'shared repo',
|
|
331
|
+
type: 'string',
|
|
332
|
+
defaultValue: 'unset',
|
|
333
|
+
mutability: 'read/write',
|
|
334
|
+
owningCommand: 'oat config set documentation.index <value>',
|
|
335
|
+
description: 'Repository-relative docs index path. Markdown: the authored root index.md. Fumadocs: the generated app-root manifest; MkDocs: mkdocs.yml. oat docs init writes it and oat docs generate-index updates the Fumadocs transition.',
|
|
336
|
+
},
|
|
325
337
|
{
|
|
326
338
|
key: 'documentation.nonReaderPaths',
|
|
327
339
|
group: 'Shared Repo (.oat/config.json)',
|
|
@@ -330,7 +342,7 @@ const CONFIG_CATALOG = [
|
|
|
330
342
|
type: 'string[]',
|
|
331
343
|
defaultValue: 'unset',
|
|
332
344
|
mutability: 'read/write',
|
|
333
|
-
owningCommand: 'oat config set documentation.nonReaderPaths <glob[,glob...]>',
|
|
345
|
+
owningCommand: 'oat config set documentation.nonReaderPaths <glob[,glob...]|JSON-array>',
|
|
334
346
|
description: 'Content-root-relative globs excluded from reader navigation, manifest and supported framework rendering. Empty clears the key.',
|
|
335
347
|
},
|
|
336
348
|
{
|
|
@@ -341,7 +353,7 @@ const CONFIG_CATALOG = [
|
|
|
341
353
|
type: 'string[]',
|
|
342
354
|
defaultValue: 'unset',
|
|
343
355
|
mutability: 'read/write',
|
|
344
|
-
owningCommand: 'oat config set documentation.excludes <glob[,glob...]>',
|
|
356
|
+
owningCommand: 'oat config set documentation.excludes <glob[,glob...]|JSON-array>',
|
|
345
357
|
description: 'Comma-separated globs, relative to the docs directory, excluded from `oat docs generate-index`. Repeated `--exclude` flags extend this list; an empty value clears the key.',
|
|
346
358
|
},
|
|
347
359
|
{
|
|
@@ -1113,7 +1125,24 @@ function parseInstructionPointerExcludes(rawValue) {
|
|
|
1113
1125
|
}
|
|
1114
1126
|
return normalized;
|
|
1115
1127
|
}
|
|
1116
|
-
function parseDocumentationExcludes(rawValue) {
|
|
1128
|
+
function parseDocumentationExcludes(rawValue, key) {
|
|
1129
|
+
const trimmed = rawValue.trim();
|
|
1130
|
+
// A glob list may be given as a JSON array (which also keeps braces such as
|
|
1131
|
+
// `{a,b}` intact). Anything else that looks like JSON is a mistake, never a
|
|
1132
|
+
// literal glob.
|
|
1133
|
+
if (trimmed.startsWith('[') || trimmed.startsWith('{')) {
|
|
1134
|
+
let parsed;
|
|
1135
|
+
try {
|
|
1136
|
+
parsed = JSON.parse(trimmed);
|
|
1137
|
+
}
|
|
1138
|
+
catch {
|
|
1139
|
+
parsed = undefined;
|
|
1140
|
+
}
|
|
1141
|
+
if (!Array.isArray(parsed) ||
|
|
1142
|
+
parsed.some((entry) => typeof entry !== 'string' || entry.trim().length === 0))
|
|
1143
|
+
throw new Error(`Invalid ${key} value: a value starting with [ or { must be a JSON array of non-empty strings; otherwise use comma-separated globs.`);
|
|
1144
|
+
return [...new Set(parsed.map((entry) => entry.trim()))];
|
|
1145
|
+
}
|
|
1117
1146
|
const parsed = rawValue
|
|
1118
1147
|
.split(',')
|
|
1119
1148
|
.map((entry) => entry.trim())
|
|
@@ -1853,7 +1882,7 @@ async function setConfigValue(repoRoot, userConfigDir, key, rawValue, surface, d
|
|
|
1853
1882
|
value = normalizeDocumentationCheck(parsed);
|
|
1854
1883
|
}
|
|
1855
1884
|
else if (key === 'documentation.nonReaderPaths') {
|
|
1856
|
-
const paths = parseDocumentationExcludes(rawValue);
|
|
1885
|
+
const paths = parseDocumentationExcludes(rawValue, 'documentation.nonReaderPaths');
|
|
1857
1886
|
value = paths.length > 0 ? paths : undefined;
|
|
1858
1887
|
}
|
|
1859
1888
|
else if (key === 'documentation.ia') {
|
|
@@ -1870,7 +1899,8 @@ async function setConfigValue(repoRoot, userConfigDir, key, rawValue, surface, d
|
|
|
1870
1899
|
}
|
|
1871
1900
|
}
|
|
1872
1901
|
catch (error) {
|
|
1873
|
-
if (key === 'documentation.check'
|
|
1902
|
+
if (key === 'documentation.check' ||
|
|
1903
|
+
key === 'documentation.nonReaderPaths')
|
|
1874
1904
|
throw error;
|
|
1875
1905
|
if (error instanceof DocumentationIaPresetError)
|
|
1876
1906
|
throw new Error(`Invalid ${key} value: ${error.detail}.`, {
|
|
@@ -2106,8 +2136,11 @@ async function setConfigValue(repoRoot, userConfigDir, key, rawValue, surface, d
|
|
|
2106
2136
|
else if (key === 'documentation.config') {
|
|
2107
2137
|
doc.config = normalizeSharedRoot(rawValue);
|
|
2108
2138
|
}
|
|
2139
|
+
else if (key === 'documentation.index') {
|
|
2140
|
+
doc.index = normalizeSharedRoot(rawValue);
|
|
2141
|
+
}
|
|
2109
2142
|
else if (key === 'documentation.excludes') {
|
|
2110
|
-
const excludes = parseDocumentationExcludes(rawValue);
|
|
2143
|
+
const excludes = parseDocumentationExcludes(rawValue, 'documentation.excludes');
|
|
2111
2144
|
if (excludes.length === 0) {
|
|
2112
2145
|
// Unlike the scalar shared keys, an empty value is the documented way
|
|
2113
2146
|
// to clear this list rather than an error.
|
|
@@ -2265,9 +2298,9 @@ function withoutOwnKey(record, key) {
|
|
|
2265
2298
|
*
|
|
2266
2299
|
* Pruning is strictly "the object has no remaining own keys". That is what
|
|
2267
2300
|
* keeps a prune from destroying a sibling the `oat config` catalog does not
|
|
2268
|
-
* expose
|
|
2269
|
-
*
|
|
2270
|
-
*
|
|
2301
|
+
* expose (such as a key a newer CLI wrote): an unset of the last catalogued
|
|
2302
|
+
* `documentation.*` key leaves the parent in place while any other key is
|
|
2303
|
+
* still present.
|
|
2271
2304
|
*
|
|
2272
2305
|
* A missing path, a `null`/`undefined` leaf, or a non-object in the middle of
|
|
2273
2306
|
* the path all report `removed: false`, which the caller surfaces as the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/check/check.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKpC,wBAAgB,sBAAsB,IAAI,OAAO,
|
|
1
|
+
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/check/check.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKpC,wBAAgB,sBAAsB,IAAI,OAAO,CA6DhD"}
|
|
@@ -4,5 +4,7 @@ export declare function checkDocs(options: {
|
|
|
4
4
|
cwd: string;
|
|
5
5
|
targetDir?: string;
|
|
6
6
|
rules?: string[];
|
|
7
|
+
/** Receives human notices; they never enter the JSON result. */
|
|
8
|
+
onNotice?: (message: string) => void;
|
|
7
9
|
}): Promise<DocsCheckResult>;
|
|
8
10
|
//# sourceMappingURL=checker.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checker.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/check/checker.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,eAAe,EAAuB,MAAM,eAAe,CAAC;AAQ1E,gFAAgF;AAChF,wBAAsB,SAAS,CAAC,OAAO,EAAE;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"checker.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/check/checker.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,eAAe,EAAuB,MAAM,eAAe,CAAC;AAQ1E,gFAAgF;AAChF,wBAAsB,SAAS,CAAC,OAAO,EAAE;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,gEAAgE;IAChE,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC,GAAG,OAAO,CAAC,eAAe,CAAC,CAiG3B"}
|
|
@@ -11,6 +11,8 @@ export async function checkDocs(options) {
|
|
|
11
11
|
if (unknown)
|
|
12
12
|
throw new Error(`Unknown docs check rule "${unknown}". Choose: ${DOCS_CHECK_RULES.join(', ')}.`);
|
|
13
13
|
const target = await resolveCheckTarget(options.cwd, options.targetDir);
|
|
14
|
+
for (const notice of target.notices)
|
|
15
|
+
options.onNotice?.(notice);
|
|
14
16
|
const policy = target.documentation.check;
|
|
15
17
|
const findings = [];
|
|
16
18
|
const exceptions = policy?.exceptions?.map((e) => ({
|
|
@@ -53,7 +55,10 @@ export async function checkDocs(options) {
|
|
|
53
55
|
await checkReaders(target, pages, report);
|
|
54
56
|
checkProvenance(target, pages, report);
|
|
55
57
|
if (!options.rules?.length || options.rules.includes('navigation'))
|
|
56
|
-
await checkNavigation(target, report)
|
|
58
|
+
await checkNavigation(target, report, (ruleId, path, line) => findings.some((f) => f.severity === 'error' &&
|
|
59
|
+
f.ruleId === ruleId &&
|
|
60
|
+
f.path === path &&
|
|
61
|
+
f.line === line));
|
|
57
62
|
if (!options.rules?.length || options.rules.includes('commands'))
|
|
58
63
|
await checkCommands(target, pages, report);
|
|
59
64
|
if (!options.rules?.length ||
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/check/commands.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAA0B,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AACnE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAgI7C,wBAAsB,aAAa,CACjC,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,UAAU,EAAE,EACnB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/check/commands.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAA0B,KAAK,WAAW,EAAE,MAAM,SAAS,CAAC;AACnE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAgI7C,wBAAsB,aAAa,CACjC,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,UAAU,EAAE,EACnB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAkNf"}
|