chief-clancy 0.5.3 → 0.5.4

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 (72) hide show
  1. package/README.md +3 -3
  2. package/dist/bundle/clancy-afk.js +2 -2
  3. package/dist/bundle/clancy-once.js +29 -28
  4. package/dist/schemas/env.d.ts +27 -0
  5. package/dist/schemas/env.d.ts.map +1 -1
  6. package/dist/schemas/env.js +8 -0
  7. package/dist/schemas/env.js.map +1 -1
  8. package/dist/scripts/afk/afk.d.ts.map +1 -1
  9. package/dist/scripts/afk/afk.js +1 -8
  10. package/dist/scripts/afk/afk.js.map +1 -1
  11. package/dist/scripts/board/github/github.d.ts.map +1 -1
  12. package/dist/scripts/board/github/github.js +1 -2
  13. package/dist/scripts/board/github/github.js.map +1 -1
  14. package/dist/scripts/board/linear/linear.d.ts.map +1 -1
  15. package/dist/scripts/board/linear/linear.js +35 -4
  16. package/dist/scripts/board/linear/linear.js.map +1 -1
  17. package/dist/scripts/once/once.d.ts.map +1 -1
  18. package/dist/scripts/once/once.js +205 -35
  19. package/dist/scripts/once/once.js.map +1 -1
  20. package/dist/scripts/shared/env-schema/env-schema.d.ts.map +1 -1
  21. package/dist/scripts/shared/env-schema/env-schema.js +3 -2
  22. package/dist/scripts/shared/env-schema/env-schema.js.map +1 -1
  23. package/dist/scripts/shared/format/format.d.ts +11 -0
  24. package/dist/scripts/shared/format/format.d.ts.map +1 -0
  25. package/dist/scripts/shared/format/format.js +18 -0
  26. package/dist/scripts/shared/format/format.js.map +1 -0
  27. package/dist/scripts/shared/git-ops/git-ops.d.ts +9 -0
  28. package/dist/scripts/shared/git-ops/git-ops.d.ts.map +1 -1
  29. package/dist/scripts/shared/git-ops/git-ops.js +20 -0
  30. package/dist/scripts/shared/git-ops/git-ops.js.map +1 -1
  31. package/dist/scripts/shared/http/http.d.ts +2 -0
  32. package/dist/scripts/shared/http/http.d.ts.map +1 -1
  33. package/dist/scripts/shared/http/http.js +2 -0
  34. package/dist/scripts/shared/http/http.js.map +1 -1
  35. package/dist/scripts/shared/progress/progress.d.ts +3 -2
  36. package/dist/scripts/shared/progress/progress.d.ts.map +1 -1
  37. package/dist/scripts/shared/progress/progress.js +1 -1
  38. package/dist/scripts/shared/progress/progress.js.map +1 -1
  39. package/dist/scripts/shared/pull-request/bitbucket/bitbucket.d.ts +38 -0
  40. package/dist/scripts/shared/pull-request/bitbucket/bitbucket.d.ts.map +1 -0
  41. package/dist/scripts/shared/pull-request/bitbucket/bitbucket.js +69 -0
  42. package/dist/scripts/shared/pull-request/bitbucket/bitbucket.js.map +1 -0
  43. package/dist/scripts/shared/pull-request/github/github.d.ts +15 -0
  44. package/dist/scripts/shared/pull-request/github/github.d.ts.map +1 -0
  45. package/dist/scripts/shared/pull-request/github/github.js +29 -0
  46. package/dist/scripts/shared/pull-request/github/github.js.map +1 -0
  47. package/dist/scripts/shared/pull-request/gitlab/gitlab.d.ts +23 -0
  48. package/dist/scripts/shared/pull-request/gitlab/gitlab.d.ts.map +1 -0
  49. package/dist/scripts/shared/pull-request/gitlab/gitlab.js +27 -0
  50. package/dist/scripts/shared/pull-request/gitlab/gitlab.js.map +1 -0
  51. package/dist/scripts/shared/pull-request/post-pr/post-pr.d.ts +31 -0
  52. package/dist/scripts/shared/pull-request/post-pr/post-pr.d.ts.map +1 -0
  53. package/dist/scripts/shared/pull-request/post-pr/post-pr.js +61 -0
  54. package/dist/scripts/shared/pull-request/post-pr/post-pr.js.map +1 -0
  55. package/dist/scripts/shared/pull-request/pr-body/pr-body.d.ts +19 -0
  56. package/dist/scripts/shared/pull-request/pr-body/pr-body.d.ts.map +1 -0
  57. package/dist/scripts/shared/pull-request/pr-body/pr-body.js +35 -0
  58. package/dist/scripts/shared/pull-request/pr-body/pr-body.js.map +1 -0
  59. package/dist/scripts/shared/remote/remote.d.ts +41 -0
  60. package/dist/scripts/shared/remote/remote.d.ts.map +1 -0
  61. package/dist/scripts/shared/remote/remote.js +227 -0
  62. package/dist/scripts/shared/remote/remote.js.map +1 -0
  63. package/dist/types/index.d.ts +1 -0
  64. package/dist/types/index.d.ts.map +1 -1
  65. package/dist/types/remote.d.ts +44 -0
  66. package/dist/types/remote.d.ts.map +1 -0
  67. package/dist/types/remote.js +5 -0
  68. package/dist/types/remote.js.map +1 -0
  69. package/package.json +1 -1
  70. package/src/roles/setup/workflows/init.md +91 -0
  71. package/src/roles/setup/workflows/scaffold.md +27 -0
  72. package/src/roles/setup/workflows/settings.md +62 -0
