inkeep/open-knowledge-pack-software-lifecycle-frame-a-proposal
Frame a new design proposal (RFC-shape) under proposals/ — problem before solution, named beneficiary and observable change, real alternatives, honest drawbacks, and a live open-questions backlog. Read when asked to frame a proposal, write an RFC, propose a design, pitch a change, draft a PRD-style design doc, or open a design proposal for review. Do NOT read to record a decision after it is accepted (use record-a-decision), to write an implementation spec (use write-a-spec), to write a postmortem (use write-a-postmortem), or to review or critique an existing design (use review-a-design).
npx skills add https://github.com/inkeep/open-knowledge --skill open-knowledge-pack-software-lifecycle-frame-a-proposal
The platform open-knowledge skill still governs every markdown operation here (reads via exec/search, writes via write/edit, links as plain relative markdown, never native Read/Edit/Grep/cat on in-scope files). This skill layers proposal-authoring craft on top: it decides *what a good proposal contains and in what order you earn each section*.
A proposal in proposals/ is a design argument, not a decision and not a plan. It exists to force a choice among options and to give reviewers enough to disagree with. Filename is 0001-feature-name.md — a zero-padded 4-digit sequence plus a kebab title. Status flows draft → fcp → accepted/rejected (fcp = final comment period). Acceptance graduates the proposal to a record in decisions/ — that is a *separate*, human act and a *separate* skill.
The failure this skill exists to prevent: an agent jumping to ## Design before anyone agrees what the problem is, padding ## Alternatives with strawmen, and leaving ## Drawbacks empty. Each step below has a gate that blocks that.
Hard gates — do NOT skip ahead. If you are about to draft ## Design and you have not passed the Step 1 framing gate, STOP — you skipped a gate. The whole point of a proposal is that the problem is agreed before the solution is written.
proposals/ and decisions/.proposal template.fcp would require.Create workflow tasks for steps 0–9 in your host's task system if it has one — they make a skipped gate visible mid-session.
Before framing anything, find out what the knowledge base already decided or proposed about this subsystem. A proposal that silently re-litigates an accepted decision is dead on arrival; a proposal that cites it and explains why the decision should be revisited is legitimate.
search({ query: "<subsystem or problem keywords>" }) — semantic, catches synonyms.exec("ls -A proposals/") and exec("ls -A decisions/") — see the sequence space and what has landed.exec("grep -rln <keyword> proposals/ decisions/") — pinpoint files that name the same subsystem.exec("cat proposals/0003-x.md") to read the full doc plus its backlinks.Classify what you find, and carry it into the draft:
| Found | Do this |
|---|---|
| An accepted decision covers this area | The new proposal MUST cite it (a markdown link into decisions/) and, in Motivation, say what changed that reopens it. If nothing changed, tell the user this may not need a proposal at all. |
| A draft/fcp proposal overlaps | Offer to extend or supersede it rather than open a near-duplicate. Two overlapping proposals split the review. |
| Nothing | Proceed clean. |
This is the gate that makes the difference between an RFC and a pile of solution text. Do NOT draft ## Design, and do NOT create the file, until the user confirms the framing.
Produce and return exactly this, then STOP and wait:
## Framing (confirm before I draft)
**Beneficiary:** who is worse off today and will be better off if this ships. A named role or user, not "the system" or "us".
**Observable change:** the concrete, checkable difference they will see. "X drops from N to M", "Y becomes possible", "Z stops happening". Not "improve", not "streamline".
**Forced decision:** the one question this proposal makes reviewers answer. If accepting it doesn't commit anyone to anything, it is a report, not a proposal.
**Rough shape:** one sentence on the direction — enough to know we're framing the right problem, not the design itself.
Discipline:
List, don't guess, the sequence. exec("ls -A proposals/"), take the highest existing NNNN, add one, zero-pad to four digits. Guessing collides the moment two proposals are drafted the same week.
Filename: NNNN-kebab-title.md (0007-async-export-pipeline.md). Create it from the template — this is the only way the ## Motivation → ## Design → ## Drawbacks → ## Alternatives → ## Unresolved questions skeleton and the frontmatter arrive correctly:
write({ document: { path: "proposals/0007-async-export-pipeline.md", template: "proposal" } })
The template stamps this frontmatter — fill it, don't retype it by hand:
type: proposal
description: "..." # one line: the forced decision, not the feature name
status: draft # stays draft until a human advances it — see Non-goals
authors: [<user>]
created: YYYY-MM-DD
tags: [proposal]
Set description to the decision the proposal forces, in one line — it is what a reader sees in a listing.
Fill ## Motivation by edit-ing the created doc. This section has to stand on its own: a reader who disagrees here will never read your Design, and that's correct.
### Non-goals line inside Motivation.Fill ## Design with the actual proposal, pitched at the altitude where a reader could disagree with it. Too low (every function signature) and reviewers rubber-stamp a design they didn't evaluate; too high ("we'll make it faster") and there's nothing to accept. The target: a competent reader could read this section and say "no, I'd do it differently, because…"
the export boundary decision.Fill ## Alternatives with at least two real options, each carrying why it was not chosen. Include the honest ones you'd have picked on a different day, plus "do nothing" if it's live.
Structure each:
### Alternative: <name>
What it is — one honest paragraph, argued at its best.
Why not — the specific tradeoff that lost, versus the proposed design.
Discipline:
Fill ## Drawbacks with the honest cost of the proposed design — not the alternatives', its own.
Fill ## Unresolved questions as a live backlog, not a disclaimer. Each entry keeps a real open question visible instead of burying it in confident prose.
For each question:
- **<question>** — What would resolve it: <the evidence, experiment, or measurement>. Who decides: <role or person>.
fcp period.draft. Hiding uncertainty to look finished is the failure mode; an honest open-questions list is what makes the proposal safe to review.Run before you tell the user it's ready:
text), never backticked, never an HTML anchor.links({ kind: "backlinks", ... }) to see what already points where.links({ kind: "dead", sourceDocNames: ["proposals/0007-async-export-pipeline"] }) returns clean — fix or remove every dead link.type: proposal, a one-line description, status: draft, authors, created, tags: [proposal].## Motivation, ## Design, ## Drawbacks, ## Alternatives, ## Unresolved questions. An empty Drawbacks or a strawman Alternatives fails this check even though the section technically exists.status is still draft. You do not advance it — see Non-goals.Close with the user in conversation:
## Recap
- Framed: <beneficiary> gets <observable change>; forces the decision <…>.
- Proposal: proposals/NNNN-title.md (status: draft)
- Alternatives weighed: <n>, chosen over them because <one line>.
- Honest drawbacks: <the main one>.
- Open questions still live: <count> — the fcp agenda.
**To advance to `fcp`:** a human moves status draft → fcp and opens the final comment
period. Acceptance (→ accepted, then a record in decisions/) is a human decision, not
mine. If it's rejected, mark status: rejected and keep the doc — the reasoning is the value.
State plainly that advancing status and accepting the proposal are human acts. Your job ended at a well-framed, honestly-argued draft.
decisions/ — that's the sibling record-a-decision skill, run *after* a human accepts. This skill stops at draft.write-a-spec.accepted (or fcp) on your own authority. Advancing status is a human act. Leave status: draft; offer the recap of what advancing would require.Take inkeep/open-knowledge-pack-software-lifecycle-frame-a-proposal from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.