@rse/ase 2.0.2 → 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-agent-definition-core.js +111 -87
- package/dst/ase-agent-fleet.js +1 -1
- package/dst/ase-agent-runtime-claim.js +5 -3
- package/dst/ase-agent-runtime-engine.js +1 -1
- package/dst/ase-agent-runtime-phase.js +1 -1
- package/dst/ase-agent-runtime-request.js +1 -1
- package/dst/ase-agent-runtime.js +3 -5
- package/dst/ase-artifact.js +3 -3
- package/dst/ase-config-core.js +2 -16
- package/dst/ase-config-schema.js +1 -3
- package/dst/ase-config.js +2 -0
- package/dst/ase-hook.js +13 -13
- package/dst/ase-lib-daemon-cli.js +117 -4
- package/dst/ase-lib-server-cli.js +4 -5
- 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-mcp.js +4 -4
- package/dst/ase-service-getopt.js +2 -2
- package/dst/ase-service-otel.js +4 -4
- package/dst/ase-service-skills.js +5 -15
- package/dst/ase-service.js +87 -210
- package/dst/ase-setup-common.js +1 -2
- package/dst/ase-setup-settings.js +6 -9
- package/dst/ase-setup.js +4 -5
- package/dst/ase-spec.js +103 -20
- package/dst/ase-statusline.js +170 -170
- package/dst/ase-task-board-filter.js +5 -4
- package/dst/ase-task-board-flow.js +7 -6
- package/dst/ase-task-board-graph.js +5 -4
- package/dst/ase-task-board-tui-control.js +41 -35
- package/dst/ase-task-board-tui-model.js +185 -167
- package/dst/ase-task-board-tui-popup.js +2 -3
- package/dst/ase-task-board-tui-view.js +74 -70
- package/dst/ase-task-board-web-client.js +27 -27
- package/dst/ase-task-board-web-server.js +2 -2
- package/dst/ase-task-board.js +1 -1
- package/dst/ase-task-format.js +1 -1
- package/dst/ase-task-store-client.js +17 -5
- package/dst/ase-task-store-core.js +5 -7
- package/dst/ase-task-store-issues.js +16 -1
- package/dst/ase-task-store-plugin-ase.js +7 -3
- package/dst/ase-task-store-plugin-gitea.js +9 -32
- package/dst/ase-task-store-plugin-github.js +3 -12
- package/dst/ase-task-store-plugin-gitlab.js +2 -18
- package/dst/ase-task.js +51 -23
- package/dst/ase-util-compat.js +1 -1
- package/dst/ase-util-diagram.js +13 -37
- package/dst/ase.js +5 -5
- 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/meta/ase-skill.md +7 -0
- package/plugin/mods/ase-mods.ts +10 -7
- package/plugin/mods/claude-code.d.ts +26 -3
- package/plugin/skills/ase-code-analyze/SKILL.md +18 -10
- package/plugin/skills/ase-code-analyze/help.md +11 -6
- package/plugin/skills/ase-code-lint/SKILL.md +37 -23
- package/plugin/skills/ase-code-lint/help.md +10 -5
- package/plugin/skills/ase-help-intent/data.md +204 -13
- 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-repo-review/SKILL.md +5 -3
- package/plugin/skills/ase-repo-review/help.md +3 -1
- 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
|
@@ -252,7 +252,6 @@ export class SetupSettings {
|
|
|
252
252
|
/* the OpenAI Codex CLI has no scriptable statusline mechanism at all */
|
|
253
253
|
if (tool === "codex")
|
|
254
254
|
return [];
|
|
255
|
-
const home = os.homedir();
|
|
256
255
|
const scopes = tool === "claude" ? ["user", "project", "local"] : ["user"];
|
|
257
256
|
const entries = [];
|
|
258
257
|
for (const scope of scopes) {
|
|
@@ -269,7 +268,7 @@ export class SetupSettings {
|
|
|
269
268
|
if (member === undefined)
|
|
270
269
|
continue;
|
|
271
270
|
entries.push({
|
|
272
|
-
file:
|
|
271
|
+
file: this.fileDisplay(file),
|
|
273
272
|
scope: tool === "claude" ? scope : "(n/a)",
|
|
274
273
|
status: this.statuslineIsOwned(member) ? "activated" : "foreign"
|
|
275
274
|
});
|
|
@@ -336,7 +335,6 @@ export class SetupSettings {
|
|
|
336
335
|
async outputStyleStatus(tool) {
|
|
337
336
|
if (tool !== "claude")
|
|
338
337
|
return [];
|
|
339
|
-
const home = os.homedir();
|
|
340
338
|
const scopes = ["user", "project", "local"];
|
|
341
339
|
const entries = [];
|
|
342
340
|
for (const scope of scopes) {
|
|
@@ -353,7 +351,7 @@ export class SetupSettings {
|
|
|
353
351
|
if (member === undefined)
|
|
354
352
|
continue;
|
|
355
353
|
entries.push({
|
|
356
|
-
file:
|
|
354
|
+
file: this.fileDisplay(file),
|
|
357
355
|
scope,
|
|
358
356
|
status: this.outputStyleIsOwned(member) ? "selected" : "foreign"
|
|
359
357
|
});
|
|
@@ -389,8 +387,7 @@ export class SetupSettings {
|
|
|
389
387
|
/* a match is the presence of the "ase@ase" key under
|
|
390
388
|
"enabledPlugins", regardless of its true/false value */
|
|
391
389
|
const plugins = settings?.enabledPlugins;
|
|
392
|
-
if (plugins !== undefined && plugins !== null
|
|
393
|
-
&& Object.prototype.hasOwnProperty.call(plugins, "ase@ase")) {
|
|
390
|
+
if (plugins !== undefined && plugins !== null && Object.hasOwn(plugins, "ase@ase")) {
|
|
394
391
|
if (scope !== "user")
|
|
395
392
|
this.log.write("info", `setup: detected ASE plugin installation in scope "${scope}"`);
|
|
396
393
|
return scope;
|
|
@@ -488,7 +485,7 @@ export class SetupSettings {
|
|
|
488
485
|
return files;
|
|
489
486
|
}
|
|
490
487
|
/* abbreviate the home directory of a file path as "~" */
|
|
491
|
-
|
|
488
|
+
fileDisplay(file) {
|
|
492
489
|
const home = os.homedir();
|
|
493
490
|
return file.startsWith(home + path.sep) ? `~${file.slice(home.length)}` : file;
|
|
494
491
|
}
|
|
@@ -796,7 +793,7 @@ export class SetupSettings {
|
|
|
796
793
|
const present = env !== undefined && env.type() === "object" ? this.telemetryClaudePresent(env) : [];
|
|
797
794
|
if (env !== undefined && present.length > 0)
|
|
798
795
|
entries.push({
|
|
799
|
-
file: this.
|
|
796
|
+
file: this.fileDisplay(file),
|
|
800
797
|
scope: "user",
|
|
801
798
|
status: this.telemetryClaudeIsOwned(env) ? "activated" : "foreign"
|
|
802
799
|
});
|
|
@@ -822,7 +819,7 @@ export class SetupSettings {
|
|
|
822
819
|
else if ([...block.head, ...block.tail].some((line) => foreign.test(line)))
|
|
823
820
|
status = "foreign";
|
|
824
821
|
if (status !== "")
|
|
825
|
-
entries.push({ file: this.
|
|
822
|
+
entries.push({ file: this.fileDisplay(file), scope: "(n/a)", status });
|
|
826
823
|
}
|
|
827
824
|
if (tool === "copilot" && this.telemetryCopilotForeignEnv() !== undefined)
|
|
828
825
|
entries.push({ file: "$OTEL_EXPORTER_OTLP_ENDPOINT", scope: "(n/a)", status: "foreign" });
|
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;
|
|
@@ -347,10 +347,9 @@ export default class SetupCommand {
|
|
|
347
347
|
});
|
|
348
348
|
/* parser for the non-negative integer "ase setup statusline" options */
|
|
349
349
|
const parseNonNeg = (name) => (value) => {
|
|
350
|
-
|
|
351
|
-
if (!Number.isFinite(n) || n < 0)
|
|
350
|
+
if (!/^\d+$/.test(value))
|
|
352
351
|
throw new Error(`invalid --${name} value: "${value}" (expected a non-negative integer)`);
|
|
353
|
-
return
|
|
352
|
+
return Number.parseInt(value, 10);
|
|
354
353
|
};
|
|
355
354
|
/* register CLI sub-command "ase setup statusline" */
|
|
356
355
|
const statuslineCmd = setupCmd
|
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 ?
|