feat: initialize monorepo with package.json for CCW workflow platform

This commit is contained in:
catlog22
2026-02-03 14:42:20 +08:00
parent 5483a72e9f
commit 39b80b3386
267 changed files with 99597 additions and 2658 deletions

View File

@@ -893,6 +893,10 @@ export interface Skill {
version?: string;
author?: string;
location?: 'project' | 'user';
folderName?: string;
path?: string;
allowedTools?: string[];
supportingFiles?: string[];
}
export interface SkillsResponse {
@@ -967,6 +971,23 @@ export async function disableSkill(
});
}
/**
* Fetch detailed information about a specific skill
* @param skillName - Name of the skill to fetch
* @param location - Location of the skill (project or user)
* @param projectPath - Optional project path
*/
export async function fetchSkillDetail(
skillName: string,
location: 'project' | 'user',
projectPath?: string
): Promise<{ skill: Skill }> {
const url = projectPath
? `/api/skills/${encodeURIComponent(skillName)}?location=${location}&path=${encodeURIComponent(projectPath)}`
: `/api/skills/${encodeURIComponent(skillName)}?location=${location}`;
return fetchApi<{ skill: Skill }>(url);
}
// ========== Commands API ==========
export interface Command {
@@ -1214,6 +1235,34 @@ export async function deleteMemory(memoryId: string, projectPath?: string): Prom
});
}
/**
* Archive a memory entry for a specific workspace
* @param memoryId - Memory ID to archive
* @param projectPath - Optional project path to filter data by workspace
*/
export async function archiveMemory(memoryId: string, projectPath?: string): Promise<void> {
const url = projectPath
? `/api/core-memory/memories/${encodeURIComponent(memoryId)}/archive?path=${encodeURIComponent(projectPath)}`
: `/api/core-memory/memories/${encodeURIComponent(memoryId)}/archive`;
return fetchApi<void>(url, {
method: 'POST',
});
}
/**
* Unarchive a memory entry for a specific workspace
* @param memoryId - Memory ID to unarchive
* @param projectPath - Optional project path to filter data by workspace
*/
export async function unarchiveMemory(memoryId: string, projectPath?: string): Promise<void> {
const url = projectPath
? `/api/core-memory/memories/${encodeURIComponent(memoryId)}/unarchive?path=${encodeURIComponent(projectPath)}`
: `/api/core-memory/memories/${encodeURIComponent(memoryId)}/unarchive`;
return fetchApi<void>(url, {
method: 'POST',
});
}
// ========== Project Overview API ==========
export interface TechnologyStack {
@@ -3829,6 +3878,9 @@ export interface CliSettingsEndpoint {
};
model?: string;
includeCoAuthoredBy?: boolean;
settingsFile?: string;
availableModels?: string[];
tags?: string[];
};
enabled: boolean;
createdAt: string;
@@ -3859,6 +3911,9 @@ export interface SaveCliSettingsRequest {
};
model?: string;
includeCoAuthoredBy?: boolean;
settingsFile?: string;
availableModels?: string[];
tags?: string[];
};
enabled?: boolean;
}

View File

@@ -0,0 +1,110 @@
// ========================================
// Chart Theme Configuration
// ========================================
// Extracts Tailwind CSS custom properties for Recharts color palette
/**
* Chart color palette extracted from CSS custom properties
*/
export interface ChartColors {
primary: string;
success: string;
warning: string;
info: string;
destructive: string;
indigo: string;
orange: string;
muted: string;
}
/**
* Converts HSL CSS variable to hex color for Recharts
* @param hslString - HSL string in format "h s% l%"
* @returns Hex color string
*/
function hslToHex(hslString: string): string {
const [h, s, l] = hslString.split(' ').map((v) => parseFloat(v));
const hue = h / 360;
const saturation = s / 100;
const lightness = l / 100;
const hueToRgb = (p: number, q: number, t: number) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
const q = lightness < 0.5 ? lightness * (1 + saturation) : lightness + saturation - lightness * saturation;
const p = 2 * lightness - q;
const r = Math.round(hueToRgb(p, q, hue + 1 / 3) * 255);
const g = Math.round(hueToRgb(p, q, hue) * 255);
const b = Math.round(hueToRgb(p, q, hue - 1 / 3) * 255);
return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
}
/**
* Get chart colors from CSS custom properties
* @returns Chart color palette object
*/
export function getChartColors(): ChartColors {
const root = document.documentElement;
const style = getComputedStyle(root);
const getColor = (varName: string): string => {
const hsl = style.getPropertyValue(varName).trim();
if (!hsl) {
// Fallback colors if CSS variables are not available
const fallbacks: Record<string, string> = {
'--primary': '220 60% 65%',
'--success': '142 71% 45%',
'--warning': '38 92% 50%',
'--info': '220 60% 60%',
'--destructive': '8 75% 55%',
'--indigo': '239 65% 60%',
'--orange': '25 90% 55%',
'--muted': '220 20% 96%',
};
return hslToHex(fallbacks[varName] || '220 60% 65%');
}
return hslToHex(hsl);
};
return {
primary: getColor('--primary'),
success: getColor('--success'),
warning: getColor('--warning'),
info: getColor('--info'),
destructive: getColor('--destructive'),
indigo: getColor('--indigo'),
orange: getColor('--orange'),
muted: getColor('--muted'),
};
}
/**
* Status color mapping for workflow status pie chart
*/
export const STATUS_COLORS: Record<string, keyof ChartColors> = {
planning: 'info',
in_progress: 'primary',
completed: 'success',
paused: 'warning',
archived: 'muted',
};
/**
* Task type color mapping for task type bar chart
*/
export const TASK_TYPE_COLORS: Record<string, keyof ChartColors> = {
implementation: 'primary',
bugfix: 'destructive',
refactor: 'indigo',
documentation: 'info',
testing: 'success',
other: 'muted',
};

