@chapterai/mcp 0.1.0 → 0.1.1

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 +490 -391
  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,29 @@ 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);
22434
22437
  },
22435
- reindex(input) {
22436
- return getClient().codeIntelligence.reindex.mutate(input);
22438
+ stats(input) {
22439
+ return getClient().workspaces.getWorkspaceStats.query(input);
22437
22440
  }
22438
22441
  },
22439
22442
  sessions: {
@@ -22446,46 +22449,54 @@ var api = {
22446
22449
  }
22447
22450
  };
22448
22451
 
22449
- // src/lib/project-context.ts
22452
+ // src/lib/workspace-context.ts
22450
22453
  import fs2 from "fs";
22451
22454
  import path2 from "path";
22452
- var LINK_FILENAME = ".chapter.json";
22453
- function findProjectLink() {
22455
+ var LINK_PATH = path2.join(".chapter", "workspace.json");
22456
+ var LEGACY_LINK_FILENAME = ".chapter.json";
22457
+ function readLinkFile(filePath) {
22458
+ try {
22459
+ const raw = fs2.readFileSync(filePath, "utf-8");
22460
+ const data = JSON.parse(raw);
22461
+ const id = data.workspaceId ?? data.projectId;
22462
+ const name = data.workspaceName ?? data.projectName;
22463
+ if (!id || !name) return null;
22464
+ return { workspaceId: id, workspaceName: name };
22465
+ } catch {
22466
+ return null;
22467
+ }
22468
+ }
22469
+ function findWorkspaceLink() {
22454
22470
  let dir = process.cwd();
22455
22471
  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
- }
22472
+ const newPath = path2.join(dir, LINK_PATH);
22473
+ if (fs2.existsSync(newPath)) return readLinkFile(newPath);
22474
+ const legacyPath = path2.join(dir, LEGACY_LINK_FILENAME);
22475
+ if (fs2.existsSync(legacyPath)) return readLinkFile(legacyPath);
22465
22476
  const parent = path2.dirname(dir);
22466
22477
  if (parent === dir) break;
22467
22478
  dir = parent;
22468
22479
  }
22469
22480
  return null;
22470
22481
  }
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()
22482
+ async function resolveWorkspaceId(workspaceId, workspaceName) {
22483
+ if (workspaceId) return workspaceId;
22484
+ if (workspaceName) {
22485
+ const workspaces = await api.workspaces.list();
22486
+ const match = workspaces.find(
22487
+ (p) => p.name.toLowerCase() === workspaceName.toLowerCase()
22477
22488
  );
22478
22489
  if (!match) {
22479
22490
  throw new Error(
22480
- `Project "${projectName}" not found. Use list_projects to see available projects.`
22491
+ `Project "${workspaceName}" not found. Use list_workspaces to see available projects.`
22481
22492
  );
22482
22493
  }
22483
22494
  return match.id;
22484
22495
  }
22485
- const link = findProjectLink();
22486
- if (link) return link.projectId;
22496
+ const link = findWorkspaceLink();
22497
+ if (link) return link.workspaceId;
22487
22498
  throw new Error(
22488
- "No project specified. Provide projectId, projectName, or run from a directory with a .chapter.json file."
22499
+ "No project specified. Provide workspaceId, workspaceName, or run from a directory linked to a Chapter workspace."
22489
22500
  );
22490
22501
  }
22491
22502
 
@@ -22515,24 +22526,24 @@ function success(text) {
22515
22526
  return { content: [{ type: "text", text }] };
22516
22527
  }
22517
22528
 
