Skip to content

T-ZF8U-harness-install-claude-verb

Status: closed/done · Impact: medium · Complexity: medium

D-0014 makes onboarding a CLI concern, including installing the control plane: after bun add/npm i, one verb wires the Claude harness into a consumer project. Today that wiring is hand-edited .claude/settings.json (the dev repo’s own stanza is the only exemplar), and nothing detects a registered plugin that has drifted from the installed package. This task adds the install verb and the doctor check that keeps the two in agreement.

LocationRole today
.claude/settings.jsonthe dev repo’s hand-written extraKnownMarketplaces stanza (source: directory, path: ./plugin) + enabledPlugins {"sdlc@sdlc": true} — the shape the verb must produce
plugin/lib/services/plugin/the plugin service noun: info-report.ts (resolves plugin root, CLAUDE_PLUGIN_ROOT-first), install-permissions op — the natural home for the new op
plugin/lib/services/project/ops/doctor.tssdlc project doctor: probes git/gh/claude binaries and runtime; no plugin-registration check
plugin/lib/services/project/ops/setup.tssdlc project setup: scaffolds docs/planning/<plural>/; the install verb slots beside it in onboarding order
plugin/skills/info/SKILL.md/sdlc:info diagnoses “which install won” — the interactive counterpart of the doctor check

D-0014’s open question: the installed plugin’s home — repo-local copy (leading candidate, .sdlc/plugin/) vs a marketplace pointer into node_modules — is decided by the pilot (T-521G); the verb must support both so the pilot can compare.

sdlc harness install claude [--mode copy|node-modules] [--project-root …] wires a consumer project idempotently: copy (default) materializes the plugin tree at <projectRoot>/.sdlc/plugin/ with a provenance stamp and registers that path; node-modules registers ./node_modules/@sksizer/sdlc/plugin directly. sdlc project doctor gains a same-install check: the registered marketplace path’s plugin version must equal the running CLI’s, and (copy mode) the materialized copy must not have drifted from its stamp. A future sdlc harness install <other> is a sibling op module, nothing more.

  1. Add op plugin/lib/services/harness/ops/install-claude.ts (path: ['harness','install','claude'], new harness service noun). Behavior: a. Resolve the source plugin root (CLAUDE_PLUGIN_ROOT → else package-relative via the existing info-report.ts resolution). b. --mode copy (default): rsync-style copy of the plugin tree to <projectRoot>/.sdlc/plugin/ (delete-extraneous, preserve executable bits); write .sdlc/plugin/.installed.json {version, sourcePath, installedAt, fileCount}. c. Merge — never overwrite — .claude/settings.json: add the sdlc marketplace (source: directory, path per mode) and enabledPlugins["sdlc@sdlc"] = true, preserving all unrelated keys; report a stable stdout marker (HARNESS: claude installed (<mode>) at <path>). d. Idempotent: a second run with identical inputs makes no file changes and says so.
  2. Ensure .sdlc/ is gitignored in the consumer (append if missing — same mechanism project setup uses for its scaffolding).
  3. Extend sdlc project doctor: read .claude/settings.json; if an sdlc marketplace is registered, compare the registered path’s .claude-plugin/plugin.json version against the running CLI’s version — mismatch is a FAIL line naming both paths and versions; in copy mode also flag drift when the tree’s file count differs from the .installed.json stamp. Unregistered harness stays a neutral INFO (CLI-only installs are legitimate).
  4. Tests: fixture project dir — install (copy) writes stanza + tree + stamp; re-run is a no-op; --mode node-modules writes the pointer stanza only; settings merge preserves pre-existing hooks/keys; doctor passes on a fresh install, fails on a version-tampered copy.
  5. Document the onboarding sequence (bun add -d @sksizer/sdlcsdlc project setupsdlc harness install claude) in the README distribution section.
LocationKindChange
plugin/lib/services/harness/ops/install-claude.tsnewthe install op (both modes, settings merge, provenance stamp, idempotence)
plugin/lib/services/harness/tests/newop tests per Approach step 4
plugin/lib/services/project/ops/doctor.tsmodifysame-install check (version parity + copy-drift)
plugin/lib/services/project/tests/modifydoctor same-install cases (pass, version mismatch, drifted copy)
plugin/lib/services/project/ops/setup.tsmodify.sdlc/ gitignore assurance shared with the install op (extract or reuse helper)
README.mdmodifyconsumer onboarding sequence
  • AC-1: in a fixture project, sdlc harness install claude exits 0, materializes .sdlc/plugin/ with .installed.json, and .claude/settings.json gains the marketplace + enabledPlugins entries with all pre-existing keys intact.
  • AC-2: an immediate second run reports idempotence and produces zero file diffs (asserted on the fixture tree).
  • AC-3: --mode node-modules writes a stanza pointing at ./node_modules/@sksizer/sdlc/plugin and materializes nothing under .sdlc/.
  • AC-4: sdlc project doctor passes after a fresh install and emits a FAIL line naming both versions when the registered copy’s plugin.json version is tampered.
  • AC-5: the op registry lists harness install claude in sdlc --help output.
  • Choosing between the two modes — T-521G pilots both and records the default in D-0014.
  • Uninstall/upgrade verbs (upgrade = re-run install after bumping the dep; a dedicated verb can come later if the pilot shows the need).
  • Other harness targets (gemini, …) — the op layout leaves room; no speculative work.
  • Claude-side verification that the plugin actually loads from the registered path — pilot concern (T-521G).
  • none hard to start (developable against the dev repo’s own plugin tree). Meaningful end-to-end only with an installed artifact — T-XTGT — and decided by T-521G.
  • D-0014 Migration step 5 and the “Where the installed plugin lives” open question (repo-local copy as leading candidate).

← Back to Tasks