arkaos 3.74.1 → 3.75.1

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.
package/VERSION CHANGED
@@ -1 +1 @@
1
- 3.74.1
1
+ 3.75.1
@@ -69,6 +69,14 @@ Query the knowledge base:
69
69
  Cite the sources found. If the KB has nothing and the ask is non-trivial,
70
70
  state the gap explicitly and propose filling it.
71
71
 
72
+ **Pattern Library check (SHOULD `pattern-library-first`, PR4 v3.75.0).**
73
+ Synapse layer L7.5 (`core/synapse/pattern_library_layer.py`) auto-injects
74
+ the top matching `PatternCard`s from `~/.arkaos/patterns/cards.jsonl`
75
+ whenever the user prompt contains substantive keywords. Read the cards
76
+ *before* designing — reuse the prior implementation, or document in the
77
+ spec why divergence is justified. Manual audit:
78
+ `python -m core.knowledge.pattern_cards_cli list` or `search <keyword>`.
79
+
72
80
  ### Phase 6 — Call team
73
81
  Dispatch specialists via the `Agent` tool. The squad lead from Phase 3
74
82
  names them. Specialists run in parallel when work is independent.
@@ -163,9 +171,21 @@ For each item, in order:
163
171
  bookkeeping. The L2.6 Synapse layer
164
172
  (`core/synapse/agent_experiences_layer.py`) injects the lessons
165
173
  into the next dispatch automatically. APPROVED verdicts produce no
166
- record (only failures are lessons). If a specialist is
167
- missing, stop and advise the user
168
- to create one via `/arka personas` + provide the knowledge.
174
+ `Experience` record (only failures are lessons).
175
+
176
+ **Pattern auto-stub convention (PR4.5 v3.75.1):** to populate the
177
+ Pattern Library on APPROVED outcomes, the orchestrator may include
178
+ `[arka:pattern-suggest <id> <name>]` in the CQO dispatch prompt
179
+ (e.g. `[arka:pattern-suggest force-specialist-dispatch Force
180
+ Specialist Dispatch]`). On APPROVED, the same PostToolUse hook
181
+ creates a stub `PatternCard` so the library remembers the feature
182
+ ID and name; the operator enriches it later via `record_pattern()`
183
+ or by editing `~/.arkaos/patterns/cards.jsonl`. Re-suggesting an
184
+ existing id is a no-op (never overwrites enriched cards).
185
+
186
+ If a specialist is missing, stop and advise the user to create one
187
+ via `/arka personas` + provide the knowledge.
188
+
169
189
  - Fail → back to the todo.
170
190
  6. Document — save the completed work to Obsidian + vector DB.
171
191
  7. Next todo.
@@ -148,6 +148,15 @@ ownership:
148
148
  owners: [architect, security-eng]
149
149
  reason: "Governance code requires architecture + security review"
150
150
 
151
+ # PR4.5 v3.75.1 — extend coverage to knowledge + synapse (Marta PR4-QG #4)
152
+ - pattern: "core/knowledge/**/*.py"
153
+ owners: [architect, senior-dev]
154
+ reason: "Knowledge storage + retrieval require architecture review"
155
+
156
+ - pattern: "core/synapse/**/*.py"
157
+ owners: [architect, senior-dev]
158
+ reason: "Synapse engine + layers require architecture review"
159
+
151
160
  - pattern: "docs/adr/**"
152
161
  owners: [architect]
153
162
  reason: "ADRs are architect's responsibility"
@@ -217,6 +217,11 @@ enforcement_levels:
217
217
  rule: "Bottom-line first output. Lead with answer, then why, then how. Confidence tags on assessments."
218
218
  enforcement: "See config/standards/communication.md for full standard"
219
219
 
220
+ # ─── Rule added in PR4 Squad Intelligence Upgrade (2026-05-28) ───────
221
+ - id: pattern-library-first
222
+ rule: "Before designing any new feature, consult the Pattern Library (core.knowledge.pattern_cards). If a similar pattern exists, reuse it or explicitly document why divergence is justified in the spec. New patterns SHOULD be registered with record_pattern() after Quality Gate APPROVED so future feature work inherits the prior art."
223
+ enforcement: "Synapse layer L7.5 (core.synapse.pattern_library_layer) auto-injects top matching cards as context. Audit via python -m core.knowledge.pattern_cards_cli list. SHOULD level — promoted from advisory once telemetry shows consistent consultation."
224
+
220
225
  tier_hierarchy:
221
226
  description: "Agent authority levels inspired by SpaceX/Google/Anthropic org structures"
222
227
  tiers:
@@ -81,23 +81,26 @@ fi
81
81
  # `agent-experience-persistence`). Never blocks the hook.
