English · Português
House Party Protocol

Contracts · INSTALL-CONTRACT.md

INSTALL-CONTRACT — the installation contract of every kit in this marketplace

Version: 1.0.0 · Enforcement: installers/kit-forge/kit_doctor.py (subcommand install) Sibling of: SKILL-CONTRACT.md (the SKILL.md contract) — this one is the contract of the INSTALLER.

01

Principle

INSTALLING A KIT IS NEVER A SURPRISE.
The operator sees the PLAN before any write happens, knows whether the target project
is NEW or ALREADY HAS CONFIG, and only a second, explicit invocation (--apply) acts.
No installer in this marketplace blocks waiting for terminal input() —
the human's "yes" is the re-invocation with --apply, not a prompt.

Every kit in this marketplace that has installation steps (file copy, profile, wiring of hooks/statusLine) is installed by the SAME engine (kit_doctor.py install), which reads a declarative per-kit manifest (install/kit.install.yaml). The engine never carries hardcoded knowledge of a specific kit — all kit-specific knowledge lives in the YAML.

02

The 6 stages (fixed order)

detect → prereqs → profile → configure → wire-suggest → smoke

1. detect (new — always first, read-only)

Classifies the target project (--target, default cwd) into one of three categories:

ClassificationSignals
greenfieldno .claude/, no settings.json/settings.local.json, git with few or no commits, no real profile present
in-progress.claude/ exists, settings*.json has hooks/statusLine in use, a real profile (operator-profile.yaml/rollup.yaml/etc.) present, non-trivial git history
re-runthis kit_dir already has an entry in ~/.claude-kits/registry.json pointing at this --target

Output: {"stage": "detect", "classification": "...", "signals": {...}, "existing_config": [list of already-configured items that will be preserved]}. Never writes anything.

2. prereqs (existing — python/PyYAML/dependencies declared in requires:)

3. profile (existing — copies *.example.* → real; never overwrites if the target already exists with content different from the generated one; skip-exists is reported, not silent)

4. configure (new)

Reads questions: from kit.install.yaml. Without --answers: uses the default of each question and lists, in the plan, which questions remain pending a human decision. With --answers <file.json|yaml>: loads, validates against the schema (type/options), applies; absent fields = default.

5. wire-suggest (existing — NEVER writes settings/hooks; only detects what exists — .claude-plugin/plugin.json, install/wiring-spec.yaml, README.md/guide — and suggests the textual command. Mutating settings/hooks remains a human gate, always.)

6. smoke (existing — runs --self-test on every .py of the kit; scripts without that support are skipped, not a failure)

03

The plan → apply flow (Confirm step)

$ python kit_doctor.py install <kit_dir> --target <project_dir>
   → runs the 6 stages in PLAN MODE (no real write happens)
   → prints the full plan (JSON) and EXITS 0 (1 if the smoke or the profile stage failed)
   → the human reads the plan (in the conversation, if an agent is operating)

$ python kit_doctor.py install <kit_dir> --target <project_dir> --apply
   → runs the 6 stages for real, applying the profile and registering the install
   → wire-suggest still NEVER writes (the human gate is always manual)

There is no (and there must not be a) blocking input() anywhere in this mechanism — the real operator of this ecosystem is frequently an agent acting on behalf of a human; a stdin prompt hangs exactly in that usage context. The "Confirm" is the double invocation (plan, then --apply), auditable and reproducible.

04

Schema of install/kit.install.yaml

kit: <kit-name>
requires:
  python: ">=3.9"          # optional, default ">=3.8"
  pyyaml: true              # optional, default false
  external_services: []     # e.g. the supabase-pack declares [{name: Supabase, via: MCP, credenciais: "URL + anon key"}]
                             # HARD RULE: never declare ANTHROPIC_API_KEY here — model authentication belongs to the host, never to the kit
questions:                  # optional — consumed by the `configure` stage AND by the --interview mode of wizards
  - id: <identifier>
    prompt: "<the question>"
    type: choice|bool|string
    options: [...]           # if type: choice
    default: <value>
verification:               # post-install acceptance commands (each must be executable and exit 0)
  - "python scripts/<script>.py --self-test"
