@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,307 @@
1
+ // Anti-AI-Slop Detector (Vision §7, ENFORCED)
2
+ //
3
+ // WHY THIS EXISTS
4
+ // The anti-slop mandate has been prompt-only: PACK_PLAYBOOKS.web tells the model
5
+ // what not to do, and the web guard in agentLoop.js forces it to LOOK at the
6
+ // result. But the model was the only judge of what it saw, and a model that just
7
+ // produced a purple gradient hero does not reliably call its own work generic.
8
+ // This module is the second opinion — a static read of the HTML/CSS the agent
9
+ // actually wrote.
10
+ //
11
+ // DESIGN RULE: WEIGHTED, NEVER A SINGLE TRIPWIRE
12
+ // A purple gradient is not automatically slop; a purple gradient PLUS emoji
13
+ // icons PLUS "Elevate your workflow" PLUS default fonts is. Every signal carries
14
+ // a weight and the verdict is a threshold, because a guard with false positives
15
+ // gets disabled — the same reasoning .agents/rules.json ZM-09 applies to the
16
+ // malware guards.
17
+ //
18
+ // Plaintext by construction: clear names, readable regex, no encoded strings,
19
+ // no shell, no dependency (Vision §10's rule for defence code applies equally to
20
+ // quality code that has to be argued with).
21
+
22
+ /** Files worth scanning. Anything else is not a rendered surface. */
23
+ const SCANNABLE_EXTENSIONS = /\.(html?|css|jsx?|tsx?|vue|svelte|astro)$/i;
24
+
25
+ export function isScannableFile(filePath) {
26
+ return SCANNABLE_EXTENSIONS.test(String(filePath || ''));
27
+ }
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Colour helpers
31
+ // ---------------------------------------------------------------------------
32
+
33
+ function hexToHue(hex) {
34
+ const clean = hex.replace('#', '');
35
+ const full = clean.length === 3
36
+ ? clean.split('').map(c => c + c).join('')
37
+ : clean;
38
+ if (full.length !== 6) return null;
39
+
40
+ const r = parseInt(full.slice(0, 2), 16) / 255;
41
+ const g = parseInt(full.slice(2, 4), 16) / 255;
42
+ const b = parseInt(full.slice(4, 6), 16) / 255;
43
+ const max = Math.max(r, g, b);
44
+ const min = Math.min(r, g, b);
45
+ const delta = max - min;
46
+ if (delta === 0) return null; // greyscale has no hue to judge
47
+
48
+ let hue;
49
+ if (max === r) hue = ((g - b) / delta) % 6;
50
+ else if (max === g) hue = (b - r) / delta + 2;
51
+ else hue = (r - g) / delta + 4;
52
+
53
+ hue = Math.round(hue * 60);
54
+ return hue < 0 ? hue + 360 : hue;
55
+ }
56
+
57
+ /** Indigo through violet — the band the AI house style lives in. */
58
+ function isPurpleBlueHue(hue) {
59
+ return hue !== null && hue >= 220 && hue <= 290;
60
+ }
61
+
62
+ // ---------------------------------------------------------------------------
63
+ // Signals
64
+ //
65
+ // Each returns null, or a finding: { id, weight, evidence, fix }.
66
+ // ---------------------------------------------------------------------------
67
+
68
+ /**
69
+ * The exact hex pairs that recur across AI-generated pages. These are not
70
+ * "purple-ish" — they are the specific values that appear verbatim in
71
+ * thousands of generated templates, which is why they score highest.
72
+ */
73
+ const NOTORIOUS_GRADIENT_COLORS = [
74
+ '#667eea', '#764ba2', // the single most reproduced pair on the web
75
+ '#6366f1', '#8b5cf6', // indigo-500 / violet-500 straight from the Tailwind ramp
76
+ '#a855f7', '#7c3aed',
77
+ '#4f46e5', '#9333ea'
78
+ ];
79
+
80
+ function detectNotoriousGradient(text) {
81
+ const lower = text.toLowerCase();
82
+ const hits = NOTORIOUS_GRADIENT_COLORS.filter(color => lower.includes(color));
83
+ if (hits.length < 2) return null;
84
+ return {
85
+ id: 'notorious-gradient-hexes',
86
+ weight: 40,
87
+ evidence: `the stock AI gradient colours ${hits.slice(0, 4).join(', ')} appear verbatim`,
88
+ fix: 'Replace with a palette chosen for THIS project. These specific hexes are the most reproduced values in AI-generated design.'
89
+ };
90
+ }
91
+
92
+ /** A linear-gradient whose stops all sit in the indigo→violet band. */
93
+ function detectPurpleBlueGradient(text) {
94
+ const gradients = text.match(/linear-gradient\s*\([^)]*\)/gi) || [];
95
+ for (const gradient of gradients) {
96
+ const hexes = gradient.match(/#[0-9a-f]{3,8}\b/gi) || [];
97
+ if (hexes.length < 2) continue;
98
+ const hues = hexes.map(hexToHue).filter(hue => hue !== null);
99
+ if (hues.length >= 2 && hues.every(isPurpleBlueHue)) {
100
+ return {
101
+ id: 'purple-blue-gradient',
102
+ weight: 30,
103
+ evidence: `a gradient runs entirely through the indigo/violet band: ${gradient.slice(0, 80)}`,
104
+ fix: 'Vision §7 bans purple-to-blue as the primary scheme. Commit to a design movement and take the palette from there.'
105
+ };
106
+ }
107
+ }
108
+ return null;
109
+ }
110
+
111
+ /** Marketing filler that says nothing about the actual product. */
112
+ const GENERIC_COPY_PATTERNS = [
113
+ /\belevate your\b/i,
114
+ /\btransform your\b/i,
115
+ /\btake your \w+ to the next level\b/i,
116
+ /\bunlock the (full )?power of\b/i,
117
+ /\bsupercharge your\b/i,
118
+ /\brevolutioni[sz]e (your|the)\b/i,
119
+ /\bseamlessly integrate\b/i,
120
+ /\blorem ipsum\b/i,
121
+ /\byour (one[- ]stop|all[- ]in[- ]one) (shop|solution|platform)\b/i,
122
+ /\bempower(ing)? (your|teams?) \w+\b/i
123
+ ];
124
+
125
+ function detectGenericCopy(text) {
126
+ const hits = GENERIC_COPY_PATTERNS
127
+ .map(pattern => text.match(pattern))
128
+ .filter(Boolean)
129
+ .map(match => match[0]);
130
+ if (hits.length === 0) return null;
131
+ return {
132
+ id: 'generic-marketing-copy',
133
+ weight: hits.length >= 2 ? 25 : 15,
134
+ evidence: `filler copy: ${[...new Set(hits)].slice(0, 3).map(h => `"${h}"`).join(', ')}`,
135
+ fix: 'Write copy about what this specific product actually does. Placeholder marketing language is a slop signature in itself.'
136
+ };
137
+ }
138
+
139
+ /** Emoji standing in for a designed icon set. */
140
+ function detectEmojiIcons(text) {
141
+ // The recurring "feature card" emoji vocabulary, not emoji in general.
142
+ const iconEmoji = text.match(/[\u{1F680}\u{2728}\u{1F4A1}\u{1F3AF}\u{26A1}\u{1F512}\u{1F4C8}\u{1F527}\u{1F31F}\u{1F4AA}]/gu) || [];
143
+ if (iconEmoji.length < 3) return null;
144
+ return {
145
+ id: 'emoji-as-icons',
146
+ weight: 20,
147
+ evidence: `${iconEmoji.length} placeholder-icon emoji (${[...new Set(iconEmoji)].slice(0, 5).join(' ')})`,
148
+ fix: 'Use a real icon set, or inline SVG drawn for this project. Rocket/sparkle/bulb emoji as feature icons is a template tell.'
149
+ };
150
+ }
151
+
152
+ /** The shadow that ships with every generated card. */
153
+ function detectStockShadow(text) {
154
+ const shadows = text.match(/box-shadow\s*:\s*0\s+(?:10|20)px\s+(?:20|25|30|40)px\s+rgba\(\s*0\s*,\s*0\s*,\s*0\s*,\s*0?\.\d+\s*\)/gi) || [];
155
+ if (shadows.length === 0) return null;
156
+ return {
157
+ id: 'stock-soft-shadow',
158
+ weight: 10,
159
+ evidence: `the default soft card shadow appears ${shadows.length}×`,
160
+ fix: 'Shadow should follow the movement you chose — brutalism has none, editorial uses hairline rules, deco uses hard offsets.'
161
+ };
162
+ }
163
+
164
+ /**
165
+ * No real typography decision was made.
166
+ *
167
+ * Only meaningful for documents that actually declare styling — a stylesheet
168
+ * fragment that sets no font is not evidence of anything.
169
+ */
170
+ function detectDefaultTypography(text) {
171
+ const declaresFontFamily = /font-family\s*:/i.test(text);
172
+ if (!declaresFontFamily) return null;
173
+
174
+ const loadsWebFont = /@font-face|fonts\.googleapis\.com|fonts\.bunny\.net|@import\s+url\([^)]*font/i.test(text);
175
+ if (loadsWebFont) return null;
176
+
177
+ const onlySystemStacks = !/font-family\s*:\s*[^;]*['"][A-Za-z]/i.test(text);
178
+ if (!onlySystemStacks) return null;
179
+
180
+ return {
181
+ id: 'default-typography',
182
+ weight: 15,
183
+ evidence: 'every font-family resolves to a system default; no typeface was chosen',
184
+ fix: 'Pick a typeface that belongs to the design movement. Typography is the fastest way out of the house style.'
185
+ };
186
+ }
187
+
188
+ /** Hero + exactly three feature cards + closing CTA: the generated-SaaS skeleton. */
189
+ function detectCookieCutterStructure(text) {
190
+ if (!/<html|<body|<section|<main/i.test(text)) return null;
191
+
192
+ const hasCenteredHero = /class\s*=\s*["'][^"']*\bhero\b/i.test(text) &&
193
+ /text-align\s*:\s*center|\btext-center\b/i.test(text);
194
+ const featureCards = (text.match(/class\s*=\s*["'][^"']*\b(feature|card)\b/gi) || []).length;
195
+ const hasCtaPair = /get started/i.test(text) && /learn more/i.test(text);
196
+
197
+ const signals = [hasCenteredHero, featureCards >= 3, hasCtaPair].filter(Boolean).length;
198
+ if (signals < 2) return null;
199
+
200
+ return {
201
+ id: 'cookie-cutter-layout',
202
+ weight: signals === 3 ? 25 : 15,
203
+ evidence: `the stock landing skeleton: ${hasCenteredHero ? 'centered hero, ' : ''}${featureCards >= 3 ? `${featureCards} feature cards, ` : ''}${hasCtaPair ? '"Get Started"/"Learn More" pair' : ''}`.replace(/,\s*$/, ''),
204
+ fix: 'Vision §7 bans the centered-hero + gradient + "Get Started" template. Restructure around the content this page actually has.'
205
+ };
206
+ }
207
+
208
+ /** Gradient text — a purple gradient clipped to a headline. */
209
+ function detectGradientText(text) {
210
+ if (!/background-clip\s*:\s*text|-webkit-background-clip\s*:\s*text/i.test(text)) return null;
211
+ const hasPurpleGradient = detectPurpleBlueGradient(text) || detectNotoriousGradient(text);
212
+ if (!hasPurpleGradient) return null;
213
+ return {
214
+ id: 'gradient-clipped-headline',
215
+ weight: 15,
216
+ evidence: 'a headline is filled with the same purple gradient',
217
+ fix: 'Gradient-filled display type over a purple ramp is the compound form of the signature. Use solid ink and let layout carry the emphasis.'
218
+ };
219
+ }
220
+
221
+ const SIGNALS = [
222
+ detectNotoriousGradient,
223
+ detectPurpleBlueGradient,
224
+ detectGenericCopy,
225
+ detectEmojiIcons,
226
+ detectStockShadow,
227
+ detectDefaultTypography,
228
+ detectCookieCutterStructure,
229
+ detectGradientText
230
+ ];
231
+
232
+ /**
233
+ * Score one document.
234
+ *
235
+ * @returns {{score: number, verdict: string, findings: Array}}
236
+ * verdict: 'clean' | 'borderline' | 'slop'
237
+ */
238
+ export function analyzeSource(text) {
239
+ const source = typeof text === 'string' ? text : '';
240
+ const findings = [];
241
+
242
+ for (const signal of SIGNALS) {
243
+ try {
244
+ const finding = signal(source);
245
+ if (finding) findings.push(finding);
246
+ } catch (e) { /* one bad signal must not sink the analysis */ }
247
+ }
248
+
249
+ const score = findings.reduce((sum, finding) => sum + finding.weight, 0);
250
+ return { score, verdict: verdictFor(score), findings };
251
+ }
252
+
253
+ // A single mid-weight signal is a stylistic choice. Two or three compounding is
254
+ // the house style. The thresholds are deliberately not tight.
255
+ export const BORDERLINE_SCORE = 30;
256
+ export const SLOP_SCORE = 45;
257
+
258
+ export function verdictFor(score) {
259
+ if (score >= SLOP_SCORE) return 'slop';
260
+ if (score >= BORDERLINE_SCORE) return 'borderline';
261
+ return 'clean';
262
+ }
263
+
264
+ /**
265
+ * Score a set of files. Findings are merged across the set, because the
266
+ * gradient usually lives in the CSS and the copy in the HTML.
267
+ */
268
+ export function analyzeFiles(files) {
269
+ const merged = new Map();
270
+ const scanned = [];
271
+
272
+ for (const file of files || []) {
273
+ if (!file || !isScannableFile(file.path)) continue;
274
+ scanned.push(file.path);
275
+ for (const finding of analyzeSource(file.content).findings) {
276
+ // Keep the highest-weight instance of each signal, and remember where.
277
+ const existing = merged.get(finding.id);
278
+ if (!existing || finding.weight > existing.weight) {
279
+ merged.set(finding.id, { ...finding, path: file.path });
280
+ }
281
+ }
282
+ }
283
+
284
+ const findings = [...merged.values()];
285
+ const score = findings.reduce((sum, finding) => sum + finding.weight, 0);
286
+ return { score, verdict: verdictFor(score), findings, scanned };
287
+ }
288
+
289
+ /** Render findings as the corrective message the agent is handed. */
290
+ export function formatFindings(result, movementBrief = '') {
291
+ const lines = [];
292
+ lines.push(`[ANTI-SLOP GATE: the generated design scores ${result.score} (${result.verdict}) against Vision §7.]`);
293
+ lines.push('');
294
+ lines.push('Detected signatures:');
295
+ for (const finding of result.findings) {
296
+ lines.push(`- ${finding.id}${finding.path ? ` (${finding.path})` : ''}: ${finding.evidence}`);
297
+ lines.push(` → ${finding.fix}`);
298
+ }
299
+ if (movementBrief) {
300
+ lines.push('');
301
+ lines.push('Commit to ONE of these movements and rebuild the visual layer from it:');
302
+ lines.push(movementBrief);
303
+ }
304
+ lines.push('');
305
+ lines.push('Rework the design and verify again. Do not simply swap one gradient for another.');
306
+ return lines.join('\n');
307
+ }
@@ -0,0 +1,45 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import { causyneHome } from './utils/paths.js';
5
+
6
+ const BUFFER_DIR = path.join(causyneHome(), 'buffers');
7
+
8
+ export class TaskBuffer {
9
+ constructor() {
10
+ this.ensureDir();
11
+ }
12
+
13
+ ensureDir() {
14
+ if (!fs.existsSync(BUFFER_DIR)) {
15
+ fs.mkdirSync(BUFFER_DIR, { recursive: true });
16
+ }
17
+ }
18
+
19
+ getBufferPath(taskId) {
20
+ return path.join(BUFFER_DIR, `${taskId}.jsonl`);
21
+ }
22
+
23
+ async appendOutput(taskId, stepData) {
24
+ const file = this.getBufferPath(taskId);
25
+ const entry = JSON.stringify({ ts: Date.now(), ...stepData }) + '\n';
26
+ fs.appendFileSync(file, entry);
27
+ }
28
+
29
+ async getBuffer(taskId) {
30
+ const file = this.getBufferPath(taskId);
31
+ if (!fs.existsSync(file)) return [];
32
+
33
+ const content = fs.readFileSync(file, 'utf8');
34
+ return content.split('\n')
35
+ .filter(l => l.trim())
36
+ .map(l => JSON.parse(l));
37
+ }
38
+
39
+ async clearBuffer(taskId) {
40
+ const file = this.getBufferPath(taskId);
41
+ if (fs.existsSync(file)) {
42
+ fs.unlinkSync(file);
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,241 @@
1
+
2
+
3
+ const PACK_SIGNALS = {
4
+ game: {
5
+ strong: ['game', 'gamedev', 'permainan', 'godot', 'unity', 'unreal', 'rpg', 'platformer', 'roguelike', 'arcade', 'voxel', 'minecraft', 'tilemap', 'spritesheet', 'boss fight', 'level design'],
6
+ weak: ['sprite', 'player', 'enemy', 'quest', 'npc', 'gameplay', 'multiplayer', 'leaderboard']
7
+ },
8
+ web: {
9
+ strong: ['web app', 'webapp', 'website', 'situs', 'landing page', 'homepage', 'frontend', 'front-end', 'dashboard', 'portfolio', 'react', 'vue', 'svelte', 'angular', 'nextjs', 'next.js', 'nuxt', 'astro', 'tailwind', 'html', 'css'],
10
+ weak: ['web', 'ui', 'ux', 'page', 'halaman', 'responsive', 'browser', 'dom', 'component', 'layout', 'design', 'desain', 'tampilan']
11
+ },
12
+ media: {
13
+ strong: ['generate image', 'generate video', 'generate music', 'text to speech', 'voiceover', 'blender', 'davinci', 'resolve', 'render', '3d model', 'animation', 'animasi'],
14
+ weak: ['image', 'gambar', 'photo', 'foto', 'video', 'audio', 'sound', 'suara', 'music', 'musik', 'lagu', 'thumbnail', 'poster', 'logo', 'illustration']
15
+ },
16
+ system: {
17
+ strong: ['kill process', 'port', 'zip', 'unzip', 'tar', 'archive', 'clipboard', 'printer', 'print', 'cetak'],
18
+ weak: ['process', 'proses', 'compress', 'extract', 'ekstrak', 'open app', 'launch']
19
+ },
20
+ advanced: {
21
+ strong: ['pdf', 'forge a tool', 'custom tool', 'subagent', 'sub-agent', 'parallel agent'],
22
+ weak: ['symbols', 'ast', 'refactor', 'orchestrate', 'swarm']
23
+ }
24
+ };
25
+
26
+ const STRONG_WEIGHT = 3;
27
+ const WEAK_WEIGHT = 1;
28
+ const PACK_THRESHOLD = 3;
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // Task archetypes. Each one carries the phase plan the agent should follow and
32
+ // the tools that matter in each phase.
33
+ // ---------------------------------------------------------------------------
34
+
35
+ const ARCHETYPES = {
36
+ integrate: {
37
+ label: 'Build an application that integrates live external data',
38
+ match: {
39
+ strong: ['realtime', 'real-time', 'real time', 'api', 'rest', 'webhook', 'integration', 'integrasi', 'sync', 'sinkron', 'automation', 'automasi', 'otomasi', 'automate', 'otomatis', 'booking', 'tiket', 'ticket', 'calendar', 'kalender', 'jadwal', 'schedule', 'payment', 'pembayaran'],
40
+ weak: ['live', 'stream', 'fetch', 'data', 'third party', 'oauth', 'token']
41
+ },
42
+ phases: [
43
+ { name: 'Research the data sources FIRST', tools: ['web_search', 'web_fetch'], note: 'Identify the exact official REST APIs, their auth model, rate limits and response shape before writing any code. Prefer documented JSON APIs over browser scraping — reading JSON always beats reading DOM.' },
44
+ { name: 'Verify access and versions', tools: ['sysRun', 'web_fetch'], note: 'Probe one real endpoint and check current package versions with `npm view <pkg> version` before pinning anything. Never assume a version from memory.' },
45
+ { name: 'Scaffold', tools: ['fs_write', 'sysRun'], note: 'Create the project structure and dependency manifest.' },
46
+ { name: 'Implement', tools: ['fs_read', 'fs_edit', 'MultiEdit'], note: 'Build the integration layer first, the UI second. Handle auth failures and rate limits explicitly.' },
47
+ { name: 'Verify', tools: ['code_verify_syntax', 'sysRun'], note: 'Run the build, lint and type-check. Exercise the live data path end to end, not just the happy path.' }
48
+ ]
49
+ },
50
+ build_app: {
51
+ label: 'Build a new application from scratch',
52
+ match: {
53
+ strong: ['build me', 'buatkan', 'buat aku', 'create a', 'make me', 'bikin', 'develop a', 'new app', 'from scratch'],
54
+ weak: ['app', 'aplikasi', 'project', 'proyek', 'tool', 'system', 'sistem']
55
+ },
56
+ phases: [
57
+ { name: 'Plan', tools: ['TodoWrite'], note: 'Break the build into atomic, verifiable steps before touching the filesystem.' },
58
+ { name: 'Scaffold', tools: ['fs_write', 'sysRun'], note: 'Create structure and install dependencies at verified current versions.' },
59
+ { name: 'Implement', tools: ['fs_write', 'fs_edit', 'MultiEdit'], note: 'Write runnable code with every import and dependency present.' },
60
+ { name: 'Verify', tools: ['code_verify_syntax', 'sysRun'], note: 'Build, lint and run it. Never assume code works.' }
61
+ ]
62
+ },
63
+ debug: {
64
+ label: 'Diagnose and fix a defect in existing code',
65
+ match: {
66
+ strong: ['bug', 'error', 'crash', 'fix', 'perbaiki', 'benerin', 'broken', 'rusak', 'not working', 'gagal', 'failing', 'exception', 'stack trace', 'regression'],
67
+ weak: ['issue', 'masalah', 'problem', 'wrong', 'salah', 'debug']
68
+ },
69
+ phases: [
70
+ { name: 'Reproduce', tools: ['sysRun', 'fs_read'], note: 'Reproduce the failure and capture the exact error before changing anything.' },
71
+ { name: 'Locate', tools: ['code_search_ripgrep', 'codebase_search', 'fs_read'], note: 'Search several phrasings in parallel. Read the file before patching it.' },
72
+ { name: 'Fix', tools: ['fs_edit', 'MultiEdit'], note: 'Make the smallest correct change. Do not touch unrelated code.' },
73
+ { name: 'Verify', tools: ['sysRun', 'code_verify_syntax'], note: 'Confirm the original reproduction now passes.' }
74
+ ]
75
+ },
76
+ research: {
77
+ label: 'Investigate and report, without changing the system',
78
+ match: {
79
+ strong: ['explain', 'jelaskan', 'analyse', 'analyze', 'analisa', 'research', 'riset', 'compare', 'bandingkan', 'what is', 'apa itu', 'how does', 'bagaimana', 'review', 'audit'],
80
+ weak: ['understand', 'pahami', 'find out', 'cari tahu', 'summarize', 'ringkas']
81
+ },
82
+ phases: [
83
+ { name: 'Gather', tools: ['code_search_ripgrep', 'fs_read', 'web_search', 'web_fetch'], note: 'Search broadly with several phrasings in parallel before concluding.' },
84
+ { name: 'Report', tools: [], note: 'Answer concisely and cite exact file paths or URLs. Do not modify anything unless asked.' }
85
+ ]
86
+ },
87
+ maintain: {
88
+ label: 'Operate on files, processes or the machine itself',
89
+ match: {
90
+ strong: ['organize', 'rapikan', 'clean up', 'bersihkan', 'rename', 'ganti nama', 'move', 'pindahkan', 'backup', 'cadangkan', 'delete', 'hapus', 'install', 'deploy', 'commit', 'merge'],
91
+ weak: ['folder', 'directory', 'file', 'berkas', 'desktop', 'downloads']
92
+ },
93
+ phases: [
94
+ { name: 'Survey', tools: ['fs_list', 'fs_find'], note: 'List and count what you are about to touch, and report it, before acting.' },
95
+ { name: 'Act', tools: ['fs_move', 'fs_write', 'sysRun'], note: 'Prefer reversible operations. Flag anything destructive for approval.' },
96
+ { name: 'Confirm', tools: ['fs_list'], note: 'Verify the end state matches what the user asked for.' }
97
+ ]
98
+ }
99
+ };
100
+
101
+ const ARCHETYPE_NAMES = Object.keys(ARCHETYPES);
102
+
103
+ // ---------------------------------------------------------------------------
104
+ // Scoring
105
+ // ---------------------------------------------------------------------------
106
+
107
+ function scoreSignals(text, signals) {
108
+ let score = 0;
109
+ const hits = [];
110
+
111
+ for (const term of signals.strong || []) {
112
+ if (text.includes(term)) {
113
+ score += STRONG_WEIGHT;
114
+ hits.push(term);
115
+ }
116
+ }
117
+ for (const term of signals.weak || []) {
118
+ // Word-boundary check for short weak terms so 'ui' cannot match 'build'
119
+ // and 'race' cannot match 'trace' — the exact class of false positive that
120
+ // used to drag ordinary tasks into game and web enforcement.
121
+ const pattern = new RegExp(`(^|[^a-z0-9])${term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}($|[^a-z0-9])`, 'i');
122
+ if (pattern.test(text)) {
123
+ score += WEAK_WEIGHT;
124
+ hits.push(term);
125
+ }
126
+ }
127
+
128
+ return { score, hits };
129
+ }
130
+
131
+ /**
132
+ * Analyse a request into the shape of the work it implies.
133
+ *
134
+ * @param {string} prompt
135
+ * @param {object} workspaceContext Optional perception output.
136
+ * @returns {{
137
+ * archetype: string,
138
+ * archetypeLabel: string,
139
+ * packs: Array<{name: string, score: number}>,
140
+ * phases: Array<object>,
141
+ * confidence: string
142
+ * }}
143
+ */
144
+ export function analyzeTask(prompt, workspaceContext = null) {
145
+ const text = String(prompt || '').toLowerCase();
146
+
147
+ // Which capability packs are worth their token cost for this request.
148
+ const packScores = [];
149
+ for (const packName of Object.keys(PACK_SIGNALS)) {
150
+ const { score } = scoreSignals(text, PACK_SIGNALS[packName]);
151
+ if (score >= PACK_THRESHOLD) {
152
+ packScores.push({ name: packName, score });
153
+ }
154
+ }
155
+
156
+ // Workspace evidence outranks keywords: a Godot project on disk is far more
157
+ // reliable than any phrasing in the prompt.
158
+ if (workspaceContext) {
159
+ const signals = [
160
+ workspaceContext.framework,
161
+ workspaceContext.projectType,
162
+ workspaceContext.designSystem,
163
+ Array.isArray(workspaceContext.keyFiles) ? workspaceContext.keyFiles.join(' ') : ''
164
+ ].filter(Boolean).join(' ').toLowerCase();
165
+
166
+ const bump = (name, amount) => {
167
+ const existing = packScores.find(p => p.name === name);
168
+ if (existing) existing.score += amount;
169
+ else packScores.push({ name, score: amount });
170
+ };
171
+
172
+ if (/godot|project\.godot|unity|\.tscn/.test(signals)) bump('game', 5);
173
+ if (/react|vue|svelte|next|nuxt|astro|angular|tailwind|vite|index\.html/.test(signals)) bump('web', 4);
174
+ }
175
+
176
+ packScores.sort((a, b) => b.score - a.score);
177
+
178
+ // Which archetype the work follows.
179
+ let bestArchetype = null;
180
+ let bestScore = 0;
181
+ for (const name of ARCHETYPE_NAMES) {
182
+ const { score } = scoreSignals(text, ARCHETYPES[name].match);
183
+ if (score > bestScore) {
184
+ bestScore = score;
185
+ bestArchetype = name;
186
+ }
187
+ }
188
+
189
+ // A build request that also talks about live external data is an integration
190
+ // job first and a build job second — that ordering is what stops the agent
191
+ // scaffolding a UI before it knows whether the API even exists.
192
+ const integrationScore = scoreSignals(text, ARCHETYPES.integrate.match).score;
193
+ if (integrationScore >= STRONG_WEIGHT && bestArchetype === 'build_app') {
194
+ bestArchetype = 'integrate';
195
+ bestScore = integrationScore;
196
+ }
197
+
198
+ const archetype = bestArchetype || 'build_app';
199
+ const confidence = bestScore >= 6 ? 'high' : (bestScore >= 3 ? 'medium' : 'low');
200
+
201
+ return {
202
+ archetype,
203
+ archetypeLabel: ARCHETYPES[archetype].label,
204
+ packs: packScores,
205
+ phases: ARCHETYPES[archetype].phases,
206
+ confidence
207
+ };
208
+ }
209
+
210
+ /**
211
+ * Render the analysis as a compact brief for the system prompt.
212
+ *
213
+ * This is the piece that turns "here are 28 tools" into "here is the order to
214
+ * use them in". It costs roughly 150-200 tokens and routinely saves whole
215
+ * exploratory steps, each of which costs thousands.
216
+ */
217
+ export function buildTaskBrief(shape, availableToolNames) {
218
+ if (!shape) return '';
219
+
220
+ const available = new Set(availableToolNames || []);
221
+ const lines = [];
222
+
223
+ lines.push('# TASK SHAPE (analysed from your request)');
224
+ lines.push(`Work type: ${shape.archetypeLabel}.`);
225
+ lines.push('Follow this sequence. Do not skip ahead — each phase de-risks the next.');
226
+
227
+ shape.phases.forEach((phase, index) => {
228
+ const tools = (phase.tools || []).filter(name => available.has(name));
229
+ const toolList = tools.length > 0 ? ` [${tools.join(', ')}]` : '';
230
+ lines.push(`${index + 1}. ${phase.name}${toolList} — ${phase.note}`);
231
+ });
232
+
233
+ lines.push('Tools outside this list remain fully available; search for them with tool_search when a phase genuinely needs one.');
234
+
235
+ return lines.join('\n');
236
+ }
237
+
238
+ /** Pack names above threshold, highest score first. */
239
+ export function packsFromShape(shape) {
240
+ return shape && Array.isArray(shape.packs) ? shape.packs.map(p => p.name) : [];
241
+ }