@covibes/zeroshot 5.4.0 → 5.4.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 (136) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +8 -465
  3. package/cli/index.js +101 -5231
  4. package/package.json +16 -130
  5. package/CHANGELOG.md +0 -537
  6. package/cli/commands/providers.js +0 -149
  7. package/cli/lib/first-run.js +0 -211
  8. package/cli/lib/update-checker.js +0 -281
  9. package/cli/message-formatter-utils.js +0 -75
  10. package/cli/message-formatters-normal.js +0 -275
  11. package/cli/message-formatters-watch.js +0 -181
  12. package/cluster-templates/base-templates/debug-workflow.json +0 -375
  13. package/cluster-templates/base-templates/full-workflow.json +0 -581
  14. package/cluster-templates/base-templates/single-worker.json +0 -63
  15. package/cluster-templates/base-templates/worker-validator.json +0 -208
  16. package/cluster-templates/conductor-bootstrap.json +0 -124
  17. package/docker/zeroshot-cluster/Dockerfile +0 -152
  18. package/docker/zeroshot-cluster/pre-baked-deps.json +0 -28
  19. package/lib/completion.js +0 -174
  20. package/lib/docker-config.js +0 -220
  21. package/lib/git-remote-utils.js +0 -165
  22. package/lib/id-detector.js +0 -56
  23. package/lib/provider-defaults.js +0 -62
  24. package/lib/provider-detection.js +0 -59
  25. package/lib/provider-names.js +0 -57
  26. package/lib/settings/claude-auth.js +0 -78
  27. package/lib/settings.js +0 -503
  28. package/lib/stream-json-parser.js +0 -10
  29. package/scripts/fix-node-pty-permissions.js +0 -75
  30. package/scripts/record-demo.sh +0 -279
  31. package/scripts/setup-merge-queue.sh +0 -170
  32. package/scripts/test-install.sh +0 -40
  33. package/scripts/validate-templates.js +0 -100
  34. package/src/agent/agent-config.js +0 -246
  35. package/src/agent/agent-context-builder.js +0 -444
  36. package/src/agent/agent-hook-executor.js +0 -599
  37. package/src/agent/agent-lifecycle.js +0 -743
  38. package/src/agent/agent-stuck-detector.js +0 -256
  39. package/src/agent/agent-task-executor.js +0 -1690
  40. package/src/agent/agent-trigger-evaluator.js +0 -67
  41. package/src/agent/output-extraction.js +0 -217
  42. package/src/agent/output-reformatter.js +0 -175
  43. package/src/agent/schema-utils.js +0 -146
  44. package/src/agent-wrapper.js +0 -579
  45. package/src/agents/git-pusher-template.js +0 -285
  46. package/src/attach/attach-client.js +0 -438
  47. package/src/attach/attach-server.js +0 -543
  48. package/src/attach/index.js +0 -35
  49. package/src/attach/protocol.js +0 -220
  50. package/src/attach/ring-buffer.js +0 -121
  51. package/src/attach/socket-discovery.js +0 -267
  52. package/src/claude-task-runner.js +0 -574
  53. package/src/config-router.js +0 -80
  54. package/src/config-validator.js +0 -1925
  55. package/src/copy-worker.js +0 -43
  56. package/src/input-helpers.js +0 -65
  57. package/src/isolation-manager.js +0 -1471
  58. package/src/issue-providers/README.md +0 -305
  59. package/src/issue-providers/azure-devops-provider.js +0 -273
  60. package/src/issue-providers/base-provider.js +0 -232
  61. package/src/issue-providers/github-provider.js +0 -179
  62. package/src/issue-providers/gitlab-provider.js +0 -241
  63. package/src/issue-providers/index.js +0 -196
  64. package/src/issue-providers/jira-provider.js +0 -239
  65. package/src/ledger.js +0 -631
  66. package/src/lib/safe-exec.js +0 -88
  67. package/src/logic-engine.js +0 -223
  68. package/src/message-bus-bridge.js +0 -139
  69. package/src/message-bus.js +0 -249
  70. package/src/name-generator.js +0 -232
  71. package/src/orchestrator.js +0 -2815
  72. package/src/preflight.js +0 -639
  73. package/src/process-metrics.js +0 -608
  74. package/src/providers/anthropic/cli-builder.js +0 -73
  75. package/src/providers/anthropic/index.js +0 -204
  76. package/src/providers/anthropic/models.js +0 -23
  77. package/src/providers/anthropic/output-parser.js +0 -177
  78. package/src/providers/base-provider.js +0 -251
  79. package/src/providers/capabilities.js +0 -60
  80. package/src/providers/google/cli-builder.js +0 -55
  81. package/src/providers/google/index.js +0 -116
  82. package/src/providers/google/models.js +0 -24
  83. package/src/providers/google/output-parser.js +0 -101
  84. package/src/providers/index.js +0 -91
  85. package/src/providers/openai/cli-builder.js +0 -133
  86. package/src/providers/openai/index.js +0 -136
  87. package/src/providers/openai/models.js +0 -21
  88. package/src/providers/openai/output-parser.js +0 -143
  89. package/src/providers/opencode/cli-builder.js +0 -42
  90. package/src/providers/opencode/index.js +0 -103
  91. package/src/providers/opencode/models.js +0 -55
  92. package/src/providers/opencode/output-parser.js +0 -122
  93. package/src/schemas/sub-cluster.js +0 -185
  94. package/src/status-footer.js +0 -1026
  95. package/src/sub-cluster-wrapper.js +0 -601
  96. package/src/task-runner.js +0 -30
  97. package/src/template-resolver.js +0 -372
  98. package/src/tui/CHANGES.txt +0 -133
  99. package/src/tui/LAYOUT.md +0 -261
  100. package/src/tui/README.txt +0 -192
  101. package/src/tui/TWO-LEVEL-NAVIGATION.md +0 -186
  102. package/src/tui/data-poller.js +0 -349
  103. package/src/tui/demo.js +0 -208
  104. package/src/tui/formatters.js +0 -129
  105. package/src/tui/index.js +0 -193
  106. package/src/tui/keybindings.js +0 -324
  107. package/src/tui/layout.js +0 -334
  108. package/src/tui/renderer.js +0 -184
  109. package/task-lib/attachable-watcher.js +0 -309
  110. package/task-lib/claude-recovery.js +0 -156
  111. package/task-lib/commands/clean.js +0 -50
  112. package/task-lib/commands/episodes.js +0 -105
  113. package/task-lib/commands/get-log-path.js +0 -23
  114. package/task-lib/commands/kill.js +0 -32
  115. package/task-lib/commands/list.js +0 -105
  116. package/task-lib/commands/logs.js +0 -453
  117. package/task-lib/commands/resume.js +0 -42
  118. package/task-lib/commands/run.js +0 -57
  119. package/task-lib/commands/schedule.js +0 -105
  120. package/task-lib/commands/scheduler-cmd.js +0 -96
  121. package/task-lib/commands/schedules.js +0 -148
  122. package/task-lib/commands/status.js +0 -44
  123. package/task-lib/commands/unschedule.js +0 -16
  124. package/task-lib/completion.js +0 -9
  125. package/task-lib/config.js +0 -8
  126. package/task-lib/name-generator.js +0 -230
  127. package/task-lib/package.json +0 -3
  128. package/task-lib/runner.js +0 -201
  129. package/task-lib/scheduler.js +0 -252
  130. package/task-lib/store.js +0 -529
  131. package/task-lib/tui/formatters.js +0 -215
  132. package/task-lib/tui/index.js +0 -197
  133. package/task-lib/tui/layout.js +0 -111
  134. package/task-lib/tui/renderer.js +0 -129
  135. package/task-lib/tui.js +0 -425
  136. package/task-lib/watcher.js +0 -226
