@cr8rcho/alkahest 0.1.69 → 0.1.70
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.ko.md +15 -0
- package/README.md +15 -0
- package/dist/commands/comments.js +1 -1
- package/dist/commands/comments.js.map +1 -1
- package/dist/commands/history.js +1 -1
- package/dist/commands/history.js.map +1 -1
- package/dist/commands/issues.js +1 -1
- package/dist/commands/issues.js.map +1 -1
- package/dist/commands/maps.js +1 -1
- package/dist/commands/maps.js.map +1 -1
- package/dist/commands/notes.js +1 -1
- package/dist/commands/notes.js.map +1 -1
- package/dist/core/comments.js +2 -2
- package/dist/core/comments.js.map +1 -1
- package/dist/core/history.js +1 -1
- package/dist/core/history.js.map +1 -1
- package/dist/core/issues.js +1 -1
- package/dist/core/issues.js.map +1 -1
- package/dist/core/maps.js +1 -1
- package/dist/core/maps.js.map +1 -1
- package/dist/core/notes.js +1 -1
- package/dist/core/notes.js.map +1 -1
- package/dist/core/project.d.ts +8 -1
- package/dist/core/project.js +11 -2
- package/dist/core/project.js.map +1 -1
- package/dist/mcp/server.js +75 -53
- package/dist/mcp/server.js.map +1 -1
- package/package.json +1 -1
package/dist/core/project.d.ts
CHANGED
|
@@ -4,7 +4,14 @@ export declare function findProjectRoot(start: string): string;
|
|
|
4
4
|
export declare function localSlug(root: string): string | undefined;
|
|
5
5
|
/** Code map this checkout publishes to, stored alongside the slug in .alkahest/project.json. */
|
|
6
6
|
export declare function localMapSlug(root: string): string | undefined;
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Resolve { root, slug, mapSlug } from any path:
|
|
9
|
+
* explicit → local file → saved creds (by root/path) → ALKAHEST_PROJECT.
|
|
10
|
+
*
|
|
11
|
+
* The env var is the LAST fallback on purpose (same shape as ALKAHEST_TOKEN in
|
|
12
|
+
* credentials.ts): it lets an MCP server be configured for one project — a personal wiki,
|
|
13
|
+
* say — without a checkout to bind, while a real linked checkout still wins over it.
|
|
14
|
+
*/
|
|
8
15
|
export declare function resolveProject(path: string, explicitSlug?: string): {
|
|
9
16
|
root: string;
|
|
10
17
|
slug?: string;
|
package/dist/core/project.js
CHANGED
|
@@ -54,14 +54,23 @@ export function localMapSlug(root) {
|
|
|
54
54
|
catch { /* missing or unparsable */ }
|
|
55
55
|
return undefined;
|
|
56
56
|
}
|
|
57
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* Resolve { root, slug, mapSlug } from any path:
|
|
59
|
+
* explicit → local file → saved creds (by root/path) → ALKAHEST_PROJECT.
|
|
60
|
+
*
|
|
61
|
+
* The env var is the LAST fallback on purpose (same shape as ALKAHEST_TOKEN in
|
|
62
|
+
* credentials.ts): it lets an MCP server be configured for one project — a personal wiki,
|
|
63
|
+
* say — without a checkout to bind, while a real linked checkout still wins over it.
|
|
64
|
+
*/
|
|
58
65
|
export function resolveProject(path, explicitSlug) {
|
|
59
66
|
const root = findProjectRoot(path);
|
|
60
67
|
const creds = loadCredentials();
|
|
61
68
|
const slug = explicitSlug ||
|
|
62
69
|
localSlug(root) ||
|
|
63
70
|
creds.projects?.[root]?.slug ||
|
|
64
|
-
creds.projects?.[resolve(path || ".")]?.slug
|
|
71
|
+
creds.projects?.[resolve(path || ".")]?.slug ||
|
|
72
|
+
process.env.ALKAHEST_PROJECT?.trim() ||
|
|
73
|
+
undefined;
|
|
65
74
|
const mapSlug = localMapSlug(root) ||
|
|
66
75
|
creds.projects?.[root]?.mapSlug ||
|
|
67
76
|
creds.projects?.[resolve(path || ".")]?.mapSlug;
|
package/dist/core/project.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project.js","sourceRoot":"","sources":["../../src/core/project.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD;;;;;;;;;GASG;AAEH,qFAAqF;AACrF,8FAA8F;AAC9F,qEAAqE;AACrE,MAAM,eAAe,GAAG,CAAC,UAAU,EAAE,cAAc,EAAE,eAAe,CAAC,CAAC;AAEtE,SAAS,YAAY,CAAC,GAAW;IAC/B,OAAO,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;AAC3E,CAAC;AAED,qGAAqG;AACrG,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,IAAI,GAAG,CAAC,CAAC;IACnC,IAAI,GAAG,GAAG,IAAI,CAAC;IACf,SAAS,CAAC;QACR,IAAI,YAAY,CAAC,GAAG,CAAC;YAAE,OAAO,GAAG,CAAC;QAClC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,MAAM,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC,CAAC,iDAAiD;QAClF,GAAG,GAAG,MAAM,CAAC;IACf,CAAC;AACH,CAAC;AAED,wGAAwG;AACxG,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,KAAK,MAAM,IAAI,IAAI,CAAC,cAAc,EAAE,eAAe,CAAC,EAAE,CAAC;QACrD,IAAI,CAAC;YACH,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;YACzE,IAAI,CAAC,EAAE,IAAI;gBAAE,OAAO,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACrC,CAAC;QAAC,MAAM,CAAC,CAAC,0CAA0C,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,gGAAgG;AAChG,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QACnF,IAAI,CAAC,EAAE,OAAO;YAAE,OAAO,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC,CAAC,2BAA2B,CAAC,CAAC;IACvC,OAAO,SAAS,CAAC;AACnB,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"project.js","sourceRoot":"","sources":["../../src/core/project.ts"],"names":[],"mappings":"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"}
|
package/dist/mcp/server.js
CHANGED
|
@@ -214,19 +214,20 @@ export function buildServer() {
|
|
|
214
214
|
"node's source location (screen → sourceFile/route/title, resource → path/label) so you can open the right file " +
|
|
215
215
|
"and address it. Use this to drive development from feedback: read open comments, edit the code, then call " +
|
|
216
216
|
"resolve_comment. Needs an API token (ALKAHEST_TOKEN in this server's config, or a prior 'alkahest login') " +
|
|
217
|
-
"and the project
|
|
217
|
+
"and the project named (see `project`).",
|
|
218
218
|
inputSchema: {
|
|
219
|
+
project: z.string().optional().describe("Which project (slug) — say it explicitly when the folder isn't a linked checkout. List them with list_projects."),
|
|
219
220
|
path: z.string().optional().describe("Project root (default: cwd)"),
|
|
220
221
|
open: z.boolean().optional().describe("Only unresolved comments (default: false = all)"),
|
|
221
222
|
},
|
|
222
|
-
}, async ({ path, open }) => {
|
|
223
|
+
}, async ({ path, open, project }) => {
|
|
223
224
|
const root = rootOf(path);
|
|
224
|
-
const res = await pullComments(root, { open });
|
|
225
|
+
const res = await pullComments(root, { open, slug: project });
|
|
225
226
|
if (!res.ok) {
|
|
226
227
|
const hints = {
|
|
227
228
|
no_token: "Set ALKAHEST_TOKEN in this MCP server's config (token from alkahest.app → Account).",
|
|
228
229
|
no_api: "Set ALKAHEST_API_URL in this MCP server's config.",
|
|
229
|
-
no_slug: "
|
|
230
|
+
no_slug: "Pass `project` — a slug from list_projects — or set ALKAHEST_PROJECT in this MCP server's config.",
|
|
230
231
|
invalid_token: "The API token is invalid or revoked — create a new one at alkahest.app → Account.",
|
|
231
232
|
not_found: "No accessible project for this slug.",
|
|
232
233
|
};
|
|
@@ -267,9 +268,10 @@ export function buildServer() {
|
|
|
267
268
|
inputSchema: {
|
|
268
269
|
node: z.string().describe("screen id/route/title, resource id/path/label, or 'map'"),
|
|
269
270
|
body: z.string().describe("the comment text"),
|
|
271
|
+
project: z.string().optional().describe("Which project (slug) — say it explicitly when the folder isn't a linked checkout. List them with list_projects."),
|
|
270
272
|
path: z.string().optional().describe("Project root (default: cwd)"),
|
|
271
273
|
},
|
|
272
|
-
}, async ({ node, body, path }) => {
|
|
274
|
+
}, async ({ node, body, path, project }) => {
|
|
273
275
|
const root = findProjectRoot(rootOf(path));
|
|
274
276
|
const map = loadOrScan(root);
|
|
275
277
|
if (!map)
|
|
@@ -277,11 +279,11 @@ export function buildServer() {
|
|
|
277
279
|
const n = resolveNode(map, node);
|
|
278
280
|
if (!n)
|
|
279
281
|
return text(`No node matches '${node}'. Use the overview tool to list screens/resources.`);
|
|
280
|
-
const res = await postComment(root, { node_key: n.node_key, anchor_kind: n.anchor_kind, anchor_label: n.anchor_label, body });
|
|
282
|
+
const res = await postComment(root, { node_key: n.node_key, anchor_kind: n.anchor_kind, anchor_label: n.anchor_label, body, slug: project });
|
|
281
283
|
if (!res.ok) {
|
|
282
284
|
const hints = {
|
|
283
285
|
no_token: "Set ALKAHEST_TOKEN in this MCP server's config.",
|
|
284
|
-
no_slug: "
|
|
286
|
+
no_slug: "Pass `project` — a slug from list_projects — or set ALKAHEST_PROJECT in this MCP server's config.",
|
|
285
287
|
forbidden: "Only the project owner or a collaborator can comment.",
|
|
286
288
|
};
|
|
287
289
|
return text(`Add comment failed (${res.code}): ${res.message}.${hints[res.code ?? ""] ? " " + hints[res.code ?? ""] : ""}`);
|
|
@@ -318,17 +320,18 @@ export function buildServer() {
|
|
|
318
320
|
"already linked to an issue (creates a new one).",
|
|
319
321
|
inputSchema: {
|
|
320
322
|
ids: z.array(z.string()).min(1).describe("Comment ids to group into one issue (from the comments tool)"),
|
|
323
|
+
project: z.string().optional().describe("Which project (slug) — say it explicitly when the folder isn't a linked checkout. List them with list_projects."),
|
|
321
324
|
path: z.string().optional().describe("Project root (default: cwd)"),
|
|
322
325
|
title: z.string().optional().describe("Issue title (else derived from the comments)"),
|
|
323
326
|
repo: z.string().optional().describe("Target GitHub repo owner/repo (else gh's default for the repo)"),
|
|
324
327
|
force: z.boolean().optional().describe("File even if some selected comments are already tracked"),
|
|
325
328
|
},
|
|
326
|
-
}, async ({ ids, path, title, repo, force }) => {
|
|
327
|
-
const res = await fileCommentsIssue(rootOf(path), ids, { title, repo, force });
|
|
329
|
+
}, async ({ ids, path, title, repo, force, project }) => {
|
|
330
|
+
const res = await fileCommentsIssue(rootOf(path), ids, { title, repo, force, slug: project });
|
|
328
331
|
if (!res.ok) {
|
|
329
332
|
const hints = {
|
|
330
333
|
no_token: "Set ALKAHEST_TOKEN in this MCP server's config.",
|
|
331
|
-
no_slug: "
|
|
334
|
+
no_slug: "Pass `project` — a slug from list_projects — or set ALKAHEST_PROJECT in this MCP server's config.",
|
|
332
335
|
already_tracked: "Some comments already have an issue — pass force:true to file a new one.",
|
|
333
336
|
gh_failed: "Install and authenticate the GitHub CLI (`gh auth login`) for this repo.",
|
|
334
337
|
forbidden: "Only the project owner or a collaborator can file issues.",
|
|
@@ -342,7 +345,7 @@ export function buildServer() {
|
|
|
342
345
|
const issueHints = {
|
|
343
346
|
no_token: "Set ALKAHEST_TOKEN in this MCP server's config (token from alkahest.app → Account).",
|
|
344
347
|
no_api: "Set ALKAHEST_API_URL in this MCP server's config.",
|
|
345
|
-
no_slug: "
|
|
348
|
+
no_slug: "Pass `project` — a slug from list_projects — or set ALKAHEST_PROJECT in this MCP server's config.",
|
|
346
349
|
invalid_token: "The API token is invalid or revoked — create a new one at alkahest.app → Account.",
|
|
347
350
|
forbidden: "Only the project owner or a collaborator can write issues.",
|
|
348
351
|
not_found: "Not found — list ids with the issues tool, or the project's issue maps with the maps tool.",
|
|
@@ -360,15 +363,16 @@ export function buildServer() {
|
|
|
360
363
|
"question awaits an answer), and awaitingDecision (open_questions > 0) — use actionable issues to decide what " +
|
|
361
364
|
"to work on next. When you hit a decision you need the user to make mid-task, post it with ask_issue (the issue " +
|
|
362
365
|
"stops being actionable until they answer and you resolve_issue_question). Read the thread with issue_comments. " +
|
|
363
|
-
"Needs an API token and a
|
|
366
|
+
"Needs an API token and a named project — pass `project` (a slug from list_projects) unless the folder is a linked checkout or ALKAHEST_PROJECT is set. No publish required.",
|
|
364
367
|
inputSchema: {
|
|
368
|
+
project: z.string().optional().describe("Which project (slug) — say it explicitly when the folder isn't a linked checkout. List them with list_projects."),
|
|
365
369
|
path: z.string().optional().describe("Project root (default: cwd)"),
|
|
366
370
|
open: z.boolean().optional().describe("Only issues that are not done (default: false = all)"),
|
|
367
371
|
map: z.string().optional().describe("Restrict to one issue map (a project can hold several; omit when there's one). List them with the maps tool."),
|
|
368
372
|
q: z.string().optional().describe("Filter issues by title/body substring (server-side). Edges/links stay unfiltered, so blockedBy may name issues outside the filtered list."),
|
|
369
373
|
},
|
|
370
|
-
}, async ({ path, open, map, q }) => {
|
|
371
|
-
const res = await pullIssues(rootOf(path), { mapSlug: map, q });
|
|
374
|
+
}, async ({ path, open, map, q, project }) => {
|
|
375
|
+
const res = await pullIssues(rootOf(path), { mapSlug: map, q, slug: project });
|
|
372
376
|
if (!res.ok || !res.graph)
|
|
373
377
|
return issueFail("Read issues", res.code, res.message, res.maps);
|
|
374
378
|
const states = deriveIssueStates(res.graph);
|
|
@@ -405,16 +409,17 @@ export function buildServer() {
|
|
|
405
409
|
parent_id: z.string().optional().describe("Parent issue id — creates a contains edge (epic → task)"),
|
|
406
410
|
target: z.string().optional().describe("Code-map target: 's:…'/'r:…' node key, '/route' (planned screen), or a resource label"),
|
|
407
411
|
map: z.string().optional().describe("Which issue map to add to (a project can hold several; omit when there's one). List them with the maps tool, or create one with create_map."),
|
|
412
|
+
project: z.string().optional().describe("Which project (slug) — say it explicitly when the folder isn't a linked checkout. List them with list_projects."),
|
|
408
413
|
path: z.string().optional().describe("Project root (default: cwd)"),
|
|
409
414
|
},
|
|
410
|
-
}, async ({ title, type, status, body, priority, due_on, assignee_id, parent_id, target, map, path }) => {
|
|
415
|
+
}, async ({ title, type, status, body, priority, due_on, assignee_id, parent_id, target, map, path, project }) => {
|
|
411
416
|
const targetFields = target
|
|
412
417
|
? {
|
|
413
418
|
target_kind: (target.startsWith("s:") || target.startsWith("r:") ? "node" : target.startsWith("/") ? "route" : "resource"),
|
|
414
419
|
target_key: target,
|
|
415
420
|
}
|
|
416
421
|
: {};
|
|
417
|
-
const res = await createIssue(rootOf(path), { title, type, status, body, priority, due_on, assignee_id, parent_id, mapSlug: map, ...targetFields });
|
|
422
|
+
const res = await createIssue(rootOf(path), { title, type, status, body, priority, due_on, assignee_id, parent_id, mapSlug: map, slug: project, ...targetFields });
|
|
418
423
|
if (!res.ok || !res.issue)
|
|
419
424
|
return issueFail("Add issue", res.code, res.message, res.maps);
|
|
420
425
|
return json({ ok: true, issue: res.issue });
|
|
@@ -694,19 +699,20 @@ export function buildServer() {
|
|
|
694
699
|
"Use it before creating anything (does a note/issue/task on this topic already exist?) and before linking " +
|
|
695
700
|
"(which note should cite this issue?). Matching is a server-side substring over title + full body (and note " +
|
|
696
701
|
"slug); results come back compact — note bodies as 240-char excerpts (read one in full with get_note), issues " +
|
|
697
|
-
"with status/type, tasks with done state. Notes and issues need a
|
|
698
|
-
"reason if there isn't one); tasks are yours and always searchable. For a deeper dive into one kind, use the " +
|
|
702
|
+
"with status/type, tasks with done state. Notes and issues need a named project (they fail soft with a " +
|
|
703
|
+
"reason if there isn't one — pass `project`); tasks are yours and always searchable. For a deeper dive into one kind, use the " +
|
|
699
704
|
"notes / issues / list_tasks tools with their own `q`.",
|
|
700
705
|
inputSchema: {
|
|
701
706
|
q: z.string().describe("Text to find — substring match over titles and full bodies"),
|
|
707
|
+
project: z.string().optional().describe("Which project (slug) — say it explicitly when the folder isn't a linked checkout. List them with list_projects."),
|
|
702
708
|
path: z.string().optional().describe("Project root (default: cwd)"),
|
|
703
709
|
},
|
|
704
|
-
}, async ({ q, path }) => {
|
|
710
|
+
}, async ({ q, path, project }) => {
|
|
705
711
|
const root = rootOf(path);
|
|
706
712
|
const [notesRes, issuesRes, tasksRes] = await Promise.all([
|
|
707
|
-
pullNotes(root, { q, bodies: "excerpt" }),
|
|
708
|
-
pullIssues(root, { q }),
|
|
709
|
-
pullTasks(root, { q, status: "all" }),
|
|
713
|
+
pullNotes(root, { q, bodies: "excerpt", slug: project }),
|
|
714
|
+
pullIssues(root, { q, slug: project }),
|
|
715
|
+
pullTasks(root, { q, status: "all", project }),
|
|
710
716
|
]);
|
|
711
717
|
const notes = notesRes.ok && notesRes.maps
|
|
712
718
|
? notesRes.maps.flatMap((m) => m.notes.map((n) => ({ slug: n.slug, title: n.title, map: m.slug, folder: n.folder ?? null, excerpt: n.body ?? null })))
|
|
@@ -732,15 +738,16 @@ export function buildServer() {
|
|
|
732
738
|
"the notebook's property schema (key/type/options); notes carry their props values (reserved key `tags`). " +
|
|
733
739
|
"ALWAYS check this before add_note when " +
|
|
734
740
|
"recording knowledge: if a note on the topic exists, update_note it instead of adding a near-duplicate. " +
|
|
735
|
-
"`q` searches title/slug/FULL body server-side. Needs an API token and a
|
|
741
|
+
"`q` searches title/slug/FULL body server-side. Needs an API token and a named project — pass `project` (a slug from list_projects) unless the folder is a linked checkout or ALKAHEST_PROJECT is set. No publish required.",
|
|
736
742
|
inputSchema: {
|
|
743
|
+
project: z.string().optional().describe("Which project (slug) — say it explicitly when the folder isn't a linked checkout. List them with list_projects."),
|
|
737
744
|
path: z.string().optional().describe("Project root (default: cwd)"),
|
|
738
745
|
q: z.string().optional().describe("Filter notes by title/slug/body substring (matches the full body)"),
|
|
739
746
|
map: z.string().optional().describe("Restrict to one note map (default: all readable). List them with the maps tool."),
|
|
740
747
|
full_bodies: z.boolean().optional().describe("Return complete bodies instead of 240-char excerpts (heavy on a big wiki)"),
|
|
741
748
|
},
|
|
742
|
-
}, async ({ path, q, map, full_bodies }) => {
|
|
743
|
-
const res = await pullNotes(rootOf(path), { mapSlug: map, q, bodies: full_bodies ? undefined : "excerpt" });
|
|
749
|
+
}, async ({ path, q, map, full_bodies, project }) => {
|
|
750
|
+
const res = await pullNotes(rootOf(path), { mapSlug: map, q, bodies: full_bodies ? undefined : "excerpt", slug: project });
|
|
744
751
|
if (!res.ok || !res.maps)
|
|
745
752
|
return issueFail("Read notes", res.code, res.message, res.mapList);
|
|
746
753
|
return json({ ok: true, project: res.project, count: res.maps.reduce((n, m) => n + m.notes.length, 0), maps: res.maps });
|
|
@@ -752,10 +759,11 @@ export function buildServer() {
|
|
|
752
759
|
inputSchema: {
|
|
753
760
|
note: z.string().describe("Note slug (or id)"),
|
|
754
761
|
map: z.string().optional().describe("Which note map (omit when the slug is unique across maps)"),
|
|
762
|
+
project: z.string().optional().describe("Which project (slug) — say it explicitly when the folder isn't a linked checkout. List them with list_projects."),
|
|
755
763
|
path: z.string().optional().describe("Project root (default: cwd)"),
|
|
756
764
|
},
|
|
757
|
-
}, async ({ note, map, path }) => {
|
|
758
|
-
const res = await getNote(rootOf(path), { note, mapSlug: map });
|
|
765
|
+
}, async ({ note, map, path, project }) => {
|
|
766
|
+
const res = await getNote(rootOf(path), { note, mapSlug: map, slug: project });
|
|
759
767
|
if (!res.ok || !res.note)
|
|
760
768
|
return issueFail("Get note", res.code, res.message, res.mapList);
|
|
761
769
|
const { ok: _ok, code: _code, message: _message, mapList: _ml, ...rest } = res;
|
|
@@ -775,10 +783,11 @@ export function buildServer() {
|
|
|
775
783
|
folder: z.string().optional().describe("Tree-sidebar path like 'raw/articles' (omit = unfiled) — the web viewer's Obsidian-style tree groups by it"),
|
|
776
784
|
props: z.record(z.any()).optional().describe("Notebook properties (flat key→value): reserved key `tags` = string array; other keys should match the map's schema (see prop_defs in the notes tool) — unknown keys are kept but show as unregistered"),
|
|
777
785
|
map: z.string().optional().describe("Which note map to add to (a project can hold several; omit when there's one). List them with the maps tool, or create one with create_map."),
|
|
786
|
+
project: z.string().optional().describe("Which project (slug) — say it explicitly when the folder isn't a linked checkout. List them with list_projects."),
|
|
778
787
|
path: z.string().optional().describe("Project root (default: cwd)"),
|
|
779
788
|
},
|
|
780
|
-
}, async ({ title, body, note_slug, folder, props, map, path }) => {
|
|
781
|
-
const res = await createNote(rootOf(path), { title, body, note_slug, folder, props, mapSlug: map });
|
|
789
|
+
}, async ({ title, body, note_slug, folder, props, map, path, project }) => {
|
|
790
|
+
const res = await createNote(rootOf(path), { title, body, note_slug, folder, props, mapSlug: map, slug: project });
|
|
782
791
|
if (!res.ok || !res.note)
|
|
783
792
|
return issueFail("Add note", res.code, res.message, res.maps);
|
|
784
793
|
return json({ ok: true, note: res.note });
|
|
@@ -795,10 +804,11 @@ export function buildServer() {
|
|
|
795
804
|
from: z.string().describe("Source note slug (or id)"),
|
|
796
805
|
to: z.string().describe("Target: 'issue:<uuid>', or 'code:s:…' / 'code:r:…'"),
|
|
797
806
|
remove: z.boolean().optional().describe("true → disconnect from→to instead"),
|
|
807
|
+
project: z.string().optional().describe("Which project (slug) — say it explicitly when the folder isn't a linked checkout. List them with list_projects."),
|
|
798
808
|
path: z.string().optional().describe("Project root (default: cwd)"),
|
|
799
809
|
},
|
|
800
|
-
}, async ({ from, to, remove, path }) => {
|
|
801
|
-
const res = await linkNotes(rootOf(path), { from, to, remove });
|
|
810
|
+
}, async ({ from, to, remove, path, project }) => {
|
|
811
|
+
const res = await linkNotes(rootOf(path), { from, to, remove, slug: project });
|
|
802
812
|
if (!res.ok)
|
|
803
813
|
return issueFail(remove ? "Unlink notes" : "Link notes", res.code, res.message, res.maps);
|
|
804
814
|
return json({ ok: true, ...(remove ? { removed: `${from} → ${to}` } : { linked: `${from} → ${to}` }) });
|
|
@@ -812,10 +822,11 @@ export function buildServer() {
|
|
|
812
822
|
inputSchema: {
|
|
813
823
|
note: z.string().describe("Note slug (or id)"),
|
|
814
824
|
map: z.string().optional().describe("Target note map (a project can hold several; omit when there's one). List them with the maps tool."),
|
|
825
|
+
project: z.string().optional().describe("Which project (slug) — say it explicitly when the folder isn't a linked checkout. List them with list_projects."),
|
|
815
826
|
path: z.string().optional().describe("Project root (default: cwd)"),
|
|
816
827
|
},
|
|
817
|
-
}, async ({ note, map, path }) => {
|
|
818
|
-
const res = await mapNote(rootOf(path), { noteRef: note, mapSlug: map });
|
|
828
|
+
}, async ({ note, map, path, project }) => {
|
|
829
|
+
const res = await mapNote(rootOf(path), { noteRef: note, mapSlug: map, slug: project });
|
|
819
830
|
if (!res.ok)
|
|
820
831
|
return issueFail("Move note", res.code, res.message, res.maps);
|
|
821
832
|
return json({ ok: true, note: res.note, map: res.map });
|
|
@@ -841,10 +852,11 @@ export function buildServer() {
|
|
|
841
852
|
reason: z.string().optional().describe("REQUIRED with delete (≤200 chars): a one-line reason the user sees in the Trash and the activity journal — say WHY the note should go, not just 'cleanup'"),
|
|
842
853
|
restore: z.boolean().optional().describe("true → restore the note from the Trash (undoes a soft delete)"),
|
|
843
854
|
map: z.string().optional().describe("Which note map (a project can hold several; omit when there's one)"),
|
|
855
|
+
project: z.string().optional().describe("Which project (slug) — say it explicitly when the folder isn't a linked checkout. List them with list_projects."),
|
|
844
856
|
path: z.string().optional().describe("Project root (default: cwd)"),
|
|
845
857
|
},
|
|
846
|
-
}, async ({ note, title, body, new_slug, folder, props, delete: del, reason, restore, map, path }) => {
|
|
847
|
-
const res = await updateNote(rootOf(path), { note, title, body, new_slug, folder, props, delete: del, reason, restore, mapSlug: map });
|
|
858
|
+
}, async ({ note, title, body, new_slug, folder, props, delete: del, reason, restore, map, path, project }) => {
|
|
859
|
+
const res = await updateNote(rootOf(path), { note, title, body, new_slug, folder, props, delete: del, reason, restore, mapSlug: map, slug: project });
|
|
848
860
|
const what = del ? "Delete note" : restore ? "Restore note" : "Update note";
|
|
849
861
|
if (!res.ok)
|
|
850
862
|
return issueFail(what, res.code, res.message, res.maps);
|
|
@@ -877,13 +889,14 @@ export function buildServer() {
|
|
|
877
889
|
})).optional().describe("Definitions to register/merge onto the note map's schema"),
|
|
878
890
|
remove: z.array(z.string()).optional().describe("Property definition key(s) to unregister; note values are kept. Reserved key `tags` is refused."),
|
|
879
891
|
map: z.string().optional().describe("Which note map (a project can hold several; omit when there's one)"),
|
|
892
|
+
project: z.string().optional().describe("Which project (slug) — say it explicitly when the folder isn't a linked checkout. List them with list_projects."),
|
|
880
893
|
path: z.string().optional().describe("Project root (default: cwd)"),
|
|
881
894
|
},
|
|
882
|
-
}, async ({ define, remove, map, path }) => {
|
|
895
|
+
}, async ({ define, remove, map, path, project }) => {
|
|
883
896
|
if (!define?.length && !remove?.length) {
|
|
884
897
|
return json({ ok: false, error: "bad_request", message: "Pass `define` (definitions to register) and/or `remove` (keys to unregister)." });
|
|
885
898
|
}
|
|
886
|
-
const res = await editPropDefs(rootOf(path), { defs: define, remove, mapSlug: map });
|
|
899
|
+
const res = await editPropDefs(rootOf(path), { defs: define, remove, mapSlug: map, slug: project });
|
|
887
900
|
if (!res.ok)
|
|
888
901
|
return issueFail("Edit note props", res.code, res.message, res.maps);
|
|
889
902
|
return json({ ok: true, added: res.added ?? 0, merged: res.merged ?? 0, removed: res.removed ?? 0, skipped: res.skipped ?? 0 });
|
|
@@ -893,21 +906,24 @@ export function buildServer() {
|
|
|
893
906
|
description: "List the maps in this published project. A project is a container of many maps (ADR-011): code maps " +
|
|
894
907
|
"(published from a scan) and issue maps — each with a per-project slug, addressed at /p/:project/:map. Maps " +
|
|
895
908
|
"are equal (no default), so when a project has several of a type the publish / add_issue / issues tools return " +
|
|
896
|
-
"'ambiguous_map' — call this to see the slugs, then pass `map`. Needs an API token and a
|
|
909
|
+
"'ambiguous_map' — call this to see the slugs, then pass `map`. Needs an API token and a named project — pass `project` (a slug from list_projects) unless the folder is a linked checkout or ALKAHEST_PROJECT is set. No publish required.",
|
|
897
910
|
inputSchema: {
|
|
911
|
+
project: z.string().optional().describe("Which project (slug) — say it explicitly when the folder isn't a linked checkout. List them with list_projects."),
|
|
898
912
|
path: z.string().optional().describe("Project root (default: cwd)"),
|
|
899
913
|
type: z.enum(["code", "issue", "note"]).optional().describe("Restrict to one type (default: all)"),
|
|
900
914
|
},
|
|
901
|
-
}, async ({ path, type }) => {
|
|
902
|
-
const res = await listMaps(rootOf(path), { type });
|
|
915
|
+
}, async ({ path, type, project }) => {
|
|
916
|
+
const res = await listMaps(rootOf(path), { type, slug: project });
|
|
903
917
|
if (!res.ok || !res.maps)
|
|
904
918
|
return issueFail("List maps", res.code, res.message);
|
|
905
919
|
return json({ ok: true, slug: res.slug, count: res.maps.length, maps: res.maps });
|
|
906
920
|
});
|
|
907
921
|
server.registerTool("list_projects", {
|
|
908
922
|
title: "List account projects & workspaces",
|
|
909
|
-
description: "List every workspace and project this account's token can reach (ADR-022).
|
|
910
|
-
"
|
|
923
|
+
description: "List every workspace and project this account's token can reach (ADR-022). **This is how you get the `project` " +
|
|
924
|
+
"slug** the notes / issues / maps tools take — call it whenever the user names a project in words ('my wiki') " +
|
|
925
|
+
"and you don't have its slug, or when a tool answers no_slug. Also useful " +
|
|
926
|
+
"to recover the right publish target after the local link was lost (a workspace move, a fresh clone, CI), " +
|
|
911
927
|
"or before publishing to confirm which existing project to update instead of creating a duplicate. Each project " +
|
|
912
928
|
"includes isOwner (only owned projects can be re-published/overwritten) and per-code-map fingerprints " +
|
|
913
929
|
"(screens/resources counts) so you can match a local scan by structure. Needs an API token; no project context.",
|
|
@@ -938,14 +954,16 @@ export function buildServer() {
|
|
|
938
954
|
description: "Show a code map's publish timeline (ADR-023) — when each publish happened, the screen/resource/" +
|
|
939
955
|
"transition counts, and which nodes were added/removed since the previous publish. Use it to answer " +
|
|
940
956
|
"'when did this last publish' and 'what changed' without diffing manually. Needs an API token and a " +
|
|
941
|
-
"
|
|
957
|
+
"named project (see `project`); `map` picks the code map when the project has several (else the oldest). " +
|
|
958
|
+
"This one DOES read published versions — an unpublished project simply has no history yet.",
|
|
942
959
|
inputSchema: {
|
|
960
|
+
project: z.string().optional().describe("Which project (slug) — say it explicitly when the folder isn't a linked checkout. List them with list_projects."),
|
|
943
961
|
path: z.string().optional().describe("Project root (default: cwd)"),
|
|
944
962
|
map: z.string().optional().describe("Which code map (default: this checkout's / the oldest)"),
|
|
945
963
|
limit: z.number().optional().describe("Max versions (default 50)"),
|
|
946
964
|
},
|
|
947
|
-
}, async ({ path, map, limit }) => {
|
|
948
|
-
const res = await listHistory(rootOf(path), { map, limit });
|
|
965
|
+
}, async ({ path, map, limit, project }) => {
|
|
966
|
+
const res = await listHistory(rootOf(path), { map, limit, slug: project });
|
|
949
967
|
if (!res.ok || !res.versions)
|
|
950
968
|
return issueFail("History", res.code, res.message);
|
|
951
969
|
// Newest first; include count deltas vs the previous version so the agent needn't recompute.
|
|
@@ -975,10 +993,11 @@ export function buildServer() {
|
|
|
975
993
|
slug: z.string().describe("The new map's slug (lowercase letters, numbers, dashes; the server slugifies)"),
|
|
976
994
|
type: z.enum(["code", "issue", "note"]).optional().describe("Map type (default: issue)"),
|
|
977
995
|
name: z.string().optional().describe("Display name (defaults to the slug)"),
|
|
996
|
+
project: z.string().optional().describe("Which project (slug) — say it explicitly when the folder isn't a linked checkout. List them with list_projects."),
|
|
978
997
|
path: z.string().optional().describe("Project root (default: cwd)"),
|
|
979
998
|
},
|
|
980
|
-
}, async ({ slug, type, name, path }) => {
|
|
981
|
-
const res = await createMap(rootOf(path), { mapSlug: slug, type, mapName: name });
|
|
999
|
+
}, async ({ slug, type, name, path, project }) => {
|
|
1000
|
+
const res = await createMap(rootOf(path), { mapSlug: slug, type, mapName: name, slug: project });
|
|
982
1001
|
if (!res.ok || !res.map)
|
|
983
1002
|
return issueFail("Create map", res.code, res.message);
|
|
984
1003
|
return json({ ok: true, map: res.map });
|
|
@@ -1059,10 +1078,11 @@ export function buildServer() {
|
|
|
1059
1078
|
issue: z.string().describe("Issue id (from the issues tool)"),
|
|
1060
1079
|
map: z.string().optional().describe("Which issue map (a project can hold several; omit when there's one). List them with the maps tool."),
|
|
1061
1080
|
remove: z.boolean().optional().describe("true → take the issue off the map (the issue itself is never deleted)"),
|
|
1081
|
+
project: z.string().optional().describe("Which project (slug) — say it explicitly when the folder isn't a linked checkout. List them with list_projects."),
|
|
1062
1082
|
path: z.string().optional().describe("Project root (default: cwd)"),
|
|
1063
1083
|
},
|
|
1064
|
-
}, async ({ issue, map, remove, path }) => {
|
|
1065
|
-
const res = await mapIssue(rootOf(path), { issueId: issue, mapSlug: map, remove });
|
|
1084
|
+
}, async ({ issue, map, remove, path, project }) => {
|
|
1085
|
+
const res = await mapIssue(rootOf(path), { issueId: issue, mapSlug: map, remove, slug: project });
|
|
1066
1086
|
if (!res.ok)
|
|
1067
1087
|
return issueFail(remove ? "Unmap issue" : "Map issue", res.code, res.message, res.maps);
|
|
1068
1088
|
return json({ ok: true, issue: res.issue, map: res.map, member: res.member });
|
|
@@ -1078,10 +1098,11 @@ export function buildServer() {
|
|
|
1078
1098
|
inputSchema: {
|
|
1079
1099
|
issue: z.string().optional().describe("Restrict to one issue's thread (issue id from the issues tool)"),
|
|
1080
1100
|
open: z.boolean().optional().describe("Only unresolved comments — the decisions still awaiting an answer (default: false)"),
|
|
1101
|
+
project: z.string().optional().describe("Which project (slug) — say it explicitly when the folder isn't a linked checkout. List them with list_projects."),
|
|
1081
1102
|
path: z.string().optional().describe("Project root (default: cwd)"),
|
|
1082
1103
|
},
|
|
1083
|
-
}, async ({ issue, open, path }) => {
|
|
1084
|
-
const res = await pullIssueComments(rootOf(path), { issue, open });
|
|
1104
|
+
}, async ({ issue, open, path, project }) => {
|
|
1105
|
+
const res = await pullIssueComments(rootOf(path), { issue, open, slug: project });
|
|
1085
1106
|
if (!res.ok || !res.comments)
|
|
1086
1107
|
return issueFail("Read issue comments", res.code, res.message);
|
|
1087
1108
|
return json({ ok: true, count: res.comments.length, comments: res.comments });
|
|
@@ -1154,14 +1175,15 @@ export function buildServer() {
|
|
|
1154
1175
|
id: z.string().describe("Issue id to complete (from the issues tool)"),
|
|
1155
1176
|
result: z.string().describe("What you did / the outcome — recorded as a 'result' comment on the issue"),
|
|
1156
1177
|
status: z.string().optional().describe("Terminal status id from issue_config (default: the first terminal status, usually 'done')"),
|
|
1178
|
+
project: z.string().optional().describe("Which project (slug) — say it explicitly when the folder isn't a linked checkout. List them with list_projects."),
|
|
1157
1179
|
path: z.string().optional().describe("Project root (default: cwd)"),
|
|
1158
1180
|
},
|
|
1159
|
-
}, async ({ id, result, status, path }) => {
|
|
1181
|
+
}, async ({ id, result, status, path, project }) => {
|
|
1160
1182
|
const root = rootOf(path);
|
|
1161
1183
|
// Resolve the terminal status to move to (explicit, else the project's first terminal status).
|
|
1162
1184
|
let target = status;
|
|
1163
1185
|
if (!target) {
|
|
1164
|
-
const g = await pullIssues(root, {});
|
|
1186
|
+
const g = await pullIssues(root, { slug: project });
|
|
1165
1187
|
if (!g.ok || !g.graph)
|
|
1166
1188
|
return issueFail("Complete issue", g.code, g.message, g.maps);
|
|
1167
1189
|
const terminal = [...terminalStatuses(g.graph.issue_config)];
|