@@ -0,0 +1,61 @@
1
+ /**
2
+ * POST a pull request / merge request and return a typed result.
3
+ *
4
+ * Handles the common try/catch, response parsing, error formatting,
5
+ * and "already exists" detection shared by all git host implementations.
6
+ *
7
+ * @param url - The API endpoint to POST to.
8
+ * @param headers - HTTP headers (including auth).
9
+ * @param body - The request body (JSON-serialised).
10
+ * @param parseSuccess - Extract `{ url, number }` from the success response JSON.
11
+ * @param isAlreadyExists - Optional check for duplicate PR detection.
12
+ * @returns A typed `PrCreationResult`.
13
+ */
14
+ export async function postPullRequest(url, headers, body, parseSuccess, isAlreadyExists) {
15
+ const controller = new AbortController();
16
+ const timeout = setTimeout(() => controller.abort(), 30_000);
17
+ try {
18
+ const response = await fetch(url, {
19
+ method: 'POST',
20
+ headers: { ...headers, 'Content-Type': 'application/json' },
21
+ body: JSON.stringify(body),
22
+ signal: controller.signal,
23
+ });
24
+ if (!response.ok) {
25
+ const text = await response.text().catch(() => '');
26
+ const alreadyExists = isAlreadyExists?.(response.status, text) ?? false;
27
+ return {
28
+ ok: false,
29
+ error: `HTTP ${response.status}${text ? `: ${text.slice(0, 200)}` : ''}`,
30
+ alreadyExists,
31
+ };
32
+ }
33
+ const json = await response.json();
34
+ const parsed = parseSuccess(json);
35
+ if (!parsed.url && !parsed.number) {
36
+ return {
37
+ ok: false,
38
+ error: 'PR created but response missing URL and number',
39
+ };
40
+ }
41
+ return { ok: true, url: parsed.url, number: parsed.number };
42
+ }
43
+ catch (err) {
44
+ return {
45
+ ok: false,
46
+ error: err instanceof Error ? err.message : String(err),
47
+ };
48
+ }
49
+ finally {
50
+ clearTimeout(timeout);
51
+ }
52
+ }
53
+ /**
54
+ * Build a Basic Auth header value from username and token.
55
+ *
56
+ * Used by Bitbucket Cloud which requires HTTP Basic Auth.
57
+ */
58
+ export function basicAuth(username, token) {
59
+ return `Basic ${Buffer.from(`${username}:${token}`).toString('base64')}`;
60
+ }
61
+ //# sourceMappingURL=post-pr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"post-pr.js","sourceRoot":"","sources":["../../../../../src/scripts/shared/pull-request/post-pr/post-pr.ts"],"names":[],"mappings":"AAQA;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,GAAW,EACX,OAA+B,EAC/B,IAA6B,EAC7B,YAAgE,EAChE,eAA2D;IAE3D,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,MAAM,CAAC,CAAC;IAE7D,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAChC,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,GAAG,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC3D,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;YAC1B,MAAM,EAAE,UAAU,CAAC,MAAM;SAC1B,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YACnD,MAAM,aAAa,GAAG,eAAe,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,KAAK,CAAC;YAExE,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,QAAQ,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE;gBACxE,aAAa;aACd,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,GAAY,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QAC5C,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;QAElC,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YAClC,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,gDAAgD;aACxD,CAAC;QACJ,CAAC;QAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;IAC9D,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,EAAE,EAAE,KAAK;YACT,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;SACxD,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,OAAO,CAAC,CAAC;IACxB,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,QAAgB,EAAE,KAAa;IACvD,OAAO,SAAS,MAAM,CAAC,IAAI,CAAC,GAAG,QAAQ,IAAI,KAAK,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;AAC3E,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * PR/MR body builder.
3
+ *
4
+ * Constructs the pull request description with a link back to the
5
+ * board ticket and a Clancy footer.
6
+ */
7
+ import type { BoardConfig, Ticket } from '../../../../types/index.js';
8
+ /**
9
+ * Build the PR/MR body for a ticket.
10
+ *
11
+ * Includes a link back to the board ticket (auto-close for GitHub Issues)
12
+ * and a Clancy attribution footer.
13
+ *
14
+ * @param config - The board configuration.
15
+ * @param ticket - The ticket being implemented.
16
+ * @returns The PR body as a markdown string.
17
+ */
18
+ export declare function buildPrBody(config: BoardConfig, ticket: Ticket): string;
19
+ //# sourceMappingURL=pr-body.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pr-body.d.ts","sourceRoot":"","sources":["../../../../../src/scripts/shared/pull-request/pr-body/pr-body.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE5D;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CA8BvE"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Build the PR/MR body for a ticket.
3
+ *
4
+ * Includes a link back to the board ticket (auto-close for GitHub Issues)
5
+ * and a Clancy attribution footer.
6
+ *
7
+ * @param config - The board configuration.
8
+ * @param ticket - The ticket being implemented.
9
+ * @returns The PR body as a markdown string.
10
+ */
11
+ export function buildPrBody(config, ticket) {
12
+ const lines = [];
13
+ switch (config.provider) {
14
+ case 'github':
15
+ lines.push(`Closes ${ticket.key}`);
16
+ break;
17
+ case 'jira':
18
+ lines.push(`**Jira:** [${ticket.key}](${config.env.JIRA_BASE_URL}/browse/${ticket.key})`);
19
+ break;
20
+ case 'linear':
21
+ lines.push(`**Linear:** ${ticket.key}`);
22
+ break;
23
+ }
24
+ lines.push('');
25
+ if (ticket.description) {
26
+ lines.push('## Description');
27
+ lines.push('');
28
+ lines.push(ticket.description);
29
+ lines.push('');
30
+ }
31
+ lines.push('---');
32
+ lines.push('*Created by [Clancy](https://github.com/Pushedskydiver/clancy)*');
33
+ return lines.join('\n');
34
+ }
35
+ //# sourceMappingURL=pr-body.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pr-body.js","sourceRoot":"","sources":["../../../../../src/scripts/shared/pull-request/pr-body/pr-body.ts"],"names":[],"mappings":"AAQA;;;;;;;;;GASG;AACH,MAAM,UAAU,WAAW,CAAC,MAAmB,EAAE,MAAc;IAC7D,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,QAAQ,MAAM,CAAC,QAAQ,EAAE,CAAC;QACxB,KAAK,QAAQ;YACX,KAAK,CAAC,IAAI,CAAC,UAAU,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;YACnC,MAAM;QACR,KAAK,MAAM;YACT,KAAK,CAAC,IAAI,CACR,cAAc,MAAM,CAAC,GAAG,KAAK,MAAM,CAAC,GAAG,CAAC,aAAa,WAAW,MAAM,CAAC,GAAG,GAAG,CAC9E,CAAC;YACF,MAAM;QACR,KAAK,QAAQ;YACX,KAAK,CAAC,IAAI,CAAC,eAAe,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;YACxC,MAAM;IACV,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEf,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;QACvB,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAC7B,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;QAC/B,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClB,KAAK,CAAC,IAAI,CAAC,iEAAiE,CAAC,CAAC;IAE9E,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,41 @@
1
+ import type { GitPlatform, RemoteInfo } from '../../../types/index.js';
2
+ /**
3
+ * Parse a git remote URL into platform-specific info.
4
+ *
5
+ * Supports HTTPS, SSH, and SSH-URL formats for GitHub, GitLab, Bitbucket
6
+ * (Cloud and Server), Azure DevOps, and self-hosted instances.
7
+ *
8
+ * @param rawUrl - The raw git remote URL.
9
+ * @returns Parsed remote info with platform and path details.
10
+ */
11
+ export declare function parseRemote(rawUrl: string): RemoteInfo;
12
+ /**
13
+ * Detect the git hosting platform from a hostname.
14
+ *
15
+ * Uses known domain patterns. Self-hosted instances with custom domains
16
+ * (e.g. `git.acme.com`) fall through to 'unknown' — use `CLANCY_GIT_PLATFORM`
17
+ * env var to override.
18
+ *
19
+ * @param hostname - The hostname from the remote URL.
20
+ * @returns The detected platform.
21
+ */
22
+ export declare function detectPlatformFromHostname(hostname: string): GitPlatform;
23
+ /**
24
+ * Detect the remote origin info from the current git repository.
25
+ *
26
+ * Falls back to `{ host: 'none' }` if no remote is configured or
27
+ * if the command fails.
28
+ *
29
+ * @param platformOverride - Override platform detection (from `CLANCY_GIT_PLATFORM` env var).
30
+ * @returns Parsed remote info.
31
+ */
32
+ export declare function detectRemote(platformOverride?: string): RemoteInfo;
33
+ /**
34
+ * Build the API base URL for a given remote.
35
+ *
36
+ * @param remote - The parsed remote info.
37
+ * @param apiUrlOverride - Override from `CLANCY_GIT_API_URL` env var.
38
+ * @returns The API base URL, or undefined if not applicable.
39
+ */
40
+ export declare function buildApiBaseUrl(remote: RemoteInfo, apiUrlOverride?: string): string | undefined;
41
+ //# sourceMappingURL=remote.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote.d.ts","sourceRoot":"","sources":["../../../../src/scripts/shared/remote/remote.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAgChE;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAgEtD;AAED;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,CAWxE;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,UAAU,CAmBlE;AAgED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,UAAU,EAClB,cAAc,CAAC,EAAE,MAAM,GACtB,MAAM,GAAG,SAAS,CAiBpB"}
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Remote git host detection.
3
+ *
4
+ * Parses `git remote get-url origin` to detect the hosting platform
5
+ * (GitHub, GitLab, Bitbucket, etc.) and extract owner/repo/project info.
6
+ */
7
+ import { execFileSync } from 'node:child_process';
8
+ /**
9
+ * Extract hostname and path from a raw git remote URL.
10
+ *
11
+ * Strips `.git` suffix, then tries SSH (`git@host:path`) and
12
+ * HTTPS/SSH-URL (`https://host/path`, `ssh://git@host/path`) formats.
13
+ *
14
+ * @param rawUrl - The raw git remote URL.
15
+ * @returns The extracted hostname and path, or `undefined` if unparseable.
16
+ */
17
+ function extractHostAndPath(rawUrl) {
18
+ const url = rawUrl.trim().replace(/\.git$/, '');
19
+ // SSH format: git@<host>:<path>
20
+ const sshMatch = url.match(/^git@([^:]+):(.+)$/);
21
+ // HTTPS or SSH-URL format: https://<host>/<path> or ssh://git@<host>/<path>
22
+ const httpsMatch = url.match(/^(?:https?|ssh):\/\/(?:[^@]+@)?([^/:]+)(?::\d+)?\/(.+)$/);
23
+ const hostname = sshMatch?.[1] ?? httpsMatch?.[1];
24
+ const path = sshMatch?.[2] ?? httpsMatch?.[2];
25
+ if (!hostname || !path)
26
+ return undefined;
27
+ return { hostname, path };
28
+ }
29
+ /**
30
+ * Parse a git remote URL into platform-specific info.
31
+ *
32
+ * Supports HTTPS, SSH, and SSH-URL formats for GitHub, GitLab, Bitbucket
33
+ * (Cloud and Server), Azure DevOps, and self-hosted instances.
34
+ *
35
+ * @param rawUrl - The raw git remote URL.
36
+ * @returns Parsed remote info with platform and path details.
37
+ */
38
+ export function parseRemote(rawUrl) {
39
+ const extracted = extractHostAndPath(rawUrl);
40
+ if (!extracted) {
41
+ return { host: 'unknown', url: rawUrl };
42
+ }
43
+ const { hostname, path } = extracted;
44
+ const platform = detectPlatformFromHostname(hostname);
45
+ switch (platform) {
46
+ case 'github': {
47
+ const parts = path.split('/');
48
+ if (parts.length >= 2) {
49
+ return {
50
+ host: 'github',
51
+ owner: parts[0],
52
+ repo: parts[1],
53
+ hostname,
54
+ };
55
+ }
56
+ return { host: 'unknown', url: rawUrl };
57
+ }
58
+ case 'gitlab': {
59
+ return {
60
+ host: 'gitlab',
61
+ projectPath: path,
62
+ hostname,
63
+ };
64
+ }
65
+ case 'bitbucket': {
66
+ // Bitbucket Server uses /scm/<projectKey>/<repo> format
67
+ const scmMatch = path.match(/^scm\/([^/]+)\/(.+)$/);
68
+ if (scmMatch) {
69
+ return {
70
+ host: 'bitbucket-server',
71
+ projectKey: scmMatch[1],
72
+ repoSlug: scmMatch[2],
73
+ hostname,
74
+ };
75
+ }
76
+ // Bitbucket Cloud: <workspace>/<repo>
77
+ const parts = path.split('/');
78
+ if (parts.length >= 2) {
79
+ return {
80
+ host: 'bitbucket',
81
+ workspace: parts[0],
82
+ repoSlug: parts[1],
83
+ hostname,
84
+ };
85
+ }
86
+ return { host: 'unknown', url: rawUrl };
87
+ }
88
+ case 'azure':
89
+ return { host: 'azure', url: rawUrl };
90
+ default:
91
+ return { host: 'unknown', url: rawUrl };
92
+ }
93
+ }
94
+ /**
95
+ * Detect the git hosting platform from a hostname.
96
+ *
97
+ * Uses known domain patterns. Self-hosted instances with custom domains
98
+ * (e.g. `git.acme.com`) fall through to 'unknown' — use `CLANCY_GIT_PLATFORM`
99
+ * env var to override.
100
+ *
101
+ * @param hostname - The hostname from the remote URL.
102
+ * @returns The detected platform.
103
+ */
104
+ export function detectPlatformFromHostname(hostname) {
105
+ const lower = hostname.toLowerCase();
106
+ if (lower === 'github.com' || lower.includes('github'))
107
+ return 'github';
108
+ if (lower === 'gitlab.com' || lower.includes('gitlab'))
109
+ return 'gitlab';
110
+ if (lower === 'bitbucket.org' || lower.includes('bitbucket'))
111
+ return 'bitbucket';
112
+ if (lower.includes('dev.azure') || lower.includes('visualstudio'))
113
+ return 'azure';
114
+ return 'unknown';
115
+ }
116
+ /**
117
+ * Detect the remote origin info from the current git repository.
118
+ *
119
+ * Falls back to `{ host: 'none' }` if no remote is configured or
120
+ * if the command fails.
121
+ *
122
+ * @param platformOverride - Override platform detection (from `CLANCY_GIT_PLATFORM` env var).
123
+ * @returns Parsed remote info.
124
+ */
125
+ export function detectRemote(platformOverride) {
126
+ let rawUrl;
127
+ try {
128
+ rawUrl = execFileSync('git', ['remote', 'get-url', 'origin'], {
129
+ encoding: 'utf8',
130
+ }).trim();
131
+ }
132
+ catch {
133
+ return { host: 'none' };
134
+ }
135
+ if (!rawUrl)
136
+ return { host: 'none' };
137
+ // If user explicitly overrides the platform, always use it — they know better
138
+ if (platformOverride) {
139
+ return overrideRemotePlatform(rawUrl, platformOverride);
140
+ }
141
+ return parseRemote(rawUrl);
142
+ }
143
+ /**
144
+ * Re-parse a remote URL with a known platform override.
145
+ *
146
+ * Used when auto-detection fails (self-hosted with custom domain)
147
+ * and the user sets `CLANCY_GIT_PLATFORM`.
148
+ */
149
+ function overrideRemotePlatform(rawUrl, platform) {
150
+ const extracted = extractHostAndPath(rawUrl);
151
+ if (!extracted)
152
+ return { host: 'unknown', url: rawUrl };
153
+ const { hostname, path } = extracted;
154
+ switch (platform.toLowerCase()) {
155
+ case 'github': {
156
+ const parts = path.split('/');
157
+ if (parts.length >= 2) {
158
+ return { host: 'github', owner: parts[0], repo: parts[1], hostname };
159
+ }
160
+ return { host: 'unknown', url: rawUrl };
161
+ }
162
+ case 'gitlab':
163
+ return { host: 'gitlab', projectPath: path, hostname };
164
+ case 'bitbucket': {
165
+ const parts = path.split('/');
166
+ if (parts.length >= 2) {
167
+ return {
168
+ host: 'bitbucket',
169
+ workspace: parts[0],
170
+ repoSlug: parts[1],
171
+ hostname,
172
+ };
173
+ }
174
+ return { host: 'unknown', url: rawUrl };
175
+ }
176
+ case 'bitbucket-server': {
177
+ // Strip leading scm/ prefix used in Bitbucket Server URLs
178
+ const scmMatch = path.match(/^scm\/([^/]+)\/(.+)$/);
179
+ if (scmMatch) {
180
+ return {
181
+ host: 'bitbucket-server',
182
+ projectKey: scmMatch[1],
183
+ repoSlug: scmMatch[2],
184
+ hostname,
185
+ };
186
+ }
187
+ const parts = path.split('/');
188
+ if (parts.length >= 2) {
189
+ return {
190
+ host: 'bitbucket-server',
191
+ projectKey: parts[0],
192
+ repoSlug: parts[1],
193
+ hostname,
194
+ };
195
+ }
196
+ return { host: 'unknown', url: rawUrl };
197
+ }
198
+ default:
199
+ return { host: 'unknown', url: rawUrl };
200
+ }
201
+ }
202
+ /**
203
+ * Build the API base URL for a given remote.
204
+ *
205
+ * @param remote - The parsed remote info.
206
+ * @param apiUrlOverride - Override from `CLANCY_GIT_API_URL` env var.
207
+ * @returns The API base URL, or undefined if not applicable.
208
+ */
209
+ export function buildApiBaseUrl(remote, apiUrlOverride) {
210
+ if (apiUrlOverride)
211
+ return apiUrlOverride.replace(/\/$/, '');
212
+ switch (remote.host) {
213
+ case 'github':
214
+ return remote.hostname === 'github.com'
215
+ ? 'https://api.github.com'
216
+ : `https://${remote.hostname}/api/v3`;
217
+ case 'gitlab':
218
+ return `https://${remote.hostname}/api/v4`;
219
+ case 'bitbucket':
220
+ return 'https://api.bitbucket.org/2.0';
221
+ case 'bitbucket-server':
222
+ return `https://${remote.hostname}/rest/api/1.0`;
223
+ default:
224
+ return undefined;
225
+ }
226
+ }
227
+ //# sourceMappingURL=remote.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote.js","sourceRoot":"","sources":["../../../../src/scripts/shared/remote/remote.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
1
  export type { BoardProvider, Ticket } from './board.js';
