arkaos 4.9.0 → 4.10.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
- 4.9.0
1
+ 4.10.0
package/bin/arka CHANGED
@@ -137,7 +137,11 @@ case "${1:-}" in
137
137
  REGISTRY="$REPO_DIR/knowledge/commands-registry.json"
138
138
  case "${1:-}" in
139
139
  rebuild)
140
- bash "$REPO_DIR/bin/arka-registry-gen"
140
+ # Shared resolver (venv-first) with plain python3 fallback —
141
+ # the generator is stdlib-only.
142
+ _ARKA_LIB="$REPO_DIR/config/hooks/_lib/arka_python.sh"
143
+ if [ -f "$_ARKA_LIB" ]; then . "$_ARKA_LIB"; else ARKA_PY="python3"; fi
144
+ (cd "$REPO_DIR" && "$ARKA_PY" -m core.registry.generator)
141
145
  echo "Registry rebuilt: $(jq '._meta.total_commands' "$REGISTRY") commands."
142
146
  ;;
143
147
  --json)
package/bin/arkaos CHANGED
@@ -65,8 +65,8 @@ case "${1:-}" in
65
65
  agents=$(jq -r '._meta.total_agents // 0' "$REPO_ROOT/knowledge/agents-registry-v2.json")
66
66
  echo " Agents: ${agents}"
67
67
  fi
68
- if [ -n "$REPO_ROOT" ] && [ -f "$REPO_ROOT/knowledge/commands-registry-v2.json" ] && command -v jq &>/dev/null; then
69
- commands=$(jq -r '._meta.total_commands // 0' "$REPO_ROOT/knowledge/commands-registry-v2.json")
68
+ if [ -n "$REPO_ROOT" ] && [ -f "$REPO_ROOT/knowledge/commands-registry.json" ] && command -v jq &>/dev/null; then
69
+ commands=$(jq -r '._meta.total_commands // 0' "$REPO_ROOT/knowledge/commands-registry.json")
70
70
  echo " Commands: ${commands}"
71
71
  fi
72
72
  echo ""
