@aicgen/aicgen 1.0.0-beta.1 ā 1.0.0-beta.2
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/.vs/ProjectSettings.json +2 -2
- package/.vs/VSWorkspaceState.json +15 -15
- package/.vs/aicgen.slnx/v18/DocumentLayout.json +53 -53
- package/assets/icon.svg +33 -33
- package/bun.lock +0 -43
- package/data/architecture/microservices/api-gateway.md +56 -56
- package/data/devops/observability.md +73 -73
- package/dist/index.js +9299 -9299
- package/package.json +2 -2
- package/.claude/agents/architecture-reviewer.md +0 -88
- package/.claude/agents/guideline-checker.md +0 -73
- package/.claude/agents/security-auditor.md +0 -108
- package/.claude/guidelines/api-design.md +0 -645
- package/.claude/guidelines/architecture.md +0 -2503
- package/.claude/guidelines/best-practices.md +0 -618
- package/.claude/guidelines/code-style.md +0 -304
- package/.claude/guidelines/design-patterns.md +0 -573
- package/.claude/guidelines/devops.md +0 -226
- package/.claude/guidelines/error-handling.md +0 -413
- package/.claude/guidelines/language.md +0 -782
- package/.claude/guidelines/performance.md +0 -706
- package/.claude/guidelines/security.md +0 -583
- package/.claude/guidelines/testing.md +0 -568
- package/.claude/settings.json +0 -98
- package/.claude/settings.local.json +0 -8
- package/.eslintrc.json +0 -28
- package/.github/workflows/release.yml +0 -180
- package/.github/workflows/test.yml +0 -81
- package/CONTRIBUTING.md +0 -821
- package/dist/commands/init.d.ts +0 -8
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.js +0 -46
- package/dist/commands/init.js.map +0 -1
- package/dist/config/profiles.d.ts +0 -4
- package/dist/config/profiles.d.ts.map +0 -1
- package/dist/config/profiles.js +0 -30
- package/dist/config/profiles.js.map +0 -1
- package/dist/config/settings.d.ts +0 -7
- package/dist/config/settings.d.ts.map +0 -1
- package/dist/config/settings.js +0 -7
- package/dist/config/settings.js.map +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/models/guideline.d.ts +0 -15
- package/dist/models/guideline.d.ts.map +0 -1
- package/dist/models/guideline.js +0 -2
- package/dist/models/guideline.js.map +0 -1
- package/dist/models/preference.d.ts +0 -9
- package/dist/models/preference.d.ts.map +0 -1
- package/dist/models/preference.js +0 -2
- package/dist/models/preference.js.map +0 -1
- package/dist/models/profile.d.ts +0 -9
- package/dist/models/profile.d.ts.map +0 -1
- package/dist/models/profile.js +0 -2
- package/dist/models/profile.js.map +0 -1
- package/dist/models/project.d.ts +0 -13
- package/dist/models/project.d.ts.map +0 -1
- package/dist/models/project.js +0 -2
- package/dist/models/project.js.map +0 -1
- package/dist/services/ai/anthropic.d.ts +0 -7
- package/dist/services/ai/anthropic.d.ts.map +0 -1
- package/dist/services/ai/anthropic.js +0 -39
- package/dist/services/ai/anthropic.js.map +0 -1
- package/dist/services/generator.d.ts +0 -2
- package/dist/services/generator.d.ts.map +0 -1
- package/dist/services/generator.js +0 -4
- package/dist/services/generator.js.map +0 -1
- package/dist/services/learner.d.ts +0 -2
- package/dist/services/learner.d.ts.map +0 -1
- package/dist/services/learner.js +0 -4
- package/dist/services/learner.js.map +0 -1
- package/dist/services/scanner.d.ts +0 -3
- package/dist/services/scanner.d.ts.map +0 -1
- package/dist/services/scanner.js +0 -54
- package/dist/services/scanner.js.map +0 -1
- package/dist/utils/errors.d.ts +0 -15
- package/dist/utils/errors.d.ts.map +0 -1
- package/dist/utils/errors.js +0 -27
- package/dist/utils/errors.js.map +0 -1
- package/dist/utils/file.d.ts +0 -7
- package/dist/utils/file.d.ts.map +0 -1
- package/dist/utils/file.js +0 -32
- package/dist/utils/file.js.map +0 -1
- package/dist/utils/logger.d.ts +0 -6
- package/dist/utils/logger.d.ts.map +0 -1
- package/dist/utils/logger.js +0 -17
- package/dist/utils/logger.js.map +0 -1
- package/dist/utils/path.d.ts +0 -6
- package/dist/utils/path.d.ts.map +0 -1
- package/dist/utils/path.js +0 -14
- package/dist/utils/path.js.map +0 -1
- package/docs/planning/memory-lane.md +0 -83
- package/packaging/linux/aicgen.spec +0 -23
- package/packaging/linux/control +0 -9
- package/packaging/macos/scripts/postinstall +0 -12
- package/packaging/windows/setup.nsi +0 -92
- package/scripts/add-categories.ts +0 -87
- package/scripts/build-binary.ts +0 -46
- package/scripts/embed-data.ts +0 -105
- package/scripts/generate-version.ts +0 -150
- package/scripts/test-decompress.ts +0 -27
- package/scripts/test-extract.ts +0 -31
- package/src/__tests__/services/assistant-file-writer.test.ts +0 -400
- package/src/__tests__/services/guideline-loader.test.ts +0 -281
- package/src/__tests__/services/tarball-extraction.test.ts +0 -125
- package/src/commands/add-guideline.ts +0 -296
- package/src/commands/clear.ts +0 -61
- package/src/commands/guideline-selector.ts +0 -123
- package/src/commands/init.ts +0 -645
- package/src/commands/quick-add.ts +0 -586
- package/src/commands/remove-guideline.ts +0 -152
- package/src/commands/stats.ts +0 -49
- package/src/commands/update.ts +0 -240
- package/src/config.ts +0 -82
- package/src/embedded-data.ts +0 -1492
- package/src/index.ts +0 -67
- package/src/models/profile.ts +0 -24
- package/src/models/project.ts +0 -43
- package/src/services/assistant-file-writer.ts +0 -612
- package/src/services/config-generator.ts +0 -150
- package/src/services/config-manager.ts +0 -70
- package/src/services/data-source.ts +0 -248
- package/src/services/first-run-init.ts +0 -148
- package/src/services/guideline-loader.ts +0 -311
- package/src/services/hook-generator.ts +0 -178
- package/src/services/subagent-generator.ts +0 -310
- package/src/utils/banner.ts +0 -66
- package/src/utils/errors.ts +0 -27
- package/src/utils/file.ts +0 -67
- package/src/utils/formatting.ts +0 -172
- package/src/utils/logger.ts +0 -89
- package/src/utils/path.ts +0 -17
- package/src/utils/wizard-state.ts +0 -132
- package/tsconfig.json +0 -25
- /package/{CLAUDE.md ā claude.md} +0 -0
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import { join } from 'path';
|
|
2
|
-
import { exists, readJSON } from '../utils/file.js';
|
|
3
|
-
import { GuidelineLoader } from './guideline-loader.js';
|
|
4
|
-
import { AssistantFileWriter } from './assistant-file-writer.js';
|
|
5
|
-
import { DetectedProject, Language } from '../models/project.js';
|
|
6
|
-
import { ProfileSelection } from '../models/profile.js';
|
|
7
|
-
|
|
8
|
-
export interface GenerationOptions {
|
|
9
|
-
projectPath: string;
|
|
10
|
-
selection: ProfileSelection;
|
|
11
|
-
customGuidelineIds?: string[];
|
|
12
|
-
dryRun?: boolean;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface GenerationResult {
|
|
16
|
-
success: boolean;
|
|
17
|
-
filesGenerated: string[];
|
|
18
|
-
errors: string[];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export class ConfigGenerator {
|
|
22
|
-
private guidelineLoader: GuidelineLoader;
|
|
23
|
-
private fileWriter: AssistantFileWriter;
|
|
24
|
-
|
|
25
|
-
static async create(): Promise<ConfigGenerator> {
|
|
26
|
-
const guidelineLoader = await GuidelineLoader.create();
|
|
27
|
-
const fileWriter = await AssistantFileWriter.create();
|
|
28
|
-
return new ConfigGenerator(guidelineLoader, fileWriter);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
private constructor(guidelineLoader: GuidelineLoader, fileWriter: AssistantFileWriter) {
|
|
32
|
-
this.guidelineLoader = guidelineLoader;
|
|
33
|
-
this.fileWriter = fileWriter;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async detectProject(projectPath: string): Promise<DetectedProject> {
|
|
37
|
-
const name = await this.getProjectName(projectPath);
|
|
38
|
-
const language = await this.detectLanguage(projectPath);
|
|
39
|
-
const hasExistingConfig = await this.hasExistingConfig(projectPath);
|
|
40
|
-
|
|
41
|
-
return { name, language, hasExistingConfig };
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async generate(options: GenerationOptions): Promise<GenerationResult> {
|
|
45
|
-
const errors: string[] = [];
|
|
46
|
-
const filesGenerated: string[] = [];
|
|
47
|
-
|
|
48
|
-
try {
|
|
49
|
-
const guidelineIds = options.customGuidelineIds || this.guidelineLoader.getGuidelinesForProfile(
|
|
50
|
-
options.selection.language,
|
|
51
|
-
options.selection.level,
|
|
52
|
-
options.selection.architecture,
|
|
53
|
-
options.selection.datasource
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
if (guidelineIds.length === 0) {
|
|
57
|
-
throw new Error(`No guidelines found for profile: ${options.selection.language}-${options.selection.level}-${options.selection.architecture}`);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const files = await this.fileWriter.generateFiles(
|
|
61
|
-
options.selection.assistant,
|
|
62
|
-
guidelineIds,
|
|
63
|
-
options.selection,
|
|
64
|
-
options.projectPath
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
if (options.dryRun) {
|
|
68
|
-
return {
|
|
69
|
-
success: true,
|
|
70
|
-
filesGenerated: files.map(f => f.path),
|
|
71
|
-
errors: []
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
await this.fileWriter.writeFiles(files);
|
|
76
|
-
files.forEach(f => filesGenerated.push(f.path));
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
success: true,
|
|
80
|
-
filesGenerated,
|
|
81
|
-
errors: []
|
|
82
|
-
};
|
|
83
|
-
} catch (error) {
|
|
84
|
-
errors.push((error as Error).message);
|
|
85
|
-
return {
|
|
86
|
-
success: false,
|
|
87
|
-
filesGenerated,
|
|
88
|
-
errors
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
getStats() {
|
|
94
|
-
return this.guidelineLoader.getStats();
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
private async getProjectName(projectPath: string): Promise<string> {
|
|
98
|
-
const pkgPath = join(projectPath, 'package.json');
|
|
99
|
-
if (await exists(pkgPath)) {
|
|
100
|
-
try {
|
|
101
|
-
const pkg = await readJSON<{ name?: string }>(pkgPath);
|
|
102
|
-
if (pkg.name) return pkg.name;
|
|
103
|
-
} catch {
|
|
104
|
-
// Ignore
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const parts = projectPath.split(/[/\\]/);
|
|
109
|
-
return parts[parts.length - 1] || 'project';
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
private async detectLanguage(projectPath: string): Promise<Language> {
|
|
113
|
-
if (await exists(join(projectPath, 'tsconfig.json'))) {
|
|
114
|
-
return 'typescript';
|
|
115
|
-
}
|
|
116
|
-
if (await exists(join(projectPath, 'package.json'))) {
|
|
117
|
-
return 'javascript';
|
|
118
|
-
}
|
|
119
|
-
if (await exists(join(projectPath, 'requirements.txt')) ||
|
|
120
|
-
await exists(join(projectPath, 'pyproject.toml')) ||
|
|
121
|
-
await exists(join(projectPath, 'Pipfile'))) {
|
|
122
|
-
return 'python';
|
|
123
|
-
}
|
|
124
|
-
if (await exists(join(projectPath, 'go.mod'))) {
|
|
125
|
-
return 'go';
|
|
126
|
-
}
|
|
127
|
-
if (await exists(join(projectPath, 'Cargo.toml'))) {
|
|
128
|
-
return 'rust';
|
|
129
|
-
}
|
|
130
|
-
if (await exists(join(projectPath, 'pom.xml')) ||
|
|
131
|
-
await exists(join(projectPath, 'build.gradle'))) {
|
|
132
|
-
return 'java';
|
|
133
|
-
}
|
|
134
|
-
if (await exists(join(projectPath, 'Gemfile'))) {
|
|
135
|
-
return 'ruby';
|
|
136
|
-
}
|
|
137
|
-
return 'unknown';
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
private async hasExistingConfig(projectPath: string): Promise<boolean> {
|
|
141
|
-
return (
|
|
142
|
-
await exists(join(projectPath, 'claude.md')) ||
|
|
143
|
-
await exists(join(projectPath, '.claude')) ||
|
|
144
|
-
await exists(join(projectPath, '.github', 'copilot-instructions.md')) ||
|
|
145
|
-
await exists(join(projectPath, '.gemini')) ||
|
|
146
|
-
await exists(join(projectPath, '.agent')) ||
|
|
147
|
-
await exists(join(projectPath, '.codex'))
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { readFile, writeFile, mkdir, access } from 'fs/promises';
|
|
2
|
-
import { join } from 'path';
|
|
3
|
-
import { homedir } from 'os';
|
|
4
|
-
import YAML from 'yaml';
|
|
5
|
-
import { CONFIG } from '../config.js';
|
|
6
|
-
|
|
7
|
-
export interface UserConfig {
|
|
8
|
-
initialized?: boolean;
|
|
9
|
-
lastUpdate?: string;
|
|
10
|
-
dataVersion?: string;
|
|
11
|
-
github?: {
|
|
12
|
-
owner?: string;
|
|
13
|
-
repo?: string;
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export class ConfigManager {
|
|
18
|
-
private configPath: string;
|
|
19
|
-
private config: UserConfig = {};
|
|
20
|
-
|
|
21
|
-
constructor() {
|
|
22
|
-
this.configPath = join(homedir(), CONFIG.CACHE_DIR_NAME, 'config.yml');
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async load(): Promise<UserConfig> {
|
|
26
|
-
try {
|
|
27
|
-
await access(this.configPath);
|
|
28
|
-
const content = await readFile(this.configPath, 'utf-8');
|
|
29
|
-
this.config = YAML.parse(content) || {};
|
|
30
|
-
} catch {
|
|
31
|
-
// Config doesn't exist yet, use defaults
|
|
32
|
-
this.config = {
|
|
33
|
-
initialized: false
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
return this.config;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async save(): Promise<void> {
|
|
40
|
-
const dir = join(homedir(), CONFIG.CACHE_DIR_NAME);
|
|
41
|
-
await mkdir(dir, { recursive: true });
|
|
42
|
-
await writeFile(this.configPath, YAML.stringify(this.config), 'utf-8');
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async markInitialized(version: string): Promise<void> {
|
|
46
|
-
this.config.initialized = true;
|
|
47
|
-
this.config.dataVersion = version;
|
|
48
|
-
this.config.lastUpdate = new Date().toISOString();
|
|
49
|
-
await this.save();
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async updateDataVersion(version: string): Promise<void> {
|
|
53
|
-
this.config.dataVersion = version;
|
|
54
|
-
this.config.lastUpdate = new Date().toISOString();
|
|
55
|
-
await this.save();
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
isInitialized(): boolean {
|
|
59
|
-
return this.config.initialized === true;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
getDataVersion(): string | undefined {
|
|
63
|
-
return this.config.dataVersion;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async setGitHubRepo(owner: string, repo: string): Promise<void> {
|
|
67
|
-
this.config.github = { owner, repo };
|
|
68
|
-
await this.save();
|
|
69
|
-
}
|
|
70
|
-
}
|
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
import { readFile, readdir, access } from 'fs/promises';
|
|
2
|
-
import { join } from 'path';
|
|
3
|
-
import YAML from 'yaml';
|
|
4
|
-
import { EMBEDDED_DATA, GuidelineMapping } from '../embedded-data.js';
|
|
5
|
-
|
|
6
|
-
export interface GuidelineData {
|
|
7
|
-
mappings: Record<string, GuidelineMapping>;
|
|
8
|
-
guidelines: Record<string, string>;
|
|
9
|
-
version?: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface DataSource {
|
|
13
|
-
load(): Promise<GuidelineData>;
|
|
14
|
-
exists(): Promise<boolean>;
|
|
15
|
-
getVersion(): Promise<string>;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export class EmbeddedDataSource implements DataSource {
|
|
19
|
-
async load(): Promise<GuidelineData> {
|
|
20
|
-
return {
|
|
21
|
-
mappings: EMBEDDED_DATA.mappings,
|
|
22
|
-
guidelines: EMBEDDED_DATA.guidelines,
|
|
23
|
-
version: 'embedded'
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async exists(): Promise<boolean> {
|
|
28
|
-
return true;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async getVersion(): Promise<string> {
|
|
32
|
-
return 'embedded';
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export class FileSystemDataSource implements DataSource {
|
|
37
|
-
constructor(private basePath: string) {}
|
|
38
|
-
|
|
39
|
-
async load(): Promise<GuidelineData> {
|
|
40
|
-
const mappingsPath = join(this.basePath, 'guideline-mappings.yml');
|
|
41
|
-
const mappingsContent = await readFile(mappingsPath, 'utf-8');
|
|
42
|
-
const mappings = YAML.parse(mappingsContent) as Record<string, GuidelineMapping>;
|
|
43
|
-
|
|
44
|
-
const guidelines: Record<string, string> = {};
|
|
45
|
-
const guidelinesDir = join(this.basePath, 'guidelines');
|
|
46
|
-
|
|
47
|
-
try {
|
|
48
|
-
await this.loadGuidelinesRecursive(guidelinesDir, '', guidelines);
|
|
49
|
-
} catch {
|
|
50
|
-
// Guidelines directory might not exist yet
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const version = await this.readVersion();
|
|
54
|
-
|
|
55
|
-
return { mappings, guidelines, version };
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async exists(): Promise<boolean> {
|
|
59
|
-
try {
|
|
60
|
-
await access(join(this.basePath, 'guideline-mappings.yml'));
|
|
61
|
-
return true;
|
|
62
|
-
} catch {
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async getVersion(): Promise<string> {
|
|
68
|
-
return await this.readVersion();
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
private async readVersion(): Promise<string> {
|
|
72
|
-
try {
|
|
73
|
-
const versionPath = join(this.basePath, 'version.json');
|
|
74
|
-
const versionContent = await readFile(versionPath, 'utf-8');
|
|
75
|
-
const versionData = JSON.parse(versionContent);
|
|
76
|
-
return versionData.version || 'unknown';
|
|
77
|
-
} catch {
|
|
78
|
-
return 'unknown';
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
private async loadGuidelinesRecursive(
|
|
83
|
-
dir: string,
|
|
84
|
-
relativePath: string,
|
|
85
|
-
guidelines: Record<string, string>
|
|
86
|
-
): Promise<void> {
|
|
87
|
-
try {
|
|
88
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
89
|
-
|
|
90
|
-
for (const entry of entries) {
|
|
91
|
-
const fullPath = join(dir, entry.name);
|
|
92
|
-
const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
93
|
-
|
|
94
|
-
if (entry.isDirectory()) {
|
|
95
|
-
await this.loadGuidelinesRecursive(fullPath, relPath, guidelines);
|
|
96
|
-
} else if (entry.name.endsWith('.md')) {
|
|
97
|
-
const content = await readFile(fullPath, 'utf-8');
|
|
98
|
-
guidelines[relPath] = content;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
} catch {
|
|
102
|
-
// Directory might not exist
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export class CustomMappingsDataSource implements DataSource {
|
|
108
|
-
constructor(private basePath: string) {}
|
|
109
|
-
|
|
110
|
-
async load(): Promise<GuidelineData> {
|
|
111
|
-
const mappingsPath = join(this.basePath, 'custom-mappings.yml');
|
|
112
|
-
|
|
113
|
-
let mappings: Record<string, GuidelineMapping> = {};
|
|
114
|
-
try {
|
|
115
|
-
const mappingsContent = await readFile(mappingsPath, 'utf-8');
|
|
116
|
-
mappings = YAML.parse(mappingsContent) as Record<string, GuidelineMapping>;
|
|
117
|
-
} catch {
|
|
118
|
-
// Custom mappings don't exist yet
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const guidelines: Record<string, string> = {};
|
|
122
|
-
const guidelinesDir = join(this.basePath, 'guidelines');
|
|
123
|
-
|
|
124
|
-
try {
|
|
125
|
-
await this.loadGuidelinesRecursive(guidelinesDir, '', guidelines);
|
|
126
|
-
} catch {
|
|
127
|
-
// Guidelines directory might not exist yet
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return { mappings, guidelines, version: 'custom' };
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
async exists(): Promise<boolean> {
|
|
134
|
-
try {
|
|
135
|
-
await access(join(this.basePath, 'custom-mappings.yml'));
|
|
136
|
-
return true;
|
|
137
|
-
} catch {
|
|
138
|
-
// Check if guidelines directory exists
|
|
139
|
-
try {
|
|
140
|
-
await access(join(this.basePath, 'guidelines'));
|
|
141
|
-
return true;
|
|
142
|
-
} catch {
|
|
143
|
-
return false;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
async getVersion(): Promise<string> {
|
|
149
|
-
return 'custom';
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
private async loadGuidelinesRecursive(
|
|
153
|
-
dir: string,
|
|
154
|
-
relativePath: string,
|
|
155
|
-
guidelines: Record<string, string>
|
|
156
|
-
): Promise<void> {
|
|
157
|
-
try {
|
|
158
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
159
|
-
|
|
160
|
-
for (const entry of entries) {
|
|
161
|
-
const fullPath = join(dir, entry.name);
|
|
162
|
-
const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
163
|
-
|
|
164
|
-
if (entry.isDirectory()) {
|
|
165
|
-
await this.loadGuidelinesRecursive(fullPath, relPath, guidelines);
|
|
166
|
-
} else if (entry.name.endsWith('.md')) {
|
|
167
|
-
const content = await readFile(fullPath, 'utf-8');
|
|
168
|
-
guidelines[relPath] = content;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
} catch {
|
|
172
|
-
// Directory might not exist
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export class HybridDataSource implements DataSource {
|
|
178
|
-
constructor(private sources: DataSource[]) {}
|
|
179
|
-
|
|
180
|
-
async load(): Promise<GuidelineData> {
|
|
181
|
-
const results = await Promise.all(
|
|
182
|
-
this.sources.map(async (source) => {
|
|
183
|
-
if (await source.exists()) {
|
|
184
|
-
try {
|
|
185
|
-
return await source.load();
|
|
186
|
-
} catch (error) {
|
|
187
|
-
console.warn(`Failed to load from data source: ${error}`);
|
|
188
|
-
return null;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
return null;
|
|
192
|
-
})
|
|
193
|
-
);
|
|
194
|
-
|
|
195
|
-
const validResults = results.filter((r): r is GuidelineData => r !== null);
|
|
196
|
-
|
|
197
|
-
if (validResults.length === 0) {
|
|
198
|
-
throw new Error('No data sources available');
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
return this.merge(validResults);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
async exists(): Promise<boolean> {
|
|
205
|
-
const exists = await Promise.all(this.sources.map((s) => s.exists()));
|
|
206
|
-
return exists.some((e) => e);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
async getVersion(): Promise<string> {
|
|
210
|
-
for (const source of this.sources) {
|
|
211
|
-
if (await source.exists()) {
|
|
212
|
-
const version = await source.getVersion();
|
|
213
|
-
if (version !== 'custom' && version !== 'unknown') {
|
|
214
|
-
return version;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
return 'embedded';
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
private merge(dataArray: GuidelineData[]): GuidelineData {
|
|
222
|
-
// Merge with priority: first source has highest priority
|
|
223
|
-
const merged: GuidelineData = {
|
|
224
|
-
mappings: {},
|
|
225
|
-
guidelines: {},
|
|
226
|
-
version: dataArray[0]?.version || 'unknown'
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
// Merge in reverse order so earlier sources override later ones
|
|
230
|
-
for (let i = dataArray.length - 1; i >= 0; i--) {
|
|
231
|
-
const data = dataArray[i];
|
|
232
|
-
Object.assign(merged.mappings, data.mappings);
|
|
233
|
-
Object.assign(merged.guidelines, data.guidelines);
|
|
234
|
-
|
|
235
|
-
// Use the first non-custom, non-unknown version
|
|
236
|
-
if (
|
|
237
|
-
data.version &&
|
|
238
|
-
data.version !== 'custom' &&
|
|
239
|
-
data.version !== 'unknown' &&
|
|
240
|
-
(merged.version === 'unknown' || merged.version === 'custom')
|
|
241
|
-
) {
|
|
242
|
-
merged.version = data.version;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
return merged;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import { mkdir, writeFile, readdir, cp, rm } from 'fs/promises';
|
|
2
|
-
import { join } from 'path';
|
|
3
|
-
import { homedir } from 'os';
|
|
4
|
-
import chalk from 'chalk';
|
|
5
|
-
import ora from 'ora';
|
|
6
|
-
import { CONFIG, GITHUB_RELEASES_URL } from '../config.js';
|
|
7
|
-
import { ConfigManager } from './config-manager.js';
|
|
8
|
-
|
|
9
|
-
const DOWNLOAD_TIMEOUT_MS = 30000; // 30 seconds
|
|
10
|
-
const MAX_TARBALL_SIZE_BYTES = 10 * 1024 * 1024; // 10MB max
|
|
11
|
-
|
|
12
|
-
export async function ensureDataInitialized(): Promise<void> {
|
|
13
|
-
const configManager = new ConfigManager();
|
|
14
|
-
await configManager.load();
|
|
15
|
-
|
|
16
|
-
if (configManager.isInitialized()) {
|
|
17
|
-
return; // Already initialized
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
console.log(chalk.cyan('\nš First-time setup...\n'));
|
|
21
|
-
|
|
22
|
-
const spinner = ora('Downloading latest guidelines from GitHub...').start();
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
await downloadFromGitHub();
|
|
26
|
-
spinner.succeed('Downloaded latest guidelines from GitHub');
|
|
27
|
-
await configManager.markInitialized('latest');
|
|
28
|
-
console.log(chalk.green('ā Using latest guidelines from GitHub\n'));
|
|
29
|
-
} catch {
|
|
30
|
-
spinner.info('Could not reach GitHub, using bundled guidelines');
|
|
31
|
-
await configManager.markInitialized('embedded');
|
|
32
|
-
console.log(chalk.green('ā Using bundled guidelines\n'));
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
console.log(chalk.gray(' Tip: Run `aicgen update` anytime to sync with latest guidelines\n'));
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async function downloadFromGitHub(): Promise<void> {
|
|
39
|
-
const controller = new AbortController();
|
|
40
|
-
const timeout = setTimeout(() => controller.abort(), DOWNLOAD_TIMEOUT_MS);
|
|
41
|
-
|
|
42
|
-
try {
|
|
43
|
-
const response = await fetch(GITHUB_RELEASES_URL, {
|
|
44
|
-
headers: {
|
|
45
|
-
'Accept': 'application/vnd.github+json',
|
|
46
|
-
'User-Agent': CONFIG.USER_AGENT
|
|
47
|
-
},
|
|
48
|
-
signal: controller.signal
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
if (!response.ok) {
|
|
52
|
-
throw new Error(`GitHub API returned ${response.status}`);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const data = await response.json() as { tag_name: string; tarball_url: string };
|
|
56
|
-
const version = data.tag_name.replace(/^v/, '');
|
|
57
|
-
|
|
58
|
-
const cacheDir = join(homedir(), CONFIG.CACHE_DIR_NAME, CONFIG.CACHE_DIR);
|
|
59
|
-
await mkdir(cacheDir, { recursive: true });
|
|
60
|
-
|
|
61
|
-
// Download tarball with timeout and size limit
|
|
62
|
-
const tarballController = new AbortController();
|
|
63
|
-
const tarballTimeout = setTimeout(() => tarballController.abort(), DOWNLOAD_TIMEOUT_MS);
|
|
64
|
-
|
|
65
|
-
try {
|
|
66
|
-
const tarballResponse = await fetch(data.tarball_url, {
|
|
67
|
-
signal: tarballController.signal
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
if (!tarballResponse.ok) {
|
|
71
|
-
throw new Error(`Failed to download tarball: ${tarballResponse.status}`);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Check content-length if available
|
|
75
|
-
const contentLength = tarballResponse.headers.get('content-length');
|
|
76
|
-
if (contentLength && parseInt(contentLength, 10) > MAX_TARBALL_SIZE_BYTES) {
|
|
77
|
-
throw new Error(`Tarball too large: ${contentLength} bytes (max ${MAX_TARBALL_SIZE_BYTES})`);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const tarballBuffer = Buffer.from(await tarballResponse.arrayBuffer());
|
|
81
|
-
|
|
82
|
-
// Verify size after download
|
|
83
|
-
if (tarballBuffer.length > MAX_TARBALL_SIZE_BYTES) {
|
|
84
|
-
throw new Error(`Downloaded tarball too large: ${tarballBuffer.length} bytes`);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const tempDir = join(cacheDir, '.temp-extract');
|
|
88
|
-
await mkdir(tempDir, { recursive: true });
|
|
89
|
-
|
|
90
|
-
try {
|
|
91
|
-
// Write tarball to temp file
|
|
92
|
-
const tarballPath = join(tempDir, 'archive.tar.gz');
|
|
93
|
-
await writeFile(tarballPath, tarballBuffer);
|
|
94
|
-
|
|
95
|
-
// Extract tarball using decompress (Windows-compatible)
|
|
96
|
-
const decompress = (await import('decompress')).default;
|
|
97
|
-
await decompress(tarballPath, tempDir);
|
|
98
|
-
|
|
99
|
-
// Find extracted directory - derive from CONFIG instead of hardcoding
|
|
100
|
-
const entries = await readdir(tempDir);
|
|
101
|
-
const expectedPrefix = `${CONFIG.GITHUB_REPO_OWNER}-${CONFIG.GITHUB_REPO_NAME}-`;
|
|
102
|
-
const rootDir = entries.find(entry => entry.startsWith(expectedPrefix));
|
|
103
|
-
|
|
104
|
-
if (!rootDir) {
|
|
105
|
-
throw new Error(`Could not find extracted repository directory (expected ${expectedPrefix}*)`);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const extractedPath = join(tempDir, rootDir);
|
|
109
|
-
|
|
110
|
-
// Copy contents to cache directory with proper structure
|
|
111
|
-
const guidelinesTarget = join(cacheDir, 'guidelines');
|
|
112
|
-
await mkdir(guidelinesTarget, { recursive: true });
|
|
113
|
-
|
|
114
|
-
// Copy extracted contents with correct structure
|
|
115
|
-
const extractedEntries = await readdir(extractedPath, { withFileTypes: true });
|
|
116
|
-
for (const entry of extractedEntries) {
|
|
117
|
-
const sourcePath = join(extractedPath, entry.name);
|
|
118
|
-
const targetPath = join(guidelinesTarget, entry.name);
|
|
119
|
-
|
|
120
|
-
if (entry.name === 'guideline-mappings.yml') {
|
|
121
|
-
await cp(sourcePath, join(cacheDir, entry.name));
|
|
122
|
-
} else if (entry.isDirectory() || entry.name.endsWith('.md')) {
|
|
123
|
-
await cp(sourcePath, targetPath, { recursive: true });
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Write version file
|
|
128
|
-
await writeFile(
|
|
129
|
-
join(cacheDir, 'version.json'),
|
|
130
|
-
JSON.stringify({ version, updatedAt: new Date().toISOString(), source: 'github' }),
|
|
131
|
-
'utf-8'
|
|
132
|
-
);
|
|
133
|
-
} finally {
|
|
134
|
-
await rm(tempDir, { recursive: true, force: true });
|
|
135
|
-
}
|
|
136
|
-
} finally {
|
|
137
|
-
clearTimeout(tarballTimeout);
|
|
138
|
-
}
|
|
139
|
-
} finally {
|
|
140
|
-
clearTimeout(timeout);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export async function shouldRunFirstTimeSetup(): Promise<boolean> {
|
|
145
|
-
const configManager = new ConfigManager();
|
|
146
|
-
await configManager.load();
|
|
147
|
-
return !configManager.isInitialized();
|
|
148
|
-
}
|