Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Grok Build, Codex, Cursor, Merlin, corvid-agent, private runners).
```bash
let where . # what agent stuff is here?
let find worktrees --json # .claude/worktrees + ~/.codex + git + …
let find skills --json # Claude + Grok + Cursor + agent.3md planes
let find skills --json # Claude + Grok + Cursor + OpenAI + agent.3md planes
let find agents --json # agent.3md + host agent dirs
let find memory --json # memtrace / codex / gemini / .let (path-only)
let find sessions --json # all hosts, path-only
Expand Down Expand Up @@ -36,14 +36,14 @@ let web # local, read-only Fleet dashboard

- **The let standard** — one closed kind set for every host; cards first, bodies on demand
- **Worktrees** — git seed + Claude / Codex / Cursor / project / Grok DB, deduped by realpath
- **Skills** — Claude, Grok, Cursor, Codex, let catalogs + **agent.3md skill planes**
- **Skills** — Claude, Grok, Cursor, Codex, OpenAI, let catalogs + **agent.3md skill planes**
- **Agents** — agent.3md / `*.3md` first-class + Claude/Codex/Cursor/Grok/Gemini/Kimi roots
- **Commands** — Claude + Cursor slash-commands (project + user)
- **Sessions / memory** — path-only federation (Claude, Grok, Codex, Cursor, Gemini, Kimi)
- **Plugins / MCP / tasks** — Claude plugins & tasks, Cursor plans, MCP configs across hosts
- **Workflows / superskills** — let-native + Grok workflow roots when present
- **Show / open / route** — progressive bodies + agent.3md-first skill ranking
- **Hosts** — Claude, Codex, Cursor, Grok, Gemini, Kimi Code, agent.3md, let
- **Hosts** — Claude, Codex, Cursor, Grok, OpenAI, Gemini, Kimi Code, agent.3md, let
- **Dogfood** — ships `agent.3md` and can find + show + route itself
- **JSON-first** — stable envelopes for agents (`--json`)

Expand Down
2 changes: 1 addition & 1 deletion docs/dogfood.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ let show skill <id> --json # load body / tool template
## Multi-host on this machine

```bash
let doctor --json # roots for claude/codex/cursor/grok/gemini/kimi
let doctor --json # roots for claude/codex/cursor/grok/openai/gemini/kimi
let find sessions --host kimi --scope user --json
let find sessions --host gemini --scope user --json
let find agents --json # agent3md + claude/codex + gemini
Expand Down
3 changes: 2 additions & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ let find instructions --json
Kinds: `instructions`, `skills`, `agents`, `commands`, `worktrees`, `sessions`,
`tasks`, `memory`, `mcp`, `plugins`, `workflows`, `superskills`.

Hosts (`--host`): `claude`, `grok`, `codex`, `cursor`, `gemini`, `kimi`,
Hosts (`--host`): `claude`, `grok`, `codex`, `cursor`, `openai`, `gemini`, `kimi`,
`agent3md`, `git`, `project`, `let`, …

### Host × kind matrix
Expand All @@ -130,6 +130,7 @@ Hosts (`--host`): `claude`, `grok`, `codex`, `cursor`, `gemini`, `kimi`,
| grok | worktrees.db | yes | bundled | — | path-only | memtrace | — | — | — |
| codex | yes | optional | yes | — | path-only | sqlite/dir | yes | config path | — |
| cursor | weak | yes | yes | yes | chats path | — | — | mcp.json | plans |
| openai | — | yes | — | — | — | — | — | — | — |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Replace the new matrix em dashes

This newly added OpenAI row introduces em-dash characters in authored content, directly violating the repository rule requiring hyphens or colons instead; replace these placeholders with permitted characters.

AGENTS.md reference: AGENTS.md:L61-L61

Useful? React with 👍 / 👎.

| gemini | — | — | antigravity | — | history path | brain/knowledge | — | mcp_config | — |
| kimi | — | — | config path | — | workspaces path | user-history | — | — | — |
| agent3md | — | skill planes | agent.3md | — | — | — | — | — | — |
Expand Down
9 changes: 5 additions & 4 deletions specs/catalog/catalog.spec.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
module: catalog
version: 1
version: 4
status: active
files:
- src/catalog/types.ts
Expand Down Expand Up @@ -49,7 +49,7 @@ Context packs never include session paths or bodies.

