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
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { z } from "zod";
|
|
10
10
|
import { isBundleSlug } from "../../asset/asset-ref.js";
|
|
11
|
+
import { hasRegistryUrlCredentials, REGISTRY_CREDENTIALS_UNSUPPORTED } from "../../registry-url.js";
|
|
11
12
|
import { httpUrl, nonEmptyString, positiveInt } from "./primitives.js";
|
|
12
13
|
// ── Sources / registries / installed ────────────────────────────────────────
|
|
13
14
|
const SourceConfigEntryOptionsSchema = z.record(z.unknown());
|
|
@@ -49,7 +50,11 @@ export const SourceConfigEntrySchema = z
|
|
|
49
50
|
});
|
|
50
51
|
export const RegistryConfigEntrySchema = z
|
|
51
52
|
.object({
|
|
52
|
-
url: httpUrl,
|
|
53
|
+
url: httpUrl.superRefine((value, ctx) => {
|
|
54
|
+
if (hasRegistryUrlCredentials(value)) {
|
|
55
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: REGISTRY_CREDENTIALS_UNSUPPORTED });
|
|
56
|
+
}
|
|
57
|
+
}),
|
|
53
58
|
name: z.string().min(1).optional(),
|
|
54
59
|
enabled: z.boolean().optional(),
|
|
55
60
|
provider: z.string().min(1).optional(),
|
|
@@ -58,9 +63,8 @@ export const RegistryConfigEntrySchema = z
|
|
|
58
63
|
.passthrough();
|
|
59
64
|
// ── Bundles (0.9.0 config-shape cutover, spec §10.1 / D-R5) ─────────────────
|
|
60
65
|
//
|
|
61
|
-
// `bundles` + `defaultBundle` are the
|
|
62
|
-
//
|
|
63
|
-
// bundle entry carries ONE source descriptor (`path` | `git` | `website` | `npm`
|
|
66
|
+
// `bundles` + `defaultBundle` are the desired-configuration shape. Each bundle
|
|
67
|
+
// entry carries ONE source descriptor (`path` | `git` | `website` | `npm`
|
|
64
68
|
// — mirroring today's source types), an optional `writable`, an optional
|
|
65
69
|
// `registryId` locator (the original registry install id, preserved verbatim so
|
|
66
70
|
// a non-slug-legal id like `github:owner/repo` is not lost when its slug-legal
|
|
@@ -69,10 +73,7 @@ export const RegistryConfigEntrySchema = z
|
|
|
69
73
|
// machinery). The map KEY is the workspace bundle slug (spec §11.1 charset: no
|
|
70
74
|
// `/`, `:`, `.`, `#`, whitespace), validated with {@link isBundleSlug}.
|
|
71
75
|
//
|
|
72
|
-
//
|
|
73
|
-
// by exactly what `deriveInstallations` derives today (D-R5). `bindings` (spec
|
|
74
|
-
// §10.1) is Tier B and is NEVER accepted here (the top-level superRefine rejects
|
|
75
|
-
// it) — it is not part of the 0.9.0 config-shape cutover.
|
|
76
|
+
// `bindings` is not accepted here; the top-level schema rejects it.
|
|
76
77
|
/** Website source descriptor for a bundle entry (spec §10.1). */
|
|
77
78
|
const BundleWebsiteDescriptorSchema = z
|
|
78
79
|
.object({
|
|
@@ -110,15 +111,11 @@ export const BundleConfigEntrySchema = z
|
|
|
110
111
|
npm: z.string().min(1).optional(),
|
|
111
112
|
writable: z.boolean().optional(),
|
|
112
113
|
// Opt a bundle out of indexing, search, refresh, and write targeting
|
|
113
|
-
// without deleting it.
|
|
114
|
-
//
|
|
115
|
-
// (`write-source.ts`, `search-source.ts`); without it here, migrating a
|
|
116
|
-
// disabled source would silently reactivate it.
|
|
114
|
+
// without deleting it. The runtime honors the derived value in write and
|
|
115
|
+
// search source selection.
|
|
117
116
|
enabled: z.boolean().optional(),
|
|
118
|
-
// The
|
|
119
|
-
//
|
|
120
|
-
// source locator survives the config-shape migration (D-R5). Absent when the
|
|
121
|
-
// bundle key already equals the source's stable id.
|
|
117
|
+
// The registry install id when the bundle key was slug-derived from it
|
|
118
|
+
// (e.g. registryId `github:owner/repo` → key `repo`).
|
|
122
119
|
registryId: z.string().min(1).optional(),
|
|
123
120
|
components: z.record(z.string().min(1), BundleComponentConfigSchema).optional(),
|
|
124
121
|
})
|
package/dist/core/errors.js
CHANGED
|
@@ -10,12 +10,14 @@ const CONFIG_HINTS = {
|
|
|
10
10
|
STASH_DIR_NOT_A_DIRECTORY: "The configured default bundle path exists but isn't a directory. Update it to point at a folder.",
|
|
11
11
|
STASH_DIR_UNREADABLE: "Check the path exists and your user has read permission, or update the default bundle path.",
|
|
12
12
|
DATA_DIR_UNREADABLE: "The data directory is not readable by the user running akm. Check its owner and mode, or point AKM_DATA_DIR / XDG_DATA_HOME somewhere this user owns.",
|
|
13
|
+
INDEX_SCHEMA_INCOMPATIBLE: "Run `akm index --full` to rebuild the derived index from the currently materialized sources.",
|
|
13
14
|
EMBEDDING_NOT_CONFIGURED: 'Run `akm config set embedding \'{"endpoint":"...","model":"..."}\'` to enable embeddings.',
|
|
14
15
|
LLM_NOT_CONFIGURED: 'Run `akm setup` or configure an `engines` entry with `kind: "llm"`, then select it with `defaults.llmEngine`.',
|
|
15
16
|
TEST_ISOLATION_MISSING: "Under bun test, when AKM_BUNDLE_DIR is set you MUST also set XDG_DATA_HOME (or AKM_DATA_DIR) and XDG_STATE_HOME (or AKM_STATE_DIR) to temp directories so the test does not touch the developer's real ~/.local/share/akm or ~/.local/state/akm.",
|
|
16
17
|
SETUP_TMP_STASH_REFUSED: "Use a persistent directory, or set AKM_FORCE_SETUP_TMP_STASH=1 to opt in to a sandboxed setup (setup also pre-sets AKM_BUNDLE_DIR so config and cache writes auto-isolate into $stashDir/.akm/ — host config is preserved).",
|
|
17
18
|
UNSAFE_STASH_DIR: "Choose a path inside your home directory (e.g. ~/akm) or another empty workspace. The bundle directory cannot be the filesystem root, your home directory itself, or a sensitive system path like /etc, /var, ~/.config, or ~/.ssh.",
|
|
18
19
|
UNKNOWN_IMPROVE_STRATEGY: "Pass one of the listed strategy names to `--strategy`, or define it under `improve.strategies`. Names are case-sensitive.",
|
|
20
|
+
EXECUTION_NOT_AUTHORIZED: "Change the selected tools or update the machine/user execution policy, then retry.",
|
|
19
21
|
};
|
|
20
22
|
/** Default hint for each UsageError code. */
|
|
21
23
|
const USAGE_HINTS = {
|
package/dist/core/events.js
CHANGED
|
@@ -1,10 +1,34 @@
|
|
|
1
1
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
/**
|
|
5
|
+
* Append-only events stream — backed by state.db (#204, Phase 3).
|
|
6
|
+
*
|
|
7
|
+
* Every mutating CLI verb funnels through `appendEvent` so external
|
|
8
|
+
* observers (sync, replication, audit, dashboards) can react to stash
|
|
9
|
+
* changes. Events are stored in the `events` table in `state.db`
|
|
10
|
+
* (SQLite, WAL mode) instead of a flat `events.jsonl` file.
|
|
11
|
+
*
|
|
12
|
+
* The helper is the only thing in akm that writes to the events table. It
|
|
13
|
+
* accepts an injectable `dbPath` (via `EventsContext`) so tests can pin a
|
|
14
|
+
* tmpdir without any global mutation.
|
|
15
|
+
*
|
|
16
|
+
* Format (each EventEnvelope):
|
|
17
|
+
* { "schemaVersion": 1, "id": <number>, "ts": "<ISO>",
|
|
18
|
+
* "eventType": "<verb>", "ref"?: "<asset-ref>", ... }
|
|
19
|
+
*
|
|
20
|
+
* - `id` is a monotonic SQLite AUTOINCREMENT rowid. Callers can persist it
|
|
21
|
+
* as a durable cursor for `--since` resumption. The public API surfaces this
|
|
22
|
+
* as the opaque `nextOffset` number.
|
|
23
|
+
* - `ts` is ISO-8601 (UTC, millisecond precision).
|
|
24
|
+
*/
|
|
25
|
+
import fs from "node:fs";
|
|
26
|
+
import { openDatabase } from "../storage/database.js";
|
|
4
27
|
import { insertEvent, readStateEvents } from "../storage/repositories/events-repository.js";
|
|
5
28
|
import { rethrowIfTestIsolationError } from "./errors.js";
|
|
6
29
|
import { getStateDbPath, openStateDatabase, withStateDb } from "./state-db.js";
|
|
7
30
|
import { borrowScopedStateDb } from "./state-db-scope.js";
|
|
31
|
+
import { isReadOnlyFilesystemError } from "./system-error.js";
|
|
8
32
|
import { error } from "./warn.js";
|
|
9
33
|
/**
|
|
10
34
|
* Resolve the state.db path from context:
|
|
@@ -39,6 +63,8 @@ function resolveNow(ctx) {
|
|
|
39
63
|
* connection.
|
|
40
64
|
*/
|
|
41
65
|
export function appendEvent(input, ctx) {
|
|
66
|
+
if (ctx?.readOnly)
|
|
67
|
+
return;
|
|
42
68
|
const now = resolveNow(ctx);
|
|
43
69
|
const ts = new Date(now()).toISOString();
|
|
44
70
|
const row = { eventType: input.eventType, ts, ref: input.ref, metadata: input.metadata };
|
|
@@ -68,31 +94,40 @@ export function appendEvent(input, ctx) {
|
|
|
68
94
|
catch (err) {
|
|
69
95
|
// Never mask the bun-test isolation guard as a silent "events failed".
|
|
70
96
|
rethrowIfTestIsolationError(err);
|
|
97
|
+
// Read-only sandboxes can serve an existing index but cannot create the
|
|
98
|
+
// maintenance lock or state DB used by best-effort usage tracking. That is
|
|
99
|
+
// expected for a read verb; unrelated storage failures remain visible.
|
|
100
|
+
if (isReadOnlyFilesystemError(err))
|
|
101
|
+
return;
|
|
71
102
|
// Best-effort: events stream failures must not break the mutating verb.
|
|
72
103
|
// Surface once to stderr so operators can diagnose.
|
|
73
104
|
error(`akm: appendEvent failed: ${String(err)}`);
|
|
74
105
|
}
|
|
75
106
|
}
|
|
76
|
-
/**
|
|
77
|
-
* 0.9.0 breaking change (owner ruling 12): `akm sync` used to persist
|
|
78
|
-
* `eventType: "save"`; it now writes `"sync"` instead (matching the command
|
|
79
|
-
* name). Existing `state.db` rows — and any user script running
|
|
80
|
-
* `akm log --type save` — still carry the old spelling. Rather than
|
|
81
|
-
* rewriting historical rows (a migration users never asked for, on data we
|
|
82
|
-
* don't get to touch at rest), reads treat the two names as synonyms: asking
|
|
83
|
-
* for either "save" or "sync" returns rows written under both names. Only
|
|
84
|
-
* the WRITE path (sources-cli.ts's `runSyncBody`) changed.
|
|
85
|
-
*/
|
|
86
|
-
const SAVE_SYNC_EVENT_TYPE_ALIASES = new Set(["save", "sync"]);
|
|
87
107
|
/**
|
|
88
108
|
* Read all events matching the filter. Returns a `nextOffset` that callers
|
|
89
109
|
* can persist between processes for monotonic resumption.
|
|
90
110
|
*/
|
|
91
111
|
export function readEvents(options = {}, ctx) {
|
|
112
|
+
if (ctx?.readOnlySnapshotUnavailable)
|
|
113
|
+
return { events: [], nextOffset: 0 };
|
|
92
114
|
const dbPath = resolveDbPath(ctx);
|
|
93
115
|
let db;
|
|
116
|
+
let ownsDb = false;
|
|
94
117
|
try {
|
|
95
|
-
|
|
118
|
+
if (ctx?.db) {
|
|
119
|
+
db = ctx.db;
|
|
120
|
+
}
|
|
121
|
+
else if (ctx?.readOnly) {
|
|
122
|
+
if (!fs.existsSync(dbPath))
|
|
123
|
+
return { events: [], nextOffset: 0 };
|
|
124
|
+
db = openDatabase(dbPath, { readonly: true, create: false });
|
|
125
|
+
ownsDb = true;
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
db = openStateDatabase(dbPath);
|
|
129
|
+
ownsDb = true;
|
|
130
|
+
}
|
|
96
131
|
}
|
|
97
132
|
catch (err) {
|
|
98
133
|
// Never mask the bun-test isolation guard as "no events".
|
|
@@ -101,12 +136,7 @@ export function readEvents(options = {}, ctx) {
|
|
|
101
136
|
return { events: [], nextOffset: 0 };
|
|
102
137
|
}
|
|
103
138
|
try {
|
|
104
|
-
//
|
|
105
|
-
// ?` match (see SAVE_SYNC_EVENT_TYPE_ALIASES above), so widen the SQL
|
|
106
|
-
// filter to "no type filter" for that one case and apply the alias match
|
|
107
|
-
// client-side alongside the existing tag post-filter below.
|
|
108
|
-
const typeIsAliased = options.type !== undefined && SAVE_SYNC_EVENT_TYPE_ALIASES.has(options.type);
|
|
109
|
-
// D-38: a JS-side post-filter (the type alias above, or the tag filters
|
|
139
|
+
// D-38: a JS-side post-filter (the tag filters
|
|
110
140
|
// below) runs AFTER the SQL read, so a SQL-level LIMIT applied before it
|
|
111
141
|
// could drop rows the post-filter would have kept out anyway, silently
|
|
112
142
|
// returning fewer than `limit` (or the wrong — oldest-in-the-SQL-window —
|
|
@@ -114,21 +144,26 @@ export function readEvents(options = {}, ctx) {
|
|
|
114
144
|
// downstream can shrink the result further; otherwise read unbounded (the
|
|
115
145
|
// pre-existing behavior) and apply `limit` ourselves, below, AFTER the
|
|
116
146
|
// post-filter runs.
|
|
117
|
-
const needsPostFilter =
|
|
118
|
-
(options.excludeTags?.length ?? 0) > 0 ||
|
|
119
|
-
(options.includeTags?.length ?? 0) > 0 ||
|
|
120
|
-
options.runId !== undefined;
|
|
147
|
+
const needsPostFilter = (options.excludeTags?.length ?? 0) > 0 || (options.includeTags?.length ?? 0) > 0 || options.runId !== undefined;
|
|
121
148
|
const pushLimitToSql = options.limit !== undefined && !needsPostFilter;
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
149
|
+
let rawEvents;
|
|
150
|
+
let nextId;
|
|
151
|
+
try {
|
|
152
|
+
({ events: rawEvents, nextId } = readStateEvents(db, {
|
|
153
|
+
sinceId: options.sinceOffset,
|
|
154
|
+
since: options.since,
|
|
155
|
+
type: options.type,
|
|
156
|
+
ref: options.ref,
|
|
157
|
+
...(pushLimitToSql ? { limit: options.limit } : {}),
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
if (ctx?.readOnly && error instanceof Error && /no such table:/i.test(error.message)) {
|
|
162
|
+
return { events: [], nextOffset: options.sinceOffset ?? 0 };
|
|
163
|
+
}
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
129
166
|
const filtered = rawEvents.filter((envelope) => {
|
|
130
|
-
if (typeIsAliased && !SAVE_SYNC_EVENT_TYPE_ALIASES.has(envelope.eventType))
|
|
131
|
-
return false;
|
|
132
167
|
if (options.runId !== undefined && envelope.metadata?.runId !== options.runId)
|
|
133
168
|
return false;
|
|
134
169
|
// Apply tag filters after the indexed state.db read.
|
|
@@ -148,6 +183,7 @@ export function readEvents(options = {}, ctx) {
|
|
|
148
183
|
return { events, nextOffset: nextId };
|
|
149
184
|
}
|
|
150
185
|
finally {
|
|
151
|
-
|
|
186
|
+
if (ownsDb)
|
|
187
|
+
db.close();
|
|
152
188
|
}
|
|
153
189
|
}
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
import { cloneExecutionJsonObject } from "../execution/json.js";
|
|
4
5
|
const COMMON_FIELDS = [
|
|
5
6
|
"schemaVersion",
|
|
6
7
|
"ok",
|
|
7
8
|
"scope",
|
|
8
9
|
"dryRun",
|
|
10
|
+
"notices",
|
|
9
11
|
"skipped",
|
|
10
12
|
"guidance",
|
|
11
13
|
"memorySummary",
|
|
12
14
|
"memoryCleanup",
|
|
13
15
|
"cyclesRun",
|
|
14
16
|
"plannedRefs",
|
|
17
|
+
"plan",
|
|
15
18
|
"actions",
|
|
16
19
|
"distillSkipped",
|
|
17
20
|
"validationFailures",
|
|
@@ -55,6 +58,308 @@ function requireExactFields(value, allowed) {
|
|
|
55
58
|
if (unknown.length > 0)
|
|
56
59
|
fail(`unknown field${unknown.length === 1 ? "" : "s"}: ${unknown.sort().join(", ")}`);
|
|
57
60
|
}
|
|
61
|
+
function requireNumber(value, field, path) {
|
|
62
|
+
if (typeof value[field] !== "number" || !Number.isFinite(value[field]))
|
|
63
|
+
fail(`${path}.${field} must be a number`);
|
|
64
|
+
}
|
|
65
|
+
function requireCount(value, field, path) {
|
|
66
|
+
requireNumber(value, field, path);
|
|
67
|
+
if (!Number.isSafeInteger(value[field]) || value[field] < 0) {
|
|
68
|
+
fail(`${path}.${field} must be a non-negative integer`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function validateLoweringNotices(value) {
|
|
72
|
+
if (!Array.isArray(value))
|
|
73
|
+
fail("notices must be an array");
|
|
74
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
75
|
+
const notice = value[index];
|
|
76
|
+
if (!isRecord(notice))
|
|
77
|
+
fail(`notices[${index}] must be an object`);
|
|
78
|
+
requireExactFields(notice, new Set(["code", "severity", "adapter", "field", "message", "details"]));
|
|
79
|
+
for (const field of ["code", "adapter", "message"]) {
|
|
80
|
+
if (typeof notice[field] !== "string")
|
|
81
|
+
fail(`notices[${index}].${field} must be a string`);
|
|
82
|
+
}
|
|
83
|
+
if (notice.severity !== "info" && notice.severity !== "warning") {
|
|
84
|
+
fail(`notices[${index}].severity is invalid`);
|
|
85
|
+
}
|
|
86
|
+
if (notice.field !== undefined && notice.field !== null && typeof notice.field !== "string") {
|
|
87
|
+
fail(`notices[${index}].field must be a string or null`);
|
|
88
|
+
}
|
|
89
|
+
if (notice.details !== undefined && notice.details !== null) {
|
|
90
|
+
try {
|
|
91
|
+
cloneExecutionJsonObject(notice.details, `notices[${index}].details`);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
fail(`notices[${index}].details must be a JSON object or null`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function validateConsolidationPlan(value) {
|
|
100
|
+
if (!isRecord(value))
|
|
101
|
+
fail("plan.consolidation must be an object");
|
|
102
|
+
requireExactFields(value, new Set([
|
|
103
|
+
"configured",
|
|
104
|
+
"effective",
|
|
105
|
+
"poolSize",
|
|
106
|
+
"candidatePoolSize",
|
|
107
|
+
"gates",
|
|
108
|
+
"wouldRun",
|
|
109
|
+
"reason",
|
|
110
|
+
"estimatedChunks",
|
|
111
|
+
]));
|
|
112
|
+
if (!isRecord(value.configured))
|
|
113
|
+
fail("plan.consolidation.configured must be an object");
|
|
114
|
+
requireExactFields(value.configured, new Set(["enabled", "minPoolSize", "limit", "maxChunkSize", "incrementalSince"]));
|
|
115
|
+
if (value.configured.enabled !== undefined && typeof value.configured.enabled !== "boolean") {
|
|
116
|
+
fail("plan.consolidation.configured.enabled must be a boolean");
|
|
117
|
+
}
|
|
118
|
+
for (const field of ["minPoolSize", "limit", "maxChunkSize"]) {
|
|
119
|
+
if (value.configured[field] !== undefined && typeof value.configured[field] !== "number") {
|
|
120
|
+
fail(`plan.consolidation.configured.${field} must be a number`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (value.configured.incrementalSince !== undefined && typeof value.configured.incrementalSince !== "string") {
|
|
124
|
+
fail("plan.consolidation.configured.incrementalSince must be a string");
|
|
125
|
+
}
|
|
126
|
+
if (!isRecord(value.effective))
|
|
127
|
+
fail("plan.consolidation.effective must be an object");
|
|
128
|
+
requireExactFields(value.effective, new Set(["enabled", "minPoolSize", "limit", "chunkSize"]));
|
|
129
|
+
if (typeof value.effective.enabled !== "boolean")
|
|
130
|
+
fail("plan.consolidation.effective.enabled must be a boolean");
|
|
131
|
+
requireCount(value.effective, "minPoolSize", "plan.consolidation.effective");
|
|
132
|
+
requireCount(value.effective, "chunkSize", "plan.consolidation.effective");
|
|
133
|
+
if (value.effective.limit !== undefined && typeof value.effective.limit !== "number") {
|
|
134
|
+
fail("plan.consolidation.effective.limit must be a number");
|
|
135
|
+
}
|
|
136
|
+
requireCount(value, "poolSize", "plan.consolidation");
|
|
137
|
+
requireCount(value, "candidatePoolSize", "plan.consolidation");
|
|
138
|
+
requireCount(value, "estimatedChunks", "plan.consolidation");
|
|
139
|
+
if (typeof value.wouldRun !== "boolean")
|
|
140
|
+
fail("plan.consolidation.wouldRun must be a boolean");
|
|
141
|
+
if (typeof value.reason !== "string")
|
|
142
|
+
fail("plan.consolidation.reason must be a string");
|
|
143
|
+
if (!isRecord(value.gates))
|
|
144
|
+
fail("plan.consolidation.gates must be an object");
|
|
145
|
+
requireExactFields(value.gates, new Set(["profile", "minimumPool", "delta"]));
|
|
146
|
+
for (const gateName of ["profile", "minimumPool", "delta"]) {
|
|
147
|
+
const gate = value.gates[gateName];
|
|
148
|
+
if (!isRecord(gate))
|
|
149
|
+
fail(`plan.consolidation.gates.${gateName} must be an object`);
|
|
150
|
+
requireExactFields(gate, new Set(["passed", "reason"]));
|
|
151
|
+
if (typeof gate.passed !== "boolean" || typeof gate.reason !== "string") {
|
|
152
|
+
fail(`plan.consolidation.gates.${gateName} must contain boolean passed and string reason`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function validateProactivePlan(value) {
|
|
157
|
+
if (!isRecord(value))
|
|
158
|
+
fail("plan.proactive must be an object");
|
|
159
|
+
requireExactFields(value, new Set(["configured", "effective", "candidatePool", "dueTotal", "neverReflected", "selected", "selectedRefs"]));
|
|
160
|
+
if (!isRecord(value.configured))
|
|
161
|
+
fail("plan.proactive.configured must be an object");
|
|
162
|
+
requireExactFields(value.configured, new Set(["dueDays", "maxPerRun", "limit"]));
|
|
163
|
+
for (const field of ["dueDays", "maxPerRun", "limit"]) {
|
|
164
|
+
if (value.configured[field] !== undefined && typeof value.configured[field] !== "number") {
|
|
165
|
+
fail(`plan.proactive.configured.${field} must be a number`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (!isRecord(value.effective))
|
|
169
|
+
fail("plan.proactive.effective must be an object");
|
|
170
|
+
requireExactFields(value.effective, new Set(["dueDays", "maxPerRun"]));
|
|
171
|
+
requireNumber(value.effective, "dueDays", "plan.proactive.effective");
|
|
172
|
+
requireNumber(value.effective, "maxPerRun", "plan.proactive.effective");
|
|
173
|
+
for (const field of ["candidatePool", "dueTotal", "neverReflected", "selected"]) {
|
|
174
|
+
requireCount(value, field, "plan.proactive");
|
|
175
|
+
}
|
|
176
|
+
if (!Array.isArray(value.selectedRefs) || value.selectedRefs.some((ref) => typeof ref !== "string")) {
|
|
177
|
+
fail("plan.proactive.selectedRefs must be an array of strings");
|
|
178
|
+
}
|
|
179
|
+
if (value.selected !== value.selectedRefs.length) {
|
|
180
|
+
fail("plan.proactive.selected must equal plan.proactive.selectedRefs.length");
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function validateImprovePlan(value, dryRun, plannedRefNames) {
|
|
184
|
+
if (!isRecord(value))
|
|
185
|
+
fail("plan must be an object");
|
|
186
|
+
requireExactFields(value, new Set([
|
|
187
|
+
"mode",
|
|
188
|
+
"dispatch",
|
|
189
|
+
"snapshot",
|
|
190
|
+
"candidates",
|
|
191
|
+
"limits",
|
|
192
|
+
"gates",
|
|
193
|
+
"effectiveRefs",
|
|
194
|
+
"proactive",
|
|
195
|
+
"consolidation",
|
|
196
|
+
"stages",
|
|
197
|
+
"triage",
|
|
198
|
+
]));
|
|
199
|
+
if (value.mode !== "estimate" && value.mode !== "execution")
|
|
200
|
+
fail('plan.mode must be "estimate" or "execution"');
|
|
201
|
+
if (typeof value.dispatch !== "boolean")
|
|
202
|
+
fail("plan.dispatch must be a boolean");
|
|
203
|
+
if ((value.mode === "estimate" && value.dispatch) || (value.mode === "execution" && !value.dispatch)) {
|
|
204
|
+
fail("plan.dispatch must be false for estimate mode and true for execution mode");
|
|
205
|
+
}
|
|
206
|
+
if (dryRun && (value.mode !== "estimate" || value.dispatch !== false)) {
|
|
207
|
+
fail("dryRun=true requires estimate mode with dispatch=false");
|
|
208
|
+
}
|
|
209
|
+
if (!dryRun && (value.mode !== "execution" || value.dispatch !== true)) {
|
|
210
|
+
fail("dryRun=false requires execution mode with dispatch=true");
|
|
211
|
+
}
|
|
212
|
+
if (!isRecord(value.snapshot))
|
|
213
|
+
fail("plan.snapshot must be an object");
|
|
214
|
+
requireExactFields(value.snapshot, new Set(["status", "reason"]));
|
|
215
|
+
if (typeof value.snapshot.status !== "string" ||
|
|
216
|
+
!new Set(["ready", "missing", "incompatible", "unknown"]).has(value.snapshot.status)) {
|
|
217
|
+
fail("plan.snapshot.status is invalid");
|
|
218
|
+
}
|
|
219
|
+
if (typeof value.snapshot.reason !== "string")
|
|
220
|
+
fail("plan.snapshot.reason must be a string");
|
|
221
|
+
if (!isRecord(value.candidates))
|
|
222
|
+
fail("plan.candidates must be an object");
|
|
223
|
+
requireExactFields(value.candidates, new Set(["rawInScope", "selected", "effective"]));
|
|
224
|
+
for (const field of ["rawInScope", "selected", "effective"]) {
|
|
225
|
+
requireCount(value.candidates, field, "plan.candidates");
|
|
226
|
+
}
|
|
227
|
+
if (value.candidates.rawInScope < value.candidates.selected) {
|
|
228
|
+
fail("plan.candidates.rawInScope cannot be less than plan.candidates.selected");
|
|
229
|
+
}
|
|
230
|
+
if (value.candidates.selected < value.candidates.effective) {
|
|
231
|
+
fail("plan.candidates.selected cannot be less than plan.candidates.effective");
|
|
232
|
+
}
|
|
233
|
+
if (!isRecord(value.limits))
|
|
234
|
+
fail("plan.limits must be an object");
|
|
235
|
+
requireExactFields(value.limits, new Set(["configured", "effective", "additiveReplayAllowance", "totalCeiling"]));
|
|
236
|
+
if (!isRecord(value.limits.configured))
|
|
237
|
+
fail("plan.limits.configured must be an object");
|
|
238
|
+
requireExactFields(value.limits.configured, new Set(["cli", "profile", "reflect"]));
|
|
239
|
+
for (const field of ["cli", "profile", "reflect"]) {
|
|
240
|
+
if (value.limits.configured[field] !== undefined)
|
|
241
|
+
requireCount(value.limits.configured, field, "plan.limits.configured");
|
|
242
|
+
}
|
|
243
|
+
if (value.limits.effective !== undefined)
|
|
244
|
+
requireCount(value.limits, "effective", "plan.limits");
|
|
245
|
+
requireCount(value.limits, "additiveReplayAllowance", "plan.limits");
|
|
246
|
+
if (value.limits.totalCeiling !== undefined)
|
|
247
|
+
requireCount(value.limits, "totalCeiling", "plan.limits");
|
|
248
|
+
if (value.limits.effective === undefined && value.limits.totalCeiling !== undefined) {
|
|
249
|
+
fail("plan.limits.totalCeiling must be omitted when plan.limits.effective is unbounded");
|
|
250
|
+
}
|
|
251
|
+
if (value.limits.effective !== undefined &&
|
|
252
|
+
value.limits.totalCeiling !== value.limits.effective + value.limits.additiveReplayAllowance) {
|
|
253
|
+
fail("plan.limits.totalCeiling must equal plan.limits.effective + plan.limits.additiveReplayAllowance");
|
|
254
|
+
}
|
|
255
|
+
const gateNames = new Set(["profile", "cleanup", "validation", "signal", "disk", "limit"]);
|
|
256
|
+
if (!Array.isArray(value.gates))
|
|
257
|
+
fail("plan.gates must be an array");
|
|
258
|
+
const gateRemovedByName = new Map();
|
|
259
|
+
for (const gate of value.gates) {
|
|
260
|
+
if (!isRecord(gate))
|
|
261
|
+
fail("plan.gates entries must be objects");
|
|
262
|
+
requireExactFields(gate, new Set(["name", "removed", "reason"]));
|
|
263
|
+
if (typeof gate.name !== "string" || !gateNames.has(gate.name))
|
|
264
|
+
fail("plan.gates.name is invalid");
|
|
265
|
+
requireCount(gate, "removed", "plan.gates entry");
|
|
266
|
+
if (typeof gate.reason !== "string")
|
|
267
|
+
fail("plan.gates.reason must be a string");
|
|
268
|
+
if (gateRemovedByName.has(gate.name))
|
|
269
|
+
fail(`plan.gates must contain exactly one ${gate.name} gate`);
|
|
270
|
+
gateRemovedByName.set(gate.name, gate.removed);
|
|
271
|
+
}
|
|
272
|
+
for (const gateName of gateNames) {
|
|
273
|
+
if (!gateRemovedByName.has(gateName))
|
|
274
|
+
fail(`plan.gates must contain exactly one ${gateName} gate`);
|
|
275
|
+
}
|
|
276
|
+
const preLimitRemoved = [...gateRemovedByName]
|
|
277
|
+
.filter(([gateName]) => gateName !== "limit")
|
|
278
|
+
.reduce((total, [, removed]) => total + removed, 0);
|
|
279
|
+
if (preLimitRemoved !== value.candidates.rawInScope - value.candidates.selected) {
|
|
280
|
+
fail("plan pre-limit gate removals must equal plan.candidates.rawInScope - plan.candidates.selected");
|
|
281
|
+
}
|
|
282
|
+
if (gateRemovedByName.get("limit") !==
|
|
283
|
+
value.candidates.selected - value.candidates.effective) {
|
|
284
|
+
fail("plan limit removal must equal plan.candidates.selected - plan.candidates.effective");
|
|
285
|
+
}
|
|
286
|
+
const lanes = new Set([
|
|
287
|
+
"scope",
|
|
288
|
+
"signal-delta",
|
|
289
|
+
"proactive",
|
|
290
|
+
"high-salience",
|
|
291
|
+
"distill-only",
|
|
292
|
+
"forgetting-safety",
|
|
293
|
+
"replay",
|
|
294
|
+
"unknown",
|
|
295
|
+
]);
|
|
296
|
+
const reasons = new Set(["scope-ref", "scope-type", "memory-cleanup", "strategy_filtered_all_passes"]);
|
|
297
|
+
if (!Array.isArray(value.effectiveRefs))
|
|
298
|
+
fail("plan.effectiveRefs must be an array");
|
|
299
|
+
for (const entry of value.effectiveRefs) {
|
|
300
|
+
if (!isRecord(entry))
|
|
301
|
+
fail("plan.effectiveRefs entries must be objects");
|
|
302
|
+
requireExactFields(entry, new Set(["ref", "lane", "reason"]));
|
|
303
|
+
if (typeof entry.ref !== "string")
|
|
304
|
+
fail("plan.effectiveRefs.ref must be a string");
|
|
305
|
+
if (typeof entry.lane !== "string" || !lanes.has(entry.lane))
|
|
306
|
+
fail("plan.effectiveRefs.lane is invalid");
|
|
307
|
+
if (typeof entry.reason !== "string" || !reasons.has(entry.reason))
|
|
308
|
+
fail("plan.effectiveRefs.reason is invalid");
|
|
309
|
+
}
|
|
310
|
+
if (value.candidates.effective !== value.effectiveRefs.length) {
|
|
311
|
+
fail("plan.candidates.effective must equal plan.effectiveRefs.length");
|
|
312
|
+
}
|
|
313
|
+
const effectiveRefNames = value.effectiveRefs.map((entry) => entry.ref);
|
|
314
|
+
if (new Set(effectiveRefNames).size !== effectiveRefNames.length) {
|
|
315
|
+
fail("plan.effectiveRefs must not contain duplicate refs");
|
|
316
|
+
}
|
|
317
|
+
if (plannedRefNames.length !== effectiveRefNames.length ||
|
|
318
|
+
plannedRefNames.some((ref, index) => ref !== effectiveRefNames[index])) {
|
|
319
|
+
fail("plannedRefs must contain the same refs in the same order as plan.effectiveRefs");
|
|
320
|
+
}
|
|
321
|
+
const replayCount = value.effectiveRefs.filter((entry) => entry.lane === "replay").length;
|
|
322
|
+
const ordinaryCount = value.effectiveRefs.length - replayCount;
|
|
323
|
+
if (replayCount > value.limits.additiveReplayAllowance) {
|
|
324
|
+
fail("plan replay refs cannot exceed plan.limits.additiveReplayAllowance");
|
|
325
|
+
}
|
|
326
|
+
if (value.limits.effective !== undefined && ordinaryCount > value.limits.effective) {
|
|
327
|
+
fail("plan ordinary refs cannot exceed plan.limits.effective");
|
|
328
|
+
}
|
|
329
|
+
if (value.limits.totalCeiling !== undefined && value.effectiveRefs.length > value.limits.totalCeiling) {
|
|
330
|
+
fail("plan.effectiveRefs cannot exceed plan.limits.totalCeiling");
|
|
331
|
+
}
|
|
332
|
+
if (value.proactive !== undefined)
|
|
333
|
+
validateProactivePlan(value.proactive);
|
|
334
|
+
validateConsolidationPlan(value.consolidation);
|
|
335
|
+
const stageNames = new Set(["consolidation", "extract", "graph-extraction", "memory-inference"]);
|
|
336
|
+
if (!Array.isArray(value.stages))
|
|
337
|
+
fail("plan.stages must be an array");
|
|
338
|
+
for (const stage of value.stages) {
|
|
339
|
+
if (!isRecord(stage))
|
|
340
|
+
fail("plan.stages entries must be objects");
|
|
341
|
+
requireExactFields(stage, new Set(["name", "wouldRun", "reason"]));
|
|
342
|
+
if (typeof stage.name !== "string" || !stageNames.has(stage.name))
|
|
343
|
+
fail("plan.stages.name is invalid");
|
|
344
|
+
if (typeof stage.wouldRun !== "boolean" || typeof stage.reason !== "string") {
|
|
345
|
+
fail("plan.stages entries must contain boolean wouldRun and string reason");
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (!isRecord(value.triage))
|
|
349
|
+
fail("plan.triage must be an object");
|
|
350
|
+
requireExactFields(value.triage, new Set(["enabled", "configuredMode", "mode", "maxAcceptsPerRun", "maxDiffLines"]));
|
|
351
|
+
if (typeof value.triage.enabled !== "boolean")
|
|
352
|
+
fail("plan.triage.enabled must be a boolean");
|
|
353
|
+
for (const field of ["configuredMode", "mode"]) {
|
|
354
|
+
if (value.triage[field] !== "queue" && value.triage[field] !== "promote") {
|
|
355
|
+
fail(`plan.triage.${field} must be queue or promote`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
requireCount(value.triage, "maxAcceptsPerRun", "plan.triage");
|
|
359
|
+
if (value.triage.maxDiffLines !== undefined && typeof value.triage.maxDiffLines !== "number") {
|
|
360
|
+
fail("plan.triage.maxDiffLines must be a number");
|
|
361
|
+
}
|
|
362
|
+
}
|
|
58
363
|
function validateCommon(value) {
|
|
59
364
|
if (typeof value.ok !== "boolean")
|
|
60
365
|
fail("ok must be a boolean");
|
|
@@ -62,6 +367,15 @@ function validateCommon(value) {
|
|
|
62
367
|
fail("dryRun must be a boolean");
|
|
63
368
|
if (!Array.isArray(value.plannedRefs))
|
|
64
369
|
fail("plannedRefs must be an array");
|
|
370
|
+
if (value.notices !== undefined)
|
|
371
|
+
validateLoweringNotices(value.notices);
|
|
372
|
+
const plannedRefNames = value.plannedRefs.map((entry, index) => {
|
|
373
|
+
if (!isRecord(entry) || typeof entry.ref !== "string")
|
|
374
|
+
fail(`plannedRefs[${index}].ref must be a string`);
|
|
375
|
+
return entry.ref;
|
|
376
|
+
});
|
|
377
|
+
if (value.plan !== undefined)
|
|
378
|
+
validateImprovePlan(value.plan, value.dryRun, plannedRefNames);
|
|
65
379
|
if (!isRecord(value.scope))
|
|
66
380
|
fail("scope must be an object");
|
|
67
381
|
requireExactFields(value.scope, new Set(["mode", "value"]));
|
|
@@ -127,6 +441,7 @@ function validateCommon(value) {
|
|
|
127
441
|
"cycleMetrics",
|
|
128
442
|
"sync",
|
|
129
443
|
"terminated",
|
|
444
|
+
"plan",
|
|
130
445
|
]) {
|
|
131
446
|
if (value[field] !== undefined && !isRecord(value[field]))
|
|
132
447
|
fail(`${field} must be an object`);
|
package/dist/core/lesson-lint.js
CHANGED
|
@@ -20,7 +20,6 @@
|
|
|
20
20
|
* lets it run from any code path (CLI, proposal-accept, asset-spec tests)
|
|
21
21
|
* without dragging in the rest of the runtime.
|
|
22
22
|
*/
|
|
23
|
-
import fs from "node:fs";
|
|
24
23
|
import { parseFrontmatter } from "./asset/frontmatter.js";
|
|
25
24
|
function isNonEmptyString(value) {
|
|
26
25
|
return typeof value === "string" && value.trim().length > 0;
|
|
@@ -66,8 +65,3 @@ export function lintLessonContent(raw, pathForMessages) {
|
|
|
66
65
|
}
|
|
67
66
|
return { path: pathForMessages, findings };
|
|
68
67
|
}
|
|
69
|
-
/** Lint a lesson file on disk. Throws if the file cannot be read. */
|
|
70
|
-
export function lintLessonFile(filePath) {
|
|
71
|
-
const raw = fs.readFileSync(filePath, "utf8");
|
|
72
|
-
return lintLessonContent(raw, filePath);
|
|
73
|
-
}
|
|
@@ -10,9 +10,9 @@ import { createLockPayload, probeLock, reclaimStaleLock, releaseLock, tryAcquire
|
|
|
10
10
|
import { getMaintenanceBarrierPath } from "./paths.js";
|
|
11
11
|
const heldBarrierContext = new AsyncLocalStorage();
|
|
12
12
|
/**
|
|
13
|
-
* Serialize
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* Serialize the short critical section that creates each long-lived AKM lock,
|
|
14
|
+
* lease, or state activity. The operation keeps its own ownership record; this
|
|
15
|
+
* barrier is released immediately after acquisition.
|
|
16
16
|
*/
|
|
17
17
|
export function tryAcquireMaintenanceBarrier() {
|
|
18
18
|
const lockPath = getMaintenanceBarrierPath();
|
|
@@ -107,7 +107,7 @@ function withMaintenanceStartBarrierSyncWait(run) {
|
|
|
107
107
|
release();
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
|
-
/** Register a long-lived operation atomically with
|
|
110
|
+
/** Register a long-lived state operation atomically with other start acquisitions. */
|
|
111
111
|
export async function acquireMaintenanceActivity(name) {
|
|
112
112
|
return withMaintenanceStartBarrierAsync(async () => {
|
|
113
113
|
const directory = path.join(path.dirname(getMaintenanceBarrierPath()), "maintenance-activities");
|