arkaos 4.21.0 → 4.23.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 (316) hide show
  1. package/README.md +1 -1
  2. package/THE-ARKAOS-GUIDE.md +72 -0
  3. package/VERSION +1 -1
  4. package/arka/SKILL.md +30 -7
  5. package/arka/skills/bootstrap-agent/SKILL.md +3 -2
  6. package/arka/skills/flow/SKILL.md +3 -2
  7. package/arka/skills/forge/SKILL.md +3 -2
  8. package/arka/skills/recipes/SKILL.md +3 -2
  9. package/arka/skills/refine/SKILL.md +3 -2
  10. package/arka/skills/research/SKILL.md +3 -2
  11. package/bin/arka-doctor +8 -0
  12. package/config/claude-agents/marta-cqo.md +9 -0
  13. package/config/standards/{claude-md-overlays → stack-rules}/laravel.md +11 -0
  14. package/config/standards/{claude-md-overlays → stack-rules}/node.md +10 -1
  15. package/config/standards/{claude-md-overlays → stack-rules}/nuxt.md +12 -1
  16. package/config/standards/stack-rules/php.md +13 -0
  17. package/config/standards/{claude-md-overlays → stack-rules}/python.md +5 -0
  18. package/config/standards/stack-rules/react.md +14 -0
  19. package/config/standards/stack-rules/vue.md +13 -0
  20. package/core/cognition/evolve.py +299 -0
  21. package/core/cognition/evolve_cli.py +55 -0
  22. package/core/cognition/insights/store.py +15 -4
  23. package/core/forge/persistence.py +27 -9
  24. package/core/forge/schema.py +45 -22
  25. package/core/governance/evidence_checks.py +101 -9
  26. package/core/governance/kb_cite_check.py +16 -6
  27. package/core/governance/stop_lint.py +293 -0
  28. package/core/governance/stop_lint_telemetry.py +141 -0
  29. package/core/governance/tool_loop_check.py +111 -0
  30. package/core/hooks/stop.py +73 -22
  31. package/core/hooks/user_prompt_submit.py +21 -23
  32. package/core/runtime/pricing.py +53 -19
  33. package/core/synapse/engine.py +46 -16
  34. package/core/synapse/graph_context_layer.py +7 -2
  35. package/core/synapse/kb_cache.py +63 -16
  36. package/core/synapse/layers.py +131 -495
  37. package/core/synapse/layers_base.py +115 -0
  38. package/core/synapse/layers_kb.py +701 -0
  39. package/core/sync/content_syncer.py +79 -10
  40. package/dashboard/app/components/AgentDependencyGraph.vue +5 -5
  41. package/dashboard/app/components/AgentEditDrawer.vue +120 -52
  42. package/dashboard/app/components/AgentSuggestionsCard.vue +4 -2
  43. package/dashboard/app/components/ConfirmDialog.vue +2 -2
  44. package/dashboard/app/components/DashboardState.vue +5 -3
  45. package/dashboard/app/components/GlobalSearch.vue +11 -7
  46. package/dashboard/app/components/KeyboardShortcutsHelp.vue +8 -6
  47. package/dashboard/app/components/KnowledgeSourcesList.vue +13 -9
  48. package/dashboard/app/components/MarkdownEditor.vue +1 -1
  49. package/dashboard/app/components/NotificationsBell.vue +8 -4
  50. package/dashboard/app/components/OnboardingTour.vue +24 -11
  51. package/dashboard/app/components/PersonaCloneDialog.vue +15 -8
  52. package/dashboard/app/components/PersonaWizard.vue +105 -72
  53. package/dashboard/app/components/SidebarFavoritesWidget.vue +5 -5
  54. package/dashboard/app/components/SidebarStatsWidget.vue +3 -1
  55. package/dashboard/app/components/Terminal.vue +9 -8
  56. package/dashboard/app/components/TextDiff.vue +27 -15
  57. package/dashboard/app/composables/useActivityFeed.ts +3 -3
  58. package/dashboard/app/composables/useApi.ts +4 -2
  59. package/dashboard/app/composables/useConfirmDialog.ts +1 -1
  60. package/dashboard/app/composables/useFavorites.ts +8 -8
  61. package/dashboard/app/composables/useTerminalThemes.ts +13 -13
  62. package/dashboard/app/composables/useThemeColor.ts +7 -4
  63. package/dashboard/app/layouts/default.vue +7 -0
  64. package/dashboard/app/pages/agents/[id].vue +319 -159
  65. package/dashboard/app/pages/agents/compare.vue +152 -52
  66. package/dashboard/app/pages/agents/index.vue +68 -55
  67. package/dashboard/app/pages/agents/new.vue +125 -58
  68. package/dashboard/app/pages/audit.vue +13 -6
  69. package/dashboard/app/pages/budget.vue +20 -18
  70. package/dashboard/app/pages/commands.vue +26 -22
  71. package/dashboard/app/pages/departments/[dept].vue +67 -31
  72. package/dashboard/app/pages/departments/compare.vue +85 -27
  73. package/dashboard/app/pages/departments/index.vue +31 -7
  74. package/dashboard/app/pages/health.vue +19 -12
  75. package/dashboard/app/pages/knowledge/index.vue +190 -78
  76. package/dashboard/app/pages/models.vue +74 -28
  77. package/dashboard/app/pages/personas/[id].vue +322 -219
  78. package/dashboard/app/pages/personas/archetypes.vue +20 -6
  79. package/dashboard/app/pages/personas/compare-with-agent.vue +109 -39
  80. package/dashboard/app/pages/personas/compare.vue +196 -124
  81. package/dashboard/app/pages/personas/index.vue +67 -60
  82. package/dashboard/app/pages/personas/new.vue +1 -1
  83. package/dashboard/app/pages/plan-canvas.vue +630 -0
  84. package/dashboard/app/pages/settings.vue +1 -0
  85. package/dashboard/app/pages/tasks.vue +80 -38
  86. package/dashboard/app/pages/trash.vue +11 -9
  87. package/dashboard/app/pages/workflows.vue +29 -19
  88. package/dashboard/nuxt.config.ts +2 -1
  89. package/dashboard/package.json +5 -0
  90. package/departments/brand/SKILL.md +3 -2
  91. package/departments/brand/skills/archetype-finder/SKILL.md +3 -2
  92. package/departments/brand/skills/colors/SKILL.md +3 -2
  93. package/departments/brand/skills/design-system/SKILL.md +3 -2
  94. package/departments/brand/skills/identity-system/SKILL.md +3 -2
  95. package/departments/brand/skills/logo-brief/SKILL.md +3 -2
  96. package/departments/brand/skills/mockup-generate/SKILL.md +3 -2
  97. package/departments/brand/skills/naming-evaluate/SKILL.md +3 -2
  98. package/departments/brand/skills/positioning-statement/SKILL.md +3 -2
  99. package/departments/brand/skills/primal-audit/SKILL.md +3 -2
  100. package/departments/brand/skills/ux-audit/SKILL.md +3 -2
  101. package/departments/brand/skills/voice-guide/SKILL.md +3 -2
  102. package/departments/brand/skills/wireframe/SKILL.md +3 -2
  103. package/departments/community/SKILL.md +3 -2
  104. package/departments/community/skills/ai-community/SKILL.md +3 -2
  105. package/departments/community/skills/betting-setup/SKILL.md +3 -2
  106. package/departments/community/skills/business-model/SKILL.md +3 -2
  107. package/departments/community/skills/content-calendar/SKILL.md +3 -2
  108. package/departments/community/skills/events-plan/SKILL.md +3 -2
  109. package/departments/community/skills/gamification-design/SKILL.md +3 -2
  110. package/departments/community/skills/growth-plan/SKILL.md +3 -2
  111. package/departments/community/skills/metrics-track/SKILL.md +3 -2
  112. package/departments/community/skills/moderation/SKILL.md +3 -2
  113. package/departments/community/skills/monetize-plan/SKILL.md +3 -2
  114. package/departments/community/skills/niche-setup/SKILL.md +3 -2
  115. package/departments/community/skills/onboarding-flow/SKILL.md +3 -2
  116. package/departments/community/skills/platform-select/SKILL.md +3 -2
  117. package/departments/content/SKILL.md +3 -2
  118. package/departments/content/skills/ai-workflow/SKILL.md +3 -2
  119. package/departments/content/skills/analytics/SKILL.md +3 -2
  120. package/departments/content/skills/calendar/SKILL.md +3 -2
  121. package/departments/content/skills/content-system/SKILL.md +3 -2
  122. package/departments/content/skills/monetization-plan/SKILL.md +3 -2
  123. package/departments/content/skills/newsletter-write/SKILL.md +3 -2
  124. package/departments/content/skills/platform-optimize/SKILL.md +3 -2
  125. package/departments/content/skills/repurpose-plan/SKILL.md +3 -2
  126. package/departments/content/skills/research-compile/SKILL.md +3 -2
  127. package/departments/content/skills/script-structure/SKILL.md +3 -2
  128. package/departments/content/skills/short-form/SKILL.md +3 -2
  129. package/departments/content/skills/thumbnail-package/SKILL.md +3 -2
  130. package/departments/content/skills/trend-hunt/SKILL.md +3 -2
  131. package/departments/content/skills/video-produce/SKILL.md +3 -2
  132. package/departments/content/skills/video-setup/SKILL.md +3 -2
  133. package/departments/content/skills/viral-design/SKILL.md +3 -2
  134. package/departments/content/skills/youtube-strategy/SKILL.md +3 -2
  135. package/departments/dev/SKILL.md +3 -2
  136. package/departments/dev/skills/ai-assisted-dev/SKILL.md +3 -2
  137. package/departments/dev/skills/animated-website/SKILL.md +7 -0
  138. package/departments/dev/skills/architecture-design/SKILL.md +3 -2
  139. package/departments/dev/skills/code-review/SKILL.md +3 -2
  140. package/departments/dev/skills/db-design/SKILL.md +3 -2
  141. package/departments/dev/skills/ddd-model/SKILL.md +3 -2
  142. package/departments/dev/skills/demo-gif/SKILL.md +3 -2
  143. package/departments/dev/skills/deploy/SKILL.md +3 -2
  144. package/departments/dev/skills/devops-pipeline/SKILL.md +3 -2
  145. package/departments/dev/skills/docs/SKILL.md +3 -2
  146. package/departments/dev/skills/mcp/SKILL.md +3 -2
  147. package/departments/dev/skills/performance-audit/SKILL.md +3 -2
  148. package/departments/dev/skills/refactor-plan/SKILL.md +3 -2
  149. package/departments/dev/skills/research/SKILL.md +3 -2
  150. package/departments/dev/skills/security-compliance/SKILL.md +3 -2
  151. package/departments/dev/skills/stack-check/SKILL.md +3 -2
  152. package/departments/ecom/SKILL.md +3 -2
  153. package/departments/ecom/skills/analytics/SKILL.md +3 -2
  154. package/departments/ecom/skills/browse-competitor/SKILL.md +3 -2
  155. package/departments/ecom/skills/cart-recovery/SKILL.md +3 -2
  156. package/departments/ecom/skills/cro-optimize/SKILL.md +3 -2
  157. package/departments/ecom/skills/customer-journey/SKILL.md +3 -2
  158. package/departments/ecom/skills/fulfillment-plan/SKILL.md +3 -2
  159. package/departments/ecom/skills/marketplace-manage/SKILL.md +3 -2
  160. package/departments/ecom/skills/pricing-strategy/SKILL.md +3 -2
  161. package/departments/ecom/skills/product-launch/SKILL.md +3 -2
  162. package/departments/ecom/skills/rfm-segment/SKILL.md +3 -2
  163. package/departments/ecom/skills/social-commerce/SKILL.md +3 -2
  164. package/departments/ecom/skills/store-audit/SKILL.md +3 -2
  165. package/departments/ecom/skills/subscription-model/SKILL.md +3 -2
  166. package/departments/finance/SKILL.md +3 -2
  167. package/departments/finance/skills/budget-plan/SKILL.md +3 -2
  168. package/departments/finance/skills/cashflow-forecast/SKILL.md +3 -2
  169. package/departments/finance/skills/ciso-advisor/SKILL.md +3 -2
  170. package/departments/finance/skills/financial-model/SKILL.md +3 -2
  171. package/departments/finance/skills/pitch-deck/SKILL.md +3 -2
  172. package/departments/finance/skills/scenario-analysis/SKILL.md +3 -2
  173. package/departments/finance/skills/unit-economics/SKILL.md +3 -2
  174. package/departments/finance/skills/valuation-model/SKILL.md +3 -2
  175. package/departments/kb/SKILL.md +3 -2
  176. package/departments/kb/skills/ai-research/SKILL.md +3 -2
  177. package/departments/kb/skills/competitive-intel/SKILL.md +3 -2
  178. package/departments/kb/skills/doc-extraction/SKILL.md +100 -0
  179. package/departments/kb/skills/doc-redaction/SKILL.md +104 -0
  180. package/departments/kb/skills/knowledge-ops/SKILL.md +3 -2
  181. package/departments/kb/skills/knowledge-review/SKILL.md +3 -2
  182. package/departments/kb/skills/learn-content/SKILL.md +3 -2
  183. package/departments/kb/skills/moc-create/SKILL.md +3 -2
  184. package/departments/kb/skills/persona-build/SKILL.md +3 -2
  185. package/departments/kb/skills/research-plan/SKILL.md +3 -2
  186. package/departments/kb/skills/search-kb/SKILL.md +3 -2
  187. package/departments/kb/skills/source-evaluate/SKILL.md +3 -2
  188. package/departments/kb/skills/taxonomy-manage/SKILL.md +3 -2
  189. package/departments/kb/skills/write-as-persona/SKILL.md +3 -2
  190. package/departments/landing/SKILL.md +3 -2
  191. package/departments/landing/skills/ab-test/SKILL.md +3 -2
  192. package/departments/landing/skills/affiliate-bridge/SKILL.md +3 -2
  193. package/departments/landing/skills/awareness-diagnose/SKILL.md +3 -2
  194. package/departments/landing/skills/email-sequence/SKILL.md +3 -2
  195. package/departments/landing/skills/funnel-metrics/SKILL.md +3 -2
  196. package/departments/landing/skills/headline-write/SKILL.md +3 -2
  197. package/departments/landing/skills/launch-sequence/SKILL.md +3 -2
  198. package/departments/landing/skills/offer-create/SKILL.md +3 -2
  199. package/departments/landing/skills/optimize-page/SKILL.md +3 -2
  200. package/departments/landing/skills/page-architect/SKILL.md +3 -2
  201. package/departments/landing/skills/persuasion-apply/SKILL.md +3 -2
  202. package/departments/landing/skills/webinar-funnel/SKILL.md +3 -2
  203. package/departments/leadership/SKILL.md +3 -2
  204. package/departments/leadership/skills/change-manage/SKILL.md +3 -2
  205. package/departments/leadership/skills/conflict-resolve/SKILL.md +3 -2
  206. package/departments/leadership/skills/culture-audit/SKILL.md +3 -2
  207. package/departments/leadership/skills/delegation-matrix/SKILL.md +3 -2
  208. package/departments/leadership/skills/disc-assess/SKILL.md +3 -2
  209. package/departments/leadership/skills/feedback-give/SKILL.md +3 -2
  210. package/departments/leadership/skills/hiring-plan/SKILL.md +3 -2
  211. package/departments/leadership/skills/performance-review/SKILL.md +3 -2
  212. package/departments/marketing/SKILL.md +3 -2
  213. package/departments/marketing/skills/ab-test/SKILL.md +3 -2
  214. package/departments/marketing/skills/analytics-report/SKILL.md +3 -2
  215. package/departments/marketing/skills/audience-segment/SKILL.md +3 -2
  216. package/departments/marketing/skills/calendar-plan/SKILL.md +3 -2
  217. package/departments/marketing/skills/competitor-analysis/SKILL.md +3 -2
  218. package/departments/marketing/skills/content-audit/SKILL.md +3 -2
  219. package/departments/marketing/skills/email-sequence/SKILL.md +3 -2
  220. package/departments/marketing/skills/growth-loop/SKILL.md +3 -2
  221. package/departments/marketing/skills/marketing-automation/SKILL.md +3 -2
  222. package/departments/marketing/skills/paid-campaign/SKILL.md +3 -2
  223. package/departments/marketing/skills/programmatic-seo/SKILL.md +3 -2
  224. package/departments/marketing/skills/seo-audit/SKILL.md +3 -2
  225. package/departments/marketing/skills/social-strategy/SKILL.md +3 -2
  226. package/departments/ops/SKILL.md +3 -2
  227. package/departments/ops/skills/bottleneck-find/SKILL.md +3 -2
  228. package/departments/ops/skills/dashboard-build/SKILL.md +3 -2
  229. package/departments/ops/skills/gdpr-compliance/SKILL.md +3 -2
  230. package/departments/ops/skills/github-ops/SKILL.md +3 -2
  231. package/departments/ops/skills/gtd-setup/SKILL.md +3 -2
  232. package/departments/ops/skills/integration-design/SKILL.md +3 -2
  233. package/departments/ops/skills/iso27001/SKILL.md +3 -2
  234. package/departments/ops/skills/lean-audit/SKILL.md +3 -2
  235. package/departments/ops/skills/metrics-dashboard/SKILL.md +3 -2
  236. package/departments/ops/skills/n8n-flow/SKILL.md +3 -2
  237. package/departments/ops/skills/quality-management/SKILL.md +3 -2
  238. package/departments/ops/skills/risk-management/SKILL.md +3 -2
  239. package/departments/ops/skills/soc2-compliance/SKILL.md +3 -2
  240. package/departments/ops/skills/sop-create/SKILL.md +3 -2
  241. package/departments/ops/skills/terminal-ops/SKILL.md +3 -2
  242. package/departments/ops/skills/workflow-automate/SKILL.md +3 -2
  243. package/departments/ops/skills/zapier-flow/SKILL.md +3 -2
  244. package/departments/org/SKILL.md +3 -2
  245. package/departments/org/skills/compensation-plan/SKILL.md +3 -2
  246. package/departments/org/skills/culture-define/SKILL.md +3 -2
  247. package/departments/org/skills/decision-framework/SKILL.md +3 -2
  248. package/departments/org/skills/hiring-plan/SKILL.md +3 -2
  249. package/departments/org/skills/meeting-optimize/SKILL.md +3 -2
  250. package/departments/org/skills/onboarding-design/SKILL.md +3 -2
  251. package/departments/org/skills/org-design/SKILL.md +3 -2
  252. package/departments/org/skills/remote-setup/SKILL.md +3 -2
  253. package/departments/org/skills/sop-process/SKILL.md +3 -2
  254. package/departments/org/skills/team-assess/SKILL.md +3 -2
  255. package/departments/pm/SKILL.md +3 -2
  256. package/departments/pm/skills/backlog-groom/SKILL.md +3 -2
  257. package/departments/pm/skills/discovery-plan/SKILL.md +3 -2
  258. package/departments/pm/skills/estimate-forecast/SKILL.md +3 -2
  259. package/departments/pm/skills/impact-map/SKILL.md +3 -2
  260. package/departments/pm/skills/kanban-setup/SKILL.md +3 -2
  261. package/departments/pm/skills/risk-register/SKILL.md +3 -2
  262. package/departments/pm/skills/roadmap-build/SKILL.md +3 -2
  263. package/departments/pm/skills/sprint-plan/SKILL.md +3 -2
  264. package/departments/pm/skills/stakeholder-map/SKILL.md +3 -2
  265. package/departments/pm/skills/standup-run/SKILL.md +3 -2
  266. package/departments/pm/skills/story-write/SKILL.md +3 -2
  267. package/departments/saas/SKILL.md +3 -2
  268. package/departments/saas/skills/benchmark-compare/SKILL.md +3 -2
  269. package/departments/saas/skills/churn-analysis/SKILL.md +3 -2
  270. package/departments/saas/skills/customer-success/SKILL.md +3 -2
  271. package/departments/saas/skills/growth-plan/SKILL.md +3 -2
  272. package/departments/saas/skills/gtm-strategy/SKILL.md +3 -2
  273. package/departments/saas/skills/launch-execute/SKILL.md +3 -2
  274. package/departments/saas/skills/metrics-dashboard/SKILL.md +3 -2
  275. package/departments/saas/skills/micro-saas-stack/SKILL.md +3 -2
  276. package/departments/saas/skills/mvp-build/SKILL.md +3 -2
  277. package/departments/saas/skills/niche-evaluate/SKILL.md +3 -2
  278. package/departments/saas/skills/onboarding-optimize/SKILL.md +3 -2
  279. package/departments/saas/skills/plg-setup/SKILL.md +3 -2
  280. package/departments/saas/skills/pricing-strategy/SKILL.md +3 -2
  281. package/departments/saas/skills/validate-idea/SKILL.md +3 -2
  282. package/departments/sales/SKILL.md +3 -2
  283. package/departments/sales/skills/challenger-sell/SKILL.md +3 -2
  284. package/departments/sales/skills/deal-qualify/SKILL.md +3 -2
  285. package/departments/sales/skills/discovery-call/SKILL.md +3 -2
  286. package/departments/sales/skills/forecast-revenue/SKILL.md +3 -2
  287. package/departments/sales/skills/negotiate-plan/SKILL.md +3 -2
  288. package/departments/sales/skills/objection-handle/SKILL.md +3 -2
  289. package/departments/sales/skills/pipeline-manage/SKILL.md +3 -2
  290. package/departments/sales/skills/pricing-negotiate/SKILL.md +3 -2
  291. package/departments/strategy/SKILL.md +3 -2
  292. package/departments/strategy/skills/blue-ocean/SKILL.md +3 -2
  293. package/departments/strategy/skills/bmc/SKILL.md +3 -2
  294. package/departments/strategy/skills/board-advisor/SKILL.md +3 -2
  295. package/departments/strategy/skills/cto-advisor/SKILL.md +3 -2
  296. package/departments/strategy/skills/extract-data/SKILL.md +3 -2
  297. package/departments/strategy/skills/five-forces/SKILL.md +3 -2
  298. package/departments/strategy/skills/growth-strategy/SKILL.md +3 -2
  299. package/departments/strategy/skills/moat-analysis/SKILL.md +3 -2
  300. package/departments/strategy/skills/position/SKILL.md +3 -2
  301. package/departments/strategy/skills/scenario-plan/SKILL.md +3 -2
  302. package/installer/config-seed.js +81 -25
  303. package/installer/graphify.js +340 -0
  304. package/installer/index.js +9 -2
  305. package/installer/keys.js +18 -3
  306. package/installer/update.js +8 -1
  307. package/knowledge/commands-registry.json +16 -3
  308. package/knowledge/skills-manifest.json +27 -1
  309. package/mcps/registry.json +9 -9
  310. package/mcps/scripts/apply-mcps.sh +6 -2
  311. package/package.json +2 -1
  312. package/pyproject.toml +1 -1
  313. package/scripts/dashboard-api.py +336 -124
  314. package/scripts/guide_gen.py +139 -0
  315. package/scripts/migrate_skills_kb_first.py +3 -2
  316. package/scripts/synapse-bridge.py +9 -1
