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
|
@@ -9,8 +9,8 @@ import { VALID_HARNESS_IDS } from "../../integrations/harnesses/index.js";
|
|
|
9
9
|
/**
|
|
10
10
|
* Canonical list of valid agent harness / platform ids. Re-exported from the
|
|
11
11
|
* unified harness registry (#562) so the Zod `AgentPlatformSchema` enum, the
|
|
12
|
-
* `
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* `AgentProfileConfig` platform union, and setup's `DetectedHarness` union all
|
|
13
|
+
* derive from one place and cannot drift. Add a harness in
|
|
14
|
+
* `src/integrations/harnesses/index.ts`.
|
|
15
15
|
*/
|
|
16
16
|
export { VALID_HARNESS_IDS };
|
|
@@ -13,20 +13,9 @@ import { warn } from "../warn.js";
|
|
|
13
13
|
// Canonical harness-id source of truth (#565) — runtime value re-export.
|
|
14
14
|
export { VALID_HARNESS_IDS } from "./config-types.js";
|
|
15
15
|
// ── Feedback failure-mode constants (F-3 / #384) ────────────────────────────
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
* Structured failure modes enable aggregation across feedback events so the
|
|
20
|
-
* distill pipeline can detect that "5 assets failed for the same reason" and
|
|
21
|
-
* act on it — free-text strings about the same issue are not aggregatable.
|
|
22
|
-
*/
|
|
23
|
-
export const FEEDBACK_FAILURE_MODES = [
|
|
24
|
-
"incorrect", // Factually wrong or logically flawed content
|
|
25
|
-
"outdated", // Correct at some point but now stale
|
|
26
|
-
"dangerous", // Could cause harm if followed (security, safety)
|
|
27
|
-
"incomplete", // Missing key steps, context, or caveats
|
|
28
|
-
"redundant", // Duplicates another asset without adding value
|
|
29
|
-
];
|
|
16
|
+
// Canonical taxonomy lives in the schema/validator layer; re-exported here so
|
|
17
|
+
// existing `../core/config/config` import sites keep working.
|
|
18
|
+
export { FEEDBACK_FAILURE_MODES } from "./config-schema.js";
|
|
30
19
|
/**
|
|
31
20
|
* Default value for {@link IndexPassConfig.graphExtractionBatchSize}. Chosen
|
|
32
21
|
* empirically: 4 amortises the per-call HTTP overhead 4× while keeping the
|
|
@@ -205,6 +194,25 @@ export function getDefaultLlmConfig(config) {
|
|
|
205
194
|
return undefined;
|
|
206
195
|
return config.profiles?.llm?.[defaultName];
|
|
207
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Resolve the per-process config section for an improve process,
|
|
199
|
+
* centralizing the deeply-nested lookup
|
|
200
|
+
* `profile?.processes?.<name>` that was previously copy-pasted across the
|
|
201
|
+
* improve command family (20+ call sites).
|
|
202
|
+
*
|
|
203
|
+
* When an `activeProfile` is supplied (the profile resolved for the current
|
|
204
|
+
* `akm improve --profile <name>` run), its per-process override wins; otherwise
|
|
205
|
+
* — and as a fallback when the active profile does not define the section — the
|
|
206
|
+
* lookup falls back to the `"default"` improve profile from the on-disk config.
|
|
207
|
+
* Callers that have not yet threaded the active profile pass only `config` and
|
|
208
|
+
* get the historical default-profile behavior unchanged.
|
|
209
|
+
*/
|
|
210
|
+
export function getImproveProcessConfig(config, processName, activeProfile) {
|
|
211
|
+
const fromActiveProfile = activeProfile?.processes?.[processName];
|
|
212
|
+
if (fromActiveProfile !== undefined)
|
|
213
|
+
return fromActiveProfile;
|
|
214
|
+
return config.profiles?.improve?.default?.processes?.[processName];
|
|
215
|
+
}
|
|
208
216
|
/**
|
|
209
217
|
* Run `migrateConfigShape` on the raw text and — unless `AKM_NO_AUTO_MIGRATE=1`
|
|
210
218
|
* is set — persist the migrated result. Returns the (possibly migrated) text
|
|
@@ -221,7 +229,18 @@ function maybeAutoMigrateConfigFile(configPath, text) {
|
|
|
221
229
|
catch {
|
|
222
230
|
return text; // Malformed JSON — let parseAndValidate surface the error.
|
|
223
231
|
}
|
|
224
|
-
|
|
232
|
+
// Downgrade protection. Skip migration when the on-disk config is NEWER than
|
|
233
|
+
// this binary (=== 1), OR when it carries a configVersion we cannot order
|
|
234
|
+
// against ours (compareConfigVersion returns undefined for an unparseable
|
|
235
|
+
// value — e.g. one written by a newer/foreign akm). Migrating such a config
|
|
236
|
+
// could strip fields a newer binary added, a cross-version data-loss path.
|
|
237
|
+
//
|
|
238
|
+
// A MISSING configVersion (absent → undefined) is a legacy pre-versioning
|
|
239
|
+
// config that MUST still migrate, so the unparseable-skip is gated on the
|
|
240
|
+
// field being PRESENT (`onDiskVersion !== undefined`).
|
|
241
|
+
const onDiskVersion = obj.configVersion;
|
|
242
|
+
const versionOrder = compareConfigVersion(onDiskVersion, CURRENT_CONFIG_VERSION);
|
|
243
|
+
if (versionOrder === 1 || (onDiskVersion !== undefined && versionOrder === undefined)) {
|
|
225
244
|
return text;
|
|
226
245
|
}
|
|
227
246
|
const { changed, result } = migrateConfigShape(obj);
|
|
@@ -249,8 +268,8 @@ function maybeAutoMigrateConfigFile(configPath, text) {
|
|
|
249
268
|
" to preview a dry-run diff: akm config migrate --dry-run --print-diff",
|
|
250
269
|
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
|
|
251
270
|
].join("\n");
|
|
252
|
-
process.stderr
|
|
253
|
-
process.stdout
|
|
271
|
+
process.stderr?.write?.(`${banner}\n`);
|
|
272
|
+
process.stdout?.write?.(`${banner}\n`);
|
|
254
273
|
}
|
|
255
274
|
catch (err) {
|
|
256
275
|
// #461: never return migrated bytes when disk write fails — that triggers
|
|
@@ -267,15 +286,32 @@ export function loadConfig() {
|
|
|
267
286
|
warnIfProjectConfigPresent(process.cwd());
|
|
268
287
|
return loadUserConfig();
|
|
269
288
|
}
|
|
289
|
+
let saveConfigOverride;
|
|
290
|
+
/** TEST-ONLY. Swap the implementation of `saveConfig`; pass undefined to restore. */
|
|
291
|
+
export function _setSaveConfigForTests(fake) {
|
|
292
|
+
saveConfigOverride = fake;
|
|
293
|
+
}
|
|
270
294
|
export function saveConfig(config) {
|
|
295
|
+
if (saveConfigOverride) {
|
|
296
|
+
saveConfigOverride(config);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
saveConfigReal(config);
|
|
300
|
+
}
|
|
301
|
+
function saveConfigReal(config) {
|
|
271
302
|
cachedConfig = undefined;
|
|
272
303
|
const configPath = getConfigPath();
|
|
273
304
|
const dir = path.dirname(configPath);
|
|
274
305
|
fs.mkdirSync(dir, { recursive: true });
|
|
275
306
|
const sanitized = sanitizeConfigForWrite(config);
|
|
276
|
-
// Final validation gate before bytes hit disk.
|
|
277
|
-
//
|
|
278
|
-
//
|
|
307
|
+
// Final validation gate before bytes hit disk. Runs the FULL schema —
|
|
308
|
+
// including the cross-field superRefine guards (removed `feedbackDistillation`
|
|
309
|
+
// process key, `defaultWriteTarget` resolution, writable npm/website sources)
|
|
310
|
+
// and all type/enum/range checks — so an `akm config set` (leaf OR object
|
|
311
|
+
// form) cannot persist a guard-violating or mistyped value. NOTE: unknown
|
|
312
|
+
// keys are intentionally NOT rejected here — object schemas are `.passthrough()`
|
|
313
|
+
// so cross-version skew round-trips (see config-schema.ts header); the lenient
|
|
314
|
+
// tolerance is by design, not an oversight.
|
|
279
315
|
const parseResult = AkmConfigSchema.safeParse(sanitized);
|
|
280
316
|
if (!parseResult.success) {
|
|
281
317
|
const lines = parseResult.error.issues.map((i) => ` - ${i.path.join(".") || "(root)"}: ${i.message}`).join("\n");
|
|
@@ -398,7 +434,11 @@ export function resolveSecret(value) {
|
|
|
398
434
|
* filtering out the reserved feature-section keys so callers don't mistake
|
|
399
435
|
* `metadataEnhance` / `stalenessDetection` for a pass.
|
|
400
436
|
*/
|
|
401
|
-
/**
|
|
437
|
+
/**
|
|
438
|
+
* Reserved well-known keys on IndexConfig that are NOT per-pass entries.
|
|
439
|
+
* `stalenessDetection` is retired (10-Q3) but stays reserved so a leftover
|
|
440
|
+
* config section is never misread as a pass entry.
|
|
441
|
+
*/
|
|
402
442
|
const INDEX_RESERVED_KEYS = new Set(["metadataEnhance", "stalenessDetection"]);
|
|
403
443
|
export function getIndexPassConfig(config, passName) {
|
|
404
444
|
if (!config)
|
|
@@ -450,7 +490,12 @@ function applyRuntimeEnvApiKeys(config) {
|
|
|
450
490
|
}
|
|
451
491
|
// LLM profile keys: AKM_LLM_API_KEY for the default profile, then
|
|
452
492
|
// AKM_PROFILE_<UPPER>_API_KEY for any profile (per-profile wins).
|
|
453
|
-
|
|
493
|
+
// Resolve the default profile the SAME way the rest of the config layer does
|
|
494
|
+
// (resolveDefaultLlmProfileName), so the implicit `profiles.llm.default`
|
|
495
|
+
// fallback is honored. Keying off the raw `defaults.llm` field alone silently
|
|
496
|
+
// dropped AKM_LLM_API_KEY for configs that rely on the implicit default —
|
|
497
|
+
// the same no-op-run class as the 2026-05-23 incident.
|
|
498
|
+
const defaultProfile = resolveDefaultLlmProfileName(next);
|
|
454
499
|
if (next.profiles?.llm) {
|
|
455
500
|
const updated = { ...next.profiles.llm };
|
|
456
501
|
let changed = false;
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
* Generic recursive object merge, extracted from the setup wizard (it is not
|
|
6
|
+
* setup-specific). Plain objects merge key-by-key; arrays and scalars replace
|
|
7
|
+
* wholesale. Used to apply a partial `--file` config over the existing config
|
|
8
|
+
* without dropping sibling subkeys.
|
|
9
|
+
*/
|
|
10
|
+
/** True for non-null, non-array plain objects. */
|
|
11
|
+
export function isPlainObject(value) {
|
|
12
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Recursively merge `incoming` into `base`: plain objects merge key-by-key,
|
|
16
|
+
* while arrays and scalars replace wholesale. A partial input therefore only
|
|
17
|
+
* updates the keys it carries and never drops sibling subkeys (e.g. a file
|
|
18
|
+
* containing `{ output: { format: "text" } }` leaves `output.detail` intact).
|
|
19
|
+
*
|
|
20
|
+
* `base` is treated as immutable — a fresh object graph is returned.
|
|
21
|
+
*/
|
|
22
|
+
export function deepMergeConfig(base, incoming) {
|
|
23
|
+
if (!isPlainObject(incoming))
|
|
24
|
+
return incoming;
|
|
25
|
+
const baseObj = isPlainObject(base) ? base : {};
|
|
26
|
+
const out = { ...baseObj };
|
|
27
|
+
for (const [key, value] of Object.entries(incoming)) {
|
|
28
|
+
if (value === undefined)
|
|
29
|
+
continue;
|
|
30
|
+
if (isPlainObject(value) && isPlainObject(baseObj[key])) {
|
|
31
|
+
out[key] = deepMergeConfig(baseObj[key], value);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
out[key] = value;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
package/dist/core/errors.js
CHANGED
|
@@ -14,6 +14,7 @@ const CONFIG_HINTS = {
|
|
|
14
14
|
TEST_ISOLATION_MISSING: "Under bun test, when AKM_STASH_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.",
|
|
15
15
|
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_STASH_DIR so config and cache writes auto-isolate into $stashDir/.akm/ — host config is preserved).",
|
|
16
16
|
UNSAFE_STASH_DIR: "Choose a path inside your home directory (e.g. ~/akm) or another empty workspace. The stash directory cannot be the filesystem root, your home directory itself, or a sensitive system path like /etc, /var, ~/.config, or ~/.ssh.",
|
|
17
|
+
UNKNOWN_IMPROVE_PROFILE: "Pass one of the listed profile names to `--profile`, or define it under `profiles.improve` in your config. Names are case-sensitive.",
|
|
17
18
|
};
|
|
18
19
|
/** Default hint for each UsageError code. */
|
|
19
20
|
const USAGE_HINTS = {
|
|
@@ -0,0 +1,113 @@
|
|
|
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
|
+
export const DEFAULT_CURATE_WEIGHTS = {
|
|
5
|
+
ndcg: 0.5,
|
|
6
|
+
recall: 0.2,
|
|
7
|
+
mrr: 0.1,
|
|
8
|
+
noBannedAboveRequired: 0.2,
|
|
9
|
+
};
|
|
10
|
+
/** nDCG@k with binary relevance: gain 1 for relevant refs, 0 otherwise. */
|
|
11
|
+
export function ndcgAtK(returned, relevant, k) {
|
|
12
|
+
const top = returned.slice(0, k);
|
|
13
|
+
let dcg = 0;
|
|
14
|
+
for (let i = 0; i < top.length; i++) {
|
|
15
|
+
if (relevant.has(top[i]))
|
|
16
|
+
dcg += 1 / Math.log2(i + 2);
|
|
17
|
+
}
|
|
18
|
+
const idealCount = Math.min(k, relevant.size);
|
|
19
|
+
let idcg = 0;
|
|
20
|
+
for (let i = 0; i < idealCount; i++)
|
|
21
|
+
idcg += 1 / Math.log2(i + 2);
|
|
22
|
+
return idcg === 0 ? 1 : dcg / idcg;
|
|
23
|
+
}
|
|
24
|
+
export function recallAtK(returned, relevant, k) {
|
|
25
|
+
if (relevant.size === 0)
|
|
26
|
+
return 1;
|
|
27
|
+
const top = new Set(returned.slice(0, k));
|
|
28
|
+
let hit = 0;
|
|
29
|
+
for (const r of relevant)
|
|
30
|
+
if (top.has(r))
|
|
31
|
+
hit += 1;
|
|
32
|
+
return hit / relevant.size;
|
|
33
|
+
}
|
|
34
|
+
export function mrr(returned, relevant) {
|
|
35
|
+
for (let i = 0; i < returned.length; i++) {
|
|
36
|
+
if (relevant.has(returned[i]))
|
|
37
|
+
return 1 / (i + 1);
|
|
38
|
+
}
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Leapfrog gate. A banned ref "leapfrogs" when it appears ABOVE at least one
|
|
43
|
+
* present relevant ref. Returns the fraction of present banned refs that do
|
|
44
|
+
* NOT leapfrog (1.0 when no banned ref is present, or none leapfrog), plus the
|
|
45
|
+
* raw violation count.
|
|
46
|
+
*/
|
|
47
|
+
export function noBannedAboveRequired(returned, relevant, banned) {
|
|
48
|
+
const rankOf = new Map();
|
|
49
|
+
returned.forEach((ref, i) => {
|
|
50
|
+
if (!rankOf.has(ref))
|
|
51
|
+
rankOf.set(ref, i);
|
|
52
|
+
});
|
|
53
|
+
const relevantRanks = returned.map((ref, i) => (relevant.has(ref) ? i : -1)).filter((i) => i >= 0);
|
|
54
|
+
if (relevantRanks.length === 0) {
|
|
55
|
+
// No relevant ref present to be leapfrogged — gate is vacuously satisfied.
|
|
56
|
+
return { score: 1, leapfrogCount: 0 };
|
|
57
|
+
}
|
|
58
|
+
const worstRelevantRank = Math.max(...relevantRanks);
|
|
59
|
+
const bannedPresent = returned.filter((ref) => banned.has(ref));
|
|
60
|
+
if (bannedPresent.length === 0)
|
|
61
|
+
return { score: 1, leapfrogCount: 0 };
|
|
62
|
+
let leapfrog = 0;
|
|
63
|
+
for (const b of bannedPresent) {
|
|
64
|
+
const rb = rankOf.get(b);
|
|
65
|
+
if (rb !== undefined && rb < worstRelevantRank)
|
|
66
|
+
leapfrog += 1;
|
|
67
|
+
}
|
|
68
|
+
return { score: 1 - leapfrog / bannedPresent.length, leapfrogCount: leapfrog };
|
|
69
|
+
}
|
|
70
|
+
/** Score a single curate result (ordered refs) against its judgment. */
|
|
71
|
+
export function scoreCurateCase(returned, judgment, weights = DEFAULT_CURATE_WEIGHTS) {
|
|
72
|
+
const k = judgment.limit;
|
|
73
|
+
const relevant = new Set(judgment.relevant);
|
|
74
|
+
const banned = new Set(judgment.banned);
|
|
75
|
+
const ndcg = ndcgAtK(returned, relevant, k);
|
|
76
|
+
const recall = recallAtK(returned, relevant, k);
|
|
77
|
+
const rr = mrr(returned, relevant);
|
|
78
|
+
const gate = noBannedAboveRequired(returned, relevant, banned);
|
|
79
|
+
const score = ndcg * weights.ndcg + recall * weights.recall + rr * weights.mrr + gate.score * weights.noBannedAboveRequired;
|
|
80
|
+
return {
|
|
81
|
+
ndcg,
|
|
82
|
+
recall,
|
|
83
|
+
mrr: rr,
|
|
84
|
+
noBannedAboveRequired: gate.score,
|
|
85
|
+
bannedLeapfrogCount: gate.leapfrogCount,
|
|
86
|
+
score,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/** Aggregate per-case metrics into a suite summary. */
|
|
90
|
+
export function summarizeCurateMetrics(metrics) {
|
|
91
|
+
const n = metrics.length;
|
|
92
|
+
if (n === 0) {
|
|
93
|
+
return {
|
|
94
|
+
caseCount: 0,
|
|
95
|
+
meanScore: 0,
|
|
96
|
+
meanNdcg: 0,
|
|
97
|
+
meanRecall: 0,
|
|
98
|
+
meanMrr: 0,
|
|
99
|
+
meanNoBannedAboveRequired: 1,
|
|
100
|
+
totalBannedLeapfrog: 0,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const sum = (sel) => metrics.reduce((a, m) => a + sel(m), 0);
|
|
104
|
+
return {
|
|
105
|
+
caseCount: n,
|
|
106
|
+
meanScore: sum((m) => m.score) / n,
|
|
107
|
+
meanNdcg: sum((m) => m.ndcg) / n,
|
|
108
|
+
meanRecall: sum((m) => m.recall) / n,
|
|
109
|
+
meanMrr: sum((m) => m.mrr) / n,
|
|
110
|
+
meanNoBannedAboveRequired: sum((m) => m.noBannedAboveRequired) / n,
|
|
111
|
+
totalBannedLeapfrog: sum((m) => m.bannedLeapfrogCount),
|
|
112
|
+
};
|
|
113
|
+
}
|
package/dist/core/events.js
CHANGED
|
@@ -25,9 +25,10 @@
|
|
|
25
25
|
* - `ts` is ISO-8601 (UTC, millisecond precision).
|
|
26
26
|
*/
|
|
27
27
|
import path from "node:path";
|
|
28
|
+
import { insertEvent, readStateEvents } from "../storage/repositories/events-repository.js";
|
|
28
29
|
import { rethrowIfTestIsolationError } from "./errors.js";
|
|
29
30
|
import { getDataDir } from "./paths.js";
|
|
30
|
-
import {
|
|
31
|
+
import { openStateDatabase, withStateDb } from "./state-db.js";
|
|
31
32
|
import { error } from "./warn.js";
|
|
32
33
|
/**
|
|
33
34
|
* Legacy events.jsonl path — used only by the migration script
|
|
@@ -82,18 +83,14 @@ export function appendEvent(input, ctx) {
|
|
|
82
83
|
// Default path: open, insert, close.
|
|
83
84
|
const dbPath = resolveDbPath(ctx);
|
|
84
85
|
try {
|
|
85
|
-
|
|
86
|
-
try {
|
|
86
|
+
withStateDb((db) => {
|
|
87
87
|
insertEvent(db, {
|
|
88
88
|
eventType: input.eventType,
|
|
89
89
|
ts,
|
|
90
90
|
ref: input.ref,
|
|
91
91
|
metadata: input.metadata,
|
|
92
92
|
});
|
|
93
|
-
}
|
|
94
|
-
finally {
|
|
95
|
-
db.close();
|
|
96
|
-
}
|
|
93
|
+
}, { path: dbPath });
|
|
97
94
|
}
|
|
98
95
|
catch (err) {
|
|
99
96
|
// Never mask the bun-test isolation guard as a silent "events failed".
|
|
@@ -11,16 +11,18 @@ import { assertNever } from "./assert.js";
|
|
|
11
11
|
*
|
|
12
12
|
* Buckets:
|
|
13
13
|
* - `accepted` — a write/content-authoring action succeeded.
|
|
14
|
-
* - `
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
14
|
+
* - `skipped` — the ref was GATED OUT before any content was produced: a
|
|
15
|
+
* cooldown, a signal-delta/eligibility skip, or a distill pool-delta skip.
|
|
16
|
+
* These are NOT rejections of produced content — they are the run declining
|
|
17
|
+
* to act on a ref it had no new reason to touch, and they scale with the
|
|
18
|
+
* whole indexed-ref pool (~13k/run), so folding them into `rejected` made the
|
|
19
|
+
* "accept rate" meaningless (deep-tuning analysis 2026-06-29, finding #1).
|
|
20
|
+
* - `rejected` — the run PRODUCED a change and a content-policy guard then
|
|
21
|
+
* rejected it (`reflect-guard-rejected`). This is the genuine value-rejection
|
|
22
|
+
* signal; it is small and meaningful, no longer drowned by gated skips.
|
|
21
23
|
* - `error` — the action failed (LLM/runtime error).
|
|
22
24
|
* - `noop` — bookkeeping that is neither a write nor a rejection (memory-prune);
|
|
23
|
-
* intentionally counted in none of the
|
|
25
|
+
* intentionally counted in none of the numeric buckets.
|
|
24
26
|
*
|
|
25
27
|
* The `default: assertNever(mode)` arm makes any future union variant a
|
|
26
28
|
* compile-time error here, forcing an explicit bucket choice.
|
|
@@ -35,6 +37,7 @@ export function classifyImproveAction(mode) {
|
|
|
35
37
|
case "reflect-cooldown":
|
|
36
38
|
case "reflect-skipped":
|
|
37
39
|
case "distill-skipped":
|
|
40
|
+
return "skipped";
|
|
38
41
|
case "reflect-guard-rejected":
|
|
39
42
|
return "rejected";
|
|
40
43
|
case "reflect-failed":
|
|
@@ -46,3 +49,39 @@ export function classifyImproveAction(mode) {
|
|
|
46
49
|
return assertNever(mode);
|
|
47
50
|
}
|
|
48
51
|
}
|
|
52
|
+
/** Upper bound on retained sample refs PER reason in {@link DistillSkippedAggregate}. */
|
|
53
|
+
export const DISTILL_SKIPPED_SAMPLE_CAP_PER_REASON = 3;
|
|
54
|
+
/**
|
|
55
|
+
* Partition an action list into the rows to persist and the `distill-skipped`
|
|
56
|
+
* aggregate. Pure — no I/O. Called once at improve-result assembly time so the
|
|
57
|
+
* serialized envelope never carries per-ref distill-skipped rows.
|
|
58
|
+
*
|
|
59
|
+
* Non-`distill-skipped` actions are returned verbatim and in order. When there
|
|
60
|
+
* are zero distill-skipped actions the aggregate is omitted (the envelope stays
|
|
61
|
+
* byte-identical to a run that skipped nothing).
|
|
62
|
+
*/
|
|
63
|
+
export function foldDistillSkipped(actions) {
|
|
64
|
+
const kept = [];
|
|
65
|
+
const byReason = {};
|
|
66
|
+
const samples = [];
|
|
67
|
+
const sampleCountByReason = {};
|
|
68
|
+
let total = 0;
|
|
69
|
+
for (const action of actions) {
|
|
70
|
+
if (action.mode !== "distill-skipped") {
|
|
71
|
+
kept.push(action);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
total += 1;
|
|
75
|
+
const r = action.result;
|
|
76
|
+
const reason = typeof r?.reason === "string" && r.reason.trim() ? r.reason : "unknown";
|
|
77
|
+
byReason[reason] = (byReason[reason] ?? 0) + 1;
|
|
78
|
+
const seen = sampleCountByReason[reason] ?? 0;
|
|
79
|
+
if (seen < DISTILL_SKIPPED_SAMPLE_CAP_PER_REASON) {
|
|
80
|
+
sampleCountByReason[reason] = seen + 1;
|
|
81
|
+
samples.push({ ref: action.ref, reason });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (total === 0)
|
|
85
|
+
return { actions: kept };
|
|
86
|
+
return { actions: kept, aggregate: { total, byReason, samples } };
|
|
87
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
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
|
+
* Structural JSON-Schema-subset validator (orchestration plan P1).
|
|
6
|
+
*
|
|
7
|
+
* The workflow engine's structured-output normalization needs to validate
|
|
8
|
+
* unit results against the author-declared unit `output` schema on any harness —
|
|
9
|
+
* including ones with no native schema support. Pulling in a full
|
|
10
|
+
* draft-2020-12 validator is deliberately avoided (dependency surface); this
|
|
11
|
+
* module implements the bounded subset that covers the schemas workflow
|
|
12
|
+
* authors actually write:
|
|
13
|
+
*
|
|
14
|
+
* Supported: `type` (string | string[] — string, number, integer, boolean,
|
|
15
|
+
* object, array, null), `properties`, `required`, `items`,
|
|
16
|
+
* `additionalProperties: false`, `enum` (primitives), `minItems`,
|
|
17
|
+
* `maxItems`, `minLength`, `maxLength`, `minimum`, `maximum`.
|
|
18
|
+
*
|
|
19
|
+
* Ignored (permissive): `$ref`, `allOf`/`anyOf`/`oneOf`/`not`, `pattern`,
|
|
20
|
+
* `format`, and every other keyword. Unknown keywords never throw — a
|
|
21
|
+
* schema using them simply constrains less. Callers needing full JSON
|
|
22
|
+
* Schema semantics should validate downstream.
|
|
23
|
+
*
|
|
24
|
+
* Returns a flat list of human-readable error strings (empty = valid), each
|
|
25
|
+
* prefixed with a JSON-pointer-ish path — the shape `runStructured`'s
|
|
26
|
+
* corrective-feedback builder wants.
|
|
27
|
+
*/
|
|
28
|
+
export function validateJsonSchemaSubset(value, schema) {
|
|
29
|
+
const errors = [];
|
|
30
|
+
validateNode(value, schema, "$", errors);
|
|
31
|
+
return errors;
|
|
32
|
+
}
|
|
33
|
+
function typeOf(value) {
|
|
34
|
+
if (value === null)
|
|
35
|
+
return "null";
|
|
36
|
+
if (Array.isArray(value))
|
|
37
|
+
return "array";
|
|
38
|
+
switch (typeof value) {
|
|
39
|
+
case "string":
|
|
40
|
+
return "string";
|
|
41
|
+
case "boolean":
|
|
42
|
+
return "boolean";
|
|
43
|
+
case "number":
|
|
44
|
+
return Number.isInteger(value) ? "integer" : "number";
|
|
45
|
+
default:
|
|
46
|
+
return "object";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function matchesType(actual, expected) {
|
|
50
|
+
if (expected === actual)
|
|
51
|
+
return true;
|
|
52
|
+
// JSON Schema: every integer is also a number.
|
|
53
|
+
return expected === "number" && actual === "integer";
|
|
54
|
+
}
|
|
55
|
+
function validateNode(value, schema, path, errors) {
|
|
56
|
+
const actual = typeOf(value);
|
|
57
|
+
const declared = schema.type;
|
|
58
|
+
if (typeof declared === "string" || Array.isArray(declared)) {
|
|
59
|
+
const expected = (Array.isArray(declared) ? declared : [declared]).filter((t) => typeof t === "string");
|
|
60
|
+
if (expected.length > 0 && !expected.some((t) => matchesType(actual, t))) {
|
|
61
|
+
errors.push(`${path}: expected type ${expected.join(" | ")}, got ${actual}`);
|
|
62
|
+
return; // type mismatch makes the remaining constraints meaningless
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (Array.isArray(schema.enum) && schema.enum.length > 0) {
|
|
66
|
+
const allowed = schema.enum;
|
|
67
|
+
if (!allowed.some((candidate) => candidate === value)) {
|
|
68
|
+
errors.push(`${path}: value ${JSON.stringify(value)} is not one of ${JSON.stringify(allowed)}`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (actual === "string" && typeof value === "string") {
|
|
73
|
+
if (typeof schema.minLength === "number" && value.length < schema.minLength) {
|
|
74
|
+
errors.push(`${path}: string shorter than minLength ${schema.minLength}`);
|
|
75
|
+
}
|
|
76
|
+
if (typeof schema.maxLength === "number" && value.length > schema.maxLength) {
|
|
77
|
+
errors.push(`${path}: string longer than maxLength ${schema.maxLength}`);
|
|
78
|
+
}
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if ((actual === "number" || actual === "integer") && typeof value === "number") {
|
|
82
|
+
if (typeof schema.minimum === "number" && value < schema.minimum) {
|
|
83
|
+
errors.push(`${path}: ${value} is below minimum ${schema.minimum}`);
|
|
84
|
+
}
|
|
85
|
+
if (typeof schema.maximum === "number" && value > schema.maximum) {
|
|
86
|
+
errors.push(`${path}: ${value} is above maximum ${schema.maximum}`);
|
|
87
|
+
}
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (actual === "array" && Array.isArray(value)) {
|
|
91
|
+
if (typeof schema.minItems === "number" && value.length < schema.minItems) {
|
|
92
|
+
errors.push(`${path}: array has fewer than minItems ${schema.minItems}`);
|
|
93
|
+
}
|
|
94
|
+
if (typeof schema.maxItems === "number" && value.length > schema.maxItems) {
|
|
95
|
+
errors.push(`${path}: array has more than maxItems ${schema.maxItems}`);
|
|
96
|
+
}
|
|
97
|
+
const items = schema.items;
|
|
98
|
+
if (items && typeof items === "object" && !Array.isArray(items)) {
|
|
99
|
+
value.forEach((element, index) => {
|
|
100
|
+
validateNode(element, items, `${path}[${index}]`, errors);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (actual === "object" && typeof value === "object" && value !== null) {
|
|
106
|
+
const record = value;
|
|
107
|
+
const properties = schema.properties && typeof schema.properties === "object" && !Array.isArray(schema.properties)
|
|
108
|
+
? schema.properties
|
|
109
|
+
: undefined;
|
|
110
|
+
if (Array.isArray(schema.required)) {
|
|
111
|
+
for (const key of schema.required) {
|
|
112
|
+
// `Object.hasOwn`, not `key in record`: a required key satisfied only by
|
|
113
|
+
// an inherited prototype member (e.g. "toString", "constructor") is NOT
|
|
114
|
+
// present on the value itself, so `{}` must fail `required: ["toString"]`.
|
|
115
|
+
if (typeof key === "string" && !Object.hasOwn(record, key)) {
|
|
116
|
+
errors.push(`${path}: missing required property "${key}"`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (properties) {
|
|
121
|
+
for (const [key, propSchema] of Object.entries(properties)) {
|
|
122
|
+
if (!Object.hasOwn(record, key))
|
|
123
|
+
continue;
|
|
124
|
+
if (propSchema && typeof propSchema === "object" && !Array.isArray(propSchema)) {
|
|
125
|
+
validateNode(record[key], propSchema, `${path}.${key}`, errors);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// `additionalProperties: false` closes the object to exactly its declared
|
|
130
|
+
// `properties`. This MUST run even when no `properties` object is present:
|
|
131
|
+
// `{ type: "object", additionalProperties: false }` admits only `{}`. Use
|
|
132
|
+
// `Object.hasOwn` so an inherited key name (e.g. "toString") on the empty
|
|
133
|
+
// property set is not mistaken for a declared property.
|
|
134
|
+
if (schema.additionalProperties === false) {
|
|
135
|
+
for (const key of Object.keys(record)) {
|
|
136
|
+
if (!properties || !Object.hasOwn(properties, key)) {
|
|
137
|
+
errors.push(`${path}: unexpected property "${key}" (additionalProperties: false)`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|