@kody-ade/kody-engine-lite 0.1.101 → 0.1.102

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -154,7 +154,7 @@ kody-engine-lite rerun --issue-number 42 --from verify
154
154
 
155
155
  ## Documentation
156
156
 
157
- **Understand Kody:** [About](docs/ABOUT.md) · [Features](docs/FEATURES.md) · [Pipeline](docs/PIPELINE.md) · [Comparison](docs/COMPARISON.md)
157
+ **Understand Kody:** [About](docs/ABOUT.md) · [Tech Stack](docs/TECH-STACK.md) · [Features](docs/FEATURES.md) · [Pipeline](docs/PIPELINE.md) · [Comparison](docs/COMPARISON.md)
158
158
 
159
159
  **Set up & use:** [CLI](docs/CLI.md) · [Configuration](docs/CONFIGURATION.md) · [Bootstrap](docs/BOOTSTRAP.md) · [LiteLLM](docs/LITELLM.md)
160
160
 
package/dist/bin/cli.js CHANGED
@@ -1546,6 +1546,11 @@ ${prompt}` : prompt;
1546
1546
  }
1547
1547
  if (isMcpEnabledForStage(stageName, config.mcp) && taskHasUI(taskDir)) {
1548
1548
  assembled = assembled + "\n\n" + getBrowserToolGuidance(stageName, taskDir);
1549
+ const qaGuidePath = path5.join(projectDir, ".kody", "qa-guide.md");
1550
+ if (fs5.existsSync(qaGuidePath)) {
1551
+ const qaGuide = fs5.readFileSync(qaGuidePath, "utf-8").trim();
1552
+ assembled = assembled + "\n\n" + qaGuide;
1553
+ }
1549
1554
  }
1550
1555
  return assembled;
1551
1556
  }
@@ -4532,12 +4537,7 @@ function detectMcpConfig(cwd, pm, pkg) {
4532
4537
  const defaultPort = isNext ? 3e3 : isVite ? 5173 : 3e3;
4533
4538
  const mcp = {
4534
4539
  enabled: true,
4535
- servers: {
4536
- playwright: {
4537
- command: "npx",
4538
- args: ["@playwright/mcp@latest"]
4539
- }
4540
- },
4540
+ servers: {},
4541
4541
  stages: ["build", "review"]
4542
4542
  };
4543
4543
  if (hasDevScript) {
@@ -5034,6 +5034,23 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
5034
5034
  }
5035
5035
  }
5036
5036
  console.log(` \u2713 Generated ${stepCount} step files in .kody/steps/`);
5037
+ console.log("\n\u2500\u2500 QA Guide \u2500\u2500");
5038
+ const qaGuidePath = path21.join(cwd, ".kody", "qa-guide.md");
5039
+ if (!fs22.existsSync(qaGuidePath) || opts.force) {
5040
+ const discovery = discoverQaContext(cwd);
5041
+ if (discovery.routes.length > 0) {
5042
+ const qaGuide = generateQaGuide(discovery);
5043
+ fs22.writeFileSync(qaGuidePath, qaGuide);
5044
+ console.log(` \u2713 .kody/qa-guide.md (${discovery.routes.length} routes, ${discovery.roles.length} roles)`);
5045
+ if (discovery.loginPage) console.log(` \u2713 Login page detected: ${discovery.loginPage}`);
5046
+ if (discovery.adminPath) console.log(` \u2713 Admin panel detected: ${discovery.adminPath}`);
5047
+ console.log(" \u2139 Add QA_ADMIN_EMAIL, QA_ADMIN_PASSWORD, QA_USER_EMAIL, QA_USER_PASSWORD as GitHub secrets");
5048
+ } else {
5049
+ console.log(" \u25CB No routes detected \u2014 skipping QA guide");
5050
+ }
5051
+ } else {
5052
+ console.log(" \u25CB .kody/qa-guide.md already exists (use --force to regenerate)");
5053
+ }
5037
5054
  console.log("\n\u2500\u2500 Labels \u2500\u2500");
5038
5055
  try {
5039
5056
  let repoSlug = "";
@@ -5111,6 +5128,7 @@ REMINDER: Output the full prompt template first (unchanged), then your three app
5111
5128
  const filesToCommit = [
5112
5129
  ".kody/memory/architecture.md",
5113
5130
  ".kody/memory/conventions.md",
5131
+ ".kody/qa-guide.md",
5114
5132
  ...installedSkillPaths
5115
5133
  ].filter((f) => fs22.existsSync(path21.join(cwd, f)));
5116
5134
  if (fs22.existsSync(path21.join(cwd, "skills-lock.json"))) {
@@ -5224,6 +5242,178 @@ Create it manually.`, cwd);
5224
5242
  console.log(" \u2713 Project bootstrap complete!");
