imhcode 1.1.1 → 2.0.1

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 (34) hide show
  1. package/README.md +18 -0
  2. package/USER_MANUAL.md +29 -0
  3. package/bin/imhcode.js +839 -0
  4. package/dist/orchestrator/builder.d.ts.map +1 -1
  5. package/dist/orchestrator/builder.js +27 -4
  6. package/dist/orchestrator/builder.js.map +1 -1
  7. package/dist/orchestrator/context-scanner.d.ts +23 -0
  8. package/dist/orchestrator/context-scanner.d.ts.map +1 -0
  9. package/dist/orchestrator/context-scanner.js +248 -0
  10. package/dist/orchestrator/context-scanner.js.map +1 -0
  11. package/dist/orchestrator/import-engine.d.ts +18 -0
  12. package/dist/orchestrator/import-engine.d.ts.map +1 -0
  13. package/dist/orchestrator/import-engine.js +116 -0
  14. package/dist/orchestrator/import-engine.js.map +1 -0
  15. package/dist/orchestrator/index.d.ts +4 -0
  16. package/dist/orchestrator/index.d.ts.map +1 -1
  17. package/dist/orchestrator/index.js +13 -1
  18. package/dist/orchestrator/index.js.map +1 -1
  19. package/dist/orchestrator/modification-engine.d.ts +12 -0
  20. package/dist/orchestrator/modification-engine.d.ts.map +1 -0
  21. package/dist/orchestrator/modification-engine.js +109 -0
  22. package/dist/orchestrator/modification-engine.js.map +1 -0
  23. package/dist/orchestrator/project-scanner.d.ts +16 -0
  24. package/dist/orchestrator/project-scanner.d.ts.map +1 -0
  25. package/dist/orchestrator/project-scanner.js +164 -0
  26. package/dist/orchestrator/project-scanner.js.map +1 -0
  27. package/package.json +1 -1
  28. package/skills/typeui-main/test/orchestrator.test.ts +88 -41
  29. package/src/orchestrator/builder.ts +29 -4
  30. package/src/orchestrator/context-scanner.ts +221 -0
  31. package/src/orchestrator/import-engine.ts +106 -0
  32. package/src/orchestrator/index.ts +8 -0
  33. package/src/orchestrator/modification-engine.ts +108 -0
  34. package/src/orchestrator/project-scanner.ts +134 -0
