aura-code 0.6.3 → 0.10.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 (265) hide show
  1. package/README.md +47 -164
  2. package/dist/agent/affect.d.ts +13 -0
  3. package/dist/agent/affect.js +34 -0
  4. package/dist/agent/affect.js.map +1 -0
  5. package/dist/agent/compactor.d.ts +27 -4
  6. package/dist/agent/compactor.js +185 -54
  7. package/dist/agent/compactor.js.map +1 -1
  8. package/dist/agent/confess.d.ts +35 -0
  9. package/dist/agent/confess.js +296 -0
  10. package/dist/agent/confess.js.map +1 -0
  11. package/dist/agent/context.d.ts +0 -4
  12. package/dist/agent/context.js +0 -38
  13. package/dist/agent/context.js.map +1 -1
  14. package/dist/agent/domain-expertise.d.ts +22 -0
  15. package/dist/agent/domain-expertise.js +110 -0
  16. package/dist/agent/domain-expertise.js.map +1 -0
  17. package/dist/agent/executive-queue.d.ts +26 -0
  18. package/dist/agent/executive-queue.js +50 -0
  19. package/dist/agent/executive-queue.js.map +1 -0
  20. package/dist/agent/generational-flush.d.ts +28 -0
  21. package/dist/agent/generational-flush.js +48 -0
  22. package/dist/agent/generational-flush.js.map +1 -0
  23. package/dist/agent/loop-profile.d.ts +50 -0
  24. package/dist/agent/loop-profile.js +88 -0
  25. package/dist/agent/loop-profile.js.map +1 -0
  26. package/dist/agent/loop.d.ts +14 -5
  27. package/dist/agent/loop.js +167 -39
  28. package/dist/agent/loop.js.map +1 -1
  29. package/dist/agent/memory-consolidate.d.ts +14 -0
  30. package/dist/agent/memory-consolidate.js +141 -0
  31. package/dist/agent/memory-consolidate.js.map +1 -0
  32. package/dist/agent/mixture.d.ts +33 -0
  33. package/dist/agent/mixture.js +138 -0
  34. package/dist/agent/mixture.js.map +1 -0
  35. package/dist/agent/spawner.js +2 -2
  36. package/dist/agent/spawner.js.map +1 -1
  37. package/dist/agent/system-prompt.d.ts +1 -1
  38. package/dist/agent/system-prompt.js +50 -22
  39. package/dist/agent/system-prompt.js.map +1 -1
  40. package/dist/agent/unified-memory.d.ts +15 -0
  41. package/dist/agent/unified-memory.js +164 -0
  42. package/dist/agent/unified-memory.js.map +1 -0
  43. package/dist/checkpoints/engine.d.ts +44 -0
  44. package/dist/checkpoints/engine.js +324 -0
  45. package/dist/checkpoints/engine.js.map +1 -0
  46. package/dist/cli/command-palette.d.ts +26 -0
  47. package/dist/cli/command-palette.js +133 -0
  48. package/dist/cli/command-palette.js.map +1 -0
  49. package/dist/cli/context-health.d.ts +51 -0
  50. package/dist/cli/context-health.js +187 -0
  51. package/dist/cli/context-health.js.map +1 -0
  52. package/dist/cli/diamond.d.ts +67 -0
  53. package/dist/cli/diamond.js +222 -12
  54. package/dist/cli/diamond.js.map +1 -1
  55. package/dist/cli/dic.d.ts +2 -0
  56. package/dist/cli/dic.js +106 -0
  57. package/dist/cli/dic.js.map +1 -0
  58. package/dist/cli/diff-view.d.ts +18 -0
  59. package/dist/cli/diff-view.js +114 -0
  60. package/dist/cli/diff-view.js.map +1 -0
  61. package/dist/cli/display.d.ts +18 -4
  62. package/dist/cli/display.js +74 -151
  63. package/dist/cli/display.js.map +1 -1
  64. package/dist/cli/help-data.d.ts +7 -0
  65. package/dist/cli/help-data.js +80 -0
  66. package/dist/cli/help-data.js.map +1 -0
  67. package/dist/cli/index.js +1002 -852
  68. package/dist/cli/index.js.map +1 -1
  69. package/dist/cli/markdown.d.ts +5 -0
  70. package/dist/cli/markdown.js +128 -0
  71. package/dist/cli/markdown.js.map +1 -0
  72. package/dist/cli/model-select.d.ts +39 -0
  73. package/dist/cli/model-select.js +65 -0
  74. package/dist/cli/model-select.js.map +1 -0
  75. package/dist/cli/tui.d.ts +34 -0
  76. package/dist/cli/tui.js +1176 -0
  77. package/dist/cli/tui.js.map +1 -0
  78. package/dist/config/defaults.d.ts +6 -17
  79. package/dist/config/defaults.js +25 -46
  80. package/dist/config/defaults.js.map +1 -1
  81. package/dist/config/project-config.d.ts +2 -6
  82. package/dist/config/project-config.js +2 -2
  83. package/dist/config/project-config.js.map +1 -1
  84. package/dist/doctor/checks.d.ts +18 -0
  85. package/dist/doctor/checks.js +524 -0
  86. package/dist/doctor/checks.js.map +1 -0
  87. package/dist/doctor/index.d.ts +4 -0
  88. package/dist/doctor/index.js +229 -0
  89. package/dist/doctor/index.js.map +1 -0
  90. package/dist/doctor/repair.d.ts +19 -0
  91. package/dist/doctor/repair.js +126 -0
  92. package/dist/doctor/repair.js.map +1 -0
  93. package/dist/doctor/types.d.ts +38 -0
  94. package/dist/doctor/types.js +10 -0
  95. package/dist/doctor/types.js.map +1 -0
  96. package/dist/dream/dream.d.ts +79 -59
  97. package/dist/dream/dream.js +324 -144
  98. package/dist/dream/dream.js.map +1 -1
  99. package/dist/dream/episode.d.ts +17 -0
  100. package/dist/dream/episode.js +116 -0
  101. package/dist/dream/episode.js.map +1 -0
  102. package/dist/kanban/engine.d.ts +26 -0
  103. package/dist/kanban/engine.js +208 -0
  104. package/dist/kanban/engine.js.map +1 -0
  105. package/dist/kanban/types.d.ts +19 -0
  106. package/dist/kanban/types.js +9 -1
  107. package/dist/kanban/types.js.map +1 -1
  108. package/dist/machina/render-html.js +4 -2
  109. package/dist/machina/render-html.js.map +1 -1
  110. package/dist/machina/render-terminal.js +11 -4
  111. package/dist/machina/render-terminal.js.map +1 -1
  112. package/dist/machina/repair.d.ts +23 -0
  113. package/dist/machina/repair.js +98 -0
  114. package/dist/machina/repair.js.map +1 -0
  115. package/dist/machina/spec.js +12 -12
  116. package/dist/machina/spec.js.map +1 -1
  117. package/dist/machina/verify.d.ts +13 -1
  118. package/dist/machina/verify.js +12 -2
  119. package/dist/machina/verify.js.map +1 -1
  120. package/dist/mining/extract.d.ts +34 -0
  121. package/dist/mining/extract.js +229 -0
  122. package/dist/mining/extract.js.map +1 -0
  123. package/dist/mining/refine.d.ts +28 -0
  124. package/dist/mining/refine.js +256 -0
  125. package/dist/mining/refine.js.map +1 -0
  126. package/dist/perception/extractor.js +5 -3
  127. package/dist/perception/extractor.js.map +1 -1
  128. package/dist/perception/graph-store.d.ts +0 -11
  129. package/dist/perception/graph-store.js +0 -33
  130. package/dist/perception/graph-store.js.map +1 -1
  131. package/dist/perception/index.d.ts +1 -1
  132. package/dist/perception/index.js +1 -2
  133. package/dist/perception/index.js.map +1 -1
  134. package/dist/plugins/commands.d.ts +38 -0
  135. package/dist/plugins/commands.js +134 -0
  136. package/dist/plugins/commands.js.map +1 -0
  137. package/dist/plugins/frontmatter.d.ts +14 -0
  138. package/dist/plugins/frontmatter.js +78 -0
  139. package/dist/plugins/frontmatter.js.map +1 -0
  140. package/dist/plugins/hooks.d.ts +12 -0
  141. package/dist/plugins/hooks.js +148 -0
  142. package/dist/plugins/hooks.js.map +1 -0
  143. package/dist/plugins/loader.d.ts +7 -0
  144. package/dist/plugins/loader.js +255 -0
  145. package/dist/plugins/loader.js.map +1 -0
  146. package/dist/plugins/market.d.ts +23 -0
  147. package/dist/plugins/market.js +281 -0
  148. package/dist/plugins/market.js.map +1 -0
  149. package/dist/plugins/types.d.ts +90 -0
  150. package/dist/plugins/types.js +20 -0
  151. package/dist/plugins/types.js.map +1 -0
  152. package/dist/providers/anthropic-oauth-draft.d.ts +119 -0
  153. package/dist/providers/anthropic-oauth-draft.js +414 -0
  154. package/dist/providers/anthropic-oauth-draft.js.map +1 -0
  155. package/dist/providers/anthropic.d.ts +4 -1
  156. package/dist/providers/anthropic.js +35 -7
  157. package/dist/providers/anthropic.js.map +1 -1
  158. package/dist/providers/factory.d.ts +43 -19
  159. package/dist/providers/factory.js +183 -129
  160. package/dist/providers/factory.js.map +1 -1
  161. package/dist/providers/google.js +9 -22
  162. package/dist/providers/google.js.map +1 -1
  163. package/dist/providers/live-models.d.ts +23 -0
  164. package/dist/providers/live-models.js +145 -0
  165. package/dist/providers/live-models.js.map +1 -0
  166. package/dist/providers/openai-compatible.d.ts +1 -12
  167. package/dist/providers/openai-compatible.js +71 -108
  168. package/dist/providers/openai-compatible.js.map +1 -1
  169. package/dist/providers/resilient-factory.js +1 -1
  170. package/dist/providers/resilient-factory.js.map +1 -1
  171. package/dist/repl/queue.d.ts +22 -0
  172. package/dist/repl/queue.js +165 -0
  173. package/dist/repl/queue.js.map +1 -0
  174. package/dist/repl/side-channel.d.ts +23 -0
  175. package/dist/repl/side-channel.js +63 -0
  176. package/dist/repl/side-channel.js.map +1 -0
  177. package/dist/research/council.d.ts +2 -0
  178. package/dist/research/council.js +29 -4
  179. package/dist/research/council.js.map +1 -1
  180. package/dist/ruby/alternator.d.ts +11 -30
  181. package/dist/ruby/alternator.js +59 -38
  182. package/dist/ruby/alternator.js.map +1 -1
  183. package/dist/ruby/episode-capture.d.ts +0 -10
  184. package/dist/ruby/episode-capture.js +0 -33
  185. package/dist/ruby/episode-capture.js.map +1 -1
  186. package/dist/ruby/types.js +1 -1
  187. package/dist/ruby/types.js.map +1 -1
  188. package/dist/safety/path-jail.d.ts +17 -0
  189. package/dist/safety/path-jail.js +100 -0
  190. package/dist/safety/path-jail.js.map +1 -0
  191. package/dist/safety/permissions.d.ts +12 -21
  192. package/dist/safety/permissions.js +81 -216
  193. package/dist/safety/permissions.js.map +1 -1
  194. package/dist/safety/ssrf.d.ts +29 -0
  195. package/dist/safety/ssrf.js +185 -0
  196. package/dist/safety/ssrf.js.map +1 -0
  197. package/dist/server/index.js +64 -10
  198. package/dist/server/index.js.map +1 -1
  199. package/dist/setup/first-run.d.ts +0 -24
  200. package/dist/setup/first-run.js +10 -344
  201. package/dist/setup/first-run.js.map +1 -1
  202. package/dist/setup/key-store.d.ts +12 -0
  203. package/dist/setup/key-store.js +108 -0
  204. package/dist/setup/key-store.js.map +1 -0
  205. package/dist/setup/provider-registry.js +31 -18
  206. package/dist/setup/provider-registry.js.map +1 -1
  207. package/dist/setup/provider-test.d.ts +7 -0
  208. package/dist/setup/provider-test.js +73 -10
  209. package/dist/setup/provider-test.js.map +1 -1
  210. package/dist/setup/provider-wizard.d.ts +4 -1
  211. package/dist/setup/provider-wizard.js +92 -26
  212. package/dist/setup/provider-wizard.js.map +1 -1
  213. package/dist/setup/xiaomi.d.ts +1 -1
  214. package/dist/setup/xiaomi.js +3 -3
  215. package/dist/setup/xiaomi.js.map +1 -1
  216. package/dist/tools/browser.js +14 -10
  217. package/dist/tools/browser.js.map +1 -1
  218. package/dist/tools/clipboard.js +7 -1
  219. package/dist/tools/clipboard.js.map +1 -1
  220. package/dist/tools/dictate.d.ts +60 -0
  221. package/dist/tools/dictate.js +983 -0
  222. package/dist/tools/dictate.js.map +1 -0
  223. package/dist/tools/dictate_patched.js +1057 -0
  224. package/dist/tools/edit-file.js +10 -2
  225. package/dist/tools/edit-file.js.map +1 -1
  226. package/dist/tools/http-request.js +14 -4
  227. package/dist/tools/http-request.js.map +1 -1
  228. package/dist/tools/image-read.js +34 -2
  229. package/dist/tools/image-read.js.map +1 -1
  230. package/dist/tools/index.d.ts +16 -1
  231. package/dist/tools/index.js +59 -8
  232. package/dist/tools/index.js.map +1 -1
  233. package/dist/tools/mcp.js +12 -0
  234. package/dist/tools/mcp.js.map +1 -1
  235. package/dist/tools/read-file.js +10 -14
  236. package/dist/tools/read-file.js.map +1 -1
  237. package/dist/tools/telegram-audio-policy.d.ts +30 -0
  238. package/dist/tools/telegram-audio-policy.js +49 -0
  239. package/dist/tools/telegram-audio-policy.js.map +1 -0
  240. package/dist/tools/telegram-bot.js +1136 -537
  241. package/dist/tools/telegram-bot.js.map +1 -1
  242. package/dist/tools/telegram-voice.d.ts +18 -5
  243. package/dist/tools/telegram-voice.js +54 -12
  244. package/dist/tools/telegram-voice.js.map +1 -1
  245. package/dist/tools/telegram.d.ts +3 -3
  246. package/dist/tools/telegram.js +5 -84
  247. package/dist/tools/telegram.js.map +1 -1
  248. package/dist/tools/tools.d.ts +2 -14
  249. package/dist/tools/tools.js +79 -144
  250. package/dist/tools/tools.js.map +1 -1
  251. package/dist/tools/web-fetch.js +39 -7
  252. package/dist/tools/web-fetch.js.map +1 -1
  253. package/dist/tools/web-search.d.ts +0 -20
  254. package/dist/tools/web-search.js +40 -137
  255. package/dist/tools/web-search.js.map +1 -1
  256. package/dist/util/errors.js +1 -15
  257. package/dist/util/errors.js.map +1 -1
  258. package/dist/util/json-repair.d.ts +11 -0
  259. package/dist/util/json-repair.js +31 -0
  260. package/dist/util/json-repair.js.map +1 -0
  261. package/dist/util/rate-limiter.js +2 -8
  262. package/dist/util/rate-limiter.js.map +1 -1
  263. package/dist/viz/index.js +793 -792
  264. package/dist/viz/index.js.map +1 -1
  265. package/package.json +12 -32
