arkaos 4.3.3 → 4.3.5

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.3.3
1
+ 4.3.5
@@ -28,40 +28,43 @@ ECOSYSTEM=""
28
28
  ECOSYSTEM_NAME=""
29
29
 
30
30
  if [ -f "$ECOSYSTEMS_FILE" ] && command -v "$ARKA_PY" >/dev/null 2>&1; then
31
- eval "$("$ARKA_PY" -c "
31
+ # OWASP A03: NEW_CWD is untrusted harness input. It is passed through the
32
+ # environment (never interpolated into the Python source), and the result
33
+ # is emitted as JSON read by jq — no `eval`, so a crafted cwd can neither
34
+ # break the source string nor inject a shell command.
35
+ _ECO_JSON=$(ARKA_CWD="$NEW_CWD" ARKA_ECO_FILE="$ECOSYSTEMS_FILE" "$ARKA_PY" -c '
32
36
  import json, os, sys
33
37
 
34
- cwd = '$NEW_CWD'
35
- eco_file = os.path.expanduser('$ECOSYSTEMS_FILE')
38
+ cwd = os.environ["ARKA_CWD"]
39
+ eco_file = os.path.expanduser(os.environ["ARKA_ECO_FILE"])
40
+
41
+
42
+ def emit(eco_id, name):
43
+ print(json.dumps({"ecosystem": eco_id, "name": name}))
44
+ sys.exit(0)
45
+
36
46
 
37
47
  try:
38
48
  data = json.load(open(eco_file))
39
- ecosystems = data.get('ecosystems', {})
49
+ ecosystems = data.get("ecosystems", {})
40
50
 
41
51
  for eco_id, eco in ecosystems.items():
42
- projects = eco.get('projects', [])
43
- for proj in projects:
44
- # Check if cwd contains the project name
52
+ for proj in eco.get("projects", []):
45
53
  if proj in cwd:
46
- print(f'ECOSYSTEM=\"{eco_id}\"')
47
- print(f'ECOSYSTEM_NAME=\"{eco.get(\"name\", eco_id)}\"')
48
- sys.exit(0)
54
+ emit(eco_id, eco.get("name", eco_id))
49
55
 
50
- # No match by project name, try by path patterns
51
- if '/herd/' in cwd or '/Herd/' in cwd:
52
- dir_name = os.path.basename(cwd.rstrip('/'))
56
+ if "/herd/" in cwd or "/Herd/" in cwd:
57
+ dir_name = os.path.basename(cwd.rstrip("/"))
53
58
  for eco_id, eco in ecosystems.items():
54
- for proj in eco.get('projects', []):
55
- if proj == dir_name:
56
- print(f'ECOSYSTEM=\"{eco_id}\"')
57
- print(f'ECOSYSTEM_NAME=\"{eco.get(\"name\", eco_id)}\"')
58
- sys.exit(0)
59
+ if dir_name in eco.get("projects", []):
60
+ emit(eco_id, eco.get("name", eco_id))
59
61
  except Exception:
60
62
  pass
61
63
 
62
- print('ECOSYSTEM=\"\"')
63
- print('ECOSYSTEM_NAME=\"\"')
64
- " 2>/dev/null)"
64
+ print(json.dumps({"ecosystem": "", "name": ""}))
65
+ ' 2>/dev/null)
66
+ ECOSYSTEM=$(echo "$_ECO_JSON" | jq -r '.ecosystem // ""' 2>/dev/null)
67
+ ECOSYSTEM_NAME=$(echo "$_ECO_JSON" | jq -r '.name // ""' 2>/dev/null)
65
68
  fi
66
69
 
67
70
  # ─── Detect stack ──────────────────────────────────────────────────────
@@ -113,5 +116,7 @@ if [ -n "$DESCRIPTOR" ]; then
113
116
  fi
114
117
 
115
118
  if [ -n "$CONTEXT" ]; then
116
- echo "{\"additionalContext\": \"${CONTEXT}\"}"
119
+ # Build the JSON with jq so any quote/backslash in the ecosystem name or
120
+ # descriptor path is escaped, never breaking the envelope (OWASP A03).
121
+ jq -nc --arg ctx "$CONTEXT" '{additionalContext: $ctx}'
117
122
  fi
@@ -184,8 +184,11 @@ except Exception:
184
184
  fi
185
185
 
186
186
  # ─── Output as systemMessage (same protocol as claude-mem) ─────────────
187
- "$ARKA_PY" -c "
188
- import json
189
- msg = '''$(echo -e "$MSG")'''
190
- print(json.dumps({'systemMessage': msg}))
187
+ # OWASP A03: MSG carries profile-derived NAME/COMPANY. Pass it through the
188
+ # environment (with \n already expanded) rather than interpolating it into
189
+ # the Python source, so a name containing quotes or ''' cannot break the
190
+ # script or the JSON envelope.
191
+ ARKA_MSG="$(echo -e "$MSG")" "$ARKA_PY" -c "
192
+ import json, os
193
+ print(json.dumps({'systemMessage': os.environ['ARKA_MSG']}))
191
194
  "
@@ -2,9 +2,10 @@ name: forge-integration
2
2
  added_in: "2.14.0"
3
3
  mandatory: true
4
4
  section_title: "Forge Integration"
5
- detection_pattern: "arka-forge"
5
+ detection_pattern: "arka:feature:forge-integration|## Forge Integration|arka-forge"
6
6
  deprecated_in: null
7
7
  content: |
8
+ <!-- arka:feature:forge-integration:start -->
8
9
  ## Forge Integration
9
10
 
10
11
  Complex requests (complexity score >= 5) are automatically routed to
@@ -14,3 +15,4 @@ content: |
14
15
  - Complexity assessment: automatic via Synapse L8 (ForgeContextLayer)
15
16
  - Manual invocation: `/forge` command
16
17
  - Handoff: Forge outputs structured plan → squad executes phases
18
+ <!-- arka:feature:forge-integration:end -->
@@ -2,9 +2,10 @@ name: quality-gate
2
2
  added_in: "2.10.0"
3
3
  mandatory: true
4
4
  section_title: "Quality Gate"
5
- detection_pattern: "Marta.*CQO|Quality Gate"
5
+ detection_pattern: "arka:feature:quality-gate|## Quality Gate"
6
6
  deprecated_in: null
7
7
  content: |
8
+ <!-- arka:feature:quality-gate:start -->
8
9
  ## Quality Gate
9
10
 
10
11
  Mandatory on every workflow. Nothing ships without approval.
@@ -13,3 +14,4 @@ content: |
13
14
  - **Eduardo (Copy Director):** Reviews all text output
14
15
  - **Francisca (Tech Director):** Reviews all code and technical output
15
16
  - Verdict: APPROVED or REJECTED (binary, no partial)
17
+ <!-- arka:feature:quality-gate:end -->
@@ -2,9 +2,10 @@ name: spec-driven-gate
2
2
  added_in: "2.13.0"
3
3
  mandatory: true
4
4
  section_title: "Spec-Driven Development"
5
- detection_pattern: "arka-spec"
5
+ detection_pattern: "arka:feature:spec-driven-gate|## Spec-Driven Development|arka-spec"
6
6
  deprecated_in: null
7
7
  content: |
8
+ <!-- arka:feature:spec-driven-gate:start -->
8
9
  ## Spec-Driven Development
9
10
 
10
11
  Phase 0 of all workflows. No implementation begins without a validated spec.
@@ -13,3 +14,4 @@ content: |
13
14
  - Gate: spec must be approved before planning phase starts
14
15
  - Storage: `docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md`
15
16
  - Review: user approval required on written spec
17
+ <!-- arka:feature:spec-driven-gate:end -->
@@ -2,9 +2,10 @@ name: workflow-tiers
2
2
  added_in: "2.12.0"
3
3
  mandatory: true
4
4
  section_title: "Workflow Tiers"
5
- detection_pattern: "Enterprise.*phase|Focused.*phase|Specialist.*phase"
5
+ detection_pattern: "arka:feature:workflow-tiers|## Workflow Tiers"
6
6
  deprecated_in: null
7
7
  content: |
8
+ <!-- arka:feature:workflow-tiers:start -->
8
9
  ## Workflow Tiers
9
10
 
10
11
  Three workflow tiers based on task complexity:
@@ -17,3 +18,4 @@ content: |
17
18
 
18
19
  Tier selection is automatic based on complexity assessment.
19
20
  Quality Gate phase is mandatory on ALL tiers.
21
+ <!-- arka:feature:workflow-tiers:end -->
@@ -74,9 +74,11 @@ Returns JSON report for downstream consumption.
74
74
  ## Feature Registry
75
75
 
76
76
  YAML files under `core/sync/features/*.yaml` (or `~/.arkaos/config/sync/features/*.yaml`). Each feature has:
77
- - `detection_pattern` — regex/string searched in ecosystem SKILL.md to decide if the feature is already present
78
- - `content` — the section to inject if missing
79
- - `deprecated_in` — if set, the matching section is removed
77
+ - `detection_pattern` — regex searched in ecosystem SKILL.md to decide if the feature is already present. Matches any of: the `arka:feature:<name>` marker, the bare `## <section_title>` heading (legacy/customized sections), or — only where a token is unique enough to never appear in unrelated prose (e.g. `arka-forge`) — a historical keyword
78
+ - `content` — the section to inject if missing, wrapped in `<!-- arka:feature:<name>:start -->` / `<!-- arka:feature:<name>:end -->` markers so future runs detect it and deprecation can remove it precisely
79
+ - `deprecated_in` — if set, the matching section is removed (marker pair preferred; fall back to the `## <section_title>` heading block)
80
+
81
+ The registry is self-detecting by contract: `detection_pattern` MUST match the feature's own `content` (locked by `tests/python/test_sync_features_registry.py`), otherwise every naive sync re-injects a duplicate section.
80
82
 
81
83
  ## Key Paths
82
84
 
@@ -47,10 +47,10 @@ After the Python engine completes, dispatch ONE subagent.
47
47
  **Subagent task — for each `~/.claude/skills/arka-{ecosystem}/SKILL.md`:**
48
48
  1. Read the SKILL.md.
49
49
  2. For each feature YAML where `deprecated_in` is null:
50
- - Search SKILL.md for `detection_pattern`.
51
- - If NOT found: inject `content` after the last existing feature section, or after the "Commands" table if no feature sections exist (before "Orchestration Workflows").
50
+ - Apply the `detection_pattern` regex to the SKILL.md text. It matches the `arka:feature:<name>` marker, the bare `## <section_title>` heading, or a unique historical keyword — a customized section without markers still counts as present and MUST NOT be duplicated.
51
+ - If NOT found: inject `content` (already marker-wrapped) after the last existing feature section, or after the "Commands" table if no feature sections exist (before "Orchestration Workflows").
52
52
  3. For each feature where `deprecated_in` is set:
53
- - Locate and remove the matching section.
53
+ - Remove the `<!-- arka:feature:<name>:start -->` … `:end -->` block when markers exist; otherwise remove the `## <section_title>` section.
54
54
  4. PRESERVE all custom content: commands, architecture, tech stack, business descriptions, ecosystem-specific workflow details.
55
55
 
56
56
  ### Report
@@ -0,0 +1,25 @@
1
+ import { existsSync, cpSync, chmodSync, readdirSync, mkdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ // Deploys config/hooks/_lib/ (shared hook libraries — the Python
5
+ // interpreter resolver lives here) into an install's hooks directory.
6
+ // Single implementation shared by the fresh-install path
7
+ // (index.js::installHooks) and the update path (update.js): the v4.3.2
8
+ // regression existed precisely because the two deploy loops drifted.
9
+ // Returns true when the lib dir was copied, false when the source has
10
+ // no _lib/ to deploy.
11
+ export function copyHookLib(srcHooksDir, destHooksDir) {
12
+ const srcLibDir = join(srcHooksDir, "_lib");
13
+ if (!existsSync(srcLibDir)) return false;
14
+ const destLibDir = join(destHooksDir, "_lib");
15
+ mkdirSync(destLibDir, { recursive: true });
16
+ cpSync(srcLibDir, destLibDir, { recursive: true });
17
+ // chmod is a no-op on Windows (NTFS ACLs aren't POSIX); the try/catch
18
+ // keeps the copy result authoritative even if it throws.
19
+ try {
20
+ for (const f of readdirSync(destLibDir)) {
21
+ if (f.endsWith(".sh")) chmodSync(join(destLibDir, f), 0o755);
22
+ }
23
+ } catch {}
24
+ return true;
25
+ }
@@ -6,6 +6,7 @@ import { fileURLToPath } from "node:url";
6
6
  import { getRuntimeConfig } from "./detect-runtime.js";
7
7
  import { findSystemPython, ensureVenv, getArkaosPython, getArkaosPip, pipInstall } from "./python-resolver.js";
8
8
  import { IS_WINDOWS, HOOK_EXT } from "./platform.js";
9
+ import { copyHookLib } from "./hook-lib.js";
9
10
 
10
11
  const __filename = fileURLToPath(import.meta.url);
11
12
  const __dirname = dirname(__filename);
@@ -665,16 +666,7 @@ function installHooks(installDir) {
665
666
  }
666
667
  }
667
668
 
668
- const srcLibDir = join(srcHooksDir, "_lib");
669
- if (existsSync(srcLibDir)) {
670
- const destLibDir = join(hooksDir, "_lib");
671
- ensureDir(destLibDir);
672
- cpSync(srcLibDir, destLibDir, { recursive: true });
673
- try {
674
- for (const f of readdirSync(destLibDir)) {
675
- if (f.endsWith(".sh")) chmodSync(join(destLibDir, f), 0o755);
676
- }
677
- } catch {}
669
+ if (copyHookLib(srcHooksDir, hooksDir)) {
678
670
  ok("Hook lib: _lib/");
679
671
  }
680
672
  }
@@ -3,6 +3,7 @@ import { join, dirname, resolve } from "node:path";
3
3
  import { homedir } from "node:os";
4
4
  import { execSync } from "node:child_process";
5
5
  import { ensureVenv, ensureVenvHealthy, getArkaosPython, pipInstall } from "./python-resolver.js";
6
+ import { copyHookLib } from "./hook-lib.js";
6
7
  import { getRuntimeConfig } from "./detect-runtime.js";
7
8
  import { loadAdapter } from "./index.js";
8
9
  import { migrateUserData, printMigrationReport } from "./migrate-user-data.js";
@@ -218,20 +219,10 @@ export async function update() {
218
219
  console.log(" ✓ Hook scripts updated");
219
220
 
220
221
  // Shared hook libraries (config/hooks/_lib/ — the Python interpreter
221
- // resolver lives here). Keep in lockstep with index.js::installHooks:
222
- // the hooks deployed above source _lib/arka_python.sh from the install
223
- // dir, so skipping this copy leaves them falling back to a bare
224
- // `python3` without ArkaOS deps.
225
- const srcLibDir = join(srcHooksDir, "_lib");
226
- if (existsSync(srcLibDir)) {
227
- const destLibDir = join(destHooksDir, "_lib");
228
- mkdirSync(destLibDir, { recursive: true });
229
- cpSync(srcLibDir, destLibDir, { recursive: true });
230
- try {
231
- for (const f of readdirSync(destLibDir)) {
232
- if (f.endsWith(".sh")) chmodSync(join(destLibDir, f), 0o755);
233
- }
234
- } catch {}
222
+ // resolver lives here). The hooks deployed above source
223
+ // _lib/arka_python.sh from the install dir, so skipping this copy
224
+ // leaves them falling back to a bare `python3` without ArkaOS deps.
225
+ if (copyHookLib(srcHooksDir, destHooksDir)) {
235
226
  console.log(" ✓ Hook lib updated (_lib/)");
236
227
  }
237
228
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arkaos",
3
- "version": "4.3.3",
3
+ "version": "4.3.5",
4
4
  "description": "The Operating System for AI Agent Teams",
5
5
  "type": "module",
6
6
  "bin": {
@@ -45,7 +45,18 @@
45
45
  "departments/",
46
46
  "scripts/",
47
47
  "config/",
48
- "bin/",
48
+ "bin/arka",
49
+ "bin/arka-claude",
50
+ "bin/arka-claude.cmd",
51
+ "bin/arka-claude.ps1",
52
+ "bin/arka-doctor",
53
+ "bin/arka-providers",
54
+ "bin/arka-py",
55
+ "bin/arka-py.cmd",
56
+ "bin/arka-py.ps1",
57
+ "bin/arka-skill",
58
+ "bin/arkaos",
59
+ "bin/scheduler-daemon.py",
49
60
  "arka/",
50
61
  "knowledge/",
51
62
  "dashboard/app/",
package/pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "arkaos-core"
3
- version = "4.3.3"
3
+ version = "4.3.5"
4
4
  description = "Core engine for ArkaOS — The Operating System for AI Agent Teams"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
@@ -1,185 +0,0 @@
1
- #!/usr/bin/env bash
2
- # ============================================================================
3
- # ARKA OS — Commands Registry Generator
4
- # Scans SKILL.md files + keyword data to generate commands-registry.json
5
- # Usage: bash bin/arka-registry-gen
6
- # ============================================================================
7
- set -e
8
-
9
- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
10
- REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
11
- KEYWORDS_FILE="$REPO_DIR/knowledge/commands-keywords.json"
12
- OUTPUT_FILE="$REPO_DIR/knowledge/commands-registry.json"
13
- ARKA_OS="${ARKA_OS:-$HOME/.claude/skills/arka}"
14
- TEMP_COMMANDS=$(mktemp)
15
- trap 'rm -f "$TEMP_COMMANDS"' EXIT
16
-
17
- # Require jq
18
- command -v jq &>/dev/null || { echo "Error: jq is required. Install: brew install jq"; exit 1; }
19
-
20
- # ─── Helper: Map department directory name to command prefix ───────────────
21
- dept_prefix_for() {
22
- case "$1" in
23
- dev) echo "dev" ;;
24
- marketing) echo "mkt" ;;
25
- ecommerce) echo "ecom" ;;
26
- finance) echo "fin" ;;
27
- operations) echo "ops" ;;
28
- strategy) echo "strat" ;;
29
- knowledge) echo "kb" ;;
30
- brand) echo "brand" ;;
31
- *) echo "$1" ;;
32
- esac
33
- }
34
-
35
- # ─── Helper: Detect metadata for a command ─────────────────────────────────
36
- get_metadata() {
37
- local command="$1"
38
- local rw="false" mc="false" tier=2
39
-
40
- case "$command" in
41
- "/dev feature"*|"/dev api"*|"/dev debug"*|"/dev refactor"*|"/dev db"*)
42
- rw="true"; mc="true"; tier=1 ;;
43
- "/dev scaffold"*|"/dev deploy"*|"/dev test"*)
44
- mc="true" ;;
45
- "/dev security-audit"*)
46
- tier=1 ;;
47
- esac
48
- echo "$rw $mc $tier"
49
- }
50
-
51
- # ─── Helper: Extract commands from a SKILL.md file ────────────────────────
52
- # Parses markdown tables: | `/<prefix> <command> [args]` | description |
53
- extract_commands() {
54
- local file="$1"
55
- local department="$2"
56
- local source="${3:-builtin}"
57
-
58
- [ -f "$file" ] || return 0
59
-
60
- # Extract lead agent from frontmatter
61
- local lead_agent=""
62
- lead_agent=$(sed -n '/^---$/,/^---$/{ /^name:/s/.*: *//p; }' "$file" 2>/dev/null | head -1 | xargs 2>/dev/null || echo "")
63
-
64
- # Parse command table rows
65
- # Match: | `/<word> <word>...` | description |
66
- # The backtick-wrapped command must START with / immediately after the backtick
67
- grep -E '^\| *`/[a-z]' "$file" 2>/dev/null | while IFS= read -r line; do
68
- # Extract command from backticks
69
- local command
70
- command=$(echo "$line" | sed -n 's/.*`\(\/[^`]*\)`.*/\1/p' | xargs 2>/dev/null)
71
- [ -z "$command" ] && continue
72
-
73
- # Extract description (second column)
74
- local description
75
- description=$(echo "$line" | sed 's/^[^|]*|[^|]*| *//' | sed 's/ *|.*//' | sed 's/^ *//;s/ *$//' | sed 's/^— *//;s/^- *//')
76
- [ -z "$description" ] && continue
77
- # Skip if description looks like a separator
78
- echo "$description" | grep -qE '^[-]+$' && continue
79
-
80
- # Generate command ID: /dev feature <desc> -> dev-feature
81
- local id
82
- id=$(echo "$command" | sed 's/ *<[^>]*>//g' | sed 's/ *\[.*//g' | sed 's/^\/\([^ ]*\) *\(.*\)/\1-\2/' | sed 's/-$//' | sed 's/ /-/g')
83
- [ -z "$id" ] && continue
84
-
85
- # Get metadata
86
- local meta
87
- meta=$(get_metadata "$command")
88
- local rw mc tier
89
- rw=$(echo "$meta" | cut -d' ' -f1)
90
- mc=$(echo "$meta" | cut -d' ' -f2)
91
- tier=$(echo "$meta" | cut -d' ' -f3)
92
-
93
- # Output JSON line
94
- jq -nc \
95
- --arg id "$id" \
96
- --arg command "$command" \
97
- --arg department "$department" \
98
- --arg description "$description" \
99
- --arg lead_agent "$lead_agent" \
100
- --argjson tier "$tier" \
101
- --argjson requires_branch "$rw" \
102
- --argjson modifies_code "$mc" \
103
- --arg source "$source" \
104
- '{id:$id,command:$command,department:$department,description:$description,lead_agent:$lead_agent,tier:$tier,requires_branch:$requires_branch,modifies_code:$modifies_code,keywords:[],examples:[],source:$source}'
105
- done
106
- }
107
-
108
- # ─── Collect commands from all SKILL.md files ─────────────────────────────
109
-
110
- # Main orchestrator
111
- extract_commands "$REPO_DIR/arka/SKILL.md" "arka" >> "$TEMP_COMMANDS"
112
-
113
- # Department SKILL.md files
114
- for dept_dir in "$REPO_DIR"/departments/*/; do
115
- [ -d "$dept_dir" ] || continue
116
- dept_name=$(basename "$dept_dir")
117
- dept_prefix=$(dept_prefix_for "$dept_name")
118
-
119
- # Main department skill
120
- [ -f "${dept_dir}SKILL.md" ] && extract_commands "${dept_dir}SKILL.md" "$dept_prefix" >> "$TEMP_COMMANDS"
121
-
122
- # Sub-skills
123
- for sub_skill in "${dept_dir}skills"/*/SKILL.md; do
124
- [ -f "$sub_skill" ] || continue
125
- extract_commands "$sub_skill" "$dept_prefix" >> "$TEMP_COMMANDS"
126
- done
127
- done
128
-
129
- # External skills
130
- for ext_skill in "$HOME/.claude/skills"/arka-ext-*/SKILL.md; do
131
- [ -f "$ext_skill" ] || continue
132
- ext_name=$(basename "$(dirname "$ext_skill")" | sed 's/^arka-ext-//')
133
- extract_commands "$ext_skill" "$ext_name" "external" >> "$TEMP_COMMANDS"
134
- done
135
-
136
- # Pro skills
137
- for pro_skill in "$HOME/.claude/skills"/arka-pro-*/SKILL.md; do
138
- [ -f "$pro_skill" ] || continue
139
- pro_name=$(basename "$(dirname "$pro_skill")" | sed 's/^arka-pro-//')
140
- extract_commands "$pro_skill" "$pro_name" "pro" >> "$TEMP_COMMANDS"
141
- done
142
-
143
- # ─── Build commands array from temp file ───────────────────────────────────
144
- if [ -s "$TEMP_COMMANDS" ]; then
145
- COMMANDS_JSON=$(jq -s '.' "$TEMP_COMMANDS")
146
- else
147
- COMMANDS_JSON="[]"
148
- fi
149
-
150
- # ─── Deduplicate by command ID (keep first occurrence) ─────────────────────
151
- COMMANDS_JSON=$(echo "$COMMANDS_JSON" | jq '[group_by(.id) | .[] | .[0]]')
152
-
153
- # ─── Merge keywords and examples from seed data ───────────────────────────
154
- if [ -f "$KEYWORDS_FILE" ]; then
155
- COMMANDS_JSON=$(jq --slurpfile kw "$KEYWORDS_FILE" '
156
- [.[] | . as $cmd |
157
- if $kw[0][$cmd.id] then
158
- .keywords = ($kw[0][$cmd.id].keywords // []) |
159
- .examples = ($kw[0][$cmd.id].examples // [])
160
- else . end
161
- ]
162
- ' <<< "$COMMANDS_JSON")
163
- fi
164
-
165
- # ─── Build final registry ─────────────────────────────────────────────────
166
- TOTAL=$(echo "$COMMANDS_JSON" | jq 'length')
167
- NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ)
168
-
169
- jq -n \
170
- --arg version "1.0.0" \
171
- --arg generated "$NOW" \
172
- --argjson total "$TOTAL" \
173
- --arg generator "bin/arka-registry-gen" \
174
- --argjson commands "$COMMANDS_JSON" \
175
- '{
176
- _meta: {
177
- version: $version,
178
- generated: $generated,
179
- total_commands: $total,
180
- generator: $generator
181
- },
182
- commands: $commands
183
- }' > "$OUTPUT_FILE"
184
-
185
- echo "Registry generated: $OUTPUT_FILE ($TOTAL commands)"