@@ -0,0 +1,12 @@
1
+ export interface ModificationOptions {
2
+ agent?: string;
3
+ engine?: string;
4
+ model?: string;
5
+ dryRun?: boolean;
6
+ scope?: string;
7
+ }
8
+ /**
9
+ * Execute a targeted project modification.
10
+ */
11
+ export declare function runModification(cwd: string, description: string, options?: ModificationOptions): Promise<any>;
12
+ //# sourceMappingURL=modification-engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modification-engine.d.ts","sourceRoot":"","sources":["../../src/orchestrator/modification-engine.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,GAAG,CAAC,CAsDd"}
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.runModification = runModification;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const context_scanner_1 = require("./context-scanner");
40
+ const registry_1 = require("./registry");
41
+ const index_1 = require("./index");
42
+ /**
43
+ * Execute a targeted project modification.
44
+ */
45
+ async function runModification(cwd, description, options = {}) {
46
+ // 1. Scan context
47
+ const context = (0, context_scanner_1.scanProjectContext)(cwd);
48
+ // 2. Select agent
49
+ const selectedAgentId = options.agent || (0, context_scanner_1.detectBestAgent)(description, context);
50
+ console.log(`\nšŸ› ļø [IMH-Code] Initializing Modification Engine`);
51
+ console.log(` Task: "${description}"`);
52
+ console.log(` Agent: ${selectedAgentId}`);
53
+ // Resolve agent manifests directory
54
+ const globalAgents = path.join(process.env.HOME || "", ".imhcode", "agents");
55
+ const localAgents = path.join(cwd, "agents");
56
+ const pkgAgents = path.join(__dirname, "..", "..", "agents");
57
+ const agentsDir = fs.existsSync(globalAgents) ? globalAgents :
58
+ fs.existsSync(localAgents) ? localAgents :
59
+ pkgAgents;
60
+ // 3. Load Registry
61
+ const { agents, errors } = await (0, registry_1.loadRegistry)(agentsDir, cwd);
62
+ if (errors.some(e => e.agentId === selectedAgentId)) {
63
+ throw new Error(`Failed to load agent "${selectedAgentId}": ${errors.find(e => e.agentId === selectedAgentId)?.error}`);
64
+ }
65
+ const agent = (0, registry_1.getAgent)(agents, selectedAgentId);
66
+ // 4. Build custom modification task
67
+ const modificationTask = (0, context_scanner_1.buildModificationPrompt)(context, description);
68
+ // 5. Run the agent
69
+ const result = await (0, index_1.runAgent)(agent, modificationTask, {
70
+ dryRun: options.dryRun !== false,
71
+ engine: options.engine,
72
+ model: options.model,
73
+ outputDir: path.join(cwd, ".imhcode", "sessions"),
74
+ cwd
75
+ });
76
+ // 6. Log changes if live and successful
77
+ if (!result.dryRun && result.errors.length === 0) {
78
+ logModification(cwd, {
79
+ task: description,
80
+ agentId: selectedAgentId,
81
+ model: result.model,
82
+ durationMs: result.durationMs
83
+ });
84
+ }
85
+ return result;
86
+ }
87
+ function logModification(cwd, entry) {
88
+ const docsDir = path.join(cwd, "docs");
89
+ if (!fs.existsSync(docsDir)) {
90
+ fs.mkdirSync(docsDir, { recursive: true });
91
+ }
92
+ const logPath = path.join(docsDir, "modifications.md");
93
+ const timestamp = new Date().toISOString().replace("T", " ").substring(0, 19);
94
+ let header = `# šŸ› ļø IMH-Code Project Modifications\n\n| Date | Task | Agent | Model | Duration |\n|------|------|-------|-------|----------|\n`;
95
+ if (fs.existsSync(logPath)) {
96
+ try {
97
+ const existing = fs.readFileSync(logPath, "utf-8");
98
+ if (existing.trim().length > 10) {
99
+ header = existing;
100
+ }
101
+ }
102
+ catch {
103
+ // Ignore
104
+ }
105
+ }
106
+ const row = `| ${timestamp} | ${entry.task} | \`${entry.agentId}\` | ${entry.model} | ${(entry.durationMs / 1000).toFixed(1)}s |\n`;
107
+ fs.writeFileSync(logPath, header.endsWith("\n") ? header + row : header + "\n" + row, "utf-8");
108
+ }
109
+ //# sourceMappingURL=modification-engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"modification-engine.js","sourceRoot":"","sources":["../../src/orchestrator/modification-engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ export interface ScanResult {
2
+ detectedFrontend: string | null;
3
+ detectedBackend: string | null;
4
+ frontendPath: string | null;
5
+ backendPath: string | null;
6
+ database: string | null;
7
+ dockerized: boolean;
8
+ hasCICD: boolean;
9
+ packageJson?: any;
10
+ composerJson?: any;
11
+ }
12
+ /**
13
+ * Deep scan an existing codebase to detect stack and structure.
14
+ */
15
+ export declare function scanProject(cwd: string): ScanResult;
16
+ //# sourceMappingURL=project-scanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-scanner.d.ts","sourceRoot":"","sources":["../../src/orchestrator/project-scanner.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,UAAU;IACzB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,GAAG,CAAC;IAClB,YAAY,CAAC,EAAE,GAAG,CAAC;CACpB;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAmHnD"}
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.scanProject = scanProject;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ /**
40
+ * Deep scan an existing codebase to detect stack and structure.
41
+ */
42
+ function scanProject(cwd) {
43
+ const result = {
44
+ detectedFrontend: null,
45
+ detectedBackend: null,
46
+ frontendPath: null,
47
+ backendPath: null,
48
+ database: null,
49
+ dockerized: false,
50
+ hasCICD: false,
51
+ };
52
+ // 1. Helper to search for files recursively with limits
53
+ function findFile(dir, name, depth = 0) {
54
+ if (depth > 3)
55
+ return null;
56
+ try {
57
+ const files = fs.readdirSync(dir);
58
+ if (files.includes(name)) {
59
+ return path.join(dir, name);
60
+ }
61
+ for (const file of files) {
62
+ if (file.startsWith(".") || file === "node_modules" || file === "dist" || file === "vendor")
63
+ continue;
64
+ const fp = path.join(dir, file);
65
+ if (fs.statSync(fp).isDirectory()) {
66
+ const found = findFile(fp, name, depth + 1);
67
+ if (found)
68
+ return found;
69
+ }
70
+ }
71
+ }
72
+ catch {
73
+ // Ignore
74
+ }
75
+ return null;
76
+ }
77
+ // 2. Scan for package.json (Frontend detection)
78
+ const pkgJsonPath = findFile(cwd, "package.json");
79
+ if (pkgJsonPath) {
80
+ try {
81
+ const content = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
82
+ result.packageJson = content;
83
+ const deps = { ...(content.dependencies || {}), ...(content.devDependencies || {}) };
84
+ if (deps.next)
85
+ result.detectedFrontend = "Next.js";
86
+ else if (deps.nuxt || deps.nuxt3 || deps.nuxt4)
87
+ result.detectedFrontend = "Nuxt/Vue";
88
+ else if (deps.react)
89
+ result.detectedFrontend = "React SPA";
90
+ else if (deps.vue)
91
+ result.detectedFrontend = "Vue SPA";
92
+ else if (deps.svelte)
93
+ result.detectedFrontend = "Svelte";
94
+ else
95
+ result.detectedFrontend = "Node/JS";
96
+ // If package.json is in a subdirectory, use that subdirectory
97
+ const relative = path.relative(cwd, path.dirname(pkgJsonPath));
98
+ result.frontendPath = relative === "" ? "." : relative;
99
+ }
100
+ catch {
101
+ // Ignore
102
+ }
103
+ }
104
+ // 3. Scan for composer.json, requirements.txt, or pom.xml (Backend detection)
105
+ const composerPath = findFile(cwd, "composer.json");
106
+ if (composerPath) {
107
+ try {
108
+ const content = JSON.parse(fs.readFileSync(composerPath, "utf-8"));
109
+ result.composerJson = content;
110
+ if (content.require && (content.require["laravel/framework"] || content.require["laravel/lumen-framework"])) {
111
+ result.detectedBackend = "Laravel";
112
+ }
113
+ else {
114
+ result.detectedBackend = "PHP";
115
+ }
116
+ const relative = path.relative(cwd, path.dirname(composerPath));
117
+ result.backendPath = relative === "" ? "." : relative;
118
+ }
119
+ catch {
120
+ // Ignore
121
+ }
122
+ }
123
+ if (!result.detectedBackend) {
124
+ const reqsTxtPath = findFile(cwd, "requirements.txt");
125
+ const pyprojectPath = findFile(cwd, "pyproject.toml");
126
+ if (reqsTxtPath || pyprojectPath) {
127
+ result.detectedBackend = "Python (Django/FastAPI)";
128
+ const matchedPath = reqsTxtPath || pyprojectPath || "";
129
+ const relative = path.relative(cwd, path.dirname(matchedPath));
130
+ result.backendPath = relative === "" ? "." : relative;
131
+ }
132
+ }
133
+ // 4. Database Detection from env or configs
134
+ const envPath = findFile(cwd, ".env") || findFile(cwd, ".env.example");
135
+ if (envPath) {
136
+ try {
137
+ const content = fs.readFileSync(envPath, "utf-8");
138
+ if (content.includes("DB_CONNECTION=pgsql") || content.includes("postgresql://")) {
139
+ result.database = "PostgreSQL";
140
+ }
141
+ else if (content.includes("DB_CONNECTION=mysql") || content.includes("mysql://")) {
142
+ result.database = "MySQL";
143
+ }
144
+ else if (content.includes("DB_CONNECTION=sqlite") || content.includes("sqlite3")) {
145
+ result.database = "SQLite";
146
+ }
147
+ }
148
+ catch {
149
+ // Ignore
150
+ }
151
+ }
152
+ // 5. Infrastructure detection
153
+ const dockerCompose = findFile(cwd, "docker-compose.yml");
154
+ const dockerfile = findFile(cwd, "Dockerfile");
155
+ if (dockerCompose || dockerfile) {
156
+ result.dockerized = true;
157
+ }
158
+ const githubWorkflows = findFile(cwd, ".github/workflows");
159
+ if (githubWorkflows) {
160
+ result.hasCICD = true;
161
+ }
162
+ return result;
163
+ }
164
+ //# sourceMappingURL=project-scanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-scanner.js","sourceRoot":"","sources":["../../src/orchestrator/project-scanner.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imhcode",
3
- "version": "1.1.1",
3
+ "version": "2.0.1",
4
4
  "description": "IMH-Code — Imam Hussain Coding Harness Platform. A fast-first multi-agent AI coding framework with intelligent model routing. 19 generic role-based agents (planner, nextjs-executor, laravel-executor, etc.), configurable testing strategy, and 7 token-saving optimizations for rapid MVP development.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -3,55 +3,108 @@ import * as fs from "fs";
