@the-open-engine/zeroshot 5.4.0

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 (295) hide show
  1. package/CHANGELOG.md +539 -0
  2. package/LICENSE +21 -0
  3. package/README.md +508 -0
  4. package/cli/commands/inspect-render.js +135 -0
  5. package/cli/commands/inspect.js +294 -0
  6. package/cli/commands/providers.js +149 -0
  7. package/cli/index.js +5431 -0
  8. package/cli/lib/first-run.js +211 -0
  9. package/cli/lib/update-checker.js +281 -0
  10. package/cli/message-formatter-utils.js +75 -0
  11. package/cli/message-formatters-normal.js +275 -0
  12. package/cli/message-formatters-watch.js +185 -0
  13. package/cluster-templates/base-templates/debug-workflow.json +422 -0
  14. package/cluster-templates/base-templates/full-workflow.json +727 -0
  15. package/cluster-templates/base-templates/heavy-validation.json +272 -0
  16. package/cluster-templates/base-templates/quick-validation.json +285 -0
  17. package/cluster-templates/base-templates/single-worker.json +71 -0
  18. package/cluster-templates/base-templates/worker-validator.json +230 -0
  19. package/cluster-templates/conductor-bootstrap.json +130 -0
  20. package/docker/zeroshot-cluster/Dockerfile +153 -0
  21. package/docker/zeroshot-cluster/pre-baked-deps.json +28 -0
  22. package/lib/agent-cli-provider/adapters/claude-parser.d.ts +3 -0
  23. package/lib/agent-cli-provider/adapters/claude-parser.d.ts.map +1 -0
  24. package/lib/agent-cli-provider/adapters/claude-parser.js +122 -0
  25. package/lib/agent-cli-provider/adapters/claude-parser.js.map +1 -0
  26. package/lib/agent-cli-provider/adapters/claude-recovery.d.ts +15 -0
  27. package/lib/agent-cli-provider/adapters/claude-recovery.d.ts.map +1 -0
  28. package/lib/agent-cli-provider/adapters/claude-recovery.js +165 -0
  29. package/lib/agent-cli-provider/adapters/claude-recovery.js.map +1 -0
  30. package/lib/agent-cli-provider/adapters/claude.d.ts +3 -0
  31. package/lib/agent-cli-provider/adapters/claude.d.ts.map +1 -0
  32. package/lib/agent-cli-provider/adapters/claude.js +181 -0
  33. package/lib/agent-cli-provider/adapters/claude.js.map +1 -0
  34. package/lib/agent-cli-provider/adapters/codex-parser.d.ts +3 -0
  35. package/lib/agent-cli-provider/adapters/codex-parser.d.ts.map +1 -0
  36. package/lib/agent-cli-provider/adapters/codex-parser.js +192 -0
  37. package/lib/agent-cli-provider/adapters/codex-parser.js.map +1 -0
  38. package/lib/agent-cli-provider/adapters/codex.d.ts +3 -0
  39. package/lib/agent-cli-provider/adapters/codex.d.ts.map +1 -0
  40. package/lib/agent-cli-provider/adapters/codex.js +151 -0
  41. package/lib/agent-cli-provider/adapters/codex.js.map +1 -0
  42. package/lib/agent-cli-provider/adapters/common.d.ts +28 -0
  43. package/lib/agent-cli-provider/adapters/common.d.ts.map +1 -0
  44. package/lib/agent-cli-provider/adapters/common.js +71 -0
  45. package/lib/agent-cli-provider/adapters/common.js.map +1 -0
  46. package/lib/agent-cli-provider/adapters/gemini.d.ts +3 -0
  47. package/lib/agent-cli-provider/adapters/gemini.d.ts.map +1 -0
  48. package/lib/agent-cli-provider/adapters/gemini.js +176 -0
  49. package/lib/agent-cli-provider/adapters/gemini.js.map +1 -0
  50. package/lib/agent-cli-provider/adapters/index.d.ts +9 -0
  51. package/lib/agent-cli-provider/adapters/index.d.ts.map +1 -0
  52. package/lib/agent-cli-provider/adapters/index.js +95 -0
  53. package/lib/agent-cli-provider/adapters/index.js.map +1 -0
  54. package/lib/agent-cli-provider/adapters/opencode.d.ts +3 -0
  55. package/lib/agent-cli-provider/adapters/opencode.d.ts.map +1 -0
  56. package/lib/agent-cli-provider/adapters/opencode.js +228 -0
  57. package/lib/agent-cli-provider/adapters/opencode.js.map +1 -0
  58. package/lib/agent-cli-provider/contract-actions.d.ts +5 -0
  59. package/lib/agent-cli-provider/contract-actions.d.ts.map +1 -0
  60. package/lib/agent-cli-provider/contract-actions.js +140 -0
  61. package/lib/agent-cli-provider/contract-actions.js.map +1 -0
  62. package/lib/agent-cli-provider/contract-env.d.ts +11 -0
  63. package/lib/agent-cli-provider/contract-env.d.ts.map +1 -0
  64. package/lib/agent-cli-provider/contract-env.js +113 -0
  65. package/lib/agent-cli-provider/contract-env.js.map +1 -0
  66. package/lib/agent-cli-provider/contract-envelope.d.ts +49 -0
  67. package/lib/agent-cli-provider/contract-envelope.d.ts.map +1 -0
  68. package/lib/agent-cli-provider/contract-envelope.js +52 -0
  69. package/lib/agent-cli-provider/contract-envelope.js.map +1 -0
  70. package/lib/agent-cli-provider/contract-errors.d.ts +22 -0
  71. package/lib/agent-cli-provider/contract-errors.d.ts.map +1 -0
  72. package/lib/agent-cli-provider/contract-errors.js +71 -0
  73. package/lib/agent-cli-provider/contract-errors.js.map +1 -0
  74. package/lib/agent-cli-provider/contract-fallback.d.ts +7 -0
  75. package/lib/agent-cli-provider/contract-fallback.d.ts.map +1 -0
  76. package/lib/agent-cli-provider/contract-fallback.js +102 -0
  77. package/lib/agent-cli-provider/contract-fallback.js.map +1 -0
  78. package/lib/agent-cli-provider/contract-invoke.d.ts +5 -0
  79. package/lib/agent-cli-provider/contract-invoke.d.ts.map +1 -0
  80. package/lib/agent-cli-provider/contract-invoke.js +89 -0
  81. package/lib/agent-cli-provider/contract-invoke.js.map +1 -0
  82. package/lib/agent-cli-provider/contract-options.d.ts +3 -0
  83. package/lib/agent-cli-provider/contract-options.d.ts.map +1 -0
  84. package/lib/agent-cli-provider/contract-options.js +140 -0
  85. package/lib/agent-cli-provider/contract-options.js.map +1 -0
  86. package/lib/agent-cli-provider/contract-parse.d.ts +15 -0
  87. package/lib/agent-cli-provider/contract-parse.d.ts.map +1 -0
  88. package/lib/agent-cli-provider/contract-parse.js +81 -0
  89. package/lib/agent-cli-provider/contract-parse.js.map +1 -0
  90. package/lib/agent-cli-provider/contract-support.d.ts +18 -0
  91. package/lib/agent-cli-provider/contract-support.d.ts.map +1 -0
  92. package/lib/agent-cli-provider/contract-support.js +154 -0
  93. package/lib/agent-cli-provider/contract-support.js.map +1 -0
  94. package/lib/agent-cli-provider/contract.d.ts +13 -0
  95. package/lib/agent-cli-provider/contract.d.ts.map +1 -0
  96. package/lib/agent-cli-provider/contract.js +30 -0
  97. package/lib/agent-cli-provider/contract.js.map +1 -0
  98. package/lib/agent-cli-provider/env-safety.d.ts +11 -0
  99. package/lib/agent-cli-provider/env-safety.d.ts.map +1 -0
  100. package/lib/agent-cli-provider/env-safety.js +83 -0
  101. package/lib/agent-cli-provider/env-safety.js.map +1 -0
  102. package/lib/agent-cli-provider/errors.d.ts +5 -0
  103. package/lib/agent-cli-provider/errors.d.ts.map +1 -0
  104. package/lib/agent-cli-provider/errors.js +115 -0
  105. package/lib/agent-cli-provider/errors.js.map +1 -0
  106. package/lib/agent-cli-provider/executable.d.ts +3 -0
  107. package/lib/agent-cli-provider/executable.d.ts.map +1 -0
  108. package/lib/agent-cli-provider/executable.js +24 -0
  109. package/lib/agent-cli-provider/executable.js.map +1 -0
  110. package/lib/agent-cli-provider/index.d.ts +8 -0
  111. package/lib/agent-cli-provider/index.d.ts.map +1 -0
  112. package/lib/agent-cli-provider/index.js +31 -0
  113. package/lib/agent-cli-provider/index.js.map +1 -0
  114. package/lib/agent-cli-provider/invoke-evidence.d.ts +4 -0
  115. package/lib/agent-cli-provider/invoke-evidence.d.ts.map +1 -0
  116. package/lib/agent-cli-provider/invoke-evidence.js +17 -0
  117. package/lib/agent-cli-provider/invoke-evidence.js.map +1 -0
  118. package/lib/agent-cli-provider/json.d.ts +16 -0
  119. package/lib/agent-cli-provider/json.d.ts.map +1 -0
  120. package/lib/agent-cli-provider/json.js +110 -0
  121. package/lib/agent-cli-provider/json.js.map +1 -0
  122. package/lib/agent-cli-provider/log-prefix.d.ts +2 -0
  123. package/lib/agent-cli-provider/log-prefix.d.ts.map +1 -0
  124. package/lib/agent-cli-provider/log-prefix.js +22 -0
  125. package/lib/agent-cli-provider/log-prefix.js.map +1 -0
  126. package/lib/agent-cli-provider/process-runner.d.ts +17 -0
  127. package/lib/agent-cli-provider/process-runner.d.ts.map +1 -0
  128. package/lib/agent-cli-provider/process-runner.js +89 -0
  129. package/lib/agent-cli-provider/process-runner.js.map +1 -0
  130. package/lib/agent-cli-provider/redaction.d.ts +9 -0
  131. package/lib/agent-cli-provider/redaction.d.ts.map +1 -0
  132. package/lib/agent-cli-provider/redaction.js +227 -0
  133. package/lib/agent-cli-provider/redaction.js.map +1 -0
  134. package/lib/agent-cli-provider/schema.d.ts +5 -0
  135. package/lib/agent-cli-provider/schema.d.ts.map +1 -0
  136. package/lib/agent-cli-provider/schema.js +136 -0
  137. package/lib/agent-cli-provider/schema.js.map +1 -0
  138. package/lib/agent-cli-provider/single-agent-runtime.d.ts +15 -0
  139. package/lib/agent-cli-provider/single-agent-runtime.d.ts.map +1 -0
  140. package/lib/agent-cli-provider/single-agent-runtime.js +228 -0
  141. package/lib/agent-cli-provider/single-agent-runtime.js.map +1 -0
  142. package/lib/agent-cli-provider/types.d.ts +194 -0
  143. package/lib/agent-cli-provider/types.d.ts.map +1 -0
  144. package/lib/agent-cli-provider/types.js +12 -0
  145. package/lib/agent-cli-provider/types.js.map +1 -0
  146. package/lib/completion.js +174 -0
  147. package/lib/detached-startup.js +220 -0
  148. package/lib/docker-config.js +220 -0
  149. package/lib/git-remote-utils.js +165 -0
  150. package/lib/id-detector.js +55 -0
  151. package/lib/provider-defaults.js +62 -0
  152. package/lib/provider-detection.js +59 -0
  153. package/lib/provider-names.js +57 -0
  154. package/lib/repo-settings.js +69 -0
  155. package/lib/settings/claude-auth.js +78 -0
  156. package/lib/settings.js +542 -0
  157. package/lib/start-cluster.js +321 -0
  158. package/lib/stream-json-parser.js +67 -0
  159. package/package.json +162 -0
  160. package/scripts/fix-node-pty-permissions.js +75 -0
  161. package/scripts/record-demo.sh +279 -0
  162. package/scripts/setup-merge-queue.sh +170 -0
  163. package/scripts/test-install.sh +40 -0
  164. package/scripts/validate-templates.js +107 -0
  165. package/src/agent/agent-config.js +266 -0
  166. package/src/agent/agent-context-builder.js +189 -0
  167. package/src/agent/agent-context-sections.js +338 -0
  168. package/src/agent/agent-context-sources.js +147 -0
  169. package/src/agent/agent-hook-executor.js +721 -0
  170. package/src/agent/agent-input-injector.js +141 -0
  171. package/src/agent/agent-lifecycle.js +982 -0
  172. package/src/agent/agent-quality-gate-schema.js +93 -0
  173. package/src/agent/agent-quality-gates-context.js +51 -0
  174. package/src/agent/agent-stuck-detector.js +256 -0
  175. package/src/agent/agent-task-executor.js +2028 -0
  176. package/src/agent/agent-trigger-evaluator.js +67 -0
  177. package/src/agent/context-metrics.js +160 -0
  178. package/src/agent/context-pack-builder.js +367 -0
  179. package/src/agent/context-replay-policy.js +51 -0
  180. package/src/agent/guidance-queue.js +77 -0
  181. package/src/agent/output-extraction.js +367 -0
  182. package/src/agent/output-reformatter.js +175 -0
  183. package/src/agent/pr-verification.js +653 -0
  184. package/src/agent/rate-limit-backoff.js +82 -0
  185. package/src/agent/schema-utils.js +146 -0
  186. package/src/agent/validation-platform.js +35 -0
  187. package/src/agent-cli-provider/adapters/claude-parser.ts +133 -0
  188. package/src/agent-cli-provider/adapters/claude-recovery.ts +203 -0
  189. package/src/agent-cli-provider/adapters/claude.ts +247 -0
  190. package/src/agent-cli-provider/adapters/codex-parser.ts +211 -0
  191. package/src/agent-cli-provider/adapters/codex.ts +217 -0
  192. package/src/agent-cli-provider/adapters/common.ts +124 -0
  193. package/src/agent-cli-provider/adapters/gemini.ts +243 -0
  194. package/src/agent-cli-provider/adapters/index.ts +126 -0
  195. package/src/agent-cli-provider/adapters/opencode.ts +286 -0
  196. package/src/agent-cli-provider/contract-actions.ts +150 -0
  197. package/src/agent-cli-provider/contract-env.ts +111 -0
  198. package/src/agent-cli-provider/contract-envelope.ts +110 -0
  199. package/src/agent-cli-provider/contract-errors.ts +66 -0
  200. package/src/agent-cli-provider/contract-fallback.ts +121 -0
  201. package/src/agent-cli-provider/contract-invoke.ts +104 -0
  202. package/src/agent-cli-provider/contract-options.ts +173 -0
  203. package/src/agent-cli-provider/contract-parse.ts +94 -0
  204. package/src/agent-cli-provider/contract-support.ts +167 -0
  205. package/src/agent-cli-provider/contract.ts +56 -0
  206. package/src/agent-cli-provider/env-safety.ts +82 -0
  207. package/src/agent-cli-provider/errors.ts +122 -0
  208. package/src/agent-cli-provider/executable.ts +24 -0
  209. package/src/agent-cli-provider/index.ts +83 -0
  210. package/src/agent-cli-provider/invoke-evidence.ts +18 -0
  211. package/src/agent-cli-provider/json.ts +114 -0
  212. package/src/agent-cli-provider/log-prefix.ts +20 -0
  213. package/src/agent-cli-provider/process-runner.ts +145 -0
  214. package/src/agent-cli-provider/redaction.ts +282 -0
  215. package/src/agent-cli-provider/schema.ts +115 -0
  216. package/src/agent-cli-provider/single-agent-runtime.ts +311 -0
  217. package/src/agent-cli-provider/types.ts +237 -0
  218. package/src/agent-wrapper.js +615 -0
  219. package/src/agents/git-pusher-template.js +705 -0
  220. package/src/attach/attach-client.js +438 -0
  221. package/src/attach/attach-server.js +543 -0
  222. package/src/attach/index.js +37 -0
  223. package/src/attach/protocol.js +220 -0
  224. package/src/attach/ring-buffer.js +121 -0
  225. package/src/attach/send-input.js +88 -0
  226. package/src/attach/socket-discovery.js +267 -0
  227. package/src/claude-task-runner.js +661 -0
  228. package/src/config-router.js +89 -0
  229. package/src/config-validator.js +2202 -0
  230. package/src/copy-worker.js +43 -0
  231. package/src/guidance-topics.js +10 -0
  232. package/src/input-helpers.js +65 -0
  233. package/src/isolation-manager.js +1734 -0
  234. package/src/issue-providers/README.md +305 -0
  235. package/src/issue-providers/azure-devops-provider.js +307 -0
  236. package/src/issue-providers/base-provider.js +232 -0
  237. package/src/issue-providers/github-provider.js +210 -0
  238. package/src/issue-providers/gitlab-provider.js +262 -0
  239. package/src/issue-providers/index.js +196 -0
  240. package/src/issue-providers/jira-provider.js +260 -0
  241. package/src/ledger.js +692 -0
  242. package/src/lib/gc.js +232 -0
  243. package/src/lib/safe-exec.js +88 -0
  244. package/src/logic-engine.js +201 -0
  245. package/src/message-buffer.js +81 -0
  246. package/src/message-bus-bridge.js +144 -0
  247. package/src/message-bus.js +256 -0
  248. package/src/name-generator.js +232 -0
  249. package/src/orchestrator.js +3924 -0
  250. package/src/preflight.js +712 -0
  251. package/src/process-metrics.js +608 -0
  252. package/src/providers/anthropic/index.js +3 -0
  253. package/src/providers/base-provider.js +355 -0
  254. package/src/providers/capabilities.js +60 -0
  255. package/src/providers/google/index.js +3 -0
  256. package/src/providers/index.js +293 -0
  257. package/src/providers/openai/index.js +3 -0
  258. package/src/providers/opencode/index.js +3 -0
  259. package/src/quality-gates.js +143 -0
  260. package/src/schemas/sub-cluster.js +208 -0
  261. package/src/state-snapshot.js +398 -0
  262. package/src/state-snapshotter.js +142 -0
  263. package/src/status-footer.js +1026 -0
  264. package/src/sub-cluster-wrapper.js +693 -0
  265. package/src/task-runner.js +30 -0
  266. package/src/template-resolver.js +425 -0
  267. package/src/template-validation/index.js +338 -0
  268. package/src/template-validation/simulate-consensus-gates.js +324 -0
  269. package/src/template-validation/simulate-random-topology.js +541 -0
  270. package/src/template-validation/simulate-two-stage-validation.js +270 -0
  271. package/src/worktree-claude-config.js +135 -0
  272. package/src/worktree-tooling-env.js +150 -0
  273. package/task-lib/attachable-watcher.js +381 -0
  274. package/task-lib/commands/clean.js +50 -0
  275. package/task-lib/commands/episodes.js +105 -0
  276. package/task-lib/commands/get-log-path.js +23 -0
  277. package/task-lib/commands/kill.js +32 -0
  278. package/task-lib/commands/list.js +105 -0
  279. package/task-lib/commands/logs.js +439 -0
  280. package/task-lib/commands/resume.js +42 -0
  281. package/task-lib/commands/run.js +57 -0
  282. package/task-lib/commands/schedule.js +105 -0
  283. package/task-lib/commands/scheduler-cmd.js +96 -0
  284. package/task-lib/commands/schedules.js +148 -0
  285. package/task-lib/commands/status.js +44 -0
  286. package/task-lib/commands/unschedule.js +16 -0
  287. package/task-lib/completion.js +9 -0
  288. package/task-lib/config.js +11 -0
  289. package/task-lib/name-generator.js +230 -0
  290. package/task-lib/package.json +3 -0
  291. package/task-lib/provider-helper-runtime.js +29 -0
  292. package/task-lib/runner.js +190 -0
  293. package/task-lib/scheduler.js +252 -0
  294. package/task-lib/store.js +529 -0
  295. package/task-lib/watcher.js +305 -0
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Base Provider - Abstract base class for issue providers
3
+ *
4
+ * This class represents a "platform" that can:
5
+ * 1. Fetch issues (all providers)
6
+ * 2. Create PRs/MRs (git-hosting providers only: GitHub, GitLab, Azure DevOps)
7
+ *
8
+ * Defines interface for fetching issues from different sources (GitHub, GitLab, Jira, etc)
9
+ * Each provider must implement:
10
+ * - Static properties: id, displayName
11
+ * - Static methods: detectIdentifier(), getRequiredTool()
12
+ * - Instance method: fetchIssue()
13
+ *
14
+ * Git-hosting providers should also implement:
15
+ * - Static method: supportsPR() returning true
16
+ * - Static method: getPRTool() for PR/MR CLI tool info
17
+ */
18
+
19
+ class IssueProvider {
20
+ /**
21
+ * Provider identifier (e.g., 'github', 'gitlab')
22
+ * @type {string}
23
+ */
24
+ static id = null;
25
+
26
+ /**
27
+ * Human-readable provider name
28
+ * @type {string}
29
+ */
30
+ static displayName = null;
31
+
32
+ /**
33
+ * Whether this provider supports PR/MR creation.
34
+ * Override in subclass to return true for git-hosting platforms.
35
+ * @returns {boolean}
36
+ */
37
+ static supportsPR() {
38
+ return false;
39
+ }
40
+
41
+ /**
42
+ * Get PR/MR CLI tool info for preflight checks.
43
+ * Only git-hosting providers (GitHub, GitLab, Azure DevOps) implement this.
44
+ * @returns {{ name: string, checkCmd: string, installHint: string, displayName: string }|null}
45
+ */
46
+ static getPRTool() {
47
+ return null;
48
+ }
49
+
50
+ /**
51
+ * Detect if input matches this provider's patterns
52
+ * @param {string} _input - User input (URL, issue key, number)
53
+ * @param {Object} _settings - User settings from settings.js
54
+ * @param {Object|null} _gitContext - Auto-detected git remote context
55
+ * @returns {boolean}
56
+ */
57
+ static detectIdentifier(_input, _settings, _gitContext) {
58
+ throw new Error(`${this.name}.detectIdentifier() must be implemented by subclass`);
59
+ }
60
+
61
+ /**
62
+ * Shared detection logic for bare numbers (e.g., "123").
63
+ * Implements the priority cascade:
64
+ * 1. Git context (highest priority) - auto-detected from git remote
65
+ * 2. Settings (defaultIssueSource)
66
+ * 3. Legacy fallback (GitHub only, for backward compatibility)
67
+ *
68
+ * Call this from subclass detectIdentifier() for bare number handling.
69
+ *
70
+ * @param {string} input - User input to check
71
+ * @param {Object} settings - User settings
72
+ * @param {Object|null} gitContext - Git context from detectGitContext()
73
+ * @param {string} providerId - This provider's ID (e.g., 'github', 'gitlab')
74
+ * @param {Object} [options] - Additional options
75
+ * @param {boolean} [options.requiresAdditionalSettings] - If true, check additional settings exist
76
+ * @param {string[]} [options.requiredSettings] - Settings keys that must exist for this provider
77
+ * @returns {boolean} True if this provider should handle the bare number
78
+ */
79
+ static detectBareNumber(input, settings, gitContext, providerId, options = {}) {
80
+ // Only handle bare numbers
81
+ if (!/^\d+$/.test(input)) {
82
+ return false;
83
+ }
84
+
85
+ // 1. Git context takes highest priority
86
+ if (gitContext?.provider) {
87
+ return gitContext.provider === providerId;
88
+ }
89
+
90
+ // 2. Check settings.defaultIssueSource
91
+ if (settings.defaultIssueSource) {
92
+ if (settings.defaultIssueSource !== providerId) {
93
+ return false;
94
+ }
95
+ // If provider requires additional settings, validate them
96
+ if (options.requiredSettings?.length > 0) {
97
+ return options.requiredSettings.every((key) => settings[key]);
98
+ }
99
+ return true;
100
+ }
101
+
102
+ // 3. Legacy fallback - only GitHub gets the fallback for backward compatibility
103
+ return providerId === 'github';
104
+ }
105
+
106
+ /**
107
+ * Shared detection logic for org/repo#123 format.
108
+ * Some providers (GitHub, GitLab) use this format.
109
+ *
110
+ * @param {string} input - User input to check
111
+ * @param {Object} settings - User settings
112
+ * @param {Object|null} gitContext - Git context
113
+ * @param {string} providerId - This provider's ID
114
+ * @returns {boolean} True if this provider should handle the input
115
+ */
116
+ static detectRepoIssueFormat(input, settings, gitContext, providerId) {
117
+ // Match org/repo#123 format
118
+ if (!/^[\w.-]+\/[\w.-]+#\d+$/.test(input)) {
119
+ return false;
120
+ }
121
+
122
+ // 1. Git context takes priority
123
+ if (gitContext?.provider) {
124
+ return gitContext.provider === providerId;
125
+ }
126
+
127
+ // 2. Settings fallback
128
+ return settings.defaultIssueSource === providerId;
129
+ }
130
+
131
+ /**
132
+ * Get required CLI tool info
133
+ * @returns {{ name: string, checkCmd: string, installHint: string }}
134
+ */
135
+ static getRequiredTool() {
136
+ throw new Error(`${this.name}.getRequiredTool() must be implemented by subclass`);
137
+ }
138
+
139
+ /**
140
+ * Check if the provider's CLI is authenticated.
141
+ * Override in subclass to implement provider-specific auth checks.
142
+ *
143
+ * @param {string|null} hostname - Optional hostname for providers with multi-instance support
144
+ * (e.g., GitLab with gitlab.com + self-hosted). When provided, only check auth for that
145
+ * specific instance to avoid false failures from other unconfigured instances.
146
+ * @returns {{ authenticated: boolean, error: string|null, recovery: string[] }}
147
+ * - authenticated: true if auth is valid
148
+ * - error: error message if not authenticated, null otherwise
149
+ * - recovery: array of steps to fix auth issues
150
+ */
151
+ static checkAuth(_hostname = null) {
152
+ // Default: no auth check required (some CLIs like jira use config files)
153
+ return { authenticated: true, error: null, recovery: [] };
154
+ }
155
+
156
+ /**
157
+ * Get settings schema for this provider.
158
+ * Override in subclass to define provider-specific settings.
159
+ *
160
+ * Schema format:
161
+ * {
162
+ * settingKey: {
163
+ * type: 'string' | 'number' | 'boolean',
164
+ * nullable: true | false,
165
+ * default: <default value>,
166
+ * description: 'Human-readable description',
167
+ * pattern?: RegExp, // Optional validation pattern
168
+ * patternMsg?: string // Error message if pattern fails
169
+ * }
170
+ * }
171
+ *
172
+ * @returns {Object.<string, Object>} Settings schema
173
+ */
174
+ static getSettingsSchema() {
175
+ return {};
176
+ }
177
+
178
+ /**
179
+ * Validate a setting value for this provider.
180
+ * Uses schema from getSettingsSchema() by default.
181
+ *
182
+ * @param {string} key - Setting key
183
+ * @param {any} value - Setting value
184
+ * @returns {string|null|undefined} Error message if invalid, null if valid, undefined if not this provider's setting
185
+ */
186
+ static validateSetting(key, value) {
187
+ const schema = this.getSettingsSchema();
188
+ const config = schema[key];
189
+
190
+ if (!config) {
191
+ return undefined; // Not this provider's setting
192
+ }
193
+
194
+ // Nullable check
195
+ if (config.nullable && value === null) {
196
+ return null; // null is valid
197
+ }
198
+
199
+ // Type validation
200
+ if (config.type === 'string' && typeof value !== 'string') {
201
+ return `${key} must be a string${config.nullable ? ' or null' : ''}`;
202
+ }
203
+
204
+ // Pattern validation
205
+ if (config.pattern && typeof value === 'string' && !config.pattern.test(value)) {
206
+ return config.patternMsg || `${key} has invalid format`;
207
+ }
208
+
209
+ return null; // Valid
210
+ }
211
+
212
+ /**
213
+ * Fetch issue from provider
214
+ * @param {string} _identifier - Issue identifier (URL, key, number)
215
+ * @param {Object} _settings - User settings from settings.js
216
+ * @returns {Promise<Object>} InputData object with structure:
217
+ * {
218
+ * number: Number|null,
219
+ * title: String,
220
+ * body: String,
221
+ * labels: Array<{name: String}>,
222
+ * comments: Array<{author, createdAt, body}>,
223
+ * url: String|null,
224
+ * context: String // Formatted markdown
225
+ * }
226
+ */
227
+ fetchIssue(_identifier, _settings) {
228
+ throw new Error(`${this.constructor.name}.fetchIssue() must be implemented by subclass`);
229
+ }
230
+ }
231
+
232
+ module.exports = IssueProvider;
@@ -0,0 +1,210 @@
1
+ /**
2
+ * GitHub Provider - Fetch issues from GitHub via gh CLI
3
+ */
4
+
5
+ const IssueProvider = require('./base-provider');
6
+ const { execSync } = require('../lib/safe-exec');
7
+
8
+ const AUTH_CHECK_TIMEOUT_MS = 2000;
9
+
10
+ class GitHubProvider extends IssueProvider {
11
+ static id = 'github';
12
+ static displayName = 'GitHub';
13
+
14
+ /**
15
+ * GitHub supports PR creation via gh CLI
16
+ */
17
+ static supportsPR() {
18
+ return true;
19
+ }
20
+
21
+ /**
22
+ * Get PR CLI tool info for preflight checks
23
+ */
24
+ static getPRTool() {
25
+ return {
26
+ name: 'gh',
27
+ checkCmd: 'gh --version',
28
+ installHint: 'https://cli.github.com/',
29
+ displayName: 'GitHub',
30
+ };
31
+ }
32
+
33
+ /**
34
+ * Detect GitHub URLs and issue references
35
+ * Matches:
36
+ * - github.com URLs
37
+ * - org/repo#123 format
38
+ * - Bare numbers when git remote is GitHub (auto-detected)
39
+ * - Bare numbers when defaultIssueSource=github
40
+ *
41
+ * @param {string} input - Issue identifier (URL, number, or org/repo#123)
42
+ * @param {Object} settings - User settings
43
+ * @param {Object|null} gitContext - Auto-detected git remote context
44
+ * @returns {boolean} True if this provider should handle the input
45
+ */
46
+ static detectIdentifier(input, settings, gitContext = null) {
47
+ // GitHub URLs
48
+ if (input.includes('github.com') && /\/issues\/\d+/.test(input)) {
49
+ return true;
50
+ }
51
+
52
+ // org/repo#123 format - use shared logic
53
+ if (IssueProvider.detectRepoIssueFormat(input, settings, gitContext, 'github')) {
54
+ return true;
55
+ }
56
+
57
+ // Bare numbers - use shared priority cascade logic
58
+ return IssueProvider.detectBareNumber(input, settings, gitContext, 'github');
59
+ }
60
+
61
+ static getRequiredTool() {
62
+ return {
63
+ name: 'gh',
64
+ checkCmd: 'gh --version',
65
+ installHint: 'Install gh CLI: https://cli.github.com/',
66
+ };
67
+ }
68
+
69
+ /**
70
+ * Check gh CLI authentication
71
+ */
72
+ static checkAuth() {
73
+ try {
74
+ execSync('gh auth status', {
75
+ encoding: 'utf8',
76
+ stdio: 'pipe',
77
+ timeout: AUTH_CHECK_TIMEOUT_MS,
78
+ });
79
+ return { authenticated: true, error: null, recovery: [] };
80
+ } catch (err) {
81
+ const stderr = err.stderr || err.message || '';
82
+
83
+ if (err.code === 'ENOENT' || stderr.includes('command not found')) {
84
+ return {
85
+ authenticated: false,
86
+ error: 'gh CLI not installed',
87
+ recovery: ['Install gh: https://cli.github.com/', 'Then verify: gh --version'],
88
+ };
89
+ }
90
+
91
+ if (stderr.includes('Command timed out')) {
92
+ return {
93
+ authenticated: false,
94
+ error: 'gh auth status timed out',
95
+ recovery: ['Retry: gh auth status', 'If it still hangs, re-run: gh auth login'],
96
+ };
97
+ }
98
+
99
+ if (stderr.includes('not logged in')) {
100
+ return {
101
+ authenticated: false,
102
+ error: 'gh CLI not authenticated',
103
+ recovery: [
104
+ 'Run: gh auth login',
105
+ 'Select GitHub.com, HTTPS, and authenticate via browser',
106
+ 'Then verify: gh auth status',
107
+ ],
108
+ };
109
+ }
110
+
111
+ return {
112
+ authenticated: false,
113
+ error: stderr.trim() || 'Unknown gh auth error',
114
+ recovery: ['Run: gh auth login', 'Then verify: gh auth status'],
115
+ };
116
+ }
117
+ }
118
+
119
+ /**
120
+ * GitHub-specific settings schema
121
+ * GitHub has no extra settings (cloud-only, no self-hosted config needed)
122
+ */
123
+ static getSettingsSchema() {
124
+ return {};
125
+ }
126
+
127
+ fetchIssue(identifier, _settings, gitContext = null) {
128
+ try {
129
+ const { repo, number } = this._parseIdentifier(identifier, gitContext);
130
+
131
+ // ALWAYS use -R flag when repo is known - never rely on CWD git remote
132
+ const repoFlag = repo ? `-R ${repo}` : '';
133
+ const cmd = `gh issue view ${number} ${repoFlag} --json number,title,body,labels,assignees,comments,url`;
134
+ const output = execSync(cmd, { encoding: 'utf8' });
135
+ const issue = JSON.parse(output);
136
+
137
+ return this._parseIssue(issue);
138
+ } catch (error) {
139
+ throw new Error(`Failed to fetch GitHub issue: ${error.message}`);
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Parse identifier into repo and issue number
145
+ * @private
146
+ * @returns {{ repo: string|null, number: string }}
147
+ */
148
+ _parseIdentifier(identifier, gitContext = null) {
149
+ // GitHub URL: https://github.com/org/repo/issues/123
150
+ const urlMatch = identifier.match(/github\.com\/([^/]+\/[^/]+)\/issues\/(\d+)/);
151
+ if (urlMatch) {
152
+ return { repo: urlMatch[1], number: urlMatch[2] };
153
+ }
154
+
155
+ // org/repo#123 format
156
+ const repoMatch = identifier.match(/^([\w.-]+\/[\w.-]+)#(\d+)$/);
157
+ if (repoMatch) {
158
+ return { repo: repoMatch[1], number: repoMatch[2] };
159
+ }
160
+
161
+ // Bare number - use gitContext if available
162
+ if (/^\d+$/.test(identifier)) {
163
+ const repo =
164
+ gitContext?.owner && gitContext?.repo ? `${gitContext.owner}/${gitContext.repo}` : null;
165
+ return { repo, number: identifier };
166
+ }
167
+
168
+ // Fallback: assume it's a number, no repo
169
+ return { repo: null, number: identifier };
170
+ }
171
+
172
+ /**
173
+ * Parse issue into standardized InputData format
174
+ * @private
175
+ */
176
+ _parseIssue(issue) {
177
+ let context = `# GitHub Issue #${issue.number}\n\n`;
178
+ context += `## Title\n${issue.title}\n\n`;
179
+
180
+ if (issue.body) {
181
+ context += `## Description\n${issue.body}\n\n`;
182
+ }
183
+
184
+ if (issue.labels && issue.labels.length > 0) {
185
+ context += `## Labels\n`;
186
+ context += issue.labels.map((l) => `- ${l.name}`).join('\n');
187
+ context += '\n\n';
188
+ }
189
+
190
+ if (issue.comments && issue.comments.length > 0) {
191
+ context += `## Comments\n\n`;
192
+ for (const comment of issue.comments) {
193
+ context += `### ${comment.author.login} (${new Date(comment.createdAt).toISOString()})\n`;
194
+ context += `${comment.body}\n\n`;
195
+ }
196
+ }
197
+
198
+ return {
199
+ number: issue.number,
200
+ title: issue.title,
201
+ body: issue.body,
202
+ labels: issue.labels || [],
203
+ comments: issue.comments || [],
204
+ url: issue.url || null,
205
+ context,
206
+ };
207
+ }
208
+ }
209
+
210
+ module.exports = GitHubProvider;
@@ -0,0 +1,262 @@
1
+ /**
2
+ * GitLab Provider - Fetch issues from GitLab via glab CLI
3
+ * Supports both cloud (gitlab.com) and self-hosted instances
4
+ */
5
+
6
+ const IssueProvider = require('./base-provider');
7
+ const { execSync } = require('../lib/safe-exec');
8
+
9
+ const AUTH_CHECK_TIMEOUT_MS = 2000;
10
+
11
+ class GitLabProvider extends IssueProvider {
12
+ static id = 'gitlab';
13
+ static displayName = 'GitLab';
14
+
15
+ /**
16
+ * GitLab supports MR creation via glab CLI
17
+ */
18
+ static supportsPR() {
19
+ return true;
20
+ }
21
+
22
+ /**
23
+ * Get MR CLI tool info for preflight checks
24
+ */
25
+ static getPRTool() {
26
+ return {
27
+ name: 'glab',
28
+ checkCmd: 'glab --version',
29
+ installHint: 'brew install glab (or https://gitlab.com/gitlab-org/cli)',
30
+ displayName: 'GitLab',
31
+ };
32
+ }
33
+
34
+ /**
35
+ * Detect GitLab URLs and issue references
36
+ * Matches:
37
+ * - gitlab.com URLs
38
+ * - Self-hosted GitLab URLs (via gitlabInstance setting)
39
+ * - org/repo#123 when git remote is GitLab (auto-detected)
40
+ * - org/repo#123 when defaultIssueSource=gitlab
41
+ * - Bare numbers when git remote is GitLab (auto-detected)
42
+ * - Bare numbers when defaultIssueSource=gitlab
43
+ *
44
+ * @param {string} input - Issue identifier (URL, number, or org/repo#123)
45
+ * @param {Object} settings - User settings
46
+ * @param {Object|null} gitContext - Auto-detected git remote context
47
+ * @returns {boolean} True if this provider should handle the input
48
+ */
49
+ static detectIdentifier(input, settings, gitContext = null) {
50
+ // GitLab cloud URLs
51
+ if (input.includes('gitlab.com') && /\/-\/issues\/\d+/.test(input)) {
52
+ return true;
53
+ }
54
+
55
+ // Self-hosted GitLab URLs
56
+ if (
57
+ settings.gitlabInstance &&
58
+ input.includes(settings.gitlabInstance) &&
59
+ /\/-\/issues\/\d+/.test(input)
60
+ ) {
61
+ return true;
62
+ }
63
+
64
+ // org/repo#123 format - use shared logic
65
+ if (IssueProvider.detectRepoIssueFormat(input, settings, gitContext, 'gitlab')) {
66
+ return true;
67
+ }
68
+
69
+ // Bare numbers - use shared priority cascade logic
70
+ return IssueProvider.detectBareNumber(input, settings, gitContext, 'gitlab');
71
+ }
72
+
73
+ static getRequiredTool() {
74
+ return {
75
+ name: 'glab',
76
+ checkCmd: 'glab --version',
77
+ installHint: 'Install glab CLI: brew install glab (or https://gitlab.com/gitlab-org/cli)',
78
+ };
79
+ }
80
+
81
+ /**
82
+ * Check glab CLI authentication for a specific hostname.
83
+ * If hostname is provided, only checks that specific instance.
84
+ * Otherwise, uses glab's default context detection (git remote).
85
+ *
86
+ * @param {string|null} hostname - GitLab hostname to check (e.g., 'gitlab.com', 'gitlab.lrz.de')
87
+ */
88
+ static checkAuth(hostname = null) {
89
+ // Use --hostname flag to check specific instance, avoiding false failures
90
+ // when other configured instances are unauthenticated
91
+ const cmd = hostname ? `glab auth status --hostname ${hostname}` : 'glab auth status';
92
+
93
+ try {
94
+ execSync(cmd, { encoding: 'utf8', stdio: 'pipe', timeout: AUTH_CHECK_TIMEOUT_MS });
95
+ return { authenticated: true, error: null, recovery: [] };
96
+ } catch (err) {
97
+ const stderr = err.stderr || err.message || '';
98
+
99
+ if (err.code === 'ENOENT' || stderr.includes('command not found')) {
100
+ return {
101
+ authenticated: false,
102
+ error: 'glab CLI not installed',
103
+ recovery: [
104
+ 'Install glab: https://gitlab.com/gitlab-org/cli',
105
+ 'Then verify: glab --version',
106
+ ],
107
+ };
108
+ }
109
+
110
+ if (stderr.includes('Command timed out')) {
111
+ return {
112
+ authenticated: false,
113
+ error: 'glab auth status timed out',
114
+ recovery: ['Retry: glab auth status', 'If it still hangs, run: glab auth login'],
115
+ };
116
+ }
117
+
118
+ const hostnameHint = hostname || 'your GitLab instance';
119
+ if (
120
+ stderr.includes('not logged in') ||
121
+ stderr.includes('no authentication') ||
122
+ stderr.includes('No token found')
123
+ ) {
124
+ return {
125
+ authenticated: false,
126
+ error: `glab CLI not authenticated for ${hostnameHint}`,
127
+ recovery: [
128
+ hostname ? `Run: glab auth login --hostname ${hostname}` : 'Run: glab auth login',
129
+ 'Authenticate via browser when prompted',
130
+ `Then verify: glab auth status${hostname ? ` --hostname ${hostname}` : ''}`,
131
+ ],
132
+ };
133
+ }
134
+
135
+ return {
136
+ authenticated: false,
137
+ error: stderr.trim() || 'Unknown glab auth error',
138
+ recovery: [
139
+ hostname ? `Run: glab auth login --hostname ${hostname}` : 'Run: glab auth login',
140
+ 'Then verify: glab auth status',
141
+ ],
142
+ };
143
+ }
144
+ }
145
+
146
+ /**
147
+ * GitLab-specific settings schema
148
+ */
149
+ static getSettingsSchema() {
150
+ return {
151
+ gitlabInstance: {
152
+ type: 'string',
153
+ nullable: true,
154
+ default: null,
155
+ description: "Self-hosted GitLab URL (e.g., 'gitlab.company.com')",
156
+ },
157
+ };
158
+ }
159
+
160
+ fetchIssue(identifier, _settings) {
161
+ try {
162
+ // glab accepts full URLs directly - use that for simplicity
163
+ const isUrl = /^https?:\/\//.test(identifier);
164
+ let cmd;
165
+
166
+ if (isUrl) {
167
+ // Pass URL directly to glab - handles self-hosted instances automatically
168
+ cmd = `glab issue view "${identifier}" --output json`;
169
+ } else {
170
+ // For org/repo#123 or bare numbers, parse and build command
171
+ const { issueNumber, repo } = this._parseIdentifier(identifier);
172
+ cmd = `glab issue view ${issueNumber} --output json`;
173
+ if (repo) {
174
+ cmd += ` --repo ${repo}`;
175
+ }
176
+ }
177
+
178
+ const output = execSync(cmd, { encoding: 'utf8' });
179
+ const issue = JSON.parse(output);
180
+
181
+ return this._parseIssue(issue);
182
+ } catch (error) {
183
+ throw new Error(`Failed to fetch GitLab issue: ${error.message}`);
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Parse identifier to extract issue number, repo, and hostname
189
+ * @private
190
+ */
191
+ _parseIdentifier(identifier) {
192
+ // URL format: https://gitlab.example.com/org/repo/-/issues/123
193
+ const urlMatch = identifier.match(/https?:\/\/([^/]+)\/([^/]+\/[^/]+)\/-\/issues\/(\d+)/);
194
+ if (urlMatch) {
195
+ const hostname = urlMatch[1];
196
+ return {
197
+ hostname: hostname !== 'gitlab.com' ? hostname : null,
198
+ repo: urlMatch[2],
199
+ issueNumber: urlMatch[3],
200
+ };
201
+ }
202
+
203
+ // org/repo#123 format
204
+ const repoMatch = identifier.match(/^([\w-]+\/[\w-]+)#(\d+)$/);
205
+ if (repoMatch) {
206
+ return { hostname: null, repo: repoMatch[1], issueNumber: repoMatch[2] };
207
+ }
208
+
209
+ // Bare number (relies on glab's default repo)
210
+ return { hostname: null, repo: null, issueNumber: identifier };
211
+ }
212
+
213
+ /**
214
+ * Parse GitLab issue into standardized InputData format
215
+ * @private
216
+ */
217
+ _parseIssue(issue) {
218
+ let context = `# GitLab Issue #${issue.iid || issue.id}\n\n`;
219
+ context += `## Title\n${issue.title}\n\n`;
220
+
221
+ if (issue.description) {
222
+ context += `## Description\n${issue.description}\n\n`;
223
+ }
224
+
225
+ if (issue.labels && issue.labels.length > 0) {
226
+ context += `## Labels\n`;
227
+ context += issue.labels.map((l) => `- ${l}`).join('\n');
228
+ context += '\n\n';
229
+ }
230
+
231
+ if (issue.notes && issue.notes.length > 0) {
232
+ context += `## Comments\n\n`;
233
+ for (const note of issue.notes) {
234
+ const author = note.author?.username || 'unknown';
235
+ context += `### ${author} (${note.created_at})\n`;
236
+ context += `${note.body}\n\n`;
237
+ }
238
+ }
239
+
240
+ // Map GitLab labels to GitHub format
241
+ const labels = (issue.labels || []).map((name) => ({ name }));
242
+
243
+ // Map GitLab notes to GitHub comment format
244
+ const comments = (issue.notes || []).map((note) => ({
245
+ author: { login: note.author?.username || 'unknown' },
246
+ createdAt: note.created_at,
247
+ body: note.body,
248
+ }));
249
+
250
+ return {
251
+ number: issue.iid || issue.id || null,
252
+ title: issue.title,
253
+ body: issue.description || '',
254
+ labels,
255
+ comments,
256
+ url: issue.web_url || null,
257
+ context,
258
+ };
259
+ }
260
+ }
261
+
262
+ module.exports = GitLabProvider;