@imenam/mcp-documentor 1.0.87 → 1.0.88
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/README.md +7 -5
- package/dist/shared/read-only.js +1 -1
- package/dist/shared/schemas/doc.schema.d.ts +1 -0
- package/dist/shared/schemas/doc.schema.js +1 -0
- package/dist/src/core/code-source.d.ts +32 -0
- package/dist/src/core/code-source.js +23 -4
- package/dist/src/core/git-sync.d.ts +5 -0
- package/dist/src/core/git-sync.js +29 -2
- package/dist/src/core/github-code-api.d.ts +3 -8
- package/dist/src/core/github-code-api.js +67 -36
- package/dist/src/core/github-log.d.ts +38 -0
- package/dist/src/core/github-log.js +64 -0
- package/dist/src/core/log-reader.d.ts +47 -0
- package/dist/src/core/log-reader.js +106 -0
- package/dist/src/core/storage-engine.d.ts +21 -6
- package/dist/src/core/storage-engine.js +80 -24
- package/dist/src/gui-worker.js +58 -1
- package/dist/src/index.js +75 -10
- package/dist/src/tools/code-sources.d.ts +9 -0
- package/dist/src/tools/code-sources.js +18 -16
- package/dist/src/tools/create-project.d.ts +19 -0
- package/dist/src/tools/create-project.js +42 -0
- package/dist/src/tools/delete-project.d.ts +16 -0
- package/dist/src/tools/delete-project.js +25 -0
- package/dist/src/tools/git-diff-doc.js +123 -98
- package/dist/src/tools/patch-doc.js +14 -3
- package/gui/dist/assets/{arc-DBgCQU2O.js → arc-s4GL8QZ_.js} +1 -1
- package/gui/dist/assets/{architectureDiagram-3BPJPVTR-BIPBAzZr.js → architectureDiagram-3BPJPVTR-CfDERg0r.js} +1 -1
- package/gui/dist/assets/{blockDiagram-GPEHLZMM-BE9WwuyF.js → blockDiagram-GPEHLZMM-CjDoEG3-.js} +1 -1
- package/gui/dist/assets/{c4Diagram-AAUBKEIU-V1_4ktIt.js → c4Diagram-AAUBKEIU-9o7f2UhK.js} +1 -1
- package/gui/dist/assets/channel-BsKHRH5P.js +1 -0
- package/gui/dist/assets/{chunk-2J33WTMH-DskG-Kmf.js → chunk-2J33WTMH-B37ouRYO.js} +1 -1
- package/gui/dist/assets/{chunk-4BX2VUAB-DaPhidEI.js → chunk-4BX2VUAB-Dcq44Skv.js} +1 -1
- package/gui/dist/assets/{chunk-55IACEB6-C9r6WDFM.js → chunk-55IACEB6-CLyM-RQf.js} +1 -1
- package/gui/dist/assets/{chunk-727SXJPM-BA-aC8US.js → chunk-727SXJPM-BjzEo2iz.js} +1 -1
- package/gui/dist/assets/{chunk-AQP2D5EJ-ByBp3whV.js → chunk-AQP2D5EJ-Co03OHPi.js} +1 -1
- package/gui/dist/assets/{chunk-FMBD7UC4-CzqUByx7.js → chunk-FMBD7UC4-ClbF2rAp.js} +1 -1
- package/gui/dist/assets/{chunk-ND2GUHAM-C0_B2ryQ.js → chunk-ND2GUHAM-CN5cTCWu.js} +1 -1
- package/gui/dist/assets/{chunk-QZHKN3VN-B3eEwjDh.js → chunk-QZHKN3VN-Bg4-kEPK.js} +1 -1
- package/gui/dist/assets/classDiagram-4FO5ZUOK-DphzXyc8.js +1 -0
- package/gui/dist/assets/classDiagram-v2-Q7XG4LA2-DphzXyc8.js +1 -0
- package/gui/dist/assets/{cose-bilkent-S5V4N54A-D_i0I2pU.js → cose-bilkent-S5V4N54A-6Dkp59bE.js} +1 -1
- package/gui/dist/assets/{dagre-BM42HDAG-DtEVPK_z.js → dagre-BM42HDAG-2dD-ID2T.js} +1 -1
- package/gui/dist/assets/{diagram-2AECGRRQ-BftnXa5w.js → diagram-2AECGRRQ-DBE2f2Lb.js} +1 -1
- package/gui/dist/assets/{diagram-5GNKFQAL-Dh9-kl_u.js → diagram-5GNKFQAL-CkKPHiH9.js} +1 -1
- package/gui/dist/assets/{diagram-KO2AKTUF-CvB0BWCa.js → diagram-KO2AKTUF-uWtGAF2N.js} +1 -1
- package/gui/dist/assets/{diagram-LMA3HP47-CDy-J1uy.js → diagram-LMA3HP47-DyQaWg_0.js} +1 -1
- package/gui/dist/assets/{diagram-OG6HWLK6-iY9FwkIF.js → diagram-OG6HWLK6-H_DfuB5t.js} +1 -1
- package/gui/dist/assets/{erDiagram-TEJ5UH35-BT2_IX8D.js → erDiagram-TEJ5UH35-Cj9K5EXA.js} +1 -1
- package/gui/dist/assets/{flowDiagram-I6XJVG4X-DaixWyMx.js → flowDiagram-I6XJVG4X-BYF24Jvt.js} +1 -1
- package/gui/dist/assets/{ganttDiagram-6RSMTGT7-DSo3cQPH.js → ganttDiagram-6RSMTGT7-D93LjmhI.js} +1 -1
- package/gui/dist/assets/{gitGraphDiagram-PVQCEYII-BfFT7Lds.js → gitGraphDiagram-PVQCEYII-EVTcusDb.js} +1 -1
- package/gui/dist/assets/index-CtPuh-0k.css +1 -0
- package/gui/dist/assets/{index-DPGmAc6z.js → index-DvVxuv5N.js} +154 -152
- package/gui/dist/assets/{infoDiagram-5YYISTIA-BxbxNxe2.js → infoDiagram-5YYISTIA-DeGbaexj.js} +1 -1
- package/gui/dist/assets/{ishikawaDiagram-YF4QCWOH-DpopzqZr.js → ishikawaDiagram-YF4QCWOH-DNdk7sPJ.js} +1 -1
- package/gui/dist/assets/{journeyDiagram-JHISSGLW-DrOlFzIk.js → journeyDiagram-JHISSGLW-yLLfyn3W.js} +1 -1
- package/gui/dist/assets/{kanban-definition-UN3LZRKU-vH81lYXy.js → kanban-definition-UN3LZRKU-D8yvVgr8.js} +1 -1
- package/gui/dist/assets/{linear-BipFpFah.js → linear-j3lplktA.js} +1 -1
- package/gui/dist/assets/{mindmap-definition-RKZ34NQL-DsTxCLgj.js → mindmap-definition-RKZ34NQL-t9adVeMH.js} +1 -1
- package/gui/dist/assets/{pieDiagram-4H26LBE5-Bbq2ZWQU.js → pieDiagram-4H26LBE5-6Yrvo8h8.js} +1 -1
- package/gui/dist/assets/{quadrantDiagram-W4KKPZXB-BdTSaEo4.js → quadrantDiagram-W4KKPZXB-C_3Su6rX.js} +1 -1
- package/gui/dist/assets/{requirementDiagram-4Y6WPE33-BndI8YZy.js → requirementDiagram-4Y6WPE33-B1qYSC11.js} +1 -1
- package/gui/dist/assets/{sankeyDiagram-5OEKKPKP-ofBkVITf.js → sankeyDiagram-5OEKKPKP-D3Ervn-u.js} +1 -1
- package/gui/dist/assets/{sequenceDiagram-3UESZ5HK-Cjj2UrN9.js → sequenceDiagram-3UESZ5HK-CKgTeWBS.js} +1 -1
- package/gui/dist/assets/{stateDiagram-AJRCARHV-CzPrXUuz.js → stateDiagram-AJRCARHV-D_PjEdFH.js} +1 -1
- package/gui/dist/assets/stateDiagram-v2-BHNVJYJU-KPbsissC.js +1 -0
- package/gui/dist/assets/{timeline-definition-PNZ67QCA-Cz4X-IOO.js → timeline-definition-PNZ67QCA-HeWRiNHu.js} +1 -1
- package/gui/dist/assets/{vennDiagram-CIIHVFJN-BwIgXJcZ.js → vennDiagram-CIIHVFJN-CREk7vzm.js} +1 -1
- package/gui/dist/assets/{wardley-L42UT6IY-keQS0aNZ.js → wardley-L42UT6IY-BWtRQ7ZH.js} +1 -1
- package/gui/dist/assets/{wardleyDiagram-YWT4CUSO-DhykSgbD.js → wardleyDiagram-YWT4CUSO-BvlqWWwp.js} +1 -1
- package/gui/dist/assets/{xychartDiagram-2RQKCTM6-DFTqc4cj.js → xychartDiagram-2RQKCTM6-BCjv6pwc.js} +1 -1
- package/gui/dist/index.html +2 -2
- package/package.json +2 -1
- package/gui/dist/assets/channel-B_cci0fU.js +0 -1
- package/gui/dist/assets/classDiagram-4FO5ZUOK-BqlcJ5BP.js +0 -1
- package/gui/dist/assets/classDiagram-v2-Q7XG4LA2-BqlcJ5BP.js +0 -1
- package/gui/dist/assets/index-CxCC1gxu.css +0 -1
- package/gui/dist/assets/stateDiagram-v2-BHNVJYJU-DAq4ObN9.js +0 -1
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { StorageEngine } from "../core/storage-engine.js";
|
|
2
|
+
import type { LocalState } from "../core/local-state.js";
|
|
3
|
+
/**
|
|
4
|
+
* Creates an empty project, with its code source when one is given. The source is checked before
|
|
5
|
+
* anything is written, so an unknown repository or branch creates nothing.
|
|
6
|
+
*/
|
|
7
|
+
export declare function createProject(storageEngine: StorageEngine, localState: LocalState, args: unknown): Promise<{
|
|
8
|
+
isError: boolean;
|
|
9
|
+
content: {
|
|
10
|
+
type: string;
|
|
11
|
+
text: string;
|
|
12
|
+
}[];
|
|
13
|
+
} | {
|
|
14
|
+
content: {
|
|
15
|
+
type: string;
|
|
16
|
+
text: string;
|
|
17
|
+
}[];
|
|
18
|
+
isError?: undefined;
|
|
19
|
+
}>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { describeCodeSourceConfig, resolveCodeSourceInput, saveCodeSource } from "./code-sources.js";
|
|
3
|
+
const CreateProjectArgsSchema = z.object({
|
|
4
|
+
project: z
|
|
5
|
+
.string()
|
|
6
|
+
.trim()
|
|
7
|
+
.min(1)
|
|
8
|
+
.refine((name) => name !== "." && name !== ".." && !/[\\/:]/.test(name), {
|
|
9
|
+
message: 'Project names cannot be "." or "..", nor contain "/", "\\" or ":".',
|
|
10
|
+
})
|
|
11
|
+
.describe("Name of the project to create."),
|
|
12
|
+
code_source: z
|
|
13
|
+
.object({
|
|
14
|
+
kind: z.enum(["local", "github"]),
|
|
15
|
+
repo: z.string().optional(),
|
|
16
|
+
branch: z.string().optional(),
|
|
17
|
+
})
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("Where the project's code is read from, as with set_code_source. Omitted: the local working copy."),
|
|
20
|
+
});
|
|
21
|
+
/**
|
|
22
|
+
* Creates an empty project, with its code source when one is given. The source is checked before
|
|
23
|
+
* anything is written, so an unknown repository or branch creates nothing.
|
|
24
|
+
*/
|
|
25
|
+
export async function createProject(storageEngine, localState, args) {
|
|
26
|
+
try {
|
|
27
|
+
const { project, code_source } = CreateProjectArgsSchema.parse(args);
|
|
28
|
+
if (storageEngine.getConfig().projects.includes(project)) {
|
|
29
|
+
return { isError: true, content: [{ type: "text", text: `Project "${project}" already exists.` }] };
|
|
30
|
+
}
|
|
31
|
+
const config = code_source ? await resolveCodeSourceInput(code_source) : undefined;
|
|
32
|
+
storageEngine.ensureProject(project);
|
|
33
|
+
if (config)
|
|
34
|
+
saveCodeSource(storageEngine, localState, project, config);
|
|
35
|
+
const source = config ? ` It reads its code from ${describeCodeSourceConfig(config)}.` : "";
|
|
36
|
+
return { content: [{ type: "text", text: `Project "${project}" created.${source}` }] };
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
40
|
+
return { isError: true, content: [{ type: "text", text: `Error creating project: ${message}` }] };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { StorageEngine } from "../core/storage-engine.js";
|
|
2
|
+
import type { LocalState } from "../core/local-state.js";
|
|
3
|
+
/** Deletes an empty project, along with this machine's code root for it. */
|
|
4
|
+
export declare function deleteProject(storageEngine: StorageEngine, localState: LocalState, args: unknown): Promise<{
|
|
5
|
+
content: {
|
|
6
|
+
type: string;
|
|
7
|
+
text: string;
|
|
8
|
+
}[];
|
|
9
|
+
isError?: undefined;
|
|
10
|
+
} | {
|
|
11
|
+
isError: boolean;
|
|
12
|
+
content: {
|
|
13
|
+
type: string;
|
|
14
|
+
text: string;
|
|
15
|
+
}[];
|
|
16
|
+
}>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { invalidateCodeSources } from "../core/code-source.js";
|
|
3
|
+
import { assertProject, scopeErrorResult } from "../core/project-scope.js";
|
|
4
|
+
const DeleteProjectArgsSchema = z.object({
|
|
5
|
+
project: z.string().min(1).describe("Project to delete. It must be empty: no doc, no image, no other file."),
|
|
6
|
+
});
|
|
7
|
+
/** Deletes an empty project, along with this machine's code root for it. */
|
|
8
|
+
export async function deleteProject(storageEngine, localState, args) {
|
|
9
|
+
try {
|
|
10
|
+
const { project } = DeleteProjectArgsSchema.parse(args);
|
|
11
|
+
const projectError = assertProject(storageEngine, project);
|
|
12
|
+
if (projectError)
|
|
13
|
+
return scopeErrorResult(projectError);
|
|
14
|
+
storageEngine.deleteProject(project);
|
|
15
|
+
localState.setProjectCodeRoot(project, undefined);
|
|
16
|
+
invalidateCodeSources(project);
|
|
17
|
+
return {
|
|
18
|
+
content: [{ type: "text", text: `Project "${project}" deleted.` }],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
23
|
+
return { isError: true, content: [{ type: "text", text: `Error deleting project: ${message}` }] };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -1,117 +1,144 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFileSync } from "child_process";
|
|
2
2
|
import path from "path";
|
|
3
|
+
import { createTwoFilesPatch } from "diff";
|
|
3
4
|
import { GitDiffDocArgsSchema } from "../../shared/schemas/doc.schema.js";
|
|
4
5
|
import { computeFreshness } from "./check-freshness.js";
|
|
5
6
|
import { toAbsolutePath } from "../core/path-resolver.js";
|
|
6
|
-
import { getCodeSource,
|
|
7
|
+
import { getCodeSource, getGitHubSource, toStoredPathForMatching } from "../core/code-source.js";
|
|
8
|
+
import { bytesToFileContent, fileContentMatches, findAndHashSection } from "../core/section-tracker.js";
|
|
9
|
+
import { mapWithConcurrency } from "../core/concurrency.js";
|
|
7
10
|
import { assertProject, scopeErrorResult } from "../core/project-scope.js";
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
/** Tracked files of one doc diffed at once. */
|
|
12
|
+
const FILE_CONCURRENCY = 4;
|
|
13
|
+
const errorMessage = (err) => (err instanceof Error ? err.message : String(err));
|
|
14
|
+
const short = (sha) => sha.slice(0, 7);
|
|
15
|
+
/**
|
|
16
|
+
* Snapshot content on GitHub, by decreasing reliability: the recorded blob (independent of any
|
|
17
|
+
* commit, so it survives a rebase), the file at the recorded commit, or the file at the newest
|
|
18
|
+
* commit on the branch at or before the snapshot date.
|
|
19
|
+
*/
|
|
20
|
+
async function githubSnapshotBase(source, tracked) {
|
|
21
|
+
const repo = source.api.label;
|
|
22
|
+
if (tracked.sourceBlobId) {
|
|
23
|
+
try {
|
|
24
|
+
return { content: await source.readBlob(tracked.sourceBlobId, tracked.path), origin: `snapshot blob ${short(tracked.sourceBlobId)}`, inferred: false };
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
return { error: `The snapshot content (blob ${short(tracked.sourceBlobId)}) cannot be read on ${repo}: ${errorMessage(err)}` };
|
|
28
|
+
}
|
|
13
29
|
}
|
|
14
|
-
|
|
15
|
-
|
|
30
|
+
let commit = tracked.sourceRevision;
|
|
31
|
+
const inferred = !commit;
|
|
32
|
+
if (!commit) {
|
|
33
|
+
try {
|
|
34
|
+
commit = await source.api.resolveCommitAtDate(tracked.lastModifiedAt);
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
return { error: `No source commit recorded, and the commit at the snapshot date (${tracked.lastModifiedAt}) could not be resolved on ${repo}: ${errorMessage(err)}` };
|
|
38
|
+
}
|
|
39
|
+
if (!commit) {
|
|
40
|
+
return { error: `No source commit recorded, and ${repo} has no commit at or before the snapshot date (${tracked.lastModifiedAt}).` };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const origin = inferred ? `commit ${short(commit)} inferred from the snapshot date` : `snapshot commit ${short(commit)}`;
|
|
16
44
|
try {
|
|
17
|
-
|
|
45
|
+
const content = await source.readAt(tracked.path, commit);
|
|
46
|
+
if (!content)
|
|
47
|
+
return { error: `The file does not exist at ${origin} on ${repo}.` };
|
|
48
|
+
return { content, origin, inferred };
|
|
18
49
|
}
|
|
19
|
-
catch {
|
|
20
|
-
return `
|
|
50
|
+
catch (err) {
|
|
51
|
+
return { error: `The ${origin} cannot be read on ${repo}: ${errorMessage(err)}` };
|
|
21
52
|
}
|
|
22
|
-
|
|
23
|
-
|
|
53
|
+
}
|
|
54
|
+
function git(repoRoot, args) {
|
|
55
|
+
return execFileSync("git", ["-C", repoRoot, ...args], { stdio: ["ignore", "pipe", "pipe"], maxBuffer: 64 * 1024 * 1024 });
|
|
56
|
+
}
|
|
57
|
+
/** Snapshot content in the local clone, with the same order of preference as on GitHub. */
|
|
58
|
+
function localSnapshotBase(tracked, project) {
|
|
59
|
+
const filePath = toAbsolutePath(tracked.path, project);
|
|
60
|
+
if (!filePath)
|
|
61
|
+
return { error: `Cannot resolve "${tracked.path}" on this machine (no code root configured).` };
|
|
62
|
+
let repoRoot;
|
|
24
63
|
try {
|
|
25
|
-
|
|
64
|
+
repoRoot = git(path.dirname(filePath), ["rev-parse", "--show-toplevel"]).toString("utf-8").trim();
|
|
26
65
|
}
|
|
27
66
|
catch {
|
|
28
|
-
return
|
|
67
|
+
return { error: `Not a git repository: ${filePath}` };
|
|
29
68
|
}
|
|
30
|
-
|
|
31
|
-
|
|
69
|
+
const relativeFilePath = path.relative(repoRoot, filePath).replace(/\\/g, "/");
|
|
70
|
+
if (tracked.sourceBlobId) {
|
|
71
|
+
try {
|
|
72
|
+
return { content: bytesToFileContent(git(repoRoot, ["cat-file", "blob", tracked.sourceBlobId])), origin: `snapshot blob ${short(tracked.sourceBlobId)}`, inferred: false };
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return { error: `The snapshot content (blob ${short(tracked.sourceBlobId)}) is not in the local repository ${repoRoot}.` };
|
|
76
|
+
}
|
|
32
77
|
}
|
|
33
|
-
let
|
|
78
|
+
let commit = tracked.sourceRevision;
|
|
79
|
+
const inferred = !commit;
|
|
80
|
+
if (!commit) {
|
|
81
|
+
try {
|
|
82
|
+
commit = git(repoRoot, ["log", `--before=${tracked.lastModifiedAt}`, "-1", "--format=%H", "--", relativeFilePath]).toString("utf-8").trim();
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return { error: `Could not read the git log of ${filePath}.` };
|
|
86
|
+
}
|
|
87
|
+
if (!commit)
|
|
88
|
+
return { error: `No commit touches ${relativeFilePath} at or before the snapshot date (${tracked.lastModifiedAt}).` };
|
|
89
|
+
}
|
|
90
|
+
const origin = inferred ? `commit ${short(commit)} inferred from the snapshot date` : `snapshot commit ${short(commit)}`;
|
|
34
91
|
try {
|
|
35
|
-
|
|
92
|
+
return { content: bytesToFileContent(git(repoRoot, ["show", `${commit}:${relativeFilePath}`])), origin, inferred };
|
|
36
93
|
}
|
|
37
94
|
catch {
|
|
38
|
-
return `
|
|
39
|
-
}
|
|
40
|
-
if (!diff) {
|
|
41
|
-
return ` [INFO] No changes since snapshot (commit ${commitHash.slice(0, 7)}) for: ${filePath}`;
|
|
95
|
+
return { error: `The file does not exist at ${origin} in ${repoRoot}, or that commit is not in the local repository.` };
|
|
42
96
|
}
|
|
43
|
-
return diff;
|
|
44
97
|
}
|
|
45
98
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* A snapshot with no recorded commit — taken before branch tracking, or from a local working copy —
|
|
50
|
-
* is anchored on the newest commit at or before its date instead, so it diffs like any other rather
|
|
51
|
-
* than demanding a refresh first.
|
|
52
|
-
*
|
|
53
|
-
* Files are grouped by that base commit so one comparison covers every file snapshotted at the
|
|
54
|
-
* same revision — which, for a doc refreshed in one go, means a single API call.
|
|
99
|
+
* Whether a content is the one the snapshot recorded, judged by its stored hashes. Undefined when
|
|
100
|
+
* the entry holds no hash to judge by.
|
|
55
101
|
*/
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
for (const [base, files] of byBase) {
|
|
88
|
-
// An inferred base is the branch state at the snapshot date, not a commit the snapshot was
|
|
89
|
-
// provably read from — worth saying, since the diff can then include changes already documented.
|
|
90
|
-
const note = inferredBases.has(base)
|
|
91
|
-
? ` [INFO] Base commit ${base.slice(0, 7)} inferred from the snapshot date (no source commit recorded).\n\n`
|
|
92
|
-
: "";
|
|
93
|
-
if (base === head) {
|
|
94
|
-
for (const file of files) {
|
|
95
|
-
diffs.set(file.path, `${note} [INFO] ${api.label} is still at the snapshot commit ${base.slice(0, 7)} — no changes.`);
|
|
96
|
-
}
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
let patches;
|
|
100
|
-
try {
|
|
101
|
-
patches = await api.comparePatches(base, head);
|
|
102
|
-
}
|
|
103
|
-
catch (err) {
|
|
104
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
105
|
-
for (const file of files)
|
|
106
|
-
diffs.set(file.path, `${note} [ERROR] Could not compare ${base.slice(0, 7)}...${head.slice(0, 7)}: ${message}`);
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
for (const file of files) {
|
|
110
|
-
const patch = patches.get(file.path);
|
|
111
|
-
diffs.set(file.path, note + (patch ?? ` [INFO] No changes to this file between ${base.slice(0, 7)} and ${head.slice(0, 7)}.`));
|
|
112
|
-
}
|
|
102
|
+
function matchesSnapshot(content, tracked) {
|
|
103
|
+
if (tracked.sections && tracked.sections.length > 0) {
|
|
104
|
+
return tracked.sections.every((section) => {
|
|
105
|
+
const result = findAndHashSection(content, section);
|
|
106
|
+
return result.found && result.matchesStored;
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
if (tracked.contentHash)
|
|
110
|
+
return fileContentMatches(content, tracked.contentHash);
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
/** The diff section of one tracked file: snapshot content against the current one at the source. */
|
|
114
|
+
async function diffTrackedFile(tracked, project, source) {
|
|
115
|
+
const github = getGitHubSource(project);
|
|
116
|
+
const base = github ? await githubSnapshotBase(github, tracked) : localSnapshotBase(tracked, project);
|
|
117
|
+
const impossible = (reason) => ` [ERROR] Diff impossible: ${reason}\n` +
|
|
118
|
+
` → Re-read the file to check the doc against the current code, then call refresh_file_snapshot.`;
|
|
119
|
+
if ("error" in base)
|
|
120
|
+
return impossible(base.error);
|
|
121
|
+
const verdict = matchesSnapshot(base.content, tracked);
|
|
122
|
+
if (verdict === false) {
|
|
123
|
+
return impossible(`the content at ${base.origin} does not match the hash recorded at the snapshot.`);
|
|
124
|
+
}
|
|
125
|
+
const unverifiedNote = verdict === undefined ? ` [INFO] No hash recorded for this entry: ${base.origin} could not be checked against the snapshot.\n\n` : "";
|
|
126
|
+
const inferredNote = base.inferred && verdict ? ` [INFO] Base: ${base.origin} (no source commit recorded), checked against the snapshot hash.\n\n` : "";
|
|
127
|
+
let current;
|
|
128
|
+
try {
|
|
129
|
+
current = await source.read(tracked.path);
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
return `${unverifiedNote}${inferredNote} [ERROR] The current content cannot be read from ${source.label}: ${errorMessage(err)}`;
|
|
113
133
|
}
|
|
114
|
-
|
|
134
|
+
if (!current)
|
|
135
|
+
return `${unverifiedNote}${inferredNote} [INFO] The file no longer exists at ${source.label}.`;
|
|
136
|
+
const before = base.content.lines.join("\n");
|
|
137
|
+
const after = current.content.lines.join("\n");
|
|
138
|
+
if (before === after)
|
|
139
|
+
return `${unverifiedNote}${inferredNote} [INFO] No changes since the snapshot.`;
|
|
140
|
+
const patch = createTwoFilesPatch(`a/${tracked.path}`, `b/${tracked.path}`, before, after, base.origin, "current", { context: 3 });
|
|
141
|
+
return unverifiedNote + inferredNote + patch.replace(/^=+\n/, "").trimEnd();
|
|
115
142
|
}
|
|
116
143
|
export async function gitDiffDoc(storageEngine, args) {
|
|
117
144
|
try {
|
|
@@ -121,7 +148,6 @@ export async function gitDiffDoc(storageEngine, args) {
|
|
|
121
148
|
if (projectError)
|
|
122
149
|
return scopeErrorResult(projectError);
|
|
123
150
|
const source = getCodeSource(project);
|
|
124
|
-
const githubApi = getGitHubApi(project);
|
|
125
151
|
const wanted = validated.file_paths
|
|
126
152
|
? new Set(await Promise.all(validated.file_paths.map((p) => toStoredPathForMatching(project, p))))
|
|
127
153
|
: undefined;
|
|
@@ -165,11 +191,10 @@ export async function gitDiffDoc(storageEngine, args) {
|
|
|
165
191
|
docSections.push(`## ${doc.title} [${docPath}]\n\n[INFO] All tracked files already shown in a previous documentation section.`);
|
|
166
192
|
continue;
|
|
167
193
|
}
|
|
168
|
-
const diffs =
|
|
169
|
-
const fileSections = fresh.map((tf) => {
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
return `### ${tf.path}\nSnapshot date: ${tf.lastModifiedAt}${base}\n\n${diff}`;
|
|
194
|
+
const diffs = await mapWithConcurrency(fresh, FILE_CONCURRENCY, (tf) => diffTrackedFile(tf, project, source));
|
|
195
|
+
const fileSections = fresh.map((tf, i) => {
|
|
196
|
+
const base = tf.sourceRevision ? `\nSnapshot commit: ${short(tf.sourceRevision)}` : "";
|
|
197
|
+
return `### ${tf.path}\nSnapshot date: ${tf.lastModifiedAt}${base}\n\n${diffs[i]}`;
|
|
173
198
|
});
|
|
174
199
|
docSections.push(`## ${doc.title} [${docPath}]\n\nCode source: ${source.label}\n\n${fileSections.join("\n\n---\n\n")}`);
|
|
175
200
|
}
|
|
@@ -26,7 +26,8 @@ export async function patchDoc(storageEngine, embeddingEngine, args) {
|
|
|
26
26
|
}, REPLACEMENT_PARAMS);
|
|
27
27
|
const search = searchSource.text;
|
|
28
28
|
const { content: replacement, additionalTrackedFiles } = await resolveCodeRefs(replacementSource.text, resolvedProject);
|
|
29
|
-
|
|
29
|
+
const occurrences = search === "" ? 0 : doc.content.split(search).length - 1;
|
|
30
|
+
if (occurrences === 0) {
|
|
30
31
|
const preview = doc.content.slice(0, 300).replace(/\n/g, "↵");
|
|
31
32
|
return {
|
|
32
33
|
isError: true,
|
|
@@ -36,7 +37,17 @@ export async function patchDoc(storageEngine, embeddingEngine, args) {
|
|
|
36
37
|
}],
|
|
37
38
|
};
|
|
38
39
|
}
|
|
39
|
-
|
|
40
|
+
if (occurrences > 1 && !validated.replace_all) {
|
|
41
|
+
return {
|
|
42
|
+
isError: true,
|
|
43
|
+
content: [{
|
|
44
|
+
type: "text",
|
|
45
|
+
text: `The text to replace occurs ${occurrences} times in "${validated.path}" (project: "${resolvedProject}") — nothing patched.\nExtend the search text with surrounding context so it matches once, or pass replace_all: true to replace every occurrence.`,
|
|
46
|
+
}],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
// split/join rather than String.replace: the replacement is inserted verbatim, `$&` and `$1` included.
|
|
50
|
+
doc.content = doc.content.split(search).join(replacement);
|
|
40
51
|
doc.tracked_files = await mergeTrackedFiles(doc.tracked_files, additionalTrackedFiles, resolvedProject);
|
|
41
52
|
doc.updatedAt = new Date().toISOString();
|
|
42
53
|
await storageEngine.saveDoc(doc, resolvedProject, "patch");
|
|
@@ -51,7 +62,7 @@ export async function patchDoc(storageEngine, embeddingEngine, args) {
|
|
|
51
62
|
if (replacementSource.draft)
|
|
52
63
|
deleteDraft(dataDir, replacementSource.draft);
|
|
53
64
|
return {
|
|
54
|
-
content: [{ type: "text", text: `Documentation entry "${validated.path}" patched successfully.` }],
|
|
65
|
+
content: [{ type: "text", text: `Documentation entry "${validated.path}" patched successfully: ${occurrences} occurrence(s) replaced.` }],
|
|
55
66
|
};
|
|
56
67
|
}
|
|
57
68
|
catch (err) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a0 as ln,a1 as an,a2 as y,a3 as tn,a4 as H,a5 as q,a6 as _,a7 as un,a8 as rn,a9 as L,aa as o,ab as B,ac as sn,ad as on,ae as fn}from"./index-
|
|
1
|
+
import{a0 as ln,a1 as an,a2 as y,a3 as tn,a4 as H,a5 as q,a6 as _,a7 as un,a8 as rn,a9 as L,aa as o,ab as B,ac as sn,ad as on,ae as fn}from"./index-DvVxuv5N.js";function cn(l){return l.innerRadius}function yn(l){return l.outerRadius}function gn(l){return l.startAngle}function dn(l){return l.endAngle}function mn(l){return l&&l.padAngle}function pn(l,h,I,D,v,A,C,a){var O=I-l,i=D-h,n=C-v,d=a-A,u=d*O-n*i;if(!(u*u<y))return u=(n*(h-A)-d*(l-v))/u,[l+u*O,h+u*i]}function W(l,h,I,D,v,A,C){var a=l-I,O=h-D,i=(C?A:-A)/L(a*a+O*O),n=i*O,d=-i*a,u=l+n,s=h+d,f=I+n,c=D+d,F=(u+f)/2,t=(s+c)/2,m=f-u,g=c-s,R=m*m+g*g,T=v-A,P=u*c-f*s,S=(g<0?-1:1)*L(fn(0,T*T*R-P*P)),j=(P*g-m*S)/R,z=(-P*m-g*S)/R,w=(P*g+m*S)/R,p=(-P*m+g*S)/R,x=j-F,e=z-t,r=w-F,G=p-t;return x*x+e*e>r*r+G*G&&(j=w,z=p),{cx:j,cy:z,x01:-n,y01:-d,x11:j*(v/T-1),y11:z*(v/T-1)}}function hn(){var l=cn,h=yn,I=B(0),D=null,v=gn,A=dn,C=mn,a=null,O=ln(i);function i(){var n,d,u=+l.apply(this,arguments),s=+h.apply(this,arguments),f=v.apply(this,arguments)-an,c=A.apply(this,arguments)-an,F=un(c-f),t=c>f;if(a||(a=n=O()),s<u&&(d=s,s=u,u=d),!(s>y))a.moveTo(0,0);else if(F>tn-y)a.moveTo(s*H(f),s*q(f)),a.arc(0,0,s,f,c,!t),u>y&&(a.moveTo(u*H(c),u*q(c)),a.arc(0,0,u,c,f,t));else{var m=f,g=c,R=f,T=c,P=F,S=F,j=C.apply(this,arguments)/2,z=j>y&&(D?+D.apply(this,arguments):L(u*u+s*s)),w=_(un(s-u)/2,+I.apply(this,arguments)),p=w,x=w,e,r;if(z>y){var G=sn(z/u*q(j)),M=sn(z/s*q(j));(P-=G*2)>y?(G*=t?1:-1,R+=G,T-=G):(P=0,R=T=(f+c)/2),(S-=M*2)>y?(M*=t?1:-1,m+=M,g-=M):(S=0,m=g=(f+c)/2)}var J=s*H(m),K=s*q(m),N=u*H(T),Q=u*q(T);if(w>y){var U=s*H(g),V=s*q(g),X=u*H(R),Y=u*q(R),E;if(F<rn)if(E=pn(J,K,X,Y,U,V,N,Q)){var Z=J-E[0],$=K-E[1],b=U-E[0],k=V-E[1],nn=1/q(on((Z*b+$*k)/(L(Z*Z+$*$)*L(b*b+k*k)))/2),en=L(E[0]*E[0]+E[1]*E[1]);p=_(w,(u-en)/(nn-1)),x=_(w,(s-en)/(nn+1))}else p=x=0}S>y?x>y?(e=W(X,Y,J,K,s,x,t),r=W(U,V,N,Q,s,x,t),a.moveTo(e.cx+e.x01,e.cy+e.y01),x<w?a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,x,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,s,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),!t),a.arc(r.cx,r.cy,x,o(r.y11,r.x11),o(r.y01,r.x01),!t))):(a.moveTo(J,K),a.arc(0,0,s,m,g,!t)):a.moveTo(J,K),!(u>y)||!(P>y)?a.lineTo(N,Q):p>y?(e=W(N,Q,U,V,u,-p,t),r=W(J,K,X,Y,u,-p,t),a.lineTo(e.cx+e.x01,e.cy+e.y01),p<w?a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(r.y01,r.x01),!t):(a.arc(e.cx,e.cy,p,o(e.y01,e.x01),o(e.y11,e.x11),!t),a.arc(0,0,u,o(e.cy+e.y11,e.cx+e.x11),o(r.cy+r.y11,r.cx+r.x11),t),a.arc(r.cx,r.cy,p,o(r.y11,r.x11),o(r.y01,r.x01),!t))):a.arc(0,0,u,T,R,t)}if(a.closePath(),n)return a=null,n+""||null}return i.centroid=function(){var n=(+l.apply(this,arguments)+ +h.apply(this,arguments))/2,d=(+v.apply(this,arguments)+ +A.apply(this,arguments))/2-rn/2;return[H(d)*n,q(d)*n]},i.innerRadius=function(n){return arguments.length?(l=typeof n=="function"?n:B(+n),i):l},i.outerRadius=function(n){return arguments.length?(h=typeof n=="function"?n:B(+n),i):h},i.cornerRadius=function(n){return arguments.length?(I=typeof n=="function"?n:B(+n),i):I},i.padRadius=function(n){return arguments.length?(D=n==null?null:typeof n=="function"?n:B(+n),i):D},i.startAngle=function(n){return arguments.length?(v=typeof n=="function"?n:B(+n),i):v},i.endAngle=function(n){return arguments.length?(A=typeof n=="function"?n:B(+n),i):A},i.padAngle=function(n){return arguments.length?(C=typeof n=="function"?n:B(+n),i):C},i.context=function(n){return arguments.length?(a=n??null,i):a},i}export{hn as d};
|