refactor: rombak arsitektur file ke modular clean-arch (vertikal per-feature)
Organisasi ulang layer-first (domain/application/infrastructure) menjadi vertikal per-feature, behavior tidak berubah: - src/shared/domain/ -> fondasi bersama (ex-domain/core): message, provider, tool_call, tool_result, store, error, usage, conversation - src/features/agent/ -> LLM turn loop: domain (TurnEvent, AgentTurnParams) + application (turn_service, ports) + infrastructure (llm LlmClient + tools) - src/features/cms/ -> settings/conversation/memory persistence: domain + application (services) + infrastructure (persistence repos) - src/features/subagent/ -> orchestration: domain (AccessTier) + infrastructure (engine runner, context, provider, delegate, spawn_tools) - src/features/workflow/ -> multi-phase + hive-mind: domain + infrastructure - src/interfaces/ -> cli + tui (outer ring) tetap Subagent-runner (engine.ts runAgent, division.ts toolsFor) dipindah ke features/subagent (bukan agent tools) utk memutus coupling agent<->subagent. Alias tsconfig: @zesdex/domain -> shared/domain, + @zesdex/agent, @zesdex/agent-infra, @zesdex/cms, @zesdex/subagent, @zesdex/workflow, @zesdex/shared. Bersihkan dead: application/ports PasswordService/TokenService/AuthService. compose.ts (composition root) & bootstrap repoint ke barrel feature. Gates: tsc --noEmit 0 error, bun test 70 pass/0 fail, bun build 147 modules, ./dist/zesdex --headless -> real turn OK.
This commit is contained in:
1 parent
7e6ed34f0d
commit
b5ebd07fa8
126 files changed
+157
-166
No files matched your search
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Best-practice engine — embedded skills, Conventional Commits validation,
|
||||
* and lightweight architecture/quality scans. Mirrors `crate::best_practice`.
|
||||
*/
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
|
||||
interface Skill {
|
||||
name: string;
|
||||
description: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
const SKILLS: Skill[] = [
|
||||
{
|
||||
name: "clean-code",
|
||||
description: "Clean Code principles and naming conventions",
|
||||
content:
|
||||
"Clean Code:\n- Use meaningful, intention-revealing names.\n- Functions should do one thing and be short.\n- Avoid duplication (DRY).\n- Comments should explain 'why', not 'what'.\n- Favor composition over deep inheritance.",
|
||||
},
|
||||
{
|
||||
name: "commit-convention",
|
||||
description: "Conventional Commits (Bahasa Indonesia) rules",
|
||||
content:
|
||||
"Conventional Commits:\n- Format: <type>(<scope>): <subject>\n- Types: feat, fix, chore, docs, refactor, test, style, perf, ci, build, revert\n- Subject: imperative, lowercase, no trailing period.\n- Example: 'feat(auth): tambah login PKCE'",
|
||||
},
|
||||
{
|
||||
name: "layering",
|
||||
description: "Clean Architecture layering rules (domain → application → infrastructure → interfaces)",
|
||||
content:
|
||||
"Clean Architecture:\n- domain: pure types, zero I/O.\n- application: ports + use cases.\n- infrastructure: concrete adapters.\n- interfaces: presentation.\n- Dependencies point inward; domain depends on nothing.",
|
||||
},
|
||||
];
|
||||
|
||||
const COMMIT_TYPES = ["feat", "fix", "chore", "docs", "refactor", "test", "style", "perf", "ci", "build", "revert"];
|
||||
|
||||
export class BestPracticeEngine {
|
||||
listSkills(): string[] {
|
||||
return SKILLS.map((s) => s.name);
|
||||
}
|
||||
skillSummaries(): Array<[string, string]> {
|
||||
return SKILLS.map((s) => [s.name, s.description] as [string, string]);
|
||||
}
|
||||
getSkill(name: string): string | undefined {
|
||||
return SKILLS.find((s) => s.name === name)?.content;
|
||||
}
|
||||
|
||||
parseCommit(msg: string): { type_: string; scope: string | null; breaking: boolean; description: string } | null {
|
||||
const m = /^([a-z]+)(?:\(([^)]+)\))?(!)?:\s*(.+)$/.exec(msg.trim().split("\n")[0] ?? "");
|
||||
if (!m) return null;
|
||||
return { type_: m[1]!, scope: m[2] ?? null, breaking: m[3] === "!", description: (m[4] ?? "").trim() };
|
||||
}
|
||||
|
||||
validateCommit(msg: string): string[] {
|
||||
const errors: string[] = [];
|
||||
const first = (msg.trim().split("\n")[0] ?? "").trim();
|
||||
if (first === "") { errors.push("Commit message is empty."); return errors; }
|
||||
const parsed = this.parseCommit(first);
|
||||
if (!parsed) {
|
||||
errors.push("Does not match Conventional Commits: <type>(<scope>): <subject>");
|
||||
return errors;
|
||||
}
|
||||
if (!COMMIT_TYPES.includes(parsed.type_)) {
|
||||
errors.push(`Unknown type '${parsed.type_}'. Valid types: ${COMMIT_TYPES.join(", ")}`);
|
||||
}
|
||||
if (parsed.description === "") {
|
||||
errors.push("Subject is empty.");
|
||||
} else if (!/^[a-z]/.test(parsed.description)) {
|
||||
errors.push("Subject should start with a lowercase letter.");
|
||||
}
|
||||
if (parsed.description.endsWith(".")) {
|
||||
errors.push("Subject should not end with a period.");
|
||||
}
|
||||
if (first.length > 72) {
|
||||
errors.push("Subject is longer than 72 characters.");
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
suggestCommitTemplate(type_: string, scope: string | null): string {
|
||||
if (!COMMIT_TYPES.includes(type_)) type_ = "feat";
|
||||
return `${type_}${scope ? `(${scope})` : ""}: <imperative description>`;
|
||||
}
|
||||
|
||||
auditLayering(ws: string): { filesScanned: number; violations: Array<{ severity: string; file: string; line: number; message: string }> } {
|
||||
const violations: Array<{ severity: string; file: string; line: number; message: string }> = [];
|
||||
let filesScanned = 0;
|
||||
const scan = (dir: string) => {
|
||||
let entries: fs.Dirent[];
|
||||
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
||||
for (const e of entries) {
|
||||
if (e.name === ".git" || e.name === "node_modules" || e.name === "target") continue;
|
||||
const full = path.join(dir, e.name);
|
||||
if (e.isDirectory()) scan(full);
|
||||
else if (e.name.endsWith(".ts") || e.name.endsWith(".tsx")) {
|
||||
filesScanned++;
|
||||
const lines = fs.readFileSync(full, "utf8").split("\n");
|
||||
lines.forEach((line, i) => {
|
||||
if (line.includes("@zesdex/infrastructure") && full.includes("/domain/")) {
|
||||
violations.push({ severity: "error", file: full, line: i + 1, message: "domain must not depend on infrastructure" });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
scan(ws);
|
||||
return { filesScanned, violations };
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user