arkaos 2.20.1 → 2.22.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 (268) hide show
  1. package/VERSION +1 -1
  2. package/arka/SKILL.md +16 -1
  3. package/arka/skills/costs/SKILL.md +62 -0
  4. package/arka/skills/flow/SKILL.md +14 -0
  5. package/arka/skills/forge/SKILL.md +14 -0
  6. package/config/hooks/post-tool-use.ps1 +80 -0
  7. package/config/hooks/post-tool-use.sh +47 -0
  8. package/config/hooks/pre-tool-use.ps1 +81 -8
  9. package/config/hooks/pre-tool-use.sh +81 -7
  10. package/config/hooks/stop.ps1 +79 -0
  11. package/config/hooks/stop.sh +88 -0
  12. package/config/hooks/user-prompt-submit.ps1 +50 -0
  13. package/config/hooks/user-prompt-submit.sh +24 -0
  14. package/core/cognition/__pycache__/auto_documentor.cpython-313.pyc +0 -0
  15. package/core/cognition/auto_documentor.py +433 -0
  16. package/core/jobs/__init__.py +14 -3
  17. package/core/jobs/__pycache__/__init__.cpython-313.pyc +0 -0
  18. package/core/jobs/__pycache__/auto_doc_worker.cpython-313.pyc +0 -0
  19. package/core/jobs/auto_doc_worker.py +257 -0
  20. package/core/obsidian/__init__.py +30 -3
  21. package/core/obsidian/__pycache__/__init__.cpython-313.pyc +0 -0
  22. package/core/obsidian/__pycache__/cataloger.cpython-313.pyc +0 -0
  23. package/core/obsidian/__pycache__/relator.cpython-313.pyc +0 -0
  24. package/core/obsidian/__pycache__/taxonomy.cpython-313.pyc +0 -0
  25. package/core/obsidian/cataloger.py +251 -0
  26. package/core/obsidian/relator.py +241 -0
  27. package/core/obsidian/taxonomy.py +100 -0
  28. package/core/runtime/__init__.py +22 -1
  29. package/core/runtime/__pycache__/__init__.cpython-313.pyc +0 -0
  30. package/core/runtime/__pycache__/base.cpython-313.pyc +0 -0
  31. package/core/runtime/__pycache__/claude_code.cpython-313.pyc +0 -0
  32. package/core/runtime/__pycache__/codex_cli.cpython-313.pyc +0 -0
  33. package/core/runtime/__pycache__/cursor.cpython-313.pyc +0 -0
  34. package/core/runtime/__pycache__/gemini_cli.cpython-313.pyc +0 -0
  35. package/core/runtime/__pycache__/llm_cost_telemetry.cpython-313.pyc +0 -0
  36. package/core/runtime/__pycache__/llm_cost_telemetry_cli.cpython-313.pyc +0 -0
  37. package/core/runtime/__pycache__/llm_provider.cpython-313.pyc +0 -0
  38. package/core/runtime/__pycache__/pricing.cpython-313.pyc +0 -0
  39. package/core/runtime/base.py +30 -1
  40. package/core/runtime/claude_code.py +68 -0
  41. package/core/runtime/codex_cli.py +33 -0
  42. package/core/runtime/cursor.py +19 -0
  43. package/core/runtime/gemini_cli.py +33 -0
  44. package/core/runtime/llm_cost_telemetry.py +306 -0
  45. package/core/runtime/llm_cost_telemetry_cli.py +138 -0
  46. package/core/runtime/llm_provider.py +382 -0
  47. package/core/runtime/pricing.py +85 -0
  48. package/core/synapse/__init__.py +8 -1
  49. package/core/synapse/__pycache__/__init__.cpython-313.pyc +0 -0
  50. package/core/synapse/__pycache__/engine.cpython-313.pyc +0 -0
  51. package/core/synapse/__pycache__/kb_cache.cpython-313.pyc +0 -0
  52. package/core/synapse/__pycache__/layers.cpython-313.pyc +0 -0
  53. package/core/synapse/engine.py +15 -1
  54. package/core/synapse/kb_cache.py +125 -6
  55. package/core/synapse/layers.py +296 -0
  56. package/core/workflow/__pycache__/flow_enforcer.cpython-313.pyc +0 -0
  57. package/core/workflow/__pycache__/kb_first_decider.cpython-313.pyc +0 -0
  58. package/core/workflow/__pycache__/marker_cache.cpython-313.pyc +0 -0
  59. package/core/workflow/__pycache__/research_gate.cpython-313.pyc +0 -0
  60. package/core/workflow/flow_enforcer.py +14 -3
  61. package/core/workflow/kb_first_decider.py +63 -0
  62. package/core/workflow/marker_cache.py +147 -0
  63. package/core/workflow/research_gate.py +301 -0
  64. package/departments/brand/SKILL.md +14 -0
  65. package/departments/brand/skills/archetype-finder/SKILL.md +14 -0
  66. package/departments/brand/skills/colors/SKILL.md +14 -0
  67. package/departments/brand/skills/design-system/SKILL.md +14 -0
  68. package/departments/brand/skills/identity-system/SKILL.md +14 -0
  69. package/departments/brand/skills/logo-brief/SKILL.md +14 -0
  70. package/departments/brand/skills/mockup-generate/SKILL.md +14 -0
  71. package/departments/brand/skills/naming-evaluate/SKILL.md +14 -0
  72. package/departments/brand/skills/positioning-statement/SKILL.md +14 -0
  73. package/departments/brand/skills/primal-audit/SKILL.md +14 -0
  74. package/departments/brand/skills/ux-audit/SKILL.md +14 -0
  75. package/departments/brand/skills/voice-guide/SKILL.md +14 -0
  76. package/departments/brand/skills/wireframe/SKILL.md +14 -0
  77. package/departments/community/SKILL.md +14 -0
  78. package/departments/community/skills/ai-community/SKILL.md +14 -0
  79. package/departments/community/skills/betting-setup/SKILL.md +14 -0
  80. package/departments/community/skills/business-model/SKILL.md +14 -0
  81. package/departments/community/skills/content-calendar/SKILL.md +14 -0
  82. package/departments/community/skills/events-plan/SKILL.md +14 -0
  83. package/departments/community/skills/gamification-design/SKILL.md +14 -0
  84. package/departments/community/skills/growth-plan/SKILL.md +14 -0
  85. package/departments/community/skills/metrics-track/SKILL.md +14 -0
  86. package/departments/community/skills/moderation/SKILL.md +14 -0
  87. package/departments/community/skills/monetize-plan/SKILL.md +14 -0
  88. package/departments/community/skills/niche-setup/SKILL.md +14 -0
  89. package/departments/community/skills/onboarding-flow/SKILL.md +14 -0
  90. package/departments/community/skills/platform-select/SKILL.md +14 -0
  91. package/departments/content/SKILL.md +14 -0
  92. package/departments/content/skills/ai-workflow/SKILL.md +14 -0
  93. package/departments/content/skills/analytics/SKILL.md +14 -0
  94. package/departments/content/skills/calendar/SKILL.md +14 -0
  95. package/departments/content/skills/content-system/SKILL.md +14 -0
  96. package/departments/content/skills/monetization-plan/SKILL.md +14 -0
  97. package/departments/content/skills/newsletter-write/SKILL.md +14 -0
  98. package/departments/content/skills/platform-optimize/SKILL.md +14 -0
  99. package/departments/content/skills/repurpose-plan/SKILL.md +14 -0
  100. package/departments/content/skills/script-structure/SKILL.md +14 -0
  101. package/departments/content/skills/short-form/SKILL.md +14 -0
  102. package/departments/content/skills/thumbnail-package/SKILL.md +14 -0
  103. package/departments/content/skills/viral-design/SKILL.md +14 -0
  104. package/departments/content/skills/youtube-strategy/SKILL.md +14 -0
  105. package/departments/dev/SKILL.md +14 -0
  106. package/departments/dev/skills/ai-assisted-dev/SKILL.md +14 -0
  107. package/departments/dev/skills/architecture-design/SKILL.md +14 -0
  108. package/departments/dev/skills/code-review/SKILL.md +14 -0
  109. package/departments/dev/skills/db-design/SKILL.md +14 -0
  110. package/departments/dev/skills/ddd-model/SKILL.md +14 -0
  111. package/departments/dev/skills/demo-gif/SKILL.md +14 -0
  112. package/departments/dev/skills/deploy/SKILL.md +14 -0
  113. package/departments/dev/skills/devops-pipeline/SKILL.md +14 -0
  114. package/departments/dev/skills/docs/SKILL.md +14 -0
  115. package/departments/dev/skills/mcp/SKILL.md +14 -0
  116. package/departments/dev/skills/performance-audit/SKILL.md +14 -0
  117. package/departments/dev/skills/refactor-plan/SKILL.md +14 -0
  118. package/departments/dev/skills/research/SKILL.md +14 -0
  119. package/departments/dev/skills/security-compliance/SKILL.md +14 -0
  120. package/departments/dev/skills/stack-check/SKILL.md +14 -0
  121. package/departments/ecom/SKILL.md +14 -0
  122. package/departments/ecom/skills/analytics/SKILL.md +14 -0
  123. package/departments/ecom/skills/browse-competitor/SKILL.md +14 -0
  124. package/departments/ecom/skills/cart-recovery/SKILL.md +14 -0
  125. package/departments/ecom/skills/cro-optimize/SKILL.md +14 -0
  126. package/departments/ecom/skills/customer-journey/SKILL.md +14 -0
  127. package/departments/ecom/skills/fulfillment-plan/SKILL.md +14 -0
  128. package/departments/ecom/skills/marketplace-manage/SKILL.md +14 -0
  129. package/departments/ecom/skills/pricing-strategy/SKILL.md +14 -0
  130. package/departments/ecom/skills/product-launch/SKILL.md +14 -0
  131. package/departments/ecom/skills/rfm-segment/SKILL.md +14 -0
  132. package/departments/ecom/skills/social-commerce/SKILL.md +14 -0
  133. package/departments/ecom/skills/store-audit/SKILL.md +14 -0
  134. package/departments/ecom/skills/subscription-model/SKILL.md +14 -0
  135. package/departments/finance/SKILL.md +14 -0
  136. package/departments/finance/skills/budget-plan/SKILL.md +14 -0
  137. package/departments/finance/skills/cashflow-forecast/SKILL.md +14 -0
  138. package/departments/finance/skills/ciso-advisor/SKILL.md +14 -0
  139. package/departments/finance/skills/financial-model/SKILL.md +14 -0
  140. package/departments/finance/skills/pitch-deck/SKILL.md +14 -0
  141. package/departments/finance/skills/scenario-analysis/SKILL.md +14 -0
  142. package/departments/finance/skills/unit-economics/SKILL.md +14 -0
  143. package/departments/finance/skills/valuation-model/SKILL.md +14 -0
  144. package/departments/kb/SKILL.md +14 -0
  145. package/departments/kb/skills/ai-research/SKILL.md +14 -0
  146. package/departments/kb/skills/competitive-intel/SKILL.md +14 -0
  147. package/departments/kb/skills/knowledge-review/SKILL.md +14 -0
  148. package/departments/kb/skills/learn-content/SKILL.md +14 -0
  149. package/departments/kb/skills/moc-create/SKILL.md +14 -0
  150. package/departments/kb/skills/persona-build/SKILL.md +14 -0
  151. package/departments/kb/skills/research-plan/SKILL.md +14 -0
  152. package/departments/kb/skills/search-kb/SKILL.md +14 -0
  153. package/departments/kb/skills/source-evaluate/SKILL.md +14 -0
  154. package/departments/kb/skills/taxonomy-manage/SKILL.md +14 -0
  155. package/departments/kb/skills/write-as-persona/SKILL.md +14 -0
  156. package/departments/landing/SKILL.md +14 -0
  157. package/departments/landing/skills/ab-test/SKILL.md +14 -0
  158. package/departments/landing/skills/affiliate-bridge/SKILL.md +14 -0
  159. package/departments/landing/skills/awareness-diagnose/SKILL.md +14 -0
  160. package/departments/landing/skills/email-sequence/SKILL.md +14 -0
  161. package/departments/landing/skills/funnel-metrics/SKILL.md +14 -0
  162. package/departments/landing/skills/headline-write/SKILL.md +14 -0
  163. package/departments/landing/skills/launch-sequence/SKILL.md +14 -0
  164. package/departments/landing/skills/offer-create/SKILL.md +14 -0
  165. package/departments/landing/skills/optimize-page/SKILL.md +14 -0
  166. package/departments/landing/skills/page-architect/SKILL.md +14 -0
  167. package/departments/landing/skills/persuasion-apply/SKILL.md +14 -0
  168. package/departments/landing/skills/webinar-funnel/SKILL.md +14 -0
  169. package/departments/leadership/SKILL.md +14 -0
  170. package/departments/leadership/skills/change-manage/SKILL.md +14 -0
  171. package/departments/leadership/skills/conflict-resolve/SKILL.md +14 -0
  172. package/departments/leadership/skills/culture-audit/SKILL.md +14 -0
  173. package/departments/leadership/skills/delegation-matrix/SKILL.md +14 -0
  174. package/departments/leadership/skills/disc-assess/SKILL.md +14 -0
  175. package/departments/leadership/skills/feedback-give/SKILL.md +14 -0
  176. package/departments/leadership/skills/hiring-plan/SKILL.md +14 -0
  177. package/departments/leadership/skills/performance-review/SKILL.md +14 -0
  178. package/departments/marketing/SKILL.md +14 -0
  179. package/departments/marketing/skills/ab-test/SKILL.md +14 -0
  180. package/departments/marketing/skills/analytics-report/SKILL.md +14 -0
  181. package/departments/marketing/skills/audience-segment/SKILL.md +14 -0
  182. package/departments/marketing/skills/calendar-plan/SKILL.md +14 -0
  183. package/departments/marketing/skills/competitor-analysis/SKILL.md +14 -0
  184. package/departments/marketing/skills/content-audit/SKILL.md +14 -0
  185. package/departments/marketing/skills/email-sequence/SKILL.md +14 -0
  186. package/departments/marketing/skills/growth-loop/SKILL.md +14 -0
  187. package/departments/marketing/skills/marketing-automation/SKILL.md +14 -0
  188. package/departments/marketing/skills/paid-campaign/SKILL.md +14 -0
  189. package/departments/marketing/skills/programmatic-seo/SKILL.md +14 -0
  190. package/departments/marketing/skills/seo-audit/SKILL.md +14 -0
  191. package/departments/marketing/skills/social-strategy/SKILL.md +14 -0
  192. package/departments/ops/SKILL.md +14 -0
  193. package/departments/ops/skills/bottleneck-find/SKILL.md +14 -0
  194. package/departments/ops/skills/dashboard-build/SKILL.md +14 -0
  195. package/departments/ops/skills/gdpr-compliance/SKILL.md +14 -0
  196. package/departments/ops/skills/gtd-setup/SKILL.md +14 -0
  197. package/departments/ops/skills/integration-design/SKILL.md +14 -0
  198. package/departments/ops/skills/iso27001/SKILL.md +14 -0
  199. package/departments/ops/skills/lean-audit/SKILL.md +14 -0
  200. package/departments/ops/skills/metrics-dashboard/SKILL.md +14 -0
  201. package/departments/ops/skills/n8n-flow/SKILL.md +14 -0
  202. package/departments/ops/skills/quality-management/SKILL.md +14 -0
  203. package/departments/ops/skills/risk-management/SKILL.md +14 -0
  204. package/departments/ops/skills/soc2-compliance/SKILL.md +14 -0
  205. package/departments/ops/skills/sop-create/SKILL.md +14 -0
  206. package/departments/ops/skills/workflow-automate/SKILL.md +14 -0
  207. package/departments/ops/skills/zapier-flow/SKILL.md +14 -0
  208. package/departments/org/SKILL.md +14 -0
  209. package/departments/org/skills/compensation-plan/SKILL.md +14 -0
  210. package/departments/org/skills/culture-define/SKILL.md +14 -0
  211. package/departments/org/skills/decision-framework/SKILL.md +14 -0
  212. package/departments/org/skills/hiring-plan/SKILL.md +14 -0
  213. package/departments/org/skills/meeting-optimize/SKILL.md +14 -0
  214. package/departments/org/skills/onboarding-design/SKILL.md +14 -0
  215. package/departments/org/skills/org-design/SKILL.md +14 -0
  216. package/departments/org/skills/remote-setup/SKILL.md +14 -0
  217. package/departments/org/skills/sop-process/SKILL.md +14 -0
  218. package/departments/org/skills/team-assess/SKILL.md +14 -0
  219. package/departments/pm/SKILL.md +14 -0
  220. package/departments/pm/skills/backlog-groom/SKILL.md +14 -0
  221. package/departments/pm/skills/discovery-plan/SKILL.md +14 -0
  222. package/departments/pm/skills/estimate-forecast/SKILL.md +14 -0
  223. package/departments/pm/skills/impact-map/SKILL.md +14 -0
  224. package/departments/pm/skills/kanban-setup/SKILL.md +14 -0
  225. package/departments/pm/skills/risk-register/SKILL.md +14 -0
  226. package/departments/pm/skills/roadmap-build/SKILL.md +14 -0
  227. package/departments/pm/skills/sprint-plan/SKILL.md +14 -0
  228. package/departments/pm/skills/stakeholder-map/SKILL.md +14 -0
  229. package/departments/pm/skills/standup-run/SKILL.md +14 -0
  230. package/departments/pm/skills/story-write/SKILL.md +14 -0
  231. package/departments/saas/SKILL.md +14 -0
  232. package/departments/saas/skills/benchmark-compare/SKILL.md +14 -0
  233. package/departments/saas/skills/churn-analysis/SKILL.md +14 -0
  234. package/departments/saas/skills/customer-success/SKILL.md +14 -0
  235. package/departments/saas/skills/growth-plan/SKILL.md +14 -0
  236. package/departments/saas/skills/gtm-strategy/SKILL.md +14 -0
  237. package/departments/saas/skills/launch-execute/SKILL.md +14 -0
  238. package/departments/saas/skills/metrics-dashboard/SKILL.md +14 -0
  239. package/departments/saas/skills/micro-saas-stack/SKILL.md +14 -0
  240. package/departments/saas/skills/mvp-build/SKILL.md +14 -0
  241. package/departments/saas/skills/niche-evaluate/SKILL.md +14 -0
  242. package/departments/saas/skills/onboarding-optimize/SKILL.md +14 -0
  243. package/departments/saas/skills/plg-setup/SKILL.md +14 -0
  244. package/departments/saas/skills/pricing-strategy/SKILL.md +14 -0
  245. package/departments/saas/skills/validate-idea/SKILL.md +14 -0
  246. package/departments/sales/SKILL.md +14 -0
  247. package/departments/sales/skills/challenger-sell/SKILL.md +14 -0
  248. package/departments/sales/skills/deal-qualify/SKILL.md +14 -0
  249. package/departments/sales/skills/discovery-call/SKILL.md +14 -0
  250. package/departments/sales/skills/forecast-revenue/SKILL.md +14 -0
  251. package/departments/sales/skills/negotiate-plan/SKILL.md +14 -0
  252. package/departments/sales/skills/objection-handle/SKILL.md +14 -0
  253. package/departments/sales/skills/pipeline-manage/SKILL.md +14 -0
  254. package/departments/sales/skills/pricing-negotiate/SKILL.md +14 -0
  255. package/departments/strategy/SKILL.md +14 -0
  256. package/departments/strategy/skills/blue-ocean/SKILL.md +14 -0
  257. package/departments/strategy/skills/bmc/SKILL.md +14 -0
  258. package/departments/strategy/skills/board-advisor/SKILL.md +14 -0
  259. package/departments/strategy/skills/cto-advisor/SKILL.md +14 -0
  260. package/departments/strategy/skills/extract-data/SKILL.md +14 -0
  261. package/departments/strategy/skills/five-forces/SKILL.md +14 -0
  262. package/departments/strategy/skills/growth-strategy/SKILL.md +14 -0
  263. package/departments/strategy/skills/moat-analysis/SKILL.md +14 -0
  264. package/departments/strategy/skills/position/SKILL.md +14 -0
  265. package/departments/strategy/skills/scenario-plan/SKILL.md +14 -0
  266. package/package.json +3 -3
  267. package/pyproject.toml +1 -1
  268. package/scripts/migrate_skills_kb_first.py +137 -0
