gitx.do 0.0.3 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (231) hide show
  1. package/README.md +319 -92
  2. package/dist/cli/commands/add.d.ts +176 -0
  3. package/dist/cli/commands/add.d.ts.map +1 -0
  4. package/dist/cli/commands/add.js +979 -0
  5. package/dist/cli/commands/add.js.map +1 -0
  6. package/dist/cli/commands/blame.d.ts +1 -1
  7. package/dist/cli/commands/blame.d.ts.map +1 -1
  8. package/dist/cli/commands/blame.js +1 -1
  9. package/dist/cli/commands/blame.js.map +1 -1
  10. package/dist/cli/commands/branch.d.ts +1 -1
  11. package/dist/cli/commands/branch.d.ts.map +1 -1
  12. package/dist/cli/commands/branch.js +2 -2
  13. package/dist/cli/commands/branch.js.map +1 -1
  14. package/dist/cli/commands/checkout.d.ts +73 -0
  15. package/dist/cli/commands/checkout.d.ts.map +1 -0
  16. package/dist/cli/commands/checkout.js +725 -0
  17. package/dist/cli/commands/checkout.js.map +1 -0
  18. package/dist/cli/commands/commit.d.ts.map +1 -1
  19. package/dist/cli/commands/commit.js +22 -2
  20. package/dist/cli/commands/commit.js.map +1 -1
  21. package/dist/cli/commands/diff.d.ts +4 -4
  22. package/dist/cli/commands/diff.d.ts.map +1 -1
  23. package/dist/cli/commands/diff.js +9 -8
  24. package/dist/cli/commands/diff.js.map +1 -1
  25. package/dist/cli/commands/log.d.ts +1 -1
  26. package/dist/cli/commands/log.d.ts.map +1 -1
  27. package/dist/cli/commands/log.js +1 -1
  28. package/dist/cli/commands/log.js.map +1 -1
  29. package/dist/cli/commands/merge.d.ts +106 -0
  30. package/dist/cli/commands/merge.d.ts.map +1 -0
  31. package/dist/cli/commands/merge.js +852 -0
  32. package/dist/cli/commands/merge.js.map +1 -0
  33. package/dist/cli/commands/review.d.ts +1 -1
  34. package/dist/cli/commands/review.d.ts.map +1 -1
  35. package/dist/cli/commands/review.js +26 -1
  36. package/dist/cli/commands/review.js.map +1 -1
  37. package/dist/cli/commands/stash.d.ts +157 -0
  38. package/dist/cli/commands/stash.d.ts.map +1 -0
  39. package/dist/cli/commands/stash.js +655 -0
  40. package/dist/cli/commands/stash.js.map +1 -0
  41. package/dist/cli/commands/status.d.ts.map +1 -1
  42. package/dist/cli/commands/status.js +1 -2
  43. package/dist/cli/commands/status.js.map +1 -1
  44. package/dist/cli/commands/web.d.ts.map +1 -1
  45. package/dist/cli/commands/web.js +3 -2
  46. package/dist/cli/commands/web.js.map +1 -1
  47. package/dist/cli/fs-adapter.d.ts.map +1 -1
  48. package/dist/cli/fs-adapter.js +3 -5
  49. package/dist/cli/fs-adapter.js.map +1 -1
  50. package/dist/cli/fsx-cli-adapter.d.ts +359 -0
  51. package/dist/cli/fsx-cli-adapter.d.ts.map +1 -0
  52. package/dist/cli/fsx-cli-adapter.js +619 -0
  53. package/dist/cli/fsx-cli-adapter.js.map +1 -0
  54. package/dist/cli/index.d.ts.map +1 -1
  55. package/dist/cli/index.js +68 -12
  56. package/dist/cli/index.js.map +1 -1
  57. package/dist/cli/ui/components/DiffView.d.ts +7 -2
  58. package/dist/cli/ui/components/DiffView.d.ts.map +1 -1
  59. package/dist/cli/ui/components/DiffView.js.map +1 -1
  60. package/dist/cli/ui/components/ErrorDisplay.d.ts +6 -2
  61. package/dist/cli/ui/components/ErrorDisplay.d.ts.map +1 -1
  62. package/dist/cli/ui/components/ErrorDisplay.js.map +1 -1
  63. package/dist/cli/ui/components/FuzzySearch.d.ts +8 -2
  64. package/dist/cli/ui/components/FuzzySearch.d.ts.map +1 -1
  65. package/dist/cli/ui/components/FuzzySearch.js.map +1 -1
  66. package/dist/cli/ui/components/LoadingSpinner.d.ts +6 -2
  67. package/dist/cli/ui/components/LoadingSpinner.d.ts.map +1 -1
  68. package/dist/cli/ui/components/LoadingSpinner.js.map +1 -1
  69. package/dist/cli/ui/components/NavigationList.d.ts +7 -2
  70. package/dist/cli/ui/components/NavigationList.d.ts.map +1 -1
  71. package/dist/cli/ui/components/NavigationList.js.map +1 -1
  72. package/dist/cli/ui/components/ScrollableContent.d.ts +7 -2
  73. package/dist/cli/ui/components/ScrollableContent.d.ts.map +1 -1
  74. package/dist/cli/ui/components/ScrollableContent.js.map +1 -1
  75. package/dist/cli/ui/terminal-ui.d.ts +42 -9
  76. package/dist/cli/ui/terminal-ui.d.ts.map +1 -1
  77. package/dist/cli/ui/terminal-ui.js.map +1 -1
  78. package/dist/do/BashModule.d.ts +871 -0
  79. package/dist/do/BashModule.d.ts.map +1 -0
  80. package/dist/do/BashModule.js +1143 -0
  81. package/dist/do/BashModule.js.map +1 -0
  82. package/dist/do/FsModule.d.ts +612 -0
  83. package/dist/do/FsModule.d.ts.map +1 -0
  84. package/dist/do/FsModule.js +1120 -0
  85. package/dist/do/FsModule.js.map +1 -0
  86. package/dist/do/GitModule.d.ts +635 -0
  87. package/dist/do/GitModule.d.ts.map +1 -0
  88. package/dist/do/GitModule.js +784 -0
  89. package/dist/do/GitModule.js.map +1 -0
  90. package/dist/do/GitRepoDO.d.ts +281 -0
  91. package/dist/do/GitRepoDO.d.ts.map +1 -0
  92. package/dist/do/GitRepoDO.js +479 -0
  93. package/dist/do/GitRepoDO.js.map +1 -0
  94. package/dist/do/bash-ast.d.ts +246 -0
  95. package/dist/do/bash-ast.d.ts.map +1 -0
  96. package/dist/do/bash-ast.js +888 -0
  97. package/dist/do/bash-ast.js.map +1 -0
  98. package/dist/do/container-executor.d.ts +491 -0
  99. package/dist/do/container-executor.d.ts.map +1 -0
  100. package/dist/do/container-executor.js +731 -0
  101. package/dist/do/container-executor.js.map +1 -0
  102. package/dist/do/index.d.ts +53 -0
  103. package/dist/do/index.d.ts.map +1 -0
  104. package/dist/do/index.js +91 -0
  105. package/dist/do/index.js.map +1 -0
  106. package/dist/do/tiered-storage.d.ts +403 -0
  107. package/dist/do/tiered-storage.d.ts.map +1 -0
  108. package/dist/do/tiered-storage.js +689 -0
  109. package/dist/do/tiered-storage.js.map +1 -0
  110. package/dist/do/withBash.d.ts +231 -0
  111. package/dist/do/withBash.d.ts.map +1 -0
  112. package/dist/do/withBash.js +244 -0
  113. package/dist/do/withBash.js.map +1 -0
  114. package/dist/do/withFs.d.ts +237 -0
  115. package/dist/do/withFs.d.ts.map +1 -0
  116. package/dist/do/withFs.js +387 -0
  117. package/dist/do/withFs.js.map +1 -0
  118. package/dist/do/withGit.d.ts +180 -0
  119. package/dist/do/withGit.d.ts.map +1 -0
  120. package/dist/do/withGit.js +271 -0
  121. package/dist/do/withGit.js.map +1 -0
  122. package/dist/durable-object/object-store.d.ts +157 -15
  123. package/dist/durable-object/object-store.d.ts.map +1 -1
  124. package/dist/durable-object/object-store.js +435 -47
  125. package/dist/durable-object/object-store.js.map +1 -1
  126. package/dist/durable-object/schema.d.ts +12 -1
  127. package/dist/durable-object/schema.d.ts.map +1 -1
  128. package/dist/durable-object/schema.js +87 -2
  129. package/dist/durable-object/schema.js.map +1 -1
  130. package/dist/index.d.ts +84 -1
  131. package/dist/index.d.ts.map +1 -1
  132. package/dist/index.js +34 -0
  133. package/dist/index.js.map +1 -1
  134. package/dist/mcp/sandbox/miniflare-evaluator.d.ts +22 -0
  135. package/dist/mcp/sandbox/miniflare-evaluator.d.ts.map +1 -0
  136. package/dist/mcp/sandbox/miniflare-evaluator.js +140 -0
  137. package/dist/mcp/sandbox/miniflare-evaluator.js.map +1 -0
  138. package/dist/mcp/sandbox/object-store-proxy.d.ts +32 -0
  139. package/dist/mcp/sandbox/object-store-proxy.d.ts.map +1 -0
  140. package/dist/mcp/sandbox/object-store-proxy.js +30 -0
  141. package/dist/mcp/sandbox/object-store-proxy.js.map +1 -0
  142. package/dist/mcp/sandbox/template.d.ts +17 -0
  143. package/dist/mcp/sandbox/template.d.ts.map +1 -0
  144. package/dist/mcp/sandbox/template.js +71 -0
  145. package/dist/mcp/sandbox/template.js.map +1 -0
  146. package/dist/mcp/sandbox.d.ts.map +1 -1
  147. package/dist/mcp/sandbox.js +16 -4
  148. package/dist/mcp/sandbox.js.map +1 -1
  149. package/dist/mcp/tools/do.d.ts +32 -0
  150. package/dist/mcp/tools/do.d.ts.map +1 -0
  151. package/dist/mcp/tools/do.js +117 -0
  152. package/dist/mcp/tools/do.js.map +1 -0
  153. package/dist/mcp/tools.d.ts.map +1 -1
  154. package/dist/mcp/tools.js +1258 -22
  155. package/dist/mcp/tools.js.map +1 -1
  156. package/dist/pack/delta.d.ts +8 -0
  157. package/dist/pack/delta.d.ts.map +1 -1
  158. package/dist/pack/delta.js +241 -30
  159. package/dist/pack/delta.js.map +1 -1
  160. package/dist/refs/branch.d.ts +38 -25
  161. package/dist/refs/branch.d.ts.map +1 -1
  162. package/dist/refs/branch.js +421 -94
  163. package/dist/refs/branch.js.map +1 -1
  164. package/dist/refs/storage.d.ts +77 -5
  165. package/dist/refs/storage.d.ts.map +1 -1
  166. package/dist/refs/storage.js +193 -43
  167. package/dist/refs/storage.js.map +1 -1
  168. package/dist/refs/tag.d.ts +44 -24
  169. package/dist/refs/tag.d.ts.map +1 -1
  170. package/dist/refs/tag.js +411 -70
  171. package/dist/refs/tag.js.map +1 -1
  172. package/dist/storage/backend.d.ts +425 -0
  173. package/dist/storage/backend.d.ts.map +1 -0
  174. package/dist/storage/backend.js +41 -0
  175. package/dist/storage/backend.js.map +1 -0
  176. package/dist/storage/fsx-adapter.d.ts +204 -0
  177. package/dist/storage/fsx-adapter.d.ts.map +1 -0
  178. package/dist/storage/fsx-adapter.js +518 -0
  179. package/dist/storage/fsx-adapter.js.map +1 -0
  180. package/dist/storage/r2-pack.d.ts.map +1 -1
  181. package/dist/storage/r2-pack.js +4 -1
  182. package/dist/storage/r2-pack.js.map +1 -1
  183. package/dist/tiered/cdc-pipeline.js +3 -3
  184. package/dist/tiered/cdc-pipeline.js.map +1 -1
  185. package/dist/tiered/migration.d.ts.map +1 -1
  186. package/dist/tiered/migration.js +4 -1
  187. package/dist/tiered/migration.js.map +1 -1
  188. package/dist/types/capability.d.ts +1385 -0
  189. package/dist/types/capability.d.ts.map +1 -0
  190. package/dist/types/capability.js +36 -0
  191. package/dist/types/capability.js.map +1 -0
  192. package/dist/types/index.d.ts +13 -0
  193. package/dist/types/index.d.ts.map +1 -0
  194. package/dist/types/index.js +18 -0
  195. package/dist/types/index.js.map +1 -0
  196. package/dist/types/interfaces.d.ts +673 -0
  197. package/dist/types/interfaces.d.ts.map +1 -0
  198. package/dist/types/interfaces.js +26 -0
  199. package/dist/types/interfaces.js.map +1 -0
  200. package/dist/types/objects.d.ts +182 -0
  201. package/dist/types/objects.d.ts.map +1 -1
  202. package/dist/types/objects.js +249 -4
  203. package/dist/types/objects.js.map +1 -1
  204. package/dist/types/storage.d.ts +114 -0
  205. package/dist/types/storage.d.ts.map +1 -1
  206. package/dist/types/storage.js +160 -1
  207. package/dist/types/storage.js.map +1 -1
  208. package/dist/types/worker-loader.d.ts +60 -0
  209. package/dist/types/worker-loader.d.ts.map +1 -0
  210. package/dist/types/worker-loader.js +62 -0
  211. package/dist/types/worker-loader.js.map +1 -0
  212. package/dist/utils/hash.d.ts +126 -80
  213. package/dist/utils/hash.d.ts.map +1 -1
  214. package/dist/utils/hash.js +191 -100
  215. package/dist/utils/hash.js.map +1 -1
  216. package/dist/utils/sha1.d.ts +206 -0
  217. package/dist/utils/sha1.d.ts.map +1 -1
  218. package/dist/utils/sha1.js +405 -0
  219. package/dist/utils/sha1.js.map +1 -1
  220. package/dist/wire/path-security.d.ts +157 -0
  221. package/dist/wire/path-security.d.ts.map +1 -0
  222. package/dist/wire/path-security.js +307 -0
  223. package/dist/wire/path-security.js.map +1 -0
  224. package/dist/wire/receive-pack.d.ts +7 -0
  225. package/dist/wire/receive-pack.d.ts.map +1 -1
  226. package/dist/wire/receive-pack.js +29 -1
  227. package/dist/wire/receive-pack.js.map +1 -1
  228. package/dist/wire/upload-pack.d.ts.map +1 -1
  229. package/dist/wire/upload-pack.js +4 -1
  230. package/dist/wire/upload-pack.js.map +1 -1
  231. package/package.json +10 -1
