@difflab/pi 0.7.0-rc.202609271854.6af8600 → 0.7.0
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/agents/diffpi-planner.md +5 -5
- package/dist/cli.js +100 -53
- package/dist/extensions/index.js +86 -90
- package/dist/index.js +87 -90
- package/dist/plan/store.d.ts.map +1 -1
- package/dist/tools/index.js +86 -90
- package/package.json +1 -1
- package/skills/plan/SKILL.md +5 -5
- package/skills/plan/references/design-quality.md +29 -18
- package/skills/plan/references/workflows/new.md +5 -5
- package/skills/plan/references/workflows/update.md +5 -5
- package/templates/plan/PLAN.md +11 -17
- package/templates/plan/implementation.md +12 -28
- package/agents/diffpi-plan-reviewer.md +0 -27
package/dist/extensions/index.js
CHANGED
|
@@ -15049,7 +15049,7 @@ function sanitize(value) {
|
|
|
15049
15049
|
}
|
|
15050
15050
|
|
|
15051
15051
|
// src/tools/plan.ts
|
|
15052
|
-
import { join as
|
|
15052
|
+
import { join as join19 } from "node:path";
|
|
15053
15053
|
import { pathToFileURL } from "node:url";
|
|
15054
15054
|
import { defineTool as defineTool5 } from "@earendil-works/pi-coding-agent";
|
|
15055
15055
|
import { z as z8 } from "zod";
|
|
@@ -20993,8 +20993,8 @@ function isPlanReviewDump(value) {
|
|
|
20993
20993
|
|
|
20994
20994
|
// src/plan/store.ts
|
|
20995
20995
|
import { createHash as createHash3, randomUUID as randomUUID2 } from "node:crypto";
|
|
20996
|
-
import { mkdir as mkdir6, readdir as readdir3, readFile as
|
|
20997
|
-
import { basename as basename6, dirname as dirname8, join as
|
|
20996
|
+
import { mkdir as mkdir6, readdir as readdir3, readFile as readFile10, realpath as realpath3, rename as rename2, rm as rm2 } from "node:fs/promises";
|
|
20997
|
+
import { basename as basename6, dirname as dirname8, join as join18, resolve as resolve4, sep } from "node:path";
|
|
20998
20998
|
|
|
20999
20999
|
// src/extensions/fsx.ts
|
|
21000
21000
|
import { mkdir as mkdir5, rename, rm, writeFile as writeFile4 } from "node:fs/promises";
|
|
@@ -21030,6 +21030,42 @@ function sleep(ms) {
|
|
|
21030
21030
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
21031
21031
|
}
|
|
21032
21032
|
|
|
21033
|
+
// src/templates.ts
|
|
21034
|
+
import { readFile as readFile9 } from "node:fs/promises";
|
|
21035
|
+
import { homedir as homedir7 } from "node:os";
|
|
21036
|
+
import { join as join17, normalize } from "node:path";
|
|
21037
|
+
async function loadTemplate(name, options = {}) {
|
|
21038
|
+
const relative = templateRelativePath(name);
|
|
21039
|
+
const userPath = join17(options.homeDir ?? homedir7(), ".difflab", "diffpi", "templates", relative);
|
|
21040
|
+
const bundledPath = join17(options.bundledDir ?? resolveBundledTemplatesDir(), relative);
|
|
21041
|
+
const user = await readOptionalFile(userPath);
|
|
21042
|
+
if (user !== undefined)
|
|
21043
|
+
return { name, path: userPath, source: "user", content: user };
|
|
21044
|
+
const bundled = await readOptionalFile(bundledPath);
|
|
21045
|
+
if (bundled !== undefined)
|
|
21046
|
+
return { name, path: bundledPath, source: "bundled", content: bundled };
|
|
21047
|
+
throw new Error(`Template "${name}" was not found at ${userPath} or ${bundledPath}.`);
|
|
21048
|
+
}
|
|
21049
|
+
function renderTemplate(content, variables) {
|
|
21050
|
+
return content.replace(/\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g, (match, key) => variables[key] ?? match);
|
|
21051
|
+
}
|
|
21052
|
+
function templateRelativePath(name) {
|
|
21053
|
+
const normalized = normalize(name.replaceAll("\\", "/")).replace(/^\.\//, "");
|
|
21054
|
+
if (!normalized || normalized.startsWith("../") || normalized.includes("/../") || normalized.startsWith("/")) {
|
|
21055
|
+
throw new Error(`Invalid template name: ${name}`);
|
|
21056
|
+
}
|
|
21057
|
+
return normalized.endsWith(".md") ? normalized : `${normalized}.md`;
|
|
21058
|
+
}
|
|
21059
|
+
async function readOptionalFile(path) {
|
|
21060
|
+
try {
|
|
21061
|
+
return await readFile9(path, "utf8");
|
|
21062
|
+
} catch (error) {
|
|
21063
|
+
if (error.code === "ENOENT")
|
|
21064
|
+
return;
|
|
21065
|
+
throw error;
|
|
21066
|
+
}
|
|
21067
|
+
}
|
|
21068
|
+
|
|
21033
21069
|
// src/plan/store.ts
|
|
21034
21070
|
function planRecordName(shortSlug, date = new Date) {
|
|
21035
21071
|
const slug = normalizeSlug(shortSlug);
|
|
@@ -21045,7 +21081,7 @@ async function resolvePlan(cwd, query, filters = {}, options = {}) {
|
|
|
21045
21081
|
continue;
|
|
21046
21082
|
if (normalizedQuery && entry.name !== normalizedQuery && stripDate(entry.name) !== normalizedQuery)
|
|
21047
21083
|
continue;
|
|
21048
|
-
const dir =
|
|
21084
|
+
const dir = join18(root, entry.name);
|
|
21049
21085
|
await assertContained(root, dir);
|
|
21050
21086
|
try {
|
|
21051
21087
|
const record = await readRecord(dir);
|
|
@@ -21080,30 +21116,26 @@ function createPlanStore(options = {}) {
|
|
|
21080
21116
|
async init(input) {
|
|
21081
21117
|
const root = await plansDir(input.cwd, options.homeDir);
|
|
21082
21118
|
const id = planRecordName(input.shortSlug, now());
|
|
21083
|
-
const dir =
|
|
21119
|
+
const dir = join18(root, id);
|
|
21084
21120
|
await createPlanDirectory(root, dir, id);
|
|
21085
21121
|
try {
|
|
21086
21122
|
const timestamp = now().toISOString();
|
|
21123
|
+
const template = await loadTemplate("plan/PLAN", {
|
|
21124
|
+
homeDir: options.homeDir,
|
|
21125
|
+
bundledDir: options.bundledTemplatesDir
|
|
21126
|
+
});
|
|
21087
21127
|
const title = input.title?.trim() || titleFromSlug(input.shortSlug);
|
|
21088
|
-
const
|
|
21089
|
-
schemaVersion: 1,
|
|
21128
|
+
const source = renderTemplate(template.content, {
|
|
21090
21129
|
id,
|
|
21091
|
-
revision: 0,
|
|
21092
|
-
title,
|
|
21093
21130
|
branch: input.branch,
|
|
21094
|
-
|
|
21095
|
-
...input.issueUrl?.trim() ? { issueUrl: input.issueUrl.trim() } : {},
|
|
21131
|
+
title,
|
|
21096
21132
|
intent: input.intent?.trim() || "<!-- Describe the intended outcome. -->",
|
|
21097
|
-
|
|
21098
|
-
|
|
21099
|
-
|
|
21100
|
-
|
|
21101
|
-
|
|
21102
|
-
|
|
21103
|
-
updatedAt: timestamp
|
|
21104
|
-
};
|
|
21105
|
-
const source = renderPlanDocument(document);
|
|
21106
|
-
parsePlanDocument(source, join17(dir, "PLAN.md"));
|
|
21133
|
+
issue_id: input.issueId?.trim() || "",
|
|
21134
|
+
issue_url: input.issueUrl?.trim() || "",
|
|
21135
|
+
created_at: timestamp,
|
|
21136
|
+
updated_at: timestamp
|
|
21137
|
+
});
|
|
21138
|
+
const document = parsePlanDocument(source, join18(dir, "PLAN.md"));
|
|
21107
21139
|
const authoringRequest = input.request ?? { kind: "user", text: input.intent?.trim() || `Initialize phase-less plan ${title}.` };
|
|
21108
21140
|
await writeRevisionSnapshot(dir, document, source, authoringRequest, []);
|
|
21109
21141
|
await writeLatestView(dir, document, source, []);
|
|
@@ -21118,14 +21150,14 @@ function createPlanStore(options = {}) {
|
|
|
21118
21150
|
if (request.mode === "create") {
|
|
21119
21151
|
const root = await plansDir(cwd, options.homeDir);
|
|
21120
21152
|
const id = planRecordName(request.shortSlug, now());
|
|
21121
|
-
const dir =
|
|
21153
|
+
const dir = join18(root, id);
|
|
21122
21154
|
await createPlanDirectory(root, dir, id);
|
|
21123
21155
|
try {
|
|
21124
21156
|
const timestamp = now().toISOString();
|
|
21125
21157
|
const document = normalizeRevision(undefined, request, id, 0, timestamp);
|
|
21126
21158
|
assertCompleteRevision(document, request.briefs);
|
|
21127
21159
|
const source = renderPlanDocument(document);
|
|
21128
|
-
parsePlanDocument(source,
|
|
21160
|
+
parsePlanDocument(source, join18(dir, "PLAN.md"));
|
|
21129
21161
|
await writeRevisionSnapshot(dir, document, source, request.request, request.briefs);
|
|
21130
21162
|
await writeLatestView(dir, document, source, request.briefs);
|
|
21131
21163
|
return await readRecord(dir);
|
|
@@ -21135,7 +21167,7 @@ function createPlanStore(options = {}) {
|
|
|
21135
21167
|
}
|
|
21136
21168
|
}
|
|
21137
21169
|
const initial = await read(cwd, request.plan);
|
|
21138
|
-
return withDirectoryLock(
|
|
21170
|
+
return withDirectoryLock(join18(initial.dir, ".lock"), async () => {
|
|
21139
21171
|
const current = await readRecord(initial.dir);
|
|
21140
21172
|
if (current.document.revision !== request.expectedPlanRevision)
|
|
21141
21173
|
throw new Error(`Expected plan revision ${request.expectedPlanRevision}, found ${current.document.revision}.`);
|
|
@@ -21148,7 +21180,7 @@ function createPlanStore(options = {}) {
|
|
|
21148
21180
|
try {
|
|
21149
21181
|
await writeLatestView(current.dir, document, source, request.briefs);
|
|
21150
21182
|
} catch (error) {
|
|
21151
|
-
await rm2(
|
|
21183
|
+
await rm2(join18(current.dir, "revisions", String(revision)), { recursive: true, force: true });
|
|
21152
21184
|
throw error;
|
|
21153
21185
|
}
|
|
21154
21186
|
return await readRecord(current.dir);
|
|
@@ -21156,7 +21188,7 @@ function createPlanStore(options = {}) {
|
|
|
21156
21188
|
},
|
|
21157
21189
|
async mutate(cwd, query, operation, update) {
|
|
21158
21190
|
const initial = await read(cwd, query);
|
|
21159
|
-
return withDirectoryLock(
|
|
21191
|
+
return withDirectoryLock(join18(initial.dir, ".lock"), async () => {
|
|
21160
21192
|
const current = await readRecord(initial.dir);
|
|
21161
21193
|
const next = await update(structuredClone(current.document));
|
|
21162
21194
|
if (next.id !== current.id)
|
|
@@ -21179,16 +21211,16 @@ function createPlanStore(options = {}) {
|
|
|
21179
21211
|
};
|
|
21180
21212
|
}
|
|
21181
21213
|
async function readRecord(dir) {
|
|
21182
|
-
const planPath =
|
|
21183
|
-
const logPath =
|
|
21184
|
-
const source = await
|
|
21214
|
+
const planPath = join18(dir, "PLAN.md");
|
|
21215
|
+
const logPath = join18(dir, "logs.txt");
|
|
21216
|
+
const source = await readFile10(planPath, "utf8");
|
|
21185
21217
|
const document = parsePlanDocument(source, planPath);
|
|
21186
21218
|
return {
|
|
21187
21219
|
id: basename6(dir),
|
|
21188
21220
|
dir,
|
|
21189
21221
|
planPath,
|
|
21190
21222
|
logPath,
|
|
21191
|
-
implementationDir:
|
|
21223
|
+
implementationDir: join18(dir, "implementation"),
|
|
21192
21224
|
document,
|
|
21193
21225
|
source
|
|
21194
21226
|
};
|
|
@@ -21208,17 +21240,17 @@ async function writeRevisionSnapshot(planDir, document, source, request, briefs)
|
|
|
21208
21240
|
throw new Error("An exact non-empty authoring request is required.");
|
|
21209
21241
|
if (request.kind === "annotation" && !request.response?.trim())
|
|
21210
21242
|
throw new Error("Annotation requests require a non-empty response.");
|
|
21211
|
-
const revisionsDir =
|
|
21243
|
+
const revisionsDir = join18(planDir, "revisions");
|
|
21212
21244
|
await mkdir6(revisionsDir, { recursive: true });
|
|
21213
|
-
const target =
|
|
21245
|
+
const target = join18(revisionsDir, String(document.revision));
|
|
21214
21246
|
try {
|
|
21215
|
-
await
|
|
21247
|
+
await readFile10(join18(target, "metadata.json"), "utf8");
|
|
21216
21248
|
throw new Error(`Revision ${document.revision} already exists and is immutable.`);
|
|
21217
21249
|
} catch (error) {
|
|
21218
21250
|
if (error.code !== "ENOENT")
|
|
21219
21251
|
throw error;
|
|
21220
21252
|
}
|
|
21221
|
-
const staging =
|
|
21253
|
+
const staging = join18(revisionsDir, `.tmp-${document.revision}-${randomUUID2()}`);
|
|
21222
21254
|
await mkdir6(staging);
|
|
21223
21255
|
try {
|
|
21224
21256
|
const requestSource = request.kind === "annotation" ? `## Original annotation
|
|
@@ -21226,8 +21258,8 @@ ${request.text}
|
|
|
21226
21258
|
|
|
21227
21259
|
## LLM response
|
|
21228
21260
|
${request.response}` : request.text;
|
|
21229
|
-
await atomicWrite(
|
|
21230
|
-
await atomicWrite(
|
|
21261
|
+
await atomicWrite(join18(staging, "request.md"), requestSource);
|
|
21262
|
+
await atomicWrite(join18(staging, "metadata.json"), `${JSON.stringify({
|
|
21231
21263
|
schemaVersion: 1,
|
|
21232
21264
|
id: document.id,
|
|
21233
21265
|
revision: document.revision,
|
|
@@ -21237,7 +21269,7 @@ ${request.response}` : request.text;
|
|
|
21237
21269
|
createdAt: document.updatedAt
|
|
21238
21270
|
}, null, 2)}
|
|
21239
21271
|
`);
|
|
21240
|
-
await atomicWrite(
|
|
21272
|
+
await atomicWrite(join18(staging, "PLAN.md"), source);
|
|
21241
21273
|
await writeImplementationDirectory(staging, document, briefs);
|
|
21242
21274
|
await rename2(staging, target);
|
|
21243
21275
|
} catch (error) {
|
|
@@ -21246,10 +21278,10 @@ ${request.response}` : request.text;
|
|
|
21246
21278
|
}
|
|
21247
21279
|
}
|
|
21248
21280
|
async function writeLatestView(planDir, document, source, briefs) {
|
|
21249
|
-
const current =
|
|
21250
|
-
const stagingRoot =
|
|
21251
|
-
const stagedImplementation =
|
|
21252
|
-
const backup =
|
|
21281
|
+
const current = join18(planDir, "implementation");
|
|
21282
|
+
const stagingRoot = join18(planDir, `.latest-${randomUUID2()}`);
|
|
21283
|
+
const stagedImplementation = join18(stagingRoot, "implementation");
|
|
21284
|
+
const backup = join18(planDir, `.implementation-backup-${randomUUID2()}`);
|
|
21253
21285
|
await mkdir6(stagingRoot);
|
|
21254
21286
|
await writeImplementationDirectory(stagingRoot, document, briefs);
|
|
21255
21287
|
let backedUp = false;
|
|
@@ -21262,7 +21294,7 @@ async function writeLatestView(planDir, document, source, briefs) {
|
|
|
21262
21294
|
throw error;
|
|
21263
21295
|
}
|
|
21264
21296
|
await rename2(stagedImplementation, current);
|
|
21265
|
-
await atomicWrite(
|
|
21297
|
+
await atomicWrite(join18(planDir, "PLAN.md"), source);
|
|
21266
21298
|
if (backedUp)
|
|
21267
21299
|
await rm2(backup, { recursive: true, force: true });
|
|
21268
21300
|
} catch (error) {
|
|
@@ -21275,13 +21307,13 @@ async function writeLatestView(planDir, document, source, briefs) {
|
|
|
21275
21307
|
}
|
|
21276
21308
|
}
|
|
21277
21309
|
async function writeImplementationDirectory(baseDir, document, briefs) {
|
|
21278
|
-
const implementationDir =
|
|
21310
|
+
const implementationDir = join18(baseDir, "implementation");
|
|
21279
21311
|
await mkdir6(implementationDir, { recursive: true });
|
|
21280
21312
|
for (const [index, phase] of document.phases.entries()) {
|
|
21281
21313
|
const brief = briefs[index];
|
|
21282
21314
|
if (!brief)
|
|
21283
21315
|
throw new Error(`Missing implementation brief for phase ${phase.id}.`);
|
|
21284
|
-
await atomicWrite(
|
|
21316
|
+
await atomicWrite(join18(implementationDir, `phase-${index + 1}.md`), renderImplementationBrief(document.revision, index + 1, phase, brief));
|
|
21285
21317
|
}
|
|
21286
21318
|
}
|
|
21287
21319
|
function normalizeRevision(current, request, id, revision, updatedAt) {
|
|
@@ -21767,7 +21799,7 @@ function createPlanTools(_pi, _modes, store = createPlanStore()) {
|
|
|
21767
21799
|
const resolution = await store.context(workingDirectory, params.plan);
|
|
21768
21800
|
if (!resolution.record)
|
|
21769
21801
|
throw new Error(resolution.ambiguous ? `Plan is ambiguous: ${resolution.candidates.map((item) => item.id).join(", ")}.` : "No matching plan.");
|
|
21770
|
-
const cliPath =
|
|
21802
|
+
const cliPath = join19(resolveBundledAgentsDir(), "..", "dist", "cli.js");
|
|
21771
21803
|
const command = [
|
|
21772
21804
|
process.execPath,
|
|
21773
21805
|
cliPath,
|
|
@@ -22567,10 +22599,10 @@ function hasGitlabDraftNotes(input) {
|
|
|
22567
22599
|
}
|
|
22568
22600
|
|
|
22569
22601
|
// src/review/local-review-backend.ts
|
|
22570
|
-
import { readFile as
|
|
22602
|
+
import { readFile as readFile12, writeFile as writeFile5 } from "node:fs/promises";
|
|
22571
22603
|
|
|
22572
22604
|
// src/extensions/tuicrx.ts
|
|
22573
|
-
import { readFile as
|
|
22605
|
+
import { readFile as readFile11, realpath as realpath4 } from "node:fs/promises";
|
|
22574
22606
|
import { resolve as resolve5 } from "node:path";
|
|
22575
22607
|
async function tuicrAvailable() {
|
|
22576
22608
|
return Boolean(await findExecutable("tuicr"));
|
|
@@ -22667,7 +22699,7 @@ async function findMatchingSession(sessions, cwd, branch) {
|
|
|
22667
22699
|
return;
|
|
22668
22700
|
}
|
|
22669
22701
|
async function readSession(path) {
|
|
22670
|
-
const content = await
|
|
22702
|
+
const content = await readFile11(path, "utf8");
|
|
22671
22703
|
try {
|
|
22672
22704
|
return JSON.parse(content);
|
|
22673
22705
|
} catch {
|
|
@@ -22788,7 +22820,7 @@ function LocalReviewBackend(options) {
|
|
|
22788
22820
|
},
|
|
22789
22821
|
async listThreads() {
|
|
22790
22822
|
try {
|
|
22791
|
-
return parseThreadArtifact(await
|
|
22823
|
+
return parseThreadArtifact(await readFile12(options.artifactPath, "utf8"));
|
|
22792
22824
|
} catch (error) {
|
|
22793
22825
|
if (error.code === "ENOENT")
|
|
22794
22826
|
return [];
|
|
@@ -22796,7 +22828,7 @@ function LocalReviewBackend(options) {
|
|
|
22796
22828
|
}
|
|
22797
22829
|
},
|
|
22798
22830
|
async reply(input) {
|
|
22799
|
-
const content = await
|
|
22831
|
+
const content = await readFile12(options.artifactPath, "utf8");
|
|
22800
22832
|
await writeFile5(options.artifactPath, upsertThreadReply(content, input.threadId, input.body, input.question, input.resolve), "utf8");
|
|
22801
22833
|
},
|
|
22802
22834
|
async publish() {
|
|
@@ -22818,8 +22850,8 @@ function createLocalReviewBackend(options) {
|
|
|
22818
22850
|
}
|
|
22819
22851
|
// src/review/review-state.ts
|
|
22820
22852
|
import { createHash as createHash4 } from "node:crypto";
|
|
22821
|
-
import { readFile as
|
|
22822
|
-
import { join as
|
|
22853
|
+
import { readFile as readFile13, rename as rename3, writeFile as writeFile6 } from "node:fs/promises";
|
|
22854
|
+
import { join as join20 } from "node:path";
|
|
22823
22855
|
import { z as z11 } from "zod";
|
|
22824
22856
|
var reviewPublicationStateSchema = z11.object({
|
|
22825
22857
|
target: z11.string().optional(),
|
|
@@ -22843,9 +22875,9 @@ function unpublishedReviewComments(comments, knownFingerprints) {
|
|
|
22843
22875
|
}
|
|
22844
22876
|
async function loadReviewPublicationState(cwd, vcs, number, homeDir) {
|
|
22845
22877
|
const target = `${vcs.provider}:${vcs.owner}/${vcs.repo}#${number}`;
|
|
22846
|
-
const path =
|
|
22878
|
+
const path = join20(await sessionsDir(cwd, homeDir), `review-publish-${digest(target).slice(0, 16)}.json`);
|
|
22847
22879
|
try {
|
|
22848
|
-
const parsed = reviewPublicationStateSchema.parse(JSON.parse(await
|
|
22880
|
+
const parsed = reviewPublicationStateSchema.parse(JSON.parse(await readFile13(path, "utf8")));
|
|
22849
22881
|
return { path, state: { ...parsed, target } };
|
|
22850
22882
|
} catch (error) {
|
|
22851
22883
|
if (error.code === "ENOENT") {
|
|
@@ -22866,42 +22898,6 @@ async function saveReviewPublicationState(path, state) {
|
|
|
22866
22898
|
function digest(value) {
|
|
22867
22899
|
return createHash4("sha256").update(value).digest("hex");
|
|
22868
22900
|
}
|
|
22869
|
-
// src/templates.ts
|
|
22870
|
-
import { readFile as readFile13 } from "node:fs/promises";
|
|
22871
|
-
import { homedir as homedir7 } from "node:os";
|
|
22872
|
-
import { join as join20, normalize } from "node:path";
|
|
22873
|
-
async function loadTemplate(name, options = {}) {
|
|
22874
|
-
const relative = templateRelativePath(name);
|
|
22875
|
-
const userPath = join20(options.homeDir ?? homedir7(), ".difflab", "diffpi", "templates", relative);
|
|
22876
|
-
const bundledPath = join20(options.bundledDir ?? resolveBundledTemplatesDir(), relative);
|
|
22877
|
-
const user = await readOptionalFile(userPath);
|
|
22878
|
-
if (user !== undefined)
|
|
22879
|
-
return { name, path: userPath, source: "user", content: user };
|
|
22880
|
-
const bundled = await readOptionalFile(bundledPath);
|
|
22881
|
-
if (bundled !== undefined)
|
|
22882
|
-
return { name, path: bundledPath, source: "bundled", content: bundled };
|
|
22883
|
-
throw new Error(`Template "${name}" was not found at ${userPath} or ${bundledPath}.`);
|
|
22884
|
-
}
|
|
22885
|
-
function renderTemplate(content, variables) {
|
|
22886
|
-
return content.replace(/\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g, (match, key) => variables[key] ?? match);
|
|
22887
|
-
}
|
|
22888
|
-
function templateRelativePath(name) {
|
|
22889
|
-
const normalized = normalize(name.replaceAll("\\", "/")).replace(/^\.\//, "");
|
|
22890
|
-
if (!normalized || normalized.startsWith("../") || normalized.includes("/../") || normalized.startsWith("/")) {
|
|
22891
|
-
throw new Error(`Invalid template name: ${name}`);
|
|
22892
|
-
}
|
|
22893
|
-
return normalized.endsWith(".md") ? normalized : `${normalized}.md`;
|
|
22894
|
-
}
|
|
22895
|
-
async function readOptionalFile(path) {
|
|
22896
|
-
try {
|
|
22897
|
-
return await readFile13(path, "utf8");
|
|
22898
|
-
} catch (error) {
|
|
22899
|
-
if (error.code === "ENOENT")
|
|
22900
|
-
return;
|
|
22901
|
-
throw error;
|
|
22902
|
-
}
|
|
22903
|
-
}
|
|
22904
|
-
|
|
22905
22901
|
// src/tools/review.ts
|
|
22906
22902
|
var contextSchema = z12.object({
|
|
22907
22903
|
cwd: z12.string().optional(),
|