From d0ce3c72ba3174812401b10d747763f3e8cd7498 Mon Sep 17 00:00:00 2001 From: ty Date: Sun, 30 Aug 2026 01:31:58 -0500 Subject: [PATCH 1/6] chore: pin qwen-flash to dated snapshot (alias retirement) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The unversioned `qwen-flash` alias silently re-points as Alibaba ships new flash generations, so the kit's documented fast lane was a moving target. Pin the default to `qwen-flash-2025-07-28`, the behaviour-equivalent dated snapshot (live-probed against dashscope-intl compatible-mode: 200 OK, echoes back the snapshot id — note it is NOT listed by GET /models, so a catalog grep is a false negative; only an inference probe confirms it). Also updates the surfaces that state the default — README env table + fast-lane prose, .env.example, the architecture diagram (md + generated html) and the routing unit test — so the public lane mapping matches the code. 39/39 tests pass. `dist/` is gitignored here, so there is no compiled output to commit. Co-Authored-By: Claude Opus 5 --- .env.example | 2 +- README.md | 4 ++-- autopilot/src/routing.ts | 2 +- autopilot/test/routing.test.ts | 2 +- docs/architecture.html | 2 +- docs/architecture.md | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.env.example b/.env.example index 5404405..e250c33 100644 --- a/.env.example +++ b/.env.example @@ -2,7 +2,7 @@ # curl -H "Authorization: Bearer $QWEN_API_KEY" $QWEN_BASE_URL/models QWEN_API_KEY= QWEN_BASE_URL=https://dashscope-intl.aliyuncs.com/compatible-mode/v1 -QWEN_MODEL_FAST=qwen-flash +QWEN_MODEL_FAST=qwen-flash-2025-07-28 QWEN_MODEL_CRITICAL=qwen3.7-max # Hosted Corpus platform (the kit is a thin client of it) CORPUS_BASE_URL=https://corpuslaw.us diff --git a/README.md b/README.md index 5e0190a..28a4f7c 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ See [docs/architecture.md](docs/architecture.md) for the Mermaid source. The autopilot uses two Qwen models with different cost/quality profiles: -- **Fast lane** (`QWEN_MODEL_FAST`, default `qwen-flash`): all standard turns — +- **Fast lane** (`QWEN_MODEL_FAST`, default `qwen-flash-2025-07-28`): all standard turns — intent parsing, law search, checklist generation. Low latency, low spend. - **Critical lane** (`QWEN_MODEL_CRITICAL`, default `qwen3.7-max`): the final pre-handoff draft review only. Higher quality for the one turn that shapes the @@ -171,7 +171,7 @@ cp .env.example .env |---|---|---| | `QWEN_API_KEY` | _(required)_ | Qwen Cloud API key | | `QWEN_BASE_URL` | _(required)_ | Qwen OpenAI-compatible base URL (e.g. `https://dashscope-intl.aliyuncs.com/compatible-mode/v1`) | -| `QWEN_MODEL_FAST` | `qwen-flash` | Fast-lane model — standard turns | +| `QWEN_MODEL_FAST` | `qwen-flash-2025-07-28` | Fast-lane model — standard turns | | `QWEN_MODEL_CRITICAL` | `qwen3.7-max` | Critical-lane model — pre-handoff review | | `CORPUS_BASE_URL` | `https://corpuslaw.us` | Hosted Corpus platform base URL | | `CORPUS_API_KEY` | _(optional)_ | Corpus platform API key (optional — anonymous is rate-limited) | diff --git a/autopilot/src/routing.ts b/autopilot/src/routing.ts index 0d93ed9..6cdb0d8 100644 --- a/autopilot/src/routing.ts +++ b/autopilot/src/routing.ts @@ -3,5 +3,5 @@ export type Lane = "fast" | "critical"; export function lane(name: Lane): string { if (name === "critical") return process.env.QWEN_MODEL_CRITICAL || "qwen3.7-max"; - return process.env.QWEN_MODEL_FAST || "qwen-flash"; + return process.env.QWEN_MODEL_FAST || "qwen-flash-2025-07-28"; } diff --git a/autopilot/test/routing.test.ts b/autopilot/test/routing.test.ts index 408f4f6..3982bcd 100644 --- a/autopilot/test/routing.test.ts +++ b/autopilot/test/routing.test.ts @@ -6,7 +6,7 @@ describe("lane", () => { delete process.env.QWEN_MODEL_FAST; delete process.env.QWEN_MODEL_CRITICAL; }); - it("defaults fast lane to qwen-flash", () => expect(lane("fast")).toBe("qwen-flash")); + it("defaults fast lane to the pinned qwen-flash snapshot", () => expect(lane("fast")).toBe("qwen-flash-2025-07-28")); it("defaults critical lane to qwen3.7-max", () => expect(lane("critical")).toBe("qwen3.7-max")); it("honors env overrides", () => { process.env.QWEN_MODEL_FAST = "qwen-turbo"; diff --git a/docs/architecture.html b/docs/architecture.html index 4cd5a75..57a3c0b 100644 --- a/docs/architecture.html +++ b/docs/architecture.html @@ -98,7 +98,7 @@
model inference
-
qwen-flash
+
qwen-flash-2025-07-28
FAST lane · every turn
intent · search · checklist
diff --git a/docs/architecture.md b/docs/architecture.md index 1dee02f..ea70dfd 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -11,7 +11,7 @@ is the machine-readable equivalent. ```mermaid flowchart LR subgraph OPEN["corpus-agent-kit (Apache-2.0, this repo)"] - UI[Chat UI] --> A[Formation Autopilot
qwen-flash default lane
qwen3.7 critical lane] + UI[Chat UI] --> A[Formation Autopilot
qwen-flash-2025-07-28 default lane
qwen3.7 critical lane] A -->|search_law| MCPC[law_search tool] A --> N[NAICS lookup] A --> H[handoff builder] From 91baf12374ca0a324f442b7d0a78930240841fc6 Mon Sep 17 00:00:00 2001 From: ty Date: Sun, 20 Sep 2026 00:17:28 -0500 Subject: [PATCH 2/6] feat(plugin): package Corpus for OpenAI Codex with reproducible releases --- .agents/plugins/marketplace.json | 20 ++++ .github/workflows/ci.yml | 5 + README.md | 20 +++- docs/OPENAI_PLUGIN.md | 81 +++++++++++++++++ plugins/corpus/.codex-plugin/plugin.json | 30 ++++++ plugins/corpus/.mcp.json | 8 ++ plugins/corpus/README.md | 30 ++++-- .../references/harness-setup.md | 6 +- scripts/check-plugin-package.mjs | 17 ++++ scripts/package-openai-plugin.py | 91 +++++++++++++++++++ .../references/harness-setup.md | 6 +- 11 files changed, 300 insertions(+), 14 deletions(-) create mode 100644 .agents/plugins/marketplace.json create mode 100644 docs/OPENAI_PLUGIN.md create mode 100644 plugins/corpus/.codex-plugin/plugin.json create mode 100644 plugins/corpus/.mcp.json create mode 100644 scripts/package-openai-plugin.py diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json new file mode 100644 index 0000000..ef6315d --- /dev/null +++ b/.agents/plugins/marketplace.json @@ -0,0 +1,20 @@ +{ + "name": "corpus-agent-kit", + "interface": { + "displayName": "Corpus Agent Kit" + }, + "plugins": [ + { + "name": "corpus", + "source": { + "source": "local", + "path": "./plugins/corpus" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "Productivity" + } + ] +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d84e016..6e55700 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,3 +20,8 @@ jobs: - run: npm run build - run: npm pack --workspace mcp-server --dry-run - run: node scripts/check-plugin-package.mjs + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - run: python3 scripts/package-openai-plugin.py HEAD + - run: unzip -t dist/corpus-openai-*.zip diff --git a/README.md b/README.md index 250219b..a6e2ab3 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ Autopilot from this repo (`autopilot/`), running on Alibaba Cloud Function Compute. Describe a business in plain English; get a cited launch checklist and a prefilled formation handoff. -### 3. Drop a skill folder — or install the Hermes plugin +### 3. Drop a skill folder — or install a plugin Two portable [Agent Skills](https://agentskills.io/specification): @@ -74,7 +74,19 @@ agent learns to reach for Corpus *before any MCP connection exists* ([install paths](skills/corpus-business-formation/README.md)). Or install **both skills plus the MCP URL** as one plugin. This repo is a -Claude Code plugin marketplace: +plugin marketplace. For **OpenAI Codex**, use a local checkout or the extracted +OpenAI release archive: + +```bash +codex plugin marketplace add /absolute/path/to/corpus-agent-kit +codex plugin add corpus@corpus-agent-kit +``` + +See the [OpenAI package guide](docs/OPENAI_PLUGIN.md) for archive installation, +verification, and reproducible tagged builds. Restart Codex or open a new task +after installation so both skills and the MCP connector load. + +For **Claude Code**: ``` /plugin marketplace add teakesdev/corpus-agent-kit @@ -136,8 +148,8 @@ guessing. - **`widget/`** — embeddable law-search widget (Preact, ~12 kB gzipped). - **`skills/corpus-business-formation/`** — portable Agent Skill (formation). - **`skills/corpus-legal-research/`** — portable Agent Skill (live statute search). -- **`plugins/corpus/`** — Agent Plugins v1 package for Hermes (generated skill - copies + headerless `mcp.json`). +- **`plugins/corpus/`** — plugin wrappers for Codex, Hermes, Claude Code, and + Cursor, sharing generated skills and the headerless hosted MCP endpoint. All of these are thin clients of the hosted Corpus platform. The law corpus, hybrid search engine, human approval gate, and filing execution live in the hosted diff --git a/docs/OPENAI_PLUGIN.md b/docs/OPENAI_PLUGIN.md new file mode 100644 index 0000000..7c85d60 --- /dev/null +++ b/docs/OPENAI_PLUGIN.md @@ -0,0 +1,81 @@ +# Corpus for OpenAI Codex + +This package connects Codex to `https://corpuslaw.us/api/mcp` and includes two +skills: `corpus-legal-research` and `corpus-business-formation`. Research works +anonymously. The packaged connector has no authentication or rehearsal headers. + +## Install a local release archive + +Verify the checksum next to the archive before extracting it: + +```bash +shasum -a 256 -c corpus-openai-0.1.0.zip.sha256 +unzip corpus-openai-0.1.0.zip +codex plugin marketplace add "$(pwd)/corpus-openai-0.1.0" +codex plugin add corpus@corpus-agent-kit +``` + +Keep the extracted directory in place: it is the local marketplace source. +For a source checkout, pass the repository root to `marketplace add` instead. +These commands register and install the package locally; they do not publish it. +This package does not imply a listing in the OpenAI or ChatGPT catalog. + +Restart Codex or start a new task. Confirm that both Corpus skills are available, +then ask: **"Use Corpus to call law.list_coverage and summarize its coverage."** +That read-only call checks the live connector without starting a formation order. +Manifest validation and archive checks alone do not prove a live MCP connection. + +## What is included + +- `.agents/plugins/marketplace.json` points to `plugins/corpus`. +- `plugins/corpus/.codex-plugin/plugin.json` declares the two skills and MCP file. +- `plugins/corpus/.mcp.json` registers the hosted server as `corpus` using HTTP. +- `plugins/corpus/skills/` contains generated copies of both canonical skills, + including the vendored formation intake and harness setup references. +- The logo, this guide as `README.md`, Apache-2.0 `LICENSE`, and `release.json` + with the source ref, commit SHA, and plugin version complete the archive. + +The archive excludes the other harness wrappers, application code, dependencies, +and local configuration. It needs no Node build or local MCP server process. + +## Before using the formation skill + +The formation skill displays fixed Corpus-authored warnings about sponsored +formation offers, including a named competitor and a Corpus comparison link. +The text is vendor-authored, not an independent assessment by the agent. + +The intake collects the founder's name, email, phone, and street address. Sending +these to MCP tools or opening a populated Corpus handoff link transmits them to +Corpus. Contact details use the URL fragment in connector-free handoffs; other +fields use the query string. The skill never collects a Social Security Number. +The intake instructions are bundled in the package, not fetched at runtime. + +Creating an order or USDC payment request requires the founder to confirm the +exact amount in the current turn. USDC must come from a self-custody wallet; +refunds go to the sending wallet. Email confirmation is required within 48 hours, +and every filing still requires human approval of the exact filing payload. +`formation.handoff` alone creates no order or charge. + +## Build from a Git ref + +From a source checkout, after committing the package changes: + +```bash +python3 scripts/package-openai-plugin.py corpus-openai-v0.1.0 +``` + +The command requires an existing ref and reads only files from that commit. +It writes `dist/corpus-openai-0.1.0.zip` and its `.sha256` file. Repeating the +command with the same ref produces identical bytes, even with working-tree edits. +For CI or a pre-release check, pass `HEAD` instead. A release tag must use +`corpus-openai-v` and point at the intended release commit. +Existing tags `v0.1.0` and `corpus--v0.1.0` predate the OpenAI package and cannot +build it. Do not move those tags to retrofit the new files. + +The corrected Hermes instructions are already on `main`: plugin installs use +`hermes plugins doctor corpus --ci` followed by a fresh-chat tool check. +`hermes mcp test corpus` checks only a separately configured standalone server; +it does not validate the installed plugin's namespaced MCP server. + +Source and license: [corpus-agent-kit](https://github.com/teakesdev/corpus-agent-kit), +Apache-2.0. Building or tagging locally does not publish a GitHub release. diff --git a/plugins/corpus/.codex-plugin/plugin.json b/plugins/corpus/.codex-plugin/plugin.json new file mode 100644 index 0000000..809e7bd --- /dev/null +++ b/plugins/corpus/.codex-plugin/plugin.json @@ -0,0 +1,30 @@ +{ + "name": "corpus", + "version": "0.1.0", + "description": "US legal research and LLC or nonprofit formation intake, with cited law and human-approved filing.", + "author": { + "name": "Corpus", + "url": "https://corpuslaw.us" + }, + "homepage": "https://corpuslaw.us", + "repository": "https://github.com/teakesdev/corpus-agent-kit", + "license": "Apache-2.0", + "keywords": ["legal", "law", "formation", "llc", "nonprofit", "mcp"], + "skills": "./skills/", + "interface": { + "displayName": "Corpus", + "shortDescription": "Research US law and prepare a business formation.", + "longDescription": "Search and cite US law, or work through LLC and nonprofit formation intake. Includes two skills and the hosted Corpus MCP connector. Filing requires human approval; creating a payment request requires the founder's explicit consent.", + "developerName": "Corpus", + "category": "Productivity", + "capabilities": [], + "websiteURL": "https://corpuslaw.us", + "logo": "./assets/logo.png", + "defaultPrompt": [ + "What does Mississippi require to form an LLC?", + "Find the law and cite the official text.", + "Help me prepare a nonprofit formation intake." + ] + }, + "mcpServers": "./.mcp.json" +} diff --git a/plugins/corpus/.mcp.json b/plugins/corpus/.mcp.json new file mode 100644 index 0000000..c3a6789 --- /dev/null +++ b/plugins/corpus/.mcp.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "corpus": { + "type": "http", + "url": "https://corpuslaw.us/api/mcp" + } + } +} diff --git a/plugins/corpus/README.md b/plugins/corpus/README.md index 912feba..ae7626b 100644 --- a/plugins/corpus/README.md +++ b/plugins/corpus/README.md @@ -1,6 +1,6 @@ # corpus -Agent Plugins v1 package for [Corpus](https://corpuslaw.us): live US legal +Plugin package for [Corpus](https://corpuslaw.us): live US legal research and business-formation handoff. The **content** (the two skills + the MCP URL) is vendor-neutral. Wrappers in @@ -9,13 +9,13 @@ this directory: - Agent Plugins v1 (`plugin.json` + `mcp.json`) — Hermes - `.claude-plugin/plugin.json` — Claude Code - `.cursor-plugin/plugin.json` — Cursor / Grok Bot marketplace +- `.codex-plugin/plugin.json` + `.mcp.json` — OpenAI Codex -All three describe the same skills and the same hosted MCP URL; none generate -or own the content. Codex/ChatGPT use a fourth folder shape and reuse the same -URL. +All four describe the same skills and the same hosted MCP URL; none generate +or own the content. -The two wrappers spell the MCP transport differently on purpose: Agent Plugins -v1 uses `"type": "streamable-http"`, Claude Code uses `"type": "http"`. Same +The wrappers spell the MCP transport differently on purpose: Agent Plugins +v1 uses `"type": "streamable-http"`, Claude Code and Codex use `"type": "http"`. Same endpoint. Manifest `name` is `corpus`. Files under `skills/` are **generated copies** of @@ -31,10 +31,24 @@ the canonical skill trees at the repo root (`SKILL.md` plus any | Hermes (Agent Plugins v1) | Yes | Yes | | Claude Code (`.claude-plugin/`) | Yes | Yes | | Grok Bot / Cursor | Yes (`.cursor-plugin/`) | Yes — marketplace package or add `https://corpuslaw.us/api/mcp` (see [docs/GROK_BOT.md](../../docs/GROK_BOT.md)) | -| Codex / ChatGPT | No (own layout: `.codex-plugin/` + `.mcp.json`) | Yes — `codex mcp add corpus --url https://corpuslaw.us/api/mcp` | +| OpenAI Codex | Yes (`.codex-plugin/` + `.mcp.json`) | Yes — hosted MCP plus both skills | +| ChatGPT | No ChatGPT catalog listing claimed | Hosted MCP, where custom connectors are supported | | Any MCP-capable harness | No | Yes — same Streamable HTTP URL | -A Codex folder-adapter is a later package, not this one. Cursor/Grok listing uses `.cursor-plugin/` in this directory. +## Install (OpenAI Codex) + +From a local checkout of this repository: + +```bash +codex plugin marketplace add /absolute/path/to/corpus-agent-kit +codex plugin add corpus@corpus-agent-kit +``` + +The repository marketplace is `.agents/plugins/marketplace.json`. The OpenAI +release archive includes only that marketplace and the Codex package files. +See the [OpenAI package guide](../../docs/OPENAI_PLUGIN.md) for tagged builds, +archive installation, and validation. No API key is required. Start a new Codex +task after installation, confirm both skills load, and ask for `law.list_coverage`. ## Install (Claude Code) diff --git a/plugins/corpus/skills/corpus-business-formation/references/harness-setup.md b/plugins/corpus/skills/corpus-business-formation/references/harness-setup.md index 047afc4..599a97d 100644 --- a/plugins/corpus/skills/corpus-business-formation/references/harness-setup.md +++ b/plugins/corpus/skills/corpus-business-formation/references/harness-setup.md @@ -27,11 +27,15 @@ claude mcp add --transport http corpus https://corpuslaw.us/api/mcp # OpenAI Codex (experimental_use_rmcp_client is NO LONGER required) codex mcp add corpus --url https://corpuslaw.us/api/mcp -# Hermes (interactive; answer "no" when asked if it needs auth) +# Hermes standalone server (interactive; answer "no" when asked if it needs auth) hermes mcp add corpus --url "https://corpuslaw.us/api/mcp" hermes mcp test corpus ``` +`hermes mcp test corpus` applies only to the standalone server added above. +For the installed Hermes plugin, use `hermes plugins doctor corpus --ci`, then +start a fresh chat and ask for `law.list_coverage`; plugin MCP IDs are namespaced. + ## Config files ```jsonc diff --git a/scripts/check-plugin-package.mjs b/scripts/check-plugin-package.mjs index 0d42404..fedee9d 100644 --- a/scripts/check-plugin-package.mjs +++ b/scripts/check-plugin-package.mjs @@ -22,5 +22,22 @@ for (const [folder, transport] of [['.claude-plugin', 'http'], ['.cursor-plugin' for (const name of ['corpus-business-formation', 'corpus-legal-research']) { assert.ok(existsSync(root + 'skills/' + name + '/SKILL.md')); } +const openai = read(root + '.codex-plugin/plugin.json'); +assert.equal(openai.name, portable.name); +assert.match(openai.version, /^\d+\.\d+\.\d+$/); +assert.equal(openai.skills, './skills/'); +assert.equal(openai.mcpServers, './.mcp.json'); +assert.deepEqual(read(root + '.mcp.json'), { + mcpServers: { corpus: { type: 'http', url } }, +}, 'OpenAI MCP must use HTTP and stay headerless'); +assert.ok(existsSync(root + openai.interface.logo)); +const openaiMarket = read('.agents/plugins/marketplace.json'); +assert.equal(openaiMarket.name, 'corpus-agent-kit'); +assert.deepEqual(openaiMarket.plugins, [{ + name: 'corpus', + source: { source: 'local', path: './plugins/corpus' }, + policy: { installation: 'AVAILABLE', authentication: 'ON_INSTALL' }, + category: 'Productivity', +}]); assert.ok(existsSync(root + 'skills/corpus-business-formation/references/intake.md')); console.log('Plugin manifests, transports, marketplace entries, skills and assets agree.'); diff --git a/scripts/package-openai-plugin.py b/scripts/package-openai-plugin.py new file mode 100644 index 0000000..89c54b8 --- /dev/null +++ b/scripts/package-openai-plugin.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +"""Build an OpenAI-only, reproducible marketplace archive from a committed ref.""" + +import argparse +import hashlib +import io +import json +from pathlib import Path, PurePosixPath +import re +import subprocess +import tarfile +import zipfile + + +ROOT = Path(__file__).resolve().parent.parent +# Explicit inputs: never archive the whole checkout or copy local configuration. +INPUTS = ( + ".agents/plugins/marketplace.json", + "plugins/corpus/.codex-plugin/plugin.json", + "plugins/corpus/.mcp.json", + "plugins/corpus/skills", + "plugins/corpus/assets/logo.png", + "docs/OPENAI_PLUGIN.md", + "LICENSE", +) + + +def git(*args): + return subprocess.check_output(["git", "-C", str(ROOT), *args]) + + +def build(ref, output_dir): + commit = git("rev-parse", "--verify", "--end-of-options", ref + "^{commit}").decode().strip() + source = git("archive", "--format=tar", commit, "--", *INPUTS) + files = {} + with tarfile.open(fileobj=io.BytesIO(source)) as archive: + for member in archive: + if member.isdir(): + continue + path = PurePosixPath(member.name) + if not member.isfile() or path.is_absolute() or ".." in path.parts: + raise ValueError(f"Unsafe archive member: {member.name}") + if member.name not in INPUTS and not ( + member.name.startswith("plugins/corpus/skills/") + and (path.name == "SKILL.md" or "references" in path.parts) + and path.name != ".DS_Store" + ): + raise ValueError(f"Unexpected package file: {member.name}") + files[member.name] = archive.extractfile(member).read() + + manifest = json.loads(files["plugins/corpus/.codex-plugin/plugin.json"]) + version = manifest["version"] + if not re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+", version): + raise ValueError("Release version must be major.minor.patch") + tag = ref.removeprefix("refs/tags/") + if tag.startswith("corpus-openai-v") and tag != f"corpus-openai-v{version}": + raise ValueError("Release tag does not match the OpenAI manifest version") + for name in ("corpus-business-formation", "corpus-legal-research"): + if f"plugins/corpus/skills/{name}/SKILL.md" not in files: + raise ValueError(f"Missing skill: {name}") + files["README.md"] = files.pop("docs/OPENAI_PLUGIN.md") + files["release.json"] = (json.dumps({ + "plugin": manifest["name"], "version": version, + "ref": ref, "commit": commit, + }, indent=2) + "\n").encode() + + output_dir.mkdir(parents=True, exist_ok=True) + stem = f"corpus-openai-{version}" + destination = output_dir / f"{stem}.zip" + # Fixed metadata and no compression make bytes independent of clock and zlib. + with zipfile.ZipFile(destination, "w", compression=zipfile.ZIP_STORED) as archive: + for name, content in sorted(files.items()): + info = zipfile.ZipInfo(f"{stem}/{name}", date_time=(1980, 1, 1, 0, 0, 0)) + info.create_system = 3 + info.external_attr = 0o100644 << 16 + archive.writestr(info, content) + digest = hashlib.sha256(destination.read_bytes()).hexdigest() + destination.with_suffix(".zip.sha256").write_text(f"{digest} {destination.name}\n") + print(f"Built {destination} from {ref} ({commit})") + print(f"SHA-256: {digest}") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("ref", help="Committed Git ref, ideally corpus-openai-v") + parser.add_argument("--output-dir", type=Path, default=ROOT / "dist") + args = parser.parse_args() + try: + build(args.ref, args.output_dir) + except (subprocess.CalledProcessError, ValueError, KeyError) as error: + parser.exit(1, f"Package build failed: {error}\n") diff --git a/skills/corpus-business-formation/references/harness-setup.md b/skills/corpus-business-formation/references/harness-setup.md index d6377c0..ba035ce 100644 --- a/skills/corpus-business-formation/references/harness-setup.md +++ b/skills/corpus-business-formation/references/harness-setup.md @@ -26,11 +26,15 @@ claude mcp add --transport http corpus https://corpuslaw.us/api/mcp # OpenAI Codex (experimental_use_rmcp_client is NO LONGER required) codex mcp add corpus --url https://corpuslaw.us/api/mcp -# Hermes (interactive; answer "no" when asked if it needs auth) +# Hermes standalone server (interactive; answer "no" when asked if it needs auth) hermes mcp add corpus --url "https://corpuslaw.us/api/mcp" hermes mcp test corpus ``` +`hermes mcp test corpus` applies only to the standalone server added above. +For the installed Hermes plugin, use `hermes plugins doctor corpus --ci`, then +start a fresh chat and ask for `law.list_coverage`; plugin MCP IDs are namespaced. + ## Config files ```jsonc From c212cbe9349c288e213699e1529e7aa817678ab8 Mon Sep 17 00:00:00 2001 From: ty Date: Sat, 12 Sep 2026 14:05:12 -0500 Subject: [PATCH 3/6] docs(grok-bot): private rehearsal header; marketplace plugin stays headerless Public MCP remains 8 tools. formation.checkout is gated, not missing. X-Corpus-Rehearsal belongs on a private Grok Bot connector only. No canary claimed. --- WORKLOG.md | 38 ++++++++++++++++++++++++++++++++++++++ docs/GROK_BOT.md | 32 ++++++++++++++++++++++++++++++++ plugins/corpus/README.md | 4 ++++ 3 files changed, 74 insertions(+) diff --git a/WORKLOG.md b/WORKLOG.md index aadf5b6..3151db1 100644 --- a/WORKLOG.md +++ b/WORKLOG.md @@ -87,3 +87,41 @@ Validation: 39 autopilot + 6 bridge tests, typechecks, builds, packaging and Hermes validate/doctor pass. Clean temporary Hermes home installed the pinned plugin over SSH after HTTPS timed out; runtime discovery/live call evidence is recorded with the Corpus release-readiness report. No paid transaction performed. + +## 2026-09-12 — agent-checkout rehearsal split (docs only) + +### Verified live on corpuslaw.us (no deploy this session) +- Public headerless `https://corpuslaw.us/api/mcp` still lists **8 tools**: + `law.search`, `law.get_node`, `law.list_coverage`, `account.status`, + `formation.requirements`, `formation.compare`, `formation.lookup_naics`, + `formation.handoff`. +- `formation.checkout` / `formation.payment_status` exist on the server but are + gated: without `X-Corpus-Rehearsal`, `tools/call` returns + `agent_checkout_disabled` (**not** `Unknown tool`). Production flag = `rehearsal`. + +### Decisions +- Rehearsal is a **private connector header** only: `X-Corpus-Rehearsal` with the + token stored in Vercel as `FORMATION_AGENT_CHECKOUT_REHEARSAL_TOKEN`. The token + is never written into this repo. +- Marketplace / published plugin stays **headerless**: `plugins/corpus/mcp.json` + and `plugins/corpus/.cursor-plugin/plugin.json` remain URL-only (no + `Authorization`, no `X-Corpus-Rehearsal`). Both re-read and confirmed unchanged. +- Public demo path unchanged — stops at the `formation.handoff` link. The two + checkout tools stay out of the public skill tool table until the public flag is on. +- GATE 2 unchanged: never auto-file. Rehearsal covers spend, not filing. +- **No USDC canary run and none claimed.** + +### Files changed +- `docs/GROK_BOT.md` — 8-tool public install kept; new "Rehearsal connector (not + the marketplace plugin)" section; 2026-09-12 re-verification line +- `plugins/corpus/README.md` — one sentence: marketplace MCP headerless on + purpose, rehearsal is a private connector header +- `WORKLOG.md` — this entry + +### Not done (deliberately) +- No deploy. No manifest/header edits. No `SKILL.md` formation-procedure change. +- Env files remain absent by design. + +### Next +- Flip the public flag (server-side) before the two checkout tools go into the + public skill tool table; re-verify the public tool list at that point. diff --git a/docs/GROK_BOT.md b/docs/GROK_BOT.md index dd9e26f..1bce664 100644 --- a/docs/GROK_BOT.md +++ b/docs/GROK_BOT.md @@ -28,6 +28,38 @@ Verified dry-run (2026-09-10): `formation.requirements` for MS LLC returns the live checklist and all-in pricing; `formation.handoff` returns **COMPLETE** and a prefilled approval link. Agents never auto-file (GATE 2 human approval). USDC pay is email-confirmed; filing still needs human approval. Sept 15 demo stops at the handoff link — USDC pay is optional. +Re-verified (2026-09-12): the public headerless endpoint still lists the same +eight tools. + +## Rehearsal connector (not the marketplace plugin) + +`formation.checkout` and `formation.payment_status` exist on the hosted server +but are gated. They do **not** appear in the public headerless tool list, and a +`tools/call` without the rehearsal header returns `agent_checkout_disabled` — +not `Unknown tool`. Production runs the flag at `rehearsal`. + +To rehearse agent checkout, add the header on a **private** Grok Bot connector +only: + +``` +X-Corpus-Rehearsal: +``` + +The token is stored in Vercel as `FORMATION_AGENT_CHECKOUT_REHEARSAL_TOKEN`. +Never write it into this repo, a bot template, or a marketplace manifest. + +Rules: + +- The marketplace / published plugin stays **headerless**: + `plugins/corpus/mcp.json` and `plugins/corpus/.cursor-plugin/plugin.json` are + URL-only — no `Authorization`, no `X-Corpus-Rehearsal`. +- If the checkout tools are missing, or a call returns `agent_checkout_disabled`, + **stop** and use the `formation.handoff` approval link. Do not invent a payment + step or a pay tool id. +- GATE 2 is unchanged: never auto-file. Rehearsal covers spend, not filing. +- The public demo path still stops at the handoff link, and the two checkout + tools stay out of the public skill tool table until the public flag is on. + ## Install skills (optional, richer intake) Copy from this repo into your Grok Bot / Cursor skills library: diff --git a/plugins/corpus/README.md b/plugins/corpus/README.md index 912feba..ab4bf7d 100644 --- a/plugins/corpus/README.md +++ b/plugins/corpus/README.md @@ -74,6 +74,10 @@ cannot expand to `Bearer ` and break anonymous use. Formation stays free; set a key under Plugins → Configure / connector settings when you want higher research quota. +Marketplace MCP stays headerless on purpose — the agent-checkout rehearsal +header (`X-Corpus-Rehearsal`) belongs on a private connector only, never in this +package (see [docs/GROK_BOT.md](../../docs/GROK_BOT.md)). + ## Install (Grok Bot / Cursor) Add the hosted MCP as a remote connector (no clone required): From a6b1b039723e34dc9ed728f824290531e9f1043d Mon Sep 17 00:00:00 2001 From: ty Date: Sun, 20 Sep 2026 11:06:38 -0500 Subject: [PATCH 4/6] docs: reconcile rehearsal guidance with public checkout --- WORKLOG.md | 14 +++++++++++++- docs/GROK_BOT.md | 19 ++++++++++--------- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/WORKLOG.md b/WORKLOG.md index 3151db1..d9fb95e 100644 --- a/WORKLOG.md +++ b/WORKLOG.md @@ -88,7 +88,10 @@ Hermes validate/doctor pass. Clean temporary Hermes home installed the pinned plugin over SSH after HTTPS timed out; runtime discovery/live call evidence is recorded with the Corpus release-readiness report. No paid transaction performed. -## 2026-09-12 — agent-checkout rehearsal split (docs only) +## 2026-09-12 — agent-checkout rehearsal split (historical observation) + +Superseded by the ten-tool public checkout release recorded on 2026-09-19. +The observations and next steps below describe September 12 only. ### Verified live on corpuslaw.us (no deploy this session) - Public headerless `https://corpuslaw.us/api/mcp` still lists **8 tools**: @@ -125,3 +128,12 @@ recorded with the Corpus release-readiness report. No paid transaction performed ### Next - Flip the public flag (server-side) before the two checkout tools go into the public skill tool table; re-verify the public tool list at that point. + + +## 2026-09-20 — merge readiness audit + +Integrated the OpenAI plugin package and private rehearsal-header guidance. +Corrected the latter's stale eight-tool/public-disabled claims before landing; +current public checkout documentation remains the ten-tool contract. Original +session branches and worktrees are preserved. No release tag, package publication, +paid request, flag change, or production deployment is part of this integration. diff --git a/docs/GROK_BOT.md b/docs/GROK_BOT.md index 1bce664..79f0f9f 100644 --- a/docs/GROK_BOT.md +++ b/docs/GROK_BOT.md @@ -28,15 +28,15 @@ Verified dry-run (2026-09-10): `formation.requirements` for MS LLC returns the live checklist and all-in pricing; `formation.handoff` returns **COMPLETE** and a prefilled approval link. Agents never auto-file (GATE 2 human approval). USDC pay is email-confirmed; filing still needs human approval. Sept 15 demo stops at the handoff link — USDC pay is optional. -Re-verified (2026-09-12): the public headerless endpoint still lists the same -eight tools. +## Private rehearsal connector -## Rehearsal connector (not the marketplace plugin) +The public endpoint now lists ten tools, including `formation.checkout` and +`formation.payment_status`. The eight-tool rehearsal-only state observed on +2026-09-12 is historical; it is not the current public install contract. -`formation.checkout` and `formation.payment_status` exist on the hosted server -but are gated. They do **not** appear in the public headerless tool list, and a -`tools/call` without the rehearsal header returns `agent_checkout_disabled` — -not `Unknown tool`. Production runs the flag at `rehearsal`. +If a deployment is explicitly configured in rehearsal mode, its checkout tools +require a private connector header. This is a deployment-specific testing setup, +not a prerequisite for the public marketplace plugin. To rehearse agent checkout, add the header on a **private** Grok Bot connector only: @@ -57,8 +57,9 @@ Rules: **stop** and use the `formation.handoff` approval link. Do not invent a payment step or a pay tool id. - GATE 2 is unchanged: never auto-file. Rehearsal covers spend, not filing. -- The public demo path still stops at the handoff link, and the two checkout - tools stay out of the public skill tool table until the public flag is on. +- A handoff creates no order or payment request. Public checkout requires + the founder to confirm the exact amount in the current turn; filing still + requires separate human approval. ## Install skills (optional, richer intake) From 0725eaccde427abb959009442bdb58d84f3e8e90 Mon Sep 17 00:00:00 2001 From: ty Date: Sun, 20 Sep 2026 11:10:40 -0500 Subject: [PATCH 5/6] docs: record agent-kit integration validation --- WORKLOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/WORKLOG.md b/WORKLOG.md index d9fb95e..f99e9ce 100644 --- a/WORKLOG.md +++ b/WORKLOG.md @@ -137,3 +137,16 @@ Corrected the latter's stale eight-tool/public-disabled claims before landing; current public checkout documentation remains the ten-tool contract. Original session branches and worktrees are preserved. No release tag, package publication, paid request, flag change, or production deployment is part of this integration. + +Also integrated the older `chore/pin-qwen-flash-snapshot` branch: fast-lane +model defaults to `qwen-flash-2025-07-28`, with environment overrides preserved. +Alibaba's model documentation still identifies that snapshot as equivalent to +`qwen-flash`: https://help.aliyun.com/zh/model-studio/qwen-flash (checked 2026-09-20). +No inference request was made during this audit. + +Validation on the combined code: 39 autopilot + 6 bridge tests passed, all +workspace typechecks/builds and plugin checks passed. OpenAI archive integrity +passed; repeated builds of the same ref matched byte-for-byte. GitHub CI passed +at `52d8397`. Older Cursor and Grok work is already present (with later copy +corrections); initialize and canonical law.search fixes are already ancestors of +main. Existing peer branches/checkouts remain untouched. From 790473f0f137f2f8dfba05623e588821147c50f0 Mon Sep 17 00:00:00 2001 From: ty Date: Sun, 20 Sep 2026 11:14:01 -0500 Subject: [PATCH 6/6] fix(plugin): validate release tags and gate reproducible archives --- .github/workflows/ci.yml | 3 ++ WORKLOG.md | 6 +++ scripts/package-openai-plugin.py | 5 +- scripts/tests/test_package_openai_plugin.py | 59 +++++++++++++++++++++ 4 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 scripts/tests/test_package_openai_plugin.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6e55700..a257e45 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,5 +23,8 @@ jobs: - uses: actions/setup-python@v5 with: python-version: '3.12' + - run: python3 -m unittest discover -s scripts/tests -p 'test_*.py' - run: python3 scripts/package-openai-plugin.py HEAD + - run: python3 scripts/package-openai-plugin.py HEAD --output-dir dist/repeat + - run: cmp dist/corpus-openai-*.zip dist/repeat/corpus-openai-*.zip - run: unzip -t dist/corpus-openai-*.zip diff --git a/WORKLOG.md b/WORKLOG.md index f99e9ce..6ab2aa9 100644 --- a/WORKLOG.md +++ b/WORKLOG.md @@ -150,3 +150,9 @@ passed; repeated builds of the same ref matched byte-for-byte. GitHub CI passed at `52d8397`. Older Cursor and Grok work is already present (with later copy corrections); initialize and canonical law.search fixes are already ancestors of main. Existing peer branches/checkouts remain untouched. + +Greptile's PR review found two packaging gaps. Release tag validation now checks +Git's resolved tag ref, including differently named tags; isolated repository +tests cover invalid names/versions, a valid annotated tag, and byte stability +despite working-tree edits (3 tests passed). CI now builds twice and compares +archives. These fixes do not touch user tags or publish a release. diff --git a/scripts/package-openai-plugin.py b/scripts/package-openai-plugin.py index 89c54b8..61e0dbe 100644 --- a/scripts/package-openai-plugin.py +++ b/scripts/package-openai-plugin.py @@ -52,8 +52,9 @@ def build(ref, output_dir): version = manifest["version"] if not re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+", version): raise ValueError("Release version must be major.minor.patch") - tag = ref.removeprefix("refs/tags/") - if tag.startswith("corpus-openai-v") and tag != f"corpus-openai-v{version}": + resolved_ref = git("rev-parse", "--symbolic-full-name", "--verify", "--end-of-options", ref).decode().strip() + tag = resolved_ref.removeprefix("refs/tags/") + if resolved_ref.startswith("refs/tags/") and tag != f"corpus-openai-v{version}": raise ValueError("Release tag does not match the OpenAI manifest version") for name in ("corpus-business-formation", "corpus-legal-research"): if f"plugins/corpus/skills/{name}/SKILL.md" not in files: diff --git a/scripts/tests/test_package_openai_plugin.py b/scripts/tests/test_package_openai_plugin.py new file mode 100644 index 0000000..f84c104 --- /dev/null +++ b/scripts/tests/test_package_openai_plugin.py @@ -0,0 +1,59 @@ +"""Exercise packaging against an isolated Git repository, never the user's tags.""" +import importlib.util +import json +from pathlib import Path +import subprocess +import tempfile +import unittest + +spec = importlib.util.spec_from_file_location("package_plugin", Path(__file__).parents[1] / "package-openai-plugin.py") +package = importlib.util.module_from_spec(spec) +spec.loader.exec_module(package) + + +class PackageTest(unittest.TestCase): + def setUp(self): + self.temp = tempfile.TemporaryDirectory() + self.addCleanup(self.temp.cleanup) + self.root = Path(self.temp.name) + original_root = package.ROOT + self.addCleanup(setattr, package, "ROOT", original_root) + package.ROOT = self.root + self.git("init", "-q") + self.git("config", "user.name", "Test") + self.git("config", "user.email", "test@example.invalid") + for name in package.INPUTS: + if name.endswith("/skills"): + for skill in ("corpus-business-formation", "corpus-legal-research"): + self.write(f"{name}/{skill}/SKILL.md", "test skill") + else: + self.write(name, json.dumps({"name": "corpus", "version": "0.1.0"})) + self.git("add", ".") + self.git("commit", "-qm", "fixture") + + def git(self, *args): + subprocess.run(["git", "-C", str(self.root), *args], check=True, capture_output=True) + + def write(self, name, text): + path = self.root / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(text) + + def test_head_is_reproducible_and_ignores_working_tree(self): + package.build("HEAD", self.root / "first") + self.write("LICENSE", "uncommitted edit") + package.build("HEAD", self.root / "second") + self.assertEqual((self.root / "first/corpus-openai-0.1.0.zip").read_bytes(), + (self.root / "second/corpus-openai-0.1.0.zip").read_bytes()) + + def test_valid_annotated_release_tag(self): + self.git("tag", "-a", "corpus-openai-v0.1.0", "-m", "release") + package.build("refs/tags/corpus-openai-v0.1.0", self.root / "out") + self.assertTrue((self.root / "out/corpus-openai-0.1.0.zip").exists()) + + def test_rejects_wrong_name_and_wrong_version_tags(self): + for tag in ("v0.2.0", "corpus-openai-v0.2.0"): + self.git("tag", tag) + for ref in (tag, f"refs/tags/{tag}"): + with self.subTest(ref=ref), self.assertRaisesRegex(ValueError, "Release tag"): + package.build(ref, self.root / "out")