@chapterai/mcp 0.1.0 → 0.1.2

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.
Files changed (2) hide show
  1. package/dist/index.js +732 -382
  2. package/package.json +12 -11
package/dist/index.js CHANGED
@@ -22352,30 +22352,30 @@ function getClient() {
22352
22352
  return client;
22353
22353
  }
22354
22354
  var api = {
22355
- projects: {
22355
+ workspaces: {
22356
22356
  list(input) {
22357
- return getClient().projects.list.query(input);
22357
+ return getClient().workspaces.list.query(input);
22358
22358
  },
22359
22359
  getById(input) {
22360
- return getClient().projects.getById.query(input);
22360
+ return getClient().workspaces.getById.query(input);
22361
22361
  },
22362
22362
  getReadme(input) {
22363
- return getClient().projects.getReadme.query(input);
22363
+ return getClient().workspaces.getReadme.query(input);
22364
22364
  },
22365
22365
  listFiles(input) {
22366
- return getClient().projects.listFiles.query(input);
22366
+ return getClient().workspaces.listFiles.query(input);
22367
22367
  },
22368
22368
  listCommits(input) {
22369
- return getClient().projects.listCommits.query(input);
22369
+ return getClient().workspaces.listCommits.query(input);
22370
22370
  },
22371
22371
  readFile(input) {
22372
- return getClient().projects.readFile.query(input);
22372
+ return getClient().workspaces.readFile.query(input);
22373
22373
  },
22374
22374
  listChangedFiles(input) {
22375
- return getClient().projects.listChangedFiles.query(input);
22375
+ return getClient().workspaces.listChangedFiles.query(input);
22376
22376
  },
22377
22377
  getFileDiff(input) {
22378
- return getClient().projects.getFileDiff.query(input);
22378
+ return getClient().workspaces.getFileDiff.query(input);
22379
22379
  }
22380
22380
  },
22381
22381
  changeRequests: {
@@ -22414,26 +22414,52 @@ var api = {
22414
22414
  return getClient().search.semantic.query(input);
22415
22415
  }
22416
22416
  },
22417
- codeIntelligence: {
22418
- getProjectStructure(input) {
22419
- return getClient().codeIntelligence.getProjectStructure.query(input);
22417
+ tasks: {
22418
+ list(input) {
22419
+ return getClient().tasks.list.query(input);
22420
22420
  },
22421
- getFileSymbols(input) {
22422
- return getClient().codeIntelligence.getFileSymbols.query(input);
22421
+ getByNumber(input) {
22422
+ return getClient().tasks.getByNumber.query(input);
22423
22423
  },
22424
- getDependencies(input) {
22425
- return getClient().codeIntelligence.getDependencies.query(input);
22424
+ create(input) {
22425
+ return getClient().tasks.create.mutate(input);
22426
22426
  },
22427
- searchSymbols(input) {
22428
- return getClient().codeIntelligence.searchSymbols.query(input);
22427
+ update(input) {
22428
+ return getClient().tasks.update.mutate(input);
22429
+ }
22430
+ },
22431
+ activity: {
22432
+ feed(input) {
22433
+ return getClient().workspaces.getActivity.query(input);
22429
22434
  },
22430
- getImpactAnalysis(input) {
22431
- return getClient().codeIntelligence.getImpactAnalysis.query(
22432
- input
22433
- );
22435
+ fileHistory(input) {
22436
+ return getClient().workspaces.getFileHistory.query(input);
22437
+ },
22438
+ stats(input) {
22439
+ return getClient().workspaces.getWorkspaceStats.query(input);
22440
+ }
22441
+ },
22442
+ intelligence: {
22443
+ scan(input) {
22444
+ return getClient().intelligence.scan.mutate(input);
22445
+ },
22446
+ query(input) {
22447
+ return getClient().intelligence.query.query(input);
22448
+ },
22449
+ findSymbol(input) {
22450
+ return getClient().intelligence.findSymbol.query(input);
22451
+ },
22452
+ findCopy(input) {
22453
+ return getClient().intelligence.findCopy.query(input);
22454
+ },
22455
+ contextForTask(input) {
22456
+ return getClient().intelligence.contextForTask.query(input);
22434
22457
  },
22435
- reindex(input) {
22436
- return getClient().codeIntelligence.reindex.mutate(input);
22458
+ fileMap(input) {
22459
+ return getClient().intelligence.fileMap.query(input);
22460
+ },
22461
+ deps(input) {
22462
+ return getClient().intelligence.deps.query(input);
22437
22463
  }
22438
22464
  },
22439
22465
  sessions: {
@@ -22446,46 +22472,54 @@ var api = {
22446
22472
  }
22447
22473
  };
22448
22474
 
22449
- // src/lib/project-context.ts
22475
+ // src/lib/workspace-context.ts
22450
22476
  import fs2 from "fs";
22451
22477
  import path2 from "path";
22452
- var LINK_FILENAME = ".chapter.json";
22453
- function findProjectLink() {
22478
+ var LINK_PATH = path2.join(".chapter", "workspace.json");
22479
+ var LEGACY_LINK_FILENAME = ".chapter.json";
22480
+ function readLinkFile(filePath) {
22481
+ try {
22482
+ const raw = fs2.readFileSync(filePath, "utf-8");
22483
+ const data = JSON.parse(raw);
22484
+ const id = data.workspaceId ?? data.projectId;
22485
+ const name = data.workspaceName ?? data.projectName;
22486
+ if (!id || !name) return null;
22487
+ return { workspaceId: id, workspaceName: name };
22488
+ } catch {
22489
+ return null;
22490
+ }
22491
+ }
22492
+ function findWorkspaceLink() {
22454
22493
  let dir = process.cwd();
22455
22494
  while (true) {
22456
- const candidate = path2.join(dir, LINK_FILENAME);
22457
- if (fs2.existsSync(candidate)) {
22458
- try {
22459
- const raw = fs2.readFileSync(candidate, "utf-8");
22460
- return JSON.parse(raw);
22461
- } catch {
22462
- return null;
22463
- }
22464
- }
22495
+ const newPath = path2.join(dir, LINK_PATH);
22496
+ if (fs2.existsSync(newPath)) return readLinkFile(newPath);
22497
+ const legacyPath = path2.join(dir, LEGACY_LINK_FILENAME);
22498
+ if (fs2.existsSync(legacyPath)) return readLinkFile(legacyPath);
22465
22499
  const parent = path2.dirname(dir);
22466
22500
  if (parent === dir) break;
22467
22501
  dir = parent;
22468
22502
  }
22469
22503
  return null;
22470
22504
  }
22471
- async function resolveProjectId(projectId, projectName) {
22472
- if (projectId) return projectId;
22473
- if (projectName) {
22474
- const projects = await api.projects.list();
22475
- const match = projects.find(
22476
- (p) => p.name.toLowerCase() === projectName.toLowerCase()
22505
+ async function resolveWorkspaceId(workspaceId, workspaceName) {
22506
+ if (workspaceId) return workspaceId;
22507
+ if (workspaceName) {
22508
+ const workspaces = await api.workspaces.list();
22509
+ const match = workspaces.find(
22510
+ (p) => p.name.toLowerCase() === workspaceName.toLowerCase()
22477
22511
  );
22478
22512
  if (!match) {
22479
22513
  throw new Error(
22480
- `Project "${projectName}" not found. Use list_projects to see available projects.`
22514
+ `Project "${workspaceName}" not found. Use list_workspaces to see available projects.`
22481
22515
  );
22482
22516
  }
22483
22517
  return match.id;
22484
22518
  }
22485
- const link = findProjectLink();
22486
- if (link) return link.projectId;
22519
+ const link = findWorkspaceLink();
22520
+ if (link) return link.workspaceId;
22487
22521
  throw new Error(
22488
- "No project specified. Provide projectId, projectName, or run from a directory with a .chapter.json file."
22522
+ "No project specified. Provide workspaceId, workspaceName, or run from a directory linked to a Chapter workspace."
22489
22523
  );
22490
22524
  }
22491
22525
 
@@ -22515,24 +22549,24 @@ function success(text) {
22515
22549
  return { content: [{ type: "text", text }] };
22516
22550
  }
22517
22551
 
22518
- // src/tools/projects.ts
22519
- function registerProjectTools(server2) {
22552
+ // src/tools/workspaces.ts
22553
+ function registerWorkspaceTools(server2) {
22520
22554
  server2.tool(
22521
- "list_projects",
22522
- "List all accessible Chapter projects",
22555
+ "list_workspaces",
22556
+ "List all accessible Chapter workspaces",
22523
22557
  { orgId: external_exports.string().uuid().optional().describe("Filter by organization ID") },
22524
22558
  async ({ orgId }) => {
22525
22559
  try {
22526
- const projects = await api.projects.list(
22560
+ const workspaces = await api.workspaces.list(
22527
22561
  orgId ? { orgId } : void 0
22528
22562
  );
22529
- const lines = projects.map(
22563
+ const lines = workspaces.map(
22530
22564
  (p) => `- ${p.name} (id: ${p.id}, backend: ${p.backend})${p.description ? ` \u2014 ${p.description}` : ""}`
22531
22565
  );
22532
22566
  return success(
22533
22567
  lines.length > 0 ? `Found ${lines.length} project(s):
22534
22568
 
22535
- ${lines.join("\n")}` : "No projects found."
22569
+ ${lines.join("\n")}` : "No workspaces found."
22536
22570
  );
22537
22571
  } catch (error2) {
22538
22572
  return formatError2(error2);
@@ -22543,23 +22577,23 @@ ${lines.join("\n")}` : "No projects found."
22543
22577
  "get_project",
22544
22578
  "Get project details including README content",
22545
22579
  {
22546
- projectId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from .chapter.json if omitted)"),
22547
- projectName: external_exports.string().optional().describe("Project name (alternative to projectId)")
22580
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
22581
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)")
22548
22582
  },
22549
- async ({ projectId, projectName }) => {
22583
+ async ({ workspaceId, workspaceName }) => {
22550
22584
  try {
22551
- const id = await resolveProjectId(projectId, projectName);
22552
- const [project, readme] = await Promise.all([
22553
- api.projects.getById({ id }),
22554
- api.projects.getReadme({ projectId: id })
22585
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
22586
+ const [workspace, readme] = await Promise.all([
22587
+ api.workspaces.getById({ id }),
22588
+ api.workspaces.getReadme({ workspaceId: id })
22555
22589
  ]);
22556
22590
  const parts = [
22557
- `# ${project.name}`,
22558
- project.description ? `
22559
- ${project.description}` : "",
22591
+ `# ${workspace.name}`,
22592
+ workspace.description ? `
22593
+ ${workspace.description}` : "",
22560
22594
  `
22561
- Backend: ${project.backend}`,
22562
- `Created: ${project.createdAt}`,
22595
+ Backend: ${workspace.backend}`,
22596
+ `Created: ${workspace.createdAt}`,
22563
22597
  readme.content ? `
22564
22598
  ## README
22565
22599
 
@@ -22572,34 +22606,72 @@ ${readme.content}` : ""
22572
22606
  }
22573
22607
  );
22574
22608
  server2.tool(
22575
- "get_project_overview",
22576
- "Get complete project context in one call: README, file tree, recent commits, and open change requests. Ideal for onboarding into a project.",
22609
+ "get_workspace_overview",
22610
+ "Get complete workspace context in one call: README, file tree, recent commits, active tasks, and open changes. This is the best tool to start with when working on a workspace \u2014 it gives you full situational awareness.",
22577
22611
  {
22578
- projectId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from .chapter.json if omitted)"),
22579
- projectName: external_exports.string().optional().describe("Project name (alternative to projectId)")
22612
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
22613
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)")
22580
22614
  },
22581
- async ({ projectId, projectName }) => {
22615
+ async ({ workspaceId, workspaceName }) => {
22582
22616
  try {
22583
- const id = await resolveProjectId(projectId, projectName);
22584
- const [project, readme, files, commits, changeRequests] = await Promise.all([
22585
- api.projects.getById({ id }),
22586
- api.projects.getReadme({ projectId: id }),
22587
- api.projects.listFiles({ projectId: id, path: "" }),
22588
- api.projects.listCommits({ projectId: id, limit: 10 }),
22617
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
22618
+ const [workspace, readme, files, commits, openCRs, appliedCRs, activeTasks, todoTasks, recentSessions] = await Promise.all([
22619
+ api.workspaces.getById({ id }),
22620
+ api.workspaces.getReadme({ workspaceId: id }),
22621
+ api.workspaces.listFiles({ workspaceId: id, path: "" }),
22622
+ api.workspaces.listCommits({ workspaceId: id, limit: 10 }),
22589
22623
  api.changeRequests.list({
22590
- projectId: id,
22624
+ workspaceId: id,
22591
22625
  status: "open",
22592
- limit: 10
22593
- })
22626
+ limit: 5
22627
+ }),
22628
+ api.changeRequests.list({
22629
+ workspaceId: id,
22630
+ status: "applied",
22631
+ limit: 5
22632
+ }),
22633
+ api.tasks.list({ workspaceId: id, status: "in_progress" }),
22634
+ api.tasks.list({ workspaceId: id, status: "todo" }),
22635
+ api.sessions.list({ workspaceId: id, status: "completed", limit: 5 })
22594
22636
  ]);
22595
22637
  const parts = [];
22596
- parts.push(`# ${project.name}`);
22597
- if (project.description) parts.push(project.description);
22598
- parts.push(`Backend: ${project.backend}`);
22638
+ parts.push(`# ${workspace.name}`);
22639
+ if (workspace.description) parts.push(workspace.description);
22640
+ parts.push(`Backend: ${workspace.backend}`);
22599
22641
  if (readme.content) {
22600
22642
  parts.push("\n## README\n");
22601
22643
  parts.push(readme.content);
22602
22644
  }
22645
+ const allActiveTasks = [...activeTasks, ...todoTasks];
22646
+ if (allActiveTasks.length > 0) {
22647
+ parts.push("\n## Active Tasks\n");
22648
+ parts.push(
22649
+ allActiveTasks.map((t) => {
22650
+ const priority = t.priority !== "none" ? ` (${t.priority})` : "";
22651
+ const status = t.status === "in_progress" ? " [IN PROGRESS]" : "";
22652
+ return `- #${t.number}: ${t.title}${status}${priority}${t.description ? `
22653
+ ${t.description.slice(0, 200)}` : ""}`;
22654
+ }).join("\n")
22655
+ );
22656
+ }
22657
+ const sessionsWithSummaries = recentSessions.filter((s) => s.summary);
22658
+ if (sessionsWithSummaries.length > 0) {
22659
+ parts.push("\n## Recent Sessions (what's been done)\n");
22660
+ parts.push(
22661
+ sessionsWithSummaries.map((s) => {
22662
+ try {
22663
+ const parsed = JSON.parse(s.summary);
22664
+ const lines = [`- **${s.title ?? "(untitled)"}** (${new Date(s.createdAt).toLocaleDateString()})`];
22665
+ lines.push(` Asked: ${parsed.whatWasAsked}`);
22666
+ lines.push(` Done: ${parsed.whatWasDone}`);
22667
+ if (parsed.whatToWatch) lines.push(` Watch: ${parsed.whatToWatch}`);
22668
+ return lines.join("\n");
22669
+ } catch {
22670
+ return `- ${s.title ?? "(untitled)"} (${new Date(s.createdAt).toLocaleDateString()})`;
22671
+ }
22672
+ }).join("\n")
22673
+ );
22674
+ }
22603
22675
  parts.push("\n## File Tree (root)\n");
22604
22676
  if (files.length > 0) {
22605
22677
  parts.push(
@@ -22608,7 +22680,7 @@ ${readme.content}` : ""
22608
22680
  } else {
22609
22681
  parts.push("(empty)");
22610
22682
  }
22611
- parts.push("\n## Recent Changes\n");
22683
+ parts.push("\n## Recent Commits\n");
22612
22684
  if (commits.length > 0) {
22613
22685
  parts.push(
22614
22686
  commits.map(
@@ -22618,15 +22690,23 @@ ${readme.content}` : ""
22618
22690
  } else {
22619
22691
  parts.push("No commits yet.");
22620
22692
  }
22621
- parts.push("\n## Open Change Requests\n");
22622
- if (changeRequests.length > 0) {
22693
+ if (appliedCRs.length > 0) {
22694
+ parts.push("\n## Recently Shipped Changes\n");
22695
+ parts.push(
22696
+ appliedCRs.map((cr) => {
22697
+ const summary = cr.aiSummary ? `
22698
+ ${cr.aiSummary}` : "";
22699
+ return `- #${cr.number}: ${cr.title} (${cr.filesChanged} files, +${cr.additions}/-${cr.deletions})${summary}`;
22700
+ }).join("\n")
22701
+ );
22702
+ }
22703
+ if (openCRs.length > 0) {
22704
+ parts.push("\n## Open Changes (pending review)\n");
22623
22705
  parts.push(
22624
- changeRequests.map(
22706
+ openCRs.map(
22625
22707
  (cr) => `- #${cr.number}: ${cr.title} (${cr.filesChanged} files, +${cr.additions}/-${cr.deletions})`
22626
22708
  ).join("\n")
22627
22709
  );
22628
- } else {
22629
- parts.push("No open change requests.");
22630
22710
  }
22631
22711
  return success(parts.join("\n"));
22632
22712
  } catch (error2) {
@@ -22640,18 +22720,18 @@ ${readme.content}` : ""
22640
22720
  function registerFileTools(server2) {
22641
22721
  server2.tool(
22642
22722
  "list_files",
22643
- "Browse files and directories at a path in a project",
22723
+ "Browse files and directories at a path in a workspace",
22644
22724
  {
22645
- projectId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from .chapter.json if omitted)"),
22646
- projectName: external_exports.string().optional().describe("Project name (alternative to projectId)"),
22725
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
22726
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
22647
22727
  path: external_exports.string().default("").describe("Directory path to list (empty string for root)"),
22648
22728
  ref: external_exports.string().optional().describe("Git ref (branch, tag, or commit SHA)")
22649
22729
  },
22650
- async ({ projectId, projectName, path: path3, ref }) => {
22730
+ async ({ workspaceId, workspaceName, path: path3, ref }) => {
22651
22731
  try {
22652
- const id = await resolveProjectId(projectId, projectName);
22653
- const files = await api.projects.listFiles({
22654
- projectId: id,
22732
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
22733
+ const files = await api.workspaces.listFiles({
22734
+ workspaceId: id,
22655
22735
  path: path3,
22656
22736
  ref
22657
22737
  });
@@ -22669,18 +22749,18 @@ function registerFileTools(server2) {
22669
22749
  );
22670
22750
  server2.tool(
22671
22751
  "read_file",
22672
- "Read the content of a file in a project",
22752
+ "Read the content of a file in a workspace",
22673
22753
  {
22674
- projectId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from .chapter.json if omitted)"),
22675
- projectName: external_exports.string().optional().describe("Project name (alternative to projectId)"),
22754
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
22755
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
22676
22756
  path: external_exports.string().describe("File path to read"),
22677
22757
  ref: external_exports.string().optional().describe("Git ref (branch, tag, or commit SHA)")
22678
22758
  },
22679
- async ({ projectId, projectName, path: path3, ref }) => {
22759
+ async ({ workspaceId, workspaceName, path: path3, ref }) => {
22680
22760
  try {
22681
- const id = await resolveProjectId(projectId, projectName);
22682
- const result = await api.projects.readFile({
22683
- projectId: id,
22761
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
22762
+ const result = await api.workspaces.readFile({
22763
+ workspaceId: id,
22684
22764
  path: path3,
22685
22765
  ref
22686
22766
  });
@@ -22692,24 +22772,24 @@ function registerFileTools(server2) {
22692
22772
  );
22693
22773
  server2.tool(
22694
22774
  "search_files",
22695
- "Find files by name pattern in a project (recursive, client-side filtering)",
22775
+ "Find files by name pattern in a workspace (recursive, client-side filtering)",
22696
22776
  {
22697
- projectId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from .chapter.json if omitted)"),
22698
- projectName: external_exports.string().optional().describe("Project name (alternative to projectId)"),
22777
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
22778
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
22699
22779
  pattern: external_exports.string().describe("Pattern to match against file names (case-insensitive substring match)"),
22700
22780
  path: external_exports.string().default("").describe("Directory path to search from")
22701
22781
  },
22702
- async ({ projectId, projectName, pattern, path: path3 }) => {
22782
+ async ({ workspaceId, workspaceName, pattern, path: path3 }) => {
22703
22783
  try {
22704
- const id = await resolveProjectId(projectId, projectName);
22784
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
22705
22785
  const matches = [];
22706
22786
  const lowerPattern = pattern.toLowerCase();
22707
22787
  const maxResults = 50;
22708
22788
  const maxDepth = 5;
22709
22789
  async function searchDir(dirPath, depth) {
22710
22790
  if (depth > maxDepth || matches.length >= maxResults) return;
22711
- const files = await api.projects.listFiles({
22712
- projectId: id,
22791
+ const files = await api.workspaces.listFiles({
22792
+ workspaceId: id,
22713
22793
  path: dirPath
22714
22794
  });
22715
22795
  for (const f of files) {
@@ -22741,30 +22821,30 @@ function registerFileTools(server2) {
22741
22821
  function registerChangeRequestTools(server2) {
22742
22822
  server2.tool(
22743
22823
  "list_change_requests",
22744
- "List change requests for a project, optionally filtered by status",
22824
+ "List changes for a workspace, optionally filtered by status",
22745
22825
  {
22746
- projectId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from .chapter.json if omitted)"),
22747
- projectName: external_exports.string().optional().describe("Project name (alternative to projectId)"),
22826
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
22827
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
22748
22828
  status: external_exports.enum(["draft", "open", "applied", "dismissed"]).optional().describe("Filter by status"),
22749
- limit: external_exports.number().min(1).max(100).default(20).describe("Max results to return")
22829
+ limit: external_exports.coerce.number().min(1).max(100).default(20).describe("Max results to return")
22750
22830
  },
22751
- async ({ projectId, projectName, status, limit }) => {
22831
+ async ({ workspaceId, workspaceName, status, limit }) => {
22752
22832
  try {
22753
- const id = await resolveProjectId(projectId, projectName);
22833
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
22754
22834
  const crs = await api.changeRequests.list({
22755
- projectId: id,
22835
+ workspaceId: id,
22756
22836
  status,
22757
22837
  limit
22758
22838
  });
22759
22839
  if (crs.length === 0) {
22760
22840
  return success(
22761
- status ? `No ${status} change requests found.` : "No change requests found."
22841
+ status ? `No ${status} changes found.` : "No changes found."
22762
22842
  );
22763
22843
  }
22764
22844
  const lines = crs.map(
22765
22845
  (cr) => `- #${cr.number}: ${cr.title} [${cr.status}] (${cr.filesChanged} files, +${cr.additions}/-${cr.deletions})${cr.author ? ` by ${cr.author.name}` : ""}`
22766
22846
  );
22767
- return success(`Found ${crs.length} change request(s):
22847
+ return success(`Found ${crs.length} change(s):
22768
22848
 
22769
22849
  ${lines.join("\n")}`);
22770
22850
  } catch (error2) {
@@ -22774,21 +22854,21 @@ ${lines.join("\n")}`);
22774
22854
  );
22775
22855
  server2.tool(
22776
22856
  "get_change_request",
22777
- "Get details of a specific change request including changed files and AI summary",
22857
+ "Get details of a specific change including changed files and AI summary",
22778
22858
  {
22779
- projectId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from .chapter.json if omitted)"),
22780
- projectName: external_exports.string().optional().describe("Project name (alternative to projectId)"),
22781
- number: external_exports.number().describe("Change request number")
22859
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
22860
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
22861
+ number: external_exports.coerce.number().describe("Change number")
22782
22862
  },
22783
- async ({ projectId, projectName, number: number3 }) => {
22863
+ async ({ workspaceId, workspaceName, number: number3 }) => {
22784
22864
  try {
22785
- const id = await resolveProjectId(projectId, projectName);
22865
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
22786
22866
  const cr = await api.changeRequests.getByNumber({
22787
- projectId: id,
22867
+ workspaceId: id,
22788
22868
  number: number3
22789
22869
  });
22790
22870
  const changedFiles = await api.changeRequests.listChangedFiles({
22791
- projectId: id,
22871
+ workspaceId: id,
22792
22872
  changeRequestId: cr.id
22793
22873
  });
22794
22874
  const parts = [
@@ -22823,15 +22903,15 @@ ${cr.aiSummary}`);
22823
22903
  );
22824
22904
  server2.tool(
22825
22905
  "list_branches",
22826
- "List branches available for creating change requests. Shows branches ahead of main with linked session info.",
22906
+ "List branches available for creating changes. Shows branches ahead of main with linked session info.",
22827
22907
  {
22828
- projectId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from .chapter.json if omitted)"),
22829
- projectName: external_exports.string().optional().describe("Project name (alternative to projectId)")
22908
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
22909
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)")
22830
22910
  },
22831
- async ({ projectId, projectName }) => {
22911
+ async ({ workspaceId, workspaceName }) => {
22832
22912
  try {
22833
- const id = await resolveProjectId(projectId, projectName);
22834
- const branches = await api.changeRequests.listBranches({ projectId: id });
22913
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
22914
+ const branches = await api.changeRequests.listBranches({ workspaceId: id });
22835
22915
  if (branches.length === 0) {
22836
22916
  return success("No draft branches found. Start a coding session to create one.");
22837
22917
  }
@@ -22849,25 +22929,25 @@ ${lines.join("\n")}`);
22849
22929
  );
22850
22930
  server2.tool(
22851
22931
  "create_change_request",
22852
- "Create a new change request from a source branch",
22932
+ "Create a new change from a source branch",
22853
22933
  {
22854
- projectId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from .chapter.json if omitted)"),
22855
- projectName: external_exports.string().optional().describe("Project name (alternative to projectId)"),
22856
- title: external_exports.string().min(1).max(500).describe("Title for the change request"),
22934
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
22935
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
22936
+ title: external_exports.string().min(1).max(500).describe("Title for the change"),
22857
22937
  description: external_exports.string().max(5e3).optional().describe("Description of the changes"),
22858
22938
  sourceBranch: external_exports.string().min(1).describe("Source branch name \u2014 all commits ahead of main form the CR")
22859
22939
  },
22860
- async ({ projectId, projectName, title, description, sourceBranch }) => {
22940
+ async ({ workspaceId, workspaceName, title, description, sourceBranch }) => {
22861
22941
  try {
22862
- const id = await resolveProjectId(projectId, projectName);
22942
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
22863
22943
  const cr = await api.changeRequests.create({
22864
- projectId: id,
22944
+ workspaceId: id,
22865
22945
  title,
22866
22946
  description,
22867
22947
  sourceBranch
22868
22948
  });
22869
22949
  return success(
22870
- `Created change request #${cr.number}: "${cr.title}" (id: ${cr.id})`
22950
+ `Created change #${cr.number}: "${cr.title}" (id: ${cr.id})`
22871
22951
  );
22872
22952
  } catch (error2) {
22873
22953
  return formatError2(error2);
@@ -22876,24 +22956,24 @@ ${lines.join("\n")}`);
22876
22956
  );
22877
22957
  server2.tool(
22878
22958
  "apply_change_request",
22879
- "Apply (merge) a change request",
22959
+ "Apply (merge) a change",
22880
22960
  {
22881
- projectId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from .chapter.json if omitted)"),
22882
- projectName: external_exports.string().optional().describe("Project name (alternative to projectId)"),
22883
- number: external_exports.number().describe("Change request number")
22961
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
22962
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
22963
+ number: external_exports.coerce.number().describe("Change number")
22884
22964
  },
22885
- async ({ projectId, projectName, number: number3 }) => {
22965
+ async ({ workspaceId, workspaceName, number: number3 }) => {
22886
22966
  try {
22887
- const id = await resolveProjectId(projectId, projectName);
22967
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
22888
22968
  const cr = await api.changeRequests.getByNumber({
22889
- projectId: id,
22969
+ workspaceId: id,
22890
22970
  number: number3
22891
22971
  });
22892
22972
  await api.changeRequests.applyChanges({
22893
- projectId: id,
22973
+ workspaceId: id,
22894
22974
  changeRequestId: cr.id
22895
22975
  });
22896
- return success(`Change request #${number3} has been applied.`);
22976
+ return success(`Change #${number3} has been applied.`);
22897
22977
  } catch (error2) {
22898
22978
  return formatError2(error2);
22899
22979
  }
@@ -22901,24 +22981,24 @@ ${lines.join("\n")}`);
22901
22981
  );
22902
22982
  server2.tool(
22903
22983
  "dismiss_change_request",
22904
- "Dismiss (close without applying) a change request",
22984
+ "Dismiss (close without applying) a change",
22905
22985
  {
22906
- projectId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from .chapter.json if omitted)"),
22907
- projectName: external_exports.string().optional().describe("Project name (alternative to projectId)"),
22908
- number: external_exports.number().describe("Change request number")
22986
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
22987
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
22988
+ number: external_exports.coerce.number().describe("Change number")
22909
22989
  },
22910
- async ({ projectId, projectName, number: number3 }) => {
22990
+ async ({ workspaceId, workspaceName, number: number3 }) => {
22911
22991
  try {
22912
- const id = await resolveProjectId(projectId, projectName);
22992
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
22913
22993
  const cr = await api.changeRequests.getByNumber({
22914
- projectId: id,
22994
+ workspaceId: id,
22915
22995
  number: number3
22916
22996
  });
22917
22997
  await api.changeRequests.dismiss({
22918
- projectId: id,
22998
+ workspaceId: id,
22919
22999
  changeRequestId: cr.id
22920
23000
  });
22921
- return success(`Change request #${number3} has been dismissed.`);
23001
+ return success(`Change #${number3} has been dismissed.`);
22922
23002
  } catch (error2) {
22923
23003
  return formatError2(error2);
22924
23004
  }
@@ -22926,12 +23006,12 @@ ${lines.join("\n")}`);
22926
23006
  );
22927
23007
  server2.tool(
22928
23008
  "add_comment",
22929
- "Add a comment to a change request. Can be a general comment or an inline code comment (provide path, line, and side for inline).",
23009
+ "Add a comment to a change. Can be a general comment or an inline code comment (provide path, line, and side for inline).",
22930
23010
  {
22931
- changeRequestId: external_exports.string().uuid().describe("Change request ID"),
23011
+ changeRequestId: external_exports.string().uuid().describe("Change ID"),
22932
23012
  body: external_exports.string().min(1).max(1e4).describe("Comment text"),
22933
23013
  path: external_exports.string().optional().describe("File path (for inline code comments)"),
22934
- line: external_exports.number().int().positive().optional().describe("Line number (for inline code comments)"),
23014
+ line: external_exports.coerce.number().int().positive().optional().describe("Line number (for inline code comments)"),
22935
23015
  side: external_exports.enum(["LEFT", "RIGHT"]).optional().describe("Diff side (for inline code comments)")
22936
23016
  },
22937
23017
  async ({ changeRequestId, body, path: path3, line, side }) => {
@@ -22965,18 +23045,18 @@ ${h.lines.join("\n")}`
22965
23045
  function registerCommitTools(server2) {
22966
23046
  server2.tool(
22967
23047
  "list_commits",
22968
- "List recent commit history for a project",
23048
+ "List recent commit history for a workspace",
22969
23049
  {
22970
- projectId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from .chapter.json if omitted)"),
22971
- projectName: external_exports.string().optional().describe("Project name (alternative to projectId)"),
23050
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
23051
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
22972
23052
  ref: external_exports.string().optional().describe("Git ref (branch, tag, or commit SHA)"),
22973
- limit: external_exports.number().min(1).max(200).default(20).describe("Max commits to return")
23053
+ limit: external_exports.coerce.number().min(1).max(200).default(20).describe("Max commits to return")
22974
23054
  },
22975
- async ({ projectId, projectName, ref, limit }) => {
23055
+ async ({ workspaceId, workspaceName, ref, limit }) => {
22976
23056
  try {
22977
- const id = await resolveProjectId(projectId, projectName);
22978
- const commits = await api.projects.listCommits({
22979
- projectId: id,
23057
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
23058
+ const commits = await api.workspaces.listCommits({
23059
+ workspaceId: id,
22980
23060
  ref,
22981
23061
  limit
22982
23062
  });
@@ -22996,29 +23076,29 @@ ${lines.join("\n")}`);
22996
23076
  );
22997
23077
  server2.tool(
22998
23078
  "get_diff",
22999
- "Get the diff for a change request or a specific commit. Provide either changeRequestNumber or commitOid.",
23079
+ "Get the diff for a change or a specific commit. Provide either changeRequestNumber or commitOid.",
23000
23080
  {
23001
- projectId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from .chapter.json if omitted)"),
23002
- projectName: external_exports.string().optional().describe("Project name (alternative to projectId)"),
23003
- changeRequestNumber: external_exports.number().optional().describe("Change request number (to get the full CR diff)"),
23081
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
23082
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
23083
+ changeRequestNumber: external_exports.coerce.number().optional().describe("Change number (to get the full diff)"),
23004
23084
  commitOid: external_exports.string().optional().describe("Commit OID (to get a single commit diff)")
23005
23085
  },
23006
- async ({ projectId, projectName, changeRequestNumber, commitOid }) => {
23086
+ async ({ workspaceId, workspaceName, changeRequestNumber, commitOid }) => {
23007
23087
  try {
23008
23088
  if (!changeRequestNumber && !commitOid) {
23009
23089
  return formatError2(
23010
23090
  new Error("Provide either changeRequestNumber or commitOid.")
23011
23091
  );
23012
23092
  }
23013
- const id = await resolveProjectId(projectId, projectName);
23093
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
23014
23094
  const maxFiles = 10;
23015
23095
  if (changeRequestNumber) {
23016
23096
  const cr = await api.changeRequests.getByNumber({
23017
- projectId: id,
23097
+ workspaceId: id,
23018
23098
  number: changeRequestNumber
23019
23099
  });
23020
23100
  const changedFiles2 = await api.changeRequests.listChangedFiles({
23021
- projectId: id,
23101
+ workspaceId: id,
23022
23102
  changeRequestId: cr.id
23023
23103
  });
23024
23104
  const filesToShow2 = changedFiles2.slice(0, maxFiles);
@@ -23029,7 +23109,7 @@ ${lines.join("\n")}`);
23029
23109
  ];
23030
23110
  for (const file of filesToShow2) {
23031
23111
  const diff = await api.changeRequests.getFileDiff({
23032
- projectId: id,
23112
+ workspaceId: id,
23033
23113
  changeRequestId: cr.id,
23034
23114
  filePath: file.path
23035
23115
  });
@@ -23045,8 +23125,8 @@ ${lines.join("\n")}`);
23045
23125
  }
23046
23126
  return success(parts2.join("\n"));
23047
23127
  }
23048
- const changedFiles = await api.projects.listChangedFiles({
23049
- projectId: id,
23128
+ const changedFiles = await api.workspaces.listChangedFiles({
23129
+ workspaceId: id,
23050
23130
  commitOid
23051
23131
  });
23052
23132
  const filesToShow = changedFiles.slice(0, maxFiles);
@@ -23056,8 +23136,8 @@ ${lines.join("\n")}`);
23056
23136
  `
23057
23137
  ];
23058
23138
  for (const file of filesToShow) {
23059
- const diff = await api.projects.getFileDiff({
23060
- projectId: id,
23139
+ const diff = await api.workspaces.getFileDiff({
23140
+ workspaceId: id,
23061
23141
  commitOid,
23062
23142
  filePath: file.path
23063
23143
  });
@@ -23083,24 +23163,34 @@ ${lines.join("\n")}`);
23083
23163
  function registerSessionTools(server2) {
23084
23164
  server2.tool(
23085
23165
  "list_sessions",
23086
- "List AI coding sessions for the current user in a project",
23166
+ "List AI coding sessions in a workspace with summaries of what was done. Use this to understand previous work before starting a new session.",
23087
23167
  {
23088
- projectId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from .chapter.json if omitted)"),
23089
- projectName: external_exports.string().optional().describe("Project name (alternative to projectId)"),
23168
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
23169
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
23090
23170
  status: external_exports.enum(["active", "completed", "archived"]).optional().describe("Filter by status"),
23091
- limit: external_exports.number().min(1).max(100).default(20).describe("Max results to return")
23171
+ limit: external_exports.coerce.number().min(1).max(100).default(20).describe("Max results to return")
23092
23172
  },
23093
- async ({ projectId, projectName, status, limit }) => {
23173
+ async ({ workspaceId, workspaceName, status, limit }) => {
23094
23174
  try {
23095
- const id = await resolveProjectId(projectId, projectName);
23175
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
23096
23176
  const sessions = await api.sessions.list({
23097
- projectId: id,
23177
+ workspaceId: id,
23098
23178
  status,
23099
23179
  limit
23100
23180
  });
23101
- const lines = sessions.map(
23102
- (s) => `- ${s.title ?? "(untitled)"} (id: ${s.id}, by ${s.userName ?? "unknown"}, ${s.messageCount} messages, ${new Date(s.createdAt).toLocaleDateString()})`
23103
- );
23181
+ const lines = sessions.map((s) => {
23182
+ const parts = [
23183
+ `- ${s.title ?? "(untitled)"} (id: ${s.id}, by ${s.userName ?? "unknown"}, ${s.messageCount} messages, ${new Date(s.createdAt).toLocaleDateString()})`
23184
+ ];
23185
+ if (s.summary) {
23186
+ try {
23187
+ const parsed = JSON.parse(s.summary);
23188
+ parts.push(` \u2192 ${parsed.whatWasDone}`);
23189
+ } catch {
23190
+ }
23191
+ }
23192
+ return parts.join("\n");
23193
+ });
23104
23194
  return success(
23105
23195
  lines.length > 0 ? `Found ${lines.length} session(s):
23106
23196
 
@@ -23115,25 +23205,41 @@ ${lines.join("\n")}` : "No sessions found."
23115
23205
  "get_session",
23116
23206
  "Get a session with its full chat history",
23117
23207
  {
23118
- projectId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from .chapter.json if omitted)"),
23119
- projectName: external_exports.string().optional().describe("Project name (alternative to projectId)"),
23208
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
23209
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
23120
23210
  sessionId: external_exports.string().uuid().describe("Session ID")
23121
23211
  },
23122
- async ({ projectId, projectName, sessionId }) => {
23212
+ async ({ workspaceId, workspaceName, sessionId }) => {
23123
23213
  try {
23124
- const id = await resolveProjectId(projectId, projectName);
23214
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
23125
23215
  const session = await api.sessions.getById({
23126
- projectId: id,
23216
+ workspaceId: id,
23127
23217
  sessionId
23128
23218
  });
23129
23219
  const parts = [];
23130
23220
  parts.push(`# ${session.title ?? "(untitled)"}`);
23131
- if (session.toolName) parts.push(`Tool: ${session.toolName}`);
23221
+ if (session.client) parts.push(`Client: ${session.client}`);
23132
23222
  parts.push(`Status: ${session.status}`);
23133
23223
  if (session.branch) parts.push(`Branch: ${session.branch}`);
23134
23224
  if (session.changeRequestNumber != null)
23135
- parts.push(`Change Request: #${session.changeRequestNumber}`);
23225
+ parts.push(`Change: #${session.changeRequestNumber}`);
23226
+ if (session.task)
23227
+ parts.push(`Task: #${session.task.number} ${session.task.title} [${session.task.status}]`);
23136
23228
  parts.push(`Created: ${session.createdAt}`);
23229
+ if (session.summary) {
23230
+ try {
23231
+ const s = JSON.parse(session.summary);
23232
+ parts.push("\n## Summary\n");
23233
+ parts.push(`**Asked:** ${s.whatWasAsked}`);
23234
+ parts.push(`**Done:** ${s.whatWasDone}`);
23235
+ if (s.whatToTest.length > 0) {
23236
+ parts.push("**To verify:**");
23237
+ s.whatToTest.forEach((item) => parts.push(`- ${item}`));
23238
+ }
23239
+ if (s.whatToWatch) parts.push(`**Watch out:** ${s.whatToWatch}`);
23240
+ } catch {
23241
+ }
23242
+ }
23137
23243
  parts.push(`
23138
23244
  ## Conversation (${session.messages.length} messages)
23139
23245
  `);
@@ -23157,23 +23263,23 @@ ${lines.join("\n")}` : "No sessions found."
23157
23263
  function registerSearchTools(server2) {
23158
23264
  server2.tool(
23159
23265
  "search_project",
23160
- 'Search sessions and change requests using natural language. Find answers to questions like "when did we add the serif font?" or "who fixed the auth bug?"',
23266
+ `Search the workspace's session and change history using natural language. This is the PRIMARY way to answer questions about why something was done, when a feature was added or removed, or what happened in past work. Always try this BEFORE falling back to git log or file diffs. Examples: "why did we remove the code tab?", "when did we add the serif font?", "who fixed the auth bug?"`,
23161
23267
  {
23162
- projectId: external_exports.string().uuid().optional().describe(
23163
- "Project ID (auto-resolved from .chapter.json if omitted)"
23268
+ workspaceId: external_exports.string().uuid().optional().describe(
23269
+ "Project ID (auto-resolved from workspace link if omitted)"
23164
23270
  ),
23165
- projectName: external_exports.string().optional().describe("Project name (alternative to projectId)"),
23271
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
23166
23272
  query: external_exports.string().min(1).describe("Natural language search query"),
23167
23273
  entityTypes: external_exports.array(external_exports.enum(["session", "change_request"])).optional().describe(
23168
- "Filter by entity type. Omit to search both sessions and change requests."
23274
+ "Filter by entity type. Omit to search both sessions and changes."
23169
23275
  ),
23170
23276
  limit: external_exports.number().min(1).max(50).default(10).describe("Max results to return")
23171
23277
  },
23172
- async ({ projectId, projectName, query, entityTypes, limit }) => {
23278
+ async ({ workspaceId, workspaceName, query, entityTypes, limit }) => {
23173
23279
  try {
23174
- const id = await resolveProjectId(projectId, projectName);
23280
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
23175
23281
  const results = await api.search.semantic({
23176
- projectId: id,
23282
+ workspaceId: id,
23177
23283
  query,
23178
23284
  entityTypes,
23179
23285
  limit
@@ -23184,7 +23290,7 @@ function registerSearchTools(server2) {
23184
23290
  );
23185
23291
  }
23186
23292
  const lines = results.map((r, i) => {
23187
- const type = r.entityType === "session" ? "Session" : "Change Request";
23293
+ const type = r.entityType === "session" ? "Session" : "Change";
23188
23294
  return `${i + 1}. [${type}] ${r.snippet} (id: ${r.entityId}, relevance: ${r.similarity.toFixed(2)})`;
23189
23295
  });
23190
23296
  return success(
@@ -23199,208 +23305,450 @@ ${lines.join("\n")}`
23199
23305
  );
23200
23306
  }
23201
23307
 
23202
- // src/tools/code-intelligence.ts
23203
- function registerCodeIntelligenceTools(server2) {
23308
+ // src/tools/tasks.ts
23309
+ var STATUS_LABELS = {
23310
+ backlog: "Backlog",
23311
+ todo: "To Do",
23312
+ in_progress: "In Progress",
23313
+ done: "Done"
23314
+ };
23315
+ var PRIORITY_LABELS = {
23316
+ none: "None",
23317
+ urgent: "Urgent",
23318
+ high: "High",
23319
+ medium: "Medium",
23320
+ low: "Low"
23321
+ };
23322
+ function registerTaskTools(server2) {
23204
23323
  server2.tool(
23205
- "get_code_structure",
23206
- "Get project code structure \u2014 directories, files, languages, and exported symbols. Shows what the project looks like at a code level.",
23324
+ "list_tasks",
23325
+ 'List tasks in the workspace backlog. Use this at the START of your work to see what needs to be done. If you are working on a specific task, update its status to "in_progress" using update_task before you begin.',
23207
23326
  {
23208
- projectId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from .chapter.json if omitted)"),
23209
- projectName: external_exports.string().optional().describe("Project name (alternative to projectId)")
23327
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
23328
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
23329
+ status: external_exports.enum(["backlog", "todo", "in_progress", "done"]).optional().describe("Filter by status"),
23330
+ priority: external_exports.enum(["none", "urgent", "high", "medium", "low"]).optional().describe("Filter by priority")
23210
23331
  },
23211
- async ({ projectId, projectName }) => {
23332
+ async ({ workspaceId, workspaceName, status, priority }) => {
23212
23333
  try {
23213
- const id = await resolveProjectId(projectId, projectName);
23214
- const buckets = await api.codeIntelligence.getProjectStructure({
23215
- projectId: id
23334
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
23335
+ const tasks = await api.tasks.list({ workspaceId: id, status, priority });
23336
+ if (tasks.length === 0) {
23337
+ return success("No tasks found.");
23338
+ }
23339
+ const lines = tasks.map((t) => {
23340
+ const parts = [`- #${t.number}: ${t.title}`];
23341
+ parts.push(`[${STATUS_LABELS[t.status] ?? t.status}]`);
23342
+ if (t.priority !== "none") parts.push(`(${PRIORITY_LABELS[t.priority] ?? t.priority})`);
23343
+ if (t.assignee) parts.push(`assigned to ${t.assignee.name}`);
23344
+ if (t.sessionCount > 0) parts.push(`(${t.sessionCount} session${t.sessionCount > 1 ? "s" : ""})`);
23345
+ return parts.join(" ");
23216
23346
  });
23217
- if (buckets.length === 0) {
23218
- return success(
23219
- "No code index found for this project. The project may not have been indexed yet."
23220
- );
23221
- }
23222
- const lines = ["# Project Code Structure\n"];
23223
- for (const bucket of buckets) {
23224
- const lang = bucket.primaryLanguage ?? "mixed";
23225
- lines.push(
23226
- `## ${bucket.path}/ (${bucket.fileCount} files \xB7 ${lang} \xB7 ${bucket.exportCount} exports)`
23227
- );
23228
- if (bucket.subdirectories.length > 0) {
23229
- const subdirs = bucket.subdirectories.map((s) => `${s.name}/ (${s.fileCount})`).join(" ");
23230
- lines.push(` ${subdirs}`);
23347
+ return success(`Found ${tasks.length} task(s):
23348
+
23349
+ ${lines.join("\n")}`);
23350
+ } catch (error2) {
23351
+ return formatError2(error2);
23352
+ }
23353
+ }
23354
+ );
23355
+ server2.tool(
23356
+ "get_task",
23357
+ "Get full details of a task including its description, linked sessions, and history. Use this to understand requirements before starting work on a task.",
23358
+ {
23359
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
23360
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
23361
+ number: external_exports.coerce.number().describe("Task number (e.g. 1, 2, 3)")
23362
+ },
23363
+ async ({ workspaceId, workspaceName, number: number3 }) => {
23364
+ try {
23365
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
23366
+ const task = await api.tasks.getByNumber({ workspaceId: id, number: number3 });
23367
+ const parts = [];
23368
+ parts.push(`# Task #${task.number}: ${task.title}`);
23369
+ parts.push(`Status: ${STATUS_LABELS[task.status] ?? task.status}`);
23370
+ parts.push(`Priority: ${PRIORITY_LABELS[task.priority] ?? task.priority}`);
23371
+ if (task.assignee) parts.push(`Assigned to: ${task.assignee.name}`);
23372
+ if (task.description) parts.push(`
23373
+ ## Description
23374
+
23375
+ ${task.description}`);
23376
+ if (task.sessions.length > 0) {
23377
+ parts.push(`
23378
+ ## Linked Sessions (${task.sessions.length})
23379
+ `);
23380
+ for (const s of task.sessions) {
23381
+ const crInfo = s.changeRequestNumber != null ? ` (CR #${s.changeRequestNumber})` : "";
23382
+ parts.push(`- ${s.title ?? "(untitled)"} \u2014 ${s.status}${crInfo}`);
23383
+ if (s.summary) {
23384
+ try {
23385
+ const parsed = JSON.parse(s.summary);
23386
+ parts.push(` Done: ${parsed.whatWasDone}`);
23387
+ if (parsed.whatToWatch) parts.push(` Watch: ${parsed.whatToWatch}`);
23388
+ } catch {
23389
+ }
23390
+ }
23231
23391
  }
23232
- lines.push("");
23233
23392
  }
23234
- const totalFiles = buckets.reduce((n, b) => n + b.fileCount, 0);
23235
- const totalSymbols = buckets.reduce((n, b) => n + b.symbolCount, 0);
23236
- lines.push(
23237
- `---
23238
- Total: ${totalFiles} files, ${totalSymbols} symbols across ${buckets.length} directories`
23393
+ return success(parts.join("\n"));
23394
+ } catch (error2) {
23395
+ return formatError2(error2);
23396
+ }
23397
+ }
23398
+ );
23399
+ server2.tool(
23400
+ "create_task",
23401
+ "Create a new task in the workspace backlog. Use this to add work items that need to be built. If you discover bugs or follow-up work while coding, create tasks for them rather than leaving TODOs in code.",
23402
+ {
23403
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
23404
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
23405
+ title: external_exports.string().min(1).max(500).describe("Task title \u2014 a plain-language description of what to build"),
23406
+ description: external_exports.string().max(5e3).optional().describe("Optional longer description with details or acceptance criteria"),
23407
+ status: external_exports.enum(["backlog", "todo", "in_progress", "done"]).optional().describe("Initial status (defaults to backlog)"),
23408
+ priority: external_exports.enum(["none", "urgent", "high", "medium", "low"]).optional().describe("Priority level")
23409
+ },
23410
+ async ({ workspaceId, workspaceName, title, description, status, priority }) => {
23411
+ try {
23412
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
23413
+ const task = await api.tasks.create({
23414
+ workspaceId: id,
23415
+ title,
23416
+ description,
23417
+ status,
23418
+ priority
23419
+ });
23420
+ return success(
23421
+ `Created task #${task.number}: ${task.title}
23422
+ Status: ${STATUS_LABELS[task.status] ?? task.status}` + (task.priority !== "none" ? `
23423
+ Priority: ${PRIORITY_LABELS[task.priority] ?? task.priority}` : "")
23239
23424
  );
23240
- return success(lines.join("\n"));
23241
23425
  } catch (error2) {
23242
23426
  return formatError2(error2);
23243
23427
  }
23244
23428
  }
23245
23429
  );
23246
23430
  server2.tool(
23247
- "get_file_symbols",
23248
- "Get all symbols (functions, classes, interfaces, types, variables) in a specific file. Shows what's defined in the file.",
23431
+ "update_task",
23432
+ `Update a task's status, title, description, or priority. IMPORTANT: You MUST use this tool to keep task status current as you work. Set status to "in_progress" when you start working on a task, and "done" when you finish. This is not optional \u2014 task tracking is how the team stays in sync.`,
23249
23433
  {
23250
- projectId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from .chapter.json if omitted)"),
23251
- projectName: external_exports.string().optional().describe("Project name (alternative to projectId)"),
23252
- filePath: external_exports.string().min(1).describe("File path to inspect")
23434
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
23435
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
23436
+ number: external_exports.coerce.number().describe("Task number to update"),
23437
+ title: external_exports.string().min(1).max(500).optional().describe("New title"),
23438
+ description: external_exports.string().max(5e3).nullable().optional().describe("New description (null to clear)"),
23439
+ status: external_exports.enum(["backlog", "todo", "in_progress", "done"]).optional().describe('New status \u2014 set to "done" when the task is complete'),
23440
+ priority: external_exports.enum(["none", "urgent", "high", "medium", "low"]).optional().describe("New priority")
23253
23441
  },
23254
- async ({ projectId, projectName, filePath }) => {
23442
+ async ({ workspaceId, workspaceName, number: number3, title, description, status, priority }) => {
23255
23443
  try {
23256
- const id = await resolveProjectId(projectId, projectName);
23257
- const symbols = await api.codeIntelligence.getFileSymbols({
23258
- projectId: id,
23259
- filePath
23444
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
23445
+ const existing = await api.tasks.getByNumber({ workspaceId: id, number: number3 });
23446
+ const task = await api.tasks.update({
23447
+ taskId: existing.id,
23448
+ title,
23449
+ description,
23450
+ status,
23451
+ priority
23260
23452
  });
23261
- if (symbols.length === 0) {
23262
- return success(`No symbols found in ${filePath}. The file may not be indexed.`);
23263
- }
23264
- const lines = [`# Symbols in ${filePath}
23265
- `];
23266
- for (const sym of symbols) {
23267
- const exported = sym.exported ? " (exported)" : "";
23268
- const parent = sym.parentName ? ` [${sym.parentName}]` : "";
23269
- const sig = sym.signature ? `: ${sym.signature}` : "";
23270
- lines.push(
23271
- `- **${sym.symbolType}** \`${sym.name}\`${parent}${exported} (L${sym.startLine}-${sym.endLine})${sig}`
23272
- );
23273
- }
23274
- return success(lines.join("\n"));
23453
+ return success(
23454
+ `Updated task #${task.number}: ${task.title}
23455
+ Status: ${STATUS_LABELS[task.status] ?? task.status}` + (task.priority !== "none" ? `
23456
+ Priority: ${PRIORITY_LABELS[task.priority] ?? task.priority}` : "")
23457
+ );
23275
23458
  } catch (error2) {
23276
23459
  return formatError2(error2);
23277
23460
  }
23278
23461
  }
23279
23462
  );
23463
+ }
23464
+
23465
+ // src/tools/activity.ts
23466
+ function registerActivityTools(server2) {
23280
23467
  server2.tool(
23281
- "get_dependencies",
23282
- "Get import/dependency information for a file \u2014 what it imports and what imports it. Shows the file's connections.",
23468
+ "get_activity",
23469
+ "Get a timeline of recent activity in a workspace \u2014 sessions, changes, tasks, and commits. Use this to understand what has been happening recently.",
23283
23470
  {
23284
- projectId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from .chapter.json if omitted)"),
23285
- projectName: external_exports.string().optional().describe("Project name (alternative to projectId)"),
23286
- filePath: external_exports.string().min(1).describe("File path to check dependencies for")
23471
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
23472
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
23473
+ limit: external_exports.coerce.number().min(1).max(100).default(20).describe("Max events to return"),
23474
+ since: external_exports.string().optional().describe("ISO date \u2014 only show events after this date (default: 7 days ago)")
23287
23475
  },
23288
- async ({ projectId, projectName, filePath }) => {
23476
+ async ({ workspaceId, workspaceName, limit, since }) => {
23289
23477
  try {
23290
- const id = await resolveProjectId(projectId, projectName);
23291
- const deps = await api.codeIntelligence.getDependencies({
23292
- projectId: id,
23293
- filePath
23478
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
23479
+ const events = await api.activity.feed({
23480
+ workspaceId: id,
23481
+ limit,
23482
+ since
23294
23483
  });
23295
- const lines = [`# Dependencies for ${filePath}
23296
- `];
23297
- lines.push(`## Imports (${deps.imports.length})`);
23298
- if (deps.imports.length === 0) {
23299
- lines.push("No internal imports.");
23300
- } else {
23301
- for (const d of deps.imports) {
23302
- const specs = d.importSpecifiers?.join(", ") ?? "*";
23303
- lines.push(`- ${d.targetFilePath} \u2192 {${specs}}`);
23484
+ const lines = events.map((e) => {
23485
+ const date3 = new Date(e.timestamp).toLocaleString();
23486
+ const author = e.author ? ` by ${e.author}` : "";
23487
+ return `- [${e.type}] ${e.description}${author} (${date3})`;
23488
+ });
23489
+ return success(
23490
+ lines.length > 0 ? `Recent activity (${lines.length} events):
23491
+
23492
+ ${lines.join("\n")}` : "No recent activity found."
23493
+ );
23494
+ } catch (error2) {
23495
+ return formatError2(error2);
23496
+ }
23497
+ }
23498
+ );
23499
+ server2.tool(
23500
+ "get_file_history",
23501
+ "Get the commit history for a specific file, including which AI sessions produced each change. Use this to understand how a file has evolved.",
23502
+ {
23503
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
23504
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
23505
+ filePath: external_exports.string().describe('Path to the file (e.g. "src/index.ts")'),
23506
+ limit: external_exports.coerce.number().min(1).max(100).default(10).describe("Max commits to return")
23507
+ },
23508
+ async ({ workspaceId, workspaceName, filePath, limit }) => {
23509
+ try {
23510
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
23511
+ const history = await api.activity.fileHistory({
23512
+ workspaceId: id,
23513
+ filePath,
23514
+ limit
23515
+ });
23516
+ const lines = history.map((entry) => {
23517
+ const date3 = new Date(entry.timestamp).toLocaleString();
23518
+ const parts = [
23519
+ `- ${entry.commitOid.slice(0, 7)} ${entry.message} (${entry.author}, ${date3})`
23520
+ ];
23521
+ if (entry.session) {
23522
+ parts.push(
23523
+ ` Session: ${entry.session.title ?? "(untitled)"} (id: ${entry.session.id})`
23524
+ );
23525
+ }
23526
+ return parts.join("\n");
23527
+ });
23528
+ return success(
23529
+ lines.length > 0 ? `File history for ${filePath} (${lines.length} commits):
23530
+
23531
+ ${lines.join("\n")}` : `No commit history found for ${filePath}.`
23532
+ );
23533
+ } catch (error2) {
23534
+ return formatError2(error2);
23535
+ }
23536
+ }
23537
+ );
23538
+ server2.tool(
23539
+ "get_workspace_stats",
23540
+ "Get high-level project statistics \u2014 sessions this week, total changes, active tasks, and total commits.",
23541
+ {
23542
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
23543
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)")
23544
+ },
23545
+ async ({ workspaceId, workspaceName }) => {
23546
+ try {
23547
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
23548
+ const stats = await api.activity.stats({ workspaceId: id });
23549
+ const lines = [
23550
+ `Sessions this week: ${stats.sessionsThisWeek}`,
23551
+ `Total change requests: ${stats.totalChangeRequests}`,
23552
+ `Active tasks (in progress): ${stats.activeTasks}`,
23553
+ `Total commits: ${stats.totalCommits}`
23554
+ ];
23555
+ return success(`Project stats:
23556
+
23557
+ ${lines.join("\n")}`);
23558
+ } catch (error2) {
23559
+ return formatError2(error2);
23560
+ }
23561
+ }
23562
+ );
23563
+ }
23564
+
23565
+ // src/tools/intelligence.ts
23566
+ function registerIntelligenceTools(server2) {
23567
+ server2.tool(
23568
+ "scan_codebase",
23569
+ "Scan and index a codebase to build a structured map of all files, symbols, routes, endpoints, models, and copy. Run this once per workspace to enable fast lookups. Accepts an optional projectPath for local directories.",
23570
+ {
23571
+ workspaceId: external_exports.string().uuid().optional().describe("Workspace ID"),
23572
+ workspaceName: external_exports.string().optional().describe("Workspace name (alternative to ID)"),
23573
+ projectPath: external_exports.string().optional().describe("Local filesystem path to scan (overrides workspace path)")
23574
+ },
23575
+ async ({ workspaceId, workspaceName, projectPath }) => {
23576
+ try {
23577
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
23578
+ const result = await api.intelligence.scan({
23579
+ workspaceId: id,
23580
+ projectPath
23581
+ });
23582
+ const lines = [
23583
+ `Codebase scanned in ${result.stats.durationMs}ms`,
23584
+ ``,
23585
+ `Files: ${result.fileCount}`,
23586
+ `Symbols: ${result.symbolCount}`,
23587
+ `Routes: ${result.routeCount}`,
23588
+ `API endpoints: ${result.endpointCount}`,
23589
+ `DB models: ${result.modelCount}`,
23590
+ `Copy entries: ${result.copyCount}`,
23591
+ `Dependencies: ${result.dependencyCount}`,
23592
+ ``,
23593
+ `Tech stack:`,
23594
+ ` Languages: ${result.techStack.languages.join(", ")}`,
23595
+ ` Frameworks: ${result.techStack.frameworks.join(", ")}`,
23596
+ ` Databases: ${result.techStack.databases.join(", ")}`,
23597
+ ` Package manager: ${result.techStack.packageManager ?? "unknown"}`
23598
+ ];
23599
+ return success(lines.join("\n"));
23600
+ } catch (error2) {
23601
+ return formatError2(error2);
23602
+ }
23603
+ }
23604
+ );
23605
+ server2.tool(
23606
+ "query_codebase",
23607
+ "Search the codebase index for files, symbols, routes, endpoints, and copy matching a query. Returns ranked results with file paths and line numbers. The index must exist (run scan_codebase first).",
23608
+ {
23609
+ workspaceId: external_exports.string().uuid().optional().describe("Workspace ID"),
23610
+ workspaceName: external_exports.string().optional().describe("Workspace name (alternative to ID)"),
23611
+ query: external_exports.string().describe('Search query \u2014 e.g. "task creation", "auth middleware", "welcome page"'),
23612
+ limit: external_exports.number().optional().describe("Max results per category (default 20)")
23613
+ },
23614
+ async ({ workspaceId, workspaceName, query, limit }) => {
23615
+ try {
23616
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
23617
+ const result = await api.intelligence.query({ workspaceId: id, query, limit });
23618
+ const sections = [];
23619
+ if (result.files.length > 0) {
23620
+ sections.push("## Files");
23621
+ for (const f of result.files) {
23622
+ const exports = f.exports.length > 0 ? ` (exports: ${f.exports.slice(0, 3).join(", ")})` : "";
23623
+ sections.push(`- \`${f.path}\` [${f.category}]${exports}`);
23304
23624
  }
23305
23625
  }
23306
- lines.push("");
23307
- lines.push(`## Imported By (${deps.importedBy.length})`);
23308
- if (deps.importedBy.length === 0) {
23309
- lines.push("No files import this file.");
23310
- } else {
23311
- for (const d of deps.importedBy) {
23312
- const specs = d.importSpecifiers?.join(", ") ?? "*";
23313
- lines.push(`- ${d.sourceFilePath} \u2192 {${specs}}`);
23626
+ if (result.symbols.length > 0) {
23627
+ sections.push("\n## Symbols");
23628
+ for (const s of result.symbols) {
23629
+ const sig = s.signature ? ` \u2014 \`${s.signature}\`` : "";
23630
+ sections.push(`- \`${s.name}\` (${s.kind}) at \`${s.filePath}:${s.line}\`${sig}`);
23314
23631
  }
23315
23632
  }
23316
- return success(lines.join("\n"));
23633
+ if (result.copy.length > 0) {
23634
+ sections.push("\n## Copy/Text");
23635
+ for (const c of result.copy) {
23636
+ sections.push(`- "${c.text}" at \`${c.filePath}:${c.line}\` in ${c.context}`);
23637
+ }
23638
+ }
23639
+ if (result.routes.length > 0) {
23640
+ sections.push("\n## Routes");
23641
+ for (const r of result.routes) {
23642
+ sections.push(`- ${r.method ?? "ANY"} \`${r.path}\` \u2192 ${r.handler} at \`${r.filePath}:${r.line}\``);
23643
+ }
23644
+ }
23645
+ if (result.endpoints.length > 0) {
23646
+ sections.push("\n## API Endpoints");
23647
+ for (const e of result.endpoints) {
23648
+ sections.push(`- \`${e.router ? e.router + "." : ""}${e.name}\` (${e.type}) at \`${e.filePath}:${e.line}\``);
23649
+ }
23650
+ }
23651
+ if (sections.length === 0) {
23652
+ return success(`No results found for "${query}". Try different keywords or run scan_codebase first.`);
23653
+ }
23654
+ return success(sections.join("\n"));
23317
23655
  } catch (error2) {
23318
23656
  return formatError2(error2);
23319
23657
  }
23320
23658
  }
23321
23659
  );
23322
23660
  server2.tool(
23323
- "get_impact_analysis",
23324
- "Analyze what would be affected if a file changes \u2014 shows transitive dependents (files that directly or indirectly depend on this file).",
23661
+ "find_symbol",
23662
+ "Find a specific function, class, component, type, or other symbol by name. Returns exact file path and line number.",
23325
23663
  {
23326
- projectId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from .chapter.json if omitted)"),
23327
- projectName: external_exports.string().optional().describe("Project name (alternative to projectId)"),
23328
- filePath: external_exports.string().min(1).describe("File path to analyze impact for"),
23329
- depth: external_exports.number().min(1).max(10).default(3).describe("Max dependency depth to traverse (default: 3)")
23664
+ workspaceId: external_exports.string().uuid().optional().describe("Workspace ID"),
23665
+ workspaceName: external_exports.string().optional().describe("Workspace name (alternative to ID)"),
23666
+ name: external_exports.string().describe('Symbol name to search for \u2014 e.g. "TaskCreateInline", "authorizeWorkspaceAccess"'),
23667
+ kind: external_exports.string().optional().describe("Filter by kind: function, class, component, type, interface, enum, hook, struct, method")
23330
23668
  },
23331
- async ({ projectId, projectName, filePath, depth }) => {
23669
+ async ({ workspaceId, workspaceName, name, kind }) => {
23332
23670
  try {
23333
- let renderNode2 = function(node, indent) {
23334
- const prefix = " ".repeat(indent);
23335
- const exports = node.exports?.join(", ") ?? "";
23336
- const exportsStr = exports ? ` [exports: ${exports}]` : "";
23337
- lines.push(`${prefix}- ${node.filePath}${exportsStr}`);
23338
- for (const dep of node.dependents) {
23339
- renderNode2(dep, indent + 1);
23340
- }
23341
- };
23342
- var renderNode = renderNode2;
23343
- const id = await resolveProjectId(projectId, projectName);
23344
- const tree = await api.codeIntelligence.getImpactAnalysis({
23345
- projectId: id,
23346
- filePath,
23347
- depth
23671
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
23672
+ const results = await api.intelligence.findSymbol({ workspaceId: id, name, kind });
23673
+ if (results.length === 0) {
23674
+ return success(`No symbol found matching "${name}"${kind ? ` (kind: ${kind})` : ""}`);
23675
+ }
23676
+ const lines = results.map((s) => {
23677
+ const sig = s.signature ? `
23678
+ ${s.signature}` : "";
23679
+ return `- \`${s.name}\` (${s.kind}) at \`${s.filePath}:${s.line}\`${s.exported ? " [exported]" : ""}${sig}`;
23348
23680
  });
23349
- const lines = [`# Impact Analysis for ${filePath}
23350
- `];
23351
- if (tree.dependents.length === 0) {
23352
- lines.push("No files depend on this file.");
23353
- } else {
23354
- lines.push(
23355
- `${tree.dependents.length} file(s) directly depend on this file:
23356
- `
23357
- );
23358
- renderNode2(tree, 0);
23681
+ return success(`Found ${results.length} match(es):
23682
+ ${lines.join("\n")}`);
23683
+ } catch (error2) {
23684
+ return formatError2(error2);
23685
+ }
23686
+ }
23687
+ );
23688
+ server2.tool(
23689
+ "find_copy",
23690
+ "Find user-facing text/copy in the codebase by content. Returns the file path, line number, and surrounding component context. Use this to quickly locate where a specific string appears in the UI.",
23691
+ {
23692
+ workspaceId: external_exports.string().uuid().optional().describe("Workspace ID"),
23693
+ workspaceName: external_exports.string().optional().describe("Workspace name (alternative to ID)"),
23694
+ text: external_exports.string().describe('Text to search for \u2014 e.g. "Welcome to Chapter", "Sign in", "No tasks yet"')
23695
+ },
23696
+ async ({ workspaceId, workspaceName, text }) => {
23697
+ try {
23698
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
23699
+ const results = await api.intelligence.findCopy({ workspaceId: id, text });
23700
+ if (results.length === 0) {
23701
+ return success(`No copy found matching "${text}"`);
23359
23702
  }
23360
- return success(lines.join("\n"));
23703
+ const lines = results.map(
23704
+ (c) => `- "${c.text}" at \`${c.filePath}:${c.line}\` in \`${c.context}\` (${c.type})`
23705
+ );
23706
+ return success(`Found ${results.length} match(es):
23707
+ ${lines.join("\n")}`);
23361
23708
  } catch (error2) {
23362
23709
  return formatError2(error2);
23363
23710
  }
23364
23711
  }
23365
23712
  );
23366
23713
  server2.tool(
23367
- "search_symbols",
23368
- "Search for symbols (functions, classes, types, etc.) by name across the project.",
23714
+ "get_codebase_context",
23715
+ "Generate a structured context block for a task description. Returns relevant files, symbols, copy, routes, and endpoints that an agent should know about before starting work. This is the key tool for speeding up agent sessions \u2014 call it at the start of any task.",
23369
23716
  {
23370
- projectId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from .chapter.json if omitted)"),
23371
- projectName: external_exports.string().optional().describe("Project name (alternative to projectId)"),
23372
- query: external_exports.string().min(1).describe("Symbol name to search for (case-insensitive)"),
23373
- symbolType: external_exports.enum([
23374
- "function",
23375
- "class",
23376
- "interface",
23377
- "type",
23378
- "variable",
23379
- "method",
23380
- "export"
23381
- ]).optional().describe("Filter by symbol type")
23717
+ workspaceId: external_exports.string().uuid().optional().describe("Workspace ID"),
23718
+ workspaceName: external_exports.string().optional().describe("Workspace name (alternative to ID)"),
23719
+ taskDescription: external_exports.string().describe('Description of the task \u2014 e.g. "change the welcome message on the home page"')
23382
23720
  },
23383
- async ({ projectId, projectName, query, symbolType: symbolType2 }) => {
23721
+ async ({ workspaceId, workspaceName, taskDescription }) => {
23384
23722
  try {
23385
- const id = await resolveProjectId(projectId, projectName);
23386
- const symbols = await api.codeIntelligence.searchSymbols({
23387
- projectId: id,
23388
- query,
23389
- symbolType: symbolType2
23723
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
23724
+ const result = await api.intelligence.contextForTask({
23725
+ workspaceId: id,
23726
+ taskDescription
23390
23727
  });
23391
- if (symbols.length === 0) {
23392
- return success(`No symbols matching "${query}" found.`);
23393
- }
23394
- const lines = [`# Symbol Search: "${query}"
23395
- `];
23396
- lines.push(`Found ${symbols.length} result(s):
23397
- `);
23398
- for (const sym of symbols) {
23399
- const exported = sym.exported ? " (exported)" : "";
23400
- const parent = sym.parentName ? ` in ${sym.parentName}` : "";
23401
- lines.push(
23402
- `- **${sym.symbolType}** \`${sym.name}\`${parent}${exported} \u2014 ${sym.filePath}:${sym.startLine}`
23403
- );
23728
+ return success(result.context);
23729
+ } catch (error2) {
23730
+ return formatError2(error2);
23731
+ }
23732
+ }
23733
+ );
23734
+ server2.tool(
23735
+ "get_file_map",
23736
+ "Get a compact map of every file in the codebase with its category and key exports. Useful for understanding project structure at a glance.",
23737
+ {
23738
+ workspaceId: external_exports.string().uuid().optional().describe("Workspace ID"),
23739
+ workspaceName: external_exports.string().optional().describe("Workspace name (alternative to ID)")
23740
+ },
23741
+ async ({ workspaceId, workspaceName }) => {
23742
+ try {
23743
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
23744
+ const result = await api.intelligence.fileMap({ workspaceId: id });
23745
+ const lines = [
23746
+ `# File Map (${Object.keys(result.tree).length} files)`,
23747
+ `Tech stack: ${result.techStack.frameworks.join(", ")} | ${result.techStack.languages.join(", ")}`,
23748
+ ""
23749
+ ];
23750
+ for (const [path3, desc] of Object.entries(result.tree)) {
23751
+ lines.push(`${path3} \u2014 ${desc}`);
23404
23752
  }
23405
23753
  return success(lines.join("\n"));
23406
23754
  } catch (error2) {
@@ -23409,33 +23757,33 @@ Total: ${totalFiles} files, ${totalSymbols} symbols across ${buckets.length} dir
23409
23757
  }
23410
23758
  );
23411
23759
  server2.tool(
23412
- "reindex_project",
23413
- "Trigger a full code intelligence reindex for a project. Parses all source files with tree-sitter and rebuilds the symbol and dependency graph.",
23760
+ "get_file_deps",
23761
+ "Get the dependency graph for a specific file \u2014 what it imports (dependencies) or what imports it (dependents).",
23414
23762
  {
23415
- projectId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from .chapter.json if omitted)"),
23416
- projectName: external_exports.string().optional().describe("Project name (alternative to projectId)")
23763
+ workspaceId: external_exports.string().uuid().optional().describe("Workspace ID"),
23764
+ workspaceName: external_exports.string().optional().describe("Workspace name (alternative to ID)"),
23765
+ filePath: external_exports.string().describe('Relative file path \u2014 e.g. "src/components/task-card.tsx"'),
23766
+ direction: external_exports.enum(["dependents", "dependencies"]).optional().describe('Direction: "dependents" (what imports this file) or "dependencies" (what this file imports). Default: dependents')
23417
23767
  },
23418
- async ({ projectId, projectName }) => {
23768
+ async ({ workspaceId, workspaceName, filePath, direction }) => {
23419
23769
  try {
23420
- const id = await resolveProjectId(projectId, projectName);
23421
- const project = await api.projects.getById({ id });
23422
- if (project.backend !== "chapter") {
23423
- return {
23424
- content: [
23425
- {
23426
- type: "text",
23427
- text: "Code intelligence indexing is only available for Chapter-hosted projects. GitHub-backed projects cannot be indexed."
23428
- }
23429
- ],
23430
- isError: true
23431
- };
23770
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
23771
+ const result = await api.intelligence.deps({
23772
+ workspaceId: id,
23773
+ filePath,
23774
+ direction
23775
+ });
23776
+ const label = direction === "dependencies" ? "depends on" : "is imported by";
23777
+ if (result.files.length === 0) {
23778
+ return success(`\`${filePath}\` ${label} no other files`);
23432
23779
  }
23433
- const result = await api.codeIntelligence.reindex({ projectId: id });
23434
- return success(
23435
- `Code indexing job enqueued${result.jobId ? ` (job: ${result.jobId})` : ""}. The project will be fully reindexed in the background.`
23436
- );
23437
- } catch (err) {
23438
- return formatError2(err);
23780
+ const lines = [`\`${filePath}\` ${label}:`];
23781
+ for (const f of result.files) {
23782
+ lines.push(` - \`${f}\``);
23783
+ }
23784
+ return success(lines.join("\n"));
23785
+ } catch (error2) {
23786
+ return formatError2(error2);
23439
23787
  }
23440
23788
  }
23441
23789
  );
@@ -23443,13 +23791,15 @@ Total: ${totalFiles} files, ${totalSymbols} symbols across ${buckets.length} dir
23443
23791
 
23444
23792
  // src/tools/index.ts
23445
23793
  function registerAllTools(server2) {
23446
- registerProjectTools(server2);
23794
+ registerWorkspaceTools(server2);
23447
23795
  registerFileTools(server2);
23448
23796
  registerChangeRequestTools(server2);
23449
23797
  registerCommitTools(server2);
23450
23798
  registerSessionTools(server2);
23451
23799
  registerSearchTools(server2);
23452
- registerCodeIntelligenceTools(server2);
23800
+ registerTaskTools(server2);
23801
+ registerActivityTools(server2);
23802
+ registerIntelligenceTools(server2);
23453
23803
  }
23454
23804
 
23455
23805
  // src/index.ts