@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,327 @@
1
+ /**
2
+ * Constants for the interview system knowledge graph.
3
+ * Defines node types, valid edge relations, required sections per type,
4
+ * and scoring weights for completeness calculation.
5
+ */
6
+
7
+ // All valid node types with their ID prefixes
8
+ export const NODE_TYPES = {
9
+ feature: 'feat',
10
+ component: 'comp',
11
+ data_entity: 'data',
12
+ decision: 'dec',
13
+ tech_choice: 'tech',
14
+ non_functional_requirement: 'nfr',
15
+ design_token: 'dtok',
16
+ design_pattern: 'dpat',
17
+ user_role: 'role',
18
+ flow: 'flow',
19
+ assumption: 'asmp',
20
+ open_question: 'oq',
21
+ };
22
+
23
+ // Required sections per node type — completeness scoring checks these
24
+ export const REQUIRED_SECTIONS = {
25
+ feature: ['Description', 'Acceptance Criteria', 'Open Questions'],
26
+ component: ['Description', 'Responsibilities', 'Tech', 'Exposes', 'Consumes'],
27
+ data_entity: ['Description', 'Fields', 'Used By'],
28
+ decision: ['Description', 'Rationale', 'Alternatives Considered', 'Affects'],
29
+ tech_choice: ['Description', 'Category', 'Constraints', 'Chosen For'],
30
+ non_functional_requirement: ['Description', 'Category', 'Metric', 'Affects'],
31
+ design_token: ['Category', 'Value', 'Used In'],
32
+ design_pattern: ['Description', 'When To Use', 'Examples'],
33
+ user_role: ['Description', 'Goals', 'Permissions'],
34
+ flow: ['Description', 'Steps', 'Actors'],
35
+ assumption: ['Description', 'Confidence', 'Depends On'],
36
+ open_question: ['Description', 'Blocking', 'Affects'],
37
+ };
38
+
39
+ // Valid edge relation types
40
+ export const VALID_RELATIONS = [
41
+ 'contains',
42
+ 'depends_on',
43
+ 'governed_by',
44
+ 'constrained_by',
45
+ 'implemented_with',
46
+ 'reads_writes',
47
+ 'exposes',
48
+ 'consumes',
49
+ 'performed_by',
50
+ 'escalates_to',
51
+ 'relates_to',
52
+ ];
53
+
54
+ // Valid node statuses
55
+ export const VALID_STATUSES = ['draft', 'partially_defined', 'defined'];
56
+
57
+ // Valid priority levels
58
+ export const VALID_PRIORITIES = ['low', 'medium', 'high', 'blocking'];
59
+
60
+ // Valid feature kind values
61
+ export const VALID_FEATURE_KINDS = ['new', 'improvement', 'bugfix'];
62
+
63
+ // Scoring weights per node type — each rule is { check, weight, description }
64
+ // check receives (frontmatter, sections) and returns boolean
65
+ export const SCORING_RULES = {
66
+ feature: [
67
+ {
68
+ weight: 0.2,
69
+ description: 'Description present and > 50 chars',
70
+ check: (_fm, sections) => {
71
+ const desc = sections['Description'] || '';
72
+ return desc.trim().length > 50;
73
+ },
74
+ },
75
+ {
76
+ weight: 0.3,
77
+ description: 'At least 1 acceptance criterion',
78
+ check: (_fm, sections) => {
79
+ const ac = sections['Acceptance Criteria'] || '';
80
+ return ac.trim().split('\n').filter(l => l.trim().startsWith('-')).length >= 1;
81
+ },
82
+ },
83
+ {
84
+ weight: 0.3,
85
+ description: 'No open questions',
86
+ check: (_fm, sections) => {
87
+ const oq = sections['Open Questions'] || '';
88
+ return oq.trim().split('\n').filter(l => l.trim().startsWith('- [ ]')).length === 0;
89
+ },
90
+ },
91
+ {
92
+ weight: 0.2,
93
+ description: 'Status is defined',
94
+ check: (fm, _sections) => fm.status === 'defined',
95
+ },
96
+ ],
97
+ component: [
98
+ {
99
+ weight: 0.2,
100
+ description: 'Description present and > 50 chars',
101
+ check: (_fm, sections) => (sections['Description'] || '').trim().length > 50,
102
+ },
103
+ {
104
+ weight: 0.2,
105
+ description: 'Responsibilities listed',
106
+ check: (_fm, sections) => {
107
+ const r = sections['Responsibilities'] || '';
108
+ return r.trim().split('\n').filter(l => l.trim().startsWith('-')).length >= 1;
109
+ },
110
+ },
111
+ {
112
+ weight: 0.2,
113
+ description: 'Tech specified',
114
+ check: (_fm, sections) => (sections['Tech'] || '').trim().length > 0,
115
+ },
116
+ {
117
+ weight: 0.2,
118
+ description: 'Exposes defined',
119
+ check: (_fm, sections) => (sections['Exposes'] || '').trim().length > 0,
120
+ },
121
+ {
122
+ weight: 0.2,
123
+ description: 'Consumes defined',
124
+ check: (_fm, sections) => (sections['Consumes'] || '').trim().length > 0,
125
+ },
126
+ ],
127
+ data_entity: [
128
+ {
129
+ weight: 0.3,
130
+ description: 'Description present and > 50 chars',
131
+ check: (_fm, sections) => (sections['Description'] || '').trim().length > 50,
132
+ },
133
+ {
134
+ weight: 0.4,
135
+ description: 'Fields listed',
136
+ check: (_fm, sections) => {
137
+ const f = sections['Fields'] || '';
138
+ return f.trim().split('\n').filter(l => l.trim().startsWith('-')).length >= 1;
139
+ },
140
+ },
141
+ {
142
+ weight: 0.3,
143
+ description: 'Used By specified',
144
+ check: (_fm, sections) => (sections['Used By'] || '').trim().length > 0,
145
+ },
146
+ ],
147
+ decision: [
148
+ {
149
+ weight: 0.25,
150
+ description: 'Description present and > 50 chars',
151
+ check: (_fm, sections) => (sections['Description'] || '').trim().length > 50,
152
+ },
153
+ {
154
+ weight: 0.3,
155
+ description: 'Rationale provided',
156
+ check: (_fm, sections) => (sections['Rationale'] || '').trim().length > 20,
157
+ },
158
+ {
159
+ weight: 0.25,
160
+ description: 'Alternatives considered',
161
+ check: (_fm, sections) => {
162
+ const a = sections['Alternatives Considered'] || '';
163
+ return a.trim().split('\n').filter(l => l.trim().startsWith('-')).length >= 1;
164
+ },
165
+ },
166
+ {
167
+ weight: 0.2,
168
+ description: 'Affects listed',
169
+ check: (_fm, sections) => (sections['Affects'] || '').trim().length > 0,
170
+ },
171
+ ],
172
+ tech_choice: [
173
+ {
174
+ weight: 0.25,
175
+ description: 'Description present',
176
+ check: (_fm, sections) => (sections['Description'] || '').trim().length > 20,
177
+ },
178
+ {
179
+ weight: 0.25,
180
+ description: 'Category specified',
181
+ check: (_fm, sections) => (sections['Category'] || '').trim().length > 0,
182
+ },
183
+ {
184
+ weight: 0.25,
185
+ description: 'Constraints listed',
186
+ check: (_fm, sections) => (sections['Constraints'] || '').trim().length > 0,
187
+ },
188
+ {
189
+ weight: 0.25,
190
+ description: 'Chosen For explained',
191
+ check: (_fm, sections) => (sections['Chosen For'] || '').trim().length > 20,
192
+ },
193
+ ],
194
+ non_functional_requirement: [
195
+ {
196
+ weight: 0.25,
197
+ description: 'Description present',
198
+ check: (_fm, sections) => (sections['Description'] || '').trim().length > 20,
199
+ },
200
+ {
201
+ weight: 0.25,
202
+ description: 'Category specified',
203
+ check: (_fm, sections) => (sections['Category'] || '').trim().length > 0,
204
+ },
205
+ {
206
+ weight: 0.25,
207
+ description: 'Metric defined',
208
+ check: (_fm, sections) => (sections['Metric'] || '').trim().length > 0,
209
+ },
210
+ {
211
+ weight: 0.25,
212
+ description: 'Affects listed',
213
+ check: (_fm, sections) => (sections['Affects'] || '').trim().length > 0,
214
+ },
215
+ ],
216
+ design_token: [
217
+ {
218
+ weight: 0.33,
219
+ description: 'Category specified',
220
+ check: (_fm, sections) => (sections['Category'] || '').trim().length > 0,
221
+ },
222
+ {
223
+ weight: 0.34,
224
+ description: 'Value defined',
225
+ check: (_fm, sections) => (sections['Value'] || '').trim().length > 0,
226
+ },
227
+ {
228
+ weight: 0.33,
229
+ description: 'Used In listed',
230
+ check: (_fm, sections) => (sections['Used In'] || '').trim().length > 0,
231
+ },
232
+ ],
233
+ design_pattern: [
234
+ {
235
+ weight: 0.35,
236
+ description: 'Description present',
237
+ check: (_fm, sections) => (sections['Description'] || '').trim().length > 20,
238
+ },
239
+ {
240
+ weight: 0.35,
241
+ description: 'When To Use explained',
242
+ check: (_fm, sections) => (sections['When To Use'] || '').trim().length > 20,
243
+ },
244
+ {
245
+ weight: 0.3,
246
+ description: 'Examples provided',
247
+ check: (_fm, sections) => (sections['Examples'] || '').trim().length > 0,
248
+ },
249
+ ],
250
+ user_role: [
251
+ {
252
+ weight: 0.35,
253
+ description: 'Description present',
254
+ check: (_fm, sections) => (sections['Description'] || '').trim().length > 20,
255
+ },
256
+ {
257
+ weight: 0.35,
258
+ description: 'Goals listed',
259
+ check: (_fm, sections) => {
260
+ const g = sections['Goals'] || '';
261
+ return g.trim().split('\n').filter(l => l.trim().startsWith('-')).length >= 1;
262
+ },
263
+ },
264
+ {
265
+ weight: 0.3,
266
+ description: 'Permissions defined',
267
+ check: (_fm, sections) => (sections['Permissions'] || '').trim().length > 0,
268
+ },
269
+ ],
270
+ flow: [
271
+ {
272
+ weight: 0.3,
273
+ description: 'Description present',
274
+ check: (_fm, sections) => (sections['Description'] || '').trim().length > 20,
275
+ },
276
+ {
277
+ weight: 0.4,
278
+ description: 'Steps listed',
279
+ check: (_fm, sections) => {
280
+ const s = sections['Steps'] || '';
281
+ return s.trim().split('\n').filter(l => l.trim().match(/^\d+\.|^-/)).length >= 2;
282
+ },
283
+ },
284
+ {
285
+ weight: 0.3,
286
+ description: 'Actors specified',
287
+ check: (_fm, sections) => (sections['Actors'] || '').trim().length > 0,
288
+ },
289
+ ],
290
+ assumption: [
291
+ {
292
+ weight: 0.4,
293
+ description: 'Description present',
294
+ check: (_fm, sections) => (sections['Description'] || '').trim().length > 20,
295
+ },
296
+ {
297
+ weight: 0.3,
298
+ description: 'Confidence level set',
299
+ check: (_fm, sections) => (sections['Confidence'] || '').trim().length > 0,
300
+ },
301
+ {
302
+ weight: 0.3,
303
+ description: 'Depends On specified',
304
+ check: (_fm, sections) => (sections['Depends On'] || '').trim().length > 0,
305
+ },
306
+ ],
307
+ open_question: [
308
+ {
309
+ weight: 0.4,
310
+ description: 'Description present',
311
+ check: (_fm, sections) => (sections['Description'] || '').trim().length > 20,
312
+ },
313
+ {
314
+ weight: 0.3,
315
+ description: 'Blocking status set',
316
+ check: (_fm, sections) => (sections['Blocking'] || '').trim().length > 0,
317
+ },
318
+ {
319
+ weight: 0.3,
320
+ description: 'Affects listed',
321
+ check: (_fm, sections) => (sections['Affects'] || '').trim().length > 0,
322
+ },
323
+ ],
324
+ };
325
+
326
+ // Optional sections that any node can have
327
+ export const COMMON_OPTIONAL_SECTIONS = ['Resolved Questions', 'Notes', 'Relations'];
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Database connection via Drizzle ORM.
3
+ * Supports Turso cloud (when TURSO_DATABASE_URL + TURSO_AUTH_TOKEN are set)
4
+ * or local SQLite at data/local.db (default, zero-config).
5
+ * Lazy initialization — connection established on first query, not at import time.
6
+ */
7
+
8
+ import { createClient } from '@libsql/client';
9
+ import { drizzle } from 'drizzle-orm/libsql';
10
+ import { config } from 'dotenv';
11
+ import { dirname, join } from 'node:path';
12
+ import { fileURLToPath } from 'node:url';
13
+ import { existsSync, mkdirSync } from 'node:fs';
14
+ import * as schema from '../db/schema.js';
15
+
16
+ const __dirname = dirname(fileURLToPath(import.meta.url));
17
+ // Walk up from packages/shared/lib/ (or build/lib/) to find product-system/
18
+ let PROJECT_ROOT = join(__dirname, '..');
19
+ while (PROJECT_ROOT !== dirname(PROJECT_ROOT)) {
20
+ if (existsSync(join(PROJECT_ROOT, 'pnpm-workspace.yaml'))) break;
21
+ PROJECT_ROOT = dirname(PROJECT_ROOT);
22
+ }
23
+ const ENV_PATH = join(PROJECT_ROOT, '.env');
24
+
25
+ let _db = null;
26
+ let _client = null;
27
+
28
+ /**
29
+ * Returns the project root path (directory containing pnpm-workspace.yaml).
30
+ */
31
+ export const getProjectRoot = () => PROJECT_ROOT;
32
+
33
+ /**
34
+ * Get the Drizzle database instance. Creates it lazily on first call.
35
+ * Uses Turso if TURSO_DATABASE_URL and TURSO_AUTH_TOKEN are set,
36
+ * otherwise falls back to a local SQLite file at data/local.db.
37
+ */
38
+ export const getDb = () => {
39
+ if (_db) return _db;
40
+
41
+ // Load .env file if it exists (local dev). In production (Docker/Coolify),
42
+ // env vars are injected directly — no .env file needed.
43
+ if (existsSync(ENV_PATH)) {
44
+ config({ path: ENV_PATH, quiet: true });
45
+ }
46
+
47
+ const tursoUrl = process.env.TURSO_DATABASE_URL;
48
+ const tursoAuthToken = process.env.TURSO_AUTH_TOKEN;
49
+
50
+ let connectionUrl: string;
51
+ let connectionAuthToken: string | undefined;
52
+
53
+ if (tursoUrl && tursoAuthToken) {
54
+ connectionUrl = tursoUrl;
55
+ connectionAuthToken = tursoAuthToken;
56
+ } else {
57
+ // Local SQLite fallback — create data/ directory if needed
58
+ const dataDir = join(PROJECT_ROOT, 'data');
59
+ if (!existsSync(dataDir)) {
60
+ mkdirSync(dataDir, { recursive: true });
61
+ }
62
+ connectionUrl = `file:${join(dataDir, 'local.db')}`;
63
+ connectionAuthToken = undefined;
64
+ }
65
+
66
+ _client = createClient({ url: connectionUrl, authToken: connectionAuthToken });
67
+ _db = drizzle(_client, { schema });
68
+
69
+ return _db;
70
+ };
71
+
72
+ /**
73
+ * Close the database connection. Useful for cleanup in tests.
74
+ */
75
+ export const closeDb = () => {
76
+ if (_client) {
77
+ _client.close();
78
+ _client = null;
79
+ _db = null;
80
+ }
81
+ };
@@ -0,0 +1,110 @@
1
+ /**
2
+ * GitWorkflow — handles worktree creation/cleanup, rebase, merge, branch management.
3
+ */
4
+
5
+ import { join } from 'node:path';
6
+
7
+ export class GitWorkflow {
8
+ constructor({ claudeService, projectRoot, worktreesDir, log }) {
9
+ this.claudeService = claudeService;
10
+ this.projectRoot = projectRoot;
11
+ this.worktreesDir = worktreesDir;
12
+ this.log = log;
13
+ }
14
+
15
+ /**
16
+ * Returns list of uncommitted changed files in the main repo, or empty array if clean.
17
+ */
18
+ getDirtyFiles = async () => {
19
+ const result = await this.claudeService.spawnCommand('git', ['status', '--porcelain'], this.projectRoot);
20
+ if (!result.trim()) return [];
21
+ return result.trim().split('\n').map(line => line.trim());
22
+ };
23
+
24
+ createWorktree = async (featureId) => {
25
+ const worktreePath = join(this.worktreesDir, featureId);
26
+ this.log('DEVELOP', `Creating worktree at ${worktreePath} (branch: feature/${featureId})`);
27
+ this.log('DEVELOP', `PROJECT_ROOT=${this.projectRoot}`);
28
+
29
+ // Remove leftover worktree from a previous failed/aborted pipeline
30
+ await this.claudeService.spawnCommand('git', ['worktree', 'remove', '--force', worktreePath], this.projectRoot).catch(() => {});
31
+ // Delete leftover branch
32
+ await this.claudeService.spawnCommand('git', ['branch', '-D', `feature/${featureId}`], this.projectRoot).catch(() => {});
33
+
34
+ await this.claudeService.spawnCommand('git', ['worktree', 'add', worktreePath, '-b', `feature/${featureId}`], this.projectRoot);
35
+ this.log('DEVELOP', `Worktree created successfully`);
36
+ return worktreePath;
37
+ };
38
+
39
+ removeWorktree = async (worktreePath) => {
40
+ await this.claudeService.spawnCommand('git', ['worktree', 'remove', '--force', worktreePath], this.projectRoot).catch(err => {
41
+ this.log('PIPELINE', `Worktree cleanup failed (non-fatal): ${err.message}`);
42
+ });
43
+ };
44
+
45
+ deleteBranch = async (featureId, force = false) => {
46
+ const flag = force ? '-D' : '-d';
47
+ await this.claudeService.spawnCommand('git', ['branch', flag, `feature/${featureId}`], this.projectRoot).catch(err => {
48
+ this.log('PIPELINE', `Branch cleanup failed (non-fatal): ${err.message}`);
49
+ });
50
+ };
51
+
52
+ commitChanges = async (featureId, worktreePath) => {
53
+ const statusResult = await this.claudeService.spawnCommand('git', ['status', '--porcelain'], worktreePath);
54
+ if (statusResult.trim()) {
55
+ this.log('PIPELINE', `Committing developer changes for ${featureId}...`);
56
+ await this.claudeService.spawnCommand('git', ['add', '-A'], worktreePath);
57
+ await this.claudeService.spawnCommand('git', ['commit', '-m', `feat(${featureId}): implement ${featureId}`], worktreePath);
58
+ this.log('PIPELINE', `Developer changes committed for ${featureId}`);
59
+ } else {
60
+ this.log('PIPELINE', `No uncommitted changes found for ${featureId} (developer may have committed already)`);
61
+ }
62
+ };
63
+
64
+ rebaseBranch = async (featureId, worktreePath) => {
65
+ this.log('PIPELINE', `Rebasing feature/${featureId} onto main...`);
66
+ try {
67
+ await this.claudeService.spawnCommand('git', ['rebase', 'main'], worktreePath);
68
+ this.log('PIPELINE', `Rebase succeeded for ${featureId}`);
69
+ } catch (rebaseErr) {
70
+ this.log('PIPELINE', `Rebase conflict for ${featureId} — aborting and attempting merge instead`);
71
+ await this.claudeService.spawnCommand('git', ['rebase', '--abort'], worktreePath).catch(() => {});
72
+ try {
73
+ await this.claudeService.spawnCommand('git', ['merge', 'main', '-m', `merge main into feature/${featureId}`], worktreePath);
74
+ this.log('PIPELINE', `Merge main into feature/${featureId} succeeded`);
75
+ } catch (mergeErr) {
76
+ this.log('PIPELINE', `WARN: Could not integrate main into ${featureId}: ${mergeErr.message}`);
77
+ }
78
+ }
79
+ };
80
+
81
+ mergeBranch = async (featureId) => {
82
+ this.log('PIPELINE', `Merging feature/${featureId} into parent branch...`);
83
+ await this.claudeService.spawnCommand('git', ['merge', `feature/${featureId}`], this.projectRoot);
84
+ this.log('PIPELINE', `Merge succeeded`);
85
+ };
86
+
87
+ fixMergeConflicts = async (featureId) => {
88
+ await this.claudeService.spawnClaude(
89
+ `There are merge conflicts after merging feature/${featureId}. Resolve all conflicts and commit.`,
90
+ this.projectRoot,
91
+ `merge-fix:${featureId}`,
92
+ );
93
+ const mergedBranches = await this.claudeService.spawnCommand('git', ['branch', '--merged', 'HEAD', '--list', `feature/${featureId}`], this.projectRoot);
94
+ return mergedBranches.trim().length > 0;
95
+ };
96
+
97
+ stash = async () => {
98
+ const stashResult = await this.claudeService.spawnCommand('git', ['stash', '--include-untracked'], this.projectRoot);
99
+ const stashed = !stashResult.includes('No local changes to save');
100
+ if (stashed) {
101
+ this.log('PIPELINE', `Stashed local changes before merge`);
102
+ }
103
+ return stashed;
104
+ };
105
+
106
+ unstash = async () => {
107
+ await this.claudeService.spawnCommand('git', ['stash', 'pop'], this.projectRoot);
108
+ this.log('PIPELINE', `Restored stashed local changes`);
109
+ };
110
+ }
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Graph operations backed by Drizzle/Turso.
3
+ * Replaces the old filesystem-based graph.json read/write.
4
+ * All query functions are async (Turso uses async driver).
5
+ */
6
+
7
+ import { eq, and } from 'drizzle-orm';
8
+ import { getDb, getProjectRoot as _getProjectRoot } from './db.js';
9
+ import { nodes, edges } from '../db/schema.js';
10
+
11
+ export { getProjectRoot } from './db.js';
12
+
13
+ /**
14
+ * Read the full graph structure from the database.
15
+ * Returns the same shape as the old graph.json: { version, project, nodes, edges }.
16
+ */
17
+ export const readGraph = async (projectId?: string) => {
18
+ const db = getDb();
19
+ const allNodes = projectId
20
+ ? await db.select().from(nodes).where(eq(nodes.projectId, projectId))
21
+ : await db.select().from(nodes);
22
+
23
+ const allEdges = await db.select().from(edges);
24
+ // When filtering by project, only include edges where both endpoints are in the filtered set
25
+ const filteredEdges = projectId
26
+ ? (() => { const ids = new Set(allNodes.map(n => n.id)); return allEdges.filter(e => ids.has(e.fromId) && ids.has(e.toId)); })()
27
+ : allEdges;
28
+
29
+ return {
30
+ version: '1.0',
31
+ project: {
32
+ name: '',
33
+ description: '',
34
+ created_at: '',
35
+ updated_at: '',
36
+ },
37
+ nodes: allNodes.map(n => ({
38
+ id: n.id,
39
+ type: n.type,
40
+ name: n.name,
41
+ status: n.status,
42
+ completeness: n.completeness,
43
+ open_questions_count: n.openQuestionsCount,
44
+ ...(n.kind ? { kind: n.kind } : {}),
45
+ created_at: n.createdAt,
46
+ updated_at: n.updatedAt,
47
+ })),
48
+ edges: filteredEdges.map(e => ({
49
+ from: e.fromId,
50
+ to: e.toId,
51
+ relation: e.relation,
52
+ })),
53
+ };
54
+ };
55
+
56
+ /**
57
+ * Write the full graph to the database (replaces all nodes and edges).
58
+ * Used by rebuild_index to do a full replace.
59
+ */
60
+ export const writeGraph = async (graph, projectId?: string) => {
61
+ const db = getDb();
62
+
63
+ await db.transaction(async (tx) => {
64
+ // Clear existing data
65
+ await tx.delete(edges);
66
+ await tx.delete(nodes);
67
+
68
+ // Insert all nodes
69
+ for (const n of graph.nodes) {
70
+ await tx.insert(nodes).values({
71
+ id: n.id,
72
+ type: n.type,
73
+ name: n.name,
74
+ status: n.status,
75
+ priority: n.priority || 'medium',
76
+ completeness: n.completeness,
77
+ openQuestionsCount: n.open_questions_count,
78
+ kind: n.kind || null,
79
+ createdAt: n.created_at,
80
+ updatedAt: n.updated_at,
81
+ body: n.body || null,
82
+ projectId: projectId || n.projectId || null,
83
+ });
84
+ }
85
+
86
+ // Insert all edges
87
+ for (const e of graph.edges) {
88
+ await tx.insert(edges).values({
89
+ fromId: e.from,
90
+ relation: e.relation,
91
+ toId: e.to,
92
+ projectId: projectId || e.projectId || null,
93
+ });
94
+ }
95
+ });
96
+ };
97
+
98
+ /**
99
+ * Patch specific fields on a node by ID.
100
+ */
101
+ export const patchNode = async (id, fields) => {
102
+ const db = getDb();
103
+ const rows = await db.select().from(nodes).where(eq(nodes.id, id));
104
+ const existing = rows[0];
105
+
106
+ if (!existing) {
107
+ throw new Error(`Node not found in graph: ${id}`);
108
+ }
109
+
110
+ const updates = {};
111
+ if (fields.status !== undefined) updates.status = fields.status;
112
+ if (fields.completeness !== undefined) updates.completeness = fields.completeness;
113
+ if (fields.open_questions_count !== undefined) updates.openQuestionsCount = fields.open_questions_count;
114
+ if (fields.kind !== undefined) updates.kind = fields.kind;
115
+ if (fields.name !== undefined) updates.name = fields.name;
116
+ if (fields.priority !== undefined) updates.priority = fields.priority;
117
+ if (fields.body !== undefined) updates.body = fields.body;
118
+ updates.updatedAt = new Date().toISOString();
119
+
120
+ await db.update(nodes).set(updates).where(eq(nodes.id, id));
121
+
122
+ return { ...existing, ...updates };
123
+ };
124
+
125
+ /**
126
+ * Add an edge to the database.
127
+ */
128
+ export const addEdge = async (from, relation, to, projectId?: string) => {
129
+ const db = getDb();
130
+ await db.insert(edges).values({
131
+ fromId: from,
132
+ relation,
133
+ toId: to,
134
+ projectId: projectId || null,
135
+ });
136
+ };
137
+
138
+ /**
139
+ * Remove an edge from the database.
140
+ */
141
+ export const removeEdge = async (from, relation, to) => {
142
+ const db = getDb();
143
+ const result = await db.delete(edges).where(
144
+ and(
145
+ eq(edges.fromId, from),
146
+ eq(edges.relation, relation),
147
+ eq(edges.toId, to),
148
+ )
149
+ );
150
+
151
+ if (result.rowsAffected === 0) {
152
+ throw new Error(`Edge not found: ${from} --${relation}--> ${to}`);
153
+ }
154
+ };
155
+
156
+ /**
157
+ * Check if a node exists in the database.
158
+ */
159
+ export const nodeExists = async (id) => {
160
+ const db = getDb();
161
+ const rows = await db.select({ id: nodes.id }).from(nodes).where(eq(nodes.id, id));
162
+ return rows.length > 0;
163
+ };
164
+
165
+ /**
166
+ * Get a node's metadata from the database.
167
+ */
168
+ export const getNode = async (id) => {
169
+ const db = getDb();
170
+ const rows = await db.select().from(nodes).where(eq(nodes.id, id));
171
+ if (rows.length === 0) return null;
172
+
173
+ const row = rows[0];
174
+ return {
175
+ id: row.id,
176
+ type: row.type,
177
+ name: row.name,
178
+ status: row.status,
179
+ completeness: row.completeness,
180
+ open_questions_count: row.openQuestionsCount,
181
+ ...(row.kind ? { kind: row.kind } : {}),
182
+ created_at: row.createdAt,
183
+ updated_at: row.updatedAt,
184
+ project_id: row.projectId,
185
+ };
186
+ };