| Export | Description |
|--------|-------------|
| `HostId` | Host attribution id (claude, grok, codex, cursor, gemini, kimi, git, project, corvid, let, agent3md, unknown). |
| `HostId` | Host attribution id (claude, grok, codex, cursor, openai, gemini, kimi, git, project, corvid, let, agent3md, unknown). |
| `FindKind` | Closed catalog kind union for find/show. |
| `FIND_KINDS` | Runtime list of all FindKind values. |
| `FindScope` | project \| user \| all. |
Expand Down Expand Up @@ -115,7 +115,7 @@ Context packs never include session paths or bodies.
| `pathCardId` | Document caller-visible behavior and constraints. |
| `inRepoWorktreeBases` | Document caller-visible behavior and constraints. |
| `externalWorktreeRoots` | Document caller-visible behavior and constraints. |
| `skillRoots` | Document caller-visible behavior and constraints. |
| `skillRoots` | Fixed project and user skill catalog roots, including `.openai/skills`; user roots obey `include_user_skills`. |
| `isDeniedBasename` | Document caller-visible behavior and constraints. |
| `MAX_BODY_BYTES` | Document caller-visible behavior and constraints. |
| `MAX_OPEN_PREVIEW_BYTES` | Document caller-visible behavior and constraints. |
Expand Down Expand Up @@ -155,7 +155,7 @@ Context packs never include session paths or bodies.

| Export | Description |
|--------|-------------|
| `HostId` | claude, grok, codex, cursor, gemini, kimi, git, project, corvid, let, agent3md, unknown. |
| `HostId` | claude, grok, codex, cursor, openai, gemini, kimi, git, project, corvid, let, agent3md, unknown. |
| `FindKind` | Closed catalog kinds (worktrees, skills, instructions, sessions, ...). |
| `FindScope` | project \| user \| all. |
| `CardScope` | project \| user \| global on individual cards. |
Expand Down Expand Up @@ -255,6 +255,7 @@ Then body is undefined and payload.path_only is true

| Version | Date | Changes |
|---------|------|---------|
| 4 | 2026-08-01 | Add OpenAI skill-root discovery and attribution. |
| 1 | 2026-07-30 | Federated find/where/context (PR1b). |
| 2 | 2026-07-31 | Full host federation: memory/plugins/mcp/tasks/commands; agent.3md first-class. |
| 3 | 2026-07-31 | Security: open path_only refusal; project session scope; partial body reads; underRepo bounds. |
4 changes: 3 additions & 1 deletion specs/doctor/doctor.spec.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
module: doctor
version: 1
version: 2
status: active
files:
- src/doctor.ts
Expand Down Expand Up @@ -32,6 +32,7 @@ defaults. Used by `let doctor --json`.
2. Report always includes a git check and bun check.
3. Config summary exposes allow_shell_exec and default_limit.
4. Codex worktrees root is noted as shallow-scan only when present.
5. OpenAI home and skills roots are reported when skill discovery supports OpenAI.

## Behavioral Examples

Expand All @@ -57,4 +58,5 @@ Then checks include id "git" with ok true and id "bun" with ok true

| Version | Date | Changes |
|---------|------|---------|
| 2 | 2026-08-01 | Report OpenAI home and skills roots. |
| 1 | 2026-07-30 | Initial doctor contract. |
8 changes: 5 additions & 3 deletions specs/paths/paths.spec.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
module: paths
version: 1
version: 2
status: active
files:
- src/paths.ts
Expand All @@ -12,8 +12,8 @@ depends_on: []

## Purpose

Host and project path helpers for Claude, Grok, Codex, Cursor, and let directories,
plus Claude project path encoding.
Host and project path helpers for Claude, Grok, Codex, Cursor, Gemini, Kimi, OpenAI, and let
directories, plus Claude project path encoding.

## Public API

Expand All @@ -26,6 +26,7 @@ plus Claude project path encoding.
| `grokHome` | `~/.grok`. |
| `codexHome` | `~/.codex`. |
| `cursorHome` | `~/.cursor`. |
| `openaiHome` | `~/.openai`. |
| `geminiHome` | `~/.gemini`. |
| `kimiHome` | `~/.kimi-code`. |
| `projectClaudeDir` | `<repo>/.claude`. |
Expand Down Expand Up @@ -63,4 +64,5 @@ Then result is -Users-leif-Development--CorvidLabs-quill

