Skip to content

Commit b069ff8

Browse files
committed
feat(codex): add lite sharing skills and hooks
1 parent e330159 commit b069ff8

16 files changed

Lines changed: 1265 additions & 22 deletions

File tree

platform-integrations/INSTALL_SPEC.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,12 +115,13 @@ Target: project directory
115115
2. Copy shared lib from `platform-integrations/claude/plugins/evolve-lite/lib/``plugins/evolve-lite/lib/`
116116
3. Upsert plugin entry `evolve-lite` into `.agents/plugins/marketplace.json`
117117
4. Upsert a `UserPromptSubmit` hook into `.codex/hooks.json` that runs the Evolve recall helper script by walking upward from the current working directory until it finds `plugins/evolve-lite/skills/recall/scripts/retrieve_entities.py` (does not require `git`)
118-
5. Print post-install guidance that automatic recall requires `~/.codex/config.toml` to include:
118+
5. Upsert a `SessionStart` hook into `.codex/hooks.json` that runs `plugins/evolve-lite/skills/sync/scripts/sync.py --quiet`
119+
6. Print post-install guidance that automatic recall requires `~/.codex/config.toml` to include:
119120
```toml
120121
[features]
121122
codex_hooks = true
122123
```
123-
6. Print a manual fallback note that users can invoke `evolve-lite:recall` directly if they do not want to enable Codex hooks
124+
7. Print a manual fallback note that users can invoke `evolve-lite:recall` directly if they do not want to enable Codex hooks
124125

125126
Codex is currently implemented only in lite mode. Full mode is reserved for future MCP-backed work.
126127

@@ -143,6 +144,7 @@ Codex is currently implemented only in lite mode. Full mode is reserved for futu
143144
1. Remove `plugins/evolve-lite/`
144145
2. Remove the `evolve-lite` entry from `.agents/plugins/marketplace.json`
145146
3. Remove the Evolve `UserPromptSubmit` hook from `.codex/hooks.json`
147+
4. Remove the Evolve `SessionStart` hook from `.codex/hooks.json`
146148

147149
---
148150

