@ikunin/sprintpilot 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/_Sprintpilot/lib/runtime/args.js +0 -2
- package/_Sprintpilot/lib/runtime/git.js +0 -2
- package/_Sprintpilot/lib/runtime/http.js +12 -5
- package/_Sprintpilot/lib/runtime/log.js +0 -2
- package/_Sprintpilot/lib/runtime/secrets.js +14 -16
- package/_Sprintpilot/lib/runtime/spawn.js +21 -8
- package/_Sprintpilot/lib/runtime/text.js +0 -2
- package/_Sprintpilot/lib/runtime/yaml-lite.js +9 -5
- package/_Sprintpilot/manifest.yaml +1 -1
- package/_Sprintpilot/modules/git/branching-and-pr-strategy.md +2 -2
- package/_Sprintpilot/scripts/create-pr.js +76 -38
- package/_Sprintpilot/scripts/detect-platform.js +35 -10
- package/_Sprintpilot/scripts/health-check.js +17 -8
- package/_Sprintpilot/scripts/lint-changed.js +35 -16
- package/_Sprintpilot/scripts/lock.js +22 -6
- package/_Sprintpilot/scripts/sanitize-branch.js +4 -2
- package/_Sprintpilot/scripts/stage-and-commit.js +15 -7
- package/_Sprintpilot/scripts/sync-status.js +16 -6
- package/_Sprintpilot/skills/sprint-autopilot-on/workflow.md +12 -12
- package/bin/sprintpilot.js +11 -4
- package/lib/commands/check-update.js +0 -2
- package/lib/commands/install.js +139 -49
- package/lib/commands/uninstall.js +21 -11
- package/lib/core/bmad-config.js +0 -2
- package/lib/core/file-ops.js +6 -6
- package/lib/core/gitignore.js +0 -2
- package/lib/core/markers.js +5 -3
- package/lib/core/tool-registry.js +19 -21
- package/lib/core/update-check.js +0 -2
- package/lib/core/v1-detect.js +0 -2
- package/lib/prompts.js +0 -2
- package/lib/substitute.js +1 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
Sprintpilot is an autonomous delivery addon **compatible with [BMad Method](https://github.com/bmad-code-org/BMAD-METHOD) v6**. One command takes your project from sprint plan to reviewed, tested, PR-ready code — with full git workflow and multi-agent intelligence.
|
|
12
12
|
|
|
13
|
-
> **Independent project.** Sprintpilot is not affiliated with or endorsed by BMad Code, LLC.
|
|
13
|
+
> **Independent project.** Sprintpilot is not affiliated with or endorsed by BMad Code, LLC. See [TRADEMARK.md](TRADEMARK.md).
|
|
14
14
|
|
|
15
15
|
> **Migrating from `bmad-autopilot-addon` v1?** See [MIGRATION.md](MIGRATION.md). `sprintpilot install` auto-detects v1 and cleanly replaces it.
|
|
16
16
|
|
|
@@ -33,7 +33,7 @@ Sprintpilot is an autonomous delivery addon **compatible with [BMad Method](http
|
|
|
33
33
|
|
|
34
34
|
BMad Method provides a structured development workflow with 50+ skills and agent personas. But using it manually means invoking each skill one at a time, navigating menus, making routine decisions, and handling git operations yourself. For a sprint with 10 stories across 3 epics, that's dozens of manual steps, context switches, and session restarts.
|
|
35
35
|
|
|
36
|
-
## The Solution:
|
|
36
|
+
## The Solution: Sprint Autopilot
|
|
37
37
|
|
|
38
38
|
```
|
|
39
39
|
/sprint-autopilot-on
|
|
@@ -148,7 +148,7 @@ Output files (`_bmad-output/codebase-analysis/`):
|
|
|
148
148
|
| `concerns-analysis.md` | TODOs/FIXMEs, security issues, dead code, deprecated patterns, error handling gaps |
|
|
149
149
|
| `integrations-analysis.md` | External APIs, databases, message queues, cloud services, env vars |
|
|
150
150
|
|
|
151
|
-
Scanned file types: TypeScript, JavaScript, Python, Java, Go, Rust, Ruby, C#, SQL, PL/SQL (`.sps`, `.spb`), XML, Shell.
|
|
151
|
+
Scanned file types: TypeScript, JavaScript, Python, Java, Go, Rust, Ruby, C, C++, C#, SQL, PL/SQL (`.sps`, `.spb`), XML, Shell.
|
|
152
152
|
|
|
153
153
|
**`/sprintpilot-assess`** — 3 parallel agents produce actionable findings:
|
|
154
154
|
- Dependency Auditor (CVEs, outdated packages, upgrade paths)
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
1
|
const https = require('node:https');
|
|
4
2
|
const http = require('node:http');
|
|
5
3
|
const { URL } = require('node:url');
|
|
@@ -22,7 +20,12 @@ function postJson(urlStr, body, { headers = {}, timeoutMs = 15_000 } = {}) {
|
|
|
22
20
|
// can fire on the size-cap abort path (we call req.destroy(err)). Without
|
|
23
21
|
// this, the observed error message becomes non-deterministic.
|
|
24
22
|
let settled = false;
|
|
25
|
-
const done = (fn, val) => {
|
|
23
|
+
const done = (fn, val) => {
|
|
24
|
+
if (!settled) {
|
|
25
|
+
settled = true;
|
|
26
|
+
fn(val);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
26
29
|
const ok = (val) => done(resolve, val);
|
|
27
30
|
const fail = (err) => done(reject, err);
|
|
28
31
|
|
|
@@ -78,11 +81,15 @@ function postJson(urlStr, body, { headers = {}, timeoutMs = 15_000 } = {}) {
|
|
|
78
81
|
if (aborted) return; // error path handles it
|
|
79
82
|
const text = Buffer.concat(chunks).toString('utf8');
|
|
80
83
|
let json = null;
|
|
81
|
-
try {
|
|
84
|
+
try {
|
|
85
|
+
json = JSON.parse(text);
|
|
86
|
+
} catch {
|
|
87
|
+
/* non-json */
|
|
88
|
+
}
|
|
82
89
|
ok({ statusCode: res.statusCode, body: text, json });
|
|
83
90
|
});
|
|
84
91
|
res.on('error', fail);
|
|
85
|
-
}
|
|
92
|
+
},
|
|
86
93
|
);
|
|
87
94
|
req.on('timeout', () => {
|
|
88
95
|
req.destroy(new Error(`HTTP timeout after ${timeoutMs}ms`));
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
1
|
const fs = require('node:fs');
|
|
4
2
|
|
|
5
3
|
// Keyword-based fuzzy matches (high false-positive rate, but useful as a
|
|
@@ -10,20 +8,20 @@ const SECRET_KEYWORD = /API_KEY|SECRET|TOKEN|PASSWORD|aws_access|private_key/i;
|
|
|
10
8
|
// secrets actually look like. Matching here is much less noisy than the
|
|
11
9
|
// keyword list above.
|
|
12
10
|
const SECRET_FORMATS = [
|
|
13
|
-
/\bAKIA[0-9A-Z]{16}\b/,
|
|
14
|
-
/\bASIA[0-9A-Z]{16}\b/,
|
|
15
|
-
/\bghp_[A-Za-z0-9]{30,}\b/,
|
|
16
|
-
/\bgho_[A-Za-z0-9]{30,}\b/,
|
|
17
|
-
/\bghu_[A-Za-z0-9]{30,}\b/,
|
|
18
|
-
/\bghs_[A-Za-z0-9]{30,}\b/,
|
|
19
|
-
/\bghr_[A-Za-z0-9]{30,}\b/,
|
|
20
|
-
/\bgithub_pat_[A-Za-z0-9_]{20,}\b/,
|
|
21
|
-
/\bsk-[A-Za-z0-9_-]{20,}\b/,
|
|
22
|
-
/\bsk_live_[A-Za-z0-9]{20,}\b/,
|
|
23
|
-
/\bsk_test_[A-Za-z0-9]{20,}\b/,
|
|
24
|
-
/\bxox[baprs]-[A-Za-z0-9-]{10,}\b/,
|
|
25
|
-
/\bAIza[0-9A-Za-z_-]{35,99}\b/,
|
|
26
|
-
/-----BEGIN [A-Z ]*PRIVATE KEY-----/,
|
|
11
|
+
/\bAKIA[0-9A-Z]{16}\b/, // AWS Access Key ID
|
|
12
|
+
/\bASIA[0-9A-Z]{16}\b/, // AWS temporary Access Key ID
|
|
13
|
+
/\bghp_[A-Za-z0-9]{30,}\b/, // GitHub personal access token
|
|
14
|
+
/\bgho_[A-Za-z0-9]{30,}\b/, // GitHub OAuth token
|
|
15
|
+
/\bghu_[A-Za-z0-9]{30,}\b/, // GitHub user-to-server token
|
|
16
|
+
/\bghs_[A-Za-z0-9]{30,}\b/, // GitHub server-to-server token
|
|
17
|
+
/\bghr_[A-Za-z0-9]{30,}\b/, // GitHub refresh token
|
|
18
|
+
/\bgithub_pat_[A-Za-z0-9_]{20,}\b/, // GitHub fine-grained PAT
|
|
19
|
+
/\bsk-[A-Za-z0-9_-]{20,}\b/, // OpenAI / Anthropic-like
|
|
20
|
+
/\bsk_live_[A-Za-z0-9]{20,}\b/, // Stripe live secret
|
|
21
|
+
/\bsk_test_[A-Za-z0-9]{20,}\b/, // Stripe test secret
|
|
22
|
+
/\bxox[baprs]-[A-Za-z0-9-]{10,}\b/, // Slack tokens
|
|
23
|
+
/\bAIza[0-9A-Za-z_-]{35,99}\b/, // Google API key (standard is 39 chars = AIza + 35)
|
|
24
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----/, // PEM private key header
|
|
27
25
|
];
|
|
28
26
|
|
|
29
27
|
// Deprecated: callers should use `matchesSecret(line)` which applies both
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
1
|
const child = require('node:child_process');
|
|
4
2
|
|
|
5
3
|
function run(file, args, { cwd, timeoutMs = 30_000, input, env } = {}) {
|
|
@@ -17,7 +15,7 @@ function run(file, args, { cwd, timeoutMs = 30_000, input, env } = {}) {
|
|
|
17
15
|
(err, stdout, stderr) => {
|
|
18
16
|
if (err) {
|
|
19
17
|
const e = new Error(
|
|
20
|
-
`${file} ${args.join(' ')} failed with code ${err.code ?? err.signal ?? 'unknown'}
|
|
18
|
+
`${file} ${args.join(' ')} failed with code ${err.code ?? err.signal ?? 'unknown'}`,
|
|
21
19
|
);
|
|
22
20
|
e.exitCode = typeof err.code === 'number' ? err.code : 1;
|
|
23
21
|
e.signal = err.signal || null;
|
|
@@ -27,16 +25,26 @@ function run(file, args, { cwd, timeoutMs = 30_000, input, env } = {}) {
|
|
|
27
25
|
return reject(e);
|
|
28
26
|
}
|
|
29
27
|
resolve({ stdout: String(stdout || ''), stderr: String(stderr || ''), exitCode: 0 });
|
|
30
|
-
}
|
|
28
|
+
},
|
|
31
29
|
);
|
|
32
30
|
// Rejecting on spawn-time errors (ENOENT etc) ensures the caller sees a
|
|
33
31
|
// rejected Promise rather than hanging, even if `proc.stdin` was never
|
|
34
32
|
// attached. Without this, a missing binary can crash via `proc.stdin.write`.
|
|
35
33
|
proc.on('error', reject);
|
|
36
34
|
if (input !== undefined && proc.stdin) {
|
|
37
|
-
proc.stdin.on('error', () => {
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
proc.stdin.on('error', () => {
|
|
36
|
+
/* ignore EPIPE etc */
|
|
37
|
+
});
|
|
38
|
+
try {
|
|
39
|
+
proc.stdin.write(input);
|
|
40
|
+
} catch {
|
|
41
|
+
/* ignore */
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
proc.stdin.end();
|
|
45
|
+
} catch {
|
|
46
|
+
/* ignore */
|
|
47
|
+
}
|
|
40
48
|
}
|
|
41
49
|
});
|
|
42
50
|
}
|
|
@@ -60,7 +68,12 @@ function runInherit(file, args, { cwd, env, input } = {}) {
|
|
|
60
68
|
proc.on('error', reject);
|
|
61
69
|
proc.on('close', (code) => resolve({ exitCode: code ?? 0 }));
|
|
62
70
|
if (input !== undefined && proc.stdin) {
|
|
63
|
-
try {
|
|
71
|
+
try {
|
|
72
|
+
proc.stdin.write(input);
|
|
73
|
+
proc.stdin.end();
|
|
74
|
+
} catch {
|
|
75
|
+
/* ignore */
|
|
76
|
+
}
|
|
64
77
|
}
|
|
65
78
|
});
|
|
66
79
|
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
1
|
// Narrow YAML helper covering the addon-owned shape:
|
|
4
2
|
// key: value
|
|
5
3
|
// stories:
|
|
@@ -8,7 +6,7 @@
|
|
|
8
6
|
// field2: value
|
|
9
7
|
// No deep nesting, no anchors, no flow sequences beyond [a,b,c] literal we pass through.
|
|
10
8
|
|
|
11
|
-
const SPECIAL_CHARS = /[:{}
|
|
9
|
+
const SPECIAL_CHARS = /[:{}[\],&*#?|<>=!%@`\n]|^-|^\s|\s$/;
|
|
12
10
|
|
|
13
11
|
// YAML 1.1 reserved literals that parsers interpret as booleans/null when
|
|
14
12
|
// unquoted (e.g. "no" -> false). Must be quoted to round-trip as strings.
|
|
@@ -81,8 +79,14 @@ function replaceStoryBlock(existing, storyKey, newBlock) {
|
|
|
81
79
|
// header is part of this block. Blank lines inside the block count
|
|
82
80
|
// as continuations and are also consumed.
|
|
83
81
|
while (i < lines.length) {
|
|
84
|
-
if (lines[i].length === 0) {
|
|
85
|
-
|
|
82
|
+
if (lines[i].length === 0) {
|
|
83
|
+
i++;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (indentOf(lines[i]) > headerIndent) {
|
|
87
|
+
i++;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
86
90
|
break;
|
|
87
91
|
}
|
|
88
92
|
// Strip any trailing blanks we may have consumed past the block.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Git Branching and PR Strategy
|
|
2
2
|
|
|
3
|
-
How the
|
|
3
|
+
How the Sprintpilot manages branches, PRs, and merging across stories.
|
|
4
4
|
|
|
5
5
|
## Configuration
|
|
6
6
|
|
|
@@ -96,6 +96,6 @@ Regardless of the merge strategy, implementation artifacts (sprint-status.yaml,
|
|
|
96
96
|
|
|
97
97
|
| File | Owner | Autopilot access |
|
|
98
98
|
|------|-------|-----------------|
|
|
99
|
-
| `sprint-status.yaml` |
|
|
99
|
+
| `sprint-status.yaml` | BMad Method (dev-story, sprint-planning, retrospective) | Read only |
|
|
100
100
|
| `git-status.yaml` | Autopilot addon | Read/write |
|
|
101
101
|
| `autopilot-state.yaml` | Autopilot addon | Read/write |
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
2
|
|
|
4
3
|
const { parseArgs } = require('../lib/runtime/args');
|
|
5
4
|
const { tryRun, run } = require('../lib/runtime/spawn');
|
|
@@ -9,7 +8,9 @@ const { postJson } = require('../lib/runtime/http');
|
|
|
9
8
|
const log = require('../lib/runtime/log');
|
|
10
9
|
|
|
11
10
|
function help() {
|
|
12
|
-
log.out(
|
|
11
|
+
log.out(
|
|
12
|
+
"Usage: create-pr.js --platform <github|gitlab|bitbucket|gitea|git_only> --branch <name> --base <branch> --title 'title' --body 'body' [--base-url <url>]",
|
|
13
|
+
);
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
async function hasCli(name) {
|
|
@@ -80,12 +81,18 @@ function redactAuth(text) {
|
|
|
80
81
|
return String(text)
|
|
81
82
|
.replace(/("?authorization"?\s*:\s*")[^"]*(")/gi, '$1[REDACTED]$2')
|
|
82
83
|
.replace(/(bearer\s+)\S+/gi, '$1[REDACTED]')
|
|
83
|
-
.replace(
|
|
84
|
+
.replace(
|
|
85
|
+
/("?(?:token|access_token|api_key|private_token)"?\s*[:=]\s*")[^"]*(")/gi,
|
|
86
|
+
'$1[REDACTED]$2',
|
|
87
|
+
);
|
|
84
88
|
}
|
|
85
89
|
|
|
86
90
|
async function main() {
|
|
87
91
|
const { opts } = parseArgs(process.argv.slice(2), { booleanFlags: ['dry-run'] });
|
|
88
|
-
if (opts.help) {
|
|
92
|
+
if (opts.help) {
|
|
93
|
+
help();
|
|
94
|
+
process.exit(0);
|
|
95
|
+
}
|
|
89
96
|
|
|
90
97
|
const platform = opts.platform;
|
|
91
98
|
const branch = opts.branch;
|
|
@@ -130,13 +137,11 @@ async function main() {
|
|
|
130
137
|
log.out('SKIPPED');
|
|
131
138
|
process.exit(2);
|
|
132
139
|
}
|
|
133
|
-
const r = await tryRun(
|
|
134
|
-
'
|
|
135
|
-
'--base', baseBranch,
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
'--body', body,
|
|
139
|
-
], { timeoutMs: 60_000 });
|
|
140
|
+
const r = await tryRun(
|
|
141
|
+
'gh',
|
|
142
|
+
['pr', 'create', '--base', baseBranch, '--head', branch, '--title', title, '--body', body],
|
|
143
|
+
{ timeoutMs: 60_000 },
|
|
144
|
+
);
|
|
140
145
|
const combined = `${r.stdout}${r.stderr}`;
|
|
141
146
|
if (r.exitCode !== 0) {
|
|
142
147
|
log.error(`gh pr create failed: ${combined.trim()}`);
|
|
@@ -152,15 +157,24 @@ async function main() {
|
|
|
152
157
|
log.out('SKIPPED');
|
|
153
158
|
process.exit(2);
|
|
154
159
|
}
|
|
155
|
-
const r = await tryRun(
|
|
156
|
-
'
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
160
|
+
const r = await tryRun(
|
|
161
|
+
'glab',
|
|
162
|
+
[
|
|
163
|
+
'mr',
|
|
164
|
+
'create',
|
|
165
|
+
'--source-branch',
|
|
166
|
+
branch,
|
|
167
|
+
'--target-branch',
|
|
168
|
+
baseBranch,
|
|
169
|
+
'--title',
|
|
170
|
+
title,
|
|
171
|
+
'--description',
|
|
172
|
+
body,
|
|
173
|
+
'--remove-source-branch',
|
|
174
|
+
'--yes',
|
|
175
|
+
],
|
|
176
|
+
{ timeoutMs: 60_000 },
|
|
177
|
+
);
|
|
164
178
|
const combined = `${r.stdout}${r.stderr}`;
|
|
165
179
|
if (r.exitCode !== 0) {
|
|
166
180
|
log.error(`glab mr create failed: ${combined.trim()}`);
|
|
@@ -173,13 +187,22 @@ async function main() {
|
|
|
173
187
|
|
|
174
188
|
if (platform === 'bitbucket') {
|
|
175
189
|
if (await hasCli('bb')) {
|
|
176
|
-
const r = await tryRun(
|
|
177
|
-
'
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
190
|
+
const r = await tryRun(
|
|
191
|
+
'bb',
|
|
192
|
+
[
|
|
193
|
+
'pr',
|
|
194
|
+
'create',
|
|
195
|
+
'--source',
|
|
196
|
+
branch,
|
|
197
|
+
'--destination',
|
|
198
|
+
baseBranch,
|
|
199
|
+
'--title',
|
|
200
|
+
title,
|
|
201
|
+
'--description',
|
|
202
|
+
body,
|
|
203
|
+
],
|
|
204
|
+
{ timeoutMs: 60_000 },
|
|
205
|
+
);
|
|
183
206
|
const combined = `${r.stdout}${r.stderr}`;
|
|
184
207
|
if (r.exitCode !== 0) {
|
|
185
208
|
log.error(`bb pr create failed: ${combined.trim()}`);
|
|
@@ -200,11 +223,14 @@ async function main() {
|
|
|
200
223
|
destination: { branch: { name: baseBranch } },
|
|
201
224
|
description: body,
|
|
202
225
|
},
|
|
203
|
-
{ headers: { Authorization: `Bearer ${process.env.BITBUCKET_TOKEN}` } }
|
|
226
|
+
{ headers: { Authorization: `Bearer ${process.env.BITBUCKET_TOKEN}` } },
|
|
204
227
|
);
|
|
205
228
|
if (res.statusCode === 201) {
|
|
206
229
|
const href = res.json?.links?.html?.href;
|
|
207
|
-
if (href) {
|
|
230
|
+
if (href) {
|
|
231
|
+
log.out(href);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
208
234
|
log.out('CREATED (URL not extracted from response)');
|
|
209
235
|
return;
|
|
210
236
|
}
|
|
@@ -223,13 +249,22 @@ async function main() {
|
|
|
223
249
|
|
|
224
250
|
if (platform === 'gitea') {
|
|
225
251
|
if (await hasCli('tea')) {
|
|
226
|
-
const r = await tryRun(
|
|
227
|
-
'
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
252
|
+
const r = await tryRun(
|
|
253
|
+
'tea',
|
|
254
|
+
[
|
|
255
|
+
'pr',
|
|
256
|
+
'create',
|
|
257
|
+
'--base',
|
|
258
|
+
baseBranch,
|
|
259
|
+
'--head',
|
|
260
|
+
branch,
|
|
261
|
+
'--title',
|
|
262
|
+
title,
|
|
263
|
+
'--description',
|
|
264
|
+
body,
|
|
265
|
+
],
|
|
266
|
+
{ timeoutMs: 60_000 },
|
|
267
|
+
);
|
|
233
268
|
const combined = `${r.stdout}${r.stderr}`;
|
|
234
269
|
if (r.exitCode !== 0) {
|
|
235
270
|
log.error(`tea pr create failed: ${combined.trim()}`);
|
|
@@ -245,11 +280,14 @@ async function main() {
|
|
|
245
280
|
const res = await postJson(
|
|
246
281
|
`${baseUrl.replace(/\/+$/, '')}/api/v1/repos/${ownerRepo}/pulls`,
|
|
247
282
|
{ base: baseBranch, head: branch, title, body },
|
|
248
|
-
{ headers: { Authorization: `token ${process.env.GITEA_TOKEN}` } }
|
|
283
|
+
{ headers: { Authorization: `token ${process.env.GITEA_TOKEN}` } },
|
|
249
284
|
);
|
|
250
285
|
if (res.statusCode === 201) {
|
|
251
286
|
const href = res.json?.html_url;
|
|
252
|
-
if (href) {
|
|
287
|
+
if (href) {
|
|
288
|
+
log.out(href);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
253
291
|
log.out('CREATED (URL not extracted from response)');
|
|
254
292
|
return;
|
|
255
293
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
2
|
|
|
4
3
|
const { parseArgs } = require('../lib/runtime/args');
|
|
5
4
|
const { tryRun } = require('../lib/runtime/spawn');
|
|
@@ -17,7 +16,10 @@ async function hasCli(name) {
|
|
|
17
16
|
|
|
18
17
|
async function main() {
|
|
19
18
|
const { opts } = parseArgs(process.argv.slice(2));
|
|
20
|
-
if (opts.help) {
|
|
19
|
+
if (opts.help) {
|
|
20
|
+
help();
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
21
23
|
const provider = opts.provider || 'auto';
|
|
22
24
|
|
|
23
25
|
if (provider !== 'auto') {
|
|
@@ -45,16 +47,39 @@ async function main() {
|
|
|
45
47
|
|
|
46
48
|
const remote = (await tryGitStdout(['remote', 'get-url', 'origin'])) || '';
|
|
47
49
|
|
|
48
|
-
if (/github\.com[:/]/i.test(remote)) {
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
if (/github\.com[:/]/i.test(remote)) {
|
|
51
|
+
log.out('github');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (/gitlab\./i.test(remote)) {
|
|
55
|
+
log.out('gitlab');
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (/bitbucket\.org[:/]/i.test(remote)) {
|
|
59
|
+
log.out('bitbucket');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
51
62
|
|
|
52
|
-
if (hasTea) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
63
|
+
if (hasTea) {
|
|
64
|
+
log.out('gitea');
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (hasGh) {
|
|
68
|
+
log.out('github');
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (hasGlab) {
|
|
72
|
+
log.out('gitlab');
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (hasBb) {
|
|
76
|
+
log.out('bitbucket');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
56
79
|
|
|
57
|
-
log.err(
|
|
80
|
+
log.err(
|
|
81
|
+
"WARN: no platform CLI found (gh, glab, bb, tea) and remote URL didn't match known platforms",
|
|
82
|
+
);
|
|
58
83
|
log.out('git_only');
|
|
59
84
|
}
|
|
60
85
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
2
|
|
|
4
3
|
const fs = require('node:fs');
|
|
5
4
|
const path = require('node:path');
|
|
@@ -10,12 +9,17 @@ const { readStoryField } = require('../lib/runtime/yaml-lite');
|
|
|
10
9
|
const log = require('../lib/runtime/log');
|
|
11
10
|
|
|
12
11
|
function help() {
|
|
13
|
-
log.out(
|
|
12
|
+
log.out(
|
|
13
|
+
'Usage: health-check.js [--worktrees-dir path] [--base-branch main] [--status-file path]',
|
|
14
|
+
);
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
async function main() {
|
|
17
18
|
const { opts } = parseArgs(process.argv.slice(2));
|
|
18
|
-
if (opts.help) {
|
|
19
|
+
if (opts.help) {
|
|
20
|
+
help();
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
19
23
|
const worktreesDir = opts['worktrees-dir'] || '.worktrees';
|
|
20
24
|
const baseBranch = opts['base-branch'] || 'main';
|
|
21
25
|
const statusFile = opts['status-file'] || '';
|
|
@@ -34,13 +38,18 @@ async function main() {
|
|
|
34
38
|
log.err("WARN: no 'origin' remote configured — commit comparison may be inaccurate");
|
|
35
39
|
}
|
|
36
40
|
|
|
37
|
-
let total = 0,
|
|
41
|
+
let total = 0,
|
|
42
|
+
cleanDone = 0,
|
|
43
|
+
committed = 0,
|
|
44
|
+
stale = 0,
|
|
45
|
+
dirty = 0,
|
|
46
|
+
orphan = 0;
|
|
38
47
|
|
|
39
|
-
const statusText =
|
|
40
|
-
? fs.readFileSync(statusFile, 'utf8')
|
|
41
|
-
: null;
|
|
48
|
+
const statusText =
|
|
49
|
+
statusFile && fs.existsSync(statusFile) ? fs.readFileSync(statusFile, 'utf8') : null;
|
|
42
50
|
|
|
43
|
-
const entries = fs
|
|
51
|
+
const entries = fs
|
|
52
|
+
.readdirSync(worktreesDir, { withFileTypes: true })
|
|
44
53
|
.filter((e) => e.isDirectory());
|
|
45
54
|
|
|
46
55
|
for (const entry of entries) {
|