akm-cli 0.9.4 → 0.9.6

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.
Files changed (123) hide show
  1. package/CHANGELOG.md +265 -0
  2. package/dist/commands/env/env-cli.js +1 -2
  3. package/dist/commands/env/secret-cli.js +1 -5
  4. package/dist/commands/feedback-cli.js +0 -4
  5. package/dist/commands/health/checks.js +0 -32
  6. package/dist/commands/health/surfaces.js +2 -2
  7. package/dist/commands/health.js +6 -15
  8. package/dist/commands/improve/anti-collapse.js +4 -91
  9. package/dist/commands/improve/autonomy-gate.js +1 -1
  10. package/dist/commands/improve/consolidate.js +25 -26
  11. package/dist/commands/improve/distill.js +2 -2
  12. package/dist/commands/improve/extract.js +8 -1
  13. package/dist/commands/improve/improve.js +3 -5
  14. package/dist/commands/improve/locks.js +13 -5
  15. package/dist/commands/improve/loop-stages.js +8 -9
  16. package/dist/commands/improve/memory/memory-contradiction-detect.js +1 -19
  17. package/dist/commands/improve/outcome-loop.js +0 -6
  18. package/dist/commands/improve/reflect.js +20 -9
  19. package/dist/commands/lint/index.js +4 -6
  20. package/dist/commands/proposal/validators/proposal-quality-validators.js +0 -7
  21. package/dist/commands/proposal/validators/proposal-validators.js +12 -0
  22. package/dist/commands/read/search.js +14 -24
  23. package/dist/commands/sources/bundle-config-ops.js +9 -4
  24. package/dist/commands/sources/info.js +14 -10
  25. package/dist/commands/tasks/tasks-cli.js +56 -3
  26. package/dist/commands/tasks/tasks.js +101 -18
  27. package/dist/commands/workflow-cli.js +2 -2
  28. package/dist/core/adapter/adapters/akm-adapter.js +2 -0
  29. package/dist/core/common.js +61 -24
  30. package/dist/core/config/config-io.js +2 -2
  31. package/dist/core/config/config-sources.js +32 -2
  32. package/dist/core/config/config-version-shim.js +101 -0
  33. package/dist/core/config/config.js +7 -7
  34. package/dist/core/config/schema/engines.js +9 -7
  35. package/dist/core/config/schema/primitives.js +0 -5
  36. package/dist/core/config/schema/search.js +2 -1
  37. package/dist/core/file-lock.js +2 -1
  38. package/dist/core/improve-result.js +35 -14
  39. package/dist/core/maintenance-barrier.js +2 -14
  40. package/dist/core/paths.js +0 -3
  41. package/dist/core/redaction.js +2 -2
  42. package/dist/core/spawn-env.js +8 -12
  43. package/dist/core/state/migrations.js +1 -12
  44. package/dist/core/state-db.js +9 -27
  45. package/dist/core/write-source.js +12 -19
  46. package/dist/execution/directory-identity.js +36 -10
  47. package/dist/execution/guarded-source.js +0 -10
  48. package/dist/indexer/graph/graph-boost.js +0 -4
  49. package/dist/indexer/index-writer-lock.js +43 -24
  50. package/dist/indexer/index-written-assets.js +5 -6
  51. package/dist/indexer/indexer.js +2 -39
  52. package/dist/indexer/materialize-embeddings.js +85 -41
  53. package/dist/indexer/passes/metadata.js +12 -4
  54. package/dist/indexer/scan/doc-to-entry.js +2 -0
  55. package/dist/indexer/search/db-search.js +15 -48
  56. package/dist/indexer/search/ranking-contributors.js +0 -25
  57. package/dist/indexer/search/ranking.js +3 -13
  58. package/dist/indexer/search/search-fields.js +16 -1
  59. package/dist/integrations/agent/builder-shared.js +0 -25
  60. package/dist/integrations/agent/model-map.js +2 -60
  61. package/dist/integrations/harnesses/aider/agent-builder.js +1 -3
  62. package/dist/integrations/harnesses/amazonq/agent-builder.js +2 -7
  63. package/dist/integrations/harnesses/claude/agent-builder.js +1 -4
  64. package/dist/integrations/harnesses/codex/agent-builder.js +1 -4
  65. package/dist/integrations/harnesses/copilot/agent-builder.js +2 -6
  66. package/dist/integrations/harnesses/gemini/agent-builder.js +2 -7
  67. package/dist/integrations/harnesses/opencode/agent-builder.js +1 -4
  68. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +2 -16
  69. package/dist/integrations/harnesses/openhands/agent-builder.js +1 -3
  70. package/dist/integrations/harnesses/pi/agent-builder.js +2 -4
  71. package/dist/integrations/session-logs/index.js +0 -9
  72. package/dist/llm/client.js +75 -42
  73. package/dist/llm/embedder.js +7 -3
  74. package/dist/llm/embedders/remote.js +141 -42
  75. package/dist/output/shapes/passthrough.js +17 -5
  76. package/dist/registry/network.js +5 -37
  77. package/dist/runtime.js +2 -10
  78. package/dist/scripts/akm-migrate-node.js +180 -238
  79. package/dist/scripts/akm-migrate.js +180 -238
  80. package/dist/setup/engine-config.js +2 -5
  81. package/dist/setup/registry-stash-loader.js +0 -8
  82. package/dist/setup/setup.js +9 -46
  83. package/dist/setup/steps/connection-shared.js +10 -13
  84. package/dist/sources/providers/git-install.js +1 -1
  85. package/dist/storage/engines/sqlite-migrations.js +20 -1
  86. package/dist/storage/repositories/index-entries-repository.js +0 -15
  87. package/dist/storage/repositories/proposals-repository.js +32 -6
  88. package/dist/tasks/backends/launchd.js +15 -20
  89. package/dist/tasks/backends/schtasks.js +18 -8
  90. package/dist/tasks/run/run-native-task.js +8 -6
  91. package/dist/tasks/scheduler-binding.js +15 -5
  92. package/dist/tasks/scheduler-sync-preview.js +4 -2
  93. package/dist/tasks/scheduler-sync.js +77 -42
  94. package/dist/tasks/source/bounded-document.js +2 -4
  95. package/dist/tasks/source/task-source-v3-frozen.js +5 -7
  96. package/dist/tasks/source/task-source-v4.js +5 -10
  97. package/dist/tasks/source/task-to-v3.js +29 -22
  98. package/dist/tasks/source/task-to-v4.js +1 -14
  99. package/dist/tasks/source-v3.js +6 -6
  100. package/dist/workflows/exec/native-executor.js +21 -31
  101. package/dist/workflows/exec/run-workflow.js +5 -6
  102. package/dist/workflows/exec/scheduler.js +3 -19
  103. package/dist/workflows/exec/step-work.js +1 -4
  104. package/dist/workflows/exec/unit-dispatch.js +2 -2
  105. package/dist/workflows/exec/worktree.js +1 -13
  106. package/dist/workflows/freeze/targets/child-workflow.js +2 -10
  107. package/dist/workflows/ir/plan-hash.js +4 -6
  108. package/dist/workflows/ir/schema-v4.js +0 -12
  109. package/dist/workflows/ir/schema.js +20 -31
  110. package/dist/workflows/parser.js +11 -52
  111. package/dist/workflows/renderer.js +2 -3
  112. package/dist/workflows/resource-limits.js +11 -41
  113. package/dist/workflows/runtime/runs.js +3 -4
  114. package/dist/workflows/source-ir/schema.js +14 -30
  115. package/dist/workflows/validator.js +1 -7
  116. package/docs/reference/cli.md +25 -3
  117. package/docs/reference/configuration.md +28 -7
  118. package/docs/reference/tasks.md +10 -0
  119. package/package.json +1 -1
  120. package/schemas/akm-config.json +4 -23
  121. package/schemas/akm-task.json +1 -2
  122. package/schemas/akm-workflow.json +1 -13
  123. package/dist/indexer/search/semantic-status.js +0 -142