@@ -0,0 +1,299 @@
1
+ """Evolve — turn accumulated cross-project signals into instinct proposals.
2
+
3
+ Propose-only, LLM-free. The engine ingests the raw signals sessions
4
+ already accumulate (``~/.arkaos/gotchas.json`` — error patterns with
5
+ occurrence counts and the projects they appeared in) into the
6
+ ``InsightStore`` as deterministic instincts: confidence derives from the
7
+ occurrence count inside the [0.3, 0.9] band, evidence_count mirrors the
8
+ raw count, and the record id is a stable digest of (source, title,
9
+ project) so re-ingestion is idempotent. It then renders a markdown
10
+ proposal at ``~/.arkaos/evolve-proposals/<date>.md`` with the
11
+ cross-project promotion candidates (``InsightStore.promotable`` — this
12
+ module is that API's first production caller) and the strongest
13
+ instincts.
14
+
15
+ Boundaries: Dreaming owns vault clustering (LLM, multi-backend) — evolve
16
+ never re-clusters markdown; it consumes structured stores only. Nothing
17
+ is promoted, modified, or deleted: the proposal file is the only output
18
+ (the reorganizer contract). Client identifiers are redacted and
19
+ project names are never rendered — only distinct-project counts.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import hashlib
25
+ import json
26
+ import os
27
+ import tempfile
28
+ from dataclasses import dataclass, field
29
+ from datetime import UTC, datetime
30
+ from pathlib import Path
31
+
32
+ from core.cognition.insights.store import InsightStore
33
+ from core.cognition.memory.schemas import (
34
+ INSTINCT_CONFIDENCE_MAX,
35
+ INSTINCT_CONFIDENCE_MIN,
36
+ ActionableInsight,
37
+ )
38
+ from core.cognition.reorganizer import md_escape, redact_clients
39
+
40
+ _DEFAULT_OUTPUT_DIR = Path.home() / ".arkaos" / "evolve-proposals"
41
+ _DEFAULT_GOTCHAS_PATH = Path.home() / ".arkaos" / "gotchas.json"
42
+ _DEFAULT_DB_PATH = Path.home() / ".arkaos" / "insights.db"
43
+ _PROPOSAL_FILENAME_FMT = "%Y-%m-%d.md"
44
+ _CONFIDENCE_STEP = 0.1
45
+ _TOP_INSTINCTS = 10
46
+
47
+
48
+ @dataclass(frozen=True)
49
+ class PromotionCandidate:
50
+ """Cross-project instinct eligible for promotion (propose-only)."""
51
+
52
+ title: str
53
+ project_count: int
54
+ mean_confidence: float
55
+ evidence_count: int
56
+
57
+
58
+ @dataclass(frozen=True)
59
+ class EvolveReport:
60
+ """Evolve run outcome. Never contains client or project identifiers."""
61
+
62
+ date: str
63
+ ingested: int
64
+ pending_instincts: int
65
+ candidates: list[PromotionCandidate] = field(default_factory=list)
66
+ proposal_path: str | None = None
67
+
68
+
69
+ def derive_confidence(count: int) -> float:
70
+ """Occurrence count -> confidence, deterministic, inside the band."""
71
+ raw = INSTINCT_CONFIDENCE_MIN + _CONFIDENCE_STEP * (max(1, count) - 1)
72
+ return min(INSTINCT_CONFIDENCE_MAX, raw)
73
+
74
+
75
+ def _stable_id(source: str, title: str, project: str) -> str:
76
+ digest = hashlib.sha1(f"{source}:{title}:{project}".encode())
77
+ return digest.hexdigest()[:32]
78
+
79
+
80
+ def _load_gotchas(gotchas_path: Path) -> list[dict]:
81
+ try:
82
+ data = json.loads(gotchas_path.read_text(encoding="utf-8"))
83
+ except (OSError, json.JSONDecodeError):
84
+ return []
85
+ entries = data.get("gotchas") if isinstance(data, dict) else data
86
+ return [e for e in entries or [] if isinstance(e, dict)]
87
+
88
+
89
+ def _instinct_from_gotcha(gotcha: dict, project: str) -> ActionableInsight | None:
90
+ title = str(gotcha.get("pattern") or "").strip()
91
+ if not title or not project.strip():
92
+ return None
93
+ count = int(gotcha.get("count") or 1)
94
+ return ActionableInsight(
95
+ id=_stable_id("gotcha", title, project),
96
+ project=project,
97
+ trigger="evolve-ingest",
98
+ category="technical",
99
+ severity="improve" if count >= 5 else "consider",
100
+ title=title,
101
+ description=str(gotcha.get("full_pattern") or title),
102
+ recommendation=str(gotcha.get("suggestion") or "").strip()
103
+ or "Recurring signal — consider a rule, skill, or fix.",
104
+ context=f"tool={gotcha.get('tool', '?')} count={count}",
105
+ confidence=derive_confidence(count),
106
+ evidence_count=max(1, count),
107
+ )
108
+
109
+
110
+ def ingest_gotchas(store: InsightStore, gotchas_path: Path) -> int:
111
+ """Mirror gotcha signals into the store as project-scoped instincts.
112
+
113
+ Deterministic derivation from the source of truth: re-ingesting
114
+ recomputes the same ids and values (INSERT OR REPLACE), so repeat
115
+ runs are idempotent.
116
+ """
117
+ ingested = 0
118
+ for gotcha in _load_gotchas(gotchas_path):
119
+ projects = gotcha.get("projects") or []
120
+ for project in {str(p) for p in projects if str(p).strip()}:
121
+ insight = _instinct_from_gotcha(gotcha, project)
122
+ if insight is None:
123
+ continue
124
+ store.save(insight)
125
+ ingested += 1
126
+ return ingested
127
+
128
+
129
+ def _candidates(
130
+ store: InsightStore, min_projects: int, min_confidence: float,
131
+ ) -> list[PromotionCandidate]:
132
+ titles = store.promotable(
133
+ min_projects=min_projects, min_confidence=min_confidence
134
+ )
135
+ rows = [i for i in store.get_all_pending() if i.scope == "project"]
136
+ out: list[PromotionCandidate] = []
137
+ for title in titles:
138
+ group = [i for i in rows if i.title == title]
139
+ if not group:
140
+ continue
141
+ out.append(
142
+ PromotionCandidate(
143
+ title=title,
144
+ project_count=len({i.project for i in group}),
145
+ mean_confidence=round(
146
+ sum(i.confidence for i in group) / len(group), 2
147
+ ),
148
+ evidence_count=sum(i.evidence_count for i in group),
149
+ )
150
+ )
151
+ return out
152
+
153
+
154
+ def build_proposal(
155
+ *,
156
+ db_path: Path | None = None,
157
+ gotchas_path: Path | None = None,
158
+ output_dir: Path | None = None,
159
+ dry_run: bool = False,
160
+ min_projects: int = 2,
161
+ min_confidence: float = 0.8,
162
+ ) -> EvolveReport:
163
+ """Ingest signals, compute promotion candidates, write the proposal."""
164
+ store = InsightStore(db_path or _DEFAULT_DB_PATH)
165
+ ingested = ingest_gotchas(store, gotchas_path or _DEFAULT_GOTCHAS_PATH)
166
+ pending = store.get_all_pending()
167
+ candidates = _candidates(store, min_projects, min_confidence)
168
+ date = datetime.now(UTC).strftime("%Y-%m-%d")
169
+ markdown = _render(date, ingested, pending, candidates)
170
+ if dry_run:
171
+ print(markdown)
172
+ return EvolveReport(date, ingested, len(pending), candidates)
173
+ path = _write_report(markdown, output_dir)
174
+ return EvolveReport(date, ingested, len(pending), candidates, str(path))
175
+
176
+
177
+ def _render_candidate(candidate: PromotionCandidate) -> str:
178
+ title = md_escape(redact_clients(candidate.title))
179
+ return (
180
+ f"| {title} | {candidate.project_count} | "
181
+ f"{candidate.mean_confidence:.2f} | {candidate.evidence_count} |"
182
+ )
183
+
184
+
185
+ def _render_instinct(insight: ActionableInsight) -> str:
186
+ title = md_escape(redact_clients(insight.title))
187
+ rec = md_escape(redact_clients(insight.recommendation))
188
+ return (
189
+ f"- **{title}** — confidence {insight.confidence:.2f}, "
190
+ f"evidence {insight.evidence_count}. {rec}"
191
+ )
192
+
193
+
194
+ def _render_summary(
195
+ date: str, ingested: int, pending_count: int, candidate_count: int,
196
+ ) -> list[str]:
197
+ return [
198
+ f"# Evolve proposal — {date}",
199
+ "",
200
+ "> Propose-only: nothing was promoted or modified. Review and",
201
+ "> act via the squads; promotion stays a deliberate operator call.",
202
+ "",
203
+ "## Summary",
204
+ "",
205
+ f"- Instincts ingested this run: **{ingested}**",
206
+ f"- Pending instincts in store: **{pending_count}**",
207
+ f"- Promotion candidates: **{candidate_count}**",
208
+ "",
209
+ ]
210
+
211
+
212
+ def _render_candidates_block(candidates: list[PromotionCandidate]) -> list[str]:
213
+ lines = ["## Promotion candidates (cross-project)", ""]
214
+ if candidates:
215
+ lines += [
216
+ "| Instinct | Projects | Mean confidence | Evidence |",
217
+ "|---|---|---|---|",
218
+ *[_render_candidate(c) for c in candidates],
219
+ ]
220
+ else:
221
+ lines.append("None yet — needs the same signal in 2+ projects.")
222
+ return lines
223
+
224
+
225
+ def _strongest_by_title(pending: list[ActionableInsight]) -> list[ActionableInsight]:
226
+ """Top instincts, one row per title (a cross-project pattern has one
227
+ project-scoped row per project — the operator needs it once)."""
228
+ ranked = sorted(
229
+ pending, key=lambda i: (i.confidence, i.evidence_count), reverse=True
230
+ )
231
+ seen: set[str] = set()
232
+ out: list[ActionableInsight] = []
233
+ for insight in ranked:
234
+ if insight.title in seen:
235
+ continue
236
+ seen.add(insight.title)
237
+ out.append(insight)
238
+ if len(out) == _TOP_INSTINCTS:
239
+ break
240
+ return out
241
+
242
+
243
+ def _render_instincts_block(pending: list[ActionableInsight]) -> list[str]:
244
+ strongest = _strongest_by_title(pending)
245
+ lines = ["", "## Strongest instincts", ""]
246
+ if strongest:
247
+ lines += [_render_instinct(i) for i in strongest]
248
+ else:
249
+ lines.append("Store is empty — no signals accumulated yet.")
250
+ return lines
251
+
252
+
253
+ def _render(
254
+ date: str,
255
+ ingested: int,
256
+ pending: list[ActionableInsight],
257
+ candidates: list[PromotionCandidate],
258
+ ) -> str:
259
+ lines = (
260
+ _render_summary(date, ingested, len(pending), len(candidates))
261
+ + _render_candidates_block(candidates)
262
+ + _render_instincts_block(pending)
263
+ )
264
+ return "\n".join(lines) + "\n"
265
+
266
+
267
+ def _write_report(markdown: str, output_dir: Path | None) -> Path:
268
+ """Atomic markdown write to an allowlisted output directory."""
269
+ out = _validate_output_dir(output_dir)
270
+ out.mkdir(parents=True, exist_ok=True)
271
+ report_path = out / datetime.now(UTC).strftime(
272
+ _PROPOSAL_FILENAME_FMT
273
+ )
274
+ tmp_path = report_path.with_suffix(f".tmp-{os.getpid()}.md")
275
+ tmp_path.write_text(markdown, encoding="utf-8")
276
+ os.replace(tmp_path, report_path)
277
+ return report_path
278
+
279
+
280
+ def _validate_output_dir(output_dir: Path | None) -> Path:
281
+ """Allowlist writes to ~/.arkaos or the system tempdir (defence in
282
+ depth — same contract as the reorganizer)."""
283
+ if output_dir is None:
284
+ return _DEFAULT_OUTPUT_DIR
285
+ resolved = Path(output_dir).expanduser().resolve()
286
+ allowed_roots = (
287
+ (Path.home() / ".arkaos").resolve(),
288
+ Path(tempfile.gettempdir()).resolve(),
289
+ )
290
+ for root in allowed_roots:
291
+ try:
292
+ resolved.relative_to(root)
293
+ return resolved
294
+ except ValueError:
295
+ continue
296
+ raise ValueError(
297
+ "output_dir must be under one of "
298
+ f"{[str(r) for r in allowed_roots]}; got {resolved}"
299
+ )
@@ -0,0 +1,55 @@
1
+ """CLI for the evolve engine: ``python -m core.cognition.evolve_cli``.
2
+
3
+ Mirrors ``reorganizer_cli``: argparse in, one ``build_proposal`` call,
4
+ human-readable summary out. ``--dry-run`` prints the proposal to stdout
5
+ without writing anything.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+
12
+ from core.cognition.evolve import build_proposal
13
+
14
+
15
+ def _build_parser() -> argparse.ArgumentParser:
16
+ parser = argparse.ArgumentParser(
17
+ prog="python -m core.cognition.evolve_cli",
18
+ description=(
19
+ "Ingest accumulated signals into instincts and write a "
20
+ "propose-only evolution proposal."
21
+ ),
22
+ )
23
+ parser.add_argument(
24
+ "--min-projects", type=int, default=2,
25
+ help="distinct projects required for a promotion candidate",
26
+ )
27
+ parser.add_argument(
28
+ "--min-confidence", type=float, default=0.8,
29
+ help="mean confidence required for a promotion candidate",
30
+ )
31
+ parser.add_argument(
32
+ "--dry-run", action="store_true",
33
+ help="print the proposal to stdout; write nothing",
34
+ )
35
+ return parser
36
+
37
+
38
+ def main(argv: list[str] | None = None) -> int:
39
+ args = _build_parser().parse_args(argv)
40
+ report = build_proposal(
41
+ dry_run=args.dry_run,
42
+ min_projects=args.min_projects,
43
+ min_confidence=args.min_confidence,
44
+ )
45
+ if not args.dry_run:
46
+ print(
47
+ f"evolve: ingested={report.ingested} "
48
+ f"pending={report.pending_instincts} "
49
+ f"candidates={len(report.candidates)} -> {report.proposal_path}"
50
+ )
51
+ return 0
52
+
53
+
54
+ if __name__ == "__main__":
55
+ raise SystemExit(main())
@@ -4,7 +4,9 @@ Persists ActionableInsight instances with support for status-based retrieval,
4
4
  project filtering, presentation lifecycle, and dismissal analytics.
