ALWAYS loaded FIRST when starting any new task — non-negotiable, before any tool use, regardless of whether the task seems trivial. Triggers on EVERY user turn: questions, requests, code, debugging, design, build, create, make, write, fix, add, change, set up, plan, implement, refactor, install, deploy, schema, SQL, database, YAML, JSON, config, API, function, class, test, document, hello, help me. Workflow: (1) If other skills apply (domain, language, framework, integration), load them FIRST...
Installs into .claude/skills of the current project.
Are you the author of Writing Plans?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/yizhao95-writing-plans)
---
name: writing-plans
description: "ALWAYS loaded FIRST when starting any new task — non-negotiable, before any tool use, regardless of whether the task seems trivial. Triggers on EVERY user turn: questions, requests, code, debugging, design, build, create, make, write, fix, add, change, set up, plan, implement, refactor, install, deploy, schema, SQL, database, YAML, JSON, config, API, function, class, test, document, hello, help me. Workflow: (1) If other skills apply (domain, language, framework, integration), load them FIRST with the Skill tool. (2) Then size the task: count the anticipated steps and check whether a sub-agent will be dispatched (Agent tool). (3) GATING RULE: run the full writing-plans body (compose plan-input.json + run scripts/publish-plan.sh) IF AND ONLY IF estimated_steps >= 3 OR a sub-agent will be dispatched; otherwise note 'trivial — no plan needed' and proceed. (4) Whenever a plan IS published, IMMEDIATELY trigger executing-plans — never defer it, never skip it. The chain writing-plans → executing-plans is atomic."
---
# Writing Plans
SQLite-backed plan creation. The agent **composes** a plan-input file (JSON or YAML) and **publishes** it via `scripts/publish-plan.sh`. After publish, all revisions/updates/deviations belong to the **executing-plans** skill — this skill never modifies an already-published plan.
## ⚡ When to use this skill
Trigger the full workflow when:
- `estimated_steps >= 3`, **OR**
- a sub-agent will be dispatched (Agent tool)
Otherwise: announce "trivial — no plan needed" and proceed directly. (Trivial 1-step tasks may still publish for audit trail.)
## 🔄 The workflow
1. **Load other skills first.** Check the available skills for the task domain + run the standing-skill checklist ([`reference/skill-source-enum.md`](reference/skill-source-enum.md)). Load every skill that even might apply with the Skill tool. Do NOT proceed until they're all loaded.
2. **Ensure the dashboard is up.** Run the idempotent script — never read the underlying webapp launcher:
```bash
bash ${CLAUDE_PLUGIN_ROOT}/skills/writing-plans/scripts/ensure-dashboard.sh
```
3. **Draft `plan-input.json`** (or `.yaml`). Schema: `plan-input.schema.json`. Worked example: `plan-input.example.json`. Required fields: `goal`, `prefix`, `steps[]`. Strongly recommended: `user_query` (verbatim), `skills[]` (every iron-law + topic skill activated).
4. **Self-review** (5-bullet checklist — see `reference/step-quality.md`):
- [ ] Spec coverage — every requirement maps to a step
- [ ] No placeholders ("TBD", "handle errors", "similar to step N")
- [ ] TDD pairing — every `CODE` step preceded by a `TEST` step (`reference/tdd-test-spec.md`)
- [ ] Skills declared — every iron-law + topic skill in `skills[]`
- [ ] 10k rule respected — any step >10k context tagged `SUB_AGENT` with `ctx_estimate_k` (`reference/10k-context-rule.md`)
5. **Publish to SQLite** — this skill's only side effect on the database:
```bash
bash ${CLAUDE_PLUGIN_ROOT}/skills/writing-plans/scripts/publish-plan.sh path/to/plan-input.json
```
The script returns `{plan_id, step_ids, review_step_id, skills_recorded}` as JSON.
`review_step_id` is the auto-appended `<plan>-REVIEW` step (migration 006). It
flips to COMPLETED or FAILED automatically when the last regular step terminates
— no `finish-plan.sh` call needed. See `executing-plans/SKILL.md` for details.
6. **Hand off to `executing-plans`** — immediately, atomically. No pause, no user confirmation. Anything that happens to the plan after this moment (revise, update, modify, deviate, retry, decompose) is **executing-plans** territory.
## 🚨 Mandatory rules
| Rule | Where it's enforced |
|---|---|
| Every `CODE` step preceded by a `TEST` step describing behavior + edge cases + assertions | `reference/tdd-test-spec.md` |
| Every step description names WHO/WHAT/HOW (no placeholders) | `reference/step-quality.md` |
| Every activated skill recorded in `skills[]` with one of 4 valid sources | `reference/skill-source-enum.md` |
| Steps >10k context tagged `SUB_AGENT` with `ctx_estimate_k` and `[parallel-safe]` when applicable | `reference/10k-context-rule.md` |
| When `project` is set, `declared_targets` is MANDATORY (publish errors out if missing) | `scripts/publish_plan.py` (Phase D) |
## 🛰️ Plan-time pre-flight (provLedger Phase D)
When a plan-input names a tracked **`project`** (one in
`~/skill-workspace/project-graphs/projects.json`), publishing becomes one atomic
act with **forward impact analysis** — you cannot publish a project-scoped plan
without declaring what it touches:
- Add `"project": "<name>"` and `"declared_targets": ["<symbol>", ...]` to the
plan-input. `declared_targets` is the symbols/columns you judge the change will
touch (qualified_name or bare name).
- `publish_plan.py` then runs `impact_preflight.compute_impact_context` against
that project's state-graph as part of the same publish (since DP phase 2 right
after the Plan row exists, so every record it surfaces is a `read_hit` of
that plan) and stores the result on `Plans.impact_context`. Missing
`declared_targets` → **non-zero exit**.
- The analysis unions your declared targets with a deterministic keyword
reverse-lookup of `user_query`, then per symbol: **existing** → callers /
output_consumers / dtype_map / lineage_downstream; **missing** → flagged
`new` (a useful signal: genuinely new, or a misremembered name). It also
surfaces **upstream-data assumptions** (sql_table/api_source assumed schema +
a fail-fast load-seam recommendation) and ledger reminders (Phase E — see below).
- **Capability boundary:** strongest for *modifying existing code*. For brand-new
modules it degrades to describing the existing functions the new code will call
— the graph only describes code that exists.
- Project-less plans skip all of this and publish exactly as before.
## 🏷️ Project attribution (FL-014, phase 3.5)
Which registered project a plan belongs to is **explicit** (`Plans.project` +
`project_source`), decided once at publish, in this order:
1. `"project": "<name>"` in the plan-input (must be registered) — or
`"project": "none"` to say "no project, no state-graph review" on purpose;
2. otherwise the **repo you publish from**: when `cwd` is inside a git repo whose
toplevel is a registered project's `repo`, the plan is attributed to it
(`project_source = cwd`) — the goal does **not** need to mention the project;
3. otherwise NULL, with a ⚠️ on stderr (the plan closes without a review).
A declared project that contradicts the cwd repo fails the publish ("pick one").
Plans published before this column existed are matched once from goal text at
close and labelled `legacy`.
## 📓 Decision-Memory Ledger (provLedger Phase E)
The **ledger track** — the home for everything the gate track honestly *cannot*
check: semantic contracts, data-engineering rationale, and past failures. It is
the "never repeat a mistake" half of provLedger.
- The ledger stores **decisions** (+ their rationale) and **anti-patterns**
(failures + their cause), scoped to a project, with subject symbols/tables +
free keywords for matching.
- It is **manual and opt-in** — you add entries by hand as real decisions get
made and real failures get hit (never auto-populated):
```
bash scripts/ledger-add.sh add --project <name> --kind decision \
--statement "rolling-window split, not random split" \
--rationale "random split leaks temporal information" \
--subjects train_test_split,split --keywords split,rolling,temporal
bash scripts/ledger-add.sh list --project <name>
```
- At plan time (the Phase D step-4 match point), a **deterministic lexical**
fuzzy-match scores active entries against the plan's declared_targets +
user_query and surfaces the top matches as `impact_context.ledger_reminders`.
A decision → `reminder: <statement> — because <rationale>; confirm before
changing.` An anti-pattern → `warning: <statement> was tried and failed —
<rationale>; reconsider before proceeding.`
- **Reminders are advisory — NEVER blocks.** A plan that triggers a reminder
still publishes (exit 0); the agent is simply reminded *why* the current
choice exists.
- **Honest boundary:** matching is lexical/deterministic (no embeddings/LLM), so
recall is bounded by the keywords a human recorded — the ledger is only as good
as what gets written into it. That is by design: grow it gradually from real
decisions and real failures.
## 📰 The plan headline (DP phase 2) — read it, answer it, never fear it
Publishing a project-scoped plan now ends with a **headline** on stderr (and
`result["headline"]` in the JSON): the two-layer pre-change check — the
targets' own history (active constraints, rejected paths, failed prior claims,
removed upstream) and their blast radius (consumers, unverified upstream,
downstream constraints) — as findings with a severity and a **tier label**
(`stated` / `asserted` / `derived` / `observed`), plus a one-line summary
(`n findings unanswered · m records shown · k adopted`).
- **It never blocks.** `publish-plan.sh` exits 0 with unanswered findings; they
are counted, and at close every blocking finding you proceeded past or left
unanswered becomes a survival expectation. The single exception is a **human**
constraint declared `block: true` in `provledger-extensions.json` (exit 5 until
someone answers it).
- **Progressive loading.** The headline and the context pack are summaries with
counts; what the budget cut appears as `n more … — provledger why <qn> --all`.
Expand (run `provledger why <qn>`) when `blocking > 0`, when a target carries
constraints, or when a neighbour carries rejected paths. Do **not** expand when
the only cuts are minor reasons.
- **Answer blocking findings** with `executing-plans/scripts/headline-respond.sh`
(`action: revise | proceed`, one sentence of rationale, `cites: [reason_id]`);
every record you cite becomes *adopted* (`influence`). A second answer to the
same finding is refused — recompute the headline to change your mind.
- **`headline_notes`** in the plan-input lets you add your own `similar_intent`
finding: `[{"finding_kind": "similar_intent", "cites": [reason_id], "text": "…"}]`
— the cited records must exist (I3), the finding is tiered `asserted`.
- Wording: a record was **shown** (`read_hit`) or **adopted** (`influence`).
Nothing here says *read*.
## 📚 Reference index
- [`reference/step-types.md`](reference/step-types.md) — the 6 valid step types (THINKING / ANALYSIS / CODE / COMMAND / DOCUMENTATION / SUB_AGENT)
- [`reference/skill-source-enum.md`](reference/skill-source-enum.md) — the 4 valid skill sources + always-on iron-laws
- [`reference/10k-context-rule.md`](reference/10k-context-rule.md) — when to dispatch a SUB_AGENT
- [`reference/step-quality.md`](reference/step-quality.md) — specificity bar, granularity, no-placeholders
- [`reference/tdd-test-spec.md`](reference/tdd-test-spec.md) — what every TEST step description must contain
- [`plan-input.schema.json`](plan-input.schema.json) — formal schema for the input file
- [`plan-input.example.json`](plan-input.example.json) — copy-paste-edit starting point
- [`plan-document-reviewer-prompt.md`](plan-document-reviewer-prompt.md) — optional reviewer agent prompt
## 🔌 Boundary with executing-plans
This skill **only** writes the plan to SQLite. It does NOT:
- modify a plan after publish
- record `record-skill` (deferred-load activations) — that's executing-plans
- run `start-step` / `complete-step` / `deviate` — that's executing-plans
- enforce circuit breakers (immutability, max_revisions, depth_limit) at runtime — that's executing-plans
If you find yourself wanting to "update the plan I just wrote", stop. The next call belongs to executing-plans.
## 🛠️ Tests
```bash
"${PROVLEDGER_VENV:-$HOME/skill-workspace/.venv}/bin/python" -m pytest \
"${CLAUDE_PLUGIN_ROOT}/skills/writing-plans/tests" -q
```
They cover the scripts (publish, pre-flight, ledger, dashboard) and `test_plan_input_schema.py`, which holds the schema and example to the code.
## 🔗 See also
- [`../../README.md`](../../README.md) — what provLedger does; [`../../INSTALL.md`](../../INSTALL.md) — installation, and the dashboard (§6)
- [`../executing-plans/SKILL.md`](../executing-plans/SKILL.md) — the other half of the contract; owns ALL post-publish writes
- **Dashboard** — the bundled `orchestrator-webapp/` at http://localhost:8765 visualizes plans (tree view + parallel branches); workflow step 2 (`ensure-dashboard.sh`) starts it.