Skip to content

feat: drafting CLI, preview deploys, DTPR Claude Code plugin (P3)#266

Merged
pichot merged 6 commits intomainfrom
feat/dtpr-drafting-skills
Apr 17, 2026
Merged

feat: drafting CLI, preview deploys, DTPR Claude Code plugin (P3)#266
pichot merged 6 commits intomainfrom
feat/dtpr-drafting-skills

Conversation

@pichot
Copy link
Copy Markdown
Member

@pichot pichot commented Apr 17, 2026

Summary

Closes Phase P3 of the parent DTPR API plan (docs/plans/2026-04-16-001-feat-dtpr-api-mcp-plan.md). Ships five units on top of the read-side REST + MCP that landed in #262 / #265.

  • Unit 1 — schema drafting CLI. pnpm schema:new <type> <YYYY-MM-DD-beta> copies the newest existing version into a new beta directory and rewrites meta.yaml with a sentinel content_hash that schema:build stamps on emit. pnpm schema:promote <type>@<date>-beta validates the beta, renames the directory to its stable form, rewrites the manifest, and creates a schema/promote-<type>-<date> branch with the rename committed — ready for the user to push and PR. Both are pure-function-plus-thin-fs-wrapper and tested against a 2-cat / 2-el tmpdir fixture (11 new tests).
  • Unit 2 — preview deploy + noindex. .github/workflows/api-preview-deploy.yaml is label-gated on schema:preview, mirrors the prod workflow shape, and deploys via wrangler --env preview + an atomic R2 upload to dtpr-api-preview. A new noindex Hono middleware reads c.env.ENVIRONMENT at request time and stamps X-Robots-Tag: noindex, nofollow only when the preview env sets the var, so production responses are untouched. Preview deploy dry-runs cleanly; 203/203 worker tests stay green.
  • Unit 3 — Claude Code plugin scaffold. plugin/dtpr/ ships a .claude-plugin/plugin.json, a .mcp.json that registers api.dtpr.io/mcp as a remote HTTP MCP, and a README.md. .claude-plugin/marketplace.json at repo root lets users install via /plugin marketplace add Helpful-Places/dtpr + /plugin install dtpr.
  • Units 4 + 5 — agent skills. dtpr-describe-system turns a natural-language AI-system description into a schema-validated DTPR datachain, driving the 7 MCP tools through a 5-phase workflow. dtpr-schema-brainstorm stress-tests the current taxonomy against a novel scenario and emits a concrete schema-edit proposal ending with the schema:new command line for the user to run (the skill never shells out or touches api/schemas/). Skill bodies reference MCP tool names in prose only; the MCP server owns the tool descriptions.
  • Eval harness. plugin/dtpr/evals/verify.mjs is an offline conformance check invoked by pnpm test:plugin and the new .github/workflows/plugin-test.yaml. It validates frontmatter on each SKILL.md, eval JSON shape, and cross-references backticked tool-name tokens against api/src/mcp/tools.ts so skill bodies can't silently drift when MCP tools are renamed upstream. Each skill ships 5 should-trigger + 5 should-not-trigger prompts, with sibling-skill prompts used as negatives so routing between the two skills is exercised both ways.

Test plan

  • pnpm --filter ./api typecheck clean
  • pnpm --filter ./api test:cli — 26/26 pass (CLI + migration)
  • pnpm --filter ./api test:workers — 203/203 pass (REST + MCP + middleware, incl. new noindex tests)
  • node plugin/dtpr/evals/verify.mjs — 2 skills, 2 eval sets, 7 MCP tools pass
  • wrangler deploy --env preview --dry-run succeeds with the new ENVIRONMENT binding
  • Manual preview deploy: apply schema:preview label to a throwaway PR → workflow runs → curl -sI https://api-preview.dtpr.io/healthz returns X-Robots-Tag: noindex, nofollow
  • Manual plugin install: /plugin marketplace add Helpful-Places/dtpr + /plugin install dtpr/mcp lists dtprdescribe our facial-recognition parking kiosk as a DTPR datachain produces a validate_datachain: ok:true response

