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
@@ -25,17 +25,19 @@ from typing import Optional, Any
25
25
  @dataclass
26
26
  class LayerResult:
27
27
  """Result from computing a single layer."""
28
+
28
29
  layer_id: str
29
- tag: str # e.g., "[dept:dev]"
30
- content: str # Full content for this layer
30
+ tag: str # e.g., "[dept:dev]"
31
+ content: str # Full content for this layer
31
32
  tokens_est: int # Estimated token count
32
33
  compute_ms: int # Time to compute in milliseconds
33
- cached: bool # Whether this was served from cache
34
+ cached: bool # Whether this was served from cache
34
35
 
35
36
 
36
37
  @dataclass
37
38
  class PromptContext:
38
39
  """Input context for layer computation."""
40
+
39
41
  user_input: str = ""
40
42
  cwd: str = ""
41
43
  git_branch: str = ""
@@ -87,6 +89,7 @@ class Layer(ABC):
87
89
 
88
90
  # --- L0: Constitution ---
89
91
 
92
+
90
93
  class ConstitutionLayer(Layer):
91
94
  """L0: Compressed Constitution rules. Highest priority, longest cache."""
92
95
 
@@ -168,23 +171,49 @@ class DepartmentLayer(Layer):
168
171
  text = ctx.user_input.lower()
169
172
 
170
173
  # Check for explicit command prefix first
171
- prefix_match = re.match(r"^/(\w+)\s", text)
174
+ # Use [-\w] to handle hyphenated commands like /arka-do
175
+ prefix_match = re.match(r"^/([-\w]+)\s", text)
172
176
  if prefix_match:
173
177
  prefix = prefix_match.group(1)
174
178
  dept_map = {
175
- "dev": "dev", "mkt": "marketing", "fin": "finance",
176
- "strat": "strategy", "ops": "ops", "ecom": "ecom",
177
- "kb": "kb", "brand": "brand", "saas": "saas",
178
- "landing": "landing", "community": "community",
179
- "content": "content", "pm": "pm", "lead": "lead",
180
- "sales": "sales", "org": "org",
179
+ "dev": "dev",
180
+ "mkt": "marketing",
181
+ "fin": "finance",
182
+ "strat": "strategy",
183
+ "ops": "ops",
184
+ "ecom": "ecom",
185
+ "kb": "kb",
186
+ "brand": "brand",
187
+ "saas": "saas",
188
+ "landing": "landing",
189
+ "community": "community",
190
+ "content": "content",
191
+ "pm": "pm",
192
+ "lead": "lead",
193
+ "sales": "sales",
194
+ "org": "org",
195
+ "do": "orchestrator",
196
+ "arka-do": "orchestrator",
181
197
  }
182
198
  if prefix in dept_map:
183
199
  dept = dept_map[prefix]
184
200
  ms = int((time.time() - start) * 1000)
201
+ if dept == "orchestrator":
202
+ return LayerResult(
203
+ layer_id=self.id,
204
+ tag="",
205
+ content="",
206
+ tokens_est=0,
207
+ compute_ms=ms,
208
+ cached=False,
209
+ )
185
210
  return LayerResult(
186
- layer_id=self.id, tag=f"[dept:{dept}]",
187
- content=dept, tokens_est=1, compute_ms=ms, cached=False,
211
+ layer_id=self.id,
212
+ tag=f"[dept:{dept}]",
213
+ content=dept,
214
+ tokens_est=1,
215
+ compute_ms=ms,
216
+ cached=False,
188
217
  )
189
218
 
190
219
  # Pattern matching on input text
@@ -199,13 +228,18 @@ class DepartmentLayer(Layer):
199
228
 
200
229
  ms = int((time.time() - start) * 1000)
201
230
  return LayerResult(
202
- layer_id=self.id, tag=tag, content=dept,
203
- tokens_est=1, compute_ms=ms, cached=False,
231
+ layer_id=self.id,
232
+ tag=tag,
233
+ content=dept,
234
+ tokens_est=1,
235
+ compute_ms=ms,
236
+ cached=False,
204
237
  )