| Version | Date | Changes |
|---------|------|---------|
| 2 | 2026-08-01 | Add the OpenAI home helper contract. |
| 1 | 2026-07-30 | Initial path helpers. |
3 changes: 3 additions & 0 deletions src/catalog/scan-policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
cursorHome,
grokHome,
homeDir,
openaiHome,
} from "../paths.ts";

export type ScanPolicy = {
Expand Down Expand Up @@ -88,6 +89,7 @@ export function skillRoots(repoRoot: string | null): {
join(repoRoot, ".grok", "skills"),
join(repoRoot, ".cursor", "skills"),
join(repoRoot, ".cursor", "skills-cursor"),
join(repoRoot, ".openai", "skills"),
join(repoRoot, ".let", "skills"),
join(repoRoot, "skills"),
join(repoRoot, ".agents", "skills"),
Expand All @@ -99,6 +101,7 @@ export function skillRoots(repoRoot: string | null): {
join(grokHome(), "skills"),
join(cursorHome(), "skills-cursor"),
join(cursorHome(), "skills"),
join(openaiHome(), "skills"),
join(codexHome(), "skills"),
join(homeDir(), ".let", "skills"),
];
Expand Down
7 changes: 7 additions & 0 deletions src/catalog/skills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
pathExists,
readTextFile,
} from "../fs-scan.ts";
import { openaiHome } from "../paths.ts";
import { skillId } from "./ids.ts";
import { skillRoots } from "./scan-policy.ts";
import type { HostId, IndexCard } from "./types.ts";
Expand All @@ -26,6 +27,12 @@ function hostForSkillRoot(root: string, ctx: ScanContext): HostId {
if (root.includes("/.cursor/") || root.includes("skills-cursor")) {
return "cursor";
}
if (
root === join(openaiHome(), "skills") ||
(ctx.repoRoot !== null && root === join(ctx.repoRoot, ".openai", "skills"))
Comment on lines +30 to +32

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Attribute exact OpenAI roots before ancestor hosts

When a repository lives beneath a host-owned directory such as ~/.claude/worktrees/repo (and similarly beneath .grok or .cursor), its <repo>/.openai/skills root satisfies an earlier broad ancestor check and returns that ancestor host before reaching this exact OpenAI comparison. As a result, let find skills --host openai omits valid project OpenAI skills and exposes them under the wrong host; perform the exact OpenAI-root match before the broad host predicates.

Useful? React with 👍 / 👎.

) {
return "openai";
}
if (root.includes("/.codex/")) {
return "codex";
}
Expand Down
1 change: 1 addition & 0 deletions src/catalog/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export type HostId =
| "grok"
| "codex"
| "cursor"
| "openai"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep the host APIs in sync with their specs

This changes HostId and skillRoots, and src/paths.ts adds openaiHome, but neither specs/catalog/catalog.spec.md nor specs/paths/paths.spec.md is updated; both still enumerate only the old host and helper sets. The public contracts are therefore stale under the repository's strict spec-sync gate, so update both module specs in the same change.

AGENTS.md reference: AGENTS.md:L55-L58

Useful? React with 👍 / 👎.

| "gemini"
| "kimi"
| "git"
Expand Down
3 changes: 3 additions & 0 deletions src/doctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
grokHome,
homeDir,
kimiHome,
openaiHome,
projectClaudeDir,
} from "./paths.ts";

Expand Down Expand Up @@ -104,6 +105,8 @@ export function runDoctor(cwd: string = process.cwd()): DoctorReport {
"cursor.chats": rootEntry(join(cursorHome(), "chats"), "path-only"),
"cursor.plans": rootEntry(join(cursorHome(), "plans"), "tasks kind"),
"cursor.worktrees": rootEntry(join(cursorHome(), "worktrees")),
"openai.home": rootEntry(openaiHome()),
"openai.skills": rootEntry(join(openaiHome(), "skills")),
"gemini.home": rootEntry(
geminiHome(),
"GEMINI.md + history + projects.json",
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export {
grokHome,
homeDir,
kimiHome,
openaiHome,
projectClaudeDir,
projectGeminiDir,
projectLetDir,
Expand Down
5 changes: 5 additions & 0 deletions src/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ export function cursorHome(): string {
return join(homeDir(), ".cursor");
}

/** OpenAI agent/CLI home. */
export function openaiHome(): string {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Re-export the OpenAI home helper from the package root

The package exposes only its . entry through src/index.ts, and every existing host-home helper is re-exported there, but this new helper is not. Consumers of @corvidlabs/let therefore cannot import openaiHome even though the source declares it as exported; add it to the path export list in src/index.ts.

Useful? React with 👍 / 👎.

return join(homeDir(), ".openai");
}

/** Google Gemini CLI / Antigravity home. */
export function geminiHome(): string {
return join(homeDir(), ".gemini");
Expand Down
1 change: 1 addition & 0 deletions test/doctor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ describe("runDoctor", () => {
expect(report.version).toBeTruthy();
expect(report.checks.length).toBeGreaterThan(0);
expect(report.roots["claude.home"]).toBeDefined();
expect(report.roots["openai.skills"]).toBeDefined();
expect(report.roots["project.claude.worktrees"]).toBeDefined();
const git = report.checks.find((c) => c.id === "git");
expect(git).toBeDefined();
Expand Down
56 changes: 56 additions & 0 deletions test/find.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { describe, expect, test } from "bun:test";
import { mkdirSync, mkdtempSync, realpathSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { buildContext } from "../src/catalog/context.ts";
import { buildScanContext } from "../src/catalog/context-builder.ts";
import { findAssets } from "../src/catalog/find.ts";
import { whereAmI } from "../src/catalog/where.ts";
import { DEFAULT_CONFIG } from "../src/config.ts";

describe("find", () => {
test("find instructions in let repo", async () => {
Expand All @@ -23,6 +27,58 @@ describe("find", () => {
expect(r.items.length).toBeLessThanOrEqual(ctx.limit);
});

test("find skills discovers project openai skill roots", async () => {
const root = mkdtempSync(join(tmpdir(), "let-openai-"));
Bun.spawnSync(["git", "init"], { cwd: root });
const skillDir = join(root, ".openai", "skills", "agent-coordination");
mkdirSync(skillDir, { recursive: true });
writeFileSync(
join(skillDir, "SKILL.md"),
"---\nname: agent-coordination\ndescription: Coordinate agents\n---\n# Coordination\n",
);
const ctx = buildScanContext({
cwd: root,
scope: "project",
config: {
...DEFAULT_CONFIG,
find: { ...DEFAULT_CONFIG.find, include_user_skills: false },
},
});
const r = await findAssets("skills", ctx, { host: "openai" });
expect(r.items).toHaveLength(1);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Isolate the OpenAI skill fixture from user catalogs

On any developer or CI account that already has entries under ~/.openai/skills, this project-scoped query includes those entries because the default configuration has include_user_skills=true; the host filter retains every OpenAI card, so this count exceeds one and the following index-based assertions may inspect a user skill instead of the fixture. Explicitly disable user-skill inclusion in this test context or select the fixture card by path or name.

Useful? React with 👍 / 👎.

expect(r.items[0]?.host).toBe("openai");
expect(r.items[0]?.name).toBe("agent-coordination");
expect(realpathSync(r.items[0]?.path ?? "")).toBe(
realpathSync(join(skillDir, "SKILL.md")),
);
});

test("find skills keeps generic project roots below .openai attributed to project", async () => {
const parent = mkdtempSync(join(tmpdir(), "let-openai-parent-"));
const root = join(parent, ".openai", "worktrees", "repo");
mkdirSync(root, { recursive: true });
Bun.spawnSync(["git", "init"], { cwd: root });
const skillDir = join(root, "skills", "generic-skill");
mkdirSync(skillDir, { recursive: true });
writeFileSync(join(skillDir, "SKILL.md"), "# Generic skill\n");

const ctx = buildScanContext({
cwd: root,
scope: "project",
config: {
...DEFAULT_CONFIG,
find: { ...DEFAULT_CONFIG.find, include_user_skills: false },
},
});
const r = await findAssets("skills", ctx, { host: "project" });
const genericSkill = r.items.find(
(item) =>
realpathSync(item.path) === realpathSync(join(skillDir, "SKILL.md")),
);

expect(genericSkill?.host).toBe("project");
});

test("find worktrees dedupes paths", async () => {
const ctx = buildScanContext({ cwd: process.cwd() });
const r = await findAssets("worktrees", ctx);
Expand Down
Loading