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.
Files changed (50) hide show
  1. package/THE-ARKAOS-GUIDE.md +1 -1
  2. package/VERSION +1 -1
  3. package/bin/arka-doctor +3 -2
  4. package/config/hooks/_lib/fastpath/engine.cjs +44 -7
  5. package/config/hooks/gate-manifest.json +2 -6
  6. package/config/hooks/post-tool-use.ps1 +42 -7
  7. package/config/settings-template.json +11 -0
  8. package/core/governance/evidence_checks.py +7 -1
  9. package/core/governance/judge.py +23 -1
  10. package/core/governance/qg_digest.py +64 -0
  11. package/core/governance/qg_verdict.py +50 -1
  12. package/core/governance/reviewer_ledger.py +11 -2
  13. package/core/hooks/gate_manifest.py +14 -6
  14. package/core/hooks/post_tool_use.py +87 -26
  15. package/harness/codex/AGENTS.md +1 -1
  16. package/harness/copilot/copilot-instructions.md +1 -1
  17. package/harness/cursor/rules/arkaos.mdc +2 -2
  18. package/harness/gemini/GEMINI.md +1 -1
  19. package/harness/opencode/AGENTS.md +1 -1
  20. package/harness/opencode/agents/arka-architect-gabriel.md +1 -1
  21. package/harness/opencode/agents/arka-brand-director-valentina.md +1 -1
  22. package/harness/opencode/agents/arka-cfo-helena.md +1 -1
  23. package/harness/opencode/agents/arka-chief-of-staff-afonso.md +1 -1
  24. package/harness/opencode/agents/arka-community-strategist-beatriz.md +1 -1
  25. package/harness/opencode/agents/arka-content-strategist-rafael.md +1 -1
  26. package/harness/opencode/agents/arka-conversion-strategist-ines.md +1 -1
  27. package/harness/opencode/agents/arka-coo-sofia.md +1 -1
  28. package/harness/opencode/agents/arka-copy-director-eduardo.md +1 -1
  29. package/harness/opencode/agents/arka-cqo-marta.md +1 -1
  30. package/harness/opencode/agents/arka-cto-marco.md +1 -1
  31. package/harness/opencode/agents/arka-design-ops-lead-iris.md +1 -1
  32. package/harness/opencode/agents/arka-ecom-director-ricardo.md +1 -1
  33. package/harness/opencode/agents/arka-knowledge-director-clara.md +1 -1
  34. package/harness/opencode/agents/arka-leadership-director-rodrigo.md +1 -1
  35. package/harness/opencode/agents/arka-marketing-director-luna.md +1 -1
  36. package/harness/opencode/agents/arka-ops-lead-daniel.md +1 -1
  37. package/harness/opencode/agents/arka-pm-director-carolina.md +1 -1
  38. package/harness/opencode/agents/arka-revops-lead-vicente.md +1 -1
  39. package/harness/opencode/agents/arka-saas-strategist-tiago.md +1 -1
  40. package/harness/opencode/agents/arka-sales-director-miguel.md +1 -1
  41. package/harness/opencode/agents/arka-strategy-director-tomas.md +1 -1
  42. package/harness/opencode/agents/arka-tech-director-francisca.md +1 -1
  43. package/harness/opencode/agents/arka-tech-lead-paulo.md +1 -1
  44. package/harness/opencode/agents/arka-video-producer-simao.md +1 -1
  45. package/harness/zed/.rules +1 -1
  46. package/installer/adapters/claude-code.js +8 -0
  47. package/knowledge/skills-manifest.json +1 -1
  48. package/mcps/arka-tools/pyproject.toml +1 -1
  49. package/package.json +1 -1
  50. package/pyproject.toml +5 -2
@@ -1,6 +1,6 @@
1
1
  # The ArkaOS Guide
2
2
 
3
- > v4.39.0 — 89 agents, 17 departments, 332 skills, 297 commands, 17 ADRs.
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.39.0
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":10}]}],
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
- const SCHEMA_VERSION = 1;
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
- const exitCode = String(payload.exit_code ?? "") || "0";
277
- if (!manifest.post.success_exit_codes.includes(exitCode)) {
278
- return { action: "delegate", reason: "error-exit-code" };
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": 1,
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 (tool_name, tool_output, exit_code,
10
- # cwd).
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
- $toolOutput = ''
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 ($toolOutput -notmatch $errorPattern) {
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 = $toolOutput -split "`r?`n"
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) {
@@ -52,6 +52,17 @@
52
52
  ]
53
53
  }
54
54
  ],