205
238
 
206
239
 
207
240
  # --- L2: Agent Context ---
208
241
 
242
+
209
243
  class AgentLayer(Layer):
210
244
  """L2: Active agent profile and recent gotchas."""
211
245
 
@@ -234,8 +268,12 @@ class AgentLayer(Layer):
234
268
  if not agent_id:
235
269
  ms = int((time.time() - start) * 1000)
236
270
  return LayerResult(
237
- layer_id=self.id, tag="", content="",
238
- tokens_est=0, compute_ms=ms, cached=False,
271
+ layer_id=self.id,
272
+ tag="",
273
+ content="",
274
+ tokens_est=0,
275
+ compute_ms=ms,
276
+ cached=False,
239
277
  )
240
278
 
241
279
  agent = self._registry.get(agent_id, {})
@@ -244,13 +282,18 @@ class AgentLayer(Layer):
244
282
 
245
283
  ms = int((time.time() - start) * 1000)
246
284
  return LayerResult(
247
- layer_id=self.id, tag=tag, content=agent_id,
248
- tokens_est=3, compute_ms=ms, cached=False,
285
+ layer_id=self.id,
286
+ tag=tag,
287
+ content=agent_id,
288
+ tokens_est=3,
289
+ compute_ms=ms,
290
+ cached=False,
249
291
  )
250
292
 
251
293
 
252
294
  # --- L3: Project Context ---
253
295
 
296
+
254
297
  class ProjectLayer(Layer):
255
298
  """L3: Active project name and stack."""
256
299
 
@@ -281,13 +324,18 @@ class ProjectLayer(Layer):
281
324
  tag = f"[{' '.join(parts)}]" if parts else ""
282
325
  ms = int((time.time() - start) * 1000)
283
326
  return LayerResult(
284
- layer_id=self.id, tag=tag, content=ctx.project_name or "",
285
- tokens_est=len(parts), compute_ms=ms, cached=False,
327
+ layer_id=self.id,
328
+ tag=tag,
329
+ content=ctx.project_name or "",
330
+ tokens_est=len(parts),
331
+ compute_ms=ms,
332
+ cached=False,
286
333
  )
287
334
 
288
335
 
289
336
  # --- L4: Git Branch ---
290
337
 
338
+
291
339
  class BranchLayer(Layer):
292
340
  """L4: Current git branch (hidden for main/master/dev)."""
293
341
 
@@ -314,13 +362,18 @@ class BranchLayer(Layer):
314
362
 
315
363
  ms = int((time.time() - start) * 1000)
316
364
  return LayerResult(
317
- layer_id=self.id, tag=tag, content=branch,
318
- tokens_est=1 if tag else 0, compute_ms=ms, cached=False,
365
+ layer_id=self.id,
366
+ tag=tag,
367
+ content=branch,
368
+ tokens_est=1 if tag else 0,
369
+ compute_ms=ms,
370
+ cached=False,
319
371
  )
320
372
 
321
373
 
322
374
  # --- L5: Command Hints ---
323
375
 
376
+
324
377
  class CommandHintsLayer(Layer):
325
378
  """L5: Matching commands from the registry for non-explicit requests."""
326
379
 
@@ -347,12 +400,17 @@ class CommandHintsLayer(Layer):
347
400
  start = time.time()
348
401
  text = ctx.user_input.lower()
349
402
 
350
- # Skip if already an explicit command
351
- if text.startswith("/"):
403
+ # Skip if already an explicit command — EXCEPT /arka-do which needs hints
404
+ # /do-style commands should still get command hints for sub-commands
405
+ if text.startswith("/") and not text.startswith("/arka-do"):
352
406
  ms = int((time.time() - start) * 1000)