5
5
  """
6
6
 
7
+ import contextlib
7
8
  import sqlite3
9
+ from collections.abc import Iterator
8
10
  from datetime import UTC, datetime
9
11
  from pathlib import Path
10
12
 
@@ -24,11 +26,20 @@ class InsightStore:
24
26
  self._db_path = db_path
25
27
  self._init_db()
26
28
 
27
- def _conn(self) -> sqlite3.Connection:
29
+ @contextlib.contextmanager
30
+ def _conn(self) -> Iterator[sqlite3.Connection]:
31
+ """Per-operation connection: transaction semantics AND a real
32
+ close. ``with sqlite3.connect(...)`` alone only commits/rolls
33
+ back — it never closes, leaking one connection per operation
34
+ (ResourceWarning under coverage, amplified by ingest loops)."""
28
35
  conn = sqlite3.connect(self._db_path)
29
- conn.row_factory = sqlite3.Row
30
- conn.execute("PRAGMA journal_mode=WAL")
31
- return conn
36
+ try:
37
+ conn.row_factory = sqlite3.Row
38
+ conn.execute("PRAGMA journal_mode=WAL")
39
+ with conn:
40
+ yield conn
41
+ finally:
42
+ conn.close()
32
43
 
33
44
  # Instinct columns added after the original 12-column schema. An
34
45
  # operator's existing DB has the old shape, so these are ALTER-added
@@ -4,8 +4,9 @@ import os
4
4
  import re as _re
5
5
  from pathlib import Path
6
6
  from tempfile import NamedTemporaryFile
7
- from typing import Optional
7
+
8
8
  import yaml
9
+
9
10
  from core.forge.schema import ForgePlan, ForgeStatus
10
11
 
11
12
 
@@ -23,7 +24,9 @@ def save_plan(plan: ForgePlan) -> Path:
23
24
  plans.mkdir(parents=True, exist_ok=True)
24
25
  target = plans / f"{plan.id}.yaml"
25
26
  data = plan.model_dump(mode="json")
26
- fd = NamedTemporaryFile(mode="w", dir=str(plans), suffix=".tmp", delete=False, encoding="utf-8")
27
+ fd = NamedTemporaryFile( # noqa: SIM115 — handle outlives the block: os.replace consumes fd.name
28
+ mode="w", dir=str(plans), suffix=".tmp", delete=False, encoding="utf-8"
29
+ )
27
30
  try:
28
31
  yaml.dump(data, fd, default_flow_style=False, allow_unicode=True)
29
32
  fd.close()
@@ -35,7 +38,7 @@ def save_plan(plan: ForgePlan) -> Path:
35
38
  return target
36
39
 
37
40
 
38
- def load_plan(plan_id: str) -> Optional[ForgePlan]:
41
+ def load_plan(plan_id: str) -> ForgePlan | None:
39
42
  """Load a forge plan by ID. Returns None if not found."""
40
43
  path = _plans_dir() / f"{plan_id}.yaml"
41
44
  if not path.exists():
@@ -54,12 +57,20 @@ def list_plans() -> list[dict]:
54
57
  if path.name == "active.yaml":
55
58
  continue
56
59
  data = yaml.safe_load(path.read_text(encoding="utf-8"))
60
+ if not isinstance(data, dict):
61
+ continue
62
+ # Pre-v4 plans stored complexity/critic as scalars — a summary
63
+ # must tolerate every shape that ever hit disk.
64
+ complexity = data.get("complexity")
65
+ critic = data.get("critic")
57
66
  results.append({
58
67
  "id": data.get("id", path.stem),
59
68
  "name": data.get("name", ""),
60
69
  "status": data.get("status", "draft"),
61
- "tier": data.get("complexity", {}).get("tier", "shallow"),
62
- "confidence": data.get("critic", {}).get("confidence", 0.0),
70
+ "tier": complexity.get("tier", "shallow")
71
+ if isinstance(complexity, dict) else "shallow",
72
+ "confidence": critic.get("confidence", 0.0)
73
+ if isinstance(critic, dict) else 0.0,
63
74
  "created_at": data.get("created_at", ""),
64
75
  })
65
76
  return results
@@ -72,7 +83,7 @@ def set_active_plan(plan_id: str) -> None:
72
83
  link.write_text(plan_id, encoding="utf-8")
73
84
 
74
85
 
75
- def get_active_plan() -> Optional[ForgePlan]:
86
+ def get_active_plan() -> ForgePlan | None:
76
87
  """Get the currently active forge plan."""
77
88
  link = _active_link()
78
89
  if not link.exists():
@@ -94,7 +105,10 @@ def clear_active_plan() -> None:
94
105
 
95
106
  def _obsidian_forge_dir() -> Path:
96
107
  """Obsidian vault path for Forge documents."""
97
- return Path.home() / "Documents" / "Personal" / "Projects" / "WizardingCode Internal" / "ArkaOS" / "Forge"
108
+ return (
109
+ Path.home() / "Documents" / "Personal" / "Projects"
110
+ / "WizardingCode Internal" / "ArkaOS" / "Forge"
111
+ )
98
112
 
99
113
 
100
114
  def export_to_obsidian(plan: ForgePlan) -> Path:
@@ -150,7 +164,8 @@ def _render_obsidian_context(plan: ForgePlan) -> list[str]:
150
164
  ctx = plan.context
151
165
  return [
152
166
  "## Context",
153
- f"Repo: {ctx.repo} | Branch: {ctx.branch} | Commit: {ctx.commit_at_forge} | ArkaOS: {ctx.arkaos_version}",
167
+ f"Repo: {ctx.repo} | Branch: {ctx.branch} | "
168
+ f"Commit: {ctx.commit_at_forge} | ArkaOS: {ctx.arkaos_version}",
154
169
  "",
155
170
  "## Prompt",
156
171
  f"> {ctx.prompt}",
@@ -195,7 +210,10 @@ def _render_obsidian_critic(plan: ForgePlan) -> list[str]:
195
210
  if critic.risks:
196
211
  lines.append("### Risks")
197
212
  for risk in critic.risks:
198
- lines.append(f"- **{risk.risk}** ({risk.severity.value}) — Mitigation: {risk.mitigation}")
213
+ lines.append(
214
+ f"- **{risk.risk}** ({risk.severity.value}) — "
215
+ f"Mitigation: {risk.mitigation}"
216
+ )
199
217
  lines.append("")
200
218
  return lines
201
219
 
@@ -6,23 +6,21 @@ structured ForgePlan that downstream agents consume.
6
6
  """