82
82
  if [ "$TOOL_NAME" = "Task" ] || [ "$TOOL_NAME" = "Agent" ]; then
83
83
  SUBAGENT_TYPE=$(echo "$input" | jq -r '.tool_input.subagent_type // ""' 2>/dev/null)
84
- if [ "$SUBAGENT_TYPE" = "cqo" ] && echo "$TOOL_OUTPUT" | grep -qE 'Quality Gate Verdict:[[:space:]]*REJECTED'; then
84
+ if [ "$SUBAGENT_TYPE" = "cqo" ]; then
85
85
  TOOL_INPUT_PROMPT=$(echo "$input" | jq -r '.tool_input.prompt // ""' 2>/dev/null)
86
- REVIEWING_TARGET=$(printf '%s' "$TOOL_INPUT_PROMPT" \
87
- | grep -oE '\[arka:reviewing[[:space:]]+[A-Za-z0-9_.-]+\]' \
88
- | head -1 \
89
- | sed -E 's/.*\[arka:reviewing[[:space:]]+([A-Za-z0-9_.-]+)\].*/\1/')
90
- if [ -n "$REVIEWING_TARGET" ]; then
91
- _AE_ROOT="${ARKAOS_ROOT:-}"
92
- if [ -z "$_AE_ROOT" ] && [ -f "$HOME/.arkaos/.repo-path" ]; then
93
- _AE_ROOT=$(cat "$HOME/.arkaos/.repo-path" 2>/dev/null)
94
- fi
95
- [ -z "$_AE_ROOT" ] && _AE_ROOT="$HOME/.arkaos"
96
- VERDICT_TEXT="$TOOL_OUTPUT" \
97
- AGENT_ID="$REVIEWING_TARGET" \
98
- SESSION_ID="$SESSION_ID_PTU" \
99
- ARKAOS_ROOT="$_AE_ROOT" \
100
- python3 - <<'PY' 2>/dev/null || true
86
+ _AE_ROOT="${ARKAOS_ROOT:-}"
87
+ if [ -z "$_AE_ROOT" ] && [ -f "$HOME/.arkaos/.repo-path" ]; then
88
+ _AE_ROOT=$(cat "$HOME/.arkaos/.repo-path" 2>/dev/null)
89
+ fi
90
+ [ -z "$_AE_ROOT" ] && _AE_ROOT="$HOME/.arkaos"
91
+
92
+ # ─── REJECTED experience auto-record ────────────────────────────
93
+ if echo "$TOOL_OUTPUT" | grep -qE 'Quality Gate Verdict:[[:space:]]*REJECTED'; then
94
+ REVIEWING_TARGET=$(printf '%s' "$TOOL_INPUT_PROMPT" \
95
+ | grep -oE '\[arka:reviewing[[:space:]]+[A-Za-z0-9_.-]+\]' \
96
+ | head -1 \
97
+ | sed -E 's/.*\[arka:reviewing[[:space:]]+([A-Za-z0-9_.-]+)\].*/\1/')
98
+ if [ -n "$REVIEWING_TARGET" ]; then
99
+ VERDICT_TEXT="$TOOL_OUTPUT" \
100
+ AGENT_ID="$REVIEWING_TARGET" \
101
+ SESSION_ID="$SESSION_ID_PTU" \
102
+ ARKAOS_ROOT="$_AE_ROOT" \
103
+ python3 - <<'PY' 2>/dev/null || true
101
104
  import os, sys
102
105
  sys.path.insert(0, os.environ["ARKAOS_ROOT"])
103
106
  try:
@@ -111,6 +114,55 @@ try:
111
114
  except Exception:
112
115
  pass
113
116
  PY
