arkaos 4.13.2 → 4.14.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/SKILL.md +16 -0
- package/bin/arka-doctor +20 -1
- package/config/cognition/schedules.yaml +16 -0
- package/config/hooks/_lib/fastpath/engine.cjs +337 -0
- package/config/hooks/gate-manifest.json +741 -0
- package/config/hooks/post-tool-use.cjs +113 -0
- package/config/hooks/pre-tool-use.cjs +109 -0
- package/config/hooks/session-end.ps1 +40 -0
- package/config/hooks/session-end.sh +55 -0
- package/config/hooks/session-start.sh +25 -189
- package/config/hooks/subagent-stop.ps1 +40 -0
- package/config/hooks/subagent-stop.sh +56 -0
- package/config/mcp-policy.yaml +8 -0
- package/config/skills-curated.yaml +39 -0
- package/config/statusline.sh +57 -1
- package/core/cognition/capture/__pycache__/store.cpython-313.pyc +0 -0
- package/core/cognition/capture/store.py +7 -2
- package/core/evals/__pycache__/__init__.cpython-314.pyc +0 -0
- package/core/evals/__pycache__/sanitizer.cpython-314.pyc +0 -0
- package/core/evals/__pycache__/schema.cpython-314.pyc +0 -0
- package/core/evals/__pycache__/verdict_labels.cpython-314.pyc +0 -0
- package/core/forge/__pycache__/complexity.cpython-314.pyc +0 -0
- package/core/governance/__pycache__/evidence_checks.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/judge.cpython-314.pyc +0 -0
- package/core/governance/__pycache__/leak_scanner.cpython-314.pyc +0 -0
- package/core/governance/__pycache__/qg_verdict.cpython-314.pyc +0 -0
- package/core/governance/__pycache__/routing_feedback.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/routing_feedback.cpython-314.pyc +0 -0
- package/core/governance/__pycache__/routing_feedback_cli.cpython-313.pyc +0 -0
- package/core/governance/evidence_checks.py +42 -7
- package/core/governance/routing_feedback.py +229 -0
- package/core/governance/routing_feedback_cli.py +49 -0
- package/core/hooks/__pycache__/_shared.cpython-312.pyc +0 -0
- package/core/hooks/__pycache__/gate_manifest.cpython-313.pyc +0 -0
- package/core/hooks/__pycache__/post_tool_use.cpython-312.pyc +0 -0
- package/core/hooks/__pycache__/post_tool_use.cpython-313.pyc +0 -0
- package/core/hooks/__pycache__/pre_tool_use.cpython-312.pyc +0 -0
- package/core/hooks/__pycache__/session_end.cpython-313.pyc +0 -0
- package/core/hooks/__pycache__/session_start.cpython-312.pyc +0 -0
- package/core/hooks/__pycache__/session_start.cpython-313.pyc +0 -0
- package/core/hooks/__pycache__/stop.cpython-313.pyc +0 -0
- package/core/hooks/__pycache__/stop.cpython-314.pyc +0 -0
- package/core/hooks/__pycache__/subagent_stop.cpython-313.pyc +0 -0
- package/core/hooks/__pycache__/subagent_stop.cpython-314.pyc +0 -0
- package/core/hooks/__pycache__/user_prompt_submit.cpython-313.pyc +0 -0
- package/core/hooks/__pycache__/user_prompt_submit.cpython-314.pyc +0 -0
- package/core/hooks/gate_manifest.py +366 -0
- package/core/hooks/post_tool_use.py +5 -2
- package/core/hooks/session_end.py +127 -0
- package/core/hooks/session_start.py +322 -0
- package/core/hooks/stop.py +69 -0
- package/core/hooks/subagent_stop.py +186 -0
- package/core/hooks/user_prompt_submit.py +10 -6
- package/core/knowledge/__pycache__/embedding_backends.cpython-313.pyc +0 -0
- package/core/knowledge/__pycache__/embedding_backends.cpython-314.pyc +0 -0
- package/core/knowledge/__pycache__/pattern_cards.cpython-313.pyc +0 -0
- package/core/knowledge/__pycache__/pattern_cards.cpython-314.pyc +0 -0
- package/core/knowledge/__pycache__/recipes.cpython-314.pyc +0 -0
- package/core/knowledge/__pycache__/vector_store.cpython-313.pyc +0 -0
- package/core/knowledge/__pycache__/vector_store.cpython-314.pyc +0 -0
- package/core/knowledge/embedding_backends.py +278 -0
- package/core/knowledge/pattern_cards.py +49 -1
- package/core/knowledge/vector_store.py +14 -2
- package/core/memory/__pycache__/semantic_store.cpython-312.pyc +0 -0
- package/core/memory/__pycache__/semantic_store.cpython-313.pyc +0 -0
- package/core/memory/__pycache__/semantic_store.cpython-314.pyc +0 -0
- package/core/memory/__pycache__/turn_capture.cpython-313.pyc +0 -0
- package/core/memory/__pycache__/turn_capture.cpython-314.pyc +0 -0
- package/core/memory/semantic_store.py +301 -0
- package/core/memory/turn_capture.py +277 -0
- package/core/registry/__pycache__/generator.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/claude_code.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/codex_cli.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/cost_governor.cpython-314.pyc +0 -0
- package/core/runtime/__pycache__/gemini_cli.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/llm_cost_telemetry.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/mcp_telemetry.cpython-312.pyc +0 -0
- package/core/shared/__pycache__/decay.cpython-313.pyc +0 -0
- package/core/shared/__pycache__/decay.cpython-314.pyc +0 -0
- package/core/shared/__pycache__/sqlite_recovery.cpython-312.pyc +0 -0
- package/core/shared/__pycache__/sqlite_recovery.cpython-313.pyc +0 -0
- package/core/shared/__pycache__/sqlite_recovery.cpython-314.pyc +0 -0
- package/core/shared/decay.py +94 -0
- package/core/shared/sqlite_recovery.py +266 -0
- package/core/synapse/__pycache__/agent_experiences_layer.cpython-313.pyc +0 -0
- package/core/synapse/__pycache__/agent_experiences_layer.cpython-314.pyc +0 -0
- package/core/synapse/__pycache__/engine.cpython-312.pyc +0 -0
- package/core/synapse/__pycache__/engine.cpython-313.pyc +0 -0
- package/core/synapse/__pycache__/engine.cpython-314.pyc +0 -0
- package/core/synapse/__pycache__/layers.cpython-312.pyc +0 -0
- package/core/synapse/__pycache__/pattern_library_layer.cpython-314.pyc +0 -0
- package/core/synapse/__pycache__/recipe_layer.cpython-313.pyc +0 -0
- package/core/synapse/__pycache__/recipe_layer.cpython-314.pyc +0 -0
- package/core/synapse/__pycache__/routing_feedback_layer.cpython-313.pyc +0 -0
- package/core/synapse/__pycache__/routing_feedback_layer.cpython-314.pyc +0 -0
- package/core/synapse/__pycache__/session_memory_layer.cpython-312.pyc +0 -0
- package/core/synapse/__pycache__/session_memory_layer.cpython-313.pyc +0 -0
- package/core/synapse/__pycache__/session_memory_layer.cpython-314.pyc +0 -0
- package/core/synapse/agent_experiences_layer.py +42 -3
- package/core/synapse/engine.py +13 -0
- package/core/synapse/recipe_layer.py +27 -4
- package/core/synapse/routing_feedback_layer.py +132 -0
- package/core/synapse/session_memory_layer.py +152 -0
- package/core/workflow/__pycache__/__init__.cpython-312.pyc +0 -0
- package/core/workflow/__pycache__/design_authorization.cpython-314.pyc +0 -0
- package/core/workflow/__pycache__/frontend_gate.cpython-314.pyc +0 -0
- package/core/workflow/__pycache__/specialist_enforcer.cpython-314.pyc +0 -0
- package/installer/adapters/claude-code.js +37 -0
- package/installer/claude-plugins.js +6 -0
- package/installer/cli.js +23 -2
- package/installer/config-seed.js +25 -16
- package/installer/doctor.js +116 -1
- package/installer/hook-lib.js +28 -0
- package/installer/index.js +48 -153
- package/installer/mcp-runner.js +108 -0
- package/installer/skill-deploy.js +189 -0
- package/installer/skills-mode.js +64 -0
- package/installer/update.js +55 -79
- package/knowledge/commands-registry.json.bak +171 -4
- package/knowledge/skills-manifest.json +2337 -0
- package/package.json +1 -1
- package/pyproject.toml +9 -1
- package/scripts/__pycache__/marketplace_gen.cpython-313.pyc +0 -0
- package/scripts/__pycache__/synapse-bridge.cpython-314.pyc +0 -0
- package/scripts/marketplace_gen.py +310 -0
- package/scripts/tools/__pycache__/docs_stats.cpython-313.pyc +0 -0
- package/scripts/tools/__pycache__/skill_budget.cpython-313.pyc +0 -0
- package/scripts/tools/docs_stats.py +6 -4
- package/scripts/tools/skill_budget.py +209 -0
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "arkaos-core"
|
|
3
|
-
version = "4.
|
|
3
|
+
version = "4.14.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"}
|
|
@@ -32,6 +32,14 @@ knowledge = [
|
|
|
32
32
|
"fastembed>=0.8.0",
|
|
33
33
|
"sqlite-vec>=0.1.1",
|
|
34
34
|
]
|
|
35
|
+
mcp = [
|
|
36
|
+
# The arka-tools MCP server runs under its own `uv` env in production
|
|
37
|
+
# (mcps/arka-tools/pyproject.toml); this extra lets the CORE test suite
|
|
38
|
+
# import the FastMCP decorators so the arka-tools security tests
|
|
39
|
+
# (OWASP LLM01 neutralization, write-gating) run on every Python
|
|
40
|
+
# version, not just where mcp happens to be installed.
|
|
41
|
+
"mcp[cli]>=1.2.0",
|
|
42
|
+
]
|
|
35
43
|
dashboard = [
|
|
36
44
|
"fastapi>=0.115.0",
|
|
37
45
|
"uvicorn>=0.32.0",
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
"""Marketplace generator — per-department plugins from the curated cut.
|
|
2
|
+
|
|
3
|
+
F2-7b (ruflo-teardown campaign, distribution endgame): the ArkaOS
|
|
4
|
+
marketplace answers ruflo's 38-plugin distribution surface WITHOUT its
|
|
5
|
+
344-SKILL.md context blowout. The curated core stays on the installer
|
|
6
|
+
channel (multi-runtime); every sub-skill OUTSIDE the curated set ships
|
|
7
|
+
à la carte in a per-department plugin, where the plugin namespace
|
|
8
|
+
(``arkaos-<dept>:<skill>``) also disambiguates the later-wins collision
|
|
9
|
+
slugs natively.
|
|
10
|
+
|
|
11
|
+
Single source of truth: ``config/skills-curated.yaml`` + the
|
|
12
|
+
``departments/`` tree. This script GENERATES (never hand-typed —
|
|
13
|
+
docs_stats/gate_manifest precedent):
|
|
14
|
+
|
|
15
|
+
- ``plugins/arkaos-<dept>/`` — one plugin per department with a
|
|
16
|
+
non-empty complement (dept sub-skills minus curated), skills exported
|
|
17
|
+
with the marketplace transformations;
|
|
18
|
+
- ``.claude-plugin/marketplace.json`` — EXTENDED, never replaced: the
|
|
19
|
+
``name``/``owner`` fields and the legacy ``arkaos-dev-skills`` entry
|
|
20
|
+
are preserved verbatim (that plugin may already be installed on user
|
|
21
|
+
machines and its 10-skill list is locked by
|
|
22
|
+
``test_marketplace_export.py``); top-level ``version`` tracks the
|
|
23
|
+
VERSION file (kills the stale ``metadata.version`` for good);
|
|
24
|
+
- ``knowledge/skills-manifest.json`` — slug -> {depts, curated,
|
|
25
|
+
plugins, collision} map consumed by /do install hints, the doctor and
|
|
26
|
+
the F2-7c skill-budget linter.
|
|
27
|
+
|
|
28
|
+
Regenerate + commit after touching the YAML, any SKILL.md set, or
|
|
29
|
+
VERSION:
|
|
30
|
+
|
|
31
|
+
python scripts/marketplace_gen.py
|
|
32
|
+
|
|
33
|
+
``tests/python/test_marketplace_gen.py`` drift-gates the committed tree
|
|
34
|
+
against a fresh in-memory run, byte for byte.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
import json
|
|
40
|
+
import re
|
|
41
|
+
import shutil
|
|
42
|
+
import sys
|
|
43
|
+
from pathlib import Path
|
|
44
|
+
|
|
45
|
+
import yaml
|
|
46
|
+
|
|
47
|
+
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
48
|
+
if str(REPO_ROOT / "scripts") not in sys.path:
|
|
49
|
+
sys.path.insert(0, str(REPO_ROOT / "scripts"))
|
|
50
|
+
|
|
51
|
+
from marketplace_export import _convert # noqa: E402
|
|
52
|
+
|
|
53
|
+
CURATED_YAML = REPO_ROOT / "config" / "skills-curated.yaml"
|
|
54
|
+
DEPARTMENTS_DIR = REPO_ROOT / "departments"
|
|
55
|
+
PLUGINS_DIR = REPO_ROOT / "plugins"
|
|
56
|
+
MARKETPLACE_JSON = REPO_ROOT / ".claude-plugin" / "marketplace.json"
|
|
57
|
+
SKILLS_MANIFEST_JSON = REPO_ROOT / "knowledge" / "skills-manifest.json"
|
|
58
|
+
VERSION = (REPO_ROOT / "VERSION").read_text(encoding="utf-8").strip()
|
|
59
|
+
|
|
60
|
+
MARKETPLACE_NAME = "arkaos"
|
|
61
|
+
LEGACY_PLUGIN = "arkaos-dev-skills"
|
|
62
|
+
_RESOURCE_DIRS = ("scripts", "references", "assets")
|
|
63
|
+
|
|
64
|
+
# The export _convert strips slash-command suffixes but lets markdown
|
|
65
|
+
# links to ArkaOS commands through (observed leak:
|
|
66
|
+
# "[Browser Integration Pattern](/arka)" in marketplace/skills/
|
|
67
|
+
# code-review). Fifth transformation: unwrap them to plain text.
|
|
68
|
+
_ARKA_LINK_PATTERN = re.compile(r"\[([^\]]+)\]\(/arka[^)]*\)")
|
|
69
|
+
|
|
70
|
+
_DEPT_LABELS = {
|
|
71
|
+
"brand": "Brand & Design",
|
|
72
|
+
"community": "Communities & Groups",
|
|
73
|
+
"content": "Content & Viralization",
|
|
74
|
+
"dev": "Development",
|
|
75
|
+
"ecom": "E-Commerce",
|
|
76
|
+
"finance": "Finance & Investment",
|
|
77
|
+
"kb": "Knowledge Management",
|
|
78
|
+
"landing": "Landing Pages & Funnels",
|
|
79
|
+
"leadership": "Leadership & People",
|
|
80
|
+
"marketing": "Marketing & Growth",
|
|
81
|
+
"ops": "Operations & Automation",
|
|
82
|
+
"org": "Organization & Teams",
|
|
83
|
+
"pm": "Project Management",
|
|
84
|
+
"quality": "Quality",
|
|
85
|
+
"saas": "SaaS & Micro-SaaS",
|
|
86
|
+
"sales": "Sales & Negotiation",
|
|
87
|
+
"strategy": "Strategy & Innovation",
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def load_curated() -> dict[str, list[str]]:
|
|
92
|
+
config = yaml.safe_load(CURATED_YAML.read_text(encoding="utf-8"))
|
|
93
|
+
return {
|
|
94
|
+
dept: sorted(slugs)
|
|
95
|
+
for dept, slugs in config["curated_subskills"].items()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def dept_subskills() -> dict[str, list[str]]:
|
|
100
|
+
"""All sub-skill slugs per department, from disk."""
|
|
101
|
+
result: dict[str, list[str]] = {}
|
|
102
|
+
for dept_dir in sorted(DEPARTMENTS_DIR.iterdir()):
|
|
103
|
+
skills_dir = dept_dir / "skills"
|
|
104
|
+
if not skills_dir.is_dir():
|
|
105
|
+
continue
|
|
106
|
+
slugs = sorted(
|
|
107
|
+
p.name for p in skills_dir.iterdir()
|
|
108
|
+
if (p / "SKILL.md").is_file()
|
|
109
|
+
)
|
|
110
|
+
if slugs:
|
|
111
|
+
result[dept_dir.name] = slugs
|
|
112
|
+
return result
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def collision_slugs(subskills: dict[str, list[str]]) -> dict[str, list[str]]:
|
|
116
|
+
"""Slugs owned by more than one department (later-wins on deploy)."""
|
|
117
|
+
owners: dict[str, list[str]] = {}
|
|
118
|
+
for dept, slugs in subskills.items():
|
|
119
|
+
for slug in slugs:
|
|
120
|
+
owners.setdefault(slug, []).append(dept)
|
|
121
|
+
return {s: sorted(d) for s, d in owners.items() if len(d) > 1}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
_NAME_LINE_PATTERN = re.compile(r"^name:\s*.*$", flags=re.MULTILINE)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def convert_skill(body: str, slug: str) -> str:
|
|
128
|
+
"""The 4 marketplace_export transformations + two plugin-specific
|
|
129
|
+
ones: unwrap markdown links to ArkaOS commands, and FORCE the
|
|
130
|
+
frontmatter name to the directory slug — the in-tree files carry
|
|
131
|
+
`name: <dept>/<name>` (235 of 265) and sometimes a name that is not
|
|
132
|
+
the dir slug at all (`logo` in `logo-brief/`); the plugin runtime
|
|
133
|
+
rejects name/dir mismatches."""
|
|
134
|
+
out = _ARKA_LINK_PATTERN.sub(r"\1", _convert(body))
|
|
135
|
+
return _NAME_LINE_PATTERN.sub(f"name: {slug}", out, count=1)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _plugin_description(dept: str, slugs: list[str]) -> str:
|
|
139
|
+
label = _DEPT_LABELS.get(dept, dept.title())
|
|
140
|
+
shown = ", ".join(slugs[:8])
|
|
141
|
+
more = f", +{len(slugs) - 8} more" if len(slugs) > 8 else ""
|
|
142
|
+
return (
|
|
143
|
+
f"ArkaOS {label} skills, à la carte — {len(slugs)} framework-backed "
|
|
144
|
+
f"skills beyond the curated core: {shown}{more}. Generated by "
|
|
145
|
+
f"scripts/marketplace_gen.py from departments/{dept}/skills/."
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _emit_skill(src_dir: Path, dest_dir: Path) -> None:
|
|
150
|
+
dest_dir.mkdir(parents=True, exist_ok=True)
|
|
151
|
+
body = (src_dir / "SKILL.md").read_text(encoding="utf-8")
|
|
152
|
+
(dest_dir / "SKILL.md").write_text(
|
|
153
|
+
convert_skill(body, dest_dir.name), encoding="utf-8")
|
|
154
|
+
for resource in _RESOURCE_DIRS:
|
|
155
|
+
src = src_dir / resource
|
|
156
|
+
if src.is_dir():
|
|
157
|
+
shutil.copytree(src, dest_dir / resource, dirs_exist_ok=True)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def build_plugins() -> dict[str, list[str]]:
|
|
161
|
+
"""Write plugins/arkaos-<dept>/ trees. Returns dept -> plugin slugs."""
|
|
162
|
+
curated = load_curated()
|
|
163
|
+
subskills = dept_subskills()
|
|
164
|
+
if PLUGINS_DIR.exists():
|
|
165
|
+
shutil.rmtree(PLUGINS_DIR)
|
|
166
|
+
emitted: dict[str, list[str]] = {}
|
|
167
|
+
for dept, slugs in subskills.items():
|
|
168
|
+
complement = [s for s in slugs if s not in set(curated.get(dept, []))]
|
|
169
|
+
if not complement:
|
|
170
|
+
continue
|
|
171
|
+
plugin_dir = PLUGINS_DIR / f"arkaos-{dept}"
|
|
172
|
+
for slug in complement:
|
|
173
|
+
_emit_skill(
|
|
174
|
+
DEPARTMENTS_DIR / dept / "skills" / slug,
|
|
175
|
+
plugin_dir / "skills" / slug,
|
|
176
|
+
)
|
|
177
|
+
_write_plugin_manifest(plugin_dir, dept, complement)
|
|
178
|
+
emitted[dept] = complement
|
|
179
|
+
return emitted
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _write_plugin_manifest(
|
|
183
|
+
plugin_dir: Path, dept: str, slugs: list[str]
|
|
184
|
+
) -> None:
|
|
185
|
+
manifest_dir = plugin_dir / ".claude-plugin"
|
|
186
|
+
manifest_dir.mkdir(parents=True, exist_ok=True)
|
|
187
|
+
plugin_json = {
|
|
188
|
+
"name": f"arkaos-{dept}",
|
|
189
|
+
"description": _plugin_description(dept, slugs),
|
|
190
|
+
# Version lives ONLY here (plugin.json wins silently over a
|
|
191
|
+
# marketplace-entry version per the plugin spec — never both).
|
|
192
|
+
"version": VERSION,
|
|
193
|
+
"author": {"name": "WizardingCode", "email": "hello@wizardingcode.io"},
|
|
194
|
+
"license": "MIT",
|
|
195
|
+
}
|
|
196
|
+
(manifest_dir / "plugin.json").write_text(
|
|
197
|
+
json.dumps(plugin_json, indent=2, ensure_ascii=False) + "\n",
|
|
198
|
+
encoding="utf-8",
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def build_marketplace(emitted: dict[str, list[str]]) -> dict:
|
|
203
|
+
"""Extend the existing manifest — name/owner/legacy entry verbatim."""
|
|
204
|
+
manifest = json.loads(MARKETPLACE_JSON.read_text(encoding="utf-8"))
|
|
205
|
+
if manifest.get("name") != MARKETPLACE_NAME:
|
|
206
|
+
raise ValueError(
|
|
207
|
+
f"marketplace name changed ({manifest.get('name')!r}) — the "
|
|
208
|
+
f"registered name is load-bearing on user machines; refusing"
|
|
209
|
+
)
|
|
210
|
+
legacy = [p for p in manifest["plugins"] if p.get("name") == LEGACY_PLUGIN]
|
|
211
|
+
if len(legacy) != 1:
|
|
212
|
+
raise ValueError("legacy arkaos-dev-skills entry missing — refusing")
|
|
213
|
+
# Top-level version tracks VERSION (metadata.version is deprecated
|
|
214
|
+
# upstream; the metadata KEY stays one release for backwards
|
|
215
|
+
# compatibility, but its value tracks VERSION too — the stale 2.71.0
|
|
216
|
+
# it carried was exactly the hand-typed drift this generator kills).
|
|
217
|
+
manifest["version"] = VERSION
|
|
218
|
+
if isinstance(manifest.get("metadata"), dict):
|
|
219
|
+
manifest["metadata"]["version"] = VERSION
|
|
220
|
+
dept_entries = [
|
|
221
|
+
{
|
|
222
|
+
"name": f"arkaos-{dept}",
|
|
223
|
+
"source": f"./plugins/arkaos-{dept}",
|
|
224
|
+
"category": dept,
|
|
225
|
+
"description": _plugin_description(dept, slugs),
|
|
226
|
+
}
|
|
227
|
+
for dept, slugs in sorted(emitted.items())
|
|
228
|
+
]
|
|
229
|
+
manifest["plugins"] = legacy + dept_entries
|
|
230
|
+
return manifest
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def build_skills_manifest(emitted: dict[str, list[str]]) -> dict:
|
|
234
|
+
curated = load_curated()
|
|
235
|
+
subskills = dept_subskills()
|
|
236
|
+
collisions = collision_slugs(subskills)
|
|
237
|
+
skills: dict[str, dict] = {}
|
|
238
|
+
for dept, slugs in subskills.items():
|
|
239
|
+
for slug in slugs:
|
|
240
|
+
entry = skills.setdefault(slug, {
|
|
241
|
+
"depts": [], "curated": False, "plugins": [],
|
|
242
|
+
"collision": slug in collisions,
|
|
243
|
+
})
|
|
244
|
+
entry["depts"].append(dept)
|
|
245
|
+
if slug in set(curated.get(dept, [])):
|
|
246
|
+
entry["curated"] = True
|
|
247
|
+
elif dept in emitted and slug in emitted[dept]:
|
|
248
|
+
entry["plugins"].append(f"arkaos-{dept}@{MARKETPLACE_NAME}")
|
|
249
|
+
for entry in skills.values():
|
|
250
|
+
entry["depts"].sort()
|
|
251
|
+
entry["plugins"].sort()
|
|
252
|
+
return {
|
|
253
|
+
"_meta": {
|
|
254
|
+
"generator": "scripts/marketplace_gen.py",
|
|
255
|
+
"version": VERSION,
|
|
256
|
+
"marketplace": MARKETPLACE_NAME,
|
|
257
|
+
},
|
|
258
|
+
"structural": _structural_surface(),
|
|
259
|
+
"skills": dict(sorted(skills.items())),
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _structural_surface() -> dict:
|
|
264
|
+
return {
|
|
265
|
+
"main": "arka",
|
|
266
|
+
"hubs": sorted(
|
|
267
|
+
f"arka-{d.name}" for d in DEPARTMENTS_DIR.iterdir()
|
|
268
|
+
if (d / "SKILL.md").is_file()
|
|
269
|
+
),
|
|
270
|
+
"meta": sorted(
|
|
271
|
+
f"arka-{p.name}" for p in (REPO_ROOT / "arka" / "skills").iterdir()
|
|
272
|
+
if (p / "SKILL.md").is_file()
|
|
273
|
+
),
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def generate() -> dict:
|
|
278
|
+
"""Full generation. Returns summary counts for the CLI output."""
|
|
279
|
+
emitted = build_plugins()
|
|
280
|
+
marketplace = build_marketplace(emitted)
|
|
281
|
+
MARKETPLACE_JSON.write_text(
|
|
282
|
+
json.dumps(marketplace, indent=2, ensure_ascii=False) + "\n",
|
|
283
|
+
encoding="utf-8",
|
|
284
|
+
)
|
|
285
|
+
skills_manifest = build_skills_manifest(emitted)
|
|
286
|
+
SKILLS_MANIFEST_JSON.write_text(
|
|
287
|
+
json.dumps(skills_manifest, indent=2, ensure_ascii=False) + "\n",
|
|
288
|
+
encoding="utf-8",
|
|
289
|
+
)
|
|
290
|
+
return {
|
|
291
|
+
"plugins": len(emitted),
|
|
292
|
+
"plugin_skills": sum(len(s) for s in emitted.values()),
|
|
293
|
+
"curated": sum(len(s) for s in load_curated().values()),
|
|
294
|
+
"collisions": len(collision_slugs(dept_subskills())),
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def main() -> int:
|
|
299
|
+
summary = generate()
|
|
300
|
+
print(
|
|
301
|
+
f"marketplace generated: {summary['plugins']} dept plugins, "
|
|
302
|
+
f"{summary['plugin_skills']} plugin skills, "
|
|
303
|
+
f"{summary['curated']} curated in core, "
|
|
304
|
+
f"{summary['collisions']} collision slugs (plugin-namespaced)"
|
|
305
|
+
)
|
|
306
|
+
return 0
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
if __name__ == "__main__":
|
|
310
|
+
raise SystemExit(main())
|
|
Binary file
|
|
Binary file
|
|
@@ -20,13 +20,12 @@ import re
|
|
|
20
20
|
import subprocess
|
|
21
21
|
import sys
|
|
22
22
|
from pathlib import Path
|
|
23
|
-
from typing import Optional
|
|
24
23
|
|
|
25
24
|
_TEST_DEF_RE = re.compile(r"^\s*(?:async\s+)?def\s+test_\w+", re.MULTILINE)
|
|
26
25
|
_COLLECTED_RE = re.compile(r"(\d+)\s+tests?\s+collected")
|
|
27
26
|
|
|
28
27
|
|
|
29
|
-
def repo_root(start:
|
|
28
|
+
def repo_root(start: Path | None = None) -> Path:
|
|
30
29
|
"""Find the repo root by walking up to a dir with VERSION + departments/."""
|
|
31
30
|
cur = (start or Path(__file__).resolve()).resolve()
|
|
32
31
|
candidates = [cur, *cur.parents] if cur.is_dir() else [cur.parent, *cur.parents]
|
|
@@ -64,8 +63,11 @@ def count_skills(root: Path) -> dict:
|
|
|
64
63
|
return len(list(base.rglob("SKILL.md"))) if base.is_dir() else 0
|
|
65
64
|
|
|
66
65
|
dept, arka, market = _n("departments"), _n("arka"), _n("marketplace")
|
|
66
|
+
# plugins/ is a GENERATED export-copy (scripts/marketplace_gen.py)
|
|
67
|
+
# of non-curated department skills — informative only, never part
|
|
68
|
+
# of the core headline (the sources remain under departments/).
|
|
67
69
|
return {"departments": dept, "arka": arka, "marketplace": market,
|
|
68
|
-
"core": dept + arka}
|
|
70
|
+
"plugins": _n("plugins"), "core": dept + arka}
|
|
69
71
|
|
|
70
72
|
|
|
71
73
|
def count_adrs(root: Path) -> int:
|
|
@@ -83,7 +85,7 @@ def count_test_functions(root: Path) -> int:
|
|
|
83
85
|
for f in tdir.rglob("test_*.py"))
|
|
84
86
|
|
|
85
87
|
|
|
86
|
-
def collect_pytest_cases(root: Path) ->
|
|
88
|
+
def collect_pytest_cases(root: Path) -> int | None:
|
|
87
89
|
"""Authoritative pytest case count via --collect-only. None on failure."""
|
|
88
90
|
try:
|
|
89
91
|
out = subprocess.run(
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"""Skill-budget linter — the curated default must stay small (F2-7c).
|
|
2
|
+
|
|
3
|
+
Measures the ALWAYS-ON context surface of the default skill install:
|
|
4
|
+
the frontmatter descriptions of every skill the curated cut deploys
|
|
5
|
+
(main /arka + 17 hubs + 14 meta + curated sub-skills, resolved from
|
|
6
|
+
``knowledge/skills-manifest.json`` — the generated classification the
|
|
7
|
+
marketplace generator emits). Skill bodies are invoke-only and do not
|
|
8
|
+
count; descriptions are what the runtime can inject per session.
|
|
9
|
+
|
|
10
|
+
Honesty note, printed with every report: live sessions currently
|
|
11
|
+
observe a smaller footprint (~2.3k tokens) because the runtime
|
|
12
|
+
truncates most entries to name-only — but that truncation is
|
|
13
|
+
NON-CONTRACTUAL runtime behavior. This budget sizes the GUARANTEED
|
|
14
|
+
surface (chars of description; tokens estimated at chars/4).
|
|
15
|
+
|
|
16
|
+
Thresholds (calibrated against the measured 2026-07-12 baseline:
|
|
17
|
+
69 skills, 34,929 chars):
|
|
18
|
+
|
|
19
|
+
skills WARN > 72 (90% of cap) FAIL > 80
|
|
20
|
+
total WARN > 36,000 chars FAIL > 40,000 (~10k tokens)
|
|
21
|
+
per-skill WARN > 800 chars FAIL > 1,024
|
|
22
|
+
structural completeness FAIL (flow & co. must be default)
|
|
23
|
+
curated slug in a collision set FAIL
|
|
24
|
+
|
|
25
|
+
CI gate: ``tests/python/test_skill_budget.py`` (repo-only, no install
|
|
26
|
+
needed). CLI: ``python scripts/tools/skill_budget.py [--json]`` —
|
|
27
|
+
exit 1 when any FAIL finding exists. stdlib only (docs_stats pattern).
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import argparse
|
|
33
|
+
import json
|
|
34
|
+
import re
|
|
35
|
+
import sys
|
|
36
|
+
from dataclasses import asdict, dataclass
|
|
37
|
+
from pathlib import Path
|
|
38
|
+
|
|
39
|
+
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
40
|
+
MANIFEST_PATH = REPO_ROOT / "knowledge" / "skills-manifest.json"
|
|
41
|
+
|
|
42
|
+
MAX_SKILLS_FAIL = 80
|
|
43
|
+
MAX_SKILLS_WARN = 72
|
|
44
|
+
TOTAL_CHARS_FAIL = 40_000
|
|
45
|
+
TOTAL_CHARS_WARN = 36_000
|
|
46
|
+
PER_SKILL_FAIL = 1_024
|
|
47
|
+
PER_SKILL_WARN = 800
|
|
48
|
+
|
|
49
|
+
_FRONTMATTER_RE = re.compile(r"^---\n(.*?)\n---", re.DOTALL)
|
|
50
|
+
_DESCRIPTION_RE = re.compile(r"^description:\s*(.*)$", re.MULTILINE)
|
|
51
|
+
_ROOT_KEY_RE = re.compile(r"^[A-Za-z_-]+:")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass(frozen=True)
|
|
55
|
+
class Finding:
|
|
56
|
+
level: str # "FAIL" | "WARN"
|
|
57
|
+
check: str
|
|
58
|
+
detail: str
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def frontmatter_description(skill_md: Path) -> str:
|
|
62
|
+
"""Extract the description value, including folded (>) blocks."""
|
|
63
|
+
match = _FRONTMATTER_RE.match(skill_md.read_text(encoding="utf-8"))
|
|
64
|
+
if not match:
|
|
65
|
+
return ""
|
|
66
|
+
frontmatter = match.group(1)
|
|
67
|
+
desc = _DESCRIPTION_RE.search(frontmatter)
|
|
68
|
+
if not desc:
|
|
69
|
+
return ""
|
|
70
|
+
value = desc.group(1).strip()
|
|
71
|
+
if value not in (">", "|", ">-", "|-"):
|
|
72
|
+
return value
|
|
73
|
+
folded: list[str] = []
|
|
74
|
+
for line in frontmatter[desc.end():].split("\n"):
|
|
75
|
+
if line.startswith((" ", "\t")):
|
|
76
|
+
folded.append(line.strip())
|
|
77
|
+
elif _ROOT_KEY_RE.match(line):
|
|
78
|
+
break
|
|
79
|
+
return " ".join(folded).strip()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def default_surface(manifest: dict) -> dict[str, Path]:
|
|
83
|
+
"""Deployed-name -> source SKILL.md for the curated default install."""
|
|
84
|
+
surface: dict[str, Path] = {"arka": REPO_ROOT / "arka" / "SKILL.md"}
|
|
85
|
+
for hub in manifest["structural"]["hubs"]:
|
|
86
|
+
dept = hub.removeprefix("arka-")
|
|
87
|
+
surface[hub] = REPO_ROOT / "departments" / dept / "SKILL.md"
|
|
88
|
+
for meta in manifest["structural"]["meta"]:
|
|
89
|
+
slug = meta.removeprefix("arka-")
|
|
90
|
+
surface[meta] = REPO_ROOT / "arka" / "skills" / slug / "SKILL.md"
|
|
91
|
+
for slug, entry in manifest["skills"].items():
|
|
92
|
+
if entry["curated"]:
|
|
93
|
+
dept = entry["depts"][0]
|
|
94
|
+
surface[f"arka-{slug}"] = (
|
|
95
|
+
REPO_ROOT / "departments" / dept / "skills" / slug / "SKILL.md"
|
|
96
|
+
)
|
|
97
|
+
return surface
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _threshold_findings(count: int, total: int) -> list[Finding]:
|
|
101
|
+
findings: list[Finding] = []
|
|
102
|
+
if count > MAX_SKILLS_FAIL:
|
|
103
|
+
findings.append(Finding(
|
|
104
|
+
"FAIL", "skill-count",
|
|
105
|
+
f"default install carries {count} skills (cap {MAX_SKILLS_FAIL})"))
|
|
106
|
+
elif count > MAX_SKILLS_WARN:
|
|
107
|
+
findings.append(Finding(
|
|
108
|
+
"WARN", "skill-count",
|
|
109
|
+
f"{count} skills — approaching the {MAX_SKILLS_FAIL} cap"))
|
|
110
|
+
if total > TOTAL_CHARS_FAIL:
|
|
111
|
+
findings.append(Finding(
|
|
112
|
+
"FAIL", "total-description-chars",
|
|
113
|
+
f"{total} chars of always-on descriptions "
|
|
114
|
+
f"(cap {TOTAL_CHARS_FAIL} ~= {TOTAL_CHARS_FAIL // 4} tokens)"))
|
|
115
|
+
elif total > TOTAL_CHARS_WARN:
|
|
116
|
+
findings.append(Finding(
|
|
117
|
+
"WARN", "total-description-chars",
|
|
118
|
+
f"{total} chars — approaching the {TOTAL_CHARS_FAIL} cap"))
|
|
119
|
+
return findings
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _structural_findings(manifest: dict) -> list[Finding]:
|
|
123
|
+
findings: list[Finding] = []
|
|
124
|
+
meta = set(manifest["structural"]["meta"])
|
|
125
|
+
for required in ("arka-flow", "arka-forge", "arka-fusion"):
|
|
126
|
+
if required not in meta:
|
|
127
|
+
findings.append(Finding(
|
|
128
|
+
"FAIL", "structural-completeness",
|
|
129
|
+
f"{required} missing from the structural default — "
|
|
130
|
+
f"evidence-flow and planning are NON-NEGOTIABLE"))
|
|
131
|
+
for slug, entry in manifest["skills"].items():
|
|
132
|
+
if entry["curated"] and entry["collision"]:
|
|
133
|
+
findings.append(Finding(
|
|
134
|
+
"FAIL", "curated-collision",
|
|
135
|
+
f"curated slug {slug!r} is a later-wins collision — it "
|
|
136
|
+
f"must live only in plugins"))
|
|
137
|
+
return findings
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def audit(manifest_path: Path | None = None) -> dict:
|
|
141
|
+
"""Full audit. Returns {summary, findings, note}.
|
|
142
|
+
|
|
143
|
+
``manifest_path`` resolves the module attribute at CALL time so
|
|
144
|
+
tests can monkeypatch ``MANIFEST_PATH`` (a def-time default would
|
|
145
|
+
freeze the original binding).
|
|
146
|
+
"""
|
|
147
|
+
manifest_path = manifest_path or MANIFEST_PATH
|
|
148
|
+
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
149
|
+
surface = default_surface(manifest)
|
|
150
|
+
sizes = {
|
|
151
|
+
name: len(frontmatter_description(path))
|
|
152
|
+
for name, path in surface.items() if path.is_file()
|
|
153
|
+
}
|
|
154
|
+
missing = sorted(set(surface) - set(sizes))
|
|
155
|
+
total = sum(sizes.values())
|
|
156
|
+
findings = _threshold_findings(len(surface), total)
|
|
157
|
+
findings.extend(_structural_findings(manifest))
|
|
158
|
+
for name in missing:
|
|
159
|
+
findings.append(Finding(
|
|
160
|
+
"FAIL", "missing-source", f"{name}: source SKILL.md not found"))
|
|
161
|
+
for name, size in sorted(sizes.items(), key=lambda kv: -kv[1]):
|
|
162
|
+
if size > PER_SKILL_FAIL:
|
|
163
|
+
findings.append(Finding(
|
|
164
|
+
"FAIL", "per-skill-description",
|
|
165
|
+
f"{name}: {size} chars (cap {PER_SKILL_FAIL})"))
|
|
166
|
+
elif size > PER_SKILL_WARN:
|
|
167
|
+
findings.append(Finding(
|
|
168
|
+
"WARN", "per-skill-description",
|
|
169
|
+
f"{name}: {size} chars (warn over {PER_SKILL_WARN})"))
|
|
170
|
+
return {
|
|
171
|
+
"summary": {
|
|
172
|
+
"skills": len(surface),
|
|
173
|
+
"total_description_chars": total,
|
|
174
|
+
"estimated_tokens": total // 4,
|
|
175
|
+
"fails": sum(1 for f in findings if f.level == "FAIL"),
|
|
176
|
+
"warns": sum(1 for f in findings if f.level == "WARN"),
|
|
177
|
+
},
|
|
178
|
+
"findings": [asdict(f) for f in findings],
|
|
179
|
+
"note": (
|
|
180
|
+
"Budget sizes the GUARANTEED always-on surface (description "
|
|
181
|
+
"chars). Live sessions may observe less (~2.3k tokens) because "
|
|
182
|
+
"the runtime truncates entries to name-only — that truncation "
|
|
183
|
+
"is non-contractual and not relied upon."
|
|
184
|
+
),
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def main(argv: list[str] | None = None) -> int:
|
|
189
|
+
parser = argparse.ArgumentParser(
|
|
190
|
+
prog="python scripts/tools/skill_budget.py",
|
|
191
|
+
description="Audit the curated default skill surface. Exit 1 on FAIL.",
|
|
192
|
+
)
|
|
193
|
+
parser.add_argument("--json", action="store_true", dest="as_json")
|
|
194
|
+
args = parser.parse_args(argv)
|
|
195
|
+
report = audit()
|
|
196
|
+
if args.as_json:
|
|
197
|
+
print(json.dumps(report, indent=2))
|
|
198
|
+
else:
|
|
199
|
+
s = report["summary"]
|
|
200
|
+
print(f"skills: {s['skills']} | always-on description chars: "
|
|
201
|
+
f"{s['total_description_chars']} (~{s['estimated_tokens']} tokens)")
|
|
202
|
+
for f in report["findings"]:
|
|
203
|
+
print(f" [{f['level']}] {f['check']}: {f['detail']}")
|
|
204
|
+
print(report["note"])
|
|
205
|
+
return 1 if report["summary"]["fails"] else 0
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
if __name__ == "__main__":
|
|
209
|
+
sys.exit(main())
|