arkaos 2.17.1 → 2.17.4

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 (77) hide show
  1. package/VERSION +1 -1
  2. package/arka/SKILL.md +9 -67
  3. package/arka/skills/comfyui/SKILL.md +50 -12
  4. package/arka/skills/conclave/SKILL.md +43 -141
  5. package/arka/skills/conclave/references/advisors.md +36 -0
  6. package/arka/skills/human-writing/SKILL.md +15 -100
  7. package/arka/skills/human-writing/references/forbidden-patterns.md +32 -0
  8. package/config/hooks/post-tool-use.sh +72 -0
  9. package/config/hooks/session-start.sh +16 -0
  10. package/config/hooks/user-prompt-submit.ps1 +2 -2
  11. package/config/hooks/user-prompt-submit.sh +102 -26
  12. package/core/agents/__pycache__/behavior_enforcer.cpython-313.pyc +0 -0
  13. package/core/agents/__pycache__/dna_registry.cpython-313.pyc +0 -0
  14. package/core/agents/adapters/__pycache__/disc_adapter.cpython-313.pyc +0 -0
  15. package/core/agents/adapters/disc_adapter.py +149 -0
  16. package/core/agents/behavior_enforcer.py +255 -0
  17. package/core/agents/dna_registry.py +235 -0
  18. package/core/forge/__init__.py +36 -0
  19. package/core/forge/__pycache__/__init__.cpython-313.pyc +0 -0
  20. package/core/forge/__pycache__/orchestrator.cpython-313.pyc +0 -0
  21. package/core/forge/__pycache__/runtime_dispatcher.cpython-313.pyc +0 -0
  22. package/core/forge/orchestrator.py +770 -0
  23. package/core/forge/runtime_dispatcher.py +465 -0
  24. package/core/governance/__pycache__/quality_api.cpython-313.pyc +0 -0
  25. package/core/governance/__pycache__/quality_router.cpython-313.pyc +0 -0
  26. package/core/governance/__pycache__/review_workflow.cpython-313.pyc +0 -0
  27. package/core/governance/quality_api.py +280 -0
  28. package/core/governance/quality_router.py +304 -0
  29. package/core/governance/review_workflow.py +386 -0
  30. package/core/memory/__pycache__/compressor.cpython-313.pyc +0 -0
  31. package/core/memory/__pycache__/rehydrator.cpython-313.pyc +0 -0
  32. package/core/memory/__pycache__/session_store.cpython-313.pyc +0 -0
  33. package/core/memory/compressor.py +269 -0
  34. package/core/memory/rehydrator.py +204 -0
  35. package/core/memory/session_store.py +256 -0
  36. package/core/runtime/__pycache__/context_compactor.cpython-313.pyc +0 -0
  37. package/core/runtime/__pycache__/subagent.cpython-313.pyc +0 -0
  38. package/core/synapse/__init__.py +10 -3
  39. package/core/synapse/__pycache__/__init__.cpython-313.pyc +0 -0
  40. package/core/synapse/__pycache__/engine.cpython-313.pyc +0 -0
  41. package/core/synapse/__pycache__/kb_cache.cpython-313.pyc +0 -0
  42. package/core/synapse/__pycache__/layers.cpython-313.pyc +0 -0
  43. package/core/synapse/engine.py +27 -16
  44. package/core/synapse/kb_cache.py +382 -0
  45. package/core/synapse/layers.py +253 -50
  46. package/core/sync/__pycache__/self_healing.cpython-313.pyc +0 -0
  47. package/core/workflow/__pycache__/announcer.cpython-313.pyc +0 -0
  48. package/core/workflow/__pycache__/dashboard.cpython-313.pyc +0 -0
  49. package/core/workflow/__pycache__/enforcer.cpython-313.pyc +0 -0
  50. package/core/workflow/__pycache__/rules_registry.cpython-313.pyc +0 -0
  51. package/core/workflow/__pycache__/state.cpython-313.pyc +0 -0
  52. package/core/workflow/announcer.py +246 -0
  53. package/core/workflow/dashboard.py +194 -0
  54. package/core/workflow/enforcer.py +234 -0
  55. package/core/workflow/recovery.py +196 -0
  56. package/core/workflow/rules_registry.py +484 -0
  57. package/core/workflow/session_summary.py +204 -0
  58. package/core/workflow/state.py +12 -2
  59. package/departments/dev/SKILL.md +10 -42
  60. package/departments/dev/skills/agent-design/SKILL.md +6 -26
  61. package/departments/dev/skills/ci-cd-pipeline/SKILL.md +6 -29
  62. package/departments/dev/skills/db-schema/SKILL.md +6 -24
  63. package/departments/dev/skills/dependency-audit/SKILL.md +1 -3
  64. package/departments/dev/skills/incident/SKILL.md +6 -24
  65. package/departments/dev/skills/mcp-builder/SKILL.md +4 -17
  66. package/departments/dev/skills/observability/SKILL.md +6 -29
  67. package/departments/dev/skills/performance-profiler/SKILL.md +5 -26
  68. package/departments/dev/skills/rag-architect/SKILL.md +5 -23
  69. package/departments/dev/skills/release/SKILL.md +4 -17
  70. package/departments/dev/skills/spec/SKILL.md +47 -148
  71. package/departments/landing/skills/landing-gen/SKILL.md +3 -15
  72. package/departments/marketing/skills/cold-email/SKILL.md +5 -17
  73. package/departments/marketing/skills/programmatic-seo/SKILL.md +6 -21
  74. package/departments/strategy/skills/board-advisor/SKILL.md +7 -21
  75. package/package.json +1 -1
  76. package/pyproject.toml +1 -1
  77. package/scripts/synapse-bridge.py +27 -3