117
+ fi
118
+ fi
119
+
120
+ # ─── APPROVED → pattern stub auto-create (PR4.5 v3.75.1) ──────────
121
+ # When the dispatch prompt contains `[arka:pattern-suggest <id> <name>]`
122
+ # AND the verdict is APPROVED, create a stub PatternCard. The
123
+ # operator enriches by editing ~/.arkaos/patterns/cards.jsonl or
124
+ # calling record_pattern() with the full metadata. Skips when the
125
+ # id already exists (never overwrites enriched cards).
126
+ if echo "$TOOL_OUTPUT" | grep -qE 'Quality Gate Verdict:[[:space:]]*APPROVED'; then
127
+ PATTERN_LINE=$(printf '%s' "$TOOL_INPUT_PROMPT" \
128
+ | grep -oE '\[arka:pattern-suggest[[:space:]]+[A-Za-z0-9_.-]+[[:space:]]+[^][]+\]' \
129
+ | head -1)
130
+ if [ -n "$PATTERN_LINE" ]; then
131
+ PID=$(printf '%s' "$PATTERN_LINE" | sed -E 's/\[arka:pattern-suggest[[:space:]]+([A-Za-z0-9_.-]+).*/\1/')
132
+ PNAME=$(printf '%s' "$PATTERN_LINE" | sed -E 's/\[arka:pattern-suggest[[:space:]]+[A-Za-z0-9_.-]+[[:space:]]+([^][]+)\]/\1/')
133
+ if [ -n "$PID" ] && [ -n "$PNAME" ]; then
134
+ PATTERN_ID="$PID" \
135
+ PATTERN_NAME="$PNAME" \
136
+ ARKAOS_ROOT="$_AE_ROOT" \
137
+ python3 - <<'PY' 2>/dev/null || true
138
+ import os, sys
139
+ sys.path.insert(0, os.environ["ARKAOS_ROOT"])
140
+ try:
141
+ from datetime import datetime, timezone
142
+ from core.knowledge.pattern_cards import PatternCard, record_pattern, query_patterns
143
+ pid = os.environ["PATTERN_ID"]
144
+ existing = [c for c in query_patterns(limit=1000) if c.id == pid]
145
+ if not existing:
146
+ ts = datetime.now(timezone.utc).isoformat()
147
+ record_pattern(PatternCard(
148
+ id=pid,
149
+ name=os.environ["PATTERN_NAME"],
150
+ feature_keywords=[pid.replace("-", " "), os.environ["PATTERN_NAME"].lower()],
151
+ description="Stub auto-created from APPROVED CQO verdict — enrich via record_pattern() or by editing the JSONL.",
152
+ stack=[],
153
+ files=[],
154
+ acceptance_criteria=[],
155
+ edge_cases=[],
156
+ references=[],
157
+ projects_using=["arkaos"],
158
+ created_at=ts,
159
+ last_updated=ts,
160
+ ))
161
+ except Exception:
162
+ pass
163
+ PY
164
+ fi
165
+ fi
114
166
  fi
115
167
  fi
116
168
  fi