Plan: docs/plans/2026-04-17-001-feat-dtpr-drafting-skills-plan.md.

pichot added 5 commits April 17, 2026 13:58
- schema:new <type> <date-beta> copies the newest existing version
  into a new beta directory and rewrites meta.yaml with beta status,
  fresh created_at, and a sentinel content_hash stamped by schema:build.
  Source resolution picks the newest date; same date prefers stable.

- schema:promote <type>@<date>-beta validates the beta, renames the
  directory, rewrites meta.yaml as stable, and creates a git branch
  schema/promote-<type>-<date> with the rename staged and committed —
  ready for the user to push and open a PR.

Covers parent plan R21 + R23. Also lands the plan doc that reorganizes
parent Units 13-15 into this drafting-skills plan.
- api-preview-deploy.yaml runs on pull_request (labeled + synchronize)
  and is gated by the `schema:preview` label so unrelated PRs never
  trigger a deploy. Mirrors the prod workflow shape but uses the
  _PREVIEW token + R2 bucket and deploys via `wrangler --env preview`.

- noindex middleware registers unconditionally on every route; at
  request time it checks `c.env.ENVIRONMENT === 'preview'` and adds
  `X-Robots-Tag: noindex, nofollow` only then. Prod never sets the
  var, so prod responses are untouched.

- wrangler.jsonc env.preview now carries `vars.ENVIRONMENT: "preview"`.
  worker-configuration.d.ts is regenerated so the new binding typechecks.

- api/docs/preview-deployments.md documents the label, required
  secrets, and verification workflow.
… (Unit 3)

- plugin/dtpr/.claude-plugin/plugin.json: plugin manifest.
- plugin/dtpr/.mcp.json: registers remote HTTP MCP at api.dtpr.io/mcp
  with a static User-Agent header so the Worker can attribute plugin
  traffic separately from generic MCP clients.
- plugin/dtpr/README.md: install, skills summary, troubleshoot.
- .claude-plugin/marketplace.json: top-level marketplace listing with
  plugin/dtpr/ as a local-path plugin. Users install with
  `/plugin marketplace add Helpful-Places/dtpr` + `/plugin install dtpr`.
- root README.md: short "Claude Code plugin" section pointing at the
  new plugin directory.

Skills and eval harness (Units 4 + 5) land in follow-up commits.
- skills/dtpr-describe-system/SKILL.md: 5-phase workflow for turning a
  natural-language AI-system description into a validated DTPR
  datachain. References the 7 MCP tools by name in workflow prose; the
  MCP server owns tool-level documentation via Zod .describe() text.