@@ -1,28 +1,47 @@
1
- """Commands registry generator.
2
-
3
- Scans all SKILL.md files, extracts command tables, and generates
4
- a machine-readable JSON registry for /do routing and Synapse L5.
1
+ """Commands registry generator — single canonical source (M2 consolidation).
2
+
3
+ Scans every SKILL.md command table in the repo (arka orchestrator,
4
+ department SKILL.md files, and department sub-skills) and generates
5
+ ``knowledge/commands-registry.json`` — the one registry consumed by
6
+ runtime routing (system-prompt.sh), Synapse L5 hints (synapse-bridge),
7
+ ``bin/arka commands``, the dashboard API, and ``bin/arkaos`` status.
8
+
9
+ Replaces the retired bash ``bin/arka-registry-gen`` and the parallel
10
+ ``commands-registry-v2.json`` (2026-07-09): one deterministic generator,
11
+ repo sources only — machine-local ``arka-ext-*``/``arka-pro-*`` skills
12
+ are intentionally NOT scanned so the committed file is reproducible;
13
+ ``tests/python/test_commands_registry.py`` locks committed-vs-regen
14
+ drift the same way the agents registry is locked.
15
+
16
+ Department naming follows the command prefixes (``mkt``/``fin``/
17
+ ``strat``), matching the runtime tables in CLAUDE.md.
5
18
  """
6
19
 
7
20
  import json
8
21
  import re
9
- from datetime import datetime
22
+ from datetime import datetime, timezone
10
23
  from pathlib import Path
11
- from typing import Optional
12
24
 
25
+ # Directory name → command-prefix department slug.
26
+ DEPT_PREFIX = {
27
+ "marketing": "mkt",
28
+ "finance": "fin",
29
+ "strategy": "strat",
30
+ }
13
31
 
14
- # Keywords map: department common keywords users might type
32
+ # Fallback keywords per department slug when a command has no entry in
33
+ # knowledge/commands-keywords.json — feeds Synapse L5 hint matching.
15
34
  DEPARTMENT_KEYWORDS: dict[str, list[str]] = {
16
35
  "dev": ["build", "code", "feature", "deploy", "test", "review", "scaffold", "debug",
17
36
  "refactor", "api", "migration", "implement", "fix", "bug", "database", "architecture"],
18
- "marketing": ["social", "content", "campaign", "post", "instagram", "linkedin", "twitter",
19
- "tiktok", "seo", "marketing", "ads", "email", "growth", "analytics"],
37
+ "mkt": ["social", "content", "campaign", "post", "instagram", "linkedin", "twitter",
38
+ "tiktok", "seo", "marketing", "ads", "email", "growth", "analytics"],
20
39
  "brand": ["brand", "logo", "colors", "palette", "mockup", "identity", "naming",
21
40
  "positioning", "voice", "guidelines", "ux", "wireframe", "design", "ui"],
22
- "finance": ["budget", "invoice", "revenue", "forecast", "profit", "financial", "invest",
23
- "valuation", "cashflow", "expense", "pitch", "model", "scenario"],
24
- "strategy": ["strategy", "brainstorm", "market", "swot", "competitor", "roadmap",
25
- "position", "blue ocean", "five forces", "tam", "moat", "growth"],
41
+ "fin": ["budget", "invoice", "revenue", "forecast", "profit", "financial", "invest",
42
+ "valuation", "cashflow", "expense", "pitch", "model", "scenario"],
43
+ "strat": ["strategy", "brainstorm", "market", "swot", "competitor", "roadmap",
44
+ "position", "blue ocean", "five forces", "tam", "moat", "growth"],
26
45
  "ecom": ["store", "product", "shop", "shopify", "ecommerce", "catalog", "cart",
27
46
  "checkout", "pricing", "marketplace", "rfm", "conversion", "cro"],
28
47
  "kb": ["learn", "persona", "knowledge", "youtube", "transcribe", "research",
@@ -41,150 +60,159 @@ DEPARTMENT_KEYWORDS: dict[str, list[str]] = {
41
60
  "circle", "gamification", "engagement", "moderate", "event"],
42
61
  "sales": ["pipeline", "proposal", "discovery", "objection", "negotiate", "deal",
43
62
  "close", "prospect", "spin", "challenger", "forecast"],
44
- "lead": ["leadership", "delegation", "1on1", "feedback", "culture", "hiring",
45
- "performance review", "team build", "conflict", "okr"],
63
+ "leadership": ["leadership", "delegation", "1on1", "feedback", "culture", "hiring",
64
+ "performance review", "team build", "conflict", "okr"],
46
65
  "org": ["org design", "hiring plan", "onboarding", "remote", "meeting",
47
66
  "compensation", "decision", "team assess", "sop"],
48
67
  }
49
68
 
69
+ # Metadata rules ported verbatim from the retired bin/arka-registry-gen.
70
+ _DEV_BRANCH_WORDS = {"feature", "api", "debug", "refactor", "db"}
71
+ _DEV_MODIFY_WORDS = {"scaffold", "deploy", "test"}
72
+
73
+ _TABLE_ROW_RE = re.compile(r"\|\s*`([^`]+)`\s*\|\s*([^|]+)\|")
74
+ _FRONTMATTER_NAME_RE = re.compile(r"^name:\s*(\S+)", re.MULTILINE)
75
+
76
+
77
+ def derive_command_id(command_text: str) -> str:
78
+ """``/arka costs [period]`` → ``arka-costs``.
79
+
80
+ Drops everything from the first argument token (``<arg>``,
81
+ ``[optional]``, ``--flag``) onward, so ids never embed argument
82
+ syntax (the old ``dev-onboard---ecosystem`` class of bug).
83
+ """
84
+ tokens: list[str] = []
85
+ for token in command_text.lstrip("/").split():
86
+ if token.startswith(("<", "[", "--")):
87
+ break
88
+ tokens.append(token)
89
+ return "-".join(tokens).lower()
90
+
91
+
92
+ def extract_lead_agent(text: str) -> str:
93
+ match = _FRONTMATTER_NAME_RE.search(text)
94
+ return match.group(1) if match else ""
95
+
50
96
 
51
97
  def extract_commands_from_skill(skill_path: Path) -> list[dict]:
52
98
  """Extract commands from a SKILL.md file's command table.
53
99
 
