@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.
Files changed (49) hide show
  1. package/package.json +2 -1
  2. package/templates/assistkick-product-system/packages/backend/src/routes/git.ts +231 -0
  3. package/templates/assistkick-product-system/packages/backend/src/routes/kanban.ts +4 -4
  4. package/templates/assistkick-product-system/packages/backend/src/routes/pipeline.ts +49 -2
  5. package/templates/assistkick-product-system/packages/backend/src/routes/terminal.ts +82 -0
  6. package/templates/assistkick-product-system/packages/backend/src/server.ts +19 -6
  7. package/templates/assistkick-product-system/packages/backend/src/services/github_app_service.ts +146 -0
  8. package/templates/assistkick-product-system/packages/backend/src/services/init.ts +69 -2
  9. package/templates/assistkick-product-system/packages/backend/src/services/project_service.ts +71 -0
  10. package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.test.ts +87 -0
  11. package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.ts +194 -0
  12. package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.test.ts +88 -17
  13. package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.ts +114 -39
  14. package/templates/assistkick-product-system/packages/backend/src/services/terminal_ws_handler.ts +28 -14
  15. package/templates/assistkick-product-system/packages/frontend/src/App.tsx +1 -1
  16. package/templates/assistkick-product-system/packages/frontend/src/api/client.ts +151 -0
  17. package/templates/assistkick-product-system/packages/frontend/src/components/GitRepoModal.tsx +352 -0
  18. package/templates/assistkick-product-system/packages/frontend/src/components/KanbanView.tsx +208 -95
  19. package/templates/assistkick-product-system/packages/frontend/src/components/ProjectSelector.tsx +17 -1
  20. package/templates/assistkick-product-system/packages/frontend/src/components/TerminalView.tsx +238 -105
  21. package/templates/assistkick-product-system/packages/frontend/src/components/Toolbar.tsx +15 -13
  22. package/templates/assistkick-product-system/packages/frontend/src/constants/graph.ts +1 -0
  23. package/templates/assistkick-product-system/packages/frontend/src/hooks/useProjects.ts +4 -0
  24. package/templates/assistkick-product-system/packages/frontend/src/routes/dashboard.tsx +22 -4
  25. package/templates/assistkick-product-system/packages/frontend/src/styles/index.css +486 -38
  26. package/templates/assistkick-product-system/packages/shared/db/migrations/0001_vengeful_wallop.sql +1 -0
  27. package/templates/assistkick-product-system/packages/shared/db/migrations/0002_greedy_excalibur.sql +4 -0
  28. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0001_snapshot.json +826 -0
  29. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0002_snapshot.json +854 -0
  30. package/templates/assistkick-product-system/packages/shared/db/migrations/meta/_journal.json +14 -0
  31. package/templates/assistkick-product-system/packages/shared/db/schema.ts +5 -0
  32. package/templates/assistkick-product-system/packages/shared/lib/claude-service.ts +54 -1
  33. package/templates/assistkick-product-system/packages/shared/lib/git_workflow.ts +25 -0
  34. package/templates/assistkick-product-system/packages/shared/lib/pipeline-state-store.ts +4 -0
  35. package/templates/assistkick-product-system/packages/shared/lib/pipeline.ts +329 -89
  36. package/templates/assistkick-product-system/packages/shared/lib/pipeline_orchestrator.ts +186 -0
  37. package/templates/assistkick-product-system/packages/shared/tools/db_explorer.ts +275 -0
  38. package/templates/assistkick-product-system/packages/shared/tools/get_kanban.ts +2 -1
  39. package/templates/assistkick-product-system/packages/shared/tools/move_card.ts +3 -2
  40. package/templates/assistkick-product-system/packages/shared/tools/update_node.ts +2 -2
  41. package/templates/assistkick-product-system/tests/kanban.test.ts +1 -1
  42. package/templates/assistkick-product-system/tests/pipeline_stats_all_cards.test.ts +1 -1
  43. package/templates/assistkick-product-system/tests/web_terminal.test.ts +189 -150
  44. package/templates/skills/assistkick-bootstrap/SKILL.md +33 -25
  45. package/templates/skills/assistkick-code-reviewer/SKILL.md +23 -15
  46. package/templates/skills/assistkick-db-explorer/SKILL.md +86 -0
  47. package/templates/skills/assistkick-debugger/SKILL.md +30 -22
  48. package/templates/skills/assistkick-developer/SKILL.md +37 -29
  49. package/templates/skills/assistkick-interview/SKILL.md +34 -26
@@ -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
  };