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,373 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
/**
|
|
5
|
+
* Authoritative workflow-source ownership.
|
|
6
|
+
*
|
|
7
|
+
* Markdown and the bounded GitHub-shaped YAML subset are peer authoring
|
|
8
|
+
* formats, but one canonical workflow ref must have exactly one source file.
|
|
9
|
+
* This module is the shared filesystem arbitration point used before indexing,
|
|
10
|
+
* cache reuse, lookup/show, and runtime load/start. It never chooses one
|
|
11
|
+
* extension by priority: two recognized siblings are a hard collision.
|
|
12
|
+
*/
|
|
13
|
+
import fs from "node:fs";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import { UsageError } from "../core/errors.js";
|
|
16
|
+
import { canonicalizeWorkflowName, WORKFLOW_EXTENSIONS } from "../core/recognition-util.js";
|
|
17
|
+
export class WorkflowSourceRejectionError extends UsageError {
|
|
18
|
+
sourcePaths;
|
|
19
|
+
constructor(message, code, sourcePaths) {
|
|
20
|
+
super(message, code);
|
|
21
|
+
this.sourcePaths = [...sourcePaths].sort(comparePaths);
|
|
22
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export class WorkflowSourceCollisionError extends WorkflowSourceRejectionError {
|
|
26
|
+
canonicalName;
|
|
27
|
+
constructor(canonicalName, sourcePaths) {
|
|
28
|
+
const sorted = [...sourcePaths].sort(comparePaths);
|
|
29
|
+
super(`Workflow "${canonicalName}" resolves to multiple workflow source files: ${sorted.join(", ")}. ` +
|
|
30
|
+
"A canonical workflow ref must be owned by exactly one recognized .md or .yml source; remove or rename the duplicate.", "RESOURCE_ALREADY_EXISTS", sorted);
|
|
31
|
+
this.name = "WorkflowSourceCollisionError";
|
|
32
|
+
this.canonicalName = canonicalName;
|
|
33
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export class WorkflowSourceDomainError extends WorkflowSourceRejectionError {
|
|
37
|
+
canonicalName;
|
|
38
|
+
constructor(canonicalName, sourcePaths, issues, collidingSourcePaths) {
|
|
39
|
+
const sortedPaths = [...sourcePaths].sort(comparePaths);
|
|
40
|
+
const sortedCollisions = [...collidingSourcePaths].sort(comparePaths);
|
|
41
|
+
const code = issues.some((issue) => issue.code === "PATH_ESCAPE_VIOLATION")
|
|
42
|
+
? "PATH_ESCAPE_VIOLATION"
|
|
43
|
+
: "INVALID_FLAG_VALUE";
|
|
44
|
+
const collisionDetail = sortedCollisions.length > 1 ? ` Valid owners also collide: ${sortedCollisions.join(", ")}.` : "";
|
|
45
|
+
super(`Workflow "${canonicalName}" has an invalid source ownership domain across candidates: ${sortedPaths.join(", ")}. ` +
|
|
46
|
+
`Problems: ${issues.map((issue) => issue.message).join(" ")}${collisionDetail} ` +
|
|
47
|
+
"Every candidate in the canonical domain is rejected until all invalid or duplicate sources are removed.", code, sortedPaths);
|
|
48
|
+
this.name = "WorkflowSourceDomainError";
|
|
49
|
+
this.canonicalName = canonicalName;
|
|
50
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export class WorkflowSourceIdentityError extends UsageError {
|
|
54
|
+
constructor(ref, indexedPath, authoritativePath) {
|
|
55
|
+
super(`Indexed workflow source identity for "${ref}" points to ${indexedPath}, but the authoritative source is ${authoritativePath}. ` +
|
|
56
|
+
"Refusing the stale index/cache identity; run `akm index --full` to reconcile it.", "INVALID_FLAG_VALUE");
|
|
57
|
+
this.name = "WorkflowSourceIdentityError";
|
|
58
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export class WorkflowSourceNameError extends WorkflowSourceRejectionError {
|
|
62
|
+
constructor(sourcePath, nestedSuffix) {
|
|
63
|
+
super(`Workflow source filename ${sourcePath} has an extensionless stem ending in recognized workflow suffix "${nestedSuffix}". ` +
|
|
64
|
+
"Nested workflow suffixes are invalid; remove the inner .md or .yml suffix instead of relying on repeated stripping.", "INVALID_FLAG_VALUE", [sourcePath]);
|
|
65
|
+
this.name = "WorkflowSourceNameError";
|
|
66
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export class WorkflowSourceLinkIdentityError extends WorkflowSourceRejectionError {
|
|
70
|
+
constructor(sourcePath, targetPath) {
|
|
71
|
+
super(`Workflow source ${sourcePath} resolves through a symlink to ${targetPath} with a different source format. ` +
|
|
72
|
+
"The authored workflow path and resolved source must use the same .md or .yml format.", "INVALID_FLAG_VALUE", [sourcePath]);
|
|
73
|
+
this.name = "WorkflowSourceLinkIdentityError";
|
|
74
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export class WorkflowSourceLinkResolutionError extends WorkflowSourceRejectionError {
|
|
78
|
+
constructor(sourcePath) {
|
|
79
|
+
super(`Workflow source symlink ${sourcePath} cannot be resolved to a regular file.`, "INVALID_FLAG_VALUE", [
|
|
80
|
+
sourcePath,
|
|
81
|
+
]);
|
|
82
|
+
this.name = "WorkflowSourceLinkResolutionError";
|
|
83
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
export class WorkflowSourcePathIdentityError extends WorkflowSourceRejectionError {
|
|
87
|
+
constructor(sourcePath, targetPath) {
|
|
88
|
+
super(`Workflow source ${sourcePath} resolves outside the bundle root to ${targetPath}.`, "PATH_ESCAPE_VIOLATION", [
|
|
89
|
+
sourcePath,
|
|
90
|
+
]);
|
|
91
|
+
this.name = "WorkflowSourcePathIdentityError";
|
|
92
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** Convert an adapter-owned concept id into its extensionless workflow name. */
|
|
96
|
+
export function workflowNameForConceptId(adapterId, conceptId) {
|
|
97
|
+
if (adapterId === "akm-workflow")
|
|
98
|
+
return conceptId;
|
|
99
|
+
if (adapterId !== "akm")
|
|
100
|
+
return undefined;
|
|
101
|
+
const prefix = "workflows/";
|
|
102
|
+
if (!conceptId.startsWith(prefix) || conceptId.length === prefix.length)
|
|
103
|
+
return undefined;
|
|
104
|
+
return conceptId.slice(prefix.length);
|
|
105
|
+
}
|
|
106
|
+
/** Derive the once-canonicalizable workflow name from an adapter-owned authored path. */
|
|
107
|
+
export function workflowNameForSourcePath(sourceRoot, adapterId, sourcePath) {
|
|
108
|
+
if (adapterId !== "akm" && adapterId !== "akm-workflow")
|
|
109
|
+
return undefined;
|
|
110
|
+
const relativePath = toPosix(path.relative(path.resolve(sourceRoot), path.resolve(sourcePath)));
|
|
111
|
+
if (!isSafeRelativeName(relativePath))
|
|
112
|
+
return undefined;
|
|
113
|
+
const ownedPath = adapterId === "akm" ? relativePath.replace(/^workflows\//, "") : relativePath;
|
|
114
|
+
if (adapterId === "akm" && ownedPath === relativePath)
|
|
115
|
+
return undefined;
|
|
116
|
+
const extension = path.posix.extname(ownedPath);
|
|
117
|
+
if (!WORKFLOW_EXTENSIONS.includes(extension.toLowerCase()))
|
|
118
|
+
return undefined;
|
|
119
|
+
return ownedPath;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Enumerate every owned source path that maps to `name` under one component.
|
|
123
|
+
* Ownership is decided before parsing so a malformed peer cannot be hidden by
|
|
124
|
+
* a valid source. Results retain authored extension case for diagnostics.
|
|
125
|
+
*/
|
|
126
|
+
export function listWorkflowSourceFiles(sourceRoot, adapterId, name) {
|
|
127
|
+
if (adapterId !== "akm" && adapterId !== "akm-workflow")
|
|
128
|
+
return [];
|
|
129
|
+
const canonicalName = canonicalizeWorkflowName(normalizeName(name));
|
|
130
|
+
if (!isSafeRelativeName(canonicalName)) {
|
|
131
|
+
throw new UsageError("Workflow ref resolves outside the bundle root.", "PATH_ESCAPE_VIOLATION");
|
|
132
|
+
}
|
|
133
|
+
let realRoot;
|
|
134
|
+
const authoredRoot = path.resolve(sourceRoot);
|
|
135
|
+
try {
|
|
136
|
+
realRoot = fs.realpathSync(authoredRoot);
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
const ownershipRoot = adapterId === "akm" ? path.join(authoredRoot, "workflows") : authoredRoot;
|
|
142
|
+
const parent = path.join(ownershipRoot, path.dirname(canonicalName));
|
|
143
|
+
if (!isWithinResolved(parent, authoredRoot)) {
|
|
144
|
+
throw new UsageError("Workflow ref resolves outside the bundle root.", "PATH_ESCAPE_VIOLATION");
|
|
145
|
+
}
|
|
146
|
+
let entries;
|
|
147
|
+
try {
|
|
148
|
+
entries = fs.readdirSync(parent, { withFileTypes: true });
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return [];
|
|
152
|
+
}
|
|
153
|
+
const basename = path.basename(canonicalName);
|
|
154
|
+
const candidates = [];
|
|
155
|
+
for (const entry of entries) {
|
|
156
|
+
if (!entry.isFile() && !entry.isSymbolicLink())
|
|
157
|
+
continue;
|
|
158
|
+
const extension = path.extname(entry.name);
|
|
159
|
+
const lowerExtension = extension.toLowerCase();
|
|
160
|
+
if (!WORKFLOW_EXTENSIONS.includes(lowerExtension))
|
|
161
|
+
continue;
|
|
162
|
+
if (entry.name.slice(0, -extension.length) !== basename)
|
|
163
|
+
continue;
|
|
164
|
+
const candidatePath = path.join(parent, entry.name);
|
|
165
|
+
candidates.push({
|
|
166
|
+
path: candidatePath,
|
|
167
|
+
relativePath: toPosix(path.relative(authoredRoot, candidatePath)),
|
|
168
|
+
lowerExtension,
|
|
169
|
+
extensionlessStem: entry.name.slice(0, -extension.length),
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
candidates.sort((left, right) => comparePaths(left.relativePath, right.relativePath));
|
|
173
|
+
const { sources, issues } = inspectWorkflowSourceDomain(candidates, canonicalName, realRoot);
|
|
174
|
+
if (issues.length > 0) {
|
|
175
|
+
throw workflowSourceDomainError(adapterId, canonicalName, candidates, sources, issues);
|
|
176
|
+
}
|
|
177
|
+
return sources;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Resolve a pre-enumerated set of authored workflow candidates in one batch.
|
|
181
|
+
*
|
|
182
|
+
* Callers that already walked a component (for example full lint/index scans)
|
|
183
|
+
* must use this surface instead of point-resolving every canonical ref and
|
|
184
|
+
* re-reading the same parent directory once per workflow. Candidate
|
|
185
|
+
* inspection, symlink containment/format rules, nested-suffix rejection, and
|
|
186
|
+
* collision construction remain shared with {@link listWorkflowSourceFiles}.
|
|
187
|
+
*/
|
|
188
|
+
export function resolveWorkflowSourceDomains(sourceRoot, adapterId, sourcePaths) {
|
|
189
|
+
if (adapterId !== "akm" && adapterId !== "akm-workflow")
|
|
190
|
+
return [];
|
|
191
|
+
const authoredRoot = path.resolve(sourceRoot);
|
|
192
|
+
let realRoot;
|
|
193
|
+
try {
|
|
194
|
+
realRoot = fs.realpathSync(authoredRoot);
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
return [];
|
|
198
|
+
}
|
|
199
|
+
const candidatesByName = new Map();
|
|
200
|
+
const seenAuthoredPaths = new Set();
|
|
201
|
+
for (const sourcePath of sourcePaths) {
|
|
202
|
+
// A full walk can legitimately feed the same authored file through more
|
|
203
|
+
// than one lexical spelling (or a caller may repeat it verbatim). Collapse
|
|
204
|
+
// only the normalized AUTHORED path here: distinct symlink/case-variant
|
|
205
|
+
// paths must remain separate ownership candidates even when they resolve to
|
|
206
|
+
// the same physical inode.
|
|
207
|
+
const normalizedSourcePath = path.resolve(sourcePath);
|
|
208
|
+
if (seenAuthoredPaths.has(normalizedSourcePath))
|
|
209
|
+
continue;
|
|
210
|
+
seenAuthoredPaths.add(normalizedSourcePath);
|
|
211
|
+
const authoredName = workflowNameForSourcePath(authoredRoot, adapterId, normalizedSourcePath);
|
|
212
|
+
if (authoredName === undefined)
|
|
213
|
+
continue;
|
|
214
|
+
const canonicalName = canonicalizeWorkflowName(authoredName);
|
|
215
|
+
if (!isSafeRelativeName(canonicalName))
|
|
216
|
+
continue;
|
|
217
|
+
const extension = path.extname(normalizedSourcePath);
|
|
218
|
+
const lowerExtension = extension.toLowerCase();
|
|
219
|
+
if (!WORKFLOW_EXTENSIONS.includes(lowerExtension))
|
|
220
|
+
continue;
|
|
221
|
+
const candidate = {
|
|
222
|
+
path: normalizedSourcePath,
|
|
223
|
+
relativePath: toPosix(path.relative(authoredRoot, normalizedSourcePath)),
|
|
224
|
+
lowerExtension,
|
|
225
|
+
extensionlessStem: path.basename(normalizedSourcePath).slice(0, -extension.length),
|
|
226
|
+
};
|
|
227
|
+
const domain = candidatesByName.get(canonicalName) ?? [];
|
|
228
|
+
domain.push(candidate);
|
|
229
|
+
candidatesByName.set(canonicalName, domain);
|
|
230
|
+
}
|
|
231
|
+
const resolutions = [];
|
|
232
|
+
for (const canonicalName of [...candidatesByName.keys()].sort(comparePaths)) {
|
|
233
|
+
const candidates = candidatesByName.get(canonicalName) ?? [];
|
|
234
|
+
candidates.sort((left, right) => comparePaths(left.relativePath, right.relativePath));
|
|
235
|
+
const { sources, issues } = inspectWorkflowSourceDomain(candidates, canonicalName, realRoot);
|
|
236
|
+
const sourcePaths = candidates.map((candidate) => candidate.relativePath);
|
|
237
|
+
if (issues.length > 0) {
|
|
238
|
+
resolutions.push({
|
|
239
|
+
canonicalName,
|
|
240
|
+
sourcePaths,
|
|
241
|
+
rejection: workflowSourceDomainError(adapterId, canonicalName, candidates, sources, issues),
|
|
242
|
+
});
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
if (sources.length > 1) {
|
|
246
|
+
resolutions.push({
|
|
247
|
+
canonicalName,
|
|
248
|
+
sourcePaths,
|
|
249
|
+
rejection: new WorkflowSourceCollisionError(adapterId === "akm" ? `workflows/${canonicalName}` : canonicalName, sources.map((source) => source.relativePath)),
|
|
250
|
+
});
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
resolutions.push({ canonicalName, sourcePaths, source: sources[0] });
|
|
254
|
+
}
|
|
255
|
+
return resolutions;
|
|
256
|
+
}
|
|
257
|
+
function inspectWorkflowSourceDomain(candidates, canonicalName, realRoot) {
|
|
258
|
+
const sources = [];
|
|
259
|
+
const issues = [];
|
|
260
|
+
for (const candidate of candidates) {
|
|
261
|
+
const inspection = inspectWorkflowSourceCandidate(candidate, canonicalName, realRoot);
|
|
262
|
+
if (inspection.source)
|
|
263
|
+
sources.push(inspection.source);
|
|
264
|
+
issues.push(...inspection.issues);
|
|
265
|
+
}
|
|
266
|
+
return { sources, issues };
|
|
267
|
+
}
|
|
268
|
+
function workflowSourceDomainError(adapterId, canonicalName, candidates, sources, issues) {
|
|
269
|
+
return new WorkflowSourceDomainError(adapterId === "akm" ? `workflows/${canonicalName}` : canonicalName, candidates.map((candidate) => candidate.relativePath), issues, sources.map((source) => source.relativePath));
|
|
270
|
+
}
|
|
271
|
+
function inspectWorkflowSourceCandidate(candidate, canonicalName, realRoot) {
|
|
272
|
+
const issues = [];
|
|
273
|
+
const nestedSuffix = WORKFLOW_EXTENSIONS.find((suffix) => candidate.extensionlessStem.toLowerCase().endsWith(suffix));
|
|
274
|
+
if (nestedSuffix)
|
|
275
|
+
issues.push(new WorkflowSourceNameError(candidate.relativePath, nestedSuffix));
|
|
276
|
+
let authoredStat;
|
|
277
|
+
try {
|
|
278
|
+
authoredStat = fs.lstatSync(candidate.path);
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
issues.push(new WorkflowSourceLinkResolutionError(candidate.relativePath));
|
|
282
|
+
return { issues };
|
|
283
|
+
}
|
|
284
|
+
const isLink = authoredStat.isSymbolicLink();
|
|
285
|
+
if (!isLink && !authoredStat.isFile()) {
|
|
286
|
+
issues.push(new WorkflowSourceLinkResolutionError(candidate.relativePath));
|
|
287
|
+
return { issues };
|
|
288
|
+
}
|
|
289
|
+
let realPath;
|
|
290
|
+
try {
|
|
291
|
+
realPath = fs.realpathSync(candidate.path);
|
|
292
|
+
}
|
|
293
|
+
catch {
|
|
294
|
+
issues.push(new WorkflowSourceLinkResolutionError(candidate.relativePath));
|
|
295
|
+
return { issues };
|
|
296
|
+
}
|
|
297
|
+
const targetPath = toPosix(path.relative(realRoot, realPath));
|
|
298
|
+
const contained = isWithinResolved(realPath, realRoot);
|
|
299
|
+
if (!contained)
|
|
300
|
+
issues.push(new WorkflowSourcePathIdentityError(candidate.relativePath, targetPath));
|
|
301
|
+
if (isLink && path.extname(realPath).toLowerCase() !== candidate.lowerExtension) {
|
|
302
|
+
issues.push(new WorkflowSourceLinkIdentityError(candidate.relativePath, targetPath));
|
|
303
|
+
}
|
|
304
|
+
if (contained) {
|
|
305
|
+
try {
|
|
306
|
+
if (!fs.statSync(realPath).isFile())
|
|
307
|
+
issues.push(new WorkflowSourceLinkResolutionError(candidate.relativePath));
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
issues.push(new WorkflowSourceLinkResolutionError(candidate.relativePath));
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
if (issues.length > 0)
|
|
314
|
+
return { issues };
|
|
315
|
+
return {
|
|
316
|
+
issues,
|
|
317
|
+
source: {
|
|
318
|
+
path: candidate.path,
|
|
319
|
+
realPath,
|
|
320
|
+
relativePath: candidate.relativePath,
|
|
321
|
+
canonicalName,
|
|
322
|
+
format: candidate.lowerExtension === ".md" ? "markdown" : "github-yaml",
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
/** Return the sole owner, throw on a collision, or return undefined when absent. */
|
|
327
|
+
export function resolveUniqueWorkflowSource(sourceRoot, adapterId, name) {
|
|
328
|
+
const sources = listWorkflowSourceFiles(sourceRoot, adapterId, name);
|
|
329
|
+
if (sources.length > 1) {
|
|
330
|
+
const canonicalName = sources[0]?.canonicalName ?? canonicalizeWorkflowName(normalizeName(name));
|
|
331
|
+
throw new WorkflowSourceCollisionError(adapterId === "akm" ? `workflows/${canonicalName}` : canonicalName, sources.map((source) => source.relativePath));
|
|
332
|
+
}
|
|
333
|
+
return sources[0];
|
|
334
|
+
}
|
|
335
|
+
/** Compare an indexed path with the single authoritative on-disk source. */
|
|
336
|
+
export function assertIndexedWorkflowSourceIdentity(ref, indexedPath, authoritative) {
|
|
337
|
+
let indexedRealPath;
|
|
338
|
+
try {
|
|
339
|
+
indexedRealPath = fs.realpathSync(indexedPath);
|
|
340
|
+
}
|
|
341
|
+
catch {
|
|
342
|
+
throw new WorkflowSourceIdentityError(ref, indexedPath, authoritative.path);
|
|
343
|
+
}
|
|
344
|
+
if (path.resolve(indexedPath) !== path.resolve(authoritative.path) ||
|
|
345
|
+
path.resolve(indexedRealPath) !== path.resolve(authoritative.realPath)) {
|
|
346
|
+
throw new WorkflowSourceIdentityError(ref, indexedPath, authoritative.path);
|
|
347
|
+
}
|
|
348
|
+
const indexedExtension = path.extname(indexedPath).toLowerCase();
|
|
349
|
+
const authoritativeExtension = authoritative.format === "markdown" ? ".md" : ".yml";
|
|
350
|
+
if (indexedExtension !== authoritativeExtension) {
|
|
351
|
+
throw new WorkflowSourceIdentityError(ref, indexedPath, authoritative.path);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
function normalizeName(name) {
|
|
355
|
+
return name.replaceAll("\\", "/");
|
|
356
|
+
}
|
|
357
|
+
function isSafeRelativeName(name) {
|
|
358
|
+
return (name.length > 0 &&
|
|
359
|
+
!path.posix.isAbsolute(name) &&
|
|
360
|
+
name !== ".." &&
|
|
361
|
+
!name.startsWith("../") &&
|
|
362
|
+
path.posix.normalize(name) === name);
|
|
363
|
+
}
|
|
364
|
+
function isWithinResolved(candidate, root) {
|
|
365
|
+
const relative = path.relative(root, path.resolve(candidate));
|
|
366
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
367
|
+
}
|
|
368
|
+
function toPosix(value) {
|
|
369
|
+
return value.replaceAll("\\", "/");
|
|
370
|
+
}
|
|
371
|
+
function comparePaths(left, right) {
|
|
372
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
373
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
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 path from "node:path";
|
|
5
|
+
import { parseFrontmatterBlock } from "../../core/asset/frontmatter.js";
|
|
6
|
+
import { classifyTaskV3Triggers, classifyTaskV3Uses } from "../../tasks/source-v3.js";
|
|
7
|
+
import { parseWorkflow } from "../parser.js";
|
|
8
|
+
import { projectExecCore } from "../program/schema.js";
|
|
9
|
+
import { parseGithubWorkflowSource } from "./github-yaml.js";
|
|
10
|
+
import { sourceFailureResult, WorkflowSourceFailure } from "./result.js";
|
|
11
|
+
import { decodeWorkflowSourceIrV1, } from "./schema.js";
|
|
12
|
+
import { canonicalizeWorkflowWorkingDirectory, WorkflowSourceSemanticError } from "./semantics.js";
|
|
13
|
+
export { looksLikeGithubWorkflowSource } from "./github-yaml.js";
|
|
14
|
+
export function compileGithubWorkflowSource(source, options) {
|
|
15
|
+
try {
|
|
16
|
+
return {
|
|
17
|
+
ok: true,
|
|
18
|
+
ir: decodeWorkflowSourceIrV1(parseGithubWorkflowSource(source, {
|
|
19
|
+
...options,
|
|
20
|
+
classifyUses: options.classifyUses ?? classifyTaskV3Uses,
|
|
21
|
+
classifyTriggers: options.classifyTriggers ?? classifyTaskV3Triggers,
|
|
22
|
+
}), {
|
|
23
|
+
workspaceRoot: options.workspaceRoot,
|
|
24
|
+
}),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
catch (cause) {
|
|
28
|
+
return sourceFailureResult(cause, options.path);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function compileMarkdownWorkflowSource(source, options) {
|
|
32
|
+
const parsed = parseWorkflow(source, {
|
|
33
|
+
path: options.path,
|
|
34
|
+
validateExecCwd: (value) => {
|
|
35
|
+
try {
|
|
36
|
+
return { ok: true, value: canonicalizeWorkflowWorkingDirectory(value, options.workspaceRoot) };
|
|
37
|
+
}
|
|
38
|
+
catch (cause) {
|
|
39
|
+
if (cause instanceof WorkflowSourceSemanticError) {
|
|
40
|
+
return { ok: false, code: cause.code, message: cause.message };
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
ok: false,
|
|
44
|
+
code: "working-directory-unverifiable",
|
|
45
|
+
message: cause instanceof Error ? cause.message : "working-directory cannot be physically verified.",
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
if (!parsed.ok) {
|
|
51
|
+
return {
|
|
52
|
+
ok: false,
|
|
53
|
+
errors: parsed.errors.map((error) => ({
|
|
54
|
+
code: error.code ?? "invalid-markdown-workflow",
|
|
55
|
+
message: error.message,
|
|
56
|
+
path: options.path,
|
|
57
|
+
line: error.line,
|
|
58
|
+
})),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const sourceSpan = wholeSourceSpan(source, options.path);
|
|
63
|
+
const steps = parsed.document.steps.map((step) => markdownStep(step, options.path));
|
|
64
|
+
const ir = {
|
|
65
|
+
sourceIrVersion: 1,
|
|
66
|
+
name: markdownName(source, options.path),
|
|
67
|
+
...(parsed.document.description ? { description: parsed.document.description } : {}),
|
|
68
|
+
...(parsed.document.tags ? { tags: [...parsed.document.tags] } : {}),
|
|
69
|
+
...(parsed.document.params ? { params: jsonClone(parsed.document.params) } : {}),
|
|
70
|
+
...(parsed.document.defaults
|
|
71
|
+
? { defaults: jsonClone(parsed.document.defaults) }
|
|
72
|
+
: {}),
|
|
73
|
+
...(parsed.document.budget ? { budget: jsonClone(parsed.document.budget) } : {}),
|
|
74
|
+
...(parsed.document.preamble ? { preamble: parsed.document.preamble } : {}),
|
|
75
|
+
triggers: [{ kind: "workflow_dispatch", source: sourceSpan }],
|
|
76
|
+
jobs: [
|
|
77
|
+
{
|
|
78
|
+
id: "contract",
|
|
79
|
+
needs: [],
|
|
80
|
+
steps,
|
|
81
|
+
source: sourceSpan,
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
extensions: {
|
|
85
|
+
"akm.dev/workflow-markdown": jsonExtension({
|
|
86
|
+
workflowSchemaVersion: parsed.document.schemaVersion,
|
|
87
|
+
}),
|
|
88
|
+
},
|
|
89
|
+
source: sourceSpan,
|
|
90
|
+
};
|
|
91
|
+
return {
|
|
92
|
+
ok: true,
|
|
93
|
+
ir: decodeWorkflowSourceIrV1(ir, { workspaceRoot: options.workspaceRoot }),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
catch (cause) {
|
|
97
|
+
return sourceFailureResult(cause, options.path);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/** Compile by authoritative source extension without rewriting either format. */
|
|
101
|
+
export function compileWorkflowSource(source, options) {
|
|
102
|
+
const extension = path.extname(options.path).toLowerCase();
|
|
103
|
+
if (extension === ".md")
|
|
104
|
+
return compileMarkdownWorkflowSource(source, options);
|
|
105
|
+
if (extension === ".yml")
|
|
106
|
+
return compileGithubWorkflowSource(source, options);
|
|
107
|
+
return {
|
|
108
|
+
ok: false,
|
|
109
|
+
errors: [
|
|
110
|
+
{
|
|
111
|
+
code: "unsupported-workflow-extension",
|
|
112
|
+
message: `Workflow source ${options.path} must use .md or .yml.`,
|
|
113
|
+
path: options.path,
|
|
114
|
+
line: 1,
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function markdownStep(step, filePath) {
|
|
120
|
+
const source = step.source ?? { path: filePath, start: 1, end: 1 };
|
|
121
|
+
const dispatchUnit = step.map?.unit ?? step.unit;
|
|
122
|
+
const unit = markdownUnit(dispatchUnit);
|
|
123
|
+
const common = {
|
|
124
|
+
id: step.id,
|
|
125
|
+
...(unit ? { unit } : {}),
|
|
126
|
+
...(step.map
|
|
127
|
+
? {
|
|
128
|
+
map: {
|
|
129
|
+
over: step.map.over,
|
|
130
|
+
...(step.map.concurrency !== undefined ? { concurrency: step.map.concurrency } : {}),
|
|
131
|
+
...(step.map.reducer !== undefined ? { reducer: step.map.reducer } : {}),
|
|
132
|
+
},
|
|
133
|
+
}
|
|
134
|
+
: {}),
|
|
135
|
+
...(step.route ? { route: jsonClone(step.route) } : {}),
|
|
136
|
+
...(step.inputs ? { inputs: [...step.inputs] } : {}),
|
|
137
|
+
...(step.output ? { output: jsonClone(step.output) } : {}),
|
|
138
|
+
...(step.gate || step.gateRubric
|
|
139
|
+
? {
|
|
140
|
+
gate: {
|
|
141
|
+
...(step.gate?.maxLoops !== undefined ? { maxLoops: step.gate.maxLoops } : {}),
|
|
142
|
+
...(step.gateRubric?.text.trim() ? { rubric: step.gateRubric.text } : {}),
|
|
143
|
+
},
|
|
144
|
+
}
|
|
145
|
+
: {}),
|
|
146
|
+
source,
|
|
147
|
+
};
|
|
148
|
+
if (step.route) {
|
|
149
|
+
return {
|
|
150
|
+
...common,
|
|
151
|
+
...(step.instructions?.text.trim() ? { instructions: step.instructions.text } : {}),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
if (dispatchUnit?.exec) {
|
|
155
|
+
return {
|
|
156
|
+
...common,
|
|
157
|
+
exec: projectExecCore(dispatchUnit.exec),
|
|
158
|
+
...(step.instructions?.text.trim() ? { instructions: step.instructions.text } : {}),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
const content = step.instructions?.text ?? "";
|
|
162
|
+
if (content.trim() === "") {
|
|
163
|
+
throw new WorkflowSourceFailure("markdown-command-content-required", `Markdown step ${JSON.stringify(step.id)} has no executable command content.`, source);
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
...common,
|
|
167
|
+
uses: "akm/command",
|
|
168
|
+
commandMode: "literal",
|
|
169
|
+
with: { content },
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function markdownUnit(value) {
|
|
173
|
+
if (!value)
|
|
174
|
+
return undefined;
|
|
175
|
+
const { exec: _exec, source: _source, ...unit } = value;
|
|
176
|
+
return Object.keys(unit).length > 0
|
|
177
|
+
? jsonClone(unit)
|
|
178
|
+
: undefined;
|
|
179
|
+
}
|
|
180
|
+
function markdownName(source, filePath) {
|
|
181
|
+
const block = parseFrontmatterBlock(source);
|
|
182
|
+
const body = block?.content ?? source;
|
|
183
|
+
const title = body.match(/^#\s+(.+?)\s*$/m)?.[1]?.trim();
|
|
184
|
+
if (title)
|
|
185
|
+
return title;
|
|
186
|
+
return path.basename(filePath, path.extname(filePath));
|
|
187
|
+
}
|
|
188
|
+
function wholeSourceSpan(source, filePath) {
|
|
189
|
+
return { path: filePath, start: 1, end: Math.max(1, source.split(/\r?\n/).length) };
|
|
190
|
+
}
|
|
191
|
+
function jsonClone(value) {
|
|
192
|
+
return JSON.parse(JSON.stringify(value));
|
|
193
|
+
}
|
|
194
|
+
function jsonExtension(value) {
|
|
195
|
+
return JSON.parse(JSON.stringify(value));
|
|
196
|
+
}
|