Skip to content

Commit 237ee50

Browse files
Merge pull request #251 from ScriptedAlchemy/feat/plugin-followons
feat(plugin): Phase-7 follow-ons — cursor commands, support files, memory merge, unified lint
2 parents 67cace7 + 53905b2 commit 237ee50

61 files changed

Lines changed: 2072 additions & 1096 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

build.rs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,101 @@ fn emit_dashboard_asset_inputs() -> String {
315315
format!("{:016x}", hasher.finish())
316316
}
317317

318+
/// Recursively collects every file under `root`, relative to `root`, using
319+
/// forward-slash separators. Returns sorted paths so codegen is deterministic.
320+
fn collect_files_relative(root: &Path) -> Vec<String> {
321+
fn walk(base: &Path, dir: &Path, out: &mut Vec<String>) {
322+
let Ok(entries) = fs::read_dir(dir) else {
323+
return;
324+
};
325+
for entry in entries.flatten() {
326+
let path = entry.path();
327+
if path.is_dir() {
328+
walk(base, &path, out);
329+
} else if path.is_file() {
330+
if let Ok(relative) = path.strip_prefix(base) {
331+
out.push(relative.to_string_lossy().replace('\\', "/"));
332+
}
333+
}
334+
}
335+
}
336+
let mut files = Vec::new();
337+
walk(root, root, &mut files);
338+
files.sort();
339+
files
340+
}
341+
342+
/// True when `path` is a readable UTF-8 text file. Used to fail the skill
343+
/// bundle codegen early with a clear message when a binary support file would
344+
/// otherwise break `include_str!` with an opaque compile error.
345+
fn is_probably_utf8_text(path: &Path) -> bool {
346+
match fs::read(path) {
347+
Ok(bytes) => std::str::from_utf8(&bytes).is_ok(),
348+
// Unreadable files fall through to include_str!'s own error.
349+
Err(_) => true,
350+
}
351+
}
352+
353+
/// Generates `$OUT_DIR/plugin_bundle_generated.rs`: a recursive manifest of
354+
/// every file under `plugin/skills/` (SKILL.md *and* any `references/`,
355+
/// `scripts/`, `assets/` support files), embedded via `include_str!` at compile
356+
/// time. Moving embedding off a hand-maintained flat list lets skills ship
357+
/// support files without a matching table edit; the coverage tests assert the
358+
/// generated set equals the on-disk tree.
359+
///
360+
/// Each entry's deploy path equals its `plugin/`-relative source path
361+
/// (`skills/<skill>/<subpath>`), which is identical for every host, so a single
362+
/// generated slice serves Claude, Codex, and Cursor (Cursor filters out the
363+
/// `tracedecay-*` dispatcher skills at compose time).
364+
fn generate_skill_bundle() {
365+
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR");
366+
let skills_root = Path::new(&manifest_dir).join("plugin/skills");
367+
println!("cargo::rerun-if-changed=plugin/skills");
368+
369+
let mut code = String::new();
370+
code.push_str(
371+
"// @generated by build.rs (generate_skill_bundle). Do not edit.\n\
372+
/// Every file under `plugin/skills/` (SKILL.md + support files), embedded\n\
373+
/// recursively at compile time. `relative` is the deploy path (identical to\n\
374+
/// the `plugin/`-relative source path).\n\
375+
pub const GENERATED_SKILL_FILES: &[PluginFile] = &[\n",
376+
);
377+
for relative in collect_files_relative(&skills_root) {
378+
// rerun on each individual file so edits re-trigger codegen even if the
379+
// directory mtime does not change.
380+
println!("cargo::rerun-if-changed=plugin/skills/{relative}");
381+
// Every embedded file goes through `include_str!`, which only accepts
382+
// UTF-8. A binary support file (e.g. `assets/*.png`) would otherwise
383+
// fail to compile with an opaque "stream did not contain valid UTF-8"
384+
// error pointing at the generated file, not the offending asset. Guard
385+
// it here with a clear message; binary support files are not embeddable
386+
// yet (add `include_bytes!` handling when that becomes a requirement).
387+
let abs = skills_root.join(&relative);
388+
if !is_probably_utf8_text(&abs) {
389+
panic!(
390+
"plugin/skills/{relative} is not a UTF-8 text file. The skill bundle codegen \
391+
embeds every file via include_str!, so binary support files (e.g. images) are \
392+
not embeddable yet. Remove the binary file or add include_bytes! support to \
393+
build.rs (generate_skill_bundle)."
394+
);
395+
}
396+
let deploy = format!("skills/{relative}");
397+
let source = format!("skills/{relative}");
398+
code.push_str(&format!(
399+
" PluginFile {{ relative: {deploy:?}, contents: include_str!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/plugin/{source}\")) }},\n"
400+
));
401+
}
402+
code.push_str("];\n");
403+
404+
let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR");
405+
let out_path = Path::new(&out_dir).join("plugin_bundle_generated.rs");
406+
if let Err(e) = fs::write(&out_path, code) {
407+
panic!("failed to write {}: {e}", out_path.display());
408+
}
409+
}
410+
318411
fn main() {
412+
generate_skill_bundle();
319413
let out_path = Path::new("src/resources/logo.ansi");
320414
let logo_bytes = include_bytes!("src/resources/logo.png");
321415
let ansi = logo_art::image_to_ansi(logo_bytes, 90);

docs/AGENT-MEMORY-INTERCEPTION.md

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@ instead of — or layered on top of — each agent's native memory mechanism.
1010
All file paths below were verified on this machine (Codex CLI 0.142.4, Cursor
1111
with hooks + plugins, tracedecay plugin v0.0.23 installed for both agents).
1212

13+
> **Note (as of 2026-07-03):** the per-host `cursor-plugin/` / `codex-plugin/`
14+
> source trees have since collapsed into a single shared `plugin/` tree, so
15+
> skill/command/agent sources now live under `plugin/skills/…`,
16+
> `plugin/commands/…`, and `plugin/agents/…` (Cursor-only surfaces under
17+
> `plugin/overlays/cursor/…`). The `recalling-project-memory` and
18+
> `curating-project-memory` skills were also merged into a single
19+
> `project-memory` skill. References below to the old paths/slugs are retained
20+
> as historical design context; the current locations are the shared-tree
21+
> equivalents.
22+
1323
---
1424

1525
## 1. How Codex reads memory today
@@ -171,9 +181,9 @@ to `~/.cursor/plugins/local/tracedecay/`:
171181
`src/hooks/`).
172182
- **`rules/tracedecay.mdc`** — always-applied rule; its **Recall** bullet
173183
steers models to `tracedecay_message_search` / `tracedecay_fact_store`
174-
search and the `recalling-project-memory` skill.
175-
- **`skills/`** — 25+ workflow skills incl. `recalling-project-memory`,
176-
`curating-project-memory`, `recalling-session-context`; plus an
184+
search and the `project-memory` skill.
185+
- **`skills/`** — 25+ workflow skills incl. `project-memory` (the merged
186+
recall+curate memory skill) and `recalling-session-context`; plus an
177187
agent-managed skill overlay (`install_cursor_managed_skill_overlay`).
178188
- **`agents/`**`code-explorer`, `code-health-auditor`, `session-historian`
179189
subagent definitions.
@@ -277,9 +287,9 @@ surfaces on a schedule," which design D reuses for memory.
277287
| --- | --- | --- |
278288
| Facts stored | ✅ fact store (9 facts here) + LCM transcripts | same store |
279289
| Model *can* recall | ✅ MCP `tracedecay_fact_store` search + skill | ✅ same |
280-
| Model is *told* to recall | ⚠️ soft steering in SessionStart/UserPromptSubmit context; `recalling-project-memory` skill matches only when the model thinks "recall" | ⚠️ one Recall bullet in `tracedecay.mdc`; same skill-match dependency |
290+
| Model is *told* to recall | ⚠️ soft steering in SessionStart/UserPromptSubmit context; `project-memory` skill matches only when the model thinks "recall" | ⚠️ one Recall bullet in `tracedecay.mdc`; same skill-match dependency |
281291
| Facts *pushed* into context | ❌ none — hook context is index status + hints only | ❌ none |
282-
| Automatic storage | ⚠️ session_reflector exists but disabled by default; skills say "add facts **only when the user asks**" (`recalling-project-memory` guardrail) | same |
292+
| Automatic storage | ⚠️ session_reflector exists but disabled by default; skills say "add facts **only when the user asks**" (`project-memory` guardrail) | same |
283293
| Native memory overlap | ⚠️ Codex memories **on** (`features.memories=true`), learning from the same threads in parallel | Unknown toggle state; server-side, uninspectable |
284294

285295
The delta is precisely: **nothing proactively retrieves facts at
@@ -323,7 +333,7 @@ Implementation pointers: `src/hooks/codex.rs`, `src/hooks/cursor.rs`,
323333
`src/memory/retrieval.rs` (`FactRetriever::search/probe`), analytics via
324334
`record_hint_analytics` so injection quality is measurable. No plugin schema
325335
change; hook hashes change → users re-trust via `/hooks` (already documented
326-
in `codex-plugin/README.md`).
336+
in the Codex plugin README).
327337

