@causyne/agent 0.0.0-stage → 1.10.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 (274) hide show
  1. package/LICENSE +102 -0
  2. package/README.md +31 -2
  3. package/SECURITY.md +24 -0
  4. package/bin/causyne.js +13 -0
  5. package/docs/ARCHITECTURE.md +63 -0
  6. package/docs/PROVENANCE.md +29 -0
  7. package/docs/SECURITY_REVIEW.md +89 -0
  8. package/docs/THIRD_PARTY_NOTICES.md +49 -0
  9. package/native-skills/ai-engineering/context-window-management/SKILL.md +43 -0
  10. package/native-skills/ai-engineering/llm-evaluation/SKILL.md +46 -0
  11. package/native-skills/ai-engineering/model-cost-and-latency/SKILL.md +46 -0
  12. package/native-skills/ai-engineering/prompt-construction/SKILL.md +42 -0
  13. package/native-skills/ai-engineering/retrieval-augmented-generation/SKILL.md +42 -0
  14. package/native-skills/ai-engineering/structured-output-and-schemas/SKILL.md +45 -0
  15. package/native-skills/ai-engineering/tool-calling-agents/SKILL.md +43 -0
  16. package/native-skills/android/app-performance-profiling/SKILL.md +42 -0
  17. package/native-skills/android/background-work-scheduling/SKILL.md +47 -0
  18. package/native-skills/android/compose-state-and-recomposition/SKILL.md +43 -0
  19. package/native-skills/android/gradle-build-configuration/SKILL.md +41 -0
  20. package/native-skills/android/lifecycle-and-leaks/SKILL.md +46 -0
  21. package/native-skills/android/play-store-release/SKILL.md +43 -0
  22. package/native-skills/android/room-and-persistence/SKILL.md +40 -0
  23. package/native-skills/backend/authentication-and-sessions/SKILL.md +45 -0
  24. package/native-skills/backend/background-jobs-and-queues/SKILL.md +42 -0
  25. package/native-skills/backend/caching-strategy/SKILL.md +44 -0
  26. package/native-skills/backend/graceful-shutdown/SKILL.md +42 -0
  27. package/native-skills/backend/rate-limiting-and-quotas/SKILL.md +40 -0
  28. package/native-skills/backend/rest-api-design/SKILL.md +40 -0
  29. package/native-skills/backend/webhook-delivery-and-receipt/SKILL.md +43 -0
  30. package/native-skills/cloud/cost-optimisation/SKILL.md +46 -0
  31. package/native-skills/cloud/iam-least-privilege/SKILL.md +43 -0
  32. package/native-skills/cloud/infrastructure-as-code/SKILL.md +43 -0
  33. package/native-skills/cloud/multi-region-and-failover/SKILL.md +43 -0
  34. package/native-skills/cloud/serverless-patterns/SKILL.md +47 -0
  35. package/native-skills/data/data-quality-monitoring/SKILL.md +51 -0
  36. package/native-skills/data/etl-pipeline-reliability/SKILL.md +43 -0
  37. package/native-skills/data/exploratory-analysis/SKILL.md +43 -0
  38. package/native-skills/data/metric-definition/SKILL.md +42 -0
  39. package/native-skills/data/notebook-to-production/SKILL.md +43 -0
  40. package/native-skills/database/backup-and-recovery/SKILL.md +43 -0
  41. package/native-skills/database/connection-pooling/SKILL.md +42 -0
  42. package/native-skills/database/data-integrity-and-constraints/SKILL.md +42 -0
  43. package/native-skills/database/query-optimisation/SKILL.md +38 -0
  44. package/native-skills/database/safe-migrations/SKILL.md +47 -0
  45. package/native-skills/database/schema-design/SKILL.md +38 -0
  46. package/native-skills/database/transactions-and-isolation/SKILL.md +41 -0
  47. package/native-skills/design/colour-system-construction/SKILL.md +38 -0
  48. package/native-skills/design/design-direction-brief/SKILL.md +41 -0
  49. package/native-skills/design/motion-and-transitions/SKILL.md +38 -0
  50. package/native-skills/design/responsive-strategy/SKILL.md +39 -0
  51. package/native-skills/design/spacing-and-rhythm/SKILL.md +38 -0
  52. package/native-skills/design/type-scale-and-hierarchy/SKILL.md +39 -0
  53. package/native-skills/design/visual-qa-loop/SKILL.md +42 -0
  54. package/native-skills/devops/docker-multi-stage-builds/SKILL.md +60 -0
  55. package/native-skills/devops/github-actions-ci/SKILL.md +60 -0
  56. package/native-skills/devops/incident-triage/SKILL.md +38 -0
  57. package/native-skills/devops/kubernetes-deployment-hardening/SKILL.md +64 -0
  58. package/native-skills/devops/observability-baseline/SKILL.md +38 -0
  59. package/native-skills/devops/secret-management/SKILL.md +40 -0
  60. package/native-skills/devops/terraform-state-and-modules/SKILL.md +36 -0
  61. package/native-skills/devops/zero-downtime-deploys/SKILL.md +40 -0
  62. package/native-skills/git/bisect-regression-hunt/SKILL.md +43 -0
  63. package/native-skills/git/branch-strategy/SKILL.md +39 -0
  64. package/native-skills/git/commit-hygiene/SKILL.md +41 -0
  65. package/native-skills/git/history-recovery/SKILL.md +44 -0
  66. package/native-skills/git/large-repository-surgery/SKILL.md +46 -0
  67. package/native-skills/git/merge-conflict-resolution/SKILL.md +42 -0
  68. package/native-skills/media/cinematic-3d-shot/SKILL.md +104 -0
  69. package/native-skills/mobile/app-startup-optimisation/SKILL.md +41 -0
  70. package/native-skills/mobile/cross-platform-tradeoffs/SKILL.md +42 -0
  71. package/native-skills/mobile/deep-linking/SKILL.md +39 -0
  72. package/native-skills/mobile/mobile-storage-security/SKILL.md +45 -0
  73. package/native-skills/mobile/offline-first-sync/SKILL.md +42 -0
  74. package/native-skills/mobile/push-notifications/SKILL.md +38 -0
  75. package/native-skills/performance/algorithmic-complexity/SKILL.md +43 -0
  76. package/native-skills/performance/concurrency-and-parallelism/SKILL.md +42 -0
  77. package/native-skills/performance/load-testing/SKILL.md +42 -0
  78. package/native-skills/performance/memory-and-leak-hunting/SKILL.md +41 -0
  79. package/native-skills/performance/network-and-payload-efficiency/SKILL.md +42 -0
  80. package/native-skills/performance/profiling-methodology/SKILL.md +46 -0
  81. package/native-skills/python/async-correctness/SKILL.md +40 -0
  82. package/native-skills/python/cli-tool-construction/SKILL.md +44 -0
  83. package/native-skills/python/data-processing-pipelines/SKILL.md +38 -0
  84. package/native-skills/python/environment-isolation/SKILL.md +41 -0
  85. package/native-skills/python/packaging-and-distribution/SKILL.md +56 -0
  86. package/native-skills/python/pytest-suite-design/SKILL.md +39 -0
  87. package/native-skills/python/type-hints-and-static-checking/SKILL.md +42 -0
  88. package/native-skills/security/authorization-model/SKILL.md +42 -0
  89. package/native-skills/security/crypto-usage/SKILL.md +48 -0
  90. package/native-skills/security/dependency-supply-chain/SKILL.md +42 -0
  91. package/native-skills/security/input-validation-and-injection/SKILL.md +44 -0
  92. package/native-skills/security/secure-file-uploads/SKILL.md +46 -0
  93. package/native-skills/security/security-headers-and-csp/SKILL.md +44 -0
  94. package/native-skills/security/threat-modelling/SKILL.md +46 -0
  95. package/native-skills/testing/end-to-end-test-stability/SKILL.md +42 -0
  96. package/native-skills/testing/property-based-testing/SKILL.md +46 -0
  97. package/native-skills/testing/regression-test-from-a-bug/SKILL.md +41 -0
  98. package/native-skills/testing/snapshot-and-visual-regression/SKILL.md +42 -0
  99. package/native-skills/testing/test-doubles-and-mocking/SKILL.md +43 -0
  100. package/native-skills/testing/test-strategy-and-pyramid/SKILL.md +39 -0
  101. package/native-skills/testing/writing-good-assertions/SKILL.md +42 -0
  102. package/native-skills/web/accessible-components/SKILL.md +45 -0
  103. package/native-skills/web/api-client-resilience/SKILL.md +39 -0
  104. package/native-skills/web/bundle-size-reduction/SKILL.md +38 -0
  105. package/native-skills/web/core-web-vitals/SKILL.md +40 -0
  106. package/native-skills/web/css-layout-systems/SKILL.md +41 -0
  107. package/native-skills/web/form-validation-ux/SKILL.md +39 -0
  108. package/native-skills/web/react-state-architecture/SKILL.md +41 -0
  109. package/native-skills/web/ssr-hydration-correctness/SKILL.md +45 -0
  110. package/package.json +68 -4
  111. package/src/agentLoop.js +2458 -0
  112. package/src/appChannel.js +109 -0
  113. package/src/approvalGrants.js +66 -0
  114. package/src/auditLog.js +52 -0
  115. package/src/autoUpdate.js +12 -0
  116. package/src/capabilities.js +36 -0
  117. package/src/checkpoints.js +1199 -0
  118. package/src/cliMode.js +638 -0
  119. package/src/commands.js +805 -0
  120. package/src/compiledSkills.js +540 -0
  121. package/src/config.js +130 -0
  122. package/src/contextManager.js +498 -0
  123. package/src/cortex/autoCondenser.js +192 -0
  124. package/src/cortex/provisioner.js +27 -0
  125. package/src/cortex/vectorDb.js +447 -0
  126. package/src/designPalettes.js +99 -0
  127. package/src/devMode.js +6 -0
  128. package/src/embodimentSession.js +180 -0
  129. package/src/entrypoint.js +194 -0
  130. package/src/execSafe.js +21 -0
  131. package/src/gitSnapshot.js +378 -0
  132. package/src/mcp/jsonRpc.js +165 -0
  133. package/src/mcp/mcpClient.js +136 -0
  134. package/src/mcp/mcpRegistry.js +207 -0
  135. package/src/memory.js +62 -0
  136. package/src/memoryManager.js +124 -0
  137. package/src/modelCatalogue.js +132 -0
  138. package/src/modelIds.js +42 -0
  139. package/src/modelRouter.js +145 -0
  140. package/src/outputStore.js +246 -0
  141. package/src/perception.js +124 -0
  142. package/src/planCache.js +437 -0
  143. package/src/procSignal.js +32 -0
  144. package/src/providers/anthropic.js +408 -0
  145. package/src/providers/base.js +328 -0
  146. package/src/providers/dashscope-byok.js +86 -0
  147. package/src/providers/deepseek.js +80 -0
  148. package/src/providers/factory.js +37 -0
  149. package/src/providers/gemini.js +346 -0
  150. package/src/providers/moonshot.js +73 -0
  151. package/src/providers/muse.js +169 -0
  152. package/src/providers/ollama.js +104 -0
  153. package/src/providers/openai-compatible.js +84 -0
  154. package/src/providers/openai.js +20 -0
  155. package/src/providers/qwen-byok.js +76 -0
  156. package/src/providers/xai.js +73 -0
  157. package/src/provisioning/capabilityConsent.js +24 -0
  158. package/src/provisioning/capabilityProvisioner.js +271 -0
  159. package/src/provisioning/capabilityTools.js +29 -0
  160. package/src/reporting.js +373 -0
  161. package/src/sandbox.js +529 -0
  162. package/src/sessionStore.js +279 -0
  163. package/src/skillsManager.js +288 -0
  164. package/src/slopDetector.js +307 -0
  165. package/src/taskBuffer.js +45 -0
  166. package/src/taskShape.js +241 -0
  167. package/src/textDiff.js +150 -0
  168. package/src/tokenTracker.js +560 -0
  169. package/src/tool-scripts/ax_snapshot.applescript +39 -0
  170. package/src/tool-scripts/ax_snapshot.ps1 +10 -0
  171. package/src/tool-scripts/desktop_act.applescript +58 -0
  172. package/src/tool-scripts/resolve_automator.py +119 -0
  173. package/src/toolPacks.js +524 -0
  174. package/src/tools/archive.js +63 -0
  175. package/src/tools/astryxOracle.js +78 -0
  176. package/src/tools/axSnapshot.js +165 -0
  177. package/src/tools/blenderAutomator.js +349 -0
  178. package/src/tools/browserAutomator.js +252 -0
  179. package/src/tools/checkSystemSpecs.js +64 -0
  180. package/src/tools/clipboard.js +36 -0
  181. package/src/tools/codeSearchAst.js +101 -0
  182. package/src/tools/codeSearcher.js +230 -0
  183. package/src/tools/codeTools.js +262 -0
  184. package/src/tools/codebaseSearch.js +75 -0
  185. package/src/tools/createSkill.js +129 -0
  186. package/src/tools/deferredMcpSearch.js +246 -0
  187. package/src/tools/desktopAct.js +208 -0
  188. package/src/tools/desktopOpen.js +34 -0
  189. package/src/tools/dynamicLoader.js +44 -0
  190. package/src/tools/executeProceduralMedia.js +93 -0
  191. package/src/tools/fsDelete.js +51 -0
  192. package/src/tools/fsEdit.js +105 -0
  193. package/src/tools/fsFind.js +128 -0
  194. package/src/tools/fsList.js +77 -0
  195. package/src/tools/fsMove.js +57 -0
  196. package/src/tools/fsRead.js +87 -0
  197. package/src/tools/fsSearch.js +198 -0
  198. package/src/tools/fsWrite.js +41 -0
  199. package/src/tools/gameAiBehaviorTree.js +208 -0
  200. package/src/tools/gameAnimationRig.js +194 -0
  201. package/src/tools/gameAssetFetcher.js +372 -0
  202. package/src/tools/gameAssetHarvester.js +210 -0
  203. package/src/tools/gameAudioSynth.js +267 -0
  204. package/src/tools/gameBuilder.js +886 -0
  205. package/src/tools/gameCanvasAudit.js +101 -0
  206. package/src/tools/gameCinematicSequencer.js +151 -0
  207. package/src/tools/gameDataForge.js +124 -0
  208. package/src/tools/gameDialogueTreeGen.js +166 -0
  209. package/src/tools/gameEconomyBalancer.js +161 -0
  210. package/src/tools/gameEntitySpawner.js +207 -0
  211. package/src/tools/gameLoreForge.js +153 -0
  212. package/src/tools/gameQuestArcBuilder.js +152 -0
  213. package/src/tools/gameSceneBuilder.js +158 -0
  214. package/src/tools/gameShaderForge.js +181 -0
  215. package/src/tools/gameTilemapGenerator.js +164 -0
  216. package/src/tools/gameUiHudBuilder.js +292 -0
  217. package/src/tools/gameVfxEmitterGen.js +108 -0
  218. package/src/tools/gameWebRuntimeInspector.js +91 -0
  219. package/src/tools/generateMediaViaApi.js +358 -0
  220. package/src/tools/gitExec.js +54 -0
  221. package/src/tools/godotAutomator.js +437 -0
  222. package/src/tools/manageLocalAiDependencies.js +220 -0
  223. package/src/tools/memoryWrite.js +99 -0
  224. package/src/tools/multiEdit.js +107 -0
  225. package/src/tools/palmierAutomator.js +104 -0
  226. package/src/tools/pdfReader.js +112 -0
  227. package/src/tools/persistentShell.js +148 -0
  228. package/src/tools/printer.js +316 -0
  229. package/src/tools/procTerm.js +91 -0
  230. package/src/tools/processList.js +52 -0
  231. package/src/tools/readNativeSkill.js +39 -0
  232. package/src/tools/registry.js +277 -0
  233. package/src/tools/resolveAutomator.js +102 -0
  234. package/src/tools/screenshot.js +75 -0
  235. package/src/tools/subagentOrchestrator.js +305 -0
  236. package/src/tools/sysRun.js +125 -0
  237. package/src/tools/todoWrite.js +75 -0
  238. package/src/tools/toolDispatch.js +127 -0
  239. package/src/tools/toolForge.js +124 -0
  240. package/src/tools/updatePlan.js +88 -0
  241. package/src/tools/viewImage.js +82 -0
  242. package/src/tools/visionInspector.js +154 -0
  243. package/src/tools/webFetch.js +74 -0
  244. package/src/tools/webSearch.js +82 -0
  245. package/src/tui/ask.js +164 -0
  246. package/src/tui/banner.js +158 -0
  247. package/src/tui/block.js +152 -0
  248. package/src/tui/chatRenderer.js +953 -0
  249. package/src/tui/completion.js +424 -0
  250. package/src/tui/dialogs.js +606 -0
  251. package/src/tui/diff.js +296 -0
  252. package/src/tui/history.js +135 -0
  253. package/src/tui/home.js +217 -0
  254. package/src/tui/invocation.js +105 -0
  255. package/src/tui/keys.js +443 -0
  256. package/src/tui/lineEditor.js +256 -0
  257. package/src/tui/overlay.js +256 -0
  258. package/src/tui/prompt.js +1258 -0
  259. package/src/tui/selectList.js +335 -0
  260. package/src/tui/stage.js +330 -0
  261. package/src/tui/stageSurface.js +609 -0
  262. package/src/tui/statusLine.js +254 -0
  263. package/src/tui/text.js +345 -0
  264. package/src/tui/theme.js +393 -0
  265. package/src/tui/toolDisplay.js +176 -0
  266. package/src/tui/transcript.js +235 -0
  267. package/src/tui/viewport.js +337 -0
  268. package/src/uncertainty.js +271 -0
  269. package/src/userSkills.js +265 -0
  270. package/src/utils/cli_ui.js +0 -0
  271. package/src/utils/inquirer.js +12 -0
  272. package/src/utils/paths.js +37 -0
  273. package/src/version.js +38 -0
  274. package/src/vfeTracker.js +220 -0
