@nanopm/cli 0.1.20 → 0.1.22

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
@@ -5426,6 +5426,9 @@ function answerForAgent(answer) {
5426
5426
  return isDecideForMe(answer) ? DECIDE_FOR_ME : answer;
5427
5427
  }
5428
5428
  var BUILD_HISTORY_KINDS = ["worktree", "plan", "step", "question", "answer", "paused", "pr", "handed", "throw", "thrown", "closed"];
5429
+ function checkNotesOf(json2) {
5430
+ return (Array.isArray(json2) ? json2 : []).flatMap((n) => typeof n?.ask === "string" && typeof n?.answer === "string" ? [{ ask: n.ask, answer: n.answer }] : []);
5431
+ }
5429
5432
  function originOf(job) {
5430
5433
  const named = (job.params ?? {}).origin;
5431
5434
  return typeof named === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(named) ? named : job.id;
@@ -5476,6 +5479,9 @@ function buildHistory(events, at) {
5476
5479
  const answered2 = last?.kind === "question" && answers.has(last.seq) ? answers.get(last.seq) : null;
5477
5480
  if (answered2 !== null && last.payload.about === "usage") return found({ why: "limit" });
5478
5481
  if (answered2 !== null && (last.payload.about === "conflict" || last.payload.about === "refused")) return found({ why: last.payload.about, answer: answered2 });
5482
+ if (answered2 !== null && last.payload.about === "check" && typeof last.payload.question === "string" && !isDecideForMe(answered2)) {
5483
+ return found({ why: "answer", answer: answered2, keep: last.payload.question });
5484
+ }
5479
5485
  if (answered2 !== null) return found({ why: "answer", answer: answered2 });
5480
5486
  if (last?.kind === "paused") return found({ why: "limit" });
5481
5487
  return found({ why: "lost" });
@@ -11288,6 +11294,7 @@ async function prepareBuild(client, job, clone, adapter) {
11288
11294
  made = true;
11289
11295
  await appendJobEvent(client, job.id, "worktree", { dir, branch, base, fork }, "nanopm");
11290
11296
  await handOverTheSpec(worktree, specForAgent(solution.agent_spec, solution.spec));
11297
+ await handOverNotes(client, job.project_id, worktree);
11291
11298
  await startSolution(client, job.project_id, solution.ref, job.id);
11292
11299
  return { ok: true, build: { solution, clone, dir, worktree, branch, base, fork, network: await network(dir), history } };
11293
11300
  } catch (err) {
@@ -11311,7 +11318,8 @@ async function runBuildJob(input) {
11311
11318
  };
11312
11319
  const park = async (why) => {
11313
11320
  if ("ask" in why) {
11314
- await events.write("question", { ...why.ask, ...why.ask.about ? { report: said } : plan ? {} : { about: "plan" } });
11321
+ const atClose = why.ask.about === "conflict" || why.ask.about === "refused";
11322
+ await events.write("question", { ...why.ask, ...atClose ? { report: said } : why.ask.about || plan ? {} : { about: "plan" } });
11315
11323
  await log("build.ask", `${solution.ref} waits on the founder`);
11316
11324
  } else if (why.resetsAt) {
11317
11325
  await events.write("paused", { why: "limit", resets_at: new Date(why.resetsAt * 1e3).toISOString() });
@@ -11401,6 +11409,10 @@ The project's own check is \`${verifyCommand2}\`. Run it before your last build_
11401
11409
  } else {
11402
11410
  await events.write("text", { text: AGAIN[resume2.why] });
11403
11411
  if (resume2.why === "answer" && spec2.answerFile) await writeFile5(spec2.answerFile, answerForAgent(resume2.answer), { mode: 384 });
11412
+ if (resume2.why === "answer" && resume2.keep) {
11413
+ const { error } = await client.rpc("add_check_note", { p_project_id: job.project_id, p_ask: resume2.keep, p_answer: resume2.answer });
11414
+ if (error) await log("build.note", `the answer was not kept: ${error.message}`, "error");
11415
+ }
11404
11416
  }
11405
11417
  if (resume2?.why === "refused") return openPr();
11406
11418
  let turn = null;
@@ -11462,9 +11474,10 @@ var AGAIN = {
11462
11474
  refused: "Asking GitHub for the pull request again."
11463
11475
  };
11464
11476
  function askOf(input) {
11465
- const { question, options, why } = input ?? {};
11477
+ const { question, options, why, about } = input ?? {};
11466
11478
  return {
11467
11479
  question: String(question ?? ""),
11480
+ ...about === "check" ? { about: "check" } : {},
11468
11481
  ...Array.isArray(options) ? { options: options.map(String) } : {},
11469
11482
  ...typeof why === "string" ? { why } : {}
11470
11483
  };
@@ -11497,6 +11510,16 @@ async function handOverTheSpec(worktree, agentSpec) {
11497
11510
  `);
11498
11511
  }
11499
11512
  }
11513
+ async function handOverNotes(client, projectId, worktree) {
11514
+ const { data } = await client.from("project_preferences").select("check_notes").eq("project_id", projectId).maybeSingle();
11515
+ const lines = checkNotesOf(data?.check_notes).map((n) => `- ${n.ask.replace(/\s*\n\s*/g, " ")}
11516
+ ${n.answer.replace(/\n/g, "\n ")}`);
11517
+ if (!lines.length) return;
11518
+ await writeFile5(join13(worktree.dir, ".nanopm", "CHECK.md"), `# What earlier builds asked the founder, to check the app
11519
+
11520
+ ${lines.join("\n")}
11521
+ `);
11522
+ }
11500
11523
  function stepsOf(input) {
11501
11524
  const steps = input?.steps;
11502
11525
  return Array.isArray(steps) && steps.length && steps.every((s) => typeof s === "string") ? steps : null;
@@ -12640,7 +12663,9 @@ ${renderMap()}`,
12640
12663
  // important question of the run must not be refused for being long.
12641
12664
  question: z21.string().min(1).max(8e3),
12642
12665
  options: z21.array(z21.string().min(1)).max(8).optional(),
12643
- why: z21.string().max(400).optional()
12666
+ why: z21.string().max(400).optional(),
12667
+ // A build asking what it needs to check the app that the code cannot tell (spec 77).
12668
+ about: z21.enum(["check"]).optional().describe("In a build, `check` when you ask what you need to check the app and cannot find in the code (a test user, where an email lands): the answer is kept for the next builds.")
12644
12669
  }
12645
12670
  },
12646
12671
  async ({ question, options, why }, extra) => {
@@ -14727,7 +14752,7 @@ import { promisify as promisify9 } from "util";
14727
14752
  // package.json
14728
14753
  var package_default = {
14729
14754
  name: "@nanopm/cli",
14730
- version: "0.1.20",
14755
+ version: "0.1.22",
14731
14756
  description: "An autonomous product manager for builders who run several small products. Runs on your machine, drives your own coding harness.",
14732
14757
  type: "module",
14733
14758
  license: "MIT",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nanopm/cli",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
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",
@@ -36,6 +36,9 @@ NanoPM commits each step for you and opens the pull request when you are done.
36
36
  5. **Check each acceptance criterion**, inside your last step: go through each one of
37
37
  `.nanopm/SPEC.md` and check it as close to what the user sees as you can here — run the code
38
38
  path it names (call the handler, run the script, render the component), and note what you saw.
39
+ `.nanopm/CHECK.md`, when there is one, holds what earlier builds asked the founder to check the
40
+ app; read it first. What you need and cannot find in the code (a test user, where an email
41
+ lands), ask with `ask_user` and `about: "check"`: the answer is kept for every build after.
39
42
  Your sandbox cannot open a local port, so a running server, a browser or a device is out of
40
43
  reach: say so for what needs one. Never touch a database, a service or a file outside your copy
41
44
  to check something. Keep package caches in `$TMPDIR` (`npm install --cache "$TMPDIR/npm"`, a
@@ -50,7 +53,9 @@ NanoPM commits each step for you and opens the pull request when you are done.
50
53
  - **How I checked it**: first one line per acceptance criterion, exactly `- AC1 — saw: what you
51
54
  saw` or `- AC1 — could not: what, because why`. *Saw* is for what you saw happen when you ran
52
55
  the code the criterion is about — say how (a handler called with what, a script's output);
53
- what you could not reach is *could not*, with why. Never say you saw what you did not run. Then the commands you ran, and their result.
56
+ what you could not reach is *could not*, with why, and ends with `Try it:` and how a person
57
+ checks it in one or two steps, in the founder's words (« sign up with a new account; after the
58
+ notifications step, the warm-up appears »). Never say you saw what you did not run. Then the commands you ran, and their result.
54
59
  7. `log` one line: what you built.
55
60
 
56
61
  ## When to ask the founder