arkaos 3.74.0 → 3.75.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.
package/VERSION CHANGED
@@ -1 +1 @@
1
- 3.74.0
1
+ 3.75.0
@@ -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.
@@ -150,12 +158,20 @@ For each item, in order:
150
158
  injection, missing auth, data exposure.
151
159
  - Fail → back to the todo.
152
160
  5. **Quality Gate** — Marta (CQO) orchestrates the right specialists
153
- for the area. After Marta returns the verdict, the orchestrator MUST
154
- call `core.governance.cqo_experience_recorder.record_from_verdict(...)`
155
- when verdict is REJECTED (constitution rule `agent-experience-persistence`,
156
- MUST level PR3 v3.74.0). The recorder parses the blockers and writes
157
- one Experience to the failing agent's log so the lesson is visible on
158
- the next dispatch via the L2.6 Synapse layer. If a specialist is
161
+ for the area.
162
+
163
+ **CQO dispatch convention (PR3.5 v3.74.1):** when invoking the `cqo`
164
+ subagent, the orchestrator MUST include the marker
165
+ `[arka:reviewing <agent_id>]` in the dispatch prompt, naming the
166
+ agent whose work is under review (e.g.
167
+ `[arka:reviewing tech-lead-paulo]`). On a REJECTED verdict, the
168
+ PostToolUse hook `config/hooks/post-tool-use.sh` reads the marker
169
+ plus the verdict text and auto-appends an `Experience` to that
170
+ agent's log — closing the QG learning loop without manual
171
+ bookkeeping. The L2.6 Synapse layer
172
+ (`core/synapse/agent_experiences_layer.py`) injects the lessons
173
+ into the next dispatch automatically. APPROVED verdicts produce no
174
+ record (only failures are lessons). If a specialist is
159
175
  missing, stop and advise the user
160
176
  to create one via `/arka personas` + provide the knowledge.
161
177
  - Fail → back to the todo.
@@ -122,6 +122,19 @@ ownership:
122
122
  owners: [devops-eng]
123
123
  reason: "Infrastructure-as-code requires devops specialist"
124
124
 
125
+ # PR3.5 v3.74.1 — installer + dashboard launcher coverage
126
+ - pattern: "installer/**/*.js"
127
+ owners: [devops-eng, senior-dev]
128
+ reason: "npx arkaos installer surface requires devops + backend review"
129
+
130
+ - pattern: "scripts/start-dashboard*"
131
+ owners: [devops-eng]
132
+ reason: "Dashboard launcher is operational devops surface"
133
+
134
+ - pattern: "scripts/dashboard-api.py"
135
+ owners: [devops-eng, senior-dev]
136
+ reason: "Dashboard API backend bridges installer ops + Python service code"
137
+
125
138
  # ─── Core architecture ──────────────────────────────────────────────
126
139
  - pattern: "core/workflow/**/*.py"
127
140
  owners: [architect, senior-dev]
@@ -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:
@@ -72,6 +72,49 @@ except Exception:
72
72
  fi
73
73
  fi
74
74
 
75
+ # ─── CQO REJECTED auto-record (PR3.5 v3.74.1) ────────────────────────
76
+ # When a Task/Agent dispatch to subagent_type=cqo returns
77
+ # `Quality Gate Verdict: REJECTED`, append an Experience to the
78
+ # failing agent's log. The agent under review is identified by the
79
+ # `[arka:reviewing <agent_id>]` marker that the orchestrator MUST
80
+ # include in the CQO dispatch prompt (constitution rule
81
+ # `agent-experience-persistence`). Never blocks the hook.
82
+ if [ "$TOOL_NAME" = "Task" ] || [ "$TOOL_NAME" = "Agent" ]; then
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
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
101
+ import os, sys
102
+ sys.path.insert(0, os.environ["ARKAOS_ROOT"])
103
+ try:
104
+ from core.governance.cqo_experience_recorder import record_from_verdict
105
+ record_from_verdict(
106
+ verdict_text=os.environ.get("VERDICT_TEXT", ""),
107
+ agent_id=os.environ.get("AGENT_ID", ""),
108
+ session_id=os.environ.get("SESSION_ID", ""),
109
+ context="auto-recorded via PostToolUse hook (cqo dispatch REJECTED)",
110
+ )
111
+ except Exception:
112
+ pass
113
+ PY
114
+ fi
115
+ fi
116
+ fi
117
+
75
118
  # Only process if there was an error
76
119
  if [ "$EXIT_CODE" = "0" ] || [ -z "$EXIT_CODE" ]; then
77
120
  # Also check for error patterns in output even with exit code 0
@@ -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())
@@ -187,6 +187,8 @@ def create_default_engine(
187
187
  ForgeContextLayer,
188
188
  SessionContextLayer,
189
189
  )
190
+ from core.synapse.agent_experiences_layer import AgentExperiencesLayer
191
+ from core.synapse.pattern_library_layer import PatternLibraryLayer
190
192
 
191
193
  engine = SynapseEngine()
192
194
 