@@ -0,0 +1,382 @@
1
+ """Knowledge session cache — project-scoped on-demand knowledge retrieval.
2
+
3
+ Provides session-scoped caching for L3.5 knowledge retrieval results.
4
+ Enables automatic knowledge injection when topics overlap across conversation turns
5
+ within the same project context.
6
+
7
+ Architecture:
8
+ - Path: /tmp/arkaos-kb-{project_hash}/{session_id}.json
9
+ - Max entries per session: 50
10
+ - TTL: 30 minutes
11
+ - Project-scoped via cwd hash (isolates knowledge by project)
12
+ - Auto-inject when Jaccard topic overlap >= 0.3
13
+ """
14
+
15
+ import hashlib
16
+ import json
17
+ import time
18
+ from pathlib import Path
19
+ from typing import Any, Optional
20
+
21
+
22
+ STOP_WORDS = {
23
+ "a",
24
+ "an",
25
+ "the",
26
+ "and",
27
+ "or",
28
+ "but",
29
+ "in",
30
+ "on",
31
+ "at",
32
+ "to",
33
+ "for",
34
+ "of",
35
+ "with",
36
+ "by",
37
+ "from",
38
+ "as",
39
+ "is",
40
+ "was",
41
+ "are",
42
+ "were",
43
+ "been",
44
+ "be",
45
+ "have",
46
+ "has",
47
+ "had",
48
+ "do",
49
+ "does",
50
+ "did",
51
+ "will",
52
+ "would",
53
+ "could",
54
+ "should",
55
+ "may",
56
+ "might",
57
+ "must",
58
+ "shall",
59
+ "can",
60
+ "need",
61
+ "dare",
62
+ "ought",
63
+ "used",
64
+ "it",
65
+ "its",
66
+ "this",
67
+ "that",
68
+ "these",
69
+ "those",
70
+ "i",
71
+ "you",
72
+ "he",
73
+ "she",
74
+ "we",
75
+ "they",
76
+ "what",
77
+ "which",
78
+ "who",
79
+ "whom",
80
+ "whose",
81
+ "where",
82
+ "when",
83
+ "why",
84
+ "how",
85
+ "all",
86
+ "each",
87
+ "every",
88
+ "both",
89
+ "few",
90
+ "more",
91
+ "most",
92
+ "other",
93
+ "some",
94
+ "such",
95
+ "no",
96
+ "nor",
97
+ "not",
98
+ "only",
99
+ "own",
100
+ "same",
101
+ "so",
102
+ "than",
103
+ "too",
104
+ "very",
105
+ "just",
106
+ "also",
107
+ "now",
108
+ "here",
109
+ "there",
110
+ "then",
111
+ }
112
+
113
+
114
+ class KBSessionCache:
115
+ """Project-scoped session cache for knowledge retrieval.
116
+
117
+ Stores L3.5 search results per session with topic extraction for
118
+ overlap-based auto-injection.
119
+
120
+ Usage:
121
+ cache = KBSessionCache(session_id="abc123", project_path="/path/to/project")
122
+ cache.store("deploy arkaos on kubernetes", [{"text": "...", "source": "..."}])
123
+ results = cache.retrieve(topics={"deploy", "kubernetes"})
124
+ """
125
+
126
+ def __init__(
127
+ self,
128
+ session_id: str,
129
+ project_path: Optional[str] = None,
130
+ cache_dir: Optional[str] = None,
131
+ max_entries: int = 50,
132
+ ttl_seconds: int = 1800,
133
+ ) -> None:
134
+ self._session_id = session_id
135
+ self._max_entries = max_entries
136
+ self._ttl_seconds = ttl_seconds
137
+
138
+ if cache_dir:
139
+ self._cache_dir = Path(cache_dir)
140
+ else:
141
+ project_hash = self._hash_project(project_path or "")
142
+ self._cache_dir = Path("/tmp") / f"arkaos-kb-{project_hash}"
143
+
144
+ self._cache_file = self._cache_dir / f"{session_id}.json"
145
+ self._ensure_dir()
146
+
147
+ @staticmethod
148
+ def _hash_project(path: str) -> str:
149
+ """Create short hash of project path for directory naming."""
150
+ if not path:
151
+ return "default"
152
+ h = hashlib.md5(path.encode(), usedforsecurity=False)
153
+ return h.hexdigest()[:12]
154
+
155
+ def _ensure_dir(self) -> None:
156
+ """Ensure cache directory exists."""
157
+ self._cache_dir.mkdir(parents=True, exist_ok=True)
158
+
159
+ def _load(self) -> dict[str, Any]:
160
+ """Load cache from disk."""
161
+ if not self._cache_file.exists():
162
+ return {}
163
+ try:
164
+ return json.loads(self._cache_file.read_text())
165
+ except (json.JSONDecodeError, OSError):
166
+ return {}
167
+
168
+ def _save(self, data: dict[str, Any]) -> None:
169
+ """Save cache to disk."""
170
+ self._cache_file.write_text(json.dumps(data, indent=2))
171
+
172
+ def extract_topics(self, query: str) -> set[str]:
173
+ """Extract key topics from a query string.
174
+
175
+ Removes stop words and extracts nouns, verbs, and key phrases.
176
+ Used for Jaccard overlap calculation.
177
+
178
+ Args:
179
+ query: Raw query string
180
+
181
+ Returns:
182
+ Set of topic keywords
183
+ """
184
+ if not query:
185
+ return set()
186
+
187
+ words = query.lower().split()
188
+ topics = {w.strip(".,!?;:()[]{}") for w in words if len(w) > 2 and w not in STOP_WORDS}
189
+ return topics
190
+
191
+ @staticmethod
192
+ def jaccard(topics1: set[str], topics2: set[str]) -> float:
193
+ """Calculate Jaccard similarity between two topic sets.
194
+
195
+ Args:
196
+ topics1: First set of topics
197
+ topics2: Second set of topics
198
+
199
+ Returns:
200
+ Jaccard coefficient between 0.0 and 1.0
201
+ """
202
+ if not topics1 or not topics2:
203
+ return 0.0
204
+ intersection = len(topics1 & topics2)
205
+ union = len(topics1 | topics2)
206
+ return intersection / union if union > 0 else 0.0
207
+
208
+ def store(self, query: str, results: list[dict]) -> set[str]:
209
+ """Store knowledge retrieval results in session cache.
210
+
211
+ Args:
212
+ query: The original user query
213
+ results: List of dicts with 'text', 'source', 'heading', 'score'
214
+
215
+ Returns:
216
+ Set of extracted topics for this query (for overlap detection)
217
+ """
218
+ if not results:
219
+ return set()
220
+
221
+ topics = self.extract_topics(query)
222
+ now = time.time()
223
+
224
+ cache = self._load()
225
+
226
+ entry = {
227
+ "query": query,
228
+ "topics": list(topics),
229
+ "normalized_topics": sorted(topics),
230
+ "snippets": [
231
+ {
232
+ "text": r.get("text", "")[:300],
233
+ "source": r.get("source", ""),
234
+ "heading": r.get("heading", ""),
235
+ "score": r.get("score", 0.0),
236
+ }
237
+ for r in results
238
+ ],
239
+ "timestamp": now,
240
+ }
241
+
242
+ query_hash = hashlib.sha256(query.lower().encode(), usedforsecurity=False).hexdigest()[:16]
243
+ cache[query_hash] = entry
244
+
245
+ cache["_meta"] = {
246
+ "session_id": self._session_id,
247
+ "updated_at": now,
248
+ "entry_count": len(cache) - 1,
249
+ }
250
+
251
+ if len(cache) - 1 > self._max_entries:
252
+ cache = self._evict_oldest(cache)
253
+
254
+ self._save(cache)
255
+ return topics
256
+
257
+ def retrieve(
258
+ self,
259
+ query: Optional[str] = None,
260
+ topics: Optional[set[str]] = None,
261
+ threshold: float = 0.3,
262
+ ) -> list[dict]:
263
+ """Retrieve cached knowledge by query or topic overlap.
264
+
265
+ Args:
266
+ query: Optional exact query to retrieve
267
+ topics: Optional topic set for Jaccard matching
268
+ threshold: Minimum Jaccard similarity for auto-inject (default 0.3)
269
+
270
+ Returns:
271
+ List of snippet dicts from the best matching cached entry
272
+ """
273
+ cache = self._load()
274
+ if not cache or "_meta" not in cache:
275
+ return []
276
+
277
+ now = time.time()
278
+ candidates: list[tuple[float, dict]] = []
279
+
280
+ for query_hash, entry in cache.items():
281
+ if query_hash == "_meta":
282
+ continue
283
+
284
+ entry_age = now - entry.get("timestamp", 0)
285
+ if entry_age > self._ttl_seconds:
286
+ continue
287
+
288
+ if query:
289
+ entry_hash = hashlib.sha256(
290
+ query.lower().encode(), usedforsecurity=False
291
+ ).hexdigest()[:16]
292
+ if entry_hash == query_hash:
293
+ return entry.get("snippets", [])
294
+
295
+ if topics:
296
+ entry_topics = set(entry.get("normalized_topics", []))
297
+ score = self.jaccard(topics, entry_topics)
298
+ if score >= threshold:
299
+ candidates.append((score, entry))
300
+
301
+ if not candidates:
302
+ return []
303
+
304
+ candidates.sort(key=lambda x: x[0], reverse=True)
305
+ best = candidates[0][1]
306
+ return best.get("snippets", [])
307
+
308
+ def get_overlap(self, query: str, threshold: float = 0.3) -> list[dict]:
309
+ """Get overlapping knowledge for a query using Jaccard similarity.
310
+
311
+ Convenience method that extracts topics and retrieves.
312
+
313
+ Args:
314
+ query: Query string to match against cached entries
315
+ threshold: Minimum Jaccard similarity (default 0.3)
316
+
317
+ Returns:
318
+ List of snippet dicts from matching entries
319
+ """
320
+ topics = self.extract_topics(query)
321
+ return self.retrieve(topics=topics, threshold=threshold)
322
+
323
+ def _evict_oldest(self, cache: dict[str, Any]) -> dict[str, Any]:
324
+ """Evict oldest entries when cache exceeds max_entries."""
325
+ entries = [(k, v) for k, v in cache.items() if k != "_meta"]
326
+ entries.sort(key=lambda x: x[1].get("timestamp", 0))
327
+
328
+ keep_count = self._max_entries - 1
329
+ keep_entries = dict(entries[-keep_count:]) if len(entries) > keep_count else dict(entries)
330
+ keep_entries["_meta"] = cache.get("_meta", {})
331
+
332
+ return keep_entries
333
+
334
+ def clear(self) -> None:
335
+ """Clear all cached entries for this session."""
336
+ if self._cache_file.exists():
337
+ self._cache_file.unlink()
338
+
339
+ def stats(self) -> dict[str, Any]:
340
+ """Get cache statistics."""
341
+ cache = self._load()
342
+ now = time.time()
343
+ valid_entries = 0
344
+ oldest = None
345
+ newest = None
346
+
347
+ for k, v in cache.items():
348
+ if k == "_meta":
349
+ continue
350
+ age = now - v.get("timestamp", 0)
351
+ if age <= self._ttl_seconds:
352
+ valid_entries += 1
353
+ if oldest is None or v.get("timestamp", 0) < oldest:
354
+ oldest = v.get("timestamp", 0)
355
+ if newest is None or v.get("timestamp", 0) > newest:
356
+ newest = v.get("timestamp", 0)
357
+
358
+ return {
359
+ "session_id": self._session_id,
360
+ "cache_dir": str(self._cache_dir),
361
+ "total_entries": len(cache) - 1,
362
+ "valid_entries": valid_entries,
363
+ "oldest_entry": oldest,
364
+ "newest_entry": newest,
365
+ "ttl_seconds": self._ttl_seconds,
366
+ }
367
+
368
+
369
+ def extract_topics(query: str) -> set[str]:
370
+ """Standalone topic extraction function.
371
+
372
+ Convenience wrapper for use in external contexts.
373
+ """
374
+ return KBSessionCache(session_id="", project_path="").extract_topics(query)
375
+
376
+
377
+ def jaccard_similarity(topics1: set[str], topics2: set[str]) -> float:
378
+ """Standalone Jaccard similarity calculation.
379
+
380
+ Convenience wrapper for use in external contexts.
381
+ """
382
+ return KBSessionCache.jaccard(topics1, topics2)