3
3
  import * as path from "path";
4
4
  import { loadMemoryFiles } from "../../../src/orchestrator/loader";
5
5
  import { getRoleKeyForAgent, resolveAgentIdAlias } from "../../../src/orchestrator/index";
6
+ import { scanProjectContext, detectBestAgent } from "../../../src/orchestrator/context-scanner";
7
+ import { scanProject, ScanResult } from "../../../src/orchestrator/project-scanner";
8
+ import { importProject } from "../../../src/orchestrator/import-engine";
6
9
 
7
10
  describe("orchestrator resolveAgentIdAlias", () => {
8
- it("resolves Zara aliases correctly", () => {
9
- expect(resolveAgentIdAlias("zara-seo")).toBe("zara-content");
10
- expect(resolveAgentIdAlias("Zara")).toBe("zara-content");
11
- });
12
-
13
- it("resolves Farhan aliases correctly", () => {
14
- expect(resolveAgentIdAlias("farhan-growth")).toBe("farhan-marketing");
15
- expect(resolveAgentIdAlias("farhan")).toBe("farhan-marketing");
16
- });
17
-
18
- it("resolves other mismatched agent aliases", () => {
19
- expect(resolveAgentIdAlias("taha-slides")).toBe("taha-presentation");
20
- expect(resolveAgentIdAlias("sami-spatial")).toBe("sami-computational");
21
- expect(resolveAgentIdAlias("yahya-phd")).toBe("yahya-researcher");
22
- expect(resolveAgentIdAlias("maryam-business")).toBe("maryam-ops");
23
- expect(resolveAgentIdAlias("zainab-product-manager")).toBe("zainab-pm");
24
- expect(resolveAgentIdAlias("gohar")).toBe("gohar-ceo");
11
+ it("resolves generic agent aliases correctly", () => {
12
+ expect(resolveAgentIdAlias("nextjs")).toBe("nextjs-executor");
13
+ expect(resolveAgentIdAlias("laravel")).toBe("laravel-executor");
14
+ expect(resolveAgentIdAlias("qa")).toBe("tester");
15
+ expect(resolveAgentIdAlias("security")).toBe("security-auditor");
16
+ expect(resolveAgentIdAlias("seo")).toBe("seo-optimizer");
17
+ expect(resolveAgentIdAlias("debug")).toBe("debugger");
25
18
  });
26
19
 
27
20
  it("returns canonical ID unchanged", () => {
28
- expect(resolveAgentIdAlias("karar-frontend")).toBe("karar-frontend");
21
+ expect(resolveAgentIdAlias("laravel-executor")).toBe("laravel-executor");
29
22
  });
30
23
  });
31
24
 
32
25
  describe("orchestrator getRoleKeyForAgent", () => {
33
- it("resolves security roles correctly", () => {
34
- expect(getRoleKeyForAgent("hamid-security", "Audit the authentication code")).toBe("primary_security_reviewer");
35
- expect(getRoleKeyForAgent("hamid-security", "Laravel security fallback audit")).toBe("security_fallback");
36
- });
37
-
38
- it("resolves debugger roles correctly", () => {
39
- expect(getRoleKeyForAgent("sajjad-debugger", "Fix login bug")).toBe("fast_bug_fixing");
26
+ it("resolves generic executor roles correctly", () => {
27
+ expect(getRoleKeyForAgent("nextjs-executor", "Build a dashboard")).toBe("frontend");
28
+ expect(getRoleKeyForAgent("laravel-executor", "Create API route")).toBe("backend");
29
+ expect(getRoleKeyForAgent("planner", "Define architecture")).toBe("planning");
30
+ expect(getRoleKeyForAgent("tester", "Run E2E tests")).toBe("testing");
31
+ expect(getRoleKeyForAgent("debugger", "Fix authentication issue")).toBe("review");
40
32
  });
33
+ });
41
34
 
42
- it("resolves designer roles correctly", () => {
43
- expect(getRoleKeyForAgent("mustafa-visual", "Create hero section")).toBe("primary_design_brain");
44
- expect(getRoleKeyForAgent("mahdi-designer", "Polish the UX layout")).toBe("design_polish_ux_review");
45
- });
35
+ describe("project scanners and imports", () => {
36
+ const tempDir = path.join(process.cwd(), ".tmp-tests", `import-test-${Date.now()}`);
46
37
 
47
- it("resolves frontend roles correctly", () => {
48
- expect(getRoleKeyForAgent("karar-frontend", "Build button component")).toBe("frontend_builder");
49
- expect(getRoleKeyForAgent("karar-frontend", "Verify and review final frontend")).toBe("frontend_final_review");
38
+ it("scans and imports project structures correctly", () => {
39
+ // 1. Create a dummy Next.js + Laravel project layout
40
+ fs.mkdirSync(tempDir, { recursive: true });
41
+ fs.mkdirSync(path.join(tempDir, "frontend"), { recursive: true });
42
+ fs.mkdirSync(path.join(tempDir, "backend"), { recursive: true });
43
+
44
+ // Write package.json
45
+ fs.writeFileSync(
46
+ path.join(tempDir, "frontend", "package.json"),
47
+ JSON.stringify({
48
+ name: "test-frontend",
49
+ dependencies: {
50
+ next: "^15.0.0",
51
+ react: "^19.0.0"
52
+ }
53
+ })
54
+ );
55
+
56
+ // Write composer.json
57
+ fs.writeFileSync(
58
+ path.join(tempDir, "backend", "composer.json"),
59
+ JSON.stringify({
60
+ name: "laravel/laravel",
61
+ require: {
62
+ "laravel/framework": "^11.0"
63
+ }
64
+ })
65
+ );
66
+
67
+ // Write .env with pgsql database config
68
+ fs.writeFileSync(
69
+ path.join(tempDir, ".env"),
70
+ "DB_CONNECTION=pgsql\nDATABASE_URL=postgresql://localhost:5432/test"
71
+ );
72
+
73
+ // 2. Test project scanner
74
+ const scanResult = scanProject(tempDir);
75
+ expect(scanResult.detectedFrontend).toBe("Next.js");
76
+ expect(scanResult.detectedBackend).toBe("Laravel");
77
+ expect(scanResult.frontendPath).toBe("frontend");
78
+ expect(scanResult.backendPath).toBe("backend");
79
+ expect(scanResult.database).toBe("PostgreSQL");
80
+
81
+ // 3. Test context scanner
82
+ const contextScan = scanProjectContext(tempDir);
83
+ expect(contextScan.hasFrontend).toBe(true);
84
+ expect(contextScan.hasBackend).toBe(true);
85
+ expect(contextScan.frontendFramework).toBe("Next.js");
86
+ expect(contextScan.backendFramework).toBe("Laravel");
87
+
88
+ // 4. Test best agent detection
89
+ expect(detectBestAgent("fix a bug in navbar component", contextScan)).toBe("nextjs-executor");
90
+ expect(detectBestAgent("create a database migration for comments table", contextScan)).toBe("laravel-executor");
91
+
92
+ // 5. Test import execution
93
+ const importResult = importProject(tempDir);
94
+ expect(importResult.success).toBe(true);
95
+ expect(importResult.importMap.frontend).toBe("frontend");
96
+ expect(importResult.importMap.backend).toBe("backend");
97
+
98
+ // Check generated files exist
99
+ expect(fs.existsSync(path.join(tempDir, ".imhcode", "import-map.json"))).toBe(true);
100
+ expect(fs.existsSync(path.join(tempDir, "PROJECT_BRIEF.md"))).toBe(true);
50
101
  });
51
102
 
52
- it("resolves backend roles correctly", () => {
53
- expect(getRoleKeyForAgent("tariq-backend", "Write DB migration")).toBe("primary_backend_builder");
54
- expect(getRoleKeyForAgent("tariq-backend", "Refactor multiple files in Laravel")).toBe("backend_multi_file_builder");
103
+ // Clean up
104
+ afterAll(() => {
105
+ if (fs.existsSync(tempDir)) {
106
+ fs.rmSync(tempDir, { recursive: true, force: true });
107
+ }
55
108
  });
56
109
  });
57
110
 
@@ -59,14 +112,12 @@ describe("loadMemoryFiles exclusions and limits", () => {
59
112
  const tempDir = path.join(process.cwd(), ".tmp-tests", `mem-test-${Date.now()}`);
60
113
 
61
114
  it("filters out binary files, lockfiles, excluded directories, and large files", async () => {
62
- // 1. Create temp structures
63
115
  fs.mkdirSync(tempDir, { recursive: true });
64
116
  fs.mkdirSync(path.join(tempDir, "docs"), { recursive: true });
65
117
  fs.mkdirSync(path.join(tempDir, "node_modules"), { recursive: true });
66
118
  fs.mkdirSync(path.join(tempDir, "frontend", ".next"), { recursive: true });
67
119
  fs.mkdirSync(path.join(tempDir, "backend", "build"), { recursive: true });
68
120
 
69
- // 2. Write files
70
121
  fs.writeFileSync(path.join(tempDir, "docs", "test.md"), "hello docs");
71
122
  fs.writeFileSync(path.join(tempDir, "docs", "info.txt"), "hello info");
72
123
  fs.writeFileSync(path.join(tempDir, "node_modules", "bad.js"), "console.log('bad')");
@@ -75,7 +126,6 @@ describe("loadMemoryFiles exclusions and limits", () => {
75
126
  fs.writeFileSync(path.join(tempDir, "frontend", "image.png"), "binary png contents");
76
127
  fs.writeFileSync(path.join(tempDir, "package-lock.json"), "{}");
77
128
 
78
- // Write a file larger than 1MB
79
129
  const largeFilePath = path.join(tempDir, "docs", "large.txt");
80
130
  const largeContent = "a".repeat(1024 * 1024 + 10);
81
131
  fs.writeFileSync(largeFilePath, largeContent);
@@ -86,10 +136,8 @@ describe("loadMemoryFiles exclusions and limits", () => {
86
136
  }
87
137
  };
88
138
 
89
- // 3. Execute loadMemoryFiles
90
139
  const files = await loadMemoryFiles(mockManifest, tempDir);
91
140
 
92
- // 4. Verify results
93
141
  const relativePaths = files.map(f => path.relative(tempDir, f.path).replace(/\\/g, "/"));
94
142
  expect(relativePaths).toContain("docs/test.md");
95
143
  expect(relativePaths).toContain("docs/info.txt");
@@ -104,7 +152,6 @@ describe("loadMemoryFiles exclusions and limits", () => {
104
152
  expect(files.find(f => f.path.endsWith("info.txt"))?.content).toBe("hello info");
105
153
  });
106
154
 
107
- // Clean up
108
155
  afterAll(() => {
109
156
  if (fs.existsSync(tempDir)) {
110
157
  fs.rmSync(tempDir, { recursive: true, force: true });
@@ -222,7 +222,32 @@ export function buildPrompt(
222
222
  }
223
223
 
224
224
  // ── 9. Category-Specific Output Rules ────────────────────────────────────
225
- const categoryRules = CATEGORY_OUTPUT_RULES[category] ?? CATEGORY_OUTPUT_RULES["backend"];
225
+ let frontendDir = "frontend/";
226
+ let backendDir = "backend/";
227
+
228
+ if (cwd) {
229
+ const mapPath = path.join(cwd, ".imhcode", "import-map.json");
230
+ if (fs.existsSync(mapPath)) {
231
+ try {
232
+ const importMap = JSON.parse(fs.readFileSync(mapPath, "utf-8"));
233
+ if (importMap.frontend) {
234
+ frontendDir = importMap.frontend === "." ? "project root (./)" : `${importMap.frontend}/`;
235
+ }
236
+ if (importMap.backend) {
237
+ backendDir = importMap.backend === "." ? "project root (./)" : `${importMap.backend}/`;
238
+ }
239
+ } catch {
240
+ // Ignore
241
+ }
242
+ }
243
+ }
244
+
245
+ let categoryRules = CATEGORY_OUTPUT_RULES[category] ?? CATEGORY_OUTPUT_RULES["backend"];
246
+ // Dynamically replace default folder names with actual mapped ones
247
+ categoryRules = categoryRules
248
+ .replace(/`frontend\/`/g, `\`${frontendDir}\``)
249
+ .replace(/`backend\/`/g, `\`${backendDir}\``);
250
+
226
251
  const outputLines = [
227
252
  `**Category**: ${category}`,
228
253
  `**Format**: ${manifest.output.format}`,
@@ -236,10 +261,10 @@ export function buildPrompt(
236
261
 
237
262
  // ── 10. Codebase Containment Rules ───────────────────────────────────────
238
263
  const containmentRules = [
239
- `- All frontend source files, styles, assets, and components → \`frontend/\` directory ONLY.`,
240
- `- All backend source files, APIs, migrations, schemas, and logic → \`backend/\` directory ONLY.`,
264
+ `- All frontend source files, styles, assets, and components → \`${frontendDir}\` directory ONLY.`,
265
+ `- All backend source files, APIs, migrations, schemas, and logic → \`${backendDir}\` directory ONLY.`,
241
266
  `- All planning and documentation → \`docs/\` directory ONLY.`,
242
- `- NEVER create application code at the workspace root. Keep root clean.`,
267
+ `- NEVER create application code at the workspace root unless the mapped directory is root (./).`,
243
268
  ].join("\n");
244
269
  parts.push(section("Codebase Containment Rules", containmentRules));
245
270