akm-cli 0.8.6 → 0.8.14
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 +442 -0
- package/dist/assets/help/help-proposals.md +1 -2
- package/dist/assets/hints/cli-hints-full.md +34 -19
- package/dist/assets/hints/cli-hints-short.md +1 -1
- package/dist/assets/profiles/catchup.json +13 -0
- package/dist/assets/profiles/consolidate.json +13 -0
- package/dist/assets/profiles/frequent.json +13 -0
- package/dist/assets/tasks/core/backup.yml +4 -0
- package/dist/assets/tasks/core/extract.yml +4 -0
- package/dist/assets/tasks/core/improve.yml +4 -0
- package/dist/assets/tasks/core/index-refresh.yml +4 -0
- package/dist/assets/tasks/core/sync.yml +4 -0
- package/dist/assets/tasks/core/update-stashes.yml +4 -0
- package/dist/assets/tasks/core/version-check.yml +4 -0
- package/dist/assets/templates/html/default.html +78 -0
- package/dist/assets/templates/html/health.html +560 -0
- package/dist/assets/templates/html/vendor/echarts.min.js +45 -0
- package/dist/cli/config-migrate.js +6 -6
- package/dist/cli/config-validate.js +4 -4
- package/dist/cli/confirm.js +3 -3
- package/dist/cli/parse-args.js +1 -1
- package/dist/cli/shared.js +72 -19
- package/dist/cli-node.mjs +26 -0
- package/dist/cli.js +206 -3866
- package/dist/commands/{agent-dispatch.js → agent/agent-dispatch.js} +6 -6
- package/dist/commands/{agent-support.js → agent/agent-support.js} +2 -2
- package/dist/commands/agent/contribute-cli.js +200 -0
- package/dist/commands/completions.js +1 -1
- package/dist/commands/config-cli.js +230 -3
- package/dist/commands/db-cli.js +2 -2
- package/dist/commands/env/env-cli.js +529 -0
- package/dist/commands/env/env.js +410 -0
- package/dist/commands/env/secret-cli.js +259 -0
- package/dist/commands/{secret.js → env/secret.js} +6 -47
- package/dist/commands/events.js +4 -4
- package/dist/commands/feedback-cli.js +18 -34
- package/dist/commands/graph/graph-cli.js +132 -0
- package/dist/commands/{graph.js → graph/graph.js} +22 -16
- package/dist/commands/health/checks.js +279 -0
- package/dist/commands/health/html-report.js +448 -0
- package/dist/commands/health.js +189 -266
- package/dist/commands/{consolidate.js → improve/consolidate.js} +63 -38
- package/dist/commands/{distill-promotion-policy.js → improve/distill-promotion-policy.js} +3 -3
- package/dist/commands/{distill.js → improve/distill.js} +39 -18
- package/dist/commands/{eval-cases.js → improve/eval-cases.js} +1 -1
- package/dist/commands/{extract-cli.js → improve/extract-cli.js} +4 -4
- package/dist/commands/{extract-prompt.js → improve/extract-prompt.js} +2 -2
- package/dist/commands/{extract.js → improve/extract.js} +221 -26
- package/dist/commands/{improve-auto-accept.js → improve/improve-auto-accept.js} +30 -4
- package/dist/commands/{improve-cli.js → improve/improve-cli.js} +44 -22
- package/dist/commands/{improve-profiles.js → improve/improve-profiles.js} +13 -7
- package/dist/commands/{improve-result-file.js → improve/improve-result-file.js} +1 -1
- package/dist/commands/{improve.js → improve/improve.js} +672 -292
- package/dist/{core → commands/improve/memory}/memory-belief.js +2 -2
- package/dist/{core → commands/improve/memory}/memory-contradiction-detect.js +5 -5
- package/dist/{core → commands/improve/memory}/memory-improve.js +4 -4
- package/dist/commands/improve/reflect-noise.js +0 -0
- package/dist/commands/{reflect.js → improve/reflect.js} +58 -28
- package/dist/commands/improve/session-asset.js +248 -0
- package/dist/commands/lint/agent-linter.js +1 -1
- package/dist/commands/lint/base-linter.js +55 -37
- package/dist/commands/lint/command-linter.js +1 -1
- package/dist/commands/lint/default-linter.js +1 -1
- package/dist/commands/lint/env-key-rules.js +1 -1
- package/dist/commands/lint/index.js +19 -25
- package/dist/commands/lint/knowledge-linter.js +1 -1
- package/dist/commands/lint/memory-linter.js +1 -1
- package/dist/commands/lint/registry.js +8 -8
- package/dist/commands/lint/skill-linter.js +1 -1
- package/dist/commands/lint/task-linter.js +1 -1
- package/dist/commands/lint/workflow-linter.js +1 -1
- package/dist/commands/lint.js +1 -1
- package/dist/commands/observability-cli.js +244 -0
- package/dist/commands/proposal/drain-policies.js +3 -3
- package/dist/commands/proposal/drain.js +87 -15
- package/dist/commands/proposal/proposal-cli.js +490 -0
- package/dist/commands/{proposal.js → proposal/proposal.js} +17 -6
- package/dist/commands/{propose.js → proposal/propose.js} +11 -11
- package/dist/{core → commands/proposal/validators}/proposal-quality-validators.js +8 -3
- package/dist/{core → commands/proposal/validators}/proposal-validators.js +5 -5
- package/dist/{core → commands/proposal/validators}/proposals.js +374 -345
- package/dist/commands/{curate.js → read/curate.js} +7 -7
- package/dist/commands/{knowledge.js → read/knowledge.js} +22 -9
- package/dist/commands/{registry-search.js → read/registry-search.js} +5 -5
- package/dist/commands/{remember-cli.js → read/remember-cli.js} +15 -7
- package/dist/commands/read/search-cli.js +207 -0
- package/dist/commands/{search.js → read/search.js} +22 -27
- package/dist/commands/{show.js → read/show.js} +31 -45
- package/dist/commands/registry-cli.js +8 -8
- package/dist/commands/remember.js +14 -10
- package/dist/commands/sources/add-cli.js +293 -0
- package/dist/commands/{history.js → sources/history.js} +27 -25
- package/dist/commands/{info.js → sources/info.js} +6 -6
- package/dist/commands/{init.js → sources/init.js} +6 -6
- package/dist/commands/{installed-stashes.js → sources/installed-stashes.js} +12 -12
- package/dist/commands/{migration-help.js → sources/migration-help.js} +3 -2
- package/dist/commands/{schema-repair.js → sources/schema-repair.js} +8 -8
- package/dist/commands/{self-update.js → sources/self-update.js} +10 -9
- package/dist/commands/{source-add.js → sources/source-add.js} +10 -10
- package/dist/commands/{source-clone.js → sources/source-clone.js} +7 -7
- package/dist/commands/{source-manage.js → sources/source-manage.js} +4 -4
- package/dist/commands/sources/sources-cli.js +305 -0
- package/dist/commands/sources/stash-cli.js +219 -0
- package/dist/commands/{stash-skeleton.js → sources/stash-skeleton.js} +2 -1
- package/dist/commands/tasks/default-tasks.js +173 -0
- package/dist/commands/tasks/tasks-cli.js +210 -0
- package/dist/commands/{tasks.js → tasks/tasks.js} +14 -14
- package/dist/commands/wiki-cli.js +307 -0
- package/dist/commands/workflow-cli.js +329 -0
- package/dist/core/action-contributors.js +1 -1
- package/dist/core/assert.js +40 -0
- package/dist/core/asset/asset-create.js +54 -0
- package/dist/core/{asset-ref.js → asset/asset-ref.js} +21 -4
- package/dist/core/{asset-registry.js → asset/asset-registry.js} +3 -3
- package/dist/core/{asset-spec.js → asset/asset-spec.js} +17 -31
- package/dist/core/{markdown.js → asset/markdown.js} +1 -1
- package/dist/core/{stash-meta.js → asset/stash-meta.js} +1 -1
- package/dist/core/best-effort.js +64 -0
- package/dist/core/common.js +32 -18
- package/dist/core/{config-io.js → config/config-io.js} +29 -19
- package/dist/core/{config-migration.js → config/config-migration.js} +11 -9
- package/dist/core/{config-schema.js → config/config-schema.js} +50 -7
- package/dist/core/config/config-types.js +16 -0
- package/dist/core/{config-walker.js → config/config-walker.js} +2 -2
- package/dist/core/{config.js → config/config.js} +10 -8
- package/dist/core/env-secret-ref.js +90 -0
- package/dist/core/errors.js +13 -3
- package/dist/core/events.js +27 -4
- package/dist/core/file-lock.js +1 -1
- package/dist/core/improve-types.js +48 -0
- package/dist/core/lesson-lint.js +2 -2
- package/dist/core/logs-db.js +304 -0
- package/dist/core/paths.js +2 -2
- package/dist/core/ripgrep/install.js +2 -2
- package/dist/core/ripgrep/resolve.js +2 -2
- package/dist/core/state-db.js +195 -60
- package/dist/core/text-truncation.js +148 -0
- package/dist/core/time.js +1 -1
- package/dist/core/write-source.js +98 -85
- package/dist/indexer/{db-backup.js → db/db-backup.js} +9 -24
- package/dist/indexer/{db.js → db/db.js} +128 -118
- package/dist/indexer/{graph-db.js → db/graph-db.js} +9 -4
- package/dist/indexer/{llm-cache.js → db/llm-cache.js} +15 -12
- package/dist/indexer/ensure-index.js +4 -4
- package/dist/indexer/{graph-boost.js → graph/graph-boost.js} +1 -1
- package/dist/indexer/{graph-extraction.js → graph/graph-extraction.js} +55 -13
- package/dist/indexer/indexer.js +37 -30
- package/dist/indexer/init.js +54 -0
- package/dist/indexer/manifest.js +10 -10
- package/dist/indexer/{memory-inference.js → passes/memory-inference.js} +141 -33
- package/dist/indexer/{metadata-contributors.js → passes/metadata-contributors.js} +10 -8
- package/dist/indexer/{metadata.js → passes/metadata.js} +15 -19
- package/dist/indexer/{staleness-detect.js → passes/staleness-detect.js} +53 -12
- package/dist/indexer/{db-search.js → search/db-search.js} +28 -16
- package/dist/indexer/{ranking-contributors.js → search/ranking-contributors.js} +1 -1
- package/dist/indexer/{ranking.js → search/ranking.js} +2 -2
- package/dist/indexer/{search-hit-enrichers.js → search/search-hit-enrichers.js} +3 -3
- package/dist/indexer/{search-source.js → search/search-source.js} +8 -8
- package/dist/indexer/{semantic-status.js → search/semantic-status.js} +3 -3
- package/dist/indexer/usage/unmigrated-vaults-guard.js +94 -0
- package/dist/indexer/{usage-events.js → usage/usage-events.js} +32 -0
- package/dist/indexer/{file-context.js → walk/file-context.js} +10 -15
- package/dist/indexer/{matchers.js → walk/matchers.js} +13 -9
- package/dist/indexer/{path-resolver.js → walk/path-resolver.js} +6 -6
- package/dist/indexer/{project-context.js → walk/project-context.js} +1 -1
- package/dist/indexer/{walker.js → walk/walker.js} +4 -3
- package/dist/integrations/agent/builder-shared.js +39 -0
- package/dist/integrations/agent/builders.js +14 -81
- package/dist/integrations/agent/config.js +6 -4
- package/dist/integrations/agent/detect.js +1 -1
- package/dist/integrations/agent/index.js +23 -8
- package/dist/integrations/agent/prompts.js +2 -3
- package/dist/integrations/agent/runner.js +22 -3
- package/dist/integrations/agent/spawn.js +9 -10
- package/dist/integrations/harnesses/claude/agent-builder.js +48 -0
- package/dist/integrations/harnesses/claude/config-import.js +70 -0
- package/dist/integrations/harnesses/claude/index.js +64 -0
- package/dist/integrations/{session-logs/providers/claude-code.js → harnesses/claude/session-log.js} +32 -5
- package/dist/integrations/harnesses/index.js +144 -0
- package/dist/integrations/harnesses/opencode/agent-builder.js +43 -0
- package/dist/integrations/harnesses/opencode/config-import.js +82 -0
- package/dist/integrations/harnesses/opencode/index.js +59 -0
- package/dist/integrations/{session-logs/providers/opencode.js → harnesses/opencode/session-log.js} +1 -1
- package/dist/integrations/harnesses/opencode-sdk/index.js +49 -0
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +234 -0
- package/dist/integrations/harnesses/types.js +43 -0
- package/dist/integrations/lockfile.js +7 -16
- package/dist/integrations/session-logs/index.js +82 -9
- package/dist/llm/call-ai.js +4 -4
- package/dist/llm/client.js +146 -6
- package/dist/llm/embedder.js +6 -6
- package/dist/llm/embedders/local.js +9 -22
- package/dist/llm/embedders/remote.js +2 -2
- package/dist/llm/embedders/types.js +1 -1
- package/dist/llm/graph-extract.js +31 -12
- package/dist/llm/index-passes.js +1 -1
- package/dist/llm/memory-infer.js +12 -5
- package/dist/llm/metadata-enhance.js +2 -2
- package/dist/llm/usage-persist.js +77 -0
- package/dist/llm/usage-telemetry.js +103 -0
- package/dist/output/context.js +9 -46
- package/dist/output/html-render.js +73 -0
- package/dist/output/renderers.js +88 -58
- package/dist/output/shapes/curate.js +7 -3
- package/dist/output/shapes/distill.js +7 -3
- package/dist/output/shapes/env-list.js +18 -16
- package/dist/output/shapes/events.js +5 -4
- package/dist/output/shapes/helpers.js +19 -5
- package/dist/output/shapes/history.js +7 -3
- package/dist/output/shapes/passthrough.js +8 -11
- package/dist/output/shapes/{proposal-accept.js → proposal/accept.js} +7 -3
- package/dist/output/shapes/{proposal-diff.js → proposal/diff.js} +7 -3
- package/dist/output/shapes/{proposal-list.js → proposal/list.js} +7 -3
- package/dist/output/shapes/{proposal-producer.js → proposal/producer.js} +5 -4
- package/dist/output/shapes/{proposal-reject.js → proposal/reject.js} +7 -3
- package/dist/output/shapes/{proposal-show.js → proposal/show.js} +7 -3
- package/dist/output/shapes/registry-search.js +7 -3
- package/dist/output/shapes/registry.js +12 -0
- package/dist/output/shapes/search.js +7 -3
- package/dist/output/shapes/secret-list.js +18 -16
- package/dist/output/shapes/show.js +7 -3
- package/dist/output/shapes.js +55 -30
- package/dist/output/text/add.js +2 -3
- package/dist/output/text/clone.js +2 -3
- package/dist/output/text/config.js +2 -3
- package/dist/output/text/curate.js +4 -3
- package/dist/output/text/distill.js +2 -3
- package/dist/output/text/enable-disable.js +5 -4
- package/dist/output/text/env.js +13 -0
- package/dist/output/text/events.js +5 -4
- package/dist/output/text/feedback.js +4 -3
- package/dist/output/text/helpers.js +123 -40
- package/dist/output/text/history.js +2 -3
- package/dist/output/text/import.js +2 -3
- package/dist/output/text/index.js +2 -3
- package/dist/output/text/info.js +2 -3
- package/dist/output/text/init.js +2 -3
- package/dist/output/text/list.js +2 -3
- package/dist/output/text/proposal/producer.js +9 -0
- package/dist/output/text/proposal/proposal.js +13 -0
- package/dist/output/text/registry-commands.js +8 -7
- package/dist/output/text/registry.js +12 -0
- package/dist/output/text/remember.js +4 -3
- package/dist/output/text/remove.js +2 -3
- package/dist/output/text/save.js +2 -3
- package/dist/output/text/search.js +4 -3
- package/dist/output/text/show.js +4 -3
- package/dist/output/text/update.js +2 -3
- package/dist/output/text/upgrade.js +2 -3
- package/dist/output/text/wiki.js +12 -11
- package/dist/output/text/workflow.js +12 -10
- package/dist/output/text.js +66 -32
- package/dist/registry/build-index.js +11 -10
- package/dist/registry/factory.js +1 -1
- package/dist/registry/origin-resolve.js +1 -1
- package/dist/registry/providers/index.js +2 -2
- package/dist/registry/providers/skills-sh.js +91 -72
- package/dist/registry/providers/static-index.js +75 -52
- package/dist/registry/resolve.js +3 -3
- package/dist/runtime.js +242 -0
- package/dist/scripts/migrate-storage.js +1654 -683
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +254 -168
- package/dist/setup/detect.js +311 -9
- package/dist/setup/harness-config-import.js +6 -120
- package/dist/setup/setup.js +454 -43
- package/dist/sources/include.js +1 -1
- package/dist/sources/provider-factory.js +2 -2
- package/dist/sources/providers/filesystem.js +3 -3
- package/dist/sources/providers/git.js +9 -9
- package/dist/sources/providers/index.js +4 -4
- package/dist/sources/providers/npm.js +6 -6
- package/dist/sources/providers/provider-utils.js +13 -20
- package/dist/sources/providers/sync-from-ref.js +5 -5
- package/dist/sources/providers/tar-utils.js +2 -2
- package/dist/sources/providers/website.js +2 -2
- package/dist/sources/resolve.js +5 -5
- package/dist/sources/website-ingest.js +5 -5
- package/dist/storage/database.js +102 -0
- package/dist/storage/engines/sqlite-migrations.js +42 -0
- package/dist/storage/locations.js +25 -0
- package/dist/storage/repositories/index-db.js +43 -0
- package/dist/storage/repositories/workflow-runs-repository.js +141 -0
- package/dist/tasks/backends/cron.js +4 -4
- package/dist/tasks/backends/exec-utils.js +32 -0
- package/dist/tasks/backends/index.js +3 -3
- package/dist/tasks/backends/launchd.js +7 -14
- package/dist/tasks/backends/schtasks.js +7 -16
- package/dist/tasks/embedded.js +71 -0
- package/dist/tasks/parser.js +2 -2
- package/dist/tasks/resolveAkmBin.js +1 -1
- package/dist/tasks/runner.js +127 -31
- package/dist/tasks/schedule.js +1 -1
- package/dist/tasks/validator.js +7 -7
- package/dist/text-import-hook.mjs +51 -0
- package/dist/version.js +2 -1
- package/dist/wiki/wiki.js +7 -7
- package/dist/workflows/{authoring.js → authoring/authoring.js} +6 -6
- package/dist/workflows/{scope-key.js → authoring/scope-key.js} +1 -1
- package/dist/workflows/cli.js +1 -1
- package/dist/workflows/db.js +54 -32
- package/dist/workflows/parser.js +4 -4
- package/dist/workflows/renderer.js +5 -5
- package/dist/workflows/runtime/agent-identity.js +56 -0
- package/dist/workflows/runtime/checkin.js +57 -0
- package/dist/workflows/{runs.js → runtime/runs.js} +197 -101
- package/dist/workflows/validate-summary.js +82 -0
- package/docs/README.md +1 -1
- package/docs/data-and-telemetry.md +6 -6
- package/package.json +17 -8
- package/dist/commands/add-cli.js +0 -279
- package/dist/commands/env.js +0 -213
- package/dist/integrations/agent/sdk-runner.js +0 -126
- package/dist/output/shapes/vault-list.js +0 -19
- package/dist/output/text/proposal-producer.js +0 -8
- package/dist/output/text/proposal.js +0 -12
- package/dist/output/text/vault.js +0 -16
- /package/dist/core/{asset-serialize.js → asset/asset-serialize.js} +0 -0
- /package/dist/core/{frontmatter.js → asset/frontmatter.js} +0 -0
- /package/dist/core/{config-sources.js → config/config-sources.js} +0 -0
- /package/dist/indexer/{graph-dedup.js → graph/graph-dedup.js} +0 -0
- /package/dist/{core/config-types.js → indexer/passes/pass-context.js} +0 -0
- /package/dist/indexer/{search-fields.js → search/search-fields.js} +0 -0
- /package/dist/indexer/{index-context.js → walk/index-context.js} +0 -0
- /package/dist/workflows/{document-cache.js → runtime/document-cache.js} +0 -0
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { parse as parseYaml } from "yaml";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { resolveSourceEntries } from "../../indexer/search-source";
|
|
11
|
-
import {
|
|
12
|
-
import { getLinterForType } from "./registry";
|
|
7
|
+
import { parseFrontmatter } from "../../core/asset/frontmatter.js";
|
|
8
|
+
import { resolveStashDir } from "../../core/common.js";
|
|
9
|
+
import { loadConfig } from "../../core/config/config.js";
|
|
10
|
+
import { resolveSourceEntries } from "../../indexer/search/search-source.js";
|
|
11
|
+
import { checkEnvForDangerousKeys } from "./env-key-rules.js";
|
|
12
|
+
import { getLinterForType } from "./registry.js";
|
|
13
13
|
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
14
14
|
const STASH_SUBDIRS = [
|
|
15
15
|
"agents",
|
|
@@ -154,27 +154,21 @@ export function akmLint(options = {}) {
|
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
156
|
// ── Env dangerous-key pass ─────────────────────────────────────────────────
|
|
157
|
-
// Scan every `.env` file under <stashRoot>/env/
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
// never `fixed`.
|
|
157
|
+
// Scan every `.env` file under <stashRoot>/env/ across all stash roots for
|
|
158
|
+
// keys that are known to enable process-execution hijacking. Warn-only —
|
|
159
|
+
// findings go into `flagged`, never `fixed`.
|
|
161
160
|
const envRoots = [stashRoot, ...extraStashRoots];
|
|
162
161
|
for (const root of envRoots) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const ref = baseName === "" ? `${prefix}:default` : `${prefix}:${baseName}`;
|
|
174
|
-
const relPath = path.relative(root, envPath);
|
|
175
|
-
for (const issue of checkVaultForDangerousKeys(envPath, relPath, ref)) {
|
|
176
|
-
flagged.push(issue);
|
|
177
|
-
}
|
|
162
|
+
const dir = path.join(root, "env");
|
|
163
|
+
if (!fs.existsSync(dir))
|
|
164
|
+
continue;
|
|
165
|
+
for (const envPath of collectEnvFiles(dir)) {
|
|
166
|
+
const baseName = path.basename(envPath, ".env");
|
|
167
|
+
// "default" (or empty) maps to ".env" → env:default
|
|
168
|
+
const ref = baseName === "" ? "env:default" : `env:${baseName}`;
|
|
169
|
+
const relPath = path.relative(root, envPath);
|
|
170
|
+
for (const issue of checkEnvForDangerousKeys(envPath, relPath, ref)) {
|
|
171
|
+
flagged.push(issue);
|
|
178
172
|
}
|
|
179
173
|
}
|
|
180
174
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
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 { BaseLinter } from "./base-linter";
|
|
4
|
+
import { BaseLinter } from "./base-linter.js";
|
|
5
5
|
/**
|
|
6
6
|
* Linter for `knowledge/` assets.
|
|
7
7
|
*
|
|
@@ -2,7 +2,7 @@
|
|
|
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
4
|
import fs from "node:fs";
|
|
5
|
-
import { BaseLinter } from "./base-linter";
|
|
5
|
+
import { BaseLinter } from "./base-linter.js";
|
|
6
6
|
/**
|
|
7
7
|
* Linter for `memories/` assets.
|
|
8
8
|
*
|
|
@@ -1,14 +1,14 @@
|
|
|
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 { AgentLinter } from "./agent-linter";
|
|
5
|
-
import { CommandLinter } from "./command-linter";
|
|
6
|
-
import { DefaultLinter } from "./default-linter";
|
|
7
|
-
import { KnowledgeLinter } from "./knowledge-linter";
|
|
8
|
-
import { MemoryLinter } from "./memory-linter";
|
|
9
|
-
import { SkillLinter } from "./skill-linter";
|
|
10
|
-
import { TaskLinter } from "./task-linter";
|
|
11
|
-
import { WorkflowLinter } from "./workflow-linter";
|
|
4
|
+
import { AgentLinter } from "./agent-linter.js";
|
|
5
|
+
import { CommandLinter } from "./command-linter.js";
|
|
6
|
+
import { DefaultLinter } from "./default-linter.js";
|
|
7
|
+
import { KnowledgeLinter } from "./knowledge-linter.js";
|
|
8
|
+
import { MemoryLinter } from "./memory-linter.js";
|
|
9
|
+
import { SkillLinter } from "./skill-linter.js";
|
|
10
|
+
import { TaskLinter } from "./task-linter.js";
|
|
11
|
+
import { WorkflowLinter } from "./workflow-linter.js";
|
|
12
12
|
// Singleton instances — one per type, shared across all lint runs.
|
|
13
13
|
const LINTERS = [
|
|
14
14
|
new AgentLinter(),
|
|
@@ -1,7 +1,7 @@
|
|
|
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 { BaseLinter } from "./base-linter";
|
|
4
|
+
import { BaseLinter } from "./base-linter.js";
|
|
5
5
|
/**
|
|
6
6
|
* Linter for `tasks/` assets.
|
|
7
7
|
*
|
|
@@ -2,7 +2,7 @@
|
|
|
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
4
|
import fs from "node:fs";
|
|
5
|
-
import { BaseLinter } from "./base-linter";
|
|
5
|
+
import { BaseLinter } from "./base-linter.js";
|
|
6
6
|
const PLACEHOLDER_STRINGS = ["Describe what this workflow accomplishes", "Example Workflow"];
|
|
7
7
|
/**
|
|
8
8
|
* Linter for `workflows/` assets.
|
package/dist/commands/lint.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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
|
-
export { akmLint } from "./lint/index";
|
|
4
|
+
export { akmLint } from "./lint/index.js";
|
|
@@ -0,0 +1,244 @@
|
|
|
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
|
+
* Observability command cluster — `akm log` (events list/tail), `akm lessons`
|
|
6
|
+
* (coverage), and `akm hints`. Extracted verbatim from src/cli.ts (WS6) so the
|
|
7
|
+
* God Module shrinks; the `main.subCommands.{log,lessons,hints}` keys and every
|
|
8
|
+
* subcommand's args/output shape are byte-identical.
|
|
9
|
+
*
|
|
10
|
+
* These three surfaces are cohesive read-only "tell me what happened / what to
|
|
11
|
+
* do" commands: `log` reads the append-only state.db events stream, `lessons
|
|
12
|
+
* coverage` reports tag-coverage gaps from the index, and `hints` prints the
|
|
13
|
+
* embedded AGENTS.md guidance. They share no helpers with any command still
|
|
14
|
+
* inline in cli.ts, so the `loadHints` private helper and the
|
|
15
|
+
* `formatEventLine` / `EMBEDDED_HINTS*` / db-tag-set imports move with them.
|
|
16
|
+
*
|
|
17
|
+
* The leaf handlers whose body is a plain `runWithJsonErrors(...) + output(...)`
|
|
18
|
+
* (`events list`, `lessons coverage`) are migrated onto `defineJsonCommand`,
|
|
19
|
+
* which emits the same JSON envelope (stdout/stderr/exit-code) as the inline
|
|
20
|
+
* form. `events tail` (manual streaming console/stderr writes) and `hints`
|
|
21
|
+
* (direct `process.stdout.write`) keep a plain `defineCommand` wrapping
|
|
22
|
+
* `runWithJsonErrors` so their byte-for-byte output stays untouched.
|
|
23
|
+
*/
|
|
24
|
+
import fs from "node:fs";
|
|
25
|
+
import path from "node:path";
|
|
26
|
+
import { defineCommand } from "citty";
|
|
27
|
+
import { parsePositiveIntFlag } from "../cli/parse-args.js";
|
|
28
|
+
import { defineJsonCommand, output, parseAllFlagValues, runWithJsonErrors } from "../cli/shared.js";
|
|
29
|
+
import { closeDatabase, collectTagSetFromEntries, openExistingDatabase } from "../indexer/db/db.js";
|
|
30
|
+
import { EMBEDDED_HINTS, EMBEDDED_HINTS_FULL } from "../output/cli-hints.js";
|
|
31
|
+
import { getHyphenatedArg, getOutputMode, parseDetailLevel } from "../output/context.js";
|
|
32
|
+
import { formatEventLine } from "../output/text.js";
|
|
33
|
+
import { getDirname } from "../runtime.js";
|
|
34
|
+
import { akmEventsList, akmEventsTail } from "./events.js";
|
|
35
|
+
// ── `akm log` ────────────────────────────────────────────────────────────────
|
|
36
|
+
// Append-only events stream surface (#204). `list` reads state.db events
|
|
37
|
+
// with optional --since/--type/--ref filters; `tail` follows the table via
|
|
38
|
+
// a polling loop and prints each event as a single JSONL line.
|
|
39
|
+
const eventsListCommand = defineJsonCommand({
|
|
40
|
+
meta: { name: "list", description: "List events from the append-only state.db events stream" },
|
|
41
|
+
args: {
|
|
42
|
+
since: {
|
|
43
|
+
type: "string",
|
|
44
|
+
description: "ISO timestamp / epoch ms, OR `@offset:<id>` for a durable row-id cursor (resume across processes)",
|
|
45
|
+
},
|
|
46
|
+
type: { type: "string", description: "Filter by event type (add, remove, remember, feedback, ...)" },
|
|
47
|
+
ref: { type: "string", description: "Filter by asset ref (type:name)" },
|
|
48
|
+
"exclude-tags": {
|
|
49
|
+
type: "string",
|
|
50
|
+
description: "Exclude events matching these tags (repeatable)",
|
|
51
|
+
},
|
|
52
|
+
"include-tags": {
|
|
53
|
+
type: "string",
|
|
54
|
+
description: "Only include events with ALL these tags (repeatable)",
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
run({ args }) {
|
|
58
|
+
const excludeTags = parseAllFlagValues("--exclude-tags");
|
|
59
|
+
const includeTags = parseAllFlagValues("--include-tags");
|
|
60
|
+
const result = akmEventsList({
|
|
61
|
+
since: args.since,
|
|
62
|
+
type: args.type,
|
|
63
|
+
ref: args.ref,
|
|
64
|
+
...(excludeTags.length > 0 ? { excludeTags } : {}),
|
|
65
|
+
...(includeTags.length > 0 ? { includeTags } : {}),
|
|
66
|
+
});
|
|
67
|
+
output("events-list", result);
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
const eventsTailCommand = defineCommand({
|
|
71
|
+
meta: { name: "tail", description: "Follow the append-only state.db events stream (polling)" },
|
|
72
|
+
args: {
|
|
73
|
+
since: {
|
|
74
|
+
type: "string",
|
|
75
|
+
description: "ISO timestamp / epoch ms, OR `@offset:<id>` for a durable row-id cursor (resume across processes)",
|
|
76
|
+
},
|
|
77
|
+
type: { type: "string", description: "Filter by event type" },
|
|
78
|
+
ref: { type: "string", description: "Filter by asset ref (type:name)" },
|
|
79
|
+
"interval-ms": { type: "string", description: "Polling interval in ms (default: 75)" },
|
|
80
|
+
"max-duration-ms": { type: "string", description: "Stop after this many ms (default: never)" },
|
|
81
|
+
"max-events": { type: "string", description: "Stop after observing this many events" },
|
|
82
|
+
"exclude-tags": {
|
|
83
|
+
type: "string",
|
|
84
|
+
description: "Exclude events matching these tags (repeatable)",
|
|
85
|
+
},
|
|
86
|
+
"include-tags": {
|
|
87
|
+
type: "string",
|
|
88
|
+
description: "Only include events with ALL these tags (repeatable)",
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
async run({ args }) {
|
|
92
|
+
await runWithJsonErrors(async () => {
|
|
93
|
+
const intervalMs = parsePositiveIntFlag(getHyphenatedArg(args, "interval-ms"), "--interval-ms");
|
|
94
|
+
const maxDurationMs = parsePositiveIntFlag(getHyphenatedArg(args, "max-duration-ms"), "--max-duration-ms");
|
|
95
|
+
const maxEvents = parsePositiveIntFlag(getHyphenatedArg(args, "max-events"), "--max-events");
|
|
96
|
+
const mode = getOutputMode();
|
|
97
|
+
// In streaming text mode we want each event to print as soon as it
|
|
98
|
+
// arrives. The polling loop emits via `onEvent`; the final result is
|
|
99
|
+
// also rendered through the standard output() pipeline so JSON
|
|
100
|
+
// consumers always get the canonical envelope.
|
|
101
|
+
const stream = mode.format === "text" || mode.format === "jsonl";
|
|
102
|
+
const excludeTags = parseAllFlagValues("--exclude-tags");
|
|
103
|
+
const includeTags = parseAllFlagValues("--include-tags");
|
|
104
|
+
const result = await akmEventsTail({
|
|
105
|
+
since: args.since,
|
|
106
|
+
type: args.type,
|
|
107
|
+
ref: args.ref,
|
|
108
|
+
intervalMs,
|
|
109
|
+
maxDurationMs,
|
|
110
|
+
maxEvents,
|
|
111
|
+
...(excludeTags.length > 0 ? { excludeTags } : {}),
|
|
112
|
+
...(includeTags.length > 0 ? { includeTags } : {}),
|
|
113
|
+
onEvent: stream
|
|
114
|
+
? (event) => {
|
|
115
|
+
if (mode.format === "jsonl") {
|
|
116
|
+
console.log(JSON.stringify(event));
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
console.log(formatEventLine(event));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
: undefined,
|
|
123
|
+
});
|
|
124
|
+
// Emit the canonical envelope last (JSON/YAML modes rely on this;
|
|
125
|
+
// streaming modes already printed each event but we still emit a
|
|
126
|
+
// trailer so callers can persist the resumable cursor).
|
|
127
|
+
if (!stream) {
|
|
128
|
+
output("events-tail", result);
|
|
129
|
+
}
|
|
130
|
+
else if (mode.format === "jsonl") {
|
|
131
|
+
// Final discriminated trailer row so jsonl consumers can resume.
|
|
132
|
+
const trailer = {
|
|
133
|
+
_kind: "trailer",
|
|
134
|
+
schemaVersion: 1,
|
|
135
|
+
nextOffset: result.nextOffset,
|
|
136
|
+
totalCount: result.totalCount,
|
|
137
|
+
reason: result.reason,
|
|
138
|
+
};
|
|
139
|
+
console.log(JSON.stringify(trailer));
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
// text mode: keep stdout pristine for line-oriented parsers and
|
|
143
|
+
// emit the trailer on stderr.
|
|
144
|
+
process.stderr.write(`[events-tail] reason=${result.reason} nextOffset=${result.nextOffset} total=${result.totalCount}\n`);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
export const logCommand = defineCommand({
|
|
150
|
+
meta: {
|
|
151
|
+
name: "log",
|
|
152
|
+
description: "Read or follow the append-only state.db events stream (mutations, feedback, indexing)",
|
|
153
|
+
},
|
|
154
|
+
subCommands: {
|
|
155
|
+
list: eventsListCommand,
|
|
156
|
+
tail: eventsTailCommand,
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
// ── lessons subcommands (Phase 7A / Advantage D4c) ──────────────────────────
|
|
160
|
+
const lessonsCoverageCommand = defineJsonCommand({
|
|
161
|
+
meta: {
|
|
162
|
+
name: "coverage",
|
|
163
|
+
description: "Report tags that exist on indexed assets but are NOT yet covered by any lesson.\n\n" +
|
|
164
|
+
"Useful for spotting topics where the stash has skills/commands/scripts but no\n" +
|
|
165
|
+
"crystallized lesson — a signal that the team has tacit knowledge worth distilling.\n\n" +
|
|
166
|
+
"Default output is JSON: { uncoveredTags: string[], lessonTagCount: number, totalTagCount: number }.\n" +
|
|
167
|
+
"Pass --format text for a plain-text bulleted list.",
|
|
168
|
+
},
|
|
169
|
+
args: {},
|
|
170
|
+
run() {
|
|
171
|
+
const db = openExistingDatabase();
|
|
172
|
+
try {
|
|
173
|
+
const allTagSet = collectTagSetFromEntries(db, undefined);
|
|
174
|
+
const lessonTagSet = collectTagSetFromEntries(db, "lesson");
|
|
175
|
+
const uncovered = [];
|
|
176
|
+
for (const tag of allTagSet) {
|
|
177
|
+
if (!lessonTagSet.has(tag))
|
|
178
|
+
uncovered.push(tag);
|
|
179
|
+
}
|
|
180
|
+
uncovered.sort((a, b) => a.localeCompare(b));
|
|
181
|
+
output("lessons-coverage", {
|
|
182
|
+
ok: true,
|
|
183
|
+
uncoveredTags: uncovered,
|
|
184
|
+
lessonTagCount: lessonTagSet.size,
|
|
185
|
+
totalTagCount: allTagSet.size,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
finally {
|
|
189
|
+
closeDatabase(db);
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
export const lessonsCommand = defineCommand({
|
|
194
|
+
meta: {
|
|
195
|
+
name: "lessons",
|
|
196
|
+
alias: "lesson",
|
|
197
|
+
description: "Lesson-asset tooling: tag-coverage gaps, strength queries.",
|
|
198
|
+
},
|
|
199
|
+
subCommands: {
|
|
200
|
+
coverage: lessonsCoverageCommand,
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
// ── `akm hints` ──────────────────────────────────────────────────────────────
|
|
204
|
+
export const hintsCommand = defineCommand({
|
|
205
|
+
meta: {
|
|
206
|
+
name: "hints",
|
|
207
|
+
description: "Print agent instructions on how to use akm, use --detail full for a complete guide",
|
|
208
|
+
},
|
|
209
|
+
args: {
|
|
210
|
+
detail: {
|
|
211
|
+
type: "string",
|
|
212
|
+
description: "Hints detail level (brief|normal|full). `brief` prints the short guide; `normal`/`full` print the complete guide.",
|
|
213
|
+
default: "normal",
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
run({ args }) {
|
|
217
|
+
return runWithJsonErrors(() => {
|
|
218
|
+
// Let the global parser validate the value so an invalid `--detail`
|
|
219
|
+
// returns the standard JSON error envelope (exit 2) rather than a raw
|
|
220
|
+
// stack trace + exit 1. `brief` → short doc; `normal`/`full` → full doc.
|
|
221
|
+
const detail = parseDetailLevel(args.detail) ?? "normal";
|
|
222
|
+
process.stdout.write(loadHints(detail === "brief" ? "brief" : "full"));
|
|
223
|
+
});
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
// ── Hints (embedded AGENTS.md) ──────────────────────────────────────────────
|
|
227
|
+
function loadHints(detail = "normal") {
|
|
228
|
+
// `brief` → the short AGENTS.md guide; `normal`/`full` → the complete guide.
|
|
229
|
+
const wantFull = detail !== "brief";
|
|
230
|
+
const filename = wantFull ? "AGENTS.full.md" : "AGENTS.md";
|
|
231
|
+
const fallback = wantFull ? EMBEDDED_HINTS_FULL : EMBEDDED_HINTS;
|
|
232
|
+
// Try reading from the docs/ directory (works in dev and when installed via npm)
|
|
233
|
+
try {
|
|
234
|
+
const docsPath = path.resolve(getDirname(import.meta.url), `../../docs/agents/${filename}`);
|
|
235
|
+
if (fs.existsSync(docsPath)) {
|
|
236
|
+
return fs.readFileSync(docsPath, "utf8");
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
// fall through
|
|
241
|
+
}
|
|
242
|
+
// Fallback for compiled binary — inline content
|
|
243
|
+
return fallback;
|
|
244
|
+
}
|
|
@@ -16,10 +16,10 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import fs from "node:fs";
|
|
18
18
|
import { z } from "zod";
|
|
19
|
-
import { UsageError } from "../../core/errors";
|
|
20
|
-
import { PROPOSAL_SOURCES } from "
|
|
19
|
+
import { UsageError } from "../../core/errors.js";
|
|
20
|
+
import { PROPOSAL_SOURCES } from "./validators/proposals.js";
|
|
21
21
|
// Valid `generator` values for a drain rule are exactly the canonical proposal
|
|
22
|
-
// `source` values (see {@link PROPOSAL_SOURCES} in src/
|
|
22
|
+
// `source` values (see {@link PROPOSAL_SOURCES} in src/commands/proposal/validators/proposals.ts). The
|
|
23
23
|
// engine matches rules via `policy.accept.find(r => r.generator === proposal.source)`,
|
|
24
24
|
// so a generator that is not a real source can never match — it would be a
|
|
25
25
|
// silent permanent no-op. Validate against the closed set to surface typos.
|
|
@@ -36,16 +36,17 @@
|
|
|
36
36
|
*/
|
|
37
37
|
import fs from "node:fs";
|
|
38
38
|
import path from "node:path";
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
42
|
-
import { parseFrontmatter } from "../../core/frontmatter";
|
|
43
|
-
import {
|
|
44
|
-
import { info, warn } from "../../core/warn";
|
|
45
|
-
import { runAgent } from "../../integrations/agent";
|
|
46
|
-
import { runOpencodeSdk } from "../../integrations/
|
|
47
|
-
import { chatCompletion, stripJsonFences } from "../../llm/client";
|
|
48
|
-
import { akmProposalAccept, akmProposalReject } from "
|
|
39
|
+
import { assertNever } from "../../core/assert.js";
|
|
40
|
+
import { parseAssetRef } from "../../core/asset/asset-ref.js";
|
|
41
|
+
import { resolveAssetPathFromName, TYPE_DIRS } from "../../core/asset/asset-spec.js";
|
|
42
|
+
import { parseFrontmatter } from "../../core/asset/frontmatter.js";
|
|
43
|
+
import { appendEvent } from "../../core/events.js";
|
|
44
|
+
import { info, warn } from "../../core/warn.js";
|
|
45
|
+
import { runAgent } from "../../integrations/agent/index.js";
|
|
46
|
+
import { runOpencodeSdk } from "../../integrations/harnesses/opencode-sdk/index.js";
|
|
47
|
+
import { chatCompletion, stripJsonFences } from "../../llm/client.js";
|
|
48
|
+
import { akmProposalAccept, akmProposalReject } from "./proposal.js";
|
|
49
|
+
import { listProposals, recordGateDecision } from "./validators/proposals.js";
|
|
49
50
|
// ---------------------------------------------------------------------------
|
|
50
51
|
// Content helpers
|
|
51
52
|
// ---------------------------------------------------------------------------
|
|
@@ -77,7 +78,7 @@ export function classifyProposal(proposal, policy, maxDiffLines) {
|
|
|
77
78
|
const content = proposal.payload.content ?? "";
|
|
78
79
|
// Empty / near-empty diffs reject first (the reject-empty floor).
|
|
79
80
|
if (policy.rejectEmpty && isEmptyDiff(proposal)) {
|
|
80
|
-
return { verdict: "reject", reason: "empty diff" };
|
|
81
|
+
return { verdict: "reject", reason: "empty diff", gate: { reason: "empty-diff" } };
|
|
81
82
|
}
|
|
82
83
|
const rule = policy.accept.find((r) => r.generator === proposal.source);
|
|
83
84
|
if (rule) {
|
|
@@ -86,16 +87,25 @@ export function classifyProposal(proposal, policy, maxDiffLines) {
|
|
|
86
87
|
// Per-rule and global diff bounds defer large accepts (no silent rewrites).
|
|
87
88
|
const effectiveMax = Math.min(rule.maxDiffLines ?? Number.POSITIVE_INFINITY, maxDiffLines ?? Number.POSITIVE_INFINITY);
|
|
88
89
|
if (lines > effectiveMax) {
|
|
89
|
-
return {
|
|
90
|
+
return {
|
|
91
|
+
verdict: "defer",
|
|
92
|
+
reason: "mid-band",
|
|
93
|
+
gate: { reason: "max-diff-lines", measured: lines, thresholds: { maxDiffLines: effectiveMax } },
|
|
94
|
+
};
|
|
90
95
|
}
|
|
91
96
|
if (rule.minContentLines !== undefined && body < rule.minContentLines) {
|
|
92
97
|
// Too little content to confidently auto-accept — leave for judgment.
|
|
93
|
-
return {
|
|
98
|
+
return {
|
|
99
|
+
verdict: "defer",
|
|
100
|
+
reason: "mid-band",
|
|
101
|
+
gate: { reason: "min-content-lines", measured: body, thresholds: { minContentLines: rule.minContentLines } },
|
|
102
|
+
};
|
|
94
103
|
}
|
|
95
|
-
return { verdict: "accept" };
|
|
104
|
+
return { verdict: "accept", gate: { reason: "policy-accept" } };
|
|
96
105
|
}
|
|
97
106
|
if (policy.defer.includes(proposal.source)) {
|
|
98
|
-
|
|
107
|
+
const reason = deferReasonForSource(proposal.source);
|
|
108
|
+
return { verdict: "defer", reason, gate: { reason } };
|
|
99
109
|
}
|
|
100
110
|
// No matching rule — leave pending, untouched.
|
|
101
111
|
return null;
|
|
@@ -220,6 +230,10 @@ async function dispatchJudgment(runner, prompt, seams) {
|
|
|
220
230
|
raw = stdout;
|
|
221
231
|
break;
|
|
222
232
|
}
|
|
233
|
+
default:
|
|
234
|
+
// Exhaustiveness arm (H1): a 4th RunnerSpec kind becomes a compile error
|
|
235
|
+
// here instead of leaving `raw` undefined at runtime.
|
|
236
|
+
return assertNever(runner);
|
|
223
237
|
}
|
|
224
238
|
return parseJudgmentVerdict(raw);
|
|
225
239
|
}
|
|
@@ -342,10 +356,31 @@ export async function drainProposals(opts, promoteFn = akmProposalAccept, reject
|
|
|
342
356
|
// First, classify every proposal deterministically.
|
|
343
357
|
const acceptIds = [];
|
|
344
358
|
const rejectTargets = [];
|
|
359
|
+
const gateLabel = `triage:${opts.policy.name}`;
|
|
360
|
+
// Items deferred purely because they need a judge (no threshold-based reason)
|
|
361
|
+
// — these are re-stamped `no-judge-configured` when no runner resolves them.
|
|
362
|
+
const needsJudge = new Set();
|
|
345
363
|
for (const proposal of pending) {
|
|
346
364
|
const decision = classifyProposal(proposal, opts.policy, opts.maxDiffLines);
|
|
347
365
|
if (decision === null)
|
|
348
366
|
continue;
|
|
367
|
+
// #577: stamp the gate's verdict onto the proposal so `akm proposal show`
|
|
368
|
+
// can explain WHY it landed here. A dry-run performs zero writes, so it
|
|
369
|
+
// records nothing.
|
|
370
|
+
const outcome = decision.verdict === "accept" ? "auto-accepted" : decision.verdict === "reject" ? "auto-rejected" : "deferred";
|
|
371
|
+
stampGateDecision(opts, proposal.id, {
|
|
372
|
+
outcome,
|
|
373
|
+
reason: decision.gate.reason,
|
|
374
|
+
...(decision.gate.measured !== undefined ? { measured: decision.gate.measured } : {}),
|
|
375
|
+
...(decision.gate.thresholds ? { thresholds: decision.gate.thresholds } : {}),
|
|
376
|
+
gate: gateLabel,
|
|
377
|
+
});
|
|
378
|
+
// A defer with no threshold (mid-band / possible-dup from the defer list) is
|
|
379
|
+
// pending only because it needs adjudication — re-stampable to
|
|
380
|
+
// `no-judge-configured`. A band-based defer keeps its specific reason.
|
|
381
|
+
if (decision.verdict === "defer" && !decision.gate.thresholds) {
|
|
382
|
+
needsJudge.add(proposal.id);
|
|
383
|
+
}
|
|
349
384
|
if (decision.verdict === "accept") {
|
|
350
385
|
acceptIds.push(proposal.id);
|
|
351
386
|
}
|
|
@@ -429,14 +464,51 @@ export async function drainProposals(opts, promoteFn = akmProposalAccept, reject
|
|
|
429
464
|
if (tier.skippedByCap.length > 0) {
|
|
430
465
|
info(`[triage] accept ceiling reached in judgment tier: ${tier.skippedByCap.length} judged-accept items skipped by cap (maxAccepts=${opts.maxAccepts})`);
|
|
431
466
|
}
|
|
467
|
+
// #577: re-stamp the gate decision for items the judgment tier resolved so
|
|
468
|
+
// `akm proposal show` reflects the judge's verdict, not the earlier
|
|
469
|
+
// deterministic defer.
|
|
470
|
+
for (const id of tier.promoted) {
|
|
471
|
+
stampGateDecision(opts, id, { outcome: "auto-accepted", reason: "judgment-accept", gate: gateLabel });
|
|
472
|
+
}
|
|
473
|
+
for (const id of tier.rejected) {
|
|
474
|
+
stampGateDecision(opts, id, { outcome: "auto-rejected", reason: "judgment-reject", gate: gateLabel });
|
|
475
|
+
}
|
|
432
476
|
// Replace the deferred list with only the items the judgment tier could NOT
|
|
433
477
|
// resolve (verdict "defer", parse failure, or runner error). Staged
|
|
434
478
|
// queue-mode accepts are RESOLVED and tracked in result.staged instead.
|
|
435
479
|
result.deferred = tier.stillDeferred;
|
|
436
480
|
}
|
|
481
|
+
else if (result.deferred.length > 0) {
|
|
482
|
+
// #577: no judgment runner configured — items deferred *because they need a
|
|
483
|
+
// judge* (mid-band / possible-dup, no threshold reason) stay pending solely
|
|
484
|
+
// for lack of one. Re-stamp those as `no-judge-configured` so the operator
|
|
485
|
+
// sees a per-proposal reason instead of inferring it from the run-level
|
|
486
|
+
// triage_deferred aggregate. Band-deferred items keep their specific reason
|
|
487
|
+
// (e.g. `max-diff-lines`), which is more actionable than "no judge".
|
|
488
|
+
for (const item of result.deferred) {
|
|
489
|
+
if (needsJudge.has(item.id)) {
|
|
490
|
+
stampGateDecision(opts, item.id, { outcome: "deferred", reason: "no-judge-configured", gate: gateLabel });
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
437
494
|
emitDrainEvents(opts, result);
|
|
438
495
|
return result;
|
|
439
496
|
}
|
|
497
|
+
/**
|
|
498
|
+
* Persist a gate decision onto a proposal, honouring the dry-run contract
|
|
499
|
+
* (a dry run performs zero writes, so it records nothing) and never letting a
|
|
500
|
+
* persistence failure abort the drain (#577). Best-effort by design.
|
|
501
|
+
*/
|
|
502
|
+
function stampGateDecision(opts, id, decision) {
|
|
503
|
+
if (opts.dryRun)
|
|
504
|
+
return;
|
|
505
|
+
try {
|
|
506
|
+
recordGateDecision(opts.stashDir, id, decision);
|
|
507
|
+
}
|
|
508
|
+
catch (err) {
|
|
509
|
+
warn(`[triage] failed to record gate decision for ${id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
440
512
|
// ---------------------------------------------------------------------------
|
|
441
513
|
// Events
|
|
442
514
|
// ---------------------------------------------------------------------------
|