@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,96 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * remove_node — Deletes a node and all its edges from the knowledge graph.
5
+ */
6
+
7
+ import { program } from 'commander';
8
+ import chalk from 'chalk';
9
+ import { eq, or } from 'drizzle-orm';
10
+ import { createInterface } from 'node:readline';
11
+ import { getDb } from '../lib/db.js';
12
+ import { getNode } from '../lib/graph.js';
13
+ import { assertNodeExists } from '../lib/validator.js';
14
+ import { nodes, edges, kanban, reviewMeta } from '../db/schema.js';
15
+
16
+ program
17
+ .argument('<id>', 'Node ID to delete (e.g. feat_034)')
18
+ .requiredOption('--project-id <id>', 'Project ID')
19
+ .option('--force', 'Skip confirmation prompt')
20
+ .parse();
21
+
22
+ const [id] = program.args;
23
+ const opts = program.opts();
24
+
25
+ const confirm = async (message: string): Promise<boolean> => {
26
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
27
+ return new Promise((resolve) => {
28
+ rl.question(message, (answer) => {
29
+ rl.close();
30
+ resolve(answer.trim().toLowerCase() === 'y');
31
+ });
32
+ });
33
+ };
34
+
35
+ (async () => {
36
+ try {
37
+ await assertNodeExists(id);
38
+
39
+ const node = await getNode(id);
40
+ const db = getDb();
41
+
42
+ // Count edges that will be removed
43
+ const relatedEdges = await db.select().from(edges).where(
44
+ or(eq(edges.fromId, id), eq(edges.toId, id))
45
+ );
46
+
47
+ // Check kanban status
48
+ const kanbanRows = await db.select().from(kanban).where(eq(kanban.nodeId, id));
49
+ const kanbanStatus = kanbanRows[0]?.columnName ?? '(none)';
50
+
51
+ // Print summary
52
+ console.log(chalk.yellow('\n⚠ About to delete:'));
53
+ console.log(` Node: ${chalk.bold(id)} — ${node.name}`);
54
+ console.log(` Type: ${node.type}`);
55
+ console.log(` Status: ${node.status}`);
56
+ console.log(` Kanban: ${kanbanStatus}`);
57
+ console.log(` Edges: ${relatedEdges.length} will be removed\n`);
58
+
59
+ if (!opts.force) {
60
+ const ok = await confirm(chalk.red('Type "y" to confirm deletion: '));
61
+ if (!ok) {
62
+ console.log(chalk.gray('Aborted.'));
63
+ process.exit(0);
64
+ }
65
+ }
66
+
67
+ // Delete everything in a single transaction
68
+ await db.transaction(async (tx) => {
69
+ // kanban row (FK to nodes — must go first)
70
+ await tx.delete(kanban).where(eq(kanban.nodeId, id));
71
+
72
+ // review_meta row with key kanban:<id>
73
+ await tx.delete(reviewMeta).where(eq(reviewMeta.key, `kanban:${id}`));
74
+
75
+ // All edges where from_id = id OR to_id = id
76
+ await tx.delete(edges).where(
77
+ or(eq(edges.fromId, id), eq(edges.toId, id))
78
+ );
79
+
80
+ // The node itself
81
+ await tx.delete(nodes).where(eq(nodes.id, id));
82
+ });
83
+
84
+ console.log(chalk.green(`\n✓ Deleted node: ${id}`));
85
+ console.log(JSON.stringify({
86
+ id,
87
+ name: node.name,
88
+ type: node.type,
89
+ edgesRemoved: relatedEdges.length,
90
+ kanbanRemoved: kanbanRows.length > 0,
91
+ }));
92
+ } catch (err) {
93
+ console.error(chalk.red(`Error: ${err.message}`));
94
+ process.exit(1);
95
+ }
96
+ })();
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * resolve_question — Marks an open question as resolved and records the answer.
5
+ */
6
+
7
+ import { program } from 'commander';
8
+ import chalk from 'chalk';
9
+ import { readGraph, patchNode } from '../lib/graph.js';
10
+ import { readNode, writeNode, deriveMetadata } from '../lib/markdown.js';
11
+ import { assertNodeExists } from '../lib/validator.js';
12
+
13
+ program
14
+ .argument('<id>', 'Node ID containing the question')
15
+ .requiredOption('--project-id <id>', 'Project ID')
16
+ .requiredOption('--question <text>', 'The open question text to resolve (substring match)')
17
+ .requiredOption('--answer <text>', 'The answer to record')
18
+ .parse();
19
+
20
+ const id = program.args[0];
21
+ const opts = program.opts();
22
+
23
+ (async () => {
24
+ try {
25
+ await assertNodeExists(id);
26
+
27
+ const graph = await readGraph();
28
+ const nodeMeta = graph.nodes.find(n => n.id === id);
29
+ let { frontmatter, sections } = await readNode(id);
30
+
31
+ // Find the open question line
32
+ const oqContent = sections['Open Questions'] || '';
33
+ const oqLines = oqContent.split('\n');
34
+ const matchIdx = oqLines.findIndex(l => l.includes(opts.question));
35
+
36
+ if (matchIdx === -1) {
37
+ throw new Error(`Open question not found containing: "${opts.question}"`);
38
+ }
39
+
40
+ // Remove from Open Questions
41
+ const matchedLine = oqLines[matchIdx];
42
+ oqLines.splice(matchIdx, 1);
43
+ sections['Open Questions'] = oqLines.join('\n').trim();
44
+
45
+ // Extract the question text (strip the checkbox prefix)
46
+ const questionText = matchedLine.replace(/^-\s*\[\s*\]\s*/, '').trim();
47
+
48
+ // Add to Resolved Questions with answer
49
+ const resolvedLine = `- [x] ${questionText} → ${opts.answer}`;
50
+ const rqContent = sections['Resolved Questions'] || '';
51
+ sections['Resolved Questions'] = rqContent ? rqContent + '\n' + resolvedLine : resolvedLine;
52
+
53
+ frontmatter.updated_at = new Date().toISOString();
54
+ await writeNode(id, frontmatter, sections);
55
+
56
+ // Re-derive metadata and update graph
57
+ const meta = deriveMetadata(frontmatter, sections, nodeMeta.type);
58
+ await patchNode(id, {
59
+ status: meta.status,
60
+ completeness: meta.completeness,
61
+ open_questions_count: meta.open_questions_count,
62
+ });
63
+
64
+ console.log(chalk.green(`✓ Resolved question on ${id}`));
65
+ console.log(JSON.stringify({
66
+ id,
67
+ question: questionText,
68
+ answer: opts.answer,
69
+ open_questions_remaining: meta.open_questions_count,
70
+ }));
71
+ } catch (err) {
72
+ console.error(chalk.red(`Error: ${err.message}`));
73
+ process.exit(1);
74
+ }
75
+ })();
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * search_nodes — Graph-aware relevance search with ranking.
5
+ *
6
+ * When --query is provided:
7
+ * 1. Direct keyword matches (name or body) scored highest
8
+ * 2. 1-hop graph neighbors of matches scored by edge type weight
9
+ * 3. Cold start fallback: top-5 most-connected per type if zero matches
10
+ *
11
+ * Filters (--type, --status, --has-open-questions, --completeness-below) are
12
+ * applied after relevance ranking.
13
+ */
14
+
15
+ import { program } from 'commander';
16
+ import chalk from 'chalk';
17
+ import { eq } from 'drizzle-orm';
18
+ import { readGraph } from '../lib/graph.js';
19
+ import { getDb } from '../lib/db.js';
20
+ import { nodes } from '../db/schema.js';
21
+ import { relevanceSearch } from '../lib/relevance_search.js';
22
+
23
+ program
24
+ .requiredOption('--project-id <id>', 'Project ID')
25
+ .option('--query <keyword>', 'Search by keyword in name and body content')
26
+ .option('--type <type>', 'Filter by node type')
27
+ .option('--status <status>', 'Filter by status')
28
+ .option('--has-open-questions', 'Only nodes with open questions')
29
+ .option('--completeness-below <threshold>', 'Only nodes below completeness threshold', parseFloat)
30
+ .parse();
31
+
32
+ const opts = program.opts();
33
+
34
+ (async () => {
35
+ try {
36
+ const graph = await readGraph(opts.projectId);
37
+
38
+ let results;
39
+
40
+ if (opts.query) {
41
+ const db = getDb();
42
+ const getBody = async (nodeId) => {
43
+ const rows = await db.select({ body: nodes.body }).from(nodes).where(eq(nodes.id, nodeId));
44
+ return rows[0]?.body ?? null;
45
+ };
46
+
47
+ results = await relevanceSearch(opts.query, graph.nodes, graph.edges, getBody);
48
+ } else {
49
+ // No keyword — return all nodes without relevance (backward compat)
50
+ results = graph.nodes.map(n => ({
51
+ id: n.id,
52
+ name: n.name,
53
+ type: n.type,
54
+ status: n.status,
55
+ completeness: n.completeness,
56
+ open_questions_count: n.open_questions_count,
57
+ relevance: null,
58
+ _score: 0,
59
+ }));
60
+ }
61
+
62
+ // Apply post-ranking filters
63
+ if (opts.type) {
64
+ results = results.filter(n => n.type === opts.type);
65
+ }
66
+
67
+ if (opts.status) {
68
+ results = results.filter(n => n.status === opts.status);
69
+ }
70
+
71
+ if (opts.hasOpenQuestions) {
72
+ results = results.filter(n => n.open_questions_count > 0);
73
+ }
74
+
75
+ if (opts.completenessBelow !== undefined) {
76
+ results = results.filter(n => n.completeness < opts.completenessBelow);
77
+ }
78
+
79
+ if (results.length === 0) {
80
+ console.log(chalk.yellow('No matching nodes found.'));
81
+ } else {
82
+ console.log(chalk.cyan(`Found ${results.length} node(s):\n`));
83
+ for (const n of results) {
84
+ const bar = progressBar(n.completeness);
85
+ const oq = n.open_questions_count > 0 ? chalk.yellow(` (${n.open_questions_count} open questions)`) : '';
86
+ const rel = n.relevance ? chalk.gray(` [${n.relevance}]`) : '';
87
+ console.log(` ${chalk.bold(n.id)} ${n.name} [${n.type}] ${n.status} ${bar} ${Math.round(n.completeness * 100)}%${oq}${rel}`);
88
+ }
89
+ }
90
+
91
+ // JSON output includes relevance label
92
+ console.log('\n' + JSON.stringify(results.map(n => ({
93
+ id: n.id, name: n.name, type: n.type, status: n.status,
94
+ completeness: n.completeness, open_questions_count: n.open_questions_count,
95
+ relevance: n.relevance,
96
+ }))));
97
+ } catch (err) {
98
+ console.error(chalk.red(`Error: ${err.message}`));
99
+ process.exit(1);
100
+ }
101
+ })();
102
+
103
+ function progressBar(value, width = 10) {
104
+ const filled = Math.round(value * width);
105
+ return chalk.green('█'.repeat(filled)) + chalk.gray('░'.repeat(width - filled));
106
+ }
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * start_session — Creates a session record in the database, runs get_status logic,
5
+ * loads context from recent sessions, and prints resume context.
6
+ */
7
+
8
+ import { program } from 'commander';
9
+ import chalk from 'chalk';
10
+ import { readGraph } from '../lib/graph.js';
11
+ import {
12
+ getNextSessionNumber,
13
+ createSession,
14
+ getRecentSessions,
15
+ getRecentlyModifiedNodes,
16
+ getRecentDecisions,
17
+ } from '../lib/session.js';
18
+
19
+ program
20
+ .requiredOption('--project-id <id>', 'Project ID')
21
+ .parse();
22
+
23
+ const opts = program.opts();
24
+
25
+ /**
26
+ * Extract a short description excerpt from a node body (first non-empty line after ## Description).
27
+ */
28
+ const extractDescriptionExcerpt = (body, maxLen = 120) => {
29
+ if (!body) return '';
30
+ const match = body.match(/## Description\s*\n+(.+)/);
31
+ if (!match) return '';
32
+ const line = match[1].trim();
33
+ return line.length > maxLen ? line.slice(0, maxLen) + '…' : line;
34
+ };
35
+
36
+ (async () => {
37
+ try {
38
+ const sessionNum = await getNextSessionNumber(opts.projectId);
39
+ const graph = await readGraph(opts.projectId);
40
+ const graphNodes = graph.nodes;
41
+
42
+ // Compute status summary
43
+ const totalNodes = graphNodes.length;
44
+ const avgCompleteness = totalNodes > 0
45
+ ? graphNodes.reduce((s, n) => s + n.completeness, 0) / totalNodes
46
+ : 0;
47
+ const totalOpenQuestions = graphNodes.reduce((s, n) => s + n.open_questions_count, 0);
48
+ const blockingGaps = graphNodes.filter(n => {
49
+ return n.status === 'draft' && graph.edges.some(e => e.to === n.id);
50
+ }).length;
51
+
52
+ // Load context recall data
53
+ const recentSessions = await getRecentSessions(3, opts.projectId);
54
+ const recentNodes = await getRecentlyModifiedNodes(10, opts.projectId);
55
+ const recentDecisions = await getRecentDecisions(recentSessions);
56
+
57
+ // Create session body
58
+ const body = `## Session ${sessionNum}
59
+
60
+ ### Status at Start
61
+ - Total nodes: ${totalNodes}
62
+ - Overall completeness: ${Math.round(avgCompleteness * 100)}%
63
+ - Open questions: ${totalOpenQuestions}
64
+ - Blocking gaps: ${blockingGaps}
65
+
66
+ ### Notes
67
+
68
+ ### Summary
69
+ `;
70
+
71
+ await createSession(sessionNum, body, opts.projectId);
72
+
73
+ // Print resume context
74
+ const sessionId = `session_${String(sessionNum).padStart(3, '0')}`;
75
+ console.log(chalk.bold(`\n📋 Session ${sessionNum} started`));
76
+ console.log(chalk.cyan(`Session: ${sessionId}\n`));
77
+
78
+ if (totalNodes === 0) {
79
+ console.log('No nodes yet. Start by adding the first feature or component.');
80
+ } else {
81
+ console.log(`Project: ${graph.project.name || '(unnamed)'}`);
82
+ console.log(`Nodes: ${totalNodes} — ${Math.round(avgCompleteness * 100)}% overall completeness`);
83
+ console.log(`Open questions: ${totalOpenQuestions}`);
84
+ console.log(`Blocking gaps: ${blockingGaps}`);
85
+
86
+ // Show top gaps
87
+ const highPriority = graphNodes
88
+ .filter(n => n.open_questions_count > 0 || n.status === 'draft')
89
+ .sort((a, b) => a.completeness - b.completeness)
90
+ .slice(0, 5);
91
+
92
+ if (highPriority.length > 0) {
93
+ console.log(chalk.yellow('\nSuggested focus:'));
94
+ for (const n of highPriority) {
95
+ console.log(` ${n.id} ${n.name} — ${Math.round(n.completeness * 100)}%, ${n.open_questions_count} open question(s)`);
96
+ }
97
+ }
98
+
99
+ // --- Context Recall ---
100
+ if (recentSessions.length > 0) {
101
+ console.log(chalk.bold('\n── Recent Session Context ──'));
102
+
103
+ // Recent session summaries
104
+ console.log(chalk.yellow('\nLast sessions:'));
105
+ for (const s of recentSessions) {
106
+ const nodesTouched = s.nodesTouched ? JSON.parse(s.nodesTouched) : [];
107
+ console.log(` Session ${s.sessionNumber} (${s.startedAt} → ${s.endedAt})`);
108
+ if (s.summary) {
109
+ console.log(` Summary: ${s.summary}`);
110
+ }
111
+ if (nodesTouched.length > 0) {
112
+ console.log(` Nodes touched: ${nodesTouched.join(', ')}`);
113
+ }
114
+ console.log(` Questions resolved: ${s.questionsResolved || 0}`);
115
+ }
116
+
117
+ // Recently modified nodes
118
+ if (recentNodes.length > 0) {
119
+ console.log(chalk.yellow('\nRecently modified nodes:'));
120
+ for (const n of recentNodes) {
121
+ console.log(` ${n.id} ${n.name} [${n.type}] ${n.status} — ${Math.round(n.completeness * 100)}% (updated ${n.updatedAt})`);
122
+ }
123
+ }
124
+
125
+ // Recent decisions
126
+ if (recentDecisions.length > 0) {
127
+ console.log(chalk.yellow('\nRecent decisions:'));
128
+ for (const d of recentDecisions) {
129
+ const excerpt = extractDescriptionExcerpt(d.body);
130
+ console.log(` ${d.id} ${d.name} [${d.status}]`);
131
+ if (excerpt) {
132
+ console.log(` ${excerpt}`);
133
+ }
134
+ }
135
+ }
136
+ }
137
+ }
138
+
139
+ console.log('\n' + JSON.stringify({ session: sessionNum, id: sessionId }));
140
+ } catch (err) {
141
+ console.error(chalk.red(`Error: ${err.message}`));
142
+ process.exit(1);
143
+ }
144
+ })();
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * update_node — Modifies content within an existing node and re-derives metadata.
5
+ */
6
+
7
+ import { program } from 'commander';
8
+ import chalk from 'chalk';
9
+ import { readGraph, patchNode } from '../lib/graph.js';
10
+ import { readNode, writeNode, appendToSection, setSection, deriveMetadata } from '../lib/markdown.js';
11
+ import { assertNodeExists } from '../lib/validator.js';
12
+ import { VALID_STATUSES, VALID_PRIORITIES, VALID_FEATURE_KINDS } from '../lib/constants.js';
13
+ import { getKanbanEntry, saveKanbanEntry } from '../lib/kanban.js';
14
+
15
+ program
16
+ .argument('<id>', 'Node ID to update')
17
+ .requiredOption('--project-id <id>', 'Project ID')
18
+ .option('--set-status <status>', 'Set node status (draft, partially_defined, defined)')
19
+ .option('--set-priority <priority>', 'Set priority (low, medium, high, blocking)')
20
+ .option('--add-acceptance-criteria <text>', 'Append an acceptance criterion')
21
+ .option('--add-open-question <text>', 'Append an open question')
22
+ .option('--add-note <text>', 'Append a note')
23
+ .option('--set-section <name=content>', 'Set a section\'s content (name=content format)')
24
+ .option('--set-description <text>', 'Set the Description section content')
25
+ .option('--set-kind <kind>', 'Set feature kind (new, improvement, bugfix). Only applies to features.')
26
+ .option('--set-name <name>', 'Rename the node')
27
+ .parse();
28
+
29
+ const id = program.args[0];
30
+ const opts = program.opts();
31
+
32
+ (async () => {
33
+ try {
34
+ await assertNodeExists(id);
35
+
36
+ const graph = await readGraph();
37
+ const nodeMeta = graph.nodes.find(n => n.id === id);
38
+ let { frontmatter, sections } = await readNode(id);
39
+
40
+ if (opts.setStatus) {
41
+ if (!VALID_STATUSES.includes(opts.setStatus)) {
42
+ throw new Error(`Invalid status "${opts.setStatus}". Valid: ${VALID_STATUSES.join(', ')}`);
43
+ }
44
+ frontmatter.status = opts.setStatus;
45
+ }
46
+
47
+ if (opts.setPriority) {
48
+ if (!VALID_PRIORITIES.includes(opts.setPriority)) {
49
+ throw new Error(`Invalid priority "${opts.setPriority}". Valid: ${VALID_PRIORITIES.join(', ')}`);
50
+ }
51
+ frontmatter.priority = opts.setPriority;
52
+ }
53
+
54
+ if (opts.setKind) {
55
+ if (nodeMeta.type !== 'feature') {
56
+ throw new Error('--set-kind is only valid for feature nodes');
57
+ }
58
+ if (!VALID_FEATURE_KINDS.includes(opts.setKind)) {
59
+ throw new Error(`Invalid kind "${opts.setKind}". Valid: ${VALID_FEATURE_KINDS.join(', ')}`);
60
+ }
61
+ frontmatter.kind = opts.setKind;
62
+ }
63
+
64
+ if (opts.setName) {
65
+ frontmatter.name = opts.setName;
66
+ }
67
+
68
+ if (opts.addAcceptanceCriteria) {
69
+ appendToSection(sections, 'Acceptance Criteria', `- ${opts.addAcceptanceCriteria}`);
70
+ }
71
+
72
+ if (opts.addOpenQuestion) {
73
+ appendToSection(sections, 'Open Questions', `- [ ] ${opts.addOpenQuestion}`);
74
+ }
75
+
76
+ if (opts.addNote) {
77
+ appendToSection(sections, 'Notes', `> ${opts.addNote}`);
78
+ }
79
+
80
+ if (opts.setDescription) {
81
+ setSection(sections, 'Description', opts.setDescription);
82
+ }
83
+
84
+ if (opts.setSection) {
85
+ const eqIdx = opts.setSection.indexOf('=');
86
+ if (eqIdx === -1) {
87
+ throw new Error('--set-section must be in "SectionName=content" format');
88
+ }
89
+ const sectionName = opts.setSection.slice(0, eqIdx);
90
+ const sectionContent = opts.setSection.slice(eqIdx + 1);
91
+ setSection(sections, sectionName, sectionContent);
92
+ }
93
+
94
+ frontmatter.updated_at = new Date().toISOString();
95
+ await writeNode(id, frontmatter, sections);
96
+
97
+ // Re-derive metadata and update graph
98
+ const meta = deriveMetadata(frontmatter, sections, nodeMeta.type);
99
+ const patch = {
100
+ status: opts.setStatus || meta.status,
101
+ completeness: meta.completeness,
102
+ open_questions_count: meta.open_questions_count,
103
+ };
104
+ if (opts.setKind) {
105
+ patch.kind = opts.setKind;
106
+ }
107
+ if (opts.setName) {
108
+ patch.name = opts.setName;
109
+ }
110
+ await patchNode(id, patch);
111
+
112
+ // Auto-add feature to kanban when it reaches defined + 100% completeness
113
+ const finalStatus = opts.setStatus || meta.status;
114
+ if (nodeMeta.type === 'feature' && finalStatus === 'defined' && meta.completeness >= 1) {
115
+ const existing = await getKanbanEntry(id);
116
+ if (!existing) {
117
+ await saveKanbanEntry(id, { column: 'todo', rejection_count: 0, notes: [] }, opts.projectId);
118
+ console.log(chalk.green(`✓ Auto-added ${id} to kanban board (todo)`));
119
+ }
120
+ }
121
+
122
+ console.log(chalk.green(`✓ Updated ${id}`));
123
+ console.log(JSON.stringify({
124
+ id,
125
+ status: opts.setStatus || meta.status,
126
+ completeness: meta.completeness,
127
+ open_questions_count: meta.open_questions_count,
128
+ }));
129
+ } catch (err) {
130
+ console.error(chalk.red(`Error: ${err.message}`));
131
+ process.exit(1);
132
+ }
133
+ })();
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "strict": false,
4
+ "allowJs": true,
5
+ "module": "NodeNext",
6
+ "moduleResolution": "NodeNext",
7
+ "target": "ESNext",
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "outDir": "build",
12
+ "rootDir": ".",
13
+ "sourceMap": true,
14
+ "declaration": true
15
+ },
16
+ "include": [
17
+ "lib/**/*.ts",
18
+ "tools/**/*.ts",
19
+ "db/**/*.ts"
20
+ ],
21
+ "exclude": [
22
+ "node_modules"
23
+ ]
24
+ }
@@ -0,0 +1,2 @@
1
+ packages:
2
+ - 'packages/*'