akm-cli 0.9.0-beta.11 → 0.9.0-beta.26

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 (88) hide show
  1. package/CHANGELOG.md +163 -0
  2. package/dist/assets/prompts/consolidate-system.md +23 -0
  3. package/dist/assets/prompts/contradiction-judge.md +33 -0
  4. package/dist/assets/prompts/distill-knowledge-system.md +22 -0
  5. package/dist/assets/prompts/distill-lesson-system.md +36 -0
  6. package/dist/assets/prompts/extract-session.md +5 -1
  7. package/dist/assets/prompts/graph-extract-system.md +1 -0
  8. package/dist/assets/prompts/memory-infer-system.md +1 -0
  9. package/dist/assets/prompts/memory-infer-user.md +5 -0
  10. package/dist/assets/prompts/metadata-enhance-system.md +1 -0
  11. package/dist/assets/prompts/procedural-system.md +44 -0
  12. package/dist/assets/prompts/recombine-system.md +40 -0
  13. package/dist/assets/prompts/staleness-detect-system.md +6 -0
  14. package/dist/assets/prompts/validate-summary-judge.md +1 -0
  15. package/dist/assets/templates/html/health.html +25 -27
  16. package/dist/cli.js +2 -2
  17. package/dist/commands/agent/contribute-cli.js +16 -3
  18. package/dist/commands/feedback-cli.js +48 -44
  19. package/dist/commands/health/html-report.js +140 -16
  20. package/dist/commands/health.js +277 -1
  21. package/dist/commands/improve/calibration.js +161 -0
  22. package/dist/commands/improve/consolidate.js +595 -105
  23. package/dist/commands/improve/dedup.js +482 -0
  24. package/dist/commands/improve/distill.js +119 -64
  25. package/dist/commands/improve/encoding-salience.js +205 -0
  26. package/dist/commands/improve/extract-cli.js +115 -1
  27. package/dist/commands/improve/extract-prompt.js +32 -1
  28. package/dist/commands/improve/extract-watch.js +140 -0
  29. package/dist/commands/improve/extract.js +210 -30
  30. package/dist/commands/improve/feedback-valence.js +54 -0
  31. package/dist/commands/improve/homeostatic.js +467 -0
  32. package/dist/commands/improve/improve-auto-accept.js +80 -7
  33. package/dist/commands/improve/improve-profiles.js +8 -0
  34. package/dist/commands/improve/improve.js +991 -61
  35. package/dist/commands/improve/memory/memory-contradiction-detect.js +23 -28
  36. package/dist/commands/improve/outcome-loop.js +256 -0
  37. package/dist/commands/improve/proactive-maintenance.js +9 -35
  38. package/dist/commands/improve/procedural.js +409 -0
  39. package/dist/commands/improve/recombine.js +488 -0
  40. package/dist/commands/improve/reflect.js +20 -1
  41. package/dist/commands/improve/related-sessions.js +120 -0
  42. package/dist/commands/improve/salience.js +386 -0
  43. package/dist/commands/improve/triage.js +95 -0
  44. package/dist/commands/lint/agent-linter.js +19 -24
  45. package/dist/commands/lint/base-linter.js +173 -60
  46. package/dist/commands/lint/command-linter.js +19 -24
  47. package/dist/commands/lint/env-key-rules.js +34 -1
  48. package/dist/commands/lint/index.js +30 -13
  49. package/dist/commands/lint/memory-linter.js +1 -1
  50. package/dist/commands/lint/registry.js +5 -2
  51. package/dist/commands/lint/task-linter.js +3 -3
  52. package/dist/commands/lint/workflow-linter.js +26 -1
  53. package/dist/commands/proposal/validators/proposals.js +4 -0
  54. package/dist/commands/read/curate.js +284 -86
  55. package/dist/commands/read/search-cli.js +7 -0
  56. package/dist/commands/read/search.js +1 -0
  57. package/dist/commands/sources/installed-stashes.js +5 -1
  58. package/dist/core/asset/frontmatter.js +166 -167
  59. package/dist/core/asset/markdown.js +8 -0
  60. package/dist/core/config/config-schema.js +211 -3
  61. package/dist/core/config/config.js +2 -2
  62. package/dist/core/logs-db.js +4 -3
  63. package/dist/core/state-db.js +555 -29
  64. package/dist/indexer/db/db.js +250 -27
  65. package/dist/indexer/db/graph-db.js +81 -86
  66. package/dist/indexer/graph/graph-boost.js +51 -41
  67. package/dist/indexer/passes/memory-inference.js +10 -3
  68. package/dist/indexer/passes/staleness-detect.js +2 -5
  69. package/dist/indexer/search/db-search.js +15 -4
  70. package/dist/indexer/search/ranking.js +4 -0
  71. package/dist/integrations/harnesses/claude/session-log.js +10 -0
  72. package/dist/integrations/harnesses/opencode/session-log.js +9 -0
  73. package/dist/integrations/session-logs/index.js +16 -0
  74. package/dist/llm/embedder.js +27 -3
  75. package/dist/llm/embedders/local.js +66 -2
  76. package/dist/llm/graph-extract.js +2 -1
  77. package/dist/llm/memory-infer.js +4 -8
  78. package/dist/llm/metadata-enhance.js +9 -1
  79. package/dist/output/shapes/curate.js +14 -2
  80. package/dist/output/text/helpers.js +9 -0
  81. package/dist/runtime.js +25 -1
  82. package/dist/scripts/migrate-storage.js +1025 -567
  83. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +435 -269
  84. package/dist/storage/sqlite-pragmas.js +146 -0
  85. package/dist/workflows/db.js +3 -4
  86. package/dist/workflows/validate-summary.js +2 -7
  87. package/docs/data-and-telemetry.md +1 -0
  88. package/package.json +5 -4