@@ -0,0 +1,175 @@
1
+ """Pattern Library — append-style store of prior implementations.
2
+
3
+ When a feature ships and Marta APPROVES it, the orchestrator may
4
+ register a `PatternCard` so future feature work has a "we have
5
+ already built this" reference: files, AC, edge cases, references.
6
+ The Synapse L7.5 layer queries this store on every user prompt and
7
+ injects top matching cards as context.
8
+
9
+ For v1 (PR4 v3.75.0): keyword + tag matching, recency-sorted, dedup
10
+ by id. Semantic similarity via vector embeddings lands in v3.75.x.
11
+
12
+ PR4 of the Squad Intelligence Upgrade.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ from contextlib import contextmanager
19
+ from dataclasses import asdict, dataclass, field
20
+ from datetime import datetime, timezone
21
+ from pathlib import Path
22
+
23
+ from core.shared import safe_session_id as _safe_session_id_module
24
+
25
+ try:
26
+ import fcntl # POSIX only
27
+ _HAS_FLOCK = True
28
+ except ImportError:
29
+ _HAS_FLOCK = False
30
+
31
+
32
+ PATTERNS_PATH: Path = Path.home() / ".arkaos" / "patterns" / "cards.jsonl"
33
+
34
+
35
+ @dataclass
36
+ class PatternCard:
37
+ """One reusable feature implementation pattern."""
38
+
39
+ id: str
40
+ name: str
41
+ feature_keywords: list[str]
42
+ description: str
43
+ stack: list[str]
44
+ files: list[str] = field(default_factory=list)
45
+ acceptance_criteria: list[str] = field(default_factory=list)
46
+ edge_cases: list[str] = field(default_factory=list)
47
+ references: list[str] = field(default_factory=list)
48
+ projects_using: list[str] = field(default_factory=list)
49
+ created_at: str = ""
50
+ last_updated: str = ""
51
+
52
+
53
+ def pattern_to_dict(card: PatternCard) -> dict:
54
+ """Public serialiser for callers that persist outside this store."""
55
+ return asdict(card)
56
+
57
+
58
+ def _safe_id(id_: str) -> str | None:
59
+ """Apply the same allowlist as session/agent IDs (CWE-22 guard)."""
60
+ return _safe_session_id_module.safe_session_id(id_)
61
+
62
+
63
+ @contextmanager
64
+ def _locked(path: Path, mode: str):
65
+ """Open `path` under an exclusive POSIX flock with Windows fallback."""
66
+ path.parent.mkdir(parents=True, exist_ok=True)
67
+ fh = path.open(mode, encoding="utf-8")
68
+ try:
69
+ if _HAS_FLOCK:
70
+ fcntl.flock(fh.fileno(), fcntl.LOCK_EX)
71
+ yield fh
72
+ finally:
73
+ if _HAS_FLOCK:
74
+ try:
75
+ fcntl.flock(fh.fileno(), fcntl.LOCK_UN)
76
+ except OSError:
77
+ pass
78
+ fh.close()
79
+
80
+
81
+ def _load_all() -> list[PatternCard]:
82
+ """Load all valid PatternCards. Malformed lines are skipped."""
83
+ if not PATTERNS_PATH.exists():
84
+ return []
85
+ cards: list[PatternCard] = []
86
+ try:
87
+ with PATTERNS_PATH.open(encoding="utf-8") as fh:
88
+ for line in fh:
89
+ if not line.strip():
90
+ continue
91
+ try:
92
+ data = json.loads(line)
93
+ cards.append(PatternCard(**data))
94
+ except (json.JSONDecodeError, TypeError, ValueError):
95
+ continue
96
+ except OSError:
97
+ return []
98
+ return cards
99
+
100
+
101
+ def _stamp_card(card: PatternCard) -> None:
102
+ """Set created_at on first persist, last_updated on every persist."""
103
+ now = datetime.now(timezone.utc).isoformat()
104
+ if not card.created_at:
105
+ card.created_at = now
106
+ card.last_updated = now
107
+
108
+
109
+ def _write_all(cards: list[PatternCard]) -> None:
110
+ """Atomically rewrite the entire JSONL. Locked for cross-process safety."""
111
+ try:
112
+ with _locked(PATTERNS_PATH, "w") as fh:
113
+ for c in cards:
114
+ fh.write(json.dumps(asdict(c)) + "\n")
115
+ except OSError:
116
+ return
117
+
118
+
119
+ def record_pattern(card: PatternCard) -> None:
120
+ """Persist a PatternCard. Silently drops on unsafe/empty id.
121
+
122
+ Re-recording an existing id REPLACES the prior entry (dedup-by-id).
123
+ The file is rewritten in full under exclusive lock — fine for the
124
+ expected scale (low thousands of cards). Larger scales should move
125
+ to per-card files in v3.75.x.
126
+ """
127
+ if _safe_id(card.id) is None:
128
+ return
129
+ _stamp_card(card)
130
+ others = [c for c in _load_all() if c.id != card.id]
131
+ others.append(card)
132
+ _write_all(others)
133
+
134
+
135
+ def _card_text(card: PatternCard) -> str:
136
+ """Flatten searchable text from a card."""
137
+ parts = [card.name, card.description] + (card.feature_keywords or [])
138
+ return " ".join(str(p) for p in parts).lower()
139
+
140
+
141
+ def _matches_keywords(card: PatternCard, keywords: list[str]) -> bool:
142
+ if not keywords:
143
+ return True
144
+ text = _card_text(card)
145
+ return any(kw.lower() in text for kw in keywords)
146
+
147
+
148
+ def _matches_tags(card: PatternCard, tags: list[str]) -> bool:
149
+ if not tags:
150
+ return True
151
+ stack = {t.lower() for t in (card.stack or [])}
152
+ return any(tag.lower() in stack for tag in tags)
153
+
154
+
155
+ def query_patterns(
156
+ *,
157
+ keywords: list[str] | None = None,
158
+ tags: list[str] | None = None,
159
+ limit: int = 10,
160
+ ) -> list[PatternCard]:
161
+ """Return cards matching ALL filters, most recent first.
162
+
163
+ Empty filters return all cards. Match semantics: case-insensitive
164
+ substring on name/description/feature_keywords; exact tag match
165
+ against `stack`. Both filters must hold (intersection).
166
+ """
167
+ cards = _load_all()
168
+ kws = keywords or []
169
+ tgs = tags or []
170
+ matched = [
171
+ c for c in cards
172
+ if _matches_keywords(c, kws) and _matches_tags(c, tgs)
173
+ ]
174
+ matched.sort(key=lambda c: c.last_updated or c.created_at, reverse=True)
175
+ return matched[:limit]
@@ -0,0 +1,103 @@
1
+ """CLI viewer for the Pattern Library.
2
+
3
+ Usage:
4
+ python -m core.knowledge.pattern_cards_cli list [--tag TAG] [--limit N]
5
+ python -m core.knowledge.pattern_cards_cli search <keyword> [--tag TAG] [--limit N]
6
+ python -m core.knowledge.pattern_cards_cli show <id>
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+ import sys
13
+
14
+ from core.knowledge.pattern_cards import PatternCard, query_patterns
15
+
16
+
17
+ def _format_card(card: PatternCard, index: int | None = None) -> str:
18
+ head = f" [{index}] " if index is not None else " "
19
+ lines = [f"{head}{card.id} — {card.name}"]
20
+ if card.stack:
21
+ lines.append(f" stack: {', '.join(card.stack)}")
22
+ if card.description:
23
+ lines.append(f" {card.description}")
24
+ if card.files:
25
+ lines.append(f" files: {', '.join(card.files[:5])}")
26
+ if card.acceptance_criteria:
27
+ lines.append(" AC:")
28
+ for ac in card.acceptance_criteria[:3]:
29
+ lines.append(f" - {ac}")
30
+ if card.references:
31
+ lines.append(f" refs: {', '.join(card.references[:3])}")
32
+ return "\n".join(lines)
33
+
34
+
35
+ def _build_parser() -> argparse.ArgumentParser:
36
+ parser = argparse.ArgumentParser(
37
+ prog="python -m core.knowledge.pattern_cards_cli",
38
+ description="Browse the ArkaOS Pattern Library.",
39
+ )
40
+ subparsers = parser.add_subparsers(dest="cmd", required=True)
41
+
42
+ list_p = subparsers.add_parser("list", help="List all patterns by recency.")
43
+ list_p.add_argument("--tag", default=None, help="Filter by stack tag")
44
+ list_p.add_argument("--limit", type=int, default=20, help="Max records")
45
+
46
+ search_p = subparsers.add_parser("search", help="Search by keyword.")
47
+ search_p.add_argument("keyword", help="Keyword (case-insensitive substring)")
48
+ search_p.add_argument("--tag", default=None, help="Filter by stack tag")
49
+ search_p.add_argument("--limit", type=int, default=20, help="Max records")
50
+
51
+ show_p = subparsers.add_parser("show", help="Show one card by id.")
52
+ show_p.add_argument("id", help="Pattern id slug")
53
+ return parser
54
+
55
+
56
+ def _do_list(args) -> int:
57
+ tags = [args.tag] if args.tag else None
58
+ cards = query_patterns(tags=tags, limit=args.limit)
59
+ return _print_list(cards, scope="all")
60
+
61
+
62
+ def _do_search(args) -> int:
63
+ tags = [args.tag] if args.tag else None
64
+ cards = query_patterns(keywords=[args.keyword], tags=tags, limit=args.limit)
65
+ return _print_list(cards, scope=f"matching '{args.keyword}'")
66
+
67
+
68
+ def _do_show(args) -> int:
69
+ cards = query_patterns()
70
+ for c in cards:
71
+ if c.id == args.id:
72
+ print(_format_card(c))
73
+ return 0
74
+ print(f"No pattern with id '{args.id}'.")
75
+ return 1
76
+
77
+
78
+ def _print_list(cards: list[PatternCard], *, scope: str) -> int:
79
+ if not cards:
80
+ print(f"No patterns {scope}.")
81
+ return 0
82
+ print(f"Patterns {scope} ({len(cards)} record(s), most recent first):\n")
83
+ for i, c in enumerate(cards, start=1):
84
+ print(_format_card(c, index=i))
85
+ print()
86
+ return 0
87
+
88
+
89
+ def main(argv: list[str] | None = None) -> int:
90
+ parser = _build_parser()
91
+ args = parser.parse_args(argv if argv is not None else sys.argv[1:])
92
+ if args.cmd == "list":
93
+ return _do_list(args)
94
+ if args.cmd == "search":
95
+ return _do_search(args)
96
+ if args.cmd == "show":
97
+ return _do_show(args)
98
+ parser.print_help()
99
+ return 2
100
+
101
+
102
+ if __name__ == "__main__": # pragma: no cover
103
+ sys.exit(main())
@@ -188,6 +188,7 @@ def create_default_engine(
188
188
  SessionContextLayer,
189
189
  )
