akm-cli 0.9.0-beta.5 → 0.9.0-beta.51

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 (221) hide show
  1. package/CHANGELOG.md +711 -0
  2. package/README.md +12 -4
  3. package/dist/akm +38 -0
  4. package/dist/akm-migrate-storage +38 -0
  5. package/dist/assets/profiles/default.json +9 -4
  6. package/dist/assets/profiles/frequent.json +1 -1
  7. package/dist/assets/profiles/memory-focus.json +1 -1
  8. package/dist/assets/profiles/quick.json +1 -1
  9. package/dist/assets/profiles/synthesize.json +15 -0
  10. package/dist/assets/profiles/thorough.json +1 -1
  11. package/dist/assets/prompts/consolidate-system.md +23 -0
  12. package/dist/assets/prompts/contradiction-judge.md +33 -0
  13. package/dist/assets/prompts/distill-knowledge-system.md +22 -0
  14. package/dist/assets/prompts/distill-lesson-system.md +36 -0
  15. package/dist/assets/prompts/extract-session.md +6 -2
  16. package/dist/assets/prompts/graph-extract-system.md +1 -0
  17. package/dist/assets/prompts/graph-extract-user-prompt.md +1 -1
  18. package/dist/assets/prompts/memory-infer-system.md +1 -0
  19. package/dist/assets/prompts/memory-infer-user.md +5 -0
  20. package/dist/assets/prompts/metadata-enhance-system.md +1 -0
  21. package/dist/assets/prompts/procedural-system.md +44 -0
  22. package/dist/assets/prompts/recombine-system.md +40 -0
  23. package/dist/assets/prompts/staleness-detect-system.md +6 -0
  24. package/dist/assets/prompts/validate-summary-judge.md +1 -0
  25. package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +38 -0
  26. package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +38 -0
  27. package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +39 -0
  28. package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +40 -0
  29. package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +43 -0
  30. package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +38 -0
  31. package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +43 -0
  32. package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +40 -0
  33. package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +43 -0
  34. package/dist/assets/templates/html/health.html +281 -111
  35. package/dist/assets/wiki/ingest-workflow-template.md +38 -10
  36. package/dist/cli/parse-args.js +46 -1
  37. package/dist/cli/shared.js +28 -0
  38. package/dist/cli.js +27 -11
  39. package/dist/commands/agent/agent-dispatch.js +2 -2
  40. package/dist/commands/agent/agent-support.js +0 -7
  41. package/dist/commands/agent/contribute-cli.js +17 -4
  42. package/dist/commands/config-cli.js +18 -2
  43. package/dist/commands/env/child-env.js +47 -0
  44. package/dist/commands/env/env-cli.js +33 -26
  45. package/dist/commands/env/secret-cli.js +36 -22
  46. package/dist/commands/feedback-cli.js +15 -6
  47. package/dist/commands/graph/graph-cli.js +5 -13
  48. package/dist/commands/graph/graph.js +76 -72
  49. package/dist/commands/health/checks.js +49 -1
  50. package/dist/commands/health/html-report.js +422 -80
  51. package/dist/commands/health.js +386 -9
  52. package/dist/commands/improve/calibration.js +161 -0
  53. package/dist/commands/improve/consolidate/chunking.js +141 -0
  54. package/dist/commands/improve/consolidate/eligibility.js +81 -0
  55. package/dist/commands/improve/consolidate/merge.js +145 -0
  56. package/dist/commands/improve/consolidate/sanitize.js +231 -0
  57. package/dist/commands/{lint.js → improve/consolidate/types.js} +1 -1
  58. package/dist/commands/improve/consolidate.js +635 -660
  59. package/dist/commands/improve/dedup.js +482 -0
  60. package/dist/commands/improve/distill.js +159 -69
  61. package/dist/commands/improve/eligibility.js +434 -0
  62. package/dist/commands/improve/encoding-salience.js +205 -0
  63. package/dist/commands/improve/extract-cli.js +124 -2
  64. package/dist/commands/improve/extract-prompt.js +39 -2
  65. package/dist/commands/improve/extract-watch.js +140 -0
  66. package/dist/commands/improve/extract.js +389 -40
  67. package/dist/commands/improve/feedback-valence.js +54 -0
  68. package/dist/commands/improve/homeostatic.js +467 -0
  69. package/dist/commands/improve/improve-auto-accept.js +138 -7
  70. package/dist/commands/improve/improve-cli.js +36 -61
  71. package/dist/commands/improve/improve-profiles.js +14 -0
  72. package/dist/commands/improve/improve-result-file.js +14 -25
  73. package/dist/commands/improve/improve-session.js +58 -0
  74. package/dist/commands/improve/improve.js +485 -2498
  75. package/dist/commands/improve/locks.js +154 -0
  76. package/dist/commands/improve/loop-stages.js +1083 -0
  77. package/dist/commands/improve/memory/memory-contradiction-detect.js +23 -28
  78. package/dist/commands/improve/outcome-loop.js +256 -0
  79. package/dist/commands/improve/preparation.js +1966 -0
  80. package/dist/commands/improve/proactive-maintenance.js +115 -0
  81. package/dist/commands/improve/procedural.js +418 -0
  82. package/dist/commands/improve/recombine.js +850 -0
  83. package/dist/commands/improve/reflect-noise.js +0 -0
  84. package/dist/commands/improve/reflect.js +183 -40
  85. package/dist/commands/improve/salience.js +438 -0
  86. package/dist/commands/improve/triage.js +93 -0
  87. package/dist/commands/lint/agent-linter.js +19 -24
  88. package/dist/commands/lint/base-linter.js +173 -60
  89. package/dist/commands/lint/command-linter.js +19 -24
  90. package/dist/commands/lint/env-key-rules.js +38 -1
  91. package/dist/commands/lint/fact-linter.js +39 -0
  92. package/dist/commands/lint/index.js +31 -13
  93. package/dist/commands/lint/memory-linter.js +1 -1
  94. package/dist/commands/lint/registry.js +7 -2
  95. package/dist/commands/lint/task-linter.js +3 -3
  96. package/dist/commands/lint/workflow-linter.js +26 -1
  97. package/dist/commands/proposal/drain-policies.js +5 -0
  98. package/dist/commands/proposal/drain.js +43 -50
  99. package/dist/commands/proposal/proposal-cli.js +21 -31
  100. package/dist/commands/proposal/proposal.js +5 -0
  101. package/dist/commands/proposal/propose.js +7 -2
  102. package/dist/commands/proposal/validators/proposal-quality-validators.js +9 -8
  103. package/dist/commands/proposal/validators/proposals.js +189 -63
  104. package/dist/commands/read/curate.js +414 -94
  105. package/dist/commands/read/knowledge.js +6 -3
  106. package/dist/commands/read/search-cli.js +9 -4
  107. package/dist/commands/read/search.js +10 -6
  108. package/dist/commands/read/show.js +86 -7
  109. package/dist/commands/sources/init.js +49 -17
  110. package/dist/commands/sources/installed-stashes.js +11 -3
  111. package/dist/commands/sources/schema-repair.js +43 -45
  112. package/dist/commands/sources/self-update.js +2 -2
  113. package/dist/commands/sources/source-add.js +7 -3
  114. package/dist/commands/sources/stash-cli.js +28 -40
  115. package/dist/commands/sources/stash-skeleton.js +23 -8
  116. package/dist/commands/tasks/tasks-cli.js +19 -27
  117. package/dist/commands/tasks/tasks.js +39 -11
  118. package/dist/commands/wiki-cli.js +21 -35
  119. package/dist/core/asset/asset-registry.js +3 -1
  120. package/dist/core/asset/asset-spec.js +18 -2
  121. package/dist/core/asset/frontmatter.js +166 -167
  122. package/dist/core/asset/markdown.js +8 -0
  123. package/dist/core/authoring-rules.js +92 -0
  124. package/dist/core/common.js +0 -5
  125. package/dist/core/config/config-migration.js +12 -11
  126. package/dist/core/config/config-schema.js +340 -56
  127. package/dist/core/config/config-types.js +3 -3
  128. package/dist/core/config/config.js +28 -7
  129. package/dist/core/events.js +3 -7
  130. package/dist/core/improve-types.js +11 -8
  131. package/dist/core/logs-db.js +10 -66
  132. package/dist/core/parse.js +36 -16
  133. package/dist/core/paths.js +3 -0
  134. package/dist/core/standards/resolve-standards-context.js +87 -0
  135. package/dist/core/standards/resolve-stash-standards.js +99 -0
  136. package/dist/core/standards/resolve-type-conventions.js +66 -0
  137. package/dist/core/state/migrations.js +714 -0
  138. package/dist/core/state-db.js +525 -474
  139. package/dist/indexer/db/db.js +439 -247
  140. package/dist/indexer/db/graph-db.js +129 -86
  141. package/dist/indexer/ensure-index.js +152 -17
  142. package/dist/indexer/graph/graph-boost.js +51 -41
  143. package/dist/indexer/graph/graph-extraction.js +218 -4
  144. package/dist/indexer/index-writer-lock.js +99 -0
  145. package/dist/indexer/indexer.js +123 -221
  146. package/dist/indexer/passes/dir-staleness.js +114 -0
  147. package/dist/indexer/passes/memory-inference.js +13 -5
  148. package/dist/indexer/passes/staleness-detect.js +2 -5
  149. package/dist/indexer/search/db-search.js +19 -6
  150. package/dist/indexer/search/ranking-contributors.js +22 -0
  151. package/dist/indexer/search/ranking.js +4 -0
  152. package/dist/indexer/search/search-source.js +17 -18
  153. package/dist/indexer/search/semantic-status.js +4 -0
  154. package/dist/indexer/walk/matchers.js +9 -0
  155. package/dist/integrations/agent/config.js +6 -53
  156. package/dist/integrations/agent/index.js +2 -18
  157. package/dist/integrations/agent/prompts.js +75 -9
  158. package/dist/integrations/agent/runner-dispatch.js +59 -0
  159. package/dist/integrations/harnesses/claude/session-log.js +11 -1
  160. package/dist/integrations/harnesses/index.js +2 -3
  161. package/dist/integrations/harnesses/opencode/session-log.js +173 -3
  162. package/dist/integrations/harnesses/opencode-sdk/index.js +2 -2
  163. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +0 -2
  164. package/dist/integrations/session-logs/index.js +16 -0
  165. package/dist/llm/client.js +45 -15
  166. package/dist/llm/embedder.js +42 -3
  167. package/dist/llm/embedders/deterministic.js +66 -0
  168. package/dist/llm/embedders/local.js +66 -2
  169. package/dist/llm/feature-gate.js +8 -4
  170. package/dist/llm/graph-extract.js +67 -44
  171. package/dist/llm/memory-infer-impl.js +138 -0
  172. package/dist/llm/memory-infer.js +1 -127
  173. package/dist/llm/metadata-enhance.js +44 -31
  174. package/dist/llm/structured-call.js +49 -0
  175. package/dist/migrate-storage-node.mjs +8 -0
  176. package/dist/output/context.js +5 -5
  177. package/dist/output/renderers.js +74 -2
  178. package/dist/output/shapes/curate.js +14 -2
  179. package/dist/output/shapes/passthrough.js +0 -1
  180. package/dist/output/text/helpers.js +16 -1
  181. package/dist/registry/providers/skills-sh.js +21 -147
  182. package/dist/registry/providers/static-index.js +15 -157
  183. package/dist/registry/resolve.js +22 -9
  184. package/dist/runtime.js +25 -1
  185. package/dist/scripts/migrate-storage.js +2617 -1961
  186. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +759 -510
  187. package/dist/setup/setup.js +29 -8
  188. package/dist/sources/include.js +6 -2
  189. package/dist/sources/providers/filesystem.js +0 -1
  190. package/dist/sources/providers/git-install.js +210 -0
  191. package/dist/sources/providers/git-provider.js +234 -0
  192. package/dist/sources/providers/git-stash.js +248 -0
  193. package/dist/sources/providers/git.js +10 -661
  194. package/dist/sources/providers/npm.js +2 -6
  195. package/dist/sources/providers/provider-utils.js +13 -7
  196. package/dist/sources/providers/sync-from-ref.js +9 -1
  197. package/dist/sources/providers/tar-utils.js +16 -8
  198. package/dist/sources/providers/website.js +9 -5
  199. package/dist/sources/website-ingest.js +187 -29
  200. package/dist/sources/wiki-fetchers/registry.js +53 -0
  201. package/dist/sources/wiki-fetchers/youtube.js +239 -0
  202. package/dist/storage/database.js +45 -10
  203. package/dist/storage/managed-db.js +82 -0
  204. package/dist/storage/repositories/registry-cache.js +92 -0
  205. package/dist/storage/sqlite-pragmas.js +146 -0
  206. package/dist/tasks/backends/cron.js +1 -1
  207. package/dist/tasks/backends/launchd.js +1 -1
  208. package/dist/tasks/backends/schtasks.js +1 -1
  209. package/dist/tasks/{resolveAkmBin.js → resolve-akm-bin.js} +2 -2
  210. package/dist/tasks/runner.js +5 -13
  211. package/dist/text-import-hook.mjs +0 -0
  212. package/dist/wiki/wiki.js +37 -0
  213. package/dist/workflows/db.js +3 -4
  214. package/dist/workflows/runtime/runs.js +1 -117
  215. package/dist/workflows/runtime/workflow-asset-loader.js +125 -0
  216. package/dist/workflows/validate-summary.js +2 -7
  217. package/docs/data-and-telemetry.md +3 -2
  218. package/docs/migration/release-notes/0.9.0.md +39 -0
  219. package/package.json +13 -11
  220. package/dist/commands/db-cli.js +0 -23
  221. package/dist/indexer/db/db-backup.js +0 -376
