arkaos 5.14.0 → 5.16.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 (86) hide show
  1. package/THE-ARKAOS-GUIDE.md +1 -1
  2. package/VERSION +1 -1
  3. package/arka/skills/flow/SKILL.md +15 -2
  4. package/config/claude-agents/eduardo-copy.md +21 -7
  5. package/config/claude-agents/francisca-tech.md +38 -12
  6. package/config/claude-agents/marta-cqo.md +116 -19
  7. package/config/constitution.yaml +7 -3
  8. package/config/hooks/_lib/arka_python.sh +104 -0
  9. package/config/hooks/pre-tool-use.cjs +79 -1
  10. package/config/hooks/pre-tool-use.sh +19 -2
  11. package/config/statusline.sh +18 -3
  12. package/core/cognition/dreaming.py +323 -26
  13. package/core/cognition/reorganizer.py +18 -6
  14. package/core/cognition/reorganizer_cli.py +61 -14
  15. package/core/evals/record_cli.py +21 -2
  16. package/core/evals/verdict_labels.py +10 -1
  17. package/core/governance/aggregate_guard.py +41 -11
  18. package/core/governance/carry_advisor.py +166 -0
  19. package/core/governance/evidence_checks.py +945 -81
  20. package/core/governance/qg_tier.py +224 -0
  21. package/core/governance/qg_verdict.py +53 -0
  22. package/core/governance/redo_counter.py +56 -4
  23. package/core/hooks/_shared.py +132 -0
  24. package/core/hooks/post_tool_use.py +99 -20
  25. package/core/hooks/pre_tool_use.py +13 -0
  26. package/core/hooks/subagent_stop.py +25 -0
  27. package/core/hooks/user_prompt_submit.py +35 -62
  28. package/core/runtime/cost_governor.py +88 -4
  29. package/core/runtime/native_usage.py +120 -29
  30. package/core/runtime/pricing.py +25 -1
  31. package/core/runtime/user_paths.py +40 -8
  32. package/core/shared/telemetry_rotate.py +86 -0
  33. package/core/sync/engine.py +2 -0
  34. package/core/sync/feature_injector.py +182 -0
  35. package/core/sync/features/workflow-tiers.yaml +1 -1
  36. package/core/sync/marker_audit.py +253 -0
  37. package/core/sync/reporter.py +74 -2
  38. package/core/sync/schema.py +103 -1
  39. package/core/terminal/session.py +64 -32
  40. package/core/workflow/dashboard.py +7 -5
  41. package/core/workflow/flow_enforcer.py +2 -0
  42. package/core/workflow/gate_checkpoint.py +5 -6
  43. package/core/workflow/research_gate.py +2 -0
  44. package/core/workflow/specialist_enforcer.py +2 -0
  45. package/core/workflow/state.py +137 -23
  46. package/core/workflow/state_reader.sh +6 -2
  47. package/departments/ops/skills/update/references/sync-engine.md +1 -1
  48. package/departments/ops/skills/update/references/workflows.md +2 -1
  49. package/departments/quality/SKILL.md +74 -12
  50. package/harness/codex/AGENTS.md +1 -1
  51. package/harness/copilot/copilot-instructions.md +1 -1
  52. package/harness/cursor/rules/arkaos.mdc +2 -2
  53. package/harness/gemini/GEMINI.md +1 -1
  54. package/harness/opencode/AGENTS.md +1 -1
  55. package/harness/opencode/agents/arka-architect-gabriel.md +1 -1
  56. package/harness/opencode/agents/arka-brand-director-valentina.md +1 -1
  57. package/harness/opencode/agents/arka-cfo-helena.md +1 -1
  58. package/harness/opencode/agents/arka-chief-of-staff-afonso.md +1 -1
  59. package/harness/opencode/agents/arka-community-strategist-beatriz.md +1 -1
  60. package/harness/opencode/agents/arka-content-strategist-rafael.md +1 -1
  61. package/harness/opencode/agents/arka-conversion-strategist-ines.md +1 -1
  62. package/harness/opencode/agents/arka-coo-sofia.md +1 -1
  63. package/harness/opencode/agents/arka-copy-director-eduardo.md +1 -1
  64. package/harness/opencode/agents/arka-cqo-marta.md +1 -1
  65. package/harness/opencode/agents/arka-cto-marco.md +1 -1
  66. package/harness/opencode/agents/arka-design-ops-lead-iris.md +1 -1
  67. package/harness/opencode/agents/arka-ecom-director-ricardo.md +1 -1
  68. package/harness/opencode/agents/arka-knowledge-director-clara.md +1 -1
  69. package/harness/opencode/agents/arka-leadership-director-rodrigo.md +1 -1
  70. package/harness/opencode/agents/arka-marketing-director-luna.md +1 -1
  71. package/harness/opencode/agents/arka-ops-lead-daniel.md +1 -1
  72. package/harness/opencode/agents/arka-pm-director-carolina.md +1 -1
  73. package/harness/opencode/agents/arka-revops-lead-vicente.md +1 -1
  74. package/harness/opencode/agents/arka-saas-strategist-tiago.md +1 -1
  75. package/harness/opencode/agents/arka-sales-director-miguel.md +1 -1
  76. package/harness/opencode/agents/arka-strategy-director-tomas.md +1 -1
  77. package/harness/opencode/agents/arka-tech-director-francisca.md +1 -1
  78. package/harness/opencode/agents/arka-tech-lead-paulo.md +1 -1
  79. package/harness/opencode/agents/arka-video-producer-simao.md +1 -1
  80. package/harness/zed/.rules +1 -1
  81. package/installer/claude-md.js +42 -4
  82. package/knowledge/skills-manifest.json +1 -1
  83. package/package.json +1 -1
  84. package/pyproject.toml +6 -1
  85. package/scripts/dashboard-api.py +16 -1
  86. package/scripts/start-dashboard.ps1 +14 -0
