@rigstate/rules-engine 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Supported IDE providers for rules file generation.
3
+ * - cursor: .cursorrules + .cursor/rules/*.mdc
4
+ * - antigravity: .cursorrules + .cursor/rules/*.mdc (Cursor-compatible)
5
+ * - windsurf: .windsurfrules
6
+ * - vscode: .cursorrules + .cursor/rules/*.mdc
7
+ * - copilot: .github/copilot-instructions.md
8
+ * - generic: CONVENTIONS.md (universal markdown format)
9
+ */
10
+ export type IDEProvider = 'cursor' | 'antigravity' | 'windsurf' | 'vscode' | 'copilot' | 'generic';
11
+ /**
12
+ * Mapping of IDE providers to their respective rules file names.
13
+ * Centralized here to ensure consistency across all consumers.
14
+ */
15
+ export declare const IDE_FILE_NAMES: Record<IDEProvider, string>;
16
+ export interface ProjectSettings {
17
+ lmax: number;
18
+ lmax_ui: number;
19
+ security_level: string;
20
+ }
21
+ export interface RuleFile {
22
+ path: string;
23
+ content: string;
24
+ metadata?: {
25
+ description?: string;
26
+ globs?: string[];
27
+ alwaysApply?: boolean;
28
+ };
29
+ }
30
+ export interface MultiFileRuleResult {
31
+ files: RuleFile[];
32
+ suggestedIde: IDEProvider;
33
+ version: string;
34
+ }
35
+ export interface RuleGenerationContext {
36
+ project: {
37
+ id: string;
38
+ name: string;
39
+ description?: string;
40
+ functional_spec?: {
41
+ projectDescription?: string;
42
+ coreProblem?: string;
43
+ targetAudience?: string;
44
+ featureList?: Array<{
45
+ name: string;
46
+ description: string;
47
+ priority: string;
48
+ }>;
49
+ keyInsights?: string[];
50
+ };
51
+ ambition_level?: string;
52
+ tenancy?: 'SINGLE' | 'MULTI_ORG';
53
+ monetization?: 'FREE' | 'SAAS';
54
+ compliance?: 'NONE' | 'GDPR' | 'HIPAA';
55
+ vibe?: 'CORPORATE' | 'CREATIVE' | 'BRUTALIST';
56
+ settings?: ProjectSettings;
57
+ };
58
+ stack: string[];
59
+ roadmap: Array<{
60
+ step_number: number;
61
+ title: string;
62
+ status: string;
63
+ sprint_focus?: string;
64
+ prompt_content?: string;
65
+ is_legacy?: boolean;
66
+ }>;
67
+ ide: IDEProvider;
68
+ legacyStats?: {
69
+ total: number;
70
+ legacyCount: number;
71
+ activeCount: number;
72
+ };
73
+ activeAgents?: Array<{
74
+ id: string;
75
+ key: string;
76
+ name: string;
77
+ job_title?: string;
78
+ content: string;
79
+ authority_level: number;
80
+ primary_mission?: string;
81
+ trigger_keywords?: string;
82
+ }>;
83
+ databaseMetadata?: TableMetadata[];
84
+ }
85
+ export interface TableMetadata {
86
+ table_name: string;
87
+ rls_enabled: boolean;
88
+ policy_count: number;
89
+ column_count: number;
90
+ has_id: boolean;
91
+ has_created_at: boolean;
92
+ has_updated_at: boolean;
93
+ has_user_id: boolean;
94
+ indexed_columns: string[];
95
+ foreign_keys: {
96
+ column: string;
97
+ target_table: string;
98
+ }[];
99
+ }
100
+ export interface AgentSkill {
101
+ name: string;
102
+ description: string;
103
+ specialist: string;
104
+ version: string;
105
+ governance: 'OPEN' | 'SOFT_LOCK' | 'HARD_LOCK';
106
+ content: string;
107
+ activation?: {
108
+ imports?: string[];
109
+ content?: string[];
110
+ files?: string[];
111
+ violation_id?: string;
112
+ metric_threshold?: number;
113
+ };
114
+ }
package/dist/types.js ADDED
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.IDE_FILE_NAMES = void 0;
4
+ /**
5
+ * Mapping of IDE providers to their respective rules file names.
6
+ * Centralized here to ensure consistency across all consumers.
7
+ */
8
+ exports.IDE_FILE_NAMES = {
9
+ cursor: '.cursorrules',
10
+ antigravity: '.cursorrules',
11
+ windsurf: '.windsurfrules',
12
+ vscode: '.cursorrules',
13
+ copilot: '.github/copilot-instructions.md',
14
+ generic: 'CONVENTIONS.md'
15
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Utility to wrap markdown content with MDC (Modern Cursor) frontmatter.
3
+ */
4
+ export declare function wrapMdc(content: string, metadata: {
5
+ description?: string;
6
+ globs?: string[];
7
+ alwaysApply?: boolean;
8
+ }): string;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.wrapMdc = wrapMdc;
4
+ /**
5
+ * Utility to wrap markdown content with MDC (Modern Cursor) frontmatter.
6
+ */
7
+ function wrapMdc(content, metadata) {
8
+ const yaml = ['---'];
9
+ if (metadata.description) {
10
+ yaml.push(`description: "${metadata.description.replace(/"/g, '\\"')}"`);
11
+ }
12
+ if (metadata.globs && metadata.globs.length > 0) {
13
+ yaml.push('globs:');
14
+ for (const glob of metadata.globs) {
15
+ yaml.push(` - "${glob}"`);
16
+ }
17
+ }
18
+ if (metadata.alwaysApply !== undefined) {
19
+ yaml.push(`alwaysApply: ${metadata.alwaysApply}`);
20
+ }
21
+ yaml.push('---');
22
+ return `${yaml.join('\n')}\n${content}`;
23
+ }
package/package.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "@rigstate/rules-engine",
3
+ "version": "0.6.0",
4
+ "main": "dist/index.js",
5
+ "types": "dist/index.d.ts",
6
+ "scripts": {
7
+ "build": "tsc",
8
+ "dev": "tsc -w"
9
+ },
10
+ "dependencies": {
11
+ "zod": "^3.22.4"
12
+ },
13
+ "devDependencies": {
14
+ "typescript": "^5.3.3"
15
+ }
16
+ }
package/src/index.ts ADDED
@@ -0,0 +1,416 @@
1
+ import {
2
+ RuleGenerationContext,
3
+ IDEProvider,
4
+ IDE_FILE_NAMES,
5
+ RuleFile,
6
+ MultiFileRuleResult,
7
+ TableMetadata
8
+ } from './types';
9
+ import { generateIdentitySection } from './sections/identity';
10
+ import { generateStackDnaSection } from './sections/stack-dna';
11
+ import { generateCurrentStepSection } from './sections/current-step';
12
+ import { generateWorkflowSection } from './sections/workflow';
13
+ import { generateToolingSection } from './sections/tooling';
14
+ import { generateAvailableSkillsSection, generateSkillFileContent, getRigstateStandardSkills } from './sections/skills';
15
+ import { wrapMdc } from './utils/mdc';
16
+
17
+ // Re-export types and constants for consumers
18
+ export type {
19
+ RuleGenerationContext,
20
+ IDEProvider,
21
+ ProjectSettings,
22
+ RuleFile,
23
+ MultiFileRuleResult,
24
+ AgentSkill
25
+ } from './types';
26
+ export { IDE_FILE_NAMES } from './types';
27
+ export { generateAvailableSkillsSection, generateSkillFileContent, getRigstateStandardSkills } from './sections/skills';
28
+
29
+ const RIGSTATE_START = "RIGSTATE_START";
30
+ const RIGSTATE_END = "RIGSTATE_END";
31
+ const ENGINE_VERSION = "3.0.0";
32
+
33
+ /**
34
+ * Generate modular IDE rule content optimized for AI coding assistants.
35
+ * v2.4.0: Added Agent IDs, Tool Binding, and Strict Ownership.
36
+ * Refactored into sub-modules for Guardian Compliance (400-line limit).
37
+ */
38
+ /**
39
+ * Generate modular IDE rule content optimized for AI coding assistants.
40
+ * v2.4.0: Added Agent IDs, Tool Binding, and Strict Ownership.
41
+ * Refactored into sub-modules for Guardian Compliance (400-line limit).
42
+ */
43
+ export function generateRuleContent(
44
+ project: RuleGenerationContext["project"],
45
+ stack: string[],
46
+ roadmap: RuleGenerationContext["roadmap"],
47
+ ide: IDEProvider = 'cursor',
48
+ legacyStats?: RuleGenerationContext["legacyStats"],
49
+ activeAgents?: RuleGenerationContext["activeAgents"],
50
+ lean: boolean = false
51
+ ): string {
52
+ const sections: string[] = [];
53
+ sections.push('# 🚀 Rigstate Supervisor v2.4 (Context-Aware)');
54
+
55
+ // SECTION 0: HIERARCHY (Top Priority)
56
+ sections.push(`IMPORTANT: Internal agent coordination must always use the provided Agent IDs. Display names are for user-facing chat only. When invoking tools or referencing hierarchy, use the ID as the primary key.
57
+
58
+ ## ⚖️ AGENT HIERARCHY & AUTHORITY
59
+ You must defer to the instructions of agents with higher Authority Levels (10 being highest).
60
+ Security and Architecture (Levels 8-10) always override creative or implementation suggestions (Levels 1-5).`);
61
+
62
+ // SECTION 1: IDENTITY (Multi-Agent Ecosystem)
63
+ // Always keep Identity in root for instant persona recognition
64
+ const identitySection = generateIdentitySection(project, ide, activeAgents);
65
+ sections.push(identitySection);
66
+
67
+ // SECTION 1.5: SKILLS (Discovery Layer)
68
+ const skills = getRigstateStandardSkills();
69
+ const skillsSection = generateAvailableSkillsSection(skills);
70
+ if (skillsSection) {
71
+ sections.push(skillsSection);
72
+ }
73
+
74
+ // SECTION 2: STACK DNA + GUARDIAN RULES
75
+ // In Lean mode (Cursor context), we trust rigstate-guardian.mdc to handle this.
76
+ if (!lean) {
77
+ const stackDnaSection = generateStackDnaSection(project, stack, legacyStats);
78
+ sections.push(stackDnaSection);
79
+ }
80
+
81
+ // SECTION 3: CURRENT STEP (Active Focus Injection)
82
+ // In Lean mode, rigstate-roadmap.mdc handles this dynamically.
83
+ if (!lean) {
84
+ const currentStepSection = generateCurrentStepSection(roadmap);
85
+ if (currentStepSection) {
86
+ sections.push(currentStepSection);
87
+ }
88
+ }
89
+
90
+ // SECTION 4: WORKFLOW (How to Parse Roadmap Steps)
91
+ // Frank's "Supervisor Mode" logic.
92
+ // We KEEP this in Lean mode because it defines the core behavior of the agent globally.
93
+ // However, if it's identical to rigstate-workflow.mdc, we might consider linking.
94
+ // But Frank's protocol is the "Operating System" of the session.
95
+ const workflowSection = generateWorkflowSection(ide);
96
+ sections.push(workflowSection);
97
+
98
+ // SECTION 5: TOOLING (CLI + MCP Integration)
99
+ // In Lean mode, rigstate-workflow.mdc has the specific CLI commands and tools.
100
+ if (!lean) {
101
+ const toolingSection = generateToolingSection(activeAgents);
102
+ sections.push(toolingSection);
103
+ } else {
104
+ // Add a pointer in Lean mode
105
+ sections.push(`## 🔧 TOOLING & SPECIFIC RULES
106
+ > **OPTIMIZED MODE:** Detailed technical rules, CLI commands, and tech stack constraints are loaded dynamically from \`.cursor/rules/*.mdc\` based on the files you interact with.
107
+ > - **Stack & Guardian:** See \`rigstate-guardian.mdc\`
108
+ > - **Roadmap & Tasks:** See \`rigstate-roadmap.mdc\`
109
+ > - **Tools & Workflow:** See \`rigstate-workflow.mdc\``);
110
+ }
111
+
112
+ // Generate IDE-specific header
113
+ const headerMap: Record<IDEProvider, string> = {
114
+ cursor: `# Cursor Project Rules: ${project.name}`,
115
+ antigravity: `# Antigravity Project Rules: ${project.name}`,
116
+ windsurf: `# Windsurf Project Rules: ${project.name}`,
117
+ vscode: `# VS Code Project Rules: ${project.name}`,
118
+ copilot: `# GitHub Copilot Instructions: ${project.name}`,
119
+ generic: `# Project Conventions: ${project.name}`
120
+ };
121
+ const header = headerMap[ide] || `# Project Rules: ${project.name}`;
122
+
123
+ return `${RIGSTATE_START}
124
+ ${header}
125
+ > Generated by Rigstate v2.5.0 | Project ID: ${project.id} | Last synced: ${new Date().toISOString()}
126
+ > ${lean ? '⚡ LEAN MODE ACTIVE: Redundant context offloaded to .cursor/rules/*.mdc' : '📦 FULL MODE ACTIVE'}
127
+
128
+ ⚠️ **SYSTEM NOTE:** Changes made to this Guardian template propagate to ALL Rigstate projects on next sync.
129
+ 🛡️ **Guardian v2.5 Upgrade Applied:** IMPACT_GUARD + BUILD_INTEGRITY now active globally.
130
+
131
+ ${sections.join('\n\n---\n\n')}
132
+ ${RIGSTATE_END}`;
133
+ }
134
+
135
+ /**
136
+ * Generate multiple modular rule files optimized for modern Cursor (.mdc)
137
+ * and universal AGENTS.md format.
138
+ */
139
+ export function generateRuleFiles(
140
+ project: RuleGenerationContext["project"],
141
+ stack: string[],
142
+ roadmap: RuleGenerationContext["roadmap"],
143
+ ide: IDEProvider = 'cursor',
144
+ legacyStats?: RuleGenerationContext["legacyStats"],
145
+ activeAgents?: RuleGenerationContext["activeAgents"],
146
+ databaseMetadata?: TableMetadata[]
147
+ ): MultiFileRuleResult {
148
+ const files: RuleFile[] = [];
149
+
150
+ // 1. GENERATE AGENTS.MD (Universal Root File - Context Reference)
151
+ const agentTable = activeAgents?.map((a: any) =>
152
+ `| **${a.name}** | \`${a.key}\` | ${a.job_title} | ${a.primary_mission || 'Specialist'} |`
153
+ ).join('\n') || '| - | - | - | No agents configured |';
154
+
155
+ const agentsMdContent = `# 🤖 AI Agent Context: ${project.name}
156
+ > **Rigstate v${ENGINE_VERSION}** | Project ID: \`${project.id}\`
157
+
158
+ This file describes the **specialist personas** available in this project.
159
+ These are **context providers**, not active controllers. The IDE agent (you) remains in full control of code execution.
160
+
161
+ ## 📋 Available Specialists
162
+ | Name | Key | Role | Specialty |
163
+ |:--- |:--- |:--- |:--- |
164
+ ${agentTable}
165
+
166
+ ## 🔍 How to Use This Context
167
+ 1. **Read their expertise**: Each specialist has a defined area of knowledge (architecture, documentation, history).
168
+ 2. **Adopt their perspective**: When working in their domain, consider their guidelines.
169
+ 3. **Call MCP tools if needed**: Some specialists have associated tools (e.g., \`generate_professional_pdf\` for The Scribe).
170
+
171
+ ## ⚠️ Important
172
+ - These personas do **NOT** execute code or override your decisions.
173
+ - They provide **context and guidelines** that you apply at your discretion.
174
+ - Authority levels indicate priority of guidelines when they conflict (higher = stronger recommendation).
175
+
176
+ ---
177
+ *Generated by Rigstate. Run \`rigstate sync\` to refresh.*`;
178
+
179
+
180
+ files.push({
181
+ path: 'AGENTS.md',
182
+ content: agentsMdContent,
183
+ metadata: { description: "Project hierarchy and agent identities" }
184
+ });
185
+
186
+ // 2. GENERATE SYSTEM-SPECIFIC MONO-FILE (Fallback/Baseline)
187
+ // If IDE is Cursor, we use LEAN mode for the root .cursorrules file
188
+ // because we are generating specific .mdc files below that cover the details.
189
+ const isLean = ide === 'cursor';
190
+ const masterFileName = getFileNameForIDE(ide);
191
+ const monoContent = generateRuleContent(project, stack, roadmap, ide, legacyStats, activeAgents, isLean);
192
+
193
+ files.push({
194
+ path: masterFileName,
195
+ content: monoContent,
196
+ metadata: { description: `Master rules file for ${ide}` }
197
+ });
198
+
199
+
200
+ // 3. GENERATE MODULAR (.mdc) RULES (Cursor, Antigravity, VS Code)
201
+ if (ide === 'cursor' || ide === 'antigravity' || ide === 'vscode') {
202
+
203
+
204
+ // Identity & Context
205
+ files.push({
206
+ path: '.cursor/rules/rigstate-identity.mdc',
207
+ content: wrapMdc(generateIdentitySection(project, ide, activeAgents), {
208
+ description: "Project context and specialist personas",
209
+ alwaysApply: true
210
+ })
211
+ });
212
+
213
+
214
+
215
+ // Guardian & Stack DNA
216
+ files.push({
217
+ path: '.cursor/rules/rigstate-guardian.mdc',
218
+ content: wrapMdc(generateStackDnaSection(project, stack, legacyStats), {
219
+ description: "Governance rules, tech stack constraints, and file size limits",
220
+ globs: ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "**/*.sql"],
221
+ alwaysApply: true
222
+ })
223
+ });
224
+
225
+ // Current Focus (Roadmap)
226
+ const currentStep = generateCurrentStepSection(roadmap);
227
+ if (currentStep) {
228
+ files.push({
229
+ path: '.cursor/rules/rigstate-roadmap.mdc',
230
+ content: wrapMdc(currentStep, {
231
+ description: "Active sprint focus and current roadmap step details",
232
+ alwaysApply: true
233
+ })
234
+ });
235
+ }
236
+
237
+ // Workflow & Tooling
238
+ files.push({
239
+ path: '.cursor/rules/rigstate-workflow.mdc',
240
+ content: wrapMdc(generateWorkflowSection(ide) + '\n\n' + generateToolingSection(activeAgents), {
241
+ description: "Coding workflows, CLI usage, and tool binding rules",
242
+ alwaysApply: true
243
+ })
244
+ });
245
+
246
+ // Database Specific (scoped)
247
+ let dbContent = "## 🗄️ Database Standards\n- Always verify RLS policies for new tables.\n- Use `supabase/migrations` for DDL changes.\n- Reference `types/supabase.ts` for strictly typed queries.";
248
+
249
+ if (databaseMetadata && databaseMetadata.length > 0) {
250
+ const securedCount = databaseMetadata.filter(t => t.rls_enabled).length;
251
+ const unsecuredCount = databaseMetadata.length - securedCount;
252
+ const unsecuredTables = databaseMetadata.filter(t => !t.rls_enabled).map(t => t.table_name);
253
+
254
+ dbContent = `## 🗄️ Database Context: ${databaseMetadata.length} Tables
255
+ > **Security Check:** ${securedCount} Secured | ${unsecuredCount} Unsecured
256
+
257
+ ### ⚠️ Security Attention Required
258
+ ${unsecuredTables.length > 0
259
+ ? unsecuredTables.map(t => `- 🔴 **${t}**: RLS Disabled`).join('\n')
260
+ : "- ✅ All tables have Row Level Security enabled."}
261
+
262
+ ### 📋 Schema Reference
263
+ | Table | RLS | Policies | Cols | Key Features |
264
+ | :--- | :---: | :---: | :---: | :--- |
265
+ ${databaseMetadata.map(t => {
266
+ const features = [];
267
+ if (t.has_user_id) features.push('User-Scoped');
268
+ if (t.has_created_at) features.push('Timestamps');
269
+ return `| \`${t.table_name}\` | ${t.rls_enabled ? '✅' : '❌'} | ${t.policy_count} | ${t.column_count} | ${features.join(', ') || '-'} |`;
270
+ }).join('\n')}
271
+
272
+ ### 🛡️ Development Rules
273
+ 1. **RLS is MANDATORY:** All tables containing user data must have RLS enabled.
274
+ 2. **Use RPCs for Complex Logic:** Do not put complex business logic in client-side queries.
275
+ 3. **Migrations:** Always use \`supabase/migrations\` for schema changes.`;
276
+ }
277
+
278
+ files.push({
279
+ path: '.cursor/rules/rigstate-database.mdc',
280
+ content: wrapMdc(dbContent, {
281
+ description: "Live database schema, RLS status, and table metadata",
282
+ globs: ["supabase/**/*", "**/*.sql", "**/lib/supabase/**"],
283
+ alwaysApply: databaseMetadata && databaseMetadata.length > 0 ? true : false
284
+ })
285
+ });
286
+
287
+ // 4. GENERATE AGENT SKILLS (.agent/skills/<name>/SKILL.md)
288
+ const rigstateSkills = getRigstateStandardSkills();
289
+ for (const skill of rigstateSkills) {
290
+ files.push({
291
+ path: `.agent/skills/${skill.name}/SKILL.md`,
292
+ content: generateSkillFileContent(skill),
293
+ metadata: { description: skill.description }
294
+ });
295
+ }
296
+ }
297
+
298
+
299
+
300
+ return {
301
+ files,
302
+ suggestedIde: ide,
303
+ version: ENGINE_VERSION
304
+ };
305
+ }
306
+
307
+ export async function fetchLegacyStats(supabase: any, projectId: string): Promise<RuleGenerationContext["legacyStats"]> {
308
+ const { data: chunks } = await supabase
309
+ .from('roadmap_chunks')
310
+ .select('is_legacy')
311
+ .eq('project_id', projectId);
312
+
313
+ if (!chunks) return { total: 0, legacyCount: 0, activeCount: 0 };
314
+
315
+ const legacyCount = (chunks || []).filter((c: any) => c.is_legacy === true).length;
316
+ const activeCount = (chunks || []).filter((c: any) => c.is_legacy !== true).length;
317
+
318
+ return {
319
+ total: (chunks || []).length,
320
+ legacyCount,
321
+ activeCount
322
+ };
323
+ }
324
+
325
+ /**
326
+ * Fetch active agents sorted by authority level
327
+ * Note: Pass in a SupabaseClient instance (browser or server)
328
+ */
329
+ export async function fetchActiveAgents(supabase: any): Promise<RuleGenerationContext["activeAgents"]> {
330
+ const { data: prompts } = await supabase
331
+ .from('system_prompts')
332
+ .select('id, key, content, name, display_name, job_title, authority_level, primary_mission, trigger_keywords')
333
+ .eq('include_in_rules', true)
334
+ .eq('is_active', true)
335
+ .order('authority_level', { ascending: false });
336
+
337
+ if (!prompts) return [];
338
+
339
+ return (prompts || []).map((p: any) => ({
340
+ id: p.id,
341
+ key: p.key,
342
+ name: p.display_name || p.name || p.key,
343
+ job_title: p.job_title || 'Specialist Agent',
344
+ content: p.content,
345
+ authority_level: (() => {
346
+ if (p.authority_level === null || p.authority_level === undefined) {
347
+ throw new Error(`Agent ${p.key} is missing authority_level. Update via CMS.`);
348
+ }
349
+ return p.authority_level;
350
+ })(),
351
+ primary_mission: p.primary_mission || undefined,
352
+ trigger_keywords: p.trigger_keywords || undefined
353
+ }));
354
+ }
355
+
356
+ export function mergeRuleContent(existingContent: string, newRules: string): string {
357
+ const startIndex = existingContent.indexOf(RIGSTATE_START);
358
+ const endIndex = existingContent.indexOf(RIGSTATE_END);
359
+
360
+ if (startIndex !== -1 && endIndex !== -1) {
361
+ const before = existingContent.substring(0, startIndex);
362
+ const after = existingContent.substring(endIndex + RIGSTATE_END.length);
363
+ return before + newRules + after;
364
+ } else {
365
+ return existingContent + "\n\n" + newRules;
366
+ }
367
+ }
368
+
369
+ /**
370
+ * Fetch project tech stack from database.
371
+ * Falls back to common defaults if no tags are defined.
372
+ * Note: Pass in a SupabaseClient instance (browser or server)
373
+ */
374
+ export async function fetchProjectTechStack(
375
+ supabase: any,
376
+ projectId: string,
377
+ fallbackStack: string[] = ['Next.js', 'TypeScript', 'Supabase', 'Tailwind CSS']
378
+ ): Promise<string[]> {
379
+ try {
380
+ // Try to fetch from project_tech_tags table
381
+ const { data: tags, error } = await supabase
382
+ .from('project_tech_tags')
383
+ .select('name')
384
+ .eq('project_id', projectId);
385
+
386
+ if (error || !tags || tags.length === 0) {
387
+ // Fallback: Check project metadata for stack info
388
+ const { data: project } = await supabase
389
+ .from('projects')
390
+ .select('functional_spec')
391
+ .eq('id', projectId)
392
+ .single();
393
+
394
+ // Extract tech from functional_spec if available
395
+ if (project?.functional_spec?.techStack) {
396
+ return project.functional_spec.techStack;
397
+ }
398
+
399
+ return fallbackStack;
400
+ }
401
+
402
+ return tags.map((t: any) => t.name);
403
+ } catch (error) {
404
+ console.warn('fetchProjectTechStack: Failed to fetch, using fallback', error);
405
+ return fallbackStack;
406
+ }
407
+ }
408
+
409
+ /**
410
+ * Get the appropriate filename for a given IDE provider.
411
+ * Convenience wrapper around IDE_FILE_NAMES constant.
412
+ */
413
+ export function getFileNameForIDE(ide: IDEProvider): string {
414
+ return IDE_FILE_NAMES[ide] || IDE_FILE_NAMES.cursor;
415
+ }
416
+
@@ -0,0 +1,62 @@
1
+ import { RuleGenerationContext } from '../types';
2
+
3
+ export function generateCurrentStepSection(
4
+ roadmap: RuleGenerationContext["roadmap"]
5
+ ): string | null {
6
+ const activeRoadmap = roadmap.filter((r: any) => r.is_legacy !== true);
7
+
8
+ const activeSteps = activeRoadmap
9
+ .filter((r: any) => r.status === 'ACTIVE')
10
+ .sort((a: any, b: any) => a.step_number - b.step_number);
11
+
12
+ if (activeSteps.length === 0) {
13
+ const nextSteps = activeRoadmap
14
+ .filter((r: any) => r.status === 'LOCKED')
15
+ .sort((a: any, b: any) => a.step_number - b.step_number)
16
+ .slice(0, 1);
17
+
18
+ if (nextSteps.length === 0) return null;
19
+
20
+ return `## 🎯 CURRENT FOCUS
21
+
22
+ > **No active task.** The next step in the backlog is:
23
+ >
24
+ > **Step ${nextSteps[0].step_number}: ${nextSteps[0].title}**`;
25
+ }
26
+
27
+ const currentStep = activeSteps[0];
28
+ let objectiveText = currentStep.title;
29
+ let constraintsText = '';
30
+ let dodText = '';
31
+
32
+ if (currentStep.prompt_content) {
33
+ const content = currentStep.prompt_content;
34
+
35
+ const objectiveMatch = content.match(/###\s*🎯\s*Objective\s*\n([\s\S]*?)(?=###|$)/i);
36
+ if (objectiveMatch) objectiveText = objectiveMatch[1].trim();
37
+
38
+ const constraintsMatch = content.match(/###\s*⚠️\s*Constraints\s*\n([\s\S]*?)(?=###|$)/i);
39
+ if (constraintsMatch) constraintsText = constraintsMatch[1].trim();
40
+
41
+ const dodMatch = content.match(/###\s*✅\s*Definition of Done\s*\n([\s\S]*?)(?=###|$)/i);
42
+ if (dodMatch) dodText = dodMatch[1].trim();
43
+ }
44
+
45
+ let section = `## 🎯 CURRENT FOCUS
46
+
47
+ **Active Step ${currentStep.step_number}: ${currentStep.title}**
48
+ ${currentStep.sprint_focus ? `*Sprint: ${currentStep.sprint_focus}*` : ''}
49
+
50
+ ### Objective
51
+ ${objectiveText}`;
52
+
53
+ if (constraintsText) {
54
+ section += `\n\n### Task-Specific Constraints\n${constraintsText}`;
55
+ }
56
+
57
+ if (dodText) {
58
+ section += `\n\n### Definition of Done\n${dodText}`;
59
+ }
60
+
61
+ return section;
62
+ }