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 +1 -1
- package/arka/skills/flow/SKILL.md +22 -6
- package/config/agent-ownership.yaml +13 -0
- package/config/constitution.yaml +5 -0
- package/config/hooks/post-tool-use.sh +43 -0
- package/core/governance/__pycache__/agent_experiences.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/cqo_experience_recorder.cpython-313.pyc +0 -0
- package/core/knowledge/__pycache__/pattern_cards.cpython-313.pyc +0 -0
- package/core/knowledge/__pycache__/pattern_cards_cli.cpython-313.pyc +0 -0
- package/core/knowledge/pattern_cards.py +175 -0
- package/core/knowledge/pattern_cards_cli.py +103 -0
- package/core/synapse/__pycache__/agent_experiences_layer.cpython-313.pyc +0 -0
- package/core/synapse/__pycache__/engine.cpython-313.pyc +0 -0
- package/core/synapse/__pycache__/pattern_library_layer.cpython-313.pyc +0 -0
- package/core/synapse/engine.py +11 -0
- package/core/synapse/pattern_library_layer.py +129 -0
- package/installer/cli.js +5 -2
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/scripts/__pycache__/seed_initial_patterns.cpython-313.pyc +0 -0
- package/scripts/seed_initial_patterns.py +184 -0
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
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.
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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]
|
package/config/constitution.yaml
CHANGED
|
@@ -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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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())
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/core/synapse/engine.py
CHANGED
|
@@ -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
|
-
|
|
98
|
-
await doctor({ fix: fixMode });
|
|
101
|
+
await doctor({ fix: values.fix === true });
|
|
99
102
|
break;
|
|
100
103
|
}
|
|
101
104
|
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
Binary file
|
|
@@ -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())
|