Repository navigation
Prism 0.6.0
Release date: 2026-10-08
Prism 0.6.0 generates workspaces from an app list, with templates as stack packs. One copier.yml and one template tag cover two layers: the workspace layer (the knowledge base, the guidance and shared/) and one app layer per app, from five packs: a Spring Boot backend, Next.js web apps, Android apps, iOS apps and a Python agent service. Every app is one tested slice with a fail-closed local development identity. The questionnaire is minimal: the project identity and the app list, with ports, packages and workflow names derived. Every skill, command and Cursor rule has one source, template-skills/, rendered into the layers of each tool. A committed golden workspace is built and tested in CI with every version pinned in packs/versions.yml, and automated dependency updates keep the pins current. The release also carries the security hardening from the independent reviews (confined paths and answers files, checked update layers, commit-bound releases, a fail-closed audit gate). Workspaces generated by earlier versions must be recreated.
Added
- Two-layer generation and five stack packs. The hidden question
prism_layerchooses the workspace layer (template/) or one app's pack (packs/<stack>/). Copier applies a pack to the repository root with every path under{{ app_path }}/, plus the app's workflow.github/workflows/<id>.ymland Cursor rule.cursor/rules/<id>.mdc. Each app keeps its own answers in<path>/.copier-answers.yml, so one template applies several times to one project. The packs are:spring-backend: a Spring Boot 4 app with theusersentity and its Flyway migration,GET /api/me, the shared error model, an OAuth2 resource-server configuration and the dev identity, with unit tests and integration tests against a PostgreSQL started by Testcontainers (./gradlew testneeds Docker and no credentials).task db-upandtask db-downstart and stop the workspace's PostgreSQL andtask <app-id>:devruns the app with thelocalprofile. A README,docs/entities/, a Dockerfile whose healthcheck needs no tool of the image, and its own schema in the shared development database (DATABASE_SCHEMA), so several backends share it.nextjs-web: a Next.js (App Router, TypeScript) app per audience with a "Local development sign-in" route that keeps the dev-identity token in an httpOnly cookie, one page that showsGET /api/methrough a client thatopenapi-typescriptandopenapi-fetchgenerate fromshared/api-contracts/openapi.yml, Vitest and Testing Library tests and a committedpackage-lock.json. Each web app has its own port, package name and session cookie name.android-compose: a Kotlin and Jetpack Compose (MVVM) app with a sign-in screen and a profile screen, a hand-written Retrofit client thatApiContractTestchecks against the shared contract, an in-memory session and JVM unit tests (a Robolectric Compose test included) that need no emulator. The app callslocalhost, andtask <app-id>:reverseforwards the backend's port withadb reverse; a release build refuses anapiBaseUrlthat is nothttps://.ios-swiftui: a SwiftUI app (Swift 6 strict concurrency,@MainActor @Observableview models) with a sign-in screen and a profile screen, aURLSessionclient kept to the contract, XCTest unit tests, one XCUITest, an XcodeGenproject.ymland a Fastlane TestFlight lane. The dev identity works in the simulator only. Only the macOS CI job builds and tests the pack, so its maturity isexperimental.python-agent-service: a FastAPI app (uv, committeduv.lock) that assists and never advises:GET /api/healthandPOST /api/assist, which runs one agent turn through a provider interface (a deterministic fake by default and a Claude API adapter with the model inAGENT_CLAUDE_MODELand the key only inANTHROPIC_API_KEY) with one read-only tool that reads the backend'sGET /api/mewith the caller's own token. It verifies bearer tokens against the key the backend publishes atGET /api/dev-identity/jwks, or against a configured issuer and audience, and has its ownopenapi.yml, pytest tests and an evaluation harness (python -m evals.run) that runs the fake provider in CI and the live model only on demand (task <app-id>:eval-live). The Claude adapter is tested against a stub only.
- The local development identity.
POST /api/dev-identity/tokenandGET /api/dev-identity/jwksexist only under the backend's@Profile("local")and answer loopback requests only (a non-loopback peer, a forwarding header or a non-loopback host name is refused). The token is a 15-minute JWT (iss=prism-dev-identity) signed with an RSA key generated in memory at startup, never written to disk. Startup fails whenlocalis active together with a configured identity provider, and with no provider configured the app rejects every token. No JWT secret exists anywhere in the template. The shared contract defines the dev identity (createDevToken, taggedx-prism-dev-only) andGET /api/me(getMe). packs/versions.yml, the one place that pins versions. A pack reads its stack's baseline asversions; the workspace guidance anddocker-compose.ymlread the same pins, and a test fails when a pack file repeats a pinned version.scripts/read-pins.py <stack>gives the repository workflows the same pins (--select-xcodeprints the developer directory of the installed Xcode that matches the pin, and--check-xcodeis exact:26.6accepts its patch releases and refuses26.7). The lockfiles are rewritten from the pins byscripts/refresh-nextjs-web-lock.pyandscripts/refresh-python-agent-service-lock.py.- Derived identifiers, remembered ports and the backend an app calls. From an app's ID the CLI derives its package segment, package, module name, workflow name and
paths:filters, and picks the first free port of the stack's range (spring-backendfrom 8080,nextjs-webfrom 3000,python-agent-servicefrom 8200), kept in the app's answers. An app of a client stack (web, Android, iOS, agent service) may name its backend (backend: <app id>in the manifest and in an answers file,prism app add --backend, a question per client in the interactive flow when there are several backends); without it a client calls the first scaffolded backend. The CLI renders that backend's loopback address (backend_base_url) into every default of the client, its guidance, the root README, the API conventions and theserversof the shared contract. An unknown backend isunknown-app-backend. prism app add --scaffold. It generates a new app into an existing generated workspace at the template revision the workspace records. With--applyit works on the branchprism-scaffold-<id>with one commit per layer, and it needs the workspace to be its own git repository with a clean tree and--trust-templatefor a custom template. The manifest fieldgenerationisscaffolded(Prism generated the code, soprism updatekeeps it current) orregistered(the default);otherapps and apps of external repositories are always registered.agent-serviceis a choice of the interactive app list and ofprism workflow install --app, with the other four generated apps; thefullpreset is one app of each of the backend, web, Android and iOS stacks.- The golden workspace (
golden/). A generated workspace in the repository with one app of each of the five stacks, generated byprism newfromscripts/golden-answers.yml.python scripts/build-golden.pyregenerates it deterministically and--checkfails on any difference; thegolden-currentjob and a test run the check. It is excluded from the source archive, the wheel and the staged copy of a local template.scripts/run-golden-workflow.py <app>runs therun:steps of a golden app's own workflow in CI, so a pack workflow cannot change unnoticed, andscripts/sync-golden.pyrefreshes the lockfiles andgolden/after a pin moves. - Automated dependency updates and an advisory gate.
renovate.jsonproposes updates topacks/versions.yml, one pull request per pack, and.github/workflows/dependency-sync.ymlpushes the regenerated lockfiles andgolden/to that pull request.scripts/audit-gate.pyrunsnpm audit --audit-level=highover the golden web lockfile anduv audit --lockedover the golden agent service lockfile, and fails on any advisory thatpacks/audit-allowlist.ymldoes not list with its advisory link and a reason (docs/current-status.mdrecords each entry). - A broken slice blocks the release. The first job of
release.ymlrunsscripts/check-ci-green.py, which requires a green run of Template Validation (which builds and tests the golden app of every pack) and of CLI Validation for the tagged commit. - A
deploymentskill in every generated workspace..claude/skills/deployment/and.agents/skills/deployment/explain how to deploy each generated stack and hold worked examples underreferences/: Azure Container Apps for a backend, Cloudflare Workers through OpenNext for web apps and the tag-triggered store release jobs for the mobile apps. The user and their agent own the cloud choice, the secrets and the deployment. - One source for every skill, command and Cursor rule.
template-skills/<name>/skill.mdholds a skill's name, description, layers, stacks and body, andscripts/build-skill-layers.pyrenderstemplate/.agents/skills/,template/.claude/commands/,template/.claude/skills/andtemplate/.cursor/rules/from it and writes the stack conditions into the generated block of_exclude.--checkand a test fail when a generated file differs from its source. The stack skills cite the slice files they teach from in aSlice filessection, andtests/test_stack_skill_slice_paths.pyfails when a cited path is missing from any app of the skill's stack. Four agent skills (agent-conventions,add-tool,add-evaluation-case,agent-safety) ship with an agent service, andweb-conventionswith a web app. feature-scope, an explicit scope edit through the board. A feature'sappsand its## App scopesection change together in one board-validated proposal that may also creatependingrequirement pages for the apps the scope gains. It changes no status, owner or other field, refuses adonefeature (reopen it first), never adds a retired app (app_retired) and never rewrites an existing requirement page. It is a canonical skill rather than a human action, because the human actions staypo-handoff,design-startanddev-start; it is the way out ofapp_retired_in_scopefor a feature in progress. The board serves 27 canonical skills.- A generated workspace's board is writable out of the box.
prism newpins the packaged workflow in the manifest (version, board identity and the digest of the packaged assets,workflow.mode: generated) and the generated.gitignoreignores.prism/state/, soprism board grantandprism board servetake a write withoutprism workflow upgrade. A workspace whose pin is missing or stale stays read-only. - Generated workspaces ship a root
.gitattributes(* text=auto eol=lf, CRLF only for*.batand*.cmd), so a generated workspace has the same line endings on every OS and the CLI's scaffold and update commits leavegit statusclean. - Conflicts are reported per layer. After each layer
prism updatescans for.rejfiles and conflict markers, and a conflicted update exits with 6. - Checks for the pack workflows. Template validation has an
agent-servicejob and generates through the CLI. The macOSios-buildjob builds and tests two iOS apps, and the Android and web jobs build two apps each.
Changed
prism newcollects an app list; Copier no longer asks about platforms. The questionnaire asks for the project identity and the apps (an ID, a stack, a path, an audience, a repository and whether to scaffold or only register each). A preset is an app list, an answers file carriesapps(leaving it out is an error;apps: []generates the workspace layer alone), and the interactive flow asks for the apps and then for further apps. The CLI runs the workspace layer and then each scaffolded app's pack, each from its own answers file.prism presets --jsoncarries each preset's apps. The default web app isweb, and a second web app is one morenextjs-webentry.- The backend is generated by the
spring-backendpack. A backend app gets the pack's slice at its own path, with its own package, port, workflow and Cursor rule, and the workspace'sdocker-compose.ymlhas one service for each backend app with the PostgreSQL development database. The backend'schecktask runs Gradle'scheckwithout the tests (no Kotlin style linter is configured), so the roottask lintcovers the web, Android and agent service apps. - The API contract is the auth contract.
shared/api-contracts/openapi.ymldepends on no answer. Template validation checks it, the web client is type-checked against it, and the Android and iOS clients are hand-written and checked against it (ApiContractTest, the contract paths test). prism updateworks on a branch, one commit per layer. It createsprism-update-<template tag>, updates the workspace layer and commits, then updates each active scaffolded app from its own answers file and commits after each one, reports every layer and stops before merging when a layer conflicted. Without conflicts the branch stays checked out for you to review and merge. A scaffolded app with no answers file stops the update with the ways out (restore the file, retire the app or drop its entry), Copier's.rejfiles are never committed, and_excluderestores Copier's default exclusions for__pycache__,*.py[co]and.DS_Store. A recopy follows the same branch flow and keeps the workspace's apps and repositories. For a remote templateprism newandprism updateleave.copier-answers.ymlas Copier wrote it.prism validatereads the manifest. It checks each scaffolded app's directory, its own.copier-answers.ymland.github/workflows/<app id>.ymlfor any app ID and path, treats the manifest's error diagnostics as errors, and requiresprism.workspace.yml.- Generated CI builds and tests only, with the same hygiene in every pack. No generated workflow pushes an image, deploys, releases to a store or reads a secret. Every pack workflow has
permissions: contents: read, aconcurrencygroup per app and branch, and a display name unique per app (<name> CI (<app id>)). The iOS workflow runs on the pinnedmacos-26image, selects the Xcode of the pin (26.6), caches Homebrew's downloads, and picks the simulator by readingxcrun simctl list devices available -jfor an iPhone whose runtime equals the selected SDK's. - The Android and iOS apps behave alike. Neither sign-in sends an email or a display name, a 401 on the profile screen returns to the sign-in with "Your session ended. Sign in again." on both, and both accept a base URL with or without trailing slashes. The iOS project sets
VERSIONING_SYSTEM = apple-genericfor Fastlane'sincrement_build_number. The roottask testincludes each iOS app's unit tests, which skip off macOS. - A skill has the same text in every tool. A workflow skill that is both a Codex skill and a Claude command has one body; the host differences are the invocation (
$namein Codex,/namein Claude Code) and a few marked lines. Cursor rules carry adescription, and the board review is a skill with no Cursor rule, because Cursor already loads the skill from.agents/skills/and.claude/skills/(the two skill folders stay: Claude Code and Codex each load only their own). - The clarify skills write complete sentences.
po-clarify,design-clarifyanddev-clarifyturn a short answer such asyesinto a complete sentence built from the question's wording, with the answer's words unchanged inside it, andaskand the clarify skills copy every other line of the page exactly as read. The board still requires the answer verbatim in each changed section, and its rejection now asks for the sentence. setup-projectand the guidance name the current commands.setup-projectruns afterprism neworprism workflow install, confirms the apps from the manifest, and the iOS and Android pack guidance states what the packs do (generate-clientsvalidates the contract for a hand-written client, the Android compile SDK is printed as the compile SDK, the iOS fake client has no template suffix).- Skills and docs describe the slices. The backend, web, Android, iOS and agent skills cite the slice's files by path and explain how to replace the dev identity with an identity provider; the workspace layer lists each app from the app list in the README,
AGENTS.md,Taskfile.ymland the architecture and CI docs;deploy-deviceships with an Android app and covers each one; the README, getting-started guide, workspace model and questionnaire match the output of the commands they show. - The repository workflows read every pinned version from
packs/versions.ymlthroughscripts/read-pins.py, andcli-validation.ymlalso runs on a manual start. - The manifest rejects unknown keys. A misspelled field of an app or a repository (
satus: retired) isunknown-app-fieldorunknown-repository-fieldinstead of being ignored. - A retired app frees its path (its ID stays reserved):
app-path-conflictcompares only active apps, and a scaffolded app still refuses a directory that holds files. dev-startanddev-doneon the board enforce the API rule (api_surface_without_api_app), asdesign-handoffdoes; the status-board header is exact on the board as in lint; and lint compares a design page'sfeature-idwithout regard to case.- A fresh apply no longer evaluates the operation twice. The apply that records an operation has just revalidated it against the live files, so its roll-forward skips the second full evaluation (about half of an apply's parses and file opens). Every write still checks the recorded before-state, and recovery and the retry of a pending operation revalidate in full.
Removed
- The full samples and everything that only served them.
template/backend,template/web-user-app,template/web-admin-portal,template/mobile-androidandtemplate/mobile-iosare gone with their authentication (passwords, refresh tokens, Google, Apple, Facebook and Microsoft OAuth), transactions, examples, docs, per-sample workflows, Cursor rule sources and_excludeentries; the packs' slices replace them. The workspace layer renders no app code. - The
platformsquestion and answer, and thedatabase,supporting_services,use_docker,cloud_provider,web_hosting,auth_methods,github_org,ios_module_nameandpackage_pathquestions. No generated app reads them.prism newrejects an answers file that sets one with an "Unknown answer(s)" error, and the "Username + Password is required" check and the Apple Sign-In warning are gone with them. - The JWT and OAuth variables of
.envand.env.example, the OAuth and transactions operations of the API contract, and Redis (its compose service, variables and Azure steps). The template holds no JWT secret. - The generated
infra/azure/scripts,docs/deployment/cloudflare-setup.mdand the rootinfra:*tasks. They moved into thedeploymentskill's references, andprism validateno longer requires the Cloudflare guide. - The Hygen generators (
template/_templates/) and every mention of them, including thehygenprerequisite. - The always-on Cursor rule
project.mdcand the Cursor ruleadvisory-review.mdc. Cursor readsAGENTS.mdand the skill folders itself, so a rule whose content is@AGENTS.mdor a copy of the board-review skill loaded the same text twice. - The per-stack Cursor rule sources and the workspace workflow
backend.yml.jinja. A stack's rule and workflow belong to its pack, per app. - Presets with their own answers. A preset is an app list only.
Fixed
- A feature in progress that names a retired app, or a
donefeature that does, can be repaired through the board. The first leavesapp_retired_in_scopethroughfeature-scope; the second can be reopened, because the requirement page of the retired app it lists is valid for the reopen. - The history-date lint covers
advisory/BOARD.mdandadvisory/PROJECT_FOUNDATION.md, whichSCHEMA.mdnames as current-state pages; their other exemptions stay. - The
Adding a feature laterexample ofdocs/workspace-model.mduses therelease:form thatrelease-evidence-requiredaccepts. - A board write no longer ends in a
conflictreceipt when Windows refuses a replace for a moment. Windows refuses to replace a file that another handle holds open without delete sharing, which includes the board's graph poller reading the pages. A replace of a page, a log or a folder now waits out that momentary refusal (access denied, sharing or lock violation) for up to a second and raises any other error as before. - The board no longer publishes a spurious version after a write. The server stores the fingerprint it took under its lock, so a poll that raced a write does not announce a version the write already covered; the browser tests wait until the page has adopted the newest version before they drag or open a dialog.
- Generated workspaces show no racy modifications in
git statuson Windows withcore.autocrlf=true(see.gitattributesabove).
Security
- One resolver for every wiki path. A link, a
sourcesentry or arepo:link on a page is untrusted text.prism_cli/wiki_paths.pydecodes first, refuses UNC, rooted, drive-qualified, backslash, double-encoded and..paths as text, confines the path lexically and only then looks at the components for symlinks and reparse points, so a path such as%2F%2Fhost%2Fshare%2Fx.mdreaches no filesystem call during proposal validation or lint. A refused path is abroken-linkfinding. - Names, paths and audiences cannot become code. An app path is restricted to safe segments (
[a-z0-9][a-z0-9._-]*); names, audiences, project names and descriptions are one line of plain characters (no quotes, backticks,$,<,>or;). The rule is one module (prism_cli/safe_values.py) used by the manifest normalizer,prism new,prism app addand Copier's own validators, so a rawcopier copyis covered too. Pack workflows pass the app's path once, as a quoted environment variable (APP_PATH), and templates serialize every user value that enters YAML. prism updatechecks every layer before it runs Copier with--trust. Each app layer must name the workspace's approved template source, a plain template revision and the identity, port and derived values that the manifest gives it, and hold no answer that no app layer asks. The workspace layer's own answers file must select theworkspacelayer and carry a safe project identity and anappslist that passes the safe-value rule, withstacksand the package segments re-derived and compared. A tampered file stops the update before Copier starts, andprism app add --scaffoldrefuses it the same way.- Recopy and every answers file are confined. The recopy writes its saved answers to an exclusively created temporary file in a checked directory and replaces the answers file instead of writing through it. Every answers file the update reads or writes, the workspace's own included, is confined to its folder before anything opens or follows it; a symlink, a junction or another reparse point on its path is refused with the way out (restore the regular file from git).
- Copier gets exactly the answers that were validated.
prism update,prism app add --scaffoldand recopy read each layer's saved answers once, without following a link, and give Copier exactly what was validated: a private file for a recopy, the same bytes for the manifest render. A smart update starts only while the tracked file still holds the validated bytes, and the file Copier writes must hold exactly the validated answers apart from the template revision and the stacks and apps handed over. When an answers file changes after validation, the command refuses and names the file; nothing that depends on it is moved into place or committed. - A write retry is checked like the first attempt. Connected writes recheck the expected content or intake tree, confinement, the participant's grant and the relevant sources before every Windows replacement retry. The relevant sources are bound as a set at validation (which pages and their content), so a page that appears during the wait is a conflict, never an overwrite.
- The generated web app serves the local development identity to this machine only.
next devandnext startlisten on127.0.0.1;POST /api/sessionanswers only in explicit local mode (LOCAL_DEV_SIGNIN=1, set by.env.development, which a production build does not load), only to a request that carries the app's ownOrigin, and only from this machine. The agent service answers callers on its own machine only under thelocalprofile. A Next.js route cannot see the socket peer, so the loopback bind, the flag and the refusal of a non-loopback bound address are its walls. - The generated backend requires an audience. Outside the
localprofile, a configured identity provider needsspring.security.oauth2.resourceserver.jwt.audiences: startup fails without one, and a token whoseaudis missing or names another API is rejected. The agent service requires the matchingAGENT_OIDC_AUDIENCE. The backend listens on127.0.0.1under itslocalprofile, and the generateddocker-compose.ymlbinds the development database and every other published port to127.0.0.1. - The agent service cannot be overspent concurrently. A turn reserves the most each model call can cost before the call, a user has a bounded number of turns in flight (
AGENT_BUDGET_CONCURRENT_TURNS), the usage of every completed call is recorded even when the turn fails later, and the budget answers429. Tool failures log the exception type only, never its message or traceback. - iOS Release builds allow no cleartext.
NSAllowsLocalNetworkingis inPlists/Info.Debug.plist, which only the Debug configuration uses, andAPIURL.makeaccepts anhttpURL only in Debug builds. - Releases are bound to one commit and to what was built.
release.ymlandnpm-release.ymlrequirerefs/tags/<tag>(a branch named like a tag is refused), resolve its commit once and check out that SHA in every later job. The built wheel is smoke-tested before any publication, and the TestPyPI and PyPI copies are downloaded from their own index alone and must be byte-identical to the build before the release goes on. - Dependency sync holds its write token away from unreviewed tooling.
dependency-sync.ymlregenerates the lockfiles andgolden/in a read-only job (persist-credentials: false, pinned to the head SHA) and uploads one patch; a second job holding the write token validates that the patch touches only the pack lockfiles,packs/versions.ymlandgolden/, then applies and pushes it. - The audit gate fails closed.
scripts/audit-gate.pyrejects annpm auditreport without the expected structure, whose exit status disagrees with it, whosemetadata.vulnerabilitiescounts differ from the entries present, whose entries and advisories lack the severity, package, link and title the gate reports, or whose entry does not reach an advisory through the entries it names (an emptyvia, a self-reference, a loop or a package the report does not list);uv auditwith an exit status other than 0 or 1 fails too. - Recovery keeps checking the sources a human reviewed. A recovery confirmed by a human binds the review to the current relevant paths and their digests, and the snapshot is checked after the before-state is reconstructed and before every move and write, so an editor's change between the review and the apply stops it. The review is verified against the one snapshot that the recovery then keeps.
- Ingest cannot write format templates. A skill output whose file name starts with
_(knowledge/wiki/topics/_FORMAT.mdand the same in research and plans) is refused, and every output must resolve to a page kind the board validates.