@happyvertical/projects 0.79.0 → 0.80.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.
package/AGENT.md CHANGED
@@ -7,7 +7,7 @@ Standardized project management interface for GitHub Projects, Jira, ZenHub, and
7
7
  ## Package Map
8
8
  - Package: `@happyvertical/projects`
9
9
  - Hierarchy path: `@happyvertical/sdk > packages > projects`
10
- - Workspace position: `21 of 31` local packages
10
+ - Workspace position: `21 of 32` local packages
11
11
  - Internal dependencies: `@happyvertical/graphql`, `@happyvertical/repos`
12
12
  - Internal dependents: `@happyvertical/github-actions`
13
13
  - Knowledge graph files: `AGENT.md`, `metadata.json`, `ecosystem-manifest.json`
@@ -1,21 +1,21 @@
1
1
  #!/usr/bin/env node
2
- import { existsSync, mkdirSync, copyFileSync } from "node:fs";
2
+ import { copyFileSync, existsSync, mkdirSync } from "node:fs";
3
3
  import { dirname, join } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- const Dirname = dirname(fileURLToPath(import.meta.url));
6
- const pkgRoot = join(Dirname, "../..");
7
- const targetDir = join(process.cwd(), ".claude");
8
- if (!existsSync(targetDir)) {
9
- mkdirSync(targetDir, { recursive: true });
10
- }
11
- const pkgName = "projects";
12
- const agentMdSrc = existsSync(join(pkgRoot, "AGENT.md")) ? join(pkgRoot, "AGENT.md") : join(pkgRoot, "CLAUDE.md");
13
- const metaSrc = existsSync(join(pkgRoot, "metadata.json")) ? join(pkgRoot, "metadata.json") : join(pkgRoot, ".claude-meta.json");
14
- if (existsSync(agentMdSrc)) {
15
- copyFileSync(agentMdSrc, join(targetDir, `have-${pkgName}.md`));
16
- }
17
- if (existsSync(metaSrc)) {
18
- copyFileSync(metaSrc, join(targetDir, `have-${pkgName}.meta.json`));
19
- }
5
+ //#region src/cli/claude-context.ts
6
+ /**
7
+ * CLI script to install agent context for @happyvertical/projects
8
+ * Run the published context installer binary for this package.
9
+ */
10
+ var pkgRoot = join(dirname(fileURLToPath(import.meta.url)), "../..");
11
+ var targetDir = join(process.cwd(), ".claude");
12
+ if (!existsSync(targetDir)) mkdirSync(targetDir, { recursive: true });
13
+ var pkgName = "projects";
14
+ var agentMdSrc = existsSync(join(pkgRoot, "AGENT.md")) ? join(pkgRoot, "AGENT.md") : join(pkgRoot, "CLAUDE.md");
15
+ var metaSrc = existsSync(join(pkgRoot, "metadata.json")) ? join(pkgRoot, "metadata.json") : join(pkgRoot, ".claude-meta.json");
16
+ if (existsSync(agentMdSrc)) copyFileSync(agentMdSrc, join(targetDir, `have-${pkgName}.md`));
17
+ if (existsSync(metaSrc)) copyFileSync(metaSrc, join(targetDir, `have-${pkgName}.meta.json`));
20
18
  console.log(`✓ Installed @happyvertical/${pkgName} context to .claude/`);
21
- //# sourceMappingURL=claude-context.js.map
19
+ //#endregion
20
+
21
+ //# sourceMappingURL=claude-context.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"claude-context.js","sources":["../../src/cli/claude-context.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * CLI script to install agent context for @happyvertical/projects\n * Run the published context installer binary for this package.\n */\nimport { copyFileSync, existsSync, mkdirSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst Dirname = dirname(fileURLToPath(import.meta.url));\nconst pkgRoot = join(Dirname, '../..');\nconst targetDir = join(process.cwd(), '.claude');\n\nif (!existsSync(targetDir)) {\n mkdirSync(targetDir, { recursive: true });\n}\n\nconst pkgName = 'projects';\nconst agentMdSrc = existsSync(join(pkgRoot, 'AGENT.md'))\n ? join(pkgRoot, 'AGENT.md')\n : join(pkgRoot, 'CLAUDE.md');\nconst metaSrc = existsSync(join(pkgRoot, 'metadata.json'))\n ? join(pkgRoot, 'metadata.json')\n : join(pkgRoot, '.claude-meta.json');\n\nif (existsSync(agentMdSrc)) {\n copyFileSync(agentMdSrc, join(targetDir, `have-${pkgName}.md`));\n}\n\nif (existsSync(metaSrc)) {\n copyFileSync(metaSrc, join(targetDir, `have-${pkgName}.meta.json`));\n}\n\nconsole.log(`✓ Installed @happyvertical/${pkgName} context to .claude/`);\n"],"names":[],"mappings":";;;;AASA,MAAM,UAAU,QAAQ,cAAc,YAAY,GAAG,CAAC;AACtD,MAAM,UAAU,KAAK,SAAS,OAAO;AACrC,MAAM,YAAY,KAAK,QAAQ,IAAA,GAAO,SAAS;AAE/C,IAAI,CAAC,WAAW,SAAS,GAAG;AAC1B,YAAU,WAAW,EAAE,WAAW,KAAA,CAAM;AAC1C;AAEA,MAAM,UAAU;AAChB,MAAM,aAAa,WAAW,KAAK,SAAS,UAAU,CAAC,IACnD,KAAK,SAAS,UAAU,IACxB,KAAK,SAAS,WAAW;AAC7B,MAAM,UAAU,WAAW,KAAK,SAAS,eAAe,CAAC,IACrD,KAAK,SAAS,eAAe,IAC7B,KAAK,SAAS,mBAAmB;AAErC,IAAI,WAAW,UAAU,GAAG;AAC1B,eAAa,YAAY,KAAK,WAAW,QAAQ,OAAO,KAAK,CAAC;AAChE;AAEA,IAAI,WAAW,OAAO,GAAG;AACvB,eAAa,SAAS,KAAK,WAAW,QAAQ,OAAO,YAAY,CAAC;AACpE;AAEA,QAAQ,IAAI,8BAA8B,OAAO,sBAAsB;"}
1
+ {"version":3,"file":"claude-context.js","names":[],"sources":["../../src/cli/claude-context.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * CLI script to install agent context for @happyvertical/projects\n * Run the published context installer binary for this package.\n */\nimport { copyFileSync, existsSync, mkdirSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst Dirname = dirname(fileURLToPath(import.meta.url));\nconst pkgRoot = join(Dirname, '../..');\nconst targetDir = join(process.cwd(), '.claude');\n\nif (!existsSync(targetDir)) {\n mkdirSync(targetDir, { recursive: true });\n}\n\nconst pkgName = 'projects';\nconst agentMdSrc = existsSync(join(pkgRoot, 'AGENT.md'))\n ? join(pkgRoot, 'AGENT.md')\n : join(pkgRoot, 'CLAUDE.md');\nconst metaSrc = existsSync(join(pkgRoot, 'metadata.json'))\n ? join(pkgRoot, 'metadata.json')\n : join(pkgRoot, '.claude-meta.json');\n\nif (existsSync(agentMdSrc)) {\n copyFileSync(agentMdSrc, join(targetDir, `have-${pkgName}.md`));\n}\n\nif (existsSync(metaSrc)) {\n copyFileSync(metaSrc, join(targetDir, `have-${pkgName}.meta.json`));\n}\n\nconsole.log(`✓ Installed @happyvertical/${pkgName} context to .claude/`);\n"],"mappings":";;;;;;;;;AAUA,IAAM,UAAU,KADA,QAAQ,cAAc,OAAO,KAAK,GAAG,CAChC,GAAS,OAAO;AACrC,IAAM,YAAY,KAAK,QAAQ,IAAI,GAAG,SAAS;AAE/C,IAAI,CAAC,WAAW,SAAS,GACvB,UAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAG1C,IAAM,UAAU;AAChB,IAAM,aAAa,WAAW,KAAK,SAAS,UAAU,CAAC,IACnD,KAAK,SAAS,UAAU,IACxB,KAAK,SAAS,WAAW;AAC7B,IAAM,UAAU,WAAW,KAAK,SAAS,eAAe,CAAC,IACrD,KAAK,SAAS,eAAe,IAC7B,KAAK,SAAS,mBAAmB;AAErC,IAAI,WAAW,UAAU,GACvB,aAAa,YAAY,KAAK,WAAW,QAAQ,QAAQ,IAAI,CAAC;AAGhE,IAAI,WAAW,OAAO,GACpB,aAAa,SAAS,KAAK,WAAW,QAAQ,QAAQ,WAAW,CAAC;AAGpE,QAAQ,IAAI,8BAA8B,QAAQ,qBAAqB"}
package/dist/index.js CHANGED
@@ -1,138 +1,170 @@
1
1
  import { GraphQLClient } from "@happyvertical/graphql";
2
- var ProjectErrorCode = /* @__PURE__ */ ((ProjectErrorCode2) => {
3
- ProjectErrorCode2["NOT_FOUND"] = "NOT_FOUND";
4
- ProjectErrorCode2["UNAUTHORIZED"] = "UNAUTHORIZED";
5
- ProjectErrorCode2["INVALID_STATUS"] = "INVALID_STATUS";
6
- ProjectErrorCode2["INVALID_FIELD"] = "INVALID_FIELD";
7
- ProjectErrorCode2["ITEM_NOT_IN_PROJECT"] = "ITEM_NOT_IN_PROJECT";
8
- ProjectErrorCode2["RATE_LIMITED"] = "RATE_LIMITED";
9
- ProjectErrorCode2["NETWORK_ERROR"] = "NETWORK_ERROR";
10
- ProjectErrorCode2["VALIDATION_ERROR"] = "VALIDATION_ERROR";
11
- ProjectErrorCode2["UNKNOWN"] = "UNKNOWN";
12
- return ProjectErrorCode2;
13
- })(ProjectErrorCode || {});
14
- class ProjectError extends Error {
15
- constructor(message, code, statusCode, response) {
16
- super(message);
17
- this.code = code;
18
- this.statusCode = statusCode;
19
- this.response = response;
20
- this.name = "ProjectError";
21
- Error.captureStackTrace(this, this.constructor);
22
- }
23
- static fromHTTPStatus(statusCode, message, response) {
24
- let code;
25
- switch (statusCode) {
26
- case 404:
27
- code = "NOT_FOUND";
28
- break;
29
- case 401:
30
- code = "UNAUTHORIZED";
31
- break;
32
- case 403:
33
- code = "UNAUTHORIZED";
34
- break;
35
- case 429:
36
- code = "RATE_LIMITED";
37
- break;
38
- case 422:
39
- code = "VALIDATION_ERROR";
40
- break;
41
- default:
42
- code = "UNKNOWN";
43
- }
44
- return new ProjectError(message, code, statusCode, response);
45
- }
46
- static networkError(message, cause) {
47
- return new ProjectError(
48
- message,
49
- "NETWORK_ERROR",
50
- void 0,
51
- cause
52
- );
53
- }
54
- isRetryable() {
55
- return this.code === "RATE_LIMITED" || this.code === "NETWORK_ERROR";
56
- }
57
- }
2
+ //#region \0rolldown/runtime.js
3
+ var __defProp = Object.defineProperty;
4
+ var __exportAll = (all, no_symbols) => {
5
+ let target = {};
6
+ for (var name in all) __defProp(target, name, {
7
+ get: all[name],
8
+ enumerable: true
9
+ });
10
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
11
+ return target;
12
+ };
13
+ //#endregion
14
+ //#region src/errors.ts
15
+ /**
16
+ * Project error handling
17
+ */
18
+ var ProjectErrorCode = /* @__PURE__ */ function(ProjectErrorCode) {
19
+ ProjectErrorCode["NOT_FOUND"] = "NOT_FOUND";
20
+ ProjectErrorCode["UNAUTHORIZED"] = "UNAUTHORIZED";
21
+ ProjectErrorCode["INVALID_STATUS"] = "INVALID_STATUS";
22
+ ProjectErrorCode["INVALID_FIELD"] = "INVALID_FIELD";
23
+ ProjectErrorCode["ITEM_NOT_IN_PROJECT"] = "ITEM_NOT_IN_PROJECT";
24
+ ProjectErrorCode["RATE_LIMITED"] = "RATE_LIMITED";
25
+ ProjectErrorCode["NETWORK_ERROR"] = "NETWORK_ERROR";
26
+ ProjectErrorCode["VALIDATION_ERROR"] = "VALIDATION_ERROR";
27
+ ProjectErrorCode["UNKNOWN"] = "UNKNOWN";
28
+ return ProjectErrorCode;
29
+ }({});
30
+ var ProjectError = class ProjectError extends Error {
31
+ code;
32
+ statusCode;
33
+ response;
34
+ constructor(message, code, statusCode, response) {
35
+ super(message);
36
+ this.code = code;
37
+ this.statusCode = statusCode;
38
+ this.response = response;
39
+ this.name = "ProjectError";
40
+ Error.captureStackTrace(this, this.constructor);
41
+ }
42
+ static fromHTTPStatus(statusCode, message, response) {
43
+ let code;
44
+ switch (statusCode) {
45
+ case 404:
46
+ code = "NOT_FOUND";
47
+ break;
48
+ case 401:
49
+ code = "UNAUTHORIZED";
50
+ break;
51
+ case 403:
52
+ code = "UNAUTHORIZED";
53
+ break;
54
+ case 429:
55
+ code = "RATE_LIMITED";
56
+ break;
57
+ case 422:
58
+ code = "VALIDATION_ERROR";
59
+ break;
60
+ default: code = "UNKNOWN";
61
+ }
62
+ return new ProjectError(message, code, statusCode, response);
63
+ }
64
+ static networkError(message, cause) {
65
+ return new ProjectError(message, "NETWORK_ERROR", void 0, cause);
66
+ }
67
+ isRetryable() {
68
+ return this.code === "RATE_LIMITED" || this.code === "NETWORK_ERROR";
69
+ }
70
+ };
71
+ //#endregion
72
+ //#region src/factory.ts
73
+ /**
74
+ * Project factory function
75
+ *
76
+ * Creates project instances following the pattern from @happyvertical/files and @happyvertical/sql
77
+ */
78
+ /**
79
+ * Check if value is already a project instance
80
+ */
58
81
  function isProjectInstance(value) {
59
- return value !== null && typeof value === "object" && "getProject" in value && "addItem" in value && "updateItemStatus" in value && typeof value.getProject === "function";
82
+ return value !== null && typeof value === "object" && "getProject" in value && "addItem" in value && "updateItemStatus" in value && typeof value.getProject === "function";
60
83
  }
