@bitkentech/shipsmooth-opencode 0.3.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@bitkentech/shipsmooth-opencode",
3
+ "version": "0.3.27",
4
+ "description": "Agent coding workflow with plan-before-implement discipline, TDD, vertical slices, Linear integration, and immutable git-based plan versioning.",
5
+ "type": "module",
6
+ "main": "plugin/index.js",
7
+ "files": [
8
+ "plugin/",
9
+ "skills/"
10
+ ],
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "author": {
15
+ "name": "Pramod Biligiri"
16
+ },
17
+ "homepage": "https://github.com/bitkentech/shipsmooth",
18
+ "repository": "https://github.com/bitkentech/shipsmooth",
19
+ "license": "Apache-2.0"
20
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "version" : "0.3.27",
3
+ "name" : "shipsmooth"
4
+ }
@@ -0,0 +1,71 @@
1
+ #!/bin/sh
2
+ # Node-free SessionStart bootstrap for the shipsmooth jlink runtime.
3
+ #
4
+ # Invoked by the plugin's SessionStart hook as:
5
+ # sh ".../hooks/install-shipsmooth.sh" <plugin-name> <version>
6
+ #
7
+ # Strict POSIX sh using only the stock-macOS toolset (sh, curl, unzip, uname,
8
+ # chmod, mktemp, mv): detect platform, skip if already installed, download the
9
+ # release zip, unzip (Info-ZIP restores the stored +x on runtime/lib/jspawnhelper
10
+ # that OpenJ9 needs to spawn subprocesses), force-chmod the launcher as a
11
+ # backstop, then atomically mv into place. Mirrors session-start.ts.
12
+ #
13
+ # Env override (tests): SS_URL_BASE replaces the GitHub releases base URL.
14
+ set -eu
15
+
16
+ NAME="${1:?usage: install-shipsmooth.sh <plugin-name> <version>}"
17
+ VERSION="${2:?usage: install-shipsmooth.sh <plugin-name> <version>}"
18
+
19
+ CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/$NAME"
20
+ URL_BASE="${SS_URL_BASE:-https://github.com/bitkentech/shipsmooth/releases/download/v$VERSION}"
21
+
22
+ log() { printf '%s: %s\n' "$NAME" "$1" >&2; }
23
+ die() { log "$1"; exit 1; }
24
+
25
+ os=$(uname -s)
26
+ arch=$(uname -m)
27
+ case "$os" in
28
+ Darwin) os=darwin ;;
29
+ Linux) os=linux ;;
30
+ *) die "unsupported OS: $os" ;;
31
+ esac
32
+ case "$arch" in
33
+ arm64|aarch64) arch=arm64 ;;
34
+ x86_64|amd64) arch=x64 ;;
35
+ *) die "unsupported arch: $arch" ;;
36
+ esac
37
+ PLATFORM="$os-$arch"
38
+
39
+ RUNTIME_DIR="$CACHE_DIR/$VERSION"
40
+ BIN="$RUNTIME_DIR/bin/shipsmooth"
41
+ if [ -x "$BIN" ]; then
42
+ log "runtime $VERSION already installed at $RUNTIME_DIR"
43
+ exit 0
44
+ fi
45
+
46
+ case "$PLATFORM" in
47
+ linux-x64|darwin-x64|darwin-arm64) : ;;
48
+ *) die "platform $PLATFORM is not supported" ;;
49
+ esac
50
+
51
+ TMP=$(mktemp -d "${TMPDIR:-/tmp}/$NAME-XXXXXX")
52
+ EXTRACT_DIR="$RUNTIME_DIR.tmp"
53
+ trap 'rm -rf "$TMP" "$EXTRACT_DIR"' EXIT INT TERM
54
+
55
+ ZIP="$TMP/runtime.zip"
56
+ URL="$URL_BASE/$NAME-$VERSION-$PLATFORM.zip"
57
+ log "downloading runtime from $URL"
58
+ curl -fsSL --retry 2 --retry-delay 1 -A "$NAME-runtime-installer" -o "$ZIP" "$URL" \
59
+ || die "failed to download $URL"
60
+
61
+ rm -rf "$EXTRACT_DIR"
62
+ mkdir -p "$EXTRACT_DIR"
63
+ unzip -q "$ZIP" -d "$EXTRACT_DIR"
64
+
65
+ EXTRACTED_BIN="$EXTRACT_DIR/bin/shipsmooth"
66
+ [ -f "$EXTRACTED_BIN" ] || die "extracted archive is missing bin/shipsmooth"
67
+ chmod 0755 "$EXTRACTED_BIN"
68
+
69
+ rm -rf "$RUNTIME_DIR"
70
+ mv "$EXTRACT_DIR" "$RUNTIME_DIR"
71
+ log "runtime $VERSION installed at $RUNTIME_DIR"
@@ -0,0 +1,84 @@
1
+ // shipsmooth OpenCode plugin entry (plan-86 Task 10/11).
2
+ //
3
+ // The only executable code shipsmooth ships to any host. Two jobs, both proven
4
+ // in the Task 1 de-risk:
5
+ // 1. Bootstrap the jlink runtime on the first `session.created` event by
6
+ // shelling out (via Bun's `$`) to the bundled hooks/install-shipsmooth.sh.
7
+ // Idempotent + non-fatal: a failure logs and degrades to "CLI unavailable",
8
+ // never crashing the session.
9
+ // 2. Register the `shipsmooth:start` slash command whose template is a thin
10
+ // pointer to the native `start` SKILL.md (the single-sourced workflow text).
11
+ //
12
+ // Authored in TS, shipped as plain transpiled .js (Task 4). `@opencode-ai/plugin`
13
+ // is a type-only devDependency.
14
+ //
15
+ // IMPORTANT (Task 11 integration finding). OpenCode 1.17.9 discovers plugins by
16
+ // scanning `<config-dir>/plugin/*.js` (NON-recursively) and, for each file, calls
17
+ // EVERY export as a plugin factory (`for (const v of Object.values(mod))`). Two
18
+ // consequences this module is shaped around:
19
+ // 1. The entry exports ONLY the factory (named + default). Exporting a helper
20
+ // here would make OpenCode call e.g. readConfig(context) → it path.joins the
21
+ // context object → plugin load crashes with `"paths[0]" ... got object`.
22
+ // 2. The pure helpers live in ./lib/internal.js — under plugin/lib/, NOT
23
+ // plugin/, so OpenCode's non-recursive scan never loads them as a plugin (a
24
+ // sibling plugin/internal.js WOULD be loaded and rejected as
25
+ // "Plugin export is not a function" for its non-function exports).
26
+ //
27
+ // The version + skill name come from the rendered config JSON next to the
28
+ // compiled module (dist/session-start-config.json), so a version bump re-renders
29
+ // one file and this source is untouched.
30
+ import { existsSync } from "node:fs";
31
+ import { moduleDir, readConfig, startCommandId, startCommandTemplate, installerPath, safeLog, } from "./lib/internal.js";
32
+ export const ShipsmoothPlugin = async ({ client, $ }) => {
33
+ const base = moduleDir(import.meta.url);
34
+ let cfg;
35
+ try {
36
+ cfg = readConfig(base);
37
+ }
38
+ catch (e) {
39
+ // Without config we cannot bootstrap; log and return an inert plugin.
40
+ await safeLog(client, "error", `shipsmooth: ${e.message}`);
41
+ return {};
42
+ }
43
+ let bootstrapped = false;
44
+ async function bootstrap() {
45
+ if (bootstrapped)
46
+ return;
47
+ bootstrapped = true;
48
+ const installer = installerPath(base);
49
+ if (!existsSync(installer)) {
50
+ await safeLog(client, "error", `shipsmooth: installer not found at ${installer}`);
51
+ return;
52
+ }
53
+ try {
54
+ // Idempotent: the script early-exits if the runtime is already installed.
55
+ const res = await $ `sh ${installer} ${cfg.name} ${cfg.version}`.nothrow().quiet();
56
+ if (res.exitCode !== 0) {
57
+ await safeLog(client, "error", `shipsmooth: runtime bootstrap exit ${res.exitCode}: ${res.stderr.toString().trim()}`);
58
+ }
59
+ else {
60
+ await safeLog(client, "info", `shipsmooth: runtime ${cfg.version} ready`);
61
+ }
62
+ }
63
+ catch (e) {
64
+ // Non-fatal: a missing runtime degrades to "CLI unavailable", never crashes.
65
+ await safeLog(client, "error", `shipsmooth: bootstrap failed: ${e.message}`);
66
+ }
67
+ }
68
+ const hooks = {
69
+ config: async (config) => {
70
+ config.command = config.command ?? {};
71
+ config.command[startCommandId(cfg.name)] = {
72
+ description: "Apply the shipsmooth agent coding workflow",
73
+ template: startCommandTemplate(cfg.name),
74
+ };
75
+ },
76
+ event: async ({ event }) => {
77
+ if (event?.type === "session.created") {
78
+ await bootstrap();
79
+ }
80
+ },
81
+ };
82
+ return hooks;
83
+ };
84
+ export default ShipsmoothPlugin;
@@ -0,0 +1,65 @@
1
+ // shipsmooth OpenCode plugin — internal helpers (plan-86 Task 10/11).
2
+ //
3
+ // These pure helpers live OUTSIDE the plugin entry module on purpose. OpenCode
4
+ // (verified on 1.17.9) loads a plugin file by importing it and treating EVERY
5
+ // named export as a plugin factory — it invokes each export with the plugin
6
+ // context object. Helpers like `readConfig`/`installerPath` call `path.join` on
7
+ // their first argument, so if OpenCode called them with the context object the
8
+ // join throws `"paths[0]" must be of type string, got object` and the plugin
9
+ // "fails to load". Keeping them here (imported by index.ts, never re-exported)
10
+ // means the entry module exposes only the real factory. The unit suite imports
11
+ // these directly from this module.
12
+ import { readFileSync, existsSync } from "node:fs";
13
+ import { dirname, join } from "node:path";
14
+ import { fileURLToPath } from "node:url";
15
+ export const CONFIG_FILE = "session-start-config.json";
16
+ /** Resolve the directory a compiled module lives in (pass `import.meta.url`). */
17
+ export function moduleDir(metaUrl) {
18
+ return dirname(fileURLToPath(metaUrl));
19
+ }
20
+ /**
21
+ * Read {name, version} from the rendered config JSON. The renderer writes it to
22
+ * `dist/session-start-config.json`; the compiled plugin lives next to that dist
23
+ * dir, so we look in the module dir and its `dist` sibling. Throws if absent —
24
+ * the payload is malformed without it.
25
+ */
26
+ export function readConfig(baseDir) {
27
+ const candidates = [
28
+ join(baseDir, CONFIG_FILE),
29
+ join(baseDir, "dist", CONFIG_FILE),
30
+ ];
31
+ for (const c of candidates) {
32
+ if (existsSync(c)) {
33
+ const parsed = JSON.parse(readFileSync(c, "utf-8"));
34
+ return { name: parsed.name, version: parsed.version };
35
+ }
36
+ }
37
+ throw new Error(`shipsmooth: ${CONFIG_FILE} not found near ${baseDir}`);
38
+ }
39
+ /** The skill the start command delegates to: `start` (prod) or `start-dev` (dev). */
40
+ export function skillName(pluginName) {
41
+ return pluginName.endsWith("-dev") ? "start-dev" : "start";
42
+ }
43
+ /** Thin launcher template — points the agent at the canonical skill, no inlined workflow. */
44
+ export function startCommandTemplate(pluginName) {
45
+ const skill = skillName(pluginName);
46
+ return (`Apply the shipsmooth agent coding workflow. Invoke the \`${skill}\` skill ` +
47
+ `(via the skill tool) and follow it for this task.`);
48
+ }
49
+ /** Command id registered in OpenCode's config.command map. */
50
+ export function startCommandId(pluginName) {
51
+ return `${pluginName}:start`;
52
+ }
53
+ /** Path to the bundled installer the bootstrap shells out to. */
54
+ export function installerPath(baseDir) {
55
+ return join(baseDir, "hooks", "install-shipsmooth.sh");
56
+ }
57
+ /** Best-effort structured log; never throws (logging must not break a session). */
58
+ export async function safeLog(client, level, message) {
59
+ try {
60
+ await client.app.log({ body: { service: "shipsmooth", level, message } });
61
+ }
62
+ catch {
63
+ // logging is best-effort
64
+ }
65
+ }
@@ -0,0 +1,502 @@
1
+ ---
2
+ name: start
3
+ description: Use when starting any task — applies the shipsmooth agent coding workflow.
4
+ ---
5
+
6
+ # start — Agent Coding Workflow
7
+
8
+ ## When to apply this skill
9
+ Apply this skill whenever you are:
10
+ - Starting work on a new feature or task
11
+ - Asked to write, revise, or execute a plan
12
+ - Picking up existing work from Linear
13
+ - Closing out, abandoning, or handing off a plan
14
+
15
+ ---
16
+
17
+ ## Core Invariants — Never Violate These
18
+
19
+ 1. **Features vs Plans are strictly separate.** Feature issues live in the permanent backlog forever. Plan issues live in transient `[agent]` projects and are archived after completion. Never create feature issues inside an `[agent]` project.
20
+ 2. **A committed, pushed, human-reviewed plan is the contract.** You execute against it. You do not autonomously modify it.
21
+ 3. **Every plan must reference at least one permanent backlog feature issue.** `[Linear]` Create an `[agent]` project linking to it. `[Local]` Record it in the `<backlog-issue>` metadata element of the tasks XML file. If no backlog issue exists, stop and create one before proceeding.
22
+ 4. **Task tracking is never the source of truth for plan content.** Git is. Linear (or the local tasks file) tracks task state only.
23
+ 5. **Tags are permanent.** Never delete a plan version tag from remote, even on abandonment or squash merge.
24
+ 6. **Tests precede implementation.** Write integration test(s) before any task code (Phase 2 preamble), then the unit test for each task before its implementation. Never implement without a failing test already committed. (Apply as far as possible — migrations and config may not be TDD-able.)
25
+
26
+ ---
27
+
28
+ ## Control Strategy: The Risk-Quality Loop
29
+
30
+ To maximize productivity while minimizing "hallucination drift," treat
31
+ risk and quality as two pressures that peak at different times — and never
32
+ chase both at once.
33
+
34
+ - **Spiral risk** — the chance that the architecture or core logic is
35
+ simply wrong. It is highest at the *start* of a task, when the approach
36
+ is unproven, and collapses once the logic is validated.
37
+ - **Implementation quality** — readability, project-pattern conformance,
38
+ and test coverage. It matters only *after* the approach is proven; polishing
39
+ code that may be thrown away is wasted effort.
40
+
41
+ **Strategy:** De-risk aggressively first — prove the logic works and ignore
42
+ quality rules. Once the approach is validated and approved, switch modes and
43
+ harden the code to the quality bar. The per-task **De-risk & Harden Cycle**
44
+ below operationalizes this; this section only explains *why* the two phases
45
+ are kept separate.
46
+
47
+ ---
48
+
49
+ ## Task Tracking Mode
50
+
51
+ This workflow supports two task tracking modes. Choose one at the start of each plan:
52
+
53
+ - **`[Linear]`** — Uses Linear issues and projects. Requires a Linear account and the Linear MCP server configured in Claude Code.
54
+ - **`[Local]`** — Uses a local XML file at `.shipsmooth/plans/plan-{N}-tasks.xml`. No external services required. Requires the plugin's SessionStart hook to have run (downloads the Java CLI runtime to `~/.cache/shipsmooth/`).
55
+
56
+ Throughout this skill, instructions marked `[Linear]` apply only in Linear mode; instructions marked `[Local]` apply only in Local mode. Unmarked instructions apply to both.
57
+
58
+ `[Local]` Script invocations use `${XDG_CACHE_HOME:-~/.cache}/shipsmooth/0.3.27/bin/shipsmooth <subcommand>`. All scripts read/write `.shipsmooth/plans/plan-{N}-tasks.xml` relative to the repo root.
59
+
60
+ ---
61
+
62
+ ## Repository Structure
63
+
64
+ ```
65
+ .shipsmooth/
66
+ plans/
67
+ plan-07.md # plan files live here, versioned in git
68
+ plan-07-tasks.xml # [Local] task state (sibling to plan file)
69
+ ```
70
+
71
+ Plans are markdown files. They contain: narrative, design decisions, architecture notes, open questions, and references. Code never goes here.
72
+
73
+ ---
74
+
75
+ ## What Lives Where — Quick Reference
76
+
77
+ | Content | Location | Reason |
78
+ |---|---|---|
79
+ | Plan narrative, design decisions, references | `.shipsmooth/plans/*.md` in git | Needs diffs, version history, co-evolution with code |
80
+ | Task state (done / not done) | `[Linear]` Linear `[agent]` project · `[Local]` `.shipsmooth/plans/plan-{N}-tasks.xml` | Needs status tracking and human review |
81
+ | Feature definitions | `[Linear]` Linear permanent backlog · `[Local]` Noted in plan file Context section | Permanent, human-curated |
82
+ | Link between plan version and tasks | `[Linear]` Tag-based GitHub permalink in Linear issue description · `[Local]` `<created-from>` child element in XML | Immutable, survives branch lifecycle |
83
+ | This workflow | `~/.claude/skills/start/SKILL.md` | Loaded by agent at task start |
84
+ | Repo-specific overrides | `CLAUDE.md` in repo root | Workspace name, project conventions, etc. |
85
+
86
+ ---
87
+
88
+ ## Git Tagging Convention
89
+
90
+ Every time a plan file is committed, immediately create and push a version tag:
91
+
92
+ ```bash
93
+ ${XDG_CACHE_HOME:-~/.cache}/shipsmooth/0.3.27/bin/shipsmooth plan tag --plan {N} --kind version
94
+ # prints: git push origin plan-{N}-v{K} — run that line to push
95
+ ```
96
+
97
+ On clean completion:
98
+ ```bash
99
+ ${XDG_CACHE_HOME:-~/.cache}/shipsmooth/0.3.27/bin/shipsmooth plan tag --plan {N} --kind complete
100
+ # prints: git push origin plan-{N}-complete
101
+ ```
102
+
103
+ On abandonment:
104
+ ```bash
105
+ ${XDG_CACHE_HOME:-~/.cache}/shipsmooth/0.3.27/bin/shipsmooth plan tag --plan {N} --kind abandoned
106
+ # prints: git push origin plan-{N}-abandoned
107
+ ```
108
+
109
+ Tag naming: `plan-{N}-v{version}` for iterations, `plan-{N}-complete` for clean closeout, `plan-{N}-abandoned` for abandonment. `plan tag --kind version` refuses to re-tag if the computed tag already exists — commit more changes first.
110
+
111
+ ---
112
+
113
+ ## Linear Structure
114
+
115
+ `[Linear]` only. Skip this section in Local mode.
116
+
117
+ ### Permanent Backlog Project
118
+ - Named e.g. `AppName — Backlog & Roadmap`
119
+ - Contains feature issues only
120
+ - Human-created and human-prioritised
121
+ - Never deleted, survives all plan lifecycles
122
+
123
+ ### Transient Agent Projects
124
+ - Named: `[agent] {N} · {short-description}` e.g. `[agent] 07 · home-accounts-settings-bottom-tabs`
125
+ - Created per plan, archived after completion
126
+ - Project description must contain:
127
+ - Link to the permanent backlog feature issue(s) it delivers
128
+ - Permalink to the plan file using the tag-based commit hash URL (see below)
129
+ - Brief plan narrative / design rationale
130
+
131
+ ### Tag-based GitHub permalink format
132
+ ```
133
+ https://github.com/{org}/{repo}/blob/{tag-commit-hash}/.shipsmooth/plans/plan-07.md
134
+ ```
135
+
136
+ Resolve the commit hash for a tag:
137
+ ```bash
138
+ git rev-list -n 1 plan-07-v1
139
+ ```
140
+
141
+ Use this hash (not the tag name) in Linear links — it is immutable and survives branch deletion, rebases, and squash merges.
142
+
143
+ ---
144
+
145
+ ## Phase 0 — Intake
146
+
147
+ **First, check for an active plan — do not start a new one on top of it.**
148
+ Before treating any message as a fresh kickoff, look for a plan that is already
149
+ in flight. Glance at the plans on disk and their state — especially the **latest**
150
+ one:
151
+
152
+ - find the plans directory first — in **external** mode (the default) it is not
153
+ `.shipsmooth/plans/` but a separate state dir. Ask the CLI:
154
+ `${XDG_CACHE_HOME:-~/.cache}/shipsmooth/0.3.27/bin/shipsmooth store info --json` reports `plansDir` (when `status` is `ready`).
155
+ List `plansDir`'s `plan-*-tasks.xml` (the highest plan number is the most likely
156
+ candidate); if `status` is **not** `ready`, state is not set up yet — run the
157
+ **first-run handshake** below before going further (there is no active plan to resume).
158
+ - check that plan's state with
159
+ `${XDG_CACHE_HOME:-~/.cache}/shipsmooth/0.3.27/bin/shipsmooth plan resume --plan {N}` — a plan-level status of `active` /
160
+ `in-review` with tasks still `pending` / in-progress means work is unfinished.
161
+
162
+
163
+ > **First-run handshake `[Local]`.** When a `${XDG_CACHE_HOME:-~/.cache}/shipsmooth/0.3.27/bin/shipsmooth` command reports that state is
164
+ > not set up — `store info --json` returns a `status` other than `ready`, or a
165
+ > state-dependent command exits with a `status:"needs-decision"` / `status:"unresolvable"`
166
+ > JSON line (exit 10 / 11) — do **not** treat it as a normal error. Run this handshake. The
167
+ > CLI never prompts on stdin; presenting the choice and capturing a real human answer is the
168
+ > skill's job.
169
+ >
170
+ > **`needs-decision` → ask the user, then act:**
171
+ >
172
+ > 1. **Show the CLI's `prompt` field verbatim.** The `needs-decision` JSON carries a ready-to-
173
+ > display `prompt` (the question, each option with its real resolved path, the recommended
174
+ > one marked, and — for a clean first run — a note that a different folder may be given).
175
+ > Render it as-is; do not rewrite it or invent paths.
176
+ > 2. **Wait for a real answer. Never auto-pick.** The CLI only ever creates a state location
177
+ > on a fresh human "yes" (consented creation). Default toward the recommended option, but
178
+ > the user may pick the alternative or name a different folder.
179
+ > 3. **Re-invoke the CLI to act** on the choice, matching the `choice` token from the chosen
180
+ > option:
181
+ > ```bash
182
+ > # external (recommended) — accept the proposed folder:
183
+ > ${XDG_CACHE_HOME:-~/.cache}/shipsmooth/0.3.27/bin/shipsmooth store init --choice external --json
184
+ > # external — a different folder the user named:
185
+ > ${XDG_CACHE_HOME:-~/.cache}/shipsmooth/0.3.27/bin/shipsmooth store init --choice external --path <user's folder> --json
186
+ > # keep it inside this repo:
187
+ > ${XDG_CACHE_HOME:-~/.cache}/shipsmooth/0.3.27/bin/shipsmooth store init --choice in-repo --json
188
+ > # a configured external folder went missing — recreate it:
189
+ > ${XDG_CACHE_HOME:-~/.cache}/shipsmooth/0.3.27/bin/shipsmooth store init --choice recreate --path <path from the option> --json
190
+ > ```
191
+ > `store init` creates the chosen location, writes the config entry, and prints the
192
+ > `ready` shape — read its `plansDir` for where plan files now live.
193
+ >
194
+ > **`unresolvable` → stop.** The situation cannot be settled automatically (e.g. a legacy
195
+ > `.agents/` tree). Show the JSON `message` to the user and stop; do not try to fix it. The
196
+ > message says what to correct by hand.
197
+ >
198
+ > **Already settled → silent.** When resolution is `ready`, none of this applies; the command
199
+ > just runs. Steady state never re-prompts.
200
+
201
+
202
+ If any plan looks active, **surface it as a question** before doing anything
203
+ else: name the plan and ask the user whether to continue it or deliberately
204
+ start a new one. Do not auto-create a new branch or plan file while a plan
205
+ appears to be in flight. *(This is a judgment call for now — there is no single
206
+ deterministic "is any plan active" check; tracked as a known gap. Lean toward
207
+ asking when unsure.)*
208
+
209
+ Once you have confirmed there is no active plan to resume, decide how much
210
+ context you actually have. The kickoff sets the mode for everything that
211
+ follows — choose it deliberately.
212
+
213
+ **The thin-vs-rich test.** Context is **thin** when *all three* hold:
214
+
215
+ - the kickoff message is short (roughly two sentences or fewer), **and**
216
+ - no spec, PRD, or plan body is attached, **and**
217
+ - there is no substantial planning earlier in this conversation.
218
+
219
+ If any one of these is absent, context is **rich** — skip to Phase 1.
220
+
221
+ ### Thin context → quickstart, then hand back
222
+
223
+ A short kickoff means the user wants to move fast and iterate. He is signalling
224
+ that he will add detail later or work exploratorily. **Do not slow him down.**
225
+ Run **one** command and hand back:
226
+
227
+ ```bash
228
+ ${XDG_CACHE_HOME:-~/.cache}/shipsmooth/0.3.27/bin/shipsmooth plan quick --desc "{short-description}"
229
+ # derives the next plan number, creates + checks out t/{N}-{slug},
230
+ # and writes a stub .shipsmooth/plans/plan-{N}.md.
231
+ # It does NOT commit — that is intentional.
232
+ ```
233
+
234
+ Then relay the command's output to the user in one or two lines — the branch and
235
+ stub plan file now exist on the branch for him to flesh out — and **stop, return
236
+ control to the chat.**
237
+
238
+ `plan quick` owns the whole thin-path scaffold: plan-number derivation, branch
239
+ creation, and writing the stub file. **You do not author the plan file or run
240
+ git yourself.** In particular, **do not commit** what `plan quick` wrote — it
241
+ deliberately leaves the stub uncommitted so the user commits on his own terms
242
+ (and so a missing git identity can't strand the quickstart). There is no
243
+ follow-up step after `plan quick` on the thin path.
244
+
245
+ **Do not**, on the thin path:
246
+
247
+ - hand-author the stub plan file, then `git add`/`git commit` it — `plan quick`
248
+ already wrote it and intentionally left it uncommitted; adding a commit is the
249
+ exact mistake this path exists to prevent,
250
+ - run `git commit`, `git tag`, `git push`, or configure git identity,
251
+ - investigate the repository or read source files to "understand the feature",
252
+ - ask clarifying questions or present an options questionnaire,
253
+ - estimate per-task risk, run `plan init`, tag, or set up task tracking.
254
+
255
+ Those belong to the rich-context pass (Phase 1), reached once the user has
256
+ fleshed out the stub.
257
+
258
+ ### Worked example (target vs. anti-target)
259
+
260
+ Kickoff: *"start a new plan, feature is X"* — no spec, no prior planning.
261
+
262
+ - ✅ **Target:** run `${XDG_CACHE_HOME:-~/.cache}/shipsmooth/0.3.27/bin/shipsmooth plan quick --desc "X"` → relay its
263
+ output (branch + stub created, uncommitted) → **stop**.
264
+ - ❌ **Anti-target #1:** run several rounds of repo investigation, then fire a
265
+ multi-part questionnaire asking the user to choose the approach, before
266
+ creating anything. This interrogates the user at the moment he wanted to move
267
+ fast. *Do not do this.*
268
+ - ❌ **Anti-target #2:** after `plan quick` (or instead of it), hand-write the
269
+ stub file and `git commit` it. The commit is unrequested git work that can
270
+ fail on an unconfigured identity and strand the flow. *Do not do this.*
271
+
272
+ ---
273
+
274
+ ## Phase 1 — Plan, Calibrate, & Commit
275
+
276
+ This is the **rich-context** path, reached either directly when kickoff context
277
+ is already rich, or after the user has fleshed out a Phase 0 stub.
278
+
279
+ **You do not write or run any implementation code during this phase.**
280
+
281
+ 1. **Draft Plan:** Write or update the plan file at `.shipsmooth/plans/plan-{N}.md`.
282
+ 2. **Risk Analysis:**
283
+ - For every task in the plan, suggest a **Default Risk Level** (Low, Medium, or High) with a one-sentence justification.
284
+ 3. **Collaborative Calibration:**
285
+ - **Stop.** Ask the human: *"I've estimated these risk levels. Do you want to override any of them?"*
286
+ - The human's choice becomes the **Actual Risk ($R$)**.
287
+ 4. **Risk-Sorted Task Ordering:**
288
+ - Re-order tasks in the plan file in **descending order of risk** ($High \to Med \to Low$).
289
+ - *Exception:* If a Low-risk task is a hard technical dependency for a High-risk task, the dependency must come first.
290
+ 5. **Commit & Tag:**
291
+ ```bash
292
+ git add .shipsmooth/plans/plan-07.md
293
+ git commit -m "plan(07): risk-calibrated plan for [short-description]"
294
+ git push origin t/{issue-id}-{short-description}
295
+ # Lefthook auto-tags plan-07-v1 and pushes it
296
+ ```
297
+ 6. **Verify Preconditions:**
298
+ ```bash
299
+ ${XDG_CACHE_HOME:-~/.cache}/shipsmooth/0.3.27/bin/shipsmooth plan preflight --plan {N}
300
+ # Exits 0 (PASS) or 1 (FAIL: dirty tree / missing version tag). Warns on unpushed branch.
301
+ ```
302
+ 7. **Create Task Tracking Infrastructure:**
303
+ - `[Linear]` Create the `[agent]` Linear project. Create Linear issues from the **risk-sorted** plan tasks. Each issue description must include the **Risk Level** ($L/M/H$) and the tag-based GitHub URL of the specific plan version that generated it.
304
+ - `[Local]` Run `${XDG_CACHE_HOME:-~/.cache}/shipsmooth/0.3.27/bin/shipsmooth plan init --plan {N} --tasks-from .shipsmooth/plans/plan-{N}.md` to generate `.shipsmooth/plans/plan-{N}-tasks.xml`. Commit the XML file immediately after creation. **Never hand-write this XML file — always generate it via the CLI. The format uses child elements, not attributes.** The CLI requires task headings in the form `### Task N: Name [Risk]` where `N` is a positive integer — alphanumeric IDs (e.g. `01-A`) are not supported. To express a dependency between tasks, add a `*Depends-on: P[,Q...]*` line anywhere in the task body before the next heading (e.g. `*Depends-on: 1,3*`). The CLI parses this line and writes `<depends-on>` into the XML automatically.
305
+ - Organise tasks as **thin vertical slices** in both modes.
306
+ 8. **Final Review & Go-ahead:**
307
+ - `[Linear]` **Stop.** Post to the Linear project that the risk-sorted plan is ready for review.
308
+ - `[Local]` **Stop.** Tell the human the XML task file has been committed and the plan is ready for review.
309
+ - **Wait for explicit human go-ahead before proceeding to Phase 2.**
310
+
311
+ ---
312
+
313
+ ## Phase 2 — Execute
314
+
315
+ **Session-resume pre-flight `[Local]`** — If you are picking up a plan that was started in a previous session, run this before doing anything else:
316
+
317
+ ```bash
318
+ ${XDG_CACHE_HOME:-~/.cache}/shipsmooth/0.3.27/bin/shipsmooth plan resume --plan {N}
319
+ # Prints: XML file present check and task state summary.
320
+ ```
321
+
322
+ Only proceed once you know which tasks are done and which are next.
323
+
324
+ **Where the plan files live `[Local]`** — Do not assume plan narratives are under
325
+ `.shipsmooth/plans/` in the project repo. In **external** mode (the default) the project
326
+ repo stays untouched and the plan files live in a separate state directory. Ask the CLI
327
+ where to read them — it is the source of truth — rather than guessing:
328
+
329
+ ```bash
330
+ ${XDG_CACHE_HOME:-~/.cache}/shipsmooth/0.3.27/bin/shipsmooth store info --json
331
+ # -> {"status":"ready","mode":"external","stateRoot":"...","plansDir":"<dir>/plans"}
332
+ # Read plan narratives (plan-{N}.md) and task XML from the reported `plansDir`.
333
+ # If status is not "ready", state is not set up yet — handle per first-run (Phase 0).
334
+ ```
335
+
336
+ Load the plan narrative for `{N}` from the reported `plansDir` before executing, the same
337
+ as you would for an in-repo plan — `mode: in-repo` simply reports the in-repo `plansDir`.
338
+
339
+ ---
340
+
341
+ **Step 0: Create a branch**
342
+
343
+ Create a branch named after the primary issue for this plan:
344
+ ```bash
345
+ ${XDG_CACHE_HOME:-~/.cache}/shipsmooth/0.3.27/bin/shipsmooth plan branch --issue {issue-id} --desc "{short-description}"
346
+ # prints: git push -u origin t/{issue-id}-{slug} — run that line to push
347
+ ```
348
+ All task commits go on this branch. The `t/` prefix stands for "task". Usernames are omitted — the task identity is what matters long-term.
349
+
350
+ **Before writing any code**, confirm the test coverage threshold with the human (default: 95%). Record the agreed value before proceeding.
351
+
352
+
353
+ > **Commit-message convention (code commits in the project repo).** How you word a code
354
+ > commit depends on the resolved storage mode. Check it once per session with
355
+ > `${XDG_CACHE_HOME:-~/.cache}/shipsmooth/0.3.27/bin/shipsmooth store info --json` and read `mode`.
356
+ >
357
+ > - **`in-repo` mode:** keep the prefixed convention — `task(N): <short description>` and
358
+ > `draft(N): de-risk <task name>`. The plan/task history is shipsmooth's own and lives
359
+ > alongside the code, so the prefixes are welcome.
360
+ > - **`external` (standalone) mode:** the project repo must stay **zero-trace**. Write
361
+ > plain, feature-oriented messages with **no `plan(N)`/`task(N)`/`draft(N)` prefix** and
362
+ > no plan or task references — e.g. `Add retry to upload client`, not
363
+ > `task(3): add retry`. This applies to **every** project-repo commit, including the
364
+ > preamble integration-test commit (write `Add end-to-end test for <feature>`, not a
365
+ > `plan(N)`-referencing message).
366
+ >
367
+ > Traceability is **not lost** in standalone mode: the task↔commit link lives in the state
368
+ > repo's task XML, recorded via `task set-commit`. State-repo commits (the plan file and
369
+ > task XML — the `plan(N): …` commit) keep full plan/task info; that history is shipsmooth's
370
+ > own and invisible to the user. This convention governs only the **project repo's** code
371
+ > commits.
372
+
373
+
374
+ ### Preamble: integration tests (once, before any task)
375
+
376
+ 1. Write 1–2 integration tests that exercise the feature end-to-end. No more than two.
377
+ 2. Commit and push them with no implementation — they must fail (red). Word the commit message per the **commit-message convention** above (in standalone mode, no `plan(N)`/`task(N)` reference — this is a project-repo commit too). `[Linear]` Reference the Linear project in the commit message.
378
+ 3. Confirm red state:
379
+ ```bash
380
+ # run your project's test command, e.g.:
381
+ npm test # or: pytest, go test ./..., etc.
382
+ ```
383
+ If a test passes at this point, it is testing the wrong thing. Fix or discard it before continuing.
384
+
385
+ ### Per-task loop (The De-risk & Harden Cycle)
386
+
387
+ For every task in the risk-sorted sequence, apply the appropriate sub-phases:
388
+
389
+ #### High and Medium risk tasks — De-risk & Harden Cycle
390
+
391
+ ##### Step A: De-risking (Spiral Phase)
392
+ - **Goal:** Validate logic and architectural direction. Ignore "Implementation Quality" rules.
393
+ - Write at least one failing test (and not more than 3) that targets the core logic (preserving
394
+ Core Invariant #6).
395
+ - Implement just enough to prove the approach works. Focus on the core complexity.
396
+ - Commit per the **commit-message convention**: `draft(N): de-risk [task name]` in in-repo mode; in standalone mode a plain feature message with no `draft(N)`/`task(N)` reference.
397
+ - `[Linear]` Post a comment on the Linear issue notifying the human the draft is ready.
398
+ - `[Local]` Run `${XDG_CACHE_HOME:-~/.cache}/shipsmooth/0.3.27/bin/shipsmooth task status --plan {N} --task {id} --status de-risked` and `${XDG_CACHE_HOME:-~/.cache}/shipsmooth/0.3.27/bin/shipsmooth task comment --plan {N} --task {id} --message "De-risk draft ready for review"`.
399
+ - **Wait for explicit approval of the approach.**
400
+
401
+ ##### Step B: Hardening (Quality Phase)
402
+ - **Goal:** Achieve technical excellence, human readability, and coverage threshold.
403
+ - Refactor the de-risked code for readability, performance, and project patterns. If skill
404
+ "experimental-refine-dev" exists, then use it to improve the design.
405
+ - Follow Test Driven Development if possible: Write only one test at a time, then the implementing code
406
+ and then refactor.
407
+ - Keep doing Step B until coverage meets the agreed threshold (and if "experimental-refine-dev" skill exists,
408
+ quality conforms to its instructions):
409
+ ```bash
410
+ # example — adjust to your toolchain:
411
+ npm test -- --coverage --coverageThreshold='{"global":{"lines":95}}'
412
+ ```
413
+ - Commit the completed task (tests + implementation), wording the message per the **commit-message convention** (standalone → plain feature message, no `task(N)` prefix):
414
+ ```bash
415
+ git commit -m "task(N): <short description>" # in-repo mode; standalone: plain feature message
416
+ git push origin t/{issue-id}-{short-description}
417
+ ```
418
+ This creates a stable rollback point. A human reviewing the PR can check out this commit to inspect each task in isolation.
419
+ - `[Linear]` Mark the Linear issue **Agent Coded**.
420
+
421
+
422
+ - `[Local]` Run `${XDG_CACHE_HOME:-~/.cache}/shipsmooth/0.3.27/bin/shipsmooth task status --plan {N} --task {id} --status agent-coded` and `${XDG_CACHE_HOME:-~/.cache}/shipsmooth/0.3.27/bin/shipsmooth task set-commit --plan {N} --task {id} --commit $(git rev-parse HEAD)`.
423
+
424
+
425
+ #### Low risk tasks — Single-pass (current behavior)
426
+
427
+ 1. Write the unit test(s) for this task. Commit them failing (red).
428
+ 2. Implement the task. Run tests until green.
429
+ 3. Check coverage meets the agreed threshold:
430
+ ```bash
431
+ # example — adjust to your toolchain:
432
+ npm test -- --coverage --coverageThreshold='{"global":{"lines":95}}'
433
+ ```
434
+ Do not proceed until coverage passes.
435
+ 4. Commit the completed task (tests + implementation), wording the message per the **commit-message convention** (standalone → plain feature message, no `task(N)` prefix):
436
+ ```bash
437
+ git commit -m "task(N): <short description>" # in-repo mode; standalone: plain feature message
438
+ git push origin t/{issue-id}-{short-description}
439
+ ```
440
+ - `[Linear]` Mark the Linear issue **Agent Coded**. No draft review needed.
441
+
442
+
443
+ - `[Local]` Run `${XDG_CACHE_HOME:-~/.cache}/shipsmooth/0.3.27/bin/shipsmooth task status --plan {N} --task {id} --status agent-coded` and `${XDG_CACHE_HOME:-~/.cache}/shipsmooth/0.3.27/bin/shipsmooth task set-commit --plan {N} --task {id} --commit $(git rev-parse HEAD)`. No draft review needed.
444
+
445
+
446
+ ---
447
+
448
+ - **Minor deviation** (task split, reorder, clarification):
449
+ - `[Linear]` Update the Linear issue(s), add a deviation comment explaining why, continue.
450
+ - `[Local]` Run `${XDG_CACHE_HOME:-~/.cache}/shipsmooth/0.3.27/bin/shipsmooth task deviation --plan {N} --task {id} --type minor --message "..."`, continue.
451
+ - **Major deviation** (fundamental plan problem, architecture issue, blocked): Stop immediately.
452
+ - `[Linear]` Post a Linear project update. Set project health to **"At Risk"**.
453
+ - `[Local]` Run `${XDG_CACHE_HOME:-~/.cache}/shipsmooth/0.3.27/bin/shipsmooth plan update --plan {N} --blocked --message "..."`.
454
+ - Wait for the human to revise the plan file, commit, push, and give a new go-ahead.
455
+
456
+ Never autonomously modify the `.shipsmooth/plans/` file during execution. If a plan change is needed, surface it and wait.
457
+
458
+ ---
459
+
460
+ ## Plan Closeout
461
+
462
+ ### Clean Completion
463
+ ```bash
464
+ git tag plan-07-complete
465
+ git push origin plan-07-complete
466
+ ```
467
+ - `[Linear]` Close all Linear issues in the `[agent]` project. Mark `[agent]` project complete and archive it. Update the permanent backlog feature issue to reflect delivery (link to completing PR, note what was delivered).
468
+ - `[Local]` Run `${XDG_CACHE_HOME:-~/.cache}/shipsmooth/0.3.27/bin/shipsmooth plan update --plan {N} --status complete --message "Plan complete."`. Commit the final XML state. Update the permanent backlog feature issue (if tracked externally) or note delivery in the plan file.
469
+
470
+ ### Completion with Loose Ends
471
+ - `[Linear]` Label unresolved issues `needs-triage`. Set `[agent]` project to **"In Review"**. Post a project update listing each open issue and why it's unresolved. Wait for human to review: they will promote worthy issues to the permanent backlog or discard them. Human marks the project complete and archives it.
472
+ - `[Local]` Run `${XDG_CACHE_HOME:-~/.cache}/shipsmooth/0.3.27/bin/shipsmooth task status --plan {N} --task {id} --status needs-triage` for each unresolved task. Run `${XDG_CACHE_HOME:-~/.cache}/shipsmooth/0.3.27/bin/shipsmooth plan update --plan {N} --status in-review --message "..."`. Commit the XML. Wait for human to review.
473
+
474
+ ### Abandonment
475
+ - Human commits a plan file deletion with a commit message referencing the superseding plan number
476
+ - You tag the deletion commit:
477
+ ```bash
478
+ git tag plan-07-abandoned
479
+ git push origin plan-07-abandoned
480
+ ```
481
+ - **Do not delete any earlier tags** (`plan-07-v1`, `plan-07-v2`, etc.) — they are the audit trail
482
+ - `[Linear]` Surface all open tasks for human triage. Migrate worthy tasks to the permanent backlog with a note: "Partial delivery — see plan-07-abandoned, superseded by plan-{M}". Archive the `[agent]` project with a closing note referencing the deletion commit hash and the superseding plan.
483
+ - `[Local]` Run `${XDG_CACHE_HOME:-~/.cache}/shipsmooth/0.3.27/bin/shipsmooth plan update --plan {N} --status abandoned --message "Superseded by plan-{M}."`. Commit the final XML state.
484
+
485
+ ---
486
+
487
+ ## Audit Trail
488
+
489
+ `[Linear]` Record in every Linear issue:
490
+
491
+ | Event | What to store in the issue |
492
+ |---|---|
493
+ | Task created | `github.com/.../blob/{plan-07-v1-hash}/.shipsmooth/plans/plan-07.md` |
494
+ | Task closed / obsoleted | `github.com/.../blob/{plan-07-vN-hash}/.shipsmooth/plans/plan-07.md` + one-line reason |
495
+
496
+ `[Local]` The XML file is the audit trail. `<created-from>` and `<closed-at-version>` child elements on each `<task>` serve the same role. The XML is versioned in git, so `git diff` between two plan tags shows exactly what changed.
497
+
498
+ If the creation version equals the closeout version, the plan never changed during execution. If they differ, the git diff between the two tag hashes shows exactly what changed and why.
499
+
500
+ Feature issues in the permanent backlog should accumulate references to every plan that contributed to them — this gives a full delivery history across the feature's lifetime.
501
+
502
+ ---