Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
a720a9e
feat(types): add ScopeMetadata interface, Rule.metadata, and ProjectC…
gpolanco Apr 12, 2026
9bd9e7a
feat(types): refactor Bridge into DirectoryBridge | MarkerBridge disc…
gpolanco Apr 12, 2026
ae4e43d
feat(core): add scopeToFilename and cleanStaleFiles utilities
gpolanco Apr 12, 2026
740a882
feat(core): add legacy file detection and migration utilities
gpolanco Apr 12, 2026
b1e9eec
feat(bridges): refactor Claude, Cursor, and Windsurf to multi-file ou…
gpolanco Apr 12, 2026
131e250
test(bridges): verify Copilot and Gemini MarkerBridge behavior preserved
gpolanco Apr 12, 2026
b9b233b
fix(tests): update existing tests for multi-file bridge output
gpolanco Apr 12, 2026
9392891
fix(tests): add missing global property to ProjectConfig in test fixt…
gpolanco Apr 12, 2026
7775e1f
feat(core): add config version validation and scope metadata parsing
gpolanco Apr 12, 2026
83d6ef6
feat(compile): integrate DirectoryBridge/MarkerBridge pipeline with s…
gpolanco Apr 12, 2026
a114df1
feat(explain): update explain command for multi-file DirectoryBridge …
gpolanco Apr 12, 2026
e116db9
fix(types): make ProjectConfig.global required instead of optional
gpolanco Apr 12, 2026
c619f88
test: add comprehensive tests for Phase 1 tasks 1.10-1.18
gpolanco Apr 12, 2026
5dc770d
feat(cli): complete phase 2 canonical distribution and hardening
gpolanco Apr 12, 2026
7c6335c
feat(cli): complete phase 3 global scope support
gpolanco Apr 12, 2026
9d9738d
feat(cli): complete phase 4 visual overhaul
gpolanco Apr 12, 2026
4dbcdb6
feat(registry): add registry manifest, ETag caching, search, and vers…
gpolanco Apr 12, 2026
35efaa9
fix(banner): replace rainbow gradient with subtle gray gradient
gpolanco Apr 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .github/workflows/generate-registry.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Generate Registry

