├── .DS_Store
├── .cline_custom_instructions
├── .clinerules
├── .gitignore
├── LICENSE
├── README.md
├── app
└── page.tsx
├── components
└── facts
│ ├── columns.ts
│ └── data-table.tsx
├── docs
├── side-by-side.png
└── validation
│ └── 2025-02-24-validation-report.md
├── facts-server
├── .DS_Store
├── build
│ ├── index.d.ts
│ ├── index.js
│ ├── sdk
│ │ ├── server.d.ts
│ │ ├── server.js
│ │ ├── stdio.d.ts
│ │ ├── stdio.js
│ │ ├── types.d.ts
│ │ └── types.js
│ ├── storage.d.ts
│ ├── storage.js
│ ├── types.d.ts
│ ├── types.js
│ ├── validation.d.ts
│ └── validation.js
├── package-lock.json
├── package.json
├── prisma
│ ├── facts.db
│ ├── migrations
│ │ ├── 20250223115817_init
│ │ │ └── migration.sql
│ │ ├── 20250223171046_add_fact_category
│ │ │ └── migration.sql
│ │ └── migration_lock.toml
│ ├── schema.prisma
│ └── setup_vector.sql
├── src
│ ├── index.ts
│ ├── storage.ts
│ ├── types.ts
│ └── validation.ts
└── tsconfig.json
├── facts-ui
├── .gitignore
├── README.md
├── app
│ ├── api
│ │ └── mcp
│ │ │ └── route.ts
│ ├── facts
│ │ └── new
│ │ │ └── page.tsx
│ ├── favicon.ico
│ ├── globals.css
│ ├── layout.tsx
│ └── page.tsx
├── components.json
├── components
│ ├── facts
│ │ ├── columns.tsx
│ │ └── data-table.tsx
│ └── ui
│ │ └── button.tsx
├── eslint.config.mjs
├── jsconfig.json
├── lib
│ └── api
│ │ └── facts.ts
├── next-env.d.ts
├── next.config.ts
├── package-lock.json
├── package.json
├── path
│ └── to
│ │ └── file
├── postcss.config.mjs
├── prisma
│ └── schema.prisma
├── public
│ ├── file.svg
│ ├── globe.svg
│ ├── next.svg
│ ├── vercel.svg
│ └── window.svg
├── src
│ ├── components
│ │ ├── facts
│ │ │ ├── columns.tsx
│ │ │ ├── data-table.tsx
│ │ │ └── new-fact-form.tsx
│ │ └── ui
│ │ │ ├── badge.tsx
│ │ │ ├── button.tsx
│ │ │ ├── dropdown-menu.tsx
│ │ │ ├── input.tsx
│ │ │ ├── select.tsx
│ │ │ ├── sonner.tsx
│ │ │ ├── table.tsx
│ │ │ └── textarea.tsx
│ └── lib
│ │ ├── api
│ │ └── facts.ts
│ │ ├── prisma.ts
│ │ ├── types
│ │ └── facts.ts
│ │ └── utils.ts
├── tailwind.config.ts
└── tsconfig.json
├── hivemind-ui
└── next-env.d.ts
├── hivemind
└── package.json
├── index.ts
├── lib
└── api
│ └── facts.ts
└── src
└── components
└── facts
└── data-table.tsx
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andrewhopper/hivemind/b82fb50a74b5eadef26a19cc1f69a178772b29d3/.DS_Store
--------------------------------------------------------------------------------
/.cline_custom_instructions:
--------------------------------------------------------------------------------
1 | You have access to a knowledgebase MCP server that stores your guidance for architecting, implementing and validating software. Use this in your workflow to fetch and store architecture decisions. Make sure to use the get all facts service to check for libraries that could be useful. For example if building a frontend you'll search with get all facts, see that NextJS with Tailwind and ShadCN is preferred. Or when selecting a DB you'll see that Postgres is required. If you add a new major library like ORM or AUTH that's not in the facts ask the user for approval.
2 |
3 | # Fact Validation Rules
4 |
5 | ## Pre-Planning Phase
6 | 1. Before creating any plan, check the facts server for:
7 | - Required architecture patterns for the project type
8 | - Mandatory tools and frameworks
9 | - Project structure requirements
10 | - Database requirements if applicable
11 | - Security requirements if applicable
12 |
13 | 2. Use these facts to:
14 | - Determine which tools and frameworks must be used
15 | - Identify required project structure
16 | - Set constraints for the planning phase
17 | - Generate initial requirements list
18 |
19 | ## Planning Phase
20 | 1. While creating the plan:
21 | - Reference fact requirements for each architectural decision
22 | - Validate tool choices against fact dependencies
23 | - Ensure compliance with required patterns
24 | - Check version compatibility between components
25 |
26 | 2. Before finalizing the plan:
27 | - Validate against all REQUIRED facts
28 | - Check compatibility between chosen components
29 | - Verify adherence to architectural patterns
30 | - Generate a planning validation report
31 |
32 | ## Implementation Phase
33 | 1. During implementation:
34 | - Follow fact-based patterns and practices
35 | - Use required tools and frameworks
36 | - Implement required security measures
37 | - Follow documentation requirements
38 |
39 | 2. After implementation:
40 | - Run automated validation against facts
41 | - Generate compliance report
42 | - Check all acceptance criteria
43 | - Verify all required patterns are followed
--------------------------------------------------------------------------------
/.clinerules:
--------------------------------------------------------------------------------
1 | version: "1.0"
2 |
3 | # Communication settings
4 | communication:
5 | emojis:
6 | design:
7 | start: "🎨 Starting design phase..."
8 | thinking: "🤔 Analyzing design requirements..."
9 | diagram: "📊 Creating system diagram..."
10 | complete: "✨ Design spec created in docs/design/"
11 |
12 | implement:
13 | start: "🚀 Starting implementation..."
14 | progress: "⚙️ Implementing features..."
15 | testing: "🧪 Testing implementation..."
16 | complete: "✅ Implementation finished"
17 |
18 | validate:
19 | start: "🔍 Starting validation..."
20 | checking: "📋 Checking against facts..."
21 | issue: "⚠️ Found compliance issue..."
22 | pass: "✅ Validation passed"
23 | fail: "❌ Validation failed"
24 | complete: "📝 Validation report created in docs/validation/"
25 |
26 | revise:
27 | start: "🔄 Starting revision..."
28 | progress: "📝 Making changes..."
29 | complete: "✨ Revision complete"
30 |
31 | refactor:
32 | start: "🔧 Starting refactoring..."
33 | progress: "🔨 Refactoring in progress..."
34 | metrics: "📊 Checking quality metrics..."
35 | complete: "✨ Refactoring complete"
36 |
37 | # Documentation settings
38 | documentation:
39 | output_dir: "./docs"
40 | templates:
41 | design:
42 | path: "docs/design"
43 | files:
44 | - name: "{timestamp}-design-spec.md"
45 | sections:
46 | - "Overview"
47 | - "Architecture"
48 | - "Components"
49 | - "Data Flow"
50 | - "Technical Decisions"
51 |
52 | validate:
53 | path: "docs/validation"
54 | files:
55 | - name: "{timestamp}-validation-report.md"
56 | sections:
57 | - "Executive Summary"
58 | - "Validation Context"
59 | - "Required Facts"
60 | subsections:
61 | - "Compliant"
62 | - "Non-compliant"
63 | - "Not Applicable"
64 | - "Recommended Facts"
65 | subsections:
66 | - "Implemented"
67 | - "Missing"
68 | - "Performance Metrics"
69 | - "Security Analysis"
70 | - "Code Quality Metrics"
71 | - "Issues"
72 | subsections:
73 | - "Critical"
74 | - "Major"
75 | - "Minor"
76 | - "Recommendations"
77 | - "Next Steps"
78 |
79 | # Operation modes configuration
80 | modes:
81 | design:
82 | description: "Architecture and system design phase"
83 | allowed_operations:
84 | - read_file
85 | - list_files
86 | - search_files
87 | - list_code_definition_names
88 | - write_to_file:
89 | extensions: [".md", ".mdx", ".mmd"]
90 | - access_mcp_resource
91 | - use_mcp_tool
92 | - ask_followup_question
93 | restrictions:
94 | - "No code modifications allowed"
95 | - "Can only create/modify markdown and mermaid files"
96 | - "Focus on architecture documentation and diagrams"
97 |
98 | implement:
99 | description: "Code implementation phase"
100 | allowed_operations:
101 | - all
102 | restrictions:
103 | - "Must follow design documents"
104 | - "Must validate against facts"
105 |
106 | validate:
107 | description: "Verification and compliance checking"
108 | allowed_operations:
109 | - read_file
110 | - list_files
111 | - search_files
112 | - list_code_definition_names
113 | - write_to_file:
114 | extensions: [".md"]
115 | - browser_action
116 | - execute_command:
117 | type: "read-only"
118 | - access_mcp_resource
119 | - use_mcp_tool
120 | - ask_followup_question
121 | restrictions:
122 | - "No code modifications allowed"
123 | - "Can only create validation reports in markdown"
124 | - "Can run tests and analysis tools"
125 |
126 | revise:
127 | description: "Code modification and improvement"
128 | allowed_operations:
129 | - all
130 | restrictions:
131 | - "Must maintain existing architecture"
132 | - "Changes must be validated against facts"
133 |
134 | refactor:
135 | description: "Code restructuring and optimization"
136 | allowed_operations:
137 | - all
138 | restrictions:
139 | - "Must maintain existing functionality"
140 | - "Must improve code quality metrics"
141 | - "Changes must be validated against facts"
142 |
143 | # Facts integration settings
144 | facts:
145 | # Automatically save decisions made during development
146 | auto_save: true
147 |
148 | # Default settings for new facts
149 | defaults:
150 | strictness: "RECOMMENDED" # Default strictness level
151 | min_version: "1.0.0" # Minimum version compatibility
152 | max_version: "*" # Maximum version compatibility ("*" means latest)
153 |
154 | # Validation settings
155 | validation:
156 | # Validate all proposals against existing facts
157 | validate_proposals: true
158 | # Stop if validation fails for REQUIRED facts
159 | strict_validation: true
160 | # Categories to validate against
161 | categories: [
162 | "ARCHITECTURE_PATTERN",
163 | "DATABASE",
164 | "FRONTEND",
165 | "BACKEND",
166 | "DOCUMENTATION"
167 | ]
168 |
169 | # Validation stages
170 | stages:
171 | pre_planning:
172 | enabled: true
173 | categories:
174 | - ARCHITECTURE_PATTERN
175 | - PROJECT_STRUCTURE
176 | - FRAMEWORK
177 | - DATABASE
178 | - SECURITY
179 | actions:
180 | on_validation:
181 | - check_required_facts
182 | - determine_tools
183 | - set_constraints
184 | on_failure:
185 | - block_planning
186 | - generate_requirements
187 |
188 | post_planning:
189 | enabled: true
190 | categories:
191 | - ARCHITECTURE_PATTERN
192 | - DESIGN_PATTERN
193 | - PROJECT_STRUCTURE
194 | - FRAMEWORK
195 | actions:
196 | on_validation:
197 | - validate_architecture
198 | - check_dependencies
199 | - verify_patterns
200 | on_failure:
201 | - reject_plan
202 | - generate_violations_report
203 |
204 | post_implementation:
205 | enabled: true
206 | categories:
207 | - all
208 | checks:
209 | - type: automated
210 | patterns:
211 | - file_structure
212 | - dependency_usage
213 | - security_compliance
214 | - type: manual
215 | patterns:
216 | - code_quality
217 | - pattern_adherence
218 | actions:
219 | on_success:
220 | - generate_compliance_report
221 | - update_metrics
222 | on_failure:
223 | - generate_issues_report
224 | - suggest_fixes
225 | # LLM-based validation hooks
226 | hooks:
227 | pre_commit: true # Validate before git commits
228 | pre_deploy: true # Validate before deployments
229 | pull_request: true # Validate on PR creation/update
230 |
231 | # LLM-based preflight validation
232 | preflight:
233 | enabled: true
234 | post_implementation: true
235 | report_path: "docs/validation"
236 | checks:
237 | - type: "architecture"
238 | categories: ["ARCHITECTURE_PATTERN", "PROJECT_STRUCTURE", "DESIGN_PATTERN"]
239 | strictness: ["REQUIRED", "RECOMMENDED"]
240 | - type: "database"
241 | categories: ["DATABASE"]
242 | strictness: ["REQUIRED"]
243 | - type: "api"
244 | categories: ["BACKEND", "FRONTEND"]
245 | strictness: ["REQUIRED"]
246 | actions:
247 | on_failure:
248 | - generate_report
249 | - fail_implementation
250 | on_success:
251 | - generate_report
252 | - mark_validated
253 |
254 | # LLM-based postflight validation
255 | postflight:
256 | enabled: true
257 | report_path: "docs/validation/postflight"
258 | checks:
259 | - type: "performance"
260 | thresholds:
261 | response_time: 200
262 | memory_usage: 512
263 | - type: "security"
264 | categories: ["SECURITY"]
265 | strictness: ["REQUIRED"]
266 | - type: "quality"
267 | metrics:
268 | code_coverage: 80
269 | complexity: 20
270 | actions:
271 | on_failure:
272 | - generate_report
273 | - notify_team
274 | on_success:
275 | - generate_report
276 | - update_metrics
277 |
278 | # Automatic fact creation triggers
279 | triggers:
280 | # Create facts when new dependencies are added
281 | dependencies:
282 | - package.json
283 | - requirements.txt
284 | - pyproject.toml
285 | - Cargo.toml
286 |
287 | # Create facts for new architectural components
288 | architecture:
289 | - "**/*.config.{js,ts}"
290 | - "**/schema.prisma"
291 | - "**/docker-compose.yml"
292 | - "**/Dockerfile"
293 |
294 | # Create facts for API contracts
295 | api:
296 | - "**/api/**/*.{ts,js}"
297 | - "**/routes/**/*.{ts,js}"
298 | - "**/*.proto"
299 | - "**/openapi.{yaml,yml,json}"
300 |
301 | # Create facts for security configurations
302 | security:
303 | - "**/auth/**/*.{ts,js}"
304 | - "**/*.env.example"
305 | - "**/security.config.{js,ts}"
306 |
307 | # Fact type mappings
308 | types:
309 | architecture_decision:
310 | category: "ARCHITECTURE_PATTERN"
311 | strictness: "REQUIRED"
312 |
313 | code_style:
314 | category: "CODE_STYLE"
315 | strictness: "RECOMMENDED"
316 |
317 | naming_convention:
318 | category: "NAMING_CONVENTION"
319 | strictness: "REQUIRED"
320 |
321 | project_structure:
322 | category: "PROJECT_STRUCTURE"
323 | strictness: "REQUIRED"
324 |
325 | design_pattern:
326 | category: "DESIGN_PATTERN"
327 | strictness: "RECOMMENDED"
328 |
329 | testing_pattern:
330 | category: "TESTING_PATTERN"
331 | strictness: "REQUIRED"
332 |
333 | security_rule:
334 | category: "SECURITY"
335 | strictness: "REQUIRED"
336 |
337 | deployment_rule:
338 | category: "DEPLOYMENT"
339 | strictness: "REQUIRED"
340 |
341 | performance_guideline:
342 | category: "OPTIMIZATION"
343 | strictness: "RECOMMENDED"
344 |
345 | error_handling:
346 | category: "ERROR_HANDLING"
347 | strictness: "REQUIRED"
348 |
349 | documentation:
350 | category: "DOCUMENTATION"
351 | strictness: "RECOMMENDED"
352 |
353 | git_workflow:
354 | category: "GIT_WORKFLOW"
355 | strictness: "REQUIRED"
356 |
357 | code_review:
358 | category: "CODE_REVIEW"
359 | strictness: "RECOMMENDED"
360 |
361 | package_management:
362 | category: "PACKAGE_MANAGEMENT"
363 | strictness: "REQUIRED"
364 |
365 | accessibility:
366 | category: "ACCESSIBILITY"
367 | strictness: "REQUIRED"
368 |
369 | internationalization:
370 | category: "INTERNATIONALIZATION"
371 | strictness: "RECOMMENDED"
372 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | facts-server/node_modules/*
2 | facts-ui/.next/*
3 | facts-ui/node_modules/*
4 | .obsidian/app.json
5 | .obsidian/appearance.json
6 | .obsidian/core-plugins.json
7 | .obsidian/workspace.json
8 | hivemind-ui/.next/*
9 | hivemind-ui/node_modules/*
10 | hivemind/node_modules/*
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Andrew Hopper | andrewhopper.com
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # HiveMind
2 |
3 | A fact management system that provides automatic coding tools with consistent guidance about libraries, patterns, and development standards.
4 |
5 | ## Why HiveMind?
6 |
7 | AI coding tools make the same mistakes again and again. If you've tried building with NextJS 15 they frequently mess up AppRouter; using ShadCN chances are they've used the wrong command- npx install shadcn-ui instead of npx install shadcn. They also don't make good or consistent architecture and design decisions.
8 |
9 | 
10 |
11 | When working with automatic coding tools like Cline in Cursor, developers often find themselves repeatedly providing the same guidance about coding standards, preferred libraries, and architectural patterns. HiveMind solves this by creating a centralized system for managing and enforcing these development guidelines through the Model Context Protocol (MCP).
12 |
13 | This repository is a prototype that demonstrates an MCP server that allows facts to be set, retrieved, searched, and validated against. The goal is to help autonomous coding tools maintain consistency with your project's standards and preferences.
14 |
15 | See an [example validation report](docs/validation/2025-02-24-validation-report.md) that demonstrates how HiveMind validates projects against established facts.
16 |
17 | ## Hivemind in Action
18 |
19 |
27 |
28 |
36 |
37 |
38 | ## Features
39 | - **Facts Management**: Store and retrieve development decisions, guidelines, and standards
40 | - **Automated Validation**: Validate code changes against established criteria
41 | - **Flexible Categories**: Support for multiple development aspects (frontend, backend, security, etc.)
42 | - **Configurable Rules**: Adjustable strictness levels for different types of guidelines
43 |
44 | ## Getting Started
45 |
46 | ### Prerequisites
47 | - Node.js
48 | - SQLite
49 | - MCP SDK
50 |
51 | ### Installation
52 | 1. Clone the repository
53 | 2. Install dependencies:
54 | ```bash
55 | cd facts-server
56 | npm install
57 | ```
58 | 3. Build the project:
59 | ```bash
60 | npm run build
61 | ```
62 | 4. Configure the MCP service:
63 |
64 | ```
65 | "hivemind": {
66 | "command": "node",
67 | "args": [
68 | "/hivemind/facts-server/build/index.js"
69 | ],
70 | "env": {},
71 | "disabled": false,
72 | "autoApprove": []
73 | }
74 | ```
75 |
76 | ### Configuration
77 |
78 | The `.clinerules` file configures how facts are managed:
79 |
80 | - Automatic saving of development decisions
81 | - Validation of proposals against existing facts
82 | - Strictness levels for different fact types
83 | - Category mappings for various development aspects
84 |
85 | ## Usage
86 |
87 | ### Running the Facts Server
88 | ```bash
89 | cd facts-server
90 | npm start
91 | ```
92 |
93 | ### Interacting with Facts
94 |
95 | The facts server provides the following tools:
96 | - Create/update facts
97 | - Search facts by type, strictness, and version
98 | - Validate content against fact criteria
99 | - Retrieve existing facts
100 |
101 | ## Technical Details
102 |
103 | ### System Architecture
104 |
105 | ```mermaid
106 | graph TB
107 | subgraph "System Architecture"
108 | MCP[Model Context Protocol]
109 |
110 | subgraph "Facts Management"
111 | FS[Facts Server]
112 | DB[(SQLite DB)]
113 | VS[Vector Search]
114 | end
115 |
116 | subgraph "Validation Layer"
117 | VP[Validation Processor]
118 | RC[Rules Configuration]
119 | end
120 |
121 | IDE[Development Environment]
122 | end
123 |
124 | classDef default fill:#f9f9f9,stroke:#333,stroke-width:2px;
125 | classDef highlight fill:#e1f5fe,stroke:#0288d1,stroke-width:2px;
126 | class MCP,FS highlight
127 | ```
128 |
129 | ### Request Flow
130 |
131 | ```mermaid
132 | %%{init: {'sequence': {'width': 400, 'actorMargin': 30, 'messageMargin': 15, 'boxMargin': 10, 'fontSize': 11, 'actorFontSize': 11, 'noteFontSize': 11, 'messageFontSize': 11}}}%%
133 | sequenceDiagram
134 | participant IDE as Dev Env
135 | participant MCP as MCP
136 | participant FS as Facts
137 | participant DB as DB
138 | participant VP as Validator
139 |
140 | IDE->>MCP: Dev Request
141 | MCP->>FS: Query Facts
142 | FS->>DB: Fetch Rules
143 | DB-->>FS: Rules Data
144 | FS->>VP: Validate
145 | VP-->>FS: Result
146 | FS-->>MCP: Facts & Valid.
147 | MCP-->>IDE: Standards
148 | ```
149 |
150 | The architecture diagram shows the core components of HiveMind, centered around the Model Context Protocol (MCP) which integrates with your development environment. The Facts Server manages development guidelines and standards using a SQLite database with vector search capabilities.
151 |
152 | ### Project Structure
153 |
154 | ```
155 | HiveMind/
156 | ├── facts-server/ # MCP server for facts management
157 | │ ├── src/ # TypeScript source files
158 | │ ├── prisma/ # Database schema and migrations
159 | │ └── build/ # Compiled JavaScript output
160 | └── .clinerules # Configuration for facts integration
161 | ```
162 |
163 | ### Technology Stack
164 | - TypeScript
165 | - Prisma (SQLite)
166 | - MCP SDK
167 | - Vector extensions for similarity search
168 |
169 | ### Development Scripts
170 | - `npm run build` - Build the TypeScript code
171 | - `npm run dev` - Run with debugging enabled
172 | - `npm start` - Start the facts server
173 |
174 | ## Contributing
175 |
176 | Contributions are welcome! Feel free to submit issues, feature requests, or pull requests.
177 |
178 | ## Contact
179 |
180 | You can reach me on:
181 | - https://www.linkedin.com/in/andrewhopper
182 | - https://x.com/andrewhopper
183 | - https://andrewhopper.com
184 |
185 | ## License
186 |
187 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
188 |
--------------------------------------------------------------------------------
/app/page.tsx:
--------------------------------------------------------------------------------
1 | import { DataTable } from "@/components/facts/data-table"
2 | import { columns } from "@/components/facts/columns"
3 | import { getAllFacts } from "@/lib/api/facts" // This should now work
4 | import { Button } from "@/components/ui/button"
5 | import Link from "next/link"
6 |
7 | // ... rest of your page code
--------------------------------------------------------------------------------
/components/facts/columns.ts:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { ColumnDef } from "@/components/ui/table"
4 |
5 | export const columns: ColumnDef[] = [
6 | {
7 | accessorKey: "title",
8 | header: "Title",
9 | },
10 | {
11 | accessorKey: "description",
12 | header: "Description",
13 | },
14 | {
15 | accessorKey: "createdAt",
16 | header: "Created At",
17 | }
18 | ]
--------------------------------------------------------------------------------
/components/facts/data-table.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import React from "react"
4 | import {
5 | ColumnDef,
6 | flexRender,
7 | getCoreRowModel,
8 | useReactTable,
9 | } from "@/components/ui/table"
10 | import { Button } from "@/components/ui/button"
11 | import { useState } from "react"
12 |
13 | interface DataTableProps {
14 | columns: ColumnDef[]
15 | data: TData[]
16 | }
17 |
18 | export function DataTable({
19 | columns,
20 | data
21 | }: DataTableProps) {
22 | const table = useReactTable({
23 | data,
24 | columns,
25 | getCoreRowModel: getCoreRowModel(),
26 | })
27 |
28 | return (
29 |
30 |
31 |
32 | {table.getHeaderGroups().map((headerGroup) => (
33 |
34 | {headerGroup.headers.map((header) => (
35 |
36 | {header.isPlaceholder
37 | ? null
38 | : flexRender(
39 | header.column.columnDef.header,
40 | header.getContext()
41 | )}
42 | |
43 | ))}
44 |
45 | ))}
46 |
47 |
48 | {table.getRowModel().rows.map((row) => (
49 |
50 | {row.getVisibleCells().map((cell) => (
51 |
52 | {flexRender(
53 | cell.column.columnDef.cell,
54 | cell.getContext()
55 | )}
56 | |
57 | ))}
58 |
59 | ))}
60 |
61 |
62 |
63 | )
64 | }
--------------------------------------------------------------------------------
/docs/side-by-side.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andrewhopper/hivemind/b82fb50a74b5eadef26a19cc1f69a178772b29d3/docs/side-by-side.png
--------------------------------------------------------------------------------
/docs/validation/2025-02-24-validation-report.md:
--------------------------------------------------------------------------------
1 | # Validation Report
2 |
3 | ## Summary
4 | Validation of current architecture against established facts reveals some conflicts that need to be addressed.
5 |
6 | ## Required Facts
7 | - ✅ Next.js Frontend (arch_nextjs_frontend)
8 | - Properly implemented with Next.js 15 and App Router
9 | - Follows required architectural patterns
10 |
11 | - ✅ Prisma ORM (arch_prisma_orm)
12 | - Schema and migrations properly configured
13 | - Type-safe database operations implemented
14 |
15 | - ❌ Database Choice (use-postgres)
16 | - **CONFLICT**: Current implementation uses SQLite, but fact requires PostgreSQL
17 | - This is a REQUIRED fact and needs immediate attention
18 |
19 | ## Recommended Facts
20 | - ✅ Documentation (maintain-readme)
21 | - README.md maintained with required sections
22 | - Documentation follows markdown format
23 |
24 | ## Issues
25 | 1. Database Implementation Conflict
26 | - Current: SQLite with Prisma
27 | - Required: PostgreSQL (per use-postgres fact)
28 | - Impact: High (REQUIRED fact violation)
29 |
30 | ## Recommendations
31 | 1. Database Migration
32 | - Plan migration from SQLite to PostgreSQL
33 | - Update Prisma schema to use PostgreSQL provider
34 | - Ensure all database operations are compatible
35 | - Create migration scripts for existing data
36 |
37 | 2. Alternative Approach
38 | - If SQLite is necessary for specific use cases:
39 | - Update use-postgres fact to allow SQLite for development/embedded cases
40 | - Add conditions specifying when each database type should be used
41 | - Document rationale for database choice flexibility
42 |
--------------------------------------------------------------------------------
/facts-server/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andrewhopper/hivemind/b82fb50a74b5eadef26a19cc1f69a178772b29d3/facts-server/.DS_Store
--------------------------------------------------------------------------------
/facts-server/build/index.d.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | export {};
3 |
--------------------------------------------------------------------------------
/facts-server/build/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4 | import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError } from '@modelcontextprotocol/sdk/types.js';
5 | import { PrismaStorageProvider } from './storage.js';
6 | import { validateCriteria } from './validation.js';
7 | import { StrictnessLevel, FactCategory } from './types.js';
8 | class FactsServer {
9 | constructor() {
10 | this.storage = new PrismaStorageProvider();
11 | this.server = new Server({
12 | name: 'facts-server',
13 | version: '0.1.0',
14 | }, {
15 | capabilities: {
16 | tools: {
17 | get_all_facts: {
18 | description: 'Get all facts in the system',
19 | inputSchema: {
20 | type: 'object',
21 | properties: {}
22 | }
23 | },
24 | get_fact: {
25 | description: 'Get a fact by ID',
26 | inputSchema: {
27 | type: 'object',
28 | properties: {
29 | id: {
30 | type: 'string',
31 | description: 'The ID of the fact to retrieve'
32 | }
33 | },
34 | required: ['id']
35 | }
36 | },
37 | search_facts: {
38 | description: 'Search facts by type, strictness, and version',
39 | inputSchema: {
40 | type: 'object',
41 | properties: {
42 | type: {
43 | type: 'string',
44 | description: 'Filter by fact type'
45 | },
46 | strictness: {
47 | type: 'string',
48 | enum: Object.values(StrictnessLevel),
49 | description: 'Filter by strictness level'
50 | },
51 | version: {
52 | type: 'string',
53 | description: 'Filter by version compatibility'
54 | }
55 | }
56 | }
57 | },
58 | set_fact: {
59 | description: 'Create or update a fact',
60 | inputSchema: {
61 | type: 'object',
62 | properties: {
63 | id: { type: 'string' },
64 | content: { type: 'string' },
65 | strictness: {
66 | type: 'string',
67 | enum: Object.values(StrictnessLevel)
68 | },
69 | type: { type: 'string' },
70 | category: {
71 | type: 'string',
72 | enum: Object.values(FactCategory),
73 | description: 'The category this fact belongs to'
74 | },
75 | minVersion: { type: 'string' },
76 | maxVersion: { type: 'string' },
77 | conditions: {
78 | type: 'array',
79 | items: {
80 | type: 'object',
81 | properties: {
82 | factId: { type: 'string' },
83 | type: {
84 | type: 'string',
85 | enum: ['REQUIRES', 'CONFLICTS_WITH']
86 | }
87 | },
88 | required: ['factId', 'type']
89 | }
90 | },
91 | acceptanceCriteria: {
92 | type: 'array',
93 | items: {
94 | type: 'object',
95 | properties: {
96 | id: { type: 'string' },
97 | description: { type: 'string' },
98 | validationType: {
99 | type: 'string',
100 | enum: ['MANUAL', 'AUTOMATED']
101 | },
102 | validationScript: { type: 'string' }
103 | },
104 | required: ['id', 'description', 'validationType']
105 | }
106 | }
107 | },
108 | required: ['id', 'content', 'strictness', 'type', 'category', 'minVersion', 'maxVersion']
109 | }
110 | },
111 | validate_criteria: {
112 | description: 'Validate content against fact acceptance criteria',
113 | inputSchema: {
114 | type: 'object',
115 | properties: {
116 | factId: {
117 | type: 'string',
118 | description: 'ID of the fact to validate against'
119 | },
120 | content: {
121 | type: 'string',
122 | description: 'Content to validate'
123 | }
124 | },
125 | required: ['factId', 'content']
126 | }
127 | }
128 | }
129 | }
130 | });
131 | this.setupHandlers();
132 | }
133 | setupHandlers() {
134 | // Handler for listing available tools
135 | this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
136 | tools: [
137 | {
138 | name: 'get_all_facts',
139 | description: 'Get all facts in the system',
140 | inputSchema: {
141 | type: 'object',
142 | properties: {}
143 | }
144 | },
145 | {
146 | name: 'get_fact',
147 | description: 'Get a fact by ID',
148 | inputSchema: {
149 | type: 'object',
150 | properties: {
151 | id: {
152 | type: 'string',
153 | description: 'The ID of the fact to retrieve'
154 | }
155 | },
156 | required: ['id']
157 | }
158 | },
159 | {
160 | name: 'search_facts',
161 | description: 'Search facts by type, strictness, and version',
162 | inputSchema: {
163 | type: 'object',
164 | properties: {
165 | type: {
166 | type: 'string',
167 | description: 'Filter by fact type'
168 | },
169 | strictness: {
170 | type: 'string',
171 | enum: Object.values(StrictnessLevel),
172 | description: 'Filter by strictness level'
173 | },
174 | version: {
175 | type: 'string',
176 | description: 'Filter by version compatibility'
177 | }
178 | }
179 | }
180 | },
181 | {
182 | name: 'set_fact',
183 | description: 'Create or update a fact',
184 | inputSchema: {
185 | type: 'object',
186 | properties: {
187 | id: { type: 'string' },
188 | content: { type: 'string' },
189 | strictness: {
190 | type: 'string',
191 | enum: Object.values(StrictnessLevel)
192 | },
193 | type: { type: 'string' },
194 | category: {
195 | type: 'string',
196 | enum: Object.values(FactCategory),
197 | description: 'The category this fact belongs to'
198 | },
199 | minVersion: { type: 'string' },
200 | maxVersion: { type: 'string' },
201 | conditions: {
202 | type: 'array',
203 | items: {
204 | type: 'object',
205 | properties: {
206 | factId: { type: 'string' },
207 | type: {
208 | type: 'string',
209 | enum: ['REQUIRES', 'CONFLICTS_WITH']
210 | }
211 | },
212 | required: ['factId', 'type']
213 | }
214 | },
215 | acceptanceCriteria: {
216 | type: 'array',
217 | items: {
218 | type: 'object',
219 | properties: {
220 | id: { type: 'string' },
221 | description: { type: 'string' },
222 | validationType: {
223 | type: 'string',
224 | enum: ['MANUAL', 'AUTOMATED']
225 | },
226 | validationScript: { type: 'string' }
227 | },
228 | required: ['id', 'description', 'validationType']
229 | }
230 | }
231 | },
232 | required: ['id', 'content', 'strictness', 'type', 'category', 'minVersion', 'maxVersion']
233 | }
234 | },
235 | {
236 | name: 'validate_criteria',
237 | description: 'Validate content against fact acceptance criteria',
238 | inputSchema: {
239 | type: 'object',
240 | properties: {
241 | factId: {
242 | type: 'string',
243 | description: 'ID of the fact to validate against'
244 | },
245 | content: {
246 | type: 'string',
247 | description: 'Content to validate'
248 | }
249 | },
250 | required: ['factId', 'content']
251 | }
252 | }
253 | ]
254 | }));
255 | // Handler for executing tools
256 | this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
257 | switch (request.params.name) {
258 | case 'get_all_facts': {
259 | const facts = await this.storage.searchFacts({});
260 | return {
261 | content: [{ type: 'text', text: JSON.stringify(facts, null, 2) }]
262 | };
263 | }
264 | case 'get_fact': {
265 | const args = request.params.arguments;
266 | const fact = await this.storage.getFact(args.id);
267 | if (!fact) {
268 | return {
269 | content: [{ type: 'text', text: `Fact not found: ${args.id}` }],
270 | isError: true
271 | };
272 | }
273 | return {
274 | content: [{ type: 'text', text: JSON.stringify(fact, null, 2) }]
275 | };
276 | }
277 | case 'search_facts': {
278 | const args = request.params.arguments;
279 | const facts = await this.storage.searchFacts(args);
280 | return {
281 | content: [{ type: 'text', text: JSON.stringify(facts, null, 2) }]
282 | };
283 | }
284 | case 'set_fact': {
285 | const args = request.params.arguments;
286 | try {
287 | await this.storage.setFact(args.id, args.content, args.strictness, args.type, args.category, args.minVersion, args.maxVersion, args.conditions || [], args.acceptanceCriteria || []);
288 | return {
289 | content: [{ type: 'text', text: `Fact ${args.id} saved successfully` }]
290 | };
291 | }
292 | catch (error) {
293 | return {
294 | content: [{ type: 'text', text: `Error saving fact: ${error instanceof Error ? error.message : 'Unknown error'}` }],
295 | isError: true
296 | };
297 | }
298 | }
299 | case 'validate_criteria': {
300 | const args = request.params.arguments;
301 | const fact = await this.storage.getFact(args.factId);
302 | if (!fact) {
303 | return {
304 | content: [{ type: 'text', text: `Fact not found: ${args.factId}` }],
305 | isError: true
306 | };
307 | }
308 | try {
309 | const results = await validateCriteria(args.content, fact.acceptanceCriteria);
310 | return {
311 | content: [{ type: 'text', text: JSON.stringify(results, null, 2) }]
312 | };
313 | }
314 | catch (error) {
315 | return {
316 | content: [{ type: 'text', text: `Validation error: ${error instanceof Error ? error.message : 'Unknown error'}` }],
317 | isError: true
318 | };
319 | }
320 | }
321 | }
322 | throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
323 | });
324 | }
325 | async run() {
326 | const transport = new StdioServerTransport();
327 | await this.server.connect(transport);
328 | console.error('Facts MCP server running on stdio');
329 | // Handle cleanup on shutdown
330 | process.on('SIGINT', async () => {
331 | await this.storage.close();
332 | process.exit(0);
333 | });
334 | process.on('SIGTERM', async () => {
335 | await this.storage.close();
336 | process.exit(0);
337 | });
338 | }
339 | }
340 | const server = new FactsServer();
341 | server.run().catch(console.error);
342 |
--------------------------------------------------------------------------------
/facts-server/build/sdk/server.d.ts:
--------------------------------------------------------------------------------
1 | import { ServerRequest, ServerResponse, ServerInfo, ServerCapabilities, ServerTransport } from './types.js';
2 | export declare class Server {
3 | private info;
4 | private capabilities;
5 | private transport;
6 | private handlers;
7 | constructor(info: ServerInfo, capabilities: {
8 | capabilities: ServerCapabilities;
9 | });
10 | connect(transport: ServerTransport): Promise;
11 | close(): Promise;
12 | setHandler(method: string, handler: (request: ServerRequest) => Promise): void;
13 | }
14 |
--------------------------------------------------------------------------------
/facts-server/build/sdk/server.js:
--------------------------------------------------------------------------------
1 | export class Server {
2 | constructor(info, capabilities) {
3 | this.info = info;
4 | this.capabilities = capabilities;
5 | this.transport = null;
6 | this.handlers = new Map();
7 | }
8 | async connect(transport) {
9 | this.transport = transport;
10 | transport.onRequest(async (request) => {
11 | const handler = this.handlers.get(request.method);
12 | if (!handler) {
13 | return {
14 | content: [{ type: 'text', text: `Unknown method: ${request.method}` }],
15 | isError: true
16 | };
17 | }
18 | return handler(request);
19 | });
20 | await transport.connect();
21 | }
22 | async close() {
23 | if (this.transport) {
24 | await this.transport.disconnect();
25 | this.transport = null;
26 | }
27 | }
28 | setHandler(method, handler) {
29 | this.handlers.set(method, handler);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/facts-server/build/sdk/stdio.d.ts:
--------------------------------------------------------------------------------
1 | import { ServerRequest, ServerResponse, ServerTransport } from './types.js';
2 | export declare class StdioServerTransport implements ServerTransport {
3 | private requestHandler;
4 | private readline;
5 | connect(): Promise;
6 | disconnect(): Promise;
7 | onRequest(handler: (request: ServerRequest) => Promise): void;
8 | }
9 |
--------------------------------------------------------------------------------
/facts-server/build/sdk/stdio.js:
--------------------------------------------------------------------------------
1 | import { createInterface } from 'readline';
2 | export class StdioServerTransport {
3 | constructor() {
4 | this.requestHandler = null;
5 | this.readline = createInterface({
6 | input: process.stdin,
7 | output: process.stdout
8 | });
9 | }
10 | async connect() {
11 | this.readline.on('line', async (line) => {
12 | try {
13 | const request = JSON.parse(line);
14 | if (this.requestHandler) {
15 | const response = await this.requestHandler(request);
16 | console.log(JSON.stringify(response));
17 | }
18 | }
19 | catch (error) {
20 | console.error('Error processing request:', error);
21 | console.log(JSON.stringify({
22 | content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}` }],
23 | isError: true
24 | }));
25 | }
26 | });
27 | }
28 | async disconnect() {
29 | this.readline.close();
30 | }
31 | onRequest(handler) {
32 | this.requestHandler = handler;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/facts-server/build/sdk/types.d.ts:
--------------------------------------------------------------------------------
1 | export interface ServerRequest {
2 | method: string;
3 | params?: Record;
4 | }
5 | export interface ServerResponse {
6 | content: Array<{
7 | type: string;
8 | text: string;
9 | }>;
10 | isError?: boolean;
11 | }
12 | export interface ServerCapabilities {
13 | tools?: Record;
18 | required?: string[];
19 | };
20 | }>;
21 | }
22 | export interface ServerInfo {
23 | name: string;
24 | version: string;
25 | }
26 | export interface ServerTransport {
27 | connect(): Promise;
28 | disconnect(): Promise;
29 | onRequest(handler: (request: ServerRequest) => Promise): void;
30 | }
31 |
--------------------------------------------------------------------------------
/facts-server/build/sdk/types.js:
--------------------------------------------------------------------------------
1 | export {};
2 |
--------------------------------------------------------------------------------
/facts-server/build/storage.d.ts:
--------------------------------------------------------------------------------
1 | import { StorageProvider, StorageSearchResult, StrictnessLevel, FactCategory, Condition, AcceptanceCriterion } from './types.js';
2 | export declare class PrismaStorageProvider implements StorageProvider {
3 | private prisma;
4 | constructor();
5 | setFact(id: string, content: string, strictness: StrictnessLevel, type: string, category: FactCategory, minVersion: string, maxVersion: string, conditions: Condition[], acceptanceCriteria: AcceptanceCriterion[], contentEmbedding?: string): Promise;
6 | getFact(id: string): Promise;
7 | searchFacts(options: {
8 | type?: string;
9 | category?: FactCategory;
10 | strictness?: StrictnessLevel;
11 | version?: string;
12 | embedding?: string;
13 | similarityThreshold?: number;
14 | }): Promise;
15 | deleteFact(id: string): Promise;
16 | close(): Promise;
17 | }
18 |
--------------------------------------------------------------------------------
/facts-server/build/storage.js:
--------------------------------------------------------------------------------
1 | import { PrismaClient } from '@prisma/client';
2 | export class PrismaStorageProvider {
3 | constructor() {
4 | this.prisma = new PrismaClient();
5 | }
6 | async setFact(id, content, strictness, type, category, minVersion, maxVersion, conditions, acceptanceCriteria, contentEmbedding) {
7 | const data = {
8 | id,
9 | content,
10 | strictness,
11 | type,
12 | category,
13 | minVersion,
14 | maxVersion,
15 | content_embedding: contentEmbedding,
16 | conditions: {
17 | create: conditions.map(condition => ({
18 | type: condition.type,
19 | targetId: condition.factId
20 | }))
21 | },
22 | acceptanceCriteria: {
23 | create: acceptanceCriteria.map(criterion => ({
24 | id: criterion.id,
25 | description: criterion.description,
26 | validationType: criterion.validationType,
27 | validationScript: criterion.validationScript
28 | }))
29 | }
30 | };
31 | await this.prisma.fact.upsert({
32 | where: { id },
33 | create: data,
34 | update: {
35 | ...data,
36 | conditions: {
37 | deleteMany: {},
38 | create: conditions.map(condition => ({
39 | type: condition.type,
40 | targetId: condition.factId
41 | }))
42 | },
43 | acceptanceCriteria: {
44 | deleteMany: {},
45 | create: acceptanceCriteria.map(criterion => ({
46 | id: criterion.id,
47 | description: criterion.description,
48 | validationType: criterion.validationType,
49 | validationScript: criterion.validationScript
50 | }))
51 | }
52 | }
53 | });
54 | }
55 | async getFact(id) {
56 | const fact = await this.prisma.fact.findUnique({
57 | where: { id },
58 | include: {
59 | conditions: true,
60 | acceptanceCriteria: true
61 | }
62 | });
63 | if (!fact) {
64 | return null;
65 | }
66 | return {
67 | id: fact.id,
68 | content: fact.content,
69 | strictness: fact.strictness,
70 | type: fact.type,
71 | category: fact.category,
72 | minVersion: fact.minVersion,
73 | maxVersion: fact.maxVersion,
74 | conditions: fact.conditions.map(c => ({
75 | factId: c.targetId,
76 | type: c.type
77 | })),
78 | acceptanceCriteria: fact.acceptanceCriteria.map(ac => ({
79 | id: ac.id,
80 | description: ac.description,
81 | validationType: ac.validationType,
82 | validationScript: ac.validationScript || undefined
83 | })),
84 | createdAt: fact.createdAt.toISOString(),
85 | updatedAt: fact.updatedAt.toISOString(),
86 | applicable: fact.applicable
87 | };
88 | }
89 | async searchFacts(options) {
90 | let facts;
91 | if (options.embedding) {
92 | // Use raw query for vector similarity search
93 | const threshold = options.similarityThreshold || 0.8;
94 | const whereConditions = [];
95 | const params = [options.embedding, threshold];
96 | if (options.type) {
97 | whereConditions.push('f.type = ?');
98 | params.push(options.type);
99 | }
100 | if (options.category) {
101 | whereConditions.push('f.category = ?');
102 | params.push(options.category);
103 | }
104 | if (options.strictness) {
105 | whereConditions.push('f.strictness = ?');
106 | params.push(options.strictness);
107 | }
108 | if (options.version) {
109 | whereConditions.push('f.minVersion <= ? AND f.maxVersion >= ?');
110 | params.push(options.version, options.version);
111 | }
112 | const whereClause = whereConditions.length
113 | ? 'AND ' + whereConditions.join(' AND ')
114 | : '';
115 | const rawResults = await this.prisma.$queryRawUnsafe(`SELECT f.*,
116 | vector_similarity(f.content_embedding, ?) as similarity
117 | FROM "Fact" f
118 | WHERE vector_similarity(f.content_embedding, ?) > ?
119 | ${whereClause}
120 | ORDER BY similarity DESC`, options.embedding, options.embedding, threshold, ...params);
121 | // Fetch full fact data including relations
122 | const factPromises = rawResults.map(result => this.prisma.fact.findUnique({
123 | where: { id: result.id },
124 | include: {
125 | conditions: true,
126 | acceptanceCriteria: true
127 | }
128 | }));
129 | facts = (await Promise.all(factPromises)).filter((fact) => fact !== null);
130 | }
131 | else {
132 | const where = {};
133 | if (options.type) {
134 | where.type = options.type;
135 | }
136 | if (options.category) {
137 | where.category = options.category;
138 | }
139 | if (options.strictness) {
140 | where.strictness = options.strictness;
141 | }
142 | if (options.version) {
143 | where.AND = [
144 | { minVersion: { lte: options.version } },
145 | { maxVersion: { gte: options.version } }
146 | ];
147 | }
148 | facts = await this.prisma.fact.findMany({
149 | where,
150 | include: {
151 | conditions: true,
152 | acceptanceCriteria: true
153 | }
154 | });
155 | }
156 | return facts.map(fact => ({
157 | id: fact.id,
158 | content: fact.content,
159 | strictness: fact.strictness,
160 | type: fact.type,
161 | category: fact.category,
162 | minVersion: fact.minVersion,
163 | maxVersion: fact.maxVersion,
164 | conditions: fact.conditions.map(c => ({
165 | factId: c.targetId,
166 | type: c.type
167 | })),
168 | acceptanceCriteria: fact.acceptanceCriteria.map(ac => ({
169 | id: ac.id,
170 | description: ac.description,
171 | validationType: ac.validationType,
172 | validationScript: ac.validationScript || undefined
173 | })),
174 | createdAt: fact.createdAt.toISOString(),
175 | updatedAt: fact.updatedAt.toISOString(),
176 | applicable: fact.applicable
177 | }));
178 | }
179 | async deleteFact(id) {
180 | try {
181 | await this.prisma.fact.delete({
182 | where: { id }
183 | });
184 | return true;
185 | }
186 | catch (error) {
187 | return false;
188 | }
189 | }
190 | async close() {
191 | await this.prisma.$disconnect();
192 | }
193 | }
194 |
--------------------------------------------------------------------------------
/facts-server/build/types.d.ts:
--------------------------------------------------------------------------------
1 | export declare enum FactCategory {
2 | FRONTEND = "FRONTEND",
3 | BACKEND = "BACKEND",
4 | DATABASE = "DATABASE",
5 | FULL_STACK = "FULL_STACK",
6 | DESIGN_PATTERN = "DESIGN_PATTERN",
7 | ARCHITECTURE_PATTERN = "ARCHITECTURE_PATTERN",
8 | TESTING_PATTERN = "TESTING_PATTERN",
9 | NAMING_CONVENTION = "NAMING_CONVENTION",
10 | PROJECT_STRUCTURE = "PROJECT_STRUCTURE",
11 | CODE_STYLE = "CODE_STYLE",
12 | DEPLOYMENT = "DEPLOYMENT",
13 | CI_CD = "CI_CD",
14 | MONITORING = "MONITORING",
15 | SECURITY = "SECURITY",
16 | GIT_WORKFLOW = "GIT_WORKFLOW",
17 | CODE_REVIEW = "CODE_REVIEW",
18 | DOCUMENTATION = "DOCUMENTATION",
19 | PACKAGE_MANAGEMENT = "PACKAGE_MANAGEMENT",
20 | VERSIONING = "VERSIONING",
21 | OPTIMIZATION = "OPTIMIZATION",
22 | CACHING = "CACHING",
23 | ACCESSIBILITY = "ACCESSIBILITY",
24 | INTERNATIONALIZATION = "INTERNATIONALIZATION",
25 | ERROR_HANDLING = "ERROR_HANDLING"
26 | }
27 | export declare enum StrictnessLevel {
28 | REQUIRED = "REQUIRED",
29 | RECOMMENDED = "RECOMMENDED",
30 | OPTIONAL = "OPTIONAL"
31 | }
32 | export interface Condition {
33 | factId: string;
34 | type: 'REQUIRES' | 'CONFLICTS_WITH';
35 | }
36 | export type ValidationType = 'MANUAL' | 'AUTOMATED' | 'URL_CHECK';
37 | export interface AcceptanceCriterion {
38 | id: string;
39 | description: string;
40 | validationType: ValidationType;
41 | validationScript?: string;
42 | }
43 | export interface FactData {
44 | content: string;
45 | strictness: StrictnessLevel;
46 | type: string;
47 | category: FactCategory;
48 | minVersion: string;
49 | maxVersion: string;
50 | conditions: Condition[];
51 | acceptanceCriteria: AcceptanceCriterion[];
52 | createdAt: string;
53 | updatedAt: string;
54 | }
55 | export interface StorageSearchResult extends FactData {
56 | id: string;
57 | applicable: boolean;
58 | }
59 | export interface ValidationResult {
60 | criterionId: string;
61 | passed: boolean;
62 | message?: string;
63 | }
64 | export interface StorageProvider {
65 | setFact(id: string, content: string, strictness: StrictnessLevel, type: string, category: FactCategory, minVersion: string, maxVersion: string, conditions: Condition[], acceptanceCriteria: AcceptanceCriterion[], contentEmbedding?: string): Promise;
66 | getFact(id: string): Promise;
67 | searchFacts(options: {
68 | type?: string;
69 | strictness?: StrictnessLevel;
70 | version?: string;
71 | embedding?: string;
72 | similarityThreshold?: number;
73 | }): Promise;
74 | deleteFact(id: string): Promise;
75 | }
76 |
--------------------------------------------------------------------------------
/facts-server/build/types.js:
--------------------------------------------------------------------------------
1 | export var FactCategory;
2 | (function (FactCategory) {
3 | // Architecture Layers
4 | FactCategory["FRONTEND"] = "FRONTEND";
5 | FactCategory["BACKEND"] = "BACKEND";
6 | FactCategory["DATABASE"] = "DATABASE";
7 | FactCategory["FULL_STACK"] = "FULL_STACK";
8 | // Development Patterns
9 | FactCategory["DESIGN_PATTERN"] = "DESIGN_PATTERN";
10 | FactCategory["ARCHITECTURE_PATTERN"] = "ARCHITECTURE_PATTERN";
11 | FactCategory["TESTING_PATTERN"] = "TESTING_PATTERN";
12 | // Code Organization
13 | FactCategory["NAMING_CONVENTION"] = "NAMING_CONVENTION";
14 | FactCategory["PROJECT_STRUCTURE"] = "PROJECT_STRUCTURE";
15 | FactCategory["CODE_STYLE"] = "CODE_STYLE";
16 | // Operations
17 | FactCategory["DEPLOYMENT"] = "DEPLOYMENT";
18 | FactCategory["CI_CD"] = "CI_CD";
19 | FactCategory["MONITORING"] = "MONITORING";
20 | FactCategory["SECURITY"] = "SECURITY";
21 | // Development Process
22 | FactCategory["GIT_WORKFLOW"] = "GIT_WORKFLOW";
23 | FactCategory["CODE_REVIEW"] = "CODE_REVIEW";
24 | FactCategory["DOCUMENTATION"] = "DOCUMENTATION";
25 | // Dependencies
26 | FactCategory["PACKAGE_MANAGEMENT"] = "PACKAGE_MANAGEMENT";
27 | FactCategory["VERSIONING"] = "VERSIONING";
28 | // Performance
29 | FactCategory["OPTIMIZATION"] = "OPTIMIZATION";
30 | FactCategory["CACHING"] = "CACHING";
31 | // Cross-cutting
32 | FactCategory["ACCESSIBILITY"] = "ACCESSIBILITY";
33 | FactCategory["INTERNATIONALIZATION"] = "INTERNATIONALIZATION";
34 | FactCategory["ERROR_HANDLING"] = "ERROR_HANDLING";
35 | })(FactCategory || (FactCategory = {}));
36 | export var StrictnessLevel;
37 | (function (StrictnessLevel) {
38 | StrictnessLevel["REQUIRED"] = "REQUIRED";
39 | StrictnessLevel["RECOMMENDED"] = "RECOMMENDED";
40 | StrictnessLevel["OPTIONAL"] = "OPTIONAL";
41 | })(StrictnessLevel || (StrictnessLevel = {}));
42 |
--------------------------------------------------------------------------------
/facts-server/build/validation.d.ts:
--------------------------------------------------------------------------------
1 | import { AcceptanceCriterion, ValidationResult } from './types.js';
2 | export declare class ValidationError extends Error {
3 | constructor(message: string);
4 | }
5 | export declare function validateCriteria(content: string, criteria: AcceptanceCriterion[]): Promise;
6 | export declare function validateFactConditions(factConditions: Map, conditions: {
7 | factId: string;
8 | type: 'REQUIRES' | 'CONFLICTS_WITH';
9 | }[]): boolean;
10 |
--------------------------------------------------------------------------------
/facts-server/build/validation.js:
--------------------------------------------------------------------------------
1 | export class ValidationError extends Error {
2 | constructor(message) {
3 | super(message);
4 | this.name = 'ValidationError';
5 | }
6 | }
7 | export async function validateCriteria(content, criteria) {
8 | const results = [];
9 | for (const criterion of criteria) {
10 | if (criterion.validationType === 'MANUAL') {
11 | results.push({
12 | criterionId: criterion.id,
13 | passed: false,
14 | message: 'Manual validation required'
15 | });
16 | continue;
17 | }
18 | if (!criterion.validationScript) {
19 | throw new ValidationError(`Automated criterion ${criterion.id} has no validation script`);
20 | }
21 | try {
22 | // Create a function from the validation script
23 | const validationFn = new Function('content', criterion.validationScript + '\nreturn ' + criterion.validationScript.match(/function\s+(\w+)/)?.[1] + '(content);');
24 | const passed = await Promise.resolve(validationFn(content));
25 | results.push({
26 | criterionId: criterion.id,
27 | passed,
28 | message: passed ? 'Validation passed' : 'Validation failed'
29 | });
30 | }
31 | catch (error) {
32 | results.push({
33 | criterionId: criterion.id,
34 | passed: false,
35 | message: `Validation error: ${error instanceof Error ? error.message : 'Unknown error'}`
36 | });
37 | }
38 | }
39 | return results;
40 | }
41 | export function validateFactConditions(factConditions, conditions) {
42 | for (const condition of conditions) {
43 | const factExists = factConditions.get(condition.factId) ?? false;
44 | if (condition.type === 'REQUIRES' && !factExists) {
45 | return false;
46 | }
47 | if (condition.type === 'CONFLICTS_WITH' && factExists) {
48 | return false;
49 | }
50 | }
51 | return true;
52 | }
53 |
--------------------------------------------------------------------------------
/facts-server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "facts-server",
3 | "version": "1.0.0",
4 | "description": "MCP server for managing documentation facts and acceptance criteria",
5 | "type": "module",
6 | "main": "build/index.js",
7 | "scripts": {
8 | "build": "tsc && chmod +x build/index.js",
9 | "dev": "node --inspect -r ts-node/register --loader ts-node/esm src/index.ts",
10 | "start": "node build/index.js"
11 | },
12 | "keywords": [
13 | "mcp",
14 | "documentation",
15 | "validation"
16 | ],
17 | "author": "Andrew Hopper (andrewhopper.com)",
18 | "license": "MIT",
19 | "dependencies": {
20 | "@modelcontextprotocol/sdk": "^1.5.0",
21 | "@prisma/client": "^6.4.1",
22 | "sqlite-vec": "^0.1.7-alpha.2",
23 | "sqlite3": "^5.1.7"
24 | },
25 | "devDependencies": {
26 | "prisma": "^6.4.1",
27 | "ts-node": "^10.9.2",
28 | "typescript": "^5.3.3"
29 | }
30 | }
--------------------------------------------------------------------------------
/facts-server/prisma/facts.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andrewhopper/hivemind/b82fb50a74b5eadef26a19cc1f69a178772b29d3/facts-server/prisma/facts.db
--------------------------------------------------------------------------------
/facts-server/prisma/migrations/20250223115817_init/migration.sql:
--------------------------------------------------------------------------------
1 | -- CreateTable
2 | CREATE TABLE "Fact" (
3 | "id" TEXT NOT NULL PRIMARY KEY,
4 | "content" TEXT NOT NULL,
5 | "strictness" TEXT NOT NULL,
6 | "type" TEXT NOT NULL,
7 | "minVersion" TEXT NOT NULL,
8 | "maxVersion" TEXT NOT NULL,
9 | "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
10 | "updatedAt" DATETIME NOT NULL,
11 | "applicable" BOOLEAN NOT NULL DEFAULT true,
12 | "content_embedding" TEXT
13 | );
14 |
15 | -- CreateTable
16 | CREATE TABLE "Condition" (
17 | "id" TEXT NOT NULL PRIMARY KEY,
18 | "factId" TEXT NOT NULL,
19 | "type" TEXT NOT NULL,
20 | "targetId" TEXT NOT NULL,
21 | CONSTRAINT "Condition_factId_fkey" FOREIGN KEY ("factId") REFERENCES "Fact" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
22 | );
23 |
24 | -- CreateTable
25 | CREATE TABLE "AcceptanceCriterion" (
26 | "id" TEXT NOT NULL PRIMARY KEY,
27 | "factId" TEXT NOT NULL,
28 | "description" TEXT NOT NULL,
29 | "validationType" TEXT NOT NULL,
30 | "validationScript" TEXT,
31 | CONSTRAINT "AcceptanceCriterion_factId_fkey" FOREIGN KEY ("factId") REFERENCES "Fact" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
32 | );
33 |
--------------------------------------------------------------------------------
/facts-server/prisma/migrations/20250223171046_add_fact_category/migration.sql:
--------------------------------------------------------------------------------
1 | -- Add category column as nullable first
2 | PRAGMA foreign_keys=OFF;
3 |
4 | -- Create new table with nullable category
5 | CREATE TABLE "new_Fact" (
6 | "id" TEXT NOT NULL PRIMARY KEY,
7 | "content" TEXT NOT NULL,
8 | "strictness" TEXT NOT NULL,
9 | "type" TEXT NOT NULL,
10 | "category" TEXT,
11 | "minVersion" TEXT NOT NULL,
12 | "maxVersion" TEXT NOT NULL,
13 | "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
14 | "updatedAt" DATETIME NOT NULL,
15 | "applicable" BOOLEAN NOT NULL DEFAULT true,
16 | "content_embedding" TEXT
17 | );
18 |
19 | -- Copy existing data
20 | INSERT INTO "new_Fact" ("applicable", "content", "content_embedding", "createdAt", "id", "maxVersion", "minVersion", "strictness", "type", "updatedAt")
21 | SELECT "applicable", "content", "content_embedding", "createdAt", "id", "maxVersion", "minVersion", "strictness", "type", "updatedAt"
22 | FROM "Fact";
23 |
24 | -- Drop old table
25 | DROP TABLE "Fact";
26 | ALTER TABLE "new_Fact" RENAME TO "Fact";
27 |
28 | -- Update existing records with appropriate categories
29 | UPDATE "Fact" SET "category" = 'FRONTEND' WHERE "type" = 'styling';
30 | UPDATE "Fact" SET "category" = 'FRAMEWORK' WHERE "type" = 'framework';
31 |
32 | -- Make category required by creating new table
33 | CREATE TABLE "new_Fact2" (
34 | "id" TEXT NOT NULL PRIMARY KEY,
35 | "content" TEXT NOT NULL,
36 | "strictness" TEXT NOT NULL,
37 | "type" TEXT NOT NULL,
38 | "category" TEXT NOT NULL,
39 | "minVersion" TEXT NOT NULL,
40 | "maxVersion" TEXT NOT NULL,
41 | "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
42 | "updatedAt" DATETIME NOT NULL,
43 | "applicable" BOOLEAN NOT NULL DEFAULT true,
44 | "content_embedding" TEXT
45 | );
46 |
47 | -- Copy data with non-null categories
48 | INSERT INTO "new_Fact2" SELECT * FROM "Fact";
49 |
50 | -- Replace the table
51 | DROP TABLE "Fact";
52 | ALTER TABLE "new_Fact2" RENAME TO "Fact";
53 |
54 | PRAGMA foreign_keys=ON;
55 |
--------------------------------------------------------------------------------
/facts-server/prisma/migrations/migration_lock.toml:
--------------------------------------------------------------------------------
1 | # Please do not edit this file manually
2 | # It should be added in your version-control system (e.g., Git)
3 | provider = "sqlite"
--------------------------------------------------------------------------------
/facts-server/prisma/schema.prisma:
--------------------------------------------------------------------------------
1 | datasource db {
2 | provider = "sqlite"
3 | url = "file:./facts.db"
4 | }
5 |
6 | generator client {
7 | provider = "prisma-client-js"
8 | }
9 |
10 | model Fact {
11 | id String @id
12 | content String
13 | strictness String // Will map to StrictnessLevel enum
14 | type String
15 | category String // Will map to FactCategory enum
16 | minVersion String
17 | maxVersion String
18 | createdAt DateTime @default(now())
19 | updatedAt DateTime @updatedAt
20 | applicable Boolean @default(true)
21 | content_embedding String?
22 |
23 | // Relations
24 | conditions Condition[]
25 | acceptanceCriteria AcceptanceCriterion[]
26 | }
27 |
28 | model Condition {
29 | id String @id @default(cuid())
30 | factId String
31 | type String // REQUIRES or CONFLICTS_WITH
32 | targetId String // The ID of the fact this condition references
33 | fact Fact @relation(fields: [factId], references: [id])
34 | }
35 |
36 | model AcceptanceCriterion {
37 | id String @id @default(cuid())
38 | factId String
39 | description String
40 | validationType String // MANUAL or AUTOMATED
41 | validationScript String?
42 | fact Fact @relation(fields: [factId], references: [id])
43 | }
44 |
--------------------------------------------------------------------------------
/facts-server/prisma/setup_vector.sql:
--------------------------------------------------------------------------------
1 | -- Load sqlite-vec extension
2 | SELECT load_extension('sqlite-vec');
3 |
4 | -- Create a function to calculate vector similarity
5 | CREATE FUNCTION vector_similarity(vec1 TEXT, vec2 TEXT)
6 | RETURNS REAL AS
7 | BEGIN
8 | RETURN cosine_similarity(json(vec1), json(vec2));
9 | END;
10 |
--------------------------------------------------------------------------------
/facts-server/src/index.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4 | import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError } from '@modelcontextprotocol/sdk/types.js';
5 | import { PrismaStorageProvider } from './storage.js';
6 | import { validateCriteria, validateFactConditions } from './validation.js';
7 | import { StrictnessLevel, FactCategory, AcceptanceCriterion, Condition } from './types.js';
8 |
9 | class FactsServer {
10 | private server: Server;
11 | private storage: PrismaStorageProvider;
12 |
13 | constructor() {
14 | this.storage = new PrismaStorageProvider();
15 | this.server = new Server(
16 | {
17 | name: 'facts-server',
18 | version: '0.1.0',
19 | },
20 | {
21 | capabilities: {
22 | tools: {
23 | get_all_facts: {
24 | description: 'Get all facts in the system',
25 | inputSchema: {
26 | type: 'object',
27 | properties: {}
28 | }
29 | },
30 | get_fact: {
31 | description: 'Get a fact by ID',
32 | inputSchema: {
33 | type: 'object',
34 | properties: {
35 | id: {
36 | type: 'string',
37 | description: 'The ID of the fact to retrieve'
38 | }
39 | },
40 | required: ['id']
41 | }
42 | },
43 | search_facts: {
44 | description: 'Search facts by type, strictness, and version',
45 | inputSchema: {
46 | type: 'object',
47 | properties: {
48 | type: {
49 | type: 'string',
50 | description: 'Filter by fact type'
51 | },
52 | strictness: {
53 | type: 'string',
54 | enum: Object.values(StrictnessLevel),
55 | description: 'Filter by strictness level'
56 | },
57 | version: {
58 | type: 'string',
59 | description: 'Filter by version compatibility'
60 | }
61 | }
62 | }
63 | },
64 | set_fact: {
65 | description: 'Create or update a fact',
66 | inputSchema: {
67 | type: 'object',
68 | properties: {
69 | id: { type: 'string' },
70 | content: { type: 'string' },
71 | strictness: {
72 | type: 'string',
73 | enum: Object.values(StrictnessLevel)
74 | },
75 | type: { type: 'string' },
76 | category: {
77 | type: 'string',
78 | enum: Object.values(FactCategory),
79 | description: 'The category this fact belongs to'
80 | },
81 | minVersion: { type: 'string' },
82 | maxVersion: { type: 'string' },
83 | conditions: {
84 | type: 'array',
85 | items: {
86 | type: 'object',
87 | properties: {
88 | factId: { type: 'string' },
89 | type: {
90 | type: 'string',
91 | enum: ['REQUIRES', 'CONFLICTS_WITH']
92 | }
93 | },
94 | required: ['factId', 'type']
95 | }
96 | },
97 | acceptanceCriteria: {
98 | type: 'array',
99 | items: {
100 | type: 'object',
101 | properties: {
102 | id: { type: 'string' },
103 | description: { type: 'string' },
104 | validationType: {
105 | type: 'string',
106 | enum: ['MANUAL', 'AUTOMATED']
107 | },
108 | validationScript: { type: 'string' }
109 | },
110 | required: ['id', 'description', 'validationType']
111 | }
112 | }
113 | },
114 | required: ['id', 'content', 'strictness', 'type', 'category', 'minVersion', 'maxVersion']
115 | }
116 | },
117 | validate_criteria: {
118 | description: 'Validate content against fact acceptance criteria',
119 | inputSchema: {
120 | type: 'object',
121 | properties: {
122 | factId: {
123 | type: 'string',
124 | description: 'ID of the fact to validate against'
125 | },
126 | content: {
127 | type: 'string',
128 | description: 'Content to validate'
129 | }
130 | },
131 | required: ['factId', 'content']
132 | }
133 | }
134 | }
135 | }
136 | }
137 | );
138 |
139 | this.setupHandlers();
140 | }
141 |
142 | private setupHandlers(): void {
143 | // Handler for listing available tools
144 | this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
145 | tools: [
146 | {
147 | name: 'get_all_facts',
148 | description: 'Get all facts in the system',
149 | inputSchema: {
150 | type: 'object',
151 | properties: {}
152 | }
153 | },
154 | {
155 | name: 'get_fact',
156 | description: 'Get a fact by ID',
157 | inputSchema: {
158 | type: 'object',
159 | properties: {
160 | id: {
161 | type: 'string',
162 | description: 'The ID of the fact to retrieve'
163 | }
164 | },
165 | required: ['id']
166 | }
167 | },
168 | {
169 | name: 'search_facts',
170 | description: 'Search facts by type, strictness, and version',
171 | inputSchema: {
172 | type: 'object',
173 | properties: {
174 | type: {
175 | type: 'string',
176 | description: 'Filter by fact type'
177 | },
178 | strictness: {
179 | type: 'string',
180 | enum: Object.values(StrictnessLevel),
181 | description: 'Filter by strictness level'
182 | },
183 | version: {
184 | type: 'string',
185 | description: 'Filter by version compatibility'
186 | }
187 | }
188 | }
189 | },
190 | {
191 | name: 'set_fact',
192 | description: 'Create or update a fact',
193 | inputSchema: {
194 | type: 'object',
195 | properties: {
196 | id: { type: 'string' },
197 | content: { type: 'string' },
198 | strictness: {
199 | type: 'string',
200 | enum: Object.values(StrictnessLevel)
201 | },
202 | type: { type: 'string' },
203 | category: {
204 | type: 'string',
205 | enum: Object.values(FactCategory),
206 | description: 'The category this fact belongs to'
207 | },
208 | minVersion: { type: 'string' },
209 | maxVersion: { type: 'string' },
210 | conditions: {
211 | type: 'array',
212 | items: {
213 | type: 'object',
214 | properties: {
215 | factId: { type: 'string' },
216 | type: {
217 | type: 'string',
218 | enum: ['REQUIRES', 'CONFLICTS_WITH']
219 | }
220 | },
221 | required: ['factId', 'type']
222 | }
223 | },
224 | acceptanceCriteria: {
225 | type: 'array',
226 | items: {
227 | type: 'object',
228 | properties: {
229 | id: { type: 'string' },
230 | description: { type: 'string' },
231 | validationType: {
232 | type: 'string',
233 | enum: ['MANUAL', 'AUTOMATED']
234 | },
235 | validationScript: { type: 'string' }
236 | },
237 | required: ['id', 'description', 'validationType']
238 | }
239 | }
240 | },
241 | required: ['id', 'content', 'strictness', 'type', 'category', 'minVersion', 'maxVersion']
242 | }
243 | },
244 | {
245 | name: 'validate_criteria',
246 | description: 'Validate content against fact acceptance criteria',
247 | inputSchema: {
248 | type: 'object',
249 | properties: {
250 | factId: {
251 | type: 'string',
252 | description: 'ID of the fact to validate against'
253 | },
254 | content: {
255 | type: 'string',
256 | description: 'Content to validate'
257 | }
258 | },
259 | required: ['factId', 'content']
260 | }
261 | }
262 | ]
263 | }));
264 |
265 | // Handler for executing tools
266 | this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
267 | switch (request.params.name) {
268 | case 'get_all_facts': {
269 | const facts = await this.storage.searchFacts({});
270 | return {
271 | content: [{ type: 'text', text: JSON.stringify(facts, null, 2) }]
272 | };
273 | }
274 |
275 | case 'get_fact': {
276 | const args = request.params.arguments as { id: string };
277 | const fact = await this.storage.getFact(args.id);
278 | if (!fact) {
279 | return {
280 | content: [{ type: 'text', text: `Fact not found: ${args.id}` }],
281 | isError: true
282 | };
283 | }
284 | return {
285 | content: [{ type: 'text', text: JSON.stringify(fact, null, 2) }]
286 | };
287 | }
288 |
289 | case 'search_facts': {
290 | const args = request.params.arguments as {
291 | type?: string;
292 | category?: FactCategory;
293 | strictness?: StrictnessLevel;
294 | version?: string;
295 | };
296 | const facts = await this.storage.searchFacts(args);
297 | return {
298 | content: [{ type: 'text', text: JSON.stringify(facts, null, 2) }]
299 | };
300 | }
301 |
302 | case 'set_fact': {
303 | const args = request.params.arguments as {
304 | id: string;
305 | content: string;
306 | strictness: StrictnessLevel;
307 | type: string;
308 | category: FactCategory;
309 | minVersion: string;
310 | maxVersion: string;
311 | conditions?: Condition[];
312 | acceptanceCriteria?: AcceptanceCriterion[];
313 | };
314 | try {
315 | await this.storage.setFact(
316 | args.id,
317 | args.content,
318 | args.strictness as StrictnessLevel,
319 | args.type,
320 | args.category,
321 | args.minVersion,
322 | args.maxVersion,
323 | args.conditions || [],
324 | args.acceptanceCriteria || []
325 | );
326 | return {
327 | content: [{ type: 'text', text: `Fact ${args.id} saved successfully` }]
328 | };
329 | } catch (error) {
330 | return {
331 | content: [{ type: 'text', text: `Error saving fact: ${error instanceof Error ? error.message : 'Unknown error'}` }],
332 | isError: true
333 | };
334 | }
335 | }
336 |
337 | case 'validate_criteria': {
338 | const args = request.params.arguments as {
339 | factId: string;
340 | content: string;
341 | };
342 | const fact = await this.storage.getFact(args.factId);
343 | if (!fact) {
344 | return {
345 | content: [{ type: 'text', text: `Fact not found: ${args.factId}` }],
346 | isError: true
347 | };
348 | }
349 |
350 | try {
351 | const results = await validateCriteria(args.content, fact.acceptanceCriteria);
352 | return {
353 | content: [{ type: 'text', text: JSON.stringify(results, null, 2) }]
354 | };
355 | } catch (error) {
356 | return {
357 | content: [{ type: 'text', text: `Validation error: ${error instanceof Error ? error.message : 'Unknown error'}` }],
358 | isError: true
359 | };
360 | }
361 | }
362 | }
363 |
364 | throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
365 | });
366 | }
367 |
368 | async run(): Promise {
369 | const transport = new StdioServerTransport();
370 | await this.server.connect(transport);
371 | console.error('Facts MCP server running on stdio');
372 |
373 | // Handle cleanup on shutdown
374 | process.on('SIGINT', async () => {
375 | await this.storage.close();
376 | process.exit(0);
377 | });
378 |
379 | process.on('SIGTERM', async () => {
380 | await this.storage.close();
381 | process.exit(0);
382 | });
383 | }
384 | }
385 |
386 | const server = new FactsServer();
387 | server.run().catch(console.error);
388 |
--------------------------------------------------------------------------------
/facts-server/src/storage.ts:
--------------------------------------------------------------------------------
1 | import { PrismaClient, Prisma } from '@prisma/client';
2 | import { StorageProvider, StorageSearchResult, StrictnessLevel, FactCategory, Condition, AcceptanceCriterion } from './types.js';
3 |
4 | export class PrismaStorageProvider implements StorageProvider {
5 | private prisma: PrismaClient;
6 |
7 | constructor() {
8 | this.prisma = new PrismaClient();
9 | }
10 |
11 | async setFact(
12 | id: string,
13 | content: string,
14 | strictness: StrictnessLevel,
15 | type: string,
16 | category: FactCategory,
17 | minVersion: string,
18 | maxVersion: string,
19 | conditions: Condition[],
20 | acceptanceCriteria: AcceptanceCriterion[],
21 | contentEmbedding?: string
22 | ): Promise {
23 | const data: Prisma.FactCreateInput | Prisma.FactUpdateInput = {
24 | id,
25 | content,
26 | strictness,
27 | type,
28 | category,
29 | minVersion,
30 | maxVersion,
31 | content_embedding: contentEmbedding,
32 | conditions: {
33 | create: conditions.map(condition => ({
34 | type: condition.type,
35 | targetId: condition.factId
36 | }))
37 | },
38 | acceptanceCriteria: {
39 | create: acceptanceCriteria.map(criterion => ({
40 | id: criterion.id,
41 | description: criterion.description,
42 | validationType: criterion.validationType,
43 | validationScript: criterion.validationScript
44 | }))
45 | }
46 | };
47 |
48 | await this.prisma.fact.upsert({
49 | where: { id },
50 | create: data as Prisma.FactCreateInput,
51 | update: {
52 | ...data,
53 | conditions: {
54 | deleteMany: {},
55 | create: conditions.map(condition => ({
56 | type: condition.type,
57 | targetId: condition.factId
58 | }))
59 | },
60 | acceptanceCriteria: {
61 | deleteMany: {},
62 | create: acceptanceCriteria.map(criterion => ({
63 | id: criterion.id,
64 | description: criterion.description,
65 | validationType: criterion.validationType,
66 | validationScript: criterion.validationScript
67 | }))
68 | }
69 | }
70 | });
71 | }
72 |
73 | async getFact(id: string): Promise {
74 | const fact = await this.prisma.fact.findUnique({
75 | where: { id },
76 | include: {
77 | conditions: true,
78 | acceptanceCriteria: true
79 | }
80 | });
81 |
82 | if (!fact) {
83 | return null;
84 | }
85 |
86 | return {
87 | id: fact.id,
88 | content: fact.content,
89 | strictness: fact.strictness as StrictnessLevel,
90 | type: fact.type,
91 | category: fact.category as FactCategory,
92 | minVersion: fact.minVersion,
93 | maxVersion: fact.maxVersion,
94 | conditions: fact.conditions.map(c => ({
95 | factId: c.targetId,
96 | type: c.type as 'REQUIRES' | 'CONFLICTS_WITH'
97 | })),
98 | acceptanceCriteria: fact.acceptanceCriteria.map(ac => ({
99 | id: ac.id,
100 | description: ac.description,
101 | validationType: ac.validationType as 'MANUAL' | 'AUTOMATED',
102 | validationScript: ac.validationScript || undefined
103 | })),
104 | createdAt: fact.createdAt.toISOString(),
105 | updatedAt: fact.updatedAt.toISOString(),
106 | applicable: fact.applicable
107 | };
108 | }
109 |
110 | async searchFacts(options: {
111 | type?: string;
112 | category?: FactCategory;
113 | strictness?: StrictnessLevel;
114 | version?: string;
115 | embedding?: string;
116 | similarityThreshold?: number;
117 | }): Promise {
118 | let facts;
119 |
120 | if (options.embedding) {
121 | // Use raw query for vector similarity search
122 | const threshold = options.similarityThreshold || 0.8;
123 | const whereConditions = [];
124 | const params: any[] = [options.embedding, threshold];
125 |
126 | if (options.type) {
127 | whereConditions.push('f.type = ?');
128 | params.push(options.type);
129 | }
130 |
131 | if (options.category) {
132 | whereConditions.push('f.category = ?');
133 | params.push(options.category);
134 | }
135 |
136 | if (options.strictness) {
137 | whereConditions.push('f.strictness = ?');
138 | params.push(options.strictness);
139 | }
140 |
141 | if (options.version) {
142 | whereConditions.push('f.minVersion <= ? AND f.maxVersion >= ?');
143 | params.push(options.version, options.version);
144 | }
145 |
146 | const whereClause = whereConditions.length
147 | ? 'AND ' + whereConditions.join(' AND ')
148 | : '';
149 |
150 | const rawResults = await this.prisma.$queryRawUnsafe>(
151 | `SELECT f.*,
152 | vector_similarity(f.content_embedding, ?) as similarity
153 | FROM "Fact" f
154 | WHERE vector_similarity(f.content_embedding, ?) > ?
155 | ${whereClause}
156 | ORDER BY similarity DESC`,
157 | options.embedding,
158 | options.embedding,
159 | threshold,
160 | ...params
161 | );
162 |
163 | // Fetch full fact data including relations
164 | const factPromises = rawResults.map(result =>
165 | this.prisma.fact.findUnique({
166 | where: { id: result.id },
167 | include: {
168 | conditions: true,
169 | acceptanceCriteria: true
170 | }
171 | })
172 | );
173 |
174 | facts = (await Promise.all(factPromises)).filter((fact): fact is NonNullable => fact !== null);
175 | } else {
176 | const where: Prisma.FactWhereInput = {};
177 |
178 | if (options.type) {
179 | where.type = options.type;
180 | }
181 |
182 | if (options.category) {
183 | where.category = options.category;
184 | }
185 |
186 | if (options.strictness) {
187 | where.strictness = options.strictness;
188 | }
189 |
190 | if (options.version) {
191 | where.AND = [
192 | { minVersion: { lte: options.version } },
193 | { maxVersion: { gte: options.version } }
194 | ];
195 | }
196 |
197 | facts = await this.prisma.fact.findMany({
198 | where,
199 | include: {
200 | conditions: true,
201 | acceptanceCriteria: true
202 | }
203 | });
204 | }
205 |
206 | return facts.map(fact => ({
207 | id: fact.id,
208 | content: fact.content,
209 | strictness: fact.strictness as StrictnessLevel,
210 | type: fact.type,
211 | category: fact.category as FactCategory,
212 | minVersion: fact.minVersion,
213 | maxVersion: fact.maxVersion,
214 | conditions: fact.conditions.map(c => ({
215 | factId: c.targetId,
216 | type: c.type as 'REQUIRES' | 'CONFLICTS_WITH'
217 | })),
218 | acceptanceCriteria: fact.acceptanceCriteria.map(ac => ({
219 | id: ac.id,
220 | description: ac.description,
221 | validationType: ac.validationType as 'MANUAL' | 'AUTOMATED',
222 | validationScript: ac.validationScript || undefined
223 | })),
224 | createdAt: fact.createdAt.toISOString(),
225 | updatedAt: fact.updatedAt.toISOString(),
226 | applicable: fact.applicable
227 | }));
228 | }
229 |
230 | async deleteFact(id: string): Promise {
231 | try {
232 | await this.prisma.fact.delete({
233 | where: { id }
234 | });
235 | return true;
236 | } catch (error) {
237 | return false;
238 | }
239 | }
240 |
241 | async close(): Promise {
242 | await this.prisma.$disconnect();
243 | }
244 | }
245 |
--------------------------------------------------------------------------------
/facts-server/src/types.ts:
--------------------------------------------------------------------------------
1 | export enum FactCategory {
2 | // Architecture Layers
3 | FRONTEND = 'FRONTEND',
4 | BACKEND = 'BACKEND',
5 | DATABASE = 'DATABASE',
6 | FULL_STACK = 'FULL_STACK',
7 |
8 | // Development Patterns
9 | DESIGN_PATTERN = 'DESIGN_PATTERN',
10 | ARCHITECTURE_PATTERN = 'ARCHITECTURE_PATTERN',
11 | TESTING_PATTERN = 'TESTING_PATTERN',
12 |
13 | // Code Organization
14 | NAMING_CONVENTION = 'NAMING_CONVENTION',
15 | PROJECT_STRUCTURE = 'PROJECT_STRUCTURE',
16 | CODE_STYLE = 'CODE_STYLE',
17 |
18 | // Operations
19 | DEPLOYMENT = 'DEPLOYMENT',
20 | CI_CD = 'CI_CD',
21 | MONITORING = 'MONITORING',
22 | SECURITY = 'SECURITY',
23 |
24 | // Development Process
25 | GIT_WORKFLOW = 'GIT_WORKFLOW',
26 | CODE_REVIEW = 'CODE_REVIEW',
27 | DOCUMENTATION = 'DOCUMENTATION',
28 |
29 | // Dependencies
30 | PACKAGE_MANAGEMENT = 'PACKAGE_MANAGEMENT',
31 | VERSIONING = 'VERSIONING',
32 |
33 | // Performance
34 | OPTIMIZATION = 'OPTIMIZATION',
35 | CACHING = 'CACHING',
36 |
37 | // Cross-cutting
38 | ACCESSIBILITY = 'ACCESSIBILITY',
39 | INTERNATIONALIZATION = 'INTERNATIONALIZATION',
40 | ERROR_HANDLING = 'ERROR_HANDLING'
41 | }
42 |
43 | export enum StrictnessLevel {
44 | REQUIRED = 'REQUIRED',
45 | RECOMMENDED = 'RECOMMENDED',
46 | OPTIONAL = 'OPTIONAL'
47 | }
48 |
49 | export interface Condition {
50 | factId: string;
51 | type: 'REQUIRES' | 'CONFLICTS_WITH';
52 | }
53 |
54 | export type ValidationType = 'MANUAL' | 'AUTOMATED' | 'URL_CHECK';
55 |
56 | export interface AcceptanceCriterion {
57 | id: string;
58 | description: string;
59 | validationType: ValidationType;
60 | validationScript?: string;
61 | }
62 |
63 | export interface FactData {
64 | content: string;
65 | strictness: StrictnessLevel;
66 | type: string;
67 | category: FactCategory;
68 | minVersion: string;
69 | maxVersion: string;
70 | conditions: Condition[];
71 | acceptanceCriteria: AcceptanceCriterion[];
72 | createdAt: string;
73 | updatedAt: string;
74 | }
75 |
76 | export interface StorageSearchResult extends FactData {
77 | id: string;
78 | applicable: boolean;
79 | }
80 |
81 | export interface ValidationResult {
82 | criterionId: string;
83 | passed: boolean;
84 | message?: string;
85 | }
86 |
87 | export interface StorageProvider {
88 | setFact(
89 | id: string,
90 | content: string,
91 | strictness: StrictnessLevel,
92 | type: string,
93 | category: FactCategory,
94 | minVersion: string,
95 | maxVersion: string,
96 | conditions: Condition[],
97 | acceptanceCriteria: AcceptanceCriterion[],
98 | contentEmbedding?: string
99 | ): Promise;
100 |
101 | getFact(id: string): Promise;
102 |
103 | searchFacts(options: {
104 | type?: string;
105 | strictness?: StrictnessLevel;
106 | version?: string;
107 | embedding?: string;
108 | similarityThreshold?: number;
109 | }): Promise;
110 |
111 | deleteFact(id: string): Promise;
112 | }
113 |
--------------------------------------------------------------------------------
/facts-server/src/validation.ts:
--------------------------------------------------------------------------------
1 | import { AcceptanceCriterion, ValidationResult } from './types.js';
2 |
3 | export class ValidationError extends Error {
4 | constructor(message: string) {
5 | super(message);
6 | this.name = 'ValidationError';
7 | }
8 | }
9 |
10 | export async function validateCriteria(
11 | content: string,
12 | criteria: AcceptanceCriterion[]
13 | ): Promise {
14 | const results: ValidationResult[] = [];
15 |
16 | for (const criterion of criteria) {
17 | if (criterion.validationType === 'MANUAL') {
18 | results.push({
19 | criterionId: criterion.id,
20 | passed: false,
21 | message: 'Manual validation required'
22 | });
23 | continue;
24 | }
25 |
26 | if (!criterion.validationScript) {
27 | throw new ValidationError(`Automated criterion ${criterion.id} has no validation script`);
28 | }
29 |
30 | try {
31 | // Create a function from the validation script
32 | const validationFn = new Function('content', criterion.validationScript + '\nreturn ' + criterion.validationScript.match(/function\s+(\w+)/)?.[1] + '(content);');
33 |
34 | const passed = await Promise.resolve(validationFn(content));
35 |
36 | results.push({
37 | criterionId: criterion.id,
38 | passed,
39 | message: passed ? 'Validation passed' : 'Validation failed'
40 | });
41 | } catch (error) {
42 | results.push({
43 | criterionId: criterion.id,
44 | passed: false,
45 | message: `Validation error: ${error instanceof Error ? error.message : 'Unknown error'}`
46 | });
47 | }
48 | }
49 |
50 | return results;
51 | }
52 |
53 | export function validateFactConditions(
54 | factConditions: Map,
55 | conditions: { factId: string; type: 'REQUIRES' | 'CONFLICTS_WITH' }[]
56 | ): boolean {
57 | for (const condition of conditions) {
58 | const factExists = factConditions.get(condition.factId) ?? false;
59 |
60 | if (condition.type === 'REQUIRES' && !factExists) {
61 | return false;
62 | }
63 |
64 | if (condition.type === 'CONFLICTS_WITH' && factExists) {
65 | return false;
66 | }
67 | }
68 |
69 | return true;
70 | }
71 |
--------------------------------------------------------------------------------
/facts-server/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "module": "ES2020",
5 | "moduleResolution": "node",
6 | "outDir": "./build",
7 | "rootDir": "./src",
8 | "strict": true,
9 | "esModuleInterop": true,
10 | "skipLibCheck": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "declaration": true
13 | },
14 | "include": [
15 | "src/**/*"
16 | ],
17 | "exclude": [
18 | "node_modules",
19 | "build"
20 | ]
21 | }
--------------------------------------------------------------------------------
/facts-ui/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.*
7 | .yarn/*
8 | !.yarn/patches
9 | !.yarn/plugins
10 | !.yarn/releases
11 | !.yarn/versions
12 |
13 | # testing
14 | /coverage
15 |
16 | # next.js
17 | /.next/
18 | /out/
19 |
20 | # production
21 | /build
22 |
23 | # misc
24 | .DS_Store
25 | *.pem
26 |
27 | # debug
28 | npm-debug.log*
29 | yarn-debug.log*
30 | yarn-error.log*
31 | .pnpm-debug.log*
32 |
33 | # env files (can opt-in for committing if needed)
34 | .env*
35 |
36 | # vercel
37 | .vercel
38 |
39 | # typescript
40 | *.tsbuildinfo
41 | next-env.d.ts
42 |
--------------------------------------------------------------------------------
/facts-ui/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | # or
12 | pnpm dev
13 | # or
14 | bun dev
15 | ```
16 |
17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18 |
19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20 |
21 | This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
22 |
23 | ## Learn More
24 |
25 | To learn more about Next.js, take a look at the following resources:
26 |
27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29 |
30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
31 |
32 | ## Deploy on Vercel
33 |
34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35 |
36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
37 |
--------------------------------------------------------------------------------
/facts-ui/app/api/mcp/route.ts:
--------------------------------------------------------------------------------
1 | import { Server } from "@modelcontextprotocol/sdk/server"
2 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio"
3 |
4 | export async function POST(request: Request) {
5 | try {
6 | const body = await request.json()
7 | const { server, tool, args } = body
8 |
9 | if (server !== "facts") {
10 | return Response.json({ error: "Invalid MCP server" }, { status: 400 })
11 | }
12 |
13 | const mcpServer = new Server(
14 | {
15 | name: "facts-server",
16 | version: "0.1.0",
17 | },
18 | {
19 | capabilities: {
20 | tools: {},
21 | },
22 | }
23 | )
24 |
25 | const transport = new StdioServerTransport()
26 | await mcpServer.connect(transport)
27 |
28 | const result = await mcpServer.callTool(tool, args)
29 | return Response.json(result)
30 | } catch (error) {
31 | console.error("MCP error:", error)
32 | return Response.json(
33 | { error: "Failed to process MCP request" },
34 | { status: 500 }
35 | )
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/facts-ui/app/facts/new/page.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from "@/components/ui/button"
2 | import Link from "next/link"
3 | import { NewFactForm } from "@/components/facts/new-fact-form"
4 |
5 | export default function NewFactPage() {
6 | return (
7 |
8 |
9 |
Add New Fact
10 |
13 |
14 |
15 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/facts-ui/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andrewhopper/hivemind/b82fb50a74b5eadef26a19cc1f69a178772b29d3/facts-ui/app/favicon.ico
--------------------------------------------------------------------------------
/facts-ui/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | :root {
7 | --background: 0 0% 100%;
8 | --foreground: 0 0% 3.9%;
9 |
10 | --card: 0 0% 100%;
11 | --card-foreground: 0 0% 3.9%;
12 |
13 | --popover: 0 0% 100%;
14 | --popover-foreground: 0 0% 3.9%;
15 |
16 | --primary: 0 0% 9%;
17 | --primary-foreground: 0 0% 98%;
18 |
19 | --secondary: 0 0% 96.1%;
20 | --secondary-foreground: 0 0% 9%;
21 |
22 | --muted: 0 0% 96.1%;
23 | --muted-foreground: 0 0% 45.1%;
24 |
25 | --accent: 0 0% 96.1%;
26 | --accent-foreground: 0 0% 9%;
27 |
28 | --destructive: 0 84.2% 60.2%;
29 | --destructive-foreground: 0 0% 98%;
30 |
31 | --border: 0 0% 89.8%;
32 | --input: 0 0% 89.8%;
33 | --ring: 0 0% 3.9%;
34 |
35 | --radius: 0.5rem;
36 | }
37 |
38 | .dark {
39 | --background: 0 0% 3.9%;
40 | --foreground: 0 0% 98%;
41 |
42 | --card: 0 0% 3.9%;
43 | --card-foreground: 0 0% 98%;
44 |
45 | --popover: 0 0% 3.9%;
46 | --popover-foreground: 0 0% 98%;
47 |
48 | --primary: 0 0% 98%;
49 | --primary-foreground: 0 0% 9%;
50 |
51 | --secondary: 0 0% 14.9%;
52 | --secondary-foreground: 0 0% 98%;
53 |
54 | --muted: 0 0% 14.9%;
55 | --muted-foreground: 0 0% 63.9%;
56 |
57 | --accent: 0 0% 14.9%;
58 | --accent-foreground: 0 0% 98%;
59 |
60 | --destructive: 0 62.8% 30.6%;
61 | --destructive-foreground: 0 0% 98%;
62 |
63 | --border: 0 0% 14.9%;
64 | --input: 0 0% 14.9%;
65 | --ring: 0 0% 83.1%;
66 | }
67 | }
68 |
69 | @layer base {
70 | * {
71 | @apply border-border;
72 | }
73 | body {
74 | @apply bg-background text-foreground;
75 | }
76 | }
77 |
78 |
79 |
80 | @layer base {
81 | * {
82 | @apply border-border outline-ring/50;
83 | }
84 | body {
85 | @apply bg-background text-foreground;
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/facts-ui/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import { Inter } from "next/font/google";
3 | import "./globals.css";
4 | import { Toaster } from "@/components/ui/sonner";
5 |
6 | const inter = Inter({ subsets: ["latin"] });
7 |
8 | export const metadata: Metadata = {
9 | title: "Facts Library",
10 | description: "A library of development facts and guidelines",
11 | };
12 |
13 | interface RootLayoutProps {
14 | children: React.ReactNode;
15 | }
16 |
17 | export default function RootLayout({ children }: RootLayoutProps) {
18 | return (
19 |
20 |
21 | {children}
22 |
23 |
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/facts-ui/app/page.tsx:
--------------------------------------------------------------------------------
1 | import { DataTable } from "@/components/facts/data-table"
2 | import { columns } from "@/components/facts/columns"
3 | import { getAllFacts } from "@/lib/api/facts"
4 | import { Button } from "@/components/ui/button"
5 | import Link from "next/link"
6 |
7 | export default async function Home() {
8 | const facts = await getAllFacts()
9 |
10 | return (
11 |
12 |
13 |
14 |
Facts Library
15 |
18 |
19 |
20 |
21 |
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/facts-ui/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "new-york",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.ts",
8 | "css": "app/globals.css",
9 | "baseColor": "neutral",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils",
16 | "ui": "@/components/ui",
17 | "lib": "@/lib",
18 | "hooks": "@/hooks"
19 | },
20 | "iconLibrary": "lucide"
21 | }
--------------------------------------------------------------------------------
/facts-ui/components/facts/columns.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { ColumnDef } from "@tanstack/react-table"
4 |
5 | export type Fact = {
6 | id: string
7 | title: string
8 | description: string
9 | source: string
10 | createdAt: Date
11 | }
12 |
13 | export const columns: ColumnDef[] = [
14 | {
15 | accessorKey: "title",
16 | header: "Title",
17 | },
18 | {
19 | accessorKey: "description",
20 | header: "Description",
21 | },
22 | {
23 | accessorKey: "source",
24 | header: "Source",
25 | },
26 | ]
--------------------------------------------------------------------------------
/facts-ui/components/facts/data-table.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import {
4 | ColumnDef,
5 | flexRender,
6 | getCoreRowModel,
7 | useReactTable,
8 | } from "@tanstack/react-table"
9 |
10 | import {
11 | Table,
12 | TableBody,
13 | TableCell,
14 | TableHead,
15 | TableHeader,
16 | TableRow,
17 | } from "@/components/ui/table"
18 |
19 | interface DataTableProps {
20 | columns: ColumnDef[]
21 | data: TData[]
22 | }
23 |
24 | export function DataTable({
25 | columns,
26 | data,
27 | }: DataTableProps) {
28 | const table = useReactTable({
29 | data,
30 | columns,
31 | getCoreRowModel: getCoreRowModel(),
32 | })
33 |
34 | return (
35 |
36 |
37 |
38 | {table.getHeaderGroups().map((headerGroup) => (
39 |
40 | {headerGroup.headers.map((header) => (
41 |
42 | {header.isPlaceholder
43 | ? null
44 | : flexRender(
45 | header.column.columnDef.header,
46 | header.getContext()
47 | )}
48 |
49 | ))}
50 |
51 | ))}
52 |
53 |
54 | {table.getRowModel().rows?.length ? (
55 | table.getRowModel().rows.map((row) => (
56 |
57 | {row.getVisibleCells().map((cell) => (
58 |
59 | {flexRender(cell.column.columnDef.cell, cell.getContext())}
60 |
61 | ))}
62 |
63 | ))
64 | ) : (
65 |
66 |
67 | No results.
68 |
69 |
70 | )}
71 |
72 |
73 |
74 | )
75 | }
--------------------------------------------------------------------------------
/facts-ui/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
9 | {
10 | variants: {
11 | variant: {
12 | default:
13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90",
14 | destructive:
15 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
16 | outline:
17 | "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
18 | secondary:
19 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
20 | ghost: "hover:bg-accent hover:text-accent-foreground",
21 | link: "text-primary underline-offset-4 hover:underline",
22 | },
23 | size: {
24 | default: "h-9 px-4 py-2",
25 | sm: "h-8 rounded-md px-3 text-xs",
26 | lg: "h-10 rounded-md px-8",
27 | icon: "h-9 w-9",
28 | },
29 | },
30 | defaultVariants: {
31 | variant: "default",
32 | size: "default",
33 | },
34 | }
35 | )
36 |
37 | export interface ButtonProps
38 | extends React.ButtonHTMLAttributes,
39 | VariantProps {
40 | asChild?: boolean
41 | }
42 |
43 | const Button = React.forwardRef(
44 | ({ className, variant, size, asChild = false, ...props }, ref) => {
45 | const Comp = asChild ? Slot : "button"
46 | return (
47 |
52 | )
53 | }
54 | )
55 | Button.displayName = "Button"
56 |
57 | export { Button, buttonVariants }
58 |
--------------------------------------------------------------------------------
/facts-ui/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import { dirname } from "path";
2 | import { fileURLToPath } from "url";
3 | import { FlatCompat } from "@eslint/eslintrc";
4 |
5 | const __filename = fileURLToPath(import.meta.url);
6 | const __dirname = dirname(__filename);
7 |
8 | const compat = new FlatCompat({
9 | baseDirectory: __dirname,
10 | });
11 |
12 | const eslintConfig = [
13 | ...compat.extends("next/core-web-vitals", "next/typescript"),
14 | ];
15 |
16 | export default eslintConfig;
17 |
--------------------------------------------------------------------------------
/facts-ui/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "@/*": [
6 | "./*"
7 | ]
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/facts-ui/lib/api/facts.ts:
--------------------------------------------------------------------------------
1 | import prisma from '@/lib/prisma'
2 |
3 | export async function getAllFacts() {
4 | const facts = await prisma.fact.findMany({
5 | orderBy: {
6 | createdAt: 'desc',
7 | },
8 | })
9 | return facts
10 | }
--------------------------------------------------------------------------------
/facts-ui/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
6 |
--------------------------------------------------------------------------------
/facts-ui/next.config.ts:
--------------------------------------------------------------------------------
1 | import type { NextConfig } from "next";
2 |
3 | const nextConfig: NextConfig = {
4 | /* config options here */
5 | };
6 |
7 | export default nextConfig;
8 |
--------------------------------------------------------------------------------
/facts-ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "facts-ui",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev --turbopack",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@hookform/resolvers": "^4.1.1",
13 | "@modelcontextprotocol/sdk": "^1.5.0",
14 | "@prisma/client": "^6.4.1",
15 | "@radix-ui/react-dialog": "^1.1.6",
16 | "@radix-ui/react-dropdown-menu": "^2.1.6",
17 | "@radix-ui/react-label": "^2.1.2",
18 | "@radix-ui/react-popover": "^1.1.6",
19 | "@radix-ui/react-scroll-area": "^1.2.3",
20 | "@radix-ui/react-select": "^2.1.6",
21 | "@radix-ui/react-separator": "^1.1.2",
22 | "@radix-ui/react-slot": "^1.1.2",
23 | "@radix-ui/react-tabs": "^1.1.3",
24 | "@tanstack/react-table": "^8.21.2",
25 | "class-variance-authority": "^0.7.1",
26 | "clsx": "^2.1.1",
27 | "lucide-react": "^0.475.0",
28 | "next": "15.1.7",
29 | "next-themes": "^0.4.4",
30 | "prisma": "^6.4.1",
31 | "react": "^19.0.0",
32 | "react-dom": "^19.0.0",
33 | "react-hook-form": "^7.54.2",
34 | "sonner": "^2.0.1",
35 | "tailwind-merge": "^3.0.2",
36 | "tailwindcss-animate": "^1.0.7",
37 | "zod": "^3.24.2"
38 | },
39 | "devDependencies": {
40 | "@eslint/eslintrc": "^3",
41 | "@types/node": "^20",
42 | "@types/react": "^19",
43 | "@types/react-dom": "^19",
44 | "eslint": "^9",
45 | "eslint-config-next": "15.1.7",
46 | "postcss": "^8",
47 | "tailwindcss": "^3.4.1",
48 | "typescript": "^5"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/facts-ui/path/to/file:
--------------------------------------------------------------------------------
1 | import { columns } from '../../components/facts/columns'
--------------------------------------------------------------------------------
/facts-ui/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('postcss-load-config').Config} */
2 | const config = {
3 | plugins: {
4 | tailwindcss: {},
5 | },
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/facts-ui/prisma/schema.prisma:
--------------------------------------------------------------------------------
1 | datasource db {
2 | provider = "sqlite"
3 | url = "../../facts-server/prisma/facts.db"
4 | }
5 |
6 | generator client {
7 | provider = "prisma-client-js"
8 | }
9 |
10 | model Fact {
11 | id String @id
12 | content String
13 | strictness String // Will map to StrictnessLevel enum
14 | type String
15 | category String // Will map to FactCategory enum
16 | minVersion String
17 | maxVersion String
18 | createdAt DateTime @default(now())
19 | updatedAt DateTime @updatedAt
20 | applicable Boolean @default(true)
21 | content_embedding String?
22 |
23 | // Relations
24 | conditions Condition[]
25 | acceptanceCriteria AcceptanceCriterion[]
26 | }
27 |
28 | model Condition {
29 | id String @id @default(cuid())
30 | factId String
31 | type String // REQUIRES or CONFLICTS_WITH
32 | targetId String // The ID of the fact this condition references
33 | fact Fact @relation(fields: [factId], references: [id])
34 | }
35 |
36 | model AcceptanceCriterion {
37 | id String @id @default(cuid())
38 | factId String
39 | description String
40 | validationType String // MANUAL or AUTOMATED
41 | validationScript String?
42 | fact Fact @relation(fields: [factId], references: [id])
43 | }
44 |
--------------------------------------------------------------------------------
/facts-ui/public/file.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/facts-ui/public/globe.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/facts-ui/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/facts-ui/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/facts-ui/public/window.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/facts-ui/src/components/facts/columns.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { ColumnDef } from "@tanstack/react-table"
4 | import { Fact, StrictnessLevel } from "@/lib/types/facts"
5 | import { Badge } from "@/components/ui/badge"
6 | import { Button } from "@/components/ui/button"
7 | import { MoreHorizontal } from "lucide-react"
8 | import {
9 | DropdownMenu,
10 | DropdownMenuContent,
11 | DropdownMenuItem,
12 | DropdownMenuLabel,
13 | DropdownMenuSeparator,
14 | DropdownMenuTrigger,
15 | } from "@/components/ui/dropdown-menu"
16 | import Link from "next/link"
17 |
18 | export const columns: ColumnDef[] = [
19 | {
20 | accessorKey: "id",
21 | header: "ID",
22 | },
23 | {
24 | accessorKey: "content",
25 | header: "Content",
26 | },
27 | {
28 | accessorKey: "type",
29 | header: "Type",
30 | },
31 | {
32 | accessorKey: "category",
33 | header: "Category",
34 | },
35 | {
36 | accessorKey: "strictness",
37 | header: "Strictness",
38 | cell: ({ row }) => {
39 | const strictness = row.getValue("strictness") as StrictnessLevel
40 | return (
41 |
46 | {strictness}
47 |
48 | )
49 | },
50 | },
51 | {
52 | id: "actions",
53 | cell: ({ row }) => {
54 | const fact = row.original
55 |
56 | return (
57 |
58 |
59 |
63 |
64 |
65 | Actions
66 |
67 | View Details
68 |
69 |
70 |
71 | Edit
72 |
73 |
74 |
75 | )
76 | },
77 | },
78 | ]
79 |
--------------------------------------------------------------------------------
/facts-ui/src/components/facts/data-table.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import {
4 | ColumnDef,
5 | flexRender,
6 | getCoreRowModel,
7 | useReactTable,
8 | getPaginationRowModel,
9 | getSortedRowModel,
10 | SortingState,
11 | } from "@tanstack/react-table"
12 | import {
13 | Table,
14 | TableBody,
15 | TableCell,
16 | TableHead,
17 | TableHeader,
18 | TableRow,
19 | } from "@/components/ui/table"
20 | import { Button } from "@/components/ui/button"
21 | import { useState } from "react"
22 |
23 | interface DataTableProps {
24 | columns: ColumnDef[]
25 | data: TData[]
26 | }
27 |
28 | export function DataTable({
29 | columns,
30 | data,
31 | }: DataTableProps) {
32 | const [sorting, setSorting] = useState([])
33 |
34 | const table = useReactTable({
35 | data,
36 | columns,
37 | getCoreRowModel: getCoreRowModel(),
38 | getPaginationRowModel: getPaginationRowModel(),
39 | getSortedRowModel: getSortedRowModel(),
40 | onSortingChange: setSorting,
41 | state: {
42 | sorting,
43 | },
44 | })
45 |
46 | return (
47 |
48 |
49 |
50 |
51 | {table.getHeaderGroups().map((headerGroup) => (
52 |
53 | {headerGroup.headers.map((header) => {
54 | return (
55 |
56 | {header.isPlaceholder
57 | ? null
58 | : flexRender(
59 | header.column.columnDef.header,
60 | header.getContext()
61 | )}
62 |
63 | )
64 | })}
65 |
66 | ))}
67 |
68 |
69 | {table.getRowModel().rows?.length ? (
70 | table.getRowModel().rows.map((row) => (
71 |
75 | {row.getVisibleCells().map((cell) => (
76 |
77 | {flexRender(
78 | cell.column.columnDef.cell,
79 | cell.getContext()
80 | )}
81 |
82 | ))}
83 |
84 | ))
85 | ) : (
86 |
87 |
91 | No results.
92 |
93 |
94 | )}
95 |
96 |
97 |
98 |
99 |
107 |
115 |
116 |
117 | )
118 | }
119 |
--------------------------------------------------------------------------------
/facts-ui/src/components/facts/new-fact-form.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react"
2 | import { useRouter } from "next/navigation"
3 | import { zodResolver } from "@hookform/resolvers/zod"
4 | import { useForm } from "react-hook-form"
5 | import { toast } from "sonner"
6 | import { Button } from "@/components/ui/button"
7 | import { Input } from "@/components/ui/input"
8 | import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
9 | import { Textarea } from "@/components/ui/textarea"
10 | import { FactCategory, StrictnessLevel, factSchema, type FactFormData } from "@/lib/types/facts"
11 | import { createFact } from "@/lib/api/facts"
12 |
13 | export function NewFactForm() {
14 | const router = useRouter()
15 | const [isSubmitting, setIsSubmitting] = useState(false)
16 |
17 | const form = useForm({
18 | resolver: zodResolver(factSchema),
19 | defaultValues: {
20 | conditions: [],
21 | acceptanceCriteria: []
22 | }
23 | })
24 |
25 | async function onSubmit(data: FactFormData) {
26 | try {
27 | setIsSubmitting(true)
28 | await createFact(data)
29 | toast.success("Fact created successfully")
30 | router.push("/")
31 | } catch (error) {
32 | toast.error("Failed to create fact")
33 | console.error(error)
34 | } finally {
35 | setIsSubmitting(false)
36 | }
37 | }
38 |
39 | return (
40 |
161 | )
162 | }
163 |
--------------------------------------------------------------------------------
/facts-ui/src/components/ui/badge.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const badgeVariants = cva(
7 | "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
8 | {
9 | variants: {
10 | variant: {
11 | default:
12 | "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
13 | secondary:
14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15 | destructive:
16 | "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
17 | outline: "text-foreground",
18 | },
19 | },
20 | defaultVariants: {
21 | variant: "default",
22 | },
23 | }
24 | )
25 |
26 | export interface BadgeProps
27 | extends React.HTMLAttributes,
28 | VariantProps { }
29 |
30 | function Badge({ className, variant, ...props }: BadgeProps) {
31 | return (
32 |
33 | )
34 | }
35 |
36 | export { Badge, badgeVariants }
37 |
--------------------------------------------------------------------------------
/facts-ui/src/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
9 | {
10 | variants: {
11 | variant: {
12 | default: "bg-primary text-primary-foreground hover:bg-primary/90",
13 | destructive:
14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15 | outline:
16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17 | secondary:
18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19 | ghost: "hover:bg-accent hover:text-accent-foreground",
20 | link: "text-primary underline-offset-4 hover:underline",
21 | },
22 | size: {
23 | default: "h-10 px-4 py-2",
24 | sm: "h-9 rounded-md px-3",
25 | lg: "h-11 rounded-md px-8",
26 | icon: "h-10 w-10",
27 | },
28 | },
29 | defaultVariants: {
30 | variant: "default",
31 | size: "default",
32 | },
33 | }
34 | )
35 |
36 | export interface ButtonProps
37 | extends React.ButtonHTMLAttributes,
38 | VariantProps {
39 | asChild?: boolean
40 | }
41 |
42 | const Button = React.forwardRef(
43 | ({ className, variant, size, asChild = false, ...props }, ref) => {
44 | const Comp = asChild ? Slot : "button"
45 | return (
46 |
51 | )
52 | }
53 | )
54 | Button.displayName = "Button"
55 |
56 | export { Button, buttonVariants }
57 |
--------------------------------------------------------------------------------
/facts-ui/src/components/ui/dropdown-menu.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
3 | import { Check, ChevronRight, Circle } from "lucide-react"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const DropdownMenu = DropdownMenuPrimitive.Root
8 |
9 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
10 |
11 | const DropdownMenuGroup = DropdownMenuPrimitive.Group
12 |
13 | const DropdownMenuPortal = DropdownMenuPrimitive.Portal
14 |
15 | const DropdownMenuSub = DropdownMenuPrimitive.Sub
16 |
17 | const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
18 |
19 | const DropdownMenuSubTrigger = React.forwardRef<
20 | React.ElementRef,
21 | React.ComponentPropsWithoutRef & {
22 | inset?: boolean
23 | }
24 | >(({ className, inset, children, ...props }, ref) => (
25 |
34 | {children}
35 |
36 |
37 | ))
38 | DropdownMenuSubTrigger.displayName =
39 | DropdownMenuPrimitive.SubTrigger.displayName
40 |
41 | const DropdownMenuSubContent = React.forwardRef<
42 | React.ElementRef,
43 | React.ComponentPropsWithoutRef
44 | >(({ className, ...props }, ref) => (
45 |
53 | ))
54 | DropdownMenuSubContent.displayName =
55 | DropdownMenuPrimitive.SubContent.displayName
56 |
57 | const DropdownMenuContent = React.forwardRef<
58 | React.ElementRef,
59 | React.ComponentPropsWithoutRef
60 | >(({ className, sideOffset = 4, ...props }, ref) => (
61 |
62 |
71 |
72 | ))
73 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
74 |
75 | const DropdownMenuItem = React.forwardRef<
76 | React.ElementRef,
77 | React.ComponentPropsWithoutRef & {
78 | inset?: boolean
79 | }
80 | >(({ className, inset, ...props }, ref) => (
81 |
90 | ))
91 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
92 |
93 | const DropdownMenuCheckboxItem = React.forwardRef<
94 | React.ElementRef,
95 | React.ComponentPropsWithoutRef
96 | >(({ className, children, checked, ...props }, ref) => (
97 |
106 |
107 |
108 |
109 |
110 |
111 | {children}
112 |
113 | ))
114 | DropdownMenuCheckboxItem.displayName =
115 | DropdownMenuPrimitive.CheckboxItem.displayName
116 |
117 | const DropdownMenuRadioItem = React.forwardRef<
118 | React.ElementRef,
119 | React.ComponentPropsWithoutRef
120 | >(({ className, children, ...props }, ref) => (
121 |
129 |
130 |
131 |
132 |
133 |
134 | {children}
135 |
136 | ))
137 | DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
138 |
139 | const DropdownMenuLabel = React.forwardRef<
140 | React.ElementRef,
141 | React.ComponentPropsWithoutRef & {
142 | inset?: boolean
143 | }
144 | >(({ className, inset, ...props }, ref) => (
145 |
154 | ))
155 | DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
156 |
157 | const DropdownMenuSeparator = React.forwardRef<
158 | React.ElementRef,
159 | React.ComponentPropsWithoutRef
160 | >(({ className, ...props }, ref) => (
161 |
166 | ))
167 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
168 |
169 | const DropdownMenuShortcut = ({
170 | className,
171 | ...props
172 | }: React.HTMLAttributes) => {
173 | return (
174 |
178 | )
179 | }
180 | DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
181 |
182 | export {
183 | DropdownMenu,
184 | DropdownMenuTrigger,
185 | DropdownMenuContent,
186 | DropdownMenuItem,
187 | DropdownMenuCheckboxItem,
188 | DropdownMenuRadioItem,
189 | DropdownMenuLabel,
190 | DropdownMenuSeparator,
191 | DropdownMenuShortcut,
192 | DropdownMenuGroup,
193 | DropdownMenuPortal,
194 | DropdownMenuSub,
195 | DropdownMenuSubContent,
196 | DropdownMenuSubTrigger,
197 | DropdownMenuRadioGroup,
198 | }
199 |
--------------------------------------------------------------------------------
/facts-ui/src/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export type InputProps = React.InputHTMLAttributes
6 |
7 | const Input = React.forwardRef(
8 | ({ className, type, ...props }, ref) => {
9 | return (
10 |
19 | )
20 | }
21 | )
22 | Input.displayName = "Input"
23 |
24 | export { Input }
25 |
--------------------------------------------------------------------------------
/facts-ui/src/components/ui/select.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as SelectPrimitive from "@radix-ui/react-select"
3 | import { Check, ChevronDown } from "lucide-react"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const Select = SelectPrimitive.Root
8 |
9 | const SelectGroup = SelectPrimitive.Group
10 |
11 | const SelectValue = SelectPrimitive.Value
12 |
13 | const SelectTrigger = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef
16 | >(({ className, children, ...props }, ref) => (
17 |
25 | {children}
26 |
27 |
28 | ))
29 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
30 |
31 | const SelectContent = React.forwardRef<
32 | React.ElementRef,
33 | React.ComponentPropsWithoutRef
34 | >(({ className, children, position = "popper", ...props }, ref) => (
35 |
36 |
47 |
54 | {children}
55 |
56 |
57 |
58 | ))
59 | SelectContent.displayName = SelectPrimitive.Content.displayName
60 |
61 | const SelectItem = React.forwardRef<
62 | React.ElementRef,
63 | React.ComponentPropsWithoutRef
64 | >(({ className, children, ...props }, ref) => (
65 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | {children}
80 |
81 | ))
82 | SelectItem.displayName = SelectPrimitive.Item.displayName
83 |
84 | export {
85 | Select,
86 | SelectGroup,
87 | SelectValue,
88 | SelectTrigger,
89 | SelectContent,
90 | SelectItem,
91 | }
92 |
--------------------------------------------------------------------------------
/facts-ui/src/components/ui/sonner.tsx:
--------------------------------------------------------------------------------
1 | import { Toaster as Sonner } from "sonner"
2 |
3 | type ToasterProps = React.ComponentProps
4 |
5 | function Toaster({ ...props }: ToasterProps) {
6 | return (
7 |
22 | )
23 | }
24 |
25 | export { Toaster }
26 |
--------------------------------------------------------------------------------
/facts-ui/src/components/ui/table.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Table = React.forwardRef<
6 | HTMLTableElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
16 | ))
17 | Table.displayName = "Table"
18 |
19 | const TableHeader = React.forwardRef<
20 | HTMLTableSectionElement,
21 | React.HTMLAttributes
22 | >(({ className, ...props }, ref) => (
23 |
24 | ))
25 | TableHeader.displayName = "TableHeader"
26 |
27 | const TableBody = React.forwardRef<
28 | HTMLTableSectionElement,
29 | React.HTMLAttributes
30 | >(({ className, ...props }, ref) => (
31 |
36 | ))
37 | TableBody.displayName = "TableBody"
38 |
39 | const TableFooter = React.forwardRef<
40 | HTMLTableSectionElement,
41 | React.HTMLAttributes
42 | >(({ className, ...props }, ref) => (
43 | tr]:last:border-b-0",
47 | className
48 | )}
49 | {...props}
50 | />
51 | ))
52 | TableFooter.displayName = "TableFooter"
53 |
54 | const TableRow = React.forwardRef<
55 | HTMLTableRowElement,
56 | React.HTMLAttributes
57 | >(({ className, ...props }, ref) => (
58 |
66 | ))
67 | TableRow.displayName = "TableRow"
68 |
69 | const TableHead = React.forwardRef<
70 | HTMLTableCellElement,
71 | React.ThHTMLAttributes
72 | >(({ className, ...props }, ref) => (
73 | |
81 | ))
82 | TableHead.displayName = "TableHead"
83 |
84 | const TableCell = React.forwardRef<
85 | HTMLTableCellElement,
86 | React.TdHTMLAttributes
87 | >(({ className, ...props }, ref) => (
88 | |
93 | ))
94 | TableCell.displayName = "TableCell"
95 |
96 | const TableCaption = React.forwardRef<
97 | HTMLTableCaptionElement,
98 | React.HTMLAttributes
99 | >(({ className, ...props }, ref) => (
100 |
105 | ))
106 | TableCaption.displayName = "TableCaption"
107 |
108 | export {
109 | Table,
110 | TableHeader,
111 | TableBody,
112 | TableFooter,
113 | TableHead,
114 | TableRow,
115 | TableCell,
116 | TableCaption,
117 | }
118 |
--------------------------------------------------------------------------------
/facts-ui/src/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export type TextareaProps = React.TextareaHTMLAttributes
6 |
7 | const Textarea = React.forwardRef(
8 | ({ className, ...props }, ref) => {
9 | return (
10 |
18 | )
19 | }
20 | )
21 | Textarea.displayName = "Textarea"
22 |
23 | export { Textarea }
24 |
--------------------------------------------------------------------------------
/facts-ui/src/lib/api/facts.ts:
--------------------------------------------------------------------------------
1 | import { Fact, FactFormData } from "../types/facts"
2 | import prisma from "@/lib/prisma"
3 |
4 | export async function getAllFacts(): Promise {
5 | return prisma.fact.findMany({
6 | include: {
7 | conditions: true,
8 | acceptanceCriteria: true
9 | }
10 | })
11 | }
12 |
13 | export async function getFact(id: string): Promise {
14 | return prisma.fact.findUnique({
15 | where: { id },
16 | include: {
17 | conditions: true,
18 | acceptanceCriteria: true
19 | }
20 | })
21 | }
22 |
23 | export async function searchFacts(params: {
24 | type?: string
25 | strictness?: string
26 | version?: string
27 | }): Promise {
28 | const { type, strictness, version } = params
29 | return prisma.fact.findMany({
30 | where: {
31 | ...(type && { type }),
32 | ...(strictness && { strictness }),
33 | ...(version && {
34 | OR: [
35 | { minVersion: { lte: version } },
36 | { minVersion: null }
37 | ],
38 | AND: [
39 | { maxVersion: { gte: version } },
40 | { maxVersion: { not: null } }
41 | ]
42 | })
43 | },
44 | include: {
45 | conditions: true,
46 | acceptanceCriteria: true
47 | }
48 | })
49 | }
50 |
51 | export async function createFact(data: FactFormData): Promise {
52 | await prisma.fact.create({
53 | data: {
54 | ...data,
55 | conditions: {
56 | create: data.conditions || []
57 | },
58 | acceptanceCriteria: {
59 | create: data.acceptanceCriteria || []
60 | }
61 | }
62 | })
63 | }
64 |
65 | export async function updateFact(id: string, data: FactFormData): Promise {
66 | await prisma.fact.update({
67 | where: { id },
68 | data: {
69 | ...data,
70 | conditions: {
71 | deleteMany: {},
72 | create: data.conditions || []
73 | },
74 | acceptanceCriteria: {
75 | deleteMany: {},
76 | create: data.acceptanceCriteria || []
77 | }
78 | }
79 | })
80 | }
81 |
82 | export async function validateFact(factId: string, content: string): Promise<{
83 | passed: boolean
84 | message?: string
85 | }> {
86 | const fact = await prisma.fact.findUnique({
87 | where: { id: factId },
88 | include: { acceptanceCriteria: true }
89 | })
90 |
91 | if (!fact) {
92 | return {
93 | passed: false,
94 | message: "Fact not found"
95 | }
96 | }
97 |
98 | // For now, just check if content exists
99 | return {
100 | passed: !!content,
101 | message: content ? "Content provided" : "Content is required"
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/facts-ui/src/lib/prisma.ts:
--------------------------------------------------------------------------------
1 | import { PrismaClient } from '@prisma/client'
2 |
3 | const globalForPrisma = globalThis as unknown as {
4 | prisma: PrismaClient | undefined
5 | }
6 |
7 | export const prisma = globalForPrisma.prisma ?? new PrismaClient()
8 |
9 | if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
10 |
11 | export default prisma
12 |
--------------------------------------------------------------------------------
/facts-ui/src/lib/types/facts.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod"
2 |
3 | export enum FactCategory {
4 | FRONTEND = "FRONTEND",
5 | BACKEND = "BACKEND",
6 | DATABASE = "DATABASE",
7 | FULL_STACK = "FULL_STACK",
8 | DESIGN_PATTERN = "DESIGN_PATTERN",
9 | ARCHITECTURE_PATTERN = "ARCHITECTURE_PATTERN",
10 | TESTING_PATTERN = "TESTING_PATTERN",
11 | NAMING_CONVENTION = "NAMING_CONVENTION",
12 | PROJECT_STRUCTURE = "PROJECT_STRUCTURE",
13 | CODE_STYLE = "CODE_STYLE",
14 | DEPLOYMENT = "DEPLOYMENT",
15 | CI_CD = "CI_CD",
16 | MONITORING = "MONITORING",
17 | SECURITY = "SECURITY",
18 | GIT_WORKFLOW = "GIT_WORKFLOW",
19 | CODE_REVIEW = "CODE_REVIEW",
20 | DOCUMENTATION = "DOCUMENTATION",
21 | PACKAGE_MANAGEMENT = "PACKAGE_MANAGEMENT",
22 | VERSIONING = "VERSIONING",
23 | OPTIMIZATION = "OPTIMIZATION",
24 | CACHING = "CACHING",
25 | ACCESSIBILITY = "ACCESSIBILITY",
26 | INTERNATIONALIZATION = "INTERNATIONALIZATION",
27 | ERROR_HANDLING = "ERROR_HANDLING"
28 | }
29 |
30 | export enum StrictnessLevel {
31 | REQUIRED = "REQUIRED",
32 | RECOMMENDED = "RECOMMENDED",
33 | OPTIONAL = "OPTIONAL"
34 | }
35 |
36 | export interface Condition {
37 | factId: string
38 | type: "REQUIRES" | "CONFLICTS_WITH"
39 | }
40 |
41 | export interface AcceptanceCriterion {
42 | id: string
43 | description: string
44 | validationType: "MANUAL" | "AUTOMATED"
45 | validationScript?: string
46 | }
47 |
48 | export interface Fact {
49 | id: string
50 | content: string
51 | strictness: StrictnessLevel
52 | type: string
53 | category: FactCategory
54 | minVersion: string
55 | maxVersion: string
56 | conditions: Condition[]
57 | acceptanceCriteria: AcceptanceCriterion[]
58 | createdAt: string
59 | updatedAt: string
60 | applicable: boolean
61 | }
62 |
63 | export const factSchema = z.object({
64 | id: z.string().min(1, "ID is required"),
65 | content: z.string().min(1, "Content is required"),
66 | strictness: z.nativeEnum(StrictnessLevel),
67 | type: z.string().min(1, "Type is required"),
68 | category: z.nativeEnum(FactCategory),
69 | minVersion: z.string().min(1, "Minimum version is required"),
70 | maxVersion: z.string().min(1, "Maximum version is required"),
71 | conditions: z.array(z.object({
72 | factId: z.string().min(1, "Fact ID is required"),
73 | type: z.enum(["REQUIRES", "CONFLICTS_WITH"])
74 | })),
75 | acceptanceCriteria: z.array(z.object({
76 | id: z.string().min(1, "Criterion ID is required"),
77 | description: z.string().min(1, "Description is required"),
78 | validationType: z.enum(["MANUAL", "AUTOMATED"]),
79 | validationScript: z.string().optional()
80 | }))
81 | })
82 |
83 | export type FactFormData = z.infer
84 |
--------------------------------------------------------------------------------
/facts-ui/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx"
2 | import { twMerge } from "tailwind-merge"
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/facts-ui/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "tailwindcss";
2 |
3 | export default {
4 | darkMode: ["class"],
5 | content: [
6 | "./pages/**/*.{js,ts,jsx,tsx,mdx}",
7 | "./components/**/*.{js,ts,jsx,tsx,mdx}",
8 | "./app/**/*.{js,ts,jsx,tsx,mdx}",
9 | ],
10 | theme: {
11 | extend: {
12 | colors: {
13 | background: 'hsl(var(--background))',
14 | foreground: 'hsl(var(--foreground))',
15 | card: {
16 | DEFAULT: 'hsl(var(--card))',
17 | foreground: 'hsl(var(--card-foreground))'
18 | },
19 | popover: {
20 | DEFAULT: 'hsl(var(--popover))',
21 | foreground: 'hsl(var(--popover-foreground))'
22 | },
23 | primary: {
24 | DEFAULT: 'hsl(var(--primary))',
25 | foreground: 'hsl(var(--primary-foreground))'
26 | },
27 | secondary: {
28 | DEFAULT: 'hsl(var(--secondary))',
29 | foreground: 'hsl(var(--secondary-foreground))'
30 | },
31 | muted: {
32 | DEFAULT: 'hsl(var(--muted))',
33 | foreground: 'hsl(var(--muted-foreground))'
34 | },
35 | accent: {
36 | DEFAULT: 'hsl(var(--accent))',
37 | foreground: 'hsl(var(--accent-foreground))'
38 | },
39 | destructive: {
40 | DEFAULT: 'hsl(var(--destructive))',
41 | foreground: 'hsl(var(--destructive-foreground))'
42 | },
43 | border: 'hsl(var(--border))',
44 | input: 'hsl(var(--input))',
45 | ring: 'hsl(var(--ring))',
46 | chart: {
47 | '1': 'hsl(var(--chart-1))',
48 | '2': 'hsl(var(--chart-2))',
49 | '3': 'hsl(var(--chart-3))',
50 | '4': 'hsl(var(--chart-4))',
51 | '5': 'hsl(var(--chart-5))'
52 | }
53 | },
54 | borderRadius: {
55 | lg: 'var(--radius)',
56 | md: 'calc(var(--radius) - 2px)',
57 | sm: 'calc(var(--radius) - 4px)'
58 | }
59 | }
60 | },
61 | plugins: [require("tailwindcss-animate")],
62 | } satisfies Config;
63 |
--------------------------------------------------------------------------------
/facts-ui/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "strict": true,
12 | "noEmit": true,
13 | "esModuleInterop": true,
14 | "module": "esnext",
15 | "moduleResolution": "bundler",
16 | "resolveJsonModule": true,
17 | "isolatedModules": true,
18 | "jsx": "preserve",
19 | "incremental": true,
20 | "plugins": [
21 | {
22 | "name": "next"
23 | }
24 | ],
25 | "paths": {
26 | "@/*": [
27 | "./src/*"
28 | ]
29 | },
30 | "baseUrl": "."
31 | },
32 | "include": [
33 | "next-env.d.ts",
34 | "**/*.ts",
35 | "**/*.tsx",
36 | ".next/types/**/*.ts"
37 | ],
38 | "exclude": [
39 | "node_modules"
40 | ]
41 | }
--------------------------------------------------------------------------------
/hivemind-ui/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
6 |
--------------------------------------------------------------------------------
/hivemind/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "repository": {
3 | "type": "git",
4 | "url": "git+https://github.com/andrewhopper/hivemind.git"
5 | },
6 | "bugs": {
7 | "url": "https://github.com/andrewhopper/hivemind/issues"
8 | },
9 | "homepage": "https://github.com/andrewhopper/hivemind#readme"
10 | }
--------------------------------------------------------------------------------
/index.ts:
--------------------------------------------------------------------------------
1 | import express, { Request, Response } from 'express';
2 | import { promises as fs } from 'fs';
3 |
4 | // Enhanced type definitions to include acceptance criteria
5 | interface Condition {
6 | factId: string;
7 | type: 'REQUIRES' | 'CONFLICTS_WITH';
8 | }
9 |
10 | interface AcceptanceCriterion {
11 | id: string;
12 | description: string;
13 | validationType: 'MANUAL' | 'AUTOMATED';
14 | validationScript?: string; // For automated validation
15 | }
16 |
17 | interface FactData {
18 | content: string;
19 | strictness: StrictnessLevel;
20 | type: string;
21 | minVersion: string;
22 | maxVersion: string;
23 | conditions: Condition[];
24 | acceptanceCriteria: AcceptanceCriterion[];
25 | createdAt: string;
26 | updatedAt: string;
27 | }
28 |
29 | interface StorageSearchResult extends FactData {
30 | id: string;
31 | applicable: boolean;
32 | }
33 |
34 | // Update request types
35 | interface SetFactRequest extends Request {
36 | body: {
37 | fact: string;
38 | strictness?: StrictnessLevel;
39 | type: string;
40 | minVersion: string;
41 | maxVersion: string;
42 | conditions: Condition[];
43 | acceptanceCriteria: AcceptanceCriterion[];
44 | }
45 | }
46 |
47 | // ... (previous code for StorageProvider and implementations remains the same) ...
48 |
49 | // Example usage with acceptance criteria
50 | async function setupExampleFacts(server: MCPFactsServer) {
51 | const facts = [
52 | {
53 | id: "documentation-diagrams",
54 | fact: "Always create both entity relationship and sequence diagrams when implementing new features.",
55 | type: "documentation",
56 | strictness: StrictnessLevel.REQUIRED,
57 | minVersion: "1.0.0",
58 | maxVersion: "2.0.0",
59 | conditions: [],
60 | acceptanceCriteria: [
61 | {
62 | id: "seq-diagram-exists",
63 | description: "Mermaid sequence diagram exists in documentation",
64 | validationType: "MANUAL"
65 | },
66 | {
67 | id: "entity-diagram-exists",
68 | description: "Mermaid entity diagram exists in documentation",
69 | validationType: "MANUAL"
70 | },
71 | {
72 | id: "unique-node-ids",
73 | description: "Each node in diagrams has a unique identifier",
74 | validationType: "AUTOMATED",
75 | validationScript: `
76 | function validateNodeIds(diagramContent) {
77 | const nodeMatches = diagramContent.match(/participant \w+|actor \w+/g);
78 | if (!nodeMatches) return true;
79 |
80 | const nodes = nodeMatches.map(match => match.split(' ')[1]);
81 | const uniqueNodes = new Set(nodes);
82 | return nodes.length === uniqueNodes.size;
83 | }
84 | `
85 | },
86 | {
87 | id: "diagram-title",
88 | description: "Diagram includes a title section",
89 | validationType: "AUTOMATED",
90 | validationScript: `
91 | function validateTitle(diagramContent) {
92 | return diagramContent.includes('title:') ||
93 | diagramContent.includes('---\ntitle:');
94 | }
95 | `
96 | }
97 | ]
98 | },
99 | {
100 | id: "api-documentation",
101 | fact: "Document all API endpoints using OpenAPI/Swagger specification.",
102 | type: "documentation",
103 | strictness: StrictnessLevel.REQUIRED,
104 | minVersion: "1.0.0",
105 | maxVersion: "2.0.0",
106 | conditions: [],
107 | acceptanceCriteria: [
108 | {
109 | id: "swagger-file-exists",
110 | description: "OpenAPI/Swagger specification file exists",
111 | validationType: "MANUAL"
112 | },
113 | {
114 | id: "endpoint-examples",
115 | description: "Each endpoint includes request/response examples",
116 | validationType: "AUTOMATED",
117 | validationScript: `
118 | function validateEndpointExamples(swaggerContent) {
119 | const swagger = JSON.parse(swaggerContent);
120 | return Object.values(swagger.paths).every(path =>
121 | Object.values(path).every(method =>
122 | method.requestBody?.content?.['application/json']?.example &&
123 | method.responses?.['200']?.content?.['application/json']?.example
124 | )
125 | );
126 | }
127 | `
128 | }
129 | ]
130 | },
131 | {
132 | id: "component-documentation",
133 | fact: "Document React components with TSDoc comments.",
134 | type: "documentation",
135 | strictness: StrictnessLevel.REQUIRED,
136 | minVersion: "1.0.0",
137 | maxVersion: "2.0.0",
138 | conditions: [
139 | {
140 | factId: "typescript",
141 | type: "REQUIRES"
142 | }
143 | ],
144 | acceptanceCriteria: [
145 | {
146 | id: "tsdoc-exists",
147 | description: "TSDoc comment block exists above component definition",
148 | validationType: "AUTOMATED",
149 | validationScript: `
150 | function validateTSDoc(fileContent) {
151 | return /\/\*\*[\s\S]*?\*\/\s*(export\s+)?((default\s+)?function|class|const)\s+\w+/.test(fileContent);
152 | }
153 | `
154 | },
155 | {
156 | id: "props-documented",
157 | description: "All props are documented with types and descriptions",
158 | validationType: "AUTOMATED",
159 | validationScript: `
160 | function validatePropsDoc(fileContent) {
161 | const propsInterface = fileContent.match(/interface\s+\w+Props\s*{[\s\S]*?}/);
162 | if (!propsInterface) return false;
163 |
164 | const props = propsInterface[0].match(/\/\*\*[\s\S]*?\*\/\s*\w+:/g);
165 | return props && props.length > 0;
166 | }
167 | `
168 | }
169 | ]
170 | }
171 | ];
172 |
173 | for (const fact of facts) {
174 | await server.storage.setFact(
175 | fact.id,
176 | fact.fact,
177 | fact.strictness,
178 | fact.type,
179 | fact.minVersion,
180 | fact.maxVersion,
181 | fact.conditions,
182 | fact.acceptanceCriteria
183 | );
184 | }
185 | }
186 |
187 | /*
188 | Example API usage:
189 |
190 | # Get documentation requirements with acceptance criteria
191 | curl "http://localhost:3000/search_facts?type=documentation"
192 |
193 | Response will include acceptance criteria:
194 | {
195 | "results": [
196 | {
197 | "id": "documentation-diagrams",
198 | "content": "Always create both entity relationship and sequence diagrams...",
199 | "acceptanceCriteria": [
200 | {
201 | "id": "seq-diagram-exists",
202 | "description": "Mermaid sequence diagram exists in documentation",
203 | "validationType": "MANUAL"
204 | },
205 | {
206 | "id": "unique-node-ids",
207 | "description": "Each node in diagrams has a unique identifier",
208 | "validationType": "AUTOMATED",
209 | "validationScript": "..."
210 | }
211 | ],
212 | ...
213 | }
214 | ]
215 | }
216 |
217 | # Validate specific criteria
218 | curl -X POST http://localhost:3000/validate-criteria \
219 | -H "Content-Type: application/json" \
220 | -d '{
221 | "factId": "documentation-diagrams",
222 | "content": "sequenceDiagram\n participant A\n participant B\n A->>B: Message"
223 | }'
224 | */
--------------------------------------------------------------------------------
/lib/api/facts.ts:
--------------------------------------------------------------------------------
1 | interface Fact {
2 | id: string;
3 | title: string;
4 | description: string;
5 | createdAt: Date;
6 | updatedAt: Date;
7 | }
8 |
9 | // Temporary mock function - replace with actual API call later
10 | export async function getAllFacts(): Promise {
11 | // Mock data for now
12 | return [
13 | {
14 | id: "1",
15 | title: "Example Fact",
16 | description: "This is a sample fact",
17 | createdAt: new Date(),
18 | updatedAt: new Date()
19 | }
20 | ];
21 | }
22 |
23 | export async function getFact(id: string): Promise {
24 | const facts = await getAllFacts();
25 | return facts.find(fact => fact.id === id) || null;
26 | }
--------------------------------------------------------------------------------
/src/components/facts/data-table.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import {
4 | ColumnDef,
5 | // ... existing imports ...
6 | } from "@/components/ui/table"
7 | import { Button } from "@/components/ui/button"
8 | import { useState } from "react"
9 |
10 | interface DataTableProps {
11 | columns: ColumnDef[]
12 | // ... rest of interface
13 | }
14 |
15 | // ... rest of component code ...
--------------------------------------------------------------------------------