Skip to content

Commit e01d666

Browse files
committed
0.1.2
1 parent 0b7a31f commit e01d666

9 files changed

Lines changed: 243 additions & 756 deletions

File tree

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
name: Plugin sanity
2+
3+
on:
4+
push:
5+
pull_request:
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: read
10+
11+
jobs:
12+
sanity:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
17+
- uses: actions/setup-python@v5
18+
with:
19+
python-version: "3.12"
20+
21+
- name: Validate Codex plugin package
22+
run: |
23+
python - <<'PY'
24+
import json
25+
import re
26+
from pathlib import Path
27+
28+
root = Path.cwd()
29+
30+
def fail(message: str) -> None:
31+
raise SystemExit(message)
32+
33+
def load_json(path: Path) -> dict:
34+
try:
35+
return json.loads(path.read_text(encoding="utf-8"))
36+
except Exception as exc:
37+
fail(f"{path} is not valid JSON: {exc}")
38+
39+
marketplace = load_json(root / ".agents" / "plugins" / "marketplace.json")
40+
entries = marketplace.get("plugins")
41+
if not isinstance(entries, list):
42+
fail("marketplace plugins must be a list")
43+
44+
docent_entries = [entry for entry in entries if entry.get("name") == "docent"]
45+
if len(docent_entries) != 1:
46+
fail("marketplace must contain exactly one docent plugin entry")
47+
48+
source = docent_entries[0].get("source")
49+
if not isinstance(source, dict) or source.get("source") != "local":
50+
fail("docent marketplace source must be local")
51+
plugin_dir = root / source.get("path", "")
52+
if not plugin_dir.is_dir():
53+
fail(f"marketplace source path does not exist: {plugin_dir}")
54+
55+
manifest = load_json(plugin_dir / ".codex-plugin" / "plugin.json")
56+
if manifest.get("name") != "docent":
57+
fail("plugin manifest name must be docent")
58+
59+
version = manifest.get("version")
60+
if not isinstance(version, str) or not re.fullmatch(r"\d+\.\d+\.\d+", version):
61+
fail("plugin manifest version must be plain major.minor.patch")
62+
if manifest.get("skills") != "./skills/":
63+
fail("plugin manifest skills must point to ./skills/")
64+
if manifest.get("mcpServers") != "./.mcp.json":
65+
fail("plugin manifest mcpServers must point to ./.mcp.json")
66+
67+
required_files = [
68+
".codex-plugin/plugin.json",
69+
".mcp.json",
70+
"skills/docent/SKILL.md",
71+
"skills/docent/analysis.md",
72+
"skills/docent/dql-reference.md",
73+
"skills/docent/ingestion-reference.md",
74+
"skills/docent/ingestion.md",
75+
"skills/docent/readings-reference.md",
76+
"skills/docent/report.md",
77+
]
78+
for rel_path in required_files:
79+
path = plugin_dir / rel_path
80+
if not path.is_file():
81+
fail(f"required plugin file is missing: {rel_path}")
82+
if path.suffix == ".md" and not path.read_text(encoding="utf-8").strip():
83+
fail(f"markdown file is empty: {rel_path}")
84+
85+
mcp = load_json(plugin_dir / ".mcp.json")
86+
server = mcp.get("mcpServers", {}).get("docent")
87+
if not isinstance(server, dict):
88+
fail(".mcp.json must define mcpServers.docent")
89+
if server.get("type") != "stdio" or server.get("command") != "uv":
90+
fail("docent MCP server must run as uv stdio")
91+
args = server.get("args")
92+
if not isinstance(args, list) or "--from" not in args:
93+
fail("docent MCP server args must include --from")
94+
package = args[args.index("--from") + 1]
95+
if package != "docent-python>=0.1.73":
96+
fail("docent MCP server must require docent-python>=0.1.73")
97+
98+
forbidden_names = {".mcp.local.json", "docent.env"}
99+
for path in plugin_dir.rglob("*"):
100+
if path.name in forbidden_names or path.name.startswith("docent.env."):
101+
fail(f"local credential/config file must not be published: {path}")
102+
103+
print("Codex plugin sanity checks passed")
104+
PY

