@assistkick/create 1.2.0 → 1.3.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/package.json +2 -1
- package/templates/assistkick-product-system/packages/backend/src/routes/git.ts +231 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/kanban.ts +4 -4
- package/templates/assistkick-product-system/packages/backend/src/routes/pipeline.ts +49 -2
- package/templates/assistkick-product-system/packages/backend/src/routes/terminal.ts +82 -0
- package/templates/assistkick-product-system/packages/backend/src/server.ts +19 -6
- package/templates/assistkick-product-system/packages/backend/src/services/github_app_service.ts +146 -0
- package/templates/assistkick-product-system/packages/backend/src/services/init.ts +69 -2
- package/templates/assistkick-product-system/packages/backend/src/services/project_service.ts +71 -0
- package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.test.ts +87 -0
- package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.ts +194 -0
- package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.test.ts +88 -17
- package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.ts +114 -39
- package/templates/assistkick-product-system/packages/backend/src/services/terminal_ws_handler.ts +28 -14
- package/templates/assistkick-product-system/packages/frontend/src/App.tsx +1 -1
- package/templates/assistkick-product-system/packages/frontend/src/api/client.ts +151 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/GitRepoModal.tsx +352 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/KanbanView.tsx +208 -95
- package/templates/assistkick-product-system/packages/frontend/src/components/ProjectSelector.tsx +17 -1
- package/templates/assistkick-product-system/packages/frontend/src/components/TerminalView.tsx +238 -105
- package/templates/assistkick-product-system/packages/frontend/src/components/Toolbar.tsx +15 -13
- package/templates/assistkick-product-system/packages/frontend/src/constants/graph.ts +1 -0
- package/templates/assistkick-product-system/packages/frontend/src/hooks/useProjects.ts +4 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/dashboard.tsx +22 -4
- package/templates/assistkick-product-system/packages/frontend/src/styles/index.css +486 -38
- package/templates/assistkick-product-system/packages/shared/db/migrations/0001_vengeful_wallop.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0002_greedy_excalibur.sql +4 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0001_snapshot.json +826 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0002_snapshot.json +854 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/_journal.json +14 -0
- package/templates/assistkick-product-system/packages/shared/db/schema.ts +5 -0
- package/templates/assistkick-product-system/packages/shared/lib/claude-service.ts +54 -1
- package/templates/assistkick-product-system/packages/shared/lib/git_workflow.ts +25 -0
- package/templates/assistkick-product-system/packages/shared/lib/pipeline-state-store.ts +4 -0
- package/templates/assistkick-product-system/packages/shared/lib/pipeline.ts +329 -89
- package/templates/assistkick-product-system/packages/shared/lib/pipeline_orchestrator.ts +186 -0
- package/templates/assistkick-product-system/packages/shared/tools/db_explorer.ts +275 -0
- package/templates/assistkick-product-system/packages/shared/tools/get_kanban.ts +2 -1
- package/templates/assistkick-product-system/packages/shared/tools/move_card.ts +3 -2
- package/templates/assistkick-product-system/packages/shared/tools/update_node.ts +2 -2
- package/templates/assistkick-product-system/tests/kanban.test.ts +1 -1
- package/templates/assistkick-product-system/tests/pipeline_stats_all_cards.test.ts +1 -1
- package/templates/assistkick-product-system/tests/web_terminal.test.ts +189 -150
- package/templates/skills/assistkick-bootstrap/SKILL.md +33 -25
- package/templates/skills/assistkick-code-reviewer/SKILL.md +23 -15
- package/templates/skills/assistkick-db-explorer/SKILL.md +86 -0
- package/templates/skills/assistkick-debugger/SKILL.md +30 -22
- package/templates/skills/assistkick-developer/SKILL.md +37 -29
- package/templates/skills/assistkick-interview/SKILL.md +34 -26
package/templates/assistkick-product-system/packages/shared/db/migrations/meta/_journal.json
CHANGED
|
@@ -8,6 +8,20 @@
|
|
|
8
8
|
"when": 1772730355833,
|
|
9
9
|
"tag": "0000_dashing_gorgon",
|
|
10
10
|
"breakpoints": true
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"idx": 1,
|
|
14
|
+
"version": "6",
|
|
15
|
+
"when": 1772802625250,
|
|
16
|
+
"tag": "0001_vengeful_wallop",
|
|
17
|
+
"breakpoints": true
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"idx": 2,
|
|
21
|
+
"version": "6",
|
|
22
|
+
"when": 1772807644324,
|
|
23
|
+
"tag": "0002_greedy_excalibur",
|
|
24
|
+
"breakpoints": true
|
|
11
25
|
}
|
|
12
26
|
]
|
|
13
27
|
}
|
|
@@ -119,6 +119,10 @@ export const projects = sqliteTable('projects', {
|
|
|
119
119
|
name: text('name').notNull(),
|
|
120
120
|
isDefault: integer('is_default').notNull().default(0),
|
|
121
121
|
archivedAt: text('archived_at'),
|
|
122
|
+
repoUrl: text('repo_url'),
|
|
123
|
+
githubInstallationId: text('github_installation_id'),
|
|
124
|
+
githubRepoFullName: text('github_repo_full_name'),
|
|
125
|
+
baseBranch: text('base_branch'),
|
|
122
126
|
createdAt: text('created_at').notNull(),
|
|
123
127
|
updatedAt: text('updated_at').notNull(),
|
|
124
128
|
});
|
|
@@ -131,6 +135,7 @@ export const pipelineState = sqliteTable('pipeline_state', {
|
|
|
131
135
|
tasksJson: text('tasks_json'),
|
|
132
136
|
toolCallsJson: text('tool_calls_json'),
|
|
133
137
|
workSummariesJson: text('work_summaries_json'),
|
|
138
|
+
stageStatsJson: text('stage_stats_json'),
|
|
134
139
|
error: text('error'),
|
|
135
140
|
updatedAt: text('updated_at').notNull(),
|
|
136
141
|
projectId: text('project_id'),
|
|
@@ -131,13 +131,62 @@ export const emitToolUseEvents = (jsonStr, callback) => {
|
|
|
131
131
|
}
|
|
132
132
|
};
|
|
133
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Extract result event metadata from a stream-json line and invoke a callback.
|
|
136
|
+
* Fires once when the result event is encountered, providing usage stats.
|
|
137
|
+
*/
|
|
138
|
+
export const emitResultEvent = (jsonStr, callback) => {
|
|
139
|
+
try {
|
|
140
|
+
const event = JSON.parse(jsonStr);
|
|
141
|
+
if (event.type === 'result') {
|
|
142
|
+
const model = event.model
|
|
143
|
+
?? (event.modelUsage ? Object.keys(event.modelUsage)[0] : null);
|
|
144
|
+
// Extract contextWindow from modelUsage (e.g. modelUsage["claude-..."].contextWindow)
|
|
145
|
+
let contextWindow: number | null = null;
|
|
146
|
+
if (event.modelUsage) {
|
|
147
|
+
const modelKey = model ?? Object.keys(event.modelUsage)[0];
|
|
148
|
+
if (modelKey && event.modelUsage[modelKey]?.contextWindow) {
|
|
149
|
+
contextWindow = event.modelUsage[modelKey].contextWindow;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
callback({
|
|
153
|
+
costUsd: event.cost_usd ?? event.total_cost_usd ?? null,
|
|
154
|
+
durationMs: event.duration_ms ?? null,
|
|
155
|
+
numTurns: event.num_turns ?? null,
|
|
156
|
+
stopReason: event.stop_reason ?? null,
|
|
157
|
+
usage: event.usage ?? null,
|
|
158
|
+
model: model ?? null,
|
|
159
|
+
contextWindow,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
} catch {
|
|
163
|
+
// Not valid JSON — skip silently
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Extract per-turn usage from an assistant event's message.usage and invoke a callback.
|
|
169
|
+
* Each assistant event contains usage for THAT turn only. The callback is called
|
|
170
|
+
* for every assistant event, so the last call represents the final turn's usage.
|
|
171
|
+
*/
|
|
172
|
+
export const emitAssistantUsage = (jsonStr, callback) => {
|
|
173
|
+
try {
|
|
174
|
+
const event = JSON.parse(jsonStr);
|
|
175
|
+
if (event.type === 'assistant' && event.message?.usage) {
|
|
176
|
+
callback(event.message.usage);
|
|
177
|
+
}
|
|
178
|
+
} catch {
|
|
179
|
+
// Not valid JSON — skip silently
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
134
183
|
/**
|
|
135
184
|
* Create a Claude CLI invocation service.
|
|
136
185
|
* @param {{ verbose: boolean, log: function }} opts
|
|
137
186
|
* @returns {{ spawnClaude: function, spawnCommand: function }}
|
|
138
187
|
*/
|
|
139
188
|
export const createClaudeService = ({ verbose = false, log: logFn }) => {
|
|
140
|
-
const spawnClaude = (prompt, cwd, label = 'claude', { onToolUse } = {}) => new Promise((resolve, reject) => {
|
|
189
|
+
const spawnClaude = (prompt, cwd, label = 'claude', { onToolUse, onResult, onTurnUsage } = {}) => new Promise((resolve, reject) => {
|
|
141
190
|
const promptPreview = prompt.slice(0, 100).replace(/\n/g, '\\n');
|
|
142
191
|
logFn('CLAUDE', `Spawning ${label} in ${cwd} — prompt length: ${prompt.length} chars`);
|
|
143
192
|
logFn('CLAUDE', `Prompt preview: "${promptPreview}..."`);
|
|
@@ -169,6 +218,8 @@ export const createClaudeService = ({ verbose = false, log: logFn }) => {
|
|
|
169
218
|
if (line.trim()) {
|
|
170
219
|
formatStreamEvent(line.trim(), tag);
|
|
171
220
|
if (onToolUse) emitToolUseEvents(line.trim(), onToolUse);
|
|
221
|
+
if (onResult) emitResultEvent(line.trim(), onResult);
|
|
222
|
+
if (onTurnUsage) emitAssistantUsage(line.trim(), onTurnUsage);
|
|
172
223
|
}
|
|
173
224
|
}
|
|
174
225
|
}
|
|
@@ -192,6 +243,8 @@ export const createClaudeService = ({ verbose = false, log: logFn }) => {
|
|
|
192
243
|
if (verbose && lineBuf.trim()) {
|
|
193
244
|
formatStreamEvent(lineBuf.trim(), tag);
|
|
194
245
|
if (onToolUse) emitToolUseEvents(lineBuf.trim(), onToolUse);
|
|
246
|
+
if (onResult) emitResultEvent(lineBuf.trim(), onResult);
|
|
247
|
+
if (onTurnUsage) emitAssistantUsage(lineBuf.trim(), onTurnUsage);
|
|
195
248
|
}
|
|
196
249
|
if (code === 0) {
|
|
197
250
|
const result = verbose ? extractResultText(stdout) : stdout;
|
|
@@ -107,4 +107,29 @@ export class GitWorkflow {
|
|
|
107
107
|
await this.claudeService.spawnCommand('git', ['stash', 'pop'], this.projectRoot);
|
|
108
108
|
this.log('PIPELINE', `Restored stashed local changes`);
|
|
109
109
|
};
|
|
110
|
+
|
|
111
|
+
/** Pull the default branch before creating a worktree to ensure up-to-date base. */
|
|
112
|
+
pullDefaultBranch = async (branch?: string) => {
|
|
113
|
+
const targetBranch = branch || 'main';
|
|
114
|
+
this.log('PIPELINE', `Pulling ${targetBranch} to ensure up-to-date base...`);
|
|
115
|
+
try {
|
|
116
|
+
await this.claudeService.spawnCommand('git', ['pull', 'origin', targetBranch], this.projectRoot);
|
|
117
|
+
this.log('PIPELINE', `Pull succeeded for ${targetBranch}`);
|
|
118
|
+
} catch (err: any) {
|
|
119
|
+
this.log('PIPELINE', `Pull failed (non-fatal): ${err.message}`);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/** Push the current branch to the remote after a successful merge. */
|
|
124
|
+
pushToRemote = async (branch?: string) => {
|
|
125
|
+
const targetBranch = branch || 'main';
|
|
126
|
+
this.log('PIPELINE', `Pushing ${targetBranch} to remote...`);
|
|
127
|
+
await this.claudeService.spawnCommand('git', ['push', 'origin', targetBranch], this.projectRoot);
|
|
128
|
+
this.log('PIPELINE', `Push succeeded for ${targetBranch}`);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/** Set the remote URL (used for authenticated push/pull with tokens). */
|
|
132
|
+
setRemoteUrl = async (url: string, remote = 'origin') => {
|
|
133
|
+
await this.claudeService.spawnCommand('git', ['remote', 'set-url', remote, url], this.projectRoot);
|
|
134
|
+
};
|
|
110
135
|
}
|
|
@@ -25,6 +25,7 @@ export class PipelineStateStore {
|
|
|
25
25
|
tasksJson: state.tasks ? JSON.stringify(state.tasks.items || []) : null,
|
|
26
26
|
toolCallsJson: state.toolCalls ? JSON.stringify(state.toolCalls) : null,
|
|
27
27
|
workSummariesJson: state.workSummaries ? JSON.stringify(state.workSummaries) : null,
|
|
28
|
+
stageStatsJson: state.stageStats ? JSON.stringify(state.stageStats) : null,
|
|
28
29
|
error: state.error || null,
|
|
29
30
|
updatedAt: new Date().toISOString(),
|
|
30
31
|
projectId: projectId || null,
|
|
@@ -40,6 +41,7 @@ export class PipelineStateStore {
|
|
|
40
41
|
tasksJson: row.tasksJson,
|
|
41
42
|
toolCallsJson: row.toolCallsJson,
|
|
42
43
|
workSummariesJson: row.workSummariesJson,
|
|
44
|
+
stageStatsJson: row.stageStatsJson,
|
|
43
45
|
error: row.error,
|
|
44
46
|
updatedAt: row.updatedAt,
|
|
45
47
|
projectId: row.projectId,
|
|
@@ -62,6 +64,7 @@ export class PipelineStateStore {
|
|
|
62
64
|
const tasks = row.tasksJson ? JSON.parse(row.tasksJson) : [];
|
|
63
65
|
const toolCalls = row.toolCallsJson ? JSON.parse(row.toolCallsJson) : null;
|
|
64
66
|
const rawSummaries = row.workSummariesJson ? JSON.parse(row.workSummariesJson) : [];
|
|
67
|
+
const stageStats = row.stageStatsJson ? JSON.parse(row.stageStatsJson) : null;
|
|
65
68
|
|
|
66
69
|
// Migrate old flat filesChanged format to categorized format
|
|
67
70
|
const workSummaries = rawSummaries.map((ws) => {
|
|
@@ -89,6 +92,7 @@ export class PipelineStateStore {
|
|
|
89
92
|
} : undefined,
|
|
90
93
|
toolCalls: toolCalls && toolCalls.total > 0 ? toolCalls : undefined,
|
|
91
94
|
workSummaries: workSummaries.length > 0 ? workSummaries : undefined,
|
|
95
|
+
stageStats: stageStats || undefined,
|
|
92
96
|
updatedAt: row.updatedAt,
|
|
93
97
|
};
|
|
94
98
|
};
|