@open-agent-toolkit/cli 0.3.12 → 0.3.13
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/cli-utilities/configuration.md +1 -1
- package/assets/docs/cli-utilities/tool-packs.md +4 -1
- package/assets/docs/docs-tooling/add-docs-to-a-repo.md +80 -15
- package/assets/docs/docs-tooling/commands.md +74 -30
- package/assets/docs/docs-tooling/index.md +6 -6
- package/assets/docs/docs-tooling/workflows.md +26 -9
- package/assets/docs/provider-sync/commands.md +1 -1
- package/assets/docs/provider-sync/instruction-sync.md +11 -4
- package/assets/docs/reference/cli-reference.md +2 -2
- package/assets/docs/reference/docs-index-contract.md +19 -4
- package/assets/docs/reference/file-locations.md +3 -1
- package/assets/docs/reference/oat-directory-structure.md +23 -21
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/oat-docs-analyze/SKILL.md +27 -7
- package/assets/skills/oat-docs-analyze/references/quality-checklist.md +16 -3
- package/assets/skills/oat-docs-apply/SKILL.md +20 -3
- package/assets/skills/oat-docs-authoring/SKILL.md +11 -7
- package/assets/skills/oat-docs-authoring/references/docs-root-resolution.md +12 -5
- package/assets/skills/oat-docs-authoring/references/lifecycle-boundaries.md +7 -7
- package/assets/skills/oat-docs-authoring/references/oat-fumadocs-contract.md +13 -3
- package/assets/skills/oat-docs-authoring/references/targeted-authoring-workflow.md +5 -4
- package/assets/skills/oat-docs-authoring/references/validation.md +11 -1
- package/assets/skills/oat-docs-bootstrap/SKILL.md +31 -8
- package/assets/skills/oat-docs-bootstrap/assets/AGENTS.md.template +2 -1
- package/assets/skills/oat-project-document/SKILL.md +21 -2
- package/assets/templates/docs-markdown/contributing.md +35 -0
- package/assets/templates/docs-markdown/index.md +18 -0
- package/dist/commands/docs/index-generate/index.d.ts.map +1 -1
- package/dist/commands/docs/index-generate/index.js +41 -8
- package/dist/commands/docs/init/index.d.ts +17 -2
- package/dist/commands/docs/init/index.d.ts.map +1 -1
- package/dist/commands/docs/init/index.js +194 -14
- package/dist/commands/docs/init/markdown.d.ts +39 -0
- package/dist/commands/docs/init/markdown.d.ts.map +1 -0
- package/dist/commands/docs/init/markdown.js +317 -0
- package/dist/commands/docs/init/resolve-options.d.ts +3 -1
- package/dist/commands/docs/init/resolve-options.d.ts.map +1 -1
- package/dist/commands/docs/init/resolve-options.js +36 -0
- package/dist/commands/docs/init/scaffold.d.ts.map +1 -1
- package/dist/commands/docs/init/scaffold.js +11 -0
- package/dist/commands/shared/agents-md.d.ts +7 -0
- package/dist/commands/shared/agents-md.d.ts.map +1 -1
- package/dist/commands/shared/agents-md.js +131 -91
- package/dist/commands/tools/shared/pack-manifest.d.ts.map +1 -1
- package/dist/commands/tools/shared/pack-manifest.js +1 -0
- package/dist/config/oat-config.d.ts +2 -1
- package/dist/config/oat-config.d.ts.map +1 -1
- package/dist/config/oat-config.js +4 -2
- package/package.json +2 -2
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { lstat, open, realpath, readdir, readFile, stat, unlink, } from 'node:fs/promises';
|
|
2
|
+
import { basename, dirname, join, relative, resolve } from 'node:path';
|
|
3
|
+
import { createExclusionMatcher } from '../../docs/index-generate/generator.js';
|
|
4
|
+
import { dirExists, ensureDir } from '../../../fs/io.js';
|
|
5
|
+
import { normalizeToPosixPath, validatePathWithinScope } from '../../../fs/paths.js';
|
|
6
|
+
import { parse as parseYaml } from 'yaml';
|
|
7
|
+
import { humanizeAppName, } from './resolve-options.js';
|
|
8
|
+
/** Validate a dedicated Markdown root, including existing symlink ancestors. */
|
|
9
|
+
export async function validateMarkdownTarget(repoRoot, targetDir) {
|
|
10
|
+
const target = validatePathWithinScope(resolve(repoRoot, targetDir), repoRoot);
|
|
11
|
+
const relativeTarget = relative(resolve(repoRoot), target);
|
|
12
|
+
if (!relativeTarget)
|
|
13
|
+
throw new Error('Markdown docs require a dedicated directory; the repository root is unsafe.');
|
|
14
|
+
const canonicalRoot = await realpath(repoRoot);
|
|
15
|
+
let ancestor = target;
|
|
16
|
+
while (true) {
|
|
17
|
+
try {
|
|
18
|
+
await lstat(ancestor);
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
if (!(error instanceof Error && 'code' in error && error.code === 'ENOENT'))
|
|
23
|
+
throw error;
|
|
24
|
+
ancestor = dirname(ancestor);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const canonicalAncestor = await realpath(ancestor);
|
|
28
|
+
const canonicalTarget = resolve(canonicalAncestor, relative(ancestor, target));
|
|
29
|
+
validatePathWithinScope(canonicalTarget, canonicalRoot);
|
|
30
|
+
if (canonicalTarget === canonicalRoot)
|
|
31
|
+
throw new Error('Markdown docs require a dedicated directory; target resolves to the repository root.');
|
|
32
|
+
if (ancestor === target &&
|
|
33
|
+
!(await lstat(target)).isDirectory() &&
|
|
34
|
+
!(await lstat(target)).isSymbolicLink()) {
|
|
35
|
+
throw new Error(`Markdown docs target must be a directory: ${target}`);
|
|
36
|
+
}
|
|
37
|
+
return normalizeToPosixPath(relativeTarget);
|
|
38
|
+
}
|
|
39
|
+
function renderMarkdownTemplate(template, options, contents) {
|
|
40
|
+
const replacements = {
|
|
41
|
+
"'{{TITLE_METADATA}}'": JSON.stringify(options.siteName),
|
|
42
|
+
"'{{DESCRIPTION_METADATA}}'": JSON.stringify(options.siteDescription),
|
|
43
|
+
'{{SITE_NAME}}': options.siteName,
|
|
44
|
+
'{{REPO_NAME}}': basename(options.repoRoot),
|
|
45
|
+
'{{CONTENTS}}': contents,
|
|
46
|
+
'{{LINT_MODE}}': options.lint,
|
|
47
|
+
'{{FORMAT_MODE}}': options.format,
|
|
48
|
+
};
|
|
49
|
+
return template.replace(/'\{\{(?:TITLE|DESCRIPTION)_METADATA\}\}'|\{\{[A-Z_]+\}\}/g, (token) => replacements[token] ?? token);
|
|
50
|
+
}
|
|
51
|
+
export class MarkdownDocsWriteError extends Error {
|
|
52
|
+
result;
|
|
53
|
+
constructor(message, result, cause) {
|
|
54
|
+
super(message, { cause });
|
|
55
|
+
this.result = result;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export function validateMarkdownConfig(repoRoot, targetDir, declared) {
|
|
59
|
+
if ((declared?.tooling &&
|
|
60
|
+
declared.tooling.trim().toLowerCase() !== 'markdown') ||
|
|
61
|
+
(declared?.root &&
|
|
62
|
+
resolve(repoRoot, declared.root) !== resolve(repoRoot, targetDir)) ||
|
|
63
|
+
(declared?.index &&
|
|
64
|
+
resolve(repoRoot, declared.index) !==
|
|
65
|
+
resolve(repoRoot, targetDir, 'index.md')) ||
|
|
66
|
+
declared?.config) {
|
|
67
|
+
throw new Error('Existing documentation config is incompatible with Markdown setup. Preserve it and choose adoption or framework replacement explicitly.');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function isMissing(error) {
|
|
71
|
+
return error instanceof Error && 'code' in error && error.code === 'ENOENT';
|
|
72
|
+
}
|
|
73
|
+
async function readBaseline(repoRoot, path) {
|
|
74
|
+
try {
|
|
75
|
+
await lstat(path);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
if (isMissing(error))
|
|
79
|
+
return null;
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
// Existing symlinks are preserved, but may not point outside the repository.
|
|
83
|
+
validatePathWithinScope(await realpath(path), await realpath(repoRoot));
|
|
84
|
+
if (!(await stat(path)).isFile())
|
|
85
|
+
throw new Error(`Markdown baseline entrypoint must be a readable file: ${path}`);
|
|
86
|
+
return readFile(path, 'utf8');
|
|
87
|
+
}
|
|
88
|
+
function indexAdvice(content) {
|
|
89
|
+
const advice = [];
|
|
90
|
+
try {
|
|
91
|
+
const frontmatter = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(content);
|
|
92
|
+
const metadata = frontmatter
|
|
93
|
+
? parseYaml(frontmatter[1])
|
|
94
|
+
: null;
|
|
95
|
+
if (!metadata ||
|
|
96
|
+
typeof metadata.title !== 'string' ||
|
|
97
|
+
!metadata.title.trim() ||
|
|
98
|
+
typeof metadata.description !== 'string' ||
|
|
99
|
+
!metadata.description.trim()) {
|
|
100
|
+
advice.push('Existing index.md needs title/description metadata; it was preserved.');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
advice.push('Existing index.md has malformed metadata; it was preserved.');
|
|
105
|
+
}
|
|
106
|
+
const contents = /^## Contents\s*\r?\n([\s\S]*?)(?=^## |$(?![\s\S]))/m.exec(content)?.[1];
|
|
107
|
+
if (!contents || !/\[[^\]]+\]\([^)]+\.md(?:#[^)]*)?\)/.test(contents)) {
|
|
108
|
+
advice.push('Existing index.md needs a populated Contents map; it was preserved.');
|
|
109
|
+
}
|
|
110
|
+
return advice;
|
|
111
|
+
}
|
|
112
|
+
function link(label, path) {
|
|
113
|
+
const safeLabel = label.replace(/[\r\n]/g, ' ').replace(/([[\]\\])/g, '\\$1');
|
|
114
|
+
const href = path
|
|
115
|
+
.split('/')
|
|
116
|
+
.map((segment) => encodeURIComponent(segment).replaceAll('(', '%28').replaceAll(')', '%29'))
|
|
117
|
+
.join('/');
|
|
118
|
+
return `- [${safeLabel}](${href})`;
|
|
119
|
+
}
|
|
120
|
+
async function buildContents(repoRoot, appRoot, excludes, advice) {
|
|
121
|
+
const matcher = createExclusionMatcher(excludes);
|
|
122
|
+
const entries = ((await dirExists(appRoot))
|
|
123
|
+
? await readdir(appRoot, { withFileTypes: true })
|
|
124
|
+
: []).sort((a, b) => a.name.localeCompare(b.name, 'en'));
|
|
125
|
+
const links = [];
|
|
126
|
+
async function hasMarkdown(directory, prefix) {
|
|
127
|
+
let children;
|
|
128
|
+
try {
|
|
129
|
+
children = await readdir(directory, { withFileTypes: true });
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
if (error instanceof Error &&
|
|
133
|
+
'code' in error &&
|
|
134
|
+
(error.code === 'EACCES' || error.code === 'EPERM')) {
|
|
135
|
+
advice.push(`${prefix}/ could not be inspected due to permissions; it was preserved. Run oat-docs-analyze for repair recommendations.`);
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
for (const entry of children) {
|
|
141
|
+
const path = `${prefix}/${entry.name}`;
|
|
142
|
+
if (entry.isFile() &&
|
|
143
|
+
/\.md$/i.test(entry.name) &&
|
|
144
|
+
!['AGENTS.md', 'CLAUDE.md'].includes(entry.name) &&
|
|
145
|
+
!matcher.excludesFile(path))
|
|
146
|
+
return true;
|
|
147
|
+
if (entry.isDirectory() &&
|
|
148
|
+
!matcher.excludesDirectory(path) &&
|
|
149
|
+
(await hasMarkdown(join(directory, entry.name), path)))
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
for (const entry of entries) {
|
|
155
|
+
if (entry.isFile() &&
|
|
156
|
+
/\.md$/i.test(entry.name) &&
|
|
157
|
+
!['index.md', 'contributing.md', 'AGENTS.md', 'CLAUDE.md'].includes(entry.name) &&
|
|
158
|
+
!matcher.excludesFile(entry.name)) {
|
|
159
|
+
links.push(link(humanizeAppName(entry.name.replace(/\.md$/i, '')), entry.name));
|
|
160
|
+
}
|
|
161
|
+
else if (entry.isDirectory() && !matcher.excludesDirectory(entry.name)) {
|
|
162
|
+
const indexPath = `${entry.name}/index.md`;
|
|
163
|
+
if (matcher.excludesFile(indexPath))
|
|
164
|
+
continue;
|
|
165
|
+
let index;
|
|
166
|
+
try {
|
|
167
|
+
index = await readBaseline(repoRoot, join(appRoot, indexPath));
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
advice.push(`${indexPath} is not a usable in-repository file; it was preserved and omitted from Contents. Run oat-docs-analyze for repair recommendations.`);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (index !== null) {
|
|
174
|
+
links.push(link(humanizeAppName(entry.name), indexPath));
|
|
175
|
+
}
|
|
176
|
+
else if (await hasMarkdown(join(appRoot, entry.name), entry.name)) {
|
|
177
|
+
advice.push(`${entry.name}/ has Markdown but no authored index.md; run oat-docs-analyze for repair recommendations.`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (!matcher.excludesFile('contributing.md'))
|
|
182
|
+
links.push(link('Contributing', 'contributing.md'));
|
|
183
|
+
return links.join('\n');
|
|
184
|
+
}
|
|
185
|
+
/** Read-only inspection used by both adoption and CLI dry-run. */
|
|
186
|
+
export async function planMarkdownDocs(options) {
|
|
187
|
+
const targetDir = await validateMarkdownTarget(options.repoRoot, options.targetDir);
|
|
188
|
+
validateMarkdownConfig(options.repoRoot, targetDir, options.documentation);
|
|
189
|
+
const appRoot = resolve(options.repoRoot, targetDir);
|
|
190
|
+
if (!options.adopt &&
|
|
191
|
+
(await dirExists(appRoot)) &&
|
|
192
|
+
(await readdir(appRoot)).length > 0) {
|
|
193
|
+
throw new Error(`Markdown docs target is not empty: ${targetDir}. Use --adopt to preserve existing docs, then run oat-docs-analyze for content gaps.`);
|
|
194
|
+
}
|
|
195
|
+
const documentationConfig = {
|
|
196
|
+
...options.documentation,
|
|
197
|
+
root: targetDir,
|
|
198
|
+
tooling: 'markdown',
|
|
199
|
+
index: `${targetDir}/index.md`,
|
|
200
|
+
};
|
|
201
|
+
const configChanged = options.documentation?.root !== targetDir ||
|
|
202
|
+
options.documentation?.tooling !== 'markdown' ||
|
|
203
|
+
options.documentation?.index !== `${targetDir}/index.md`;
|
|
204
|
+
const existing = new Map();
|
|
205
|
+
for (const name of ['index.md', 'contributing.md'])
|
|
206
|
+
existing.set(name, await readBaseline(options.repoRoot, join(appRoot, name)));
|
|
207
|
+
const auditAdvice = [];
|
|
208
|
+
const index = existing.get('index.md');
|
|
209
|
+
if (index !== null && index !== undefined)
|
|
210
|
+
auditAdvice.push(...indexAdvice(index));
|
|
211
|
+
const contents = index == null
|
|
212
|
+
? await buildContents(options.repoRoot, appRoot, options.documentation?.excludes ?? [], auditAdvice)
|
|
213
|
+
: '';
|
|
214
|
+
if (options.adopt)
|
|
215
|
+
auditAdvice.push('Adoption preserves existing content. Run oat-docs-analyze to audit context, metadata, navigation, and local instructions; use oat-docs-apply for approved repairs.');
|
|
216
|
+
const files = [];
|
|
217
|
+
const preservedFiles = [];
|
|
218
|
+
for (const [name, content] of existing) {
|
|
219
|
+
if (content !== null) {
|
|
220
|
+
preservedFiles.push(name);
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
const template = await readFile(join(options.assetsRoot, 'templates', 'docs-markdown', name), 'utf8');
|
|
224
|
+
files.push({
|
|
225
|
+
name,
|
|
226
|
+
content: renderMarkdownTemplate(template, options, contents),
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
repoRoot: options.repoRoot,
|
|
231
|
+
targetDir,
|
|
232
|
+
appRoot,
|
|
233
|
+
files,
|
|
234
|
+
preservedFiles,
|
|
235
|
+
auditAdvice,
|
|
236
|
+
documentationConfig,
|
|
237
|
+
configChanged,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
/** Adds missing baseline files exclusively; config is persisted by the caller. */
|
|
241
|
+
export async function applyMarkdownDocsPlan(plan) {
|
|
242
|
+
const result = {
|
|
243
|
+
appRoot: plan.appRoot,
|
|
244
|
+
createdFiles: [],
|
|
245
|
+
incompleteFiles: [],
|
|
246
|
+
preservedFiles: [...plan.preservedFiles],
|
|
247
|
+
auditAdvice: plan.auditAdvice,
|
|
248
|
+
documentationConfig: plan.documentationConfig,
|
|
249
|
+
};
|
|
250
|
+
try {
|
|
251
|
+
await validateMarkdownTarget(plan.repoRoot, plan.targetDir);
|
|
252
|
+
if (plan.files.length)
|
|
253
|
+
await ensureDir(plan.appRoot);
|
|
254
|
+
for (const { name, content } of plan.files) {
|
|
255
|
+
await validateMarkdownTarget(plan.repoRoot, plan.targetDir);
|
|
256
|
+
const path = join(plan.appRoot, name);
|
|
257
|
+
let handle;
|
|
258
|
+
try {
|
|
259
|
+
handle = await open(path, 'wx');
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
if (error instanceof Error &&
|
|
263
|
+
'code' in error &&
|
|
264
|
+
error.code === 'EEXIST' &&
|
|
265
|
+
(await readBaseline(plan.repoRoot, join(plan.appRoot, name))) !== null) {
|
|
266
|
+
result.preservedFiles.push(name);
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
else
|
|
270
|
+
throw error;
|
|
271
|
+
}
|
|
272
|
+
let createdIdentity;
|
|
273
|
+
try {
|
|
274
|
+
createdIdentity = await handle.stat();
|
|
275
|
+
await handle.writeFile(content, 'utf8');
|
|
276
|
+
await handle.close();
|
|
277
|
+
result.createdFiles.push(name);
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
try {
|
|
281
|
+
await handle.close();
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
// Preserve the original write/close error while attempting cleanup.
|
|
285
|
+
}
|
|
286
|
+
try {
|
|
287
|
+
await validateMarkdownTarget(plan.repoRoot, plan.targetDir);
|
|
288
|
+
const current = await lstat(path);
|
|
289
|
+
if (!createdIdentity ||
|
|
290
|
+
!current.isFile() ||
|
|
291
|
+
current.dev !== createdIdentity.dev ||
|
|
292
|
+
current.ino !== createdIdentity.ino) {
|
|
293
|
+
throw new Error('Baseline file identity changed during creation.', {
|
|
294
|
+
cause: error,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
await unlink(path);
|
|
298
|
+
}
|
|
299
|
+
catch (cleanupError) {
|
|
300
|
+
if (!isMissing(cleanupError)) {
|
|
301
|
+
result.incompleteFiles.push(name);
|
|
302
|
+
result.auditAdvice.push(`${name} could not be safely cleaned up after a failed write; inspect or repair it before retrying. Adoption preserves existing files.`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
throw error;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
if ((await readBaseline(plan.repoRoot, join(plan.appRoot, 'index.md'))) ===
|
|
309
|
+
null)
|
|
310
|
+
throw new Error('Authored Markdown index is unavailable; configuration was not persisted.');
|
|
311
|
+
return result;
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
315
|
+
throw new MarkdownDocsWriteError(message, result, error);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { PromptContext, SelectChoice } from '../../shared/shared.prompts.js';
|
|
2
2
|
export type DocsRepoShape = 'monorepo' | 'single-package' | 'nested-standalone';
|
|
3
3
|
export type DocsDetectedRepoShape = Exclude<DocsRepoShape, 'nested-standalone'>;
|
|
4
|
-
export type DocsFramework = 'fumadocs' | 'mkdocs';
|
|
4
|
+
export type DocsFramework = 'fumadocs' | 'mkdocs' | 'markdown';
|
|
5
5
|
export type DocsLintMode = 'none' | 'markdownlint-cli2';
|
|
6
6
|
export type DocsFormatMode = 'oxfmt' | 'none';
|
|
7
7
|
export interface DocsInitResolvedOptions {
|
|
@@ -15,6 +15,7 @@ export interface DocsInitResolvedOptions {
|
|
|
15
15
|
lint: DocsLintMode;
|
|
16
16
|
format: DocsFormatMode;
|
|
17
17
|
rootPatch: boolean;
|
|
18
|
+
adopt?: boolean;
|
|
18
19
|
}
|
|
19
20
|
export interface ResolveDocsInitOptionsInput {
|
|
20
21
|
repoRoot: string;
|
|
@@ -22,6 +23,7 @@ export interface ResolveDocsInitOptionsInput {
|
|
|
22
23
|
interactive: boolean;
|
|
23
24
|
acceptDefaults: boolean;
|
|
24
25
|
providedFramework?: DocsFramework;
|
|
26
|
+
providedAdopt?: boolean;
|
|
25
27
|
providedAppName?: string;
|
|
26
28
|
providedSiteName?: string;
|
|
27
29
|
providedTargetDir?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolve-options.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/init/resolve-options.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACb,MAAM,iCAAiC,CAAC;AAGzC,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,gBAAgB,GAAG,mBAAmB,CAAC;AAChF,MAAM,MAAM,qBAAqB,GAAG,OAAO,CAAC,aAAa,EAAE,mBAAmB,CAAC,CAAC;AAChF,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"resolve-options.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/init/resolve-options.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@ import { dirExists, fileExists } from '../../../fs/io.js';
|
|
|
3
3
|
const FRAMEWORK_CHOICES = [
|
|
4
4
|
{ label: 'Fumadocs (Next.js + MDX, static export)', value: 'fumadocs' },
|
|
5
5
|
{ label: 'MkDocs (Python, Material theme)', value: 'mkdocs' },
|
|
6
|
+
{ label: 'Plain Markdown (authored pages, no site app)', value: 'markdown' },
|
|
6
7
|
];
|
|
7
8
|
const LINT_CHOICES = [
|
|
8
9
|
{ label: 'none', value: 'none' },
|
|
@@ -62,6 +63,8 @@ export async function detectDocsRepoShape(repoRoot, dependencies) {
|
|
|
62
63
|
return 'single-package';
|
|
63
64
|
}
|
|
64
65
|
export function getTemplateDir(framework) {
|
|
66
|
+
if (framework === 'markdown')
|
|
67
|
+
return 'docs-markdown';
|
|
65
68
|
return framework === 'fumadocs' ? 'docs-app-fuma' : 'docs-app-mkdocs';
|
|
66
69
|
}
|
|
67
70
|
export function resolveEffectiveDocsShape(repoShape, framework, repoRoot, targetDir) {
|
|
@@ -88,6 +91,39 @@ export async function resolveDocsInitOptions(input) {
|
|
|
88
91
|
if (!framework) {
|
|
89
92
|
return null;
|
|
90
93
|
}
|
|
94
|
+
if (framework === 'markdown') {
|
|
95
|
+
const defaultSiteName = humanizeAppName(basename(input.repoRoot)).trim() || 'Documentation';
|
|
96
|
+
const requestedSiteName = input.providedSiteName ??
|
|
97
|
+
(input.interactive && !input.acceptDefaults
|
|
98
|
+
? await input.inputWithDefault('Documentation title', defaultSiteName, ctx)
|
|
99
|
+
: defaultSiteName);
|
|
100
|
+
if (requestedSiteName === null)
|
|
101
|
+
return null;
|
|
102
|
+
const siteName = requestedSiteName.trim()
|
|
103
|
+
? requestedSiteName
|
|
104
|
+
: defaultSiteName;
|
|
105
|
+
const targetDir = input.providedTargetDir?.trim() ||
|
|
106
|
+
(input.interactive && !input.acceptDefaults
|
|
107
|
+
? await input.inputWithDefault('Documentation directory', 'docs', ctx)
|
|
108
|
+
: 'docs');
|
|
109
|
+
if (!targetDir)
|
|
110
|
+
return null;
|
|
111
|
+
return {
|
|
112
|
+
repoRoot: input.repoRoot,
|
|
113
|
+
repoShape: input.repoShape,
|
|
114
|
+
framework,
|
|
115
|
+
appName: 'docs',
|
|
116
|
+
siteName,
|
|
117
|
+
targetDir,
|
|
118
|
+
siteDescription: input.providedSiteDescription?.trim()
|
|
119
|
+
? input.providedSiteDescription
|
|
120
|
+
: `Documentation for ${basename(input.repoRoot).trim() || 'this repository'}.`,
|
|
121
|
+
lint: input.providedLint ?? 'none',
|
|
122
|
+
format: input.providedFormat ?? 'none',
|
|
123
|
+
rootPatch: false,
|
|
124
|
+
adopt: input.providedAdopt ?? false,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
91
127
|
const defaultAppName = getDefaultDocsAppName(input.repoRoot, input.repoShape);
|
|
92
128
|
const appName = input.providedAppName?.trim() ||
|
|
93
129
|
(input.interactive && !input.acceptDefaults
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/init/scaffold.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../../../src/commands/docs/init/scaffold.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAMjE,OAAO,KAAK,EAEV,aAAa,EACb,uBAAuB,EAExB,MAAM,mBAAmB,CAAC;AAsF3B,MAAM,WAAW,sBAAuB,SAAQ,uBAAuB;IACrE,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,mBAAmB,EAAE,sBAAsB,CAAC;CAC7C;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC5C;AAED,KAAK,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;AAEtC,MAAM,WAAW,2BAA2B;IAC1C,iBAAiB,EAAE,MAAM,YAAY,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACjE,0BAA0B,EAAE,CAC1B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,KACZ,YAAY,CAAC,OAAO,CAAC,CAAC;CAC5B;AAUD,eAAO,MAAM,aAAa,YAAY,CAAC;AAEvC,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOxE;AAgDD,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,aAAa,CAAC,CAaxB;AAiMD;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,aAAa,EACxB,SAAS,EAAE,MAAM,GAChB,sBAAsB,CAsBxB;AAaD,wBAAsB,eAAe,CACnC,OAAO,EAAE,sBAAsB,EAC/B,SAAS,GAAE,OAAO,CAAC,2BAA2B,CAAM,GACnD,OAAO,CAAC,qBAAqB,CAAC,CAqDhC"}
|
|
@@ -3,6 +3,7 @@ import { basename, dirname, isAbsolute, join, relative, resolve, } from 'node:pa
|
|
|
3
3
|
import { dirExists, ensureDir, fileExists } from '../../../fs/io.js';
|
|
4
4
|
import { OAT_VERSION } from '../../../shared/oat-version.js';
|
|
5
5
|
import { buildDocsCommands } from './docs-commands.js';
|
|
6
|
+
import { applyMarkdownDocsPlan, planMarkdownDocs } from './markdown.js';
|
|
6
7
|
import { getTemplateDir } from './resolve-options.js';
|
|
7
8
|
const MKDOCS_TEMPLATE_FILES = [
|
|
8
9
|
{ source: '.gitignore', destination: '.gitignore' },
|
|
@@ -266,6 +267,13 @@ function renderTemplate(template, options, depContext, packageManagerField) {
|
|
|
266
267
|
* pointing at the nav/config YAML, which is that framework's entry point.
|
|
267
268
|
*/
|
|
268
269
|
export function buildDocumentationConfig(framework, targetDir) {
|
|
270
|
+
if (framework === 'markdown') {
|
|
271
|
+
return {
|
|
272
|
+
root: targetDir,
|
|
273
|
+
tooling: 'markdown',
|
|
274
|
+
index: join(targetDir, 'index.md'),
|
|
275
|
+
};
|
|
276
|
+
}
|
|
269
277
|
if (framework === 'fumadocs') {
|
|
270
278
|
return {
|
|
271
279
|
root: targetDir,
|
|
@@ -290,6 +298,9 @@ async function ensureTargetWritable(appRoot) {
|
|
|
290
298
|
}
|
|
291
299
|
}
|
|
292
300
|
export async function scaffoldDocsApp(options, overrides = {}) {
|
|
301
|
+
if (options.framework === 'markdown') {
|
|
302
|
+
return applyMarkdownDocsPlan(await planMarkdownDocs(options));
|
|
303
|
+
}
|
|
293
304
|
const appRoot = join(options.repoRoot, options.targetDir);
|
|
294
305
|
const templateDir = getTemplateDir(options.framework);
|
|
295
306
|
const templateRoot = join(options.assetsRoot, 'templates', templateDir);
|
|
@@ -61,6 +61,13 @@ export declare function formatAgentsMdGuidanceResult(result: UpsertSectionResult
|
|
|
61
61
|
* hanging; it has no effect on regular-file writes.
|
|
62
62
|
*/
|
|
63
63
|
export declare const AGENTS_MD_APPEND_FLAGS: number;
|
|
64
|
+
/**
|
|
65
|
+
* Predict managed guidance actions from current bytes without writing. This
|
|
66
|
+
* advisory result does not establish write permission or guarantee a later
|
|
67
|
+
* upsert; mutation independently rechecks path identity and opened handles.
|
|
68
|
+
*/
|
|
69
|
+
export declare function previewAgentsMdSections(repoRoot: string, sections: readonly AgentsMdSectionInput[], options?: Pick<AgentsMdMutationOptions, 'fileSystem'>): Promise<Record<string, UpsertSectionResult>>;
|
|
70
|
+
export declare function previewAgentsMdSection(repoRoot: string, key: string, body: string, options?: AgentsMdMutationOptions): Promise<UpsertSectionResult>;
|
|
64
71
|
export declare function upsertAgentsMdSections(repoRoot: string, sections: readonly AgentsMdSectionInput[], options?: Pick<AgentsMdMutationOptions, 'fileSystem'>): Promise<Record<string, UpsertSectionResult>>;
|
|
65
72
|
export declare function upsertAgentsMdSection(repoRoot: string, key: string, body: string, options?: AgentsMdMutationOptions): Promise<UpsertSectionResult>;
|
|
66
73
|
export declare function removeAgentsMdSection(repoRoot: string, key: string, options?: Pick<AgentsMdMutationOptions, 'fileSystem'>): Promise<boolean | 'manual-required' | 'blocked'>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agents-md.d.ts","sourceRoot":"","sources":["../../../src/commands/shared/agents-md.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,SAAS,EACV,MAAM,kBAAkB,CAAC;AAG1B;;;;;;;;GAQG;AAEH,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,OAAO,KAAK,CAAC;IACpB,IAAI,EAAE,OAAO,IAAI,CAAC;IAClB,QAAQ,EAAE,OAAO,QAAQ,CAAC;IAC1B,QAAQ,EAAE,OAAO,QAAQ,CAAC;IAC1B,QAAQ,EAAE,OAAO,QAAQ,CAAC;IAC1B,SAAS,EAAE,OAAO,SAAS,CAAC;CAC7B;AAED,MAAM,WAAW,uBAAuB;IACtC,UAAU,CAAC,EAAE,kBAAkB,CAAC;IAChC,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC;AAmCD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,UAAU,GAAG,iBAAiB,CAAC;IAClD,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,SAAS,GAAG,UAAU,GAAG,WAAW,GAAG,iBAAiB,GAAG,SAAS,CAAC;IAC7E,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAClC,OAAO,CAAC,EAAE,eAAe,CAAC;CAC3B;AAED,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAcD;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3E;AAySD,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAGpE;AAED,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,mBAAmB,GAC1B,SAAS,MAAM,EAAE,CAoBnB;AAmDD;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,QAIb,CAAC;
|
|
1
|
+
{"version":3,"file":"agents-md.d.ts","sourceRoot":"","sources":["../../../src/commands/shared/agents-md.ts"],"names":[],"mappings":"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"}
|