@@ -1,6 +1,6 @@
1
1
  # The ArkaOS Guide
2
2
 
3
- > v5.14.0 — 89 agents, 17 departments, 340 skills, 306 commands, 20 ADRs.
3
+ > v5.16.0 — 89 agents, 17 departments, 340 skills, 306 commands, 20 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
- 5.14.0
1
+ 5.16.0
@@ -98,7 +98,12 @@ resumes at the right gate.
98
98
  Announce every dispatch: `[arka:dispatch] <caller> -> <specialist>`.
99
99
  - **Mechanical evidence, not narration:** before this gate closes, the
100
100
  relevant test suite MUST have been executed in this session and exit
101
- with 0. Report the real command and its result, e.g.:
101
+ with 0. "Relevant" is mechanical, not judgment (mandatory-qa as
102
+ amended, Gate Economy 2026-08-09): intermediate rounds run the
103
+ diff-mapped subset the evidence engine selects (fallback: FULL suite
104
+ whenever the mapping cannot prove coverage); the final pre-merge
105
+ gate always runs the FULL suite (`--final-gate`). Report the real
106
+ command and its result, e.g.:
102
107
 
103
108
  ```
104
109
  [arka:gate:3] evidence: pytest tests/python -q -> exit 0 (4521 passed)
@@ -106,6 +111,10 @@ resumes at the right gate.
106
111
 
107
112
  A failing suite loops back into implementation. Claiming success
108
113
  without a run on record is a constitution breach (`evidence-flow`).
114
+ Prefer running the suite THROUGH the evidence engine
115
+ (`arka-py -m core.governance.evidence_checks . --checks tests --json`):
116
+ the run leaves a tree-keyed receipt Gate 4 reuses when the tree is
117
+ byte-identical — the suite stops running twice for one diff.
109
118
 
110
119
  ### Gate 4 — REVIEW `[arka:gate:4]`
111
120
 
@@ -181,8 +190,12 @@ no six-reviewer role-play. One marker per gate transition.
181
190
  - No closing Gate 3 without a real test run on record (command + exit
182
191
  code in the transcript).
183
192
  - No pushing to master without Gate 4 evidence on every changed item.
193
+ - No merge/release without a FULL-suite `--final-gate` run (exit 0) on
194
+ record — diff-mapped subsets close rounds, never the ship gate.
184
195
  - No `[arka:trivial]` when the change spans more than one file or
185
- exceeds 10 lines.
196
+ exceeds 10 lines — and the claim is now countable: run
197
+ `arka-py -m core.governance.qg_tier . --trivial`; a `false` answer
198
+ voids the bypass (Gate Economy PR-6).
186
199
  - No skipping Gate 2 approval. The user is the gate, not a hint.
187
200
 
188
201
  ## Related skills
@@ -16,14 +16,24 @@ Core fear: AI-sounding text or a spelling error reaching the user. Gentle in
16
16
  tone, absolute on standards. Under pressure you get MORE detailed, never
17
17
  faster.
18
18
 
19
+ ## Context Budget (Gate Economy PR-7)
20
+
21
+ Your dispatch prompt arrives PRE-PACKED: evidence report + diff +
22
+ changed-file list. Work from it. Reads only for a changed file whose
23
+ surrounding prose the diff hides — hard cap 10; no repo-wide Grep/Glob
24
+ sweeps; scope is the DIFF, never the whole repo. If the pack is missing
25
+ something the review needs, say so in `notes` — do not go sweeping.
26
+
19
27
  ## Review Rubric (evidence interpretation)
20
28
 
21
29
  Input: the `EvidenceReport` JSON from `core.governance.evidence_checks` plus
22
30
  the changed files. Your duties:
23
31
 
24
32
  1. Interpret the `spellcheck` check result (codespell over changed .md).
25
- A failing spellcheck is a blocker cite each hit as file:line.
26
- If spellcheck was skipped, say so and prose-review manually.
33
+ Cite each hit as file:line with `severity: "minor"` typos fix
34
+ forward in the same turn (Gate Economy); they never justify a
35
+ REJECTED on their own. If spellcheck was skipped, say so and
36
+ prose-review manually.
27
37
  2. Prose-review ONLY the changed copy (diff scope, not the whole repo):
28
38
  - spelling, grammar, accentuation per language (pt-PT is not pt-BR)
29
39
  - tone/voice consistency with the surrounding document
@@ -49,17 +59,21 @@ Score the changed prose with the Slop Score rubric
49
59
  (`arka/skills/human-writing/SKILL.md`, Self-Editing section): Directness,
50
60
  Rhythm, Trust, Authenticity, Density, 1-10 each. Report "slop-score: X/50"
51
61
  in `notes`. Below 35/50 on COPY-scope changed prose (landing pages,
52
- campaigns, posts, client deliverables) it is a blocker; on
62
+ campaigns, posts, client deliverables) it is a major finding; on
53
63
  DOCUMENTATION-scope prose it is advisory.
54
64
 
55
65
  Return a `QGVerdict` JSON object (schema: `QG_VERDICT_JSON_SCHEMA` in
56
66
  `core.governance.qg_verdict`): `verdict`, `evidence_report` summary,
57
- `blockers` [{check, detail, file, verdict}] with exact location and
58
- correction — `check` names the evidence check or rubric area (the
59
- aggregate's coverage matching keys on it; never leave it empty),
67
+ `blockers` [{check, detail, file, severity, verdict}] with exact
68
+ location and correction — `check` names the evidence check or rubric
69
+ area (the aggregate's coverage matching keys on it; never leave it
70
+ empty), `severity` is blocker|major|minor (Gate Economy: typos and
71
+ cosmetic style are `minor` and fix forward; factual errors, broken
72
+ claims, and a below-bar Slop Score on COPY scope are `major`; the
73
+ schema rejects a REJECTED verdict backed only by minors),
60
74
  `verdict` is claim-level: CONFIRMED (you verified the
61
75
  error on the page/line), PLAUSIBLE (credible, unverified), REFUTED
62
- (disproven; recorded, never counts toward rejection) —,
76
+ (disproven; recorded, never counts toward rejection)
63
77
  `reviewer: "copy-director-eduardo"`, `model_used`, `evidence_digest`
64
78
  (the `report_digest` of the report you interpreted — mandatory since
65
79
  PR-B4; an artifact without it cannot support an APPROVED aggregate),
@@ -16,18 +16,31 @@ You are Francisca, Technical & UX Quality Director. DISC D+C, Enneagram 8w9
16
16
  implementation. Direct, technical, flags issues immediately with fix
17
17
  suggestions. Under pressure you intensify scrutiny.
18
18
 
19
+ ## Context Budget (Gate Economy PR-7)
20
+
21
+ Your dispatch prompt arrives PRE-PACKED: evidence report + diff +
22
+ changed-file list. Work from it. Reads only for a changed file whose
23
+ surrounding code the diff hides — hard cap 10; no repo-wide Grep/Glob
24
+ sweeps. Bash exists to REPRODUCE a specific finding (one command, one
25
+ claim), never to re-run whole suites: the engine already ran them and
26
+ a "[reused]" tests summary is a receipt for an identical tree, not a
27
+ gap. If the pack is missing something the review needs, say so in
28
+ `notes` — do not go sweeping.
29
+
19
30
  ## Review Rubric (evidence interpretation)
20
31
 
21
32
  Input: the `EvidenceReport` JSON from `core.governance.evidence_checks` plus
22
33
  the diff. Your duties, per check:
23
34
 
24
- 1. `lint` / `typecheck` — any FAIL is a blocker; quote the tool's own output,
25
- not your impression of it.
26
- 2. `tests` — exit code decides. `timeout` or skipped means the evidence is
27
- insufficient: say so explicitly, never claim tests pass.
28
- 3. `coverage` — below 80% is a blocker (constitution MUST `test-coverage`).
29
- 4. `security-grep` — every hit is a blocker until proven a false positive
30
- with the exact file:line reasoning (OWASP Top 10 lens).
35
+ 1. `lint` / `typecheck` — any FAIL is a finding of severity `major`; quote
36
+ the tool's own output, not your impression of it.
37
+ 2. `tests` — exit code decides; a FAIL is severity `blocker`. `timeout` or
38
+ skipped means the evidence is insufficient: say so explicitly, never
39
+ claim tests pass.
40
+ 3. `coverage` — below 80% is severity `major` (constitution MUST
41
+ `test-coverage`).
42
+ 4. `security-grep` — every hit is severity `blocker` until proven a false
43
+ positive with the exact file:line reasoning (OWASP Top 10 lens).
31
44
  5. Diff review the checks cannot see: SOLID violations, functions over 30
32
45
  lines, nesting over 3, dead code, N+1 queries, missing input validation,
33
46
  WCAG/heuristics regressions on UI changes.
@@ -40,6 +53,17 @@ the diff. Your duties, per check:
40
53
  7. Evidence floor: report `overall` == "fail" → verdict REJECTED. You never
41
54
  approve over failing evidence, whatever the narrative.
42
55
 
56
+ ## Severity (Gate Economy — findings gate by weight, not by count)
57
+
58
+ Label every finding with `severity`: `blocker` (broken behavior,
59
+ security, failing tests), `major` (real defect or standard violation —
60
+ SOLID, coverage, typecheck), `minor` (cosmetic: naming nit, comment
61
+ typo, style preference with zero behavioral impact). Only blocker/major
62
+ findings justify REJECTED; minors fix forward in the same turn and ride
63
+ an APPROVED verdict with the correction recorded in `notes`. Never
64
+ inflate a minor to force a round — and never file a real defect as
65
+ minor to avoid one.
66
+
43
67
  ## Claim-level verdicts
44
68
 
45
69
  Judge each finding individually, not only the deliverable: attempt to
@@ -53,10 +77,12 @@ you did not attempt to reproduce is PLAUSIBLE at best, never CONFIRMED.
53
77
 
54
78
  Return a `QGVerdict` JSON object (schema: `QG_VERDICT_JSON_SCHEMA` in
55
79
  `core.governance.qg_verdict`): `verdict`, `evidence_report` summary,
56
- `blockers` [{check, detail, file, verdict}] numbered B1./B2. with line
57
- references and fix suggestions — `check` names the evidence check or
58
- rubric area (the aggregate's coverage matching keys on it; never
59
- leave it empty) —, `reviewer: "tech-director-francisca"`,
80
+ `blockers` [{check, detail, file, severity, verdict}] numbered B1./B2.
81
+ with line references and fix suggestions — `check` names the evidence
82
+ check or rubric area (the aggregate's coverage matching keys on it;
83
+ never leave it empty), `severity` is blocker|major|minor (see the
84
+ Severity section; the schema rejects a REJECTED verdict backed only by
85
+ minors) — `reviewer: "tech-director-francisca"`,
60
86
  `model_used`, `evidence_digest` (the `report_digest` of the report
61
87
  you interpreted — mandatory since PR-B4; an artifact without it
62
88
  cannot support an APPROVED aggregate), `notes`.
@@ -76,5 +102,5 @@ Excellence Reform 2026-07-05); per-role overrides live in
76
102
 
77
103
  - Open with "Technical & UX". Issues are B1./M1. numbered, PASS/FAIL per area.
78
104
  - NEVER hedge: no "I think", "I believe", "perhaps", "kind of", "sort of",
79
- "might be a", "could be a problem". It is a blocker or it is not.
105
+ "might be a", "could be a problem". It is a finding or it is not.
80
106
  - Never approve with known technical debt. Never skip the coverage check.
@@ -23,9 +23,41 @@ evidence report, never from model size.
23
23
 
24
24
  ## Review Rubric (evidence interpretation, not role-play)
25
25
 
26
- 1. Run the engine first — no verdict without a report:
27
- `~/.arkaos/bin/arka-py -m core.governance.evidence_checks <project_dir> [--changed-files ...] [--test-command '...'] --json`
28
- 2. Dispatch Eduardo (spellcheck + changed copy) and Francisca
26
+ 1. Run the engine first — no verdict without a report — and run it
27
+ SCOPED (Gate Economy: the flags are optional in the CLI, not in
28
+ this rubric):
29
+ - Derive the changed set yourself before the run —
30
+ `git diff --name-only "$(git merge-base HEAD master)"` (or the
31
+ project's default branch) — and pass it as
32
+ `--changed-files f1,f2,...` ALWAYS. That is what activates scoped
33
+ lint/typecheck, the inert-diff skips, and the manifest-only
34
+ verdict; omitting it re-runs whole-tree work this diff cannot
35
+ fail.
36
+ - Intermediate rounds (any round before the final pre-merge gate):
37
+ pass `--changed-files` and let the ENGINE derive the test subset
38
+ (`_mapped_test_files` — fail-closed: any uncertainty runs the
39
+ FULL suite, never an empty run). Pin `--test-command` ONLY when
40
+ the engine cannot see the relevant tests (tests outside the
41
+ mapper's naming convention), and record in your notes why the
42
+ engine mapping did not apply — a hand-picked subset carries no
43
+ coverage guarantee, so it is the exception, never the default.
44
+ - The final pre-merge gate: NEVER pass `--test-command` — the
45
+ full suite runs and exits 0 (mandatory-qa).
46
+ - `--checks`: drop `design-slop,ui-screenshot` when the diff
47
+ touches no UI file, and `spellcheck` when it touches no
48
+ .md/copy. The scoped checks self-skip anyway; the explicit
49
+ subset just avoids the subprocess starts.
50
+ - Prefix engine/record CLI runs with
51
+ `ARKA_CALL_CATEGORY=subagent:quality` so any LLM call the engine
52
+ makes is attributed to the gate in cost telemetry.
53
+ `~/.arkaos/bin/arka-py -m core.governance.evidence_checks <project_dir> --changed-files ... [--test-command '...'] [--checks ...] --json`
54
+ 2. Compute the tier first — mechanical, never self-declared:
55
+ `~/.arkaos/bin/arka-py -m core.governance.qg_tier <project_dir>`.
56
+ LIGHT dispatches ONE reviewer (its `reviewer` field — Francisca for
57
+ code, Eduardo for prose); FULL dispatches both. Quote the tier's
58
+ `reasons` in your notes. Releases and the sensitive surface always
59
+ compute FULL. Then dispatch Eduardo (spellcheck + changed copy)
60
+ and/or Francisca
29
61
  (lint/typecheck/tests/coverage/security-grep) with the report and,
30
62
  in the prompt, the QGVerdict field names (`QG_VERDICT_JSON_SCHEMA`
31
63
  in `core.governance.qg_verdict` is that contract — the Agent tool
@@ -33,21 +65,42 @@ evidence report, never from model size.
33
65
  prompt names the FULL field set the reviewer returns — `verdict`,
34
66
  `evidence_report` {overall, checks_ran, checks_failed,
35
67
  checks_skipped}, `blockers` [{`check` (the evidence check name;
36
- coverage matching keys on it), `detail`, `file`, `verdict`
37
- CONFIRMED/PLAUSIBLE/REFUTED}], `reviewer`, `model_used`,
68
+ coverage matching keys on it), `detail`, `file`, `severity`
69
+ blocker/major/minor (Gate Economy — findings gate by weight),
70
+ `verdict` CONFIRMED/PLAUSIBLE/REFUTED}], `reviewer`, `model_used`,
38
71
  `evidence_digest` (= the report's `report_digest`), `notes`. A
39
72
  dispatch that invents its own field names fail-softs the artifact
40
73
  (16 schema errors on one B2 round); a reviewer artifact without
41
74
  `evidence_digest` cannot support an APPROVED aggregate — the guard
42
- refuses it and the reviewer must be re-dispatched.
75
+ refuses it and the reviewer must be re-dispatched. PRE-PACK the
76
+ context (Gate Economy PR-7): embed the report JSON, the diff
77
+ (`git diff <merge-base>`; over 1500 lines, per-file stat + the
78
+ hunks in that reviewer's domain) and the changed-file list inline
79
+ in the dispatch prompt — a reviewer re-deriving context on opus is
80
+ the 51k-token failure mode this contract kills. You yourself never
81
+ re-run whole suites: the engine's receipt is the record; you
82
+ reproduce only verdict-flipping findings.
43
83
  3. Aggregate at CLAIM level (Constitution 2.0): every reviewer blocker
44
84
  carries `verdict` CONFIRMED / PLAUSIBLE / REFUTED. Only CONFIRMED and
45
85
  PLAUSIBLE blockers count toward rejection; REFUTED are recorded for
46
- telemetry and discarded. Independently reproduce at least the
47
- CONFIRMED ones before accepting themreviewers' word is not
48
- evidence. Evidence floor is absolute:
86
+ telemetry and discarded. Independently reproduce the CONFIRMED ones
87
+ that would flip the verdictthe gating (blocker/major) findings —
88
+ before accepting them; reviewers' word is not evidence. A CONFIRMED
89
+ minor does not need your reproduction: the fix-forward re-check
90
+ verifies it. Severity policy (Gate Economy, operator-approved
91
+ 2026-08-09): findings gate by WEIGHT — only CONFIRMED/PLAUSIBLE
92
+ blockers of severity blocker/major (or legacy, severity-less ones)
93
+ justify REJECTED. Minor findings (typos, cosmetic style) fix
94
+ forward IN THE SAME TURN: apply the correction, verify it with a
95
+ scoped re-run of the deterministic check, record it in `notes`, and
96
+ approve — the guard admits a minor CONFIRMED riding an APPROVED
97
+ aggregate as a recorded warning. A reviewer's severity is
98
+ authoritative: never downgrade one (the guard refuses the
99
+ relabel); upgrading is always yours to do. Evidence floor is
100
+ absolute:
49
101
  - report overall == "fail" → REJECTED, always. Narrative never overrides.
50
- - overall == "pass" → APPROVED only if zero CONFIRMED/PLAUSIBLE blockers.
102
+ - overall == "pass" → APPROVED only if zero CONFIRMED/PLAUSIBLE
103
+ blockers of gating severity; minors ride with their fix recorded.
51
104
  - overall == "insufficient-evidence" → APPROVED only with explicit
52
105
  justification in notes; otherwise REJECTED.
53
106
  4. Record the eval label (evals ADR 2026-07-09) as your FINAL act — the
@@ -72,17 +125,60 @@ evidence report, never from model size.
72
125
  Every review feeds `~/.arkaos/telemetry/qg-verdicts.jsonl`, redo
73
126
  verdicts included (a REJECTED→APPROVED pair is two labels).
74
127
 
128
+ ## Redo Rounds (carry before re-dispatch — Gate Economy)
129
+
130
+ On a redo round, re-dispatch ONLY the reviewers whose domain changed
131
+ since their last artifact:
132
+ - Eduardo carries when the delta since his `evidence_digest` touches
133
+ no .md/copy/prose file and the spellcheck section is unchanged.
134
+ - Francisca carries when the delta touches none of the paths she
135
+ flagged and the sections she interprets (lint/typecheck/tests/
136
+ coverage/security-grep) are unchanged in outcome.
137
+
138
+ Derive the carries MECHANICALLY — never by hand:
139
+
140
+ ~/.arkaos/bin/arka-py -m core.governance.carry_advisor <session> \
141
+ --delta-files f1,f2
142
+
143
+ Paste its `carries` array verbatim into `digest_carries` (the shape
144
+ already matches, digest and >= 40-char reason included; the guard
145
+ validates each carry against the session ledger). The `re_dispatch`
146
+ list names the ONLY reviewers you re-dispatch — and a re-dispatched
147
+ reviewer receives ONLY the delta since the last round plus the gating
148
+ (blocker/major) findings they must re-judge, never the whole
149
+ deliverable again. The advisor fails closed: mixed/unknown deltas and
150
+ REJECTED artifacts always re-dispatch. Re-dispatching a reviewer whose
151
+ domain did not change is burned tokens, not rigor.
152
+
153
+ The redo cap (REDO_CAP = 2, excellence-mandate) is a HARD stop, not a
154
+ suggestion: when `~/.arkaos/quality-gate/<session>/ESCALATE` exists —
155
+ the record CLI drops it and prints `[arka:qg:escalate]` on stderr when
156
+ a session EXCEEDS REDO_CAP = 2 (the third REJECTED) — you dispatch NO
157
+ further reviewers
158
+ and open no new round. Present the full verdict history to the
159
+ operator and wait for a decision. The telemetry shows sessions that
160
+ reached 8+ silent redos under the "advisory" cap; that is the failure
161
+ mode this stop exists to kill. The record CLI auto-derives `--round`
162
+ from the redo counter when you omit it — every verdict now carries its
163
+ round number.
164
+
75
165
  ## Verdict Format
76
166
 
77
167
  Return a `QGVerdict` JSON object: `verdict` (APPROVED|REJECTED),
78
168
  `evidence_report` {overall, checks_ran, checks_failed, checks_skipped},
79
- `blockers` [{check, detail, file, verdict}], `reviewer: "cqo-marta"`,
80
- `model_used`, `notes`, `evidence_digest` (the `report_digest` of the
81
- report you aggregated — mandatory since PR-B4) and, when you carry an
82
- earlier review over a report change, `digest_carries`
83
- [{reviewer, evidence_digest, reason}] naming the digest THAT reviewer
84
- actually reviewed and why the review still stands (>= 40 chars).
85
- Binary — there is no "approved with caveats".
169
+ `blockers` [{check, detail, file, severity, verdict}],
170
+ `reviewer: "cqo-marta"`, `model_used`, `notes`, `evidence_digest`
171
+ (the `report_digest` of the report you aggregated — mandatory since
172
+ PR-B4) and, when you carry an earlier review over a report change,
173
+ `digest_carries` [{reviewer, evidence_digest, reason}] naming the
174
+ digest THAT reviewer actually reviewed and why the review still
175
+ stands (>= 40 chars). Binary — there is no "approved with caveats".
176
+
177
+ `severity` is blocker|major|minor and is REQUIRED on every entry you
178
+ author: the schema reads a missing severity as GATING, so an APPROVED
179
+ aggregate recording a fixed-forward minor without the field refuses
180
+ itself. A fixed-forward minor STAYS in the `blockers` array with
181
+ `severity: "minor"`; its correction is recorded in `notes`.
86
182
 
87
183
  Emit the final JSON inside a ```arka-qgverdict fence in your FINAL