5225
5243
  console.log(" Kody now has project-specific memory and customized step files.\n");
5226
5244
  }
5245
+ function discoverQaContext(cwd) {
5246
+ const result = {
5247
+ routes: [],
5248
+ authFiles: [],
5249
+ loginPage: null,
5250
+ adminPath: null,
5251
+ roles: [],
5252
+ devCommand: "",
5253
+ devPort: 3e3
5254
+ };
5255
+ try {
5256
+ const pkg = JSON.parse(fs22.readFileSync(path21.join(cwd, "package.json"), "utf-8"));
5257
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
5258
+ const pm = fs22.existsSync(path21.join(cwd, "pnpm-lock.yaml")) ? "pnpm" : fs22.existsSync(path21.join(cwd, "yarn.lock")) ? "yarn" : "npm";
5259
+ if (pkg.scripts?.dev) result.devCommand = `${pm} dev`;
5260
+ if (allDeps.next || allDeps.nuxt) result.devPort = 3e3;
5261
+ else if (allDeps.vite) result.devPort = 5173;
5262
+ } catch {
5263
+ }
5264
+ const appDirs = ["src/app", "app"];
5265
+ for (const appDir of appDirs) {
5266
+ const fullAppDir = path21.join(cwd, appDir);
5267
+ if (!fs22.existsSync(fullAppDir)) continue;
5268
+ scanRoutes(fullAppDir, appDir, "", result);
5269
+ break;
5270
+ }
5271
+ const authPatterns = ["middleware.ts", "middleware.js", "src/middleware.ts", "src/middleware.js"];
5272
+ for (const p of authPatterns) {
5273
+ if (fs22.existsSync(path21.join(cwd, p))) result.authFiles.push(p);
5274
+ }
5275
+ const authConfigGlobs = [
5276
+ "src/app/api/auth",
5277
+ "src/auth",
5278
+ "src/lib/auth",
5279
+ "auth.config.ts",
5280
+ "auth.ts",
5281
+ "src/app/api/oauth"
5282
+ ];
5283
+ for (const g of authConfigGlobs) {
5284
+ if (fs22.existsSync(path21.join(cwd, g))) result.authFiles.push(g);
5285
+ }
5286
+ try {
5287
+ const rolePaths = [
5288
+ "src/types",
5289
+ "src/lib",
5290
+ "src/utils",
5291
+ "src/constants",
5292
+ "src/access",
5293
+ "src/collections"
5294
+ ];
5295
+ for (const rp of rolePaths) {
5296
+ const dir = path21.join(cwd, rp);
5297
+ if (!fs22.existsSync(dir)) continue;
5298
+ const files = fs22.readdirSync(dir).filter((f) => f.endsWith(".ts") || f.endsWith(".tsx"));
5299
+ for (const f of files) {
5300
+ try {
5301
+ const content = fs22.readFileSync(path21.join(dir, f), "utf-8").slice(0, 5e3);
5302
+ const roleMatches = content.match(/(?:role|Role|ROLE)\s*[=:]\s*['"](\w+)['"]/g);
5303
+ if (roleMatches) {
5304
+ for (const m of roleMatches) {
5305
+ const val = m.match(/['"](\w+)['"]/);
5306
+ if (val && !result.roles.includes(val[1])) result.roles.push(val[1]);
5307
+ }
5308
+ }
5309
+ const enumMatch = content.match(/(?:enum|type)\s+\w*[Rr]ole\w*\s*[={]([^}]+)/s);
5310
+ if (enumMatch) {
5311
+ const vals = enumMatch[1].match(/['"](\w+)['"]/g);
5312
+ if (vals) {
5313
+ for (const v of vals) {
5314
+ const clean = v.replace(/['"]/g, "");
5315
+ if (!result.roles.includes(clean)) result.roles.push(clean);
5316
+ }
5317
+ }
5318
+ }
5319
+ } catch {
5320
+ }
5321
+ }
5322
+ }
5323
+ } catch {
5324
+ }
5325
+ return result;
5326
+ }
5327
+ function scanRoutes(dir, baseDir, prefix, result) {
5328
+ let entries;
5329
+ try {
5330
+ entries = fs22.readdirSync(dir, { withFileTypes: true });
5331
+ } catch {
5332
+ return;
5333
+ }
5334
+ const hasPage = entries.some((e) => e.isFile() && /^page\.(tsx?|jsx?)$/.test(e.name));
5335
+ if (hasPage) {
5336
+ const routePath = prefix || "/";
5337
+ const group = prefix.startsWith("/admin") ? "admin" : prefix.includes("/login") ? "auth" : prefix.includes("/signup") ? "auth" : prefix.includes("/api") ? "api" : "frontend";
5338
+ result.routes.push({ path: routePath, group });
5339
+ if (prefix.includes("/login")) result.loginPage = routePath;
5340
+ if (prefix.startsWith("/admin") && !result.adminPath) result.adminPath = prefix;
5341
+ }
5342
+ for (const entry of entries) {
5343
+ if (!entry.isDirectory()) continue;
5344
+ if (entry.name === "node_modules" || entry.name === ".next") continue;
5345
+ let segment = entry.name;
5346
+ if (segment.startsWith("(") && segment.endsWith(")")) {
5347
+ scanRoutes(path21.join(dir, entry.name), baseDir, prefix, result);
5348
+ continue;
5349
+ }
5350
+ if (segment.startsWith("[") && segment.endsWith("]")) {
5351
+ segment = `:${segment.slice(1, -1)}`;
5352
+ }
5353
+ if (segment.startsWith("[[") && segment.endsWith("]]")) {
5354
+ segment = `:${segment.slice(2, -2)}?`;
5355
+ }
5356
+ scanRoutes(path21.join(dir, entry.name), baseDir, `${prefix}/${segment}`, result);
5357
+ }
5358
+ }
5359
+ function generateQaGuide(discovery) {
5360
+ const lines = ["# QA Guide", "", "## Authentication", ""];
5361
+ if (discovery.loginPage) {
5362
+ lines.push(`- Login page: \`${discovery.loginPage}\``);
5363
+ }
5364
+ lines.push(
5365
+ "",
5366
+ "### Test Accounts",
5367
+ "<!-- Fill in your test/preview environment credentials below -->",
5368
+ "| Role | Email | Password |",
5369
+ "|------|-------|----------|",
5370
+ "| Admin | admin@example.com | CHANGE_ME |",
5371
+ "| User | user@example.com | CHANGE_ME |",
5372
+ "",
5373
+ "### Login Steps",
5374
+ `1. Navigate to \`${discovery.loginPage ?? "/login"}\``,
5375
+ "2. Enter credentials from the test accounts table above",
5376
+ "3. Submit the login form",
5377
+ "4. Verify redirect to dashboard or home page"
5378
+ );
5379
+ if (discovery.authFiles.length > 0) {
5380
+ lines.push("", "### Auth Files");
5381
+ for (const f of discovery.authFiles) {
5382
+ lines.push(`- \`${f}\``);
5383
+ }
5384
+ }
5385
+ if (discovery.roles.length > 0) {
5386
+ lines.push("", "## Roles", "");
5387
+ for (const role of discovery.roles) {
5388
+ lines.push(`- \`${role}\``);
5389
+ }
5390
+ }
5391
+ lines.push("", "## Key Pages", "");
5392
+ const groups = {};
5393
+ for (const route of discovery.routes) {
5394
+ if (!groups[route.group]) groups[route.group] = [];
5395
+ groups[route.group].push(route.path);
5396
+ }
5397
+ for (const [group, routes] of Object.entries(groups)) {
5398
+ lines.push(`### ${group.charAt(0).toUpperCase() + group.slice(1)}`);
5399
+ const sorted = routes.sort();
5400
+ for (const r of sorted.slice(0, 20)) {
5401
+ lines.push(`- \`${r}\``);
5402
+ }
5403
+ if (sorted.length > 20) {
5404
+ lines.push(`- ... and ${sorted.length - 20} more`);
5405
+ }
5406
+ lines.push("");
5407
+ }
5408
+ lines.push(
5409
+ "## Dev Server",
5410
+ "",
5411
+ `- Command: \`${discovery.devCommand || "pnpm dev"}\``,
5412
+ `- URL: \`http://localhost:${discovery.devPort}\``,
5413
+ ""
5414
+ );
5415
+ return lines.join("\n");
5416
+ }
5227
5417
  function detectArchitectureBasic(cwd) {
5228
5418
  const detected = [];
5229
5419
  const pkgPath = path21.join(cwd, "package.json");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kody-ade/kody-engine-lite",
3
- "version": "0.1.101",
3
+ "version": "0.1.102",
4
4
  "description": "Autonomous SDLC pipeline: Kody orchestration + Claude Code + LiteLLM",
5
5
  "license": "MIT",
6
6
  "type": "module",