@@ -63,6 +63,56 @@ if (-not $v2Installed) {
63
63
  # --- Performance timing ----------------------------------------------------
64
64
  $sw = [System.Diagnostics.Stopwatch]::StartNew()
65
65
 
66
+ # --- Flow marker cache invalidation (v2 — new turn resets ALLOW cache) ---
67
+ # Runs before the Synapse bridge so a stuck Python call cannot leave a
68
+ # stale marker across turns. Non-blocking — all failures swallowed.
69
+ try {
70
+ $sessionIdUps = ''
71
+ if ($payload -and $payload.session_id) {
72
+ $sessionIdUps = [string]$payload.session_id
73
+ }
74
+ if ($sessionIdUps) {
75
+ $arkaosRootUps = $env:ARKAOS_ROOT
76
+ if (-not $arkaosRootUps) {
77
+ $repoPathFileUps = Join-Path $env:USERPROFILE '.arkaos\.repo-path'
78
+ if (Test-Path -LiteralPath $repoPathFileUps) {
79
+ try {
80
+ $arkaosRootUps = (Get-Content -Raw -LiteralPath $repoPathFileUps -Encoding UTF8).Trim()
81
+ } catch { }
82
+ }
83
+ }
84
+ if (-not $arkaosRootUps) {
85
+ $arkaosRootUps = Join-Path $env:USERPROFILE '.arkaos'
86
+ }
87
+ $pythonForInvalidate = $null
88
+ $venvPyUps = Join-Path $env:USERPROFILE '.arkaos\venv\Scripts\python.exe'
89
+ if (Test-Path -LiteralPath $venvPyUps) {
90
+ $pythonForInvalidate = $venvPyUps
91
+ } else {
92
+ foreach ($cmd in 'python3','python','py') {
93
+ $resolvedUps = Get-Command $cmd -ErrorAction SilentlyContinue
94
+ if ($resolvedUps) { $pythonForInvalidate = $resolvedUps.Source; break }
95
+ }
96
+ }
97
+ if ($pythonForInvalidate) {
98
+ $pyCode = "import os`ntry:`n from core.workflow.marker_cache import invalidate_marker`n invalidate_marker(os.environ.get('SESSION_ID_UPS',''))`nexcept Exception:`n pass`ntry:`n from core.synapse.kb_cache import invalidate_obsidian_query`n invalidate_obsidian_query(os.environ.get('SESSION_ID_UPS',''))`nexcept Exception:`n pass"
99
+ $psi = New-Object System.Diagnostics.ProcessStartInfo
100
+ $psi.FileName = $pythonForInvalidate
101
+ $psi.Arguments = "-c `"$($pyCode -replace '"','\"' -replace "`r?`n",'; ')`""
102
+ $psi.UseShellExecute = $false
103
+ $psi.CreateNoWindow = $true
104
+ $psi.RedirectStandardOutput = $true
105
+ $psi.RedirectStandardError = $true
106
+ [void]$psi.EnvironmentVariables.Add('SESSION_ID_UPS', $sessionIdUps)
107
+ [void]$psi.EnvironmentVariables.Add('PYTHONPATH', $arkaosRootUps)
108
+ try {
109
+ $procUps = [System.Diagnostics.Process]::Start($psi)
110
+ if (-not $procUps.WaitForExit(1500)) { try { $procUps.Kill() } catch { } }
111
+ } catch { }
112
+ }
113
+ }
114
+ } catch { }
115
+
66
116
  # --- Sync version detection ------------------------------------------------
67
117
  $syncNotice = ''
68
118
  $arkaosHome = Join-Path $env:USERPROFILE '.arkaos'
@@ -88,6 +88,30 @@ if command -v jq &>/dev/null; then
88
88
  user_input=$(echo "$input" | jq -r '.userInput // .message // ""' 2>/dev/null)
89
89
  SESSION_ID=$(echo "$input" | jq -r '.session_id // ""' 2>/dev/null)
90
90
  fi
91
+
92
+ # ─── Flow marker cache invalidation (v2 — new turn, reset ALLOW cache) ──
93
+ # Cheap, non-blocking, runs before Synapse so a stuck Python later cannot
94
+ # leave a stale marker alive across turns.
95
+ if [ -n "$SESSION_ID" ] && command -v python3 &>/dev/null; then
96
+ _INVALIDATE_ROOT="${ARKAOS_ROOT:-}"
97
+ if [ -z "$_INVALIDATE_ROOT" ] && [ -f "$HOME/.arkaos/.repo-path" ]; then
98
+ _INVALIDATE_ROOT=$(cat "$HOME/.arkaos/.repo-path" 2>/dev/null)
99
+ fi
100
+ [ -z "$_INVALIDATE_ROOT" ] && _INVALIDATE_ROOT="$HOME/.arkaos"
101
+ SESSION_ID="$SESSION_ID" PYTHONPATH="$_INVALIDATE_ROOT" python3 -c "
102
+ import os
103
+ try:
104
+ from core.workflow.marker_cache import invalidate_marker
105
+ invalidate_marker(os.environ.get('SESSION_ID', ''))
106
+ except Exception:
107
+ pass
108
+ try:
109
+ from core.synapse.kb_cache import invalidate_obsidian_query
110
+ invalidate_obsidian_query(os.environ.get('SESSION_ID', ''))
111
+ except Exception:
112
+ pass
113
+ " 2>/dev/null || true
114
+ fi
91
115
  # Fallback: try to get the raw text
92
116
  if [ -z "$user_input" ]; then
93
117
  user_input=$(echo "$input" | head -c 2000)
@@ -0,0 +1,433 @@
1
+ """Auto-documentor — extracts session learnings and writes to Obsidian.
2
+
3
+ Parses the Claude Code transcript JSONL after Quality Gate approval,
4
+ synthesises learnings about external sources consulted, decisions made,
5
+ and deliverables produced, then invokes the Obsidian cataloger + relator
6
+ (Task #4 modules) to file structured, wikilinked notes into the vault.
7
+
8
+ The synthesis step is runtime- and model-agnostic: it delegates to the
9
+ active `LLMProvider` (see `core.runtime.llm_provider`). This module
10
+ NEVER picks a model — the provider / runtime / env does. When no
11
+ provider is available or the call fails, it falls through to a
12
+ deterministic template synthesiser that preserves every extracted fact.
13
+
14
+ ADR/Plan references:
15
+ - ~/.arkaos/plans/2026-04-20-intelligence-v2.md (Task #7 — Épico B)
16
+ - ~/.arkaos/plans/2026-04-20-llm-agnostic.md (Task #12/#13 — LLMProvider)
17
+ - core/obsidian/cataloger.py, core/obsidian/relator.py (Task #4)
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import json
23
+ import re
24
+ from dataclasses import dataclass, field
25
+ from pathlib import Path
26
+ from typing import Iterable
27
+
28
+ from core.obsidian import cataloger as _cataloger
29
+ from core.obsidian import relator as _relator
30
+ from core.obsidian.writer import ObsidianWriter
31
+
32
+
33
+ SAFE_SESSION_ID_RE = re.compile(r"^[A-Za-z0-9._-]{1,128}$")
34
+
35
+ _URL_RE = re.compile(r"https?://[^\s\)\]\"']+")
36
+ _FILE_PATH_RE = re.compile(r"(?:^|[\s`'])(/[A-Za-z0-9_./\-]+\.[A-Za-z0-9]+)")
37
+ _ROUTING_MARKER_RE = re.compile(
38
+ r"\[arka:routing\]\s*([\w-]+)\s*->\s*(\w+)", re.IGNORECASE
39
+ )
40
+ _DECISION_CUES = (
41
+ "decided to", "chose ", "selected ", "going with", "i'll use",
42
+ "we'll use", "opted for", "the approach is", "rationale:",
43
+ )
44
+ _EXTERNAL_RESEARCH_TOOLS = frozenset({
45
+ "WebFetch", "WebSearch",
46
+ "mcp__context7__query-docs", "mcp__context7__resolve-library-id",
47
+ "mcp__firecrawl__firecrawl_search",
48
+ "mcp__firecrawl__firecrawl_scrape",
49
+ "mcp__firecrawl__firecrawl_crawl",
50
+ "mcp__firecrawl__firecrawl_extract",
51
+ })
52
+
53
+ _AUTO_DOC_SUFFIX = "Auto-documented by ArkaOS"
54
+ _LLM_MAX_TOKENS = 1500
55
+
56
+ _SYSTEM_PROMPT = (
57
+ "You are ArkaOS's auto-documentor. Produce a concise knowledge note "
58
+ "(150-300 words) summarising the session. Structure: short intro, "
59
+ "then markdown sections for Key Facts, Decisions, and Sources. "
60
+ "Preserve every URL and file path verbatim. Use Obsidian wikilinks "
61
+ "([[Topic]]) for reusable concepts. No preamble, no sign-off, no "
62
+ "meta commentary about the model or prompt. Output only markdown."
63
+ )
64
+
65
+
66
+ @dataclass
67
+ class Learning:
68
+ """One synthesisable learning extracted from a session transcript."""
69
+
70
+ topic: str
71
+ content: str
72
+ sources: list[str] = field(default_factory=list)
73
+ metadata: dict = field(default_factory=dict)
74
+ decisions: list[str] = field(default_factory=list)
75
+
76
+
77
+ # ─── Extraction ────────────────────────────────────────────────────────
78
+
79
+
80
+ def extract_learnings(transcript_path: Path) -> list[Learning]:
81
+ """Parse a transcript and return structured Learning records."""
82
+ records = _load_transcript(transcript_path)
83
+ if not records:
84
+ return []
85
+ sources = _extract_sources(records)
86
+ decisions = _extract_decisions(records)
87
+ deliverables = _extract_deliverables(records)
88
+ content = _build_content_blob(records, decisions, deliverables)
89
+ if not content.strip() and not sources and not decisions:
90
+ return []
91
+ metadata = _detect_metadata(content, records)
92
+ topic = _derive_topic(records, metadata)
93
+ return [Learning(
94
+ topic=topic,
95
+ content=content,
96
+ sources=sources,
97
+ metadata=metadata,
98
+ decisions=decisions,
99
+ )]
100
+
101
+
102
+ def _load_transcript(path: Path) -> list[dict]:
103
+ if path is None or not path.exists():
104
+ return []
105
+ out: list[dict] = []
106
+ try:
107
+ raw = path.read_text(encoding="utf-8", errors="replace")
108
+ except OSError:
109
+ return []
110
+ for line in raw.splitlines():
111
+ if not line.strip():
112
+ continue
113
+ try:
114
+ out.append(json.loads(line))
115
+ except json.JSONDecodeError:
116
+ continue
117
+ return out
118
+
119
+
120
+ def _iter_content_items(record: dict) -> Iterable[dict]:
121
+ content = record.get("content")
122
+ if content is None:
123
+ content = record.get("message", {}).get("content")
124
+ if isinstance(content, list):
125
+ for item in content:
126
+ if isinstance(item, dict):
127
+ yield item
128
+ elif isinstance(content, str):
129
+ yield {"type": "text", "text": content}
130
+
131
+
132
+ def _role_of(record: dict) -> str:
133
+ return record.get("role") or record.get("message", {}).get("role") or ""
134
+
135
+
136
+ def _text_of(record: dict) -> str:
137
+ parts: list[str] = []
138
+ for item in _iter_content_items(record):
139
+ if "text" in item:
140
+ parts.append(str(item["text"]))
141
+ return "\n".join(parts)
142
+
143
+
144
+ def _extract_sources(records: list[dict]) -> list[str]:
145
+ seen: set[str] = set()
146
+ sources: list[str] = []
147
+ for rec in records:
148
+ for item in _iter_content_items(rec):
149
+ if item.get("type") == "tool_use":
150
+ _collect_tool_use_sources(item, sources, seen)
151
+ text = _text_of(rec)
152
+ for url in _URL_RE.findall(text):
153
+ if url not in seen:
154
+ seen.add(url)
155
+ sources.append(url)
156
+ return sources
157
+
158
+
159
+ def _collect_tool_use_sources(item: dict, sources: list[str], seen: set) -> None:
160
+ tool = item.get("name", "")
161
+ if tool not in _EXTERNAL_RESEARCH_TOOLS:
162
+ return
163
+ params = item.get("input") or {}
164
+ for key in ("url", "query", "libraryName", "topic"):
165
+ val = params.get(key)
166
+ if isinstance(val, str) and val and val not in seen:
167
+ seen.add(val)
168
+ sources.append(val)
169
+
170
+
171
+ def _extract_decisions(records: list[dict]) -> list[str]:
172
+ decisions: list[str] = []
173
+ for rec in records:
174
+ if _role_of(rec) != "assistant":
175
+ continue
176
+ text = _text_of(rec)
177
+ for match in _ROUTING_MARKER_RE.finditer(text):
178
+ decisions.append(f"routed: {match.group(1)} -> {match.group(2)}")
179
+ for line in text.splitlines():
180
+ low = line.lower().strip()
181
+ if any(cue in low for cue in _DECISION_CUES) and len(line) < 400:
182
+ decisions.append(line.strip())
183
+ return _dedupe_keep_order(decisions)
184
+
185
+
186
+ def _extract_deliverables(records: list[dict]) -> list[str]:
187
+ seen: set[str] = set()
188
+ out: list[str] = []
189
+ for rec in records:
190
+ for item in _iter_content_items(rec):
191
+ if item.get("type") != "tool_use":
192
+ continue
193
+ if item.get("name") not in ("Write", "Edit", "MultiEdit"):
194
+ continue
195
+ path = (item.get("input") or {}).get("file_path", "")
196
+ if path and path not in seen:
197
+ seen.add(path)
198
+ out.append(path)
199
+ return out
200
+
201
+
202
+ def _build_content_blob(
203
+ records: list[dict], decisions: list[str], deliverables: list[str]
204
+ ) -> str:
205
+ user_prompts = [
206
+ _text_of(r) for r in records if _role_of(r) == "user"
207
+ ]
208
+ assistant_finals = [
209
+ _text_of(r) for r in records if _role_of(r) == "assistant"
210
+ ][-3:]
211
+ sections: list[str] = []
212
+ if user_prompts:
213
+ sections.append("## Request\n\n" + user_prompts[0].strip()[:800])
214
+ if decisions:
215
+ sections.append("## Decisions\n\n" + "\n".join(f"- {d}" for d in decisions[:10]))
216
+ if deliverables:
217
+ sections.append("## Deliverables\n\n" + "\n".join(f"- `{p}`" for p in deliverables[:20]))
218
+ if assistant_finals:
219
+ sections.append("## Summary\n\n" + assistant_finals[-1].strip()[:1200])
220
+ return "\n\n".join(sections)
221
+
222
+
223
+ def _detect_metadata(content: str, records: list[dict]) -> dict:
224
+ low = content.lower()
225
+ meta: dict = {"source": "session-transcript"}
226
+ for rec in records:
227
+ if _role_of(rec) != "assistant":
228
+ continue
229
+ text = _text_of(rec)
230
+ for match in _ROUTING_MARKER_RE.finditer(text):
231
+ meta["dept"] = match.group(1).lower()
232
+ meta["agent"] = match.group(2).lower()
233
+ break
234
+ if "dept" in meta:
235
+ break
236
+ if "laravel" in low or "php" in low:
237
+ meta["stack"] = "Laravel"
238
+ elif "nuxt" in low or "vue" in low:
239
+ meta["stack"] = "Vue"
240
+ elif "next.js" in low or "react" in low:
241
+ meta["stack"] = "React"
242
+ elif "python" in low or "pydantic" in low:
243
+ meta["stack"] = "Python"
244
+ return meta
245
+
246
+
247
+ def _derive_topic(records: list[dict], metadata: dict) -> str:
248
+ for rec in records:
249
+ if _role_of(rec) != "user":
250
+ continue
251
+ text = _text_of(rec).strip()
252
+ if text:
253
+ first_line = text.splitlines()[0][:120]
254
+ return first_line or "Session Learning"
255
+ return metadata.get("dept", "Session Learning").title()
256
+
257
+
258
+ def _dedupe_keep_order(items: Iterable[str]) -> list[str]:
259
+ seen: set[str] = set()
260
+ out: list[str] = []
261
+ for item in items:
262
+ if item and item not in seen:
263
+ seen.add(item)
264
+ out.append(item)
265
+ return out
266
+
267
+
268
+ # ─── Synthesis ─────────────────────────────────────────────────────────
269
+
270
+
271
+ def synthesize(learning: Learning) -> str:
272
+ """Produce a markdown body for the learning.
273
+
274
+ Delegates to the active `LLMProvider` via `_call_llm`. If the
275
+ provider is unavailable, returns empty text, or raises, falls
276
+ through to a deterministic template that preserves every extracted
277
+ fact. No model name ever crosses this boundary.
278
+ """
279
+ llm_out = _call_llm(learning)
280
+ if llm_out:
281
+ return llm_out
282
+ return _template_synthesize(learning)
283
+
284
+
285
+ def _call_llm(learning: Learning) -> str:
286
+ from core.runtime import get_llm_provider
287
+ from core.runtime.llm_provider import LLMUnavailable
288
+
289
+ try:
290
+ provider = get_llm_provider()
291
+ if not provider.is_available():
292
+ return ""
293
+ prompt = _build_synthesis_prompt(learning)
294
+ response = provider.complete(
295
+ prompt, max_tokens=_LLM_MAX_TOKENS, system=_SYSTEM_PROMPT
296
+ )
297
+ return response.text.strip()
298
+ except LLMUnavailable:
299
+ return ""
300
+ except Exception: # noqa: BLE001 — LLM path must never crash the doc job
301
+ return ""
302
+
303
+
304
+ def _build_synthesis_prompt(learning: Learning) -> str:
305
+ lines = [f"Topic: {learning.topic}", ""]
306
+ if learning.content.strip():
307
+ lines.append("Session blob:")
308
+ lines.append(learning.content.strip())
309
+ lines.append("")
310
+ if learning.sources:
311
+ lines.append("Sources consulted:")
312
+ for src in learning.sources[:20]:
313
+ lines.append(f"- {src}")
314
+ lines.append("")
315
+ if learning.decisions:
316
+ lines.append("Decisions recorded:")
317
+ for dec in learning.decisions[:10]:
318
+ lines.append(f"- {dec}")
319
+ lines.append("")
320
+ if learning.metadata:
321
+ meta_pairs = sorted(learning.metadata.items())
322
+ lines.append("Metadata:")
323
+ for key, value in meta_pairs:
324
+ lines.append(f"- {key}: {value}")
325
+ lines.append("")
326
+ lines.append(
327
+ "Write the note now. Obey the system prompt. Output only markdown."
328
+ )
329
+ return "\n".join(lines)
330
+
331
+
332
+ def _template_synthesize(learning: Learning) -> str:
333
+ parts = [f"# {learning.topic}", ""]
334
+ parts.append(f"> {_AUTO_DOC_SUFFIX}.")
335
+ parts.append("")
336
+ if learning.content.strip():
337
+ parts.append(learning.content.strip())
338
+ parts.append("")
339
+ if learning.sources:
340
+ parts.append("## Sources")
341
+ parts.append("")
342
+ for src in learning.sources[:20]:
343
+ parts.append(f"- {src}")
344
+ parts.append("")
345
+ if learning.decisions:
346
+ parts.append("## Decisions")
347
+ parts.append("")
348
+ for dec in learning.decisions[:10]:
349
+ parts.append(f"- {dec}")
350
+ parts.append("")
351
+ return "\n".join(parts).rstrip() + "\n"
352
+
353
+
354
+ # ─── Orchestration ─────────────────────────────────────────────────────
355
+
356
+
357
+ def document_session(
358
+ transcript_path: Path,
359
+ session_id: str,
360
+ vault_path: Path,
361
+ qg_verdict: str,
362
+ ) -> list[Path]:
363
+ """Run the full pipeline. Returns the paths of every note written."""
364
+ if qg_verdict != "APPROVED":
365
+ return []
366
+ if not _safe_session_id(session_id):
367
+ return []
368
+ transcript_path = Path(transcript_path)
369
+ learnings = extract_learnings(transcript_path)
370
+ if not learnings:
371
+ return []
372
+ writer = ObsidianWriter(vault_path=vault_path)
373
+ written: list[Path] = []
374
+ for learning in learnings:
375
+ note_path = _document_one(learning, writer, vault_path, session_id)
376
+ if note_path:
377
+ written.append(note_path)
378
+ return written
379
+
380
+
381
+ def _document_one(
382
+ learning: Learning,
383
+ writer: ObsidianWriter,
384
+ vault_path: Path,
385
+ session_id: str,
386
+ ) -> Path | None:
387
+ body = synthesize(learning)
388
+ meta = dict(learning.metadata)
389
+ meta.setdefault("title", learning.topic[:80])
390
+ meta.setdefault("session", session_id)
391
+ meta.setdefault("auto_documented", True)
392
+ try:
393
+ plan = _cataloger.plan(body, meta)
394
+ except ValueError:
395
+ return None
396
+ note_path = _cataloger.execute(plan, body, writer)
397
+ _relate_note(note_path, body, vault_path, plan)
398
+ return note_path
399
+
400
+
401
+ def _relate_note(note_path: Path, body: str, vault_path: Path, plan) -> None:
402
+ try:
403
+ related = _relator.find_related(
404
+ content=body, vault=vault_path, exclude=note_path
405
+ )
406
+ except Exception:
407
+ return
408
+ if related:
409
+ _append_related_block(note_path, related)
410
+ _relator.update_back_references(note_path, related, note_path.stem)
411
+ if plan.applicable_mocs:
412
+ _relator.update_mocs(note_path.stem, list(plan.applicable_mocs), vault_path)
413
+
414
+
415
+ def _append_related_block(note_path: Path, related) -> None:
416
+ try:
417
+ current = note_path.read_text(encoding="utf-8")
418
+ except OSError:
419
+ return
420
+ block = _relator.generate_wikilinks_block(related)
421
+ if not block or "## Related" in current:
422
+ return
423
+ sep = "" if current.endswith("\n") else "\n"
424
+ try:
425
+ note_path.write_text(current + sep + "\n" + block, encoding="utf-8")
426
+ except OSError:
427
+ return
428
+
429
+
430
+ def _safe_session_id(session_id: str) -> bool:
431
+ if not isinstance(session_id, str) or not session_id:
432
+ return False
433
+ return bool(SAFE_SESSION_ID_RE.match(session_id))
@@ -1,5 +1,16 @@
1
- """Job queue — SQLite-based persistent job tracking."""
1
+ """Job queue — SQLite tracker + filesystem-backed auto-doc worker."""
2
2
 
3
- from core.jobs.manager import JobManager, Job
3
+ from core.jobs.manager import Job, JobManager
4
+ from core.jobs.auto_doc_worker import (
5
+ enqueue_job,
6
+ process_pending_jobs,
7
+ run_single_job,
8
+ )
4
9
 
5
- __all__ = ["JobManager", "Job"]
10
+ __all__ = [
11
+ "Job",
12
+ "JobManager",
13
+ "enqueue_job",
14
+ "process_pending_jobs",
15
+ "run_single_job",
16
+ ]