@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,101 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * add_node — Creates a new node in the knowledge graph.
5
+ * Generates a unique ID, writes to DB, and adds the entry to the graph.
6
+ */
7
+
8
+ import { program } from 'commander';
9
+ import chalk from 'chalk';
10
+ import { eq } from 'drizzle-orm';
11
+ import { NODE_TYPES, VALID_FEATURE_KINDS } from '../lib/constants.js';
12
+ import { readGraph, patchNode } from '../lib/graph.js';
13
+ import { getDb } from '../lib/db.js';
14
+ import { nodes } from '../db/schema.js';
15
+ import { writeNode, deriveMetadata, templateSections } from '../lib/markdown.js';
16
+ import { assertValidType, assertUniqueName } from '../lib/validator.js';
17
+
18
+ program
19
+ .requiredOption('--type <type>', 'Node type (e.g., feature, component, decision)')
20
+ .requiredOption('--name <name>', 'Node name (must be unique within type)')
21
+ .requiredOption('--project-id <id>', 'Project ID')
22
+ .option('--description <text>', 'Initial description text', '')
23
+ .option('--priority <level>', 'Priority level (low, medium, high, blocking)', 'medium')
24
+ .option('--kind <kind>', 'Feature kind (new, improvement, bugfix). Only applies to features.', 'new')
25
+ .parse();
26
+
27
+ const opts = program.opts();
28
+
29
+ (async () => {
30
+ try {
31
+ assertValidType(opts.type);
32
+ await assertUniqueName(opts.type, opts.name, opts.projectId);
33
+
34
+ // Validate kind (only applies to features)
35
+ if (opts.type === 'feature' && !VALID_FEATURE_KINDS.includes(opts.kind)) {
36
+ throw new Error(`Invalid kind "${opts.kind}". Valid: ${VALID_FEATURE_KINDS.join(', ')}`);
37
+ }
38
+
39
+ // Generate next ID: prefix_NNN
40
+ const prefix = NODE_TYPES[opts.type];
41
+ const graph = await readGraph(opts.projectId);
42
+ const existing = graph.nodes.filter(n => n.type === opts.type);
43
+ const maxNum = existing.reduce((max, n) => {
44
+ const num = parseInt(n.id.split('_').pop(), 10);
45
+ return num > max ? num : max;
46
+ }, 0);
47
+ const id = `${prefix}_${String(maxNum + 1).padStart(3, '0')}`;
48
+
49
+ // Build frontmatter and sections
50
+ const now = new Date().toISOString();
51
+ const frontmatter = {
52
+ id,
53
+ type: opts.type,
54
+ name: opts.name,
55
+ status: 'draft',
56
+ priority: opts.priority,
57
+ ...(opts.type === 'feature' ? { kind: opts.kind } : {}),
58
+ created_at: now,
59
+ updated_at: now,
60
+ };
61
+
62
+ const sections = templateSections(opts.type);
63
+ if (opts.description) {
64
+ sections['Description'] = opts.description;
65
+ }
66
+
67
+ // Derive metadata
68
+ const meta = deriveMetadata(frontmatter, sections, opts.type);
69
+
70
+ // Serialize sections to body
71
+ const body = Object.entries(sections)
72
+ .map(([name, content]) => {
73
+ if (content.trim()) return `## ${name}\n${content.trim()}\n`;
74
+ return `## ${name}\n`;
75
+ })
76
+ .join('\n');
77
+
78
+ // Insert directly into DB
79
+ const db = getDb();
80
+ await db.insert(nodes).values({
81
+ id,
82
+ type: opts.type,
83
+ name: opts.name,
84
+ status: meta.status,
85
+ priority: opts.priority,
86
+ completeness: meta.completeness,
87
+ openQuestionsCount: meta.open_questions_count,
88
+ kind: opts.type === 'feature' ? opts.kind : null,
89
+ createdAt: now,
90
+ updatedAt: now,
91
+ body,
92
+ projectId: opts.projectId,
93
+ });
94
+
95
+ console.log(chalk.green(`✓ Created ${opts.type} node: ${id}`));
96
+ console.log(JSON.stringify({ id, type: opts.type, name: opts.name }));
97
+ } catch (err) {
98
+ console.error(chalk.red(`Error: ${err.message}`));
99
+ process.exit(1);
100
+ }
101
+ })();
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * end_session — Finalizes the current session with summary and gap analysis.
5
+ */
6
+
7
+ import { program } from 'commander';
8
+ import chalk from 'chalk';
9
+ import { readGraph } from '../lib/graph.js';
10
+ import { getLatestSession, endSession } from '../lib/session.js';
11
+
12
+ program
13
+ .requiredOption('--project-id <id>', 'Project ID')
14
+ .option('--summary <text>', 'Session summary (3-5 sentences)')
15
+ .option('--nodes-touched <ids>', 'Comma-separated list of node IDs touched this session')
16
+ .option('--questions-resolved <count>', 'Number of questions resolved', parseInt)
17
+ .parse();
18
+
19
+ const opts = program.opts();
20
+
21
+ (async () => {
22
+ try {
23
+ const session = await getLatestSession(opts.projectId);
24
+ if (!session) {
25
+ throw new Error('No sessions found. Run start_session first.');
26
+ }
27
+
28
+ if (session.endedAt) {
29
+ throw new Error(`Session ${session.sessionNumber} is already ended. Start a new session first.`);
30
+ }
31
+
32
+ // Build updated body
33
+ let updatedBody = session.body || '';
34
+ if (opts.summary) {
35
+ updatedBody = updatedBody.replace('### Summary\n', `### Summary\n${opts.summary}\n`);
36
+ }
37
+
38
+ // Add gap analysis
39
+ const graph = await readGraph();
40
+ const graphNodes = graph.nodes;
41
+ const totalNodes = graphNodes.length;
42
+ const avgCompleteness = totalNodes > 0
43
+ ? graphNodes.reduce((s, n) => s + n.completeness, 0) / totalNodes
44
+ : 0;
45
+ const totalOpenQuestions = graphNodes.reduce((s, n) => s + n.open_questions_count, 0);
46
+ const blockingGaps = graphNodes.filter(n => {
47
+ return n.status === 'draft' && graph.edges.some(e => e.to === n.id);
48
+ }).length;
49
+
50
+ updatedBody += `\n### Status at End
51
+ - Total nodes: ${totalNodes}
52
+ - Overall completeness: ${Math.round(avgCompleteness * 100)}%
53
+ - Open questions: ${totalOpenQuestions}
54
+ - Blocking gaps: ${blockingGaps}
55
+ `;
56
+
57
+ // Remaining gaps
58
+ const gapNodes = graphNodes
59
+ .filter(n => n.open_questions_count > 0 || n.status !== 'defined')
60
+ .sort((a, b) => a.completeness - b.completeness)
61
+ .slice(0, 10);
62
+
63
+ if (gapNodes.length > 0) {
64
+ updatedBody += '\n### Remaining Gaps\n';
65
+ for (const n of gapNodes) {
66
+ updatedBody += `- ${n.id} ${n.name} — ${Math.round(n.completeness * 100)}%, ${n.open_questions_count} open question(s)\n`;
67
+ }
68
+ }
69
+
70
+ // Update session
71
+ await endSession(session.sessionNumber, {
72
+ ended_at: new Date().toISOString(),
73
+ summary: opts.summary || null,
74
+ nodes_touched: opts.nodesTouched ? opts.nodesTouched.split(',').map(s => s.trim()) : [],
75
+ questions_resolved: opts.questionsResolved,
76
+ body: updatedBody,
77
+ });
78
+
79
+ console.log(chalk.green(`✓ Session ${session.sessionNumber} ended`));
80
+ console.log(`Nodes: ${totalNodes} — ${Math.round(avgCompleteness * 100)}% complete`);
81
+ console.log(`Open questions remaining: ${totalOpenQuestions}`);
82
+ console.log(`Blocking gaps: ${blockingGaps}`);
83
+ } catch (err) {
84
+ console.error(chalk.red(`Error: ${err.message}`));
85
+ process.exit(1);
86
+ }
87
+ })();
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * get_gaps — Returns a prioritized list of incomplete or unresolved items across the graph.
5
+ */
6
+
7
+ import { program } from 'commander';
8
+ import chalk from 'chalk';
9
+ import { readGraph } from '../lib/graph.js';
10
+
11
+ program
12
+ .requiredOption('--project-id <id>', 'Project ID')
13
+ .option('--blocking-only', 'Only show blocking gaps')
14
+ .option('--type <type>', 'Filter by node type')
15
+ .parse();
16
+
17
+ const opts = program.opts();
18
+
19
+ (async () => {
20
+ try {
21
+ const graph = await readGraph(opts.projectId);
22
+ let nodes = [...graph.nodes];
23
+
24
+ if (opts.type) {
25
+ nodes = nodes.filter(n => n.type === opts.type);
26
+ }
27
+
28
+ // Build set of nodes that are referenced by edges (depended upon)
29
+ const referenced = new Set();
30
+ for (const edge of graph.edges) {
31
+ referenced.add(edge.to);
32
+ }
33
+
34
+ const gaps = { BLOCKING: [], HIGH: [], MEDIUM: [], LOW: [] };
35
+
36
+ for (const node of nodes) {
37
+ // BLOCKING: draft nodes that are referenced by other nodes
38
+ if (node.status === 'draft' && referenced.has(node.id)) {
39
+ gaps.BLOCKING.push(node);
40
+ }
41
+ // HIGH: nodes with open questions and completeness < 0.5
42
+ else if (node.open_questions_count > 0 && node.completeness < 0.5) {
43
+ gaps.HIGH.push(node);
44
+ }
45
+ // MEDIUM: nodes with open questions and completeness >= 0.5
46
+ else if (node.open_questions_count > 0 && node.completeness >= 0.5) {
47
+ gaps.MEDIUM.push(node);
48
+ }
49
+ // LOW: partially defined but no open questions (optional sections missing)
50
+ else if (node.status === 'partially_defined' || (node.status === 'draft' && !referenced.has(node.id))) {
51
+ gaps.LOW.push(node);
52
+ }
53
+ }
54
+
55
+ if (opts.blockingOnly) {
56
+ printSection('BLOCKING', gaps.BLOCKING);
57
+ } else {
58
+ printSection('BLOCKING', gaps.BLOCKING);
59
+ printSection('HIGH', gaps.HIGH);
60
+ printSection('MEDIUM', gaps.MEDIUM);
61
+ printSection('LOW', gaps.LOW);
62
+ }
63
+
64
+ const total = gaps.BLOCKING.length + gaps.HIGH.length + gaps.MEDIUM.length + gaps.LOW.length;
65
+ if (total === 0) {
66
+ console.log(chalk.green('\n✓ No gaps found!'));
67
+ } else {
68
+ console.log(`\nTotal: ${total} gap(s) — ${gaps.BLOCKING.length} blocking, ${gaps.HIGH.length} high, ${gaps.MEDIUM.length} medium, ${gaps.LOW.length} low`);
69
+ }
70
+
71
+ // JSON output
72
+ console.log('\n' + JSON.stringify(gaps));
73
+ } catch (err) {
74
+ console.error(chalk.red(`Error: ${err.message}`));
75
+ process.exit(1);
76
+ }
77
+ })();
78
+
79
+ function printSection(severity, items) {
80
+ if (items.length === 0) return;
81
+ const colors = { BLOCKING: chalk.red, HIGH: chalk.yellow, MEDIUM: chalk.cyan, LOW: chalk.gray };
82
+ const color = colors[severity] || chalk.white;
83
+ console.log(color(`\n${severity} (${items.length}):`));
84
+ for (const n of items) {
85
+ console.log(` ${n.id} ${n.name} [${n.type}] — ${Math.round(n.completeness * 100)}% complete, ${n.open_questions_count} open question(s)`);
86
+ }
87
+ }
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * get_kanban — Reads kanban state from the database and displays cards, optionally filtered by column.
5
+ *
6
+ * Usage:
7
+ * node tools/get_kanban.js # show all columns
8
+ * node tools/get_kanban.js --column todo # only todo items
9
+ * node tools/get_kanban.js --column qa # only qa items
10
+ */
11
+
12
+ import { program } from 'commander';
13
+ import chalk from 'chalk';
14
+ import { readGraph } from '../lib/graph.js';
15
+ import { loadKanban, kanbanExists } from '../lib/kanban.js';
16
+
17
+ const VALID_COLUMNS = ['todo', 'in_progress', 'in_review', 'qa', 'done'];
18
+
19
+ const COLUMN_COLORS = {
20
+ todo: chalk.gray,
21
+ in_progress: chalk.blue,
22
+ in_review: chalk.magenta,
23
+ qa: chalk.yellow,
24
+ done: chalk.green,
25
+ };
26
+
27
+ program
28
+ .requiredOption('--project-id <id>', 'Project ID')
29
+ .option('--column <column>', 'Filter by column (todo, in_progress, qa, done)')
30
+ .parse();
31
+
32
+ const opts = program.opts();
33
+
34
+ (async () => {
35
+ try {
36
+ if (!(await kanbanExists(opts.projectId))) {
37
+ console.log(chalk.yellow('No kanban entries found. No features are being tracked yet.'));
38
+ process.exit(0);
39
+ }
40
+
41
+ if (opts.column && !VALID_COLUMNS.includes(opts.column)) {
42
+ throw new Error(`Invalid column "${opts.column}". Valid columns: ${VALID_COLUMNS.join(', ')}`);
43
+ }
44
+
45
+ const kanban = await loadKanban(opts.projectId);
46
+ const graph = await readGraph(opts.projectId);
47
+
48
+ // Build lookups from graph nodes
49
+ const nameMap = {};
50
+ const kindMap = {};
51
+ for (const node of graph.nodes) {
52
+ nameMap[node.id] = node.name;
53
+ if (node.type === 'feature') {
54
+ kindMap[node.id] = node.kind || 'new';
55
+ }
56
+ }
57
+
58
+ // Group entries by column
59
+ const columns = { todo: [], in_progress: [], qa: [], done: [] };
60
+
61
+ for (const [featureId, entry] of Object.entries(kanban)) {
62
+ const col = entry.column;
63
+ if (!columns[col]) columns[col] = [];
64
+ columns[col].push({
65
+ id: featureId,
66
+ name: nameMap[featureId] || featureId,
67
+ kind: kindMap[featureId] || 'new',
68
+ rejection_count: entry.rejection_count || 0,
69
+ notes_count: (entry.notes || []).length,
70
+ });
71
+ }
72
+
73
+ // Display
74
+ const columnsToShow = opts.column ? [opts.column] : VALID_COLUMNS;
75
+
76
+ for (const col of columnsToShow) {
77
+ const items = columns[col] || [];
78
+ const colorFn = COLUMN_COLORS[col] || chalk.white;
79
+ const label = col.replace('_', ' ').toUpperCase();
80
+
81
+ console.log(colorFn.bold(`\n${label} (${items.length})`));
82
+
83
+ if (items.length === 0) {
84
+ console.log(chalk.gray(' (empty)'));
85
+ continue;
86
+ }
87
+
88
+ for (const item of items) {
89
+ let line = ` ${item.id} ${item.name}`;
90
+ if (item.kind !== 'new') {
91
+ const kindColors = { improvement: chalk.blue, bugfix: chalk.yellow };
92
+ const kindFn = kindColors[item.kind] || chalk.white;
93
+ line += ` ${kindFn(`[${item.kind}]`)}`;
94
+ }
95
+ if (item.rejection_count > 0) {
96
+ const rejLabel = item.rejection_count >= 3
97
+ ? chalk.red.bold(`[${item.rejection_count}x rejected]`)
98
+ : chalk.yellow(`[${item.rejection_count}x rejected]`);
99
+ line += ` ${rejLabel}`;
100
+ }
101
+ if (item.notes_count > 0) {
102
+ line += chalk.gray(` (${item.notes_count} note${item.notes_count > 1 ? 's' : ''})`);
103
+ }
104
+ console.log(line);
105
+ }
106
+ }
107
+
108
+ // JSON output
109
+ const jsonOutput = {};
110
+ for (const col of columnsToShow) {
111
+ jsonOutput[col] = (columns[col] || []).map(item => ({
112
+ id: item.id,
113
+ name: item.name,
114
+ kind: item.kind,
115
+ rejection_count: item.rejection_count,
116
+ notes_count: item.notes_count,
117
+ }));
118
+ }
119
+ console.log('\n' + JSON.stringify(jsonOutput));
120
+
121
+ } catch (err) {
122
+ console.error(chalk.red(`Error: ${err.message}`));
123
+ process.exit(1);
124
+ }
125
+ })();
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * get_node — Loads and prints the full content of a node from the database, plus its relations.
5
+ *
6
+ * Usage:
7
+ * node tools/get_node.js feat_001
8
+ * node tools/get_node.js --name "Node Name"
9
+ */
10
+
11
+ import { program } from 'commander';
12
+ import chalk from 'chalk';
13
+ import matter from 'gray-matter';
14
+ import { readGraph } from '../lib/graph.js';
15
+ import { readNode } from '../lib/markdown.js';
16
+
17
+ program
18
+ .argument('[id]', 'Node ID (e.g., feat_001)')
19
+ .requiredOption('--project-id <id>', 'Project ID')
20
+ .option('--name <name>', 'Look up node by name instead of ID')
21
+ .parse();
22
+
23
+ const args = program.args;
24
+ const opts = program.opts();
25
+
26
+ (async () => {
27
+ try {
28
+ const graph = await readGraph(opts.projectId);
29
+ let node;
30
+
31
+ if (args[0]) {
32
+ node = graph.nodes.find(n => n.id === args[0]);
33
+ if (!node) {
34
+ throw new Error(`Node not found: ${args[0]}`);
35
+ }
36
+ } else if (opts.name) {
37
+ node = graph.nodes.find(n => n.name.toLowerCase() === opts.name.toLowerCase());
38
+ if (!node) {
39
+ throw new Error(`No node named "${opts.name}"`);
40
+ }
41
+ } else {
42
+ throw new Error('Provide a node ID or --name');
43
+ }
44
+
45
+ // Read node content from DB
46
+ const { frontmatter, sections } = await readNode(node.id);
47
+
48
+ // Reconstruct the markdown output for display
49
+ const body = Object.entries(sections)
50
+ .map(([name, content]) => content.trim() ? `## ${name}\n${content.trim()}\n` : `## ${name}\n`)
51
+ .join('\n');
52
+ const content = matter.stringify(body, frontmatter);
53
+ console.log(content);
54
+
55
+ // Print relations
56
+ const edges = graph.edges.filter(e => e.from === node.id || e.to === node.id);
57
+
58
+ if (edges.length > 0) {
59
+ console.log(chalk.cyan.bold('\n── Relations ──\n'));
60
+
61
+ for (const edge of edges) {
62
+ const isOutgoing = edge.from === node.id;
63
+ const otherId = isOutgoing ? edge.to : edge.from;
64
+ const other = graph.nodes.find(n => n.id === otherId);
65
+ const otherLabel = other ? `${other.name} [${other.type}] ${other.status}` : otherId;
66
+
67
+ if (isOutgoing) {
68
+ console.log(` ${chalk.bold(node.id)} ${chalk.gray(`--${edge.relation}-->`)} ${chalk.bold(otherId)} ${otherLabel}`);
69
+ } else {
70
+ console.log(` ${chalk.bold(otherId)} ${otherLabel} ${chalk.gray(`--${edge.relation}-->`)} ${chalk.bold(node.id)}`);
71
+ }
72
+ }
73
+ }
74
+ } catch (err) {
75
+ console.error(chalk.red(`Error: ${err.message}`));
76
+ process.exit(1);
77
+ }
78
+ })();
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * get_status — Returns overall completeness across the whole project, grouped by domain.
5
+ */
6
+
7
+ import { program } from 'commander';
8
+ import chalk from 'chalk';
9
+ import { readGraph } from '../lib/graph.js';
10
+
11
+ program
12
+ .requiredOption('--project-id <id>', 'Project ID')
13
+ .parse();
14
+
15
+ const opts = program.opts();
16
+
17
+ (async () => {
18
+ try {
19
+ const graph = await readGraph(opts.projectId);
20
+ const nodes = graph.nodes;
21
+
22
+ if (nodes.length === 0) {
23
+ console.log(chalk.yellow('No nodes in the graph yet.'));
24
+ process.exit(0);
25
+ }
26
+
27
+ // Project header
28
+ const projectName = graph.project.name || '(unnamed project)';
29
+ const overallCompleteness = nodes.reduce((sum, n) => sum + n.completeness, 0) / nodes.length;
30
+
31
+ console.log(chalk.bold(`Project: ${projectName}`));
32
+ console.log(chalk.bold(`Overall: ${Math.round(overallCompleteness * 100)}% complete\n`));
33
+
34
+ // Group by type
35
+ const groups = {};
36
+ for (const node of nodes) {
37
+ if (!groups[node.type]) groups[node.type] = [];
38
+ groups[node.type].push(node);
39
+ }
40
+
41
+ // Display order (most common types first)
42
+ const typeOrder = [
43
+ 'feature', 'component', 'data_entity', 'decision', 'tech_choice',
44
+ 'non_functional_requirement', 'flow', 'user_role',
45
+ 'design_token', 'design_pattern', 'assumption', 'open_question',
46
+ ];
47
+
48
+ for (const type of typeOrder) {
49
+ const items = groups[type];
50
+ if (!items) continue;
51
+
52
+ const avgCompleteness = items.reduce((s, n) => s + n.completeness, 0) / items.length;
53
+ const defined = items.filter(n => n.status === 'defined').length;
54
+ const partial = items.filter(n => n.status === 'partially_defined').length;
55
+ const draft = items.filter(n => n.status === 'draft').length;
56
+ const bar = progressBar(avgCompleteness);
57
+
58
+ const label = formatType(type).padEnd(20);
59
+ const detail = `(${defined} defined, ${partial} partial, ${draft} draft)`;
60
+ console.log(`${label} ${bar} ${Math.round(avgCompleteness * 100)}% ${detail}`);
61
+ }
62
+
63
+ // Summary stats
64
+ const blockingGaps = nodes.filter(n => {
65
+ const isReferenced = graph.edges.some(e => e.to === n.id);
66
+ return n.status === 'draft' && isReferenced;
67
+ }).length;
68
+
69
+ const totalOpenQuestions = nodes.reduce((s, n) => s + n.open_questions_count, 0);
70
+ const unresolvedDecisions = nodes.filter(n => n.type === 'decision' && n.status !== 'defined').length;
71
+
72
+ console.log(`\nBlocking gaps: ${blockingGaps}`);
73
+ console.log(`Open questions: ${totalOpenQuestions}`);
74
+ console.log(`Unresolved decisions: ${unresolvedDecisions}`);
75
+
76
+ // JSON output
77
+ console.log('\n' + JSON.stringify({
78
+ project: projectName,
79
+ overall_completeness: Math.round(overallCompleteness * 100) / 100,
80
+ total_nodes: nodes.length,
81
+ blocking_gaps: blockingGaps,
82
+ open_questions: totalOpenQuestions,
83
+ unresolved_decisions: unresolvedDecisions,
84
+ }));
85
+ } catch (err) {
86
+ console.error(chalk.red(`Error: ${err.message}`));
87
+ process.exit(1);
88
+ }
89
+ })();
90
+
91
+ function progressBar(value, width = 10) {
92
+ const filled = Math.round(value * width);
93
+ return chalk.green('█'.repeat(filled)) + chalk.gray('░'.repeat(width - filled));
94
+ }
95
+
96
+ function formatType(type) {
97
+ return type.split('_').map(w => w[0].toUpperCase() + w.slice(1)).join(' ');
98
+ }