7
7
 
8
8
  from enum import Enum
9
- from typing import List, Optional
10
9
 
11
10
  from pydantic import BaseModel, Field, field_validator
12
11
 
13
-
14
12
  # ---------------------------------------------------------------------------
15
13
  # Enums
16
14
  # ---------------------------------------------------------------------------
17
15
 
18
- class ForgeTier(str, Enum):
16
+ class ForgeTier(str, Enum): # noqa: UP042 — StrEnum flips str()/format(); migration is its own change
19
17
  """Execution tier determined by complexity score."""
20
18
  SHALLOW = "shallow"
21
19
  STANDARD = "standard"
22
20
  DEEP = "deep"
23
21
 
24
22
 
25
- class ForgeStatus(str, Enum):
23
+ class ForgeStatus(str, Enum): # noqa: UP042 — StrEnum flips str()/format(); migration is its own change
26
24
  """Lifecycle status of a ForgePlan."""
27
25
  DRAFT = "draft"
28
26
  REVIEWING = "reviewing"
@@ -34,21 +32,21 @@ class ForgeStatus(str, Enum):
34
32
  ARCHIVED = "archived"
35
33
 
36
34
 
37
- class ExplorerLens(str, Enum):
35
+ class ExplorerLens(str, Enum): # noqa: UP042 — StrEnum flips str()/format(); migration is its own change
38
36
  """The analytical perspective used when exploring a plan."""
