@rse/ase 2.0.3 → 2.0.4
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/dst/ase-hook.js +1 -1
- package/dst/ase-lib-version.js +14 -5
- package/dst/ase-man-catalog.js +229 -0
- package/dst/ase-man-render.js +278 -0
- package/dst/ase-man-tui.js +636 -0
- package/dst/ase-man.js +33 -27
- package/dst/ase-setup.js +2 -2
- package/dst/ase-spec.js +103 -20
- package/dst/ase-task-board-filter.js +5 -4
- package/dst/ase-task-board-web-client.js +26 -26
- package/dst/man/ase-agent-adhoc.md +1165 -0
- package/dst/man/ase-agent-condenser.md +383 -0
- package/dst/man/ase-agent-editor.md +441 -0
- package/dst/man/ase-agent-estimator.md +477 -0
- package/dst/man/ase-agent-fleet.md +358 -0
- package/dst/man/ase-agent-implementer.md +521 -0
- package/dst/man/ase-agent-integrator.md +449 -0
- package/dst/man/ase-agent-qualifier.md +426 -0
- package/dst/man/ase-agent-rebooter.md +384 -0
- package/dst/man/ase-agent-relay.md +262 -0
- package/dst/man/ase-agent-researcher.md +478 -0
- package/dst/man/ase-agent.md +628 -0
- package/dst/man/ase-artifact-list.md +57 -0
- package/dst/man/ase-artifact-name.md +56 -0
- package/dst/man/ase-artifact.md +92 -0
- package/dst/man/ase-config-delete.md +74 -0
- package/dst/man/ase-config-edit.md +83 -0
- package/dst/man/ase-config-get.md +70 -0
- package/dst/man/ase-config-init.md +110 -0
- package/dst/man/ase-config-list.md +68 -0
- package/dst/man/ase-config-set.md +92 -0
- package/dst/man/ase-config.md +426 -0
- package/dst/man/ase-hook-permission-request.md +61 -0
- package/dst/man/ase-hook-pre-tool-use.md +96 -0
- package/dst/man/ase-hook-session-end.md +71 -0
- package/dst/man/ase-hook-session-persona.md +65 -0
- package/dst/man/ase-hook-session-start.md +184 -0
- package/dst/man/ase-hook-stop.md +52 -0
- package/dst/man/ase-hook-user-prompt-submit.md +54 -0
- package/dst/man/ase-hook.md +93 -0
- package/dst/man/ase-man.md +195 -0
- package/dst/man/ase-mcp.md +102 -0
- package/dst/man/ase-service-log.md +59 -0
- package/dst/man/ase-service-send.md +60 -0
- package/dst/man/ase-service-start.md +93 -0
- package/dst/man/ase-service-status.md +58 -0
- package/dst/man/ase-service-stop.md +52 -0
- package/dst/man/ase-service.md +124 -0
- package/dst/man/ase-setup-disable.md +67 -0
- package/dst/man/ase-setup-enable.md +66 -0
- package/dst/man/ase-setup-install.md +116 -0
- package/dst/man/ase-setup-mcp.md +213 -0
- package/dst/man/ase-setup-status.md +101 -0
- package/dst/man/ase-setup-statusline.md +162 -0
- package/dst/man/ase-setup-telemetry.md +166 -0
- package/dst/man/ase-setup-uninstall.md +85 -0
- package/dst/man/ase-setup-update.md +117 -0
- package/dst/man/ase-setup.md +156 -0
- package/dst/man/ase-spec-export.md +99 -0
- package/dst/man/ase-spec-init.md +57 -0
- package/dst/man/ase-spec-lint.md +74 -0
- package/dst/man/ase-spec-preview.md +57 -0
- package/dst/man/ase-spec.md +85 -0
- package/dst/man/ase-statusline.md +330 -0
- package/dst/man/ase-task-assign.md +138 -0
- package/dst/man/ase-task-board.md +651 -0
- package/dst/man/ase-task-delete.md +46 -0
- package/dst/man/ase-task-edit.md +73 -0
- package/dst/man/ase-task-idscheme.md +92 -0
- package/dst/man/ase-task-lifecycle.md +92 -0
- package/dst/man/ase-task-list.md +86 -0
- package/dst/man/ase-task-load.md +48 -0
- package/dst/man/ase-task-newid.md +100 -0
- package/dst/man/ase-task-purge.md +49 -0
- package/dst/man/ase-task-rename.md +69 -0
- package/dst/man/ase-task-request.md +108 -0
- package/dst/man/ase-task-save.md +78 -0
- package/dst/man/ase-task-status.md +79 -0
- package/dst/man/ase-task-store.md +377 -0
- package/dst/man/ase-task-view.md +50 -0
- package/dst/man/ase-task.md +335 -0
- package/dst/man/ase-util-compat.md +38 -0
- package/dst/man/ase-util-diagram.md +140 -0
- package/dst/man/ase-util-meta.md +68 -0
- package/dst/man/ase-util-metric.md +74 -0
- package/dst/man/ase-util-mint.md +85 -0
- package/dst/man/ase-util-worktree.md +109 -0
- package/dst/man/ase-util.md +81 -0
- package/dst/man/ase.md +228 -0
- package/package.json +12 -17
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/.github/plugin/plugin.json +1 -1
- package/plugin/agents/ase-code-lint.md +3 -3
- package/plugin/agents/ase-docs-proofread.md +3 -3
- package/plugin/agents/ase-docs-refine.md +3 -3
- package/plugin/agents/ase-spec-lint.md +338 -0
- package/plugin/mods/claude-code.d.ts +26 -3
- package/plugin/skills/ase-help-intent/data.md +180 -1
- package/plugin/skills/ase-help-skill/catalog.md +1 -0
- package/plugin/skills/ase-meta-workflow/workflow.txt +2 -0
- package/plugin/skills/ase-repo-commit/SKILL.md +1 -0
- package/plugin/skills/ase-repo-merge/SKILL.md +1 -0
- package/plugin/skills/ase-spec-edit/SKILL.md +3 -3
- package/plugin/skills/ase-spec-edit/help.md +1 -1
- package/plugin/skills/ase-spec-lint/SKILL.md +695 -0
- package/plugin/skills/ase-spec-lint/help.md +171 -0
- package/plugin/skills/ase-task-implement/SKILL.md +24 -6
- package/plugin/skills/ase-task-implement/help.md +11 -1
- package/dst/man/ase-agent-adhoc.1 +0 -515
- package/dst/man/ase-agent-condenser.1 +0 -193
- package/dst/man/ase-agent-editor.1 +0 -212
- package/dst/man/ase-agent-estimator.1 +0 -214
- package/dst/man/ase-agent-fleet.1 +0 -185
- package/dst/man/ase-agent-implementer.1 +0 -233
- package/dst/man/ase-agent-integrator.1 +0 -213
- package/dst/man/ase-agent-qualifier.1 +0 -214
- package/dst/man/ase-agent-rebooter.1 +0 -193
- package/dst/man/ase-agent-relay.1 +0 -128
- package/dst/man/ase-agent-researcher.1 +0 -217
- package/dst/man/ase-agent.1 +0 -264
- package/dst/man/ase-artifact-list.1 +0 -38
- package/dst/man/ase-artifact-name.1 +0 -39
- package/dst/man/ase-artifact.1 +0 -56
- package/dst/man/ase-config-delete.1 +0 -45
- package/dst/man/ase-config-edit.1 +0 -48
- package/dst/man/ase-config-get.1 +0 -46
- package/dst/man/ase-config-init.1 +0 -60
- package/dst/man/ase-config-list.1 +0 -42
- package/dst/man/ase-config-set.1 +0 -52
- package/dst/man/ase-config.1 +0 -231
- package/dst/man/ase-hook-permission-request.1 +0 -36
- package/dst/man/ase-hook-pre-tool-use.1 +0 -59
- package/dst/man/ase-hook-session-end.1 +0 -47
- package/dst/man/ase-hook-session-persona.1 +0 -37
- package/dst/man/ase-hook-session-start.1 +0 -81
- package/dst/man/ase-hook-stop.1 +0 -34
- package/dst/man/ase-hook-user-prompt-submit.1 +0 -34
- package/dst/man/ase-hook.1 +0 -51
- package/dst/man/ase-man.1 +0 -67
- package/dst/man/ase-mcp.1 +0 -61
- package/dst/man/ase-service-log.1 +0 -43
- package/dst/man/ase-service-send.1 +0 -47
- package/dst/man/ase-service-start.1 +0 -54
- package/dst/man/ase-service-status.1 +0 -46
- package/dst/man/ase-service-stop.1 +0 -37
- package/dst/man/ase-service.1 +0 -85
- package/dst/man/ase-setup-disable.1 +0 -44
- package/dst/man/ase-setup-enable.1 +0 -45
- package/dst/man/ase-setup-install.1 +0 -73
- package/dst/man/ase-setup-mcp.1 +0 -124
- package/dst/man/ase-setup-status.1 +0 -60
- package/dst/man/ase-setup-statusline.1 +0 -92
- package/dst/man/ase-setup-telemetry.1 +0 -92
- package/dst/man/ase-setup-uninstall.1 +0 -59
- package/dst/man/ase-setup-update.1 +0 -64
- package/dst/man/ase-setup.1 +0 -93
- package/dst/man/ase-spec-export.1 +0 -62
- package/dst/man/ase-spec-lint.1 +0 -40
- package/dst/man/ase-spec-preview.1 +0 -40
- package/dst/man/ase-spec.1 +0 -46
- package/dst/man/ase-statusline.1 +0 -189
- package/dst/man/ase-task-assign.1 +0 -71
- package/dst/man/ase-task-board.1 +0 -384
- package/dst/man/ase-task-delete.1 +0 -35
- package/dst/man/ase-task-edit.1 +0 -46
- package/dst/man/ase-task-idscheme.1 +0 -65
- package/dst/man/ase-task-lifecycle.1 +0 -57
- package/dst/man/ase-task-list.1 +0 -47
- package/dst/man/ase-task-load.1 +0 -36
- package/dst/man/ase-task-newid.1 +0 -61
- package/dst/man/ase-task-purge.1 +0 -37
- package/dst/man/ase-task-rename.1 +0 -44
- package/dst/man/ase-task-request.1 +0 -61
- package/dst/man/ase-task-save.1 +0 -48
- package/dst/man/ase-task-status.1 +0 -55
- package/dst/man/ase-task-store.1 +0 -194
- package/dst/man/ase-task-view.1 +0 -40
- package/dst/man/ase-task.1 +0 -160
- package/dst/man/ase-util-compat.1 +0 -25
- package/dst/man/ase-util-diagram.1 +0 -76
- package/dst/man/ase-util-meta.1 +0 -45
- package/dst/man/ase-util-metric.1 +0 -54
- package/dst/man/ase-util-mint.1 +0 -59
- package/dst/man/ase-util-worktree.1 +0 -72
- package/dst/man/ase-util.1 +0 -50
- package/dst/man/ase.1 +0 -124
package/dst/ase-man.js
CHANGED
|
@@ -5,13 +5,14 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import fs from "node:fs";
|
|
8
|
-
import { spawnSync } from "node:child_process";
|
|
9
8
|
import { fileURLToPath } from "node:url";
|
|
10
9
|
import { writeStdout } from "./ase-lib-stdio.js";
|
|
10
|
+
/* the width of the manual pages printed as plain text */
|
|
11
|
+
const WIDTH = 80;
|
|
11
12
|
/* reusable functionality: resolve the internally shipped manual pages */
|
|
12
13
|
export class Man {
|
|
13
14
|
/* determine the directory of the manual pages; the build process
|
|
14
|
-
|
|
15
|
+
copies "man/*.md" into "dst/man/*.md" (see the "build-man" build
|
|
15
16
|
step), right next to the compiled tool */
|
|
16
17
|
static dir() {
|
|
17
18
|
return path.join(path.dirname(fileURLToPath(import.meta.url)), "man");
|
|
@@ -26,15 +27,15 @@ export class Man {
|
|
|
26
27
|
throw new Error(`man: no manual pages found in "${Man.dir()}" (tool not completely built?)`, { cause: err });
|
|
27
28
|
}
|
|
28
29
|
return files
|
|
29
|
-
.filter((file) => /^ase(?:-[a-z0-9-]+)
|
|
30
|
-
.map((file) => file.replace(
|
|
30
|
+
.filter((file) => /^ase(?:-[a-z0-9-]+)?\.md$/.test(file))
|
|
31
|
+
.map((file) => file.replace(/\.md$/, ""))
|
|
31
32
|
.sort((a, b) => a.localeCompare(b, "en", { numeric: true }));
|
|
32
33
|
}
|
|
33
|
-
/* resolve the words of a requested manual page to
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
/* resolve the words of a requested manual page to its name: the words
|
|
35
|
+
are joined with dashes, the "ase" prefix is optional, and a command path
|
|
36
|
+
deeper than the shipped pages (e.g. "ase task store start") falls back to
|
|
37
|
+
the page of its nearest parent (e.g. "ase-task-store"), as the pages of a
|
|
38
|
+
group with third-level commands cover them */
|
|
38
39
|
static resolve(words) {
|
|
39
40
|
const name = words.join("-").toLowerCase();
|
|
40
41
|
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name))
|
|
@@ -46,7 +47,7 @@ export class Man {
|
|
|
46
47
|
while (parts.length > 0) {
|
|
47
48
|
const page = parts.join("-");
|
|
48
49
|
if (pages.has(page))
|
|
49
|
-
return
|
|
50
|
+
return page;
|
|
50
51
|
parts.pop();
|
|
51
52
|
if (parts.length === 1)
|
|
52
53
|
break;
|
|
@@ -64,33 +65,38 @@ export default class ManCommand {
|
|
|
64
65
|
register(program) {
|
|
65
66
|
program
|
|
66
67
|
.command("man")
|
|
67
|
-
.description("
|
|
68
|
+
.description("Browse all manual pages interactively, or print a manual page as plain text")
|
|
68
69
|
.argument("[page...]", "manual page name, either as \"ase-<command>[-<sub-command>]\" " +
|
|
69
|
-
"or as the words of the command path (\"ase\" prefix optional,
|
|
70
|
+
"or as the words of the command path (\"ase\" prefix optional), selected in the " +
|
|
71
|
+
"interactive browser on a terminal (or searched for, if no manual page fits exactly), " +
|
|
72
|
+
"else printed as plain text (default: \"ase\")")
|
|
70
73
|
.option("-l, --list", "list the names of all shipped manual pages instead", false)
|
|
71
|
-
.option("-p, --path", "print the path of the manual page file instead", false)
|
|
72
74
|
.action(async (words, opts) => {
|
|
73
75
|
/* list all manual pages */
|
|
74
76
|
if (opts.list) {
|
|
75
77
|
await writeStdout(Man.list().map((page) => `${page}\n`).join(""));
|
|
76
78
|
return;
|
|
77
79
|
}
|
|
78
|
-
/*
|
|
79
|
-
|
|
80
|
-
if (
|
|
81
|
-
await
|
|
80
|
+
/* browse all manual pages interactively (only on a terminal), with
|
|
81
|
+
the given manual page selected resp. searched for (if any) */
|
|
82
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
83
|
+
const { runManTUI } = await import("./ase-man-tui.js");
|
|
84
|
+
await runManTUI(this.log, words);
|
|
82
85
|
return;
|
|
83
86
|
}
|
|
84
|
-
/*
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
87
|
+
/* resolve manual page, where a skill or command fitting exactly
|
|
88
|
+
is preferred over the fallback onto the nearest parent command */
|
|
89
|
+
const { loadCatalog, findPage } = await import("./ase-man-catalog.js");
|
|
90
|
+
const { renderPage } = await import("./ase-man-render.js");
|
|
91
|
+
const catalog = loadCatalog();
|
|
92
|
+
const request = words.length > 0 ? words : ["ase"];
|
|
93
|
+
const page = findPage(catalog, request) ?? catalog.pages.get(`${Man.resolve(request)}.1`);
|
|
94
|
+
if (page === undefined)
|
|
95
|
+
throw new Error(`man: no manual page found for "${request.join(" ")}" (see "ase man --list")`);
|
|
96
|
+
/* print manual page as plain text */
|
|
97
|
+
this.log.write("debug", `man: printing manual page "${page.id}"`);
|
|
98
|
+
const { lines } = renderPage(page.md, WIDTH, page.id, (id) => catalog.pages.has(id));
|
|
99
|
+
await writeStdout(lines.map((line) => line.map((span) => span.text).join("").trimEnd() + "\n").join(""));
|
|
94
100
|
});
|
|
95
101
|
}
|
|
96
102
|
}
|
package/dst/ase-setup.js
CHANGED
|
@@ -71,10 +71,10 @@ export default class SetupCommand {
|
|
|
71
71
|
await this.runner.run(spec.cli, ["plugin", spec.pInstall, "ase@ase", ...scopeArgs], { retries: 3 });
|
|
72
72
|
}
|
|
73
73
|
else {
|
|
74
|
-
/* perform NPM version check */
|
|
74
|
+
/* perform direct NPM registry version check */
|
|
75
75
|
const current = Version.current();
|
|
76
76
|
this.log.write("info", `setup: update: used ASE version: ${current}`);
|
|
77
|
-
const latest = await Version.latest();
|
|
77
|
+
const latest = await Version.latest(true);
|
|
78
78
|
if (!force && latest !== "" && latest === current) {
|
|
79
79
|
this.log.write("info", `setup: update: ASE already at latest version ${current}`);
|
|
80
80
|
return 0;
|
package/dst/ase-spec.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import fs from "node:fs";
|
|
8
|
+
import os from "node:os";
|
|
8
9
|
import { isScalar } from "yaml";
|
|
9
10
|
import { z } from "zod";
|
|
10
11
|
import sourceCodeError from "source-code-error";
|
|
@@ -14,11 +15,11 @@ import { Config } from "./ase-config-core.js";
|
|
|
14
15
|
import { configSchema } from "./ase-config-schema.js";
|
|
15
16
|
import { Task } from "./ase-task.js";
|
|
16
17
|
import { Artifact } from "./ase-artifact.js";
|
|
17
|
-
import { Meta } from "./ase-util-meta.js";
|
|
18
18
|
import { writeStdout } from "./ase-lib-stdio.js";
|
|
19
|
-
/* reusable functionality: lint, export, and preview the
|
|
20
|
-
project specification, located via the
|
|
21
|
-
and "project.artifact.spec.schema"
|
|
19
|
+
/* reusable functionality: initialize, lint, export, and preview the
|
|
20
|
+
SpecBook-based project specification, located via the
|
|
21
|
+
"project.artifact.spec.basedir" and "project.artifact.spec.schema"
|
|
22
|
+
configuration */
|
|
22
23
|
export class Spec {
|
|
23
24
|
/* the SpecBook export formats, mirrored here (and checked for completeness
|
|
24
25
|
against SpecBook at compile-time) to not load SpecBook for the MCP schema */
|
|
@@ -28,8 +29,9 @@ export class Spec {
|
|
|
28
29
|
/* resolve the YAML schema configuration files: the whitespace-separated
|
|
29
30
|
entries of the configured "project.artifact.spec.schema", merged in
|
|
30
31
|
the given order, where each entry is either the literal "std" for the
|
|
31
|
-
|
|
32
|
-
path relative to the project
|
|
32
|
+
full SpecBook standard schema (not the agent-compressed plugin meta file
|
|
33
|
+
"ase-format-specbook.yaml") or a file path relative to the project
|
|
34
|
+
root, and an unset or empty value means "std" */
|
|
33
35
|
static configFiles(log) {
|
|
34
36
|
const cfg = new Config("config", configSchema, log);
|
|
35
37
|
cfg.read();
|
|
@@ -39,7 +41,7 @@ export class Spec {
|
|
|
39
41
|
if (entries.length === 0)
|
|
40
42
|
entries.push("std");
|
|
41
43
|
return entries.map((entry) => entry === "std" ?
|
|
42
|
-
|
|
44
|
+
"std" :
|
|
43
45
|
path.resolve(Task.projectRoot(), entry));
|
|
44
46
|
}
|
|
45
47
|
/* the ASE log level each SpecBook verbosity level maps onto: the
|
|
@@ -93,21 +95,81 @@ export class Spec {
|
|
|
93
95
|
return file;
|
|
94
96
|
return rel.replace(/\\/g, "/");
|
|
95
97
|
}
|
|
96
|
-
/* every specification command honors the Git exclude rules, so that
|
|
98
|
+
/* every reading specification command honors the Git exclude rules, so that
|
|
97
99
|
SpecBook resolves the very same artifact files as "ase artifact
|
|
98
100
|
list --kind spec": both consult ".gitignore", so a specification
|
|
99
101
|
the project does not track is invisible to both instead of only
|
|
100
102
|
to one of them */
|
|
101
103
|
static gitignore = true;
|
|
104
|
+
/* initialize the absent artifact files of the schema configuration below
|
|
105
|
+
the "spec" artifact base directory, letting SpecBook persist its project
|
|
106
|
+
options file ".specbook.yaml" into a throw-away working directory, as
|
|
107
|
+
ASE keeps these options in its own configuration */
|
|
108
|
+
static async init(log) {
|
|
109
|
+
const basedir = Artifact.basedir(log, "spec");
|
|
110
|
+
const cwd = await fs.promises.mkdtemp(path.join(os.tmpdir(), "ase-spec-"));
|
|
111
|
+
try {
|
|
112
|
+
const result = await Spec.unmarked(Spec.api(log).then((api) => api.init({
|
|
113
|
+
config: Spec.configFiles(log),
|
|
114
|
+
basedir,
|
|
115
|
+
cwd
|
|
116
|
+
})));
|
|
117
|
+
return result.files.map((file) => Spec.relativize(path.join(basedir, file)));
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
await fs.promises.rm(cwd, { recursive: true, force: true });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
102
123
|
/* lint the specification Markdown files below the "spec" artifact
|
|
103
|
-
base directory against the schema configuration
|
|
104
|
-
|
|
124
|
+
base directory against the schema configuration, optionally
|
|
125
|
+
followed by the generic Markdown linting */
|
|
126
|
+
static async lint(log, markdown = false) {
|
|
105
127
|
const result = await Spec.unmarked(Spec.api(log).then((api) => api.lint({
|
|
106
128
|
config: Spec.configFiles(log),
|
|
107
129
|
basedir: Artifact.basedir(log, "spec"),
|
|
108
130
|
gitignore: Spec.gitignore
|
|
109
131
|
})));
|
|
110
|
-
|
|
132
|
+
const diagnostics = result.diagnostics.map((d) => ({ ...d, file: Spec.relativize(d.file) }));
|
|
133
|
+
return markdown ? diagnostics.concat(await Spec.markdownlint(log)) : diagnostics;
|
|
134
|
+
}
|
|
135
|
+
/* the markdownlint rules conflicting with the SpecBook format
|
|
136
|
+
(trailing blank line, mixed ATX and Setext headings, free ordered list numbering,
|
|
137
|
+
padded list markers and headings, unwrapped lines, frontmatter first, images without
|
|
138
|
+
alternate text, and multiple artifacts or same-named objects per file) */
|
|
139
|
+
static markdownConfig = {
|
|
140
|
+
"no-multiple-blanks": { maximum: 2 },
|
|
141
|
+
"heading-style": false,
|
|
142
|
+
"ol-prefix": false,
|
|
143
|
+
"no-alt-text": false,
|
|
144
|
+
"list-marker-space": false,
|
|
145
|
+
"ul-indent": false,
|
|
146
|
+
"no-multiple-space-atx": false,
|
|
147
|
+
"line-length": false,
|
|
148
|
+
"first-line-heading": false,
|
|
149
|
+
"single-title": false,
|
|
150
|
+
"no-duplicate-heading": false,
|
|
151
|
+
"no-inline-html": false
|
|
152
|
+
};
|
|
153
|
+
/* the message prefix of the markdownlint diagnostics */
|
|
154
|
+
static markdownPrefix = "markdownlint: ";
|
|
155
|
+
/* lint the specification Markdown files with markdownlint, reporting
|
|
156
|
+
its findings as warnings, as they never break the SpecBook parsing
|
|
157
|
+
(markdownlint itself is loaded on first use only) */
|
|
158
|
+
static async markdownlint(log) {
|
|
159
|
+
const { lint } = await import("markdownlint/promise");
|
|
160
|
+
const files = Artifact.list(log, ["spec"]).flatMap((entry) => entry.files)
|
|
161
|
+
.filter((file) => /\.md$/i.test(file))
|
|
162
|
+
.map((file) => path.resolve(Task.projectRoot(), file));
|
|
163
|
+
const results = await lint({ files, config: Spec.markdownConfig });
|
|
164
|
+
return Object.entries(results).flatMap(([file, errors]) => errors.map((e) => ({
|
|
165
|
+
file: Spec.relativize(file),
|
|
166
|
+
line: e.lineNumber,
|
|
167
|
+
column: e.errorRange?.[0] ?? 1,
|
|
168
|
+
severity: "warning",
|
|
169
|
+
message: `${Spec.markdownPrefix}${e.ruleNames.join("/")}: ${e.ruleDescription}` +
|
|
170
|
+
(e.errorDetail !== null ? ` [${e.errorDetail}]` : "") +
|
|
171
|
+
(e.errorContext !== null ? ` [context: "${e.errorContext}"]` : "")
|
|
172
|
+
})));
|
|
111
173
|
}
|
|
112
174
|
/* render a diagnostic as a multi-line message with the affected
|
|
113
175
|
source snippet (like the verbose SpecBook CLI), falling back to
|
|
@@ -182,22 +244,39 @@ export default class SpecCommand {
|
|
|
182
244
|
/* register CLI top-level command "ase spec" */
|
|
183
245
|
const spec = program
|
|
184
246
|
.command("spec")
|
|
185
|
-
.description("
|
|
247
|
+
.description("Initialize, lint, export, and preview the SpecBook-based project specification")
|
|
186
248
|
.action(() => {
|
|
187
249
|
spec.outputHelp();
|
|
188
250
|
process.exit(1);
|
|
189
251
|
});
|
|
252
|
+
/* register CLI sub-command "ase spec init" */
|
|
253
|
+
spec
|
|
254
|
+
.command("init")
|
|
255
|
+
.description("Initialize the absent specification Markdown files of the SpecBook schema configuration")
|
|
256
|
+
.action(async () => {
|
|
257
|
+
const files = await Spec.init(this.log);
|
|
258
|
+
await writeStdout(files.length > 0 ?
|
|
259
|
+
`initialized artifact file(s): ${files.join(", ")}\n` :
|
|
260
|
+
"no artifact files were created\n");
|
|
261
|
+
});
|
|
190
262
|
/* register CLI sub-command "ase spec lint" */
|
|
191
263
|
spec
|
|
192
264
|
.command("lint")
|
|
193
265
|
.description("Lint the specification Markdown files against the SpecBook schema configuration")
|
|
266
|
+
.option("-m, --markdown", "additionally lint the generic Markdown formatting via markdownlint")
|
|
194
267
|
.option("-v, --verbose", "print each diagnostic with its affected source snippet")
|
|
195
268
|
.action(async (opts) => {
|
|
196
|
-
const diagnostics = await Spec.lint(this.log);
|
|
197
|
-
for (const diagnostic of diagnostics)
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
269
|
+
const diagnostics = await Spec.lint(this.log, opts.markdown === true);
|
|
270
|
+
for (const diagnostic of diagnostics) {
|
|
271
|
+
/* report a diagnostic as a log message of its severity, tagged with its linter */
|
|
272
|
+
const { file, line, column, severity, message } = diagnostic;
|
|
273
|
+
const markdown = message.startsWith(Spec.markdownPrefix);
|
|
274
|
+
if (opts.verbose === true)
|
|
275
|
+
await writeStdout(Spec.render(diagnostic, process.stdout.isTTY === true));
|
|
276
|
+
else
|
|
277
|
+
this.log.write(severity, `lint: ${markdown ? "markdownlint" : "specbook"}: ` +
|
|
278
|
+
`${file}:${line}:${column}: ${markdown ? message.slice(Spec.markdownPrefix.length) : message}`);
|
|
279
|
+
}
|
|
201
280
|
if (diagnostics.some((diagnostic) => diagnostic.severity === "error"))
|
|
202
281
|
process.exitCode = 1;
|
|
203
282
|
});
|
|
@@ -272,8 +351,10 @@ export class SpecMCP {
|
|
|
272
351
|
description: "Lint the SpecBook specification Markdown files of the project (located via the " +
|
|
273
352
|
"`project.artifact.spec.basedir` configuration) against the SpecBook YAML schema " +
|
|
274
353
|
"configuration (`project.artifact.spec.schema`, a whitespace-separated list of schema files " +
|
|
275
|
-
"merged in order, with `std` naming the bundled
|
|
276
|
-
"which is also the default). " +
|
|
354
|
+
"merged in order, with `std` naming the standard schema bundled with SpecBook, " +
|
|
355
|
+
"which is also the default). With `markdown`, the files are additionally linted " +
|
|
356
|
+
"for their generic Markdown formatting via markdownlint, whose findings are reported " +
|
|
357
|
+
"as `warning` diagnostics with a message prefixed by `markdownlint: `. " +
|
|
277
358
|
"Returns a `diagnostics` array of `{ file, line, column, severity, message }` objects (with " +
|
|
278
359
|
"project-relative `file` and a `severity` of `error` or `warning`), rendered as bullet " +
|
|
279
360
|
"points in `text`. With `verbose`, " +
|
|
@@ -282,6 +363,8 @@ export class SpecMCP {
|
|
|
282
363
|
"(`text` of `specification valid`) means the specification is valid; only " +
|
|
283
364
|
"diagnostics of `error` severity make an export fail.",
|
|
284
365
|
inputSchema: {
|
|
366
|
+
markdown: z.boolean().optional()
|
|
367
|
+
.describe("if true, additionally lint the generic Markdown formatting via markdownlint (default: false)"),
|
|
285
368
|
verbose: z.boolean().optional()
|
|
286
369
|
.describe("if true, render each diagnostic with its affected source snippet (default: false)")
|
|
287
370
|
},
|
|
@@ -299,7 +382,7 @@ export class SpecMCP {
|
|
|
299
382
|
}, async (args) => {
|
|
300
383
|
try {
|
|
301
384
|
const verbose = args.verbose ?? false;
|
|
302
|
-
const diagnostics = (await Spec.lint(this.log))
|
|
385
|
+
const diagnostics = (await Spec.lint(this.log, args.markdown ?? false))
|
|
303
386
|
.map((d) => verbose ? { ...d, snippet: Spec.render(d, false) } : d);
|
|
304
387
|
const text = diagnostics.length === 0 ? "specification valid" :
|
|
305
388
|
verbose ?
|
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { effortSizes } from "./ase-task-format.js";
|
|
7
7
|
/* shortest keyword still eligible for fuzzy matching: below it the
|
|
8
|
-
edit distance would equate it with almost any short word
|
|
9
|
-
|
|
8
|
+
edit distance would equate it with almost any short word
|
|
9
|
+
(shared with the search of the manual page browser) */
|
|
10
|
+
export const fuzzyMin = 4;
|
|
10
11
|
/* the Levenshtein edit distance of two words */
|
|
11
|
-
const levenshtein = (a, b) => {
|
|
12
|
+
export const levenshtein = (a, b) => {
|
|
12
13
|
let prev = [];
|
|
13
14
|
let curr = [];
|
|
14
15
|
for (let j = 0; j <= b.length; j++)
|
|
@@ -24,7 +25,7 @@ const levenshtein = (a, b) => {
|
|
|
24
25
|
return prev[b.length];
|
|
25
26
|
};
|
|
26
27
|
/* the Sorensen-Dice bigram similarity coefficient of two words */
|
|
27
|
-
const diceCoefficient = (a, b) => {
|
|
28
|
+
export const diceCoefficient = (a, b) => {
|
|
28
29
|
if (a === b)
|
|
29
30
|
return 1;
|
|
30
31
|
if (a.length < 2 || b.length < 2)
|