feat(skill-memory): per-skill cross-session recall + historian auto-extraction#181
feat(skill-memory): per-skill cross-session recall + historian auto-extraction#181iceteaSA wants to merge 16 commits into
Conversation
dc83db6 to
4034019
Compare
There was a problem hiding this comment.
11 issues found across 78 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/plugin/src/features/magic-context/dreamer/task-executor.ts">
<violation number="1" location="packages/plugin/src/features/magic-context/dreamer/task-executor.ts:419">
P2: Re-embed pre-step errors are suppressed, allowing successful task completion reporting despite a failed prerequisite data maintenance step.</violation>
</file>
Note: This PR contains a large number of files. cubic only reviews up to 40 files per PR, so some files may not have been reviewed. cubic prioritizes the most important files to review.
On a pro plan you can use ultrareview for larger PRs.
Re-trigger cubic
There was a problem hiding this comment.
4 issues found across 15 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/plugin/src/features/magic-context/dreamer/task-executor.ts">
<violation number="1" location="packages/plugin/src/features/magic-context/dreamer/task-executor.ts:419">
P2: Re-embed pre-step errors are suppressed, allowing successful task completion reporting despite a failed prerequisite data maintenance step.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
…routing/parser gaps) Council review (deepseek/sonnet/gpt-5.5) of PR cortexkit#181: - Must (consensus rev-2+rev-3): the ctx_skill_note fail-loud guard threw during plugin init when the plugin is disabled (enabled:false OR conflict-disabled) — createSessionHooks returns {magicContext:null} by design, so the unconditional guard crashed the entry module on the disabled path. Gate it on pluginConfig.enabled; pass a throwaway Map to createToolRegistry (which early-returns {} when disabled and never reads it). - Must (rev-3): singular ~/.config/opencode/skill/ global path was misclassified as project tier (opencode's pattern is {skill,skills}/**/SKILL.md) — fixed in deriveSkillTier/deriveSkillSource + the ctx_skill_recall cold-start search list. - Must (rev-3): the frontmatter parser rejected the inline flow-mapping form 'skill-memory: { enabled: true }' — the EXACT form the ctx_skill_recall remediation message and ARCHITECTURE/CONFIGURATION/README advertise. Added inline-mapping parsing so guidance and parser agree. - Should (consensus rev-1+rev-3): recallSkillMemoryBlock swallowed all errors silently — added a log() so FTS/blob corruption is diagnosable (still no-throw). Regression tests: inline frontmatter form (3 cases), singular skill/ global path (2 cases).
…routing/parser gaps) Council review (deepseek/sonnet/gpt-5.5) of PR cortexkit#181: - Must (consensus rev-2+rev-3): the ctx_skill_note fail-loud guard threw during plugin init when the plugin is disabled (enabled:false OR conflict-disabled) — createSessionHooks returns {magicContext:null} by design, so the unconditional guard crashed the entry module on the disabled path. Gate it on pluginConfig.enabled; pass a throwaway Map to createToolRegistry (which early-returns {} when disabled and never reads it). - Must (rev-3): singular ~/.config/opencode/skill/ global path was misclassified as project tier (opencode's pattern is {skill,skills}/**/SKILL.md) — fixed in deriveSkillTier/deriveSkillSource + the ctx_skill_recall cold-start search list. - Must (rev-3): the frontmatter parser rejected the inline flow-mapping form 'skill-memory: { enabled: true }' — the EXACT form the ctx_skill_recall remediation message and ARCHITECTURE/CONFIGURATION/README advertise. Added inline-mapping parsing so guidance and parser agree. - Should (consensus rev-1+rev-3): recallSkillMemoryBlock swallowed all errors silently — added a log() so FTS/blob corruption is diagnosable (still no-throw). Regression tests: inline frontmatter form (3 cases), singular skill/ global path (2 cases).
8f87530 to
00f22d2
Compare
…routing/parser gaps) Council review (deepseek/sonnet/gpt-5.5) of PR cortexkit#181: - Must (consensus rev-2+rev-3): the ctx_skill_note fail-loud guard threw during plugin init when the plugin is disabled (enabled:false OR conflict-disabled) — createSessionHooks returns {magicContext:null} by design, so the unconditional guard crashed the entry module on the disabled path. Gate it on pluginConfig.enabled; pass a throwaway Map to createToolRegistry (which early-returns {} when disabled and never reads it). - Must (rev-3): singular ~/.config/opencode/skill/ global path was misclassified as project tier (opencode's pattern is {skill,skills}/**/SKILL.md) — fixed in deriveSkillTier/deriveSkillSource + the ctx_skill_recall cold-start search list. - Must (rev-3): the frontmatter parser rejected the inline flow-mapping form 'skill-memory: { enabled: true }' — the EXACT form the ctx_skill_recall remediation message and ARCHITECTURE/CONFIGURATION/README advertise. Added inline-mapping parsing so guidance and parser agree. - Should (consensus rev-1+rev-3): recallSkillMemoryBlock swallowed all errors silently — added a log() so FTS/blob corruption is diagnosable (still no-throw). Regression tests: inline frontmatter form (3 cases), singular skill/ global path (2 cases).
00f22d2 to
8db1b8d
Compare
There was a problem hiding this comment.
4 issues found across 78 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/plugin/src/features/magic-context/dreamer/task-executor.ts">
<violation number="1" location="packages/plugin/src/features/magic-context/dreamer/task-executor.ts:419">
P2: Re-embed pre-step errors are suppressed, allowing successful task completion reporting despite a failed prerequisite data maintenance step.</violation>
</file>
Note: This PR contains a large number of files. cubic only reviews up to 40 files per PR, so some files may not have been reviewed. cubic prioritizes the most important files to review.
On a pro plan you can use ultrareview for larger PRs.
Re-trigger cubic
…routing/parser gaps) Council review (deepseek/sonnet/gpt-5.5) of PR cortexkit#181: - Must (consensus rev-2+rev-3): the ctx_skill_note fail-loud guard threw during plugin init when the plugin is disabled (enabled:false OR conflict-disabled) — createSessionHooks returns {magicContext:null} by design, so the unconditional guard crashed the entry module on the disabled path. Gate it on pluginConfig.enabled; pass a throwaway Map to createToolRegistry (which early-returns {} when disabled and never reads it). - Must (rev-3): singular ~/.config/opencode/skill/ global path was misclassified as project tier (opencode's pattern is {skill,skills}/**/SKILL.md) — fixed in deriveSkillTier/deriveSkillSource + the ctx_skill_recall cold-start search list. - Must (rev-3): the frontmatter parser rejected the inline flow-mapping form 'skill-memory: { enabled: true }' — the EXACT form the ctx_skill_recall remediation message and ARCHITECTURE/CONFIGURATION/README advertise. Added inline-mapping parsing so guidance and parser agree. - Should (consensus rev-1+rev-3): recallSkillMemoryBlock swallowed all errors silently — added a log() so FTS/blob corruption is diagnosable (still no-throw). Regression tests: inline frontmatter form (3 cases), singular skill/ global path (2 cases).
041126d to
ef5cba1
Compare
…routing/parser gaps) Council review (deepseek/sonnet/gpt-5.5) of PR cortexkit#181: - Must (consensus rev-2+rev-3): the ctx_skill_note fail-loud guard threw during plugin init when the plugin is disabled (enabled:false OR conflict-disabled) — createSessionHooks returns {magicContext:null} by design, so the unconditional guard crashed the entry module on the disabled path. Gate it on pluginConfig.enabled; pass a throwaway Map to createToolRegistry (which early-returns {} when disabled and never reads it). - Must (rev-3): singular ~/.config/opencode/skill/ global path was misclassified as project tier (opencode's pattern is {skill,skills}/**/SKILL.md) — fixed in deriveSkillTier/deriveSkillSource + the ctx_skill_recall cold-start search list. - Must (rev-3): the frontmatter parser rejected the inline flow-mapping form 'skill-memory: { enabled: true }' — the EXACT form the ctx_skill_recall remediation message and ARCHITECTURE/CONFIGURATION/README advertise. Added inline-mapping parsing so guidance and parser agree. - Should (consensus rev-1+rev-3): recallSkillMemoryBlock swallowed all errors silently — added a log() so FTS/blob corruption is diagnosable (still no-throw). Regression tests: inline frontmatter form (3 cases), singular skill/ global path (2 cases).
9c62039 to
3e28417
Compare
…routing/parser gaps) Council review (deepseek/sonnet/gpt-5.5) of PR cortexkit#181: - Must (consensus rev-2+rev-3): the ctx_skill_note fail-loud guard threw during plugin init when the plugin is disabled (enabled:false OR conflict-disabled) — createSessionHooks returns {magicContext:null} by design, so the unconditional guard crashed the entry module on the disabled path. Gate it on pluginConfig.enabled; pass a throwaway Map to createToolRegistry (which early-returns {} when disabled and never reads it). - Must (rev-3): singular ~/.config/opencode/skill/ global path was misclassified as project tier (opencode's pattern is {skill,skills}/**/SKILL.md) — fixed in deriveSkillTier/deriveSkillSource + the ctx_skill_recall cold-start search list. - Must (rev-3): the frontmatter parser rejected the inline flow-mapping form 'skill-memory: { enabled: true }' — the EXACT form the ctx_skill_recall remediation message and ARCHITECTURE/CONFIGURATION/README advertise. Added inline-mapping parsing so guidance and parser agree. - Should (consensus rev-1+rev-3): recallSkillMemoryBlock swallowed all errors silently — added a log() so FTS/blob corruption is diagnosable (still no-throw). Regression tests: inline frontmatter form (3 cases), singular skill/ global path (2 cases).
…d in frontmatter Counterpart to ctx_skill_recall's enabled-guard (greptile review, PR cortexkit#181): without it, notes for skills that never opted in inserted successfully but were permanently orphaned — recallSkillMemoryBlock returns "" when frontmatter is disabled, while the agent saw a convincing 'Skill note saved' response. Now returns an actionable error before any insert. Red-checked: new regression test fails without the guard (orphan row inserted + 'saved' response), passes with it (no row, 'not enabled').
…d in frontmatter Counterpart to ctx_skill_recall's enabled-guard (greptile review, PR cortexkit#181): without it, notes for skills that never opted in inserted successfully but were permanently orphaned — recallSkillMemoryBlock returns "" when frontmatter is disabled, while the agent saw a convincing 'Skill note saved' response. Now returns an actionable error before any insert. Red-checked: new regression test fails without the guard (orphan row inserted + 'saved' response), passes with it (no row, 'not enabled').
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
…routing/parser gaps) Council review (deepseek/sonnet/gpt-5.5) of PR cortexkit#181: - Must (consensus rev-2+rev-3): the ctx_skill_note fail-loud guard threw during plugin init when the plugin is disabled (enabled:false OR conflict-disabled) — createSessionHooks returns {magicContext:null} by design, so the unconditional guard crashed the entry module on the disabled path. Gate it on pluginConfig.enabled; pass a throwaway Map to createToolRegistry (which early-returns {} when disabled and never reads it). - Must (rev-3): singular ~/.config/opencode/skill/ global path was misclassified as project tier (opencode's pattern is {skill,skills}/**/SKILL.md) — fixed in deriveSkillTier/deriveSkillSource + the ctx_skill_recall cold-start search list. - Must (rev-3): the frontmatter parser rejected the inline flow-mapping form 'skill-memory: { enabled: true }' — the EXACT form the ctx_skill_recall remediation message and ARCHITECTURE/CONFIGURATION/README advertise. Added inline-mapping parsing so guidance and parser agree. - Should (consensus rev-1+rev-3): recallSkillMemoryBlock swallowed all errors silently — added a log() so FTS/blob corruption is diagnosable (still no-throw). Regression tests: inline frontmatter form (3 cases), singular skill/ global path (2 cases).
…d in frontmatter Counterpart to ctx_skill_recall's enabled-guard (greptile review, PR cortexkit#181): without it, notes for skills that never opted in inserted successfully but were permanently orphaned — recallSkillMemoryBlock returns "" when frontmatter is disabled, while the agent saw a convincing 'Skill note saved' response. Now returns an actionable error before any insert. Red-checked: new regression test fails without the guard (orphan row inserted + 'saved' response), passes with it (no row, 'not enabled').
…routing/parser gaps) Council review (deepseek/sonnet/gpt-5.5) of PR cortexkit#181: - Must (consensus rev-2+rev-3): the ctx_skill_note fail-loud guard threw during plugin init when the plugin is disabled (enabled:false OR conflict-disabled) — createSessionHooks returns {magicContext:null} by design, so the unconditional guard crashed the entry module on the disabled path. Gate it on pluginConfig.enabled; pass a throwaway Map to createToolRegistry (which early-returns {} when disabled and never reads it). - Must (rev-3): singular ~/.config/opencode/skill/ global path was misclassified as project tier (opencode's pattern is {skill,skills}/**/SKILL.md) — fixed in deriveSkillTier/deriveSkillSource + the ctx_skill_recall cold-start search list. - Must (rev-3): the frontmatter parser rejected the inline flow-mapping form 'skill-memory: { enabled: true }' — the EXACT form the ctx_skill_recall remediation message and ARCHITECTURE/CONFIGURATION/README advertise. Added inline-mapping parsing so guidance and parser agree. - Should (consensus rev-1+rev-3): recallSkillMemoryBlock swallowed all errors silently — added a log() so FTS/blob corruption is diagnosable (still no-throw). Regression tests: inline frontmatter form (3 cases), singular skill/ global path (2 cases).
…d in frontmatter Counterpart to ctx_skill_recall's enabled-guard (greptile review, PR cortexkit#181): without it, notes for skills that never opted in inserted successfully but were permanently orphaned — recallSkillMemoryBlock returns "" when frontmatter is disabled, while the agent saw a convincing 'Skill note saved' response. Now returns an actionable error before any insert. Red-checked: new regression test fails without the guard (orphan row inserted + 'saved' response), passes with it (no row, 'not enabled').
…ath provenance case cubic P2 (PR cortexkit#181): the 'PLAIN filesystem path for a PROJECT skill' test asserted tier/skillSource but not resolvedPath — the only plain-path project-skill test, so a path-join regression (missing /SKILL.md suffix, bad concat) would go undetected. Added the resolvedPath assertion to match the coverage pattern of every sibling test.
…ath provenance case cubic P2 (PR cortexkit#181): the 'PLAIN filesystem path for a PROJECT skill' test asserted tier/skillSource but not resolvedPath — the only plain-path project-skill test, so a path-join regression (missing /SKILL.md suffix, bad concat) would go undetected. Added the resolvedPath assertion to match the coverage pattern of every sibling test.
Per-skill "motor memory": when a skill's SKILL.md declares
`skill-memory: { enabled: true }`, accumulated gotchas/discoveries/fixes/
workflow-steps surface in a <skill-memory> block appended to the skill
tool's RESULT on every load (cache-safe — rides the tool-result tail).
Agents write back via ctx_skill_note; ctx_skill_recall is the explicit
companion to the transparent after-hook.
- migration: skill_memory table (per-skill; tier project/global; UNIQUE on
skill_id/tier/project_identity/normalized_hash) + lookup indexes.
- three-hook augmentation: tool.definition advertises an `intent` param;
tool.execute.before stashes intent (bounded TTL); after-hook parses the
skill's Base directory, reads SKILL.md frontmatter, formats the block.
- flat recency×hit recall + storage layer; ctx_skill_note / ctx_skill_recall.
- opt-in distill-skill-memory dreamer task; agent-prompt guidance; TUI/ctx-status stats.
- docs: ARCHITECTURE / STRUCTURE / CONFIGURATION / README.
Upgrade recall from flat recency×hit to a multi-rung cascade: intent + model-matched embeddings → cosine blend across intent_embedding + delta_embedding (relevance/recency/hit weights tunable per skill via ranking_* frontmatter); intent + no model match → FTS5 fallback over the content-linked skill_memory_fts vtable; empty → flat fallback. - migration: delta_embedding + recall_count columns + skill_memory_fts FTS5 vtable. - embed-on-write in insertSkillMemoryNote; delta-only semantic dedup. - programmatic, no-LLM reembed pre-step for the distill-skill-memory dreamer task. - read-side recall_count (distinct from write-side hit_count). - canonical vector serde + dedup/ranking/FTS query helpers.
…ication (P3a) Foundation for the historian to auto-capture skill notes cross-project. - surface the skill name in the historian chunk as a `TC: skill(<name>)` marker (the keystone — the tool input name was previously dropped). - migration: origin_project + source_type columns; unify global-tier notes under project_identity='*' (collision-merge) so a global note is one row recallable from any repo. - partitionKey helper routes global write/recall/reembed/stats through '*'; recall reads global-tier from '*' (cross-project); reembed sweeps '*'.
Close the loop so the historian writes skill notes during compaction without an agent volunteering ctx_skill_note. - historian prompt emits a <skill_observations> block; parser extracts it; threaded through the validated historian result. - both runners (OpenCode + Pi) promote skill observations post-commit via the shared promoteSkillObservations helper, gated by promotionActive && !discardedLast, writing global '*' notes with source_type='historian'. - self-heal net: initializeDatabase re-creates skill_memory + ensureColumn so an upgraded DB recovers even if a migration row is lost.
- Remove committed <<<<<<< HEAD conflict marker in CONFIGURATION.md (P1)
- Move injectSkillIntentParam before the lastChatContext guard so the intent
param is advertised even on tool.definition flights before first chat.message
- Key intentByCallId by sessionID:callID + prefix-prune on session delete so a
concurrent session's delete can't evict another session's in-flight intents
- Log silent catch in promoteSkillObservations (observability for dropped writes)
- Anchor frontmatter regex to start-of-file (drop m flag) so a later --- rule
can't be misparsed; strip inline # comments from unquoted YAML scalars + block header
- Scope distill report SQL to ('<identity>','*') instead of non-deterministic LIMIT 1
- Don't truncate skill name in TC: skill(<name>) marker (identity key); sanitize
newlines/control chars
- Normalize backslash->slash after fileURLToPath for Windows provenance checks
- FTS self-heal rebuild in initializeDatabase when skill_memory_fts is empty but
skill_memory has rows
- Move ctx_skill_recall _test* DI fields to a separate test-only deps type
- Hoist the shared registryKey dynamic import (one import, both blocks)
Pushback: reembed pre-step errors are already logged (task-executor.ts) — the
non-blocking try/catch is by design (failure leaves notes on the FTS rung).
…2/P3) - P1: recall now unions the skill's own partition with the global '*' partition (recallPartitionPredicate helper) so a PROJECT-LOCAL skill surfaces historian-written global notes — previously orphaned (tier='project' query never matched tier='global'/'*'). Write/dedup paths stay exact-partition. - Escape apostrophes in projectPath before SQL string interpolation in the distill prompt template. - Frontmatter regex tolerates a leading UTF-8 BOM / whitespace (still start-anchored). - TC: skill(<name>) marker emits the name VERBATIM when marker-safe, else drops it — never mutates the identity key (recall keys on raw input.name). - Fix misleading frontmatter test: now actually exercises a '#' inside a quoted scalar (preserved) vs unquoted (comment-stripped). Regression tests: project-local skill recalls a global historian note; agent project note + historian global note both surface for the same skill.
…routing/parser gaps) Council review (deepseek/sonnet/gpt-5.5) of PR cortexkit#181: - Must (consensus rev-2+rev-3): the ctx_skill_note fail-loud guard threw during plugin init when the plugin is disabled (enabled:false OR conflict-disabled) — createSessionHooks returns {magicContext:null} by design, so the unconditional guard crashed the entry module on the disabled path. Gate it on pluginConfig.enabled; pass a throwaway Map to createToolRegistry (which early-returns {} when disabled and never reads it). - Must (rev-3): singular ~/.config/opencode/skill/ global path was misclassified as project tier (opencode's pattern is {skill,skills}/**/SKILL.md) — fixed in deriveSkillTier/deriveSkillSource + the ctx_skill_recall cold-start search list. - Must (rev-3): the frontmatter parser rejected the inline flow-mapping form 'skill-memory: { enabled: true }' — the EXACT form the ctx_skill_recall remediation message and ARCHITECTURE/CONFIGURATION/README advertise. Added inline-mapping parsing so guidance and parser agree. - Should (consensus rev-1+rev-3): recallSkillMemoryBlock swallowed all errors silently — added a log() so FTS/blob corruption is diagnosable (still no-throw). Regression tests: inline frontmatter form (3 cases), singular skill/ global path (2 cases).
- budgetFill now counts per-note XML framing (~20 tokens) so the rendered
<skill-memory> block stays within max_tokens instead of ~13% overshoot (rev-1).
- clamp effective pinned budget to min(max_pinned_tokens, max_tokens) so the
default 4000>1500 can't imply pinned gets more room than the whole block (rev-2).
- ctx_skill_recall: derive tier via dirname(resolvedPath) instead of a fragile
.replace('/SKILL.md','') (rev-2).
Updated the budget-truncation test for the framing-inclusive math.
- provenance.ts: anchor the Base-directory regex to line-start (^…/gm) and take the LAST match — opencode appends the provenance line at the END of tool output, so a skill whose CONTENT echoes 'Base directory for this skill:' (e.g. a skill documenting skill-memory) would otherwise shadow the real line and misdirect recall to a bogus identity. - read-session-formatting.ts: narrow the marker-safe exclusion to CR/LF/tab only — a ')' does not break the single-line TC: skill(<name>) marker and the historian reads it as natural language, so a ')'-containing name is preserved verbatim (identity key) instead of dropped. - ARCHITECTURE.md: update the 'Skill-memory (motor memory)' Key Abstraction to the shipped reality (v50/51/52, multi-rung embedding+FTS recall, global-'*' union) — was stale (v37, 'P2 TODO'). Remove the PR-added duplicate 'Tag Identity (v3.3.1+)' section (upstream owns the lean '## Tag identity'; Tag Identity is unrelated to skill-memory — rebase scope-creep). Regression tests: provenance last-match + mid-line rejection; ')' name preserved + CR/LF/tab still dropped.
…emory off Rebase-onto-v0.29.0 resolution completion. Upstream ab4f01c added a memory.enabled gate that drops ALL ctx_memory mentions from the system prompt when memory is off (ctx_memory is then unregistered). The skill-memory guidance carried a 'those belong in ctx_memory' cross-reference that violated the new contract (buildMagicContextSection memory-gating tests). Parameterized ctxSkillMemoryGuidance(memoryEnabled) so the cross-ref drops when memory is off; skill-memory itself stays ungated (independent store).
…d in frontmatter Counterpart to ctx_skill_recall's enabled-guard (greptile review, PR cortexkit#181): without it, notes for skills that never opted in inserted successfully but were permanently orphaned — recallSkillMemoryBlock returns "" when frontmatter is disabled, while the agent saw a convincing 'Skill note saved' response. Now returns an actionable error before any insert. Red-checked: new regression test fails without the guard (orphan row inserted + 'saved' response), passes with it (no row, 'not enabled').
…opencode #33580) opencode's skill tool changed the 'Base directory for this skill:' line from a file:// URL to a plain filesystem path (upstream #33580). Our parser hard-required file:/// so parseSkillProvenance returned null on current opencode -> skill-load registry never populated -> every agent ctx_skill_note failed with a provenance parse error. Agent-written notes silently stopped 2026-07-02 (only historian-path notes, which bypass this parser, continued). Widen BASE_DIR_REGEX to capture the rest of the line and branch on the value: file:// -> fileURLToPath (legacy/back-compat); otherwise treat as a plain path. Keeps the line-anchor + last-match decoy-rejection invariant. +4 regression tests (plain global, plain project, plain decoy last-match, plain mid-line-ignore); all existing file:// tests unchanged.
… v50 collision Upstream v0.31.0 added its own migration v50 (ctx-wrapup durable marker), colliding with skill-memory's v50/51/52. Renumbered skill migrations to v51 (skill_memory table) / v52 (embeddings+FTS) / v53 (historian extraction), bumped LATEST_SUPPORTED_VERSION to 53, and rotated the migration test files (v42/v51/v52 -> v51/v52/v53) with corrected internal version refs + fence assertions.
…ath provenance case cubic P2 (PR cortexkit#181): the 'PLAIN filesystem path for a PROJECT skill' test asserted tier/skillSource but not resolvedPath — the only plain-path project-skill test, so a path-join regression (missing /SKILL.md suffix, bad concat) would go undetected. Added the resolvedPath assertion to match the coverage pattern of every sibling test.
- Regenerate src/tui-compiled/index.tsx via the new v0.31 build:tui step so the compiled TUI carries the skill-memory render block (source src/tui had it; the committed artifact was stale — 0 refs → 2). Satisfies the check:tui-compiled CI gate. - ARCHITECTURE/STRUCTURE: dreamer maintain-docs refresh — migration list v1–v53 (v50 wrapup marker, skill v51/52/53), plain-path provenance (#33580), and the ctx_skill_note enabled-guard. mc:protected untouched.
…(use provider-factory seam) Bun mock.module is process-global and mock.restore() cannot undo it cross-file in Bun 1.3.14. The skill-memory test files (reembed, recall, ctx-skill-note) and promotion.test.ts each globally mocked the embedding barrel, which bled into ctx-memory's provider-coordination tests (5s timeouts) and into each other under CI worker sharding. Converted all four files to the non-global seam that ctx-memory's own tests use: _setTestProviderFactoryForProject + registerProjectEmbedding with mandatory afterEach reset. Zero mock.module calls for any embedding barrel remain in the test suite.
There was a problem hiding this comment.
1 issue found across 4 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/plugin/src/features/magic-context/skill-memory/reembed.test.ts">
<violation number="1" location="packages/plugin/src/features/magic-context/skill-memory/reembed.test.ts:53">
P3: Redundant `_setTestProviderFactoryForProject(null)` call before `installReembedTestProvider()` -- the `installReembedTestProvider()` lambda immediately overwrites the factory anyway. The `_resetProjectEmbeddingRegistryForTests()` already clears the factory to null, so this extra call is a no-op. Looks like a fragment from an earlier refactor; consider dropping the middle call to keep the setup intent clear.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| describe("reembed", () => { | ||
| test("reembedStaleSkillNotes fills NULL embeddings (bounded, idempotent)", async () => { | ||
| _resetProjectEmbeddingRegistryForTests(); | ||
| _setTestProviderFactoryForProject(null); |
There was a problem hiding this comment.
P3: Redundant _setTestProviderFactoryForProject(null) call before installReembedTestProvider() -- the installReembedTestProvider() lambda immediately overwrites the factory anyway. The _resetProjectEmbeddingRegistryForTests() already clears the factory to null, so this extra call is a no-op. Looks like a fragment from an earlier refactor; consider dropping the middle call to keep the setup intent clear.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/plugin/src/features/magic-context/skill-memory/reembed.test.ts, line 53:
<comment>Redundant `_setTestProviderFactoryForProject(null)` call before `installReembedTestProvider()` -- the `installReembedTestProvider()` lambda immediately overwrites the factory anyway. The `_resetProjectEmbeddingRegistryForTests()` already clears the factory to null, so this extra call is a no-op. Looks like a fragment from an earlier refactor; consider dropping the middle call to keep the setup intent clear.</comment>
<file context>
@@ -1,71 +1,116 @@
+describe("reembed", () => {
+ test("reembedStaleSkillNotes fills NULL embeddings (bounded, idempotent)", async () => {
+ _resetProjectEmbeddingRegistryForTests();
+ _setTestProviderFactoryForProject(null);
+ installReembedTestProvider();
+
</file context>
Summary
Adds skill-memory — per-skill "motor memory" that gives a skill cross-session recall of its own hard-won lessons (gotchas, discoveries, fixes, workflow steps). When a skill's
SKILL.mddeclaresskill-memory: { enabled: true }, accumulated notes for that skill surface automatically in a<skill-memory>block appended to the skill tool's result on every load — and, with the historian extension, the historian writes those notes automatically during compaction (no agent action required).It's fully opt-in per skill and cache-safe by construction: the block rides the tool-result tail (conversation), never the cached system/m[0] prefix, so it can't bust the prompt cache.
How it works
tool.definitionadvertises anintentparam;tool.execute.beforestashes the intent (bounded TTL); the after-hook parses the skill'sBase directory, reads itsSKILL.mdfrontmatter, and formats the recall block. Lands in the tool RESULT (cache-safe).ctx_skill_note(agent-authored) and the historian (auto-extracted). Both dedup on a normalized hash.intent_embedding+delta_embedding(relevance/recency/hit weights tunable per skill viaranking_*frontmatter); FTS5 fallback over a content-linkedskill_memory_ftsvtable; flat recency×hit fallback.TC: skill(<name>)markers in its chunk and emits a<skill_observations>block; both the OpenCode and Pi runners promote those post-commit as global notes (project_identity='*',source_type='historian'), recallable from any project.Review units (4 commits)
The branch is organized into four coherent, reviewable phases:
skill_memorytable migration, the three-hook augmentation, flat recall + storage,ctx_skill_note/ctx_skill_recalltools, opt-indistill-skill-memorydreamer task, TUI/ctx-statusstats, docs.delta_embedding+recall_countcolumns +skill_memory_ftsFTS5 vtable; embed-on-write; the cosine/FTS recall cascade; a programmatic (no-LLM) reembed pre-step.TC: skill(<name>)marker keystone;origin_project+source_typecolumns; global-tier notes unified underproject_identity='*'(collision-merge);partitionKeyrouting.<skill_observations>, parser, validated-result threading, both runners promote via the sharedpromoteSkillObservationshelper, plus aninitializeDatabaseself-heal net (re-createsskill_memory+ensureColumnso an upgraded DB recovers even if a migration row is lost).Schema / migrations
Three migrations (numbered after the current
masterceiling):skill_memorytable, thendelta_embedding/recall_count/FTS, thenorigin_project/source_type/*unification.LATEST_SUPPORTED_VERSIONbumped in lockstep (theschema-version-fencetest enforces it). Migration bodies areensureColumn/IF NOT EXISTSidempotent.Testing
Full plugin + Pi suites pass (one unrelated pre-existing full-suite ordering flake in
tui-config.test.tsthat passes in isolation),tscclean both packages, lint clean, build produces all bundles. Dedicated coverage for the migrations (coexistence + fence), recall rungs, the tools, FTS triggers/backfill, the'*'collision-merge, and the historian promotion path on both runners. Verified working live: the historian auto-extraction writes genuinesource_type='historian'notes, embeddings populate, and the read-siderecall_countincrements on surfacing.Notes for reviewers
Need help on this PR? Tag
/codesmithwith what you need. Autofix is disabled.Summary by cubic
Adds per-skill cross-session memory with embedding + FTS ranking and historian auto-extraction. Each
skillresult now includes a<skill-memory>block;ctx_skill_note/ctx_skill_recallmanage notes, and thedistill-skill-memorytask pre-embeds and reports corpus health (TUI/ctx-statusshow stats).New Features
skilltool accepts optionalintent; before/after hooks stash intent and inject recall; enable viaSKILL.mdfrontmatter (skill-memory: { enabled: true }). Status/TUI/ctx-statusshow per-project totals and pinned counts.intent_embedding+delta_embeddingwith FTS5 fallback;recall_counttracked; a pre-step re-embeds NULL/stale vectors before the distill run.TC: skill(<name>)markers →<skill_observations>; OpenCode and Pi promote viapromoteSkillObservationsas global'*'notes (source_type='historian').distill-skill-memory(off by default) runs the re-embed pre-step and emits a read-only corpus health report.skill_memory+ FTS andorigin_project/source_type;initializeDatabasecan rebuildskill_memory_fts; global'*'partition helpers and project-scoped stats added.Bug Fixes
'*'so project-local skills surface historian notes; writes/dedup stay exact-partition.ctx_skill_notewhen disabled; accept inline{ enabled: true }; start-of-file anchored with BOM/whitespace tolerance and inline#handling; accept plain paths andfile://; normalize Windows paths; correct global vs project tiering; keep the last, line-anchored “Base directory” match.ctx_memorycross-ref when memory is off; log recall errors; count per-note XML framing and clamp pinned budget.sessionId:callId); escape apostrophes in SQL; compiled TUI regenerated to match source so the Skill Memory section renders.mock.modulewith the provider-factory seam (_setTestProviderFactoryForProject+registerProjectEmbedding) to stop cross-file bleed and timeouts; stabilize embedding-related tests.Written for commit 2000076. Summary will update on new commits.
Greptile Summary
This PR introduces skill-memory — opt-in per-skill cross-session recall that appends a
<skill-memory>block to eachskilltool result, backed by SQLite with embedding + FTS5 ranking and historian auto-extraction. All previously-flagged blocking issues have been addressed in follow-up commits.tool.definitioninjectsintentparam;tool.execute.beforestashes it;tool.execute.afterreads SKILL.md frontmatter from disk and appends the<skill-memory>block) lands in the tool result — cache-safe by construction.intent_embedding+delta_embedding(rung 1) → FTS5 BM25 fallback (rung 3) → flat recency×hit (rungs 2/4); pinned notes always surface first within the token budget;recall_counttracked separately from write-sidehit_count.TC: skill(<name>)markers →<skill_observations>output →promoteSkillObservationswrites global-tier'*'partition notes via both OpenCode and Pi runners; three new migrations (v51–v53) add the table, FTS vtable, andorigin_project/source_typecolumns with idempotentensureColumnguards.Confidence Score: 5/5
Safe to merge. All previously-identified bugs have been corrected in follow-up commits and the new code is well-guarded throughout.
All previously-identified bugs have been corrected: session-scoped intent keying, the redundant dynamic import hoist, the FTS rebuild self-heal gap, the project-local skill historian orphan, and the disabled-skill orphan write. The remaining findings are two style-level naming/duplication nits that carry no runtime risk. Migrations are idempotent, the recall cascade degrades gracefully at every rung, and the historian promotion path is gated correctly behind
promotionActive && !discardedLast.No files require special attention.
storage.tsandhook-handlers.tshave minor style suggestions but no blocking issues.Important Files Changed
searchSkillMemoryFtscallsrecallPartitionPredicatetwice unnecessarily (P2 suggestion posted).budgetFillcorrectness holds throughout; errors are now logged vialog(), addressing the previously-flagged bare-catch concern.stashIntentparameter namedcallIdbut always receives a composite key (P2 suggestion posted).intentByCallId,skillLoadRegistry, andsessionDirectoryBySession; registerstool.execute.before; prunes both maps inonSessionDeletedby session prefix. Session cleanup logic is correct.skill_memorytable, FTS5 + embeddings columns, andorigin_project/source_typewith global-*unification. Idempotent throughout; v53 collision-merge runs inside a transaction.skill_memorytable + FTS triggers toinitializeDatabase; guarded FTS rebuild self-heal addresses previously-flagged gap. AllensureColumncalls are present.promotionActive && !discardedLastgate as facts/primers. Uses resolved project identity consistently.projectPathisresolveProjectIdentity(directory)— same resolved form as the OpenCode runner, soorigin_projectvalues are consistent.Sequence Diagram
%%{init: {'theme': 'neutral'}}%% sequenceDiagram participant Model participant Hook as tool.execute hooks participant Registry as SkillLoadRegistry participant DB as skill_memory (SQLite) participant Historian Model->>Hook: tool.execute.before (skill, intent) Hook->>Hook: stashIntent(sessionId:callId, intent) Model->>Hook: tool.execute.after (skill result) Hook->>Hook: parseSkillProvenance(output) Hook->>Hook: parseFrontmatterConfig(SKILL.md) Hook->>Registry: set(sessionId:skillId, provenance + config) Hook->>DB: recallSkillMemoryBlock() Note over DB: Rung 1: cosine(intent+delta embeddings)<br/>Rung 3: FTS5 BM25<br/>Rung 4: flat recency x hit_count DB-->>Hook: skill-memory block Hook->>Model: appended to output.output Model->>Hook: ctx_skill_note(skill, kind, delta) Hook->>Registry: getSkillLoad(sessionId, skill) Hook->>DB: findExistingNote / insertSkillMemoryNote DB-->>Hook: id or null (dedup) Historian->>DB: promoteSkillObservations(global tier) Note over DB: source_type=historian<br/>project_identity=*%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% sequenceDiagram participant Model participant Hook as tool.execute hooks participant Registry as SkillLoadRegistry participant DB as skill_memory (SQLite) participant Historian Model->>Hook: tool.execute.before (skill, intent) Hook->>Hook: stashIntent(sessionId:callId, intent) Model->>Hook: tool.execute.after (skill result) Hook->>Hook: parseSkillProvenance(output) Hook->>Hook: parseFrontmatterConfig(SKILL.md) Hook->>Registry: set(sessionId:skillId, provenance + config) Hook->>DB: recallSkillMemoryBlock() Note over DB: Rung 1: cosine(intent+delta embeddings)<br/>Rung 3: FTS5 BM25<br/>Rung 4: flat recency x hit_count DB-->>Hook: skill-memory block Hook->>Model: appended to output.output Model->>Hook: ctx_skill_note(skill, kind, delta) Hook->>Registry: getSkillLoad(sessionId, skill) Hook->>DB: findExistingNote / insertSkillMemoryNote DB-->>Hook: id or null (dedup) Historian->>DB: promoteSkillObservations(global tier) Note over DB: source_type=historian<br/>project_identity=*Comments Outside Diff (1)
packages/plugin/src/features/magic-context/skill-memory/recall.ts, line 481-484 (link)catch {}makes recall errors invisibleAny exception thrown inside
recallSkillMemoryBlock— a SQLite error, embedding provider crash, or a coding mistake — is silently swallowed and returns an empty string. From the caller's perspective this is indistinguishable from "no notes exist" or "skill-memory disabled". At minimum asessionLogorconsole.warncall here would let a developer diagnose why the recall block is absent without attaching a debugger.Reviews (14): Last reviewed commit: "fix(test): stop global embedding mock.mo..." | Re-trigger Greptile