package/README.md CHANGED
@@ -1,189 +1,72 @@
1
- <p align="center">
2
- <img src="./README-hero.jpg" alt="Aura">
3
- </p>
1
+ # Aura Code
4
2
 
5
-
6
- <h1 align="center">Aura Code — Autonomous Coding Agent</h1>
7
-
8
- <p align="center">
9
- <em>I don't try. I verify.</em>
10
- </p>
11
-
12
- <p align="center">
13
- <a href="https://www.npmjs.com/package/aura-code"><img src="https://img.shields.io/npm/v/aura-code?color=cc0000&label=npm&logo=npm" alt="npm version"></a>
14
- <a href="https://github.com/milodule3-debug/aura-code/actions/workflows/ci.yml"><img src="https://github.com/milodule3-debug/aura-code/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
15
- <img src="https://img.shields.io/badge/tests-1205%2B-brightgreen" alt="1205+ tests">
16
- <a href="https://github.com/milodule3-debug/aura-code/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT License"></a>
17
- <img src="https://img.shields.io/badge/node-%3E%3D18-339933?logo=node.js&logoColor=white" alt="Node.js 18+">
18
- <a href="https://github.com/milodule3-debug/aura-code"><img src="https://img.shields.io/github/stars/milodule3-debug/aura-code?style=social" alt="GitHub Stars"></a>
19
- </p>
20
-
21
- <p align="center">
22
- <a href="./docs/ARCHITECTURE.md">
23
- <img src="./assets/architecture-diagram.png" alt="Architecture Diagram" width="800">
24
- </a>
25
- <br>
26
- <em><a href="./docs/ARCHITECTURE.md">View full architecture documentation →</a></em>
27
- </p>
28
-
29
- <p align="center">
30
- <img src="./assets/demo.gif" alt="Aura Code CLI Demo" width="800">
31
- <br>
32
- <em>CLI demo — help, models, and version overview</em>
33
- </p>
34
-
35
- ---
36
-
37
- ## What is Aura Code?
38
-
39
- Designed and brought to life by **Dušan Milosavljević** using opencode.
40
- Built by agents, for agents — AI-assisted engineering workflows with multiple AI agents contributing to design, implementation, testing, and verification.
41
- Model-agnostic: works with Claude, GPT, Gemini, MiMo, and any OpenAI-compatible endpoint.
42
- Written in TypeScript — not related to the Ruby programming language.
43
-
44
- Built on the **Praktess** framework — from Ancient Greek: *she who acts and executes*.
45
-
46
- ---
47
-
48
- ## Quick Start
3
+ > Model-agnostic autonomous coding agent. Natural-language task → read codebase → plan → execute → verify → report.
49
4
 
