feature-architect-agent 1.0.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.
Files changed (49) hide show
  1. package/README.md +704 -0
  2. package/bin/feature-architect.js +2 -0
  3. package/dist/cli/index.d.ts +3 -0
  4. package/dist/cli/index.d.ts.map +1 -0
  5. package/dist/cli/index.js +63 -0
  6. package/dist/commands/init.d.ts +2 -0
  7. package/dist/commands/init.d.ts.map +1 -0
  8. package/dist/commands/init.js +125 -0
  9. package/dist/commands/plan.d.ts +6 -0
  10. package/dist/commands/plan.d.ts.map +1 -0
  11. package/dist/commands/plan.js +147 -0
  12. package/dist/commands/verify.d.ts +2 -0
  13. package/dist/commands/verify.d.ts.map +1 -0
  14. package/dist/commands/verify.js +101 -0
  15. package/dist/config/api.config.d.ts +49 -0
  16. package/dist/config/api.config.d.ts.map +1 -0
  17. package/dist/config/api.config.js +78 -0
  18. package/dist/llm/Claude.d.ts +8 -0
  19. package/dist/llm/Claude.d.ts.map +1 -0
  20. package/dist/llm/Claude.js +44 -0
  21. package/dist/llm/OpenAI.d.ts +8 -0
  22. package/dist/llm/OpenAI.d.ts.map +1 -0
  23. package/dist/llm/OpenAI.js +43 -0
  24. package/dist/llm/factory.d.ts +9 -0
  25. package/dist/llm/factory.d.ts.map +1 -0
  26. package/dist/llm/factory.js +36 -0
  27. package/dist/llm/types.d.ts +8 -0
  28. package/dist/llm/types.d.ts.map +1 -0
  29. package/dist/llm/types.js +2 -0
  30. package/dist/services/Analyzer.d.ts +18 -0
  31. package/dist/services/Analyzer.d.ts.map +1 -0
  32. package/dist/services/Analyzer.js +235 -0
  33. package/dist/services/ContextManager.d.ts +16 -0
  34. package/dist/services/ContextManager.d.ts.map +1 -0
  35. package/dist/services/ContextManager.js +82 -0
  36. package/dist/services/Planner.d.ts +16 -0
  37. package/dist/services/Planner.d.ts.map +1 -0
  38. package/dist/services/Planner.js +181 -0
  39. package/dist/services/Scanner.d.ts +17 -0
  40. package/dist/services/Scanner.d.ts.map +1 -0
  41. package/dist/services/Scanner.js +75 -0
  42. package/dist/types/index.d.ts +90 -0
  43. package/dist/types/index.d.ts.map +1 -0
  44. package/dist/types/index.js +3 -0
  45. package/dist/utils/logger.d.ts +22 -0
  46. package/dist/utils/logger.d.ts.map +1 -0
  47. package/dist/utils/logger.js +56 -0
  48. package/package.json +48 -0
  49. package/src/config/api.config.ts +79 -0