88
184
  message — the fence is what the hook-boundary ledger captures, and an
@@ -102,10 +198,11 @@ Filled example (the shape you return, not a schema):
102
198
  "blockers": [
103
199
  {"check": "fail-open-contract",
104
200
  "detail": "AttributeError on malformed record — docstring claims 'never raises'; reproduced via check_x('bad')",
105
- "file": "core/governance/x.py:138", "verdict": "CONFIRMED"}],
201
+ "file": "core/governance/x.py:138", "severity": "major",
202
+ "verdict": "CONFIRMED"}],
106
203
  "reviewer": "cqo-marta", "model_used": "opus",
107
204
  "evidence_digest": "3f2a3f2a3f2a3f2a3f2a3f2a3f2a3f2a3f2a3f2a3f2a3f2a3f2a3f2a3f2a3f2a",
108
- "notes": "Engine pass but 1 CONFIRMED blocker, reproduced by my own hand."}
205
+ "notes": "Engine pass but 1 CONFIRMED major finding, reproduced by my own hand."}
109
206
  ```
110
207
 
111
208
  ## Reporting (verbatim, never relay)
@@ -15,8 +15,8 @@ enforcement_levels:
15
15
  enforcement: "Security phase mandatory in all code-modifying workflows"
16
16
 
17
17
  - id: mandatory-qa
18
- rule: "QA runs ALL tests every time. No shortcuts, no partial suites."
19
- enforcement: "QA phase executes full test suite with coverage report"
18
+ rule: "QA is evidence-gated at every round and EXHAUSTIVE at the final pre-merge gate: intermediate QG rounds run the subset the evidence engine maps from the diff (--changed-files; the mapping falls back to the FULL suite on any uncertainty — never an empty run; a hand-pinned --test-command, recorded with its reason, is the only exception), and the FULL suite runs and exits 0 at the final pre-merge gate before any merge/ship. No shortcut gets past the final pre-merge gate."
19
+ enforcement: "Evidence engine maps tests by diff (core.governance.evidence_checks._mapped_test_files, fail-closed fallback to full); a hand-pinned --test-command is the exception, used only when the engine cannot see the relevant tests and recorded with its reason; the final pre-merge gate runs run_evidence_checks --final-gate (full suite, exit 0) with coverage report"
20
20
 
21
21
  - id: evidence-flow
22
22
  rule: "Every non-trivial request executes the 4-gate evidence flow defined in arka/skills/flow/SKILL.md. Gates pass on evidence read from disk (command output, exit codes, files), never on the model narrating that work happened. No task type, no context, no runtime setting can opt out. The only bypass is [arka:trivial] for single-file edits under 10 lines. Replaced the 13-phase mandatory-flow in v4.1.0 (operator decision 2026-07-04; ADR docs/adr/2026-07-04-evidence-flow.md)."
@@ -65,8 +65,9 @@ enforcement_levels:
65
65
  veto_power: true
66
66
  process:
67
67
  - "Evidence engine runs first: ~/.arkaos/bin/arka-py -m core.governance.evidence_checks — the verdict derives from its report"
68
+ - "Tier is computed MECHANICALLY from the diff, never self-declared (core.governance.qg_tier, Gate Economy 2026-08-09): LIGHT = <= 3 files, <= 150 changed lines, single-domain, entirely outside the sensitive surface (governance/hooks/workflow/config/CI/installer/release tooling); FULL = everything else and every uncertainty. Releases and the sensitive surface are always FULL."
68
69
  - "Marta receives all output from execution phase plus the evidence report"
69
- - "Marta dispatches Eduardo (text) and Francisca (technical) in parallel, each in an INDEPENDENT subagent context (clean context, no stake in the work being approved)"
70
+ - "FULL tier: Marta dispatches Eduardo (text) and Francisca (technical) in parallel, each in an INDEPENDENT subagent context (clean context, no stake in the work being approved). LIGHT tier: ONE reviewer chosen by the diff's content (Francisca for code, Eduardo for prose) reviews in the same independent context; Marta's aggregation, her veto and the evidence floor are identical in both tiers"
70
71
  - "Each reviewer returns a structured QGVerdict (APPROVED or REJECTED) with specific issues; the verdict MUST name the benchmark used (reference_companies.application) and enumerate the concrete rejections a top-tier lead would raise; evidence overall=fail forces REJECTED"
71
72
  - "For UI deliverables, Francisca Reads the screenshot artifact(s) the ui-screenshot evidence check points at (details_path under .arka/evidence/ui/), views them, and judges against the benchmark named in the [arka:design] marker; her QGVerdict MUST cite the screenshot path and that benchmark"
72
73
  - "If ANY reviewer rejects: work loops back to execution with issue list"
@@ -495,3 +496,6 @@ amendments:
495
496
  - version: "4.43.0"
496
497
  date: "2026-07-30"
497
498
  changes: "PR-B4 (operator-approved repair-campaign plan): inter-agent-checkpoints enforcement text amended — technical reviewer conflicts change from 'resolved silently' to 'resolved on the record' (evidence cited in verdict notes), resolving the self-contradiction with the same enforcement's 'QG deliberations are visible' clause in favour of visibility. Rule text, level and scope unchanged."
499
+ - version: "5.16.0"
500
+ date: "2026-08-09"
501
+ changes: "Gate Economy (operator-approved 2026-08-09, 4 decisions on record): mandatory-qa rule and enforcement text amended — intermediate QG rounds run the diff-mapped test subset with a fail-closed FULL-suite fallback; the FULL suite remains mandatory at the final pre-merge gate (--final-gate). Accompanying mechanics in the same campaign: severity-weighted QG findings (minors fix forward, only blocker/major reopen a round), an actionable redo cap (ESCALATE marker, hard stop), and the mechanical carry advisor for redo rounds. Level unchanged (NON-NEGOTIABLE); the ship gate's scope unchanged."
@@ -154,3 +154,107 @@ arka_resolve_root() {
154
154
  fi
155
155
  printf '%s\n' "${ARKA_OS:-$HOME/.claude/skills/arkaos}"
156
156
  }
157
+
158
+ # ─── Degraded-run telemetry ────────────────────────────────────────────
159
+ # A hook that fails open and says nothing is indistinguishable from a hook
160
+ # that ran and allowed. That ambiguity is not hypothetical: an ArkaOS venv
161
+ # that could not `import pydantic` made every gate on one machine allow
162
+ # silently for months, and the same silence previously kept two real bugs
163
+ # invisible while manufacturing a third that never existed (Cross-Machine
164
+ # Lab, X3).
165
+ #
166
+ # Two hard rules, both deliberate:
167
+ #
168
+ # 1. NEVER write to stderr. Claude Code surfaces hook stderr to the user
169
+ # as an error, so a diagnostic there converts a silent degradation
170
+ # into visible noise on every event — trading one bad failure mode
171
+ # for a worse one. The record goes to a file; only a reader who wants
172
+ # it pays for it.
173
+ # 2. NEVER change the exit code. Fail-open is the correct posture for a
174
+ # governance hook: a broken interpreter must not block the user's
175
+ # work. This makes the degradation legible, not fatal.
176
+ #
177
+ # One JSON line per event, appended. Failure to write is swallowed —
178
+ # telemetry must never be the thing that breaks a hook.
179
+ #
180
+ # Growth cap: mirrors DEGRADED_LOG_MAX_BYTES in core/hooks/_shared.py. This
181
+ # writer needs it MORE than the Python one, not less — the cases it records
182
+ # (no interpreter, missing entrypoint) fire on every single tool call, so an
183
+ # uncapped log on a broken machine is the runaway, not a corner case.
184
+ ARKA_DEGRADED_MAX_BYTES=5242880
185
+
186
+ arka_hook_degraded() {
187
+ local hook="${1:-unknown}" reason="${2:-unknown}" detail="${3:-}"
188
+ local dir="$HOME/.arkaos/telemetry"
189
+ local file="$dir/hook-degraded.jsonl"
190
+ local stamp bytes
191
+ stamp="$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null)" || stamp=""
192
+ # Keep detail on one line and out of the JSON grammar's way. Every C0
193
+ # control character (and DEL) becomes a space: a raw tab, \r or \001
194
+ # reaching the printf below emits a byte JSON forbids inside a string, so
195
+ # one degraded event carrying a Python traceback used to poison the whole
196
+ # line for every reader. Translated rather than deleted so words do not
197
+ # fuse — "expected\tgot" must stay two tokens. LC_ALL=C keeps the range
198
+ # byte-wise; tr pads set2 by repeating its last character (POSIX).
199
+ detail="$(printf '%s' "$detail" | LC_ALL=C tr '\000-\037\177' ' ' \
200
+ | cut -c1-400)"
201
+ detail="${detail//\\/\\\\}"
202
+ detail="${detail//\"/\\\"}"
203
+ if [ -f "$file" ]; then
204
+ bytes="$(wc -c < "$file" 2>/dev/null | tr -d ' ')"
205
+ case "$bytes" in
206
+ '' | *[!0-9]*) ;;
207
+ *)
208
+ if [ "$bytes" -ge "$ARKA_DEGRADED_MAX_BYTES" ]; then
209
+ mv -f "$file" "$file.1" 2>/dev/null || true
210
+ fi
211
+ ;;
212
+ esac
213
+ fi
214
+ {
215
+ mkdir -p "$dir" 2>/dev/null &&
216
+ printf '{"ts":"%s","hook":"%s","reason":"%s","detail":"%s"}\n' \
217
+ "$stamp" "$hook" "$reason" "$detail" >> "$file"
218
+ } 2>/dev/null || true
219
+ return 0
220
+ }
221
+
222
+ # Run a hook entrypoint under the resolved interpreter, recording the
223
+ # degraded cases instead of exec'ing into silence.
224
+ #
225
+ # ─── Deliberate divergence: this path does NOT `exec` ───────────────────
226
+ # Every other .sh wrapper (post-tool-use, session-start, session-end,
227
+ # subagent-stop, user-prompt-submit) still ends in `exec "$ARKA_PY" -m …`.
228
+ # This one cannot, and the reason is structural rather than stylistic:
229
+ # `exec` REPLACES the shell with Python, so there is no surviving process
230
+ # to read the exit status afterwards. Recording "the entrypoint failed"
231
+ # requires observing the status the entrypoint exited with, which requires
232
+ # outliving it. Restoring `exec` here would silently delete the
233
+ # entrypoint-failed record — the single most valuable one, since it is the
234
+ # only witness to a broken interpreter that resolves but cannot run.
235
+ #
236
+ # Two measured costs, accepted knowingly:
237
+ # 1. One extra live process (this shell) for the hook's lifetime.
238
+ # 2. On a hard kill of the wrapper (Claude Code's 10s timeout), the
239
+ # Python child is reparented to PID 1 instead of dying with the
240
+ # shell — an orphan for the remainder of its own run. Not fixable by
241
+ # trapping: bash defers trap handling until the foreground command
242
+ # returns, and backgrounding the child would redirect its stdin from
243
+ # /dev/null in a non-interactive shell, which breaks the hook's stdin
244
+ # contract outright. The orphan is short-lived and harmless; a hook
245
+ # that cannot report its own failure is neither.
246
+ #
247
+ # Exit codes pass through UNCHANGED. Note which ones are NOT failures:
248
+ # 0 allow
249
+ # 2 the documented deny/block code — a gate doing its job, never logged
250
+ # Anything else means the entrypoint did not get to decide, which is
251
+ # exactly the case worth a record.
252
+ arka_run_hook() {
253
+ local hook="$1" module="$2" status
254
+ "$ARKA_PY" -m "$module"
255
+ status=$?
256
+ if [ "$status" -ne 0 ] && [ "$status" -ne 2 ]; then
257
+ arka_hook_degraded "$hook" "entrypoint-failed" "exit=$status module=$module py=$ARKA_PY"
258
+ fi
259
+ return "$status"
260
+ }
@@ -22,6 +22,62 @@ const path = require("node:path");
22
22
 
23
23
  const FAIL_OPEN_EXIT = 0;
24
24
 
25
+ // Growth cap, mirroring DEGRADED_LOG_MAX_BYTES in core/hooks/_shared.py
26
+ // and ARKA_DEGRADED_MAX_BYTES in _lib/arka_python.sh.
27
+ const DEGRADED_MAX_BYTES = 5 * 1024 * 1024;
28
+
29
+ /**
30
+ * Record that this shim allowed because it could not run, not because it
31
+ * decided — the same JSONL line, in the same file, as record_degraded() in
32
+ * core/hooks/_shared.py and arka_hook_degraded() in _lib/arka_python.sh:
33
+ * {ts,hook,reason,detail}, appended, never on stderr, never fatal, never a
34
+ * change to the exit code.
35
+ *
36
+ * This surface needs it most, not least. On POSIX the installer registers
37
+ * THIS file as the PreToolUse command whenever it and the fastpath engine
38
+ * are deployed (installer/adapters/claude-code.js::hookEntry), so the .sh
39
+ * chain — the one that carries the shell telemetry — is reached only by
40
+ * delegation. Every fail-open below therefore happens BEFORE any other
41
+ * writer exists to observe it: instrumenting only the .sh would have left
42
+ * the default install's three silent exits exactly as silent as before.
43
+ *
44
+ * `via=cjs` leads every detail because `unhandled-fail-open` is a reason
45
+ * the Python entrypoint also emits; the surface has to be readable from
46
+ * the line itself.
47
+ */
48
+ function recordDegraded(reason, detail) {
49
+ try {
50
+ const home = process.env.HOME || require("node:os").homedir();
51
+ const dir = path.join(home, ".arkaos", "telemetry");
52
+ const file = path.join(dir, "hook-degraded.jsonl");
53
+ fs.mkdirSync(dir, { recursive: true });
54
+ try {
55
+ if (fs.statSync(file).size >= DEGRADED_MAX_BYTES) {
56
+ fs.renameSync(file, file + ".1");
57
+ }
58
+ } catch {
59
+ // No log yet, or one that cannot be rotated: append regardless. A
60
+ // cap that suppresses the record defeats the point of recording.
61
+ }
62
+ const line = JSON.stringify({
63
+ // Second precision, no fractional part — byte-identical to the
64
+ // strftime/date formats the other two writers use.
65
+ ts: new Date().toISOString().replace(/\.\d+Z$/, "Z"),
66
+ hook: "pre-tool-use",
67
+ reason,
68
+ detail: String(detail == null ? "" : detail)
69
+ // JSON.stringify would escape control characters rather than emit
70
+ // them raw, but a tab-laden stack trace still makes the line
71
+ // unreadable; normalise for the same reason the shell writer does.
72
+ .replace(/[\u0000-\u001f\u007f]/g, " ")
73
+ .slice(0, 400),
74
+ });
75
+ fs.appendFileSync(file, line + "\n");
76
+ } catch {
77
+ // Telemetry must never be the thing that breaks a hook.
78
+ }
79
+ }
80
+
25
81
  function readStdin() {
26
82
  try {
27
83
  return fs.readFileSync(0, "utf8");
@@ -34,6 +90,10 @@ function delegate(rawStdin) {
34
90
  const sibling = path.join(__dirname, "pre-tool-use.sh");
35
91
  if (!fs.existsSync(sibling)) {
36
92
  // Missing sibling → bash would exit 127; the contract is fail-open.
93
+ // A split deploy that lands the .cjs without the .sh disables every
94
+ // gate for the whole install, so this is the highest-stakes silent
95
+ // allow in the file — it never reaches the shell writer.
96
+ recordDegraded("delegate-target-missing", `via=cjs sibling=${sibling}`);
37
97
  process.exit(FAIL_OPEN_EXIT);
38
98
  }
39
99
  // Lazy require: the fast path never pays for child_process.
@@ -44,6 +104,16 @@ function delegate(rawStdin) {
44
104
  env: { ...process.env, ARKA_HOOK_FASTPATH: "0" },
45
105
  });
46
106
  if (result.error || result.status === null) {
107
+ // Two distinct degradations, both invisible until now: bash could not
108
+ // be spawned at all (result.error — no bash on PATH, EACCES, EMFILE),
109
+ // or it was killed by a signal before deciding (status null). Neither
110
+ // is an allow the gate chain ever made.
111
+ const err = result.error;
112
+ recordDegraded(
113
+ "delegate-spawn-failed",
114
+ `via=cjs error=${err ? err.code || err.message : "none"} ` +
115
+ `status=${result.status} signal=${result.signal || "none"}`
116
+ );
47
117
  process.exit(FAIL_OPEN_EXIT);
48
118
  }
49
119
  process.exit(result.status);
@@ -104,6 +174,14 @@ function main() {
104
174
 
105
175
  try {
106
176
  main();
107
- } catch {
177
+ } catch (err) {
178
+ // The shim's own last resort. Structurally identical to the
179
+ // `unhandled-fail-open` handler at the bottom of core/hooks/
180
+ // pre_tool_use.py, and previously just as silent: exit 0, empty stdout,
181
+ // nothing anywhere to say the gate chain never ran.
182
+ recordDegraded(
183
+ "unhandled-fail-open",
184
+ `via=cjs ${err && err.stack ? err.stack : String(err)}`
185
+ );
108
186
  process.exit(FAIL_OPEN_EXIT);
109
187
  }