50
5
  ```bash
51
6
  npm install -g aura-code
52
- aura 'your task here'
7
+ aura 'fix the login bug'
53
8
  ```
54
9
 
55
- Set at least one API key before running:
10
+ ## Quick Start
56
11
 
57
12
  ```bash
58
- export ANTHROPIC_API_KEY="sk-ant-..." # Claude
59
- export OPENAI_API_KEY="sk-..." # GPT
60
- export GOOGLE_API_KEY="..." # Gemini
61
- export XIAOMI_API_KEY="tp-..." # MiMo
62
- # Local — no API key needed:
63
- # ollama pull qwen2.5-coder:1.5b
64
- ```
65
-
66
- ---
67
-
68
- ## What Aura Does
69
-
70
- 1. **Reads** your codebase — files, structure, dependencies
71
- 2. **Plans** a strategy — decides what to change and how
72
- 3. **Executes** — writes code, runs commands, makes edits
73
- 4. **Verifies** — runs tests, checks file integrity, confirms changes
74
- 5. **Reports** — summarizes what was done and what passed
75
-
76
- ---
77
-
78
- ## Modes
79
-
80
- | Mode | What it does |
81
- |------|-------------|
82
- | `normal` | Single-agent loop: read → plan → execute → verify |
83
- | `orchestrate` | Multi-agent: Researcher → Coder → Reviewer |
84
- | `architect` | High-level design and planning before implementation |
85
- | `verify` | Post-task checks with automatic retry on failure |
86
- | `analyze` | Scan session history for failure patterns |
13
+ # Install
14
+ npm install -g aura-code
87
15
 
