akm-cli 0.9.1 → 0.9.2-alpha.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 +103 -28
- package/README.md +3 -1
- package/SECURITY.md +1 -1
- package/STABILITY.md +1 -1
- package/dist/akm +2 -2
- package/dist/akm-migrate +2 -2
- package/dist/assets/hints/cli-hints-full.md +14 -9
- package/dist/assets/improve-strategies/proactive-maintenance.json +1 -1
- package/dist/assets/improve-strategies/reflect-distill.json +1 -1
- package/dist/assets/models.json +35 -0
- package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +3 -4
- package/dist/assets/stash-skeleton/facts/conventions/organization.md +1 -3
- package/dist/assets/tasks/core/extract.yml +6 -5
- package/dist/assets/tasks/core/improve.yml +6 -5
- package/dist/assets/tasks/core/index-refresh.yml +6 -5
- package/dist/assets/tasks/core/sync.yml +6 -5
- package/dist/assets/tasks/core/version-check.yml +6 -5
- package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-catchup.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-consolidate.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-frequent.yml +6 -5
- package/dist/assets/tasks/improve/akm-improve-nightly.yml +6 -5
- package/dist/cli/confirm.js +2 -2
- package/dist/cli/parse-args.js +3 -24
- package/dist/cli/retired-commands.js +1 -1
- package/dist/cli/shared.js +2 -2
- package/dist/cli.js +11 -9
- package/dist/commands/agent/agent-dispatch.js +55 -89
- package/dist/commands/agent/contribute-cli.js +12 -45
- package/dist/commands/command/builtin-action.js +32 -0
- package/dist/commands/command/command-cli.js +99 -0
- package/dist/commands/command/command-execution.js +308 -0
- package/dist/commands/command/execution-source-loader.js +176 -0
- package/dist/commands/command/portable-template.js +60 -0
- package/dist/commands/config-cli.js +10 -4
- package/dist/commands/env/env.js +4 -2
- package/dist/commands/feedback-cli.js +1 -1
- package/dist/commands/health/checks.js +241 -29
- package/dist/commands/health/html-report.js +0 -14
- package/dist/commands/health/report-view-model.js +0 -1
- package/dist/commands/health/surfaces.js +6 -7
- package/dist/commands/health/types.js +0 -2
- package/dist/commands/health.js +63 -18
- package/dist/commands/improve/collapse-detector.js +5 -6
- package/dist/commands/improve/consolidate.js +251 -214
- package/dist/commands/improve/distill/promote-memory.js +71 -34
- package/dist/commands/improve/distill/quality-gate.js +17 -5
- package/dist/commands/improve/distill.js +232 -155
- package/dist/commands/improve/eligibility.js +112 -79
- package/dist/commands/improve/execution.js +57 -0
- package/dist/commands/improve/extract-cli.js +5 -5
- package/dist/commands/improve/extract-prompt.js +64 -22
- package/dist/commands/improve/extract.js +608 -360
- package/dist/commands/improve/improve-strategies.js +43 -14
- package/dist/commands/improve/improve.js +249 -29
- package/dist/commands/improve/loop-stages.js +11 -17
- package/dist/commands/improve/memory/memory-contradiction-detect.js +90 -66
- package/dist/commands/improve/outcome-loop.js +22 -38
- package/dist/commands/improve/planner.js +134 -0
- package/dist/commands/improve/preparation.js +730 -409
- package/dist/commands/improve/reflect.js +386 -223
- package/dist/commands/improve/run-context.js +3 -4
- package/dist/commands/improve/salience.js +6 -58
- package/dist/commands/improve/session-asset.js +12 -12
- package/dist/commands/lint/index.js +101 -29
- package/dist/commands/migrate-cli.js +11 -69
- package/dist/commands/migration-tool.js +6 -9
- package/dist/commands/models-cli.js +27 -0
- package/dist/commands/proposal/drain.js +258 -186
- package/dist/commands/proposal/proposal-cli.js +32 -10
- package/dist/commands/proposal/proposal.js +2 -5
- package/dist/commands/proposal/propose.js +192 -172
- package/dist/commands/proposal/repository.js +54 -91
- package/dist/commands/proposal/validators/proposal-validators.js +9 -7
- package/dist/commands/read/curate.js +53 -22
- package/dist/commands/read/registry-search.js +25 -9
- package/dist/commands/read/remember-cli.js +14 -2
- package/dist/commands/read/search.js +10 -4
- package/dist/commands/read/show.js +139 -153
- package/dist/commands/registry-cli.js +16 -7
- package/dist/commands/remember.js +33 -18
- package/dist/commands/sources/add-cli.js +19 -178
- package/dist/commands/sources/bundle-cli.js +15 -3
- package/dist/commands/sources/dangerous-env-audit.js +135 -0
- package/dist/commands/sources/info.js +2 -1
- package/dist/commands/sources/installed-stashes.js +901 -177
- package/dist/commands/sources/schema-repair.js +174 -95
- package/dist/commands/sources/self-update.js +30 -74
- package/dist/commands/sources/source-add.js +3 -5
- package/dist/commands/sources/sources-cli.js +2 -15
- package/dist/commands/sources/update-transaction.js +220 -0
- package/dist/commands/tasks/tasks-cli.js +3 -3
- package/dist/commands/tasks/tasks.js +736 -317
- package/dist/commands/workflow-cli.js +2 -2
- package/dist/core/adapter/adapters/agent-skills-adapter.js +3 -0
- package/dist/core/adapter/adapters/akm-adapter.js +85 -35
- package/dist/core/adapter/adapters/akm-lint.js +54 -39
- package/dist/core/adapter/adapters/akm-metadata.js +45 -45
- package/dist/core/adapter/adapters/akm-task-adapter.js +32 -49
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +38 -23
- package/dist/core/adapter/adapters/dotenv-adapter.js +30 -1
- package/dist/core/adapter/adapters/generic-files-adapter.js +11 -0
- package/dist/core/adapter/adapters/index.js +0 -9
- package/dist/core/adapter/adapters/llm-wiki-adapter.js +4 -0
- package/dist/core/adapter/adapters/okf-adapter.js +4 -0
- package/dist/core/adapter/adapters/opencode-adapter.js +5 -8
- package/dist/core/adapter/adapters/tool-dir-shared.js +63 -6
- package/dist/core/adapter/adapters/website-snapshot-adapter.js +4 -0
- package/dist/core/adapter/execution-source.js +308 -0
- package/dist/core/adapter/recognize-match.js +36 -13
- package/dist/core/adapter/registry.js +0 -9
- package/dist/core/asset/stash-meta.js +94 -4
- package/dist/core/common.js +6 -11
- package/dist/core/config/config-io.js +3 -3
- package/dist/core/config/config-schema.js +18 -40
- package/dist/core/config/config-sources.js +11 -21
- package/dist/core/config/config-walker.js +31 -13
- package/dist/core/config/config.js +23 -26
- package/dist/core/config/schema/engines.js +8 -7
- package/dist/core/config/schema/improve-processes.js +29 -5
- package/dist/core/config/schema/index-config.js +0 -27
- package/dist/core/config/schema/primitives.js +1 -23
- package/dist/core/config/schema/sources-bundles.js +13 -16
- package/dist/core/errors.js +2 -0
- package/dist/core/events.js +68 -32
- package/dist/core/extra-params.js +1 -0
- package/dist/core/improve-result.js +315 -0
- package/dist/core/lesson-lint.js +0 -6
- package/dist/core/maintenance-barrier.js +4 -4
- package/dist/core/network-policy.js +152 -0
- package/dist/core/paths.js +1 -1
- package/dist/core/recognition-util.js +4 -4
- package/dist/core/registry-url.js +456 -0
- package/dist/core/state/migrations.js +161 -47
- package/dist/core/state-db.js +453 -80
- package/dist/core/system-error.js +32 -0
- package/dist/core/time.js +2 -12
- package/dist/core/write-source.js +0 -18
- package/dist/execution/directory-identity.js +52 -0
- package/dist/execution/executable-identity.js +107 -0
- package/dist/execution/guarded-source.js +398 -0
- package/dist/execution/json.js +95 -0
- package/dist/{commands/health/types-session-log.js → execution/limits.js} +2 -1
- package/dist/execution/record.js +55 -0
- package/dist/execution/resolved-request.js +730 -0
- package/dist/execution/source.js +320 -0
- package/dist/indexer/bundle-identity-guard.js +5 -4
- package/dist/indexer/db/graph-db.js +33 -0
- package/dist/indexer/graph/graph-boost.js +3 -4
- package/dist/indexer/graph/graph-extraction.js +562 -373
- package/dist/indexer/index-written-assets.js +78 -39
- package/dist/indexer/indexer.js +471 -432
- package/dist/indexer/installations.js +6 -0
- package/dist/indexer/lookup/adapter-concept-owner.js +283 -0
- package/dist/indexer/materialize-embeddings.js +155 -0
- package/dist/indexer/passes/memory-inference.js +227 -174
- package/dist/indexer/passes/metadata.js +263 -118
- package/dist/indexer/scan/doc-to-entry.js +7 -10
- package/dist/indexer/scan/drain-dir.js +51 -23
- package/dist/indexer/search/db-search.js +156 -50
- package/dist/indexer/search/fts-query.js +40 -40
- package/dist/indexer/search/ranking.js +36 -1
- package/dist/indexer/search/search-attribution.js +3 -1
- package/dist/indexer/search/search-fields.js +23 -14
- package/dist/indexer/search/search-hit-enrichers.js +1 -1
- package/dist/indexer/search/search-source.js +7 -16
- package/dist/indexer/search/semantic-status.js +10 -1
- package/dist/indexer/usage/show-usage.js +105 -0
- package/dist/indexer/usage/usage-events.js +7 -2
- package/dist/indexer/walk/matchers.js +40 -10
- package/dist/indexer/walk/path-resolver.js +5 -2
- package/dist/indexer/walk/walker.js +20 -2
- package/dist/integrations/agent/builder-shared.js +3 -6
- package/dist/integrations/agent/conversation-fallback.js +16 -0
- package/dist/integrations/agent/engine-resolution.js +87 -87
- package/dist/integrations/agent/execution-cascade.js +566 -0
- package/dist/integrations/agent/execution-definitions.js +211 -0
- package/dist/integrations/agent/execution-lowering.js +811 -0
- package/dist/integrations/agent/execution-preparation.js +67 -0
- package/dist/integrations/agent/index.js +0 -2
- package/dist/integrations/agent/inline-execution.js +74 -0
- package/dist/integrations/agent/model-map.js +515 -0
- package/dist/integrations/agent/persona-fallback.js +30 -0
- package/dist/integrations/agent/request-lowering.js +186 -0
- package/dist/integrations/agent/runner-dispatch.js +230 -37
- package/dist/integrations/agent/runner.js +12 -83
- package/dist/integrations/harnesses/aider/agent-builder.js +8 -0
- package/dist/integrations/harnesses/aider/index.js +0 -1
- package/dist/integrations/harnesses/amazonq/agent-builder.js +8 -0
- package/dist/integrations/harnesses/amazonq/index.js +0 -1
- package/dist/integrations/harnesses/claude/agent-builder.js +14 -1
- package/dist/integrations/harnesses/claude/index.js +1 -5
- package/dist/integrations/harnesses/claude/session-log.js +3 -33
- package/dist/integrations/harnesses/codex/agent-builder.js +8 -0
- package/dist/integrations/harnesses/codex/index.js +0 -1
- package/dist/integrations/harnesses/copilot/agent-builder.js +8 -0
- package/dist/integrations/harnesses/copilot/index.js +0 -1
- package/dist/integrations/harnesses/gemini/agent-builder.js +8 -0
- package/dist/integrations/harnesses/gemini/index.js +0 -1
- package/dist/integrations/harnesses/index.js +4 -44
- package/dist/integrations/harnesses/opencode/agent-builder.js +16 -9
- package/dist/integrations/harnesses/opencode/index.js +0 -2
- package/dist/integrations/harnesses/opencode/session-log.js +14 -204
- package/dist/integrations/harnesses/opencode-sdk/harness.js +12 -1
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +40 -42
- package/dist/integrations/harnesses/openhands/agent-builder.js +8 -0
- package/dist/integrations/harnesses/openhands/index.js +0 -1
- package/dist/integrations/harnesses/pi/agent-builder.js +8 -0
- package/dist/integrations/harnesses/pi/index.js +0 -1
- package/dist/integrations/harnesses/shared.js +0 -1
- package/dist/integrations/harnesses/types.js +1 -3
- package/dist/integrations/lockfile.js +82 -79
- package/dist/integrations/session-logs/index.js +6 -17
- package/dist/integrations/session-logs/provider-base.js +1 -29
- package/dist/llm/client.js +10 -5
- package/dist/llm/embedder.js +6 -7
- package/dist/llm/embedders/local.js +37 -88
- package/dist/llm/embedders/types.js +1 -1
- package/dist/llm/graph-extract.js +75 -50
- package/dist/llm/index-passes.js +43 -5
- package/dist/llm/memory-infer.js +8 -6
- package/dist/llm/metadata-enhance.js +5 -3
- package/dist/llm/structured-call.js +122 -25
- package/dist/output/format-exempt.js +1 -1
- package/dist/output/render-registry.js +0 -16
- package/dist/output/renderers.js +12 -7
- package/dist/output/shapes/curate.js +1 -0
- package/dist/output/shapes/helpers.js +10 -2
- package/dist/output/shapes/passthrough.js +2 -0
- package/dist/output/text/command-format.js +31 -33
- package/dist/output/text/health-format.js +1 -29
- package/dist/output/text/migrate.js +6 -56
- package/dist/output/text/proposal-format.js +16 -1
- package/dist/output/text/workflow-format.js +16 -0
- package/dist/registry/network.js +279 -0
- package/dist/registry/pinned-request-helper.js +247 -0
- package/dist/registry/pinned-transport.js +717 -0
- package/dist/registry/providers/skills-sh.js +18 -6
- package/dist/registry/providers/static-index.js +20 -7
- package/dist/registry/resolve.js +53 -28
- package/dist/scripts/akm-migrate-node.js +19334 -52269
- package/dist/scripts/akm-migrate.js +19270 -51612
- package/dist/setup/registry-stash-loader.js +64 -20
- package/dist/setup/semantic-assets.js +9 -34
- package/dist/setup/setup.js +12 -30
- package/dist/setup/source-identity.js +17 -0
- package/dist/setup/steps/sources.js +36 -15
- package/dist/setup/steps/tasks.js +39 -11
- package/dist/sources/providers/git-provider.js +3 -3
- package/dist/sources/providers/npm.js +2 -2
- package/dist/sources/providers/provider-utils.js +4 -3
- package/dist/sources/providers/website.js +11 -7
- package/dist/sources/snapshot-fetchers/host-guard.js +9 -136
- package/dist/sources/snapshot-fetchers/website-ingest.js +25 -109
- package/dist/sources/website-url.js +73 -0
- package/dist/storage/engines/sqlite-migrations.js +81 -26
- package/dist/storage/managed-db.js +27 -24
- package/dist/storage/repositories/events-repository.js +3 -0
- package/dist/storage/repositories/index-connection.js +42 -10
- package/dist/storage/repositories/index-entries-repository.js +203 -229
- package/dist/storage/repositories/index-entry-mapper.js +8 -12
- package/dist/storage/repositories/index-entry-schema.js +255 -0
- package/dist/storage/repositories/index-fts-repository.js +64 -71
- package/dist/storage/repositories/index-llm-cache-repository.js +8 -13
- package/dist/storage/repositories/index-meta-repository.js +0 -11
- package/dist/storage/repositories/index-schema.js +74 -350
- package/dist/storage/repositories/index-utility-repository.js +12 -17
- package/dist/storage/repositories/index-vec-repository.js +56 -7
- package/dist/storage/repositories/proposals-repository.js +4 -127
- package/dist/storage/repositories/registry-cache.js +2 -1
- package/dist/storage/repositories/task-history-repository.js +20 -40
- package/dist/storage/repositories/workflow-runs-repository.js +228 -129
- package/dist/storage/sqlite-read-snapshot.js +148 -0
- package/dist/tasks/backends/cron.js +170 -42
- package/dist/tasks/backends/index.js +1 -1
- package/dist/tasks/backends/launchd.js +787 -202
- package/dist/tasks/backends/schtasks.js +282 -83
- package/dist/tasks/embedded.js +7 -7
- package/dist/tasks/frozen-script.js +50 -0
- package/dist/tasks/resolve-akm-bin.js +5 -1
- package/dist/tasks/runner.js +239 -251
- package/dist/tasks/runtime-v3.js +281 -0
- package/dist/tasks/scheduler-binding.js +272 -0
- package/dist/tasks/scheduler-invocation.js +57 -43
- package/dist/tasks/scheduler-sync.js +654 -0
- package/dist/tasks/source-v3.js +752 -0
- package/dist/tasks/standalone-script-entry.js +5 -0
- package/dist/tasks/task-id.js +29 -0
- package/dist/workflows/authoring/authoring.js +15 -32
- package/dist/workflows/exec/dispatch-redaction.js +14 -8
- package/dist/workflows/exec/exec-unit.js +7 -28
- package/dist/workflows/exec/frozen-judge.js +57 -89
- package/dist/workflows/exec/lowering-notices.js +23 -0
- package/dist/workflows/exec/native-executor.js +301 -458
- package/dist/workflows/exec/param-secrets.js +4 -3
- package/dist/workflows/exec/run-workflow.js +26 -32
- package/dist/workflows/exec/step-work.js +105 -109
- package/dist/workflows/exec/unit-dispatch.js +103 -27
- package/dist/workflows/exec/unit-writer.js +3 -3
- package/dist/workflows/exec/worktree.js +2 -2
- package/dist/workflows/ir/compile.js +86 -72
- package/dist/workflows/ir/environment-v4.js +328 -0
- package/dist/workflows/ir/freeze-v4.js +122 -0
- package/dist/workflows/ir/plan-hash.js +13 -7
- package/dist/workflows/ir/schema-v4.js +525 -0
- package/dist/workflows/ir/schema.js +25 -284
- package/dist/workflows/ir/source-freeze-v4.js +506 -0
- package/dist/workflows/parser.js +27 -24
- package/dist/workflows/program/schema.js +1 -2
- package/dist/workflows/renderer.js +42 -29
- package/dist/workflows/resource-limits.js +4 -5
- package/dist/workflows/runtime/agent-identity.js +11 -13
- package/dist/workflows/runtime/plan-classifier.js +8 -8
- package/dist/workflows/runtime/runs.js +27 -43
- package/dist/workflows/runtime/workflow-asset-loader.js +45 -205
- package/dist/workflows/source-files.js +373 -0
- package/dist/workflows/source-ir/compile.js +196 -0
- package/dist/workflows/source-ir/github-yaml.js +577 -0
- package/dist/workflows/source-ir/ordering.js +38 -0
- package/dist/workflows/source-ir/program.js +50 -0
- package/dist/workflows/source-ir/result.js +26 -0
- package/dist/workflows/source-ir/schema.js +772 -0
- package/dist/workflows/source-ir/semantics.js +242 -0
- package/dist/workflows/source-ir/uses.js +14 -0
- package/docs/README.md +2 -0
- package/docs/migration/README.md +3 -1
- package/docs/migration/release-notes/0.9.2.md +55 -0
- package/docs/migration/release-notes/README.md +5 -0
- package/docs/migration/v0.8-to-v0.9.md +76 -1077
- package/docs/migration/v0.9.0-troubleshooting.md +104 -516
- package/docs/migration/v0.9.1-to-v0.9.2.md +150 -0
- package/docs/reference/README.md +1 -0
- package/docs/reference/cli.md +230 -98
- package/docs/reference/configuration.md +159 -36
- package/docs/reference/data-and-telemetry.md +19 -1
- package/docs/reference/supported-formats.md +23 -3
- package/docs/reference/tasks.md +182 -0
- package/docs/reference/workflow-schema.md +91 -40
- package/docs/reference/workflows.md +33 -6
- package/package.json +10 -6
- package/schemas/akm-config.json +372 -224
- package/schemas/akm-task.json +324 -80
- package/schemas/akm-workflow.json +6 -9
- package/dist/core/migration-operation.js +0 -75
- package/dist/integrations/agent/model-aliases.js +0 -74
- package/dist/tasks/parser.js +0 -380
- package/dist/tasks/schema.js +0 -123
- package/dist/tasks/validator.js +0 -80
- package/dist/workflows/ir/freeze.js +0 -320
- package/dist/workflows/runtime/document-cache.js +0 -13
|
@@ -0,0 +1,308 @@
|
|
|
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 { createHash } from "node:crypto";
|
|
5
|
+
import { isMap, isScalar, parseDocument, visit } from "yaml";
|
|
6
|
+
import { cloneExecutionJson, cloneExecutionJsonObject, } from "../../execution/json.js";
|
|
7
|
+
import { assertSnapshotKeys, snapshotStrictRecord } from "../../execution/record.js";
|
|
8
|
+
import { cloneToolSelection, createAdapterRenderedExecutionSource, } from "../../execution/source.js";
|
|
9
|
+
function nextLine(text, start) {
|
|
10
|
+
const lf = text.indexOf("\n", start);
|
|
11
|
+
if (lf < 0)
|
|
12
|
+
return { line: text.slice(start), next: text.length };
|
|
13
|
+
const end = lf > start && text[lf - 1] === "\r" ? lf - 1 : lf;
|
|
14
|
+
return { line: text.slice(start, end), next: lf + 1 };
|
|
15
|
+
}
|
|
16
|
+
/** Strict execution-only frontmatter parser; indexing's tolerant parser is deliberately not reused. */
|
|
17
|
+
export function parseExecutionMarkdown(raw) {
|
|
18
|
+
if (typeof raw !== "string")
|
|
19
|
+
throw new TypeError("execution source raw content must be a string");
|
|
20
|
+
const withoutBom = raw.startsWith("\uFEFF") ? raw.slice(1) : raw;
|
|
21
|
+
const opening = nextLine(withoutBom, 0);
|
|
22
|
+
if (opening.line !== "---")
|
|
23
|
+
return { content: withoutBom, data: Object.freeze({}) };
|
|
24
|
+
if (opening.next === withoutBom.length) {
|
|
25
|
+
throw new TypeError("execution source has unterminated frontmatter");
|
|
26
|
+
}
|
|
27
|
+
let cursor = opening.next;
|
|
28
|
+
let frontmatterEnd = -1;
|
|
29
|
+
let bodyStart = -1;
|
|
30
|
+
while (cursor <= withoutBom.length) {
|
|
31
|
+
const lineStart = cursor;
|
|
32
|
+
const current = nextLine(withoutBom, cursor);
|
|
33
|
+
if (current.line === "---") {
|
|
34
|
+
frontmatterEnd = lineStart;
|
|
35
|
+
bodyStart = current.next;
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
if (current.next === cursor || current.next === withoutBom.length)
|
|
39
|
+
break;
|
|
40
|
+
cursor = current.next;
|
|
41
|
+
}
|
|
42
|
+
if (frontmatterEnd < 0 || bodyStart < 0) {
|
|
43
|
+
throw new TypeError("execution source has unterminated frontmatter");
|
|
44
|
+
}
|
|
45
|
+
const yaml = withoutBom.slice(opening.next, frontmatterEnd);
|
|
46
|
+
const document = parseDocument(yaml, { uniqueKeys: true });
|
|
47
|
+
if (document.errors.length > 0) {
|
|
48
|
+
throw new TypeError(`execution source has invalid YAML frontmatter: ${document.errors[0]?.message ?? "parse error"}`);
|
|
49
|
+
}
|
|
50
|
+
if (document.warnings.length > 0) {
|
|
51
|
+
throw new TypeError(`execution source YAML frontmatter uses an unsupported tag or construct: ${document.warnings[0]?.message}`);
|
|
52
|
+
}
|
|
53
|
+
if (!isMap(document.contents)) {
|
|
54
|
+
throw new TypeError("execution source YAML frontmatter must be a mapping");
|
|
55
|
+
}
|
|
56
|
+
let unsupported;
|
|
57
|
+
visit(document, {
|
|
58
|
+
Alias: () => {
|
|
59
|
+
unsupported ??= "aliases";
|
|
60
|
+
},
|
|
61
|
+
Node: (_key, node) => {
|
|
62
|
+
if (node.tag)
|
|
63
|
+
unsupported ??= "custom or explicit tags";
|
|
64
|
+
if (node.anchor !== undefined)
|
|
65
|
+
unsupported ??= "anchors";
|
|
66
|
+
},
|
|
67
|
+
Pair: (_key, pair) => {
|
|
68
|
+
if (!isScalar(pair.key) || typeof pair.key.value !== "string")
|
|
69
|
+
unsupported ??= "non-string mapping keys";
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
if (unsupported)
|
|
73
|
+
throw new TypeError(`execution source YAML frontmatter does not support ${unsupported}`);
|
|
74
|
+
let root;
|
|
75
|
+
try {
|
|
76
|
+
root = document.toJS({ maxAliasCount: 0 });
|
|
77
|
+
}
|
|
78
|
+
catch (cause) {
|
|
79
|
+
throw new TypeError("execution source YAML frontmatter could not be converted safely", { cause });
|
|
80
|
+
}
|
|
81
|
+
const data = cloneExecutionJsonObject(root, "execution source YAML frontmatter");
|
|
82
|
+
return { content: withoutBom.slice(bodyStart), data: Object.freeze(data) };
|
|
83
|
+
}
|
|
84
|
+
function own(data, key) {
|
|
85
|
+
return Object.hasOwn(data, key);
|
|
86
|
+
}
|
|
87
|
+
function requireMetadataMapping(value, path) {
|
|
88
|
+
try {
|
|
89
|
+
return snapshotStrictRecord(value, path);
|
|
90
|
+
}
|
|
91
|
+
catch (cause) {
|
|
92
|
+
throw new TypeError(`${path} must be a mapping with enumerable data fields`, { cause });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function nullableString(value, path) {
|
|
96
|
+
if (value !== null && typeof value !== "string")
|
|
97
|
+
throw new TypeError(`${path} must be a string or null`);
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
function nullableObject(value, path) {
|
|
101
|
+
return value === null ? null : cloneExecutionJsonObject(value, path);
|
|
102
|
+
}
|
|
103
|
+
function nullableEnvironment(value, path) {
|
|
104
|
+
if (value === null)
|
|
105
|
+
return null;
|
|
106
|
+
const environment = cloneExecutionJsonObject(value, path);
|
|
107
|
+
if (Object.values(environment).some((entry) => typeof entry !== "string")) {
|
|
108
|
+
throw new TypeError(`${path} values must be strings`);
|
|
109
|
+
}
|
|
110
|
+
return environment;
|
|
111
|
+
}
|
|
112
|
+
function nullableTimeout(value, path) {
|
|
113
|
+
const timeout = cloneExecutionJson(value, path);
|
|
114
|
+
if (timeout !== null && typeof timeout !== "string" && typeof timeout !== "number") {
|
|
115
|
+
throw new TypeError(`${path} must be a string, number, or null`);
|
|
116
|
+
}
|
|
117
|
+
return timeout;
|
|
118
|
+
}
|
|
119
|
+
/** Adapter helper for the common frontmatter fields fixed by the 0.9.2 design. */
|
|
120
|
+
export function executionDefaultsFromFrontmatter(data, options) {
|
|
121
|
+
const frontmatter = snapshotStrictRecord(data, "frontmatter");
|
|
122
|
+
const optionRecord = snapshotStrictRecord(options, "execution frontmatter projection options");
|
|
123
|
+
assertSnapshotKeys(optionRecord, ["kind", "allowTopLevelEngine", "toolsKeys"], "execution frontmatter projection options");
|
|
124
|
+
const kind = optionRecord.kind;
|
|
125
|
+
if (kind !== "command" && kind !== "persona") {
|
|
126
|
+
throw new TypeError("execution frontmatter projection options.kind is invalid");
|
|
127
|
+
}
|
|
128
|
+
const allowTopLevelEngine = Object.hasOwn(optionRecord, "allowTopLevelEngine")
|
|
129
|
+
? optionRecord.allowTopLevelEngine
|
|
130
|
+
: false;
|
|
131
|
+
if (typeof allowTopLevelEngine !== "boolean") {
|
|
132
|
+
throw new TypeError("execution frontmatter projection options.allowTopLevelEngine must be a boolean");
|
|
133
|
+
}
|
|
134
|
+
const configuredToolsKeys = Object.hasOwn(optionRecord, "toolsKeys")
|
|
135
|
+
? cloneExecutionJson(optionRecord.toolsKeys, "execution frontmatter projection options.toolsKeys")
|
|
136
|
+
: ["tools"];
|
|
137
|
+
if (!Array.isArray(configuredToolsKeys) || configuredToolsKeys.some((key) => typeof key !== "string" || !key)) {
|
|
138
|
+
throw new TypeError("execution frontmatter projection options.toolsKeys must be an array of non-empty strings");
|
|
139
|
+
}
|
|
140
|
+
const namespace = own(frontmatter, "akm")
|
|
141
|
+
? requireMetadataMapping(frontmatter.akm, "frontmatter.akm")
|
|
142
|
+
: snapshotStrictRecord({}, "frontmatter.akm");
|
|
143
|
+
const out = Object.create(null);
|
|
144
|
+
if (kind === "command" && own(frontmatter, "agent")) {
|
|
145
|
+
out.agent = nullableString(frontmatter.agent, "frontmatter.agent");
|
|
146
|
+
}
|
|
147
|
+
const namespacedEngine = own(namespace, "engine")
|
|
148
|
+
? nullableString(namespace.engine, "frontmatter.akm.engine")
|
|
149
|
+
: undefined;
|
|
150
|
+
const topLevelEngine = allowTopLevelEngine && own(frontmatter, "engine")
|
|
151
|
+
? nullableString(frontmatter.engine, "frontmatter.engine")
|
|
152
|
+
: undefined;
|
|
153
|
+
if (topLevelEngine !== undefined)
|
|
154
|
+
out.engine = topLevelEngine;
|
|
155
|
+
else if (namespacedEngine !== undefined)
|
|
156
|
+
out.engine = namespacedEngine;
|
|
157
|
+
if (own(frontmatter, "model")) {
|
|
158
|
+
out.model = nullableString(frontmatter.model, "frontmatter.model");
|
|
159
|
+
}
|
|
160
|
+
let toolsSelected = false;
|
|
161
|
+
for (const key of configuredToolsKeys) {
|
|
162
|
+
if (!own(frontmatter, key))
|
|
163
|
+
continue;
|
|
164
|
+
const selection = cloneToolSelection(frontmatter[key], `frontmatter.${key}`);
|
|
165
|
+
if (!toolsSelected) {
|
|
166
|
+
out.tools = selection;
|
|
167
|
+
toolsSelected = true;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
let inferenceEntries;
|
|
171
|
+
if (own(namespace, "inference")) {
|
|
172
|
+
if (namespace.inference === null)
|
|
173
|
+
inferenceEntries = null;
|
|
174
|
+
else {
|
|
175
|
+
inferenceEntries = new Map();
|
|
176
|
+
for (const [key, value] of Object.entries(cloneExecutionJsonObject(namespace.inference, "frontmatter.akm.inference"))) {
|
|
177
|
+
inferenceEntries.set(key, value);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
for (const key of ["temperature", "effort"]) {
|
|
182
|
+
if (!own(frontmatter, key))
|
|
183
|
+
continue;
|
|
184
|
+
if (inferenceEntries === null) {
|
|
185
|
+
throw new TypeError(`frontmatter.${key} conflicts with explicit null frontmatter.akm.inference`);
|
|
186
|
+
}
|
|
187
|
+
inferenceEntries ??= new Map();
|
|
188
|
+
const value = frontmatter[key];
|
|
189
|
+
if (key === "temperature") {
|
|
190
|
+
if (value !== null && typeof value !== "number") {
|
|
191
|
+
throw new TypeError("frontmatter.temperature must be a number or null");
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
else if (value !== null && typeof value !== "string") {
|
|
195
|
+
throw new TypeError("frontmatter.effort must be a string or null");
|
|
196
|
+
}
|
|
197
|
+
inferenceEntries.set(key, cloneExecutionJson(value, `frontmatter.${key}`));
|
|
198
|
+
}
|
|
199
|
+
if (inferenceEntries === null)
|
|
200
|
+
out.inference = null;
|
|
201
|
+
else if (inferenceEntries !== undefined)
|
|
202
|
+
out.inference = Object.fromEntries(inferenceEntries);
|
|
203
|
+
const namespacedSchema = own(namespace, "schema")
|
|
204
|
+
? nullableObject(namespace.schema, "frontmatter.akm.schema")
|
|
205
|
+
: undefined;
|
|
206
|
+
const topLevelSchema = own(frontmatter, "schema")
|
|
207
|
+
? nullableObject(frontmatter.schema, "frontmatter.schema")
|
|
208
|
+
: undefined;
|
|
209
|
+
if (topLevelSchema !== undefined)
|
|
210
|
+
out.outputSchema = topLevelSchema;
|
|
211
|
+
else if (namespacedSchema !== undefined)
|
|
212
|
+
out.outputSchema = namespacedSchema;
|
|
213
|
+
const namespacedTimeouts = new Map();
|
|
214
|
+
for (const key of ["timeoutMs", "timeout"]) {
|
|
215
|
+
if (own(namespace, key)) {
|
|
216
|
+
namespacedTimeouts.set(key, nullableTimeout(namespace[key], `frontmatter.akm.${key}`));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const topLevelTimeouts = new Map();
|
|
220
|
+
if (allowTopLevelEngine) {
|
|
221
|
+
for (const key of ["timeoutMs", "timeout"]) {
|
|
222
|
+
if (own(frontmatter, key)) {
|
|
223
|
+
topLevelTimeouts.set(key, nullableTimeout(frontmatter[key], `frontmatter.${key}`));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const timeoutKey = namespacedTimeouts.has("timeoutMs")
|
|
228
|
+
? "timeoutMs"
|
|
229
|
+
: namespacedTimeouts.has("timeout")
|
|
230
|
+
? "timeout"
|
|
231
|
+
: undefined;
|
|
232
|
+
const topLevelTimeoutKey = allowTopLevelEngine
|
|
233
|
+
? topLevelTimeouts.has("timeoutMs")
|
|
234
|
+
? "timeoutMs"
|
|
235
|
+
: topLevelTimeouts.has("timeout")
|
|
236
|
+
? "timeout"
|
|
237
|
+
: undefined
|
|
238
|
+
: undefined;
|
|
239
|
+
const selectedTimeoutKey = topLevelTimeoutKey ?? timeoutKey;
|
|
240
|
+
if (selectedTimeoutKey) {
|
|
241
|
+
out.timeout = topLevelTimeoutKey
|
|
242
|
+
? topLevelTimeouts.get(selectedTimeoutKey)
|
|
243
|
+
: namespacedTimeouts.get(selectedTimeoutKey);
|
|
244
|
+
}
|
|
245
|
+
for (const key of ["workspace", "environment", "runtime"]) {
|
|
246
|
+
const namespaced = own(namespace, key)
|
|
247
|
+
? key === "workspace"
|
|
248
|
+
? nullableString(namespace[key], `frontmatter.akm.${key}`)
|
|
249
|
+
: key === "environment"
|
|
250
|
+
? nullableEnvironment(namespace[key], `frontmatter.akm.${key}`)
|
|
251
|
+
: nullableObject(namespace[key], `frontmatter.akm.${key}`)
|
|
252
|
+
: undefined;
|
|
253
|
+
const topLevel = allowTopLevelEngine && own(frontmatter, key)
|
|
254
|
+
? key === "workspace"
|
|
255
|
+
? nullableString(frontmatter[key], `frontmatter.${key}`)
|
|
256
|
+
: key === "environment"
|
|
257
|
+
? nullableEnvironment(frontmatter[key], `frontmatter.${key}`)
|
|
258
|
+
: nullableObject(frontmatter[key], `frontmatter.${key}`)
|
|
259
|
+
: undefined;
|
|
260
|
+
if (topLevel !== undefined)
|
|
261
|
+
out[key] = topLevel;
|
|
262
|
+
else if (namespaced !== undefined)
|
|
263
|
+
out[key] = namespaced;
|
|
264
|
+
}
|
|
265
|
+
return out;
|
|
266
|
+
}
|
|
267
|
+
function snapshotRendererInput(input) {
|
|
268
|
+
const inputSnapshot = snapshotStrictRecord(input, "adapter execution source");
|
|
269
|
+
assertSnapshotKeys(inputSnapshot, ["kind", "raw", "identity", "defaults", "extensions"], "adapter execution source");
|
|
270
|
+
const identity = snapshotStrictRecord(inputSnapshot.identity, "adapter execution source identity");
|
|
271
|
+
assertSnapshotKeys(identity, ["ref", "bundle", "adapter", "file"], "adapter execution source identity");
|
|
272
|
+
return { input: inputSnapshot, identity };
|
|
273
|
+
}
|
|
274
|
+
export function renderMarkdownExecutionSource(input) {
|
|
275
|
+
const snapshots = snapshotRendererInput(input);
|
|
276
|
+
const raw = snapshots.input.raw;
|
|
277
|
+
if (typeof raw !== "string")
|
|
278
|
+
throw new TypeError("adapter execution source.raw must be a string");
|
|
279
|
+
const kind = snapshots.input.kind;
|
|
280
|
+
if (kind !== "command" && kind !== "persona")
|
|
281
|
+
throw new TypeError("adapter execution source.kind is invalid");
|
|
282
|
+
const parsed = parseExecutionMarkdown(raw);
|
|
283
|
+
const hasDefaults = Object.hasOwn(snapshots.input, "defaults");
|
|
284
|
+
const defaultsProjection = snapshots.input.defaults;
|
|
285
|
+
const defaults = typeof defaultsProjection === "function" ? defaultsProjection(parsed.data) : defaultsProjection;
|
|
286
|
+
if (hasDefaults && defaults === undefined) {
|
|
287
|
+
throw new TypeError("adapter execution source defaults must be omitted or resolve to an object");
|
|
288
|
+
}
|
|
289
|
+
const extensionsProjection = snapshots.input.extensions;
|
|
290
|
+
const extensionsAreProjected = typeof extensionsProjection === "function";
|
|
291
|
+
const extensions = extensionsAreProjected ? extensionsProjection(parsed.data) : extensionsProjection;
|
|
292
|
+
if (Object.hasOwn(snapshots.input, "extensions") && !extensionsAreProjected && extensions === undefined) {
|
|
293
|
+
throw new TypeError("adapter execution source extensions must be omitted or be an adapter-owned extension object");
|
|
294
|
+
}
|
|
295
|
+
return createAdapterRenderedExecutionSource({
|
|
296
|
+
kind,
|
|
297
|
+
content: parsed.content,
|
|
298
|
+
identity: {
|
|
299
|
+
ref: snapshots.identity.ref,
|
|
300
|
+
bundle: snapshots.identity.bundle,
|
|
301
|
+
adapter: snapshots.identity.adapter,
|
|
302
|
+
file: snapshots.identity.file,
|
|
303
|
+
hash: createHash("sha256").update(raw, "utf8").digest("hex"),
|
|
304
|
+
},
|
|
305
|
+
...(hasDefaults ? { defaults } : {}),
|
|
306
|
+
...(extensions === undefined ? {} : { extensions }),
|
|
307
|
+
});
|
|
308
|
+
}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
-
import { directoryMatcher, extensionMatcher, parentDirHintMatcher, smartMdMatcher } from "../../indexer/walk/matchers.js";
|
|
4
|
+
import { directoryMatcher, extensionMatcher, parentDirHintMatcher, smartMdMatcher, smartMdPathCandidates, } from "../../indexer/walk/matchers.js";
|
|
5
5
|
/**
|
|
6
6
|
* The four builtin matchers, in registration order. The array index IS the
|
|
7
7
|
* registration index `runMatchers` uses for tie-breaking. (The `wiki` matcher
|
|
8
8
|
* was removed in chunk 4 — the wiki asset-type is retired; LLM Wiki content is
|
|
9
|
-
* served by the first-class `llm-wiki` adapter, not the akm adapter. The
|
|
10
|
-
* workflow-program matcher
|
|
11
|
-
* workflow
|
|
12
|
-
*
|
|
9
|
+
* served by the first-class `llm-wiki` adapter, not the akm adapter. The old
|
|
10
|
+
* YAML workflow-program matcher is also gone: peer Markdown and GitHub-shaped
|
|
11
|
+
* `.yml` workflow sources are both path-owned by residence under `workflows/`
|
|
12
|
+
* (with Markdown frontmatter as an additional signal), while source IR owns
|
|
13
|
+
* their distinct parse/compile semantics.)
|
|
13
14
|
*/
|
|
14
15
|
const AKM_MATCHERS = [
|
|
15
16
|
extensionMatcher,
|
|
@@ -17,6 +18,17 @@ const AKM_MATCHERS = [
|
|
|
17
18
|
parentDirHintMatcher,
|
|
18
19
|
smartMdMatcher,
|
|
19
20
|
];
|
|
21
|
+
function winningMatch(hits) {
|
|
22
|
+
if (hits.length === 0)
|
|
23
|
+
return null;
|
|
24
|
+
hits.sort((a, b) => {
|
|
25
|
+
const specDiff = b.result.specificity - a.result.specificity;
|
|
26
|
+
if (specDiff !== 0)
|
|
27
|
+
return specDiff;
|
|
28
|
+
return b.index - a.index;
|
|
29
|
+
});
|
|
30
|
+
return hits[0].result;
|
|
31
|
+
}
|
|
20
32
|
/**
|
|
21
33
|
* Synchronous reproduction of `file-context.ts#runMatchers`'s arbitration
|
|
22
34
|
* (`:242-265`), minus its `ensureBuiltinsRegistered()` dynamic import. Runs
|
|
@@ -32,13 +44,24 @@ export function recognizeMatch(file) {
|
|
|
32
44
|
if (result !== null)
|
|
33
45
|
hits.push({ result, index: i });
|
|
34
46
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
47
|
+
return winningMatch(hits);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Every AKM matcher winner possible from path fields alone. The three
|
|
51
|
+
* path-only matchers run exactly as production does; each possible result of
|
|
52
|
+
* the shared smart-Markdown fact table is then arbitrated at its real index.
|
|
53
|
+
*/
|
|
54
|
+
export function recognizePathCandidateMatches(file) {
|
|
55
|
+
const fileContext = file;
|
|
56
|
+
const pathHits = [extensionMatcher, directoryMatcher, parentDirHintMatcher].flatMap((matcher, index) => {
|
|
57
|
+
const result = matcher(fileContext);
|
|
58
|
+
return result ? [{ result, index }] : [];
|
|
42
59
|
});
|
|
43
|
-
|
|
60
|
+
const smartCandidates = smartMdPathCandidates(file);
|
|
61
|
+
const variants = smartCandidates.length > 0 ? smartCandidates : [undefined];
|
|
62
|
+
const winners = variants.flatMap((smart) => {
|
|
63
|
+
const winner = winningMatch(smart ? [...pathHits, { result: smart, index: 3 }] : [...pathHits]);
|
|
64
|
+
return winner ? [winner] : [];
|
|
65
|
+
});
|
|
66
|
+
return [...new Map(winners.map((winner) => [`${winner.type}\0${winner.renderer}`, winner])).values()];
|
|
44
67
|
}
|
|
@@ -45,12 +45,3 @@ export function getAdapters() {
|
|
|
45
45
|
export function adapterForId(id) {
|
|
46
46
|
return BY_ID.get(id);
|
|
47
47
|
}
|
|
48
|
-
/**
|
|
49
|
-
* DEPRECATED no-op retained for test back-compat only. The registry is a static
|
|
50
|
-
* frozen map (normative §12.6) populated at module load, so there is nothing to
|
|
51
|
-
* reset — `getAdapters()` / `adapterForId()` are always the full built-in set.
|
|
52
|
-
* New code MUST NOT depend on this.
|
|
53
|
-
*/
|
|
54
|
-
export function resetAdapterRegistryForTests() {
|
|
55
|
-
// intentionally empty — the registry is static; see the doc comment above.
|
|
56
|
-
}
|
|
@@ -95,16 +95,106 @@ export function resolveMetaFilePath(sourceRoot, name) {
|
|
|
95
95
|
if (resolved !== metaRoot && !resolved.startsWith(metaRoot + path.sep)) {
|
|
96
96
|
throw new UsageError("Meta ref resolves outside the stash .meta directory.", "PATH_ESCAPE_VIOLATION");
|
|
97
97
|
}
|
|
98
|
-
if (
|
|
98
|
+
if (isSafeRegularMetaFile(sourceRoot, metaRoot, resolved))
|
|
99
99
|
return resolved;
|
|
100
100
|
}
|
|
101
101
|
return null;
|
|
102
102
|
}
|
|
103
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Resolve and read a meta doc from one stable, no-follow file descriptor.
|
|
105
|
+
* The descriptor/path identity check closes the swap window between path
|
|
106
|
+
* validation and reading: even if a writable local bundle changes the path
|
|
107
|
+
* concurrently, bytes are returned only when the opened inode is still the
|
|
108
|
+
* validated file inside the bundle's real `.meta/` directory.
|
|
109
|
+
*/
|
|
110
|
+
export function readMetaFile(sourceRoot, name) {
|
|
111
|
+
const filePath = resolveMetaFilePath(sourceRoot, name);
|
|
112
|
+
if (!filePath)
|
|
113
|
+
return null;
|
|
114
|
+
let fd;
|
|
104
115
|
try {
|
|
105
|
-
|
|
116
|
+
fd = fs.openSync(filePath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
|
|
117
|
+
const opened = fs.fstatSync(fd);
|
|
118
|
+
if (!opened.isFile())
|
|
119
|
+
throw metaPathEscape();
|
|
120
|
+
const metaRoot = path.resolve(sourceRoot, META_DIR);
|
|
121
|
+
if (!isSafeRegularMetaFile(sourceRoot, metaRoot, filePath))
|
|
122
|
+
throw metaPathEscape();
|
|
123
|
+
const current = fs.statSync(filePath);
|
|
124
|
+
if (opened.dev !== current.dev || opened.ino !== current.ino)
|
|
125
|
+
throw metaPathEscape();
|
|
126
|
+
return { path: filePath, content: fs.readFileSync(fd, "utf8") };
|
|
106
127
|
}
|
|
107
|
-
catch {
|
|
128
|
+
catch (error) {
|
|
129
|
+
if (error instanceof UsageError)
|
|
130
|
+
throw error;
|
|
131
|
+
const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
|
|
132
|
+
if (code === "ENOENT" || code === "ENOTDIR")
|
|
133
|
+
return null;
|
|
134
|
+
if (code === "ELOOP")
|
|
135
|
+
throw metaPathEscape();
|
|
136
|
+
throw new UsageError("Meta doc could not be read safely.", "PATH_ESCAPE_VIOLATION");
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
if (fd !== undefined)
|
|
140
|
+
fs.closeSync(fd);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function isSafeRegularMetaFile(sourceRoot, metaRoot, filePath) {
|
|
144
|
+
const sourceRootReal = realPathOrNull(sourceRoot);
|
|
145
|
+
if (!sourceRootReal)
|
|
146
|
+
return false;
|
|
147
|
+
const metaStat = lstatOrNull(metaRoot);
|
|
148
|
+
if (!metaStat)
|
|
149
|
+
return false;
|
|
150
|
+
if (metaStat.isSymbolicLink())
|
|
151
|
+
throw metaPathEscape();
|
|
152
|
+
if (!metaStat.isDirectory())
|
|
153
|
+
return false;
|
|
154
|
+
const relative = path.relative(metaRoot, filePath);
|
|
155
|
+
if (relative === "" || relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
156
|
+
throw metaPathEscape();
|
|
157
|
+
}
|
|
158
|
+
let current = metaRoot;
|
|
159
|
+
for (const segment of relative.split(path.sep)) {
|
|
160
|
+
current = path.join(current, segment);
|
|
161
|
+
const stat = lstatOrNull(current);
|
|
162
|
+
if (!stat)
|
|
163
|
+
return false;
|
|
164
|
+
if (stat.isSymbolicLink())
|
|
165
|
+
throw metaPathEscape();
|
|
166
|
+
}
|
|
167
|
+
const finalStat = lstatOrNull(filePath);
|
|
168
|
+
if (!finalStat?.isFile())
|
|
169
|
+
return false;
|
|
170
|
+
const metaRootReal = realPathOrNull(metaRoot);
|
|
171
|
+
const fileReal = realPathOrNull(filePath);
|
|
172
|
+
if (!metaRootReal || !fileReal)
|
|
108
173
|
return false;
|
|
174
|
+
if (!isWithin(sourceRootReal, metaRootReal) || !isWithin(metaRootReal, fileReal))
|
|
175
|
+
throw metaPathEscape();
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
function lstatOrNull(filePath) {
|
|
179
|
+
try {
|
|
180
|
+
return fs.lstatSync(filePath);
|
|
109
181
|
}
|
|
182
|
+
catch {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function realPathOrNull(filePath) {
|
|
187
|
+
try {
|
|
188
|
+
return fs.realpathSync(filePath);
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function isWithin(root, candidate) {
|
|
195
|
+
const relative = path.relative(root, candidate);
|
|
196
|
+
return relative === "" || (relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));
|
|
197
|
+
}
|
|
198
|
+
function metaPathEscape() {
|
|
199
|
+
return new UsageError("Meta doc must be a regular file inside the stash .meta directory; symlinks are refused.", "PATH_ESCAPE_VIOLATION");
|
|
110
200
|
}
|
package/dist/core/common.js
CHANGED
|
@@ -284,12 +284,8 @@ function isValidDirectory(dir) {
|
|
|
284
284
|
* Read the primary stash path directly from config.json without pulling in the
|
|
285
285
|
* full config module, to avoid circular dependencies.
|
|
286
286
|
*
|
|
287
|
-
* Reads
|
|
288
|
-
*
|
|
289
|
-
* path) is an unmigrated config: this refuses it with the same `akm-migrate
|
|
290
|
-
* apply` hint the schema hard-reject uses (config-schema.ts), rather than
|
|
291
|
-
* silently honouring the retired key — so every `resolveStashDir` caller gets
|
|
292
|
-
* the coherent migrate posture instead of split-brain success.
|
|
287
|
+
* Reads only the current `bundles`/`defaultBundle` shape. Unsupported older
|
|
288
|
+
* keys are never interpreted as a second config architecture.
|
|
293
289
|
*/
|
|
294
290
|
function readStashDirFromConfig() {
|
|
295
291
|
try {
|
|
@@ -340,17 +336,16 @@ function readStashDirFromConfig() {
|
|
|
340
336
|
}
|
|
341
337
|
return bundlePath;
|
|
342
338
|
}
|
|
343
|
-
// Retired pre-cutover
|
|
344
|
-
//
|
|
345
|
-
// of silently resolving the old key.
|
|
339
|
+
// Retired pre-cutover shapes are not runtime inputs. Refuse them instead
|
|
340
|
+
// of silently resolving old keys.
|
|
346
341
|
for (const key of ["stashDir", "sources", "installed"]) {
|
|
347
342
|
if (key in raw && raw[key] !== undefined) {
|
|
348
|
-
throw new ConfigError(`${key} is
|
|
343
|
+
throw new ConfigError(`${key} is not supported; configure the current bundles shape`, "INVALID_CONFIG_FILE");
|
|
349
344
|
}
|
|
350
345
|
}
|
|
351
346
|
}
|
|
352
347
|
catch (err) {
|
|
353
|
-
//
|
|
348
|
+
// An unsupported-shape refusal must reach the caller; genuine missing/invalid
|
|
354
349
|
// config (read or JSON-parse failure) falls through to the platform default.
|
|
355
350
|
if (err instanceof ConfigError)
|
|
356
351
|
throw err;
|
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
* Pure I/O helpers for AKM config files.
|
|
6
6
|
*
|
|
7
7
|
* No knowledge of the AkmConfig shape — these functions just read JSON(C) text
|
|
8
|
-
* from disk and write JSON text back atomically.
|
|
9
|
-
* in `./config.ts
|
|
8
|
+
* from disk and write JSON text back atomically. Current-schema validation lives
|
|
9
|
+
* in `./config.ts`; older config shapes are rejected.
|
|
10
10
|
*
|
|
11
11
|
* Split out so the load path is testable without touching the filesystem
|
|
12
12
|
* (`parseConfigText` is pure), and so a single atomic write path serves
|
|
13
|
-
* `saveConfig
|
|
13
|
+
* `saveConfig` and the setup wizard (#464.c).
|
|
14
14
|
*/
|
|
15
15
|
import fs from "node:fs";
|
|
16
16
|
import path from "node:path";
|