@papi-ai/server 0.7.59 → 0.7.61

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.
@@ -1189,7 +1189,12 @@ var init_proxy_adapter = __esm({
1189
1189
  "listContributorReleasePrs",
1190
1190
  "claimReview",
1191
1191
  "getSiblingAds",
1192
- "getSiblingRepoTasks"
1192
+ "getSiblingRepoTasks",
1193
+ // task-2828 (C339): attributed-intelligence analytics reader — pg-only this cycle.
1194
+ // Hosted forwarding needs a SECURITY DEFINER RPC + edge handler (like task-2394 did
1195
+ // for getModuleEstimationStats); until then keep it here so hosted degrades to a
1196
+ // safe `undefined` rather than forwarding into a 403. Wire under task-2390.
1197
+ "getModelOutcomeStats"
1193
1198
  // task-2394 (C329) — Batch A wired: findPendingDocActionsForTask,
1194
1199
  // getModuleEstimationStats and getDecisionScorePatterns now have edge case handlers
1195
1200
  // (each backed by a SECURITY DEFINER RPC, migration 20260714140000) plus
package/dist/index.js CHANGED
@@ -1306,7 +1306,12 @@ var init_proxy_adapter = __esm({
1306
1306
  "listContributorReleasePrs",
1307
1307
  "claimReview",
1308
1308
  "getSiblingAds",
1309
- "getSiblingRepoTasks"
1309
+ "getSiblingRepoTasks",
1310
+ // task-2828 (C339): attributed-intelligence analytics reader — pg-only this cycle.
1311
+ // Hosted forwarding needs a SECURITY DEFINER RPC + edge handler (like task-2394 did
1312
+ // for getModuleEstimationStats); until then keep it here so hosted degrades to a
1313
+ // safe `undefined` rather than forwarding into a 403. Wire under task-2390.
1314
+ "getModelOutcomeStats"
1310
1315
  // task-2394 (C329) — Batch A wired: findPendingDocActionsForTask,
1311
1316
  // getModuleEstimationStats and getDecisionScorePatterns now have edge case handlers
1312
1317
  // (each backed by a SECURITY DEFINER RPC, migration 20260714140000) plus
@@ -7527,6 +7532,9 @@ ${footer}`);
7527
7532
  function newTaskJoinKey(task, index) {
7528
7533
  return task.tempId ?? `new-${index + 1}`;
7529
7534
  }
7535
+ function isUnresolvedPlaceholder(taskId, map) {
7536
+ return !map.has(taskId) && /^new-\d+$/i.test(taskId);
7537
+ }
7530
7538
  var NONE_PATTERN2 = /^none\b/i;
7531
7539
  function normalizeText2(text) {
7532
7540
  return text.trim().toLowerCase().replace(/[.,;:!]+$/, "").replace(/\s+/g, " ");
@@ -9010,7 +9018,7 @@ var PLAN_FRAGMENT_DESIGN_BRIEF = `
9010
9018
  **Design brief task detection:** When a task's task type is "design-brief", generate a DESIGN BRIEF handoff. Replace the standard SCOPE (DO THIS) section with these type-specific sections:
9011
9019
  - AUDIENCE: Who this design is for \u2014 persona and context of use (e.g. "non-technical Owner, first dashboard visit")
9012
9020
  - BRAND CONSTRAINTS: Palette, typography, tone \u2014 pull from \`.impeccable.md\` (dev patterns, anti-patterns, component rules) AND \`docs/branding/brand-book.html\` (brand identity, positioning, voice canon) if present. If neither exists, state "No brand doc \u2014 Owner should define constraints before starting."
9013
- - DELIVERABLE FORMAT: What the output looks like \u2014 Claude Design handoff package / annotated mockup / style spec. Be specific so the person doing the work knows what "done" means.
9021
+ - DELIVERABLE FORMAT: What the output looks like \u2014 design handoff package / annotated mockup / style spec. Be specific so the person doing the work knows what "done" means.
9014
9022
  - REVIEW POINTS: What the Owner must approve before the design is considered done (e.g. layout, copy, colour, imagery).
9015
9023
  Keep SCOPE BOUNDARY, ACCEPTANCE CRITERIA, SECURITY CONSIDERATIONS, and PRE-BUILD VERIFICATION sections as normal.
9016
9024
  Add to ACCEPTANCE CRITERIA: "[ ] Deliverable format confirmed with Owner before starting" and "[ ] Design output is self-contained \u2014 includes enough context for a developer to implement without further clarification."`;
@@ -9209,6 +9217,21 @@ function buildPlanFullInstructionsConditional(flags, ctx) {
9209
9217
  if (!flags || !ctx) return PLAN_FULL_INSTRUCTIONS;
9210
9218
  return composeFullModeInstructions(flags, ctx);
9211
9219
  }
9220
+ var CYCLE_DENSITY_TARGETS = {
9221
+ light: { label: "Light", range: "2-3" },
9222
+ standard: { label: "Standard", range: "3-5" },
9223
+ deep: { label: "Deep", range: "6-8" }
9224
+ };
9225
+ function buildCycleDensityDirective(density) {
9226
+ if (!density) return "";
9227
+ const target = CYCLE_DENSITY_TARGETS[density];
9228
+ return [
9229
+ `## CYCLE DENSITY: ${target.label}`,
9230
+ "",
9231
+ `The user set this cycle's density to **${density}** \u2014 aim for roughly **${target.range} tasks** this cycle.`,
9232
+ `This is a TARGET to size toward, not a hard floor or cap: still honour explicit user direction, any pre-assigned tasks, and impact-based sizing. If fewer genuinely-valuable tasks exist, plan fewer \u2014 do NOT pad the cycle to hit the number.`
9233
+ ].join("\n");
9234
+ }
9212
9235
  function buildPlanUserMessage(ctx) {
9213
9236
  const modeLabel = ctx.mode.toUpperCase();
9214
9237
  const parts = [
@@ -9226,6 +9249,10 @@ function buildPlanUserMessage(ctx) {
9226
9249
  ""
9227
9250
  );
9228
9251
  }
9252
+ const densityDirective = buildCycleDensityDirective(ctx.density);
9253
+ if (densityDirective) {
9254
+ parts.push(densityDirective, "");
9255
+ }
9229
9256
  if (ctx.mode === "bootstrap") {
9230
9257
  parts.push(PLAN_BOOTSTRAP_INSTRUCTIONS);
9231
9258
  } else {
@@ -10101,15 +10128,49 @@ phases:
10101
10128
 
10102
10129
  *No decisions locked yet. These are added as planning cycles confirm strategic choices.*`;
10103
10130
  }
10131
+ function buildPreScanInstruction(opts) {
10132
+ const docRouting = opts.hosted ? "paste the relevant content (decision records, roadmap, spec excerpts) directly into `description` \u2014 over a remote connector PAPI cannot read local file paths." : "pass local doc file paths as `sources` (comma-separated), or paste content into `description`.";
10133
+ return [
10134
+ `**\u{1F50E} Before you generate anything \u2014 scan the project and gather real context.**`,
10135
+ `A brief and Active Decisions built from the real code and docs are far sharper than ones guessed from a project name. You (the agent running this) have local access that PAPI does not \u2014 do the gathering, then pass it back.`,
10136
+ ``,
10137
+ `1. **Confirm the environment.** Check that you are running from the root of this project's dev environment \u2014 the folder that holds its source, git history, and docs. If you are not there, or you are connected over a remote connector with no access to the user's files, say so and ask the user where the project lives before continuing.`,
10138
+ `2. **Scan the code.** List the top-level directories and files, read the package/build manifest and the README (first ~3000 chars), and collect recent commit subjects. Pass this back as \`codebase_scan\` on the next \`setup\` call.`,
10139
+ `3. **Scan the docs.** Look through \`docs/\`, design notes, ADRs/decision records, and any roadmap or planning files \u2014 not just code.`,
10140
+ `4. **Check for sibling / separate repos.** Many projects span more than one repo (a separate frontend, backend, infra, or mobile repo, or other packages in a monorepo). Ask the user whether any related repos or directories exist, and scan those too.`,
10141
+ `5. **Ask the user to point you at extra context.** Prompt them: "Is there anything else I should read before setting this up \u2014 a PRD or spec, decision records, a roadmap, or links to related repos or docs?" Fold whatever they share into the scan.`,
10142
+ `6. **Re-run \`setup\` with what you gathered:** pass the code scan as \`codebase_scan\`; for docs, decision records, and roadmap, ${docRouting} Then answer the prompts below.`,
10143
+ ``,
10144
+ `If there is genuinely no code and no docs yet, skip this and generate from what the user tells you \u2014 the zero-context path is fully supported.`
10145
+ ].join("\n");
10146
+ }
10147
+ var PAPI_DOCS_QUICKSTART_URL = "https://getpapi.ai/docs/guide/quick-start";
10148
+ var PAPI_PUBLIC_REPO_URL = "https://github.com/getpapi/papi";
10149
+ function buildNewProjectSetupInstruction(opts) {
10150
+ const visionRouting = opts.hosted ? "paste anything they share (notes, a PRD, links) into `description` \u2014 over a remote connector PAPI cannot read local file paths." : "fold anything they share (notes, a PRD, a sketch, links) into `description`, or pass local doc paths as `sources`.";
10151
+ return [
10152
+ `**\u{1F331} New project \u2014 start from the vision.**`,
10153
+ `There's no existing code to mine, so setup builds from what the user wants to create. Do two things before answering the prompts below.`,
10154
+ ``,
10155
+ `1. **Get oriented on PAPI.** So you can guide the user well \u2014 whatever assistant you are \u2014 skim the quick-start docs at ${PAPI_DOCS_QUICKSTART_URL} and the public repo at ${PAPI_PUBLIC_REPO_URL}. They explain the plan \u2192 build \u2192 review cycle the user will run after setup.`,
10156
+ `2. **Draw out the vision with the user.** Ask what they're building and for whom, the core problem it solves, and what success looks like \u2014 enough to write a real brief, a North Star, and a starter backlog rather than a generic scaffold. If they already have a PRD, sketch, notes, or links, ${visionRouting}`,
10157
+ ``,
10158
+ `Then answer the prompts below to generate the brief, decisions, North Star, and a vision-first starter backlog.`
10159
+ ].join("\n");
10160
+ }
10104
10161
  var AD_SEED_SYSTEM = `You are a technical architect seeding initial Active Decisions for a new software project managed by PAPI.
10105
10162
 
10106
10163
  Active Decisions (ADs) are documented architectural choices with confidence levels. They guide the planner and builder agents \u2014 without ADs, planning output is generic and unhelpful.
10107
10164
 
10108
10165
  IMPORTANT: You are running as a non-interactive API call. Do NOT ask questions. Produce decisions directly.
10109
10166
 
10167
+ ## SOURCE OF TRUTH \u2014 extract real decisions first
10168
+
10169
+ If the context includes decision records, ADRs, a roadmap, a README, or codebase analysis, your PRIMARY job is to EXTRACT the real, already-made decisions from that material \u2014 not to invent generic ones. Read the provided context and capture each load-bearing choice the project has actually made (its stack, data model, architecture, positioning, deployment posture). Only fall back to informed defaults for a project of this type when the context contains no evidenced decisions.
10170
+
10110
10171
  ## OUTPUT FORMAT
10111
10172
 
10112
- Return a JSON array of 3-5 Active Decisions. Each AD must have:
10173
+ Return a JSON array of Active Decisions \u2014 ONE per real decision you find. Each AD must have:
10113
10174
  - "id": "AD-1", "AD-2", etc.
10114
10175
  - "body": Full markdown block including ### heading, confidence tag, and body text
10115
10176
 
@@ -10123,17 +10184,20 @@ The body format for each AD:
10123
10184
 
10124
10185
  ## GUIDELINES
10125
10186
 
10126
- - All seeded ADs should have Confidence: MEDIUM (they are informed defaults, not confirmed choices)
10127
- - Focus on decisions that genuinely differ by project type \u2014 avoid generic truisms
10128
- - Each AD should be actionable and falsifiable (something the team could decide differently)
10187
+ - **Count follows the evidence \u2014 there is NO fixed target.** Seed as many ADs as there are real, distinct decisions in the material. Do NOT pad to a number, and do NOT fabricate decisions to hit a count. When the context has no evidenced decisions, return a small set (2-4) of informed defaults for this project type.
10188
+ - **Confidence reflects evidence:** a decision explicitly documented as settled in the source may be HIGH; an informed default you inferred stays MEDIUM.
10189
+ - Never mint an AD that is not a genuine stance-with-alternatives \u2014 a preference, a fact, or a config value is NOT an AD.
10190
+ - Focus on decisions that genuinely differ by project \u2014 avoid generic truisms.
10191
+ - Each AD should be actionable and falsifiable (something the team could decide differently).
10129
10192
  - Cover different concerns: architecture, data, deployment, testing strategy, API design, etc.
10130
- - Keep each AD body to 4-6 lines \u2014 concise and scannable
10193
+ - Keep each AD body to 4-6 lines \u2014 concise and scannable.
10194
+ - **Do NOT duplicate** any decision already listed as an existing Active Decision in the context \u2014 skip it entirely.
10131
10195
  - **Quality bar:** ADs are for product and architecture choices that constrain future work \u2014 technology selections, data model designs, UX principles, strategic positioning. They are NOT for process preferences, configuration choices, or temporary workarounds.
10132
10196
 
10133
10197
  Return ONLY valid JSON \u2014 no preamble, no code fences, no explanation.`;
10134
10198
  function buildAdSeedPrompt(ctx) {
10135
10199
  const parts = [
10136
- `Generate 3-5 Active Decisions for this project.`,
10200
+ ctx.codebaseContext ? `Extract the real Active Decisions for this project from the context below. Seed one AD per genuine decision \u2014 do not cap the count, and do not fabricate.` : `Seed the informed default Active Decisions for this project (no decision docs were supplied \u2014 infer sensible MEDIUM-confidence defaults for this project type).`,
10137
10201
  "",
10138
10202
  `**Project:** ${ctx.projectName}`,
10139
10203
  `**Type:** ${ctx.projectType}`,
@@ -10146,12 +10210,57 @@ function buildAdSeedPrompt(ctx) {
10146
10210
  if (ctx.constraints) {
10147
10211
  parts.push(`**Constraints:** ${ctx.constraints}`);
10148
10212
  }
10213
+ if (ctx.codebaseContext) {
10214
+ parts.push(
10215
+ "",
10216
+ "## Project context \u2014 extract the real decisions from here",
10217
+ ctx.codebaseContext
10218
+ );
10219
+ }
10220
+ if (ctx.existingDecisions && ctx.existingDecisions.length > 0) {
10221
+ parts.push(
10222
+ "",
10223
+ "## Existing Active Decisions \u2014 do NOT duplicate these",
10224
+ ...ctx.existingDecisions.map((d) => `- ${d}`)
10225
+ );
10226
+ }
10149
10227
  parts.push(
10150
10228
  "",
10151
10229
  'Return a JSON array of AD objects with "id" and "body" fields. No other text.'
10152
10230
  );
10153
10231
  return parts.join("\n");
10154
10232
  }
10233
+ var NORTH_STAR_SYSTEM = `You are helping a builder define the North Star for a software project set up with PAPI.
10234
+
10235
+ A North Star is the ONE outcome that best captures whether the project is succeeding \u2014 a single, measurable, user-centred statement the team can steer by. It is not a feature list and not a vision paragraph.
10236
+
10237
+ IMPORTANT: You are running as a non-interactive API call. Do NOT ask questions in your output.
10238
+
10239
+ ## HOW TO PRODUCE IT
10240
+ - If the provided context (brief, docs, decision records) ALREADY states a North Star, goal metric, or primary success measure, EXTRACT and restate it \u2014 do not invent a competing one.
10241
+ - Otherwise, PROPOSE the most fitting North Star from the project's purpose and users. (The calling agent will confirm it with the user before it is saved.)
10242
+
10243
+ ## OUTPUT FORMAT
10244
+ Return ONLY the North Star statement \u2014 one or two sentences, concrete and measurable where possible. No heading, no preamble, no quotes, no code fences.`;
10245
+ function buildNorthStarPrompt(inputs) {
10246
+ const parts = [
10247
+ `Define the North Star for this project.`,
10248
+ "",
10249
+ `**Project:** ${inputs.projectName}`,
10250
+ `**Description:** ${inputs.description?.trim() || "(not provided \u2014 infer from context below)"}`,
10251
+ `**Target users:** ${inputs.targetUsers?.trim() || "(not provided \u2014 infer from context below)"}`,
10252
+ `**Problems solved:** ${inputs.problems}`
10253
+ ];
10254
+ if (inputs.codebaseContext) {
10255
+ parts.push(
10256
+ "",
10257
+ "## Project context \u2014 extract an existing North Star from here if one is stated",
10258
+ inputs.codebaseContext
10259
+ );
10260
+ }
10261
+ parts.push("", "Return only the North Star statement.");
10262
+ return parts.join("\n");
10263
+ }
10155
10264
  var CONVENTIONS_SYSTEM = `You are a senior software engineer generating CLAUDE.md coding conventions for a new project.
10156
10265
 
10157
10266
  IMPORTANT: You are running as a non-interactive API call. Do NOT ask questions. Produce conventions directly.
@@ -10842,7 +10951,7 @@ function computeCarryForwardStaleness(log2, doneTaskIds) {
10842
10951
  if (stale.length === 0) return void 0;
10843
10952
  const lines = stale.map(([id, count]) => `- **${id}** \u2014 deferred ${count} consecutive cycle(s)`);
10844
10953
  return [
10845
- `\u26A0\uFE0F ${stale.length} task(s) have been in carry-forward for 3+ consecutive cycles. The planner must resolve each \u2014 either escalate to P1 High or recommend cancellation with a closure reason. Deferring again without justification is not acceptable.`,
10954
+ `\u26A0\uFE0F ${stale.length} task(s) have been in carry-forward for 3+ consecutive cycles. The planner must resolve each \u2014 either escalate it to P1 High, or move it to Deferred with a reason. Do NOT cancel a stale carry-forward task: cancelling removes it from the owner's field of view, and only the owner may cancel. Deferring again without a fresh justification is not acceptable.`,
10846
10955
  "",
10847
10956
  ...lines
10848
10957
  ].join("\n");
@@ -10992,6 +11101,15 @@ async function resolveOwnerGate(adapter2, config2) {
10992
11101
 
10993
11102
  // src/services/plan.ts
10994
11103
  var PLAN_BUILD_REPORT_BUDGET = { maxReports: 12, fieldBudget: 280 };
11104
+ function leadChainWithRecommended(chain, recommendedTaskId) {
11105
+ const rec = recommendedTaskId?.trim();
11106
+ const existing = (chain ?? "").trim();
11107
+ if (!rec) return existing || void 0;
11108
+ const firstMentioned = existing.match(/task-\d+/)?.[0];
11109
+ if (firstMentioned === rec) return existing || void 0;
11110
+ const lead = `Build order: ${rec} first.`;
11111
+ return existing ? `${lead} ${existing}` : lead;
11112
+ }
10995
11113
  async function resolvePlanScope(adapter2, config2) {
10996
11114
  const gate = await resolveOwnerGate(adapter2, config2);
10997
11115
  return { callerUserId: gate.callerUserId, ownerUserId: gate.ownerUserId, callerIsOwner: gate.callerIsOwner };
@@ -11940,7 +12058,12 @@ ${cleanContent}`;
11940
12058
  // project owner when unset, so owner-operated plans stay correct).
11941
12059
  ...options.ownerUserId ? { userId: options.ownerUserId } : {},
11942
12060
  // task-2483 (C319): persist the plan's Dependency Chain build-order markdown.
11943
- ...data.dependencyChain ? { dependencyChain: data.dependencyChain } : {}
12061
+ // task-2823 (C338): seed it to lead with recommendedTaskId when the plan omits a
12062
+ // chain or it doesn't lead with the planner's pick, so the hub next-move agrees.
12063
+ ...(() => {
12064
+ const chain = leadChainWithRecommended(data.dependencyChain, data.recommendedTaskId);
12065
+ return chain ? { dependencyChain: chain } : {};
12066
+ })()
11944
12067
  };
11945
12068
  const earlyCreateWarnings = [];
11946
12069
  try {
@@ -12327,10 +12450,10 @@ ${cleanContent}`;
12327
12450
  }
12328
12451
  for (const handoff of data.cycleHandoffs) {
12329
12452
  const resolvedId = newTaskIdMap.get(handoff.taskId) ?? handoff.taskId;
12330
- if (resolvedId === handoff.taskId && /^new-\d+$/i.test(handoff.taskId)) {
12453
+ if (isUnresolvedPlaceholder(handoff.taskId, newTaskIdMap)) {
12331
12454
  const titleLine = handoff.buildHandoff.match(/^Task:\s*(.+)/m)?.[1] ?? "(unknown)";
12332
- console.error(
12333
- `[plan] UNRESOLVED handoff reference "${handoff.taskId}" (task: ${titleLine.slice(0, 80)}) \u2014 no newTask carries this tempId/index. Handoff NOT written. Ensure each newTasks entry has a tempId matching its cycleHandoffs taskId (task-2242).`
12455
+ warnings.push(
12456
+ `UNRESOLVED handoff "${handoff.taskId}" (task: ${titleLine.slice(0, 80)}) \u2014 no newTask carries this tempId/index; handoff NOT written and the task is missing from the cycle. Ensure each newTasks entry has a tempId matching its cycleHandoffs taskId (task-2242).`
12334
12457
  );
12335
12458
  continue;
12336
12459
  }
@@ -12373,7 +12496,13 @@ ${cleanContent}`;
12373
12496
  ...options.ownerUserId ? { userId: options.ownerUserId } : {},
12374
12497
  // task-2483 (C319): persist the plan's Dependency Chain build-order markdown.
12375
12498
  // This full write patches the early minimal row created in Phase 1.
12376
- ...data.dependencyChain ? { dependencyChain: data.dependencyChain } : {}
12499
+ // task-2823 (C338): seed it to lead with recommendedTaskId (mapped from any
12500
+ // new-task tempId) so the hub next-move matches the planner's explicit pick.
12501
+ ...(() => {
12502
+ const recId = data.recommendedTaskId ? newTaskIdMap.get(data.recommendedTaskId) ?? data.recommendedTaskId : null;
12503
+ const chain = leadChainWithRecommended(data.dependencyChain, recId);
12504
+ return chain ? { dependencyChain: chain } : {};
12505
+ })()
12377
12506
  };
12378
12507
  await adapter2.createCycle(cycle);
12379
12508
  } catch (err) {
@@ -12596,7 +12725,7 @@ async function processLlmOutput(adapter2, config2, rawOutput, mode, cycleNumber,
12596
12725
  skippedCancellations
12597
12726
  };
12598
12727
  }
12599
- async function preparePlan(adapter2, config2, filters, focus, force, handoffsOnly, skipHandoffs, tracker) {
12728
+ async function preparePlan(adapter2, config2, filters, focus, force, handoffsOnly, skipHandoffs, tracker, density) {
12600
12729
  const prepareTimer = startTimer();
12601
12730
  tracker?.mark("validate_and_prepare");
12602
12731
  let t = startTimer();
@@ -12657,6 +12786,7 @@ async function preparePlan(adapter2, config2, filters, focus, force, handoffsOnl
12657
12786
  throw new Error("TEMPLATE_BRIEF");
12658
12787
  }
12659
12788
  if (skipHandoffs) context.skipHandoffs = true;
12789
+ if (density) context.density = density;
12660
12790
  tracker?.mark("codebase_scan");
12661
12791
  t = startTimer();
12662
12792
  try {
@@ -12948,7 +13078,6 @@ function defaultHint(tool) {
12948
13078
  function buildSubagentDispatchPrompt(input) {
12949
13079
  const {
12950
13080
  tool,
12951
- applyMode,
12952
13081
  cycleNumber = 0,
12953
13082
  strategyReviewWarning = "",
12954
13083
  taskId,
@@ -12960,7 +13089,7 @@ function buildSubagentDispatchPrompt(input) {
12960
13089
  const isReview = tool === "review_submit";
12961
13090
  let applyNote;
12962
13091
  if (tool === "plan") {
12963
- applyNote = `\`plan\` again with mode="apply", llm_response=<sub-agent output>, plan_mode="${applyMode ?? "full"}", cycle_number=${cycleNumber + 1}, strategy_review_warning=${JSON.stringify(strategyReviewWarning)}`;
13092
+ applyNote = `\`plan\` again with mode="apply", llm_response=<sub-agent output>, cycle_number=${cycleNumber + 1}, strategy_review_warning=${JSON.stringify(strategyReviewWarning)}`;
12964
13093
  } else if (tool === "strategy_review") {
12965
13094
  applyNote = `\`strategy_review\` again with mode="apply", llm_response=<sub-agent output>, cycle_number=${cycleNumber}`;
12966
13095
  } else if (tool === "zoom_out") {
@@ -13024,6 +13153,105 @@ Do NOT post-process the sub-agent reply. The apply path expects the full structu
13024
13153
  step2Note
13025
13154
  ].join("\n");
13026
13155
  }
13156
+ var REVIEW_PRESETS = {
13157
+ // Minimal gate: does it work, and is every write actually read (AD-74)?
13158
+ gate: ["correctness", "wiring"],
13159
+ // Everything.
13160
+ full: ["correctness", "security", "wiring", "test-quality", "scope-drift"],
13161
+ // Security first, plus correctness and a lighter wiring check.
13162
+ "security-focused": ["security", "correctness", "wiring"]
13163
+ };
13164
+ var LENS_INSTRUCTIONS = {
13165
+ correctness: "CORRECTNESS lens. Does the change do what the build report claims, without bugs? Hunt logic errors, off-by-one/boundary mistakes, unhandled error branches, null/undefined hazards, race conditions, and wrong assumptions about inputs. Ignore style, security, and scope \u2014 other lenses own those.",
13166
+ security: "SECURITY lens. Any auth/data/secret/injection risk introduced? Check for missing project_id scoping on multi-tenant SQL, RLS bypass via service role, unescaped input, leaked credentials/tokens in logs or responses, and newly-public routes (PUBLIC_PATHS). Ignore correctness/style unless it is the vector for a security issue.",
13167
+ wiring: "WIRING lens (AD-74 \u2014 producer\u2192store\u2192consumer). A write with no reader is NOT done. For every new value produced (state, DB column, event, field), confirm something actually CONSUMES it \u2014 grep for the reader. localStorage is a cache, never a store. Flag decorative writes, dead affordances, and green gates that guard a component nothing imports. Ignore correctness/security details \u2014 focus on whether the data path is actually connected end-to-end.",
13168
+ "test-quality": "TEST-QUALITY lens. Are the tests present and meaningful? A test that cannot fail is worse than none. Check that assertions test TRANSITIONS not snapshots, that a probe would actually go RED if the code broke, and that new behaviour is covered. Flag tests that assert on mocks instead of real behaviour, and missing coverage on the changed paths.",
13169
+ "scope-drift": "SCOPE-DRIFT lens. Does the diff match the BUILD HANDOFF scope? Flag unrelated changes, files touched outside the stated scope, new dependencies/abstractions not asked for, and \u2014 conversely \u2014 acceptance criteria from the handoff that were skipped. Ignore whether the in-scope code is correct; only judge whether it is the RIGHT set of changes."
13170
+ };
13171
+ var SYNTHESIS_CONTRACT_SHAPE = '{"verdict":"pass|warn|fail","summary":"<one-line consolidated assessment>","findings":[{"severity":"error|warning|info","file":"<path>","line":<number>,"message":"<specific issue>"}]}';
13172
+ var LEG_CONTRACT_SHAPE = '{"lens":"<lens-name>","verdict":"pass|warn|fail","findings":[{"severity":"error|warning|info","file":"<path>","line":<number>,"message":"<specific issue>"}]}';
13173
+ function legId(lens) {
13174
+ return `leg-${lens}`;
13175
+ }
13176
+ function buildFanoutReviewDispatchPrompt(input) {
13177
+ const { taskId, preset, systemPrompt, userMessage, contextBytes } = input;
13178
+ const lenses = REVIEW_PRESETS[preset];
13179
+ const n = lenses.length;
13180
+ const sizeNote = contextBytes !== void 0 ? ` \xB7 ~${Math.round(contextBytes / 1024)} KB context` : "";
13181
+ const sharedContext = `<review_rubric>
13182
+ ${systemPrompt}
13183
+ </review_rubric>
13184
+
13185
+ <build_under_review>
13186
+ ${userMessage}
13187
+ </build_under_review>`;
13188
+ const legBlocks = lenses.map((lens, i) => {
13189
+ const legPrompt = `You are lens ${i + 1} of ${n} in a PAPI multi-lens build review. Review the completed build below through ONE lens only.
13190
+
13191
+ LENS \u2014 ${LENS_INSTRUCTIONS[lens]}
13192
+
13193
+ CONTRACT:
13194
+ - The build report says what was intended; the diff is what actually changed.
13195
+ - Report ONLY findings your lens (${lens}) is responsible for. Do not stray into other lenses.
13196
+ - Return ONLY a single JSON object \u2014 no preamble, no commentary, no fences \u2014 in exactly this shape:
13197
+ ${LEG_CONTRACT_SHAPE.replace("<lens-name>", lens)}
13198
+ - "pass" = no blocking issues for this lens; "warn" = minor/non-blocking; "fail" = a blocking issue this lens found. \`file\`/\`line\` are optional. Empty \`findings\` is valid when your lens is clean.
13199
+
13200
+ ${sharedContext}`;
13201
+ return [
13202
+ `#### Lens ${i + 1}/${n} \u2014 ${lens} \`[${legId(lens)}]\``,
13203
+ "",
13204
+ `<<<BEGIN_LENS_PROMPT:${lens}>>>`,
13205
+ legPrompt,
13206
+ `<<<END_LENS_PROMPT:${lens}>>>`
13207
+ ].join("\n");
13208
+ });
13209
+ const dependsOn = `[${lenses.map(legId).join(", ")}]`;
13210
+ const synthesisPrompt = `You are the SYNTHESIS step of a PAPI multi-lens build review.
13211
+
13212
+ depends_on = ${dependsOn}
13213
+
13214
+ You receive the JSON outputs of all ${n} specialist lens legs below. Your job:
13215
+ - Merge every leg's findings into ONE consolidated review.
13216
+ - Dedupe: collapse findings that multiple lenses raised about the same file/line/issue into a single entry (keep the highest severity).
13217
+ - Prioritize: order findings most-severe first (error \u2192 warning \u2192 info).
13218
+ - Decide ONE overall verdict: "fail" if any leg found a blocking issue, "warn" if only minor nits remain, "pass" if all legs were clean.
13219
+ - Return ONLY a single JSON object \u2014 no preamble, no commentary, no fences \u2014 in exactly this shape (the review_submit auto_review contract):
13220
+ ${SYNTHESIS_CONTRACT_SHAPE}
13221
+
13222
+ <lens_leg_outputs>
13223
+ Paste every lens leg's JSON output here, one per line or concatenated:
13224
+ <<<PASTE_ALL_LEG_OUTPUTS_HERE>>>
13225
+ </lens_leg_outputs>`;
13226
+ const applyNote = `\`review_submit\` with task_id="${taskId}", stage="build-acceptance", verdict=<your decision: accept / request-changes / reject>, comments=<your reasoning>, and auto_review set to the synthesis JSON object verbatim. The synthesized verdict is a RECOMMENDATION \u2014 you make the final call.`;
13227
+ return [
13228
+ `## PAPI review_submit \u2014 Multi-Lens Fan-Out Review (${taskId} \xB7 preset: ${preset} \xB7 ${n} lens${n === 1 ? "" : "es"} + synthesis${sizeNote})`,
13229
+ "",
13230
+ "This build review fans out across specialist lenses: each lens below reviews the SAME diff in parallel, then a synthesis step dedupes their findings into one prioritized verdict. **PAPI emits this prompt \u2014 YOU (the host) run the Task calls. PAPI never executes the agents itself.**",
13231
+ "",
13232
+ "---",
13233
+ "",
13234
+ `### Step 1 \u2014 Dispatch ${n} specialist lens${n === 1 ? "" : "es"} IN PARALLEL`,
13235
+ "",
13236
+ `Make ${n} \`Task\` call${n === 1 ? "" : "s"} in a SINGLE message so they run concurrently. Each uses \`subagent_type: general-purpose\`. For each lens, pass the prompt block between its BEGIN/END markers (trimmed of the markers). Each leg returns ONLY its own tagged JSON.`,
13237
+ "",
13238
+ legBlocks.join("\n\n"),
13239
+ "",
13240
+ "### Step 2 \u2014 Synthesis (`depends_on` = ALL lens legs)",
13241
+ "",
13242
+ `Wait until ALL ${n} lens legs above have returned. The synthesis step below declares \`depends_on = ${dependsOn}\` \u2014 do NOT run it before every leg is back. Make ONE more \`Task\` call (\`subagent_type: general-purpose\`) with the block below, pasting every leg's JSON output where marked.`,
13243
+ "",
13244
+ "<<<BEGIN_SYNTHESIS_PROMPT>>>",
13245
+ synthesisPrompt,
13246
+ "<<<END_SYNTHESIS_PROMPT>>>",
13247
+ "",
13248
+ "### Step 3 \u2014 Apply the synthesized verdict",
13249
+ "",
13250
+ `When synthesis returns its JSON, surface the consolidated findings, decide your verdict, then call ${applyNote}`,
13251
+ "",
13252
+ "Do NOT post-process the synthesis JSON \u2014 the `auto_review` apply path expects it exactly as emitted."
13253
+ ].join("\n");
13254
+ }
13027
13255
 
13028
13256
  // src/lib/resolve-llm-response.ts
13029
13257
  import { readFile as readFile3, stat } from "fs/promises";
@@ -13297,11 +13525,6 @@ var planTool = {
13297
13525
  type: "string",
13298
13526
  description: 'Absolute path to a file containing the plan output (mode "apply" only). Use this when the response is too large to pass as a string parameter (some hosts cap inputs around 50KB). The file must be absolute, exist, and be \u2264500KB. Mutually exclusive with llm_response.'
13299
13527
  },
13300
- plan_mode: {
13301
- type: "string",
13302
- enum: ["bootstrap", "full"],
13303
- description: 'The plan mode returned from prepare phase (mode "apply" only).'
13304
- },
13305
13528
  cycle_number: {
13306
13529
  type: "number",
13307
13530
  description: 'The cycle number returned from prepare phase (mode "apply" only).'
@@ -13330,6 +13553,11 @@ var planTool = {
13330
13553
  type: "string",
13331
13554
  description: 'User direction for this cycle \u2014 what to focus on, which phase/tasks to prioritise, or constraints to respect. Overrides the autonomous priority tier system. Example: "Focus on Schema Model phase 9 tasks this cycle".'
13332
13555
  },
13556
+ density: {
13557
+ type: "string",
13558
+ enum: ["light", "standard", "deep"],
13559
+ description: 'Target cycle density \u2014 how many tasks the planner should aim to include. "light" \u2248 2-3 tasks, "standard" \u2248 3-5 (the default when omitted), "deep" \u2248 6-8. A TARGET, not a floor or cap: the planner still honours user direction, pre-assigned tasks, and impact sizing, and plans fewer if fewer genuinely-valuable tasks exist.'
13560
+ },
13333
13561
  force: {
13334
13562
  type: "boolean",
13335
13563
  description: "Bypass planning guards (unreleased cycle block, strategy review hard-block). Only use when explicitly requested by the user."
@@ -13428,6 +13656,7 @@ async function handlePlan(adapter2, config2, args) {
13428
13656
  const focus = typeof args.focus === "string" ? args.focus : void 0;
13429
13657
  const force = args.force === true;
13430
13658
  const handoffsOnly = args.handoffs_only === true;
13659
+ const density = args.density === "light" || args.density === "standard" || args.density === "deep" ? args.density : void 0;
13431
13660
  const tracker = new ProgressTracker(toolMode === "apply" ? "apply_validate" : "prepare_validate").bindStream(adapter2, { stage: "plan" });
13432
13661
  try {
13433
13662
  if (toolMode === "apply") {
@@ -13439,7 +13668,6 @@ async function handlePlan(adapter2, config2, args) {
13439
13668
  return errorResponse(resolved.error);
13440
13669
  }
13441
13670
  const llmResponse = resolved.llmResponse;
13442
- const planMode = args.plan_mode || "full";
13443
13671
  const rawCycleNumber = args.cycle_number != null ? Number(args.cycle_number) : NaN;
13444
13672
  const strategyReviewWarning = args.strategy_review_warning || "";
13445
13673
  const prep = planPrepareCache.peek(callerKey) ?? loadPrepareSpill(adapter2.getProjectId?.(), callerKey);
@@ -13469,6 +13697,7 @@ async function handlePlan(adapter2, config2, args) {
13469
13697
  );
13470
13698
  }
13471
13699
  const cycleNumber = newCycleNumber - 1;
13700
+ const planMode = determineMode(cycleNumber);
13472
13701
  planPrepareCache.clear(callerKey);
13473
13702
  clearPrepareSpill(adapter2.getProjectId?.(), callerKey);
13474
13703
  let utilisation;
@@ -13500,7 +13729,7 @@ async function handlePlan(adapter2, config2, args) {
13500
13729
  } catch {
13501
13730
  }
13502
13731
  const skipHandoffs = args.skip_handoffs === true;
13503
- const result = await preparePlan(adapter2, config2, filters, focus, force, handoffsOnly, skipHandoffs, tracker);
13732
+ const result = await preparePlan(adapter2, config2, filters, focus, force, handoffsOnly, skipHandoffs, tracker, density);
13504
13733
  const prepareState = {
13505
13734
  contextHashes: result.contextHashes,
13506
13735
  userMessage: result.userMessage,
@@ -13550,7 +13779,6 @@ Follow the system prompt and context below to generate a complete cycle plan.
13550
13779
  When done, call \`plan\` again with:
13551
13780
  - \`mode\`: "apply"
13552
13781
  - \`llm_response\`: your complete output (both parts)
13553
- - \`plan_mode\`: "${result.mode}"
13554
13782
  - \`cycle_number\`: ${result.cycleNumber + 1}
13555
13783
  - \`strategy_review_warning\`: "${result.strategyReviewWarning.replace(/"/g, '\\"')}"
13556
13784
 
@@ -13575,7 +13803,9 @@ ${result.userMessage}
13575
13803
  } catch (err) {
13576
13804
  const message = err instanceof Error ? err.message : String(err);
13577
13805
  if (message === "TEMPLATE_BRIEF") {
13578
- return textResponse("Your Product Brief still contains template text. Run `setup` first to generate a real Product Brief for your project.");
13806
+ return textResponse(
13807
+ "Can't plan a full cycle yet \u2014 your Product Brief is still the placeholder template.\n\nPlan mode is derived automatically from your cycle count: your first-ever cycle runs in bootstrap mode (which tolerates a thin brief), and every cycle after that runs in full mode (which needs a real brief to scope work against). This project is past its first cycle, so full mode expected a real brief but found the template text.\n\nFix: run `setup` to generate a real Product Brief for your project, then run `plan` again."
13808
+ );
13579
13809
  }
13580
13810
  const isKnownFriendly = /^(Cannot run plan|Strategy Review gate|No tasks assigned|llm_response is required|cycle_number|Merge conflicts|applyPlan timed out)/i.test(message);
13581
13811
  if (isKnownFriendly) {
@@ -18008,6 +18238,7 @@ async function prepareSetup(adapter2, config2, input) {
18008
18238
  codebaseContext: codebaseSummary
18009
18239
  })
18010
18240
  };
18241
+ const existingDecisions = adapter2.getActiveDecisions ? (await adapter2.getActiveDecisions({ includeRetired: true }).catch(() => [])).filter((a) => !a.superseded).map((a) => `${a.displayId}: ${a.title}`) : [];
18011
18242
  const adSeedPrompt = input.projectType ? {
18012
18243
  system: AD_SEED_SYSTEM,
18013
18244
  user: buildAdSeedPrompt({
@@ -18018,7 +18249,11 @@ async function prepareSetup(adapter2, config2, input) {
18018
18249
  problems: input.problems,
18019
18250
  teamSize: input.teamSize,
18020
18251
  deploymentTarget: input.deploymentTarget,
18021
- constraints: input.constraints
18252
+ constraints: input.constraints,
18253
+ // task-2811: feed the pre-scanned codebase/decision-doc context so ADs are
18254
+ // extracted from the project's real decisions, not invented generically.
18255
+ codebaseContext: codebaseSummary,
18256
+ existingDecisions: existingDecisions.length > 0 ? existingDecisions : void 0
18022
18257
  })
18023
18258
  } : void 0;
18024
18259
  const conventionsPrompt = input.projectType ? {
@@ -18052,6 +18287,21 @@ async function prepareSetup(adapter2, config2, input) {
18052
18287
  projectType: input.projectType
18053
18288
  })
18054
18289
  } : void 0;
18290
+ const existingNorthStar = adapter2.getCurrentNorthStar ? await adapter2.getCurrentNorthStar().catch(() => null) : null;
18291
+ const northStarAlreadyExists = Boolean(existingNorthStar && existingNorthStar.trim());
18292
+ const northStarPrompt = northStarAlreadyExists ? void 0 : {
18293
+ system: NORTH_STAR_SYSTEM,
18294
+ user: buildNorthStarPrompt({
18295
+ projectName: input.projectName,
18296
+ description: input.description,
18297
+ targetUsers: input.targetUsers,
18298
+ problems: input.problems,
18299
+ codebaseContext: codebaseSummary
18300
+ })
18301
+ };
18302
+ const willGenerateBrief = !effectiveBriefAlreadyExists;
18303
+ const preScanInstruction = willGenerateBrief && isExistingProject && !input.codebaseScan ? buildPreScanInstruction({ hosted: !canScanFilesystem, existingProject: true }) : void 0;
18304
+ const newProjectInstruction = willGenerateBrief && !isExistingProject ? buildNewProjectSetupInstruction({ hosted: !canScanFilesystem }) : void 0;
18055
18305
  return {
18056
18306
  createdProject,
18057
18307
  projectName: input.projectName,
@@ -18059,17 +18309,21 @@ async function prepareSetup(adapter2, config2, input) {
18059
18309
  adSeedPrompt,
18060
18310
  conventionsPrompt,
18061
18311
  initialTasksPrompt,
18312
+ northStarPrompt,
18313
+ northStarAlreadyExists,
18062
18314
  codebaseSummary,
18063
18315
  detectedCodebaseType,
18064
18316
  autoDetected: autoDetected && detectedCodebaseType !== "new_project",
18065
18317
  briefAlreadyExists: effectiveBriefAlreadyExists,
18066
18318
  briefWillRegenerate,
18067
18319
  briefRegenReason,
18320
+ preScanInstruction,
18321
+ newProjectInstruction,
18068
18322
  warnings: warnings.length > 0 ? warnings : void 0,
18069
18323
  filesToWrite: prepareCollector.isEmpty() ? void 0 : prepareCollector
18070
18324
  };
18071
18325
  }
18072
- async function applySetup(adapter2, config2, input, briefText, adSeedText, conventionsText, initialTasksText) {
18326
+ async function applySetup(adapter2, config2, input, briefText, adSeedText, conventionsText, initialTasksText, northStarText) {
18073
18327
  const collector = new FileWriteCollector();
18074
18328
  const createdProject = await scaffoldPapiDir(adapter2, config2, input, collector);
18075
18329
  let effectiveBriefText = briefText;
@@ -18096,6 +18350,21 @@ async function applySetup(adapter2, config2, input, briefText, adSeedText, conve
18096
18350
  }
18097
18351
  }
18098
18352
  const { seededAds, warnings } = await applySetupOutputs(adapter2, config2, input, collector, effectiveBriefText, adSeedText, conventionsText);
18353
+ let northStarSet = false;
18354
+ const northStarStatement = northStarText?.trim();
18355
+ if (northStarStatement && adapter2.upsertNorthStar) {
18356
+ try {
18357
+ const existing = adapter2.getCurrentNorthStar ? await adapter2.getCurrentNorthStar().catch(() => null) : null;
18358
+ if (!(existing && existing.trim()) || input.force) {
18359
+ await adapter2.upsertNorthStar(northStarStatement, 0);
18360
+ northStarSet = true;
18361
+ }
18362
+ } catch (err) {
18363
+ warnings.push(
18364
+ `North Star not saved \u2014 ${err instanceof Error ? err.message : String(err)}. Set one later during your first \`plan\`.`
18365
+ );
18366
+ }
18367
+ }
18099
18368
  let createdTasks = 0;
18100
18369
  let tasksSkipped = 0;
18101
18370
  if (initialTasksText?.trim()) {
@@ -18241,6 +18510,7 @@ async function applySetup(adapter2, config2, input, briefText, adSeedText, conve
18241
18510
  seededAds,
18242
18511
  createdTasks,
18243
18512
  tasksSkipped: tasksSkipped > 0 ? tasksSkipped : void 0,
18513
+ northStarSet: northStarSet || void 0,
18244
18514
  briefRegenerated: briefRegenerated || void 0,
18245
18515
  cursorScaffolded,
18246
18516
  gitignoreNote,
@@ -18299,6 +18569,10 @@ var setupTool = {
18299
18569
  type: "string",
18300
18570
  description: 'Your generated conventions markdown to append to CLAUDE.md (mode "apply" only). Optional.'
18301
18571
  },
18572
+ north_star_response: {
18573
+ type: "string",
18574
+ description: `The project's North Star statement \u2014 one or two sentences (mode "apply" only). Provide the statement you extracted from the docs or agreed with the user in response to the prepare-phase North Star prompt. Optional; omit when the project already has one.`
18575
+ },
18302
18576
  project_name: {
18303
18577
  type: "string",
18304
18578
  description: "Name of the project."
@@ -18408,6 +18682,9 @@ function formatSuccessResponse(result, constraints, writesClaudeMd = true) {
18408
18682
  const adNote = result.seededAds > 0 ? `
18409
18683
 
18410
18684
  ${result.seededAds} Active Decision${result.seededAds > 1 ? "s" : ""} seeded based on project type \u2014 review them with \`strategy_review\` after your first cycle.` : "";
18685
+ const northStarNote = result.northStarSet ? `
18686
+
18687
+ \u2B50 North Star set \u2014 your project starts steered (no "define a North Star" warning on \`orient\`).` : "";
18411
18688
  const taskNote = result.createdTasks > 0 || (result.tasksSkipped ?? 0) > 0 ? (() => {
18412
18689
  const created = result.createdTasks > 0 ? `${result.createdTasks} initial backlog task${result.createdTasks > 1 ? "s" : ""} created` : "";
18413
18690
  const skipped = (result.tasksSkipped ?? 0) > 0 ? `${result.tasksSkipped} duplicate${(result.tasksSkipped ?? 0) > 1 ? "s" : ""} skipped` : "";
@@ -18427,7 +18704,7 @@ ${[created, skipped].filter(Boolean).join(", ")}.${idea}`;
18427
18704
  ${result.warnings.map((w) => `- ${w}`).join("\n")}` : "";
18428
18705
  const filesToWriteSection = result.filesToWrite ? formatFilesToWriteSection(result.filesToWrite) : "";
18429
18706
  return textResponse(
18430
- `${prefix}Product Brief generated and saved.${briefRegenNote}${adNote}${taskNote}${constraintsHint}${editorNote}${gitignoreNote}${warningsNote}
18707
+ `${prefix}Product Brief generated and saved.${briefRegenNote}${adNote}${northStarNote}${taskNote}${constraintsHint}${editorNote}${gitignoreNote}${warningsNote}
18431
18708
 
18432
18709
  **Important:** Setup created/modified files (${harnessFiles}, .claude/settings.json, docs/). Commit these changes before running \`build_execute\` \u2014 it requires a clean working directory.
18433
18710
 
@@ -18460,8 +18737,9 @@ PAPI needs the project name. Description and target users are optional \u2014 th
18460
18737
  const adSeedResponse = args.ad_seed_response;
18461
18738
  const conventionsResponse = args.conventions_response;
18462
18739
  const initialTasksResponse = args.initial_tasks_response;
18740
+ const northStarResponse = args.north_star_response;
18463
18741
  tracker.mark("apply_setup_writeback");
18464
- const result = await applySetup(adapter2, config2, input, briefResponse, adSeedResponse, conventionsResponse, initialTasksResponse);
18742
+ const result = await applySetup(adapter2, config2, input, briefResponse, adSeedResponse, conventionsResponse, initialTasksResponse, northStarResponse);
18465
18743
  tracker.mark("apply_format_response");
18466
18744
  return formatSuccessResponse(result, args.constraints, writesClaudeMd);
18467
18745
  }
@@ -18517,7 +18795,13 @@ PAPI needs the project name. Description and target users are optional \u2014 th
18517
18795
  ""
18518
18796
  );
18519
18797
  }
18520
- if (result.briefPrompt && !result.briefAlreadyExists) {
18798
+ if (result.preScanInstruction) {
18799
+ sections.push(result.preScanInstruction, "");
18800
+ }
18801
+ if (result.newProjectInstruction) {
18802
+ sections.push(result.newProjectInstruction, "");
18803
+ }
18804
+ if (!result.preScanInstruction && !result.newProjectInstruction && result.briefPrompt && !result.briefAlreadyExists) {
18521
18805
  sections.push(
18522
18806
  `**\u{1F4C4} Before you write the brief \u2014 does the user already have a PRD, brief, spec, or design doc?**`,
18523
18807
  `A brief generated from the user's real spec produces a far better first plan than a generic scaffold.`,
@@ -18534,6 +18818,7 @@ PAPI needs the project name. Description and target users are optional \u2014 th
18534
18818
  result.briefPrompt ? `- \`brief_response\`: your Product Brief markdown` : "",
18535
18819
  result.adSeedPrompt ? `- \`ad_seed_response\`: your AD seed JSON array` : "",
18536
18820
  result.conventionsPrompt ? `- \`conventions_response\`: your conventions markdown` : "",
18821
+ result.northStarPrompt ? `- \`north_star_response\`: the North Star statement (confirm it with the user first)` : "",
18537
18822
  result.initialTasksPrompt ? `- \`initial_tasks_response\`: your initial tasks JSON array` : "",
18538
18823
  `- Plus all the original setup fields (project_name, description, target_users${isExisting ? ", existing_project: true" : ""})`,
18539
18824
  "",
@@ -18586,6 +18871,25 @@ ${result.conventionsPrompt.system}
18586
18871
  "",
18587
18872
  `<context>
18588
18873
  ${result.conventionsPrompt.user}
18874
+ </context>`
18875
+ );
18876
+ }
18877
+ if (result.northStarPrompt) {
18878
+ sectionNum++;
18879
+ sections.push(
18880
+ "",
18881
+ `---`,
18882
+ "",
18883
+ `### ${sectionNum}. North Star`,
18884
+ "",
18885
+ `This project has no North Star yet \u2014 define one now so it starts steered (otherwise \`orient\` will warn that none is set). Extract it from the docs if one is already stated there; otherwise propose one and **confirm it with the user** before applying. Return it as \`north_star_response\`.`,
18886
+ "",
18887
+ `<system_prompt>
18888
+ ${result.northStarPrompt.system}
18889
+ </system_prompt>`,
18890
+ "",
18891
+ `<context>
18892
+ ${result.northStarPrompt.user}
18589
18893
  </context>`
18590
18894
  );
18591
18895
  }
@@ -20868,6 +21172,7 @@ async function completeBuild(adapter2, config2, taskId, input, options = {}, cli
20868
21172
  completed: capitalizeCompleted(input.completed),
20869
21173
  actualEffort: input.effort,
20870
21174
  estimatedEffort: input.estimatedEffort,
21175
+ model: input.model,
20871
21176
  surprises: input.surprises,
20872
21177
  discoveredIssues: input.discoveredIssues,
20873
21178
  architectureNotes: input.architectureNotes,
@@ -21804,6 +22109,11 @@ var buildExecuteTool = {
21804
22109
  enum: ["XS", "S", "M", "L", "XL"],
21805
22110
  description: "Estimated effort from the BUILD HANDOFF. Required for complete."
21806
22111
  },
22112
+ model: {
22113
+ type: "string",
22114
+ maxLength: 120,
22115
+ description: "The model/agent that executed this build, self-reported, e.g. 'claude-opus-4' / 'gpt-5' \u2014 optional; powers attributed-intelligence analytics. PAPI never selects a model (AD-58) \u2014 report the model YOU are running as."
22116
+ },
21807
22117
  surprises: {
21808
22118
  type: "string",
21809
22119
  maxLength: 800,
@@ -21812,7 +22122,7 @@ var buildExecuteTool = {
21812
22122
  discovered_issues: {
21813
22123
  type: "string",
21814
22124
  maxLength: 800,
21815
- description: `Problems found DURING this build that are OUTSIDE this task's scope. Include severity (P0-P3). Good: "P2: Auth middleware doesn't validate token expiry \u2014 affects all protected routes." Bad: "Had to install a dependency." Only real bugs or gaps that need their own task. Use "None" if none. Required for complete. TIP: When submitting a follow-up idea for a discovered issue, include "learning:<uuid>" in the idea notes to link it to this cycle learning entry \u2014 use the UUID returned in the build completion output.`
22125
+ description: `A REAL bug or gap OUTSIDE this task's scope that needs its own task. Include severity (P0-P3). INCLUSION TEST \u2014 file it only if BOTH are true: (1) the bug would exist even if this task had never run, and (2) it is outside this task's scope. If either is false, it is NOT a discovered_issue. NEVER file a trivial in-file cleanup you noticed while editing (an em-dash tidy, a rename, a lint nit, a stray console.log) \u2014 those are not bugs, just clean them up or leave them. Good: "P2: Auth middleware doesn't validate token expiry \u2014 affects all protected routes." Bad: "Two em dashes remain in the file" / "Had to install a dependency." Use "None" if none. Required for complete. TIP: When submitting a follow-up idea for a discovered issue, include "learning:<uuid>" in the idea notes to link it to this cycle learning entry \u2014 use the UUID returned in the build completion output.`
21816
22126
  },
21817
22127
  architecture_notes: {
21818
22128
  type: "string",
@@ -22098,7 +22408,7 @@ async function handleBuildExecute(adapter2, config2, args, clientName) {
22098
22408
  **PRE-BUILD VERIFICATION:** Before writing any code, read these files and check if the functionality already exists:
22099
22409
  ${verificationFiles.map((f) => `- ${f}`).join("\n")}
22100
22410
  If >80% of the scope is already implemented, call \`build_execute\` with completed="yes" and note "already built" in surprises instead of re-implementing.` : "";
22101
- const chainInstruction = '\n\n---\n\n**IMPORTANT:** After implementing this task, immediately call `build_execute` again with report fields (`completed`, `effort`, `estimated_effort`, `surprises`, `discovered_issues`, `architecture_notes`) to complete the build. Do not wait for user confirmation.\n\n**Build Report Quality Bar:**\n- **surprises**: What was DIFFERENT from expected \u2014 wrong assumptions, scope changes, missing infrastructure. NOT implementation mechanics ("used X library").\n- **discovered_issues**: Bugs/gaps OUTSIDE this task\'s scope, with severity (P0-P3). NOT "had to install a dependency".\n- **architecture_notes**: Patterns/decisions that AFFECT FUTURE WORK. NOT "used React hooks".\n- If nothing meaningful to report for a field, use "None" \u2014 empty signal is better than noise.\n- **dead_ends**: approaches you tried and RULED OUT (with why). Ruled-out paths are first-class intelligence \u2014 send "None" only if nothing you tried failed.';
22411
+ const chainInstruction = '\n\n---\n\n**IMPORTANT:** After implementing this task, immediately call `build_execute` again with report fields (`completed`, `effort`, `estimated_effort`, `surprises`, `discovered_issues`, `architecture_notes`) to complete the build. Do not wait for user confirmation.\n\n**Build Report Quality Bar:**\n- **surprises**: What was DIFFERENT from expected \u2014 wrong assumptions, scope changes, missing infrastructure. NOT implementation mechanics ("used X library").\n- **discovered_issues**: REAL bugs/gaps OUTSIDE this task\'s scope, with severity (P0-P3). Inclusion test: file it only if the bug would exist even if this task had never run AND it is outside this task\'s scope. NEVER a trivial in-file cleanup you noticed while editing (em-dash tidy, rename, lint nit) \u2014 those are not discovered_issues. NOT "had to install a dependency".\n- **architecture_notes**: Patterns/decisions that AFFECT FUTURE WORK. NOT "used React hooks".\n- If nothing meaningful to report for a field, use "None" \u2014 empty signal is better than noise.\n- **dead_ends**: approaches you tried and RULED OUT (with why). Ruled-out paths are first-class intelligence \u2014 send "None" only if nothing you tried failed.';
22102
22412
  const buildDisciplineNote = "\n\n---\n\n**BUILD DISCIPLINE:**\n- **Read before you claim.** Before asserting how something works \u2014 or that it is already done \u2014 read the actual code/state. Don't rely on memory or earlier context.\n- **Sweep existing material first.** Check the docs (`doc_search` or your docs index) and prior tasks, not just whether a file exists \u2014 the work may already be covered.\n- **Ranged reads.** For large or unfamiliar files, read the relevant range rather than the whole file.";
22103
22413
  let adSection = "";
22104
22414
  try {
@@ -22174,6 +22484,7 @@ async function handleExecuteComplete(adapter2, config2, taskId, args, light = fa
22174
22484
  const completed = args.completed;
22175
22485
  const effort = args.effort;
22176
22486
  const estimatedEffort = args.estimated_effort;
22487
+ const model = typeof args.model === "string" && args.model.trim() !== "" ? args.model.trim() : void 0;
22177
22488
  const surprises = args.surprises;
22178
22489
  const discoveredIssues = args.discovered_issues;
22179
22490
  const architectureNotes = args.architecture_notes;
@@ -22229,6 +22540,7 @@ async function handleExecuteComplete(adapter2, config2, taskId, args, light = fa
22229
22540
  completed,
22230
22541
  effort: parsedEffort,
22231
22542
  estimatedEffort: parsedEstimatedEffort,
22543
+ model,
22232
22544
  surprises,
22233
22545
  discoveredIssues,
22234
22546
  architectureNotes,
@@ -23877,7 +24189,7 @@ var REVIEW_RUBRIC = [
23877
24189
  "- Quality: tests present and meaningful, no obvious debt or dead code.",
23878
24190
  "Be specific and cite file/line where you can. Recommend fail only for blocking issues."
23879
24191
  ].join("\n");
23880
- async function buildReviewDispatch(adapter2, config2, taskId) {
24192
+ async function assembleReviewContext(adapter2, config2, taskId) {
23881
24193
  const task = await adapter2.getTask(taskId);
23882
24194
  if (!task) {
23883
24195
  return { ok: false, error: `Task ${taskId} not found \u2014 cannot assemble review context.` };
@@ -23910,13 +24222,13 @@ ${report}
23910
24222
 
23911
24223
  ${diffBlock}`;
23912
24224
  const contextBytes = Buffer.byteLength(userMessage, "utf-8");
23913
- const prompt2 = buildSubagentDispatchPrompt({
23914
- tool: "review_submit",
23915
- taskId,
23916
- systemPrompt: REVIEW_RUBRIC,
23917
- userMessage,
23918
- contextBytes
23919
- });
24225
+ return { ok: true, userMessage, contextBytes };
24226
+ }
24227
+ async function buildReviewDispatch(adapter2, config2, taskId, preset) {
24228
+ const ctx = await assembleReviewContext(adapter2, config2, taskId);
24229
+ if (!ctx.ok) return ctx;
24230
+ const { userMessage, contextBytes } = ctx;
24231
+ const prompt2 = preset ? buildFanoutReviewDispatchPrompt({ taskId, preset, systemPrompt: REVIEW_RUBRIC, userMessage, contextBytes }) : buildSubagentDispatchPrompt({ tool: "review_submit", taskId, systemPrompt: REVIEW_RUBRIC, userMessage, contextBytes });
23920
24232
  return { ok: true, prompt: prompt2, contextBytes };
23921
24233
  }
23922
24234
  var reviewListTool = {
@@ -23968,6 +24280,11 @@ var reviewSubmitTool = {
23968
24280
  enum: ["inline", "subagent"],
23969
24281
  description: `task-1864: set "subagent" (build-acceptance only) to offload code review to a fresh sub-agent. Returns a Task() invocation prompt that feeds the build report + branch diff to the sub-agent, which returns structured auto_review findings. Verdict is NOT required on this call \u2014 you call review_submit again with the human verdict + the sub-agent's auto_review. Default "inline" (record the verdict directly).`
23970
24282
  },
24283
+ review_preset: {
24284
+ type: "string",
24285
+ enum: ["gate", "full", "security-focused"],
24286
+ description: 'task-2824 (opt-in): upgrade the sub-agent dispatch to a MULTI-LENS fan-out. Requires dispatch:"subagent" (build-acceptance only). Emits N specialist lens legs (correctness/security/wiring/test-quality/scope-drift) that review the same diff in parallel, plus a synthesis leg that dedupes their findings into ONE prioritized auto_review verdict. "gate" = correctness+wiring, "full" = all 5, "security-focused" = security+correctness+wiring. Omit for the existing single-lens review (unchanged default).'
24287
+ },
23971
24288
  reviewer_confirmed: {
23972
24289
  type: "boolean",
23973
24290
  description: "Set to true to confirm you have reviewed the build (read the build report or the pending list via review_list) before submitting an accept verdict. Required to accept a build-acceptance review unless review_list was called in the same session within the last 15 minutes. Defense-in-depth against SUP-2026-010 (Codex prematurely accepted a task because review_list was missing from its tool surface)."
@@ -24182,11 +24499,24 @@ async function handleReviewSubmit(adapter2, config2, args) {
24182
24499
  caps = {};
24183
24500
  }
24184
24501
  const explicitDispatch = args.dispatch === "subagent";
24502
+ const rawPreset = args.review_preset;
24503
+ const reviewPreset = typeof rawPreset === "string" && rawPreset in REVIEW_PRESETS ? rawPreset : void 0;
24504
+ if (rawPreset !== void 0 && !explicitDispatch) {
24505
+ return errorResponse('review_preset requires dispatch:"subagent" (multi-lens fan-out is only available on an explicit sub-agent dispatch).');
24506
+ }
24507
+ if (typeof rawPreset === "string" && rawPreset.length > 0 && !reviewPreset) {
24508
+ return errorResponse(`review_preset "${rawPreset}" is not valid. Use "gate", "full", or "security-focused".`);
24509
+ }
24185
24510
  const autoDispatchOptIn = args.dispatch !== "inline" && process.env.PAPI_AUTO_DISPATCH !== "false" && isCapabilityEnabled(caps, "prReviewer");
24186
24511
  const autoDispatchEligible = !verdict && autoDispatchOptIn;
24187
24512
  const capabilityAutoReviewEligible = verdict === "accept" && !autoReview && autoDispatchOptIn;
24188
24513
  if ((explicitDispatch || autoDispatchEligible || capabilityAutoReviewEligible) && stage === "build-acceptance" && taskId) {
24189
- const dispatch = await buildReviewDispatch(adapter2, config2, taskId);
24514
+ const dispatch = await buildReviewDispatch(
24515
+ adapter2,
24516
+ config2,
24517
+ taskId,
24518
+ explicitDispatch ? reviewPreset : void 0
24519
+ );
24190
24520
  if (!dispatch.ok) {
24191
24521
  if (explicitDispatch) return errorResponse(dispatch.error);
24192
24522
  } else if (explicitDispatch || capabilityAutoReviewEligible || dispatch.contextBytes > REVIEW_DISPATCH_THRESHOLD) {
package/dist/prompts.js CHANGED
@@ -236,7 +236,7 @@ var PLAN_FRAGMENT_DESIGN_BRIEF = `
236
236
  **Design brief task detection:** When a task's task type is "design-brief", generate a DESIGN BRIEF handoff. Replace the standard SCOPE (DO THIS) section with these type-specific sections:
237
237
  - AUDIENCE: Who this design is for \u2014 persona and context of use (e.g. "non-technical Owner, first dashboard visit")
238
238
  - BRAND CONSTRAINTS: Palette, typography, tone \u2014 pull from \`.impeccable.md\` (dev patterns, anti-patterns, component rules) AND \`docs/branding/brand-book.html\` (brand identity, positioning, voice canon) if present. If neither exists, state "No brand doc \u2014 Owner should define constraints before starting."
239
- - DELIVERABLE FORMAT: What the output looks like \u2014 Claude Design handoff package / annotated mockup / style spec. Be specific so the person doing the work knows what "done" means.
239
+ - DELIVERABLE FORMAT: What the output looks like \u2014 design handoff package / annotated mockup / style spec. Be specific so the person doing the work knows what "done" means.
240
240
  - REVIEW POINTS: What the Owner must approve before the design is considered done (e.g. layout, copy, colour, imagery).
241
241
  Keep SCOPE BOUNDARY, ACCEPTANCE CRITERIA, SECURITY CONSIDERATIONS, and PRE-BUILD VERIFICATION sections as normal.
242
242
  Add to ACCEPTANCE CRITERIA: "[ ] Deliverable format confirmed with Owner before starting" and "[ ] Design output is self-contained \u2014 includes enough context for a developer to implement without further clarification."`;
@@ -435,6 +435,21 @@ function buildPlanFullInstructionsConditional(flags, ctx) {
435
435
  if (!flags || !ctx) return PLAN_FULL_INSTRUCTIONS;
436
436
  return composeFullModeInstructions(flags, ctx);
437
437
  }
438
+ var CYCLE_DENSITY_TARGETS = {
439
+ light: { label: "Light", range: "2-3" },
440
+ standard: { label: "Standard", range: "3-5" },
441
+ deep: { label: "Deep", range: "6-8" }
442
+ };
443
+ function buildCycleDensityDirective(density) {
444
+ if (!density) return "";
445
+ const target = CYCLE_DENSITY_TARGETS[density];
446
+ return [
447
+ `## CYCLE DENSITY: ${target.label}`,
448
+ "",
449
+ `The user set this cycle's density to **${density}** \u2014 aim for roughly **${target.range} tasks** this cycle.`,
450
+ `This is a TARGET to size toward, not a hard floor or cap: still honour explicit user direction, any pre-assigned tasks, and impact-based sizing. If fewer genuinely-valuable tasks exist, plan fewer \u2014 do NOT pad the cycle to hit the number.`
451
+ ].join("\n");
452
+ }
438
453
  function buildPlanUserMessage(ctx) {
439
454
  const modeLabel = ctx.mode.toUpperCase();
440
455
  const parts = [
@@ -452,6 +467,10 @@ function buildPlanUserMessage(ctx) {
452
467
  ""
453
468
  );
454
469
  }
470
+ const densityDirective = buildCycleDensityDirective(ctx.density);
471
+ if (densityDirective) {
472
+ parts.push(densityDirective, "");
473
+ }
455
474
  if (ctx.mode === "bootstrap") {
456
475
  parts.push(PLAN_BOOTSTRAP_INSTRUCTIONS);
457
476
  } else {
@@ -1327,15 +1346,49 @@ phases:
1327
1346
 
1328
1347
  *No decisions locked yet. These are added as planning cycles confirm strategic choices.*`;
1329
1348
  }
1349
+ function buildPreScanInstruction(opts) {
1350
+ const docRouting = opts.hosted ? "paste the relevant content (decision records, roadmap, spec excerpts) directly into `description` \u2014 over a remote connector PAPI cannot read local file paths." : "pass local doc file paths as `sources` (comma-separated), or paste content into `description`.";
1351
+ return [
1352
+ `**\u{1F50E} Before you generate anything \u2014 scan the project and gather real context.**`,
1353
+ `A brief and Active Decisions built from the real code and docs are far sharper than ones guessed from a project name. You (the agent running this) have local access that PAPI does not \u2014 do the gathering, then pass it back.`,
1354
+ ``,
1355
+ `1. **Confirm the environment.** Check that you are running from the root of this project's dev environment \u2014 the folder that holds its source, git history, and docs. If you are not there, or you are connected over a remote connector with no access to the user's files, say so and ask the user where the project lives before continuing.`,
1356
+ `2. **Scan the code.** List the top-level directories and files, read the package/build manifest and the README (first ~3000 chars), and collect recent commit subjects. Pass this back as \`codebase_scan\` on the next \`setup\` call.`,
1357
+ `3. **Scan the docs.** Look through \`docs/\`, design notes, ADRs/decision records, and any roadmap or planning files \u2014 not just code.`,
1358
+ `4. **Check for sibling / separate repos.** Many projects span more than one repo (a separate frontend, backend, infra, or mobile repo, or other packages in a monorepo). Ask the user whether any related repos or directories exist, and scan those too.`,
1359
+ `5. **Ask the user to point you at extra context.** Prompt them: "Is there anything else I should read before setting this up \u2014 a PRD or spec, decision records, a roadmap, or links to related repos or docs?" Fold whatever they share into the scan.`,
1360
+ `6. **Re-run \`setup\` with what you gathered:** pass the code scan as \`codebase_scan\`; for docs, decision records, and roadmap, ${docRouting} Then answer the prompts below.`,
1361
+ ``,
1362
+ `If there is genuinely no code and no docs yet, skip this and generate from what the user tells you \u2014 the zero-context path is fully supported.`
1363
+ ].join("\n");
1364
+ }
1365
+ var PAPI_DOCS_QUICKSTART_URL = "https://getpapi.ai/docs/guide/quick-start";
1366
+ var PAPI_PUBLIC_REPO_URL = "https://github.com/getpapi/papi";
1367
+ function buildNewProjectSetupInstruction(opts) {
1368
+ const visionRouting = opts.hosted ? "paste anything they share (notes, a PRD, links) into `description` \u2014 over a remote connector PAPI cannot read local file paths." : "fold anything they share (notes, a PRD, a sketch, links) into `description`, or pass local doc paths as `sources`.";
1369
+ return [
1370
+ `**\u{1F331} New project \u2014 start from the vision.**`,
1371
+ `There's no existing code to mine, so setup builds from what the user wants to create. Do two things before answering the prompts below.`,
1372
+ ``,
1373
+ `1. **Get oriented on PAPI.** So you can guide the user well \u2014 whatever assistant you are \u2014 skim the quick-start docs at ${PAPI_DOCS_QUICKSTART_URL} and the public repo at ${PAPI_PUBLIC_REPO_URL}. They explain the plan \u2192 build \u2192 review cycle the user will run after setup.`,
1374
+ `2. **Draw out the vision with the user.** Ask what they're building and for whom, the core problem it solves, and what success looks like \u2014 enough to write a real brief, a North Star, and a starter backlog rather than a generic scaffold. If they already have a PRD, sketch, notes, or links, ${visionRouting}`,
1375
+ ``,
1376
+ `Then answer the prompts below to generate the brief, decisions, North Star, and a vision-first starter backlog.`
1377
+ ].join("\n");
1378
+ }
1330
1379
  var AD_SEED_SYSTEM = `You are a technical architect seeding initial Active Decisions for a new software project managed by PAPI.
1331
1380
 
1332
1381
  Active Decisions (ADs) are documented architectural choices with confidence levels. They guide the planner and builder agents \u2014 without ADs, planning output is generic and unhelpful.
1333
1382
 
1334
1383
  IMPORTANT: You are running as a non-interactive API call. Do NOT ask questions. Produce decisions directly.
1335
1384
 
1385
+ ## SOURCE OF TRUTH \u2014 extract real decisions first
1386
+
1387
+ If the context includes decision records, ADRs, a roadmap, a README, or codebase analysis, your PRIMARY job is to EXTRACT the real, already-made decisions from that material \u2014 not to invent generic ones. Read the provided context and capture each load-bearing choice the project has actually made (its stack, data model, architecture, positioning, deployment posture). Only fall back to informed defaults for a project of this type when the context contains no evidenced decisions.
1388
+
1336
1389
  ## OUTPUT FORMAT
1337
1390
 
1338
- Return a JSON array of 3-5 Active Decisions. Each AD must have:
1391
+ Return a JSON array of Active Decisions \u2014 ONE per real decision you find. Each AD must have:
1339
1392
  - "id": "AD-1", "AD-2", etc.
1340
1393
  - "body": Full markdown block including ### heading, confidence tag, and body text
1341
1394
 
@@ -1349,17 +1402,20 @@ The body format for each AD:
1349
1402
 
1350
1403
  ## GUIDELINES
1351
1404
 
1352
- - All seeded ADs should have Confidence: MEDIUM (they are informed defaults, not confirmed choices)
1353
- - Focus on decisions that genuinely differ by project type \u2014 avoid generic truisms
1354
- - Each AD should be actionable and falsifiable (something the team could decide differently)
1405
+ - **Count follows the evidence \u2014 there is NO fixed target.** Seed as many ADs as there are real, distinct decisions in the material. Do NOT pad to a number, and do NOT fabricate decisions to hit a count. When the context has no evidenced decisions, return a small set (2-4) of informed defaults for this project type.
1406
+ - **Confidence reflects evidence:** a decision explicitly documented as settled in the source may be HIGH; an informed default you inferred stays MEDIUM.
1407
+ - Never mint an AD that is not a genuine stance-with-alternatives \u2014 a preference, a fact, or a config value is NOT an AD.
1408
+ - Focus on decisions that genuinely differ by project \u2014 avoid generic truisms.
1409
+ - Each AD should be actionable and falsifiable (something the team could decide differently).
1355
1410
  - Cover different concerns: architecture, data, deployment, testing strategy, API design, etc.
1356
- - Keep each AD body to 4-6 lines \u2014 concise and scannable
1411
+ - Keep each AD body to 4-6 lines \u2014 concise and scannable.
1412
+ - **Do NOT duplicate** any decision already listed as an existing Active Decision in the context \u2014 skip it entirely.
1357
1413
  - **Quality bar:** ADs are for product and architecture choices that constrain future work \u2014 technology selections, data model designs, UX principles, strategic positioning. They are NOT for process preferences, configuration choices, or temporary workarounds.
1358
1414
 
1359
1415
  Return ONLY valid JSON \u2014 no preamble, no code fences, no explanation.`;
1360
1416
  function buildAdSeedPrompt(ctx) {
1361
1417
  const parts = [
1362
- `Generate 3-5 Active Decisions for this project.`,
1418
+ ctx.codebaseContext ? `Extract the real Active Decisions for this project from the context below. Seed one AD per genuine decision \u2014 do not cap the count, and do not fabricate.` : `Seed the informed default Active Decisions for this project (no decision docs were supplied \u2014 infer sensible MEDIUM-confidence defaults for this project type).`,
1363
1419
  "",
1364
1420
  `**Project:** ${ctx.projectName}`,
1365
1421
  `**Type:** ${ctx.projectType}`,
@@ -1372,12 +1428,57 @@ function buildAdSeedPrompt(ctx) {
1372
1428
  if (ctx.constraints) {
1373
1429
  parts.push(`**Constraints:** ${ctx.constraints}`);
1374
1430
  }
1431
+ if (ctx.codebaseContext) {
1432
+ parts.push(
1433
+ "",
1434
+ "## Project context \u2014 extract the real decisions from here",
1435
+ ctx.codebaseContext
1436
+ );
1437
+ }
1438
+ if (ctx.existingDecisions && ctx.existingDecisions.length > 0) {
1439
+ parts.push(
1440
+ "",
1441
+ "## Existing Active Decisions \u2014 do NOT duplicate these",
1442
+ ...ctx.existingDecisions.map((d) => `- ${d}`)
1443
+ );
1444
+ }
1375
1445
  parts.push(
1376
1446
  "",
1377
1447
  'Return a JSON array of AD objects with "id" and "body" fields. No other text.'
1378
1448
  );
1379
1449
  return parts.join("\n");
1380
1450
  }
1451
+ var NORTH_STAR_SYSTEM = `You are helping a builder define the North Star for a software project set up with PAPI.
1452
+
1453
+ A North Star is the ONE outcome that best captures whether the project is succeeding \u2014 a single, measurable, user-centred statement the team can steer by. It is not a feature list and not a vision paragraph.
1454
+
1455
+ IMPORTANT: You are running as a non-interactive API call. Do NOT ask questions in your output.
1456
+
1457
+ ## HOW TO PRODUCE IT
1458
+ - If the provided context (brief, docs, decision records) ALREADY states a North Star, goal metric, or primary success measure, EXTRACT and restate it \u2014 do not invent a competing one.
1459
+ - Otherwise, PROPOSE the most fitting North Star from the project's purpose and users. (The calling agent will confirm it with the user before it is saved.)
1460
+
1461
+ ## OUTPUT FORMAT
1462
+ Return ONLY the North Star statement \u2014 one or two sentences, concrete and measurable where possible. No heading, no preamble, no quotes, no code fences.`;
1463
+ function buildNorthStarPrompt(inputs) {
1464
+ const parts = [
1465
+ `Define the North Star for this project.`,
1466
+ "",
1467
+ `**Project:** ${inputs.projectName}`,
1468
+ `**Description:** ${inputs.description?.trim() || "(not provided \u2014 infer from context below)"}`,
1469
+ `**Target users:** ${inputs.targetUsers?.trim() || "(not provided \u2014 infer from context below)"}`,
1470
+ `**Problems solved:** ${inputs.problems}`
1471
+ ];
1472
+ if (inputs.codebaseContext) {
1473
+ parts.push(
1474
+ "",
1475
+ "## Project context \u2014 extract an existing North Star from here if one is stated",
1476
+ inputs.codebaseContext
1477
+ );
1478
+ }
1479
+ parts.push("", "Return only the North Star statement.");
1480
+ return parts.join("\n");
1481
+ }
1381
1482
  var CONVENTIONS_SYSTEM = `You are a senior software engineer generating CLAUDE.md coding conventions for a new project.
1382
1483
 
1383
1484
  IMPORTANT: You are running as a non-interactive API call. Do NOT ask questions. Produce conventions directly.
@@ -1497,9 +1598,13 @@ export {
1497
1598
  AD_REJECTION_RULES,
1498
1599
  AD_SEED_SYSTEM,
1499
1600
  CONVENTIONS_SYSTEM,
1601
+ CYCLE_DENSITY_TARGETS,
1500
1602
  HANDOFF_REGEN_SYSTEM,
1501
1603
  INITIAL_TASKS_SYSTEM,
1604
+ NORTH_STAR_SYSTEM,
1502
1605
  OUTPUT_QUALITY_RUBRIC,
1606
+ PAPI_DOCS_QUICKSTART_URL,
1607
+ PAPI_PUBLIC_REPO_URL,
1503
1608
  PLAN_BOOTSTRAP_INSTRUCTIONS,
1504
1609
  PLAN_FULL_INSTRUCTIONS,
1505
1610
  PLAN_SYSTEM,
@@ -1509,11 +1614,15 @@ export {
1509
1614
  VISION_TASKS_SYSTEM,
1510
1615
  buildAdSeedPrompt,
1511
1616
  buildConventionsPrompt,
1617
+ buildCycleDensityDirective,
1512
1618
  buildHandoffRegenMessage,
1513
1619
  buildHandoffsOnlyUserMessage,
1514
1620
  buildInitialTasksPrompt,
1621
+ buildNewProjectSetupInstruction,
1622
+ buildNorthStarPrompt,
1515
1623
  buildPlanFullInstructionsConditional,
1516
1624
  buildPlanUserMessage,
1625
+ buildPreScanInstruction,
1517
1626
  buildProductBriefPrompt,
1518
1627
  buildReviewSystemPrompt,
1519
1628
  buildReviewUserMessage,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@papi-ai/server",
3
- "version": "0.7.59",
3
+ "version": "0.7.61",
4
4
  "description": "PAPI MCP server — AI-powered sprint planning, build execution, and strategy review for software projects",
5
5
  "license": "Elastic-2.0",
6
6
  "mcpName": "io.github.getpapi/papi",