@@ -194,6 +196,10 @@ def create_default_engine(
194
196
  engine.register_layer(l0)
195
197
  engine.register_layer(DepartmentLayer())
196
198
  engine.register_layer(AgentLayer(agents_registry=agents_registry))
199
+ # L2.6 (PR3.5 v3.74.1) — injects past Quality Gate experiences for the
200
+ # specialist named in `[arka:dispatch]`, so dispatched agents inherit
201
+ # prior REJECTED lessons across sessions. Closes the PR3 loop.
202
+ engine.register_layer(AgentExperiencesLayer())
197
203
  if vector_store is not None or kb_vault_path:
198
204
  engine.register_layer(
199
205
  KBContextLayer(
@@ -210,6 +216,11 @@ def create_default_engine(
210
216
  engine.register_layer(QualityGateLayer())
211
217
  engine.register_layer(TimeLayer())
212
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())
213
224
  engine.register_layer(SessionContextLayer())
214
225
 
215
226
  return engine
@@ -0,0 +1,129 @@
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 — alphanumeric runs longer than 3 chars,
24
+ # lowercased, stopword-filtered. Enough for v1 matching; a vector-DB
25
+ # embedder will replace this in v3.75.x.
26
+ _WORD_RE = re.compile(r"[A-Za-z][A-Za-z0-9_-]{3,}")
27
+
28
+ _STOPWORDS: frozenset[str] = frozenset({
29
+ "this", "that", "with", "from", "into", "have", "want", "need",
30
+ "should", "would", "could", "make", "made", "thing", "things",
31
+ "there", "their", "about", "what", "when", "where", "which",
32
+ "while", "also", "just", "like", "para", "como", "isto",
33
+ "esta", "este", "isso", "essa", "esse", "depois", "antes",
34
+ })
35
+
36
+
37
+ def _extract_keywords(text: str, max_n: int = 10) -> list[str]:
38
+ """Return the first `max_n` distinct meaningful words from `text`."""
39
+ seen: list[str] = []
40
+ seen_set: set[str] = set()
41
+ for word in _WORD_RE.findall(text or ""):
42
+ low = word.lower()
43
+ if low in _STOPWORDS or low in seen_set:
44
+ continue
45
+ seen.append(low)
46
+ seen_set.add(low)
47
+ if len(seen) >= max_n:
48
+ break
49
+ return seen
50
+
51
+
52
+ class PatternLibraryLayer(Layer):
53
+ """L7.5 — surface prior implementations matching the user prompt."""
54
+
55
+ def __init__(self, limit: int = 3) -> None:
56
+ self._limit = limit
57
+
58
+ @property
59
+ def id(self) -> str:
60
+ return "L7.5"
61
+
62
+ @property
63
+ def name(self) -> str:
64
+ return "PatternLibrary"
65
+
66
+ @property
67
+ def cache_ttl(self) -> int:
68
+ return 60
69
+
70
+ @property
71
+ def priority(self) -> int:
72
+ # 75 — runs after L7 Time (70) and BEFORE L8 ForgeContext (80) so
73
+ # the prior-art context is available to Forge's plan synthesis.
74
+ return 75
75
+
76
+ def compute(self, ctx: PromptContext) -> LayerResult:
77
+ start = time.time()
78
+ keywords = _extract_keywords(ctx.user_input)
79
+ if not keywords:
80
+ return self._empty_result(start)
81
+
82
+ cards = query_patterns(keywords=keywords, limit=self._limit)
83
+ if not cards:
84
+ return self._empty_result(start, tag="[patterns:none]")
85
+
86
+ content = format_patterns(cards)
87
+ ms = int((time.time() - start) * 1000)
88
+ return LayerResult(
89
+ layer_id=self.id,
90
+ tag=f"[patterns:{len(cards)}]",
91
+ content=content,
92
+ tokens_est=max(1, len(content) // 4),
93
+ compute_ms=ms,
94
+ cached=False,
95
+ )
96
+
97
+ def _empty_result(self, start: float, tag: str = "") -> LayerResult:
98
+ return LayerResult(
99
+ layer_id=self.id,
100
+ tag=tag,
101
+ content="",
102
+ tokens_est=0,
103
+ compute_ms=int((time.time() - start) * 1000),
104
+ cached=False,
105
+ )
106
+
107
+
108
+ def format_patterns(cards: list[PatternCard]) -> str:
109
+ """Render a compact, model-readable summary of prior implementations."""
110
+ lines = [
111
+ "Prior implementations found in the Pattern Library "
112
+ "(consult before designing — reuse or document divergence):"
113
+ ]
114
+ for i, c in enumerate(cards, start=1):
115
+ head = f" {i}. {c.name} ({c.id})"
116
+ if c.stack:
117
+ head += f" — stack: {', '.join(c.stack[:4])}"
118
+ lines.append(head)
119
+ if c.description:
120
+ lines.append(f" {c.description}")
121
+ if c.files:
122
+ lines.append(f" files: {', '.join(c.files[:3])}")
123
+ if c.acceptance_criteria:
124
+ lines.append(
125
+ f" AC: {'; '.join(c.acceptance_criteria[:2])}"
126
+ )
127
+ if c.references:
128
+ lines.append(f" refs: {', '.join(c.references[:2])}")
129
+ return "\n".join(lines)
package/installer/cli.js CHANGED
@@ -21,6 +21,10 @@ const { values, positionals } = parseArgs({
21
21
  force: { type: "boolean", short: "f" },
22
22
  "no-system": { type: "boolean" },
23
23
  "with-ollama": { type: "boolean" },
24
+ // PR3.5 v3.74.1 — declared so `npx arkaos doctor --fix` lands in
25
+ // `values.fix` rather than as a free positional under strict:false.
26
+ // Eliminates the dead-branch fallback flagged by Marta in PR2's QG.
27
+ fix: { type: "boolean" },
24
28
  },
25
29
  allowPositionals: true,
26
30
  strict: false,
@@ -94,8 +98,7 @@ async function main() {
94
98
 
95
99
  case "doctor": {
96
100
  const { doctor } = await import("./doctor.js");
97
- const fixMode = positionals.slice(1).includes("--fix") || values.fix === true;
98
- await doctor({ fix: fixMode });
101
+ await doctor({ fix: values.fix === true });
99
102
  break;
100
103
  }
101
104
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arkaos",
3
- "version": "3.74.0",
3
+ "version": "3.75.0",
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.0"
3
+ version = "3.75.0"
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())