- evals/describe-system.evals.json: 5 should-trigger prompts (varied
  domains: parking, library, 311, HR, gunshot detector) + 5
  should-not-trigger (including one should-trigger prompt for the
  sibling brainstorm skill, so the two skills don't collide).

- evals/verify.mjs: offline conformance check that validates SKILL.md
  frontmatter, eval JSON shape, and cross-references backticked tool
  names in the skill body against api/src/mcp/tools.ts — catches drift
  when MCP tools are renamed upstream.

- root test:plugin script + .github/workflows/plugin-test.yaml so the
  conformance check runs on every PR that touches plugin/, the
  marketplace manifest, or the MCP tool registry.
- skills/dtpr-schema-brainstorm/SKILL.md: 4-phase workflow for
  gap-analyzing the DTPR taxonomy against a novel scenario and
  producing a concrete schema-edit proposal. Does not invoke the CLI
  or modify api/schemas/ — the proposal ends with a `schema:new`
  command line for the user to run.

- evals/schema-brainstorm.evals.json: 5 should-trigger prompts (LLM
  hallucination, generative output, accountable deep-dive, third-party
  processor, retiring cloud_storage) + 5 should-not-trigger including
  a describe-system prompt so routing between the sibling skills is
  exercised both ways.

- verify.mjs (Unit 4) already iterates all skills and eval sets, so
  no extension needed here.
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Apr 17, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
dtpr-docs 56644a0 Apr 17 2026, 01:14 PM

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 17, 2026

Greptile Summary

Ships five units on top of the existing read-side REST + MCP: a schema:new / schema:promote drafting CLI with git integration, a preview-deploy workflow with noindex middleware, and a Claude Code plugin with two agent skills plus an offline conformance checker. The implementation is well-structured and heavily tested, with one P1 correctness gap in the promote command.

  • P1 — promote.ts: rename(betaDir, stableDir) and the meta.yaml rewrite are applied before git checkout -b branch. If that git call fails (e.g. the named branch already exists from a prior partial run), the function throws with the filesystem already mutated and no rollback — contradicting the JSDoc contract that lists "fails without mutating anything" for all failure conditions.

Confidence Score: 4/5

Safe to merge after addressing the promote rollback gap; P2s are non-blocking.

One P1 correctness issue in promote.tsgit checkout -b can fail after the filesystem has already been mutated, leaving an inconsistent state with no recovery path. The remaining findings are P2 (stale hardcoded schema version in the workflow, missing skill-name cross-reference in the eval checker).

api/cli/commands/promote.ts — missing branch-existence pre-check before the point of no return (rename).

Important Files Changed

Filename Overview
api/cli/commands/promote.ts Schema promote command — renames beta to stable, rewrites meta.yaml, creates git branch/commit; missing pre-rename branch-existence check means a git checkout -b failure after the rename leaves the filesystem in an inconsistent state with no rollback.
api/cli/commands/new.ts Schema draft command — copies newest version into beta dir, rewrites meta.yaml with sentinel hash; clean pure-function design with correct overwrite guard.
api/src/middleware/noindex.ts Post-response middleware that stamps X-Robots-Tag: noindex, nofollow only when ENVIRONMENT === "preview"; correct Hono pattern, well-tested.
.github/workflows/api-preview-deploy.yaml Label-gated preview deploy workflow with typecheck, test, schema build, and smoke-test steps; SCHEMA_VERSION is hardcoded and will break when the referenced beta is promoted.
plugin/dtpr/evals/verify.mjs Offline conformance checker for skill frontmatter, eval JSON shape, and MCP tool name drift; doesn't verify that data.skill in each eval set references an existing skill directory.
api/src/app.ts Hono app wiring — adds noindex() middleware to the stack; correct position and order relative to CORS, request-id, and logging.
.github/workflows/plugin-test.yaml Plugin conformance CI — runs npm run test:plugin (pure Node builtins, no install needed); correct path triggers covering plugin/, .claude-plugin/, and api/src/mcp/tools.ts.
plugin/dtpr/skills/dtpr-describe-system/SKILL.md 5-phase workflow skill for building schema-validated DTPR datachains; well-structured with clear phase tables, tool reference, and retry-cap for validation loops.
plugin/dtpr/skills/dtpr-schema-brainstorm/SKILL.md 4-phase taxonomy-brainstorming skill with explicit non-goal of modifying files; correct skill boundary with sibling routing guidance.
api/cli/bin.ts CLI entry point adding new and promote subcommands with usage-error exit codes (2) consistent with existing build/validate pattern.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[schema:promote type@date-beta] --> B{stable dir exists?}
    B -- yes --> FAIL1[return ok:false\nstable is immutable]
    B -- no --> C[validateCmd]
    C -- fail --> FAIL2[return ok:false\nvalidation failed]
    C -- ok --> D{skipGit?}
    D -- yes --> RENAME
    D -- no --> E[git rev-parse\nis-inside-work-tree]
    E -- no --> FAIL3[return ok:false\nnot a git repo]
    E -- yes --> F[git status --porcelain]
    F -- dirty --> FAIL4[return ok:false\nworking tree dirty]
    F -- clean --> RENAME
    RENAME[rename betaDir to stableDir]
    RENAME --> WRITE[writeFile meta.yaml status: stable]
    WRITE --> G{skipGit?}
    G -- yes --> OK
    G -- no --> H[git checkout -b branch]
    H -- fail --> ERR[throws — no rollback]
    H -- ok --> I[git add -A]
    I --> J[git commit]
    J --> OK[return ok:true]
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: api/cli/commands/promote.ts
Line: 110-139

Comment:
**No rollback if `git checkout -b` fails after the rename**

`rename(betaDir, stableDir)` and `writeFile(metaPath, ...)` are applied at lines 111–127, but `git checkout -b branch` (line 132) can fail with exit 128 ("branch already exists") if the named branch was created by a prior partial run. When that happens, `gitRun` rejects, the error propagates uncaught, and the caller gets a thrown exception — not a `PromoteResult` — while the filesystem is already in the renamed stable state with no beta to go back to.

The JSDoc contract says "Fails (without mutating anything) if…" but that guarantee only covers the pre-rename checks. A re-entry scenario — user deleted the stable dir to retry, branch still present locally — will corrupt the working tree.

Consider checking for an existing branch **before** the rename, or adding a `try/finally` that calls `rename(stableDir, betaDir)` on failure:

```ts
// Before rename: pre-check the branch name
if (!options.skipGit) {
  const branchExists = await gitOk(['rev-parse', '--verify', `refs/heads/${branch}`], gitRoot)
  if (branchExists) {
    err(`error: branch '${branch}' already exists. Delete it with 'git branch -D ${branch}' before re-promoting.`)
    return { ok: false, betaVersion: beta.canonical }
  }
}
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: .github/workflows/api-preview-deploy.yaml
Line: 18-21

Comment:
**Hardcoded `SCHEMA_VERSION` will go stale**

`SCHEMA_VERSION: ai@2026-04-16-beta` is baked into the workflow. Once this beta is promoted to stable (the directory is deleted and replaced by `2026-04-16/`), the `schema:build` step will fail on every labeled PR because the version no longer exists in `api/schemas/`. Consider deriving it dynamically or accepting it as a `workflow_dispatch` input so it can be overridden without touching the workflow file.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: plugin/dtpr/evals/verify.mjs
Line: 86-117

Comment:
**`verifyEvalSet` doesn't cross-reference `data.skill` against actual skill directories**

Every eval file has a `"skill"` field (e.g. `"skill": "dtpr-describe-system"`), but `verifyEvalSet` only validates the JSON structure — it never checks that `data.skill` matches a directory under `SKILLS_DIR`. Renaming or deleting a skill would orphan its eval set silently; `verify.mjs` would still report success.

A one-liner after the `JSON.parse` would close the gap:

```js
if (typeof data.skill === 'string' && !skillDirNames.has(data.skill)) {
  fail(`${evalPath}: 'skill' field '${data.skill}' does not match any skill directory in ${SKILLS_DIR}.`)
}
```

where `skillDirNames` is the set of directory names collected in `main()` before `verifyEvalSet` is called.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "feat(plugin): dtpr-schema-brainstorm ski..." | Re-trigger Greptile

Comment on lines +110 to +139
// Rename the dir on disk.
await rename(betaDir, stableDir)

// Rewrite meta.yaml in the new stable dir.
const metaPath = join(stableDir, 'meta.yaml')
const existingRaw = await readFile(metaPath, 'utf8')
const existing = yaml.load(existingRaw, { schema: yaml.JSON_SCHEMA }) as Record<string, unknown>

const manifest = {
version: stableCanonical,
status: 'stable' as const,
created_at: existing.created_at,
notes: `Promoted from ${beta.canonical}`,
content_hash: existing.content_hash,
locales: existing.locales,
}
SchemaManifestSchema.parse(manifest)
await writeFile(metaPath, toYaml(manifest), 'utf8')

const branch = `schema/promote-${beta.type}-${beta.date}`
if (!options.skipGit) {
// Create the branch, stage the rename, commit.
await gitRun(['checkout', '-b', branch], gitRoot)
// `git add -A <path>` picks up both the new tree and the removed beta.
await gitRun(['add', '-A', join('api', 'schemas', beta.type)], gitRoot)
await gitRun(
['commit', '-m', `promote schema ${stableCanonical}`],
gitRoot,
)
}
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 No rollback if git checkout -b fails after the rename

rename(betaDir, stableDir) and writeFile(metaPath, ...) are applied at lines 111–127, but git checkout -b branch (line 132) can fail with exit 128 ("branch already exists") if the named branch was created by a prior partial run. When that happens, gitRun rejects, the error propagates uncaught, and the caller gets a thrown exception — not a PromoteResult — while the filesystem is already in the renamed stable state with no beta to go back to.

The JSDoc contract says "Fails (without mutating anything) if…" but that guarantee only covers the pre-rename checks. A re-entry scenario — user deleted the stable dir to retry, branch still present locally — will corrupt the working tree.

Consider checking for an existing branch before the rename, or adding a try/finally that calls rename(stableDir, betaDir) on failure:

// Before rename: pre-check the branch name
if (!options.skipGit) {
  const branchExists = await gitOk(['rev-parse', '--verify', `refs/heads/${branch}`], gitRoot)
  if (branchExists) {
    err(`error: branch '${branch}' already exists. Delete it with 'git branch -D ${branch}' before re-promoting.`)
    return { ok: false, betaVersion: beta.canonical }
  }
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: api/cli/commands/promote.ts
Line: 110-139

Comment:
**No rollback if `git checkout -b` fails after the rename**

`rename(betaDir, stableDir)` and `writeFile(metaPath, ...)` are applied at lines 111–127, but `git checkout -b branch` (line 132) can fail with exit 128 ("branch already exists") if the named branch was created by a prior partial run. When that happens, `gitRun` rejects, the error propagates uncaught, and the caller gets a thrown exception — not a `PromoteResult` — while the filesystem is already in the renamed stable state with no beta to go back to.

The JSDoc contract says "Fails (without mutating anything) if…" but that guarantee only covers the pre-rename checks. A re-entry scenario — user deleted the stable dir to retry, branch still present locally — will corrupt the working tree.

Consider checking for an existing branch **before** the rename, or adding a `try/finally` that calls `rename(stableDir, betaDir)` on failure:

```ts
// Before rename: pre-check the branch name
if (!options.skipGit) {
  const branchExists = await gitOk(['rev-parse', '--verify', `refs/heads/${branch}`], gitRoot)
  if (branchExists) {
    err(`error: branch '${branch}' already exists. Delete it with 'git branch -D ${branch}' before re-promoting.`)
    return { ok: false, betaVersion: beta.canonical }
  }
}
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +18 to +21
env:
SCHEMA_VERSION: ai@2026-04-16-beta
steps:
- uses: actions/checkout@v4
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 Hardcoded SCHEMA_VERSION will go stale

SCHEMA_VERSION: ai@2026-04-16-beta is baked into the workflow. Once this beta is promoted to stable (the directory is deleted and replaced by 2026-04-16/), the schema:build step will fail on every labeled PR because the version no longer exists in api/schemas/. Consider deriving it dynamically or accepting it as a workflow_dispatch input so it can be overridden without touching the workflow file.

Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/api-preview-deploy.yaml
Line: 18-21

Comment:
**Hardcoded `SCHEMA_VERSION` will go stale**

`SCHEMA_VERSION: ai@2026-04-16-beta` is baked into the workflow. Once this beta is promoted to stable (the directory is deleted and replaced by `2026-04-16/`), the `schema:build` step will fail on every labeled PR because the version no longer exists in `api/schemas/`. Consider deriving it dynamically or accepting it as a `workflow_dispatch` input so it can be overridden without touching the workflow file.

How can I resolve this? If you propose a fix, please make it concise.

Comment thread plugin/dtpr/evals/verify.mjs Outdated
Comment on lines +86 to +117
function verifyEvalSet(evalFile) {
const evalPath = join(EVALS_DIR, evalFile)
let data
try {
data = JSON.parse(readFileSync(evalPath, 'utf8'))
} catch (e) {
fail(`${evalPath}: invalid JSON (${e.message}).`)
return
}
for (const key of ['should_trigger', 'should_not_trigger']) {
const arr = data[key]
if (!Array.isArray(arr) || arr.length === 0) {
fail(`${evalPath}: '${key}' must be a non-empty array.`)
continue
}
const ids = new Set()
for (const entry of arr) {
if (typeof entry !== 'object' || entry === null) {
fail(`${evalPath}: '${key}' contains a non-object entry.`)
continue
}
if (!entry.id || ids.has(entry.id)) {
fail(`${evalPath}: '${key}' entry is missing 'id' or has a duplicate id.`)
} else {
ids.add(entry.id)
}
if (typeof entry.prompt !== 'string' || entry.prompt.length === 0) {
fail(`${evalPath}: '${key}' entry '${entry.id ?? '?'}' is missing a non-empty 'prompt'.`)
}
}
}
}
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 verifyEvalSet doesn't cross-reference data.skill against actual skill directories

Every eval file has a "skill" field (e.g. "skill": "dtpr-describe-system"), but verifyEvalSet only validates the JSON structure — it never checks that data.skill matches a directory under SKILLS_DIR. Renaming or deleting a skill would orphan its eval set silently; verify.mjs would still report success.

A one-liner after the JSON.parse would close the gap:

if (typeof data.skill === 'string' && !skillDirNames.has(data.skill)) {
  fail(`${evalPath}: 'skill' field '${data.skill}' does not match any skill directory in ${SKILLS_DIR}.`)
}

where skillDirNames is the set of directory names collected in main() before verifyEvalSet is called.

Prompt To Fix With AI
This is a comment left during a code review.
Path: plugin/dtpr/evals/verify.mjs
Line: 86-117

Comment:
**`verifyEvalSet` doesn't cross-reference `data.skill` against actual skill directories**

Every eval file has a `"skill"` field (e.g. `"skill": "dtpr-describe-system"`), but `verifyEvalSet` only validates the JSON structure — it never checks that `data.skill` matches a directory under `SKILLS_DIR`. Renaming or deleting a skill would orphan its eval set silently; `verify.mjs` would still report success.

A one-liner after the `JSON.parse` would close the gap:

```js
if (typeof data.skill === 'string' && !skillDirNames.has(data.skill)) {
  fail(`${evalPath}: 'skill' field '${data.skill}' does not match any skill directory in ${SKILLS_DIR}.`)
}
```

where `skillDirNames` is the set of directory names collected in `main()` before `verifyEvalSet` is called.

How can I resolve this? If you propose a fix, please make it concise.

- promote.ts: pre-check that the target branch does not already
  exist *before* the rename. `git checkout -b` fails with exit 128
  when the branch is a leftover from a prior partial run, which
  would otherwise leave the tree half-promoted (directory renamed
  but no commit). Rollback guarantee in the JSDoc now holds for
  every failure path. New test covers the case.

- api-preview-deploy.yaml: derive SCHEMA_VERSION from the newest
  *-beta directory under api/schemas/ at run time instead of
  hardcoding it. Hardcoded version would go stale the moment that
  beta gets promoted to stable.

- verify.mjs: cross-reference each eval file's `skill` field
  against the set of skill directory names. Orphaned eval sets
  (e.g. after a skill rename) now fail the conformance check
  instead of passing silently.
@pichot pichot merged commit 43b62b6 into main Apr 17, 2026
7 checks passed
@pichot pichot deleted the feat/dtpr-drafting-skills branch April 17, 2026 13:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant