akm-cli 0.9.0-rc.1 → 0.9.0-rc.2
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 +223 -9
- package/SECURITY.md +23 -24
- package/dist/assets/help/help-improve.md +10 -10
- package/dist/assets/hints/cli-hints-full.md +44 -10
- package/dist/assets/hints/cli-hints-short.md +6 -2
- package/dist/assets/{profiles → improve-strategies}/default.json +1 -0
- package/dist/assets/{profiles → improve-strategies}/graph-refresh.json +1 -1
- package/dist/assets/{profiles → improve-strategies}/proactive-maintenance.json +2 -3
- package/dist/assets/{profiles → improve-strategies}/reflect-distill.json +3 -4
- package/dist/assets/stash-skeleton/README.md +28 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +12 -1
- package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +11 -1
- package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +9 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +7 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +7 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +98 -0
- package/dist/assets/stash-skeleton/facts/conventions/domains.md +63 -0
- package/dist/assets/stash-skeleton/facts/conventions/organization.md +127 -0
- package/dist/assets/tasks/core/backup.yml +1 -0
- package/dist/assets/tasks/core/extract.yml +1 -0
- package/dist/assets/tasks/core/improve.yml +1 -0
- package/dist/assets/tasks/core/index-refresh.yml +1 -0
- package/dist/assets/tasks/core/sync.yml +1 -0
- package/dist/assets/tasks/core/version-check.yml +1 -0
- package/dist/assets/tasks/graph-refresh-weekly.yml +4 -4
- package/dist/assets/templates/html/health.html +5 -1
- package/dist/cli/config-migrate.js +31 -138
- package/dist/cli/config-validate.js +10 -8
- package/dist/cli.js +48 -14
- package/dist/commands/agent/agent-dispatch.js +17 -16
- package/dist/commands/agent/agent-support.js +0 -24
- package/dist/commands/agent/contribute-cli.js +5 -15
- package/dist/commands/backup-cli.js +54 -0
- package/dist/commands/config-cli.js +45 -159
- package/dist/commands/env/secret.js +8 -5
- package/dist/commands/health/checks.js +130 -83
- package/dist/commands/health/html-report.js +4 -0
- package/dist/commands/health/improve-metrics.js +30 -32
- package/dist/commands/health/llm-usage.js +19 -19
- package/dist/commands/health/md-report.js +4 -0
- package/dist/commands/health/metrics.js +2 -1
- package/dist/commands/health/surfaces.js +5 -4
- package/dist/commands/health.js +1 -1
- package/dist/commands/improve/consolidate/chunking.js +2 -2
- package/dist/commands/improve/consolidate.js +28 -25
- package/dist/commands/improve/distill/promote-memory.js +5 -12
- package/dist/commands/improve/distill/quality-gate.js +5 -7
- package/dist/commands/improve/distill.js +16 -5
- package/dist/commands/improve/eligibility.js +22 -12
- package/dist/commands/improve/extract-cli.js +47 -19
- package/dist/commands/improve/extract.js +110 -62
- package/dist/commands/improve/improve-cli.js +38 -16
- package/dist/commands/improve/improve-result-file.js +30 -24
- package/dist/commands/improve/improve-strategies.js +137 -0
- package/dist/commands/improve/improve.js +60 -30
- package/dist/commands/improve/locks.js +66 -45
- package/dist/commands/improve/loop-stages.js +75 -33
- package/dist/commands/improve/memory/memory-belief.js +79 -7
- package/dist/commands/improve/memory/memory-contradiction-detect.js +12 -4
- package/dist/commands/improve/preparation.js +71 -73
- package/dist/commands/improve/procedural.js +3 -2
- package/dist/commands/improve/recombine.js +2 -1
- package/dist/commands/improve/reflect.js +119 -214
- package/dist/commands/improve/shared.js +11 -5
- package/dist/commands/lint/base-linter.js +152 -42
- package/dist/commands/mv-cli.js +809 -0
- package/dist/commands/proposal/proposal-cli.js +18 -8
- package/dist/commands/proposal/propose.js +64 -69
- package/dist/commands/read/knowledge.js +436 -4
- package/dist/commands/read/remember-cli.js +39 -2
- package/dist/commands/read/search-cli.js +6 -1
- package/dist/commands/registry-cli.js +29 -14
- package/dist/commands/remember.js +2 -0
- package/dist/commands/sources/init.js +13 -14
- package/dist/commands/sources/schema-repair.js +2 -4
- package/dist/commands/sources/source-add.js +62 -73
- package/dist/commands/sources/source-manage.js +50 -46
- package/dist/commands/sources/stash-cli.js +41 -4
- package/dist/commands/tasks/default-tasks.js +12 -12
- package/dist/commands/tasks/tasks-cli.js +7 -3
- package/dist/commands/tasks/tasks.js +113 -18
- package/dist/commands/wiki-cli.js +9 -10
- package/dist/core/asset/frontmatter.js +12 -2
- package/dist/core/common.js +5 -3
- package/dist/core/config/config-io.js +28 -17
- package/dist/core/config/config-schema.js +358 -66
- package/dist/core/config/config-types.js +3 -3
- package/dist/core/config/config-version.js +29 -0
- package/dist/core/config/config-walker.js +98 -27
- package/dist/core/config/config.js +132 -266
- package/dist/core/config/deep-merge.js +41 -0
- package/dist/core/config/engine-semantics.js +32 -0
- package/dist/core/errors.js +2 -2
- package/dist/core/extra-params.js +61 -0
- package/dist/core/file-lock.js +201 -56
- package/dist/core/improve-result.js +178 -0
- package/dist/core/maintenance-barrier.js +119 -0
- package/dist/core/migration-backup.js +416 -0
- package/dist/core/paths.js +3 -0
- package/dist/core/redaction.js +358 -0
- package/dist/core/state/migrations.js +17 -2
- package/dist/core/state-db.js +44 -1
- package/dist/indexer/db/db.js +116 -1
- package/dist/indexer/graph/graph-extraction.js +28 -16
- package/dist/indexer/index-writer-lock.js +31 -24
- package/dist/indexer/index-written-assets.js +15 -6
- package/dist/indexer/indexer.js +47 -2
- package/dist/indexer/passes/memory-inference.js +10 -6
- package/dist/indexer/passes/metadata.js +250 -0
- package/dist/indexer/search/db-search.js +111 -44
- package/dist/indexer/search/fts-query.js +41 -0
- package/dist/indexer/search/ranking-contributors.js +48 -0
- package/dist/indexer/search/ranking.js +36 -23
- package/dist/indexer/search/search-fields.js +11 -1
- package/dist/integrations/agent/builder-shared.js +7 -0
- package/dist/integrations/agent/builders.js +5 -52
- package/dist/integrations/agent/config.js +3 -143
- package/dist/integrations/agent/detect.js +17 -2
- package/dist/integrations/agent/engine-resolution.js +202 -0
- package/dist/integrations/agent/index.js +1 -2
- package/dist/integrations/agent/model-aliases.js +7 -2
- package/dist/integrations/agent/profiles.js +6 -99
- package/dist/integrations/agent/runner-dispatch.js +76 -13
- package/dist/integrations/agent/runner.js +76 -207
- package/dist/integrations/agent/spawn.js +4 -6
- package/dist/integrations/harnesses/aider/agent-builder.js +2 -3
- package/dist/integrations/harnesses/aider/index.js +0 -1
- package/dist/integrations/harnesses/amazonq/agent-builder.js +2 -3
- package/dist/integrations/harnesses/amazonq/index.js +0 -1
- package/dist/integrations/harnesses/claude/agent-builder.js +2 -3
- package/dist/integrations/harnesses/claude/index.js +0 -2
- package/dist/integrations/harnesses/codex/agent-builder.js +2 -3
- package/dist/integrations/harnesses/codex/index.js +0 -1
- package/dist/integrations/harnesses/copilot/agent-builder.js +2 -3
- package/dist/integrations/harnesses/copilot/index.js +0 -1
- package/dist/integrations/harnesses/gemini/agent-builder.js +2 -3
- package/dist/integrations/harnesses/gemini/index.js +0 -1
- package/dist/integrations/harnesses/index.js +1 -24
- package/dist/integrations/harnesses/opencode/agent-builder.js +2 -3
- package/dist/integrations/harnesses/opencode/index.js +0 -6
- package/dist/integrations/harnesses/opencode-sdk/harness.js +1 -6
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +274 -191
- package/dist/integrations/harnesses/openhands/agent-builder.js +2 -3
- package/dist/integrations/harnesses/openhands/index.js +0 -1
- package/dist/integrations/harnesses/pi/agent-builder.js +2 -3
- package/dist/integrations/harnesses/pi/index.js +0 -1
- package/dist/integrations/harnesses/types.js +1 -32
- package/dist/integrations/lockfile.js +32 -21
- package/dist/llm/client.js +48 -14
- package/dist/llm/feature-gate.js +15 -47
- package/dist/llm/graph-extract.js +1 -1
- package/dist/llm/index-passes.js +8 -42
- package/dist/llm/memory-infer-impl.js +1 -1
- package/dist/llm/usage-persist.js +4 -0
- package/dist/llm/usage-telemetry.js +35 -5
- package/dist/output/shapes/helpers.js +2 -1
- package/dist/output/shapes/passthrough.js +2 -0
- package/dist/output/text/helpers.js +3 -1
- package/dist/schemas/akm-config.json +11013 -8600
- package/dist/schemas/akm-task.json +87 -0
- package/dist/schemas/akm-workflow.json +57 -13
- package/dist/scripts/migrate-storage.js +8591 -509
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +8623 -484
- package/dist/setup/detected-engines.js +142 -0
- package/dist/setup/engine-config.js +89 -0
- package/dist/setup/setup.js +236 -132
- package/dist/setup/steps/connection.js +61 -32
- package/dist/setup/steps/platforms.js +4 -4
- package/dist/setup/steps.js +3 -2
- package/dist/storage/database.js +13 -1
- package/dist/storage/engines/sqlite-migrations.js +1 -0
- package/dist/storage/repositories/improve-runs-repository.js +5 -5
- package/dist/storage/repositories/task-history-repository.js +78 -0
- package/dist/storage/repositories/workflow-runs-repository.js +9 -8
- package/dist/tasks/parser.js +138 -52
- package/dist/tasks/runner.js +71 -75
- package/dist/tasks/schema.js +1 -1
- package/dist/tasks/validator.js +11 -6
- package/dist/wiki/wiki.js +9 -8
- package/dist/workflows/authoring/workflow-program-template.yaml +3 -3
- package/dist/workflows/concurrency-policy.js +15 -0
- package/dist/workflows/db.js +65 -13
- package/dist/workflows/exec/brief.js +18 -24
- package/dist/workflows/exec/frozen-judge.js +47 -0
- package/dist/workflows/exec/native-executor.js +124 -65
- package/dist/workflows/exec/report.js +93 -33
- package/dist/workflows/exec/run-workflow.js +38 -25
- package/dist/workflows/exec/scheduler.js +12 -41
- package/dist/workflows/exec/step-work.js +91 -35
- package/dist/workflows/ir/compile.js +13 -26
- package/dist/workflows/ir/freeze.js +243 -0
- package/dist/workflows/ir/plan-hash.js +40 -5
- package/dist/workflows/ir/schema.js +542 -1
- package/dist/workflows/parser.js +7 -0
- package/dist/workflows/program/parser.js +132 -23
- package/dist/workflows/program/project.js +3 -4
- package/dist/workflows/program/schema.js +2 -2
- package/dist/workflows/resource-limits.js +20 -0
- package/dist/workflows/runtime/plan-classifier.js +187 -0
- package/dist/workflows/runtime/runs.js +54 -47
- package/dist/workflows/runtime/workflow-asset-loader.js +0 -22
- package/dist/workflows/validator.js +25 -0
- package/docs/data-and-telemetry.md +2 -2
- package/docs/migration/release-notes/0.6.0.md +1 -1
- package/docs/migration/release-notes/0.7.0.md +5 -4
- package/docs/migration/v0.8-to-v0.9.md +401 -0
- package/package.json +4 -2
- package/schemas/akm-config.json +16638 -0
- package/schemas/akm-task.json +87 -0
- package/schemas/akm-workflow.json +372 -0
- package/dist/commands/improve/improve-profiles.js +0 -168
- package/dist/core/config/config-migration.js +0 -602
- package/dist/core/deep-merge.js +0 -38
- package/dist/llm/call-ai.js +0 -62
- package/dist/setup/legacy-config.js +0 -106
- package/docs/README.md +0 -104
- /package/dist/assets/{profiles → improve-strategies}/catchup.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/consolidate.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/frequent.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/memory-focus.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/quick.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/recombine-only.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/synthesize.json +0 -0
- /package/dist/assets/{profiles → improve-strategies}/thorough.json +0 -0
|
@@ -141,12 +141,35 @@ function buildRefTypeAlternation() {
|
|
|
141
141
|
const types = [...getAssetTypes()].sort((a, b) => b.length - a.length);
|
|
142
142
|
return types.map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
|
|
143
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* Body-ref boundary grammar, shared with `akm mv`'s ref-rewrite pattern —
|
|
146
|
+
* `src/commands/mv-cli.ts` imports these constants so the two grammars cannot
|
|
147
|
+
* drift. Any character-class change here retargets both the lint missing-ref
|
|
148
|
+
* scan and mv's inbound-xref rewriting.
|
|
149
|
+
*
|
|
150
|
+
* `REF_BOUNDARY_PREFIX_CLASS_SRC` is the character class a ref may start
|
|
151
|
+
* after (a ref also matches at line start): whitespace, backtick, quote,
|
|
152
|
+
* `(`, `[`, or `,` — the `[` admits markdown-link-style refs like
|
|
153
|
+
* `see [memory:foo]`, which the legacy class silently skipped, and the `,`
|
|
154
|
+
* admits the ref AFTER a bare comma in a no-space flow list like
|
|
155
|
+
* `xrefs: [memory:a,memory:b]` (valid YAML). `,` is already a slug
|
|
156
|
+
* TERMINATOR (excluded from `REF_SLUG_CHAR_CLASS_SRC`), so `a,b` splits
|
|
157
|
+
* cleanly and adding it here cannot extend any existing match; false
|
|
158
|
+
* positives are fenced by the type alternation — a comma only starts a match
|
|
159
|
+
* when a literal `<type>:` follows it.
|
|
160
|
+
*
|
|
161
|
+
* `REF_SLUG_CHAR_CLASS_SRC` is the character class a ref's `<type>:<slug>`
|
|
162
|
+
* token is made of; the first excluded character ends the ref.
|
|
163
|
+
*/
|
|
164
|
+
export const REF_BOUNDARY_PREFIX_CLASS_SRC = "[\\s`\"'(,\\[]";
|
|
165
|
+
export const REF_SLUG_CHAR_CLASS_SRC = "[^\\s\"'`)\\]>,\\n]";
|
|
144
166
|
// Only the TYPE alternation is registry-derived; the surrounding grammar
|
|
145
167
|
// (boundary prefix, capture group, slug charset) is byte-identical to the
|
|
146
|
-
// legacy hand-written pattern
|
|
147
|
-
// `
|
|
148
|
-
//
|
|
149
|
-
|
|
168
|
+
// legacy hand-written pattern, except that the boundary prefix now also
|
|
169
|
+
// admits `[`. Deriving the types from `getAssetTypes()` means `env`/`secret`
|
|
170
|
+
// (added in 0.9) are now matched, and the removed `vault` type is not — both
|
|
171
|
+
// follow the registry automatically.
|
|
172
|
+
const REF_RE = new RegExp(`(?:^|${REF_BOUNDARY_PREFIX_CLASS_SRC})((${buildRefTypeAlternation()}):${REF_SLUG_CHAR_CLASS_SRC}+)`, "gm");
|
|
150
173
|
/**
|
|
151
174
|
* Map from ref type to relative path pattern within stashRoot. Returns null to
|
|
152
175
|
* skip (type is unresolvable by the slug walker).
|
|
@@ -185,49 +208,70 @@ export function refToRelPath(refType, refName) {
|
|
|
185
208
|
*/
|
|
186
209
|
export function refExistsInAnyStash(relPath, refType, refName, stashRoots) {
|
|
187
210
|
for (const root of stashRoots) {
|
|
188
|
-
|
|
189
|
-
if (fs.existsSync(absPath))
|
|
211
|
+
if (resolveRefPathInStash(relPath, refType, refName, root) !== null)
|
|
190
212
|
return true;
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
213
|
+
}
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Resolve the on-disk primary file for a ref within a SINGLE stash root, using
|
|
218
|
+
* the same reachability rules (in the same order) as
|
|
219
|
+
* {@link refExistsInAnyStash}, which delegates here. Returns the absolute path
|
|
220
|
+
* of the file that makes the ref "exist" — for a multi-file skill directory
|
|
221
|
+
* that is its `SKILL.md` primary — or `null` when the ref does not resolve in
|
|
222
|
+
* this root.
|
|
223
|
+
*
|
|
224
|
+
* Extracted for SPEC-5 (`--supersedes` demotion): write commands need the
|
|
225
|
+
* superseded asset's actual file to mutate, and forking a second resolver
|
|
226
|
+
* would drift from lint's. NOT part of the akm-plugins ref-resolver contract
|
|
227
|
+
* (the contract pins `refToRelPath` + `refExistsInAnyStash`; this is the
|
|
228
|
+
* shared internal both build on).
|
|
229
|
+
*/
|
|
230
|
+
export function resolveRefPathInStash(relPath, refType, refName, root) {
|
|
231
|
+
const absPath = path.join(root, relPath);
|
|
232
|
+
if (fs.existsSync(absPath))
|
|
233
|
+
return absPath;
|
|
234
|
+
// Multi-file skill layout: directory containing SKILL.md
|
|
235
|
+
const bareDir = absPath.replace(/\.md$/, "");
|
|
236
|
+
if (fs.existsSync(bareDir) && fs.existsSync(path.join(bareDir, "SKILL.md"))) {
|
|
237
|
+
return path.join(bareDir, "SKILL.md");
|
|
238
|
+
}
|
|
239
|
+
// .derived.md variant for memory refs
|
|
240
|
+
if (refType === "memory") {
|
|
241
|
+
const derivedPath = path.join(root, "memories", `${refName}.derived.md`);
|
|
242
|
+
if (fs.existsSync(derivedPath))
|
|
243
|
+
return derivedPath;
|
|
244
|
+
}
|
|
245
|
+
// Knowledge-specific: search subdirectories like knowledge/projects/, knowledge/tools/, etc.
|
|
246
|
+
if (refType === "knowledge") {
|
|
247
|
+
try {
|
|
248
|
+
const knowledgeDir = path.join(root, "knowledge");
|
|
249
|
+
if (fs.existsSync(knowledgeDir) && fs.statSync(knowledgeDir).isDirectory()) {
|
|
250
|
+
const entries = fs.readdirSync(knowledgeDir, { withFileTypes: true });
|
|
251
|
+
for (const entry of entries) {
|
|
252
|
+
if (!entry.isDirectory())
|
|
253
|
+
continue;
|
|
254
|
+
const subPath = path.join(knowledgeDir, entry.name, `${refName}.md`);
|
|
255
|
+
if (fs.existsSync(subPath))
|
|
256
|
+
return subPath;
|
|
214
257
|
}
|
|
215
258
|
}
|
|
216
|
-
catch {
|
|
217
|
-
// Ignore errors reading directory
|
|
218
|
-
}
|
|
219
259
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
const directPath = path.join(root, `${refName}.md`);
|
|
224
|
-
if (fs.existsSync(directPath))
|
|
225
|
-
return true;
|
|
226
|
-
const directDir = path.join(root, refName);
|
|
227
|
-
if (fs.existsSync(directDir) && fs.existsSync(path.join(directDir, "SKILL.md")))
|
|
228
|
-
return true;
|
|
260
|
+
catch {
|
|
261
|
+
// Ignore errors reading directory
|
|
262
|
+
}
|
|
229
263
|
}
|
|
230
|
-
|
|
264
|
+
// Fallback: the refName may already encode the full stash-relative path
|
|
265
|
+
// (e.g. knowledge:skills/foo/references/bar where the file lives at
|
|
266
|
+
// <stash>/skills/foo/references/bar.md, not <stash>/knowledge/skills/...).
|
|
267
|
+
const directPath = path.join(root, `${refName}.md`);
|
|
268
|
+
if (fs.existsSync(directPath))
|
|
269
|
+
return directPath;
|
|
270
|
+
const directDir = path.join(root, refName);
|
|
271
|
+
if (fs.existsSync(directDir) && fs.existsSync(path.join(directDir, "SKILL.md"))) {
|
|
272
|
+
return path.join(directDir, "SKILL.md");
|
|
273
|
+
}
|
|
274
|
+
return null;
|
|
231
275
|
}
|
|
232
276
|
/**
|
|
233
277
|
* Returns an array of {ref, resolvedRelPath} for every local AKM ref in the
|
|
@@ -293,6 +337,20 @@ function checkMissingRefs(body, stashRoot, extraStashRoots = []) {
|
|
|
293
337
|
return missing;
|
|
294
338
|
}
|
|
295
339
|
// ── frontmatter refs ─────────────────────────────────────────────────────────
|
|
340
|
+
/**
|
|
341
|
+
* Frontmatter keys that carry cross-reference lists per the stash
|
|
342
|
+
* organization conventions: `xrefs:` (provenance / associative links),
|
|
343
|
+
* `supersededBy:` and `contradictedBy:` (belief-state correction links).
|
|
344
|
+
* The missing-ref check validates each of these in ADDITION to the body /
|
|
345
|
+
* `refs:` scan — they are the channel the conventions mandate, and a rename
|
|
346
|
+
* would otherwise dangle them silently.
|
|
347
|
+
*
|
|
348
|
+
* `sources:` is deliberately excluded (non-wiki `sources:` was rejected as a
|
|
349
|
+
* typed channel; wiki `sources:` is checked by lintWiki). `source_refs:` /
|
|
350
|
+
* `evidenceSources:` are excluded too — they legitimately point at
|
|
351
|
+
* merged-away or pruned assets (historical provenance).
|
|
352
|
+
*/
|
|
353
|
+
const XREF_FRONTMATTER_KEYS = ["xrefs", "supersededBy", "contradictedBy"];
|
|
296
354
|
/**
|
|
297
355
|
* Return the `refs:` array from frontmatter when it is present and is an
|
|
298
356
|
* array of strings; otherwise return `null` to signal the caller should
|
|
@@ -342,6 +400,22 @@ function readRefsArray(value) {
|
|
|
342
400
|
}
|
|
343
401
|
return out;
|
|
344
402
|
}
|
|
403
|
+
/**
|
|
404
|
+
* Like {@link readRefsArray} but also accepts a single scalar string,
|
|
405
|
+
* normalizing it to a one-element list. The indexer's
|
|
406
|
+
* `normalizeNonEmptyStringList` treats `supersededBy: memory:x` and
|
|
407
|
+
* `supersededBy: [memory:x]` identically — both are live data — so the
|
|
408
|
+
* frontmatter xref-channel check must validate both shapes; the array-only
|
|
409
|
+
* reader silently skipped dangling scalar refs. Returns `null` for any other
|
|
410
|
+
* type (missing key, number, object) and for a blank scalar.
|
|
411
|
+
*/
|
|
412
|
+
function readRefStringOrArray(value) {
|
|
413
|
+
if (typeof value === "string") {
|
|
414
|
+
const trimmed = value.trim();
|
|
415
|
+
return trimmed ? [trimmed] : null;
|
|
416
|
+
}
|
|
417
|
+
return readRefsArray(value);
|
|
418
|
+
}
|
|
345
419
|
/**
|
|
346
420
|
* Detect a leading nested frontmatter block in `body` (i.e. a `---\n…\n---`
|
|
347
421
|
* pair that opens within the first few lines of the body). When present,
|
|
@@ -604,6 +678,42 @@ export class BaseLinter {
|
|
|
604
678
|
fixed: false,
|
|
605
679
|
});
|
|
606
680
|
}
|
|
681
|
+
// Frontmatter xref channels (xrefs / supersededBy / contradictedBy).
|
|
682
|
+
// Runs regardless of the `refs:` body-scan carve-out above — that
|
|
683
|
+
// carve-out governs only the BODY scan (`refs: []` declares "no
|
|
684
|
+
// outbound refs in the body", not "skip my correction links").
|
|
685
|
+
// Non-ref-shaped values (URLs, `raw/<slug>`, `<placeholder>`
|
|
686
|
+
// templates, shell vars) fall out via checkMissingRefs' guards.
|
|
687
|
+
//
|
|
688
|
+
// Gate: runs when the file has a frontmatter block OR when an
|
|
689
|
+
// authoritative `refs:` list was extracted. On the task/YAML path
|
|
690
|
+
// (lint/index.ts) ctx.frontmatter is always null and the whole file
|
|
691
|
+
// IS the body (`body === raw`); the top-level YAML keys land in
|
|
692
|
+
// ctx.data. Without `refs:` the body scan above already catches ref
|
|
693
|
+
// values under these keys, so running the pass would double-report —
|
|
694
|
+
// skip it. With `refs:` present the body scan is suppressed
|
|
695
|
+
// (refSource is the refs list), so this pass is the ONLY thing that
|
|
696
|
+
// validates the xref keys — it must run or dangling task xrefs go
|
|
697
|
+
// unreported. The two cases are mutually exclusive, so no ref is
|
|
698
|
+
// ever double-reported. Md files without a frontmatter block and
|
|
699
|
+
// without `refs:` land in the skip branch with empty ctx.data, so
|
|
700
|
+
// nothing is lost for them either.
|
|
701
|
+
if (ctx.frontmatter !== null || explicitRefs !== null) {
|
|
702
|
+
for (const key of XREF_FRONTMATTER_KEYS) {
|
|
703
|
+
const values = readRefStringOrArray(ctx.data?.[key]);
|
|
704
|
+
if (values === null)
|
|
705
|
+
continue;
|
|
706
|
+
const missingXrefs = checkMissingRefs(values.join("\n"), ctx.stashRoot, ctx.extraStashRoots);
|
|
707
|
+
for (const { ref, resolvedRelPath } of missingXrefs) {
|
|
708
|
+
issues.push({
|
|
709
|
+
file: ctx.relPath,
|
|
710
|
+
issue: "missing-ref",
|
|
711
|
+
detail: `missing ref: ${ref} (frontmatter ${key}; resolved to ${resolvedRelPath})`,
|
|
712
|
+
fixed: false,
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
607
717
|
}
|
|
608
718
|
return issues;
|
|
609
719
|
}
|