@bretwardjames/ghp-core 0.2.0-beta.3 → 0.2.0-beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -22,54 +32,79 @@ var index_exports = {};
22
32
  __export(index_exports, {
23
33
  BranchLinker: () => BranchLinker,
24
34
  CLI_TO_VSCODE_MAP: () => CLI_TO_VSCODE_MAP,
35
+ ClaudeClient: () => ClaudeClient,
25
36
  DEFAULT_VALUES: () => DEFAULT_VALUES,
37
+ GHP_TOOLS: () => GHP_TOOLS,
26
38
  GitHubAPI: () => GitHubAPI,
27
39
  SETTING_DISPLAY_NAMES: () => SETTING_DISPLAY_NAMES,
28
40
  SYNCABLE_KEYS: () => SYNCABLE_KEYS,
41
+ SessionWatcher: () => SessionWatcher,
42
+ TOOL_NAMES: () => TOOL_NAMES,
29
43
  VSCODE_TO_CLI_MAP: () => VSCODE_TO_CLI_MAP,
30
44
  branchExists: () => branchExists,
45
+ buildConventionsContext: () => buildConventionsContext,
31
46
  buildIssueUrl: () => buildIssueUrl,
32
47
  buildOrgProjectUrl: () => buildOrgProjectUrl,
33
48
  buildProjectUrl: () => buildProjectUrl,
34
49
  buildPullRequestUrl: () => buildPullRequestUrl,
35
50
  buildRepoUrl: () => buildRepoUrl,
51
+ checkTmuxForPermission: () => checkTmuxForPermission,
36
52
  checkoutBranch: () => checkoutBranch,
53
+ claudePrompts: () => prompts_exports,
54
+ cleanupStaleAgents: () => cleanupStaleAgents,
37
55
  computeSettingsDiff: () => computeSettingsDiff,
38
56
  createBranch: () => createBranch,
57
+ createSessionWatcher: () => createSessionWatcher,
39
58
  createWorktree: () => createWorktree,
40
59
  detectRepository: () => detectRepository,
41
60
  extractIssueNumberFromBranch: () => extractIssueNumberFromBranch,
42
61
  fetchOrigin: () => fetchOrigin,
62
+ findSessionFile: () => findSessionFile,
63
+ formatAction: () => formatAction,
43
64
  formatConflict: () => formatConflict,
44
65
  generateBranchName: () => generateBranchName,
45
66
  generateWorktreePath: () => generateWorktreePath,
67
+ getAgent: () => getAgent,
68
+ getAgentByIssue: () => getAgentByIssue,
69
+ getAgentSummaries: () => getAgentSummaries,
46
70
  getAllBranches: () => getAllBranches,
47
71
  getCommitsAhead: () => getCommitsAhead,
48
72
  getCommitsBehind: () => getCommitsBehind,
49
73
  getCurrentBranch: () => getCurrentBranch,
50
74
  getDefaultBranch: () => getDefaultBranch,
51
75
  getDiffSummary: () => getDiffSummary,
76
+ getIssueReferenceText: () => getIssueReferenceText,
52
77
  getLocalBranches: () => getLocalBranches,
78
+ getRegistryPath: () => getRegistryPath,
53
79
  getRemoteBranches: () => getRemoteBranches,
54
80
  getRepositoryRoot: () => getRepositoryRoot,
81
+ getTools: () => getTools,
55
82
  getWorktreeForBranch: () => getWorktreeForBranch,
56
83
  hasDifferences: () => hasDifferences,
57
84
  hasUncommittedChanges: () => hasUncommittedChanges,
58
85
  isGitRepository: () => isGitRepository,
86
+ listAgents: () => listAgents,
59
87
  listWorktrees: () => listWorktrees,
88
+ loadProjectConventions: () => loadProjectConventions,
89
+ loadRegistry: () => loadRegistry,
60
90
  normalizeVSCodeSettings: () => normalizeVSCodeSettings,
61
91
  parseBranchLink: () => parseBranchLink,
62
92
  parseGitHubUrl: () => parseGitHubUrl,
63
93
  parseIssueUrl: () => parseIssueUrl,
94
+ parseSessionLine: () => parseSessionLine,
64
95
  pullLatest: () => pullLatest,
65
96
  queries: () => queries_exports,
97
+ registerAgent: () => registerAgent,
66
98
  removeBranchLinkFromBody: () => removeBranchLinkFromBody,
67
99
  removeWorktree: () => removeWorktree,
68
100
  resolveConflicts: () => resolveConflicts,
69
101
  sanitizeForBranchName: () => sanitizeForBranchName,
102
+ saveRegistry: () => saveRegistry,
70
103
  setBranchLinkInBody: () => setBranchLinkInBody,
71
104
  skip: () => skip,
72
105
  toVSCodeSettings: () => toVSCodeSettings,
106
+ unregisterAgent: () => unregisterAgent,
107
+ updateAgent: () => updateAgent,
73
108
  useCli: () => useCli,
74
109
  useCustom: () => useCustom,
75
110
  useVSCode: () => useVSCode,
@@ -85,6 +120,7 @@ var queries_exports = {};
85
120
  __export(queries_exports, {
86
121
  ADD_COMMENT_MUTATION: () => ADD_COMMENT_MUTATION,
87
122
  ADD_LABELS_MUTATION: () => ADD_LABELS_MUTATION,
123
+ ADD_SUB_ISSUE_MUTATION: () => ADD_SUB_ISSUE_MUTATION,
88
124
  ADD_TO_PROJECT_MUTATION: () => ADD_TO_PROJECT_MUTATION,
89
125
  COLLABORATORS_QUERY: () => COLLABORATORS_QUERY,
90
126
  CREATE_ISSUE_MUTATION: () => CREATE_ISSUE_MUTATION,
@@ -93,6 +129,7 @@ __export(queries_exports, {
93
129
  ISSUE_DETAILS_QUERY: () => ISSUE_DETAILS_QUERY,
94
130
  ISSUE_FOR_UPDATE_QUERY: () => ISSUE_FOR_UPDATE_QUERY,
95
131
  ISSUE_NODE_ID_QUERY: () => ISSUE_NODE_ID_QUERY,
132
+ ISSUE_RELATIONSHIPS_QUERY: () => ISSUE_RELATIONSHIPS_QUERY,
96
133
  ISSUE_TYPES_QUERY: () => ISSUE_TYPES_QUERY,
97
134
  LABEL_EXISTS_QUERY: () => LABEL_EXISTS_QUERY,
98
135
  PROJECT_FIELDS_QUERY: () => PROJECT_FIELDS_QUERY,
@@ -100,6 +137,7 @@ __export(queries_exports, {
100
137
  PROJECT_VIEWS_QUERY: () => PROJECT_VIEWS_QUERY,
101
138
  RECENT_ISSUES_QUERY: () => RECENT_ISSUES_QUERY,
102
139
  REMOVE_LABELS_MUTATION: () => REMOVE_LABELS_MUTATION,
140
+ REMOVE_SUB_ISSUE_MUTATION: () => REMOVE_SUB_ISSUE_MUTATION,
103
141
  REPOSITORY_ID_QUERY: () => REPOSITORY_ID_QUERY,
104
142
  REPOSITORY_PROJECTS_QUERY: () => REPOSITORY_PROJECTS_QUERY,
105
143
  UPDATE_ISSUE_BODY_MUTATION: () => UPDATE_ISSUE_BODY_MUTATION,
@@ -180,6 +218,8 @@ var PROJECT_ITEMS_QUERY = `
180
218
  assignees(first: 5) { nodes { login } }
181
219
  labels(first: 10) { nodes { name color } }
182
220
  repository { name }
221
+ parent { id number title state }
222
+ subIssues(first: 50) { nodes { id number title state } }
183
223
  }
184
224
  ... on PullRequest {
185
225
  title
@@ -468,6 +508,37 @@ var UPDATE_ISSUE_MUTATION = `
468
508
  }
469
509
  }
470
510
  `;
511
+ var ADD_SUB_ISSUE_MUTATION = `
512
+ mutation($issueId: ID!, $subIssueId: ID!) {
513
+ addSubIssue(input: { issueId: $issueId, subIssueId: $subIssueId }) {
514
+ issue { id number title }
515
+ subIssue { id number title }
516
+ }
517
+ }
518
+ `;
519
+ var REMOVE_SUB_ISSUE_MUTATION = `
520
+ mutation($issueId: ID!, $subIssueId: ID!) {
521
+ removeSubIssue(input: { issueId: $issueId, subIssueId: $subIssueId }) {
522
+ issue { id number title }
523
+ subIssue { id number title }
524
+ }
525
+ }
526
+ `;
527
+ var ISSUE_RELATIONSHIPS_QUERY = `
528
+ query($owner: String!, $name: String!, $number: Int!) {
529
+ repository(owner: $owner, name: $name) {
530
+ issue(number: $number) {
531
+ id
532
+ number
533
+ title
534
+ parent { id number title state }
535
+ subIssues(first: 50) {
536
+ nodes { id number title state }
537
+ }
538
+ }
539
+ }
540
+ }
541
+ `;
471
542
 
472
543
  // src/github-api.ts
473
544
  function createAuthError(message, type, details) {
@@ -502,6 +573,7 @@ function checkAuthError(error) {
502
573
  }
503
574
  var GitHubAPI = class {
504
575
  graphqlWithAuth = null;
576
+ graphqlWithSubIssues = null;
505
577
  tokenProvider;
506
578
  onAuthError;
507
579
  username = null;
@@ -535,12 +607,19 @@ var GitHubAPI = class {
535
607
  authorization: `token ${token}`
536
608
  }
537
609
  });
610
+ this.graphqlWithSubIssues = import_graphql.graphql.defaults({
611
+ headers: {
612
+ authorization: `token ${token}`,
613
+ "GraphQL-Features": "sub_issues"
614
+ }
615
+ });
538
616
  try {
539
617
  const response = await this.graphqlWithAuth(VIEWER_QUERY);
540
618
  this.username = response.viewer.login;
541
619
  return true;
542
620
  } catch {
543
621
  this.graphqlWithAuth = null;
622
+ this.graphqlWithSubIssues = null;
544
623
  return false;
545
624
  }
546
625
  }
@@ -582,7 +661,7 @@ var GitHubAPI = class {
582
661
  * Get items from a project
583
662
  */
584
663
  async getProjectItems(projectId, projectTitle) {
585
- if (!this.graphqlWithAuth) throw new Error("Not authenticated");
664
+ if (!this.graphqlWithSubIssues) throw new Error("Not authenticated");
586
665
  const statusField = await this.getStatusField(projectId);
587
666
  const statusOrderMap = /* @__PURE__ */ new Map();
588
667
  if (statusField) {
@@ -590,7 +669,7 @@ var GitHubAPI = class {
590
669
  statusOrderMap.set(opt.name.toLowerCase(), idx);
591
670
  });
592
671
  }
593
- const response = await this.graphqlWithAuth(PROJECT_ITEMS_QUERY, { projectId });
672
+ const response = await this.graphqlWithSubIssues(PROJECT_ITEMS_QUERY, { projectId });
594
673
  return response.node.items.nodes.filter((item) => item.content).map((item) => {
595
674
  const content = item.content;
596
675
  const fields = {};
@@ -624,6 +703,8 @@ var GitHubAPI = class {
624
703
  state = "closed";
625
704
  }
626
705
  }
706
+ const parent = content.parent || null;
707
+ const subIssues = content.subIssues?.nodes || [];
627
708
  return {
628
709
  id: item.id,
629
710
  title: content.title || "Untitled",
@@ -639,7 +720,9 @@ var GitHubAPI = class {
639
720
  url: content.url || null,
640
721
  projectId,
641
722
  projectTitle,
642
- fields
723
+ fields,
724
+ parent,
725
+ subIssues
643
726
  };
644
727
  });
645
728
  }
@@ -1097,6 +1180,101 @@ var GitHubAPI = class {
1097
1180
  return false;
1098
1181
  }
1099
1182
  }
1183
+ // =========================================================================
1184
+ // Sub-Issue / Parent-Child Relationships
1185
+ // =========================================================================
1186
+ /**
1187
+ * Get the node ID for an issue
1188
+ */
1189
+ async getIssueNodeId(repo, issueNumber) {
1190
+ if (!this.graphqlWithAuth) throw new Error("Not authenticated");
1191
+ try {
1192
+ const response = await this.graphqlWithAuth(ISSUE_FOR_UPDATE_QUERY, {
1193
+ owner: repo.owner,
1194
+ name: repo.name,
1195
+ number: issueNumber
1196
+ });
1197
+ return response.repository.issue?.id ?? null;
1198
+ } catch {
1199
+ return null;
1200
+ }
1201
+ }
1202
+ /**
1203
+ * Add a sub-issue to a parent issue
1204
+ * @param repo Repository info
1205
+ * @param parentNumber Parent issue number
1206
+ * @param childNumber Child issue number to add as sub-issue
1207
+ * @returns true if successful
1208
+ */
1209
+ async addSubIssue(repo, parentNumber, childNumber) {
1210
+ if (!this.graphqlWithSubIssues) throw new Error("Not authenticated");
1211
+ try {
1212
+ const [parentId, childId] = await Promise.all([
1213
+ this.getIssueNodeId(repo, parentNumber),
1214
+ this.getIssueNodeId(repo, childNumber)
1215
+ ]);
1216
+ if (!parentId || !childId) {
1217
+ return false;
1218
+ }
1219
+ await this.graphqlWithSubIssues(ADD_SUB_ISSUE_MUTATION, {
1220
+ issueId: parentId,
1221
+ subIssueId: childId
1222
+ });
1223
+ return true;
1224
+ } catch {
1225
+ return false;
1226
+ }
1227
+ }
1228
+ /**
1229
+ * Remove a sub-issue from its parent issue
1230
+ * @param repo Repository info
1231
+ * @param parentNumber Parent issue number
1232
+ * @param childNumber Child issue number to remove
1233
+ * @returns true if successful
1234
+ */
1235
+ async removeSubIssue(repo, parentNumber, childNumber) {
1236
+ if (!this.graphqlWithSubIssues) throw new Error("Not authenticated");
1237
+ try {
1238
+ const [parentId, childId] = await Promise.all([
1239
+ this.getIssueNodeId(repo, parentNumber),
1240
+ this.getIssueNodeId(repo, childNumber)
1241
+ ]);
1242
+ if (!parentId || !childId) {
1243
+ return false;
1244
+ }
1245
+ await this.graphqlWithSubIssues(REMOVE_SUB_ISSUE_MUTATION, {
1246
+ issueId: parentId,
1247
+ subIssueId: childId
1248
+ });
1249
+ return true;
1250
+ } catch {
1251
+ return false;
1252
+ }
1253
+ }
1254
+ /**
1255
+ * Get issue relationships (parent and sub-issues)
1256
+ */
1257
+ async getIssueRelationships(repo, issueNumber) {
1258
+ if (!this.graphqlWithSubIssues) throw new Error("Not authenticated");
1259
+ try {
1260
+ const response = await this.graphqlWithSubIssues(ISSUE_RELATIONSHIPS_QUERY, {
1261
+ owner: repo.owner,
1262
+ name: repo.name,
1263
+ number: issueNumber
1264
+ });
1265
+ const issue = response.repository.issue;
1266
+ if (!issue) return null;
1267
+ return {
1268
+ id: issue.id,
1269
+ number: issue.number,
1270
+ title: issue.title,
1271
+ parent: issue.parent,
1272
+ subIssues: issue.subIssues.nodes
1273
+ };
1274
+ } catch {
1275
+ return null;
1276
+ }
1277
+ }
1100
1278
  };
1101
1279
 
1102
1280
  // src/branch-linker.ts
@@ -1406,13 +1584,13 @@ async function getDefaultBranch(options = {}) {
1406
1584
  }
1407
1585
  return "master";
1408
1586
  }
1409
- function validatePath(path) {
1410
- if (!path || path.trim().length === 0) {
1587
+ function validatePath(path2) {
1588
+ if (!path2 || path2.trim().length === 0) {
1411
1589
  throw new Error("Path cannot be empty");
1412
1590
  }
1413
1591
  const dangerousChars = /[`$;|&<>(){}[\]'"\n\r]/;
1414
- if (dangerousChars.test(path)) {
1415
- throw new Error(`Path contains invalid characters: ${path}`);
1592
+ if (dangerousChars.test(path2)) {
1593
+ throw new Error(`Path contains invalid characters: ${path2}`);
1416
1594
  }
1417
1595
  }
1418
1596
  async function createWorktree(worktreePath, branch, options = {}) {
@@ -1652,58 +1830,1409 @@ function getDiffSummary(diff) {
1652
1830
  }
1653
1831
  return parts.join(", ");
1654
1832
  }
1833
+
1834
+ // src/claude/client.ts
1835
+ var import_sdk = __toESM(require("@anthropic-ai/sdk"), 1);
1836
+
1837
+ // src/claude/prompts/pr-description.ts
1838
+ var PR_DESCRIPTION_PROMPT = `You are a helpful assistant that generates clear, concise pull request descriptions.
1839
+
1840
+ Given a git diff and optionally a linked issue and commit history, generate a PR description that:
1841
+
1842
+ 1. Summarizes what the PR does in 1-2 sentences
1843
+ 2. Lists the key changes made
1844
+ 3. Notes any breaking changes or migration steps needed
1845
+ 4. References the linked issue if provided (following project conventions)
1846
+
1847
+ Format the output as markdown suitable for a GitHub PR description.
1848
+
1849
+ Guidelines:
1850
+ - Be concise but complete
1851
+ - Focus on the "why" not just the "what"
1852
+ - Use bullet points for lists of changes
1853
+ - Highlight any important decisions or trade-offs
1854
+ - If there are UI changes, note that screenshots may be needed
1855
+ - IMPORTANT: Follow any project-specific conventions provided (e.g., "Relates to" vs "Closes")
1856
+
1857
+ Output format:
1858
+ ## Summary
1859
+ [1-2 sentence summary]
1860
+
1861
+ ## Changes
1862
+ - [Change 1]
1863
+ - [Change 2]
1864
+ ...
1865
+
1866
+ ## Notes
1867
+ [Any additional notes, breaking changes, or migration steps - omit if none]
1868
+
1869
+ [Issue reference following project conventions]
1870
+ `;
1871
+ function buildPRDescriptionSystemPrompt(conventions) {
1872
+ let prompt = PR_DESCRIPTION_PROMPT;
1873
+ if (conventions) {
1874
+ prompt += `
1875
+
1876
+ ## Project Conventions
1877
+ ${conventions}
1878
+ `;
1879
+ }
1880
+ return prompt;
1881
+ }
1882
+ function buildPRDescriptionUserPrompt(options) {
1883
+ let prompt = "## Git Diff\n```diff\n" + options.diff + "\n```\n\n";
1884
+ if (options.issue) {
1885
+ prompt += "## Linked Issue\n";
1886
+ prompt += `**#${options.issue.number}: ${options.issue.title}**
1887
+
1888
+ `;
1889
+ prompt += options.issue.body + "\n\n";
1890
+ }
1891
+ if (options.commits && options.commits.length > 0) {
1892
+ prompt += "## Recent Commits\n";
1893
+ for (const commit of options.commits) {
1894
+ prompt += `- ${commit}
1895
+ `;
1896
+ }
1897
+ prompt += "\n";
1898
+ }
1899
+ if (options.context) {
1900
+ prompt += "## Additional Context\n" + options.context + "\n";
1901
+ }
1902
+ return prompt;
1903
+ }
1904
+
1905
+ // src/claude/prompts/plan-epic.ts
1906
+ var PLAN_EPIC_SYSTEM_PROMPT = `You are an expert software project planner. Your job is to break down epics (large features) into well-structured, actionable GitHub issues.
1907
+
1908
+ When planning an epic, you should:
1909
+
1910
+ 1. **Analyze the Epic**: Understand the scope and goals of the feature
1911
+ 2. **Identify Components**: Break it into logical sub-tasks
1912
+ 3. **Create Issues**: Generate issues that are:
1913
+ - Small enough to complete in 1-3 days
1914
+ - Independent where possible (minimize blocking dependencies)
1915
+ - Clear with acceptance criteria
1916
+ - Properly labeled and categorized
1917
+
1918
+ 4. **Structure Hierarchy**:
1919
+ - Create a parent epic issue first
1920
+ - Create child issues that link to the parent
1921
+ - Set up any blocking dependencies
1922
+
1923
+ Issue Structure Guidelines:
1924
+ - Title: Clear, action-oriented (e.g., "Add user authentication endpoint")
1925
+ - Body should include:
1926
+ - ## Overview (1-2 sentences)
1927
+ - ## Tasks (checkbox list of specific implementation tasks)
1928
+ - ## Acceptance Criteria (what "done" looks like)
1929
+ - ## Dependencies (if any)
1930
+
1931
+ When you have tools available, use them to actually create the issues.
1932
+ When you don't have tools, output a structured plan in markdown format.
1933
+
1934
+ Keep issues focused and atomic. It's better to have more small issues than fewer large ones.
1935
+ `;
1936
+ function buildPlanEpicUserPrompt(options) {
1937
+ let prompt = `# Epic to Plan
1938
+
1939
+ ${options.title}
1940
+
1941
+ `;
1942
+ if (options.context) {
1943
+ prompt += `## Project Context
1944
+ ${options.context}
1945
+
1946
+ `;
1947
+ }
1948
+ if (options.existingIssues && options.existingIssues.length > 0) {
1949
+ prompt += "## Existing Issues in Project\n";
1950
+ prompt += "Consider these existing issues to avoid duplication:\n\n";
1951
+ for (const issue of options.existingIssues.slice(0, 20)) {
1952
+ prompt += `- #${issue.number}: ${issue.title}
1953
+ `;
1954
+ }
1955
+ prompt += "\n";
1956
+ }
1957
+ prompt += `## Instructions
1958
+ `;
1959
+ prompt += `Break down this epic into actionable issues. `;
1960
+ prompt += `First create the parent epic issue, then create child issues for each component.
1961
+
1962
+ `;
1963
+ prompt += `Use the available tools to create issues if they are available, `;
1964
+ prompt += `otherwise output a structured plan in markdown.
1965
+ `;
1966
+ return prompt;
1967
+ }
1968
+ var EPIC_ISSUE_TEMPLATE = `## Overview
1969
+
1970
+ {overview}
1971
+
1972
+ ## Sub-Issues
1973
+
1974
+ This epic will be broken down into the following issues:
1975
+
1976
+ {subissues}
1977
+
1978
+ ## Acceptance Criteria
1979
+
1980
+ - [ ] All sub-issues completed
1981
+ - [ ] Integration tested
1982
+ - [ ] Documentation updated
1983
+ `;
1984
+ var CHILD_ISSUE_TEMPLATE = `## Overview
1985
+
1986
+ {overview}
1987
+
1988
+ Part of #{parent_number}
1989
+
1990
+ ## Tasks
1991
+
1992
+ {tasks}
1993
+
1994
+ ## Acceptance Criteria
1995
+
1996
+ {acceptance_criteria}
1997
+
1998
+ ## Dependencies
1999
+
2000
+ {dependencies}
2001
+ `;
2002
+
2003
+ // src/claude/prompts/expand-issue.ts
2004
+ var EXPAND_ISSUE_PROMPT = `You are a helpful assistant that expands brief issue descriptions into well-structured GitHub issues.
2005
+
2006
+ Given a brief description, generate a complete issue with:
2007
+ 1. A clear, action-oriented title
2008
+ 2. A detailed description with context
2009
+ 3. Acceptance criteria as a checklist
2010
+ 4. Suggested labels (if applicable)
2011
+
2012
+ Output your response as JSON with this structure:
2013
+ {
2014
+ "title": "Clear action-oriented title",
2015
+ "body": "Full markdown body with ## sections",
2016
+ "labels": ["suggested", "labels"],
2017
+ "assignees": []
2018
+ }
2019
+
2020
+ Guidelines for the body:
2021
+ - Start with a brief ## Overview section (1-2 sentences)
2022
+ - Include ## Acceptance Criteria with checkbox items
2023
+ - Add ## Technical Notes if there are implementation considerations
2024
+ - Keep it concise but complete
2025
+ - Use markdown formatting appropriately
2026
+
2027
+ Common labels to suggest based on content:
2028
+ - bug: for bug fixes
2029
+ - enhancement: for new features
2030
+ - documentation: for docs changes
2031
+ - refactor: for code cleanup
2032
+ - performance: for optimization work
2033
+ - security: for security-related issues
2034
+ - testing: for test-related issues
2035
+
2036
+ Only output the JSON, no additional text.
2037
+ `;
2038
+ function buildExpandIssueUserPrompt(options) {
2039
+ let prompt = `Brief: ${options.brief}
2040
+
2041
+ `;
2042
+ if (options.projectContext) {
2043
+ prompt += `Project Context:
2044
+ ${options.projectContext}
2045
+
2046
+ `;
2047
+ }
2048
+ if (options.repoContext) {
2049
+ prompt += `Repository Context:
2050
+ ${options.repoContext}
2051
+
2052
+ `;
2053
+ }
2054
+ if (options.existingLabels && options.existingLabels.length > 0) {
2055
+ prompt += `Available Labels:
2056
+ ${options.existingLabels.join(", ")}
2057
+
2058
+ `;
2059
+ }
2060
+ return prompt;
2061
+ }
2062
+
2063
+ // src/claude/tools.ts
2064
+ var CREATE_ISSUE_TOOL = {
2065
+ name: "create_issue",
2066
+ description: "Create a new GitHub issue in the repository. Returns the created issue number and URL.",
2067
+ input_schema: {
2068
+ type: "object",
2069
+ properties: {
2070
+ title: {
2071
+ type: "string",
2072
+ description: "The title of the issue. Should be clear and action-oriented."
2073
+ },
2074
+ body: {
2075
+ type: "string",
2076
+ description: "The body/description of the issue in markdown format."
2077
+ },
2078
+ labels: {
2079
+ type: "array",
2080
+ description: "Labels to apply to the issue.",
2081
+ items: { type: "string" }
2082
+ },
2083
+ assignees: {
2084
+ type: "array",
2085
+ description: "GitHub usernames to assign to the issue.",
2086
+ items: { type: "string" }
2087
+ }
2088
+ },
2089
+ required: ["title", "body"]
2090
+ }
2091
+ };
2092
+ var SET_PARENT_TOOL = {
2093
+ name: "set_parent",
2094
+ description: "Set a parent-child relationship between two issues. The child issue will be linked to the parent epic.",
2095
+ input_schema: {
2096
+ type: "object",
2097
+ properties: {
2098
+ child_number: {
2099
+ type: "number",
2100
+ description: "The issue number of the child issue."
2101
+ },
2102
+ parent_number: {
2103
+ type: "number",
2104
+ description: "The issue number of the parent epic."
2105
+ }
2106
+ },
2107
+ required: ["child_number", "parent_number"]
2108
+ }
2109
+ };
2110
+ var SET_FIELD_TOOL = {
2111
+ name: "set_field",
2112
+ description: "Set a project field value on an issue (e.g., Status, Priority, Size).",
2113
+ input_schema: {
2114
+ type: "object",
2115
+ properties: {
2116
+ issue_number: {
2117
+ type: "number",
2118
+ description: "The issue number to update."
2119
+ },
2120
+ field_name: {
2121
+ type: "string",
2122
+ description: 'The name of the field to set (e.g., "Status", "Priority").'
2123
+ },
2124
+ value: {
2125
+ type: "string",
2126
+ description: "The value to set for the field."
2127
+ }
2128
+ },
2129
+ required: ["issue_number", "field_name", "value"]
2130
+ }
2131
+ };
2132
+ var ADD_BLOCKER_TOOL = {
2133
+ name: "add_blocker",
2134
+ description: "Add a blocking dependency between issues. The blocker must be completed before the blocked issue.",
2135
+ input_schema: {
2136
+ type: "object",
2137
+ properties: {
2138
+ blocked_issue: {
2139
+ type: "number",
2140
+ description: "The issue number that is blocked."
2141
+ },
2142
+ blocking_issue: {
2143
+ type: "number",
2144
+ description: "The issue number that is blocking."
2145
+ }
2146
+ },
2147
+ required: ["blocked_issue", "blocking_issue"]
2148
+ }
2149
+ };
2150
+ var ADD_TO_PROJECT_TOOL = {
2151
+ name: "add_to_project",
2152
+ description: "Add an issue to a GitHub Project board.",
2153
+ input_schema: {
2154
+ type: "object",
2155
+ properties: {
2156
+ issue_number: {
2157
+ type: "number",
2158
+ description: "The issue number to add to the project."
2159
+ },
2160
+ project_name: {
2161
+ type: "string",
2162
+ description: "The name of the project to add the issue to."
2163
+ }
2164
+ },
2165
+ required: ["issue_number"]
2166
+ }
2167
+ };
2168
+ var ADD_LABELS_TOOL = {
2169
+ name: "add_labels",
2170
+ description: "Add labels to an existing issue.",
2171
+ input_schema: {
2172
+ type: "object",
2173
+ properties: {
2174
+ issue_number: {
2175
+ type: "number",
2176
+ description: "The issue number to add labels to."
2177
+ },
2178
+ labels: {
2179
+ type: "array",
2180
+ description: "The labels to add.",
2181
+ items: { type: "string" }
2182
+ }
2183
+ },
2184
+ required: ["issue_number", "labels"]
2185
+ }
2186
+ };
2187
+ var GHP_TOOLS = [
2188
+ CREATE_ISSUE_TOOL,
2189
+ SET_PARENT_TOOL,
2190
+ SET_FIELD_TOOL,
2191
+ ADD_BLOCKER_TOOL,
2192
+ ADD_TO_PROJECT_TOOL,
2193
+ ADD_LABELS_TOOL
2194
+ ];
2195
+ function getTools(names) {
2196
+ return GHP_TOOLS.filter((tool) => names.includes(tool.name));
2197
+ }
2198
+ var TOOL_NAMES = {
2199
+ CREATE_ISSUE: "create_issue",
2200
+ SET_PARENT: "set_parent",
2201
+ SET_FIELD: "set_field",
2202
+ ADD_BLOCKER: "add_blocker",
2203
+ ADD_TO_PROJECT: "add_to_project",
2204
+ ADD_LABELS: "add_labels"
2205
+ };
2206
+
2207
+ // src/claude/client.ts
2208
+ var DEFAULT_MODEL = "claude-sonnet-4-20250514";
2209
+ var DEFAULT_MAX_TOKENS = 4096;
2210
+ var ClaudeClient = class {
2211
+ client = null;
2212
+ options;
2213
+ config;
2214
+ constructor(options) {
2215
+ this.options = options;
2216
+ this.config = {
2217
+ model: options.model || DEFAULT_MODEL,
2218
+ maxTokens: options.maxTokens || DEFAULT_MAX_TOKENS
2219
+ };
2220
+ }
2221
+ /**
2222
+ * Initialize the client with an API key
2223
+ */
2224
+ async ensureClient() {
2225
+ if (this.client) {
2226
+ return this.client;
2227
+ }
2228
+ let apiKey = this.options.apiKey;
2229
+ if (!apiKey && this.options.apiKeyProvider) {
2230
+ apiKey = await this.options.apiKeyProvider.getApiKey() ?? void 0;
2231
+ }
2232
+ if (!apiKey) {
2233
+ throw new Error(
2234
+ "No API key provided. Set ANTHROPIC_API_KEY environment variable, pass apiKey option, or provide an apiKeyProvider."
2235
+ );
2236
+ }
2237
+ this.client = new import_sdk.default({ apiKey });
2238
+ return this.client;
2239
+ }
2240
+ /**
2241
+ * Get the current model configuration
2242
+ */
2243
+ get model() {
2244
+ return this.config.model;
2245
+ }
2246
+ /**
2247
+ * Get the current max tokens configuration
2248
+ */
2249
+ get maxTokens() {
2250
+ return this.config.maxTokens;
2251
+ }
2252
+ // =========================================================================
2253
+ // Low-Level API
2254
+ // =========================================================================
2255
+ /**
2256
+ * Send a message to Claude and get a response.
2257
+ * This is the low-level API for custom prompts.
2258
+ */
2259
+ async complete(options) {
2260
+ const client = await this.ensureClient();
2261
+ const maxTokens = options.maxTokens || this.config.maxTokens;
2262
+ const messages = options.messages.map((m) => ({
2263
+ role: m.role,
2264
+ content: m.content
2265
+ }));
2266
+ let text = "";
2267
+ const toolCalls = [];
2268
+ let usage = { input_tokens: 0, output_tokens: 0, total_tokens: 0 };
2269
+ let stopReason = "end_turn";
2270
+ if (options.callbacks?.onToken || options.callbacks?.onToolCall) {
2271
+ const stream = await client.messages.stream({
2272
+ model: this.config.model,
2273
+ max_tokens: maxTokens,
2274
+ system: options.system,
2275
+ messages,
2276
+ tools: options.tools
2277
+ });
2278
+ for await (const event of stream) {
2279
+ if (event.type === "content_block_delta") {
2280
+ const delta = event.delta;
2281
+ if ("text" in delta && delta.text) {
2282
+ text += delta.text;
2283
+ options.callbacks?.onToken?.(delta.text);
2284
+ }
2285
+ } else if (event.type === "message_stop") {
2286
+ } else if (event.type === "message_delta") {
2287
+ if (event.usage) {
2288
+ usage.output_tokens = event.usage.output_tokens;
2289
+ }
2290
+ }
2291
+ }
2292
+ const finalMessage = await stream.finalMessage();
2293
+ usage.input_tokens = finalMessage.usage.input_tokens;
2294
+ usage.output_tokens = finalMessage.usage.output_tokens;
2295
+ usage.total_tokens = usage.input_tokens + usage.output_tokens;
2296
+ stopReason = finalMessage.stop_reason;
2297
+ for (const block of finalMessage.content) {
2298
+ if (block.type === "tool_use") {
2299
+ toolCalls.push({
2300
+ name: block.name,
2301
+ input: block.input
2302
+ });
2303
+ }
2304
+ }
2305
+ } else {
2306
+ const response = await client.messages.create({
2307
+ model: this.config.model,
2308
+ max_tokens: maxTokens,
2309
+ system: options.system,
2310
+ messages,
2311
+ tools: options.tools
2312
+ });
2313
+ usage = {
2314
+ input_tokens: response.usage.input_tokens,
2315
+ output_tokens: response.usage.output_tokens,
2316
+ total_tokens: response.usage.input_tokens + response.usage.output_tokens
2317
+ };
2318
+ stopReason = response.stop_reason;
2319
+ for (const block of response.content) {
2320
+ if (block.type === "text") {
2321
+ text += block.text;
2322
+ } else if (block.type === "tool_use") {
2323
+ toolCalls.push({
2324
+ name: block.name,
2325
+ input: block.input
2326
+ });
2327
+ }
2328
+ }
2329
+ }
2330
+ if (toolCalls.length > 0 && options.toolHandlers) {
2331
+ for (const toolCall of toolCalls) {
2332
+ const handler = options.toolHandlers[toolCall.name];
2333
+ if (handler) {
2334
+ options.callbacks?.onToolCall?.(toolCall.name, toolCall.input);
2335
+ try {
2336
+ const result = await handler(toolCall.input, options.toolContext || {});
2337
+ toolCall.result = result;
2338
+ options.callbacks?.onToolResult?.(toolCall.name, result);
2339
+ } catch (error) {
2340
+ toolCall.result = { error: error instanceof Error ? error.message : "Unknown error" };
2341
+ options.callbacks?.onToolResult?.(toolCall.name, toolCall.result);
2342
+ }
2343
+ }
2344
+ }
2345
+ }
2346
+ return { text, toolCalls, usage, stopReason };
2347
+ }
2348
+ /**
2349
+ * Run a multi-turn conversation with tool use until completion.
2350
+ * Handles the tool use loop automatically.
2351
+ */
2352
+ async runWithTools(options) {
2353
+ const maxTurns = options.maxTurns || 10;
2354
+ const messages = [
2355
+ { role: "user", content: options.initialMessage }
2356
+ ];
2357
+ let totalUsage = { input_tokens: 0, output_tokens: 0, total_tokens: 0 };
2358
+ const allToolCalls = [];
2359
+ let finalText = "";
2360
+ let turn = 0;
2361
+ while (turn < maxTurns) {
2362
+ turn++;
2363
+ const result = await this.complete({
2364
+ system: options.system,
2365
+ messages,
2366
+ tools: options.tools,
2367
+ toolHandlers: options.toolHandlers,
2368
+ toolContext: options.toolContext,
2369
+ maxTokens: options.maxTokens,
2370
+ callbacks: options.callbacks
2371
+ });
2372
+ totalUsage.input_tokens += result.usage.input_tokens;
2373
+ totalUsage.output_tokens += result.usage.output_tokens;
2374
+ totalUsage.total_tokens += result.usage.total_tokens;
2375
+ if (result.toolCalls.length === 0 || result.stopReason !== "tool_use") {
2376
+ finalText = result.text;
2377
+ break;
2378
+ }
2379
+ const assistantContent = [];
2380
+ if (result.text) {
2381
+ assistantContent.push({ type: "text", text: result.text });
2382
+ }
2383
+ for (const tc of result.toolCalls) {
2384
+ assistantContent.push({
2385
+ type: "tool_use",
2386
+ id: `tool_${Date.now()}_${Math.random().toString(36).slice(2)}`,
2387
+ name: tc.name,
2388
+ input: tc.input
2389
+ });
2390
+ allToolCalls.push(tc);
2391
+ }
2392
+ messages.push({ role: "assistant", content: assistantContent });
2393
+ const toolResults = result.toolCalls.map((tc, i) => ({
2394
+ type: "tool_result",
2395
+ tool_use_id: assistantContent[assistantContent.length - result.toolCalls.length + i].id,
2396
+ content: JSON.stringify(tc.result ?? { success: true })
2397
+ }));
2398
+ messages.push({ role: "user", content: toolResults });
2399
+ }
2400
+ return {
2401
+ text: finalText,
2402
+ toolCalls: allToolCalls,
2403
+ usage: totalUsage,
2404
+ stopReason: "end_turn"
2405
+ };
2406
+ }
2407
+ // =========================================================================
2408
+ // High-Level API
2409
+ // =========================================================================
2410
+ /**
2411
+ * Generate a PR description from a diff and optional issue context.
2412
+ */
2413
+ async generatePRDescription(options) {
2414
+ const userMessage = this.buildPRDescriptionPrompt(options);
2415
+ const systemPrompt = buildPRDescriptionSystemPrompt(options.conventions);
2416
+ const result = await this.complete({
2417
+ system: systemPrompt,
2418
+ messages: [{ role: "user", content: userMessage }]
2419
+ });
2420
+ return result.text;
2421
+ }
2422
+ buildPRDescriptionPrompt(options) {
2423
+ let prompt = "## Git Diff\n```diff\n" + options.diff + "\n```\n\n";
2424
+ if (options.issue) {
2425
+ prompt += "## Linked Issue\n";
2426
+ prompt += `**#${options.issue.number}: ${options.issue.title}**
2427
+
2428
+ `;
2429
+ prompt += options.issue.body + "\n\n";
2430
+ }
2431
+ if (options.feedback) {
2432
+ prompt += "## User Feedback\n";
2433
+ prompt += "Please regenerate the PR description taking this feedback into account:\n";
2434
+ prompt += options.feedback + "\n\n";
2435
+ }
2436
+ if (options.commits && options.commits.length > 0) {
2437
+ prompt += "## Recent Commits\n";
2438
+ for (const commit of options.commits) {
2439
+ prompt += `- ${commit}
2440
+ `;
2441
+ }
2442
+ prompt += "\n";
2443
+ }
2444
+ if (options.context) {
2445
+ prompt += "## Additional Context\n" + options.context + "\n";
2446
+ }
2447
+ return prompt;
2448
+ }
2449
+ /**
2450
+ * Plan an epic by breaking it down into issues.
2451
+ * Can use tools to actually create the issues.
2452
+ */
2453
+ async planEpic(options) {
2454
+ const userMessage = buildPlanEpicUserPrompt(options);
2455
+ const createdIssues = [];
2456
+ const wrappedHandlers = {};
2457
+ if (options.tools) {
2458
+ for (const [name, handler] of Object.entries(options.tools)) {
2459
+ wrappedHandlers[name] = async (input, context) => {
2460
+ const result2 = await handler(input, context);
2461
+ if (name === "create_issue" && result2 && typeof result2 === "object") {
2462
+ const issueResult = result2;
2463
+ if (issueResult.number) {
2464
+ createdIssues.push({
2465
+ number: issueResult.number,
2466
+ title: issueResult.title || input.title
2467
+ });
2468
+ }
2469
+ }
2470
+ return result2;
2471
+ };
2472
+ }
2473
+ }
2474
+ const result = await this.runWithTools({
2475
+ system: PLAN_EPIC_SYSTEM_PROMPT,
2476
+ initialMessage: userMessage,
2477
+ tools: GHP_TOOLS,
2478
+ toolHandlers: wrappedHandlers,
2479
+ toolContext: options.tools ? {} : void 0,
2480
+ callbacks: options.callbacks
2481
+ });
2482
+ return {
2483
+ ...result,
2484
+ createdIssues
2485
+ };
2486
+ }
2487
+ /**
2488
+ * Expand a brief issue description into a full issue with acceptance criteria.
2489
+ */
2490
+ async expandIssue(options) {
2491
+ let prompt = `Brief: ${options.brief}
2492
+
2493
+ `;
2494
+ if (options.projectContext) {
2495
+ prompt += `Project Context:
2496
+ ${options.projectContext}
2497
+
2498
+ `;
2499
+ }
2500
+ if (options.repoContext) {
2501
+ prompt += `Repository Context:
2502
+ ${options.repoContext}
2503
+
2504
+ `;
2505
+ }
2506
+ const result = await this.complete({
2507
+ system: EXPAND_ISSUE_PROMPT,
2508
+ messages: [{ role: "user", content: prompt }]
2509
+ });
2510
+ try {
2511
+ const parsed = JSON.parse(result.text);
2512
+ return {
2513
+ title: parsed.title || options.brief,
2514
+ body: parsed.body || result.text,
2515
+ suggestedLabels: parsed.labels,
2516
+ suggestedAssignees: parsed.assignees
2517
+ };
2518
+ } catch {
2519
+ return {
2520
+ title: options.brief,
2521
+ body: result.text
2522
+ };
2523
+ }
2524
+ }
2525
+ };
2526
+
2527
+ // src/claude/prompts/index.ts
2528
+ var prompts_exports = {};
2529
+ __export(prompts_exports, {
2530
+ CHILD_ISSUE_TEMPLATE: () => CHILD_ISSUE_TEMPLATE,
2531
+ EPIC_ISSUE_TEMPLATE: () => EPIC_ISSUE_TEMPLATE,
2532
+ EXPAND_ISSUE_PROMPT: () => EXPAND_ISSUE_PROMPT,
2533
+ PLAN_EPIC_SYSTEM_PROMPT: () => PLAN_EPIC_SYSTEM_PROMPT,
2534
+ PR_DESCRIPTION_PROMPT: () => PR_DESCRIPTION_PROMPT,
2535
+ buildExpandIssueUserPrompt: () => buildExpandIssueUserPrompt,
2536
+ buildPRDescriptionSystemPrompt: () => buildPRDescriptionSystemPrompt,
2537
+ buildPRDescriptionUserPrompt: () => buildPRDescriptionUserPrompt,
2538
+ buildPlanEpicUserPrompt: () => buildPlanEpicUserPrompt
2539
+ });
2540
+
2541
+ // src/conventions.ts
2542
+ var import_fs = require("fs");
2543
+ var import_path = require("path");
2544
+ function extractIssueReferenceConvention(content) {
2545
+ const lowerContent = content.toLowerCase();
2546
+ if (lowerContent.includes("relates to") && lowerContent.includes("not") && (lowerContent.includes("closes") || lowerContent.includes("fixes"))) {
2547
+ return "relates";
2548
+ }
2549
+ if (lowerContent.includes('use "closes"') || lowerContent.includes("use 'closes'")) {
2550
+ return "closes";
2551
+ }
2552
+ if (lowerContent.includes('use "fixes"') || lowerContent.includes("use 'fixes'")) {
2553
+ return "fixes";
2554
+ }
2555
+ const relatesMatch = content.match(/relates to #\d+/i);
2556
+ const closesMatch = content.match(/closes #\d+/i);
2557
+ const fixesMatch = content.match(/fixes #\d+/i);
2558
+ if (relatesMatch && !closesMatch && !fixesMatch) {
2559
+ return "relates";
2560
+ }
2561
+ return null;
2562
+ }
2563
+ function extractCoAuthorFormat(content) {
2564
+ const match = content.match(/Co-Authored-By:\s*[^\n]+/i);
2565
+ if (match) {
2566
+ return match[0];
2567
+ }
2568
+ return null;
2569
+ }
2570
+ function loadProjectConventions(repoRoot) {
2571
+ let claudeMd = null;
2572
+ try {
2573
+ const claudeMdPath = (0, import_path.join)(repoRoot, "CLAUDE.md");
2574
+ if ((0, import_fs.existsSync)(claudeMdPath)) {
2575
+ claudeMd = (0, import_fs.readFileSync)(claudeMdPath, "utf-8");
2576
+ }
2577
+ } catch {
2578
+ }
2579
+ const conventions = {
2580
+ claudeMd,
2581
+ pr: {
2582
+ issueReference: null,
2583
+ coAuthorFormat: null,
2584
+ titleFormat: null,
2585
+ bodyFormat: null
2586
+ },
2587
+ commit: {
2588
+ format: null,
2589
+ coAuthorFormat: null
2590
+ },
2591
+ issue: {
2592
+ titleFormat: null,
2593
+ bodyStructure: null
2594
+ }
2595
+ };
2596
+ if (claudeMd) {
2597
+ conventions.pr.issueReference = extractIssueReferenceConvention(claudeMd);
2598
+ conventions.pr.coAuthorFormat = extractCoAuthorFormat(claudeMd);
2599
+ conventions.commit.coAuthorFormat = conventions.pr.coAuthorFormat;
2600
+ const commitSection = claudeMd.match(/## Commit Messages?\s*\n([\s\S]*?)(?=\n##|\n$|$)/i);
2601
+ if (commitSection) {
2602
+ conventions.commit.format = commitSection[1].trim();
2603
+ }
2604
+ }
2605
+ return conventions;
2606
+ }
2607
+ function buildConventionsContext(conventions) {
2608
+ const parts = [];
2609
+ if (conventions.pr.issueReference) {
2610
+ const refMap = {
2611
+ "relates": 'Use "Relates to #XX" (not "Closes" or "Fixes") unless the PR actually closes the issue',
2612
+ "closes": 'Use "Closes #XX" to reference issues',
2613
+ "fixes": 'Use "Fixes #XX" to reference issues'
2614
+ };
2615
+ parts.push(`Issue References: ${refMap[conventions.pr.issueReference]}`);
2616
+ }
2617
+ if (conventions.pr.coAuthorFormat) {
2618
+ parts.push(`Co-Author Format: ${conventions.pr.coAuthorFormat}`);
2619
+ }
2620
+ if (conventions.commit.format) {
2621
+ parts.push(`Commit Format:
2622
+ ${conventions.commit.format}`);
2623
+ }
2624
+ if (parts.length === 0 && conventions.claudeMd) {
2625
+ const relevantSections = conventions.claudeMd.split("\n").filter(
2626
+ (line) => line.toLowerCase().includes("commit") || line.toLowerCase().includes("pr") || line.toLowerCase().includes("pull request") || line.toLowerCase().includes("issue") || line.toLowerCase().includes("relates") || line.toLowerCase().includes("closes")
2627
+ ).slice(0, 20).join("\n");
2628
+ if (relevantSections) {
2629
+ parts.push(`Project Guidelines:
2630
+ ${relevantSections}`);
2631
+ }
2632
+ }
2633
+ return parts.join("\n\n");
2634
+ }
2635
+ function getIssueReferenceText(issueNumber, conventions, actuallyCloses = false) {
2636
+ if (actuallyCloses) {
2637
+ if (conventions.pr.issueReference === "fixes") {
2638
+ return `Fixes #${issueNumber}`;
2639
+ }
2640
+ return `Closes #${issueNumber}`;
2641
+ }
2642
+ switch (conventions.pr.issueReference) {
2643
+ case "relates":
2644
+ return `Relates to #${issueNumber}`;
2645
+ case "closes":
2646
+ return `Closes #${issueNumber}`;
2647
+ case "fixes":
2648
+ return `Fixes #${issueNumber}`;
2649
+ default:
2650
+ return `Relates to #${issueNumber}`;
2651
+ }
2652
+ }
2653
+
2654
+ // src/agents/registry.ts
2655
+ var import_fs2 = require("fs");
2656
+ var import_os2 = require("os");
2657
+ var import_path2 = require("path");
2658
+ var import_crypto = require("crypto");
2659
+ var REGISTRY_VERSION = 1;
2660
+ function getRegistryPath() {
2661
+ return (0, import_path2.join)((0, import_os2.homedir)(), ".ghp", "agents.json");
2662
+ }
2663
+ function loadRegistry() {
2664
+ const path2 = getRegistryPath();
2665
+ if (!(0, import_fs2.existsSync)(path2)) {
2666
+ return {
2667
+ version: REGISTRY_VERSION,
2668
+ agents: {},
2669
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2670
+ };
2671
+ }
2672
+ try {
2673
+ const content = (0, import_fs2.readFileSync)(path2, "utf-8");
2674
+ const registry = JSON.parse(content);
2675
+ if (registry.version !== REGISTRY_VERSION) {
2676
+ registry.version = REGISTRY_VERSION;
2677
+ }
2678
+ return registry;
2679
+ } catch (error) {
2680
+ console.error("Warning: Could not parse agents.json, starting fresh");
2681
+ return {
2682
+ version: REGISTRY_VERSION,
2683
+ agents: {},
2684
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2685
+ };
2686
+ }
2687
+ }
2688
+ function saveRegistry(registry) {
2689
+ const path2 = getRegistryPath();
2690
+ const dir = (0, import_path2.dirname)(path2);
2691
+ if (!(0, import_fs2.existsSync)(dir)) {
2692
+ (0, import_fs2.mkdirSync)(dir, { recursive: true });
2693
+ }
2694
+ registry.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2695
+ (0, import_fs2.writeFileSync)(path2, JSON.stringify(registry, null, 2));
2696
+ }
2697
+ function registerAgent(options) {
2698
+ const registry = loadRegistry();
2699
+ const agent = {
2700
+ id: (0, import_crypto.randomUUID)(),
2701
+ issueNumber: options.issueNumber,
2702
+ issueTitle: options.issueTitle,
2703
+ pid: options.pid,
2704
+ port: options.port,
2705
+ worktreePath: options.worktreePath,
2706
+ branch: options.branch,
2707
+ status: "starting",
2708
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
2709
+ };
2710
+ registry.agents[agent.id] = agent;
2711
+ saveRegistry(registry);
2712
+ return agent;
2713
+ }
2714
+ function updateAgent(id, options) {
2715
+ const registry = loadRegistry();
2716
+ const agent = registry.agents[id];
2717
+ if (!agent) {
2718
+ return null;
2719
+ }
2720
+ if (options.status !== void 0) {
2721
+ agent.status = options.status;
2722
+ }
2723
+ if (options.port !== void 0) {
2724
+ agent.port = options.port;
2725
+ }
2726
+ if (options.error !== void 0) {
2727
+ agent.error = options.error;
2728
+ }
2729
+ if (options.currentAction !== void 0) {
2730
+ agent.currentAction = options.currentAction;
2731
+ }
2732
+ if (options.waitingForInput !== void 0) {
2733
+ agent.waitingForInput = options.waitingForInput;
2734
+ }
2735
+ agent.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
2736
+ saveRegistry(registry);
2737
+ return agent;
2738
+ }
2739
+ function unregisterAgent(id) {
2740
+ const registry = loadRegistry();
2741
+ if (!registry.agents[id]) {
2742
+ return false;
2743
+ }
2744
+ delete registry.agents[id];
2745
+ saveRegistry(registry);
2746
+ return true;
2747
+ }
2748
+ function getAgent(id) {
2749
+ const registry = loadRegistry();
2750
+ return registry.agents[id] || null;
2751
+ }
2752
+ function getAgentByIssue(issueNumber) {
2753
+ const registry = loadRegistry();
2754
+ for (const agent of Object.values(registry.agents)) {
2755
+ if (agent.issueNumber === issueNumber) {
2756
+ return agent;
2757
+ }
2758
+ }
2759
+ return null;
2760
+ }
2761
+ function listAgents() {
2762
+ const registry = loadRegistry();
2763
+ return Object.values(registry.agents);
2764
+ }
2765
+ function formatUptime(startedAt) {
2766
+ const start = new Date(startedAt).getTime();
2767
+ const now = Date.now();
2768
+ const diffMs = now - start;
2769
+ const seconds = Math.floor(diffMs / 1e3);
2770
+ const minutes = Math.floor(seconds / 60);
2771
+ const hours = Math.floor(minutes / 60);
2772
+ const days = Math.floor(hours / 24);
2773
+ if (days > 0) {
2774
+ return `${days}d ${hours % 24}h`;
2775
+ }
2776
+ if (hours > 0) {
2777
+ return `${hours}h ${minutes % 60}m`;
2778
+ }
2779
+ if (minutes > 0) {
2780
+ return `${minutes}m ${seconds % 60}s`;
2781
+ }
2782
+ return `${seconds}s`;
2783
+ }
2784
+ function getAgentSummaries() {
2785
+ const agents = listAgents();
2786
+ return agents.map((agent) => ({
2787
+ id: agent.id,
2788
+ issueNumber: agent.issueNumber,
2789
+ issueTitle: agent.issueTitle,
2790
+ status: agent.status,
2791
+ port: agent.port,
2792
+ branch: agent.branch,
2793
+ uptime: formatUptime(agent.startedAt),
2794
+ currentAction: agent.currentAction,
2795
+ waitingForInput: agent.waitingForInput
2796
+ }));
2797
+ }
2798
+ function cleanupStaleAgents() {
2799
+ return 0;
2800
+ }
2801
+
2802
+ // src/agents/session-watcher.ts
2803
+ var fs = __toESM(require("fs"), 1);
2804
+ var path = __toESM(require("path"), 1);
2805
+ var os = __toESM(require("os"), 1);
2806
+ var import_events = require("events");
2807
+ var import_child_process2 = require("child_process");
2808
+ var import_util2 = require("util");
2809
+ var execAsync2 = (0, import_util2.promisify)(import_child_process2.exec);
2810
+ function pathToClaudeProjectName(dirPath) {
2811
+ return dirPath.replace(/\//g, "-").replace(/-\./g, "--");
2812
+ }
2813
+ async function findSessionFile(worktreePath) {
2814
+ const claudeProjectsDir = path.join(os.homedir(), ".claude", "projects");
2815
+ const projectName = pathToClaudeProjectName(worktreePath);
2816
+ const projectDir = path.join(claudeProjectsDir, projectName);
2817
+ try {
2818
+ const files = await fs.promises.readdir(projectDir);
2819
+ const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
2820
+ if (jsonlFiles.length === 0) return null;
2821
+ let mostRecent = null;
2822
+ for (const file of jsonlFiles) {
2823
+ const filePath = path.join(projectDir, file);
2824
+ const stat = await fs.promises.stat(filePath);
2825
+ if (!mostRecent || stat.mtimeMs > mostRecent.mtime) {
2826
+ mostRecent = { file: filePath, mtime: stat.mtimeMs };
2827
+ }
2828
+ }
2829
+ return mostRecent?.file || null;
2830
+ } catch {
2831
+ return null;
2832
+ }
2833
+ }
2834
+ function parseSessionLine(line) {
2835
+ try {
2836
+ const entry = JSON.parse(line);
2837
+ const timestamp = new Date(entry.timestamp || Date.now());
2838
+ if (entry.type === "progress" && entry.data?.message) {
2839
+ const innerMessage = entry.data.message;
2840
+ const messageType = innerMessage.type;
2841
+ const messageContent = innerMessage.message?.content;
2842
+ if (messageType === "assistant" && messageContent) {
2843
+ for (const block of messageContent) {
2844
+ if (block.type === "tool_use") {
2845
+ return {
2846
+ type: "tool_start",
2847
+ timestamp,
2848
+ toolName: block.name,
2849
+ toolInput: block.input
2850
+ };
2851
+ }
2852
+ if (block.type === "text" && block.text) {
2853
+ return {
2854
+ type: "text",
2855
+ timestamp,
2856
+ text: block.text
2857
+ };
2858
+ }
2859
+ if (block.type === "thinking") {
2860
+ return {
2861
+ type: "thinking",
2862
+ timestamp,
2863
+ text: block.thinking
2864
+ };
2865
+ }
2866
+ }
2867
+ }
2868
+ if (messageType === "user" && messageContent) {
2869
+ for (const block of messageContent) {
2870
+ if (block.type === "tool_result") {
2871
+ if (typeof block.content === "string" && block.content.includes("rejected")) {
2872
+ return {
2873
+ type: "user_input",
2874
+ timestamp,
2875
+ interrupted: true
2876
+ };
2877
+ }
2878
+ return {
2879
+ type: "tool_end",
2880
+ timestamp,
2881
+ interrupted: false
2882
+ };
2883
+ }
2884
+ }
2885
+ }
2886
+ }
2887
+ if (entry.type === "assistant" && entry.message?.content) {
2888
+ for (const block of entry.message.content) {
2889
+ if (block.type === "tool_use") {
2890
+ return {
2891
+ type: "tool_start",
2892
+ timestamp,
2893
+ toolName: block.name,
2894
+ toolInput: block.input
2895
+ };
2896
+ }
2897
+ if (block.type === "text" && block.text) {
2898
+ return {
2899
+ type: "text",
2900
+ timestamp,
2901
+ text: block.text
2902
+ };
2903
+ }
2904
+ if (block.type === "thinking") {
2905
+ return {
2906
+ type: "thinking",
2907
+ timestamp,
2908
+ text: block.thinking
2909
+ };
2910
+ }
2911
+ }
2912
+ }
2913
+ if (entry.type === "user" && entry.toolUseResult !== void 0) {
2914
+ return {
2915
+ type: "tool_end",
2916
+ timestamp,
2917
+ interrupted: entry.toolUseResult?.interrupted === true,
2918
+ error: entry.toolUseResult?.is_error ? "Tool execution failed" : void 0
2919
+ };
2920
+ }
2921
+ if (entry.type === "user" && entry.message?.content) {
2922
+ const content = entry.message.content;
2923
+ for (const block of content) {
2924
+ if (block.type === "tool_result" && block.content?.includes?.("rejected")) {
2925
+ return {
2926
+ type: "user_input",
2927
+ timestamp,
2928
+ interrupted: true
2929
+ };
2930
+ }
2931
+ }
2932
+ }
2933
+ return null;
2934
+ } catch {
2935
+ return null;
2936
+ }
2937
+ }
2938
+ function formatAction(toolName, toolInput) {
2939
+ switch (toolName) {
2940
+ case "Read":
2941
+ return `Reading ${toolInput?.file_path || "file"}`;
2942
+ case "Write":
2943
+ return `Writing ${toolInput?.file_path || "file"}`;
2944
+ case "Edit":
2945
+ return `Editing ${toolInput?.file_path || "file"}`;
2946
+ case "Bash":
2947
+ const cmd = toolInput?.command;
2948
+ if (cmd) {
2949
+ const shortCmd = cmd.length > 40 ? cmd.substring(0, 37) + "..." : cmd;
2950
+ return `Running: ${shortCmd}`;
2951
+ }
2952
+ return "Running command";
2953
+ case "Grep":
2954
+ return `Searching for "${toolInput?.pattern || "pattern"}"`;
2955
+ case "Glob":
2956
+ return `Finding files: ${toolInput?.pattern || "pattern"}`;
2957
+ case "WebFetch":
2958
+ return `Fetching ${toolInput?.url || "URL"}`;
2959
+ case "WebSearch":
2960
+ return `Searching: ${toolInput?.query || "query"}`;
2961
+ case "Task":
2962
+ return `Spawning agent: ${toolInput?.description || "task"}`;
2963
+ default:
2964
+ return `Using ${toolName}`;
2965
+ }
2966
+ }
2967
+ async function checkTmuxForPermission(windowName) {
2968
+ try {
2969
+ if (!/^[a-zA-Z0-9_-]+$/.test(windowName)) {
2970
+ return null;
2971
+ }
2972
+ const { stdout } = await execAsync2(`tmux capture-pane -t "${windowName}" -p -S -50 2>/dev/null`);
2973
+ if (!stdout.includes("Do you want to proceed?")) {
2974
+ return null;
2975
+ }
2976
+ let toolName = "Unknown tool";
2977
+ let description;
2978
+ const mcpMatch = stdout.match(/Tool use\s*\n\s*\n\s*([^\n(]+)\(/);
2979
+ if (mcpMatch) {
2980
+ toolName = mcpMatch[1].trim().replace(/\s*\(MCP\)\s*$/, "");
2981
+ const argsMatch = stdout.match(/Tool use\s*\n\s*\n\s*[^\n(]+\(([^)]*)\)/);
2982
+ description = argsMatch ? argsMatch[1].substring(0, 40) : void 0;
2983
+ }
2984
+ if (toolName === "Unknown tool") {
2985
+ const bashMatch = stdout.match(/Bash command[^\n]*\n\s*\n\s*([^\n]+)/);
2986
+ if (bashMatch) {
2987
+ toolName = "Bash";
2988
+ description = bashMatch[1].trim().substring(0, 40);
2989
+ }
2990
+ }
2991
+ if (toolName === "Unknown tool") {
2992
+ const simpleMatch = stdout.match(/●\s*([A-Za-z]+)\s*\(/);
2993
+ if (simpleMatch) {
2994
+ toolName = simpleMatch[1];
2995
+ }
2996
+ }
2997
+ return { toolName, description };
2998
+ } catch {
2999
+ return null;
3000
+ }
3001
+ }
3002
+ var SessionWatcher = class extends import_events.EventEmitter {
3003
+ filePath;
3004
+ watcher = null;
3005
+ position = 0;
3006
+ status;
3007
+ isWatching = false;
3008
+ isReading = false;
3009
+ tmuxWindow = null;
3010
+ tmuxPollInterval = null;
3011
+ constructor(sessionFilePath, tmuxWindowName) {
3012
+ super();
3013
+ this.filePath = sessionFilePath;
3014
+ this.tmuxWindow = tmuxWindowName || null;
3015
+ this.status = {
3016
+ waitingForInput: false,
3017
+ lastActivity: /* @__PURE__ */ new Date(),
3018
+ recentEvents: []
3019
+ };
3020
+ }
3021
+ /**
3022
+ * Get current status
3023
+ */
3024
+ getStatus() {
3025
+ return {
3026
+ ...this.status,
3027
+ recentEvents: [...this.status.recentEvents]
3028
+ };
3029
+ }
3030
+ /**
3031
+ * Start watching the session file
3032
+ */
3033
+ async start() {
3034
+ if (this.isWatching) return;
3035
+ try {
3036
+ const stat = await fs.promises.stat(this.filePath);
3037
+ this.position = stat.size;
3038
+ this.watcher = fs.watch(this.filePath, async (eventType) => {
3039
+ if (eventType === "change") {
3040
+ await this.readNewLines();
3041
+ }
3042
+ });
3043
+ if (this.tmuxWindow) {
3044
+ this.startTmuxPolling();
3045
+ }
3046
+ this.isWatching = true;
3047
+ this.emit("started");
3048
+ } catch (error) {
3049
+ this.emit("error", error);
3050
+ }
3051
+ }
3052
+ /**
3053
+ * Stop watching
3054
+ */
3055
+ stop() {
3056
+ if (this.watcher) {
3057
+ this.watcher.close();
3058
+ this.watcher = null;
3059
+ }
3060
+ if (this.tmuxPollInterval) {
3061
+ clearInterval(this.tmuxPollInterval);
3062
+ this.tmuxPollInterval = null;
3063
+ }
3064
+ this.isWatching = false;
3065
+ this.emit("stopped");
3066
+ }
3067
+ /**
3068
+ * Start polling tmux for permission prompts
3069
+ */
3070
+ startTmuxPolling() {
3071
+ this.tmuxPollInterval = setInterval(async () => {
3072
+ if (!this.tmuxWindow) return;
3073
+ const prompt = await checkTmuxForPermission(this.tmuxWindow);
3074
+ if (prompt && !this.status.waitingForInput) {
3075
+ this.status.waitingForInput = true;
3076
+ this.status.currentAction = `Waiting: ${prompt.toolName}`;
3077
+ this.status.currentTool = prompt.toolName;
3078
+ this.emit("permission", prompt);
3079
+ this.emit("status", this.getStatus());
3080
+ } else if (!prompt && this.status.waitingForInput) {
3081
+ this.status.waitingForInput = false;
3082
+ this.emit("status", this.getStatus());
3083
+ }
3084
+ }, 2e3);
3085
+ }
3086
+ /**
3087
+ * Read new lines from the file
3088
+ */
3089
+ async readNewLines() {
3090
+ if (this.isReading) return;
3091
+ this.isReading = true;
3092
+ try {
3093
+ const handle = await fs.promises.open(this.filePath, "r");
3094
+ const stat = await handle.stat();
3095
+ if (stat.size <= this.position) {
3096
+ await handle.close();
3097
+ return;
3098
+ }
3099
+ const buffer = Buffer.alloc(stat.size - this.position);
3100
+ await handle.read(buffer, 0, buffer.length, this.position);
3101
+ await handle.close();
3102
+ this.position = stat.size;
3103
+ const content = buffer.toString("utf-8");
3104
+ const lines = content.split("\n").filter((l) => l.trim());
3105
+ for (const line of lines) {
3106
+ const event = parseSessionLine(line);
3107
+ if (event) {
3108
+ this.processEvent(event);
3109
+ }
3110
+ }
3111
+ } catch (error) {
3112
+ this.emit("error", error);
3113
+ } finally {
3114
+ this.isReading = false;
3115
+ }
3116
+ }
3117
+ /**
3118
+ * Process a parsed event and update status
3119
+ */
3120
+ processEvent(event) {
3121
+ this.status.lastActivity = event.timestamp;
3122
+ this.status.recentEvents.push(event);
3123
+ if (this.status.recentEvents.length > 10) {
3124
+ this.status.recentEvents.shift();
3125
+ }
3126
+ switch (event.type) {
3127
+ case "tool_start":
3128
+ this.status.currentTool = event.toolName;
3129
+ this.status.currentAction = formatAction(event.toolName, event.toolInput);
3130
+ this.status.waitingForInput = false;
3131
+ break;
3132
+ case "tool_end":
3133
+ if (event.interrupted) {
3134
+ this.status.waitingForInput = true;
3135
+ this.status.currentAction = "Waiting for approval";
3136
+ } else {
3137
+ this.status.currentTool = void 0;
3138
+ this.status.currentAction = void 0;
3139
+ }
3140
+ break;
3141
+ case "user_input":
3142
+ if (event.interrupted) {
3143
+ this.status.waitingForInput = true;
3144
+ this.status.currentAction = "Action rejected";
3145
+ }
3146
+ break;
3147
+ case "text":
3148
+ break;
3149
+ }
3150
+ this.emit("event", event);
3151
+ this.emit("status", this.getStatus());
3152
+ }
3153
+ };
3154
+ async function createSessionWatcher(worktreePath, tmuxWindowName) {
3155
+ const sessionFile = await findSessionFile(worktreePath);
3156
+ if (!sessionFile) return null;
3157
+ return new SessionWatcher(sessionFile, tmuxWindowName);
3158
+ }
1655
3159
  // Annotate the CommonJS export names for ESM import in node:
1656
3160
  0 && (module.exports = {
1657
3161
  BranchLinker,
1658
3162
  CLI_TO_VSCODE_MAP,
3163
+ ClaudeClient,
1659
3164
  DEFAULT_VALUES,
3165
+ GHP_TOOLS,
1660
3166
  GitHubAPI,
1661
3167
  SETTING_DISPLAY_NAMES,
1662
3168
  SYNCABLE_KEYS,
3169
+ SessionWatcher,
3170
+ TOOL_NAMES,
1663
3171
  VSCODE_TO_CLI_MAP,
1664
3172
  branchExists,
3173
+ buildConventionsContext,
1665
3174
  buildIssueUrl,
1666
3175
  buildOrgProjectUrl,
1667
3176
  buildProjectUrl,
1668
3177
  buildPullRequestUrl,
1669
3178
  buildRepoUrl,
3179
+ checkTmuxForPermission,
1670
3180
  checkoutBranch,
3181
+ claudePrompts,
3182
+ cleanupStaleAgents,
1671
3183
  computeSettingsDiff,
1672
3184
  createBranch,
3185
+ createSessionWatcher,
1673
3186
  createWorktree,
1674
3187
  detectRepository,
1675
3188
  extractIssueNumberFromBranch,
1676
3189
  fetchOrigin,
3190
+ findSessionFile,
3191
+ formatAction,
1677
3192
  formatConflict,
1678
3193
  generateBranchName,
1679
3194
  generateWorktreePath,
3195
+ getAgent,
3196
+ getAgentByIssue,
3197
+ getAgentSummaries,
1680
3198
  getAllBranches,
1681
3199
  getCommitsAhead,
1682
3200
  getCommitsBehind,
1683
3201
  getCurrentBranch,
1684
3202
  getDefaultBranch,
1685
3203
  getDiffSummary,
3204
+ getIssueReferenceText,
1686
3205
  getLocalBranches,
3206
+ getRegistryPath,
1687
3207
  getRemoteBranches,
1688
3208
  getRepositoryRoot,
3209
+ getTools,
1689
3210
  getWorktreeForBranch,
1690
3211
  hasDifferences,
1691
3212
  hasUncommittedChanges,
1692
3213
  isGitRepository,
3214
+ listAgents,
1693
3215
  listWorktrees,
3216
+ loadProjectConventions,
3217
+ loadRegistry,
1694
3218
  normalizeVSCodeSettings,
1695
3219
  parseBranchLink,
1696
3220
  parseGitHubUrl,
1697
3221
  parseIssueUrl,
3222
+ parseSessionLine,
1698
3223
  pullLatest,
1699
3224
  queries,
3225
+ registerAgent,
1700
3226
  removeBranchLinkFromBody,
1701
3227
  removeWorktree,
1702
3228
  resolveConflicts,
1703
3229
  sanitizeForBranchName,
3230
+ saveRegistry,
1704
3231
  setBranchLinkInBody,
1705
3232
  skip,
1706
3233
  toVSCodeSettings,
3234
+ unregisterAgent,
3235
+ updateAgent,
1707
3236
  useCli,
1708
3237
  useCustom,
1709
3238
  useVSCode,