@assistkick/create 1.0.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 (178) hide show
  1. package/dist/bin/create.d.ts +2 -0
  2. package/dist/bin/create.js +25 -0
  3. package/dist/bin/create.js.map +1 -0
  4. package/dist/src/scaffolder.d.ts +22 -0
  5. package/dist/src/scaffolder.js +120 -0
  6. package/dist/src/scaffolder.js.map +1 -0
  7. package/package.json +24 -0
  8. package/templates/product-system/.env.example +8 -0
  9. package/templates/product-system/CLAUDE.md +45 -0
  10. package/templates/product-system/package.json +32 -0
  11. package/templates/product-system/packages/backend/package.json +37 -0
  12. package/templates/product-system/packages/backend/src/middleware/auth_middleware.test.ts +86 -0
  13. package/templates/product-system/packages/backend/src/middleware/auth_middleware.ts +35 -0
  14. package/templates/product-system/packages/backend/src/routes/auth.ts +463 -0
  15. package/templates/product-system/packages/backend/src/routes/coherence.ts +187 -0
  16. package/templates/product-system/packages/backend/src/routes/graph.ts +67 -0
  17. package/templates/product-system/packages/backend/src/routes/kanban.ts +201 -0
  18. package/templates/product-system/packages/backend/src/routes/pipeline.ts +41 -0
  19. package/templates/product-system/packages/backend/src/routes/projects.ts +122 -0
  20. package/templates/product-system/packages/backend/src/routes/users.ts +97 -0
  21. package/templates/product-system/packages/backend/src/server.ts +159 -0
  22. package/templates/product-system/packages/backend/src/services/auth_service.test.ts +115 -0
  23. package/templates/product-system/packages/backend/src/services/auth_service.ts +82 -0
  24. package/templates/product-system/packages/backend/src/services/coherence-review.ts +339 -0
  25. package/templates/product-system/packages/backend/src/services/email_service.ts +75 -0
  26. package/templates/product-system/packages/backend/src/services/init.ts +80 -0
  27. package/templates/product-system/packages/backend/src/services/invitation_service.test.ts +235 -0
  28. package/templates/product-system/packages/backend/src/services/invitation_service.ts +193 -0
  29. package/templates/product-system/packages/backend/src/services/password_reset_service.test.ts +151 -0
  30. package/templates/product-system/packages/backend/src/services/password_reset_service.ts +135 -0
  31. package/templates/product-system/packages/backend/src/services/project_service.test.ts +215 -0
  32. package/templates/product-system/packages/backend/src/services/project_service.ts +171 -0
  33. package/templates/product-system/packages/backend/src/services/pty_session_manager.test.ts +88 -0
  34. package/templates/product-system/packages/backend/src/services/pty_session_manager.ts +279 -0
  35. package/templates/product-system/packages/backend/src/services/terminal_ws_handler.ts +133 -0
  36. package/templates/product-system/packages/backend/src/services/user_management_service.test.ts +158 -0
  37. package/templates/product-system/packages/backend/src/services/user_management_service.ts +128 -0
  38. package/templates/product-system/packages/backend/tsconfig.json +22 -0
  39. package/templates/product-system/packages/frontend/index.html +13 -0
  40. package/templates/product-system/packages/frontend/package-lock.json +2666 -0
  41. package/templates/product-system/packages/frontend/package.json +30 -0
  42. package/templates/product-system/packages/frontend/public/favicon.svg +16 -0
  43. package/templates/product-system/packages/frontend/src/App.tsx +29 -0
  44. package/templates/product-system/packages/frontend/src/api/client.ts +386 -0
  45. package/templates/product-system/packages/frontend/src/api/client_projects.test.ts +104 -0
  46. package/templates/product-system/packages/frontend/src/api/client_refresh.test.ts +145 -0
  47. package/templates/product-system/packages/frontend/src/components/CoherenceView.tsx +414 -0
  48. package/templates/product-system/packages/frontend/src/components/GraphLegend.tsx +124 -0
  49. package/templates/product-system/packages/frontend/src/components/GraphSettings.tsx +112 -0
  50. package/templates/product-system/packages/frontend/src/components/GraphView.tsx +370 -0
  51. package/templates/product-system/packages/frontend/src/components/InviteUserDialog.tsx +85 -0
  52. package/templates/product-system/packages/frontend/src/components/KanbanView.tsx +470 -0
  53. package/templates/product-system/packages/frontend/src/components/LoginPage.tsx +116 -0
  54. package/templates/product-system/packages/frontend/src/components/ProjectSelector.tsx +187 -0
  55. package/templates/product-system/packages/frontend/src/components/QaIssueSheet.tsx +192 -0
  56. package/templates/product-system/packages/frontend/src/components/SidePanel.tsx +231 -0
  57. package/templates/product-system/packages/frontend/src/components/TerminalView.tsx +200 -0
  58. package/templates/product-system/packages/frontend/src/components/Toolbar.tsx +84 -0
  59. package/templates/product-system/packages/frontend/src/components/UsersView.tsx +249 -0
  60. package/templates/product-system/packages/frontend/src/constants/graph.ts +191 -0
  61. package/templates/product-system/packages/frontend/src/hooks/useAuth.tsx +54 -0
  62. package/templates/product-system/packages/frontend/src/hooks/useGraph.ts +27 -0
  63. package/templates/product-system/packages/frontend/src/hooks/useKanban.ts +21 -0
  64. package/templates/product-system/packages/frontend/src/hooks/useProjects.ts +86 -0
  65. package/templates/product-system/packages/frontend/src/hooks/useTheme.ts +26 -0
  66. package/templates/product-system/packages/frontend/src/hooks/useToast.tsx +62 -0
  67. package/templates/product-system/packages/frontend/src/hooks/use_projects_logic.test.ts +61 -0
  68. package/templates/product-system/packages/frontend/src/main.tsx +12 -0
  69. package/templates/product-system/packages/frontend/src/pages/accept_invitation_page.tsx +167 -0
  70. package/templates/product-system/packages/frontend/src/pages/forgot_password_page.tsx +100 -0
  71. package/templates/product-system/packages/frontend/src/pages/register_page.tsx +137 -0
  72. package/templates/product-system/packages/frontend/src/pages/reset_password_page.tsx +146 -0
  73. package/templates/product-system/packages/frontend/src/routes/ProtectedRoute.tsx +12 -0
  74. package/templates/product-system/packages/frontend/src/routes/accept_invitation.tsx +14 -0
  75. package/templates/product-system/packages/frontend/src/routes/dashboard.tsx +221 -0
  76. package/templates/product-system/packages/frontend/src/routes/forgot_password.tsx +13 -0
  77. package/templates/product-system/packages/frontend/src/routes/login.tsx +14 -0
  78. package/templates/product-system/packages/frontend/src/routes/register.tsx +14 -0
  79. package/templates/product-system/packages/frontend/src/routes/reset_password.tsx +13 -0
  80. package/templates/product-system/packages/frontend/src/styles/index.css +3358 -0
  81. package/templates/product-system/packages/frontend/src/utils/auth_validation.test.ts +51 -0
  82. package/templates/product-system/packages/frontend/src/utils/auth_validation.ts +19 -0
  83. package/templates/product-system/packages/frontend/src/utils/login_validation.test.ts +61 -0
  84. package/templates/product-system/packages/frontend/src/utils/login_validation.ts +24 -0
  85. package/templates/product-system/packages/frontend/src/utils/logout.test.ts +63 -0
  86. package/templates/product-system/packages/frontend/src/utils/node_sizing.test.ts +62 -0
  87. package/templates/product-system/packages/frontend/src/utils/node_sizing.ts +24 -0
  88. package/templates/product-system/packages/frontend/src/utils/task_status.test.ts +53 -0
  89. package/templates/product-system/packages/frontend/src/utils/task_status.ts +14 -0
  90. package/templates/product-system/packages/frontend/tsconfig.json +21 -0
  91. package/templates/product-system/packages/frontend/vite.config.ts +20 -0
  92. package/templates/product-system/packages/shared/.env.example +3 -0
  93. package/templates/product-system/packages/shared/README.md +1 -0
  94. package/templates/product-system/packages/shared/db/migrate.ts +32 -0
  95. package/templates/product-system/packages/shared/db/migrations/0000_dashing_gorgon.sql +128 -0
  96. package/templates/product-system/packages/shared/db/migrations/meta/0000_snapshot.json +819 -0
  97. package/templates/product-system/packages/shared/db/migrations/meta/_journal.json +13 -0
  98. package/templates/product-system/packages/shared/db/schema.ts +137 -0
  99. package/templates/product-system/packages/shared/drizzle.config.js +14 -0
  100. package/templates/product-system/packages/shared/lib/claude-service.ts +215 -0
  101. package/templates/product-system/packages/shared/lib/coherence.ts +278 -0
  102. package/templates/product-system/packages/shared/lib/completeness.ts +30 -0
  103. package/templates/product-system/packages/shared/lib/constants.ts +327 -0
  104. package/templates/product-system/packages/shared/lib/db.ts +81 -0
  105. package/templates/product-system/packages/shared/lib/git_workflow.ts +110 -0
  106. package/templates/product-system/packages/shared/lib/graph.ts +186 -0
  107. package/templates/product-system/packages/shared/lib/kanban.ts +161 -0
  108. package/templates/product-system/packages/shared/lib/markdown.ts +205 -0
  109. package/templates/product-system/packages/shared/lib/pipeline-state-store.ts +124 -0
  110. package/templates/product-system/packages/shared/lib/pipeline.ts +489 -0
  111. package/templates/product-system/packages/shared/lib/prompt_builder.ts +170 -0
  112. package/templates/product-system/packages/shared/lib/relevance_search.ts +159 -0
  113. package/templates/product-system/packages/shared/lib/session.ts +152 -0
  114. package/templates/product-system/packages/shared/lib/validator.ts +117 -0
  115. package/templates/product-system/packages/shared/lib/work_summary_parser.ts +130 -0
  116. package/templates/product-system/packages/shared/package.json +30 -0
  117. package/templates/product-system/packages/shared/scripts/assign-project.ts +52 -0
  118. package/templates/product-system/packages/shared/tools/add_edge.ts +61 -0
  119. package/templates/product-system/packages/shared/tools/add_node.ts +101 -0
  120. package/templates/product-system/packages/shared/tools/end_session.ts +87 -0
  121. package/templates/product-system/packages/shared/tools/get_gaps.ts +87 -0
  122. package/templates/product-system/packages/shared/tools/get_kanban.ts +125 -0
  123. package/templates/product-system/packages/shared/tools/get_node.ts +78 -0
  124. package/templates/product-system/packages/shared/tools/get_status.ts +98 -0
  125. package/templates/product-system/packages/shared/tools/migrate_to_turso.ts +385 -0
  126. package/templates/product-system/packages/shared/tools/move_card.ts +143 -0
  127. package/templates/product-system/packages/shared/tools/rebuild_index.ts +77 -0
  128. package/templates/product-system/packages/shared/tools/remove_edge.ts +59 -0
  129. package/templates/product-system/packages/shared/tools/remove_node.ts +96 -0
  130. package/templates/product-system/packages/shared/tools/resolve_question.ts +75 -0
  131. package/templates/product-system/packages/shared/tools/search_nodes.ts +106 -0
  132. package/templates/product-system/packages/shared/tools/start_session.ts +144 -0
  133. package/templates/product-system/packages/shared/tools/update_node.ts +133 -0
  134. package/templates/product-system/packages/shared/tsconfig.json +24 -0
  135. package/templates/product-system/pnpm-workspace.yaml +2 -0
  136. package/templates/product-system/smoke_test.ts +219 -0
  137. package/templates/product-system/tests/coherence_review.test.ts +562 -0
  138. package/templates/product-system/tests/db_sqlite_fallback.test.ts +75 -0
  139. package/templates/product-system/tests/edge_type_color_coding.test.ts +147 -0
  140. package/templates/product-system/tests/emit-tool-use-events.test.ts +85 -0
  141. package/templates/product-system/tests/feature_kind.test.ts +139 -0
  142. package/templates/product-system/tests/gap_indicators.test.ts +199 -0
  143. package/templates/product-system/tests/graceful_init.test.ts +142 -0
  144. package/templates/product-system/tests/graph_legend.test.ts +314 -0
  145. package/templates/product-system/tests/graph_settings_sheet.test.ts +804 -0
  146. package/templates/product-system/tests/hide_defined_filter.test.ts +205 -0
  147. package/templates/product-system/tests/kanban.test.ts +529 -0
  148. package/templates/product-system/tests/neighborhood_focus.test.ts +132 -0
  149. package/templates/product-system/tests/node_search.test.ts +340 -0
  150. package/templates/product-system/tests/node_sizing.test.ts +170 -0
  151. package/templates/product-system/tests/node_type_toggle_filters.test.ts +285 -0
  152. package/templates/product-system/tests/node_type_visual_encoding.test.ts +103 -0
  153. package/templates/product-system/tests/pipeline-state-store.test.ts +268 -0
  154. package/templates/product-system/tests/pipeline-unit.test.ts +593 -0
  155. package/templates/product-system/tests/pipeline.test.ts +195 -0
  156. package/templates/product-system/tests/pipeline_stats_all_cards.test.ts +193 -0
  157. package/templates/product-system/tests/play_all.test.ts +296 -0
  158. package/templates/product-system/tests/qa_issue_sheet.test.ts +464 -0
  159. package/templates/product-system/tests/relevance_search.test.ts +186 -0
  160. package/templates/product-system/tests/search_reorder.test.ts +88 -0
  161. package/templates/product-system/tests/serve_ui.test.ts +281 -0
  162. package/templates/product-system/tests/serve_ui_drizzle.test.ts +114 -0
  163. package/templates/product-system/tests/session_context_recall.test.ts +135 -0
  164. package/templates/product-system/tests/side_panel.test.ts +345 -0
  165. package/templates/product-system/tests/spec_completeness_label.test.ts +69 -0
  166. package/templates/product-system/tests/url_routing_test.ts +122 -0
  167. package/templates/product-system/tests/user_login.test.ts +150 -0
  168. package/templates/product-system/tests/user_registration.test.ts +205 -0
  169. package/templates/product-system/tests/web_terminal.test.ts +572 -0
  170. package/templates/product-system/tests/work_summary.test.ts +211 -0
  171. package/templates/product-system/tests/zoom_pan.test.ts +43 -0
  172. package/templates/product-system/tsconfig.json +24 -0
  173. package/templates/skills/product-bootstrap/SKILL.md +312 -0
  174. package/templates/skills/product-code-reviewer/SKILL.md +147 -0
  175. package/templates/skills/product-debugger/SKILL.md +206 -0
  176. package/templates/skills/product-debugger/references/agent-browser.md +1156 -0
  177. package/templates/skills/product-developer/SKILL.md +182 -0
  178. package/templates/skills/product-interview/SKILL.md +220 -0
