arkaos 4.39.0 → 4.41.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/THE-ARKAOS-GUIDE.md +1 -1
- package/VERSION +1 -1
- package/bin/arka-doctor +3 -2
- package/config/hooks/_lib/fastpath/engine.cjs +44 -7
- package/config/hooks/gate-manifest.json +2 -6
- package/config/hooks/post-tool-use.ps1 +42 -7
- package/config/settings-template.json +11 -0
- package/core/governance/evidence_checks.py +7 -1
- package/core/governance/judge.py +23 -1
- package/core/governance/qg_digest.py +64 -0
- package/core/governance/qg_verdict.py +50 -1
- package/core/governance/reviewer_ledger.py +11 -2
- package/core/hooks/gate_manifest.py +14 -6
- package/core/hooks/post_tool_use.py +87 -26
- package/harness/codex/AGENTS.md +1 -1
- package/harness/copilot/copilot-instructions.md +1 -1
- package/harness/cursor/rules/arkaos.mdc +2 -2
- package/harness/gemini/GEMINI.md +1 -1
- package/harness/opencode/AGENTS.md +1 -1
- 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 +1 -1
- package/installer/adapters/claude-code.js +8 -0
- package/knowledge/skills-manifest.json +1 -1
- package/mcps/arka-tools/pyproject.toml +1 -1
- package/package.json +1 -1
- package/pyproject.toml +5 -2
package/THE-ARKAOS-GUIDE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# The ArkaOS Guide
|
|
2
2
|
|
|
3
|
-
> v4.
|
|
3
|
+
> v4.41.0 — 89 agents, 17 departments, 332 skills, 297 commands, 18 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
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
4.
|
|
1
|
+
4.41.0
|
package/bin/arka-doctor
CHANGED
|
@@ -162,9 +162,10 @@ if [ "$FIX_MODE" = true ] && [ "$HOOKS_OK" = false ]; then
|
|
|
162
162
|
--arg pc "$HOOKS_DIR/pre-compact.sh" \
|
|
163
163
|
--arg ptu "$HOOKS_DIR/post-tool-use.sh" \
|
|
164
164
|
'{
|
|
165
|
-
"UserPromptSubmit": [{"hooks":[{"type":"command","command":$ups,"timeout":
|
|
165
|
+
"UserPromptSubmit": [{"hooks":[{"type":"command","command":$ups,"timeout":20}]}],
|
|
166
166
|
"PreCompact": [{"hooks":[{"type":"command","command":$pc,"timeout":30}]}],
|
|
167
|
-
"PostToolUse": [{"hooks":[{"type":"command","command":$ptu,"timeout":5}]}]
|
|
167
|
+
"PostToolUse": [{"hooks":[{"type":"command","command":$ptu,"timeout":5}]}],
|
|
168
|
+
"PostToolUseFailure": [{"hooks":[{"type":"command","command":$ptu,"timeout":5}]}]
|
|
168
169
|
}')
|
|
169
170
|
jq --argjson hooks "$HOOKS_JSON" '.hooks = $hooks' "$SETTINGS_FILE" > "$SETTINGS_FILE.tmp" && \
|
|
170
171
|
mv "$SETTINGS_FILE.tmp" "$SETTINGS_FILE"
|
|
@@ -17,7 +17,10 @@
|
|
|
17
17
|
* this file — that is exactly the drift the manifest exists to kill.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
// v2: post.success_exit_codes removed with the PostToolUseFailure
|
|
21
|
+
// wiring — an old engine + new manifest mismatch now delegates via
|
|
22
|
+
// validateManifest instead of crashing into the shim's fail-open {}.
|
|
23
|
+
const SCHEMA_VERSION = 2;
|
|
21
24
|
|
|
22
25
|
/** Python truthiness — NOT JS Boolean(): [] and {} are falsy in Python,
|
|
23
26
|
* "false" (non-empty string) is truthy in both. flow_enforcer coerces
|
|
@@ -255,6 +258,34 @@ function flowAuthFresh(sid, manifest, ctx) {
|
|
|
255
258
|
return nowSeconds - ts <= manifest.numbers.auth_ttl_seconds;
|
|
256
259
|
}
|
|
257
260
|
|
|
261
|
+
/** Textual output of a PostToolUse tool_response — parity with the
|
|
262
|
+
* PostToolUse paths of _tool_text in post_tool_use.py (string,
|
|
263
|
+
* stdout/stderr and Task/content shapes, isAsync guard and Python's
|
|
264
|
+
* `or ""` falsy coercion included). Failure-event payloads never
|
|
265
|
+
* reach this — decidePost delegates PostToolUseFailure outright. */
|
|
266
|
+
function toolText(payload) {
|
|
267
|
+
const response = payload.tool_response;
|
|
268
|
+
if (typeof response === "string") return response;
|
|
269
|
+
if (response && typeof response === "object") {
|
|
270
|
+
if ("stdout" in response || "stderr" in response) {
|
|
271
|
+
return [response.stdout, response.stderr]
|
|
272
|
+
.map((part) => String(part || ""))
|
|
273
|
+
.filter(Boolean)
|
|
274
|
+
.join("\n");
|
|
275
|
+
}
|
|
276
|
+
if (response.isAsync) return "";
|
|
277
|
+
const content = response.content;
|
|
278
|
+
if (typeof content === "string") return content;
|
|
279
|
+
if (Array.isArray(content)) {
|
|
280
|
+
return content
|
|
281
|
+
.filter((block) => block && block.type === "text")
|
|
282
|
+
.map((block) => String(block.text || ""))
|
|
283
|
+
.join("");
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return "";
|
|
287
|
+
}
|
|
288
|
+
|
|
258
289
|
/**
|
|
259
290
|
* PostToolUse decision. Returns
|
|
260
291
|
* {action:"fast-exit", stdout:"{}", writes:[...]} — mcp telemetry only
|
|
@@ -267,18 +298,23 @@ function decidePost(payload, manifest, ctx) {
|
|
|
267
298
|
}
|
|
268
299
|
const toolName = String(payload.tool_name || "");
|
|
269
300
|
|
|
301
|
+
// A failing tool throws inside the runtime and fires
|
|
302
|
+
// PostToolUseFailure (2.1.220 contract) — failed-command turns are
|
|
303
|
+
// the gotchas pipeline's primary material, so they always go to
|
|
304
|
+
// Python. Rare by construction: no fast path is worth having here.
|
|
305
|
+
if (payload.hook_event_name === "PostToolUseFailure") {
|
|
306
|
+
return { action: "delegate", reason: "failure-event" };
|
|
307
|
+
}
|
|
308
|
+
|
|
270
309
|
// Q3/Q4 — plan approval (G2) + subagent tracking live in Python.
|
|
271
310
|
if (manifest.tools.post_delegate_always.includes(toolName)) {
|
|
272
311
|
return { action: "delegate", reason: "post-stateful-tool" };
|
|
273
312
|
}
|
|
274
313
|
|
|
275
314
|
// Q5 — error path: gotchas, violations, cognition capture, metrics.
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}
|
|
280
|
-
const toolOutput = String(payload.tool_output ?? "");
|
|
281
|
-
if (compileRegex(manifest.post.error_trigger).test(toolOutput)) {
|
|
315
|
+
// PostToolUse never carries an exit code (failures fire the event
|
|
316
|
+
// above), so the only error signal on this path is trigger text.
|
|
317
|
+
if (compileRegex(manifest.post.error_trigger).test(toolText(payload))) {
|
|
282
318
|
return { action: "delegate", reason: "error-trigger" };
|
|
283
319
|
}
|
|
284
320
|
|
|
@@ -333,5 +369,6 @@ module.exports = {
|
|
|
333
369
|
pythonTruthy,
|
|
334
370
|
readJsonFile,
|
|
335
371
|
safeSessionId,
|
|
372
|
+
toolText,
|
|
336
373
|
validateManifest,
|
|
337
374
|
};
|
|
@@ -635,13 +635,9 @@
|
|
|
635
635
|
"flags": "i",
|
|
636
636
|
"js": "(error:|fatal:|exception:|failed|ENOENT|EACCES|EPERM|panic:)",
|
|
637
637
|
"py": "(error:|fatal:|exception:|failed|ENOENT|EACCES|EPERM|panic:)"
|
|
638
|
-
}
|
|
639
|
-
"success_exit_codes": [
|
|
640
|
-
"0",
|
|
641
|
-
""
|
|
642
|
-
]
|
|
638
|
+
}
|
|
643
639
|
},
|
|
644
|
-
"schema_version":
|
|
640
|
+
"schema_version": 2,
|
|
645
641
|
"session_id": {
|
|
646
642
|
"flags": "",
|
|
647
643
|
"js": "^[A-Za-z0-9._-]{1,128}$",
|
|
@@ -6,8 +6,16 @@
|
|
|
6
6
|
# which mistakes repeat across projects.
|
|
7
7
|
#
|
|
8
8
|
# Contract:
|
|
9
|
-
# - Reads a hook payload JSON from stdin
|
|
10
|
-
#
|
|
9
|
+
# - Reads a hook payload JSON from stdin. Registered for two events
|
|
10
|
+
# (2.1.220 contract): PostToolUse delivers a structured tool_response
|
|
11
|
+
# (Bash dicts carry stdout/stderr; this port handles the string and
|
|
12
|
+
# stdout/stderr shapes only — no Task/content path); a failing tool
|
|
13
|
+
# throws inside the runtime and fires PostToolUseFailure instead,
|
|
14
|
+
# which carries the output in `error`, prefixed "Exit code N" for
|
|
15
|
+
# Bash. The payload has no top-level output or exit-code fields.
|
|
16
|
+
# Also read: tool_name, cwd, session_id, and a legacy
|
|
17
|
+
# assistant_message the payload never carries (dead read, see
|
|
18
|
+
# core/hooks/post_tool_use.py `_confirm_flow_authorization`).
|
|
11
19
|
# - Always exits 0 and writes `{}` on stdout — PostToolUse does not inject
|
|
12
20
|
# context.
|
|
13
21
|
# - Side effect: updates %USERPROFILE%\.arkaos\gotchas.json and
|
|
@@ -100,7 +108,7 @@ $stdinText = [Console]::In.ReadToEnd()
|
|
|
100
108
|
$shouldProcessGotchas = $true
|
|
101
109
|
$payload = $null
|
|
102
110
|
$toolName = ''
|
|
103
|
-
$
|
|
111
|
+
$toolText = ''
|
|
104
112
|
$exitCode = '0'
|
|
105
113
|
$cwd = ''
|
|
106
114
|
|
|
@@ -116,10 +124,37 @@ if ([string]::IsNullOrWhiteSpace($stdinText)) {
|
|
|
116
124
|
|
|
117
125
|
if ($shouldProcessGotchas -and $null -ne $payload) {
|
|
118
126
|
$toolName = if ($null -ne $payload.tool_name) { [string]$payload.tool_name } else { '' }
|
|
119
|
-
$toolOutput = if ($null -ne $payload.tool_output) { [string]$payload.tool_output } else { '' }
|
|
120
|
-
$exitCode = if ($null -ne $payload.exit_code) { [string]$payload.exit_code } else { '0' }
|
|
121
127
|
$cwd = if ($null -ne $payload.cwd) { [string]$payload.cwd } else { '' }
|
|
122
128
|
|
|
129
|
+
# Event → (text, exit code). Mirrors the string and stdout/stderr
|
|
130
|
+
# paths of _tool_text / _tool_exit_code in core/hooks/post_tool_use.py
|
|
131
|
+
# (no Task/content path in this port). On PostToolUseFailure the
|
|
132
|
+
# output arrives in `error`; the runtime's "Exit code N" envelope is
|
|
133
|
+
# its wrapper, not command output, so it is stripped from the text
|
|
134
|
+
# and surfaced as the exit code. A user interrupt is not a failure.
|
|
135
|
+
$eventName = if ($null -ne $payload.hook_event_name) { [string]$payload.hook_event_name } else { '' }
|
|
136
|
+
if ($eventName -eq 'PostToolUseFailure') {
|
|
137
|
+
$errText = if ($null -ne $payload.error) { [string]$payload.error } else { '' }
|
|
138
|
+
$envMatch = [regex]::Match($errText, '^Exit code (\d+)\n?')
|
|
139
|
+
if ($envMatch.Success) {
|
|
140
|
+
$exitCode = $envMatch.Groups[1].Value
|
|
141
|
+
$toolText = $errText.Substring($envMatch.Length)
|
|
142
|
+
} else {
|
|
143
|
+
$exitCode = '1'
|
|
144
|
+
$toolText = $errText
|
|
145
|
+
}
|
|
146
|
+
if ($payload.is_interrupt) { $exitCode = '0' }
|
|
147
|
+
} else {
|
|
148
|
+
$resp = $payload.tool_response
|
|
149
|
+
if ($resp -is [string]) {
|
|
150
|
+
$toolText = $resp
|
|
151
|
+
} elseif ($null -ne $resp) {
|
|
152
|
+
$stdoutPart = if ($null -ne $resp.stdout) { [string]$resp.stdout } else { '' }
|
|
153
|
+
$stderrPart = if ($null -ne $resp.stderr) { [string]$resp.stderr } else { '' }
|
|
154
|
+
$toolText = (@($stdoutPart, $stderrPart) | Where-Object { $_ }) -join "`n"
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
123
158
|
# --- Flow marker cache write (v2 ALLOW accelerator) ------------------
|
|
124
159
|
# Mirror of the bash hook: detect [arka:routing] or [arka:trivial] in
|
|
125
160
|
# $payload.assistant_message and persist via core.workflow.marker_cache.
|
|
@@ -211,7 +246,7 @@ except Exception:
|
|
|
211
246
|
# ─── Only process when there is actually an error ─────────────────
|
|
212
247
|
$errorPattern = '(?i)(error:|fatal:|exception:|failed|ENOENT|EACCES|EPERM|panic:)'
|
|
213
248
|
if ($exitCode -eq '0' -or [string]::IsNullOrEmpty($exitCode)) {
|
|
214
|
-
if ($
|
|
249
|
+
if ($toolText -notmatch $errorPattern) {
|
|
215
250
|
$shouldProcessGotchas = $false
|
|
216
251
|
}
|
|
217
252
|
}
|
|
@@ -230,7 +265,7 @@ $null = New-Item -ItemType Directory -Force -Path $arkaosRuntimeDir -ErrorAction
|
|
|
230
265
|
if ($shouldProcessGotchas) { do {
|
|
231
266
|
|
|
232
267
|
# ─── Extract first meaningful error line ─────────────────────────────
|
|
233
|
-
$lines = $
|
|
268
|
+
$lines = $toolText -split "`r?`n"
|
|
234
269
|
$errorLineRegex = '(?i)(error|fatal|exception|failed|ENOENT|EACCES|EPERM|panic|cannot|not found|permission denied)'
|
|
235
270
|
$errorLine = $null
|
|
236
271
|
foreach ($ln in $lines) {
|
|
@@ -36,6 +36,7 @@ import time
|
|
|
36
36
|
from dataclasses import asdict, dataclass, field, replace
|
|
37
37
|
from pathlib import Path
|
|
38
38
|
|
|
39
|
+
from core.governance.qg_digest import evidence_digest
|
|
39
40
|
from core.shared.test_evidence import coverage_percent_from_xml
|
|
40
41
|
|
|
41
42
|
TIMEOUT_SECONDS = 300
|
|
@@ -119,11 +120,16 @@ class EvidenceReport:
|
|
|
119
120
|
results: list[CheckResult] = field(default_factory=list)
|
|
120
121
|
|
|
121
122
|
def to_dict(self) -> dict:
|
|
122
|
-
|
|
123
|
+
payload = {
|
|
123
124
|
"project_dir": self.project_dir,
|
|
124
125
|
"overall": self.overall,
|
|
125
126
|
"results": [asdict(r) for r in self.results],
|
|
126
127
|
}
|
|
128
|
+
# PR-B2: the digest excludes its own key, so recomputing over
|
|
129
|
+
# the embedded dict reproduces it — reviewers can quote it and
|
|
130
|
+
# the aggregate verifies it once PR-B3 wires the check.
|
|
131
|
+
payload["report_digest"] = evidence_digest(payload)
|
|
132
|
+
return payload
|
|
127
133
|
|
|
128
134
|
|
|
129
135
|
# ─── Subprocess plumbing ────────────────────────────────────────────────
|
package/core/governance/judge.py
CHANGED
|
@@ -24,6 +24,7 @@ it.
|
|
|
24
24
|
|
|
25
25
|
from __future__ import annotations
|
|
26
26
|
|
|
27
|
+
import re
|
|
27
28
|
from typing import Literal
|
|
28
29
|
|
|
29
30
|
from pydantic import BaseModel, Field, model_validator
|
|
@@ -69,12 +70,22 @@ class JudgeVerdict(BaseModel):
|
|
|
69
70
|
max_length=120,
|
|
70
71
|
description="Opening excerpt of the judged artifact (auditability)",
|
|
71
72
|
)
|
|
73
|
+
# PR-B2: a real content hash alongside the prose excerpt above —
|
|
74
|
+
# judged_digest stays an excerpt (not repurposed); judged_sha256 is
|
|
75
|
+
# the verifiable one. Optional so existing verdicts stay valid.
|
|
76
|
+
judged_sha256: str = Field(
|
|
77
|
+
default="",
|
|
78
|
+
description=(
|
|
79
|
+
"sha256 of the judged artifact's full text; empty when the "
|
|
80
|
+
"caller did not hash it"
|
|
81
|
+
),
|
|
82
|
+
)
|
|
72
83
|
reviewer: str = Field(description="Judge id, e.g. plan-judge-g2")
|
|
73
84
|
model_used: str = Field(description="Model tier the judgment ran on")
|
|
74
85
|
notes: str = ""
|
|
75
86
|
|
|
76
87
|
@model_validator(mode="after")
|
|
77
|
-
def revise_requires_actionable_findings(self) ->
|
|
88
|
+
def revise_requires_actionable_findings(self) -> JudgeVerdict:
|
|
78
89
|
"""REVISE must cite at least one blocker/major finding —
|
|
79
90
|
a judge cannot loop work back on narrative alone."""
|
|
80
91
|
if self.verdict == "REVISE":
|
|
@@ -91,5 +102,16 @@ class JudgeVerdict(BaseModel):
|
|
|
91
102
|
)
|
|
92
103
|
return self
|
|
93
104
|
|
|
105
|
+
@model_validator(mode="after")
|
|
106
|
+
def judged_sha256_is_hex_or_empty(self) -> JudgeVerdict:
|
|
107
|
+
if self.judged_sha256 and not re.fullmatch(
|
|
108
|
+
r"[0-9a-f]{64}", self.judged_sha256
|
|
109
|
+
):
|
|
110
|
+
raise ValueError(
|
|
111
|
+
"judged_sha256 must be 64 lowercase hex chars or empty, "
|
|
112
|
+
f"got {self.judged_sha256!r}"
|
|
113
|
+
)
|
|
114
|
+
return self
|
|
115
|
+
|
|
94
116
|
|
|
95
117
|
JUDGE_VERDICT_JSON_SCHEMA: dict = JudgeVerdict.model_json_schema()
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Integrity digests for the Quality Gate chain (PR-B2).
|
|
2
|
+
|
|
3
|
+
Two digest primitives supply the material for proving — once PR-B3
|
|
4
|
+
wires the verification that consumes them — that reviewers and
|
|
5
|
+
verdict refer to the same evidence report:
|
|
6
|
+
|
|
7
|
+
- ``evidence_digest`` — hashes an EvidenceReport dict.
|
|
8
|
+
- ``verdict_digest`` — hashes a QGVerdict dict.
|
|
9
|
+
|
|
10
|
+
Both return a 64-char lowercase sha256 hex string over canonical JSON
|
|
11
|
+
(sorted keys, compact separators): key order and formatting never
|
|
12
|
+
change a digest; a content change does — except under the
|
|
13
|
+
``report_digest`` key, which ``evidence_digest`` excludes by design so
|
|
14
|
+
the digest is stable whether computed before or after embedding.
|
|
15
|
+
|
|
16
|
+
A working-tree digest deliberately does NOT live here. Three designs
|
|
17
|
+
of a ``tree_digest`` were driven through six wrong-digest routes
|
|
18
|
+
over ten QG rounds, each reproduced at the gate before acceptance;
|
|
19
|
+
``docs/adr/2026-07-29-tree-digest-corpus.md`` records the corpus
|
|
20
|
+
durably and is the acceptance spec for the dedicated PR.
|
|
21
|
+
``QGVerdict.tree_digest`` is enforced empty until that primitive
|
|
22
|
+
exists and survives its own gate.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import hashlib
|
|
28
|
+
import json
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _sha256(data: bytes) -> str:
|
|
32
|
+
return hashlib.sha256(data).hexdigest()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _canonical(payload: dict) -> bytes:
|
|
36
|
+
"""Canonical JSON: sorted keys, compact separators, UTF-8-encoded
|
|
37
|
+
(surrogatepass, so surrogate-bearing strings serialize rather than
|
|
38
|
+
raise).
|
|
39
|
+
|
|
40
|
+
Key order and formatting never change the digest; content does.
|
|
41
|
+
``allow_nan=False``: NaN/Infinity are invalid JSON and would make
|
|
42
|
+
the canonical form implementation-defined — they raise instead.
|
|
43
|
+
Other non-serializable values raise too, on purpose — a digest
|
|
44
|
+
over a lossy repr would silently stop meaning anything.
|
|
45
|
+
"""
|
|
46
|
+
return json.dumps(
|
|
47
|
+
payload, sort_keys=True, separators=(",", ":"),
|
|
48
|
+
ensure_ascii=False, allow_nan=False,
|
|
49
|
+
).encode("utf-8", "surrogatepass")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def evidence_digest(report: dict) -> str:
|
|
53
|
+
"""Digest of an EvidenceReport dict, canonically normalized.
|
|
54
|
+
|
|
55
|
+
The report's own ``report_digest`` key (if present) is excluded so
|
|
56
|
+
the digest is stable whether computed before or after embedding.
|
|
57
|
+
"""
|
|
58
|
+
payload = {k: v for k, v in report.items() if k != "report_digest"}
|
|
59
|
+
return _sha256(_canonical(payload))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def verdict_digest(verdict: dict) -> str:
|
|
63
|
+
"""Digest of a QGVerdict dict, canonically normalized."""
|
|
64
|
+
return _sha256(_canonical(verdict))
|
|
@@ -15,10 +15,13 @@ schema when dispatching reviewers via the Agent tool.
|
|
|
15
15
|
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
|
+
import re
|
|
18
19
|
from typing import Literal
|
|
19
20
|
|
|
20
21
|
from pydantic import BaseModel, Field, model_validator
|
|
21
22
|
|
|
23
|
+
_SHA256_HEX_RE = re.compile(r"[0-9a-f]{64}")
|
|
24
|
+
|
|
22
25
|
|
|
23
26
|
class QGBlocker(BaseModel):
|
|
24
27
|
"""One concrete issue that blocks approval."""
|
|
@@ -66,9 +69,55 @@ class QGVerdict(BaseModel):
|
|
|
66
69
|
"evidence is insufficient"
|
|
67
70
|
),
|
|
68
71
|
)
|
|
72
|
+
# PR-B2 integrity digests — optional so the pre-B2 verdict corpus
|
|
73
|
+
# stays valid. evidence_digest and reviewer_output_sha256 must be
|
|
74
|
+
# real sha256 hex when present; tree_digest is RESERVED and
|
|
75
|
+
# rejects ANY value (see its description and the validator).
|
|
76
|
+
evidence_digest: str = Field(
|
|
77
|
+
default="",
|
|
78
|
+
description=(
|
|
79
|
+
"sha256 of the EvidenceReport the reviewer interpreted "
|
|
80
|
+
"(report_digest from the --json output); empty when the "
|
|
81
|
+
"report carried none"
|
|
82
|
+
),
|
|
83
|
+
)
|
|
84
|
+
tree_digest: str = Field(
|
|
85
|
+
default="",
|
|
86
|
+
description=(
|
|
87
|
+
"RESERVED — must be empty. The producing primitive will "
|
|
88
|
+
"ship in its own PR (acceptance spec: "
|
|
89
|
+
"docs/adr/2026-07-29-tree-digest-corpus.md); until it "
|
|
90
|
+
"exists, a non-empty value is rejected so no unverifiable "
|
|
91
|
+
"digest can enter the corpus."
|
|
92
|
+
),
|
|
93
|
+
)
|
|
94
|
+
reviewer_output_sha256: str = Field(
|
|
95
|
+
default="",
|
|
96
|
+
description=(
|
|
97
|
+
"sha256 of the reviewer's own raw output, as captured by the "
|
|
98
|
+
"ledger (raw_sha256); empty when not yet ledgered"
|
|
99
|
+
),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
@model_validator(mode="after")
|
|
103
|
+
def digests_are_sha256_hex_or_empty(self) -> QGVerdict:
|
|
104
|
+
for name in ("evidence_digest", "reviewer_output_sha256"):
|
|
105
|
+
value = getattr(self, name)
|
|
106
|
+
if value and not _SHA256_HEX_RE.fullmatch(value):
|
|
107
|
+
raise ValueError(
|
|
108
|
+
f"{name} must be 64 lowercase hex chars or empty, "
|
|
109
|
+
f"got {value!r}"
|
|
110
|
+
)
|
|
111
|
+
if self.tree_digest:
|
|
112
|
+
raise ValueError(
|
|
113
|
+
"tree_digest is RESERVED and must be empty until the "
|
|
114
|
+
"tree-digest primitive ships in its own PR — an "
|
|
115
|
+
"unverifiable digest must not enter the corpus"
|
|
116
|
+
)
|
|
117
|
+
return self
|
|
69
118
|
|
|
70
119
|
@model_validator(mode="after")
|
|
71
|
-
def enforce_evidence_floor(self) ->
|
|
120
|
+
def enforce_evidence_floor(self) -> QGVerdict:
|
|
72
121
|
"""APPROVED cannot coexist with failing evidence (evidence floor)."""
|
|
73
122
|
if self.verdict == "APPROVED" and self.evidence_report.overall == "fail":
|
|
74
123
|
raise ValueError(
|
|
@@ -338,8 +338,17 @@ def _build_record(
|
|
|
338
338
|
"raw_sha256": digest,
|
|
339
339
|
"verdict": verdict,
|
|
340
340
|
"parse_error": parse_error,
|
|
341
|
-
|
|
342
|
-
|
|
341
|
+
# Digest columns are harvested ONLY from a VALIDATED verdict:
|
|
342
|
+
# _validated is fail-soft (it keeps the raw dict so no review
|
|
343
|
+
# text is ever lost), but a value the validator rejected must
|
|
344
|
+
# not enter the corpus through these columns (QG r12 — a
|
|
345
|
+
# rejected tree_digest was reproduced landing on disk here).
|
|
346
|
+
"evidence_digest": (
|
|
347
|
+
None if parse_error else (verdict or {}).get("evidence_digest")
|
|
348
|
+
),
|
|
349
|
+
"tree_digest": (
|
|
350
|
+
None if parse_error else (verdict or {}).get("tree_digest")
|
|
351
|
+
),
|
|
343
352
|
"source": source,
|
|
344
353
|
"sanitized": sanitized,
|
|
345
354
|
}
|
|
@@ -34,7 +34,12 @@ import re
|
|
|
34
34
|
from dataclasses import asdict
|
|
35
35
|
from pathlib import Path
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
# v2: `post.success_exit_codes` removed (PostToolUse never carries an
|
|
38
|
+
# exit code; failures fire PostToolUseFailure, which the shim delegates
|
|
39
|
+
# outright). The bump makes an old engine reading a new manifest
|
|
40
|
+
# delegate everything instead of crashing into the shim's fail-open
|
|
41
|
+
# `{}` — a partial deploy must degrade to Python, never drop turns.
|
|
42
|
+
_SCHEMA_VERSION = 2
|
|
38
43
|
|
|
39
44
|
# Python-only regex constructs that would compile differently (or not at
|
|
40
45
|
# all) under JavaScript's RegExp. The generator refuses to export any
|
|
@@ -177,7 +182,7 @@ def _tool_sets() -> dict:
|
|
|
177
182
|
from core.hooks import pre_tool_use
|
|
178
183
|
from core.workflow import flow_enforcer, research_gate
|
|
179
184
|
|
|
180
|
-
if
|
|
185
|
+
if flow_enforcer.EFFECT_TOOLS_ALWAYS | {"Bash"} != pre_tool_use._FLOW_GATED_TOOLS:
|
|
181
186
|
raise ValueError(
|
|
182
187
|
"pre_tool_use._FLOW_GATED_TOOLS no longer equals "
|
|
183
188
|
"flow_enforcer.EFFECT_TOOLS_ALWAYS + Bash — re-derive the "
|
|
@@ -258,9 +263,9 @@ def _flags_and_budget() -> dict:
|
|
|
258
263
|
|
|
259
264
|
|
|
260
265
|
def _state_and_paths() -> dict:
|
|
266
|
+
from core.runtime import mcp_telemetry
|
|
261
267
|
from core.shared.temp_paths import arkaos_temp_dir
|
|
262
268
|
from core.workflow import flow_authorization, flow_enforcer, research_gate
|
|
263
|
-
from core.runtime import mcp_telemetry
|
|
264
269
|
|
|
265
270
|
auth_dir = arkaos_temp_dir("arkaos-flow-auth")
|
|
266
271
|
if str(auth_dir.parent) != "/tmp": # pragma: no cover — POSIX only
|
|
@@ -290,11 +295,11 @@ def _state_and_paths() -> dict:
|
|
|
290
295
|
|
|
291
296
|
def build_manifest() -> dict:
|
|
292
297
|
"""Assemble the manifest from the real gate constants (import-time)."""
|
|
293
|
-
from core.hooks import post_tool_use, pre_tool_use
|
|
298
|
+
from core.hooks import post_tool_use, pre_tool_use
|
|
294
299
|
from core.shared import safe_session_id as ssid
|
|
295
300
|
|
|
296
301
|
state = _state_and_paths()
|
|
297
|
-
if
|
|
302
|
+
if state["numbers"]["assistant_window"] != pre_tool_use._ASSISTANT_WINDOW:
|
|
298
303
|
raise ValueError("ASSISTANT_WINDOW diverged between hook and enforcer")
|
|
299
304
|
return {
|
|
300
305
|
"schema_version": _SCHEMA_VERSION,
|
|
@@ -312,9 +317,12 @@ def build_manifest() -> dict:
|
|
|
312
317
|
],
|
|
313
318
|
"tools": _tool_sets(),
|
|
314
319
|
"bash": _bash_classifier(),
|
|
320
|
+
# No exit-code corpus: PostToolUse never carries one (a failing
|
|
321
|
+
# tool throws and fires PostToolUseFailure, which the shim
|
|
322
|
+
# delegates outright), so trigger text is the only error signal
|
|
323
|
+
# the fast path needs.
|
|
315
324
|
"post": {
|
|
316
325
|
"error_trigger": _js_pattern(post_tool_use._ERROR_TRIGGER_RE),
|
|
317
|
-
"success_exit_codes": ["0", ""],
|
|
318
326
|
},
|
|
319
327
|
"session_id": {
|
|
320
328
|
**_js_pattern(ssid.SAFE_SESSION_ID_RE),
|
|
@@ -144,8 +144,8 @@ def _confirm_flow_authorization(session_id: str, transcript_path: str) -> None:
|
|
|
144
144
|
# ─── Section 2+3: CQO verdicts, pattern stubs, activation ───────────────
|
|
145
145
|
|
|
146
146
|
|
|
147
|
-
def _record_cqo_rejected(
|
|
148
|
-
if not _REJECTED_RE.search(
|
|
147
|
+
def _record_cqo_rejected(tool_text: str, prompt: str, session_id: str) -> None:
|
|
148
|
+
if not _REJECTED_RE.search(tool_text):
|
|
149
149
|
return
|
|
150
150
|
match = _REVIEWING_RE.search(prompt)
|
|
151
151
|
if not match:
|
|
@@ -153,7 +153,7 @@ def _record_cqo_rejected(tool_output: str, prompt: str, session_id: str) -> None
|
|
|
153
153
|
try:
|
|
154
154
|
from core.governance.cqo_experience_recorder import record_from_verdict
|
|
155
155
|
record_from_verdict(
|
|
156
|
-
verdict_text=
|
|
156
|
+
verdict_text=tool_text,
|
|
157
157
|
agent_id=match.group(1),
|
|
158
158
|
session_id=session_id,
|
|
159
159
|
context="auto-recorded via PostToolUse hook (cqo dispatch REJECTED)",
|
|
@@ -162,8 +162,8 @@ def _record_cqo_rejected(tool_output: str, prompt: str, session_id: str) -> None
|
|
|
162
162
|
pass
|
|
163
163
|
|
|
164
164
|
|
|
165
|
-
def _record_pattern_stub(
|
|
166
|
-
if not _APPROVED_RE.search(
|
|
165
|
+
def _record_pattern_stub(tool_text: str, prompt: str) -> None:
|
|
166
|
+
if not _APPROVED_RE.search(tool_text):
|
|
167
167
|
return
|
|
168
168
|
match = _PATTERN_SUGGEST_RE.search(prompt)
|
|
169
169
|
if not match:
|
|
@@ -227,6 +227,59 @@ def _task_result_text(stdin_json: dict) -> str:
|
|
|
227
227
|
return ""
|
|
228
228
|
|
|
229
229
|
|
|
230
|
+
# A failing tool THROWS inside the runtime and fires PostToolUseFailure
|
|
231
|
+
# — PostToolUse never sees it (proven live against 2.1.220: a logger
|
|
232
|
+
# registered on both events fired only PostToolUseFailure for failing
|
|
233
|
+
# tools; the binary's dispatcher reaches the failure event from its
|
|
234
|
+
# catch block). On that event the output arrives in `error`, prefixed
|
|
235
|
+
# with this envelope for Bash ("Exit code N\n<output>") and as a plain
|
|
236
|
+
# message for other tools. The envelope is the runtime's wrapper, not
|
|
237
|
+
# tool output.
|
|
238
|
+
_EXIT_ENVELOPE_RE = re.compile(r"^Exit code (\d+)\n?")
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _tool_text(stdin_json: dict) -> str:
|
|
242
|
+
"""Textual output of the tool call under the two-event contract.
|
|
243
|
+
|
|
244
|
+
PostToolUseFailure carries it in ``error`` (envelope stripped here,
|
|
245
|
+
surfaced by ``_tool_exit_code``). PostToolUse only ever delivers a
|
|
246
|
+
structured ``tool_response`` — Bash dicts carry stdout/stderr, Task
|
|
247
|
+
dicts carry content; a plain string passes through defensively.
|
|
248
|
+
"""
|
|
249
|
+
if get_str(stdin_json, "hook_event_name") == "PostToolUseFailure":
|
|
250
|
+
error = get_str(stdin_json, "error")
|
|
251
|
+
return _EXIT_ENVELOPE_RE.sub("", error, count=1)
|
|
252
|
+
response = stdin_json.get("tool_response")
|
|
253
|
+
if isinstance(response, str):
|
|
254
|
+
return response
|
|
255
|
+
if isinstance(response, dict) and (
|
|
256
|
+
"stdout" in response or "stderr" in response
|
|
257
|
+
):
|
|
258
|
+
parts = (
|
|
259
|
+
str(response.get(key) or "") for key in ("stdout", "stderr")
|
|
260
|
+
)
|
|
261
|
+
return "\n".join(part for part in parts if part)
|
|
262
|
+
return _task_result_text(stdin_json)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _tool_exit_code(stdin_json: dict) -> str:
|
|
266
|
+
"""Exit code derived from the event, not from a payload field.
|
|
267
|
+
|
|
268
|
+
Neither event carries an exit_code field. PostToolUseFailure is a
|
|
269
|
+
failure by definition: a leading ``Exit code N`` line in ``error``
|
|
270
|
+
yields N, any other message maps to "1" — except a user interrupt
|
|
271
|
+
(``is_interrupt``), which is not a command failure. PostToolUse is
|
|
272
|
+
always "0"; error text on successful turns is caught downstream by
|
|
273
|
+
the error-trigger scan.
|
|
274
|
+
"""
|
|
275
|
+
if get_str(stdin_json, "hook_event_name") != "PostToolUseFailure":
|
|
276
|
+
return "0"
|
|
277
|
+
if stdin_json.get("is_interrupt"):
|
|
278
|
+
return "0"
|
|
279
|
+
match = _EXIT_ENVELOPE_RE.match(get_str(stdin_json, "error"))
|
|
280
|
+
return match.group(1) if match else "1"
|
|
281
|
+
|
|
282
|
+
|
|
230
283
|
def _record_reviewer_output(
|
|
231
284
|
subagent_type: str, stdin_json: dict, session_id: str
|
|
232
285
|
) -> None:
|
|
@@ -261,8 +314,8 @@ def _record_activation(subagent_type: str, session_id: str) -> None:
|
|
|
261
314
|
# ─── Section 5: gotchas memory ───────────────────────────────────────────
|
|
262
315
|
|
|
263
316
|
|
|
264
|
-
def _extract_error_line(
|
|
265
|
-
lines =
|
|
317
|
+
def _extract_error_line(tool_text: str) -> str:
|
|
318
|
+
lines = tool_text.splitlines()
|
|
266
319
|
for line in lines:
|
|
267
320
|
if _ERROR_LINE_RE.search(line):
|
|
268
321
|
return line
|
|
@@ -425,16 +478,16 @@ def _detect_rule_violations(input_data: dict) -> tuple[str, list[tuple]]:
|
|
|
425
478
|
if state is None:
|
|
426
479
|
return "", []
|
|
427
480
|
tool_name = get_str(input_data, "tool_name")
|
|
428
|
-
|
|
481
|
+
tool_text = _tool_text(input_data)
|
|
429
482
|
violation_msg = ""
|
|
430
483
|
persist: list[tuple] = []
|
|
431
484
|
|
|
432
485
|
if tool_name == "Bash":
|
|
433
486
|
on_master = any(
|
|
434
487
|
re.match(r"^\[(master|main)", line)
|
|
435
|
-
for line in
|
|
488
|
+
for line in tool_text.splitlines()
|
|
436
489
|
)
|
|
437
|
-
cmd_text = get_str(input_data, "command")
|
|
490
|
+
cmd_text = get_str(input_data, "tool_input", "command")
|
|
438
491
|
if on_master and re.search(r"git commit", cmd_text):
|
|
439
492
|
persist.append((
|
|
440
493
|
"branch-isolation",
|
|
@@ -446,7 +499,7 @@ def _detect_rule_violations(input_data: dict) -> tuple[str, list[tuple]]:
|
|
|
446
499
|
)
|
|
447
500
|
|
|
448
501
|
if tool_name in ("Write", "Edit"):
|
|
449
|
-
file_path = get_str(input_data, "file_path")
|
|
502
|
+
file_path = get_str(input_data, "tool_input", "file_path")
|
|
450
503
|
if _CODE_FILE_RE.search(file_path):
|
|
451
504
|
if _phase_status(state, "spec") != "completed":
|
|
452
505
|
persist.append((
|
|
@@ -507,11 +560,13 @@ def _enforcer_messages(input_data, enforce_tool, add_violation, msg: str) -> str
|
|
|
507
560
|
except Exception:
|
|
508
561
|
extra["git_branch"] = ""
|
|
509
562
|
try:
|
|
563
|
+
# user_input is a prompt-side enforcer parameter with no source
|
|
564
|
+
# in either post-tool payload (2.1.220 contract).
|
|
510
565
|
result = enforce_tool(
|
|
511
566
|
tool_name=tool_name,
|
|
512
|
-
command=get_str(input_data, "command"),
|
|
513
|
-
file_path=get_str(input_data, "file_path"),
|
|
514
|
-
user_input=
|
|
567
|
+
command=get_str(input_data, "tool_input", "command"),
|
|
568
|
+
file_path=get_str(input_data, "tool_input", "file_path"),
|
|
569
|
+
user_input="",
|
|
515
570
|
**extra,
|
|
516
571
|
)
|
|
517
572
|
except Exception:
|
|
@@ -543,7 +598,7 @@ def _forge_violation(input_data: dict, yaml_mod) -> str:
|
|
|
543
598
|
forge_file = Path.home() / ".arkaos" / "plans" / f"{forge_id}.yaml"
|
|
544
599
|
if not forge_file.is_file():
|
|
545
600
|
return ""
|
|
546
|
-
edited = get_str(input_data, "file_path")
|
|
601
|
+
edited = get_str(input_data, "tool_input", "file_path")
|
|
547
602
|
if not edited:
|
|
548
603
|
return ""
|
|
549
604
|
try:
|
|
@@ -601,8 +656,8 @@ def _workflow_sections_with_fallback(
|
|
|
601
656
|
# ─── Section 9: cognition capture (detached, fire-and-forget) ───────────
|
|
602
657
|
|
|
603
658
|
|
|
604
|
-
def _enqueue_cognition_capture(session_id: str,
|
|
605
|
-
if not session_id or not
|
|
659
|
+
def _enqueue_cognition_capture(session_id: str, tool_text: str) -> None:
|
|
660
|
+
if not session_id or not tool_text:
|
|
606
661
|
return
|
|
607
662
|
repo = repo_path()
|
|
608
663
|
if not repo or not Path(repo).is_dir():
|
|
@@ -619,7 +674,7 @@ def _enqueue_cognition_capture(session_id: str, tool_output: str) -> None:
|
|
|
619
674
|
)
|
|
620
675
|
if proc.stdin is not None:
|
|
621
676
|
try:
|
|
622
|
-
proc.stdin.write(
|
|
677
|
+
proc.stdin.write(tool_text.encode("utf-8", "replace"))
|
|
623
678
|
proc.stdin.close()
|
|
624
679
|
except (BrokenPipeError, OSError):
|
|
625
680
|
pass
|
|
@@ -672,8 +727,8 @@ def main(stdin_json: dict | None = None) -> int:
|
|
|
672
727
|
ensure_root_on_path(root)
|
|
673
728
|
|
|
674
729
|
tool_name = get_str(stdin_json, "tool_name")
|
|
675
|
-
|
|
676
|
-
exit_code =
|
|
730
|
+
tool_text = _tool_text(stdin_json)
|
|
731
|
+
exit_code = _tool_exit_code(stdin_json)
|
|
677
732
|
cwd = get_str(stdin_json, "cwd")
|
|
678
733
|
session_id = get_str(stdin_json, "session_id")
|
|
679
734
|
transcript_path = get_str(stdin_json, "transcript_path")
|
|
@@ -717,17 +772,17 @@ def main(stdin_json: dict | None = None) -> int:
|
|
|
717
772
|
_record_reviewer_output(subagent_type, stdin_json, session_id)
|
|
718
773
|
if subagent_type in _CQO_IDS:
|
|
719
774
|
prompt = get_str(stdin_json, "tool_input", "prompt")
|
|
720
|
-
_record_cqo_rejected(
|
|
721
|
-
_record_pattern_stub(
|
|
775
|
+
_record_cqo_rejected(tool_text, prompt, session_id)
|
|
776
|
+
_record_pattern_stub(tool_text, prompt)
|
|
722
777
|
_record_activation(subagent_type, session_id)
|
|
723
778
|
|
|
724
779
|
# Only process further if there was an error signal (same early exit
|
|
725
780
|
# as the bash version — violations/metrics only run on error turns).
|
|
726
|
-
if exit_code in ("0", "") and not _ERROR_TRIGGER_RE.search(
|
|
781
|
+
if exit_code in ("0", "") and not _ERROR_TRIGGER_RE.search(tool_text):
|
|
727
782
|
print("{}")
|
|
728
783
|
return 0
|
|
729
784
|
|
|
730
|
-
error_line = _extract_error_line(
|
|
785
|
+
error_line = _extract_error_line(tool_text)
|
|
731
786
|
if not error_line:
|
|
732
787
|
print("{}")
|
|
733
788
|
return 0
|
|
@@ -746,11 +801,17 @@ def main(stdin_json: dict | None = None) -> int:
|
|
|
746
801
|
stdin_json, root, persist, violation_msg
|
|
747
802
|
)
|
|
748
803
|
|
|
749
|
-
_enqueue_cognition_capture(session_id,
|
|
804
|
+
_enqueue_cognition_capture(session_id, tool_text)
|
|
750
805
|
_log_metrics(int((time.monotonic() - start) * 1000))
|
|
751
806
|
|
|
752
807
|
if violation_msg:
|
|
753
|
-
|
|
808
|
+
# The runtime drops context whose hookEventName differs from the
|
|
809
|
+
# event it invoked (live probe, QG round 3) — echo the payload's
|
|
810
|
+
# own event so violations on failure turns are delivered.
|
|
811
|
+
emit_additional_context(
|
|
812
|
+
get_str(stdin_json, "hook_event_name") or "PostToolUse",
|
|
813
|
+
violation_msg,
|
|
814
|
+
)
|
|
754
815
|
else:
|
|
755
816
|
print("{}")
|
|
756
817
|
return 0
|
package/harness/codex/AGENTS.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ArkaOS — The Operating System for AI Agent Teams
|
|
2
2
|
|
|
3
|
-
> v4.
|
|
3
|
+
> v4.41.0 — 89 agents, 17 departments, 332 skills. Generated by `scripts/harness_gen.py`; do not edit.
|
|
4
4
|
|
|
5
5
|
You are operating within ArkaOS. Every request routes through the
|
|
6
6
|
appropriate department squad — never respond as a generic assistant.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ArkaOS — The Operating System for AI Agent Teams
|
|
2
2
|
|
|
3
|
-
> v4.
|
|
3
|
+
> v4.41.0 — 89 agents, 17 departments, 332 skills. Generated by `scripts/harness_gen.py`; do not edit.
|
|
4
4
|
|
|
5
5
|
You are operating within ArkaOS. Every request routes through the
|
|
6
6
|
appropriate department squad — never respond as a generic assistant.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: ArkaOS v4.
|
|
2
|
+
description: ArkaOS v4.41.0 agent-team contract
|
|
3
3
|
alwaysApply: true
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# ArkaOS — The Operating System for AI Agent Teams
|
|
7
7
|
|
|
8
|
-
> v4.
|
|
8
|
+
> v4.41.0 — 89 agents, 17 departments, 332 skills. Generated by `scripts/harness_gen.py`; do not edit.
|
|
9
9
|
|
|
10
10
|
You are operating within ArkaOS. Every request routes through the
|
|
11
11
|
appropriate department squad — never respond as a generic assistant.
|
package/harness/gemini/GEMINI.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ArkaOS — The Operating System for AI Agent Teams
|
|
2
2
|
|
|
3
|
-
> v4.
|
|
3
|
+
> v4.41.0 — 89 agents, 17 departments, 332 skills. Generated by `scripts/harness_gen.py`; do not edit.
|
|
4
4
|
|
|
5
5
|
You are operating within ArkaOS. Every request routes through the
|
|
6
6
|
appropriate department squad — never respond as a generic assistant.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ArkaOS — The Operating System for AI Agent Teams
|
|
2
2
|
|
|
3
|
-
> v4.
|
|
3
|
+
> v4.41.0 — 89 agents, 17 departments, 332 skills. Generated by `scripts/harness_gen.py`; do not edit.
|
|
4
4
|
|
|
5
5
|
You are operating within ArkaOS. Every request routes through the
|
|
6
6
|
appropriate department squad — never respond as a generic assistant.
|
|
@@ -3,7 +3,7 @@ description: "Software Architect — ArkaOS /dev department"
|
|
|
3
3
|
mode: subagent
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are Gabriel, Software Architect of the ArkaOS /dev department (v4.
|
|
6
|
+
You are Gabriel, Software Architect of the ArkaOS /dev department (v4.41.0; generated by scripts/harness_gen.py — do not edit).
|
|
7
7
|
|
|
8
8
|
Expertise: system design, system visualization via dev/diagram (architecture + dataflow diagrams delivered as browser artifacts), domain modeling (event storming, bounded contexts), design patterns (GoF, PoEAA), business / domain analysis, API design.
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ description: "Creative Director — ArkaOS /brand department"
|
|
|
3
3
|
mode: subagent
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are Valentina, Creative Director of the ArkaOS /brand department (v4.
|
|
6
|
+
You are Valentina, Creative Director of the ArkaOS /brand department (v4.41.0; generated by scripts/harness_gen.py — do not edit).
|
|
7
7
|
|
|
8
8
|
Expertise: brand identity creation, reference-video visual analysis via dev/watch (complete frames + transcript — motion and art direction judged on evidence, never on screenshots), visual design direction, UX/UI strategy, design systems, brand voice & tone.
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ description: "Chief Financial Officer — ArkaOS /fin department"
|
|
|
3
3
|
mode: subagent
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are Helena, Chief Financial Officer of the ArkaOS /fin department (v4.
|
|
6
|
+
You are Helena, Chief Financial Officer of the ArkaOS /fin department (v4.41.0; generated by scripts/harness_gen.py — do not edit).
|
|
7
7
|
|
|
8
8
|
Expertise: financial planning & analysis, valuation & investment, unit economics & SaaS metrics, risk management & ERM, fundraising & cap tables, cash flow management.
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ description: "Chief of Staff & Governance Lead — ArkaOS /org department"
|
|
|
3
3
|
mode: subagent
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are Afonso, Chief of Staff & Governance Lead of the ArkaOS /org department (v4.
|
|
6
|
+
You are Afonso, Chief of Staff & Governance Lead of the ArkaOS /org department (v4.41.0; generated by scripts/harness_gen.py — do not edit).
|
|
7
7
|
|
|
8
8
|
Expertise: meeting cadence (daily/weekly/quarterly/annual), OKR & CFR orchestration cross-department, decision records & RACI, premortem / blameless postmortem rituals, governance, board & founder-CEO succession, strategic alignment & single-threaded leadership.
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ description: "Community Strategist — ArkaOS /community department"
|
|
|
3
3
|
mode: subagent
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are Beatriz, Community Strategist of the ArkaOS /community department (v4.
|
|
6
|
+
You are Beatriz, Community Strategist of the ArkaOS /community department (v4.41.0; generated by scripts/harness_gen.py — do not edit).
|
|
7
7
|
|
|
8
8
|
Expertise: community strategy & design, platform selection (Discord, Telegram, Skool, Circle), member onboarding & retention, monetization (membership, courses, coaching), gamification & engagement, niche communities (betting, AI, vertical).
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ description: "Content Strategist — ArkaOS /content department"
|
|
|
3
3
|
mode: subagent
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are Rafael, Content Strategist of the ArkaOS /content department (v4.
|
|
6
|
+
You are Rafael, Content Strategist of the ArkaOS /content department (v4.41.0; generated by scripts/harness_gen.py — do not edit).
|
|
7
7
|
|
|
8
8
|
Expertise: viral content design, reference-video analysis via dev/watch (frames + timestamped transcript before judging any video), hook writing & packaging, script structure, content operating systems, platform-specific optimization.
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ description: "Conversion Strategist — ArkaOS /landing department"
|
|
|
3
3
|
mode: subagent
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are Ines, Conversion Strategist of the ArkaOS /landing department (v4.
|
|
6
|
+
You are Ines, Conversion Strategist of the ArkaOS /landing department (v4.41.0; generated by scripts/harness_gen.py — do not edit).
|
|
7
7
|
|
|
8
8
|
Expertise: sales funnels, landing page optimization, offer creation, copywriting (direct response), launch sequences, affiliate marketing.
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ description: "Chief Operations Officer — ArkaOS /org department"
|
|
|
3
3
|
mode: subagent
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are Sofia, Chief Operations Officer of the ArkaOS /org department (v4.
|
|
6
|
+
You are Sofia, Chief Operations Officer of the ArkaOS /org department (v4.41.0; generated by scripts/harness_gen.py — do not edit).
|
|
7
7
|
|
|
8
8
|
Expertise: organizational design, process optimization, cross-department coordination, culture & team health, scaling operations, workflow automation.
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ description: "Copy & Language Director — ArkaOS /quality department"
|
|
|
3
3
|
mode: subagent
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are Eduardo, Copy & Language Director of the ArkaOS /quality department (v4.
|
|
6
|
+
You are Eduardo, Copy & Language Director of the ArkaOS /quality department (v4.41.0; generated by scripts/harness_gen.py — do not edit).
|
|
7
7
|
|
|
8
8
|
Expertise: spelling and grammar (EN, PT-PT, PT-BR, ES, FR), tone and voice consistency, AI pattern detection and removal, accentuation and orthography, copywriting quality, factual accuracy in text.
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ description: "Chief Quality Officer — ArkaOS /quality department"
|
|
|
3
3
|
mode: subagent
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are Marta, Chief Quality Officer of the ArkaOS /quality department (v4.
|
|
6
|
+
You are Marta, Chief Quality Officer of the ArkaOS /quality department (v4.41.0; generated by scripts/harness_gen.py — do not edit).
|
|
7
7
|
|
|
8
8
|
Expertise: quality assurance orchestration, cross-department quality standards, text quality (spelling, grammar, tone), technical quality (code, UX, data), compliance and audit.
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ description: "Chief Technology Officer — ArkaOS /dev department"
|
|
|
3
3
|
mode: subagent
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are Marco, Chief Technology Officer of the ArkaOS /dev department (v4.
|
|
6
|
+
You are Marco, Chief Technology Officer of the ArkaOS /dev department (v4.41.0; generated by scripts/harness_gen.py — do not edit).
|
|
7
7
|
|
|
8
8
|
Expertise: software architecture, system design, tech strategy, cloud infrastructure, ai/ml systems.
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ description: "Design Ops Lead — ArkaOS /brand department"
|
|
|
3
3
|
mode: subagent
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are Iris, Design Ops Lead of the ArkaOS /brand department (v4.
|
|
6
|
+
You are Iris, Design Ops Lead of the ArkaOS /brand department (v4.41.0; generated by scripts/harness_gen.py — do not edit).
|
|
7
7
|
|
|
8
8
|
Expertise: design tokens (JSON + CSS variables), component libraries (shadcn/ui, Radix, Headless UI), design system governance, figma → code pipelines, accessibility compliance (WCAG 2.2 AA/AAA), cross-platform tokenisation (Style Dictionary, Tailwind).
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ description: "E-Commerce Director — ArkaOS /ecom department"
|
|
|
3
3
|
mode: subagent
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are Ricardo, E-Commerce Director of the ArkaOS /ecom department (v4.
|
|
6
|
+
You are Ricardo, E-Commerce Director of the ArkaOS /ecom department (v4.41.0; generated by scripts/harness_gen.py — do not edit).
|
|
7
7
|
|
|
8
8
|
Expertise: e-commerce strategy, conversion optimization, marketplace operations, pricing strategy, fulfillment & logistics, email & retention.
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ description: "Knowledge Director — ArkaOS /kb department"
|
|
|
3
3
|
mode: subagent
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are Clara, Knowledge Director of the ArkaOS /kb department (v4.
|
|
6
|
+
You are Clara, Knowledge Director of the ArkaOS /kb department (v4.41.0; generated by scripts/harness_gen.py — do not edit).
|
|
7
7
|
|
|
8
8
|
Expertise: knowledge management, research methodology, persona building, content curation, taxonomy & ontology, Obsidian vault management.
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ description: "Leadership & People Director — ArkaOS /lead department"
|
|
|
3
3
|
mode: subagent
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are Rodrigo, Leadership & People Director of the ArkaOS /lead department (v4.
|
|
6
|
+
You are Rodrigo, Leadership & People Director of the ArkaOS /lead department (v4.41.0; generated by scripts/harness_gen.py — do not edit).
|
|
7
7
|
|
|
8
8
|
Expertise: team assessment & health, leadership development, hiring & onboarding, performance management, feedback & 1-on-1s, culture building.
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ description: "Marketing Director — ArkaOS /mkt department"
|
|
|
3
3
|
mode: subagent
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are Luna, Marketing Director of the ArkaOS /mkt department (v4.
|
|
6
|
+
You are Luna, Marketing Director of the ArkaOS /mkt department (v4.41.0; generated by scripts/harness_gen.py — do not edit).
|
|
7
7
|
|
|
8
8
|
Expertise: growth strategy, video-ad teardown via dev/watch (hook, pacing and spoken-copy evidence from frames + transcript), content marketing, SEO, paid acquisition, social media.
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ description: "Operations Lead — ArkaOS /ops department"
|
|
|
3
3
|
mode: subagent
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are Daniel, Operations Lead of the ArkaOS /ops department (v4.
|
|
6
|
+
You are Daniel, Operations Lead of the ArkaOS /ops department (v4.41.0; generated by scripts/harness_gen.py — do not edit).
|
|
7
7
|
|
|
8
8
|
Expertise: workflow automation (Zapier, Make, n8n), SOP/process visualization via dev/diagram (workflow + lifecycle diagrams for automations and runbooks), process mapping & optimization, SOP creation & management, bottleneck analysis, integration design.
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ description: "Product Manager — ArkaOS /pm department"
|
|
|
3
3
|
mode: subagent
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are Carolina, Product Manager of the ArkaOS /pm department (v4.
|
|
6
|
+
You are Carolina, Product Manager of the ArkaOS /pm department (v4.41.0; generated by scripts/harness_gen.py — do not edit).
|
|
7
7
|
|
|
8
8
|
Expertise: continuous product discovery (daily habit), deliverable visualization via dev/diagram (workflow diagrams so stakeholders see scope before build), weekly customer interviewing, dual-track agile (discovery + delivery), product risk assessment (value/usability/feasibility/viability), framing problems for empowered teams (not features).
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ description: "RevOps Lead — ArkaOS /saas department"
|
|
|
3
3
|
mode: subagent
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are Vicente, RevOps Lead of the ArkaOS /saas department (v4.
|
|
6
|
+
You are Vicente, RevOps Lead of the ArkaOS /saas department (v4.41.0; generated by scripts/harness_gen.py — do not edit).
|
|
7
7
|
|
|
8
8
|
Expertise: revenue operations (cross mkt + sales + CS), unified funnel & CRM hygiene, SLA MQL→SQL between marketing and sales, revenue metrics (LTV/CAC, NRR, payback), lead scoring & routing, commission & forecast modeling.
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ description: "SaaS Strategist — ArkaOS /saas department"
|
|
|
3
3
|
mode: subagent
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are Tiago, SaaS Strategist of the ArkaOS /saas department (v4.
|
|
6
|
+
You are Tiago, SaaS Strategist of the ArkaOS /saas department (v4.41.0; generated by scripts/harness_gen.py — do not edit).
|
|
7
7
|
|
|
8
8
|
Expertise: SaaS metrics & benchmarking, product-led growth, pricing strategy, customer success, micro-SaaS validation, go-to-market for SaaS.
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ description: "Sales Director — ArkaOS /sales department"
|
|
|
3
3
|
mode: subagent
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are Miguel, Sales Director of the ArkaOS /sales department (v4.
|
|
6
|
+
You are Miguel, Sales Director of the ArkaOS /sales department (v4.41.0; generated by scripts/harness_gen.py — do not edit).
|
|
7
7
|
|
|
8
8
|
Expertise: consultative selling, pipeline management, proposal writing, negotiation, discovery calls, deal qualification.
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ description: "Chief Strategist — ArkaOS /strat department"
|
|
|
3
3
|
mode: subagent
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are Tomas, Chief Strategist of the ArkaOS /strat department (v4.
|
|
6
|
+
You are Tomas, Chief Strategist of the ArkaOS /strat department (v4.41.0; generated by scripts/harness_gen.py — do not edit).
|
|
7
7
|
|
|
8
8
|
Expertise: competitive strategy, business-flow visualization via dev/diagram (architecture + dataflow diagrams of business models and value chains), market analysis, business model design, positioning, innovation strategy.
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ description: "Technical & UX Quality Director — ArkaOS /quality department"
|
|
|
3
3
|
mode: subagent
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are Francisca, Technical & UX Quality Director of the ArkaOS /quality department (v4.
|
|
6
|
+
You are Francisca, Technical & UX Quality Director of the ArkaOS /quality department (v4.41.0; generated by scripts/harness_gen.py — do not edit).
|
|
7
7
|
|
|
8
8
|
Expertise: code quality (SOLID, Clean Code, DRY), test coverage and quality, UX/UI review (heuristics, accessibility), security review (OWASP), performance review (CWV, API latency), data integrity and API contracts.
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ description: "Tech Lead — ArkaOS /dev department"
|
|
|
3
3
|
mode: subagent
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are Paulo, Tech Lead of the ArkaOS /dev department (v4.
|
|
6
|
+
You are Paulo, Tech Lead of the ArkaOS /dev department (v4.41.0; generated by scripts/harness_gen.py — do not edit).
|
|
7
7
|
|
|
8
8
|
Expertise: workflow orchestration, visual spec/plan companions via dev/diagram (typed IR -> interactive HTML the user opens before build), code quality enforcement, sprint/cycle management, technical decision-making, developer experience.
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ description: "Video Producer & Production Lead — ArkaOS /content department"
|
|
|
3
3
|
mode: subagent
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
You are Simão, Video Producer & Production Lead of the ArkaOS /content department (v4.
|
|
6
|
+
You are Simão, Video Producer & Production Lead of the ArkaOS /content department (v4.41.0; generated by scripts/harness_gen.py — do not edit).
|
|
7
7
|
|
|
8
8
|
Expertise: video production pipelines (script → storyboard → assets → edit → render), cut review via dev/watch (frame + transcript evidence on own renders before the Quality Gate), Hyperframes video-as-code editing (HTML/CSS/JS + GSAP → MP4), Higgsfield generation orchestration (image, video, audio, motion control, upscale, reframe), shot lists and EDLs (scene/shot/VO/on-screen-text columns), transcription-synced cuts and word-level captions.
|
|
9
9
|
|
package/harness/zed/.rules
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ArkaOS — The Operating System for AI Agent Teams
|
|
2
2
|
|
|
3
|
-
> v4.
|
|
3
|
+
> v4.41.0 — 89 agents, 17 departments, 332 skills. Generated by `scripts/harness_gen.py`; do not edit.
|
|
4
4
|
|
|
5
5
|
You are operating within ArkaOS. Every request routes through the
|
|
6
6
|
appropriate department squad — never respond as a generic assistant.
|
|
@@ -166,6 +166,14 @@ export default {
|
|
|
166
166
|
{ hooks: [hookEntry(hooksDir, "post-tool-use", 5)] },
|
|
167
167
|
];
|
|
168
168
|
|
|
169
|
+
// PostToolUseFailure — a failing tool throws inside the runtime and
|
|
170
|
+
// fires THIS event, never PostToolUse (2.1.220 contract), so
|
|
171
|
+
// failed-command turns only reach the hook when it is registered
|
|
172
|
+
// here too. Same entrypoint; Python branches on hook_event_name.
|
|
173
|
+
settings.hooks.PostToolUseFailure = [
|
|
174
|
+
{ hooks: [hookEntry(hooksDir, "post-tool-use", 5)] },
|
|
175
|
+
];
|
|
176
|
+
|
|
169
177
|
// PreToolUse — Flow enforcement gate (gated by hooks.hardEnforcement
|
|
170
178
|
// feature flag in ~/.arkaos/config.json; no-op when flag is false).
|
|
171
179
|
settings.hooks.PreToolUse = [
|
|
@@ -4,7 +4,7 @@ version = "1.0.0"
|
|
|
4
4
|
description = "ArkaOS programmatic MCP tools — KB search, workflow state, Quality Gate, recipes, session memory, telemetry"
|
|
5
5
|
requires-python = ">=3.11"
|
|
6
6
|
dependencies = [
|
|
7
|
-
"mcp[cli]>=1.2.0",
|
|
7
|
+
"mcp[cli]>=1.2.0,<2", # <2: mcp 2.0.0 removed mcp.server.fastmcp (1.x import path)
|
|
8
8
|
"pydantic>=2.0",
|
|
9
9
|
"pyyaml>=6.0",
|
|
10
10
|
]
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "arkaos-core"
|
|
3
|
-
version = "4.
|
|
3
|
+
version = "4.41.0"
|
|
4
4
|
description = "Core engine for ArkaOS — The Operating System for AI Agent Teams"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = {text = "MIT"}
|
|
@@ -38,7 +38,10 @@ mcp = [
|
|
|
38
38
|
# import the FastMCP decorators so the arka-tools security tests
|
|
39
39
|
# (OWASP LLM01 neutralization, write-gating) run on every Python
|
|
40
40
|
# version, not just where mcp happens to be installed.
|
|
41
|
-
|
|
41
|
+
# <2: mcp 2.0.0 (2026-07-28) removed mcp.server.fastmcp — the 1.x
|
|
42
|
+
# import path server.py uses. Migrating to the 2.x API is its own
|
|
43
|
+
# change; the ceiling keeps CI deterministic until then.
|
|
44
|
+
"mcp[cli]>=1.2.0,<2",
|
|
42
45
|
]
|
|
43
46
|
dashboard = [
|
|
44
47
|
"fastapi>=0.115.0",
|