39
37
  PRAGMATIC = "pragmatic" # Focus on fastest viable path
40
38
  ARCHITECTURAL = "architectural" # Focus on long-term design health
41
39
  CONTRARIAN = "contrarian" # Challenge assumptions, surface risks
42
40
 
43
41
 
44
- class RiskSeverity(str, Enum):
42
+ class RiskSeverity(str, Enum): # noqa: UP042 — StrEnum flips str()/format(); migration is its own change
45
43
  """Severity level for identified risks."""
46
44
  LOW = "low"
47
45
  MEDIUM = "medium"
48
46
  HIGH = "high"
49
47
 
50
48
 
51
- class ExecutionPathType(str, Enum):
49
+ class ExecutionPathType(str, Enum): # noqa: UP042 — StrEnum flips str()/format(); migration is its own change
52
50
  """Type of execution artefact that fulfils a plan step."""
53
51
  SKILL = "skill"
54
52
  WORKFLOW = "workflow"
@@ -60,12 +58,22 @@ class ExecutionPathType(str, Enum):
60
58
  # ---------------------------------------------------------------------------
61
59
 
62
60
  class ComplexityDimensions(BaseModel):
63
- """Five-axis complexity breakdown, each scored 0100."""
61
+ """Five-axis complexity breakdown, each scored 0-100."""
64
62
 
