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.
Files changed (35) hide show
  1. package/VERSION +1 -1
  2. package/bin/arka +5 -1
  3. package/bin/arkaos +2 -2
  4. package/core/evals/__pycache__/__init__.cpython-313.pyc +0 -0
  5. package/core/evals/__pycache__/record_cli.cpython-313.pyc +0 -0
  6. package/core/evals/__pycache__/runner_cli.cpython-313.pyc +0 -0
  7. package/core/evals/__pycache__/sanitizer.cpython-313.pyc +0 -0
  8. package/core/evals/__pycache__/schema.cpython-313.pyc +0 -0
  9. package/core/evals/__pycache__/verdict_labels.cpython-313.pyc +0 -0
  10. package/core/evals/record_cli.py +63 -0
  11. package/core/evals/runner_cli.py +102 -0
  12. package/core/evals/sanitizer.py +97 -0
  13. package/core/governance/__pycache__/leak_scanner.cpython-313.pyc +0 -0
  14. package/core/governance/__pycache__/phantom_action_check.cpython-313.pyc +0 -0
  15. package/core/governance/__pycache__/phantom_action_check.cpython-314.pyc +0 -0
  16. package/core/governance/leak_scanner.py +10 -0
  17. package/core/governance/phantom_action_check.py +17 -2
  18. package/core/registry/__pycache__/__init__.cpython-312.pyc +0 -0
  19. package/core/registry/__pycache__/generator.cpython-312.pyc +0 -0
  20. package/core/registry/__pycache__/generator.cpython-313.pyc +0 -0
  21. package/core/registry/generator.py +138 -110
  22. package/core/runtime/__pycache__/llm_cost_telemetry.cpython-314.pyc +0 -0
  23. package/departments/dev/skills/scaffold/SKILL.md +3 -3
  24. package/departments/quality/SKILL.md +8 -0
  25. package/knowledge/commands-registry.json +4194 -2627
  26. package/knowledge/commands-registry.json.bak +4194 -2627
  27. package/package.json +1 -1
  28. package/pyproject.toml +1 -1
  29. package/scripts/__pycache__/dashboard-api.cpython-313.pyc +0 -0
  30. package/scripts/__pycache__/synapse-bridge.cpython-313.pyc +0 -0
  31. package/scripts/__pycache__/synapse-bridge.cpython-314.pyc +0 -0
  32. package/scripts/dashboard-api.py +2 -2
  33. package/scripts/synapse-bridge.py +1 -1
  34. package/scripts/tools/__pycache__/prompt_surface_benchmark.cpython-313.pyc +0 -0
  35. package/knowledge/commands-registry-v2.json +0 -3777
package/VERSION CHANGED
@@ -1 +1 @@
1
- 4.9.0
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
- 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 ""
@@ -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())
@@ -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)\b"
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
- r"\bI(?:'ve| have)?\s+(?:just\s+)?(created|wrote|updated|deleted"
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
  )