Skip to content

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.

LocationRole today
solutions/ontological/lib/services/project/ops/setup.tsThe 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.tsRegisters 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.rsThe only mutating surface: token-guarded over HTTP, audited on every outcome.
solutions/ontological/desktop/src-tauri/src/actions/spawn.rsSpawner::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.rsWire structs for the existing actions (WatchProbe, AuditRecord, ActionLog).
solutions/ontological/desktop/src-tauri/src/projects/mod.rs#path_ofResolves an id to a location without checking the directory exists.
solutions/ontological/desktop/src-tauri/tests/bounded_controls.rsAsserts the exact set of mutating routes and MCP tools.
solutions/ontological/desktop/src-tauri/tests/actions_http.rs401 and 200 cases for the token guard.
docs/planning/decisions/D-0018-sdf-control-plane-app.mdRule 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.

Two service fns in api/v1/action.rs, both taking project_id:

  • get_project_setup_status(project_id) -> ProjectSetupStatus. A read. Returns root_exists (a Rust is_dir), sdlc_yaml_present (a stat), harness_installed (whether .claude/settings.json or .claude/settings.local.json enables sdlc@sdlc), and items: the per-item list from project setup --dry-run --output json. When root_exists is false the items list is empty and nothing is spawned.
  • setup_project(project_id, install_harness: Option<&str>) -> ProjectSetupResult. POST. Refuses with ActionDenied when the root directory does not exist. Runs project setup --detect --output json, then, when install_harness is 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.

  1. Add sdlc_cli::capture over the existing Spawner::capture, so a failing op’s stderr and exit code reach the caller instead of a bare exit code.
  2. Write actions/project_setup.rs: status_verb, setup_verb, harness_verb, parse_setup_output (maps dirs, sdlcYaml, gitignore, sdlcDir, leaseNamespace into Vec<SetupItem>), parse_harness_output, and harness_installed(root). Unit tests use fixture JSON captured from a real sdlc project setup --dry-run --output json run, checked in beside the tests.
  3. Add the wire structs to schema/action.rs with JsonSchema and specta::Type derives like the existing ones.
  4. Add the two service fns to api/v1/action.rs. setup_project is #[post] so the HTTP guard covers it; keep double quotes out of the doc comments (the MCP generator embeds them). Run cargo check; commit the regenerated transports and TS client.
  5. Extend the allowlists in solutions/ontological/desktop/src-tauri/tests/bounded_controls.rs with /api/actions/setup-project and /api/actions/project-setup-status and the matching MCP tool names.
  6. Add to solutions/ontological/desktop/src-tauri/tests/actions_http.rs: setup-project without the token is 401 and audited; with the token and a registered tempdir project containing a fake checkout resolved through SDLC_HOME, it returns 200 and the project gains sdlc.yaml.
  7. Add the D-0018 sentence and a row for each fn in the SDF README’s action table.
AreaNote
solutions/ontological/desktop/src-tauri/src/actions/sdlc_cli.rscapture beside run; a typed Verb in place of raw &[&str] argv.
solutions/ontological/desktop/src-tauri/src/actions/project_setup.rsVerb builders, JSON parsers, harness detection, unit tests.
solutions/ontological/desktop/src-tauri/src/actions/mod.rsDeclare the module.
solutions/ontological/desktop/src-tauri/src/schema/action.rsSetupItem, ProjectSetupStatus, ProjectSetupResult.
solutions/ontological/desktop/src-tauri/src/api/v1/action.rsget_project_setup_status and setup_project.
solutions/ontological/desktop/src-tauri/src/api/transport/http/generated.rsRegenerated.
solutions/ontological/desktop/src-tauri/src/api/transport/ipc/generated.rsRegenerated.
solutions/ontological/desktop/src-tauri/src/api/transport/mcp/generated.rsRegenerated.
solutions/ontological/desktop/src-nuxt/app/generated/transport.tsRegenerated.
solutions/ontological/desktop/src-nuxt/app/generated/types.tsRegenerated.
solutions/ontological/desktop/src-tauri/tests/bounded_controls.rsAllowlist the two new names.
solutions/ontological/desktop/src-tauri/tests/actions_http.rs401 and 200 cases for setup-project.
solutions/ontological/desktop/src-tauri/tests/fixtures/project-setup-dry-run.jsonCaptured op output for the parser tests.
solutions/ontological/desktop/README.mdTwo rows in the action table.
docs/planning/decisions/D-0018-sdf-control-plane-app.mdOne sentence under Decision on in-checkout scaffolding writes.
  • AC-1: get_project_setup_status(id) for a registered tempdir with no sdlc.yaml returns sdlc_yaml_present: false and at least one item with status would-create; for a tempdir whose root does not exist it returns root_exists: false, an empty items list, and RecordingSpawner records no call.
  • AC-2: setup_project(id, None) on a registered tempdir whose root does not exist returns ActionDenied, spawns nothing, and appends an audit record with outcome denied.
  • AC-3: POST /api/actions/setup-project without x-platform-token returns 401 and appends a denied audit record; with the token it returns 200 and the project directory afterwards contains sdlc.yaml, .sdlc/, and docs/planning/tasks/.
  • AC-4: With install_harness unset, the harness step runs in link mode and .claude/settings.local.json in the project enables sdlc@sdlc; with install_harness: "false" no harness argv is recorded.
  • AC-5: A setup run against a project whose sdlc.yaml declares a lease_authority shadowing the lease namespace exits 0 under --output json, and the parser maps its leaseNamespace section to lease_namespace_ok: false plus 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.rs passes with exactly the two new names added and no other change to the allowlists.
  • AC-7: moon run sdf:check and moon run sdf:test pass, and the codegen-drift gate is clean.
  • Any UI (T-VVNK).
  • The interactive /sdlc:find-verbs steps of /sdlc:setup; --detect writes the suggestions as comments instead.
  • sdlc entities audit, sdlc project doctor, and --obsidian bases.
  • Running setup automatically on registration or on launch; the operator triggers it.
  • copy and node-modules harness modes; SDF always runs from a source checkout on this machine.
  • T-FW4Z gives every action a project_id and the id-keyed audit trail.
  • T-B7GN gives the CLI locator the argv builders run through.

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.

T-FW4Z, T-B7GN


← Back to Tasks