T-1D8N-sdf-project-setup-action
State: closed/done · Impact: high · Complexity: medium
Opening a project in SDF should be able to set up its SDLC configuration.
The deterministic tail already exists as sdlc project setup (planning
dirs, sdlc.yaml, .gitignore blocks, .sdlc/, lease-namespace check)
and sdlc harness install claude (the Claude Code plugin registration).
This task exposes them through SDF’s bounded-controls surface: one read
that says whether a registered project is set up and what setup would
change, and one token-guarded action that runs it. The UI that prompts the
user is T-VVNK.
| Location | Role today |
|---|---|
solutions/ontological/lib/services/project/ops/setup.ts | The op. Idempotent; --dry-run --output json returns per-item created / exists / would-create statuses; --detect adds commented runner suggestions to a fresh sdlc.yaml. Its render hook returns exit 15 on a lease-namespace conflict, and the CLI adapter runs that hook only under --output text; under --output json the conflict rides the body and the op exits 0. |
solutions/ontological/lib/services/harness/ops/install-claude.ts | Registers the plugin marketplace in the project’s Claude settings. --mode link writes the running checkout’s absolute path into the gitignored .claude/settings.local.json. |
solutions/ontological/desktop/src-tauri/src/api/v1/action.rs | The only mutating surface: token-guarded over HTTP, audited on every outcome. |
solutions/ontological/desktop/src-tauri/src/actions/spawn.rs | Spawner::capture already hands back Outcome { stdout, stderr, code }; run is the exit-0 convenience over it. The seam this task needs is one layer up, in sdlc_cli. |
solutions/ontological/desktop/src-tauri/src/schema/action.rs | Wire structs for the existing actions (WatchProbe, AuditRecord, ActionLog). |
solutions/ontological/desktop/src-tauri/src/projects/mod.rs#path_of | Resolves an id to a location without checking the directory exists. |
solutions/ontological/desktop/src-tauri/tests/bounded_controls.rs | Asserts the exact set of mutating routes and MCP tools. |
solutions/ontological/desktop/src-tauri/tests/actions_http.rs | 401 and 200 cases for the token guard. |
docs/planning/decisions/D-0018-sdf-control-plane-app.md | Rule 6: controls are token-guarded and bounded; no current action writes inside a checkout. |
The CLI locator actions/sdlc_cli.rs with its run(verb_args, project_root, spawner) helper
arrives with T-B7GN and is what the argv builders here run through.
Proposed
Section titled “Proposed”Two service fns in api/v1/action.rs, both taking project_id:
get_project_setup_status(project_id) -> ProjectSetupStatus. A read. Returnsroot_exists(a Rustis_dir),sdlc_yaml_present(a stat),harness_installed(whether.claude/settings.jsonor.claude/settings.local.jsonenablessdlc@sdlc), anditems: the per-item list fromproject setup --dry-run --output json. Whenroot_existsis false the items list is empty and nothing is spawned.setup_project(project_id, install_harness: Option<&str>) -> ProjectSetupResult. POST. Refuses withActionDeniedwhen the root directory does not exist. Runsproject setup --detect --output json, then, wheninstall_harnessis not"false",harness install claude --mode link --output json. Returns both results and the lease-namespace verdict. Every outcome is audited under the project’s trail.
New wire structs SetupItem, ProjectSetupStatus, ProjectSetupResult
in schema/action.rs. A pure module actions/project_setup.rs holds the
verb builders and the JSON parsers. sdlc_cli gains a capture beside
run, returning the Outcome the spawn seam already produces: on a genuine
non-zero exit the op writes its marker or error: <message> to stderr and
leaves stdout empty, so run — which keeps only stdout — can report nothing
but a bare exit code. Capturing all three lets the failure name itself.
The D-0018 Decision section gains one sentence: scaffolding writes inside a registered checkout, produced by an idempotent sdlc op the operator triggered, are inside the bounded-controls envelope; merge and lease ownership remain outside it.
Approach
Section titled “Approach”- Add
sdlc_cli::captureover the existingSpawner::capture, so a failing op’s stderr and exit code reach the caller instead of a bare exit code. - Write
actions/project_setup.rs:status_verb,setup_verb,harness_verb,parse_setup_output(mapsdirs,sdlcYaml,gitignore,sdlcDir,leaseNamespaceintoVec<SetupItem>),parse_harness_output, andharness_installed(root). Unit tests use fixture JSON captured from a realsdlc project setup --dry-run --output jsonrun, checked in beside the tests. - Add the wire structs to
schema/action.rswithJsonSchemaandspecta::Typederives like the existing ones. - Add the two service fns to
api/v1/action.rs.setup_projectis#[post]so the HTTP guard covers it; keep double quotes out of the doc comments (the MCP generator embeds them). Runcargo check; commit the regenerated transports and TS client. - Extend the allowlists in
solutions/ontological/desktop/src-tauri/tests/bounded_controls.rswith/api/actions/setup-projectand/api/actions/project-setup-statusand the matching MCP tool names. - Add to
solutions/ontological/desktop/src-tauri/tests/actions_http.rs:setup-projectwithout the token is 401 and audited; with the token and a registered tempdir project containing a fake checkout resolved throughSDLC_HOME, it returns 200 and the project gainssdlc.yaml. - Add the D-0018 sentence and a row for each fn in the SDF README’s action table.
| Area | Note |
|---|---|
solutions/ontological/desktop/src-tauri/src/actions/sdlc_cli.rs | capture beside run; a typed Verb in place of raw &[&str] argv. |
solutions/ontological/desktop/src-tauri/src/actions/project_setup.rs | Verb builders, JSON parsers, harness detection, unit tests. |
solutions/ontological/desktop/src-tauri/src/actions/mod.rs | Declare the module. |
solutions/ontological/desktop/src-tauri/src/schema/action.rs | SetupItem, ProjectSetupStatus, ProjectSetupResult. |
solutions/ontological/desktop/src-tauri/src/api/v1/action.rs | get_project_setup_status and setup_project. |
solutions/ontological/desktop/src-tauri/src/api/transport/http/generated.rs | Regenerated. |
solutions/ontological/desktop/src-tauri/src/api/transport/ipc/generated.rs | Regenerated. |
solutions/ontological/desktop/src-tauri/src/api/transport/mcp/generated.rs | Regenerated. |
solutions/ontological/desktop/src-nuxt/app/generated/transport.ts | Regenerated. |
solutions/ontological/desktop/src-nuxt/app/generated/types.ts | Regenerated. |
solutions/ontological/desktop/src-tauri/tests/bounded_controls.rs | Allowlist the two new names. |
solutions/ontological/desktop/src-tauri/tests/actions_http.rs | 401 and 200 cases for setup-project. |
solutions/ontological/desktop/src-tauri/tests/fixtures/project-setup-dry-run.json | Captured op output for the parser tests. |
solutions/ontological/desktop/README.md | Two rows in the action table. |
docs/planning/decisions/D-0018-sdf-control-plane-app.md | One sentence under Decision on in-checkout scaffolding writes. |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
get_project_setup_status(id)for a registered tempdir with nosdlc.yamlreturnssdlc_yaml_present: falseand at least one item with statuswould-create; for a tempdir whose root does not exist it returnsroot_exists: false, an empty items list, andRecordingSpawnerrecords no call. - AC-2:
setup_project(id, None)on a registered tempdir whose root does not exist returnsActionDenied, spawns nothing, and appends an audit record with outcomedenied. - AC-3:
POST /api/actions/setup-projectwithoutx-platform-tokenreturns 401 and appends a denied audit record; with the token it returns 200 and the project directory afterwards containssdlc.yaml,.sdlc/, anddocs/planning/tasks/. - AC-4: With
install_harnessunset, the harness step runs inlinkmode and.claude/settings.local.jsonin the project enablessdlc@sdlc; withinstall_harness: "false"noharnessargv is recorded. - AC-5: A setup run against a project whose
sdlc.yamldeclares alease_authorityshadowing the lease namespace exits 0 under--output json, and the parser maps itsleaseNamespacesection tolease_namespace_ok: falseplus the conflict detail. No exit code is special-cased: the verdict is read from the body of a successful run. - AC-6:
solutions/ontological/desktop/src-tauri/tests/bounded_controls.rspasses with exactly the two new names added and no other change to the allowlists. - AC-7:
moon run sdf:checkandmoon run sdf:testpass, and the codegen-drift gate is clean.
Out of scope
Section titled “Out of scope”- Any UI (
T-VVNK). - The interactive
/sdlc:find-verbssteps of/sdlc:setup;--detectwrites the suggestions as comments instead. sdlc entities audit,sdlc project doctor, and--obsidianbases.- Running setup automatically on registration or on launch; the operator triggers it.
copyandnode-modulesharness modes; SDF always runs from a source checkout on this machine.
Dependencies
Section titled “Dependencies”T-FW4Zgives every action aproject_idand the id-keyed audit trail.T-B7GNgives the CLI locator the argv builders run through.
Discovery context
Section titled “Discovery context”The deliverable of the 2026-09-20 survey into “open a project in SDF and set up its SDLC configuration”. The op already existed; what was missing was the SDF seam, the CLI locator, and the no-ambient-project rule.
Depends on
Section titled “Depends on”T-FW4Z, T-B7GN