@nanopm/cli 0.1.8 → 0.1.10

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/dist/index.js CHANGED
@@ -9234,6 +9234,9 @@ function gateOne(ctx, w, leaf) {
9234
9234
  return { draft, refused };
9235
9235
  }
9236
9236
  function draftOf(ctx, w) {
9237
+ return { ...draftFields(w), sources: resolve3(ctx, w.restsOn) };
9238
+ }
9239
+ function draftFields(w) {
9237
9240
  const headline = w.changelog.headline.trim().replace(/\s+/g, " ");
9238
9241
  const room = CHANGELOG_MAX - headline.length - 1;
9239
9242
  return {
@@ -9243,8 +9246,7 @@ function draftOf(ctx, w) {
9243
9246
  steps: w.steps.map((s) => ({ do: s.do.trim().replace(/\s+/g, " "), who: s.who, ...s.checks ? { checks: true } : {} })),
9244
9247
  size: w.size,
9245
9248
  assumption: clipOrNull(w.worksIf, WORKS_IF_MAX),
9246
- reasoning: wholeSentences(w.why, WHY_THIS_MAX),
9247
- sources: resolve3(ctx, w.restsOn)
9249
+ reasoning: wholeSentences(w.why, WHY_THIS_MAX)
9248
9250
  };
9249
9251
  }
9250
9252
  function resolve3(ctx, ids) {
@@ -9312,14 +9314,13 @@ async function readRoadmap(client, projectId) {
9312
9314
  var SPEC_SKILL = "spec";
9313
9315
  var SPEC_MINUTES = 5;
9314
9316
  var SPEC_STEPS = {
9315
- brief: { says: "Writing what your users will see, and how we\u2019ll know", label: "Writing the brief" },
9316
- agent: { says: "Turning the brief into a spec a coding agent can build from", label: "Writing the agent spec" },
9317
+ solution: { says: "Writing what your users will see, and how we\u2019ll know", label: "Writing what your users will see" },
9318
+ agent: { says: "Turning the solution into a spec a coding agent can build from", label: "Writing the agent spec" },
9317
9319
  review: { says: "Reading both as you would, then as a coding agent would", label: "Checking both as a coding agent would" }
9318
9320
  };
9319
- function briefRefusal(brief2) {
9320
- if (!brief2.in_short.trim()) return "it does not say in short what we build";
9321
- if (!brief2.experience.some((s) => s.trim())) return "it does not walk through the experience";
9322
- if (!brief2.measure.signal.trim()) return "it does not say how we will know";
9321
+ function specRefusal(spec2) {
9322
+ if (!spec2.experience.some((s) => s.trim())) return "it does not walk through the experience";
9323
+ if (!spec2.measure.signal.trim()) return "it does not say how we will know";
9323
9324
  return null;
9324
9325
  }
9325
9326
  function agentSpecRefusal(markdown) {
@@ -9327,19 +9328,25 @@ function agentSpecRefusal(markdown) {
9327
9328
  if (!/^##\s+Acceptance criteria\b/im.test(markdown)) return "it has no acceptance criteria";
9328
9329
  return null;
9329
9330
  }
9330
- function specsRefusal({ brief: brief2, markdown }) {
9331
- const b = briefRefusal(brief2);
9332
- if (b) return `the brief ${b}`;
9331
+ function specsRefusal({ spec: spec2, markdown }) {
9332
+ const b = specRefusal(spec2);
9333
+ if (b) return `the spec ${b}`;
9333
9334
  const a = agentSpecRefusal(markdown);
9334
9335
  return a ? `the agent spec ${a}` : null;
9335
9336
  }
9337
+ function specRecord(mode, answer) {
9338
+ if (mode === "write") return { spec: answer.spec, markdown: answer.markdown, solution: null };
9339
+ if (!answer.solution) throw new Error("a rework writes the whole solution again, and this one came back without it");
9340
+ return { spec: answer.spec, markdown: answer.markdown, solution: answer.solution };
9341
+ }
9336
9342
  function specSolutionOf(params) {
9337
9343
  const id = params?.solution;
9338
9344
  return typeof id === "string" ? id : null;
9339
9345
  }
9340
9346
  async function queueSpecs(client, ask7) {
9347
+ const mode = ask7.mode ?? "write";
9341
9348
  const [{ data: solution }, { data: active }, { data: clone }] = await Promise.all([
9342
- client.from("solutions").select("id, ref, status, opportunity_id").eq("project_id", ask7.projectId).eq("ref", ask7.ref.trim().toUpperCase()).maybeSingle(),
9349
+ client.from("solutions").select("id, ref, status, opportunity_id, specced_at").eq("project_id", ask7.projectId).eq("ref", ask7.ref.trim().toUpperCase()).maybeSingle(),
9343
9350
  client.from("jobs").select("params").eq("project_id", ask7.projectId).eq("skill", SPEC_SKILL).in("status", [...ACTIVE_STATUSES]),
9344
9351
  // The clone on the machine that will run it: the daemon reads the code only in its own (`resolveCwd`).
9345
9352
  client.from("clones").select("repo_path").eq("project_id", ask7.projectId).eq("daemon_id", ask7.daemonId).maybeSingle()
@@ -9347,13 +9354,15 @@ async function queueSpecs(client, ask7) {
9347
9354
  if (!solution || !solution.opportunity_id) throw new Error(`No solution ${ask7.ref.toUpperCase()} on the Roadmap.`);
9348
9355
  if (solution.status === "rejected") throw new Error(`${solution.ref} was thrown out: it gets no specs.`);
9349
9356
  if ((active ?? []).some((j) => specSolutionOf(j.params) === solution.id)) throw new Error(`Nano is already writing the specs of ${solution.ref}.`);
9357
+ if (mode === "write" && solution.specced_at) throw new Error(`${solution.ref} has its specs already: rework the solution to write it all again.`);
9358
+ if (mode === "rework" && !solution.specced_at) throw new Error(`${solution.ref} has no specs yet: write them first.`);
9350
9359
  const { data, error } = await client.from("jobs").insert({
9351
9360
  project_id: ask7.projectId,
9352
9361
  skill: SPEC_SKILL,
9353
9362
  requested_by: ask7.userId,
9354
9363
  trigger: "user",
9355
9364
  target_daemon_id: ask7.daemonId,
9356
- params: { solution: solution.id, ...clone?.repo_path ? { repo_path: clone.repo_path } : {} }
9365
+ params: { solution: solution.id, ...mode === "rework" ? { rework: true } : {}, ...clone?.repo_path ? { repo_path: clone.repo_path } : {} }
9357
9366
  }).select("id").single();
9358
9367
  if (error?.code === "23505") throw new Error(`Nano is already writing the specs of ${solution.ref}.`);
9359
9368
  if (error || !data) throw new Error(error?.message ?? "could not ask Nano for the specs");
@@ -9364,40 +9373,30 @@ async function queueSpecs(client, ask7) {
9364
9373
  import { z as z15 } from "zod";
9365
9374
  var SpecParams = z15.object({
9366
9375
  solution: z15.guid(),
9376
+ /** *Rework the solution* (spec 74): the whole solution written again, as a new version. */
9377
+ rework: z15.literal(true).optional(),
9367
9378
  repo_path: z15.string().optional(),
9368
9379
  background: z15.boolean().optional()
9369
9380
  }).strict();
9370
9381
  function specAsk(params) {
9371
9382
  const parsed = SpecParams.safeParse(params);
9372
- if (parsed.success) return { ok: true, solution: parsed.data.solution };
9383
+ if (parsed.success) return { ok: true, solution: parsed.data.solution, mode: parsed.data.rework ? "rework" : "write" };
9373
9384
  const issue = parsed.error.issues[0];
9374
9385
  const unknown2 = issue.code === "unrecognized_keys" ? issue.keys?.join(", ") : null;
9375
9386
  return { ok: false, why: unknown2 ? `this run does not take ${unknown2}` : `${issue.path.join(".") || "params"}: ${issue.message}` };
9376
9387
  }
9377
- var Journey = z15.object({
9378
- kind: z15.literal("journey"),
9379
- steps: z15.array(prose(1)).max(9).describe('The user\u2019s path, three to seven steps, each a few words from their side of the screen: "Opens the trial", "Picks a closed search".'),
9380
- fork: z15.object({
9381
- after: z15.number().int().describe("The step the path forks after, counted from 1."),
9382
- when: prose(1).describe('When it forks, a few words: "No closed search".'),
9383
- then: prose(1).describe("Where it goes instead, a few words.")
9384
- }).nullable().default(null).describe("One fork at most, only when the path really splits.")
9385
- });
9386
- var BeforeAfter = z15.object({
9387
- kind: z15.literal("before_after"),
9388
- before: z15.array(prose(1)).max(6).describe("What the user lives today, two to five short lines."),
9389
- after: z15.array(prose(1)).max(6).describe("What they will live, two to five short lines, side by side with before.")
9388
+ var Diagram = z15.object({
9389
+ caption: prose(1).describe('What it shows, a few words: "Starting the trial on a closed search".'),
9390
+ mermaid: z15.string().min(1).max(4e3).describe("The diagram in Mermaid syntax, whichever type helps most: flowchart, sequenceDiagram, stateDiagram-v2, journey, or a flowchart with two subgraphs for before and after. At most about twelve nodes, labels of a few words in quotes. No styling: no classDef, style, colours, click or links.")
9390
9391
  });
9391
- var BriefFields = {
9392
- questions: z15.array(z15.object({ ask: prose(1).describe("The question, one sentence."), blocks: prose(1).describe('What it blocks, a few words: "what the trial screen says".') })).max(5).default([]).describe("To finish this brief: up to three questions only the founder can answer. Decide everything else yourself."),
9393
- in_short: prose(1).describe("In short: three sentences at most \u2014 what we build, for whom, what changes for them. The first could be read alone."),
9394
- diagrams: z15.array(z15.discriminatedUnion("kind", [Journey, BeforeAfter])).max(2).default([]).describe("At most two. The journey whenever the experience has three steps or more; before and after when the solution changes something the product already does."),
9392
+ var SpecFields = {
9393
+ questions: z15.array(z15.object({ ask: prose(1).describe("The question, one sentence."), blocks: prose(1).describe('What it blocks, a few words: "what the trial screen says".') })).max(5).default([]).describe("Questions for you: up to three questions only the founder can answer, and any place the code shows the solution as settled is wrong. Decide everything else yourself."),
9394
+ in_short: prose(1).describe("In short: three sentences at most \u2014 what we build, for whom, what changes for them. The first could be read alone. Read first, right after the problem: not the proposed solution again, its gist."),
9395
+ diagrams: z15.array(Diagram).max(2).default([]).describe("Optional: only when a drawing makes it faster to understand than the words. At most two, in Mermaid, the shape that helps most."),
9395
9396
  experience: z15.array(prose(1)).max(8).describe("The experience: three to six steps, in the present tense, from the user\u2019s side of the screen. Under a hypothesis, the founder\u2019s check comes first, in one line."),
9396
- build: z15.array(prose(1)).max(8).describe("What we build: three to six points, in users\u2019 words \u2014 what is true the day it ships."),
9397
9397
  wont_do: z15.array(z15.object({ what: prose(1), why: prose(1) })).max(6).default([]).describe("What we won\u2019t do: two to four points, each with why or when."),
9398
9398
  measure: z15.object({
9399
9399
  signal: prose(1).describe("The signal, one sentence: who \xB7 does what \xB7 how many \xB7 by when. Small numbers for a product with few users. Never a baseline you did not measure."),
9400
- wrong_if: prose(1).describe("We were wrong if\u2026, one sentence. Under a hypothesis, it can show the problem itself wrong."),
9401
9400
  existing: z15.array(z15.object({ name: prose(1).describe("The event as the product records it, exactly."), counts: prose(1).describe("What it counts here, and where you found it.") })).max(8).default([]).describe("Events the product already records that measure this, found in the code or in the connected source. Only what you found."),
9402
9401
  added: z15.array(
9403
9402
  z15.object({
@@ -9411,22 +9410,26 @@ var BriefFields = {
9411
9410
  dependencies: z15.array(prose(1)).max(6).default([]).describe('What must exist first. Empty when nothing: the page says None. No analytics tool in the product: "An analytics tool" is one.')
9412
9411
  };
9413
9412
  var Notes2 = prose().nullable().default(null).describe("A sentence or two, for the Journal.");
9414
- var BriefAnswer = z15.object({
9415
- ...BriefFields,
9416
- code: z15.array(z15.object({ path: z15.string().max(200), holds: prose(1).describe("What it holds that matters here, one line.") })).max(16).default([]).describe("The files you read that matter for this change, each with what it holds, for the agent spec\u2019s *Start here*. Empty when the code was not there."),
9413
+ var Code = z15.array(z15.object({ path: z15.string().max(200), holds: prose(1).describe("What it holds that matters here, one line.") })).max(16).default([]).describe("The files you read that matter for this change, each with what it holds, for the agent spec\u2019s *Start here*. Empty when the code was not there.");
9414
+ var Rewritten = Written.omit({ restsOn: true });
9415
+ var SolutionAnswer = z15.object({
9416
+ solution: Rewritten.nullable().default(null).describe('Only when the snapshot says mode "rework": the whole solution, written again. Null in mode "write": those fields are settled.'),
9417
+ ...SpecFields,
9418
+ code: Code,
9417
9419
  notes: Notes2
9418
9420
  });
9421
+ var AgentMarkdown = z15.string().min(1).max(6e4);
9419
9422
  var AgentAnswer = z15.object({
9420
- markdown: prose(1).max(6e4).describe("The agent spec, whole, in markdown, with the sections named in your instructions."),
9423
+ markdown: AgentMarkdown.describe("The agent spec, whole, in markdown, with the sections named in your instructions."),
9421
9424
  notes: Notes2
9422
9425
  });
9423
9426
  var ReviewAnswer = z15.object({
9424
- brief: z15.object(BriefFields).describe("The brief, whole, corrected where it failed a reader; as it was where it did not."),
9425
- markdown: prose(1).max(6e4).describe("The agent spec, whole, corrected; as it was where it did not fail."),
9427
+ spec: z15.object(SpecFields).describe("The solution\u2019s sections, whole, corrected where they failed a reader; as they were where they did not."),
9428
+ markdown: AgentMarkdown.describe("The agent spec, whole, corrected; as it was where it did not fail."),
9426
9429
  exchanges: z15.array(z15.object({ asked: prose(1).describe("What the coding agent would have had to guess, as its question, one short sentence."), did: prose(1).describe("What you did: decided it (and what), or left it to the founder.") })).max(5).default([]).describe("Two to five: the questions a coding agent asked of the spec, and what you did about each. They go in the Journal."),
9427
9430
  changed: z15.array(z15.string().max(200)).max(10).default([]).describe("What you changed, a line each, for the Journal.")
9428
9431
  });
9429
- var SCHEMAS7 = { brief: BriefAnswer, agent: AgentAnswer, review: ReviewAnswer };
9432
+ var SCHEMAS7 = { solution: SolutionAnswer, agent: AgentAnswer, review: ReviewAnswer };
9430
9433
  function parseSpecAnswer(role7, json2) {
9431
9434
  return SCHEMAS7[role7].parse(json2);
9432
9435
  }
@@ -9557,6 +9560,7 @@ async function runSpecsJob({ client, job, adapter, spec: spec2, onEvent }) {
9557
9560
  const solution = row;
9558
9561
  if (!solution?.opportunity_id) throw new Error("That solution is not on the Roadmap.");
9559
9562
  if (solution.status === "rejected") throw new Error(`${solution.ref} was thrown out: it gets no specs.`);
9563
+ const mode = asked.mode;
9560
9564
  const clone = typeof job.params?.repo_path === "string";
9561
9565
  const [all, card, stance, numbers, connections, commit] = await Promise.all([
9562
9566
  readOpportunities(client, projectId),
@@ -9571,16 +9575,8 @@ async function runSpecsJob({ client, job, adapter, spec: spec2, onEvent }) {
9571
9575
  const path = leaf ? pathOf(tree, leaf.id) : [];
9572
9576
  const known = {
9573
9577
  today: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
9574
- solution: {
9575
- ref: solution.ref,
9576
- title: solution.title,
9577
- proposed_solution: solution.description,
9578
- future_changelog: parseChangelog(solution.changelog),
9579
- plan: solution.steps,
9580
- size: solution.size,
9581
- works_if: solution.assumption,
9582
- why: solution.reasoning
9583
- },
9578
+ mode,
9579
+ solution: snapshotOf({ ref: solution.ref, title: solution.title, description: solution.description, changelog: solution.changelog ?? "", steps: solution.steps, size: solution.size ?? "small", assumption: solution.assumption, reasoning: solution.reasoning }),
9584
9580
  problem: leaf ? problemOf(tree, leaf) : null,
9585
9581
  part_of: path.slice(1).map((p) => p.problem),
9586
9582
  objective: lensOf(card.goals),
@@ -9590,27 +9586,37 @@ async function runSpecsJob({ client, job, adapter, spec: spec2, onEvent }) {
9590
9586
  numbers
9591
9587
  };
9592
9588
  const reads = clone ? `the code read at ${commit ?? "its current commit"}` : "no clone here, so the code is not read";
9593
- await journal("spec.read", `Writing the specs of \u201C${clip3(solution.title, 80)}\u201D (${solution.ref}): ${reads}; ${connections.length ? `${connections.length} connected source${connections.length === 1 ? "" : "s"}` : "no analytics connected"}.`);
9589
+ const doing = mode === "rework" ? "Reworking" : "Writing the specs of";
9590
+ await journal("spec.read", `${doing} \u201C${clip3(solution.title, 80)}\u201D (${solution.ref}): ${reads}; ${connections.length ? `${connections.length} connected source${connections.length === 1 ? "" : "s"}` : "no analytics connected"}.`);
9594
9591
  const ctx = { adapter, spec: spec2, onEvent };
9595
9592
  const timings = [];
9596
- const time = (what, cost) => timings.push({ what, ms: cost.ms });
9593
+ const time = (what2, cost) => timings.push({ what: what2, ms: cost.ms });
9597
9594
  const repo = clone ? { repository: "the working directory", why_no_repository: null } : { repository: null, why_no_repository: "No clone of the product rode along with this run: work from what the snapshot says the product does." };
9598
9595
  const writerGrant = [...clone ? ["read_repo"] : [], ...connections.length ? ["read_data"] : []];
9599
- const brief2 = await ask3(ctx, "brief", { grant: writerGrant, prompt: { ...known, ...repo, connections } });
9600
- time("brief", brief2.cost);
9601
- const { code, notes: briefNotes, ...draft } = brief2.answer;
9602
- await step("spec.brief", `Brief written: ${count(draft.build.length, "point")} built, ${count(draft.questions.length, "question")} for you, ${count(draft.measure.added.length, "new event")}.${briefNotes ? ` ${briefNotes}` : ""}`, brief2.cost);
9596
+ const writing = { grant: writerGrant, prompt: { ...known, ...repo, connections } };
9597
+ const written = await ask3(ctx, "solution", writing);
9598
+ time("solution", written.cost);
9599
+ const { solution: rewritten, code, notes, ...draft } = written.answer;
9600
+ const fields = mode === "rework" && rewritten ? draftFields(rewritten) : null;
9601
+ const wrote = fields ? `Solution written again: \u201C${clip3(fields.title, 80)}\u201D` : `Sections written: ${count(draft.experience.length, "step")} of the experience`;
9602
+ await step("spec.solution", `${wrote}, ${count(draft.questions.length, "question")} for you, ${count(draft.measure.added.length, "new event")}.${notes ? ` ${notes}` : ""}`, written.cost);
9603
+ const whole = fields ? snapshotOf({ ref: solution.ref, ...fields }) : known.solution;
9604
+ const first = specRecord(mode, { spec: draft, markdown: "", solution: fields });
9605
+ const unreadable = specRefusal(first.spec) ?? (first.solution && leaf ? solutionRefusal(first.solution, leaf) : null);
9606
+ if (unreadable) throw new Error(`The solution cannot be recorded: ${unreadable}.`);
9607
+ const target = await recordSections(client, { projectId, jobId: job.id, solution, spec: first.spec, fields: first.solution });
9608
+ await journal("spec.sections", first.solution ? `${target.ref} \u201C${clip3(first.solution.title, 80)}\u201D takes ${solution.ref}\u2019s place on the board; its agent spec is on its way.` : `Sections on the sheet; the agent spec is on its way.`);
9603
9609
  const agent = await ask3(ctx, "agent", {
9604
9610
  grant: clone ? ["read_repo"] : [],
9605
- prompt: { today: known.today, ref: solution.ref, commit: commit ?? "not read", ...repo, brief: draft, code, solution: known.solution, problem: known.problem, product: known.product, constraints: stance }
9611
+ prompt: { today: known.today, ref: target.ref, commit: commit ?? "not read", ...repo, solution: whole, spec: draft, code, problem: known.problem, product: known.product, constraints: stance }
9606
9612
  });
9607
9613
  time("agent spec", agent.cost);
9608
9614
  await step("spec.agent", `Agent spec written.${agent.answer.notes ? ` ${agent.answer.notes}` : ""}`, agent.cost);
9609
- let final = { brief: draft, markdown: agent.answer.markdown };
9615
+ let final = { spec: first.spec, markdown: agent.answer.markdown };
9610
9616
  try {
9611
- const review2 = await ask3(ctx, "review", { prompt: { brief: draft, markdown: agent.answer.markdown, solution: known.solution, problem: known.problem } });
9617
+ const review2 = await ask3(ctx, "review", { prompt: { mode, solution: whole, spec: draft, markdown: agent.answer.markdown, problem: known.problem } });
9612
9618
  time("review", review2.cost);
9613
- const reviewed = { brief: review2.answer.brief, markdown: review2.answer.markdown };
9619
+ const reviewed = { spec: review2.answer.spec, markdown: review2.answer.markdown };
9614
9620
  const refused2 = specsRefusal(reviewed);
9615
9621
  if (refused2) await journal("spec.review_kept_out", `The review\u2019s version was left out (${refused2}): the writers\u2019 drafts are recorded.`);
9616
9622
  else final = reviewed;
@@ -9624,19 +9630,15 @@ async function runSpecsJob({ client, job, adapter, spec: spec2, onEvent }) {
9624
9630
  await journal("spec.review_failed", `The review did not finish (${clip3(errorMessage(err), 200)}): the writers\u2019 drafts are recorded.`);
9625
9631
  }
9626
9632
  const refused = specsRefusal(final);
9627
- if (refused) throw new Error(`The specs cannot be read: ${refused}.`);
9628
- const { error: published } = await client.rpc("publish_spec", {
9629
- p_project_id: projectId,
9630
- p_solution_id: solution.id,
9631
- p_brief: final.brief,
9632
- p_agent_spec: final.markdown
9633
- });
9633
+ if (refused) throw new Error(`The specs cannot be recorded: ${refused}.`);
9634
+ const { error: published } = await client.rpc("publish_solution_spec", { p_project_id: projectId, p_solution_id: target.id, p_spec: final.spec, p_agent_spec: final.markdown });
9634
9635
  if (published) throw new Error(`could not record the specs: ${published.message}`);
9635
- const summary = `Specs of \u201C${clip3(solution.title, 80)}\u201D (${solution.ref}) written: the brief, with ${count(final.brief.questions.length, "question")} for you, and the agent spec. Took ${duration(Date.now() - started)}: ${timings.map((t) => `${t.what} ${duration(t.ms)}`).join(", ")}.`;
9636
+ const what = first.solution ? `\u201C${clip3(solution.title, 80)}\u201D (${solution.ref}) reworked as ${target.ref} \u201C${clip3(first.solution.title, 80)}\u201D` : `Specs of \u201C${clip3(solution.title, 80)}\u201D (${solution.ref}) written`;
9637
+ const summary = `${what}: ${count(final.spec.questions.length, "question")} for you, and the agent spec. Took ${duration(Date.now() - started)}: ${timings.map((t) => `${t.what} ${duration(t.ms)}`).join(", ")}.`;
9636
9638
  await journal("spec.summary", summary);
9637
9639
  return { status: "succeeded", result: summary };
9638
9640
  }
9639
- var ROLE_NAME = { brief: "spec brief writer", agent: "spec agent writer", review: "spec reviewer" };
9641
+ var ROLE_NAME = { solution: "spec solution writer", agent: "spec agent writer", review: "spec reviewer" };
9640
9642
  async function ask3(ctx, role7, call2) {
9641
9643
  const cost = await runRole({
9642
9644
  adapter: ctx.adapter,
@@ -9653,6 +9655,33 @@ async function ask3(ctx, role7, call2) {
9653
9655
  });
9654
9656
  return { answer: stripDeep(parseSpecAnswer(role7, JSON.parse(cost.text))), cost };
9655
9657
  }
9658
+ async function recordSections(client, r) {
9659
+ if (!r.fields) {
9660
+ const { error: error2 } = await client.rpc("publish_solution_spec", { p_project_id: r.projectId, p_solution_id: r.solution.id, p_spec: r.spec });
9661
+ if (error2) throw new Error(`could not record the sections: ${error2.message}`);
9662
+ return { id: r.solution.id, ref: r.solution.ref };
9663
+ }
9664
+ const f = r.fields;
9665
+ const { data, error } = await client.rpc("rework_spec", {
9666
+ p_project_id: r.projectId,
9667
+ p_solution_id: r.solution.id,
9668
+ p_job_id: r.jobId,
9669
+ p_title: f.title,
9670
+ p_description: f.description ?? "",
9671
+ p_changelog: f.changelog,
9672
+ p_steps: f.steps,
9673
+ p_size: f.size,
9674
+ p_assumption: f.assumption ?? "",
9675
+ p_reasoning: f.reasoning,
9676
+ p_spec: r.spec
9677
+ });
9678
+ const made = data?.[0];
9679
+ if (error || !made) throw new Error(`could not record the reworked solution: ${error?.message ?? "no new version"}`);
9680
+ return made;
9681
+ }
9682
+ function snapshotOf(s) {
9683
+ return { ref: s.ref, title: s.title, proposed_solution: s.description, future_changelog: parseChangelog(s.changelog), plan: s.steps, size: s.size, works_if: s.assumption, why: s.reasoning };
9684
+ }
9656
9685
  function problemOf(tree, leaf) {
9657
9686
  return { ref: placeOf(tree, leaf.id) ?? leaf.ref, problem: leaf.problem, description: leaf.description ?? null, status: leaf.evidence_status, confirmed_by_founder: leaf.status === "confirmed" };
9658
9687
  }
@@ -14231,7 +14260,7 @@ import { promisify as promisify8 } from "util";
14231
14260
  // package.json
14232
14261
  var package_default = {
14233
14262
  name: "@nanopm/cli",
14234
- version: "0.1.8",
14263
+ version: "0.1.10",
14235
14264
  description: "An autonomous product manager for builders who run several small products. Runs on your machine, drives your own coding harness.",
14236
14265
  type: "module",
14237
14266
  license: "MIT",
@@ -15937,7 +15966,9 @@ async function spec(ref, opts) {
15937
15966
  const wanted = ref.trim().toUpperCase();
15938
15967
  const { data: solution } = await auth.client.from("solutions").select("id, ref, title, agent_spec").eq("project_id", project.id).eq("ref", wanted).maybeSingle();
15939
15968
  if (!solution) throw new Error(`no solution ${wanted}. Run \`nanopm roadmap\` to see them.`);
15940
- if (!opts.write) {
15969
+ if (opts.write && opts.rework) throw new Error("--write or --rework, not both.");
15970
+ const mode = opts.rework ? "rework" : opts.write ? "write" : null;
15971
+ if (!mode) {
15941
15972
  if (!solution.agent_spec) return `${solution.ref} has no specs yet: \`nanopm spec ${solution.ref} --write\`, or *Write the specs* on its sheet.`;
15942
15973
  return solution.agent_spec.trimEnd();
15943
15974
  }
@@ -15945,11 +15976,9 @@ async function spec(ref, opts) {
15945
15976
  if (!daemon) throw new Error("Your machine is asleep: none of your daemons has been seen in the last two minutes, so nothing was queued. `nanopm daemon install` starts it; then ask again.");
15946
15977
  const repo = await detectRepo(opts.cwd);
15947
15978
  if (repo) await rememberClone(auth.client, project.id, repo.root);
15948
- await queueSpecs(auth.client, { projectId: project.id, userId: auth.userId, daemonId: daemon.id, ref: solution.ref });
15949
- return [
15950
- `Writing the specs of ${solution.ref} \u201C${clip(solution.title, 60)}\u201D: the brief and the agent spec, about ${SPEC_MINUTES} minutes on ${daemon.hostname}${repo ? ", reading this code" : ""}.`,
15951
- `They land on its sheet; \`nanopm spec ${solution.ref}\` prints the agent spec.`
15952
- ].join("\n");
15979
+ await queueSpecs(auth.client, { projectId: project.id, userId: auth.userId, daemonId: daemon.id, ref: solution.ref, mode });
15980
+ const where = `about ${SPEC_MINUTES} minutes on ${daemon.hostname}${repo ? ", reading this code" : ""}`;
15981
+ return mode === "rework" ? [`Reworking ${solution.ref} \u201C${clip(solution.title, 60)}\u201D: the whole solution and its agent spec, ${where}.`, "The new version takes its card on the Roadmap; `nanopm roadmap` names it."].join("\n") : [`Writing the specs of ${solution.ref} \u201C${clip(solution.title, 60)}\u201D: what it lacks and the agent spec, ${where}.`, `They land on its sheet; \`nanopm spec ${solution.ref}\` prints the agent spec.`].join("\n");
15953
15982
  }
15954
15983
 
15955
15984
  // src/commands/want.ts
@@ -17941,8 +17970,8 @@ function buildProgram() {
17941
17970
  }
17942
17971
  console.log(await roadmap(ref, { cwd: process.cwd(), projectId: opts.project, board: opts.board, color: colorOf(opts) }));
17943
17972
  });
17944
- program.command("spec <ref>").description("A solution's agent spec, as written, to hand to any coding agent (`nanopm spec DOG-3 > SPEC.md`); `--write` asks Nano for its specs \u2014 the solution brief and the agent spec \u2014 reading this code, read-only").option("--project <id>", "project id (default: the project of the current repo)").option("--write", "write the specs, or write them again").action(async (ref, opts) => {
17945
- console.log(await spec(ref, { cwd: process.cwd(), projectId: opts.project, write: opts.write }));
17973
+ program.command("spec <ref>").description("A solution's agent spec, as written, to hand to any coding agent (`nanopm spec DOG-3 > SPEC.md`); `--write` asks Nano for what the solution lacks and its agent spec, reading this code, read-only; `--rework` for the whole solution again").option("--project <id>", "project id (default: the project of the current repo)").option("--write", "write the specs: the sections the solution lacks, and the agent spec").option("--rework", "rework the whole solution and its agent spec, as a new version in its place").action(async (ref, opts) => {
17974
+ console.log(await spec(ref, { cwd: process.cwd(), projectId: opts.project, write: opts.write, rework: opts.rework }));
17946
17975
  });
17947
17976
  for (const [kind, help] of [
17948
17977
  ["accept", "Accept a solution; NanoPM builds it when you ask, or you do it and say so with 'nanopm done'"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanopm/cli",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "An autonomous product manager for builders who run several small products. Runs on your machine, drives your own coding harness.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,3 +1,3 @@
1
1
  The spec crew is coordinated in code (`packages/daemon/src/specs-job.ts`): this skill is never run
2
- as one session. Its roles are `spec-brief`, `spec-agent` and `spec-review`
3
- (docs/✅nanopm-73-specs-from-a-solution.md §5).
2
+ as one session. Its roles are `spec-solution`, `spec-agent` and `spec-review`
3
+ (docs/✅nanopm-73-specs-from-a-solution.md §5, docs/✅nanopm-74-a-solution-is-its-spec.md §2).
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spec",
3
- "description": "Write the specs of one solution on the Roadmap: a crew coordinated in code. spec-brief writes the solution brief the founder reads, reading the code and the connected analytics read-only; spec-agent writes the agent spec any coding agent starts from; spec-review reads both cold as the founder and as a coding agent, and hands both back corrected. Recorded on the solution, both at once.",
3
+ "description": "Write the specs of one solution on the Roadmap, or rework it whole: a crew coordinated in code. spec-solution writes the sections the solution lacks — or the whole solution, in a rework — reading the code and the connected analytics read-only; spec-agent writes the agent spec any coding agent starts from; spec-review reads both cold as the founder and as a coding agent, and hands both back corrected. Recorded on the solution, both at once.",
4
4
  "capabilities": ["memory"],
5
5
  "maxTurns": 10,
6
6
  "timeout": "30m",
@@ -1,5 +1,5 @@
1
1
  You are Nano, the product manager of this product, on the spec crew. The founder asked for the
2
- specs of a solution on their Roadmap, and the brief is written. Your part is the **agent spec**:
2
+ specs of a solution on their Roadmap, and the solution's page is written. Your part is the **agent spec**:
3
3
  the markdown a coding agent starts from — Claude Code, Codex, Cursor, or a person. A third role
4
4
  will read it cold, as that agent would, and fix what it would have to guess.
5
5
 
@@ -9,13 +9,15 @@ reads the code better than any spec — except where the product's rules decide.
9
9
  ## What you hold
10
10
 
11
11
  - **The code**, read-only — Read, Glob and Grep in the working directory — when the snapshot says
12
- `repository`. Start from the files the brief's writer handed over in `code`, then look where the
12
+ `repository`. Start from the files the solution's writer handed over in `code`, then look where the
13
13
  change will live: the screens, the data, the tests beside them, the project's own instructions
14
14
  (`CLAUDE.md`, `AGENTS.md`, `CONTRIBUTING.md`). Never open `.env` files or anything holding
15
- secrets. Without the code, write from the brief and say in *Start here* that the code was not read.
15
+ secrets. Without the code, write from the solution and say in *Start here* that the code was not read.
16
16
  - Nothing else: no web, no write.
17
17
 
18
- The snapshot gives you the `brief`, the `solution` it comes from (its plan among it), the `problem`
18
+ The snapshot gives you the `solution` whole — its title, the proposed solution, its changelog, its
19
+ plan, its size, *It works if* — and its `spec`: the questions, the experience and its diagrams,
20
+ what we won't do, how we'll know, the risks, the dependencies. Then the `problem`
19
21
  it solves, the `product`, the founder's `constraints`, the `commit` the code was read at, and
20
22
  `today`.
21
23
 
@@ -27,18 +29,18 @@ an agent and the pages look for them:
27
29
  ```markdown
28
30
  # <the solution's title>
29
31
 
30
- > Spec for a coding agent, written by NanoPM on <today> from the solution brief of <ref>.
32
+ > Spec for a coding agent, written by NanoPM on <today> from the solution <ref>.
31
33
  > Code read at <commit>.
32
34
 
33
35
  ## Goal
34
36
  Two lines: the change, and what it does for users.
35
37
 
36
38
  ## Context
37
- The product in three lines, who the user is, the problem this solves — from the brief.
39
+ The product in three lines, who the user is, the problem this solves — from the solution.
38
40
 
39
41
  ## Before you start
40
42
  - Read the repository's CLAUDE.md or AGENTS.md, when there is one, and follow it.
41
- - The founder's open questions, from the brief: do not guess these; ask.
43
+ - The founder's open questions, from the solution's *Questions for you*: do not guess these; ask.
42
44
 
43
45
  ## Start here
44
46
  The files and places it lives, each with what it holds — from your reading. Verify before relying
@@ -55,7 +57,7 @@ The existing events it reuses, and what they count. The new ones: name, when it
55
57
  properties. No event carries words a user wrote.
56
58
 
57
59
  ## Out of scope
58
- What not to build, from the brief's *What we won't do*.
60
+ What not to build, from the solution's *What we won't do*.
59
61
 
60
62
  ## Constraints
61
63
  The founder's constraints, the project's conventions, and: ask the founder before adding a
@@ -71,7 +73,7 @@ The tests to write, the commands to run (the project's own: its package scripts,
71
73
  runner), what to look at by hand.
72
74
 
73
75
  ## Decisions Nano took
74
- What the brief left open that you settled, and why — one line each.
76
+ What the solution left open that you settled, and why — one line each.
75
77
 
76
78
  ## Definition of done
77
79
  - [ ] Every acceptance criterion passes.
@@ -81,18 +83,18 @@ What the brief left open that you settled, and why — one line each.
81
83
  ## How to write it
82
84
 
83
85
  - **Only what an agent can build.** The plan's steps that are the founder's (*Ask three
84
- principals…*) stay in the brief. Under a hypothesis, keep the build as small as the check needs,
86
+ principals…*) stay on the solution's page. Under a hypothesis, keep the build as small as the check needs,
85
87
  and say so in the goal.
86
- - **Every point of the brief's *What we build* has at least one acceptance criterion**, and every
88
+ - **Every change the proposed solution and the changelog promise has at least one acceptance criterion**, and every
87
89
  *won't do* is in *Out of scope*.
88
90
  - **IDs and checkboxes** — R1, AC1, `- [ ]` — so an agent can cite them in commits and tick them.
89
- - **The diagrams** of the brief, when they help the agent, as `mermaid` blocks under *Context*.
91
+ - **The diagrams** of the solution, when they help the agent, as `mermaid` blocks under *Context*.
90
92
  - **Name what exists.** When the product already has a screen, a component, a table or a helper
91
93
  that this should reuse, name it in *Start here*: an agent that does not know it will build a
92
94
  second one.
93
95
  - **Be exact where it matters**: event names, routes, field names, the words on a button the
94
- brief settled. Elsewhere, describe the behaviour and let the agent choose.
95
- - Write in the language the brief is written in. Keep it as long as the change needs and no
96
+ solution settled. Elsewhere, describe the behaviour and let the agent choose.
97
+ - Write in the language the solution is written in. Keep it as long as the change needs and no
96
98
  longer: a Small solution reads in a few minutes.
97
99
 
98
100
  ## Rules
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spec-agent",
3
- "description": "Write the agent spec of one solution from its brief: markdown any coding agent can start from — goal, context, where it lives in the code, requirements, acceptance criteria, analytics, out of scope, constraints, slices, how to verify, the decisions taken, a definition of done. Reads the founder's code, read-only. Internal crew role; no publication authority.",
3
+ "description": "Write the agent spec of one solution from the whole solution: markdown any coding agent can start from — goal, context, where it lives in the code, requirements, acceptance criteria, analytics, out of scope, constraints, slices, how to verify, the decisions taken, a definition of done. Reads the founder's code, read-only. Internal crew role; no publication authority.",
4
4
  "capabilities": ["read_repo"],
5
5
  "model": "claude-opus-5-5",
6
6
  "maxTurns": 40,
@@ -1,17 +1,25 @@
1
- You are the reviewer on the spec crew. Nano wrote two documents from one solution on the founder's
2
- Roadmap: the **solution brief**, which the founder reads in two minutes, and the **agent spec**,
3
- which a coding agent starts from. You read both **cold**, as two readers, and you hand both back
4
- corrected. You have no tools: everything is in the snapshot — the `brief`, the agent spec as
5
- `markdown`, the `solution` they come from, and its `problem`.
1
+ You are the reviewer on the spec crew. Nano completed one solution on the founder's Roadmap: the
2
+ **solution**, one page the founder reads in two minutes — its roadmap fields in `solution`, the
3
+ sections just written in `spec` — and the **agent spec**, which a coding agent starts from. You
4
+ read both **cold**, as two readers, and you hand both back corrected. You have no tools:
5
+ everything is in the snapshot — the `mode`, the `solution`, its `spec`, the agent spec as
6
+ `markdown`, and its `problem`.
7
+
8
+ **You touch only `spec` and the agent spec.** The solution's fields are on the founder's board
9
+ already, in both modes (in `rework`, Nano just wrote them again): they are settled. A section that
10
+ says again what the proposed solution says, or disagrees with it in silence, fails: cut the
11
+ repeat, and turn a disagreement into a question. The founder may be reading the sections while you
12
+ work: change what fails a reader, not what you would merely say differently.
6
13
 
7
14
  ## Two readers
8
15
 
9
- **The founder**, reading the brief with no context, in thirty seconds:
16
+ **The founder**, reading the solution's page with no context, in thirty seconds:
10
17
 
11
- - Do I know what we build, for whom, and what changes for them? (*In short*.)
12
- - Can I picture what my users will see and do? (*The experience*, the diagram.)
13
- - Do I know how we'll know it worked — who does what, how many, by when — and what would prove us
14
- wrong?
18
+ - Do I know what we build, for whom, and what changes for them? (The proposed solution.)
19
+ - Can I picture what my users will see and do? (*The experience*, the diagrams.) Does each diagram
20
+ help, or say again what the words say? Cut one that does not; keep each valid Mermaid, small,
21
+ with no styling, colours or links.
22
+ - Do I know how we'll know it worked — who does what, how many, by when?
15
23
  - Is anything in words I would not use about my own product? Any Nano shorthand (*leaf*, *lens*,
16
24
  *pick*, *run*, *crew*)? Any sentence too long to read once?
17
25
  - Are the open questions really mine to answer — a price, a promise to users, a rule of my
@@ -31,11 +39,11 @@ corrected. You have no tools: everything is in the snapshot — the `brief`, the
31
39
  jargon. Keep what holds as it was: you are an editor, not a second author.
32
40
  - **For each thing the agent would have to guess**, either **decide** it — the obvious choice for
33
41
  this product, written into *Decisions Nano took* with one line of why — or, when only the founder
34
- can answer, **leave it to them**: add it to the brief's questions (three at most) and to *Before
42
+ can answer, **leave it to them**: add it to the solution's questions (three at most) and to *Before
35
43
  you start*.
36
- - **Make the two agree**: every point of *What we build* has at least one acceptance criterion;
37
- every *won't do* is in *Out of scope*; the same events, under the same names, in both; the
38
- questions in the brief are the ones in *Before you start*.
44
+ - **Make the two agree**: every change the proposed solution and the changelog promise has at
45
+ least one acceptance criterion; every *won't do* is in *Out of scope*; the same events, under
46
+ the same names, in both; the solution's questions are the ones in *Before you start*.
39
47
  - **Honesty**: cut any number nobody measured (a baseline, a percentage of users), any file or
40
48
  event named as existing that the documents do not show was found, and any event property that
41
49
  would carry words a user wrote.
@@ -43,10 +51,10 @@ corrected. You have no tools: everything is in the snapshot — the `brief`, the
43
51
 
44
52
  ## What you hand back
45
53
 
46
- - `brief` and `markdown`: both documents, **whole**, corrected.
54
+ - `spec` and `markdown`: the sections and the agent spec, **whole**, corrected.
47
55
  - `exchanges`: two to five — each a question the coding agent asked of the spec, in its voice,
48
56
  one short sentence (*"Does a closed search count against the trial's limit?"*), and what you did
49
- (*"Decided: it does not — in Decisions."*, *"Left to you: added to the brief's questions."*).
57
+ (*"Decided: it does not — in Decisions."*, *"Left to you: added to the solution's questions."*).
50
58
  They go in the Journal, where the founder reads what the review did, so make them the real
51
59
  ones, the ones that mattered most.
52
60
  - `changed`: what you changed, a line each.
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "spec-review",
3
- "description": "Read a solution brief and its agent spec cold, as the founder and as a coding agent; check they agree; hand both back corrected, with the questions a coding agent would have asked and what was done about each. Internal crew role; no tools and no publication authority.",
3
+ "description": "Read a solution and its agent spec cold, as the founder and as a coding agent; check they agree; hand both back corrected, with the questions a coding agent would have asked and what was done about each. Internal crew role; no tools and no publication authority.",
4
4
  "capabilities": [],
5
5
  "model": "claude-opus-5-5",
6
6
  "maxTurns": 8,
7
7
  "timeout": "15m",
8
- "prompt": "Review the brief and the agent spec, and return only the required JSON."
8
+ "prompt": "Review the solution and the agent spec, and return only the required JSON."
9
9
  }
@@ -1,8 +1,28 @@
1
1
  You are Nano, the product manager of this product, on the spec crew. The founder picked a solution
2
- on their Roadmap and asked for its specs. Your part is the **solution brief**: the page they read
3
- in two minutes, like a short PRD, and understand at once — **what we build, what their users will
4
- live, and how we will know it worked**. After you, a second role turns your brief into a spec for
5
- a coding agent, and a third reads both cold and fixes what fails.
2
+ on their Roadmap and asked for its specs. A solution is one page the founder reads like a short
3
+ PRD: on the Roadmap it already has its title, the proposed solution, its future changelog, its
4
+ plan, its size and what it rests on (*It works if*). Your part is **the rest of that page** — what
5
+ their users will live, what we won't do, **how we will know it worked**, the risks, the
6
+ dependencies, and the questions only the founder can answer. After you, a second role turns the
7
+ whole solution into a spec for a coding agent, and a third reads both cold and fixes what fails.
8
+
9
+ ## Two modes
10
+
11
+ The snapshot says which, in `mode`:
12
+
13
+ - **`write`** — *Write the specs*. The solution's fields are **settled**: build on them, never
14
+ repeat them, never contradict them. Write only the sections below. The proposed solution
15
+ already says what we build, in short: there is no section for that. **Where the code shows a
16
+ settled field is wrong** — a step that already exists, a size that does not hold, a promise the
17
+ product cannot keep — you do not correct it: you say it in `questions`, with what it blocks.
18
+ - **`rework`** — *Rework the solution*. The founder asked you to write **the whole solution
19
+ again**, in `solution`, from what you know and from the code, then the sections below. Hold its
20
+ fields to the Roadmap's rules: a title of at most 60 characters starting with a verb, in words a
21
+ user understands; the proposed solution in two to four short paragraphs, never the changelog
22
+ again; a changelog in the users' own words; a plan of two to six concrete steps, Nano's or the
23
+ founder's, the first one checking the problem when it is still a hypothesis; Small or Big,
24
+ scope, never time; *It works if* the one belief it rests on. Keep what was right; change what the
25
+ code or what you know shows wrong, the title and the size included.
6
26
 
7
27
  Write it like a very good PM who knows this product: specific, plain, short. The founder is often
8
28
  alone on their product. They will hand the work to a coding agent, so what you leave vague, an
@@ -18,14 +38,15 @@ agent will guess.
18
38
  - Nothing else: no web, no write. You change nothing anywhere.
19
39
 
20
40
  The snapshot gives you the `solution` (its title, the proposed solution, its future changelog, its
21
- plan, its size, what it rests on), the `problem` it solves and what that problem is `part_of`, the
41
+ plan, its size, *It works if*, why this one), the `problem` it solves and what that problem is `part_of`, the
22
42
  `objective`, the `product` and its `pitch` as the business card says them, the `personas`, the
23
43
  founder's `stance` and `constraints`, and the `numbers` Nano has measured.
24
44
 
25
45
  ## Steps
26
46
 
27
- 1. **Read the solution and its problem.** The brief never tells the problem again: its page links
28
- to it. Start from the changelog — what users would read the day it ships.
47
+ 1. **Read the solution and its problem.** Never tell the problem or the proposed solution again:
48
+ the page shows them just above your sections. Start from the changelog — what users would read
49
+ the day it ships.
29
50
  2. **Look at the code, briefly**, when you have it: where this change would live, what the product
30
51
  already does around it, what it already records. You are writing for the founder, not building:
31
52
  a few minutes, not an audit. Never open `.env` files or anything holding secrets.
@@ -33,26 +54,29 @@ founder's `stance` and `constraints`, and the `numbers` Nano has measured.
33
54
  `posthog.capture`, `analytics.track`, `track(`, `amplitude`, `mixpanel`, `segment`, `gtag`, an
34
55
  events table — and note the event names exactly as written. With a connected source,
35
56
  `describe_source` to see which events it receives. Query only when a count settles the signal.
36
- 4. **Write the brief.** Hand over in `code` the files that matter, each with what it holds: the
37
- agent spec starts from them.
57
+ 4. **Write your sections.** Hand over in `code` the files that matter, each with what it holds:
58
+ the agent spec starts from them.
38
59
 
39
- ## The brief
60
+ ## The sections
40
61
 
41
- - **questions** — *To finish this brief*: up to three questions **only the founder can answer**
42
- (a price, a promise to users, a rule of their business, a taste call), each with what it blocks.
43
- Decide everything else yourself: the reviewer will move what a coding agent would guess into
44
- decisions. No question for the sake of a section; none is fine.
45
- - **in_short** — three sentences at most: what we build, for whom, what changes for them. The
46
- first sentence could be read alone.
62
+ - **questions** — *Questions for you*: up to three questions **only the founder can
63
+ answer** (a price, a promise to users, a rule of their business, a taste call), or a settled
64
+ field the code shows wrong, each with what it blocks. Decide everything else yourself: the
65
+ reviewer will move what a coding agent would guess into decisions. No question for the sake of a
66
+ section; none is fine.
47
67
  - **experience** — three to six steps, in the present tense, from the user's side of the screen:
48
68
  what they see, what they do, what happens. When the problem is still a hypothesis, the first line
49
69
  is the founder's check (the plan's first step), in one line.
50
- - **diagrams** — at most two, only when there is something to draw:
51
- - the **journey**, the user's path in three to seven steps of a few words, with one fork at most
52
- — drawn whenever the experience has three steps or more;
53
- - **before and after**, two to five short lines each — when the solution changes something the
54
- product already does.
55
- - **build** — three to six points, in users' words: what is true the day it ships.
70
+ - **in_short** — *In short*, read first, right after the problem: three sentences at most — what
71
+ we build, for whom, what changes for them. The first sentence could be read alone. The gist of
72
+ the proposed solution, not its paragraphs again.
73
+ - **diagrams** — **optional**, at most two, in **Mermaid**. Draw one only when it makes the
74
+ solution faster to understand than the words already do. Any shape that helps: a `flowchart` of
75
+ the user's path or of a decision, a `sequenceDiagram` of who talks to whom, a `stateDiagram-v2`
76
+ of what something goes through, a `journey`, a flowchart with two subgraphs for *before* and
77
+ *after*. Each with a `caption` of a few words saying what it shows. Keep it small — about twelve
78
+ nodes at most — with labels of a few words, in quotes, in the founder's words. No styling at all:
79
+ no `classDef`, `style`, colours, `click` or links. Make sure it is valid Mermaid.
56
80
  - **wont_do** — two to four points, each with why or when. The obvious next thing someone would
57
81
  add belongs here.
58
82
  - **measure** — how we'll know (below).
@@ -65,8 +89,6 @@ founder's `stance` and `constraints`, and the `numbers` Nano has measured.
65
89
  - **signal**: who · does what · how many · by when — *3 of the next 10 principals who start a
66
90
  trial run a past search on their first day*. Small numbers for a product with few users; a
67
91
  percentage over a handful of people is not a signal.
68
- - **wrong_if**: one sentence that would prove us wrong. Under a hypothesis, it can show the
69
- problem itself wrong, not only the solution.
70
92
  - **existing**: the events the product already records that measure this — **only what you
71
93
  found**, named exactly as the code or the source names them, with what each counts here.
72
94
  - **added**: the new events, in the **object-action** form — an object the user would recognise,
@@ -82,14 +104,10 @@ founder's `stance` and `constraints`, and the `numbers` Nano has measured.
82
104
 
83
105
  Short sentences, the founder's own words, the product's names for things. No jargon a founder
84
106
  would not use about their own product, no Nano shorthand (*leaf*, *lens*, *pick*, *run*, *crew*).
85
- About 500 words in all. Write in the language the solution is written in.
107
+ About 400 words for your sections. Write in the language the solution is written in.
86
108
 
87
109
  An example of the register, on a recruiting tool:
88
110
 
89
- > **In short** — Principals can start their trial on a search they closed this year. dogo builds
90
- > the longlist as if the role opened today, and shows the people they placed beside it. They judge
91
- > dogo on ground they know, without waiting for a live search.
92
- >
93
111
  > **What we won't do** — Import searches from another tool: the closed searches already in dogo
94
112
  > are enough to start. Count a past search against the trial's limit: it would punish the
95
113
  > principals who try it.
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "spec-solution",
3
+ "description": "Write what a solution on the Roadmap lacks — the open questions as a to-do, the experience with its diagram, what we won't do, how we'll know with analytics, risks, dependencies — or, in a rework, the whole solution again. Reads the founder's code and connected analytics, read-only. Internal crew role; no publication authority.",
4
+ "capabilities": ["read_repo", "read_data"],
5
+ "model": "claude-opus-5-5",
6
+ "maxTurns": 40,
7
+ "timeout": "20m",
8
+ "prompt": "Write the solution's sections, and return only the required JSON."
9
+ }
@@ -1,9 +0,0 @@
1
- {
2
- "name": "spec-brief",
3
- "description": "Write the solution brief of one solution on the Roadmap: a short PRD the founder reads in two minutes — the open questions as a to-do, in short, the experience with its diagram, what we build and won't, how we'll know with analytics, risks, dependencies. Reads the founder's code and connected analytics, read-only. Internal crew role; no publication authority.",
4
- "capabilities": ["read_repo", "read_data"],
5
- "model": "claude-opus-5-5",
6
- "maxTurns": 40,
7
- "timeout": "20m",
8
- "prompt": "Write the solution brief, and return only the required JSON."
9
- }