@guiho/xdocs 0.3.0-alpha.2 → 0.4.2
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/CHANGELOG.md +20 -0
- package/DOCS.md +70 -36
- package/README.md +57 -22
- package/docs/2026-07-05-xdocs-document-model.md +77 -0
- package/docs/docs.xdocs.md +22 -0
- package/jsr.json +1 -1
- package/library/agents.d.ts +4 -5
- package/library/agents.d.ts.map +1 -1
- package/library/agents.js +15 -14
- package/library/cli.d.ts.map +1 -1
- package/library/cli.js +5 -4
- package/library/commands/generate.d.ts.map +1 -1
- package/library/commands/generate.js +23 -1
- package/library/commands/list.d.ts.map +1 -1
- package/library/commands/list.js +14 -4
- package/library/commands/merge.d.ts.map +1 -1
- package/library/commands/merge.js +12 -1
- package/library/commands/scan.d.ts.map +1 -1
- package/library/commands/scan.js +13 -2
- package/library/config.d.ts.map +1 -1
- package/library/config.js +7 -3
- package/library/discovery.d.ts +8 -4
- package/library/discovery.d.ts.map +1 -1
- package/library/discovery.js +107 -17
- package/library/flags.d.ts +1 -1
- package/library/flags.js +1 -1
- package/library/guiho-xdocs.d.ts +2 -2
- package/library/guiho-xdocs.d.ts.map +1 -1
- package/library/guiho-xdocs.js +1 -1
- package/library/help.js +16 -15
- package/library/metadata.d.ts +1 -1
- package/library/metadata.d.ts.map +1 -1
- package/library/metadata.js +23 -3
- package/library/tree.d.ts +1 -1
- package/library/tree.d.ts.map +1 -1
- package/library/tree.js +2 -2
- package/library/types.d.ts +14 -2
- package/library/types.d.ts.map +1 -1
- package/package.json +6 -3
- package/prompts/agents.md +9 -2
- package/prompts/generate.md +10 -2
- package/prompts/prompts.xdocs.md +27 -0
- package/prompts/update.md +19 -7
- package/prompts/write.md +18 -5
- package/scripts/install-package.ts +88 -0
- package/scripts/scripts.xdocs.md +12 -5
- package/scripts/xdocs-bin.ts +33 -0
- package/skills/guiho-s-xdocs/SKILL.md +135 -34
- package/skills/guiho-s-xdocs/guiho-s-xdocs.xdocs.md +26 -0
- package/skills/skills.xdocs.md +9 -4
- package/scripts/install-native.cjs +0 -101
package/library/agents.d.ts
CHANGED
|
@@ -38,7 +38,7 @@ export declare const detectAgentTools: (cwd: string) => XDocsAgentTool[];
|
|
|
38
38
|
*/
|
|
39
39
|
export declare const resolveInstallTools: (cwd: string, toolFlag: string | undefined) => XDocsAgentTool[];
|
|
40
40
|
/** The small AGENTS.md section announcing xdocs and pointing to the skill. */
|
|
41
|
-
export declare const xdocsAgentsSection = "<!-- BEGIN XDOCS \u2014 DO NOT EDIT THIS SECTION -->\n## XDocs Structured Documentation\n\nThis project uses **xdocs** (`@guiho/xdocs`) for structured, machine-readable\ndocumentation. The repository has one root `XDOCS.md` index (no frontmatter),\nand each package/application has a root
|
|
41
|
+
export declare const xdocsAgentsSection = "<!-- BEGIN XDOCS \u2014 DO NOT EDIT THIS SECTION -->\n## XDocs Structured Documentation\n\nThis project uses **xdocs** (`@guiho/xdocs`) for structured, machine-readable\ndocumentation. The repository has one root `XDOCS.md` index (no frontmatter),\nand each package/application has a root named `*.xdocs.md` descriptor file. Each\ndocumented module has exactly one named `*.xdocs.md` descriptor in its directory\nwith YAML frontmatter (`subject`, `description`, `parent`, `children`,\n`files`, `documents`, `tags`, `keywords`, `flags`). Same-directory plain\n`*.md` files are companion documents and must be listed in the descriptor's\n`documents` metadata map. Ordinary companion documents should also include\n`keywords` in their own frontmatter.\n\n**Load the `guiho-s-xdocs` agent skill** for any documentation work:\ncreating, updating, regenerating, scanning, merging, or navigating xdocs descriptors.\nThe skill holds the full workflow, metadata schema, and CLI reference.\n\nBefore changing documentation, read `xdocs.config.toml` and respect `[ai].mode`:\n\n- **prompt** \u2014 announce which xdocs descriptors need updating and wait for confirmation.\n- **auto** \u2014 update the relevant xdocs descriptors immediately.\n\nUse the xdocs CLI for operations: `xdocs scan`, `xdocs tree`, `xdocs generate`,\n`xdocs list`, `xdocs merge`.\n<!-- END XDOCS -->";
|
|
42
42
|
type SkillPathOptions = {
|
|
43
43
|
cwd?: string;
|
|
44
44
|
homeDirectory?: string;
|
|
@@ -70,10 +70,9 @@ export declare const findAgentsFile: (cwd: string) => string | undefined;
|
|
|
70
70
|
/** Resolve agent settings from the discovered xdocs config (defaults when absent). */
|
|
71
71
|
export declare const resolveAgentSettings: (options: XDocsCliOptions) => Promise<XDocsAgentSettings>;
|
|
72
72
|
/**
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* the configured tool from the bundled package copy.
|
|
73
|
+
* Agent automation run by bare and data commands. It always bootstraps the
|
|
74
|
+
* configured global skill from the bundled package copy; when config exists,
|
|
75
|
+
* it also keeps the AGENTS.md section fresh if enabled and AGENTS.md exists.
|
|
77
76
|
*/
|
|
78
77
|
export declare const runAgentAutomation: (options: AgentAutomationOptions, notify?: (message: string) => void) => Promise<XDocsAgentAutomationResult>;
|
|
79
78
|
/** Read a skill version from SKILL.md YAML frontmatter. */
|
package/library/agents.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../source/agents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH,OAAO,KAAK,EACV,0BAA0B,EAC1B,kBAAkB,EAClB,cAAc,EACd,6BAA6B,EAC7B,eAAe,EACf,uBAAuB,EACvB,eAAe,EAChB,MAAM,YAAY,CAAA;AAInB,+CAA+C;AAC/C,eAAO,MAAM,cAAc,kBAAkB,CAAA;AAE7C,0EAA0E;AAC1E,eAAO,MAAM,qBAAqB,EAAE,SAAS,MAAM,EAAuB,CAAA;AAE1E;;iEAEiE;AACjE,eAAO,MAAM,iBAAiB,EAAE,MAS5B,CAAA;AAEJ,yDAAyD;AACzD,eAAO,MAAM,iBAAiB,oBAAsC,CAAA;AAEpE,6EAA6E;AAC7E,eAAO,MAAM,eAAe,EAAE,SAAS,cAAc,EAAyB,CAAA;AAE9E,8EAA8E;AAC9E,eAAO,MAAM,iBAAiB,EAAE,cAAyB,CAAA;AAEzD,2FAA2F;AAC3F,eAAO,MAAM,eAAe,GAAI,OAAO,MAAM,GAAG,SAAS,KAAG,cAAc,EAKzE,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,KAAK,MAAM,KAAG,cAAc,EAK5D,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAAI,KAAK,MAAM,EAAE,UAAU,MAAM,GAAG,SAAS,KAAG,cAAc,EAChC,CAAA;AAkB9D,8EAA8E;AAC9E,eAAO,MAAM,kBAAkB,
|
|
1
|
+
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../source/agents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH,OAAO,KAAK,EACV,0BAA0B,EAC1B,kBAAkB,EAClB,cAAc,EACd,6BAA6B,EAC7B,eAAe,EACf,uBAAuB,EACvB,eAAe,EAChB,MAAM,YAAY,CAAA;AAInB,+CAA+C;AAC/C,eAAO,MAAM,cAAc,kBAAkB,CAAA;AAE7C,0EAA0E;AAC1E,eAAO,MAAM,qBAAqB,EAAE,SAAS,MAAM,EAAuB,CAAA;AAE1E;;iEAEiE;AACjE,eAAO,MAAM,iBAAiB,EAAE,MAS5B,CAAA;AAEJ,yDAAyD;AACzD,eAAO,MAAM,iBAAiB,oBAAsC,CAAA;AAEpE,6EAA6E;AAC7E,eAAO,MAAM,eAAe,EAAE,SAAS,cAAc,EAAyB,CAAA;AAE9E,8EAA8E;AAC9E,eAAO,MAAM,iBAAiB,EAAE,cAAyB,CAAA;AAEzD,2FAA2F;AAC3F,eAAO,MAAM,eAAe,GAAI,OAAO,MAAM,GAAG,SAAS,KAAG,cAAc,EAKzE,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,KAAK,MAAM,KAAG,cAAc,EAK5D,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAAI,KAAK,MAAM,EAAE,UAAU,MAAM,GAAG,SAAS,KAAG,cAAc,EAChC,CAAA;AAkB9D,8EAA8E;AAC9E,eAAO,MAAM,kBAAkB,w1CAwBV,CAAA;AAErB,KAAK,gBAAgB,GAAG;IACtB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAED,KAAK,mBAAmB,GAAG,gBAAgB,GAAG;IAC5C,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB,CAAA;AAED,KAAK,sBAAsB,GAAG,eAAe,GAAG;IAC9C,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAED,uEAAuE;AACvE,eAAO,MAAM,gBAAgB,GAAI,MAAM,cAAc,EAAE,OAAO,eAAe,EAAE,UAAS,gBAAqB,KAAG,MACnD,CAAA;AAO7D,mEAAmE;AACnE,eAAO,MAAM,gBAAgB,GAAI,MAAM,cAAc,EAAE,OAAO,eAAe,EAAE,UAAS,gBAAqB,KAAG,OAC5D,CAAA;AAEpD,kEAAkE;AAClE,eAAO,MAAM,YAAY,GACvB,MAAM,cAAc,EACpB,OAAO,eAAe,EACtB,UAAS,mBAAwB,KAChC,OAAO,CAAC,uBAAuB,CA6BjC,CAAA;AAED,mDAAmD;AACnD,eAAO,MAAM,aAAa,GACxB,OAAO,SAAS,cAAc,EAAE,EAChC,OAAO,eAAe,EACtB,UAAS,mBAAwB,KAChC,OAAO,CAAC,uBAAuB,EAAE,CAInC,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,GAAU,KAAK,MAAM,EAAE,gBAAc,KAAG,OAAO,CAAC,6BAA6B,CAkBjH,CAAA;AA4BD,sDAAsD;AACtD,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,KAAG,MAAM,GAAG,SAWrD,CAAA;AAED,sFAAsF;AACtF,eAAO,MAAM,oBAAoB,GAAU,SAAS,eAAe,KAAG,OAAO,CAAC,kBAAkB,CAI/F,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAC7B,SAAS,sBAAsB,EAC/B,SAAQ,CAAC,OAAO,EAAE,MAAM,KAAK,IAAe,KAC3C,OAAO,CAAC,0BAA0B,CAgBpC,CAAA;AAED,2DAA2D;AAC3D,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEpE"}
|
package/library/agents.js
CHANGED
|
@@ -86,18 +86,22 @@ export const xdocsAgentsSection = `${AGENTS_BEGIN_MARKER}
|
|
|
86
86
|
|
|
87
87
|
This project uses **xdocs** (\`@guiho/xdocs\`) for structured, machine-readable
|
|
88
88
|
documentation. The repository has one root \`XDOCS.md\` index (no frontmatter),
|
|
89
|
-
and each package/application has a root
|
|
90
|
-
|
|
91
|
-
\`
|
|
89
|
+
and each package/application has a root named \`*.xdocs.md\` descriptor file. Each
|
|
90
|
+
documented module has exactly one named \`*.xdocs.md\` descriptor in its directory
|
|
91
|
+
with YAML frontmatter (\`subject\`, \`description\`, \`parent\`, \`children\`,
|
|
92
|
+
\`files\`, \`documents\`, \`tags\`, \`keywords\`, \`flags\`). Same-directory plain
|
|
93
|
+
\`*.md\` files are companion documents and must be listed in the descriptor's
|
|
94
|
+
\`documents\` metadata map. Ordinary companion documents should also include
|
|
95
|
+
\`keywords\` in their own frontmatter.
|
|
92
96
|
|
|
93
97
|
**Load the \`${xdocsSkillName}\` agent skill** for any documentation work:
|
|
94
|
-
creating, updating, regenerating, scanning, merging, or navigating xdocs
|
|
98
|
+
creating, updating, regenerating, scanning, merging, or navigating xdocs descriptors.
|
|
95
99
|
The skill holds the full workflow, metadata schema, and CLI reference.
|
|
96
100
|
|
|
97
101
|
Before changing documentation, read \`xdocs.config.toml\` and respect \`[ai].mode\`:
|
|
98
102
|
|
|
99
|
-
- **prompt** — announce which xdocs
|
|
100
|
-
- **auto** — update the relevant xdocs
|
|
103
|
+
- **prompt** — announce which xdocs descriptors need updating and wait for confirmation.
|
|
104
|
+
- **auto** — update the relevant xdocs descriptors immediately.
|
|
101
105
|
|
|
102
106
|
Use the xdocs CLI for operations: \`xdocs scan\`, \`xdocs tree\`, \`xdocs generate\`,
|
|
103
107
|
\`xdocs list\`, \`xdocs merge\`.
|
|
@@ -208,19 +212,16 @@ export const resolveAgentSettings = async (options) => {
|
|
|
208
212
|
return normalizeAgentSettings(discovered.raw?.agents);
|
|
209
213
|
};
|
|
210
214
|
/**
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
* the configured tool from the bundled package copy.
|
|
215
|
+
* Agent automation run by bare and data commands. It always bootstraps the
|
|
216
|
+
* configured global skill from the bundled package copy; when config exists,
|
|
217
|
+
* it also keeps the AGENTS.md section fresh if enabled and AGENTS.md exists.
|
|
215
218
|
*/
|
|
216
219
|
export const runAgentAutomation = async (options, notify = () => { }) => {
|
|
217
220
|
const cwd = resolve(options.cwd);
|
|
218
221
|
const discovered = await discoverConfig(cwd, options.config);
|
|
219
|
-
|
|
220
|
-
return { settings: { ...normalizeAgentSettings(undefined) } };
|
|
221
|
-
const settings = normalizeAgentSettings(discovered.raw.agents);
|
|
222
|
+
const settings = normalizeAgentSettings(discovered.raw?.agents);
|
|
222
223
|
const result = { settings };
|
|
223
|
-
if (settings.autoAgentsMd)
|
|
224
|
+
if (discovered.raw && settings.autoAgentsMd)
|
|
224
225
|
result.agentsMd = await ensureAgentsInstructions(cwd, false);
|
|
225
226
|
if (settings.autoSkillInstall) {
|
|
226
227
|
const globalSkill = await installSkill(settings.skillTool, 'global', { cwd, homeDirectory: options.homeDirectory });
|
package/library/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../source/cli.ts"],"names":[],"mappings":"AAAA;;GAEG;AAsBH,4BAA4B;AAC5B,eAAO,MAAM,MAAM,GAAU,UAAS,MAAM,EAA0B,KAAG,OAAO,CAAC,IAAI,
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../source/cli.ts"],"names":[],"mappings":"AAAA;;GAEG;AAsBH,4BAA4B;AAC5B,eAAO,MAAM,MAAM,GAAU,UAAS,MAAM,EAA0B,KAAG,OAAO,CAAC,IAAI,CA6DpF,CAAA;AAmBD,uCAAuC;AACvC,eAAO,MAAM,uBAAuB,GAAU,UAAU,MAAM,EAAE,KAAG,OAAO,CAAC,IAAI,CAiB9E,CAAA"}
|
package/library/cli.js
CHANGED
|
@@ -33,14 +33,15 @@ export const runCli = async (rawArgs = process.argv.slice(2)) => {
|
|
|
33
33
|
}
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
36
|
+
if (parsed.command && !validCommands.has(parsed.command)) {
|
|
37
|
+
throw new XDocsError(`Unknown command: ${parsed.command}\n\nRun \`xdocs --help\` for available commands.`);
|
|
38
|
+
}
|
|
39
|
+
const options = resolveOptions(parsed.flags);
|
|
36
40
|
if (!parsed.command) {
|
|
41
|
+
await runAgentAutomation(options, (message) => process.stderr.write(message + '\n'));
|
|
37
42
|
process.stdout.write(showHelp() + '\n');
|
|
38
43
|
return;
|
|
39
44
|
}
|
|
40
|
-
if (!validCommands.has(parsed.command)) {
|
|
41
|
-
throw new XDocsError(`Unknown command: ${parsed.command}\n\nRun \`xdocs --help\` for available commands.`);
|
|
42
|
-
}
|
|
43
|
-
const options = resolveOptions(parsed.flags);
|
|
44
45
|
const command = parsed.command;
|
|
45
46
|
if (automationCommands.has(command)) {
|
|
46
47
|
await runAgentAutomation(options, (message) => process.stderr.write(message + '\n'));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../source/commands/generate.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAAE,eAAe,
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../source/commands/generate.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAa,eAAe,EAAE,MAAM,aAAa,CAAA;AAM9E,gCAAgC;AAChC,eAAO,MAAM,WAAW,GAAU,SAAS,eAAe,EAAE,QAAQ,eAAe,KAAG,OAAO,CAAC,IAAI,CA2BjG,CAAA"}
|
|
@@ -55,6 +55,9 @@ const generateProjectDoc = async (projectName, xdocsFiles, _cwd) => {
|
|
|
55
55
|
lines.push(`### ${file.metadata.subject}`, '');
|
|
56
56
|
lines.push(file.metadata.description, '');
|
|
57
57
|
lines.push(`Location: \`${file.relativePath}\``, '');
|
|
58
|
+
if (file.metadata.keywords.length > 0) {
|
|
59
|
+
lines.push(`Keywords: ${file.metadata.keywords.map((keyword) => `\`${keyword}\``).join(', ')}`, '');
|
|
60
|
+
}
|
|
58
61
|
const fileEntries = Object.entries(file.metadata.files);
|
|
59
62
|
if (fileEntries.length > 0) {
|
|
60
63
|
lines.push('Files:', '');
|
|
@@ -63,6 +66,14 @@ const generateProjectDoc = async (projectName, xdocsFiles, _cwd) => {
|
|
|
63
66
|
}
|
|
64
67
|
lines.push('');
|
|
65
68
|
}
|
|
69
|
+
const documentEntries = Object.entries(file.metadata.documents);
|
|
70
|
+
if (documentEntries.length > 0) {
|
|
71
|
+
lines.push('Documents:', '');
|
|
72
|
+
for (const [name, desc] of documentEntries) {
|
|
73
|
+
lines.push(`- \`${name}\`: ${desc}`);
|
|
74
|
+
}
|
|
75
|
+
lines.push('');
|
|
76
|
+
}
|
|
66
77
|
if (file.body.trim()) {
|
|
67
78
|
lines.push(file.body.trim(), '');
|
|
68
79
|
}
|
|
@@ -90,6 +101,9 @@ const generateModuleDoc = async (moduleName, xdocsFiles, _cwd, targetPath) => {
|
|
|
90
101
|
continue;
|
|
91
102
|
lines.push(`## ${file.metadata.subject}`, '');
|
|
92
103
|
lines.push(file.metadata.description, '');
|
|
104
|
+
if (file.metadata.keywords.length > 0) {
|
|
105
|
+
lines.push(`Keywords: ${file.metadata.keywords.map((keyword) => `\`${keyword}\``).join(', ')}`, '');
|
|
106
|
+
}
|
|
93
107
|
const fileEntries = Object.entries(file.metadata.files);
|
|
94
108
|
if (fileEntries.length > 0) {
|
|
95
109
|
lines.push('### Files', '');
|
|
@@ -98,6 +112,14 @@ const generateModuleDoc = async (moduleName, xdocsFiles, _cwd, targetPath) => {
|
|
|
98
112
|
}
|
|
99
113
|
lines.push('');
|
|
100
114
|
}
|
|
115
|
+
const documentEntries = Object.entries(file.metadata.documents);
|
|
116
|
+
if (documentEntries.length > 0) {
|
|
117
|
+
lines.push('### Documents', '');
|
|
118
|
+
for (const [name, desc] of documentEntries) {
|
|
119
|
+
lines.push(`- \`${name}\`: ${desc}`);
|
|
120
|
+
}
|
|
121
|
+
lines.push('');
|
|
122
|
+
}
|
|
101
123
|
if (file.metadata.children.length > 0) {
|
|
102
124
|
lines.push('### Submodules', '');
|
|
103
125
|
for (const child of file.metadata.children) {
|
|
@@ -111,7 +133,7 @@ const generateModuleDoc = async (moduleName, xdocsFiles, _cwd, targetPath) => {
|
|
|
111
133
|
}
|
|
112
134
|
}
|
|
113
135
|
else {
|
|
114
|
-
lines.push(`No xdocs
|
|
136
|
+
lines.push(`No xdocs descriptors found in this directory.`, '');
|
|
115
137
|
lines.push('### Directory contents', '');
|
|
116
138
|
for (const entry of dirEntries) {
|
|
117
139
|
lines.push(`- \`${entry}\``);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../source/commands/list.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAInE,4BAA4B;AAC5B,eAAO,MAAM,OAAO,GAAU,SAAS,eAAe,EAAE,QAAQ,eAAe,KAAG,OAAO,CAAC,IAAI,
|
|
1
|
+
{"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../source/commands/list.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAInE,4BAA4B;AAC5B,eAAO,MAAM,OAAO,GAAU,SAAS,eAAe,EAAE,QAAQ,eAAe,KAAG,OAAO,CAAC,IAAI,CAsD7F,CAAA"}
|
package/library/commands/list.js
CHANGED
|
@@ -10,13 +10,22 @@ export const runList = async (options, parsed) => {
|
|
|
10
10
|
const targetPath = parsed.positionals[0] ? resolve(options.cwd, parsed.positionals[0]) : options.cwd;
|
|
11
11
|
const result = await scanProject(config);
|
|
12
12
|
const relevantFiles = result.xdocsFiles.filter((f) => f.path.startsWith(targetPath));
|
|
13
|
-
// Collect all files
|
|
13
|
+
// Collect all source files and Markdown companion documents from metadata.
|
|
14
14
|
const fileList = [];
|
|
15
15
|
for (const xdocsFile of relevantFiles) {
|
|
16
16
|
if (!xdocsFile.metadata)
|
|
17
17
|
continue;
|
|
18
18
|
for (const [fileName, description] of Object.entries(xdocsFile.metadata.files)) {
|
|
19
19
|
fileList.push({
|
|
20
|
+
kind: 'file',
|
|
21
|
+
file: fileName,
|
|
22
|
+
description,
|
|
23
|
+
source: xdocsFile.relativePath,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
for (const [fileName, description] of Object.entries(xdocsFile.metadata.documents)) {
|
|
27
|
+
fileList.push({
|
|
28
|
+
kind: 'document',
|
|
20
29
|
file: fileName,
|
|
21
30
|
description,
|
|
22
31
|
source: xdocsFile.relativePath,
|
|
@@ -29,13 +38,14 @@ export const runList = async (options, parsed) => {
|
|
|
29
38
|
}
|
|
30
39
|
if (fileList.length === 0) {
|
|
31
40
|
const scope = targetPath === options.cwd ? 'project' : relative(options.cwd, targetPath);
|
|
32
|
-
process.stdout.write(`No documented files found in ${scope}.\n`);
|
|
41
|
+
process.stdout.write(`No documented files or documents found in ${scope}.\n`);
|
|
33
42
|
return;
|
|
34
43
|
}
|
|
35
44
|
const scope = targetPath === options.cwd ? 'project' : relative(options.cwd, targetPath);
|
|
36
|
-
process.stdout.write(`\
|
|
45
|
+
process.stdout.write(`\nentries in ${scope}:\n\n`);
|
|
37
46
|
for (const entry of fileList) {
|
|
38
|
-
|
|
47
|
+
const label = entry.kind === 'document' ? 'document' : 'file';
|
|
48
|
+
process.stdout.write(` ${label} ${entry.file}: ${entry.description}\n`);
|
|
39
49
|
}
|
|
40
50
|
process.stdout.write('\n');
|
|
41
51
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"merge.d.ts","sourceRoot":"","sources":["../../source/commands/merge.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAKnE,6BAA6B;AAC7B,eAAO,MAAM,QAAQ,GAAU,SAAS,eAAe,EAAE,QAAQ,eAAe,KAAG,OAAO,CAAC,IAAI,
|
|
1
|
+
{"version":3,"file":"merge.d.ts","sourceRoot":"","sources":["../../source/commands/merge.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAKnE,6BAA6B;AAC7B,eAAO,MAAM,QAAQ,GAAU,SAAS,eAAe,EAAE,QAAQ,eAAe,KAAG,OAAO,CAAC,IAAI,CAsE9F,CAAA"}
|
|
@@ -14,7 +14,7 @@ export const runMerge = async (options, parsed) => {
|
|
|
14
14
|
const result = await scanProject(config);
|
|
15
15
|
const relevantFiles = result.xdocsFiles.filter((f) => f.path.startsWith(targetPath));
|
|
16
16
|
if (relevantFiles.length === 0) {
|
|
17
|
-
process.stdout.write('No xdocs
|
|
17
|
+
process.stdout.write('No xdocs descriptors found in the specified path.\n');
|
|
18
18
|
return;
|
|
19
19
|
}
|
|
20
20
|
const lines = [];
|
|
@@ -23,6 +23,9 @@ export const runMerge = async (options, parsed) => {
|
|
|
23
23
|
if (file.metadata) {
|
|
24
24
|
lines.push(`# ${file.metadata.subject}`, '');
|
|
25
25
|
lines.push(file.metadata.description, '');
|
|
26
|
+
if (file.metadata.keywords.length > 0) {
|
|
27
|
+
lines.push(`Keywords: ${file.metadata.keywords.map((keyword) => `\`${keyword}\``).join(', ')}`, '');
|
|
28
|
+
}
|
|
26
29
|
const fileEntries = Object.entries(file.metadata.files);
|
|
27
30
|
if (fileEntries.length > 0) {
|
|
28
31
|
lines.push('## Files', '');
|
|
@@ -31,6 +34,14 @@ export const runMerge = async (options, parsed) => {
|
|
|
31
34
|
}
|
|
32
35
|
lines.push('');
|
|
33
36
|
}
|
|
37
|
+
const documentEntries = Object.entries(file.metadata.documents);
|
|
38
|
+
if (documentEntries.length > 0) {
|
|
39
|
+
lines.push('## Documents', '');
|
|
40
|
+
for (const [name, desc] of documentEntries) {
|
|
41
|
+
lines.push(`- \`${name}\`: ${desc}`);
|
|
42
|
+
}
|
|
43
|
+
lines.push('');
|
|
44
|
+
}
|
|
34
45
|
if (file.metadata.children.length > 0) {
|
|
35
46
|
lines.push('## Submodules', '');
|
|
36
47
|
for (const child of file.metadata.children) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../../source/commands/scan.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAInE,4BAA4B;AAC5B,eAAO,MAAM,OAAO,GAAU,SAAS,eAAe,EAAE,SAAS,eAAe,KAAG,OAAO,CAAC,IAAI,
|
|
1
|
+
{"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../../source/commands/scan.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAInE,4BAA4B;AAC5B,eAAO,MAAM,OAAO,GAAU,SAAS,eAAe,EAAE,SAAS,eAAe,KAAG,OAAO,CAAC,IAAI,CAiE9F,CAAA"}
|
package/library/commands/scan.js
CHANGED
|
@@ -11,25 +11,31 @@ export const runScan = async (options, _parsed) => {
|
|
|
11
11
|
process.stdout.write(JSON.stringify({
|
|
12
12
|
totalFiles: result.totalFiles,
|
|
13
13
|
totalDirectories: result.totalDirectories,
|
|
14
|
+
totalMarkdownDocuments: result.totalMarkdownDocuments,
|
|
14
15
|
coveredDirectories: result.coveredDirectories,
|
|
15
16
|
uncoveredDirectories: result.uncoveredDirectories,
|
|
16
17
|
xdocsFiles: result.xdocsFiles.map((f) => ({
|
|
17
18
|
path: f.relativePath,
|
|
18
19
|
valid: f.valid,
|
|
19
20
|
subject: f.metadata?.subject ?? null,
|
|
21
|
+
keywords: f.metadata?.keywords ?? null,
|
|
22
|
+
documents: f.metadata?.documents ?? null,
|
|
23
|
+
discoveredDocuments: f.documents.map((document) => document.relativePath),
|
|
20
24
|
errors: f.errors,
|
|
21
25
|
})),
|
|
26
|
+
markdownDocuments: result.markdownDocuments.map((document) => document.relativePath),
|
|
22
27
|
uncoveredPaths: result.uncoveredPaths,
|
|
23
28
|
}, null, 2) + '\n');
|
|
24
29
|
return;
|
|
25
30
|
}
|
|
26
31
|
process.stdout.write(`\nxdocs scan\n\n`);
|
|
27
|
-
process.stdout.write(`
|
|
32
|
+
process.stdout.write(`descriptor extension: ${config.extensions.supported.join(', ')}\n`);
|
|
28
33
|
process.stdout.write(`total files scanned: ${result.totalFiles}\n`);
|
|
29
34
|
process.stdout.write(`total directories: ${result.totalDirectories}\n`);
|
|
35
|
+
process.stdout.write(`markdown documents found: ${result.totalMarkdownDocuments}\n`);
|
|
30
36
|
process.stdout.write(`covered directories: ${result.coveredDirectories}\n`);
|
|
31
37
|
process.stdout.write(`uncovered directories: ${result.uncoveredDirectories}\n`);
|
|
32
|
-
process.stdout.write(`xdocs
|
|
38
|
+
process.stdout.write(`xdocs descriptors found: ${result.xdocsFiles.length}\n`);
|
|
33
39
|
if (result.xdocsFiles.length > 0) {
|
|
34
40
|
process.stdout.write(`\nfiles:\n`);
|
|
35
41
|
for (const file of result.xdocsFiles) {
|
|
@@ -42,6 +48,11 @@ export const runScan = async (options, _parsed) => {
|
|
|
42
48
|
process.stdout.write(` error: ${error}\n`);
|
|
43
49
|
}
|
|
44
50
|
}
|
|
51
|
+
if (options.verbose && file.documents.length > 0) {
|
|
52
|
+
for (const document of file.documents) {
|
|
53
|
+
process.stdout.write(` document: ${document.name}\n`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
45
56
|
}
|
|
46
57
|
}
|
|
47
58
|
if (options.verbose && result.uncoveredPaths.length > 0) {
|
package/library/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../source/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAAE,kBAAkB,EAA+B,eAAe,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAU/H,gFAAgF;AAChF,eAAO,MAAM,sBAAsB,EAAE,kBAIpC,CAAA;AAED,yEAAyE;AACzE,eAAO,MAAM,sBAAsB,GAAI,KAAK,cAAc,CAAC,QAAQ,CAAC,KAAG,kBAgBtE,CAAA;AAQD,2CAA2C;AAC3C,eAAO,MAAM,cAAc,GAAU,KAAK,MAAM,EAAE,eAAe,MAAM,KAAG,OAAO,CAAC;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,cAAc,CAAA;CAAE,CAaxH,CAAA;AAED,kDAAkD;AAClD,eAAO,MAAM,UAAU,GAAU,SAAS,eAAe,KAAG,OAAO,CAAC,WAAW,CAS9E,CAAA;AAED,2DAA2D;AAC3D,eAAO,MAAM,oBAAoB,GAAU,SAAS,eAAe,KAAG,OAAO,CAAC,WAAW,CAOxF,CAAA;AAED,4DAA4D;AAC5D,eAAO,MAAM,eAAe,GAAI,KAAK,cAAc,EAAE,KAAK,MAAM,EAAE,aAAa,MAAM,KAAG,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../source/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAAE,kBAAkB,EAA+B,eAAe,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAU/H,gFAAgF;AAChF,eAAO,MAAM,sBAAsB,EAAE,kBAIpC,CAAA;AAED,yEAAyE;AACzE,eAAO,MAAM,sBAAsB,GAAI,KAAK,cAAc,CAAC,QAAQ,CAAC,KAAG,kBAgBtE,CAAA;AAQD,2CAA2C;AAC3C,eAAO,MAAM,cAAc,GAAU,KAAK,MAAM,EAAE,eAAe,MAAM,KAAG,OAAO,CAAC;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,cAAc,CAAA;CAAE,CAaxH,CAAA;AAED,kDAAkD;AAClD,eAAO,MAAM,UAAU,GAAU,SAAS,eAAe,KAAG,OAAO,CAAC,WAAW,CAS9E,CAAA;AAED,2DAA2D;AAC3D,eAAO,MAAM,oBAAoB,GAAU,SAAS,eAAe,KAAG,OAAO,CAAC,WAAW,CAOxF,CAAA;AAED,4DAA4D;AAC5D,eAAO,MAAM,eAAe,GAAI,KAAK,cAAc,EAAE,KAAK,MAAM,EAAE,aAAa,MAAM,KAAG,WAkCvF,CAAA;AAED,sDAAsD;AACtD,eAAO,MAAM,aAAa,GAAI,KAAK,MAAM,KAAG,WAQ1C,CAAA;AAEF,sDAAsD;AACtD,eAAO,MAAM,0BAA0B,GAAI,KAAK,MAAM,KAAG,MAsBxD,CAAA;AAED,mDAAmD;AACnD,eAAO,MAAM,kBAAkB,GAAU,KAAK,MAAM,EAAE,mBAAiB,KAAG,OAAO,CAAC,MAAM,CASvF,CAAA;AAuBD,sDAAsD;AACtD,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,EAAE,MAAM,MAAM,WACP,CAAA"}
|
package/library/config.js
CHANGED
|
@@ -5,7 +5,7 @@ import { existsSync } from 'node:fs';
|
|
|
5
5
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
6
6
|
import { basename, isAbsolute, resolve } from 'node:path';
|
|
7
7
|
import { XDocsError } from './errors.js';
|
|
8
|
-
const DEFAULT_EXTENSIONS = ['.
|
|
8
|
+
const DEFAULT_EXTENSIONS = ['.xdocs.md'];
|
|
9
9
|
const DEFAULT_EXCLUDE = ['node_modules', '.git', 'dist', 'build', 'library', 'bin', 'bundle'];
|
|
10
10
|
const DEFAULT_AI_MODE = 'prompt';
|
|
11
11
|
const CONFIG_FILENAME = 'xdocs.config.toml';
|
|
@@ -83,6 +83,10 @@ export const normalizeConfig = (raw, cwd, configPath) => {
|
|
|
83
83
|
if (!Array.isArray(extensions) || extensions.some((ext) => typeof ext !== 'string')) {
|
|
84
84
|
throw new XDocsError('Invalid extensions.supported. Expected an array of strings.');
|
|
85
85
|
}
|
|
86
|
+
const normalizedExtensions = extensions.map((ext) => ext.toLowerCase());
|
|
87
|
+
if (normalizedExtensions.length !== 1 || normalizedExtensions[0] !== '.xdocs.md') {
|
|
88
|
+
throw new XDocsError('Invalid extensions.supported. xdocs supports only named "*.xdocs.md" descriptor files.');
|
|
89
|
+
}
|
|
86
90
|
const exclude = raw.scan?.exclude ?? DEFAULT_EXCLUDE;
|
|
87
91
|
if (!Array.isArray(exclude) || exclude.some((dir) => typeof dir !== 'string')) {
|
|
88
92
|
throw new XDocsError('Invalid scan.exclude. Expected an array of strings.');
|
|
@@ -91,7 +95,7 @@ export const normalizeConfig = (raw, cwd, configPath) => {
|
|
|
91
95
|
schema: 1,
|
|
92
96
|
cwd,
|
|
93
97
|
configPath,
|
|
94
|
-
extensions: { supported:
|
|
98
|
+
extensions: { supported: DEFAULT_EXTENSIONS },
|
|
95
99
|
ai: { mode: aiMode ?? DEFAULT_AI_MODE },
|
|
96
100
|
scan: { exclude },
|
|
97
101
|
project: { name: raw.project?.name ?? basename(cwd) },
|
|
@@ -114,7 +118,7 @@ export const createDefaultConfigContent = (cwd) => {
|
|
|
114
118
|
return `schema = 1
|
|
115
119
|
|
|
116
120
|
[extensions]
|
|
117
|
-
supported = [".
|
|
121
|
+
supported = [".xdocs.md"]
|
|
118
122
|
|
|
119
123
|
[ai]
|
|
120
124
|
mode = "prompt"
|
package/library/discovery.d.ts
CHANGED
|
@@ -2,11 +2,15 @@
|
|
|
2
2
|
* @copyright Copyright (c) 2026 GUIHO Technologies as represented by Cristóvão GUIHO. All Rights Reserved.
|
|
3
3
|
*/
|
|
4
4
|
import type { XDocsConfig, XDocsFile, XDocsScanResult } from './types.js';
|
|
5
|
-
/** Scan the project for xdocs files
|
|
5
|
+
/** Scan the project for xdocs descriptor files and sibling Markdown documents. */
|
|
6
6
|
export declare const scanProject: (config: XDocsConfig) => Promise<XDocsScanResult>;
|
|
7
|
-
/** Check if a file path
|
|
8
|
-
export declare const isXDocsFile: (filePath: string,
|
|
9
|
-
/**
|
|
7
|
+
/** Check if a file path is a root index or xdocs descriptor file. */
|
|
8
|
+
export declare const isXDocsFile: (filePath: string, _extensions?: string[]) => boolean;
|
|
9
|
+
/** Check if a file path is an xdocs module descriptor. */
|
|
10
|
+
export declare const isXDocsDescriptorFile: (filePath: string) => boolean;
|
|
11
|
+
/** Check if a file path is a companion Markdown document, not an xdocs descriptor. */
|
|
12
|
+
export declare const isPlainMarkdownDocument: (filePath: string) => boolean;
|
|
13
|
+
/** Scan a specific directory (not recursive) for xdocs descriptors. */
|
|
10
14
|
export declare const scanDirectory: (dirPath: string, config: XDocsConfig) => Promise<XDocsFile[]>;
|
|
11
15
|
/** List all files in a directory (non-recursive). */
|
|
12
16
|
export declare const listDirectoryFiles: (dirPath: string, config: XDocsConfig) => Promise<string[]>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../source/discovery.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,
|
|
1
|
+
{"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../source/discovery.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAyB,eAAe,EAAE,MAAM,YAAY,CAAA;AAMhG,kFAAkF;AAClF,eAAO,MAAM,WAAW,GAAU,QAAQ,WAAW,KAAG,OAAO,CAAC,eAAe,CA4D9E,CAAA;AAED,qEAAqE;AACrE,eAAO,MAAM,WAAW,GAAI,UAAU,MAAM,EAAE,cAAa,MAAM,EAAiC,KAAG,OACtB,CAAA;AAE/E,0DAA0D;AAC1D,eAAO,MAAM,qBAAqB,GAAI,UAAU,MAAM,KAAG,OACc,CAAA;AAEvE,sFAAsF;AACtF,eAAO,MAAM,uBAAuB,GAAI,UAAU,MAAM,KAAG,OAI1D,CAAA;AAmCD,uEAAuE;AACvE,eAAO,MAAM,aAAa,GAAU,SAAS,MAAM,EAAE,QAAQ,WAAW,KAAG,OAAO,CAAC,SAAS,EAAE,CA6B7F,CAAA;AAED,qDAAqD;AACrD,eAAO,MAAM,kBAAkB,GAAU,SAAS,MAAM,EAAE,QAAQ,WAAW,KAAG,OAAO,CAAC,MAAM,EAAE,CAiB/F,CAAA"}
|
package/library/discovery.js
CHANGED
|
@@ -2,24 +2,32 @@
|
|
|
2
2
|
* @copyright Copyright (c) 2026 GUIHO Technologies as represented by Cristóvão GUIHO. All Rights Reserved.
|
|
3
3
|
*/
|
|
4
4
|
import { readdir, stat } from 'node:fs/promises';
|
|
5
|
-
import { join, relative } from 'node:path';
|
|
5
|
+
import { basename, dirname, join, relative, resolve } from 'node:path';
|
|
6
6
|
import { parseXDocsFile } from './metadata.js';
|
|
7
|
-
|
|
7
|
+
const XDOCS_DESCRIPTOR_EXTENSION = '.xdocs.md';
|
|
8
|
+
const ROOT_XDOCS_FILENAME = 'XDOCS.md';
|
|
9
|
+
/** Scan the project for xdocs descriptor files and sibling Markdown documents. */
|
|
8
10
|
export const scanProject = async (config) => {
|
|
9
11
|
const xdocsFiles = [];
|
|
12
|
+
const markdownDocuments = [];
|
|
13
|
+
const markdownDocumentsByDirectory = new Map();
|
|
10
14
|
const allDirectories = [];
|
|
11
|
-
const coveredDirectories = new Set();
|
|
12
15
|
let totalFiles = 0;
|
|
13
|
-
await walkDirectory(config.cwd, config, async (filePath,
|
|
16
|
+
await walkDirectory(config.cwd, config, async (filePath, _dir) => {
|
|
14
17
|
totalFiles += 1;
|
|
15
|
-
if (
|
|
18
|
+
if (isXDocsDescriptorFile(filePath)) {
|
|
16
19
|
const file = await parseXDocsFile(filePath, config.cwd);
|
|
17
20
|
xdocsFiles.push(file);
|
|
18
|
-
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (isPlainMarkdownDocument(filePath)) {
|
|
24
|
+
const document = createMarkdownDocument(filePath, config.cwd);
|
|
25
|
+
markdownDocuments.push(document);
|
|
26
|
+
addMarkdownDocument(markdownDocumentsByDirectory, document.directory, document);
|
|
19
27
|
}
|
|
20
28
|
}, allDirectories);
|
|
21
29
|
// The root XDOCS.md is always recognized
|
|
22
|
-
const rootXDocs = join(config.cwd,
|
|
30
|
+
const rootXDocs = join(config.cwd, ROOT_XDOCS_FILENAME);
|
|
23
31
|
const rootAlreadyFound = xdocsFiles.some((f) => f.path === rootXDocs);
|
|
24
32
|
if (!rootAlreadyFound) {
|
|
25
33
|
try {
|
|
@@ -27,30 +35,43 @@ export const scanProject = async (config) => {
|
|
|
27
35
|
if (rootStat.isFile()) {
|
|
28
36
|
const file = await parseXDocsFile(rootXDocs, config.cwd);
|
|
29
37
|
xdocsFiles.push(file);
|
|
30
|
-
coveredDirectories.add(config.cwd);
|
|
31
38
|
}
|
|
32
39
|
}
|
|
33
40
|
catch {
|
|
34
41
|
// Root XDOCS.md does not exist, that's fine
|
|
35
42
|
}
|
|
36
43
|
}
|
|
44
|
+
enrichDescriptorFiles(xdocsFiles, markdownDocumentsByDirectory);
|
|
45
|
+
const coveredDirectories = new Set();
|
|
46
|
+
if (xdocsFiles.some((file) => file.path === rootXDocs))
|
|
47
|
+
coveredDirectories.add(config.cwd);
|
|
48
|
+
for (const file of xdocsFiles) {
|
|
49
|
+
if (file.path === rootXDocs)
|
|
50
|
+
continue;
|
|
51
|
+
if (file.valid)
|
|
52
|
+
coveredDirectories.add(file.directory);
|
|
53
|
+
}
|
|
37
54
|
const uncoveredPaths = allDirectories.filter((dir) => !coveredDirectories.has(dir));
|
|
38
55
|
return {
|
|
39
56
|
totalFiles,
|
|
40
57
|
totalDirectories: allDirectories.length,
|
|
58
|
+
totalMarkdownDocuments: markdownDocuments.length,
|
|
41
59
|
coveredDirectories: coveredDirectories.size,
|
|
42
60
|
uncoveredDirectories: uncoveredPaths.length,
|
|
43
61
|
xdocsFiles,
|
|
62
|
+
markdownDocuments,
|
|
44
63
|
uncoveredPaths,
|
|
45
64
|
};
|
|
46
65
|
};
|
|
47
|
-
/** Check if a file path
|
|
48
|
-
export const isXDocsFile = (filePath,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
66
|
+
/** Check if a file path is a root index or xdocs descriptor file. */
|
|
67
|
+
export const isXDocsFile = (filePath, _extensions = [XDOCS_DESCRIPTOR_EXTENSION]) => basename(filePath) === ROOT_XDOCS_FILENAME || isXDocsDescriptorFile(filePath);
|
|
68
|
+
/** Check if a file path is an xdocs module descriptor. */
|
|
69
|
+
export const isXDocsDescriptorFile = (filePath) => basename(filePath).toLowerCase().endsWith(XDOCS_DESCRIPTOR_EXTENSION);
|
|
70
|
+
/** Check if a file path is a companion Markdown document, not an xdocs descriptor. */
|
|
71
|
+
export const isPlainMarkdownDocument = (filePath) => {
|
|
72
|
+
const name = basename(filePath);
|
|
73
|
+
const lower = name.toLowerCase();
|
|
74
|
+
return lower.endsWith('.md') && lower !== 'xdocs.md' && !isXDocsDescriptorFile(filePath);
|
|
54
75
|
};
|
|
55
76
|
/** Recursively walk a directory, skipping excluded directories. */
|
|
56
77
|
const walkDirectory = async (dir, config, onFile, allDirectories) => {
|
|
@@ -76,9 +97,10 @@ const walkDirectory = async (dir, config, onFile, allDirectories) => {
|
|
|
76
97
|
};
|
|
77
98
|
/** Check if a directory name is in the exclude list. */
|
|
78
99
|
const isExcluded = (name, exclude) => exclude.includes(name) || name.startsWith('.');
|
|
79
|
-
/** Scan a specific directory (not recursive) for xdocs
|
|
100
|
+
/** Scan a specific directory (not recursive) for xdocs descriptors. */
|
|
80
101
|
export const scanDirectory = async (dirPath, config) => {
|
|
81
102
|
const files = [];
|
|
103
|
+
const markdownDocumentsByDirectory = new Map();
|
|
82
104
|
let entries;
|
|
83
105
|
try {
|
|
84
106
|
entries = await readdir(dirPath, { withFileTypes: true });
|
|
@@ -90,11 +112,17 @@ export const scanDirectory = async (dirPath, config) => {
|
|
|
90
112
|
if (!entry.isFile())
|
|
91
113
|
continue;
|
|
92
114
|
const fullPath = join(dirPath, entry.name);
|
|
93
|
-
if (
|
|
115
|
+
if (isXDocsDescriptorFile(fullPath) || isRootXDocsFile(fullPath, config.cwd)) {
|
|
94
116
|
const file = await parseXDocsFile(fullPath, config.cwd);
|
|
95
117
|
files.push(file);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (isPlainMarkdownDocument(fullPath)) {
|
|
121
|
+
const document = createMarkdownDocument(fullPath, config.cwd);
|
|
122
|
+
addMarkdownDocument(markdownDocumentsByDirectory, document.directory, document);
|
|
96
123
|
}
|
|
97
124
|
}
|
|
125
|
+
enrichDescriptorFiles(files, markdownDocumentsByDirectory);
|
|
98
126
|
return files;
|
|
99
127
|
};
|
|
100
128
|
/** List all files in a directory (non-recursive). */
|
|
@@ -116,3 +144,65 @@ export const listDirectoryFiles = async (dirPath, config) => {
|
|
|
116
144
|
}
|
|
117
145
|
return result;
|
|
118
146
|
};
|
|
147
|
+
const isRootXDocsFile = (filePath, cwd) => resolve(filePath) === resolve(cwd, ROOT_XDOCS_FILENAME);
|
|
148
|
+
const createMarkdownDocument = (filePath, cwd) => ({
|
|
149
|
+
path: filePath,
|
|
150
|
+
relativePath: relative(cwd, filePath),
|
|
151
|
+
directory: dirname(filePath),
|
|
152
|
+
name: basename(filePath),
|
|
153
|
+
});
|
|
154
|
+
const addMarkdownDocument = (documentsByDirectory, directory, document) => {
|
|
155
|
+
const documents = documentsByDirectory.get(directory) ?? [];
|
|
156
|
+
documents.push(document);
|
|
157
|
+
documentsByDirectory.set(directory, documents);
|
|
158
|
+
};
|
|
159
|
+
const enrichDescriptorFiles = (files, documentsByDirectory) => {
|
|
160
|
+
const descriptorsByDirectory = new Map();
|
|
161
|
+
for (const file of files) {
|
|
162
|
+
if (!isXDocsDescriptorFile(file.path))
|
|
163
|
+
continue;
|
|
164
|
+
const descriptors = descriptorsByDirectory.get(file.directory) ?? [];
|
|
165
|
+
descriptors.push(file);
|
|
166
|
+
descriptorsByDirectory.set(file.directory, descriptors);
|
|
167
|
+
}
|
|
168
|
+
for (const descriptors of descriptorsByDirectory.values()) {
|
|
169
|
+
if (descriptors.length <= 1)
|
|
170
|
+
continue;
|
|
171
|
+
for (const file of descriptors) {
|
|
172
|
+
file.errors.push('Multiple xdocs descriptors found in this directory. Keep exactly one named "*.xdocs.md" file per directory.');
|
|
173
|
+
file.valid = false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
for (const file of files) {
|
|
177
|
+
if (!isXDocsDescriptorFile(file.path))
|
|
178
|
+
continue;
|
|
179
|
+
file.documents = documentsByDirectory.get(file.directory) ?? [];
|
|
180
|
+
validateDocumentReferences(file);
|
|
181
|
+
if (file.errors.length > 0)
|
|
182
|
+
file.valid = false;
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
const validateDocumentReferences = (file) => {
|
|
186
|
+
if (!file.metadata)
|
|
187
|
+
return;
|
|
188
|
+
const actualDocuments = new Set(file.documents.map((document) => document.name));
|
|
189
|
+
const declaredDocuments = file.metadata.documents;
|
|
190
|
+
for (const document of file.documents) {
|
|
191
|
+
if (Object.hasOwn(declaredDocuments, document.name))
|
|
192
|
+
continue;
|
|
193
|
+
file.errors.push(`Undocumented Markdown document: "${document.name}" must be listed in the "documents" metadata map.`);
|
|
194
|
+
}
|
|
195
|
+
for (const name of Object.keys(declaredDocuments)) {
|
|
196
|
+
if (!isPlainMarkdownDocumentName(name)) {
|
|
197
|
+
file.errors.push(`Invalid document entry: "${name}" must be a sibling plain "*.md" filename, not an xdocs descriptor or path.`);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (!actualDocuments.has(name)) {
|
|
201
|
+
file.errors.push(`Missing Markdown document: "${name}" is listed in metadata but does not exist beside the descriptor.`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
const isPlainMarkdownDocumentName = (name) => {
|
|
206
|
+
const lower = name.toLowerCase();
|
|
207
|
+
return !name.includes('/') && !name.includes('\\') && lower.endsWith('.md') && lower !== 'xdocs.md' && !lower.endsWith(XDOCS_DESCRIPTOR_EXTENSION);
|
|
208
|
+
};
|
package/library/flags.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ import type { XDocsParsedArgs } from './types.js';
|
|
|
10
10
|
* - Long flags with value: `--name=write` or `--name write`
|
|
11
11
|
* - Long boolean flags: `--verbose`
|
|
12
12
|
* - Short flags: `-h`, `-v`
|
|
13
|
-
* - List values as comma-separated: `--extensions=.
|
|
13
|
+
* - List values as comma-separated: `--extensions=.xdocs.md,.custom.md`
|
|
14
14
|
* - Positional arguments after the command
|
|
15
15
|
*/
|
|
16
16
|
export declare const parseArgs: (rawArgs: string[]) => XDocsParsedArgs;
|
package/library/flags.js
CHANGED
|
@@ -24,7 +24,7 @@ const listFlags = new Set([
|
|
|
24
24
|
* - Long flags with value: `--name=write` or `--name write`
|
|
25
25
|
* - Long boolean flags: `--verbose`
|
|
26
26
|
* - Short flags: `-h`, `-v`
|
|
27
|
-
* - List values as comma-separated: `--extensions=.
|
|
27
|
+
* - List values as comma-separated: `--extensions=.xdocs.md,.custom.md`
|
|
28
28
|
* - Positional arguments after the command
|
|
29
29
|
*/
|
|
30
30
|
export const parseArgs = (rawArgs) => {
|
package/library/guiho-xdocs.d.ts
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
* @copyright Copyright (c) 2026 GUIHO Technologies as represented by Cristóvão GUIHO. All Rights Reserved.
|
|
3
3
|
*/
|
|
4
4
|
export { XDocsError, invariant } from './errors.js';
|
|
5
|
-
export type { XDocsAgentAutomationResult, XDocsAgentSettings, XDocsAgentTool, XDocsAgentsInstructionsResult, XDocsAiMode, XDocsCliOptions, XDocsCommand, XDocsConfig, XDocsFile, XDocsFormat, XDocsMetadata, XDocsParsedArgs, XDocsPrompt, XDocsPromptName, XDocsRawConfig, XDocsScanResult, XDocsSkillInstallResult, XDocsSkillScope, XDocsTreeNode, XDocsTreeValidation, } from './types.js';
|
|
5
|
+
export type { XDocsAgentAutomationResult, XDocsAgentSettings, XDocsAgentTool, XDocsAgentsInstructionsResult, XDocsAiMode, XDocsCliOptions, XDocsCommand, XDocsConfig, XDocsFile, XDocsFormat, XDocsMarkdownDocument, XDocsMetadata, XDocsParsedArgs, XDocsPrompt, XDocsPromptName, XDocsRawConfig, XDocsScanResult, XDocsSkillInstallResult, XDocsSkillScope, XDocsTreeNode, XDocsTreeValidation, } from './types.js';
|
|
6
6
|
export { parseArgs, stringFlag, booleanFlag, listFlag } from './flags.js';
|
|
7
7
|
export { createDefaultConfigContent, DEFAULT_AGENT_SETTINGS, defaultConfig, discoverConfig, loadConfig, loadConfigOrDefaults, normalizeAgentSettings, normalizeConfig, resolvePath, writeDefaultConfig, } from './config.js';
|
|
8
|
-
export { isXDocsFile, listDirectoryFiles, scanDirectory, scanProject } from './discovery.js';
|
|
8
|
+
export { isPlainMarkdownDocument, isXDocsDescriptorFile, isXDocsFile, listDirectoryFiles, scanDirectory, scanProject } from './discovery.js';
|
|
9
9
|
export { extractFrontmatter, parseXDocsFile, validateMetadata } from './metadata.js';
|
|
10
10
|
export { buildTree, renderTree, renderTreeMarkdown, validateTree } from './tree.js';
|
|
11
11
|
export { readPackageVersion, showCommandHelp, showHelp, showVersion } from './help.js';
|