@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/backfill-cycle-metrics.js +47 -22
- package/dist/index.js +351 -232
- package/package.json +2 -2
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
|
|
4236
|
+
import { existsSync as existsSync11, readFileSync as readFileSync12 } from "fs";
|
|
4231
4237
|
import { homedir as homedir4 } from "os";
|
|
4232
|
-
import { join as
|
|
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
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
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 (!
|
|
4254
|
+
if (!existsSync11(path7)) continue;
|
|
4249
4255
|
try {
|
|
4250
|
-
const raw =
|
|
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
|
|
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
|
|
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 (!
|
|
4536
|
+
if (!existsSync12(path7)) continue;
|
|
4531
4537
|
let raw;
|
|
4532
4538
|
let parsed;
|
|
4533
4539
|
try {
|
|
4534
|
-
raw =
|
|
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
|
-
|
|
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
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
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
|
|
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
|
|
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 =
|
|
4654
|
-
if (!
|
|
4659
|
+
const path7 = join22(projectPath, ".mcp.json");
|
|
4660
|
+
if (!existsSync13(path7)) return { servers: [] };
|
|
4655
4661
|
try {
|
|
4656
|
-
const parsed = JSON.parse(
|
|
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(
|
|
4689
|
-
agentSkills: safeListDirs(
|
|
4690
|
-
agents: safeListFiles(
|
|
4691
|
-
hooks: safeListFiles(
|
|
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 =
|
|
4699
|
-
if (
|
|
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 (!
|
|
4716
|
+
if (!existsSync13(GLOBAL_CLAUDE_JSON)) return [];
|
|
4711
4717
|
try {
|
|
4712
|
-
const parsed = JSON.parse(
|
|
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 = [
|
|
4865
|
-
GLOBAL_SKILLS_DIR =
|
|
4866
|
-
GLOBAL_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
|
|
4879
|
-
import { join as
|
|
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 =
|
|
4917
|
+
const path7 = join23(process.cwd(), ".mcp.json");
|
|
4912
4918
|
let parsed = {};
|
|
4913
|
-
if (
|
|
4919
|
+
if (existsSync14(path7)) {
|
|
4914
4920
|
try {
|
|
4915
|
-
parsed = JSON.parse(
|
|
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
|
-
|
|
4942
|
+
writeFileSync7(path7, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
4937
4943
|
try {
|
|
4938
|
-
const mode =
|
|
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
|
|
5047
|
-
import { dirname as dirname6, join as
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
8324
|
-
|
|
8325
|
-
|
|
8326
|
-
|
|
8327
|
-
|
|
8328
|
-
|
|
8329
|
-
|
|
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
|
-
|
|
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: [{
|
|
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
|
|
8461
|
+
const avgPlanned = Math.round(
|
|
8413
8462
|
recentVelocities.reduce((sum, v) => sum + v.effortPoints, 0) / recentVelocities.length * 10
|
|
8414
8463
|
) / 10;
|
|
8415
|
-
|
|
8416
|
-
|
|
8417
|
-
lines.push(
|
|
8418
|
-
lines.push(
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
14445
|
-
if (
|
|
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 =
|
|
14449
|
-
const stat4 =
|
|
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 =
|
|
14466
|
-
if (hasLocalWorkspace() &&
|
|
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(
|
|
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:
|
|
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
|
|
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
|
|
16616
|
-
import { readdirSync as readdirSync2, statSync as
|
|
16617
|
-
import { join as
|
|
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 (
|
|
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 (
|
|
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
|
|
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
|
|
16654
|
-
import { dirname, join as
|
|
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 =
|
|
16775
|
+
var PROJECT_BUNDLE_REL = join6(".agents", "skills", "papi-cycle");
|
|
16657
16776
|
function bundleDestRel(rel) {
|
|
16658
|
-
return rel === "AGENTS.md" ? "AGENTS.md" :
|
|
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 =
|
|
16664
|
-
if (
|
|
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 || !
|
|
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 =
|
|
16677
|
-
const childRel = rel ?
|
|
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:
|
|
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 =
|
|
16689
|
-
if (opts.skipExisting &&
|
|
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
|
|
16698
|
-
import { dirname as dirname2, join as
|
|
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:
|
|
16702
|
-
{ srcRel:
|
|
16703
|
-
{ srcRel:
|
|
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 =
|
|
16710
|
-
if (
|
|
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 =
|
|
16723
|
-
if (!
|
|
16724
|
-
const dest = projectRoot ?
|
|
16725
|
-
if (opts.skipExisting && projectRoot &&
|
|
16726
|
-
out.push({ dest, content:
|
|
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
|
|
16733
|
-
import { join as
|
|
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 =
|
|
16736
|
-
if (!
|
|
16854
|
+
const path7 = join8(projectRoot, "package.json");
|
|
16855
|
+
if (!existsSync6(path7)) return null;
|
|
16737
16856
|
try {
|
|
16738
|
-
const raw =
|
|
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 =
|
|
16760
|
-
if (!
|
|
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 =
|
|
16770
|
-
if (!
|
|
16888
|
+
const path7 = join8(projectRoot, ".env.example");
|
|
16889
|
+
if (!existsSync6(path7)) return false;
|
|
16771
16890
|
try {
|
|
16772
|
-
const raw =
|
|
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 (
|
|
16780
|
-
const vercelDir =
|
|
16781
|
-
if (!
|
|
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
|
|
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(
|
|
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 :
|
|
17288
|
-
const docsDir = useCollector ? docsRel :
|
|
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" :
|
|
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` :
|
|
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` :
|
|
17313
|
-
[useCollector ? `${commandsRel}/test.md` :
|
|
17314
|
-
[useCollector ? `${docsRel}/README.md` :
|
|
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 =
|
|
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 =
|
|
17350
|
-
const cursorRulesPath =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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(
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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(
|
|
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(
|
|
18198
|
+
await access2(join9(projectRoot, ".git"));
|
|
18080
18199
|
} catch {
|
|
18081
18200
|
return void 0;
|
|
18082
18201
|
}
|
|
18083
|
-
const gitignorePath =
|
|
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
|
|
18599
|
-
import { join as
|
|
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
|
|
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 =
|
|
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 =
|
|
20577
|
-
if (!
|
|
20695
|
+
const papiDir = join11(projectRoot, ".papi");
|
|
20696
|
+
if (!existsSync7(papiDir)) {
|
|
20578
20697
|
mkdirSync2(papiDir, { recursive: true });
|
|
20579
20698
|
}
|
|
20580
|
-
const scopePath =
|
|
20581
|
-
|
|
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 =
|
|
20585
|
-
if (
|
|
20586
|
-
|
|
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 =
|
|
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 =
|
|
20985
|
-
if (
|
|
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 =
|
|
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(
|
|
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
|
|
21156
|
-
import { join as
|
|
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 && !
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
21429
|
-
if (
|
|
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(
|
|
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(
|
|
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
|
|
22875
|
-
import { join as
|
|
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 =
|
|
22880
|
-
const raw =
|
|
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
|
|
23456
|
-
import { join as
|
|
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 =
|
|
23679
|
-
if (
|
|
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
|
-
${
|
|
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 =
|
|
23838
|
-
if (
|
|
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
|
|
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 =
|
|
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(
|
|
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:
|
|
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
|
|
25555
|
-
import { join as
|
|
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 =
|
|
25871
|
-
const pkg = JSON.parse(
|
|
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 =
|
|
26542
|
-
if (!
|
|
26543
|
-
const content =
|
|
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
|
-
|
|
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
|
|
27276
|
-
import { join as
|
|
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 =
|
|
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
|
-
|
|
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
|
|
28163
|
-
import { createHash as
|
|
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 =
|
|
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(
|
|
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
|
|
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(
|
|
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 =
|
|
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(
|
|
28339
|
+
description: await readSkillDescription(join18(skillsDir, d.name)),
|
|
28221
28340
|
version,
|
|
28222
28341
|
status: stale.has(d.name) ? "stale_fork" : "ok",
|
|
28223
|
-
path:
|
|
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(
|
|
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:
|
|
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(
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
29522
|
+
const pkg = JSON.parse(readFileSync16(join24(__dirname, "..", "package.json"), "utf-8"));
|
|
29404
29523
|
pkgVersion = pkg.version;
|
|
29405
29524
|
} catch {
|
|
29406
29525
|
}
|