on:
push:
branches:
- main
paths:
- content/**
paths-ignore:
- content/registry.json

permissions:
contents: write

jobs:
generate-registry:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22

- name: Generate registry artifact
run: node scripts/generate-registry.js

- name: Commit registry updates
run: |
if git diff --quiet -- content/registry.json; then
echo "No registry changes detected"
exit 0
fi

git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add content/registry.json
git commit -m "chore: regenerate registry.json"
git push
187 changes: 187 additions & 0 deletions content/registry.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
{
"version": 1,
"generated_at": "2026-04-12T15:46:19.045Z",
"rules": [
{
"path": "css/tailwind",
"name": "tailwind",
"description": "Utility-first Tailwind CSS conventions and design tokens",
"version": "0.1.0",
"scope": "conventions",
"tags": [
"tailwind",
"css",
"styling"
],
"size_bytes": 874
},
{
"path": "frontend/accessibility",
"name": "accessibility",
"description": "Accessibility best practices for AI coding agents",
"version": "0.1.0",
"scope": "conventions",
"tags": [
"frontend",
"accessibility",
"a11y",
"html"
],
"size_bytes": 2781
},
{
"path": "frontend/design-guidelines",
"name": "design-guidelines",
"description": "UI design principles for AI coding agents",
"version": "0.1.0",
"scope": "design",
"tags": [
"frontend",
"design",
"ui",
"ux"
],
"size_bytes": 3476
},
{
"path": "frontend/performance",
"name": "performance",
"description": "Frontend performance optimization rules",
"version": "0.1.0",
"scope": "performance",
"tags": [
"frontend",
"performance",
"core-web-vitals"
],
"size_bytes": 2558
},
{
"path": "javascript/nextjs",
"name": "nextjs",
"description": "Next.js App Router patterns and React Server Components",
"version": "0.1.0",
"scope": "architecture",
"tags": [
"nextjs",
"react",
"app-router",
"rsc"
],
"size_bytes": 1250
},
{
"path": "javascript/react",
"name": "react",
"description": "React conventions and best practices for AI coding agents",
"version": "0.1.0",
"scope": "conventions",
"tags": [
"react",
"frontend",
"components",
"hooks"
],
"size_bytes": 1314
},
{
"path": "security/auth-patterns",
"name": "auth-patterns",
"description": "Authentication and authorization best practices",
"version": "0.1.0",
"scope": "security",
"tags": [
"security",
"auth",
"authentication",
"authorization"
],
"size_bytes": 2762
},
{
"path": "security/supabase-rls",
"name": "supabase-rls",
"description": "Supabase Row-Level Security enforcement and auth patterns",
"version": "0.1.0",
"scope": "security",
"tags": [
"supabase",
"rls",
"security",
"database"
],
"size_bytes": 958
},
{
"path": "testing/vitest",
"name": "vitest",
"description": "Vitest testing patterns and best practices",
"version": "0.1.0",
"scope": "testing",
"tags": [
"vitest",
"testing",
"unit-tests"
],
"size_bytes": 1174
},
{
"path": "typescript/strict",
"name": "strict",
"description": "Strict TypeScript conventions for professional codebases",
"version": "0.1.0",
"scope": "conventions",
"tags": [
"typescript",
"strict",
"types"
],
"size_bytes": 1124
},
{
"path": "workflow/debugging",
"name": "debugging",
"description": "Systematic debugging methodology for AI coding agents",
"version": "0.1.0",
"scope": "workflow",
"tags": [
"workflow",
"debugging",
"methodology"
],
"size_bytes": 2464
},
{
"path": "workflow/git-conventions",
"name": "git-conventions",
"description": "Git workflow and commit conventions",
"version": "0.1.0",
"scope": "workflow",
"tags": [
"git",
"workflow",
"conventions"
],
"size_bytes": 2010
},
{
"path": "workflow/spec-driven",
"name": "spec-driven",
"description": "Spec-driven development workflow: spec, plan, build, ship",
"version": "0.1.0",
"scope": "workflow",
"tags": [
"workflow",
"spec-driven",
"methodology"
],
"size_bytes": 1441
}
],
"assets": {
"commands": [],
"templates": [],
"hooks": [],
"presets": []
}
}
4 changes: 2 additions & 2 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,11 @@
"test:e2e": "tsc && tsc -p tsconfig.test.json && find .test-build/tests/e2e -name '*.test.js' -exec node --test {} +"
},
"dependencies": {
"@inquirer/prompts": "^7.0.0",
"@clack/prompts": "^0.9.0",
"chokidar": "^3.6.0",
"commander": "^13.0.0",
"yaml": "^2.7.0",
"chalk": "^5.4.0"
"picocolors": "^1.1.0"
},
"devDependencies": {
"typescript": "^5.7.0",
Expand Down
82 changes: 58 additions & 24 deletions packages/cli/src/bridges/claude.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,51 @@
import type { Bridge, Rule, ProjectConfig } from './types.js';
import type { DirectoryBridge, Rule, ProjectConfig, ScopeMetadata } from './types.js';
import { filterRules, groupByScope, formatScopeHeading } from '../core/helpers.js';
import { scopeToFilename } from '../core/scope-filename.js';

const GENERATED_COMMENT = '<!-- Generated by dev-workflows. Do not edit manually. -->';

function buildFrontmatter(metadata?: ScopeMetadata): string {
if (!metadata?.paths || metadata.paths.length === 0) {
return '';
}

function buildMarkdown(rules: Rule[]): string {
const lines: string[] = [
'# Project Rules',
'---',
'paths:',
];
for (const p of metadata.paths) {
lines.push(` - "${p}"`);
}
lines.push('---');

const filtered = filterRules(rules);
const grouped = groupByScope(filtered);

for (const [scope, scopeRules] of grouped) {
lines.push('', `## ${formatScopeHeading(scope)}`);
lines.push('');
for (const rule of scopeRules) {
const contentLines = rule.content.split('\n');
const first = contentLines[0];
if (first !== undefined) {
lines.push(`- ${first}`);
}
for (let i = 1; i < contentLines.length; i++) {
const line = contentLines[i];
if (line !== undefined) {
lines.push(` ${line}`);
}
return lines.join('\n');
}

function buildScopeMarkdown(scope: string, rules: Rule[]): string {
const lines: string[] = [];

// Get metadata from the first rule in the scope (all rules in a scope share metadata)
const metadata = rules[0]?.metadata;
const frontmatter = buildFrontmatter(metadata);

if (frontmatter) {
lines.push(frontmatter);
}

lines.push(GENERATED_COMMENT);
lines.push(`# ${formatScopeHeading(scope)}`);
lines.push('');

for (const rule of rules) {
const contentLines = rule.content.split('\n');
const first = contentLines[0];
if (first !== undefined) {
lines.push(`- ${first}`);
}
for (let i = 1; i < contentLines.length; i++) {
const line = contentLines[i];
if (line !== undefined) {
lines.push(` ${line}`);
}
}
}
Expand All @@ -31,14 +54,25 @@ function buildMarkdown(rules: Rule[]): string {
return lines.join('\n');
}

export const claudeBridge: Bridge = {
export const claudeBridge: DirectoryBridge = {
id: 'claude',
outputPaths: ['CLAUDE.md'],
usesMarkers: true,
kind: 'directory',
outputDir: '.claude/rules',
filePrefix: 'dwf-',
fileExtension: '.md',

compile(rules: Rule[], _config: ProjectConfig): Map<string, string> {
const output = new Map<string, string>();
output.set('CLAUDE.md', buildMarkdown(rules));

const filtered = filterRules(rules);
const grouped = groupByScope(filtered);

for (const [scope, scopeRules] of grouped) {
const filename = scopeToFilename(scope, 'dwf-', '.md');
const key = `.claude/rules/${filename}`;
output.set(key, buildScopeMarkdown(scope, scopeRules));
}

return output;
},
};
5 changes: 3 additions & 2 deletions packages/cli/src/bridges/copilot.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Bridge, Rule, ProjectConfig } from './types.js';
import type { MarkerBridge, Rule, ProjectConfig } from './types.js';
import { filterRules, groupByScope, formatScopeHeading } from '../core/helpers.js';

function buildMarkdown(rules: Rule[]): string {
Expand Down Expand Up @@ -31,8 +31,9 @@ function buildMarkdown(rules: Rule[]): string {
return lines.join('\n');
}

export const copilotBridge: Bridge = {
export const copilotBridge: MarkerBridge = {
id: 'copilot',
kind: 'marker',
outputPaths: ['.github/copilot-instructions.md'],
usesMarkers: true,

Expand Down
Loading
Loading