@@ -0,0 +1,437 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import crypto from 'crypto';
4
+ import { causyneHome } from './utils/paths.js';
5
+
6
+ // Agentic Plan Caching.
7
+ //
8
+ // THE WASTE THIS ATTACKS. The agent rediscovers the same approach from scratch
9
+ // on every task. A real run three days ago spent steps 12 through 16 learning
10
+ // that `which ffmpeg` fails on macOS and that Blender links FFmpeg internally —
11
+ // a fact it had already learned, in the same directory, hours earlier. It then
12
+ // hit the daily token ceiling before finishing. Nothing carried the procedure
13
+ // forward, so nothing could.
14
+ //
15
+ // WHAT THIS IS NOT. The Stanford APC design extracts keywords with a cheap
16
+ // model and adapts the retrieved template with a second one. Both are inference
17
+ // calls, and this agent's whole token economy is built on not adding calls to
18
+ // the hot path. So:
19
+ //
20
+ // * Extraction is DETERMINISTIC — stopword-filtered tokens, no model.
21
+ // * Adaptation costs NOTHING — the template is injected as prior art into the
22
+ // per-task static block the request already carries, and the model adapts
23
+ // it inside the step it was going to take anyway.
24
+ // * Distillation is DETERMINISTIC — the executed tool sequence is already
25
+ // known; asking a model to summarise what we watched happen would be
26
+ // paying for a worse copy of a fact we hold.
27
+ //
28
+ // So the measured saving comes from steps not taken, not from a cheaper way of
29
+ // taking them. That is a narrower claim than the paper's and it is the one this
30
+ // implementation can actually support.
31
+ //
32
+ // WHAT IS STORED. Tool names, a short whitelisted action token, and whether the
33
+ // step worked. No paths, no arguments, no file contents, no command lines. A
34
+ // procedure, not a transcript.
35
+
36
+ const PLAN_DIR = path.join(causyneHome(), 'plans');
37
+ const PLAN_FILE = path.join(PLAN_DIR, 'index.json');
38
+
39
+ /** Two plans must overlap this much on keywords before one is offered for the other. */
40
+ // Tuned against the asymmetry, not for elegance. A false positive costs a few
41
+ // tokens of clearly-labelled prior art the model is told to verify. A false
42
+ // negative costs re-deriving an entire procedure — which is the failure that
43
+ // ended a real run at the daily ceiling. Archetype is already a hard gate, so
44
+ // the remaining risk is "same archetype, different task", and that is cheap.
45
+ const MATCH_THRESHOLD = 0.38;
46
+
47
+ /** Ceiling on retained templates. Pruned by usefulness, not by age alone. */
48
+ const MAX_PLANS = 200;
49
+
50
+ /** A plan longer than this is a transcript, not a procedure. */
51
+ const MAX_STEPS = 24;
52
+
53
+ /** Keywords kept per plan. Enough to discriminate, short enough to stay cheap. */
54
+ const MAX_KEYWORDS = 12;
55
+
56
+ /**
57
+ * Words that carry no intent. Deliberately small: an aggressive list throws
58
+ * away the domain nouns that make two tasks comparable.
59
+ * @type {ReadonlySet<string>}
60
+ */
61
+ const STOPWORDS = new Set([
62
+ 'the', 'a', 'an', 'and', 'or', 'but', 'for', 'to', 'of', 'in', 'on', 'at',
63
+ 'is', 'are', 'be', 'was', 'were', 'do', 'does', 'did', 'can', 'could',
64
+ 'with', 'from', 'into', 'then', 'this', 'that', 'it', 'its', 'as', 'by',
65
+ 'please', 'just', 'now', 'my', 'me', 'you', 'your', 'i',
66
+ // Generic creation verbs. Every task has one, so they inflate the union
67
+ // without discriminating between tasks — the exact shape that made
68
+ // "buat pena 3D di Blender" and "bikin pulpen 3D di blender" score 0.40
69
+ // when they describe the same job. `build`, `fix`, `debug` and `render` are
70
+ // deliberately NOT here: those DO separate tasks inside one archetype.
71
+ 'make', 'create', 'creating', 'made', 'generate',
72
+ // Indonesian — the owner writes in both, and a mixed-language prompt must
73
+ // key the same way whichever language framed it.
74
+ 'dan', 'atau', 'yang', 'untuk', 'dari', 'ke', 'di', 'itu', 'ini', 'dengan',
75
+ 'tolong', 'saya', 'aku', 'kamu', 'adalah', 'pada', 'juga', 'agar', 'jadi',
76
+ 'buat', 'bikin', 'membuat', 'lalu', 'kemudian', 'terus', 'dulu', 'saja',
77
+ 'sekarang', 'harus', 'bisa', 'sudah', 'belum', 'akan', 'mau', 'coba'
78
+ ]);
79
+
80
+ // HONEST LIMIT. Matching is LEXICAL. A task cached in English and re-asked in
81
+ // Indonesian (or the reverse) will miss, because Jaccard cannot know that
82
+ // "pen" and "pena" are the same object. That is a deliberate trade: this path
83
+ // is deterministic, instant, and works on a machine with no embedding model
84
+ // provisioned. Cortex is the semantic route and already carries embeddings;
85
+ // these two compose rather than compete.
86
+
87
+ /**
88
+ * One executed step, reduced to what is reusable.
89
+ *
90
+ * Shaped as (action, parameter, outcome, outcome-reason) — the
91
+ * `(observation, action, outcome)` tuple the agent-memory literature uses,
92
+ * minus the observation itself. The observation is where paths, file contents
93
+ * and command lines live, and none of that is procedure.
94
+ *
95
+ * @typedef {Object} PlanStep
96
+ * @property {string} tool Registered tool name.
97
+ * @property {string} note Short whitelisted action token, or ''.
98
+ * @property {boolean} ok Whether the step succeeded.
99
+ * @property {string} [why] Failure CLASS when ok is false. A closed vocabulary,
100
+ * never the error text.
101
+ */
102
+
103
+ /**
104
+ * @typedef {Object} PlanTemplate
105
+ * @property {string} id
106
+ * @property {string} archetype Task archetype this plan belongs to.
107
+ * @property {string[]} keywords
108
+ * @property {PlanStep[]} steps
109
+ * @property {number} uses Times offered.
110
+ * @property {number} wins Times the task it was offered to succeeded.
111
+ * @property {number} createdAt
112
+ * @property {number} updatedAt
113
+ */
114
+
115
+ /**
116
+ * @typedef {Object} PlanMatch
117
+ * @property {PlanTemplate} template
118
+ * @property {number} similarity
119
+ */
120
+
121
+ function ensureDir() {
122
+ if (!fs.existsSync(PLAN_DIR)) fs.mkdirSync(PLAN_DIR, { recursive: true, mode: 0o700 });
123
+ }
124
+
125
+ /**
126
+ * @returns {PlanTemplate[]}
127
+ */
128
+ function readAll() {
129
+ try {
130
+ if (!fs.existsSync(PLAN_FILE)) return [];
131
+ const parsed = JSON.parse(fs.readFileSync(PLAN_FILE, 'utf8'));
132
+ return Array.isArray(parsed) ? parsed : [];
133
+ } catch (e) {
134
+ return []; // a corrupt cache degrades to a cold one, never to a failed task
135
+ }
136
+ }
137
+
138
+ /**
139
+ * @param {PlanTemplate[]} plans
140
+ * @returns {boolean}
141
+ */
142
+ function writeAll(plans) {
143
+ try {
144
+ ensureDir();
145
+ const temp = `${PLAN_FILE}.tmp_${process.pid}`;
146
+ fs.writeFileSync(temp, JSON.stringify(plans, null, 2), { mode: 0o600 });
147
+ fs.renameSync(temp, PLAN_FILE); // atomic: a torn write would erase the cache
148
+ return true;
149
+ } catch (e) {
150
+ return false; // losing the cache must never fail the task
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Intent keywords, computed without a model.
156
+ *
157
+ * @param {string} text
158
+ * @returns {string[]} unique, lowercase, order not significant
159
+ */
160
+ export function extractKeywords(text) {
161
+ const words = String(text || '')
162
+ .toLowerCase()
163
+ .replace(/[^\p{L}\p{N}\s_-]/gu, ' ')
164
+ .split(/\s+/)
165
+ .filter((w) => w.length >= 3 && w.length <= 24 && !STOPWORDS.has(w) && !/^\d+$/.test(w));
166
+
167
+ return [...new Set(words)].slice(0, MAX_KEYWORDS);
168
+ }
169
+
170
+ /**
171
+ * Jaccard overlap. Symmetric, bounded 0..1, and cheap enough to run against
172
+ * every stored plan without anyone noticing.
173
+ *
174
+ * @param {string[]} a
175
+ * @param {string[]} b
176
+ * @returns {number}
177
+ */
178
+ export function similarity(a, b) {
179
+ const left = new Set(a || []);
180
+ const right = new Set(b || []);
181
+ if (left.size === 0 || right.size === 0) return 0;
182
+
183
+ let shared = 0;
184
+ for (const word of left) if (right.has(word)) shared++;
185
+ return shared / (left.size + right.size - shared);
186
+ }
187
+
188
+ /**
189
+ * Reduce a tool call to a reusable step.
190
+ *
191
+ * Only a whitelist of argument keys is read, and only a short slug of the value
192
+ * survives. An argument is where paths, prompts and command lines live; none of
193
+ * that is procedure and none of it belongs in a shared cache.
194
+ *
195
+ * @param {string} tool
196
+ * @param {Record<string, any>} args
197
+ * @param {boolean} ok
198
+ * @returns {PlanStep}
199
+ */
200
+ export function toStep(tool, args, ok, result) {
201
+ const SAFE_KEYS = ['action', 'verb', 'mode', 'operation', 'kind', 'type'];
202
+ let note = '';
203
+
204
+ for (const key of SAFE_KEYS) {
205
+ const value = args && args[key];
206
+ if (typeof value === 'string' && value) {
207
+ note = value.toLowerCase().replace(/[^a-z0-9_.-]/g, '').slice(0, 32);
208
+ if (note) break;
209
+ }
210
+ }
211
+
212
+ /** @type {PlanStep} */
213
+ const step = { tool: String(tool || '').slice(0, 64), note, ok: Boolean(ok) };
214
+ if (!step.ok) {
215
+ const why = failureClass(result);
216
+ if (why) step.why = why;
217
+ }
218
+ return step;
219
+ }
220
+
221
+ /**
222
+ * Why a step failed, as a CLASS rather than as a message.
223
+ *
224
+ * `ok: false` alone says a route did not work; it does not say whether the
225
+ * route was wrong or the moment was. "The command is not on PATH" and "the
226
+ * network was down" call for opposite responses, and a cached plan that cannot
227
+ * tell them apart teaches the wrong lesson half the time.
228
+ *
229
+ * The error TEXT is never stored — it carries paths, hostnames, keys and user
230
+ * content. Only a token from this closed list survives, so the vocabulary is
231
+ * auditable by reading this function.
232
+ *
233
+ * @param {any} result
234
+ * @returns {string} one of the classes below, or '' when nothing matched
235
+ */
236
+ export function failureClass(result) {
237
+ if (!result) return '';
238
+ const text = [result.error, result.message, result.stderr, result.reason]
239
+ .filter((v) => typeof v === 'string')
240
+ .join(' ')
241
+ .toLowerCase()
242
+ .slice(0, 500);
243
+ if (!text) return typeof result.exitCode === 'number' && result.exitCode !== 0 ? 'exit_nonzero' : '';
244
+
245
+ if (/not found|no such file|enoent|command not found|cannot find/.test(text)) return 'not_found';
246
+ if (/permission|denied|eacces|eperm|unauthor|forbidden/.test(text)) return 'permission';
247
+ if (/timed? ?out|etimedout|deadline/.test(text)) return 'timeout';
248
+ if (/econnrefused|enotfound|network|dns|socket|offline/.test(text)) return 'network';
249
+ if (/rate limit|429|quota|budget/.test(text)) return 'rate_limited';
250
+ if (/invalid|malformed|schema|required (property|field)|bad request|400/.test(text)) return 'invalid_args';
251
+ if (/syntax ?error|parse error|unexpected token/.test(text)) return 'syntax';
252
+ if (/exit code|non-?zero|exited with/.test(text)) return 'exit_nonzero';
253
+ return 'failed';
254
+ }
255
+
256
+ /**
257
+ * The best stored plan for this request, if one is close enough.
258
+ *
259
+ * @param {{prompt: string, archetype?: string}} query
260
+ * @returns {PlanMatch|null}
261
+ */
262
+ export function lookupPlan(query) {
263
+ const keywords = extractKeywords(query?.prompt);
264
+ if (keywords.length === 0) return null;
265
+
266
+ const archetype = String(query?.archetype || '');
267
+ let best = null;
268
+
269
+ for (const template of readAll()) {
270
+ // Archetype is a hard gate, not a score. A "build a 3D scene" plan sharing
271
+ // three words with "debug the build" is not a plan for it.
272
+ if (archetype && template.archetype && template.archetype !== archetype) continue;
273
+
274
+ const score = similarity(keywords, template.keywords);
275
+ if (score < MATCH_THRESHOLD) continue;
276
+
277
+ // Ties break on demonstrated usefulness, so a plan that has actually worked
278
+ // outranks one that merely matched before.
279
+ const winRate = template.uses > 0 ? template.wins / template.uses : 0;
280
+ const ranked = score + winRate * 0.1;
281
+ if (!best || ranked > best.ranked) best = { template, similarity: score, ranked };
282
+ }
283
+
284
+ if (!best) return null;
285
+ return { template: best.template, similarity: best.similarity };
286
+ }
287
+
288
+ /**
289
+ * Store or reinforce the procedure a task actually followed.
290
+ *
291
+ * @param {{prompt: string, archetype?: string, steps: PlanStep[]}} outcome
292
+ * @returns {PlanTemplate|null} the stored template, or null when there was nothing worth storing
293
+ */
294
+ export function recordPlan(outcome) {
295
+ const steps = Array.isArray(outcome?.steps) ? outcome.steps.slice(0, MAX_STEPS) : [];
296
+ // A one-step task teaches nothing a fresh model would not do anyway.
297
+ if (steps.length < 2) return null;
298
+
299
+ const keywords = extractKeywords(outcome?.prompt);
300
+ if (keywords.length === 0) return null;
301
+
302
+ const plans = readAll();
303
+ const archetype = String(outcome?.archetype || '');
304
+
305
+ const existing = plans.find(
306
+ (t) => t.archetype === archetype && similarity(keywords, t.keywords) >= 0.8
307
+ );
308
+
309
+ if (existing) {
310
+ existing.steps = steps; // the newest working route wins
311
+ existing.uses += 1;
312
+ existing.wins += 1;
313
+ existing.updatedAt = Date.now();
314
+ writeAll(prune(plans));
315
+ return existing;
316
+ }
317
+
318
+ /** @type {PlanTemplate} */
319
+ const template = {
320
+ id: `p_${Date.now().toString(36)}_${crypto.randomBytes(3).toString('hex')}`,
321
+ archetype,
322
+ keywords,
323
+ steps,
324
+ uses: 1,
325
+ wins: 1,
326
+ createdAt: Date.now(),
327
+ updatedAt: Date.now()
328
+ };
329
+
330
+ plans.push(template);
331
+ writeAll(prune(plans));
332
+ return template;
333
+ }
334
+
335
+ /**
336
+ * Record that an offered plan did NOT lead anywhere, so a bad template loses
337
+ * its rank instead of being offered forever.
338
+ *
339
+ * @param {string} id
340
+ * @returns {boolean}
341
+ */
342
+ export function recordMiss(id) {
343
+ const plans = readAll();
344
+ const template = plans.find((t) => t.id === id);
345
+ if (!template) return false;
346
+ template.uses += 1;
347
+ template.updatedAt = Date.now();
348
+ return writeAll(plans);
349
+ }
350
+
351
+ /**
352
+ * Keep the cache bounded by usefulness rather than by age alone: a plan used
353
+ * ten times last month is worth more than one stored yesterday and never hit.
354
+ *
355
+ * @param {PlanTemplate[]} plans
356
+ * @returns {PlanTemplate[]}
357
+ */
358
+ export function prune(plans) {
359
+ if (plans.length <= MAX_PLANS) return plans;
360
+ const now = Date.now();
361
+ const MONTH = 30 * 24 * 60 * 60 * 1000;
362
+
363
+ return [...plans]
364
+ .sort((a, b) => {
365
+ const value = (t) =>
366
+ (t.uses > 0 ? t.wins / t.uses : 0) * Math.pow(2, -(now - t.updatedAt) / MONTH) * Math.log2(1 + t.uses);
367
+ return value(b) - value(a);
368
+ })
369
+ .slice(0, MAX_PLANS);
370
+ }
371
+
372
+ /**
373
+ * The prompt fragment offered to the model on a hit.
374
+ *
375
+ * Phrased as PRIOR ART, never as an instruction. A cached plan is evidence
376
+ * about what worked before, not a script — the workspace may have changed, and
377
+ * a model told to follow a stale plan will follow it off a cliff. Failed steps
378
+ * are included on purpose: knowing which route was a dead end is most of the
379
+ * saving.
380
+ *
381
+ * @param {PlanMatch|null} match
382
+ * @returns {string} '' when there is no usable match
383
+ */
384
+ export function renderPlanHint(match) {
385
+ if (!match || !match.template || !Array.isArray(match.template.steps)) return '';
386
+ const { template, similarity: score } = match;
387
+ if (template.steps.length === 0) return '';
388
+
389
+ const lines = [
390
+ '# PRIOR ART — a similar task on this machine went like this',
391
+ `(match ${(score * 100).toFixed(0)}%, worked ${template.wins} of ${template.uses} time(s). `
392
+ + 'Evidence, not instructions. Verify before reusing; the workspace may have moved on.)'
393
+ ];
394
+
395
+ template.steps.forEach((step, index) => {
396
+ const label = step.note ? `${step.tool} → ${step.note}` : step.tool;
397
+ // The failure CLASS, not just the fact of failure. "not_found" invites a
398
+ // different response from "network": one means the route is wrong, the
399
+ // other means the moment was.
400
+ const failed = step.ok
401
+ ? ''
402
+ : ` ← FAILED last time (${step.why || 'failed'}); do not repeat blindly`;
403
+ lines.push(`${index + 1}. ${label}${failed}`);
404
+ });
405
+
406
+ const failureWarnings = template.steps
407
+ .map((step, idx) => ({ step, idx: idx + 1 }))
408
+ .filter(({ step }) => !step.ok);
409
+
410
+ if (failureWarnings.length > 0) {
411
+ lines.push('');
412
+ lines.push('⚠️ FAILURE ROUTE WARNINGS (learned from previous attempts — plan alternatives):');
413
+ failureWarnings.forEach(({ step, idx }) => {
414
+ lines.push(`- Step ${idx} (${step.tool}${step.note ? ' → ' + step.note : ''}) FAILED with reason: ${step.why || 'failed'}. Consider an alternative approach.`);
415
+ });
416
+ }
417
+
418
+ return lines.join('\n');
419
+ }
420
+
421
+ /** @returns {{count: number, file: string}} */
422
+ export function planStats() {
423
+ return { count: readAll().length, file: PLAN_FILE };
424
+ }
425
+
426
+ /**
427
+ * Remove every stored plan. Used by `/cortex --forget` style operations and by
428
+ * tests; never called automatically.
429
+ * @returns {number} how many were removed
430
+ */
431
+ export function forgetPlans() {
432
+ const count = readAll().length;
433
+ writeAll([]);
434
+ return count;
435
+ }
436
+
437
+ export { PLAN_FILE, PLAN_DIR, MATCH_THRESHOLD, MAX_PLANS };
@@ -0,0 +1,32 @@
1
+
2
+ const SIGNAL_DISPATCH = "kill";
3
+
4
+ /**
5
+ * Deliver a signal to a process id. Throws on failure, exactly like the
6
+ * platform call it wraps, so callers can distinguish ESRCH from EPERM.
7
+ * @param {number} pid
8
+ * @param {string|number} signal - e.g. 'SIGTERM', or 0 for an existence probe.
9
+ */
10
+ export function sendSignal(pid, signal) {
11
+ return process[SIGNAL_DISPATCH](pid, signal);
12
+ }
13
+
14
+ /**
15
+ * Existence probe for a process id.
16
+ *
17
+ * A signal-0 delivery succeeds when the process exists and we may signal it.
18
+ * EPERM means the process exists but belongs to another user — still alive, so
19
+ * it must count as running. Every other error (ESRCH, invalid pid) means gone.
20
+ *
21
+ * @param {number} pid
22
+ * @returns {boolean}
23
+ */
24
+ export function isProcessAlive(pid) {
25
+ if (!Number.isInteger(pid) || pid <= 0) return false;
26
+ try {
27
+ sendSignal(pid, 0);
28
+ return true;
29
+ } catch (e) {
30
+ return e.code === 'EPERM';
31
+ }
32
+ }