@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/backfill-cycle-metrics.js +55 -29
- package/dist/index.js +373 -245
- package/package.json +1 -1
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
|
|
4237
|
+
import { existsSync as existsSync11, readFileSync as readFileSync12 } from "fs";
|
|
4231
4238
|
import { homedir as homedir4 } from "os";
|
|
4232
|
-
import { join as
|
|
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
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
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 (!
|
|
4255
|
+
if (!existsSync11(path7)) continue;
|
|
4249
4256
|
try {
|
|
4250
|
-
const raw =
|
|
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
|
|
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
|
|
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 (!
|
|
4537
|
+
if (!existsSync12(path7)) continue;
|
|
4531
4538
|
let raw;
|
|
4532
4539
|
let parsed;
|
|
4533
4540
|
try {
|
|
4534
|
-
raw =
|
|
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
|
-
|
|
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
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
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
|
|
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
|
|
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 =
|
|
4654
|
-
if (!
|
|
4660
|
+
const path7 = join22(projectPath, ".mcp.json");
|
|
4661
|
+
if (!existsSync13(path7)) return { servers: [] };
|
|
4655
4662
|
try {
|
|
4656
|
-
const parsed = JSON.parse(
|
|
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(
|
|
4689
|
-
agentSkills: safeListDirs(
|
|
4690
|
-
agents: safeListFiles(
|
|
4691
|
-
hooks: safeListFiles(
|
|
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 =
|
|
4699
|
-
if (
|
|
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 (!
|
|
4717
|
+
if (!existsSync13(GLOBAL_CLAUDE_JSON)) return [];
|
|
4711
4718
|
try {
|
|
4712
|
-
const parsed = JSON.parse(
|
|
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 = [
|
|
4865
|
-
GLOBAL_SKILLS_DIR =
|
|
4866
|
-
GLOBAL_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
|
|
4879
|
-
import { join as
|
|
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 =
|
|
4918
|
+
const path7 = join23(process.cwd(), ".mcp.json");
|
|
4912
4919
|
let parsed = {};
|
|
4913
|
-
if (
|
|
4920
|
+
if (existsSync14(path7)) {
|
|
4914
4921
|
try {
|
|
4915
|
-
parsed = JSON.parse(
|
|
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
|
-
|
|
4943
|
+
writeFileSync7(path7, JSON.stringify(parsed, null, 2) + "\n", "utf-8");
|
|
4937
4944
|
try {
|
|
4938
|
-
const mode =
|
|
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
|
|
5047
|
-
import { dirname as dirname6, join as
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
8324
|
-
|
|
8325
|
-
|
|
8326
|
-
|
|
8327
|
-
|
|
8328
|
-
|
|
8329
|
-
|
|
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
|
-
|
|
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: [{
|
|
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
|
|
8465
|
+
const avgPlanned = Math.round(
|
|
8413
8466
|
recentVelocities.reduce((sum, v) => sum + v.effortPoints, 0) / recentVelocities.length * 10
|
|
8414
8467
|
) / 10;
|
|
8415
|
-
|
|
8416
|
-
|
|
8417
|
-
|
|
8418
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
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 =
|
|
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 =
|
|
14445
|
-
if (
|
|
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 =
|
|
14449
|
-
const stat4 =
|
|
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 =
|
|
14466
|
-
if (hasLocalWorkspace() &&
|
|
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(
|
|
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:
|
|
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
|
|
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
|
|
16616
|
-
import { readdirSync as readdirSync2, statSync as
|
|
16617
|
-
import { join as
|
|
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 (
|
|
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 (
|
|
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
|
|
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
|
|
16654
|
-
import { dirname, join as
|
|
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 =
|
|
16784
|
+
var PROJECT_BUNDLE_REL = join6(".agents", "skills", "papi-cycle");
|
|
16657
16785
|
function bundleDestRel(rel) {
|
|
16658
|
-
return rel === "AGENTS.md" ? "AGENTS.md" :
|
|
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 =
|
|
16664
|
-
if (
|
|
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 || !
|
|
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 =
|
|
16677
|
-
const childRel = rel ?
|
|
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:
|
|
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 =
|
|
16689
|
-
if (opts.skipExisting &&
|
|
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
|
|
16698
|
-
import { dirname as dirname2, join as
|
|
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:
|
|
16702
|
-
{ srcRel:
|
|
16703
|
-
{ srcRel:
|
|
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 =
|
|
16710
|
-
if (
|
|
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 =
|
|
16723
|
-
if (!
|
|
16724
|
-
const dest = projectRoot ?
|
|
16725
|
-
if (opts.skipExisting && projectRoot &&
|
|
16726
|
-
out.push({ dest, content:
|
|
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
|
|
16733
|
-
import { join as
|
|
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 =
|
|
16736
|
-
if (!
|
|
16863
|
+
const path7 = join8(projectRoot, "package.json");
|
|
16864
|
+
if (!existsSync6(path7)) return null;
|
|
16737
16865
|
try {
|
|
16738
|
-
const raw =
|
|
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 =
|
|
16760
|
-
if (!
|
|
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 =
|
|
16770
|
-
if (!
|
|
16897
|
+
const path7 = join8(projectRoot, ".env.example");
|
|
16898
|
+
if (!existsSync6(path7)) return false;
|
|
16771
16899
|
try {
|
|
16772
|
-
const raw =
|
|
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 (
|
|
16780
|
-
const vercelDir =
|
|
16781
|
-
if (!
|
|
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
|
|
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(
|
|
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 :
|
|
17288
|
-
const docsDir = useCollector ? docsRel :
|
|
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" :
|
|
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` :
|
|
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` :
|
|
17313
|
-
[useCollector ? `${commandsRel}/test.md` :
|
|
17314
|
-
[useCollector ? `${docsRel}/README.md` :
|
|
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 =
|
|
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 =
|
|
17350
|
-
const cursorRulesPath =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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(
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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(
|
|
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(
|
|
18207
|
+
await access2(join9(projectRoot, ".git"));
|
|
18080
18208
|
} catch {
|
|
18081
18209
|
return void 0;
|
|
18082
18210
|
}
|
|
18083
|
-
const gitignorePath =
|
|
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
|
|
18599
|
-
import { join as
|
|
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
|
|
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 =
|
|
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 =
|
|
20577
|
-
if (!
|
|
20704
|
+
const papiDir = join11(projectRoot, ".papi");
|
|
20705
|
+
if (!existsSync7(papiDir)) {
|
|
20578
20706
|
mkdirSync2(papiDir, { recursive: true });
|
|
20579
20707
|
}
|
|
20580
|
-
const scopePath =
|
|
20581
|
-
|
|
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 =
|
|
20585
|
-
if (
|
|
20586
|
-
|
|
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 =
|
|
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 =
|
|
20985
|
-
if (
|
|
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 =
|
|
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(
|
|
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
|
|
21156
|
-
import { join as
|
|
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 && !
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
21429
|
-
if (
|
|
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(
|
|
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(
|
|
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
|
|
22875
|
-
import { join as
|
|
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 =
|
|
22880
|
-
const raw =
|
|
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
|
|
23456
|
-
import { join as
|
|
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 =
|
|
23679
|
-
if (
|
|
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
|
-
${
|
|
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 =
|
|
23838
|
-
if (
|
|
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]
|
|
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
|
|
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 =
|
|
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(
|
|
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:
|
|
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
|
|
25555
|
-
import { join as
|
|
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 =
|
|
25871
|
-
const pkg = JSON.parse(
|
|
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 =
|
|
26542
|
-
if (!
|
|
26543
|
-
const content =
|
|
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
|
-
|
|
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
|
|
27276
|
-
import { join as
|
|
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 =
|
|
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
|
-
|
|
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
|
|
28163
|
-
import { createHash as
|
|
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 =
|
|
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(
|
|
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
|
|
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(
|
|
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 =
|
|
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(
|
|
28348
|
+
description: await readSkillDescription(join18(skillsDir, d.name)),
|
|
28221
28349
|
version,
|
|
28222
28350
|
status: stale.has(d.name) ? "stale_fork" : "ok",
|
|
28223
|
-
path:
|
|
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(
|
|
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:
|
|
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(
|
|
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 =
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
29531
|
+
const pkg = JSON.parse(readFileSync16(join24(__dirname, "..", "package.json"), "utf-8"));
|
|
29404
29532
|
pkgVersion = pkg.version;
|
|
29405
29533
|
} catch {
|
|
29406
29534
|
}
|