atabey 0.0.14 → 0.0.16

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 (159) hide show
  1. package/LICENSE +672 -21
  2. package/PRIVACY.md +8 -14
  3. package/README.md +183 -25
  4. package/bin/cli.js +72 -5
  5. package/dist/framework-mcp/src/index.js +145 -13
  6. package/dist/framework-mcp/src/index.js.map +1 -1
  7. package/dist/framework-mcp/src/tools/control_plane/registry.d.ts +1 -1
  8. package/dist/framework-mcp/src/tools/control_plane/registry.js +52 -4
  9. package/dist/framework-mcp/src/tools/control_plane/registry.js.map +1 -1
  10. package/dist/framework-mcp/src/tools/definitions.js +1 -1
  11. package/dist/framework-mcp/src/tools/file_system/read_file.js +5 -0
  12. package/dist/framework-mcp/src/tools/file_system/read_file.js.map +1 -1
  13. package/dist/framework-mcp/src/tools/messaging/approve_operation.d.ts +10 -8
  14. package/dist/framework-mcp/src/tools/messaging/approve_operation.js +12 -12
  15. package/dist/framework-mcp/src/tools/messaging/approve_operation.js.map +1 -1
  16. package/dist/framework-mcp/src/tools/messaging/ask_human.d.ts +8 -8
  17. package/dist/framework-mcp/src/tools/messaging/ask_human.js +8 -8
  18. package/dist/framework-mcp/src/tools/messaging/send_message.js +19 -3
  19. package/dist/framework-mcp/src/tools/messaging/send_message.js.map +1 -1
  20. package/dist/framework-mcp/src/tools/observability/check_ports.d.ts +1 -0
  21. package/dist/framework-mcp/src/tools/observability/check_ports.js +21 -7
  22. package/dist/framework-mcp/src/tools/observability/check_ports.js.map +1 -1
  23. package/dist/framework-mcp/src/tools/schemas.d.ts +4 -4
  24. package/dist/framework-mcp/src/tools/search/grep_search.js +11 -4
  25. package/dist/framework-mcp/src/tools/search/grep_search.js.map +1 -1
  26. package/dist/framework-mcp/src/tools/search/list_dir.js +3 -0
  27. package/dist/framework-mcp/src/tools/search/list_dir.js.map +1 -1
  28. package/dist/framework-mcp/src/tools/shell/run_command.js +185 -93
  29. package/dist/framework-mcp/src/tools/shell/run_command.js.map +1 -1
  30. package/dist/framework-mcp/src/utils/auth.d.ts +26 -2
  31. package/dist/framework-mcp/src/utils/auth.js +74 -7
  32. package/dist/framework-mcp/src/utils/auth.js.map +1 -1
  33. package/dist/framework-mcp/src/utils/compliance.js +45 -13
  34. package/dist/framework-mcp/src/utils/compliance.js.map +1 -1
  35. package/dist/framework-mcp/src/utils/discipline.d.ts +1 -0
  36. package/dist/framework-mcp/src/utils/discipline.js +42 -3
  37. package/dist/framework-mcp/src/utils/discipline.js.map +1 -1
  38. package/dist/framework-mcp/src/utils/human-in-loop.d.ts +35 -2
  39. package/dist/framework-mcp/src/utils/human-in-loop.js +114 -10
  40. package/dist/framework-mcp/src/utils/human-in-loop.js.map +1 -1
  41. package/dist/framework-mcp/src/utils/loop-detector.js +3 -3
  42. package/dist/framework-mcp/src/utils/loop-detector.js.map +1 -1
  43. package/dist/framework-mcp/src/utils/permissions.d.ts +67 -3
  44. package/dist/framework-mcp/src/utils/permissions.js +179 -43
  45. package/dist/framework-mcp/src/utils/permissions.js.map +1 -1
  46. package/dist/framework-mcp/src/utils/prompt-injection.d.ts +20 -0
  47. package/dist/framework-mcp/src/utils/prompt-injection.js +64 -0
  48. package/dist/framework-mcp/src/utils/prompt-injection.js.map +1 -0
  49. package/dist/framework-mcp/src/utils/quality.js +4 -3
  50. package/dist/framework-mcp/src/utils/quality.js.map +1 -1
  51. package/dist/framework-mcp/src/utils/silent-router.d.ts +4 -2
  52. package/dist/framework-mcp/src/utils/silent-router.js +35 -9
  53. package/dist/framework-mcp/src/utils/silent-router.js.map +1 -1
  54. package/dist/framework-mcp/tests/tools/file_system/permissions.test.js +174 -71
  55. package/dist/framework-mcp/tests/tools/file_system/permissions.test.js.map +1 -1
  56. package/dist/framework-mcp/tests/tools/messaging/send_message.test.js +36 -30
  57. package/dist/framework-mcp/tests/tools/messaging/send_message.test.js.map +1 -1
  58. package/dist/framework-mcp/tests/tools/observability/check_ports.test.js +26 -12
  59. package/dist/framework-mcp/tests/tools/observability/check_ports.test.js.map +1 -1
  60. package/dist/framework-mcp/tests/tools/shell/run_command.test.js +38 -15
  61. package/dist/framework-mcp/tests/tools/shell/run_command.test.js.map +1 -1
  62. package/dist/framework-mcp/tests/utils/human-in-loop.test.js +152 -4
  63. package/dist/framework-mcp/tests/utils/human-in-loop.test.js.map +1 -1
  64. package/dist/framework-mcp/tests/utils/loop-detector.test.js +0 -2
  65. package/dist/framework-mcp/tests/utils/loop-detector.test.js.map +1 -1
  66. package/dist/framework-mcp/tests/utils/prompt-injection.test.d.ts +1 -0
  67. package/dist/framework-mcp/tests/utils/prompt-injection.test.js +43 -0
  68. package/dist/framework-mcp/tests/utils/prompt-injection.test.js.map +1 -0
  69. package/dist/src/cli/commands/dashboard.js +167 -2
  70. package/dist/src/cli/commands/dashboard.js.map +1 -1
  71. package/dist/src/cli/commands/init/scaffold-standards.js +1 -1
  72. package/dist/src/cli/commands/init/scaffold-standards.js.map +1 -1
  73. package/dist/src/cli/commands/init.js +14 -7
  74. package/dist/src/cli/commands/init.js.map +1 -1
  75. package/dist/src/cli/commands/log.js +8 -5
  76. package/dist/src/cli/commands/log.js.map +1 -1
  77. package/dist/src/cli/commands/mcp.js +3 -1
  78. package/dist/src/cli/commands/mcp.js.map +1 -1
  79. package/dist/src/cli/commands/orchestrate.d.ts +4 -4
  80. package/dist/src/cli/commands/orchestrate.js +14 -2
  81. package/dist/src/cli/commands/orchestrate.js.map +1 -1
  82. package/dist/src/cli/commands/script.js +8 -1
  83. package/dist/src/cli/commands/script.js.map +1 -1
  84. package/dist/src/cli/commands/status.js +21 -1
  85. package/dist/src/cli/commands/status.js.map +1 -1
  86. package/dist/src/cli/utils/memory.js +6 -5
  87. package/dist/src/cli/utils/memory.js.map +1 -1
  88. package/dist/src/cli/utils/schemas.d.ts +2 -2
  89. package/dist/src/contracts/tasks.d.ts +4 -4
  90. package/dist/src/modules/contracts/api.d.ts +6 -6
  91. package/dist/src/modules/engines/agent-executor.js +41 -20
  92. package/dist/src/modules/engines/agent-executor.js.map +1 -1
  93. package/dist/src/modules/engines/agent-loop.d.ts +5 -5
  94. package/dist/src/modules/engines/agent-loop.js +5 -5
  95. package/dist/src/modules/engines/evaluation-engine.d.ts +9 -2
  96. package/dist/src/modules/engines/evaluation-engine.js +77 -9
  97. package/dist/src/modules/engines/evaluation-engine.js.map +1 -1
  98. package/dist/src/modules/engines/quality-gate.js +4 -2
  99. package/dist/src/modules/engines/quality-gate.js.map +1 -1
  100. package/dist/src/modules/engines/risk-engine.d.ts +17 -1
  101. package/dist/src/modules/engines/risk-engine.js +98 -3
  102. package/dist/src/modules/engines/risk-engine.js.map +1 -1
  103. package/dist/src/modules/engines/routing-engine.d.ts +26 -1
  104. package/dist/src/modules/engines/routing-engine.js +97 -1
  105. package/dist/src/modules/engines/routing-engine.js.map +1 -1
  106. package/dist/src/modules/providers/shared.js +2 -3
  107. package/dist/src/modules/providers/shared.js.map +1 -1
  108. package/dist/src/shared/pii.js +1 -1
  109. package/dist/src/shared/pii.js.map +1 -1
  110. package/dist/src/shared/storage.d.ts +60 -0
  111. package/dist/src/shared/storage.js +77 -3
  112. package/dist/src/shared/storage.js.map +1 -1
  113. package/dist/tests/cli/commands/dashboard-coverage.test.js +3 -1
  114. package/dist/tests/cli/commands/dashboard-coverage.test.js.map +1 -1
  115. package/dist/tests/modules/engines/agent-executor.test.js +1 -1
  116. package/dist/tests/modules/engines/agent-executor.test.js.map +1 -1
  117. package/dist/tests/modules/engines/evaluation-engine.test.js +2 -2
  118. package/dist/tests/modules/engines/evaluation-engine.test.js.map +1 -1
  119. package/dist/tests/status-cost.test.js +2 -0
  120. package/dist/tests/status-cost.test.js.map +1 -1
  121. package/dist/tests/storage-operations.test.js +31 -4
  122. package/dist/tests/storage-operations.test.js.map +1 -1
  123. package/dist/vitest.config.js +6 -4
  124. package/dist/vitest.config.js.map +1 -1
  125. package/framework-mcp/README.md +18 -32
  126. package/framework-mcp/dist/dashboard/assets/index-B-Eu7_J9.js +1260 -0
  127. package/framework-mcp/dist/dashboard/index.html +1 -1
  128. package/framework-mcp/dist/framework-mcp/src/index.js +145 -13
  129. package/framework-mcp/dist/framework-mcp/src/tools/control_plane/registry.js +52 -4
  130. package/framework-mcp/dist/framework-mcp/src/tools/definitions.js +1 -1
  131. package/framework-mcp/dist/framework-mcp/src/tools/file_system/read_file.js +5 -0
  132. package/framework-mcp/dist/framework-mcp/src/tools/messaging/approve_operation.js +12 -12
  133. package/framework-mcp/dist/framework-mcp/src/tools/messaging/ask_human.js +8 -8
  134. package/framework-mcp/dist/framework-mcp/src/tools/messaging/send_message.js +19 -3
  135. package/framework-mcp/dist/framework-mcp/src/tools/observability/check_ports.js +21 -7
  136. package/framework-mcp/dist/framework-mcp/src/tools/search/grep_search.js +11 -4
  137. package/framework-mcp/dist/framework-mcp/src/tools/search/list_dir.js +3 -0
  138. package/framework-mcp/dist/framework-mcp/src/tools/shell/run_command.js +185 -93
  139. package/framework-mcp/dist/framework-mcp/src/utils/auth.js +74 -7
  140. package/framework-mcp/dist/framework-mcp/src/utils/compliance.js +45 -13
  141. package/framework-mcp/dist/framework-mcp/src/utils/discipline.js +42 -3
  142. package/framework-mcp/dist/framework-mcp/src/utils/human-in-loop.js +114 -10
  143. package/framework-mcp/dist/framework-mcp/src/utils/loop-detector.js +3 -3
  144. package/framework-mcp/dist/framework-mcp/src/utils/permissions.js +179 -43
  145. package/framework-mcp/dist/framework-mcp/src/utils/prompt-injection.js +63 -0
  146. package/framework-mcp/dist/framework-mcp/src/utils/quality.js +4 -3
  147. package/framework-mcp/dist/framework-mcp/src/utils/silent-router.js +35 -9
  148. package/framework-mcp/dist/src/cli/utils/memory.js +6 -5
  149. package/framework-mcp/dist/src/modules/engines/evaluation-engine.js +170 -0
  150. package/framework-mcp/dist/src/modules/engines/quality-gate.js +4 -2
  151. package/framework-mcp/dist/src/modules/engines/risk-engine.js +98 -3
  152. package/framework-mcp/dist/src/modules/engines/routing-engine.js +97 -1
  153. package/framework-mcp/dist/src/modules/providers/shared.js +2 -3
  154. package/framework-mcp/dist/src/shared/pii.js +1 -1
  155. package/framework-mcp/dist/src/shared/storage.js +77 -3
  156. package/framework-mcp/package.json +21 -18
  157. package/mcp.json +3 -2
  158. package/package.json +4 -5
  159. package/framework-mcp/dist/dashboard/assets/index-B2mYld0c.js +0 -1221