@@ -0,0 +1,385 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * migrate_to_turso — One-time migration tool that reads all existing local .md files
5
+ * (nodes/, sessions/) and graph.json/kanban.json, then inserts everything into the
6
+ * Turso database.
7
+ *
8
+ * Usage:
9
+ * node tools/migrate_to_turso.js
10
+ * node tools/migrate_to_turso.js --dry-run # Report what would be migrated without writing
11
+ */
12
+
13
+ import { program } from 'commander';
14
+ import chalk from 'chalk';
15
+ import matter from 'gray-matter';
16
+ import { readFileSync, readdirSync, existsSync } from 'node:fs';
17
+ import { join } from 'node:path';
18
+ import { getDb, getProjectRoot, closeDb } from '../lib/db.js';
19
+ import { nodes, edges, kanban, sessions, reviewMeta } from '../db/schema.js';
20
+ import { scoreNode } from '../lib/completeness.js';
21
+
22
+ program
23
+ .option('--dry-run', 'Report what would be migrated without writing to database')
24
+ .parse();
25
+
26
+ const opts = program.opts();
27
+
28
+ /**
29
+ * Parse a markdown body into sections map { sectionName: content }.
30
+ */
31
+ const parseSections = (body) => {
32
+ const sectionMap = {};
33
+ const lines = body.split('\n');
34
+ let currentSection = null;
35
+ let currentLines = [];
36
+
37
+ for (const line of lines) {
38
+ const match = line.match(/^## (.+)$/);
39
+ if (match) {
40
+ if (currentSection !== null) {
41
+ sectionMap[currentSection] = currentLines.join('\n').trim();
42
+ }
43
+ currentSection = match[1].trim();
44
+ currentLines = [];
45
+ } else if (currentSection !== null) {
46
+ currentLines.push(line);
47
+ }
48
+ }
49
+ if (currentSection !== null) {
50
+ sectionMap[currentSection] = currentLines.join('\n').trim();
51
+ }
52
+
53
+ return sectionMap;
54
+ };
55
+
56
+ /**
57
+ * Extract text under a ### subsection header from raw markdown content.
58
+ * Session files use h3 subsections (### Summary, ### Notes, etc.) within h2 sections.
59
+ */
60
+ const extractSubsection = (content, name) => {
61
+ const regex = new RegExp(`^### ${name}\\s*$`, 'm');
62
+ const match = content.match(regex);
63
+ if (!match) return '';
64
+
65
+ const start = match.index + match[0].length;
66
+ const rest = content.slice(start);
67
+ // Find the next heading (## or ###)
68
+ const nextHeading = rest.search(/^#{2,3} /m);
69
+ const block = nextHeading === -1 ? rest : rest.slice(0, nextHeading);
70
+ return block.trim();
71
+ };
72
+
73
+ /**
74
+ * Count open questions (unchecked checkboxes) in a sections map.
75
+ */
76
+ const countOpenQuestions = (sectionMap) => {
77
+ const oq = sectionMap['Open Questions'] || '';
78
+ return oq.split('\n').filter(l => l.trim().startsWith('- [ ]')).length;
79
+ };
80
+
81
+ /**
82
+ * Migrate all node .md files from nodes/ directory.
83
+ */
84
+ const migrateNodes = async (db, projectRoot, dryRun) => {
85
+ const nodesDir = join(projectRoot, 'nodes');
86
+ if (!existsSync(nodesDir)) {
87
+ console.log(chalk.yellow(' No nodes/ directory found. Skipping nodes migration.'));
88
+ return { migrated: 0, errors: [] };
89
+ }
90
+
91
+ const files = readdirSync(nodesDir).filter(f => f.endsWith('.md')).sort();
92
+ const errors = [];
93
+ let migrated = 0;
94
+
95
+ for (const file of files) {
96
+ const filepath = join(nodesDir, file);
97
+ try {
98
+ const raw = readFileSync(filepath, 'utf8');
99
+ const { data: fm, content } = matter(raw);
100
+
101
+ const sectionMap = parseSections(content);
102
+ const completeness = scoreNode(fm.type, fm, sectionMap);
103
+ const openQuestionsCount = countOpenQuestions(sectionMap);
104
+
105
+ const body = content.trim();
106
+
107
+ if (!dryRun) {
108
+ await db.insert(nodes).values({
109
+ id: fm.id,
110
+ type: fm.type,
111
+ name: fm.name,
112
+ status: fm.status || 'draft',
113
+ priority: fm.priority || 'medium',
114
+ completeness,
115
+ openQuestionsCount,
116
+ kind: fm.kind || null,
117
+ createdAt: fm.created_at || new Date().toISOString(),
118
+ updatedAt: fm.updated_at || new Date().toISOString(),
119
+ body,
120
+ }).onConflictDoUpdate({
121
+ target: nodes.id,
122
+ set: {
123
+ type: fm.type,
124
+ name: fm.name,
125
+ status: fm.status || 'draft',
126
+ priority: fm.priority || 'medium',
127
+ completeness,
128
+ openQuestionsCount,
129
+ kind: fm.kind || null,
130
+ updatedAt: fm.updated_at || new Date().toISOString(),
131
+ body,
132
+ },
133
+ });
134
+ }
135
+
136
+ migrated++;
137
+ } catch (err) {
138
+ errors.push({ file, error: err.message });
139
+ }
140
+ }
141
+
142
+ return { migrated, errors };
143
+ };
144
+
145
+ /**
146
+ * Migrate edges from graph.json.
147
+ */
148
+ const migrateEdges = async (db, projectRoot, dryRun) => {
149
+ const graphPath = join(projectRoot, 'graph.json');
150
+ if (!existsSync(graphPath)) {
151
+ console.log(chalk.yellow(' No graph.json found. Skipping edges migration.'));
152
+ return { migrated: 0, errors: [] };
153
+ }
154
+
155
+ const graph = JSON.parse(readFileSync(graphPath, 'utf8'));
156
+ const edgeList = graph.edges || [];
157
+ const errors = [];
158
+ let migrated = 0;
159
+
160
+ for (const edge of edgeList) {
161
+ try {
162
+ if (!dryRun) {
163
+ await db.insert(edges).values({
164
+ fromId: edge.from,
165
+ relation: edge.relation,
166
+ toId: edge.to,
167
+ }).onConflictDoNothing();
168
+ }
169
+ migrated++;
170
+ } catch (err) {
171
+ errors.push({ edge: `${edge.from} -${edge.relation}-> ${edge.to}`, error: err.message });
172
+ }
173
+ }
174
+
175
+ return { migrated, errors };
176
+ };
177
+
178
+ /**
179
+ * Migrate kanban state from kanban.json.
180
+ */
181
+ const migrateKanban = async (db, projectRoot, dryRun) => {
182
+ const kanbanPath = join(projectRoot, 'kanban.json');
183
+ if (!existsSync(kanbanPath)) {
184
+ console.log(chalk.yellow(' No kanban.json found. Skipping kanban migration.'));
185
+ return { migrated: 0, errors: [] };
186
+ }
187
+
188
+ const kanbanData = JSON.parse(readFileSync(kanbanPath, 'utf8'));
189
+ const errors = [];
190
+ let migrated = 0;
191
+ let position = 0;
192
+
193
+ for (const [featureId, entry] of Object.entries(kanbanData)) {
194
+ try {
195
+ if (!dryRun) {
196
+ await db.insert(kanban).values({
197
+ nodeId: featureId,
198
+ columnName: entry.column,
199
+ position: position++,
200
+ }).onConflictDoUpdate({
201
+ target: kanban.nodeId,
202
+ set: {
203
+ columnName: entry.column,
204
+ position: position - 1,
205
+ },
206
+ });
207
+
208
+ // Store extra metadata (rejection_count, notes, moved_at) in review_meta
209
+ const metaKey = `kanban:${featureId}`;
210
+ const metaValue = JSON.stringify({
211
+ rejection_count: entry.rejection_count || 0,
212
+ notes: entry.notes || [],
213
+ ...(entry.dev_blocked ? { dev_blocked: true } : {}),
214
+ ...(entry.moved_at ? { moved_at: entry.moved_at } : {}),
215
+ });
216
+
217
+ await db.insert(reviewMeta).values({
218
+ key: metaKey,
219
+ value: metaValue,
220
+ }).onConflictDoUpdate({
221
+ target: reviewMeta.key,
222
+ set: { value: metaValue },
223
+ });
224
+ }
225
+
226
+ migrated++;
227
+ } catch (err) {
228
+ errors.push({ featureId, error: err.message });
229
+ }
230
+ }
231
+
232
+ return { migrated, errors };
233
+ };
234
+
235
+ /**
236
+ * Migrate session .md files from sessions/ directory.
237
+ */
238
+ const migrateSessions = async (db, projectRoot, dryRun) => {
239
+ const sessionsDir = join(projectRoot, 'sessions');
240
+ if (!existsSync(sessionsDir)) {
241
+ console.log(chalk.yellow(' No sessions/ directory found. Skipping sessions migration.'));
242
+ return { migrated: 0, errors: [] };
243
+ }
244
+
245
+ const files = readdirSync(sessionsDir).filter(f => f.endsWith('.md')).sort();
246
+ const errors = [];
247
+ let migrated = 0;
248
+
249
+ for (const file of files) {
250
+ const filepath = join(sessionsDir, file);
251
+ try {
252
+ const raw = readFileSync(filepath, 'utf8');
253
+ const { data: fm, content } = matter(raw);
254
+
255
+ const sessionNumber = fm.session;
256
+ if (sessionNumber == null) {
257
+ errors.push({ file, error: 'Missing session number in frontmatter' });
258
+ continue;
259
+ }
260
+
261
+ const nodesTouched = Array.isArray(fm.nodes_touched)
262
+ ? fm.nodes_touched.join(',')
263
+ : (fm.nodes_touched || '');
264
+
265
+ // Extract summary — sessions use ### Summary (h3 subsections)
266
+ const summary = extractSubsection(content, 'Summary');
267
+
268
+ if (!dryRun) {
269
+ await db.insert(sessions).values({
270
+ sessionNumber,
271
+ startedAt: fm.started_at || new Date().toISOString(),
272
+ endedAt: fm.ended_at || null,
273
+ summary,
274
+ nodesTouched,
275
+ questionsResolved: fm.questions_resolved || 0,
276
+ body: content.trim(),
277
+ }).onConflictDoUpdate({
278
+ target: sessions.sessionNumber,
279
+ set: {
280
+ startedAt: fm.started_at || new Date().toISOString(),
281
+ endedAt: fm.ended_at || null,
282
+ summary,
283
+ nodesTouched,
284
+ questionsResolved: fm.questions_resolved || 0,
285
+ body: content.trim(),
286
+ },
287
+ });
288
+ }
289
+
290
+ migrated++;
291
+ } catch (err) {
292
+ errors.push({ file, error: err.message });
293
+ }
294
+ }
295
+
296
+ return { migrated, errors };
297
+ };
298
+
299
+ /**
300
+ * Main migration entry point.
301
+ */
302
+ const main = async () => {
303
+ const projectRoot = getProjectRoot();
304
+ const dryRun = opts.dryRun || false;
305
+
306
+ if (dryRun) {
307
+ console.log(chalk.cyan('=== DRY RUN — no database writes will occur ===\n'));
308
+ }
309
+
310
+ console.log(chalk.bold('Migrating local files to Turso database...\n'));
311
+
312
+ const db = getDb();
313
+
314
+ // Step 1: Migrate nodes
315
+ console.log(chalk.blue('1. Migrating nodes...'));
316
+ const nodesResult = await migrateNodes(db, projectRoot, dryRun);
317
+ console.log(` ${chalk.green(nodesResult.migrated)} nodes migrated`);
318
+ if (nodesResult.errors.length > 0) {
319
+ for (const e of nodesResult.errors) {
320
+ console.log(` ${chalk.red('ERROR')} ${e.file}: ${e.error}`);
321
+ }
322
+ }
323
+
324
+ // Step 2: Migrate edges
325
+ console.log(chalk.blue('\n2. Migrating edges...'));
326
+ const edgesResult = await migrateEdges(db, projectRoot, dryRun);
327
+ console.log(` ${chalk.green(edgesResult.migrated)} edges migrated`);
328
+ if (edgesResult.errors.length > 0) {
329
+ for (const e of edgesResult.errors) {
330
+ console.log(` ${chalk.red('ERROR')} ${e.edge}: ${e.error}`);
331
+ }
332
+ }
333
+
334
+ // Step 3: Migrate kanban
335
+ console.log(chalk.blue('\n3. Migrating kanban state...'));
336
+ const kanbanResult = await migrateKanban(db, projectRoot, dryRun);
337
+ console.log(` ${chalk.green(kanbanResult.migrated)} kanban entries migrated`);
338
+ if (kanbanResult.errors.length > 0) {
339
+ for (const e of kanbanResult.errors) {
340
+ console.log(` ${chalk.red('ERROR')} ${e.featureId}: ${e.error}`);
341
+ }
342
+ }
343
+
344
+ // Step 4: Migrate sessions
345
+ console.log(chalk.blue('\n4. Migrating sessions...'));
346
+ const sessionsResult = await migrateSessions(db, projectRoot, dryRun);
347
+ console.log(` ${chalk.green(sessionsResult.migrated)} sessions migrated`);
348
+ if (sessionsResult.errors.length > 0) {
349
+ for (const e of sessionsResult.errors) {
350
+ console.log(` ${chalk.red('ERROR')} ${e.file}: ${e.error}`);
351
+ }
352
+ }
353
+
354
+ // Summary
355
+ const totalErrors = [
356
+ ...nodesResult.errors,
357
+ ...edgesResult.errors,
358
+ ...kanbanResult.errors,
359
+ ...sessionsResult.errors,
360
+ ];
361
+
362
+ console.log(chalk.bold('\n=== Migration Summary ==='));
363
+ console.log(` Nodes: ${nodesResult.migrated}`);
364
+ console.log(` Edges: ${edgesResult.migrated}`);
365
+ console.log(` Kanban: ${kanbanResult.migrated}`);
366
+ console.log(` Sessions: ${sessionsResult.migrated}`);
367
+
368
+ if (totalErrors.length > 0) {
369
+ console.log(chalk.red(`\n Errors: ${totalErrors.length}`));
370
+ } else {
371
+ console.log(chalk.green('\n No errors.'));
372
+ }
373
+
374
+ if (dryRun) {
375
+ console.log(chalk.cyan('\n=== DRY RUN complete — no changes were made ==='));
376
+ }
377
+
378
+ closeDb();
379
+ };
380
+
381
+ main().catch((err) => {
382
+ console.error(chalk.red(`Migration failed: ${err.message}`));
383
+ closeDb();
384
+ process.exit(1);
385
+ });
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * move_card — Moves a feature card to a new column on the kanban board.
5
+ * Validates that the transition is allowed.
6
+ *
7
+ * Usage:
8
+ * node tools/move_card.js feat_010 in_progress # todo → in_progress
9
+ * node tools/move_card.js feat_010 in_review # in_progress → in_review
10
+ * node tools/move_card.js feat_010 qa # in_review → qa (approve)
11
+ * node tools/move_card.js feat_010 todo --note "Missing tests" # in_review → todo (reject)
12
+ *
13
+ * Allowed transitions (AI via CLI):
14
+ * todo → in_progress (developer picks up feature)
15
+ * in_progress → in_review (developer submits for review)
16
+ * in_review → qa (reviewer approves)
17
+ * in_review → todo (reviewer rejects — increments rejection_count)
18
+ *
19
+ * User-only transitions:
20
+ * qa → done
21
+ * qa → todo (user QA rejection — increments rejection_count)
22
+ */
23
+
24
+ import { program } from 'commander';
25
+ import chalk from 'chalk';
26
+ import { getKanbanEntry, saveKanbanEntry, kanbanExists } from '../lib/kanban.js';
27
+
28
+ const VALID_COLUMNS = ['todo', 'in_progress', 'in_review', 'qa', 'done'];
29
+
30
+ // AI-allowed transitions (via CLI)
31
+ const ALLOWED_TRANSITIONS = {
32
+ 'todo': ['in_progress'],
33
+ 'in_progress': ['in_review'],
34
+ 'in_review': ['qa', 'todo'],
35
+ 'qa': ['todo'],
36
+ 'done': [],
37
+ };
38
+
39
+ program
40
+ .argument('<feature_id>', 'Feature ID (e.g., feat_010)')
41
+ .argument('<target_column>', `Target column (${VALID_COLUMNS.join(', ')})`)
42
+ .requiredOption('--project-id <id>', 'Project ID')
43
+ .option('--note <text>', 'Add a note (required for rejections)')
44
+ .option('--unblock', 'Remove dev_blocked flag from the feature')
45
+ .parse();
46
+
47
+ const [featureId, targetColumn] = program.args;
48
+
49
+ (async () => {
50
+ try {
51
+ const opts = program.opts();
52
+ if (!(await kanbanExists(opts.projectId))) {
53
+ throw new Error('No kanban entries found. No features are being tracked yet.');
54
+ }
55
+
56
+ if (!VALID_COLUMNS.includes(targetColumn)) {
57
+ throw new Error(`Invalid column "${targetColumn}". Valid columns: ${VALID_COLUMNS.join(', ')}`);
58
+ }
59
+
60
+ const entry = await getKanbanEntry(featureId);
61
+ if (!entry) {
62
+ throw new Error(`Feature "${featureId}" not found on the kanban board.`);
63
+ }
64
+
65
+ const currentColumn = entry.column;
66
+
67
+ // Handle --unblock flag
68
+ if (opts.unblock) {
69
+ if (!entry.dev_blocked) {
70
+ console.log(chalk.yellow(`Feature "${featureId}" is not blocked.`));
71
+ } else {
72
+ delete entry.dev_blocked;
73
+ await saveKanbanEntry(featureId, entry);
74
+ console.log(chalk.green(`✓ Unblocked ${featureId}`));
75
+ }
76
+ process.exit(0);
77
+ }
78
+
79
+ // Check dev_blocked
80
+ if (entry.dev_blocked && targetColumn === 'in_progress') {
81
+ throw new Error(
82
+ `Feature "${featureId}" is dev_blocked (failed 3 automated dev cycles). ` +
83
+ `Use --unblock flag to remove the block first.`
84
+ );
85
+ }
86
+
87
+ if (currentColumn === targetColumn) {
88
+ throw new Error(`Feature "${featureId}" is already in "${targetColumn}".`);
89
+ }
90
+
91
+ const allowed = ALLOWED_TRANSITIONS[currentColumn] || [];
92
+ if (!allowed.includes(targetColumn)) {
93
+ const allowedStr = allowed.length > 0 ? allowed.join(', ') : 'none (terminal state)';
94
+ throw new Error(
95
+ `Invalid transition: "${currentColumn}" → "${targetColumn}" is not allowed.\n` +
96
+ ` Allowed transitions from "${currentColumn}": ${allowedStr}`
97
+ );
98
+ }
99
+
100
+ // Handle rejections (in_review → todo or qa → todo)
101
+ const isRejection = targetColumn === 'todo' && (currentColumn === 'in_review' || currentColumn === 'qa');
102
+ if (isRejection) {
103
+ if (!opts.note) {
104
+ throw new Error('Rejections require a --note explaining what needs to be fixed.');
105
+ }
106
+ entry.rejection_count = (entry.rejection_count || 0) + 1;
107
+ if (!entry.notes) entry.notes = [];
108
+ entry.notes.push({
109
+ from: currentColumn,
110
+ text: opts.note,
111
+ timestamp: new Date().toISOString(),
112
+ });
113
+ const count = entry.rejection_count;
114
+ const source = currentColumn === 'in_review' ? 'Review' : 'QA';
115
+ console.log(chalk.yellow(`⚠ ${source} rejection #${count} for ${featureId}`));
116
+ console.log(chalk.gray(` Note: ${opts.note}`));
117
+ if (count >= 3) {
118
+ console.log(chalk.red.bold(`⚠ WARNING: ${featureId} has been rejected ${count} times — needs attention`));
119
+ }
120
+ }
121
+
122
+ // Perform the transition
123
+ const previousColumn = entry.column;
124
+ entry.column = targetColumn;
125
+ entry.moved_at = new Date().toISOString();
126
+
127
+ await saveKanbanEntry(featureId, entry);
128
+
129
+ const arrow = `${previousColumn} → ${targetColumn}`;
130
+ console.log(chalk.green(`✓ Moved ${featureId}: ${arrow}`));
131
+
132
+ console.log('\n' + JSON.stringify({
133
+ feature_id: featureId,
134
+ previous_column: previousColumn,
135
+ new_column: targetColumn,
136
+ rejection_count: entry.rejection_count || 0,
137
+ }));
138
+
139
+ } catch (err) {
140
+ console.error(chalk.red(`Error: ${err.message}`));
141
+ process.exit(1);
142
+ }
143
+ })();
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * rebuild_index — Regenerates the nodes table metadata by re-deriving completeness
5
+ * and status from each node's body content. With Turso as source of truth,
6
+ * this recalculates derived fields rather than rebuilding from .md files.
7
+ */
8
+
9
+ import { program } from 'commander';
10
+ import chalk from 'chalk';
11
+ import { readGraph, writeGraph } from '../lib/graph.js';
12
+ import { readNode, deriveMetadata } from '../lib/markdown.js';
13
+ import { getDb } from '../lib/db.js';
14
+ import { nodes } from '../db/schema.js';
15
+
16
+ program
17
+ .requiredOption('--project-id <id>', 'Project ID')
18
+ .option('--dry-run', 'Show what would change without writing')
19
+ .parse();
20
+
21
+ const opts = program.opts();
22
+
23
+ (async () => {
24
+ try {
25
+ const db = getDb();
26
+ const allNodes = await db.select().from(nodes);
27
+
28
+ if (allNodes.length === 0) {
29
+ console.log(chalk.yellow('No nodes in the database. Nothing to rebuild.'));
30
+ process.exit(0);
31
+ }
32
+
33
+ const oldGraph = await readGraph();
34
+
35
+ const newGraph = {
36
+ version: '1.0',
37
+ project: oldGraph.project,
38
+ nodes: [],
39
+ edges: oldGraph.edges,
40
+ };
41
+
42
+ for (const row of allNodes) {
43
+ const { frontmatter, sections } = await readNode(row.id);
44
+ const meta = deriveMetadata(frontmatter, sections, row.type);
45
+
46
+ const nodeEntry = {
47
+ id: row.id,
48
+ type: row.type,
49
+ name: row.name,
50
+ status: meta.status,
51
+ completeness: meta.completeness,
52
+ open_questions_count: meta.open_questions_count,
53
+ priority: row.priority,
54
+ created_at: row.createdAt,
55
+ updated_at: row.updatedAt,
56
+ ...(row.kind ? { kind: row.kind } : {}),
57
+ body: row.body,
58
+ };
59
+ newGraph.nodes.push(nodeEntry);
60
+ }
61
+
62
+ if (opts.dryRun) {
63
+ console.log(chalk.cyan('Dry run — would produce:'));
64
+ console.log(` Nodes: ${newGraph.nodes.length} (was ${oldGraph.nodes.length})`);
65
+ console.log(` Edges: ${newGraph.edges.length}`);
66
+ for (const node of newGraph.nodes) {
67
+ console.log(` ${node.id}: ${node.name} (${node.type}) — ${Math.round(node.completeness * 100)}%`);
68
+ }
69
+ } else {
70
+ await writeGraph(newGraph, opts.projectId);
71
+ console.log(chalk.green(`✓ Rebuilt index: ${newGraph.nodes.length} nodes, ${newGraph.edges.length} edges`));
72
+ }
73
+ } catch (err) {
74
+ console.error(chalk.red(`Error: ${err.message}`));
75
+ process.exit(1);
76
+ }
77
+ })();
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * remove_edge — Removes a relationship between two nodes.
5
+ */
6
+
7
+ import { program } from 'commander';
8
+ import chalk from 'chalk';
9
+ import { readGraph, removeEdge as graphRemoveEdge } from '../lib/graph.js';
10
+ import { readNode, writeNode } from '../lib/markdown.js';
11
+ import { assertNodeExists, assertValidRelation } from '../lib/validator.js';
12
+
13
+ program
14
+ .argument('<from>', 'Source node ID')
15
+ .argument('<relation>', 'Relation type')
16
+ .argument('<to>', 'Target node ID')
17
+ .requiredOption('--project-id <id>', 'Project ID')
18
+ .parse();
19
+
20
+ const [from, relation, to] = program.args;
21
+
22
+ (async () => {
23
+ try {
24
+ await assertNodeExists(from);
25
+ await assertNodeExists(to);
26
+ assertValidRelation(relation);
27
+
28
+ // Get node metadata before removing edge
29
+ const graph = await readGraph();
30
+ const fromNode = graph.nodes.find(n => n.id === from);
31
+ const toNode = graph.nodes.find(n => n.id === to);
32
+
33
+ // Remove edge from DB
34
+ await graphRemoveEdge(from, relation, to);
35
+
36
+ // Remove relation lines from body content
37
+ const fromData = await readNode(from);
38
+ const fromRelations = (fromData.sections['Relations'] || '').split('\n');
39
+ fromData.sections['Relations'] = fromRelations
40
+ .filter(l => !l.includes(`${relation} → ${to}`))
41
+ .join('\n')
42
+ .trim();
43
+ await writeNode(from, fromData.frontmatter, fromData.sections);
44
+
45
+ const toData = await readNode(to);
46
+ const toRelations = (toData.sections['Relations'] || '').split('\n');
47
+ toData.sections['Relations'] = toRelations
48
+ .filter(l => !l.includes(`${relation} ← ${from}`))
49
+ .join('\n')
50
+ .trim();
51
+ await writeNode(to, toData.frontmatter, toData.sections);
52
+
53
+ console.log(chalk.green(`✓ Removed edge: ${from} --${relation}--> ${to}`));
54
+ console.log(JSON.stringify({ from, relation, to }));
55
+ } catch (err) {
56
+ console.error(chalk.red(`Error: ${err.message}`));
57
+ process.exit(1);
58
+ }
59
+ })();