@papi-ai/server 0.7.53 → 0.7.54

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
@@ -1297,21 +1297,27 @@ var init_proxy_adapter = __esm({
1297
1297
  // getToolCallCount + updatePhaseStatus are ABSENT: they now have edge handlers and
1298
1298
  // are served through the forwarder (getToolCallCount = SUP-2026-026 handler #1).
1299
1299
  "createOwnerAction",
1300
- "findPendingDocActionsForTask",
1301
1300
  "getContributorRole",
1302
- "getDecisionScorePatterns",
1303
- "getModuleEstimationStats",
1304
- "correctLatestBuildReportEffort",
1305
1301
  "recordContributorReleasePr",
1306
1302
  "setContributorReleasePrStatus",
1307
1303
  "listContributorReleasePrs",
1308
- "resolveLearningsForDoneTasks",
1309
- "markCycleLearningResolved",
1310
- "updateStageExitCriteria",
1311
- "updateDocAction",
1312
1304
  "claimReview",
1313
1305
  "getSiblingAds",
1314
1306
  "getSiblingRepoTasks"
1307
+ // task-2394 (C329) — Batch A wired: findPendingDocActionsForTask,
1308
+ // getModuleEstimationStats and getDecisionScorePatterns now have edge case handlers
1309
+ // (each backed by a SECURITY DEFINER RPC, migration 20260714140000) plus
1310
+ // ALLOWED_METHODS entries, so they forward. The two planner-context reads returned
1311
+ // EMPTY for every hosted user before this — the planner ran on worse context than
1312
+ // the owner's on the only install path external users have (AD-72).
1313
+ // task-2412 (C329) — listOwnerActionsForBlockerScan + linkOwnerActionToTask wired.
1314
+ // First USER-scoped ([C]) methods to forward: the edge binds both to the bearer's
1315
+ // user_id and discards the client-supplied one, so the typed-blocker scan (task-2343)
1316
+ // now works for hosted users without exposing one member's owner actions to another.
1317
+ // task-2393 (C329) — Batch B wired: markCycleLearningResolved (the P1 — hosted
1318
+ // discovered_issue_resolve hard-errored), correctLatestBuildReportEffort,
1319
+ // updateStageExitCriteria, updateDocAction, resolveLearningsForDoneTasks all have
1320
+ // edge case handlers + ALLOWED_METHODS/WRITE_METHODS entries now, so they forward.
1315
1321
  // task-2489 (C320): recordProgressStep is now wired to the edge data-proxy
1316
1322
  // (case handler + ALLOWED_METHODS/WRITE_METHODS entries), so it forwards for
1317
1323
  // hosted callers and persists a project-scoped cycle_progress_steps row. Removed
@@ -4227,9 +4233,9 @@ __export(doctor_exports, {
4227
4233
  __testing: () => __testing,
4228
4234
  runDoctor: () => runDoctor
4229
4235
  });
4230
- import { existsSync as existsSync10, readFileSync as readFileSync11 } from "fs";
4236
+ import { existsSync as existsSync11, readFileSync as readFileSync12 } from "fs";
4231
4237
  import { homedir as homedir4 } from "os";
4232
- import { join as join19 } from "path";
4238
+ import { join as join20 } from "path";
4233
4239
  function redact(name, value) {
4234
4240
  if (!value) return "(empty)";
4235
4241
  if (SECRET_VARS.has(name)) {
@@ -4240,14 +4246,14 @@ function redact(name, value) {
4240
4246
  }
4241
4247
  function findMcpJson() {
4242
4248
  const candidates = [
4243
- join19(process.cwd(), ".mcp.json"),
4244
- join19(homedir4(), ".claude", ".mcp.json"),
4245
- join19(homedir4(), ".mcp.json")
4249
+ join20(process.cwd(), ".mcp.json"),
4250
+ join20(homedir4(), ".claude", ".mcp.json"),
4251
+ join20(homedir4(), ".mcp.json")
4246
4252
  ];
4247
4253
  for (const path7 of candidates) {
4248
- if (!existsSync10(path7)) continue;
4254
+ if (!existsSync11(path7)) continue;
4249
4255
  try {
4250
- const raw = readFileSync11(path7, "utf-8");
4256
+ const raw = readFileSync12(path7, "utf-8");
4251
4257
  const parsed = JSON.parse(raw);
4252
4258
  const papiEntry = parsed.papi ?? parsed.mcpServers?.papi;
4253
4259
  if (!papiEntry) continue;
@@ -4521,17 +4527,17 @@ __export(reset_exports, {
4521
4527
  removePapiEntry: () => removePapiEntry,
4522
4528
  runReset: () => runReset
4523
4529
  });
4524
- import { existsSync as existsSync11, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "fs";
4530
+ import { existsSync as existsSync12, readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
4525
4531
  import { homedir as homedir5 } from "os";
4526
- import { join as join20 } from "path";
4532
+ import { join as join21 } from "path";
4527
4533
  import { createInterface } from "readline/promises";
4528
4534
  function findResetTarget() {
4529
4535
  for (const path7 of CANDIDATE_PATHS()) {
4530
- if (!existsSync11(path7)) continue;
4536
+ if (!existsSync12(path7)) continue;
4531
4537
  let raw;
4532
4538
  let parsed;
4533
4539
  try {
4534
- raw = readFileSync12(path7, "utf-8");
4540
+ raw = readFileSync13(path7, "utf-8");
4535
4541
  parsed = JSON.parse(raw);
4536
4542
  } catch {
4537
4543
  continue;
@@ -4602,7 +4608,7 @@ async function runReset(args = []) {
4602
4608
  }
4603
4609
  }
4604
4610
  try {
4605
- writeFileSync5(target.path, removePapiEntry(target), "utf-8");
4611
+ writeFileSync6(target.path, removePapiEntry(target), "utf-8");
4606
4612
  process.stdout.write(`
4607
4613
  \u2713 Removed papi entry from ${target.path}
4608
4614
  `);
@@ -4619,9 +4625,9 @@ var init_reset = __esm({
4619
4625
  "src/cli/reset.ts"() {
4620
4626
  "use strict";
4621
4627
  CANDIDATE_PATHS = () => [
4622
- join20(process.cwd(), ".mcp.json"),
4623
- join20(homedir5(), ".claude", ".mcp.json"),
4624
- join20(homedir5(), ".mcp.json")
4628
+ join21(process.cwd(), ".mcp.json"),
4629
+ join21(homedir5(), ".claude", ".mcp.json"),
4630
+ join21(homedir5(), ".mcp.json")
4625
4631
  ];
4626
4632
  }
4627
4633
  });
@@ -4632,9 +4638,9 @@ __export(audit_exports, {
4632
4638
  __testing: () => __testing2,
4633
4639
  runAudit: () => runAudit
4634
4640
  });
4635
- import { existsSync as existsSync12, readFileSync as readFileSync13, readdirSync as readdirSync7 } from "fs";
4641
+ import { existsSync as existsSync13, readFileSync as readFileSync14, readdirSync as readdirSync7 } from "fs";
4636
4642
  import { homedir as homedir6 } from "os";
4637
- import { join as join21 } from "path";
4643
+ import { join as join22 } from "path";
4638
4644
  function safeListDirs(dir) {
4639
4645
  try {
4640
4646
  return readdirSync7(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort((a, b2) => a.localeCompare(b2));
@@ -4650,10 +4656,10 @@ function safeListFiles(dir, ext) {
4650
4656
  }
4651
4657
  }
4652
4658
  function readMcp(projectPath) {
4653
- const path7 = join21(projectPath, ".mcp.json");
4654
- if (!existsSync12(path7)) return { servers: [] };
4659
+ const path7 = join22(projectPath, ".mcp.json");
4660
+ if (!existsSync13(path7)) return { servers: [] };
4655
4661
  try {
4656
- const parsed = JSON.parse(readFileSync13(path7, "utf-8"));
4662
+ const parsed = JSON.parse(readFileSync14(path7, "utf-8"));
4657
4663
  const mcpServers = parsed.mcpServers ?? {};
4658
4664
  const servers = Object.keys(mcpServers);
4659
4665
  if (parsed.papi && !servers.includes("papi")) servers.push("papi");
@@ -4685,18 +4691,18 @@ function auditProjectSync(projectPath, name) {
4685
4691
  path: projectPath,
4686
4692
  papiProjectId,
4687
4693
  mcpServers: servers,
4688
- skills: safeListDirs(join21(projectPath, ".claude", "skills")),
4689
- agentSkills: safeListDirs(join21(projectPath, ".agents", "skills")),
4690
- agents: safeListFiles(join21(projectPath, ".claude", "agents"), ".md"),
4691
- hooks: safeListFiles(join21(projectPath, ".claude", "hooks"), ".sh")
4694
+ skills: safeListDirs(join22(projectPath, ".claude", "skills")),
4695
+ agentSkills: safeListDirs(join22(projectPath, ".agents", "skills")),
4696
+ agents: safeListFiles(join22(projectPath, ".claude", "agents"), ".md"),
4697
+ hooks: safeListFiles(join22(projectPath, ".claude", "hooks"), ".sh")
4692
4698
  };
4693
4699
  }
4694
4700
  function discoverProjects() {
4695
4701
  const out = [];
4696
4702
  for (const root of PROJECT_ROOTS) {
4697
4703
  for (const name of safeListDirs(root)) {
4698
- const path7 = join21(root, name);
4699
- if (existsSync12(join21(path7, ".mcp.json")) || existsSync12(join21(path7, ".claude"))) {
4704
+ const path7 = join22(root, name);
4705
+ if (existsSync13(join22(path7, ".mcp.json")) || existsSync13(join22(path7, ".claude"))) {
4700
4706
  out.push({ name, path: path7 });
4701
4707
  }
4702
4708
  }
@@ -4707,9 +4713,9 @@ function readGlobalSkills() {
4707
4713
  return safeListDirs(GLOBAL_SKILLS_DIR);
4708
4714
  }
4709
4715
  function readGlobalMcpServers() {
4710
- if (!existsSync12(GLOBAL_CLAUDE_JSON)) return [];
4716
+ if (!existsSync13(GLOBAL_CLAUDE_JSON)) return [];
4711
4717
  try {
4712
- const parsed = JSON.parse(readFileSync13(GLOBAL_CLAUDE_JSON, "utf-8"));
4718
+ const parsed = JSON.parse(readFileSync14(GLOBAL_CLAUDE_JSON, "utf-8"));
4713
4719
  const servers = parsed.mcpServers ?? {};
4714
4720
  return Object.keys(servers).sort((a, b2) => a.localeCompare(b2));
4715
4721
  } catch {
@@ -4861,9 +4867,9 @@ var PROJECT_ROOTS, GLOBAL_SKILLS_DIR, GLOBAL_CLAUDE_JSON, IDLE_WINDOW_DAYS, GLOB
4861
4867
  var init_audit = __esm({
4862
4868
  "src/cli/audit.ts"() {
4863
4869
  "use strict";
4864
- PROJECT_ROOTS = [join21(homedir6(), "Ai-App-Projects"), join21(homedir6(), "android-projects")];
4865
- GLOBAL_SKILLS_DIR = join21(homedir6(), ".claude", "skills");
4866
- GLOBAL_CLAUDE_JSON = join21(homedir6(), ".claude.json");
4870
+ PROJECT_ROOTS = [join22(homedir6(), "Ai-App-Projects"), join22(homedir6(), "android-projects")];
4871
+ GLOBAL_SKILLS_DIR = join22(homedir6(), ".claude", "skills");
4872
+ GLOBAL_CLAUDE_JSON = join22(homedir6(), ".claude.json");
4867
4873
  IDLE_WINDOW_DAYS = 30;
4868
4874
  GLOBALIZE_THRESHOLD = 3;
4869
4875
  __testing2 = { readMcp, computeFlags, formatReport: formatReport2, discoverProjects, auditProjectSync };
@@ -4875,8 +4881,8 @@ var setup_exports = {};
4875
4881
  __export(setup_exports, {
4876
4882
  runSetup: () => runSetup
4877
4883
  });
4878
- import { existsSync as existsSync13, readFileSync as readFileSync14, writeFileSync as writeFileSync6, chmodSync as chmodSync2, statSync as statSync6 } from "fs";
4879
- import { join as join22 } from "path";
4884
+ import { existsSync as existsSync14, readFileSync as readFileSync15, writeFileSync as writeFileSync7, chmodSync as chmodSync2, statSync as statSync7 } from "fs";
4885
+ import { join as join23 } from "path";
4880
4886
  function baseUrl() {
4881
4887
  const fromEnv = process.env["PAPI_HOST"] ?? process.env["PAPI_BASE_URL"];
4882
4888
  if (fromEnv) return fromEnv.replace(/\/$/, "");
@@ -4908,11 +4914,11 @@ function sleep(ms) {
4908
4914
  return new Promise((resolve3) => setTimeout(resolve3, ms));
4909
4915
  }
4910
4916
  function writeMcpJson(opts) {
4911
- const path7 = join22(process.cwd(), ".mcp.json");
4917
+ const path7 = join23(process.cwd(), ".mcp.json");
4912
4918
  let parsed = {};
4913
- if (existsSync13(path7)) {
4919
+ if (existsSync14(path7)) {
4914
4920
  try {
4915
- parsed = JSON.parse(readFileSync14(path7, "utf-8"));
4921
+ parsed = JSON.parse(readFileSync15(path7, "utf-8"));
4916
4922
  } catch {
4917
4923
  throw new Error(`.mcp.json at ${path7} is not valid JSON. Fix it or remove it before re-running setup.`);
4918
4924
  }
@@ -4933,9 +4939,9 @@ function writeMcpJson(opts) {
4933
4939
  }
4934
4940
  mcpServers.papi = papiEntry;
4935
4941
  parsed.mcpServers = mcpServers;
4936
- writeFileSync6(path7, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
4942
+ writeFileSync7(path7, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
4937
4943
  try {
4938
- const mode = statSync6(path7).mode & 511;
4944
+ const mode = statSync7(path7).mode & 511;
4939
4945
  if (mode !== 384) chmodSync2(path7, 384);
4940
4946
  } catch {
4941
4947
  }
@@ -5043,8 +5049,8 @@ var init_setup = __esm({
5043
5049
  });
5044
5050
 
5045
5051
  // src/index.ts
5046
- import { readFileSync as readFileSync15 } from "fs";
5047
- import { dirname as dirname6, join as join23 } from "path";
5052
+ import { readFileSync as readFileSync16 } from "fs";
5053
+ import { dirname as dirname6, join as join24 } from "path";
5048
5054
  import { fileURLToPath as fileURLToPath4 } from "url";
5049
5055
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5050
5056
  import { Server as Server2 } from "@modelcontextprotocol/sdk/server/index.js";
@@ -8053,9 +8059,9 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
8053
8059
  }
8054
8060
 
8055
8061
  // src/server.ts
8056
- import { readFileSync as readFileSync10 } from "fs";
8062
+ import { readFileSync as readFileSync11 } from "fs";
8057
8063
  import { access as access4, readdir as readdir4, readFile as readFile9 } from "fs/promises";
8058
- import { join as join18, dirname as dirname5 } from "path";
8064
+ import { join as join19, dirname as dirname5 } from "path";
8059
8065
  import { fileURLToPath as fileURLToPath3 } from "url";
8060
8066
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
8061
8067
  import {
@@ -8317,16 +8323,41 @@ ${deferredSection}` : body;
8317
8323
  if (deferredSection) sections.push(deferredSection);
8318
8324
  return sections.join("\n\n");
8319
8325
  }
8320
- function formatCandidateTaskFullNotes(tasks) {
8326
+ var PLAN_FULL_NOTES_BUDGET_BYTES = Number(process.env.PAPI_PLAN_NOTES_BUDGET) || 6e4;
8327
+ var priorityRank = (p) => p ? PRIORITY_RANK[p] ?? 4 : 4;
8328
+ function formatCandidateTaskFullNotes(tasks, budgetBytes = PLAN_FULL_NOTES_BUDGET_BYTES) {
8321
8329
  const candidates = tasks.filter((t) => !PLAN_EXCLUDED_STATUSES.has(t.status)).filter((t) => (t.notes?.length ?? 0) > PLAN_NOTES_MAX_LENGTH);
8322
8330
  if (candidates.length === 0) return void 0;
8323
- const lines = candidates.map((t) => `**${t.id}** \u2014 ${t.title}
8324
- ${t.notes}`);
8325
- return [
8326
- `${candidates.length} candidate task(s) have notes longer than ${PLAN_NOTES_MAX_LENGTH} chars. Full untruncated notes below \u2014 reference these when generating BUILD HANDOFFs so submitter context, constraints, and reasoning are preserved. The Board section above uses truncated notes for concise task selection; this section supplies the missing detail for tasks you choose to schedule.`,
8327
- "",
8328
- ...lines
8329
- ].join("\n\n");
8331
+ const ranked = candidates.map((t, i) => ({ t, i })).sort((a, b2) => priorityRank(a.t.priority) - priorityRank(b2.t.priority) || a.i - b2.i).map(({ t }) => t);
8332
+ const included = [];
8333
+ const elided = [];
8334
+ let spent = 0;
8335
+ for (const t of ranked) {
8336
+ const entry = `**${t.id}** \u2014 ${t.title}
8337
+ ${t.notes}`;
8338
+ const cost = Buffer.byteLength(entry, "utf-8");
8339
+ if (included.length > 0 && spent + cost > budgetBytes) {
8340
+ elided.push(t);
8341
+ continue;
8342
+ }
8343
+ included.push(entry);
8344
+ spent += cost;
8345
+ }
8346
+ const header = `${candidates.length} candidate task(s) have notes longer than ${PLAN_NOTES_MAX_LENGTH} chars. Full untruncated notes below \u2014 reference these when generating BUILD HANDOFFs so submitter context, constraints, and reasoning are preserved. The Board section above uses truncated notes for concise task selection; this section supplies the missing detail for tasks you choose to schedule.`;
8347
+ const parts = [header, "", ...included];
8348
+ if (elided.length > 0) {
8349
+ parts.push(
8350
+ "",
8351
+ `### ELIDED \u2014 ${elided.length} lower-priority candidate(s) omitted to keep this payload under ${Math.round(budgetBytes / 1024)} KB`,
8352
+ "",
8353
+ "Full notes were included above for the highest-priority candidates only. The following tasks have long notes that are NOT shown here:",
8354
+ "",
8355
+ elided.map((t) => `- ${t.id} (${t.priority ?? "unranked"})`).join("\n"),
8356
+ "",
8357
+ `You still have each of these tasks in the Board section with its notes truncated to ${PLAN_NOTES_MAX_LENGTH} chars, which is enough to rank and select them. If you schedule one, say so in the cycle log and scope its handoff from the truncated notes. Do NOT invent or infer the elided detail \u2014 if a task genuinely needs its full notes to be scoped, prefer leaving it unscheduled over guessing at the submitter's intent.`
8358
+ );
8359
+ }
8360
+ return parts.join("\n\n");
8330
8361
  }
8331
8362
  function formatBoardForReview(tasks) {
8332
8363
  if (tasks.length === 0) return "No tasks on the board.";
@@ -8357,6 +8388,29 @@ function effortWeight(size2) {
8357
8388
  return 3;
8358
8389
  }
8359
8390
  }
8391
+ function computeCycleEffort(cycleTaskRows, cycleReports) {
8392
+ if (cycleTaskRows && cycleTaskRows.length > 0) {
8393
+ const done = cycleTaskRows.filter((t) => t.status === "Done");
8394
+ const reportByTask = /* @__PURE__ */ new Map();
8395
+ for (const r of cycleReports) if (r.taskId) reportByTask.set(r.taskId, r);
8396
+ return {
8397
+ completed: done.length,
8398
+ total: cycleTaskRows.length,
8399
+ plannedPoints: done.reduce((s, t) => s + effortWeight(t.complexity), 0),
8400
+ deliveredPoints: done.reduce((s, t) => {
8401
+ const actual = reportByTask.get(t.id)?.actualEffort;
8402
+ return s + effortWeight(actual || t.complexity);
8403
+ }, 0)
8404
+ };
8405
+ }
8406
+ const completed = cycleReports.filter((r) => r.completed === "Yes").length;
8407
+ return {
8408
+ completed,
8409
+ total: cycleReports.length,
8410
+ plannedPoints: cycleReports.reduce((s, r) => s + effortWeight(r.estimatedEffort || r.actualEffort), 0),
8411
+ deliveredPoints: cycleReports.reduce((s, r) => s + effortWeight(r.actualEffort || r.estimatedEffort), 0)
8412
+ };
8413
+ }
8360
8414
  function computeSnapshotsFromBuildReports(reports, tasks) {
8361
8415
  const reportsByCycle = /* @__PURE__ */ new Map();
8362
8416
  for (const r of reports) {
@@ -8380,24 +8434,19 @@ function computeSnapshotsFromBuildReports(reports, tasks) {
8380
8434
  const withEffort = cycleReports.filter((r) => r.estimatedEffort && r.actualEffort);
8381
8435
  const accurate = withEffort.filter((r) => r.estimatedEffort === r.actualEffort).length;
8382
8436
  const matchRate = withEffort.length > 0 ? Math.round(accurate / withEffort.length * 100) : 0;
8383
- let completed;
8384
- let total;
8385
- let effortPoints;
8386
- if (cycleTaskRows && cycleTaskRows.length > 0) {
8387
- const done = cycleTaskRows.filter((t) => t.status === "Done");
8388
- completed = done.length;
8389
- total = cycleTaskRows.length;
8390
- effortPoints = done.reduce((s, t) => s + effortWeight(t.complexity), 0);
8391
- } else {
8392
- completed = cycleReports.filter((r) => r.completed === "Yes").length;
8393
- total = cycleReports.length;
8394
- effortPoints = cycleReports.reduce((s, r) => s + effortWeight(r.actualEffort), 0);
8395
- }
8437
+ const { completed, total, plannedPoints, deliveredPoints } = computeCycleEffort(cycleTaskRows, cycleReports);
8396
8438
  snapshots.push({
8397
8439
  cycle: sn,
8398
8440
  date: (/* @__PURE__ */ new Date()).toISOString(),
8399
8441
  accuracy: [{ cycle: sn, reports: cycleReports.length, matchRate, mae: 0, bias: 0 }],
8400
- velocity: [{ cycle: sn, completed, partial: 0, failed: Math.max(0, total - completed), effortPoints }]
8442
+ velocity: [{
8443
+ cycle: sn,
8444
+ completed,
8445
+ partial: 0,
8446
+ failed: Math.max(0, total - completed),
8447
+ effortPoints: plannedPoints,
8448
+ deliveredPoints
8449
+ }]
8401
8450
  });
8402
8451
  }
8403
8452
  snapshots.sort((a, b2) => a.cycle - b2.cycle);
@@ -8409,13 +8458,25 @@ function formatCycleMetrics(snapshots) {
8409
8458
  const allVelocities = snapshots.flatMap((s) => s.velocity).sort((a, b2) => a.cycle - b2.cycle);
8410
8459
  const recentVelocities = allVelocities.slice(-5);
8411
8460
  if (recentVelocities.length > 0) {
8412
- const avgEffort = Math.round(
8461
+ const avgPlanned = Math.round(
8413
8462
  recentVelocities.reduce((sum, v) => sum + v.effortPoints, 0) / recentVelocities.length * 10
8414
8463
  ) / 10;
8415
- lines.push("**Cycle Sizing (effort points \u2014 primary signal)**");
8416
- lines.push(`- Last ${recentVelocities.length} cycles: ${recentVelocities.map((v) => `S${v.cycle}=${v.effortPoints}pts`).join(", ")}`);
8417
- lines.push(`- Average: ${avgEffort} effort points/cycle (XS=1, S=2, M=3, L=5, XL=8)`);
8418
- lines.push(`- Use average as a reference, not a target \u2014 size cycles based on what the selected tasks actually require.`);
8464
+ const withDelivered = recentVelocities.filter((v) => v.deliveredPoints !== void 0);
8465
+ const avgDelivered = withDelivered.length > 0 ? Math.round(withDelivered.reduce((sum, v) => sum + (v.deliveredPoints ?? 0), 0) / withDelivered.length * 10) / 10 : void 0;
8466
+ lines.push("**Cycle Sizing \u2014 planned vs delivered effort points**");
8467
+ lines.push(
8468
+ `- Last ${recentVelocities.length} cycles: ` + recentVelocities.map((v) => v.deliveredPoints !== void 0 ? `S${v.cycle}=${v.effortPoints} planned/${v.deliveredPoints} delivered` : `S${v.cycle}=${v.effortPoints} planned`).join(", ")
8469
+ );
8470
+ if (avgDelivered !== void 0) {
8471
+ const delta = Math.round((avgDelivered - avgPlanned) * 10) / 10;
8472
+ const sign = delta > 0 ? "+" : "";
8473
+ const read = delta === 0 ? "delivered matches planned" : delta > 0 ? "cycles cost MORE than scoped (under-scoping)" : "cycles cost LESS than scoped (over-scoping)";
8474
+ lines.push(`- Average: ${avgPlanned} planned / ${avgDelivered} delivered (XS=1, S=2, M=3, L=5, XL=8)`);
8475
+ lines.push(`- Scope accuracy: ${sign}${delta} pts/cycle \u2014 ${read}.`);
8476
+ } else {
8477
+ lines.push(`- Average: ${avgPlanned} planned effort points/cycle (XS=1, S=2, M=3, L=5, XL=8)`);
8478
+ }
8479
+ lines.push(`- Size cycles on what the selected tasks actually require \u2014 planned is a reference, not a target.`);
8419
8480
  }
8420
8481
  return lines.join("\n");
8421
8482
  }
@@ -12490,6 +12551,9 @@ async function preparePlan(adapter2, config2, filters, focus, force, handoffsOnl
12490
12551
  const prepareScope = await resolvePlanScope(adapter2, config2);
12491
12552
  const { mode, cycleNumber, strategyReviewWarning } = await validateAndPrepare(adapter2, force, prepareScope.callerUserId);
12492
12553
  const validateMs = t();
12554
+ const incomingCycle = cycleNumber + 1;
12555
+ tracker?.setStreamScope({ cycle: incomingCycle });
12556
+ await recordPlanPrepareStep(tracker, incomingCycle, "health-check");
12493
12557
  if (handoffsOnly) {
12494
12558
  tracker?.mark("handoffs_only_assemble");
12495
12559
  t = startTimer();
@@ -12534,6 +12598,8 @@ async function preparePlan(adapter2, config2, filters, focus, force, handoffsOnl
12534
12598
  t = startTimer();
12535
12599
  const { context, contextHashes } = await assembleContext(adapter2, mode, config2, filters, focus);
12536
12600
  const assembleMs = t();
12601
+ await recordPlanPrepareStep(tracker, incomingCycle, "inbox-triage");
12602
+ await recordPlanPrepareStep(tracker, incomingCycle, "board-integrity");
12537
12603
  const TEMPLATE_MARKER2 = "*Describe your project's core value proposition here.*";
12538
12604
  if (mode !== "bootstrap" && context.productBrief.includes(TEMPLATE_MARKER2)) {
12539
12605
  throw new Error("TEMPLATE_BRIEF");
@@ -12551,6 +12617,7 @@ async function preparePlan(adapter2, config2, filters, focus, force, handoffsOnl
12551
12617
  }
12552
12618
  const scanMs = t();
12553
12619
  console.error(`[plan-perf] codebaseScan: ${scanMs}ms`);
12620
+ await recordPlanPrepareStep(tracker, incomingCycle, "maturity-gate");
12554
12621
  tracker?.mark("build_user_message");
12555
12622
  t = startTimer();
12556
12623
  const foundation = await buildProjectFoundation(adapter2);
@@ -12586,7 +12653,13 @@ async function preparePlan(adapter2, config2, filters, focus, force, handoffsOnl
12586
12653
  contextHashes
12587
12654
  };
12588
12655
  }
12589
- var PLAN_STAGE_STEPS = ["health-check", "inbox-triage", "board-integrity", "maturity-gate", "recommendation", "dependency-chain"];
12656
+ var PLAN_PREPARE_STEPS = ["health-check", "inbox-triage", "board-integrity", "maturity-gate"];
12657
+ var PLAN_APPLY_STEPS = ["recommendation", "dependency-chain"];
12658
+ var PLAN_STAGE_STEPS = [...PLAN_PREPARE_STEPS, ...PLAN_APPLY_STEPS];
12659
+ async function recordPlanPrepareStep(tracker, incomingCycleNumber, step) {
12660
+ if (!tracker) return;
12661
+ await tracker.recordStep(step, { cycle: incomingCycleNumber, stage: "plan" });
12662
+ }
12590
12663
  async function streamPlanStageSteps(tracker, newCycleNumber) {
12591
12664
  tracker.setStreamScope({ cycle: newCycleNumber });
12592
12665
  for (const step of PLAN_STAGE_STEPS) {
@@ -13107,6 +13180,49 @@ var PerCallerCache = class {
13107
13180
  }
13108
13181
  };
13109
13182
 
13183
+ // src/lib/plan-prepare-store.ts
13184
+ import { createHash as createHash2 } from "crypto";
13185
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync, existsSync, statSync } from "fs";
13186
+ import { tmpdir } from "os";
13187
+ import { join as join3 } from "path";
13188
+ var SPILL_TTL_MS = 24 * 60 * 60 * 1e3;
13189
+ var DEFAULT_CALLER_KEY3 = "__default__";
13190
+ function spillPath(projectId, callerKey) {
13191
+ const id = createHash2("sha256").update(`${projectId ?? "no-project"}|${callerKey ?? DEFAULT_CALLER_KEY3}`).digest("hex").slice(0, 16);
13192
+ return join3(tmpdir(), `papi-plan-prepare-${id}.json`);
13193
+ }
13194
+ function savePrepareSpill(projectId, callerKey, state) {
13195
+ try {
13196
+ writeFileSync2(
13197
+ spillPath(projectId, callerKey),
13198
+ JSON.stringify({ savedAt: Date.now(), state }),
13199
+ { mode: 384 }
13200
+ );
13201
+ } catch {
13202
+ }
13203
+ }
13204
+ function loadPrepareSpill(projectId, callerKey) {
13205
+ const path7 = spillPath(projectId, callerKey);
13206
+ try {
13207
+ if (!existsSync(path7)) return void 0;
13208
+ if (Date.now() - statSync(path7).mtimeMs > SPILL_TTL_MS) {
13209
+ clearPrepareSpill(projectId, callerKey);
13210
+ return void 0;
13211
+ }
13212
+ const parsed = JSON.parse(readFileSync2(path7, "utf-8"));
13213
+ return parsed.state;
13214
+ } catch {
13215
+ return void 0;
13216
+ }
13217
+ }
13218
+ function clearPrepareSpill(projectId, callerKey) {
13219
+ try {
13220
+ const path7 = spillPath(projectId, callerKey);
13221
+ if (existsSync(path7)) unlinkSync(path7);
13222
+ } catch {
13223
+ }
13224
+ }
13225
+
13110
13226
  // src/tools/plan.ts
13111
13227
  var planPrepareCache = new PerCallerCache();
13112
13228
  var planTool = {
@@ -13274,7 +13390,7 @@ async function handlePlan(adapter2, config2, args) {
13274
13390
  const planMode = args.plan_mode || "full";
13275
13391
  const rawCycleNumber = args.cycle_number != null ? Number(args.cycle_number) : NaN;
13276
13392
  const strategyReviewWarning = args.strategy_review_warning || "";
13277
- const prep = planPrepareCache.peek(callerKey);
13393
+ const prep = planPrepareCache.peek(callerKey) ?? loadPrepareSpill(adapter2.getProjectId?.(), callerKey);
13278
13394
  const contextHashes = prep?.contextHashes;
13279
13395
  const inputContext = prep?.userMessage;
13280
13396
  const contextBytes = prep?.contextBytes;
@@ -13302,6 +13418,7 @@ async function handlePlan(adapter2, config2, args) {
13302
13418
  }
13303
13419
  const cycleNumber = newCycleNumber - 1;
13304
13420
  planPrepareCache.clear(callerKey);
13421
+ clearPrepareSpill(adapter2.getProjectId?.(), callerKey);
13305
13422
  let utilisation;
13306
13423
  if (inputContext) {
13307
13424
  try {
@@ -13332,13 +13449,15 @@ async function handlePlan(adapter2, config2, args) {
13332
13449
  }
13333
13450
  const skipHandoffs = args.skip_handoffs === true;
13334
13451
  const result = await preparePlan(adapter2, config2, filters, focus, force, handoffsOnly, skipHandoffs, tracker);
13335
- planPrepareCache.set(callerKey, {
13452
+ const prepareState = {
13336
13453
  contextHashes: result.contextHashes,
13337
13454
  userMessage: result.userMessage,
13338
13455
  contextBytes: result.contextBytes,
13339
13456
  cycleNumber: result.cycleNumber,
13340
13457
  skipHandoffs: skipHandoffs || void 0
13341
- });
13458
+ };
13459
+ planPrepareCache.set(callerKey, prepareState);
13460
+ savePrepareSpill(adapter2.getProjectId?.(), callerKey, prepareState);
13342
13461
  const autoDispatchEnabled = process.env.PAPI_AUTO_DISPATCH !== "false";
13343
13462
  const autoDispatchThreshold = 50 * 1024;
13344
13463
  let dispatch;
@@ -13422,10 +13541,10 @@ ${result.userMessage}
13422
13541
  }
13423
13542
 
13424
13543
  // src/services/strategy.ts
13425
- import { randomUUID as randomUUID10, createHash as createHash2 } from "crypto";
13544
+ import { randomUUID as randomUUID10, createHash as createHash3 } from "crypto";
13426
13545
  import { execFileSync as execFileSync2 } from "child_process";
13427
- import { existsSync, readdirSync, statSync } from "fs";
13428
- import { join as join3 } from "path";
13546
+ import { existsSync as existsSync2, readdirSync, statSync as statSync2 } from "fs";
13547
+ import { join as join4 } from "path";
13429
13548
  import { homedir as homedir2 } from "os";
13430
13549
 
13431
13550
  // src/lib/hosted-mode.ts
@@ -14371,7 +14490,7 @@ async function assembleContext2(adapter2, cycleNumber, cyclesSinceLastReview, pr
14371
14490
  try {
14372
14491
  const fullCanvasText = formatDiscoveryCanvas(canvas);
14373
14492
  if (fullCanvasText) {
14374
- const canvasHash = createHash2("md5").update(fullCanvasText).digest("hex");
14493
+ const canvasHash = createHash3("md5").update(fullCanvasText).digest("hex");
14375
14494
  const lastReview = previousStrategyReviews?.[0];
14376
14495
  const prevHash = lastReview?.structuredData?.canvasHash;
14377
14496
  if (prevHash && prevHash === canvasHash) {
@@ -14441,12 +14560,12 @@ ${lines.join("\n")}`;
14441
14560
  }
14442
14561
  let recentPlansText;
14443
14562
  try {
14444
- const plansDir = join3(homedir2(), ".claude", "plans");
14445
- if (existsSync(plansDir)) {
14563
+ const plansDir = join4(homedir2(), ".claude", "plans");
14564
+ if (existsSync2(plansDir)) {
14446
14565
  const lastReviewDate = previousStrategyReviews?.[0]?.createdAt ? new Date(previousStrategyReviews[0].createdAt) : /* @__PURE__ */ new Date(0);
14447
14566
  const planFiles = readdirSync(plansDir).filter((f) => f.endsWith(".md")).map((f) => {
14448
- const fullPath = join3(plansDir, f);
14449
- const stat4 = statSync(fullPath);
14567
+ const fullPath = join4(plansDir, f);
14568
+ const stat4 = statSync2(fullPath);
14450
14569
  return { name: f, modified: stat4.mtime, size: stat4.size };
14451
14570
  }).filter((f) => f.modified > lastReviewDate).sort((a, b2) => b2.modified.getTime() - a.modified.getTime()).slice(0, 15);
14452
14571
  if (planFiles.length > 0) {
@@ -14462,15 +14581,15 @@ ${lines.join("\n")}`;
14462
14581
  }
14463
14582
  let unregisteredDocsText;
14464
14583
  try {
14465
- const docsDir = join3(projectRoot, "docs");
14466
- if (hasLocalWorkspace() && existsSync(docsDir)) {
14584
+ const docsDir = join4(projectRoot, "docs");
14585
+ if (hasLocalWorkspace() && existsSync2(docsDir)) {
14467
14586
  const registeredPaths = new Set(
14468
14587
  (registeredDocs ?? []).map((d) => d.path).filter(Boolean)
14469
14588
  );
14470
14589
  const allDocFiles = [];
14471
14590
  const scanDir = (dir, prefix) => {
14472
14591
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
14473
- if (entry.isDirectory()) scanDir(join3(dir, entry.name), `${prefix}${entry.name}/`);
14592
+ if (entry.isDirectory()) scanDir(join4(dir, entry.name), `${prefix}${entry.name}/`);
14474
14593
  else if (entry.name.endsWith(".md")) allDocFiles.push(`${prefix}${entry.name}`);
14475
14594
  }
14476
14595
  };
@@ -14746,7 +14865,7 @@ ${cleanContent}`;
14746
14865
  const currentCanvas = await adapter2.readDiscoveryCanvas();
14747
14866
  const canvasText = formatDiscoveryCanvas(currentCanvas);
14748
14867
  if (canvasText) {
14749
- return { ...sd, canvasHash: createHash2("md5").update(canvasText).digest("hex") };
14868
+ return { ...sd, canvasHash: createHash3("md5").update(canvasText).digest("hex") };
14750
14869
  }
14751
14870
  } catch {
14752
14871
  }
@@ -16608,15 +16727,15 @@ ${existing}` : entry;
16608
16727
 
16609
16728
  // src/services/setup.ts
16610
16729
  import { mkdir, writeFile as writeFile2, readFile as readFile4, readdir, access as access2, stat as stat2, chmod } from "fs/promises";
16611
- import { join as join8, basename, extname, dirname as dirname3 } from "path";
16730
+ import { join as join9, basename, extname, dirname as dirname3 } from "path";
16612
16731
  import { execFileSync as execFileSync3 } from "child_process";
16613
16732
 
16614
16733
  // src/lib/detect-codebase.ts
16615
- import { existsSync as existsSync2 } from "fs";
16616
- import { readdirSync as readdirSync2, statSync as statSync2 } from "fs";
16617
- import { join as join4 } from "path";
16734
+ import { existsSync as existsSync3 } from "fs";
16735
+ import { readdirSync as readdirSync2, statSync as statSync3 } from "fs";
16736
+ import { join as join5 } from "path";
16618
16737
  function detectCodebaseType(projectRoot) {
16619
- if (existsSync2(join4(projectRoot, ".git"))) {
16738
+ if (existsSync3(join5(projectRoot, ".git"))) {
16620
16739
  return "existing_codebase";
16621
16740
  }
16622
16741
  const manifests = [
@@ -16630,7 +16749,7 @@ function detectCodebaseType(projectRoot) {
16630
16749
  "CMakeLists.txt"
16631
16750
  ];
16632
16751
  for (const manifest of manifests) {
16633
- if (existsSync2(join4(projectRoot, manifest))) {
16752
+ if (existsSync3(join5(projectRoot, manifest))) {
16634
16753
  return "existing_codebase";
16635
16754
  }
16636
16755
  }
@@ -16638,7 +16757,7 @@ function detectCodebaseType(projectRoot) {
16638
16757
  const entries = readdirSync2(projectRoot).filter((f) => !f.startsWith("."));
16639
16758
  const fileCount = entries.filter((f) => {
16640
16759
  try {
16641
- return statSync2(join4(projectRoot, f)).isFile();
16760
+ return statSync3(join5(projectRoot, f)).isFile();
16642
16761
  } catch {
16643
16762
  return false;
16644
16763
  }
@@ -16650,18 +16769,18 @@ function detectCodebaseType(projectRoot) {
16650
16769
  }
16651
16770
 
16652
16771
  // src/lib/agents-bundle.ts
16653
- import { readFileSync as readFileSync2, existsSync as existsSync3, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
16654
- import { dirname, join as join5, resolve } from "path";
16772
+ import { readFileSync as readFileSync3, existsSync as existsSync4, readdirSync as readdirSync3, statSync as statSync4 } from "fs";
16773
+ import { dirname, join as join6, resolve } from "path";
16655
16774
  import { fileURLToPath } from "url";
16656
- var PROJECT_BUNDLE_REL = join5(".agents", "skills", "papi-cycle");
16775
+ var PROJECT_BUNDLE_REL = join6(".agents", "skills", "papi-cycle");
16657
16776
  function bundleDestRel(rel) {
16658
- return rel === "AGENTS.md" ? "AGENTS.md" : join5(PROJECT_BUNDLE_REL, rel);
16777
+ return rel === "AGENTS.md" ? "AGENTS.md" : join6(PROJECT_BUNDLE_REL, rel);
16659
16778
  }
16660
16779
  function resolveBundleDir() {
16661
16780
  let dir = dirname(fileURLToPath(import.meta.url));
16662
16781
  for (let i = 0; i < 5; i++) {
16663
- const candidate = join5(dir, "skills", "papi-cycle");
16664
- if (existsSync3(join5(candidate, "AGENTS.md"))) return candidate;
16782
+ const candidate = join6(dir, "skills", "papi-cycle");
16783
+ if (existsSync4(join6(candidate, "AGENTS.md"))) return candidate;
16665
16784
  const parent = resolve(dir, "..");
16666
16785
  if (parent === dir) break;
16667
16786
  dir = parent;
@@ -16669,14 +16788,14 @@ function resolveBundleDir() {
16669
16788
  return void 0;
16670
16789
  }
16671
16790
  function readBundleFiles(bundleDir = resolveBundleDir()) {
16672
- if (!bundleDir || !existsSync3(bundleDir)) return [];
16791
+ if (!bundleDir || !existsSync4(bundleDir)) return [];
16673
16792
  const files = [];
16674
16793
  const walk = (abs, rel) => {
16675
16794
  for (const entry of readdirSync3(abs, { withFileTypes: true })) {
16676
- const childAbs = join5(abs, entry.name);
16677
- const childRel = rel ? join5(rel, entry.name) : entry.name;
16795
+ const childAbs = join6(abs, entry.name);
16796
+ const childRel = rel ? join6(rel, entry.name) : entry.name;
16678
16797
  if (entry.isDirectory()) walk(childAbs, childRel);
16679
- else if (entry.isFile()) files.push({ rel: childRel, content: readFileSync2(childAbs, "utf8") });
16798
+ else if (entry.isFile()) files.push({ rel: childRel, content: readFileSync3(childAbs, "utf8") });
16680
16799
  }
16681
16800
  };
16682
16801
  walk(bundleDir, "");
@@ -16685,8 +16804,8 @@ function readBundleFiles(bundleDir = resolveBundleDir()) {
16685
16804
  function planBundleInstall(projectRoot, projectName, opts = {}) {
16686
16805
  const out = {};
16687
16806
  for (const f of readBundleFiles()) {
16688
- const dest = join5(projectRoot, bundleDestRel(f.rel));
16689
- if (opts.skipExisting && existsSync3(dest) && statSync3(dest).isFile()) continue;
16807
+ const dest = join6(projectRoot, bundleDestRel(f.rel));
16808
+ if (opts.skipExisting && existsSync4(dest) && statSync4(dest).isFile()) continue;
16690
16809
  const content = f.rel === "AGENTS.md" ? f.content.replace(/\{\{project_name\}\}/g, projectName) : f.content;
16691
16810
  out[dest] = content;
16692
16811
  }
@@ -16694,20 +16813,20 @@ function planBundleInstall(projectRoot, projectName, opts = {}) {
16694
16813
  }
16695
16814
 
16696
16815
  // src/lib/design-bundle.ts
16697
- import { readFileSync as readFileSync3, existsSync as existsSync4, statSync as statSync4 } from "fs";
16698
- import { dirname as dirname2, join as join6, resolve as resolve2 } from "path";
16816
+ import { readFileSync as readFileSync4, existsSync as existsSync5, statSync as statSync5 } from "fs";
16817
+ import { dirname as dirname2, join as join7, resolve as resolve2 } from "path";
16699
16818
  import { fileURLToPath as fileURLToPath2 } from "url";
16700
16819
  var DESIGN_ASSETS = [
16701
- { srcRel: join6("agents", "frontend-design-engineer.md"), destRel: join6(".claude", "agents", "frontend-design-engineer.md"), executable: false },
16702
- { srcRel: join6("skills", "design-critique", "SKILL.md"), destRel: join6(".claude", "skills", "design-critique", "SKILL.md"), executable: false },
16703
- { srcRel: join6("hooks", "frontend-design-guard.sh"), destRel: join6(".claude", "hooks", "frontend-design-guard.sh"), executable: true }
16820
+ { srcRel: join7("agents", "frontend-design-engineer.md"), destRel: join7(".claude", "agents", "frontend-design-engineer.md"), executable: false },
16821
+ { srcRel: join7("skills", "design-critique", "SKILL.md"), destRel: join7(".claude", "skills", "design-critique", "SKILL.md"), executable: false },
16822
+ { srcRel: join7("hooks", "frontend-design-guard.sh"), destRel: join7(".claude", "hooks", "frontend-design-guard.sh"), executable: true }
16704
16823
  ];
16705
16824
  var DESIGN_HOOK_COMMAND = ".claude/hooks/frontend-design-guard.sh";
16706
16825
  function resolveDesignAssetsDir() {
16707
16826
  let dir = dirname2(fileURLToPath2(import.meta.url));
16708
16827
  for (let i = 0; i < 5; i++) {
16709
- const candidate = join6(dir, "design-assets");
16710
- if (existsSync4(join6(candidate, "agents", "frontend-design-engineer.md"))) return candidate;
16828
+ const candidate = join7(dir, "design-assets");
16829
+ if (existsSync5(join7(candidate, "agents", "frontend-design-engineer.md"))) return candidate;
16711
16830
  const parent = resolve2(dir, "..");
16712
16831
  if (parent === dir) break;
16713
16832
  dir = parent;
@@ -16719,23 +16838,23 @@ function planDesignInstall(projectRoot, opts = {}) {
16719
16838
  if (!assetsDir) return [];
16720
16839
  const out = [];
16721
16840
  for (const asset of DESIGN_ASSETS) {
16722
- const srcAbs = join6(assetsDir, asset.srcRel);
16723
- if (!existsSync4(srcAbs)) continue;
16724
- const dest = projectRoot ? join6(projectRoot, asset.destRel) : asset.destRel;
16725
- if (opts.skipExisting && projectRoot && existsSync4(dest) && statSync4(dest).isFile()) continue;
16726
- out.push({ dest, content: readFileSync3(srcAbs, "utf8"), executable: asset.executable });
16841
+ const srcAbs = join7(assetsDir, asset.srcRel);
16842
+ if (!existsSync5(srcAbs)) continue;
16843
+ const dest = projectRoot ? join7(projectRoot, asset.destRel) : asset.destRel;
16844
+ if (opts.skipExisting && projectRoot && existsSync5(dest) && statSync5(dest).isFile()) continue;
16845
+ out.push({ dest, content: readFileSync4(srcAbs, "utf8"), executable: asset.executable });
16727
16846
  }
16728
16847
  return out;
16729
16848
  }
16730
16849
 
16731
16850
  // src/lib/skill-detection.ts
16732
- import { existsSync as existsSync5, readdirSync as readdirSync4, readFileSync as readFileSync4, statSync as statSync5 } from "fs";
16733
- import { join as join7 } from "path";
16851
+ import { existsSync as existsSync6, readdirSync as readdirSync4, readFileSync as readFileSync5, statSync as statSync6 } from "fs";
16852
+ import { join as join8 } from "path";
16734
16853
  function readPackageJson(projectRoot) {
16735
- const path7 = join7(projectRoot, "package.json");
16736
- if (!existsSync5(path7)) return null;
16854
+ const path7 = join8(projectRoot, "package.json");
16855
+ if (!existsSync6(path7)) return null;
16737
16856
  try {
16738
- const raw = readFileSync4(path7, "utf-8");
16857
+ const raw = readFileSync5(path7, "utf-8");
16739
16858
  return JSON.parse(raw);
16740
16859
  } catch {
16741
16860
  return null;
@@ -16756,8 +16875,8 @@ function detectsFrontendStack(projectRoot) {
16756
16875
  return hasDependencyMatching(allDeps(readPackageJson(projectRoot)), FRONTEND_DEP_PATTERN);
16757
16876
  }
16758
16877
  function hasGitHubWorkflows(projectRoot) {
16759
- const dir = join7(projectRoot, ".github", "workflows");
16760
- if (!existsSync5(dir)) return false;
16878
+ const dir = join8(projectRoot, ".github", "workflows");
16879
+ if (!existsSync6(dir)) return false;
16761
16880
  try {
16762
16881
  const entries = readdirSync4(dir);
16763
16882
  return entries.some((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
@@ -16766,21 +16885,21 @@ function hasGitHubWorkflows(projectRoot) {
16766
16885
  }
16767
16886
  }
16768
16887
  function envExampleMentionsStaging(projectRoot) {
16769
- const path7 = join7(projectRoot, ".env.example");
16770
- if (!existsSync5(path7)) return false;
16888
+ const path7 = join8(projectRoot, ".env.example");
16889
+ if (!existsSync6(path7)) return false;
16771
16890
  try {
16772
- const raw = readFileSync4(path7, "utf-8");
16891
+ const raw = readFileSync5(path7, "utf-8");
16773
16892
  return /\b(STAGING_URL|STAGING_API|STAGING_HOST|NEXT_PUBLIC_STAGING)/i.test(raw);
16774
16893
  } catch {
16775
16894
  return false;
16776
16895
  }
16777
16896
  }
16778
16897
  function hasVercelConfig(projectRoot) {
16779
- if (existsSync5(join7(projectRoot, "vercel.json"))) return true;
16780
- const vercelDir = join7(projectRoot, ".vercel");
16781
- if (!existsSync5(vercelDir)) return false;
16898
+ if (existsSync6(join8(projectRoot, "vercel.json"))) return true;
16899
+ const vercelDir = join8(projectRoot, ".vercel");
16900
+ if (!existsSync6(vercelDir)) return false;
16782
16901
  try {
16783
- return statSync5(vercelDir).isDirectory();
16902
+ return statSync6(vercelDir).isDirectory();
16784
16903
  } catch {
16785
16904
  return false;
16786
16905
  }
@@ -17266,7 +17385,7 @@ async function scaffoldPapiDir(adapter2, config2, input, collector) {
17266
17385
  await mkdir(config2.papiDir, { recursive: true });
17267
17386
  for (const [filename, template] of Object.entries(FILE_TEMPLATES)) {
17268
17387
  const content = substitute(template, vars);
17269
- await writeFile2(join8(config2.papiDir, filename), content, "utf-8");
17388
+ await writeFile2(join9(config2.papiDir, filename), content, "utf-8");
17270
17389
  }
17271
17390
  }
17272
17391
  } else {
@@ -17284,13 +17403,13 @@ async function scaffoldPapiDir(adapter2, config2, input, collector) {
17284
17403
  const useCollector = config2.adapterType === "proxy";
17285
17404
  const docsRel = "docs";
17286
17405
  const commandsRel = ".claude/commands";
17287
- const commandsDir = useCollector ? commandsRel : join8(config2.projectRoot, ".claude", "commands");
17288
- const docsDir = useCollector ? docsRel : join8(config2.projectRoot, "docs");
17406
+ const commandsDir = useCollector ? commandsRel : join9(config2.projectRoot, ".claude", "commands");
17407
+ const docsDir = useCollector ? docsRel : join9(config2.projectRoot, "docs");
17289
17408
  if (!useCollector) {
17290
17409
  await mkdir(commandsDir, { recursive: true });
17291
17410
  await mkdir(docsDir, { recursive: true });
17292
17411
  }
17293
- const claudeMdPath = useCollector ? "CLAUDE.md" : join8(config2.projectRoot, "CLAUDE.md");
17412
+ const claudeMdPath = useCollector ? "CLAUDE.md" : join9(config2.projectRoot, "CLAUDE.md");
17294
17413
  let claudeMdExists = false;
17295
17414
  if (!useCollector) {
17296
17415
  try {
@@ -17299,7 +17418,7 @@ async function scaffoldPapiDir(adapter2, config2, input, collector) {
17299
17418
  } catch {
17300
17419
  }
17301
17420
  }
17302
- const docsIndexPath = useCollector ? `${docsRel}/INDEX.md` : join8(docsDir, "INDEX.md");
17421
+ const docsIndexPath = useCollector ? `${docsRel}/INDEX.md` : join9(docsDir, "INDEX.md");
17303
17422
  let docsIndexExists = false;
17304
17423
  if (!useCollector) {
17305
17424
  try {
@@ -17309,9 +17428,9 @@ async function scaffoldPapiDir(adapter2, config2, input, collector) {
17309
17428
  }
17310
17429
  }
17311
17430
  const scaffoldFiles = {
17312
- [useCollector ? `${commandsRel}/papi-audit.md` : join8(commandsDir, "papi-audit.md")]: PAPI_AUDIT_COMMAND_TEMPLATE,
17313
- [useCollector ? `${commandsRel}/test.md` : join8(commandsDir, "test.md")]: TEST_COMMAND_TEMPLATE,
17314
- [useCollector ? `${docsRel}/README.md` : join8(docsDir, "README.md")]: substitute(DOCS_README_TEMPLATE, vars)
17431
+ [useCollector ? `${commandsRel}/papi-audit.md` : join9(commandsDir, "papi-audit.md")]: PAPI_AUDIT_COMMAND_TEMPLATE,
17432
+ [useCollector ? `${commandsRel}/test.md` : join9(commandsDir, "test.md")]: TEST_COMMAND_TEMPLATE,
17433
+ [useCollector ? `${docsRel}/README.md` : join9(docsDir, "README.md")]: substitute(DOCS_README_TEMPLATE, vars)
17315
17434
  };
17316
17435
  if (!docsIndexExists) {
17317
17436
  scaffoldFiles[docsIndexPath] = substitute(DOCS_INDEX_TEMPLATE, vars);
@@ -17338,7 +17457,7 @@ async function scaffoldPapiDir(adapter2, config2, input, collector) {
17338
17457
  if (useCollector) {
17339
17458
  scaffoldFiles[".cursor/rules/papi.mdc"] = substitute(CURSOR_RULES_TEMPLATE, vars);
17340
17459
  } else {
17341
- const cursorDir = join8(config2.projectRoot, ".cursor");
17460
+ const cursorDir = join9(config2.projectRoot, ".cursor");
17342
17461
  let cursorDetected = false;
17343
17462
  try {
17344
17463
  await access2(cursorDir);
@@ -17346,8 +17465,8 @@ async function scaffoldPapiDir(adapter2, config2, input, collector) {
17346
17465
  } catch {
17347
17466
  }
17348
17467
  if (cursorDetected) {
17349
- const cursorRulesDir = join8(cursorDir, "rules");
17350
- const cursorRulesPath = join8(cursorRulesDir, "papi.mdc");
17468
+ const cursorRulesDir = join9(cursorDir, "rules");
17469
+ const cursorRulesPath = join9(cursorRulesDir, "papi.mdc");
17351
17470
  await mkdir(cursorRulesDir, { recursive: true });
17352
17471
  try {
17353
17472
  await access2(cursorRulesPath);
@@ -17402,7 +17521,7 @@ async function scaffoldPapiDir(adapter2, config2, input, collector) {
17402
17521
  }
17403
17522
  var PAPI_PERMISSION = "mcp__papi__*";
17404
17523
  async function ensurePapiPermission(projectRoot) {
17405
- const settingsPath = join8(projectRoot, ".claude", "settings.json");
17524
+ const settingsPath = join9(projectRoot, ".claude", "settings.json");
17406
17525
  try {
17407
17526
  let settings = {};
17408
17527
  try {
@@ -17421,13 +17540,13 @@ async function ensurePapiPermission(projectRoot) {
17421
17540
  if (!allow.includes(PAPI_PERMISSION)) {
17422
17541
  allow.push(PAPI_PERMISSION);
17423
17542
  }
17424
- await mkdir(join8(projectRoot, ".claude"), { recursive: true });
17543
+ await mkdir(join9(projectRoot, ".claude"), { recursive: true });
17425
17544
  await writeFile2(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
17426
17545
  } catch {
17427
17546
  }
17428
17547
  }
17429
17548
  async function ensureDesignHookRegistered(projectRoot) {
17430
- const settingsPath = join8(projectRoot, ".claude", "settings.json");
17549
+ const settingsPath = join9(projectRoot, ".claude", "settings.json");
17431
17550
  try {
17432
17551
  let settings = {};
17433
17552
  try {
@@ -17457,7 +17576,7 @@ async function ensureDesignHookRegistered(projectRoot) {
17457
17576
  chain.push({ type: "command", command: DESIGN_HOOK_COMMAND });
17458
17577
  }
17459
17578
  }
17460
- await mkdir(join8(projectRoot, ".claude"), { recursive: true });
17579
+ await mkdir(join9(projectRoot, ".claude"), { recursive: true });
17461
17580
  await writeFile2(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
17462
17581
  } catch {
17463
17582
  }
@@ -17561,7 +17680,7 @@ ${conventionsText.trim()}
17561
17680
  );
17562
17681
  } else {
17563
17682
  try {
17564
- const claudeMdPath = join8(config2.projectRoot, "CLAUDE.md");
17683
+ const claudeMdPath = join9(config2.projectRoot, "CLAUDE.md");
17565
17684
  const existing = await readFile4(claudeMdPath, "utf-8");
17566
17685
  if (existing.includes(CONVENTIONS_SENTINEL) || existing.includes(CONVENTIONS_HEADING)) {
17567
17686
  warnings.push(
@@ -17648,13 +17767,13 @@ async function scanCodebase(projectRoot) {
17648
17767
  }
17649
17768
  let packageJson;
17650
17769
  try {
17651
- const content = await readFile4(join8(projectRoot, "package.json"), "utf-8");
17770
+ const content = await readFile4(join9(projectRoot, "package.json"), "utf-8");
17652
17771
  packageJson = JSON.parse(content);
17653
17772
  } catch {
17654
17773
  }
17655
17774
  let readme;
17656
17775
  for (const name of ["README.md", "readme.md", "README.txt", "README"]) {
17657
- const content = await safeReadFile(join8(projectRoot, name), 5e3);
17776
+ const content = await safeReadFile(join9(projectRoot, name), 5e3);
17658
17777
  if (content) {
17659
17778
  readme = content;
17660
17779
  break;
@@ -17664,7 +17783,7 @@ async function scanCodebase(projectRoot) {
17664
17783
  let totalFiles = topLevelFiles.length;
17665
17784
  for (const dir of topLevelDirs) {
17666
17785
  try {
17667
- const entries = await readdir(join8(projectRoot, dir), { withFileTypes: true });
17786
+ const entries = await readdir(join9(projectRoot, dir), { withFileTypes: true });
17668
17787
  const files = entries.filter((e) => e.isFile());
17669
17788
  const extensions = [...new Set(files.map((f) => extname(f.name).toLowerCase()).filter(Boolean))];
17670
17789
  totalFiles += files.length;
@@ -18011,7 +18130,7 @@ async function applySetup(adapter2, config2, input, briefText, adSeedText, conve
18011
18130
  collector.add({ path: "CLAUDE.md", content: dogfoodSection, mode: "append" });
18012
18131
  } else {
18013
18132
  try {
18014
- const claudeMdPath = join8(config2.projectRoot, "CLAUDE.md");
18133
+ const claudeMdPath = join9(config2.projectRoot, "CLAUDE.md");
18015
18134
  const existing = await readFile4(claudeMdPath, "utf-8");
18016
18135
  if (!existing.includes("Dogfood Logging")) {
18017
18136
  await writeFile2(claudeMdPath, existing + dogfoodSection, "utf-8");
@@ -18056,7 +18175,7 @@ async function applySetup(adapter2, config2, input, briefText, adSeedText, conve
18056
18175
  cursorScaffolded = true;
18057
18176
  } else {
18058
18177
  try {
18059
- await access2(join8(config2.projectRoot, ".cursor", "rules", "papi.mdc"));
18178
+ await access2(join9(config2.projectRoot, ".cursor", "rules", "papi.mdc"));
18060
18179
  cursorScaffolded = true;
18061
18180
  } catch {
18062
18181
  }
@@ -18076,11 +18195,11 @@ async function applySetup(adapter2, config2, input, briefText, adSeedText, conve
18076
18195
  }
18077
18196
  async function ensureMcpJsonGitignored(projectRoot) {
18078
18197
  try {
18079
- await access2(join8(projectRoot, ".git"));
18198
+ await access2(join9(projectRoot, ".git"));
18080
18199
  } catch {
18081
18200
  return void 0;
18082
18201
  }
18083
- const gitignorePath = join8(projectRoot, ".gitignore");
18202
+ const gitignorePath = join9(projectRoot, ".gitignore");
18084
18203
  let existing = "";
18085
18204
  try {
18086
18205
  existing = await readFile4(gitignorePath, "utf-8");
@@ -18595,8 +18714,8 @@ PAPI never runs this command itself (AD-58) \u2014 you run it in your own enviro
18595
18714
 
18596
18715
  // src/services/build.ts
18597
18716
  import { randomUUID as randomUUID11 } from "crypto";
18598
- import { readdirSync as readdirSync5, existsSync as existsSync6, readFileSync as readFileSync5, writeFileSync as writeFileSync2, unlinkSync, mkdirSync as mkdirSync2 } from "fs";
18599
- import { join as join10 } from "path";
18717
+ import { readdirSync as readdirSync5, existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2, mkdirSync as mkdirSync2 } from "fs";
18718
+ import { join as join11 } from "path";
18600
18719
 
18601
18720
  // src/lib/harness-capability.ts
18602
18721
  var HARNESS_REGISTRY = {
@@ -18672,7 +18791,7 @@ init_git();
18672
18791
  // src/services/release.ts
18673
18792
  init_telemetry();
18674
18793
  import { writeFile as writeFile3, readFile as readFile5 } from "fs/promises";
18675
- import { join as join9 } from "path";
18794
+ import { join as join10 } from "path";
18676
18795
  import { execFileSync as execFileSync4 } from "child_process";
18677
18796
  init_git();
18678
18797
  var INITIAL_RELEASE_NOTES = `# Changelog
@@ -19136,7 +19255,7 @@ async function createRelease(config2, branch, version, adapter2, cycleNum, optio
19136
19255
  }
19137
19256
  }
19138
19257
  const latestTag = getLatestTag(config2.projectRoot);
19139
- const changelogPath = join9(config2.projectRoot, "CHANGELOG.md");
19258
+ const changelogPath = join10(config2.projectRoot, "CHANGELOG.md");
19140
19259
  if (!latestTag) {
19141
19260
  const initialContent = INITIAL_RELEASE_NOTES.replace("v0.1.0-alpha", version);
19142
19261
  if (config2.adapterType === "proxy") {
@@ -20573,17 +20692,17 @@ function writeActiveTaskScope(projectRoot, taskId, filesLikelyTouched, adapterTy
20573
20692
  collector.add({ path: ".papi/active-task-scope.txt", content, mode: "overwrite" });
20574
20693
  return;
20575
20694
  }
20576
- const papiDir = join10(projectRoot, ".papi");
20577
- if (!existsSync6(papiDir)) {
20695
+ const papiDir = join11(projectRoot, ".papi");
20696
+ if (!existsSync7(papiDir)) {
20578
20697
  mkdirSync2(papiDir, { recursive: true });
20579
20698
  }
20580
- const scopePath = join10(papiDir, "active-task-scope.txt");
20581
- writeFileSync2(scopePath, content, "utf-8");
20699
+ const scopePath = join11(papiDir, "active-task-scope.txt");
20700
+ writeFileSync3(scopePath, content, "utf-8");
20582
20701
  }
20583
20702
  function clearActiveTaskScope(projectRoot) {
20584
- const scopePath = join10(projectRoot, ".papi", "active-task-scope.txt");
20585
- if (existsSync6(scopePath)) {
20586
- unlinkSync(scopePath);
20703
+ const scopePath = join11(projectRoot, ".papi", "active-task-scope.txt");
20704
+ if (existsSync7(scopePath)) {
20705
+ unlinkSync2(scopePath);
20587
20706
  }
20588
20707
  }
20589
20708
  function sanitiseResponseExcerpt(raw) {
@@ -20602,7 +20721,7 @@ function extractDocMeta(absolutePath, relativePath, cycleNumber) {
20602
20721
  else if (relativePath.startsWith("docs/architecture/")) type = "architecture";
20603
20722
  else if (relativePath.startsWith("docs/audits/")) type = "audit";
20604
20723
  try {
20605
- const content = readFileSync5(absolutePath, "utf-8").slice(0, 2e3);
20724
+ const content = readFileSync6(absolutePath, "utf-8").slice(0, 2e3);
20606
20725
  const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
20607
20726
  if (fmMatch) {
20608
20727
  const fm = fmMatch[1];
@@ -20981,14 +21100,14 @@ async function completeBuild(adapter2, config2, taskId, input, options = {}, cli
20981
21100
  let docWarning;
20982
21101
  try {
20983
21102
  if (adapter2.searchDocs && hasLocalWorkspace() && await ownsLocalWorkspace(adapter2, config2.projectRoot)) {
20984
- const docsDir = join10(config2.projectRoot, "docs");
20985
- if (existsSync6(docsDir)) {
21103
+ const docsDir = join11(config2.projectRoot, "docs");
21104
+ if (existsSync7(docsDir)) {
20986
21105
  const scanDir = (dir, depth = 0) => {
20987
21106
  if (depth > 8) return [];
20988
21107
  const entries = readdirSync5(dir, { withFileTypes: true });
20989
21108
  const files = [];
20990
21109
  for (const e of entries) {
20991
- const full = join10(dir, e.name);
21110
+ const full = join11(dir, e.name);
20992
21111
  if (e.isDirectory() && !e.isSymbolicLink()) files.push(...scanDir(full, depth + 1));
20993
21112
  else if (e.name.endsWith(".md")) files.push(full.replace(config2.projectRoot + "/", ""));
20994
21113
  }
@@ -21003,7 +21122,7 @@ async function completeBuild(adapter2, config2, taskId, input, options = {}, cli
21003
21122
  const failed = [];
21004
21123
  for (const docPath of unregistered) {
21005
21124
  try {
21006
- const meta = extractDocMeta(join10(config2.projectRoot, docPath), docPath, cycleNumber);
21125
+ const meta = extractDocMeta(join11(config2.projectRoot, docPath), docPath, cycleNumber);
21007
21126
  await adapter2.registerDoc({
21008
21127
  title: meta.title,
21009
21128
  type: meta.type,
@@ -21152,8 +21271,8 @@ ${instructions}`;
21152
21271
  }
21153
21272
 
21154
21273
  // src/tools/doc-registry.ts
21155
- import { readdirSync as readdirSync6, existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
21156
- import { join as join11, relative } from "path";
21274
+ import { readdirSync as readdirSync6, existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
21275
+ import { join as join12, relative } from "path";
21157
21276
  import { homedir as homedir3 } from "os";
21158
21277
  import { randomUUID as randomUUID12 } from "crypto";
21159
21278
  var docRegisterTool = {
@@ -21350,7 +21469,7 @@ async function handleDocSearch(adapter2, args, config2) {
21350
21469
  const lines = docs.map((d) => {
21351
21470
  const actionCount = d.actions?.filter((a) => a.status === "pending").length ?? 0;
21352
21471
  const actionNote = actionCount > 0 ? ` | ${actionCount} pending action(s)` : "";
21353
- const missingNote = root && d.path && !existsSync7(join11(root, d.path)) ? `
21472
+ const missingNote = root && d.path && !existsSync8(join12(root, d.path)) ? `
21354
21473
  > \u26A0\uFE0F **File missing on disk** \u2014 the registry points at \`${d.path}\` but nothing is there. Check \`git stash list\` for a papi-autostash entry, or re-create/deregister the doc.` : "";
21355
21474
  return `### ${d.title}
21356
21475
  **Type:** ${d.type} | **Status:** ${d.status} | **Cycle:** ${d.cycleCreated}${d.cycleUpdated ? `\u2192${d.cycleUpdated}` : ""}${actionNote}
@@ -21364,12 +21483,12 @@ ${d.summary}
21364
21483
  ${lines.join("\n---\n\n")}`);
21365
21484
  }
21366
21485
  function scanMdFiles(dir, rootDir) {
21367
- if (!existsSync7(dir)) return [];
21486
+ if (!existsSync8(dir)) return [];
21368
21487
  const files = [];
21369
21488
  try {
21370
21489
  const entries = readdirSync6(dir, { withFileTypes: true });
21371
21490
  for (const entry of entries) {
21372
- const full = join11(dir, entry.name);
21491
+ const full = join12(dir, entry.name);
21373
21492
  if (entry.isDirectory()) {
21374
21493
  files.push(...scanMdFiles(full, rootDir));
21375
21494
  } else if (entry.name.endsWith(".md")) {
@@ -21382,7 +21501,7 @@ function scanMdFiles(dir, rootDir) {
21382
21501
  }
21383
21502
  function extractTitle(filePath) {
21384
21503
  try {
21385
- const content = readFileSync6(filePath, "utf-8").slice(0, 1e3);
21504
+ const content = readFileSync7(filePath, "utf-8").slice(0, 1e3);
21386
21505
  const fmMatch = content.match(/^---[\s\S]*?title:\s*(.+?)$/m);
21387
21506
  if (fmMatch) return fmMatch[1].trim().replace(/^["']|["']$/g, "");
21388
21507
  const headingMatch = content.match(/^#+\s+(.+)$/m);
@@ -21394,7 +21513,7 @@ function extractTitle(filePath) {
21394
21513
  async function detectUnregisteredDocsNote(adapter2, config2) {
21395
21514
  try {
21396
21515
  if (!adapter2.searchDocs || !hasLocalWorkspace()) return "";
21397
- const docsDir = join11(config2.projectRoot, "docs");
21516
+ const docsDir = join12(config2.projectRoot, "docs");
21398
21517
  const docsFiles = scanMdFiles(docsDir, config2.projectRoot);
21399
21518
  if (docsFiles.length === 0) return "";
21400
21519
  const registered = await adapter2.searchDocs({ limit: 500, status: "all" });
@@ -21420,17 +21539,17 @@ async function handleDocScan(adapter2, config2, args) {
21420
21539
  const includePlans = args.include_plans ?? false;
21421
21540
  const registered = await adapter2.searchDocs({ limit: 500, status: "all" });
21422
21541
  const registeredPaths = new Set(registered.map((d) => d.path));
21423
- const docsDir = join11(config2.projectRoot, "docs");
21542
+ const docsDir = join12(config2.projectRoot, "docs");
21424
21543
  const docsFiles = scanMdFiles(docsDir, config2.projectRoot);
21425
21544
  const unregisteredDocs = docsFiles.filter((f) => !registeredPaths.has(f));
21426
21545
  let unregisteredPlans = [];
21427
21546
  if (includePlans) {
21428
- const plansDir = join11(homedir3(), ".claude", "plans");
21429
- if (existsSync7(plansDir)) {
21547
+ const plansDir = join12(homedir3(), ".claude", "plans");
21548
+ if (existsSync8(plansDir)) {
21430
21549
  const planFiles = scanMdFiles(plansDir, plansDir);
21431
21550
  unregisteredPlans = planFiles.map((f) => `plans/${f}`).filter((f) => !registeredPaths.has(f)).map((f) => ({
21432
21551
  path: f,
21433
- title: extractTitle(join11(plansDir, f.replace("plans/", "")))
21552
+ title: extractTitle(join12(plansDir, f.replace("plans/", "")))
21434
21553
  }));
21435
21554
  }
21436
21555
  }
@@ -21441,7 +21560,7 @@ async function handleDocScan(adapter2, config2, args) {
21441
21560
  if (unregisteredDocs.length > 0) {
21442
21561
  lines.push(`## Unregistered Docs (${unregisteredDocs.length})`);
21443
21562
  for (const f of unregisteredDocs) {
21444
- const title = extractTitle(join11(config2.projectRoot, f));
21563
+ const title = extractTitle(join12(config2.projectRoot, f));
21445
21564
  lines.push(`- \`${f}\`${title ? ` \u2014 ${title}` : ""}`);
21446
21565
  }
21447
21566
  }
@@ -22871,13 +22990,13 @@ _To correct: board_edit ${result.task.id} with updated fields._`
22871
22990
  init_git();
22872
22991
 
22873
22992
  // src/services/reconcile.ts
22874
- import { readFileSync as readFileSync7 } from "fs";
22875
- import { join as join12 } from "path";
22993
+ import { readFileSync as readFileSync8 } from "fs";
22994
+ import { join as join13 } from "path";
22876
22995
  function loadDocsIndex(projectRoot) {
22877
22996
  if (!hasLocalWorkspace()) return "";
22878
22997
  try {
22879
- const indexPath = join12(projectRoot, "docs", "INDEX.md");
22880
- const raw = readFileSync7(indexPath, "utf8");
22998
+ const indexPath = join13(projectRoot, "docs", "INDEX.md");
22999
+ const raw = readFileSync8(indexPath, "utf8");
22881
23000
  const rows = raw.split("\n").filter((l) => l.startsWith("| ["));
22882
23001
  if (rows.length === 0) return "";
22883
23002
  const entries = rows.map((row) => {
@@ -23452,8 +23571,8 @@ Produce your analysis and structured output above. Present Part 1 to the user an
23452
23571
  }
23453
23572
 
23454
23573
  // src/tools/review.ts
23455
- import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
23456
- import { join as join13 } from "path";
23574
+ import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
23575
+ import { join as join14 } from "path";
23457
23576
  init_git();
23458
23577
 
23459
23578
  // src/services/review.ts
@@ -23675,11 +23794,11 @@ ${task.buildReport}` : "### Build Report\n(none recorded)";
23675
23794
  ${diff}
23676
23795
  \`\`\`` : "### Branch diff vs base\n(no diff resolved \u2014 not a git repo, no base ref, or no committed changes)";
23677
23796
  let projectContext = "";
23678
- const ctxPath = join13(config2.projectRoot, ".agents", "papi-context.md");
23679
- if (existsSync8(ctxPath)) {
23797
+ const ctxPath = join14(config2.projectRoot, ".agents", "papi-context.md");
23798
+ if (existsSync9(ctxPath)) {
23680
23799
  try {
23681
23800
  projectContext = `### Project context (.agents/papi-context.md)
23682
- ${readFileSync8(ctxPath, "utf-8")}
23801
+ ${readFileSync9(ctxPath, "utf-8")}
23683
23802
 
23684
23803
  `;
23685
23804
  } catch {
@@ -23834,8 +23953,8 @@ function mergeAfterAccept(config2, taskId) {
23834
23953
  };
23835
23954
  }
23836
23955
  const details = [];
23837
- const papiDir = join13(config2.projectRoot, ".papi");
23838
- if (existsSync8(papiDir)) {
23956
+ const papiDir = join14(config2.projectRoot, ".papi");
23957
+ if (existsSync9(papiDir)) {
23839
23958
  try {
23840
23959
  const commitResult = stageDirAndCommit(
23841
23960
  config2.projectRoot,
@@ -25379,7 +25498,7 @@ function formatDeferredGateSection(sweep) {
25379
25498
 
25380
25499
  // src/tools/agent-list.ts
25381
25500
  import { readdir as readdir2, readFile as readFile7 } from "fs/promises";
25382
- import { join as join14 } from "path";
25501
+ import { join as join15 } from "path";
25383
25502
  var NO_AGENTS_HINT = "No project sub-agents found in `.claude/agents/`. Add a `*.md` file with `name` + `description` frontmatter \u2014 see the 1926-Census marketing sub-agent for a reference implementation.";
25384
25503
  function parseAgentFrontmatter(content) {
25385
25504
  const match = content.match(/^---\n([\s\S]*?)\n---/);
@@ -25391,7 +25510,7 @@ function parseAgentFrontmatter(content) {
25391
25510
  return { name: nameMatch?.[1].trim(), description };
25392
25511
  }
25393
25512
  async function listAgents(projectRoot) {
25394
- const agentsDir = join14(projectRoot, ".claude", "agents");
25513
+ const agentsDir = join15(projectRoot, ".claude", "agents");
25395
25514
  let files;
25396
25515
  try {
25397
25516
  files = await readdir2(agentsDir);
@@ -25402,7 +25521,7 @@ async function listAgents(projectRoot) {
25402
25521
  for (const file of files.filter((f) => f.endsWith(".md"))) {
25403
25522
  let content;
25404
25523
  try {
25405
- content = await readFile7(join14(agentsDir, file), "utf-8");
25524
+ content = await readFile7(join15(agentsDir, file), "utf-8");
25406
25525
  } catch {
25407
25526
  continue;
25408
25527
  }
@@ -25410,7 +25529,7 @@ async function listAgents(projectRoot) {
25410
25529
  agents.push({
25411
25530
  name: meta?.name ?? file.replace(/\.md$/, ""),
25412
25531
  description: meta?.description ?? "",
25413
- path: join14(".claude", "agents", file)
25532
+ path: join15(".claude", "agents", file)
25414
25533
  });
25415
25534
  }
25416
25535
  agents.sort((a, b2) => a.name.localeCompare(b2.name));
@@ -25551,8 +25670,8 @@ async function verifyProject(adapter2) {
25551
25670
  // src/tools/orient.ts
25552
25671
  import { execFile as execFile2 } from "child_process";
25553
25672
  import { promisify as promisify2 } from "util";
25554
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync3, existsSync as existsSync9 } from "fs";
25555
- import { join as join15 } from "path";
25673
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync4, existsSync as existsSync10 } from "fs";
25674
+ import { join as join16 } from "path";
25556
25675
  var execFileAsync2 = promisify2(execFile2);
25557
25676
  var GIT_DEPENDENT_ENVS = /* @__PURE__ */ new Set(["hosted", "api"]);
25558
25677
  var VALID_ENVS = /* @__PURE__ */ new Set(["local-cli", "hosted", "api", "unknown"]);
@@ -25867,8 +25986,8 @@ async function getLatestGitTag(projectRoot) {
25867
25986
  }
25868
25987
  async function checkNpmVersionDrift() {
25869
25988
  try {
25870
- const pkgPath = join15(new URL(".", import.meta.url).pathname, "..", "..", "package.json");
25871
- const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
25989
+ const pkgPath = join16(new URL(".", import.meta.url).pathname, "..", "..", "package.json");
25990
+ const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
25872
25991
  const localVersion = pkg.version;
25873
25992
  const packageName = pkg.name;
25874
25993
  const { stdout } = await execFileAsync2("npm", ["view", packageName, "version"], {
@@ -26538,9 +26657,9 @@ function enrichClaudeMd(projectRoot, cycleNumber, adapterType, collector, client
26538
26657
 
26539
26658
  \u{1F4DD} **CLAUDE.md enriched** \u2014 added ${tierNames2.join(" + ")} guidance for cycle ${cycleNumber}+ projects.`;
26540
26659
  }
26541
- const claudeMdPath = join15(projectRoot, "CLAUDE.md");
26542
- if (!existsSync9(claudeMdPath)) return "";
26543
- const content = readFileSync9(claudeMdPath, "utf-8");
26660
+ const claudeMdPath = join16(projectRoot, "CLAUDE.md");
26661
+ if (!existsSync10(claudeMdPath)) return "";
26662
+ const content = readFileSync10(claudeMdPath, "utf-8");
26544
26663
  const additions = [];
26545
26664
  if (cycleNumber >= 6 && !content.includes(CLAUDE_MD_ENRICHMENT_SENTINEL_T1)) {
26546
26665
  additions.push(dedupeEnrichmentBlob(content, CLAUDE_MD_TIER_1));
@@ -26549,7 +26668,7 @@ function enrichClaudeMd(projectRoot, cycleNumber, adapterType, collector, client
26549
26668
  additions.push(dedupeEnrichmentBlob(content, CLAUDE_MD_TIER_2));
26550
26669
  }
26551
26670
  if (additions.length === 0) return "";
26552
- writeFileSync3(claudeMdPath, content + additions.join(""), "utf-8");
26671
+ writeFileSync4(claudeMdPath, content + additions.join(""), "utf-8");
26553
26672
  const tierNames = [];
26554
26673
  if (additions.some((a) => a.includes(CLAUDE_MD_ENRICHMENT_SENTINEL_T1))) tierNames.push("Established (batch building, strategy reviews, AD lifecycle)");
26555
26674
  if (additions.some((a) => a.includes(CLAUDE_MD_ENRICHMENT_SENTINEL_T2))) tierNames.push("Mature (idea pipeline, doc registry, advanced patterns)");
@@ -27272,8 +27391,8 @@ ${result.userMessage}
27272
27391
  }
27273
27392
 
27274
27393
  // src/services/scope-brief.ts
27275
- import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync3 } from "fs";
27276
- import { join as join16, dirname as dirname4 } from "path";
27394
+ import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync3 } from "fs";
27395
+ import { join as join17, dirname as dirname4 } from "path";
27277
27396
  import Anthropic from "@anthropic-ai/sdk";
27278
27397
  var SCOPE_BRIEF_SYSTEM = `You are a technical scoping tool. You receive a brief-class task (too large to build directly) and decompose it into a structured scope document.
27279
27398
 
@@ -27328,14 +27447,14 @@ async function runScopeBrief(adapter2, input) {
27328
27447
  }
27329
27448
  const slug = input.taskId.replace(/[^a-z0-9-]/g, "-").toLowerCase();
27330
27449
  const relPath = `docs/scopes/${slug}.md`;
27331
- const absPath = join16(input.projectRoot, relPath);
27450
+ const absPath = join17(input.projectRoot, relPath);
27332
27451
  const docBody = addFrontmatter(docContent, task, input.cycleNumber);
27333
27452
  const collector = new FileWriteCollector();
27334
27453
  if (input.adapterType === "proxy") {
27335
27454
  collector.add({ path: relPath, content: docBody, mode: "overwrite" });
27336
27455
  } else {
27337
27456
  mkdirSync3(dirname4(absPath), { recursive: true });
27338
- writeFileSync4(absPath, docBody, "utf-8");
27457
+ writeFileSync5(absPath, docBody, "utf-8");
27339
27458
  }
27340
27459
  const taskCount = countSubTasks(docContent);
27341
27460
  const summary = buildSummary(task, taskCount);
@@ -28159,19 +28278,19 @@ Its build reports, comments, and history moved with it; the cycle assignment was
28159
28278
 
28160
28279
  // src/services/harness-inventory.ts
28161
28280
  import { readdir as readdir3, readFile as readFile8, stat as stat3 } from "fs/promises";
28162
- import { join as join17 } from "path";
28163
- import { createHash as createHash3 } from "crypto";
28281
+ import { join as join18 } from "path";
28282
+ import { createHash as createHash4 } from "crypto";
28164
28283
  var RECOMMENDED_HOOKS = ["stop-release-check.sh", "claude-md-size-guard.sh"];
28165
28284
  async function computeFingerprint(root) {
28166
28285
  const parts = [];
28167
28286
  for (const sub of [".claude/skills", ".claude/agents", ".claude/hooks"]) {
28168
- const dir = join17(root, sub);
28287
+ const dir = join18(root, sub);
28169
28288
  try {
28170
28289
  const names = (await readdir3(dir)).sort((a, b2) => a.localeCompare(b2));
28171
28290
  for (const name of names) {
28172
28291
  let mtime = "";
28173
28292
  try {
28174
- mtime = String(Math.floor((await stat3(join17(dir, name))).mtimeMs));
28293
+ mtime = String(Math.floor((await stat3(join18(dir, name))).mtimeMs));
28175
28294
  } catch {
28176
28295
  }
28177
28296
  parts.push(`${sub}/${name}:${mtime}`);
@@ -28186,11 +28305,11 @@ async function computeFingerprint(root) {
28186
28305
  } catch {
28187
28306
  parts.push("manifest:none");
28188
28307
  }
28189
- return createHash3("sha256").update(parts.join("|")).digest("hex").slice(0, 16);
28308
+ return createHash4("sha256").update(parts.join("|")).digest("hex").slice(0, 16);
28190
28309
  }
28191
28310
  async function readSkillDescription(skillDir) {
28192
28311
  try {
28193
- const content = await readFile8(join17(skillDir, "SKILL.md"), "utf-8");
28312
+ const content = await readFile8(join18(skillDir, "SKILL.md"), "utf-8");
28194
28313
  const fm = content.match(/^---\n([\s\S]*?)\n---/);
28195
28314
  if (!fm) return void 0;
28196
28315
  const desc = fm[1].match(/^description:\s*[>|]?\s*\n?([\s\S]*?)(?=\n\w+:|\n---|$)/m);
@@ -28210,17 +28329,17 @@ async function scanInventory(root, toolDefs) {
28210
28329
  version = loadManifest().packageVersion;
28211
28330
  } catch {
28212
28331
  }
28213
- const skillsDir = join17(root, ".claude", "skills");
28332
+ const skillsDir = join18(root, ".claude", "skills");
28214
28333
  try {
28215
28334
  const dirents = await readdir3(skillsDir, { withFileTypes: true });
28216
28335
  for (const d of dirents.filter((e) => e.isDirectory())) {
28217
28336
  entries.push({
28218
28337
  kind: "skill",
28219
28338
  name: d.name,
28220
- description: await readSkillDescription(join17(skillsDir, d.name)),
28339
+ description: await readSkillDescription(join18(skillsDir, d.name)),
28221
28340
  version,
28222
28341
  status: stale.has(d.name) ? "stale_fork" : "ok",
28223
- path: join17(".claude", "skills", d.name)
28342
+ path: join18(".claude", "skills", d.name)
28224
28343
  });
28225
28344
  }
28226
28345
  } catch {
@@ -28236,9 +28355,9 @@ async function scanInventory(root, toolDefs) {
28236
28355
  }
28237
28356
  const present = /* @__PURE__ */ new Set();
28238
28357
  try {
28239
- for (const f of (await readdir3(join17(root, ".claude", "hooks"))).filter((n) => n.endsWith(".sh"))) {
28358
+ for (const f of (await readdir3(join18(root, ".claude", "hooks"))).filter((n) => n.endsWith(".sh"))) {
28240
28359
  present.add(f);
28241
- entries.push({ kind: "hook", name: f, status: "ok", path: join17(".claude", "hooks", f) });
28360
+ entries.push({ kind: "hook", name: f, status: "ok", path: join18(".claude", "hooks", f) });
28242
28361
  }
28243
28362
  } catch {
28244
28363
  }
@@ -28678,7 +28797,7 @@ function createServer(adapter2, config2) {
28678
28797
  const __pkgDir = dirname5(__pkgFilename);
28679
28798
  let serverVersion = "unknown";
28680
28799
  try {
28681
- const pkg = JSON.parse(readFileSync10(join18(__pkgDir, "..", "package.json"), "utf-8"));
28800
+ const pkg = JSON.parse(readFileSync11(join19(__pkgDir, "..", "package.json"), "utf-8"));
28682
28801
  serverVersion = pkg.version ?? "unknown";
28683
28802
  } catch {
28684
28803
  }
@@ -28696,7 +28815,7 @@ function createServer(adapter2, config2) {
28696
28815
  }
28697
28816
  const __filename = fileURLToPath3(import.meta.url);
28698
28817
  const __dirname2 = dirname5(__filename);
28699
- const skillsDir = join18(__dirname2, "..", "skills");
28818
+ const skillsDir = join19(__dirname2, "..", "skills");
28700
28819
  function parseSkillFrontmatter(content) {
28701
28820
  const match = content.match(/^---\n([\s\S]*?)\n---/);
28702
28821
  if (!match) return null;
@@ -28714,7 +28833,7 @@ function createServer(adapter2, config2) {
28714
28833
  const mdFiles = files.filter((f) => f.endsWith(".md"));
28715
28834
  const prompts = [];
28716
28835
  for (const file of mdFiles) {
28717
- const content = await readFile9(join18(skillsDir, file), "utf-8");
28836
+ const content = await readFile9(join19(skillsDir, file), "utf-8");
28718
28837
  const meta = parseSkillFrontmatter(content);
28719
28838
  if (meta) {
28720
28839
  prompts.push({ name: meta.name, description: meta.description });
@@ -28730,7 +28849,7 @@ function createServer(adapter2, config2) {
28730
28849
  try {
28731
28850
  const files = await readdir4(skillsDir);
28732
28851
  for (const file of files.filter((f) => f.endsWith(".md"))) {
28733
- const content = await readFile9(join18(skillsDir, file), "utf-8");
28852
+ const content = await readFile9(join19(skillsDir, file), "utf-8");
28734
28853
  const meta = parseSkillFrontmatter(content);
28735
28854
  if (meta?.name === name) {
28736
28855
  const body = content.replace(/^---\n[\s\S]*?\n---\n*/, "");
@@ -29400,7 +29519,7 @@ async function dispatchRequest(args) {
29400
29519
  var __dirname = dirname6(fileURLToPath4(import.meta.url));
29401
29520
  var pkgVersion = "unknown";
29402
29521
  try {
29403
- const pkg = JSON.parse(readFileSync15(join23(__dirname, "..", "package.json"), "utf-8"));
29522
+ const pkg = JSON.parse(readFileSync16(join24(__dirname, "..", "package.json"), "utf-8"));
29404
29523
  pkgVersion = pkg.version;
29405
29524
  } catch {
29406
29525
  }