190
190
  from core.synapse.agent_experiences_layer import AgentExperiencesLayer
191
+ from core.synapse.pattern_library_layer import PatternLibraryLayer
191
192
 
192
193
  engine = SynapseEngine()
193
194
 
@@ -215,6 +216,11 @@ def create_default_engine(
215
216
  engine.register_layer(QualityGateLayer())
216
217
  engine.register_layer(TimeLayer())
217
218
  engine.register_layer(ForgeContextLayer())
219
+ # L7.5 (PR4 v3.75.0) — Pattern Library injection. Surfaces prior
220
+ # `PatternCard`s when the user prompt has matching keywords, so
221
+ # specs and dispatched specialists start from prior art rather than
222
+ # reinventing.
223
+ engine.register_layer(PatternLibraryLayer())
218
224
  engine.register_layer(SessionContextLayer())
219
225
 
220
226
  return engine
@@ -0,0 +1,137 @@
1
+ """Synapse layer L7.5 — Pattern Library injection.
2
+
3
+ When the user's prompt has substantive keywords overlapping a recorded
4
+ `PatternCard`, this layer surfaces the top-3 matching cards as context
5
+ so the orchestrator (and the dispatched specialists) start from the
6
+ "we have already built this" prior art rather than reinventing.
7
+
8
+ Matching is keyword-substring + tag overlap for v1. Semantic similarity
9
+ via vector embeddings lands in v3.75.x.
10
+
11
+ PR4 of the Squad Intelligence Upgrade.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import re
17
+ import time
18
+
19
+ from core.knowledge.pattern_cards import PatternCard, query_patterns
20
+ from core.synapse.layers import Layer, LayerResult, PromptContext
21
+
22
+
23
+ # Cheap keyword extractor — letter-led runs of ≥4 chars, lowercased,
24
+ # stopword-filtered. The character class includes Latin-1 Supplement
25
+ # (À-ÿ, U+00C0–U+00FF) so pt-PT input like "autenticação", "paginação",
26
+ # "implementação" is captured whole instead of truncated at the
27
+ # accented character. Latin Extended-A (U+0100+) is intentionally
28
+ # excluded — extend the class when adding CS/PL/HU/TR corpora
29
+ # (š, ý, ě, ą, ł, ő, ı, etc.). Vector-DB embedder lands in v3.75.x.
30
+ _WORD_RE = re.compile(r"[A-Za-zÀ-ÿ][A-Za-zÀ-ÿ0-9_-]{3,}")
31
+
32
+ _STOPWORDS: frozenset[str] = frozenset({
33
+ # EN
34
+ "this", "that", "with", "from", "into", "have", "want", "need",
35
+ "should", "would", "could", "make", "made", "thing", "things",
36
+ "there", "their", "about", "what", "when", "where", "which",
37
+ "while", "also", "just", "like",
38
+ # PT
39
+ "para", "como", "isto", "esta", "este", "isso", "essa", "esse",
40
+ "depois", "antes", "pode", "deve", "muito", "pelo", "pela",
41
+ "desde", "ainda", "assim", "porque", "mais", "menos", "sobre",
42
+ })
43
+
44
+
45
+ def _extract_keywords(text: str, max_n: int = 10) -> list[str]:
46
+ """Return the first `max_n` distinct meaningful words from `text`."""
47
+ seen: list[str] = []
48
+ seen_set: set[str] = set()
49
+ for word in _WORD_RE.findall(text or ""):
50
+ low = word.lower()
51
+ if low in _STOPWORDS or low in seen_set:
52
+ continue
53
+ seen.append(low)
54
+ seen_set.add(low)
55
+ if len(seen) >= max_n:
56
+ break
57
+ return seen
58
+
59
+
60
+ class PatternLibraryLayer(Layer):
61
+ """L7.5 — surface prior implementations matching the user prompt."""
62
+
63
+ def __init__(self, limit: int = 3) -> None:
64
+ self._limit = limit
65
+
66
+ @property
67
+ def id(self) -> str:
68
+ return "L7.5"
69
+
70
+ @property
71
+ def name(self) -> str:
72
+ return "PatternLibrary"
73
+
74
+ @property
75
+ def cache_ttl(self) -> int:
76
+ return 60
77
+
78
+ @property
79
+ def priority(self) -> int:
80
+ # 75 — runs after L7 Time (70) and BEFORE L8 ForgeContext (80) so
81
+ # the prior-art context is available to Forge's plan synthesis.
82
+ return 75
83
+
84
+ def compute(self, ctx: PromptContext) -> LayerResult:
85
+ start = time.time()
86
+ keywords = _extract_keywords(ctx.user_input)
87
+ if not keywords:
88
+ return self._empty_result(start)
89
+
90
+ cards = query_patterns(keywords=keywords, limit=self._limit)
91
+ if not cards:
92
+ return self._empty_result(start, tag="[patterns:none]")
93
+
94
+ content = format_patterns(cards)
95
+ ms = int((time.time() - start) * 1000)
96
+ return LayerResult(
97
+ layer_id=self.id,
98
+ tag=f"[patterns:{len(cards)}]",
99
+ content=content,
100
+ tokens_est=max(1, len(content) // 4),
101
+ compute_ms=ms,
102
+ cached=False,
103
+ )
104
+
105
+ def _empty_result(self, start: float, tag: str = "") -> LayerResult:
106
+ return LayerResult(
107
+ layer_id=self.id,
108
+ tag=tag,
109
+ content="",
110
+ tokens_est=0,
111
+ compute_ms=int((time.time() - start) * 1000),
112
+ cached=False,
113
+ )
114
+
115
+
116
+ def format_patterns(cards: list[PatternCard]) -> str:
117
+ """Render a compact, model-readable summary of prior implementations."""
118
+ lines = [
119
+ "Prior implementations found in the Pattern Library "
120
+ "(consult before designing — reuse or document divergence):"
121
+ ]
122
+ for i, c in enumerate(cards, start=1):
123
+ head = f" {i}. {c.name} ({c.id})"
124
+ if c.stack:
125
+ head += f" — stack: {', '.join(c.stack[:4])}"
126
+ lines.append(head)
127
+ if c.description:
128
+ lines.append(f" {c.description}")
129
+ if c.files:
130
+ lines.append(f" files: {', '.join(c.files[:3])}")
131
+ if c.acceptance_criteria:
132
+ lines.append(
133
+ f" AC: {'; '.join(c.acceptance_criteria[:2])}"
134
+ )
135
+ if c.references:
136
+ lines.append(f" refs: {', '.join(c.references[:2])}")
137
+ return "\n".join(lines)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arkaos",
3
- "version": "3.74.1",
3
+ "version": "3.75.1",
4
4
  "description": "The Operating System for AI Agent Teams",
5
5
  "type": "module",
6
6
  "bin": {
package/pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "arkaos-core"
3
- version = "3.74.1"
3
+ version = "3.75.1"
4
4
  description = "Core engine for ArkaOS — The Operating System for AI Agent Teams"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
@@ -0,0 +1,184 @@
1
+ """One-shot seeder for the ArkaOS Pattern Library.
2
+
3
+ Populates `~/.arkaos/patterns/cards.jsonl` with the four patterns
4
+ shipped in PR1–PR3.5 of the Squad Intelligence Upgrade so the library
5
+ starts with real day-1 data instead of waiting for the operator to
6
+ record patterns manually.
7
+
8
+ Idempotent: re-running replaces same-id entries (the recorder dedups).
9
+
10
+ Usage:
11
+ python -m scripts.seed_initial_patterns
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from datetime import datetime, timezone
17
+
18
+ from core.knowledge.pattern_cards import PatternCard, record_pattern
19
+
20
+
21
+ def _ts(year: int, month: int, day: int) -> str:
22
+ return datetime(year, month, day, tzinfo=timezone.utc).isoformat()
23
+
24
+
25
+ _INITIAL_PATTERNS: list[PatternCard] = [
26
+ PatternCard(
27
+ id="force-specialist-dispatch",
28
+ name="Force Specialist Dispatch",
29
+ feature_keywords=[
30
+ "specialist", "dispatch", "ownership", "file-glob", "owner",
31
+ "agent", "persona", "pretooluse", "hook", "enforcement",
32
+ ],
33
+ description=(
34
+ "PreToolUse hook that blocks Tier-1 squad leads from writing "
35
+ "to specialist-owned files unless they dispatch the specialist "
36
+ "via the Agent tool first."
37
+ ),
38
+ stack=["python", "bash", "powershell", "hook", "constitution", "governance"],
39
+ files=[
40
+ "core/workflow/specialist_enforcer.py",
41
+ "config/agent-ownership.yaml",
42
+ "config/hooks/pre-tool-use.sh",
43
+ "config/hooks/pre-tool-use.ps1",
44
+ ],
45
+ acceptance_criteria=[
46
+ "BLOCK lead writes on specialist-owned files",
47
+ "Bypass via [arka:specialist-bypass <reason>] (non-empty reason)",
48
+ "Telemetry to ~/.arkaos/telemetry/specialist-dispatch.jsonl",
49
+ "Path-traversal safe via safe_session_id",
50
+ "Feature flag hooks.specialistEnforcement defaults off",
51
+ ],
52
+ edge_cases=[
53
+ "Subagent transcripts isolated from parent — negative gate only",
54
+ "Bypass marker scope strict: last assistant message only",
55
+ "C-Suite + lead_allowed cross-cutting paths always pass",
56
+ ],
57
+ references=[
58
+ "https://github.com/andreagroferreira/arka-os/pull/204",
59
+ "docs/adr/2026-05-28-specialist-dispatch-subagent-blindspot.md",
60
+ ],
61
+ projects_using=["arkaos"],
62
+ created_at=_ts(2026, 5, 28),
63
+ last_updated=_ts(2026, 5, 28),
64
+ ),
65
+ PatternCard(
66
+ id="dashboard-venv-doctor",
67
+ name="Dashboard venv-doctor",
68
+ feature_keywords=[
69
+ "venv", "dashboard", "python", "broken", "symlink", "repair",
70
+ "doctor", "homebrew", "rotation", "fail-fast",
71
+ ],
72
+ description=(
73
+ "Detects broken venv symlinks (typical after Homebrew Python "
74
+ "patch rotations) and auto-repairs via `python -m venv --clear`. "
75
+ "Dashboard launcher scripts fail-fast with actionable remediation."
76
+ ),
77
+ stack=["nodejs", "bash", "powershell", "installer"],
78
+ files=[
79
+ "installer/python-resolver.js",
80
+ "installer/doctor.js",
81
+ "scripts/start-dashboard.sh",
82
+ "scripts/start-dashboard.ps1",
83
+ ],
84
+ acceptance_criteria=[
85
+ "lstat-based broken-symlink detection (existsSync follows links)",
86
+ "--clear recreate + post-repair re-diagnose",
87
+ "Dashboard never falls back to ambient python3",
88
+ "npx arkaos doctor --fix auto-repairs in place",
89
+ ],
90
+ edge_cases=[
91
+ "Homebrew patch-version rotation (3.13.X -> 3.13.Y)",
92
+ "sqlite-vec optional with graceful degradation",
93
+ ],
94
+ references=[
95
+ "https://github.com/andreagroferreira/arka-os/pull/205",
96
+ ],
97
+ projects_using=["arkaos"],
98
+ created_at=_ts(2026, 5, 28),
99
+ last_updated=_ts(2026, 5, 28),
100
+ ),
101
+ PatternCard(
102
+ id="agent-experience-persistence",
103
+ name="Agent Experience persistence (QG learning loop)",
104
+ feature_keywords=[
105
+ "experience", "learning", "loop", "qg", "rejected", "persistence",
106
+ "memory", "agent", "quality-gate", "verdict",
107
+ ],
108
+ description=(
109
+ "REJECTED Quality Gate verdicts persist as Experience records on "
110
+ "the failing agent's log. Synapse layer L2.6 surfaces past "
111
+ "lessons when the same agent is dispatched again."
112
+ ),
113
+ stack=["python", "synapse", "governance", "constitution", "jsonl"],
114
+ files=[
115
+ "core/governance/agent_experiences.py",
116
+ "core/governance/cqo_experience_recorder.py",
117
+ "core/synapse/agent_experiences_layer.py",
118
+ "core/governance/agent_experiences_cli.py",
119
+ ],
120
+ acceptance_criteria=[
121
+ "Append-only JSONL with path-safe agent_id (CWE-22)",
122
+ "Verdict parser: APPROVED, REJECTED, UNKNOWN",
123
+ "Blockers parsed with B/M/N labels (./:/ space separators)",
124
+ "Multiple patterns surface in registry order (not first-match-wins)",
125
+ ],
126
+ edge_cases=[
127
+ "Inline mid-paragraph blockers are NOT extracted (anchored regex)",
128
+ "Pattern field is list[str] for multi-category failures",
129
+ ],
130
+ references=[
131
+ "https://github.com/andreagroferreira/arka-os/pull/206",
132
+ ],
133
+ projects_using=["arkaos"],
134
+ created_at=_ts(2026, 5, 28),
135
+ last_updated=_ts(2026, 5, 28),
136
+ ),
137
+ PatternCard(
138
+ id="qg-experience-loop-wiring",
139
+ name="QG Experience Loop Wiring",
140
+ feature_keywords=[
141
+ "posttooluse", "hook", "auto-record", "wiring", "integration",
142
+ "loop", "reviewing", "cqo",
143
+ ],
144
+ description=(
145
+ "PostToolUse hook auto-detects Agent calls with subagent_type=cqo "
146
+ "returning REJECTED, parses [arka:reviewing <agent_id>], invokes "
147
+ "record_from_verdict. Synapse L2.6 registered in engine."
148
+ ),
149
+ stack=["bash", "python", "synapse", "hook"],
150
+ files=[
151
+ "config/hooks/post-tool-use.sh",
152
+ "core/synapse/engine.py",
153
+ ],
154
+ acceptance_criteria=[
155
+ "[arka:reviewing <agent_id>] marker in CQO dispatch prompt",
156
+ "Auto-record on REJECTED verdict via PostToolUse hook",
157
+ "Path-traversal-safe by double boundary (shell + Python)",
158
+ "Never blocks the hook",
159
+ ],
160
+ edge_cases=[
161
+ "subagent_type exact 'cqo' (no sub-reviewer variants yet)",
162
+ "Idempotent: same output processed twice writes two records",
163
+ ],
164
+ references=[
165
+ "https://github.com/andreagroferreira/arka-os/pull/207",
166
+ ],
167
+ projects_using=["arkaos"],
168
+ created_at=_ts(2026, 5, 28),
169
+ last_updated=_ts(2026, 5, 28),
170
+ ),
171
+ ]
172
+
173
+
174
+ def main() -> int:
175
+ for card in _INITIAL_PATTERNS:
176
+ record_pattern(card)
177
+ print(f"Seeded {len(_INITIAL_PATTERNS)} pattern cards.")
178
+ print("Inspect: python -m core.knowledge.pattern_cards_cli list")
179
+ return 0
180
+
181
+
182
+ if __name__ == "__main__": # pragma: no cover
183
+ import sys
184
+ sys.exit(main())