353
407
  return LayerResult(
354
- layer_id=self.id, tag="", content="",
355
- tokens_est=0, compute_ms=ms, cached=False,
408
+ layer_id=self.id,
409
+ tag="",
410
+ content="",
411
+ tokens_est=0,
412
+ compute_ms=ms,
413
+ cached=False,
356
414
  )
357
415
 
358
416
  # Score commands by keyword match
@@ -369,13 +427,18 @@ class CommandHintsLayer(Layer):
369
427
  tags = " ".join(f"[hint:{h}]" for h in hints)
370
428
  ms = int((time.time() - start) * 1000)
371
429
  return LayerResult(
372
- layer_id=self.id, tag=tags, content=" ".join(hints),
373
- tokens_est=len(hints) * 2, compute_ms=ms, cached=False,
430
+ layer_id=self.id,
431
+ tag=tags,
432
+ content=" ".join(hints),
433
+ tokens_est=len(hints) * 2,
434
+ compute_ms=ms,
435
+ cached=False,
374
436
  )
375
437
 
376
438
 
377
439
  # --- L6: Quality Gate Status ---
378
440
 
441
+
379
442
  class QualityGateLayer(Layer):
380
443
  """L6: Current quality gate status and recent verdicts."""
381
444
 
@@ -397,17 +460,41 @@ class QualityGateLayer(Layer):
397
460
 
398
461
  def compute(self, ctx: PromptContext) -> LayerResult:
399
462
  start = time.time()
400
- # Quality Gate status is contextual — loaded from governance engine
463
+ try:
464
+ from core.governance.quality_api import list_pending, list_approved
465
+
466
+ pending = list_pending()
467
+ approved = list_approved(limit=3)
468
+ except Exception:
469
+ pending = []
470
+ approved = []
471
+
472
+ parts = []
473
+ if pending:
474
+ parts.append(f"{len(pending)} pending")
475
+ titles = [p.get("title", "")[:30] for p in pending[:2]]
476
+ parts.append(f"pending: {'; '.join(titles)}")
477
+ if approved:
478
+ parts.append(f"recent approved: {len(approved)}")
479
+
401
480
  tag = "[qg:active]"
481
+ content = " | ".join(parts) if parts else "active"
482
+ tokens = len(content.split())
483
+
402
484
  ms = int((time.time() - start) * 1000)
403
485
  return LayerResult(
404
- layer_id=self.id, tag=tag, content="active",
405
- tokens_est=1, compute_ms=ms, cached=False,
486
+ layer_id=self.id,
487
+ tag=tag,
488
+ content=content,
489
+ tokens_est=tokens,
490
+ compute_ms=ms,
491
+ cached=False,
406
492
  )
407
493
 
408
494
 
409
495
  # --- L7: Time Signal ---
410
496
 
497
+
411
498
  class TimeLayer(Layer):
412
499
  """L7: Time-of-day signal for context-aware behavior."""
413
500
 
@@ -433,6 +520,7 @@ class TimeLayer(Layer):
433
520
  def compute(self, ctx: PromptContext) -> LayerResult:
434
521
  start = time.time()
435
522
  import datetime
523
+
436
524
  hour = datetime.datetime.now().hour
437
525
  if 5 <= hour < 12:
438
526
  period = "morning"
@@ -444,13 +532,18 @@ class TimeLayer(Layer):
444
532
  tag = f"[time:{period}]"
445
533
  ms = int((time.time() - start) * 1000)
446
534
  return LayerResult(
447
- layer_id=self.id, tag=tag, content=period,
448
- tokens_est=1, compute_ms=ms, cached=False,
535
+ layer_id=self.id,
536
+ tag=tag,
537
+ content=period,
538
+ tokens_est=1,
539
+ compute_ms=ms,
540
+ cached=False,
449
541
  )
450
542
 
451
543
 
452
544
  # --- L3.5: Knowledge Retrieval ---
453
545
 
546
+
454
547
  class KnowledgeRetrievalLayer(Layer):