2
+ export type { GitPlatform, PrCreationResult, ProgressStatus, RemoteInfo, } from './remote.js';
2
3
  export type { BoardConfig, GitHubEnv, JiraEnv, LinearEnv, SharedEnv, } from '../schemas/env.js';
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACxD,YAAY,EACV,WAAW,EACX,SAAS,EACT,OAAO,EACP,SAAS,EACT,SAAS,GACV,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACxD,YAAY,EACV,WAAW,EACX,gBAAgB,EAChB,cAAc,EACd,UAAU,GACX,MAAM,aAAa,CAAC;AACrB,YAAY,EACV,WAAW,EACX,SAAS,EACT,OAAO,EACP,SAAS,EACT,SAAS,GACV,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Remote git hosting types shared across remote detection and PR creation.
3
+ */
4
+ /** Supported git hosting platforms. */
5
+ export type GitPlatform = 'github' | 'gitlab' | 'bitbucket' | 'bitbucket-server' | 'azure' | 'unknown';
6
+ /** Parsed remote URL with platform and path info. */
7
+ export type RemoteInfo = {
8
+ host: 'github';
9
+ owner: string;
10
+ repo: string;
11
+ hostname: string;
12
+ } | {
13
+ host: 'gitlab';
14
+ projectPath: string;
15
+ hostname: string;
16
+ } | {
17
+ host: 'bitbucket';
18
+ workspace: string;
19
+ repoSlug: string;
20
+ hostname: string;
21
+ } | {
22
+ host: 'bitbucket-server';
23
+ projectKey: string;
24
+ repoSlug: string;
25
+ hostname: string;
26
+ } | {
27
+ host: 'azure' | 'unknown';
28
+ url: string;
29
+ } | {
30
+ host: 'none';
31
+ };
32
+ /** Result of a PR/MR creation attempt. */
33
+ export type PrCreationResult = {
34
+ ok: true;
35
+ url: string;
36
+ number: number;
37
+ } | {
38
+ ok: false;
39
+ error: string;
40
+ alreadyExists?: boolean;
41
+ };
42
+ /** Progress log status values. */
43
+ export type ProgressStatus = 'DONE' | 'SKIPPED' | 'PR_CREATED' | 'PUSHED' | 'PUSH_FAILED' | 'LOCAL' | 'PLAN' | 'APPROVE' | 'REWORK';
44
+ //# sourceMappingURL=remote.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote.d.ts","sourceRoot":"","sources":["../../src/types/remote.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,uCAAuC;AACvC,MAAM,MAAM,WAAW,GACnB,QAAQ,GACR,QAAQ,GACR,WAAW,GACX,kBAAkB,GAClB,OAAO,GACP,SAAS,CAAC;AAEd,qDAAqD;AACrD,MAAM,MAAM,UAAU,GAClB;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB,GACD;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB,GACD;IACE,IAAI,EAAE,WAAW,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,GACD;IACE,IAAI,EAAE,kBAAkB,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,GACD;IACE,IAAI,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;CACb,GACD;IACE,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEN,0CAA0C;AAC1C,MAAM,MAAM,gBAAgB,GACxB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACzC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAE1D,kCAAkC;AAClC,MAAM,MAAM,cAAc,GACtB,MAAM,GACN,SAAS,GACT,YAAY,GACZ,QAAQ,GACR,aAAa,GACb,OAAO,GACP,MAAM,GACN,SAAS,GACT,QAAQ,CAAC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Remote git hosting types shared across remote detection and PR creation.
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=remote.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote.js","sourceRoot":"","sources":["../../src/types/remote.ts"],"names":[],"mappings":"AAAA;;GAEG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chief-clancy",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
4
4
  "description": "Autonomous, board-driven development for Claude Code — scaffolds docs, integrates Kanban boards, runs tickets in a loop.",
