akm-cli 0.8.0-rc1 → 0.8.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/{.github/CHANGELOG.md → CHANGELOG.md} +191 -3
- package/README.md +22 -6
- package/SECURITY.md +93 -0
- package/dist/cli/config-migrate.js +144 -0
- package/dist/cli/config-validate.js +39 -0
- package/dist/cli/confirm.js +73 -0
- package/dist/cli/parse-args.js +93 -3
- package/dist/cli/shared.js +129 -0
- package/dist/cli.js +2162 -1258
- package/dist/commands/add-cli.js +279 -0
- package/dist/commands/agent-dispatch.js +20 -12
- package/dist/commands/agent-support.js +11 -5
- package/dist/commands/completions.js +3 -0
- package/dist/commands/config-cli.js +129 -517
- package/dist/commands/consolidate.js +1533 -144
- package/dist/commands/curate.js +44 -3
- package/dist/commands/db-cli.js +23 -0
- package/dist/commands/distill-promotion-policy.js +5 -3
- package/dist/commands/distill.js +906 -100
- package/dist/commands/env.js +213 -0
- package/dist/commands/eval-cases.js +3 -0
- package/dist/commands/events.js +3 -0
- package/dist/commands/extract-cli.js +127 -0
- package/dist/commands/extract-prompt.js +204 -0
- package/dist/commands/extract.js +477 -0
- package/dist/commands/feedback-cli.js +331 -0
- package/dist/commands/graph.js +260 -5
- package/dist/commands/health.js +977 -51
- package/dist/commands/help/help-accept.md +6 -3
- package/dist/commands/help/help-improve.md +36 -8
- package/dist/commands/help/help-proposals.md +7 -4
- package/dist/commands/help/help-reject.md +5 -2
- package/dist/commands/history.js +51 -16
- package/dist/commands/improve-auto-accept.js +97 -0
- package/dist/commands/improve-cli.js +236 -0
- package/dist/commands/improve-profiles.js +184 -0
- package/dist/commands/improve-result-file.js +167 -0
- package/dist/commands/improve.js +1725 -332
- package/dist/commands/info.js +3 -0
- package/dist/commands/init.js +49 -1
- package/dist/commands/installed-stashes.js +6 -23
- package/dist/commands/knowledge.js +3 -0
- package/dist/commands/lint/agent-linter.js +3 -0
- package/dist/commands/lint/base-linter.js +233 -5
- package/dist/commands/lint/command-linter.js +3 -0
- package/dist/commands/lint/default-linter.js +3 -0
- package/dist/commands/lint/env-key-rules.js +154 -0
- package/dist/commands/lint/index.js +92 -3
- package/dist/commands/lint/knowledge-linter.js +3 -0
- package/dist/commands/lint/markdown-insertion.js +343 -0
- package/dist/commands/lint/memory-linter.js +3 -0
- package/dist/commands/lint/registry.js +3 -0
- package/dist/commands/lint/skill-linter.js +3 -0
- package/dist/commands/lint/task-linter.js +15 -12
- package/dist/commands/lint/types.js +3 -0
- package/dist/commands/lint/workflow-linter.js +3 -0
- package/dist/commands/lint.js +3 -0
- package/dist/commands/migration-help.js +5 -2
- package/dist/commands/proposal-drain-policies.js +128 -0
- package/dist/commands/proposal-drain.js +477 -0
- package/dist/commands/proposal.js +60 -6
- package/dist/commands/propose.js +24 -19
- package/dist/commands/reflect.js +1004 -94
- package/dist/commands/registry-cli.js +150 -0
- package/dist/commands/registry-search.js +3 -0
- package/dist/commands/remember-cli.js +257 -0
- package/dist/commands/remember.js +15 -6
- package/dist/commands/schema-repair.js +88 -15
- package/dist/commands/search.js +99 -14
- package/dist/commands/secret.js +173 -0
- package/dist/commands/self-update.js +3 -0
- package/dist/commands/show.js +32 -13
- package/dist/commands/source-add.js +7 -35
- package/dist/commands/source-clone.js +3 -0
- package/dist/commands/source-manage.js +3 -0
- package/dist/commands/tasks.js +161 -95
- package/dist/commands/url-checker.js +3 -0
- package/dist/core/action-contributors.js +3 -0
- package/dist/core/asset-ref.js +17 -2
- package/dist/core/asset-registry.js +9 -2
- package/dist/core/asset-serialize.js +88 -0
- package/dist/core/asset-spec.js +61 -5
- package/dist/core/common.js +93 -5
- package/dist/core/concurrent.js +3 -0
- package/dist/core/config-io.js +347 -0
- package/dist/core/config-migration.js +622 -0
- package/dist/core/config-schema.js +558 -0
- package/dist/core/config-sources.js +108 -0
- package/dist/core/config-types.js +4 -0
- package/dist/core/config-walker.js +337 -0
- package/dist/core/config.js +366 -1077
- package/dist/core/errors.js +42 -20
- package/dist/core/events.js +31 -25
- package/dist/core/file-lock.js +104 -0
- package/dist/core/frontmatter.js +75 -10
- package/dist/core/lesson-lint.js +3 -0
- package/dist/core/markdown.js +3 -0
- package/dist/core/memory-belief.js +62 -0
- package/dist/core/memory-contradiction-detect.js +274 -0
- package/dist/core/memory-improve.js +142 -14
- package/dist/core/parse.js +3 -0
- package/dist/core/paths.js +218 -50
- package/dist/core/proposal-quality-validators.js +380 -0
- package/dist/core/proposal-validators.js +11 -3
- package/dist/core/proposals.js +464 -5
- package/dist/core/state-db.js +349 -56
- package/dist/core/text-truncation.js +107 -0
- package/dist/core/time.js +3 -0
- package/dist/core/tty.js +59 -0
- package/dist/core/warn.js +7 -2
- package/dist/core/write-source.js +12 -0
- package/dist/indexer/db-backup.js +391 -0
- package/dist/indexer/db-search.js +136 -28
- package/dist/indexer/db.js +662 -166
- package/dist/indexer/ensure-index.js +3 -0
- package/dist/indexer/file-context.js +3 -0
- package/dist/indexer/graph-boost.js +162 -40
- package/dist/indexer/graph-db.js +241 -51
- package/dist/indexer/graph-dedup.js +3 -7
- package/dist/indexer/graph-extraction.js +242 -149
- package/dist/indexer/index-context.js +3 -9
- package/dist/indexer/indexer.js +84 -14
- package/dist/indexer/llm-cache.js +24 -19
- package/dist/indexer/manifest.js +3 -0
- package/dist/indexer/matchers.js +184 -11
- package/dist/indexer/memory-inference.js +94 -50
- package/dist/indexer/metadata-contributors.js +3 -0
- package/dist/indexer/metadata.js +114 -48
- package/dist/indexer/path-resolver.js +3 -0
- package/dist/indexer/project-context.js +192 -0
- package/dist/indexer/ranking-contributors.js +134 -7
- package/dist/indexer/ranking.js +8 -1
- package/dist/indexer/search-fields.js +5 -9
- package/dist/indexer/search-hit-enrichers.js +91 -2
- package/dist/indexer/search-source.js +20 -1
- package/dist/indexer/semantic-status.js +4 -1
- package/dist/indexer/staleness-detect.js +447 -0
- package/dist/indexer/usage-events.js +12 -9
- package/dist/indexer/walker.js +3 -0
- package/dist/integrations/agent/builders.js +135 -0
- package/dist/integrations/agent/config.js +121 -401
- package/dist/integrations/agent/detect.js +3 -0
- package/dist/integrations/agent/index.js +6 -14
- package/dist/integrations/agent/model-aliases.js +55 -0
- package/dist/integrations/agent/profiles.js +3 -0
- package/dist/integrations/agent/prompts.js +137 -8
- package/dist/integrations/agent/runner.js +208 -0
- package/dist/integrations/agent/sdk-runner.js +8 -2
- package/dist/integrations/agent/spawn.js +54 -14
- package/dist/integrations/github.js +3 -0
- package/dist/integrations/lockfile.js +22 -51
- package/dist/integrations/session-logs/index.js +4 -0
- package/dist/integrations/session-logs/inline-refs.js +35 -0
- package/dist/integrations/session-logs/pre-filter.js +152 -0
- package/dist/integrations/session-logs/providers/claude-code.js +226 -0
- package/dist/integrations/session-logs/providers/opencode.js +231 -25
- package/dist/integrations/session-logs/types.js +3 -0
- package/dist/llm/call-ai.js +14 -26
- package/dist/llm/client.js +16 -2
- package/dist/llm/embedder.js +20 -29
- package/dist/llm/embedders/cache.js +3 -7
- package/dist/llm/embedders/local.js +42 -1
- package/dist/llm/embedders/remote.js +20 -8
- package/dist/llm/embedders/types.js +3 -7
- package/dist/llm/feature-gate.js +92 -56
- package/dist/llm/graph-extract.js +401 -30
- package/dist/llm/index-passes.js +44 -29
- package/dist/llm/memory-infer.js +30 -2
- package/dist/llm/metadata-enhance.js +3 -7
- package/dist/llm/prompts/extract-session.md +80 -0
- package/dist/llm/prompts/graph-extract-user-prompt.md +24 -1
- package/dist/output/cli-hints-full.md +60 -32
- package/dist/output/cli-hints-short.md +10 -7
- package/dist/output/cli-hints.js +5 -2
- package/dist/output/context.js +60 -8
- package/dist/output/renderers.js +170 -194
- package/dist/output/shapes/curate.js +56 -0
- package/dist/output/shapes/distill.js +10 -0
- package/dist/output/shapes/env-list.js +19 -0
- package/dist/output/shapes/events.js +11 -0
- package/dist/output/shapes/helpers.js +424 -0
- package/dist/output/shapes/history.js +7 -0
- package/dist/output/shapes/passthrough.js +105 -0
- package/dist/output/shapes/proposal-accept.js +7 -0
- package/dist/output/shapes/proposal-diff.js +7 -0
- package/dist/output/shapes/proposal-list.js +7 -0
- package/dist/output/shapes/proposal-producer.js +11 -0
- package/dist/output/shapes/proposal-reject.js +7 -0
- package/dist/output/shapes/proposal-show.js +7 -0
- package/dist/output/shapes/registry-search.js +6 -0
- package/dist/output/shapes/registry.js +30 -0
- package/dist/output/shapes/search.js +6 -0
- package/dist/output/shapes/secret-list.js +19 -0
- package/dist/output/shapes/show.js +6 -0
- package/dist/output/shapes/vault-list.js +19 -0
- package/dist/output/shapes.js +51 -549
- package/dist/output/text/add.js +6 -0
- package/dist/output/text/clone.js +6 -0
- package/dist/output/text/config.js +6 -0
- package/dist/output/text/curate.js +6 -0
- package/dist/output/text/distill.js +7 -0
- package/dist/output/text/enable-disable.js +7 -0
- package/dist/output/text/events.js +10 -0
- package/dist/output/text/feedback.js +6 -0
- package/dist/output/text/helpers.js +1059 -0
- package/dist/output/text/history.js +7 -0
- package/dist/output/text/import.js +6 -0
- package/dist/output/text/index.js +6 -0
- package/dist/output/text/info.js +6 -0
- package/dist/output/text/init.js +6 -0
- package/dist/output/text/list.js +6 -0
- package/dist/output/text/proposal-producer.js +8 -0
- package/dist/output/text/proposal.js +12 -0
- package/dist/output/text/registry-commands.js +11 -0
- package/dist/output/text/registry.js +30 -0
- package/dist/output/text/remember.js +6 -0
- package/dist/output/text/remove.js +6 -0
- package/dist/output/text/save.js +6 -0
- package/dist/output/text/search.js +6 -0
- package/dist/output/text/show.js +6 -0
- package/dist/output/text/update.js +6 -0
- package/dist/output/text/upgrade.js +6 -0
- package/dist/output/text/vault.js +16 -0
- package/dist/output/text/wiki.js +15 -0
- package/dist/output/text/workflow.js +14 -0
- package/dist/output/text.js +44 -1329
- package/dist/registry/build-index.js +3 -0
- package/dist/registry/create-provider-registry.js +3 -0
- package/dist/registry/factory.js +4 -1
- package/dist/registry/origin-resolve.js +3 -0
- package/dist/registry/providers/index.js +3 -0
- package/dist/registry/providers/skills-sh.js +11 -2
- package/dist/registry/providers/static-index.js +10 -1
- package/dist/registry/providers/types.js +3 -24
- package/dist/registry/resolve.js +11 -16
- package/dist/registry/types.js +3 -0
- package/dist/scripts/migrate-storage.js +17767 -0
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +9031 -0
- package/dist/scripts/migrations/v16-to-v17.js +141 -0
- package/dist/setup/detect.js +3 -0
- package/dist/setup/ripgrep-install.js +3 -0
- package/dist/setup/ripgrep-resolve.js +3 -0
- package/dist/setup/setup.js +306 -67
- package/dist/setup/steps.js +3 -15
- package/dist/sources/include.js +3 -0
- package/dist/sources/provider-factory.js +3 -11
- package/dist/sources/provider.js +3 -20
- package/dist/sources/providers/filesystem.js +19 -23
- package/dist/sources/providers/git.js +171 -21
- package/dist/sources/providers/index.js +3 -0
- package/dist/sources/providers/install-types.js +3 -13
- package/dist/sources/providers/npm.js +3 -4
- package/dist/sources/providers/provider-utils.js +3 -0
- package/dist/sources/providers/sync-from-ref.js +3 -11
- package/dist/sources/providers/tar-utils.js +3 -0
- package/dist/sources/providers/website.js +18 -22
- package/dist/sources/resolve.js +3 -0
- package/dist/sources/types.js +3 -0
- package/dist/sources/website-ingest.js +3 -0
- package/dist/tasks/backends/cron.js +3 -0
- package/dist/tasks/backends/exec-utils.js +3 -0
- package/dist/tasks/backends/index.js +3 -11
- package/dist/tasks/backends/launchd.js +3 -0
- package/dist/tasks/backends/schtasks.js +3 -0
- package/dist/tasks/parser.js +51 -38
- package/dist/tasks/resolveAkmBin.js +3 -0
- package/dist/tasks/runner.js +35 -9
- package/dist/tasks/schedule.js +20 -1
- package/dist/tasks/schema.js +5 -3
- package/dist/tasks/validator.js +6 -3
- package/dist/version.js +3 -0
- package/dist/wiki/wiki-templates.js +3 -0
- package/dist/wiki/wiki.js +3 -0
- package/dist/workflows/authoring.js +3 -0
- package/dist/workflows/cli.js +3 -0
- package/dist/workflows/db.js +140 -10
- package/dist/workflows/document-cache.js +3 -10
- package/dist/workflows/parser.js +3 -0
- package/dist/workflows/renderer.js +3 -0
- package/dist/workflows/runs.js +18 -1
- package/dist/workflows/schema.js +3 -0
- package/dist/workflows/scope-key.js +3 -0
- package/dist/workflows/validator.js +5 -9
- package/docs/README.md +7 -2
- package/docs/data-and-telemetry.md +225 -0
- package/docs/migration/release-notes/0.7.5.md +2 -2
- package/docs/migration/release-notes/0.8.0.md +57 -5
- package/docs/migration/v0.7-to-v0.8.md +1378 -0
- package/package.json +28 -11
- package/.github/LICENSE +0 -374
- package/dist/commands/install-audit.js +0 -385
- package/dist/commands/vault.js +0 -307
- package/dist/indexer/match-contributors.js +0 -141
- package/dist/integrations/agent/pipeline.js +0 -39
- package/dist/integrations/agent/runners.js +0 -31
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
1
4
|
import fs from "node:fs";
|
|
2
5
|
import path from "node:path";
|
|
6
|
+
import { parse as parseYaml } from "yaml";
|
|
3
7
|
import { resolveStashDir } from "../../core/common";
|
|
4
8
|
import { loadConfig } from "../../core/config";
|
|
5
9
|
import { parseFrontmatter } from "../../core/frontmatter";
|
|
6
10
|
import { resolveSourceEntries } from "../../indexer/search-source";
|
|
11
|
+
import { checkVaultForDangerousKeys } from "./env-key-rules";
|
|
7
12
|
import { getLinterForType } from "./registry";
|
|
8
13
|
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
9
14
|
const STASH_SUBDIRS = [
|
|
@@ -17,6 +22,21 @@ const STASH_SUBDIRS = [
|
|
|
17
22
|
"knowledge",
|
|
18
23
|
];
|
|
19
24
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
25
|
+
function collectYamlFiles(dir) {
|
|
26
|
+
if (!fs.existsSync(dir))
|
|
27
|
+
return [];
|
|
28
|
+
const results = [];
|
|
29
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
30
|
+
const full = path.join(dir, entry.name);
|
|
31
|
+
if (entry.isDirectory()) {
|
|
32
|
+
results.push(...collectYamlFiles(full));
|
|
33
|
+
}
|
|
34
|
+
else if (entry.isFile() && entry.name.endsWith(".yml")) {
|
|
35
|
+
results.push(full);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return results;
|
|
39
|
+
}
|
|
20
40
|
function collectMarkdownFiles(dir) {
|
|
21
41
|
if (!fs.existsSync(dir))
|
|
22
42
|
return [];
|
|
@@ -32,6 +52,22 @@ function collectMarkdownFiles(dir) {
|
|
|
32
52
|
}
|
|
33
53
|
return results;
|
|
34
54
|
}
|
|
55
|
+
function collectEnvFiles(dir) {
|
|
56
|
+
const results = [];
|
|
57
|
+
try {
|
|
58
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
59
|
+
const full = path.join(dir, entry.name);
|
|
60
|
+
if (entry.isDirectory())
|
|
61
|
+
results.push(...collectEnvFiles(full));
|
|
62
|
+
else if (entry.isFile() && entry.name.endsWith(".env"))
|
|
63
|
+
results.push(full);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
/* dir may not exist */
|
|
68
|
+
}
|
|
69
|
+
return results;
|
|
70
|
+
}
|
|
35
71
|
/** True when the issue represents a file deletion that was successfully applied. */
|
|
36
72
|
function isFileDeletion(issue) {
|
|
37
73
|
return issue.fixed === true && (issue.issue === "orphaned-stub" || issue.issue === "placeholder-stub");
|
|
@@ -51,7 +87,8 @@ export function akmLint(options = {}) {
|
|
|
51
87
|
const flagged = [];
|
|
52
88
|
for (const subdir of STASH_SUBDIRS) {
|
|
53
89
|
const dirPath = path.join(stashRoot, subdir);
|
|
54
|
-
|
|
90
|
+
// Tasks are .yml files; everything else is .md
|
|
91
|
+
const files = subdir === "tasks" ? collectYamlFiles(dirPath) : collectMarkdownFiles(dirPath);
|
|
55
92
|
const linter = getLinterForType(subdir);
|
|
56
93
|
// If the linter supports directory-level checks, run them for each direct
|
|
57
94
|
// subdirectory once before the per-file loop.
|
|
@@ -79,7 +116,25 @@ export function akmLint(options = {}) {
|
|
|
79
116
|
catch {
|
|
80
117
|
continue;
|
|
81
118
|
}
|
|
82
|
-
|
|
119
|
+
let data;
|
|
120
|
+
let body;
|
|
121
|
+
let frontmatter;
|
|
122
|
+
if (subdir === "tasks") {
|
|
123
|
+
// Task files are pure YAML — parseFrontmatter returns empty data for them.
|
|
124
|
+
try {
|
|
125
|
+
const parsed = parseYaml(raw);
|
|
126
|
+
data =
|
|
127
|
+
parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
data = {};
|
|
131
|
+
}
|
|
132
|
+
body = raw;
|
|
133
|
+
frontmatter = null;
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
({ data, content: body, frontmatter } = parseFrontmatter(raw));
|
|
137
|
+
}
|
|
83
138
|
const issues = linter.lint({ filePath, relPath, raw, data, body, frontmatter, fix, stashRoot, extraStashRoots });
|
|
84
139
|
let fileDeleted = false;
|
|
85
140
|
for (const issue of issues) {
|
|
@@ -98,8 +153,42 @@ export function akmLint(options = {}) {
|
|
|
98
153
|
continue; // file is gone — skip any remaining checks
|
|
99
154
|
}
|
|
100
155
|
}
|
|
156
|
+
// ── Env dangerous-key pass ─────────────────────────────────────────────────
|
|
157
|
+
// Scan every `.env` file under <stashRoot>/env/ (and the deprecated
|
|
158
|
+
// <stashRoot>/vaults/) across all stash roots for keys that are known to
|
|
159
|
+
// enable process-execution hijacking. Warn-only — findings go into `flagged`,
|
|
160
|
+
// never `fixed`.
|
|
161
|
+
const envRoots = [stashRoot, ...extraStashRoots];
|
|
162
|
+
for (const root of envRoots) {
|
|
163
|
+
for (const [subdir, prefix] of [
|
|
164
|
+
["env", "env"],
|
|
165
|
+
["vaults", "vault"],
|
|
166
|
+
]) {
|
|
167
|
+
const dir = path.join(root, subdir);
|
|
168
|
+
if (!fs.existsSync(dir))
|
|
169
|
+
continue;
|
|
170
|
+
for (const envPath of collectEnvFiles(dir)) {
|
|
171
|
+
const baseName = path.basename(envPath, ".env");
|
|
172
|
+
// "default" (or empty) maps to ".env" → <prefix>:default
|
|
173
|
+
const ref = baseName === "" ? `${prefix}:default` : `${prefix}:${baseName}`;
|
|
174
|
+
const relPath = path.relative(root, envPath);
|
|
175
|
+
for (const issue of checkVaultForDangerousKeys(envPath, relPath, ref)) {
|
|
176
|
+
flagged.push(issue);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// `ok` reflects whether the lint run completed successfully — NOT whether
|
|
182
|
+
// it found anything. Findings are surfaced via `summary.flagged`; the CLI
|
|
183
|
+
// gates its exit code on `--fail-on-flagged`. Conflating "issues exist"
|
|
184
|
+
// with "command failed" caused two downstream problems:
|
|
185
|
+
// 1. `akm lint --json | jq …` saw stdout-flush races on Bun's non-zero
|
|
186
|
+
// exit, intermittently truncating the JSON the consumer read.
|
|
187
|
+
// 2. `ok` is the shared `{ok, error, code}` failure indicator across the
|
|
188
|
+
// whole CLI; reusing it for "found stuff" forced callers to disambiguate
|
|
189
|
+
// a successful-but-flagged run from a hard error by inspecting fields.
|
|
101
190
|
return {
|
|
102
|
-
ok:
|
|
191
|
+
ok: true,
|
|
103
192
|
fixed,
|
|
104
193
|
flagged,
|
|
105
194
|
summary: { fixed: fixed.length, flagged: flagged.length },
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
1
4
|
import { BaseLinter } from "./base-linter";
|
|
2
5
|
/**
|
|
3
6
|
* Linter for `knowledge/` assets.
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
/**
|
|
5
|
+
* Table-aware insertion-point selection for markdown auto-fixers.
|
|
6
|
+
*
|
|
7
|
+
* Background: an earlier `lint --fix` rule that auto-inserted a callout note
|
|
8
|
+
* landed it INSIDE a markdown table in `knowledge/akm-cli-reference.md`, which
|
|
9
|
+
* split the table fence and broke rendering. This module centralises the
|
|
10
|
+
* "where is it safe to insert a new block?" decision so any current or future
|
|
11
|
+
* fixer that wants to inject content into a markdown body can route through
|
|
12
|
+
* `findSafeInsertionPoint` and avoid the same class of bug.
|
|
13
|
+
*
|
|
14
|
+
* The helper is intentionally pure: it takes a `string[]` of body lines plus a
|
|
15
|
+
* proposed insertion line, and returns an adjusted insertion line that is
|
|
16
|
+
* guaranteed to fall outside of any of the following no-insert regions:
|
|
17
|
+
*
|
|
18
|
+
* - Markdown pipe tables (header row + `|---|---|` separator + data rows)
|
|
19
|
+
* - HTML tables (`<table>…</table>`)
|
|
20
|
+
* - Fenced code blocks (``` or ~~~ fences)
|
|
21
|
+
* - Indented code blocks (4+ leading spaces or a tab, after a blank line)
|
|
22
|
+
*
|
|
23
|
+
* Frontmatter is intentionally NOT detected here — callers should already
|
|
24
|
+
* strip the frontmatter and operate on the body, or pass the full content
|
|
25
|
+
* including frontmatter (in which case the helper treats it like prose and
|
|
26
|
+
* will not detect it as a no-insert region; the existing
|
|
27
|
+
* `fixMissingUpdated` flow injects into the frontmatter via regex without
|
|
28
|
+
* needing this helper).
|
|
29
|
+
*
|
|
30
|
+
* Line numbers are 0-based throughout this module to match `Array.splice`
|
|
31
|
+
* semantics. Callers using 1-based line numbers (e.g. from
|
|
32
|
+
* `parseMarkdownToc`) must subtract 1 before passing in.
|
|
33
|
+
*/
|
|
34
|
+
// ── Pipe-table detection ─────────────────────────────────────────────────────
|
|
35
|
+
/**
|
|
36
|
+
* Pattern matching a markdown table separator row, e.g. `|---|---|`,
|
|
37
|
+
* `| :--- | ---: |`, or `:---|---:` (pipe-less style).
|
|
38
|
+
*
|
|
39
|
+
* Allows optional leading/trailing whitespace, optional outer pipes, and
|
|
40
|
+
* alignment colons. Requires at least two cells (i.e. at least one inner
|
|
41
|
+
* pipe between dash sequences) so we don't false-positive on a horizontal
|
|
42
|
+
* rule like `---`.
|
|
43
|
+
*/
|
|
44
|
+
const TABLE_SEPARATOR_RE = /^\s*\|?\s*:?-{3,}:?\s*(?:\|\s*:?-{3,}:?\s*)+\|?\s*$/;
|
|
45
|
+
/**
|
|
46
|
+
* Pattern matching a plausible markdown table header/data row. Must contain
|
|
47
|
+
* at least one pipe character that is not at the very start AND not part of
|
|
48
|
+
* an inline code span. We don't try to be perfect here — the existence of a
|
|
49
|
+
* matching separator row on the next line is the real signal that this is a
|
|
50
|
+
* table.
|
|
51
|
+
*/
|
|
52
|
+
function looksLikeTableRow(line) {
|
|
53
|
+
const trimmed = line.trim();
|
|
54
|
+
if (trimmed === "")
|
|
55
|
+
return false;
|
|
56
|
+
// Must contain at least one pipe.
|
|
57
|
+
if (!trimmed.includes("|"))
|
|
58
|
+
return false;
|
|
59
|
+
// Exclude lines that are obviously not table rows: headings, list items
|
|
60
|
+
// starting with `- |` are rare but possible; we lean permissive here
|
|
61
|
+
// because the separator-row check below is the real gate.
|
|
62
|
+
if (/^#{1,6}\s/.test(trimmed))
|
|
63
|
+
return false;
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Given the start line of a candidate table (the header row), return the
|
|
68
|
+
* **exclusive** end line — the first line after the table that is NOT part
|
|
69
|
+
* of it (either a blank line, EOF, or a line that doesn't look like a table
|
|
70
|
+
* row). Returns -1 if the candidate is not actually a table.
|
|
71
|
+
*
|
|
72
|
+
* @param lines Full body as a `string[]`.
|
|
73
|
+
* @param headerLine 0-based index of the candidate header row.
|
|
74
|
+
*/
|
|
75
|
+
export function findEndOfTable(lines, headerLine) {
|
|
76
|
+
if (headerLine < 0 || headerLine >= lines.length)
|
|
77
|
+
return -1;
|
|
78
|
+
if (!looksLikeTableRow(lines[headerLine]))
|
|
79
|
+
return -1;
|
|
80
|
+
const sepLine = headerLine + 1;
|
|
81
|
+
if (sepLine >= lines.length)
|
|
82
|
+
return -1;
|
|
83
|
+
if (!TABLE_SEPARATOR_RE.test(lines[sepLine]))
|
|
84
|
+
return -1;
|
|
85
|
+
// Walk forward through data rows. A blank line, EOF, or a line that does
|
|
86
|
+
// not look like a table row terminates the table.
|
|
87
|
+
let i = sepLine + 1;
|
|
88
|
+
while (i < lines.length) {
|
|
89
|
+
if (lines[i].trim() === "")
|
|
90
|
+
break;
|
|
91
|
+
if (!looksLikeTableRow(lines[i]))
|
|
92
|
+
break;
|
|
93
|
+
i += 1;
|
|
94
|
+
}
|
|
95
|
+
return i;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* If `lineIdx` falls inside a markdown pipe table, return the exclusive end
|
|
99
|
+
* line of that table. Otherwise return -1.
|
|
100
|
+
*
|
|
101
|
+
* "Inside" includes the header row, the separator row, and any data row.
|
|
102
|
+
*/
|
|
103
|
+
export function isInsideTable(lines, lineIdx) {
|
|
104
|
+
if (lineIdx < 0 || lineIdx >= lines.length)
|
|
105
|
+
return -1;
|
|
106
|
+
// Walk backwards from lineIdx looking for a plausible table header
|
|
107
|
+
// (i.e. a line followed by a separator row), up to the nearest blank
|
|
108
|
+
// line or start-of-file.
|
|
109
|
+
for (let i = lineIdx; i >= 0; i -= 1) {
|
|
110
|
+
if (lines[i].trim() === "")
|
|
111
|
+
return -1; // blank line — out of any table
|
|
112
|
+
if (!looksLikeTableRow(lines[i]))
|
|
113
|
+
return -1;
|
|
114
|
+
const end = findEndOfTable(lines, i);
|
|
115
|
+
if (end !== -1 && lineIdx < end)
|
|
116
|
+
return end;
|
|
117
|
+
// Continue scanning backwards — this row looks like a table row but
|
|
118
|
+
// the table doesn't start here (could be a data row).
|
|
119
|
+
}
|
|
120
|
+
return -1;
|
|
121
|
+
}
|
|
122
|
+
// ── Fenced code block detection ───────────────────────────────────────────────
|
|
123
|
+
/**
|
|
124
|
+
* Match a fenced code block opener/closer: ```` ``` ```` or `~~~`, with
|
|
125
|
+
* optional leading whitespace and optional language identifier. The fence
|
|
126
|
+
* character must repeat at least three times; the matched group is the
|
|
127
|
+
* fence character + repeat count so we can detect matching closers.
|
|
128
|
+
*/
|
|
129
|
+
const FENCE_RE = /^(\s*)(`{3,}|~{3,})(.*)$/;
|
|
130
|
+
/**
|
|
131
|
+
* Return all fenced-code-block regions in `lines`. A fence is considered
|
|
132
|
+
* unterminated if EOF is reached without a matching closer — in that case
|
|
133
|
+
* the region extends to the last line. This matches CommonMark behaviour
|
|
134
|
+
* and means "EOF closes any open fence" so we still treat the tail as a
|
|
135
|
+
* no-insert region (otherwise a fixer could inject content into what the
|
|
136
|
+
* author meant as a multi-line code sample).
|
|
137
|
+
*/
|
|
138
|
+
export function findFenceRegions(lines) {
|
|
139
|
+
const regions = [];
|
|
140
|
+
let openIdx = -1;
|
|
141
|
+
let openFence = "";
|
|
142
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
143
|
+
const match = lines[i].match(FENCE_RE);
|
|
144
|
+
if (!match)
|
|
145
|
+
continue;
|
|
146
|
+
const fence = match[2];
|
|
147
|
+
if (openIdx === -1) {
|
|
148
|
+
// Opening fence
|
|
149
|
+
openIdx = i;
|
|
150
|
+
openFence = fence[0]; // ``` or ~~~
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
// Inside a fence — only a matching fence character closes it, and the
|
|
154
|
+
// closer must be at least as long. Per CommonMark we ignore any info
|
|
155
|
+
// string on the closer (`match[3]` is allowed but typically empty).
|
|
156
|
+
if (fence[0] === openFence && fence.length >= openFence.length) {
|
|
157
|
+
regions.push({ start: openIdx, end: i });
|
|
158
|
+
openIdx = -1;
|
|
159
|
+
openFence = "";
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (openIdx !== -1) {
|
|
163
|
+
// Unterminated fence — extends to EOF.
|
|
164
|
+
regions.push({ start: openIdx, end: lines.length - 1 });
|
|
165
|
+
}
|
|
166
|
+
return regions;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* If `lineIdx` falls inside any fenced code block, return the exclusive
|
|
170
|
+
* end line (one past the closing fence). Otherwise return -1.
|
|
171
|
+
*/
|
|
172
|
+
export function isInsideCodeFence(lines, lineIdx) {
|
|
173
|
+
if (lineIdx < 0 || lineIdx >= lines.length)
|
|
174
|
+
return -1;
|
|
175
|
+
for (const region of findFenceRegions(lines)) {
|
|
176
|
+
if (lineIdx >= region.start && lineIdx <= region.end) {
|
|
177
|
+
return region.end + 1;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return -1;
|
|
181
|
+
}
|
|
182
|
+
// ── HTML table detection ─────────────────────────────────────────────────────
|
|
183
|
+
/**
|
|
184
|
+
* If `lineIdx` falls inside an HTML `<table>…</table>` block, return the
|
|
185
|
+
* exclusive end line (one past the `</table>`). Otherwise return -1.
|
|
186
|
+
*
|
|
187
|
+
* We do a deliberately simple scan: detect `<table` on any prior line (case
|
|
188
|
+
* insensitive, allowing attributes) and require a `</table>` on or after
|
|
189
|
+
* `lineIdx`. Nested tables are NOT supported — that's a markdown
|
|
190
|
+
* anti-pattern and we'd rather under-detect than over-detect.
|
|
191
|
+
*/
|
|
192
|
+
export function isInsideHtmlTable(lines, lineIdx) {
|
|
193
|
+
if (lineIdx < 0 || lineIdx >= lines.length)
|
|
194
|
+
return -1;
|
|
195
|
+
let openIdx = -1;
|
|
196
|
+
for (let i = 0; i <= lineIdx; i += 1) {
|
|
197
|
+
if (/<table[\s>]/i.test(lines[i]))
|
|
198
|
+
openIdx = i;
|
|
199
|
+
if (/<\/table\s*>/i.test(lines[i]) && openIdx !== -1 && i >= openIdx) {
|
|
200
|
+
// Closing tag before lineIdx — table already finished, reset.
|
|
201
|
+
if (i < lineIdx)
|
|
202
|
+
openIdx = -1;
|
|
203
|
+
else
|
|
204
|
+
return i + 1;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (openIdx === -1)
|
|
208
|
+
return -1;
|
|
209
|
+
// We're after a `<table` opener — find the matching `</table>`.
|
|
210
|
+
for (let i = lineIdx; i < lines.length; i += 1) {
|
|
211
|
+
if (/<\/table\s*>/i.test(lines[i]))
|
|
212
|
+
return i + 1;
|
|
213
|
+
}
|
|
214
|
+
// Unterminated table — extend to EOF so we don't inject into malformed HTML.
|
|
215
|
+
return lines.length;
|
|
216
|
+
}
|
|
217
|
+
// ── Indented code block detection ────────────────────────────────────────────
|
|
218
|
+
/**
|
|
219
|
+
* Per CommonMark, an indented code block is a sequence of lines indented by
|
|
220
|
+
* 4+ spaces (or one tab), preceded by a blank line. We use a simplified
|
|
221
|
+
* detection: if `lineIdx` is indented 4+ spaces / starts with a tab AND
|
|
222
|
+
* either is the first line of the body or follows a blank line, treat it
|
|
223
|
+
* as part of an indented code block and skip to the next non-indented
|
|
224
|
+
* non-blank line.
|
|
225
|
+
*
|
|
226
|
+
* Returns the exclusive end of the code block if `lineIdx` is inside one,
|
|
227
|
+
* otherwise -1.
|
|
228
|
+
*/
|
|
229
|
+
export function isInsideIndentedCode(lines, lineIdx) {
|
|
230
|
+
if (lineIdx < 0 || lineIdx >= lines.length)
|
|
231
|
+
return -1;
|
|
232
|
+
const isIndented = (s) => /^( {4}|\t)/.test(s);
|
|
233
|
+
if (!isIndented(lines[lineIdx]))
|
|
234
|
+
return -1;
|
|
235
|
+
// Walk backwards: every line above must be either indented or blank, and
|
|
236
|
+
// we must eventually hit a blank line (or BOF) before any non-indented
|
|
237
|
+
// non-blank line. If we find a non-indented non-blank line first, this
|
|
238
|
+
// isn't an indented code block (it's just a continuation of a list item
|
|
239
|
+
// or paragraph).
|
|
240
|
+
let foundBlankBoundary = false;
|
|
241
|
+
for (let i = lineIdx - 1; i >= 0; i -= 1) {
|
|
242
|
+
if (lines[i].trim() === "") {
|
|
243
|
+
foundBlankBoundary = true;
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
if (!isIndented(lines[i])) {
|
|
247
|
+
return -1; // probably a list continuation, not a code block
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (!foundBlankBoundary && lineIdx > 0) {
|
|
251
|
+
// Walked all the way to BOF without a blank line — but lineIdx > 0
|
|
252
|
+
// means there was a non-indented non-blank line above, which would
|
|
253
|
+
// have returned -1 already. This branch is for safety only.
|
|
254
|
+
return -1;
|
|
255
|
+
}
|
|
256
|
+
// Walk forwards to find the end of the block.
|
|
257
|
+
let i = lineIdx + 1;
|
|
258
|
+
while (i < lines.length) {
|
|
259
|
+
if (lines[i].trim() === "") {
|
|
260
|
+
// A blank line MAY terminate the block, but per CommonMark a single
|
|
261
|
+
// blank line followed by more indented lines is still part of the
|
|
262
|
+
// same block. Peek ahead.
|
|
263
|
+
let j = i + 1;
|
|
264
|
+
while (j < lines.length && lines[j].trim() === "")
|
|
265
|
+
j += 1;
|
|
266
|
+
if (j >= lines.length || !isIndented(lines[j])) {
|
|
267
|
+
break; // block ends at the blank line
|
|
268
|
+
}
|
|
269
|
+
i = j;
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
if (!isIndented(lines[i]))
|
|
273
|
+
break;
|
|
274
|
+
i += 1;
|
|
275
|
+
}
|
|
276
|
+
return i;
|
|
277
|
+
}
|
|
278
|
+
// ── Composite: find a safe insertion point ───────────────────────────────────
|
|
279
|
+
/**
|
|
280
|
+
* Given a proposed 0-based insertion line, return an adjusted 0-based line
|
|
281
|
+
* that is guaranteed to fall outside of any markdown table, HTML table,
|
|
282
|
+
* fenced code block, or indented code block.
|
|
283
|
+
*
|
|
284
|
+
* Strategy: if the proposed line falls inside a no-insert region, push it
|
|
285
|
+
* to the line immediately AFTER that region. We never push it before —
|
|
286
|
+
* most callouts are forward references to surrounding content, so
|
|
287
|
+
* post-region is the safer choice (and prevents the very bug this helper
|
|
288
|
+
* exists to fix: a callout landing between the header separator and the
|
|
289
|
+
* first data row).
|
|
290
|
+
*
|
|
291
|
+
* The check is iterative: pushing past one region may land inside another
|
|
292
|
+
* (e.g. table immediately followed by code fence), so we re-check until a
|
|
293
|
+
* stable safe point is reached or we hit EOF. The iteration is bounded by
|
|
294
|
+
* line count to guarantee termination.
|
|
295
|
+
*
|
|
296
|
+
* @param lines Body as a `string[]`.
|
|
297
|
+
* @param proposedLineNumber 0-based index where the caller wants to insert.
|
|
298
|
+
* @returns 0-based safe insertion index (may equal `lines.length`).
|
|
299
|
+
*/
|
|
300
|
+
export function findSafeInsertionPoint(lines, proposedLineNumber) {
|
|
301
|
+
if (lines.length === 0)
|
|
302
|
+
return 0;
|
|
303
|
+
let target = Math.max(0, Math.min(proposedLineNumber, lines.length));
|
|
304
|
+
// Iterate at most `lines.length` times — each iteration that finds a
|
|
305
|
+
// region only moves `target` forward, so we cannot loop forever.
|
|
306
|
+
for (let guard = 0; guard <= lines.length; guard += 1) {
|
|
307
|
+
if (target >= lines.length)
|
|
308
|
+
return lines.length;
|
|
309
|
+
const tableEnd = isInsideTable(lines, target);
|
|
310
|
+
if (tableEnd !== -1) {
|
|
311
|
+
target = tableEnd;
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
const fenceEnd = isInsideCodeFence(lines, target);
|
|
315
|
+
if (fenceEnd !== -1) {
|
|
316
|
+
target = fenceEnd;
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
const htmlEnd = isInsideHtmlTable(lines, target);
|
|
320
|
+
if (htmlEnd !== -1) {
|
|
321
|
+
target = htmlEnd;
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
const indentedEnd = isInsideIndentedCode(lines, target);
|
|
325
|
+
if (indentedEnd !== -1) {
|
|
326
|
+
target = indentedEnd;
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
return target;
|
|
330
|
+
}
|
|
331
|
+
// Defensive fallback — should be unreachable given the guard above.
|
|
332
|
+
return Math.min(target, lines.length);
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Convenience wrapper that operates on a raw string (splits on `\r?\n` and
|
|
336
|
+
* accepts 0-based line numbers). Returns the adjusted 0-based line.
|
|
337
|
+
*
|
|
338
|
+
* Useful when a caller has the markdown as a single string and only wants
|
|
339
|
+
* to know "where can I safely splice in N more lines?"
|
|
340
|
+
*/
|
|
341
|
+
export function findSafeInsertionPointInText(content, proposedLineNumber) {
|
|
342
|
+
return findSafeInsertionPoint(content.split(/\r?\n/), proposedLineNumber);
|
|
343
|
+
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
1
4
|
import fs from "node:fs";
|
|
2
5
|
import { BaseLinter } from "./base-linter";
|
|
3
6
|
/**
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
1
4
|
import { AgentLinter } from "./agent-linter";
|
|
2
5
|
import { CommandLinter } from "./command-linter";
|
|
3
6
|
import { DefaultLinter } from "./default-linter";
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
1
4
|
import fs from "node:fs";
|
|
2
5
|
import path from "node:path";
|
|
3
6
|
import { BaseLinter } from "./base-linter";
|
|
@@ -1,24 +1,27 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
1
4
|
import { BaseLinter } from "./base-linter";
|
|
2
5
|
/**
|
|
3
6
|
* Linter for `tasks/` assets.
|
|
4
7
|
*
|
|
5
|
-
* Tasks are
|
|
6
|
-
* this linter validates the required task fields:
|
|
8
|
+
* Tasks are pure YAML files at `<stash>/tasks/<id>.yml`. In addition to the
|
|
9
|
+
* base checks this linter validates the required task fields:
|
|
7
10
|
*
|
|
8
11
|
* - `schedule` (string, non-empty) — cron expression or `@`-alias
|
|
9
12
|
* - `enabled` (boolean)
|
|
10
|
-
* - At least one of: `prompt` or `
|
|
13
|
+
* - At least one of: `prompt`, `workflow`, or `command` field present
|
|
11
14
|
*
|
|
12
|
-
* All issues are reported as `invalid-task-
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
+
* All issues are reported as `invalid-task-yaml` and are **not** auto-fixable.
|
|
16
|
+
* Cron expression syntax validation is intentionally out of scope (that
|
|
17
|
+
* belongs to `parseSchedule()`).
|
|
15
18
|
*/
|
|
16
19
|
export class TaskLinter extends BaseLinter {
|
|
17
20
|
types = ["tasks"];
|
|
18
21
|
lint(ctx) {
|
|
19
22
|
const issues = this.runBaseChecks(ctx);
|
|
20
|
-
//
|
|
21
|
-
if (ctx.
|
|
23
|
+
// Skip files that failed to parse — `data` will be empty.
|
|
24
|
+
if (ctx.data === null || Object.keys(ctx.data).length === 0)
|
|
22
25
|
return issues;
|
|
23
26
|
const missing = [];
|
|
24
27
|
// schedule: must be present and non-empty
|
|
@@ -29,15 +32,15 @@ export class TaskLinter extends BaseLinter {
|
|
|
29
32
|
if (!("enabled" in ctx.data)) {
|
|
30
33
|
missing.push("enabled");
|
|
31
34
|
}
|
|
32
|
-
// At least one of: prompt or
|
|
33
|
-
const hasTarget = "prompt" in ctx.data || "workflow" in ctx.data;
|
|
35
|
+
// At least one of: prompt, workflow, or command
|
|
36
|
+
const hasTarget = "prompt" in ctx.data || "workflow" in ctx.data || "command" in ctx.data;
|
|
34
37
|
if (!hasTarget) {
|
|
35
|
-
missing.push("prompt or
|
|
38
|
+
missing.push("prompt, workflow, or command");
|
|
36
39
|
}
|
|
37
40
|
if (missing.length > 0) {
|
|
38
41
|
issues.push({
|
|
39
42
|
file: ctx.relPath,
|
|
40
|
-
issue: "invalid-task-
|
|
43
|
+
issue: "invalid-task-yaml",
|
|
41
44
|
detail: `missing required fields: ${missing.join(", ")}`,
|
|
42
45
|
fixed: false,
|
|
43
46
|
});
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
1
4
|
import fs from "node:fs";
|
|
2
5
|
import { BaseLinter } from "./base-linter";
|
|
3
6
|
const PLACEHOLDER_STRINGS = ["Describe what this workflow accomplishes", "Example Workflow"];
|
package/dist/commands/lint.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
1
4
|
import fs from "node:fs";
|
|
2
5
|
import path from "node:path";
|
|
3
|
-
const CHANGELOG_URL = "https://github.com/itlackey/akm/blob/main
|
|
6
|
+
const CHANGELOG_URL = "https://github.com/itlackey/akm/blob/main/CHANGELOG.md";
|
|
4
7
|
const MIGRATION_DOC_URL = "https://github.com/itlackey/akm/blob/main/docs/migration/v0.5-to-v0.6.md";
|
|
5
8
|
/**
|
|
6
9
|
* Directory containing per-version release notes. Resolved relative to
|
|
@@ -14,7 +17,7 @@ function releaseNotesDir() {
|
|
|
14
17
|
}
|
|
15
18
|
function loadChangelog() {
|
|
16
19
|
try {
|
|
17
|
-
const changelogPath = path.resolve(import.meta.dir, "
|
|
20
|
+
const changelogPath = path.resolve(import.meta.dir, "../../CHANGELOG.md");
|
|
18
21
|
if (fs.existsSync(changelogPath)) {
|
|
19
22
|
return fs.readFileSync(changelogPath, "utf8");
|
|
20
23
|
}
|