@nanopm/cli 0.1.17 → 0.1.19
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 +44 -25
- package/package.json +1 -1
- package/skills/build/SKILL.md +5 -2
package/dist/index.js
CHANGED
|
@@ -5335,6 +5335,8 @@ var BUILD_SKILL = "build";
|
|
|
5335
5335
|
var BUILD_STEPS_MIN = 1;
|
|
5336
5336
|
var BUILD_STEPS_MAX = 8;
|
|
5337
5337
|
var BUILD_STEP_MAX = 120;
|
|
5338
|
+
var BUILD_UNDERSTOOD_MIN = 40;
|
|
5339
|
+
var BUILD_UNDERSTOOD_MAX = 600;
|
|
5338
5340
|
var BUILD_RESUMES_MAX = 3;
|
|
5339
5341
|
var BUILD_TRAILER = "Co-Authored-By: NanoPM <noreply@nanopm.app>";
|
|
5340
5342
|
var BUILD_COLUMNS = "id, ref, title, status, actor, agent_spec, spec";
|
|
@@ -5369,9 +5371,10 @@ function stepsLeft(plan, done) {
|
|
|
5369
5371
|
function buildDir(home, jobId) {
|
|
5370
5372
|
return `${home.replace(/\/+$/, "")}/.cache/nanopm/builds/${jobId}`;
|
|
5371
5373
|
}
|
|
5372
|
-
function buildBranch(ref, title) {
|
|
5374
|
+
function buildBranch(ref, title, jobId) {
|
|
5373
5375
|
const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40).replace(/-+$/, "");
|
|
5374
|
-
|
|
5376
|
+
const own = jobId.replace(/[^a-z0-9]/gi, "").slice(0, 6).toLowerCase();
|
|
5377
|
+
return `nano/${ref.toLowerCase()}${slug ? `-${slug}` : ""}-${own}`;
|
|
5375
5378
|
}
|
|
5376
5379
|
var REGISTRIES = {
|
|
5377
5380
|
"package-lock.json": ["registry.npmjs.org"],
|
|
@@ -5420,7 +5423,7 @@ var isDecideForMe = (answer) => /^\s*decide(\s+for\s+me)?\s*\.?\s*$/i.test(answe
|
|
|
5420
5423
|
function answerForAgent(answer) {
|
|
5421
5424
|
return isDecideForMe(answer) ? DECIDE_FOR_ME : answer;
|
|
5422
5425
|
}
|
|
5423
|
-
var BUILD_HISTORY_KINDS = ["worktree", "plan", "step", "question", "answer", "paused", "pr", "handed", "throw", "thrown"];
|
|
5426
|
+
var BUILD_HISTORY_KINDS = ["worktree", "plan", "step", "question", "answer", "paused", "pr", "handed", "throw", "thrown", "closed"];
|
|
5424
5427
|
function originOf(job) {
|
|
5425
5428
|
const named = (job.params ?? {}).origin;
|
|
5426
5429
|
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;
|
|
@@ -5662,11 +5665,12 @@ async function ghReady(cwd) {
|
|
|
5662
5665
|
return /ENOENT/.test(errorMessage(err)) ? "the GitHub CLI is not installed on this machine: brew install gh, then gh auth login" : "the GitHub CLI is not signed in on this machine: run gh auth login in your terminal";
|
|
5663
5666
|
}
|
|
5664
5667
|
}
|
|
5665
|
-
async function
|
|
5668
|
+
async function pullRequestState(cwd, number) {
|
|
5666
5669
|
try {
|
|
5667
|
-
|
|
5670
|
+
const { state, headRefName } = JSON.parse(await gh(cwd, ["pr", "view", String(number), "--json", "state,headRefName"]));
|
|
5671
|
+
return (state === "MERGED" || state === "CLOSED" || state === "OPEN") && typeof headRefName === "string" ? { state, head: headRefName } : null;
|
|
5668
5672
|
} catch {
|
|
5669
|
-
return
|
|
5673
|
+
return null;
|
|
5670
5674
|
}
|
|
5671
5675
|
}
|
|
5672
5676
|
async function openPullRequest(cwd, pr) {
|
|
@@ -11226,7 +11230,7 @@ async function prepareBuild(client, job, clone, adapter) {
|
|
|
11226
11230
|
await startSolution(client, job.project_id, solution.ref, job.id);
|
|
11227
11231
|
return { ok: true, build: { solution, clone, ...place, worktree: worktree2, network: await network(place.dir), history } };
|
|
11228
11232
|
}
|
|
11229
|
-
const branch = buildBranch(solution.ref, solution.title);
|
|
11233
|
+
const branch = buildBranch(solution.ref, solution.title, job.id);
|
|
11230
11234
|
await mkdir5(join13(dir, ".."), { recursive: true, mode: 448 });
|
|
11231
11235
|
const { base, fork, worktree } = await addWorktree(clone, dir, branch);
|
|
11232
11236
|
made = true;
|
|
@@ -11269,9 +11273,10 @@ async function runBuildJob(input) {
|
|
|
11269
11273
|
let end = { stop: failed("the agent produced no result") };
|
|
11270
11274
|
for await (const event of adapter.run({ ...spec2, prompt, resumeSessionId: resume3 ? spec2.sessionId : void 0 })) {
|
|
11271
11275
|
await input.onEvent(event);
|
|
11272
|
-
if (event.type === "
|
|
11276
|
+
if (event.type === "tool_result" && event.tool === PLAN_TOOL && !event.isError && !plan) {
|
|
11273
11277
|
plan = stepsOf(event.input);
|
|
11274
|
-
|
|
11278
|
+
const understood = event.input?.understood;
|
|
11279
|
+
if (plan) await events.write("plan", { steps: plan, ...typeof understood === "string" && understood.trim() ? { understood: understood.trim().slice(0, BUILD_UNDERSTOOD_MAX) } : {} });
|
|
11275
11280
|
} else if (event.type === "tool_result" && event.tool === STEP_TOOL && !event.isError) {
|
|
11276
11281
|
const { step, summary } = event.input ?? {};
|
|
11277
11282
|
const index = Number(step) - 1;
|
|
@@ -11513,26 +11518,40 @@ async function throwAwayBuilds(client, daemonId, userId) {
|
|
|
11513
11518
|
}
|
|
11514
11519
|
return thrown;
|
|
11515
11520
|
}
|
|
11516
|
-
async function
|
|
11517
|
-
const { data: building } = await client.from("solutions").select("id, ref, project_id").eq("status", "building");
|
|
11521
|
+
async function settledBuilds(client, daemonId, userId) {
|
|
11522
|
+
const { data: building } = await client.from("solutions").select("id, ref, project_id").eq("status", "building").is("superseded_by", null);
|
|
11518
11523
|
if (!building?.length) return [];
|
|
11519
11524
|
const [{ data: built }, { data: clones }] = await Promise.all([
|
|
11520
|
-
client.from("jobs").select("id, params, pr:result->pr").eq("skill", BUILD_SKILL).eq("status", "succeeded").eq("assigned_daemon_id", daemonId).eq("requested_by", userId).in("params->>solution_id", building.map((s) => s.id)),
|
|
11525
|
+
client.from("jobs").select("id, project_id, params, created_at, pr:result->pr").eq("skill", BUILD_SKILL).eq("status", "succeeded").eq("assigned_daemon_id", daemonId).eq("requested_by", userId).in("params->>solution_id", building.map((s) => s.id)).order("created_at", { ascending: false }),
|
|
11521
11526
|
client.from("clones").select("project_id, repo_path").eq("daemon_id", daemonId)
|
|
11522
11527
|
]);
|
|
11528
|
+
const newest = [...new Map((built ?? []).reverse().map((j) => [j.params?.solution_id, j])).values()];
|
|
11529
|
+
const { data: closed } = newest.length ? await client.from("job_events").select("job_id").in("job_id", newest.map((j) => j.id)).eq("kind", "closed") : { data: [] };
|
|
11523
11530
|
const cloneOf = new Map((clones ?? []).map((c) => [c.project_id, c.repo_path]));
|
|
11524
|
-
const
|
|
11525
|
-
for (const j of
|
|
11531
|
+
const settled2 = [];
|
|
11532
|
+
for (const j of newest.filter((n) => !closed?.some((c) => c.job_id === n.id))) {
|
|
11526
11533
|
const solution = building.find((s) => s.id === j.params?.solution_id);
|
|
11527
11534
|
const number = j.pr?.number;
|
|
11528
11535
|
const clone = solution ? cloneOf.get(solution.project_id) : void 0;
|
|
11529
|
-
if (!solution || typeof number !== "number" || number <= 0 || !clone || !existsSync5(clone)
|
|
11530
|
-
const {
|
|
11531
|
-
if (
|
|
11532
|
-
|
|
11533
|
-
|
|
11534
|
-
|
|
11535
|
-
|
|
11536
|
+
if (!solution || typeof number !== "number" || number <= 0 || !clone || !existsSync5(clone)) continue;
|
|
11537
|
+
const [{ place }, pr] = await Promise.all([loadHistory(client, j), pullRequestState(clone, number)]);
|
|
11538
|
+
if (!place || !pr || pr.head !== place.branch) continue;
|
|
11539
|
+
const at = { id: j.id, project_id: solution.project_id };
|
|
11540
|
+
if (pr.state === "MERGED") {
|
|
11541
|
+
const { error } = await client.rpc("finish_solution", { p_project_id: solution.project_id, p_ref: solution.ref, p_job_id: j.id });
|
|
11542
|
+
if (error) continue;
|
|
11543
|
+
await logActivity({ client }, at, "shipped", `Pull request #${number} merged: ${solution.ref} shipped`);
|
|
11544
|
+
settled2.push({ id: j.id, ref: solution.ref, merged: true });
|
|
11545
|
+
} else if (pr.state === "CLOSED") {
|
|
11546
|
+
const { error } = await client.rpc("unstart_solution", { p_project_id: solution.project_id, p_ref: solution.ref, p_to: wasOf(j.params) });
|
|
11547
|
+
if (error) continue;
|
|
11548
|
+
await deleteBranch(clone, place.branch);
|
|
11549
|
+
await appendJobEvent(client, j.id, "closed", { number }, "nanopm");
|
|
11550
|
+
await logActivity({ client }, at, "closed", `Pull request #${number} closed without merging: ${solution.ref} back on the roadmap`);
|
|
11551
|
+
settled2.push({ id: j.id, ref: solution.ref, merged: false });
|
|
11552
|
+
}
|
|
11553
|
+
}
|
|
11554
|
+
return settled2;
|
|
11536
11555
|
}
|
|
11537
11556
|
async function tellTheApp(accessToken, jobId, fetchImpl = fetch) {
|
|
11538
11557
|
try {
|
|
@@ -11696,7 +11715,7 @@ async function runDaemon(options = {}) {
|
|
|
11696
11715
|
}
|
|
11697
11716
|
if (!options.once && Date.now() - lastShipCheck > SHIP_CHECK_MS) {
|
|
11698
11717
|
lastShipCheck = Date.now();
|
|
11699
|
-
for (const
|
|
11718
|
+
for (const b of await settledBuilds(auth.client, daemonId, auth.userId).catch(() => [])) log(`build ${b.id}: its pull request ${b.merged ? `merged, ${b.ref} shipped` : `closed without merging, ${b.ref} back on the roadmap`}`);
|
|
11700
11719
|
}
|
|
11701
11720
|
if (!options.once && Date.now() - lastDeployCheck > DEPLOY_CHECK_MS) {
|
|
11702
11721
|
lastDeployCheck = Date.now();
|
|
@@ -12811,8 +12830,8 @@ ${renderMap()}`,
|
|
|
12811
12830
|
server.registerTool(
|
|
12812
12831
|
"build_plan",
|
|
12813
12832
|
{
|
|
12814
|
-
description: `Say
|
|
12815
|
-
inputSchema: { steps: z21.array(z21.string().min(3).max(BUILD_STEP_MAX)).min(BUILD_STEPS_MIN).max(BUILD_STEPS_MAX) }
|
|
12833
|
+
description: `Say what you understood and your plan before you change anything. \`understood\`: in your own words and plain language, the problem, what you will change, and what you will not touch \u2014 a short paragraph the founder reads first, so a misunderstanding shows before any code (at most ${BUILD_UNDERSTOOD_MAX} characters). \`steps\`: ${BUILD_STEPS_MIN} to ${BUILD_STEPS_MAX}, in order, each one change the founder can recognise, in their words (at most ${BUILD_STEP_MAX} characters); the last is your own check of the whole change. Call it once; the founder sees the paragraph, then the steps as a checklist.`,
|
|
12834
|
+
inputSchema: { understood: z21.string().min(BUILD_UNDERSTOOD_MIN).max(BUILD_UNDERSTOOD_MAX), steps: z21.array(z21.string().min(3).max(BUILD_STEP_MAX)).min(BUILD_STEPS_MIN).max(BUILD_STEPS_MAX) }
|
|
12816
12835
|
},
|
|
12817
12836
|
async ({ steps }) => ctx.workspace ? json({ recorded: steps.length, note: "Recorded. Build step 1 now; call build_step when it is done." }) : failure("this job builds nothing")
|
|
12818
12837
|
);
|
|
@@ -14651,7 +14670,7 @@ import { promisify as promisify8 } from "util";
|
|
|
14651
14670
|
// package.json
|
|
14652
14671
|
var package_default = {
|
|
14653
14672
|
name: "@nanopm/cli",
|
|
14654
|
-
version: "0.1.
|
|
14673
|
+
version: "0.1.19",
|
|
14655
14674
|
description: "An autonomous product manager for builders who run several small products. Runs on your machine, drives your own coding harness.",
|
|
14656
14675
|
type: "module",
|
|
14657
14676
|
license: "MIT",
|
package/package.json
CHANGED
package/skills/build/SKILL.md
CHANGED
|
@@ -24,8 +24,11 @@ NanoPM commits each step for you and opens the pull request when you are done.
|
|
|
24
24
|
2. If the code contradicts the spec — what it asks already exists, a step it assumes is not
|
|
25
25
|
there, the size does not hold — `ask_user` before you plan: what the spec says, what the code
|
|
26
26
|
has, what it changes, and the options. Do not guess what the founder decided.
|
|
27
|
-
3. `build_plan`:
|
|
28
|
-
|
|
27
|
+
3. `build_plan`: first, in `understood`, what you understood, in your own words and plain
|
|
28
|
+
language — the problem, what you will change, what you will not touch. The founder reads it
|
|
29
|
+
before anything else; a misunderstanding must show there, not in the pull request. Then your
|
|
30
|
+
plan, 1 to 8 steps, in order, each a change the founder can recognise, in their words. The last
|
|
31
|
+
step is your check of the whole change.
|
|
29
32
|
4. Build one step at a time. After each: run its tests and the project's own checks (the ones
|
|
30
33
|
CLAUDE.md, the README or `package.json` name), fix what they report, then `build_step` with the
|
|
31
34
|
step's number and one line on what you did. NanoPM commits right then, so a step is committed
|