5
5
  "keywords": [
6
6
  "claude",
@@ -207,6 +207,56 @@ Never silently continue with unverified credentials — the user must explicitly
207
207
 
208
208
  ---
209
209
 
210
+ ### Q2c (Jira and Linear only): Git host token
211
+
212
+ When the board is **Jira** or **Linear**, Clancy needs a git host token to create pull requests after implementation. Skip this step entirely for **GitHub Issues** — the `GITHUB_TOKEN` collected in Q2 already covers PR creation.
213
+
214
+ Output:
215
+
216
+ ```
217
+ Clancy can push your feature branch and create a pull request automatically.
218
+ Which git host does this project use?
219
+
220
+ [1] GitHub
221
+ [2] GitLab
222
+ [3] Bitbucket
223
+ [4] Skip — I'll push and create PRs manually
224
+ ```
225
+
226
+ **If [1] GitHub:**
227
+
228
+ `Paste your GitHub personal access token: (needs repo scope — create at github.com/settings/tokens)`
229
+
230
+ Store as `GITHUB_TOKEN` in `.clancy/.env`.
231
+
232
+ Verify by calling `GET https://api.github.com/user` with `Authorization: Bearer {token}` and `X-GitHub-Api-Version: 2022-11-28`.
233
+
234
+ On success: `✅ GitHub connected — {login}`
235
+ On failure: offer re-enter or skip (same pattern as Q2b).
236
+
237
+ **If [2] GitLab:**
238
+
239
+ `Paste your GitLab personal access token: (needs api scope — create at gitlab.com/-/user_settings/personal_access_tokens)`
240
+
241
+ Store as `GITLAB_TOKEN` in `.clancy/.env`.
242
+
243
+ If the user is using a self-hosted GitLab instance, also ask:
244
+ `What's your GitLab API base URL? (e.g. https://gitlab.example.com/api/v4 — press Enter for gitlab.com)`
245
+
246
+ If a URL is entered, store as `CLANCY_GIT_API_URL` in `.clancy/.env` and `CLANCY_GIT_PLATFORM="gitlab"`.
247
+ If the user enters just a hostname or instance URL without `/api/v4`, append `/api/v4` automatically.
248
+
249
+ **If [3] Bitbucket:**
250
+
251
+ 1. `What's your Bitbucket username? (your Atlassian account username)`
252
+ 2. `Paste your Bitbucket app password: (needs repository:write scope — create at bitbucket.org/account/settings/app-passwords)`
253
+
254
+ Store as `BITBUCKET_USER` and `BITBUCKET_TOKEN` in `.clancy/.env`.
255
+
256
+ **If [4] Skip:** no git host token is written. Clancy will still implement tickets but leave the feature branch for the user to push and create PRs manually.
257
+
258
+ ---
259
+
210
260
  ### Q3 (Jira only): Status name
211
261
 
212
262
  Output:
@@ -315,6 +365,47 @@ You can always configure these later via `/clancy:settings`.
315
365
 
316
366
  ---
317
367
 
368
+ ### Q3d-2 (Jira and Linear only): Review status
369
+
370
+ Only ask this if a git host token was configured in Q2c (i.e. the user didn't skip PR creation).
371
+
372
+ **GitHub:** Skip entirely — not applicable (GitHub Issues don't have workflow states).
373
+
374
+ **Jira:** Output:
375
+
376
+ ```
377
+ When Clancy creates a pull request, it can transition the ticket to a review status.
378
+
379
+ What transition should Clancy use after creating a PR?
380
+
381
+ [1] In Review
382
+ [2] Ready for Review
383
+ [3] Enter a different value
384
+ [4] Skip — use the same status as completion (CLANCY_STATUS_DONE)
385
+ ```
386
+
387
+ If [1]: store `CLANCY_STATUS_REVIEW="In Review"` in `.clancy/.env`.
388
+ If [2]: store `CLANCY_STATUS_REVIEW="Ready for Review"` in `.clancy/.env`.
389
+ If [3]: prompt for the value, store as `CLANCY_STATUS_REVIEW` in `.clancy/.env`. Wrap in double quotes.
390
+ If [4] or the user says "skip"/"none": skip — no `CLANCY_STATUS_REVIEW` line written (falls back to `CLANCY_STATUS_DONE`).
391
+
392
+ **Linear:** Output:
393
+
394
+ ```
395
+ When Clancy creates a pull request, it can move the issue to a review state.
396
+
397
+ What state should Clancy move an issue to after creating a PR?
398
+
399
+ [1] In Review
400
+ [2] Ready for Review
401
+ [3] Enter a different value
402
+ [4] Skip — use the same state as completion (CLANCY_STATUS_DONE)
403
+ ```
404
+
405
+ Same storage logic as Jira above.
406
+
407
+ ---
408
+
318
409
  ### Q4: Base branch (auto-detect)
319
410
 
320
411
  Silently detect the base branch — do not ask unless detection fails:
@@ -383,6 +383,18 @@ MAX_ITERATIONS=5
383
383
  # "Done" can be any transition to a post-implementation status.
384
384
  # CLANCY_STATUS_IN_PROGRESS="In Progress"
385
385
  # CLANCY_STATUS_DONE="Done"
386
+ # CLANCY_STATUS_REVIEW="In Review" # used when creating a PR instead of merging locally
387
+
388
+ # ─── Optional: Git host (PR creation) ───────────────────────────────────────
389
+ # When a ticket has no parent epic, Clancy pushes the feature branch and creates
390
+ # a pull request instead of squash-merging locally. Requires a git host token.
391
+ # GitHub Issues users already have GITHUB_TOKEN above — no extra config needed.
392
+ # GITHUB_TOKEN=ghp_your-token # if your git host is GitHub
393
+ # GITLAB_TOKEN=glpat-your-token # if your git host is GitLab
394
+ # BITBUCKET_USER=your-username # if your git host is Bitbucket
395
+ # BITBUCKET_TOKEN=your-app-password # if your git host is Bitbucket
396
+ # CLANCY_GIT_PLATFORM=gitlab # override auto-detection (github/gitlab/bitbucket)
397
+ # CLANCY_GIT_API_URL=https://gitlab.example.com/api/v4 # self-hosted git API base URL
386
398
 
387
399
  # ─── Optional: Planner queue ─────────────────────────────────────────────────
388
400
  # Status for backlog tickets that /clancy:plan fetches from (default: Backlog)
@@ -419,6 +431,10 @@ GITHUB_REPO=owner/repo-name
419
431
  # When an issue has a milestone, Clancy auto-creates milestone/{slug} from this branch.
420
432
  CLANCY_BASE_BRANCH=main
421
433
 
434
+ # ─── PR creation ─────────────────────────────────────────────────────────────
435
+ # When an issue has no milestone, Clancy pushes the feature branch and creates a
436
+ # PR using your GITHUB_TOKEN above. No extra config needed for GitHub Issues users.
437
+
422
438
  # ─── Loop ─────────────────────────────────────────────────────────────────────
423
439
  # Max tickets to process per /clancy:run session (default: 20)
424
440
  MAX_ITERATIONS=20
@@ -495,6 +511,17 @@ MAX_ITERATIONS=20
495
511
  # "Done" can be any post-implementation state (e.g. "Ready for Review", "In Review").
496
512
  # CLANCY_STATUS_IN_PROGRESS="In Progress"
497
513
  # CLANCY_STATUS_DONE="Done"
514
+ # CLANCY_STATUS_REVIEW="In Review" # used when creating a PR instead of merging locally
515
+
516
+ # ─── Optional: Git host (PR creation) ───────────────────────────────────────
517
+ # When an issue has no parent, Clancy pushes the feature branch and creates a
518
+ # pull request instead of squash-merging locally. Requires a git host token.
519
+ # GITHUB_TOKEN=ghp_your-token # if your git host is GitHub
520
+ # GITLAB_TOKEN=glpat-your-token # if your git host is GitLab
521
+ # BITBUCKET_USER=your-username # if your git host is Bitbucket
522
+ # BITBUCKET_TOKEN=your-app-password # if your git host is Bitbucket
523
+ # CLANCY_GIT_PLATFORM=gitlab # override auto-detection (github/gitlab/bitbucket)
524
+ # CLANCY_GIT_API_URL=https://gitlab.example.com/api/v4 # self-hosted git API base URL
498
525
 
499
526
  # ─── Optional: Notifications ──────────────────────────────────────────────────
500
527
  # Webhook URL for Slack or Teams notifications on ticket completion