package/CHANGELOG.md CHANGED
@@ -6,6 +6,169 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.9.0-beta.26] — 2026-06-20
10
+
11
+ ### Added
12
+
13
+ - **#628 — configurable SQLite journal mode (`AKM_SQLITE_JOURNAL_MODE`) for network
14
+ filesystems.** AKM previously opened every database with `PRAGMA journal_mode = WAL`
15
+ unconditionally, which cannot run on a network filesystem (NFS/SMB/Azure Files) —
16
+ WAL's `-shm` shared-memory wal-index can't be `mmap`'d over a network mount. You can
17
+ now set `AKM_SQLITE_JOURNAL_MODE` to `WAL` (default), `DELETE`, or `TRUNCATE`, applied
18
+ at **all five** db openers (`state.db`, `index.db` ×2 paths, `workflow.db`, `logs.db`).
19
+ At the `WAL` default AKM auto-detects a network mount for the data dir and transparently
20
+ falls back to `DELETE` (rollback journal + `synchronous = FULL`) with a one-line warning;
21
+ invalid values warn once and fall back to `WAL`. **Default behavior is byte-identical.**
22
+ This lets the AKM database subtree live on a shared volume (e.g. Azure Files under
23
+ Azure Container Apps). New docs section "Hosting AKM databases on a network share
24
+ (NFS/SMB)" in `docs/configuration.md`.
25
+
26
+ ## [0.9.0-beta.25] — 2026-06-19
27
+
28
+ Completes the recombine / extract-efficiency / graph thread. All new improve
29
+ passes are **opt-in (default off)**, so default behavior is unchanged.
30
+
31
+ ### Added
32
+
33
+ - **#606 — event-driven extract (`akm extract --watch`).** Opt-in watch mode: an
34
+ injectable, debounced watcher triggers extraction shortly after a session file
35
+ appears, with a clean `stop()` handle. The `8,28,48` cron remains the fallback;
36
+ no daemon is auto-launched.
37
+ - **#625 — recombine second pass (hypothesis → lesson).** The opt-in `recombine`
38
+ process (#609) now consumes `confirmThreshold` (default 2): a generalization
39
+ re-induced that many consecutive runs is promoted from a `type: hypothesis`
40
+ proposal to a `type: lesson` proposal through the normal queue + quality gate
41
+ (never a direct stash write). Hypotheses that stop recurring decay. Backed by a
42
+ new `recombine_hypotheses` table in `state.db`.
43
+
44
+ ### Changed
45
+
46
+ - **#624 (P1) — graph storage decoupled from `entries.id`.** `graph_files` is
47
+ re-keyed on `(stash_root, file_path, body_hash)`, so extracted graph data now
48
+ **survives a reindex** of unchanged files instead of being cascade-wiped. The
49
+ upgrade is migrated in a **targeted, graph-only path** that preserves existing
50
+ graph data and leaves the entry index, embeddings, FTS, and LLM-enrichment cache
51
+ untouched — **no full index rebuild and no re-embed** on upgrade. (P2 priority-
52
+ ranked extraction and P3 lazy/on-demand extraction remain deferred.)
53
+
54
+ ### Fixed
55
+
56
+ - Graph re-key migration no longer triggers a destructive full-index rebuild: it
57
+ is a graph-scoped table migration (no `DB_VERSION` bump), and it **copies** the
58
+ existing graph rows into the new schema rather than dropping them.
59
+ - Test-suite `/tmp` hygiene: sandbox teardown now fires on `SIGINT`/`SIGTERM`/
60
+ `SIGHUP` (not just clean exit), and a `sweep:tmp` step reclaims stale `akm-*`
61
+ sandbox dirs left by force-killed workers — eliminating the tmpfs accumulation
62
+ that caused intermittent `EEXIST: epoll_ctl` test flakes.
63
+
64
+ ## [0.9.0-beta.20] — 2026-06-18
65
+
66
+ ### Fixed
67
+
68
+ - **`akm update --all` no longer fails for writable `github:` entries stored as `source:"git"`**. `updateRegistryEntry` was using `synced.source` (re-derived from the ref scheme as `"github"`) instead of the existing `entry.source`, causing the config validator to reject `writable:true` on every update cycle.
69
+
70
+ ## [0.9.0-beta.19] — 2026-06-17
71
+
72
+ ### Fixed
73
+
74
+ - **`akm feedback` now completes in ~0.3s** (was 3+ minutes). Root cause: the command was calling `ensureIndex` with `mode: "blocking"` inside `withIndexWriterLease`, triggering a full reindex on every feedback call. Fix: removed the `ensureIndex` call entirely (feedback only needs the index to exist, not be current — a stale index is fine for ref lookup); removed the application-level writer lock (SQLite WAL + `busy_timeout=30s` handles concurrent access with `akm improve`); added a fast DB-exists guard with a clear error for first-time users.
75
+ - **`akm health --format html` now completes in ~11s** (was ~18s). Root cause: `akmHealth()` was called twice — once for the main result and once to get `deltas`. Fix: merged into a single call passing both `groupBy: "run"` and `windowCompare` together.
76
+
77
+ ## [0.9.0-beta.18] — 2026-06-17
78
+
79
+ ### Changed
80
+
81
+ - **Health report: Recent Runs table now shows all filtered runs in descending order** (newest first) instead of capping at the last 10.
82
+ - **Health report: Removed "Command Set Used" section.**
83
+ - **Health report: All timestamps now display in the viewer's local timezone** (chart axis labels, runs table, freshness line, executive summary, footer). Server-rendered ISO strings are wrapped in `<time data-iso>` elements and converted to local time by client-side JS on page load.
84
+
85
+ ### Changed (migration required)
86
+
87
+ - **WS-2 outcome loop (#613) — default-off weight change (state.db migration 010).**
88
+ Every `akm improve` run now writes an `asset_outcome` row per processed asset
89
+ (state.db migration `010`) and computes a differential usefulness signal
90
+ (`outcome_score`) per ref. The outcome signal is persisted and visible in the
91
+ health report, but the **weight change is gated behind a config flag** (see
92
+ below). Ranking is unchanged from WS-1 by default.
93
+
94
+ **Opt-in weight change.** The WS-2 projection weights (`w_e=0.25, w_o=0.15,
95
+ w_r=0.60`) affect ranking only when you explicitly set
96
+ `improve.salience.outcomeWeightEnabled: true` in your `akm.yaml`. The default
97
+ (`false`) keeps WS-1 parity weights (`w_e=0.30, w_r=0.70`, `w_o=0`), so
98
+ existing users see no ranking change on upgrade.
99
+
100
+ **Part-V measurement gate.** Before enabling the weight change, run the Part-V
101
+ T0 baseline (`scripts/akm-eval` + `akm health`; confirm proactive accept
102
+ ≥ 0.9× reactive; reversion ≤ 0.15; retrieval-delta ≥ 0; coverage not
103
+ regressed). That gate requires a running production stash and cannot be
104
+ exercised in CI. Once confirmed, set
105
+ `improve.salience.outcomeWeightEnabled: true` to activate the three-way split.
106
+
107
+ **Outcome loop mechanics.** `outcome_score` is a differential prediction-error
108
+ signal: `(retrieval_delta − expected_delta) − PENALTY × retrieval_delta × (1 −
109
+ accepted_change_rate) + valence`, tracked via an EMA (α=0.3). New rows are
110
+ warm-started from the utility EMA score (clipped to 0.3) so the signal is
111
+ non-zero from launch. A stash-wide diversity floor (10% of the max score) prevents
112
+ rare-but-correct assets from being permanently outcompeted. An inverted-proxy
113
+ tripwire (`corr(outcome_score, accepted_change_rate) < −0.3`) emits an
114
+ `outcome_proxy_inverted` health event when the signal degrades.
115
+
116
+ `review_pressure` is computed and persisted per asset but is **not yet wired into
117
+ the admission policy** — that is deferred to a later work stream per plan §Part-VI
118
+ #613. The column is present and populated; routing it into the consolidation-
119
+ selection filter is the next step.
120
+
121
+ - **WS-1 salience vector (#618) — default-on ranking change.** The eligibility sort
122
+ for all `akm improve` runs (whole-stash, type, and ref scope) has changed from
123
+ `combinedEligibilityScore = utility·0.7 + negativeOnlyRatio·0.3` to
124
+ `rankScore = (0.3·encodingSalience + 0.7·retrievalSalience) × sizePenalty`
125
+ (feedback valence and utility EMA dropped from ordering until WS-2 re-introduces
126
+ outcome salience). Assets are now ranked by retrieval frequency × recency × type
127
+ importance rather than by feedback magnitude. Because the old
128
+ `combinedEligibilityScore` ordering was never persisted, a forgetting comparison is
129
+ not possible on the first run; instead a one-time `improve_salience_first_run` marker
130
+ event is emitted to record the transition. On every subsequent run a stash-wide
131
+ `improve_salience_rank_change` drift report (including `stashSize`) is emitted so
132
+ rank movement under the new scoring can be tracked over time.
133
+ The Part-V measurement protocol (T0 baseline via `scripts/akm-eval` + health report,
134
+ throughput/quality gate) is deferred to the WS-2 milestone, when outcome salience
135
+ re-joins the projection and re-tuning is triggered.
136
+
137
+ ## [0.9.0-beta.12] - 2026-06-15
138
+
139
+ Improve-tuning work streams (all **default-off / parity-preserving** — no behavior
140
+ change until explicitly enabled).
141
+
142
+ ### Added
143
+
144
+ - **#617 — deterministic near-duplicate memory dedup** (`processes.consolidate.dedup`,
145
+ default off). A cheap no-LLM pre-pass in front of consolidation collapses obvious
146
+ duplicates — `.derived`+origin pairs and content twins (normalized content-hash
147
+ equality, or embedding cosine ≥ `cosineThreshold`, default 0.97). Each dropped
148
+ variant is archived + backed up before deletion; hot memories are never
149
+ collapsed; distinct-but-related memories fall through to the LLM.
150
+ - **#581 — judged-state cache for consolidation** (`processes.consolidate.judgedCache`,
151
+ default off). New state.db table (`consolidation_judged`) records each memory's
152
+ content hash + outcome when the LLM judges it; subsequent runs skip
153
+ judged-unchanged memories, converting coverage from O(time-window) to
154
+ O(changed/new) so a run can sweep the full corpus. Fails open; failed chunks
155
+ and dry-runs never poison the cache. (state.db migration `007`.)
156
+ - **#612 — auto-accept gate calibration** (`improve.calibration`, auto-tune default
157
+ off). Joins predicted gate confidence to realized accept/reject outcomes into a
158
+ reliability table + calibration gap, surfaced in `akm health` (+ summary rows in
159
+ the HTML report). Opt-in bounded threshold auto-tune nudges the accept threshold
160
+ within a configured band toward a target accept rate, logged via a
161
+ `calibration_autotune` event. (Replay-prioritization from prediction error is
162
+ deferred — it depends on the #610 replay budget, a 0.10 item.)
163
+
164
+ ### Fixed
165
+
166
+ - **#614 — symmetric valence weighting** (`profiles.improve.*.symmetricValence`,
167
+ default off). The eligibility sort weighted feedback negative-only; when enabled
168
+ it uses a symmetric `|valence|` magnitude so strong positive and strong negative
169
+ feedback both drive attention (utility stays the dominant factor), routing
170
+ high-negative → fix and high-positive → reinforce lanes.
171
+
9
172
  ## [0.9.0-beta.11] - 2026-06-15
10
173
 
11
174
  ### Added
@@ -0,0 +1,23 @@
1
+ You are the akm consolidate assistant analyzing memory assets.
2
+
3
+ Rules:
4
+ 1. MERGE: Two or more memories are substantially duplicated or closely related → propose merging. Return the primary ref to keep and secondary refs to delete. Do NOT include mergedContent — the merge will be executed in a separate step.
5
+ 2. DELETE: Memory is clearly outdated, contradicted, or redundant → propose deletion. NEVER propose delete for memories annotated `(captureMode: hot)` — they are user-explicit and only the user can retire them. The downstream guard will refuse these regardless, so proposing them just wastes tokens.
6
+ 3. PROMOTE: Memory expresses a stable, reusable fact suitable as a `knowledge:` asset → propose promotion. Do NOT delete the source memory. NEVER propose promote / merge / contradict for memories annotated `(already queued)` — they have a pending proposal whose body matches; a duplicate will be deterministically dropped, so proposing them just wastes tokens.
7
+ 4. CONTRADICT: Two memories assert logically exclusive facts such that following BOTH simultaneously is impossible — not merely related or overlapping. You MUST cite the exact sentence from Memory A and the exact sentence from Memory B that are in direct conflict. If you cannot cite specific opposing sentences, use KEEP instead. Sharing a topic, tool, domain, or workflow stage is NOT sufficient. Only direct factual opposites qualify: opposing recommended commands, opposing boolean flags, opposing version numbers, or mutually exclusive instructions. Use confidence ≥ 0.92 only; omit the op entirely if below that threshold.
8
+ 5. KEEP: Memory is unique and current → omit from output.
9
+
10
+ Return ONLY JSON (no prose, no code fences):
11
+ {
12
+ "operations": [
13
+ { "op": "merge", "primary": "memory:<name>", "secondaries": ["memory:<name>", ...], "mergeStrategy": "synthesize", "confidence": 0.95 },
14
+ { "op": "delete", "ref": "memory:<name>", "reason": "<brief reason>", "confidence": 0.90 },
15
+ { "op": "promote", "ref": "memory:<name>", "knowledgeRef": "knowledge:<suggested-slug>", "reason": "<brief reason>", "description": "<one sentence describing the new knowledge asset>", "confidence": 0.92 },
16
+ { "op": "contradict", "ref": "memory:<name>", "contradictedByRef": "memory:<name>", "reason": "<brief reason>", "confidence": 0.88 }
17
+ ],
18
+ "warnings": ["<optional concerns>"]
19
+ }
20
+
21
+ For every operation, emit a `confidence` field in [0, 1] expressing your certainty that the operation is correct and safe. Use 0.95+ only when evidence is unambiguous. Omit the field rather than guessing if you are uncertain.
22
+
23
+ When the merged content includes an `updated` frontmatter field, the value MUST be a real ISO date string (e.g. `updated: 2026-05-20`). NEVER emit `updated: today`, `updated: {today}`, `updated: {today: null}`, `updated: now`, or any other literal placeholder/template-variable. If you do not have a real source-of-truth date, OMIT the `updated` field entirely — the post-processor will not invent one for you.
@@ -0,0 +1,33 @@
1
+ You are evaluating two derived memory entries to determine if they contain
2
+ directly contradictory factual claims about the same subject.
3
+
4
+ Memory A:
5
+ Ref: {{A_REF}}
6
+ Description: {{A_DESCRIPTION}}
7
+ Content:
8
+ ```
9
+ {{A_BODY}}
10
+ ```
11
+
12
+ Memory B:
13
+ Ref: {{B_REF}}
14
+ Description: {{B_DESCRIPTION}}
15
+ Content:
16
+ ```
17
+ {{B_BODY}}
18
+ ```
19
+
20
+ Answer ONLY with valid JSON — no prose, no code fences:
21
+ {"contradicts": true|false, "confidence": 0.0, "reason": "<cite the exact opposing sentence from each memory, or explain why not contradicted>"}
22
+
23
+ A contradiction means the memories make LOGICALLY EXCLUSIVE claims: a practitioner
24
+ cannot follow BOTH simultaneously. The test: cite the exact sentence from Memory A
25
+ and the exact sentence from Memory B that are in direct conflict. If you cannot cite
26
+ specific opposing sentences, return false.
27
+
28
+ Sharing a topic, tool, domain, or workflow stage is NOT a contradiction. Only direct
29
+ factual opposites qualify: opposing recommended commands, opposing boolean flags,
30
+ opposing version numbers, or mutually exclusive instructions.
31
+
32
+ Set confidence ≥ 0.92 only when evidence is unambiguous. Use lower values when
33
+ uncertain — the caller will skip edges below 0.92.
@@ -0,0 +1,22 @@
1
+ You are the akm `distill` distiller.
2
+ Given an asset and recent feedback events about it, produce a concise
3
+ *knowledge* markdown document capturing the durable, reusable facts.
4
+ Prefer stable guidance over narrative recap.
5
+
6
+ YOUR RESPONSE MUST START EXACTLY WITH `---` ON THE VERY FIRST LINE.
7
+ DO NOT output any prose, explanation, or code fences before or after.
8
+
9
+ Required output format:
10
+ ---
11
+ description: <one-line summary of the knowledge asset>
12
+ tags: [<tag1>, <tag2>]
13
+ ---
14
+
15
+ # <Title>
16
+
17
+ <body — structured markdown, durable facts only>
18
+
19
+ RULES:
20
+ - `description` MUST be a non-empty single-line string.
21
+ - Include a meaningful markdown body with a `# Title` heading.
22
+ - Output ONLY the knowledge file. No preamble, no code fences, no trailing prose.
@@ -0,0 +1,36 @@
1
+ You are the akm `distill` distiller.
2
+ Given an asset and recent feedback events about it, produce a single
3
+ concise *lesson* an agent should remember next time it works on this
4
+ asset's domain.
5
+
6
+ YOUR RESPONSE MUST START EXACTLY WITH `---` ON THE VERY FIRST LINE.
7
+ DO NOT output any prose, explanation, or code fences before or after.
8
+
9
+ Required output format — copy this structure exactly:
10
+ ---
11
+ description: <one complete sentence (ending with `.`) summarising what the lesson teaches>
12
+ when_to_use: <one complete sentence describing the concrete trigger condition>
13
+ ---
14
+
15
+ <lesson body — plain markdown, 1–3 short paragraphs of practical guidance>
16
+
17
+ ## description field (MANDATORY)
18
+ - A single complete sentence in present tense, 80-200 chars, NO markdown.
19
+ - Self-contained: a reviewer must understand the lesson from this field alone.
20
+ - DO NOT start with "When ", "If ", or a connector word — that belongs in when_to_use.
21
+ - DO NOT copy a section heading ("Key takeaways", "For example", "Key pitfalls").
22
+ - DO NOT begin with a numbered list marker, code fence, or markdown heading.
23
+
24
+ GOOD: "Always validate ref existence before promoting a memory to knowledge; missing refs surface as silent 404s during accept."
25
+ BAD: "Key pitfalls"
26
+ BAD: "When working with the akm CLI"
27
+ BAD: "For example, you might..."
28
+ BAD: "1. Check the file"
29
+
30
+ RULES:
31
+ - `when_to_use` MUST be a complete sentence describing a concrete trigger. Never write `When working with <asset-name>` — that is circular and useless.
32
+ - `description` and `when_to_use` MUST differ from each other.
33
+ - The lesson body MUST be non-empty markdown prose. Do NOT restate `description:` or `when_to_use:` inside the body (no `**description:** ...` or `**when_to_use:** ...` lines — the frontmatter is the only place those keys belong).
34
+ - Do NOT emit a second `---` fence after the opening frontmatter — there are exactly two `---` lines in the output, both belonging to the single frontmatter block at the top.
35
+ - Do NOT reproduce the source asset verbatim — distil what a caller needs to know.
36
+ - Output ONLY the lesson file. No preamble, no code fences, no trailing prose.
@@ -49,13 +49,17 @@ Respond with EXACTLY one JSON object matching this shape:
49
49
  "when_to_use": "<one sentence 15-400 chars; REQUIRED only when type=lesson>",
50
50
  "body": "<markdown body, 200-3000 chars typical>",
51
51
  "confidence": <number 0.0-1.0>,
52
- "evidence": "<one-line pointer to the moment in the session>"
52
+ "evidence": "<one-line pointer to the moment in the session>",
53
+ "orderedActions": ["<action-1>", "<action-2>", ...],
54
+ "outcomeData": "<one sentence describing the outcome of the action sequence>"
53
55
  }
54
56
  ],
55
57
  "rationale_if_empty": "<one sentence; REQUIRED when candidates is empty>"
56
58
  }
57
59
  ```
58
60
 
61
+ `orderedActions` and `outcomeData` are **optional**. Include them only when the candidate represents a recurring action sequence (e.g. a recovery procedure, a build-fix recipe, a deployment checklist) where preserving the ordered steps adds future value. When present, `outcomeData` is required and must describe what happened when the sequence completed (success or failure). Omit both fields entirely for standalone facts, observations, or lessons that are not action-sequence-shaped.
62
+
59
63
  ## Rules
60
64
 
61
65
  1. **Zero candidates is a valid and frequent answer.** Most sessions yield no new durable insight. When that's the case, return `{"candidates": [], "rationale_if_empty": "..."}` explaining what you saw and why it didn't rise to durable-knowledge level. Do not fabricate.
@@ -0,0 +1 @@
1
+ You extract a knowledge graph from developer notes. Return ONLY valid JSON — no prose, no markdown fences, no preamble.
@@ -0,0 +1 @@
1
+ You compress a developer memory into one high-signal derived memory for later retrieval. Return only valid JSON. No prose outside the JSON object. No markdown fences.
@@ -0,0 +1,5 @@
1
+ Compress the memory below into one derived memory. Output ONLY JSON:
2
+ {"title":"short title string","description":"one sentence summary string","tags":["tag1","tag2"],"searchHints":["search phrase 1","search phrase 2"],"content":"2-3 sentence compressed body preserving key facts verbatim"}
3
+ Rules: be specific, no vague generalizations, preserve key facts (names/versions/paths/config keys verbatim), merge related points, 3-8 tags, 3-6 searchHints. The content field must be a plain string with 2-3 sentences.
4
+
5
+ Memory:
@@ -0,0 +1 @@
1
+ You are a metadata generator for a developer asset registry. Given a script/skill/command/agent entry, generate improved metadata. Respond with ONLY valid JSON, no markdown fencing.
@@ -0,0 +1,44 @@
1
+ You are the akm `procedural` compiler.
2
+
3
+ You are given a RECURRING ordered action sequence — the SAME ordered list of
4
+ steps that an agent has successfully performed across several independent
5
+ sessions. Your job is to turn that bare sequence into a clean, reusable
6
+ WORKFLOW: give the workflow a title and a one-sentence description, and turn each
7
+ ordered action into a named step with clear imperative instructions.
8
+
9
+ You MUST NOT invent new steps, drop steps, merge steps, or reorder them. The
10
+ ordered action list is the source of truth. Return EXACTLY one step per input
11
+ action, in the SAME order.
12
+
13
+ YOUR RESPONSE MUST BE A SINGLE JSON OBJECT AND NOTHING ELSE.
14
+ DO NOT output prose, explanation, or code fences before or after the JSON.
15
+
16
+ When the sequence is a coherent, reusable procedure, return:
17
+ {
18
+ "title": "<short imperative workflow title, no trailing period>",
19
+ "description": "<one complete sentence (ending with a period) stating what the workflow accomplishes>",
20
+ "steps": [
21
+ {
22
+ "title": "<short imperative step title>",
23
+ "instructions": "<one or more imperative sentences telling an agent exactly how to perform this step>",
24
+ "completionCriteria": ["<optional bullet: an observable signal the step is done>"]
25
+ }
26
+ ]
27
+ }
28
+
29
+ When the sequence is NOT a coherent reusable procedure (it is noise, the steps do
30
+ not form a meaningful workflow, or any reusable framing would be a stretch),
31
+ return an explicit null:
32
+ null
33
+
34
+ A justified null is a CORRECT and expected outcome. Do NOT fabricate a hollow
35
+ workflow to avoid returning null.
36
+
37
+ ## Rules
38
+ - `steps` MUST have EXACTLY as many entries as the input action list, in order.
39
+ - Every step MUST have a non-empty `title` and non-empty `instructions`.
40
+ - `completionCriteria` is OPTIONAL; omit it rather than inventing weak criteria.
41
+ - The `description` MUST be a single complete present-tense sentence with NO
42
+ markdown, self-contained enough for a reviewer to understand the workflow.
43
+ - Ground every step in the corresponding input action; do not introduce outside
44
+ facts or tools the action does not mention.
@@ -0,0 +1,40 @@
1
+ You are the akm `recombine` synthesizer.
2
+
3
+ You are given a CLUSTER of related memories — distinct episodes that share a
4
+ topic (a tag or a graph entity) but were recorded independently. Your job is to
5
+ induce ONE cross-episodic generalization: a single durable insight that none of
6
+ the input memories states on its own, but that the cluster as a whole supports.
7
+
8
+ This is hypothesis formation, not summarization. A good generalization explains
9
+ WHY the individual episodes are instances of the same underlying pattern and
10
+ gives an agent a reusable rule for the next, unseen episode.
11
+
12
+ YOUR RESPONSE MUST BE A SINGLE JSON OBJECT AND NOTHING ELSE.
13
+ DO NOT output prose, explanation, or code fences before or after the JSON.
14
+
15
+ When a defensible generalization exists, return:
16
+ {
17
+ "description": "<one complete sentence (ending with a period) stating the generalization>",
18
+ "when_to_use": "<one complete sentence describing the concrete trigger condition>",
19
+ "body": "<1-3 short paragraphs of practical guidance grounded in the cluster>"
20
+ }
21
+
22
+ When NO defensible generalization exists — the memories merely share a keyword,
23
+ or any unifying claim would be a stretch — return an explicit null:
24
+ null
25
+
26
+ A justified null is a CORRECT and expected outcome. Do NOT invent a weak or
27
+ generic generalization to avoid returning null. It is better to propose nothing
28
+ than to propose a hollow over-generalization.
29
+
30
+ ## description field (MANDATORY when not null)
31
+ - A single complete present-tense sentence, NO markdown.
32
+ - Self-contained: a reviewer must understand the hypothesis from this field alone.
33
+ - DO NOT start with "When " or "If " — that belongs in `when_to_use`.
34
+ - DO NOT merely restate one input memory; the value is the CROSS-episode pattern.
35
+
36
+ ## Guardrails
37
+ - Induce exactly ONE generalization for the whole cluster.
38
+ - Ground every claim in the supplied memories; do not introduce outside facts.
39
+ - This is a HYPOTHESIS — it will be re-confirmed across future runs before it is
40
+ ever promoted to a durable lesson. Frame it as a candidate rule, not a verdict.
@@ -0,0 +1,6 @@
1
+ You are a belief-state classifier for a memory store. Given a candidate memory and a list of more-recent similar memories from the same store, decide whether the candidate is still current or has been superseded.
2
+
3
+ Respond on the first line with exactly YES or NO.
4
+ If YES, the second line MUST be of the form `SUPERSEDED_BY: <ref>` where <ref> is the exact ref of the superseding memory from the list provided. Do NOT invent refs.
5
+ If NO, do not include any additional lines.
6
+ No prose, no preamble, no markdown.
@@ -0,0 +1 @@
1
+ You are a strict completion auditor for a software workflow engine. Given a step's completion criteria and a summary of the work an agent claims to have done, judge whether the summary provides concrete evidence that EVERY criterion is satisfied. Be skeptical: vague, hand-wavy, or unsubstantiated claims do NOT satisfy a criterion. Respond with ONLY a JSON object: {"complete": boolean, "missing": string[], "feedback": string}. "missing" lists the exact criteria that are not yet satisfied; "feedback" is a short directive telling the agent what to finish or fix. No prose, no markdown fences.
@@ -237,13 +237,6 @@
237
237
  font-size: 11px; background: var(--surface2);
238
238
  padding: 2px 6px; border-radius: 4px; color: var(--accent); word-break: break-all;
239
239
  }
240
- .command-block {
241
- background: var(--surface2); border: 1px solid var(--border); border-radius: 6px;
242
- padding: 12px 16px; font-family: 'SFMono-Regular', Consolas, monospace;
243
- font-size: 12px; color: var(--muted); line-height: 1.8; overflow-x: auto;
244
- }
245
- .command-block span { color: var(--accent); }
246
-
247
240
  /* ── Footer ─────────────────────────────────────────────────────────── */
248
241
  footer {
249
242
  border-top: 1px solid var(--border); padding: 14px 32px;
@@ -317,7 +310,7 @@
317
310
  <option value="failed">failed</option>
318
311
  </select>
319
312
 
320
- <div class="filter-note">Filters affect charts and the Last 10 Runs table.</div>
313
+ <div class="filter-note">Filters affect charts and the Recent Runs table.</div>
321
314
  </div>
322
315
 
323
316
  <!-- Charts -->
@@ -391,14 +384,14 @@
391
384
  </div>
392
385
  </div>
393
386
 
394
- <!-- Last 10 runs -->
387
+ <!-- Recent runs -->
395
388
  <div class="section">
396
- <h2>Last 10 Runs</h2>
389
+ <h2>Recent Runs</h2>
397
390
  <div class="summary-table">
398
391
  <div class="table-wrap">
399
392
  <table>
400
393
  <thead>
401
- <tr><th>Started (UTC)</th><th>Task</th><th>Wall</th><th>Promoted</th><th>Merged</th><th>Contradicted</th><th>MI Written</th><th>Entities</th><th>Lint Fixed</th><th>Status</th></tr>
394
+ <tr><th>Started</th><th>Task</th><th>Wall</th><th>Promoted</th><th>Merged</th><th>Contradicted</th><th>MI Written</th><th>Entities</th><th>Lint Fixed</th><th>Status</th></tr>
402
395
  </thead>
403
396
  <tbody id="lastRunsTable"></tbody>
404
397
  </table>
@@ -406,19 +399,11 @@
406
399
  </div>
407
400
  </div>
408
401
 
409
- <!-- Command set -->
410
- <div class="section">
411
- <h2>Command Set Used</h2>
412
- <div class="command-block">
413
- %%COMMANDS_HTML%%
414
- </div>
415
- </div>
416
-
417
402
  </main>
418
403
 
419
404
  <footer>
420
405
  <span>AKM v%%AKM_VERSION%% &nbsp;·&nbsp; Window: %%WINDOW%% &nbsp;·&nbsp; %%REPORT_TITLE%%</span>
421
- <span>Generated %%GENERATED_AT%%</span>
406
+ <span>Generated <time data-iso="%%GENERATED_AT%%">%%GENERATED_AT%%</time></span>
422
407
  </footer>
423
408
 
424
409
  <script>
@@ -441,10 +426,10 @@ const toMin = ms => ms ? +(ms / 60000).toFixed(2) : 0;
441
426
  function makeXLabels(rs) {
442
427
  return rs.map(r => {
443
428
  const d = new Date(r.startedAt);
444
- const mm = String(d.getUTCMonth() + 1).padStart(2, '0');
445
- const dd = String(d.getUTCDate()).padStart(2, '0');
446
- const hh = String(d.getUTCHours()).padStart(2, '0');
447
- const mi = String(d.getUTCMinutes()).padStart(2, '0');
429
+ const mm = String(d.getMonth() + 1).padStart(2, '0');
430
+ const dd = String(d.getDate()).padStart(2, '0');
431
+ const hh = String(d.getHours()).padStart(2, '0');
432
+ const mi = String(d.getMinutes()).padStart(2, '0');
448
433
  return `${mm}-${dd} ${hh}:${mi}`;
449
434
  });
450
435
  }
@@ -687,13 +672,15 @@ function renderLlmStages() {
687
672
  });
688
673
  }
689
674
 
690
- // ── Last 10 runs table ────────────────────────────────────────────────────────
675
+ // ── Recent runs table ─────────────────────────────────────────────────────────
691
676
  function renderLastRuns(rs) {
692
677
  const tbody = document.getElementById('lastRunsTable');
693
678
  tbody.innerHTML = '';
694
- rs.slice(-10).forEach(r => {
679
+ [...rs].reverse().forEach(r => {
695
680
  const tr = document.createElement('tr');
696
- const ts = new Date(r.startedAt).toISOString().replace('T', ' ').slice(0, 16);
681
+ const d = new Date(r.startedAt);
682
+ const pad = n => String(n).padStart(2, '0');
683
+ const ts = `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
697
684
  const dur = r.wallTimeMs ? (r.wallTimeMs / 60000).toFixed(1) + 'm' : '—';