55
+ "PostToolUseFailure": [
56
+ {
57
+ "hooks": [
58
+ {
59
+ "type": "command",
60
+ "command": "{{HOOKS_DIR}}/post-tool-use.sh",
61
+ "timeout": 5
62
+ }
63
+ ]
64
+ }
65
+ ],
55
66
  "CwdChanged": [
56
67
  {
57
68
  "hooks": [
@@ -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
- return {
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 ────────────────────────────────────────────────
@@ -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) -> "JudgeVerdict":
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) -> "QGVerdict":
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
- "evidence_digest": (verdict or {}).get("evidence_digest"),
342
- "tree_digest": (verdict or {}).get("tree_digest"),
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
- _SCHEMA_VERSION = 1
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 pre_tool_use._FLOW_GATED_TOOLS != flow_enforcer.EFFECT_TOOLS_ALWAYS | {"Bash"}:
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 # noqa: F401
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 pre_tool_use._ASSISTANT_WINDOW != state["numbers"]["assistant_window"]:
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(tool_output: str, prompt: str, session_id: str) -> None:
148
- if not _REJECTED_RE.search(tool_output):
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=tool_output,
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(tool_output: str, prompt: str) -> None:
166
- if not _APPROVED_RE.search(tool_output):
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(tool_output: str) -> str:
265
- lines = tool_output.splitlines()
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
- tool_output = get_str(input_data, "tool_output")
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 tool_output.splitlines()
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=get_str(input_data, "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, tool_output: str) -> None:
605
- if not session_id or not tool_output:
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(tool_output.encode("utf-8", "replace"))
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
- tool_output = get_str(stdin_json, "tool_output")
676
- exit_code = get_str(stdin_json, "exit_code") or "0"
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(tool_output, prompt, session_id)
721
- _record_pattern_stub(tool_output, prompt)
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(tool_output):
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(tool_output)
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, tool_output)
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
- emit_additional_context("PostToolUse", violation_msg)
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
@@ -1,6 +1,6 @@
1
1
  # ArkaOS — The Operating System for AI Agent Teams
2
2
 
3
- > v4.39.0 — 89 agents, 17 departments, 332 skills. Generated by `scripts/harness_gen.py`; do not edit.
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.39.0 — 89 agents, 17 departments, 332 skills. Generated by `scripts/harness_gen.py`; do not edit.
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.39.0 agent-team contract
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.39.0 — 89 agents, 17 departments, 332 skills. Generated by `scripts/harness_gen.py`; do not edit.
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.
@@ -1,6 +1,6 @@
1
1
  # ArkaOS — The Operating System for AI Agent Teams
2
2
 
3
- > v4.39.0 — 89 agents, 17 departments, 332 skills. Generated by `scripts/harness_gen.py`; do not edit.
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.39.0 — 89 agents, 17 departments, 332 skills. Generated by `scripts/harness_gen.py`; do not edit.
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.39.0; generated by scripts/harness_gen.py — do not edit).
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.39.0; generated by scripts/harness_gen.py — do not edit).
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.39.0; generated by scripts/harness_gen.py — do not edit).
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.39.0; generated by scripts/harness_gen.py — do not edit).
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.39.0; generated by scripts/harness_gen.py — do not edit).
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.39.0; generated by scripts/harness_gen.py — do not edit).
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.39.0; generated by scripts/harness_gen.py — do not edit).
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.39.0; generated by scripts/harness_gen.py — do not edit).
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.39.0; generated by scripts/harness_gen.py — do not edit).
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.39.0; generated by scripts/harness_gen.py — do not edit).
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.39.0; generated by scripts/harness_gen.py — do not edit).
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.39.0; generated by scripts/harness_gen.py — do not edit).
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.39.0; generated by scripts/harness_gen.py — do not edit).
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.39.0; generated by scripts/harness_gen.py — do not edit).
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.39.0; generated by scripts/harness_gen.py — do not edit).
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.39.0; generated by scripts/harness_gen.py — do not edit).
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.39.0; generated by scripts/harness_gen.py — do not edit).
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.39.0; generated by scripts/harness_gen.py — do not edit).
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.39.0; generated by scripts/harness_gen.py — do not edit).
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.39.0; generated by scripts/harness_gen.py — do not edit).
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.39.0; generated by scripts/harness_gen.py — do not edit).
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.39.0; generated by scripts/harness_gen.py — do not edit).
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.39.0; generated by scripts/harness_gen.py — do not edit).
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.39.0; generated by scripts/harness_gen.py — do not edit).
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.39.0; generated by scripts/harness_gen.py — do not edit).
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
 
@@ -1,6 +1,6 @@
1
1
  # ArkaOS — The Operating System for AI Agent Teams
2
2
 
3
- > v4.39.0 — 89 agents, 17 departments, 332 skills. Generated by `scripts/harness_gen.py`; do not edit.
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 = [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "_meta": {
3
3
  "generator": "scripts/marketplace_gen.py",
4
- "version": "4.39.0",
4
+ "version": "4.41.0",
5
5
  "marketplace": "arkaos"
6
6
  },
7
7
  "structural": {
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arkaos",
3
- "version": "4.39.0",
3
+ "version": "4.41.0",
4
4
  "description": "The Operating System for AI Agent Teams",
5
5
  "type": "module",
6
6
  "bin": {
package/pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "arkaos-core"
3
- version = "4.39.0"
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
- "mcp[cli]>=1.2.0",
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",