@c4a/context-cli 0.7.35 → 0.7.37
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/cli.js +980 -498
- package/indexers/contracts/profile-contract.json +245 -245
- package/indexers/release-manifest.json +1 -1
- package/package.json +12 -12
- package/plugins/VERSION +1 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/commands/context.md +44 -8
- package/plugins/claude/skills/context-plan/SKILL.md +18 -6
- package/plugins/claude/skills/context-plan/references/project-planning.md +74 -10
- package/plugins/claude/skills/context-plan/templates/PLAN.md +9 -4
- package/plugins/codex/.codex-plugin/plugin.json +2 -2
- package/plugins/codex/skills/context/SKILL.md +44 -8
- package/plugins/codex/skills/context-plan/SKILL.md +18 -6
- package/plugins/codex/skills/context-plan/references/project-planning.md +74 -10
- package/plugins/codex/skills/context-plan/templates/PLAN.md +9 -4
- package/plugins/cursor/.cursor-plugin/plugin.json +1 -1
- package/plugins/cursor/commands/c4a-context.md +44 -8
- package/plugins/cursor/skills/context-plan/SKILL.md +18 -6
- package/plugins/cursor/skills/context-plan/references/project-planning.md +74 -10
- package/plugins/cursor/skills/context-plan/templates/PLAN.md +9 -4
- package/plugins/skills/context/SKILL.md +44 -8
- package/plugins/skills/context-plan/SKILL.md +18 -6
- package/plugins/skills/context-plan/references/project-planning.md +74 -10
- package/plugins/skills/context-plan/templates/PLAN.md +9 -4
- package/providers/context/actions/apply-confirmed-review.yaml +5 -0
- package/providers/context/codes.yaml +1 -1
- package/providers/context/graphs/workspace.yaml +2 -2
- package/providers/context/manifest.json +18 -18
- package/providers/context/provider.yaml +1 -1
- package/providers/context/resources/dialogue/knowledge-review.md +55 -52
- package/providers/context/resources/manuals/guides/knowledge-updates.md +14 -10
- package/providers/context/resources/procedures/knowledge-review.md +29 -25
- package/providers/context/resources/procedures/knowledge-updates.md +14 -10
- package/providers/context/actions/apply-forced-review.yaml +0 -5
package/cli.js
CHANGED
|
@@ -79425,6 +79425,33 @@ var init_productionStage = __esm(() => {
|
|
|
79425
79425
|
terminal = new Set(["accepted", "excluded", "replaced"]);
|
|
79426
79426
|
});
|
|
79427
79427
|
|
|
79428
|
+
// src/project/productionSourceSummary.ts
|
|
79429
|
+
function productionSourceSummary(stage) {
|
|
79430
|
+
const pending = new Set(stage.pending_scopes);
|
|
79431
|
+
const unavailable = new Set(stage.gaps.map((gap) => gap.scope));
|
|
79432
|
+
return {
|
|
79433
|
+
stage_source_count: new Set(stage.scopes.map((source2) => source2.scope)).size,
|
|
79434
|
+
pending_scope_count: pending.size,
|
|
79435
|
+
unavailable_source_count: unavailable.size,
|
|
79436
|
+
pending_without_read_failure_count: [...pending].filter((scope2) => !unavailable.has(scope2)).length,
|
|
79437
|
+
planned_article_count: new Set(stage.tasks.filter((task) => !["excluded", "replaced"].includes(task.status)).map((task) => task.path)).size,
|
|
79438
|
+
unavailable_sources: stage.gaps,
|
|
79439
|
+
meaning: "Pending scopes are remaining investigation, not missing knowledge or failed captures. Unavailable sources have current material/baseline read failures; inspect their reasons. Counts can overlap and must not be added. Restore required code dependencies even for document-led work."
|
|
79440
|
+
};
|
|
79441
|
+
}
|
|
79442
|
+
function productionSourceSummaryMarkdown(stage) {
|
|
79443
|
+
const summary = productionSourceSummary(stage);
|
|
79444
|
+
return [
|
|
79445
|
+
"## Source availability and progress",
|
|
79446
|
+
"",
|
|
79447
|
+
`Stage sources: ${summary.stage_source_count}; pending investigation scopes: ${summary.pending_scope_count}; sources with read failures: ${summary.unavailable_source_count}; pending scopes without read failures: ${summary.pending_without_read_failure_count}; planned articles: ${summary.planned_article_count}.`,
|
|
79448
|
+
summary.meaning,
|
|
79449
|
+
"",
|
|
79450
|
+
...summary.unavailable_sources.map((gap) => `- ${gap.scope}: ${gap.reason}`),
|
|
79451
|
+
""
|
|
79452
|
+
];
|
|
79453
|
+
}
|
|
79454
|
+
|
|
79428
79455
|
// src/project/productionStageStore.ts
|
|
79429
79456
|
var exports_productionStageStore = {};
|
|
79430
79457
|
__export(exports_productionStageStore, {
|
|
@@ -79544,8 +79571,7 @@ function productionPlanMarkdown(stage) {
|
|
|
79544
79571
|
"Then present the updated report and apply context.gate.work_start_scope; an old decision does not approve changed article goals.",
|
|
79545
79572
|
""
|
|
79546
79573
|
] : [],
|
|
79547
|
-
...stage
|
|
79548
|
-
""
|
|
79574
|
+
...productionSourceSummaryMarkdown(stage)
|
|
79549
79575
|
].join(`
|
|
79550
79576
|
`);
|
|
79551
79577
|
}
|
|
@@ -81069,8 +81095,9 @@ async function resolveProductionExclusions(requirements, readBaseline) {
|
|
|
81069
81095
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
81070
81096
|
import { readFile as readFile34, access as access3 } from "node:fs/promises";
|
|
81071
81097
|
import { join as join39 } from "node:path";
|
|
81072
|
-
function refreshRequired(stage, message) {
|
|
81098
|
+
function refreshRequired(stage, message, detail = {}) {
|
|
81073
81099
|
return new ContextError(ExitCode.UserError, message, {
|
|
81100
|
+
...detail,
|
|
81074
81101
|
category: ErrorCategory.UserInputInvalid,
|
|
81075
81102
|
reason_code: "production-planning-refresh-required",
|
|
81076
81103
|
next_action: { command: `context action prepare-current --revision ${stage} --format json` }
|
|
@@ -81169,6 +81196,7 @@ Restore the authorized source and retry preparation.
|
|
|
81169
81196
|
`Existing reader topics: ${join39(directory, "guidance/existing-articles.md")}`,
|
|
81170
81197
|
`Stage: ${stage.id}`,
|
|
81171
81198
|
"",
|
|
81199
|
+
...productionSourceSummaryMarkdown(stage),
|
|
81172
81200
|
...scopes.map((scope2) => `- ${scope2.scope}: ${join39(directory, productionSourceFile(scope2.scope))}`),
|
|
81173
81201
|
"",
|
|
81174
81202
|
"Use code skeletons and document outlines to identify the authorized capability families and document tasks, then selectively read full material to decide reader topics. Navigation is not a complete feature inventory. Keep unchecked scope pending; do not parse all code or maintain per-symbol disposition just to plan.",
|
|
@@ -81192,7 +81220,7 @@ Restore the authorized source and retry preparation.
|
|
|
81192
81220
|
capabilities: productionCapabilitiesSchema.parse({}),
|
|
81193
81221
|
materials: { ...prepared.materials, sources: sourceMaterials, guidance }
|
|
81194
81222
|
});
|
|
81195
|
-
return { stage_state: "active", next: { directory, mode: "single-agent" } };
|
|
81223
|
+
return { stage_state: "active", source_summary: productionSourceSummary(stage), next: { directory, mode: "single-agent" } };
|
|
81196
81224
|
});
|
|
81197
81225
|
}
|
|
81198
81226
|
async function submitProductionPlan(input) {
|
|
@@ -81220,9 +81248,12 @@ async function submitProductionPlan(input) {
|
|
|
81220
81248
|
throw new TypeError("Pending investigation must name unique authorized stage scopes");
|
|
81221
81249
|
}
|
|
81222
81250
|
if (stage.gaps.some((gap) => !pendingScopes.includes(gap.scope)))
|
|
81223
|
-
throw refreshRequired(stage.id, "Keep unresolved material gaps in pending_scopes
|
|
81251
|
+
throw refreshRequired(stage.id, "Keep unresolved material gaps in pending_scopes. These are current material/baseline read failures, not evidence that knowledge was never captured. Restore required dependencies before preparing their snapshots; do not label every pending source as unavailable.", {
|
|
81252
|
+
source_summary: productionSourceSummary(stage),
|
|
81253
|
+
missing_pending_scopes: [...new Set(stage.gaps.filter((gap) => !pendingScopes.includes(gap.scope)).map((gap) => gap.scope))]
|
|
81254
|
+
});
|
|
81224
81255
|
if (plan.articles.some((article) => article.sources.some((scope2) => stage.gaps.some((gap) => gap.scope === scope2)))) {
|
|
81225
|
-
throw refreshRequired(stage.id, "An article requires unavailable material in the captured stage. Restore the source
|
|
81256
|
+
throw refreshRequired(stage.id, "An article requires unavailable material in the captured stage. Restore the required source, including code dependencies of document-led work, then prepare the current stage; independent source plans may proceed.", { source_summary: productionSourceSummary(stage) });
|
|
81226
81257
|
}
|
|
81227
81258
|
for (const source2 of stage.scopes)
|
|
81228
81259
|
if (!stage.gaps.some((gap) => gap.scope === source2.scope) && await productionSourceBaseline(input.projectRoot, source2.scope) !== source2.baseline) {
|
|
@@ -92313,11 +92344,11 @@ function reviewApproveCommand(observation) {
|
|
|
92313
92344
|
}
|
|
92314
92345
|
return "context review apply '<review-decisions-file>' --format json";
|
|
92315
92346
|
}
|
|
92316
|
-
function
|
|
92347
|
+
function reviewConfirmedApproveCommand(observation) {
|
|
92317
92348
|
if (observation.draftCandidates <= 0 || observation.draftCollections.length === 0) {
|
|
92318
92349
|
return;
|
|
92319
92350
|
}
|
|
92320
|
-
return observation.draftCollections.length === 1 ? `context review approve-all ${observation.draftCollections[0]} --
|
|
92351
|
+
return observation.draftCollections.length === 1 ? `context review approve-all ${observation.draftCollections[0]} --confirmed --format json` : "context review approve-all --all --confirmed --format json";
|
|
92321
92352
|
}
|
|
92322
92353
|
function workspaceRepairPlan(observation) {
|
|
92323
92354
|
return { commands: [command("context verify --format json --compact", "read")] };
|
|
@@ -92441,8 +92472,8 @@ var init_workflowHostPlans = __esm(() => {
|
|
|
92441
92472
|
commands: next === undefined ? [] : [command(next, "write", "agent-required")]
|
|
92442
92473
|
};
|
|
92443
92474
|
},
|
|
92444
|
-
"context.review.
|
|
92445
|
-
const next =
|
|
92475
|
+
"context.review.confirm-current": (observation) => {
|
|
92476
|
+
const next = reviewConfirmedApproveCommand(observation);
|
|
92446
92477
|
return {
|
|
92447
92478
|
commands: next === undefined ? [] : [command(next, "write")]
|
|
92448
92479
|
};
|
|
@@ -98100,7 +98131,7 @@ var init_larkResourceCommand = __esm(() => {
|
|
|
98100
98131
|
|
|
98101
98132
|
// src/lib/larkMediaDownload.ts
|
|
98102
98133
|
import { readdir as readdir21, rm as rm20 } from "node:fs/promises";
|
|
98103
|
-
import { join as
|
|
98134
|
+
import { join as join95 } from "node:path";
|
|
98104
98135
|
async function downloadLarkMedia(input) {
|
|
98105
98136
|
const shared = ["--as", input.identity, "--token", input.token, "--output", "./resource", "--format", "json"];
|
|
98106
98137
|
try {
|
|
@@ -98110,7 +98141,7 @@ async function downloadLarkMedia(input) {
|
|
|
98110
98141
|
if (!input.allowPreview || !isLarkResourcePermissionDenied(error))
|
|
98111
98142
|
throw error;
|
|
98112
98143
|
for (const entry of await readdir21(input.cwd))
|
|
98113
|
-
await rm20(
|
|
98144
|
+
await rm20(join95(input.cwd, entry), { recursive: true, force: true });
|
|
98114
98145
|
await runLarkResourceCommand(input.runner, ["docs", "+media-preview", ...shared], { cwd: input.cwd });
|
|
98115
98146
|
return "preview";
|
|
98116
98147
|
}
|
|
@@ -98120,9 +98151,9 @@ var init_larkMediaDownload = __esm(() => {
|
|
|
98120
98151
|
});
|
|
98121
98152
|
|
|
98122
98153
|
// src/lib/larkWhiteboardExport.ts
|
|
98123
|
-
import { mkdtemp as mkdtemp4, readFile as
|
|
98154
|
+
import { mkdtemp as mkdtemp4, readFile as readFile75, rm as rm21 } from "node:fs/promises";
|
|
98124
98155
|
import { tmpdir } from "node:os";
|
|
98125
|
-
import { join as
|
|
98156
|
+
import { join as join96 } from "node:path";
|
|
98126
98157
|
function stableJson2(value) {
|
|
98127
98158
|
if (value === undefined)
|
|
98128
98159
|
return "null";
|
|
@@ -98152,7 +98183,7 @@ function canonicalWhiteboardPayload(value) {
|
|
|
98152
98183
|
};
|
|
98153
98184
|
}
|
|
98154
98185
|
async function exportLarkWhiteboardRaw(runner2, identity2, token) {
|
|
98155
|
-
const tempRoot = await mkdtemp4(
|
|
98186
|
+
const tempRoot = await mkdtemp4(join96(tmpdir(), "context-lark-whiteboard-"));
|
|
98156
98187
|
try {
|
|
98157
98188
|
await runLarkResourceCommand(runner2, [
|
|
98158
98189
|
"whiteboard",
|
|
@@ -98169,7 +98200,7 @@ async function exportLarkWhiteboardRaw(runner2, identity2, token) {
|
|
|
98169
98200
|
"--format",
|
|
98170
98201
|
"json"
|
|
98171
98202
|
], { cwd: tempRoot });
|
|
98172
|
-
const rawPayload = JSON.parse(await
|
|
98203
|
+
const rawPayload = JSON.parse(await readFile75(join96(tempRoot, "raw.json"), "utf8"));
|
|
98173
98204
|
return Buffer.from(`${stableJson2(canonicalWhiteboardPayload(rawPayload))}
|
|
98174
98205
|
`, "utf8");
|
|
98175
98206
|
} finally {
|
|
@@ -98208,8 +98239,8 @@ var init_larkImagePolicy = __esm(() => {
|
|
|
98208
98239
|
|
|
98209
98240
|
// src/lib/larkResourceMaterialization.ts
|
|
98210
98241
|
import { createHash as createHash28 } from "node:crypto";
|
|
98211
|
-
import { mkdtemp as mkdtemp5, readFile as
|
|
98212
|
-
import { extname as extname13, join as
|
|
98242
|
+
import { mkdtemp as mkdtemp5, readFile as readFile76, readdir as readdir22, rm as rm22 } from "node:fs/promises";
|
|
98243
|
+
import { extname as extname13, join as join97 } from "node:path";
|
|
98213
98244
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
98214
98245
|
function countByKind(items, status) {
|
|
98215
98246
|
const counts2 = new Map;
|
|
@@ -98314,17 +98345,17 @@ function findBooleanField(value, name3) {
|
|
|
98314
98345
|
}
|
|
98315
98346
|
async function downloadedFile(input) {
|
|
98316
98347
|
if (input.localPath !== undefined) {
|
|
98317
|
-
const bytes = await
|
|
98348
|
+
const bytes = await readFile76(input.localPath);
|
|
98318
98349
|
return { path: input.localPath, bytes, mediaType: mediaTypeFor(input.localPath, bytes), representation: "original" };
|
|
98319
98350
|
}
|
|
98320
|
-
const tempRoot = await mkdtemp5(
|
|
98351
|
+
const tempRoot = await mkdtemp5(join97(tmpdir2(), "context-lark-resource-"));
|
|
98321
98352
|
try {
|
|
98322
98353
|
const representation = await downloadLarkMedia({ ...input, cwd: tempRoot });
|
|
98323
98354
|
const entries2 = (await readdir22(tempRoot, { withFileTypes: true })).filter((entry) => entry.isFile());
|
|
98324
98355
|
if (entries2.length !== 1)
|
|
98325
98356
|
throw new Error(`media download produced ${entries2.length} files, expected exactly one`);
|
|
98326
98357
|
const path3 = entries2[0]?.name ?? "resource.bin";
|
|
98327
|
-
const bytes = await
|
|
98358
|
+
const bytes = await readFile76(join97(tempRoot, path3));
|
|
98328
98359
|
return { path: path3, bytes, mediaType: mediaTypeFor(path3, bytes), representation };
|
|
98329
98360
|
} finally {
|
|
98330
98361
|
await rm22(tempRoot, { recursive: true, force: true });
|
|
@@ -98396,9 +98427,9 @@ async function sheetMaterialization(resource, runner2, identity2) {
|
|
|
98396
98427
|
const sheetId = resource.attributes["sheet-id"];
|
|
98397
98428
|
if (token === undefined || sheetId === undefined)
|
|
98398
98429
|
throw new Error("embedded Sheet requires token and sheet-id");
|
|
98399
|
-
const tempRoot = await mkdtemp5(
|
|
98430
|
+
const tempRoot = await mkdtemp5(join97(tmpdir2(), "context-lark-sheet-"));
|
|
98400
98431
|
try {
|
|
98401
|
-
const outputPath =
|
|
98432
|
+
const outputPath = join97(tempRoot, "sheet.json");
|
|
98402
98433
|
const stdout = await runLarkResourceCommand(runner2, [
|
|
98403
98434
|
"sheets",
|
|
98404
98435
|
"+csv-get",
|
|
@@ -98418,7 +98449,7 @@ async function sheetMaterialization(resource, runner2, identity2) {
|
|
|
98418
98449
|
if (findBooleanField(receipt2, "truncated") === true || findBooleanField(receipt2, "complete") === false) {
|
|
98419
98450
|
throw new Error("embedded Sheet read was truncated");
|
|
98420
98451
|
}
|
|
98421
|
-
const payload = JSON.parse(await
|
|
98452
|
+
const payload = JSON.parse(await readFile76(outputPath, "utf8"));
|
|
98422
98453
|
const csv = findStringField(payload, new Set(["annotated_csv", "csv", "content", "text"]));
|
|
98423
98454
|
if (csv === undefined)
|
|
98424
98455
|
throw new Error("embedded Sheet response has no CSV payload");
|
|
@@ -99533,8 +99564,8 @@ var init_sensitiveSourceLiteral = __esm(() => {
|
|
|
99533
99564
|
|
|
99534
99565
|
// src/project/larkSnapshotBundle.ts
|
|
99535
99566
|
import { createHash as createHash29 } from "node:crypto";
|
|
99536
|
-
import { lstat as lstat10, mkdir as mkdir33, readFile as
|
|
99537
|
-
import { basename as basename10, dirname as dirname39, isAbsolute as isAbsolute17, join as
|
|
99567
|
+
import { lstat as lstat10, mkdir as mkdir33, readFile as readFile77, realpath as realpath10, rename as rename8, writeFile as writeFile26 } from "node:fs/promises";
|
|
99568
|
+
import { basename as basename10, dirname as dirname39, isAbsolute as isAbsolute17, join as join98, relative as relative24, resolve as resolve30, sep as sep8 } from "node:path";
|
|
99538
99569
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "node:util";
|
|
99539
99570
|
function normalizedSnapshotResourcePolicy(policy) {
|
|
99540
99571
|
return { ...policy, images: policy.images ?? "bundle", gifs: policy.gifs ?? "bundle" };
|
|
@@ -99575,13 +99606,13 @@ async function readFileEntry(root2, entry) {
|
|
|
99575
99606
|
const parts = safePath(entry.path).split("/");
|
|
99576
99607
|
let current2 = root2;
|
|
99577
99608
|
for (const [index2, part] of parts.entries()) {
|
|
99578
|
-
current2 =
|
|
99609
|
+
current2 = join98(current2, part);
|
|
99579
99610
|
const stat11 = await lstat10(current2);
|
|
99580
99611
|
if (stat11.isSymbolicLink() || (index2 === parts.length - 1 ? !stat11.isFile() : !stat11.isDirectory())) {
|
|
99581
99612
|
throw larkSnapshotError(`Snapshot entry is not a regular file: ${entry.path}`, "path-invalid");
|
|
99582
99613
|
}
|
|
99583
99614
|
}
|
|
99584
|
-
const bytes = await
|
|
99615
|
+
const bytes = await readFile77(current2);
|
|
99585
99616
|
if (digest6(bytes) !== entry.sha256)
|
|
99586
99617
|
throw larkSnapshotError(`Snapshot content changed: ${entry.path}`, "digest-mismatch");
|
|
99587
99618
|
return bytes;
|
|
@@ -99644,13 +99675,13 @@ async function writeLarkSnapshotBundle(input) {
|
|
|
99644
99675
|
};
|
|
99645
99676
|
schema2.parse(manifest);
|
|
99646
99677
|
for (const [path3, bytes] of files) {
|
|
99647
|
-
await mkdir33(dirname39(
|
|
99648
|
-
await writeFile26(
|
|
99678
|
+
await mkdir33(dirname39(join98(root2, path3)), { recursive: true });
|
|
99679
|
+
await writeFile26(join98(root2, path3), bytes, { flag: "wx", mode: 384 });
|
|
99649
99680
|
}
|
|
99650
99681
|
await validateBundle(root2, manifest);
|
|
99651
|
-
await writeFile26(
|
|
99682
|
+
await writeFile26(join98(root2, `${FILE2}.pending`), `${JSON.stringify(manifest, null, 2)}
|
|
99652
99683
|
`, { flag: "wx", mode: 384 });
|
|
99653
|
-
await rename8(
|
|
99684
|
+
await rename8(join98(root2, `${FILE2}.pending`), join98(root2, FILE2));
|
|
99654
99685
|
return manifest;
|
|
99655
99686
|
}
|
|
99656
99687
|
async function validateBundle(root2, manifest) {
|
|
@@ -99723,11 +99754,11 @@ async function validateBundle(root2, manifest) {
|
|
|
99723
99754
|
async function readLarkSnapshotBundle(directory, target) {
|
|
99724
99755
|
try {
|
|
99725
99756
|
const root2 = await assertSnapshotDirectory(directory);
|
|
99726
|
-
const path3 =
|
|
99757
|
+
const path3 = join98(root2, FILE2);
|
|
99727
99758
|
const stat11 = await lstat10(path3);
|
|
99728
99759
|
if (stat11.isSymbolicLink() || !stat11.isFile())
|
|
99729
99760
|
throw larkSnapshotError("Snapshot receipt must be a regular file", "path-invalid");
|
|
99730
|
-
const manifest = schema2.parse(JSON.parse(await
|
|
99761
|
+
const manifest = schema2.parse(JSON.parse(await readFile77(path3, "utf8")));
|
|
99731
99762
|
assertTarget(manifest.url, target);
|
|
99732
99763
|
return await validateBundle(root2, manifest);
|
|
99733
99764
|
} catch (error) {
|
|
@@ -99751,8 +99782,8 @@ async function reserveSnapshotDirectory(directory, workspaceRoot) {
|
|
|
99751
99782
|
ancestor = dirname39(ancestor);
|
|
99752
99783
|
}
|
|
99753
99784
|
}
|
|
99754
|
-
const parent =
|
|
99755
|
-
const output =
|
|
99785
|
+
const parent = join98(ancestor, ...missing);
|
|
99786
|
+
const output = join98(parent, basename10(requested));
|
|
99756
99787
|
const { findContextProjectRoot: findContextProjectRoot2 } = await Promise.resolve().then(() => (init_workspace(), exports_workspace));
|
|
99757
99788
|
const owner = findContextProjectRoot2(parent)?.projectRoot ?? workspaceRoot;
|
|
99758
99789
|
if (owner !== undefined) {
|
|
@@ -99825,8 +99856,8 @@ var init_larkSnapshotBundle = __esm(() => {
|
|
|
99825
99856
|
});
|
|
99826
99857
|
|
|
99827
99858
|
// src/project/documentCaptureLark.ts
|
|
99828
|
-
import { readdir as readdir23, readFile as
|
|
99829
|
-
import { basename as basename11, extname as extname14, join as
|
|
99859
|
+
import { readdir as readdir23, readFile as readFile78 } from "node:fs/promises";
|
|
99860
|
+
import { basename as basename11, extname as extname14, join as join99 } from "node:path";
|
|
99830
99861
|
function titleFromMarkdown2(markdown, fallbackPath) {
|
|
99831
99862
|
const heading2 = markdown.split(`
|
|
99832
99863
|
`).find((line) => /^#\s+\S/u.test(line));
|
|
@@ -99845,7 +99876,7 @@ function countLines3(markdown) {
|
|
|
99845
99876
|
}
|
|
99846
99877
|
async function fileContentMatches(path3, content3) {
|
|
99847
99878
|
try {
|
|
99848
|
-
const current2 = await
|
|
99879
|
+
const current2 = await readFile78(path3);
|
|
99849
99880
|
const expected = typeof content3 === "string" ? Buffer.from(content3, "utf8") : Buffer.from(content3);
|
|
99850
99881
|
return current2.equals(expected);
|
|
99851
99882
|
} catch {
|
|
@@ -99853,7 +99884,7 @@ async function fileContentMatches(path3, content3) {
|
|
|
99853
99884
|
}
|
|
99854
99885
|
}
|
|
99855
99886
|
function sourceManifestPath2(entry) {
|
|
99856
|
-
return entry.snapshot?.manifest ??
|
|
99887
|
+
return entry.snapshot?.manifest ?? join99(entry.materializedAt, "manifest.json");
|
|
99857
99888
|
}
|
|
99858
99889
|
function larkRuntimeError(message, detail) {
|
|
99859
99890
|
return new ContextError(ExitCode.ExternalToolError, message, {
|
|
@@ -99980,7 +100011,7 @@ function assetManifestEntry(asset, assetRoot) {
|
|
|
99980
100011
|
};
|
|
99981
100012
|
}
|
|
99982
100013
|
async function listSnapshotAssetFiles(root2, assetRoot) {
|
|
99983
|
-
const assetsRoot =
|
|
100014
|
+
const assetsRoot = join99(root2, assetRoot);
|
|
99984
100015
|
const files = [];
|
|
99985
100016
|
const visit4 = async (dir, prefix = assetRoot) => {
|
|
99986
100017
|
let entries2;
|
|
@@ -99993,7 +100024,7 @@ async function listSnapshotAssetFiles(root2, assetRoot) {
|
|
|
99993
100024
|
}
|
|
99994
100025
|
for (const entry of entries2) {
|
|
99995
100026
|
const relPath = `${prefix}/${entry.name}`;
|
|
99996
|
-
const absolutePath =
|
|
100027
|
+
const absolutePath = join99(dir, entry.name);
|
|
99997
100028
|
if (entry.isDirectory()) {
|
|
99998
100029
|
await visit4(absolutePath, relPath);
|
|
99999
100030
|
continue;
|
|
@@ -100008,7 +100039,7 @@ async function listSnapshotAssetFiles(root2, assetRoot) {
|
|
|
100008
100039
|
}
|
|
100009
100040
|
async function staleSnapshotAssetPaths(input) {
|
|
100010
100041
|
const existingPaths = await listSnapshotAssetFiles(input.materializedAtAbsPath, input.assetRoot);
|
|
100011
|
-
return existingPaths.filter((path3) => !input.currentPaths.has(path3)).map((path3) =>
|
|
100042
|
+
return existingPaths.filter((path3) => !input.currentPaths.has(path3)).map((path3) => join99(input.materializedAtAbsPath, path3));
|
|
100012
100043
|
}
|
|
100013
100044
|
function normalizeLarkError(error, sourceName) {
|
|
100014
100045
|
if (error instanceof ContextError)
|
|
@@ -100129,9 +100160,9 @@ async function runCaptureLarkPhaseUnlocked(input) {
|
|
|
100129
100160
|
locator
|
|
100130
100161
|
}];
|
|
100131
100162
|
const manifestPath = sourceManifestPath2(entry);
|
|
100132
|
-
const manifestAbsPath =
|
|
100163
|
+
const manifestAbsPath = join99(input.projectRoot, manifestPath);
|
|
100133
100164
|
const materializedAt = entry.materializedAt;
|
|
100134
|
-
const materializedAtAbsPath =
|
|
100165
|
+
const materializedAtAbsPath = join99(input.projectRoot, materializedAt);
|
|
100135
100166
|
const manifest = createDocumentSnapshotManifest({
|
|
100136
100167
|
sourceType: "lark",
|
|
100137
100168
|
sourceName: resolved.sourceName,
|
|
@@ -100165,13 +100196,13 @@ async function runCaptureLarkPhaseUnlocked(input) {
|
|
|
100165
100196
|
}));
|
|
100166
100197
|
try {
|
|
100167
100198
|
const requestedWrites = [{
|
|
100168
|
-
path:
|
|
100199
|
+
path: join99(materializedAtAbsPath, documentPath),
|
|
100169
100200
|
bytes: normalized
|
|
100170
100201
|
}];
|
|
100171
100202
|
for (const asset of assets) {
|
|
100172
100203
|
if (asset.bytes !== undefined) {
|
|
100173
100204
|
requestedWrites.push({
|
|
100174
|
-
path:
|
|
100205
|
+
path: join99(materializedAtAbsPath, asset.entry.path),
|
|
100175
100206
|
bytes: asset.bytes
|
|
100176
100207
|
});
|
|
100177
100208
|
}
|
|
@@ -100188,7 +100219,7 @@ async function runCaptureLarkPhaseUnlocked(input) {
|
|
|
100188
100219
|
currentPaths: new Set(assets.filter((asset) => asset.bytes !== undefined).map((asset) => asset.entry.path))
|
|
100189
100220
|
});
|
|
100190
100221
|
await applyAtomicFileBatch({
|
|
100191
|
-
transactionRoot:
|
|
100222
|
+
transactionRoot: join99(input.projectRoot, ".tmp", "context-runtime", "capture-transactions"),
|
|
100192
100223
|
writes,
|
|
100193
100224
|
removals
|
|
100194
100225
|
});
|
|
@@ -100338,7 +100369,7 @@ __export(exports_articleRetirement, {
|
|
|
100338
100369
|
retireArticles: () => retireArticles,
|
|
100339
100370
|
articleRetirementSchema: () => articleRetirementSchema
|
|
100340
100371
|
});
|
|
100341
|
-
import { readFile as
|
|
100372
|
+
import { readFile as readFile84, readdir as readdir24 } from "node:fs/promises";
|
|
100342
100373
|
import { posix as posix10 } from "node:path";
|
|
100343
100374
|
function rebuildInput(revision) {
|
|
100344
100375
|
return JSON.stringify({ id: `retirement-${revision.slice(7)}`, operation: "rebuild", timing: "priority", targets: [] });
|
|
@@ -100359,7 +100390,7 @@ function invalid2(reason, message, details = {}) {
|
|
|
100359
100390
|
}
|
|
100360
100391
|
async function text9(root2, path3) {
|
|
100361
100392
|
try {
|
|
100362
|
-
return await
|
|
100393
|
+
return await readFile84(await safeProjectTarget(root2, path3), "utf8");
|
|
100363
100394
|
} catch (error) {
|
|
100364
100395
|
if (error.code === "ENOENT")
|
|
100365
100396
|
return;
|
|
@@ -100621,8 +100652,8 @@ __export(exports_writeLockRecovery, {
|
|
|
100621
100652
|
recoverWriterLock: () => recoverWriterLock,
|
|
100622
100653
|
inspectWriterLock: () => inspectWriterLock
|
|
100623
100654
|
});
|
|
100624
|
-
import { lstat as lstat11, mkdir as mkdir35, readFile as
|
|
100625
|
-
import { join as
|
|
100655
|
+
import { lstat as lstat11, mkdir as mkdir35, readFile as readFile85, readdir as readdir25, rename as rename9, rmdir as rmdir2 } from "node:fs/promises";
|
|
100656
|
+
import { join as join104 } from "node:path";
|
|
100626
100657
|
import { createHash as createHash31, randomUUID as randomUUID7 } from "node:crypto";
|
|
100627
100658
|
async function inspectWriterLock(root2) {
|
|
100628
100659
|
const path3 = await safeProjectTarget(root2, lockRelative);
|
|
@@ -100637,7 +100668,7 @@ async function inspectWriterLock(root2) {
|
|
|
100637
100668
|
if (!stat11.isDirectory() || stat11.isSymbolicLink())
|
|
100638
100669
|
throw new Error("Writer lock must be a real directory.");
|
|
100639
100670
|
const ownerPath = await safeProjectTarget(root2, `${lockRelative}/owner.json`);
|
|
100640
|
-
const bytes = await
|
|
100671
|
+
const bytes = await readFile85(ownerPath, "utf8");
|
|
100641
100672
|
const owner = JSON.parse(bytes);
|
|
100642
100673
|
if (owner.protocol !== "context.project-write-lock.v1" || !Number.isSafeInteger(owner.pid) || owner.pid <= 0) {
|
|
100643
100674
|
throw new Error("Writer lock owner is invalid; preserve the lock for diagnosis.");
|
|
@@ -100669,8 +100700,8 @@ async function recoverWriterLock(input) {
|
|
|
100669
100700
|
};
|
|
100670
100701
|
if (input.plan_digest !== before.digest)
|
|
100671
100702
|
throw new Error("Writer lock changed; preview recovery again.");
|
|
100672
|
-
const path3 =
|
|
100673
|
-
const guard =
|
|
100703
|
+
const path3 = join104(input.projectRoot, lockRelative);
|
|
100704
|
+
const guard = join104(path3, ".recovery");
|
|
100674
100705
|
await mkdir35(guard);
|
|
100675
100706
|
let archived = false;
|
|
100676
100707
|
try {
|
|
@@ -100681,7 +100712,7 @@ async function recoverWriterLock(input) {
|
|
|
100681
100712
|
if (names.some((name3) => name3 !== "owner.json" && name3 !== ".recovery"))
|
|
100682
100713
|
throw new Error("Unexpected lock contents; preserve for diagnosis.");
|
|
100683
100714
|
const archive = `.tmp/context-runtime/locks/recovered-write-${randomUUID7()}.lock`;
|
|
100684
|
-
await rename9(path3,
|
|
100715
|
+
await rename9(path3, join104(input.projectRoot, archive));
|
|
100685
100716
|
archived = true;
|
|
100686
100717
|
return { action: "writer-lock-recovered", archived_lock: archive, next: "context task recover --format json" };
|
|
100687
100718
|
} finally {
|
|
@@ -100705,12 +100736,12 @@ __export(exports_taskRecovery, {
|
|
|
100705
100736
|
RECOVERY_COMMAND: () => RECOVERY_COMMAND
|
|
100706
100737
|
});
|
|
100707
100738
|
import { existsSync as existsSync26 } from "node:fs";
|
|
100708
|
-
import { dirname as dirname42, join as
|
|
100709
|
-
import { lstat as lstat12, readdir as readdir26, readFile as
|
|
100739
|
+
import { dirname as dirname42, join as join105 } from "node:path";
|
|
100740
|
+
import { lstat as lstat12, readdir as readdir26, readFile as readFile86 } from "node:fs/promises";
|
|
100710
100741
|
async function recoveryText(root2, path3) {
|
|
100711
100742
|
const target = await safeProjectTarget(root2, path3);
|
|
100712
100743
|
try {
|
|
100713
|
-
return await
|
|
100744
|
+
return await readFile86(target, "utf8");
|
|
100714
100745
|
} catch (error) {
|
|
100715
100746
|
if (error.code === "ENOENT")
|
|
100716
100747
|
return;
|
|
@@ -100723,7 +100754,7 @@ async function recoveryJournals(root2) {
|
|
|
100723
100754
|
await safeProjectTarget(root2, path3);
|
|
100724
100755
|
let stat11;
|
|
100725
100756
|
try {
|
|
100726
|
-
stat11 = await lstat12(
|
|
100757
|
+
stat11 = await lstat12(join105(root2, path3));
|
|
100727
100758
|
} catch (error) {
|
|
100728
100759
|
if (error.code === "ENOENT")
|
|
100729
100760
|
return;
|
|
@@ -100734,7 +100765,7 @@ async function recoveryJournals(root2) {
|
|
|
100734
100765
|
if (stat11.isDirectory()) {
|
|
100735
100766
|
if (depth > 3)
|
|
100736
100767
|
throw new TypeError("Unexpected transaction directory depth; preserve it for diagnosis.");
|
|
100737
|
-
for (const name3 of (await readdir26(
|
|
100768
|
+
for (const name3 of (await readdir26(join105(root2, path3))).sort())
|
|
100738
100769
|
await visit4(`${path3}/${name3}`, depth + 1);
|
|
100739
100770
|
} else if (stat11.isFile())
|
|
100740
100771
|
entries2.push({ path: path3, digest: indexerProtocolDigest(await recoveryText(root2, path3)) });
|
|
@@ -100746,8 +100777,8 @@ function recoveryResources() {
|
|
|
100746
100777
|
try {
|
|
100747
100778
|
const root2 = dirname42(contextWorkflowProviderPath());
|
|
100748
100779
|
const resources = {
|
|
100749
|
-
skill:
|
|
100750
|
-
issue_template:
|
|
100780
|
+
skill: join105(root2, "skills/recover-workspace/SKILL.md"),
|
|
100781
|
+
issue_template: join105(root2, "resources/templates/recovery-issue.md")
|
|
100751
100782
|
};
|
|
100752
100783
|
if (!Object.values(resources).every((path3) => existsSync26(path3)))
|
|
100753
100784
|
throw new Error("Recovery resources are absent from this Provider.");
|
|
@@ -100832,8 +100863,8 @@ var exports_taskLocalSourceAdjustment = {};
|
|
|
100832
100863
|
__export(exports_taskLocalSourceAdjustment, {
|
|
100833
100864
|
adjustLocalRevisionSources: () => adjustLocalRevisionSources
|
|
100834
100865
|
});
|
|
100835
|
-
import { readFile as
|
|
100836
|
-
import { join as
|
|
100866
|
+
import { readFile as readFile87 } from "node:fs/promises";
|
|
100867
|
+
import { join as join106 } from "node:path";
|
|
100837
100868
|
async function adjustLocalRevisionSources(root2, input) {
|
|
100838
100869
|
const { readMaintenance: readMaintenance2 } = await Promise.resolve().then(() => (init_maintenanceStorage(), exports_maintenanceStorage));
|
|
100839
100870
|
if ((await readMaintenance2(root2)).active && await readProductionStage(root2))
|
|
@@ -100876,7 +100907,7 @@ async function adjustLocalRevisionSources(root2, input) {
|
|
|
100876
100907
|
if (input.refresh && (!current2.refresh_sources || indexerProtocolDigest([...current2.refresh_sources].sort()) !== indexerProtocolDigest([...selected].sort()))) {
|
|
100877
100908
|
throw new TypeError("No matching acquisition adjustment exists. Run task adjust without refresh first.");
|
|
100878
100909
|
}
|
|
100879
|
-
const raw = await
|
|
100910
|
+
const raw = await readFile87(join106(root2, await revisionStoragePath(root2)), "utf8");
|
|
100880
100911
|
let next2;
|
|
100881
100912
|
const discardIds = new Set;
|
|
100882
100913
|
if (!input.refresh) {
|
|
@@ -100979,7 +101010,7 @@ ${input.instruction}` : revision.instruction
|
|
|
100979
101010
|
content: content3
|
|
100980
101011
|
}];
|
|
100981
101012
|
if (discardIds.size > 0) {
|
|
100982
|
-
const ledger = await
|
|
101013
|
+
const ledger = await readFile87(join106(root2, CANDIDATE_LEDGER_FILE), "utf8").catch((error) => {
|
|
100983
101014
|
if (error.code === "ENOENT")
|
|
100984
101015
|
return;
|
|
100985
101016
|
throw error;
|
|
@@ -101104,7 +101135,7 @@ var init_taskSourceAdjustment = __esm(() => {
|
|
|
101104
101135
|
});
|
|
101105
101136
|
|
|
101106
101137
|
// src/project/managedDocumentImport.ts
|
|
101107
|
-
import { readFile as
|
|
101138
|
+
import { readFile as readFile96 } from "node:fs/promises";
|
|
101108
101139
|
async function importManagedDocument(projectRoot, value) {
|
|
101109
101140
|
const input = inputSchema.parse(value);
|
|
101110
101141
|
if (input.type !== "sessions" && input.changes !== undefined)
|
|
@@ -101113,7 +101144,7 @@ async function importManagedDocument(projectRoot, value) {
|
|
|
101113
101144
|
const path3 = await assertManagedDocumentPath(projectRoot, input.type, input.name);
|
|
101114
101145
|
let previous3;
|
|
101115
101146
|
try {
|
|
101116
|
-
previous3 = await
|
|
101147
|
+
previous3 = await readFile96(path3, "utf8");
|
|
101117
101148
|
} catch (error) {
|
|
101118
101149
|
if (!(error && typeof error === "object" && ("code" in error) && error.code === "ENOENT"))
|
|
101119
101150
|
throw error;
|
|
@@ -101160,7 +101191,7 @@ var init_managedDocumentImport = __esm(() => {
|
|
|
101160
101191
|
});
|
|
101161
101192
|
|
|
101162
101193
|
// src/project/larkDocumentImport.ts
|
|
101163
|
-
import { readFile as
|
|
101194
|
+
import { readFile as readFile97 } from "node:fs/promises";
|
|
101164
101195
|
import { resolve as resolve40 } from "node:path";
|
|
101165
101196
|
async function importLarkDocument(projectRoot, value) {
|
|
101166
101197
|
const input = schema4.parse(value);
|
|
@@ -101177,7 +101208,7 @@ async function importLarkDocument(projectRoot, value) {
|
|
|
101177
101208
|
const responsePages = [];
|
|
101178
101209
|
for (const path3 of input.response_files) {
|
|
101179
101210
|
assertActionInputWorkspace(projectRoot, path3);
|
|
101180
|
-
responsePages.push(await
|
|
101211
|
+
responsePages.push(await readFile97(resolve40(projectRoot, path3), "utf8"));
|
|
101181
101212
|
}
|
|
101182
101213
|
const mediaFiles = {};
|
|
101183
101214
|
for (const [token, path3] of Object.entries(input.media_files ?? {})) {
|
|
@@ -101244,11 +101275,11 @@ var exports_managedDocumentRename = {};
|
|
|
101244
101275
|
__export(exports_managedDocumentRename, {
|
|
101245
101276
|
renameManagedDocument: () => renameManagedDocument
|
|
101246
101277
|
});
|
|
101247
|
-
import { readFile as
|
|
101248
|
-
import { join as
|
|
101278
|
+
import { readFile as readFile98 } from "node:fs/promises";
|
|
101279
|
+
import { join as join118, posix as posix11 } from "node:path";
|
|
101249
101280
|
async function optionalText2(path3) {
|
|
101250
101281
|
try {
|
|
101251
|
-
return await
|
|
101282
|
+
return await readFile98(path3, "utf8");
|
|
101252
101283
|
} catch (error) {
|
|
101253
101284
|
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT")
|
|
101254
101285
|
return;
|
|
@@ -101332,7 +101363,7 @@ async function renameManagedDocument(input) {
|
|
|
101332
101363
|
if (path3.split("/").includes("..") || path3.startsWith("/"))
|
|
101333
101364
|
throw new TypeError("Source references contain an unsafe knowledge path; repair it before renaming.");
|
|
101334
101365
|
await safeProjectTarget(input.projectRoot, path3);
|
|
101335
|
-
const before = await optionalText2(
|
|
101366
|
+
const before = await optionalText2(join118(input.projectRoot, path3));
|
|
101336
101367
|
if (before === undefined)
|
|
101337
101368
|
continue;
|
|
101338
101369
|
let after;
|
|
@@ -101421,7 +101452,7 @@ var init_managedDocumentRename = __esm(() => {
|
|
|
101421
101452
|
|
|
101422
101453
|
// src/cli.ts
|
|
101423
101454
|
import { existsSync as existsSync36, realpathSync as realpathSync3 } from "node:fs";
|
|
101424
|
-
import { dirname as dirname50, join as
|
|
101455
|
+
import { dirname as dirname50, join as join124 } from "node:path";
|
|
101425
101456
|
import { fileURLToPath as fileURLToPath11, pathToFileURL as pathToFileURL3 } from "node:url";
|
|
101426
101457
|
|
|
101427
101458
|
// ../../node_modules/.bun/commander@11.1.0/node_modules/commander/esm.mjs
|
|
@@ -101476,7 +101507,7 @@ init_dist();
|
|
|
101476
101507
|
init_cliFeedback();
|
|
101477
101508
|
init_errors3();
|
|
101478
101509
|
init_exitCode();
|
|
101479
|
-
import { join as
|
|
101510
|
+
import { join as join93 } from "node:path";
|
|
101480
101511
|
|
|
101481
101512
|
// src/project/documentCaptureAvailability.ts
|
|
101482
101513
|
init_atomicFileBatch();
|
|
@@ -102470,6 +102501,7 @@ async function projectStatusSummary(status, projectRoot) {
|
|
|
102470
102501
|
packages: status.packages.map((item) => ({ name: item.name, state: item.state })),
|
|
102471
102502
|
meaning: status.evidenceStatus === "not-checked" ? "Progress only: workspace verification and delivery freshness were not checked. Delivery actions validate their inputs; context verify performs an explicit audit." : "Approved pages may not yet be built. Package freshness describes dist; task completion describes the current stage only."
|
|
102472
102503
|
},
|
|
102504
|
+
counts_scope: "Workspace inventory, not this request’s additions or article workload. Captured documents, approved articles, pending investigation and source-read failures are different measures; source ready counts alone do not explain failure causes.",
|
|
102473
102505
|
counts: {
|
|
102474
102506
|
sources: status.sourceSummary,
|
|
102475
102507
|
draftCandidates: status.draftCandidates,
|
|
@@ -102556,9 +102588,9 @@ init_workflowTypes();
|
|
|
102556
102588
|
// src/project/reviewHtml.ts
|
|
102557
102589
|
init_candidateLedger();
|
|
102558
102590
|
init_reviewShared();
|
|
102559
|
-
import { mkdir as mkdir31, readFile as
|
|
102591
|
+
import { mkdir as mkdir31, readFile as readFile72, writeFile as writeFile25 } from "node:fs/promises";
|
|
102560
102592
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
102561
|
-
import { dirname as dirname38, isAbsolute as isAbsolute15, join as
|
|
102593
|
+
import { dirname as dirname38, isAbsolute as isAbsolute15, join as join91, resolve as resolve28 } from "node:path";
|
|
102562
102594
|
|
|
102563
102595
|
// src/project/reviewSiteModel.ts
|
|
102564
102596
|
init_siteTheme2();
|
|
@@ -105760,6 +105792,7 @@ async function collectReviewSiteModel(root2, candidates) {
|
|
|
105760
105792
|
html: old === undefined ? renderReviewMarkdown(next.replace(/^# [^\n]+\n*/u, "")) : reviewBodyDiff(body(old).replace(/^# [^\n]+\n*/u, ""), next.replace(/^# [^\n]+\n*/u, "")),
|
|
105761
105793
|
sources: [...r.source_refs, ...r.indexer_candidate.sections.flatMap((s) => s.references.map((ref2) => JSON.stringify(ref2)))]
|
|
105762
105794
|
};
|
|
105795
|
+
page.readVersion = hash3(JSON.stringify([page.id, page.title, page.path, page.html, page.sources]));
|
|
105763
105796
|
if (found)
|
|
105764
105797
|
pages.splice(pages.indexOf(found), 1, page);
|
|
105765
105798
|
else
|
|
@@ -105800,146 +105833,394 @@ async function collectReviewSiteModel(root2, candidates) {
|
|
|
105800
105833
|
const siteTitle = project?.project.packages.flatMap((p) => p.kind === "package.kb" && p.site?.title ? [p.site.title] : [])[0];
|
|
105801
105834
|
const sitePackages = project?.project.packages.flatMap((p) => p.kind === "package.kb" && p.site ? [p.site] : []) ?? [];
|
|
105802
105835
|
const themeCss = siteThemeVariables(await resolveSiteTheme(root2, sitePackages.length === 1 ? sitePackages[0]?.theme : undefined));
|
|
105803
|
-
return { themeCss, title: siteTitle ?? pkg.name ?? "Knowledge
|
|
105836
|
+
return { storageScope: hash3(root2), themeCss, title: siteTitle ?? pkg.name ?? "Knowledge reading report", baselineHash: await reviewSiteBaselineHash(root2, candidates.map((c) => c.record.approved_revision?.previous_path ?? c.record.path)), nodes, pages, navigationBaseline };
|
|
105804
105837
|
}
|
|
105805
105838
|
var reviewHtmlJson = (value) => JSON.stringify(value).replace(/</gu, "\\u003c").replace(/\u2028/gu, "\\u2028").replace(/\u2029/gu, "\\u2029");
|
|
105806
105839
|
|
|
105807
|
-
// src/project/
|
|
105808
|
-
|
|
105809
|
-
|
|
105810
|
-
|
|
105811
|
-
|
|
105812
|
-
|
|
105813
|
-
|
|
105814
|
-
|
|
105815
|
-
|
|
105840
|
+
// src/project/reviewReportScope.ts
|
|
105841
|
+
init_atomicWrite();
|
|
105842
|
+
init_errors3();
|
|
105843
|
+
init_cliFeedback();
|
|
105844
|
+
init_exitCode();
|
|
105845
|
+
init_reviewShared();
|
|
105846
|
+
import { readFile as readFile71 } from "node:fs/promises";
|
|
105847
|
+
import { join as join90 } from "node:path";
|
|
105848
|
+
var scopePath = (root2, scope2) => join90(root2, ".tmp/context-runtime/review", `${scope2}.scope.json`);
|
|
105849
|
+
async function saveReviewReportScope(root2, scope2, candidates, baselineHash) {
|
|
105850
|
+
const ids = candidates.map((candidate) => candidate.record.candidate_id).sort();
|
|
105851
|
+
const value = {
|
|
105852
|
+
baseline_hash: baselineHash,
|
|
105853
|
+
scope: {
|
|
105854
|
+
kind: scope2 === "all" ? "all" : "collection",
|
|
105855
|
+
...scope2 === "all" ? {} : { collection: scope2 },
|
|
105856
|
+
count: ids.length,
|
|
105857
|
+
ids_sha256: candidateIdsHash(ids),
|
|
105858
|
+
candidates_sha256: candidateSetHash(candidates.map((candidate) => candidate.record)),
|
|
105859
|
+
visible_candidate_ids: ids
|
|
105816
105860
|
}
|
|
105817
|
-
|
|
105818
|
-
|
|
105819
|
-
|
|
105820
|
-
|
|
105821
|
-
|
|
105822
|
-
|
|
105823
|
-
|
|
105824
|
-
|
|
105825
|
-
|
|
105826
|
-
|
|
105861
|
+
};
|
|
105862
|
+
await atomicWriteFile(scopePath(root2, scope2), JSON.stringify(value, null, 2) + `
|
|
105863
|
+
`);
|
|
105864
|
+
}
|
|
105865
|
+
async function readConfirmedReviewScope(root2, scope2) {
|
|
105866
|
+
try {
|
|
105867
|
+
const value = JSON.parse(await readFile71(scopePath(root2, scope2), "utf8"));
|
|
105868
|
+
if (!value || typeof value !== "object" || !("scope" in value) || !("baseline_hash" in value))
|
|
105869
|
+
throw new Error("Missing report scope");
|
|
105870
|
+
const parsed = value;
|
|
105871
|
+
if (typeof parsed.baseline_hash !== "string" || !/^[a-f0-9]{64}$/u.test(parsed.baseline_hash) || !parsed.scope || !Number.isInteger(parsed.scope.count) || parsed.scope.count < 0 || typeof parsed.scope.ids_sha256 !== "string" || typeof parsed.scope.candidates_sha256 !== "string" || !Array.isArray(parsed.scope.visible_candidate_ids) || parsed.scope.visible_candidate_ids.some((id3) => typeof id3 !== "string") || (scope2 === "all" ? parsed.scope.kind !== "all" : parsed.scope.kind !== "collection" || parsed.scope.collection !== scope2)) {
|
|
105872
|
+
throw new Error("Invalid report scope");
|
|
105827
105873
|
}
|
|
105828
|
-
|
|
105829
|
-
|
|
105830
|
-
|
|
105874
|
+
return parsed;
|
|
105875
|
+
} catch (error) {
|
|
105876
|
+
throw new ContextError(ExitCode.UserError, "A current report scope is required for user confirmation", {
|
|
105877
|
+
category: ErrorCategory.UserInputInvalid,
|
|
105878
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
105879
|
+
next: `context review html ${scope2 === "all" ? "--all" : scope2} --format json`
|
|
105880
|
+
});
|
|
105831
105881
|
}
|
|
105832
|
-
|
|
105833
|
-
|
|
105834
|
-
|
|
105835
|
-
|
|
105836
|
-
|
|
105837
|
-
|
|
105838
|
-
|
|
105839
|
-
|
|
105840
|
-
|
|
105841
|
-
|
|
105842
|
-
|
|
105843
|
-
|
|
105844
|
-
|
|
105845
|
-
|
|
105846
|
-
|
|
105847
|
-
|
|
105848
|
-
|
|
105849
|
-
|
|
105850
|
-
|
|
105851
|
-
|
|
105852
|
-
|
|
105853
|
-
|
|
105854
|
-
|
|
105882
|
+
}
|
|
105883
|
+
|
|
105884
|
+
// src/project/reviewSiteClient.ts
|
|
105885
|
+
var REVIEW_SITE_CLIENT = `
|
|
105886
|
+
const $ = id => document.getElementById(id);
|
|
105887
|
+
const escape = value => String(value).replace(/[&<>"']/g, char => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[char]));
|
|
105888
|
+
const words = {
|
|
105889
|
+
read: ['已读', 'Read'], revisions: ['修订', 'Revisions'], total: ['总', 'Total'], unit: ['篇', ''],
|
|
105890
|
+
copy: ['复制修订意见', 'Copy revision notes'], home: ['本次内容', 'This update'],
|
|
105891
|
+
homeHint: ['阅读文章,可在正文下方填写修订意见。完成后返回会话确认或反馈。', 'Read the articles and add revision notes below. Return to the conversation to confirm or share feedback.'],
|
|
105892
|
+
navigate: ['目录与文章', 'Directories and articles'], files: ['预期工作区变化', 'Expected workspace changes'],
|
|
105893
|
+
note: ['输入本篇修订意见……', 'Enter revision notes for this article…'], clear: ['清空本条修订', 'Clear this note'],
|
|
105894
|
+
revisionTitle: ['修订意见', 'Revision notes'], revisionHint: ['点击文章标题返回正文,继续编辑修订意见。', 'Select an article title to return to the article and edit its notes.'],
|
|
105895
|
+
empty: ['暂无修订意见', 'No revision notes yet'], emptyHint: ['可在文章正文下方填写修订意见。', 'Add revision notes below an article.'],
|
|
105896
|
+
emptyCopy: ['暂无修订意见,可返回会话确认。', 'No revision notes. You can confirm in the conversation.'],
|
|
105897
|
+
copied: ['已复制修订意见,可粘贴到会话中。', 'Revision notes copied. Paste them into the conversation.'],
|
|
105898
|
+
manualCopy: ['请手动复制修订意见', 'Copy revision notes manually'], manualHint: ['自动复制不可用,请复制下方文本并回复到会话中。', 'Automatic copy is unavailable. Copy the text below and reply in the conversation.'],
|
|
105899
|
+
close: ['关闭', 'Close'], baseline: ['无 Git 导航基线,目录沿用当前工作区;未推断历史目录变化。', 'No Git navigation baseline. The current workspace navigation is shown.'],
|
|
105900
|
+
approvedContext: ['既有正文,仅供对照。', 'Existing content, shown for reference.'], unchanged: ['本次未变更内容略。', 'Unchanged content omitted.'],
|
|
105901
|
+
previous: ['查看旧文本', 'Previous text'], removed: ['移除或替换的旧内容', 'Removed or replaced content'],
|
|
105902
|
+
removedNav: ['移除的目录条目', 'Removed navigation entries'], unplaced: ['待落位文章', 'Unplaced articles'],
|
|
105903
|
+
sources: ['来源引用', 'Sources'], feedbackTag: ['修订', 'Notes'],
|
|
105904
|
+
};
|
|
105905
|
+
let language = 'zh';
|
|
105906
|
+
const t = key => words[key]?.[language === 'zh' ? 0 : 1] || key;
|
|
105907
|
+
const candidates = DATA.pages.filter(page => page.candidate_id);
|
|
105908
|
+
const pagesById = new Map(DATA.pages.map(page => [page.id, page]));
|
|
105909
|
+
const read = new Set();
|
|
105910
|
+
const notes = new Map();
|
|
105911
|
+
const expanded = new Set();
|
|
105912
|
+
let selected = null;
|
|
105913
|
+
let active = null;
|
|
105914
|
+
let view = 'home';
|
|
105915
|
+
let toastTimer;
|
|
105916
|
+
let copyTimer;
|
|
105917
|
+
const storageKey = 'context-reading-v1:' + DATA.storageScope;
|
|
105918
|
+
|
|
105919
|
+
function restore() {
|
|
105920
|
+
for (const page of candidates) {
|
|
105921
|
+
if (page.revisionInstruction?.trim()) notes.set(page.id, page.revisionInstruction);
|
|
105855
105922
|
}
|
|
105856
|
-
|
|
105857
|
-
const
|
|
105858
|
-
|
|
105859
|
-
|
|
105860
|
-
const
|
|
105861
|
-
|
|
105862
|
-
|
|
105863
|
-
|
|
105864
|
-
|
|
105865
|
-
|
|
105866
|
-
|
|
105867
|
-
throw new Error("Review feedback is damaged or incomplete; copy the full code again");
|
|
105868
|
-
let bits = 0, carry = 0;
|
|
105869
|
-
const bytes = [];
|
|
105870
|
-
for (const char of match[1]) {
|
|
105871
|
-
carry = carry << 6 | alphabet.indexOf(char);
|
|
105872
|
-
bits += 6;
|
|
105873
|
-
if (bits >= 8) {
|
|
105874
|
-
bits -= 8;
|
|
105875
|
-
bytes.push(carry >>> bits & 255);
|
|
105923
|
+
try {
|
|
105924
|
+
const saved = JSON.parse(localStorage.getItem(storageKey) || '{}');
|
|
105925
|
+
language = saved.language === 'en' ? 'en' : 'zh';
|
|
105926
|
+
document.body.classList.toggle('dark', saved.dark === true);
|
|
105927
|
+
for (const page of candidates) {
|
|
105928
|
+
const entry = saved.pages?.[page.id];
|
|
105929
|
+
if (entry?.version !== page.readVersion) continue;
|
|
105930
|
+
if (entry.read === true) read.add(page.id);
|
|
105931
|
+
if (typeof entry.note === 'string') {
|
|
105932
|
+
if (entry.note.trim()) notes.set(page.id, entry.note);
|
|
105933
|
+
else notes.delete(page.id);
|
|
105876
105934
|
}
|
|
105877
105935
|
}
|
|
105878
|
-
|
|
105879
|
-
const value = validate({ ...headerValue, repairs: lines.map((line) => {
|
|
105880
|
-
const row = JSON.parse(line);
|
|
105881
|
-
if (!Array.isArray(row) || row.length !== 2)
|
|
105882
|
-
throw new Error("Invalid revision instruction line");
|
|
105883
|
-
return { index: row[0], instruction: row[1] };
|
|
105884
|
-
}) });
|
|
105885
|
-
if (encode(value) !== code3)
|
|
105886
|
-
throw new Error("Noncanonical review feedback encoding");
|
|
105887
|
-
return value;
|
|
105888
|
-
}
|
|
105889
|
-
return { encode, decode: decode2 };
|
|
105936
|
+
} catch { /* The report remains usable when local storage is unavailable. */ }
|
|
105890
105937
|
}
|
|
105891
105938
|
|
|
105892
|
-
|
|
105893
|
-
|
|
105894
|
-
|
|
105895
|
-
|
|
105896
|
-
|
|
105897
|
-
|
|
105898
|
-
|
|
105899
|
-
|
|
105900
|
-
|
|
105901
|
-
|
|
105902
|
-
|
|
105903
|
-
|
|
105904
|
-
function
|
|
105905
|
-
function
|
|
105906
|
-
|
|
105907
|
-
|
|
105908
|
-
|
|
105909
|
-
|
|
105910
|
-
function
|
|
105911
|
-
|
|
105912
|
-
|
|
105913
|
-
|
|
105914
|
-
|
|
105915
|
-
function
|
|
105916
|
-
|
|
105917
|
-
|
|
105918
|
-
|
|
105919
|
-
|
|
105920
|
-
function
|
|
105921
|
-
|
|
105922
|
-
|
|
105923
|
-
|
|
105924
|
-
|
|
105925
|
-
|
|
105926
|
-
|
|
105927
|
-
|
|
105928
|
-
|
|
105929
|
-
const
|
|
105930
|
-
|
|
105931
|
-
|
|
105932
|
-
|
|
105933
|
-
|
|
105934
|
-
|
|
105935
|
-
|
|
105936
|
-
|
|
105939
|
+
function persist() {
|
|
105940
|
+
try {
|
|
105941
|
+
localStorage.setItem(storageKey, JSON.stringify({
|
|
105942
|
+
language, dark: document.body.classList.contains('dark'),
|
|
105943
|
+
pages: Object.fromEntries(candidates.map(page => [page.id, {
|
|
105944
|
+
version: page.readVersion, read: read.has(page.id), note: notes.get(page.id) || '',
|
|
105945
|
+
}])),
|
|
105946
|
+
}));
|
|
105947
|
+
} catch { /* Reading and copying still work without storage access. */ }
|
|
105948
|
+
}
|
|
105949
|
+
|
|
105950
|
+
function revisedPages() { return candidates.filter(page => (notes.get(page.id) || '').trim()); }
|
|
105951
|
+
function countText(label, count) { return \`\${label} <b>\${count}</b>\${language === 'zh' ? ' 篇' : ''}\`; }
|
|
105952
|
+
function badges(page) {
|
|
105953
|
+
if (!page?.candidate_id) return '';
|
|
105954
|
+
return (read.has(page.id) ? '<span class="badge read">READ</span>' : '')
|
|
105955
|
+
+ ((notes.get(page.id) || '').trim() ? \`<span class="badge has-feedback">\${t('feedbackTag')}</span>\` : '');
|
|
105956
|
+
}
|
|
105957
|
+
function badge(change) {
|
|
105958
|
+
if (!change || change === 'unchanged') return '';
|
|
105959
|
+
const label = { new: 'New', modify: 'Modify', removed: 'Remove' }[change] || change;
|
|
105960
|
+
return \`<span class="badge \${escape(change)}">\${escape(label)}</span>\`;
|
|
105961
|
+
}
|
|
105962
|
+
function descendants(key, seen = new Set()) {
|
|
105963
|
+
if (seen.has(key)) return [];
|
|
105964
|
+
seen.add(key);
|
|
105965
|
+
return DATA.nodes.filter(node => node.parent === key).flatMap(node => [node, ...descendants(node.key, seen)]);
|
|
105966
|
+
}
|
|
105967
|
+
function nodeChange(node) {
|
|
105968
|
+
if (node.removed) return 'removed';
|
|
105969
|
+
const nested = descendants(node.key);
|
|
105970
|
+
const changes = [node.change, ...nested.map(child => child.change),
|
|
105971
|
+
...[node, ...nested].map(child => pagesById.get(child.page)?.change)];
|
|
105972
|
+
return nested.some(child => child.removed) || changes.includes('modify') ? 'modify' : changes.includes('new') ? 'new' : 'unchanged';
|
|
105973
|
+
}
|
|
105974
|
+
function nodeTitle(node) { return node.key === 'review-unplaced' ? t('unplaced') : node.title; }
|
|
105975
|
+
function nodeLabel(node) {
|
|
105976
|
+
const title = escape(nodeTitle(node));
|
|
105977
|
+
return (node.removed ? \`<del>\${title}</del>\` : title)
|
|
105978
|
+
+ (node.oldTitle ? \`<small class="old-title"> ← \${escape(node.oldTitle)}</small>\` : '');
|
|
105979
|
+
}
|
|
105980
|
+
function rootFor(pageId) {
|
|
105981
|
+
let node = DATA.nodes.find(item => item.page === pageId);
|
|
105982
|
+
const seen = new Set();
|
|
105983
|
+
while (node?.parent && !seen.has(node.key)) { seen.add(node.key); node = DATA.nodes.find(item => item.key === node.parent); }
|
|
105984
|
+
return node?.key || null;
|
|
105985
|
+
}
|
|
105986
|
+
|
|
105987
|
+
function updateCounts() {
|
|
105988
|
+
const revised = revisedPages().length;
|
|
105989
|
+
$('counts').innerHTML = \`<span>\${countText(t('read'), read.size)}</span><span class="separator">/</span>\`
|
|
105990
|
+
+ \`<button id="revisions-link" class="\${revised ? 'has-notes' : ''}" aria-pressed="\${view === 'revisions'}">\${countText(t('revisions'), revised)}</button>\`
|
|
105991
|
+
+ \`<span class="separator">/</span><span>\${countText(t('total'), candidates.length)}</span>\`;
|
|
105992
|
+
$('revisions-link').onclick = () => { view = 'revisions'; selected = null; location.hash = 'revisions'; render(); window.scrollTo(0, 0); };
|
|
105993
|
+
$('read-fill').style.width = (candidates.length ? read.size / candidates.length * 100 : 0) + '%';
|
|
105994
|
+
const track = document.querySelector('.read-track');
|
|
105995
|
+
track.setAttribute('aria-valuenow', String(read.size));
|
|
105996
|
+
track.setAttribute('aria-valuemax', String(candidates.length));
|
|
105997
|
+
}
|
|
105998
|
+
|
|
105999
|
+
function renderNav() {
|
|
106000
|
+
const roots = DATA.nodes.filter(node => !node.parent).sort((a, b) => a.order - b.order);
|
|
106001
|
+
$('top').innerHTML = roots.map(node => \`<button data-root="\${escape(node.key)}" class="\${nodeChange(node)}\${active === node.key ? ' active' : ''}">\${nodeLabel(node)}</button>\`).join('');
|
|
106002
|
+
function nodeButton(node, depth) {
|
|
106003
|
+
const page = pagesById.get(node.page);
|
|
106004
|
+
const isDirectory = DATA.nodes.some(child => child.parent === node.key);
|
|
106005
|
+
return \`<button data-node="\${escape(node.key)}" title="\${escape(nodeTitle(node))}" class="node \${isDirectory ? 'directory ' : ''}\${nodeChange(node)}\${selected === node.page ? ' selected' : ''}" style="padding-left:\${26 + 14 * depth}px">\`
|
|
106006
|
+
+ \`<span class="node-content"><span class="node-title">\${nodeLabel(node)}</span>\`
|
|
106007
|
+
+ \`<span class="node-status">\${badges(page)}</span>\${isDirectory ? '<span class="caret">›</span>' : ''}</span></button>\`;
|
|
106008
|
+
}
|
|
106009
|
+
let content = '';
|
|
106010
|
+
function walk(key, depth, seen = new Set()) {
|
|
106011
|
+
if (seen.has(key)) return;
|
|
106012
|
+
seen.add(key);
|
|
106013
|
+
for (const node of DATA.nodes.filter(item => item.parent === key).sort((a, b) => a.order - b.order)) {
|
|
106014
|
+
content += nodeButton(node, depth);
|
|
106015
|
+
if (expanded.has(node.key) || node.page === selected || descendants(node.key).some(child => child.page === selected)) walk(node.key, depth + 1, seen);
|
|
106016
|
+
}
|
|
106017
|
+
}
|
|
106018
|
+
if (active) walk(active, 0);
|
|
106019
|
+
else content = roots.map(node => nodeButton(node, 0)).join('');
|
|
106020
|
+
$('tree').innerHTML = content;
|
|
106021
|
+
}
|
|
106022
|
+
|
|
106023
|
+
function workspaceTree() {
|
|
106024
|
+
const root = {};
|
|
106025
|
+
for (const page of candidates) {
|
|
106026
|
+
let node = root;
|
|
106027
|
+
for (const part of ('knowledge/' + page.path).split('/')) node = node[part] ??= {};
|
|
106028
|
+
node.$page = page;
|
|
106029
|
+
}
|
|
106030
|
+
function lines(node, depth = 0) {
|
|
106031
|
+
return Object.entries(node).filter(([key]) => key !== '$page').sort(([a], [b]) => a.localeCompare(b)).map(([key, child]) => {
|
|
106032
|
+
const label = child.$page ? \`<button data-page="\${escape(child.$page.id)}">\${escape(key)}</button>\${badge(child.$page.change)}\${badges(child.$page)}\` : escape(key) + '/';
|
|
106033
|
+
return \`<div style="padding-left:\${depth * 18}px">\${label}</div>\${lines(child, depth + 1)}\`;
|
|
106034
|
+
}).join('');
|
|
106035
|
+
}
|
|
106036
|
+
return \`<div class="workspace-tree">\${lines(root)}</div>\`;
|
|
106037
|
+
}
|
|
106038
|
+
|
|
106039
|
+
function renderHome() {
|
|
106040
|
+
$('article').innerHTML = \`<h1>\${t('home')}</h1><p class="stats">\${t('homeHint')}</p>\`
|
|
106041
|
+
+ (DATA.navigationBaseline === 'current' ? \`<p class="note">\${t('baseline')}</p>\` : '')
|
|
106042
|
+
+ \`<h2>\${t('navigate')}</h2>\`
|
|
106043
|
+
+ DATA.nodes.filter(node => !node.parent).map(node => {
|
|
106044
|
+
const ids = new Set([node, ...descendants(node.key)].map(item => item.page));
|
|
106045
|
+
const pages = candidates.filter(page => ids.has(page.id));
|
|
106046
|
+
if (!pages.length) return '';
|
|
106047
|
+
return \`<section class="home-group"><h3>\${nodeLabel(node)}\${badge(nodeChange(node))}</h3><div class="cards">\`
|
|
106048
|
+
+ pages.map(page => \`<button data-page="\${escape(page.id)}">\${escape(page.title)}\${badge(page.change)}<span data-status-for="\${escape(page.id)}">\${badges(page)}</span></button>\`).join('') + '</div></section>';
|
|
106049
|
+
}).join('')
|
|
106050
|
+
+ (DATA.nodes.some(node => node.removed) ? \`<h2>\${t('removedNav')}</h2><ul>\${DATA.nodes.filter(node => node.removed).map(node => \`<li class="removed">\${nodeLabel(node)}</li>\`).join('')}</ul>\` : '')
|
|
106051
|
+
+ \`<h2>\${t('files')}</h2>\${workspaceTree()}\`;
|
|
106052
|
+
}
|
|
106053
|
+
|
|
106054
|
+
function renderRevisions() {
|
|
106055
|
+
const pages = revisedPages();
|
|
106056
|
+
$('article').innerHTML = \`<h1>\${t('revisionTitle')} <span class="revision-count">· \${pages.length}</span></h1><p class="stats">\${t('revisionHint')}</p>\`
|
|
106057
|
+
+ (pages.length ? pages.map(page => \`<section class="revision-card"><h2><button class="revision-title" data-page="\${escape(page.id)}">\${escape(page.title)}<span class="arrow" aria-hidden="true">↗</span></button></h2>\`
|
|
106058
|
+
+ \`<p class="revision-path">\${escape(page.path)}</p><p class="revision-body">\${escape(notes.get(page.id))}</p></section>\`).join('')
|
|
106059
|
+
: \`<div class="empty-notes">\${t('empty')}<br><span class="note">\${t('emptyHint')}</span></div>\`);
|
|
106060
|
+
}
|
|
106061
|
+
|
|
106062
|
+
function resizeInput() {
|
|
106063
|
+
const input = $('revision-note');
|
|
106064
|
+
input.style.height = '42px';
|
|
106065
|
+
input.style.height = Math.min(112, Math.max(42, input.scrollHeight + 2)) + 'px';
|
|
106066
|
+
}
|
|
106067
|
+
|
|
106068
|
+
function render() {
|
|
106069
|
+
document.documentElement.lang = language === 'zh' ? 'zh-CN' : 'en';
|
|
106070
|
+
document.body.classList.toggle('home', view === 'home');
|
|
106071
|
+
document.body.classList.toggle('revision-index', view === 'revisions');
|
|
106072
|
+
const page = pagesById.get(selected);
|
|
106073
|
+
$('article').classList.toggle('review-new-page', view === 'article' && page?.change === 'new');
|
|
106074
|
+
renderNav();
|
|
106075
|
+
updateCounts();
|
|
106076
|
+
if (view === 'home') renderHome();
|
|
106077
|
+
else if (view === 'revisions') renderRevisions();
|
|
106078
|
+
else if (page) {
|
|
106079
|
+
$('article').innerHTML = \`<h1>\${escape(page.title)}\${badge(page.change)}<span id="article-status">\${badges(page)}</span></h1>\`
|
|
106080
|
+
+ (page.previousPath ? \`<p class="note">\${escape(page.previousPath)} → \${escape(page.path)}</p>\` : '')
|
|
106081
|
+
+ (page.candidate_id ? page.html : page.html ? \`<p class="note">\${t('approvedContext')}</p>\${page.html}\` : \`<div class="review-omitted">\${t('unchanged')}</div>\`)
|
|
106082
|
+
+ (page.sources.length ? \`<details><summary>\${t('sources')}</summary><ul>\${page.sources.map(source => \`<li>\${escape(source)}</li>\`).join('')}</ul></details>\` : '');
|
|
106083
|
+
}
|
|
106084
|
+
document.querySelectorAll('[data-label]').forEach(element => { element.textContent = t(element.dataset.label); });
|
|
106085
|
+
$('footer').hidden = !(view === 'article' && page?.candidate_id);
|
|
106086
|
+
$('revision-note').value = page ? notes.get(page.id) || '' : '';
|
|
106087
|
+
$('revision-note').placeholder = t('note');
|
|
106088
|
+
$('clear-note').textContent = t('clear');
|
|
106089
|
+
$('clear-note').disabled = !page || !(notes.get(page.id) || '').trim();
|
|
106090
|
+
$('copy-notes').textContent = t('copy');
|
|
106091
|
+
$('copy-close').textContent = t('close');
|
|
106092
|
+
$('language').textContent = language === 'zh' ? 'EN' : '中文';
|
|
106093
|
+
if (!$('footer').hidden) resizeInput();
|
|
106094
|
+
globalThis.contextDiagramViewer?.render($('article'), { dark: document.body.classList.contains('dark'), language });
|
|
106095
|
+
}
|
|
106096
|
+
|
|
106097
|
+
function showPage(id, updateHash = true) {
|
|
106098
|
+
const page = pagesById.get(id);
|
|
106099
|
+
if (!page) return;
|
|
106100
|
+
selected = id;
|
|
106101
|
+
active = rootFor(id) || active;
|
|
106102
|
+
view = 'article';
|
|
106103
|
+
if (page.candidate_id) { read.add(page.id); persist(); }
|
|
106104
|
+
if (updateHash) history.replaceState(null, '', '#article=' + encodeURIComponent(id));
|
|
106105
|
+
render();
|
|
106106
|
+
window.scrollTo(0, 0);
|
|
106107
|
+
const currentNode = $('tree').querySelector('.selected');
|
|
106108
|
+
if (currentNode) $('tree').scrollTop = Math.max(0, currentNode.offsetTop - $('tree').offsetTop - $('tree').clientHeight / 3);
|
|
106109
|
+
}
|
|
106110
|
+
|
|
106111
|
+
function showToast(message) {
|
|
106112
|
+
clearTimeout(toastTimer);
|
|
106113
|
+
$('toast').textContent = message;
|
|
106114
|
+
$('toast').hidden = false;
|
|
106115
|
+
toastTimer = setTimeout(() => { $('toast').hidden = true; }, 3000);
|
|
106116
|
+
}
|
|
106117
|
+
|
|
106118
|
+
function feedbackText() {
|
|
106119
|
+
return t('revisionTitle') + '\\n\\n' + revisedPages().map(page =>
|
|
106120
|
+
\`《\${page.title}》\\n\${language === 'zh' ? '文章 ID' : 'Article ID'}:\${page.id}\\n\${language === 'zh' ? '修订意见' : 'Revision notes'}:\${notes.get(page.id)}\`
|
|
106121
|
+
).join('\\n\\n');
|
|
106122
|
+
}
|
|
106123
|
+
|
|
106124
|
+
async function copyNotes() {
|
|
106125
|
+
if (!revisedPages().length) { showToast(t('emptyCopy')); return; }
|
|
106126
|
+
const text = feedbackText();
|
|
106127
|
+
try {
|
|
106128
|
+
if (!navigator.clipboard?.writeText) throw new Error('Clipboard unavailable');
|
|
106129
|
+
await navigator.clipboard.writeText(text);
|
|
106130
|
+
showCopyDialog(text, true);
|
|
106131
|
+
} catch {
|
|
106132
|
+
showCopyDialog(text, false);
|
|
106133
|
+
}
|
|
106134
|
+
}
|
|
106135
|
+
|
|
106136
|
+
function showCopyDialog(text, copied) {
|
|
106137
|
+
clearInterval(copyTimer);
|
|
106138
|
+
$('copy-title').textContent = copied ? (language === 'zh' ? '✓ 已复制修订意见' : '✓ Revision notes copied') : t('manualCopy');
|
|
106139
|
+
$('copy-title').classList.toggle('copy-success', copied);
|
|
106140
|
+
$('copy-summary').textContent = copied ? (language === 'zh' ? '可返回会话粘贴以下修订意见。' : 'Paste these revision notes into the conversation.') : t('manualHint');
|
|
106141
|
+
$('copy-preview').hidden = !copied;
|
|
106142
|
+
$('copy-preview').classList.remove('has-overflow');
|
|
106143
|
+
$('copy-preview-text').textContent = text;
|
|
106144
|
+
$('copied-notes').hidden = copied;
|
|
106145
|
+
$('copied-notes').value = text;
|
|
106146
|
+
$('copy-close').parentElement.hidden = copied;
|
|
106147
|
+
$('copy-countdown').hidden = !copied;
|
|
106148
|
+
$('copy-countdown').textContent = language === 'zh' ? '5 秒后关闭' : 'Closes in 5s';
|
|
106149
|
+
if (!$('copy-dialog').open) $('copy-dialog').showModal();
|
|
106150
|
+
if (!copied) {
|
|
106151
|
+
$('copied-notes').focus();
|
|
106152
|
+
$('copied-notes').select();
|
|
106153
|
+
return;
|
|
106154
|
+
}
|
|
106155
|
+
const preview = $('copy-preview');
|
|
106156
|
+
preview.classList.toggle('has-overflow', preview.scrollHeight > preview.clientHeight);
|
|
106157
|
+
const deadline = performance.now() + 5000;
|
|
106158
|
+
copyTimer = setInterval(() => {
|
|
106159
|
+
const remaining = Math.max(0, Math.ceil((deadline - performance.now()) / 1000));
|
|
106160
|
+
$('copy-countdown').textContent = language === 'zh' ? \`\${remaining} 秒后关闭\` : \`Closes in \${remaining}s\`;
|
|
106161
|
+
if (!remaining) { clearInterval(copyTimer); $('copy-dialog').close(); }
|
|
106162
|
+
}, 100);
|
|
106163
|
+
}
|
|
106164
|
+
|
|
106165
|
+
document.addEventListener('click', event => {
|
|
106166
|
+
const button = event.target.closest('button');
|
|
106167
|
+
if (!button) return;
|
|
106168
|
+
if (button.dataset.page) showPage(button.dataset.page);
|
|
106169
|
+
else if (button.dataset.root) {
|
|
106170
|
+
active = button.dataset.root;
|
|
106171
|
+
const node = DATA.nodes.find(item => item.key === active);
|
|
106172
|
+
const children = descendants(active);
|
|
106173
|
+
const preferred = candidates.find(page => [node, ...children].some(item => item?.page === page.id));
|
|
106174
|
+
const pageId = preferred?.id || node?.page || children.find(child => child.page)?.page;
|
|
106175
|
+
if (pageId) showPage(pageId);
|
|
106176
|
+
else render();
|
|
106177
|
+
} else if (button.dataset.node) {
|
|
106178
|
+
const node = DATA.nodes.find(item => item.key === button.dataset.node);
|
|
106179
|
+
if (node.page) showPage(node.page);
|
|
106180
|
+
else { expanded.has(node.key) ? expanded.delete(node.key) : expanded.add(node.key); renderNav(); }
|
|
106181
|
+
}
|
|
106182
|
+
});
|
|
106183
|
+
|
|
106184
|
+
$('revision-note').addEventListener('input', event => {
|
|
106185
|
+
const page = pagesById.get(selected);
|
|
106186
|
+
if (!page?.candidate_id) return;
|
|
106187
|
+
const value = event.target.value;
|
|
106188
|
+
if (value.trim()) notes.set(page.id, value);
|
|
106189
|
+
else notes.delete(page.id);
|
|
106190
|
+
$('clear-note').disabled = !value.trim();
|
|
106191
|
+
persist();
|
|
106192
|
+
updateCounts();
|
|
106193
|
+
renderNav();
|
|
106194
|
+
$('article-status').innerHTML = badges(page);
|
|
106195
|
+
resizeInput();
|
|
106196
|
+
});
|
|
106197
|
+
$('clear-note').onclick = () => {
|
|
106198
|
+
notes.delete(selected);
|
|
106199
|
+
persist();
|
|
106200
|
+
$('revision-note').value = '';
|
|
106201
|
+
$('revision-note').dispatchEvent(new Event('input', { bubbles: true }));
|
|
106202
|
+
$('revision-note').focus();
|
|
106203
|
+
};
|
|
106204
|
+
$('home').onclick = () => { selected = null; active = null; view = 'home'; history.replaceState(null, '', location.pathname + location.search); render(); window.scrollTo(0, 0); };
|
|
106205
|
+
$('theme').onclick = () => { document.body.classList.toggle('dark'); persist(); render(); };
|
|
106206
|
+
$('language').onclick = () => { language = language === 'zh' ? 'en' : 'zh'; persist(); render(); };
|
|
106207
|
+
$('copy-notes').onclick = copyNotes;
|
|
106208
|
+
$('copy-close').onclick = () => $('copy-dialog').close();
|
|
106209
|
+
$('copy-dialog').onclose = () => clearInterval(copyTimer);
|
|
106210
|
+
window.addEventListener('hashchange', () => {
|
|
106211
|
+
if (location.hash.startsWith('#article=')) showPage(decodeURIComponent(location.hash.slice(9)), false);
|
|
106212
|
+
else { selected = null; view = location.hash === '#revisions' ? 'revisions' : 'home'; render(); }
|
|
106213
|
+
});
|
|
106214
|
+
new ResizeObserver(() => { document.documentElement.style.setProperty('--header-height', document.querySelector('header').offsetHeight + 'px'); }).observe(document.querySelector('header'));
|
|
106215
|
+
restore();
|
|
106216
|
+
if (location.hash.startsWith('#article=')) showPage(decodeURIComponent(location.hash.slice(9)), false);
|
|
106217
|
+
else { if (location.hash === '#revisions') view = 'revisions'; render(); }
|
|
105937
106218
|
`;
|
|
105938
106219
|
|
|
105939
106220
|
// src/project/reviewSiteStyles.ts
|
|
105940
106221
|
init_diagramStyles();
|
|
105941
106222
|
var REVIEW_SITE_STYLES = String.raw`
|
|
105942
|
-
:root{--blue:#2563eb;--text:#161e2e;--muted:#646b7c;--line:#e7e9ef;--bg:#fff;--side:#f8f9fc;--green:#28734f;--red:#b7444c;--amber:#93651d}*{box-sizing:border-box}body{margin:0;color:var(--text);background:var(--bg);font:14px/1.75 -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif}button,input,textarea{font:inherit}button{cursor:pointer}button:disabled{opacity:.35;cursor:not-allowed}[hidden]{display:none!important}header{height:64px;padding:0 24px;display:flex;align-items:center;border-bottom:1px solid var(--line);position:sticky;top:0;background:var(--bg);z-index:5}#home{border:0;background:none;color:var(--text);width:280px;flex-shrink:0;text-align:left;font-size:15px;font-weight:650;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}nav{display:flex;flex:1;min-width:0;overflow:auto;align-self:stretch}nav button{border:0;background:none;color:var(--text);padding:0 16px;font-size:13px;font-weight:550;white-space:nowrap}nav .active{color:var(--blue);box-shadow:inset 0 -2px var(--blue)}.new,nav .new{color:var(--green)}.modify,nav .modify{color:var(--amber)}.badge{display:inline-block;margin-left:7px;padding:1px 6px;font-size:10px;line-height:18px;vertical-align:middle;border-radius:4px;font-weight:600;letter-spacing:0}.badge.new
|
|
106223
|
+
:root{--blue:#2563eb;--text:#161e2e;--muted:#646b7c;--line:#e7e9ef;--bg:#fff;--side:#f8f9fc;--green:#28734f;--red:#b7444c;--amber:#93651d}*{box-sizing:border-box}body{margin:0;color:var(--text);background:var(--bg);font:14px/1.75 -apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif}button,input,textarea{font:inherit}button{cursor:pointer}button:disabled{opacity:.35;cursor:not-allowed}[hidden]{display:none!important}header{height:64px;padding:0 24px;display:flex;align-items:center;border-bottom:1px solid var(--line);position:sticky;top:0;background:var(--bg);z-index:5}#home{border:0;background:none;color:var(--text);width:280px;flex-shrink:0;text-align:left;font-size:15px;font-weight:650;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}nav{display:flex;flex:1;min-width:0;overflow:auto;align-self:stretch}nav button{border:0;background:none;color:var(--text);padding:0 16px;font-size:13px;font-weight:550;white-space:nowrap}nav .active{color:var(--blue);box-shadow:inset 0 -2px var(--blue)}.new,nav .new{color:var(--green)}.modify,nav .modify{color:var(--amber)}.badge{display:inline-block;margin-left:7px;padding:1px 6px;font-size:10px;line-height:18px;vertical-align:middle;border-radius:4px;font-weight:600;letter-spacing:0}.badge.new{color:var(--green);background:color-mix(in srgb,var(--green) 10%,var(--bg))}.badge.modify{color:var(--amber);background:color-mix(in srgb,var(--amber) 10%,var(--bg))}.badge.removed{color:var(--red);background:color-mix(in srgb,var(--red) 10%,var(--bg))}.removed,nav .removed,.node.removed{color:var(--red)}.badge{border:1px solid color-mix(in srgb,currentColor 18%,transparent)}.tools{display:flex;align-items:center;gap:8px;position:relative;margin-left:12px}.btn{border:1px solid var(--line);border-radius:6px;background:var(--bg);color:var(--text);padding:5px 10px;font-size:12px;line-height:22px;white-space:nowrap}.primary{color:var(--bg);background:var(--blue);border-color:var(--blue)}#theme,#language{margin-left:8px;flex-shrink:0}.layout{display:grid;grid-template-columns:280px minmax(0,1fr)}aside{height:calc(100vh - 64px);position:sticky;top:64px;overflow:auto;background:var(--side);border-right:1px solid var(--line);padding:12px 0 80px}.node{display:block;width:100%;text-align:left;border:0;background:none;height:38px;min-height:38px;padding:9px 16px 9px 26px;font-size:13px;font-weight:450;line-height:20px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--muted)}.node.directory{color:var(--text)}.node.selected.unchanged{color:var(--blue);font-weight:550}.node.new{color:var(--green)}.node.modify{color:var(--amber)}.node.selected{background:color-mix(in srgb,var(--blue) 8%,transparent)}.caret{float:right}.node:hover{background:#8e96aa1a}main{min-width:0;padding:44px 60px 100px 36px}h1{font-size:36px;line-height:1.3;letter-spacing:-.025em;margin:0 0 28px}h2{font-size:25px;line-height:1.4;font-weight:650;margin:46px 0 22px;border-bottom:1px solid var(--line);padding-bottom:14px}h3{font-size:19px;font-weight:600;line-height:1.5;margin:30px 0 14px}article{font-size:16px;line-height:1.8}article>h1>.badge{margin-left:12px}article a{color:inherit}article a:hover{color:var(--blue)}article table{border-collapse:collapse;font-size:14px;display:block;overflow:auto}article th,article td{border:1px solid var(--line);padding:11px 14px}article th{background:var(--side)}pre{overflow:auto;background:var(--side);padding:16px}blockquote{padding:16px 20px;margin:24px 0;border-left:3px solid var(--context-accent);background:color-mix(in srgb,var(--blue) 8%,transparent);color:var(--muted)}.home .layout{display:block}.home aside{display:none}.home main{width:1120px;max-width:calc(100% - 280px);margin-left:280px;padding-top:32px}.home article{font-size:14px}.home h1{font-size:28px;margin-bottom:12px}.home h2{font-size:19px;margin:24px 0 12px;padding-bottom:10px}.home h3{font-size:15px;margin:16px 0 10px}.stats{font-size:12px;color:var(--muted)}.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(270px,1fr));gap:8px}.cards button{border:1px solid var(--line);border-radius:7px;background:var(--bg);color:var(--text);text-align:left;font-size:13px;line-height:20px;padding:10px 14px}.cards button:hover{border-color:var(--blue)}.review-unchanged-heading{color:var(--muted)}.review-omitted{margin:20px 0;padding:18px 20px;border:1px dashed var(--line);border-radius:8px;color:var(--muted);font-size:13px}.changed{position:relative;display:flow-root;color:var(--amber);padding:0 76px 0 0;margin:20px 0}.changed>.badge{position:absolute;right:0;top:4px}.changed>:nth-child(2){margin-top:0}.changed>:last-child{margin-bottom:0}.changed details{color:inherit}.changed.removed{color:var(--red)}.workspace-tree{font:12px/1.9 ui-monospace,monospace;border:1px solid var(--line);border-radius:8px;padding:16px;background:var(--side);overflow:auto}.workspace-tree button{border:0;background:none;color:var(--text);padding:0;font:inherit;white-space:nowrap}.note{font-size:12px;color:var(--muted)}footer{position:fixed;bottom:0;left:280px;right:0;background:var(--bg);border-top:1px solid var(--line);padding:12px 35px;display:flex;gap:10px;z-index:4}footer .btn{min-width:76px}dialog{width:560px;max-width:90vw;background:var(--bg);color:var(--text);border:1px solid var(--line);border-radius:10px;padding:20px;font-size:13px;line-height:1.65}dialog::backdrop{background:#131b3255}dialog h2{font-size:17px;margin:0 0 12px;border:0;padding:0}dialog textarea{width:100%;height:130px;border:1px solid var(--line);border-radius:6px;padding:10px;font:11px/1.6 monospace;background:var(--side);color:var(--text)}.dialog-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:18px}.dark{--bg:#171b24;--side:#1b1d24;--text:#e2e4ed;--muted:#a1a6b7;--line:#303440;--blue:#8eb7ff;--green:#83c9a0;--amber:#dab779;--red:#e49a9e}@media(min-width:1600px){main,.home main{padding-left:52px}}@media(max-width:1279px){#home{width:220px}nav button{padding:0 9px}.tools{gap:4px}}@media(max-width:1050px){header{height:auto;flex-wrap:wrap;min-height:64px}nav{order:3;flex-basis:100%;height:44px}.tools{margin-left:auto}}@media(max-width:700px){.layout{display:block}aside{position:relative;top:0;height:200px}main,.home main{width:100%;max-width:100%;margin:0;padding:24px 20px 90px}footer{left:0;padding:10px;flex-wrap:wrap}footer input{flex-basis:100%}.tools{flex-wrap:wrap}h1{font-size:28px}}
|
|
105943
106224
|
${readerContentStyles("article")}
|
|
105944
106225
|
article code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
|
|
105945
106226
|
article :not(pre)>code{padding:3px 6px;overflow-wrap:break-word}
|
|
@@ -105962,10 +106243,85 @@ article.review-new-page .language-mermaid{--context-text:var(--green);--context-
|
|
|
105962
106243
|
article .changed .language-mermaid{--context-text:var(--amber);--context-muted-text:var(--amber)}
|
|
105963
106244
|
article .changed.removed .language-mermaid{--context-text:var(--red);--context-muted-text:var(--red)}
|
|
105964
106245
|
article.review-new-page .context-diagram-shell,article .changed .context-diagram-shell{--diagram-text:var(--context-text)}
|
|
106246
|
+
header { gap: 0; }
|
|
106247
|
+
header #home { width: 242px; }
|
|
106248
|
+
header nav { scrollbar-width: none; }
|
|
106249
|
+
header nav::-webkit-scrollbar { display: none; }
|
|
106250
|
+
header nav button { padding: 0 11px; }
|
|
106251
|
+
.tools { flex-shrink: 0; gap: 16px; }
|
|
106252
|
+
.reading-progress { min-width: 225px; }
|
|
106253
|
+
#counts { display: flex; align-items: center; justify-content: space-between; gap: 8px; white-space: nowrap; font-size: 12px; color: var(--muted); }
|
|
106254
|
+
#counts b { font-weight: 650; color: var(--text); font-variant-numeric: tabular-nums; }
|
|
106255
|
+
#counts .separator { color: var(--line); }
|
|
106256
|
+
#revisions-link { border: 0; padding: 0; background: transparent; color: var(--muted); font-size: inherit; }
|
|
106257
|
+
#revisions-link.has-notes, #revisions-link.has-notes b { color: var(--amber); }
|
|
106258
|
+
#revisions-link:hover, #revisions-link[aria-pressed="true"] { color: var(--blue); text-decoration: none; }
|
|
106259
|
+
.read-track { height: 2px; margin-top: 5px; background: var(--line); border-radius: 2px; overflow: hidden; }
|
|
106260
|
+
#read-fill { display: block; height: 100%; width: 0; background: var(--blue); transition: width .2s ease; }
|
|
106261
|
+
.badge.read { color: var(--blue); background: color-mix(in srgb,var(--blue) 8%,var(--bg)); font-size: 9px; letter-spacing: .03em; }
|
|
106262
|
+
.badge.has-feedback { color: var(--amber); background: color-mix(in srgb,var(--amber) 8%,var(--bg)); }
|
|
106263
|
+
aside { top: var(--header-height,64px); height: calc(100vh - var(--header-height,64px)); }
|
|
106264
|
+
.node { height: auto; min-height: 38px; }
|
|
106265
|
+
.node .node-content { display: flex; align-items: center; min-width: 0; gap: 5px; }
|
|
106266
|
+
.node .node-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
|
|
106267
|
+
.node .node-status { margin-left: auto; display: inline-flex; gap: 3px; flex-shrink: 0; }
|
|
106268
|
+
.node .badge { margin: 0; padding: 0 4px; line-height: 17px; }
|
|
106269
|
+
.node .caret { margin-left: auto; }
|
|
106270
|
+
main { padding-bottom: 160px; }
|
|
106271
|
+
.home main { max-width: 1100px; width: calc(100% - 280px); }
|
|
106272
|
+
.home h1 { font-size: 28px; }
|
|
106273
|
+
.revision-index main { max-width: 1060px; }
|
|
106274
|
+
.revision-index h1 { font-size: 28px; margin-bottom: 12px; }
|
|
106275
|
+
.revision-index .revision-card { margin: 18px 0; padding: 22px 24px; border: 1px solid var(--line); background: var(--bg); border-radius: 9px; }
|
|
106276
|
+
.revision-card h2 { border: 0; padding: 0; margin: 0 0 8px; font-size: 17px; line-height: 1.5; }
|
|
106277
|
+
.revision-title { border: 0; padding: 0; background: transparent; color: var(--text); text-align: left; font-size: inherit; font-weight: 600; }
|
|
106278
|
+
.revision-title:hover { color: var(--blue); }
|
|
106279
|
+
.revision-title .arrow { color: var(--muted); font-size: 14px; margin-left: 5px; }
|
|
106280
|
+
.revision-path { color: var(--muted); font-size: 11px; margin: 0 0 15px; overflow-wrap: anywhere; }
|
|
106281
|
+
.revision-body { font-size: 14px; line-height: 1.85; white-space: pre-wrap; overflow-wrap: anywhere; margin: 0; }
|
|
106282
|
+
.empty-notes { padding: 38px 24px; margin: 24px 0; border: 1px dashed var(--line); border-radius: 9px; color: var(--muted); text-align: center; }
|
|
106283
|
+
footer { align-items: center; flex-wrap: nowrap; padding: 12px 35px; gap: 12px; }
|
|
106284
|
+
#revision-note { display: block; flex: 1 1 0%; min-width: 0; height: 42px; max-height: 112px; resize: none; overflow-y: auto; border: 1px solid var(--line); border-radius: 6px; padding: 9px 12px; background: var(--side); color: var(--text); font: inherit; font-size: 14px; line-height: 22px; }
|
|
106285
|
+
#revision-note::placeholder { color: var(--muted); opacity: .85; }
|
|
106286
|
+
#revision-note:focus { outline: 2px solid color-mix(in srgb,var(--blue) 25%,transparent); outline-offset: 0; border-color: var(--blue); }
|
|
106287
|
+
#clear-note { flex-shrink: 0; min-width: 108px; height: 36px; }
|
|
106288
|
+
#clear-note:disabled { opacity: .4; }
|
|
106289
|
+
#toast { position: fixed; bottom: 100px; left: 50%; transform: translateX(-50%); z-index: 20; max-width: calc(100vw - 32px); border: 1px solid var(--line); border-radius: 8px; padding: 10px 18px; color: var(--text); background: var(--bg); box-shadow: 0 5px 24px #17244222; font-size: 13px; }
|
|
106290
|
+
#copied-notes { height: 240px; font: 13px/1.8 inherit; resize: vertical; }
|
|
106291
|
+
.copy-heading { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
|
106292
|
+
.copy-heading h2 { margin: 0; }
|
|
106293
|
+
.copy-heading h2.copy-success { color: var(--green); }
|
|
106294
|
+
#copy-countdown { color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
106295
|
+
#copy-preview { position: relative; max-height: min(400px, calc(100dvh - 180px)); overflow: hidden; border: 1px solid var(--line); border-radius: 7px; background: var(--side); }
|
|
106296
|
+
#copy-preview-text { padding: 14px 16px; white-space: pre-wrap; overflow-wrap: anywhere; font-size: 13px; line-height: 1.85; }
|
|
106297
|
+
.copy-ellipsis { display: none; position: absolute; bottom: 0; left: 0; right: 0; height: 40px; padding: 13px 16px 2px; background: linear-gradient(transparent, var(--side) 35%); color: var(--muted); font-size: 16px; }
|
|
106298
|
+
#copy-preview.has-overflow .copy-ellipsis { display: block; }
|
|
106299
|
+
button:focus-visible { outline: 2px solid var(--blue); outline-offset: 3px; }
|
|
106300
|
+
@media (min-width: 1600px) { .home main { max-width: 1100px; } }
|
|
106301
|
+
@media (max-width: 1200px) { header #home { width: 205px; } .tools { gap: 10px; } header nav button { padding: 0 8px; } }
|
|
106302
|
+
@media (max-width: 1050px) { header { padding: 10px 20px 0; } header nav { order: 3; flex-basis: 100%; height: 42px; } header #home { flex: 1; width: auto; } .tools { margin-left: 8px; } }
|
|
106303
|
+
@media (max-width: 700px) {
|
|
106304
|
+
header { padding: 10px 14px 0; gap: 6px 0; }
|
|
106305
|
+
header #home { flex: 1 1 auto; font-size: 14px; }
|
|
106306
|
+
header .tools { order: 2; width: 100%; margin-left: 0; justify-content: space-between; flex-wrap: nowrap; padding: 4px 0; }
|
|
106307
|
+
header .reading-progress { flex: 1; min-width: 0; max-width: 260px; }
|
|
106308
|
+
header #theme, header #language { margin-left: 6px; }
|
|
106309
|
+
header nav { order: 3; margin-top: 2px; }
|
|
106310
|
+
aside { top: 0; position: relative; height: 180px; }
|
|
106311
|
+
main, .home main { width: 100%; max-width: 100%; margin: 0; padding: 24px 20px 150px; }
|
|
106312
|
+
.home h1 { font-size: 26px; }
|
|
106313
|
+
footer { left: 0; flex-wrap: nowrap; gap: 8px; padding: 10px 12px; }
|
|
106314
|
+
#revision-note { flex: 1 1 0%; font-size: 14px; }
|
|
106315
|
+
#clear-note { min-width: 96px; padding: 5px 8px; }
|
|
106316
|
+
.revision-index .revision-card { padding: 18px; }
|
|
106317
|
+
.changed { padding-right: 0; }
|
|
106318
|
+
.changed > .badge { position: static; margin: 0 0 10px; }
|
|
106319
|
+
}
|
|
106320
|
+
|
|
105965
106321
|
`;
|
|
105966
106322
|
|
|
105967
106323
|
// src/project/reviewHtml.ts
|
|
105968
|
-
var REVIEW_HTML_ROOT =
|
|
106324
|
+
var REVIEW_HTML_ROOT = join91(".tmp", "context-runtime", "review");
|
|
105969
106325
|
async function collectReviewCandidates(projectRoot, collection) {
|
|
105970
106326
|
const rows = await readCandidateRecords(projectRoot);
|
|
105971
106327
|
const draftRows = rows.filter((row) => row.collection === collection && row.status === "draft");
|
|
@@ -105982,23 +106338,39 @@ async function collectAllReviewCandidates(projectRoot) {
|
|
|
105982
106338
|
snapshot: await readReviewCandidateSnapshot(projectRoot, record4)
|
|
105983
106339
|
})));
|
|
105984
106340
|
}
|
|
105985
|
-
function renderReviewHtml(
|
|
105986
|
-
|
|
105987
|
-
|
|
105988
|
-
|
|
105989
|
-
|
|
105990
|
-
|
|
105991
|
-
|
|
105992
|
-
<
|
|
105993
|
-
<div class="
|
|
105994
|
-
|
|
105995
|
-
<
|
|
105996
|
-
|
|
105997
|
-
|
|
106341
|
+
function renderReviewHtml(model, diagramScript) {
|
|
106342
|
+
return `<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${escapeReviewHtml(model.title)} · Reading report</title><style>${REVIEW_SITE_STYLES}${model.themeCss ?? ""}</style></head><body>
|
|
106343
|
+
<header>
|
|
106344
|
+
<button id="home" aria-label="返回本次内容">${escapeReviewHtml(model.title)}</button>
|
|
106345
|
+
<nav id="top" aria-label="知识分类"></nav>
|
|
106346
|
+
<div class="tools">
|
|
106347
|
+
<div class="reading-progress">
|
|
106348
|
+
<div id="counts" aria-label="阅读进度"></div>
|
|
106349
|
+
<div class="read-track" role="progressbar" aria-label="本轮阅读进度" aria-valuemin="0" aria-valuenow="0"><span id="read-fill"></span></div>
|
|
106350
|
+
</div>
|
|
106351
|
+
<button class="btn primary" id="copy-notes">复制修订意见</button>
|
|
106352
|
+
</div>
|
|
106353
|
+
<button class="btn" id="theme" aria-label="切换深色主题" title="切换深色主题">◐</button>
|
|
106354
|
+
<button class="btn" id="language" aria-label="Switch language">EN</button>
|
|
106355
|
+
</header>
|
|
106356
|
+
<div class="layout"><aside id="tree" aria-label="文章目录"></aside><main><article id="article"></article></main></div>
|
|
106357
|
+
<footer id="footer" hidden>
|
|
106358
|
+
<textarea id="revision-note" rows="1" placeholder="输入本篇修订意见……" aria-label="本篇修订意见"></textarea>
|
|
106359
|
+
<button class="btn" id="clear-note" disabled>清空本条修订</button>
|
|
106360
|
+
</footer>
|
|
106361
|
+
<dialog id="copy-dialog" aria-labelledby="copy-title">
|
|
106362
|
+
<div class="copy-heading"><h2 id="copy-title"></h2><span id="copy-countdown" hidden></span></div>
|
|
106363
|
+
<p id="copy-summary"></p>
|
|
106364
|
+
<div id="copy-preview" hidden><div id="copy-preview-text"></div><span class="copy-ellipsis" aria-hidden="true">...</span></div>
|
|
106365
|
+
<textarea id="copied-notes" readonly aria-label="修订意见文本"></textarea>
|
|
106366
|
+
<div class="dialog-actions"><button class="btn" id="copy-close">关闭</button></div>
|
|
106367
|
+
</dialog>
|
|
106368
|
+
<div id="toast" role="status" aria-live="polite" hidden></div>
|
|
106369
|
+
${diagramScript ? `<script>${diagramScript}</script>` : ""}<script>const DATA=${reviewHtmlJson(model)};${REVIEW_SITE_CLIENT}</script></body></html>`;
|
|
105998
106370
|
}
|
|
105999
106371
|
function resolveOutputPath(projectRoot, outPath, reviewScope) {
|
|
106000
106372
|
if (outPath === undefined)
|
|
106001
|
-
return
|
|
106373
|
+
return join91(projectRoot, REVIEW_HTML_ROOT, `${reviewScope}.html`);
|
|
106002
106374
|
return isAbsolute15(outPath) ? outPath : resolve28(projectRoot, outPath);
|
|
106003
106375
|
}
|
|
106004
106376
|
async function writeReviewHtml(input) {
|
|
@@ -106014,14 +106386,15 @@ async function writeReviewHtml(input) {
|
|
|
106014
106386
|
if (model.pages.some((page) => page.html.includes('class="language-mermaid"'))) {
|
|
106015
106387
|
const here = dirname38(fileURLToPath6(import.meta.url));
|
|
106016
106388
|
try {
|
|
106017
|
-
diagramScript = await
|
|
106389
|
+
diagramScript = await readFile72(join91(here, "browser/diagrams.js"), "utf8");
|
|
106018
106390
|
} catch (error) {
|
|
106019
106391
|
if (error.code !== "ENOENT")
|
|
106020
106392
|
throw error;
|
|
106021
|
-
diagramScript = await
|
|
106393
|
+
diagramScript = await readFile72(resolve28(here, "../../dist/browser/diagrams.js"), "utf8");
|
|
106022
106394
|
}
|
|
106023
106395
|
}
|
|
106024
|
-
await writeFile25(outPath, renderReviewHtml(
|
|
106396
|
+
await writeFile25(outPath, renderReviewHtml(model, diagramScript), "utf8");
|
|
106397
|
+
await saveReviewReportScope(input.projectRoot, reviewScope, candidates, model.baselineHash);
|
|
106025
106398
|
const unplaced = model.pages.filter((page) => page.candidate_id && !model.nodes.some((node3) => node3.page === page.id && node3.parent !== "review-unplaced" && !node3.removed)).map((page) => ({ article_id: page.id, candidate_id: page.candidate_id, path: page.path, title: page.title }));
|
|
106026
106399
|
return {
|
|
106027
106400
|
navigation: { ready: unplaced.length === 0, unplaced_count: unplaced.length, unplaced },
|
|
@@ -106041,8 +106414,8 @@ init_productionRequirements();
|
|
|
106041
106414
|
init_dist();
|
|
106042
106415
|
init_atomicWrite();
|
|
106043
106416
|
init_reviewShared();
|
|
106044
|
-
import { mkdir as mkdir32, readFile as
|
|
106045
|
-
import { join as
|
|
106417
|
+
import { mkdir as mkdir32, readFile as readFile73 } from "node:fs/promises";
|
|
106418
|
+
import { join as join92 } from "node:path";
|
|
106046
106419
|
var REVIEW_BATCH_MAX_CANDIDATES = 6;
|
|
106047
106420
|
var REVIEW_BATCH_MAX_BYTES = 512 * 1024;
|
|
106048
106421
|
async function readerPurposes(projectRoot, sources) {
|
|
@@ -106122,12 +106495,12 @@ async function materializeCurrentReviewBatchSet(input) {
|
|
|
106122
106495
|
const batches = buildCurrentReviewBatchDocuments(input.candidates);
|
|
106123
106496
|
const setDigest = digestText(batches.map((batch) => `${batch.task_key}:${batch.digest}`).join(`
|
|
106124
106497
|
`));
|
|
106125
|
-
const root2 =
|
|
106498
|
+
const root2 = join92(input.projectRoot, ".tmp", "context-runtime", "review", `current-${setDigest.slice("sha256:".length)}`);
|
|
106126
106499
|
await mkdir32(root2, { recursive: true });
|
|
106127
106500
|
const entries2 = [];
|
|
106128
106501
|
for (const batch of batches) {
|
|
106129
|
-
const path4 =
|
|
106130
|
-
const existing = await
|
|
106502
|
+
const path4 = join92(input.projectRoot, ".tmp", "context-runtime", "review", `${batch.task_key}-${batch.digest.slice("sha256:".length)}.md`);
|
|
106503
|
+
const existing = await readFile73(path4, "utf8").catch((error) => {
|
|
106131
106504
|
if (error.code === "ENOENT")
|
|
106132
106505
|
return;
|
|
106133
106506
|
throw error;
|
|
@@ -106155,7 +106528,7 @@ async function materializeCurrentReviewBatchSet(input) {
|
|
|
106155
106528
|
"Apply only decisions for pages actually reviewed. Keep undecided or repair pages out of decisions; do not set default unless every page has that decision. Omit remains a durable exclusion, never a request to repair.",
|
|
106156
106529
|
"Save the following current scope in a temporary JSON input file and add decisions as {candidate_id, status: approved|rejected}; use that file in the Route command. Applied pages survive later repairs of other pages.",
|
|
106157
106530
|
"```json",
|
|
106158
|
-
JSON.stringify({ schema: REVIEW_PAYLOAD_SCHEMA, scope: {
|
|
106531
|
+
JSON.stringify({ schema: REVIEW_PAYLOAD_SCHEMA, baseline_hash: await reviewSiteBaselineHash(input.projectRoot, input.candidates.map((c) => c.record.approved_revision?.previous_path ?? c.record.path)), scope: {
|
|
106159
106532
|
kind: "all",
|
|
106160
106533
|
count: input.candidates.length,
|
|
106161
106534
|
ids_sha256: candidateIdsHash(input.candidates.map((item) => item.record.candidate_id).sort()),
|
|
@@ -106180,7 +106553,7 @@ async function materializeCurrentReviewBatchSet(input) {
|
|
|
106180
106553
|
].join(`
|
|
106181
106554
|
`);
|
|
106182
106555
|
const digest6 = digestText(content3);
|
|
106183
|
-
const path3 =
|
|
106556
|
+
const path3 = join92(root2, "index.md");
|
|
106184
106557
|
await atomicWriteFile(path3, `${content3}
|
|
106185
106558
|
`);
|
|
106186
106559
|
return {
|
|
@@ -106213,11 +106586,11 @@ function shellQuote6(value) {
|
|
|
106213
106586
|
}
|
|
106214
106587
|
function receiptSetPath(receipts) {
|
|
106215
106588
|
const token = digestText(JSON.stringify(receipts)).slice("sha256:".length);
|
|
106216
|
-
return
|
|
106589
|
+
return join93(".tmp", "context-runtime", "workflow", "read-receipts", `${token}.json`);
|
|
106217
106590
|
}
|
|
106218
106591
|
async function writeReceiptContinuation(input) {
|
|
106219
106592
|
const path3 = receiptSetPath(input.receipts);
|
|
106220
|
-
const absolutePath =
|
|
106593
|
+
const absolutePath = join93(input.projectRoot, path3);
|
|
106221
106594
|
await writeJsonAtomic(absolutePath, input.receipts);
|
|
106222
106595
|
const contextCommand = input.managed ? [
|
|
106223
106596
|
"context",
|
|
@@ -106312,7 +106685,7 @@ async function materializeContextWorkflowResource(input) {
|
|
|
106312
106685
|
const resourceId = workflowResourceId(input.resourceId);
|
|
106313
106686
|
const content3 = renderContextWorkflowResource(resourceId, status);
|
|
106314
106687
|
const location = await materializeResource(await loadContextWorkflowProvider(), resourceId, {
|
|
106315
|
-
cache:
|
|
106688
|
+
cache: join93(found.projectRoot, ".tmp", "context-runtime", "workflow", "resources"),
|
|
106316
106689
|
workspace: found.projectRoot,
|
|
106317
106690
|
revision: input.revision,
|
|
106318
106691
|
input: {
|
|
@@ -106344,7 +106717,7 @@ async function materializeContextWorkflowResource(input) {
|
|
|
106344
106717
|
receipts: afterReadReceipts
|
|
106345
106718
|
});
|
|
106346
106719
|
const directResources = (status.workflow.current?.resources.required ?? []).filter((resource) => resource.read_state === "read-required" && resource.path !== undefined && resource.digest !== undefined);
|
|
106347
|
-
const afterReadCommand = directResources.length === 0 ? continuation.command : `context resource acknowledge-current --revision ${shellQuote6(input.revision)}${authorityCommandOptions(authorities, "resource")} --resource-receipts ${shellQuote6(`@${
|
|
106720
|
+
const afterReadCommand = directResources.length === 0 ? continuation.command : `context resource acknowledge-current --revision ${shellQuote6(input.revision)}${authorityCommandOptions(authorities, "resource")} --resource-receipts ${shellQuote6(`@${join93(found.projectRoot, continuation.path)}`)} --format json`;
|
|
106348
106721
|
return {
|
|
106349
106722
|
protocol: "context.workflow.resource.v1",
|
|
106350
106723
|
id: resourceId,
|
|
@@ -106405,14 +106778,15 @@ async function acknowledgeCurrentWorkflowResources(input) {
|
|
|
106405
106778
|
const reevaluated = await reevaluateProjectStatusWorkflow({
|
|
106406
106779
|
snapshot,
|
|
106407
106780
|
resourceReceipts: normalizedReceipts,
|
|
106408
|
-
resourceReceiptsReference: `@${
|
|
106781
|
+
resourceReceiptsReference: `@${join93(found.projectRoot, continuation.path)}`
|
|
106409
106782
|
});
|
|
106410
106783
|
return {
|
|
106411
106784
|
...reevaluated,
|
|
106412
106785
|
resourceAcknowledgement: {
|
|
106413
106786
|
protocol: "context.workflow.resource-receipts.v1",
|
|
106414
106787
|
acknowledged: directResources.length,
|
|
106415
|
-
receiptReference: `@${
|
|
106788
|
+
receiptReference: `@${join93(found.projectRoot, continuation.path)}`,
|
|
106789
|
+
message: "Read the returned workflow and follow its selected command unchanged, including revision and resource-receipt arguments. Do not pre-chain an earlier write command or replace this result with status without receipts. Acknowledgement does not necessarily change the revision; reread only changed or unavailable required content."
|
|
106416
106790
|
}
|
|
106417
106791
|
};
|
|
106418
106792
|
}
|
|
@@ -106458,9 +106832,9 @@ init_cliFeedback();
|
|
|
106458
106832
|
init_errors3();
|
|
106459
106833
|
init_exitCode();
|
|
106460
106834
|
init_workspace();
|
|
106461
|
-
import { readFile as
|
|
106462
|
-
import { isAbsolute as isAbsolute16, join as
|
|
106463
|
-
var RECEIPT_DIRECTORY =
|
|
106835
|
+
import { readFile as readFile74 } from "node:fs/promises";
|
|
106836
|
+
import { isAbsolute as isAbsolute16, join as join94, sep as sep6, resolve as resolve29 } from "node:path";
|
|
106837
|
+
var RECEIPT_DIRECTORY = join94(".tmp", "context-runtime", "workflow", "read-receipts");
|
|
106464
106838
|
function workflowResourceReceiptCwd(value, cwd) {
|
|
106465
106839
|
if (value === undefined || !value.startsWith("@"))
|
|
106466
106840
|
return cwd;
|
|
@@ -106478,7 +106852,7 @@ async function receiptDocument(value, cwd) {
|
|
|
106478
106852
|
let source2 = value;
|
|
106479
106853
|
if (value.startsWith("@")) {
|
|
106480
106854
|
try {
|
|
106481
|
-
source2 = await
|
|
106855
|
+
source2 = await readFile74(resolve29(cwd, value.slice(1)), "utf8");
|
|
106482
106856
|
} catch (error) {
|
|
106483
106857
|
const ioCode = error !== null && typeof error === "object" && "code" in error && typeof error.code === "string" ? error.code : undefined;
|
|
106484
106858
|
throw new ContextError(ExitCode.UserError, "resource read receipt file is unavailable", {
|
|
@@ -106737,14 +107111,14 @@ function compactJsonResult(result, verbose) {
|
|
|
106737
107111
|
// src/project/runLog.ts
|
|
106738
107112
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
106739
107113
|
import { mkdir as mkdir34, writeFile as writeFile27 } from "node:fs/promises";
|
|
106740
|
-
import { dirname as dirname40, join as
|
|
107114
|
+
import { dirname as dirname40, join as join100 } from "node:path";
|
|
106741
107115
|
var createPhaseRunId = () => {
|
|
106742
107116
|
const timestamp = new Date().toISOString().replace(/[-:.TZ]/gu, "");
|
|
106743
107117
|
return `run_${timestamp}_${randomUUID5().slice(0, 8)}`;
|
|
106744
107118
|
};
|
|
106745
107119
|
async function writePhaseRunLog(input) {
|
|
106746
|
-
const relPath =
|
|
106747
|
-
const absPath =
|
|
107120
|
+
const relPath = join100(".tmp", "context-runtime", "runs", `${input.runId}.json`);
|
|
107121
|
+
const absPath = join100(input.projectRoot, relPath);
|
|
106748
107122
|
await mkdir34(dirname40(absPath), { recursive: true });
|
|
106749
107123
|
await writeFile27(absPath, `${JSON.stringify({
|
|
106750
107124
|
run_id: input.runId,
|
|
@@ -107687,6 +108061,9 @@ execution scope cleanup failed`, true)
|
|
|
107687
108061
|
// src/project/workflow/workflowInProcessActions.ts
|
|
107688
108062
|
init_workflowFacts();
|
|
107689
108063
|
|
|
108064
|
+
// src/project/reviewLegacy.ts
|
|
108065
|
+
init_candidateLedger();
|
|
108066
|
+
|
|
107690
108067
|
// src/project/reviewCode.ts
|
|
107691
108068
|
function createReviewCodeCodec() {
|
|
107692
108069
|
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
@@ -107826,11 +108203,140 @@ function createReviewCodeCodec() {
|
|
|
107826
108203
|
return { encode, decode: decode2 };
|
|
107827
108204
|
}
|
|
107828
108205
|
|
|
108206
|
+
// src/project/reviewFeedbackCode.ts
|
|
108207
|
+
function createReviewFeedbackCodec() {
|
|
108208
|
+
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
108209
|
+
function checksum(text9) {
|
|
108210
|
+
let crc = 4294967295;
|
|
108211
|
+
for (let i2 = 0;i2 < text9.length; i2++) {
|
|
108212
|
+
crc ^= text9.charCodeAt(i2);
|
|
108213
|
+
for (let j = 0;j < 8; j++)
|
|
108214
|
+
crc = crc >>> 1 ^ (crc & 1 ? 3988292384 : 0);
|
|
108215
|
+
}
|
|
108216
|
+
return ((crc ^ 4294967295) >>> 0).toString(16).padStart(8, "0");
|
|
108217
|
+
}
|
|
108218
|
+
function validate2(value) {
|
|
108219
|
+
if (!value || !/^[a-z][a-z0-9-]*$/.test(value.scope) || ![value.idsHash, value.contentHash, value.baselineHash].every((h) => typeof h === "string" && /^[a-f0-9]{64}$/.test(h)) || !Array.isArray(value.statuses) || !value.statuses.length || value.statuses.length > 1e5 || value.statuses.some((s) => !["approved", "rejected", "pending", "revised"].includes(s)) || value.statuses.every((s) => s === "pending") || !Array.isArray(value.repairs))
|
|
108220
|
+
throw new Error("Invalid review feedback scope or decisions");
|
|
108221
|
+
const seen = new Set;
|
|
108222
|
+
for (const repair of value.repairs) {
|
|
108223
|
+
if (!repair || !Number.isInteger(repair.index) || seen.has(repair.index) || value.statuses[repair.index] !== "revised" || typeof repair.instruction !== "string" || !repair.instruction.trim() || repair.instruction.length > 20000)
|
|
108224
|
+
throw new Error("Invalid or conflicting revision instruction");
|
|
108225
|
+
seen.add(repair.index);
|
|
108226
|
+
}
|
|
108227
|
+
if (value.statuses.filter((s) => s === "revised").length !== seen.size)
|
|
108228
|
+
throw new Error("Missing revision instruction");
|
|
108229
|
+
return value;
|
|
108230
|
+
}
|
|
108231
|
+
function encode(value) {
|
|
108232
|
+
validate2(value);
|
|
108233
|
+
const bytes = new TextEncoder().encode(JSON.stringify({ scope: value.scope, idsHash: value.idsHash, contentHash: value.contentHash, baselineHash: value.baselineHash, statuses: value.statuses }));
|
|
108234
|
+
let bits = 0, carry = 0, data2 = "";
|
|
108235
|
+
for (const byte of bytes) {
|
|
108236
|
+
carry = carry << 8 | byte;
|
|
108237
|
+
bits += 8;
|
|
108238
|
+
while (bits >= 6) {
|
|
108239
|
+
bits -= 6;
|
|
108240
|
+
data2 += alphabet[carry >>> bits & 63];
|
|
108241
|
+
}
|
|
108242
|
+
}
|
|
108243
|
+
if (bits)
|
|
108244
|
+
data2 += alphabet[carry << 6 - bits & 63];
|
|
108245
|
+
const body2 = `CR2.${data2}`;
|
|
108246
|
+
const repairs = value.repairs.map((r) => JSON.stringify([r.index, r.instruction])).join(`
|
|
108247
|
+
`);
|
|
108248
|
+
const trailer = repairs ? `
|
|
108249
|
+
${repairs}` : "";
|
|
108250
|
+
const code3 = `${body2}.${checksum(body2 + trailer)}${trailer}`;
|
|
108251
|
+
if (code3.length > 250000)
|
|
108252
|
+
throw new Error("Review feedback is too large; shorten instructions or review a smaller scope");
|
|
108253
|
+
return code3;
|
|
108254
|
+
}
|
|
108255
|
+
function decode2(raw) {
|
|
108256
|
+
const code3 = raw.trim();
|
|
108257
|
+
if (code3.length > 250000)
|
|
108258
|
+
throw new Error("Review feedback exceeds supported size");
|
|
108259
|
+
const [header, ...lines] = code3.split(`
|
|
108260
|
+
`);
|
|
108261
|
+
const trailer = lines.length ? `
|
|
108262
|
+
${lines.join(`
|
|
108263
|
+
`)}` : "";
|
|
108264
|
+
const match = /^CR2\.([A-Za-z0-9_-]+)\.([a-f0-9]{8})$/.exec(header);
|
|
108265
|
+
if (!match || checksum(`CR2.${match[1]}` + trailer) !== match[2])
|
|
108266
|
+
throw new Error("Review feedback is damaged or incomplete; copy the full code again");
|
|
108267
|
+
let bits = 0, carry = 0;
|
|
108268
|
+
const bytes = [];
|
|
108269
|
+
for (const char of match[1]) {
|
|
108270
|
+
carry = carry << 6 | alphabet.indexOf(char);
|
|
108271
|
+
bits += 6;
|
|
108272
|
+
if (bits >= 8) {
|
|
108273
|
+
bits -= 8;
|
|
108274
|
+
bytes.push(carry >>> bits & 255);
|
|
108275
|
+
}
|
|
108276
|
+
}
|
|
108277
|
+
const headerValue = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(new Uint8Array(bytes)));
|
|
108278
|
+
const value = validate2({ ...headerValue, repairs: lines.map((line) => {
|
|
108279
|
+
const row = JSON.parse(line);
|
|
108280
|
+
if (!Array.isArray(row) || row.length !== 2)
|
|
108281
|
+
throw new Error("Invalid revision instruction line");
|
|
108282
|
+
return { index: row[0], instruction: row[1] };
|
|
108283
|
+
}) });
|
|
108284
|
+
if (encode(value) !== code3)
|
|
108285
|
+
throw new Error("Noncanonical review feedback encoding");
|
|
108286
|
+
return value;
|
|
108287
|
+
}
|
|
108288
|
+
return { encode, decode: decode2 };
|
|
108289
|
+
}
|
|
108290
|
+
|
|
108291
|
+
// src/project/reviewLegacy.ts
|
|
108292
|
+
init_reviewShared();
|
|
108293
|
+
init_errors3();
|
|
108294
|
+
init_cliFeedback();
|
|
108295
|
+
init_exitCode();
|
|
108296
|
+
function warnLegacyReview(entry) {
|
|
108297
|
+
process.stderr.write(`[context] ${entry} is accepted for compatibility. 新版审核页无需审核码;直接回复确认或修订意见。Agent 在用户确认后使用 context review approve-all [collection] --confirmed(跨集合加 --all);修订意见通过当前范围 JSON 提交。
|
|
108298
|
+
`);
|
|
108299
|
+
}
|
|
108300
|
+
async function readLegacyReviewPayload(raw, projectRoot) {
|
|
108301
|
+
warnLegacyReview("Legacy review input");
|
|
108302
|
+
try {
|
|
108303
|
+
const feedback = raw.trim().startsWith("CR2.") ? createReviewFeedbackCodec().decode(raw) : undefined;
|
|
108304
|
+
const decoded = feedback ?? createReviewCodeCodec().decode(raw);
|
|
108305
|
+
const collection = decoded.scope === "all" ? undefined : assertCollection(decoded.scope);
|
|
108306
|
+
const rows = (await readCandidateRecords(projectRoot)).filter((row) => row.status === "draft" && (collection === undefined || row.collection === collection)).sort((a, b) => a.candidate_id < b.candidate_id ? -1 : a.candidate_id > b.candidate_id ? 1 : 0);
|
|
108307
|
+
return {
|
|
108308
|
+
...collection === undefined ? {} : { collection },
|
|
108309
|
+
...feedback === undefined ? {} : { baseline_hash: feedback.baselineHash },
|
|
108310
|
+
scope: {
|
|
108311
|
+
kind: collection === undefined ? "all" : "collection",
|
|
108312
|
+
...collection === undefined ? {} : { collection },
|
|
108313
|
+
count: decoded.statuses.length,
|
|
108314
|
+
ids_sha256: decoded.idsHash,
|
|
108315
|
+
candidates_sha256: decoded.contentHash,
|
|
108316
|
+
visible_candidate_ids: rows.map((row) => row.candidate_id)
|
|
108317
|
+
},
|
|
108318
|
+
decisions: rows.flatMap((row, index2) => {
|
|
108319
|
+
const status = decoded.statuses[index2];
|
|
108320
|
+
return status === "approved" || status === "rejected" ? [{ candidate_id: row.candidate_id, status }] : [];
|
|
108321
|
+
}),
|
|
108322
|
+
repairs: (feedback?.repairs ?? []).map((repair) => ({
|
|
108323
|
+
candidate_id: rows[repair.index]?.candidate_id ?? "legacy-stale-candidate",
|
|
108324
|
+
instruction: repair.instruction
|
|
108325
|
+
}))
|
|
108326
|
+
};
|
|
108327
|
+
} catch (error) {
|
|
108328
|
+
throw new ContextError(ExitCode.UserError, error instanceof Error ? error.message : String(error), {
|
|
108329
|
+
category: ErrorCategory.UserInputInvalid,
|
|
108330
|
+
next: "No new code is needed. Return confirmation or revision notes in the conversation; use the current report scope."
|
|
108331
|
+
});
|
|
108332
|
+
}
|
|
108333
|
+
}
|
|
108334
|
+
|
|
107829
108335
|
// src/project/review.ts
|
|
107830
108336
|
init_cliFeedback();
|
|
107831
108337
|
init_errors3();
|
|
107832
108338
|
init_exitCode();
|
|
107833
|
-
import { readFile as
|
|
108339
|
+
import { readFile as readFile81 } from "node:fs/promises";
|
|
107834
108340
|
import { isAbsolute as isAbsolute19, resolve as resolve31 } from "node:path";
|
|
107835
108341
|
|
|
107836
108342
|
// src/project/reviewApply.ts
|
|
@@ -107847,8 +108353,8 @@ init_durableMultiFileTransaction();
|
|
|
107847
108353
|
init_durableSingleFileTransaction();
|
|
107848
108354
|
init_reviewShared();
|
|
107849
108355
|
import { existsSync as existsSync25 } from "node:fs";
|
|
107850
|
-
import { readFile as
|
|
107851
|
-
import { join as
|
|
108356
|
+
import { readFile as readFile79 } from "node:fs/promises";
|
|
108357
|
+
import { join as join101 } from "node:path";
|
|
107852
108358
|
function existingTimestamp(markdown) {
|
|
107853
108359
|
if (markdown === undefined)
|
|
107854
108360
|
return;
|
|
@@ -107875,7 +108381,7 @@ async function prepareApprovedPage(input) {
|
|
|
107875
108381
|
next: "Refresh the current production or article revision, then reopen Review before approval."
|
|
107876
108382
|
});
|
|
107877
108383
|
}
|
|
107878
|
-
const relPath =
|
|
108384
|
+
const relPath = join101("knowledge", input.record.path);
|
|
107879
108385
|
const existingView = findApprovedPageForArticleId(input.record.indexer_candidate.artifact_ref, input.approvedPageIndex);
|
|
107880
108386
|
const previousPath = input.record.approved_revision?.previous_path;
|
|
107881
108387
|
if (previousPath !== undefined && (!isSafeKnowledgeTargetPath(previousPath.split("/")[0], previousPath) || previousPath.includes("\\")))
|
|
@@ -107890,13 +108396,13 @@ async function prepareApprovedPage(input) {
|
|
|
107890
108396
|
next: "Resolve the approved page path migration explicitly before approving this candidate."
|
|
107891
108397
|
});
|
|
107892
108398
|
}
|
|
107893
|
-
const absPath =
|
|
107894
|
-
const existing = existsSync25(absPath) ? await
|
|
108399
|
+
const absPath = join101(input.projectRoot, relPath);
|
|
108400
|
+
const existing = existsSync25(absPath) ? await readFile79(absPath, "utf8") : undefined;
|
|
107895
108401
|
let previous3;
|
|
107896
108402
|
if (previousPath !== undefined) {
|
|
107897
108403
|
if (existing !== undefined || existingView?.relPath !== `knowledge/${previousPath}`)
|
|
107898
108404
|
throw new TypeError("Page move destination or original identity changed; refresh its revision.");
|
|
107899
|
-
previous3 = { path: `knowledge/${previousPath}`, content: await
|
|
108405
|
+
previous3 = { path: `knowledge/${previousPath}`, content: await readFile79(join101(input.projectRoot, "knowledge", previousPath), "utf8") };
|
|
107900
108406
|
}
|
|
107901
108407
|
if (input.record.approved_revision !== undefined) {
|
|
107902
108408
|
const base = previous3?.content ?? existing;
|
|
@@ -107943,7 +108449,7 @@ async function prepareApprovedPage(input) {
|
|
|
107943
108449
|
}
|
|
107944
108450
|
async function readProjectFileMaybe(projectRoot, relPath) {
|
|
107945
108451
|
try {
|
|
107946
|
-
return await
|
|
108452
|
+
return await readFile79(join101(projectRoot, relPath), "utf8");
|
|
107947
108453
|
} catch (error) {
|
|
107948
108454
|
if (error !== null && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
107949
108455
|
return;
|
|
@@ -107972,31 +108478,19 @@ function reviewFileTarget(input) {
|
|
|
107972
108478
|
content: input.targetContent
|
|
107973
108479
|
};
|
|
107974
108480
|
}
|
|
107975
|
-
function expandEncodedReviewDecisions(payload, scopedIds) {
|
|
107976
|
-
if (payload.scope?.candidates_sha256 === undefined || payload.encoded_statuses.length !== scopedIds.length || payload.decisions.length !== 0 || payload.default !== undefined || payload.encoded_statuses.some((status) => status !== "approved" && status !== "rejected" && status !== "pending")) {
|
|
107977
|
-
throw new ContextError(ExitCode.UserError, "Invalid scoped review code decisions", {
|
|
107978
|
-
category: ErrorCategory.UserInputInvalid,
|
|
107979
|
-
next: "Copy a fresh complete review code from the current HTML report."
|
|
107980
|
-
});
|
|
107981
|
-
}
|
|
107982
|
-
return scopedIds.flatMap((candidate_id, index2) => {
|
|
107983
|
-
const status = payload.encoded_statuses[index2];
|
|
107984
|
-
return status === "pending" ? [] : [{ candidate_id, status }];
|
|
107985
|
-
});
|
|
107986
|
-
}
|
|
107987
108481
|
function expandReviewPayload(payload, rows) {
|
|
107988
108482
|
const scopedRows = payload.scope?.kind === "all" ? rows.filter((row) => row.status === "draft") : payload.collection !== undefined ? rows.filter((row) => row.collection === payload.collection && row.status === "draft") : [];
|
|
107989
108483
|
const scopedIds = scopedRows.map((row) => row.candidate_id).sort();
|
|
107990
108484
|
if (payload.scope === undefined) {
|
|
107991
108485
|
throw new ContextError(ExitCode.UserError, "review payload requires an explicit scope from the current review gate", {
|
|
107992
108486
|
category: ErrorCategory.UserInputInvalid,
|
|
107993
|
-
next: payload.collection === undefined ? "Rerun context review html --all --format json and
|
|
108487
|
+
next: payload.collection === undefined ? "Rerun context review html --all --format json and use its current scope template." : `Rerun context review html ${payload.collection} --format json and use its current scope template.`
|
|
107994
108488
|
});
|
|
107995
108489
|
}
|
|
107996
108490
|
const actualHash = candidateIdsHash(scopedIds);
|
|
107997
108491
|
const actualCandidatesHash = candidateSetHash(scopedRows);
|
|
107998
108492
|
const visibleIds = payload.scope.visible_candidate_ids;
|
|
107999
|
-
if (payload.scope.kind === "all" && visibleIds === undefined
|
|
108493
|
+
if (payload.scope.kind === "all" && visibleIds === undefined) {
|
|
108000
108494
|
throw new ContextError(ExitCode.UserError, "all-scope review payload requires scope.visible_candidate_ids", {
|
|
108001
108495
|
category: ErrorCategory.UserInputInvalid,
|
|
108002
108496
|
expected: {
|
|
@@ -108004,7 +108498,7 @@ function expandReviewPayload(payload, rows) {
|
|
|
108004
108498
|
ids_sha256: actualHash,
|
|
108005
108499
|
visible_candidate_ids: scopedIds
|
|
108006
108500
|
},
|
|
108007
|
-
next: "Rerun context review html --all --format json and
|
|
108501
|
+
next: "Rerun context review html --all --format json and use its current scope template."
|
|
108008
108502
|
});
|
|
108009
108503
|
}
|
|
108010
108504
|
const visibleMismatch = visibleIds !== undefined && (visibleIds.length !== scopedIds.length || visibleIds.some((id3, index2) => id3 !== scopedIds[index2]));
|
|
@@ -108020,11 +108514,9 @@ function expandReviewPayload(payload, rows) {
|
|
|
108020
108514
|
candidates_sha256: actualCandidatesHash,
|
|
108021
108515
|
visible_candidate_ids: scopedIds
|
|
108022
108516
|
},
|
|
108023
|
-
next: `Rerun context review html ${scopeLabel} and
|
|
108517
|
+
next: `Rerun context review html ${scopeLabel} and use its current scope template.`
|
|
108024
108518
|
});
|
|
108025
108519
|
}
|
|
108026
|
-
if (payload.encoded_statuses !== undefined)
|
|
108027
|
-
return expandEncodedReviewDecisions(payload, scopedIds);
|
|
108028
108520
|
if (payload.default === undefined) {
|
|
108029
108521
|
const scopedSet = new Set(scopedIds);
|
|
108030
108522
|
for (const decision of payload.decisions) {
|
|
@@ -108074,6 +108566,14 @@ async function applyReviewDecisions(input) {
|
|
|
108074
108566
|
const nextRows = [...rows];
|
|
108075
108567
|
const decisions = expandReviewPayload(input.payload, rows);
|
|
108076
108568
|
const scoped = rows.filter((r) => r.status === "draft" && (input.payload.scope?.kind === "all" || r.collection === input.payload.collection)).sort((a, b) => a.candidate_id < b.candidate_id ? -1 : 1);
|
|
108569
|
+
const pendingFeedback = await readPendingReviewFeedback(input.projectRoot, scoped.map((record4) => ({ record: record4, snapshot: undefined })));
|
|
108570
|
+
const unresolved = pendingFeedback.filter((repair) => decisions.some((decision) => decision.candidate_id === repair.candidate_id && decision.status === "approved"));
|
|
108571
|
+
if (unresolved.length) {
|
|
108572
|
+
throw new ContextError(ExitCode.UserError, "Pending revision instructions must be resolved before approval", {
|
|
108573
|
+
category: ErrorCategory.UserInputInvalid,
|
|
108574
|
+
repairs: unresolved
|
|
108575
|
+
});
|
|
108576
|
+
}
|
|
108077
108577
|
if (input.payload.baseline_hash && input.payload.baseline_hash !== await reviewSiteBaselineHash(input.projectRoot, scoped.map((r) => r.approved_revision?.previous_path ?? r.path))) {
|
|
108078
108578
|
throw new ContextError(ExitCode.WorkspaceStateError, "Review navigation or approved baseline changed; generate a fresh report", {
|
|
108079
108579
|
category: ErrorCategory.WorkspaceStateInvalid,
|
|
@@ -108082,11 +108582,15 @@ async function applyReviewDecisions(input) {
|
|
|
108082
108582
|
});
|
|
108083
108583
|
}
|
|
108084
108584
|
const repairIds = new Set;
|
|
108085
|
-
const repairs = (input.payload.
|
|
108086
|
-
const row = scoped
|
|
108087
|
-
if (!
|
|
108088
|
-
throw new
|
|
108089
|
-
|
|
108585
|
+
const repairs = (input.payload.repairs ?? []).map((repair) => {
|
|
108586
|
+
const row = scoped.find((candidate) => candidate.candidate_id === repair.candidate_id);
|
|
108587
|
+
if (!row || repairIds.has(repair.candidate_id) || !repair.instruction.trim() || decisions.some((decision) => decision.candidate_id === repair.candidate_id)) {
|
|
108588
|
+
throw new ContextError(ExitCode.UserError, "Revision requests must be unique, scoped and separate from decisions", {
|
|
108589
|
+
category: ErrorCategory.UserInputInvalid,
|
|
108590
|
+
candidate_id: repair.candidate_id
|
|
108591
|
+
});
|
|
108592
|
+
}
|
|
108593
|
+
repairIds.add(repair.candidate_id);
|
|
108090
108594
|
const quote = (value) => "'" + value.replace(/'/gu, "'\\''") + "'";
|
|
108091
108595
|
return {
|
|
108092
108596
|
candidate_id: row.candidate_id,
|
|
@@ -108166,7 +108670,7 @@ async function applyReviewDecisions(input) {
|
|
|
108166
108670
|
});
|
|
108167
108671
|
}
|
|
108168
108672
|
seenApprovedIds.set(approvedRef, row.candidate_id);
|
|
108169
|
-
const approvedPath =
|
|
108673
|
+
const approvedPath = join101("knowledge", row.path);
|
|
108170
108674
|
const previousPathCandidate = seenApprovedPaths.get(knowledgeTargetPathKey(approvedPath));
|
|
108171
108675
|
if (previousPathCandidate !== undefined) {
|
|
108172
108676
|
throw new ContextError(ExitCode.UserError, `multiple approved review decisions target the same knowledge path: ${approvedPath}`, {
|
|
@@ -108216,7 +108720,7 @@ async function applyReviewDecisions(input) {
|
|
|
108216
108720
|
for (const path3 of approvedPageIndex.byRelPath.keys()) {
|
|
108217
108721
|
if (pagesToWrite.some((page) => page.relPath === path3 || page.previous?.path === path3))
|
|
108218
108722
|
continue;
|
|
108219
|
-
const before = await
|
|
108723
|
+
const before = await readFile79(join101(input.projectRoot, path3), "utf8");
|
|
108220
108724
|
const local = path3.replace(/^knowledge\//u, "");
|
|
108221
108725
|
const after = moveKnowledgeLinkTargets2(before, local, local, moved);
|
|
108222
108726
|
navigationTargets.push(reviewFileTarget({ path: path3, baseContent: before, targetContent: after }));
|
|
@@ -108275,11 +108779,11 @@ async function applyReviewDecisions(input) {
|
|
|
108275
108779
|
init_reviewShared();
|
|
108276
108780
|
init_writeLock();
|
|
108277
108781
|
init_verifyFrontmatter();
|
|
108278
|
-
import { readFile as
|
|
108782
|
+
import { readFile as readFile80, writeFile as writeFile28 } from "node:fs/promises";
|
|
108279
108783
|
function deprecateApprovedPage(input) {
|
|
108280
108784
|
return withProjectWriteLock(input.projectRoot, "deprecate-article", async () => {
|
|
108281
108785
|
const page = await approvedPageForArticleId(input.projectRoot, input.viewRef);
|
|
108282
|
-
const original = await
|
|
108786
|
+
const original = await readFile80(page.path, "utf8");
|
|
108283
108787
|
const content3 = parseFrontmatterLoose(original).deprecated === true ? original : updateFrontmatter(original, (metadata) => ({ ...metadata, deprecated: true, timestamp: new Date().toISOString() }));
|
|
108284
108788
|
const changed = content3 !== original;
|
|
108285
108789
|
if (changed)
|
|
@@ -108300,12 +108804,12 @@ init_candidateLedger();
|
|
|
108300
108804
|
|
|
108301
108805
|
// src/project/localHtmlReport.ts
|
|
108302
108806
|
import { execFile as execFile11 } from "node:child_process";
|
|
108303
|
-
import { isAbsolute as isAbsolute18, join as
|
|
108807
|
+
import { isAbsolute as isAbsolute18, join as join102 } from "node:path";
|
|
108304
108808
|
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
108305
108809
|
import { promisify as promisify11 } from "node:util";
|
|
108306
108810
|
var execFileAsync5 = promisify11(execFile11);
|
|
108307
108811
|
function htmlReportReference(input) {
|
|
108308
|
-
const absolutePath = isAbsolute18(input.path) ? input.path :
|
|
108812
|
+
const absolutePath = isAbsolute18(input.path) ? input.path : join102(input.projectRoot, input.path);
|
|
108309
108813
|
return {
|
|
108310
108814
|
format: "html",
|
|
108311
108815
|
path: input.path,
|
|
@@ -108409,7 +108913,7 @@ function parsePayloadValues(parsed) {
|
|
|
108409
108913
|
if (first.schema !== REVIEW_PAYLOAD_SCHEMA) {
|
|
108410
108914
|
throw new ContextError(ExitCode.UserError, `review payload header schema must be ${REVIEW_PAYLOAD_SCHEMA}`, {
|
|
108411
108915
|
category: ErrorCategory.UserInputInvalid,
|
|
108412
|
-
next: "
|
|
108916
|
+
next: "Use the current scope template from the review resource."
|
|
108413
108917
|
});
|
|
108414
108918
|
}
|
|
108415
108919
|
const scope2 = parsePayloadScope(first.scope);
|
|
@@ -108437,7 +108941,18 @@ function parsePayloadValues(parsed) {
|
|
|
108437
108941
|
}
|
|
108438
108942
|
const decisions = first.decisions ?? parsed.slice(1);
|
|
108439
108943
|
const validatedDecisions = decisions.map((item, index2) => parsePayloadLineDecision(item, index2 + 2));
|
|
108944
|
+
const repairs = first.repairs;
|
|
108945
|
+
if (repairs !== undefined && (!Array.isArray(repairs) || repairs.some((item) => !isRecord14(item) || typeof item.candidate_id !== "string" || !item.candidate_id.trim() || typeof item.instruction !== "string" || !item.instruction.trim()))) {
|
|
108946
|
+
throw new ContextError(ExitCode.UserError, "review repairs require candidate_id and nonempty instruction", {
|
|
108947
|
+
category: ErrorCategory.UserInputInvalid
|
|
108948
|
+
});
|
|
108949
|
+
}
|
|
108950
|
+
if (first.baseline_hash !== undefined && (typeof first.baseline_hash !== "string" || !/^[a-f0-9]{64}$/iu.test(first.baseline_hash))) {
|
|
108951
|
+
throw new ContextError(ExitCode.UserError, "review baseline_hash must be a SHA-256 digest", { category: ErrorCategory.UserInputInvalid });
|
|
108952
|
+
}
|
|
108440
108953
|
return {
|
|
108954
|
+
...Array.isArray(repairs) ? { repairs } : {},
|
|
108955
|
+
...typeof first.baseline_hash === "string" ? { baseline_hash: first.baseline_hash.toLowerCase() } : {},
|
|
108441
108956
|
decisions: validatedDecisions,
|
|
108442
108957
|
...typeof first.note === "string" ? { note: first.note } : {},
|
|
108443
108958
|
...collection !== undefined ? { collection } : {},
|
|
@@ -108474,59 +108989,21 @@ function parseReviewPayloadText(raw) {
|
|
|
108474
108989
|
throw error;
|
|
108475
108990
|
}
|
|
108476
108991
|
}
|
|
108477
|
-
async function readReviewPayloadFile(filePath2) {
|
|
108992
|
+
async function readReviewPayloadFile(filePath2, projectRoot) {
|
|
108478
108993
|
let raw;
|
|
108479
108994
|
try {
|
|
108480
|
-
raw = await
|
|
108995
|
+
raw = await readFile81(filePath2, "utf8");
|
|
108481
108996
|
} catch (error) {
|
|
108482
108997
|
const message = error instanceof Error ? error.message : String(error);
|
|
108483
108998
|
throw new ContextError(ExitCode.UserError, `review payload file cannot be read: ${filePath2}`, {
|
|
108484
108999
|
category: ErrorCategory.UserInputInvalid,
|
|
108485
109000
|
path: filePath2,
|
|
108486
109001
|
reason: message,
|
|
108487
|
-
next: "Pass the JSON or JSONL
|
|
109002
|
+
next: "Pass the scoped JSON or JSONL decisions prepared from the current review resource."
|
|
108488
109003
|
});
|
|
108489
109004
|
}
|
|
108490
|
-
if (raw.trim()
|
|
108491
|
-
|
|
108492
|
-
if (raw.trim().startsWith("CR2.")) {
|
|
108493
|
-
const feedback = createReviewFeedbackCodec().decode(raw);
|
|
108494
|
-
const collection2 = feedback.scope === "all" ? undefined : assertCollection(feedback.scope);
|
|
108495
|
-
return {
|
|
108496
|
-
decisions: [],
|
|
108497
|
-
encoded_statuses: feedback.statuses.map((s) => s === "revised" ? "pending" : s),
|
|
108498
|
-
feedback_repairs: feedback.repairs,
|
|
108499
|
-
baseline_hash: feedback.baselineHash,
|
|
108500
|
-
...collection2 === undefined ? {} : { collection: collection2 },
|
|
108501
|
-
scope: {
|
|
108502
|
-
kind: collection2 === undefined ? "all" : "collection",
|
|
108503
|
-
...collection2 === undefined ? {} : { collection: collection2 },
|
|
108504
|
-
count: feedback.statuses.length,
|
|
108505
|
-
ids_sha256: feedback.idsHash,
|
|
108506
|
-
candidates_sha256: feedback.contentHash
|
|
108507
|
-
}
|
|
108508
|
-
};
|
|
108509
|
-
}
|
|
108510
|
-
const decoded = createReviewCodeCodec().decode(raw);
|
|
108511
|
-
const collection = decoded.scope === "all" ? undefined : assertCollection(decoded.scope);
|
|
108512
|
-
return {
|
|
108513
|
-
decisions: [],
|
|
108514
|
-
encoded_statuses: decoded.statuses,
|
|
108515
|
-
...collection === undefined ? {} : { collection },
|
|
108516
|
-
scope: {
|
|
108517
|
-
kind: collection === undefined ? "all" : "collection",
|
|
108518
|
-
...collection === undefined ? {} : { collection },
|
|
108519
|
-
count: decoded.count,
|
|
108520
|
-
ids_sha256: decoded.idsHash,
|
|
108521
|
-
candidates_sha256: decoded.contentHash
|
|
108522
|
-
}
|
|
108523
|
-
};
|
|
108524
|
-
} catch (error) {
|
|
108525
|
-
throw new ContextError(ExitCode.UserError, error instanceof Error ? error.message : String(error), {
|
|
108526
|
-
category: ErrorCategory.UserInputInvalid,
|
|
108527
|
-
next: "Copy the complete review code and all following revision instruction lines unchanged from the current report into one input file. Older segmented codes require every segment."
|
|
108528
|
-
});
|
|
108529
|
-
}
|
|
109005
|
+
if (/^CR(?:[12]|P1)\./u.test(raw.trim())) {
|
|
109006
|
+
return readLegacyReviewPayload(raw, projectRoot ?? projectRootFromCwd(process.cwd()));
|
|
108530
109007
|
}
|
|
108531
109008
|
return parseReviewPayloadText(raw);
|
|
108532
109009
|
}
|
|
@@ -108664,7 +109141,7 @@ async function runReviewHtmlCommand(input) {
|
|
|
108664
109141
|
...openResult.error !== undefined ? [`open_error: ${openResult.error}`] : []
|
|
108665
109142
|
] : []
|
|
108666
109143
|
],
|
|
108667
|
-
next: result.next_action ? `${result.next_action.message} ${result.next_action.command}` : "
|
|
109144
|
+
next: result.next_action ? `${result.next_action.message} ${result.next_action.command}` : "Return to the conversation to confirm or provide revision notes."
|
|
108668
109145
|
}));
|
|
108669
109146
|
}
|
|
108670
109147
|
async function runReviewListCommand(input) {
|
|
@@ -108717,41 +109194,45 @@ async function runReviewListCommand(input) {
|
|
|
108717
109194
|
async function runReviewApplyCommand(input) {
|
|
108718
109195
|
const projectRoot = projectRootFromCwd(input.cwd);
|
|
108719
109196
|
const payloadPath = isAbsolute19(input.payloadInput) ? input.payloadInput : resolve31(input.cwd, input.payloadInput);
|
|
108720
|
-
const payload = await readReviewPayloadFile(payloadPath);
|
|
109197
|
+
const payload = await readReviewPayloadFile(payloadPath, projectRoot);
|
|
108721
109198
|
const result = await applyReviewDecisions({ projectRoot, payload });
|
|
108722
109199
|
const continuation = await input.afterApply?.(projectRoot);
|
|
108723
109200
|
process.stdout.write(formatApplyResult({ ...result, ...continuation === undefined ? {} : { continuation } }, input.format ?? "text"));
|
|
108724
109201
|
}
|
|
108725
109202
|
async function runReviewApproveAllCommand(input) {
|
|
108726
|
-
if (input.managed === true && input.force === true) {
|
|
108727
|
-
throw new ContextError(ExitCode.UserError, "review approve-all accepts either --managed or --
|
|
109203
|
+
if (input.managed === true && (input.confirmed === true || input.force === true)) {
|
|
109204
|
+
throw new ContextError(ExitCode.UserError, "review approve-all accepts either --managed or --confirmed, not both", {
|
|
108728
109205
|
category: ErrorCategory.UserInputInvalid,
|
|
108729
109206
|
code: "review-approval-authority-conflict",
|
|
108730
109207
|
next: "context status --format json"
|
|
108731
109208
|
});
|
|
108732
109209
|
}
|
|
108733
|
-
if (input.managed !== true && input.force !== true) {
|
|
108734
|
-
throw new ContextError(ExitCode.UserError, "review approve-all requires explicit --managed authority or --
|
|
109210
|
+
if (input.managed !== true && input.confirmed !== true && input.force !== true) {
|
|
109211
|
+
throw new ContextError(ExitCode.UserError, "review approve-all requires explicit --managed authority or --confirmed user confirmation", {
|
|
108735
109212
|
category: ErrorCategory.UserInputInvalid,
|
|
108736
109213
|
code: "review-approval-authority-required",
|
|
108737
109214
|
next: "context status --format json"
|
|
108738
109215
|
});
|
|
108739
109216
|
}
|
|
108740
|
-
|
|
109217
|
+
if (input.force === true)
|
|
109218
|
+
warnLegacyReview("--force");
|
|
109219
|
+
const decisionSource = input.managed === true ? "managed-session" : "explicit-user-confirmation";
|
|
108741
109220
|
const projectRoot = projectRootFromCwd(input.cwd);
|
|
108742
109221
|
const scoped = await reviewCommandScope({
|
|
108743
109222
|
projectRoot,
|
|
108744
109223
|
...input.collection !== undefined ? { collection: input.collection } : {},
|
|
108745
109224
|
...input.all === true ? { all: true } : {}
|
|
108746
109225
|
});
|
|
109226
|
+
const confirmedScope = input.confirmed === true ? await readConfirmedReviewScope(projectRoot, scoped.collection ?? "all") : undefined;
|
|
108747
109227
|
const result = await applyReviewDecisions({
|
|
108748
109228
|
projectRoot,
|
|
108749
109229
|
payload: {
|
|
109230
|
+
...confirmedScope === undefined ? {} : { baseline_hash: confirmedScope.baseline_hash },
|
|
108750
109231
|
decisions: [],
|
|
108751
109232
|
default: "approved",
|
|
108752
|
-
note: input.managed === true ? "managed-session auto approval" : "explicit user
|
|
109233
|
+
note: input.managed === true ? "managed-session auto approval" : "explicit user confirmation",
|
|
108753
109234
|
...scoped.collection !== undefined ? { collection: scoped.collection } : {},
|
|
108754
|
-
scope: scoped.scope
|
|
109235
|
+
scope: confirmedScope?.scope ?? scoped.scope
|
|
108755
109236
|
}
|
|
108756
109237
|
});
|
|
108757
109238
|
const continuation = await input.afterApply?.(projectRoot);
|
|
@@ -108785,7 +109266,7 @@ async function runReviewApproveAllCommand(input) {
|
|
|
108785
109266
|
process.stdout.write(formatFeedback({
|
|
108786
109267
|
symbol: "✓",
|
|
108787
109268
|
action: "approved",
|
|
108788
|
-
subject: input.managed === true ? "managed review batch" : "
|
|
109269
|
+
subject: input.managed === true ? "managed review batch" : "confirmed review batch",
|
|
108789
109270
|
headline: `${result.approved} candidate(s) approved`,
|
|
108790
109271
|
body: [
|
|
108791
109272
|
`decision source: ${decisionSource}`,
|
|
@@ -108954,7 +109435,7 @@ function supportsCommand(command2) {
|
|
|
108954
109435
|
return options !== undefined && options.positionals.length === 0 && outputFormat(options) !== undefined;
|
|
108955
109436
|
}
|
|
108956
109437
|
if (topLevel === "review" && subcommand === "approve-all") {
|
|
108957
|
-
const options = parseOptions(command2.slice(2), new Set(["--format"]), new Set(["--all", "--managed", "--
|
|
109438
|
+
const options = parseOptions(command2.slice(2), new Set(["--format"]), new Set(["--all", "--managed", "--confirmed", "--verbose"]));
|
|
108958
109439
|
return options !== undefined && options.positionals.length <= 1 && !(options.flags.has("--all") && options.positionals.length === 1) && outputFormat(options) !== undefined;
|
|
108959
109440
|
}
|
|
108960
109441
|
if (topLevel === "package" && subcommand === "template" && command2[2] === "accept") {
|
|
@@ -108993,7 +109474,7 @@ async function executeBuild(cwd, command2) {
|
|
|
108993
109474
|
});
|
|
108994
109475
|
}
|
|
108995
109476
|
async function executeReviewApproveAll(cwd, invocation) {
|
|
108996
|
-
const options = parseOptions(invocation.command.slice(2), new Set(["--format"]), new Set(["--all", "--managed", "--
|
|
109477
|
+
const options = parseOptions(invocation.command.slice(2), new Set(["--format"]), new Set(["--all", "--managed", "--confirmed", "--verbose"]));
|
|
108997
109478
|
if (options === undefined || options.positionals.length > 1)
|
|
108998
109479
|
throw new Error("unsupported review approve-all command");
|
|
108999
109480
|
await runReviewApproveAllCommand({
|
|
@@ -109001,7 +109482,7 @@ async function executeReviewApproveAll(cwd, invocation) {
|
|
|
109001
109482
|
...options.positionals[0] === undefined ? {} : { collection: options.positionals[0] },
|
|
109002
109483
|
...options.flags.has("--all") ? { all: true } : {},
|
|
109003
109484
|
managed: invocation.managed || options.flags.has("--managed"),
|
|
109004
|
-
|
|
109485
|
+
confirmed: options.flags.has("--confirmed"),
|
|
109005
109486
|
verbose: options.flags.has("--verbose"),
|
|
109006
109487
|
format: outputFormat(options) ?? "text"
|
|
109007
109488
|
});
|
|
@@ -109329,8 +109810,8 @@ init_exitCode();
|
|
|
109329
109810
|
init_maintenanceStorage();
|
|
109330
109811
|
init_productionFeedback();
|
|
109331
109812
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
109332
|
-
import { readFile as
|
|
109333
|
-
import { join as
|
|
109813
|
+
import { readFile as readFile82 } from "node:fs/promises";
|
|
109814
|
+
import { join as join103 } from "node:path";
|
|
109334
109815
|
async function beginProductionRevision(input) {
|
|
109335
109816
|
return withProductionFeedback({ operation: "revision" }, () => withProjectWriteLock(input.projectRoot, "production-revision", async () => {
|
|
109336
109817
|
await recoverDurableMultiFileTransactions(input.projectRoot);
|
|
@@ -109369,7 +109850,7 @@ async function beginProductionRevision(input) {
|
|
|
109369
109850
|
const formal = approved.byPath.get(path3);
|
|
109370
109851
|
if (!prior && !formal)
|
|
109371
109852
|
throw invalid2("Write the current task first; there is no article draft to revise yet.");
|
|
109372
|
-
const markdown = prior?.body ?? await
|
|
109853
|
+
const markdown = prior?.body ?? await readFile82(await safeProjectTarget(input.projectRoot, join103("knowledge", path3)), "utf8");
|
|
109373
109854
|
const sections = prior?.indexer_candidate.sections.map((section) => ({ id: section.section_key, references: section.references })) ?? formal?.sections;
|
|
109374
109855
|
const sources = [];
|
|
109375
109856
|
for (const source2 of owner.sources) {
|
|
@@ -109546,7 +110027,7 @@ init_cliFeedback();
|
|
|
109546
110027
|
init_errors3();
|
|
109547
110028
|
init_exitCode();
|
|
109548
110029
|
var import_yaml41 = __toESM(require_dist(), 1);
|
|
109549
|
-
import { readFile as
|
|
110030
|
+
import { readFile as readFile83 } from "node:fs/promises";
|
|
109550
110031
|
function userInputError2(message, detail = {}) {
|
|
109551
110032
|
return new ContextError(ExitCode.UserError, message, {
|
|
109552
110033
|
category: ErrorCategory.UserInputInvalid,
|
|
@@ -109615,7 +110096,7 @@ async function readPayloadTextFromStdin(stdin) {
|
|
|
109615
110096
|
}
|
|
109616
110097
|
async function readPayloadText(path3) {
|
|
109617
110098
|
if (path3 !== "-")
|
|
109618
|
-
return
|
|
110099
|
+
return readFile83(path3, "utf8");
|
|
109619
110100
|
return readPayloadTextFromStdin(process.stdin);
|
|
109620
110101
|
}
|
|
109621
110102
|
async function readYamlOrJsonInput(input) {
|
|
@@ -110009,7 +110490,7 @@ init_atomicWrite();
|
|
|
110009
110490
|
var import_yaml44 = __toESM(require_dist(), 1);
|
|
110010
110491
|
import { Buffer as Buffer5 } from "node:buffer";
|
|
110011
110492
|
import { createHash as createHash32 } from "node:crypto";
|
|
110012
|
-
import { join as
|
|
110493
|
+
import { join as join107 } from "node:path";
|
|
110013
110494
|
var INLINE_LIMIT = 16 * 1024;
|
|
110014
110495
|
function record4(value) {
|
|
110015
110496
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
@@ -110035,8 +110516,8 @@ async function prepareActionCompletionOutput(input) {
|
|
|
110035
110516
|
if (production && Buffer5.byteLength(full) <= INLINE_LIMIT)
|
|
110036
110517
|
return input.result;
|
|
110037
110518
|
const digest7 = createHash32("sha256").update(full).digest("hex");
|
|
110038
|
-
const root2 =
|
|
110039
|
-
const resultFile =
|
|
110519
|
+
const root2 = join107(input.projectRoot, ".tmp/context-runtime/action-results");
|
|
110520
|
+
const resultFile = join107(root2, `${digest7}.json`);
|
|
110040
110521
|
await atomicWriteFile(resultFile, full);
|
|
110041
110522
|
if (production)
|
|
110042
110523
|
return {
|
|
@@ -110455,7 +110936,7 @@ init_cliFeedback();
|
|
|
110455
110936
|
import { existsSync as existsSync27 } from "node:fs";
|
|
110456
110937
|
import { readdir as readdir27, rm as rm23 } from "node:fs/promises";
|
|
110457
110938
|
import { homedir } from "node:os";
|
|
110458
|
-
import { join as
|
|
110939
|
+
import { join as join108 } from "node:path";
|
|
110459
110940
|
var ORPHAN_MARKER = ".orphaned_at";
|
|
110460
110941
|
var CLAUDE_PLUGIN_CACHE_ROOT_ENV = "C4A_CLAUDE_PLUGIN_CACHE_ROOT";
|
|
110461
110942
|
var CLAUDE_PLUGIN_CACHE_HOME_ENV = "C4A_CLAUDE_PLUGIN_CACHE_HOME";
|
|
@@ -110472,19 +110953,19 @@ async function cleanClaudePluginCache(opts = {}) {
|
|
|
110472
110953
|
for (const mp of marketplaces) {
|
|
110473
110954
|
if (!mp.isDirectory())
|
|
110474
110955
|
continue;
|
|
110475
|
-
const mpDir =
|
|
110956
|
+
const mpDir = join108(cacheRoot, mp.name);
|
|
110476
110957
|
const plugins = await readdir27(mpDir, { withFileTypes: true });
|
|
110477
110958
|
for (const pl of plugins) {
|
|
110478
110959
|
if (!pl.isDirectory())
|
|
110479
110960
|
continue;
|
|
110480
|
-
const plDir =
|
|
110961
|
+
const plDir = join108(mpDir, pl.name);
|
|
110481
110962
|
const versions = await readdir27(plDir, { withFileTypes: true });
|
|
110482
110963
|
for (const ver of versions) {
|
|
110483
110964
|
if (!ver.isDirectory())
|
|
110484
110965
|
continue;
|
|
110485
110966
|
scanned += 1;
|
|
110486
|
-
const verDir =
|
|
110487
|
-
const markerPath =
|
|
110967
|
+
const verDir = join108(plDir, ver.name);
|
|
110968
|
+
const markerPath = join108(verDir, ORPHAN_MARKER);
|
|
110488
110969
|
if (!existsSync27(markerPath))
|
|
110489
110970
|
continue;
|
|
110490
110971
|
const label2 = `${mp.name}/${pl.name}/${ver.name}`;
|
|
@@ -110516,7 +110997,7 @@ function resolveClaudePluginCacheRoot(opts) {
|
|
|
110516
110997
|
if (explicitRoot)
|
|
110517
110998
|
return explicitRoot;
|
|
110518
110999
|
const home = opts.home ?? process.env[CLAUDE_PLUGIN_CACHE_HOME_ENV] ?? homedir();
|
|
110519
|
-
return
|
|
111000
|
+
return join108(home, ".claude", "plugins", "cache");
|
|
110520
111001
|
}
|
|
110521
111002
|
async function isEmptyDir(dir) {
|
|
110522
111003
|
try {
|
|
@@ -110548,13 +111029,13 @@ init_exitCode();
|
|
|
110548
111029
|
|
|
110549
111030
|
// src/lib/packageVersion.ts
|
|
110550
111031
|
import { existsSync as existsSync28, readFileSync as readFileSync9 } from "node:fs";
|
|
110551
|
-
import { dirname as dirname43, join as
|
|
111032
|
+
import { dirname as dirname43, join as join109 } from "node:path";
|
|
110552
111033
|
import { fileURLToPath as fileURLToPath8 } from "node:url";
|
|
110553
111034
|
function readPackageVersion() {
|
|
110554
111035
|
try {
|
|
110555
111036
|
let dir = dirname43(fileURLToPath8(import.meta.url));
|
|
110556
111037
|
for (let depth = 0;depth < 8; depth += 1) {
|
|
110557
|
-
const packagePath =
|
|
111038
|
+
const packagePath = join109(dir, "package.json");
|
|
110558
111039
|
if (existsSync28(packagePath)) {
|
|
110559
111040
|
const parsed = JSON.parse(readFileSync9(packagePath, "utf8"));
|
|
110560
111041
|
if (typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
@@ -110577,7 +111058,7 @@ function readPackageVersion() {
|
|
|
110577
111058
|
|
|
110578
111059
|
// src/project/sourceCommands.ts
|
|
110579
111060
|
init_src2();
|
|
110580
|
-
import { readFile as
|
|
111061
|
+
import { readFile as readFile99 } from "node:fs/promises";
|
|
110581
111062
|
import { isAbsolute as isAbsolute23, resolve as resolve41 } from "node:path";
|
|
110582
111063
|
init_cliFeedback();
|
|
110583
111064
|
init_errors3();
|
|
@@ -110940,15 +111421,15 @@ async function restoreRepositorySources(input) {
|
|
|
110940
111421
|
init_src2();
|
|
110941
111422
|
init_documentCapture();
|
|
110942
111423
|
import { existsSync as existsSync30 } from "node:fs";
|
|
110943
|
-
import { readFile as
|
|
110944
|
-
import { join as
|
|
111424
|
+
import { readFile as readFile89 } from "node:fs/promises";
|
|
111425
|
+
import { join as join111 } from "node:path";
|
|
110945
111426
|
|
|
110946
111427
|
// src/project/sourceCommandViews.ts
|
|
110947
111428
|
init_documentSiteDetection();
|
|
110948
111429
|
init_workspace();
|
|
110949
111430
|
init_documentBatchManifest();
|
|
110950
|
-
import { readFile as
|
|
110951
|
-
import { join as
|
|
111431
|
+
import { readFile as readFile88 } from "node:fs/promises";
|
|
111432
|
+
import { join as join110 } from "node:path";
|
|
110952
111433
|
function repoSourceAgentView(source2) {
|
|
110953
111434
|
return {
|
|
110954
111435
|
id: source2.id ?? source2.name,
|
|
@@ -111009,7 +111490,7 @@ async function fileSourceAgentViewWithNextAction(input) {
|
|
|
111009
111490
|
};
|
|
111010
111491
|
}
|
|
111011
111492
|
function documentSourceManifestPath(source2) {
|
|
111012
|
-
return source2.snapshot?.manifest ??
|
|
111493
|
+
return source2.snapshot?.manifest ?? join110(source2.materializedAt, "manifest.json");
|
|
111013
111494
|
}
|
|
111014
111495
|
async function fileSourceDocumentSiteHint(input) {
|
|
111015
111496
|
const detection = await detectDocumentSiteFiles({
|
|
@@ -111019,7 +111500,7 @@ async function fileSourceDocumentSiteHint(input) {
|
|
|
111019
111500
|
let snapshotConfigured = false;
|
|
111020
111501
|
const manifest = documentSourceManifestPath(input.source);
|
|
111021
111502
|
try {
|
|
111022
|
-
const parsed = parseDocumentSnapshotForSource(JSON.parse(await
|
|
111503
|
+
const parsed = parseDocumentSnapshotForSource(JSON.parse(await readFile88(join110(input.projectRoot, manifest), "utf8")), input.source.name);
|
|
111023
111504
|
snapshotConfigured = manifestUsesMdxJsonDocs(parsed);
|
|
111024
111505
|
} catch {
|
|
111025
111506
|
snapshotConfigured = false;
|
|
@@ -111052,11 +111533,11 @@ async function larkSourceAgentViewWithNextAction(input) {
|
|
|
111052
111533
|
init_documentBatchManifest();
|
|
111053
111534
|
init_documentSnapshotFidelity();
|
|
111054
111535
|
function documentSourceManifestPath2(source2) {
|
|
111055
|
-
return source2.snapshot?.manifest ??
|
|
111536
|
+
return source2.snapshot?.manifest ?? join111(source2.materializedAt, "manifest.json");
|
|
111056
111537
|
}
|
|
111057
111538
|
async function documentSnapshotState(input) {
|
|
111058
111539
|
const manifest = documentSourceManifestPath2(input.source);
|
|
111059
|
-
const manifestPath =
|
|
111540
|
+
const manifestPath = join111(input.projectRoot, manifest);
|
|
111060
111541
|
if (!existsSync30(manifestPath)) {
|
|
111061
111542
|
return {
|
|
111062
111543
|
snapshotReady: false,
|
|
@@ -111067,7 +111548,7 @@ async function documentSnapshotState(input) {
|
|
|
111067
111548
|
};
|
|
111068
111549
|
}
|
|
111069
111550
|
try {
|
|
111070
|
-
const parsed = findDocumentSnapshotForSource(JSON.parse(await
|
|
111551
|
+
const parsed = findDocumentSnapshotForSource(JSON.parse(await readFile89(manifestPath, "utf8")), input.source.name);
|
|
111071
111552
|
if (parsed === null) {
|
|
111072
111553
|
return {
|
|
111073
111554
|
snapshotReady: false,
|
|
@@ -111135,7 +111616,7 @@ async function documentSnapshotState(input) {
|
|
|
111135
111616
|
const missing = [
|
|
111136
111617
|
...parsed.files.map((file) => file.path),
|
|
111137
111618
|
...(parsed.assets ?? []).filter((asset) => asset.content_hash !== undefined).map((asset) => asset.path)
|
|
111138
|
-
].find((path3) => !existsSync30(
|
|
111619
|
+
].find((path3) => !existsSync30(join111(input.projectRoot, input.source.materializedAt, path3)));
|
|
111139
111620
|
if (missing !== undefined) {
|
|
111140
111621
|
return {
|
|
111141
111622
|
snapshotReady: false,
|
|
@@ -111236,8 +111717,8 @@ init_errors3();
|
|
|
111236
111717
|
init_exitCode();
|
|
111237
111718
|
var import_yaml47 = __toESM(require_dist(), 1);
|
|
111238
111719
|
import { createHash as createHash33 } from "node:crypto";
|
|
111239
|
-
import { readFile as
|
|
111240
|
-
import { basename as basename13, extname as extname15, isAbsolute as isAbsolute21, join as
|
|
111720
|
+
import { readFile as readFile90, realpath as realpath12 } from "node:fs/promises";
|
|
111721
|
+
import { basename as basename13, extname as extname15, isAbsolute as isAbsolute21, join as join112, relative as relative28, resolve as resolve36 } from "node:path";
|
|
111241
111722
|
init_writeLock();
|
|
111242
111723
|
var SOURCE_NAME_PATTERN3 = /^[a-z0-9][a-z0-9._-]*$/u;
|
|
111243
111724
|
function isDateSourceNamespace(value) {
|
|
@@ -111333,7 +111814,7 @@ function assertSafeFileInclude(value) {
|
|
|
111333
111814
|
}
|
|
111334
111815
|
async function readRegistryDocument(projectRoot, registryPath2) {
|
|
111335
111816
|
try {
|
|
111336
|
-
const content3 = await
|
|
111817
|
+
const content3 = await readFile90(join112(projectRoot, registryPath2), "utf8");
|
|
111337
111818
|
return content3.trim().length === 0 ? { sources: [] } : import_yaml47.default.parse(content3);
|
|
111338
111819
|
} catch (error) {
|
|
111339
111820
|
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
@@ -111451,7 +111932,7 @@ async function addFileSourceUnlocked(input) {
|
|
|
111451
111932
|
const record6 = entry2;
|
|
111452
111933
|
return record6.name !== input.name && record6.id !== input.name;
|
|
111453
111934
|
}), nextEntry];
|
|
111454
|
-
await atomicWriteFile(
|
|
111935
|
+
await atomicWriteFile(join112(input.projectRoot, DEFAULT_FILE_SOURCES_REGISTRY_PATH), import_yaml47.default.stringify({ sources: nextSources }));
|
|
111455
111936
|
const updated = await loadSourcesRegistry({ rootDir: input.projectRoot });
|
|
111456
111937
|
const entry = updated.files.find((source2) => source2.name === input.name || source2.id === input.name);
|
|
111457
111938
|
if (entry === undefined) {
|
|
@@ -111510,7 +111991,7 @@ async function addLarkSourceUnlocked(input) {
|
|
|
111510
111991
|
const record6 = entry2;
|
|
111511
111992
|
return record6.name !== input.name && record6.id !== input.name;
|
|
111512
111993
|
}), nextEntry];
|
|
111513
|
-
await atomicWriteFile(
|
|
111994
|
+
await atomicWriteFile(join112(input.projectRoot, DEFAULT_LARK_SOURCES_REGISTRY_PATH), import_yaml47.default.stringify({ sources: nextSources }));
|
|
111514
111995
|
const updated = await loadSourcesRegistry({ rootDir: input.projectRoot });
|
|
111515
111996
|
const entry = updated.larks.find((source2) => source2.name === input.name || source2.id === input.name);
|
|
111516
111997
|
if (entry === undefined) {
|
|
@@ -111542,18 +112023,18 @@ init_cliFeedback();
|
|
|
111542
112023
|
init_errors3();
|
|
111543
112024
|
init_exitCode();
|
|
111544
112025
|
var import_yaml48 = __toESM(require_dist(), 1);
|
|
111545
|
-
import { readFile as
|
|
111546
|
-
import { join as
|
|
112026
|
+
import { readFile as readFile91 } from "node:fs/promises";
|
|
112027
|
+
import { join as join113 } from "node:path";
|
|
111547
112028
|
import { isDeepStrictEqual as isDeepStrictEqual3 } from "node:util";
|
|
111548
112029
|
function identityKeys(entry) {
|
|
111549
112030
|
return new Set([entry.name, entry.id]);
|
|
111550
112031
|
}
|
|
111551
112032
|
async function createLarkBatchRegistration(projectRoot, namespace) {
|
|
111552
112033
|
const registry2 = await loadSourcesRegistry({ rootDir: projectRoot });
|
|
111553
|
-
const registryPath2 =
|
|
112034
|
+
const registryPath2 = join113(projectRoot, DEFAULT_LARK_SOURCES_REGISTRY_PATH);
|
|
111554
112035
|
let committedSources;
|
|
111555
112036
|
try {
|
|
111556
|
-
const text10 = await
|
|
112037
|
+
const text10 = await readFile91(registryPath2, "utf8");
|
|
111557
112038
|
committedSources = text10.trim().length === 0 ? [] : import_yaml48.default.parse(text10).sources ?? [];
|
|
111558
112039
|
} catch (error) {
|
|
111559
112040
|
if (!(error instanceof Error && ("code" in error) && error.code === "ENOENT"))
|
|
@@ -111671,8 +112152,8 @@ init_cliFeedback();
|
|
|
111671
112152
|
init_errors3();
|
|
111672
112153
|
init_exitCode();
|
|
111673
112154
|
import { createHash as createHash34 } from "node:crypto";
|
|
111674
|
-
import { lstat as lstat14, readFile as
|
|
111675
|
-
import { join as
|
|
112155
|
+
import { lstat as lstat14, readFile as readFile92 } from "node:fs/promises";
|
|
112156
|
+
import { join as join114, resolve as resolve37 } from "node:path";
|
|
111676
112157
|
var INPUT_SCHEMA = "context.source-batch.input.v1";
|
|
111677
112158
|
var STATE_SCHEMA = "context.source-batch.state.v1";
|
|
111678
112159
|
var RUNTIME_PARTS = [".tmp", "context-runtime", "source-batches"];
|
|
@@ -111723,14 +112204,14 @@ function paths(projectRoot, jobId) {
|
|
|
111723
112204
|
throw journalError("source-batch-id-invalid", "Source batch checkpoint ID must contain exactly 32 lowercase hexadecimal characters.");
|
|
111724
112205
|
}
|
|
111725
112206
|
const directory = resolve37(projectRoot, ...RUNTIME_PARTS, jobId);
|
|
111726
|
-
return { inputPath:
|
|
112207
|
+
return { inputPath: join114(directory, "input.json"), statePath: join114(directory, "state.json") };
|
|
111727
112208
|
}
|
|
111728
112209
|
async function assertJournalPath(projectRoot, jobId, filename) {
|
|
111729
112210
|
paths(projectRoot, jobId);
|
|
111730
112211
|
let current2 = resolve37(projectRoot);
|
|
111731
112212
|
const parts = [...RUNTIME_PARTS, jobId, filename];
|
|
111732
112213
|
for (const [index2, part] of parts.entries()) {
|
|
111733
|
-
current2 =
|
|
112214
|
+
current2 = join114(current2, part);
|
|
111734
112215
|
let stat11;
|
|
111735
112216
|
try {
|
|
111736
112217
|
stat11 = await lstat14(current2);
|
|
@@ -111761,7 +112242,7 @@ async function loadSourceBatchJournal(input) {
|
|
|
111761
112242
|
const { inputPath } = paths(input.projectRoot, input.jobId);
|
|
111762
112243
|
try {
|
|
111763
112244
|
await assertJournalPath(input.projectRoot, input.jobId, "input.json");
|
|
111764
|
-
const stored = JSON.parse(await
|
|
112245
|
+
const stored = JSON.parse(await readFile92(inputPath, "utf8"));
|
|
111765
112246
|
if (stored === null || typeof stored !== "object" || Array.isArray(stored)) {
|
|
111766
112247
|
throw journalError("source-batch-input-invalid", "Source batch checkpoint input must be an object.");
|
|
111767
112248
|
}
|
|
@@ -111864,7 +112345,7 @@ async function readSourceBatchJournalStatus(input) {
|
|
|
111864
112345
|
let warning;
|
|
111865
112346
|
try {
|
|
111866
112347
|
await assertJournalPath(journal.projectRoot, journal.jobId, "state.json");
|
|
111867
|
-
const parsed = JSON.parse(await
|
|
112348
|
+
const parsed = JSON.parse(await readFile92(journal.statePath, "utf8"));
|
|
111868
112349
|
if (parsed.schema !== STATE_SCHEMA || parsed.job_id !== journal.jobId || parsed.input_digest !== journal.inputDigest || parsed.total !== journal.items.length || !Number.isSafeInteger(parsed.pid) || parsed.pid <= 0 || typeof parsed.updated_at !== "string" || !Number.isFinite(Date.parse(parsed.updated_at))) {
|
|
111869
112350
|
throw journalError("source-batch-state-invalid", "Source batch checkpoint progress is invalid.");
|
|
111870
112351
|
}
|
|
@@ -112455,13 +112936,13 @@ init_exitCode();
|
|
|
112455
112936
|
|
|
112456
112937
|
// src/project/wikiDiscovery.ts
|
|
112457
112938
|
import { mkdir as mkdir38 } from "node:fs/promises";
|
|
112458
|
-
import { join as
|
|
112939
|
+
import { join as join116, relative as relative30 } from "node:path";
|
|
112459
112940
|
|
|
112460
112941
|
// src/project/wikiDiscoveryStore.ts
|
|
112461
112942
|
init_atomicWrite();
|
|
112462
112943
|
import { createHash as createHash35, randomUUID as randomUUID8 } from "node:crypto";
|
|
112463
|
-
import { link as link2, lstat as lstat15, mkdir as mkdir37, readFile as
|
|
112464
|
-
import { join as
|
|
112944
|
+
import { link as link2, lstat as lstat15, mkdir as mkdir37, readFile as readFile93, readdir as readdir28, rm as rm25, writeFile as writeFile29 } from "node:fs/promises";
|
|
112945
|
+
import { join as join115, relative as relative29, resolve as resolve38, sep as sep9 } from "node:path";
|
|
112465
112946
|
function wikiDiscoveryDigest(value) {
|
|
112466
112947
|
return createHash35("sha256").update(value).digest("hex");
|
|
112467
112948
|
}
|
|
@@ -112475,7 +112956,7 @@ async function assertWikiDiscoveryPath(root2, path3) {
|
|
|
112475
112956
|
throw new WikiDiscoveryError("unsafe-checkpoint-path", false, "Discovery checkpoint must stay inside its workspace");
|
|
112476
112957
|
let current2 = base;
|
|
112477
112958
|
for (const part of parts) {
|
|
112478
|
-
current2 =
|
|
112959
|
+
current2 = join115(current2, part);
|
|
112479
112960
|
try {
|
|
112480
112961
|
if ((await lstat15(current2)).isSymbolicLink()) {
|
|
112481
112962
|
throw new WikiDiscoveryError("unsafe-checkpoint-path", false, "Discovery checkpoint paths must not contain symbolic links");
|
|
@@ -112491,7 +112972,7 @@ async function readWikiDiscoveryJson(path3) {
|
|
|
112491
112972
|
try {
|
|
112492
112973
|
if ((await lstat15(path3)).isSymbolicLink())
|
|
112493
112974
|
throw new WikiDiscoveryError("unsafe-checkpoint-path", false, "Discovery checkpoint must not be a symbolic link");
|
|
112494
|
-
return JSON.parse(await
|
|
112975
|
+
return JSON.parse(await readFile93(path3, "utf8"));
|
|
112495
112976
|
} catch (error) {
|
|
112496
112977
|
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT")
|
|
112497
112978
|
return;
|
|
@@ -112504,12 +112985,12 @@ async function saveWikiDiscoveryJson(path3, value) {
|
|
|
112504
112985
|
}
|
|
112505
112986
|
async function loadWikiDiscoveryReceipts(directory) {
|
|
112506
112987
|
const result = new Map;
|
|
112507
|
-
await assertWikiDiscoveryPath(directory,
|
|
112508
|
-
await mkdir37(
|
|
112509
|
-
for (const file of await readdir28(
|
|
112988
|
+
await assertWikiDiscoveryPath(directory, join115(directory, "pages"));
|
|
112989
|
+
await mkdir37(join115(directory, "pages"), { recursive: true });
|
|
112990
|
+
for (const file of await readdir28(join115(directory, "pages"))) {
|
|
112510
112991
|
if (!/^[a-f0-9]{64}\.json$/u.test(file))
|
|
112511
112992
|
continue;
|
|
112512
|
-
const value = await readWikiDiscoveryJson(
|
|
112993
|
+
const value = await readWikiDiscoveryJson(join115(directory, "pages", file));
|
|
112513
112994
|
if (!value || typeof value.parent_node_token !== "string" || value.page_token !== undefined && typeof value.page_token !== "string" || !["completed", "failed"].includes(value.status) || value.status === "completed" && (!value.page || !Array.isArray(value.page.nodes) || value.page.next_page_token !== undefined && typeof value.page.next_page_token !== "string") || value.status === "failed" && (!value.failure || typeof value.failure.retryable !== "boolean" || typeof value.failure.reason !== "string" || typeof value.failure.message !== "string") || `${wikiDiscoveryPageKey(value)}.json` !== file) {
|
|
112514
112995
|
throw new WikiDiscoveryError("checkpoint-invalid", false, "Discovery page checkpoint is invalid; preserve it for inspection");
|
|
112515
112996
|
}
|
|
@@ -112528,14 +113009,14 @@ function ownerRunning(pid) {
|
|
|
112528
113009
|
}
|
|
112529
113010
|
}
|
|
112530
113011
|
async function acquireWikiDiscoveryLease(directory) {
|
|
112531
|
-
const leases =
|
|
113012
|
+
const leases = join115(directory, "leases");
|
|
112532
113013
|
await assertWikiDiscoveryPath(directory, leases);
|
|
112533
113014
|
await mkdir37(leases, { recursive: true });
|
|
112534
113015
|
for (let attempt = 0;attempt < 8; attempt++) {
|
|
112535
113016
|
const files = (await readdir28(leases)).filter((file) => /^\d{12}\.json$/u.test(file)).sort();
|
|
112536
113017
|
const latest = files.at(-1);
|
|
112537
113018
|
if (latest) {
|
|
112538
|
-
const current2 = await readWikiDiscoveryJson(
|
|
113019
|
+
const current2 = await readWikiDiscoveryJson(join115(leases, latest));
|
|
112539
113020
|
if (!current2 || !Number.isSafeInteger(current2.pid) || current2.pid <= 0 || typeof current2.token !== "string" || typeof current2.released !== "boolean") {
|
|
112540
113021
|
throw new WikiDiscoveryError("checkpoint-invalid", false, "Discovery lease is invalid; preserve it for inspection");
|
|
112541
113022
|
}
|
|
@@ -112544,9 +113025,9 @@ async function acquireWikiDiscoveryLease(directory) {
|
|
|
112544
113025
|
}
|
|
112545
113026
|
}
|
|
112546
113027
|
const sequence = latest ? Number(latest.slice(0, 12)) + 1 : 1;
|
|
112547
|
-
const path3 =
|
|
113028
|
+
const path3 = join115(leases, `${String(sequence).padStart(12, "0")}.json`);
|
|
112548
113029
|
const owner = { pid: process.pid, token: randomUUID8(), released: false };
|
|
112549
|
-
const temporary =
|
|
113030
|
+
const temporary = join115(leases, `.lease-${owner.token}`);
|
|
112550
113031
|
await writeFile29(temporary, JSON.stringify(owner), { flag: "wx", mode: 384 });
|
|
112551
113032
|
try {
|
|
112552
113033
|
await link2(temporary, path3);
|
|
@@ -112671,7 +113152,7 @@ async function discoverLarkWiki(input) {
|
|
|
112671
113152
|
throw new WikiDiscoveryError("invalid-concurrency", false, "Wiki concurrency must be an integer from 1 to 4");
|
|
112672
113153
|
}
|
|
112673
113154
|
const jobId = wikiDiscoveryDigest(url);
|
|
112674
|
-
const directory =
|
|
113155
|
+
const directory = join116(input.projectRoot, ".tmp", "context-runtime", "wiki-discovery", jobId);
|
|
112675
113156
|
await assertWikiDiscoveryPath(input.projectRoot, directory);
|
|
112676
113157
|
await mkdir38(directory, { recursive: true });
|
|
112677
113158
|
const release = await acquireWikiDiscoveryLease(directory);
|
|
@@ -112683,7 +113164,7 @@ async function discoverLarkWiki(input) {
|
|
|
112683
113164
|
}
|
|
112684
113165
|
async function runDiscovery(input, job) {
|
|
112685
113166
|
const { directory, url, identity: identity2 } = job;
|
|
112686
|
-
const metadataPath =
|
|
113167
|
+
const metadataPath = join116(directory, "job.json");
|
|
112687
113168
|
let metadata = await readWikiDiscoveryJson(metadataPath);
|
|
112688
113169
|
if (metadata && (metadata.protocol !== "context.wiki-discovery.v1" || metadata.url !== url || metadata.identity !== identity2)) {
|
|
112689
113170
|
throw new WikiDiscoveryError("job-identity-mismatch", false, "Resume must preserve the discovery's original URL and identity");
|
|
@@ -112732,7 +113213,7 @@ async function runDiscovery(input, job) {
|
|
|
112732
113213
|
}
|
|
112733
113214
|
const key = wikiDiscoveryPageKey(cursor);
|
|
112734
113215
|
try {
|
|
112735
|
-
await saveWikiDiscoveryJson(
|
|
113216
|
+
await saveWikiDiscoveryJson(join116(directory, "pages", `${key}.json`), receipt2);
|
|
112736
113217
|
receipts.set(key, receipt2);
|
|
112737
113218
|
} catch {
|
|
112738
113219
|
checkpointError = "A discovery page could not be checkpointed; saved pages remain reusable and unsaved pages must be requested again";
|
|
@@ -112750,7 +113231,7 @@ async function finishDiscovery(input, state) {
|
|
|
112750
113231
|
const { directory, url, identity: identity2, jobId, metadata, view } = state;
|
|
112751
113232
|
let checkpointError = state.checkpointError;
|
|
112752
113233
|
let status = input.signal?.aborted ? "paused" : checkpointError || view.pending.length || view.failed.length || !metadata.root ? "partial" : "completed";
|
|
112753
|
-
const manifestPath =
|
|
113234
|
+
const manifestPath = join116(directory, "manifest.json");
|
|
112754
113235
|
try {
|
|
112755
113236
|
await saveWikiDiscoveryJson(manifestPath, {
|
|
112756
113237
|
protocol: "context.wiki-discovery.manifest.v1",
|
|
@@ -112930,8 +113411,8 @@ init_writeLock();
|
|
|
112930
113411
|
var import_yaml51 = __toESM(require_dist(), 1);
|
|
112931
113412
|
import { existsSync as existsSync31 } from "node:fs";
|
|
112932
113413
|
import { createHash as createHash36 } from "node:crypto";
|
|
112933
|
-
import { readFile as
|
|
112934
|
-
import { isAbsolute as isAbsolute22, join as
|
|
113414
|
+
import { readFile as readFile94, readdir as readdir29, rm as rm26 } from "node:fs/promises";
|
|
113415
|
+
import { isAbsolute as isAbsolute22, join as join117, relative as relative31, resolve as resolve39, sep as sep10 } from "node:path";
|
|
112935
113416
|
function sourceIdentity(source2) {
|
|
112936
113417
|
if (source2.kind === "source.collection")
|
|
112937
113418
|
return;
|
|
@@ -112963,10 +113444,10 @@ function collectStrings(value, output) {
|
|
|
112963
113444
|
}
|
|
112964
113445
|
}
|
|
112965
113446
|
async function yamlReferences(input) {
|
|
112966
|
-
const absolutePath =
|
|
113447
|
+
const absolutePath = join117(input.projectRoot, input.path);
|
|
112967
113448
|
if (!existsSync31(absolutePath))
|
|
112968
113449
|
return false;
|
|
112969
|
-
const parsed = import_yaml51.default.parse(await
|
|
113450
|
+
const parsed = import_yaml51.default.parse(await readFile94(absolutePath, "utf8"));
|
|
112970
113451
|
const strings = [];
|
|
112971
113452
|
collectStrings(parsed, strings);
|
|
112972
113453
|
return strings.some((value) => stringReferencesSource(value, input.source));
|
|
@@ -113092,8 +113573,8 @@ async function registryRemovalWrite(projectRoot, source2) {
|
|
|
113092
113573
|
const path3 = registryPath2(source2.type);
|
|
113093
113574
|
if (path3 === null)
|
|
113094
113575
|
return;
|
|
113095
|
-
const absolutePath =
|
|
113096
|
-
const document4 = existsSync31(absolutePath) ? import_yaml51.default.parse(await
|
|
113576
|
+
const absolutePath = join117(projectRoot, path3);
|
|
113577
|
+
const document4 = existsSync31(absolutePath) ? import_yaml51.default.parse(await readFile94(absolutePath, "utf8")) : { sources: [] };
|
|
113097
113578
|
return {
|
|
113098
113579
|
path: absolutePath,
|
|
113099
113580
|
bytes: import_yaml51.default.stringify(removeDocumentEntry(document4, source2))
|
|
@@ -113111,7 +113592,7 @@ function safeManagedMaterializedPath(projectRoot, source2) {
|
|
|
113111
113592
|
return absolute;
|
|
113112
113593
|
}
|
|
113113
113594
|
function safeManagedManifestPath(projectRoot, source2) {
|
|
113114
|
-
const manifest = source2.manifest ??
|
|
113595
|
+
const manifest = source2.manifest ?? join117(source2.materializedAt, "manifest.json");
|
|
113115
113596
|
if (isAbsolute22(manifest))
|
|
113116
113597
|
throw unsafeOwnership(source2, manifest);
|
|
113117
113598
|
const absolute = resolve39(projectRoot, manifest);
|
|
@@ -113240,7 +113721,7 @@ async function createRemovalPlan(projectRoot, selector) {
|
|
|
113240
113721
|
source: source2,
|
|
113241
113722
|
registry: registryPath2(source2.type),
|
|
113242
113723
|
registryBytes: registryWrite?.bytes ?? null,
|
|
113243
|
-
managedBytes: source2.type === "note" || source2.type === "sessions" ? await
|
|
113724
|
+
managedBytes: source2.type === "note" || source2.type === "sessions" ? await readFile94(absoluteRemovals[0], "utf8") : null,
|
|
113244
113725
|
references,
|
|
113245
113726
|
cleanup,
|
|
113246
113727
|
manifestBytes: manifestWrite?.bytes ?? null
|
|
@@ -113273,10 +113754,10 @@ function publicRemovalResult(plan, action) {
|
|
|
113273
113754
|
};
|
|
113274
113755
|
}
|
|
113275
113756
|
async function pruneExtractRuntime(projectRoot, source2) {
|
|
113276
|
-
const fingerprintPath =
|
|
113757
|
+
const fingerprintPath = join117(projectRoot, ".tmp/context-runtime/extract/source-fingerprints.json");
|
|
113277
113758
|
const removedPhaseIds = new Set;
|
|
113278
113759
|
if (existsSync31(fingerprintPath)) {
|
|
113279
|
-
const parsed = JSON.parse(await
|
|
113760
|
+
const parsed = JSON.parse(await readFile94(fingerprintPath, "utf8"));
|
|
113280
113761
|
const phases = parsed.phases ?? {};
|
|
113281
113762
|
const next2 = Object.fromEntries(Object.entries(phases).filter(([phaseId, raw]) => {
|
|
113282
113763
|
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
@@ -113290,27 +113771,27 @@ async function pruneExtractRuntime(projectRoot, source2) {
|
|
|
113290
113771
|
await atomicWriteFile(fingerprintPath, `${JSON.stringify({ ...parsed, phases: next2 }, null, 2)}
|
|
113291
113772
|
`);
|
|
113292
113773
|
}
|
|
113293
|
-
const phaseOwnershipPath =
|
|
113774
|
+
const phaseOwnershipPath = join117(projectRoot, ".tmp/context-runtime/extract/custom-phase-candidates.json");
|
|
113294
113775
|
if (existsSync31(phaseOwnershipPath) && removedPhaseIds.size > 0) {
|
|
113295
|
-
const parsed = JSON.parse(await
|
|
113776
|
+
const parsed = JSON.parse(await readFile94(phaseOwnershipPath, "utf8"));
|
|
113296
113777
|
const phases = Object.fromEntries(Object.entries(parsed.phases ?? {}).filter(([phaseId]) => !removedPhaseIds.has(phaseId)));
|
|
113297
113778
|
await atomicWriteFile(phaseOwnershipPath, `${JSON.stringify({ ...parsed, phases }, null, 2)}
|
|
113298
113779
|
`);
|
|
113299
113780
|
}
|
|
113300
|
-
const symbolPath =
|
|
113781
|
+
const symbolPath = join117(projectRoot, ".tmp/context-runtime/extract/source-symbols.json");
|
|
113301
113782
|
if (existsSync31(symbolPath)) {
|
|
113302
|
-
const parsed = JSON.parse(await
|
|
113783
|
+
const parsed = JSON.parse(await readFile94(symbolPath, "utf8"));
|
|
113303
113784
|
const symbols = Array.isArray(parsed.symbols) ? parsed.symbols.filter((entry) => entry === null || typeof entry !== "object" || Array.isArray(entry) || entry.source !== source2.name) : [];
|
|
113304
113785
|
const phaseFingerprints = Object.fromEntries(Object.entries(parsed.phaseFingerprints ?? {}).filter(([phaseId]) => !removedPhaseIds.has(phaseId)));
|
|
113305
113786
|
await atomicWriteFile(symbolPath, `${JSON.stringify({ ...parsed, phaseFingerprints, symbols }, null, 2)}
|
|
113306
113787
|
`);
|
|
113307
113788
|
}
|
|
113308
|
-
const snapshotRoot =
|
|
113789
|
+
const snapshotRoot = join117(projectRoot, ".tmp/context-runtime/extract/candidates");
|
|
113309
113790
|
const visit4 = async (directory) => {
|
|
113310
113791
|
if (!existsSync31(directory))
|
|
113311
113792
|
return;
|
|
113312
113793
|
for (const entry of await readdir29(directory, { withFileTypes: true })) {
|
|
113313
|
-
const path3 =
|
|
113794
|
+
const path3 = join117(directory, entry.name);
|
|
113314
113795
|
if (entry.isDirectory()) {
|
|
113315
113796
|
await visit4(path3);
|
|
113316
113797
|
continue;
|
|
@@ -113318,7 +113799,7 @@ async function pruneExtractRuntime(projectRoot, source2) {
|
|
|
113318
113799
|
if (!entry.isFile() || !entry.name.endsWith(".json"))
|
|
113319
113800
|
continue;
|
|
113320
113801
|
try {
|
|
113321
|
-
const parsed = JSON.parse(await
|
|
113802
|
+
const parsed = JSON.parse(await readFile94(path3, "utf8"));
|
|
113322
113803
|
const refs = Array.isArray(parsed.source_refs) ? parsed.source_refs : [];
|
|
113323
113804
|
if (parsed.source === source2.name || refs.some((ref2) => typeof ref2 === "string" && stringReferencesSource(ref2, source2))) {
|
|
113324
113805
|
await rm26(path3, { force: true });
|
|
@@ -113360,7 +113841,7 @@ async function removeProjectSource(input) {
|
|
|
113360
113841
|
});
|
|
113361
113842
|
}
|
|
113362
113843
|
await applyAtomicFileBatch({
|
|
113363
|
-
transactionRoot:
|
|
113844
|
+
transactionRoot: join117(input.projectRoot, ".tmp", "context-runtime", "source-remove-transactions"),
|
|
113364
113845
|
writes: [...plan.registryWrite === undefined ? [] : [plan.registryWrite], ...plan.manifestWrite !== undefined ? [plan.manifestWrite] : []],
|
|
113365
113846
|
removals: plan.absoluteRemovals
|
|
113366
113847
|
});
|
|
@@ -113376,7 +113857,7 @@ init_workspace();
|
|
|
113376
113857
|
init_writeLock();
|
|
113377
113858
|
init_durableMultiFileTransaction();
|
|
113378
113859
|
init_durableSingleFileTransaction();
|
|
113379
|
-
import { readFile as
|
|
113860
|
+
import { readFile as readFile95 } from "node:fs/promises";
|
|
113380
113861
|
import ts from "typescript";
|
|
113381
113862
|
function generateSourceConfiguration(text10, selected) {
|
|
113382
113863
|
const file = ts.createSourceFile("index.ts", text10, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
@@ -113516,7 +113997,7 @@ function generateSourceConfiguration(text10, selected) {
|
|
|
113516
113997
|
async function configureRegisteredSources(projectRoot, selected) {
|
|
113517
113998
|
return withProjectWriteLock(projectRoot, "source-project-configuration", async () => {
|
|
113518
113999
|
const path3 = "src/index.ts";
|
|
113519
|
-
const config = JSON.parse(await
|
|
114000
|
+
const config = JSON.parse(await readFile95(await safeProjectTarget(projectRoot, "package.json"), "utf8"));
|
|
113520
114001
|
if (config.context?.entry !== path3)
|
|
113521
114002
|
return {
|
|
113522
114003
|
status: "manual",
|
|
@@ -113525,7 +114006,7 @@ async function configureRegisteredSources(projectRoot, selected) {
|
|
|
113525
114006
|
sources: selected
|
|
113526
114007
|
};
|
|
113527
114008
|
const target = await safeProjectTarget(projectRoot, path3);
|
|
113528
|
-
const text10 = await
|
|
114009
|
+
const text10 = await readFile95(target, "utf8");
|
|
113529
114010
|
const updated = generateSourceConfiguration(text10, selected);
|
|
113530
114011
|
if (updated === undefined)
|
|
113531
114012
|
return {
|
|
@@ -113605,7 +114086,7 @@ async function readIncludeList(projectRoot, path3) {
|
|
|
113605
114086
|
}
|
|
113606
114087
|
let content3;
|
|
113607
114088
|
try {
|
|
113608
|
-
content3 = await
|
|
114089
|
+
content3 = await readFile99(isAbsolute23(trimmed) ? resolve41(trimmed) : resolve41(projectRoot, trimmed), "utf8");
|
|
113609
114090
|
} catch (error) {
|
|
113610
114091
|
throw new ContextError(ExitCode.UserError, `cannot read include list: ${trimmed}`, {
|
|
113611
114092
|
category: ErrorCategory.UserInputInvalid,
|
|
@@ -114027,7 +114508,7 @@ init_exitCode();
|
|
|
114027
114508
|
var import_yaml54 = __toESM(require_dist(), 1);
|
|
114028
114509
|
import { constants as constants6, existsSync as existsSync32 } from "node:fs";
|
|
114029
114510
|
import { lstat as lstat16, open as open5, readdir as readdir30 } from "node:fs/promises";
|
|
114030
|
-
import { dirname as dirname45, join as
|
|
114511
|
+
import { dirname as dirname45, join as join119, resolve as resolve42 } from "node:path";
|
|
114031
114512
|
import { fileURLToPath as fileURLToPath9 } from "node:url";
|
|
114032
114513
|
function bundledSkillRoot() {
|
|
114033
114514
|
const directory = dirname45(fileURLToPath9(import.meta.url));
|
|
@@ -114057,7 +114538,7 @@ async function readProductionSkills(root2) {
|
|
|
114057
114538
|
const entries2 = (await readdir30(root2, { withFileTypes: true })).filter((entry) => entry.isDirectory()).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
114058
114539
|
const skills = [];
|
|
114059
114540
|
for (const directory of entries2) {
|
|
114060
|
-
const entry =
|
|
114541
|
+
const entry = join119(root2, directory.name, "SKILL.md");
|
|
114061
114542
|
if (!(await lstat16(entry)).isFile())
|
|
114062
114543
|
throw new TypeError(`Skill entry must be a regular file: ${entry}`);
|
|
114063
114544
|
const handle3 = await open5(entry, constants6.O_RDONLY | constants6.O_NOFOLLOW | constants6.O_NONBLOCK);
|
|
@@ -114465,8 +114946,8 @@ init_cliFeedback();
|
|
|
114465
114946
|
init_errors3();
|
|
114466
114947
|
init_exitCode();
|
|
114467
114948
|
import { createHash as createHash37 } from "node:crypto";
|
|
114468
|
-
import { cp as cp2, lstat as lstat17, mkdir as mkdir39, mkdtemp as mkdtemp6, readFile as
|
|
114469
|
-
import { dirname as dirname47, join as
|
|
114949
|
+
import { cp as cp2, lstat as lstat17, mkdir as mkdir39, mkdtemp as mkdtemp6, readFile as readFile100, readdir as readdir31, rename as rename10, rm as rm27, writeFile as writeFile30 } from "node:fs/promises";
|
|
114950
|
+
import { dirname as dirname47, join as join120, resolve as resolve44 } from "node:path";
|
|
114470
114951
|
var MARKER = ".context-skill-install.json";
|
|
114471
114952
|
function conflict(path3) {
|
|
114472
114953
|
throw new ContextError(ExitCode.UserError, `Local skill install conflict: ${path3}`, {
|
|
@@ -114497,7 +114978,7 @@ async function digest8(root2) {
|
|
|
114497
114978
|
for (const name3 of (await readdir31(dir)).sort()) {
|
|
114498
114979
|
if (!prefix && name3 === MARKER)
|
|
114499
114980
|
continue;
|
|
114500
|
-
const path3 =
|
|
114981
|
+
const path3 = join120(dir, name3);
|
|
114501
114982
|
const entry = await lstat17(path3);
|
|
114502
114983
|
if (entry.isSymbolicLink())
|
|
114503
114984
|
conflict(path3);
|
|
@@ -114505,7 +114986,7 @@ async function digest8(root2) {
|
|
|
114505
114986
|
if (entry.isDirectory())
|
|
114506
114987
|
await visit4(path3, `${prefix}${name3}/`);
|
|
114507
114988
|
else if (entry.isFile())
|
|
114508
|
-
hash4.update(await
|
|
114989
|
+
hash4.update(await readFile100(path3));
|
|
114509
114990
|
else
|
|
114510
114991
|
conflict(path3);
|
|
114511
114992
|
}
|
|
@@ -114517,31 +114998,31 @@ async function installLocalSkills(root2, path3, dryRun, agent) {
|
|
|
114517
114998
|
if (!path3.trim())
|
|
114518
114999
|
conflict("--local requires a non-empty path");
|
|
114519
115000
|
const targetRoot = resolve44(path3);
|
|
114520
|
-
const skillsRoot =
|
|
115001
|
+
const skillsRoot = join120(targetRoot, "skills");
|
|
114521
115002
|
await checkParents(skillsRoot);
|
|
114522
|
-
const sourceRoot2 =
|
|
115003
|
+
const sourceRoot2 = join120(root2, "skills");
|
|
114523
115004
|
const commandPlans = [];
|
|
114524
115005
|
const commandEntries = new Set;
|
|
114525
115006
|
if (agent === "claude" || agent === "cursor") {
|
|
114526
|
-
for (const name3 of await readdir31(
|
|
115007
|
+
for (const name3 of await readdir31(join120(root2, "claude", "commands"))) {
|
|
114527
115008
|
if (name3.endsWith(".md"))
|
|
114528
115009
|
commandEntries.add(name3.slice(0, -3));
|
|
114529
115010
|
}
|
|
114530
|
-
const commandsRoot =
|
|
115011
|
+
const commandsRoot = join120(targetRoot, "commands");
|
|
114531
115012
|
await checkParents(commandsRoot);
|
|
114532
|
-
for (const entry of await readdir31(
|
|
115013
|
+
for (const entry of await readdir31(join120(root2, agent, "commands"), { withFileTypes: true })) {
|
|
114533
115014
|
if (!entry.isFile() || !entry.name.endsWith(".md"))
|
|
114534
115015
|
continue;
|
|
114535
|
-
let content3 = await
|
|
115016
|
+
let content3 = await readFile100(join120(root2, agent, "commands", entry.name), "utf8");
|
|
114536
115017
|
if (agent === "claude" && entry.name !== "context-plan.md") {
|
|
114537
115018
|
const frontmatter2 = /^(---\r?\n)([\s\S]*?)(\r?\n---(?:\r?\n|$))/.exec(content3);
|
|
114538
115019
|
if (!frontmatter2)
|
|
114539
|
-
conflict(
|
|
115020
|
+
conflict(join120(root2, agent, "commands", entry.name));
|
|
114540
115021
|
content3 = `${frontmatter2[1]}disable-model-invocation: true
|
|
114541
115022
|
${frontmatter2[2].replace(/^disable-model-invocation:.*\r?\n?/mu, "")}${frontmatter2[3]}${content3.slice(frontmatter2[0].length)}`;
|
|
114542
115023
|
}
|
|
114543
|
-
const target =
|
|
114544
|
-
const marker =
|
|
115024
|
+
const target = join120(commandsRoot, entry.name);
|
|
115025
|
+
const marker = join120(commandsRoot, `.context-${entry.name}.json`);
|
|
114545
115026
|
for (const file of [target, marker]) {
|
|
114546
115027
|
const existing = await stat11(file);
|
|
114547
115028
|
if (existing && (!existing.isFile() || existing.isSymbolicLink()))
|
|
@@ -114550,11 +115031,11 @@ ${frontmatter2[2].replace(/^disable-model-invocation:.*\r?\n?/mu, "")}${frontmat
|
|
|
114550
115031
|
if (await stat11(target)) {
|
|
114551
115032
|
let previous3;
|
|
114552
115033
|
try {
|
|
114553
|
-
previous3 = JSON.parse(await
|
|
115034
|
+
previous3 = JSON.parse(await readFile100(marker, "utf8"));
|
|
114554
115035
|
} catch {
|
|
114555
115036
|
conflict(target);
|
|
114556
115037
|
}
|
|
114557
|
-
const current2 = createHash37("sha256").update(await
|
|
115038
|
+
const current2 = createHash37("sha256").update(await readFile100(target)).digest("hex");
|
|
114558
115039
|
if (previous3?.owner !== "context-plugin-local" || previous3.digest !== current2)
|
|
114559
115040
|
conflict(target);
|
|
114560
115041
|
} else if (await stat11(marker))
|
|
@@ -114562,14 +115043,14 @@ ${frontmatter2[2].replace(/^disable-model-invocation:.*\r?\n?/mu, "")}${frontmat
|
|
|
114562
115043
|
commandPlans.push({ name: entry.name, target, marker, content: content3, hash: createHash37("sha256").update(content3).digest("hex") });
|
|
114563
115044
|
}
|
|
114564
115045
|
if (!commandPlans.length)
|
|
114565
|
-
conflict(
|
|
115046
|
+
conflict(join120(root2, agent, "commands"));
|
|
114566
115047
|
}
|
|
114567
115048
|
const plans = [];
|
|
114568
115049
|
for (const entry of (await readdir31(sourceRoot2, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
114569
|
-
if (!entry.isDirectory() || !await stat11(
|
|
115050
|
+
if (!entry.isDirectory() || !await stat11(join120(sourceRoot2, entry.name, "SKILL.md")))
|
|
114570
115051
|
continue;
|
|
114571
|
-
const source2 =
|
|
114572
|
-
const target =
|
|
115052
|
+
const source2 = join120(sourceRoot2, entry.name);
|
|
115053
|
+
const target = join120(skillsRoot, entry.name);
|
|
114573
115054
|
if (commandEntries.has(entry.name) && entry.name !== "context-plan") {
|
|
114574
115055
|
if (await stat11(target))
|
|
114575
115056
|
conflict(`${target} (use a fresh host directory to avoid duplicate command/skill entries)`);
|
|
@@ -114579,12 +115060,12 @@ ${frontmatter2[2].replace(/^disable-model-invocation:.*\r?\n?/mu, "")}${frontmat
|
|
|
114579
115060
|
if (existing) {
|
|
114580
115061
|
if (!existing.isDirectory() || existing.isSymbolicLink())
|
|
114581
115062
|
conflict(target);
|
|
114582
|
-
const markerPath =
|
|
115063
|
+
const markerPath = join120(target, MARKER);
|
|
114583
115064
|
if (!(await stat11(markerPath))?.isFile())
|
|
114584
115065
|
conflict(target);
|
|
114585
115066
|
let marker;
|
|
114586
115067
|
try {
|
|
114587
|
-
marker = JSON.parse(await
|
|
115068
|
+
marker = JSON.parse(await readFile100(markerPath, "utf8"));
|
|
114588
115069
|
} catch {
|
|
114589
115070
|
conflict(target);
|
|
114590
115071
|
}
|
|
@@ -114598,15 +115079,15 @@ ${frontmatter2[2].replace(/^disable-model-invocation:.*\r?\n?/mu, "")}${frontmat
|
|
|
114598
115079
|
if (!dryRun) {
|
|
114599
115080
|
await mkdir39(skillsRoot, { recursive: true });
|
|
114600
115081
|
for (const plan of plans) {
|
|
114601
|
-
const staging = await mkdtemp6(
|
|
114602
|
-
const candidate =
|
|
114603
|
-
const previous3 =
|
|
115082
|
+
const staging = await mkdtemp6(join120(skillsRoot, ".context-install-"));
|
|
115083
|
+
const candidate = join120(staging, "candidate");
|
|
115084
|
+
const previous3 = join120(staging, "previous");
|
|
114604
115085
|
let moved = false;
|
|
114605
115086
|
try {
|
|
114606
115087
|
await cp2(plan.source, candidate, { recursive: true });
|
|
114607
115088
|
if (agent === "claude" || agent === "cursor" && plan.name === "context-plan") {
|
|
114608
|
-
const skillFile =
|
|
114609
|
-
const content3 = await
|
|
115089
|
+
const skillFile = join120(candidate, "SKILL.md");
|
|
115090
|
+
const content3 = await readFile100(skillFile, "utf8");
|
|
114610
115091
|
const parts = /^(---\r?\n)([\s\S]*?)(\r?\n---(?:\r?\n|$))/.exec(content3);
|
|
114611
115092
|
if (parts && (plan.name === "context-plan" || /^\s*context-public-entry:\s*["']?false["']?\s*$/mu.test(parts[2]))) {
|
|
114612
115093
|
const frontmatter2 = parts[2].replace(/^user-invocable:.*\r?\n?/mu, "");
|
|
@@ -114614,7 +115095,7 @@ ${frontmatter2[2].replace(/^disable-model-invocation:.*\r?\n?/mu, "")}${frontmat
|
|
|
114614
115095
|
${frontmatter2}${parts[3]}${content3.slice(parts[0].length)}`);
|
|
114615
115096
|
}
|
|
114616
115097
|
}
|
|
114617
|
-
await writeFile30(
|
|
115098
|
+
await writeFile30(join120(candidate, MARKER), JSON.stringify({ owner: "context-plugin-local", digest: await digest8(candidate) }) + `
|
|
114618
115099
|
`);
|
|
114619
115100
|
if (plan.exists) {
|
|
114620
115101
|
await rename10(plan.target, previous3);
|
|
@@ -114638,12 +115119,12 @@ ${frontmatter2}${parts[3]}${content3.slice(parts[0].length)}`);
|
|
|
114638
115119
|
}
|
|
114639
115120
|
for (const command3 of commandPlans) {
|
|
114640
115121
|
await mkdir39(dirname47(command3.target), { recursive: true });
|
|
114641
|
-
const staging = await mkdtemp6(
|
|
114642
|
-
await writeFile30(
|
|
114643
|
-
await writeFile30(
|
|
115122
|
+
const staging = await mkdtemp6(join120(dirname47(command3.target), ".context-install-"));
|
|
115123
|
+
await writeFile30(join120(staging, "command"), command3.content);
|
|
115124
|
+
await writeFile30(join120(staging, "marker"), JSON.stringify({ owner: "context-plugin-local", digest: command3.hash }) + `
|
|
114644
115125
|
`);
|
|
114645
|
-
await rename10(
|
|
114646
|
-
await rename10(
|
|
115126
|
+
await rename10(join120(staging, "command"), command3.target);
|
|
115127
|
+
await rename10(join120(staging, "marker"), command3.marker);
|
|
114647
115128
|
await rm27(staging, { recursive: true, force: true });
|
|
114648
115129
|
}
|
|
114649
115130
|
}
|
|
@@ -114667,10 +115148,10 @@ init_cliFeedback();
|
|
|
114667
115148
|
init_errors3();
|
|
114668
115149
|
init_exitCode();
|
|
114669
115150
|
import { lstat as lstat18 } from "node:fs/promises";
|
|
114670
|
-
import { join as
|
|
115151
|
+
import { join as join121, resolve as resolve45 } from "node:path";
|
|
114671
115152
|
var HOST_DIRS = { claude: ".claude", cursor: ".cursor", codex: ".agents" };
|
|
114672
115153
|
async function detectLocalAgent(agent, repo) {
|
|
114673
|
-
const path3 =
|
|
115154
|
+
const path3 = join121(repo, HOST_DIRS[agent]);
|
|
114674
115155
|
try {
|
|
114675
115156
|
const entry = await lstat18(path3);
|
|
114676
115157
|
if (!entry.isDirectory() || entry.isSymbolicLink()) {
|
|
@@ -114695,7 +115176,7 @@ async function installAutoLocalSkills(root2, path3, dryRun, detect = detectLocal
|
|
|
114695
115176
|
if (selectedAgent ? selectedAgent === "all" || agent === selectedAgent : await detect(agent, repo))
|
|
114696
115177
|
agents.push(agent);
|
|
114697
115178
|
}
|
|
114698
|
-
const targets = agents.length ? agents.map((agent) => ({ agent, path:
|
|
115179
|
+
const targets = agents.length ? agents.map((agent) => ({ agent, path: join121(repo, HOST_DIRS[agent]) })) : [{ path: join121(repo, ".agents") }];
|
|
114699
115180
|
const previews = [];
|
|
114700
115181
|
for (const target of targets)
|
|
114701
115182
|
previews.push(await installLocalSkills(root2, target.path, true, target.agent));
|
|
@@ -114728,7 +115209,7 @@ init_cliFeedback();
|
|
|
114728
115209
|
init_errors3();
|
|
114729
115210
|
init_exitCode();
|
|
114730
115211
|
import { existsSync as existsSync35 } from "node:fs";
|
|
114731
|
-
import { dirname as dirname49, join as
|
|
115212
|
+
import { dirname as dirname49, join as join123, resolve as resolve46 } from "node:path";
|
|
114732
115213
|
import { fileURLToPath as fileURLToPath10 } from "node:url";
|
|
114733
115214
|
|
|
114734
115215
|
// src/project/pluginInstallTargets.ts
|
|
@@ -114737,9 +115218,9 @@ init_errors3();
|
|
|
114737
115218
|
init_exitCode();
|
|
114738
115219
|
import { execFile as execFile13 } from "node:child_process";
|
|
114739
115220
|
import { existsSync as existsSync34 } from "node:fs";
|
|
114740
|
-
import { cp as cp3, mkdir as mkdir40, readdir as readdir32, readFile as
|
|
115221
|
+
import { cp as cp3, mkdir as mkdir40, readdir as readdir32, readFile as readFile101, rename as rename11, rm as rm28, writeFile as writeFile31 } from "node:fs/promises";
|
|
114741
115222
|
import { homedir as homedir2 } from "node:os";
|
|
114742
|
-
import { dirname as dirname48, join as
|
|
115223
|
+
import { dirname as dirname48, join as join122 } from "node:path";
|
|
114743
115224
|
import { promisify as promisify13 } from "node:util";
|
|
114744
115225
|
var execFileAsync7 = promisify13(execFile13);
|
|
114745
115226
|
var MARKETPLACE_NAME = "c4a";
|
|
@@ -114793,23 +115274,23 @@ async function claudePluginInstalled(pluginId) {
|
|
|
114793
115274
|
}
|
|
114794
115275
|
}
|
|
114795
115276
|
function codexHome() {
|
|
114796
|
-
return process.env.CODEX_HOME?.trim() ||
|
|
115277
|
+
return process.env.CODEX_HOME?.trim() || join122(homedir2(), ".codex");
|
|
114797
115278
|
}
|
|
114798
115279
|
function claudePluginCacheRoot() {
|
|
114799
115280
|
const explicitRoot = process.env[CLAUDE_PLUGIN_CACHE_ROOT_ENV2]?.trim();
|
|
114800
115281
|
if (explicitRoot)
|
|
114801
115282
|
return explicitRoot;
|
|
114802
115283
|
const home = process.env[CLAUDE_PLUGIN_CACHE_HOME_ENV2]?.trim() || homedir2();
|
|
114803
|
-
return
|
|
115284
|
+
return join122(home, ".claude", "plugins", "cache");
|
|
114804
115285
|
}
|
|
114805
115286
|
function sharedSkillsRoot() {
|
|
114806
|
-
return process.env[SHARED_SKILLS_ROOT_ENV]?.trim() ||
|
|
115287
|
+
return process.env[SHARED_SKILLS_ROOT_ENV]?.trim() || join122(homedir2(), ".agents", "skills");
|
|
114807
115288
|
}
|
|
114808
115289
|
function claudeSkillsRoot() {
|
|
114809
|
-
return process.env[CLAUDE_SKILLS_ROOT_ENV]?.trim() ||
|
|
115290
|
+
return process.env[CLAUDE_SKILLS_ROOT_ENV]?.trim() || join122(homedir2(), ".claude", "skills");
|
|
114810
115291
|
}
|
|
114811
115292
|
function cursorPluginRoot() {
|
|
114812
|
-
return process.env[CURSOR_PLUGIN_ROOT_ENV]?.trim() ||
|
|
115293
|
+
return process.env[CURSOR_PLUGIN_ROOT_ENV]?.trim() || join122(homedir2(), ".cursor", "plugins", "local", PLUGIN_NAME);
|
|
114813
115294
|
}
|
|
114814
115295
|
function blockHeader(line) {
|
|
114815
115296
|
const match = line.match(/^\s*\[([^\]]+)\]\s*$/u);
|
|
@@ -114860,8 +115341,8 @@ function pruneLegacyCodexConfigContent(content3) {
|
|
|
114860
115341
|
`), removed };
|
|
114861
115342
|
}
|
|
114862
115343
|
async function pruneLegacyCodexConfig(dryRun, steps) {
|
|
114863
|
-
const configPath =
|
|
114864
|
-
const current2 = await
|
|
115344
|
+
const configPath = join122(codexHome(), "config.toml");
|
|
115345
|
+
const current2 = await readFile101(configPath, "utf8").catch(() => "");
|
|
114865
115346
|
if (!current2)
|
|
114866
115347
|
return;
|
|
114867
115348
|
const next2 = pruneLegacyCodexConfigContent(current2);
|
|
@@ -114877,7 +115358,7 @@ async function pruneLegacyCodexConfig(dryRun, steps) {
|
|
|
114877
115358
|
}
|
|
114878
115359
|
}
|
|
114879
115360
|
async function pruneCodexPluginCacheForName(pluginName, keepVersion, dryRun, steps) {
|
|
114880
|
-
const cacheRoot =
|
|
115361
|
+
const cacheRoot = join122(codexHome(), "plugins", "cache", MARKETPLACE_NAME, pluginName);
|
|
114881
115362
|
if (!existsSync34(cacheRoot))
|
|
114882
115363
|
return;
|
|
114883
115364
|
const versions = (await readdir32(cacheRoot, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory() && entry.name !== keepVersion).map((entry) => entry.name).sort();
|
|
@@ -114889,7 +115370,7 @@ async function pruneCodexPluginCacheForName(pluginName, keepVersion, dryRun, ste
|
|
|
114889
115370
|
status: dryRun ? "planned" : "ran"
|
|
114890
115371
|
});
|
|
114891
115372
|
if (!dryRun)
|
|
114892
|
-
await Promise.all(versions.map((version3) => rm28(
|
|
115373
|
+
await Promise.all(versions.map((version3) => rm28(join122(cacheRoot, version3), { recursive: true, force: true })));
|
|
114893
115374
|
}
|
|
114894
115375
|
async function pruneCodexPluginCache(currentVersion, dryRun, steps) {
|
|
114895
115376
|
await pruneCodexPluginCacheForName(PLUGIN_NAME, currentVersion, dryRun, steps);
|
|
@@ -114913,15 +115394,15 @@ async function pruneClaudeOrphanContextCache(dryRun, steps) {
|
|
|
114913
115394
|
for (const marketplace of marketplaces) {
|
|
114914
115395
|
if (!marketplace.isDirectory())
|
|
114915
115396
|
continue;
|
|
114916
|
-
const pluginDir =
|
|
115397
|
+
const pluginDir = join122(cacheRoot, marketplace.name, PLUGIN_NAME);
|
|
114917
115398
|
if (!existsSync34(pluginDir))
|
|
114918
115399
|
continue;
|
|
114919
115400
|
const versions = await readdir32(pluginDir, { withFileTypes: true }).catch(() => []);
|
|
114920
115401
|
for (const version3 of versions) {
|
|
114921
115402
|
if (!version3.isDirectory())
|
|
114922
115403
|
continue;
|
|
114923
|
-
const versionDir =
|
|
114924
|
-
if (!existsSync34(
|
|
115404
|
+
const versionDir = join122(pluginDir, version3.name);
|
|
115405
|
+
if (!existsSync34(join122(versionDir, ORPHAN_MARKER2)))
|
|
114925
115406
|
continue;
|
|
114926
115407
|
removed.push(`${marketplace.name}/${PLUGIN_NAME}/${version3.name}`);
|
|
114927
115408
|
if (!dryRun) {
|
|
@@ -114931,7 +115412,7 @@ async function pruneClaudeOrphanContextCache(dryRun, steps) {
|
|
|
114931
115412
|
if (!dryRun && await isEmptyDir2(pluginDir)) {
|
|
114932
115413
|
await rm28(pluginDir, { recursive: true, force: true });
|
|
114933
115414
|
}
|
|
114934
|
-
const marketplaceDir =
|
|
115415
|
+
const marketplaceDir = join122(cacheRoot, marketplace.name);
|
|
114935
115416
|
if (!dryRun && await isEmptyDir2(marketplaceDir)) {
|
|
114936
115417
|
await rm28(marketplaceDir, { recursive: true, force: true });
|
|
114937
115418
|
}
|
|
@@ -114952,7 +115433,7 @@ async function pruneClaudeLegacyPluginCache(dryRun, steps) {
|
|
|
114952
115433
|
return;
|
|
114953
115434
|
const removed = [];
|
|
114954
115435
|
for (const pluginName of LEGACY_PLUGIN_NAMES) {
|
|
114955
|
-
const pluginDir =
|
|
115436
|
+
const pluginDir = join122(cacheRoot, MARKETPLACE_NAME, pluginName);
|
|
114956
115437
|
if (!existsSync34(pluginDir))
|
|
114957
115438
|
continue;
|
|
114958
115439
|
removed.push(`${MARKETPLACE_NAME}/${pluginName}`);
|
|
@@ -114969,11 +115450,11 @@ async function pruneClaudeLegacyPluginCache(dryRun, steps) {
|
|
|
114969
115450
|
}
|
|
114970
115451
|
}
|
|
114971
115452
|
async function pruneClaudeSupersededContextCache(root2, dryRun, steps) {
|
|
114972
|
-
const manifest = await
|
|
115453
|
+
const manifest = await readFile101(join122(root2, "claude", ".claude-plugin", "plugin.json"), "utf8").then((content3) => JSON.parse(content3)).catch(() => null);
|
|
114973
115454
|
const currentVersion = typeof manifest?.version === "string" ? manifest.version : null;
|
|
114974
115455
|
if (!currentVersion)
|
|
114975
115456
|
return;
|
|
114976
|
-
const pluginDir =
|
|
115457
|
+
const pluginDir = join122(claudePluginCacheRoot(), MARKETPLACE_NAME, PLUGIN_NAME);
|
|
114977
115458
|
const staleVersions = (await readdir32(pluginDir, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory() && entry.name !== currentVersion).map((entry) => entry.name);
|
|
114978
115459
|
if (staleVersions.length === 0)
|
|
114979
115460
|
return;
|
|
@@ -114983,7 +115464,7 @@ async function pruneClaudeSupersededContextCache(root2, dryRun, steps) {
|
|
|
114983
115464
|
status: dryRun ? "planned" : "ran"
|
|
114984
115465
|
});
|
|
114985
115466
|
if (!dryRun) {
|
|
114986
|
-
await Promise.all(staleVersions.map((version3) => rm28(
|
|
115467
|
+
await Promise.all(staleVersions.map((version3) => rm28(join122(pluginDir, version3), { recursive: true, force: true })));
|
|
114987
115468
|
}
|
|
114988
115469
|
}
|
|
114989
115470
|
function enableCodexPluginConfig(content3) {
|
|
@@ -115047,33 +115528,33 @@ source = ${JSON.stringify(root2)}
|
|
|
115047
115528
|
`;
|
|
115048
115529
|
}
|
|
115049
115530
|
async function ensureCodexPluginEnabled() {
|
|
115050
|
-
const configPath =
|
|
115531
|
+
const configPath = join122(codexHome(), "config.toml");
|
|
115051
115532
|
await mkdir40(dirname48(configPath), { recursive: true });
|
|
115052
|
-
const current2 = await
|
|
115533
|
+
const current2 = await readFile101(configPath, "utf8").catch(() => "");
|
|
115053
115534
|
const next2 = enableCodexPluginConfig(current2);
|
|
115054
115535
|
if (next2 !== current2) {
|
|
115055
115536
|
await writeFile31(configPath, next2, "utf8");
|
|
115056
115537
|
}
|
|
115057
115538
|
}
|
|
115058
115539
|
async function ensureCodexLocalMarketplace(root2) {
|
|
115059
|
-
const configPath =
|
|
115540
|
+
const configPath = join122(codexHome(), "config.toml");
|
|
115060
115541
|
await mkdir40(dirname48(configPath), { recursive: true });
|
|
115061
|
-
const current2 = await
|
|
115542
|
+
const current2 = await readFile101(configPath, "utf8").catch(() => "");
|
|
115062
115543
|
const next2 = upsertCodexLocalMarketplaceConfig(current2, root2);
|
|
115063
115544
|
if (next2 !== current2) {
|
|
115064
115545
|
await writeFile31(configPath, next2, "utf8");
|
|
115065
115546
|
}
|
|
115066
115547
|
}
|
|
115067
115548
|
async function codexPluginVersion(root2) {
|
|
115068
|
-
const manifestPath =
|
|
115069
|
-
const manifest = JSON.parse(await
|
|
115549
|
+
const manifestPath = join122(root2, "codex", ".codex-plugin", "plugin.json");
|
|
115550
|
+
const manifest = JSON.parse(await readFile101(manifestPath, "utf8"));
|
|
115070
115551
|
if (typeof manifest.version !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/u.test(manifest.version)) {
|
|
115071
115552
|
throw new Error(`Codex plugin manifest has an invalid version: ${manifestPath}`);
|
|
115072
115553
|
}
|
|
115073
115554
|
return manifest.version;
|
|
115074
115555
|
}
|
|
115075
115556
|
function codexPluginCacheDir(version3) {
|
|
115076
|
-
return
|
|
115557
|
+
return join122(codexHome(), "plugins", "cache", MARKETPLACE_NAME, PLUGIN_NAME, version3);
|
|
115077
115558
|
}
|
|
115078
115559
|
async function replaceDirectoryFromSource(source2, target) {
|
|
115079
115560
|
await mkdir40(dirname48(target), { recursive: true });
|
|
@@ -115096,7 +115577,7 @@ async function replaceDirectoryFromSource(source2, target) {
|
|
|
115096
115577
|
}
|
|
115097
115578
|
}
|
|
115098
115579
|
async function materializeCodexPluginCache(root2, version3, dryRun, steps) {
|
|
115099
|
-
const source2 =
|
|
115580
|
+
const source2 = join122(root2, "codex");
|
|
115100
115581
|
const target = codexPluginCacheDir(version3);
|
|
115101
115582
|
steps.push({
|
|
115102
115583
|
agent: "codex",
|
|
@@ -115108,16 +115589,16 @@ async function materializeCodexPluginCache(root2, version3, dryRun, steps) {
|
|
|
115108
115589
|
await replaceDirectoryFromSource(source2, target);
|
|
115109
115590
|
}
|
|
115110
115591
|
async function bundledProviderSkillNames(root2) {
|
|
115111
|
-
const skillsRoot =
|
|
115592
|
+
const skillsRoot = join122(root2, "skills");
|
|
115112
115593
|
const entries2 = await readdir32(skillsRoot, { withFileTypes: true });
|
|
115113
115594
|
const names = [];
|
|
115114
115595
|
for (const entry of entries2) {
|
|
115115
115596
|
if (!entry.isDirectory() || entry.name === "context")
|
|
115116
115597
|
continue;
|
|
115117
|
-
const skillPath =
|
|
115598
|
+
const skillPath = join122(skillsRoot, entry.name, "SKILL.md");
|
|
115118
115599
|
if (!existsSync34(skillPath))
|
|
115119
115600
|
continue;
|
|
115120
|
-
const skill = await
|
|
115601
|
+
const skill = await readFile101(skillPath, "utf8");
|
|
115121
115602
|
if (!/^\s*context-role:\s*["']?indexer-provider["']?\s*$/mu.test(skill))
|
|
115122
115603
|
continue;
|
|
115123
115604
|
names.push(entry.name);
|
|
@@ -115129,10 +115610,10 @@ async function bundledProviderSkillNames(root2) {
|
|
|
115129
115610
|
return names;
|
|
115130
115611
|
}
|
|
115131
115612
|
async function materializeProviderSkills(root2, targetRoot, agent, dryRun, steps) {
|
|
115132
|
-
const sourceRoot2 =
|
|
115613
|
+
const sourceRoot2 = join122(root2, "skills");
|
|
115133
115614
|
for (const name3 of await bundledProviderSkillNames(root2)) {
|
|
115134
|
-
const source2 =
|
|
115135
|
-
const target =
|
|
115615
|
+
const source2 = join122(sourceRoot2, name3);
|
|
115616
|
+
const target = join122(targetRoot, name3);
|
|
115136
115617
|
steps.push({
|
|
115137
115618
|
agent,
|
|
115138
115619
|
command: `materialize lifecycle Provider skill: ${shellQuote8(source2)} -> ${shellQuote8(target)}`,
|
|
@@ -115143,7 +115624,7 @@ async function materializeProviderSkills(root2, targetRoot, agent, dryRun, steps
|
|
|
115143
115624
|
}
|
|
115144
115625
|
}
|
|
115145
115626
|
async function installCursor(root2, dryRun, steps) {
|
|
115146
|
-
const source2 =
|
|
115627
|
+
const source2 = join122(root2, "cursor");
|
|
115147
115628
|
const target = cursorPluginRoot();
|
|
115148
115629
|
steps.push({
|
|
115149
115630
|
agent: "cursor",
|
|
@@ -115198,12 +115679,12 @@ async function installCodex(root2, dryRun, steps) {
|
|
|
115198
115679
|
steps.push({ agent: "codex", command: commandLine("codex", addArgs), status: dryRun ? "planned" : "ran" });
|
|
115199
115680
|
steps.push({
|
|
115200
115681
|
agent: "codex",
|
|
115201
|
-
command: `ensure ${shellQuote8(
|
|
115682
|
+
command: `ensure ${shellQuote8(join122(codexHome(), "config.toml"))} registers local marketplace ${shellQuote8(MARKETPLACE_NAME)}`,
|
|
115202
115683
|
status: dryRun ? "planned" : "ran"
|
|
115203
115684
|
});
|
|
115204
115685
|
steps.push({
|
|
115205
115686
|
agent: "codex",
|
|
115206
|
-
command: `ensure ${shellQuote8(
|
|
115687
|
+
command: `ensure ${shellQuote8(join122(codexHome(), "config.toml"))} enables ${shellQuote8(PLUGIN_ID)}`,
|
|
115207
115688
|
status: dryRun ? "planned" : "ran"
|
|
115208
115689
|
});
|
|
115209
115690
|
if (dryRun) {
|
|
@@ -115258,13 +115739,13 @@ function pluginRootCandidates() {
|
|
|
115258
115739
|
return [resolve46(envRoot)];
|
|
115259
115740
|
const candidates = [];
|
|
115260
115741
|
for (const dir of packageCandidateDirs()) {
|
|
115261
|
-
candidates.push(
|
|
115262
|
-
candidates.push(
|
|
115742
|
+
candidates.push(join123(dir, "plugins"));
|
|
115743
|
+
candidates.push(join123(dir, "dist", "plugins"));
|
|
115263
115744
|
}
|
|
115264
115745
|
return [...new Set(candidates)];
|
|
115265
115746
|
}
|
|
115266
115747
|
function isInstallablePluginRoot(root2) {
|
|
115267
|
-
return existsSync35(
|
|
115748
|
+
return existsSync35(join123(root2, ".claude-plugin", "marketplace.json")) && existsSync35(join123(root2, ".agents", "plugins", "marketplace.json")) && existsSync35(join123(root2, "claude", ".claude-plugin", "plugin.json")) && existsSync35(join123(root2, "codex", ".codex-plugin", "plugin.json")) && existsSync35(join123(root2, "cursor", ".cursor-plugin", "plugin.json")) && existsSync35(join123(root2, "skills"));
|
|
115268
115749
|
}
|
|
115269
115750
|
function resolveBundledPluginsRoot() {
|
|
115270
115751
|
const candidates = pluginRootCandidates();
|
|
@@ -115511,10 +115992,10 @@ function readQuickstartPath() {
|
|
|
115511
115992
|
try {
|
|
115512
115993
|
let dir = dirname50(fileURLToPath11(import.meta.url));
|
|
115513
115994
|
for (let i2 = 0;i2 < 8; i2++) {
|
|
115514
|
-
const candidate =
|
|
115995
|
+
const candidate = join124(dir, "docs", "quickstart.md");
|
|
115515
115996
|
if (existsSync36(candidate))
|
|
115516
115997
|
return candidate;
|
|
115517
|
-
const pkg =
|
|
115998
|
+
const pkg = join124(dir, "package.json");
|
|
115518
115999
|
if (existsSync36(pkg))
|
|
115519
116000
|
return candidate;
|
|
115520
116001
|
const parent = dirname50(dir);
|
|
@@ -115523,7 +116004,7 @@ function readQuickstartPath() {
|
|
|
115523
116004
|
dir = parent;
|
|
115524
116005
|
}
|
|
115525
116006
|
} catch {}
|
|
115526
|
-
return
|
|
116007
|
+
return join124(dirname50(fileURLToPath11(import.meta.url)), "docs", "quickstart.md");
|
|
115527
116008
|
}
|
|
115528
116009
|
var GREEN = "\x1B[32m";
|
|
115529
116010
|
var RESET = "\x1B[0m";
|
|
@@ -115653,7 +116134,7 @@ function createCliProgram() {
|
|
|
115653
116134
|
format: options.format === "json" ? "json" : "text"
|
|
115654
116135
|
});
|
|
115655
116136
|
});
|
|
115656
|
-
review.command("apply <payload-file>").description("Apply
|
|
116137
|
+
review.command("apply <payload-file>").description("Apply scoped JSON/JSONL decisions and revision instructions").option("--format <format>", "output format: text | json", "text").action(async (payloadInput, options) => {
|
|
115657
116138
|
if (options.format !== "text" && options.format !== "json") {
|
|
115658
116139
|
throw new ContextError(ExitCode.UserError, "--format must be text or json", {
|
|
115659
116140
|
category: ErrorCategory.UserInputInvalid
|
|
@@ -115674,7 +116155,7 @@ function createCliProgram() {
|
|
|
115674
116155
|
})
|
|
115675
116156
|
});
|
|
115676
116157
|
});
|
|
115677
|
-
review.command("approve-all [collection]").description("Approve the complete current review scope under explicit managed or user
|
|
116158
|
+
review.command("approve-all [collection]").description("Approve the complete current review scope under explicit managed authority or user confirmation").option("--all", "approve all current draft candidates across internal collections").option("--managed", "assert explicit current-conversation managed approval").option("--force", "deprecated compatibility flag; use --confirmed for user approval").option("--confirmed", "apply explicit user confirmation to the current report scope").option("--verbose", "include candidate ids and materialized page paths in JSON output").option("--format <format>", "output format: text | json", "text").action(async (collection, options) => {
|
|
115678
116159
|
if (options.format !== "text" && options.format !== "json") {
|
|
115679
116160
|
throw new ContextError(ExitCode.UserError, "--format must be text or json", {
|
|
115680
116161
|
category: ErrorCategory.UserInputInvalid
|
|
@@ -115685,6 +116166,7 @@ function createCliProgram() {
|
|
|
115685
116166
|
...collection !== undefined ? { collection } : {},
|
|
115686
116167
|
...options.all === true ? { all: true } : {},
|
|
115687
116168
|
managed: options.managed === true,
|
|
116169
|
+
confirmed: options.confirmed === true,
|
|
115688
116170
|
force: options.force === true,
|
|
115689
116171
|
verbose: options.verbose === true,
|
|
115690
116172
|
format: options.format === "json" ? "json" : "text",
|