View File

@@ -0,0 +1,218 @@
// ========================================
// Web Vitals Performance Monitoring
// ========================================
// Measures and logs Core Web Vitals metrics (LCP, INP, CLS)
// These are essential for measuring page performance and user experience
import {
onCLS,
onFCP,
onINP,
onLCP,
onTTFB,
type Metric,
} from 'web-vitals';
/**
* Threshold values for Web Vitals (WCAG recommendations)
* @see https://web.dev/metrics/
*/
export const VITALS_THRESHOLDS = {
LCP: 2500, // Largest Contentful Paint - target < 2.5s
INP: 200, // Interaction to Next Paint - target < 200ms (replaces FID)
CLS: 0.1, // Cumulative Layout Shift - target < 0.1
FCP: 1800, // First Contentful Paint - target < 1.8s
TTFB: 600, // Time to First Byte - target < 600ms
} as const;
/**
* Web Vitals metric entry
*/
export interface VitalsMetric extends Metric {
vitalsName: string;
isBad: boolean;
rating: 'good' | 'needs-improvement' | 'poor';
}
/**
* Web Vitals callback function
*/
export type VitalsCallback = (metric: VitalsMetric) => void;
/**
* Determine if a metric is within good range
*/
function isGoodMetric(name: string, value: number): boolean {
switch (name) {
case 'LCP':
return value <= VITALS_THRESHOLDS.LCP;
case 'INP':
return value <= VITALS_THRESHOLDS.INP;
case 'CLS':
return value <= VITALS_THRESHOLDS.CLS;
case 'FCP':
return value <= VITALS_THRESHOLDS.FCP;
case 'TTFB':
return value <= VITALS_THRESHOLDS.TTFB;
default:
return true;
}
}
/**
* Get rating for a metric
*/
function getMetricRating(name: string, value: number): 'good' | 'needs-improvement' | 'poor' {
const goodThreshold = VITALS_THRESHOLDS[name as keyof typeof VITALS_THRESHOLDS];
if (!goodThreshold) return 'good';
// Good threshold
if (value <= goodThreshold) {
return 'good';
}
// Poor threshold (typically 1.25x of good threshold)
const poorThreshold = goodThreshold * 1.25;
if (value <= poorThreshold) {
return 'needs-improvement';
}
return 'poor';
}
/**
* Initialize Web Vitals monitoring
*
* @param callback - Function to call when metrics are collected
* @param reportAllMetrics - Include FCP and TTFB (optional, default false)
*
* @example
* ```ts
* initWebVitals((metric) => {
* console.log(`${metric.name}: ${metric.value}`);
* if (metric.isBad) {
* analytics.trackVitalsIssue(metric);
* }
* });
* ```
*/
export function initWebVitals(
callback: VitalsCallback,
reportAllMetrics = false
): void {
// Core Web Vitals (always measured)
onLCP((metric) => {
const m: VitalsMetric = {
...metric,
vitalsName: 'LCP',
isBad: !isGoodMetric('LCP', metric.value),
rating: getMetricRating('LCP', metric.value),
};
callback(m);
});
onINP((metric) => {
const m: VitalsMetric = {
...metric,
vitalsName: 'INP',
isBad: !isGoodMetric('INP', metric.value),
rating: getMetricRating('INP', metric.value),
};
callback(m);
});
onCLS((metric) => {
const m: VitalsMetric = {
...metric,
vitalsName: 'CLS',
isBad: !isGoodMetric('CLS', metric.value),
rating: getMetricRating('CLS', metric.value),
};
callback(m);
});
// Optional metrics
if (reportAllMetrics) {
onFCP((metric) => {
const m: VitalsMetric = {
...metric,
vitalsName: 'FCP',
isBad: !isGoodMetric('FCP', metric.value),
rating: getMetricRating('FCP', metric.value),
};
callback(m);
});
onTTFB((metric) => {
const m: VitalsMetric = {
...metric,
vitalsName: 'TTFB',
isBad: !isGoodMetric('TTFB', metric.value),
rating: getMetricRating('TTFB', metric.value),
};
callback(m);
});
}
}
/**
* Log Web Vitals metrics to console
* Useful for development and debugging
*/
export function logWebVitals(): void {
initWebVitals((metric) => {
const style = metric.isBad
? 'background: #ff6b6b; color: white; padding: 2px 6px; border-radius: 3px;'
: 'background: #51cf66; color: white; padding: 2px 6px; border-radius: 3px;';
console.log(
`%c${metric.vitalsName}%c ${metric.value.toFixed(2)}ms (${metric.rating})`,
style,
'background: none;'
);
});
}
/**
* Send Web Vitals to analytics service
*
* @param endpoint - Analytics endpoint URL
*
* @example
* ```ts
* sendWebVitalsToAnalytics('/api/analytics/vitals');
* ```
*/
export function sendWebVitalsToAnalytics(endpoint: string): void {
initWebVitals((metric) => {
// Only send bad metrics to reduce noise
if (!metric.isBad) return;
// Queue the metric and send in batches
const data = {
metric: metric.vitalsName,
value: metric.value,
rating: metric.rating,
delta: metric.delta,
url: window.location.href,
timestamp: new Date().toISOString(),
};
// Use sendBeacon for reliability (survives page unload)
if (navigator.sendBeacon) {
navigator.sendBeacon(endpoint, JSON.stringify(data));
} else {
// Fallback to fetch
fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
keepalive: true,
}).catch(() => {
// Silently fail to avoid disrupting user experience
});
}
});
}
export default initWebVitals;