84
+ /**
85
+ * Get a project management client instance
86
+ *
87
+ * This is the main entry point following the pattern from @happyvertical/files and @happyvertical/sql.
88
+ * It can accept either a configuration object or an existing project instance.
89
+ *
90
+ * @param options - Project configuration or existing project instance
91
+ * @returns Promise resolving to project interface
92
+ *
93
+ * @example
94
+ * ```typescript
95
+ * import { getProject } from '@happyvertical/projects';
96
+ *
97
+ * // Create a GitHub Projects client
98
+ * const project = await getProject({
99
+ * type: 'github',
100
+ * owner: 'happyvertical',
101
+ * projectId: 'PVT_kwDOB9Y8ns4A8-TY',
102
+ * token: process.env.GITHUB_TOKEN,
103
+ * statusFieldId: 'PVTSSF_lADOB9Y8ns4A8-TYzgw0GaY',
104
+ * statusOptions: {
105
+ * 'New': 'option-id-1',
106
+ * 'Backlog': 'option-id-2',
107
+ * 'In Progress': 'option-id-3',
108
+ * 'Done': 'option-id-4'
109
+ * }
110
+ * });
111
+ *
112
+ * // Use the client
113
+ * await project.addItem(issueNodeId);
114
+ * await project.updateItemStatus(itemId, 'In Progress');
115
+ *
116
+ * // Pass existing instance (returns it unchanged)
117
+ * const sameProject = await getProject(project);
118
+ * ```
119
+ */
61
120
  async function getProject(options) {
62
- if (isProjectInstance(options)) {
63
- return options;
64
- }
65
- if (!options.type) {
66
- throw new ProjectError(
67
- "Project type is required",
68
- ProjectErrorCode.VALIDATION_ERROR
69
- );
70
- }
71
- if (!options.projectId) {
72
- throw new ProjectError(
73
- "Project ID is required",
74
- ProjectErrorCode.VALIDATION_ERROR
75
- );
76
- }
77
- if (!options.token) {
78
- throw new ProjectError(
79
- "Authentication token is required",
80
- ProjectErrorCode.UNAUTHORIZED
81
- );
82
- }
83
- switch (options.type) {
84
- case "github": {
85
- const { GitHubProject: GitHubProject2 } = await Promise.resolve().then(() => index);
86
- return new GitHubProject2(options);
87
- }
88
- case "jira":
89
- throw new ProjectError(
90
- "Jira support not yet implemented",
91
- ProjectErrorCode.UNKNOWN
92
- );
93
- case "zenhub":
94
- throw new ProjectError(
95
- "ZenHub support not yet implemented",
96
- ProjectErrorCode.UNKNOWN
97
- );
98
- case "linear":
99
- throw new ProjectError(
100
- "Linear support not yet implemented",
101
- ProjectErrorCode.UNKNOWN
102
- );
103
- default:
104
- throw new ProjectError(
105
- `Unsupported project type: ${options.type}`,
106
- ProjectErrorCode.VALIDATION_ERROR
107
- );
108
- }
121
+ if (isProjectInstance(options)) return options;
122
+ if (!options.type) throw new ProjectError("Project type is required", ProjectErrorCode.VALIDATION_ERROR);
123
+ if (!options.projectId) throw new ProjectError("Project ID is required", ProjectErrorCode.VALIDATION_ERROR);
124
+ if (!options.token) throw new ProjectError("Authentication token is required", ProjectErrorCode.UNAUTHORIZED);
125
+ switch (options.type) {
126
+ case "github": {
127
+ const { GitHubProject } = await Promise.resolve().then(() => github_exports);
128
+ return new GitHubProject(options);
129
+ }
130
+ case "jira": throw new ProjectError("Jira support not yet implemented", ProjectErrorCode.UNKNOWN);
131
+ case "zenhub": throw new ProjectError("ZenHub support not yet implemented", ProjectErrorCode.UNKNOWN);
132
+ case "linear": throw new ProjectError("Linear support not yet implemented", ProjectErrorCode.UNKNOWN);
133
+ default: throw new ProjectError(`Unsupported project type: ${options.type}`, ProjectErrorCode.VALIDATION_ERROR);
134
+ }
109
135
  }
