arkaos 4.9.0 → 4.11.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/bin/arka +5 -1
- package/bin/arkaos +2 -2
- package/core/evals/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/evals/__pycache__/record_cli.cpython-313.pyc +0 -0
- package/core/evals/__pycache__/runner_cli.cpython-313.pyc +0 -0
- package/core/evals/__pycache__/sanitizer.cpython-313.pyc +0 -0
- package/core/evals/__pycache__/schema.cpython-313.pyc +0 -0
- package/core/evals/__pycache__/verdict_labels.cpython-313.pyc +0 -0
- package/core/evals/record_cli.py +63 -0
- package/core/evals/runner_cli.py +102 -0
- package/core/evals/sanitizer.py +97 -0
- package/core/governance/__pycache__/leak_scanner.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/phantom_action_check.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/phantom_action_check.cpython-314.pyc +0 -0
- package/core/governance/leak_scanner.py +10 -0
- package/core/governance/phantom_action_check.py +17 -2
- package/core/registry/__pycache__/__init__.cpython-312.pyc +0 -0
- package/core/registry/__pycache__/generator.cpython-312.pyc +0 -0
- package/core/registry/__pycache__/generator.cpython-313.pyc +0 -0
- package/core/registry/generator.py +138 -110
- package/core/runtime/__pycache__/llm_cost_telemetry.cpython-314.pyc +0 -0
- package/departments/dev/skills/scaffold/SKILL.md +3 -3
- package/departments/quality/SKILL.md +8 -0
- package/knowledge/commands-registry.json +4194 -2627
- package/knowledge/commands-registry.json.bak +4194 -2627
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/scripts/__pycache__/dashboard-api.cpython-313.pyc +0 -0
- package/scripts/__pycache__/synapse-bridge.cpython-313.pyc +0 -0
- package/scripts/__pycache__/synapse-bridge.cpython-314.pyc +0 -0
- package/scripts/dashboard-api.py +2 -2
- package/scripts/synapse-bridge.py +1 -1
- package/scripts/tools/__pycache__/prompt_surface_benchmark.cpython-313.pyc +0 -0
- package/knowledge/commands-registry-v2.json +0 -3777
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
4.
|
|
1
|
+
4.11.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
|
-
|
|
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
|
|
69
|
-
commands=$(jq -r '._meta.total_commands // 0' "$REPO_ROOT/knowledge/commands-registry
|
|
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 ""
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Record a QGVerdict as an eval label — the QG-skill wiring point.
|
|
2
|
+
|
|
3
|
+
Reads the reviewer's QGVerdict JSON from stdin (or --file) and appends
|
|
4
|
+
it to the label corpus via ``record_verdict_label``. Invoked by the
|
|
5
|
+
orchestrator right after a Quality Gate verdict lands (see the Quality
|
|
6
|
+
Gate skill instructions), closing the "labels gratuitos" loop from the
|
|
7
|
+
evals ADR.
|
|
8
|
+
|
|
9
|
+
Unlike the underlying writer (telemetry contract: never raises), this
|
|
10
|
+
explicit CLI fails LOUDLY on invalid verdict JSON — a malformed label
|
|
11
|
+
recorded silently would poison the corpus.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import json
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from pydantic import ValidationError
|
|
22
|
+
|
|
23
|
+
from core.evals.verdict_labels import record_verdict_label
|
|
24
|
+
from core.governance.qg_verdict import QGVerdict
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def main(argv: list[str] | None = None) -> int:
|
|
28
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
29
|
+
parser.add_argument("--file", help="verdict JSON file (default: stdin)")
|
|
30
|
+
parser.add_argument("--deliverable", default="")
|
|
31
|
+
parser.add_argument("--department", default="")
|
|
32
|
+
parser.add_argument("--eval-task-id", default="")
|
|
33
|
+
parser.add_argument("--session-id", default="")
|
|
34
|
+
args = parser.parse_args(argv)
|
|
35
|
+
|
|
36
|
+
raw = (
|
|
37
|
+
Path(args.file).read_text(encoding="utf-8")
|
|
38
|
+
if args.file
|
|
39
|
+
else sys.stdin.read()
|
|
40
|
+
)
|
|
41
|
+
try:
|
|
42
|
+
verdict = QGVerdict.model_validate(json.loads(raw))
|
|
43
|
+
except (json.JSONDecodeError, ValidationError) as exc:
|
|
44
|
+
print(f"error: invalid QGVerdict JSON — {exc}", file=sys.stderr)
|
|
45
|
+
return 1
|
|
46
|
+
|
|
47
|
+
record_verdict_label(
|
|
48
|
+
verdict,
|
|
49
|
+
deliverable=args.deliverable,
|
|
50
|
+
department=args.department,
|
|
51
|
+
eval_task_id=args.eval_task_id,
|
|
52
|
+
session_id=args.session_id,
|
|
53
|
+
)
|
|
54
|
+
print(json.dumps({
|
|
55
|
+
"recorded": True,
|
|
56
|
+
"verdict": verdict.verdict,
|
|
57
|
+
"eval_task_id": args.eval_task_id,
|
|
58
|
+
}))
|
|
59
|
+
return 0
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
if __name__ == "__main__":
|
|
63
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Eval runner CLI — list tasks, corpus status, dispatch-ready prompts.
|
|
2
|
+
|
|
3
|
+
Evals reuse the Quality Gate as the judge (ADR 2026-07-09): a run is
|
|
4
|
+
the orchestrator dispatching a task prompt through the normal squad
|
|
5
|
+
flow, then recording the QGVerdict with the task id via
|
|
6
|
+
``core.evals.record_cli``. This CLI is the deterministic half — it
|
|
7
|
+
never invokes models itself.
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
arka-py -m core.evals.runner_cli list [--department dev]
|
|
11
|
+
arka-py -m core.evals.runner_cli status
|
|
12
|
+
arka-py -m core.evals.runner_cli prompt <task-id>
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import argparse
|
|
18
|
+
import sys
|
|
19
|
+
|
|
20
|
+
from core.evals.schema import EvalTask, load_eval_tasks
|
|
21
|
+
from core.evals.verdict_labels import load_verdict_labels
|
|
22
|
+
|
|
23
|
+
DISTILLATION_LABEL_GATE = 500 # ADR 2026-07-09-evals-and-distillation
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _cmd_list(department: str | None) -> int:
|
|
27
|
+
tasks = load_eval_tasks()
|
|
28
|
+
if department:
|
|
29
|
+
tasks = [t for t in tasks if t.department == department]
|
|
30
|
+
for task in tasks:
|
|
31
|
+
print(f"{task.id:35s} {task.department:10s} {task.prompt[:60].strip()}…")
|
|
32
|
+
print(f"\n{len(tasks)} task(s)")
|
|
33
|
+
return 0
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _cmd_status() -> int:
|
|
37
|
+
labels = load_verdict_labels()
|
|
38
|
+
verdicts = [str(entry.get("verdict", "")) for entry in labels]
|
|
39
|
+
by_dept: dict[str, int] = {}
|
|
40
|
+
linked = 0
|
|
41
|
+
for entry in labels:
|
|
42
|
+
dept = str(entry.get("department") or "(none)")
|
|
43
|
+
by_dept[dept] = by_dept.get(dept, 0) + 1
|
|
44
|
+
if entry.get("eval_task_id"):
|
|
45
|
+
linked += 1
|
|
46
|
+
total = len(labels)
|
|
47
|
+
print(f"labels: {total} (gate: {DISTILLATION_LABEL_GATE} — "
|
|
48
|
+
f"{max(0, DISTILLATION_LABEL_GATE - total)} to go)")
|
|
49
|
+
print(f" APPROVED: {verdicts.count('APPROVED')} "
|
|
50
|
+
f"REJECTED: {verdicts.count('REJECTED')}")
|
|
51
|
+
print(f" linked to eval tasks: {linked}")
|
|
52
|
+
for dept, count in sorted(by_dept.items()):
|
|
53
|
+
print(f" {dept}: {count}")
|
|
54
|
+
print(f"eval tasks defined: {len(load_eval_tasks())}")
|
|
55
|
+
return 0
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _dispatch_prompt(task: EvalTask) -> str:
|
|
59
|
+
properties = "\n".join(f"- {p}" for p in task.expected_properties)
|
|
60
|
+
rubric = f"\nRubrica adicional: {task.rubric}" if task.rubric else ""
|
|
61
|
+
return (
|
|
62
|
+
f"[EVAL RUN — task {task.id}]\n"
|
|
63
|
+
f"Departamento: {task.department}\n\n"
|
|
64
|
+
f"Tarefa: {task.prompt}\n\n"
|
|
65
|
+
f"O deliverable será julgado pelo Quality Gate contra estas "
|
|
66
|
+
f"propriedades verificáveis:\n{properties}{rubric}\n\n"
|
|
67
|
+
f"Após o verdict, regista o label com:\n"
|
|
68
|
+
f" arka-py -m core.evals.record_cli --eval-task-id {task.id} "
|
|
69
|
+
f"--department {task.department} < verdict.json"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _cmd_prompt(task_id: str) -> int:
|
|
74
|
+
tasks = {t.id: t for t in load_eval_tasks()}
|
|
75
|
+
task = tasks.get(task_id)
|
|
76
|
+
if task is None:
|
|
77
|
+
print(f"error: unknown eval task {task_id!r}; run `list`",
|
|
78
|
+
file=sys.stderr)
|
|
79
|
+
return 1
|
|
80
|
+
print(_dispatch_prompt(task))
|
|
81
|
+
return 0
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def main(argv: list[str] | None = None) -> int:
|
|
85
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
86
|
+
sub = parser.add_subparsers(dest="cmd", required=True)
|
|
87
|
+
p_list = sub.add_parser("list")
|
|
88
|
+
p_list.add_argument("--department")
|
|
89
|
+
sub.add_parser("status")
|
|
90
|
+
p_prompt = sub.add_parser("prompt")
|
|
91
|
+
p_prompt.add_argument("task_id")
|
|
92
|
+
args = parser.parse_args(argv)
|
|
93
|
+
|
|
94
|
+
if args.cmd == "list":
|
|
95
|
+
return _cmd_list(args.department)
|
|
96
|
+
if args.cmd == "status":
|
|
97
|
+
return _cmd_status()
|
|
98
|
+
return _cmd_prompt(args.task_id)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
if __name__ == "__main__":
|
|
102
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Client-identifier sanitizer — distillation prerequisite (evals ADR).
|
|
2
|
+
|
|
3
|
+
Redacts every client identifier from transcript text BEFORE it can feed
|
|
4
|
+
a LoRA training run. Confidentiality is NON-NEGOTIABLE (v2.18.0 npm
|
|
5
|
+
leak precedent), so this module fails CLOSED: with no redaction config
|
|
6
|
+
on disk there is nothing to prove the text is clean, and sanitize()
|
|
7
|
+
refuses to run rather than pass text through unredacted.
|
|
8
|
+
|
|
9
|
+
The identifier list is the same one the release leak-scanner uses
|
|
10
|
+
(``~/.arkaos/redaction-clients.json`` via
|
|
11
|
+
``core.governance.leak_scanner.load_redaction_patterns``) — one source
|
|
12
|
+
of truth, never hardcoded in the repo.
|
|
13
|
+
|
|
14
|
+
Replacement is deterministic: the Nth pattern in the config maps to
|
|
15
|
+
``[CLIENT-N]`` in every run. Corpora stay diffable under the APPEND-ONLY
|
|
16
|
+
invariant — new clients go at the END of the config list; reordering or
|
|
17
|
+
deleting entries remaps the placeholders of everything sanitized before.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import re
|
|
23
|
+
import sys
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
from core.governance.leak_scanner import load_redaction_patterns
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class SanitizerConfigMissing(RuntimeError):
|
|
30
|
+
"""No redaction config — sanitization cannot be proven, so it refuses."""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def sanitize_text(
|
|
34
|
+
text: str, config_path: Path | None = None
|
|
35
|
+
) -> tuple[str, dict[str, int]]:
|
|
36
|
+
"""Redact client identifiers; return (clean_text, per-placeholder counts).
|
|
37
|
+
|
|
38
|
+
Raises SanitizerConfigMissing when the redaction config is absent or
|
|
39
|
+
empty — fail closed, never pass text through unproven.
|
|
40
|
+
"""
|
|
41
|
+
patterns = load_redaction_patterns(config_path)
|
|
42
|
+
if not patterns:
|
|
43
|
+
raise SanitizerConfigMissing(
|
|
44
|
+
"redaction config missing or empty (~/.arkaos/redaction-"
|
|
45
|
+
"clients.json) — refusing to sanitize without an identifier "
|
|
46
|
+
"list; a silent pass-through would poison the training corpus"
|
|
47
|
+
)
|
|
48
|
+
# SINGLE pass over ONE longest-first alternation (QG blocker,
|
|
49
|
+
# 2026-07-09): a positional per-pattern loop leaked the suffix of a
|
|
50
|
+
# longer identifier whenever a shorter prefix pattern came first in
|
|
51
|
+
# the config ("data" before "data dynamics corp"), and replacement
|
|
52
|
+
# text could itself be re-matched. Placeholder numbers stay keyed to
|
|
53
|
+
# CONFIG position, so appends never remap an existing corpus.
|
|
54
|
+
index_by_pattern = {p: i for i, p in enumerate(patterns, start=1)}
|
|
55
|
+
alternation = "|".join(
|
|
56
|
+
re.escape(p) for p in sorted(patterns, key=len, reverse=True)
|
|
57
|
+
)
|
|
58
|
+
regex = re.compile(
|
|
59
|
+
r"(?<![a-z0-9])(" + alternation + r")(?![a-z0-9])", re.IGNORECASE
|
|
60
|
+
)
|
|
61
|
+
counts: dict[str, int] = {}
|
|
62
|
+
|
|
63
|
+
def _redact(match: re.Match[str]) -> str:
|
|
64
|
+
placeholder = f"[CLIENT-{index_by_pattern[match.group(1).lower()]}]"
|
|
65
|
+
counts[placeholder] = counts.get(placeholder, 0) + 1
|
|
66
|
+
return placeholder
|
|
67
|
+
|
|
68
|
+
return regex.sub(_redact, text), counts
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def sanitize_file(
|
|
72
|
+
path: Path, config_path: Path | None = None
|
|
73
|
+
) -> tuple[str, dict[str, int]]:
|
|
74
|
+
return sanitize_text(
|
|
75
|
+
path.read_text(encoding="utf-8"), config_path=config_path
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def main(argv: list[str] | None = None) -> int:
|
|
80
|
+
"""CLI: sanitize a file (or stdin) to stdout; counts on stderr."""
|
|
81
|
+
args = argv if argv is not None else sys.argv[1:]
|
|
82
|
+
try:
|
|
83
|
+
if args:
|
|
84
|
+
clean, counts = sanitize_file(Path(args[0]))
|
|
85
|
+
else:
|
|
86
|
+
clean, counts = sanitize_text(sys.stdin.read())
|
|
87
|
+
except SanitizerConfigMissing as exc:
|
|
88
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
89
|
+
return 2
|
|
90
|
+
sys.stdout.write(clean)
|
|
91
|
+
total = sum(counts.values())
|
|
92
|
+
print(f"sanitized: {total} redaction(s) {counts}", file=sys.stderr)
|
|
93
|
+
return 0
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
if __name__ == "__main__":
|
|
97
|
+
raise SystemExit(main())
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -93,6 +93,16 @@ def scan_text(
|
|
|
93
93
|
return [m.group(1).lower() for m in regex.finditer(text)]
|
|
94
94
|
|
|
95
95
|
|
|
96
|
+
def load_redaction_patterns(config_path: Path | None = None) -> tuple[str, ...]:
|
|
97
|
+
"""Public accessor for the redaction client list.
|
|
98
|
+
|
|
99
|
+
Consumed by core.evals.sanitizer (distillation prerequisite) so the
|
|
100
|
+
scanner and the sanitizer can never disagree on what a client
|
|
101
|
+
identifier is. Returns () when the config is missing or empty.
|
|
102
|
+
"""
|
|
103
|
+
return _load_patterns(config_path or _DEFAULT_CONFIG_PATH)
|
|
104
|
+
|
|
105
|
+
|
|
96
106
|
def _load_patterns(config_path: Path) -> tuple[str, ...]:
|
|
97
107
|
try:
|
|
98
108
|
data = json.loads(config_path.read_text(encoding="utf-8"))
|
|
@@ -45,7 +45,12 @@ _ANALYTIC_NOUN = (
|
|
|
45
45
|
r"|takeaways?|impressions?|sense|grasp|view|idea|picture"
|
|
46
46
|
r"|mental\s+model|contexto|resumo|notas?|entendimento"
|
|
47
47
|
r"|perce[çc][ãa]o|an[áa]lise|analysis|ideia|vis[ãa]o|imagem"
|
|
48
|
-
r"|no[çc][ãa]o|perspe[ct]?tiva|opini[ãa]o|conhecimento|knowledge
|
|
48
|
+
r"|no[çc][ãa]o|perspe[ct]?tiva|opini[ãa]o|conhecimento|knowledge"
|
|
49
|
+
# Synonym tail (ADR 2026-07-09-phantom M3): noun-reads anchored by
|
|
50
|
+
# the preposition — plurals included — so verb uses ("read the
|
|
51
|
+
# file") stay untouched.
|
|
52
|
+
r"|read(?:ing)?s?(?=\s+(?:on|of)\b)|takes?(?=\s+on\b)"
|
|
53
|
+
r"|interpretations?|assessments?|interpreta[çc][õã][eo]s?|leituras?)\b"
|
|
49
54
|
)
|
|
50
55
|
_GAP = r"(?:(?!" + _ANALYTIC_NOUN + r")[^.\n!?]){0,60}?"
|
|
51
56
|
|
|
@@ -67,7 +72,17 @@ _BOUND_PT = (
|
|
|
67
72
|
r"|movi|apliquei|corri|executei)\b" + _GAP + _EFFECT_OBJECT
|
|
68
73
|
)
|
|
69
74
|
_BOUND_EN = (
|
|
70
|
-
|
|
75
|
+
# Optional coordinated clause after the I-subject (ADR 2026-07-09-
|
|
76
|
+
# phantom M2): "I ran through the plan, then ran the tests" — the
|
|
77
|
+
# second bare verb shares the subject. The chunk must START with the
|
|
78
|
+
# I-clause's own idiom verb (ran into/through/over/across), so an
|
|
79
|
+
# embedded clause with a third-party subject ("I confirmed the hook
|
|
80
|
+
# ran and created…") can never bridge the I anchor to a foreign
|
|
81
|
+
# effect verb (QG blocker, 2026-07-09 re-review).
|
|
82
|
+
r"\bI(?:'ve| have)?\s+(?:just\s+)?"
|
|
83
|
+
r"(?:ran\s+(?:into|through|over|across)\b"
|
|
84
|
+
r"[^.\n!?]{0,40}?[,;]?\s*\b(?:then|and)\s+)?"
|
|
85
|
+
r"(created|wrote|updated|deleted"
|
|
71
86
|
r"|added|moved|applied|renamed|ran(?!\s+(?:into|through|over|across))"
|
|
72
87
|
r"|executed)\b" + _GAP + _EFFECT_OBJECT
|
|
73
88
|
)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|