54
- Parses markdown tables with | Command | Description | format.
100
+ Parses markdown tables with | `/command` | Description | rows.
55
101
  """
56
102
  if not skill_path.exists():
57
103
  return []
58
104
 
59
- text = skill_path.read_text()
105
+ text = skill_path.read_text(encoding="utf-8")
106
+ lead_agent = extract_lead_agent(text)
60
107
  commands = []
61
-
62
- # Find command tables (lines with | `/ ... ` | description | ...)
63
108
  for line in text.split("\n"):
64
- match = re.match(r"\|\s*`([^`]+)`\s*\|\s*([^|]+)\|", line)
65
- if match:
66
- command = match.group(1).strip()
67
- description = match.group(2).strip()
68
- if not command.startswith("/") or "Description" in description or description.startswith("-"):
69
- continue
70
- commands.append({
71
- "command": command,
72
- "description": description,
73
- })
74
-
109
+ match = _TABLE_ROW_RE.match(line)
110
+ if not match:
111
+ continue
112
+ command = match.group(1).strip()
113
+ description = match.group(2).strip()
114
+ if not command.startswith("/") or "Description" in description or description.startswith("-"):
115
+ continue
116
+ commands.append({
117
+ "command": command,
118
+ "description": description,
119
+ "lead_agent": lead_agent,
120
+ })
75
121
  return commands
76
122
 
77
123
 
78
- def determine_department(skill_path: Path) -> str:
79
- """Determine department from the skill file's directory."""
80
- # departments/dev/SKILL.md → dev
81
- # arka/SKILL.md arka
82
- parent = skill_path.parent.name
83
- return parent
124
+ def command_metadata(command_text: str) -> dict:
125
+ """tier / requires_branch / modifies_code bash-generator rules."""
126
+ if command_text.startswith("/dev "):
127
+ first_arg = command_text.split()[1] if len(command_text.split()) > 1 else ""
128
+ if first_arg in _DEV_BRANCH_WORDS:
129
+ return {"tier": 1, "requires_branch": True, "modifies_code": True}
130
+ if first_arg in _DEV_MODIFY_WORDS:
131
+ return {"tier": 2, "requires_branch": False, "modifies_code": True}
132
+ if first_arg == "security-audit":
133
+ return {"tier": 1, "requires_branch": False, "modifies_code": False}
134
+ return {"tier": 2, "requires_branch": False, "modifies_code": False}
135
+
136
+
137
+ def _load_keywords_seed(base_dir: Path) -> dict:
138
+ seed_path = base_dir / "knowledge" / "commands-keywords.json"
139
+ if not seed_path.exists():
140
+ return {}
141
+ return json.loads(seed_path.read_text(encoding="utf-8"))
142
+
143
+
144
+ def _department_slug(skill_path: Path) -> str:
145
+ # departments/<dir>/SKILL.md and departments/<dir>/skills/<s>/SKILL.md
146
+ parts = skill_path.parts
147
+ dir_name = parts[parts.index("departments") + 1]
148
+ return DEPT_PREFIX.get(dir_name, dir_name)
84
149
 
85
150
 
86
- def extract_tier(description: str) -> int:
87
- """Guess command tier from description keywords."""
88
- desc_lower = description.lower()
89
- if any(w in desc_lower for w in ["full", "enterprise", "comprehensive", "complete"]):
90
- return 1
91
- if any(w in desc_lower for w in ["audit", "analysis", "strategy", "design"]):
92
- return 2
93
- return 3
151
+ def _skill_sources(base_dir: Path) -> list[tuple[Path, str]]:
152
+ """(skill_path, department) in deterministic scan order.
153
+
154
+ Order matters: dedup keeps the FIRST occurrence of an id, so the
155
+ orchestrator wins over departments, departments over sub-skills.
156
+ """
157
+ sources: list[tuple[Path, str]] = [(base_dir / "arka" / "SKILL.md", "arka")]
158
+ for path in sorted(base_dir.glob("departments/*/SKILL.md")):
159
+ sources.append((path, _department_slug(path)))
160
+ for path in sorted(base_dir.glob("departments/*/skills/*/SKILL.md")):
161
+ sources.append((path, _department_slug(path)))
162
+ return sources
94
163
 
95
164
 
96
165
  def generate_commands_registry(
97
166
  base_dir: str | Path,
98
167
  output_path: str | Path,
99
168
  ) -> dict:
100
- """Generate commands-registry-v2.json from all SKILL.md files.
101
-
102
- Args:
103
- base_dir: Project root directory.
104
- output_path: Where to write the JSON registry.
105
-
106
- Returns:
107
- The registry dict.
108
- """
169
+ """Generate the canonical commands-registry.json from SKILL.md files."""
109
170
  base_dir = Path(base_dir)
110
171
  output_path = Path(output_path)
172
+ keywords_seed = _load_keywords_seed(base_dir)
111
173
 
112
- all_commands = []
113
-
114
- # v2 department directories (exclude v1 legacy dirs)
115
- v2_departments = {
116
- "dev", "marketing", "brand", "finance", "strategy", "ecom", "kb",
117
- "ops", "pm", "saas", "landing", "content", "community", "sales",
118
- "leadership", "org", "quality",
119
- }
120
-
121
- # Scan department SKILL.md files
122
- for skill_file in sorted(base_dir.glob("departments/*/SKILL.md")):
123
- if skill_file.parent.name not in v2_departments:
124
- continue
125
- dept = determine_department(skill_file)
126
- raw_commands = extract_commands_from_skill(skill_file)
127
- keywords = DEPARTMENT_KEYWORDS.get(dept, [])
128
-
129
- for cmd in raw_commands:
174
+ all_commands: list[dict] = []
175
+ seen_ids: set[str] = set()
176
+ for skill_path, dept in _skill_sources(base_dir):
177
+ for cmd in extract_commands_from_skill(skill_path):
130
178
  command_text = cmd["command"]