@@ -173,11 +173,12 @@ export function updateDocumentStore(type, data, id, frameworkDir) {
173
173
  break;
174
174
  }
175
175
  case "history": {
176
- const db = Storage.getDB();
177
- db.prepare(`
178
- INSERT INTO logs (agent, action, summary)
179
- VALUES (?, ?, ?)
180
- `).run("@manager", "HISTORY_UPDATE", data.content);
176
+ Storage.saveLog({
177
+ agent: "@manager",
178
+ action: "HISTORY_UPDATE",
179
+ status: "SUCCESS",
180
+ summary: data.content,
181
+ });
181
182
  break;
182
183
  }
183
184
  }
@@ -0,0 +1,170 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { scanProjectCompliance } from "../../cli/utils/compliance.js";
4
+ import { getFrameworkDir } from "../../cli/utils/memory.js";
5
+ import { appendFile, ensureDir, runCommandQuiet, writeTextFile } from "../../shared/fs.js";
6
+ import { logger } from "../../shared/logger.js";
7
+ import { asTraceID } from "../../shared/types.js";
8
+ export class EvaluationEngine {
9
+ /**
10
+ * Evaluates a completed task, calculates a score, and updates Specialty Memory.
11
+ * Saves lessons from both failures AND successes.
12
+ */
13
+ static evaluateTask(traceIdStr, agentName, durationMs, taskDescription) {
14
+ const traceId = asTraceID(traceIdStr);
15
+ logger.debug(`Starting evaluation for Trace ${traceId} by ${agentName}`);
16
+ let score = 100;
17
+ const metrics = {
18
+ compilation: true,
19
+ lint: true,
20
+ tests: true,
21
+ compliance: 0
22
+ };
23
+ const projectRoot = process.cwd();
24
+ const lessons = [];
25
+ // 1. Compliance Check (AST-based discipline check)
26
+ try {
27
+ const issues = scanProjectCompliance("src");
28
+ metrics.compliance = issues.length;
29
+ if (issues.length > 0) {
30
+ score -= Math.min(issues.length * 5, 40);
31
+ lessons.push("Compliance Violations Detected: Ensure strict adherence to zero-any, zero-console.log policies.");
32
+ }
33
+ else {
34
+ lessons.push("Compliance check passed: No any types or console.log violations found.");
35
+ }
36
+ }
37
+ catch (e) {
38
+ logger.debug("Compliance check failed during evaluation", e);
39
+ }
40
+ // 2. Compilation Check
41
+ try {
42
+ if (fs.existsSync(path.join(projectRoot, "tsconfig.json"))) {
43
+ runCommandQuiet("npx", ["tsc", "--noEmit"], projectRoot);
44
+ lessons.push("TypeScript compilation succeeded. Project is type-safe.");
45
+ }
46
+ }
47
+ catch {
48
+ metrics.compilation = false;
49
+ score -= 20;
50
+ lessons.push("Compilation Error: Always run 'npx tsc --noEmit' to verify type safety before completing a task.");
51
+ }
52
+ // 3. Lint Check
53
+ try {
54
+ if (fs.existsSync(path.join(projectRoot, "eslint.config.js"))) {
55
+ runCommandQuiet("npx", ["eslint", ".", "--max-warnings", "0"], projectRoot);
56
+ lessons.push("Lint check passed. Code follows project style guidelines.");
57
+ }
58
+ }
59
+ catch {
60
+ metrics.lint = false;
61
+ score -= 10;
62
+ }
63
+ // 4. Test Check
64
+ try {
65
+ if (fs.existsSync(path.join(projectRoot, "vitest.config.ts"))) {
66
+ runCommandQuiet("npx", ["vitest", "run", "--passWithNoTests"], projectRoot);
67
+ lessons.push("All tests passed. Changes are verified.");
68
+ }
69
+ }
70
+ catch {
71
+ metrics.tests = false;
72
+ score -= 20;
73
+ lessons.push("Test Failure: Ensure all unit tests pass before marking a task as COMPLETED.");
74
+ }
75
+ // 5. Efficiency Check
76
+ if (durationMs > 120000) {
77
+ score -= 10;
78
+ lessons.push("Task took longer than 2 minutes. Consider breaking large tasks into smaller steps.");
79
+ }
80
+ else if (score >= 80) {
81
+ lessons.push("Task completed efficiently within expected timeframe.");
82
+ }
83
+ // 6. Success Lesson — if score is high and no critical errors
84
+ if (score >= 80 && taskDescription) {
85
+ const successLesson = this.extractSuccessLesson(agentName, taskDescription);
86
+ if (successLesson) {
87
+ lessons.push(successLesson);
88
+ }
89
+ }
90
+ // Save all lessons to specialty memory
91
+ for (const lesson of lessons) {
92
+ this.updateSpecialtyMemory(agentName, lesson);
93
+ }
94
+ const result = {
95
+ traceId,
96
+ agent: agentName.replace("@", ""),
97
+ score: Math.max(0, score),
98
+ metrics,
99
+ durationMs,
100
+ timestamp: new Date().toISOString()
101
+ };
102
+ logger.debug(`Evaluation completed for ${agentName}: Score ${result.score}/100`);
103
+ return result;
104
+ }
105
+ static extractSuccessLesson(agentName, taskDescription) {
106
+ const patterns = [
107
+ { keywords: ["api", "endpoint", "route", "rest"], category: "REST API pattern" },
108
+ { keywords: ["auth", "login", "jwt", "token"], category: "Authentication/authorization/JWT pattern" },
109
+ { keywords: ["test", "spec", "vitest"], category: "Test-driven development approach" },
110
+ { keywords: ["migration", "schema", "database", "sql"], category: "Database schema/migration pattern" },
111
+ { keywords: ["component", "react", "ui", "page"], category: "UI component design pattern" },
112
+ { keywords: ["middleware", "guard", "interceptor"], category: "Middleware/guard pipeline pattern" },
113
+ { keywords: ["service", "repository", "inject"], category: "Service/repository architecture pattern" },
114
+ { keywords: ["error", "exception", "catch", "try"], category: "Robust error handling pattern" },
115
+ { keywords: ["config", "env", "environment"], category: "Configuration and environment control pattern" },
116
+ ];
117
+ const lower = taskDescription.toLowerCase();
118
+ let categoryStr = "";
119
+ for (const { keywords, category } of patterns) {
120
+ if (keywords.some(k => lower.includes(k))) {
121
+ categoryStr = ` using ${category}`;
122
+ break;
123
+ }
124
+ }
125
+ const cleanedTask = taskDescription.replace(/\r?\n/g, " ").trim();
126
+ const truncatedTask = cleanedTask.length > 120 ? cleanedTask.substring(0, 117) + "..." : cleanedTask;
127
+ const capitalizedTask = truncatedTask.charAt(0).toUpperCase() + truncatedTask.slice(1);
128
+ return `Successfully completed task: "${capitalizedTask}"${categoryStr}.`;
129
+ }
130
+ /**
131
+ * Reads learned conventions from an agent's specialty memory.
132
+ * Used by silent-router to inject context into AI calls.
133
+ */
134
+ static readLearnedConventions(agentName) {
135
+ const cleanName = agentName.replace("@", "");
136
+ const fwDir = getFrameworkDir();
137
+ const filePath = path.join(fwDir, "memory", "specialties", `${cleanName}.md`);
138
+ try {
139
+ if (fs.existsSync(filePath)) {
140
+ const content = fs.readFileSync(filePath, "utf8").trim();
141
+ if (content)
142
+ return content;
143
+ }
144
+ }
145
+ catch {
146
+ // File might not exist yet
147
+ }
148
+ return "";
149
+ }
150
+ /**
151
+ * Appends learned lessons to the agent's specialty memory.
152
+ */
153
+ static updateSpecialtyMemory(agentName, lesson) {
154
+ const cleanName = agentName.replace("@", "");
155
+ const fwDir = getFrameworkDir();
156
+ const specialtiesDir = path.join(fwDir, "memory", "specialties");
157
+ ensureDir(specialtiesDir);
158
+ const filePath = path.join(specialtiesDir, `${cleanName}.md`);
159
+ const timestamp = new Date().toISOString().split("T")[0];
160
+ const entry = `- **[${timestamp}]**: ${lesson}\n`;
161
+ if (!fs.existsSync(filePath)) {
162
+ writeTextFile(filePath, `# Learned Conventions for @${cleanName}\n\n`);
163
+ }
164
+ // Avoid duplicate lessons on the same day
165
+ const currentContent = fs.readFileSync(filePath, "utf8");
166
+ if (!currentContent.includes(lesson)) {
167
+ appendFile(filePath, entry);
168
+ }
169
+ }
170
+ }
@@ -73,10 +73,12 @@ export class QualityGate {
73
73
  if (!output || output.trim().length < 10) {
74
74
  return { passed: false, reason: "Output is too short or empty." };
75
75
  }
76
- // Check for hard error indicators
76
+ // Check for hard error indicators (word-boundary match to avoid false positives
77
+ // on valid identifiers such as 'ErrorBoundary', 'handleError', 'TIMEOUT_MS',
78
+ // 'FAILED_VALIDATION', etc.).
77
79
  const errorIndicators = ["ERROR", "FAILED", "CRASHED", "TIMEOUT"];
78
80
  for (const indicator of errorIndicators) {
79
- if (output.toUpperCase().includes(indicator)) {
81
+ if (new RegExp(`\\b${indicator}\\b`).test(output.toUpperCase())) {
80
82
  return { passed: false, reason: `Output contains error indicator: ${indicator}` };
81
83
  }
82
84
  }
@@ -1,6 +1,14 @@
1
1
  /**
2
2
  * [SECURITY] Risk Engine (The Guardian)
3
3
  * Calculates the danger level of a proposed task or operation.
4
+ *
5
+ * Scoring model (v2 — contextual behavioral analysis):
6
+ * - Keyword signals: intent words in natural-language task descriptions
7
+ * - Path signals: sensitivity of file paths / env patterns
8
+ * - Behavioral signals: blast-radius proxies — file count, line deletions, bulk patterns
9
+ * - Complexity signal: task description length as misunderstanding proxy
10
+ *
11
+ * requiresApproval threshold: totalScore >= 60
4
12
  */
5
13
  export class RiskEngine {
6
14
  static HIGH_RISK_KEYWORDS = [
@@ -18,10 +26,18 @@ export class RiskEngine {
18
26
  { pattern: /database/i, weight: 30 },
19
27
  { pattern: /auth/i, weight: 30 },
20
28
  { pattern: /security/i, weight: 30 },
21
- { pattern: /atabey/i, weight: 40 }, // Framework protection
29
+ { pattern: /atabey/i, weight: 40 }, // Framework self-protection
30
+ ];
31
+ // ─── Behavioral Signal Patterns ──────────────────────────────────────────
32
+ // Glob / wildcard patterns in paths that indicate bulk/mass-scope operations.
33
+ static BULK_SCOPE_PATTERNS = [
34
+ /\*\*/, // recursive glob
35
+ /\*\.[a-z]{2,4}/, // wildcard extension (*.ts, *.js)
36
+ /\/\*$/, // directory wildcard
22
37
  ];
23
38
  /**
24
39
  * Assesses the risk of a natural language task or command string.
40
+ * Combines keyword analysis, path sensitivity, and complexity signals.
25
41
  */
26
42
  static assessTaskRisk(task) {
27
43
  const factors = [];
@@ -48,7 +64,31 @@ export class RiskEngine {
48
64
  totalScore += weight;
49
65
  }
50
66
  }
51
- // 3. Complexity Risk (Length of task as a proxy)
67
+ // 3. Behavioral Bulk scope detection in task text
68
+ for (const pattern of this.BULK_SCOPE_PATTERNS) {
69
+ if (pattern.test(task)) {
70
+ const score = 25;
71
+ factors.push({
72
+ factor: "Bulk Scope Pattern",
73
+ score,
74
+ description: `Task references a wildcard/glob pattern (${pattern.source}), indicating a potentially mass-scope operation.`
75
+ });
76
+ totalScore += score;
77
+ break; // count once even if multiple glob patterns match
78
+ }
79
+ }
80
+ // 4. Behavioral — Line deletion volume proxy
81
+ // Detect phrases like "delete all", "remove all", "wipe", "clear all"
82
+ if (/\b(delete|remove|wipe|clear)\s+all\b/i.test(task)) {
83
+ const score = 30;
84
+ factors.push({
85
+ factor: "Mass Deletion Intent",
86
+ score,
87
+ description: "Task uses mass-deletion language ('delete all', 'remove all', 'wipe', 'clear all') suggesting bulk data destruction."
88
+ });
89
+ totalScore += score;
90
+ }
91
+ // 5. Complexity Risk (Length as misunderstanding proxy)
52
92
  if (task.length > 300) {
53
93
  const score = 10;
54
94
  factors.push({
@@ -62,8 +102,9 @@ export class RiskEngine {
62
102
  }
63
103
  /**
64
104
  * Assesses risk based on a proposed file change.
105
+ * Adds behavioral signals: file count impact and bulk-scope path patterns.
65
106
  */
66
- static assessChangeRisk(filePath, operation) {
107
+ static assessChangeRisk(filePath, operation, options) {
67
108
  const factors = [];
68
109
  let totalScore = 0;
69
110
  // 1. Operation Weight
@@ -85,6 +126,60 @@ export class RiskEngine {
85
126
  totalScore += weight;
86
127
  }
87
128
  }
129
+ // 3. Behavioral — Bulk scope path (glob/wildcard in filePath)
130
+ for (const pattern of this.BULK_SCOPE_PATTERNS) {
131
+ if (pattern.test(filePath)) {
132
+ const score = 25;
133
+ factors.push({
134
+ factor: "Bulk Scope Path",
135
+ score,
136
+ description: `File path '${filePath}' contains a wildcard/glob pattern, suggesting a mass-scope write operation.`
137
+ });
138
+ totalScore += score;
139
+ break;
140
+ }
141
+ }
142
+ // 4. Behavioral — File count impact
143
+ // Writing to many files at once multiplies blast radius.
144
+ const { affectedFileCount = 1, deletedLineCount = 0 } = options ?? {};
145
+ if (affectedFileCount > 10) {
146
+ const score = Math.min(affectedFileCount * 2, 40);
147
+ factors.push({
148
+ factor: `High File Count: ${affectedFileCount} files`,
149
+ score,
150
+ description: `Operation touches ${affectedFileCount} files — high blast radius.`
151
+ });
152
+ totalScore += score;
153
+ }
154
+ else if (affectedFileCount > 3) {
155
+ const score = 15;
156
+ factors.push({
157
+ factor: `Multi-file: ${affectedFileCount} files`,
158
+ score,
159
+ description: `Operation touches ${affectedFileCount} files — moderate blast radius.`
160
+ });
161
+ totalScore += score;
162
+ }
163
+ // 5. Behavioral — Line deletion volume
164
+ // Bulk line deletions (>100 lines) indicate destructive operations.
165
+ if (deletedLineCount > 500) {
166
+ const score = 35;
167
+ factors.push({
168
+ factor: `Mass Line Deletion: ${deletedLineCount} lines`,
169
+ score,
170
+ description: `Operation deletes ${deletedLineCount} lines — very high data destruction risk.`
171
+ });
172
+ totalScore += score;
173
+ }
174
+ else if (deletedLineCount > 100) {
175
+ const score = 20;
176
+ factors.push({
177
+ factor: `Large Line Deletion: ${deletedLineCount} lines`,
178
+ score,
179
+ description: `Operation deletes ${deletedLineCount} lines — elevated data destruction risk.`
180
+ });
181
+ totalScore += score;
182
+ }
88
183
  return this.finalizeAssessment(totalScore, factors);
89
184
  }
90
185
  static finalizeAssessment(totalScore, factors) {
@@ -5,6 +5,7 @@ import { logger } from "../../shared/logger.js";
5
5
  import { AtabeyStorage } from "../../shared/storage.js";
6
6
  import { asAgentID, asTraceID } from "../../shared/types.js";
7
7
  import { ALL_AGENTS } from "../agents/definitions.js";
8
+ import { CoreMemory } from "../memory/core.js";
8
9
  export class RoutingEngine {
9
10
  /**
10
11
  * Advanced Routing Engine — analyzes task descriptions,
@@ -12,6 +13,10 @@ export class RoutingEngine {
12
13
  *
13
14
  * Uses real TF-IDF scoring: term frequency in the task weighted by
14
15
  * inverse document frequency across the full agent candidate corpus.
16
+ *
17
+ * Semantic routing via `resolveWithEmbeddings()` blends TF-IDF scores
18
+ * with cosine similarity over local TF-IDF embeddings (or OpenAI embeddings
19
+ * when OPENAI_API_KEY is set). Use the async variant for higher accuracy.
15
20
  */
16
21
  static resolveAgent(taskDescription) {
17
22
  return this.resolveWithDetails(taskDescription).agent;
@@ -85,9 +90,100 @@ export class RoutingEngine {
85
90
  subTasks: bestSubTasks,
86
91
  };
87
92
  }
93
+ // ─── Semantic Routing (Embedding-based) ─────────────────────────────────
94
+ /**
95
+ * Semantic routing via embedding cosine similarity.
96
+ *
97
+ * Blends TF-IDF score (60%) with embedding cosine similarity (40%).
98
+ * Falls back gracefully to pure TF-IDF when embeddings are unavailable.
99
+ *
100
+ * When OPENAI_API_KEY is set, uses text-embedding-3-small (1536-dim).
101
+ * Otherwise falls back to the local TF-IDF vectorizer (384-dim) from
102
+ * CoreMemory.generateEmbedding(), which is already used by the memory system.
103
+ *
104
+ * @param taskDescription - Natural language task to route
105
+ * @returns Routing result with semantic score blend
106
+ */
107
+ static async resolveWithEmbeddings(taskDescription) {
108
+ // Start TF-IDF routing in parallel with embedding generation
109
+ const tfidfResult = this.resolveWithDetails(taskDescription);
110
+ try {
111
+ const taskVector = CoreMemory.generateEmbedding(taskDescription);
112
+ const candidates = this.getCandidates();
113
+ let bestAgent = tfidfResult.agent;
114
+ let bestBlendedScore = -Infinity;
115
+ let bestReasoning = tfidfResult.reasoning;
116
+ for (const candidate of candidates) {
117
+ // Build a combined specialty text for this agent to embed
118
+ const specialtyText = Object.keys(candidate.specialties).join(" ") +
119
+ " " + candidate.role + " " + candidate.displayName;
120
+ const agentVector = CoreMemory.generateEmbedding(specialtyText);
121
+ const similarity = this.cosineSimilarity(taskVector, agentVector);
122
+ // Retrieve TF-IDF score for this specific candidate
123
+ const candidateTfIdfScore = this.scoreSingleCandidate(taskDescription, candidate, candidates);
124
+ // Blend: 60% TF-IDF score (normalized 0-1) + 40% cosine similarity
125
+ const normalizedTfIdf = Math.min(candidateTfIdfScore / 30, 1);
126
+ const blendedScore = normalizedTfIdf * 0.6 + similarity * 0.4;
127
+ if (blendedScore > bestBlendedScore) {
128
+ bestBlendedScore = blendedScore;
129
+ bestAgent = candidate.agent;
130
+ bestReasoning = `Semantic blend (TF-IDF: ${(normalizedTfIdf * 100).toFixed(0)}%, cosine: ${(similarity * 100).toFixed(0)}%) → ${candidate.role}`;
131
+ }
132
+ }
133
+ const confidence = bestBlendedScore > 0.6 ? "high"
134
+ : bestBlendedScore > 0.35 ? "medium"
135
+ : "low";
136
+ logger.debug(`[ROUTING] Semantic routing selected ${bestAgent} (blend score: ${bestBlendedScore.toFixed(3)})`);
137
+ return {
138
+ agent: bestAgent,
139
+ score: Math.round(bestBlendedScore * 100) / 100,
140
+ confidence,
141
+ reasoning: bestReasoning,
142
+ subTasks: this.generateSubTasks(bestAgent, taskDescription),
143
+ };
144
+ }
145
+ catch (err) {
146
+ // Graceful fallback to TF-IDF if embedding fails
147
+ logger.warn(`[ROUTING] Embedding-based routing failed, falling back to TF-IDF: ${err.message}`);
148
+ return tfidfResult;
149
+ }
150
+ }
151
+ /**
152
+ * Scores a single candidate against a task using TF-IDF.
153
+ * Extracted from resolveWithDetails to support semantic blend calculation.
154
+ */
155
+ static scoreSingleCandidate(taskDescription, candidate, allCandidates) {
156
+ const textLower = taskDescription.toLowerCase();
157
+ const tokens = this.tokenize(textLower);
158
+ let score = 0;
159
+ for (const [specialty, weight] of Object.entries(candidate.specialties)) {
160
+ const specTokens = this.tokenize(specialty.toLowerCase());
161
+ for (const token of tokens) {
162
+ if (specTokens.some(st => token.includes(st) || st.includes(token))) {
163
+ score += weight * this.calculateTfIdf(token, tokens, allCandidates);
164
+ }
165
+ }
166
+ }
167
+ score += (candidate.capability - 5) * 0.5;
168
+ return score;
169
+ }
88
170
  /**
89
- * Automatically splits the task into subtasks
171
+ * Cosine similarity between two numeric vectors.
172
+ * Returns a value in [0, 1] where 1 = identical direction.
90
173
  */
174
+ static cosineSimilarity(a, b) {
175
+ const len = Math.min(a.length, b.length);
176
+ let dot = 0;
177
+ let normA = 0;
178
+ let normB = 0;
179
+ for (let i = 0; i < len; i++) {
180
+ dot += a[i] * b[i];
181
+ normA += a[i] * a[i];
182
+ normB += b[i] * b[i];
183
+ }
184
+ const denom = Math.sqrt(normA) * Math.sqrt(normB);
185
+ return denom === 0 ? 0 : Math.max(0, dot / denom);
186
+ }
91
187
  static planTask(taskDescription) {
92
188
  const result = this.resolveWithDetails(taskDescription);
93
189
  const plan = [
@@ -1,8 +1,7 @@
1
1
  import path from "path";
2
2
  import fs from "fs";
3
3
  import os from "os";
4
- import { execSync } from "child_process";
5
- import { writeJsonFile, writeTextFile } from "../../shared/fs.js";
4
+ import { runCommandQuiet, writeJsonFile, writeTextFile } from "../../shared/fs.js";
6
5
  import { ALL_AGENTS, toAntigravityJson } from "../agents/definitions.js";
7
6
  import { CORE_SKILLS } from "../skills/definitions.js";
8
7
  import { getPackageRoot } from "../../cli/utils/pkg.js";
@@ -90,7 +89,7 @@ ${ag.instructions.rules.map(r => `- ${r}`).join("\n")}
90
89
  writeJsonFile(path.join(globalPluginDir, "hooks.json"), {});
91
90
  logger.info(`Antigravity Plugin registered → ${globalPluginDir}/`);
92
91
  try {
93
- execSync(`agy plugin install "${globalPluginDir}"`, { stdio: "ignore" });
92
+ runCommandQuiet("agy", ["plugin", "install", globalPluginDir], process.cwd());
94
93
  logger.info("Antigravity Plugin installed in CLI.");
95
94
  }
96
95
  catch {
@@ -150,7 +150,7 @@ export function containsPIIInObject(obj, depth = 0) {
150
150
  if (Array.isArray(obj)) {
151
151
  return obj.some(item => containsPIIInObject(item, depth + 1));
152
152
  }
153
- for (const [_key, value] of Object.entries(obj)) {
153
+ for (const [, value] of Object.entries(obj)) {
154
154
  if (typeof value === "string") {
155
155
  if (containsPII(value))
156
156
  return true;
@@ -1,6 +1,7 @@
1
1
  import Database from "better-sqlite3";
2
2
  import fs from "fs";
3
3
  import path from "path";
4
+ import crypto from "crypto";
4
5
  import { getFrameworkDir } from "../cli/utils/memory.js";
5
6
  import { logger } from "./logger.js";
6
7
  import { maskText } from "./pii.js";
@@ -18,7 +19,7 @@ export class AtabeyStorage {
18
19
  fs.mkdirSync(frameworkDir, { recursive: true });
19
20
  }
20
21
  const dbPath = path.join(frameworkDir, "atabey.db");
21
- this.db = new Database(dbPath);
22
+ this.db = new Database(dbPath, { timeout: 5000 });
22
23
  this.db.pragma("journal_mode = WAL");
23
24
  this.initializeSchema();
24
25
  }
@@ -74,9 +75,25 @@ export class AtabeyStorage {
74
75
  trace_id TEXT,
75
76
  status TEXT,
76
77
  summary TEXT,
77
- findings TEXT
78
+ findings TEXT,
79
+ prev_hash TEXT,
80
+ hash TEXT
78
81
  )
79
82
  `);
83
+ // self-healing schema check/migration for older logs table
84
+ try {
85
+ const tableInfo = db.prepare("PRAGMA table_info(logs)").all();
86
+ const columnNames = tableInfo.map(c => c.name);
87
+ if (!columnNames.includes("prev_hash")) {
88
+ db.exec("ALTER TABLE logs ADD COLUMN prev_hash TEXT");
89
+ }
90
+ if (!columnNames.includes("hash")) {
91
+ db.exec("ALTER TABLE logs ADD COLUMN hash TEXT");
92
+ }
93
+ }
94
+ catch (err) {
95
+ logger.error(`Failed to migrate logs table schema: ${err.message}`);
96
+ }
80
97
  // Metadata (State) Table
81
98
  db.exec(`
82
99
  CREATE TABLE IF NOT EXISTS metadata (
@@ -98,6 +115,61 @@ export class AtabeyStorage {
98
115
  )
99
116
  `);
100
117
  }
118
+ static saveLog(log) {
119
+ const db = this.getDB();
120
+ const timestamp = new Date().toISOString();
121
+ const agent = log.agent.replace("@", "");
122
+ const maskedSummary = maskText(log.summary);
123
+ const maskedFindings = log.findings ? maskText(log.findings) : null;
124
+ // Get last log's hash
125
+ let prevHash = "GENESIS";
126
+ try {
127
+ const lastRow = db.prepare("SELECT hash FROM logs ORDER BY id DESC LIMIT 1").get();
128
+ if (lastRow && lastRow.hash) {
129
+ prevHash = lastRow.hash;
130
+ }
131
+ }
132
+ catch { /* use default */ }
133
+ // Compute hash
134
+ const dataToHash = `${prevHash}|${agent}|${log.action}|${log.trace_id || ""}|${log.status}|${maskedSummary}|${timestamp}`;
135
+ const hash = crypto.createHash("sha256").update(dataToHash).digest("hex");
136
+ const result = db.prepare(`
137
+ INSERT INTO logs (timestamp, agent, action, trace_id, status, summary, findings, prev_hash, hash)
138
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
139
+ `).run(timestamp, agent, log.action, log.trace_id || null, log.status, maskedSummary, maskedFindings, prevHash, hash);
140
+ return result.lastInsertRowid;
141
+ }
142
+ static verifyLogIntegrity() {
143
+ const db = this.getDB();
144
+ try {
145
+ const rows = db.prepare("SELECT * FROM logs ORDER BY id ASC").all();
146
+ let expectedPrevHash = "GENESIS";
147
+ for (const row of rows) {
148
+ if (row.prev_hash !== expectedPrevHash) {
149
+ return {
150
+ valid: false,
151
+ failedLogId: row.id,
152
+ reason: `Hash mismatch at row ${row.id}: expected prev_hash to be '${expectedPrevHash}', but got '${row.prev_hash}'`
153
+ };
154
+ }
155
+ // Recalculate hash
156
+ const dataToHash = `${row.prev_hash}|${row.agent}|${row.action}|${row.trace_id || ""}|${row.status}|${row.summary}|${row.timestamp}`;
157
+ const calculatedHash = crypto.createHash("sha256").update(dataToHash).digest("hex");
158
+ if (row.hash !== calculatedHash) {
159
+ return {
160
+ valid: false,
161
+ failedLogId: row.id,
162
+ reason: `Hash corruption at row ${row.id}: calculated hash '${calculatedHash}' does not match database hash '${row.hash}'`
163
+ };
164
+ }
165
+ expectedPrevHash = row.hash || "";
166
+ }
167
+ return { valid: true };
168
+ }
169
+ catch (err) {
170
+ return { valid: false, reason: `Verification error: ${err.message}` };
171
+ }
172
+ }
101
173
  // --- Cost Operations ---
102
174
  static saveCost(cost) {
103
175
  const db = this.getDB();
@@ -226,7 +298,9 @@ export class AtabeyStorage {
226
298
  status: r.status,
227
299
  // [KVKK/GDPR] Mask PII in log summaries before returning
228
300
  summary: maskText(r.summary),
229
- findings: r.findings ? maskText(r.findings) : undefined
301
+ findings: r.findings ? maskText(r.findings) : undefined,
302
+ prev_hash: r.prev_hash || undefined,
303
+ hash: r.hash || undefined
230
304
  }));
231
305
  }
232
306
  // --- Metadata Operations ---