asciidoclint 1.0.0 → 1.2.0
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/README.md +79 -16
- package/dist/api/lint.js +41 -4
- package/dist/api/rules.js +1 -1
- package/dist/cli/index.js +7 -7
- package/dist/cli/install-skill.d.ts +5 -0
- package/dist/cli/install-skill.js +131 -42
- package/dist/parsers/asciidoctor.d.ts +2 -1
- package/dist/parsers/asciidoctor.js +150 -1
- package/dist/parsers/tolerant.js +29 -5
- package/dist/rules/AD004.js +99 -0
- package/dist/rules/AD009.d.ts +2 -0
- package/dist/rules/AD009.js +80 -0
- package/dist/rules/AD010.js +17 -3
- package/dist/rules/AD011.js +20 -5
- package/dist/rules/AD012.js +14 -2
- package/dist/rules/AD013.js +2 -2
- package/dist/rules/AD016.js +16 -10
- package/dist/rules/AD017.js +4 -4
- package/dist/rules/AD020.js +41 -20
- package/dist/rules/AD025.js +2 -2
- package/dist/rules/AD028.js +77 -10
- package/dist/rules/AD029.js +2 -2
- package/dist/rules/AD039.js +2 -2
- package/dist/rules/AD046.d.ts +2 -0
- package/dist/rules/AD046.js +91 -0
- package/dist/rules/AD047.d.ts +2 -0
- package/dist/rules/AD047.js +47 -0
- package/dist/rules/AD048.d.ts +2 -0
- package/dist/rules/AD048.js +37 -0
- package/dist/rules/AD049.d.ts +2 -0
- package/dist/rules/AD049.js +46 -0
- package/dist/rules/AD050.d.ts +2 -0
- package/dist/rules/AD050.js +46 -0
- package/dist/rules/AD051.d.ts +2 -0
- package/dist/rules/AD051.js +52 -0
- package/dist/rules/AD052.d.ts +2 -0
- package/dist/rules/AD052.js +46 -0
- package/dist/rules/AD053.d.ts +2 -0
- package/dist/rules/AD053.js +46 -0
- package/dist/rules/AD054.d.ts +2 -0
- package/dist/rules/AD054.js +46 -0
- package/dist/rules/AD055.d.ts +2 -0
- package/dist/rules/AD055.js +41 -0
- package/dist/rules/AD056.d.ts +2 -0
- package/dist/rules/AD056.js +49 -0
- package/dist/rules/AD057.d.ts +2 -0
- package/dist/rules/AD057.js +79 -0
- package/dist/rules/AD058.d.ts +2 -0
- package/dist/rules/AD058.js +33 -0
- package/dist/rules/AD059.d.ts +2 -0
- package/dist/rules/AD059.js +99 -0
- package/dist/rules/builtin.js +30 -0
- package/dist/rules/specialSections.d.ts +13 -0
- package/dist/rules/specialSections.js +54 -0
- package/dist/types.d.ts +5 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/architecture.md +250 -77
- package/docs/rules/AD009.md +59 -0
- package/docs/rules/AD010.md +12 -3
- package/docs/rules/AD011.md +17 -9
- package/docs/rules/AD012.md +14 -3
- package/docs/rules/AD013.md +3 -3
- package/docs/rules/AD016.md +11 -10
- package/docs/rules/AD017.md +5 -4
- package/docs/rules/AD020.md +57 -11
- package/docs/rules/AD025.md +2 -2
- package/docs/rules/AD028.md +14 -9
- package/docs/rules/AD029.md +2 -2
- package/docs/rules/AD046.md +118 -0
- package/docs/rules/AD047.md +46 -0
- package/docs/rules/AD048.md +43 -0
- package/docs/rules/AD049.md +43 -0
- package/docs/rules/AD050.md +41 -0
- package/docs/rules/AD051.md +49 -0
- package/docs/rules/AD052.md +39 -0
- package/docs/rules/AD053.md +41 -0
- package/docs/rules/AD054.md +40 -0
- package/docs/rules/AD055.md +60 -0
- package/docs/rules/AD056.md +36 -0
- package/docs/rules/AD057.md +44 -0
- package/docs/rules/AD058.md +28 -0
- package/docs/rules/AD059.md +68 -0
- package/docs/rules/rule-necessity.md +22 -7
- package/package.json +1 -1
- package/skills/asciidoclint/SKILL.md +28 -6
- package/skills/asciidoclint/references/feedback.md +9 -6
- package/skills/asciidoclint/references/lint-summary.md +4 -4
- package/skills/asciidoclint/references/rule-create.md +19 -8
- package/skills/asciidoclint/references/rule-review.md +8 -1
package/README.md
CHANGED
|
@@ -9,12 +9,39 @@ CLI, AI-agent, and editor workflows.
|
|
|
9
9
|
|
|
10
10
|
## Install the npm package
|
|
11
11
|
|
|
12
|
+
`asciidoclint` requires Node.js 20 or newer. The package does not install or
|
|
13
|
+
manage Node.js; use the Node.js version chosen by your shell, package manager, or
|
|
14
|
+
higher-level tooling.
|
|
15
|
+
|
|
16
|
+
Install globally when you want `asciidoclint` available as a shell command:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install -g asciidoclint
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
If the shell cannot find `asciidoclint` after install, npm's global bin
|
|
23
|
+
directory may not be on `PATH`. It is usually:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
$(npm prefix -g)/bin
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
For documentation repositories that need a pinned project-local install without
|
|
30
|
+
creating a project-root `package.json`, install under `.asciidoclint`:
|
|
31
|
+
|
|
12
32
|
```bash
|
|
13
|
-
npm
|
|
33
|
+
npm --prefix .asciidoclint install asciidoclint@<version>
|
|
14
34
|
```
|
|
15
35
|
|
|
16
36
|
## Use the CLI
|
|
17
37
|
|
|
38
|
+
The examples below use `npx asciidoclint` because that works even when npm's
|
|
39
|
+
global bin directory is not on `PATH`. If `asciidoclint` is recognized by your
|
|
40
|
+
shell, you can use `asciidoclint` instead.
|
|
41
|
+
|
|
42
|
+
If the package is installed with the dedicated project-local layout, use
|
|
43
|
+
`.asciidoclint/node_modules/.bin/asciidoclint`.
|
|
44
|
+
|
|
18
45
|
Run lint:
|
|
19
46
|
|
|
20
47
|
```bash
|
|
@@ -60,26 +87,43 @@ reporting.
|
|
|
60
87
|
|
|
61
88
|
## Install the AI skill
|
|
62
89
|
|
|
63
|
-
The
|
|
64
|
-
npm package
|
|
90
|
+
The npm package includes an `asciidoclint` skill for AI agents. Installing the
|
|
91
|
+
npm package does not automatically install the skill into agent roots; run
|
|
92
|
+
`install-skill` explicitly.
|
|
93
|
+
|
|
94
|
+
For a user-global skill install:
|
|
65
95
|
|
|
66
96
|
```bash
|
|
67
97
|
npx asciidoclint install-skill
|
|
68
98
|
```
|
|
69
99
|
|
|
70
|
-
|
|
100
|
+
This copies the bundled skill to `~/.agents/skills/asciidoclint` and creates
|
|
101
|
+
`~/.claude/skills/asciidoclint` as a symbolic link to that copy.
|
|
102
|
+
|
|
103
|
+
For a pinned project-local skill install:
|
|
71
104
|
|
|
72
105
|
```bash
|
|
73
|
-
|
|
106
|
+
npm --prefix .asciidoclint install asciidoclint@<version>
|
|
107
|
+
.asciidoclint/node_modules/.bin/asciidoclint install-skill --project
|
|
74
108
|
```
|
|
75
109
|
|
|
76
|
-
|
|
77
|
-
|
|
110
|
+
This creates symbolic links at `.agents/skills/asciidoclint` and
|
|
111
|
+
`.claude/skills/asciidoclint`, pointing to the skill source bundled with the
|
|
112
|
+
package or checkout that ran `install-skill`. Add `--force` only when replacing
|
|
113
|
+
an existing project-local skill install.
|
|
114
|
+
|
|
115
|
+
Remove installed skills explicitly:
|
|
78
116
|
|
|
79
117
|
```bash
|
|
80
118
|
npx asciidoclint uninstall-skill
|
|
119
|
+
npx asciidoclint uninstall-skill --project
|
|
81
120
|
```
|
|
82
121
|
|
|
122
|
+
Normal npm uninstall removes package files only. It does not remove
|
|
123
|
+
`~/.agents/skills/asciidoclint`, `~/.claude/skills/asciidoclint`,
|
|
124
|
+
`.agents/skills/asciidoclint`, or `.claude/skills/asciidoclint`; run
|
|
125
|
+
`uninstall-skill` for those.
|
|
126
|
+
|
|
83
127
|
The public skill exposes these user-facing workflows:
|
|
84
128
|
|
|
85
129
|
| Workflow | Purpose |
|
|
@@ -122,6 +166,7 @@ extension commands and settings.
|
|
|
122
166
|
| `AD006/included-document-title` | Included AsciiDoc files should not introduce a level-0 title without level offset |
|
|
123
167
|
| `AD007/heading-depth-limit` | Section headings should not exceed Asciidoctor's supported depth |
|
|
124
168
|
| `AD008/blank-before-list` | Lists should be separated from preceding paragraph text |
|
|
169
|
+
| `AD009/blank-after-list` | Lists should be separated from following structural blocks |
|
|
125
170
|
| `AD010/table-title` | Table blocks should have a title |
|
|
126
171
|
| `AD011/image-title` | Block images should have a title |
|
|
127
172
|
| `AD012/diagram-title` | Diagram blocks should have a title |
|
|
@@ -129,14 +174,14 @@ extension commands and settings.
|
|
|
129
174
|
| `AD016/malformed-figure-caption` | Figure captions should use AsciiDoc title syntax |
|
|
130
175
|
| `AD017/malformed-table-caption` | Table captions should use AsciiDoc title syntax |
|
|
131
176
|
| `AD019/content-after-include` | Text should not be attached directly after include directives |
|
|
132
|
-
| `AD020/appendix-
|
|
177
|
+
| `AD020/appendix-placement` | Appendix markers should apply to documented appendix section levels |
|
|
133
178
|
| `AD022/circular-include` | Include trees must not contain cycles |
|
|
134
179
|
| `AD023/empty-section` | Sections should contain body content or child sections |
|
|
135
180
|
| `AD024/missing-include` | Include targets should exist after attribute substitution |
|
|
136
181
|
| `AD025/missing-image` | Image targets should exist after attribute substitution |
|
|
137
182
|
| `AD026/missing-xref` | Cross-reference targets should resolve to an anchor or file |
|
|
138
183
|
| `AD027/missing-local-link` | Local link targets should resolve to existing files |
|
|
139
|
-
| `AD028/image-alt-text` | Images should
|
|
184
|
+
| `AD028/image-alt-text` | Images should provide meaningful alt text |
|
|
140
185
|
| `AD029/markdown-link-image-residue` | Markdown link and image residue should not render as text |
|
|
141
186
|
| `AD030/markdown-table-residue` | Markdown pipe table residue should not render as text |
|
|
142
187
|
| `AD031/no-nested-link-text` | Link text should not contain nested links or cross references |
|
|
@@ -152,6 +197,20 @@ extension commands and settings.
|
|
|
152
197
|
| `AD043/section-title-start-left` | Section title syntax should start at the beginning of the line |
|
|
153
198
|
| `AD044/local-adoc-link` | Local AsciiDoc files should be referenced with xref, not link |
|
|
154
199
|
| `AD045/markdown-heading-mix` | Markdown-compatible headings should not be mixed with AsciiDoc headings |
|
|
200
|
+
| `AD046/preface-placement` | Preface sections should match documented book placement |
|
|
201
|
+
| `AD047/abstract-placement` | Abstract sections should match documented article placement |
|
|
202
|
+
| `AD048/bibliography-placement` | Bibliography markers should apply to documented bibliography sections |
|
|
203
|
+
| `AD049/glossary-placement` | Glossary markers should apply to documented glossary section levels |
|
|
204
|
+
| `AD050/index-placement` | Index markers should apply to documented index section levels |
|
|
205
|
+
| `AD051/partintro-placement` | Part introduction markers should apply to the introductory block of a book part |
|
|
206
|
+
| `AD052/acknowledgments-placement` | Acknowledgments markers should apply to documented book section levels |
|
|
207
|
+
| `AD053/dedication-placement` | Dedication markers should apply to documented book section levels |
|
|
208
|
+
| `AD054/colophon-placement` | Colophon markers should apply to documented book section levels |
|
|
209
|
+
| `AD055/docx-anchor-caption-residue` | DOCX anchor caption residue should be converted to AsciiDoc titles |
|
|
210
|
+
| `AD056/dangling-blank-list-continuation` | Blank list continuations should not capture following content |
|
|
211
|
+
| `AD057/semantic-anchor-target` | Semantic anchors should attach to the matching block type |
|
|
212
|
+
| `AD058/docx-bookmark-hash-residue` | DOCX bookmark/hash residue should be cleaned up |
|
|
213
|
+
| `AD059/docx-nested-table-structure` | DOCX-converted nested tables should use valid nested table separators and shallow nesting |
|
|
155
214
|
| `ADW01/unknown-waiver-directive` | Waiver directive names should be known |
|
|
156
215
|
| `ADW02/missing-waiver-rule-list` | Waiver directives should include a rule list |
|
|
157
216
|
| `ADW03/malformed-waiver-rule-list` | Waiver rule lists should use comma-separated rule IDs |
|
|
@@ -168,26 +227,30 @@ rules. `ADW##` waiver diagnostics use tags for discovery, but remain always on.
|
|
|
168
227
|
|
|
169
228
|
| Group | IDs |
|
|
170
229
|
|---|---|
|
|
171
|
-
| `
|
|
230
|
+
| `anchor` | `AD057` |
|
|
231
|
+
| `blank_lines` | `AD008`, `AD009` |
|
|
172
232
|
| `blocks` | `AD003`, `AD032`, `AD035` |
|
|
173
233
|
| `accessibility` | `AD028`, `AD042` |
|
|
174
|
-
| `cleanup` | `AD032`, `AD034`, `AD035`, `AD036`, `AD037`, `AD039`, `AD040`, `AD041` |
|
|
175
|
-
| `conversion` | `AD029`, `AD030`, `AD031`, `
|
|
234
|
+
| `cleanup` | `AD032`, `AD034`, `AD035`, `AD036`, `AD037`, `AD039`, `AD040`, `AD041`, `AD055`, `AD058`, `AD059` |
|
|
235
|
+
| `conversion` | `AD029`, `AD030`, `AD031`, `AD037`, `AD039`, `AD040`, `AD055`, `AD056`, `AD058`, `AD059` |
|
|
236
|
+
| `docx` | `AD016`, `AD017`, `AD028`, `AD055`, `AD056`, `AD058`, `AD059` |
|
|
176
237
|
| `dependencies` | `AD024`, `AD025`, `AD026`, `AD027` |
|
|
177
238
|
| `diagram` | `AD012` |
|
|
178
239
|
| `format` | `AD041` |
|
|
179
240
|
| `headings` | `AD001`, `AD002`, `AD005`, `AD006`, `AD007`, `AD043`, `AD045` |
|
|
180
|
-
| `image` | `AD011`, `AD013`, `AD016`, `AD025`, `AD028` |
|
|
241
|
+
| `image` | `AD011`, `AD013`, `AD016`, `AD025`, `AD028`, `AD057` |
|
|
181
242
|
| `include` | `AD006`, `AD019`, `AD022`, `AD024` |
|
|
182
243
|
| `inline` | `AD041` |
|
|
183
|
-
| `
|
|
244
|
+
| `list` | `AD056` |
|
|
245
|
+
| `lists` | `AD008`, `AD009`, `AD036` |
|
|
246
|
+
| `pandoc` | `AD059` |
|
|
184
247
|
| `parser` | `AD000` |
|
|
185
|
-
| `table` | `AD004`, `AD010`, `AD017`, `AD030` |
|
|
248
|
+
| `table` | `AD004`, `AD010`, `AD017`, `AD030`, `AD057`, `AD059` |
|
|
186
249
|
| `waiver` | `ADW01`, `ADW02`, `ADW03`, `ADW04`, `ADW05`, `ADW06`, `ADW07`, `ADW08` |
|
|
187
250
|
| `whitespace` | `AD034` |
|
|
188
251
|
| `references` | `AD023` |
|
|
189
252
|
| `links` | `AD027`, `AD031`, `AD042`, `AD044` |
|
|
190
|
-
| `structure` | `AD043` |
|
|
253
|
+
| `structure` | `AD009`, `AD019`, `AD020`, `AD022`, `AD023`, `AD043`, `AD046`, `AD047`, `AD048`, `AD049`, `AD050`, `AD051`, `AD052`, `AD053`, `AD054`, `AD056`, `AD057` |
|
|
191
254
|
| `markdown-compatibility` | `AD045` |
|
|
192
255
|
| `maintainability` | `AD045` |
|
|
193
256
|
| `xref` | `AD026`, `AD042`, `AD044` |
|
package/dist/api/lint.js
CHANGED
|
@@ -23,6 +23,7 @@ async function lintFilesInternal(patterns, options, afterFix) {
|
|
|
23
23
|
for (const parsedFile of document.files) {
|
|
24
24
|
parsedFiles.set(path.resolve(parsedFile.file), parsedFile);
|
|
25
25
|
}
|
|
26
|
+
mergeAsciidoctorSections(document, await collectParserSections(file));
|
|
26
27
|
mergeAsciidoctorBlocks(document, await collectParserBlocks(file));
|
|
27
28
|
mergeAsciidoctorReferenceTargets(document, await collectParserReferenceTargets(file));
|
|
28
29
|
resolveDocumentXrefs(document);
|
|
@@ -69,24 +70,60 @@ async function collectParserBlocks(file) {
|
|
|
69
70
|
const { collectAsciidoctorBlocks } = await import("../parsers/asciidoctor.js");
|
|
70
71
|
return collectAsciidoctorBlocks(file);
|
|
71
72
|
}
|
|
73
|
+
async function collectParserSections(file) {
|
|
74
|
+
const { collectAsciidoctorSections } = await import("../parsers/asciidoctor.js");
|
|
75
|
+
return collectAsciidoctorSections(file);
|
|
76
|
+
}
|
|
72
77
|
async function collectParserReferenceTargets(file) {
|
|
73
78
|
const { collectAsciidoctorReferenceTargets } = await import("../parsers/asciidoctor.js");
|
|
74
79
|
return collectAsciidoctorReferenceTargets(file);
|
|
75
80
|
}
|
|
81
|
+
function mergeAsciidoctorSections(document, sections) {
|
|
82
|
+
if (!sections.length) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
for (const section of sections) {
|
|
86
|
+
const existing = document.sections.find((candidate) => (path.resolve(candidate.range.start.file) === path.resolve(section.range.start.file)
|
|
87
|
+
&& candidate.range.start.line === section.range.start.line
|
|
88
|
+
&& candidate.title === section.title));
|
|
89
|
+
if (!existing) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
existing.sectname = section.sectname;
|
|
93
|
+
existing.source = "asciidoctor";
|
|
94
|
+
if (!existing.style && section.style) {
|
|
95
|
+
existing.style = section.style;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
76
99
|
function mergeAsciidoctorBlocks(document, blocks) {
|
|
77
100
|
if (!blocks.length) {
|
|
78
101
|
return;
|
|
79
102
|
}
|
|
80
|
-
const authoritativeTypes = new Set(blocks
|
|
103
|
+
const authoritativeTypes = new Set(blocks
|
|
104
|
+
.filter((block) => ["table", "image", "diagram"].includes(block.type))
|
|
105
|
+
.map((block) => block.type));
|
|
81
106
|
const authoritativeFiles = new Set(blocks.map((block) => path.resolve(block.range.start.file)));
|
|
82
|
-
|
|
107
|
+
const merged = [
|
|
83
108
|
...document.blocks.filter((block) => (!authoritativeTypes.has(block.type)
|
|
84
109
|
|| !authoritativeFiles.has(path.resolve(block.range.start.file)))),
|
|
85
|
-
|
|
86
|
-
|
|
110
|
+
];
|
|
111
|
+
for (const block of blocks) {
|
|
112
|
+
if (!merged.some((candidate) => sameBlock(candidate, block))) {
|
|
113
|
+
merged.push(block);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
document.blocks = merged.sort((a, b) => (a.range.start.file.localeCompare(b.range.start.file)
|
|
87
117
|
|| a.range.start.line - b.range.start.line
|
|
88
118
|
|| a.range.start.column - b.range.start.column));
|
|
89
119
|
}
|
|
120
|
+
function sameBlock(a, b) {
|
|
121
|
+
return path.resolve(a.range.start.file) === path.resolve(b.range.start.file)
|
|
122
|
+
&& a.range.start.line === b.range.start.line
|
|
123
|
+
&& a.range.start.column === b.range.start.column
|
|
124
|
+
&& a.type === b.type
|
|
125
|
+
&& a.style === b.style;
|
|
126
|
+
}
|
|
90
127
|
function mergeAsciidoctorReferenceTargets(document, targets) {
|
|
91
128
|
const byKey = new Map();
|
|
92
129
|
for (const target of document.referenceTargets) {
|
package/dist/api/rules.js
CHANGED
|
@@ -90,7 +90,7 @@ function configSources(options) {
|
|
|
90
90
|
return sources;
|
|
91
91
|
}
|
|
92
92
|
const project = findProjectConfig(options.cwd);
|
|
93
|
-
if (project) {
|
|
93
|
+
if (project && !sources.some((source) => path.resolve(source.file) === path.resolve(project))) {
|
|
94
94
|
sources.push({ kind: "project", file: project });
|
|
95
95
|
}
|
|
96
96
|
return sources;
|
package/dist/cli/index.js
CHANGED
|
@@ -38,13 +38,13 @@ program
|
|
|
38
38
|
.command("install-skill")
|
|
39
39
|
.description("install the bundled AI-agent skill")
|
|
40
40
|
.option("--dest <directory>", "skills root directory")
|
|
41
|
-
.option("--agent <agent>", "
|
|
42
|
-
.option("--project", "install into
|
|
41
|
+
.option("--agent <agent>", "deprecated; fails with guidance instead of selecting a skill root")
|
|
42
|
+
.option("--project", "install into project .agents and .claude skill roots")
|
|
43
43
|
.option("--force", "replace an existing installed skill")
|
|
44
44
|
.action((options) => {
|
|
45
45
|
try {
|
|
46
46
|
const result = installSkill(options);
|
|
47
|
-
console.log(`Installed asciidoclint skill to ${result.
|
|
47
|
+
console.log(`Installed asciidoclint skill to ${result.destinations.join(", ")}`);
|
|
48
48
|
}
|
|
49
49
|
catch (error) {
|
|
50
50
|
console.error(error instanceof Error ? error.message : String(error));
|
|
@@ -55,14 +55,14 @@ program
|
|
|
55
55
|
.command("uninstall-skill")
|
|
56
56
|
.description("uninstall the AI-agent skill")
|
|
57
57
|
.option("--dest <directory>", "skills root directory")
|
|
58
|
-
.option("--agent <agent>", "
|
|
59
|
-
.option("--project", "uninstall from
|
|
58
|
+
.option("--agent <agent>", "deprecated; fails with guidance instead of selecting a skill root")
|
|
59
|
+
.option("--project", "uninstall from project .agents and .claude skill roots")
|
|
60
60
|
.action((options) => {
|
|
61
61
|
try {
|
|
62
62
|
const result = uninstallSkill(options);
|
|
63
63
|
console.log(result.removed
|
|
64
|
-
? `Uninstalled asciidoclint skill from ${result.
|
|
65
|
-
: `No asciidoclint skill installed at ${result.
|
|
64
|
+
? `Uninstalled asciidoclint skill from ${result.removedDestinations.join(", ")}`
|
|
65
|
+
: `No asciidoclint skill installed at ${result.destinations.join(", ")}`);
|
|
66
66
|
}
|
|
67
67
|
catch (error) {
|
|
68
68
|
console.error(error instanceof Error ? error.message : String(error));
|
|
@@ -3,19 +3,24 @@ export interface InstallSkillOptions {
|
|
|
3
3
|
project?: boolean;
|
|
4
4
|
agent?: SupportedSkillAgent;
|
|
5
5
|
force?: boolean;
|
|
6
|
+
homeDir?: string;
|
|
6
7
|
}
|
|
7
8
|
export interface InstallSkillResult {
|
|
8
9
|
source: string;
|
|
9
10
|
destination: string;
|
|
11
|
+
destinations: string[];
|
|
10
12
|
}
|
|
11
13
|
export interface UninstallSkillOptions {
|
|
12
14
|
dest?: string;
|
|
13
15
|
project?: boolean;
|
|
14
16
|
agent?: SupportedSkillAgent;
|
|
17
|
+
homeDir?: string;
|
|
15
18
|
}
|
|
16
19
|
export interface UninstallSkillResult {
|
|
17
20
|
destination: string;
|
|
21
|
+
destinations: string[];
|
|
18
22
|
removed: boolean;
|
|
23
|
+
removedDestinations: string[];
|
|
19
24
|
}
|
|
20
25
|
export type SupportedSkillAgent = "codex" | "cursor" | "claude-code" | "openclaw";
|
|
21
26
|
export declare function installSkill(options?: InstallSkillOptions): InstallSkillResult;
|
|
@@ -3,60 +3,149 @@ import os from "node:os";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
const skillName = "asciidoclint";
|
|
6
|
-
const
|
|
7
|
-
const skillAgentRoots = {
|
|
8
|
-
codex: {
|
|
9
|
-
project: path.join(".agents", "skills"),
|
|
10
|
-
global: path.join(os.homedir(), ".codex", "skills"),
|
|
11
|
-
},
|
|
12
|
-
cursor: {
|
|
13
|
-
project: path.join(".agents", "skills"),
|
|
14
|
-
global: path.join(os.homedir(), ".cursor", "skills"),
|
|
15
|
-
},
|
|
16
|
-
"claude-code": {
|
|
17
|
-
project: path.join(".claude", "skills"),
|
|
18
|
-
global: path.join(os.homedir(), ".claude", "skills"),
|
|
19
|
-
},
|
|
20
|
-
openclaw: {
|
|
21
|
-
project: "skills",
|
|
22
|
-
global: path.join(os.homedir(), ".openclaw", "skills"),
|
|
23
|
-
},
|
|
24
|
-
};
|
|
6
|
+
const deprecatedAgentMessage = "--agent is deprecated and no longer selects a skill root. Use the command without --agent for the default global roots, add --project for project roots, or use --dest <directory> for a custom root.";
|
|
25
7
|
export function installSkill(options = {}) {
|
|
8
|
+
rejectDeprecatedAgentOption(options.agent);
|
|
26
9
|
const source = findBundledSkill();
|
|
27
|
-
|
|
28
|
-
|
|
10
|
+
if (options.dest) {
|
|
11
|
+
const destination = path.join(path.resolve(options.dest), skillName);
|
|
12
|
+
installCopy(source, destination, options.force);
|
|
13
|
+
return { source, destination, destinations: [destination] };
|
|
14
|
+
}
|
|
15
|
+
if (options.project) {
|
|
16
|
+
const destinations = projectSkillDestinations(process.cwd());
|
|
17
|
+
prepareDestinations(destinations, options.force);
|
|
18
|
+
installPreparedDestinations(destinations, (destination) => writeSymlink(source, destination));
|
|
19
|
+
return { source, destination: destinations[0], destinations };
|
|
20
|
+
}
|
|
21
|
+
const { common, claude } = globalSkillDestinations(options.homeDir);
|
|
22
|
+
prepareDestinations([common, claude], options.force);
|
|
23
|
+
installPreparedDestinations([common, claude], (destination) => {
|
|
24
|
+
if (destination === common) {
|
|
25
|
+
writeCopy(source, common);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
writeSymlink(common, claude);
|
|
29
|
+
});
|
|
30
|
+
return { source, destination: common, destinations: [common, claude] };
|
|
31
|
+
}
|
|
32
|
+
export function uninstallSkill(options = {}) {
|
|
33
|
+
rejectDeprecatedAgentOption(options.agent);
|
|
34
|
+
const destinations = uninstallDestinations(options);
|
|
35
|
+
const removedDestinations = [];
|
|
36
|
+
for (const destination of destinations) {
|
|
37
|
+
if (pathExists(destination)) {
|
|
38
|
+
removePath(destination);
|
|
39
|
+
removedDestinations.push(destination);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
// For global uninstall, destinations are ordered for removal safety:
|
|
44
|
+
// Claude symlink first, then the common copy.
|
|
45
|
+
destination: destinations[0],
|
|
46
|
+
destinations,
|
|
47
|
+
removed: removedDestinations.length > 0,
|
|
48
|
+
removedDestinations,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function rejectDeprecatedAgentOption(agent) {
|
|
52
|
+
if (agent) {
|
|
53
|
+
throw new Error(deprecatedAgentMessage);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function uninstallDestinations(options) {
|
|
57
|
+
if (options.dest) {
|
|
58
|
+
return [path.join(path.resolve(options.dest), skillName)];
|
|
59
|
+
}
|
|
60
|
+
if (options.project) {
|
|
61
|
+
return projectSkillDestinations(process.cwd());
|
|
62
|
+
}
|
|
63
|
+
const { common, claude } = globalSkillDestinations(options.homeDir);
|
|
64
|
+
return [claude, common];
|
|
65
|
+
}
|
|
66
|
+
function projectSkillDestinations(root) {
|
|
67
|
+
return [
|
|
68
|
+
path.resolve(root, ".agents", "skills", skillName),
|
|
69
|
+
path.resolve(root, ".claude", "skills", skillName),
|
|
70
|
+
];
|
|
71
|
+
}
|
|
72
|
+
function globalSkillDestinations(homeDir = os.homedir()) {
|
|
73
|
+
return {
|
|
74
|
+
common: path.join(homeDir, ".agents", "skills", skillName),
|
|
75
|
+
claude: path.join(homeDir, ".claude", "skills", skillName),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function installCopy(source, destination, force = false) {
|
|
29
79
|
if (path.resolve(source) === path.resolve(destination)) {
|
|
30
|
-
return
|
|
80
|
+
return;
|
|
31
81
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
82
|
+
prepareDestination(destination, force);
|
|
83
|
+
writeCopy(source, destination);
|
|
84
|
+
}
|
|
85
|
+
function prepareDestinations(destinations, force = false) {
|
|
86
|
+
for (const destination of destinations) {
|
|
87
|
+
prepareDestination(destination, force);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function installPreparedDestinations(destinations, write) {
|
|
91
|
+
const written = [];
|
|
92
|
+
try {
|
|
93
|
+
for (const destination of destinations) {
|
|
94
|
+
write(destination);
|
|
95
|
+
written.push(destination);
|
|
35
96
|
}
|
|
36
|
-
fs.rmSync(destination, { recursive: true, force: true });
|
|
37
97
|
}
|
|
38
|
-
|
|
98
|
+
catch (error) {
|
|
99
|
+
rollbackWrittenDestinations(written);
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function rollbackWrittenDestinations(destinations) {
|
|
104
|
+
for (const destination of destinations.slice().reverse()) {
|
|
105
|
+
if (pathExists(destination)) {
|
|
106
|
+
removePath(destination);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function writeCopy(source, destination) {
|
|
111
|
+
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
39
112
|
fs.cpSync(source, destination, { recursive: true });
|
|
40
|
-
return { source, destination };
|
|
41
113
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (removed) {
|
|
46
|
-
fs.rmSync(destination, { recursive: true, force: true });
|
|
114
|
+
function writeSymlink(source, destination) {
|
|
115
|
+
if (path.resolve(source) === path.resolve(destination)) {
|
|
116
|
+
return;
|
|
47
117
|
}
|
|
48
|
-
|
|
118
|
+
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
119
|
+
fs.symlinkSync(path.relative(path.dirname(destination), source), destination, "dir");
|
|
49
120
|
}
|
|
50
|
-
function
|
|
51
|
-
if (
|
|
52
|
-
return
|
|
121
|
+
function prepareDestination(destination, force) {
|
|
122
|
+
if (!pathExists(destination)) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (!force) {
|
|
126
|
+
throw new Error(`${destination} already exists; pass --force to replace it`);
|
|
127
|
+
}
|
|
128
|
+
removePath(destination);
|
|
129
|
+
}
|
|
130
|
+
function removePath(file) {
|
|
131
|
+
const stat = fs.lstatSync(file);
|
|
132
|
+
if (stat.isSymbolicLink()) {
|
|
133
|
+
fs.unlinkSync(file);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
fs.rmSync(file, { recursive: true, force: true });
|
|
137
|
+
}
|
|
138
|
+
function pathExists(file) {
|
|
139
|
+
try {
|
|
140
|
+
fs.lstatSync(file);
|
|
141
|
+
return true;
|
|
53
142
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
143
|
+
catch (error) {
|
|
144
|
+
if (error.code === "ENOENT") {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
throw error;
|
|
58
148
|
}
|
|
59
|
-
return options.project ? path.resolve(process.cwd(), roots.project) : roots.global;
|
|
60
149
|
}
|
|
61
150
|
function findBundledSkill() {
|
|
62
151
|
let current = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { BlockNode, LintFinding, ReferenceTarget } from "../types.js";
|
|
1
|
+
import type { BlockNode, LintFinding, ReferenceTarget, SectionNode } from "../types.js";
|
|
2
2
|
export declare function collectAsciidoctorDiagnostics(file: string): LintFinding[];
|
|
3
3
|
export declare function collectAsciidoctorBlocks(file: string): BlockNode[];
|
|
4
|
+
export declare function collectAsciidoctorSections(file: string): SectionNode[];
|
|
4
5
|
export declare function collectAsciidoctorReferenceTargets(file: string): ReferenceTarget[];
|