golem-cc 1.0.2 → 2.1.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.
- package/README.md +19 -7
- package/bin/golem +211 -58
- package/commands/golem/build.md +142 -77
- package/commands/golem/help.md +177 -37
- package/commands/golem/plan.md +102 -44
- package/commands/golem/review.md +376 -0
- package/commands/golem/security.md +204 -0
- package/commands/golem/spec.md +93 -50
- package/golem/prompts/PROMPT_build.md +53 -49
- package/package.json +11 -3
- package/.github/workflows/publish.yml +0 -42
- package/.golem/IMPLEMENTATION_PLAN.md +0 -59
- package/bin/install.sh +0 -69
- package/docs/FRESHSERVICE_SETUP.md +0 -338
- package/docs/HANDOFF.md +0 -241
- package/src/api/freshworks.ts +0 -167
- package/src/api/gitea.ts +0 -215
- package/src/cli/index.ts +0 -370
- package/src/sync/ticket-sync.ts +0 -303
- package/src/types.ts +0 -152
- package/src/worktree/manager.ts +0 -236
- package/tsconfig.json +0 -18
package/src/types.ts
DELETED
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Core types for golem workflow
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
// Ticket status across systems
|
|
6
|
-
export type TicketStatus =
|
|
7
|
-
| 'new' // Just created
|
|
8
|
-
| 'spec' // Defining specs
|
|
9
|
-
| 'planning' // Creating implementation plan
|
|
10
|
-
| 'in-progress' // Building
|
|
11
|
-
| 'review' // PR created, awaiting review
|
|
12
|
-
| 'done' // Merged and deployed
|
|
13
|
-
| 'blocked'; // Waiting on external dependency
|
|
14
|
-
|
|
15
|
-
// Conventional commit types
|
|
16
|
-
export type CommitType = 'feat' | 'fix' | 'refactor' | 'docs' | 'test' | 'chore';
|
|
17
|
-
|
|
18
|
-
// The local source of truth linking Fresh ↔ Gitea ↔ Local
|
|
19
|
-
export interface TicketState {
|
|
20
|
-
// Identifiers
|
|
21
|
-
id: string; // Local ID (usually Fresh ticket ID)
|
|
22
|
-
slug: string; // Human-readable slug for branch name
|
|
23
|
-
|
|
24
|
-
// Freshworks
|
|
25
|
-
fresh: {
|
|
26
|
-
id: string; // e.g., "INC-1234"
|
|
27
|
-
url: string;
|
|
28
|
-
subject: string;
|
|
29
|
-
description: string;
|
|
30
|
-
priority: 1 | 2 | 3 | 4; // 1=Urgent, 4=Low
|
|
31
|
-
status: number; // Fresh status codes
|
|
32
|
-
} | null;
|
|
33
|
-
|
|
34
|
-
// Gitea
|
|
35
|
-
gitea: {
|
|
36
|
-
repo: string; // e.g., "CRDE/dashboard-api"
|
|
37
|
-
issueNumber: number;
|
|
38
|
-
url: string;
|
|
39
|
-
prNumber?: number;
|
|
40
|
-
prUrl?: string;
|
|
41
|
-
} | null;
|
|
42
|
-
|
|
43
|
-
// Local git
|
|
44
|
-
git: {
|
|
45
|
-
worktree: string; // Path to worktree
|
|
46
|
-
branch: string; // Branch name
|
|
47
|
-
commits: string[]; // Commit SHAs for this ticket
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
// Workflow state
|
|
51
|
-
status: TicketStatus;
|
|
52
|
-
type: CommitType;
|
|
53
|
-
created: string; // ISO timestamp
|
|
54
|
-
updated: string; // ISO timestamp
|
|
55
|
-
|
|
56
|
-
// Spec/plan references
|
|
57
|
-
specFile?: string; // Path to spec file
|
|
58
|
-
planFile?: string; // Path to implementation plan
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Freshservice API types (subset we care about)
|
|
62
|
-
export interface FreshTicket {
|
|
63
|
-
id: number;
|
|
64
|
-
subject: string;
|
|
65
|
-
description: string;
|
|
66
|
-
description_text: string;
|
|
67
|
-
status: number;
|
|
68
|
-
priority: 1 | 2 | 3 | 4;
|
|
69
|
-
ticket_type: 'Incident' | 'Service Request';
|
|
70
|
-
created_at: string;
|
|
71
|
-
updated_at: string;
|
|
72
|
-
requester_id: number;
|
|
73
|
-
responder_id: number;
|
|
74
|
-
custom_fields: Record<string, unknown>;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export interface FreshTicketCreatePayload {
|
|
78
|
-
subject: string;
|
|
79
|
-
description: string;
|
|
80
|
-
priority?: 1 | 2 | 3 | 4;
|
|
81
|
-
status?: number;
|
|
82
|
-
source?: number;
|
|
83
|
-
email?: string;
|
|
84
|
-
requester_id?: number;
|
|
85
|
-
responder_id?: number;
|
|
86
|
-
group_id?: number;
|
|
87
|
-
category?: string;
|
|
88
|
-
sub_category?: string;
|
|
89
|
-
resolution_notes?: string;
|
|
90
|
-
custom_fields?: Record<string, unknown>;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Gitea API types (subset we care about)
|
|
94
|
-
export interface GiteaIssue {
|
|
95
|
-
id: number;
|
|
96
|
-
number: number;
|
|
97
|
-
title: string;
|
|
98
|
-
body: string;
|
|
99
|
-
state: 'open' | 'closed';
|
|
100
|
-
html_url: string;
|
|
101
|
-
created_at: string;
|
|
102
|
-
updated_at: string;
|
|
103
|
-
labels: { name: string; color: string }[];
|
|
104
|
-
milestone?: { title: string };
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export interface GiteaIssueCreatePayload {
|
|
108
|
-
title: string;
|
|
109
|
-
body: string;
|
|
110
|
-
labels?: string[];
|
|
111
|
-
milestone?: number;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export interface GiteaPullRequest {
|
|
115
|
-
id: number;
|
|
116
|
-
number: number;
|
|
117
|
-
title: string;
|
|
118
|
-
body: string;
|
|
119
|
-
state: 'open' | 'closed' | 'merged';
|
|
120
|
-
html_url: string;
|
|
121
|
-
head: { ref: string };
|
|
122
|
-
base: { ref: string };
|
|
123
|
-
merged: boolean;
|
|
124
|
-
mergeable: boolean;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// CLI command context
|
|
128
|
-
export interface GolemContext {
|
|
129
|
-
projectRoot: string; // Where .golem/ lives
|
|
130
|
-
golemDir: string; // .golem/ path
|
|
131
|
-
ticketsDir: string; // .golem/tickets/
|
|
132
|
-
specsDir: string; // .golem/specs/
|
|
133
|
-
worktreesDir: string; // .golem/worktrees/
|
|
134
|
-
currentTicket?: TicketState; // If working on a ticket
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Sync operation result
|
|
138
|
-
export interface SyncResult {
|
|
139
|
-
success: boolean;
|
|
140
|
-
freshUpdated: boolean;
|
|
141
|
-
giteaUpdated: boolean;
|
|
142
|
-
localUpdated: boolean;
|
|
143
|
-
error?: string;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Build loop iteration result
|
|
147
|
-
export interface BuildIterationResult {
|
|
148
|
-
taskCompleted: string | null; // Task ID if completed
|
|
149
|
-
allDone: boolean; // No more tasks
|
|
150
|
-
commits: string[]; // Commit SHAs from this iteration
|
|
151
|
-
error?: string;
|
|
152
|
-
}
|
package/src/worktree/manager.ts
DELETED
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Git worktree management
|
|
3
|
-
*
|
|
4
|
-
* Creates isolated worktrees per ticket under .golem/worktrees/
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { execSync, exec } from 'node:child_process';
|
|
8
|
-
import { promisify } from 'node:util';
|
|
9
|
-
import { mkdir, rm, access } from 'node:fs/promises';
|
|
10
|
-
import { join } from 'node:path';
|
|
11
|
-
import type { TicketState } from '../types.js';
|
|
12
|
-
|
|
13
|
-
const execAsync = promisify(exec);
|
|
14
|
-
|
|
15
|
-
export interface WorktreeInfo {
|
|
16
|
-
path: string;
|
|
17
|
-
branch: string;
|
|
18
|
-
commitSha: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Run git command and return stdout
|
|
23
|
-
*/
|
|
24
|
-
function git(command: string, cwd?: string): string {
|
|
25
|
-
return execSync(`git ${command}`, {
|
|
26
|
-
cwd,
|
|
27
|
-
encoding: 'utf-8',
|
|
28
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
29
|
-
}).trim();
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Run git command async
|
|
34
|
-
*/
|
|
35
|
-
async function gitAsync(command: string, cwd?: string): Promise<string> {
|
|
36
|
-
const { stdout } = await execAsync(`git ${command}`, { cwd });
|
|
37
|
-
return stdout.trim();
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Get the root of the current git repository
|
|
42
|
-
*/
|
|
43
|
-
export function getRepoRoot(): string {
|
|
44
|
-
return git('rev-parse --show-toplevel');
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Get the default branch (main or master)
|
|
49
|
-
*/
|
|
50
|
-
export function getDefaultBranch(): string {
|
|
51
|
-
try {
|
|
52
|
-
// Try to get from remote HEAD
|
|
53
|
-
const ref = git('symbolic-ref refs/remotes/origin/HEAD');
|
|
54
|
-
return ref.replace('refs/remotes/origin/', '');
|
|
55
|
-
} catch {
|
|
56
|
-
// Fall back to checking if main or master exists
|
|
57
|
-
try {
|
|
58
|
-
git('rev-parse --verify main');
|
|
59
|
-
return 'main';
|
|
60
|
-
} catch {
|
|
61
|
-
return 'master';
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* List all worktrees
|
|
68
|
-
*/
|
|
69
|
-
export function listWorktrees(): WorktreeInfo[] {
|
|
70
|
-
const output = git('worktree list --porcelain');
|
|
71
|
-
const worktrees: WorktreeInfo[] = [];
|
|
72
|
-
let current: Partial<WorktreeInfo> = {};
|
|
73
|
-
|
|
74
|
-
for (const line of output.split('\n')) {
|
|
75
|
-
if (line.startsWith('worktree ')) {
|
|
76
|
-
current.path = line.replace('worktree ', '');
|
|
77
|
-
} else if (line.startsWith('HEAD ')) {
|
|
78
|
-
current.commitSha = line.replace('HEAD ', '');
|
|
79
|
-
} else if (line.startsWith('branch ')) {
|
|
80
|
-
current.branch = line.replace('branch refs/heads/', '');
|
|
81
|
-
} else if (line === '') {
|
|
82
|
-
if (current.path && current.branch && current.commitSha) {
|
|
83
|
-
worktrees.push(current as WorktreeInfo);
|
|
84
|
-
}
|
|
85
|
-
current = {};
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Handle last entry
|
|
90
|
-
if (current.path && current.branch && current.commitSha) {
|
|
91
|
-
worktrees.push(current as WorktreeInfo);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return worktrees;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Create a worktree for a ticket
|
|
99
|
-
*/
|
|
100
|
-
export async function createWorktree(
|
|
101
|
-
ticket: TicketState,
|
|
102
|
-
options: { baseBranch?: string } = {}
|
|
103
|
-
): Promise<string> {
|
|
104
|
-
const repoRoot = getRepoRoot();
|
|
105
|
-
const baseBranch = options.baseBranch || getDefaultBranch();
|
|
106
|
-
const worktreePath = join(repoRoot, ticket.git.worktree);
|
|
107
|
-
const branch = ticket.git.branch;
|
|
108
|
-
|
|
109
|
-
// Ensure worktrees directory exists
|
|
110
|
-
await mkdir(join(repoRoot, '.golem/worktrees'), { recursive: true });
|
|
111
|
-
|
|
112
|
-
// Check if worktree already exists
|
|
113
|
-
const worktrees = listWorktrees();
|
|
114
|
-
const existing = worktrees.find(w => w.branch === branch);
|
|
115
|
-
if (existing) {
|
|
116
|
-
return existing.path;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Determine the best base ref to use
|
|
120
|
-
let baseRef = baseBranch;
|
|
121
|
-
|
|
122
|
-
// Try to fetch and use remote if available
|
|
123
|
-
try {
|
|
124
|
-
git('fetch origin', repoRoot);
|
|
125
|
-
// Verify origin/baseBranch exists
|
|
126
|
-
git(`rev-parse --verify origin/${baseBranch}`, repoRoot);
|
|
127
|
-
baseRef = `origin/${baseBranch}`;
|
|
128
|
-
} catch {
|
|
129
|
-
// No remote or remote branch doesn't exist, use local branch
|
|
130
|
-
try {
|
|
131
|
-
git(`rev-parse --verify ${baseBranch}`, repoRoot);
|
|
132
|
-
baseRef = baseBranch;
|
|
133
|
-
} catch {
|
|
134
|
-
// Local branch doesn't exist either, use HEAD
|
|
135
|
-
baseRef = 'HEAD';
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Create the worktree with a new branch based on the base ref
|
|
140
|
-
git(`worktree add -b ${branch} "${worktreePath}" ${baseRef}`, repoRoot);
|
|
141
|
-
|
|
142
|
-
return worktreePath;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Remove a worktree
|
|
147
|
-
*/
|
|
148
|
-
export async function removeWorktree(worktreePath: string): Promise<void> {
|
|
149
|
-
const repoRoot = getRepoRoot();
|
|
150
|
-
|
|
151
|
-
// Check if it exists
|
|
152
|
-
try {
|
|
153
|
-
await access(worktreePath);
|
|
154
|
-
} catch {
|
|
155
|
-
return; // Already gone
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Remove the worktree
|
|
159
|
-
git(`worktree remove "${worktreePath}" --force`, repoRoot);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Get current branch in a worktree
|
|
164
|
-
*/
|
|
165
|
-
export function getCurrentBranch(worktreePath: string): string {
|
|
166
|
-
return git('rev-parse --abbrev-ref HEAD', worktreePath);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Get list of commits on branch since it diverged from base
|
|
171
|
-
*/
|
|
172
|
-
export async function getCommitsSinceBase(
|
|
173
|
-
worktreePath: string,
|
|
174
|
-
baseBranch?: string
|
|
175
|
-
): Promise<string[]> {
|
|
176
|
-
const base = baseBranch || getDefaultBranch();
|
|
177
|
-
const output = await gitAsync(
|
|
178
|
-
`log origin/${base}..HEAD --format=%H`,
|
|
179
|
-
worktreePath
|
|
180
|
-
);
|
|
181
|
-
return output ? output.split('\n') : [];
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Squash all commits since base into one
|
|
186
|
-
*/
|
|
187
|
-
export async function squashCommits(
|
|
188
|
-
worktreePath: string,
|
|
189
|
-
message: string,
|
|
190
|
-
baseBranch?: string
|
|
191
|
-
): Promise<string> {
|
|
192
|
-
const base = baseBranch || getDefaultBranch();
|
|
193
|
-
|
|
194
|
-
// Soft reset to merge base, keeping changes staged
|
|
195
|
-
await gitAsync(`reset --soft origin/${base}`, worktreePath);
|
|
196
|
-
|
|
197
|
-
// Create new commit with all changes
|
|
198
|
-
await gitAsync(`commit -m "${message.replace(/"/g, '\\"')}"`, worktreePath);
|
|
199
|
-
|
|
200
|
-
// Return new commit SHA
|
|
201
|
-
return gitAsync('rev-parse HEAD', worktreePath);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Create a commit in the worktree
|
|
206
|
-
*/
|
|
207
|
-
export async function createCommit(
|
|
208
|
-
worktreePath: string,
|
|
209
|
-
message: string
|
|
210
|
-
): Promise<string> {
|
|
211
|
-
// Stage all changes
|
|
212
|
-
await gitAsync('add -A', worktreePath);
|
|
213
|
-
|
|
214
|
-
// Check if there are changes to commit
|
|
215
|
-
try {
|
|
216
|
-
await gitAsync('diff --cached --quiet', worktreePath);
|
|
217
|
-
// No changes
|
|
218
|
-
return '';
|
|
219
|
-
} catch {
|
|
220
|
-
// There are changes, commit them
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
await gitAsync(`commit -m "${message.replace(/"/g, '\\"')}"`, worktreePath);
|
|
224
|
-
return gitAsync('rev-parse HEAD', worktreePath);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Push branch to remote
|
|
229
|
-
*/
|
|
230
|
-
export async function pushBranch(
|
|
231
|
-
worktreePath: string,
|
|
232
|
-
force = false
|
|
233
|
-
): Promise<void> {
|
|
234
|
-
const forceFlag = force ? '--force-with-lease' : '';
|
|
235
|
-
await gitAsync(`push -u origin HEAD ${forceFlag}`, worktreePath);
|
|
236
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "NodeNext",
|
|
5
|
-
"moduleResolution": "NodeNext",
|
|
6
|
-
"outDir": "./dist",
|
|
7
|
-
"rootDir": "./src",
|
|
8
|
-
"strict": true,
|
|
9
|
-
"esModuleInterop": true,
|
|
10
|
-
"skipLibCheck": true,
|
|
11
|
-
"declaration": true,
|
|
12
|
-
"declarationMap": true,
|
|
13
|
-
"sourceMap": true,
|
|
14
|
-
"resolveJsonModule": true
|
|
15
|
-
},
|
|
16
|
-
"include": ["src/**/*"],
|
|
17
|
-
"exclude": ["node_modules", "dist"]
|
|
18
|
-
}
|