ashlrcode 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 (133) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +295 -0
  3. package/package.json +46 -0
  4. package/src/__tests__/branded-types.test.ts +47 -0
  5. package/src/__tests__/context.test.ts +163 -0
  6. package/src/__tests__/cost-tracker.test.ts +274 -0
  7. package/src/__tests__/cron.test.ts +197 -0
  8. package/src/__tests__/dream.test.ts +204 -0
  9. package/src/__tests__/error-handler.test.ts +192 -0
  10. package/src/__tests__/features.test.ts +69 -0
  11. package/src/__tests__/file-history.test.ts +177 -0
  12. package/src/__tests__/hooks.test.ts +145 -0
  13. package/src/__tests__/keybindings.test.ts +159 -0
  14. package/src/__tests__/model-patches.test.ts +82 -0
  15. package/src/__tests__/permissions-rules.test.ts +121 -0
  16. package/src/__tests__/permissions.test.ts +108 -0
  17. package/src/__tests__/project-config.test.ts +63 -0
  18. package/src/__tests__/retry.test.ts +321 -0
  19. package/src/__tests__/router.test.ts +158 -0
  20. package/src/__tests__/session-compact.test.ts +191 -0
  21. package/src/__tests__/session.test.ts +145 -0
  22. package/src/__tests__/skill-registry.test.ts +130 -0
  23. package/src/__tests__/speculation.test.ts +196 -0
  24. package/src/__tests__/tasks-v2.test.ts +267 -0
  25. package/src/__tests__/telemetry.test.ts +149 -0
  26. package/src/__tests__/tool-executor.test.ts +141 -0
  27. package/src/__tests__/tool-registry.test.ts +166 -0
  28. package/src/__tests__/undercover.test.ts +93 -0
  29. package/src/__tests__/workflow.test.ts +195 -0
  30. package/src/agent/async-context.ts +64 -0
  31. package/src/agent/context.ts +245 -0
  32. package/src/agent/cron.ts +189 -0
  33. package/src/agent/dream.ts +165 -0
  34. package/src/agent/error-handler.ts +108 -0
  35. package/src/agent/ipc.ts +256 -0
  36. package/src/agent/kairos.ts +207 -0
  37. package/src/agent/loop.ts +314 -0
  38. package/src/agent/model-patches.ts +68 -0
  39. package/src/agent/speculation.ts +219 -0
  40. package/src/agent/sub-agent.ts +125 -0
  41. package/src/agent/system-prompt.ts +231 -0
  42. package/src/agent/team.ts +220 -0
  43. package/src/agent/tool-executor.ts +162 -0
  44. package/src/agent/workflow.ts +189 -0
  45. package/src/agent/worktree-manager.ts +86 -0
  46. package/src/autopilot/queue.ts +186 -0
  47. package/src/autopilot/scanner.ts +245 -0
  48. package/src/autopilot/types.ts +58 -0
  49. package/src/bridge/bridge-client.ts +57 -0
  50. package/src/bridge/bridge-server.ts +81 -0
  51. package/src/cli.ts +1120 -0
  52. package/src/config/features.ts +51 -0
  53. package/src/config/git.ts +137 -0
  54. package/src/config/hooks.ts +201 -0
  55. package/src/config/permissions.ts +251 -0
  56. package/src/config/project-config.ts +63 -0
  57. package/src/config/remote-settings.ts +163 -0
  58. package/src/config/settings-sync.ts +170 -0
  59. package/src/config/settings.ts +113 -0
  60. package/src/config/undercover.ts +76 -0
  61. package/src/config/upgrade-notice.ts +65 -0
  62. package/src/mcp/client.ts +197 -0
  63. package/src/mcp/manager.ts +125 -0
  64. package/src/mcp/oauth.ts +252 -0
  65. package/src/mcp/types.ts +61 -0
  66. package/src/persistence/memory.ts +129 -0
  67. package/src/persistence/session.ts +289 -0
  68. package/src/planning/plan-mode.ts +128 -0
  69. package/src/planning/plan-tools.ts +138 -0
  70. package/src/providers/anthropic.ts +177 -0
  71. package/src/providers/cost-tracker.ts +184 -0
  72. package/src/providers/retry.ts +264 -0
  73. package/src/providers/router.ts +159 -0
  74. package/src/providers/types.ts +79 -0
  75. package/src/providers/xai.ts +217 -0
  76. package/src/repl.tsx +1384 -0
  77. package/src/setup.ts +119 -0
  78. package/src/skills/loader.ts +78 -0
  79. package/src/skills/registry.ts +78 -0
  80. package/src/skills/types.ts +11 -0
  81. package/src/state/file-history.ts +264 -0
  82. package/src/telemetry/event-log.ts +116 -0
  83. package/src/tools/agent.ts +133 -0
  84. package/src/tools/ask-user.ts +229 -0
  85. package/src/tools/bash.ts +146 -0
  86. package/src/tools/config.ts +147 -0
  87. package/src/tools/diff.ts +137 -0
  88. package/src/tools/file-edit.ts +123 -0
  89. package/src/tools/file-read.ts +82 -0
  90. package/src/tools/file-write.ts +82 -0
  91. package/src/tools/glob.ts +76 -0
  92. package/src/tools/grep.ts +187 -0
  93. package/src/tools/ls.ts +77 -0
  94. package/src/tools/lsp.ts +375 -0
  95. package/src/tools/mcp-resources.ts +83 -0
  96. package/src/tools/mcp-tool.ts +47 -0
  97. package/src/tools/memory.ts +148 -0
  98. package/src/tools/notebook-edit.ts +133 -0
  99. package/src/tools/peers.ts +113 -0
  100. package/src/tools/powershell.ts +83 -0
  101. package/src/tools/registry.ts +114 -0
  102. package/src/tools/send-message.ts +75 -0
  103. package/src/tools/sleep.ts +50 -0
  104. package/src/tools/snip.ts +143 -0
  105. package/src/tools/tasks.ts +349 -0
  106. package/src/tools/team.ts +309 -0
  107. package/src/tools/todo-write.ts +93 -0
  108. package/src/tools/tool-search.ts +83 -0
  109. package/src/tools/types.ts +52 -0
  110. package/src/tools/web-browser.ts +263 -0
  111. package/src/tools/web-fetch.ts +118 -0
  112. package/src/tools/web-search.ts +107 -0
  113. package/src/tools/workflow.ts +188 -0
  114. package/src/tools/worktree.ts +143 -0
  115. package/src/types/branded.ts +22 -0
  116. package/src/ui/App.tsx +184 -0
  117. package/src/ui/BuddyPanel.tsx +52 -0
  118. package/src/ui/PermissionPrompt.tsx +29 -0
  119. package/src/ui/banner.ts +217 -0
  120. package/src/ui/buddy-ai.ts +108 -0
  121. package/src/ui/buddy.ts +466 -0
  122. package/src/ui/context-bar.ts +60 -0
  123. package/src/ui/effort.ts +65 -0
  124. package/src/ui/keybindings.ts +143 -0
  125. package/src/ui/markdown.ts +271 -0
  126. package/src/ui/message-renderer.ts +73 -0
  127. package/src/ui/mode.ts +80 -0
  128. package/src/ui/notifications.ts +57 -0
  129. package/src/ui/speech-bubble.ts +95 -0
  130. package/src/ui/spinner.ts +116 -0
  131. package/src/ui/theme.ts +98 -0
  132. package/src/version.ts +5 -0
  133. package/src/voice/voice-mode.ts +169 -0
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Codebase scanner — discovers work items autonomously.
3
+ *
4
+ * Scans for: TODOs, missing tests, lint errors, type errors,
5
+ * security issues, dead code, complexity, missing docs.
6
+ */
7
+
8
+ import { randomUUID } from "crypto";
9
+ import type { WorkItem, WorkItemType, WorkItemPriority } from "./types.ts";
10
+
11
+ interface ScanContext {
12
+ cwd: string;
13
+ runCommand: (cmd: string) => Promise<string>;
14
+ searchFiles: (pattern: string, path?: string) => Promise<string>;
15
+ grepContent: (pattern: string, glob?: string) => Promise<string>;
16
+ }
17
+
18
+ /**
19
+ * Run a full scan and return discovered work items.
20
+ */
21
+ export async function scanCodebase(ctx: ScanContext, types: WorkItemType[]): Promise<WorkItem[]> {
22
+ const items: WorkItem[] = [];
23
+ const now = new Date().toISOString();
24
+
25
+ // Run scans in parallel for speed
26
+ const scanners: Array<Promise<WorkItem[]>> = [];
27
+
28
+ if (types.includes("todo")) scanners.push(scanTodos(ctx, now));
29
+ if (types.includes("missing_test")) scanners.push(scanMissingTests(ctx, now));
30
+ if (types.includes("type_error")) scanners.push(scanTypeErrors(ctx, now));
31
+ if (types.includes("lint_error")) scanners.push(scanLintErrors(ctx, now));
32
+ if (types.includes("complexity")) scanners.push(scanComplexity(ctx, now));
33
+ if (types.includes("security")) scanners.push(scanSecurity(ctx, now));
34
+ if (types.includes("dead_code")) scanners.push(scanDeadCode(ctx, now));
35
+
36
+ const results = await Promise.allSettled(scanners);
37
+ for (const result of results) {
38
+ if (result.status === "fulfilled") {
39
+ items.push(...result.value);
40
+ }
41
+ }
42
+
43
+ return items;
44
+ }
45
+
46
+ // ── Individual Scanners ──
47
+
48
+ async function scanTodos(ctx: ScanContext, now: string): Promise<WorkItem[]> {
49
+ const items: WorkItem[] = [];
50
+ try {
51
+ const result = await ctx.grepContent("(TODO|FIXME|HACK|XXX):", "*.{ts,tsx,js,jsx,py,go,rs}");
52
+ const lines = result.split("\n").filter(l => l.trim());
53
+
54
+ for (const line of lines.slice(0, 50)) { // Cap at 50
55
+ const match = line.match(/^(.+?):(\d+):(.+)$/);
56
+ if (!match) continue;
57
+ const [, file, lineNum, content] = match;
58
+ const isFixme = content!.includes("FIXME") || content!.includes("HACK");
59
+
60
+ items.push({
61
+ id: randomUUID().slice(0, 8),
62
+ type: "todo",
63
+ priority: isFixme ? "high" : "medium",
64
+ title: `${isFixme ? "FIXME" : "TODO"} in ${file}:${lineNum}`,
65
+ description: content!.trim().slice(0, 200),
66
+ file: file!,
67
+ line: parseInt(lineNum!, 10),
68
+ status: "discovered",
69
+ discoveredAt: now,
70
+ });
71
+ }
72
+ } catch {}
73
+ return items;
74
+ }
75
+
76
+ async function scanMissingTests(ctx: ScanContext, now: string): Promise<WorkItem[]> {
77
+ const items: WorkItem[] = [];
78
+ try {
79
+ const srcFiles = await ctx.searchFiles("src/**/*.ts");
80
+ const testFiles = await ctx.searchFiles("src/**/*.test.ts");
81
+
82
+ const srcList = srcFiles.split("\n").filter(f => f.trim() && !f.includes(".test.") && !f.includes("__tests__"));
83
+ const testList = new Set(testFiles.split("\n").map(f => f.trim()));
84
+
85
+ for (const src of srcList.slice(0, 20)) {
86
+ const expectedTest = src.replace(".ts", ".test.ts");
87
+ if (!testList.has(expectedTest) && !src.includes("/types") && !src.includes("/index")) {
88
+ items.push({
89
+ id: randomUUID().slice(0, 8),
90
+ type: "missing_test",
91
+ priority: "medium",
92
+ title: `No tests for ${src.split("/").pop()}`,
93
+ description: `${src} has no corresponding test file`,
94
+ file: src,
95
+ status: "discovered",
96
+ discoveredAt: now,
97
+ });
98
+ }
99
+ }
100
+ } catch {}
101
+ return items;
102
+ }
103
+
104
+ async function scanTypeErrors(ctx: ScanContext, now: string): Promise<WorkItem[]> {
105
+ const items: WorkItem[] = [];
106
+ try {
107
+ const result = await ctx.runCommand("bunx tsc --noEmit 2>&1 || true");
108
+ const errors = result.split("\n").filter(l => l.includes("error TS"));
109
+
110
+ for (const error of errors.slice(0, 20)) {
111
+ const match = error.match(/^(.+?)\((\d+),\d+\):\s*error\s+(TS\d+):\s*(.+)$/);
112
+ if (!match) continue;
113
+ const [, file, lineNum, code, msg] = match;
114
+
115
+ items.push({
116
+ id: randomUUID().slice(0, 8),
117
+ type: "type_error",
118
+ priority: "high",
119
+ title: `${code} in ${file}:${lineNum}`,
120
+ description: msg!.trim(),
121
+ file: file!,
122
+ line: parseInt(lineNum!, 10),
123
+ status: "discovered",
124
+ discoveredAt: now,
125
+ });
126
+ }
127
+ } catch {}
128
+ return items;
129
+ }
130
+
131
+ async function scanLintErrors(ctx: ScanContext, now: string): Promise<WorkItem[]> {
132
+ const items: WorkItem[] = [];
133
+ try {
134
+ const result = await ctx.runCommand("npx eslint src/ --format json 2>/dev/null || true");
135
+ // Parse JSON output if available
136
+ try {
137
+ const data = JSON.parse(result);
138
+ for (const file of data) {
139
+ for (const msg of (file.messages ?? []).slice(0, 10)) {
140
+ items.push({
141
+ id: randomUUID().slice(0, 8),
142
+ type: "lint_error",
143
+ priority: msg.severity === 2 ? "high" : "low",
144
+ title: `${msg.ruleId} in ${file.filePath.split("/").pop()}:${msg.line}`,
145
+ description: msg.message,
146
+ file: file.filePath,
147
+ line: msg.line,
148
+ status: "discovered",
149
+ discoveredAt: now,
150
+ });
151
+ }
152
+ }
153
+ } catch {
154
+ // ESLint not available or no JSON output
155
+ }
156
+ } catch {}
157
+ return items;
158
+ }
159
+
160
+ async function scanComplexity(ctx: ScanContext, now: string): Promise<WorkItem[]> {
161
+ const items: WorkItem[] = [];
162
+ try {
163
+ // Find functions > 50 lines by searching for function/method patterns
164
+ const result = await ctx.grepContent("^(export )?(async )?(function |const .+ = )", "*.{ts,tsx}");
165
+ const lines = result.split("\n").filter(l => l.trim());
166
+
167
+ // Group by file to detect long functions
168
+ // (Simplified: just flag files with many function definitions as potentially complex)
169
+ const fileCounts = new Map<string, number>();
170
+ for (const line of lines) {
171
+ const file = line.split(":")[0];
172
+ if (file) fileCounts.set(file, (fileCounts.get(file) ?? 0) + 1);
173
+ }
174
+
175
+ for (const [file, count] of fileCounts) {
176
+ if (count > 15) { // Files with many functions might need splitting
177
+ items.push({
178
+ id: randomUUID().slice(0, 8),
179
+ type: "complexity",
180
+ priority: "low",
181
+ title: `${file.split("/").pop()} has ${count} functions`,
182
+ description: `Consider splitting ${file} into smaller modules`,
183
+ file,
184
+ status: "discovered",
185
+ discoveredAt: now,
186
+ });
187
+ }
188
+ }
189
+ } catch {}
190
+ return items;
191
+ }
192
+
193
+ async function scanSecurity(ctx: ScanContext, now: string): Promise<WorkItem[]> {
194
+ const items: WorkItem[] = [];
195
+ try {
196
+ const result = await ctx.runCommand("bun pm ls 2>/dev/null | grep -i 'vulnerab' || npm audit --json 2>/dev/null || true");
197
+ if (result.includes("vulnerab") || result.includes("critical") || result.includes("high")) {
198
+ items.push({
199
+ id: randomUUID().slice(0, 8),
200
+ type: "security",
201
+ priority: "critical",
202
+ title: "Dependency vulnerabilities detected",
203
+ description: result.slice(0, 300),
204
+ file: "package.json",
205
+ status: "discovered",
206
+ discoveredAt: now,
207
+ });
208
+ }
209
+ } catch {}
210
+ return items;
211
+ }
212
+
213
+ async function scanDeadCode(ctx: ScanContext, now: string): Promise<WorkItem[]> {
214
+ const items: WorkItem[] = [];
215
+ try {
216
+ // Find exports that might not be imported anywhere
217
+ const exports = await ctx.grepContent("^export (function|const|class|interface|type) (\\w+)", "*.ts");
218
+ const exportLines = exports.split("\n").filter(l => l.trim()).slice(0, 100);
219
+
220
+ for (const line of exportLines.slice(0, 10)) {
221
+ const match = line.match(/export (?:function|const|class|interface|type) (\w+)/);
222
+ if (!match) continue;
223
+ const name = match[1]!;
224
+
225
+ // Check if it's imported anywhere
226
+ const imports = await ctx.grepContent(name, "*.{ts,tsx}");
227
+ const importCount = imports.split("\n").filter(l => l.trim()).length;
228
+
229
+ if (importCount <= 1) { // Only the definition itself
230
+ const file = line.split(":")[0]!;
231
+ items.push({
232
+ id: randomUUID().slice(0, 8),
233
+ type: "dead_code",
234
+ priority: "low",
235
+ title: `Unused export: ${name}`,
236
+ description: `${name} in ${file} may not be used anywhere`,
237
+ file,
238
+ status: "discovered",
239
+ discoveredAt: now,
240
+ });
241
+ }
242
+ }
243
+ } catch {}
244
+ return items;
245
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Autopilot types — autonomous work discovery and execution.
3
+ */
4
+
5
+ export type WorkItemType =
6
+ | "todo" // TODO/FIXME/HACK comments
7
+ | "missing_test" // files without test coverage
8
+ | "lint_error" // linter issues
9
+ | "type_error" // TypeScript errors
10
+ | "security" // dependency vulnerabilities
11
+ | "dead_code" // unused exports/imports
12
+ | "complexity" // functions > 50 lines
13
+ | "missing_docs" // public APIs without docs
14
+ | "stale_dep" // outdated dependencies
15
+ | "error_handling" // uncaught errors, missing try/catch
16
+
17
+ export type WorkItemPriority = "critical" | "high" | "medium" | "low";
18
+
19
+ export type WorkItemStatus =
20
+ | "discovered" // found by scanner
21
+ | "approved" // user approved for execution
22
+ | "in_progress" // being worked on
23
+ | "completed" // done
24
+ | "rejected" // user rejected
25
+ | "failed" // execution failed
26
+
27
+ export interface WorkItem {
28
+ id: string;
29
+ type: WorkItemType;
30
+ priority: WorkItemPriority;
31
+ title: string;
32
+ description: string;
33
+ file: string;
34
+ line?: number;
35
+ status: WorkItemStatus;
36
+ discoveredAt: string;
37
+ completedAt?: string;
38
+ error?: string;
39
+ }
40
+
41
+ export type TrustLevel = "propose" | "auto";
42
+
43
+ export interface AutopilotConfig {
44
+ trustLevel: TrustLevel;
45
+ scanInterval: number; // ms between scans (default: 60000)
46
+ maxConcurrent: number; // max items to work on at once
47
+ scanTypes: WorkItemType[];
48
+ }
49
+
50
+ export const DEFAULT_CONFIG: AutopilotConfig = {
51
+ trustLevel: "propose",
52
+ scanInterval: 60_000,
53
+ maxConcurrent: 1,
54
+ scanTypes: [
55
+ "todo", "missing_test", "lint_error", "type_error",
56
+ "security", "dead_code", "complexity",
57
+ ],
58
+ };
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Bridge Client — connect to a remote AshlrCode instance.
3
+ */
4
+
5
+ export interface BridgeClientConfig {
6
+ url: string;
7
+ authToken: string;
8
+ }
9
+
10
+ export class BridgeClient {
11
+ private url: string;
12
+ private token: string;
13
+
14
+ constructor(config: BridgeClientConfig) {
15
+ this.url = config.url.replace(/\/$/, "");
16
+ this.token = config.authToken;
17
+ }
18
+
19
+ private async request(path: string, options?: RequestInit): Promise<any> {
20
+ const response = await fetch(`${this.url}${path}`, {
21
+ ...options,
22
+ headers: {
23
+ Authorization: `Bearer ${this.token}`,
24
+ "Content-Type": "application/json",
25
+ ...options?.headers,
26
+ },
27
+ });
28
+ if (!response.ok) throw new Error(`Bridge error: ${response.status}`);
29
+ return response.json();
30
+ }
31
+
32
+ async getStatus(): Promise<{
33
+ mode: string;
34
+ contextPercent: number;
35
+ isProcessing: boolean;
36
+ sessionId: string;
37
+ }> {
38
+ return this.request("/api/status");
39
+ }
40
+
41
+ async submit(prompt: string): Promise<{ result: string }> {
42
+ return this.request("/api/submit", {
43
+ method: "POST",
44
+ body: JSON.stringify({ prompt }),
45
+ });
46
+ }
47
+
48
+ async getHistory(): Promise<{
49
+ messages: Array<{ role: string; content: string }>;
50
+ }> {
51
+ return this.request("/api/history");
52
+ }
53
+
54
+ async health(): Promise<{ status: string; uptime: number }> {
55
+ return this.request("/api/health");
56
+ }
57
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Bridge Server — HTTP API for IDE and remote client integration.
3
+ * Runs alongside the REPL on a configurable port.
4
+ */
5
+
6
+ export interface BridgeConfig {
7
+ port: number;
8
+ authToken: string;
9
+ onSubmit: (prompt: string) => Promise<string>;
10
+ getStatus: () => { mode: string; contextPercent: number; isProcessing: boolean; sessionId: string };
11
+ getHistory: () => Array<{ role: string; content: string }>;
12
+ }
13
+
14
+ let _server: ReturnType<typeof Bun.serve> | null = null;
15
+ let _config: BridgeConfig | null = null;
16
+
17
+ export function startBridgeServer(config: BridgeConfig): void {
18
+ _config = config;
19
+
20
+ _server = Bun.serve({
21
+ port: config.port,
22
+ fetch: async (req) => {
23
+ // Auth check
24
+ const auth = req.headers.get("Authorization");
25
+ if (auth !== `Bearer ${config.authToken}`) {
26
+ return new Response(JSON.stringify({ error: "Unauthorized" }), {
27
+ status: 401,
28
+ headers: { "Content-Type": "application/json" },
29
+ });
30
+ }
31
+
32
+ const url = new URL(req.url);
33
+
34
+ // Routes
35
+ switch (url.pathname) {
36
+ case "/api/status": {
37
+ const status = config.getStatus();
38
+ return Response.json(status);
39
+ }
40
+
41
+ case "/api/submit": {
42
+ if (req.method !== "POST") return new Response("Method not allowed", { status: 405 });
43
+ const body = (await req.json()) as { prompt: string };
44
+ if (!body.prompt) return Response.json({ error: "prompt required" }, { status: 400 });
45
+
46
+ try {
47
+ const result = await config.onSubmit(body.prompt);
48
+ return Response.json({ result });
49
+ } catch (err) {
50
+ return Response.json({ error: String(err) }, { status: 500 });
51
+ }
52
+ }
53
+
54
+ case "/api/history": {
55
+ const history = config.getHistory();
56
+ return Response.json({ messages: history.slice(-50) });
57
+ }
58
+
59
+ case "/api/health": {
60
+ return Response.json({ status: "ok", uptime: process.uptime() });
61
+ }
62
+
63
+ default:
64
+ return new Response("Not found", { status: 404 });
65
+ }
66
+ },
67
+ });
68
+
69
+ console.log(` Bridge server listening on http://localhost:${config.port}`);
70
+ }
71
+
72
+ export function stopBridgeServer(): void {
73
+ if (_server) {
74
+ _server.stop();
75
+ _server = null;
76
+ }
77
+ }
78
+
79
+ export function getBridgePort(): number | null {
80
+ return _config?.port ?? null;
81
+ }