akm-cli 0.7.4 → 0.8.0-rc.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +224 -1
- 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 +133 -0
- package/dist/cli/shared.js +129 -0
- package/dist/cli.js +2631 -1440
- package/dist/commands/add-cli.js +279 -0
- package/dist/commands/agent-dispatch.js +110 -0
- package/dist/commands/agent-support.js +68 -0
- package/dist/commands/completions.js +3 -0
- package/dist/commands/config-cli.js +130 -534
- package/dist/commands/consolidate.js +2122 -0
- package/dist/commands/curate.js +45 -3
- package/dist/commands/db-cli.js +23 -0
- package/dist/commands/distill-promotion-policy.js +660 -0
- package/dist/commands/distill.js +1081 -73
- package/dist/commands/env.js +213 -0
- package/dist/commands/eval-cases.js +43 -0
- package/dist/commands/events.js +15 -24
- 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 +477 -0
- package/dist/commands/health.js +1302 -0
- package/dist/commands/help/help-accept.md +12 -0
- package/dist/commands/help/help-improve.md +69 -0
- package/dist/commands/help/help-proposals.md +18 -0
- package/dist/commands/help/help-propose.md +17 -0
- package/dist/commands/help/help-reject.md +11 -0
- package/dist/commands/history.js +54 -46
- package/dist/commands/improve-auto-accept.js +97 -0
- package/dist/commands/improve-cli.js +217 -0
- package/dist/commands/improve-profiles.js +166 -0
- package/dist/commands/improve-result-file.js +167 -0
- package/dist/commands/improve.js +2373 -0
- package/dist/commands/info.js +5 -2
- package/dist/commands/init.js +50 -2
- package/dist/commands/installed-stashes.js +102 -139
- package/dist/commands/knowledge.js +136 -0
- package/dist/commands/lint/agent-linter.js +49 -0
- package/dist/commands/lint/base-linter.js +479 -0
- package/dist/commands/lint/command-linter.js +49 -0
- package/dist/commands/lint/default-linter.js +16 -0
- package/dist/commands/lint/env-key-rules.js +154 -0
- package/dist/commands/lint/index.js +196 -0
- package/dist/commands/lint/knowledge-linter.js +16 -0
- package/dist/commands/lint/markdown-insertion.js +343 -0
- package/dist/commands/lint/memory-linter.js +61 -0
- package/dist/commands/lint/registry.js +36 -0
- package/dist/commands/lint/skill-linter.js +45 -0
- package/dist/commands/lint/task-linter.js +50 -0
- package/dist/commands/lint/types.js +4 -0
- package/dist/commands/lint/workflow-linter.js +56 -0
- package/dist/commands/lint.js +4 -0
- package/dist/commands/migration-help.js +3 -0
- package/dist/commands/proposal.js +67 -12
- package/dist/commands/propose.js +120 -45
- package/dist/commands/reflect.js +1104 -60
- package/dist/commands/registry-cli.js +150 -0
- package/dist/commands/registry-search.js +5 -2
- package/dist/commands/remember-cli.js +257 -0
- package/dist/commands/remember.js +70 -7
- package/dist/commands/schema-repair.js +203 -0
- package/dist/commands/search.js +115 -14
- package/dist/commands/secret.js +173 -0
- package/dist/commands/self-update.js +3 -0
- package/dist/commands/show.js +158 -60
- package/dist/commands/source-add.js +17 -45
- package/dist/commands/source-clone.js +3 -0
- package/dist/commands/source-manage.js +14 -19
- package/dist/commands/tasks.js +437 -0
- package/dist/commands/url-checker.js +42 -0
- package/dist/core/action-contributors.js +28 -0
- package/dist/core/asset-ref.js +17 -2
- package/dist/core/asset-registry.js +12 -17
- package/dist/core/asset-serialize.js +88 -0
- package/dist/core/asset-spec.js +67 -1
- package/dist/core/common.js +182 -0
- package/dist/core/concurrent.js +25 -0
- package/dist/core/config-io.js +347 -0
- package/dist/core/config-migration.js +622 -0
- package/dist/core/config-schema.js +534 -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 +364 -968
- package/dist/core/errors.js +42 -20
- package/dist/core/events.js +105 -135
- package/dist/core/file-lock.js +104 -0
- package/dist/core/frontmatter.js +75 -8
- package/dist/core/lesson-lint.js +3 -0
- package/dist/core/markdown.js +20 -0
- package/dist/core/memory-belief.js +62 -0
- package/dist/core/memory-contradiction-detect.js +274 -0
- package/dist/core/memory-improve.js +806 -0
- package/dist/core/parse.js +158 -0
- package/dist/core/paths.js +280 -14
- package/dist/core/proposal-quality-validators.js +380 -0
- package/dist/core/proposal-validators.js +69 -0
- package/dist/core/proposals.js +512 -42
- package/dist/core/state-db.js +1068 -0
- package/dist/core/text-truncation.js +107 -0
- package/dist/core/time.js +54 -0
- package/dist/core/tty.js +59 -0
- package/dist/core/warn.js +64 -1
- package/dist/core/write-source.js +3 -0
- package/dist/indexer/db-backup.js +391 -0
- package/dist/indexer/db-search.js +198 -489
- package/dist/indexer/db.js +990 -108
- package/dist/indexer/ensure-index.js +136 -0
- package/dist/indexer/file-context.js +3 -0
- package/dist/indexer/graph-boost.js +376 -101
- package/dist/indexer/graph-db.js +391 -0
- package/dist/indexer/graph-dedup.js +95 -0
- package/dist/indexer/graph-extraction.js +550 -114
- package/dist/indexer/index-context.js +4 -0
- package/dist/indexer/indexer.js +547 -309
- package/dist/indexer/llm-cache.js +52 -0
- package/dist/indexer/manifest.js +3 -0
- package/dist/indexer/matchers.js +167 -160
- package/dist/indexer/memory-inference.js +152 -74
- package/dist/indexer/metadata-contributors.js +29 -0
- package/dist/indexer/metadata.js +275 -196
- package/dist/indexer/path-resolver.js +92 -0
- package/dist/indexer/project-context.js +192 -0
- package/dist/indexer/ranking-contributors.js +331 -0
- package/dist/indexer/ranking.js +81 -0
- package/dist/indexer/search-fields.js +5 -9
- package/dist/indexer/search-hit-enrichers.js +111 -0
- package/dist/indexer/search-source.js +44 -10
- package/dist/indexer/semantic-status.js +6 -17
- package/dist/indexer/staleness-detect.js +447 -0
- package/dist/indexer/usage-events.js +12 -9
- package/dist/indexer/walker.js +28 -0
- package/dist/integrations/agent/builders.js +135 -0
- package/dist/integrations/agent/config.js +122 -230
- package/dist/integrations/agent/detect.js +3 -0
- package/dist/integrations/agent/index.js +7 -13
- package/dist/integrations/agent/model-aliases.js +55 -0
- package/dist/integrations/agent/profiles.js +70 -5
- package/dist/integrations/agent/prompts.js +250 -36
- package/dist/integrations/agent/runner.js +151 -0
- package/dist/integrations/agent/sdk-runner.js +126 -0
- package/dist/integrations/agent/spawn.js +183 -35
- package/dist/integrations/github.js +3 -0
- package/dist/integrations/lockfile.js +32 -69
- package/dist/integrations/session-logs/index.js +69 -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 +282 -0
- package/dist/integrations/session-logs/providers/opencode.js +258 -0
- package/dist/integrations/session-logs/types.js +4 -0
- package/dist/llm/call-ai.js +62 -0
- package/dist/llm/client.js +79 -88
- 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 +95 -48
- package/dist/llm/graph-extract.js +676 -72
- package/dist/llm/index-passes.js +44 -29
- package/dist/llm/memory-infer.js +80 -71
- package/dist/llm/metadata-enhance.js +42 -29
- package/dist/llm/prompts/extract-session.md +80 -0
- package/dist/llm/prompts/graph-extract-user-prompt.md +35 -0
- package/dist/output/cli-hints-full.md +292 -0
- package/dist/output/cli-hints-short.md +66 -0
- package/dist/output/cli-hints.js +7 -311
- package/dist/output/context.js +60 -8
- package/dist/output/renderers.js +306 -258
- 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 +102 -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 -511
- 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 +1039 -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 +11 -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 -1093
- 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 +71 -50
- package/dist/registry/providers/static-index.js +53 -48
- 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 +17750 -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 +775 -37
- package/dist/setup/steps.js +3 -15
- package/dist/sources/include.js +3 -0
- package/dist/sources/provider-factory.js +5 -12
- package/dist/sources/provider.js +3 -20
- package/dist/sources/providers/filesystem.js +19 -23
- package/dist/sources/providers/git.js +179 -20
- 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 +7 -0
- package/dist/tasks/backends/cron.js +203 -0
- package/dist/tasks/backends/exec-utils.js +28 -0
- package/dist/tasks/backends/index.js +24 -0
- package/dist/tasks/backends/launchd-template.xml +19 -0
- package/dist/tasks/backends/launchd.js +187 -0
- package/dist/tasks/backends/schtasks-template.xml +29 -0
- package/dist/tasks/backends/schtasks.js +215 -0
- package/dist/tasks/parser.js +211 -0
- package/dist/tasks/resolveAkmBin.js +87 -0
- package/dist/tasks/runner.js +458 -0
- package/dist/tasks/schedule.js +227 -0
- package/dist/tasks/schema.js +15 -0
- package/dist/tasks/validator.js +62 -0
- package/dist/version.js +3 -0
- package/dist/wiki/index-template.md +12 -0
- package/dist/wiki/ingest-workflow-template.md +54 -0
- package/dist/wiki/log-template.md +8 -0
- package/dist/wiki/schema-template.md +61 -0
- package/dist/wiki/wiki-templates.js +15 -0
- package/dist/wiki/wiki.js +13 -61
- package/dist/workflows/authoring.js +8 -25
- package/dist/workflows/cli.js +3 -0
- package/dist/workflows/db.js +141 -2
- package/dist/workflows/document-cache.js +3 -10
- package/dist/workflows/parser.js +3 -0
- package/dist/workflows/renderer.js +11 -3
- package/dist/workflows/runs.js +91 -89
- package/dist/workflows/schema.js +3 -0
- package/dist/workflows/scope-key.js +79 -0
- package/dist/workflows/validator.js +4 -8
- package/dist/workflows/workflow-template.md +24 -0
- package/docs/README.md +10 -2
- package/docs/data-and-telemetry.md +225 -0
- package/docs/migration/release-notes/0.7.0.md +1 -1
- package/docs/migration/release-notes/0.7.4.md +1 -1
- package/docs/migration/release-notes/0.7.5.md +20 -0
- package/docs/migration/release-notes/0.8.0.md +48 -0
- package/docs/migration/v0.7-to-v0.8.md +1307 -0
- package/package.json +29 -11
- package/dist/commands/install-audit.js +0 -381
- package/dist/commands/vault.js +0 -333
- package/dist/templates/wiki-templates.js +0 -100
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
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
|
+
import fs from "node:fs";
|
|
5
|
+
import { BaseLinter } from "./base-linter";
|
|
6
|
+
/**
|
|
7
|
+
* Linter for `memories/` assets.
|
|
8
|
+
*
|
|
9
|
+
* Extra check beyond base:
|
|
10
|
+
* - `orphaned-stub`: `inferenceProcessed: true` in frontmatter AND body < 100
|
|
11
|
+
* chars AND no sibling `.derived.md` file. Fix: delete the stub file.
|
|
12
|
+
*/
|
|
13
|
+
export class MemoryLinter extends BaseLinter {
|
|
14
|
+
types = ["memories"];
|
|
15
|
+
lint(ctx) {
|
|
16
|
+
const issues = this.runBaseChecks(ctx);
|
|
17
|
+
// After base checks the file might have been mutated; re-parse body from
|
|
18
|
+
// ctx.raw which was updated in place by BaseLinter when fix === true.
|
|
19
|
+
const body = ctx.body;
|
|
20
|
+
if (this.#isOrphanedStub(ctx.data, body, ctx.filePath)) {
|
|
21
|
+
if (ctx.fix) {
|
|
22
|
+
try {
|
|
23
|
+
fs.unlinkSync(ctx.filePath);
|
|
24
|
+
issues.push({
|
|
25
|
+
file: ctx.relPath,
|
|
26
|
+
issue: "orphaned-stub",
|
|
27
|
+
detail: "deleted orphaned stub",
|
|
28
|
+
fixed: true,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
issues.push({
|
|
33
|
+
file: ctx.relPath,
|
|
34
|
+
issue: "orphaned-stub",
|
|
35
|
+
detail: `could not delete: ${e instanceof Error ? e.message : String(e)}`,
|
|
36
|
+
fixed: false,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
// Signal caller to skip remaining checks via a sentinel issue
|
|
40
|
+
// (caller must handle the deletion path; we mark the file as gone)
|
|
41
|
+
return issues;
|
|
42
|
+
}
|
|
43
|
+
issues.push({
|
|
44
|
+
file: ctx.relPath,
|
|
45
|
+
issue: "orphaned-stub",
|
|
46
|
+
detail: "inferenceProcessed stub with no derived sibling",
|
|
47
|
+
fixed: false,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return issues;
|
|
51
|
+
}
|
|
52
|
+
#isOrphanedStub(data, body, filePath) {
|
|
53
|
+
if (data.inferenceProcessed !== true)
|
|
54
|
+
return false;
|
|
55
|
+
if (body.trim().length >= 100)
|
|
56
|
+
return false;
|
|
57
|
+
const baseName = filePath.replace(/\.md$/, "");
|
|
58
|
+
const derivedPath = `${baseName}.derived.md`;
|
|
59
|
+
return !fs.existsSync(derivedPath);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
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
|
+
import { AgentLinter } from "./agent-linter";
|
|
5
|
+
import { CommandLinter } from "./command-linter";
|
|
6
|
+
import { DefaultLinter } from "./default-linter";
|
|
7
|
+
import { KnowledgeLinter } from "./knowledge-linter";
|
|
8
|
+
import { MemoryLinter } from "./memory-linter";
|
|
9
|
+
import { SkillLinter } from "./skill-linter";
|
|
10
|
+
import { TaskLinter } from "./task-linter";
|
|
11
|
+
import { WorkflowLinter } from "./workflow-linter";
|
|
12
|
+
// Singleton instances — one per type, shared across all lint runs.
|
|
13
|
+
const LINTERS = [
|
|
14
|
+
new AgentLinter(),
|
|
15
|
+
new MemoryLinter(),
|
|
16
|
+
new WorkflowLinter(),
|
|
17
|
+
new CommandLinter(),
|
|
18
|
+
new KnowledgeLinter(),
|
|
19
|
+
new SkillLinter(),
|
|
20
|
+
new TaskLinter(),
|
|
21
|
+
new DefaultLinter(),
|
|
22
|
+
];
|
|
23
|
+
const LINTER_MAP = new Map();
|
|
24
|
+
for (const linter of LINTERS) {
|
|
25
|
+
for (const t of linter.types) {
|
|
26
|
+
LINTER_MAP.set(t, linter);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const DEFAULT_LINTER = new DefaultLinter();
|
|
30
|
+
/**
|
|
31
|
+
* Return the appropriate linter for the given stash subdirectory name.
|
|
32
|
+
* Falls back to `DefaultLinter` for unknown types.
|
|
33
|
+
*/
|
|
34
|
+
export function getLinterForType(subdir) {
|
|
35
|
+
return LINTER_MAP.get(subdir) ?? DEFAULT_LINTER;
|
|
36
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
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
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { BaseLinter } from "./base-linter";
|
|
7
|
+
/**
|
|
8
|
+
* Linter for `skills/` assets.
|
|
9
|
+
*
|
|
10
|
+
* Skills are **directory bundles**: each skill lives at `skills/<name>/` and
|
|
11
|
+
* must contain a `SKILL.md` entry-point file.
|
|
12
|
+
*
|
|
13
|
+
* Directory-level check (via `lintDirectory`):
|
|
14
|
+
* - `missing-skill-md`: a skill subdirectory has no `SKILL.md`. Not
|
|
15
|
+
* auto-fixable — flagged with detail `"no SKILL.md in skills/<name>/"`.
|
|
16
|
+
*
|
|
17
|
+
* Per-file check:
|
|
18
|
+
* - Base checks (`unquoted-colon`, `missing-updated`) are run against any
|
|
19
|
+
* `.md` files found inside skill subdirectories.
|
|
20
|
+
*/
|
|
21
|
+
export class SkillLinter extends BaseLinter {
|
|
22
|
+
types = ["skills"];
|
|
23
|
+
/**
|
|
24
|
+
* Called once per direct subdirectory of `skills/`. Reports a
|
|
25
|
+
* `missing-skill-md` issue when the directory does not contain a `SKILL.md`.
|
|
26
|
+
*/
|
|
27
|
+
lintDirectory(subdirPath, stashRoot) {
|
|
28
|
+
const skillMdPath = path.join(subdirPath, "SKILL.md");
|
|
29
|
+
if (!fs.existsSync(skillMdPath)) {
|
|
30
|
+
const relDir = path.relative(stashRoot, subdirPath);
|
|
31
|
+
return [
|
|
32
|
+
{
|
|
33
|
+
file: relDir,
|
|
34
|
+
issue: "missing-skill-md",
|
|
35
|
+
detail: `no SKILL.md in ${relDir}/`,
|
|
36
|
+
fixed: false,
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
}
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
lint(ctx) {
|
|
43
|
+
return this.runBaseChecks(ctx);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
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
|
+
import { BaseLinter } from "./base-linter";
|
|
5
|
+
/**
|
|
6
|
+
* Linter for `tasks/` assets.
|
|
7
|
+
*
|
|
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:
|
|
10
|
+
*
|
|
11
|
+
* - `schedule` (string, non-empty) — cron expression or `@`-alias
|
|
12
|
+
* - `enabled` (boolean)
|
|
13
|
+
* - At least one of: `prompt`, `workflow`, or `command` field present
|
|
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()`).
|
|
18
|
+
*/
|
|
19
|
+
export class TaskLinter extends BaseLinter {
|
|
20
|
+
types = ["tasks"];
|
|
21
|
+
lint(ctx) {
|
|
22
|
+
const issues = this.runBaseChecks(ctx);
|
|
23
|
+
// Skip files that failed to parse — `data` will be empty.
|
|
24
|
+
if (ctx.data === null || Object.keys(ctx.data).length === 0)
|
|
25
|
+
return issues;
|
|
26
|
+
const missing = [];
|
|
27
|
+
// schedule: must be present and non-empty
|
|
28
|
+
if (!("schedule" in ctx.data) || typeof ctx.data.schedule !== "string" || ctx.data.schedule.trim() === "") {
|
|
29
|
+
missing.push("schedule");
|
|
30
|
+
}
|
|
31
|
+
// enabled: must be present (boolean — value of false is valid)
|
|
32
|
+
if (!("enabled" in ctx.data)) {
|
|
33
|
+
missing.push("enabled");
|
|
34
|
+
}
|
|
35
|
+
// At least one of: prompt, workflow, or command
|
|
36
|
+
const hasTarget = "prompt" in ctx.data || "workflow" in ctx.data || "command" in ctx.data;
|
|
37
|
+
if (!hasTarget) {
|
|
38
|
+
missing.push("prompt, workflow, or command");
|
|
39
|
+
}
|
|
40
|
+
if (missing.length > 0) {
|
|
41
|
+
issues.push({
|
|
42
|
+
file: ctx.relPath,
|
|
43
|
+
issue: "invalid-task-yaml",
|
|
44
|
+
detail: `missing required fields: ${missing.join(", ")}`,
|
|
45
|
+
fixed: false,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return issues;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
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
|
+
import fs from "node:fs";
|
|
5
|
+
import { BaseLinter } from "./base-linter";
|
|
6
|
+
const PLACEHOLDER_STRINGS = ["Describe what this workflow accomplishes", "Example Workflow"];
|
|
7
|
+
/**
|
|
8
|
+
* Linter for `workflows/` assets.
|
|
9
|
+
*
|
|
10
|
+
* Extra check beyond base:
|
|
11
|
+
* - `placeholder-stub`: body contains a known placeholder string.
|
|
12
|
+
* Fix: delete the file.
|
|
13
|
+
*/
|
|
14
|
+
export class WorkflowLinter extends BaseLinter {
|
|
15
|
+
types = ["workflows"];
|
|
16
|
+
lint(ctx) {
|
|
17
|
+
const issues = this.runBaseChecks(ctx);
|
|
18
|
+
const placeholderMatch = this.#checkPlaceholderStub(ctx.body);
|
|
19
|
+
if (placeholderMatch) {
|
|
20
|
+
if (ctx.fix) {
|
|
21
|
+
try {
|
|
22
|
+
fs.unlinkSync(ctx.filePath);
|
|
23
|
+
issues.push({
|
|
24
|
+
file: ctx.relPath,
|
|
25
|
+
issue: "placeholder-stub",
|
|
26
|
+
detail: `deleted: found "${placeholderMatch}"`,
|
|
27
|
+
fixed: true,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
issues.push({
|
|
32
|
+
file: ctx.relPath,
|
|
33
|
+
issue: "placeholder-stub",
|
|
34
|
+
detail: `could not delete: ${e instanceof Error ? e.message : String(e)}`,
|
|
35
|
+
fixed: false,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return issues;
|
|
39
|
+
}
|
|
40
|
+
issues.push({
|
|
41
|
+
file: ctx.relPath,
|
|
42
|
+
issue: "placeholder-stub",
|
|
43
|
+
detail: `placeholder text: "${placeholderMatch}"`,
|
|
44
|
+
fixed: false,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return issues;
|
|
48
|
+
}
|
|
49
|
+
#checkPlaceholderStub(body) {
|
|
50
|
+
for (const placeholder of PLACEHOLDER_STRINGS) {
|
|
51
|
+
if (body.includes(placeholder))
|
|
52
|
+
return placeholder;
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -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
|
const CHANGELOG_URL = "https://github.com/itlackey/akm/blob/main/CHANGELOG.md";
|