328338
### B. Cursor session-start injection + a materialized memory rule — **do with A**
329339

@@ -359,11 +369,10 @@ doctor checks cover it.
359369

360370
*Effort: XS. Effect: medium. Risk: memory spam (mitigated by write-time dedupe).*
361371

362-
Today `recalling-project-memory`'s guardrail says add facts "**only when the
372+
Today `project-memory`'s guardrail says add facts "**only when the
363373
user asks**" — the opposite of agent-memory behavior. Change the instruction
364-
(rule Recall bullet + skill in `cursor-plugin/rules/tracedecay.mdc`,
365-
`cursor-plugin/skills/recalling-project-memory/SKILL.md`, codex-plugin
366-
mirrors) to:
374+
(rule Recall bullet in `plugin/overlays/cursor/rules/tracedecay.mdc` + the
375+
`plugin/skills/project-memory/SKILL.md` skill, shared across every host) to:
367376

368377
- *Recall:* "before starting non-trivial work, search `tracedecay_fact_store`
369378
for prior decisions" (currently phrased as fallback, not default).
@@ -458,7 +467,7 @@ alongside D and reusing the managed-file conventions from the skill overlay.
458467
analytics.
459468
2. **B2 + C** (materialized Cursor memory rule + proactive storage wording) —
460469
one PR in `src/agents/cursor.rs` embedded files + plugin rule/skill text
461-
(mirrored in `codex-plugin/` skill text).
470+
(shared skill text under `plugin/skills/`).
462471
3. **D** (reflector enablement UX) — config/doctor/dashboard nudge.
463472
4. **E** (coexistence policy + optional Codex-memories harvest importer).
464473
5. **F** (generalized AGENTS.md materialization across all 15 agent

0 commit comments

Comments
 (0)