455
548
  """L3.5: Semantic knowledge retrieval from vector DB.
456
549
 
@@ -459,7 +552,9 @@ class KnowledgeRetrievalLayer(Layer):
459
552
  is unavailable or empty.
460
553
  """
461
554
 
462
- def __init__(self, vector_store: Any = None, max_chunks: int = 3, max_tokens: int = 400) -> None:
555
+ def __init__(
556
+ self, vector_store: Any = None, max_chunks: int = 3, max_tokens: int = 400
557
+ ) -> None:
463
558
  self._store = vector_store
464
559
  self._max_chunks = max_chunks
465
560
  self._max_tokens = max_tokens
@@ -485,26 +580,50 @@ class KnowledgeRetrievalLayer(Layer):
485
580
 
486
581
  if not self._store or not ctx.user_input:
487
582
  return LayerResult(
488
- layer_id=self.id, tag="", content="",
489
- tokens_est=0, compute_ms=0, cached=False,
583
+ layer_id=self.id,
584
+ tag="",
585
+ content="",
586
+ tokens_est=0,
587
+ compute_ms=0,
588
+ cached=False,
490
589
  )
491
590
 
492
591
  try:
493
592
  results = self._store.search(ctx.user_input, top_k=self._max_chunks)
494
593
  except Exception:
495
594
  return LayerResult(
496
- layer_id=self.id, tag="", content="",
497
- tokens_est=0, compute_ms=0, cached=False,
595
+ layer_id=self.id,
596
+ tag="",
597
+ content="",
598
+ tokens_est=0,
599
+ compute_ms=0,
600
+ cached=False,
498
601
  )
499
602
 
500
603
  if not results:
501
604
  ms = int((time.time() - start) * 1000)
502
605
  return LayerResult(
503
- layer_id=self.id, tag="", content="",
504
- tokens_est=0, compute_ms=ms, cached=False,
606
+ layer_id=self.id,
607
+ tag="",
608
+ content="",
609
+ tokens_est=0,
610
+ compute_ms=ms,
611
+ cached=False,
505
612
  )
506
613
 
507
- # Build compact knowledge context
614
+ session_id = ctx.extra.get("session_id", "default") if ctx.extra else "default"
615
+ project_path = ctx.cwd or None
616
+
617
+ overlapping: list[dict] = []
618
+ try:
619
+ from core.synapse.kb_cache import KBSessionCache
620
+
621
+ cache = KBSessionCache(session_id=session_id, project_path=project_path)
622
+ cache.store(ctx.user_input, results)
623
+ overlapping = cache.get_overlap(ctx.user_input, threshold=0.3)
624
+ except Exception:
625
+ pass
626
+
508
627
  snippets = []
509
628
  total_tokens = 0
510
629
  for r in results:
@@ -517,25 +636,44 @@ class KnowledgeRetrievalLayer(Layer):
517
636
  snippets.append(snippet)
518
637
  total_tokens += tokens
519
638
 
520
- if not snippets:
639
+ if not snippets and not overlapping:
521
640
  ms = int((time.time() - start) * 1000)
522
641
  return LayerResult(
523
- layer_id=self.id, tag="", content="",
524
- tokens_est=0, compute_ms=ms, cached=False,
642
+ layer_id=self.id,
643
+ tag="",
644
+ content="",
645
+ tokens_est=0,
646
+ compute_ms=ms,
647
+ cached=False,
525
648
  )
526
649
 
527
- content = " | ".join(snippets)
528
- tag = f"[knowledge:{len(snippets)} chunks]"
650
+ parts: list[str] = []
651
+ if overlapping:
652
+ for o in overlapping[:2]:
653
+ text = o.get("text", "")[:200].replace("\n", " ").strip()
654
+ src = o.get("source", "").split("/")[-1] if o.get("source") else ""
655
+ parts.append(f"[cached] {src}: {text}" if src else f"[cached] {text}")
656
+ parts.extend(snippets)
657
+
658
+ content = " | ".join(parts)
659
+ chunk_count = len(snippets) + (len(overlapping) if overlapping else 0)
660
+ tag = f"[knowledge:{chunk_count} chunks]"
529
661
  ms = int((time.time() - start) * 1000)