88
- ```bash
89
- aura 'fix the bug' # normal
90
- aura --orchestrate 'add error handling to all endpoints' # orchestrate
91
- aura --architect 'design the new auth system' # architect
92
- aura --verify --test-command "npm test" 'fix the tests' # verify
93
- aura --analyze # analyze
16
+ # Run (requires at least one provider API key)
17
+ export ANTHROPIC_API_KEY=sk-... # or OPENAI_API_KEY, GOOGLE_API_KEY, etc.
18
+ aura 'add a logout button'
94
19
  ```
95
20
 
96
- ---
97
-
98
- ## Providers
21
+ ## Providers (model-agnostic)
99
22
 
100
23
  | Provider | Models |
101
24
  |----------|--------|
102
- | **Claude** (Anthropic) | Opus, Sonnet, Haiku |
103
- | **GPT** (OpenAI) | gpt-4o, gpt-4o-mini |
104
- | **Gemini** (Google) | gemini-2.5-pro, gemini-2.5-flash |
105
- | **MiMo** (Xiaomi) | mimo-v2.5-pro, mimo-v2.5 |
106
- | **DeepSeek** (OpenRouter) | deepseek-v4-pro, deepseek-v4-flash:free |
107
- | **Ollama** (Local) | Any local model — no API key needed |
108
-
109
- Any OpenAI-compatible endpoint also works via `openrouter/<model>`.
110
-
111
- ---
112
-
113
- ## Testing
114
-
115
- ```bash
116
- npm install
117
- npm run build # compiles TypeScript, zero errors expected
118
- npm test # runs the full suite (vitest)
119
- ```
120
-
121
- For local development:
122
-
123
- ```bash
124
- npm run test:watch # re-runs tests on file changes
125
- npm run test:coverage # generates a coverage report
126
- ```
25
+ | Claude (Anthropic) | Opus, Sonnet, Haiku |
26
+ | GPT (OpenAI) | gpt-4o, gpt-4o-mini |
27
+ | Gemini (Google) | gemini-2.5-pro, gemini-2.5-flash |
28
+ | GLM (Zhipu / Z.ai) | glm-5.2, glm-5.1, glm-5 |
29
+ | MiMo (Xiaomi) | mimo-v2.5-pro, mimo-v2.5 |
30
+ | Ollama | any local model |
31
+ | OpenCode Go | Anthropic-compatible endpoint |
127
32
 
128
- The suite currently runs 1000+ tests across 60+ files, covering the agent
129
- loop, all provider integrations, the tool registry, safety/permissions, the
130
- orchestration and self-improvement layers, and the dashboard generator.
33
+ Run `aura` without arguments for the interactive setup wizard.
131
34
 
132
- Contributions should keep `npm run build` and `npm test` clean before
133
- opening a pull request.
35
+ ## Features
134
36
 
135
- ---
37
+ - **Autonomous execution** — reads files, edits code, runs commands, verifies results
38
+ - **Multi-provider fallback** — resilient API handling with circuit breakers
39
+ - **TUI mode** — full terminal UI with command palette, diff view, markdown rendering
40
+ - **Telegram bot** — voice replies, PC control, file sending
41
+ - **Benchmark harness** — reproducible coding tasks with verified outcomes
42
+ - **Context compaction** — adaptive memory for long sessions
136
43
 
137
- ## Known Limitations
44
+ ## CLI Flags
138
45
 