@@ -490,31 +490,6 @@ export const salienceRankingContributor = {
490
490
  // comment. Do not add it back here without a config gate + a measured
491
491
  // curate-golden-bench delta justifying it.
492
492
  export const defaultUtilityRankingContributors = [utilityRankingContributor];
493
- /**
494
- * EVAL/DEBUG ONLY — remove named ranking contributors from a list.
495
- *
496
- * Driven by the `AKM_ABLATE_CONTRIBUTORS` env var (comma-separated contributor
497
- * `name`s). A no-op — returns the input list unchanged (same reference) — when
498
- * the env value is unset/empty, so production ranking is never affected unless
499
- * the operator opts in. Its sole purpose is per-contributor ablation for the
500
- * curate ablation harness (`scripts/akm-eval/`): run the same fixture with and without a contributor
501
- * and diff the ranked results to measure whether that contributor is load-bearing.
502
- *
503
- * NOTE (from the 2026-06 ablation analysis): a contributor's ablation delta is only observable
504
- * in the UNSATURATED score regime — once entries saturate at the `displayScore`
505
- * ceiling their contributor deltas are absorbed and ablation reads Δ=0.
506
- */
507
- export function applyContributorAblation(contributors, ablateEnv) {
508
- if (!ablateEnv)
509
- return contributors;
510
- const ablated = new Set(ablateEnv
511
- .split(",")
512
- .map((s) => s.trim())
513
- .filter(Boolean));
514
- if (ablated.size === 0)
515
- return contributors;
516
- return contributors.filter((c) => !ablated.has(c.name));
517
- }
518
493
  export function applyScoreContributors(item, ctx, contributors = defaultRankingContributors) {
519
494
  let boostSum = 0;
520
495
  for (const contributor of contributors) {
@@ -3,7 +3,7 @@
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  import { getUtilityScoresByIds } from "../../storage/repositories/index-utility-repository.js";
5
5
  import { buildLexicalQueryPlan } from "./fts-query.js";
6
- import { applyBeliefStateScoreCeiling, applyContributorAblation, applyScoreContributors, applyUtilityContributors, defaultRankingContributors, defaultUtilityRankingContributors, } from "./ranking-contributors.js";
6
+ import { applyBeliefStateScoreCeiling, applyScoreContributors, applyUtilityContributors, defaultRankingContributors, defaultUtilityRankingContributors, } from "./ranking-contributors.js";
7
7
  export function normalizeFtsScores(results) {
8
8
  const ftsScoreMap = new Map();
9
9
  if (results.length === 0)
@@ -75,18 +75,8 @@ export function applyRankingRules(options) {
75
75
  graphContext: options.graphContext,
76
76
  projectContext: options.projectContext,
77
77
  };
78
- // Eval/debug only: lets the ablation harness drop named contributors to
79
- // measure their effect. Resolved once per query; a no-op (full lists) when
80
- // unset — see applyContributorAblation. C9: DI via
81
- // `options.ablateContributors` is now the primary path (see the field's
82
- // doc comment on RankEntriesOptions); AKM_ABLATE_CONTRIBUTORS is a
83
- // temporary fallback for the one caller that cannot yet thread the option
84
- // through (see that doc comment for why).
85
- const ablateEnv = options.ablateContributors ?? process.env.AKM_ABLATE_CONTRIBUTORS;
86
- const activeScoreContributors = applyContributorAblation(defaultRankingContributors, ablateEnv);
87
- const activeUtilityContributors = applyContributorAblation(defaultUtilityRankingContributors, ablateEnv);
88
78
  for (const item of options.items) {
89
- applyScoreContributors(item, rankingContext, activeScoreContributors);
79
+ applyScoreContributors(item, rankingContext, defaultRankingContributors);
90
80
  }
91
81
  const { global: utilScoresMap, scoped: scopedUtilScoresMap } = getUtilityScoresByIds(options.db, options.items.map((item) => item.id), options.scopeKey);
92
82
  // R2 / #692 — salience-ranking is not in defaultUtilityRankingContributors
@@ -104,7 +94,7 @@ export function applyRankingRules(options) {
104
94
  salienceRankScores,
105
95
  };
106
96
  for (const item of options.items) {
107
- applyUtilityContributors(item, utilityContext, activeUtilityContributors);
97
+ applyUtilityContributors(item, utilityContext, defaultUtilityRankingContributors);
108
98
  applyRelaxedLexicalScoreCeiling(item, queryTokens);
109
99
  // SPEC-5: demoting belief states (superseded/contradicted/archived/
110
100
  // deprecated) cap the FINAL score. The additive belief penalty inside the
@@ -1,6 +1,16 @@
1
1
  // This Source Code Form is subject to the terms of the Mozilla Public
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ /**
5
+ * Per-field search text extraction for FTS5 indexing.
6
+ *
7
+ * Extracted from indexer.ts to break the circular dependency:
8
+ * db.ts -> indexer.ts -> db.ts
9
+ *
10
+ * This module imports only from metadata.ts (for the IndexDocument type),
11
+ * so it can be safely imported by both db.ts and indexer.ts.
12
+ */
13
+ import { warnVerbose } from "../../core/warn.js";
4
14
  /** Structured metadata plus bounded body text supplied to embedding providers. */
5
15
  export const SEARCH_TEXT_MAX_CHARS = 8_192;
6
16
  /**
@@ -80,12 +90,17 @@ export function buildSearchText(entry) {
80
90
  const structured = [fields.name, fields.description, fields.tags, fields.hints]
81
91
  .filter((field) => field.length > 0)
82
92
  .join(" ");
83
- if (structured.length >= SEARCH_TEXT_MAX_CHARS)
93
+ if (structured.length >= SEARCH_TEXT_MAX_CHARS) {
94
+ warnVerbose(`${entry.ref ?? entry.name}: search text truncated to ${SEARCH_TEXT_MAX_CHARS} chars (content dropped entirely)`);
84
95
  return truncateUnicodeSafe(structured, SEARCH_TEXT_MAX_CHARS);
96
+ }
85
97
  if (!fields.content)
86
98
  return structured;
87
99
  const separator = structured ? " " : "";
88
100
  const remaining = SEARCH_TEXT_MAX_CHARS - structured.length - separator.length;
101
+ if (fields.content.length > remaining) {
102
+ warnVerbose(`${entry.ref ?? entry.name}: search text truncated to ${SEARCH_TEXT_MAX_CHARS} chars`);
103
+ }
89
104
  return `${structured}${separator}${truncateUnicodeSafe(fields.content, remaining)}`;
90
105
  }
91
106
  function truncateUnicodeSafe(text, maxChars) {
@@ -1,35 +1,10 @@
1
1
  // This Source Code Form is subject to the terms of the Mozilla Public
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
- /**
5
- * Shared agent-command-builder types and helpers (#563).
6
- *
7
- * Extracted from `agent/builders.ts` so per-harness builders (e.g.
8
- * `harnesses/claude/agent-builder.ts`) can depend on the common
9
- * types/validation WITHOUT creating an import cycle back through
10
- * `builders.ts` (which imports the per-harness builders into its registry).
11
- * This module is a dependency-graph LEAF for the builder subsystem.
12
- *
13
- * Behaviour-preserving: the type shapes and helper bodies are unchanged from
14
- * their previous home in `builders.ts`.
15
- */
16
- import { UsageError } from "../../core/errors.js";
17
4
  /** Models reaching a harness have already crossed the single model-map boundary. */
18
5
  export function resolveDispatchModel(request, _profile, _platform) {
19
6
  return request.model;
20
7
  }
21
- /**
22
- * Guard against values that start with `--`, which would be mis-interpreted as
23
- * CLI flags by the spawned process when used as flag values (model, systemPrompt).
24
- * Bun.spawn uses array argv so there is no shell injection, but a `--`-prefixed
25
- * value passed as the argument to `--model` or `--system-prompt` can still
26
- * confuse the CLI parser of the target process.
27
- */
28
- export function assertNotFlag(value, field) {
29
- if (value?.trimStart().startsWith("--")) {
30
- throw new UsageError(`${field} must not start with "--": ${JSON.stringify(value.slice(0, 60))}`, "INVALID_FLAG_VALUE");
31
- }
32
- }
33
8
  /**
34
9
  * Normalize a toolPolicy value to a comma-separated string suitable for a
35
10
  * CLI flag. Structured policy objects are JSON-serialized.
@@ -5,7 +5,7 @@ import { randomBytes } from "node:crypto";
5
5
  import fs from "node:fs";
6
6
  import path from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
- import { MAX_CONFIG_FILE_BYTES, readTextFileDescriptorWithLimit, writeFileAtomic } from "../../core/common.js";
8
+ import { writeFileAtomic } from "../../core/common.js";
9
9
  import { ENGINE_NAME_PATTERN_SOURCE } from "../../core/config/engine-semantics.js";
10
10
  import { ConfigError, UsageError } from "../../core/errors.js";
11
11
  import { getConfigDir } from "../../core/paths.js";
@@ -21,9 +21,6 @@ import { cloneExecutionJsonObject } from "../../execution/json.js";
21
21
  * provider mappings.
22
22
  */
23
23
  export const MODEL_MAP_VERSION = 1;
24
- export const MODEL_MAP_MAX_BYTES = MAX_CONFIG_FILE_BYTES;
25
- export const MODEL_MAP_MAX_DEPTH = 64;
26
- export const MODEL_MAP_MAX_NODES = 100_000;
27
24
  let standaloneInstalledModelMapText;
28
25
  const ENGINE_KEY_PATTERN = new RegExp(ENGINE_NAME_PATTERN_SOURCE);
29
26
  const ALIAS_KEY_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
@@ -92,62 +89,8 @@ function parseProfileLayer(value, source, jsonPath) {
92
89
  }
93
90
  return Object.freeze(out);
94
91
  }
95
- /**
96
- * Bound nesting before JSON.parse or any recursive durable-JSON clone runs.
97
- * Brackets inside strings are ignored, including escaped quote sequences.
98
- */
99
- function assertJsonDepthBudget(text, source) {
100
- let depth = 0;
101
- let inString = false;
102
- let escaped = false;
103
- for (const character of text) {
104
- if (inString) {
105
- if (escaped)
106
- escaped = false;
107
- else if (character === "\\")
108
- escaped = true;
109
- else if (character === '"')
110
- inString = false;
111
- continue;
112
- }
113
- if (character === '"') {
114
- inString = true;
115
- continue;
116
- }
117
- if (character === "{" || character === "[") {
118
- depth += 1;
119
- if (depth > MODEL_MAP_MAX_DEPTH) {
120
- invalid(source, "$", `JSON nesting exceeds the ${MODEL_MAP_MAX_DEPTH}-level limit`);
121
- }
122
- }
123
- else if (character === "}" || character === "]") {
124
- depth -= 1;
125
- }
126
- }
127
- }
128
- /** Iterative node budget, deliberately completed before recursive inference cloning. */
129
- function assertJsonNodeBudget(value, source) {
130
- const pending = [value];
131
- let count = 0;
132
- while (pending.length > 0) {
133
- const current = pending.pop();
134
- count += 1;
135
- if (count > MODEL_MAP_MAX_NODES) {
136
- invalid(source, "$", `JSON value exceeds the ${MODEL_MAP_MAX_NODES}-node limit`);
137
- }
138
- if (current !== null && typeof current === "object") {
139
- const children = Array.isArray(current) ? current : Object.values(current);
140
- for (const child of children)
141
- pending.push(child);
142
- }
143
- }
144
- }
145
92
  /** Parse one installed or user layer. Partial structured profiles are valid at this stage. */
146
93
  export function parseModelMapLayer(text, source) {
147
- if (Buffer.byteLength(text, "utf8") > MODEL_MAP_MAX_BYTES) {
148
- invalid(source, "$", `document exceeds the ${MODEL_MAP_MAX_BYTES}-byte limit`);
149
- }
150
- assertJsonDepthBudget(text, source);
151
94
  let parsed;
152
95
  try {
153
96
  parsed = JSON.parse(text);
@@ -155,7 +98,6 @@ export function parseModelMapLayer(text, source) {
155
98
  catch {
156
99
  invalid(source, "$", "invalid JSON syntax");
157
100
  }
158
- assertJsonNodeBudget(parsed, source);
159
101
  const root = requireRecord(parsed, source, "$");
160
102
  assertOnlyKeys(root, ["version", "aliases"], source, "$");
161
103
  if (root.version !== MODEL_MAP_VERSION) {
@@ -314,7 +256,7 @@ function readModelMapFile(filePath, label, optional) {
314
256
  if (!sameFileIdentity(linkStat, openedStat)) {
315
257
  throw new ConfigError(`${label} changed while it was being opened: ${filePath}.`, "INVALID_CONFIG_FILE", "Retry after ensuring no other process is replacing models.json.");
316
258
  }
317
- const text = readTextFileDescriptorWithLimit(fd, MODEL_MAP_MAX_BYTES, label, filePath);
259
+ const text = fs.readFileSync(fd, "utf8");
318
260
  fs.closeSync(fd);
319
261
  fd = undefined;
320
262
  return text;
@@ -67,7 +67,7 @@
67
67
  * name without any further wiring. The registry entry also declares
68
68
  * `structuredOutput: "none"` alongside it (`./index.ts`).
69
69
  */
70
- import { assertNotFlag, resolveDispatchModel, } from "../../agent/builder-shared.js";
70
+ import { resolveDispatchModel } from "../../agent/builder-shared.js";
71
71
  import { createAgentRequestLowerer } from "../../agent/request-lowering.js";
72
72
  /** Canonical harness/platform id used for model-alias resolution. */
73
73
  export const AIDER_PLATFORM = "aider";
@@ -103,8 +103,6 @@ export const aiderBuilder = {
103
103
  outputSchema: true,
104
104
  }),
105
105
  build(profile, req) {
106
- assertNotFlag(req.systemPrompt, "systemPrompt");
107
- assertNotFlag(req.model, "model");
108
106
  const args = [...profile.args];
109
107
  if (req.model) {
110
108
  const resolved = resolveDispatchModel(req, profile, AIDER_PLATFORM);
@@ -32,7 +32,7 @@
32
32
  * - **systemPrompt** — `q chat` has no system-prompt flag (persona/context
33
33
  * comes from Q's own agent config files), so the system prompt is folded
34
34
  * into the positional payload ahead of the task prompt, separated by a
35
- * blank line. `assertNotFlag` still guards it.
35
+ * blank line.
36
36
  * - **schema** — the matrix places Q in the NO-structured-output tier
37
37
  * ("via prompt+validate": *(none documented)* — there is no `--json` or
38
38
  * `--output-format` to ask for). The JSON Schema is therefore passed
@@ -62,7 +62,7 @@
62
62
  * pattern `local-runner`, structuredOutput `none` — is declared alongside it
63
63
  * (`./index.ts`).
64
64
  */
65
- import { assertNotFlag, resolveDispatchModel, } from "../../agent/builder-shared.js";
65
+ import { resolveDispatchModel } from "../../agent/builder-shared.js";
66
66
  import { createAgentRequestLowerer } from "../../agent/request-lowering.js";
67
67
  /** Canonical harness/platform id used for model-alias resolution. */
68
68
  export const AMAZONQ_PLATFORM = "amazonq";
@@ -116,8 +116,6 @@ export const amazonqBuilder = {
116
116
  outputSchema: true,
117
117
  }),
118
118
  build(profile, req) {
119
- assertNotFlag(req.systemPrompt, "systemPrompt");
120
- assertNotFlag(req.model, "model");
121
119
  // Built-in q profiles would ship `args: []`; headless dispatch is the
122
120
  // `chat` subcommand. Don't double it when a user profile already pins it.
123
121
  const extra = profile.args[0] === "chat" ? profile.args.slice(1) : [...profile.args];
@@ -129,9 +127,6 @@ export const amazonqBuilder = {
129
127
  // flags: dropping a restriction must never widen to --trust-all-tools.
130
128
  const entries = toolPolicyEntries(req.tools);
131
129
  if (entries !== undefined) {
132
- for (const tool of entries) {
133
- assertNotFlag(tool, "tools entry");
134
- }
135
130
  // Q's documented allowlist form is equals-joined and comma-separated
136
131
  // (`--trust-tools=fs_read,fs_write`); an empty list trusts no tools.
137
132
  args.push(`--trust-tools=${entries.join(",")}`);
@@ -34,7 +34,7 @@
34
34
  *
35
35
  * The builder's `platform` stays `'claude'` (the canonical harness id).
36
36
  */
37
- import { assertNotFlag, normalizeTools, resolveDispatchModel, } from "../../agent/builder-shared.js";
37
+ import { normalizeTools, resolveDispatchModel, } from "../../agent/builder-shared.js";
38
38
  import { createAgentRequestLowerer } from "../../agent/request-lowering.js";
39
39
  /**
40
40
  * Assemble the positional prompt: the task prompt and — when a schema is
@@ -68,9 +68,6 @@ export const claudeBuilder = {
68
68
  outputSchema: true,
69
69
  }),
70
70
  build(profile, req) {
71
- assertNotFlag(req.systemPrompt, "systemPrompt");
72
- assertNotFlag(req.model, "model");
73
- assertNotFlag(req.agent, "agent");
74
71
  const args = [...profile.args];
75
72
  if (req.agent) {
76
73
  args.push("--agent", req.agent);
@@ -55,7 +55,7 @@
55
55
  import { mkdtempSync, writeFileSync } from "node:fs";
56
56
  import { tmpdir } from "node:os";
57
57
  import { join } from "node:path";
58
- import { assertNotFlag, resolveDispatchModel } from "../../agent/builder-shared.js";
58
+ import { resolveDispatchModel } from "../../agent/builder-shared.js";
59
59
  import { createAgentRequestLowerer } from "../../agent/request-lowering.js";
60
60
  /**
61
61
  * Write a node's JSON Schema to a fresh temp file for `--output-schema`.
@@ -77,7 +77,6 @@ export function writeCodexOutputSchemaFile(schema) {
77
77
  * extractor); akm never depends on it (plan §"Session, MCP, and identity").
78
78
  */
79
79
  export function codexResumeArgs(sessionId) {
80
- assertNotFlag(sessionId, "sessionId");
81
80
  return ["exec", "resume", sessionId];
82
81
  }
83
82
  /**
@@ -114,8 +113,6 @@ export const codexBuilder = {
114
113
  outputSchema: true,
115
114
  }),
116
115
  build(profile, req) {
117
- assertNotFlag(req.systemPrompt, "systemPrompt");
118
- assertNotFlag(req.model, "model");
119
116
  // Built-in codex profiles ship `args: []`; headless dispatch is the `exec`
120
117
  // subcommand. Don't double it when a user profile already pins it.
121
118
  const extra = profile.args[0] === "exec" ? profile.args.slice(1) : [...profile.args];
@@ -21,8 +21,7 @@
21
21
  * - **systemPrompt** — Copilot CLI has no system-prompt flag (it reads
22
22
  * repo/user custom-instructions files instead), so the system prompt is
23
23
  * folded into the `-p` payload ahead of the task prompt, separated by a
24
- * blank line. `assertNotFlag` still guards it so a `--`-prefixed system
25
- * prompt cannot turn the front of the `-p` value into a flag.
24
+ * blank line.
26
25
  * - **schema** — the matrix places Copilot in the "via prompt+validate" tier
27
26
  * (no native `--output-schema` equivalent, unlike Codex), so the JSON
28
27
  * Schema is passed through the prompt: a directive matching the engine's
@@ -45,7 +44,7 @@
45
44
  * that registry, so this builder is reachable under the `"copilot"` platform
46
45
  * name without any further wiring.
47
46
  */
48
- import { assertNotFlag, resolveDispatchModel, } from "../../agent/builder-shared.js";
47
+ import { resolveDispatchModel } from "../../agent/builder-shared.js";
49
48
  import { createAgentRequestLowerer } from "../../agent/request-lowering.js";
50
49
  /** Canonical harness/platform id used for model-alias resolution. */
51
50
  export const COPILOT_PLATFORM = "copilot";
@@ -98,8 +97,6 @@ export const copilotBuilder = {
98
97
  outputSchema: true,
99
98
  }),
100
99
  build(profile, req) {
101
- assertNotFlag(req.systemPrompt, "systemPrompt");
102
- assertNotFlag(req.model, "model");
103
100
  const args = [...profile.args];
104
101
  if (req.model) {
105
102
  const resolved = resolveDispatchModel(req, profile, COPILOT_PLATFORM);
@@ -110,7 +107,6 @@ export const copilotBuilder = {
110
107
  // Structured policy objects (entries === undefined) emit NO allow flags:
111
108
  // dropping a restriction must never widen to --allow-all-tools.
112
109
  for (const tool of entries ?? []) {
113
- assertNotFlag(tool, "tools entry");
114
110
  args.push("--allow-tool", tool);
115
111
  }
116
112
  }
@@ -23,9 +23,7 @@
23
23
  * - **systemPrompt** — Gemini CLI has no system-prompt flag in headless mode
24
24
  * (system text comes from `GEMINI.md` context files / `GEMINI_SYSTEM_MD`),
25
25
  * so the system prompt is folded into the `-p` payload ahead of the task
26
- * prompt, separated by a blank line. `assertNotFlag` still guards it so a
27
- * `--`-prefixed system prompt cannot turn the front of the `-p` value into
28
- * a flag.
26
+ * prompt, separated by a blank line.
29
27
  * - **schema** — the matrix places Gemini in the "via prompt+validate" tier
30
28
  * (no native `--output-schema` equivalent, unlike Codex — so no temp-file
31
29
  * plumbing here), so the JSON Schema is passed through the prompt: a
@@ -49,7 +47,7 @@
49
47
  * that registry, so this builder is reachable under the `"gemini"` platform
50
48
  * name without any further wiring.
51
49
  */
52
- import { assertNotFlag, resolveDispatchModel, } from "../../agent/builder-shared.js";
50
+ import { resolveDispatchModel } from "../../agent/builder-shared.js";
53
51
  import { createAgentRequestLowerer } from "../../agent/request-lowering.js";
54
52
  /** Canonical harness/platform id used for model-alias resolution. */
55
53
  export const GEMINI_PLATFORM = "gemini";
@@ -102,8 +100,6 @@ export const geminiBuilder = {
102
100
  outputSchema: true,
103
101
  }),
104
102
  build(profile, req) {
105
- assertNotFlag(req.systemPrompt, "systemPrompt");
106
- assertNotFlag(req.model, "model");
107
103
  const args = [...profile.args];
108
104
  if (req.model) {
109
105
  const resolved = resolveDispatchModel(req, profile, GEMINI_PLATFORM);
@@ -113,7 +109,6 @@ export const geminiBuilder = {
113
109
  // Structured policy objects (entries === undefined) emit NO flags:
114
110
  // dropping a restriction must never widen to auto-approval.
115
111
  for (const tool of toolPolicyEntries(req.tools) ?? []) {
116
- assertNotFlag(tool, "tools entry");
117
112
  args.push("--allowed-tools", tool);
118
113
  }
119
114
  }
@@ -14,7 +14,7 @@
14
14
  * pre-migration `opencodeBuilder`. The builder's `platform` stays `'opencode'`
15
15
  * (the canonical harness id).
16
16
  */
17
- import { assertNotFlag, resolveDispatchModel } from "../../agent/builder-shared.js";
17
+ import { resolveDispatchModel } from "../../agent/builder-shared.js";
18
18
  import { createAgentRequestLowerer } from "../../agent/request-lowering.js";
19
19
  /**
20
20
  * OpenCode builder.
@@ -34,9 +34,6 @@ export const opencodeBuilder = {
34
34
  outputSchema: false,
35
35
  }),
36
36
  build(profile, req) {
37
- assertNotFlag(req.systemPrompt, "systemPrompt");
38
- assertNotFlag(req.model, "model");
39
- assertNotFlag(req.agent, "agent");
40
37
  const args = req.model ? [] : [...profile.args];
41
38
  if (req.model) {
42
39
  for (let index = 0; index < profile.args.length; index += 1) {
@@ -92,6 +92,7 @@
92
92
  */
93
93
  import { spawn } from "node:child_process";
94
94
  import { createHash } from "node:crypto";
95
+ import { COMMON_SPAWN_ENV_PASSTHROUGH, spawnEnvNamesFor } from "../../../core/spawn-env.js";
95
96
  import { DEFAULT_AGENT_TIMEOUT_MS } from "../../agent/config.js";
96
97
  // Server registry — one server per complete server-material signature. Caller
97
98
  // deadlines race the shared promise independently; they never become startup
@@ -269,24 +270,9 @@ function serverRegistryKey(profile, env) {
269
270
  .update(JSON.stringify(canonicalize(material)))
270
271
  .digest("hex");
271
272
  }
272
- const OPENCODE_SDK_SERVER_ENV_NAMES = [
273
- "HOME",
274
- "PATH",
275
- "USER",
276
- "LANG",
277
- "LC_ALL",
278
- "TERM",
279
- "TMPDIR",
280
- "SYSTEMROOT",
281
- "COMSPEC",
282
- "PATHEXT",
283
- "WINDIR",
284
- "TEMP",
285
- "TMP",
286
- ];
287
273
  /** @internal Exact environment allowlist used to start the OpenCode SDK server. */
288
274
  export function opencodeSdkServerEnvironmentNames(profile) {
289
- return [...new Set([...OPENCODE_SDK_SERVER_ENV_NAMES, ...(profile.envPassthrough ?? [])])];
275
+ return [...new Set([...spawnEnvNamesFor(COMMON_SPAWN_ENV_PASSTHROUGH), ...(profile.envPassthrough ?? [])])];
290
276
  }
291
277
  function buildServerEnv(profile, config, bindings, envSource) {
292
278
  const env = {};
@@ -71,7 +71,7 @@
71
71
  * `pattern: "local-runner"`, `structuredOutput: "native-json"` alongside it
72
72
  * (`./index.ts`).
73
73
  */
74
- import { assertNotFlag, resolveDispatchModel, } from "../../agent/builder-shared.js";
74
+ import { resolveDispatchModel } from "../../agent/builder-shared.js";
75
75
  import { createAgentRequestLowerer } from "../../agent/request-lowering.js";
76
76
  /** Canonical harness/platform id used for model-alias resolution. */
77
77
  export const OPENHANDS_PLATFORM = "openhands";
@@ -115,8 +115,6 @@ export const openhandsBuilder = {
115
115
  outputSchema: true,
116
116
  }),
117
117
  build(profile, req) {
118
- assertNotFlag(req.systemPrompt, "systemPrompt");
119
- assertNotFlag(req.model, "model");
120
118
  const args = [...profile.args];
121
119
  // Headless essentials (matrix shape): non-interactive run, JSONL stdout
122
120
  // for the extractor.
@@ -25,7 +25,7 @@
25
25
  * builders, so a prompt whose text begins with `-`/`--` can never be parsed
26
26
  * as flags.
27
27
  * - **systemPrompt** — passed via `--system-prompt` (Pi follows the Claude
28
- * Code flag conventions), guarded by `assertNotFlag`.
28
+ * Code flag conventions).
29
29
  * - **schema** — the matrix places Pi in the "via prompt+validate" tier (no
30
30
  * native `--output-schema` equivalent, unlike Codex), so the JSON Schema is
31
31
  * passed through the prompt: a directive matching the engine's wording
@@ -50,7 +50,7 @@
50
50
  * wiring. The `PI_SESSION_ID` identity-env marker is declared alongside it
51
51
  * (`./index.ts`).
52
52
  */
53
- import { assertNotFlag, resolveDispatchModel, } from "../../agent/builder-shared.js";
53
+ import { resolveDispatchModel } from "../../agent/builder-shared.js";
54
54
  import { createAgentRequestLowerer } from "../../agent/request-lowering.js";
55
55
  /** Canonical harness/platform id used for model-alias resolution. */
56
56
  export const PI_PLATFORM = "pi";
@@ -81,8 +81,6 @@ export const piBuilder = {
81
81
  outputSchema: true,
82
82
  }),
83
83
  build(profile, req) {
84
- assertNotFlag(req.systemPrompt, "systemPrompt");
85
- assertNotFlag(req.model, "model");
86
84
  const args = [...profile.args];
87
85
  if (req.systemPrompt) {
88
86
  args.push("--system-prompt", req.systemPrompt);
@@ -133,12 +133,3 @@ export function collectSessionEvents(harnesses, sinceMs, maxSessionsPerHarness =
133
133
  }
134
134
  return events;
135
135
  }
136
- /**
137
- * Scan recent session logs from all available harnesses and return
138
- * repeated failure patterns that might warrant new AKM assets.
139
- */
140
- export function getExecutionLogCandidates(sinceDays = 7) {
141
- const sinceMs = Date.now() - sinceDays * 24 * 60 * 60 * 1000;
142
- const events = collectSessionEvents(getAvailableHarnesses(), sinceMs);
143
- return aggregateSessionEvents(events);
144
- }