110
- const GITHUB_DEFAULT_ITEMS_LIMIT = 100;
111
- const GITHUB_ITEMS_PAGE_SIZE = 20;
112
- class GitHubProject {
113
- graphql;
114
- projectId;
115
- owner;
116
- repo;
117
- statusFieldId;
118
- statusOptions;
119
- constructor(config) {
120
- if (config.type !== "github") {
121
- throw new Error("Invalid config type for GitHubProject");
122
- }
123
- this.graphql = new GraphQLClient({
124
- endpoint: "https://api.github.com/graphql",
125
- token: config.token
126
- });
127
- this.projectId = config.projectId;
128
- this.owner = config.owner;
129
- this.repo = config.repo;
130
- this.statusFieldId = config.statusFieldId;
131
- this.statusOptions = config.statusOptions;
132
- }
133
- // Project Info
134
- async getProject() {
135
- const query = `
136
+ //#endregion
137
+ //#region src/github/index.ts
138
+ /**
139
+ * GitHub Projects V2 implementation
140
+ */
141
+ var github_exports = /* @__PURE__ */ __exportAll({ GitHubProject: () => GitHubProject });
142
+ var GITHUB_DEFAULT_ITEMS_LIMIT = 100;
143
+ var GITHUB_ITEMS_PAGE_SIZE = 20;
144
+ /**
145
+ * GitHub Projects V2 implementation
146
+ */
147
+ var GitHubProject = class {
148
+ graphql;
149
+ projectId;
150
+ owner;
151
+ repo;
152
+ statusFieldId;
153
+ statusOptions;
154
+ constructor(config) {
155
+ if (config.type !== "github") throw new Error("Invalid config type for GitHubProject");
156
+ this.graphql = new GraphQLClient({
157
+ endpoint: "https://api.github.com/graphql",
158
+ token: config.token
159
+ });
160
+ this.projectId = config.projectId;
161
+ this.owner = config.owner;
162
+ this.repo = config.repo;
163
+ this.statusFieldId = config.statusFieldId;
164
+ this.statusOptions = config.statusOptions;
165
+ }
166
+ async getProject() {
167
+ const data = await this.graphql.query(`
136
168
  query($projectId: ID!) {
137
169
  node(id: $projectId) {
138
170
  ... on ProjectV2 {
@@ -171,51 +203,45 @@ class GitHubProject {
171
203
  }
172
204
  }
173
205
  }
174
- `;
175
- const data = await this.graphql.query(query, {
176
- projectId: this.projectId
177
- });
178
- const statusField = data.node.fields.nodes.find(
179
- (f) => f.name === "Status" && f.options
180
- );
181
- return {
182
- id: data.node.id,
183
- title: data.node.title,
184
- description: data.node.shortDescription,
185
- owner: data.node.owner.login,
186
- url: data.node.url,
187
- statuses: statusField?.options ? statusField.options.map((opt, index2) => ({
188
- id: opt.id,
189
- name: opt.name,
190
- description: opt.description,
191
- color: opt.color,
192
- order: index2
193
- })) : [],
194
- fields: data.node.fields.nodes.map((f) => ({
195
- id: f.id,
196
- name: f.name,
197
- type: this.mapFieldType(f.dataType),
198
- options: f.options?.map((opt) => ({
199
- id: opt.id,
200
- name: opt.name,
201
- description: opt.description,
202
- color: opt.color
203
- }))
204
- }))
205
- };
206
- }
207
- // Items
208
- /**
209
- * Add an issue or pull request to the configured GitHub project.
210
- *
211
- * The mutation result contains item identity and content type only. Use
212
- * {@link listItems} when content metadata, status, or fields are required.
213
- *
214
- * @param contentId - GitHub node ID for the issue or pull request
215
- * @returns The created provider-neutral project item
216
- */
217
- async addItem(contentId) {
218
- const mutation = `
206
+ `, { projectId: this.projectId });
207
+ const statusField = data.node.fields.nodes.find((f) => f.name === "Status" && f.options);
208
+ return {
209
+ id: data.node.id,
210
+ title: data.node.title,
211
+ description: data.node.shortDescription,
212
+ owner: data.node.owner.login,
213
+ url: data.node.url,
214
+ statuses: statusField?.options ? statusField.options.map((opt, index) => ({
215
+ id: opt.id,
216
+ name: opt.name,
217
+ description: opt.description,
218
+ color: opt.color,
219
+ order: index
220
+ })) : [],
221
+ fields: data.node.fields.nodes.map((f) => ({
222
+ id: f.id,
223
+ name: f.name,
224
+ type: this.mapFieldType(f.dataType),
225
+ options: f.options?.map((opt) => ({
226
+ id: opt.id,
227
+ name: opt.name,
228
+ description: opt.description,
229
+ color: opt.color
230
+ }))
231
+ }))
232
+ };
233
+ }
234
+ /**
235
+ * Add an issue or pull request to the configured GitHub project.
236
+ *
237
+ * The mutation result contains item identity and content type only. Use
238
+ * {@link listItems} when content metadata, status, or fields are required.
239
+ *
240
+ * @param contentId - GitHub node ID for the issue or pull request
241
+ * @returns The created provider-neutral project item
242
+ */
243
+ async addItem(contentId) {
244
+ const data = await this.graphql.mutate(`
219
245
  mutation($projectId: ID!, $contentId: ID!) {
220
246
  addProjectV2ItemById(input: {
221
247
  projectId: $projectId
@@ -236,20 +262,19 @@ class GitHubProject {
236
262
  }
237
263
  }
238
264
  }
239
- `;
240
- const data = await this.graphql.mutate(mutation, {
241
- projectId: this.projectId,
242
- contentId
243
- });
244
- return {
245
- id: data.addProjectV2ItemById.item.id,
246
- contentId: data.addProjectV2ItemById.item.content.id,
247
- fields: {},
248
- type: data.addProjectV2ItemById.item.content.typename
249
- };
250
- }
251
- async removeItem(itemId) {
252
- const mutation = `
265
+ `, {
266
+ projectId: this.projectId,
267
+ contentId
268
+ });
269
+ return {
270
+ id: data.addProjectV2ItemById.item.id,
271
+ contentId: data.addProjectV2ItemById.item.content.id,
272
+ fields: {},
273
+ type: data.addProjectV2ItemById.item.content.typename
274
+ };
275
+ }
276
+ async removeItem(itemId) {
277
+ await this.graphql.mutate(`
253
278
  mutation($projectId: ID!, $itemId: ID!) {
254
279
  deleteProjectV2Item(input: {
255
280
  projectId: $projectId
@@ -258,23 +283,22 @@ class GitHubProject {
258
283
  deletedItemId
259
284
  }
260
285
  }
261
- `;
262
- await this.graphql.mutate(mutation, {
263
- projectId: this.projectId,
264
- itemId
265
- });
266
- }
267
- /**
268
- * Get one GitHub Projects V2 item by its project-item ID.
269
- *
270
- * This identity lookup does not hydrate content metadata, status, or fields.
271
- * Use {@link listItems} for the enriched board-item representation.
272
- *
273
- * @param itemId - GitHub project-item node ID
274
- * @returns The item, or `null` when it or its content is inaccessible
275
- */
276
- async getItem(itemId) {
277
- const query = `
286
+ `, {
287
+ projectId: this.projectId,
288
+ itemId
289
+ });
290
+ }
291
+ /**
292
+ * Get one GitHub Projects V2 item by its project-item ID.
293
+ *
294
+ * This identity lookup does not hydrate content metadata, status, or fields.
295
+ * Use {@link listItems} for the enriched board-item representation.
296
+ *
297
+ * @param itemId - GitHub project-item node ID
298
+ * @returns The item, or `null` when it or its content is inaccessible
299
+ */
300
+ async getItem(itemId) {
301
+ const data = await this.graphql.query(`
278
302
  query($itemId: ID!) {
279
303
  node(id: $itemId) {
280
304
  ... on ProjectV2Item {
@@ -296,29 +320,26 @@ class GitHubProject {
296
320
  }
297
321
  }
298
322
  }
299
- `;
300
- const data = await this.graphql.query(query, { itemId });
301
- if (!data.node?.content?.id || !data.node.content.typename) {
302
- return null;
303
- }
304
- return {
305
- id: data.node.id,
306
- contentId: data.node.content.id,
307
- fields: {},
308
- type: data.node.content.typename
309
- };
310
- }
311
- /**
312
- * List GitHub Projects V2 items after an optional cursor.
313
- *
314
- * Deleted or inaccessible content is skipped, and the result defaults to at
315
- * most 100 accessible items to preserve the adapter's historical limit.
316
- *
317
- * @param filters - Optional cursor and maximum result count
318
- * @returns Provider-neutral project items with content metadata and fields
319
- */
320
- async listItems(filters) {
321
- const query = `
323
+ `, { itemId });
324
+ if (!data.node?.content?.id || !data.node.content.typename) return null;
325
+ return {
326
+ id: data.node.id,
327
+ contentId: data.node.content.id,
328
+ fields: {},
329
+ type: data.node.content.typename
330
+ };
331
+ }
332
+ /**
333
+ * List GitHub Projects V2 items after an optional cursor.
334
+ *
335
+ * Deleted or inaccessible content is skipped, and the result defaults to at
336
+ * most 100 accessible items to preserve the adapter's historical limit.
337
+ *
338
+ * @param filters - Optional cursor and maximum result count
339
+ * @returns Provider-neutral project items with content metadata and fields
340
+ */
341
+ async listItems(filters) {
342
+ const query = `
322
343
  query($projectId: ID!, $first: Int!, $after: String) {
323
344
  node(id: $projectId) {
324
345
  ... on ProjectV2 {
@@ -508,171 +529,111 @@ class GitHubProject {
508
529
  name
509
530
  }
510
531
  `;
511
- const requestedLimit = filters?.limit ?? GITHUB_DEFAULT_ITEMS_LIMIT;
512
- const limit = Number.isFinite(requestedLimit) ? Math.max(0, Math.floor(requestedLimit)) : GITHUB_DEFAULT_ITEMS_LIMIT;
513
- if (limit === 0) {
514
- return [];
515
- }
516
- const items = [];
517
- let after = filters?.cursor;
518
- let fillPage = false;
519
- const seenCursors = /* @__PURE__ */ new Set();
520
- while (items.length < limit) {
521
- const remaining = limit - items.length;
522
- const first = fillPage ? GITHUB_ITEMS_PAGE_SIZE : Math.min(GITHUB_ITEMS_PAGE_SIZE, remaining);
523
- const data = await this.graphql.query(query, {
524
- projectId: this.projectId,
525
- first,
526
- after
527
- });
528
- if (!data.node) {
529
- throw new ProjectError(
530
- `GitHub project ${this.projectId} was not found or is inaccessible`,
531
- ProjectErrorCode.NOT_FOUND
532
- );
533
- }
534
- const connection = data.node.items;
535
- let skippedContent = false;
536
- for (const node of connection.nodes) {
537
- if (!node) {
538
- skippedContent = true;
539
- continue;
540
- }
541
- const item = this.mapProjectItem(node);
542
- if (item) {
543
- items.push(item);
544
- } else {
545
- skippedContent = true;
546
- }
547
- if (items.length >= limit) {
548
- break;
549
- }
550
- }
551
- fillPage = skippedContent;
552
- const { endCursor, hasNextPage } = connection.pageInfo;
553
- if (!hasNextPage || !endCursor || endCursor === after || seenCursors.has(endCursor)) {
554
- break;
555
- }
556
- seenCursors.add(endCursor);
557
- after = endCursor;
558
- }
559
- return items;
560
- }
561
- /**
562
- * Convert one GitHub Projects V2 item into the provider-neutral item shape.
563
- */
564
- mapProjectItem(item) {
565
- const { content } = item;
566
- if (!content) {
567
- return null;
568
- }
569
- const fieldEntries = [];
570
- let status;
571
- for (const fieldValue of item.fieldValues.nodes) {
572
- if (!fieldValue?.field) {
573
- continue;
574
- }
575
- const value = this.mapProjectFieldValue(fieldValue);
576
- if (value !== void 0) {
577
- fieldEntries.push([fieldValue.field.name, value]);
578
- }
579
- const isStatusField = this.statusFieldId ? fieldValue.field.id === this.statusFieldId : fieldValue.field.name.toLowerCase() === "status";
580
- if (isStatusField && typeof value === "string") {
581
- status = value;
582
- }
583
- }
584
- return {
585
- id: item.id,
586
- contentId: content.id,
587
- title: content.title,
588
- url: content.url ?? null,
589
- assignees: content.assignees.nodes.flatMap(
590
- (assignee) => assignee ? [assignee.login] : []
591
- ),
592
- status,
593
- fields: Object.fromEntries(fieldEntries),
594
- type: content.typename
595
- };
596
- }
597
- /**
598
- * Convert GitHub's field-value union into stable scalar or string-array values.
599
- */
600
- mapProjectFieldValue(value) {
601
- switch (value.typename) {
602
- case "ProjectV2ItemFieldDateValue":
603
- return value.date;
604
- case "ProjectV2ItemFieldIterationValue":
605
- return value.title;
606
- case "ProjectV2ItemFieldLabelValue":
607
- return value.labels?.nodes.flatMap(
608
- (label) => label ? [label.name] : []
609
- );
610
- case "ProjectV2ItemFieldMilestoneValue":
611
- return value.milestone?.title;
612
- case "ProjectV2ItemFieldNumberValue":
613
- return value.number;
614
- case "ProjectV2ItemFieldPullRequestValue":
615
- return value.pullRequests?.nodes.flatMap(
616
- (pullRequest) => pullRequest ? [pullRequest.url] : []
617
- );
618
- case "ProjectV2ItemFieldRepositoryValue":
619
- return value.repository?.nameWithOwner;
620
- case "ProjectV2ItemFieldReviewerValue":
621
- return value.reviewers?.nodes.flatMap((reviewer) => {
622
- const name = reviewer?.login ?? reviewer?.slug;
623
- return name ? [name] : [];
624
- });
625
- case "ProjectV2ItemFieldSingleSelectValue":
626
- return value.name;
627
- case "ProjectV2ItemFieldTextValue":
628
- return value.text;
629
- case "ProjectV2ItemFieldUserValue":
630
- return value.users?.nodes.flatMap((user) => user ? [user.login] : []);
631
- case "ProjectV2ItemIssueFieldValue": {
632
- const issueValue = value.issueFieldValue;
633
- switch (issueValue?.typename) {
634
- case "IssueFieldDateValue":
635
- return issueValue.dateValue;
636
- case "IssueFieldMultiSelectValue":
637
- return issueValue.multiSelectOptions?.map((option) => option.name);
638
- case "IssueFieldNumberValue":
639
- return issueValue.numberValue;
640
- case "IssueFieldSingleSelectValue":
641
- return issueValue.name ?? issueValue.singleSelectValue;
642
- case "IssueFieldTextValue":
643
- return issueValue.textValue;
644
- default:
645
- return void 0;
646
- }
647
- }
648
- default:
649
- return void 0;
650
- }
651
- }
652
- // Status Management
653
- async updateItemStatus(itemId, status) {
654
- if (!this.statusFieldId || !this.statusOptions) {
655
- throw new ProjectError(
656
- "Status field configuration is required. Provide statusFieldId and statusOptions in config.",
657
- ProjectErrorCode.INVALID_STATUS
658
- );
659
- }
660
- const statusOptionId = this.statusOptions[status];
661
- if (!statusOptionId) {
662
- throw new ProjectError(
663
- `Status "${status}" not found in project configuration. Available: ${Object.keys(this.statusOptions).join(", ")}`,
664
- ProjectErrorCode.INVALID_STATUS
665
- );
666
- }
667
- await this.updateItemField(itemId, this.statusFieldId, statusOptionId);
668
- }
669
- async listStatuses() {
670
- const project = await this.getProject();
671
- return project.statuses;
672
- }
673
- // Field Management
674
- async updateItemField(itemId, fieldId, value) {
675
- const mutation = `
532
+ const requestedLimit = filters?.limit ?? GITHUB_DEFAULT_ITEMS_LIMIT;
533
+ const limit = Number.isFinite(requestedLimit) ? Math.max(0, Math.floor(requestedLimit)) : GITHUB_DEFAULT_ITEMS_LIMIT;
534
+ if (limit === 0) return [];
535
+ const items = [];
536
+ let after = filters?.cursor;
537
+ let fillPage = false;
538
+ const seenCursors = /* @__PURE__ */ new Set();
539
+ while (items.length < limit) {
540
+ const remaining = limit - items.length;
541
+ const first = fillPage ? GITHUB_ITEMS_PAGE_SIZE : Math.min(GITHUB_ITEMS_PAGE_SIZE, remaining);
542
+ const data = await this.graphql.query(query, {
543
+ projectId: this.projectId,
544
+ first,
545
+ after
546
+ });
547
+ if (!data.node) throw new ProjectError(`GitHub project ${this.projectId} was not found or is inaccessible`, ProjectErrorCode.NOT_FOUND);
548
+ const connection = data.node.items;
549
+ let skippedContent = false;
550
+ for (const node of connection.nodes) {
551
+ if (!node) {
552
+ skippedContent = true;
553
+ continue;
554
+ }
555
+ const item = this.mapProjectItem(node);
556
+ if (item) items.push(item);
557
+ else skippedContent = true;
558
+ if (items.length >= limit) break;
559
+ }
560
+ fillPage = skippedContent;
561
+ const { endCursor, hasNextPage } = connection.pageInfo;
562
+ if (!hasNextPage || !endCursor || endCursor === after || seenCursors.has(endCursor)) break;
563
+ seenCursors.add(endCursor);
564
+ after = endCursor;
565
+ }
566
+ return items;
567
+ }
568
+ /**
569
+ * Convert one GitHub Projects V2 item into the provider-neutral item shape.
570
+ */
571
+ mapProjectItem(item) {
572
+ const { content } = item;
573
+ if (!content) return null;
574
+ const fieldEntries = [];
575
+ let status;
576
+ for (const fieldValue of item.fieldValues.nodes) {
577
+ if (!fieldValue?.field) continue;
578
+ const value = this.mapProjectFieldValue(fieldValue);
579
+ if (value !== void 0) fieldEntries.push([fieldValue.field.name, value]);
580
+ if ((this.statusFieldId ? fieldValue.field.id === this.statusFieldId : fieldValue.field.name.toLowerCase() === "status") && typeof value === "string") status = value;
581
+ }
582
+ return {
583
+ id: item.id,
584
+ contentId: content.id,
585
+ title: content.title,
586
+ url: content.url ?? null,
587
+ assignees: content.assignees.nodes.flatMap((assignee) => assignee ? [assignee.login] : []),
588
+ status,
589
+ fields: Object.fromEntries(fieldEntries),
590
+ type: content.typename
591
+ };
592
+ }
593
+ /**
594
+ * Convert GitHub's field-value union into stable scalar or string-array values.
595
+ */
596
+ mapProjectFieldValue(value) {
597
+ switch (value.typename) {
598
+ case "ProjectV2ItemFieldDateValue": return value.date;
599
+ case "ProjectV2ItemFieldIterationValue": return value.title;
600
+ case "ProjectV2ItemFieldLabelValue": return value.labels?.nodes.flatMap((label) => label ? [label.name] : []);
601
+ case "ProjectV2ItemFieldMilestoneValue": return value.milestone?.title;
602
+ case "ProjectV2ItemFieldNumberValue": return value.number;
603
+ case "ProjectV2ItemFieldPullRequestValue": return value.pullRequests?.nodes.flatMap((pullRequest) => pullRequest ? [pullRequest.url] : []);
604
+ case "ProjectV2ItemFieldRepositoryValue": return value.repository?.nameWithOwner;
605
+ case "ProjectV2ItemFieldReviewerValue": return value.reviewers?.nodes.flatMap((reviewer) => {
606
+ const name = reviewer?.login ?? reviewer?.slug;
607
+ return name ? [name] : [];
608
+ });
609
+ case "ProjectV2ItemFieldSingleSelectValue": return value.name;
610
+ case "ProjectV2ItemFieldTextValue": return value.text;
611
+ case "ProjectV2ItemFieldUserValue": return value.users?.nodes.flatMap((user) => user ? [user.login] : []);
612
+ case "ProjectV2ItemIssueFieldValue": {
613
+ const issueValue = value.issueFieldValue;
614
+ switch (issueValue?.typename) {
615
+ case "IssueFieldDateValue": return issueValue.dateValue;
616
+ case "IssueFieldMultiSelectValue": return issueValue.multiSelectOptions?.map((option) => option.name);
617
+ case "IssueFieldNumberValue": return issueValue.numberValue;
618
+ case "IssueFieldSingleSelectValue": return issueValue.name ?? issueValue.singleSelectValue;
619
+ case "IssueFieldTextValue": return issueValue.textValue;
620
+ default: return;
621
+ }
622
+ }
623
+ default: return;
624
+ }
625
+ }
626
+ async updateItemStatus(itemId, status) {
627
+ if (!this.statusFieldId || !this.statusOptions) throw new ProjectError("Status field configuration is required. Provide statusFieldId and statusOptions in config.", ProjectErrorCode.INVALID_STATUS);
628
+ const statusOptionId = this.statusOptions[status];
629
+ if (!statusOptionId) throw new ProjectError(`Status "${status}" not found in project configuration. Available: ${Object.keys(this.statusOptions).join(", ")}`, ProjectErrorCode.INVALID_STATUS);
630
+ await this.updateItemField(itemId, this.statusFieldId, statusOptionId);
631
+ }
632
+ async listStatuses() {
633
+ return (await this.getProject()).statuses;
634
+ }
635
+ async updateItemField(itemId, fieldId, value) {
636
+ await this.graphql.mutate(`
676
637
  mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $value: ProjectV2FieldValue!) {
677
638
  updateProjectV2ItemFieldValue(input: {
678
639
  projectId: $projectId
@@ -685,56 +646,45 @@ class GitHubProject {
685
646
  }
686
647
  }
687
648
  }
688
- `;
689
- await this.graphql.mutate(mutation, {
690
- projectId: this.projectId,
691
- itemId,
692
- fieldId,
693
- value: { singleSelectOptionId: value }
694
- });
695
- }
696
- async listFields() {
697
- const project = await this.getProject();
698
- return project.fields;
699
- }
700
- /**
701
- * Map GitHub field type to our standard types
702
- */
703
- mapFieldType(dataType) {
704
- switch (dataType.toLowerCase()) {
705
- case "text":
706
- return "text";
707
- case "number":
708
- return "number";
709
- case "date":
710
- return "date";
711
- case "single_select":
712
- return "single_select";
713
- case "iteration":
714
- return "iteration";
715
- default:
716
- return "text";
717
- }
718
- }
719
- }
720
- const index = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
721
- __proto__: null,
722
- GitHubProject
723
- }, Symbol.toStringTag, { value: "Module" }));
724
- const KANBAN_STATUSES = [
725
- "New",
726
- "Backlog",
727
- "Planning",
728
- "Ready",
729
- "In Progress",
730
- "Review",
731
- "Done"
732
- ];
733
- export {
734
- GitHubProject,
735
- KANBAN_STATUSES,
736
- ProjectError,
737
- ProjectErrorCode,
738
- getProject
649
+ `, {
650
+ projectId: this.projectId,
651
+ itemId,
652
+ fieldId,
653
+ value: { singleSelectOptionId: value }
654
+ });
655
+ }
656
+ async listFields() {
657
+ return (await this.getProject()).fields;
658
+ }
659
+ /**
660
+ * Map GitHub field type to our standard types
661
+ */
662
+ mapFieldType(dataType) {
663
+ switch (dataType.toLowerCase()) {
664
+ case "text": return "text";
665
+ case "number": return "number";
666
+ case "date": return "date";
667
+ case "single_select": return "single_select";
668
+ case "iteration": return "iteration";
669
+ default: return "text";
670
+ }
671
+ }
739
672
  };
740
- //# sourceMappingURL=index.js.map
673
+ //#endregion
674
+ //#region src/types.ts
675
+ /**
676
+ * Kanban status names for 6-lane structure
677
+ */
678
+ var KANBAN_STATUSES = [
679
+ "New",
680
+ "Backlog",
681
+ "Planning",
682
+ "Ready",
683
+ "In Progress",
684
+ "Review",
685
+ "Done"
686
+ ];
687
+ //#endregion
688
+ export { GitHubProject, KANBAN_STATUSES, ProjectError, ProjectErrorCode, getProject };
689
+
690
+ //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/errors.ts","../src/factory.ts","../src/github/index.ts","../src/types.ts"],"sourcesContent":["/**\n * Project error handling\n */\n\nexport enum ProjectErrorCode {\n NOT_FOUND = 'NOT_FOUND',\n UNAUTHORIZED = 'UNAUTHORIZED',\n INVALID_STATUS = 'INVALID_STATUS',\n INVALID_FIELD = 'INVALID_FIELD',\n ITEM_NOT_IN_PROJECT = 'ITEM_NOT_IN_PROJECT',\n RATE_LIMITED = 'RATE_LIMITED',\n NETWORK_ERROR = 'NETWORK_ERROR',\n VALIDATION_ERROR = 'VALIDATION_ERROR',\n UNKNOWN = 'UNKNOWN',\n}\n\nexport class ProjectError extends Error {\n constructor(\n message: string,\n public code: ProjectErrorCode,\n public statusCode?: number,\n public response?: unknown,\n ) {\n super(message);\n this.name = 'ProjectError';\n Error.captureStackTrace(this, this.constructor);\n }\n\n static fromHTTPStatus(\n statusCode: number,\n message: string,\n response?: unknown,\n ): ProjectError {\n let code: ProjectErrorCode;\n\n switch (statusCode) {\n case 404:\n code = ProjectErrorCode.NOT_FOUND;\n break;\n case 401:\n code = ProjectErrorCode.UNAUTHORIZED;\n break;\n case 403:\n code = ProjectErrorCode.UNAUTHORIZED;\n break;\n case 429:\n code = ProjectErrorCode.RATE_LIMITED;\n break;\n case 422:\n code = ProjectErrorCode.VALIDATION_ERROR;\n break;\n default:\n code = ProjectErrorCode.UNKNOWN;\n }\n\n return new ProjectError(message, code, statusCode, response);\n }\n\n static networkError(message: string, cause?: Error): ProjectError {\n return new ProjectError(\n message,\n ProjectErrorCode.NETWORK_ERROR,\n undefined,\n cause,\n );\n }\n\n isRetryable(): boolean {\n return (\n this.code === ProjectErrorCode.RATE_LIMITED ||\n this.code === ProjectErrorCode.NETWORK_ERROR\n );\n }\n}\n","/**\n * Project factory function\n *\n * Creates project instances following the pattern from @happyvertical/files and @happyvertical/sql\n */\n\nimport { ProjectError, ProjectErrorCode } from './errors.js';\nimport type { IProject, ProjectConfig } from './types.js';\n\n/**\n * Check if value is already a project instance\n */\nfunction isProjectInstance(value: unknown): value is IProject {\n return (\n value !== null &&\n typeof value === 'object' &&\n 'getProject' in value &&\n 'addItem' in value &&\n 'updateItemStatus' in value &&\n typeof (value as IProject).getProject === 'function'\n );\n}\n\n/**\n * Get a project management client instance\n *\n * This is the main entry point following the pattern from @happyvertical/files and @happyvertical/sql.\n * It can accept either a configuration object or an existing project instance.\n *\n * @param options - Project configuration or existing project instance\n * @returns Promise resolving to project interface\n *\n * @example\n * ```typescript\n * import { getProject } from '@happyvertical/projects';\n *\n * // Create a GitHub Projects client\n * const project = await getProject({\n * type: 'github',\n * owner: 'happyvertical',\n * projectId: 'PVT_kwDOB9Y8ns4A8-TY',\n * token: process.env.GITHUB_TOKEN,\n * statusFieldId: 'PVTSSF_lADOB9Y8ns4A8-TYzgw0GaY',\n * statusOptions: {\n * 'New': 'option-id-1',\n * 'Backlog': 'option-id-2',\n * 'In Progress': 'option-id-3',\n * 'Done': 'option-id-4'\n * }\n * });\n *\n * // Use the client\n * await project.addItem(issueNodeId);\n * await project.updateItemStatus(itemId, 'In Progress');\n *\n * // Pass existing instance (returns it unchanged)\n * const sameProject = await getProject(project);\n * ```\n */\nexport async function getProject(\n options: ProjectConfig | IProject,\n): Promise<IProject> {\n // If already a project instance, return it\n if (isProjectInstance(options)) {\n return options;\n }\n\n // Validate configuration\n if (!options.type) {\n throw new ProjectError(\n 'Project type is required',\n ProjectErrorCode.VALIDATION_ERROR,\n );\n }\n\n if (!options.projectId) {\n throw new ProjectError(\n 'Project ID is required',\n ProjectErrorCode.VALIDATION_ERROR,\n );\n }\n\n if (!options.token) {\n throw new ProjectError(\n 'Authentication token is required',\n ProjectErrorCode.UNAUTHORIZED,\n );\n }\n\n // Create project instance based on type\n switch (options.type) {\n case 'github': {\n const { GitHubProject } = await import('./github/index.js');\n return new GitHubProject(options);\n }\n\n case 'jira':\n throw new ProjectError(\n 'Jira support not yet implemented',\n ProjectErrorCode.UNKNOWN,\n );\n\n case 'zenhub':\n throw new ProjectError(\n 'ZenHub support not yet implemented',\n ProjectErrorCode.UNKNOWN,\n );\n\n case 'linear':\n throw new ProjectError(\n 'Linear support not yet implemented',\n ProjectErrorCode.UNKNOWN,\n );\n\n default:\n throw new ProjectError(\n `Unsupported project type: ${options.type}`,\n ProjectErrorCode.VALIDATION_ERROR,\n );\n }\n}\n","/**\n * GitHub Projects V2 implementation\n */\n\nimport { GraphQLClient, type IGraphQLClient } from '@happyvertical/graphql';\nimport { ProjectError, ProjectErrorCode } from '../errors.js';\nimport type {\n Field,\n IProject,\n ItemFilters,\n Project,\n ProjectConfig,\n ProjectItem,\n Status,\n} from '../types.js';\n\nconst GITHUB_DEFAULT_ITEMS_LIMIT = 100;\nconst GITHUB_ITEMS_PAGE_SIZE = 20;\n\ninterface GitHubProjectItemReference {\n typename: ProjectItem['type'];\n id: string;\n}\n\ninterface GitHubProjectItemContent extends GitHubProjectItemReference {\n title: string;\n url?: string;\n assignees: {\n nodes: Array<{ login: string } | null>;\n };\n}\n\ninterface GitHubProjectFieldValue {\n typename: string;\n field?: {\n id: string;\n name: string;\n };\n date?: string | null;\n title?: string;\n labels?: {\n nodes: Array<{ name: string } | null>;\n } | null;\n milestone?: {\n title: string;\n } | null;\n number?: number | null;\n pullRequests?: {\n nodes: Array<{ url: string } | null>;\n } | null;\n repository?: {\n nameWithOwner: string;\n } | null;\n reviewers?: {\n nodes: Array<{\n login?: string;\n slug?: string;\n } | null>;\n } | null;\n name?: string | null;\n text?: string | null;\n users?: {\n nodes: Array<{ login: string } | null>;\n } | null;\n issueFieldValue?: {\n typename: string;\n name?: string;\n dateValue?: string;\n multiSelectOptions?: Array<{ name: string }>;\n numberValue?: number;\n singleSelectValue?: string;\n textValue?: string;\n } | null;\n}\n\ninterface GitHubProjectItemNode {\n id: string;\n content: GitHubProjectItemContent | null;\n fieldValues: {\n nodes: Array<GitHubProjectFieldValue | null>;\n };\n}\n\ninterface GitHubProjectItemsPage {\n node: {\n items: {\n nodes: Array<GitHubProjectItemNode | null>;\n pageInfo: {\n hasNextPage: boolean;\n endCursor: string | null;\n };\n };\n } | null;\n}\n\n/**\n * GitHub Projects V2 implementation\n */\nexport class GitHubProject implements IProject {\n private graphql: IGraphQLClient;\n private projectId: string;\n private owner?: string;\n private repo?: string;\n private statusFieldId?: string;\n private statusOptions?: Record<string, string>;\n\n constructor(config: ProjectConfig) {\n if (config.type !== 'github') {\n throw new Error('Invalid config type for GitHubProject');\n }\n\n this.graphql = new GraphQLClient({\n endpoint: 'https://api.github.com/graphql',\n token: config.token,\n });\n this.projectId = config.projectId;\n this.owner = config.owner;\n this.repo = config.repo;\n this.statusFieldId = config.statusFieldId;\n this.statusOptions = config.statusOptions;\n }\n\n // Project Info\n async getProject(): Promise<Project> {\n const query = `\n query($projectId: ID!) {\n node(id: $projectId) {\n ... on ProjectV2 {\n id\n title\n shortDescription\n url\n owner {\n ... on Organization {\n login\n }\n ... on User {\n login\n }\n }\n fields(first: 20) {\n nodes {\n ... on ProjectV2Field {\n id\n name\n dataType\n }\n ... on ProjectV2SingleSelectField {\n id\n name\n dataType\n options {\n id\n name\n description\n color\n }\n }\n }\n }\n }\n }\n }\n `;\n\n const data = (await this.graphql.query(query, {\n projectId: this.projectId,\n })) as {\n node: {\n id: string;\n title: string;\n shortDescription: string;\n url: string;\n owner: { login: string };\n fields: {\n nodes: Array<{\n id: string;\n name: string;\n dataType: string;\n options?: Array<{\n id: string;\n name: string;\n description?: string;\n color?: string;\n }>;\n }>;\n };\n };\n };\n\n const statusField = data.node.fields.nodes.find(\n (f) => f.name === 'Status' && f.options,\n );\n\n return {\n id: data.node.id,\n title: data.node.title,\n description: data.node.shortDescription,\n owner: data.node.owner.login,\n url: data.node.url,\n statuses: statusField?.options\n ? statusField.options.map((opt, index) => ({\n id: opt.id,\n name: opt.name,\n description: opt.description,\n color: opt.color,\n order: index,\n }))\n : [],\n fields: data.node.fields.nodes.map((f) => ({\n id: f.id,\n name: f.name,\n type: this.mapFieldType(f.dataType),\n options: f.options?.map((opt) => ({\n id: opt.id,\n name: opt.name,\n description: opt.description,\n color: opt.color,\n })),\n })),\n };\n }\n\n // Items\n /**\n * Add an issue or pull request to the configured GitHub project.\n *\n * The mutation result contains item identity and content type only. Use\n * {@link listItems} when content metadata, status, or fields are required.\n *\n * @param contentId - GitHub node ID for the issue or pull request\n * @returns The created provider-neutral project item\n */\n async addItem(contentId: string): Promise<ProjectItem> {\n const mutation = `\n mutation($projectId: ID!, $contentId: ID!) {\n addProjectV2ItemById(input: {\n projectId: $projectId\n contentId: $contentId\n }) {\n item {\n id\n content {\n ... on Issue {\n id\n typename: __typename\n }\n ... on PullRequest {\n id\n typename: __typename\n }\n }\n }\n }\n }\n `;\n\n const data = (await this.graphql.mutate(mutation, {\n projectId: this.projectId,\n contentId,\n })) as {\n addProjectV2ItemById: {\n item: {\n id: string;\n content: GitHubProjectItemReference;\n };\n };\n };\n\n return {\n id: data.addProjectV2ItemById.item.id,\n contentId: data.addProjectV2ItemById.item.content.id,\n fields: {},\n type: data.addProjectV2ItemById.item.content.typename,\n };\n }\n\n async removeItem(itemId: string): Promise<void> {\n const mutation = `\n mutation($projectId: ID!, $itemId: ID!) {\n deleteProjectV2Item(input: {\n projectId: $projectId\n itemId: $itemId\n }) {\n deletedItemId\n }\n }\n `;\n\n await this.graphql.mutate(mutation, {\n projectId: this.projectId,\n itemId,\n });\n }\n\n /**\n * Get one GitHub Projects V2 item by its project-item ID.\n *\n * This identity lookup does not hydrate content metadata, status, or fields.\n * Use {@link listItems} for the enriched board-item representation.\n *\n * @param itemId - GitHub project-item node ID\n * @returns The item, or `null` when it or its content is inaccessible\n */\n async getItem(itemId: string): Promise<ProjectItem | null> {\n const query = `\n query($itemId: ID!) {\n node(id: $itemId) {\n ... on ProjectV2Item {\n id\n content {\n ... on Issue {\n id\n typename: __typename\n }\n ... on PullRequest {\n id\n typename: __typename\n }\n ... on DraftIssue {\n id\n typename: __typename\n }\n }\n }\n }\n }\n `;\n\n const data = (await this.graphql.query(query, { itemId })) as {\n node: {\n id: string;\n content: Partial<GitHubProjectItemReference> | null;\n } | null;\n };\n\n if (!data.node?.content?.id || !data.node.content.typename) {\n return null;\n }\n\n return {\n id: data.node.id,\n contentId: data.node.content.id,\n fields: {},\n type: data.node.content.typename,\n };\n }\n\n /**\n * List GitHub Projects V2 items after an optional cursor.\n *\n * Deleted or inaccessible content is skipped, and the result defaults to at\n * most 100 accessible items to preserve the adapter's historical limit.\n *\n * @param filters - Optional cursor and maximum result count\n * @returns Provider-neutral project items with content metadata and fields\n */\n async listItems(filters?: ItemFilters): Promise<ProjectItem[]> {\n const query = `\n query($projectId: ID!, $first: Int!, $after: String) {\n node(id: $projectId) {\n ... on ProjectV2 {\n items(first: $first, after: $after) {\n nodes {\n id\n content {\n ... on Issue {\n id\n typename: __typename\n title\n url\n assignees(first: 100) {\n nodes {\n login\n }\n }\n }\n ... on PullRequest {\n id\n typename: __typename\n title\n url\n assignees(first: 100) {\n nodes {\n login\n }\n }\n }\n ... on DraftIssue {\n id\n typename: __typename\n title\n assignees(first: 100) {\n nodes {\n login\n }\n }\n }\n }\n fieldValues(first: 50) {\n nodes {\n typename: __typename\n # GitHub Projects have at most 50 fields. A 20-item page and\n # four 100-node nested connections stay below GitHub's\n # 500,000-node query limit while avoiding partial values.\n ... on ProjectV2ItemFieldDateValue {\n field {\n ...ProjectField\n }\n date\n }\n ... on ProjectV2ItemFieldIterationValue {\n field {\n ...ProjectField\n }\n title\n }\n ... on ProjectV2ItemFieldLabelValue {\n field {\n ...ProjectField\n }\n labels(first: 100) {\n nodes {\n name\n }\n }\n }\n ... on ProjectV2ItemFieldMilestoneValue {\n field {\n ...ProjectField\n }\n milestone {\n title\n }\n }\n ... on ProjectV2ItemFieldNumberValue {\n field {\n ...ProjectField\n }\n number\n }\n ... on ProjectV2ItemFieldPullRequestValue {\n field {\n ...ProjectField\n }\n pullRequests(first: 100) {\n nodes {\n url\n }\n }\n }\n ... on ProjectV2ItemFieldRepositoryValue {\n field {\n ...ProjectField\n }\n repository {\n nameWithOwner\n }\n }\n ... on ProjectV2ItemFieldReviewerValue {\n field {\n ...ProjectField\n }\n reviewers(first: 100) {\n nodes {\n ... on Bot {\n login\n }\n ... on EnterpriseTeam {\n slug\n }\n ... on Mannequin {\n login\n }\n ... on Team {\n slug\n }\n ... on User {\n login\n }\n }\n }\n }\n ... on ProjectV2ItemFieldSingleSelectValue {\n field {\n ...ProjectField\n }\n name\n }\n ... on ProjectV2ItemFieldTextValue {\n field {\n ...ProjectField\n }\n text\n }\n ... on ProjectV2ItemFieldUserValue {\n field {\n ...ProjectField\n }\n users(first: 100) {\n nodes {\n login\n }\n }\n }\n ... on ProjectV2ItemIssueFieldValue {\n field {\n ...ProjectField\n }\n issueFieldValue {\n typename: __typename\n ... on IssueFieldDateValue {\n dateValue: value\n }\n ... on IssueFieldMultiSelectValue {\n multiSelectOptions: options {\n name\n }\n }\n ... on IssueFieldNumberValue {\n numberValue: value\n }\n ... on IssueFieldSingleSelectValue {\n name\n singleSelectValue: value\n }\n ... on IssueFieldTextValue {\n textValue: value\n }\n }\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n }\n }\n\n fragment ProjectField on ProjectV2FieldCommon {\n id\n name\n }\n `;\n\n const requestedLimit = filters?.limit ?? GITHUB_DEFAULT_ITEMS_LIMIT;\n const limit = Number.isFinite(requestedLimit)\n ? Math.max(0, Math.floor(requestedLimit))\n : GITHUB_DEFAULT_ITEMS_LIMIT;\n if (limit === 0) {\n return [];\n }\n\n const items: ProjectItem[] = [];\n let after = filters?.cursor;\n let fillPage = false;\n const seenCursors = new Set<string>();\n\n while (items.length < limit) {\n const remaining = limit - items.length;\n const first = fillPage\n ? GITHUB_ITEMS_PAGE_SIZE\n : Math.min(GITHUB_ITEMS_PAGE_SIZE, remaining);\n const data = await this.graphql.query<GitHubProjectItemsPage>(query, {\n projectId: this.projectId,\n first,\n after,\n });\n if (!data.node) {\n throw new ProjectError(\n `GitHub project ${this.projectId} was not found or is inaccessible`,\n ProjectErrorCode.NOT_FOUND,\n );\n }\n const connection = data.node.items;\n let skippedContent = false;\n\n for (const node of connection.nodes) {\n if (!node) {\n skippedContent = true;\n continue;\n }\n const item = this.mapProjectItem(node);\n if (item) {\n items.push(item);\n } else {\n skippedContent = true;\n }\n if (items.length >= limit) {\n break;\n }\n }\n fillPage = skippedContent;\n\n const { endCursor, hasNextPage } = connection.pageInfo;\n if (\n !hasNextPage ||\n !endCursor ||\n endCursor === after ||\n seenCursors.has(endCursor)\n ) {\n break;\n }\n seenCursors.add(endCursor);\n after = endCursor;\n }\n\n return items;\n }\n\n /**\n * Convert one GitHub Projects V2 item into the provider-neutral item shape.\n */\n private mapProjectItem(item: GitHubProjectItemNode): ProjectItem | null {\n const { content } = item;\n if (!content) {\n return null;\n }\n\n const fieldEntries: Array<[string, unknown]> = [];\n let status: string | undefined;\n\n for (const fieldValue of item.fieldValues.nodes) {\n if (!fieldValue?.field) {\n continue;\n }\n const value = this.mapProjectFieldValue(fieldValue);\n if (value !== undefined) {\n fieldEntries.push([fieldValue.field.name, value]);\n }\n\n const isStatusField = this.statusFieldId\n ? fieldValue.field.id === this.statusFieldId\n : fieldValue.field.name.toLowerCase() === 'status';\n if (isStatusField && typeof value === 'string') {\n status = value;\n }\n }\n\n return {\n id: item.id,\n contentId: content.id,\n title: content.title,\n url: content.url ?? null,\n assignees: content.assignees.nodes.flatMap((assignee) =>\n assignee ? [assignee.login] : [],\n ),\n status,\n fields: Object.fromEntries(fieldEntries),\n type: content.typename,\n };\n }\n\n /**\n * Convert GitHub's field-value union into stable scalar or string-array values.\n */\n private mapProjectFieldValue(value: GitHubProjectFieldValue): unknown {\n switch (value.typename) {\n case 'ProjectV2ItemFieldDateValue':\n return value.date;\n case 'ProjectV2ItemFieldIterationValue':\n return value.title;\n case 'ProjectV2ItemFieldLabelValue':\n return value.labels?.nodes.flatMap((label) =>\n label ? [label.name] : [],\n );\n case 'ProjectV2ItemFieldMilestoneValue':\n return value.milestone?.title;\n case 'ProjectV2ItemFieldNumberValue':\n return value.number;\n case 'ProjectV2ItemFieldPullRequestValue':\n return value.pullRequests?.nodes.flatMap((pullRequest) =>\n pullRequest ? [pullRequest.url] : [],\n );\n case 'ProjectV2ItemFieldRepositoryValue':\n return value.repository?.nameWithOwner;\n case 'ProjectV2ItemFieldReviewerValue':\n return value.reviewers?.nodes.flatMap((reviewer) => {\n const name = reviewer?.login ?? reviewer?.slug;\n return name ? [name] : [];\n });\n case 'ProjectV2ItemFieldSingleSelectValue':\n return value.name;\n case 'ProjectV2ItemFieldTextValue':\n return value.text;\n case 'ProjectV2ItemFieldUserValue':\n return value.users?.nodes.flatMap((user) => (user ? [user.login] : []));\n case 'ProjectV2ItemIssueFieldValue': {\n const issueValue = value.issueFieldValue;\n switch (issueValue?.typename) {\n case 'IssueFieldDateValue':\n return issueValue.dateValue;\n case 'IssueFieldMultiSelectValue':\n return issueValue.multiSelectOptions?.map((option) => option.name);\n case 'IssueFieldNumberValue':\n return issueValue.numberValue;\n case 'IssueFieldSingleSelectValue':\n return issueValue.name ?? issueValue.singleSelectValue;\n case 'IssueFieldTextValue':\n return issueValue.textValue;\n default:\n return undefined;\n }\n }\n default:\n return undefined;\n }\n }\n\n // Status Management\n async updateItemStatus(itemId: string, status: string): Promise<void> {\n if (!this.statusFieldId || !this.statusOptions) {\n throw new ProjectError(\n 'Status field configuration is required. Provide statusFieldId and statusOptions in config.',\n ProjectErrorCode.INVALID_STATUS,\n );\n }\n\n const statusOptionId = this.statusOptions[status];\n if (!statusOptionId) {\n throw new ProjectError(\n `Status \"${status}\" not found in project configuration. Available: ${Object.keys(this.statusOptions).join(', ')}`,\n ProjectErrorCode.INVALID_STATUS,\n );\n }\n\n await this.updateItemField(itemId, this.statusFieldId, statusOptionId);\n }\n\n async listStatuses(): Promise<Status[]> {\n const project = await this.getProject();\n return project.statuses;\n }\n\n // Field Management\n async updateItemField(\n itemId: string,\n fieldId: string,\n value: unknown,\n ): Promise<void> {\n const mutation = `\n mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $value: ProjectV2FieldValue!) {\n updateProjectV2ItemFieldValue(input: {\n projectId: $projectId\n itemId: $itemId\n fieldId: $fieldId\n value: $value\n }) {\n projectV2Item {\n id\n }\n }\n }\n `;\n\n await this.graphql.mutate(mutation, {\n projectId: this.projectId,\n itemId,\n fieldId,\n value: { singleSelectOptionId: value },\n });\n }\n\n async listFields(): Promise<Field[]> {\n const project = await this.getProject();\n return project.fields;\n }\n\n /**\n * Map GitHub field type to our standard types\n */\n private mapFieldType(\n dataType: string,\n ): 'text' | 'number' | 'date' | 'single_select' | 'iteration' {\n switch (dataType.toLowerCase()) {\n case 'text':\n return 'text';\n case 'number':\n return 'number';\n case 'date':\n return 'date';\n case 'single_select':\n return 'single_select';\n case 'iteration':\n return 'iteration';\n default:\n return 'text';\n }\n }\n}\n","/**\n * Core types for project management operations\n */\n\nexport interface Project {\n id: string;\n title: string;\n description?: string;\n owner: string; // Organization or user\n url: string;\n statuses: Status[];\n fields: Field[];\n}\n\n/**\n * A provider-neutral item on a project board.\n */\nexport interface ProjectItem {\n /** Provider project-item ID. */\n id: string;\n /** Provider content-node ID for the issue, pull request, or draft issue. */\n contentId: string;\n /** Content title when returned by the provider. */\n title?: string;\n /** Content URL, or `null` when the content has no provider URL. */\n url?: string | null;\n /** Provider usernames assigned to the content. */\n assignees?: string[];\n /** Selected status name from the configured or canonical status field. */\n status?: string;\n /** Provider field values keyed by field name. */\n fields: Record<string, unknown>;\n /** Kind of content represented by the project item. */\n type: 'Issue' | 'PullRequest' | 'DraftIssue';\n}\n\nexport interface Status {\n id: string;\n name: string;\n description?: string;\n color?: string;\n order: number;\n}\n\nexport interface Field {\n id: string;\n name: string;\n type: 'text' | 'number' | 'date' | 'single_select' | 'iteration';\n options?: FieldOption[];\n}\n\nexport interface FieldOption {\n id: string;\n name: string;\n description?: string;\n color?: string;\n}\n\n/**\n * Options for reading project items.\n */\nexport interface ItemFilters {\n /** Return items in this status when supported by the provider. */\n status?: string;\n /** Return items assigned to these provider usernames when supported. */\n assignees?: string[];\n /** Return items with these labels when supported. */\n labels?: string[];\n /** Maximum number of items to return. Defaults to 100; zero returns none. */\n limit?: number;\n /** Provider cursor after which item pagination begins. */\n cursor?: string;\n}\n\n/**\n * Project configuration\n */\nexport interface ProjectConfig {\n type: 'github' | 'jira' | 'zenhub' | 'linear';\n projectId: string;\n token: string;\n\n // GitHub Projects V2\n owner?: string; // Organization or user\n repo?: string; // Repository name (for repo-level projects)\n statusFieldId?: string;\n statusOptions?: Record<string, string>; // Map status name to option ID\n\n // Jira\n boardId?: string;\n\n // Linear\n teamId?: string;\n}\n\n/**\n * Kanban status names for 6-lane structure\n */\nexport const KANBAN_STATUSES = [\n 'New',\n 'Backlog',\n 'Planning',\n 'Ready',\n 'In Progress',\n 'Review',\n 'Done',\n] as const;\n\nexport type KanbanStatus = (typeof KANBAN_STATUSES)[number];\n\n/**\n * Project interface - all project implementations must implement this\n */\nexport interface IProject {\n // Project Info\n getProject(): Promise<Project>;\n\n // Items (Issues/Cards)\n addItem(contentId: string): Promise<ProjectItem>;\n removeItem(itemId: string): Promise<void>;\n getItem(itemId: string): Promise<ProjectItem | null>;\n listItems(filters?: ItemFilters): Promise<ProjectItem[]>;\n\n // Status/Column Management\n updateItemStatus(itemId: string, status: string): Promise<void>;\n listStatuses(): Promise<Status[]>;\n\n // Field Management\n updateItemField(\n itemId: string,\n fieldId: string,\n value: unknown,\n ): Promise<void>;\n listFields(): Promise<Field[]>;\n}\n"],"names":["ProjectErrorCode","GitHubProject","index"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/errors.ts","../src/factory.ts","../src/github/index.ts","../src/types.ts"],"sourcesContent":["/**\n * Project error handling\n */\n\nexport enum ProjectErrorCode {\n NOT_FOUND = 'NOT_FOUND',\n UNAUTHORIZED = 'UNAUTHORIZED',\n INVALID_STATUS = 'INVALID_STATUS',\n INVALID_FIELD = 'INVALID_FIELD',\n ITEM_NOT_IN_PROJECT = 'ITEM_NOT_IN_PROJECT',\n RATE_LIMITED = 'RATE_LIMITED',\n NETWORK_ERROR = 'NETWORK_ERROR',\n VALIDATION_ERROR = 'VALIDATION_ERROR',\n UNKNOWN = 'UNKNOWN',\n}\n\nexport class ProjectError extends Error {\n constructor(\n message: string,\n public code: ProjectErrorCode,\n public statusCode?: number,\n public response?: unknown,\n ) {\n super(message);\n this.name = 'ProjectError';\n Error.captureStackTrace(this, this.constructor);\n }\n\n static fromHTTPStatus(\n statusCode: number,\n message: string,\n response?: unknown,\n ): ProjectError {\n let code: ProjectErrorCode;\n\n switch (statusCode) {\n case 404:\n code = ProjectErrorCode.NOT_FOUND;\n break;\n case 401:\n code = ProjectErrorCode.UNAUTHORIZED;\n break;\n case 403:\n code = ProjectErrorCode.UNAUTHORIZED;\n break;\n case 429:\n code = ProjectErrorCode.RATE_LIMITED;\n break;\n case 422:\n code = ProjectErrorCode.VALIDATION_ERROR;\n break;\n default:\n code = ProjectErrorCode.UNKNOWN;\n }\n\n return new ProjectError(message, code, statusCode, response);\n }\n\n static networkError(message: string, cause?: Error): ProjectError {\n return new ProjectError(\n message,\n ProjectErrorCode.NETWORK_ERROR,\n undefined,\n cause,\n );\n }\n\n isRetryable(): boolean {\n return (\n this.code === ProjectErrorCode.RATE_LIMITED ||\n this.code === ProjectErrorCode.NETWORK_ERROR\n );\n }\n}\n","/**\n * Project factory function\n *\n * Creates project instances following the pattern from @happyvertical/files and @happyvertical/sql\n */\n\nimport { ProjectError, ProjectErrorCode } from './errors.js';\nimport type { IProject, ProjectConfig } from './types.js';\n\n/**\n * Check if value is already a project instance\n */\nfunction isProjectInstance(value: unknown): value is IProject {\n return (\n value !== null &&\n typeof value === 'object' &&\n 'getProject' in value &&\n 'addItem' in value &&\n 'updateItemStatus' in value &&\n typeof (value as IProject).getProject === 'function'\n );\n}\n\n/**\n * Get a project management client instance\n *\n * This is the main entry point following the pattern from @happyvertical/files and @happyvertical/sql.\n * It can accept either a configuration object or an existing project instance.\n *\n * @param options - Project configuration or existing project instance\n * @returns Promise resolving to project interface\n *\n * @example\n * ```typescript\n * import { getProject } from '@happyvertical/projects';\n *\n * // Create a GitHub Projects client\n * const project = await getProject({\n * type: 'github',\n * owner: 'happyvertical',\n * projectId: 'PVT_kwDOB9Y8ns4A8-TY',\n * token: process.env.GITHUB_TOKEN,\n * statusFieldId: 'PVTSSF_lADOB9Y8ns4A8-TYzgw0GaY',\n * statusOptions: {\n * 'New': 'option-id-1',\n * 'Backlog': 'option-id-2',\n * 'In Progress': 'option-id-3',\n * 'Done': 'option-id-4'\n * }\n * });\n *\n * // Use the client\n * await project.addItem(issueNodeId);\n * await project.updateItemStatus(itemId, 'In Progress');\n *\n * // Pass existing instance (returns it unchanged)\n * const sameProject = await getProject(project);\n * ```\n */\nexport async function getProject(\n options: ProjectConfig | IProject,\n): Promise<IProject> {\n // If already a project instance, return it\n if (isProjectInstance(options)) {\n return options;\n }\n\n // Validate configuration\n if (!options.type) {\n throw new ProjectError(\n 'Project type is required',\n ProjectErrorCode.VALIDATION_ERROR,\n );\n }\n\n if (!options.projectId) {\n throw new ProjectError(\n 'Project ID is required',\n ProjectErrorCode.VALIDATION_ERROR,\n );\n }\n\n if (!options.token) {\n throw new ProjectError(\n 'Authentication token is required',\n ProjectErrorCode.UNAUTHORIZED,\n );\n }\n\n // Create project instance based on type\n switch (options.type) {\n case 'github': {\n const { GitHubProject } = await import('./github/index.js');\n return new GitHubProject(options);\n }\n\n case 'jira':\n throw new ProjectError(\n 'Jira support not yet implemented',\n ProjectErrorCode.UNKNOWN,\n );\n\n case 'zenhub':\n throw new ProjectError(\n 'ZenHub support not yet implemented',\n ProjectErrorCode.UNKNOWN,\n );\n\n case 'linear':\n throw new ProjectError(\n 'Linear support not yet implemented',\n ProjectErrorCode.UNKNOWN,\n );\n\n default:\n throw new ProjectError(\n `Unsupported project type: ${options.type}`,\n ProjectErrorCode.VALIDATION_ERROR,\n );\n }\n}\n","/**\n * GitHub Projects V2 implementation\n */\n\nimport { GraphQLClient, type IGraphQLClient } from '@happyvertical/graphql';\nimport { ProjectError, ProjectErrorCode } from '../errors.js';\nimport type {\n Field,\n IProject,\n ItemFilters,\n Project,\n ProjectConfig,\n ProjectItem,\n Status,\n} from '../types.js';\n\nconst GITHUB_DEFAULT_ITEMS_LIMIT = 100;\nconst GITHUB_ITEMS_PAGE_SIZE = 20;\n\ninterface GitHubProjectItemReference {\n typename: ProjectItem['type'];\n id: string;\n}\n\ninterface GitHubProjectItemContent extends GitHubProjectItemReference {\n title: string;\n url?: string;\n assignees: {\n nodes: Array<{ login: string } | null>;\n };\n}\n\ninterface GitHubProjectFieldValue {\n typename: string;\n field?: {\n id: string;\n name: string;\n };\n date?: string | null;\n title?: string;\n labels?: {\n nodes: Array<{ name: string } | null>;\n } | null;\n milestone?: {\n title: string;\n } | null;\n number?: number | null;\n pullRequests?: {\n nodes: Array<{ url: string } | null>;\n } | null;\n repository?: {\n nameWithOwner: string;\n } | null;\n reviewers?: {\n nodes: Array<{\n login?: string;\n slug?: string;\n } | null>;\n } | null;\n name?: string | null;\n text?: string | null;\n users?: {\n nodes: Array<{ login: string } | null>;\n } | null;\n issueFieldValue?: {\n typename: string;\n name?: string;\n dateValue?: string;\n multiSelectOptions?: Array<{ name: string }>;\n numberValue?: number;\n singleSelectValue?: string;\n textValue?: string;\n } | null;\n}\n\ninterface GitHubProjectItemNode {\n id: string;\n content: GitHubProjectItemContent | null;\n fieldValues: {\n nodes: Array<GitHubProjectFieldValue | null>;\n };\n}\n\ninterface GitHubProjectItemsPage {\n node: {\n items: {\n nodes: Array<GitHubProjectItemNode | null>;\n pageInfo: {\n hasNextPage: boolean;\n endCursor: string | null;\n };\n };\n } | null;\n}\n\n/**\n * GitHub Projects V2 implementation\n */\nexport class GitHubProject implements IProject {\n private graphql: IGraphQLClient;\n private projectId: string;\n private owner?: string;\n private repo?: string;\n private statusFieldId?: string;\n private statusOptions?: Record<string, string>;\n\n constructor(config: ProjectConfig) {\n if (config.type !== 'github') {\n throw new Error('Invalid config type for GitHubProject');\n }\n\n this.graphql = new GraphQLClient({\n endpoint: 'https://api.github.com/graphql',\n token: config.token,\n });\n this.projectId = config.projectId;\n this.owner = config.owner;\n this.repo = config.repo;\n this.statusFieldId = config.statusFieldId;\n this.statusOptions = config.statusOptions;\n }\n\n // Project Info\n async getProject(): Promise<Project> {\n const query = `\n query($projectId: ID!) {\n node(id: $projectId) {\n ... on ProjectV2 {\n id\n title\n shortDescription\n url\n owner {\n ... on Organization {\n login\n }\n ... on User {\n login\n }\n }\n fields(first: 20) {\n nodes {\n ... on ProjectV2Field {\n id\n name\n dataType\n }\n ... on ProjectV2SingleSelectField {\n id\n name\n dataType\n options {\n id\n name\n description\n color\n }\n }\n }\n }\n }\n }\n }\n `;\n\n const data = (await this.graphql.query(query, {\n projectId: this.projectId,\n })) as {\n node: {\n id: string;\n title: string;\n shortDescription: string;\n url: string;\n owner: { login: string };\n fields: {\n nodes: Array<{\n id: string;\n name: string;\n dataType: string;\n options?: Array<{\n id: string;\n name: string;\n description?: string;\n color?: string;\n }>;\n }>;\n };\n };\n };\n\n const statusField = data.node.fields.nodes.find(\n (f) => f.name === 'Status' && f.options,\n );\n\n return {\n id: data.node.id,\n title: data.node.title,\n description: data.node.shortDescription,\n owner: data.node.owner.login,\n url: data.node.url,\n statuses: statusField?.options\n ? statusField.options.map((opt, index) => ({\n id: opt.id,\n name: opt.name,\n description: opt.description,\n color: opt.color,\n order: index,\n }))\n : [],\n fields: data.node.fields.nodes.map((f) => ({\n id: f.id,\n name: f.name,\n type: this.mapFieldType(f.dataType),\n options: f.options?.map((opt) => ({\n id: opt.id,\n name: opt.name,\n description: opt.description,\n color: opt.color,\n })),\n })),\n };\n }\n\n // Items\n /**\n * Add an issue or pull request to the configured GitHub project.\n *\n * The mutation result contains item identity and content type only. Use\n * {@link listItems} when content metadata, status, or fields are required.\n *\n * @param contentId - GitHub node ID for the issue or pull request\n * @returns The created provider-neutral project item\n */\n async addItem(contentId: string): Promise<ProjectItem> {\n const mutation = `\n mutation($projectId: ID!, $contentId: ID!) {\n addProjectV2ItemById(input: {\n projectId: $projectId\n contentId: $contentId\n }) {\n item {\n id\n content {\n ... on Issue {\n id\n typename: __typename\n }\n ... on PullRequest {\n id\n typename: __typename\n }\n }\n }\n }\n }\n `;\n\n const data = (await this.graphql.mutate(mutation, {\n projectId: this.projectId,\n contentId,\n })) as {\n addProjectV2ItemById: {\n item: {\n id: string;\n content: GitHubProjectItemReference;\n };\n };\n };\n\n return {\n id: data.addProjectV2ItemById.item.id,\n contentId: data.addProjectV2ItemById.item.content.id,\n fields: {},\n type: data.addProjectV2ItemById.item.content.typename,\n };\n }\n\n async removeItem(itemId: string): Promise<void> {\n const mutation = `\n mutation($projectId: ID!, $itemId: ID!) {\n deleteProjectV2Item(input: {\n projectId: $projectId\n itemId: $itemId\n }) {\n deletedItemId\n }\n }\n `;\n\n await this.graphql.mutate(mutation, {\n projectId: this.projectId,\n itemId,\n });\n }\n\n /**\n * Get one GitHub Projects V2 item by its project-item ID.\n *\n * This identity lookup does not hydrate content metadata, status, or fields.\n * Use {@link listItems} for the enriched board-item representation.\n *\n * @param itemId - GitHub project-item node ID\n * @returns The item, or `null` when it or its content is inaccessible\n */\n async getItem(itemId: string): Promise<ProjectItem | null> {\n const query = `\n query($itemId: ID!) {\n node(id: $itemId) {\n ... on ProjectV2Item {\n id\n content {\n ... on Issue {\n id\n typename: __typename\n }\n ... on PullRequest {\n id\n typename: __typename\n }\n ... on DraftIssue {\n id\n typename: __typename\n }\n }\n }\n }\n }\n `;\n\n const data = (await this.graphql.query(query, { itemId })) as {\n node: {\n id: string;\n content: Partial<GitHubProjectItemReference> | null;\n } | null;\n };\n\n if (!data.node?.content?.id || !data.node.content.typename) {\n return null;\n }\n\n return {\n id: data.node.id,\n contentId: data.node.content.id,\n fields: {},\n type: data.node.content.typename,\n };\n }\n\n /**\n * List GitHub Projects V2 items after an optional cursor.\n *\n * Deleted or inaccessible content is skipped, and the result defaults to at\n * most 100 accessible items to preserve the adapter's historical limit.\n *\n * @param filters - Optional cursor and maximum result count\n * @returns Provider-neutral project items with content metadata and fields\n */\n async listItems(filters?: ItemFilters): Promise<ProjectItem[]> {\n const query = `\n query($projectId: ID!, $first: Int!, $after: String) {\n node(id: $projectId) {\n ... on ProjectV2 {\n items(first: $first, after: $after) {\n nodes {\n id\n content {\n ... on Issue {\n id\n typename: __typename\n title\n url\n assignees(first: 100) {\n nodes {\n login\n }\n }\n }\n ... on PullRequest {\n id\n typename: __typename\n title\n url\n assignees(first: 100) {\n nodes {\n login\n }\n }\n }\n ... on DraftIssue {\n id\n typename: __typename\n title\n assignees(first: 100) {\n nodes {\n login\n }\n }\n }\n }\n fieldValues(first: 50) {\n nodes {\n typename: __typename\n # GitHub Projects have at most 50 fields. A 20-item page and\n # four 100-node nested connections stay below GitHub's\n # 500,000-node query limit while avoiding partial values.\n ... on ProjectV2ItemFieldDateValue {\n field {\n ...ProjectField\n }\n date\n }\n ... on ProjectV2ItemFieldIterationValue {\n field {\n ...ProjectField\n }\n title\n }\n ... on ProjectV2ItemFieldLabelValue {\n field {\n ...ProjectField\n }\n labels(first: 100) {\n nodes {\n name\n }\n }\n }\n ... on ProjectV2ItemFieldMilestoneValue {\n field {\n ...ProjectField\n }\n milestone {\n title\n }\n }\n ... on ProjectV2ItemFieldNumberValue {\n field {\n ...ProjectField\n }\n number\n }\n ... on ProjectV2ItemFieldPullRequestValue {\n field {\n ...ProjectField\n }\n pullRequests(first: 100) {\n nodes {\n url\n }\n }\n }\n ... on ProjectV2ItemFieldRepositoryValue {\n field {\n ...ProjectField\n }\n repository {\n nameWithOwner\n }\n }\n ... on ProjectV2ItemFieldReviewerValue {\n field {\n ...ProjectField\n }\n reviewers(first: 100) {\n nodes {\n ... on Bot {\n login\n }\n ... on EnterpriseTeam {\n slug\n }\n ... on Mannequin {\n login\n }\n ... on Team {\n slug\n }\n ... on User {\n login\n }\n }\n }\n }\n ... on ProjectV2ItemFieldSingleSelectValue {\n field {\n ...ProjectField\n }\n name\n }\n ... on ProjectV2ItemFieldTextValue {\n field {\n ...ProjectField\n }\n text\n }\n ... on ProjectV2ItemFieldUserValue {\n field {\n ...ProjectField\n }\n users(first: 100) {\n nodes {\n login\n }\n }\n }\n ... on ProjectV2ItemIssueFieldValue {\n field {\n ...ProjectField\n }\n issueFieldValue {\n typename: __typename\n ... on IssueFieldDateValue {\n dateValue: value\n }\n ... on IssueFieldMultiSelectValue {\n multiSelectOptions: options {\n name\n }\n }\n ... on IssueFieldNumberValue {\n numberValue: value\n }\n ... on IssueFieldSingleSelectValue {\n name\n singleSelectValue: value\n }\n ... on IssueFieldTextValue {\n textValue: value\n }\n }\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n }\n }\n\n fragment ProjectField on ProjectV2FieldCommon {\n id\n name\n }\n `;\n\n const requestedLimit = filters?.limit ?? GITHUB_DEFAULT_ITEMS_LIMIT;\n const limit = Number.isFinite(requestedLimit)\n ? Math.max(0, Math.floor(requestedLimit))\n : GITHUB_DEFAULT_ITEMS_LIMIT;\n if (limit === 0) {\n return [];\n }\n\n const items: ProjectItem[] = [];\n let after = filters?.cursor;\n let fillPage = false;\n const seenCursors = new Set<string>();\n\n while (items.length < limit) {\n const remaining = limit - items.length;\n const first = fillPage\n ? GITHUB_ITEMS_PAGE_SIZE\n : Math.min(GITHUB_ITEMS_PAGE_SIZE, remaining);\n const data = await this.graphql.query<GitHubProjectItemsPage>(query, {\n projectId: this.projectId,\n first,\n after,\n });\n if (!data.node) {\n throw new ProjectError(\n `GitHub project ${this.projectId} was not found or is inaccessible`,\n ProjectErrorCode.NOT_FOUND,\n );\n }\n const connection = data.node.items;\n let skippedContent = false;\n\n for (const node of connection.nodes) {\n if (!node) {\n skippedContent = true;\n continue;\n }\n const item = this.mapProjectItem(node);\n if (item) {\n items.push(item);\n } else {\n skippedContent = true;\n }\n if (items.length >= limit) {\n break;\n }\n }\n fillPage = skippedContent;\n\n const { endCursor, hasNextPage } = connection.pageInfo;\n if (\n !hasNextPage ||\n !endCursor ||\n endCursor === after ||\n seenCursors.has(endCursor)\n ) {\n break;\n }\n seenCursors.add(endCursor);\n after = endCursor;\n }\n\n return items;\n }\n\n /**\n * Convert one GitHub Projects V2 item into the provider-neutral item shape.\n */\n private mapProjectItem(item: GitHubProjectItemNode): ProjectItem | null {\n const { content } = item;\n if (!content) {\n return null;\n }\n\n const fieldEntries: Array<[string, unknown]> = [];\n let status: string | undefined;\n\n for (const fieldValue of item.fieldValues.nodes) {\n if (!fieldValue?.field) {\n continue;\n }\n const value = this.mapProjectFieldValue(fieldValue);\n if (value !== undefined) {\n fieldEntries.push([fieldValue.field.name, value]);\n }\n\n const isStatusField = this.statusFieldId\n ? fieldValue.field.id === this.statusFieldId\n : fieldValue.field.name.toLowerCase() === 'status';\n if (isStatusField && typeof value === 'string') {\n status = value;\n }\n }\n\n return {\n id: item.id,\n contentId: content.id,\n title: content.title,\n url: content.url ?? null,\n assignees: content.assignees.nodes.flatMap((assignee) =>\n assignee ? [assignee.login] : [],\n ),\n status,\n fields: Object.fromEntries(fieldEntries),\n type: content.typename,\n };\n }\n\n /**\n * Convert GitHub's field-value union into stable scalar or string-array values.\n */\n private mapProjectFieldValue(value: GitHubProjectFieldValue): unknown {\n switch (value.typename) {\n case 'ProjectV2ItemFieldDateValue':\n return value.date;\n case 'ProjectV2ItemFieldIterationValue':\n return value.title;\n case 'ProjectV2ItemFieldLabelValue':\n return value.labels?.nodes.flatMap((label) =>\n label ? [label.name] : [],\n );\n case 'ProjectV2ItemFieldMilestoneValue':\n return value.milestone?.title;\n case 'ProjectV2ItemFieldNumberValue':\n return value.number;\n case 'ProjectV2ItemFieldPullRequestValue':\n return value.pullRequests?.nodes.flatMap((pullRequest) =>\n pullRequest ? [pullRequest.url] : [],\n );\n case 'ProjectV2ItemFieldRepositoryValue':\n return value.repository?.nameWithOwner;\n case 'ProjectV2ItemFieldReviewerValue':\n return value.reviewers?.nodes.flatMap((reviewer) => {\n const name = reviewer?.login ?? reviewer?.slug;\n return name ? [name] : [];\n });\n case 'ProjectV2ItemFieldSingleSelectValue':\n return value.name;\n case 'ProjectV2ItemFieldTextValue':\n return value.text;\n case 'ProjectV2ItemFieldUserValue':\n return value.users?.nodes.flatMap((user) => (user ? [user.login] : []));\n case 'ProjectV2ItemIssueFieldValue': {\n const issueValue = value.issueFieldValue;\n switch (issueValue?.typename) {\n case 'IssueFieldDateValue':\n return issueValue.dateValue;\n case 'IssueFieldMultiSelectValue':\n return issueValue.multiSelectOptions?.map((option) => option.name);\n case 'IssueFieldNumberValue':\n return issueValue.numberValue;\n case 'IssueFieldSingleSelectValue':\n return issueValue.name ?? issueValue.singleSelectValue;\n case 'IssueFieldTextValue':\n return issueValue.textValue;\n default:\n return undefined;\n }\n }\n default:\n return undefined;\n }\n }\n\n // Status Management\n async updateItemStatus(itemId: string, status: string): Promise<void> {\n if (!this.statusFieldId || !this.statusOptions) {\n throw new ProjectError(\n 'Status field configuration is required. Provide statusFieldId and statusOptions in config.',\n ProjectErrorCode.INVALID_STATUS,\n );\n }\n\n const statusOptionId = this.statusOptions[status];\n if (!statusOptionId) {\n throw new ProjectError(\n `Status \"${status}\" not found in project configuration. Available: ${Object.keys(this.statusOptions).join(', ')}`,\n ProjectErrorCode.INVALID_STATUS,\n );\n }\n\n await this.updateItemField(itemId, this.statusFieldId, statusOptionId);\n }\n\n async listStatuses(): Promise<Status[]> {\n const project = await this.getProject();\n return project.statuses;\n }\n\n // Field Management\n async updateItemField(\n itemId: string,\n fieldId: string,\n value: unknown,\n ): Promise<void> {\n const mutation = `\n mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $value: ProjectV2FieldValue!) {\n updateProjectV2ItemFieldValue(input: {\n projectId: $projectId\n itemId: $itemId\n fieldId: $fieldId\n value: $value\n }) {\n projectV2Item {\n id\n }\n }\n }\n `;\n\n await this.graphql.mutate(mutation, {\n projectId: this.projectId,\n itemId,\n fieldId,\n value: { singleSelectOptionId: value },\n });\n }\n\n async listFields(): Promise<Field[]> {\n const project = await this.getProject();\n return project.fields;\n }\n\n /**\n * Map GitHub field type to our standard types\n */\n private mapFieldType(\n dataType: string,\n ): 'text' | 'number' | 'date' | 'single_select' | 'iteration' {\n switch (dataType.toLowerCase()) {\n case 'text':\n return 'text';\n case 'number':\n return 'number';\n case 'date':\n return 'date';\n case 'single_select':\n return 'single_select';\n case 'iteration':\n return 'iteration';\n default:\n return 'text';\n }\n }\n}\n","/**\n * Core types for project management operations\n */\n\nexport interface Project {\n id: string;\n title: string;\n description?: string;\n owner: string; // Organization or user\n url: string;\n statuses: Status[];\n fields: Field[];\n}\n\n/**\n * A provider-neutral item on a project board.\n */\nexport interface ProjectItem {\n /** Provider project-item ID. */\n id: string;\n /** Provider content-node ID for the issue, pull request, or draft issue. */\n contentId: string;\n /** Content title when returned by the provider. */\n title?: string;\n /** Content URL, or `null` when the content has no provider URL. */\n url?: string | null;\n /** Provider usernames assigned to the content. */\n assignees?: string[];\n /** Selected status name from the configured or canonical status field. */\n status?: string;\n /** Provider field values keyed by field name. */\n fields: Record<string, unknown>;\n /** Kind of content represented by the project item. */\n type: 'Issue' | 'PullRequest' | 'DraftIssue';\n}\n\nexport interface Status {\n id: string;\n name: string;\n description?: string;\n color?: string;\n order: number;\n}\n\nexport interface Field {\n id: string;\n name: string;\n type: 'text' | 'number' | 'date' | 'single_select' | 'iteration';\n options?: FieldOption[];\n}\n\nexport interface FieldOption {\n id: string;\n name: string;\n description?: string;\n color?: string;\n}\n\n/**\n * Options for reading project items.\n */\nexport interface ItemFilters {\n /** Return items in this status when supported by the provider. */\n status?: string;\n /** Return items assigned to these provider usernames when supported. */\n assignees?: string[];\n /** Return items with these labels when supported. */\n labels?: string[];\n /** Maximum number of items to return. Defaults to 100; zero returns none. */\n limit?: number;\n /** Provider cursor after which item pagination begins. */\n cursor?: string;\n}\n\n/**\n * Project configuration\n */\nexport interface ProjectConfig {\n type: 'github' | 'jira' | 'zenhub' | 'linear';\n projectId: string;\n token: string;\n\n // GitHub Projects V2\n owner?: string; // Organization or user\n repo?: string; // Repository name (for repo-level projects)\n statusFieldId?: string;\n statusOptions?: Record<string, string>; // Map status name to option ID\n\n // Jira\n boardId?: string;\n\n // Linear\n teamId?: string;\n}\n\n/**\n * Kanban status names for 6-lane structure\n */\nexport const KANBAN_STATUSES = [\n 'New',\n 'Backlog',\n 'Planning',\n 'Ready',\n 'In Progress',\n 'Review',\n 'Done',\n] as const;\n\nexport type KanbanStatus = (typeof KANBAN_STATUSES)[number];\n\n/**\n * Project interface - all project implementations must implement this\n */\nexport interface IProject {\n // Project Info\n getProject(): Promise<Project>;\n\n // Items (Issues/Cards)\n addItem(contentId: string): Promise<ProjectItem>;\n removeItem(itemId: string): Promise<void>;\n getItem(itemId: string): Promise<ProjectItem | null>;\n listItems(filters?: ItemFilters): Promise<ProjectItem[]>;\n\n // Status/Column Management\n updateItemStatus(itemId: string, status: string): Promise<void>;\n listStatuses(): Promise<Status[]>;\n\n // Field Management\n updateItemField(\n itemId: string,\n fieldId: string,\n value: unknown,\n ): Promise<void>;\n listFields(): Promise<Field[]>;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAIA,IAAY,mBAAL,yBAAA,kBAAA;CACL,iBAAA,eAAA;CACA,iBAAA,kBAAA;CACA,iBAAA,oBAAA;CACA,iBAAA,mBAAA;CACA,iBAAA,yBAAA;CACA,iBAAA,kBAAA;CACA,iBAAA,mBAAA;CACA,iBAAA,sBAAA;CACA,iBAAA,aAAA;;AACF,EAAA,CAAA,CAAA;AAEA,IAAa,eAAb,MAAa,qBAAqB,MAAM;CAG7B;CACA;CACA;CAJT,YACE,SACA,MACA,YACA,UACA;EACA,MAAM,OAAO;EAJN,KAAA,OAAA;EACA,KAAA,aAAA;EACA,KAAA,WAAA;EAGP,KAAK,OAAO;EACZ,MAAM,kBAAkB,MAAM,KAAK,WAAW;CAChD;CAEA,OAAO,eACL,YACA,SACA,UACc;EACd,IAAI;EAEJ,QAAQ,YAAR;GACE,KAAK;IACH,OAAA;IACA;GACF,KAAK;IACH,OAAA;IACA;GACF,KAAK;IACH,OAAA;IACA;GACF,KAAK;IACH,OAAA;IACA;GACF,KAAK;IACH,OAAA;IACA;GACF,SACE,OAAA;EACJ;EAEA,OAAO,IAAI,aAAa,SAAS,MAAM,YAAY,QAAQ;CAC7D;CAEA,OAAO,aAAa,SAAiB,OAA6B;EAChE,OAAO,IAAI,aACT,SAAA,iBAEA,KAAA,GACA,KACF;CACF;CAEA,cAAuB;EACrB,OACE,KAAK,SAAA,kBACL,KAAK,SAAA;CAET;AACF;;;;;;;;;;;AC7DA,SAAS,kBAAkB,OAAmC;CAC5D,OACE,UAAU,QACV,OAAO,UAAU,YACjB,gBAAgB,SAChB,aAAa,SACb,sBAAsB,SACtB,OAAQ,MAAmB,eAAe;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,eAAsB,WACpB,SACmB;CAEnB,IAAI,kBAAkB,OAAO,GAC3B,OAAO;CAIT,IAAI,CAAC,QAAQ,MACX,MAAM,IAAI,aACR,4BACA,iBAAiB,gBACnB;CAGF,IAAI,CAAC,QAAQ,WACX,MAAM,IAAI,aACR,0BACA,iBAAiB,gBACnB;CAGF,IAAI,CAAC,QAAQ,OACX,MAAM,IAAI,aACR,oCACA,iBAAiB,YACnB;CAIF,QAAQ,QAAQ,MAAhB;EACE,KAAK,UAAU;GACb,MAAM,EAAE,kBAAkB,MAAA,QAAA,QAAA,CAAA,CAAA,WAAA,cAAA;GAC1B,OAAO,IAAI,cAAc,OAAO;EAClC;EAEA,KAAK,QACH,MAAM,IAAI,aACR,oCACA,iBAAiB,OACnB;EAEF,KAAK,UACH,MAAM,IAAI,aACR,sCACA,iBAAiB,OACnB;EAEF,KAAK,UACH,MAAM,IAAI,aACR,sCACA,iBAAiB,OACnB;EAEF,SACE,MAAM,IAAI,aACR,6BAA6B,QAAQ,QACrC,iBAAiB,gBACnB;CACJ;AACF;;;;;;;ACxGA,IAAM,6BAA6B;AACnC,IAAM,yBAAyB;;;;AAiF/B,IAAa,gBAAb,MAA+C;CAC7C;CACA;CACA;CACA;CACA;CACA;CAEA,YAAY,QAAuB;EACjC,IAAI,OAAO,SAAS,UAClB,MAAM,IAAI,MAAM,uCAAuC;EAGzD,KAAK,UAAU,IAAI,cAAc;GAC/B,UAAU;GACV,OAAO,OAAO;EAChB,CAAC;EACD,KAAK,YAAY,OAAO;EACxB,KAAK,QAAQ,OAAO;EACpB,KAAK,OAAO,OAAO;EACnB,KAAK,gBAAgB,OAAO;EAC5B,KAAK,gBAAgB,OAAO;CAC9B;CAGA,MAAM,aAA+B;EA0CnC,MAAM,OAAQ,MAAM,KAAK,QAAQ,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAAO,EAC5C,WAAW,KAAK,UAClB,CAAC;EAuBD,MAAM,cAAc,KAAK,KAAK,OAAO,MAAM,MACxC,MAAM,EAAE,SAAS,YAAY,EAAE,OAClC;EAEA,OAAO;GACL,IAAI,KAAK,KAAK;GACd,OAAO,KAAK,KAAK;GACjB,aAAa,KAAK,KAAK;GACvB,OAAO,KAAK,KAAK,MAAM;GACvB,KAAK,KAAK,KAAK;GACf,UAAU,aAAa,UACnB,YAAY,QAAQ,KAAK,KAAK,WAAW;IACvC,IAAI,IAAI;IACR,MAAM,IAAI;IACV,aAAa,IAAI;IACjB,OAAO,IAAI;IACX,OAAO;GACT,EAAE,IACF,CAAC;GACL,QAAQ,KAAK,KAAK,OAAO,MAAM,KAAK,OAAO;IACzC,IAAI,EAAE;IACN,MAAM,EAAE;IACR,MAAM,KAAK,aAAa,EAAE,QAAQ;IAClC,SAAS,EAAE,SAAS,KAAK,SAAS;KAChC,IAAI,IAAI;KACR,MAAM,IAAI;KACV,aAAa,IAAI;KACjB,OAAO,IAAI;IACb,EAAE;GACJ,EAAE;EACJ;CACF;;;;;;;;;;CAYA,MAAM,QAAQ,WAAyC;EAwBrD,MAAM,OAAQ,MAAM,KAAK,QAAQ,OAAO;;;;;;;;;;;;;;;;;;;;;OAAU;GAChD,WAAW,KAAK;GAChB;EACF,CAAC;EASD,OAAO;GACL,IAAI,KAAK,qBAAqB,KAAK;GACnC,WAAW,KAAK,qBAAqB,KAAK,QAAQ;GAClD,QAAQ,CAAC;GACT,MAAM,KAAK,qBAAqB,KAAK,QAAQ;EAC/C;CACF;CAEA,MAAM,WAAW,QAA+B;EAY9C,MAAM,KAAK,QAAQ,OAAO;;;;;;;;;OAAU;GAClC,WAAW,KAAK;GAChB;EACF,CAAC;CACH;;;;;;;;;;CAWA,MAAM,QAAQ,QAA6C;EAyBzD,MAAM,OAAQ,MAAM,KAAK,QAAQ,MAAM;;;;;;;;;;;;;;;;;;;;;;OAAO,EAAE,OAAO,CAAC;EAOxD,IAAI,CAAC,KAAK,MAAM,SAAS,MAAM,CAAC,KAAK,KAAK,QAAQ,UAChD,OAAO;EAGT,OAAO;GACL,IAAI,KAAK,KAAK;GACd,WAAW,KAAK,KAAK,QAAQ;GAC7B,QAAQ,CAAC;GACT,MAAM,KAAK,KAAK,QAAQ;EAC1B;CACF;;;;;;;;;;CAWA,MAAM,UAAU,SAA+C;EAC7D,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+Ld,MAAM,iBAAiB,SAAS,SAAS;EACzC,MAAM,QAAQ,OAAO,SAAS,cAAc,IACxC,KAAK,IAAI,GAAG,KAAK,MAAM,cAAc,CAAC,IACtC;EACJ,IAAI,UAAU,GACZ,OAAO,CAAC;EAGV,MAAM,QAAuB,CAAC;EAC9B,IAAI,QAAQ,SAAS;EACrB,IAAI,WAAW;EACf,MAAM,8BAAc,IAAI,IAAY;EAEpC,OAAO,MAAM,SAAS,OAAO;GAC3B,MAAM,YAAY,QAAQ,MAAM;GAChC,MAAM,QAAQ,WACV,yBACA,KAAK,IAAI,wBAAwB,SAAS;GAC9C,MAAM,OAAO,MAAM,KAAK,QAAQ,MAA8B,OAAO;IACnE,WAAW,KAAK;IAChB;IACA;GACF,CAAC;GACD,IAAI,CAAC,KAAK,MACR,MAAM,IAAI,aACR,kBAAkB,KAAK,UAAU,oCACjC,iBAAiB,SACnB;GAEF,MAAM,aAAa,KAAK,KAAK;GAC7B,IAAI,iBAAiB;GAErB,KAAK,MAAM,QAAQ,WAAW,OAAO;IACnC,IAAI,CAAC,MAAM;KACT,iBAAiB;KACjB;IACF;IACA,MAAM,OAAO,KAAK,eAAe,IAAI;IACrC,IAAI,MACF,MAAM,KAAK,IAAI;SAEf,iBAAiB;IAEnB,IAAI,MAAM,UAAU,OAClB;GAEJ;GACA,WAAW;GAEX,MAAM,EAAE,WAAW,gBAAgB,WAAW;GAC9C,IACE,CAAC,eACD,CAAC,aACD,cAAc,SACd,YAAY,IAAI,SAAS,GAEzB;GAEF,YAAY,IAAI,SAAS;GACzB,QAAQ;EACV;EAEA,OAAO;CACT;;;;CAKA,eAAuB,MAAiD;EACtE,MAAM,EAAE,YAAY;EACpB,IAAI,CAAC,SACH,OAAO;EAGT,MAAM,eAAyC,CAAC;EAChD,IAAI;EAEJ,KAAK,MAAM,cAAc,KAAK,YAAY,OAAO;GAC/C,IAAI,CAAC,YAAY,OACf;GAEF,MAAM,QAAQ,KAAK,qBAAqB,UAAU;GAClD,IAAI,UAAU,KAAA,GACZ,aAAa,KAAK,CAAC,WAAW,MAAM,MAAM,KAAK,CAAC;GAMlD,KAHsB,KAAK,gBACvB,WAAW,MAAM,OAAO,KAAK,gBAC7B,WAAW,MAAM,KAAK,YAAY,MAAM,aACvB,OAAO,UAAU,UACpC,SAAS;EAEb;EAEA,OAAO;GACL,IAAI,KAAK;GACT,WAAW,QAAQ;GACnB,OAAO,QAAQ;GACf,KAAK,QAAQ,OAAO;GACpB,WAAW,QAAQ,UAAU,MAAM,SAAS,aAC1C,WAAW,CAAC,SAAS,KAAK,IAAI,CAAC,CACjC;GACA;GACA,QAAQ,OAAO,YAAY,YAAY;GACvC,MAAM,QAAQ;EAChB;CACF;;;;CAKA,qBAA6B,OAAyC;EACpE,QAAQ,MAAM,UAAd;GACE,KAAK,+BACH,OAAO,MAAM;GACf,KAAK,oCACH,OAAO,MAAM;GACf,KAAK,gCACH,OAAO,MAAM,QAAQ,MAAM,SAAS,UAClC,QAAQ,CAAC,MAAM,IAAI,IAAI,CAAC,CAC1B;GACF,KAAK,oCACH,OAAO,MAAM,WAAW;GAC1B,KAAK,iCACH,OAAO,MAAM;GACf,KAAK,sCACH,OAAO,MAAM,cAAc,MAAM,SAAS,gBACxC,cAAc,CAAC,YAAY,GAAG,IAAI,CAAC,CACrC;GACF,KAAK,qCACH,OAAO,MAAM,YAAY;GAC3B,KAAK,mCACH,OAAO,MAAM,WAAW,MAAM,SAAS,aAAa;IAClD,MAAM,OAAO,UAAU,SAAS,UAAU;IAC1C,OAAO,OAAO,CAAC,IAAI,IAAI,CAAC;GAC1B,CAAC;GACH,KAAK,uCACH,OAAO,MAAM;GACf,KAAK,+BACH,OAAO,MAAM;GACf,KAAK,+BACH,OAAO,MAAM,OAAO,MAAM,SAAS,SAAU,OAAO,CAAC,KAAK,KAAK,IAAI,CAAC,CAAE;GACxE,KAAK,gCAAgC;IACnC,MAAM,aAAa,MAAM;IACzB,QAAQ,YAAY,UAApB;KACE,KAAK,uBACH,OAAO,WAAW;KACpB,KAAK,8BACH,OAAO,WAAW,oBAAoB,KAAK,WAAW,OAAO,IAAI;KACnE,KAAK,yBACH,OAAO,WAAW;KACpB,KAAK,+BACH,OAAO,WAAW,QAAQ,WAAW;KACvC,KAAK,uBACH,OAAO,WAAW;KACpB,SACE;IACJ;GACF;GACA,SACE;EACJ;CACF;CAGA,MAAM,iBAAiB,QAAgB,QAA+B;EACpE,IAAI,CAAC,KAAK,iBAAiB,CAAC,KAAK,eAC/B,MAAM,IAAI,aACR,8FACA,iBAAiB,cACnB;EAGF,MAAM,iBAAiB,KAAK,cAAc;EAC1C,IAAI,CAAC,gBACH,MAAM,IAAI,aACR,WAAW,OAAO,mDAAmD,OAAO,KAAK,KAAK,aAAa,CAAC,CAAC,KAAK,IAAI,KAC9G,iBAAiB,cACnB;EAGF,MAAM,KAAK,gBAAgB,QAAQ,KAAK,eAAe,cAAc;CACvE;CAEA,MAAM,eAAkC;EAEtC,QAAO,MADe,KAAK,WAAW,EAAA,CACvB;CACjB;CAGA,MAAM,gBACJ,QACA,SACA,OACe;EAgBf,MAAM,KAAK,QAAQ,OAAO;;;;;;;;;;;;;OAAU;GAClC,WAAW,KAAK;GAChB;GACA;GACA,OAAO,EAAE,sBAAsB,MAAM;EACvC,CAAC;CACH;CAEA,MAAM,aAA+B;EAEnC,QAAO,MADe,KAAK,WAAW,EAAA,CACvB;CACjB;;;;CAKA,aACE,UAC4D;EAC5D,QAAQ,SAAS,YAAY,GAA7B;GACE,KAAK,QACH,OAAO;GACT,KAAK,UACH,OAAO;GACT,KAAK,QACH,OAAO;GACT,KAAK,iBACH,OAAO;GACT,KAAK,aACH,OAAO;GACT,SACE,OAAO;EACX;CACF;AACF;;;;;;ACvrBA,IAAa,kBAAkB;CAC7B;CACA;CACA;CACA;CACA;CACA;CACA;AACF"}
package/metadata.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "path": "packages/projects",
4
4
  "position": {
5
5
  "index": 21,
6
- "count": 31
6
+ "count": 32
7
7
  },
8
8
  "description": "Standardized project management interface for GitHub Projects, Jira, ZenHub, and Linear",
9
9
  "provides": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/projects",
3
- "version": "0.79.0",
3
+ "version": "0.80.1",
4
4
  "description": "Standardized project management interface for GitHub Projects, Jira, ZenHub, and Linear",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -15,15 +15,15 @@
15
15
  "have-projects-context": "./dist/cli/claude-context.js"
16
16
  },
17
17
  "dependencies": {
18
- "@happyvertical/graphql": "0.79.0",
19
- "@happyvertical/repos": "0.79.0"
18
+ "@happyvertical/graphql": "0.80.1",
19
+ "@happyvertical/repos": "0.80.1"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/node": "25.0.10",
23
- "typescript": "^5.9.3",
24
- "vite": "7.3.2",
23
+ "typescript": "5.9.3",
24
+ "vite": "8.1.4",
25
25
  "vite-plugin-dts": "4.5.4",
26
- "vitest": "4.1.5"
26
+ "vitest": "4.1.10"
27
27
  },
28
28
  "files": [
29
29
  "dist",