139
- - **Self-improvement routing (RubyAlternator) is implemented but inactive.**
140
- Episode recording and competence scoring both work and are visible in the
141
- dashboard's Learning tab, but the routing logic that would actually use
142
- competence scores to pick models isn't wired into the main agent loop yet.
143
- Today, model selection doesn't change based on what Aura has learned.
144
-
145
- - **Provider behavior varies by model.** Tool-calling support, context
146
- window handling, and error message quality differ across providers —
147
- some of this is normalized (see CHANGELOG for cross-provider config
148
- fixes), but not all of it. If a model behaves unexpectedly with tools,
149
- check whether the provider documents function-calling support before
150
- assuming it's a bug in Aura.
151
-
152
- - **Large refactors still need verification.** `--verify` and `--test-command`
153
- catch regressions Aura's own changes introduce, but they don't replace
154
- review for changes spanning many files or with subtle behavioral intent.
155
-
156
- - **Conversation compaction is new.** Long sessions now auto-summarize older
157
- turns to stay within context limits; this is recently added and still
158
- being hardened against edge cases in unusual tool-call sequences.
159
-
160
- ---
161
-
162
- ## Stats
163
-
164
- | Metric | Value |
165
- |--------|-------|
166
- | Tests | 1205+ passing, 0 failures |
167
- | Version | v0.6.2 |
168
- | Language | TypeScript (strict) |
169
- | License | MIT |
170
-
171
- ---
172
-
173
- ## Repository
46
+ | Flag | Description |
47
+ |------|-------------|
48
+ | `--auto` | Fully autonomous (no confirmations) |
49
+ | `--model <id>` | Specific model (e.g. `claude-sonnet-4`) |
50
+ | `--provider <name>` | Force provider |
51
+ | `--orchestrate` | Multi-agent mode (Researcher → Coder → Reviewer) |
52
+ | `--verify` | Post-task checks with auto-retry |
53
+ | `--readonly` | Read-only analysis |
54
+ | `--doctor` | Run self-diagnostic checks |
174
55
 
175
- GitHub: https://github.com/milodule3-debug/aura-code
176
- (Repo renamed from milodule3-debug/rubyness — existing clone URLs redirect automatically)
56
+ ## Configuration
177
57
 
178
- ---
58
+ - `.aura.json` — project-level model/provider config
59
+ - Global config via first-run wizard
60
+ - Environment overrides: `AURA_MODEL`, `AURA_FALLBACK_MODEL`, `AURA_MAX_RETRIES`
179
61
 
180
- ## Links
62
+ ## Development
181
63
 