131
- # Generate ID: /dev feature → dev-feature
132
- cmd_id = command_text.lstrip("/").replace(" ", "-").split("<")[0].rstrip("-")
133
-
134
- # Determine if code-modifying
135
- modifies_code = dept == "dev" and any(
136
- w in command_text for w in ["feature", "api", "debug", "refactor", "db", "scaffold"]
137
- )
138
-
179
+ cmd_id = derive_command_id(command_text)
180
+ if not cmd_id or cmd_id in seen_ids:
181
+ continue
182
+ seen_ids.add(cmd_id)
183
+ seed = keywords_seed.get(cmd_id, {})
184
+ keywords = seed.get("keywords") or DEPARTMENT_KEYWORDS.get(dept, [])[:8]
139
185
  all_commands.append({
140
186
  "id": cmd_id,
141
187
  "command": command_text,
142
188
  "department": dept,
143
189
  "description": cmd["description"],
144
- "keywords": keywords[:8],
145
- "tier": extract_tier(cmd["description"]),
146
- "modifies_code": modifies_code,
147
- "requires_branch": modifies_code,
148
- })
149
-
150
- # Scan orchestrator SKILL.md
151
- arka_skill = base_dir / "arka" / "SKILL.md"
152
- if arka_skill.exists():
153
- raw_commands = extract_commands_from_skill(arka_skill)
154
- for cmd in raw_commands:
155
- command_text = cmd["command"]
156
- cmd_id = command_text.lstrip("/").replace(" ", "-").split("<")[0].rstrip("-")
157
- all_commands.append({
158
- "id": cmd_id,
159
- "command": command_text,
160
- "department": "arka",
161
- "description": cmd["description"],
162
- "keywords": [],
163
- "tier": 3,
164
- "modifies_code": False,
165
- "requires_branch": False,
190
+ "lead_agent": cmd["lead_agent"],
191
+ "keywords": keywords,
192
+ "examples": seed.get("examples", []),
193
+ "source": "builtin",
194
+ **command_metadata(command_text),
166
195
  })
167
196
 
168
- # Build department summary
169
197
  dept_counts: dict[str, int] = {}
170
198
  for cmd in all_commands:
171
- d = cmd["department"]
172
- dept_counts[d] = dept_counts.get(d, 0) + 1
199
+ dept_counts[cmd["department"]] = dept_counts.get(cmd["department"], 0) + 1
173
200
 
174
201
  registry = {
175
202
  "_meta": {
176
- "version": "2.0.0",
177
- "generated": datetime.now().isoformat(),
203
+ "version": "3.0.0",
204
+ "generated": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
178
205
  "total_commands": len(all_commands),
179
206
  "generator": "core/registry/generator.py",
180
- "departments": dept_counts,
207
+ "departments": dict(sorted(dept_counts.items())),
181
208
  },
182
209
  "commands": all_commands,
183
210
  }
184
211
 
185
212
  output_path.parent.mkdir(parents=True, exist_ok=True)
186
- with open(output_path, "w") as f:
213
+ with open(output_path, "w", encoding="utf-8") as f:
187
214
  json.dump(registry, f, indent=2, ensure_ascii=False)
215
+ f.write("\n")
188
216
 
189
217
  return registry
190
218
 
@@ -193,7 +221,7 @@ if __name__ == "__main__":
193
221
  base = Path(__file__).parent.parent.parent
194
222
  reg = generate_commands_registry(
195
223
  base,
196
- base / "knowledge" / "commands-registry-v2.json",
224
+ base / "knowledge" / "commands-registry.json",
197
225
  )
198
- print(f"Generated: {reg['_meta']['total_commands']} commands")
226
+ print(f"Registry generated: {reg['_meta']['total_commands']} commands")
199
227
  print(f"Departments: {reg['_meta']['departments']}")
@@ -18,9 +18,9 @@ Create new projects from real git repositories with full automation: dependencie
18
18
  ## Commands
19
19
 
20
20
  <!-- Column convention: command | DESCRIPTION | repo. The registry
21
- generator (bin/arka-registry-gen) reads column 2 as the user-facing
22
- description — a repo URL there ships garbled command help (QG
23
- blocker, 2026-07-09). -->
21
+ generator (core/registry/generator.py) reads column 2 as the
22
+ user-facing description — a repo URL there ships garbled command
23
+ help (QG blocker, 2026-07-09). -->
24
24
  | Command | Description | Git Repository |
25
25
  |---------|-------------|----------------|
26
26
  | `/dev scaffold laravel <name>` | Scaffold a Laravel app into ~/Herd from the starter repo | `https://${GIT_HOST}/laravel/laravel.git` (override with `ARKAOS_LARAVEL_STARTER_REPO` env) |