akm-cli 0.9.0-beta.9 → 0.9.0-rc.1
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 +715 -0
- package/README.md +12 -4
- package/dist/akm +38 -0
- package/dist/akm-migrate-storage +38 -0
- package/dist/assets/help/help-improve.md +9 -6
- package/dist/assets/hints/cli-hints-full.md +6 -5
- package/dist/assets/profiles/default.json +9 -4
- package/dist/assets/profiles/frequent.json +1 -1
- package/dist/assets/profiles/memory-focus.json +1 -1
- package/dist/assets/profiles/proactive-maintenance.json +25 -0
- package/dist/assets/profiles/quick.json +1 -1
- package/dist/assets/profiles/recombine-only.json +21 -0
- package/dist/assets/profiles/reflect-distill.json +30 -0
- package/dist/assets/profiles/synthesize.json +15 -0
- package/dist/assets/profiles/thorough.json +1 -1
- package/dist/assets/prompts/consolidate-system.md +23 -0
- package/dist/assets/prompts/contradiction-judge.md +33 -0
- package/dist/assets/prompts/distill-knowledge-system.md +22 -0
- package/dist/assets/prompts/distill-lesson-system.md +36 -0
- package/dist/assets/prompts/extract-session.md +11 -3
- package/dist/assets/prompts/graph-extract-system.md +1 -0
- package/dist/assets/prompts/graph-extract-user-prompt.md +1 -1
- package/dist/assets/prompts/memory-infer-system.md +1 -0
- package/dist/assets/prompts/memory-infer-user.md +5 -0
- package/dist/assets/prompts/metadata-enhance-system.md +1 -0
- package/dist/assets/prompts/procedural-system.md +44 -0
- package/dist/assets/prompts/recombine-system.md +40 -0
- package/dist/assets/prompts/staleness-detect-system.md +6 -0
- package/dist/assets/prompts/validate-summary-judge.md +1 -0
- package/dist/assets/prompts/workflow-unit-preamble.md +26 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +39 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +40 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +43 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +38 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +43 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +40 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +43 -0
- package/dist/assets/templates/html/health.html +281 -111
- package/dist/assets/wiki/ingest-workflow-template.md +45 -16
- package/dist/assets/wiki/schema-template.md +4 -4
- package/dist/cli/clack.js +56 -0
- package/dist/cli/config-migrate.js +7 -1
- package/dist/cli/confirm.js +1 -1
- package/dist/cli/parse-args.js +46 -1
- package/dist/cli/shared.js +28 -0
- package/dist/cli.js +25 -21
- package/dist/commands/agent/agent-dispatch.js +3 -2
- package/dist/commands/agent/agent-support.js +0 -7
- package/dist/commands/agent/contribute-cli.js +26 -7
- package/dist/commands/config-cli.js +26 -13
- package/dist/commands/env/child-env.js +47 -0
- package/dist/commands/env/env-binding.js +95 -0
- package/dist/commands/env/env-cli.js +228 -292
- package/dist/commands/env/env.js +14 -67
- package/dist/commands/env/secret-cli.js +140 -138
- package/dist/commands/feedback-cli.js +156 -155
- package/dist/commands/graph/graph-cli.js +5 -13
- package/dist/commands/graph/graph.js +3 -3
- package/dist/commands/health/advisories.js +151 -0
- package/dist/commands/health/checks.js +103 -16
- package/dist/commands/health/html-report.js +447 -81
- package/dist/commands/health/improve-metrics.js +771 -0
- package/dist/commands/health/llm-usage.js +65 -0
- package/dist/commands/health/md-report.js +103 -0
- package/dist/commands/health/metrics.js +278 -0
- package/dist/commands/health/stash-exposure.js +46 -0
- package/dist/commands/health/surfaces.js +216 -0
- package/dist/commands/health/task-runs.js +135 -0
- package/dist/commands/health/types.js +26 -0
- package/dist/commands/health/windows.js +195 -0
- package/dist/commands/health.js +91 -1091
- package/dist/commands/improve/anti-collapse.js +170 -0
- package/dist/commands/improve/calibration.js +161 -0
- package/dist/commands/improve/collapse-detector.js +421 -0
- package/dist/commands/improve/consolidate/chunking.js +141 -0
- package/dist/commands/improve/consolidate/eligibility.js +64 -0
- package/dist/commands/improve/consolidate/merge.js +145 -0
- package/dist/commands/improve/consolidate/sanitize.js +231 -0
- package/dist/commands/{lint.js → improve/consolidate/types.js} +1 -1
- package/dist/commands/improve/consolidate.js +1295 -1277
- package/dist/commands/improve/dedup.js +482 -0
- package/dist/commands/improve/distill/content-repair.js +202 -0
- package/dist/commands/improve/distill/promote-memory.js +229 -0
- package/dist/commands/improve/distill/quality-gate.js +236 -0
- package/dist/commands/improve/distill-guards.js +127 -0
- package/dist/commands/improve/distill-promotion-policy.js +826 -167
- package/dist/commands/improve/distill.js +228 -605
- package/dist/commands/improve/eligibility.js +434 -0
- package/dist/commands/improve/encoding-salience.js +205 -0
- package/dist/commands/improve/extract-cli.js +179 -59
- package/dist/commands/improve/extract-prompt.js +54 -3
- package/dist/commands/improve/extract-watch.js +140 -0
- package/dist/commands/improve/extract.js +409 -43
- package/dist/commands/improve/feedback-valence.js +54 -0
- package/dist/commands/improve/hot-probation.js +45 -0
- package/dist/commands/improve/improve-auto-accept.js +157 -10
- package/dist/commands/improve/improve-cli.js +115 -73
- package/dist/commands/improve/improve-profiles.js +28 -8
- package/dist/commands/improve/improve-result-file.js +15 -25
- package/dist/commands/improve/improve-session.js +58 -0
- package/dist/commands/improve/improve.js +485 -2764
- package/dist/commands/improve/locks.js +154 -0
- package/dist/commands/improve/loop-stages.js +1100 -0
- package/dist/commands/improve/memory/memory-belief.js +14 -15
- package/dist/commands/improve/memory/memory-contradiction-detect.js +83 -60
- package/dist/commands/improve/memory/memory-improve.js +27 -27
- package/dist/commands/improve/outcome-loop.js +270 -0
- package/dist/commands/improve/preparation.js +2002 -0
- package/dist/commands/improve/proactive-maintenance.js +37 -35
- package/dist/commands/improve/procedural.js +398 -0
- package/dist/commands/improve/recombine.js +818 -0
- package/dist/commands/improve/reflect-noise.js +0 -0
- package/dist/commands/improve/reflect.js +206 -45
- package/dist/commands/improve/salience.js +455 -0
- package/dist/commands/improve/schema-similarity-gate.js +168 -0
- package/dist/commands/improve/shared.js +51 -0
- package/dist/commands/improve/triage.js +93 -0
- package/dist/commands/lint/agent-linter.js +19 -24
- package/dist/commands/lint/base-linter.js +173 -60
- package/dist/commands/lint/command-linter.js +19 -24
- package/dist/commands/lint/env-key-rules.js +38 -1
- package/dist/commands/lint/fact-linter.js +39 -0
- package/dist/commands/lint/index.js +31 -13
- package/dist/commands/lint/memory-linter.js +1 -1
- package/dist/commands/lint/registry.js +7 -2
- package/dist/commands/lint/task-linter.js +3 -3
- package/dist/commands/lint/workflow-linter.js +26 -1
- package/dist/commands/observability-cli.js +4 -4
- package/dist/commands/proposal/drain-policies.js +13 -4
- package/dist/commands/proposal/drain.js +45 -51
- package/dist/commands/proposal/legacy-import.js +115 -0
- package/dist/commands/proposal/proposal-cli.js +24 -34
- package/dist/commands/proposal/proposal.js +2 -1
- package/dist/commands/proposal/propose.js +8 -3
- package/dist/commands/proposal/repository.js +829 -0
- package/dist/commands/proposal/validators/proposal-quality-validators.js +9 -8
- package/dist/commands/proposal/validators/proposals.js +93 -895
- package/dist/commands/read/curate.js +410 -111
- package/dist/commands/read/knowledge.js +10 -3
- package/dist/commands/read/remember-cli.js +133 -138
- package/dist/commands/read/search-cli.js +15 -8
- package/dist/commands/read/search.js +22 -11
- package/dist/commands/read/show.js +106 -14
- package/dist/commands/registry-cli.js +76 -87
- package/dist/commands/remember.js +11 -12
- package/dist/commands/sources/add-cli.js +91 -95
- package/dist/commands/sources/history.js +1 -1
- package/dist/commands/sources/init.js +66 -18
- package/dist/commands/sources/installed-stashes.js +11 -3
- package/dist/commands/sources/migration-help.js +7 -4
- package/dist/commands/sources/schema-repair.js +44 -46
- package/dist/commands/sources/self-update.js +2 -2
- package/dist/commands/sources/source-add.js +7 -3
- package/dist/commands/sources/sources-cli.js +3 -3
- package/dist/commands/sources/stash-cli.js +19 -39
- package/dist/commands/sources/stash-skeleton.js +57 -8
- package/dist/commands/tasks/default-tasks.js +15 -2
- package/dist/commands/tasks/tasks-cli.js +20 -29
- package/dist/commands/tasks/tasks.js +39 -11
- package/dist/commands/wiki-cli.js +23 -38
- package/dist/commands/workflow-cli.js +291 -13
- package/dist/core/asset/asset-registry.js +3 -1
- package/dist/core/asset/asset-spec.js +79 -5
- package/dist/core/asset/frontmatter.js +188 -167
- package/dist/core/asset/markdown.js +8 -0
- package/dist/core/authoring-rules.js +92 -0
- package/dist/core/common.js +4 -23
- package/dist/core/concurrent.js +10 -1
- package/dist/core/config/config-io.js +10 -1
- package/dist/core/config/config-migration.js +18 -40
- package/dist/core/config/config-schema.js +403 -62
- package/dist/core/config/config-types.js +3 -3
- package/dist/core/config/config.js +67 -22
- package/dist/core/deep-merge.js +38 -0
- package/dist/core/errors.js +1 -0
- package/dist/core/eval/rank-metrics.js +113 -0
- package/dist/core/events.js +4 -7
- package/dist/core/improve-types.js +47 -8
- package/dist/core/json-schema.js +142 -0
- package/dist/core/logs-db.js +14 -75
- package/dist/core/parse.js +36 -16
- package/dist/core/paths.js +18 -18
- package/dist/core/standards/resolve-standards-context.js +87 -0
- package/dist/core/standards/resolve-stash-standards.js +99 -0
- package/dist/core/standards/resolve-type-conventions.js +66 -0
- package/dist/core/state/migrations.js +770 -0
- package/dist/core/state-db.js +132 -1126
- package/dist/core/structured.js +69 -0
- package/dist/core/time.js +53 -0
- package/dist/core/warn.js +21 -0
- package/dist/core/write-source.js +37 -0
- package/dist/indexer/db/db.js +261 -770
- package/dist/indexer/db/entry-mapper.js +41 -0
- package/dist/indexer/db/graph-db.js +129 -86
- package/dist/indexer/db/llm-cache.js +2 -2
- package/dist/indexer/db/schema.js +516 -0
- package/dist/indexer/ensure-index.js +36 -92
- package/dist/indexer/feedback/utility-policy.js +75 -0
- package/dist/indexer/graph/graph-boost.js +51 -41
- package/dist/indexer/graph/graph-extraction.js +207 -4
- package/dist/indexer/index-writer-lock.js +18 -11
- package/dist/indexer/index-written-assets.js +105 -0
- package/dist/indexer/indexer.js +182 -204
- package/dist/indexer/passes/dir-staleness.js +114 -0
- package/dist/indexer/passes/memory-inference.js +13 -5
- package/dist/indexer/passes/metadata.js +20 -0
- package/dist/indexer/read-preflight.js +23 -0
- package/dist/indexer/search/db-search.js +89 -13
- package/dist/indexer/search/fts-query.js +51 -0
- package/dist/indexer/search/ranking-contributors.js +95 -9
- package/dist/indexer/search/ranking.js +79 -3
- package/dist/indexer/search/search-fields.js +6 -0
- package/dist/indexer/search/search-source.js +32 -21
- package/dist/indexer/search/semantic-status.js +4 -0
- package/dist/indexer/walk/matchers.js +48 -0
- package/dist/indexer/walk/walker.js +21 -13
- package/dist/integrations/agent/builders.js +41 -13
- package/dist/integrations/agent/config.js +20 -59
- package/dist/integrations/agent/detect.js +9 -0
- package/dist/integrations/agent/index.js +3 -19
- package/dist/integrations/agent/model-aliases.js +16 -2
- package/dist/integrations/agent/profiles.js +79 -6
- package/dist/integrations/agent/prompts.js +75 -9
- package/dist/integrations/agent/runner-dispatch.js +83 -0
- package/dist/integrations/agent/runner.js +13 -9
- package/dist/integrations/agent/spawn.js +206 -81
- package/dist/integrations/harnesses/aider/agent-builder.js +113 -0
- package/dist/integrations/harnesses/aider/index.js +58 -0
- package/dist/integrations/harnesses/aider/result-extractor.js +53 -0
- package/dist/integrations/harnesses/amazonq/agent-builder.js +153 -0
- package/dist/integrations/harnesses/amazonq/index.js +59 -0
- package/dist/integrations/harnesses/amazonq/result-extractor.js +48 -0
- package/dist/integrations/harnesses/claude/agent-builder.js +46 -7
- package/dist/integrations/harnesses/claude/index.js +27 -23
- package/dist/integrations/harnesses/claude/result-extractor.js +52 -0
- package/dist/integrations/harnesses/claude/session-log.js +10 -0
- package/dist/integrations/harnesses/codex/agent-builder.js +137 -0
- package/dist/integrations/harnesses/codex/index.js +63 -0
- package/dist/integrations/harnesses/codex/result-extractor.js +73 -0
- package/dist/integrations/harnesses/copilot/agent-builder.js +122 -0
- package/dist/integrations/harnesses/copilot/index.js +60 -0
- package/dist/integrations/harnesses/copilot/result-extractor.js +151 -0
- package/dist/integrations/harnesses/gemini/agent-builder.js +121 -0
- package/dist/integrations/harnesses/gemini/index.js +60 -0
- package/dist/integrations/harnesses/gemini/result-extractor.js +121 -0
- package/dist/integrations/harnesses/index.js +28 -7
- package/dist/integrations/harnesses/opencode/agent-builder.js +1 -1
- package/dist/integrations/harnesses/opencode/index.js +17 -16
- package/dist/integrations/harnesses/opencode/session-log.js +173 -3
- package/dist/integrations/harnesses/opencode-sdk/harness.js +65 -0
- package/dist/integrations/harnesses/opencode-sdk/index.js +10 -34
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +642 -71
- package/dist/integrations/harnesses/openhands/agent-builder.js +126 -0
- package/dist/integrations/harnesses/openhands/index.js +58 -0
- package/dist/integrations/harnesses/openhands/result-extractor.js +103 -0
- package/dist/integrations/harnesses/pi/agent-builder.js +104 -0
- package/dist/integrations/harnesses/pi/index.js +58 -0
- package/dist/integrations/harnesses/pi/result-extractor.js +135 -0
- package/dist/integrations/harnesses/types.js +8 -0
- package/dist/integrations/session-logs/index.js +40 -11
- package/dist/llm/call-ai.js +2 -2
- package/dist/llm/client.js +34 -11
- package/dist/llm/embedder.js +67 -4
- package/dist/llm/embedders/cache.js +3 -1
- package/dist/llm/embedders/deterministic.js +66 -0
- package/dist/llm/embedders/local.js +73 -3
- package/dist/llm/feature-gate.js +16 -15
- package/dist/llm/graph-extract.js +67 -44
- package/dist/llm/memory-infer-impl.js +138 -0
- package/dist/llm/memory-infer.js +1 -127
- package/dist/llm/metadata-enhance.js +44 -31
- package/dist/llm/structured-call.js +49 -0
- package/dist/migrate-storage-node.mjs +8 -0
- package/dist/output/context.js +5 -5
- package/dist/output/renderers.js +87 -15
- package/dist/output/shapes/curate.js +14 -2
- package/dist/output/shapes/helpers.js +0 -3
- package/dist/output/shapes/passthrough.js +6 -1
- package/dist/output/text/helpers.js +241 -2
- package/dist/output/text/workflow.js +4 -1
- package/dist/registry/providers/skills-sh.js +21 -147
- package/dist/registry/providers/static-index.js +15 -157
- package/dist/registry/resolve.js +27 -9
- package/dist/runtime.js +25 -1
- package/dist/schemas/akm-config.json +14225 -0
- package/dist/schemas/akm-workflow.json +328 -0
- package/dist/scripts/migrate-storage.js +2743 -8390
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +1652 -607
- package/dist/setup/detect.js +9 -0
- package/dist/setup/legacy-config.js +106 -0
- package/dist/setup/prompt.js +57 -0
- package/dist/setup/providers.js +14 -0
- package/dist/setup/registry-stash-loader.js +12 -0
- package/dist/setup/semantic-assets.js +124 -0
- package/dist/setup/setup.js +52 -1614
- package/dist/setup/steps/connection.js +734 -0
- package/dist/setup/steps/output.js +31 -0
- package/dist/setup/steps/platforms.js +124 -0
- package/dist/setup/steps/semantic.js +27 -0
- package/dist/setup/steps/sources.js +222 -0
- package/dist/setup/steps/stashdir.js +42 -0
- package/dist/setup/steps/tasks.js +152 -0
- package/dist/sources/include.js +6 -2
- package/dist/sources/providers/filesystem.js +0 -1
- package/dist/sources/providers/git-install.js +210 -0
- package/dist/sources/providers/git-provider.js +234 -0
- package/dist/sources/providers/git-stash.js +248 -0
- package/dist/sources/providers/git.js +10 -661
- package/dist/sources/providers/npm.js +2 -6
- package/dist/sources/providers/provider-utils.js +13 -7
- package/dist/sources/providers/sync-from-ref.js +9 -1
- package/dist/sources/providers/website.js +9 -5
- package/dist/sources/website-ingest.js +187 -29
- package/dist/sources/wiki-fetchers/registry.js +53 -0
- package/dist/sources/wiki-fetchers/youtube.js +239 -0
- package/dist/storage/database.js +45 -10
- package/dist/storage/managed-db.js +82 -0
- package/dist/storage/repositories/canaries-repository.js +107 -0
- package/dist/storage/repositories/consolidation-repository.js +38 -0
- package/dist/storage/repositories/embeddings-repository.js +72 -0
- package/dist/storage/repositories/events-repository.js +187 -0
- package/dist/storage/repositories/extract-sessions-repository.js +96 -0
- package/dist/storage/repositories/improve-runs-repository.js +146 -0
- package/dist/storage/repositories/index-db.js +14 -8
- package/dist/storage/repositories/proposals-repository.js +220 -0
- package/dist/storage/repositories/recombine-repository.js +213 -0
- package/dist/storage/repositories/registry-cache.js +93 -0
- package/dist/storage/repositories/registry-index-cache-repository.js +46 -0
- package/dist/storage/repositories/task-history-repository.js +93 -0
- package/dist/storage/repositories/workflow-runs-repository.js +189 -1
- package/dist/storage/sqlite-pragmas.js +146 -0
- package/dist/tasks/backends/cron.js +1 -1
- package/dist/tasks/backends/index.js +9 -0
- package/dist/tasks/backends/launchd.js +1 -1
- package/dist/tasks/backends/schtasks.js +1 -1
- package/dist/tasks/{resolveAkmBin.js → resolve-akm-bin.js} +2 -2
- package/dist/tasks/runner.js +15 -13
- package/dist/text-import-hook.mjs +1 -1
- package/dist/wiki/wiki.js +52 -11
- package/dist/workflows/authoring/authoring.js +123 -10
- package/dist/workflows/authoring/workflow-program-template.yaml +31 -0
- package/dist/workflows/cli.js +5 -0
- package/dist/workflows/db.js +138 -4
- package/dist/workflows/exec/brief.js +484 -0
- package/dist/workflows/exec/native-executor.js +975 -0
- package/dist/workflows/exec/param-secrets.js +115 -0
- package/dist/workflows/exec/report.js +1295 -0
- package/dist/workflows/exec/run-workflow.js +596 -0
- package/dist/workflows/exec/scheduler.js +100 -0
- package/dist/workflows/exec/step-work.js +1156 -0
- package/dist/workflows/exec/unit-writer.js +23 -0
- package/dist/workflows/exec/watch.js +116 -0
- package/dist/workflows/exec/worktree.js +171 -0
- package/dist/workflows/ir/compile.js +388 -0
- package/dist/workflows/ir/params.js +54 -0
- package/dist/workflows/ir/plan-hash.js +33 -0
- package/dist/workflows/ir/schema.js +4 -0
- package/dist/workflows/parser.js +3 -1
- package/dist/workflows/program/expressions.js +369 -0
- package/dist/workflows/program/parser.js +760 -0
- package/dist/workflows/program/project.js +105 -0
- package/dist/workflows/program/schema.js +54 -0
- package/dist/workflows/renderer.js +82 -5
- package/dist/workflows/runtime/agent-identity.js +59 -14
- package/dist/workflows/runtime/runs.js +248 -153
- package/dist/workflows/runtime/unit-checkin.js +45 -0
- package/dist/workflows/runtime/workflow-asset-loader.js +188 -0
- package/dist/workflows/validate-summary.js +26 -10
- package/dist/workflows/validator.js +1 -1
- package/docs/README.md +69 -18
- package/docs/data-and-telemetry.md +7 -5
- package/docs/migration/release-notes/0.7.0.md +1 -1
- package/docs/migration/release-notes/0.9.0-beta.60.md +19 -0
- package/docs/migration/release-notes/0.9.0.md +39 -0
- package/package.json +10 -10
- package/dist/assets/tasks/core/update-stashes.yml +0 -4
- package/dist/commands/db-cli.js +0 -23
- package/dist/indexer/db/db-backup.js +0 -376
- package/dist/indexer/passes/staleness-detect.js +0 -488
|
@@ -0,0 +1,146 @@
|
|
|
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
|
+
* Shared SQLite PRAGMA application + journal-mode resolution (#628).
|
|
6
|
+
*
|
|
7
|
+
* Every AKM SQLite opener applies the same opening PRAGMAs: a journal mode, a
|
|
8
|
+
* 30 s busy_timeout, and (for most) foreign_keys = ON. Historically each opener
|
|
9
|
+
* hard-coded `PRAGMA journal_mode = WAL`. WAL is impossible on network
|
|
10
|
+
* filesystems (NFS/SMB) because its `-shm` shared-memory wal-index cannot be
|
|
11
|
+
* mmap'd over a network mount, so AKM could not run with its data dir on a
|
|
12
|
+
* network share.
|
|
13
|
+
*
|
|
14
|
+
* This module centralises that PRAGMA block behind {@link applyStandardPragmas}
|
|
15
|
+
* and makes the journal mode configurable via the `AKM_SQLITE_JOURNAL_MODE`
|
|
16
|
+
* env var (WAL | DELETE | TRUNCATE; default WAL = unchanged behaviour). When the
|
|
17
|
+
* mode is the WAL default and the data directory is detected to live on a
|
|
18
|
+
* network filesystem, it auto-falls-back to DELETE with a one-line warning.
|
|
19
|
+
*
|
|
20
|
+
* Boundary note: this is a PLAIN module, not a runtime-boundary file. It only
|
|
21
|
+
* does pure string work plus `db.exec()` (allowed everywhere). The single
|
|
22
|
+
* runtime primitive it needs — a filesystem-type probe (statfs) — lives in
|
|
23
|
+
* src/runtime.ts and is injected here, keeping the network-FS classifier a
|
|
24
|
+
* pure, unit-testable function.
|
|
25
|
+
*
|
|
26
|
+
* @module storage/sqlite-pragmas
|
|
27
|
+
*/
|
|
28
|
+
import { warn } from "../core/warn.js";
|
|
29
|
+
import { statfsType } from "../runtime.js";
|
|
30
|
+
const VALID_MODES = new Set(["WAL", "DELETE", "TRUNCATE"]);
|
|
31
|
+
// One-shot warning guards so a misconfigured env var or a network-FS fallback
|
|
32
|
+
// each emit AT MOST ONCE per process rather than on every db open.
|
|
33
|
+
let warnedInvalid = false;
|
|
34
|
+
let warnedNetworkFallback = false;
|
|
35
|
+
/**
|
|
36
|
+
* Resolve a raw `AKM_SQLITE_JOURNAL_MODE` value to a canonical {@link JournalMode}.
|
|
37
|
+
*
|
|
38
|
+
* PURE and unit-testable: the raw string is passed in (not read from
|
|
39
|
+
* `process.env` here). Trims + uppercases; an empty/undefined value yields the
|
|
40
|
+
* WAL default; a recognised value yields its canonical form; any other
|
|
41
|
+
* non-empty value warns once and falls back to WAL. Never throws.
|
|
42
|
+
*/
|
|
43
|
+
export function resolveJournalMode(raw) {
|
|
44
|
+
if (raw === undefined)
|
|
45
|
+
return "WAL";
|
|
46
|
+
const normalized = raw.trim().toUpperCase();
|
|
47
|
+
if (normalized === "")
|
|
48
|
+
return "WAL";
|
|
49
|
+
if (VALID_MODES.has(normalized)) {
|
|
50
|
+
return normalized;
|
|
51
|
+
}
|
|
52
|
+
warnInvalidJournalModeOnce(raw);
|
|
53
|
+
return "WAL";
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The single env-reading seam: resolve the configured journal mode from
|
|
57
|
+
* `process.env.AKM_SQLITE_JOURNAL_MODE`. Read at call time (per open) so tests
|
|
58
|
+
* that set the env per-case see the right value and we avoid stale-env flakes.
|
|
59
|
+
*/
|
|
60
|
+
export function resolveConfiguredJournalMode(env = process.env) {
|
|
61
|
+
return resolveJournalMode(env.AKM_SQLITE_JOURNAL_MODE);
|
|
62
|
+
}
|
|
63
|
+
function warnInvalidJournalModeOnce(raw) {
|
|
64
|
+
if (warnedInvalid)
|
|
65
|
+
return;
|
|
66
|
+
warnedInvalid = true;
|
|
67
|
+
warn(`[akm] invalid AKM_SQLITE_JOURNAL_MODE=${JSON.stringify(raw)} — using WAL (valid: WAL, DELETE, TRUNCATE)`);
|
|
68
|
+
}
|
|
69
|
+
// Known Linux f_type magic numbers for network filesystems. node's statfs
|
|
70
|
+
// normalises `type` to this numeric f_type magic on all platforms.
|
|
71
|
+
const FS_MAGIC_NFS = 0x6969; // 26985 — NFS
|
|
72
|
+
const FS_MAGIC_SMB = 0x517b; // 20859 — older SMB_SUPER_MAGIC
|
|
73
|
+
const FS_MAGIC_CIFS = 0xff534d42; // 4283649346 — SMB/CIFS
|
|
74
|
+
const FS_MAGIC_SMB2 = 0xfe534d42; // 4267272514 — SMB2
|
|
75
|
+
const FS_MAGIC_FUSE = 0x65735546; // 1702057286 — FUSE (sshfs + many network FUSE mounts)
|
|
76
|
+
const NETWORK_FS_MAGICS = new Set([
|
|
77
|
+
FS_MAGIC_NFS,
|
|
78
|
+
FS_MAGIC_SMB,
|
|
79
|
+
FS_MAGIC_CIFS,
|
|
80
|
+
FS_MAGIC_SMB2,
|
|
81
|
+
// FUSE is a judgment call: it backs BOTH network mounts (sshfs) and local
|
|
82
|
+
// mounts (some encrypted/overlay FS). Treating it as network falls back to
|
|
83
|
+
// DELETE — conservative-but-safe (DELETE works everywhere; the only cost is
|
|
84
|
+
// losing WAL concurrency). An operator can always force WAL via the env var.
|
|
85
|
+
FS_MAGIC_FUSE,
|
|
86
|
+
]);
|
|
87
|
+
/**
|
|
88
|
+
* PURE classifier: is `fsType` a known network-filesystem magic number?
|
|
89
|
+
* Returns false for `undefined` (probe failed/unsupported) and for local
|
|
90
|
+
* magics (ext4 0xEF53, btrfs, xfs, tmpfs, apfs, …). Unit-testable with
|
|
91
|
+
* injected magic numbers — no real mount required.
|
|
92
|
+
*/
|
|
93
|
+
export function isNetworkFilesystem(fsType) {
|
|
94
|
+
if (fsType === undefined)
|
|
95
|
+
return false;
|
|
96
|
+
return NETWORK_FS_MAGICS.has(fsType);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Apply AKM's standard opening PRAGMAs to `db`, in order:
|
|
100
|
+
* 1. `journal_mode` = the configured mode (with WAL→DELETE network-FS fallback)
|
|
101
|
+
* 2. `busy_timeout = 30000`
|
|
102
|
+
* 3. `foreign_keys = ON` (unless `opts.foreignKeys === false`)
|
|
103
|
+
* 4. `synchronous = FULL` (only in a rollback-journal mode — DELETE/TRUNCATE)
|
|
104
|
+
*
|
|
105
|
+
* Returns the effective {@link JournalMode} so callers/tests can assert it.
|
|
106
|
+
*
|
|
107
|
+
* `synchronous = FULL` is set explicitly only in DELETE/TRUNCATE so durability
|
|
108
|
+
* intent is clear: rollback journals need FULL for crash-durability across
|
|
109
|
+
* power loss, whereas WAL is durable at NORMAL. SQLite's default synchronous is
|
|
110
|
+
* already FULL when unset, so this never changes WAL-default behaviour — the
|
|
111
|
+
* WAL path emits no `synchronous` pragma, exactly as before.
|
|
112
|
+
*/
|
|
113
|
+
export function applyStandardPragmas(db, opts = {}) {
|
|
114
|
+
let mode = resolveConfiguredJournalMode(opts.env);
|
|
115
|
+
// Network-FS fallback only fires for the WAL default and only when we have a
|
|
116
|
+
// directory to probe. An explicitly-requested DELETE/TRUNCATE is never
|
|
117
|
+
// overridden, and a failed/unsupported probe (undefined) keeps WAL.
|
|
118
|
+
if (mode === "WAL" && opts.dataDir) {
|
|
119
|
+
const probe = opts.fsTypeProbe ?? statfsType;
|
|
120
|
+
if (isNetworkFilesystem(probe(opts.dataDir))) {
|
|
121
|
+
mode = "DELETE";
|
|
122
|
+
warnNetworkFallbackOnce(opts.dataDir);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// PRAGMAs must run before any DDL or DML. busy_timeout is applied FIRST so a
|
|
126
|
+
// journal-mode change that must reclaim a leftover `-wal` file (WAL→DELETE on
|
|
127
|
+
// reopen of an unclean WAL db, e.g. after a crash) can wait out a transient
|
|
128
|
+
// lock instead of failing immediately with SQLITE_BUSY. For the WAL default
|
|
129
|
+
// this is a no-op (WAL→WAL changes nothing), so byte-identical behaviour is
|
|
130
|
+
// preserved.
|
|
131
|
+
db.exec("PRAGMA busy_timeout = 30000");
|
|
132
|
+
db.exec(`PRAGMA journal_mode = ${mode}`);
|
|
133
|
+
if (opts.foreignKeys !== false) {
|
|
134
|
+
db.exec("PRAGMA foreign_keys = ON");
|
|
135
|
+
}
|
|
136
|
+
if (mode !== "WAL") {
|
|
137
|
+
db.exec("PRAGMA synchronous = FULL");
|
|
138
|
+
}
|
|
139
|
+
return mode;
|
|
140
|
+
}
|
|
141
|
+
function warnNetworkFallbackOnce(dataDir) {
|
|
142
|
+
if (warnedNetworkFallback)
|
|
143
|
+
return;
|
|
144
|
+
warnedNetworkFallback = true;
|
|
145
|
+
warn(`[akm] network filesystem detected at ${dataDir} — WAL unsupported, using DELETE journal mode`);
|
|
146
|
+
}
|
|
@@ -29,7 +29,7 @@ import fs from "node:fs";
|
|
|
29
29
|
import path from "node:path";
|
|
30
30
|
import { ConfigError } from "../../core/errors.js";
|
|
31
31
|
import { getTaskLogDir } from "../../core/paths.js";
|
|
32
|
-
import { resolveAkmInvocation } from "../
|
|
32
|
+
import { resolveAkmInvocation } from "../resolve-akm-bin.js";
|
|
33
33
|
import { parseSchedule, translateToCron } from "../schedule.js";
|
|
34
34
|
const BEGIN = (id) => `# akm:task ${id} BEGIN`;
|
|
35
35
|
const END = (id) => `# akm:task ${id} END`;
|
|
@@ -4,7 +4,14 @@
|
|
|
4
4
|
import { CRON_BACKEND } from "./cron.js";
|
|
5
5
|
import { LAUNCHD_BACKEND } from "./launchd.js";
|
|
6
6
|
import { SCHTASKS_BACKEND } from "./schtasks.js";
|
|
7
|
+
let backendsOverrides;
|
|
8
|
+
/** TEST-ONLY. Swap backend selection; pass undefined to restore the real implementations. */
|
|
9
|
+
export function _setBackendsForTests(fakes) {
|
|
10
|
+
backendsOverrides = fakes;
|
|
11
|
+
}
|
|
7
12
|
export function selectBackend(options = {}) {
|
|
13
|
+
if (backendsOverrides?.selectBackend)
|
|
14
|
+
return backendsOverrides.selectBackend(options);
|
|
8
15
|
const platform = options.platform ?? process.platform;
|
|
9
16
|
switch (platform) {
|
|
10
17
|
case "win32":
|
|
@@ -16,6 +23,8 @@ export function selectBackend(options = {}) {
|
|
|
16
23
|
}
|
|
17
24
|
}
|
|
18
25
|
export function backendNameForPlatform(platform = process.platform) {
|
|
26
|
+
if (backendsOverrides?.backendNameForPlatform)
|
|
27
|
+
return backendsOverrides.backendNameForPlatform(platform);
|
|
19
28
|
if (platform === "win32")
|
|
20
29
|
return "schtasks";
|
|
21
30
|
if (platform === "darwin")
|
|
@@ -27,7 +27,7 @@ import path from "node:path";
|
|
|
27
27
|
import launchdTemplate from "../../assets/backends/launchd-template.xml" with { type: "text" };
|
|
28
28
|
import { ConfigError } from "../../core/errors.js";
|
|
29
29
|
import { getTaskLogDir } from "../../core/paths.js";
|
|
30
|
-
import { resolveAkmInvocation } from "../
|
|
30
|
+
import { resolveAkmInvocation } from "../resolve-akm-bin.js";
|
|
31
31
|
import { parseSchedule, translateToLaunchd } from "../schedule.js";
|
|
32
32
|
import { escapeXml, nodeExec, nodeFs } from "./exec-utils.js";
|
|
33
33
|
export const LAUNCHD_LABEL_PREFIX = "com.akm.task.";
|
|
@@ -35,7 +35,7 @@ import path from "node:path";
|
|
|
35
35
|
import schtasksTemplate from "../../assets/backends/schtasks-template.xml" with { type: "text" };
|
|
36
36
|
import { ConfigError } from "../../core/errors.js";
|
|
37
37
|
import { getTaskLogDir } from "../../core/paths.js";
|
|
38
|
-
import { resolveAkmInvocation } from "../
|
|
38
|
+
import { resolveAkmInvocation } from "../resolve-akm-bin.js";
|
|
39
39
|
import { parseSchedule, translateToSchtasks } from "../schedule.js";
|
|
40
40
|
import { escapeXml, nodeExec, nodeFs } from "./exec-utils.js";
|
|
41
41
|
export const DEFAULT_FOLDER_PREFIX = "\\akm\\";
|
|
@@ -44,8 +44,8 @@ export function resolveAkmInvocation(options = {}) {
|
|
|
44
44
|
/**
|
|
45
45
|
* From the URL of a module inside `src/tasks/` figure out the CLI entry.
|
|
46
46
|
*
|
|
47
|
-
* • dev `…/src/tasks/
|
|
48
|
-
* • build `…/dist/tasks/
|
|
47
|
+
* • dev `…/src/tasks/resolve-akm-bin.ts` → `…/src/cli.ts`
|
|
48
|
+
* • build `…/dist/tasks/resolve-akm-bin.js` → `…/dist/cli.js`
|
|
49
49
|
*/
|
|
50
50
|
function resolveCliEntry(moduleUrl) {
|
|
51
51
|
let modulePath;
|
package/dist/tasks/runner.js
CHANGED
|
@@ -33,13 +33,14 @@ import { loadConfig } from "../core/config/config.js";
|
|
|
33
33
|
import { NotFoundError, rethrowIfTestIsolationError } from "../core/errors.js";
|
|
34
34
|
import { buildTaskRunId, insertTaskLogLines, openLogsDatabase, } from "../core/logs-db.js";
|
|
35
35
|
import { getTaskLogDir } from "../core/paths.js";
|
|
36
|
-
import {
|
|
36
|
+
import { withStateDb } from "../core/state-db.js";
|
|
37
37
|
import { error } from "../core/warn.js";
|
|
38
38
|
import { requireAgentProfile, runAgent } from "../integrations/agent/index.js";
|
|
39
39
|
import { resolveProcessAgentProfile } from "../integrations/agent/config.js";
|
|
40
40
|
import { resolveRunner } from "../integrations/agent/runner.js";
|
|
41
41
|
import { spawn } from "../runtime.js";
|
|
42
42
|
import { resolveAssetPath } from "../sources/resolve.js";
|
|
43
|
+
import { getTaskHistory, queryTaskHistory, upsertTaskHistory } from "../storage/repositories/task-history-repository.js";
|
|
43
44
|
import { startWorkflowRun } from "../workflows/runtime/runs.js";
|
|
44
45
|
import { parseTaskDocument } from "./parser.js";
|
|
45
46
|
export async function runTask(id, options = {}) {
|
|
@@ -131,6 +132,11 @@ async function runCommandTask(input) {
|
|
|
131
132
|
stdout: "pipe",
|
|
132
133
|
stderr: "pipe",
|
|
133
134
|
cwd: process.env.HOME ?? "/tmp",
|
|
135
|
+
// Stamp task-runner provenance so any akm invocation in the command tree
|
|
136
|
+
// records usage events as machine traffic, not user demand (DRIFT-6).
|
|
137
|
+
// A more specific stamp already in the environment (e.g. improve's
|
|
138
|
+
// AKM_EVENT_SOURCE=improve on its child spawns) still wins in children.
|
|
139
|
+
env: { ...process.env, AKM_EVENT_SOURCE: process.env.AKM_EVENT_SOURCE ?? "task" },
|
|
134
140
|
});
|
|
135
141
|
let timer;
|
|
136
142
|
let timedOut = false;
|
|
@@ -351,6 +357,10 @@ async function runPromptTask(input) {
|
|
|
351
357
|
timeoutMs: agentTimeoutMs,
|
|
352
358
|
cwd: stashDir,
|
|
353
359
|
...agentOptions,
|
|
360
|
+
// Stamp task-runner provenance for any akm invocation the agent makes
|
|
361
|
+
// (DRIFT-6: agent-task traffic must not be recorded as user demand).
|
|
362
|
+
// Caller-supplied env still wins on conflicts.
|
|
363
|
+
env: { AKM_EVENT_SOURCE: "task", ...agentOptions?.env },
|
|
354
364
|
});
|
|
355
365
|
const finishedAt = now();
|
|
356
366
|
const log = renderPromptLog({ task, profileName: profile.name, result });
|
|
@@ -460,8 +470,7 @@ function persistRunLog(input) {
|
|
|
460
470
|
// ── history ─────────────────────────────────────────────────────────────────
|
|
461
471
|
function appendHistory(result) {
|
|
462
472
|
try {
|
|
463
|
-
|
|
464
|
-
try {
|
|
473
|
+
withStateDb((db) => {
|
|
465
474
|
upsertTaskHistory(db, {
|
|
466
475
|
task_id: result.id,
|
|
467
476
|
status: result.status,
|
|
@@ -477,10 +486,7 @@ function appendHistory(result) {
|
|
|
477
486
|
profile: result.target.kind === "prompt" ? result.target.profile : undefined,
|
|
478
487
|
}),
|
|
479
488
|
});
|
|
480
|
-
}
|
|
481
|
-
finally {
|
|
482
|
-
db.close();
|
|
483
|
-
}
|
|
489
|
+
});
|
|
484
490
|
}
|
|
485
491
|
catch (err) {
|
|
486
492
|
rethrowIfTestIsolationError(err);
|
|
@@ -488,8 +494,7 @@ function appendHistory(result) {
|
|
|
488
494
|
}
|
|
489
495
|
}
|
|
490
496
|
export function readTaskHistory(options = {}) {
|
|
491
|
-
|
|
492
|
-
try {
|
|
497
|
+
return withStateDb((db) => {
|
|
493
498
|
let rows;
|
|
494
499
|
if (options.id) {
|
|
495
500
|
const row = getTaskHistory(db, options.id);
|
|
@@ -503,10 +508,7 @@ export function readTaskHistory(options = {}) {
|
|
|
503
508
|
return rows.slice(0, options.limit);
|
|
504
509
|
}
|
|
505
510
|
return rows;
|
|
506
|
-
}
|
|
507
|
-
finally {
|
|
508
|
-
db.close();
|
|
509
|
-
}
|
|
511
|
+
});
|
|
510
512
|
}
|
|
511
513
|
/**
|
|
512
514
|
* Convert a `TaskHistoryRow` from state.db back to a `TaskRunResult` shape
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
import { readFile } from "node:fs/promises";
|
|
17
17
|
import { fileURLToPath } from "node:url";
|
|
18
18
|
|
|
19
|
-
const TEXT_EXTENSIONS = new Set([".md", ".xml", ".txt", ".sql"]);
|
|
19
|
+
const TEXT_EXTENSIONS = new Set([".md", ".xml", ".txt", ".sql", ".yaml", ".yml"]);
|
|
20
20
|
|
|
21
21
|
function isTextImport(url, importAttributes) {
|
|
22
22
|
if (importAttributes && importAttributes.type === "text") return true;
|
package/dist/wiki/wiki.js
CHANGED
|
@@ -58,6 +58,8 @@ export const SCHEMA_MD = "schema.md";
|
|
|
58
58
|
export const INDEX_MD = "index.md";
|
|
59
59
|
export const LOG_MD = "log.md";
|
|
60
60
|
export const RAW_SUBDIR = "raw";
|
|
61
|
+
/** Canonical location for agent-authored pages, mirroring RAW_SUBDIR. */
|
|
62
|
+
export const PAGES_SUBDIR = "pages";
|
|
61
63
|
/** Files at a wiki root that are not pages. */
|
|
62
64
|
const WIKI_SPECIAL_FILES = new Set([SCHEMA_MD, INDEX_MD, LOG_MD]);
|
|
63
65
|
const WIKI_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
@@ -224,6 +226,43 @@ function readSchemaDescription(wikiDir) {
|
|
|
224
226
|
return undefined;
|
|
225
227
|
}
|
|
226
228
|
}
|
|
229
|
+
/**
|
|
230
|
+
* Load a wiki's `schema.md` body and frontmatter.
|
|
231
|
+
*
|
|
232
|
+
* Unlike {@link readSchemaDescription} (which returns only the frontmatter
|
|
233
|
+
* `description` for `listWikis` summaries), this returns the markdown **body**
|
|
234
|
+
* — everything after the closing `---` of the frontmatter — which is where the
|
|
235
|
+
* page contract, operations, and hard rules live. The body is the rulebook
|
|
236
|
+
* injected into the write-time prompt for wiki-page edits.
|
|
237
|
+
*
|
|
238
|
+
* Swallow-and-degrade like the existing reader: a missing file, an unresolvable
|
|
239
|
+
* wiki dir, or malformed content yields `{ body: "", frontmatter: {} }`. Never
|
|
240
|
+
* throws.
|
|
241
|
+
*/
|
|
242
|
+
export function loadWikiSchema(stashRoot, name) {
|
|
243
|
+
const empty = { body: "", frontmatter: {} };
|
|
244
|
+
let wikiDir;
|
|
245
|
+
try {
|
|
246
|
+
wikiDir = resolveWikiDir(stashRoot, name);
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
return empty;
|
|
250
|
+
}
|
|
251
|
+
let raw;
|
|
252
|
+
try {
|
|
253
|
+
raw = fs.readFileSync(path.join(wikiDir, SCHEMA_MD), "utf8");
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
return empty;
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
const parsed = parseFrontmatter(raw);
|
|
260
|
+
return { body: parsed.content, frontmatter: parsed.data };
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
return empty;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
227
266
|
function toIsoDate(ms) {
|
|
228
267
|
return new Date(ms).toISOString();
|
|
229
268
|
}
|
|
@@ -369,18 +408,20 @@ export function createWiki(stashDir, name) {
|
|
|
369
408
|
fs.writeFileSync(absPath, content, "utf8");
|
|
370
409
|
created.push(absPath);
|
|
371
410
|
}
|
|
372
|
-
// Ensure raw/
|
|
373
|
-
// Handle the dir-exists-but-no-.gitkeep case too (partial scaffolds,
|
|
411
|
+
// Ensure raw/ and pages/ exist with a .gitkeep so empty wikis survive clean
|
|
412
|
+
// clones. Handle the dir-exists-but-no-.gitkeep case too (partial scaffolds,
|
|
374
413
|
// user-created directories) so the invariant always holds after `create`.
|
|
375
|
-
const
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
414
|
+
for (const subdir of [RAW_SUBDIR, PAGES_SUBDIR]) {
|
|
415
|
+
const dir = path.join(wikiDir, subdir);
|
|
416
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
417
|
+
const gitkeepPath = path.join(dir, ".gitkeep");
|
|
418
|
+
if (fs.existsSync(gitkeepPath)) {
|
|
419
|
+
skipped.push(gitkeepPath);
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
fs.writeFileSync(gitkeepPath, "", "utf8");
|
|
423
|
+
created.push(gitkeepPath);
|
|
424
|
+
}
|
|
384
425
|
}
|
|
385
426
|
return { name, ref: `wiki:${name}`, path: wikiDir, created, skipped };
|
|
386
427
|
}
|
|
@@ -4,11 +4,14 @@
|
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import workflowTemplate from "../../assets/workflows/workflow-template.md" with { type: "text" };
|
|
7
|
-
import {
|
|
7
|
+
import { canonicalizeWorkflowName, WORKFLOW_EXTENSIONS } from "../../core/asset/asset-spec.js";
|
|
8
8
|
import { isWithin, resolveStashDir } from "../../core/common.js";
|
|
9
9
|
import { UsageError } from "../../core/errors.js";
|
|
10
10
|
import { warn } from "../../core/warn.js";
|
|
11
|
+
import { compileWorkflowProgram } from "../ir/compile.js";
|
|
11
12
|
import { parseWorkflow } from "../parser.js";
|
|
13
|
+
import { parseWorkflowProgram } from "../program/parser.js";
|
|
14
|
+
import workflowProgramTemplate from "./workflow-program-template.yaml" with { type: "text" };
|
|
12
15
|
const DEFAULT_WORKFLOW_TEMPLATE = renderWorkflowTemplate({
|
|
13
16
|
title: "Example Workflow",
|
|
14
17
|
firstStepTitle: "First Step",
|
|
@@ -17,6 +20,15 @@ const DEFAULT_WORKFLOW_TEMPLATE = renderWorkflowTemplate({
|
|
|
17
20
|
export function getWorkflowTemplate() {
|
|
18
21
|
return DEFAULT_WORKFLOW_TEMPLATE;
|
|
19
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Minimal valid YAML workflow *program* (redesign addendum, R1), printed by
|
|
25
|
+
* `akm workflow template --yaml`. Kept as an external asset file per the repo
|
|
26
|
+
* convention (see `workflow-program-template.yaml` next to this module);
|
|
27
|
+
* `tests/workflows/program-assets.test.ts` pins that it parses AND compiles.
|
|
28
|
+
*/
|
|
29
|
+
export function getWorkflowProgramTemplate() {
|
|
30
|
+
return workflowProgramTemplate;
|
|
31
|
+
}
|
|
20
32
|
export function buildWorkflowTemplate(name) {
|
|
21
33
|
if (!name)
|
|
22
34
|
return DEFAULT_WORKFLOW_TEMPLATE;
|
|
@@ -33,25 +45,97 @@ export function buildWorkflowTemplate(name) {
|
|
|
33
45
|
}
|
|
34
46
|
return customized;
|
|
35
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Customize the shipped YAML program template ({@link getWorkflowProgramTemplate})
|
|
50
|
+
* for a named workflow: swap the placeholder `name:` for the workflow's slug so
|
|
51
|
+
* a freshly created `.yaml`/`.yml` asset round-trips through the program parser
|
|
52
|
+
* (its `title` becomes the slug). Parses AND compiles the result — mirroring
|
|
53
|
+
* {@link buildWorkflowTemplate} — so a create never writes an asset that
|
|
54
|
+
* `show`/`start`/`validate` would then reject.
|
|
55
|
+
*/
|
|
56
|
+
export function buildWorkflowProgramTemplate(name) {
|
|
57
|
+
if (!name)
|
|
58
|
+
return workflowProgramTemplate;
|
|
59
|
+
const programName = slugifyWorkflowStepId(name);
|
|
60
|
+
const customized = workflowProgramTemplate.replace(/^name:.*$/m, `name: ${programName}`);
|
|
61
|
+
const parsed = parseWorkflowProgram(customized, { path: `<template:${name}>` });
|
|
62
|
+
if (!parsed.ok) {
|
|
63
|
+
throw new UsageError(formatWorkflowErrors(`<template:${name}>`, parsed.errors));
|
|
64
|
+
}
|
|
65
|
+
const compiled = compileWorkflowProgram(parsed.program);
|
|
66
|
+
if (!compiled.ok) {
|
|
67
|
+
throw new UsageError(formatWorkflowErrors(`<template:${name}>`, compiled.errors));
|
|
68
|
+
}
|
|
69
|
+
return customized;
|
|
70
|
+
}
|
|
71
|
+
/** Recognized YAML workflow-program suffixes (see {@link WORKFLOW_EXTENSIONS}). */
|
|
72
|
+
const WORKFLOW_PROGRAM_SUFFIX_RE = /\.ya?ml$/i;
|
|
36
73
|
export function createWorkflowAsset(input) {
|
|
37
74
|
const stashDir = resolveStashDir();
|
|
38
75
|
const typeRoot = path.join(stashDir, "workflows");
|
|
39
76
|
fs.mkdirSync(typeRoot, { recursive: true });
|
|
77
|
+
// A `.yaml`/`.yml` name selects the YAML *program* format (redesign
|
|
78
|
+
// addendum, R1); capture the exact suffix the user typed so the written file
|
|
79
|
+
// keeps it, then strip every workflow extension to get the canonical name.
|
|
80
|
+
const suffixMatch = input.name.trim().replace(/\\/g, "/").match(WORKFLOW_PROGRAM_SUFFIX_RE);
|
|
81
|
+
const programSuffix = suffixMatch ? suffixMatch[0].toLowerCase() : undefined;
|
|
82
|
+
const isProgram = programSuffix !== undefined;
|
|
40
83
|
const normalizedName = normalizeWorkflowName(input.name);
|
|
41
|
-
|
|
84
|
+
// The write target is DEFINITIVE — the canonical name plus the chosen format's
|
|
85
|
+
// extension (`.yaml`/`.yml` for a program, `.md` for markdown). We deliberately
|
|
86
|
+
// do NOT go through `resolveAssetPathFromName`, which PROBES existing files and
|
|
87
|
+
// would redirect a markdown create onto an existing `foo.yaml` (writing
|
|
88
|
+
// markdown into a `.yaml`). Computing the target directly makes a markdown
|
|
89
|
+
// create always write `.md`, so the finding-C cross-extension check below sees
|
|
90
|
+
// the real collision instead of a self-match.
|
|
91
|
+
const targetSuffix = isProgram ? programSuffix : ".md";
|
|
92
|
+
const assetPath = path.join(typeRoot, `${normalizedName}${targetSuffix}`);
|
|
42
93
|
if (!isWithin(assetPath, typeRoot)) {
|
|
43
94
|
throw new UsageError(`Resolved workflow path escapes the stash: "${normalizedName}"`, "PATH_ESCAPE_VIOLATION");
|
|
44
95
|
}
|
|
96
|
+
// Codex round-3 finding C: a `workflow:<name>` ref is canonical across every
|
|
97
|
+
// recognized extension (`.md`/`.yaml`/`.yml`) and resolves `.md` BEFORE
|
|
98
|
+
// `.yaml`. So creating `foo.yaml` while `foo.md` exists would return the ref
|
|
99
|
+
// `workflow:foo` that still starts the OLD markdown workflow — a silently
|
|
100
|
+
// shadowed asset. Reject creation when ANY recognized extension already holds
|
|
101
|
+
// the same canonical name, naming the existing file. A same-extension collision
|
|
102
|
+
// (the target path itself exists) keeps the classic `--force` overwrite escape;
|
|
103
|
+
// a DIFFERENT-extension collision cannot be force-overwritten (writing a new
|
|
104
|
+
// file would leave the old one shadowing it) — remove the existing file first.
|
|
105
|
+
const existingPaths = WORKFLOW_EXTENSIONS.map((ext) => path.join(typeRoot, `${normalizedName}${ext}`)).filter((candidate) => fs.existsSync(candidate));
|
|
106
|
+
const conflicting = existingPaths.find((p) => p !== assetPath);
|
|
107
|
+
if (conflicting !== undefined) {
|
|
108
|
+
throw new UsageError(`Workflow "${normalizedName}" already exists as ${path.relative(stashDir, conflicting)} — the ` +
|
|
109
|
+
`\`workflow:${normalizedName}\` ref resolves to that file, so creating this one would shadow it. ` +
|
|
110
|
+
`Remove or rename the existing file first, or create the workflow under a different name.`, "RESOURCE_ALREADY_EXISTS");
|
|
111
|
+
}
|
|
45
112
|
if (fs.existsSync(assetPath) && !input.force) {
|
|
46
113
|
throw new UsageError(`Workflow "${normalizedName}" already exists. Re-run with --force to overwrite it.`, "RESOURCE_ALREADY_EXISTS");
|
|
47
114
|
}
|
|
48
115
|
const content = input.from
|
|
49
116
|
? readWorkflowSource(input.from, stashDir)
|
|
50
|
-
: (input.content ??
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
117
|
+
: (input.content ??
|
|
118
|
+
(isProgram ? buildWorkflowProgramTemplate(normalizedName) : buildWorkflowTemplate(normalizedName)));
|
|
119
|
+
const sourcePath = input.from ?? `workflows/${normalizedName}${isProgram ? programSuffix : ".md"}`;
|
|
120
|
+
// Validate against the format the destination extension selects — a YAML
|
|
121
|
+
// program parses+compiles as a program, markdown as a document — so the
|
|
122
|
+
// created asset is guaranteed usable by show/start/validate, which pick
|
|
123
|
+
// their parser by the same extension.
|
|
124
|
+
if (isProgram) {
|
|
125
|
+
const parsed = parseWorkflowProgram(content, { path: sourcePath });
|
|
126
|
+
if (!parsed.ok) {
|
|
127
|
+
throw new UsageError(formatWorkflowErrors(sourcePath, parsed.errors));
|
|
128
|
+
}
|
|
129
|
+
const compiled = compileWorkflowProgram(parsed.program);
|
|
130
|
+
if (!compiled.ok) {
|
|
131
|
+
throw new UsageError(formatWorkflowErrors(sourcePath, compiled.errors));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
const result = parseWorkflow(content, { path: sourcePath });
|
|
136
|
+
if (!result.ok) {
|
|
137
|
+
throw new UsageError(formatWorkflowErrors(sourcePath, result.errors));
|
|
138
|
+
}
|
|
55
139
|
}
|
|
56
140
|
fs.mkdirSync(path.dirname(assetPath), { recursive: true });
|
|
57
141
|
fs.writeFileSync(assetPath, content.endsWith("\n") ? content : `${content}\n`, "utf8");
|
|
@@ -83,11 +167,14 @@ function readWorkflowSource(source, stashDir) {
|
|
|
83
167
|
return fs.readFileSync(resolved, "utf8");
|
|
84
168
|
}
|
|
85
169
|
function normalizeWorkflowName(name) {
|
|
86
|
-
|
|
170
|
+
// Strip any recognized workflow extension (.md/.yaml/.yml) so the canonical
|
|
171
|
+
// name — and thus the `workflow:<name>` ref — is extension-free regardless of
|
|
172
|
+
// how the user spelled it. The chosen format is recovered from the raw suffix
|
|
173
|
+
// by the caller (createWorkflowAsset).
|
|
174
|
+
const normalized = canonicalizeWorkflowName(name
|
|
87
175
|
.trim()
|
|
88
176
|
.replace(/\\/g, "/")
|
|
89
|
-
.replace(/^\/+|\/+$/g, "")
|
|
90
|
-
.replace(/\.md$/i, "");
|
|
177
|
+
.replace(/^\/+|\/+$/g, ""));
|
|
91
178
|
if (!normalized) {
|
|
92
179
|
throw new UsageError("Workflow name cannot be empty.");
|
|
93
180
|
}
|
|
@@ -137,6 +224,32 @@ export function validateWorkflowSource(target) {
|
|
|
137
224
|
const content = fs.readFileSync(resolved, "utf8");
|
|
138
225
|
return { path: target, parse: parseWorkflow(content, { path: target }) };
|
|
139
226
|
}
|
|
227
|
+
/**
|
|
228
|
+
* Validate a YAML workflow *program* by filesystem path: parse via
|
|
229
|
+
* `parseWorkflowProgram`, then — only when the parse is clean — compile via
|
|
230
|
+
* `compileWorkflowProgram` so expression/reference errors surface too. Both
|
|
231
|
+
* error lists carry line numbers and are returned in the same
|
|
232
|
+
* `WorkflowError[]` shape, ready for `formatWorkflowErrors`. Throws
|
|
233
|
+
* `UsageError` only when the target cannot be located on disk.
|
|
234
|
+
*/
|
|
235
|
+
export function validateWorkflowProgramSource(target) {
|
|
236
|
+
const resolved = path.resolve(target);
|
|
237
|
+
if (!fs.existsSync(resolved)) {
|
|
238
|
+
throw new UsageError(`Workflow file not found: "${target}".`);
|
|
239
|
+
}
|
|
240
|
+
const content = fs.readFileSync(resolved, "utf8");
|
|
241
|
+
const parse = parseWorkflowProgram(content, { path: target });
|
|
242
|
+
if (!parse.ok) {
|
|
243
|
+
return { path: target, result: { ok: false, errors: parse.errors } };
|
|
244
|
+
}
|
|
245
|
+
const compiled = compileWorkflowProgram(parse.program);
|
|
246
|
+
if (!compiled.ok) {
|
|
247
|
+
return { path: target, result: { ok: false, errors: compiled.errors } };
|
|
248
|
+
}
|
|
249
|
+
// Non-fatal advisories ride along on a successful validation (additive) so
|
|
250
|
+
// `workflow validate` can surface them without changing the ok verdict.
|
|
251
|
+
return { path: target, result: { ok: true, program: parse.program, warnings: compiled.warnings } };
|
|
252
|
+
}
|
|
140
253
|
function renderWorkflowTemplate(input) {
|
|
141
254
|
return workflowTemplate
|
|
142
255
|
.replace("{{TITLE}}", input.title)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# akm YAML workflow program (redesign addendum, v1).
|
|
2
|
+
# Save under workflows/<name>.yaml and lint with:
|
|
3
|
+
# akm workflow validate workflows/<name>.yaml
|
|
4
|
+
version: 1
|
|
5
|
+
name: example-workflow
|
|
6
|
+
description: Describe what this workflow accomplishes.
|
|
7
|
+
|
|
8
|
+
params:
|
|
9
|
+
example_param: { type: string, description: Explain this parameter }
|
|
10
|
+
|
|
11
|
+
defaults: # run-level defaults, overridable per unit
|
|
12
|
+
runner: inherit # llm | agent | sdk | inherit
|
|
13
|
+
timeout: 10m # "<n>ms" | "<n>s" | "<n>m" | "none"
|
|
14
|
+
on_error: fail # fail | continue
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- id: first-step
|
|
18
|
+
title: First Step
|
|
19
|
+
unit:
|
|
20
|
+
instructions: |
|
|
21
|
+
Describe what to do in this step.
|
|
22
|
+
Reference run parameters explicitly: ${{ params.example_param }}.
|
|
23
|
+
gate:
|
|
24
|
+
criteria:
|
|
25
|
+
- Confirm the first step is complete
|
|
26
|
+
|
|
27
|
+
- id: second-step
|
|
28
|
+
title: Second Step
|
|
29
|
+
unit:
|
|
30
|
+
instructions: |
|
|
31
|
+
Describe what happens next.
|
package/dist/workflows/cli.js
CHANGED