arkaos 2.46.3 → 2.48.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/arka/SKILL.md +1 -0
- package/core/governance/__pycache__/compliance_telemetry.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/compliance_telemetry_cli.cpython-313.pyc +0 -0
- package/core/governance/compliance_telemetry.py +140 -0
- package/core/governance/compliance_telemetry_cli.py +65 -0
- package/installer/index.js +16 -0
- package/installer/user-data-scaffold.js +59 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.
|
|
1
|
+
2.48.0
|
package/arka/SKILL.md
CHANGED
|
@@ -188,6 +188,7 @@ violation (squad-routing, arka-supremacy, spec-driven, mandatory-qa).
|
|
|
188
188
|
| `/arka status` | System status (version, departments, agents, active projects). Includes **LLM costs (24h)** section: top-line cost + cache hit rate + call count from `core.runtime.llm_cost_telemetry.summarise(period="today")`. Also includes **Enforcement (24h)** section: total calls, block rate, top blocked tools/reasons from `core.governance.enforcement_telemetry.summarise(period="today")` (PR19 v2.41.0). Plus **Reorganization (today)** section: today's proposal path + artifact count from `core.cognition.reorganizer_scheduler.status_summary()` (PR24 v2.46.0). |
|
|
189
189
|
| `/arka costs [period]` | LLM cost visibility — aggregates telemetry by day/week/month/all, with top expensive sessions. See `arka/skills/costs/SKILL.md`. Shells out to `python -m core.runtime.llm_cost_telemetry_cli <period>`. |
|
|
190
190
|
| `/arka enforcement [period]` | Enforcement compliance — aggregates flow-marker enforcement telemetry by day/week/month/all. Shows block rate, top blocked tools, top reasons. Shells out to `python -m core.governance.enforcement_telemetry_cli <period>`. |
|
|
191
|
+
| `/arka compliance [period]` | Behavior compliance summary (PR29 v2.48.0) — aggregates stop-hook telemetry by day/week/month/all. Shows rates for the four contracts: closing marker, `[arka:meta]` tag, KB citation pass, sycophancy clean. Shells out to `python -m core.governance.compliance_telemetry_cli <period>`. |
|
|
191
192
|
| `/arka reorganize [--since-days N]` | Dreaming → Agent reorganizer. Aggregates recent KB pattern/anti-pattern/lesson artifacts (default last 7 days) into a markdown proposal at `~/.arkaos/reorganize-proposals/<date>.md`. **Propose-only** — never modifies agent YAMLs. Sanitizes client identifiers from titles and body excerpts; drops `tags:` field entirely to prevent project-name leaks. **Auto-fires on session start when today's proposal is missing** (PR24 v2.46.0 stale-aware trigger, 30s timeout, background). Shells out to `python -m core.cognition.reorganizer_cli [--since-days N] [--dry-run]`. |
|
|
192
193
|
| `/arka standup` | Daily standup (projects, priorities, blockers, updates) |
|
|
193
194
|
| `/arka monitor` | System health monitoring |
|
|
Binary file
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Behavior compliance telemetry summarizer (PR29 v2.48.0).
|
|
2
|
+
|
|
3
|
+
Reads the stop-hook entries in ``~/.arkaos/telemetry/enforcement.jsonl``
|
|
4
|
+
and reports compliance with the four contracts the session-start hook
|
|
5
|
+
establishes:
|
|
6
|
+
|
|
7
|
+
- closing_marker_found — [arka:phase:13] or [arka:trivial] present
|
|
8
|
+
- meta_tag_found — [arka:meta] one-liner present (PR12)
|
|
9
|
+
- kb_cite_passed — KB citation soft block result (PR18)
|
|
10
|
+
- sycophancy clean — inverse of sycophancy_is_flagged (PR13)
|
|
11
|
+
|
|
12
|
+
Mirrors the period vocabulary of ``enforcement_telemetry.summarise``
|
|
13
|
+
(today / week / month / all). Empty/missing file = no-op zero rates.
|
|
14
|
+
Null fields excluded from denominators so rates reflect *observed*
|
|
15
|
+
behavior, not ``unknown``-padded data.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
from dataclasses import dataclass
|
|
22
|
+
from datetime import datetime, timedelta, timezone
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Any, Iterable
|
|
25
|
+
|
|
26
|
+
DEFAULT_PATH: Path = Path.home() / ".arkaos" / "telemetry" / "enforcement.jsonl"
|
|
27
|
+
_VALID_PERIODS: frozenset[str] = frozenset({"today", "week", "month", "all"})
|
|
28
|
+
_STOP_EVENT: str = "stop-hook-flow-check"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True)
|
|
32
|
+
class ComplianceSummary:
|
|
33
|
+
"""Compliance snapshot over a slice of stop-hook telemetry."""
|
|
34
|
+
period: str
|
|
35
|
+
stop_events: int
|
|
36
|
+
closing_marker_rate: float
|
|
37
|
+
meta_tag_rate: float
|
|
38
|
+
kb_cite_pass_rate: float
|
|
39
|
+
sycophancy_clean_rate: float
|
|
40
|
+
corrupt_line_count: int = 0
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def summarise(period: str, *, path: Path | None = None) -> ComplianceSummary:
|
|
44
|
+
"""Return a ComplianceSummary for the requested period."""
|
|
45
|
+
if period not in _VALID_PERIODS:
|
|
46
|
+
raise ValueError(f"invalid period: {period!r}")
|
|
47
|
+
src = path or DEFAULT_PATH
|
|
48
|
+
cutoff = _period_cutoff(period)
|
|
49
|
+
entries, corrupt = _read_stop_entries(src, cutoff)
|
|
50
|
+
return _build_summary(period, entries, corrupt)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _period_cutoff(period: str, now: datetime | None = None) -> datetime | None:
|
|
54
|
+
ref = now or datetime.now(timezone.utc)
|
|
55
|
+
if period == "today":
|
|
56
|
+
return ref.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
57
|
+
if period == "week":
|
|
58
|
+
return ref - timedelta(days=7)
|
|
59
|
+
if period == "month":
|
|
60
|
+
return ref - timedelta(days=30)
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _read_stop_entries(
|
|
65
|
+
src: Path, cutoff: datetime | None,
|
|
66
|
+
) -> tuple[list[dict[str, Any]], int]:
|
|
67
|
+
if not src.exists():
|
|
68
|
+
return [], 0
|
|
69
|
+
entries: list[dict[str, Any]] = []
|
|
70
|
+
corrupt = 0
|
|
71
|
+
try:
|
|
72
|
+
with src.open("r", encoding="utf-8", errors="replace") as fh:
|
|
73
|
+
for line in fh:
|
|
74
|
+
if not line.strip():
|
|
75
|
+
continue
|
|
76
|
+
try:
|
|
77
|
+
entry = json.loads(line)
|
|
78
|
+
except json.JSONDecodeError:
|
|
79
|
+
corrupt += 1
|
|
80
|
+
continue
|
|
81
|
+
if not isinstance(entry, dict):
|
|
82
|
+
corrupt += 1
|
|
83
|
+
continue
|
|
84
|
+
if entry.get("event") != _STOP_EVENT:
|
|
85
|
+
continue
|
|
86
|
+
if cutoff is not None and not _within_cutoff(entry, cutoff):
|
|
87
|
+
continue
|
|
88
|
+
entries.append(entry)
|
|
89
|
+
except OSError:
|
|
90
|
+
return entries, corrupt
|
|
91
|
+
return entries, corrupt
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _within_cutoff(entry: dict[str, Any], cutoff: datetime) -> bool:
|
|
95
|
+
ts = _parse_ts(entry.get("ts"))
|
|
96
|
+
return ts is not None and ts >= cutoff
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _parse_ts(raw: Any) -> datetime | None:
|
|
100
|
+
if not isinstance(raw, str) or not raw:
|
|
101
|
+
return None
|
|
102
|
+
try:
|
|
103
|
+
parsed = datetime.fromisoformat(raw.replace("Z", "+00:00"))
|
|
104
|
+
except ValueError:
|
|
105
|
+
return None
|
|
106
|
+
if parsed.tzinfo is None:
|
|
107
|
+
parsed = parsed.replace(tzinfo=timezone.utc)
|
|
108
|
+
return parsed
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _build_summary(
|
|
112
|
+
period: str,
|
|
113
|
+
entries: Iterable[dict[str, Any]],
|
|
114
|
+
corrupt: int,
|
|
115
|
+
) -> ComplianceSummary:
|
|
116
|
+
rows = list(entries)
|
|
117
|
+
return ComplianceSummary(
|
|
118
|
+
period=period,
|
|
119
|
+
stop_events=len(rows),
|
|
120
|
+
closing_marker_rate=_true_rate(rows, "closing_marker_found"),
|
|
121
|
+
meta_tag_rate=_true_rate(rows, "meta_tag_found"),
|
|
122
|
+
kb_cite_pass_rate=_true_rate(rows, "kb_cite_passed"),
|
|
123
|
+
sycophancy_clean_rate=_false_rate(rows, "sycophancy_is_flagged"),
|
|
124
|
+
corrupt_line_count=corrupt,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _true_rate(rows: list[dict[str, Any]], key: str) -> float:
|
|
129
|
+
observed = [r for r in rows if isinstance(r.get(key), bool)]
|
|
130
|
+
if not observed:
|
|
131
|
+
return 0.0
|
|
132
|
+
return sum(1 for r in observed if r[key] is True) / len(observed)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _false_rate(rows: list[dict[str, Any]], key: str) -> float:
|
|
136
|
+
"""Rate of `key is False` among rows where the field was observed."""
|
|
137
|
+
observed = [r for r in rows if isinstance(r.get(key), bool)]
|
|
138
|
+
if not observed:
|
|
139
|
+
return 0.0
|
|
140
|
+
return sum(1 for r in observed if r[key] is False) / len(observed)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""CLI entry for the behavior-compliance summarizer (PR29 v2.48.0).
|
|
2
|
+
|
|
3
|
+
Invoked as ``python -m core.governance.compliance_telemetry_cli <period>``.
|
|
4
|
+
Renders a markdown report covering the four stop-hook behavior contracts:
|
|
5
|
+
closing marker, meta tag, KB cite pass, sycophancy clean.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
from core.governance.compliance_telemetry import (
|
|
13
|
+
ComplianceSummary,
|
|
14
|
+
summarise,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _fmt_rate(value: float) -> str:
|
|
19
|
+
return f"{value * 100:.2f}%"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _render(summary: ComplianceSummary) -> str:
|
|
23
|
+
lines = [
|
|
24
|
+
f"# Behavior compliance — {summary.period}",
|
|
25
|
+
"",
|
|
26
|
+
f"- Stop-hook events observed: **{summary.stop_events}**",
|
|
27
|
+
"",
|
|
28
|
+
"| Contract | Compliance rate |",
|
|
29
|
+
"|---|---|",
|
|
30
|
+
f"| `[arka:phase:13]` / `[arka:trivial]` closing marker "
|
|
31
|
+
f"| {_fmt_rate(summary.closing_marker_rate)} |",
|
|
32
|
+
f"| `[arka:meta]` one-liner (PR12 v2.34.0) "
|
|
33
|
+
f"| {_fmt_rate(summary.meta_tag_rate)} |",
|
|
34
|
+
f"| KB citation pass (PR18 v2.40.0) "
|
|
35
|
+
f"| {_fmt_rate(summary.kb_cite_pass_rate)} |",
|
|
36
|
+
f"| Sycophancy clean (inverse of flagged, PR13 v2.35.0) "
|
|
37
|
+
f"| {_fmt_rate(summary.sycophancy_clean_rate)} |",
|
|
38
|
+
]
|
|
39
|
+
if summary.corrupt_line_count:
|
|
40
|
+
lines += [
|
|
41
|
+
"",
|
|
42
|
+
f"_(skipped {summary.corrupt_line_count} corrupt line(s) in telemetry)_",
|
|
43
|
+
]
|
|
44
|
+
if summary.stop_events == 0:
|
|
45
|
+
lines += [
|
|
46
|
+
"",
|
|
47
|
+
"_(no stop-hook events in window — open a session and complete "
|
|
48
|
+
"a turn to populate telemetry)_",
|
|
49
|
+
]
|
|
50
|
+
return "\n".join(lines)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def main(argv: list[str]) -> int:
|
|
54
|
+
period = argv[1] if len(argv) > 1 else "today"
|
|
55
|
+
try:
|
|
56
|
+
summary = summarise(period)
|
|
57
|
+
except ValueError as exc:
|
|
58
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
59
|
+
return 2
|
|
60
|
+
print(_render(summary))
|
|
61
|
+
return 0
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if __name__ == "__main__":
|
|
65
|
+
raise SystemExit(main(sys.argv))
|
package/installer/index.js
CHANGED
|
@@ -295,6 +295,22 @@ export async function install({ runtime, path, force, skipSystem, withOllama })
|
|
|
295
295
|
console.log(` Warning: could not seed config.json (${err.message})`);
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
+
// PR28 v2.47.0 — scaffold the user-mutable files the discipline-arc
|
|
299
|
+
// commands depend on: redaction-clients.json (leak scanner config)
|
|
300
|
+
// and reorganize-proposals/ (daily proposal output). Idempotent.
|
|
301
|
+
try {
|
|
302
|
+
const { scaffoldArkaosUserData } = await import("./user-data-scaffold.js");
|
|
303
|
+
const scaffoldResult = scaffoldArkaosUserData({ home: homedir() });
|
|
304
|
+
if (scaffoldResult.redaction.action === "created") {
|
|
305
|
+
console.log(` redaction-clients.json scaffolded (empty list — populate to enable leak scanner).`);
|
|
306
|
+
}
|
|
307
|
+
if (scaffoldResult.proposals.action === "created") {
|
|
308
|
+
console.log(` reorganize-proposals/ directory created.`);
|
|
309
|
+
}
|
|
310
|
+
} catch (err) {
|
|
311
|
+
console.log(` Warning: could not scaffold user-data (${err.message})`);
|
|
312
|
+
}
|
|
313
|
+
|
|
298
314
|
const manifest = {
|
|
299
315
|
version: VERSION,
|
|
300
316
|
runtime,
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// ~/.arkaos user-data scaffolding (PR28 v2.47.0).
|
|
2
|
+
//
|
|
3
|
+
// Run on every `npx arkaos install` and `npx arkaos@latest update`.
|
|
4
|
+
// Ensures the operator-mutable files and directories that the
|
|
5
|
+
// discipline-arc commands depend on exist:
|
|
6
|
+
//
|
|
7
|
+
// - ~/.arkaos/redaction-clients.json (leak scanner config)
|
|
8
|
+
// - ~/.arkaos/reorganize-proposals/ (daily reorganizer output)
|
|
9
|
+
//
|
|
10
|
+
// Idempotent: never overwrites operator-authored content. On fresh
|
|
11
|
+
// installs, the redaction config is created with an empty `clients`
|
|
12
|
+
// list plus a `_doc` field explaining how to populate it.
|
|
13
|
+
//
|
|
14
|
+
// Returns a status object with one entry per scaffolded resource so
|
|
15
|
+
// the installer caller can log a human-readable line per run.
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
existsSync, mkdirSync, writeFileSync, renameSync,
|
|
19
|
+
} from "node:fs";
|
|
20
|
+
import { homedir } from "node:os";
|
|
21
|
+
import { join, dirname } from "node:path";
|
|
22
|
+
|
|
23
|
+
const REDACTION_TEMPLATE = {
|
|
24
|
+
_doc: (
|
|
25
|
+
"Add real client/project identifiers (lowercase) to enable the leak "
|
|
26
|
+
+ "scanner. Empty `clients` list = scanner is a no-op (no false "
|
|
27
|
+
+ "positives in CI). See core/governance/leak_scanner.py."
|
|
28
|
+
),
|
|
29
|
+
clients: [],
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export function scaffoldArkaosUserData({ home = homedir() } = {}) {
|
|
33
|
+
return {
|
|
34
|
+
redaction: scaffoldRedactionConfig(home),
|
|
35
|
+
proposals: scaffoldProposalsDir(home),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function scaffoldRedactionConfig(home) {
|
|
40
|
+
const path = join(home, ".arkaos", "redaction-clients.json");
|
|
41
|
+
if (existsSync(path)) {
|
|
42
|
+
return { action: "preserved", path };
|
|
43
|
+
}
|
|
44
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
45
|
+
// Atomic write: tmp + rename, matches config-seed.js convention.
|
|
46
|
+
const tmp = `${path}.tmp-${process.pid}`;
|
|
47
|
+
writeFileSync(tmp, JSON.stringify(REDACTION_TEMPLATE, null, 2) + "\n");
|
|
48
|
+
renameSync(tmp, path);
|
|
49
|
+
return { action: "created", path };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function scaffoldProposalsDir(home) {
|
|
53
|
+
const path = join(home, ".arkaos", "reorganize-proposals");
|
|
54
|
+
if (existsSync(path)) {
|
|
55
|
+
return { action: "preserved", path };
|
|
56
|
+
}
|
|
57
|
+
mkdirSync(path, { recursive: true });
|
|
58
|
+
return { action: "created", path };
|
|
59
|
+
}
|
package/package.json
CHANGED