@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,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
|
+
}
|