hosts:
  claude-code:               # native plugin host — see the "Host seam" section below
    plugin: true|false        # does .claude-plugin/plugin.json exist?
    wiring_spec: install/wiring-spec.yaml   # if there is hooks/statusLine wiring
    manual_gates: [statusLine]              # what must NEVER be auto-armed (always a human gate)
  codex:                     # host installed by explicit copy, with no automatic activation of hooks
    plugin: false
    agents: AGENTS.md
    skills_path: .agents/skills
    runtime_path: .agents/hpp/<kit-name>
    manual_gates: [hooks]
docs: README.md

credenciais is the key the supabase-pack's manifest really uses; it is quoted here as it is written there.

05

Question schema (questions:)

The same schema is consumed in two places: by the configure stage of kit_doctor.py, and by the --interview mode of standalone wizards (e.g. agent-framework-wizard/wizard.py). This guarantees that "answering questions" has ONE format, not two.

{"id": "lane_mode", "prompt": "Does the project run multi-session (lanes) or solo?", "type": "choice", "options": ["solo", "lanes"], "default": "solo"}

06

Host seam (Claude Code + Codex CLI)

The engine keeps an internal dict with the two proven hosts:

HOSTS = {
    "claude-code": {
        "settings_path": ".claude/settings.local.json",
        "plugin_manifest": ".claude-plugin/plugin.json",
        "path_token": "${CLAUDE_PLUGIN_ROOT}",
    },
    "codex": {
        "agents_file": "AGENTS.md",
        "skills_path": ".agents/skills",
        "runtime_path": ".agents/hpp",
        "path_token": "{{HPP_CODEX_RUNTIME}}",
    },
}

NEW sources (kit.install.yaml, install/wiring-spec.yaml) use the neutral token {{KIT_ROOT}}, resolved to the path_token of the active host (--host; the default follows claude-code). EXISTING uses of ${CLAUDE_PLUGIN_ROOT} in hooks.json/scripts are NOT rewritten — they are native Claude Code artefacts and remain so. On Codex, codex_skills.py copies the runtime to .agents/hpp/<kit> and generates namespaced skills under .agents/skills/, substituting the token only in those copies. Hooks stay off. A future host adapter = a new entry in HOSTS + a hosts.<new>: block in the YAMLs — zero change to the 6 stages. Do not invent a fake "neutral" event vocabulary: events such as Stop/PreCompact are Claude Code concepts and are declared under hosts.claude-code, honestly.

07

Exit codes (kit_doctor.py install)

ExitMeaning
0plan printed (default mode) OR applied successfully (--apply)
1the smoke stage failed, or the profile stage failed (the Codex host's copy)
3usage error (kit_dir/target missing)

08

Checklist before considering a kit "really installable"

[ ] install/kit.install.yaml existe e valida contra o schema acima
[ ] requires.external_services declarado (mesmo que vazio) — nunca ANTHROPIC_API_KEY
[ ] questions (se houver) têm default sensato — instalar sem --answers nunca trava
[ ] verification: tem ≥1 comando real, executável, que sai 0 quando tudo está ok
[ ] hosts.claude-code.manual_gates lista tudo que NUNCA deve ser auto-armado
[ ] hosts.codex declara AGENTS.md, .agents/skills, runtime namespaced e hooks em manual_gates
[ ] README.md segue o INSTALL-GUIDE-TEMPLATE.md
[ ] kit_doctor.py install <kit> --target <dir-virgem> roda sem erro (plano)
[ ] kit_doctor.py install <kit> --target <dir-virgem> --apply roda sem erro (aplica)
[ ] rodar de novo (--apply) sobre o MESMO dir → detect classifica re-run, nada duplica

The ten boxes, in order: install/kit.install.yaml exists and validates against the schema above; requires.external_services is declared (even if empty) — never ANTHROPIC_API_KEY; questions (if any) have a sensible default — installing without --answers never hangs; verification: has ≥1 real, executable command that exits 0 when everything is fine; hosts.claude-code.manual_gates lists everything that must NEVER be auto-armed; hosts.codex declares AGENTS.md, .agents/skills, a namespaced runtime and hooks in manual_gates; README.md follows INSTALL-GUIDE-TEMPLATE.md; kit_doctor.py install <kit> --target <dir-virgem> runs without error (plan); the same with --apply runs without error (applies); running --apply again over the SAME dir → detect classifies re-run, nothing is duplicated.