akm-cli 0.9.7 → 0.9.8-beta.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 +310 -0
- package/dist/commands/env/env.js +2 -21
- package/dist/commands/health/checks.js +0 -25
- package/dist/commands/health/data-dir-usage.js +165 -0
- package/dist/commands/health/improve-metrics.js +8 -34
- package/dist/commands/health/windows.js +0 -4
- package/dist/commands/health.js +18 -37
- package/dist/commands/improve/consolidate/eligibility.js +11 -5
- package/dist/commands/improve/extract.js +36 -32
- package/dist/commands/improve/memory/memory-belief.js +15 -5
- package/dist/commands/improve/memory/memory-contradiction-detect.js +16 -25
- package/dist/commands/improve/memory/memory-improve.js +7 -19
- package/dist/commands/improve/preparation.js +52 -36
- package/dist/commands/improve/reflect.js +13 -17
- package/dist/commands/lint/base-linter.js +42 -2
- package/dist/commands/lint/index.js +4 -5
- package/dist/commands/migrate/config-extra-params.js +61 -0
- package/dist/commands/migrate/dead-residue.js +113 -0
- package/dist/commands/migrate/stale-txn.js +49 -0
- package/dist/commands/migrate-cli.js +42 -1
- package/dist/commands/proposal/proposal.js +1 -21
- package/dist/commands/proposal/repository.js +0 -4
- package/dist/commands/proposal/validators/proposal-quality-validators.js +0 -6
- package/dist/commands/read/curate.js +4 -18
- package/dist/commands/read/show.js +2 -1
- package/dist/commands/sources/installed-stashes.js +5 -1
- package/dist/commands/sources/self-update.js +38 -1
- package/dist/commands/sources/sources-cli.js +17 -1
- package/dist/commands/tasks/tasks.js +0 -8
- package/dist/commands/url-checker.js +20 -28
- package/dist/commands/workflow-cli.js +11 -61
- package/dist/core/adapter/adapters/agent-skills-adapter.js +2 -6
- package/dist/core/adapter/adapters/akm-adapter.js +2 -3
- package/dist/core/adapter/adapters/akm-lint.js +2 -20
- package/dist/core/adapter/adapters/akm-task-adapter.js +2 -6
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +2 -6
- package/dist/core/adapter/adapters/dotenv-adapter.js +2 -22
- package/dist/core/adapter/adapters/generic-files-adapter.js +3 -9
- package/dist/core/adapter/adapters/llm-wiki-adapter.js +2 -6
- package/dist/core/adapter/adapters/okf-adapter.js +7 -18
- package/dist/core/adapter/adapters/shared.js +2 -0
- package/dist/core/adapter/adapters/tool-dir-shared.js +3 -9
- package/dist/core/adapter/adapters/website-snapshot-adapter.js +2 -6
- package/dist/core/adapter/validate-context.js +1 -3
- package/dist/core/asset/asset-placement.js +1 -3
- package/dist/core/asset/frontmatter.js +96 -0
- package/dist/core/common.js +66 -2
- package/dist/core/config/config-io.js +21 -8
- package/dist/core/config/config-walker.js +6 -10
- package/dist/core/config/config.js +11 -8
- package/dist/core/extra-params.js +17 -13
- package/dist/core/improve-result.js +1 -3
- package/dist/core/json-schema.js +9 -11
- package/dist/core/state/migrations.js +74 -2
- package/dist/core/state-db.js +2 -1
- package/dist/core/warn.js +15 -0
- package/dist/execution/executable-identity.js +1 -3
- package/dist/execution/guarded-source.js +1 -6
- package/dist/indexer/bundle-identity-guard.js +6 -1
- package/dist/indexer/db/graph-db.js +139 -154
- package/dist/indexer/ensure-index.js +11 -19
- package/dist/indexer/graph/graph-boost.js +23 -34
- package/dist/indexer/graph/graph-extraction.js +12 -2
- package/dist/indexer/indexer.js +43 -22
- package/dist/indexer/lookup/adapter-concept-owner.js +12 -9
- package/dist/indexer/passes/dir-staleness.js +30 -9
- package/dist/indexer/passes/memory-inference.js +7 -2
- package/dist/indexer/scan/drain-dir.js +2 -4
- package/dist/indexer/search/db-search.js +3 -3
- package/dist/indexer/search/fts-query.js +10 -15
- package/dist/indexer/search/search-source.js +0 -13
- package/dist/indexer/usage/usage-events.js +9 -1
- package/dist/indexer/walk/walker.js +11 -6
- package/dist/integrations/harnesses/claude/result-extractor.js +30 -15
- package/dist/integrations/harnesses/codex/result-extractor.js +43 -6
- package/dist/integrations/harnesses/copilot/result-extractor.js +39 -12
- package/dist/integrations/harnesses/gemini/result-extractor.js +40 -12
- package/dist/integrations/harnesses/index.js +0 -4
- package/dist/integrations/harnesses/openhands/result-extractor.js +51 -19
- package/dist/integrations/harnesses/pi/result-extractor.js +45 -12
- package/dist/integrations/lockfile.js +0 -14
- package/dist/integrations/session-logs/index.js +0 -81
- package/dist/llm/client.js +0 -12
- package/dist/llm/memory-infer.js +1 -3
- package/dist/llm/usage-telemetry.js +1 -3
- package/dist/registry/create-provider-registry.js +4 -0
- package/dist/registry/factory.js +4 -0
- package/dist/registry/resolve.js +23 -8
- package/dist/runtime.js +0 -12
- package/dist/scripts/akm-migrate-node.js +179 -248
- package/dist/scripts/akm-migrate.js +179 -248
- package/dist/setup/setup.js +6 -12
- package/dist/sources/providers/git-install.js +7 -2
- package/dist/sources/providers/tar-utils.js +1 -7
- package/dist/sources/snapshot-fetchers/content-extract.js +0 -17
- package/dist/storage/managed-db.js +20 -7
- package/dist/storage/repositories/events-repository.js +0 -81
- package/dist/storage/repositories/index-entries-repository.js +24 -28
- package/dist/storage/repositories/index-fts-repository.js +24 -30
- package/dist/storage/repositories/index-meta-repository.js +6 -4
- package/dist/storage/repositories/index-schema.js +16 -1
- package/dist/storage/repositories/index-utility-repository.js +0 -57
- package/dist/storage/repositories/index-vec-repository.js +25 -27
- package/dist/storage/repositories/proposals-repository.js +4 -1
- package/dist/storage/repositories/task-history-repository.js +9 -3
- package/dist/tasks/backends/launchd.js +8 -18
- package/dist/tasks/resolve-akm-bin.js +15 -0
- package/dist/tasks/run/task-history.js +21 -31
- package/dist/tasks/scheduler-sync.js +1 -6
- package/dist/tasks/source/bounded-document.js +1 -14
- package/dist/tasks/source/task-to-v3.js +10 -2
- package/dist/workflows/exec/child-workflow.js +1 -1
- package/dist/workflows/exec/native-executor.js +2 -2
- package/dist/workflows/exec/step-work.js +5 -17
- package/dist/workflows/exec/worktree.js +40 -6
- package/dist/workflows/freeze/task-bindings.js +2 -4
- package/dist/workflows/ir/compile.js +3 -14
- package/dist/workflows/ir/schema-v4.js +4 -6
- package/dist/workflows/ir/schema.js +2 -5
- package/dist/workflows/parser.js +23 -26
- package/dist/workflows/source-files.js +8 -13
- package/dist/workflows/source-ir/schema.js +1 -19
- package/docs/migration/v0.9.0-troubleshooting.md +12 -2
- package/docs/migration/v0.9.1-to-v0.9.2.md +18 -4
- package/docs/reference/cli.md +3 -1
- package/docs/reference/data-and-telemetry.md +1 -0
- package/docs/reference/tasks.md +11 -0
- package/docs/reference/workflow-schema.md +24 -0
- package/package.json +2 -9
- package/dist/workflows/source-ir/compare.js +0 -17
|
@@ -7,18 +7,12 @@
|
|
|
7
7
|
* `create --print` emits Markdown; execution accepts peer `.md` and
|
|
8
8
|
* GitHub-shaped `.yml` workflow sources. Validate with `akm lint --type workflows`.
|
|
9
9
|
*/
|
|
10
|
-
import { getParsedInvocation } from "../cli/invocation.js";
|
|
11
10
|
import { getStringArg } from "../cli/parse-args.js";
|
|
12
11
|
import { defineGroupCommand, defineJsonCommand, EXIT_CODES, output } from "../cli/shared.js";
|
|
13
12
|
import { armAbortDeadline } from "../core/abort-deadline.js";
|
|
14
13
|
import { assertFlatAssetName, combineCreatePath, normalizeCreateSubPath } from "../core/asset/asset-create.js";
|
|
15
14
|
import { NotFoundError, UsageError } from "../core/errors.js";
|
|
16
15
|
import { akmIndex } from "../indexer/indexer.js";
|
|
17
|
-
import { getOutputMode } from "../output/context.js";
|
|
18
|
-
import { renderGenericText } from "../output/generic-render.js";
|
|
19
|
-
import { deliverRendered } from "../output/html-render.js";
|
|
20
|
-
import { shapeForCommand } from "../output/shapes.js";
|
|
21
|
-
import { formatPlain } from "../output/text.js";
|
|
22
16
|
import { assertWorkflowMarkdownName, createWorkflowAsset, getWorkflowTemplate } from "../workflows/authoring/authoring.js";
|
|
23
17
|
import { WORKFLOW_MAX_TIMEOUT_MS } from "../workflows/ir/schema.js";
|
|
24
18
|
import { abandonWorkflowRun, getWorkflowStatus, hasWorkflowRun, listWorkflowRuns, resumeWorkflowRun, } from "../workflows/runtime/runs.js";
|
|
@@ -319,61 +313,17 @@ const workflowPlanCommand = defineJsonCommand({
|
|
|
319
313
|
},
|
|
320
314
|
async run({ args }) {
|
|
321
315
|
const result = await akmWorkflowPlan(args.ref);
|
|
322
|
-
//
|
|
323
|
-
//
|
|
324
|
-
//
|
|
325
|
-
//
|
|
326
|
-
//
|
|
327
|
-
//
|
|
328
|
-
//
|
|
329
|
-
//
|
|
330
|
-
//
|
|
331
|
-
//
|
|
332
|
-
//
|
|
333
|
-
// (e.g. `akm --format json workflow plan <ref>`) is consumed by the ROOT
|
|
334
|
-
// command's own declared `format` arg before the `workflow`/`plan`
|
|
335
|
-
// subcommand tokens are even resolved — this LEAF's `args.format` reads
|
|
336
|
-
// `undefined` in exactly that case too, indistinguishable from "no
|
|
337
|
-
// format was named anywhere". Reproduced live: that invocation printed
|
|
338
|
-
// the human TEXT summary at exit 0 even though `getOutputMode().format`
|
|
339
|
-
// was already `"json"` (the control, `akm --format json workflow list`,
|
|
340
|
-
// correctly emitted JSON — only this leaf's own arg-read was wrong).
|
|
341
|
-
// Detect it instead off the process-wide invocation singleton
|
|
342
|
-
// (`getParsedInvocation`, src/cli/invocation.ts) — the same canonical,
|
|
343
|
-
// position-independent argv parse `src/cli.ts` mints ONCE at startup
|
|
344
|
-
// (`setParsedInvocation`, immediately before `initOutputMode` builds the
|
|
345
|
-
// `getOutputMode()` singleton from that identical argv), so this agrees
|
|
346
|
-
// with `getOutputMode()` regardless of where `--format` appeared. A bare
|
|
347
|
-
// `process.argv` read is reserved for `src/cli.ts`/`cli/invocation.ts`
|
|
348
|
-
// themselves (`lint-process-argv.ts`); every other module reads through
|
|
349
|
-
// this singleton instead. When explicit, this defers to the normal
|
|
350
|
-
// `output()` path (json/yaml/text/md/html/jsonl, `--output <path>`)
|
|
351
|
-
// unchanged; when absent, it reproduces `output()`'s OWN "text" branch
|
|
352
|
-
// verbatim (same shape/detail projection, same registered-formatter-or-
|
|
353
|
-
// generic-fallback, same `--output <path>` handling) without touching
|
|
354
|
-
// the shared dispatcher other commands rely on.
|
|
355
|
-
//
|
|
356
|
-
// "No format named anywhere" also has to check the RESOLVED mode, not
|
|
357
|
-
// just argv: `getOutputMode().format` already folds a persisted
|
|
358
|
-
// `output.format` config default in ahead of the hardcoded "json"
|
|
359
|
-
// fallback (`resolveOutputMode`, src/output/context.ts — argv ?? config
|
|
360
|
-
// default ?? "json"). A user who has configured e.g. `output.format:
|
|
361
|
-
// "yaml"` gets yaml from every other command with no `--format` on the
|
|
362
|
-
// line; `workflow plan` must honor that too instead of forcing its
|
|
363
|
-
// human-text branch over a real persisted default. A resolved format of
|
|
364
|
-
// exactly "json" is deliberately left on the text branch below: it's
|
|
365
|
-
// indistinguishable from "nothing configured" (DEFAULT_CONFIG.output.format
|
|
366
|
-
// is also "json" — OutputConfigSchema's `format` carries no independent
|
|
367
|
-
// zod default, so the merge is the only source), and collapsing that case
|
|
368
|
-
// onto the JSON envelope would erase the documented unmarked-default text
|
|
369
|
-
// summary for the overwhelmingly common "user configured nothing" case.
|
|
370
|
-
const mode = getOutputMode();
|
|
371
|
-
if (getParsedInvocation().getFlagValue("--format") === undefined && mode.format === "json") {
|
|
372
|
-
const shaped = shapeForCommand("workflow-plan", result, mode.detail, mode.shape);
|
|
373
|
-
const plain = formatPlain("workflow-plan", shaped, mode.detail);
|
|
374
|
-
deliverRendered(plain ?? renderGenericText("workflow-plan", shaped), mode.outputPath);
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
316
|
+
// json-by-default, like every other verb (#903). This used to default to
|
|
317
|
+
// the human summary, which cost ~60 lines of branch: `args.format` cannot
|
|
318
|
+
// detect "no format named" (citty parses per level, so a global
|
|
319
|
+
// pre-subcommand `--format json` is eaten by the ROOT command and the leaf
|
|
320
|
+
// reads undefined), so it had to route through `getParsedInvocation()`,
|
|
321
|
+
// then fold in a persisted `output.format`, and then still leave a
|
|
322
|
+
// resolved "json" on the text branch because that is indistinguishable
|
|
323
|
+
// from "nothing configured". The last compromise meant an explicit
|
|
324
|
+
// `--format json` silently did nothing for anyone whose config already
|
|
325
|
+
// resolved to json. `--format text` still renders the summary through the
|
|
326
|
+
// registered formatter; it is just no longer the unmarked default.
|
|
377
327
|
output("workflow-plan", result);
|
|
378
328
|
},
|
|
379
329
|
});
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
import fs from "node:fs";
|
|
45
45
|
import path from "node:path";
|
|
46
46
|
import { parseFrontmatter } from "../../asset/frontmatter.js";
|
|
47
|
+
import { toPosix } from "../../common.js";
|
|
47
48
|
import { hashContent, nonEmptyString, readTags } from "./shared.js";
|
|
48
49
|
/** A skills collection is a single-component bundle; its one component is `main`. */
|
|
49
50
|
const COMPONENT_ID = "main";
|
|
@@ -56,11 +57,6 @@ const DESCRIPTION_MAX = 1024;
|
|
|
56
57
|
const RESERVED_NAME_WORDS = ["anthropic", "claude"];
|
|
57
58
|
/** name charset/shape rule (spec §4.5). */
|
|
58
59
|
const NAME_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
59
|
-
/** Upper bound on the bounded `content` FTS field (mirrors okf-adapter). */
|
|
60
|
-
const MAX_CONTENT_CHARS = 100_000;
|
|
61
|
-
function toPosix(p) {
|
|
62
|
-
return p.replace(/\\/g, "/");
|
|
63
|
-
}
|
|
64
60
|
/** conceptId + package-dir name for a `<name>/SKILL.md` file, or null when the file is not a skill manifest. */
|
|
65
61
|
function skillPackage(relPath) {
|
|
66
62
|
const posix = toPosix(relPath);
|
|
@@ -95,7 +91,7 @@ function recognize(c, file) {
|
|
|
95
91
|
adapterId: "agent-skills",
|
|
96
92
|
type: "skill",
|
|
97
93
|
name,
|
|
98
|
-
content: body
|
|
94
|
+
content: body,
|
|
99
95
|
};
|
|
100
96
|
if (description !== undefined)
|
|
101
97
|
doc.description = description;
|
|
@@ -88,7 +88,7 @@ import { executionDefaultsFromFrontmatter, renderMarkdownExecutionSource } from
|
|
|
88
88
|
import { recognizeMatch } from "../recognize-match.js";
|
|
89
89
|
import { perTypeValidateChecks, skillDirectoryDiagnostics, workflowYamlSourceDiagnostics } from "./akm-lint.js";
|
|
90
90
|
import { applyFoldedMetadata, foldRecognizedMetadata } from "./akm-metadata.js";
|
|
91
|
-
import { hashContent, runBaseValidateChecks } from "./shared.js";
|
|
91
|
+
import { hashContent, RESERVED_FILES, runBaseValidateChecks } from "./shared.js";
|
|
92
92
|
// `recognizeMatch` + the builtin matcher list moved to the cycle-free leaf
|
|
93
93
|
// `../recognize-match` (Chunk 5 M-b) so both this adapter AND the indexer
|
|
94
94
|
// metadata pass import it without closing a metadata ↔ adapter cycle. Re-exported
|
|
@@ -99,12 +99,11 @@ export { recognizeMatch } from "../recognize-match.js";
|
|
|
99
99
|
* OKF reserved structural files (ref-grammar decision D-R6, spec §5.1 /
|
|
100
100
|
* adapter-spec §5.1/§6): `index.md` (directory listing) and `log.md` (update
|
|
101
101
|
* history) are bundle structure at EVERY depth — never a concept document. The
|
|
102
|
-
* `okf` / `llm-wiki` adapters already exclude them (`
|
|
102
|
+
* `okf` / `llm-wiki` adapters already exclude them (`shared.ts`'s
|
|
103
103
|
* `RESERVED_FILES`); this brings the `akm` adapter into conformance so a
|
|
104
104
|
* `knowledge/index.md` never classifies as a `knowledge` item. Case-insensitive,
|
|
105
105
|
* matched on the bare filename so the exclusion holds at any depth.
|
|
106
106
|
*/
|
|
107
|
-
const RESERVED_FILES = new Set(["index.md", "log.md"]);
|
|
108
107
|
/** True when `name` (a bare file name) is an OKF reserved file, case-insensitively (D-R6). */
|
|
109
108
|
function isReservedFileName(name) {
|
|
110
109
|
return RESERVED_FILES.has(name.toLowerCase());
|
|
@@ -57,7 +57,7 @@ import { taskSourceErrorDetail } from "../../../tasks/source-v3.js";
|
|
|
57
57
|
import { compileWorkflowPlan } from "../../../workflows/ir/compile.js";
|
|
58
58
|
import { compileWorkflowSource } from "../../../workflows/source-ir/compile.js";
|
|
59
59
|
import { conceptIdForStashFile } from "../../asset/resolve-ref.js";
|
|
60
|
-
import { isAkmRegistryCachePath } from "../../common.js";
|
|
60
|
+
import { isAkmRegistryCachePath, scanEnvKeyNames } from "../../common.js";
|
|
61
61
|
/** Recommended `category` values for facts — `commands/lint/fact-linter.ts:9`. */
|
|
62
62
|
const KNOWN_CATEGORIES = new Set(["personal", "team", "project", "convention", "meta"]);
|
|
63
63
|
/** Placeholder markers a workflow stub carries — `commands/lint/workflow-linter.ts:10`. */
|
|
@@ -118,24 +118,6 @@ export function nameOrTypeDiagnostics(relPath, data, frontmatter, allowedTypes)
|
|
|
118
118
|
return [];
|
|
119
119
|
}
|
|
120
120
|
// ── env dangerous-key scan (content-based port of env-key-rules.ts) ──────────
|
|
121
|
-
/** Matches a KEY=value assignment line, capturing only the key — `commands/env/env.ts:62`. */
|
|
122
|
-
const ASSIGN_RE = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/;
|
|
123
|
-
/** Port of `commands/env/env.ts#scanKeys` (`:65-77`) — content-based (overlay `raw`, not disk). */
|
|
124
|
-
function scanKeys(text) {
|
|
125
|
-
const keys = [];
|
|
126
|
-
const seen = new Set();
|
|
127
|
-
for (const line of text.split(/\r?\n/)) {
|
|
128
|
-
const m = line.match(ASSIGN_RE);
|
|
129
|
-
if (!m)
|
|
130
|
-
continue;
|
|
131
|
-
const key = m[1];
|
|
132
|
-
if (seen.has(key))
|
|
133
|
-
continue;
|
|
134
|
-
seen.add(key);
|
|
135
|
-
keys.push(key);
|
|
136
|
-
}
|
|
137
|
-
return keys;
|
|
138
|
-
}
|
|
139
121
|
/** Port of `env-key-rules.ts#collectSuppressedKeys` (`:144-164`) — content-based. */
|
|
140
122
|
function collectSuppressedKeys(raw) {
|
|
141
123
|
const suppressed = new Set();
|
|
@@ -171,7 +153,7 @@ export function dangerousEnvKeyDiagnostics(type, relPath, raw) {
|
|
|
171
153
|
return []; // NARROWNESS: collectEnvFiles only visits *.env
|
|
172
154
|
// `relPath` is already stash-root-relative, so "." IS the stash root here.
|
|
173
155
|
const ref = conceptIdForStashFile(type, ".", relPath);
|
|
174
|
-
const keys =
|
|
156
|
+
const keys = scanEnvKeyNames(raw);
|
|
175
157
|
const suppressed = collectSuppressedKeys(raw);
|
|
176
158
|
const diagnostics = [];
|
|
177
159
|
for (const key of keys) {
|
|
@@ -35,16 +35,12 @@ import fs from "node:fs";
|
|
|
35
35
|
import path from "node:path";
|
|
36
36
|
import { parseTaskSource } from "../../../tasks/source/parse-task-source.js";
|
|
37
37
|
import { TASK_EXTENSION, TASK_NEAR_MISS_EXTENSION, taskExtensionDetail, taskSourceErrorDetail, } from "../../../tasks/source-v3.js";
|
|
38
|
+
import { toPosix } from "../../common.js";
|
|
38
39
|
import { hashContent } from "./shared.js";
|
|
39
40
|
/** A native task bundle is single-component; its one component is `main`. */
|
|
40
41
|
const COMPONENT_ID = "main";
|
|
41
42
|
/** The task YAML extension (spec §6 task row). */
|
|
42
43
|
const TASK_EXT = TASK_EXTENSION;
|
|
43
|
-
/** Upper bound on the bounded `content` FTS field (mirrors okf-adapter). */
|
|
44
|
-
const MAX_CONTENT_CHARS = 100_000;
|
|
45
|
-
function toPosix(p) {
|
|
46
|
-
return p.replace(/\\/g, "/");
|
|
47
|
-
}
|
|
48
44
|
function recognize(c, file) {
|
|
49
45
|
if (file.ext !== TASK_EXT)
|
|
50
46
|
return null;
|
|
@@ -61,7 +57,7 @@ function recognize(c, file) {
|
|
|
61
57
|
adapterId: "akm-task",
|
|
62
58
|
type: "task",
|
|
63
59
|
name,
|
|
64
|
-
content: raw
|
|
60
|
+
content: raw,
|
|
65
61
|
};
|
|
66
62
|
}
|
|
67
63
|
async function validate(c, changes, ctx) {
|
|
@@ -23,17 +23,13 @@ import fs from "node:fs";
|
|
|
23
23
|
import path from "node:path";
|
|
24
24
|
import { looksLikeGithubWorkflowSource } from "../../../workflows/source-ir/compile.js";
|
|
25
25
|
import { parseFrontmatter } from "../../asset/frontmatter.js";
|
|
26
|
+
import { toPosix } from "../../common.js";
|
|
26
27
|
import { perTypeValidateChecks, workflowYamlSourceDiagnostics } from "./akm-lint.js";
|
|
27
28
|
import { hashContent, nonEmptyString, readTags, runBaseValidateChecks } from "./shared.js";
|
|
28
29
|
/** A native workflow bundle is single-component; its one component is `main`. */
|
|
29
30
|
const COMPONENT_ID = "main";
|
|
30
31
|
/** The two authoritative workflow source formats. `.yaml` is deliberately not accepted. */
|
|
31
32
|
const WORKFLOW_EXTS = new Set([".md", ".yml"]);
|
|
32
|
-
/** Upper bound on the bounded `content` FTS field (mirrors okf-adapter). */
|
|
33
|
-
const MAX_CONTENT_CHARS = 100_000;
|
|
34
|
-
function toPosix(p) {
|
|
35
|
-
return p.replace(/\\/g, "/");
|
|
36
|
-
}
|
|
37
33
|
/** Strip the recognized workflow extension from a component-root-relative path → conceptId. */
|
|
38
34
|
function conceptIdOf(relPath) {
|
|
39
35
|
return toPosix(relPath).replace(/\.(?:md|yml)$/i, "");
|
|
@@ -78,7 +74,7 @@ function recognize(c, file) {
|
|
|
78
74
|
adapterId: "akm-workflow",
|
|
79
75
|
type: "workflow",
|
|
80
76
|
name,
|
|
81
|
-
content: body
|
|
77
|
+
content: body,
|
|
82
78
|
};
|
|
83
79
|
if (description !== undefined)
|
|
84
80
|
doc.description = description;
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
import fs from "node:fs";
|
|
31
31
|
import path from "node:path";
|
|
32
32
|
import { assetPathCandidatesForName, assetPathForName, typeForStashDir } from "../../asset/asset-placement.js";
|
|
33
|
+
import { scanEnvKeyNames, toPosix } from "../../common.js";
|
|
33
34
|
import { dangerousEnvKeyDiagnostics } from "./akm-lint.js";
|
|
34
35
|
import { hashContent } from "./shared.js";
|
|
35
36
|
/** A dotenv bundle is single-component; its one component is `main`. */
|
|
@@ -40,11 +41,6 @@ const ENV_DIR = "env";
|
|
|
40
41
|
const SECRETS_DIR = "secrets";
|
|
41
42
|
/** Non-secret marker suffixes under `secrets/` (spec §6 secret row). */
|
|
42
43
|
const SECRET_SKIP_SUFFIXES = [".lock", ".sensitive"];
|
|
43
|
-
/** Matches a KEY=value assignment line, capturing only the key (mirrors akm-lint scanKeys). */
|
|
44
|
-
const ASSIGN_RE = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/;
|
|
45
|
-
function toPosix(p) {
|
|
46
|
-
return p.replace(/\\/g, "/");
|
|
47
|
-
}
|
|
48
44
|
/** Classify a component-root-relative file as env / secret, or null (abstain). */
|
|
49
45
|
function classify(relPath) {
|
|
50
46
|
const posix = toPosix(relPath);
|
|
@@ -80,22 +76,6 @@ function hasSensitiveMarker(absPath, type) {
|
|
|
80
76
|
const marker = type === "env" ? absPath.replace(/\.env$/i, ".sensitive") : `${absPath}.sensitive`;
|
|
81
77
|
return marker !== absPath && fs.existsSync(marker);
|
|
82
78
|
}
|
|
83
|
-
/** Extract KEY NAMES (never values) from an env file's raw content, first-appearance order, deduped. */
|
|
84
|
-
function scanKeyNames(raw) {
|
|
85
|
-
const keys = [];
|
|
86
|
-
const seen = new Set();
|
|
87
|
-
for (const line of raw.split(/\r?\n/)) {
|
|
88
|
-
const m = line.match(ASSIGN_RE);
|
|
89
|
-
if (!m)
|
|
90
|
-
continue;
|
|
91
|
-
const key = m[1];
|
|
92
|
-
if (seen.has(key))
|
|
93
|
-
continue;
|
|
94
|
-
seen.add(key);
|
|
95
|
-
keys.push(key);
|
|
96
|
-
}
|
|
97
|
-
return keys;
|
|
98
|
-
}
|
|
99
79
|
function conceptIdForPath(type, relativePath) {
|
|
100
80
|
const posix = toPosix(relativePath);
|
|
101
81
|
if (type === "secret")
|
|
@@ -115,7 +95,7 @@ function recognize(c, file) {
|
|
|
115
95
|
// env: strip `.env`; surface KEY NAMES only (never values/comments/content).
|
|
116
96
|
const conceptId = conceptIdForPath(type, posix);
|
|
117
97
|
const name = (conceptId.split("/").pop() ?? conceptId) || "default";
|
|
118
|
-
const keys =
|
|
98
|
+
const keys = scanEnvKeyNames(raw);
|
|
119
99
|
const doc = {
|
|
120
100
|
ref: `${c.id}//${conceptId}`,
|
|
121
101
|
bundle: c.id,
|
|
@@ -23,19 +23,13 @@
|
|
|
23
23
|
*/
|
|
24
24
|
import path from "node:path";
|
|
25
25
|
import { parseFrontmatter } from "../../asset/frontmatter.js";
|
|
26
|
+
import { toPosix } from "../../common.js";
|
|
26
27
|
import { SCRIPT_EXTENSIONS } from "../../recognition-util.js";
|
|
27
|
-
import { hashContent, nonEmptyString, readTags, runBaseValidateChecks } from "./shared.js";
|
|
28
|
+
import { hashContent, nonEmptyString, RESERVED_FILES, readTags, runBaseValidateChecks } from "./shared.js";
|
|
28
29
|
/** A generic-files bundle is single-component; its one component is `main`. */
|
|
29
30
|
const COMPONENT_ID = "main";
|
|
30
31
|
/** Markdown / plain-text extensions classified as `document`. */
|
|
31
32
|
const DOCUMENT_EXTENSIONS = new Set([".md", ".markdown", ".txt", ".text"]);
|
|
32
|
-
/** OKF reserved structural files (D-R6) — excluded from the catch-all, case-insensitive. */
|
|
33
|
-
const RESERVED_FILES = new Set(["index.md", "log.md"]);
|
|
34
|
-
/** Upper bound on the bounded `content` FTS field (mirrors okf-adapter). */
|
|
35
|
-
const MAX_CONTENT_CHARS = 100_000;
|
|
36
|
-
function toPosix(p) {
|
|
37
|
-
return p.replace(/\\/g, "/");
|
|
38
|
-
}
|
|
39
33
|
function isReserved(base) {
|
|
40
34
|
return RESERVED_FILES.has(base.toLowerCase());
|
|
41
35
|
}
|
|
@@ -69,7 +63,7 @@ function recognize(c, file) {
|
|
|
69
63
|
adapterId: "generic-files",
|
|
70
64
|
type,
|
|
71
65
|
name,
|
|
72
|
-
content: body
|
|
66
|
+
content: body,
|
|
73
67
|
};
|
|
74
68
|
if (type !== "script")
|
|
75
69
|
doc.ownsPresentation = true;
|
|
@@ -73,6 +73,7 @@ import fs from "node:fs";
|
|
|
73
73
|
import path from "node:path";
|
|
74
74
|
import { parse as parseYaml } from "yaml";
|
|
75
75
|
import { parseFrontmatter, parseFrontmatterBlock } from "../../asset/frontmatter.js";
|
|
76
|
+
import { toPosix } from "../../common.js";
|
|
76
77
|
import { hashContent, nonEmptyString } from "./shared.js";
|
|
77
78
|
/** A wiki is a single-component bundle; its one component is conventionally `main` (recognition golden). */
|
|
78
79
|
const WIKI_COMPONENT_ID = "main";
|
|
@@ -86,12 +87,7 @@ const RESERVED_ROOT_FILES = new Set(["schema.md", "index.md", "log.md"]);
|
|
|
86
87
|
const RAW_SUBDIR = "raw";
|
|
87
88
|
/** Content subdirectory holding agent-authored pages. */
|
|
88
89
|
const PAGES_SUBDIR = "pages";
|
|
89
|
-
/** Upper bound on the bounded `content` FTS field (mirrors okf-adapter). */
|
|
90
|
-
const MAX_CONTENT_CHARS = 100_000;
|
|
91
90
|
/** POSIX-normalize separators. */
|
|
92
|
-
function toPosix(p) {
|
|
93
|
-
return p.replace(/\\/g, "/");
|
|
94
|
-
}
|
|
95
91
|
/** conceptId = component-root-relative path minus `.md`. */
|
|
96
92
|
function conceptIdOf(relPath) {
|
|
97
93
|
return toPosix(relPath).replace(/\.md$/i, "");
|
|
@@ -280,7 +276,7 @@ function recognize(c, file) {
|
|
|
280
276
|
ownsPresentation: true,
|
|
281
277
|
type: isRaw ? WIKI_SOURCE_TYPE : (fm.pageKind ?? DEFAULT_PAGE_KIND),
|
|
282
278
|
name: lastSegment,
|
|
283
|
-
content: body
|
|
279
|
+
content: body,
|
|
284
280
|
};
|
|
285
281
|
if (fm.description !== undefined)
|
|
286
282
|
doc.description = fm.description;
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
import fs from "node:fs";
|
|
32
32
|
import path from "node:path";
|
|
33
33
|
import { parseFrontmatter } from "../../asset/frontmatter.js";
|
|
34
|
-
import {
|
|
34
|
+
import { isRecord, toPosix } from "../../common.js";
|
|
35
|
+
import { hashContent, nonEmptyString, RESERVED_FILES, readTags, runBaseValidateChecks } from "./shared.js";
|
|
35
36
|
/** v0.2 frontmatter keys consumed into first-class fields below (§0.1) — excluded from the generic `documentJson` extras fold alongside the v0.1 five, so nothing is duplicated between a first-class field and the opaque extras bag. */
|
|
36
37
|
const CONSUMED_FRONTMATTER_KEYS = [
|
|
37
38
|
"type",
|
|
@@ -46,17 +47,13 @@ const CONSUMED_FRONTMATTER_KEYS = [
|
|
|
46
47
|
"stale_after",
|
|
47
48
|
"okf_version",
|
|
48
49
|
];
|
|
49
|
-
/** True for a plain (non-null, non-array) object — the shape every v0.2 mapping (`generated`, one `verified`/`sources` entry) must have. */
|
|
50
|
-
function isPlainObject(value) {
|
|
51
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
52
|
-
}
|
|
53
50
|
/**
|
|
54
51
|
* Parse one `verified:` actor mapping (`{by, at?}`). Tolerant: a missing/blank
|
|
55
52
|
* `by` yields `undefined` (the entry is dropped, never rejecting the document
|
|
56
53
|
* — OKF conformance leniency); `at` is independently optional.
|
|
57
54
|
*/
|
|
58
55
|
function parseActorMapping(value) {
|
|
59
|
-
if (!
|
|
56
|
+
if (!isRecord(value))
|
|
60
57
|
return undefined;
|
|
61
58
|
const by = nonEmptyString(value.by);
|
|
62
59
|
if (by === undefined)
|
|
@@ -96,7 +93,7 @@ function parseOkfSources(value) {
|
|
|
96
93
|
return undefined;
|
|
97
94
|
const out = [];
|
|
98
95
|
for (const item of value) {
|
|
99
|
-
if (!
|
|
96
|
+
if (!isRecord(item))
|
|
100
97
|
continue;
|
|
101
98
|
const resource = nonEmptyString(item.resource);
|
|
102
99
|
if (resource === undefined)
|
|
@@ -125,15 +122,7 @@ function parseOkfSources(value) {
|
|
|
125
122
|
function parseLifecycleStatus(value) {
|
|
126
123
|
return value === "draft" || value === "stable" || value === "deprecated" ? value : undefined;
|
|
127
124
|
}
|
|
128
|
-
/**
|
|
129
|
-
const RESERVED_FILES = new Set(["index.md", "log.md"]);
|
|
130
|
-
/** Upper bound on the bounded `content` FTS field (§3: "content: FTS 1 (bounded)"). Small fixtures are never truncated. */
|
|
131
|
-
const MAX_CONTENT_CHARS = 100_000;
|
|
132
|
-
/** POSIX-normalize separators without importing a cycle-participant helper. */
|
|
133
|
-
function toPosix(p) {
|
|
134
|
-
return p.replace(/\\/g, "/");
|
|
135
|
-
}
|
|
136
|
-
/** True when `name` (a bare file name) is a reserved OKF file, case-insensitively. */
|
|
125
|
+
/** True when `name` (a bare file name) is a reserved OKF file, case-insensitively (§5, OKF §1.4). */
|
|
137
126
|
function isReservedFileName(name) {
|
|
138
127
|
return RESERVED_FILES.has(name.toLowerCase());
|
|
139
128
|
}
|
|
@@ -237,7 +226,7 @@ function recognize(c, file) {
|
|
|
237
226
|
// `generated.at` — v0.2's replacement for `timestamp` — takes precedence;
|
|
238
227
|
// `timestamp` remains a fully valid fallback (the v0.2-permitted legacy
|
|
239
228
|
// reading, not merely tolerated). Both stay fully optional (never rejects).
|
|
240
|
-
const generatedMapping =
|
|
229
|
+
const generatedMapping = isRecord(data.generated) ? data.generated : undefined;
|
|
241
230
|
const generatedAt = generatedMapping ? nonEmptyString(generatedMapping.at) : undefined;
|
|
242
231
|
const generatedBy = generatedMapping ? nonEmptyString(generatedMapping.by) : undefined;
|
|
243
232
|
const legacyTimestamp = nonEmptyString(data.timestamp);
|
|
@@ -272,7 +261,7 @@ function recognize(c, file) {
|
|
|
272
261
|
ownsPresentation: true,
|
|
273
262
|
type,
|
|
274
263
|
name,
|
|
275
|
-
content: body
|
|
264
|
+
content: body,
|
|
276
265
|
};
|
|
277
266
|
if (description !== undefined)
|
|
278
267
|
doc.description = description;
|
|
@@ -69,6 +69,8 @@ export function readTags(value) {
|
|
|
69
69
|
const tags = value.filter((tag) => typeof tag === "string" && tag.trim().length > 0);
|
|
70
70
|
return tags.length > 0 ? tags : undefined;
|
|
71
71
|
}
|
|
72
|
+
/** Reserved files (case-insensitive) every concrete adapter recognizes but never indexes as a concept. */
|
|
73
|
+
export const RESERVED_FILES = new Set(["index.md", "log.md"]);
|
|
72
74
|
// ── Base validate checks (port of `BaseLinter.runBaseChecks`) ────────────────
|
|
73
75
|
function checkMissingUpdated(data, frontmatterText) {
|
|
74
76
|
return frontmatterText !== null && !("updated" in data);
|
|
@@ -49,21 +49,15 @@
|
|
|
49
49
|
import path from "node:path";
|
|
50
50
|
import { createAdapterExtensions, } from "../../../execution/source.js";
|
|
51
51
|
import { parseFrontmatter } from "../../asset/frontmatter.js";
|
|
52
|
+
import { toPosix } from "../../common.js";
|
|
52
53
|
import { executionDefaultsFromFrontmatter, renderMarkdownExecutionSource } from "../execution-source.js";
|
|
53
54
|
import { skillDirectoryDiagnostics } from "./akm-lint.js";
|
|
54
|
-
import { hashContent, nonEmptyString, readTags, runBaseValidateChecks } from "./shared.js";
|
|
55
|
-
/** OKF reserved structural files (D-R6) — excluded at every depth, case-insensitive. */
|
|
56
|
-
const RESERVED_FILES = new Set(["index.md", "log.md"]);
|
|
57
|
-
/** Upper bound on the bounded `content` FTS field (mirrors okf-adapter). */
|
|
58
|
-
const MAX_CONTENT_CHARS = 100_000;
|
|
55
|
+
import { hashContent, nonEmptyString, RESERVED_FILES, readTags, runBaseValidateChecks } from "./shared.js";
|
|
59
56
|
/** The canonical (plural) subdir spellings writes normalize to (open-question-6). */
|
|
60
57
|
const CANONICAL_COMMAND_DIR = "commands";
|
|
61
58
|
const CANONICAL_AGENT_DIR = "agents";
|
|
62
59
|
const CANONICAL_SKILL_DIR = "skills";
|
|
63
60
|
const SKILL_MANIFEST = "SKILL.md";
|
|
64
|
-
function toPosix(p) {
|
|
65
|
-
return p.replace(/\\/g, "/");
|
|
66
|
-
}
|
|
67
61
|
function isReserved(base) {
|
|
68
62
|
return RESERVED_FILES.has(base.toLowerCase());
|
|
69
63
|
}
|
|
@@ -122,7 +116,7 @@ export function recognizeToolDir(layout, c, file) {
|
|
|
122
116
|
adapterId: layout.adapterId,
|
|
123
117
|
type: cls.type,
|
|
124
118
|
name,
|
|
125
|
-
content: body
|
|
119
|
+
content: body,
|
|
126
120
|
};
|
|
127
121
|
if (cls.type === "instruction")
|
|
128
122
|
doc.ownsPresentation = true;
|
|
@@ -28,6 +28,7 @@ import fs from "node:fs";
|
|
|
28
28
|
import path from "node:path";
|
|
29
29
|
import { parse as parseYaml } from "yaml";
|
|
30
30
|
import { parseFrontmatter, parseFrontmatterBlock } from "../../asset/frontmatter.js";
|
|
31
|
+
import { toPosix } from "../../common.js";
|
|
31
32
|
import { hashContent, nonEmptyString, runBaseValidateChecks } from "./shared.js";
|
|
32
33
|
/** A snapshot bundle is single-component; its one component is `main`. */
|
|
33
34
|
const COMPONENT_ID = "main";
|
|
@@ -37,11 +38,6 @@ const PAGES_PREFIX = "stash/knowledge/";
|
|
|
37
38
|
const MANIFEST_FILE = "manifest.json";
|
|
38
39
|
/** The tag the snapshot writer stamps on every crawled page. */
|
|
39
40
|
const WEBSITE_TAG = "website";
|
|
40
|
-
/** Upper bound on the bounded `content` FTS field (mirrors okf-adapter). */
|
|
41
|
-
const MAX_CONTENT_CHARS = 100_000;
|
|
42
|
-
function toPosix(p) {
|
|
43
|
-
return p.replace(/\\/g, "/");
|
|
44
|
-
}
|
|
45
41
|
function parseSnapshotFrontmatter(raw) {
|
|
46
42
|
const out = { tags: [] };
|
|
47
43
|
const block = parseFrontmatterBlock(raw);
|
|
@@ -98,7 +94,7 @@ function recognize(c, file) {
|
|
|
98
94
|
ownsPresentation: true,
|
|
99
95
|
type: "website",
|
|
100
96
|
name,
|
|
101
|
-
content: body
|
|
97
|
+
content: body,
|
|
102
98
|
};
|
|
103
99
|
if (fm.description !== undefined)
|
|
104
100
|
doc.description = fm.description;
|
|
@@ -48,9 +48,7 @@ import fs from "node:fs";
|
|
|
48
48
|
import path from "node:path";
|
|
49
49
|
import { assetPathForName, stashDirFor } from "../asset/asset-placement.js";
|
|
50
50
|
import { typeNameFromConceptId } from "../asset/resolve-ref.js";
|
|
51
|
-
|
|
52
|
-
return p.replace(/\\/g, "/");
|
|
53
|
-
}
|
|
51
|
+
import { toPosix } from "../common.js";
|
|
54
52
|
/** Build the overlay map, keyed by POSIX path relative to `root`. */
|
|
55
53
|
function buildOverlay(root, changes) {
|
|
56
54
|
const overlay = new Map();
|
|
@@ -25,10 +25,8 @@
|
|
|
25
25
|
*/
|
|
26
26
|
import fs from "node:fs";
|
|
27
27
|
import path from "node:path";
|
|
28
|
+
import { toPosix } from "../common.js";
|
|
28
29
|
import { DERIVED_SUFFIX, SCRIPT_EXTENSIONS, WORKFLOW_EXTENSIONS } from "../recognition-util.js";
|
|
29
|
-
function toPosix(input) {
|
|
30
|
-
return input.replace(/\\/g, "/");
|
|
31
|
-
}
|
|
32
30
|
const workflowSpec = {
|
|
33
31
|
isRelevantFile: (fileName) => WORKFLOW_EXTENSIONS.includes(path.extname(fileName).toLowerCase()),
|
|
34
32
|
toCanonicalName: (typeRoot, filePath) => {
|
|
@@ -323,6 +323,102 @@ export function removeFrontmatterListValues(raw, key, values) {
|
|
|
323
323
|
}
|
|
324
324
|
return changed ? out.join("\n") : null;
|
|
325
325
|
}
|
|
326
|
+
/**
|
|
327
|
+
* Rewrite specific VALUES in one frontmatter list key to new spellings,
|
|
328
|
+
* preserving every other byte — the rename counterpart to
|
|
329
|
+
* {@link removeFrontmatterListValues}, used by `akm lint --fix` to migrate
|
|
330
|
+
* retired `type:slug` xref values (`xrefs:`/`supersededBy:`/`contradictedBy:`)
|
|
331
|
+
* to their conceptId form once resolution confirms the rewritten spelling
|
|
332
|
+
* still points at a real asset.
|
|
333
|
+
*
|
|
334
|
+
* Handles the same three spellings a belief channel appears in: a block
|
|
335
|
+
* sequence, an inline flow, and a bare scalar. `replacements` maps an
|
|
336
|
+
* unquoted OLD value to its NEW value; a value not present in the map is left
|
|
337
|
+
* untouched byte-for-byte (including any quoting it had).
|
|
338
|
+
*
|
|
339
|
+
* Returns the rewritten source, or `null` when `raw` has no well-formed
|
|
340
|
+
* frontmatter block or nothing matched, so the caller can leave the file
|
|
341
|
+
* untouched. Deliberately source-preserving, like its sibling.
|
|
342
|
+
*/
|
|
343
|
+
export function rewriteFrontmatterListValue(raw, key, replacements) {
|
|
344
|
+
if (replacements.size === 0)
|
|
345
|
+
return null;
|
|
346
|
+
const lines = raw.split(/\r?\n/);
|
|
347
|
+
if (lines[0]?.trim() !== "---")
|
|
348
|
+
return null;
|
|
349
|
+
const closeIdx = lines.findIndex((l, i) => i > 0 && l.trim() === "---");
|
|
350
|
+
if (closeIdx === -1)
|
|
351
|
+
return null;
|
|
352
|
+
const out = [];
|
|
353
|
+
let changed = false;
|
|
354
|
+
let index = 0;
|
|
355
|
+
while (index < lines.length) {
|
|
356
|
+
const line = lines[index];
|
|
357
|
+
if (index === 0 || index >= closeIdx) {
|
|
358
|
+
out.push(line);
|
|
359
|
+
index += 1;
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
const kv = line.match(/^(\w[\w-]*):\s*(.*)$/);
|
|
363
|
+
if (kv === null || kv[1] !== key) {
|
|
364
|
+
out.push(line);
|
|
365
|
+
index += 1;
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
const rest = kv[2].trim();
|
|
369
|
+
// Inline flow: xrefs: [a, b]
|
|
370
|
+
const flow = rest.match(/^\[(.*)\]$/);
|
|
371
|
+
if (flow !== null) {
|
|
372
|
+
const items = flow[1].split(",").map((item) => item.trim());
|
|
373
|
+
const nextItems = items.map((item) => {
|
|
374
|
+
if (!item)
|
|
375
|
+
return item;
|
|
376
|
+
const replacement = replacements.get(unquote(item));
|
|
377
|
+
if (replacement === undefined)
|
|
378
|
+
return item;
|
|
379
|
+
changed = true;
|
|
380
|
+
return replacement;
|
|
381
|
+
});
|
|
382
|
+
out.push(items.length > 0 && items[0] !== "" ? `${kv[1]}: [${nextItems.join(", ")}]` : line);
|
|
383
|
+
index += 1;
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
// Bare scalar: xrefs: a
|
|
387
|
+
if (rest !== "") {
|
|
388
|
+
const replacement = replacements.get(unquote(rest));
|
|
389
|
+
if (replacement === undefined) {
|
|
390
|
+
out.push(line);
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
changed = true;
|
|
394
|
+
out.push(`${kv[1]}: ${replacement}`);
|
|
395
|
+
}
|
|
396
|
+
index += 1;
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
// Block sequence: the key line, then ` - value` items.
|
|
400
|
+
out.push(line);
|
|
401
|
+
let cursor = index + 1;
|
|
402
|
+
while (cursor < closeIdx) {
|
|
403
|
+
const itemLine = lines[cursor];
|
|
404
|
+
const itemMatch = itemLine.match(/^(\s*-\s*)(.*)$/);
|
|
405
|
+
if (itemMatch === null)
|
|
406
|
+
break;
|
|
407
|
+
const [, prefix, value] = itemMatch;
|
|
408
|
+
const replacement = replacements.get(unquote(value));
|
|
409
|
+
if (replacement === undefined) {
|
|
410
|
+
out.push(itemLine);
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
changed = true;
|
|
414
|
+
out.push(`${prefix}${replacement}`);
|
|
415
|
+
}
|
|
416
|
+
cursor += 1;
|
|
417
|
+
}
|
|
418
|
+
index = cursor;
|
|
419
|
+
}
|
|
420
|
+
return changed ? out.join("\n") : null;
|
|
421
|
+
}
|
|
326
422
|
/**
|
|
327
423
|
* Parse a YAML scalar value (string, boolean, or number).
|
|
328
424
|
*
|