package/lib/completion.js DELETED
@@ -1,174 +0,0 @@
1
- const omelette = require('omelette');
2
- const fs = require('fs');
3
- const path = require('path');
4
-
5
- function setupCompletion() {
6
- const complete = omelette('zeroshot');
7
-
8
- complete.on('start', ({ reply }) => {
9
- reply(['--issue', '--text', '--config']);
10
- });
11
-
12
- complete.on('list', ({ reply }) => {
13
- reply([]);
14
- });
15
-
16
- complete.on('status', ({ reply }) => {
17
- // Complete with cluster IDs
18
- try {
19
- const clustersDir = path.join(process.env.HOME, '.zeroshot', 'clusters');
20
- if (fs.existsSync(clustersDir)) {
21
- const clusterIds = fs
22
- .readdirSync(clustersDir)
23
- .filter((f) => f.endsWith('.json'))
24
- .map((f) => f.replace('.json', ''));
25
- reply(clusterIds);
26
- } else {
27
- reply([]);
28
- }
29
- } catch {
30
- reply([]);
31
- }
32
- });
33
-
34
- complete.on('logs', ({ reply }) => {
35
- // Complete with cluster IDs or flags
36
- try {
37
- const clustersDir = path.join(process.env.HOME, '.zeroshot', 'clusters');
38
- const completions = ['--follow', '-f', '--limit', '-n'];
39
-
40
- if (fs.existsSync(clustersDir)) {
41
- const clusterIds = fs
42
- .readdirSync(clustersDir)
43
- .filter((f) => f.endsWith('.json'))
44
- .map((f) => f.replace('.json', ''));
45
- reply([...clusterIds, ...completions]);
46
- } else {
47
- reply(completions);
48
- }
49
- } catch {
50
- reply(['--follow', '-f', '--limit', '-n']);
51
- }
52
- });
53
-
54
- complete.on('stop', ({ reply }) => {
55
- // Complete with active cluster IDs
56
- try {
57
- const clustersDir = path.join(process.env.HOME, '.zeroshot', 'clusters');
58
- if (fs.existsSync(clustersDir)) {
59
- const clusterIds = fs
60
- .readdirSync(clustersDir)
61
- .filter((f) => f.endsWith('.json'))
62
- .map((f) => f.replace('.json', ''));
63
- reply(clusterIds);
64
- } else {
65
- reply([]);
66
- }
67
- } catch {
68
- reply([]);
69
- }
70
- });
71
-
72
- complete.on('kill', ({ reply }) => {
73
- // Complete with active cluster IDs
74
- try {
75
- const clustersDir = path.join(process.env.HOME, '.zeroshot', 'clusters');
76
- if (fs.existsSync(clustersDir)) {
77
- const clusterIds = fs
78
- .readdirSync(clustersDir)
79
- .filter((f) => f.endsWith('.json'))
80
- .map((f) => f.replace('.json', ''));
81
- reply(clusterIds);
82
- } else {
83
- reply([]);
84
- }
85
- } catch {
86
- reply([]);
87
- }
88
- });
89
-
90
- complete.on('export', ({ reply }) => {
91
- // Complete with cluster IDs
92
- try {
93
- const clustersDir = path.join(process.env.HOME, '.zeroshot', 'clusters');
94
- if (fs.existsSync(clustersDir)) {
95
- const clusterIds = fs
96
- .readdirSync(clustersDir)
97
- .filter((f) => f.endsWith('.json'))
98
- .map((f) => f.replace('.json', ''));
99
- reply(clusterIds);
100
- } else {
101
- reply([]);
102
- }
103
- } catch {
104
- reply([]);
105
- }
106
- });
107
-
108
- complete.on('finish', ({ reply }) => {
109
- // Complete with cluster IDs and flags
110
- try {
111
- const clustersDir = path.join(process.env.HOME, '.zeroshot', 'clusters');
112
- const completions = ['--merge', '--pr', '--push'];
113
-
114
- if (fs.existsSync(clustersDir)) {
115
- const clusterIds = fs
116
- .readdirSync(clustersDir)
117
- .filter((f) => f.endsWith('.json'))
118
- .map((f) => f.replace('.json', ''));
119
- reply([...clusterIds, ...completions]);
120
- } else {
121
- reply(completions);
122
- }
123
- } catch {
124
- reply(['--merge', '--pr', '--push']);
125
- }
126
- });
127
-
128
- complete.on('resume', ({ reply }) => {
129
- // Complete with cluster IDs
130
- try {
131
- const clustersDir = path.join(process.env.HOME, '.zeroshot', 'clusters');
132
- if (fs.existsSync(clustersDir)) {
133
- const clusterIds = fs
134
- .readdirSync(clustersDir)
135
- .filter((f) => f.endsWith('.json'))
136
- .map((f) => f.replace('.json', ''));
137
- reply(clusterIds);
138
- } else {
139
- reply([]);
140
- }
141
- } catch {
142
- reply([]);
143
- }
144
- });
145
-
146
- complete.on('ui', ({ reply }) => {
147
- reply(['--port']);
148
- });
149
-
150
- complete.on('watch', ({ reply }) => {
151
- reply(['--filter', '--refresh-rate', 'running', 'stopped', 'all']);
152
- });
153
-
154
- // Default completion - show commands
155
- complete.on('', ({ reply }) => {
156
- reply([
157
- 'start',
158
- 'list',
159
- 'status',
160
- 'logs',
161
- 'stop',
162
- 'kill',
163
- 'finish',
164
- 'resume',
165
- 'export',
166
- 'ui',
167
- 'watch',
168
- ]);
169
- });
170
-
171
- complete.init();
172
- }
173
-
174
- module.exports = { setupCompletion };
@@ -1,220 +0,0 @@
1
- /**
2
- * Docker mount configuration for isolation mode
3
- * Fully generic - no hardcoded paths, all configurable
4
- */
5
-
6
- /**
7
- * Built-in mount presets
8
- * Uses $HOME placeholder - resolved at runtime based on dockerContainerHome setting
9
- * Host paths use ~ (expanded to host user's home)
10
- * Container paths use $HOME (expanded to configured container home)
11
- */
12
- const MOUNT_PRESETS = {
13
- gh: { host: '~/.config/gh', container: '$HOME/.config/gh', readonly: false },
14
- git: { host: '~/.gitconfig', container: '$HOME/.gitconfig', readonly: true },
15
- ssh: { host: '~/.ssh', container: '$HOME/.ssh', readonly: true },
16
- aws: { host: '~/.aws', container: '$HOME/.aws', readonly: true },
17
- azure: { host: '~/.azure', container: '$HOME/.azure', readonly: true },
18
- kube: { host: '~/.kube', container: '$HOME/.kube', readonly: true },
19
- terraform: { host: '~/.terraform.d', container: '$HOME/.terraform.d', readonly: false },
20
- gcloud: { host: '~/.config/gcloud', container: '$HOME/.config/gcloud', readonly: true },
21
- claude: { host: '~/.claude', container: '$HOME/.claude', readonly: true },
22
- codex: { host: '~/.config/codex', container: '$HOME/.config/codex', readonly: true },
23
- gemini: { host: '~/.config/gemini', container: '$HOME/.config/gemini', readonly: true },
24
- opencode: {
25
- host: '~/.local/share/opencode',
26
- container: '$HOME/.local/share/opencode',
27
- readonly: true,
28
- },
29
- };
30
-
31
- /**
32
- * Environment variables to auto-pass for each preset
33
- * Supports:
34
- * - Simple: 'VAR_NAME' (pass if set in host env)
35
- * - Pattern: 'VAR_*' (pass all matching vars)
36
- * - Forced: 'VAR=value' (always set to value)
37
- * - Empty: 'VAR=' (always set to empty string)
38
- */
39
- const { CLAUDE_AUTH_ENV_VARS } = require('./settings/claude-auth');
40
-
41
- const ENV_PRESETS = {
42
- aws: ['AWS_REGION', 'AWS_DEFAULT_REGION', 'AWS_PROFILE', 'AWS_PAGER='],
43
- azure: ['AZURE_SUBSCRIPTION_ID', 'AZURE_TENANT_ID', 'AZURE_CLIENT_ID'],
44
- gcloud: ['CLOUDSDK_CORE_PROJECT', 'GOOGLE_CLOUD_PROJECT'],
45
- kube: ['KUBECONFIG'],
46
- terraform: ['TF_VAR_*'],
47
- claude: CLAUDE_AUTH_ENV_VARS,
48
- };
49
-
50
- /**
51
- * Resolve mount config to actual mount specs
52
- * @param {Array<string|object>} config - Preset names or {host, container, readonly?} objects
53
- * @param {object} options - Resolution options
54
- * @param {string} [options.containerHome='/root'] - Container home directory for $HOME expansion
55
- * @returns {Array<{host: string, container: string, readonly: boolean}>}
56
- */
57
- function resolveMounts(config, options = {}) {
58
- if (!Array.isArray(config)) {
59
- throw new Error('dockerMounts must be an array');
60
- }
61
-
62
- const containerHome = options.containerHome || '/root';
63
-
64
- return config.flatMap((item) => {
65
- if (typeof item === 'string') {
66
- const preset = MOUNT_PRESETS[item];
67
- if (!preset) {
68
- throw new Error(
69
- `Unknown mount preset: "${item}". Valid presets: ${Object.keys(MOUNT_PRESETS).join(', ')}`
70
- );
71
- }
72
- return {
73
- host: preset.host,
74
- container: preset.container.replace(/\$HOME/g, containerHome),
75
- readonly: preset.readonly,
76
- };
77
- }
78
-
79
- if (typeof item === 'object' && item !== null) {
80
- if (!item.host || !item.container) {
81
- throw new Error('Custom mount must have "host" and "container" properties');
82
- }
83
- return {
84
- host: item.host,
85
- container: item.container.replace(/\$HOME/g, containerHome),
86
- readonly: item.readonly !== false,
87
- };
88
- }
89
-
90
- throw new Error(
91
- `Invalid mount config: ${JSON.stringify(item)}. Use preset name or {host, container, readonly?}`
92
- );
93
- });
94
- }
95
-
96
- /**
97
- * Resolve env vars to pass based on enabled presets + explicit additions
98
- * @param {Array<string|object>} mountConfig - The mount config (to detect presets)
99
- * @param {Array<string>} extraEnvs - Additional env vars to pass
100
- * @returns {Array<string>} - List of env var specs
101
- */
102
- function resolveEnvs(mountConfig, extraEnvs = []) {
103
- const envs = new Set(extraEnvs);
104
-
105
- for (const item of mountConfig) {
106
- if (typeof item === 'string' && ENV_PRESETS[item]) {
107
- for (const envVar of ENV_PRESETS[item]) {
108
- envs.add(envVar);
109
- }
110
- }
111
- }
112
-
113
- return [...envs];
114
- }
115
-
116
- /**
117
- * Expand env var patterns and resolve values
118
- * Supports:
119
- * - Simple: 'VAR_NAME' (use value from env if set)
120
- * - Pattern: 'VAR_*' (expand to all matching vars)
121
- * - Forced: 'VAR=value' (always set to value)
122
- * - Empty: 'VAR=' (always set to empty string)
123
- *
124
- * @param {Array<string>} envVars - List of env var specs
125
- * @param {object} env - Environment object (defaults to process.env)
126
- * @returns {Array<{name: string, value: string|null, forced: boolean}>}
127
- */
128
- function expandEnvPatterns(envVars, env = process.env) {
129
- const result = [];
130
-
131
- for (const envVar of envVars) {
132
- // Forced value: VAR=value or VAR=
133
- if (envVar.includes('=')) {
134
- const [name, ...valueParts] = envVar.split('=');
135
- const value = valueParts.join('=');
136
- result.push({ name, value, forced: true });
137
- }
138
- // Pattern: VAR_*
139
- else if (envVar.endsWith('*')) {
140
- const prefix = envVar.slice(0, -1);
141
- for (const key of Object.keys(env)) {
142
- if (key.startsWith(prefix)) {
143
- result.push({ name: key, value: null, forced: false });
144
- }
145
- }
146
- }
147
- // Simple: VAR_NAME
148
- else {
149
- result.push({ name: envVar, value: null, forced: false });
150
- }
151
- }
152
-
153
- return result;
154
- }
155
-
156
- /**
157
- * Validate mount config
158
- * @param {unknown} value - Value to validate
159
- * @returns {string|null} - Error message if invalid, null if valid
160
- */
161
- function validateMountConfig(value) {
162
- if (!Array.isArray(value)) {
163
- return 'dockerMounts must be an array';
164
- }
165
-
166
- for (const item of value) {
167
- if (typeof item === 'string') {
168
- if (!MOUNT_PRESETS[item]) {
169
- return `Unknown mount preset: "${item}". Valid: ${Object.keys(MOUNT_PRESETS).join(', ')}`;
170
- }
171
- } else if (typeof item === 'object' && item !== null) {
172
- if (!item.host) {
173
- return 'Custom mount missing "host" property';
174
- }
175
- if (!item.container) {
176
- return 'Custom mount missing "container" property';
177
- }
178
- if (item.readonly !== undefined && typeof item.readonly !== 'boolean') {
179
- return '"readonly" must be a boolean';
180
- }
181
- } else {
182
- return `Invalid mount: ${JSON.stringify(item)}. Use preset name or {host, container, readonly?}`;
183
- }
184
- }
185
-
186
- return null;
187
- }
188
-
189
- /**
190
- * Validate env passthrough config
191
- * @param {unknown} value - Value to validate
192
- * @returns {string|null} - Error message if invalid, null if valid
193
- */
194
- function validateEnvPassthrough(value) {
195
- if (!Array.isArray(value)) {
196
- return 'dockerEnvPassthrough must be an array';
197
- }
198
-
199
- for (const item of value) {
200
- if (typeof item !== 'string') {
201
- return `Invalid env var: ${JSON.stringify(item)}. Must be a string`;
202
- }
203
- // Allow: VAR, VAR_*, VAR=value, VAR=
204
- if (!/^[A-Z_][A-Z0-9_]*(\*|=.*)?$/i.test(item)) {
205
- return `Invalid env var spec: "${item}". Use VAR, VAR_*, or VAR=value`;
206
- }
207
- }
208
-
209
- return null;
210
- }
211
-
212
- module.exports = {
213
- MOUNT_PRESETS,
214
- ENV_PRESETS,
215
- resolveMounts,
216
- resolveEnvs,
217
- expandEnvPatterns,
218
- validateMountConfig,
219
- validateEnvPassthrough,
220
- };
@@ -1,165 +0,0 @@
1
- /**
2
- * Git remote URL parsing and provider detection.
3
- * Automatically detects issue provider from git remote URL.
4
- */
5
-
6
- const { execSync } = require('../src/lib/safe-exec');
7
-
8
- /**
9
- * Parse git remote URL into structured provider context.
10
- * Supports GitHub, GitLab, and Azure DevOps (cloud + self-hosted).
11
- * Handles both HTTPS and SSH URL formats.
12
- *
13
- * @param {string} remoteUrl - Git remote URL
14
- * @returns {Object|null} Provider context or null if unparseable
15
- *
16
- * @example
17
- * parseGitRemoteUrl('https://github.com/org/repo.git')
18
- * // → { provider: 'github', host: 'github.com', org: 'org', repo: 'repo', fullRepo: 'org/repo' }
19
- *
20
- * @example
21
- * parseGitRemoteUrl('git@gitlab.com:org/repo.git')
22
- * // → { provider: 'gitlab', host: 'gitlab.com', org: 'org', repo: 'repo', fullRepo: 'org/repo' }
23
- *
24
- * @example
25
- * parseGitRemoteUrl('https://dev.azure.com/myorg/myproject/_git/myrepo')
26
- * // → { provider: 'azure-devops', host: 'dev.azure.com', azureOrg: 'https://dev.azure.com/myorg', azureProject: 'myproject', repo: 'myrepo' }
27
- */
28
- function parseGitRemoteUrl(remoteUrl) {
29
- if (!remoteUrl || typeof remoteUrl !== 'string') {
30
- return null;
31
- }
32
-
33
- const url = remoteUrl.trim();
34
-
35
- // Normalize SSH URLs to HTTPS format for easier parsing
36
- // git@host:path → https://host/path
37
- let normalizedUrl = url;
38
- const sshMatch = url.match(/^git@([^:]+):(.+)$/);
39
- if (sshMatch) {
40
- const [, host, path] = sshMatch;
41
- normalizedUrl = `https://${host}/${path}`;
42
- }
43
-
44
- // Remove .git suffix if present
45
- normalizedUrl = normalizedUrl.replace(/\.git$/, '');
46
-
47
- // Azure DevOps: https://dev.azure.com/org/project/_git/repo
48
- // Azure Legacy: https://org.visualstudio.com/project/_git/repo
49
- // Azure SSH: git@ssh.dev.azure.com:v3/org/project/repo
50
- const azureMatch =
51
- normalizedUrl.match(/https:\/\/dev\.azure\.com\/([^/]+)\/([^/]+)\/_git\/([^/]+)/) ||
52
- normalizedUrl.match(/https:\/\/([^.]+)\.visualstudio\.com\/([^/]+)\/_git\/([^/]+)/) ||
53
- // After normalization, `git@ssh.dev.azure.com:v3/org/project/repo` becomes
54
- // `https://ssh.dev.azure.com/v3/org/project/repo`
55
- normalizedUrl.match(/https:\/\/ssh\.dev\.azure\.com\/v3\/([^/]+)\/([^/]+)\/([^/]+)/);
56
-
57
- if (azureMatch) {
58
- const [, orgPart, project, repo] = azureMatch;
59
- // For dev.azure.com, org is the first path segment
60
- // For visualstudio.com, org is the subdomain
61
- const isLegacy = normalizedUrl.includes('visualstudio.com');
62
- const azureOrg = isLegacy
63
- ? `https://${orgPart}.visualstudio.com`
64
- : `https://dev.azure.com/${orgPart}`;
65
-
66
- return {
67
- provider: 'azure-devops',
68
- host: isLegacy ? `${orgPart}.visualstudio.com` : 'dev.azure.com',
69
- azureOrg,
70
- azureProject: project,
71
- repo,
72
- };
73
- }
74
-
75
- // GitHub: https://github.com/org/repo
76
- // GitLab: https://gitlab.com/org/repo (or self-hosted)
77
- // Generic: https://host/org/repo
78
- const httpsMatch = normalizedUrl.match(/https?:\/\/([^/]+)\/([^/]+)\/([^/]+)/);
79
- if (httpsMatch) {
80
- const [, host, org, repo] = httpsMatch;
81
-
82
- let provider = null;
83
- if (host === 'github.com') {
84
- provider = 'github';
85
- } else if (host.includes('gitlab')) {
86
- // Matches gitlab.com or any gitlab.* subdomain or *gitlab* in hostname
87
- provider = 'gitlab';
88
- } else {
89
- // Unknown provider - could be self-hosted GitLab or other
90
- // Return null to fall back to settings
91
- return null;
92
- }
93
-
94
- return {
95
- provider,
96
- host,
97
- org,
98
- repo,
99
- fullRepo: `${org}/${repo}`,
100
- };
101
- }
102
-
103
- return null;
104
- }
105
-
106
- /**
107
- * Detect git repository context from current working directory.
108
- * Returns provider context extracted from git remote URL.
109
- *
110
- * @param {string} [cwd=process.cwd()] - Directory to check
111
- * @returns {Object|null} Git context or null
112
- *
113
- * Gracefully returns null for:
114
- * - Not in git repository
115
- * - No remote configured
116
- * - Remote URL unparseable
117
- * - Git command fails
118
- *
119
- * @example
120
- * // In a GitHub repo with remote
121
- * detectGitContext()
122
- * // → { provider: 'github', host: 'github.com', org: 'myorg', repo: 'myrepo', fullRepo: 'myorg/myrepo' }
123
- *
124
- * @example
125
- * // Not in git repo or no remote
126
- * detectGitContext()
127
- * // → null
128
- */
129
- function detectGitContext(cwd = process.cwd()) {
130
- try {
131
- // Check if we're in a git repository
132
- execSync('git rev-parse --git-dir', {
133
- cwd,
134
- stdio: 'pipe',
135
- encoding: 'utf8',
136
- });
137
- } catch {
138
- // Not a git repository
139
- return null;
140
- }
141
-
142
- try {
143
- // Try to get remote URL (origin by default)
144
- const remoteUrl = execSync('git remote get-url origin', {
145
- cwd,
146
- stdio: 'pipe',
147
- encoding: 'utf8',
148
- }).trim();
149
-
150
- if (!remoteUrl) {
151
- return null;
152
- }
153
-
154
- // Parse the remote URL to extract provider context
155
- return parseGitRemoteUrl(remoteUrl);
156
- } catch {
157
- // No remote configured or command failed
158
- return null;
159
- }
160
- }
161
-
162
- module.exports = {
163
- parseGitRemoteUrl,
164
- detectGitContext,
165
- };
@@ -1,56 +0,0 @@
1
- /**
2
- * ID Detector - Determines if an ID is a task or cluster
3
- *
4
- * Strategy:
5
- * 1. Check if ID exists in cluster storage
6
- * 2. If not, check if ID exists in task storage (SQLite)
7
- * 3. Return type: 'cluster', 'task', or null
8
- */
9
-
10
- const path = require('path');
11
- const fs = require('fs');
12
- const os = require('os');
13
- const Database = require('better-sqlite3');
14
-
15
- // Storage paths
16
- const CLUSTER_DIR = path.join(os.homedir(), '.zeroshot');
17
- const TASK_DIR = path.join(os.homedir(), '.claude-zeroshot');
18
- const TASK_DB_FILE = path.join(TASK_DIR, 'store.db');
19
-
20
- /**
21
- * Detect if ID is a cluster or task
22
- * @param {string} id - The ID to check
23
- * @returns {'cluster'|'task'|null} - Type of ID or null if not found
24
- */
25
- function detectIdType(id) {
26
- // Check clusters
27
- const clusterFile = path.join(CLUSTER_DIR, 'clusters.json');
28
- if (fs.existsSync(clusterFile)) {
29
- try {
30
- const clusters = JSON.parse(fs.readFileSync(clusterFile, 'utf8'));
31
- if (clusters[id]) {
32
- return 'cluster';
33
- }
34
- } catch {
35
- // Ignore parse errors
36
- }
37
- }
38
-
39
- // Check tasks in SQLite
40
- if (fs.existsSync(TASK_DB_FILE)) {
41
- try {
42
- const db = new Database(TASK_DB_FILE, { readonly: true, timeout: 5000 });
43
- const row = db.prepare('SELECT id FROM tasks WHERE id = ?').get(id);
44
- db.close();
45
- if (row) {
46
- return 'task';
47
- }
48
- } catch {
49
- // Ignore DB errors
50
- }
51
- }
52
-
53
- return null;
54
- }
55
-
56
- module.exports = { detectIdType };
@@ -1,62 +0,0 @@
1
- /**
2
- * Provider defaults module
3
- *
4
- * Separated from settings.js to break circular dependency:
5
- * - settings.js requires provider defaults
6
- * - providers require settings.js (for loadSettings, getClaudeCommand, etc.)
7
- *
8
- * CRITICAL: This module should NOT import from settings.js
9
- */
10
-
11
- // Cache provider defaults to avoid repeated instantiation
12
- let _providerDefaultsCache = null;
13
-
14
- /**
15
- * Build provider default settings by instantiating each provider
16
- * and calling getDefaultSettings()
17
- * @returns {Object} Map of provider name to default settings
18
- */
19
- function buildProviderDefaults() {
20
- const { listProviders, getProvider } = require('../src/providers');
21
-
22
- const defaults = {};
23
- for (const providerName of listProviders()) {
24
- try {
25
- const provider = getProvider(providerName);
26
- defaults[providerName] = provider.getDefaultSettings();
27
- } catch (error) {
28
- // If provider fails to instantiate, use basic defaults
29
- console.warn(`Warning: Could not get defaults for ${providerName}: ${error.message}`);
30
- defaults[providerName] = {
31
- maxLevel: 'level3',
32
- minLevel: 'level1',
33
- defaultLevel: 'level2',
34
- levelOverrides: {},
35
- };
36
- }
37
- }
38
- return defaults;
39
- }
40
-
41
- /**
42
- * Get or build cached provider defaults
43
- * @returns {Object} Provider defaults
44
- */
45
- function getProviderDefaults() {
46
- if (!_providerDefaultsCache) {
47
- _providerDefaultsCache = buildProviderDefaults();
48
- }
49
- return _providerDefaultsCache;
50
- }
51
-
52
- /**
53
- * Clear the provider defaults cache (primarily for testing)
54
- */
55
- function clearProviderDefaultsCache() {
56
- _providerDefaultsCache = null;
57
- }
58
-
59
- module.exports = {
60
- getProviderDefaults,
61
- clearProviderDefaultsCache,
62
- };