arkaos 5.17.0 → 5.17.1
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/README.md +2 -2
- package/THE-ARKAOS-GUIDE.md +2 -2
- package/VERSION +1 -1
- package/arka/SKILL.md +1 -1
- package/bin/arka-doctor +14 -8
- package/core/hooks/session_start.py +0 -26
- package/core/jobs/auto_doc_worker.py +34 -127
- package/core/synapse/layers.py +92 -2
- package/departments/content/SKILL.md +1 -0
- package/departments/content/skills/hyperframes/SKILL.md +95 -0
- package/departments/content/skills/video-produce/SKILL.md +27 -17
- package/departments/content/skills/video-setup/SKILL.md +26 -13
- package/harness/codex/AGENTS.md +2 -2
- package/harness/copilot/copilot-instructions.md +2 -2
- package/harness/cursor/rules/arkaos.mdc +3 -3
- package/harness/gemini/GEMINI.md +2 -2
- package/harness/opencode/AGENTS.md +2 -2
- package/harness/opencode/agents/arka-architect-gabriel.md +1 -1
- package/harness/opencode/agents/arka-brand-director-valentina.md +1 -1
- package/harness/opencode/agents/arka-cfo-helena.md +1 -1
- package/harness/opencode/agents/arka-chief-of-staff-afonso.md +1 -1
- package/harness/opencode/agents/arka-community-strategist-beatriz.md +1 -1
- package/harness/opencode/agents/arka-content-strategist-rafael.md +1 -1
- package/harness/opencode/agents/arka-conversion-strategist-ines.md +1 -1
- package/harness/opencode/agents/arka-coo-sofia.md +1 -1
- package/harness/opencode/agents/arka-copy-director-eduardo.md +1 -1
- package/harness/opencode/agents/arka-cqo-marta.md +1 -1
- package/harness/opencode/agents/arka-cto-marco.md +1 -1
- package/harness/opencode/agents/arka-design-ops-lead-iris.md +1 -1
- package/harness/opencode/agents/arka-ecom-director-ricardo.md +1 -1
- package/harness/opencode/agents/arka-knowledge-director-clara.md +1 -1
- package/harness/opencode/agents/arka-leadership-director-rodrigo.md +1 -1
- package/harness/opencode/agents/arka-marketing-director-luna.md +1 -1
- package/harness/opencode/agents/arka-ops-lead-daniel.md +1 -1
- package/harness/opencode/agents/arka-pm-director-carolina.md +1 -1
- package/harness/opencode/agents/arka-revops-lead-vicente.md +1 -1
- package/harness/opencode/agents/arka-saas-strategist-tiago.md +1 -1
- package/harness/opencode/agents/arka-sales-director-miguel.md +1 -1
- package/harness/opencode/agents/arka-strategy-director-tomas.md +1 -1
- package/harness/opencode/agents/arka-tech-director-francisca.md +1 -1
- package/harness/opencode/agents/arka-tech-lead-paulo.md +1 -1
- package/harness/opencode/agents/arka-video-producer-simao.md +1 -1
- package/harness/zed/.rules +2 -2
- package/installer/doctor.js +18 -6
- package/knowledge/commands-keywords.json +29 -2
- package/knowledge/commands-registry.json +41 -5
- package/knowledge/skills-manifest.json +14 -1
- package/package.json +1 -1
- package/pyproject.toml +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**The Operating System for AI Agent Teams.**
|
|
6
6
|
|
|
7
|
-
89 agents. 17 departments.
|
|
7
|
+
89 agents. 17 departments. 341 skills. Enterprise frameworks. Multi-runtime. One install.
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
npx arkaos install
|
|
@@ -97,7 +97,7 @@ npx arkaos doctor # Health check
|
|
|
97
97
|
|
|
98
98
|
### Skill packs, à la carte
|
|
99
99
|
|
|
100
|
-
The default install ships a curated core so your context window stays lean. Everything else lives in the ArkaOS plugin marketplace: 16 department packs with
|
|
100
|
+
The default install ships a curated core so your context window stays lean. Everything else lives in the ArkaOS plugin marketplace: 16 department packs with 267 skills, generated straight from the same sources the core uses. Inside Claude Code:
|
|
101
101
|
|
|
102
102
|
```
|
|
103
103
|
/plugin marketplace add andreagroferreira/arka-os
|
package/THE-ARKAOS-GUIDE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# The ArkaOS Guide
|
|
2
2
|
|
|
3
|
-
> v5.
|
|
3
|
+
> v5.17.1 — 89 agents, 17 departments, 341 skills, 307 commands, 20 ADRs.
|
|
4
4
|
> One file, everything you need to start. Generated by `scripts/guide_gen.py` — never hand-edited.
|
|
5
5
|
|
|
6
6
|
## What it is
|
|
@@ -40,7 +40,7 @@ answers as a generic assistant.
|
|
|
40
40
|
| `/arka` | 22 |
|
|
41
41
|
| `/brand` | 14 |
|
|
42
42
|
| `/community` | 15 |
|
|
43
|
-
| `/content` |
|
|
43
|
+
| `/content` | 21 |
|
|
44
44
|
| `/dev` | 42 |
|
|
45
45
|
| `/do` | 1 |
|
|
46
46
|
| `/ecom` | 18 |
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
5.17.
|
|
1
|
+
5.17.1
|
package/arka/SKILL.md
CHANGED
|
@@ -63,7 +63,7 @@ complementary, not alternatives.
|
|
|
63
63
|
# ArkaOS — Main Orchestrator
|
|
64
64
|
|
|
65
65
|
> **The Operating System for AI Agent Teams**
|
|
66
|
-
> 89 agents. 17 departments.
|
|
66
|
+
> 89 agents. 17 departments. 341 skills. Multi-runtime. Dashboard. Knowledge RAG.
|
|
67
67
|
|
|
68
68
|
## ⛔ Evidence flow — 4 gates (NON-NEGOTIABLE)
|
|
69
69
|
|
package/bin/arka-doctor
CHANGED
|
@@ -370,17 +370,23 @@ run_check "plugins" "warn" "$PLUGINS_DESC" "$PLUGINS_OK" "Run: bash install.sh"
|
|
|
370
370
|
|
|
371
371
|
# ─── Check 17: Hyperframes Skills (video production) ──────────────────────
|
|
372
372
|
# Parity with installer/doctor.js `hyperframes-skills` (PR-C2): same name,
|
|
373
|
-
# same
|
|
373
|
+
# same sentinels, same two roots (~/.claude/skills and ~/.agents/skills),
|
|
374
|
+
# warn-only — video production is opt-in.
|
|
374
375
|
HF_OK=false
|
|
375
376
|
HF_DESC="Hyperframes skills AUSENTE (video-as-code editing for /content video)"
|
|
376
|
-
for
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
377
|
+
for hf_root in "$HOME/.claude/skills" "$HOME/.agents/skills"; do
|
|
378
|
+
for hf_name in hyperframes hyperframes-core; do
|
|
379
|
+
if [ -f "$hf_root/$hf_name/SKILL.md" ]; then
|
|
380
|
+
HF_OK=true
|
|
381
|
+
# Static, matching installer/doctor.js `description` exactly: run_check
|
|
382
|
+
# splices $desc unescaped into the --json payload, so no path or
|
|
383
|
+
# name interpolation may reach it.
|
|
384
|
+
HF_DESC="Hyperframes skills installed (video-as-code editing for /content video)"
|
|
385
|
+
break 2
|
|
386
|
+
fi
|
|
387
|
+
done
|
|
382
388
|
done
|
|
383
|
-
run_check "hyperframes-skills" "warn" "$HF_DESC" "$HF_OK" "Run /content video-setup in Claude Code"
|
|
389
|
+
run_check "hyperframes-skills" "warn" "$HF_DESC" "$HF_OK" "Run /content video-setup in Claude Code, or: npx hyperframes skills update (core set; npx hyperframes skills check reports staleness)"
|
|
384
390
|
|
|
385
391
|
# ─── Check 18: SQLite corrupt-store backups (self-heal F1-D1) ──────────────
|
|
386
392
|
# Parity with installer/doctor.js `sqlite-corrupt-backups`: same name,
|
|
@@ -318,31 +318,6 @@ def _trigger_reorganizer(repo: str, config: dict) -> None:
|
|
|
318
318
|
_spawn_detached([sys.executable, "-m", "core.cognition.reorganizer_cli"], repo)
|
|
319
319
|
|
|
320
320
|
|
|
321
|
-
def _trigger_auto_doc_drain(repo: str, config: dict) -> None:
|
|
322
|
-
"""Queue-aware AND config-gated: ``cognition.auto_doc_drain_on_session``
|
|
323
|
-
(default True). The Stop hook enqueues auto-doc jobs; sessions drain
|
|
324
|
-
them — the consumer follows the producer's cadence, so a machine with
|
|
325
|
-
no sessions never wakes a worker for nothing (knowledge-flow PR1)."""
|
|
326
|
-
cognition = config.get("cognition") or {}
|
|
327
|
-
if not cognition.get("auto_doc_drain_on_session", True):
|
|
328
|
-
return
|
|
329
|
-
if not repo:
|
|
330
|
-
return
|
|
331
|
-
try:
|
|
332
|
-
from core.jobs.auto_doc_worker import _queue_root
|
|
333
|
-
if not any((_queue_root() / "pending").glob("*.json")):
|
|
334
|
-
return
|
|
335
|
-
except Exception:
|
|
336
|
-
return
|
|
337
|
-
max_age = cognition.get("auto_doc_max_age_days", 14)
|
|
338
|
-
_spawn_detached(
|
|
339
|
-
[sys.executable, "-m", "core.jobs.auto_doc_worker", "--once",
|
|
340
|
-
"--max-jobs", "10", "--max-age-days", str(max_age)],
|
|
341
|
-
repo,
|
|
342
|
-
log_path=Path.home() / ".arkaos" / "logs" / "auto-doc-drain.log",
|
|
343
|
-
)
|
|
344
|
-
|
|
345
|
-
|
|
346
321
|
def _ensure_dashboard(repo: str, config: dict) -> None:
|
|
347
322
|
dashboard = config.get("dashboard") or {}
|
|
348
323
|
if not dashboard.get("ensure_on_session", True):
|
|
@@ -409,7 +384,6 @@ def build_visible(cwd: str) -> str:
|
|
|
409
384
|
msg += _forge_line()
|
|
410
385
|
msg += _drift(version)
|
|
411
386
|
_trigger_reorganizer(repo, config)
|
|
412
|
-
_trigger_auto_doc_drain(repo, config)
|
|
413
387
|
_ensure_dashboard(repo, config)
|
|
414
388
|
return msg
|
|
415
389
|
|
|
@@ -6,8 +6,6 @@ Queue layout under `~/.arkaos/jobs/auto-doc/`:
|
|
|
6
6
|
processing/ — jobs currently being handled
|
|
7
7
|
completed/ — jobs finished successfully
|
|
8
8
|
failed/ — jobs that exhausted retries or could not be parsed
|
|
9
|
-
expired/ — jobs older than --max-age-days, moved aside unprocessed
|
|
10
|
-
(never deleted: the operator can audit or re-queue them)
|
|
11
9
|
|
|
12
10
|
Each job is a JSON file:
|
|
13
11
|
|
|
@@ -28,31 +26,22 @@ second worker that tries to claim the same job will raise and skip it.
|
|
|
28
26
|
from __future__ import annotations
|
|
29
27
|
|
|
30
28
|
import argparse
|
|
31
|
-
import contextlib
|
|
32
29
|
import json
|
|
33
30
|
import os
|
|
34
31
|
import sys
|
|
35
32
|
import time
|
|
36
33
|
import uuid
|
|
37
|
-
from datetime import
|
|
34
|
+
from datetime import datetime, timezone
|
|
38
35
|
from pathlib import Path
|
|
36
|
+
from typing import Optional
|
|
39
37
|
|
|
40
|
-
from core.knowledge.vault import resolve_vault_path
|
|
41
38
|
from core.shared import safe_session_id as _safe_session_id_module
|
|
42
39
|
|
|
40
|
+
|
|
43
41
|
MAX_ATTEMPTS = 3
|
|
44
42
|
# Re-export for backward compatibility with any external importers.
|
|
45
43
|
SAFE_SESSION_ID_RE = _safe_session_id_module.SAFE_SESSION_ID_RE
|
|
46
|
-
_QUEUE_SUBDIRS = ("pending", "processing", "completed", "failed"
|
|
47
|
-
|
|
48
|
-
_VAULT_REMEDIATION = (
|
|
49
|
-
"No vault configured. Set `knowledge.vaultPath` in ~/.arkaos/config.json "
|
|
50
|
-
"(or export ARKAOS_VAULT). Jobs were left untouched in pending/."
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
class VaultNotConfiguredError(RuntimeError):
|
|
55
|
-
"""No vault answered; pending jobs must not be claimed or retried."""
|
|
44
|
+
_QUEUE_SUBDIRS = ("pending", "processing", "completed", "failed")
|
|
56
45
|
|
|
57
46
|
|
|
58
47
|
def _queue_root() -> Path:
|
|
@@ -62,17 +51,11 @@ def _queue_root() -> Path:
|
|
|
62
51
|
return Path.home() / ".arkaos" / "jobs" / "auto-doc"
|
|
63
52
|
|
|
64
53
|
|
|
65
|
-
def _vault_path() -> Path
|
|
66
|
-
""
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
``ARKAOS_VAULT`` env var, both required to exist on disk. This
|
|
71
|
-
replaces a hardcoded ``~/Documents/Personal`` fallback that silently
|
|
72
|
-
sent every note to one developer's personal layout (the exact defect
|
|
73
|
-
``core.knowledge.vault`` was written to end — the worker predates it).
|
|
74
|
-
"""
|
|
75
|
-
return resolve_vault_path()
|
|
54
|
+
def _vault_path() -> Path:
|
|
55
|
+
override = os.environ.get("ARKAOS_VAULT", "").strip()
|
|
56
|
+
if override:
|
|
57
|
+
return Path(override)
|
|
58
|
+
return Path.home() / "Documents" / "Personal"
|
|
76
59
|
|
|
77
60
|
|
|
78
61
|
def _ensure_queue(root: Path) -> None:
|
|
@@ -81,7 +64,7 @@ def _ensure_queue(root: Path) -> None:
|
|
|
81
64
|
|
|
82
65
|
|
|
83
66
|
def _now_iso() -> str:
|
|
84
|
-
return datetime.now(
|
|
67
|
+
return datetime.now(timezone.utc).isoformat()
|
|
85
68
|
|
|
86
69
|
|
|
87
70
|
# ─── Enqueue ───────────────────────────────────────────────────────────
|
|
@@ -91,7 +74,7 @@ def enqueue_job(
|
|
|
91
74
|
session_id: str,
|
|
92
75
|
transcript_path: str,
|
|
93
76
|
qg_verdict: str,
|
|
94
|
-
queue_root: Path
|
|
77
|
+
queue_root: Optional[Path] = None,
|
|
95
78
|
) -> str:
|
|
96
79
|
"""Write a pending job file. Returns the job id."""
|
|
97
80
|
root = queue_root or _queue_root()
|
|
@@ -118,71 +101,22 @@ def enqueue_job(
|
|
|
118
101
|
|
|
119
102
|
def process_pending_jobs(
|
|
120
103
|
max_jobs: int = 10,
|
|
121
|
-
queue_root: Path
|
|
122
|
-
max_age_days: float | None = None,
|
|
104
|
+
queue_root: Optional[Path] = None,
|
|
123
105
|
) -> list[dict]:
|
|
124
|
-
"""Process up to `max_jobs` pending jobs. Returns per-job result dicts.
|
|
125
|
-
|
|
126
|
-
Raises :class:`VaultNotConfiguredError` BEFORE claiming anything when
|
|
127
|
-
no vault is configured — a missing vault is an environment failure
|
|
128
|
-
and must never consume job attempts or move files out of pending/.
|
|
129
|
-
"""
|
|
106
|
+
"""Process up to `max_jobs` pending jobs. Returns per-job result dicts."""
|
|
130
107
|
root = queue_root or _queue_root()
|
|
131
108
|
_ensure_queue(root)
|
|
132
|
-
vault = _vault_path()
|
|
133
|
-
if vault is None:
|
|
134
|
-
raise VaultNotConfiguredError(_VAULT_REMEDIATION)
|
|
135
|
-
pending = sorted((root / "pending").glob("*.json"))
|
|
136
109
|
results: list[dict] = []
|
|
137
|
-
|
|
138
|
-
pending, expired = _partition_expired(pending, root, max_age_days)
|
|
139
|
-
results.extend(expired)
|
|
110
|
+
pending = sorted((root / "pending").glob("*.json"))
|
|
140
111
|
for job_file in pending[:max_jobs]:
|
|
141
112
|
claimed = _claim(job_file, root)
|
|
142
113
|
if claimed is None:
|
|
143
114
|
continue
|
|
144
|
-
results.append(run_single_job(claimed, root
|
|
115
|
+
results.append(run_single_job(claimed, root))
|
|
145
116
|
return results
|
|
146
117
|
|
|
147
118
|
|
|
148
|
-
def
|
|
149
|
-
prefix = job_file.name.split("-", 1)[0]
|
|
150
|
-
try:
|
|
151
|
-
return float(prefix)
|
|
152
|
-
except ValueError:
|
|
153
|
-
return None
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
def _partition_expired(
|
|
157
|
-
pending: list[Path], root: Path, max_age_days: float
|
|
158
|
-
) -> tuple[list[Path], list[dict]]:
|
|
159
|
-
"""Split by job age, read from the unix prefix of the job filename.
|
|
160
|
-
|
|
161
|
-
A name without a parsable prefix is kept for processing — unknown
|
|
162
|
-
age must never silently shelve a job.
|
|
163
|
-
"""
|
|
164
|
-
cutoff = time.time() - max_age_days * 86400.0
|
|
165
|
-
keep: list[Path] = []
|
|
166
|
-
expired: list[dict] = []
|
|
167
|
-
for job_file in pending:
|
|
168
|
-
stamp = _job_timestamp(job_file)
|
|
169
|
-
if stamp is not None and stamp < cutoff:
|
|
170
|
-
expired.append(_move_to_expired(job_file, root))
|
|
171
|
-
else:
|
|
172
|
-
keep.append(job_file)
|
|
173
|
-
return keep, expired
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
def _move_to_expired(job_file: Path, root: Path) -> dict:
|
|
177
|
-
target = root / "expired" / job_file.name
|
|
178
|
-
try:
|
|
179
|
-
os.replace(job_file, target)
|
|
180
|
-
except OSError:
|
|
181
|
-
return {"job_id": job_file.stem, "status": "expire-failed"}
|
|
182
|
-
return {"job_id": job_file.stem, "status": "expired"}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
def _claim(job_file: Path, root: Path) -> Path | None:
|
|
119
|
+
def _claim(job_file: Path, root: Path) -> Optional[Path]:
|
|
186
120
|
target = root / "processing" / job_file.name
|
|
187
121
|
try:
|
|
188
122
|
os.replace(job_file, target)
|
|
@@ -193,17 +127,10 @@ def _claim(job_file: Path, root: Path) -> Path | None:
|
|
|
193
127
|
return None
|
|
194
128
|
|
|
195
129
|
|
|
196
|
-
def run_single_job(
|
|
197
|
-
job_path: Path,
|
|
198
|
-
queue_root: Path | None = None,
|
|
199
|
-
vault: Path | None = None,
|
|
200
|
-
) -> dict:
|
|
130
|
+
def run_single_job(job_path: Path, queue_root: Optional[Path] = None) -> dict:
|
|
201
131
|
"""Process one job end-to-end. Moves to completed/ or failed/."""
|
|
202
132
|
root = queue_root or _queue_root()
|
|
203
133
|
_ensure_queue(root)
|
|
204
|
-
vault = vault or _vault_path()
|
|
205
|
-
if vault is None:
|
|
206
|
-
return _release_claim(job_path, root)
|
|
207
134
|
try:
|
|
208
135
|
payload = json.loads(job_path.read_text(encoding="utf-8"))
|
|
209
136
|
if not isinstance(payload, dict):
|
|
@@ -213,26 +140,18 @@ def run_single_job(
|
|
|
213
140
|
payload["attempts"] = int(payload.get("attempts") or 0) + 1
|
|
214
141
|
job_path.write_text(json.dumps(payload), encoding="utf-8")
|
|
215
142
|
try:
|
|
216
|
-
written = _execute(payload
|
|
143
|
+
written = _execute(payload)
|
|
217
144
|
except Exception as exc:
|
|
218
145
|
return _handle_retry(job_path, payload, root, str(exc))
|
|
219
146
|
return _move_to_completed(job_path, root, payload, written)
|
|
220
147
|
|
|
221
148
|
|
|
222
|
-
def
|
|
223
|
-
"""Vault vanished between pre-flight and execution: put the job back
|
|
224
|
-
WITHOUT touching attempts — environment failure, not job failure."""
|
|
225
|
-
target = root / "pending" / job_path.name
|
|
226
|
-
with contextlib.suppress(OSError):
|
|
227
|
-
os.replace(job_path, target)
|
|
228
|
-
return {"job_id": job_path.stem, "status": "vault-unconfigured"}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
def _execute(payload: dict, vault: Path) -> list[str]:
|
|
149
|
+
def _execute(payload: dict) -> list[str]:
|
|
232
150
|
from core.cognition.auto_documentor import document_session
|
|
233
151
|
transcript = Path(payload.get("transcript_path") or "")
|
|
234
152
|
session_id = str(payload.get("session_id") or "")
|
|
235
153
|
verdict = str(payload.get("qg_verdict") or "")
|
|
154
|
+
vault = _vault_path()
|
|
236
155
|
paths = document_session(transcript, session_id, vault, verdict)
|
|
237
156
|
return [str(p) for p in paths]
|
|
238
157
|
|
|
@@ -264,8 +183,10 @@ def _move_to_completed(
|
|
|
264
183
|
payload["status"] = "completed"
|
|
265
184
|
target = root / "completed" / job_path.name
|
|
266
185
|
job_path.write_text(json.dumps(payload), encoding="utf-8")
|
|
267
|
-
|
|
186
|
+
try:
|
|
268
187
|
os.replace(job_path, target)
|
|
188
|
+
except OSError:
|
|
189
|
+
pass
|
|
269
190
|
return {
|
|
270
191
|
"job_id": payload.get("job_id"),
|
|
271
192
|
"status": "completed",
|
|
@@ -279,13 +200,17 @@ def _move_to_failed(job_path: Path, root: Path, error: str) -> dict:
|
|
|
279
200
|
info["status"] = "failed"
|
|
280
201
|
info["error"] = error
|
|
281
202
|
info["failed_at"] = _now_iso()
|
|
282
|
-
|
|
203
|
+
try:
|
|
283
204
|
job_path.write_text(json.dumps(info), encoding="utf-8")
|
|
205
|
+
except OSError:
|
|
206
|
+
pass
|
|
284
207
|
try:
|
|
285
208
|
os.replace(job_path, target)
|
|
286
209
|
except OSError:
|
|
287
|
-
|
|
210
|
+
try:
|
|
288
211
|
job_path.unlink()
|
|
212
|
+
except OSError:
|
|
213
|
+
pass
|
|
289
214
|
return {
|
|
290
215
|
"job_id": info.get("job_id"),
|
|
291
216
|
"status": "failed",
|
|
@@ -314,38 +239,20 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
314
239
|
help="Cap on jobs per pass.")
|
|
315
240
|
parser.add_argument("--interval", type=float, default=5.0,
|
|
316
241
|
help="Seconds to sleep between passes when daemonised.")
|
|
317
|
-
parser.add_argument("--max-age-days", type=float, default=None,
|
|
318
|
-
help="Move pending jobs older than this to expired/ "
|
|
319
|
-
"instead of processing them.")
|
|
320
242
|
return parser
|
|
321
243
|
|
|
322
244
|
|
|
323
|
-
def
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
results = process_pending_jobs(
|
|
327
|
-
max_jobs=max_jobs, max_age_days=max_age_days
|
|
328
|
-
)
|
|
329
|
-
except VaultNotConfiguredError as exc:
|
|
330
|
-
print(str(exc), file=sys.stderr)
|
|
331
|
-
return 2
|
|
245
|
+
def main(argv: Optional[list[str]] = None) -> int:
|
|
246
|
+
args = _build_parser().parse_args(argv)
|
|
247
|
+
results = process_pending_jobs(max_jobs=args.max_jobs)
|
|
332
248
|
for r in results:
|
|
333
249
|
print(json.dumps(r), flush=True)
|
|
334
|
-
return None
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
def main(argv: list[str] | None = None) -> int:
|
|
338
|
-
args = _build_parser().parse_args(argv)
|
|
339
|
-
rc = _run_pass(args.max_jobs, args.max_age_days)
|
|
340
|
-
if rc is not None:
|
|
341
|
-
return rc
|
|
342
250
|
if args.once:
|
|
343
251
|
return 0
|
|
344
252
|
while True:
|
|
345
253
|
time.sleep(max(0.1, float(args.interval)))
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
return rc
|
|
254
|
+
for r in process_pending_jobs(max_jobs=args.max_jobs):
|
|
255
|
+
print(json.dumps(r), flush=True)
|
|
349
256
|
|
|
350
257
|
|
|
351
258
|
if __name__ == "__main__":
|
package/core/synapse/layers.py
CHANGED
|
@@ -21,6 +21,7 @@ layers_kb.py. Both are re-exported here so existing
|
|
|
21
21
|
`core.synapse.layers` imports keep working.
|
|
22
22
|
"""
|
|
23
23
|
|
|
24
|
+
import os
|
|
24
25
|
import re
|
|
25
26
|
import time
|
|
26
27
|
from typing import Any
|
|
@@ -412,6 +413,24 @@ def _hub_skill(department: str) -> str:
|
|
|
412
413
|
return _DEPT_HUB_EXCEPTIONS.get(department, f"arka-{department}")
|
|
413
414
|
|
|
414
415
|
|
|
416
|
+
# Project-shape signals: a cwd carrying ANY of these file groups (every
|
|
417
|
+
# file in a group present) forces the command's hint regardless of the
|
|
418
|
+
# prompt text — "working inside a Hyperframes project" is a route.
|
|
419
|
+
PROJECT_SIGNALS: dict[str, tuple[tuple[str, ...], ...]] = {
|
|
420
|
+
"content-hyperframes": (
|
|
421
|
+
("hyperframes.json",),
|
|
422
|
+
("BRIEF.md", "STORYBOARD.md"),
|
|
423
|
+
),
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
# The score slot exists only so signal entries share the
|
|
427
|
+
# (score, command, department) tuple shape of _score_commands. Nothing
|
|
428
|
+
# sorts on it in the merge path: _hint_tag and compute discard the score,
|
|
429
|
+
# and precedence comes from _merge_hints concatenating signal entries
|
|
430
|
+
# before the keyword ones.
|
|
431
|
+
_SIGNAL_SCORE = 10_000
|
|
432
|
+
|
|
433
|
+
|
|
415
434
|
def _hint_department(cmd: dict) -> str:
|
|
416
435
|
"""Department for the hint's hub skill.
|
|
417
436
|
|
|
@@ -426,8 +445,63 @@ def _hint_department(cmd: dict) -> str:
|
|
|
426
445
|
return prefix.split("-")[0]
|
|
427
446
|
|
|
428
447
|
|
|
448
|
+
def _group_present(cwd: str, group: tuple[str, ...]) -> bool:
|
|
449
|
+
"""True when EVERY file of the group sits directly in ``cwd``."""
|
|
450
|
+
return all(os.path.isfile(os.path.join(cwd, name)) for name in group)
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def _project_signal_ids(cwd: str) -> list[str]:
|
|
454
|
+
"""Registry command ids whose project shape matches the hook cwd.
|
|
455
|
+
|
|
456
|
+
Looks at ``cwd`` itself only — no parent walk — and never raises:
|
|
457
|
+
``PromptContext.cwd`` is typed ``str``, and for str input the
|
|
458
|
+
``os.path`` predicates (``isdir``/``isfile``) are exception-free —
|
|
459
|
+
they swallow OSError and ValueError internally — so an empty cwd, a
|
|
460
|
+
directory that does not exist, or an unreadable path yields no signal
|
|
461
|
+
and L5 degrades to plain keyword scoring. No try/except is needed.
|
|
462
|
+
"""
|
|
463
|
+
if not cwd or not os.path.isdir(cwd):
|
|
464
|
+
return []
|
|
465
|
+
return [
|
|
466
|
+
cmd_id
|
|
467
|
+
for cmd_id, groups in PROJECT_SIGNALS.items()
|
|
468
|
+
if any(_group_present(cwd, group) for group in groups)
|
|
469
|
+
]
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def _signal_commands(
|
|
473
|
+
commands: list[dict[str, Any]], ids: list[str]
|
|
474
|
+
) -> list[tuple[int, str, str]]:
|
|
475
|
+
"""Scored tuples for the signalled ids, shaped like _score_commands.
|
|
476
|
+
|
|
477
|
+
An id the registry does not carry is skipped in silence: a signal
|
|
478
|
+
may only surface a command that actually exists, never invent one.
|
|
479
|
+
"""
|
|
480
|
+
wanted = set(ids)
|
|
481
|
+
return [
|
|
482
|
+
(_SIGNAL_SCORE, cmd.get("command", ""), _hint_department(cmd))
|
|
483
|
+
for cmd in commands
|
|
484
|
+
if cmd.get("id") in wanted
|
|
485
|
+
]
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def _merge_hints(
|
|
489
|
+
signal: list[tuple[int, str, str]],
|
|
490
|
+
keyword: list[tuple[int, str, str]],
|
|
491
|
+
) -> list[tuple[int, str, str]]:
|
|
492
|
+
"""Signal hints first, keyword hints after, de-duped by command text."""
|
|
493
|
+
seen: set[str] = set()
|
|
494
|
+
merged: list[tuple[int, str, str]] = []
|
|
495
|
+
for entry in [*signal, *keyword]:
|
|
496
|
+
if entry[1] in seen:
|
|
497
|
+
continue
|
|
498
|
+
seen.add(entry[1])
|
|
499
|
+
merged.append(entry)
|
|
500
|
+
return merged
|
|
501
|
+
|
|
502
|
+
|
|
429
503
|
def _score_commands(
|
|
430
|
-
commands: list[dict], text: str
|
|
504
|
+
commands: list[dict[str, Any]], text: str
|
|
431
505
|
) -> list[tuple[int, str, str]]:
|
|
432
506
|
"""Keyword-score the registry commands against the prompt text,
|
|
433
507
|
best score first; zero-score commands never qualify."""
|
|
@@ -468,6 +542,17 @@ class CommandHintsLayer(Layer):
|
|
|
468
542
|
the route. The old `[hint:/cmd]` form was declarative and routinely
|
|
469
543
|
ignored: the model announced the squad in prose and the skill's
|
|
470
544
|
content never entered context.
|
|
545
|
+
|
|
546
|
+
Beyond the prompt words, the layer routes by PROJECT SHAPE: when the
|
|
547
|
+
hook cwd carries the files that identify a project kind
|
|
548
|
+
(``PROJECT_SIGNALS`` — e.g. a Hyperframes video-as-code project ships
|
|
549
|
+
``hyperframes.json``, or ``BRIEF.md`` + ``STORYBOARD.md``), that
|
|
550
|
+
command is hinted even when the prompt matches no keyword. An
|
|
551
|
+
operator standing inside such a project asks "render the intro" or
|
|
552
|
+
"muda o segundo plano" — vocabulary the registry cannot enumerate —
|
|
553
|
+
and keyword-only scoring left the specialist route silent while the
|
|
554
|
+
generic assistant answered. Signal hints lead the merged list and the
|
|
555
|
+
top-2 cap still holds, so a signal costs at most one keyword slot.
|
|
471
556
|
"""
|
|
472
557
|
|
|
473
558
|
def __init__(self, commands: list[dict] | None = None) -> None:
|
|
@@ -510,7 +595,12 @@ class CommandHintsLayer(Layer):
|
|
|
510
595
|
cached=False,
|
|
511
596
|
)
|
|
512
597
|
|
|
513
|
-
|
|
598
|
+
signal = _signal_commands(
|
|
599
|
+
self._commands, _project_signal_ids(ctx.cwd)
|
|
600
|
+
)
|
|
601
|
+
top = _merge_hints(
|
|
602
|
+
signal, _score_commands(self._commands, text)
|
|
603
|
+
)[:2]
|
|
514
604
|
hints = [command for _, command, _ in top]
|
|
515
605
|
|
|
516
606
|
tags = _hint_tag(top)
|
|
@@ -38,6 +38,7 @@ allowed-tools: [Read, Write, Edit, Bash, Grep, Glob, Agent, WebFetch, WebSearch]
|
|
|
38
38
|
| `/content calendar <period>` | Content calendar with themes and batching | Focused |
|
|
39
39
|
| `/content thumbnail <video>` | Thumbnail + title packaging (A/B variants) | Specialist |
|
|
40
40
|
| `/content video <topic>` | End-to-end video production (research → script → assets → render) | Enterprise |
|
|
41
|
+
| `/content hyperframes <task>` | Direct Hyperframes work: compose, animate, caption, port, render and review a video-as-code project | Specialist |
|
|
41
42
|
| `/content trends <niche>` | Demand-first trend and niche analysis with STEPPS scoring | Specialist |
|
|
42
43
|
| `/content research <topic>` | Sourced research compiled into a production brief | Focused |
|
|
43
44
|
| `/content video-setup` | Video production environment bootstrap (Hyperframes, Agent-Reach, Higgsfield) | Specialist |
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: content/hyperframes
|
|
3
|
+
description: >
|
|
4
|
+
Direct Hyperframes work — video-as-code in HTML/CSS/JS + GSAP rendered to a
|
|
5
|
+
deterministic MP4 — on a new or existing project: compose, animate, keyframe,
|
|
6
|
+
caption, mix audio, port from Remotion, import from Figma, render, preview,
|
|
7
|
+
publish, batch-render. Loads the /hyperframes router FIRST and wraps it in the
|
|
8
|
+
ArkaOS doctrine (Simão owns it, [arka:design] marker on compositions,
|
|
9
|
+
dev/watch review of the render, Quality Gate). TRIGGER: "/content
|
|
10
|
+
hyperframes", "renderiza o vídeo", "render this composition", "ajusta os
|
|
11
|
+
keyframes", "adiciona legendas embutidas", "motion graphic", "title card",
|
|
12
|
+
"lower third", "converte este projeto Remotion", "importa do Figma para
|
|
13
|
+
vídeo", "slideshow", "faceless explainer", "explainer sem rosto", any cwd
|
|
14
|
+
holding hyperframes.json or BRIEF.md + STORYBOARD.md (Synapse L5 project
|
|
15
|
+
signal — inside a running /content video pipeline the pipeline keeps
|
|
16
|
+
ownership and calls this skill at its edit/render phase). SKIP: brief-to-MP4
|
|
17
|
+
production with research, script and asset generation -> content/video-produce
|
|
18
|
+
(its edit/render phase loads this skill); one-off environment install
|
|
19
|
+
-> content/video-setup; watching or analysing a video -> dev/watch;
|
|
20
|
+
scroll-driven website from a video -> dev/animated-website.
|
|
21
|
+
allowed-tools: [Read, Write, Edit, Bash, Grep, Glob, Agent, Skill, WebFetch]
|
|
22
|
+
metadata:
|
|
23
|
+
origin: arkaos
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
<!-- arka:kb-first-prefix begin -->
|
|
27
|
+
> **KB-first:** query `mcp__obsidian__search_notes` (and
|
|
28
|
+
> `mcp__graphify__query_graph` when configured) and cite `[[wikilinks]]`
|
|
29
|
+
> or graph nodes — or declare the gap — BEFORE any external research.
|
|
30
|
+
> Full doctrine: `arka/SKILL.md` (KB-First Research).
|
|
31
|
+
<!-- arka:kb-first-prefix end -->
|
|
32
|
+
|
|
33
|
+
# Hyperframes — `/content hyperframes <task>`
|
|
34
|
+
|
|
35
|
+
> **Agent:** Simão (Video Producer) | **Engine:** Hyperframes by HeyGen (Apache-2.0) — core skills under `~/.claude/skills/`, creation workflows installed on demand by the router
|
|
36
|
+
> **Rule:** the `/hyperframes` router owns the workflow; this skill owns the ArkaOS ceremony around it. Never reconstruct Hyperframes from memory.
|
|
37
|
+
|
|
38
|
+
## Phase 0 — Preflight (one command, always)
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
npx hyperframes skills check
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
| Result | Action |
|
|
45
|
+
|---|---|
|
|
46
|
+
| `N current`, nothing outdated | proceed |
|
|
47
|
+
| `↑ outdated` listed | `npx hyperframes skills update` refreshes the core set and every skill already installed; it never installs a workflow that is absent. The router installs that one when it picks it, with `npx hyperframes skills update <workflow>` |
|
|
48
|
+
| `~/.claude/skills/hyperframes/SKILL.md` missing | STOP with exactly "Hyperframes não instalado — corre /content video-setup". Never improvise the format. |
|
|
49
|
+
| `node --version` below 22, or no `ffmpeg` | STOP and print the platform install line — `content/video-setup` owns installs, this skill never mutates system packages |
|
|
50
|
+
|
|
51
|
+
## Phase 1 — Load the router, then follow it
|
|
52
|
+
|
|
53
|
+
Invoke `Skill(hyperframes)` FIRST. It resumes project state (`BRIEF.md`, `hyperframes.json`, `STORYBOARD.md`), runs the intent interview for fresh creation, picks the owning workflow from its route table and installs it. Two ArkaOS constraints on top:
|
|
54
|
+
|
|
55
|
+
- A specific operation on an existing project (inspect, diagnose, validate, preview, render, publish, batch-render): the router loads `/hyperframes-cli` — do that operation and nothing else.
|
|
56
|
+
- Fresh creation: the interview ends by writing `BRIEF.md`. Read the marketing context first (KB-first: `WizardingCode/Marketing/product-marketing.md` in Obsidian, else the project-local `.agents/product-marketing.md`) so the brief carries the real positioning, never an invented one.
|
|
57
|
+
|
|
58
|
+
## Phase 2 — Compose under the design doctrine
|
|
59
|
+
|
|
60
|
+
A Hyperframes composition is `.html`, so the frontend excellence gate applies exactly as it does to UI (constitution `excellence-mandate`, `core/workflow/frontend_gate.py`). Before the first Write/Edit of a composition emit the structured marker on a line of its own:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
[arka:design] benchmark=<named reference: Linear launch films, Vercel Ship keynote, Stripe Sessions…> skills=hyperframes,hyperframes-core,hyperframes-animation,<hyperframes-creative|hyperframes-keyframes|hyperframes-audio as loaded> tokens=<brand tokens path|none>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
- ArkaOS-branded output follows The Assembly v1.1 (`docs/design/brand-assembly/`, skill `arkaos-design`): zinc + rose, Signal used sparingly, Space Grotesk / IBM Plex — `tokens=` points there.
|
|
67
|
+
- Client work: `tokens=` points at the project's design-system document (`project-design-system-prerequisite`): no document, no composition — extract it first with `/brand design-system`.
|
|
68
|
+
- Anti-default: no template title cards, no stock easing on every element, no "AI video" look. GSAP timelines with labels and position parameters (Simão's framework): load `dev/gsap` for GSAP itself (`references/timeline.md` for timelines, labels and position parameters; `references/core.md` for tweens and easing; `references/plugins.md` for kinetic type and SplitText; `references/performance.md` before the render) — upstream `hyperframes-animation` covers the Hyperframes-specific timing; it does not replace it. Word-level captions through `/embedded-captions` whenever there is speech.
|
|
69
|
+
- Assets: `/media-use` resolves BGM, SFX, images and logos from licensed sources before anything is downloaded. Generated assets come from Higgsfield (`content/image-create`, `generate_video`); credits are metered — never regenerate in a loop without explicit approval.
|
|
70
|
+
|
|
71
|
+
## Phase 3 — Render and review with evidence
|
|
72
|
+
|
|
73
|
+
1. Render through the router / `/hyperframes-cli` (`npx hyperframes render …`). Keep the command and exit code on record (G3 of the evidence flow).
|
|
74
|
+
2. Review the render with `dev/watch` on the output MP4: complete frames + timestamped transcript. Judge the hook (first 3 s), pacing, caption accuracy, audio sync, aspect ratio, brand fidelity. Screenshots are not evidence; frames are.
|
|
75
|
+
3. Loop on defects. A reshoot the review demands is never blocked by time or token cost; the CostGovernor budget is the only ceiling.
|
|
76
|
+
|
|
77
|
+
## Phase 4 — Quality Gate and delivery
|
|
78
|
+
|
|
79
|
+
Marta orchestrates Eduardo (on-screen copy, captions, description) and Francisca (render spec, determinism, composition code quality). Binary verdict. Deliver the master MP4, the per-platform reframes the brief asked for, the SRT when captions exist, and the `[arka:design]` line plus review notes in the delivery summary. Output goes to Obsidian `WizardingCode/Content/Video/<date>-<slug>/` (or the client project's vault path).
|
|
80
|
+
|
|
81
|
+
## Never
|
|
82
|
+
|
|
83
|
+
- Never load `/hyperframes` when the preflight fails, and never guess what Hyperframes is.
|
|
84
|
+
- Never bypass the router's route table with a hand-picked workflow "because it looks right" — the router reads `references/routes/<workflow>.md` and decides.
|
|
85
|
+
- Never ship a render nobody watched.
|
|
86
|
+
- Never install Hyperframes skills from a marketplace other than `heygen-com/hyperframes`; third-party "video" skills that wrap paid engines or hook every `*video*` skill are rejected by decision (spec `hyperframes-routing`).
|
|
87
|
+
|
|
88
|
+
## Examples
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
/content hyperframes "renderiza o projeto em ./launch-film e revê o resultado"
|
|
92
|
+
/content hyperframes "adiciona legendas embutidas word-level ao talking head em ./ep03"
|
|
93
|
+
/content hyperframes "converte a composição Remotion em ./remotion-intro para Hyperframes"
|
|
94
|
+
/content hyperframes "motion graphic de 8 s com o número 30K a subir, brand ArkaOS"
|
|
95
|
+
```
|