Files
devplacepy/.claude/workflows/devii-tool.js
T
retoorandClaude Opus 5 91fac7fd67 Gate blocked actions behind an in-place terms acceptance dialog
A member whose account has not accepted the terms in force now gets one
dialog on the action they attempted instead of a dead-end refusal. The
client handler is the single TermsGate, wired into every Http POST helper
so the four optimistic controllers cannot swallow the gate into an error
flash, and the original request is replayed once the acceptance is
recorded. Reading the site and deleting an account stay unblocked.

apple.md is the source brief the compliance research documents reference.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 11:25:57 +02:00

141 lines
7.9 KiB
JavaScript

// retoor <retoor@molodetz.nl>
export const meta = {
name: 'devii-tool',
description: 'Add a Devii agent capability: an Action in the catalog with auth flags matched to the route guard, dispatcher wiring, API docs, then verify role-gating and confirmation and write the api-tier integration test for the action',
phases: [
{ title: 'Understand', detail: 'find the underlying route and a similar Action to mirror' },
{ title: 'Implement', detail: 'add the Action, wire the handler, document it' },
{ title: 'Verify', detail: 'role-gating, flag alignment, and confirm gating' },
{ title: 'Fix', detail: 'close gaps from the review' },
{ title: 'Test', detail: 'write the api-tier integration test for the action (visibility, auth gating, confirm)' },
],
}
const RULES = [
'Obey DevPlace hard rules while editing:',
'- No comments or docstrings in source except the file header and the @tool docstring required for a tool schema. New files start with the "retoor <retoor@molodetz.nl>" header.',
'- No em-dash characters; use a hyphen. Full Python type hints; pathlib over os.',
'- A Devii Action requires_auth/requires_admin MUST exactly match the underlying route guard. Never grant a member an admin capability. A non-admin must not even see an admin tool schema.',
'- If the action is irreversible or destructive, add it to dispatcher CONFIRM_REQUIRED and declare a confirm boolean param in its spec (schemas set additionalProperties:false, so a gated tool without a declared confirm param can never receive confirm=true and loops forever).',
'- Prefer handler="http" reusing an existing REST route; only add a local controller handler when there is no route. Reuse the arg()/body()/query()/confirm() helpers for params.',
'- Validate with the per-language checks (py_compile + pyflakes for Python, node --check for JS, brace balance for CSS, tag and {% %} balance for templates) and "python -c \\"from devplacepy.main import app\\"". NEVER run the test suite. Never perform any git write.',
].join('\n')
const TESTS = [
'DevPlace test standard (a hard project requirement - one test file per endpoint, the directory tree mirroring the URL path):',
'A Devii tool is reached over the same HTTP surface a user hits, so its test lives in tests/api/ (often tests/api/devii/), against the live uvicorn subprocess (app_server/seeded_db, requests/httpx vs BASE_URL).',
'Cover the role-gating that is the whole point of the tool: an unauthenticated/guest caller is refused, a member sees and can call a requires_auth tool but is refused a requires_admin one (and its schema is withheld), an admin can call it, and a destructive action is refused without confirm=true and proceeds with it.',
'Required patterns: scoped assertions; try/finally restore of any flipped global setting; the shared fixtures (alice, bob, app_server); test FUNCTIONS are test_-prefixed though files are not. Validate by a clean import only. NEVER run the suite.',
].join('\n')
function toolBrief() {
if (!args) return ''
if (typeof args === 'string') return args
if (typeof args.description === 'string') return args.description
return JSON.stringify(args)
}
const ask = toolBrief()
if (!ask) {
log('No tool description provided. Invoke as /devii-tool <what the tool should do>.')
return { error: 'no description provided' }
}
const MAP_SCHEMA = {
type: 'object',
additionalProperties: false,
required: ['summary'],
properties: {
summary: { type: 'string' },
underlyingRoute: { type: 'string' },
routeGuard: { type: 'string' },
similarAction: { type: 'string' },
handler: { type: 'string' },
destructive: { type: 'boolean' },
},
}
const BUILD_SCHEMA = {
type: 'object',
additionalProperties: false,
required: ['summary', 'filesChanged', 'validatorPassed'],
properties: {
summary: { type: 'string' },
actionName: { type: 'string' },
filesChanged: { type: 'array', items: { type: 'string' } },
validatorPassed: { type: 'boolean' },
importOk: { type: 'boolean' },
},
}
const FINDINGS_SCHEMA = {
type: 'object',
additionalProperties: false,
required: ['summary', 'findings'],
properties: {
summary: { type: 'string' },
findings: {
type: 'array',
items: {
type: 'object',
additionalProperties: false,
required: ['severity', 'file', 'rule', 'message'],
properties: {
severity: { type: 'string', enum: ['error', 'warning', 'info'] },
file: { type: 'string' },
line: { type: 'integer' },
rule: { type: 'string' },
message: { type: 'string' },
},
},
},
},
}
log(`Devii tool: ${ask}`)
const map = await agent(
`Find the underlying REST route this Devii tool should call (or determine it needs a local controller handler), its exact auth guard, and the most similar existing Action in services/devii/actions/catalog.py to mirror. Note whether the action is destructive. Do not write anything.\n\nTool request: ${ask}`,
{ agentType: 'Explore', label: 'understand', phase: 'Understand', schema: MAP_SCHEMA }
)
const build = await agent(
`Add this Devii tool, editing files directly in the repo. Add the Action to the catalog mirroring the similar action, set requires_auth/requires_admin to exactly match the underlying route guard, wire the dispatcher handler if a new local handler is needed, and add a docs_api.py entry if it wraps an HTTP endpoint. If destructive, add it to CONFIRM_REQUIRED and declare a confirm param. Then run the per-language checks (py_compile + pyflakes for Python, node --check for JS, brace balance for CSS, tag and {% %} balance for templates) and "python -c \\"from devplacepy.main import app\\"". Do not write the test here. Do not run the suite. Do not commit.\n\nTool request: ${ask}\n\nContext:\n${JSON.stringify(map, null, 2)}\n\n${RULES}\n\nReturn the action name, files changed, and whether the checks and the import passed.`,
{ label: 'implement', phase: 'Implement', schema: BUILD_SCHEMA }
)
const changed = (build && build.filesChanged) || []
const scopeNote = changed.length ? `\n\nRestrict findings to these files:\n${changed.join('\n')}` : ''
const audits = await parallel(
[
{ key: 'devii', agent: 'devii-maintainer' },
{ key: 'security', agent: 'security-maintainer' },
].map((a) => () =>
agent(
`Operate in REPORT mode (read-only). Audit the new Devii tool for your single dimension: confirm the auth flags match the route guard, no admin schema leaks to a non-admin, and any destructive action has both CONFIRM_REQUIRED membership and a declared confirm param.${scopeNote}\n\nTool request: ${ask}`,
{ agentType: a.agent, label: `verify:${a.key}`, phase: 'Verify', schema: FINDINGS_SCHEMA }
).then((r) => ({ key: a.key, findings: (r && r.findings) || [] }))
)
)
const gaps = audits
.filter(Boolean)
.flatMap((r) => r.findings.map((f) => ({ dimension: r.key, ...f })))
.filter((f) => f.severity !== 'info')
let gapFix = 'no actionable gaps'
if (gaps.length) {
gapFix = await agent(
`Close these Devii tool gaps with minimal root-cause fixes in the repo, then re-run the per-language checks. Do not run the suite. Do not commit.\n\nGaps:\n${JSON.stringify(gaps, null, 2)}\n\n${RULES}`,
{ label: 'fix-gaps', phase: 'Fix' }
)
}
const test = await agent(
`Operate in FIX mode. Write the integration test for this Devii tool following the required patterns (tests/api/devii layout), asserting the role-gating and confirm behavior described below. The tool is not complete until its gating is tested. Create any missing package directories the test path needs. Validate by a clean import only. NEVER run the suite.\n\n${TESTS}\n\nTool request: ${ask}\nAction: ${build && build.actionName}\nFiles changed:\n${changed.join('\n')}\n\nReturn the test file written and the gating cases it covers.`,
{ agentType: 'test-maintainer', label: 'test', phase: 'Test' }
)
return { ask, map, build, audit: gaps, gapFix, test }