akm-cli 0.9.0-beta.42 → 0.9.0-beta.44

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.
@@ -1,22 +1,38 @@
1
1
  ---
2
2
  category: convention
3
- description: Starter SOFT authoring conventions for agent assets edit to taste.
3
+ description: Soft authoring conventions for agent assets using scoped role, tool, and maintenance rules.
4
4
  when_to_use: Surfaced to authoring agents when they write or revise an agent asset.
5
5
  ---
6
6
 
7
7
  <!--
8
8
  SOFT guidance only — advice, not a contract. Nothing here is enforced by the
9
- proposal gate; the validator-rejecting HARD rules live in
10
- src/core/authoring-rules.ts (#645) and remain the sole enforced source. Editing
11
- or deleting this file cannot weaken the gate.
9
+ proposal gate; validator-rejecting HARD rules live in src/core/authoring-rules.ts
10
+ and remain the sole enforced source. Editing or deleting this file cannot weaken
11
+ the gate. Tune the guidance below to match how your stash wants this asset type
12
+ maintained.
12
13
  -->
13
14
 
14
15
  # Agent authoring conventions
15
16
 
16
- An agent is markdown whose frontmatter describes a reusable role.
17
+ An agent asset defines a reusable role. Treat it like a disciplined maintainer, not a generic personality. Its job is to know its scope, read the right rulebooks, use the right tools, and leave the stash in better shape.
17
18
 
18
- - Frontmatter typically carries `name`, `description`, and optionally `tools` and
19
- `model`. Write the `description` so a dispatcher knows exactly when to delegate.
20
- - The body is the system prompt: establish the role, its scope, and its boundaries.
21
- - Be explicit about what the agent should and should not do, and what it returns.
22
- - Prefer a focused single-responsibility persona over a broad do-everything agent.
19
+ ## Purpose
20
+
21
+ Use an agent when a recurring task benefits from a specialized role, bounded responsibilities, and explicit tool behavior.
22
+
23
+ ## Authoring strategy
24
+
25
+ - Write the description so a dispatcher knows exactly when to delegate to this agent.
26
+ - Define the agent’s domain, authority, boundaries, and expected output.
27
+ - Specify what the agent must read first: relevant stash standards, type conventions, reference docs, source files, or prior lessons.
28
+ - State tool expectations plainly: what tools it may use, what it should avoid, and when it must ask for human review.
29
+ - Give the agent maintenance duties when appropriate: update cross-references, append logs, preserve provenance, and surface contradictions.
30
+ - Prefer a narrow role that does one thing reliably over a broad do-everything persona.
31
+ - Include handoff behavior: what the agent should return when it cannot complete the task safely.
32
+
33
+ ## Maintenance strategy
34
+
35
+ - Refine the agent when repeated sessions show the same delegation failure.
36
+ - Add explicit negative guidance when the agent overreaches.
37
+ - Keep role instructions stable and concise; move large background material into knowledge assets.
38
+ - Use lessons to capture operational improvements, then promote stable ones into the agent when they become part of the role.
@@ -1,22 +1,38 @@
1
1
  ---
2
2
  category: convention
3
- description: Starter SOFT authoring conventions for command assets edit to taste.
3
+ description: Soft authoring conventions for command assets using repeatable LLM operation patterns.
4
4
  when_to_use: Surfaced to authoring agents when they write or revise a command asset.
5
5
  ---
6
6
 
7
7
  <!--
8
8
  SOFT guidance only — advice, not a contract. Nothing here is enforced by the
9
- proposal gate; the validator-rejecting HARD rules live in
10
- src/core/authoring-rules.ts (#645) and remain the sole enforced source. Editing
11
- or deleting this file cannot weaken the gate.
9
+ proposal gate; validator-rejecting HARD rules live in src/core/authoring-rules.ts
10
+ and remain the sole enforced source. Editing or deleting this file cannot weaken
11
+ the gate. Tune the guidance below to match how your stash wants this asset type
12
+ maintained.
12
13
  -->
13
14
 
14
15
  # Command authoring conventions
15
16
 
16
- A command is a markdown prompt template the user invokes by name.
17
+ A command is a reusable markdown prompt template invoked by name. Treat it like an operation in the stash: every vague instruction will compound into repeated vague output.
17
18
 
18
- - Optional frontmatter carries `name` and `description`; the body *is* the prompt.
19
- - Write the body as the instruction you want the model to follow when invoked.
20
- - State the task, the expected inputs, and the shape of the desired output up front.
21
- - Keep it tight and unambiguous — a command is run repeatedly, so vagueness compounds.
22
- - Use clear placeholders for any arguments the caller will supply.
19
+ ## Purpose
20
+
21
+ Use a command when the user or agent needs to perform the same prompt-shaped task repeatedly with different arguments or context.
22
+
23
+ ## Authoring strategy
24
+
25
+ - Put the task, inputs, constraints, and expected output shape near the top.
26
+ - Make argument placeholders obvious and describe what each one should contain.
27
+ - Tell the model what to inspect before acting, especially relevant stash assets, facts, standards, or reference docs.
28
+ - State the decision boundary: what the command should do directly, what it should only propose, and what it should refuse or defer.
29
+ - Include output requirements that are stable across runs.
30
+ - Keep the prompt tight. A command should be easy to invoke and hard to misinterpret.
31
+ - Avoid embedding one-time project details unless the command is intentionally project-specific.
32
+
33
+ ## Maintenance strategy
34
+
35
+ - If users repeatedly clarify the same missing detail, add that detail to the command.
36
+ - If command output regularly becomes useful durable knowledge, instruct the agent to file the result into the right asset type.
37
+ - If the command starts handling multiple unrelated tasks, split it into smaller commands.
38
+ - Preserve a clear invocation contract so future agents can call the command safely.
@@ -1,24 +1,39 @@
1
1
  ---
2
2
  category: convention
3
- description: Starter SOFT authoring conventions for fact assets edit to taste.
3
+ description: Soft authoring conventions for fact assets using pinned-core and just-in-time context principles.
4
4
  when_to_use: Surfaced to authoring agents when they write or revise a fact asset.
5
5
  ---
6
6
 
7
7
  <!--
8
8
  SOFT guidance only — advice, not a contract. Nothing here is enforced by the
9
- proposal gate; the validator-rejecting HARD rules live in
10
- src/core/authoring-rules.ts (#645) and remain the sole enforced source. Editing
11
- or deleting this file cannot weaken the gate.
9
+ proposal gate; validator-rejecting HARD rules live in src/core/authoring-rules.ts
10
+ and remain the sole enforced source. Editing or deleting this file cannot weaken
11
+ the gate. Tune the guidance below to match how your stash wants this asset type
12
+ maintained.
12
13
  -->
13
14
 
14
15
  # Fact authoring conventions
15
16
 
16
- A fact is durable stash-level context personal, team, or project details,
17
- coding conventions, or stash-meta.
17
+ A fact is durable stash-level context: personal, team, project, convention, or meta knowledge. Treat facts as the stash’s semantic layer — selectively loaded context that should guide future work without bloating every prompt.
18
18
 
19
- - Frontmatter should include a `description` and a `category`
20
- (personal | team | project | convention | meta).
21
- - Set `pinned: true` only for the small always-injected core; most facts stay unpinned.
22
- - Keep each fact short, high-signal, and self-contained — it is durable context,
23
- not an episodic note.
24
- - Write it as a standing declaration that stays true across sessions.
19
+ ## Purpose
20
+
21
+ Use a fact for stable information that future agents should treat as true or normative: user preferences, project identity, team stack, architecture principles, naming conventions, tag vocabulary, or stash organization.
22
+
23
+ ## Authoring strategy
24
+
25
+ - Write each fact as a standing declaration that can survive across sessions.
26
+ - Keep it short, high-signal, and self-contained.
27
+ - Choose the narrowest useful category: personal, team, project, convention, or meta.
28
+ - Use `pinned: true` only for the small core that should be available constantly.
29
+ - Leave most facts unpinned so they can be retrieved just-in-time.
30
+ - Include scope and provenance when a fact is project-specific, inferred, or subject to change.
31
+ - Separate facts from memories: memories preserve observations; facts state durable truth or durable policy.
32
+ - Separate facts from knowledge: knowledge explains a topic; facts declare compact context.
33
+
34
+ ## Maintenance strategy
35
+
36
+ - Revise or supersede facts when the durable truth changes.
37
+ - Do not allow contradictory facts to remain equally active.
38
+ - Promote repeated memories or lessons into facts only when they become stable context.
39
+ - Keep convention and meta facts especially clear, because they steer future asset creation.
@@ -1,22 +1,40 @@
1
1
  ---
2
2
  category: convention
3
- description: Starter SOFT authoring conventions for knowledge assets edit to taste.
3
+ description: Soft authoring conventions for knowledge assets as compiled, on-demand reference documents.
4
4
  when_to_use: Surfaced to authoring agents when they write or revise a knowledge asset.
5
5
  ---
6
6
 
7
7
  <!--
8
8
  SOFT guidance only — advice, not a contract. Nothing here is enforced by the
9
- proposal gate; the validator-rejecting HARD rules live in
10
- src/core/authoring-rules.ts (#645) and remain the sole enforced source. Editing
11
- or deleting this file cannot weaken the gate.
9
+ proposal gate; validator-rejecting HARD rules live in src/core/authoring-rules.ts
10
+ and remain the sole enforced source. Editing or deleting this file cannot weaken
11
+ the gate. Tune the guidance below to match how your stash wants this asset type
12
+ maintained.
12
13
  -->
13
14
 
14
15
  # Knowledge authoring conventions
15
16
 
16
- A knowledge asset is a reference document meant to be read on demand.
17
+ A knowledge asset is a compiled reference document meant to be read on demand. Treat it as the synthesized layer above raw material: not source files, not chat residue, but integrated, navigable understanding that saves future agents from rediscovering the same material.
17
18
 
18
- - Open with a top-level `# Title` that names the subject plainly.
19
- - Organise into concise, well-headed sections so a reader can jump to what they need.
20
- - Favour accuracy and clarity over completeness; link or cross-reference rather than
21
- duplicating other assets.
22
- - Voice: explanatory and neutral, written to be re-read months later.
19
+ ## Purpose
20
+
21
+ Use a knowledge asset for durable reference material, synthesized explanations, design notes, comparisons, and project context that is broader than a single memory but less procedural than a skill.
22
+
23
+ ## Authoring strategy
24
+
25
+ - Open with a plain top-level title that names the subject.
26
+ - Add a concise orientation paragraph: what this document covers and when it should be read.
27
+ - For a long reference, add a table of contents near the top so the full scope is visible even on a partial read.
28
+ - Organize by stable concepts, decisions, entities, or questions — roughly one page per concept.
29
+ - Cross-reference related assets instead of duplicating them, so a navigable graph forms over time.
30
+ - Preserve provenance where it matters: cite source files, raw notes, session logs, or issues by path/ref.
31
+ - Call out contradictions, uncertainty, stale claims, and open questions explicitly.
32
+ - Prefer accurate synthesis over exhaustive dumping. Raw material belongs elsewhere; this file is the compiled layer.
33
+ - Use tables or checklists when they make retrieval and comparison easier.
34
+
35
+ ## Maintenance strategy
36
+
37
+ - Update the existing page when new information changes the same topic; append a dated note rather than silently rewriting when provenance matters.
38
+ - Create a new page when the concept deserves its own durable entry.
39
+ - Add links both ways when a new relationship matters.
40
+ - Periodically scan for orphaned, stale, or overlapping knowledge docs and consolidate them.
@@ -1,25 +1,43 @@
1
1
  ---
2
2
  category: convention
3
- description: Starter SOFT authoring conventions for lesson assets edit to taste.
3
+ description: Soft authoring conventions for lesson assets that capture compounding, hard-won judgment.
4
4
  when_to_use: Surfaced to authoring agents when they write or revise a lesson asset.
5
5
  ---
6
6
 
7
7
  <!--
8
8
  SOFT guidance only — advice, not a contract. Nothing here is enforced by the
9
- proposal gate; the validator-rejecting HARD rules live in
10
- src/core/authoring-rules.ts (#645) and remain the sole enforced source. Editing
11
- or deleting this file cannot weaken the gate. Tune the guidance below to match
12
- how your stash likes lessons written.
9
+ proposal gate; validator-rejecting HARD rules live in src/core/authoring-rules.ts
10
+ and remain the sole enforced source. Editing or deleting this file cannot weaken
11
+ the gate. Tune the guidance below to match how your stash wants this asset type
12
+ maintained.
13
13
  -->
14
14
 
15
15
  # Lesson authoring conventions
16
16
 
17
- A lesson captures durable, hard-won judgement about *when* to reach for something
18
- and *what goes wrong without it* — it is not a restatement of the source asset.
17
+ A lesson captures durable, hard-won judgment that should compound across future agent sessions. Treat it as distilled judgment about how to act: it should preserve the extracted meaning of what real use revealed, not merely recount an incident or summarize another asset.
19
18
 
20
- - Lead with the trigger: when should a reader reach for this lesson?
21
- - Then the failure mode: what breaks, or what gets missed, without it?
22
- - Then the insight: what did real use reveal that the asset itself does not say?
23
- - Keep it to one to three short, concrete paragraphs. Prefer specifics over
24
- general advice; a lesson earns its keep by being actionable in a real moment.
25
- - Voice: direct and practical, written for a future agent mid-task.
19
+ ## Purpose
20
+
21
+ Use a lesson to record:
22
+
23
+ - when to reach for a pattern, asset, or decision;
24
+ - what tends to go wrong without it;
25
+ - what evidence, feedback, or repeated experience made the lesson worth keeping;
26
+ - how a future agent should act differently because this lesson exists.
27
+
28
+ ## Authoring strategy
29
+
30
+ - Lead with the trigger: the concrete situation where this lesson should be loaded.
31
+ - Follow with the failure mode: what mistake, omission, or confusion this prevents.
32
+ - End with the reusable judgment: the practical rule a future agent can apply.
33
+ - Keep the scope narrow. A lesson should teach one durable behavior.
34
+ - Prefer observed evidence over generic advice. Mention the kind of signal that produced the lesson, such as rejected proposals, repeated lint findings, user feedback, or session outcomes.
35
+ - Do not restate the source asset. Lessons are compiled judgment, not copied documentation.
36
+ - Write for a future agent mid-task: direct, practical, and easy to apply.
37
+
38
+ ## Maintenance strategy
39
+
40
+ - Update an existing lesson when new feedback sharpens the same judgment.
41
+ - Create a new lesson only when the trigger or failure mode is meaningfully different.
42
+ - Deprecate or revise stale lessons instead of allowing contradictory guidance to accumulate.
43
+ - When a lesson becomes broadly normative, consider promoting the stable rule into a `fact:conventions/...` asset.
@@ -1,21 +1,38 @@
1
1
  ---
2
2
  category: convention
3
- description: Starter SOFT authoring conventions for memory assets edit to taste.
3
+ description: Soft authoring conventions for memory assets using durable-context and provenance discipline.
4
4
  when_to_use: Surfaced to authoring agents when they write or revise a memory asset.
5
5
  ---
6
6
 
7
7
  <!--
8
8
  SOFT guidance only — advice, not a contract. Nothing here is enforced by the
9
- proposal gate; the validator-rejecting HARD rules live in
10
- src/core/authoring-rules.ts (#645) and remain the sole enforced source. Editing
11
- or deleting this file cannot weaken the gate.
9
+ proposal gate; validator-rejecting HARD rules live in src/core/authoring-rules.ts
10
+ and remain the sole enforced source. Editing or deleting this file cannot weaken
11
+ the gate. Tune the guidance below to match how your stash wants this asset type
12
+ maintained.
12
13
  -->
13
14
 
14
15
  # Memory authoring conventions
15
16
 
16
- A memory is a short factual note the user wants persisted across sessions.
17
+ A memory is a short, durable note that should survive beyond the current session. Treat it as a small compiled fact or decision, not a transcript fragment.
17
18
 
18
- - Frontmatter usually includes a `description` that states the fact in one line.
19
- - Keep the body brief and self-contained — one durable fact or decision per memory.
20
- - Write it so it still reads clearly with no surrounding conversation for context.
21
- - Prefer durable, reusable facts over episodic play-by-play of a single session.
19
+ ## Purpose
20
+
21
+ Use a memory when a future agent would make a better decision by knowing a specific user preference, project decision, environmental fact, constraint, or observed outcome.
22
+
23
+ ## Authoring strategy
24
+
25
+ - Record one durable fact, decision, or constraint per memory.
26
+ - Write it so it stands alone without the original conversation.
27
+ - Include enough context to prevent misapplication: subject, scope, and when it matters.
28
+ - Prefer stable, reusable information over step-by-step session play-by-play.
29
+ - Mark uncertainty or subjectivity clearly when the memory is not a settled fact.
30
+ - Preserve source/provenance in frontmatter or body when the memory came from a session, log, user statement, or derived inference.
31
+ - Avoid storing secrets, private tokens, or volatile temporary state as memory.
32
+
33
+ ## Maintenance strategy
34
+
35
+ - Update or supersede memories when newer evidence changes the truth.
36
+ - Consolidate repeated memories into a clearer fact or knowledge asset.
37
+ - Convert broad, stable conventions into `fact` assets.
38
+ - Archive memories that are no longer current rather than letting stale context keep influencing agents.
@@ -1,21 +1,43 @@
1
1
  ---
2
2
  category: convention
3
- description: Starter SOFT authoring conventions for script assets edit to taste.
3
+ description: Soft authoring conventions for script assets using agent-safe CLI helper principles.
4
4
  when_to_use: Surfaced to authoring agents when they write or revise a script asset.
5
5
  ---
6
6
 
7
7
  <!--
8
8
  SOFT guidance only — advice, not a contract. Nothing here is enforced by the
9
- proposal gate; the validator-rejecting HARD rules live in
10
- src/core/authoring-rules.ts (#645) and remain the sole enforced source. Editing
11
- or deleting this file cannot weaken the gate.
9
+ proposal gate; validator-rejecting HARD rules live in src/core/authoring-rules.ts
10
+ and remain the sole enforced source. Editing or deleting this file cannot weaken
11
+ the gate. Tune the guidance below to match how your stash wants this asset type
12
+ maintained.
12
13
  -->
13
14
 
14
15
  # Script authoring conventions
15
16
 
16
- A script is an executable text file stored as-is and run on demand.
17
+ A script is an executable helper that an agent or human can run on demand. Treat it like a small, deterministic tool that reduces manual bookkeeping and makes repeatable operations safer.
17
18
 
18
- - Start with a shebang appropriate to the interpreter (e.g. `#!/usr/bin/env bash`).
19
- - Follow it with a short usage comment: what the script does and how to invoke it.
20
- - Keep the script focused on one job; fail loudly and early on bad input.
21
- - Prefer readable, well-commented logic over cleverness — these get re-run by others.
19
+ ## Purpose
20
+
21
+ Use a script when a task is mechanical, repeatable, and better handled by a deterministic program than by free-form agent edits.
22
+
23
+ ## Authoring strategy
24
+
25
+ - Start with the appropriate interpreter line and a short usage comment.
26
+ - State what the script does, expected inputs, outputs, side effects, and failure behavior.
27
+ - Keep one script focused on one job.
28
+ - Validate inputs before mutation.
29
+ - Fail loudly and early on unsafe or ambiguous input.
30
+ - Declare required dependencies and assumptions explicitly rather than assuming a tool is installed.
31
+ - Justify any non-obvious constant (timeout, retry count, limit) in a comment so a future reader can adjust it safely.
32
+ - Prefer idempotent behavior where practical.
33
+ - Avoid hidden network calls, destructive defaults, or silent writes.
34
+ - Never print secrets or sensitive values.
35
+ - Write output that is easy for both humans and agents to parse.
36
+ - Favor clear names, straightforward control flow, and comments at decision points.
37
+
38
+ ## Maintenance strategy
39
+
40
+ - Add examples when agents or users repeatedly invoke the script incorrectly.
41
+ - Keep dangerous actions behind explicit flags.
42
+ - When a script becomes a core operation, add or update a workflow that explains when to run it.
43
+ - If the script encodes a convention, also document that convention in a fact or knowledge asset.
@@ -1,23 +1,40 @@
1
1
  ---
2
2
  category: convention
3
- description: Starter SOFT authoring conventions for skill assets edit to taste.
3
+ description: Soft authoring conventions for skill assets as reusable, just-in-time procedural rulebooks.
4
4
  when_to_use: Surfaced to authoring agents when they write or revise a skill asset.
5
5
  ---
6
6
 
7
7
  <!--
8
8
  SOFT guidance only — advice, not a contract. Nothing here is enforced by the
9
- proposal gate; the validator-rejecting HARD rules live in
10
- src/core/authoring-rules.ts (#645) and remain the sole enforced source. Editing
11
- or deleting this file cannot weaken the gate.
9
+ proposal gate; validator-rejecting HARD rules live in src/core/authoring-rules.ts
10
+ and remain the sole enforced source. Editing or deleting this file cannot weaken
11
+ the gate. Tune the guidance below to match how your stash wants this asset type
12
+ maintained.
12
13
  -->
13
14
 
14
15
  # Skill authoring conventions
15
16
 
16
- A skill is a reusable, self-contained capability stored as `skills/<name>/SKILL.md`.
17
+ A skill is a reusable, self-contained capability stored as `skills/<name>/SKILL.md`. Treat it like a compact operating manual that an agent can load just-in-time, follow without rediscovering the process, and improve when repeated use exposes gaps.
17
18
 
18
- - Frontmatter usually carries `name`, `description`, and `when_to_use`. Write the
19
- `description` so a dispatcher can decide *from it alone* whether to load the skill.
20
- - Open the body with the goal in an imperative voice ("Generate…", "Review…").
21
- - Structure as: purpose, when to use, then the procedure or guidance as ordered
22
- steps or short sections. Favour scannable headings over long prose.
23
- - Keep it focused on one capability; split unrelated concerns into separate skills.
19
+ ## Purpose
20
+
21
+ Use a skill when the stash needs reusable procedural guidance for a recurring class of work. A good skill reduces repeated reasoning cost: future agents should not have to reconstruct the same method from raw context.
22
+
23
+ ## Authoring strategy
24
+
25
+ - Make the dispatch signal clear. The description should let a dispatcher decide whether to load the skill without reading the whole body.
26
+ - Open with the outcome the skill helps produce.
27
+ - State when to use it, when not to use it, and what inputs the agent should gather before acting.
28
+ - Structure the body as a rulebook: principles first, then procedure, then checks.
29
+ - Use short sections and ordered steps where sequence matters.
30
+ - Match the level of detail to how fragile the task is: open-ended work gets high-level heuristics and room to reason, while fragile or consistency-critical steps get exact, unambiguous instructions.
31
+ - Keep the body lean and move bulky background into companion knowledge docs referenced one level deep, so the skill loads cheaply and stays scannable.
32
+ - Include failure modes and verification steps. A skill should tell the agent how to know the work is complete.
33
+ - Keep one skill focused on one capability. Split unrelated concerns into separate skills and cross-reference them.
34
+
35
+ ## Maintenance strategy
36
+
37
+ - Update the skill when session logs, feedback, or rejected proposals reveal repeatable confusion.
38
+ - Add companion knowledge docs when the skill needs background material that would bloat the main procedure.
39
+ - Promote durable recurring corrections into the skill; leave one-off observations in memories or lessons.
40
+ - Prefer small edits that preserve the skill’s operational shape over broad rewrites that erase tested guidance.
@@ -1,22 +1,43 @@
1
1
  ---
2
2
  category: convention
3
- description: Starter SOFT authoring conventions for workflow assets edit to taste.
3
+ description: Soft authoring conventions for workflow assets using explicit operations, logging, and lintable steps.
4
4
  when_to_use: Surfaced to authoring agents when they write or revise a workflow asset.
5
5
  ---
6
6
 
7
7
  <!--
8
8
  SOFT guidance only — advice, not a contract. Nothing here is enforced by the
9
- proposal gate; the validator-rejecting HARD rules live in
10
- src/core/authoring-rules.ts (#645) and remain the sole enforced source. Editing
11
- or deleting this file cannot weaken the gate.
9
+ proposal gate; validator-rejecting HARD rules live in src/core/authoring-rules.ts
10
+ and remain the sole enforced source. Editing or deleting this file cannot weaken
11
+ the gate. Tune the guidance below to match how your stash wants this asset type
12
+ maintained.
12
13
  -->
13
14
 
14
15
  # Workflow authoring conventions
15
16
 
16
- A workflow describes a multi-step process an agent or human follows in order.
17
+ A workflow describes an ordered process an agent or human can follow. Treat it as the operation layer of a maintained stash: clear steps, clear state, clear completion criteria, and enough bookkeeping to resume safely.
17
18
 
18
- - Open with a top-level `# <Title>` naming the workflow's outcome.
19
- - Lay out the steps as ordered `## Step N` sections, each with a clear action.
20
- - For each step, state what to do, what it depends on, and how to know it is done.
21
- - Keep steps atomic and resumable — a reader should be able to stop and pick up midway.
22
- - Note any branch points or preconditions explicitly rather than burying them in prose.
19
+ ## Purpose
20
+
21
+ Use a workflow when the task requires multiple steps, branching decisions, repeated checks, or durable progress tracking.
22
+
23
+ ## Authoring strategy
24
+
25
+ - Open with the outcome the workflow produces.
26
+ - State prerequisites, required inputs, and tools before the steps.
27
+ - Use ordered step sections when sequence matters.
28
+ - For each step, specify:
29
+ - what to do;
30
+ - what evidence or input it depends on;
31
+ - what output it produces;
32
+ - how to know the step is done.
33
+ - Make branch points explicit. Do not bury conditional behavior in prose.
34
+ - Include validation, lint, or review steps near the end.
35
+ - Include rollback or recovery notes when the workflow mutates files, state, repos, or external systems.
36
+ - Keep steps atomic and resumable so an interrupted run can continue without guessing.
37
+
38
+ ## Maintenance strategy
39
+
40
+ - Update the workflow when repeated execution reveals missing checks or unclear handoffs.
41
+ - Add logging expectations when the workflow creates durable state.
42
+ - Extract reusable sub-procedures into skills or scripts when the workflow grows too broad.
43
+ - Record recurring mistakes as lessons, then fold stable corrections back into the workflow.
@@ -964,6 +964,63 @@ export async function akmImprove(options = {}) {
964
964
  // #576: clears the per-run LLM usage sink. Defaults to a no-op until the sink
965
965
  // is installed inside the try; the `finally` always calls it.
966
966
  let disposeLlmUsageSink = () => { };
967
+ // ── Crash-safe / incremental stash sync (#662) ──────────────────────────────
968
+ // The primary stash writes as a filesystem source DURING the run
969
+ // (write-source.ts case-3); those writes become a git commit only when this
970
+ // closure runs. Historically the only call site was a single BATCH commit at
971
+ // the very end of the happy path, so a run interrupted AFTER writing but
972
+ // BEFORE finishing — a mid-cycle crash, a budget abort, or an external
973
+ // SIGTERM/`process.exit` — left every write uncommitted until some LATER run
974
+ // happened to finish cleanly and swept the whole backlog up. We now call this
975
+ // from THREE places: between cycles (bank each completed cycle), at end-of-run
976
+ // (the converged commit), and from the catch path (commit what was written
977
+ // before the crash). That shrinks the worst-case loss from "the entire run" to
978
+ // "the in-flight cycle".
979
+ //
980
+ // Declared in the OUTER scope (not inside the try) so the catch block can reach
981
+ // it. Idempotent + NON-FATAL: `saveGitStash` short-circuits a clean working
982
+ // tree ("nothing to commit") and a thrown sync error is swallowed here, so a
983
+ // repeat call after a no-op cycle is cheap and a failed push never fails the
984
+ // run. Gated identically to the original end-of-run block (git-backed primary
985
+ // stash, sync not disabled). `eventsCtx` is captured by reference, so calls
986
+ // after the db-backed context is installed inside the try use the live handle.
987
+ const effectiveSync = { ...improveProfile.sync, ...options.sync };
988
+ const commitStashBatch = (messageContext) => {
989
+ if (!primaryStashDir || effectiveSync.enabled === false || !isGitBackedStash(primaryStashDir)) {
990
+ return undefined;
991
+ }
992
+ const saveGitStashFn = options.saveGitStashFn ?? saveGitStash;
993
+ const writableOverride = resolveWritableOverride(_earlyConfig);
994
+ const push = effectiveSync.push !== false;
995
+ const message = renderSyncCommitMessage(effectiveSync.message ?? "akm improve auto-sync", messageContext, Date.now());
996
+ try {
997
+ const syncResult = saveGitStashFn(undefined, message, writableOverride, { push, repoDir: primaryStashDir });
998
+ appendEvent({
999
+ eventType: "stash_synced",
1000
+ metadata: {
1001
+ committed: syncResult.committed,
1002
+ pushed: syncResult.pushed,
1003
+ skipped: syncResult.skipped,
1004
+ reason: syncResult.reason ?? null,
1005
+ },
1006
+ }, eventsCtx);
1007
+ return {
1008
+ committed: syncResult.committed,
1009
+ pushed: syncResult.pushed,
1010
+ skipped: syncResult.skipped,
1011
+ ...(syncResult.reason !== undefined ? { reason: syncResult.reason } : {}),
1012
+ };
1013
+ }
1014
+ catch (syncErr) {
1015
+ const reason = syncErr instanceof Error ? syncErr.message : String(syncErr);
1016
+ warn(`improve: stash sync failed (non-fatal): ${reason}`);
1017
+ appendEvent({
1018
+ eventType: "stash_synced",
1019
+ metadata: { committed: false, pushed: false, skipped: true, reason },
1020
+ }, eventsCtx);
1021
+ return { committed: false, pushed: false, skipped: true, reason };
1022
+ }
1023
+ };
967
1024
  try {
968
1025
  // H7 (#566): arm the budget watchdog. `armBudgetWatchdog` captures both the
969
1026
  // budget timer and the hard-kill timer it schedules on exhaustion, returning
@@ -1053,6 +1110,16 @@ export async function akmImprove(options = {}) {
1053
1110
  break;
1054
1111
  }
1055
1112
  }
1113
+ // #662 incremental sync: bank the PREVIOUS cycle's writes before starting a
1114
+ // new one, so a crash/abort/timeout mid-run loses at most the in-flight
1115
+ // cycle rather than the whole run. Guarded on `cycleIndex > 0`, so the
1116
+ // common maxCycles:1 path never calls this — its single end-of-run commit
1117
+ // below stays the only sync and the serialized envelope is byte-identical
1118
+ // to pre-#662. `saveGitStash` no-ops a clean tree, so a cycle that wrote
1119
+ // nothing costs only a `git status`.
1120
+ if (cycleIndex > 0) {
1121
+ commitStashBatch({ scope, plannedRefs, runId: options.runId });
1122
+ }
1056
1123
  // Re-run ensureIndex + collectEligibleRefs + memory-cleanup recompute for
1057
1124
  // cycles 2+ (cycle 1 already ran them in the first try above). This makes
1058
1125
  // cycle N's gate-promoted proposals visible to this cycle's ref selection.
@@ -1309,57 +1376,18 @@ export async function akmImprove(options = {}) {
1309
1376
  warningCount: allWarnings.length,
1310
1377
  orphansPurged: orphansPurged ?? 0,
1311
1378
  }, eventsCtx);
1312
- // End-of-run BATCH auto-sync. Recognition is decoupled from the per-write
1313
- // path (see write-source.ts case-3): the primary stash writes as a
1314
- // filesystem source during the run, then is committed in one shot here via
1315
- // the same `saveGitStash` that `akm sync` calls. Gated on a non-dry-run, a
1316
- // git-backed primary stash (by `.git`, not by remote), and sync not
1317
- // disabled. A sync failure is NON-FATAL it never fails a successful run
1318
- // (mirrors the contradiction-detection best-effort pattern).
1319
- const effectiveSync = { ...improveProfile.sync, ...options.sync };
1320
- if (!result.dryRun && primaryStashDir && effectiveSync.enabled !== false && isGitBackedStash(primaryStashDir)) {
1321
- const saveGitStashFn = options.saveGitStashFn ?? saveGitStash;
1322
- // Reuse the config resolved at the top of the run (`_earlyConfig`) instead
1323
- // of a second loadConfig(); the writable derivation is shared with
1324
- // `akm sync` via resolveWritableOverride().
1325
- const writableOverride = resolveWritableOverride(_earlyConfig);
1326
- const push = effectiveSync.push !== false;
1327
- // `sync.message` may contain `{token}` placeholders (timestamp/date/time/
1328
- // scope/refs/accepted) expanded against this run's results; the default
1329
- // template has no tokens so it renders verbatim.
1330
- const message = renderSyncCommitMessage(effectiveSync.message ?? "akm improve auto-sync", result, Date.now());
1331
- try {
1332
- // Pass primaryStashDir as the explicit commit target so the gate above
1333
- // (which validated primaryStashDir via isGitBackedStash) and the commit
1334
- // operate on the SAME directory — avoids divergence when a caller passes
1335
- // a non-default options.stashDir (FIX 9).
1336
- const syncResult = saveGitStashFn(undefined, message, writableOverride, { push, repoDir: primaryStashDir });
1337
- result.sync = {
1338
- committed: syncResult.committed,
1339
- pushed: syncResult.pushed,
1340
- skipped: syncResult.skipped,
1341
- ...(syncResult.reason !== undefined ? { reason: syncResult.reason } : {}),
1342
- };
1343
- appendEvent({
1344
- eventType: "stash_synced",
1345
- metadata: {
1346
- committed: syncResult.committed,
1347
- pushed: syncResult.pushed,
1348
- skipped: syncResult.skipped,
1349
- reason: syncResult.reason ?? null,
1350
- },
1351
- }, eventsCtx);
1352
- }
1353
- catch (syncErr) {
1354
- const reason = syncErr instanceof Error ? syncErr.message : String(syncErr);
1355
- warn(`improve: end-of-run stash sync failed (non-fatal): ${reason}`);
1356
- result.sync = { committed: false, pushed: false, skipped: true, reason };
1357
- appendEvent({
1358
- eventType: "stash_synced",
1359
- metadata: { committed: false, pushed: false, skipped: true, reason },
1360
- }, eventsCtx);
1361
- }
1362
- }
1379
+ // End-of-run BATCH auto-sync the converged commit. Recognition is
1380
+ // decoupled from the per-write path (see write-source.ts case-3): the primary
1381
+ // stash writes as a filesystem source during the run, then is committed via
1382
+ // the same `saveGitStash` that `akm sync` calls. The gating (git-backed
1383
+ // primary stash, sync not disabled) and the NON-FATAL guarantee now live in
1384
+ // `commitStashBatch` (#662); the inter-cycle and catch-path calls reuse it.
1385
+ // dry-run already returned above, so this always runs on a completed live
1386
+ // run. `result.sync` reflects this final commit (for a one-cycle run it is
1387
+ // the only commit; for a multi-cycle run the earlier cycles were banked by
1388
+ // the inter-cycle calls and this records the last batch). `result` carries
1389
+ // the full `{accepted}`/`{refs}`/`{triage_*}` token data for the message.
1390
+ result.sync = commitStashBatch(result);
1363
1391
  return result;
1364
1392
  }
1365
1393
  catch (err) {
@@ -1372,6 +1400,13 @@ export async function akmImprove(options = {}) {
1372
1400
  durationMs: Date.now() - startMs,
1373
1401
  },
1374
1402
  }, eventsCtx);
1403
+ // #662 crash/abort safety net: commit whatever this run already wrote to the
1404
+ // primary stash BEFORE rethrowing, so an interrupted run (mid-cycle crash or
1405
+ // a cooperative budget abort that surfaces as a throw) does not leave its
1406
+ // writes uncommitted until a later clean run sweeps them up. Best-effort —
1407
+ // `commitStashBatch` swallows its own errors and no-ops a clean tree, so this
1408
+ // never masks or supersedes the original failure being rethrown below.
1409
+ commitStashBatch({ scope, plannedRefs, runId: options.runId });
1375
1410
  throw err;
1376
1411
  }
1377
1412
  finally {
@@ -3952,6 +3987,7 @@ async function runImprovePostLoopStage(args) {
3952
3987
  // #632 — clustering-tuning knobs. UNSET = pre-#632 behaviour.
3953
3988
  maxClusterSize: improveProfile.processes?.recombine?.maxClusterSize,
3954
3989
  excludeTags: improveProfile.processes?.recombine?.excludeTags,
3990
+ excludeEntities: improveProfile.processes?.recombine?.excludeEntities,
3955
3991
  });
3956
3992
  }
3957
3993
  catch (e) {
@@ -57,7 +57,27 @@ import { isConsolidationEligibleMemoryName } from "./consolidate.js";
57
57
  const RECOMBINE_SYSTEM_PROMPT = recombineSystemPrompt;
58
58
  const DEFAULT_MIN_CLUSTER_SIZE = 3;
59
59
  const DEFAULT_MAX_CLUSTERS_PER_RUN = 5;
60
- const DEFAULT_RELATEDNESS_SOURCE = "tags";
60
+ // #632 slots in each run's processed budget reserved for the top TAG clusters
61
+ // when entity clusters are present, so tag-only topics (a topic with a good tag
62
+ // but no extracted graph entity) are never fully starved by entity preference.
63
+ // Entities still take the rest of the budget; whichever kind is short, the other
64
+ // backfills. UNSET of entities (tags-only stash) ignores this and takes top-N tags.
65
+ const RESERVED_TAG_SLOTS = 3;
66
+ // #632 — a tag cluster larger than this is treated as an over-broad project
67
+ // mega-bucket (low-coherence, the bland signal #632 de-emphasizes), so the
68
+ // reserved tag slots prefer TIGHTER clusters at or below this size first.
69
+ const TAG_RESERVE_SOFT_CAP = 20;
70
+ // #632 — default to the UNION of tag + graph-entity relatedness, with entity
71
+ // clusters PREFERRED at selection time (see the rank in buildRelatednessClusters).
72
+ // Entity clustering surfaces coherent, subject-scoped clusters (a tool/subsystem)
73
+ // that the coarse stash-wide tag buckets miss, while tags still cover memories the
74
+ // graph has no entity for. The `entity:` vs `tag:` signature namespaces are
75
+ // independent, so a pure tag cluster's confirmation streak is only re-baselined
76
+ // when its OWN membership changes — which is exactly what the session-capture pool
77
+ // exclusion intends for the telemetry-polluted buckets (re-baselining a noisy
78
+ // cluster's streak is correct, not a regression). A stash with no extracted graph
79
+ // entities falls through to tag-only.
80
+ const DEFAULT_RELATEDNESS_SOURCE = "both";
61
81
  /** #625 — re-induction count required before a hypothesis promotes to a lesson. */
62
82
  const DEFAULT_CONFIRM_THRESHOLD = 2;
63
83
  /**
@@ -131,6 +151,76 @@ export function isJunkTag(tag) {
131
151
  return true; // short hex hashes: 002c624c, 192d
132
152
  return false;
133
153
  }
154
+ /**
155
+ * #632 — generic extraction-artefact entities the graph routinely emits: session
156
+ * bookkeeping (`session_id`, `session_checkpoint`), structured-log field names
157
+ * (`reason`, `harness`, `structured event log`), and the like. They are
158
+ * stash-wide and carry no topical signal, so an `entity:<norm>` cluster keyed on
159
+ * one is exactly the bland mega-bucket #632 aims to remove. Lowercased; matched
160
+ * against the already-normalised `entity_norm`.
161
+ */
162
+ const JUNK_ENTITY_NORMS = new Set([
163
+ "session",
164
+ "session_id",
165
+ "session_checkpoint",
166
+ "checkpoint",
167
+ "reason",
168
+ "harness",
169
+ "event",
170
+ "event log",
171
+ "structured event",
172
+ "structured event log",
173
+ "timestamp",
174
+ "metadata",
175
+ "status",
176
+ ]);
177
+ /**
178
+ * #632 — AKM session-capture telemetry memories: auto-generated session-end
179
+ * checkpoints named `<harness>-session-<YYYYMMDD>-<id>` or
180
+ * `<harness>-checkpoint-<YYYYMMDD…>-<id>`, carrying an embedded
181
+ * `akm_memory_kind: session_checkpoint` metadata block. Their bodies are
182
+ * pipeline bookkeeping, so the graph extracts their metadata FIELDS
183
+ * (`session_checkpoint`, `harness`, `buffered observations`, `tool_*_observed`,
184
+ * …) as ENTITIES — which then dominate entity clustering as bland, stash-wide
185
+ * mega-buckets (the #632 symptom, just under an `entity:` signature). They are
186
+ * session telemetry, not durable knowledge to generalize, so recombine excludes
187
+ * them from its pool. The `\d{8}` datestamp anchor is what distinguishes a
188
+ * capture name from a durable memory that merely MENTIONS session/checkpoint
189
+ * (e.g. `akm-plugins-session-end-extract-hook`, `session-checkpoint-lint-skips`),
190
+ * which stay in the pool.
191
+ */
192
+ export function isSessionCaptureMemoryName(name) {
193
+ return /-(session|checkpoint)-\d{8}/.test(name);
194
+ }
195
+ /**
196
+ * #632 — an entity carries no clustering signal (and must be skipped) when it is
197
+ * a generic extraction artefact (session / structured-log bookkeeping), a raw
198
+ * filesystem path (absolute paths the extractor lifts verbatim), or the same
199
+ * number / date / hash / version / stopword junk `isJunkTag` rejects. Mirrors
200
+ * `isJunkTag` so the graph relatedness source does not reintroduce the very
201
+ * bland buckets entity clustering is meant to replace. Unlike `excludeEntities`
202
+ * (a fixed user list), this catches the OPEN-ENDED junk without config upkeep.
203
+ */
204
+ export function isJunkEntity(entity) {
205
+ const e = entity.trim().toLowerCase();
206
+ if (e.length <= 1)
207
+ return true;
208
+ if (JUNK_ENTITY_NORMS.has(e))
209
+ return true;
210
+ if (JUNK_STOPWORD_TAGS.has(e))
211
+ return true;
212
+ if (e.includes("/") || e.includes("\\"))
213
+ return true; // raw file paths
214
+ if (/^\d+$/.test(e))
215
+ return true; // pure numbers + dates
216
+ if (/^v?\d+(?:\.\d+)+$/.test(e))
217
+ return true; // versions
218
+ if (/^v\d+$/.test(e))
219
+ return true; // v0, v2
220
+ if (/^[0-9a-f]{4,}$/.test(e) && /\d/.test(e))
221
+ return true; // short hex hashes
222
+ return false;
223
+ }
134
224
  /**
135
225
  * Build relatedness clusters from the memory pool. Clustering is driven purely
136
226
  * by shared tags / graph entities — it MUST NOT use embedding similarity, so
@@ -146,13 +236,19 @@ export function isJunkTag(tag) {
146
236
  * A cluster is a signal whose member set is >= `minClusterSize`. Overlapping
147
237
  * clusters are de-duplicated by member-set identity, and the result is RANKED
148
238
  * by member-count descending (deterministic alphabetical tiebreak). The
149
- * `maxClustersPerRun` cap is NOT applied here — call {@link capClusters} on the
150
- * result for the processed slice; the full ranked list is retained so the
151
- * cap-aware decay sweep can tell cap-displacement from corpus absence (#658).
239
+ * `maxClustersPerRun` cap is NOT applied here — call {@link selectClustersForRun}
240
+ * (entity-aware blend) or {@link capClusters} (tags-only) on the result for the
241
+ * processed slice; the full ranked list is retained so the cap-aware decay sweep
242
+ * can tell cap-displacement from corpus absence (#658).
152
243
  */
153
244
  export function buildRelatednessClusters(entries, opts) {
154
245
  // Only consolidation-eligible memories participate (exclude `.derived`).
155
- const memories = entries.filter((e) => e.entry.type === "memory" && isConsolidationEligibleMemoryName(e.entry.name));
246
+ // #632 durable memories only: exclude `.derived` (via
247
+ // `isConsolidationEligibleMemoryName`) AND session-capture telemetry dumps
248
+ // whose embedded metadata pollutes both tag and entity clustering.
249
+ const memories = entries.filter((e) => e.entry.type === "memory" &&
250
+ isConsolidationEligibleMemoryName(e.entry.name) &&
251
+ !isSessionCaptureMemoryName(e.entry.name));
156
252
  // signal -> member entries
157
253
  const groups = new Map();
158
254
  const add = (signal, entry) => {
@@ -173,9 +269,13 @@ export function buildRelatednessClusters(entries, opts) {
173
269
  const hasEntities = !!opts.entityByEntryId && opts.entityByEntryId.size > 0;
174
270
  const useGraph = (opts.relatednessSource === "graph" || opts.relatednessSource === "both") && hasEntities;
175
271
  const tagsFallback = !useTags && opts.relatednessSource === "graph" && !hasEntities;
176
- // #632 — tags excluded from tag-based clustering (applies regardless of
177
- // source). UNSET/[] leaves clustering byte-identical to the pre-#632 path.
272
+ // #632 — tags/entities excluded from clustering (applies regardless of
273
+ // source). UNSET/[] leaves tag clustering byte-identical to the pre-#632 path.
178
274
  const excludeTags = new Set(opts.excludeTags ?? []);
275
+ // `entity_norm` is always lowercased (graph-dedup.ts), so normalise the
276
+ // user-supplied exclusion list to match — `excludeEntities: ["OpenCode"]`
277
+ // should suppress the stored `opencode` entity (Reviewer A, #632).
278
+ const excludeEntities = new Set((opts.excludeEntities ?? []).map((e) => e.toLowerCase()));
179
279
  for (const entry of memories) {
180
280
  if (useTags || tagsFallback) {
181
281
  for (const tag of entry.entry.tags ?? []) {
@@ -187,8 +287,13 @@ export function buildRelatednessClusters(entries, opts) {
187
287
  }
188
288
  }
189
289
  if (useGraph && opts.entityByEntryId) {
190
- for (const ent of opts.entityByEntryId.get(entry.id) ?? [])
290
+ for (const ent of opts.entityByEntryId.get(entry.id) ?? []) {
291
+ if (excludeEntities.has(ent))
292
+ continue;
293
+ if (isJunkEntity(ent))
294
+ continue; // #632 — skip generic extraction-artefact / path entities
191
295
  add(`entity:${ent}`, entry);
296
+ }
192
297
  }
193
298
  }
194
299
  // Keep only groups at or above the minimum cluster size. #632 — when
@@ -216,12 +321,25 @@ export function buildRelatednessClusters(entries, opts) {
216
321
  seenMemberKeys.add(memberKey);
217
322
  return true;
218
323
  });
219
- // Rank largest-first (deterministic alphabetical tiebreak). The cap is applied
220
- // by the caller via {@link capClusters}, NOT here, so the FULL formed set
221
- // (every cluster that genuinely re-forms this run) stays available for the
222
- // cap-aware decay sweep — a cluster displaced by the cap must not be confused
223
- // with a cluster that vanished from the corpus (#658).
224
- clusters.sort((a, b) => b.members.length - a.members.length || a.signature.localeCompare(b.signature));
324
+ // #632 rank ENTITY clusters ahead of tag clusters, then largest-first within
325
+ // each kind (deterministic alphabetical tiebreak). A graph entity is an
326
+ // EXTRACTED SUBJECT (a tool / subsystem / component), so it is a far
327
+ // higher-signal cluster key than an auto-tokenized frontmatter tag, whose
328
+ // broadest buckets (`tag:<project>` e.g. every memory tagged `akm`) are the
329
+ // coarse, bland clusters #632 set out to kill. Largest-first ALONE let those
330
+ // tag mega-buckets fill the `maxClustersPerRun` slice every run and starve the
331
+ // coherent entity clusters this pass produces. Preferring entities keeps tag
332
+ // clustering as the fallback (a stash with no graph entities, or a topic with a
333
+ // tag but no extracted entity, still clusters) while ensuring the better signal
334
+ // wins the cap. The processed slice is chosen by the caller via
335
+ // {@link selectClustersForRun} (NOT here), so the FULL formed set stays
336
+ // available for the cap-aware decay sweep —
337
+ // a cluster displaced by the cap must not be confused with a cluster that
338
+ // vanished from the corpus (#658).
339
+ const entityRank = (sig) => (sig.startsWith("entity:") ? 0 : 1);
340
+ clusters.sort((a, b) => entityRank(a.signature) - entityRank(b.signature) ||
341
+ b.members.length - a.members.length ||
342
+ a.signature.localeCompare(b.signature));
225
343
  return clusters;
226
344
  }
227
345
  /**
@@ -235,6 +353,54 @@ export function buildRelatednessClusters(entries, opts) {
235
353
  export function capClusters(ranked, maxClustersPerRun) {
236
354
  return ranked.slice(0, Math.max(0, maxClustersPerRun));
237
355
  }
356
+ /**
357
+ * #632 — pick the per-run PROCESSED slice from the full ranked list, BLENDING
358
+ * entity and tag clusters so neither starves the other:
359
+ *
360
+ * - No entity clusters → top `maxClustersPerRun` TAG clusters (a tags-only
361
+ * stash is byte-identical to {@link capClusters}).
362
+ * - Entity clusters present → ENTITIES LEAD the budget (the higher-signal key —
363
+ * an extracted subject vs an auto-tokenized filename tag), but RESERVE up to
364
+ * `RESERVED_TAG_SLOTS` for tag clusters so tag-only topics still surface every
365
+ * run. Entities are never starved below one slot when present and the budget
366
+ * allows. Whichever kind is short, the other backfills, so the full budget is
367
+ * always used when enough clusters exist.
368
+ *
369
+ * The reserved tag slots prefer TIGHTER tag clusters (size <= `TAG_RESERVE_SOFT_CAP`,
370
+ * largest-first within that band, then the over-cap buckets): a broad
371
+ * auto-tokenized `tag:<project>` mega-bucket is exactly the bland, low-coherence
372
+ * signal #632 de-emphasizes, so the reserve should not spend its protected slots
373
+ * on the largest tags. Entities themselves stay largest-first — an entity is a
374
+ * coherent subject at any size.
375
+ *
376
+ * Entities lead the returned order. The FULL pre-cap `ranked` list (not this
377
+ * slice) still feeds the cap-aware decay sweep (#658), so a cluster left out of
378
+ * the processed slice this run is NOT decayed as if it vanished.
379
+ */
380
+ export function selectClustersForRun(ranked, maxClustersPerRun) {
381
+ const max = Math.max(0, maxClustersPerRun);
382
+ if (max === 0)
383
+ return [];
384
+ const entities = ranked.filter((c) => c.signature.startsWith("entity:"));
385
+ if (entities.length === 0)
386
+ return ranked.slice(0, max); // tags-only → top-N (capClusters parity)
387
+ const tags = ranked.filter((c) => !c.signature.startsWith("entity:"));
388
+ // Reserve up to RESERVED_TAG_SLOTS for tags, but never below one slot for the
389
+ // leading entities when the budget allows (so a small `maxClustersPerRun` does
390
+ // not silently invert the entity preference).
391
+ const reservedForTags = Math.min(tags.length, RESERVED_TAG_SLOTS, Math.max(0, max - 1));
392
+ const entityTake = Math.min(entities.length, max - reservedForTags);
393
+ const tagTake = Math.min(tags.length, max - entityTake); // tags take their reserve + any slot entities left
394
+ // Prefer tight tags (<= soft cap) over broad mega-buckets for the reserve.
395
+ const reserveTags = [...tags]
396
+ .sort((a, b) => {
397
+ const aOver = a.members.length > TAG_RESERVE_SOFT_CAP ? 1 : 0;
398
+ const bOver = b.members.length > TAG_RESERVE_SOFT_CAP ? 1 : 0;
399
+ return aOver - bOver || b.members.length - a.members.length || a.signature.localeCompare(b.signature);
400
+ })
401
+ .slice(0, tagTake);
402
+ return [...entities.slice(0, entityTake), ...reserveTags];
403
+ }
238
404
  // ── Prompt + ref derivation ───────────────────────────────────────────────────
239
405
  /** Read a memory body (frontmatter stripped) for the cluster prompt. */
240
406
  function readBody(entry) {
@@ -408,8 +574,9 @@ export async function akmRecombine(opts) {
408
574
  ...(entityByEntryId ? { entityByEntryId } : {}),
409
575
  ...(opts.maxClusterSize != null ? { maxClusterSize: opts.maxClusterSize } : {}),
410
576
  ...(opts.excludeTags ? { excludeTags: opts.excludeTags } : {}),
577
+ ...(opts.excludeEntities ? { excludeEntities: opts.excludeEntities } : {}),
411
578
  });
412
- const clusters = capClusters(rankedClusters, maxClustersPerRun);
579
+ const clusters = selectClustersForRun(rankedClusters, maxClustersPerRun);
413
580
  let clustersFormed = 0;
414
581
  let proposalsEmitted = 0;
415
582
  let lessonsPromoted = 0;
@@ -316,9 +316,13 @@ export const ImproveProcessConfigSchema = z
316
316
  // (generic project-wide tags). Default UNSET/[]. Only meaningful on
317
317
  // `recombine`.
318
318
  excludeTags: z.array(z.string().min(1)).optional(),
319
+ // #632 — recombine process: entity_norm values that must never form an
320
+ // entity cluster (user counterpart to the built-in generic-entity filter).
321
+ // Default UNSET/[]. Only meaningful on `recombine`.
322
+ excludeEntities: z.array(z.string().min(1)).optional(),
319
323
  // #609 — recombine process: relatedness signal used to form clusters
320
324
  // (tags | graph | both). Clustering is by relatedness, never embedding
321
- // similarity. Default "tags". Only meaningful on `recombine`.
325
+ // similarity. Default "both" (#632). Only meaningful on `recombine`.
322
326
  relatednessSource: z.enum(["tags", "graph", "both"]).optional(),
323
327
  // #609 — recombine process: consecutive re-inductions required before a
324
328
  // hypothesis is promoted to a lesson. Default 2. Only meaningful on
@@ -16083,6 +16083,7 @@ var init_config_schema = __esm(() => {
16083
16083
  maxClustersPerRun: positiveInt.optional(),
16084
16084
  maxClusterSize: positiveInt.optional(),
16085
16085
  excludeTags: exports_external.array(exports_external.string().min(1)).optional(),
16086
+ excludeEntities: exports_external.array(exports_external.string().min(1)).optional(),
16086
16087
  relatednessSource: exports_external.enum(["tags", "graph", "both"]).optional(),
16087
16088
  confirmThreshold: exports_external.number().int().min(1).optional(),
16088
16089
  minRecurrence: exports_external.number().int().min(2).optional(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akm-cli",
3
- "version": "0.9.0-beta.42",
3
+ "version": "0.9.0-beta.44",
4
4
  "type": "module",
5
5
  "description": "akm (Agent Knowledge Management) — A package manager for AI agent skills, commands, tools, and knowledge. Works with Claude Code, OpenCode, Cursor, and any AI coding assistant.",
6
6
  "keywords": [