@papi-ai/server 0.7.53 → 0.7.55

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
@@ -1103,7 +1103,7 @@ function isEnabled() {
1103
1103
  function reportTelemetryEmitFailure(reason, ctx) {
1104
1104
  consecutiveTelemetryFailures += 1;
1105
1105
  console.error(
1106
- `[telemetry] emit failed (${reason}) tool=${ctx.toolName} event=${ctx.eventType} project=${ctx.projectId}`
1106
+ `[telemetry] emit failed (${reason}) tool=${ctx.toolName} event=${ctx.eventType} project=${ctx.projectId ?? "(none)"}`
1107
1107
  );
1108
1108
  if (consecutiveTelemetryFailures >= TELEMETRY_BLACKOUT_THRESHOLD) {
1109
1109
  console.error(
@@ -1120,11 +1120,11 @@ function emitTelemetryEvent(event) {
1120
1120
  if (!apiKey) return;
1121
1121
  const endpoint = process.env["PAPI_DATA_ENDPOINT"] ?? DEFAULT_TELEMETRY_ENDPOINT;
1122
1122
  const body = {
1123
- projectId: event.project_id,
1124
1123
  toolName: event.tool_name,
1125
1124
  eventType: event.event_type,
1126
1125
  metadata: event.metadata ?? {}
1127
1126
  };
1127
+ if (event.project_id) body["projectId"] = event.project_id;
1128
1128
  const ctx = {
1129
1129
  projectId: event.project_id,
1130
1130
  toolName: event.tool_name,
@@ -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
@@ -1672,18 +1678,19 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
1672
1678
  */
1673
1679
  emitTelemetry(event) {
1674
1680
  const ctx = { projectId: event.projectId, toolName: event.toolName, eventType: event.eventType };
1681
+ const payload = {
1682
+ toolName: event.toolName,
1683
+ eventType: event.eventType,
1684
+ metadata: event.metadata ?? {}
1685
+ };
1686
+ if (event.projectId) payload["projectId"] = event.projectId;
1675
1687
  fetch(`${this.endpoint}/telemetry`, {
1676
1688
  method: "POST",
1677
1689
  headers: {
1678
1690
  "Content-Type": "application/json",
1679
1691
  "Authorization": `Bearer ${this.apiKey}`
1680
1692
  },
1681
- body: JSON.stringify({
1682
- projectId: event.projectId,
1683
- toolName: event.toolName,
1684
- eventType: event.eventType,
1685
- metadata: event.metadata ?? {}
1686
- }),
1693
+ body: JSON.stringify(payload),
1687
1694
  signal: AbortSignal.timeout(5e3)
1688
1695
  }).then((res) => {
1689
1696
  if (res.ok) noteTelemetryEmitSuccess();
@@ -4227,9 +4234,9 @@ __export(doctor_exports, {
4227
4234
  __testing: () => __testing,
4228
4235
  runDoctor: () => runDoctor
4229
4236
  });
4230
- import { existsSync as existsSync10, readFileSync as readFileSync11 } from "fs";
4237
+ import { existsSync as existsSync11, readFileSync as readFileSync12 } from "fs";
4231
4238
  import { homedir as homedir4 } from "os";
4232
- import { join as join19 } from "path";
4239
+ import { join as join20 } from "path";
4233
4240
  function redact(name, value) {
4234
4241
  if (!value) return "(empty)";
4235
4242
  if (SECRET_VARS.has(name)) {
@@ -4240,14 +4247,14 @@ function redact(name, value) {
4240
4247
  }
4241
4248
  function findMcpJson() {
4242
4249
  const candidates = [
4243
- join19(process.cwd(), ".mcp.json"),
4244
- join19(homedir4(), ".claude", ".mcp.json"),
4245
- join19(homedir4(), ".mcp.json")
4250
+ join20(process.cwd(), ".mcp.json"),
4251
+ join20(homedir4(), ".claude", ".mcp.json"),
4252
+ join20(homedir4(), ".mcp.json")
4246
4253
  ];
4247
4254
  for (const path7 of candidates) {
4248
- if (!existsSync10(path7)) continue;
4255
+ if (!existsSync11(path7)) continue;
4249
4256
  try {
4250
- const raw = readFileSync11(path7, "utf-8");
4257
+ const raw = readFileSync12(path7, "utf-8");
4251
4258
  const parsed = JSON.parse(raw);
4252
4259
  const papiEntry = parsed.papi ?? parsed.mcpServers?.papi;
4253
4260
  if (!papiEntry) continue;
@@ -4521,17 +4528,17 @@ __export(reset_exports, {
4521
4528
  removePapiEntry: () => removePapiEntry,
4522
4529
  runReset: () => runReset
4523
4530
  });
4524
- import { existsSync as existsSync11, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "fs";
4531
+ import { existsSync as existsSync12, readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
4525
4532
  import { homedir as homedir5 } from "os";
4526
- import { join as join20 } from "path";
4533
+ import { join as join21 } from "path";
4527
4534
  import { createInterface } from "readline/promises";
4528
4535
  function findResetTarget() {
4529
4536
  for (const path7 of CANDIDATE_PATHS()) {
4530
- if (!existsSync11(path7)) continue;
4537
+ if (!existsSync12(path7)) continue;
4531
4538
  let raw;
4532
4539
  let parsed;
4533
4540
  try {
4534
- raw = readFileSync12(path7, "utf-8");
4541
+ raw = readFileSync13(path7, "utf-8");
4535
4542
  parsed = JSON.parse(raw);
4536
4543
  } catch {
4537
4544
  continue;
@@ -4602,7 +4609,7 @@ async function runReset(args = []) {
4602
4609
  }
4603
4610
  }
4604
4611
  try {
4605
- writeFileSync5(target.path, removePapiEntry(target), "utf-8");
4612
+ writeFileSync6(target.path, removePapiEntry(target), "utf-8");
4606
4613
  process.stdout.write(`
4607
4614
  \u2713 Removed papi entry from ${target.path}
4608
4615
  `);
@@ -4619,9 +4626,9 @@ var init_reset = __esm({
4619
4626
  "src/cli/reset.ts"() {
4620
4627
  "use strict";
4621
4628
  CANDIDATE_PATHS = () => [
4622
- join20(process.cwd(), ".mcp.json"),
4623
- join20(homedir5(), ".claude", ".mcp.json"),
4624
- join20(homedir5(), ".mcp.json")
4629
+ join21(process.cwd(), ".mcp.json"),
4630
+ join21(homedir5(), ".claude", ".mcp.json"),
4631
+ join21(homedir5(), ".mcp.json")
4625
4632
  ];
4626
4633
  }
4627
4634
  });
@@ -4632,9 +4639,9 @@ __export(audit_exports, {
4632
4639
  __testing: () => __testing2,
4633
4640
  runAudit: () => runAudit
4634
4641
  });
4635
- import { existsSync as existsSync12, readFileSync as readFileSync13, readdirSync as readdirSync7 } from "fs";
4642
+ import { existsSync as existsSync13, readFileSync as readFileSync14, readdirSync as readdirSync7 } from "fs";
4636
4643
  import { homedir as homedir6 } from "os";
4637
- import { join as join21 } from "path";
4644
+ import { join as join22 } from "path";
4638
4645
  function safeListDirs(dir) {
4639
4646
  try {
4640
4647
  return readdirSync7(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort((a, b2) => a.localeCompare(b2));
@@ -4650,10 +4657,10 @@ function safeListFiles(dir, ext) {
4650
4657
  }
4651
4658
  }
4652
4659
  function readMcp(projectPath) {
4653
- const path7 = join21(projectPath, ".mcp.json");
4654
- if (!existsSync12(path7)) return { servers: [] };
4660
+ const path7 = join22(projectPath, ".mcp.json");
4661
+ if (!existsSync13(path7)) return { servers: [] };
4655
4662
  try {
4656
- const parsed = JSON.parse(readFileSync13(path7, "utf-8"));
4663
+ const parsed = JSON.parse(readFileSync14(path7, "utf-8"));
4657
4664
  const mcpServers = parsed.mcpServers ?? {};
4658
4665
  const servers = Object.keys(mcpServers);
4659
4666
  if (parsed.papi && !servers.includes("papi")) servers.push("papi");
@@ -4685,18 +4692,18 @@ function auditProjectSync(projectPath, name) {
4685
4692
  path: projectPath,
4686
4693
  papiProjectId,
4687
4694
  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")
4695
+ skills: safeListDirs(join22(projectPath, ".claude", "skills")),
4696
+ agentSkills: safeListDirs(join22(projectPath, ".agents", "skills")),
4697
+ agents: safeListFiles(join22(projectPath, ".claude", "agents"), ".md"),
4698
+ hooks: safeListFiles(join22(projectPath, ".claude", "hooks"), ".sh")
4692
4699
  };
4693
4700
  }
4694
4701
  function discoverProjects() {
4695
4702
  const out = [];
4696
4703
  for (const root of PROJECT_ROOTS) {
4697
4704
  for (const name of safeListDirs(root)) {
4698
- const path7 = join21(root, name);
4699
- if (existsSync12(join21(path7, ".mcp.json")) || existsSync12(join21(path7, ".claude"))) {
4705
+ const path7 = join22(root, name);
4706
+ if (existsSync13(join22(path7, ".mcp.json")) || existsSync13(join22(path7, ".claude"))) {
4700
4707
  out.push({ name, path: path7 });
4701
4708
  }
4702
4709
  }
@@ -4707,9 +4714,9 @@ function readGlobalSkills() {
4707
4714
  return safeListDirs(GLOBAL_SKILLS_DIR);
4708
4715
  }
4709
4716
  function readGlobalMcpServers() {
4710
- if (!existsSync12(GLOBAL_CLAUDE_JSON)) return [];
4717
+ if (!existsSync13(GLOBAL_CLAUDE_JSON)) return [];
4711
4718
  try {
4712
- const parsed = JSON.parse(readFileSync13(GLOBAL_CLAUDE_JSON, "utf-8"));
4719
+ const parsed = JSON.parse(readFileSync14(GLOBAL_CLAUDE_JSON, "utf-8"));
4713
4720
  const servers = parsed.mcpServers ?? {};
4714
4721
  return Object.keys(servers).sort((a, b2) => a.localeCompare(b2));
4715
4722
  } catch {
@@ -4861,9 +4868,9 @@ var PROJECT_ROOTS, GLOBAL_SKILLS_DIR, GLOBAL_CLAUDE_JSON, IDLE_WINDOW_DAYS, GLOB
4861
4868
  var init_audit = __esm({
4862
4869
  "src/cli/audit.ts"() {
4863
4870
  "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");
4871
+ PROJECT_ROOTS = [join22(homedir6(), "Ai-App-Projects"), join22(homedir6(), "android-projects")];
4872
+ GLOBAL_SKILLS_DIR = join22(homedir6(), ".claude", "skills");
4873
+ GLOBAL_CLAUDE_JSON = join22(homedir6(), ".claude.json");
4867
4874
  IDLE_WINDOW_DAYS = 30;
4868
4875
  GLOBALIZE_THRESHOLD = 3;
4869
4876
  __testing2 = { readMcp, computeFlags, formatReport: formatReport2, discoverProjects, auditProjectSync };
@@ -4875,8 +4882,8 @@ var setup_exports = {};
4875
4882
  __export(setup_exports, {
4876
4883
  runSetup: () => runSetup
4877
4884
  });
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";
4885
+ import { existsSync as existsSync14, readFileSync as readFileSync15, writeFileSync as writeFileSync7, chmodSync as chmodSync2, statSync as statSync7 } from "fs";
4886
+ import { join as join23 } from "path";
4880
4887
  function baseUrl() {
4881
4888
  const fromEnv = process.env["PAPI_HOST"] ?? process.env["PAPI_BASE_URL"];
4882
4889
  if (fromEnv) return fromEnv.replace(/\/$/, "");
@@ -4908,11 +4915,11 @@ function sleep(ms) {
4908
4915
  return new Promise((resolve3) => setTimeout(resolve3, ms));
4909
4916
  }
4910
4917
  function writeMcpJson(opts) {
4911
- const path7 = join22(process.cwd(), ".mcp.json");
4918
+ const path7 = join23(process.cwd(), ".mcp.json");
4912
4919
  let parsed = {};
4913
- if (existsSync13(path7)) {
4920
+ if (existsSync14(path7)) {
4914
4921
  try {
4915
- parsed = JSON.parse(readFileSync14(path7, "utf-8"));
4922
+ parsed = JSON.parse(readFileSync15(path7, "utf-8"));
4916
4923
  } catch {
4917
4924
  throw new Error(`.mcp.json at ${path7} is not valid JSON. Fix it or remove it before re-running setup.`);
4918
4925
  }
@@ -4933,9 +4940,9 @@ function writeMcpJson(opts) {
4933
4940
  }
4934
4941
  mcpServers.papi = papiEntry;
4935
4942
  parsed.mcpServers = mcpServers;
4936
- writeFileSync6(path7, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
4943
+ writeFileSync7(path7, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
4937
4944
  try {
4938
- const mode = statSync6(path7).mode & 511;
4945
+ const mode = statSync7(path7).mode & 511;
4939
4946
  if (mode !== 384) chmodSync2(path7, 384);
4940
4947
  } catch {
4941
4948
  }
@@ -5043,8 +5050,8 @@ var init_setup = __esm({
5043
5050
  });
5044
5051
 
5045
5052
  // src/index.ts
5046
- import { readFileSync as readFileSync15 } from "fs";
5047
- import { dirname as dirname6, join as join23 } from "path";
5053
+ import { readFileSync as readFileSync16 } from "fs";
5054
+ import { dirname as dirname6, join as join24 } from "path";
5048
5055
  import { fileURLToPath as fileURLToPath4 } from "url";
5049
5056
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5050
5057
  import { Server as Server2 } from "@modelcontextprotocol/sdk/server/index.js";
@@ -8053,9 +8060,9 @@ Check PAPI_PROJECT_ID in your .mcp.json config. Find your project ID in the PAPI
8053
8060
  }
8054
8061
 
8055
8062
  // src/server.ts
8056
- import { readFileSync as readFileSync10 } from "fs";
8063
+ import { readFileSync as readFileSync11 } from "fs";
8057
8064
  import { access as access4, readdir as readdir4, readFile as readFile9 } from "fs/promises";
8058
- import { join as join18, dirname as dirname5 } from "path";
8065
+ import { join as join19, dirname as dirname5 } from "path";
8059
8066
  import { fileURLToPath as fileURLToPath3 } from "url";
8060
8067
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
8061
8068
  import {
@@ -8317,16 +8324,41 @@ ${deferredSection}` : body;
8317
8324
  if (deferredSection) sections.push(deferredSection);
8318
8325
  return sections.join("\n\n");
8319
8326
  }
8320
- function formatCandidateTaskFullNotes(tasks) {
8327
+ var PLAN_FULL_NOTES_BUDGET_BYTES = Number(process.env.PAPI_PLAN_NOTES_BUDGET) || 6e4;
8328
+ var priorityRank = (p) => p ? PRIORITY_RANK[p] ?? 4 : 4;
8329
+ function formatCandidateTaskFullNotes(tasks, budgetBytes = PLAN_FULL_NOTES_BUDGET_BYTES) {
8321
8330
  const candidates = tasks.filter((t) => !PLAN_EXCLUDED_STATUSES.has(t.status)).filter((t) => (t.notes?.length ?? 0) > PLAN_NOTES_MAX_LENGTH);
8322
8331
  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");
8332
+ 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);
8333
+ const included = [];
8334
+ const elided = [];
8335
+ let spent = 0;
8336
+ for (const t of ranked) {
8337
+ const entry = `**${t.id}** \u2014 ${t.title}
8338
+ ${t.notes}`;
8339
+ const cost = Buffer.byteLength(entry, "utf-8");
8340
+ if (included.length > 0 && spent + cost > budgetBytes) {
8341
+ elided.push(t);
8342
+ continue;
8343
+ }
8344
+ included.push(entry);
8345
+ spent += cost;
8346
+ }
8347
+ 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.`;
8348
+ const parts = [header, "", ...included];
8349
+ if (elided.length > 0) {
8350
+ parts.push(
8351
+ "",
8352
+ `### ELIDED \u2014 ${elided.length} lower-priority candidate(s) omitted to keep this payload under ${Math.round(budgetBytes / 1024)} KB`,
8353
+ "",
8354
+ "Full notes were included above for the highest-priority candidates only. The following tasks have long notes that are NOT shown here:",
8355
+ "",
8356
+ elided.map((t) => `- ${t.id} (${t.priority ?? "unranked"})`).join("\n"),
8357
+ "",
8358
+ `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.`
8359
+ );
8360
+ }
8361
+ return parts.join("\n\n");
8330
8362
  }
8331
8363
  function formatBoardForReview(tasks) {
8332
8364
  if (tasks.length === 0) return "No tasks on the board.";
@@ -8357,6 +8389,32 @@ function effortWeight(size2) {
8357
8389
  return 3;
8358
8390
  }
8359
8391
  }
8392
+ function computeCycleEffort(cycleTaskRows, cycleReports) {
8393
+ if (cycleTaskRows && cycleTaskRows.length > 0) {
8394
+ const done = cycleTaskRows.filter((t) => t.status === "Done");
8395
+ const reportByTask = /* @__PURE__ */ new Map();
8396
+ for (const r of cycleReports) if (r.taskId) reportByTask.set(r.taskId, r);
8397
+ return {
8398
+ completed: done.length,
8399
+ total: cycleTaskRows.length,
8400
+ plannedPoints: done.reduce((s, t) => s + effortWeight(t.complexity), 0),
8401
+ deliveredPoints: done.reduce((s, t) => {
8402
+ const actual = reportByTask.get(t.id)?.actualEffort;
8403
+ return s + effortWeight(actual || t.complexity);
8404
+ }, 0)
8405
+ };
8406
+ }
8407
+ const completed = cycleReports.filter((r) => r.completed === "Yes").length;
8408
+ return {
8409
+ completed,
8410
+ total: cycleReports.length,
8411
+ plannedPoints: cycleReports.reduce((s, r) => s + effortWeight(r.estimatedEffort || r.actualEffort), 0),
8412
+ deliveredPoints: cycleReports.reduce((s, r) => s + effortWeight(r.actualEffort || r.estimatedEffort), 0)
8413
+ };
8414
+ }
8415
+ function velocityPoints(v) {
8416
+ return v.deliveredPoints ?? v.effortPoints;
8417
+ }
8360
8418
  function computeSnapshotsFromBuildReports(reports, tasks) {
8361
8419
  const reportsByCycle = /* @__PURE__ */ new Map();
8362
8420
  for (const r of reports) {
@@ -8380,24 +8438,19 @@ function computeSnapshotsFromBuildReports(reports, tasks) {
8380
8438
  const withEffort = cycleReports.filter((r) => r.estimatedEffort && r.actualEffort);
8381
8439
  const accurate = withEffort.filter((r) => r.estimatedEffort === r.actualEffort).length;
8382
8440
  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
- }
8441
+ const { completed, total, plannedPoints, deliveredPoints } = computeCycleEffort(cycleTaskRows, cycleReports);
8396
8442
  snapshots.push({
8397
8443
  cycle: sn,
8398
8444
  date: (/* @__PURE__ */ new Date()).toISOString(),
8399
8445
  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 }]
8446
+ velocity: [{
8447
+ cycle: sn,
8448
+ completed,
8449
+ partial: 0,
8450
+ failed: Math.max(0, total - completed),
8451
+ effortPoints: plannedPoints,
8452
+ deliveredPoints
8453
+ }]
8401
8454
  });
8402
8455
  }
8403
8456
  snapshots.sort((a, b2) => a.cycle - b2.cycle);
@@ -8409,13 +8462,25 @@ function formatCycleMetrics(snapshots) {
8409
8462
  const allVelocities = snapshots.flatMap((s) => s.velocity).sort((a, b2) => a.cycle - b2.cycle);
8410
8463
  const recentVelocities = allVelocities.slice(-5);
8411
8464
  if (recentVelocities.length > 0) {
8412
- const avgEffort = Math.round(
8465
+ const avgPlanned = Math.round(
8413
8466
  recentVelocities.reduce((sum, v) => sum + v.effortPoints, 0) / recentVelocities.length * 10
8414
8467
  ) / 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.`);
8468
+ const avgDelivered = Math.round(
8469
+ recentVelocities.reduce((sum, v) => sum + velocityPoints(v), 0) / recentVelocities.length * 10
8470
+ ) / 10;
8471
+ const anyDelivered = recentVelocities.some((v) => v.deliveredPoints !== void 0);
8472
+ lines.push("**Velocity \u2014 delivered effort points (size the next cycle from this)**");
8473
+ lines.push(
8474
+ `- Last ${recentVelocities.length} cycles: ` + recentVelocities.map((v) => v.deliveredPoints !== void 0 ? `S${v.cycle}=${v.deliveredPoints} delivered/${v.effortPoints} planned` : `S${v.cycle}=${v.effortPoints} planned (no delivered data \u2014 using planned)`).join(", ")
8475
+ );
8476
+ lines.push(`- Velocity (avg delivered): ${avgDelivered} pts/cycle (XS=1, S=2, M=3, L=5, XL=8)`);
8477
+ if (anyDelivered) {
8478
+ const delta = Math.round((avgDelivered - avgPlanned) * 10) / 10;
8479
+ const sign = delta > 0 ? "+" : "";
8480
+ const read = delta === 0 ? "delivered matches planned" : delta > 0 ? "cycles cost MORE than scoped (under-scoping)" : "cycles cost LESS than scoped (over-scoping)";
8481
+ lines.push(`- Scope accuracy (drift vs planned): planned averaged ${avgPlanned} pts/cycle \u2014 ${sign}${delta} pts, ${read}.`);
8482
+ }
8483
+ lines.push(`- Size cycles on what the selected tasks actually cost \u2014 delivered is the signal, planned is the estimate that was wrong.`);
8419
8484
  }
8420
8485
  return lines.join("\n");
8421
8486
  }
@@ -8429,7 +8494,9 @@ function formatDerivedMetrics(snapshots, backlogTasks) {
8429
8494
  const latest = recent[recent.length - 1];
8430
8495
  lines.push("**Cycle History (5-cycle avg)**");
8431
8496
  lines.push(`- Average: ${avg.toFixed(1)} tasks/cycle`);
8432
- lines.push(`- Latest (Cycle ${latest.cycle}): ${latest.completed} tasks, ${latest.effortPoints} effort points`);
8497
+ const latestDelivered = velocityPoints(latest);
8498
+ const plannedNote = latest.deliveredPoints !== void 0 && latest.deliveredPoints !== latest.effortPoints ? ` (planned ${latest.effortPoints})` : "";
8499
+ lines.push(`- Latest (Cycle ${latest.cycle}): ${latest.completed} tasks, ${latestDelivered} effort points delivered${plannedNote}`);
8433
8500
  }
8434
8501
  }
8435
8502
  const activeTasks = backlogTasks.filter(
@@ -12490,6 +12557,9 @@ async function preparePlan(adapter2, config2, filters, focus, force, handoffsOnl
12490
12557
  const prepareScope = await resolvePlanScope(adapter2, config2);
12491
12558
  const { mode, cycleNumber, strategyReviewWarning } = await validateAndPrepare(adapter2, force, prepareScope.callerUserId);
12492
12559
  const validateMs = t();
12560
+ const incomingCycle = cycleNumber + 1;
12561
+ tracker?.setStreamScope({ cycle: incomingCycle });
12562
+ await recordPlanPrepareStep(tracker, incomingCycle, "health-check");
12493
12563
  if (handoffsOnly) {
12494
12564
  tracker?.mark("handoffs_only_assemble");
12495
12565
  t = startTimer();
@@ -12534,6 +12604,8 @@ async function preparePlan(adapter2, config2, filters, focus, force, handoffsOnl
12534
12604
  t = startTimer();
12535
12605
  const { context, contextHashes } = await assembleContext(adapter2, mode, config2, filters, focus);
12536
12606
  const assembleMs = t();
12607
+ await recordPlanPrepareStep(tracker, incomingCycle, "inbox-triage");
12608
+ await recordPlanPrepareStep(tracker, incomingCycle, "board-integrity");
12537
12609
  const TEMPLATE_MARKER2 = "*Describe your project's core value proposition here.*";
12538
12610
  if (mode !== "bootstrap" && context.productBrief.includes(TEMPLATE_MARKER2)) {
12539
12611
  throw new Error("TEMPLATE_BRIEF");
@@ -12551,6 +12623,7 @@ async function preparePlan(adapter2, config2, filters, focus, force, handoffsOnl
12551
12623
  }
12552
12624
  const scanMs = t();
12553
12625
  console.error(`[plan-perf] codebaseScan: ${scanMs}ms`);
12626
+ await recordPlanPrepareStep(tracker, incomingCycle, "maturity-gate");
12554
12627
  tracker?.mark("build_user_message");
12555
12628
  t = startTimer();
12556
12629
  const foundation = await buildProjectFoundation(adapter2);
@@ -12586,7 +12659,13 @@ async function preparePlan(adapter2, config2, filters, focus, force, handoffsOnl
12586
12659
  contextHashes
12587
12660
  };
12588
12661
  }
12589
- var PLAN_STAGE_STEPS = ["health-check", "inbox-triage", "board-integrity", "maturity-gate", "recommendation", "dependency-chain"];
12662
+ var PLAN_PREPARE_STEPS = ["health-check", "inbox-triage", "board-integrity", "maturity-gate"];
12663
+ var PLAN_APPLY_STEPS = ["recommendation", "dependency-chain"];
12664
+ var PLAN_STAGE_STEPS = [...PLAN_PREPARE_STEPS, ...PLAN_APPLY_STEPS];
12665
+ async function recordPlanPrepareStep(tracker, incomingCycleNumber, step) {
12666
+ if (!tracker) return;
12667
+ await tracker.recordStep(step, { cycle: incomingCycleNumber, stage: "plan" });
12668
+ }
12590
12669
  async function streamPlanStageSteps(tracker, newCycleNumber) {
12591
12670
  tracker.setStreamScope({ cycle: newCycleNumber });
12592
12671
  for (const step of PLAN_STAGE_STEPS) {
@@ -13107,6 +13186,49 @@ var PerCallerCache = class {
13107
13186
  }
13108
13187
  };
13109
13188
 
13189
+ // src/lib/plan-prepare-store.ts
13190
+ import { createHash as createHash2 } from "crypto";
13191
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync, existsSync, statSync } from "fs";
13192
+ import { tmpdir } from "os";
13193
+ import { join as join3 } from "path";
13194
+ var SPILL_TTL_MS = 24 * 60 * 60 * 1e3;
13195
+ var DEFAULT_CALLER_KEY3 = "__default__";
13196
+ function spillPath(projectId, callerKey) {
13197
+ const id = createHash2("sha256").update(`${projectId ?? "no-project"}|${callerKey ?? DEFAULT_CALLER_KEY3}`).digest("hex").slice(0, 16);
13198
+ return join3(tmpdir(), `papi-plan-prepare-${id}.json`);
13199
+ }
13200
+ function savePrepareSpill(projectId, callerKey, state) {
13201
+ try {
13202
+ writeFileSync2(
13203
+ spillPath(projectId, callerKey),
13204
+ JSON.stringify({ savedAt: Date.now(), state }),
13205
+ { mode: 384 }
13206
+ );
13207
+ } catch {
13208
+ }
13209
+ }
13210
+ function loadPrepareSpill(projectId, callerKey) {
13211
+ const path7 = spillPath(projectId, callerKey);
13212
+ try {
13213
+ if (!existsSync(path7)) return void 0;
13214
+ if (Date.now() - statSync(path7).mtimeMs > SPILL_TTL_MS) {
13215
+ clearPrepareSpill(projectId, callerKey);
13216
+ return void 0;
13217
+ }
13218
+ const parsed = JSON.parse(readFileSync2(path7, "utf-8"));
13219
+ return parsed.state;
13220
+ } catch {
13221
+ return void 0;
13222
+ }
13223
+ }
13224
+ function clearPrepareSpill(projectId, callerKey) {
13225
+ try {
13226
+ const path7 = spillPath(projectId, callerKey);
13227
+ if (existsSync(path7)) unlinkSync(path7);
13228
+ } catch {
13229
+ }
13230
+ }
13231
+
13110
13232
  // src/tools/plan.ts
13111
13233
  var planPrepareCache = new PerCallerCache();
13112
13234
  var planTool = {
@@ -13274,7 +13396,7 @@ async function handlePlan(adapter2, config2, args) {
13274
13396
  const planMode = args.plan_mode || "full";
13275
13397
  const rawCycleNumber = args.cycle_number != null ? Number(args.cycle_number) : NaN;
13276
13398
  const strategyReviewWarning = args.strategy_review_warning || "";
13277
- const prep = planPrepareCache.peek(callerKey);
13399
+ const prep = planPrepareCache.peek(callerKey) ?? loadPrepareSpill(adapter2.getProjectId?.(), callerKey);
13278
13400
  const contextHashes = prep?.contextHashes;
13279
13401
  const inputContext = prep?.userMessage;
13280
13402
  const contextBytes = prep?.contextBytes;
@@ -13302,6 +13424,7 @@ async function handlePlan(adapter2, config2, args) {
13302
13424
  }
13303
13425
  const cycleNumber = newCycleNumber - 1;
13304
13426
  planPrepareCache.clear(callerKey);
13427
+ clearPrepareSpill(adapter2.getProjectId?.(), callerKey);
13305
13428
  let utilisation;
13306
13429
  if (inputContext) {
13307
13430
  try {
@@ -13332,13 +13455,15 @@ async function handlePlan(adapter2, config2, args) {
13332
13455
  }
13333
13456
  const skipHandoffs = args.skip_handoffs === true;
13334
13457
  const result = await preparePlan(adapter2, config2, filters, focus, force, handoffsOnly, skipHandoffs, tracker);
13335
- planPrepareCache.set(callerKey, {
13458
+ const prepareState = {
13336
13459
  contextHashes: result.contextHashes,
13337
13460
  userMessage: result.userMessage,
13338
13461
  contextBytes: result.contextBytes,
13339
13462
  cycleNumber: result.cycleNumber,
13340
13463
  skipHandoffs: skipHandoffs || void 0
13341
- });
13464
+ };
13465
+ planPrepareCache.set(callerKey, prepareState);
13466
+ savePrepareSpill(adapter2.getProjectId?.(), callerKey, prepareState);
13342
13467
  const autoDispatchEnabled = process.env.PAPI_AUTO_DISPATCH !== "false";
13343
13468
  const autoDispatchThreshold = 50 * 1024;
13344
13469
  let dispatch;
@@ -13422,10 +13547,10 @@ ${result.userMessage}
13422
13547
  }
13423
13548
 
13424
13549
  // src/services/strategy.ts
13425
- import { randomUUID as randomUUID10, createHash as createHash2 } from "crypto";
13550
+ import { randomUUID as randomUUID10, createHash as createHash3 } from "crypto";
13426
13551
  import { execFileSync as execFileSync2 } from "child_process";
13427
- import { existsSync, readdirSync, statSync } from "fs";
13428
- import { join as join3 } from "path";
13552
+ import { existsSync as existsSync2, readdirSync, statSync as statSync2 } from "fs";
13553
+ import { join as join4 } from "path";
13429
13554
  import { homedir as homedir2 } from "os";
13430
13555
 
13431
13556
  // src/lib/hosted-mode.ts
@@ -13953,6 +14078,7 @@ function extractTrendPoints(snapshots) {
13953
14078
  matchRate: accuracyRow.matchRate,
13954
14079
  bias: accuracyRow.bias,
13955
14080
  effortPoints: velocityRow.effortPoints,
14081
+ deliveredPoints: velocityPoints(velocityRow),
13956
14082
  completed: velocityRow.completed
13957
14083
  });
13958
14084
  }
@@ -13966,7 +14092,8 @@ function generateValueReport(snapshots) {
13966
14092
  const first = recent[0];
13967
14093
  const last = recent[recent.length - 1];
13968
14094
  const matchRateDelta = last.matchRate - first.matchRate;
13969
- const effortDelta = last.effortPoints - first.effortPoints;
14095
+ const effortDelta = last.deliveredPoints - first.deliveredPoints;
14096
+ const plannedDelta = last.effortPoints - first.effortPoints;
13970
14097
  const completedDelta = last.completed - first.completed;
13971
14098
  const biasTrend = assessBiasTrend(first.bias, last.bias);
13972
14099
  const lines = [];
@@ -13975,7 +14102,8 @@ function generateValueReport(snapshots) {
13975
14102
  lines.push("");
13976
14103
  lines.push(`- **Scope accuracy:** ${first.matchRate}% \u2192 ${last.matchRate}% (${formatDelta(matchRateDelta, "pp")})`);
13977
14104
  lines.push(`- **Estimation bias:** ${formatBias(first.bias)} \u2192 ${formatBias(last.bias)} (${biasTrend})`);
13978
- lines.push(`- **Velocity:** ${first.effortPoints} \u2192 ${last.effortPoints} effort points/cycle (${formatDelta(effortDelta, "")})`);
14105
+ lines.push(`- **Velocity (delivered):** ${first.deliveredPoints} \u2192 ${last.deliveredPoints} effort points/cycle (${formatDelta(effortDelta, "")})`);
14106
+ lines.push(`- **Planned (for drift):** ${first.effortPoints} \u2192 ${last.effortPoints} effort points/cycle (${formatDelta(plannedDelta, "")})`);
13979
14107
  lines.push(`- **Throughput:** ${first.completed} \u2192 ${last.completed} tasks/cycle (${formatDelta(completedDelta, "")})`);
13980
14108
  return lines.join("\n");
13981
14109
  }
@@ -14371,7 +14499,7 @@ async function assembleContext2(adapter2, cycleNumber, cyclesSinceLastReview, pr
14371
14499
  try {
14372
14500
  const fullCanvasText = formatDiscoveryCanvas(canvas);
14373
14501
  if (fullCanvasText) {
14374
- const canvasHash = createHash2("md5").update(fullCanvasText).digest("hex");
14502
+ const canvasHash = createHash3("md5").update(fullCanvasText).digest("hex");
14375
14503
  const lastReview = previousStrategyReviews?.[0];
14376
14504
  const prevHash = lastReview?.structuredData?.canvasHash;
14377
14505
  if (prevHash && prevHash === canvasHash) {
@@ -14441,12 +14569,12 @@ ${lines.join("\n")}`;
14441
14569
  }
14442
14570
  let recentPlansText;
14443
14571
  try {
14444
- const plansDir = join3(homedir2(), ".claude", "plans");
14445
- if (existsSync(plansDir)) {
14572
+ const plansDir = join4(homedir2(), ".claude", "plans");
14573
+ if (existsSync2(plansDir)) {
14446
14574
  const lastReviewDate = previousStrategyReviews?.[0]?.createdAt ? new Date(previousStrategyReviews[0].createdAt) : /* @__PURE__ */ new Date(0);
14447
14575
  const planFiles = readdirSync(plansDir).filter((f) => f.endsWith(".md")).map((f) => {
14448
- const fullPath = join3(plansDir, f);
14449
- const stat4 = statSync(fullPath);
14576
+ const fullPath = join4(plansDir, f);
14577
+ const stat4 = statSync2(fullPath);
14450
14578
  return { name: f, modified: stat4.mtime, size: stat4.size };
14451
14579
  }).filter((f) => f.modified > lastReviewDate).sort((a, b2) => b2.modified.getTime() - a.modified.getTime()).slice(0, 15);
14452
14580
  if (planFiles.length > 0) {
@@ -14462,15 +14590,15 @@ ${lines.join("\n")}`;
14462
14590
  }
14463
14591
  let unregisteredDocsText;
14464
14592
  try {
14465
- const docsDir = join3(projectRoot, "docs");
14466
- if (hasLocalWorkspace() && existsSync(docsDir)) {
14593
+ const docsDir = join4(projectRoot, "docs");
14594
+ if (hasLocalWorkspace() && existsSync2(docsDir)) {
14467
14595
  const registeredPaths = new Set(
14468
14596
  (registeredDocs ?? []).map((d) => d.path).filter(Boolean)
14469
14597
  );
14470
14598
  const allDocFiles = [];
14471
14599
  const scanDir = (dir, prefix) => {
14472
14600
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
14473
- if (entry.isDirectory()) scanDir(join3(dir, entry.name), `${prefix}${entry.name}/`);
14601
+ if (entry.isDirectory()) scanDir(join4(dir, entry.name), `${prefix}${entry.name}/`);
14474
14602
  else if (entry.name.endsWith(".md")) allDocFiles.push(`${prefix}${entry.name}`);
14475
14603
  }
14476
14604
  };
@@ -14746,7 +14874,7 @@ ${cleanContent}`;
14746
14874
  const currentCanvas = await adapter2.readDiscoveryCanvas();
14747
14875
  const canvasText = formatDiscoveryCanvas(currentCanvas);
14748
14876
  if (canvasText) {
14749
- return { ...sd, canvasHash: createHash2("md5").update(canvasText).digest("hex") };
14877
+ return { ...sd, canvasHash: createHash3("md5").update(canvasText).digest("hex") };
14750
14878
  }
14751
14879
  } catch {
14752
14880
  }
@@ -16608,15 +16736,15 @@ ${existing}` : entry;
16608
16736
 
16609
16737
  // src/services/setup.ts
16610
16738
  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";
16739
+ import { join as join9, basename, extname, dirname as dirname3 } from "path";
16612
16740
  import { execFileSync as execFileSync3 } from "child_process";
16613
16741
 
16614
16742
  // 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";
16743
+ import { existsSync as existsSync3 } from "fs";
16744
+ import { readdirSync as readdirSync2, statSync as statSync3 } from "fs";
16745
+ import { join as join5 } from "path";
16618
16746
  function detectCodebaseType(projectRoot) {
16619
- if (existsSync2(join4(projectRoot, ".git"))) {
16747
+ if (existsSync3(join5(projectRoot, ".git"))) {
16620
16748
  return "existing_codebase";
16621
16749
  }
16622
16750
  const manifests = [
@@ -16630,7 +16758,7 @@ function detectCodebaseType(projectRoot) {
16630
16758
  "CMakeLists.txt"
16631
16759
  ];
16632
16760
  for (const manifest of manifests) {
16633
- if (existsSync2(join4(projectRoot, manifest))) {
16761
+ if (existsSync3(join5(projectRoot, manifest))) {
16634
16762
  return "existing_codebase";
16635
16763
  }
16636
16764
  }
@@ -16638,7 +16766,7 @@ function detectCodebaseType(projectRoot) {
16638
16766
  const entries = readdirSync2(projectRoot).filter((f) => !f.startsWith("."));
16639
16767
  const fileCount = entries.filter((f) => {
16640
16768
  try {
16641
- return statSync2(join4(projectRoot, f)).isFile();
16769
+ return statSync3(join5(projectRoot, f)).isFile();
16642
16770
  } catch {
16643
16771
  return false;
16644
16772
  }
@@ -16650,18 +16778,18 @@ function detectCodebaseType(projectRoot) {
16650
16778
  }
16651
16779
 
16652
16780
  // 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";
16781
+ import { readFileSync as readFileSync3, existsSync as existsSync4, readdirSync as readdirSync3, statSync as statSync4 } from "fs";
16782
+ import { dirname, join as join6, resolve } from "path";
16655
16783
  import { fileURLToPath } from "url";
16656
- var PROJECT_BUNDLE_REL = join5(".agents", "skills", "papi-cycle");
16784
+ var PROJECT_BUNDLE_REL = join6(".agents", "skills", "papi-cycle");
16657
16785
  function bundleDestRel(rel) {
16658
- return rel === "AGENTS.md" ? "AGENTS.md" : join5(PROJECT_BUNDLE_REL, rel);
16786
+ return rel === "AGENTS.md" ? "AGENTS.md" : join6(PROJECT_BUNDLE_REL, rel);
16659
16787
  }
16660
16788
  function resolveBundleDir() {
16661
16789
  let dir = dirname(fileURLToPath(import.meta.url));
16662
16790
  for (let i = 0; i < 5; i++) {
16663
- const candidate = join5(dir, "skills", "papi-cycle");
16664
- if (existsSync3(join5(candidate, "AGENTS.md"))) return candidate;
16791
+ const candidate = join6(dir, "skills", "papi-cycle");
16792
+ if (existsSync4(join6(candidate, "AGENTS.md"))) return candidate;
16665
16793
  const parent = resolve(dir, "..");
16666
16794
  if (parent === dir) break;
16667
16795
  dir = parent;
@@ -16669,14 +16797,14 @@ function resolveBundleDir() {
16669
16797
  return void 0;
16670
16798
  }
16671
16799
  function readBundleFiles(bundleDir = resolveBundleDir()) {
16672
- if (!bundleDir || !existsSync3(bundleDir)) return [];
16800
+ if (!bundleDir || !existsSync4(bundleDir)) return [];
16673
16801
  const files = [];
16674
16802
  const walk = (abs, rel) => {
16675
16803
  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;
16804
+ const childAbs = join6(abs, entry.name);
16805
+ const childRel = rel ? join6(rel, entry.name) : entry.name;
16678
16806
  if (entry.isDirectory()) walk(childAbs, childRel);
16679
- else if (entry.isFile()) files.push({ rel: childRel, content: readFileSync2(childAbs, "utf8") });
16807
+ else if (entry.isFile()) files.push({ rel: childRel, content: readFileSync3(childAbs, "utf8") });
16680
16808
  }
16681
16809
  };
16682
16810
  walk(bundleDir, "");
@@ -16685,8 +16813,8 @@ function readBundleFiles(bundleDir = resolveBundleDir()) {
16685
16813
  function planBundleInstall(projectRoot, projectName, opts = {}) {
16686
16814
  const out = {};
16687
16815
  for (const f of readBundleFiles()) {
16688
- const dest = join5(projectRoot, bundleDestRel(f.rel));
16689
- if (opts.skipExisting && existsSync3(dest) && statSync3(dest).isFile()) continue;
16816
+ const dest = join6(projectRoot, bundleDestRel(f.rel));
16817
+ if (opts.skipExisting && existsSync4(dest) && statSync4(dest).isFile()) continue;
16690
16818
  const content = f.rel === "AGENTS.md" ? f.content.replace(/\{\{project_name\}\}/g, projectName) : f.content;
16691
16819
  out[dest] = content;
16692
16820
  }
@@ -16694,20 +16822,20 @@ function planBundleInstall(projectRoot, projectName, opts = {}) {
16694
16822
  }
16695
16823
 
16696
16824
  // 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";
16825
+ import { readFileSync as readFileSync4, existsSync as existsSync5, statSync as statSync5 } from "fs";
16826
+ import { dirname as dirname2, join as join7, resolve as resolve2 } from "path";
16699
16827
  import { fileURLToPath as fileURLToPath2 } from "url";
16700
16828
  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 }
16829
+ { srcRel: join7("agents", "frontend-design-engineer.md"), destRel: join7(".claude", "agents", "frontend-design-engineer.md"), executable: false },
16830
+ { srcRel: join7("skills", "design-critique", "SKILL.md"), destRel: join7(".claude", "skills", "design-critique", "SKILL.md"), executable: false },
16831
+ { srcRel: join7("hooks", "frontend-design-guard.sh"), destRel: join7(".claude", "hooks", "frontend-design-guard.sh"), executable: true }
16704
16832
  ];
16705
16833
  var DESIGN_HOOK_COMMAND = ".claude/hooks/frontend-design-guard.sh";
16706
16834
  function resolveDesignAssetsDir() {
16707
16835
  let dir = dirname2(fileURLToPath2(import.meta.url));
16708
16836
  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;
16837
+ const candidate = join7(dir, "design-assets");
16838
+ if (existsSync5(join7(candidate, "agents", "frontend-design-engineer.md"))) return candidate;
16711
16839
  const parent = resolve2(dir, "..");
16712
16840
  if (parent === dir) break;
16713
16841
  dir = parent;
@@ -16719,23 +16847,23 @@ function planDesignInstall(projectRoot, opts = {}) {
16719
16847
  if (!assetsDir) return [];
16720
16848
  const out = [];
16721
16849
  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 });
16850
+ const srcAbs = join7(assetsDir, asset.srcRel);
16851
+ if (!existsSync5(srcAbs)) continue;
16852
+ const dest = projectRoot ? join7(projectRoot, asset.destRel) : asset.destRel;
16853
+ if (opts.skipExisting && projectRoot && existsSync5(dest) && statSync5(dest).isFile()) continue;
16854
+ out.push({ dest, content: readFileSync4(srcAbs, "utf8"), executable: asset.executable });
16727
16855
  }
16728
16856
  return out;
16729
16857
  }
16730
16858
 
16731
16859
  // 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";
16860
+ import { existsSync as existsSync6, readdirSync as readdirSync4, readFileSync as readFileSync5, statSync as statSync6 } from "fs";
16861
+ import { join as join8 } from "path";
16734
16862
  function readPackageJson(projectRoot) {
16735
- const path7 = join7(projectRoot, "package.json");
16736
- if (!existsSync5(path7)) return null;
16863
+ const path7 = join8(projectRoot, "package.json");
16864
+ if (!existsSync6(path7)) return null;
16737
16865
  try {
16738
- const raw = readFileSync4(path7, "utf-8");
16866
+ const raw = readFileSync5(path7, "utf-8");
16739
16867
  return JSON.parse(raw);
16740
16868
  } catch {
16741
16869
  return null;
@@ -16756,8 +16884,8 @@ function detectsFrontendStack(projectRoot) {
16756
16884
  return hasDependencyMatching(allDeps(readPackageJson(projectRoot)), FRONTEND_DEP_PATTERN);
16757
16885
  }
16758
16886
  function hasGitHubWorkflows(projectRoot) {
16759
- const dir = join7(projectRoot, ".github", "workflows");
16760
- if (!existsSync5(dir)) return false;
16887
+ const dir = join8(projectRoot, ".github", "workflows");
16888
+ if (!existsSync6(dir)) return false;
16761
16889
  try {
16762
16890
  const entries = readdirSync4(dir);
16763
16891
  return entries.some((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
@@ -16766,21 +16894,21 @@ function hasGitHubWorkflows(projectRoot) {
16766
16894
  }
16767
16895
  }
16768
16896
  function envExampleMentionsStaging(projectRoot) {
16769
- const path7 = join7(projectRoot, ".env.example");
16770
- if (!existsSync5(path7)) return false;
16897
+ const path7 = join8(projectRoot, ".env.example");
16898
+ if (!existsSync6(path7)) return false;
16771
16899
  try {
16772
- const raw = readFileSync4(path7, "utf-8");
16900
+ const raw = readFileSync5(path7, "utf-8");
16773
16901
  return /\b(STAGING_URL|STAGING_API|STAGING_HOST|NEXT_PUBLIC_STAGING)/i.test(raw);
16774
16902
  } catch {
16775
16903
  return false;
16776
16904
  }
16777
16905
  }
16778
16906
  function hasVercelConfig(projectRoot) {
16779
- if (existsSync5(join7(projectRoot, "vercel.json"))) return true;
16780
- const vercelDir = join7(projectRoot, ".vercel");
16781
- if (!existsSync5(vercelDir)) return false;
16907
+ if (existsSync6(join8(projectRoot, "vercel.json"))) return true;
16908
+ const vercelDir = join8(projectRoot, ".vercel");
16909
+ if (!existsSync6(vercelDir)) return false;
16782
16910
  try {
16783
- return statSync5(vercelDir).isDirectory();
16911
+ return statSync6(vercelDir).isDirectory();
16784
16912
  } catch {
16785
16913
  return false;
16786
16914
  }
@@ -17266,7 +17394,7 @@ async function scaffoldPapiDir(adapter2, config2, input, collector) {
17266
17394
  await mkdir(config2.papiDir, { recursive: true });
17267
17395
  for (const [filename, template] of Object.entries(FILE_TEMPLATES)) {
17268
17396
  const content = substitute(template, vars);
17269
- await writeFile2(join8(config2.papiDir, filename), content, "utf-8");
17397
+ await writeFile2(join9(config2.papiDir, filename), content, "utf-8");
17270
17398
  }
17271
17399
  }
17272
17400
  } else {
@@ -17284,13 +17412,13 @@ async function scaffoldPapiDir(adapter2, config2, input, collector) {
17284
17412
  const useCollector = config2.adapterType === "proxy";
17285
17413
  const docsRel = "docs";
17286
17414
  const commandsRel = ".claude/commands";
17287
- const commandsDir = useCollector ? commandsRel : join8(config2.projectRoot, ".claude", "commands");
17288
- const docsDir = useCollector ? docsRel : join8(config2.projectRoot, "docs");
17415
+ const commandsDir = useCollector ? commandsRel : join9(config2.projectRoot, ".claude", "commands");
17416
+ const docsDir = useCollector ? docsRel : join9(config2.projectRoot, "docs");
17289
17417
  if (!useCollector) {
17290
17418
  await mkdir(commandsDir, { recursive: true });
17291
17419
  await mkdir(docsDir, { recursive: true });
17292
17420
  }
17293
- const claudeMdPath = useCollector ? "CLAUDE.md" : join8(config2.projectRoot, "CLAUDE.md");
17421
+ const claudeMdPath = useCollector ? "CLAUDE.md" : join9(config2.projectRoot, "CLAUDE.md");
17294
17422
  let claudeMdExists = false;
17295
17423
  if (!useCollector) {
17296
17424
  try {
@@ -17299,7 +17427,7 @@ async function scaffoldPapiDir(adapter2, config2, input, collector) {
17299
17427
  } catch {
17300
17428
  }
17301
17429
  }
17302
- const docsIndexPath = useCollector ? `${docsRel}/INDEX.md` : join8(docsDir, "INDEX.md");
17430
+ const docsIndexPath = useCollector ? `${docsRel}/INDEX.md` : join9(docsDir, "INDEX.md");
17303
17431
  let docsIndexExists = false;
17304
17432
  if (!useCollector) {
17305
17433
  try {
@@ -17309,9 +17437,9 @@ async function scaffoldPapiDir(adapter2, config2, input, collector) {
17309
17437
  }
17310
17438
  }
17311
17439
  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)
17440
+ [useCollector ? `${commandsRel}/papi-audit.md` : join9(commandsDir, "papi-audit.md")]: PAPI_AUDIT_COMMAND_TEMPLATE,
17441
+ [useCollector ? `${commandsRel}/test.md` : join9(commandsDir, "test.md")]: TEST_COMMAND_TEMPLATE,
17442
+ [useCollector ? `${docsRel}/README.md` : join9(docsDir, "README.md")]: substitute(DOCS_README_TEMPLATE, vars)
17315
17443
  };
17316
17444
  if (!docsIndexExists) {
17317
17445
  scaffoldFiles[docsIndexPath] = substitute(DOCS_INDEX_TEMPLATE, vars);
@@ -17338,7 +17466,7 @@ async function scaffoldPapiDir(adapter2, config2, input, collector) {
17338
17466
  if (useCollector) {
17339
17467
  scaffoldFiles[".cursor/rules/papi.mdc"] = substitute(CURSOR_RULES_TEMPLATE, vars);
17340
17468
  } else {
17341
- const cursorDir = join8(config2.projectRoot, ".cursor");
17469
+ const cursorDir = join9(config2.projectRoot, ".cursor");
17342
17470
  let cursorDetected = false;
17343
17471
  try {
17344
17472
  await access2(cursorDir);
@@ -17346,8 +17474,8 @@ async function scaffoldPapiDir(adapter2, config2, input, collector) {
17346
17474
  } catch {
17347
17475
  }
17348
17476
  if (cursorDetected) {
17349
- const cursorRulesDir = join8(cursorDir, "rules");
17350
- const cursorRulesPath = join8(cursorRulesDir, "papi.mdc");
17477
+ const cursorRulesDir = join9(cursorDir, "rules");
17478
+ const cursorRulesPath = join9(cursorRulesDir, "papi.mdc");
17351
17479
  await mkdir(cursorRulesDir, { recursive: true });
17352
17480
  try {
17353
17481
  await access2(cursorRulesPath);
@@ -17402,7 +17530,7 @@ async function scaffoldPapiDir(adapter2, config2, input, collector) {
17402
17530
  }
17403
17531
  var PAPI_PERMISSION = "mcp__papi__*";
17404
17532
  async function ensurePapiPermission(projectRoot) {
17405
- const settingsPath = join8(projectRoot, ".claude", "settings.json");
17533
+ const settingsPath = join9(projectRoot, ".claude", "settings.json");
17406
17534
  try {
17407
17535
  let settings = {};
17408
17536
  try {
@@ -17421,13 +17549,13 @@ async function ensurePapiPermission(projectRoot) {
17421
17549
  if (!allow.includes(PAPI_PERMISSION)) {
17422
17550
  allow.push(PAPI_PERMISSION);
17423
17551
  }
17424
- await mkdir(join8(projectRoot, ".claude"), { recursive: true });
17552
+ await mkdir(join9(projectRoot, ".claude"), { recursive: true });
17425
17553
  await writeFile2(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
17426
17554
  } catch {
17427
17555
  }
17428
17556
  }
17429
17557
  async function ensureDesignHookRegistered(projectRoot) {
17430
- const settingsPath = join8(projectRoot, ".claude", "settings.json");
17558
+ const settingsPath = join9(projectRoot, ".claude", "settings.json");
17431
17559
  try {
17432
17560
  let settings = {};
17433
17561
  try {
@@ -17457,7 +17585,7 @@ async function ensureDesignHookRegistered(projectRoot) {
17457
17585
  chain.push({ type: "command", command: DESIGN_HOOK_COMMAND });
17458
17586
  }
17459
17587
  }
17460
- await mkdir(join8(projectRoot, ".claude"), { recursive: true });
17588
+ await mkdir(join9(projectRoot, ".claude"), { recursive: true });
17461
17589
  await writeFile2(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
17462
17590
  } catch {
17463
17591
  }
@@ -17561,7 +17689,7 @@ ${conventionsText.trim()}
17561
17689
  );
17562
17690
  } else {
17563
17691
  try {
17564
- const claudeMdPath = join8(config2.projectRoot, "CLAUDE.md");
17692
+ const claudeMdPath = join9(config2.projectRoot, "CLAUDE.md");
17565
17693
  const existing = await readFile4(claudeMdPath, "utf-8");
17566
17694
  if (existing.includes(CONVENTIONS_SENTINEL) || existing.includes(CONVENTIONS_HEADING)) {
17567
17695
  warnings.push(
@@ -17648,13 +17776,13 @@ async function scanCodebase(projectRoot) {
17648
17776
  }
17649
17777
  let packageJson;
17650
17778
  try {
17651
- const content = await readFile4(join8(projectRoot, "package.json"), "utf-8");
17779
+ const content = await readFile4(join9(projectRoot, "package.json"), "utf-8");
17652
17780
  packageJson = JSON.parse(content);
17653
17781
  } catch {
17654
17782
  }
17655
17783
  let readme;
17656
17784
  for (const name of ["README.md", "readme.md", "README.txt", "README"]) {
17657
- const content = await safeReadFile(join8(projectRoot, name), 5e3);
17785
+ const content = await safeReadFile(join9(projectRoot, name), 5e3);
17658
17786
  if (content) {
17659
17787
  readme = content;
17660
17788
  break;
@@ -17664,7 +17792,7 @@ async function scanCodebase(projectRoot) {
17664
17792
  let totalFiles = topLevelFiles.length;
17665
17793
  for (const dir of topLevelDirs) {
17666
17794
  try {
17667
- const entries = await readdir(join8(projectRoot, dir), { withFileTypes: true });
17795
+ const entries = await readdir(join9(projectRoot, dir), { withFileTypes: true });
17668
17796
  const files = entries.filter((e) => e.isFile());
17669
17797
  const extensions = [...new Set(files.map((f) => extname(f.name).toLowerCase()).filter(Boolean))];
17670
17798
  totalFiles += files.length;
@@ -18011,7 +18139,7 @@ async function applySetup(adapter2, config2, input, briefText, adSeedText, conve
18011
18139
  collector.add({ path: "CLAUDE.md", content: dogfoodSection, mode: "append" });
18012
18140
  } else {
18013
18141
  try {
18014
- const claudeMdPath = join8(config2.projectRoot, "CLAUDE.md");
18142
+ const claudeMdPath = join9(config2.projectRoot, "CLAUDE.md");
18015
18143
  const existing = await readFile4(claudeMdPath, "utf-8");
18016
18144
  if (!existing.includes("Dogfood Logging")) {
18017
18145
  await writeFile2(claudeMdPath, existing + dogfoodSection, "utf-8");
@@ -18056,7 +18184,7 @@ async function applySetup(adapter2, config2, input, briefText, adSeedText, conve
18056
18184
  cursorScaffolded = true;
18057
18185
  } else {
18058
18186
  try {
18059
- await access2(join8(config2.projectRoot, ".cursor", "rules", "papi.mdc"));
18187
+ await access2(join9(config2.projectRoot, ".cursor", "rules", "papi.mdc"));
18060
18188
  cursorScaffolded = true;
18061
18189
  } catch {
18062
18190
  }
@@ -18076,11 +18204,11 @@ async function applySetup(adapter2, config2, input, briefText, adSeedText, conve
18076
18204
  }
18077
18205
  async function ensureMcpJsonGitignored(projectRoot) {
18078
18206
  try {
18079
- await access2(join8(projectRoot, ".git"));
18207
+ await access2(join9(projectRoot, ".git"));
18080
18208
  } catch {
18081
18209
  return void 0;
18082
18210
  }
18083
- const gitignorePath = join8(projectRoot, ".gitignore");
18211
+ const gitignorePath = join9(projectRoot, ".gitignore");
18084
18212
  let existing = "";
18085
18213
  try {
18086
18214
  existing = await readFile4(gitignorePath, "utf-8");
@@ -18595,8 +18723,8 @@ PAPI never runs this command itself (AD-58) \u2014 you run it in your own enviro
18595
18723
 
18596
18724
  // src/services/build.ts
18597
18725
  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";
18726
+ import { readdirSync as readdirSync5, existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2, mkdirSync as mkdirSync2 } from "fs";
18727
+ import { join as join11 } from "path";
18600
18728
 
18601
18729
  // src/lib/harness-capability.ts
18602
18730
  var HARNESS_REGISTRY = {
@@ -18672,7 +18800,7 @@ init_git();
18672
18800
  // src/services/release.ts
18673
18801
  init_telemetry();
18674
18802
  import { writeFile as writeFile3, readFile as readFile5 } from "fs/promises";
18675
- import { join as join9 } from "path";
18803
+ import { join as join10 } from "path";
18676
18804
  import { execFileSync as execFileSync4 } from "child_process";
18677
18805
  init_git();
18678
18806
  var INITIAL_RELEASE_NOTES = `# Changelog
@@ -19136,7 +19264,7 @@ async function createRelease(config2, branch, version, adapter2, cycleNum, optio
19136
19264
  }
19137
19265
  }
19138
19266
  const latestTag = getLatestTag(config2.projectRoot);
19139
- const changelogPath = join9(config2.projectRoot, "CHANGELOG.md");
19267
+ const changelogPath = join10(config2.projectRoot, "CHANGELOG.md");
19140
19268
  if (!latestTag) {
19141
19269
  const initialContent = INITIAL_RELEASE_NOTES.replace("v0.1.0-alpha", version);
19142
19270
  if (config2.adapterType === "proxy") {
@@ -20573,17 +20701,17 @@ function writeActiveTaskScope(projectRoot, taskId, filesLikelyTouched, adapterTy
20573
20701
  collector.add({ path: ".papi/active-task-scope.txt", content, mode: "overwrite" });
20574
20702
  return;
20575
20703
  }
20576
- const papiDir = join10(projectRoot, ".papi");
20577
- if (!existsSync6(papiDir)) {
20704
+ const papiDir = join11(projectRoot, ".papi");
20705
+ if (!existsSync7(papiDir)) {
20578
20706
  mkdirSync2(papiDir, { recursive: true });
20579
20707
  }
20580
- const scopePath = join10(papiDir, "active-task-scope.txt");
20581
- writeFileSync2(scopePath, content, "utf-8");
20708
+ const scopePath = join11(papiDir, "active-task-scope.txt");
20709
+ writeFileSync3(scopePath, content, "utf-8");
20582
20710
  }
20583
20711
  function clearActiveTaskScope(projectRoot) {
20584
- const scopePath = join10(projectRoot, ".papi", "active-task-scope.txt");
20585
- if (existsSync6(scopePath)) {
20586
- unlinkSync(scopePath);
20712
+ const scopePath = join11(projectRoot, ".papi", "active-task-scope.txt");
20713
+ if (existsSync7(scopePath)) {
20714
+ unlinkSync2(scopePath);
20587
20715
  }
20588
20716
  }
20589
20717
  function sanitiseResponseExcerpt(raw) {
@@ -20602,7 +20730,7 @@ function extractDocMeta(absolutePath, relativePath, cycleNumber) {
20602
20730
  else if (relativePath.startsWith("docs/architecture/")) type = "architecture";
20603
20731
  else if (relativePath.startsWith("docs/audits/")) type = "audit";
20604
20732
  try {
20605
- const content = readFileSync5(absolutePath, "utf-8").slice(0, 2e3);
20733
+ const content = readFileSync6(absolutePath, "utf-8").slice(0, 2e3);
20606
20734
  const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
20607
20735
  if (fmMatch) {
20608
20736
  const fm = fmMatch[1];
@@ -20981,14 +21109,14 @@ async function completeBuild(adapter2, config2, taskId, input, options = {}, cli
20981
21109
  let docWarning;
20982
21110
  try {
20983
21111
  if (adapter2.searchDocs && hasLocalWorkspace() && await ownsLocalWorkspace(adapter2, config2.projectRoot)) {
20984
- const docsDir = join10(config2.projectRoot, "docs");
20985
- if (existsSync6(docsDir)) {
21112
+ const docsDir = join11(config2.projectRoot, "docs");
21113
+ if (existsSync7(docsDir)) {
20986
21114
  const scanDir = (dir, depth = 0) => {
20987
21115
  if (depth > 8) return [];
20988
21116
  const entries = readdirSync5(dir, { withFileTypes: true });
20989
21117
  const files = [];
20990
21118
  for (const e of entries) {
20991
- const full = join10(dir, e.name);
21119
+ const full = join11(dir, e.name);
20992
21120
  if (e.isDirectory() && !e.isSymbolicLink()) files.push(...scanDir(full, depth + 1));
20993
21121
  else if (e.name.endsWith(".md")) files.push(full.replace(config2.projectRoot + "/", ""));
20994
21122
  }
@@ -21003,7 +21131,7 @@ async function completeBuild(adapter2, config2, taskId, input, options = {}, cli
21003
21131
  const failed = [];
21004
21132
  for (const docPath of unregistered) {
21005
21133
  try {
21006
- const meta = extractDocMeta(join10(config2.projectRoot, docPath), docPath, cycleNumber);
21134
+ const meta = extractDocMeta(join11(config2.projectRoot, docPath), docPath, cycleNumber);
21007
21135
  await adapter2.registerDoc({
21008
21136
  title: meta.title,
21009
21137
  type: meta.type,
@@ -21152,8 +21280,8 @@ ${instructions}`;
21152
21280
  }
21153
21281
 
21154
21282
  // 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";
21283
+ import { readdirSync as readdirSync6, existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
21284
+ import { join as join12, relative } from "path";
21157
21285
  import { homedir as homedir3 } from "os";
21158
21286
  import { randomUUID as randomUUID12 } from "crypto";
21159
21287
  var docRegisterTool = {
@@ -21350,7 +21478,7 @@ async function handleDocSearch(adapter2, args, config2) {
21350
21478
  const lines = docs.map((d) => {
21351
21479
  const actionCount = d.actions?.filter((a) => a.status === "pending").length ?? 0;
21352
21480
  const actionNote = actionCount > 0 ? ` | ${actionCount} pending action(s)` : "";
21353
- const missingNote = root && d.path && !existsSync7(join11(root, d.path)) ? `
21481
+ const missingNote = root && d.path && !existsSync8(join12(root, d.path)) ? `
21354
21482
  > \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
21483
  return `### ${d.title}
21356
21484
  **Type:** ${d.type} | **Status:** ${d.status} | **Cycle:** ${d.cycleCreated}${d.cycleUpdated ? `\u2192${d.cycleUpdated}` : ""}${actionNote}
@@ -21364,12 +21492,12 @@ ${d.summary}
21364
21492
  ${lines.join("\n---\n\n")}`);
21365
21493
  }
21366
21494
  function scanMdFiles(dir, rootDir) {
21367
- if (!existsSync7(dir)) return [];
21495
+ if (!existsSync8(dir)) return [];
21368
21496
  const files = [];
21369
21497
  try {
21370
21498
  const entries = readdirSync6(dir, { withFileTypes: true });
21371
21499
  for (const entry of entries) {
21372
- const full = join11(dir, entry.name);
21500
+ const full = join12(dir, entry.name);
21373
21501
  if (entry.isDirectory()) {
21374
21502
  files.push(...scanMdFiles(full, rootDir));
21375
21503
  } else if (entry.name.endsWith(".md")) {
@@ -21382,7 +21510,7 @@ function scanMdFiles(dir, rootDir) {
21382
21510
  }
21383
21511
  function extractTitle(filePath) {
21384
21512
  try {
21385
- const content = readFileSync6(filePath, "utf-8").slice(0, 1e3);
21513
+ const content = readFileSync7(filePath, "utf-8").slice(0, 1e3);
21386
21514
  const fmMatch = content.match(/^---[\s\S]*?title:\s*(.+?)$/m);
21387
21515
  if (fmMatch) return fmMatch[1].trim().replace(/^["']|["']$/g, "");
21388
21516
  const headingMatch = content.match(/^#+\s+(.+)$/m);
@@ -21394,7 +21522,7 @@ function extractTitle(filePath) {
21394
21522
  async function detectUnregisteredDocsNote(adapter2, config2) {
21395
21523
  try {
21396
21524
  if (!adapter2.searchDocs || !hasLocalWorkspace()) return "";
21397
- const docsDir = join11(config2.projectRoot, "docs");
21525
+ const docsDir = join12(config2.projectRoot, "docs");
21398
21526
  const docsFiles = scanMdFiles(docsDir, config2.projectRoot);
21399
21527
  if (docsFiles.length === 0) return "";
21400
21528
  const registered = await adapter2.searchDocs({ limit: 500, status: "all" });
@@ -21420,17 +21548,17 @@ async function handleDocScan(adapter2, config2, args) {
21420
21548
  const includePlans = args.include_plans ?? false;
21421
21549
  const registered = await adapter2.searchDocs({ limit: 500, status: "all" });
21422
21550
  const registeredPaths = new Set(registered.map((d) => d.path));
21423
- const docsDir = join11(config2.projectRoot, "docs");
21551
+ const docsDir = join12(config2.projectRoot, "docs");
21424
21552
  const docsFiles = scanMdFiles(docsDir, config2.projectRoot);
21425
21553
  const unregisteredDocs = docsFiles.filter((f) => !registeredPaths.has(f));
21426
21554
  let unregisteredPlans = [];
21427
21555
  if (includePlans) {
21428
- const plansDir = join11(homedir3(), ".claude", "plans");
21429
- if (existsSync7(plansDir)) {
21556
+ const plansDir = join12(homedir3(), ".claude", "plans");
21557
+ if (existsSync8(plansDir)) {
21430
21558
  const planFiles = scanMdFiles(plansDir, plansDir);
21431
21559
  unregisteredPlans = planFiles.map((f) => `plans/${f}`).filter((f) => !registeredPaths.has(f)).map((f) => ({
21432
21560
  path: f,
21433
- title: extractTitle(join11(plansDir, f.replace("plans/", "")))
21561
+ title: extractTitle(join12(plansDir, f.replace("plans/", "")))
21434
21562
  }));
21435
21563
  }
21436
21564
  }
@@ -21441,7 +21569,7 @@ async function handleDocScan(adapter2, config2, args) {
21441
21569
  if (unregisteredDocs.length > 0) {
21442
21570
  lines.push(`## Unregistered Docs (${unregisteredDocs.length})`);
21443
21571
  for (const f of unregisteredDocs) {
21444
- const title = extractTitle(join11(config2.projectRoot, f));
21572
+ const title = extractTitle(join12(config2.projectRoot, f));
21445
21573
  lines.push(`- \`${f}\`${title ? ` \u2014 ${title}` : ""}`);
21446
21574
  }
21447
21575
  }
@@ -22871,13 +22999,13 @@ _To correct: board_edit ${result.task.id} with updated fields._`
22871
22999
  init_git();
22872
23000
 
22873
23001
  // src/services/reconcile.ts
22874
- import { readFileSync as readFileSync7 } from "fs";
22875
- import { join as join12 } from "path";
23002
+ import { readFileSync as readFileSync8 } from "fs";
23003
+ import { join as join13 } from "path";
22876
23004
  function loadDocsIndex(projectRoot) {
22877
23005
  if (!hasLocalWorkspace()) return "";
22878
23006
  try {
22879
- const indexPath = join12(projectRoot, "docs", "INDEX.md");
22880
- const raw = readFileSync7(indexPath, "utf8");
23007
+ const indexPath = join13(projectRoot, "docs", "INDEX.md");
23008
+ const raw = readFileSync8(indexPath, "utf8");
22881
23009
  const rows = raw.split("\n").filter((l) => l.startsWith("| ["));
22882
23010
  if (rows.length === 0) return "";
22883
23011
  const entries = rows.map((row) => {
@@ -23452,8 +23580,8 @@ Produce your analysis and structured output above. Present Part 1 to the user an
23452
23580
  }
23453
23581
 
23454
23582
  // src/tools/review.ts
23455
- import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
23456
- import { join as join13 } from "path";
23583
+ import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
23584
+ import { join as join14 } from "path";
23457
23585
  init_git();
23458
23586
 
23459
23587
  // src/services/review.ts
@@ -23675,11 +23803,11 @@ ${task.buildReport}` : "### Build Report\n(none recorded)";
23675
23803
  ${diff}
23676
23804
  \`\`\`` : "### Branch diff vs base\n(no diff resolved \u2014 not a git repo, no base ref, or no committed changes)";
23677
23805
  let projectContext = "";
23678
- const ctxPath = join13(config2.projectRoot, ".agents", "papi-context.md");
23679
- if (existsSync8(ctxPath)) {
23806
+ const ctxPath = join14(config2.projectRoot, ".agents", "papi-context.md");
23807
+ if (existsSync9(ctxPath)) {
23680
23808
  try {
23681
23809
  projectContext = `### Project context (.agents/papi-context.md)
23682
- ${readFileSync8(ctxPath, "utf-8")}
23810
+ ${readFileSync9(ctxPath, "utf-8")}
23683
23811
 
23684
23812
  `;
23685
23813
  } catch {
@@ -23834,8 +23962,8 @@ function mergeAfterAccept(config2, taskId) {
23834
23962
  };
23835
23963
  }
23836
23964
  const details = [];
23837
- const papiDir = join13(config2.projectRoot, ".papi");
23838
- if (existsSync8(papiDir)) {
23965
+ const papiDir = join14(config2.projectRoot, ".papi");
23966
+ if (existsSync9(papiDir)) {
23839
23967
  try {
23840
23968
  const commitResult = stageDirAndCommit(
23841
23969
  config2.projectRoot,
@@ -24873,7 +25001,7 @@ function computeHealthScore(cycleNumber, snapshots, activeTasks, decisionUsage)
24873
25001
  const recentSnaps = snapshots.slice(-3);
24874
25002
  const baselineSnaps = snapshots.slice(-10);
24875
25003
  if (recentSnaps.length > 0 && baselineSnaps.length > 0) {
24876
- const avg = (snaps) => snaps.reduce((s, sn) => s + (sn.velocity[0]?.effortPoints ?? 0), 0) / snaps.length;
25004
+ const avg = (snaps) => snaps.reduce((s, sn) => s + (sn.velocity[0] ? velocityPoints(sn.velocity[0]) : 0), 0) / snaps.length;
24877
25005
  const recentAvg = avg(recentSnaps);
24878
25006
  const baselineAvg = avg(baselineSnaps);
24879
25007
  const velocityScore = baselineAvg > 0 ? Math.min(100, Math.round(recentAvg / baselineAvg * 100)) : 50;
@@ -25379,7 +25507,7 @@ function formatDeferredGateSection(sweep) {
25379
25507
 
25380
25508
  // src/tools/agent-list.ts
25381
25509
  import { readdir as readdir2, readFile as readFile7 } from "fs/promises";
25382
- import { join as join14 } from "path";
25510
+ import { join as join15 } from "path";
25383
25511
  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
25512
  function parseAgentFrontmatter(content) {
25385
25513
  const match = content.match(/^---\n([\s\S]*?)\n---/);
@@ -25391,7 +25519,7 @@ function parseAgentFrontmatter(content) {
25391
25519
  return { name: nameMatch?.[1].trim(), description };
25392
25520
  }
25393
25521
  async function listAgents(projectRoot) {
25394
- const agentsDir = join14(projectRoot, ".claude", "agents");
25522
+ const agentsDir = join15(projectRoot, ".claude", "agents");
25395
25523
  let files;
25396
25524
  try {
25397
25525
  files = await readdir2(agentsDir);
@@ -25402,7 +25530,7 @@ async function listAgents(projectRoot) {
25402
25530
  for (const file of files.filter((f) => f.endsWith(".md"))) {
25403
25531
  let content;
25404
25532
  try {
25405
- content = await readFile7(join14(agentsDir, file), "utf-8");
25533
+ content = await readFile7(join15(agentsDir, file), "utf-8");
25406
25534
  } catch {
25407
25535
  continue;
25408
25536
  }
@@ -25410,7 +25538,7 @@ async function listAgents(projectRoot) {
25410
25538
  agents.push({
25411
25539
  name: meta?.name ?? file.replace(/\.md$/, ""),
25412
25540
  description: meta?.description ?? "",
25413
- path: join14(".claude", "agents", file)
25541
+ path: join15(".claude", "agents", file)
25414
25542
  });
25415
25543
  }
25416
25544
  agents.sort((a, b2) => a.name.localeCompare(b2.name));
@@ -25551,8 +25679,8 @@ async function verifyProject(adapter2) {
25551
25679
  // src/tools/orient.ts
25552
25680
  import { execFile as execFile2 } from "child_process";
25553
25681
  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";
25682
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync4, existsSync as existsSync10 } from "fs";
25683
+ import { join as join16 } from "path";
25556
25684
  var execFileAsync2 = promisify2(execFile2);
25557
25685
  var GIT_DEPENDENT_ENVS = /* @__PURE__ */ new Set(["hosted", "api"]);
25558
25686
  var VALID_ENVS = /* @__PURE__ */ new Set(["local-cli", "hosted", "api", "unknown"]);
@@ -25867,8 +25995,8 @@ async function getLatestGitTag(projectRoot) {
25867
25995
  }
25868
25996
  async function checkNpmVersionDrift() {
25869
25997
  try {
25870
- const pkgPath = join15(new URL(".", import.meta.url).pathname, "..", "..", "package.json");
25871
- const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
25998
+ const pkgPath = join16(new URL(".", import.meta.url).pathname, "..", "..", "package.json");
25999
+ const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
25872
26000
  const localVersion = pkg.version;
25873
26001
  const packageName = pkg.name;
25874
26002
  const { stdout } = await execFileAsync2("npm", ["view", packageName, "version"], {
@@ -26538,9 +26666,9 @@ function enrichClaudeMd(projectRoot, cycleNumber, adapterType, collector, client
26538
26666
 
26539
26667
  \u{1F4DD} **CLAUDE.md enriched** \u2014 added ${tierNames2.join(" + ")} guidance for cycle ${cycleNumber}+ projects.`;
26540
26668
  }
26541
- const claudeMdPath = join15(projectRoot, "CLAUDE.md");
26542
- if (!existsSync9(claudeMdPath)) return "";
26543
- const content = readFileSync9(claudeMdPath, "utf-8");
26669
+ const claudeMdPath = join16(projectRoot, "CLAUDE.md");
26670
+ if (!existsSync10(claudeMdPath)) return "";
26671
+ const content = readFileSync10(claudeMdPath, "utf-8");
26544
26672
  const additions = [];
26545
26673
  if (cycleNumber >= 6 && !content.includes(CLAUDE_MD_ENRICHMENT_SENTINEL_T1)) {
26546
26674
  additions.push(dedupeEnrichmentBlob(content, CLAUDE_MD_TIER_1));
@@ -26549,7 +26677,7 @@ function enrichClaudeMd(projectRoot, cycleNumber, adapterType, collector, client
26549
26677
  additions.push(dedupeEnrichmentBlob(content, CLAUDE_MD_TIER_2));
26550
26678
  }
26551
26679
  if (additions.length === 0) return "";
26552
- writeFileSync3(claudeMdPath, content + additions.join(""), "utf-8");
26680
+ writeFileSync4(claudeMdPath, content + additions.join(""), "utf-8");
26553
26681
  const tierNames = [];
26554
26682
  if (additions.some((a) => a.includes(CLAUDE_MD_ENRICHMENT_SENTINEL_T1))) tierNames.push("Established (batch building, strategy reviews, AD lifecycle)");
26555
26683
  if (additions.some((a) => a.includes(CLAUDE_MD_ENRICHMENT_SENTINEL_T2))) tierNames.push("Mature (idea pipeline, doc registry, advanced patterns)");
@@ -27272,8 +27400,8 @@ ${result.userMessage}
27272
27400
  }
27273
27401
 
27274
27402
  // 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";
27403
+ import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync3 } from "fs";
27404
+ import { join as join17, dirname as dirname4 } from "path";
27277
27405
  import Anthropic from "@anthropic-ai/sdk";
27278
27406
  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
27407
 
@@ -27328,14 +27456,14 @@ async function runScopeBrief(adapter2, input) {
27328
27456
  }
27329
27457
  const slug = input.taskId.replace(/[^a-z0-9-]/g, "-").toLowerCase();
27330
27458
  const relPath = `docs/scopes/${slug}.md`;
27331
- const absPath = join16(input.projectRoot, relPath);
27459
+ const absPath = join17(input.projectRoot, relPath);
27332
27460
  const docBody = addFrontmatter(docContent, task, input.cycleNumber);
27333
27461
  const collector = new FileWriteCollector();
27334
27462
  if (input.adapterType === "proxy") {
27335
27463
  collector.add({ path: relPath, content: docBody, mode: "overwrite" });
27336
27464
  } else {
27337
27465
  mkdirSync3(dirname4(absPath), { recursive: true });
27338
- writeFileSync4(absPath, docBody, "utf-8");
27466
+ writeFileSync5(absPath, docBody, "utf-8");
27339
27467
  }
27340
27468
  const taskCount = countSubTasks(docContent);
27341
27469
  const summary = buildSummary(task, taskCount);
@@ -28159,19 +28287,19 @@ Its build reports, comments, and history moved with it; the cycle assignment was
28159
28287
 
28160
28288
  // src/services/harness-inventory.ts
28161
28289
  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";
28290
+ import { join as join18 } from "path";
28291
+ import { createHash as createHash4 } from "crypto";
28164
28292
  var RECOMMENDED_HOOKS = ["stop-release-check.sh", "claude-md-size-guard.sh"];
28165
28293
  async function computeFingerprint(root) {
28166
28294
  const parts = [];
28167
28295
  for (const sub of [".claude/skills", ".claude/agents", ".claude/hooks"]) {
28168
- const dir = join17(root, sub);
28296
+ const dir = join18(root, sub);
28169
28297
  try {
28170
28298
  const names = (await readdir3(dir)).sort((a, b2) => a.localeCompare(b2));
28171
28299
  for (const name of names) {
28172
28300
  let mtime = "";
28173
28301
  try {
28174
- mtime = String(Math.floor((await stat3(join17(dir, name))).mtimeMs));
28302
+ mtime = String(Math.floor((await stat3(join18(dir, name))).mtimeMs));
28175
28303
  } catch {
28176
28304
  }
28177
28305
  parts.push(`${sub}/${name}:${mtime}`);
@@ -28186,11 +28314,11 @@ async function computeFingerprint(root) {
28186
28314
  } catch {
28187
28315
  parts.push("manifest:none");
28188
28316
  }
28189
- return createHash3("sha256").update(parts.join("|")).digest("hex").slice(0, 16);
28317
+ return createHash4("sha256").update(parts.join("|")).digest("hex").slice(0, 16);
28190
28318
  }
28191
28319
  async function readSkillDescription(skillDir) {
28192
28320
  try {
28193
- const content = await readFile8(join17(skillDir, "SKILL.md"), "utf-8");
28321
+ const content = await readFile8(join18(skillDir, "SKILL.md"), "utf-8");
28194
28322
  const fm = content.match(/^---\n([\s\S]*?)\n---/);
28195
28323
  if (!fm) return void 0;
28196
28324
  const desc = fm[1].match(/^description:\s*[>|]?\s*\n?([\s\S]*?)(?=\n\w+:|\n---|$)/m);
@@ -28210,17 +28338,17 @@ async function scanInventory(root, toolDefs) {
28210
28338
  version = loadManifest().packageVersion;
28211
28339
  } catch {
28212
28340
  }
28213
- const skillsDir = join17(root, ".claude", "skills");
28341
+ const skillsDir = join18(root, ".claude", "skills");
28214
28342
  try {
28215
28343
  const dirents = await readdir3(skillsDir, { withFileTypes: true });
28216
28344
  for (const d of dirents.filter((e) => e.isDirectory())) {
28217
28345
  entries.push({
28218
28346
  kind: "skill",
28219
28347
  name: d.name,
28220
- description: await readSkillDescription(join17(skillsDir, d.name)),
28348
+ description: await readSkillDescription(join18(skillsDir, d.name)),
28221
28349
  version,
28222
28350
  status: stale.has(d.name) ? "stale_fork" : "ok",
28223
- path: join17(".claude", "skills", d.name)
28351
+ path: join18(".claude", "skills", d.name)
28224
28352
  });
28225
28353
  }
28226
28354
  } catch {
@@ -28236,9 +28364,9 @@ async function scanInventory(root, toolDefs) {
28236
28364
  }
28237
28365
  const present = /* @__PURE__ */ new Set();
28238
28366
  try {
28239
- for (const f of (await readdir3(join17(root, ".claude", "hooks"))).filter((n) => n.endsWith(".sh"))) {
28367
+ for (const f of (await readdir3(join18(root, ".claude", "hooks"))).filter((n) => n.endsWith(".sh"))) {
28240
28368
  present.add(f);
28241
- entries.push({ kind: "hook", name: f, status: "ok", path: join17(".claude", "hooks", f) });
28369
+ entries.push({ kind: "hook", name: f, status: "ok", path: join18(".claude", "hooks", f) });
28242
28370
  }
28243
28371
  } catch {
28244
28372
  }
@@ -28678,7 +28806,7 @@ function createServer(adapter2, config2) {
28678
28806
  const __pkgDir = dirname5(__pkgFilename);
28679
28807
  let serverVersion = "unknown";
28680
28808
  try {
28681
- const pkg = JSON.parse(readFileSync10(join18(__pkgDir, "..", "package.json"), "utf-8"));
28809
+ const pkg = JSON.parse(readFileSync11(join19(__pkgDir, "..", "package.json"), "utf-8"));
28682
28810
  serverVersion = pkg.version ?? "unknown";
28683
28811
  } catch {
28684
28812
  }
@@ -28696,7 +28824,7 @@ function createServer(adapter2, config2) {
28696
28824
  }
28697
28825
  const __filename = fileURLToPath3(import.meta.url);
28698
28826
  const __dirname2 = dirname5(__filename);
28699
- const skillsDir = join18(__dirname2, "..", "skills");
28827
+ const skillsDir = join19(__dirname2, "..", "skills");
28700
28828
  function parseSkillFrontmatter(content) {
28701
28829
  const match = content.match(/^---\n([\s\S]*?)\n---/);
28702
28830
  if (!match) return null;
@@ -28714,7 +28842,7 @@ function createServer(adapter2, config2) {
28714
28842
  const mdFiles = files.filter((f) => f.endsWith(".md"));
28715
28843
  const prompts = [];
28716
28844
  for (const file of mdFiles) {
28717
- const content = await readFile9(join18(skillsDir, file), "utf-8");
28845
+ const content = await readFile9(join19(skillsDir, file), "utf-8");
28718
28846
  const meta = parseSkillFrontmatter(content);
28719
28847
  if (meta) {
28720
28848
  prompts.push({ name: meta.name, description: meta.description });
@@ -28730,7 +28858,7 @@ function createServer(adapter2, config2) {
28730
28858
  try {
28731
28859
  const files = await readdir4(skillsDir);
28732
28860
  for (const file of files.filter((f) => f.endsWith(".md"))) {
28733
- const content = await readFile9(join18(skillsDir, file), "utf-8");
28861
+ const content = await readFile9(join19(skillsDir, file), "utf-8");
28734
28862
  const meta = parseSkillFrontmatter(content);
28735
28863
  if (meta?.name === name) {
28736
28864
  const body = content.replace(/^---\n[\s\S]*?\n---\n*/, "");
@@ -28945,7 +29073,7 @@ ${usageLine(decision.usage)}`;
28945
29073
  emitMdAdapterPing(name, { duration_ms: elapsed, success: !isError }, config2.userId, mdProjectSlug);
28946
29074
  }
28947
29075
  const telemetryProjectId = resolveTelemetryProjectId(config2);
28948
- if (telemetryProjectId) {
29076
+ {
28949
29077
  const adapterEmit = adapter2.emitTelemetry?.bind(adapter2) ?? null;
28950
29078
  if (adapterEmit) {
28951
29079
  adapterEmit({
@@ -29400,7 +29528,7 @@ async function dispatchRequest(args) {
29400
29528
  var __dirname = dirname6(fileURLToPath4(import.meta.url));
29401
29529
  var pkgVersion = "unknown";
29402
29530
  try {
29403
- const pkg = JSON.parse(readFileSync15(join23(__dirname, "..", "package.json"), "utf-8"));
29531
+ const pkg = JSON.parse(readFileSync16(join24(__dirname, "..", "package.json"), "utf-8"));
29404
29532
  pkgVersion = pkg.version;
29405
29533
  } catch {
29406
29534
  }