@callumvass/forgeflow-dev 0.1.0 → 0.3.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.
- package/extensions/index.js +54 -13
- package/package.json +8 -2
- package/src/index.ts +0 -380
- package/src/pipelines/architecture.ts +0 -67
- package/src/pipelines/discover-skills.ts +0 -33
- package/src/pipelines/implement-all.ts +0 -181
- package/src/pipelines/implement.ts +0 -305
- package/src/pipelines/review.ts +0 -183
- package/src/resolve.ts +0 -6
- package/src/utils/exec.ts +0 -13
- package/src/utils/git.ts +0 -132
- package/src/utils/ui.ts +0 -29
- package/tsconfig.json +0 -12
- package/tsconfig.tsbuildinfo +0 -1
- package/tsup.config.ts +0 -15
package/src/utils/git.ts
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import { exec } from "./exec.js";
|
|
2
|
-
|
|
3
|
-
export interface ResolvedIssue {
|
|
4
|
-
source: "github" | "jira";
|
|
5
|
-
key: string; // "42" for GH, "CUS-123" for Jira
|
|
6
|
-
number: number; // GH issue number, 0 for Jira
|
|
7
|
-
title: string;
|
|
8
|
-
body: string;
|
|
9
|
-
branch: string;
|
|
10
|
-
existingPR?: number;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function slugify(text: string, maxLen = 40): string {
|
|
14
|
-
return text
|
|
15
|
-
.toLowerCase()
|
|
16
|
-
.replace(/[^a-z0-9]+/g, "-")
|
|
17
|
-
.replace(/^-|-$/g, "")
|
|
18
|
-
.slice(0, maxLen)
|
|
19
|
-
.replace(/-$/, "");
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const JIRA_KEY_RE = /^[A-Z]+-\d+$/;
|
|
23
|
-
const JIRA_BRANCH_RE = /feat\/([A-Z]+-\d+)/;
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Checkout a branch, creating it if it doesn't exist.
|
|
27
|
-
*/
|
|
28
|
-
export async function ensureBranch(cwd: string, branch: string): Promise<void> {
|
|
29
|
-
const currentBranch = await exec("git branch --show-current", cwd);
|
|
30
|
-
if (currentBranch === branch) return;
|
|
31
|
-
const exists = await exec(`git rev-parse --verify ${branch} 2>/dev/null && echo yes || echo no`, cwd);
|
|
32
|
-
if (exists === "yes") {
|
|
33
|
-
await exec(`git checkout ${branch}`, cwd);
|
|
34
|
-
} else {
|
|
35
|
-
await exec(`git checkout -b ${branch}`, cwd);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Resolve which issue to implement:
|
|
41
|
-
* 1. Jira key (CUS-123) → fetch from jira-cli
|
|
42
|
-
* 2. Numeric GitHub issue → fetch from gh
|
|
43
|
-
* 3. On a feature branch → extract from branch name
|
|
44
|
-
*/
|
|
45
|
-
export async function resolveIssue(cwd: string, issueArg?: string): Promise<ResolvedIssue | string> {
|
|
46
|
-
// Explicit Jira key
|
|
47
|
-
if (issueArg && JIRA_KEY_RE.test(issueArg)) {
|
|
48
|
-
return resolveJiraIssue(cwd, issueArg);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Explicit GitHub issue number
|
|
52
|
-
if (issueArg && /^\d+$/.test(issueArg)) {
|
|
53
|
-
return resolveGitHubIssue(cwd, parseInt(issueArg, 10));
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Free-text description (not a number or Jira key)
|
|
57
|
-
if (issueArg) {
|
|
58
|
-
return { source: "github", key: "", number: 0, title: issueArg, body: issueArg, branch: "" };
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Detect from branch name
|
|
62
|
-
const branch = await exec("git branch --show-current", cwd);
|
|
63
|
-
|
|
64
|
-
const jiraMatch = branch.match(JIRA_BRANCH_RE);
|
|
65
|
-
if (jiraMatch) {
|
|
66
|
-
// biome-ignore lint/style/noNonNullAssertion: match[1] guaranteed by regex
|
|
67
|
-
return resolveJiraIssue(cwd, jiraMatch[1]!, branch);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const ghMatch = branch.match(/(?:feat\/)?issue-(\d+)/);
|
|
71
|
-
if (ghMatch) {
|
|
72
|
-
// biome-ignore lint/style/noNonNullAssertion: match[1] guaranteed by regex
|
|
73
|
-
return resolveGitHubIssue(cwd, parseInt(ghMatch[1]!, 10));
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return `On branch "${branch}" — can't detect issue. Use /implement <issue#> or /implement <JIRA-KEY>.`;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async function resolveGitHubIssue(cwd: string, issueNum: number): Promise<ResolvedIssue | string> {
|
|
80
|
-
const issueJson = await exec(`gh issue view ${issueNum} --json number,title,body`, cwd);
|
|
81
|
-
if (!issueJson) return `Could not fetch issue #${issueNum}.`;
|
|
82
|
-
|
|
83
|
-
let issue: { number: number; title: string; body: string };
|
|
84
|
-
try {
|
|
85
|
-
issue = JSON.parse(issueJson);
|
|
86
|
-
} catch {
|
|
87
|
-
return `Could not parse issue #${issueNum}.`;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const branch = `feat/issue-${issueNum}`;
|
|
91
|
-
const prJson = await exec(`gh pr list --head "${branch}" --json number --jq '.[0].number'`, cwd);
|
|
92
|
-
const existingPR = prJson && prJson !== "null" ? parseInt(prJson, 10) : undefined;
|
|
93
|
-
|
|
94
|
-
return { source: "github", key: String(issueNum), ...issue, branch, existingPR };
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
async function resolveJiraIssue(
|
|
98
|
-
cwd: string,
|
|
99
|
-
jiraKey: string,
|
|
100
|
-
existingBranch?: string,
|
|
101
|
-
): Promise<ResolvedIssue | string> {
|
|
102
|
-
const raw = await exec(`jira issue view ${jiraKey} --raw`, cwd);
|
|
103
|
-
if (!raw) return `Could not fetch Jira issue ${jiraKey}.`;
|
|
104
|
-
|
|
105
|
-
// biome-ignore lint/suspicious/noExplicitAny: Jira JSON shape varies by instance
|
|
106
|
-
let data: any;
|
|
107
|
-
try {
|
|
108
|
-
data = JSON.parse(raw);
|
|
109
|
-
} catch {
|
|
110
|
-
return `Could not parse Jira issue ${jiraKey}.`;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const fields = data.fields ?? {};
|
|
114
|
-
const title = fields.summary ?? jiraKey;
|
|
115
|
-
|
|
116
|
-
// Build body from available Jira fields
|
|
117
|
-
const bodyParts: string[] = [];
|
|
118
|
-
if (fields.description) bodyParts.push(fields.description);
|
|
119
|
-
if (fields.acceptance_criteria) bodyParts.push(`## Acceptance Criteria\n${fields.acceptance_criteria}`);
|
|
120
|
-
if (fields.status?.name) bodyParts.push(`**Status:** ${fields.status.name}`);
|
|
121
|
-
if (fields.priority?.name) bodyParts.push(`**Priority:** ${fields.priority.name}`);
|
|
122
|
-
if (fields.story_points != null) bodyParts.push(`**Story Points:** ${fields.story_points}`);
|
|
123
|
-
if (fields.sprint?.name) bodyParts.push(`**Sprint:** ${fields.sprint.name}`);
|
|
124
|
-
|
|
125
|
-
const body = bodyParts.join("\n\n");
|
|
126
|
-
const branch = existingBranch ?? `feat/${jiraKey}-${slugify(title)}`;
|
|
127
|
-
|
|
128
|
-
const prJson = await exec(`gh pr list --head "${branch}" --json number --jq '.[0].number'`, cwd);
|
|
129
|
-
const existingPR = prJson && prJson !== "null" ? parseInt(prJson, 10) : undefined;
|
|
130
|
-
|
|
131
|
-
return { source: "jira", key: jiraKey, number: 0, title, body, branch, existingPR };
|
|
132
|
-
}
|
package/src/utils/ui.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import type { AnyCtx } from "@callumvass/forgeflow-shared";
|
|
2
|
-
|
|
3
|
-
export function setForgeflowStatus(ctx: AnyCtx, text: string | undefined): void {
|
|
4
|
-
if (ctx.hasUI) ctx.ui.setStatus("forgeflow-dev", text);
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function setForgeflowWidget(ctx: AnyCtx, lines: string[] | undefined): void {
|
|
8
|
-
if (ctx.hasUI) ctx.ui.setWidget("forgeflow-dev", lines);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function updateProgressWidget(
|
|
12
|
-
ctx: AnyCtx,
|
|
13
|
-
progress: Map<number, { title: string; status: string }>,
|
|
14
|
-
totalCost: number,
|
|
15
|
-
): void {
|
|
16
|
-
let done = 0;
|
|
17
|
-
for (const [, info] of progress) {
|
|
18
|
-
if (info.status === "done") done++;
|
|
19
|
-
}
|
|
20
|
-
let header = `implement-all · ${done}/${progress.size}`;
|
|
21
|
-
if (totalCost > 0) header += ` · $${totalCost.toFixed(2)}`;
|
|
22
|
-
const lines: string[] = [header];
|
|
23
|
-
for (const [num, info] of progress) {
|
|
24
|
-
const icon = info.status === "done" ? "✓" : info.status === "running" ? "⟳" : info.status === "failed" ? "✗" : "○";
|
|
25
|
-
const title = info.title.length > 50 ? `${info.title.slice(0, 50)}...` : info.title;
|
|
26
|
-
lines.push(` ${icon} #${num} ${title}`);
|
|
27
|
-
}
|
|
28
|
-
setForgeflowWidget(ctx, lines);
|
|
29
|
-
}
|