package/dist/refs/tag.js CHANGED
@@ -32,6 +32,8 @@
32
32
  * const versions = await listTags(manager, { pattern: 'v*' })
33
33
  * ```
34
34
  */
35
+ // Re-export RefStorage for backward compatibility
36
+ export { RefStorage } from './storage';
35
37
  /**
36
38
  * Error thrown when a tag operation fails.
37
39
  *
@@ -107,6 +109,11 @@ export class TagError extends Error {
107
109
  * ```
108
110
  */
109
111
  export class TagManager {
112
+ refStorage;
113
+ objectStorage;
114
+ gpgSigner;
115
+ // Simple in-memory lock to handle concurrent tag creation
116
+ pendingCreations = new Set();
110
117
  /**
111
118
  * Create a new TagManager.
112
119
  *
@@ -115,10 +122,9 @@ export class TagManager {
115
122
  * @param gpgSigner - Optional GPG signer for signed tags
116
123
  */
117
124
  constructor(refStorage, objectStorage, gpgSigner) {
118
- void refStorage; // Suppress unused variable warning until implementation
119
- void objectStorage;
120
- void gpgSigner;
121
- // TODO: Implement in GREEN phase
125
+ this.refStorage = refStorage;
126
+ this.objectStorage = objectStorage;
127
+ this.gpgSigner = gpgSigner;
122
128
  }
123
129
  /**
124
130
  * Create a new tag.
@@ -157,9 +163,98 @@ export class TagManager {
157
163
  * })
158
164
  * ```
159
165
  */
160
- async createTag(_name, _target, _options) {
161
- // TODO: Implement in GREEN phase
162
- throw new Error('Not implemented');
166
+ async createTag(name, target, options) {
167
+ // Validate tag name
168
+ if (!isValidTagName(name)) {
169
+ throw new TagError(`Invalid tag name: ${name}`, 'INVALID_TAG_NAME', name);
170
+ }
171
+ const refName = `refs/tags/${name}`;
172
+ // Synchronous check-and-lock to handle concurrent creation attempts
173
+ if (this.pendingCreations.has(name)) {
174
+ throw new TagError(`Tag already exists: ${name}`, 'TAG_EXISTS', name);
175
+ }
176
+ // Check if tag already exists
177
+ const existingSha = await this.refStorage.getRef(refName);
178
+ if (existingSha !== null && !options?.force) {
179
+ throw new TagError(`Tag already exists: ${name}`, 'TAG_EXISTS', name);
180
+ }
181
+ // Mark as pending (synchronous operation for atomicity)
182
+ if (!options?.force) {
183
+ if (this.pendingCreations.has(name)) {
184
+ throw new TagError(`Tag already exists: ${name}`, 'TAG_EXISTS', name);
185
+ }
186
+ this.pendingCreations.add(name);
187
+ }
188
+ // Determine if this should be an annotated tag
189
+ const isAnnotated = options?.annotated === true || (options?.message !== undefined && options?.message !== '');
190
+ try {
191
+ if (isAnnotated) {
192
+ // Validate message for annotated tags
193
+ const rawMessage = options?.message;
194
+ if (!rawMessage || rawMessage.trim().length === 0) {
195
+ throw new TagError('Annotated tag requires a message', 'MESSAGE_REQUIRED', name);
196
+ }
197
+ const formattedMessage = formatTagMessage(rawMessage);
198
+ // Validate tagger (can have timestamp set to 0 or undefined, we'll use current time)
199
+ let tagger = options?.tagger;
200
+ if (tagger && tagger.timestamp === undefined) {
201
+ tagger = {
202
+ ...tagger,
203
+ timestamp: Math.floor(Date.now() / 1000)
204
+ };
205
+ }
206
+ // Handle signing
207
+ let signature;
208
+ let finalMessage = formattedMessage;
209
+ if (options?.sign) {
210
+ if (!this.gpgSigner) {
211
+ throw new TagError('GPG signer not available', 'GPG_ERROR', name);
212
+ }
213
+ // Sign the tag content
214
+ const encoder = new TextEncoder();
215
+ signature = await this.gpgSigner.sign(encoder.encode(formattedMessage), options?.keyId);
216
+ // Append signature to message (Git stores signature in the tag object)
217
+ finalMessage = formattedMessage + '\n' + signature;
218
+ }
219
+ // Get the target object type
220
+ const targetType = await this.objectStorage.readObjectType(target);
221
+ // Create tag object
222
+ const tagObj = {
223
+ object: target,
224
+ objectType: targetType || 'commit',
225
+ name,
226
+ tagger,
227
+ message: finalMessage
228
+ };
229
+ // Write tag object and get its SHA
230
+ const tagObjSha = await this.objectStorage.writeTagObject(tagObj);
231
+ // Write ref pointing to tag object
232
+ await this.refStorage.setRef(refName, tagObjSha);
233
+ return {
234
+ name,
235
+ type: 'annotated',
236
+ sha: tagObjSha,
237
+ targetSha: target,
238
+ targetType: targetType || 'commit',
239
+ tagger,
240
+ message: formattedMessage,
241
+ signature
242
+ };
243
+ }
244
+ else {
245
+ // Lightweight tag - just write ref pointing to target
246
+ await this.refStorage.setRef(refName, target);
247
+ return {
248
+ name,
249
+ type: 'lightweight',
250
+ sha: target
251
+ };
252
+ }
253
+ }
254
+ finally {
255
+ // Clear the pending lock
256
+ this.pendingCreations.delete(name);
257
+ }
163
258
  }
164
259
  /**
165
260
  * Delete a tag.
@@ -181,9 +276,19 @@ export class TagManager {
181
276
  * await manager.deleteTag('maybe-exists', { force: true })
182
277
  * ```
183
278
  */
184
- async deleteTag(_name, _options) {
185
- // TODO: Implement in GREEN phase
186
- throw new Error('Not implemented');
279
+ async deleteTag(name, options) {
280
+ const refName = `refs/tags/${name}`;
281
+ // Check if tag exists
282
+ const existingSha = await this.refStorage.getRef(refName);
283
+ if (existingSha === null) {
284
+ if (options?.force) {
285
+ return false;
286
+ }
287
+ throw new TagError(`Tag not found: ${name}`, 'TAG_NOT_FOUND', name);
288
+ }
289
+ // Delete the ref (but not the tag object)
290
+ await this.refStorage.deleteRef(refName);
291
+ return true;
187
292
  }
188
293
  /**
189
294
  * List all tags.
@@ -216,9 +321,79 @@ export class TagManager {
216
321
  * })
217
322
  * ```
218
323
  */
219
- async listTags(_options) {
220
- // TODO: Implement in GREEN phase
221
- throw new Error('Not implemented');
324
+ async listTags(options) {
325
+ // List all refs under refs/tags/
326
+ const tagRefs = await this.refStorage.listRefs('refs/tags/');
327
+ // Determine if we need metadata (either explicitly requested or for date sorting)
328
+ const needMetadata = options?.includeMetadata || options?.sort === 'date';
329
+ // Build tag list
330
+ let tags = [];
331
+ for (const refEntry of tagRefs) {
332
+ const tagName = refEntry.name.replace('refs/tags/', '');
333
+ const sha = refEntry.sha;
334
+ // Check if it's an annotated tag by trying to read the tag object
335
+ const tagObj = await this.objectStorage.readTagObject(sha);
336
+ if (tagObj) {
337
+ // Annotated tag
338
+ const tag = {
339
+ name: tagName,
340
+ type: 'annotated',
341
+ sha
342
+ };
343
+ if (needMetadata) {
344
+ tag.targetSha = tagObj.object;
345
+ tag.targetType = tagObj.objectType;
346
+ tag.tagger = tagObj.tagger;
347
+ tag.message = tagObj.message;
348
+ }
349
+ tags.push(tag);
350
+ }
351
+ else {
352
+ // Lightweight tag
353
+ tags.push({
354
+ name: tagName,
355
+ type: 'lightweight',
356
+ sha
357
+ });
358
+ }
359
+ }
360
+ // Filter by pattern if provided
361
+ if (options?.pattern) {
362
+ tags = filterTagsByPattern(tags, options.pattern);
363
+ }
364
+ // Sort tags
365
+ const sortType = options?.sort || 'name';
366
+ const sortDirection = options?.sortDirection || 'asc';
367
+ if (sortType === 'version') {
368
+ tags = sortTagsByVersion(tags, sortDirection);
369
+ }
370
+ else if (sortType === 'date') {
371
+ // Filter out tags without timestamps when sorting by date
372
+ tags = tags.filter(t => t.tagger?.timestamp !== undefined);
373
+ // Sort by tagger timestamp
374
+ tags.sort((a, b) => {
375
+ const aTime = a.tagger.timestamp;
376
+ const bTime = b.tagger.timestamp;
377
+ const timeDiff = sortDirection === 'asc' ? aTime - bTime : bTime - aTime;
378
+ // Secondary sort by name when timestamps are equal
379
+ if (timeDiff === 0) {
380
+ return a.name.localeCompare(b.name);
381
+ }
382
+ return timeDiff;
383
+ });
384
+ }
385
+ else {
386
+ // Sort by name (default)
387
+ tags.sort((a, b) => {
388
+ const cmp = a.name.localeCompare(b.name);
389
+ return sortDirection === 'asc' ? cmp : -cmp;
390
+ });
391
+ }
392
+ // Limit results
393
+ if (options?.limit !== undefined) {
394
+ tags = tags.slice(0, options.limit);
395
+ }
396
+ return tags;
222
397
  }
223
398
  /**
224
399
  * Get a tag by name.
@@ -244,9 +419,43 @@ export class TagManager {
244
419
  * }
245
420
  * ```
246
421
  */
247
- async getTag(_name, _options) {
248
- // TODO: Implement in GREEN phase
249
- throw new Error('Not implemented');
422
+ async getTag(name, options) {
423
+ const refName = `refs/tags/${name}`;
424
+ const sha = await this.refStorage.getRef(refName);
425
+ if (sha === null) {
426
+ return null;
427
+ }
428
+ // Try to read as tag object (annotated tag)
429
+ const tagObj = await this.objectStorage.readTagObject(sha);
430
+ if (tagObj) {
431
+ // Annotated tag
432
+ const tag = {
433
+ name,
434
+ type: 'annotated',
435
+ sha
436
+ };
437
+ if (options?.resolve) {
438
+ tag.targetSha = tagObj.object;
439
+ tag.targetType = tagObj.objectType;
440
+ tag.tagger = tagObj.tagger;
441
+ tag.message = tagObj.message;
442
+ // Check for signature in message
443
+ const parsed = parseTagMessage(tagObj.message);
444
+ if (parsed.signature) {
445
+ tag.signature = parsed.signature;
446
+ tag.message = parsed.message;
447
+ }
448
+ }
449
+ return tag;
450
+ }
451
+ else {
452
+ // Lightweight tag
453
+ return {
454
+ name,
455
+ type: 'lightweight',
456
+ sha
457
+ };
458
+ }
250
459
  }
251
460
  /**
252
461
  * Check if a tag exists.
@@ -264,9 +473,10 @@ export class TagManager {
264
473
  * }
265
474
  * ```
266
475
  */
267
- async tagExists(_name) {
268
- // TODO: Implement in GREEN phase
269
- throw new Error('Not implemented');
476
+ async tagExists(name) {
477
+ const refName = `refs/tags/${name}`;
478
+ const sha = await this.refStorage.getRef(refName);
479
+ return sha !== null;
270
480
  }
271
481
  /**
272
482
  * Get the target (commit SHA) that a tag points to.
@@ -285,9 +495,26 @@ export class TagManager {
285
495
  * const commitSha = await manager.getTagTarget('v1.0.0')
286
496
  * ```
287
497
  */
288
- async getTagTarget(_name) {
289
- // TODO: Implement in GREEN phase
290
- throw new Error('Not implemented');
498
+ async getTagTarget(name) {
499
+ const refName = `refs/tags/${name}`;
500
+ let sha = await this.refStorage.getRef(refName);
501
+ if (sha === null) {
502
+ throw new TagError(`Tag not found: ${name}`, 'TAG_NOT_FOUND', name);
503
+ }
504
+ // Resolve through tag objects to get final commit
505
+ let depth = 0;
506
+ const maxDepth = 10;
507
+ while (depth < maxDepth) {
508
+ const tagObj = await this.objectStorage.readTagObject(sha);
509
+ if (!tagObj) {
510
+ // Not a tag object, this is the final target
511
+ return sha;
512
+ }
513
+ // Follow the tag to its target
514
+ sha = tagObj.object;
515
+ depth++;
516
+ }
517
+ return sha;
291
518
  }
292
519
  /**
293
520
  * Verify a tag's GPG signature.
@@ -311,9 +538,30 @@ export class TagManager {
311
538
  * }
312
539
  * ```
313
540
  */
314
- async verifyTag(_name) {
315
- // TODO: Implement in GREEN phase
316
- throw new Error('Not implemented');
541
+ async verifyTag(name) {
542
+ const refName = `refs/tags/${name}`;
543
+ const sha = await this.refStorage.getRef(refName);
544
+ if (sha === null) {
545
+ throw new TagError(`Tag not found: ${name}`, 'TAG_NOT_FOUND', name);
546
+ }
547
+ // Get tag object
548
+ const tagObj = await this.objectStorage.readTagObject(sha);
549
+ if (!tagObj) {
550
+ // Lightweight tag - cannot be signed
551
+ return { valid: false };
552
+ }
553
+ // Parse message to check for signature
554
+ const parsed = parseTagMessage(tagObj.message);
555
+ if (!parsed.signature) {
556
+ // No signature
557
+ return { valid: false };
558
+ }
559
+ // Verify signature using GPG signer
560
+ if (!this.gpgSigner) {
561
+ return { valid: false, error: 'GPG signer not available' };
562
+ }
563
+ const encoder = new TextEncoder();
564
+ return this.gpgSigner.verify(encoder.encode(parsed.message), parsed.signature);
317
565
  }
318
566
  /**
319
567
  * Check if a tag is annotated.
@@ -332,9 +580,15 @@ export class TagManager {
332
580
  * }
333
581
  * ```
334
582
  */
335
- async isAnnotatedTag(_name) {
336
- // TODO: Implement in GREEN phase
337
- throw new Error('Not implemented');
583
+ async isAnnotatedTag(name) {
584
+ const refName = `refs/tags/${name}`;
585
+ const sha = await this.refStorage.getRef(refName);
586
+ if (sha === null) {
587
+ throw new TagError(`Tag not found: ${name}`, 'TAG_NOT_FOUND', name);
588
+ }
589
+ // Try to read as tag object
590
+ const tagObj = await this.objectStorage.readTagObject(sha);
591
+ return tagObj !== null;
338
592
  }
339
593
  }
340
594
  // ============================================================================
@@ -369,9 +623,49 @@ export class TagManager {
369
623
  * isValidTagName('') // false (empty)
370
624
  * ```
371
625
  */
372
- export function isValidTagName(_name) {
373
- // TODO: Implement in GREEN phase
374
- throw new Error('Not implemented');
626
+ export function isValidTagName(name) {
627
+ // Empty name is invalid
628
+ if (!name || name.length === 0 || name.trim().length === 0) {
629
+ return false;
630
+ }
631
+ // Cannot end with .lock
632
+ if (name.endsWith('.lock')) {
633
+ return false;
634
+ }
635
+ // Cannot contain ..
636
+ if (name.includes('..')) {
637
+ return false;
638
+ }
639
+ // Cannot contain @{
640
+ if (name.includes('@{')) {
641
+ return false;
642
+ }
643
+ // Cannot contain control characters (ASCII 0-31, 127), space, ~, ^, :, ?, *, [, \
644
+ const invalidChars = /[\x00-\x1f\x7f ~^:?*[\]\\]/;
645
+ if (invalidChars.test(name)) {
646
+ return false;
647
+ }
648
+ // Cannot end with /
649
+ if (name.endsWith('/')) {
650
+ return false;
651
+ }
652
+ // Split into components and check each
653
+ const components = name.split('/');
654
+ for (const component of components) {
655
+ // Cannot have empty components (// in path)
656
+ if (component.length === 0) {
657
+ return false;
658
+ }
659
+ // Cannot start with .
660
+ if (component.startsWith('.')) {
661
+ return false;
662
+ }
663
+ // Cannot end with .
664
+ if (component.endsWith('.')) {
665
+ return false;
666
+ }
667
+ }
668
+ return true;
375
669
  }
376
670
  /**
377
671
  * Type guard for annotated tags.
@@ -391,9 +685,10 @@ export function isValidTagName(_name) {
391
685
  * }
392
686
  * ```
393
687
  */
394
- export function isAnnotatedTag(_tag) {
395
- // TODO: Implement in GREEN phase
396
- throw new Error('Not implemented');
688
+ export function isAnnotatedTag(tag) {
689
+ return tag.type === 'annotated' &&
690
+ tag.tagger !== undefined &&
691
+ tag.message !== undefined;
397
692
  }
398
693
  /**
399
694
  * Format a tag message.
@@ -410,9 +705,18 @@ export function isAnnotatedTag(_tag) {
410
705
  * formatTagMessage(' Hello World \r\n') // 'Hello World\n'
411
706
  * ```
412
707
  */
413
- export function formatTagMessage(_message) {
414
- // TODO: Implement in GREEN phase
415
- throw new Error('Not implemented');
708
+ export function formatTagMessage(message) {
709
+ // Normalize line endings (CRLF -> LF)
710
+ let formatted = message.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
711
+ // Trim leading and trailing whitespace, but preserve internal structure
712
+ // The test expects 'Hello\r\nWorld\r\n' to become 'Hello\nWorld\n'
713
+ // So we trim leading whitespace, convert line endings, but preserve trailing newline if present
714
+ const hadTrailingNewline = formatted.endsWith('\n');
715
+ formatted = formatted.trim();
716
+ if (hadTrailingNewline && formatted.length > 0) {
717
+ formatted += '\n';
718
+ }
719
+ return formatted;
416
720
  }
417
721
  /**
418
722
  * Parse a tag message from raw content.
@@ -432,9 +736,17 @@ export function formatTagMessage(_message) {
432
736
  * }
433
737
  * ```
434
738
  */
435
- export function parseTagMessage(_content) {
436
- // TODO: Implement in GREEN phase
437
- throw new Error('Not implemented');
739
+ export function parseTagMessage(content) {
740
+ const sigMarker = '-----BEGIN PGP SIGNATURE-----';
741
+ const sigIndex = content.indexOf(sigMarker);
742
+ if (sigIndex === -1) {
743
+ // No signature
744
+ return { message: content.trim() };
745
+ }
746
+ // Split message and signature
747
+ const message = content.slice(0, sigIndex).trim();
748
+ const signature = content.slice(sigIndex);
749
+ return { message, signature };
438
750
  }
439
751
  // ============================================================================
440
752
  // Convenience Functions
@@ -459,9 +771,8 @@ export function parseTagMessage(_content) {
459
771
  * })
460
772
  * ```
461
773
  */
462
- export async function createTag(_manager, _name, _target, _options) {
463
- // TODO: Implement in GREEN phase
464
- throw new Error('Not implemented');
774
+ export async function createTag(manager, name, target, options) {
775
+ return manager.createTag(name, target, options);
465
776
  }
466
777
  /**
467
778
  * Create an annotated tag with message.
@@ -489,9 +800,13 @@ export async function createTag(_manager, _name, _target, _options) {
489
800
  * )
490
801
  * ```
491
802
  */
492
- export async function createAnnotatedTag(_manager, _name, _target, _message, _tagger, _options) {
493
- // TODO: Implement in GREEN phase
494
- throw new Error('Not implemented');
803
+ export async function createAnnotatedTag(manager, name, target, message, tagger, options) {
804
+ return manager.createTag(name, target, {
805
+ ...options,
806
+ annotated: true,
807
+ message,
808
+ tagger
809
+ });
495
810
  }
496
811
  /**
497
812
  * Delete a tag.
@@ -509,9 +824,8 @@ export async function createAnnotatedTag(_manager, _name, _target, _message, _ta
509
824
  * await deleteTag(manager, 'v0.9.0-beta')
510
825
  * ```
511
826
  */
512
- export async function deleteTag(_manager, _name, _options) {
513
- // TODO: Implement in GREEN phase
514
- throw new Error('Not implemented');
827
+ export async function deleteTag(manager, name, options) {
828
+ return manager.deleteTag(name, options);
515
829
  }
516
830
  /**
517
831
  * List all tags.
@@ -528,9 +842,8 @@ export async function deleteTag(_manager, _name, _options) {
528
842
  * const tags = await listTags(manager, { pattern: 'v1.*' })
529
843
  * ```
530
844
  */
531
- export async function listTags(_manager, _options) {
532
- // TODO: Implement in GREEN phase
533
- throw new Error('Not implemented');
845
+ export async function listTags(manager, options) {
846
+ return manager.listTags(options);
534
847
  }
535
848
  /**
536
849
  * Get a tag by name.
@@ -548,9 +861,8 @@ export async function listTags(_manager, _options) {
548
861
  * const tag = await getTag(manager, 'v1.0.0', { resolve: true })
549
862
  * ```
550
863
  */
551
- export async function getTag(_manager, _name, _options) {
552
- // TODO: Implement in GREEN phase
553
- throw new Error('Not implemented');
864
+ export async function getTag(manager, name, options) {
865
+ return manager.getTag(name, options);
554
866
  }
555
867
  /**
556
868
  * Check if a tag is annotated.
@@ -569,9 +881,8 @@ export async function getTag(_manager, _name, _options) {
569
881
  * }
570
882
  * ```
571
883
  */
572
- export async function checkIsAnnotatedTag(_manager, _name) {
573
- // TODO: Implement in GREEN phase
574
- throw new Error('Not implemented');
884
+ export async function checkIsAnnotatedTag(manager, name) {
885
+ return manager.isAnnotatedTag(name);
575
886
  }
576
887
  /**
577
888
  * Verify a tag's signature.
@@ -588,9 +899,8 @@ export async function checkIsAnnotatedTag(_manager, _name) {
588
899
  * const result = await verifyTagSignature(manager, 'v1.0.0')
589
900
  * ```
590
901
  */
591
- export async function verifyTagSignature(_manager, _name) {
592
- // TODO: Implement in GREEN phase
593
- throw new Error('Not implemented');
902
+ export async function verifyTagSignature(manager, name) {
903
+ return manager.verifyTag(name);
594
904
  }
595
905
  /**
596
906
  * Get the target commit SHA for a tag.
@@ -607,9 +917,8 @@ export async function verifyTagSignature(_manager, _name) {
607
917
  * const sha = await getTagTarget(manager, 'v1.0.0')
608
918
  * ```
609
919
  */
610
- export async function getTagTarget(_manager, _name) {
611
- // TODO: Implement in GREEN phase
612
- throw new Error('Not implemented');
920
+ export async function getTagTarget(manager, name) {
921
+ return manager.getTagTarget(name);
613
922
  }
614
923
  /**
615
924
  * Sort tags by semantic version.
@@ -628,9 +937,33 @@ export async function getTagTarget(_manager, _name) {
628
937
  * // ['v2.0.0', 'v1.10.0', 'v1.9.0', 'v1.0.0', ...]
629
938
  * ```
630
939
  */
631
- export function sortTagsByVersion(_tags, _direction = 'asc') {
632
- // TODO: Implement in GREEN phase
633
- throw new Error('Not implemented');
940
+ export function sortTagsByVersion(tags, direction = 'asc') {
941
+ // Parse version from tag name (handles v1.2.3, 1.2.3, v1.2.3-beta, etc.)
942
+ const parseVersion = (name) => {
943
+ // Remove 'v' prefix if present
944
+ const normalized = name.startsWith('v') ? name.slice(1) : name;
945
+ // Extract numeric version parts (split on non-digit, non-dot)
946
+ const parts = normalized.split(/[^0-9.]/)[0].split('.');
947
+ return parts.map(p => parseInt(p, 10) || 0);
948
+ };
949
+ const compareVersions = (a, b) => {
950
+ const maxLen = Math.max(a.length, b.length);
951
+ for (let i = 0; i < maxLen; i++) {
952
+ const aVal = a[i] || 0;
953
+ const bVal = b[i] || 0;
954
+ if (aVal !== bVal) {
955
+ return aVal - bVal;
956
+ }
957
+ }
958
+ return 0;
959
+ };
960
+ const sorted = [...tags].sort((a, b) => {
961
+ const aVer = parseVersion(a.name);
962
+ const bVer = parseVersion(b.name);
963
+ const cmp = compareVersions(aVer, bVer);
964
+ return direction === 'asc' ? cmp : -cmp;
965
+ });
966
+ return sorted;
634
967
  }
635
968
  /**
636
969
  * Filter tags by glob pattern.
@@ -648,8 +981,16 @@ export function sortTagsByVersion(_tags, _direction = 'asc') {
648
981
  * const v1Tags = filterTagsByPattern(tags, 'v1.*')
649
982
  * ```
650
983
  */
651
- export function filterTagsByPattern(_tags, _pattern) {
652
- // TODO: Implement in GREEN phase
653
- throw new Error('Not implemented');
984
+ export function filterTagsByPattern(tags, pattern) {
985
+ // Convert glob pattern to regex
986
+ // * matches any number of characters
987
+ // ? matches a single character
988
+ // Escape special regex characters except * and ?
989
+ const regexPattern = pattern
990
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&') // Escape special regex chars
991
+ .replace(/\*/g, '.*') // * -> .*
992
+ .replace(/\?/g, '.'); // ? -> .
993
+ const regex = new RegExp(`^${regexPattern}$`);
994
+ return tags.filter(tag => regex.test(tag.name));
654
995
  }
655
996
  //# sourceMappingURL=tag.js.map