662
+ tokens_est = len(content.split())
530
663
 
531
664
  return LayerResult(
532
- layer_id=self.id, tag=tag, content=content,
533
- tokens_est=total_tokens, compute_ms=ms, cached=False,
665
+ layer_id=self.id,
666
+ tag=tag,
667
+ content=content,
668
+ tokens_est=tokens_est,
669
+ compute_ms=ms,
670
+ cached=False,
534
671
  )
535
672
 
536
673
 
537
674
  # --- L8: Forge Context ---
538
675
 
676
+
539
677
  class ForgeContextLayer(Layer):
540
678
  """L8: Active forge plan context — decisions, risks, rejected approaches."""
541
679
 
@@ -559,11 +697,14 @@ class ForgeContextLayer(Layer):
559
697
  start = time.time()
560
698
  try:
561
699
  from core.forge.persistence import get_active_plan
700
+
562
701
  plan = get_active_plan()
563
702
  except Exception:
564
703
  plan = None
565
704
  if plan is None:
566
- return LayerResult(layer_id=self.id, tag="", content="", tokens_est=0, compute_ms=0, cached=False)
705
+ return LayerResult(
706
+ layer_id=self.id, tag="", content="", tokens_est=0, compute_ms=0, cached=False
707
+ )
567
708
  tag = f"[forge:{plan.id}]"
568
709
  parts = [f"Forge plan: {plan.id} ({plan.status.value})"]
569
710
  if plan.critic.confidence > 0:
@@ -580,4 +721,66 @@ class ForgeContextLayer(Layer):
580
721
  parts.append(f"Risks: {'; '.join(risks[:3])}")
581
722
  content = " | ".join(parts)
582
723
  ms = int((time.time() - start) * 1000)
583
- return LayerResult(layer_id=self.id, tag=tag, content=content, tokens_est=len(content.split()), compute_ms=ms, cached=False)
724
+ return LayerResult(
725
+ layer_id=self.id,
726
+ tag=tag,
727
+ content=content,
728
+ tokens_est=len(content.split()),
729
+ compute_ms=ms,
730
+ cached=False,
731
+ )
732
+
733
+
734
+ # --- L9: Session Memory Context ---
735
+
736
+
737
+ class SessionContextLayer(Layer):
738
+ """L9: Restored session context from memory store.
739
+
740
+ Provides context from previous sessions via build_resume_context().
741
+ Shows workflow position, pending items, and violations.
742
+ """
743
+
744
+ @property
745
+ def id(self) -> str:
746
+ return "L9"
747
+
748
+ @property
749
+ def name(self) -> str:
750
+ return "SessionMemory"
751
+
752
+ @property
753
+ def cache_ttl(self) -> int:
754
+ return 0
755
+
756
+ @property
757
+ def priority(self) -> int:
758
+ return 90
759
+
760
+ def compute(self, ctx: PromptContext) -> LayerResult:
761
+ start = time.time()
762
+ try:
763
+ from core.memory.rehydrator import build_resume_context
764
+
765
+ content = build_resume_context()
766
+ except Exception:
767
+ content = ""
768
+
769
+ if not content:
770
+ return LayerResult(
771
+ layer_id=self.id, tag="", content="", tokens_est=0, compute_ms=0, cached=False
772
+ )
773
+
774
+ lines = content.split("\n")
775
+ tag = "[session:resume]"
776
+ tokens = len(content.split())
777
+
778
+ ms = int((time.time() - start) * 1000)
779
+ return LayerResult(
780
+ layer_id=self.id,
781
+ tag=tag,
782
+ content=content,
783
+ tokens_est=tokens,
784
+ compute_ms=ms,
785
+ cached=False,
786
+ )