@@ -0,0 +1,17 @@
1
+ import type { ScanConfig } from '../types/index.js';
2
+ export declare class ScannerService {
3
+ private defaultExclude;
4
+ private defaultInclude;
5
+ scan(config?: Partial<ScanConfig>): Promise<string[]>;
6
+ scanWithStats(config?: Partial<ScanConfig>): Promise<{
7
+ files: string[];
8
+ stats: ScanStats;
9
+ }>;
10
+ private getLanguage;
11
+ }
12
+ export interface ScanStats {
13
+ total: number;
14
+ byLanguage: Record<string, number>;
15
+ byDirectory: Record<string, number>;
16
+ }
17
+ //# sourceMappingURL=Scanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Scanner.d.ts","sourceRoot":"","sources":["../../src/services/Scanner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD,qBAAa,cAAc;IACzB,OAAO,CAAC,cAAc,CAapB;IAEF,OAAO,CAAC,cAAc,CAOpB;IAEI,IAAI,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAiBrD,aAAa,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,SAAS,CAAA;KAAE,CAAC;IAwBjG,OAAO,CAAC,WAAW;CAWpB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACrC"}
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ScannerService = void 0;
7
+ const path_1 = __importDefault(require("path"));
8
+ const glob_1 = require("glob");
9
+ class ScannerService {
10
+ defaultExclude = [
11
+ 'node_modules/**',
12
+ '.git/**',
13
+ 'dist/**',
14
+ 'build/**',
15
+ 'coverage/**',
16
+ '.next/**',
17
+ 'out/**',
18
+ '**/*.test.ts',
19
+ '**/*.test.js',
20
+ '**/*.spec.ts',
21
+ '**/*.spec.js',
22
+ '**/.*'
23
+ ];
24
+ defaultInclude = [
25
+ '**/*.ts',
26
+ '**/*.tsx',
27
+ '**/*.js',
28
+ '**/*.jsx',
29
+ '**/*.sql',
30
+ '**/*.prisma'
31
+ ];
32
+ async scan(config) {
33
+ const root = config?.root || process.cwd();
34
+ const include = config?.include || this.defaultInclude;
35
+ const exclude = config?.exclude || this.defaultExclude;
36
+ const patterns = include.map(p => `!(${exclude.map(e => e.replace(/\*\*/g, '')).join('|')})/${p}`);
37
+ const files = await (0, glob_1.glob)(include, {
38
+ cwd: root,
39
+ ignore: exclude,
40
+ absolute: true,
41
+ nodir: true
42
+ });
43
+ return files.sort();
44
+ }
45
+ async scanWithStats(config) {
46
+ const files = await this.scan(config);
47
+ const stats = {
48
+ total: files.length,
49
+ byLanguage: {},
50
+ byDirectory: {}
51
+ };
52
+ for (const file of files) {
53
+ const ext = path_1.default.extname(file);
54
+ const lang = this.getLanguage(ext);
55
+ stats.byLanguage[lang] = (stats.byLanguage[lang] || 0) + 1;
56
+ const dir = path_1.default.relative(process.cwd(), path_1.default.dirname(file));
57
+ const dirParts = dir.split(path_1.default.sep);
58
+ const topLevel = dirParts[0] || 'root';
59
+ stats.byDirectory[topLevel] = (stats.byDirectory[topLevel] || 0) + 1;
60
+ }
61
+ return { files, stats };
62
+ }
63
+ getLanguage(ext) {
64
+ const map = {
65
+ '.ts': 'TypeScript',
66
+ '.tsx': 'TypeScript React',
67
+ '.js': 'JavaScript',
68
+ '.jsx': 'JavaScript React',
69
+ '.sql': 'SQL',
70
+ '.prisma': 'Prisma'
71
+ };
72
+ return map[ext] || 'Other';
73
+ }
74
+ }
75
+ exports.ScannerService = ScannerService;
@@ -0,0 +1,90 @@
1
+ export interface ScanConfig {
2
+ root: string;
3
+ include: string[];
4
+ exclude: string[];
5
+ }
6
+ export interface CodebasePatterns {
7
+ apiRoutes: APIRoute[];
8
+ databaseSchemas: DatabaseSchema[];
9
+ components: Component[];
10
+ types: TypeDefinition[];
11
+ utilities: Utility[];
12
+ }
13
+ export interface APIRoute {
14
+ file: string;
15
+ line: number;
16
+ method: string;
17
+ path: string;
18
+ handler: string;
19
+ }
20
+ export interface DatabaseSchema {
21
+ file: string;
22
+ line: number;
23
+ type: 'table' | 'view' | 'migration';
24
+ name: string;
25
+ definition?: string;
26
+ }
27
+ export interface Component {
28
+ file: string;
29
+ line: number;
30
+ name: string;
31
+ type: string;
32
+ props?: string;
33
+ }
34
+ export interface TypeDefinition {
35
+ file: string;
36
+ line: number;
37
+ name: string;
38
+ kind: 'interface' | 'type' | 'enum';
39
+ }
40
+ export interface Utility {
41
+ file: string;
42
+ line: number;
43
+ name: string;
44
+ exportType: 'named' | 'default';
45
+ }
46
+ export interface CodebaseContext {
47
+ project: {
48
+ name: string;
49
+ type: string;
50
+ root: string;
51
+ };
52
+ summary: {
53
+ totalFiles: number;
54
+ languages: Record<string, number>;
55
+ frameworks: string[];
56
+ patterns: number;
57
+ };
58
+ frameworks: {
59
+ backend?: string;
60
+ frontend?: string;
61
+ database?: string;
62
+ orm?: string;
63
+ };
64
+ patterns: CodebasePatterns;
65
+ metadata: {
66
+ version: string;
67
+ analyzedAt: string;
68
+ hash: string;
69
+ };
70
+ }
71
+ export interface FeaturePlan {
72
+ id: string;
73
+ feature: string;
74
+ slug: string;
75
+ createdAt: string;
76
+ markdown: string;
77
+ }
78
+ export interface AIConfig {
79
+ provider: 'claude' | 'openai' | 'gemini' | 'opencode' | 'ollama';
80
+ model?: string;
81
+ apiKey?: string;
82
+ endpoint?: string;
83
+ temperature?: number;
84
+ maxTokens?: number;
85
+ }
86
+ export interface GenerateOptions {
87
+ temperature?: number;
88
+ maxTokens?: number;
89
+ }
90
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ // Core type definitions for Feature Architect Agent
3
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,22 @@
1
+ export declare const colors: {
2
+ reset: string;
3
+ red: string;
4
+ green: string;
5
+ yellow: string;
6
+ blue: string;
7
+ magenta: string;
8
+ cyan: string;
9
+ gray: string;
10
+ bright: string;
11
+ };
12
+ export declare function log(message: string): void;
13
+ export declare function info(message: string): void;
14
+ export declare function success(message: string): void;
15
+ export declare function warn(message: string): void;
16
+ export declare function error(message: string): void;
17
+ export declare function dim(message: string): void;
18
+ export declare function header(message: string): void;
19
+ export declare function bullet(message: string, indent?: number): void;
20
+ export declare function check(message: string): void;
21
+ export declare function cross(message: string): void;
22
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,MAAM;;;;;;;;;;CAUlB,CAAC;AAEF,wBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAEzC;AAED,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE1C;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE7C;AAED,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE1C;AAED,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE3C;AAED,wBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAEzC;AAED,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE5C;AAED,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,GAAE,MAAU,GAAG,IAAI,CAGhE;AAED,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE3C;AAED,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE3C"}
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ // Simple logger utilities (chalk replacement for minimal dependencies)
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.colors = void 0;
5
+ exports.log = log;
6
+ exports.info = info;
7
+ exports.success = success;
8
+ exports.warn = warn;
9
+ exports.error = error;
10
+ exports.dim = dim;
11
+ exports.header = header;
12
+ exports.bullet = bullet;
13
+ exports.check = check;
14
+ exports.cross = cross;
15
+ exports.colors = {
16
+ reset: '\x1b[0m',
17
+ red: '\x1b[31m',
18
+ green: '\x1b[32m',
19
+ yellow: '\x1b[33m',
20
+ blue: '\x1b[34m',
21
+ magenta: '\x1b[35m',
22
+ cyan: '\x1b[36m',
23
+ gray: '\x1b[90m',
24
+ bright: '\x1b[1m'
25
+ };
26
+ function log(message) {
27
+ console.log(message);
28
+ }
29
+ function info(message) {
30
+ console.log(`${exports.colors.blue}${message}${exports.colors.reset}`);
31
+ }
32
+ function success(message) {
33
+ console.log(`${exports.colors.green}${message}${exports.colors.reset}`);
34
+ }
35
+ function warn(message) {
36
+ console.log(`${exports.colors.yellow}${message}${exports.colors.reset}`);
37
+ }
38
+ function error(message) {
39
+ console.error(`${exports.colors.red}${message}${exports.colors.reset}`);
40
+ }
41
+ function dim(message) {
42
+ console.log(`${exports.colors.gray}${message}${exports.colors.reset}`);
43
+ }
44
+ function header(message) {
45
+ console.log(`\n${exports.colors.bright}${exports.colors.cyan}${message}${exports.colors.reset}\n`);
46
+ }
47
+ function bullet(message, indent = 0) {
48
+ const prefix = ' '.repeat(indent);
49
+ console.log(`${prefix}${exports.colors.cyan}•${exports.colors.reset} ${message}`);
50
+ }
51
+ function check(message) {
52
+ console.log(`${exports.colors.green}✓${exports.colors.reset} ${message}`);
53
+ }
54
+ function cross(message) {
55
+ console.log(`${exports.colors.red}✗${exports.colors.reset} ${message}`);
56
+ }
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "feature-architect-agent",
3
+ "version": "1.0.0",
4
+ "description": "AI-powered feature planning agent - generates complete technical specifications",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "feature-architect": "./bin/feature-architect.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "bin",
12
+ "src/config"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "dev": "tsc --watch",
17
+ "start": "node bin/feature-architect.js",
18
+ "plan": "node bin/feature-architect.js plan",
19
+ "prepublishOnly": "npm run build",
20
+ "prepack": "npm run build"
21
+ },
22
+ "keywords": [
23
+ "cli",
24
+ "ai",
25
+ "feature-planning",
26
+ "architecture",
27
+ "documentation",
28
+ "codebase-analysis"
29
+ ],
30
+ "author": "sahilshaikh-cyber",
31
+ "license": "MIT",
32
+ "dependencies": {
33
+ "commander": "^12.1.0",
34
+ "chalk": "^5.4.1",
35
+ "ora": "^8.1.1",
36
+ "inquirer": "^9.3.7",
37
+ "glob": "^11.0.0",
38
+ "dotenv": "^16.4.7"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^22.10.5",
42
+ "@types/inquirer": "^9.0.7",
43
+ "typescript": "^5.7.3"
44
+ },
45
+ "engines": {
46
+ "node": ">=18.0.0"
47
+ }
48
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Built-in API Configuration for Internal Team Use
3
+ *
4
+ * This file contains the pre-configured API key for team use.
5
+ * The package is published to a private registry for internal team access.
6
+ *
7
+ * SECURITY: This file is included in the published package.
8
+ * Only publish to PRIVATE npm registries (GitHub Packages, npm private, etc.)
9
+ */
10
+
11
+ export const API_CONFIG = {
12
+ /**
13
+ * Built-in OpenAI API Key for team use
14
+ * Replace with your actual team API key
15
+ */
16
+ openai: {
17
+ apiKey: process.env.TEAM_OPENAI_API_KEY || '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',
18
+ baseURL: 'https://api.openai.com/v1',
19
+ },
20
+
21
+ /**
22
+ * Built-in Anthropic API Key (optional)
23
+ */
24
+ anthropic: {
25
+ apiKey: process.env.TEAM_ANTHROPIC_API_KEY || '',
26
+ baseURL: 'https://api.anthropic.com',
27
+ },
28
+
29
+ /**
30
+ * Built-in Google API Key (optional)
31
+ */
32
+ google: {
33
+ apiKey: process.env.TEAM_GOOGLE_API_KEY || '',
34
+ },
35
+
36
+ /**
37
+ * Default provider to use when no user key is set
38
+ */
39
+ defaultProvider: 'openai' as const,
40
+ };
41
+
42
+ /**
43
+ * Get the built-in API key for a provider
44
+ */
45
+ export function getBuiltinKey(provider: 'openai' | 'anthropic' | 'google'): string | undefined {
46
+ switch (provider) {
47
+ case 'openai':
48
+ return API_CONFIG.openai.apiKey || undefined;
49
+ case 'anthropic':
50
+ return API_CONFIG.anthropic.apiKey || undefined;
51
+ case 'google':
52
+ return API_CONFIG.google.apiKey || undefined;
53
+ default:
54
+ return undefined;
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Check if a built-in key is available
60
+ */
61
+ export function hasBuiltinKey(provider: 'openai' | 'anthropic' | 'google'): boolean {
62
+ const key = getBuiltinKey(provider);
63
+ return !!key && key !== '' && !key.startsWith('sk-');
64
+ }
65
+
66
+ /**
67
+ * Get the best available API key (user env var first, then built-in)
68
+ */
69
+ export function getApiKey(provider: 'openai' | 'anthropic' | 'google'): string | undefined {
70
+ // First check user's environment variable
71
+ const userKey = process.env[`${provider.toUpperCase()}_API_KEY`];
72
+ if (userKey) return userKey;
73
+
74
+ // Then check generic AI_API_KEY
75
+ if (process.env.AI_API_KEY) return process.env.AI_API_KEY;
76
+
77
+ // Finally fall back to built-in key
78
+ return getBuiltinKey(provider);
79
+ }