platform-integrations/codex/plugins/evolve-lite/.codex-plugin/plugin.json

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "evolve-lite",
3-
"version": "1.0.0",
4-
"description": "Recall and save Evolve entities in Codex without MCP.",
3+
"version": "1.1.0",
4+
"description": "Recall, save, and share Evolve entities in Codex without MCP.",
55
"author": {
66
"name": "Vinod Muthusamy",
77
"url": "https://github.com/AgentToolkit/altk-evolve"
@@ -13,16 +13,18 @@
1313
"skills": "./skills/",
1414
"interface": {
1515
"displayName": "Evolve Lite",
16-
"shortDescription": "Recall and save reusable Evolve entities.",
17-
"longDescription": "A lightweight Codex plugin that helps you save reusable entities from successful sessions and recall them automatically on new prompts.",
16+
"shortDescription": "Recall, save, and share reusable Evolve entities.",
17+
"longDescription": "A lightweight Codex plugin that helps you save reusable entities, publish selected guidance, subscribe to shared repos, and recall relevant memory automatically on new prompts.",
1818
"developerName": "AgentToolkit",
1919
"category": "Productivity",
2020
"capabilities": ["Interactive", "Write"],
2121
"websiteURL": "https://github.com/AgentToolkit/altk-evolve",
2222
"defaultPrompt": [
2323
"Recall Evolve entities for this task.",
2424
"Save new Evolve learnings from this session.",
25-
"Show me the entities stored for this repo."
25+
"Show me the entities stored for this repo.",
26+
"Publish one of my Evolve guidelines.",
27+
"Subscribe to a teammate's Evolve guidelines repo."
2628
],
2729
"brandColor": "#2563EB"
2830
}

platform-integrations/codex/plugins/evolve-lite/README.md

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Evolve Lite Plugin for Codex
22

3-
A plugin that helps Codex learn from conversations by automatically extracting and applying entities.
3+
A plugin that helps Codex save, recall, and share reusable entities across workspaces.
44

55
⭐ Star the repo: https://github.com/AgentToolkit/altk-evolve
66

@@ -9,20 +9,54 @@ A plugin that helps Codex learn from conversations by automatically extracting a
99
- Automatic recall through a repo-level Codex `UserPromptSubmit` hook when Codex hooks are enabled
1010
- Manual `evolve-lite:learn` skill to save reusable entities into `.evolve/entities/`
1111
- Manual `evolve-lite:recall` skill to inspect everything stored for the current repo
12+
- Manual `evolve-lite:publish` skill to publish private guidelines to your public repo
13+
- Manual `evolve-lite:subscribe` and `evolve-lite:unsubscribe` skills to manage shared guideline repos
14+
- Automatic or manual `evolve-lite:sync` to mirror subscribed repos into local recall storage
1215

1316
## Storage
1417

15-
Entities are stored in the active workspace under:
18+
Entities and sharing data are stored in the active workspace under:
1619

1720
```text
18-
.evolve/entities/
19-
guideline/
20-
use-context-managers-for-file-operations.md
21-
cache-api-responses-locally.md
21+
.evolve/
22+
entities/
23+
guideline/
24+
use-context-managers-for-file-operations.md
25+
subscribed/
26+
alice/
27+
guideline/
28+
prefer-small-functions.md
29+
public/
30+
guideline/
31+
no-eval.md
32+
subscribed/
33+
alice/
34+
guideline/
35+
prefer-small-functions.md
36+
audit.log
2237
```
2338

2439
Each entity is a markdown file with lightweight YAML frontmatter.
2540

41+
Sharing configuration lives in `evolve.config.yaml` at the repo root:
42+
43+
```yaml
44+
identity:
45+
user: alice
46+
47+
public_repo:
48+
remote: git@github.com:alice/evolve-guidelines.git
49+
branch: main
50+
51+
subscriptions:
52+
- name: team
53+
remote: git@github.com:myorg/evolve-guidelines.git
54+
branch: main
55+
56+
sync:
57+
on_session_start: true
58+
```
59+
2660
## Source Layout
2761
2862
This source tree intentionally omits `lib/`.
@@ -33,7 +67,12 @@ The shared library lives in:
3367
platform-integrations/claude/plugins/evolve-lite/lib/
3468
```
3569

36-
`platform-integrations/install.sh` copies that shared library into the installed Codex plugin so the installed layout is self-contained.
70+
`platform-integrations/install.sh` installs Codex in this order:
71+
72+
1. copy the Codex plugin source into `plugins/evolve-lite/`
73+
2. copy the shared `lib/` from the Claude plugin into `plugins/evolve-lite/lib/`
74+
3. wire the marketplace entry
75+
4. wire the Codex hooks
3776

3877
## Installation
3978

@@ -60,6 +99,8 @@ If you do not want to enable Codex hooks, you can still invoke the installed `ev
6099

61100
The installed Codex hook does not require `git`. It walks upward from the current working directory until it finds the repo-local `plugins/evolve-lite/.../retrieve_entities.py` script.
62101

102+
If `sync.on_session_start` is enabled in `evolve.config.yaml`, the installer also registers a `SessionStart` hook that runs the sync helper quietly whenever a Codex session starts or resumes.
103+
63104
## Included Skills
64105

65106
### `evolve-lite:learn`
@@ -69,3 +110,19 @@ Analyze the current session and save proactive Evolve entities as markdown files
69110
### `evolve-lite:recall`
70111

71112
Show the entities already stored for the current workspace.
113+
114+
### `evolve-lite:publish`
115+
116+
Copy selected private guidelines into `.evolve/public/`, stamp them as public, and push them to your configured sharing repo.
117+
118+
### `evolve-lite:subscribe`
119+
120+
Clone another user's public guideline repo into `.evolve/subscribed/` and register it in `evolve.config.yaml`.
121+
122+
### `evolve-lite:unsubscribe`
123+
124+
Remove a configured subscription and delete its local clones and mirrored recall entities.
125+
126+
### `evolve-lite:sync`
127+
128+
Pull every configured subscription and mirror its markdown files into `.evolve/entities/subscribed/` so recall can include them automatically.

platform-integrations/codex/plugins/evolve-lite/skills/learn/scripts/save_entities.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
in the entities directory, organized by type.
66
"""
77

8+
import argparse
89
import json
910
import sys
1011
from pathlib import Path
@@ -13,9 +14,14 @@
1314
_script = Path(__file__).resolve()
1415
_lib = None
1516
for _ancestor in _script.parents:
16-
_candidate = _ancestor / "lib"
17-
if (_candidate / "entity_io.py").is_file():
18-
_lib = _candidate
17+
for _candidate in (
18+
_ancestor / "lib",
19+
_ancestor / "platform-integrations" / "claude" / "plugins" / "evolve-lite" / "lib",
20+
):
21+
if (_candidate / "entity_io.py").is_file():
22+
_lib = _candidate
23+
break
24+
if _lib is not None:
1925
break
2026
if _lib is None:
2127
raise ImportError(f"Cannot find plugin lib directory above {_script}")
@@ -42,6 +48,10 @@ def normalize(text):
4248

4349

4450
def main():
51+
parser = argparse.ArgumentParser()
52+
parser.add_argument("--user", default=None, help="Stamp owner on every entity written")
53+
args = parser.parse_args()
54+
4555
try:
4656
input_data = json.load(sys.stdin)
4757
log(f"Received input with keys: {list(input_data.keys())}")
@@ -82,6 +92,11 @@ def main():
8292
log(f"Skipping duplicate: {content[:60]}")
8393
continue
8494

95+
if args.user and not entity.get("owner"):
96+
entity["owner"] = args.user
97+
if not entity.get("visibility"):
98+
entity["visibility"] = "private"
99+
85100
path = write_entity_file(entities_dir, entity)
86101
existing_contents.add(normalize(content))
87102
added_count += 1
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
---
2+
name: publish
3+
description: Publish a private guideline to your public repo so others can subscribe to it.
4+
---
5+
6+
# Publish Guideline
7+
8+
## Overview
9+
10+
This skill publishes one or more private guidelines from your local `.evolve/entities/guideline/` directory to your public git repository, making them available for others to subscribe to.
11+
12+
## Workflow
13+
14+
### Step 1: Bootstrap config if missing or incomplete
15+
16+
Check whether `evolve.config.yaml` exists in the project root.
17+
18+
If it does not exist, ask the user for:
19+
20+
- a username such as `vatche`
21+
- the remote URL for the public guidelines repo
22+
23+
Create `evolve.config.yaml` with:
24+
25+
```yaml
26+
identity:
27+
user: {username}
28+
public_repo:
29+
remote: {remote}
30+
branch: main
31+
subscriptions: []
32+
sync:
33+
on_session_start: true
34+
```
35+
36+
If the file exists but `identity.user` or `public_repo.remote` is missing, ask only for the missing values and update the file.
37+
38+
### Step 2: First-time setup
39+
40+
Ensure `.evolve/` is gitignored at the project root:
41+
42+
```bash
43+
grep -qxF '.evolve/' .gitignore 2>/dev/null || echo '.evolve/' >> .gitignore
44+
```
45+
46+
If `.evolve/public/` does not already contain a `.git` directory, initialize it and add the configured remote:
47+
48+
```bash
49+
git init .evolve/public
50+
git -C .evolve/public remote add origin {public_repo.remote}
51+
```
52+
53+
### Step 3: List and select entities
54+
55+
List the files in `.evolve/entities/guideline/` and ask the user which ones to publish.
56+
57+
### Step 4: Run publish script
58+
59+
For each selected file, run:
60+
61+
```bash
62+
python3 plugins/evolve-lite/skills/publish/scripts/publish.py \
63+
--entity "{filename}" \
64+
--user "{identity.user}"
65+
```
66+
67+
### Step 5: Commit and push
68+
69+
```bash
70+
git -C .evolve/public add .
71+
git -C .evolve/public commit -m "[evolve] publish: {name}"
72+
git -C .evolve/public push origin "{public_repo.branch}"
73+
```
74+
75+
### Step 6: Confirm
76+
77+
Tell the user what was published and where it was pushed.
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#!/usr/bin/env python3
2+
"""Publish a private guideline entity to the public directory."""
3+
4+
import argparse
5+
import datetime
6+
import os
7+
import sys
8+
from pathlib import Path
9+
10+
# Walk up from the script location to find the installed plugin lib directory.
11+
_script = Path(__file__).resolve()
12+
_lib = None
13+
for _ancestor in _script.parents:
14+
for _candidate in (
15+
_ancestor / "lib",
16+
_ancestor / "platform-integrations" / "claude" / "plugins" / "evolve-lite" / "lib",
17+
):
18+
if (_candidate / "entity_io.py").is_file():
19+
_lib = _candidate
20+
break
21+
if _lib is not None:
22+
break
23+
if _lib is None:
24+
raise ImportError(f"Cannot find plugin lib directory above {_script}")
25+
sys.path.insert(0, str(_lib))
26+
from audit import append as audit_append # noqa: E402
27+
from entity_io import entity_to_markdown, markdown_to_entity # noqa: E402
28+
29+
30+
def main():
31+
parser = argparse.ArgumentParser()
32+
parser.add_argument("--entity", required=True, help="Basename of the .md file to publish")
33+
parser.add_argument("--user", default=None, help="Username to stamp as owner")
34+
args = parser.parse_args()
35+
36+
evolve_dir = Path(os.environ.get("EVOLVE_DIR", ".evolve"))
37+
src_base = (evolve_dir / "entities" / "guideline").resolve()
38+
src_path = (evolve_dir / "entities" / "guideline" / args.entity).resolve()
39+
40+
if not src_path.is_relative_to(src_base):
41+
print(f"Error: invalid entity name: {args.entity!r}", file=sys.stderr)
42+
sys.exit(1)
43+
44+
if not src_path.exists():
45+
print(f"Error: entity file not found: {src_path}", file=sys.stderr)
46+
sys.exit(1)
47+
48+
entity = markdown_to_entity(src_path)
49+
entity["visibility"] = "public"
50+
if args.user:
51+
entity["owner"] = args.user
52+
entity["published_at"] = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
53+
54+
dest_dir = evolve_dir / "public" / "guideline"
55+
dest_dir.mkdir(parents=True, exist_ok=True)
56+
dest_base = dest_dir.resolve()
57+
dest_path = (dest_dir / args.entity).resolve()
58+
if not dest_path.is_relative_to(dest_base):
59+
print(f"Error: invalid entity name: {args.entity!r}", file=sys.stderr)
60+
sys.exit(1)
61+
dest_path.write_text(entity_to_markdown(entity), encoding="utf-8")
62+
63+
audit_append(
64+
project_root=str(evolve_dir.parent) if evolve_dir.name != ".evolve" else ".",
65+
action="publish",
66+
actor=args.user or "unknown",
67+
entity=args.entity,
68+
)
69+
70+
print(f"Published: {args.entity} -> {dest_path}")
71+
72+
73+
if __name__ == "__main__":
74+
main()

0 commit comments

Comments
 (0)