@@ -2,6 +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 { parseWorkflow } from "../../workflows/parser.js";
5
6
  import { BaseLinter } from "./base-linter.js";
6
7
  const PLACEHOLDER_STRINGS = ["Describe what this workflow accomplishes", "Example Workflow"];
7
8
  /**
@@ -32,7 +33,7 @@ export class WorkflowLinter extends BaseLinter {
32
33
  file: ctx.relPath,
33
34
  issue: "placeholder-stub",
34
35
  detail: `could not delete: ${e instanceof Error ? e.message : String(e)}`,
35
- fixed: false,
36
+ fixed: "failed",
36
37
  });
37
38
  }
38
39
  return issues;
@@ -44,6 +45,30 @@ export class WorkflowLinter extends BaseLinter {
44
45
  fixed: false,
45
46
  });
46
47
  }
48
+ const isReadOnly = ctx.filePath.includes("/.cache/") || ctx.filePath.includes("/registry/");
49
+ if (!isReadOnly) {
50
+ try {
51
+ const result = parseWorkflow(ctx.raw, { path: ctx.filePath });
52
+ if (!result.ok) {
53
+ for (const err of result.errors ?? []) {
54
+ issues.push({
55
+ file: ctx.relPath,
56
+ issue: "invalid-workflow-structure",
57
+ detail: err.message ?? String(err),
58
+ fixed: false,
59
+ });
60
+ }
61
+ }
62
+ }
63
+ catch (e) {
64
+ issues.push({
65
+ file: ctx.relPath,
66
+ issue: "invalid-workflow-structure",
67
+ detail: `workflow parser error: ${e instanceof Error ? e.message : String(e)}`,
68
+ fixed: false,
69
+ });
70
+ }
71
+ }
47
72
  return issues;
48
73
  }
49
74
  #checkPlaceholderStub(body) {
@@ -45,6 +45,10 @@ export const PERSONAL_STASH = {
45
45
  { generator: "reflect", maxDiffLines: 80 },
46
46
  // Consolidate within the diff band; mid-band lands in `defer` below.
47
47
  { generator: "consolidate", maxDiffLines: 200 },
48
+ // Recombine: accept only confirmed type:lesson proposals (promoted by the
49
+ // recombine confidence gate) within the diff band. type:hypothesis proposals
50
+ // have no matching rule here and stay pending for manual review.
51
+ { generator: "recombine", requireType: "lesson", maxDiffLines: 200 },
48
52
  ],
49
53
  rejectEmpty: true,
50
54
  // Mid-band consolidate, distill duplicates, and contradiction escalations are
@@ -83,6 +87,7 @@ const DrainAcceptRuleSchema = z
83
87
  generator: GeneratorSchema,
84
88
  maxDiffLines: z.number().int().positive().optional(),
85
89
  minContentLines: z.number().int().nonnegative().optional(),
90
+ requireType: z.string().optional(),
86
91
  })
87
92
  .strict();
88
93
  const DrainPolicySchema = z
@@ -9,7 +9,7 @@
9
9
  * the engine behind `akm proposal drain` and (later) the `triage` improve
10
10
  * pre-pass.
11
11
  *
12
- * Design (see docs/technical/proposal-triage-implementation-plan.md):
12
+ * Design (see docs/archive/proposal-triage-implementation-plan.md):
13
13
  * - Reuses `listProposals` (no source filter — generator filtering is
14
14
  * in-memory) and the `akmProposalAccept` / `akmProposalReject` wrappers from
15
15
  * `proposal.ts` so the standard `promoted` / `rejected` events are emitted.
@@ -36,14 +36,12 @@
36
36
  */