plugins/docent/.codex-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "docent",
3-
"version": "0.1.1",
3+
"version": "0.1.2",
44
"description": "Docent AI analysis tools for Codex.",
55
"author": {
66
"name": "Transluce",

plugins/docent/.mcp.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"docent": {
44
"type": "stdio",
55
"command": "uv",
6-
"args": ["tool", "run", "--from", "docent-python", "docent-mcp"]
6+
"args": ["tool", "run", "--from", "docent-python>=0.1.73", "docent-mcp"]
77
}
88
}
99
}

plugins/docent/skills/docent/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@ This is the root skill for all Docent work. This file is just a table of content
1717
- For the Readings API (`client.read`, `client.query`, batching, prompts, clustering): `./readings-reference.md`
1818
- For DQL syntax, schemas, quirks, and example queries: `./dql-reference.md`
1919
- For the reports API: `./report.md` (only if the user explicitly asks for a report)
20-
- For ingestion-side data-model and conversion examples: the reference and pattern sections in `./ingestion.md`
20+
- For ingestion-side data-model and conversion examples: `./ingestion-reference.md`
2121
- SDK reference is available by visiting [our online documentation](https://docs.transluce.org/llms.txt)

plugins/docent/skills/docent/analysis.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,11 @@ client = Docent.from_url("https://docent.transluce.org/dashboard/668354d8-...")
6464
```
6565
This parses the domain and collection ID from the URL automatically.
6666

67-
The Docent SDK can be configured by a docent.env file in the working directory. The SDK will automatically discover and load a docent.env file if it exists. You do not need to explicitly source docent.env. Config files may use INI-style `[section]` headers for multi-profile support; select a profile with `Docent(profile="my-profile")` or the `DOCENT_PROFILE` environment variable.
67+
The Docent SDK can be configured by a `docent.env` file. The SDK searches from the current working directory upward through parent directories, then falls back to `~/.docent/docent.env` if no local file exists. You do not need to explicitly source `docent.env`. Config files may use INI-style `[section]` headers for multi-profile support; select a profile with `Docent(profile="my-profile")` or the `DOCENT_PROFILE` environment variable.
6868

6969
If you're not sure what collection the user is talking about:
7070
* If the user provides a Docent dashboard URL (e.g., `https://docent.transluce.org/dashboard/668354d8-...`), use `Docent.from_url()` or extract the collection ID from the last path segment (the UUID).
71-
* Otherwise, check the `docent.env` file in the working directory for `DOCENT_COLLECTION_ID`.
71+
* Otherwise, check the SDK-discovered `docent.env` file for `DOCENT_COLLECTION_ID`.
7272
* If neither is available, ask the user to paste the collection UUID.
7373

7474
The main Docent deployment lives at https://docent.transluce.org but the user may connect a different deployment by overriding DOCENT_FRONTEND_URL in docent.env. The Docent SDK will print out the frontend URL when it is initialized, e.g. `Authenticating Docent client with frontend_url='https://docent.transluce.org'`. If you see a different frontend URL, use that URL in place of `https://docent.transluce.org` for any links.
@@ -80,7 +80,7 @@ If you run into any issues or unexpected behavior with the Docent platform, paus
8080
* If authentication fails (HTTP 401) or no API key is configured, walk the user through setup:
8181
1. Open the API keys page for them: `open https://docent.transluce.org/settings/api-keys` (macOS) or `xdg-open https://docent.transluce.org/settings/api-keys` (Linux).
8282
2. Ask them to create a new API key (it will start with `dk_`).
83-
3. Write the key to a `docent.env` file in the working directory: `DOCENT_API_KEY=dk_...` (plus `DOCENT_API_URL` and `DOCENT_FRONTEND_URL` if not using the default instance).
83+
3. Write the key to a local `docent.env` file or `~/.docent/docent.env`: `DOCENT_API_KEY=dk_...` (plus `DOCENT_API_URL` and `DOCENT_FRONTEND_URL` if not using the default instance).
8484
4. Verify connectivity by constructing a `Docent()` client — the constructor validates the API key automatically.
8585
* If the SDK does not match what's documented here, check whether the SDK is up to date.
8686
* If the Docent MCP server is available but doesn't match the tools documented here, check whether the MCP server needs an upgrade (`uv tool upgrade docent`). If an upgrade was needed, ask the user to restart the session or MCP server.
@@ -322,7 +322,7 @@ ORDER BY cnt DESC
322322

323323
These are specific rules that follow from the principles above. They apply throughout the analysis:
324324

325-
* **Never present opaque Python computation as analysis results.** Orientation queries (Step 1) are for *your* understanding and can use `execute_dql()` and local Python. But once you move past orientation into actual analysis (Step 3), findings must go through Docent's inspectable pipeline — DQL query steps visible in the UI and LLM analyses with citable evidence. If the user's question requires categorization, comparison, or synthesis, use Docent analyses, not a Python script that outputs a table. The user has no way to verify, inspect, or drill into results that come from opaque code. Metadata aggregations via DQL are acceptable as supporting context (e.g., counts, averages), but the analytical conclusions should come from inspectable analyses the user can review in the Docent UI.
325+
* **Never present opaque Python computation as analysis results.** Orientation queries (Step 1) are for *your* understanding and can use `execute_dql()` and local Python. But once you move past orientation into actual analysis (Step 3), findings must go through Docent's inspectable pipeline — DQL query steps visible in the UI and analysis-plan readings with citable evidence. If the user's question requires categorization, comparison, or synthesis, use Docent analyses, not a Python script that outputs a table. The user has no way to verify, inspect, or drill into results that come from opaque code. Metadata aggregations via DQL are acceptable as supporting context (e.g., counts, averages), but the analytical conclusions should come from inspectable analyses the user can review in the Docent UI.
326326
* **Don't fall back to manual synthesis when an analysis step fails.** If a synthesis step fails (e.g., context overflow), fix the analysis design (batch it, sample it, use structured aggregation) and re-submit. Do not absorb the synthesis work into opaque Python scripts or agent-side summarization — this defeats the core value of Docent's inspectable, citable analysis. If you must do agent-side aggregation as a stopgap (e.g., counting structured output fields via a query), explicitly flag to the user that this step is not inspectable in the Docent UI and offer to re-run it properly.
327327
* If the user asks you to "read the agent runs", "summarize 10 transcripts", "classify the results", or similar, that not mean that you (the coding agent) should do so directly. Prefer to do this in an analysis plan using readings.
328328
* **Be transparent about reused work.** This has two parts:

plugins/docent/skills/docent/dql-reference.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ raw_rows = client.dql_result_to_dicts(result)
4444
| `transcripts` | Individual transcripts tied to an agent run; stores serialized messages and per-transcript metadata. |
4545
| `transcript_groups` | Hierarchical groupings of transcripts for runs. |
4646
| `judge_results` | Scored rubric outputs keyed by agent run and rubric version. |
47-
| `results` | Individual LLM analysis results from result sets. |
4847
| `readings` | Reading definitions (template or scripted LLM analysis). |
4948
| `reading_results` | Results from running readings. |
5049
| `reading_result_links` | Junction table linking readings to their results. |
@@ -288,7 +287,7 @@ LIMIT 50;
288287
- **Single statement**: Batches or multiple statements are rejected.
289288
- **Explicit projection**: Wildcard projections (`*`) are disallowed. List the columns you need.
290289
- **Collection scoping**: A single query can only access data within a single collection.
291-
- **Limit enforcement**: Every query is capped at 10,000 rows. Use pagination (`OFFSET`/`LIMIT`) for larger result sets.
290+
- **Limit enforcement**: Every query is capped at 10,000 rows. Use pagination (`OFFSET`/`LIMIT`) for larger row collections.
292291
- **JSON performance**: Heavy JSON traversal across large collections can be slow. Prefer top-level fields when available.
293292
- **Type awareness**: Cast values explicitly when precision matters.
294293

0 commit comments

Comments
 (0)