@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.
- package/dist/bin/create.d.ts +2 -0
- package/dist/bin/create.js +25 -0
- package/dist/bin/create.js.map +1 -0
- package/dist/src/scaffolder.d.ts +22 -0
- package/dist/src/scaffolder.js +120 -0
- package/dist/src/scaffolder.js.map +1 -0
- package/package.json +24 -0
- package/templates/product-system/.env.example +8 -0
- package/templates/product-system/CLAUDE.md +45 -0
- package/templates/product-system/package.json +32 -0
- package/templates/product-system/packages/backend/package.json +37 -0
- package/templates/product-system/packages/backend/src/middleware/auth_middleware.test.ts +86 -0
- package/templates/product-system/packages/backend/src/middleware/auth_middleware.ts +35 -0
- package/templates/product-system/packages/backend/src/routes/auth.ts +463 -0
- package/templates/product-system/packages/backend/src/routes/coherence.ts +187 -0
- package/templates/product-system/packages/backend/src/routes/graph.ts +67 -0
- package/templates/product-system/packages/backend/src/routes/kanban.ts +201 -0
- package/templates/product-system/packages/backend/src/routes/pipeline.ts +41 -0
- package/templates/product-system/packages/backend/src/routes/projects.ts +122 -0
- package/templates/product-system/packages/backend/src/routes/users.ts +97 -0
- package/templates/product-system/packages/backend/src/server.ts +159 -0
- package/templates/product-system/packages/backend/src/services/auth_service.test.ts +115 -0
- package/templates/product-system/packages/backend/src/services/auth_service.ts +82 -0
- package/templates/product-system/packages/backend/src/services/coherence-review.ts +339 -0
- package/templates/product-system/packages/backend/src/services/email_service.ts +75 -0
- package/templates/product-system/packages/backend/src/services/init.ts +80 -0
- package/templates/product-system/packages/backend/src/services/invitation_service.test.ts +235 -0
- package/templates/product-system/packages/backend/src/services/invitation_service.ts +193 -0
- package/templates/product-system/packages/backend/src/services/password_reset_service.test.ts +151 -0
- package/templates/product-system/packages/backend/src/services/password_reset_service.ts +135 -0
- package/templates/product-system/packages/backend/src/services/project_service.test.ts +215 -0
- package/templates/product-system/packages/backend/src/services/project_service.ts +171 -0
- package/templates/product-system/packages/backend/src/services/pty_session_manager.test.ts +88 -0
- package/templates/product-system/packages/backend/src/services/pty_session_manager.ts +279 -0
- package/templates/product-system/packages/backend/src/services/terminal_ws_handler.ts +133 -0
- package/templates/product-system/packages/backend/src/services/user_management_service.test.ts +158 -0
- package/templates/product-system/packages/backend/src/services/user_management_service.ts +128 -0
- package/templates/product-system/packages/backend/tsconfig.json +22 -0
- package/templates/product-system/packages/frontend/index.html +13 -0
- package/templates/product-system/packages/frontend/package-lock.json +2666 -0
- package/templates/product-system/packages/frontend/package.json +30 -0
- package/templates/product-system/packages/frontend/public/favicon.svg +16 -0
- package/templates/product-system/packages/frontend/src/App.tsx +29 -0
- package/templates/product-system/packages/frontend/src/api/client.ts +386 -0
- package/templates/product-system/packages/frontend/src/api/client_projects.test.ts +104 -0
- package/templates/product-system/packages/frontend/src/api/client_refresh.test.ts +145 -0
- package/templates/product-system/packages/frontend/src/components/CoherenceView.tsx +414 -0
- package/templates/product-system/packages/frontend/src/components/GraphLegend.tsx +124 -0
- package/templates/product-system/packages/frontend/src/components/GraphSettings.tsx +112 -0
- package/templates/product-system/packages/frontend/src/components/GraphView.tsx +370 -0
- package/templates/product-system/packages/frontend/src/components/InviteUserDialog.tsx +85 -0
- package/templates/product-system/packages/frontend/src/components/KanbanView.tsx +470 -0
- package/templates/product-system/packages/frontend/src/components/LoginPage.tsx +116 -0
- package/templates/product-system/packages/frontend/src/components/ProjectSelector.tsx +187 -0
- package/templates/product-system/packages/frontend/src/components/QaIssueSheet.tsx +192 -0
- package/templates/product-system/packages/frontend/src/components/SidePanel.tsx +231 -0
- package/templates/product-system/packages/frontend/src/components/TerminalView.tsx +200 -0
- package/templates/product-system/packages/frontend/src/components/Toolbar.tsx +84 -0
- package/templates/product-system/packages/frontend/src/components/UsersView.tsx +249 -0
- package/templates/product-system/packages/frontend/src/constants/graph.ts +191 -0
- package/templates/product-system/packages/frontend/src/hooks/useAuth.tsx +54 -0
- package/templates/product-system/packages/frontend/src/hooks/useGraph.ts +27 -0
- package/templates/product-system/packages/frontend/src/hooks/useKanban.ts +21 -0
- package/templates/product-system/packages/frontend/src/hooks/useProjects.ts +86 -0
- package/templates/product-system/packages/frontend/src/hooks/useTheme.ts +26 -0
- package/templates/product-system/packages/frontend/src/hooks/useToast.tsx +62 -0
- package/templates/product-system/packages/frontend/src/hooks/use_projects_logic.test.ts +61 -0
- package/templates/product-system/packages/frontend/src/main.tsx +12 -0
- package/templates/product-system/packages/frontend/src/pages/accept_invitation_page.tsx +167 -0
- package/templates/product-system/packages/frontend/src/pages/forgot_password_page.tsx +100 -0
- package/templates/product-system/packages/frontend/src/pages/register_page.tsx +137 -0
- package/templates/product-system/packages/frontend/src/pages/reset_password_page.tsx +146 -0
- package/templates/product-system/packages/frontend/src/routes/ProtectedRoute.tsx +12 -0
- package/templates/product-system/packages/frontend/src/routes/accept_invitation.tsx +14 -0
- package/templates/product-system/packages/frontend/src/routes/dashboard.tsx +221 -0
- package/templates/product-system/packages/frontend/src/routes/forgot_password.tsx +13 -0
- package/templates/product-system/packages/frontend/src/routes/login.tsx +14 -0
- package/templates/product-system/packages/frontend/src/routes/register.tsx +14 -0
- package/templates/product-system/packages/frontend/src/routes/reset_password.tsx +13 -0
- package/templates/product-system/packages/frontend/src/styles/index.css +3358 -0
- package/templates/product-system/packages/frontend/src/utils/auth_validation.test.ts +51 -0
- package/templates/product-system/packages/frontend/src/utils/auth_validation.ts +19 -0
- package/templates/product-system/packages/frontend/src/utils/login_validation.test.ts +61 -0
- package/templates/product-system/packages/frontend/src/utils/login_validation.ts +24 -0
- package/templates/product-system/packages/frontend/src/utils/logout.test.ts +63 -0
- package/templates/product-system/packages/frontend/src/utils/node_sizing.test.ts +62 -0
- package/templates/product-system/packages/frontend/src/utils/node_sizing.ts +24 -0
- package/templates/product-system/packages/frontend/src/utils/task_status.test.ts +53 -0
- package/templates/product-system/packages/frontend/src/utils/task_status.ts +14 -0
- package/templates/product-system/packages/frontend/tsconfig.json +21 -0
- package/templates/product-system/packages/frontend/vite.config.ts +20 -0
- package/templates/product-system/packages/shared/.env.example +3 -0
- package/templates/product-system/packages/shared/README.md +1 -0
- package/templates/product-system/packages/shared/db/migrate.ts +32 -0
- package/templates/product-system/packages/shared/db/migrations/0000_dashing_gorgon.sql +128 -0
- package/templates/product-system/packages/shared/db/migrations/meta/0000_snapshot.json +819 -0
- package/templates/product-system/packages/shared/db/migrations/meta/_journal.json +13 -0
- package/templates/product-system/packages/shared/db/schema.ts +137 -0
- package/templates/product-system/packages/shared/drizzle.config.js +14 -0
- package/templates/product-system/packages/shared/lib/claude-service.ts +215 -0
- package/templates/product-system/packages/shared/lib/coherence.ts +278 -0
- package/templates/product-system/packages/shared/lib/completeness.ts +30 -0
- package/templates/product-system/packages/shared/lib/constants.ts +327 -0
- package/templates/product-system/packages/shared/lib/db.ts +81 -0
- package/templates/product-system/packages/shared/lib/git_workflow.ts +110 -0
- package/templates/product-system/packages/shared/lib/graph.ts +186 -0
- package/templates/product-system/packages/shared/lib/kanban.ts +161 -0
- package/templates/product-system/packages/shared/lib/markdown.ts +205 -0
- package/templates/product-system/packages/shared/lib/pipeline-state-store.ts +124 -0
- package/templates/product-system/packages/shared/lib/pipeline.ts +489 -0
- package/templates/product-system/packages/shared/lib/prompt_builder.ts +170 -0
- package/templates/product-system/packages/shared/lib/relevance_search.ts +159 -0
- package/templates/product-system/packages/shared/lib/session.ts +152 -0
- package/templates/product-system/packages/shared/lib/validator.ts +117 -0
- package/templates/product-system/packages/shared/lib/work_summary_parser.ts +130 -0
- package/templates/product-system/packages/shared/package.json +30 -0
- package/templates/product-system/packages/shared/scripts/assign-project.ts +52 -0
- package/templates/product-system/packages/shared/tools/add_edge.ts +61 -0
- package/templates/product-system/packages/shared/tools/add_node.ts +101 -0
- package/templates/product-system/packages/shared/tools/end_session.ts +87 -0
- package/templates/product-system/packages/shared/tools/get_gaps.ts +87 -0
- package/templates/product-system/packages/shared/tools/get_kanban.ts +125 -0
- package/templates/product-system/packages/shared/tools/get_node.ts +78 -0
- package/templates/product-system/packages/shared/tools/get_status.ts +98 -0
- package/templates/product-system/packages/shared/tools/migrate_to_turso.ts +385 -0
- package/templates/product-system/packages/shared/tools/move_card.ts +143 -0
- package/templates/product-system/packages/shared/tools/rebuild_index.ts +77 -0
- package/templates/product-system/packages/shared/tools/remove_edge.ts +59 -0
- package/templates/product-system/packages/shared/tools/remove_node.ts +96 -0
- package/templates/product-system/packages/shared/tools/resolve_question.ts +75 -0
- package/templates/product-system/packages/shared/tools/search_nodes.ts +106 -0
- package/templates/product-system/packages/shared/tools/start_session.ts +144 -0
- package/templates/product-system/packages/shared/tools/update_node.ts +133 -0
- package/templates/product-system/packages/shared/tsconfig.json +24 -0
- package/templates/product-system/pnpm-workspace.yaml +2 -0
- package/templates/product-system/smoke_test.ts +219 -0
- package/templates/product-system/tests/coherence_review.test.ts +562 -0
- package/templates/product-system/tests/db_sqlite_fallback.test.ts +75 -0
- package/templates/product-system/tests/edge_type_color_coding.test.ts +147 -0
- package/templates/product-system/tests/emit-tool-use-events.test.ts +85 -0
- package/templates/product-system/tests/feature_kind.test.ts +139 -0
- package/templates/product-system/tests/gap_indicators.test.ts +199 -0
- package/templates/product-system/tests/graceful_init.test.ts +142 -0
- package/templates/product-system/tests/graph_legend.test.ts +314 -0
- package/templates/product-system/tests/graph_settings_sheet.test.ts +804 -0
- package/templates/product-system/tests/hide_defined_filter.test.ts +205 -0
- package/templates/product-system/tests/kanban.test.ts +529 -0
- package/templates/product-system/tests/neighborhood_focus.test.ts +132 -0
- package/templates/product-system/tests/node_search.test.ts +340 -0
- package/templates/product-system/tests/node_sizing.test.ts +170 -0
- package/templates/product-system/tests/node_type_toggle_filters.test.ts +285 -0
- package/templates/product-system/tests/node_type_visual_encoding.test.ts +103 -0
- package/templates/product-system/tests/pipeline-state-store.test.ts +268 -0
- package/templates/product-system/tests/pipeline-unit.test.ts +593 -0
- package/templates/product-system/tests/pipeline.test.ts +195 -0
- package/templates/product-system/tests/pipeline_stats_all_cards.test.ts +193 -0
- package/templates/product-system/tests/play_all.test.ts +296 -0
- package/templates/product-system/tests/qa_issue_sheet.test.ts +464 -0
- package/templates/product-system/tests/relevance_search.test.ts +186 -0
- package/templates/product-system/tests/search_reorder.test.ts +88 -0
- package/templates/product-system/tests/serve_ui.test.ts +281 -0
- package/templates/product-system/tests/serve_ui_drizzle.test.ts +114 -0
- package/templates/product-system/tests/session_context_recall.test.ts +135 -0
- package/templates/product-system/tests/side_panel.test.ts +345 -0
- package/templates/product-system/tests/spec_completeness_label.test.ts +69 -0
- package/templates/product-system/tests/url_routing_test.ts +122 -0
- package/templates/product-system/tests/user_login.test.ts +150 -0
- package/templates/product-system/tests/user_registration.test.ts +205 -0
- package/templates/product-system/tests/web_terminal.test.ts +572 -0
- package/templates/product-system/tests/work_summary.test.ts +211 -0
- package/templates/product-system/tests/zoom_pan.test.ts +43 -0
- package/templates/product-system/tsconfig.json +24 -0
- package/templates/skills/product-bootstrap/SKILL.md +312 -0
- package/templates/skills/product-code-reviewer/SKILL.md +147 -0
- package/templates/skills/product-debugger/SKILL.md +206 -0
- package/templates/skills/product-debugger/references/agent-browser.md +1156 -0
- package/templates/skills/product-developer/SKILL.md +182 -0
- 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
|
+
}
|