planmode 0.2.2 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +2080 -717
- package/dist/mcp.js +798 -262
- package/package.json +2 -1
- package/src/commands/context.ts +111 -0
- package/src/commands/doctor.ts +46 -14
- package/src/commands/init.ts +95 -47
- package/src/commands/install.ts +17 -2
- package/src/commands/interactive.ts +556 -0
- package/src/commands/login.ts +50 -23
- package/src/commands/publish.ts +15 -3
- package/src/commands/record.ts +32 -8
- package/src/commands/run.ts +6 -15
- package/src/commands/search.ts +89 -18
- package/src/commands/snapshot.ts +33 -9
- package/src/commands/test.ts +43 -13
- package/src/commands/update.ts +57 -15
- package/src/index.ts +11 -2
- package/src/lib/context.ts +265 -0
- package/src/lib/installer.ts +57 -29
- package/src/lib/prompts.ts +159 -0
- package/src/lib/publisher.ts +176 -144
- package/src/mcp.ts +146 -0
- package/src/types/index.ts +28 -0
package/src/lib/publisher.ts
CHANGED
|
@@ -2,10 +2,12 @@ import { readManifest, validateManifest } from "./manifest.js";
|
|
|
2
2
|
import { getGitHubToken } from "./config.js";
|
|
3
3
|
import { getRemoteUrl, getHeadSha, createTag, pushTag } from "./git.js";
|
|
4
4
|
import { logger } from "./logger.js";
|
|
5
|
+
import { withSpinner } from "./prompts.js";
|
|
5
6
|
|
|
6
7
|
export interface PublishOptions {
|
|
7
8
|
projectDir?: string;
|
|
8
9
|
token?: string;
|
|
10
|
+
interactive?: boolean;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
export interface PublishResult {
|
|
@@ -16,6 +18,7 @@ export interface PublishResult {
|
|
|
16
18
|
|
|
17
19
|
export async function publishPackage(options: PublishOptions = {}): Promise<PublishResult> {
|
|
18
20
|
const cwd = options.projectDir ?? process.cwd();
|
|
21
|
+
const interactive = options.interactive ?? false;
|
|
19
22
|
|
|
20
23
|
// Check auth
|
|
21
24
|
const token = options.token ?? getGitHubToken();
|
|
@@ -24,12 +27,21 @@ export async function publishPackage(options: PublishOptions = {}): Promise<Publ
|
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
// Read and validate manifest
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
const doValidate = async () => {
|
|
31
|
+
const manifest = readManifest(cwd);
|
|
32
|
+
const errors = validateManifest(manifest, true);
|
|
33
|
+
if (errors.length > 0) {
|
|
34
|
+
throw new Error(`Invalid manifest:\n${errors.map((e) => ` - ${e}`).join("\n")}`);
|
|
35
|
+
}
|
|
36
|
+
return manifest;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const manifest = interactive
|
|
40
|
+
? await withSpinner("Validating manifest...", doValidate, "Manifest valid")
|
|
41
|
+
: await (async () => {
|
|
42
|
+
logger.info("Reading planmode.yaml...");
|
|
43
|
+
return doValidate();
|
|
44
|
+
})();
|
|
33
45
|
|
|
34
46
|
// Check git remote
|
|
35
47
|
const remoteUrl = await getRemoteUrl(cwd);
|
|
@@ -41,162 +53,182 @@ export async function publishPackage(options: PublishOptions = {}): Promise<Publ
|
|
|
41
53
|
const tag = `v${manifest.version}`;
|
|
42
54
|
|
|
43
55
|
// Create and push tag
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
56
|
+
const doTag = async () => {
|
|
57
|
+
try {
|
|
58
|
+
await createTag(cwd, tag);
|
|
59
|
+
} catch {
|
|
60
|
+
// Tag already exists
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
await pushTag(cwd, tag);
|
|
64
|
+
} catch {
|
|
65
|
+
// Tag already pushed
|
|
66
|
+
}
|
|
67
|
+
};
|
|
50
68
|
|
|
51
|
-
|
|
52
|
-
await
|
|
69
|
+
if (interactive) {
|
|
70
|
+
await withSpinner(`Creating tag ${tag}...`, doTag, `Tag ${tag} ready`);
|
|
71
|
+
} else {
|
|
72
|
+
logger.info(`Creating tag ${tag}...`);
|
|
73
|
+
await doTag();
|
|
53
74
|
logger.success(`Pushed tag ${tag}`);
|
|
54
|
-
} catch {
|
|
55
|
-
logger.dim(`Tag ${tag} already pushed`);
|
|
56
75
|
}
|
|
57
76
|
|
|
58
77
|
// Fork registry and create PR via GitHub API
|
|
59
|
-
|
|
78
|
+
const doSubmit = async () => {
|
|
79
|
+
const headers = {
|
|
80
|
+
Authorization: `Bearer ${token}`,
|
|
81
|
+
Accept: "application/vnd.github.v3+json",
|
|
82
|
+
"User-Agent": "planmode-cli",
|
|
83
|
+
"Content-Type": "application/json",
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Fork the registry repo (idempotent)
|
|
87
|
+
await fetch("https://api.github.com/repos/kaihannonen/planmode.org/forks", {
|
|
88
|
+
method: "POST",
|
|
89
|
+
headers,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Get authenticated user
|
|
93
|
+
const userRes = await fetch("https://api.github.com/user", { headers });
|
|
94
|
+
if (!userRes.ok) {
|
|
95
|
+
throw new Error("Failed to authenticate with GitHub. Check your token.");
|
|
96
|
+
}
|
|
97
|
+
const user = (await userRes.json()) as { login: string };
|
|
98
|
+
|
|
99
|
+
// Create metadata files content
|
|
100
|
+
const repoPath = remoteUrl.replace(/^https?:\/\//, "").replace(/\.git$/, "");
|
|
101
|
+
|
|
102
|
+
const metadataContent = JSON.stringify(
|
|
103
|
+
{
|
|
104
|
+
name: manifest.name,
|
|
105
|
+
description: manifest.description,
|
|
106
|
+
author: manifest.author,
|
|
107
|
+
license: manifest.license,
|
|
108
|
+
repository: repoPath,
|
|
109
|
+
category: manifest.category ?? "other",
|
|
110
|
+
tags: manifest.tags ?? [],
|
|
111
|
+
type: manifest.type,
|
|
112
|
+
models: manifest.models ?? [],
|
|
113
|
+
latest_version: manifest.version,
|
|
114
|
+
versions: [manifest.version],
|
|
115
|
+
downloads: 0,
|
|
116
|
+
created_at: new Date().toISOString(),
|
|
117
|
+
updated_at: new Date().toISOString(),
|
|
118
|
+
dependencies: manifest.dependencies,
|
|
119
|
+
variables: manifest.variables,
|
|
120
|
+
},
|
|
121
|
+
null,
|
|
122
|
+
2,
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const versionContent = JSON.stringify(
|
|
126
|
+
{
|
|
127
|
+
version: manifest.version,
|
|
128
|
+
published_at: new Date().toISOString(),
|
|
129
|
+
source: {
|
|
130
|
+
repository: repoPath,
|
|
131
|
+
tag,
|
|
132
|
+
sha,
|
|
133
|
+
},
|
|
134
|
+
files: ["planmode.yaml", manifest.content_file ?? "inline"],
|
|
135
|
+
content_hash: `sha256:${sha.slice(0, 16)}`,
|
|
136
|
+
},
|
|
137
|
+
null,
|
|
138
|
+
2,
|
|
139
|
+
);
|
|
60
140
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
Accept: "application/vnd.github.v3+json",
|
|
64
|
-
"User-Agent": "planmode-cli",
|
|
65
|
-
"Content-Type": "application/json",
|
|
66
|
-
};
|
|
141
|
+
// Create branch on fork
|
|
142
|
+
const branchName = `add-${manifest.name}-${manifest.version}`;
|
|
67
143
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
144
|
+
// Get main branch ref
|
|
145
|
+
const refRes = await fetch(
|
|
146
|
+
`https://api.github.com/repos/${user.login}/planmode.org/git/ref/heads/main`,
|
|
147
|
+
{ headers },
|
|
148
|
+
);
|
|
73
149
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
// Create metadata files content
|
|
82
|
-
const repoPath = remoteUrl.replace(/^https?:\/\//, "").replace(/\.git$/, "");
|
|
83
|
-
|
|
84
|
-
const metadataContent = JSON.stringify(
|
|
85
|
-
{
|
|
86
|
-
name: manifest.name,
|
|
87
|
-
description: manifest.description,
|
|
88
|
-
author: manifest.author,
|
|
89
|
-
license: manifest.license,
|
|
90
|
-
repository: repoPath,
|
|
91
|
-
category: manifest.category ?? "other",
|
|
92
|
-
tags: manifest.tags ?? [],
|
|
93
|
-
type: manifest.type,
|
|
94
|
-
models: manifest.models ?? [],
|
|
95
|
-
latest_version: manifest.version,
|
|
96
|
-
versions: [manifest.version],
|
|
97
|
-
downloads: 0,
|
|
98
|
-
created_at: new Date().toISOString(),
|
|
99
|
-
updated_at: new Date().toISOString(),
|
|
100
|
-
dependencies: manifest.dependencies,
|
|
101
|
-
variables: manifest.variables,
|
|
102
|
-
},
|
|
103
|
-
null,
|
|
104
|
-
2,
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
const versionContent = JSON.stringify(
|
|
108
|
-
{
|
|
109
|
-
version: manifest.version,
|
|
110
|
-
published_at: new Date().toISOString(),
|
|
111
|
-
source: {
|
|
112
|
-
repository: repoPath,
|
|
113
|
-
tag,
|
|
114
|
-
sha,
|
|
115
|
-
},
|
|
116
|
-
files: ["planmode.yaml", manifest.content_file ?? "inline"],
|
|
117
|
-
content_hash: `sha256:${sha.slice(0, 16)}`,
|
|
118
|
-
},
|
|
119
|
-
null,
|
|
120
|
-
2,
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
// Create branch on fork
|
|
124
|
-
const branchName = `add-${manifest.name}-${manifest.version}`;
|
|
125
|
-
|
|
126
|
-
// Get main branch ref
|
|
127
|
-
const refRes = await fetch(
|
|
128
|
-
`https://api.github.com/repos/${user.login}/planmode.org/git/ref/heads/main`,
|
|
129
|
-
{ headers },
|
|
130
|
-
);
|
|
131
|
-
|
|
132
|
-
if (!refRes.ok) {
|
|
133
|
-
throw new Error("Failed to access registry fork. Make sure the fork exists.");
|
|
134
|
-
}
|
|
150
|
+
if (!refRes.ok) {
|
|
151
|
+
throw new Error("Failed to access registry fork. Make sure the fork exists.");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const refData = (await refRes.json()) as { object: { sha: string } };
|
|
155
|
+
const baseSha = refData.object.sha;
|
|
135
156
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
// Create branch
|
|
140
|
-
await fetch(`https://api.github.com/repos/${user.login}/planmode.org/git/refs`, {
|
|
141
|
-
method: "POST",
|
|
142
|
-
headers,
|
|
143
|
-
body: JSON.stringify({
|
|
144
|
-
ref: `refs/heads/${branchName}`,
|
|
145
|
-
sha: baseSha,
|
|
146
|
-
}),
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
// Create metadata.json
|
|
150
|
-
await fetch(
|
|
151
|
-
`https://api.github.com/repos/${user.login}/planmode.org/contents/registry/packages/${manifest.name}/metadata.json`,
|
|
152
|
-
{
|
|
153
|
-
method: "PUT",
|
|
157
|
+
// Create branch
|
|
158
|
+
await fetch(`https://api.github.com/repos/${user.login}/planmode.org/git/refs`, {
|
|
159
|
+
method: "POST",
|
|
154
160
|
headers,
|
|
155
161
|
body: JSON.stringify({
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
branch: branchName,
|
|
162
|
+
ref: `refs/heads/${branchName}`,
|
|
163
|
+
sha: baseSha,
|
|
159
164
|
}),
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Create metadata.json
|
|
168
|
+
await fetch(
|
|
169
|
+
`https://api.github.com/repos/${user.login}/planmode.org/contents/registry/packages/${manifest.name}/metadata.json`,
|
|
170
|
+
{
|
|
171
|
+
method: "PUT",
|
|
172
|
+
headers,
|
|
173
|
+
body: JSON.stringify({
|
|
174
|
+
message: `Add ${manifest.name}@${manifest.version}`,
|
|
175
|
+
content: Buffer.from(metadataContent).toString("base64"),
|
|
176
|
+
branch: branchName,
|
|
177
|
+
}),
|
|
178
|
+
},
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
// Create version file
|
|
182
|
+
await fetch(
|
|
183
|
+
`https://api.github.com/repos/${user.login}/planmode.org/contents/registry/packages/${manifest.name}/versions/${manifest.version}.json`,
|
|
184
|
+
{
|
|
185
|
+
method: "PUT",
|
|
186
|
+
headers,
|
|
187
|
+
body: JSON.stringify({
|
|
188
|
+
message: `Add ${manifest.name}@${manifest.version} version metadata`,
|
|
189
|
+
content: Buffer.from(versionContent).toString("base64"),
|
|
190
|
+
branch: branchName,
|
|
191
|
+
}),
|
|
192
|
+
},
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
// Create PR
|
|
196
|
+
const prRes = await fetch("https://api.github.com/repos/kaihannonen/planmode.org/pulls", {
|
|
197
|
+
method: "POST",
|
|
168
198
|
headers,
|
|
169
199
|
body: JSON.stringify({
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
200
|
+
title: `Add ${manifest.name}@${manifest.version}`,
|
|
201
|
+
head: `${user.login}:${branchName}`,
|
|
202
|
+
base: "main",
|
|
203
|
+
body: `## New package: ${manifest.name}\n\n- **Type:** ${manifest.type}\n- **Version:** ${manifest.version}\n- **Description:** ${manifest.description}\n- **Author:** ${manifest.author}\n\nSubmitted via \`planmode publish\`.`,
|
|
173
204
|
}),
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
headers,
|
|
181
|
-
body: JSON.stringify({
|
|
182
|
-
title: `Add ${manifest.name}@${manifest.version}`,
|
|
183
|
-
head: `${user.login}:${branchName}`,
|
|
184
|
-
base: "main",
|
|
185
|
-
body: `## New package: ${manifest.name}\n\n- **Type:** ${manifest.type}\n- **Version:** ${manifest.version}\n- **Description:** ${manifest.description}\n- **Author:** ${manifest.author}\n\nSubmitted via \`planmode publish\`.`,
|
|
186
|
-
}),
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
if (!prRes.ok) {
|
|
190
|
-
const err = await prRes.text();
|
|
191
|
-
throw new Error(`Failed to create PR: ${err}`);
|
|
192
|
-
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
if (!prRes.ok) {
|
|
208
|
+
const err = await prRes.text();
|
|
209
|
+
throw new Error(`Failed to create PR: ${err}`);
|
|
210
|
+
}
|
|
193
211
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
212
|
+
const pr = (await prRes.json()) as { html_url: string };
|
|
213
|
+
return pr.html_url;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
let prUrl: string;
|
|
217
|
+
if (interactive) {
|
|
218
|
+
prUrl = await withSpinner(
|
|
219
|
+
"Submitting to registry...",
|
|
220
|
+
doSubmit,
|
|
221
|
+
"Submitted to registry",
|
|
222
|
+
);
|
|
223
|
+
} else {
|
|
224
|
+
logger.info("Submitting to registry...");
|
|
225
|
+
prUrl = await doSubmit();
|
|
226
|
+
logger.success(`Published ${manifest.name}@${manifest.version}`);
|
|
227
|
+
logger.info(`PR: ${prUrl}`);
|
|
228
|
+
}
|
|
197
229
|
|
|
198
230
|
return {
|
|
199
|
-
prUrl
|
|
231
|
+
prUrl,
|
|
200
232
|
packageName: manifest.name,
|
|
201
233
|
version: manifest.version,
|
|
202
234
|
};
|
package/src/mcp.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { runDoctor } from "./lib/doctor.js";
|
|
|
17
17
|
import { testPackage } from "./lib/tester.js";
|
|
18
18
|
import { startRecordingAsync, stopRecording, isRecording } from "./lib/recorder.js";
|
|
19
19
|
import { takeSnapshot } from "./lib/snapshot.js";
|
|
20
|
+
import { addContextRepo, removeContextRepo, reindexContext, getContextSummary, formatSize } from "./lib/context.js";
|
|
20
21
|
import type { Category } from "./types/index.js";
|
|
21
22
|
|
|
22
23
|
// ── Helpers ──
|
|
@@ -784,6 +785,151 @@ server.registerTool(
|
|
|
784
785
|
},
|
|
785
786
|
);
|
|
786
787
|
|
|
788
|
+
// -- planmode_context_add --
|
|
789
|
+
server.registerTool(
|
|
790
|
+
"planmode_context_add",
|
|
791
|
+
{
|
|
792
|
+
description: "Add a document directory to the project context. Indexes all text-based files (md, pdf, txt, json, yaml, csv, html, etc.) and stores their metadata in .planmode/context.yaml. The AI can then see what documents are available and read them on demand.",
|
|
793
|
+
inputSchema: {
|
|
794
|
+
path: z.string().describe("Path to the document directory (relative to project or absolute)"),
|
|
795
|
+
name: z.string().optional().describe("Human-readable label for this directory"),
|
|
796
|
+
projectDir: z.string().optional().describe("Project directory (default: current working directory)"),
|
|
797
|
+
},
|
|
798
|
+
},
|
|
799
|
+
async ({ path: dirPath, name, projectDir }) => {
|
|
800
|
+
try {
|
|
801
|
+
const { result, messages } = withCapture(() =>
|
|
802
|
+
addContextRepo(dirPath, { name, projectDir }),
|
|
803
|
+
);
|
|
804
|
+
|
|
805
|
+
const breakdown = result.files.length > 0
|
|
806
|
+
? `\nTypes: ${getTypeBreakdownText(result)}`
|
|
807
|
+
: "";
|
|
808
|
+
|
|
809
|
+
return textResult(
|
|
810
|
+
formatMessages(
|
|
811
|
+
messages,
|
|
812
|
+
`Added "${name ?? result.repo.path}" — ${result.file_count} file(s), ${formatSize(result.total_size)}${breakdown}`,
|
|
813
|
+
),
|
|
814
|
+
);
|
|
815
|
+
} catch (err) {
|
|
816
|
+
return errorResult("Error adding context repo", err as Error);
|
|
817
|
+
}
|
|
818
|
+
},
|
|
819
|
+
);
|
|
820
|
+
|
|
821
|
+
// -- planmode_context_remove --
|
|
822
|
+
server.registerTool(
|
|
823
|
+
"planmode_context_remove",
|
|
824
|
+
{
|
|
825
|
+
description: "Remove a document directory from the project context",
|
|
826
|
+
inputSchema: {
|
|
827
|
+
pathOrName: z.string().describe("Path or name of the context repo to remove"),
|
|
828
|
+
projectDir: z.string().optional().describe("Project directory (default: current working directory)"),
|
|
829
|
+
},
|
|
830
|
+
},
|
|
831
|
+
async ({ pathOrName, projectDir }) => {
|
|
832
|
+
try {
|
|
833
|
+
const { messages } = withCapture(() =>
|
|
834
|
+
removeContextRepo(pathOrName, projectDir),
|
|
835
|
+
);
|
|
836
|
+
|
|
837
|
+
return textResult(formatMessages(messages) || `Removed "${pathOrName}" from context.`);
|
|
838
|
+
} catch (err) {
|
|
839
|
+
return errorResult("Error removing context repo", err as Error);
|
|
840
|
+
}
|
|
841
|
+
},
|
|
842
|
+
);
|
|
843
|
+
|
|
844
|
+
// -- planmode_context_list --
|
|
845
|
+
server.registerTool(
|
|
846
|
+
"planmode_context_list",
|
|
847
|
+
{
|
|
848
|
+
description: "List all document directories in the project context with file counts, sizes, and type breakdowns. Use this to see what reference documents are available for the project.",
|
|
849
|
+
inputSchema: {
|
|
850
|
+
detailed: z.boolean().optional().describe("Include full file listings for each repo (default: false, shows only summaries)"),
|
|
851
|
+
projectDir: z.string().optional().describe("Project directory (default: current working directory)"),
|
|
852
|
+
},
|
|
853
|
+
},
|
|
854
|
+
async ({ detailed, projectDir }) => {
|
|
855
|
+
try {
|
|
856
|
+
const summary = getContextSummary(projectDir);
|
|
857
|
+
|
|
858
|
+
if (summary.totalRepos === 0) {
|
|
859
|
+
return textResult("No context repos configured. Use planmode_context_add to add a document directory.");
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
const lines = [
|
|
863
|
+
`**${summary.totalRepos} context repo(s)** — ${summary.totalFiles} file(s), ${formatSize(summary.totalSize)}`,
|
|
864
|
+
"",
|
|
865
|
+
];
|
|
866
|
+
|
|
867
|
+
for (const repo of summary.repos) {
|
|
868
|
+
lines.push(`### ${repo.name}`);
|
|
869
|
+
lines.push(`- **Path:** ${repo.path}`);
|
|
870
|
+
lines.push(`- **Files:** ${repo.fileCount} (${formatSize(repo.totalSize)})`);
|
|
871
|
+
if (repo.typeBreakdown.length > 0) {
|
|
872
|
+
lines.push(`- **Types:** ${repo.typeBreakdown.join(", ")}`);
|
|
873
|
+
}
|
|
874
|
+
lines.push(`- **Indexed:** ${repo.indexedAt}`);
|
|
875
|
+
|
|
876
|
+
if (detailed) {
|
|
877
|
+
const index = (await import("./lib/context.js")).readContextIndex(projectDir);
|
|
878
|
+
const repoIndex = index.repos.find(
|
|
879
|
+
(r) => r.repo.path === repo.path || r.repo.name === repo.name,
|
|
880
|
+
);
|
|
881
|
+
if (repoIndex && repoIndex.files.length > 0) {
|
|
882
|
+
lines.push("", "**Files:**");
|
|
883
|
+
for (const file of repoIndex.files) {
|
|
884
|
+
lines.push(`- \`${file.path}\` (${file.extension}, ${formatSize(file.size)})`);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
lines.push("");
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
return textResult(lines.join("\n"));
|
|
893
|
+
} catch (err) {
|
|
894
|
+
return errorResult("Error listing context", err as Error);
|
|
895
|
+
}
|
|
896
|
+
},
|
|
897
|
+
);
|
|
898
|
+
|
|
899
|
+
// -- planmode_context_reindex --
|
|
900
|
+
server.registerTool(
|
|
901
|
+
"planmode_context_reindex",
|
|
902
|
+
{
|
|
903
|
+
description: "Re-scan files in one or all context directories to update the file index",
|
|
904
|
+
inputSchema: {
|
|
905
|
+
pathOrName: z.string().optional().describe("Path or name of a specific repo to reindex (omit to reindex all)"),
|
|
906
|
+
projectDir: z.string().optional().describe("Project directory (default: current working directory)"),
|
|
907
|
+
},
|
|
908
|
+
},
|
|
909
|
+
async ({ pathOrName, projectDir }) => {
|
|
910
|
+
try {
|
|
911
|
+
const { messages } = withCapture(() =>
|
|
912
|
+
reindexContext(pathOrName, projectDir),
|
|
913
|
+
);
|
|
914
|
+
|
|
915
|
+
return textResult(formatMessages(messages) || "Reindex complete.");
|
|
916
|
+
} catch (err) {
|
|
917
|
+
return errorResult("Error reindexing context", err as Error);
|
|
918
|
+
}
|
|
919
|
+
},
|
|
920
|
+
);
|
|
921
|
+
|
|
922
|
+
function getTypeBreakdownText(repoIndex: { files: Array<{ extension: string }> }): string {
|
|
923
|
+
const counts = new Map<string, number>();
|
|
924
|
+
for (const file of repoIndex.files) {
|
|
925
|
+
counts.set(file.extension, (counts.get(file.extension) ?? 0) + 1);
|
|
926
|
+
}
|
|
927
|
+
return Array.from(counts.entries())
|
|
928
|
+
.sort((a, b) => b[1] - a[1])
|
|
929
|
+
.map(([ext, count]) => `${ext}: ${count}`)
|
|
930
|
+
.join(", ");
|
|
931
|
+
}
|
|
932
|
+
|
|
787
933
|
// ── Resources ──
|
|
788
934
|
|
|
789
935
|
// Expose installed packages as browsable resources
|
package/src/types/index.ts
CHANGED
|
@@ -133,6 +133,34 @@ export interface PlanmodeConfig {
|
|
|
133
133
|
};
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
// ── Context (document indexing) ──
|
|
137
|
+
|
|
138
|
+
export interface ContextRepo {
|
|
139
|
+
path: string;
|
|
140
|
+
name?: string;
|
|
141
|
+
added_at: string;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface IndexedFile {
|
|
145
|
+
path: string;
|
|
146
|
+
extension: string;
|
|
147
|
+
size: number;
|
|
148
|
+
modified_at: string;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface ContextRepoIndex {
|
|
152
|
+
repo: ContextRepo;
|
|
153
|
+
files: IndexedFile[];
|
|
154
|
+
indexed_at: string;
|
|
155
|
+
file_count: number;
|
|
156
|
+
total_size: number;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface ContextIndex {
|
|
160
|
+
version: number;
|
|
161
|
+
repos: ContextRepoIndex[];
|
|
162
|
+
}
|
|
163
|
+
|
|
136
164
|
// ── Resolved package info ──
|
|
137
165
|
|
|
138
166
|
export interface ResolvedPackage {
|