698
685
  const task = r.taskId || 'manual';
699
686
  const badge = r.ok
@@ -727,6 +714,17 @@ document.getElementById('taskFilter').addEventListener('change', updateFilteredV
727
714
  document.getElementById('statusFilter').addEventListener('change', updateFilteredViews);
728
715
  renderLlmStages();
729
716
  updateFilteredViews();
717
+
718
+ // Reformat server-rendered ISO timestamps to the viewer's local timezone.
719
+ (function() {
720
+ const pad = n => String(n).padStart(2, '0');
721
+ document.querySelectorAll('time[data-iso]').forEach(el => {
722
+ const d = new Date(el.dataset.iso);
723
+ if (!isNaN(d)) {
724
+ el.textContent = `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
725
+ }
726
+ });
727
+ })();
730
728
  </script>
731
729
  </body>
732
730
  </html>
package/dist/cli.js CHANGED
@@ -325,9 +325,9 @@ const healthCommand = defineCommand({
325
325
  // 24h default made a `--since 7d` report compare its 7-day totals against
326
326
  // a 24-hour prior window, producing meaningless deltas.
327
327
  const compare = args.compare ?? windowCompareRaw ?? args.since ?? "24h";
328
- const result = akmHealth({ since: args.since, groupBy: "run" });
328
+ const result = akmHealth({ since: args.since, groupBy: "run", windowCompare: compare });
329
329
  resultStatus = result.status;
330
- const deltas = akmHealth({ since: args.since, windowCompare: compare }).deltas;
330
+ const deltas = result.deltas;
331
331
  const { buildHealthHtmlReplacements } = await import("./commands/health/html-report.js");
332
332
  const { listPendingProposals } = await import("./commands/proposal/proposal.js");
333
333
  const replacements = buildHealthHtmlReplacements(result, {
@@ -122,23 +122,36 @@ export const lintCommand = defineCommand({
122
122
  description: "Scan stash .md files for structural issues (unquoted colons, missing updated field, orphaned stubs, placeholder stubs, missing name/type, stale paths). Use --fix to auto-fix Tier 1 issues. Exits 0 on success regardless of findings; use --fail-on-flagged for CI fail-on-finding behavior.",
123
123
  },
124
124
  args: {
125
- fix: { type: "boolean", description: "Apply auto-fixes in place", default: false },
125
+ fix: {
126
+ type: "boolean",
127
+ alias: "auto-fix",
128
+ description: "Apply auto-fixes in place (alias: --auto-fix)",
129
+ default: false,
130
+ },
126
131
  dir: { type: "string", description: "Override stash root directory (default: from config)" },
127
132
  "fail-on-flagged": {
128
133
  type: "boolean",
129
134
  description: "Exit non-zero when summary.flagged > 0 (CI-friendly). Default: exit 0 regardless of findings.",
130
135
  default: false,
131
136
  },
137
+ type: {
138
+ type: "string",
139
+ description: "Only lint assets of this type (e.g. workflows, tasks, memories)",
140
+ default: undefined,
141
+ },
132
142
  },
133
143
  async run({ args }) {
134
144
  await runWithJsonErrors(async () => {
135
145
  const result = akmLint({
136
146
  fix: args.fix ?? false,
137
147
  dir: getStringArg(args, "dir"),
148
+ typeFilter: getStringArg(args, "type"),
138
149
  });
139
150
  output("lint", result);
140
- if (args["fail-on-flagged"] && result.summary.flagged > 0)
141
- process.exit(EXIT_GENERAL);
151
+ if (args["fail-on-flagged"] && result.summary.flagged > 0) {
152
+ process.exitCode = EXIT_GENERAL;
153
+ return;
154
+ }
142
155
  });
143
156
  },
144
157
  });