@rigstate/mcp 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +8 -0
- package/README.md +352 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3445 -0
- package/dist/index.js.map +1 -0
- package/package.json +43 -0
- package/roadmap.json +531 -0
- package/src/agents/the-scribe.ts +122 -0
- package/src/index.ts +1792 -0
- package/src/lib/supabase.ts +120 -0
- package/src/lib/tool-registry.ts +134 -0
- package/src/lib/types.ts +415 -0
- package/src/lib/utils.ts +10 -0
- package/src/resources/project-morals.ts +92 -0
- package/src/tools/arch-tools.ts +166 -0
- package/src/tools/archaeological-scan.ts +335 -0
- package/src/tools/check-agent-bridge.ts +169 -0
- package/src/tools/check-rules-sync.ts +85 -0
- package/src/tools/complete-roadmap-task.ts +96 -0
- package/src/tools/generate-professional-pdf.ts +232 -0
- package/src/tools/get-latest-decisions.ts +130 -0
- package/src/tools/get-next-roadmap-step.ts +76 -0
- package/src/tools/get-project-context.ts +163 -0
- package/src/tools/index.ts +17 -0
- package/src/tools/list-features.ts +67 -0
- package/src/tools/list-roadmap-tasks.ts +61 -0
- package/src/tools/pending-tasks.ts +228 -0
- package/src/tools/planning-tools.ts +123 -0
- package/src/tools/query-brain.ts +125 -0
- package/src/tools/research-tools.ts +149 -0
- package/src/tools/run-architecture-audit.ts +203 -0
- package/src/tools/save-decision.ts +77 -0
- package/src/tools/security-tools.ts +82 -0
- package/src/tools/submit-idea.ts +66 -0
- package/src/tools/sync-ide-rules.ts +76 -0
- package/src/tools/teacher-mode.ts +171 -0
- package/src/tools/ui-tools.ts +191 -0
- package/src/tools/update-roadmap.ts +105 -0
- package/tsconfig.json +29 -0
- package/tsup.config.ts +16 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
|
|
2
|
+
import { SupabaseClient } from '@supabase/supabase-js';
|
|
3
|
+
import { AgentBridgeTask, CheckAgentBridgeResponse } from '../lib/types.js';
|
|
4
|
+
import { getProjectMorals } from '../resources/project-morals.js';
|
|
5
|
+
|
|
6
|
+
export async function checkAgentBridge(
|
|
7
|
+
supabase: SupabaseClient,
|
|
8
|
+
projectId: string,
|
|
9
|
+
action: 'check' | 'update' | 'submit_for_review' = 'check',
|
|
10
|
+
bridgeId?: string,
|
|
11
|
+
status?: string,
|
|
12
|
+
summary?: string, // legacy alias for general notes
|
|
13
|
+
execution_summary?: string, // NEW: required for completion
|
|
14
|
+
proposal?: string
|
|
15
|
+
): Promise<CheckAgentBridgeResponse> {
|
|
16
|
+
|
|
17
|
+
// 4. SUBMIT FOR REVIEW MODE
|
|
18
|
+
if (action === 'submit_for_review') {
|
|
19
|
+
if (!bridgeId) throw new Error('bridgeId is required for submit_for_review action');
|
|
20
|
+
if (!proposal) throw new Error('proposal (with suggestedChanges and reasoning) is required for submission');
|
|
21
|
+
|
|
22
|
+
// Pre-Flight Check: Active Validation
|
|
23
|
+
const morals = getProjectMorals(); // Reads local .cursorrules if available
|
|
24
|
+
const rulesText = morals.formatted.toLowerCase();
|
|
25
|
+
const proposalLower = proposal.toLowerCase();
|
|
26
|
+
|
|
27
|
+
// 1. Check for RIGSTATE compliance if rules exist
|
|
28
|
+
if (rulesText.includes('rigstate_start')) {
|
|
29
|
+
// Heuristic: If rules dictate RLS, proposal must mention it
|
|
30
|
+
if (rulesText.includes('rls') && !proposalLower.includes('rls') && !proposalLower.includes('security')) {
|
|
31
|
+
throw new Error('Validation Failed: Rule "RLS/Security" was violated. Your proposal does not explicitly mention Row Level Security. Please update your proposal to confirm RLS is handled.');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Heuristic: If rules dictate Strict Types, proposal must mention it
|
|
35
|
+
if (rulesText.includes('strict types') && !proposalLower.includes('type') && !proposalLower.includes('interface')) {
|
|
36
|
+
throw new Error('Validation Failed: Rule "Strict Types" was violated. Your proposal seems to lack TypeScript definitions. Please explicitly plan your types.');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Update status to NEEDS_REVIEW
|
|
41
|
+
const { data, error } = await supabase
|
|
42
|
+
.from('agent_bridge')
|
|
43
|
+
.update({
|
|
44
|
+
status: 'NEEDS_REVIEW',
|
|
45
|
+
proposal: proposal,
|
|
46
|
+
updated_at: new Date().toISOString()
|
|
47
|
+
})
|
|
48
|
+
.eq('id', bridgeId)
|
|
49
|
+
.eq('project_id', projectId)
|
|
50
|
+
.select()
|
|
51
|
+
.single();
|
|
52
|
+
|
|
53
|
+
if (error) throw new Error(`Failed to submit for review: ${error.message}`);
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
success: true,
|
|
57
|
+
message: `Task ${bridgeId} submitted for review.`,
|
|
58
|
+
task: data as AgentBridgeTask
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 1. UPDATE MODE
|
|
63
|
+
if (action === 'update') {
|
|
64
|
+
if (!bridgeId) throw new Error('bridgeId is required for update action');
|
|
65
|
+
|
|
66
|
+
// Security Check: If trying to set COMPLETED, verify it was APPROVED
|
|
67
|
+
if (status === 'COMPLETED' || status === 'EXECUTING') {
|
|
68
|
+
const { data: currentTask } = await supabase
|
|
69
|
+
.from('agent_bridge')
|
|
70
|
+
.select('status')
|
|
71
|
+
.eq('id', bridgeId)
|
|
72
|
+
.single();
|
|
73
|
+
|
|
74
|
+
if (currentTask && (currentTask.status !== 'APPROVED' && currentTask.status !== 'EXECUTING')) {
|
|
75
|
+
throw new Error(`Security Violation: Cannot execute or complete task ${bridgeId} because it is in status '${currentTask.status}'. Wait for APPROVED status.`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!status && !summary && !proposal) throw new Error('At least one of status, summary, or proposal is required for update');
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
const updateData: any = {};
|
|
83
|
+
|
|
84
|
+
if (status === 'COMPLETED') {
|
|
85
|
+
if (!execution_summary) {
|
|
86
|
+
throw new Error('execution_summary is REQUIRED when setting status to COMPLETED.');
|
|
87
|
+
}
|
|
88
|
+
updateData.completed_at = new Date().toISOString();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (status) updateData.status = status;
|
|
92
|
+
if (summary) updateData.summary = summary;
|
|
93
|
+
if (execution_summary) updateData.execution_summary = execution_summary;
|
|
94
|
+
if (proposal) updateData.proposal = proposal;
|
|
95
|
+
updateData.updated_at = new Date().toISOString();
|
|
96
|
+
|
|
97
|
+
const { data, error } = await supabase
|
|
98
|
+
.from('agent_bridge')
|
|
99
|
+
.update(updateData)
|
|
100
|
+
.eq('id', bridgeId)
|
|
101
|
+
.eq('project_id', projectId)
|
|
102
|
+
.select()
|
|
103
|
+
.single();
|
|
104
|
+
|
|
105
|
+
if (error) throw new Error(`Failed to update agent bridge: ${error.message}`);
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
success: true,
|
|
109
|
+
message: `Updated agent bridge task ${bridgeId} to status ${data.status}`,
|
|
110
|
+
task: data as AgentBridgeTask
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 2. CHECK MODE (Default)
|
|
115
|
+
// Find the oldest PENDING or APPROVED task (prioritize approved to execute, then pending to plan)
|
|
116
|
+
// Actually, usually we want to find work.
|
|
117
|
+
// - PENDING means "Need Plan"
|
|
118
|
+
// - APPROVED means "Need Execution"
|
|
119
|
+
// So we should look for both.
|
|
120
|
+
|
|
121
|
+
const { data: tasks, error } = await supabase
|
|
122
|
+
.from('agent_bridge')
|
|
123
|
+
.select(`
|
|
124
|
+
*,
|
|
125
|
+
roadmap_chunks (
|
|
126
|
+
title,
|
|
127
|
+
description,
|
|
128
|
+
prompt_content
|
|
129
|
+
)
|
|
130
|
+
`)
|
|
131
|
+
.eq('project_id', projectId)
|
|
132
|
+
// Find tasks that need plan (PENDING) OR need execution (APPROVED)
|
|
133
|
+
// Ignoring NEEDS_REVIEW as that's user's court
|
|
134
|
+
.in('status', ['PENDING', 'APPROVED', 'REJECTED'])
|
|
135
|
+
.order('created_at', { ascending: true })
|
|
136
|
+
.limit(1);
|
|
137
|
+
|
|
138
|
+
if (error) throw new Error(`Failed to check agent bridge: ${error.message}`);
|
|
139
|
+
|
|
140
|
+
if (!tasks || tasks.length === 0) {
|
|
141
|
+
return {
|
|
142
|
+
success: true,
|
|
143
|
+
message: 'No pending or approved tasks found.',
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const taskData = tasks[0];
|
|
148
|
+
const task: AgentBridgeTask = {
|
|
149
|
+
id: taskData.id,
|
|
150
|
+
project_id: taskData.project_id,
|
|
151
|
+
task_id: taskData.task_id,
|
|
152
|
+
status: taskData.status,
|
|
153
|
+
summary: taskData.summary,
|
|
154
|
+
execution_summary: taskData.execution_summary,
|
|
155
|
+
proposal: taskData.proposal,
|
|
156
|
+
created_at: taskData.created_at,
|
|
157
|
+
completed_at: taskData.completed_at,
|
|
158
|
+
// Map joined data flattener
|
|
159
|
+
task_title: taskData.roadmap_chunks?.title,
|
|
160
|
+
task_description: taskData.roadmap_chunks?.description,
|
|
161
|
+
task_content: taskData.roadmap_chunks?.prompt_content
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
success: true,
|
|
166
|
+
message: `Found task needing attention: ${task.status}`,
|
|
167
|
+
task
|
|
168
|
+
};
|
|
169
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
|
|
2
|
+
import { SupabaseClient } from '@supabase/supabase-js';
|
|
3
|
+
import { CheckRulesSyncResponse } from '../lib/types.js';
|
|
4
|
+
|
|
5
|
+
const RIGSTATE_START = "RIGSTATE_START";
|
|
6
|
+
const RIGSTATE_END = "RIGSTATE_END";
|
|
7
|
+
|
|
8
|
+
// Minimal Safety Cache (Guardian Rules)
|
|
9
|
+
const SAFETY_CACHE_RULES = `
|
|
10
|
+
### š”ļø RIGSTATE SAFETY CACHE (OFFLINE MODE)
|
|
11
|
+
1. **Strict Types**: No 'any'. Define interfaces for all data structures.
|
|
12
|
+
2. **Row Level Security**: All database queries MUST be scoped to 'user_id' or 'org_id'.
|
|
13
|
+
3. **Validation**: Use Zod for all input validation.
|
|
14
|
+
4. **Error Handling**: Use try/catch blocks for all external API calls.
|
|
15
|
+
5. **No Blind Deletes**: Never delete data without explicit confirmation or soft-deletes.
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
export async function checkRulesSync(
|
|
19
|
+
supabase: SupabaseClient,
|
|
20
|
+
projectId: string,
|
|
21
|
+
currentRulesContent?: string
|
|
22
|
+
): Promise<CheckRulesSyncResponse> {
|
|
23
|
+
|
|
24
|
+
// If no content provided, we can't really verify local files from MCP directly
|
|
25
|
+
// without reading the file system, but MCP is often sandboxed or remote.
|
|
26
|
+
// Ideally, the Agent provides the content of .cursorrules it sees.
|
|
27
|
+
|
|
28
|
+
if (!currentRulesContent) {
|
|
29
|
+
return {
|
|
30
|
+
synced: false,
|
|
31
|
+
message: "No rules content provided for verification.",
|
|
32
|
+
shouldTriggerSync: true,
|
|
33
|
+
missingBlock: true
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const hasStart = currentRulesContent.includes(RIGSTATE_START);
|
|
38
|
+
const hasEnd = currentRulesContent.includes(RIGSTATE_END);
|
|
39
|
+
|
|
40
|
+
if (!hasStart || !hasEnd) {
|
|
41
|
+
return {
|
|
42
|
+
synced: false,
|
|
43
|
+
message: "Rigstate rules block is missing or corrupted.",
|
|
44
|
+
shouldTriggerSync: true,
|
|
45
|
+
missingBlock: true
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Resilience: Wrap DB check in try/catch to handle network failures
|
|
50
|
+
try {
|
|
51
|
+
const { data: project, error } = await supabase
|
|
52
|
+
.from('projects')
|
|
53
|
+
.select('name')
|
|
54
|
+
.eq('id', projectId)
|
|
55
|
+
.single();
|
|
56
|
+
|
|
57
|
+
if (error) throw error;
|
|
58
|
+
|
|
59
|
+
if (project) {
|
|
60
|
+
if (!currentRulesContent.includes(`Project Rules: ${project.name}`)) {
|
|
61
|
+
return {
|
|
62
|
+
synced: false,
|
|
63
|
+
message: `Rules file appears to belong to a different project (Expected: ${project.name}).`,
|
|
64
|
+
shouldTriggerSync: true
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
} catch (e) {
|
|
69
|
+
// Network or DB Error -> Fallback to Offline Mode
|
|
70
|
+
console.error("Rigstate Sync Verification Failed:", e);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
synced: true, // Assume synced to allow work to continue
|
|
74
|
+
shouldTriggerSync: false,
|
|
75
|
+
offlineMode: true,
|
|
76
|
+
message: `ā ļø Rigstate Sync utilgjengelig ā bruker lokale sikkerhetsregler.\n${SAFETY_CACHE_RULES}`
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
synced: true,
|
|
82
|
+
message: "Rules are valid and present.",
|
|
83
|
+
shouldTriggerSync: false
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
+
|
|
3
|
+
export interface CompleteRoadmapTaskResponse {
|
|
4
|
+
success: boolean;
|
|
5
|
+
taskId: string;
|
|
6
|
+
message: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function completeRoadmapTask(
|
|
10
|
+
supabase: SupabaseClient,
|
|
11
|
+
projectId: string,
|
|
12
|
+
summary: string,
|
|
13
|
+
taskId?: string,
|
|
14
|
+
gitDiff?: string
|
|
15
|
+
): Promise<CompleteRoadmapTaskResponse> {
|
|
16
|
+
|
|
17
|
+
// 1. Identify the task
|
|
18
|
+
let targetTaskId = taskId;
|
|
19
|
+
|
|
20
|
+
if (!targetTaskId) {
|
|
21
|
+
// Infer: Find the first IN_PROGRESS or ACTIVE task
|
|
22
|
+
const { data: activeTask } = await supabase
|
|
23
|
+
.from('roadmap_chunks')
|
|
24
|
+
.select('id, title')
|
|
25
|
+
.eq('project_id', projectId)
|
|
26
|
+
.in('status', ['IN_PROGRESS', 'ACTIVE'])
|
|
27
|
+
.order('step_number', { ascending: true })
|
|
28
|
+
.limit(1)
|
|
29
|
+
.single();
|
|
30
|
+
|
|
31
|
+
if (activeTask) {
|
|
32
|
+
targetTaskId = activeTask.id;
|
|
33
|
+
} else {
|
|
34
|
+
throw new Error('No active task found to complete. Please provide a specific taskId.');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 2. Update the Task Status
|
|
39
|
+
const { error: updateError } = await supabase
|
|
40
|
+
.from('roadmap_chunks')
|
|
41
|
+
.update({
|
|
42
|
+
status: 'COMPLETED',
|
|
43
|
+
completed_at: new Date().toISOString(),
|
|
44
|
+
// We could store the summary directly on the chunk if there's a column,
|
|
45
|
+
// but mission_reports is the proper place for detailed logs.
|
|
46
|
+
// Let's check if we can update metadata or similar.
|
|
47
|
+
// For now, let's assume mission_reports is the way.
|
|
48
|
+
})
|
|
49
|
+
.eq('id', targetTaskId);
|
|
50
|
+
|
|
51
|
+
if (updateError) {
|
|
52
|
+
throw new Error(`Failed to update task status: ${updateError.message}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 3. Create a Mission Report entry
|
|
56
|
+
// Check if 'mission_reports' table exists and has the schema we expect.
|
|
57
|
+
// Based on previous code in index.ts (Frank Watcher):
|
|
58
|
+
/*
|
|
59
|
+
await supabase.from('mission_reports').insert({
|
|
60
|
+
bridge_id: task.id, // We might not have a bridge_id if this is direct from IDE
|
|
61
|
+
project_id: task.project_id,
|
|
62
|
+
task_id: task.task_id,
|
|
63
|
+
human_summary: humanSummary,
|
|
64
|
+
technical_summary: technicalSummary,
|
|
65
|
+
...
|
|
66
|
+
});
|
|
67
|
+
*/
|
|
68
|
+
|
|
69
|
+
// We'll insert a report. Since we don't have a bridge_id (this didn't come from the agent bridge queue),
|
|
70
|
+
// we'll leave it null if allowed, or we might need to create a dummy bridge entry?
|
|
71
|
+
// Let's assume bridge_id is nullable or we just skip it.
|
|
72
|
+
// If bridge_id is required, we might need to fake one or just log to a 'notes' field on roadmap_chunk if available.
|
|
73
|
+
// Let's try to insert into mission_reports first.
|
|
74
|
+
|
|
75
|
+
const { error: reportError } = await supabase
|
|
76
|
+
.from('mission_reports')
|
|
77
|
+
.insert({
|
|
78
|
+
project_id: projectId,
|
|
79
|
+
task_id: targetTaskId,
|
|
80
|
+
human_summary: summary,
|
|
81
|
+
technical_summary: gitDiff || 'Completed via IDE Direct Connection.',
|
|
82
|
+
// bridge_id: null // Assuming nullable
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
if (reportError) {
|
|
86
|
+
console.warn('Failed to save mission report:', reportError.message);
|
|
87
|
+
// Fallback: Add a comment/note to the chunk?
|
|
88
|
+
// We'll just return a warning in the message.
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
success: true,
|
|
93
|
+
taskId: targetTaskId!,
|
|
94
|
+
message: `Task completed successfully! Summary saved.`
|
|
95
|
+
};
|
|
96
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
|
|
2
|
+
import { SupabaseClient } from '@supabase/supabase-js';
|
|
3
|
+
import { getScribePersona, interpolateScribePrompt, calculateScribeMetrics } from '../agents/the-scribe.js';
|
|
4
|
+
|
|
5
|
+
export async function generateProfessionalPdf(
|
|
6
|
+
supabase: SupabaseClient,
|
|
7
|
+
userId: string,
|
|
8
|
+
projectId: string,
|
|
9
|
+
reportType: 'SYSTEM_MANIFEST' | 'INVESTOR_REPORT'
|
|
10
|
+
) {
|
|
11
|
+
console.error(`šļø The Scribe is preparing a ${reportType} briefing for project ${projectId}...`);
|
|
12
|
+
|
|
13
|
+
// 1. Fetch persona from Prompt CMS (via Scribe Adapter)
|
|
14
|
+
const persona = await getScribePersona(supabase);
|
|
15
|
+
|
|
16
|
+
// 2. Fetch Project Metadata (expanded)
|
|
17
|
+
const { data: project } = await supabase
|
|
18
|
+
.from('projects')
|
|
19
|
+
.select('name, description, project_type, detected_stack')
|
|
20
|
+
.eq('id', projectId)
|
|
21
|
+
.single();
|
|
22
|
+
|
|
23
|
+
const projectName = project?.name || 'Rigstate Project';
|
|
24
|
+
const projectDescription = project?.description || 'A cutting-edge software project built with modern architecture.';
|
|
25
|
+
const projectType = project?.project_type || 'Web Application';
|
|
26
|
+
const detectedStack = project?.detected_stack || {};
|
|
27
|
+
|
|
28
|
+
// 3. Fetch COMPREHENSIVE Real Context & Metrics
|
|
29
|
+
const [
|
|
30
|
+
metrics,
|
|
31
|
+
dnaStatsResponse,
|
|
32
|
+
recentDecisions,
|
|
33
|
+
roadmapData,
|
|
34
|
+
councilSessions,
|
|
35
|
+
tableList
|
|
36
|
+
] = await Promise.all([
|
|
37
|
+
calculateScribeMetrics(supabase, projectId),
|
|
38
|
+
supabase.rpc('get_project_dna_stats', { p_project_id: projectId }),
|
|
39
|
+
supabase.from('project_memories')
|
|
40
|
+
.select('summary, category, created_at')
|
|
41
|
+
.eq('project_id', projectId)
|
|
42
|
+
.eq('category', 'decision')
|
|
43
|
+
.order('created_at', { ascending: false })
|
|
44
|
+
.limit(5),
|
|
45
|
+
supabase.from('roadmap_chunks')
|
|
46
|
+
.select('title, status, priority')
|
|
47
|
+
.eq('project_id', projectId)
|
|
48
|
+
.order('priority', { ascending: true }),
|
|
49
|
+
supabase.from('council_sessions')
|
|
50
|
+
.select('executive_summary, created_at')
|
|
51
|
+
.eq('project_id', projectId)
|
|
52
|
+
.order('created_at', { ascending: false })
|
|
53
|
+
.limit(2),
|
|
54
|
+
supabase.rpc('get_public_tables_list')
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
const dnaStats = dnaStatsResponse.data || { total_tables: 0, rls_tables: 0, rls_policies: 0 };
|
|
58
|
+
const tables = tableList.data || [];
|
|
59
|
+
|
|
60
|
+
// 4. Process Roadmap Data - Separate legacy from active
|
|
61
|
+
const roadmapSteps = roadmapData.data || [];
|
|
62
|
+
const allCompletedSteps = roadmapSteps.filter(s => s.status === 'COMPLETED');
|
|
63
|
+
const legacySteps = roadmapSteps.filter((s: any) => s.is_legacy === true);
|
|
64
|
+
const activeCompletedSteps = allCompletedSteps.filter((s: any) => s.is_legacy !== true);
|
|
65
|
+
const activeStep = roadmapSteps.find(s => s.status === 'ACTIVE');
|
|
66
|
+
const lockedSteps = roadmapSteps.filter(s => s.status === 'LOCKED');
|
|
67
|
+
|
|
68
|
+
// Progress based on ALL steps (legacy count towards completion but are clearly marked)
|
|
69
|
+
const totalNonLegacy = roadmapSteps.filter((s: any) => s.is_legacy !== true).length;
|
|
70
|
+
const progress = totalNonLegacy > 0 ? Math.round((activeCompletedSteps.length / totalNonLegacy) * 100) : 0;
|
|
71
|
+
|
|
72
|
+
// 5. Build Tech Stack Summary
|
|
73
|
+
const deps = detectedStack.dependencies || {};
|
|
74
|
+
const devDeps = detectedStack.devDependencies || {};
|
|
75
|
+
const allDeps = { ...deps, ...devDeps };
|
|
76
|
+
const techStackItems: string[] = [];
|
|
77
|
+
|
|
78
|
+
if (allDeps['next']) techStackItems.push(`Next.js ${allDeps['next']}`);
|
|
79
|
+
if (allDeps['react']) techStackItems.push(`React ${allDeps['react']}`);
|
|
80
|
+
if (allDeps['@supabase/supabase-js']) techStackItems.push('Supabase');
|
|
81
|
+
if (allDeps['tailwindcss']) techStackItems.push('Tailwind CSS');
|
|
82
|
+
if (allDeps['typescript']) techStackItems.push('TypeScript');
|
|
83
|
+
if (allDeps['zod']) techStackItems.push('Zod Validation');
|
|
84
|
+
if (allDeps['@react-pdf/renderer']) techStackItems.push('React-PDF');
|
|
85
|
+
if (allDeps['sonner']) techStackItems.push('Sonner Toasts');
|
|
86
|
+
|
|
87
|
+
const techStackSummary = techStackItems.length > 0
|
|
88
|
+
? techStackItems.join(' ⢠')
|
|
89
|
+
: 'Stack detection pending. Run repository indexing to populate.';
|
|
90
|
+
|
|
91
|
+
// 6. Build ADRs Summary
|
|
92
|
+
const adrList = recentDecisions.data?.map(d => `⢠${d.summary}`) || [];
|
|
93
|
+
const adrContent = adrList.length > 0
|
|
94
|
+
? adrList.join('\n')
|
|
95
|
+
: '⢠No architectural decisions recorded yet. Use the Council or save decisions via chat.';
|
|
96
|
+
|
|
97
|
+
// 7. Build Roadmap Details - Separate legacy from active
|
|
98
|
+
const legacyTitles = legacySteps.length > 0
|
|
99
|
+
? `Established Foundations (${legacySteps.length}):\n${legacySteps.map((s: any) => `š ${s.title}`).join('\n')}`
|
|
100
|
+
: '';
|
|
101
|
+
const activeTitles = activeCompletedSteps.map((s: any) => `ā ${s.title}`).join('\n');
|
|
102
|
+
const activeTitle = activeStep ? `ā IN PROGRESS: ${activeStep.title}` : '';
|
|
103
|
+
const nextUpTitles = lockedSteps.slice(0, 3).map((s: any) => `ā ${s.title}`).join('\n');
|
|
104
|
+
|
|
105
|
+
const roadmapDetails = [
|
|
106
|
+
legacyTitles,
|
|
107
|
+
activeTitles ? `\nRecent Completions:\n${activeTitles}` : '',
|
|
108
|
+
activeTitle,
|
|
109
|
+
nextUpTitles ? `\nNext Up:\n${nextUpTitles}` : ''
|
|
110
|
+
].filter(Boolean).join('\n');
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
// 8. Build Council Insights
|
|
114
|
+
const councilInsights = councilSessions.data?.map(s => s.executive_summary).filter(Boolean).slice(0, 2) || [];
|
|
115
|
+
const councilSummary = councilInsights.length > 0
|
|
116
|
+
? councilInsights.map(i => `⢠${i}`).join('\n')
|
|
117
|
+
: 'No council sessions recorded yet.';
|
|
118
|
+
|
|
119
|
+
// 9. Build Security Analysis (Comprehensive)
|
|
120
|
+
const securedTables = dnaStats.rls_tables || 0;
|
|
121
|
+
const totalTables = dnaStats.total_tables || tables.length || 0;
|
|
122
|
+
const rlsPolicies = dnaStats.rls_policies || 0;
|
|
123
|
+
const securityScore = totalTables > 0 ? Math.round((securedTables / totalTables) * 100) : 0;
|
|
124
|
+
|
|
125
|
+
const securityAnalysis = `
|
|
126
|
+
Security Score: ${securityScore}% of public tables secured with RLS.
|
|
127
|
+
⢠Total Public Tables: ${totalTables}
|
|
128
|
+
⢠Tables with RLS Enabled: ${securedTables}
|
|
129
|
+
⢠Active RLS Policies: ${rlsPolicies}
|
|
130
|
+
⢠Guardian Pings (Monitoring): ${metrics.riskScore} detected
|
|
131
|
+
|
|
132
|
+
${securityScore >= 90 ? 'ā EXCELLENT: All critical tables are protected.' :
|
|
133
|
+
securityScore >= 70 ? 'ā STABLE: Most tables protected. Review remaining exposures.' :
|
|
134
|
+
'ā AT RISK: Immediate RLS audit recommended.'}
|
|
135
|
+
`.trim();
|
|
136
|
+
|
|
137
|
+
// 10. Interpolate variables
|
|
138
|
+
const interpolatedPrompt = interpolateScribePrompt(persona.content, {
|
|
139
|
+
projectName,
|
|
140
|
+
velocity: metrics.velocity,
|
|
141
|
+
quality: metrics.qualityTrend,
|
|
142
|
+
riskMitigation: metrics.riskMitigation
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// 11. Structure MANIFEST report data
|
|
146
|
+
const manifestData = {
|
|
147
|
+
type: 'MANIFEST',
|
|
148
|
+
projectName: projectName,
|
|
149
|
+
sections: [
|
|
150
|
+
{
|
|
151
|
+
title: 'Project Overview',
|
|
152
|
+
content: `${projectDescription}\n\nProject Type: ${projectType}\nTech Stack: ${techStackSummary}`
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
title: 'Codebase DNA (Real-time Scan)',
|
|
156
|
+
content: `System consists of ${totalTables} tables with ${rlsPolicies} active RLS security policies.\n\nDatabase integrity is rated as ${securityScore >= 90 ? 'EXCELLENT' : securityScore >= 70 ? 'STABLE' : 'NEEDS ATTENTION'}.\n\nKey Tables Detected:\n${tables.slice(0, 10).map((t: any) => `⢠${t.table_name}`).join('\n') || '⢠Run database scan to populate'}`
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
title: 'Architectural Decisions (ADRs)',
|
|
160
|
+
content: adrContent
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
title: 'Roadmap Execution',
|
|
164
|
+
content: `Development Progress: ${progress}% (${activeCompletedSteps.length} active / ${legacySteps.length} legacy foundations)\n\nVelocity: ${metrics.velocity}\n\n${roadmapDetails || 'No roadmap steps configured yet.'}`
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
title: 'Council Insights',
|
|
168
|
+
content: councilSummary
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
title: 'Security & Risk Assessment',
|
|
172
|
+
content: securityAnalysis
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
title: 'Quality & Compliance',
|
|
176
|
+
content: `Quality Trend: ${metrics.qualityTrend}\n\nGuard Clauses:\n⢠Strict Type Checks (No Any)\n⢠Mandatory Supabase RLS Enforcement\n⢠Line Limit Enforcement: TS (400) / TSX (250)\n⢠All API routes require authentication\n⢠CORS policies enforced on public endpoints`
|
|
177
|
+
}
|
|
178
|
+
],
|
|
179
|
+
agent: persona.display_name,
|
|
180
|
+
job_title: persona.job_title,
|
|
181
|
+
metrics: {
|
|
182
|
+
velocity: metrics.velocity,
|
|
183
|
+
qualityTrend: metrics.qualityTrend,
|
|
184
|
+
progress: `${progress}%`,
|
|
185
|
+
securityScore: `${securityScore}%`,
|
|
186
|
+
totalTables: totalTables,
|
|
187
|
+
rlsPolicies: rlsPolicies
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// 12. Structure INVESTOR report data
|
|
192
|
+
const investorData = {
|
|
193
|
+
type: 'INVESTOR',
|
|
194
|
+
projectName: projectName,
|
|
195
|
+
data: {
|
|
196
|
+
velocity: metrics.velocity,
|
|
197
|
+
healthScore: Math.min(100, 70 + (activeCompletedSteps.length * 3) + (legacySteps.length * 2) + (progress / 5) + (securityScore / 10)),
|
|
198
|
+
qualityTrend: metrics.qualityTrend,
|
|
199
|
+
roadmapProgress: `${progress}% (${activeCompletedSteps.length} completed, ${legacySteps.length} established foundations)`,
|
|
200
|
+
projectType: projectType,
|
|
201
|
+
techStack: techStackSummary,
|
|
202
|
+
keyAchievements: [
|
|
203
|
+
...activeCompletedSteps.slice(0, 3).map((s: any) => `Completed: ${s.title}`),
|
|
204
|
+
...(legacySteps.length > 0 ? [`Built on ${legacySteps.length} established foundations`] : []),
|
|
205
|
+
...(recentDecisions.data?.slice(0, 2).map((d: any) => d.summary) || [])
|
|
206
|
+
].slice(0, 5),
|
|
207
|
+
currentFocus: activeStep?.title || 'Planning next phase',
|
|
208
|
+
upcomingMilestones: lockedSteps.slice(0, 3).map(s => s.title),
|
|
209
|
+
riskMitigation: metrics.riskMitigation,
|
|
210
|
+
securityPosture: {
|
|
211
|
+
score: `${securityScore}%`,
|
|
212
|
+
status: securityScore >= 90 ? 'Excellent' : securityScore >= 70 ? 'Stable' : 'Needs Attention',
|
|
213
|
+
rlsPolicies: rlsPolicies,
|
|
214
|
+
protectedTables: securedTables,
|
|
215
|
+
totalTables: totalTables
|
|
216
|
+
},
|
|
217
|
+
councilInsights: councilInsights.slice(0, 2)
|
|
218
|
+
},
|
|
219
|
+
agent: persona.display_name,
|
|
220
|
+
job_title: persona.job_title
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const reportData = reportType === 'SYSTEM_MANIFEST' ? manifestData : investorData;
|
|
224
|
+
|
|
225
|
+
// 13. Return data to caller
|
|
226
|
+
return {
|
|
227
|
+
success: true,
|
|
228
|
+
message: `${reportType} briefing is ready.`,
|
|
229
|
+
data: reportData,
|
|
230
|
+
debug_prompt_snippet: interpolatedPrompt.substring(0, 500)
|
|
231
|
+
};
|
|
232
|
+
}
|