65
- scope: int = Field(default=0, description="Breadth of change across the codebase or system.")
66
- dependencies: int = Field(default=0, description="Number and criticality of upstream/downstream dependencies.")
67
- ambiguity: int = Field(default=0, description="How unclear or under-specified the requirements are.")
68
- risk: int = Field(default=0, description="Potential for breakage, data loss, or security impact.")
63
+ scope: int = Field(
64
+ default=0, description="Breadth of change across the codebase or system."
65
+ )
66
+ dependencies: int = Field(
67
+ default=0,
68
+ description="Number and criticality of upstream/downstream dependencies.",
69
+ )
70
+ ambiguity: int = Field(
71
+ default=0, description="How unclear or under-specified the requirements are."
72
+ )
73
+ risk: int = Field(
74
+ default=0,
75
+ description="Potential for breakage, data loss, or security impact.",
76
+ )
69
77
  novelty: int = Field(default=0, description="How unlike existing patterns this work is.")
70
78
 
71
79
  @field_validator("scope", "dependencies", "ambiguity", "risk", "novelty", mode="before")
@@ -78,14 +86,19 @@ class ComplexityDimensions(BaseModel):
78
86
  class ComplexityScore(BaseModel):
79
87
  """Aggregated complexity result produced by the Complexity Scorer."""
