workflow-agent-cli 2.1.0 → 2.2.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.
- package/README.md +33 -17
- package/dist/{chunk-B27W7GWP.js → chunk-IPMSSOXR.js} +55 -21
- package/dist/chunk-IPMSSOXR.js.map +1 -0
- package/dist/{chunk-X2NQJ2ZY.js → chunk-NMHWD2GA.js} +11 -6
- package/dist/chunk-NMHWD2GA.js.map +1 -0
- package/dist/cli/index.js +455 -137
- package/dist/cli/index.js.map +1 -1
- package/dist/config/index.js +1 -1
- package/dist/index.js +2 -2
- package/dist/scripts/postinstall.js +6 -2
- package/dist/scripts/postinstall.js.map +1 -1
- package/dist/validators/index.js +1 -1
- package/package.json +15 -15
- package/templates/AGENT_EDITING_INSTRUCTIONS.md +226 -39
- package/templates/COMPONENT_LIBRARY.md +23 -17
- package/templates/CUSTOM_SCOPE_TEMPLATE.md +5 -4
- package/templates/LIBRARY_INVENTORY.md +20 -20
- package/templates/SCOPE_CREATION_WORKFLOW.md +39 -11
- package/templates/SELF_IMPROVEMENT_MANDATE.md +24 -18
- package/templates/SINGLE_SOURCE_OF_TRUTH.md +59 -42
- package/templates/TESTING_STRATEGY.md +79 -69
- package/templates/_TEMPLATE_EXAMPLE.md +2 -1
- package/LICENSE +0 -21
- package/dist/chunk-B27W7GWP.js.map +0 -1
- package/dist/chunk-X2NQJ2ZY.js.map +0 -1
|
@@ -51,12 +51,12 @@ Located in `vitest.config.ts`:
|
|
|
51
51
|
export default defineConfig({
|
|
52
52
|
test: {
|
|
53
53
|
globals: true,
|
|
54
|
-
environment:
|
|
55
|
-
setupFiles: [
|
|
56
|
-
include: [
|
|
54
|
+
environment: "jsdom",
|
|
55
|
+
setupFiles: ["./test/setup.ts"],
|
|
56
|
+
include: ["**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"],
|
|
57
57
|
coverage: {
|
|
58
|
-
provider:
|
|
59
|
-
reporter: [
|
|
58
|
+
provider: "v8",
|
|
59
|
+
reporter: ["text", "json", "html"],
|
|
60
60
|
},
|
|
61
61
|
},
|
|
62
62
|
});
|
|
@@ -149,16 +149,16 @@ describe("TaskCard", () => {
|
|
|
149
149
|
#### Mocking Hooks
|
|
150
150
|
|
|
151
151
|
```typescript
|
|
152
|
-
import { vi } from
|
|
152
|
+
import { vi } from "vitest";
|
|
153
153
|
|
|
154
154
|
// Mock a custom hook
|
|
155
|
-
vi.mock(
|
|
155
|
+
vi.mock("@/hooks/useAuthorization", () => ({
|
|
156
156
|
useAuthorization: () => ({
|
|
157
157
|
can: {
|
|
158
158
|
editTask: () => true,
|
|
159
159
|
deleteTask: () => false,
|
|
160
160
|
},
|
|
161
|
-
currentUserRole:
|
|
161
|
+
currentUserRole: "developer",
|
|
162
162
|
}),
|
|
163
163
|
}));
|
|
164
164
|
```
|
|
@@ -169,11 +169,11 @@ vi.mock('@/hooks/useAuthorization', () => ({
|
|
|
169
169
|
// Already set up in test/setup.ts - uses MSW for API mocking
|
|
170
170
|
// For unit tests, you can also mock directly:
|
|
171
171
|
|
|
172
|
-
vi.mock(
|
|
172
|
+
vi.mock("@/lib/supabase/client", () => ({
|
|
173
173
|
getSupabaseClient: vi.fn(() => ({
|
|
174
174
|
auth: {
|
|
175
175
|
getUser: vi.fn().mockResolvedValue({
|
|
176
|
-
data: { user: { id:
|
|
176
|
+
data: { user: { id: "test-user-id", email: "test@example.com" } },
|
|
177
177
|
error: null,
|
|
178
178
|
}),
|
|
179
179
|
},
|
|
@@ -191,7 +191,7 @@ vi.mock('@/lib/supabase/client', () => ({
|
|
|
191
191
|
#### Mocking Server Actions
|
|
192
192
|
|
|
193
193
|
```typescript
|
|
194
|
-
vi.mock(
|
|
194
|
+
vi.mock("@/app/actions/tasks", () => ({
|
|
195
195
|
getTasks: vi.fn().mockResolvedValue({ data: mockTasks, error: null }),
|
|
196
196
|
createTask: vi.fn().mockResolvedValue({ data: mockTask, error: null }),
|
|
197
197
|
deleteTask: vi.fn().mockResolvedValue({ success: true, error: null }),
|
|
@@ -397,25 +397,25 @@ Located in `playwright.config.ts`:
|
|
|
397
397
|
|
|
398
398
|
```typescript
|
|
399
399
|
export default defineConfig({
|
|
400
|
-
testDir:
|
|
400
|
+
testDir: "./e2e",
|
|
401
401
|
fullyParallel: true,
|
|
402
402
|
retries: process.env.CI ? 2 : 0,
|
|
403
403
|
workers: process.env.CI ? 1 : undefined,
|
|
404
|
-
reporter:
|
|
404
|
+
reporter: "html",
|
|
405
405
|
use: {
|
|
406
|
-
baseURL:
|
|
407
|
-
trace:
|
|
406
|
+
baseURL: "http://localhost:5173",
|
|
407
|
+
trace: "on-first-retry",
|
|
408
408
|
},
|
|
409
409
|
projects: [
|
|
410
|
-
{ name:
|
|
411
|
-
{ name:
|
|
412
|
-
{ name:
|
|
413
|
-
{ name:
|
|
414
|
-
{ name:
|
|
410
|
+
{ name: "chromium", use: { ...devices["Desktop Chrome"] } },
|
|
411
|
+
{ name: "firefox", use: { ...devices["Desktop Firefox"] } },
|
|
412
|
+
{ name: "webkit", use: { ...devices["Desktop Safari"] } },
|
|
413
|
+
{ name: "Mobile Chrome", use: { ...devices["Pixel 5"] } },
|
|
414
|
+
{ name: "Mobile Safari", use: { ...devices["iPhone 12"] } },
|
|
415
415
|
],
|
|
416
416
|
webServer: {
|
|
417
|
-
command:
|
|
418
|
-
url:
|
|
417
|
+
command: "pnpm dev",
|
|
418
|
+
url: "http://localhost:5173",
|
|
419
419
|
reuseExistingServer: !process.env.CI,
|
|
420
420
|
},
|
|
421
421
|
});
|
|
@@ -443,19 +443,19 @@ npx playwright show-report
|
|
|
443
443
|
### E2E Test Structure
|
|
444
444
|
|
|
445
445
|
```typescript
|
|
446
|
-
import { test, expect } from
|
|
446
|
+
import { test, expect } from "@playwright/test";
|
|
447
447
|
|
|
448
|
-
test.describe(
|
|
448
|
+
test.describe("Task Management", () => {
|
|
449
449
|
test.beforeEach(async ({ page }) => {
|
|
450
450
|
// Login or set up authenticated state
|
|
451
|
-
await page.goto(
|
|
452
|
-
await page.fill('[data-testid="email-input"]',
|
|
453
|
-
await page.fill('[data-testid="password-input"]',
|
|
451
|
+
await page.goto("/login");
|
|
452
|
+
await page.fill('[data-testid="email-input"]', "test@example.com");
|
|
453
|
+
await page.fill('[data-testid="password-input"]', "password");
|
|
454
454
|
await page.click('[data-testid="login-button"]');
|
|
455
|
-
await page.waitForURL(
|
|
455
|
+
await page.waitForURL("/dashboard");
|
|
456
456
|
});
|
|
457
457
|
|
|
458
|
-
test(
|
|
458
|
+
test("should create a new task", async ({ page }) => {
|
|
459
459
|
// Navigate to board
|
|
460
460
|
await page.click('[data-testid="nav-boards"]');
|
|
461
461
|
|
|
@@ -463,21 +463,23 @@ test.describe('Task Management', () => {
|
|
|
463
463
|
await page.click('[data-testid="create-task-btn"]');
|
|
464
464
|
|
|
465
465
|
// Fill form
|
|
466
|
-
await page.fill('[data-testid="task-title-input"]',
|
|
467
|
-
await page.selectOption('[data-testid="priority-select"]',
|
|
466
|
+
await page.fill('[data-testid="task-title-input"]', "New E2E Task");
|
|
467
|
+
await page.selectOption('[data-testid="priority-select"]', "high");
|
|
468
468
|
|
|
469
469
|
// Submit
|
|
470
470
|
await page.click('[data-testid="submit-task-btn"]');
|
|
471
471
|
|
|
472
472
|
// Verify creation
|
|
473
473
|
await expect(
|
|
474
|
-
page
|
|
474
|
+
page
|
|
475
|
+
.locator('[data-testid="task-card"]')
|
|
476
|
+
.filter({ hasText: "New E2E Task" }),
|
|
475
477
|
).toBeVisible();
|
|
476
478
|
});
|
|
477
479
|
|
|
478
|
-
test(
|
|
480
|
+
test("should move task between columns", async ({ page }) => {
|
|
479
481
|
// Navigate to kanban board
|
|
480
|
-
await page.goto(
|
|
482
|
+
await page.goto("/dashboard?view=kanban");
|
|
481
483
|
|
|
482
484
|
// Get task card
|
|
483
485
|
const taskCard = page.locator('[data-testid="task-card-TASK-0001"]');
|
|
@@ -487,7 +489,9 @@ test.describe('Task Management', () => {
|
|
|
487
489
|
await taskCard.dragTo(targetColumn);
|
|
488
490
|
|
|
489
491
|
// Verify move
|
|
490
|
-
await expect(
|
|
492
|
+
await expect(
|
|
493
|
+
targetColumn.locator('[data-testid="task-card-TASK-0001"]'),
|
|
494
|
+
).toBeVisible();
|
|
491
495
|
});
|
|
492
496
|
});
|
|
493
497
|
```
|
|
@@ -536,23 +540,23 @@ The setup file initializes:
|
|
|
536
540
|
4. **jsdom Environment** - DOM simulation
|
|
537
541
|
|
|
538
542
|
```typescript
|
|
539
|
-
import { beforeAll, afterAll, afterEach, vi } from
|
|
540
|
-
import { server } from
|
|
541
|
-
import
|
|
543
|
+
import { beforeAll, afterAll, afterEach, vi } from "vitest";
|
|
544
|
+
import { server } from "@/lib/test-utils/server";
|
|
545
|
+
import "@testing-library/jest-dom";
|
|
542
546
|
|
|
543
547
|
// Start MSW server
|
|
544
|
-
beforeAll(() => server.listen({ onUnhandledRequest:
|
|
548
|
+
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
|
|
545
549
|
afterEach(() => server.resetHandlers());
|
|
546
550
|
afterAll(() => server.close());
|
|
547
551
|
|
|
548
552
|
// Mock Next.js
|
|
549
|
-
vi.mock(
|
|
553
|
+
vi.mock("next/navigation", () => ({
|
|
550
554
|
useRouter: () => ({ push: vi.fn(), replace: vi.fn() }),
|
|
551
|
-
usePathname: () =>
|
|
555
|
+
usePathname: () => "/dashboard",
|
|
552
556
|
useSearchParams: () => new URLSearchParams(),
|
|
553
557
|
}));
|
|
554
558
|
|
|
555
|
-
vi.mock(
|
|
559
|
+
vi.mock("next/cache", () => ({
|
|
556
560
|
revalidatePath: vi.fn(),
|
|
557
561
|
revalidateTag: vi.fn(),
|
|
558
562
|
}));
|
|
@@ -563,24 +567,30 @@ vi.mock('next/cache', () => ({
|
|
|
563
567
|
Provides mock data factories and seeding utilities:
|
|
564
568
|
|
|
565
569
|
```typescript
|
|
566
|
-
import type { Task, Board, Sprint, User } from
|
|
570
|
+
import type { Task, Board, Sprint, User } from "@/types";
|
|
567
571
|
|
|
568
572
|
// ============== Mock Data ==============
|
|
569
573
|
export const mockUser: User = {
|
|
570
|
-
id:
|
|
571
|
-
email:
|
|
572
|
-
name:
|
|
573
|
-
role:
|
|
574
|
+
id: "user-001",
|
|
575
|
+
email: "test@example.com",
|
|
576
|
+
name: "Test User",
|
|
577
|
+
role: "developer",
|
|
574
578
|
};
|
|
575
579
|
|
|
576
580
|
export const mockTasks: Task[] = [
|
|
577
|
-
{ id: 'task-001', ticketId: 'TEST-0001', title: 'First Task', priority: 'high', status: 'todo' },
|
|
578
581
|
{
|
|
579
|
-
id:
|
|
580
|
-
ticketId:
|
|
581
|
-
title:
|
|
582
|
-
priority:
|
|
583
|
-
status:
|
|
582
|
+
id: "task-001",
|
|
583
|
+
ticketId: "TEST-0001",
|
|
584
|
+
title: "First Task",
|
|
585
|
+
priority: "high",
|
|
586
|
+
status: "todo",
|
|
587
|
+
},
|
|
588
|
+
{
|
|
589
|
+
id: "task-002",
|
|
590
|
+
ticketId: "TEST-0002",
|
|
591
|
+
title: "Second Task",
|
|
592
|
+
priority: "medium",
|
|
593
|
+
status: "in_progress",
|
|
584
594
|
},
|
|
585
595
|
];
|
|
586
596
|
|
|
@@ -590,13 +600,13 @@ export function createMockTask(overrides: Partial<Task> = {}): Task {
|
|
|
590
600
|
id: `task-${Date.now()}`,
|
|
591
601
|
ticketId: `TEST-${Math.floor(Math.random() * 9999)
|
|
592
602
|
.toString()
|
|
593
|
-
.padStart(4,
|
|
594
|
-
title:
|
|
595
|
-
description:
|
|
596
|
-
priority:
|
|
597
|
-
status:
|
|
598
|
-
type:
|
|
599
|
-
boardId:
|
|
603
|
+
.padStart(4, "0")}`,
|
|
604
|
+
title: "Mock Task",
|
|
605
|
+
description: "",
|
|
606
|
+
priority: "medium",
|
|
607
|
+
status: "todo",
|
|
608
|
+
type: "task",
|
|
609
|
+
boardId: "board-001",
|
|
600
610
|
createdAt: new Date().toISOString(),
|
|
601
611
|
updatedAt: new Date().toISOString(),
|
|
602
612
|
...overrides,
|
|
@@ -612,7 +622,7 @@ export function createMockSprint(overrides: Partial<Sprint> = {}): Sprint {
|
|
|
612
622
|
|
|
613
623
|
// ============== Seeding Functions ==============
|
|
614
624
|
export function seedTasks(tasks: Task[] = mockTasks): void {
|
|
615
|
-
localStorage.setItem(
|
|
625
|
+
localStorage.setItem("pm_tasks", JSON.stringify(tasks));
|
|
616
626
|
}
|
|
617
627
|
|
|
618
628
|
export function seedBoards(): void {
|
|
@@ -629,9 +639,9 @@ export function clearLocalStorage(): void {
|
|
|
629
639
|
// ============== Known Gaps ==============
|
|
630
640
|
// Document areas that don't have full test coverage yet
|
|
631
641
|
export const KNOWN_GAPS = {
|
|
632
|
-
dragAndDrop:
|
|
633
|
-
realTime:
|
|
634
|
-
fileUpload:
|
|
642
|
+
dragAndDrop: "Drag-and-drop testing requires special handling with react-dnd",
|
|
643
|
+
realTime: "Real-time subscription testing not fully implemented",
|
|
644
|
+
fileUpload: "File upload testing requires MSW file handling",
|
|
635
645
|
};
|
|
636
646
|
```
|
|
637
647
|
|
|
@@ -654,21 +664,21 @@ lib/test-utils/
|
|
|
654
664
|
Example handler (`lib/test-utils/handlers/tasks.ts`):
|
|
655
665
|
|
|
656
666
|
```typescript
|
|
657
|
-
import { http, HttpResponse } from
|
|
658
|
-
import { mockTasks } from
|
|
667
|
+
import { http, HttpResponse } from "msw";
|
|
668
|
+
import { mockTasks } from "@test/fixtures";
|
|
659
669
|
|
|
660
670
|
export const taskHandlers = [
|
|
661
|
-
http.get(
|
|
671
|
+
http.get("*/rest/v1/tasks*", () => {
|
|
662
672
|
return HttpResponse.json(mockTasks);
|
|
663
673
|
}),
|
|
664
674
|
|
|
665
|
-
http.post(
|
|
675
|
+
http.post("*/rest/v1/tasks", async ({ request }) => {
|
|
666
676
|
const body = await request.json();
|
|
667
677
|
const newTask = { id: `task-${Date.now()}`, ...body };
|
|
668
678
|
return HttpResponse.json(newTask, { status: 201 });
|
|
669
679
|
}),
|
|
670
680
|
|
|
671
|
-
http.delete(
|
|
681
|
+
http.delete("*/rest/v1/tasks*", () => {
|
|
672
682
|
return new HttpResponse(null, { status: 204 });
|
|
673
683
|
}),
|
|
674
684
|
];
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
Valid branch format: `<type>/<scope>/<description>`
|
|
19
19
|
|
|
20
20
|
Examples:
|
|
21
|
+
|
|
21
22
|
- `feature/{{scopes}}`
|
|
22
23
|
- `fix/{{scopes}}`
|
|
23
24
|
|
|
@@ -25,4 +26,4 @@ Examples:
|
|
|
25
26
|
|
|
26
27
|
---
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
_This document was auto-generated by Workflow Agent. Do not edit directly._
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Workflow Agent Team
|
|
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.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config/index.ts","../src/config/schema.ts"],"sourcesContent":["import { cosmiconfig } from 'cosmiconfig';\nimport { WorkflowConfig, WorkflowConfigSchema, validateScopeDefinitions } from './schema.js';\nimport { join } from 'path';\nimport { existsSync } from 'fs';\nimport { z } from 'zod';\n\nconst explorer = cosmiconfig('workflow', {\n searchPlaces: [\n 'workflow.config.ts',\n 'workflow.config.js',\n 'workflow.config.json',\n '.workflowrc',\n '.workflowrc.json',\n 'package.json',\n ],\n});\n\nexport async function loadConfig(cwd: string = process.cwd()): Promise<WorkflowConfig | null> {\n try {\n const result = await explorer.search(cwd);\n \n if (!result || !result.config) {\n return null;\n }\n\n // Validate config against schema\n const validated = WorkflowConfigSchema.parse(result.config);\n return validated;\n } catch (error) {\n if (error instanceof z.ZodError) {\n // Format Zod errors to be more user-friendly\n const result = await explorer.search(cwd);\n const formattedErrors = error.errors.map(err => {\n const path = err.path.join('.');\n \n // If error is in scopes array, show the scope name\n if (err.path[0] === 'scopes' && typeof err.path[1] === 'number') {\n const scopeIndex = err.path[1];\n const scopeName = result?.config?.scopes?.[scopeIndex]?.name || `scope at index ${scopeIndex}`;\n const field = err.path[2] || 'definition';\n \n // Add helpful suggestions for common errors\n let message = err.message;\n if (message.includes('reserved word')) {\n const reservedMatch = message.match(/Scope name \"([^\"]+)\" is reserved/);\n if (reservedMatch) {\n const suggestions: Record<string, string> = {\n 'docs': 'documentation',\n 'test': 'testing',\n 'config': 'configuration',\n 'build': 'builds',\n 'ci': 'cicd',\n 'deps': 'dependencies',\n };\n const badName = reservedMatch[1];\n const suggestion = suggestions[badName] || `${badName}-scope`;\n message = `${message}. Try renaming to \"${suggestion}\"`;\n }\n }\n \n return field === 'definition' \n ? `Scope \"${scopeName}\": ${message}`\n : `Scope \"${scopeName}\" ${field}: ${message}`;\n }\n \n return `${path}: ${err.message}`;\n }).join('\\n • ');\n \n throw new Error(`Invalid workflow configuration:\\n • ${formattedErrors}\\n\\n💡 Fix these issues in workflow.config.json or run: workflow config validate`);\n }\n \n if (error instanceof Error) {\n throw new Error(`Failed to load workflow config: ${error.message}`);\n }\n throw error;\n }\n}\n\nexport async function validateConfig(cwd: string = process.cwd()): Promise<{\n valid: boolean;\n errors: string[];\n warnings: string[];\n}> {\n const errors: string[] = [];\n const warnings: string[] = [];\n \n try {\n const config = await loadConfig(cwd);\n if (!config) {\n errors.push('No configuration file found');\n return { valid: false, errors, warnings };\n }\n \n // Additional validation beyond schema\n const scopeValidation = validateScopeDefinitions(config.scopes);\n errors.push(...scopeValidation.errors);\n \n return {\n valid: errors.length === 0,\n errors,\n warnings,\n };\n } catch (error) {\n errors.push(error instanceof Error ? error.message : String(error));\n return { valid: false, errors, warnings };\n }\n}\n\nexport function hasConfig(cwd: string = process.cwd()): boolean {\n const configPaths = [\n 'workflow.config.ts',\n 'workflow.config.js',\n 'workflow.config.json',\n '.workflowrc',\n '.workflowrc.json',\n ];\n\n return configPaths.some((path) => existsSync(join(cwd, path)));\n}\n\nexport { WorkflowConfig, WorkflowConfigSchema, Scope, BranchType, ConventionalType, validateScopeName, DEFAULT_RESERVED_SCOPE_NAMES } from './schema.js';\n","import { z } from 'zod';\n\n// Default reserved scope names that cannot be used\nexport const DEFAULT_RESERVED_SCOPE_NAMES = ['init', 'create', 'build', 'test', 'config', 'docs', 'ci', 'deps'];\n\n/**\n * Validates a scope name against reserved words and naming rules\n */\nexport function validateScopeName(name: string, reservedNames: string[] = DEFAULT_RESERVED_SCOPE_NAMES): {\n valid: boolean;\n error?: string;\n suggestion?: string;\n} {\n if (reservedNames.includes(name)) {\n // Provide suggestions for common reserved words\n const suggestions: Record<string, string> = {\n 'docs': 'documentation',\n 'test': 'testing',\n 'config': 'configuration',\n 'build': 'builds',\n 'ci': 'cicd',\n 'deps': 'dependencies',\n };\n \n return {\n valid: false,\n error: `Scope name \"${name}\" is reserved`,\n suggestion: suggestions[name] || `${name}-scope`,\n };\n }\n \n if (!/^[a-z0-9-]+$/.test(name)) {\n return {\n valid: false,\n error: 'Scope name must be lowercase alphanumeric with hyphens',\n };\n }\n \n if (name.length === 0 || name.length > 32) {\n return {\n valid: false,\n error: 'Scope name must be 1-32 characters',\n };\n }\n \n return { valid: true };\n}\n\nexport const BranchTypeSchema = z.enum([\n 'feature',\n 'bugfix',\n 'hotfix',\n 'chore',\n 'refactor',\n 'docs',\n 'test',\n 'release',\n]);\n\nexport const ConventionalTypeSchema = z.enum([\n 'feat',\n 'fix',\n 'refactor',\n 'chore',\n 'docs',\n 'test',\n 'perf',\n 'style',\n 'ci',\n 'build',\n 'revert',\n]);\n\nexport const ScopeSchema = z.object({\n name: z.string()\n .min(1)\n .max(32, 'Scope name must be 32 characters or less')\n .regex(/^[a-z0-9-]+$/, 'Scope name must be lowercase alphanumeric with hyphens'),\n description: z.string().min(10, 'Scope description must be at least 10 characters'),\n allowedTypes: z.array(ConventionalTypeSchema).optional(),\n mandatoryGuidelines: z.array(z.string()).optional(),\n emoji: z.string().optional(),\n category: z.enum(['auth', 'features', 'infrastructure', 'documentation', 'testing', 'performance', 'other']).optional(),\n});\n\nexport const EnforcementLevelSchema = z.enum(['strict', 'advisory', 'learning']);\n\nexport const AnalyticsConfigSchema = z.object({\n enabled: z.boolean().default(false),\n shareAnonymous: z.boolean().default(false),\n});\n\n// Pre-commit hook check types\nexport const HookCheckSchema = z.enum([\n 'validate-branch',\n 'validate-commit',\n 'check-guidelines',\n 'validate-scopes',\n]);\n\n// Git hooks configuration\nexport const HooksConfigSchema = z.object({\n /** Whether hooks are enabled */\n enabled: z.boolean().default(true),\n /** Checks to run on pre-commit */\n preCommit: z.array(HookCheckSchema).default(['validate-branch', 'check-guidelines']),\n /** Checks to run on commit-msg */\n commitMsg: z.array(HookCheckSchema).default(['validate-commit']),\n});\n\n// Guidelines configuration with mandatory templates and user overrides\nexport const GuidelinesConfigSchema = z.object({\n /** Additional templates to make mandatory (beyond the core set) */\n additionalMandatory: z.array(z.string()).optional(),\n /** Templates to make optional (override core mandatory templates) */\n optionalOverrides: z.array(z.string()).optional(),\n});\n\n// CI provider types\nexport const CIProviderSchema = z.enum(['github', 'gitlab', 'bitbucket']);\n\n// CI check types\nexport const CICheckSchema = z.enum(['lint', 'typecheck', 'format', 'test', 'build']);\n\n// CI/CD configuration\nexport const CIConfigSchema = z.object({\n /** Whether CI setup is enabled */\n enabled: z.boolean().default(true),\n /** CI provider (currently only github supported) */\n provider: CIProviderSchema.default('github'),\n /** Checks to run in CI pipeline */\n checks: z.array(CICheckSchema).default(['lint', 'typecheck', 'format', 'build', 'test']),\n});\n\nexport const WorkflowConfigSchema = z.object({\n projectName: z.string().min(1),\n scopes: z.array(ScopeSchema).min(1),\n branchTypes: z.array(BranchTypeSchema).optional(),\n conventionalTypes: z.array(ConventionalTypeSchema).optional(),\n enforcement: EnforcementLevelSchema.default('strict'),\n language: z.string().default('en'),\n analytics: AnalyticsConfigSchema.optional(),\n adapter: z.string().optional(),\n syncRemote: z.string().optional(),\n hooks: HooksConfigSchema.optional(),\n guidelines: GuidelinesConfigSchema.optional(),\n reservedScopeNames: z.array(z.string()).optional().default(DEFAULT_RESERVED_SCOPE_NAMES),\n ci: CIConfigSchema.optional(),\n}).superRefine((config, ctx) => {\n // Validate scopes against reserved names\n const reservedNames = config.reservedScopeNames || DEFAULT_RESERVED_SCOPE_NAMES;\n \n config.scopes.forEach((scope, index) => {\n const validation = validateScopeName(scope.name, reservedNames);\n if (!validation.valid) {\n let message = validation.error || 'Invalid scope name';\n if (validation.suggestion) {\n message += `. Try renaming to \"${validation.suggestion}\"`;\n }\n \n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n path: ['scopes', index, 'name'],\n message,\n });\n }\n });\n});\n\nexport type Scope = z.infer<typeof ScopeSchema>;\nexport type BranchType = z.infer<typeof BranchTypeSchema>;\nexport type ConventionalType = z.infer<typeof ConventionalTypeSchema>;\nexport type EnforcementLevel = z.infer<typeof EnforcementLevelSchema>;\nexport type AnalyticsConfig = z.infer<typeof AnalyticsConfigSchema>;\nexport type HookCheck = z.infer<typeof HookCheckSchema>;\nexport type HooksConfig = z.infer<typeof HooksConfigSchema>;\nexport type GuidelinesConfig = z.infer<typeof GuidelinesConfigSchema>;\nexport type CIProvider = z.infer<typeof CIProviderSchema>;\nexport type CICheck = z.infer<typeof CICheckSchema>;\nexport type CIConfig = z.infer<typeof CIConfigSchema>;\nexport type WorkflowConfig = z.infer<typeof WorkflowConfigSchema>;\n\nexport const defaultBranchTypes: BranchType[] = [\n 'feature',\n 'bugfix',\n 'hotfix',\n 'chore',\n 'refactor',\n 'docs',\n 'test',\n];\n\nexport const defaultConventionalTypes: ConventionalType[] = [\n 'feat',\n 'fix',\n 'refactor',\n 'chore',\n 'docs',\n 'test',\n 'perf',\n 'style',\n];\n\n/**\n * Validates scope definitions for duplicates, description quality, and category values\n * @param scopes Array of scope definitions to validate\n * @returns Object with validation result and error messages\n */\nexport function validateScopeDefinitions(scopes: Scope[]): { \n valid: boolean; \n errors: string[];\n} {\n const errors: string[] = [];\n const seenNames = new Set<string>();\n\n for (const scope of scopes) {\n // Check for duplicate names\n if (seenNames.has(scope.name)) {\n errors.push(`Duplicate scope name: \"${scope.name}\"`);\n }\n seenNames.add(scope.name);\n\n // Validate using schema (this will catch min length, reserved names, etc.)\n const result = ScopeSchema.safeParse(scope);\n if (!result.success) {\n result.error.errors.forEach(err => {\n errors.push(`Scope \"${scope.name}\": ${err.message}`);\n });\n }\n }\n\n return {\n valid: errors.length === 0,\n errors,\n };\n}\n"],"mappings":"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","names":["z"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/validators/index.ts"],"sourcesContent":["import didYouMean from 'didyoumean2';\nimport type { WorkflowConfig, BranchType, Scope } from '../config/index.js';\nimport { readdir } from 'fs/promises';\nimport { join } from 'path';\n\nexport interface ValidationResult {\n valid: boolean;\n error?: string;\n suggestion?: string;\n}\n\n// Cache for discovered custom scopes\nlet customScopesCache: Scope[] | null = null;\nlet cacheTimestamp: number = 0;\nconst CACHE_TTL = 5 * 60 * 1000; // 5 minutes\n\n/**\n * Discovers custom scope packages in the workspace and node_modules\n * @param workspacePath Path to workspace root\n * @returns Array of discovered scopes\n */\nexport async function discoverCustomScopes(workspacePath: string = process.cwd()): Promise<Scope[]> {\n // Check cache validity\n const now = Date.now();\n if (customScopesCache && (now - cacheTimestamp) < CACHE_TTL) {\n return customScopesCache;\n }\n\n const discoveredScopes: Scope[] = [];\n\n try {\n // Search for custom scope packages in workspace\n const workspaceLocations = [\n join(workspacePath, 'packages'),\n workspacePath,\n ];\n\n for (const location of workspaceLocations) {\n try {\n const entries = await readdir(location, { withFileTypes: true });\n \n for (const entry of entries) {\n if (entry.isDirectory() && entry.name.startsWith('scopes-')) {\n const indexPath = join(location, entry.name, 'src', 'index.ts');\n try {\n const module = await import(indexPath);\n const scopes = module.scopes || module.default?.scopes;\n \n if (Array.isArray(scopes)) {\n discoveredScopes.push(...scopes);\n }\n } catch {\n // Silently skip packages that can't be loaded\n }\n }\n }\n } catch {\n // Directory doesn't exist or can't be read\n }\n }\n\n // Update cache\n customScopesCache = discoveredScopes;\n cacheTimestamp = now;\n\n } catch (error) {\n // Return empty array on error\n console.warn('Warning: Error discovering custom scopes:', error);\n }\n\n return discoveredScopes;\n}\n\n/**\n * Invalidates the custom scopes cache (useful after config changes)\n */\nexport function invalidateCustomScopesCache(): void {\n customScopesCache = null;\n cacheTimestamp = 0;\n}\n\n/**\n * Gets all available scopes including custom discovered ones\n * @param config Workflow configuration\n * @param workspacePath Optional workspace path\n * @returns Combined array of scopes\n */\nexport async function getAllScopes(config: WorkflowConfig, workspacePath?: string): Promise<Scope[]> {\n const configScopes = config.scopes;\n const customScopes = await discoverCustomScopes(workspacePath);\n \n // Merge and deduplicate by name\n const scopeMap = new Map<string, Scope>();\n \n // Config scopes take precedence\n for (const scope of configScopes) {\n scopeMap.set(scope.name, scope);\n }\n \n // Add custom scopes that don't conflict\n for (const scope of customScopes) {\n if (!scopeMap.has(scope.name)) {\n scopeMap.set(scope.name, scope);\n }\n }\n \n return Array.from(scopeMap.values());\n}\n\nexport async function validateBranchName(\n branchName: string,\n config: WorkflowConfig,\n workspacePath?: string\n): Promise<ValidationResult> {\n const branchTypes = config.branchTypes || ['feature', 'bugfix', 'hotfix', 'chore', 'refactor', 'docs', 'test'];\n const allScopes = await getAllScopes(config, workspacePath);\n const scopes = allScopes.map((s) => s.name);\n\n // Expected format: <type>/<scope>/<description>\n const branchPattern = /^([a-z]+)\\/([a-z0-9-]+)\\/([a-z0-9-]+)$/;\n const match = branchName.match(branchPattern);\n\n if (!match) {\n return {\n valid: false,\n error: `Branch name must follow format: <type>/<scope>/<description> (e.g., feature/auth/add-login)`,\n suggestion: `Current: ${branchName}. All parts must be lowercase alphanumeric with hyphens.`,\n };\n }\n\n const [, type, scope, description] = match;\n\n // Validate type\n if (!branchTypes.includes(type as BranchType)) {\n const suggestion = didYouMean(type, branchTypes);\n return {\n valid: false,\n error: `Invalid branch type '${type}'. Must be one of: ${branchTypes.join(', ')}`,\n suggestion: suggestion ? `Did you mean '${suggestion}'?` : undefined,\n };\n }\n\n // Validate scope\n if (!scopes.includes(scope)) {\n const suggestion = didYouMean(scope, scopes);\n const scopeList = scopes.slice(0, 5).join(', ') + (scopes.length > 5 ? '...' : '');\n return {\n valid: false,\n error: `Invalid scope '${scope}'. Must be one of: ${scopeList}`,\n suggestion: suggestion ? `Did you mean '${suggestion}'?` : undefined,\n };\n }\n\n // Validate description (not empty, meaningful)\n if (description.length < 3) {\n return {\n valid: false,\n error: `Branch description '${description}' is too short (minimum 3 characters)`,\n };\n }\n\n return { valid: true };\n}\n\nexport async function validateCommitMessage(\n message: string,\n config: WorkflowConfig,\n workspacePath?: string\n): Promise<ValidationResult> {\n const conventionalTypes = config.conventionalTypes || [\n 'feat',\n 'fix',\n 'refactor',\n 'chore',\n 'docs',\n 'test',\n 'perf',\n 'style',\n ];\n const allScopes = await getAllScopes(config, workspacePath);\n const scopes = allScopes.map((s) => s.name);\n\n // Expected format: <type>(<scope>): <description>\n const commitPattern = /^([a-z]+)(?:\\(([a-z0-9-]+)\\))?: (.+)$/;\n const match = message.match(commitPattern);\n\n if (!match) {\n return {\n valid: false,\n error: `Commit message must follow conventional commits format: <type>(<scope>): <description>`,\n suggestion: `Example: feat(auth): add login validation`,\n };\n }\n\n const [, type, scope, description] = match;\n\n // Validate type\n if (!conventionalTypes.includes(type as any)) {\n const suggestion = didYouMean(type, conventionalTypes);\n return {\n valid: false,\n error: `Invalid commit type '${type}'. Must be one of: ${conventionalTypes.join(', ')}`,\n suggestion: suggestion ? `Did you mean '${suggestion}'?` : undefined,\n };\n }\n\n // Validate scope (optional but recommended)\n if (scope && !scopes.includes(scope)) {\n const suggestion = didYouMean(scope, scopes);\n const scopeList = scopes.slice(0, 5).join(', ') + (scopes.length > 5 ? '...' : '');\n return {\n valid: false,\n error: `Invalid scope '${scope}'. Must be one of: ${scopeList}`,\n suggestion: suggestion ? `Did you mean '${suggestion}'?` : undefined,\n };\n }\n\n // Validate description\n if (description.length < 10) {\n return {\n valid: false,\n error: `Commit description is too short (minimum 10 characters)`,\n suggestion: `Be more descriptive about what changed`,\n };\n }\n\n if (description[0] !== description[0].toLowerCase()) {\n return {\n valid: false,\n error: `Commit description must start with lowercase letter`,\n suggestion: `Change '${description}' to '${description[0].toLowerCase()}${description.slice(1)}'`,\n };\n }\n\n return { valid: true };\n}\n\nexport async function validatePRTitle(\n title: string,\n config: WorkflowConfig,\n workspacePath?: string\n): Promise<ValidationResult> {\n // PR titles follow same format as commit messages\n return validateCommitMessage(title, config, workspacePath);\n}\n"],"mappings":"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","names":[]}
|