22518
- // src/tools/projects.ts
22519
- function registerProjectTools(server2) {
22529
+ // src/tools/workspaces.ts
22530
+ function registerWorkspaceTools(server2) {
22520
22531
  server2.tool(
22521
- "list_projects",
22522
- "List all accessible Chapter projects",
22532
+ "list_workspaces",
22533
+ "List all accessible Chapter workspaces",
22523
22534
  { orgId: external_exports.string().uuid().optional().describe("Filter by organization ID") },
22524
22535
  async ({ orgId }) => {
22525
22536
  try {
22526
- const projects = await api.projects.list(
22537
+ const workspaces = await api.workspaces.list(
22527
22538
  orgId ? { orgId } : void 0
22528
22539
  );
22529
- const lines = projects.map(
22540
+ const lines = workspaces.map(
22530
22541
  (p) => `- ${p.name} (id: ${p.id}, backend: ${p.backend})${p.description ? ` \u2014 ${p.description}` : ""}`
22531
22542
  );
22532
22543
  return success(
22533
22544
  lines.length > 0 ? `Found ${lines.length} project(s):
22534
22545
 
22535
- ${lines.join("\n")}` : "No projects found."
22546
+ ${lines.join("\n")}` : "No workspaces found."
22536
22547
  );
22537
22548
  } catch (error2) {
22538
22549
  return formatError2(error2);
@@ -22543,23 +22554,23 @@ ${lines.join("\n")}` : "No projects found."
22543
22554
  "get_project",
22544
22555
  "Get project details including README content",
22545
22556
  {
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)")
22557
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
22558
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)")
22548
22559
  },
22549
- async ({ projectId, projectName }) => {
22560
+ async ({ workspaceId, workspaceName }) => {
22550
22561
  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 })
22562
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
22563
+ const [workspace, readme] = await Promise.all([
22564
+ api.workspaces.getById({ id }),
22565
+ api.workspaces.getReadme({ workspaceId: id })
22555
22566
  ]);
22556
22567
  const parts = [
22557
- `# ${project.name}`,
22558
- project.description ? `
22559
- ${project.description}` : "",
22568
+ `# ${workspace.name}`,
22569
+ workspace.description ? `
22570
+ ${workspace.description}` : "",
22560
22571
  `
22561
- Backend: ${project.backend}`,
22562
- `Created: ${project.createdAt}`,
22572
+ Backend: ${workspace.backend}`,
22573
+ `Created: ${workspace.createdAt}`,
22563
22574
  readme.content ? `
22564
22575
  ## README
22565
22576
 
@@ -22572,34 +22583,72 @@ ${readme.content}` : ""
22572
22583
  }
22573
22584
  );
22574
22585
  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.",
22586
+ "get_workspace_overview",
22587
+ "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
22588
  {
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)")
22589
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
22590
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)")
22580
22591
  },
22581
- async ({ projectId, projectName }) => {
22592
+ async ({ workspaceId, workspaceName }) => {
22582
22593
  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 }),
22594
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
22595
+ const [workspace, readme, files, commits, openCRs, appliedCRs, activeTasks, todoTasks, recentSessions] = await Promise.all([
22596
+ api.workspaces.getById({ id }),
22597
+ api.workspaces.getReadme({ workspaceId: id }),
22598
+ api.workspaces.listFiles({ workspaceId: id, path: "" }),
22599
+ api.workspaces.listCommits({ workspaceId: id, limit: 10 }),
22589
22600
  api.changeRequests.list({
22590
- projectId: id,
22601
+ workspaceId: id,
22591
22602
  status: "open",
22592
- limit: 10
22593
- })
22603
+ limit: 5
22604
+ }),
22605
+ api.changeRequests.list({
22606
+ workspaceId: id,
22607
+ status: "applied",
22608
+ limit: 5
22609
+ }),
22610
+ api.tasks.list({ workspaceId: id, status: "in_progress" }),
22611
+ api.tasks.list({ workspaceId: id, status: "todo" }),
22612
+ api.sessions.list({ workspaceId: id, status: "completed", limit: 5 })
22594
22613
  ]);
22595
22614
  const parts = [];
22596
- parts.push(`# ${project.name}`);
22597
- if (project.description) parts.push(project.description);
22598
- parts.push(`Backend: ${project.backend}`);
22615
+ parts.push(`# ${workspace.name}`);
22616
+ if (workspace.description) parts.push(workspace.description);
22617
+ parts.push(`Backend: ${workspace.backend}`);
22599
22618
  if (readme.content) {
22600
22619
  parts.push("\n## README\n");
22601
22620
  parts.push(readme.content);
22602
22621
  }
22622
+ const allActiveTasks = [...activeTasks, ...todoTasks];
22623
+ if (allActiveTasks.length > 0) {
22624
+ parts.push("\n## Active Tasks\n");
22625
+ parts.push(
22626
+ allActiveTasks.map((t) => {
22627
+ const priority = t.priority !== "none" ? ` (${t.priority})` : "";
22628
+ const status = t.status === "in_progress" ? " [IN PROGRESS]" : "";
22629
+ return `- #${t.number}: ${t.title}${status}${priority}${t.description ? `
22630
+ ${t.description.slice(0, 200)}` : ""}`;
22631
+ }).join("\n")
22632
+ );
22633
+ }
22634
+ const sessionsWithSummaries = recentSessions.filter((s) => s.summary);
22635
+ if (sessionsWithSummaries.length > 0) {
22636
+ parts.push("\n## Recent Sessions (what's been done)\n");
22637
+ parts.push(
22638
+ sessionsWithSummaries.map((s) => {
22639
+ try {
22640
+ const parsed = JSON.parse(s.summary);
22641
+ const lines = [`- **${s.title ?? "(untitled)"}** (${new Date(s.createdAt).toLocaleDateString()})`];
22642
+ lines.push(` Asked: ${parsed.whatWasAsked}`);
22643
+ lines.push(` Done: ${parsed.whatWasDone}`);
22644
+ if (parsed.whatToWatch) lines.push(` Watch: ${parsed.whatToWatch}`);
22645
+ return lines.join("\n");
22646
+ } catch {
22647
+ return `- ${s.title ?? "(untitled)"} (${new Date(s.createdAt).toLocaleDateString()})`;
22648
+ }
22649
+ }).join("\n")
22650
+ );
22651
+ }
22603
22652
  parts.push("\n## File Tree (root)\n");
22604
22653
  if (files.length > 0) {
22605
22654
  parts.push(
@@ -22608,7 +22657,7 @@ ${readme.content}` : ""
22608
22657
  } else {
22609
22658
  parts.push("(empty)");
22610
22659
  }
22611
- parts.push("\n## Recent Changes\n");
22660
+ parts.push("\n## Recent Commits\n");
22612
22661
  if (commits.length > 0) {
22613
22662
  parts.push(
22614
22663
  commits.map(
@@ -22618,15 +22667,23 @@ ${readme.content}` : ""
22618
22667
  } else {
22619
22668
  parts.push("No commits yet.");
22620
22669
  }
22621
- parts.push("\n## Open Change Requests\n");
22622
- if (changeRequests.length > 0) {
22670
+ if (appliedCRs.length > 0) {
22671
+ parts.push("\n## Recently Shipped Changes\n");
22672
+ parts.push(
22673
+ appliedCRs.map((cr) => {
22674
+ const summary = cr.aiSummary ? `
22675
+ ${cr.aiSummary}` : "";
22676
+ return `- #${cr.number}: ${cr.title} (${cr.filesChanged} files, +${cr.additions}/-${cr.deletions})${summary}`;
22677
+ }).join("\n")
22678
+ );
22679
+ }
22680
+ if (openCRs.length > 0) {
22681
+ parts.push("\n## Open Changes (pending review)\n");
22623
22682
  parts.push(
22624
- changeRequests.map(
22683
+ openCRs.map(
22625
22684
  (cr) => `- #${cr.number}: ${cr.title} (${cr.filesChanged} files, +${cr.additions}/-${cr.deletions})`
22626
22685
  ).join("\n")
22627
22686
  );
22628
- } else {
22629
- parts.push("No open change requests.");
22630
22687
  }
22631
22688
  return success(parts.join("\n"));
22632
22689
  } catch (error2) {
@@ -22640,18 +22697,18 @@ ${readme.content}` : ""
22640
22697
  function registerFileTools(server2) {
22641
22698
  server2.tool(
22642
22699
  "list_files",
22643
- "Browse files and directories at a path in a project",
22700
+ "Browse files and directories at a path in a workspace",
22644
22701
  {
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)"),
22702
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
22703
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
22647
22704
  path: external_exports.string().default("").describe("Directory path to list (empty string for root)"),
22648
22705
  ref: external_exports.string().optional().describe("Git ref (branch, tag, or commit SHA)")
22649
22706
  },
22650
- async ({ projectId, projectName, path: path3, ref }) => {
22707
+ async ({ workspaceId, workspaceName, path: path3, ref }) => {
22651
22708
  try {
22652
- const id = await resolveProjectId(projectId, projectName);
22653
- const files = await api.projects.listFiles({
22654
- projectId: id,
22709
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
22710
+ const files = await api.workspaces.listFiles({
22711
+ workspaceId: id,
22655
22712
  path: path3,
22656
22713
  ref
22657
22714
  });
@@ -22669,18 +22726,18 @@ function registerFileTools(server2) {
22669
22726
  );
22670
22727
  server2.tool(
22671
22728
  "read_file",
22672
- "Read the content of a file in a project",
22729
+ "Read the content of a file in a workspace",
22673
22730
  {
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)"),
22731
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
22732
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
22676
22733
  path: external_exports.string().describe("File path to read"),
22677
22734
  ref: external_exports.string().optional().describe("Git ref (branch, tag, or commit SHA)")
22678
22735
  },
22679
- async ({ projectId, projectName, path: path3, ref }) => {
22736
+ async ({ workspaceId, workspaceName, path: path3, ref }) => {
22680
22737
  try {
22681
- const id = await resolveProjectId(projectId, projectName);
22682
- const result = await api.projects.readFile({
22683
- projectId: id,
22738
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
22739
+ const result = await api.workspaces.readFile({
22740
+ workspaceId: id,
22684
22741
  path: path3,
22685
22742
  ref
22686
22743
  });
@@ -22692,24 +22749,24 @@ function registerFileTools(server2) {
22692
22749
  );
22693
22750
  server2.tool(
22694
22751
  "search_files",
22695
- "Find files by name pattern in a project (recursive, client-side filtering)",
22752
+ "Find files by name pattern in a workspace (recursive, client-side filtering)",
22696
22753
  {
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)"),
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)"),
22699
22756
  pattern: external_exports.string().describe("Pattern to match against file names (case-insensitive substring match)"),
22700
22757
  path: external_exports.string().default("").describe("Directory path to search from")
22701
22758
  },
22702
- async ({ projectId, projectName, pattern, path: path3 }) => {
22759
+ async ({ workspaceId, workspaceName, pattern, path: path3 }) => {
22703
22760
  try {
22704
- const id = await resolveProjectId(projectId, projectName);
22761
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
22705
22762
  const matches = [];
22706
22763
  const lowerPattern = pattern.toLowerCase();
22707
22764
  const maxResults = 50;
22708
22765
  const maxDepth = 5;
22709
22766
  async function searchDir(dirPath, depth) {
22710
22767
  if (depth > maxDepth || matches.length >= maxResults) return;
22711
- const files = await api.projects.listFiles({
22712
- projectId: id,
22768
+ const files = await api.workspaces.listFiles({
22769
+ workspaceId: id,
22713
22770
  path: dirPath
22714
22771
  });
22715
22772
  for (const f of files) {
@@ -22741,30 +22798,30 @@ function registerFileTools(server2) {
22741
22798
  function registerChangeRequestTools(server2) {
22742
22799
  server2.tool(
22743
22800
  "list_change_requests",
22744
- "List change requests for a project, optionally filtered by status",
22801
+ "List changes for a workspace, optionally filtered by status",
22745
22802
  {
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)"),
22803
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
22804
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
22748
22805
  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")
22806
+ limit: external_exports.coerce.number().min(1).max(100).default(20).describe("Max results to return")
22750
22807
  },
22751
- async ({ projectId, projectName, status, limit }) => {
22808
+ async ({ workspaceId, workspaceName, status, limit }) => {
22752
22809
  try {
22753
- const id = await resolveProjectId(projectId, projectName);
22810
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
22754
22811
  const crs = await api.changeRequests.list({
22755
- projectId: id,
22812
+ workspaceId: id,
22756
22813
  status,
22757
22814
  limit
22758
22815
  });
22759
22816
  if (crs.length === 0) {
22760
22817
  return success(
22761
- status ? `No ${status} change requests found.` : "No change requests found."
22818
+ status ? `No ${status} changes found.` : "No changes found."
22762
22819
  );
22763
22820
  }
22764
22821
  const lines = crs.map(
22765
22822
  (cr) => `- #${cr.number}: ${cr.title} [${cr.status}] (${cr.filesChanged} files, +${cr.additions}/-${cr.deletions})${cr.author ? ` by ${cr.author.name}` : ""}`
22766
22823
  );
22767
- return success(`Found ${crs.length} change request(s):
22824
+ return success(`Found ${crs.length} change(s):
22768
22825
 
22769
22826
  ${lines.join("\n")}`);
22770
22827
  } catch (error2) {
@@ -22774,21 +22831,21 @@ ${lines.join("\n")}`);
22774
22831
  );
22775
22832
  server2.tool(
22776
22833
  "get_change_request",
22777
- "Get details of a specific change request including changed files and AI summary",
22834
+ "Get details of a specific change including changed files and AI summary",
22778
22835
  {
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")
22836
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
22837
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
22838
+ number: external_exports.coerce.number().describe("Change number")
22782
22839
  },
22783
- async ({ projectId, projectName, number: number3 }) => {
22840
+ async ({ workspaceId, workspaceName, number: number3 }) => {
22784
22841
  try {
22785
- const id = await resolveProjectId(projectId, projectName);
22842
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
22786
22843
  const cr = await api.changeRequests.getByNumber({
22787
- projectId: id,
22844
+ workspaceId: id,
22788
22845
  number: number3
22789
22846
  });
22790
22847
  const changedFiles = await api.changeRequests.listChangedFiles({
22791
- projectId: id,
22848
+ workspaceId: id,
22792
22849
  changeRequestId: cr.id
22793
22850
  });
22794
22851
  const parts = [
@@ -22823,15 +22880,15 @@ ${cr.aiSummary}`);
22823
22880
  );
22824
22881
  server2.tool(
22825
22882
  "list_branches",
22826
- "List branches available for creating change requests. Shows branches ahead of main with linked session info.",
22883
+ "List branches available for creating changes. Shows branches ahead of main with linked session info.",
22827
22884
  {
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)")
22885
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
22886
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)")
22830
22887
  },
22831
- async ({ projectId, projectName }) => {
22888
+ async ({ workspaceId, workspaceName }) => {
22832
22889
  try {
22833
- const id = await resolveProjectId(projectId, projectName);
22834
- const branches = await api.changeRequests.listBranches({ projectId: id });
22890
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
22891
+ const branches = await api.changeRequests.listBranches({ workspaceId: id });
22835
22892
  if (branches.length === 0) {
22836
22893
  return success("No draft branches found. Start a coding session to create one.");
22837
22894
  }
@@ -22849,25 +22906,25 @@ ${lines.join("\n")}`);
22849
22906
  );
22850
22907
  server2.tool(
22851
22908
  "create_change_request",
22852
- "Create a new change request from a source branch",
22909
+ "Create a new change from a source branch",
22853
22910
  {
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"),
22911
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
22912
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
22913
+ title: external_exports.string().min(1).max(500).describe("Title for the change"),
22857
22914
  description: external_exports.string().max(5e3).optional().describe("Description of the changes"),
22858
22915
  sourceBranch: external_exports.string().min(1).describe("Source branch name \u2014 all commits ahead of main form the CR")
22859
22916
  },
22860
- async ({ projectId, projectName, title, description, sourceBranch }) => {
22917
+ async ({ workspaceId, workspaceName, title, description, sourceBranch }) => {
22861
22918
  try {
22862
- const id = await resolveProjectId(projectId, projectName);
22919
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
22863
22920
  const cr = await api.changeRequests.create({
22864
- projectId: id,
22921
+ workspaceId: id,
22865
22922
  title,
22866
22923
  description,
22867
22924
  sourceBranch
22868
22925
  });
22869
22926
  return success(
22870
- `Created change request #${cr.number}: "${cr.title}" (id: ${cr.id})`
22927
+ `Created change #${cr.number}: "${cr.title}" (id: ${cr.id})`
22871
22928
  );
22872
22929
  } catch (error2) {
22873
22930
  return formatError2(error2);
@@ -22876,24 +22933,24 @@ ${lines.join("\n")}`);
22876
22933
  );
22877
22934
  server2.tool(
22878
22935
  "apply_change_request",
22879
- "Apply (merge) a change request",
22936
+ "Apply (merge) a change",
22880
22937
  {
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")
22938
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
22939
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
22940
+ number: external_exports.coerce.number().describe("Change number")
22884
22941
  },
22885
- async ({ projectId, projectName, number: number3 }) => {
22942
+ async ({ workspaceId, workspaceName, number: number3 }) => {
22886
22943
  try {
22887
- const id = await resolveProjectId(projectId, projectName);
22944
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
22888
22945
  const cr = await api.changeRequests.getByNumber({
22889
- projectId: id,
22946
+ workspaceId: id,
22890
22947
  number: number3
22891
22948
  });
22892
22949
  await api.changeRequests.applyChanges({
22893
- projectId: id,
22950
+ workspaceId: id,
22894
22951
  changeRequestId: cr.id
22895
22952
  });
22896
- return success(`Change request #${number3} has been applied.`);
22953
+ return success(`Change #${number3} has been applied.`);
22897
22954
  } catch (error2) {
22898
22955
  return formatError2(error2);
22899
22956
  }
@@ -22901,24 +22958,24 @@ ${lines.join("\n")}`);
22901
22958
  );
22902
22959
  server2.tool(
22903
22960
  "dismiss_change_request",
22904
- "Dismiss (close without applying) a change request",
22961
+ "Dismiss (close without applying) a change",
22905
22962
  {
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")
22963
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
22964
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
22965
+ number: external_exports.coerce.number().describe("Change number")
22909
22966
  },
22910
- async ({ projectId, projectName, number: number3 }) => {
22967
+ async ({ workspaceId, workspaceName, number: number3 }) => {
22911
22968
  try {
22912
- const id = await resolveProjectId(projectId, projectName);
22969
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
22913
22970
  const cr = await api.changeRequests.getByNumber({
22914
- projectId: id,
22971
+ workspaceId: id,
22915
22972
  number: number3
22916
22973
  });
22917
22974
  await api.changeRequests.dismiss({
22918
- projectId: id,
22975
+ workspaceId: id,
22919
22976
  changeRequestId: cr.id
22920
22977
  });
22921
- return success(`Change request #${number3} has been dismissed.`);
22978
+ return success(`Change #${number3} has been dismissed.`);
22922
22979
  } catch (error2) {
22923
22980
  return formatError2(error2);
22924
22981
  }
@@ -22926,12 +22983,12 @@ ${lines.join("\n")}`);
22926
22983
  );
22927
22984
  server2.tool(
22928
22985
  "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).",
22986
+ "Add a comment to a change. Can be a general comment or an inline code comment (provide path, line, and side for inline).",
22930
22987
  {
22931
- changeRequestId: external_exports.string().uuid().describe("Change request ID"),
22988
+ changeRequestId: external_exports.string().uuid().describe("Change ID"),
22932
22989
  body: external_exports.string().min(1).max(1e4).describe("Comment text"),
22933
22990
  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)"),
22991
+ line: external_exports.coerce.number().int().positive().optional().describe("Line number (for inline code comments)"),
22935
22992
  side: external_exports.enum(["LEFT", "RIGHT"]).optional().describe("Diff side (for inline code comments)")
22936
22993
  },
22937
22994
  async ({ changeRequestId, body, path: path3, line, side }) => {
@@ -22965,18 +23022,18 @@ ${h.lines.join("\n")}`
22965
23022
  function registerCommitTools(server2) {
22966
23023
  server2.tool(
22967
23024
  "list_commits",
22968
- "List recent commit history for a project",
23025
+ "List recent commit history for a workspace",
22969
23026
  {
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)"),
23027
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
23028
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
22972
23029
  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")
23030
+ limit: external_exports.coerce.number().min(1).max(200).default(20).describe("Max commits to return")
22974
23031
  },
22975
- async ({ projectId, projectName, ref, limit }) => {
23032
+ async ({ workspaceId, workspaceName, ref, limit }) => {
22976
23033
  try {
22977
- const id = await resolveProjectId(projectId, projectName);
22978
- const commits = await api.projects.listCommits({
22979
- projectId: id,
23034
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
23035
+ const commits = await api.workspaces.listCommits({
23036
+ workspaceId: id,
22980
23037
  ref,
22981
23038
  limit
22982
23039
  });
@@ -22996,29 +23053,29 @@ ${lines.join("\n")}`);
22996
23053
  );
22997
23054
  server2.tool(
22998
23055
  "get_diff",
22999
- "Get the diff for a change request or a specific commit. Provide either changeRequestNumber or commitOid.",
23056
+ "Get the diff for a change or a specific commit. Provide either changeRequestNumber or commitOid.",
23000
23057
  {
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)"),
23058
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
23059
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
23060
+ changeRequestNumber: external_exports.coerce.number().optional().describe("Change number (to get the full diff)"),
23004
23061
  commitOid: external_exports.string().optional().describe("Commit OID (to get a single commit diff)")
23005
23062
  },
23006
- async ({ projectId, projectName, changeRequestNumber, commitOid }) => {
23063
+ async ({ workspaceId, workspaceName, changeRequestNumber, commitOid }) => {
23007
23064
  try {
23008
23065
  if (!changeRequestNumber && !commitOid) {
23009
23066
  return formatError2(
23010
23067
  new Error("Provide either changeRequestNumber or commitOid.")
23011
23068
  );
23012
23069
  }
23013
- const id = await resolveProjectId(projectId, projectName);
23070
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
23014
23071
  const maxFiles = 10;
23015
23072
  if (changeRequestNumber) {
23016
23073
  const cr = await api.changeRequests.getByNumber({
23017
- projectId: id,
23074
+ workspaceId: id,
23018
23075
  number: changeRequestNumber
23019
23076
  });
23020
23077
  const changedFiles2 = await api.changeRequests.listChangedFiles({
23021
- projectId: id,
23078
+ workspaceId: id,
23022
23079
  changeRequestId: cr.id
23023
23080
  });
23024
23081
  const filesToShow2 = changedFiles2.slice(0, maxFiles);
@@ -23029,7 +23086,7 @@ ${lines.join("\n")}`);
23029
23086
  ];
23030
23087
  for (const file of filesToShow2) {
23031
23088
  const diff = await api.changeRequests.getFileDiff({
23032
- projectId: id,
23089
+ workspaceId: id,
23033
23090
  changeRequestId: cr.id,
23034
23091
  filePath: file.path
23035
23092
  });
@@ -23045,8 +23102,8 @@ ${lines.join("\n")}`);
23045
23102
  }
23046
23103
  return success(parts2.join("\n"));
23047
23104
  }
23048
- const changedFiles = await api.projects.listChangedFiles({
23049
- projectId: id,
23105
+ const changedFiles = await api.workspaces.listChangedFiles({
23106
+ workspaceId: id,
23050
23107
  commitOid
23051
23108
  });
23052
23109
  const filesToShow = changedFiles.slice(0, maxFiles);
@@ -23056,8 +23113,8 @@ ${lines.join("\n")}`);
23056
23113
  `
23057
23114
  ];
23058
23115
  for (const file of filesToShow) {
23059
- const diff = await api.projects.getFileDiff({
23060
- projectId: id,
23116
+ const diff = await api.workspaces.getFileDiff({
23117
+ workspaceId: id,
23061
23118
  commitOid,
23062
23119
  filePath: file.path
23063
23120
  });
@@ -23083,24 +23140,34 @@ ${lines.join("\n")}`);
23083
23140
  function registerSessionTools(server2) {
23084
23141
  server2.tool(
23085
23142
  "list_sessions",
23086
- "List AI coding sessions for the current user in a project",
23143
+ "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
23144
  {
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)"),
23145
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
23146
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
23090
23147
  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")
23148
+ limit: external_exports.coerce.number().min(1).max(100).default(20).describe("Max results to return")
23092
23149
  },
23093
- async ({ projectId, projectName, status, limit }) => {
23150
+ async ({ workspaceId, workspaceName, status, limit }) => {
23094
23151
  try {
23095
- const id = await resolveProjectId(projectId, projectName);
23152
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
23096
23153
  const sessions = await api.sessions.list({
23097
- projectId: id,
23154
+ workspaceId: id,
23098
23155
  status,
23099
23156
  limit
23100
23157
  });
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
- );
23158
+ const lines = sessions.map((s) => {
23159
+ const parts = [
23160
+ `- ${s.title ?? "(untitled)"} (id: ${s.id}, by ${s.userName ?? "unknown"}, ${s.messageCount} messages, ${new Date(s.createdAt).toLocaleDateString()})`
23161
+ ];
23162
+ if (s.summary) {
23163
+ try {
23164
+ const parsed = JSON.parse(s.summary);
23165
+ parts.push(` \u2192 ${parsed.whatWasDone}`);
23166
+ } catch {
23167
+ }
23168
+ }
23169
+ return parts.join("\n");
23170
+ });
23104
23171
  return success(
23105
23172
  lines.length > 0 ? `Found ${lines.length} session(s):
23106
23173
 
@@ -23115,25 +23182,41 @@ ${lines.join("\n")}` : "No sessions found."
23115
23182
  "get_session",
23116
23183
  "Get a session with its full chat history",
23117
23184
  {
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)"),
23185
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
23186
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
23120
23187
  sessionId: external_exports.string().uuid().describe("Session ID")
23121
23188
  },
23122
- async ({ projectId, projectName, sessionId }) => {
23189
+ async ({ workspaceId, workspaceName, sessionId }) => {
23123
23190
  try {
23124
- const id = await resolveProjectId(projectId, projectName);
23191
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
23125
23192
  const session = await api.sessions.getById({
23126
- projectId: id,
23193
+ workspaceId: id,
23127
23194
  sessionId
23128
23195
  });
23129
23196
  const parts = [];
23130
23197
  parts.push(`# ${session.title ?? "(untitled)"}`);
23131
- if (session.toolName) parts.push(`Tool: ${session.toolName}`);
23198
+ if (session.client) parts.push(`Client: ${session.client}`);
23132
23199
  parts.push(`Status: ${session.status}`);
23133
23200
  if (session.branch) parts.push(`Branch: ${session.branch}`);
23134
23201
  if (session.changeRequestNumber != null)
23135
- parts.push(`Change Request: #${session.changeRequestNumber}`);
23202
+ parts.push(`Change: #${session.changeRequestNumber}`);
23203
+ if (session.task)
23204
+ parts.push(`Task: #${session.task.number} ${session.task.title} [${session.task.status}]`);
23136
23205
  parts.push(`Created: ${session.createdAt}`);
23206
+ if (session.summary) {
23207
+ try {
23208
+ const s = JSON.parse(session.summary);
23209
+ parts.push("\n## Summary\n");
23210
+ parts.push(`**Asked:** ${s.whatWasAsked}`);
23211
+ parts.push(`**Done:** ${s.whatWasDone}`);
23212
+ if (s.whatToTest.length > 0) {
23213
+ parts.push("**To verify:**");
23214
+ s.whatToTest.forEach((item) => parts.push(`- ${item}`));
23215
+ }
23216
+ if (s.whatToWatch) parts.push(`**Watch out:** ${s.whatToWatch}`);
23217
+ } catch {
23218
+ }
23219
+ }
23137
23220
  parts.push(`
23138
23221
  ## Conversation (${session.messages.length} messages)
23139
23222
  `);
@@ -23157,23 +23240,23 @@ ${lines.join("\n")}` : "No sessions found."
23157
23240
  function registerSearchTools(server2) {
23158
23241
  server2.tool(
23159
23242
  "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?"',
23243
+ `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
23244
  {
23162
- projectId: external_exports.string().uuid().optional().describe(
23163
- "Project ID (auto-resolved from .chapter.json if omitted)"
23245
+ workspaceId: external_exports.string().uuid().optional().describe(
23246
+ "Project ID (auto-resolved from workspace link if omitted)"
23164
23247
  ),
23165
- projectName: external_exports.string().optional().describe("Project name (alternative to projectId)"),
23248
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
23166
23249
  query: external_exports.string().min(1).describe("Natural language search query"),
23167
23250
  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."
23251
+ "Filter by entity type. Omit to search both sessions and changes."
23169
23252
  ),
23170
23253
  limit: external_exports.number().min(1).max(50).default(10).describe("Max results to return")
23171
23254
  },
23172
- async ({ projectId, projectName, query, entityTypes, limit }) => {
23255
+ async ({ workspaceId, workspaceName, query, entityTypes, limit }) => {
23173
23256
  try {
23174
- const id = await resolveProjectId(projectId, projectName);
23257
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
23175
23258
  const results = await api.search.semantic({
23176
- projectId: id,
23259
+ workspaceId: id,
23177
23260
  query,
23178
23261
  entityTypes,
23179
23262
  limit
@@ -23184,7 +23267,7 @@ function registerSearchTools(server2) {
23184
23267
  );
23185
23268
  }
23186
23269
  const lines = results.map((r, i) => {
23187
- const type = r.entityType === "session" ? "Session" : "Change Request";
23270
+ const type = r.entityType === "session" ? "Session" : "Change";
23188
23271
  return `${i + 1}. [${type}] ${r.snippet} (id: ${r.entityId}, relevance: ${r.similarity.toFixed(2)})`;
23189
23272
  });
23190
23273
  return success(
@@ -23199,243 +23282,258 @@ ${lines.join("\n")}`
23199
23282
  );
23200
23283
  }
23201
23284
 
23202
- // src/tools/code-intelligence.ts
23203
- function registerCodeIntelligenceTools(server2) {
23285
+ // src/tools/tasks.ts
23286
+ var STATUS_LABELS = {
23287
+ backlog: "Backlog",
23288
+ todo: "To Do",
23289
+ in_progress: "In Progress",
23290
+ done: "Done"
23291
+ };
23292
+ var PRIORITY_LABELS = {
23293
+ none: "None",
23294
+ urgent: "Urgent",
23295
+ high: "High",
23296
+ medium: "Medium",
23297
+ low: "Low"
23298
+ };
23299
+ function registerTaskTools(server2) {
23204
23300
  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.",
23301
+ "list_tasks",
23302
+ '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
23303
  {
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)")
23304
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
23305
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
23306
+ status: external_exports.enum(["backlog", "todo", "in_progress", "done"]).optional().describe("Filter by status"),
23307
+ priority: external_exports.enum(["none", "urgent", "high", "medium", "low"]).optional().describe("Filter by priority")
23210
23308
  },
23211
- async ({ projectId, projectName }) => {
23309
+ async ({ workspaceId, workspaceName, status, priority }) => {
23212
23310
  try {
23213
- const id = await resolveProjectId(projectId, projectName);
23214
- const buckets = await api.codeIntelligence.getProjectStructure({
23215
- projectId: id
23311
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
23312
+ const tasks = await api.tasks.list({ workspaceId: id, status, priority });
23313
+ if (tasks.length === 0) {
23314
+ return success("No tasks found.");
23315
+ }
23316
+ const lines = tasks.map((t) => {
23317
+ const parts = [`- #${t.number}: ${t.title}`];
23318
+ parts.push(`[${STATUS_LABELS[t.status] ?? t.status}]`);
23319
+ if (t.priority !== "none") parts.push(`(${PRIORITY_LABELS[t.priority] ?? t.priority})`);
23320
+ if (t.assignee) parts.push(`assigned to ${t.assignee.name}`);
23321
+ if (t.sessionCount > 0) parts.push(`(${t.sessionCount} session${t.sessionCount > 1 ? "s" : ""})`);
23322
+ return parts.join(" ");
23216
23323
  });
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}`);
23231
- }
23232
- lines.push("");
23233
- }
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`
23239
- );
23240
- return success(lines.join("\n"));
23324
+ return success(`Found ${tasks.length} task(s):
23325
+
23326
+ ${lines.join("\n")}`);
23241
23327
  } catch (error2) {
23242
23328
  return formatError2(error2);
23243
23329
  }
23244
23330
  }
23245
23331
  );
23246
23332
  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.",
23333
+ "get_task",
23334
+ "Get full details of a task including its description, linked sessions, and history. Use this to understand requirements before starting work on a task.",
23249
23335
  {
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")
23336
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
23337
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
23338
+ number: external_exports.coerce.number().describe("Task number (e.g. 1, 2, 3)")
23253
23339
  },
23254
- async ({ projectId, projectName, filePath }) => {
23340
+ async ({ workspaceId, workspaceName, number: number3 }) => {
23255
23341
  try {
23256
- const id = await resolveProjectId(projectId, projectName);
23257
- const symbols = await api.codeIntelligence.getFileSymbols({
23258
- projectId: id,
23259
- filePath
23260
- });
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
- );
23342
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
23343
+ const task = await api.tasks.getByNumber({ workspaceId: id, number: number3 });
23344
+ const parts = [];
23345
+ parts.push(`# Task #${task.number}: ${task.title}`);
23346
+ parts.push(`Status: ${STATUS_LABELS[task.status] ?? task.status}`);
23347
+ parts.push(`Priority: ${PRIORITY_LABELS[task.priority] ?? task.priority}`);
23348
+ if (task.assignee) parts.push(`Assigned to: ${task.assignee.name}`);
23349
+ if (task.description) parts.push(`
23350
+ ## Description
23351
+
23352
+ ${task.description}`);
23353
+ if (task.sessions.length > 0) {
23354
+ parts.push(`
23355
+ ## Linked Sessions (${task.sessions.length})
23356
+ `);
23357
+ for (const s of task.sessions) {
23358
+ const crInfo = s.changeRequestNumber != null ? ` (CR #${s.changeRequestNumber})` : "";
23359
+ parts.push(`- ${s.title ?? "(untitled)"} \u2014 ${s.status}${crInfo}`);
23360
+ if (s.summary) {
23361
+ try {
23362
+ const parsed = JSON.parse(s.summary);
23363
+ parts.push(` Done: ${parsed.whatWasDone}`);
23364
+ if (parsed.whatToWatch) parts.push(` Watch: ${parsed.whatToWatch}`);
23365
+ } catch {
23366
+ }
23367
+ }
23368
+ }
23273
23369
  }
23274
- return success(lines.join("\n"));
23370
+ return success(parts.join("\n"));
23275
23371
  } catch (error2) {
23276
23372
  return formatError2(error2);
23277
23373
  }
23278
23374
  }
23279
23375
  );
23280
23376
  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.",
23377
+ "create_task",
23378
+ "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.",
23283
23379
  {
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")
23380
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
23381
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
23382
+ title: external_exports.string().min(1).max(500).describe("Task title \u2014 a plain-language description of what to build"),
23383
+ description: external_exports.string().max(5e3).optional().describe("Optional longer description with details or acceptance criteria"),
23384
+ status: external_exports.enum(["backlog", "todo", "in_progress", "done"]).optional().describe("Initial status (defaults to backlog)"),
23385
+ priority: external_exports.enum(["none", "urgent", "high", "medium", "low"]).optional().describe("Priority level")
23287
23386
  },
23288
- async ({ projectId, projectName, filePath }) => {
23387
+ async ({ workspaceId, workspaceName, title, description, status, priority }) => {
23289
23388
  try {
23290
- const id = await resolveProjectId(projectId, projectName);
23291
- const deps = await api.codeIntelligence.getDependencies({
23292
- projectId: id,
23293
- filePath
23389
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
23390
+ const task = await api.tasks.create({
23391
+ workspaceId: id,
23392
+ title,
23393
+ description,
23394
+ status,
23395
+ priority
23294
23396
  });
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}}`);
23304
- }
23305
- }
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}}`);
23314
- }
23315
- }
23316
- return success(lines.join("\n"));
23397
+ return success(
23398
+ `Created task #${task.number}: ${task.title}
23399
+ Status: ${STATUS_LABELS[task.status] ?? task.status}` + (task.priority !== "none" ? `
23400
+ Priority: ${PRIORITY_LABELS[task.priority] ?? task.priority}` : "")
23401
+ );
23317
23402
  } catch (error2) {
23318
23403
  return formatError2(error2);
23319
23404
  }
23320
23405
  }
23321
23406
  );
23322
23407
  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).",
23408
+ "update_task",
23409
+ `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.`,
23325
23410
  {
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)")
23411
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
23412
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
23413
+ number: external_exports.coerce.number().describe("Task number to update"),
23414
+ title: external_exports.string().min(1).max(500).optional().describe("New title"),
23415
+ description: external_exports.string().max(5e3).nullable().optional().describe("New description (null to clear)"),
23416
+ status: external_exports.enum(["backlog", "todo", "in_progress", "done"]).optional().describe('New status \u2014 set to "done" when the task is complete'),
23417
+ priority: external_exports.enum(["none", "urgent", "high", "medium", "low"]).optional().describe("New priority")
23330
23418
  },
23331
- async ({ projectId, projectName, filePath, depth }) => {
23419
+ async ({ workspaceId, workspaceName, number: number3, title, description, status, priority }) => {
23332
23420
  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
23421
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
23422
+ const existing = await api.tasks.getByNumber({ workspaceId: id, number: number3 });
23423
+ const task = await api.tasks.update({
23424
+ taskId: existing.id,
23425
+ title,
23426
+ description,
23427
+ status,
23428
+ priority
23348
23429
  });
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);
23359
- }
23360
- return success(lines.join("\n"));
23430
+ return success(
23431
+ `Updated task #${task.number}: ${task.title}
23432
+ Status: ${STATUS_LABELS[task.status] ?? task.status}` + (task.priority !== "none" ? `
23433
+ Priority: ${PRIORITY_LABELS[task.priority] ?? task.priority}` : "")
23434
+ );
23361
23435
  } catch (error2) {
23362
23436
  return formatError2(error2);
23363
23437
  }
23364
23438
  }
23365
23439
  );
23440
+ }
23441
+
23442
+ // src/tools/activity.ts
23443
+ function registerActivityTools(server2) {
23366
23444
  server2.tool(
23367
- "search_symbols",
23368
- "Search for symbols (functions, classes, types, etc.) by name across the project.",
23445
+ "get_activity",
23446
+ "Get a timeline of recent activity in a workspace \u2014 sessions, changes, tasks, and commits. Use this to understand what has been happening recently.",
23369
23447
  {
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")
23448
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
23449
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
23450
+ limit: external_exports.coerce.number().min(1).max(100).default(20).describe("Max events to return"),
23451
+ since: external_exports.string().optional().describe("ISO date \u2014 only show events after this date (default: 7 days ago)")
23382
23452
  },
23383
- async ({ projectId, projectName, query, symbolType: symbolType2 }) => {
23453
+ async ({ workspaceId, workspaceName, limit, since }) => {
23384
23454
  try {
23385
- const id = await resolveProjectId(projectId, projectName);
23386
- const symbols = await api.codeIntelligence.searchSymbols({
23387
- projectId: id,
23388
- query,
23389
- symbolType: symbolType2
23455
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
23456
+ const events = await api.activity.feed({
23457
+ workspaceId: id,
23458
+ limit,
23459
+ since
23390
23460
  });
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
- );
23404
- }
23405
- return success(lines.join("\n"));
23461
+ const lines = events.map((e) => {
23462
+ const date3 = new Date(e.timestamp).toLocaleString();
23463
+ const author = e.author ? ` by ${e.author}` : "";
23464
+ return `- [${e.type}] ${e.description}${author} (${date3})`;
23465
+ });
23466
+ return success(
23467
+ lines.length > 0 ? `Recent activity (${lines.length} events):
23468
+
23469
+ ${lines.join("\n")}` : "No recent activity found."
23470
+ );
23406
23471
  } catch (error2) {
23407
23472
  return formatError2(error2);
23408
23473
  }
23409
23474
  }
23410
23475
  );
23411
23476
  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.",
23477
+ "get_file_history",
23478
+ "Get the commit history for a specific file, including which AI sessions produced each change. Use this to understand how a file has evolved.",
23414
23479
  {
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)")
23480
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
23481
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)"),
23482
+ filePath: external_exports.string().describe('Path to the file (e.g. "src/index.ts")'),
23483
+ limit: external_exports.coerce.number().min(1).max(100).default(10).describe("Max commits to return")
23417
23484
  },
23418
- async ({ projectId, projectName }) => {
23485
+ async ({ workspaceId, workspaceName, filePath, limit }) => {
23419
23486
  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
- };
23432
- }
23433
- const result = await api.codeIntelligence.reindex({ projectId: id });
23487
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
23488
+ const history = await api.activity.fileHistory({
23489
+ workspaceId: id,
23490
+ filePath,
23491
+ limit
23492
+ });
23493
+ const lines = history.map((entry) => {
23494
+ const date3 = new Date(entry.timestamp).toLocaleString();
23495
+ const parts = [
23496
+ `- ${entry.commitOid.slice(0, 7)} ${entry.message} (${entry.author}, ${date3})`
23497
+ ];
23498
+ if (entry.session) {
23499
+ parts.push(
23500
+ ` Session: ${entry.session.title ?? "(untitled)"} (id: ${entry.session.id})`
23501
+ );
23502
+ }
23503
+ return parts.join("\n");
23504
+ });
23434
23505
  return success(
23435
- `Code indexing job enqueued${result.jobId ? ` (job: ${result.jobId})` : ""}. The project will be fully reindexed in the background.`
23506
+ lines.length > 0 ? `File history for ${filePath} (${lines.length} commits):
23507
+
23508
+ ${lines.join("\n")}` : `No commit history found for ${filePath}.`
23436
23509
  );
23437
- } catch (err) {
23438
- return formatError2(err);
23510
+ } catch (error2) {
23511
+ return formatError2(error2);
23512
+ }
23513
+ }
23514
+ );
23515
+ server2.tool(
23516
+ "get_workspace_stats",
23517
+ "Get high-level project statistics \u2014 sessions this week, total changes, active tasks, and total commits.",
23518
+ {
23519
+ workspaceId: external_exports.string().uuid().optional().describe("Project ID (auto-resolved from workspace link if omitted)"),
23520
+ workspaceName: external_exports.string().optional().describe("Project name (alternative to workspaceId)")
23521
+ },
23522
+ async ({ workspaceId, workspaceName }) => {
23523
+ try {
23524
+ const id = await resolveWorkspaceId(workspaceId, workspaceName);
23525
+ const stats = await api.activity.stats({ workspaceId: id });
23526
+ const lines = [
23527
+ `Sessions this week: ${stats.sessionsThisWeek}`,
23528
+ `Total change requests: ${stats.totalChangeRequests}`,
23529
+ `Active tasks (in progress): ${stats.activeTasks}`,
23530
+ `Total commits: ${stats.totalCommits}`
23531
+ ];
23532
+ return success(`Project stats:
23533
+
23534
+ ${lines.join("\n")}`);
23535
+ } catch (error2) {
23536
+ return formatError2(error2);
23439
23537
  }
23440
23538
  }
23441
23539
  );
@@ -23443,13 +23541,14 @@ Total: ${totalFiles} files, ${totalSymbols} symbols across ${buckets.length} dir
23443
23541
 
23444
23542
  // src/tools/index.ts
23445
23543
  function registerAllTools(server2) {
23446
- registerProjectTools(server2);
23544
+ registerWorkspaceTools(server2);
23447
23545
  registerFileTools(server2);
23448
23546
  registerChangeRequestTools(server2);
23449
23547
  registerCommitTools(server2);
23450
23548
  registerSessionTools(server2);
23451
23549
  registerSearchTools(server2);
23452
- registerCodeIntelligenceTools(server2);
23550
+ registerTaskTools(server2);
23551
+ registerActivityTools(server2);
23453
23552
  }
23454
23553
 
23455
23554
  // src/index.ts