37
37
  import fs from "node:fs";
38
38
  import path from "node:path";
39
- import { assertNever } from "../../core/assert.js";
40
39
  import { parseAssetRef } from "../../core/asset/asset-ref.js";
41
40
  import { resolveAssetPathFromName, TYPE_DIRS } from "../../core/asset/asset-spec.js";
42
41
  import { parseFrontmatter } from "../../core/asset/frontmatter.js";
43
42
  import { appendEvent } from "../../core/events.js";
44
43
  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";
44
+ import { executeRunner } from "../../integrations/agent/runner-dispatch.js";
47
45
  import { chatCompletion, stripJsonFences } from "../../llm/client.js";
48
46
  import { akmProposalAccept, akmProposalReject } from "./proposal.js";
49
47
  import { listProposals, recordGateDecision } from "./validators/proposals.js";
@@ -80,7 +78,16 @@ export function classifyProposal(proposal, policy, maxDiffLines) {
80
78
  if (policy.rejectEmpty && isEmptyDiff(proposal)) {
81
79
  return { verdict: "reject", reason: "empty diff", gate: { reason: "empty-diff" } };
82
80
  }
83
- const rule = policy.accept.find((r) => r.generator === proposal.source);
81
+ const rule = policy.accept.find((r) => {
82
+ if (r.generator !== proposal.source)
83
+ return false;
84
+ if (r.requireType !== undefined) {
85
+ const fm = parseFrontmatter(proposal.payload.content ?? "").data;
86
+ if (typeof fm.type !== "string" || fm.type !== r.requireType)
87
+ return false;
88
+ }
89
+ return true;
90
+ });
84
91
  if (rule) {
85
92
  const lines = contentLineCount(content);
86
93
  const body = contentBodyLineCount(content);
@@ -200,59 +207,38 @@ export function parseJudgmentVerdict(raw) {
200
207
  return { decision, reason: typeof reason === "string" ? reason : "" };
201
208
  }
202
209
  /**
203
- * Dispatch a single judgment prompt to the resolved runner. The switch mirrors
204
- * the canonical consumer at `reflect.ts:1060-1090`: llm `chatCompletion`
205
- * (no filesystem), agent `runAgent`, sdk `runOpencodeSdk`.
210
+ * Dispatch a single judgment prompt to the resolved runner via the unified
211
+ * {@link executeRunner} seam (X3). The `llm` arm is drain-specific (wraps
212
+ * `chatCompletion` — no filesystem) so it is supplied as the `llm` handler; the
213
+ * byte-identical `agent` / `sdk` arms route to the default profile runners (or
214
+ * the injected {@link JudgmentSeams} test fakes). A failed spawn warns and
215
+ * yields `null`, matching the prior per-arm behavior.
206
216
  */
207
217
  async function dispatchJudgment(runner, prompt, seams) {
208
- let raw;
209
- switch (runner.kind) {
210
- case "llm": {
211
- const messages = [{ role: "user", content: prompt }];
212
- raw = seams.chat
213
- ? await seams.chat(runner, messages)
214
- : await chatCompletion(runner.connection, messages, {
215
- ...(runner.timeoutMs !== undefined ? { timeoutMs: runner.timeoutMs } : {}),
216
- });
217
- break;
218
- }
219
- case "agent": {
220
- const stdout = await runProfileJudgment(seams.runAgentFn ?? runAgent, runner.profile, prompt, "agent", runner.timeoutMs);
221
- if (stdout === null)
222
- return null;
223
- raw = stdout;
224
- break;
225
- }
226
- case "sdk": {
227
- const stdout = await runProfileJudgment(seams.runSdkFn ?? runOpencodeSdk, runner.profile, prompt, "sdk", runner.timeoutMs);
228
- if (stdout === null)
229
- return null;
230
- raw = stdout;
231
- break;
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);
237
- }
238
- return parseJudgmentVerdict(raw);
239
- }
240
- /**
241
- * Shared spawn-runner dispatch for the byte-identical `agent` / `sdk` arms: run
242
- * the profile-based runner, log a labelled warning on failure, and return the
243
- * captured stdout (or `null` on failure). `label` only changes the warn prefix.
244
- */
245
- async function runProfileJudgment(run, profile, prompt, label, timeoutMs) {
246
- const result = await run(profile, prompt, {
218
+ const runOptions = {
247
219
  stdio: "captured",
248
220
  parseOutput: "text",
249
- ...(timeoutMs !== undefined ? { timeoutMs } : {}),
221
+ ...(runner.timeoutMs !== undefined ? { timeoutMs: runner.timeoutMs } : {}),
222
+ };
223
+ const result = await executeRunner(runner, prompt, runOptions, {
224
+ llm: async (spec, p) => {
225
+ const messages = [{ role: "user", content: p }];
226
+ const raw = seams.chat
227
+ ? await seams.chat(spec, messages)
228
+ : await chatCompletion(spec.connection, messages, {
229
+ ...(spec.timeoutMs !== undefined ? { timeoutMs: spec.timeoutMs } : {}),
230
+ });
231
+ // chatCompletion has no failure envelope — a returned string is success.
232
+ return { ok: true, exitCode: 0, stdout: raw, stderr: "", durationMs: 0 };
233
+ },
234
+ ...(seams.runAgentFn ? { runAgent: seams.runAgentFn } : {}),
235
+ ...(seams.runSdkFn ? { runSdk: seams.runSdkFn } : {}),
250
236
  });
251
237
  if (!result.ok) {
252
- warn(`[triage] judgment ${label} failed: ${result.error ?? result.reason ?? "unknown error"}`);
238
+ warn(`[triage] judgment ${runner.kind} failed: ${result.error ?? result.reason ?? "unknown error"}`);
253
239
  return null;
254
240
  }
255
- return result.stdout;
241
+ return parseJudgmentVerdict(result.stdout);
256
242
  }
257
243
  /**
258
244
  * Run the judgment tier over the deferred items. The runner only *judges*; the
@@ -361,6 +347,13 @@ export async function drainProposals(opts, promoteFn = akmProposalAccept, reject
361
347
  // — these are re-stamped `no-judge-configured` when no runner resolves them.
362
348
  const needsJudge = new Set();
363
349
  for (const proposal of pending) {
350
+ // Do NOT reclassify a proposal that was already conclusively stamped
351
+ // `auto-rejected` by a prior gate run (e.g. the improve confidence gate).
352
+ // Overwriting an authoritative rejection with `auto-accepted` would corrupt
353
+ // the audit trail and silently promote content the gate explicitly rejected.
354
+ // Such proposals remain pending for manual review (or TTL expiry).
355
+ if (proposal.gateDecision?.outcome === "auto-rejected")
356
+ continue;
364
357
  const decision = classifyProposal(proposal, opts.policy, opts.maxDiffLines);
365
358
  if (decision === null)
366
359
  continue;
@@ -9,9 +9,8 @@
9
9
  * and emits the same JSON envelope (stdout/stderr/exit-code) as the inline
10
10
  * `runWithJsonErrors` form it replaces.
11
11
  */
12
- import { defineCommand } from "citty";
13
- import { hasSubcommand, parsePositiveIntFlag } from "../../cli/parse-args.js";
14
- import { defineJsonCommand, output, runWithJsonErrors } from "../../cli/shared.js";
12
+ import { parsePositiveIntFlag } from "../../cli/parse-args.js";
13
+ import { defineGroupCommand, defineJsonCommand, output } from "../../cli/shared.js";
15
14
  import { resolveStashDir } from "../../core/common.js";
16
15
  import { loadConfig } from "../../core/config/config.js";
17
16
  import { UsageError } from "../../core/errors.js";
@@ -448,19 +447,7 @@ const proposalDrainCommand = defineJsonCommand({
448
447
  // (`proposals`/`accept`/`reject`/`diff`/`revert`) remain as deprecated aliases
449
448
  // that warn to stderr and delegate to the same command bodies; they are removed
450
449
  // in 0.9.0. Bare `akm proposal` behaves as `proposal list` (mirrors `akm env`).
451
- // Single source of truth: the routing set is derived from the subCommands keys
452
- // (M10) so adding a subcommand can never silently desync from `hasSubcommand`.
453
- const proposalSubCommands = {
454
- list: proposalListCommand,
455
- show: proposalShowCommand,
456
- diff: proposalDiffCommand,
457
- accept: proposalAcceptCommand,
458
- reject: proposalRejectCommand,
459
- revert: proposalRevertCommand,
460
- drain: proposalDrainCommand,
461
- };
462
- const PROPOSAL_SUBCOMMAND_SET = new Set(Object.keys(proposalSubCommands));
463
- export const proposalCommand = defineCommand({
450
+ export const proposalCommand = defineGroupCommand({
464
451
  meta: { name: "proposal", description: "Manage the proposal queue: list, show, diff, accept, reject, revert" },
465
452
  args: {
466
453
  status: {
@@ -470,21 +457,24 @@ export const proposalCommand = defineCommand({
470
457
  ref: { type: "string", description: "Filter by asset ref (type:name)" },
471
458
  type: { type: "string", description: "Filter by asset type" },
472
459
  },
473
- subCommands: proposalSubCommands,
474
- run({ args }) {
475
- return runWithJsonErrors(() => {
476
- // citty runs the group body even after a subcommand; short-circuit so the
477
- // default-to-list body only fires for bare `akm proposal [--status …]`.
478
- if (hasSubcommand(args, PROPOSAL_SUBCOMMAND_SET))
479
- return;
480
- const status = parseProposalStatus(args.status);
481
- const result = akmProposalList({
482
- status,
483
- ref: args.ref,
484
- type: args.type,
485
- includeArchive: status === "accepted" || status === "rejected" || status === "reverted",
486
- });
487
- output("proposal-list", result);
460
+ subCommands: {
461
+ list: proposalListCommand,
462
+ show: proposalShowCommand,
463
+ diff: proposalDiffCommand,
464
+ accept: proposalAcceptCommand,
465
+ reject: proposalRejectCommand,
466
+ revert: proposalRevertCommand,
467
+ drain: proposalDrainCommand,
468
+ },
469
+ // Default body fires only for bare `akm proposal [--status …]`.
470
+ defaultRun({ args }) {
471
+ const status = parseProposalStatus(args.status);
472
+ const result = akmProposalList({
473
+ status,
474
+ ref: args.ref,
475
+ type: args.type,
476
+ includeArchive: status === "accepted" || status === "rejected" || status === "reverted",
488
477
  });
478
+ output("proposal-list", result);
489
479
  },
490
480
  });
@@ -75,6 +75,11 @@ export async function akmProposalAccept(options) {
75
75
  source: result.proposal.source,
76
76
  ...(result.proposal.sourceRun !== undefined ? { sourceRun: result.proposal.sourceRun } : {}),
77
77
  assetPath: result.assetPath,
78
+ // Attribution tagging: carry the eligibility lane from the proposal record
79
+ // onto the promoted event so accept outcomes can be sliced by lane.
80
+ ...(result.proposal.eligibilitySource !== undefined
81
+ ? { eligibilitySource: result.proposal.eligibilitySource }
82
+ : {}),
78
83
  },
79
84
  });
80
85
  return {
@@ -18,10 +18,11 @@ import { TYPE_DIRS } from "../../core/asset/asset-spec.js";
18
18
  import { resolveStashDir } from "../../core/common.js";
19
19
  import { ConfigError, UsageError } from "../../core/errors.js";
20
20
  import { appendEvent } from "../../core/events.js";
21
+ import { resolveStandardsContext } from "../../core/standards/resolve-standards-context.js";
21
22
  import { runAgent, } from "../../integrations/agent/index.js";
22
23
  import { resolveProcessAgentProfile } from "../../integrations/agent/config.js";
23
24
  import { buildProposePrompt, parseAgentProposalPayload } from "../../integrations/agent/prompts.js";
24
- import { runAgentSdk } from "../../integrations/harnesses/opencode-sdk/index.js";
25
+ import { runOpencodeSdk } from "../../integrations/harnesses/opencode-sdk/index.js";
25
26
  import { baseFailureFields, enoentHintMessage, isEnoentFailure, loadAgentConfigFromDisk, resolveAgentProfile, } from "../agent/agent-support.js";
26
27
  import { createProposal, isProposalSkipped, } from "./validators/proposals.js";
27
28
  function failureEnvelope(result, type, name, fallbackReason = "non_zero_exit") {
@@ -93,10 +94,14 @@ export async function akmPropose(options) {
93
94
  // directly using its file tools rather than returning JSON via stdout.
94
95
  const draftFilePath = import("node:os").then((os) => import("node:path").then((path) => path.join(os.tmpdir(), `akm-propose-${options.type}-${options.name.replace(/[^a-z0-9_-]/gi, "_")}-${Date.now()}.md`)));
95
96
  const resolvedDraftPath = await draftFilePath;
97
+ // Standards "rulebook" for this target — wiki schema (wiki page) or stash
98
+ // convention/meta facts (non-wiki asset); empty when neither fires.
99
+ const standardsContext = resolveStandardsContext(`${options.type}:${options.name}`, stash);
96
100
  const prompt = buildProposePrompt({
97
101
  type: options.type,
98
102
  name: options.name,
99
103
  task: options.task,
104
+ ...(standardsContext.trim() ? { standardsContext } : {}),
100
105
  draftFilePath: resolvedDraftPath,
101
106
  });
102
107
  // 4. Spawn the agent.
@@ -123,7 +128,7 @@ export async function akmPropose(options) {
123
128
  ...(resolvedTimeoutMs !== undefined ? { timeoutMs: resolvedTimeoutMs } : {}),
124
129
  };
125
130
  result = profile.sdkMode
126
- ? await runAgentSdk(profile, prompt ?? "", runOptions)
131
+ ? await runOpencodeSdk(profile, prompt ?? "", runOptions)
127
132
  : await runAgent(profile, prompt, runOptions);
128
133
  }
129
134
  if (!result.ok) {
@@ -59,6 +59,7 @@
59
59
  */
60
60
  // ── Reflect-size guard ───────────────────────────────────────────────────────
61
61
  import { parseFrontmatter } from "../../../core/asset/frontmatter.js";
62
+ import { DESCRIPTION_MAX_CHARS, DESCRIPTION_MIN_CHARS, WHEN_TO_USE_MAX_CHARS, WHEN_TO_USE_MIN_CHARS, } from "../../../core/authoring-rules.js";
62
63
  import { detectTruncatedDescription, TRUNCATION_TRAILING_WORDS } from "../../../core/text-truncation.js";
63
64
  // ── Description / when_to_use shape ─────────────────────────────────────────
64
65
  export const HEADING_FRAGMENT_PATTERNS = [
@@ -80,10 +81,10 @@ export function isValidDescription(value, inputRef, options = {}) {
80
81
  const v = value.trim();
81
82
  if (!v)
82
83
  return { ok: false, reason: "description is empty" };
83
- if (v.length < 20)
84
- return { ok: false, reason: `description is too short (${v.length} chars; need ≥20)` };
85
- if (v.length > 400)
86
- return { ok: false, reason: `description is too long (${v.length} chars; max 400)` };
84
+ if (v.length < DESCRIPTION_MIN_CHARS)
85
+ return { ok: false, reason: `description is too short (${v.length} chars; need ≥${DESCRIPTION_MIN_CHARS})` };
86
+ if (v.length > DESCRIPTION_MAX_CHARS)
87
+ return { ok: false, reason: `description is too long (${v.length} chars; max ${DESCRIPTION_MAX_CHARS})` };
87
88
  if (/^\s*[\d#*\->`]/.test(v))
88
89
  return { ok: false, reason: "description starts with a digit or markdown marker" };
89
90
  const last = v.slice(-1);
@@ -133,10 +134,10 @@ export function isValidWhenToUse(value, inputRef) {
133
134
  const v = value.trim();
134
135
  if (!v)
135
136
  return { ok: false, reason: "when_to_use is empty" };
136
- if (v.length < 15)
137
- return { ok: false, reason: `when_to_use is too short (${v.length} chars; need ≥15)` };
138
- if (v.length > 400)
139
- return { ok: false, reason: `when_to_use is too long (${v.length} chars; max 400)` };
137
+ if (v.length < WHEN_TO_USE_MIN_CHARS)
138
+ return { ok: false, reason: `when_to_use is too short (${v.length} chars; need ≥${WHEN_TO_USE_MIN_CHARS})` };
139
+ if (v.length > WHEN_TO_USE_MAX_CHARS)
140
+ return { ok: false, reason: `when_to_use is too long (${v.length} chars; max ${WHEN_TO_USE_MAX_CHARS})` };
140
141
  if (/^when working with\b/i.test(v))
141
142
  return { ok: false, reason: "when_to_use is the circular 'When working with ...' fallback" };
142
143
  const refTail = inputRef.split(":").pop()?.toLowerCase() ?? "";