@papi-ai/server 0.7.26 → 0.7.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +329 -117
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -15950,13 +15950,23 @@ ${result.userMessage}
|
|
|
15950
15950
|
}
|
|
15951
15951
|
|
|
15952
15952
|
// src/services/strategy.ts
|
|
15953
|
-
init_dist2();
|
|
15954
15953
|
import { randomUUID as randomUUID8, createHash as createHash2 } from "crypto";
|
|
15955
15954
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
15956
15955
|
import { existsSync, readdirSync, statSync } from "fs";
|
|
15957
15956
|
import { join as join2 } from "path";
|
|
15958
15957
|
import { homedir } from "os";
|
|
15959
15958
|
|
|
15959
|
+
// src/lib/hosted-mode.ts
|
|
15960
|
+
function isHostedTransport() {
|
|
15961
|
+
return Boolean(process.env.PORT || process.env.PAPI_HTTP_PORT);
|
|
15962
|
+
}
|
|
15963
|
+
function hasLocalWorkspace() {
|
|
15964
|
+
return !isHostedTransport();
|
|
15965
|
+
}
|
|
15966
|
+
|
|
15967
|
+
// src/services/strategy.ts
|
|
15968
|
+
init_dist2();
|
|
15969
|
+
|
|
15960
15970
|
// src/lib/value-report.ts
|
|
15961
15971
|
var MIN_SNAPSHOTS = 5;
|
|
15962
15972
|
var MAX_SNAPSHOTS = 10;
|
|
@@ -16433,7 +16443,7 @@ ${lines.join("\n")}`;
|
|
|
16433
16443
|
let unregisteredDocsText;
|
|
16434
16444
|
try {
|
|
16435
16445
|
const docsDir = join2(projectRoot, "docs");
|
|
16436
|
-
if (existsSync(docsDir)) {
|
|
16446
|
+
if (hasLocalWorkspace() && existsSync(docsDir)) {
|
|
16437
16447
|
const registeredPaths = new Set(
|
|
16438
16448
|
(registeredDocs ?? []).map((d) => d.path).filter(Boolean)
|
|
16439
16449
|
);
|
|
@@ -18786,6 +18796,56 @@ Run \`orient\` at the start of every session to get cycle state, in-progress tas
|
|
|
18786
18796
|
- After build_execute completes: run \`review_submit\` with the verdict before presenting for human review
|
|
18787
18797
|
`;
|
|
18788
18798
|
|
|
18799
|
+
// src/lib/files-to-write.ts
|
|
18800
|
+
function sanitiseProjectPath(path7) {
|
|
18801
|
+
if (!path7 || typeof path7 !== "string") {
|
|
18802
|
+
throw new Error("files_to_write: path must be a non-empty string");
|
|
18803
|
+
}
|
|
18804
|
+
if (path7.startsWith("/") || /^[A-Za-z]:[\\/]/.test(path7)) {
|
|
18805
|
+
throw new Error(`files_to_write: absolute path rejected: ${path7}`);
|
|
18806
|
+
}
|
|
18807
|
+
const segments = path7.split(/[\\/]/);
|
|
18808
|
+
if (segments.some((s) => s === "..")) {
|
|
18809
|
+
throw new Error(`files_to_write: path traversal rejected: ${path7}`);
|
|
18810
|
+
}
|
|
18811
|
+
return segments.filter((s) => s.length > 0).join("/");
|
|
18812
|
+
}
|
|
18813
|
+
var FileWriteCollector = class {
|
|
18814
|
+
entries = [];
|
|
18815
|
+
add(entry) {
|
|
18816
|
+
this.entries.push({ ...entry, path: sanitiseProjectPath(entry.path) });
|
|
18817
|
+
}
|
|
18818
|
+
isEmpty() {
|
|
18819
|
+
return this.entries.length === 0;
|
|
18820
|
+
}
|
|
18821
|
+
list() {
|
|
18822
|
+
return this.entries;
|
|
18823
|
+
}
|
|
18824
|
+
};
|
|
18825
|
+
function formatFilesToWriteSection(collector) {
|
|
18826
|
+
if (collector.isEmpty()) return "";
|
|
18827
|
+
const json = JSON.stringify(collector.list(), null, 2);
|
|
18828
|
+
return [
|
|
18829
|
+
"",
|
|
18830
|
+
"---",
|
|
18831
|
+
"",
|
|
18832
|
+
"**SCAFFOLDING FILES \u2014 WRITE THESE LOCALLY**",
|
|
18833
|
+
"",
|
|
18834
|
+
"This MCP connection is remote \u2014 the server cannot write files to your project directory.",
|
|
18835
|
+
"Use your Write tool to materialise each entry below into the user's project root (your current working directory).",
|
|
18836
|
+
"",
|
|
18837
|
+
"```json files_to_write",
|
|
18838
|
+
json,
|
|
18839
|
+
"```",
|
|
18840
|
+
"",
|
|
18841
|
+
"Apply each entry in order. Semantics:",
|
|
18842
|
+
'- `mode: "create"` \u2014 create the file with `content`. If `skip_if_exists: true`, skip when the file already exists.',
|
|
18843
|
+
'- `mode: "overwrite"` \u2014 replace the file fully with `content`.',
|
|
18844
|
+
'- `mode: "append"` \u2014 append `content` to the existing file (create it first if missing).',
|
|
18845
|
+
""
|
|
18846
|
+
].join("\n");
|
|
18847
|
+
}
|
|
18848
|
+
|
|
18789
18849
|
// src/services/setup.ts
|
|
18790
18850
|
var FILE_TEMPLATES = {
|
|
18791
18851
|
"PLANNING_LOG.md": PLANNING_LOG_TEMPLATE,
|
|
@@ -18809,7 +18869,7 @@ function substitute(template, vars) {
|
|
|
18809
18869
|
}
|
|
18810
18870
|
return result;
|
|
18811
18871
|
}
|
|
18812
|
-
async function scaffoldPapiDir(adapter2, config2, input) {
|
|
18872
|
+
async function scaffoldPapiDir(adapter2, config2, input, collector) {
|
|
18813
18873
|
const isPg = config2.adapterType === "pg" || config2.adapterType === "proxy";
|
|
18814
18874
|
const vars = {
|
|
18815
18875
|
project_name: input.projectName,
|
|
@@ -18839,31 +18899,37 @@ async function scaffoldPapiDir(adapter2, config2, input) {
|
|
|
18839
18899
|
} catch {
|
|
18840
18900
|
}
|
|
18841
18901
|
}
|
|
18842
|
-
|
|
18843
|
-
|
|
18902
|
+
const useCollector = config2.adapterType === "proxy";
|
|
18903
|
+
const docsRel = "docs";
|
|
18904
|
+
const commandsRel = ".claude/commands";
|
|
18905
|
+
const commandsDir = useCollector ? commandsRel : join5(config2.projectRoot, ".claude", "commands");
|
|
18906
|
+
const docsDir = useCollector ? docsRel : join5(config2.projectRoot, "docs");
|
|
18907
|
+
if (!useCollector) {
|
|
18908
|
+
await mkdir(commandsDir, { recursive: true });
|
|
18909
|
+
await mkdir(docsDir, { recursive: true });
|
|
18844
18910
|
}
|
|
18845
|
-
const
|
|
18846
|
-
const docsDir = join5(config2.projectRoot, "docs");
|
|
18847
|
-
await mkdir(commandsDir, { recursive: true });
|
|
18848
|
-
await mkdir(docsDir, { recursive: true });
|
|
18849
|
-
const claudeMdPath = join5(config2.projectRoot, "CLAUDE.md");
|
|
18911
|
+
const claudeMdPath = useCollector ? "CLAUDE.md" : join5(config2.projectRoot, "CLAUDE.md");
|
|
18850
18912
|
let claudeMdExists = false;
|
|
18851
|
-
|
|
18852
|
-
|
|
18853
|
-
|
|
18854
|
-
|
|
18913
|
+
if (!useCollector) {
|
|
18914
|
+
try {
|
|
18915
|
+
await access2(claudeMdPath);
|
|
18916
|
+
claudeMdExists = true;
|
|
18917
|
+
} catch {
|
|
18918
|
+
}
|
|
18855
18919
|
}
|
|
18856
|
-
const docsIndexPath = join5(docsDir, "INDEX.md");
|
|
18920
|
+
const docsIndexPath = useCollector ? `${docsRel}/INDEX.md` : join5(docsDir, "INDEX.md");
|
|
18857
18921
|
let docsIndexExists = false;
|
|
18858
|
-
|
|
18859
|
-
|
|
18860
|
-
|
|
18861
|
-
|
|
18922
|
+
if (!useCollector) {
|
|
18923
|
+
try {
|
|
18924
|
+
await access2(docsIndexPath);
|
|
18925
|
+
docsIndexExists = true;
|
|
18926
|
+
} catch {
|
|
18927
|
+
}
|
|
18862
18928
|
}
|
|
18863
18929
|
const scaffoldFiles = {
|
|
18864
|
-
[join5(commandsDir, "papi-audit.md")]: PAPI_AUDIT_COMMAND_TEMPLATE,
|
|
18865
|
-
[join5(commandsDir, "test.md")]: TEST_COMMAND_TEMPLATE,
|
|
18866
|
-
[join5(docsDir, "README.md")]: substitute(DOCS_README_TEMPLATE, vars)
|
|
18930
|
+
[useCollector ? `${commandsRel}/papi-audit.md` : join5(commandsDir, "papi-audit.md")]: PAPI_AUDIT_COMMAND_TEMPLATE,
|
|
18931
|
+
[useCollector ? `${commandsRel}/test.md` : join5(commandsDir, "test.md")]: TEST_COMMAND_TEMPLATE,
|
|
18932
|
+
[useCollector ? `${docsRel}/README.md` : join5(docsDir, "README.md")]: substitute(DOCS_README_TEMPLATE, vars)
|
|
18867
18933
|
};
|
|
18868
18934
|
if (!docsIndexExists) {
|
|
18869
18935
|
scaffoldFiles[docsIndexPath] = substitute(DOCS_INDEX_TEMPLATE, vars);
|
|
@@ -18880,29 +18946,42 @@ async function scaffoldPapiDir(adapter2, config2, input) {
|
|
|
18880
18946
|
} catch {
|
|
18881
18947
|
}
|
|
18882
18948
|
}
|
|
18883
|
-
|
|
18884
|
-
|
|
18949
|
+
const bundleRoot = useCollector ? "" : config2.projectRoot;
|
|
18950
|
+
for (const [dest, content] of Object.entries(planBundleInstall(bundleRoot, input.projectName, { skipExisting: true }))) {
|
|
18951
|
+
if (!useCollector) {
|
|
18952
|
+
await mkdir(dirname2(dest), { recursive: true });
|
|
18953
|
+
}
|
|
18885
18954
|
scaffoldFiles[dest] = content;
|
|
18886
18955
|
}
|
|
18887
|
-
|
|
18888
|
-
|
|
18889
|
-
|
|
18890
|
-
|
|
18891
|
-
cursorDetected =
|
|
18892
|
-
} catch {
|
|
18893
|
-
}
|
|
18894
|
-
if (cursorDetected) {
|
|
18895
|
-
const cursorRulesDir = join5(cursorDir, "rules");
|
|
18896
|
-
const cursorRulesPath = join5(cursorRulesDir, "papi.mdc");
|
|
18897
|
-
await mkdir(cursorRulesDir, { recursive: true });
|
|
18956
|
+
if (useCollector) {
|
|
18957
|
+
scaffoldFiles[".cursor/rules/papi.mdc"] = substitute(CURSOR_RULES_TEMPLATE, vars);
|
|
18958
|
+
} else {
|
|
18959
|
+
const cursorDir = join5(config2.projectRoot, ".cursor");
|
|
18960
|
+
let cursorDetected = false;
|
|
18898
18961
|
try {
|
|
18899
|
-
await access2(
|
|
18962
|
+
await access2(cursorDir);
|
|
18963
|
+
cursorDetected = true;
|
|
18900
18964
|
} catch {
|
|
18901
|
-
|
|
18965
|
+
}
|
|
18966
|
+
if (cursorDetected) {
|
|
18967
|
+
const cursorRulesDir = join5(cursorDir, "rules");
|
|
18968
|
+
const cursorRulesPath = join5(cursorRulesDir, "papi.mdc");
|
|
18969
|
+
await mkdir(cursorRulesDir, { recursive: true });
|
|
18970
|
+
try {
|
|
18971
|
+
await access2(cursorRulesPath);
|
|
18972
|
+
} catch {
|
|
18973
|
+
scaffoldFiles[cursorRulesPath] = substitute(CURSOR_RULES_TEMPLATE, vars);
|
|
18974
|
+
}
|
|
18902
18975
|
}
|
|
18903
18976
|
}
|
|
18904
|
-
|
|
18905
|
-
|
|
18977
|
+
if (useCollector) {
|
|
18978
|
+
for (const [relPath, content] of Object.entries(scaffoldFiles)) {
|
|
18979
|
+
collector.add({ path: relPath, content, mode: "create", skip_if_exists: true });
|
|
18980
|
+
}
|
|
18981
|
+
} else {
|
|
18982
|
+
for (const [filepath, content] of Object.entries(scaffoldFiles)) {
|
|
18983
|
+
await writeFile2(filepath, content, "utf-8");
|
|
18984
|
+
}
|
|
18906
18985
|
}
|
|
18907
18986
|
if (!isPg) {
|
|
18908
18987
|
await adapter2.writePhases([{
|
|
@@ -18914,7 +18993,16 @@ async function scaffoldPapiDir(adapter2, config2, input) {
|
|
|
18914
18993
|
order: 0
|
|
18915
18994
|
}]);
|
|
18916
18995
|
}
|
|
18917
|
-
|
|
18996
|
+
if (useCollector) {
|
|
18997
|
+
collector.add({
|
|
18998
|
+
path: ".claude/settings.json",
|
|
18999
|
+
content: JSON.stringify({ permissions: { allow: [PAPI_PERMISSION] } }, null, 2) + "\n",
|
|
19000
|
+
mode: "create",
|
|
19001
|
+
skip_if_exists: true
|
|
19002
|
+
});
|
|
19003
|
+
} else {
|
|
19004
|
+
await ensurePapiPermission(config2.projectRoot);
|
|
19005
|
+
}
|
|
18918
19006
|
return true;
|
|
18919
19007
|
}
|
|
18920
19008
|
var PAPI_PERMISSION = "mcp__papi__*";
|
|
@@ -18943,7 +19031,7 @@ async function ensurePapiPermission(projectRoot) {
|
|
|
18943
19031
|
} catch {
|
|
18944
19032
|
}
|
|
18945
19033
|
}
|
|
18946
|
-
async function applySetupOutputs(adapter2, config2, input, briefText, adSeedText, conventionsText) {
|
|
19034
|
+
async function applySetupOutputs(adapter2, config2, input, collector, briefText, adSeedText, conventionsText) {
|
|
18947
19035
|
const warnings = [];
|
|
18948
19036
|
await adapter2.updateProductBrief(briefText);
|
|
18949
19037
|
const briefPhases = parsePhases(briefText);
|
|
@@ -19006,12 +19094,20 @@ async function applySetupOutputs(adapter2, config2, input, briefText, adSeedText
|
|
|
19006
19094
|
}
|
|
19007
19095
|
}
|
|
19008
19096
|
}
|
|
19009
|
-
if (conventionsText?.trim()
|
|
19010
|
-
|
|
19011
|
-
|
|
19012
|
-
|
|
19013
|
-
|
|
19014
|
-
|
|
19097
|
+
if (conventionsText?.trim()) {
|
|
19098
|
+
if (config2.adapterType === "proxy") {
|
|
19099
|
+
collector.add({
|
|
19100
|
+
path: "CLAUDE.md",
|
|
19101
|
+
content: "\n" + conventionsText.trim() + "\n",
|
|
19102
|
+
mode: "append"
|
|
19103
|
+
});
|
|
19104
|
+
} else {
|
|
19105
|
+
try {
|
|
19106
|
+
const claudeMdPath = join5(config2.projectRoot, "CLAUDE.md");
|
|
19107
|
+
const existing = await readFile4(claudeMdPath, "utf-8");
|
|
19108
|
+
await writeFile2(claudeMdPath, existing + "\n" + conventionsText.trim() + "\n", "utf-8");
|
|
19109
|
+
} catch {
|
|
19110
|
+
}
|
|
19015
19111
|
}
|
|
19016
19112
|
}
|
|
19017
19113
|
return { seededAds, warnings };
|
|
@@ -19186,7 +19282,8 @@ function formatCodebaseSummary(scan, sourceContents) {
|
|
|
19186
19282
|
return parts.join("\n");
|
|
19187
19283
|
}
|
|
19188
19284
|
async function prepareSetup(adapter2, config2, input) {
|
|
19189
|
-
const
|
|
19285
|
+
const prepareCollector = new FileWriteCollector();
|
|
19286
|
+
const createdProject = await scaffoldPapiDir(adapter2, config2, input, prepareCollector);
|
|
19190
19287
|
let existingBrief;
|
|
19191
19288
|
try {
|
|
19192
19289
|
existingBrief = await adapter2.readProductBrief();
|
|
@@ -19291,11 +19388,13 @@ async function prepareSetup(adapter2, config2, input) {
|
|
|
19291
19388
|
autoDetected: autoDetected && detectedCodebaseType !== "new_project",
|
|
19292
19389
|
briefAlreadyExists: effectiveBriefAlreadyExists,
|
|
19293
19390
|
briefWillRegenerate,
|
|
19294
|
-
briefRegenReason
|
|
19391
|
+
briefRegenReason,
|
|
19392
|
+
filesToWrite: prepareCollector.isEmpty() ? void 0 : prepareCollector
|
|
19295
19393
|
};
|
|
19296
19394
|
}
|
|
19297
19395
|
async function applySetup(adapter2, config2, input, briefText, adSeedText, conventionsText, initialTasksText) {
|
|
19298
|
-
const
|
|
19396
|
+
const collector = new FileWriteCollector();
|
|
19397
|
+
const createdProject = await scaffoldPapiDir(adapter2, config2, input, collector);
|
|
19299
19398
|
const TEMPLATE_MARKER = "*Describe your project's core value proposition here.*";
|
|
19300
19399
|
let effectiveBriefText = briefText;
|
|
19301
19400
|
let briefRegenerated = false;
|
|
@@ -19320,7 +19419,7 @@ async function applySetup(adapter2, config2, input, briefText, adSeedText, conve
|
|
|
19320
19419
|
} catch {
|
|
19321
19420
|
}
|
|
19322
19421
|
}
|
|
19323
|
-
const { seededAds, warnings } = await applySetupOutputs(adapter2, config2, input, effectiveBriefText, adSeedText, conventionsText);
|
|
19422
|
+
const { seededAds, warnings } = await applySetupOutputs(adapter2, config2, input, collector, effectiveBriefText, adSeedText, conventionsText);
|
|
19324
19423
|
let createdTasks = 0;
|
|
19325
19424
|
let tasksSkipped = 0;
|
|
19326
19425
|
if (initialTasksText?.trim()) {
|
|
@@ -19367,29 +19466,33 @@ async function applySetup(adapter2, config2, input, briefText, adSeedText, conve
|
|
|
19367
19466
|
if (msg.startsWith("100% overlap")) throw err;
|
|
19368
19467
|
}
|
|
19369
19468
|
}
|
|
19370
|
-
|
|
19371
|
-
|
|
19372
|
-
|
|
19373
|
-
|
|
19374
|
-
|
|
19375
|
-
|
|
19376
|
-
|
|
19377
|
-
|
|
19378
|
-
|
|
19379
|
-
|
|
19380
|
-
|
|
19381
|
-
|
|
19382
|
-
|
|
19383
|
-
|
|
19384
|
-
|
|
19385
|
-
|
|
19386
|
-
|
|
19387
|
-
|
|
19388
|
-
|
|
19389
|
-
|
|
19390
|
-
|
|
19469
|
+
{
|
|
19470
|
+
const dogfoodSection = [
|
|
19471
|
+
"",
|
|
19472
|
+
"## Dogfood Logging",
|
|
19473
|
+
"",
|
|
19474
|
+
"After each `release`, append a dogfood entry capturing observations from the cycle.",
|
|
19475
|
+
"Call the adapter method with structured entries for each observation:",
|
|
19476
|
+
"",
|
|
19477
|
+
"- **friction** \u2014 workflow pain points, confusing flows, things that broke or slowed you down",
|
|
19478
|
+
"- **methodology** \u2014 what worked or didn't in the plan/build/review cycle",
|
|
19479
|
+
"- **signal** \u2014 indicators of product-market fit, user value, or growth potential",
|
|
19480
|
+
"- **commercial** \u2014 cost, pricing, or business model observations",
|
|
19481
|
+
"",
|
|
19482
|
+
"This is autonomous plumbing \u2014 log observations after release without asking.",
|
|
19483
|
+
""
|
|
19484
|
+
].join("\n");
|
|
19485
|
+
if (config2.adapterType === "proxy") {
|
|
19486
|
+
collector.add({ path: "CLAUDE.md", content: dogfoodSection, mode: "append" });
|
|
19487
|
+
} else {
|
|
19488
|
+
try {
|
|
19489
|
+
const claudeMdPath = join5(config2.projectRoot, "CLAUDE.md");
|
|
19490
|
+
const existing = await readFile4(claudeMdPath, "utf-8");
|
|
19491
|
+
if (!existing.includes("Dogfood Logging")) {
|
|
19492
|
+
await writeFile2(claudeMdPath, existing + dogfoodSection, "utf-8");
|
|
19493
|
+
}
|
|
19494
|
+
} catch {
|
|
19391
19495
|
}
|
|
19392
|
-
} catch {
|
|
19393
19496
|
}
|
|
19394
19497
|
}
|
|
19395
19498
|
if (adapter2.writeDogfoodEntries) {
|
|
@@ -19412,12 +19515,26 @@ async function applySetup(adapter2, config2, input, briefText, adSeedText, conve
|
|
|
19412
19515
|
});
|
|
19413
19516
|
} catch {
|
|
19414
19517
|
}
|
|
19415
|
-
|
|
19518
|
+
let gitignoreNote;
|
|
19519
|
+
if (config2.adapterType === "proxy") {
|
|
19520
|
+
collector.add({
|
|
19521
|
+
path: ".gitignore",
|
|
19522
|
+
content: "\n# PAPI: may contain credentials, do not commit\n.mcp.json\n",
|
|
19523
|
+
mode: "append"
|
|
19524
|
+
});
|
|
19525
|
+
gitignoreNote = "Added `.mcp.json` to `.gitignore` \u2014 may contain credentials, do not commit.";
|
|
19526
|
+
} else {
|
|
19527
|
+
gitignoreNote = await ensureMcpJsonGitignored(config2.projectRoot);
|
|
19528
|
+
}
|
|
19416
19529
|
let cursorScaffolded = false;
|
|
19417
|
-
|
|
19418
|
-
await access2(join5(config2.projectRoot, ".cursor", "rules", "papi.mdc"));
|
|
19530
|
+
if (config2.adapterType === "proxy") {
|
|
19419
19531
|
cursorScaffolded = true;
|
|
19420
|
-
}
|
|
19532
|
+
} else {
|
|
19533
|
+
try {
|
|
19534
|
+
await access2(join5(config2.projectRoot, ".cursor", "rules", "papi.mdc"));
|
|
19535
|
+
cursorScaffolded = true;
|
|
19536
|
+
} catch {
|
|
19537
|
+
}
|
|
19421
19538
|
}
|
|
19422
19539
|
return {
|
|
19423
19540
|
createdProject,
|
|
@@ -19428,7 +19545,8 @@ async function applySetup(adapter2, config2, input, briefText, adSeedText, conve
|
|
|
19428
19545
|
briefRegenerated: briefRegenerated || void 0,
|
|
19429
19546
|
cursorScaffolded,
|
|
19430
19547
|
gitignoreNote,
|
|
19431
|
-
warnings: warnings.length > 0 ? warnings : void 0
|
|
19548
|
+
warnings: warnings.length > 0 ? warnings : void 0,
|
|
19549
|
+
filesToWrite: collector.isEmpty() ? void 0 : collector
|
|
19432
19550
|
};
|
|
19433
19551
|
}
|
|
19434
19552
|
async function ensureMcpJsonGitignored(projectRoot) {
|
|
@@ -19580,6 +19698,7 @@ ${[created, skipped].filter(Boolean).join(", ")}.`;
|
|
|
19580
19698
|
|
|
19581
19699
|
\u26A0\uFE0F **Setup warnings (non-blocking):**
|
|
19582
19700
|
${result.warnings.map((w) => `- ${w}`).join("\n")}` : "";
|
|
19701
|
+
const filesToWriteSection = result.filesToWrite ? formatFilesToWriteSection(result.filesToWrite) : "";
|
|
19583
19702
|
return textResponse(
|
|
19584
19703
|
`${prefix}Product Brief generated and saved.${briefRegenNote}${adNote}${taskNote}${constraintsHint}${editorNote}${gitignoreNote}${warningsNote}
|
|
19585
19704
|
|
|
@@ -19589,7 +19708,7 @@ Tip: See \`docs/templates/example-project-brief.md\` for an example of a well-wr
|
|
|
19589
19708
|
|
|
19590
19709
|
**Telemetry notice:** PAPI collects anonymous usage data (tool name, duration, project ID) to improve the product. No code, file contents, or personal data is collected. To opt out, add \`"PAPI_TELEMETRY": "off"\` to the \`env\` block in your \`.mcp.json\`.
|
|
19591
19710
|
|
|
19592
|
-
Next step: run \`plan\` to start your first planning cycle
|
|
19711
|
+
Next step: run \`plan\` to start your first planning cycle.${filesToWriteSection}`
|
|
19593
19712
|
);
|
|
19594
19713
|
}
|
|
19595
19714
|
async function handleSetup(adapter2, config2, args) {
|
|
@@ -19750,7 +19869,9 @@ ${result.initialTasksPrompt.user}
|
|
|
19750
19869
|
result.codebaseSummary
|
|
19751
19870
|
);
|
|
19752
19871
|
}
|
|
19753
|
-
|
|
19872
|
+
const responseText = sections.filter(Boolean).join("\n");
|
|
19873
|
+
const filesToWriteSection = result.filesToWrite ? formatFilesToWriteSection(result.filesToWrite) : "";
|
|
19874
|
+
return textResponse(responseText + filesToWriteSection);
|
|
19754
19875
|
}
|
|
19755
19876
|
} catch (err) {
|
|
19756
19877
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -19773,11 +19894,11 @@ ${result.initialTasksPrompt.user}
|
|
|
19773
19894
|
init_dist2();
|
|
19774
19895
|
|
|
19775
19896
|
// src/services/build.ts
|
|
19776
|
-
init_git();
|
|
19777
|
-
init_git();
|
|
19778
19897
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
19779
19898
|
import { readdirSync as readdirSync4, existsSync as existsSync4, readFileSync as readFileSync2, writeFileSync, unlinkSync, mkdirSync } from "fs";
|
|
19780
19899
|
import { join as join6 } from "path";
|
|
19900
|
+
init_git();
|
|
19901
|
+
init_git();
|
|
19781
19902
|
var buildStartTimes = /* @__PURE__ */ new Map();
|
|
19782
19903
|
var taskBranchMap = /* @__PURE__ */ new Map();
|
|
19783
19904
|
var taskStartShaMap = /* @__PURE__ */ new Map();
|
|
@@ -20191,20 +20312,37 @@ async function startBuild(adapter2, config2, taskId, options = {}) {
|
|
|
20191
20312
|
phaseChanges = await propagatePhaseStatus(adapter2);
|
|
20192
20313
|
} catch {
|
|
20193
20314
|
}
|
|
20315
|
+
const collector = new FileWriteCollector();
|
|
20194
20316
|
try {
|
|
20195
|
-
writeActiveTaskScope(
|
|
20317
|
+
writeActiveTaskScope(
|
|
20318
|
+
config2.projectRoot,
|
|
20319
|
+
taskId,
|
|
20320
|
+
task.buildHandoff?.filesLikelyTouched,
|
|
20321
|
+
config2.adapterType,
|
|
20322
|
+
collector
|
|
20323
|
+
);
|
|
20196
20324
|
} catch {
|
|
20197
20325
|
}
|
|
20198
|
-
return {
|
|
20326
|
+
return {
|
|
20327
|
+
task,
|
|
20328
|
+
branchLines,
|
|
20329
|
+
phaseChanges,
|
|
20330
|
+
filesToWrite: collector.isEmpty() ? void 0 : collector
|
|
20331
|
+
};
|
|
20199
20332
|
}
|
|
20200
|
-
function writeActiveTaskScope(projectRoot, taskId, filesLikelyTouched) {
|
|
20333
|
+
function writeActiveTaskScope(projectRoot, taskId, filesLikelyTouched, adapterType, collector) {
|
|
20334
|
+
const lines = [taskId, ...filesLikelyTouched ?? []];
|
|
20335
|
+
const content = lines.join("\n") + "\n";
|
|
20336
|
+
if (adapterType === "proxy") {
|
|
20337
|
+
collector.add({ path: ".papi/active-task-scope.txt", content, mode: "overwrite" });
|
|
20338
|
+
return;
|
|
20339
|
+
}
|
|
20201
20340
|
const papiDir = join6(projectRoot, ".papi");
|
|
20202
20341
|
if (!existsSync4(papiDir)) {
|
|
20203
20342
|
mkdirSync(papiDir, { recursive: true });
|
|
20204
20343
|
}
|
|
20205
20344
|
const scopePath = join6(papiDir, "active-task-scope.txt");
|
|
20206
|
-
|
|
20207
|
-
writeFileSync(scopePath, lines.join("\n") + "\n", "utf-8");
|
|
20345
|
+
writeFileSync(scopePath, content, "utf-8");
|
|
20208
20346
|
}
|
|
20209
20347
|
function clearActiveTaskScope(projectRoot) {
|
|
20210
20348
|
const scopePath = join6(projectRoot, ".papi", "active-task-scope.txt");
|
|
@@ -20527,7 +20665,7 @@ async function completeBuild(adapter2, config2, taskId, input, options = {}) {
|
|
|
20527
20665
|
}
|
|
20528
20666
|
let docWarning;
|
|
20529
20667
|
try {
|
|
20530
|
-
if (adapter2.searchDocs) {
|
|
20668
|
+
if (adapter2.searchDocs && hasLocalWorkspace()) {
|
|
20531
20669
|
const docsDir = join6(config2.projectRoot, "docs");
|
|
20532
20670
|
if (existsSync4(docsDir)) {
|
|
20533
20671
|
const scanDir = (dir, depth = 0) => {
|
|
@@ -21037,7 +21175,8 @@ ${entries}`;
|
|
|
21037
21175
|
}
|
|
21038
21176
|
const moduleInstructions = getModuleInstructions(result.task.module);
|
|
21039
21177
|
const moduleContext = await getModuleContext(adapter2, result.task);
|
|
21040
|
-
|
|
21178
|
+
const filesToWriteSection = result.filesToWrite ? formatFilesToWriteSection(result.filesToWrite) : "";
|
|
21179
|
+
return textResponse(header + serializeBuildHandoff(result.task.buildHandoff) + adSection + moduleInstructions + moduleContext + dogfoodSection + verificationNote + chainInstruction + phaseNote + filesToWriteSection);
|
|
21041
21180
|
} catch (err) {
|
|
21042
21181
|
if (isNoHandoffError(err)) {
|
|
21043
21182
|
const lines = [
|
|
@@ -22186,6 +22325,7 @@ init_git();
|
|
|
22186
22325
|
import { readFileSync as readFileSync3 } from "fs";
|
|
22187
22326
|
import { join as join7 } from "path";
|
|
22188
22327
|
function loadDocsIndex(projectRoot) {
|
|
22328
|
+
if (!hasLocalWorkspace()) return "";
|
|
22189
22329
|
try {
|
|
22190
22330
|
const indexPath = join7(projectRoot, "docs", "INDEX.md");
|
|
22191
22331
|
const raw = readFileSync3(indexPath, "utf8");
|
|
@@ -22924,7 +23064,15 @@ function generateChangelogSection(version, commits) {
|
|
|
22924
23064
|
${commitList}
|
|
22925
23065
|
`;
|
|
22926
23066
|
}
|
|
22927
|
-
async function writeChangelogSection(changelogPath, section, version) {
|
|
23067
|
+
async function writeChangelogSection(changelogPath, section, version, adapterType, collector) {
|
|
23068
|
+
if (adapterType === "proxy") {
|
|
23069
|
+
collector.add({
|
|
23070
|
+
path: "CHANGELOG.md",
|
|
23071
|
+
content: "\n" + section + "\n",
|
|
23072
|
+
mode: "append"
|
|
23073
|
+
});
|
|
23074
|
+
return;
|
|
23075
|
+
}
|
|
22928
23076
|
let existing = "";
|
|
22929
23077
|
try {
|
|
22930
23078
|
existing = await readFile5(changelogPath, "utf-8");
|
|
@@ -23028,6 +23176,7 @@ async function resolveCycleToClose(adapter2, version) {
|
|
|
23028
23176
|
return inferCycleFromVersion(version);
|
|
23029
23177
|
}
|
|
23030
23178
|
async function createRelease(config2, branch, version, adapter2, cycleNum, options) {
|
|
23179
|
+
const collector = new FileWriteCollector();
|
|
23031
23180
|
if (!isGitAvailable()) {
|
|
23032
23181
|
throw new Error("git is not available.");
|
|
23033
23182
|
}
|
|
@@ -23165,11 +23314,15 @@ To override, pass force=true (emits a telemetry warning).`
|
|
|
23165
23314
|
const changelogPath = join9(config2.projectRoot, "CHANGELOG.md");
|
|
23166
23315
|
if (!latestTag) {
|
|
23167
23316
|
const initialContent = INITIAL_RELEASE_NOTES.replace("v0.1.0-alpha", version);
|
|
23168
|
-
|
|
23317
|
+
if (config2.adapterType === "proxy") {
|
|
23318
|
+
collector.add({ path: "CHANGELOG.md", content: initialContent, mode: "create", skip_if_exists: true });
|
|
23319
|
+
} else {
|
|
23320
|
+
await writeFile3(changelogPath, initialContent, "utf-8");
|
|
23321
|
+
}
|
|
23169
23322
|
} else {
|
|
23170
23323
|
const commits = getCommitsSinceTag(config2.projectRoot, latestTag);
|
|
23171
23324
|
const section = generateChangelogSection(version, commits);
|
|
23172
|
-
await writeChangelogSection(changelogPath, section, version);
|
|
23325
|
+
await writeChangelogSection(changelogPath, section, version, config2.adapterType, collector);
|
|
23173
23326
|
}
|
|
23174
23327
|
const commitResult = stageAllAndCommit(config2.projectRoot, `release: ${version}`);
|
|
23175
23328
|
const commitNote = commitResult.committed ? `Committed CHANGELOG.md.` : `CHANGELOG.md: ${commitResult.message}`;
|
|
@@ -23196,7 +23349,8 @@ To override, pass force=true (emits a telemetry warning).`
|
|
|
23196
23349
|
pushNotes,
|
|
23197
23350
|
warnings: warnings.length > 0 ? warnings : void 0,
|
|
23198
23351
|
...groupedBranchMerges ? { groupedBranchMerges } : {},
|
|
23199
|
-
cycleClosed: resolvedCycleNum
|
|
23352
|
+
cycleClosed: resolvedCycleNum,
|
|
23353
|
+
filesToWrite: collector.isEmpty() ? void 0 : collector
|
|
23200
23354
|
};
|
|
23201
23355
|
}
|
|
23202
23356
|
|
|
@@ -23323,7 +23477,8 @@ async function handleRelease(adapter2, config2, args) {
|
|
|
23323
23477
|
}
|
|
23324
23478
|
}
|
|
23325
23479
|
lines.push("", `Next: cycle released! Run \`plan\` to start your next planning cycle.`);
|
|
23326
|
-
|
|
23480
|
+
const filesToWriteSection = result.filesToWrite ? formatFilesToWriteSection(result.filesToWrite) : "";
|
|
23481
|
+
return textResponse(lines.join("\n") + filesToWriteSection);
|
|
23327
23482
|
} catch (err) {
|
|
23328
23483
|
const message = err instanceof Error ? err.message : String(err);
|
|
23329
23484
|
const isKnownFriendly = /^(Release blocked|Working tree|gh CLI|Tag .* already exists|Branch .* not found|Cycle .* incomplete)/i.test(message);
|
|
@@ -24336,7 +24491,15 @@ async function tryAutoProvisionProject(opts) {
|
|
|
24336
24491
|
}
|
|
24337
24492
|
return { ok: true, projectId: body.project.id, projectName: body.project.name ?? opts.projectName };
|
|
24338
24493
|
}
|
|
24339
|
-
async function ensureGitignoreEntry(projectRoot, entry) {
|
|
24494
|
+
async function ensureGitignoreEntry(projectRoot, entry, adapterType, collector) {
|
|
24495
|
+
if (adapterType === "proxy") {
|
|
24496
|
+
collector.add({
|
|
24497
|
+
path: ".gitignore",
|
|
24498
|
+
content: "\n" + entry + "\n",
|
|
24499
|
+
mode: "append"
|
|
24500
|
+
});
|
|
24501
|
+
return;
|
|
24502
|
+
}
|
|
24340
24503
|
const gitignorePath = path5.join(projectRoot, ".gitignore");
|
|
24341
24504
|
let content = "";
|
|
24342
24505
|
try {
|
|
@@ -24350,6 +24513,13 @@ async function ensureGitignoreEntry(projectRoot, entry) {
|
|
|
24350
24513
|
const separator = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
|
|
24351
24514
|
await writeFile4(gitignorePath, content + separator + entry + "\n", "utf-8");
|
|
24352
24515
|
}
|
|
24516
|
+
async function writeMcpConfig(absPath, relPath, content, isProjectScoped, adapterType, collector) {
|
|
24517
|
+
if (adapterType === "proxy" && isProjectScoped) {
|
|
24518
|
+
collector.add({ path: relPath, content, mode: "overwrite" });
|
|
24519
|
+
return;
|
|
24520
|
+
}
|
|
24521
|
+
await writeFile4(absPath, content, "utf-8");
|
|
24522
|
+
}
|
|
24353
24523
|
async function handleInit(config2, args) {
|
|
24354
24524
|
const projectRoot = config2.projectRoot;
|
|
24355
24525
|
const force = args.force === true;
|
|
@@ -24363,6 +24533,19 @@ async function handleInit(config2, args) {
|
|
|
24363
24533
|
}
|
|
24364
24534
|
const agent = requestedAgent;
|
|
24365
24535
|
const target = AGENT_TARGETS[agent];
|
|
24536
|
+
if (config2.adapterType === "proxy" && !target.isProjectScoped) {
|
|
24537
|
+
return errorResponse(
|
|
24538
|
+
[
|
|
24539
|
+
`Agent \`${agent}\` writes to a user-scoped config (${target.displayPath}) outside your project directory.`,
|
|
24540
|
+
"This MCP connection is remote \u2014 the server cannot write outside the project root.",
|
|
24541
|
+
"",
|
|
24542
|
+
"Workarounds:",
|
|
24543
|
+
"- Install the PAPI MCP server locally (`npx @papi-ai/server`) and run `init` over stdio.",
|
|
24544
|
+
"- Manually copy the config block from the getpapi.ai install snippet for this agent."
|
|
24545
|
+
].join("\n")
|
|
24546
|
+
);
|
|
24547
|
+
}
|
|
24548
|
+
const collector = new FileWriteCollector();
|
|
24366
24549
|
const configFileAbsPath = target.isProjectScoped ? path5.join(projectRoot, target.configPath) : target.configPath.startsWith("~/") ? path5.join(process.env.HOME ?? "", target.configPath.slice(2)) : target.configPath;
|
|
24367
24550
|
const mcpJsonPath = configFileAbsPath;
|
|
24368
24551
|
let existingConfig = null;
|
|
@@ -24423,12 +24606,12 @@ Path: ${mcpJsonPath}`
|
|
|
24423
24606
|
const wroteToShared = !target.isProjectScoped && !!existingConfig;
|
|
24424
24607
|
if (wroteToShared) {
|
|
24425
24608
|
const sep = existingConfig.endsWith("\n") ? "\n" : "\n\n";
|
|
24426
|
-
await
|
|
24609
|
+
await writeMcpConfig(mcpJsonPath, target.configPath, existingConfig + sep + fileBody, target.isProjectScoped, config2.adapterType, collector);
|
|
24427
24610
|
} else {
|
|
24428
|
-
await
|
|
24611
|
+
await writeMcpConfig(mcpJsonPath, target.configPath, fileBody, target.isProjectScoped, config2.adapterType, collector);
|
|
24429
24612
|
}
|
|
24430
24613
|
if (target.shouldGitignore) {
|
|
24431
|
-
await ensureGitignoreEntry(projectRoot, target.configPath);
|
|
24614
|
+
await ensureGitignoreEntry(projectRoot, target.configPath, config2.adapterType, collector);
|
|
24432
24615
|
}
|
|
24433
24616
|
const writeNote = wroteToShared ? `Appended to ${target.displayPath} (preserved your existing entries \u2014 review the file to confirm structure).` : `Your new project ID and existing connection token are saved to ${target.displayPath}.`;
|
|
24434
24617
|
return textResponse(
|
|
@@ -24444,7 +24627,7 @@ ${writeNote}
|
|
|
24444
24627
|
|
|
24445
24628
|
1. **Restart your MCP client** to pick up the new config.
|
|
24446
24629
|
2. **Run \`setup\`** \u2014 this scaffolds your project with a Product Brief and CLAUDE.md.
|
|
24447
|
-
`
|
|
24630
|
+
` + formatFilesToWriteSection(collector)
|
|
24448
24631
|
);
|
|
24449
24632
|
}
|
|
24450
24633
|
if (provisioned.reason === "project_limit_reached") {
|
|
@@ -24490,12 +24673,12 @@ ${writeNote}
|
|
|
24490
24673
|
const wroteToShared = !target.isProjectScoped && !!existingConfig;
|
|
24491
24674
|
if (wroteToShared) {
|
|
24492
24675
|
const sep = existingConfig.endsWith("\n") ? "\n" : "\n\n";
|
|
24493
|
-
await
|
|
24676
|
+
await writeMcpConfig(mcpJsonPath, target.configPath, existingConfig + sep + fileBody, target.isProjectScoped, config2.adapterType, collector);
|
|
24494
24677
|
} else {
|
|
24495
|
-
await
|
|
24678
|
+
await writeMcpConfig(mcpJsonPath, target.configPath, fileBody, target.isProjectScoped, config2.adapterType, collector);
|
|
24496
24679
|
}
|
|
24497
24680
|
if (target.shouldGitignore) {
|
|
24498
|
-
await ensureGitignoreEntry(projectRoot, target.configPath);
|
|
24681
|
+
await ensureGitignoreEntry(projectRoot, target.configPath, config2.adapterType, collector);
|
|
24499
24682
|
}
|
|
24500
24683
|
const writeNote = wroteToShared ? `Appended to ${target.displayPath} (preserved your existing entries \u2014 review the file to confirm structure).` : `Your existing API key and project ID have been saved to ${target.displayPath}.`;
|
|
24501
24684
|
return textResponse(
|
|
@@ -24510,7 +24693,7 @@ ${writeNote}
|
|
|
24510
24693
|
|
|
24511
24694
|
1. **Restart your MCP client** to pick up the new config.
|
|
24512
24695
|
2. **Run \`setup\`** \u2014 this scaffolds your project with a Product Brief and CLAUDE.md.
|
|
24513
|
-
`
|
|
24696
|
+
` + formatFilesToWriteSection(collector)
|
|
24514
24697
|
);
|
|
24515
24698
|
}
|
|
24516
24699
|
if (isDatabaseUser) {
|
|
@@ -24524,12 +24707,12 @@ ${writeNote}
|
|
|
24524
24707
|
const wroteToShared = !target.isProjectScoped && !!existingConfig;
|
|
24525
24708
|
if (wroteToShared) {
|
|
24526
24709
|
const sep = existingConfig.endsWith("\n") ? "\n" : "\n\n";
|
|
24527
|
-
await
|
|
24710
|
+
await writeMcpConfig(mcpJsonPath, target.configPath, existingConfig + sep + fileBody, target.isProjectScoped, config2.adapterType, collector);
|
|
24528
24711
|
} else {
|
|
24529
|
-
await
|
|
24712
|
+
await writeMcpConfig(mcpJsonPath, target.configPath, fileBody, target.isProjectScoped, config2.adapterType, collector);
|
|
24530
24713
|
}
|
|
24531
24714
|
if (target.shouldGitignore) {
|
|
24532
|
-
await ensureGitignoreEntry(projectRoot, target.configPath);
|
|
24715
|
+
await ensureGitignoreEntry(projectRoot, target.configPath, config2.adapterType, collector);
|
|
24533
24716
|
}
|
|
24534
24717
|
const output2 = [
|
|
24535
24718
|
`# PAPI Initialised \u2014 ${projectName}`,
|
|
@@ -24544,7 +24727,7 @@ ${writeNote}
|
|
|
24544
24727
|
...process.env.DATABASE_URL ? ["1. **Restart your MCP client** to pick up the new config."] : [`1. **Set your DATABASE_URL** \u2014 replace \`<YOUR_DATABASE_URL>\` in \`${target.displayPath}\` with your Supabase **session pooler** connection string (port **5432**, not 6543 \u2014 the transaction pooler wedges on interrupted calls).`],
|
|
24545
24728
|
"2. **Run `setup`** \u2014 this scaffolds your project with a Product Brief, Active Decisions, and CLAUDE.md."
|
|
24546
24729
|
].join("\n");
|
|
24547
|
-
return textResponse(output2);
|
|
24730
|
+
return textResponse(output2 + formatFilesToWriteSection(collector));
|
|
24548
24731
|
}
|
|
24549
24732
|
const output = [
|
|
24550
24733
|
`# PAPI \u2014 Account Required`,
|
|
@@ -25272,6 +25455,11 @@ async function handleDocScan(adapter2, config2, args) {
|
|
|
25272
25455
|
if (!adapter2.searchDocs) {
|
|
25273
25456
|
return errorResponse("Doc registry not available on this adapter.");
|
|
25274
25457
|
}
|
|
25458
|
+
if (!hasLocalWorkspace()) {
|
|
25459
|
+
return textResponse(
|
|
25460
|
+
"doc_scan is unavailable on the hosted remote MCP transport \u2014 the server has no access to your local filesystem. Register docs explicitly via `doc_register` with the path/title/type you want indexed."
|
|
25461
|
+
);
|
|
25462
|
+
}
|
|
25275
25463
|
const includePlans = args.include_plans ?? false;
|
|
25276
25464
|
const registered = await adapter2.searchDocs({ limit: 500, status: "all" });
|
|
25277
25465
|
const registeredPaths = new Set(registered.map((d) => d.path));
|
|
@@ -26070,7 +26258,7 @@ async function handleOrient(adapter2, config2, args = {}) {
|
|
|
26070
26258
|
}
|
|
26071
26259
|
let unregisteredDocsNote2 = "";
|
|
26072
26260
|
try {
|
|
26073
|
-
if (adapter2.searchDocs) {
|
|
26261
|
+
if (adapter2.searchDocs && hasLocalWorkspace()) {
|
|
26074
26262
|
const docsDir = join14(config2.projectRoot, "docs");
|
|
26075
26263
|
const docsFiles = scanMdFiles(docsDir, config2.projectRoot);
|
|
26076
26264
|
if (docsFiles.length > 0) {
|
|
@@ -26126,10 +26314,12 @@ ${versionDrift}` : "";
|
|
|
26126
26314
|
const deliveryShapeNote = deliveryShapeOutcome.status === "fulfilled" ? deliveryShapeOutcome.value : "";
|
|
26127
26315
|
const { reconciliationNote, unrecordedNote, unregisteredDocsNote, staleSkillsNote } = deepHousekeepingOutcome.status === "fulfilled" ? deepHousekeepingOutcome.value : { reconciliationNote: "", unrecordedNote: "", unregisteredDocsNote: "", staleSkillsNote: "" };
|
|
26128
26316
|
let enrichmentNote = "";
|
|
26317
|
+
const enrichmentCollector = new FileWriteCollector();
|
|
26129
26318
|
try {
|
|
26130
|
-
enrichmentNote = enrichClaudeMd(config2.projectRoot, healthResult.cycleNumber);
|
|
26319
|
+
enrichmentNote = enrichClaudeMd(config2.projectRoot, healthResult.cycleNumber, config2.adapterType, enrichmentCollector);
|
|
26131
26320
|
} catch {
|
|
26132
26321
|
}
|
|
26322
|
+
const enrichmentFilesSection = enrichmentCollector.isEmpty() ? "" : formatFilesToWriteSection(enrichmentCollector);
|
|
26133
26323
|
let preBuildCheckNote = "";
|
|
26134
26324
|
if (!cycleIsComplete) {
|
|
26135
26325
|
const researchOrSpikeTasks = allTasks.filter((t) => {
|
|
@@ -26165,7 +26355,7 @@ ${section}`;
|
|
|
26165
26355
|
tracker.mark("format-summary");
|
|
26166
26356
|
const subAgents = await listAgents(config2.projectRoot);
|
|
26167
26357
|
const deepHint = deepHousekeeping ? "" : "\n\n*Tip: pass `deep_housekeeping: true` to also check orphaned branches, unrecorded commits, unregistered docs, and stale skill forks.*";
|
|
26168
|
-
return textResponse(projectBannerNote + formatOrientSummary(healthResult, buildInfo, hierarchy, latestTag, config2.projectRoot, environment, subAgents) + unblockNote + alertsNote + ttfvNote + reconciliationNote + unrecordedNote + unregisteredDocsNote + staleSkillsNote + researchSignalsNote + recsNote + pendingReviewNote + patternsNote + unactionedIssuesNote + skillProposalsNote + sessionGuidanceNote + versionNote + enrichmentNote + deliveryShapeNote + preBuildCheckNote + deepHint);
|
|
26358
|
+
return textResponse(projectBannerNote + formatOrientSummary(healthResult, buildInfo, hierarchy, latestTag, config2.projectRoot, environment, subAgents) + unblockNote + alertsNote + ttfvNote + reconciliationNote + unrecordedNote + unregisteredDocsNote + staleSkillsNote + researchSignalsNote + recsNote + pendingReviewNote + patternsNote + unactionedIssuesNote + skillProposalsNote + sessionGuidanceNote + versionNote + enrichmentNote + deliveryShapeNote + preBuildCheckNote + deepHint + enrichmentFilesSection);
|
|
26169
26359
|
} catch (err) {
|
|
26170
26360
|
const message = err instanceof Error ? err.message : String(err);
|
|
26171
26361
|
const isKnownFriendly = /^(Orient failed|Project not found|No project|Setup required)/i.test(message);
|
|
@@ -26182,7 +26372,20 @@ ${section}`;
|
|
|
26182
26372
|
}));
|
|
26183
26373
|
}
|
|
26184
26374
|
}
|
|
26185
|
-
function enrichClaudeMd(projectRoot, cycleNumber) {
|
|
26375
|
+
function enrichClaudeMd(projectRoot, cycleNumber, adapterType, collector) {
|
|
26376
|
+
if (adapterType === "proxy") {
|
|
26377
|
+
const additions2 = [];
|
|
26378
|
+
if (cycleNumber >= 6) additions2.push(CLAUDE_MD_TIER_1);
|
|
26379
|
+
if (cycleNumber >= 21) additions2.push(CLAUDE_MD_TIER_2);
|
|
26380
|
+
if (additions2.length === 0) return "";
|
|
26381
|
+
collector.add({ path: "CLAUDE.md", content: additions2.join(""), mode: "append" });
|
|
26382
|
+
const tierNames2 = [];
|
|
26383
|
+
if (cycleNumber >= 6) tierNames2.push("Established (batch building, strategy reviews, AD lifecycle)");
|
|
26384
|
+
if (cycleNumber >= 21) tierNames2.push("Mature (idea pipeline, doc registry, advanced patterns)");
|
|
26385
|
+
return `
|
|
26386
|
+
|
|
26387
|
+
\u{1F4DD} **CLAUDE.md enriched** \u2014 added ${tierNames2.join(" + ")} guidance for cycle ${cycleNumber}+ projects.`;
|
|
26388
|
+
}
|
|
26186
26389
|
const claudeMdPath = join14(projectRoot, "CLAUDE.md");
|
|
26187
26390
|
if (!existsSync8(claudeMdPath)) return "";
|
|
26188
26391
|
const content = readFileSync8(claudeMdPath, "utf-8");
|
|
@@ -26927,8 +27130,14 @@ async function runScopeBrief(adapter2, input) {
|
|
|
26927
27130
|
const slug = input.taskId.replace(/[^a-z0-9-]/g, "-").toLowerCase();
|
|
26928
27131
|
const relPath = `docs/scopes/${slug}.md`;
|
|
26929
27132
|
const absPath = join15(input.projectRoot, relPath);
|
|
26930
|
-
|
|
26931
|
-
|
|
27133
|
+
const docBody = addFrontmatter(docContent, task, input.cycleNumber);
|
|
27134
|
+
const collector = new FileWriteCollector();
|
|
27135
|
+
if (input.adapterType === "proxy") {
|
|
27136
|
+
collector.add({ path: relPath, content: docBody, mode: "overwrite" });
|
|
27137
|
+
} else {
|
|
27138
|
+
mkdirSync3(dirname3(absPath), { recursive: true });
|
|
27139
|
+
writeFileSync4(absPath, docBody, "utf-8");
|
|
27140
|
+
}
|
|
26932
27141
|
const taskCount = countSubTasks(docContent);
|
|
26933
27142
|
const summary = buildSummary(task, taskCount);
|
|
26934
27143
|
if (!adapter2.registerDoc) {
|
|
@@ -26958,7 +27167,8 @@ ${decomposedNote}` : decomposedNote
|
|
|
26958
27167
|
docPath: relPath,
|
|
26959
27168
|
docId: entry.id,
|
|
26960
27169
|
taskCount,
|
|
26961
|
-
summary
|
|
27170
|
+
summary,
|
|
27171
|
+
filesToWrite: collector.isEmpty() ? void 0 : collector
|
|
26962
27172
|
};
|
|
26963
27173
|
}
|
|
26964
27174
|
function buildTaskContext(task) {
|
|
@@ -27028,15 +27238,17 @@ async function handleScopeBrief(adapter2, config2, args) {
|
|
|
27028
27238
|
taskId,
|
|
27029
27239
|
apiKey,
|
|
27030
27240
|
projectRoot: config2.projectRoot,
|
|
27031
|
-
cycleNumber: health.totalCycles
|
|
27241
|
+
cycleNumber: health.totalCycles,
|
|
27242
|
+
adapterType: config2.adapterType
|
|
27032
27243
|
});
|
|
27244
|
+
const filesToWriteSection = result.filesToWrite ? formatFilesToWriteSection(result.filesToWrite) : "";
|
|
27033
27245
|
return textResponse(
|
|
27034
27246
|
`**Scope document created:** \`${result.docPath}\`
|
|
27035
27247
|
- **Sub-tasks defined:** ${result.taskCount}
|
|
27036
27248
|
- **Doc registry ID:** ${result.docId}
|
|
27037
27249
|
- **Source task:** ${taskId} marked as decomposed
|
|
27038
27250
|
|
|
27039
|
-
**Next step:** Review \`${result.docPath}\` and submit each sub-task via \`idea\` with \`Reference: ${result.docPath}\` in notes. The planner will pick them up in the next cycle.`
|
|
27251
|
+
**Next step:** Review \`${result.docPath}\` and submit each sub-task via \`idea\` with \`Reference: ${result.docPath}\` in notes. The planner will pick them up in the next cycle.` + filesToWriteSection
|
|
27040
27252
|
);
|
|
27041
27253
|
} catch (err) {
|
|
27042
27254
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -27229,7 +27441,7 @@ async function handleDiscoveredIssueResolve(adapter2, args) {
|
|
|
27229
27441
|
// src/tools/project.ts
|
|
27230
27442
|
import path6 from "path";
|
|
27231
27443
|
function workspacePapiDir(config2) {
|
|
27232
|
-
if (
|
|
27444
|
+
if (!hasLocalWorkspace()) return void 0;
|
|
27233
27445
|
return config2.papiDir;
|
|
27234
27446
|
}
|
|
27235
27447
|
var NO_SUPPORT = "Project lifecycle tools require a hosted (proxy) or PostgreSQL (pg) adapter. The local md adapter does not support them.";
|
package/package.json
CHANGED