@lovelybunch/api 1.0.7
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/dist/lib/gait-path.d.ts +13 -0
- package/dist/lib/gait-path.js +57 -0
- package/dist/lib/project-paths.d.ts +13 -0
- package/dist/lib/project-paths.js +57 -0
- package/dist/lib/storage/file-storage.d.ts +28 -0
- package/dist/lib/storage/file-storage.js +224 -0
- package/dist/lib/symlinks/symlink-manager.d.ts +66 -0
- package/dist/lib/symlinks/symlink-manager.js +444 -0
- package/dist/lib/symlinks/types.d.ts +23 -0
- package/dist/lib/symlinks/types.js +4 -0
- package/dist/lib/terminal/context-helper.d.ts +11 -0
- package/dist/lib/terminal/context-helper.js +164 -0
- package/dist/lib/terminal/global-manager.d.ts +2 -0
- package/dist/lib/terminal/global-manager.js +15 -0
- package/dist/lib/terminal/shell-utils.d.ts +33 -0
- package/dist/lib/terminal/shell-utils.js +176 -0
- package/dist/lib/terminal/terminal-manager.d.ts +26 -0
- package/dist/lib/terminal/terminal-manager.js +276 -0
- package/dist/lib/user-preferences.d.ts +48 -0
- package/dist/lib/user-preferences.js +87 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/lib/utils.js +5 -0
- package/dist/routes/api/symlink-status/route.d.ts +1 -0
- package/dist/routes/api/symlink-status/route.js +37 -0
- package/dist/routes/api/symlinks/[id]/route.d.ts +19 -0
- package/dist/routes/api/symlinks/[id]/route.js +95 -0
- package/dist/routes/api/symlinks/[id]/toggle/route.d.ts +11 -0
- package/dist/routes/api/symlinks/[id]/toggle/route.js +32 -0
- package/dist/routes/api/symlinks/debug/route.d.ts +1 -0
- package/dist/routes/api/symlinks/debug/route.js +35 -0
- package/dist/routes/api/symlinks/route.d.ts +9 -0
- package/dist/routes/api/symlinks/route.js +72 -0
- package/dist/routes/api/toggle-symlink/route.d.ts +2 -0
- package/dist/routes/api/toggle-symlink/route.js +94 -0
- package/dist/routes/api/v1/agents/[id]/index.d.ts +1 -0
- package/dist/routes/api/v1/agents/[id]/index.js +1 -0
- package/dist/routes/api/v1/agents/[id]/route.d.ts +3 -0
- package/dist/routes/api/v1/agents/[id]/route.js +163 -0
- package/dist/routes/api/v1/agents/index.d.ts +1 -0
- package/dist/routes/api/v1/agents/index.js +1 -0
- package/dist/routes/api/v1/agents/route.d.ts +3 -0
- package/dist/routes/api/v1/agents/route.js +133 -0
- package/dist/routes/api/v1/ai/index.d.ts +3 -0
- package/dist/routes/api/v1/ai/index.js +5 -0
- package/dist/routes/api/v1/ai/route.d.ts +8 -0
- package/dist/routes/api/v1/ai/route.js +86 -0
- package/dist/routes/api/v1/chats/[id]/index.d.ts +3 -0
- package/dist/routes/api/v1/chats/[id]/index.js +6 -0
- package/dist/routes/api/v1/chats/[id]/route.d.ts +12 -0
- package/dist/routes/api/v1/chats/[id]/route.js +31 -0
- package/dist/routes/api/v1/chats/index.d.ts +3 -0
- package/dist/routes/api/v1/chats/index.js +6 -0
- package/dist/routes/api/v1/chats/route.d.ts +32 -0
- package/dist/routes/api/v1/chats/route.js +67 -0
- package/dist/routes/api/v1/config/index.d.ts +3 -0
- package/dist/routes/api/v1/config/index.js +5 -0
- package/dist/routes/api/v1/config/route.d.ts +9 -0
- package/dist/routes/api/v1/config/route.js +29 -0
- package/dist/routes/api/v1/context/[...path]/route.d.ts +16 -0
- package/dist/routes/api/v1/context/[...path]/route.js +107 -0
- package/dist/routes/api/v1/context/architecture/route.d.ts +3 -0
- package/dist/routes/api/v1/context/architecture/route.js +198 -0
- package/dist/routes/api/v1/context/index.d.ts +3 -0
- package/dist/routes/api/v1/context/index.js +9 -0
- package/dist/routes/api/v1/context/knowledge/[filename]/index.d.ts +1 -0
- package/dist/routes/api/v1/context/knowledge/[filename]/index.js +1 -0
- package/dist/routes/api/v1/context/knowledge/[filename]/route.d.ts +3 -0
- package/dist/routes/api/v1/context/knowledge/[filename]/route.js +165 -0
- package/dist/routes/api/v1/context/knowledge/index.d.ts +1 -0
- package/dist/routes/api/v1/context/knowledge/index.js +1 -0
- package/dist/routes/api/v1/context/knowledge/route.d.ts +3 -0
- package/dist/routes/api/v1/context/knowledge/route.js +121 -0
- package/dist/routes/api/v1/context/project/route.d.ts +3 -0
- package/dist/routes/api/v1/context/project/route.js +153 -0
- package/dist/routes/api/v1/proposals/[id]/route.d.ts +337 -0
- package/dist/routes/api/v1/proposals/[id]/route.js +99 -0
- package/dist/routes/api/v1/proposals/index.d.ts +3 -0
- package/dist/routes/api/v1/proposals/index.js +10 -0
- package/dist/routes/api/v1/proposals/route.d.ts +315 -0
- package/dist/routes/api/v1/proposals/route.js +103 -0
- package/dist/routes/api/v1/resources/[id]/index.d.ts +3 -0
- package/dist/routes/api/v1/resources/[id]/index.js +7 -0
- package/dist/routes/api/v1/resources/[id]/route.d.ts +46 -0
- package/dist/routes/api/v1/resources/[id]/route.js +143 -0
- package/dist/routes/api/v1/resources/[id]/thumbnail/index.d.ts +3 -0
- package/dist/routes/api/v1/resources/[id]/thumbnail/index.js +5 -0
- package/dist/routes/api/v1/resources/[id]/thumbnail/route.d.ts +2 -0
- package/dist/routes/api/v1/resources/[id]/thumbnail/route.js +50 -0
- package/dist/routes/api/v1/resources/index.d.ts +3 -0
- package/dist/routes/api/v1/resources/index.js +6 -0
- package/dist/routes/api/v1/resources/route.d.ts +51 -0
- package/dist/routes/api/v1/resources/route.js +147 -0
- package/dist/routes/api/v1/search/route.d.ts +3 -0
- package/dist/routes/api/v1/search/route.js +39 -0
- package/dist/routes/api/v1/terminal/[proposalId]/create/index.d.ts +3 -0
- package/dist/routes/api/v1/terminal/[proposalId]/create/index.js +5 -0
- package/dist/routes/api/v1/terminal/[proposalId]/create/route.d.ts +10 -0
- package/dist/routes/api/v1/terminal/[proposalId]/create/route.js +27 -0
- package/dist/routes/api/v1/terminal/[proposalId]/destroy/index.d.ts +3 -0
- package/dist/routes/api/v1/terminal/[proposalId]/destroy/index.js +5 -0
- package/dist/routes/api/v1/terminal/[proposalId]/destroy/route.d.ts +10 -0
- package/dist/routes/api/v1/terminal/[proposalId]/destroy/route.js +21 -0
- package/dist/routes/api/v1/terminal/[proposalId]/resize/index.d.ts +3 -0
- package/dist/routes/api/v1/terminal/[proposalId]/resize/index.js +5 -0
- package/dist/routes/api/v1/terminal/[proposalId]/resize/route.d.ts +10 -0
- package/dist/routes/api/v1/terminal/[proposalId]/resize/route.js +21 -0
- package/dist/routes/api/v1/terminal/sessions/index.d.ts +3 -0
- package/dist/routes/api/v1/terminal/sessions/index.js +5 -0
- package/dist/routes/api/v1/terminal/sessions/route.d.ts +6 -0
- package/dist/routes/api/v1/terminal/sessions/route.js +29 -0
- package/dist/routes/api/v1/user/index.d.ts +3 -0
- package/dist/routes/api/v1/user/index.js +5 -0
- package/dist/routes/api/v1/user/preferences/route.d.ts +11 -0
- package/dist/routes/api/v1/user/preferences/route.js +31 -0
- package/dist/routes/api/v1/user/profile/route.d.ts +11 -0
- package/dist/routes/api/v1/user/profile/route.js +31 -0
- package/dist/routes/api/v1/user/settings/index.d.ts +1 -0
- package/dist/routes/api/v1/user/settings/index.js +1 -0
- package/dist/routes/api/v1/user/settings/route.d.ts +3 -0
- package/dist/routes/api/v1/user/settings/route.js +51 -0
- package/dist/server-with-static.d.ts +4 -0
- package/dist/server-with-static.js +144 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +91 -0
- package/package.json +42 -0
- package/static/assets/index-BvTnrm0O.js +576 -0
- package/static/assets/index-Cm5dZHTl.css +33 -0
- package/static/assets/index-ORkAkJNi.js +576 -0
- package/static/assets/index-_Keadpms.js +576 -0
- package/static/index.html +17 -0
- package/static/vite.svg +1 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Find the .gait directory by traversing up from the current working directory
|
|
3
|
+
* If GAIT_DATA_PATH is set (from CLI), use that instead
|
|
4
|
+
*/
|
|
5
|
+
export declare function findGaitDirectory(startDir?: string): Promise<string | null>;
|
|
6
|
+
/**
|
|
7
|
+
* Get the path to a context file, automatically finding the .gait directory
|
|
8
|
+
*/
|
|
9
|
+
export declare function getContextFilePath(fileName: string): Promise<string | null>;
|
|
10
|
+
/**
|
|
11
|
+
* Get the .gait config to read storage path settings
|
|
12
|
+
*/
|
|
13
|
+
export declare function getGaitConfig(): Promise<any | null>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
/**
|
|
4
|
+
* Find the .gait directory by traversing up from the current working directory
|
|
5
|
+
* If GAIT_DATA_PATH is set (from CLI), use that instead
|
|
6
|
+
*/
|
|
7
|
+
export async function findGaitDirectory(startDir) {
|
|
8
|
+
// If running from CLI (gait serve), use the directory where command was run
|
|
9
|
+
if (process.env.GAIT_DATA_PATH) {
|
|
10
|
+
const gaitPath = path.join(process.env.GAIT_DATA_PATH, '.gait');
|
|
11
|
+
try {
|
|
12
|
+
await fs.access(gaitPath);
|
|
13
|
+
return gaitPath;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
// Fall through to directory traversal
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
// Otherwise traverse up from start directory
|
|
20
|
+
let currentDir = startDir || process.cwd();
|
|
21
|
+
while (currentDir !== path.parse(currentDir).root) {
|
|
22
|
+
const gaitPath = path.join(currentDir, '.gait');
|
|
23
|
+
try {
|
|
24
|
+
await fs.access(gaitPath);
|
|
25
|
+
return gaitPath;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
currentDir = path.dirname(currentDir);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Get the path to a context file, automatically finding the .gait directory
|
|
35
|
+
*/
|
|
36
|
+
export async function getContextFilePath(fileName) {
|
|
37
|
+
const gaitDir = await findGaitDirectory();
|
|
38
|
+
if (!gaitDir)
|
|
39
|
+
return null;
|
|
40
|
+
return path.join(gaitDir, 'context', fileName);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Get the .gait config to read storage path settings
|
|
44
|
+
*/
|
|
45
|
+
export async function getGaitConfig() {
|
|
46
|
+
const gaitDir = await findGaitDirectory();
|
|
47
|
+
if (!gaitDir)
|
|
48
|
+
return null;
|
|
49
|
+
const configPath = path.join(gaitDir, 'config.json');
|
|
50
|
+
try {
|
|
51
|
+
const config = await fs.readFile(configPath, 'utf-8');
|
|
52
|
+
return JSON.parse(config);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gets the consistent project root path for GAIT operations.
|
|
3
|
+
* This ensures that both symlink operations use the same base path.
|
|
4
|
+
*/
|
|
5
|
+
export declare function getProjectRoot(): Promise<string>;
|
|
6
|
+
/**
|
|
7
|
+
* Gets the path to the CLAUDE.md symlink in the project root
|
|
8
|
+
*/
|
|
9
|
+
export declare function getSymlinkPath(): Promise<string>;
|
|
10
|
+
/**
|
|
11
|
+
* Gets the path to the actual CLAUDE.md file in .gait/rules
|
|
12
|
+
*/
|
|
13
|
+
export declare function getTargetPath(): Promise<string>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { promises as fs } from 'fs';
|
|
3
|
+
/**
|
|
4
|
+
* Gets the consistent project root path for GAIT operations.
|
|
5
|
+
* This ensures that both symlink operations use the same base path.
|
|
6
|
+
*/
|
|
7
|
+
export async function getProjectRoot() {
|
|
8
|
+
// First, check if GAIT_DATA_PATH is explicitly set
|
|
9
|
+
if (process.env.GAIT_DATA_PATH) {
|
|
10
|
+
return path.resolve(process.env.GAIT_DATA_PATH);
|
|
11
|
+
}
|
|
12
|
+
// In development, we need to find the actual project root
|
|
13
|
+
// Starting from the current working directory
|
|
14
|
+
let currentPath = process.cwd();
|
|
15
|
+
// If we're in a Next.js app, we might be in packages/web
|
|
16
|
+
// We need to traverse up to find the actual project root
|
|
17
|
+
while (currentPath !== path.dirname(currentPath)) {
|
|
18
|
+
try {
|
|
19
|
+
// Check if we're at the project root by looking for distinctive files
|
|
20
|
+
const possibleRoot = currentPath;
|
|
21
|
+
const hasPackageJson = await fs.access(path.join(possibleRoot, 'package.json')).then(() => true).catch(() => false);
|
|
22
|
+
const hasGaitDir = await fs.access(path.join(possibleRoot, '.gait')).then(() => true).catch(() => false);
|
|
23
|
+
const hasPnpmWorkspace = await fs.access(path.join(possibleRoot, 'pnpm-workspace.yaml')).then(() => true).catch(() => false);
|
|
24
|
+
// If this looks like the project root, use it
|
|
25
|
+
if (hasPackageJson && hasGaitDir && hasPnpmWorkspace) {
|
|
26
|
+
return possibleRoot;
|
|
27
|
+
}
|
|
28
|
+
// Otherwise, check parent directory
|
|
29
|
+
currentPath = path.dirname(currentPath);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
currentPath = path.dirname(currentPath);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// Fallback: if we can't find the project root, use the current working directory
|
|
36
|
+
// But also try a known relative path from the web package
|
|
37
|
+
const webPackagePath = process.cwd();
|
|
38
|
+
if (webPackagePath.includes('packages/web')) {
|
|
39
|
+
// We're in the web package, go up to the root
|
|
40
|
+
return path.resolve(webPackagePath, '../..');
|
|
41
|
+
}
|
|
42
|
+
return process.cwd();
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Gets the path to the CLAUDE.md symlink in the project root
|
|
46
|
+
*/
|
|
47
|
+
export async function getSymlinkPath() {
|
|
48
|
+
const projectRoot = await getProjectRoot();
|
|
49
|
+
return path.join(projectRoot, 'CLAUDE.md');
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Gets the path to the actual CLAUDE.md file in .gait/rules
|
|
53
|
+
*/
|
|
54
|
+
export async function getTargetPath() {
|
|
55
|
+
const projectRoot = await getProjectRoot();
|
|
56
|
+
return path.join(projectRoot, '.gait', 'rules', 'CLAUDE.md');
|
|
57
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ChangeProposal, CPStatus } from '@lovelybunch/types';
|
|
2
|
+
export interface CPFilter {
|
|
3
|
+
status?: CPStatus;
|
|
4
|
+
author?: string;
|
|
5
|
+
priority?: string;
|
|
6
|
+
tags?: string[];
|
|
7
|
+
}
|
|
8
|
+
export interface StorageAdapter {
|
|
9
|
+
createCP(cp: ChangeProposal): Promise<void>;
|
|
10
|
+
getCP(id: string): Promise<ChangeProposal | null>;
|
|
11
|
+
updateCP(id: string, cp: Partial<ChangeProposal>): Promise<void>;
|
|
12
|
+
deleteCP(id: string): Promise<void>;
|
|
13
|
+
listCPs(filter?: CPFilter): Promise<ChangeProposal[]>;
|
|
14
|
+
searchCPs(query: string): Promise<ChangeProposal[]>;
|
|
15
|
+
}
|
|
16
|
+
export declare class FileStorageAdapter implements StorageAdapter {
|
|
17
|
+
private basePath;
|
|
18
|
+
constructor(basePath?: string);
|
|
19
|
+
ensureDirectories(): Promise<void>;
|
|
20
|
+
createCP(cp: ChangeProposal): Promise<void>;
|
|
21
|
+
getCP(id: string): Promise<ChangeProposal | null>;
|
|
22
|
+
updateCP(id: string, updates: Partial<ChangeProposal>): Promise<void>;
|
|
23
|
+
deleteCP(id: string): Promise<void>;
|
|
24
|
+
listCPs(filter?: CPFilter): Promise<ChangeProposal[]>;
|
|
25
|
+
searchCPs(query: string): Promise<ChangeProposal[]>;
|
|
26
|
+
private frontmatterToCP;
|
|
27
|
+
private getDefaultContent;
|
|
28
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import matter from 'gray-matter';
|
|
4
|
+
import Fuse from 'fuse.js';
|
|
5
|
+
export class FileStorageAdapter {
|
|
6
|
+
basePath;
|
|
7
|
+
constructor(basePath) {
|
|
8
|
+
// Use environment variable if available (for web server), otherwise default to .gait
|
|
9
|
+
this.basePath = basePath || (process.env.GAIT_DATA_PATH ?
|
|
10
|
+
path.join(process.env.GAIT_DATA_PATH, '.gait') :
|
|
11
|
+
'.gait');
|
|
12
|
+
}
|
|
13
|
+
async ensureDirectories() {
|
|
14
|
+
const dirs = ['proposals', 'specs', 'flags', 'experiments', 'templates'];
|
|
15
|
+
for (const dir of dirs) {
|
|
16
|
+
await fs.mkdir(path.join(this.basePath, dir), { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async createCP(cp) {
|
|
20
|
+
await this.ensureDirectories();
|
|
21
|
+
// Extract content from the proposal if it exists
|
|
22
|
+
const { content, ...frontmatter } = cp;
|
|
23
|
+
// Convert the proposal to markdown with YAML frontmatter
|
|
24
|
+
const markdown = matter.stringify(content || this.getDefaultContent(cp), {
|
|
25
|
+
// Required fields
|
|
26
|
+
id: cp.id,
|
|
27
|
+
intent: cp.intent,
|
|
28
|
+
createdAt: cp.metadata.createdAt.toISOString(),
|
|
29
|
+
updatedAt: cp.metadata.updatedAt.toISOString(),
|
|
30
|
+
status: cp.status,
|
|
31
|
+
priority: cp.metadata.priority || 'medium',
|
|
32
|
+
// Author information
|
|
33
|
+
author: {
|
|
34
|
+
id: cp.author.id,
|
|
35
|
+
name: cp.author.name,
|
|
36
|
+
email: cp.author.email || '',
|
|
37
|
+
role: 'engineer', // Default role
|
|
38
|
+
type: cp.author.type
|
|
39
|
+
},
|
|
40
|
+
// Plan
|
|
41
|
+
plan: {
|
|
42
|
+
estimatedDuration: 'TBD',
|
|
43
|
+
dependencies: [],
|
|
44
|
+
steps: cp.planSteps.map(step => ({
|
|
45
|
+
id: step.id,
|
|
46
|
+
description: step.description,
|
|
47
|
+
status: step.status
|
|
48
|
+
}))
|
|
49
|
+
},
|
|
50
|
+
// Metadata
|
|
51
|
+
tags: cp.metadata.tags || [],
|
|
52
|
+
labels: [],
|
|
53
|
+
comments: cp.comments || []
|
|
54
|
+
});
|
|
55
|
+
const filePath = path.join(this.basePath, 'proposals', `${cp.id}.md`);
|
|
56
|
+
await fs.writeFile(filePath, markdown, 'utf-8');
|
|
57
|
+
}
|
|
58
|
+
async getCP(id) {
|
|
59
|
+
try {
|
|
60
|
+
const filePath = path.join(this.basePath, 'proposals', `${id}.md`);
|
|
61
|
+
const fileContent = await fs.readFile(filePath, 'utf-8');
|
|
62
|
+
const { data, content } = matter(fileContent);
|
|
63
|
+
// Convert the frontmatter back to a ChangeProposal
|
|
64
|
+
return this.frontmatterToCP(data, content);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (error.code === 'ENOENT')
|
|
68
|
+
return null;
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async updateCP(id, updates) {
|
|
73
|
+
const existing = await this.getCP(id);
|
|
74
|
+
if (!existing)
|
|
75
|
+
throw new Error(`CP ${id} not found`);
|
|
76
|
+
// Handle comments separately as they're stored at root level in file format
|
|
77
|
+
let updated = {
|
|
78
|
+
...existing,
|
|
79
|
+
...updates,
|
|
80
|
+
metadata: {
|
|
81
|
+
...existing.metadata,
|
|
82
|
+
...updates.metadata,
|
|
83
|
+
updatedAt: new Date()
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
// If comments are being updated, store them at the root level for the file format
|
|
87
|
+
if (updates.comments) {
|
|
88
|
+
updated.comments = updates.comments;
|
|
89
|
+
}
|
|
90
|
+
await this.createCP(updated);
|
|
91
|
+
}
|
|
92
|
+
async deleteCP(id) {
|
|
93
|
+
const filePath = path.join(this.basePath, 'proposals', `${id}.md`);
|
|
94
|
+
await fs.unlink(filePath);
|
|
95
|
+
}
|
|
96
|
+
async listCPs(filter) {
|
|
97
|
+
const proposalsDir = path.join(this.basePath, 'proposals');
|
|
98
|
+
try {
|
|
99
|
+
const files = await fs.readdir(proposalsDir);
|
|
100
|
+
const proposals = await Promise.all(files
|
|
101
|
+
.filter(f => f.endsWith('.md'))
|
|
102
|
+
.map(async (file) => {
|
|
103
|
+
const content = await fs.readFile(path.join(proposalsDir, file), 'utf-8');
|
|
104
|
+
const { data, content: body } = matter(content);
|
|
105
|
+
return this.frontmatterToCP(data, body);
|
|
106
|
+
}));
|
|
107
|
+
// Apply filters
|
|
108
|
+
return proposals.filter(cp => {
|
|
109
|
+
if (!cp)
|
|
110
|
+
return false;
|
|
111
|
+
if (filter?.status && cp.status !== filter.status)
|
|
112
|
+
return false;
|
|
113
|
+
if (filter?.author && cp.author.id !== filter.author)
|
|
114
|
+
return false;
|
|
115
|
+
if (filter?.priority && cp.metadata.priority !== filter.priority)
|
|
116
|
+
return false;
|
|
117
|
+
if (filter?.tags && filter.tags.length > 0) {
|
|
118
|
+
const cpTags = cp.metadata.tags || [];
|
|
119
|
+
if (!filter.tags.some(tag => cpTags.includes(tag)))
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
return true;
|
|
123
|
+
}).filter(Boolean);
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
if (error.code === 'ENOENT')
|
|
127
|
+
return [];
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async searchCPs(query) {
|
|
132
|
+
const allCPs = await this.listCPs();
|
|
133
|
+
const fuse = new Fuse(allCPs, {
|
|
134
|
+
keys: [
|
|
135
|
+
{ name: 'intent', weight: 2 },
|
|
136
|
+
{ name: 'content', weight: 1 },
|
|
137
|
+
{ name: 'author.name', weight: 0.5 },
|
|
138
|
+
{ name: 'metadata.tags', weight: 1 }
|
|
139
|
+
],
|
|
140
|
+
threshold: 0.3,
|
|
141
|
+
includeScore: true
|
|
142
|
+
});
|
|
143
|
+
return fuse.search(query).map(result => result.item);
|
|
144
|
+
}
|
|
145
|
+
frontmatterToCP(data, content) {
|
|
146
|
+
// Handle both old and new format files
|
|
147
|
+
const steps = data.plan?.steps || data.steps || [];
|
|
148
|
+
// Transform comments to match expected format
|
|
149
|
+
const transformedComments = (data.comments || []).map((comment) => ({
|
|
150
|
+
id: comment.id,
|
|
151
|
+
text: comment.content || comment.text || '', // Handle both content and text fields
|
|
152
|
+
author: typeof comment.author === 'string' ? comment.author : comment.author?.name || 'Unknown',
|
|
153
|
+
timestamp: comment.createdAt || comment.timestamp || new Date().toISOString()
|
|
154
|
+
}));
|
|
155
|
+
return {
|
|
156
|
+
id: data.id,
|
|
157
|
+
intent: data.intent,
|
|
158
|
+
author: {
|
|
159
|
+
id: data.author?.id || data.author?.name || 'unknown',
|
|
160
|
+
name: data.author?.name || 'Unknown',
|
|
161
|
+
email: data.author?.email,
|
|
162
|
+
type: data.author?.type || 'human'
|
|
163
|
+
},
|
|
164
|
+
productSpecRef: data.productSpecRef,
|
|
165
|
+
planSteps: steps.map((step) => ({
|
|
166
|
+
id: step.id || `step-${Date.now()}`,
|
|
167
|
+
description: step.description || step.title || 'Unnamed step',
|
|
168
|
+
status: step.status || 'pending',
|
|
169
|
+
command: step.command,
|
|
170
|
+
expectedOutcome: step.expectedOutcome,
|
|
171
|
+
output: step.output,
|
|
172
|
+
error: step.error,
|
|
173
|
+
executedAt: step.executedAt ? new Date(step.executedAt) : undefined
|
|
174
|
+
})),
|
|
175
|
+
evidence: data.evidence || [],
|
|
176
|
+
policies: data.policies || [],
|
|
177
|
+
featureFlags: data.featureFlags || [],
|
|
178
|
+
experiments: data.experiments || [],
|
|
179
|
+
telemetryContracts: data.telemetryContracts || [],
|
|
180
|
+
releasePlan: data.releasePlan || { strategy: 'immediate' },
|
|
181
|
+
status: data.status || 'draft',
|
|
182
|
+
metadata: {
|
|
183
|
+
createdAt: new Date(data.createdAt || Date.now()),
|
|
184
|
+
updatedAt: new Date(data.updatedAt || Date.now()),
|
|
185
|
+
reviewers: data.reviewers || [],
|
|
186
|
+
aiInteractions: data.aiInteractions || [],
|
|
187
|
+
tags: data.tags || [],
|
|
188
|
+
priority: data.priority || 'medium',
|
|
189
|
+
comments: transformedComments
|
|
190
|
+
},
|
|
191
|
+
content // Store the markdown content
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
getDefaultContent(cp) {
|
|
195
|
+
return `# ${cp.intent}
|
|
196
|
+
|
|
197
|
+
## Problem Statement
|
|
198
|
+
Describe the problem this change proposal addresses.
|
|
199
|
+
|
|
200
|
+
## Proposed Solution
|
|
201
|
+
Describe the solution you're proposing.
|
|
202
|
+
|
|
203
|
+
### Key Features
|
|
204
|
+
- Feature 1
|
|
205
|
+
- Feature 2
|
|
206
|
+
- Feature 3
|
|
207
|
+
|
|
208
|
+
## Implementation Details
|
|
209
|
+
Describe the technical implementation approach.
|
|
210
|
+
|
|
211
|
+
## Testing Strategy
|
|
212
|
+
- [ ] Unit tests
|
|
213
|
+
- [ ] Integration tests
|
|
214
|
+
- [ ] Manual testing
|
|
215
|
+
|
|
216
|
+
## Rollout Plan
|
|
217
|
+
1. Development and testing
|
|
218
|
+
2. Staging deployment
|
|
219
|
+
3. Production rollout
|
|
220
|
+
|
|
221
|
+
## Success Metrics
|
|
222
|
+
Define how you'll measure the success of this change.`;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { SymlinkConfig, SymlinkOperationResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* SymlinkManager handles all symlink operations and persists state
|
|
4
|
+
*/
|
|
5
|
+
export declare class SymlinkManager {
|
|
6
|
+
private configPath;
|
|
7
|
+
private projectRoot;
|
|
8
|
+
private state;
|
|
9
|
+
constructor(projectRoot: string);
|
|
10
|
+
/**
|
|
11
|
+
* Initialize the manager and load existing configuration
|
|
12
|
+
*/
|
|
13
|
+
initialize(): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Create default configuration with the CLAUDE.md symlink
|
|
16
|
+
*/
|
|
17
|
+
private createDefaultConfig;
|
|
18
|
+
/**
|
|
19
|
+
* Validate symlinks against actual filesystem state
|
|
20
|
+
*/
|
|
21
|
+
private validateSymlinks;
|
|
22
|
+
/**
|
|
23
|
+
* Save current state to configuration file
|
|
24
|
+
*/
|
|
25
|
+
private saveState;
|
|
26
|
+
/**
|
|
27
|
+
* Get all symlink configurations
|
|
28
|
+
*/
|
|
29
|
+
getSymlinks(): Promise<SymlinkConfig[]>;
|
|
30
|
+
/**
|
|
31
|
+
* Get a specific symlink by ID
|
|
32
|
+
*/
|
|
33
|
+
getSymlink(id: string): Promise<SymlinkConfig | undefined>;
|
|
34
|
+
/**
|
|
35
|
+
* Add a new symlink configuration
|
|
36
|
+
*/
|
|
37
|
+
addSymlink(config: Omit<SymlinkConfig, 'id' | 'createdAt' | 'updatedAt'>): Promise<SymlinkOperationResult>;
|
|
38
|
+
/**
|
|
39
|
+
* Toggle a symlink on/off
|
|
40
|
+
*/
|
|
41
|
+
toggleSymlink(id: string): Promise<SymlinkOperationResult>;
|
|
42
|
+
/**
|
|
43
|
+
* Expand tilde in path to actual home directory
|
|
44
|
+
*/
|
|
45
|
+
private expandTilde;
|
|
46
|
+
/**
|
|
47
|
+
* Create a symlink on the filesystem
|
|
48
|
+
*/
|
|
49
|
+
private createSymlink;
|
|
50
|
+
/**
|
|
51
|
+
* Remove a symlink from the filesystem
|
|
52
|
+
*/
|
|
53
|
+
private removeSymlink;
|
|
54
|
+
/**
|
|
55
|
+
* Delete a symlink configuration entirely
|
|
56
|
+
*/
|
|
57
|
+
deleteSymlink(id: string): Promise<SymlinkOperationResult>;
|
|
58
|
+
/**
|
|
59
|
+
* Update a symlink configuration
|
|
60
|
+
*/
|
|
61
|
+
updateSymlink(id: string, updates: Partial<Omit<SymlinkConfig, 'id' | 'createdAt'>>): Promise<SymlinkOperationResult>;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Get or create the SymlinkManager instance
|
|
65
|
+
*/
|
|
66
|
+
export declare function getSymlinkManager(): Promise<SymlinkManager>;
|