Capability: distribution
Shipping the-loop as an installable plugin for Claude Code, Cursor and Codex from a single repository — no bespoke marketplace publishing.
What it is
The packaging that makes the-loop installable across harnesses: thin plugin manifests over one shared set of skills, commands and templates. The templates and the config schemas are internal to the plugin — read from it when authoring artifacts or validating a config, never copied into the projects the-loop is run on.
Current behaviour
- the-loop SHALL be installable in Claude Code directly from GitHub (
/plugin marketplace add MadaraUchiha-314/the-loop+/plugin install the-loop@the-loop) via.claude-plugin/plugin.json+marketplace.json. - the-loop SHALL be installable in Cursor (≥ 2.5) from the same repo via
.cursor-plugin/plugin.json+marketplace.json, or by cloning under~/.cursor/plugins/local/. - The Claude Code plugin SHALL also be installable non-interactively from a terminal with
the-loop install claude [--scope user|project](and moved forward withthe-loop upgrade), which drivesclaude's own plugin CLI where it has one and otherwise falls back to the settings keys above. The Cursor plugin SHALL keep its documented in-editor routes until issue-157 establishes a terminal one — the CLI reportscursoras an unknown component rather than half-supporting it (owner decision on PR #153). See cli and decision-057; the in-session/pluginand/add-pluginroutes remain exactly as documented. - Both plugins SHALL reuse the SAME
skills/(Agent Skills standard) andcommands/; nothing is forked per harness. - Plugin and marketplace manifest
versionfields SHALL carry the released version:cz bumprewrites them in lockstep on every release (see release-publishing, decision-028). - WHERE Claude Code uses the SessionStart hook (
hooks/hooks.json) the Cursor package SHALL use the always-applied rulerules/the-loop.mdcinstead.hooks/hooks.jsonSHALL also declare the Stop gate (the-loop-gate.py) and, since issue-370, thePostToolUserecorder (the-loop-link-pr.py, which runssessions link-pr --discoverafter agit pushor a pull-request-creating call — issue-447, noghoutput parsed); Cursor has a hook surface for the first and none for the second, so there the skill's prose rule is what records a pull request. - Work-item and process templates SHALL be internal to the plugin, shipped under
skills/the-loop/templates/(manifest.templatesDir) and read from${CLAUDE_PLUGIN_ROOT}when an artifact is authored./the-loop:initSHALL NOT copy them into a project; a project carries only its own generated artifacts. - WHEN
/the-loop:upgrade-the-loopruns on a project that an older version scaffolded a.the-loop/templates/folder into THEN it SHALL remove that folder (permanifest.deprecated), confirming first only if the user has added their own files under it. - Config schemas SHALL be internal to the plugin too (issue-220, decision-080), shipped under
${CLAUDE_PLUGIN_ROOT}/.the-loop/and declared once asmanifest.schemasDir— the same shapetemplatesDirhas./the-loop:initSHALL NOT createharness-config.schema.json,collaborators.schema.jsonorcli-config.schema.jsonin a project, and the opt-in.the-loop/cli-config.yamlSHALL be scaffolded alone. - WHEN init or upgrade validates a config, or drives the
x-onboardingwalkthrough, THEN it SHALL read the schema frommanifest.schemasDiron disk — the absence of a project-local copy SHALL NOT weaken, skip, or move that validation onto the network. - WHEN
/the-loop:upgrade-the-loopruns on a project holding a schema copy an older version left behind THEN it SHALL delete it and report it under removed (deprecated); WHERE that copy differs from the plugin's shipped schema the difference SHALL be surfaced first, and WHERE the file cannot be established as a the-loop copy it SHALL be left in place and reported under needs-user. Deletion is name-driven frommanifest.deprecated; a path resolving outside the project's.the-loop/SHALL be refused. - Every config the-loop scaffolds SHALL open, on its first line, with a
# yaml-language-server: $schema=<published url>modeline, so an operator's editor validates the file with no local schema. It is a comment: the loop SHALL never read it, and SHALL never fetch a schema over the network. - WHEN
/the-loop:initscaffolds.the-loop/harness-config.yamlTHEN it SHALL establish the config with the user via a guided onboarding driven by the schema'sx-onboardinggroups: related keys clubbed and decided together, each group explained, enum keys presented with ALL possibilities, free-form keys with schemaexamples, and sensible defaults resolved as existing answer → detected signal → schema default (see the skill'sreference/onboarding.md). - Each config has its own init command (issue-514, decision-149).
/the-loop:initSHALL set up the repository only — the harness config, collaborators, manifest, the docs trees, theloop:<phase>labels, hooks and CI parity — and SHALL NOT ask a daemon question, check a credential, or create or modify a CLI config, a Slack app manifest or agateway-config.yaml; its report SHALL name/the-loop:init-cliand/the-loop:init-gateway./the-loop:init-cliSHALL own the CLI config — the posture questions, its walkthrough, Slack, Jira, therouting.autoExecuteLabelslabels and the credential preflight — and SHALL need no harness config./the-loop:init-gatewaySHALL owngateway-config.yamland its own secrets' preflight. All three SHALL take--dry-runand--defaultsand SHALL be idempotent and non-clobbering. Where a bullet below says "init" of the CLI config, it is/the-loop:init-cli. - The CLI config is onboarded by the same walkthrough (issue-415).
cli-config.schema.jsonSHALL carry its ownx-onboardingblock — ordered groups, ask levels, per-groupexplain— and init SHALL walk it after the harness config, whichever answer the user gave to where that file lives: the path is a storage question, and what the file turns on is why they ran init. Every top-level property exceptversionSHALL belong to exactly one group, so no block ships that nobody is ever asked about. - The user's threshold for automation is ONE question (issue-415).
x-onboarding.profilesSHALL declare an ordered autonomy ladder — rungs each carrying atitle, asummary, astillHumanline and thevaluesthey propose — and init SHALL present the whole ladder with the recommendation marked, before the groups those values seed. A rung is a proposal: init SHALL show the resolved values before writing them, and SHALL record the applied rung as a provenance comment in the written config and by id in the final report. The least autonomous rung SHALL be the shipped defaults, so "not yet" is a supported answer. - No autonomy profile SHALL buy away a human gate. A rung's values SHALL be confined to
webhooks,polling,routing,service,selfDiagnosisandchannels, and SHALL NOT includerouting.authorizedUsers— the phase-selection checklist, the artifact approval gates, the pull-request approval and the risk tiers are fixed rules of the skill and the process graph, not settings, and authority is granted by name and never implied by a threshold.cli/tests/test_onboarding_schema.pyenforces both. - Init SHALL establish the deployment shape before proposing an ingress (issue-415): a personal machine proposes the poller, an always-on host with an inbound route proposes the webhook receiver, and neither is proposed when the user has nowhere to run it. The answer is used and never stored — a second source of truth for "is the webhook enabled" would only drift.
- Init SHALL walk the Slack setup and verify it (issue-415): the short explanation (one thread per work item;
subscribeis what the channel hears;publishis the channel's authority; GitHub stays the ledger), then the app manifest, the two tokens, the invite and the conversation id, thenthe-loop channels status [--probe]andthe-loop doctor slack. A declined Slack SHALL end the group immediately, and a verification that could not run SHALL be reported as unverified, never as working. - Init SHALL preflight every credential its configs name, presence only (issue-415). The variable names SHALL be read from the written configs — not a hardcoded list — and init SHALL report whether each is set and nothing else: never a value, a prefix, a length or a hash, and never a credential written into any file. An unset variable SHALL be a needs-user line naming what it gates and where to obtain it; a variable only the daemon's environment would carry SHALL be reported as unverified, distinctly from unset. WHERE an
env.fileis proposed init SHALL say it holds secrets and verify.gitignorecovers it, offering the line when it does not. - WHERE
--defaultsis passed init SHALL apply sensible defaults without interaction and report the remaining gaps under needs-user — including, since issue-415, the unanswered autonomy profile and every unset credential, and saying plainly that no automation is enabled; WHEN init re-runs it SHALL raise only gaps, never re-asking established answers, the autonomy profile included. - WHEN
/the-loop:upgrade-the-loopfinds a removed schema key that still carries live operational settings (not just a stale default) THEN it SHALL migrate the data, not merely flag and drop it — e.g. a pre-decision-032.the-loop/harness-config.yamlstill carryingwebhooks/polling/observability.eventLogSHALL have that block extracted,eventLog-renamed, and written to a CLI config (asking the same yes/no location question/initasks), both resulting files validated, and the migration reported as its own line — never silently dropped. - WHEN a release adds a config key whose default changes runtime behaviour THEN upgrade SHALL surface it under needs-user rather than adding it silently — the add-with-defaults rule covers opt-in keys, not behaviour flips. The first instance is
routing.control.requireStartCommand(issue-106): its default demotes the auto-execute label to necessary but not sufficient, so upgrade asks whether to keep the previous behaviour (false) or adopt the gate (true). Related state moves are offered, never performed silently: an older state layout (.the-loop/poll-state.json, then<state.root>/sessions/) may be tidied away, but the daemon keeps READING what is there until each work item has been written forward, because an empty ledger would re-forward every watched thread (issue-106, issue-128). - A CLI config that lives in the operator's home directory is outside upgrade's reach (it reconciles project files). Upgrade SHALL say so and print what to paste, and the runtime SHALL stay correct for an un-migrated config — every key added this way is optional and falls back to the same defaults.
Design
docs/specs/issue-12/design.md · architecture § distribution
History
| Work item | What changed | Links |
|---|---|---|
| issue-514 | /the-loop:init split three ways: /init sets up the repository only (harness config, collaborators, docs trees, phase labels, hooks), /init-cli the operator's CLI config (posture, walkthrough, Slack, Jira, arming labels, credentials), /init-gateway a gateway host's gateway-config.yaml; a repository that only uses the-loop is no longer asked about a daemon | spec, decision-149 |
| issue-447 | The plugin's PostToolUse recorder became trigger-agnostic — a push or any pull-request-creating call runs sessions link-pr --discover — and the skill and slash commands send the harness's GitHub acts through the-loop verbs instead of gh | spec, decision-140, issue |
| issue-415 | The onboarding covers what people install the-loop for: cli-config.schema.json gained its own x-onboarding block (seven groups, every block covered), an autonomy ladder turns "how much should it do unattended?" into one question with three rungs that provably cannot reach a human gate, init establishes the deployment shape before proposing an ingress, walks and probes the Slack setup, and preflights every credential the configs name — presence only, never a value | spec, onboarding guide, issue |
| issue-220 | Config schemas made internal to the plugin (manifest.schemasDir); init stops copying up to 118 KB of them into each project, upgrade deletes the copies already there, and scaffolded configs carry a # yaml-language-server: $schema= modeline instead | spec, decision-080, issue |
| issue-152 | The Claude Code plugin became installable and upgradable from the CLI (the-loop install / upgrade), at user or project scope, without opening a session — the terminal-side counterpart to the marketplace routes. Cursor stays in-editor-only, split out as issue-157 | spec, decision-057, cli, issue |
| issue-106 | A key whose default changes behaviour (routing.control.requireStartCommand) is reported as needs-user, not silently added; the state/control blocks are added with defaults and the poll-state move is offered, not forced | spec, decision-040, issue |
| issue-63 | /upgrade migrates (not just flags) removed schema keys with live data — the webhooks/polling/observability.eventLog → CLI config extraction | spec, decision-032 |
| issue-46 | Plugin/marketplace manifest versions bumped by the release engine (were frozen at 0.1.0) | spec, decision-028 |
| issue-49 | Guided, schema-driven config onboarding in /init (x-onboarding groups, ask levels, --defaults mode, examples on gap-prone keys) | spec, decision-024 |
| issue-36 | Templates made internal to the plugin (skills/the-loop/templates/); init no longer copies them into projects, and upgrade cleans up the deprecated .the-loop/templates/ folder | spec |
| issue-12 | Added Cursor packaging (.cursor-plugin/, rules/the-loop.mdc) reusing the same skills/commands | spec, decision-015 |
| issue-1 | Shipped the Claude Code plugin + marketplace manifests (v0) | spec, decision-001 |
Codex installation (issue-449)
the-loop install codex --scope project --project-dir <repo> checks the Codex binary and prepares native instructions and project hooks without starting a model session. --scope user prepares ~/.codex/AGENTS.md and hooks.json. all includes Codex, and the default includes it when its binary is on PATH. A missing binary is a named failed step, and --dry-run performs no writes. Existing instructions and hook definitions are preserved. Trust new hook definitions with /hooks before relying on unattended execution.
The repository also ships .codex-plugin/plugin.json, using the shared skills and Codex-specific lifecycle hooks under hooks/codex-hooks.json. Native plugin hook installation follows Codex's own trust review. Plugin versions remain in commitizen's version lockstep. The CLI distribution bundles the operating skill, its required sibling writing skill and both sets of references, command procedures and hook scripts so installation from a wheel or source distribution has the same instruction surface as a repository checkout.