80
88
 
81
- score: int = Field(default=0, description="Composite 0100 score derived from all dimensions.")
82
- tier: ForgeTier = Field(default=ForgeTier.SHALLOW, description="Execution tier selected based on the composite score.")
83
- dimensions: ComplexityDimensions = Field(default_factory=ComplexityDimensions, description="Per-dimension breakdown.")
84
- similar_plans: List[str] = Field(
89
+ score: int = Field(default=0, description="Composite 0-100 score derived from all dimensions.")
90
+ tier: ForgeTier = Field(
91
+ default=ForgeTier.SHALLOW,
92
+ description="Execution tier selected based on the composite score.",
93
+ )
94
+ dimensions: ComplexityDimensions = Field(
95
+ default_factory=ComplexityDimensions, description="Per-dimension breakdown."
96
+ )
97
+ similar_plans: list[str] = Field(
85
98
  default_factory=list,
86
99
  description="IDs of previously completed plans with similar profiles.",
87
100
  )
88
- reused_patterns: List[str] = Field(
101
+ reused_patterns: list[str] = Field(
89
102
  default_factory=list,
90
103
  description="Named patterns from the ArkaOS pattern library reused in this plan.",
91
104
  )
@@ -213,13 +226,23 @@ class ForgePlan(BaseModel):
213
226
  "contains only constitution-enforced phases and confidence is 0.0."
214
227
  ),
215
228
  )
216
- dispatch_errors: List[str] = Field(
229
+ dispatch_errors: list[str] = Field(
217
230
  default_factory=list,
218
231
  description="Dispatch failures collected during forge (lens: error class: message).",
219
232
  )
220
233
 
221
234
  status: ForgeStatus = ForgeStatus.DRAFT
222
- approved_at: Optional[str] = None
223
- approved_by: Optional[str] = None
224
- executed_at: Optional[str] = None
225
- completion_notes: Optional[str] = None
235
+ approved_at: str | None = None
236
+ approved_by: str | None = None
237
+ rejected_at: str | None = None
238
+ rejected_by: str | None = None
239
+ executed_at: str | None = None
240
+ completion_notes: str | None = None
241
+ review_note: str | None = Field(
242
+ default=None,
243
+ description=(
244
+ "Operator note attached at decision time (plan-canvas review) "
245
+ "— the 'ponto-e-anota' trail; distinct from completion_notes, "
246
+ "which belongs to execution."
247
+ ),
248
+ )