182
- - [Lean Progress IQ](https://lean-progress-iq-site.vercel.app)
183
- - [Aura Manifesto](her-rubyness-manifesto.html)
64
+ ```bash
65
+ npm run build # tsc → dist/
66
+ npm run dev # ts-node src/cli/index.ts
67
+ npm test # vitest
68
+ ```
184
69
 
185
- ---
70
+ ## License
186
71
 
187
- <p align="center">
188
- Built by <a href="https://lean-progress-iq-site.vercel.app">Lean Progress IQ</a>
189
- </p>
72
+ MIT
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Lightweight affect heuristic — integrated into the loop, not a standalone
3
+ * NLU module. A lexical scan of recent user messages; when it detects
4
+ * frustration it returns one advisory line for the compaction recap.
5
+ * No LLM call; false positives cost a single recap line.
6
+ */
7
+ import type { HistoryMessage } from '../providers/types.js';
8
+ /**
9
+ * Scan the last `lookback` user messages. Requires ≥2 total hits, or ≥1 in
10
+ * the very last user message, so ordinary bug-fixing vocabulary ("fix this
11
+ * error") doesn't trip it. Returns an advisory line or null.
12
+ */
13
+ export declare function detectFrustration(history: HistoryMessage[], lookback?: number): string | null;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.detectFrustration = detectFrustration;
4
+ // Seeded from the dream reconciler's NEGATIVE_WORDS plus frustration-specific
5
+ // terms. Kept local: the agent core must not depend on src/dream/.
6
+ const FRUSTRATION_WORDS = [
7
+ 'broken', 'fails', 'failing', 'wrong', 'bug', 'error', 'not working',
8
+ "doesn't work", 'never works', 'stop doing', 'again!!', 'still broken',
9
+ 'still not', 'why does', 'why is this', 'frustrat', 'annoying', 'useless',
10
+ 'wtf', 'terrible',
11
+ ];
12
+ function hits(text) {
13
+ const t = text.toLowerCase();
14
+ return FRUSTRATION_WORDS.reduce((n, w) => (t.includes(w) ? n + 1 : n), 0);
15
+ }
16
+ /**
17
+ * Scan the last `lookback` user messages. Requires ≥2 total hits, or ≥1 in
18
+ * the very last user message, so ordinary bug-fixing vocabulary ("fix this
19
+ * error") doesn't trip it. Returns an advisory line or null.
20
+ */
21
+ function detectFrustration(history, lookback = 3) {
22
+ const userMessages = history.filter(m => m.role === 'user').slice(-lookback);
23
+ if (userMessages.length === 0)
24
+ return null;
25
+ let total = 0;
26
+ for (const m of userMessages)
27
+ total += hits(m.content);
28
+ const lastHit = hits(userMessages[userMessages.length - 1].content) > 0;
29
+ if (total >= 2 || lastHit) {
30
+ return 'Note: recent user messages show signs of frustration — prioritize directness, verify before claiming success.';
31
+ }
32
+ return null;
33
+ }
34
+ //# sourceMappingURL=affect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"affect.js","sourceRoot":"","sources":["../../src/agent/affect.ts"],"names":[],"mappings":";;AA2BA,8CAYC;AA/BD,8EAA8E;AAC9E,mEAAmE;AACnE,MAAM,iBAAiB,GAAG;IACxB,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,aAAa;IACpE,cAAc,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,EAAE,cAAc;IACtE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS;IACzE,KAAK,EAAE,UAAU;CAClB,CAAC;AAEF,SAAS,IAAI,CAAC,IAAY;IACxB,MAAM,CAAC,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IAC7B,OAAO,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAC5E,CAAC;AAED;;;;GAIG;AACH,SAAgB,iBAAiB,CAAC,OAAyB,EAAE,QAAQ,GAAG,CAAC;IACvE,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC;IAC7E,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAE3C,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,CAAC,IAAI,YAAY;QAAE,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;IACvD,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAExE,IAAI,KAAK,IAAI,CAAC,IAAI,OAAO,EAAE,CAAC;QAC1B,OAAO,+GAA+G,CAAC;IACzH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -1,4 +1,13 @@
1
1
  import type { HistoryMessage } from '../providers/types.js';
2
+ export declare const ROLLOVER_AT_GENERATION = 3;
3
+ /** Generation of the current recap (0 if history hasn't been compacted yet). */
4
+ export declare function getRecapGeneration(history: HistoryMessage[]): number;
5
+ /** Index of the current recap message, or -1 if history hasn't been
6
+ * compacted yet. Used by generational-flush.ts to locate the recap it's
7
+ * about to flush and replace. */
8
+ export declare function findRecapIndex(history: HistoryMessage[]): number;
9
+ /** Tokens for a single string: exact via tokenizer, else char-ratio estimate. */
10
+ export declare function countText(text: string): number;
2
11
  /**
3
12
  * Provider-independent estimate of how many tokens the *next* prompt will
4
13
  * carry, measured directly from the system prompt + current history. This is
@@ -13,14 +22,28 @@ import type { HistoryMessage } from '../providers/types.js';
13
22
  * conservative char-ratio estimate. Result is identical in shape either way.
14
23
  */
15
24
  export declare function estimateContextTokens(system: string, history: HistoryMessage[]): number;
25
+ /** Token estimate for a single history message. */
26
+ export declare function countMessage(msg: HistoryMessage): number;
16
27
  /**
17
- * Compact conversation history when context usage crosses ~70% of the model's
18
- * window. Keeps the first message (task) and the most recent messages
19
- * verbatim; replaces the middle with a recap.
28
+ * Compact conversation history when context usage crosses the escalating
29
+ * LADDER threshold for the current recap generation (see thresholdRatio).
30
+ * Keeps the first message (task) and a recent tail of up to RETENTION_RATIO
31
+ * of the window verbatim; replaces the middle with a recap that can carry an
32
+ * executive digest and tone hint (see extras). If the middle already
33
+ * contains a prior recap, it's compressed via compressRecap rather than
34
+ * blindly truncated, and the new recap's generation increments — once it
35
+ * would exceed ROLLOVER_AT_GENERATION, callers should flush via
36
+ * generational-flush.ts instead of calling this again.
20
37
  *
21
38
  * Mutates `history` in place (clears and re-fills) so callers that hold a
22
39
  * shared reference see the compacted version without reassignment.
23
40
  *
24
41
  * Returns `true` if compaction happened.
25
42
  */
26
- export declare function compactHistory(history: HistoryMessage[], totalTokens: number, model: string): boolean;
43
+ export interface CompactionExtras {
44
+ /** Digest of recent state-altering tool calls (ExecutiveQueue.digest()). */
45
+ executiveDigest?: string;
46
+ /** Advisory tone hint (detectFrustration()). */
47
+ affectHint?: string;
48
+ }
49
+ export declare function compactHistory(history: HistoryMessage[], totalTokens: number, model: string, extras?: CompactionExtras): boolean;
@@ -1,11 +1,54 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ROLLOVER_AT_GENERATION = void 0;
4
+ exports.getRecapGeneration = getRecapGeneration;
5
+ exports.findRecapIndex = findRecapIndex;
6
+ exports.countText = countText;
3
7
  exports.estimateContextTokens = estimateContextTokens;
8
+ exports.countMessage = countMessage;
4
9
  exports.compactHistory = compactHistory;
5
10
  const factory_js_1 = require("../providers/factory.js");
6
- const COMPACTION_THRESHOLD = 0.7;
7
- const PRESERVE_RECENT = 3;
11
+ /**
12
+ * Escalating trigger ladder, indexed by "recap generation" (how many times
13
+ * the current recap has already been through compaction). Later generations
14
+ * tolerate more fill before firing again — that content is already dense/
15
+ * pre-summarized, so there's less marginal value in acting early. Held at
16
+ * the last value once the ladder is exhausted; ROLLOVER_AT_GENERATION caps
17
+ * how many in-place rounds happen before a full generational flush (see
18
+ * generational-flush.ts) instead of a further round of lossy recompaction.
19
+ */
20
+ const LADDER = [0.55, 0.70, 0.85];
21
+ exports.ROLLOVER_AT_GENERATION = 3;
22
+ /** Verbatim recent-history budget kept through compaction, as a share of the
23
+ * window. 40% preserves interaction nuance over long sequences. */
24
+ const RETENTION_RATIO = 0.40;
8
25
  const DEFAULT_WINDOW = 128_000;
26
+ /** Messages kept when no later user turn exists to anchor the tail. */
27
+ const FALLBACK_KEEP = 3;
28
+ /** Per-tool-result cap applied by the churn guard (see compactHistory). */
29
+ const MAX_RESULT_CHARS = 4_000;
30
+ function thresholdRatio(generation) {
31
+ return LADDER[Math.min(generation, LADDER.length - 1)];
32
+ }
33
+ /** Marker prefixing every recap message; also carries the generation number. */
34
+ const RECAP_MARKER = '[Earlier conversation compacted';
35
+ function isRecap(msg) {
36
+ return msg.role === 'assistant' && msg.content.startsWith(RECAP_MARKER);
37
+ }
38
+ /** Generation of the current recap (0 if history hasn't been compacted yet). */
39
+ function getRecapGeneration(history) {
40
+ const recap = history.find(isRecap);
41
+ if (!recap)
42
+ return 0;
43
+ const match = recap.content.match(/\(gen (\d+)\)/);
44
+ return match ? parseInt(match[1], 10) : 0;
45
+ }
46
+ /** Index of the current recap message, or -1 if history hasn't been
47
+ * compacted yet. Used by generational-flush.ts to locate the recap it's
48
+ * about to flush and replace. */
49
+ function findRecapIndex(history) {
50
+ return history.findIndex(isRecap);
51
+ }
9
52
  /** Average characters per token used for the local size estimate. Deliberately
10
53
  * conservative (real ratio is ~4 for English prose, lower for code/JSON) so we
11
54
  * err toward compacting slightly early rather than overflowing. */
@@ -59,25 +102,77 @@ function countText(text) {
59
102
  */
60
103
  function estimateContextTokens(system, history) {
61
104
  let tokens = countText(system);
62
- for (const msg of history) {
63
- switch (msg.role) {
64
- case 'user':
65
- tokens += countText(msg.content);
66
- break;
67
- case 'assistant':
68
- tokens += countText(msg.content ?? '');
69
- for (const call of msg.toolCalls ?? []) {
70
- tokens += countText(call.name) + countText(JSON.stringify(call.input));
71
- }
72
- break;
73
- case 'tool_result':
74
- for (const r of msg.results) {
75
- tokens += countText(r.name) + countText(r.content ?? '');
76
- }
77
- break;
105
+ for (const msg of history)
106
+ tokens += countMessage(msg);
107
+ return tokens;
108
+ }
109
+ /** Token estimate for a single history message. */
110
+ function countMessage(msg) {
111
+ switch (msg.role) {
112
+ case 'user':
113
+ return countText(msg.content);
114
+ case 'assistant': {
115
+ let tokens = countText(msg.content ?? '');
116
+ for (const call of msg.toolCalls ?? []) {
117
+ tokens += countText(call.name) + countText(JSON.stringify(call.input));
118
+ }
119
+ return tokens;
120
+ }
121
+ case 'tool_result': {
122
+ let tokens = 0;
123
+ for (const r of msg.results) {
124
+ tokens += countText(r.name) + countText(r.content ?? '');
125
+ }
126
+ return tokens;
78
127
  }
79
128
  }
80
- return tokens;
129
+ }
130
+ /** Generic English + prior-recap boilerplate excluded from term extraction. */
131
+ const RECAP_STOPWORDS = new Set([
132
+ 'the', 'a', 'an', 'is', 'was', 'were', 'be', 'been', 'to', 'of', 'in', 'on',
133
+ 'for', 'and', 'or', 'it', 'this', 'that', 'with', 'not', 'note', 'recent',
134
+ 'user', 'assistant', 'tool', 'results', 'called', 'earlier', 'conversation',
135
+ 'compacted', 'turns', 'removed', 'stay', 'within', 'context', 'limits',
136
+ ]);
137
+ /** Top-N most frequent non-stopword tokens across a set of lines. Pure stats,
138
+ * no LLM — same style as dream.ts's topicKey / mining/extract.ts. */
139
+ function extractTopTerms(lines, n) {
140
+ const freq = new Map();
141
+ for (const line of lines) {
142
+ const words = line.toLowerCase().replace(/[^a-z0-9\s./_-]/g, ' ').split(/\s+/);
143
+ for (const w of words) {
144
+ if (w.length <= 3 || RECAP_STOPWORDS.has(w))
145
+ continue;
146
+ freq.set(w, (freq.get(w) ?? 0) + 1);
147
+ }
148
+ }
149
+ return [...freq.entries()].sort((a, b) => b[1] - a[1]).slice(0, n).map(([w]) => w);
150
+ }
151
+ /** First non-empty line, truncated to ~8 words — a cheap topic label. */
152
+ function extractConcept(lines) {
153
+ const first = lines.find(l => l.trim().length > 0)?.trim() ?? '(no content)';
154
+ const words = first.split(/\s+/);
155
+ return words.length > 8 ? words.slice(0, 8).join(' ') + '…' : first;
156
+ }
157
+ /**
158
+ * Reduce an existing recap to: 1 concept + top 5 terms, plus its LAST body
159
+ * line preserved verbatim ("last thread, pre-compaction"). Replaces
160
+ * summariseMessage's blind slice(0,120) for recap messages specifically —
161
+ * that truncation was destroying the executive digest and prior context on
162
+ * any second compaction in the same run.
163
+ */
164
+ function compressRecap(msg) {
165
+ const lines = msg.content.split('\n').filter(l => l.trim().length > 0);
166
+ const body = lines.slice(1); // drop the "[Earlier conversation compacted...]" header
167
+ const lastThread = body.length > 0 ? body[body.length - 1] : '';
168
+ const rest = body.slice(0, -1);
169
+ const concept = extractConcept(rest);
170
+ const terms = extractTopTerms(rest, 5);
171
+ return [
172
+ `Concept: ${concept}`,
173
+ terms.length > 0 ? `Terms: ${terms.join(', ')}` : '',
174
+ lastThread ? `Last thread: ${lastThread}` : '',
175
+ ].filter(Boolean).join(' · ');
81
176
  }
82
177
  function summariseMessage(msg) {
83
178
  switch (msg.role) {
@@ -94,56 +189,65 @@ function summariseMessage(msg) {
94
189
  }
95
190
  }
96
191
  }
97
- /**
98
- * Compact conversation history when context usage crosses ~70% of the model's
99
- * window. Keeps the first message (task) and the most recent messages
100
- * verbatim; replaces the middle with a recap.
101
- *
102
- * Mutates `history` in place (clears and re-fills) so callers that hold a
103
- * shared reference see the compacted version without reassignment.
104
- *
105
- * Returns `true` if compaction happened.
106
- */
107
- function compactHistory(history, totalTokens, model) {
192
+ function compactHistory(history, totalTokens, model, extras) {
193
+ const generation = getRecapGeneration(history);
108
194
  const window = (0, factory_js_1.getContextWindow)(model) ?? DEFAULT_WINDOW;
109
- const threshold = Math.floor(window * COMPACTION_THRESHOLD);
195
+ const threshold = Math.floor(window * thresholdRatio(generation));
110
196
  if (totalTokens < threshold)
111
197
  return false;
112
- const MIN_KEEP = 2 + PRESERVE_RECENT;
113
- if (history.length <= MIN_KEEP)
198
+ if (history.length <= 3)
114
199
  return false;
115
- // Prefer preserving the ENTIRE most recent user turn intact — search
116
- // backward for the last 'user' message (excluding the original task at
117
- // index 0). A forward-only search from an arbitrary message-count
118
- // boundary can miss this entirely, since the most recent user turn may
119
- // start well before that boundary — confirmed by testing a multi-turn
120
- // session where a forward search found nothing and fell through to
121
- // collapsing the whole session, discarding a real, recent instruction.
122
- let keepFrom = -1;
200
+ // Keep-boundary by token budget: walk backward accumulating message sizes
201
+ // until the verbatim tail would exceed RETENTION_RATIO of the window. The
202
+ // last message is always kept even if it alone busts the budget.
203
+ const retainBudget = Math.floor(window * RETENTION_RATIO);
204
+ let acc = 0;
205
+ let keepFrom = history.length;
123
206
  for (let i = history.length - 1; i >= 1; i--) {
207
+ const cost = countMessage(history[i]);
208
+ if (acc + cost > retainBudget && keepFrom < history.length)
209
+ break;
210
+ acc += cost;
211
+ keepFrom = i;
212
+ }
213
+ if (keepFrom >= history.length)
214
+ keepFrom = history.length - 1;
215
+ // Snap the boundary to the start of a user turn when one is nearby, so the
216
+ // kept slice opens with user context and tool_use/tool_result pairs stay
217
+ // intact (a user message never sits between a tool_use and its result).
218
+ let snapped = -1;
219
+ for (let i = keepFrom; i >= Math.max(1, keepFrom - 6); i--) {
124
220
  if (history[i].role === 'user') {
125
- keepFrom = i;
221
+ snapped = i;
126
222
  break;
127
223
  }
128
224
  }
129
- if (keepFrom === -1) {
130
- // No later user turn exists (e.g. still mid-task, only the original
131
- // instruction so far) — fall back to keeping roughly the last
132
- // PRESERVE_RECENT messages, walked forward to avoid landing on an
133
- // orphaned tool_result or directly after the assistant-role recap.
134
- keepFrom = Math.max(1, history.length - PRESERVE_RECENT);
135
- while (keepFrom < history.length &&
136
- (history[keepFrom].role === 'tool_result' || history[keepFrom].role === 'assistant'))
225
+ if (snapped !== -1) {
226
+ keepFrom = snapped;
227
+ }
228
+ else {
229
+ // No user turn nearby (e.g. one long tool-heavy stretch, like the old
230
+ // FALLBACK_KEEP case) never let the kept slice START with a
231
+ // tool_result whose tool_use was compacted away.
232
+ keepFrom = Math.max(keepFrom, Math.max(1, history.length - FALLBACK_KEEP));
233
+ while (keepFrom < history.length - 1 &&
234
+ history[keepFrom].role === 'tool_result')
137
235
  keepFrom++;
138
236
  }
139
237
  const toCompact = history.slice(1, keepFrom);
140
- if (toCompact.length === 0)
141
- return false;
142
- const summaries = toCompact.map(summariseMessage);
238
+ if (toCompact.length === 0) {
239
+ // Nothing droppable (the over-budget content IS the recent tail) — the
240
+ // churn guard is the only lever left.
241
+ return truncateOversizedResults(history, threshold);
242
+ }
243
+ const summaries = toCompact.map(msg => isRecap(msg) ? compressRecap(msg) : summariseMessage(msg));
244
+ const newGeneration = generation + 1;
143
245
  const recap = {
144
246
  role: 'assistant',
145
247
  content: [
146
- `[Earlier conversation compacted: ${toCompact.length} turns removed to stay within context limits.]`,
248
+ `${RECAP_MARKER} (gen ${newGeneration}): ${toCompact.length} turns removed to stay within context limits.]`,
249
+ ...(extras?.affectHint ? [extras.affectHint] : []),
250
+ ...(extras?.executiveDigest ? ['', extras.executiveDigest, ''] : []),
147
251
  ...summaries,
148
252
  ].join('\n'),
149
253
  };
@@ -151,6 +255,33 @@ function compactHistory(history, totalTokens, model) {
151
255
  history.length = 0;
152
256
  for (const msg of preserved)
153
257
  history.push(msg);
258
+ // Churn guard (see truncateOversizedResults).
259
+ truncateOversizedResults(history, threshold);
154
260
  return true;
155
261
  }
262
+ /**
263
+ * Churn guard: a single oversized tool_result in the kept tail can hold the
264
+ * estimate above the trigger forever, so compaction would fire every turn
265
+ * with no shrinkage. If the history is still over threshold, cap result
266
+ * bodies. Returns true if anything was truncated.
267
+ */
268
+ function truncateOversizedResults(history, threshold) {
269
+ let total = 0;
270
+ for (const msg of history)
271
+ total += countMessage(msg);
272
+ if (total < threshold)
273
+ return false;
274
+ let truncated = false;
275
+ for (const msg of history) {
276
+ if (msg.role !== 'tool_result')
277
+ continue;
278
+ for (const r of msg.results) {
279
+ if (r.content && r.content.length > MAX_RESULT_CHARS) {
280
+ r.content = r.content.slice(0, MAX_RESULT_CHARS) + '\n[truncated during compaction]';
281
+ truncated = true;
282
+ }
283
+ }
284
+ }
285
+ return truncated;
286
+ }
156
287
  //# sourceMappingURL=compactor.js.map