arkaos 5.15.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 (65) 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/statusline.sh +18 -3
  9. package/core/evals/record_cli.py +21 -2
  10. package/core/evals/verdict_labels.py +10 -1
  11. package/core/governance/aggregate_guard.py +41 -11
  12. package/core/governance/carry_advisor.py +166 -0
  13. package/core/governance/evidence_checks.py +420 -15
  14. package/core/governance/qg_tier.py +224 -0
  15. package/core/governance/qg_verdict.py +53 -0
  16. package/core/governance/redo_counter.py +56 -4
  17. package/core/hooks/post_tool_use.py +61 -17
  18. package/core/hooks/subagent_stop.py +25 -0
  19. package/core/hooks/user_prompt_submit.py +35 -62
  20. package/core/runtime/cost_governor.py +88 -4
  21. package/core/runtime/native_usage.py +120 -29
  22. package/core/runtime/pricing.py +25 -1
  23. package/core/shared/telemetry_rotate.py +86 -0
  24. package/core/workflow/dashboard.py +7 -5
  25. package/core/workflow/flow_enforcer.py +2 -0
  26. package/core/workflow/gate_checkpoint.py +5 -6
  27. package/core/workflow/research_gate.py +2 -0
  28. package/core/workflow/specialist_enforcer.py +2 -0
  29. package/core/workflow/state.py +137 -23
  30. package/core/workflow/state_reader.sh +6 -2
  31. package/departments/quality/SKILL.md +74 -12
  32. package/harness/codex/AGENTS.md +1 -1
  33. package/harness/copilot/copilot-instructions.md +1 -1
  34. package/harness/cursor/rules/arkaos.mdc +2 -2
  35. package/harness/gemini/GEMINI.md +1 -1
  36. package/harness/opencode/AGENTS.md +1 -1
  37. package/harness/opencode/agents/arka-architect-gabriel.md +1 -1
  38. package/harness/opencode/agents/arka-brand-director-valentina.md +1 -1
  39. package/harness/opencode/agents/arka-cfo-helena.md +1 -1
  40. package/harness/opencode/agents/arka-chief-of-staff-afonso.md +1 -1
  41. package/harness/opencode/agents/arka-community-strategist-beatriz.md +1 -1
  42. package/harness/opencode/agents/arka-content-strategist-rafael.md +1 -1
  43. package/harness/opencode/agents/arka-conversion-strategist-ines.md +1 -1
  44. package/harness/opencode/agents/arka-coo-sofia.md +1 -1
  45. package/harness/opencode/agents/arka-copy-director-eduardo.md +1 -1
  46. package/harness/opencode/agents/arka-cqo-marta.md +1 -1
  47. package/harness/opencode/agents/arka-cto-marco.md +1 -1
  48. package/harness/opencode/agents/arka-design-ops-lead-iris.md +1 -1
  49. package/harness/opencode/agents/arka-ecom-director-ricardo.md +1 -1
  50. package/harness/opencode/agents/arka-knowledge-director-clara.md +1 -1
  51. package/harness/opencode/agents/arka-leadership-director-rodrigo.md +1 -1
  52. package/harness/opencode/agents/arka-marketing-director-luna.md +1 -1
  53. package/harness/opencode/agents/arka-ops-lead-daniel.md +1 -1
  54. package/harness/opencode/agents/arka-pm-director-carolina.md +1 -1
  55. package/harness/opencode/agents/arka-revops-lead-vicente.md +1 -1
  56. package/harness/opencode/agents/arka-saas-strategist-tiago.md +1 -1
  57. package/harness/opencode/agents/arka-sales-director-miguel.md +1 -1
  58. package/harness/opencode/agents/arka-strategy-director-tomas.md +1 -1
  59. package/harness/opencode/agents/arka-tech-director-francisca.md +1 -1
  60. package/harness/opencode/agents/arka-tech-lead-paulo.md +1 -1
  61. package/harness/opencode/agents/arka-video-producer-simao.md +1 -1
  62. package/harness/zed/.rules +1 -1
  63. package/knowledge/skills-manifest.json +1 -1
  64. package/package.json +1 -1
  65. package/pyproject.toml +1 -1
@@ -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.15.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."
@@ -118,11 +118,26 @@ fi
118
118
  COST_FMT=$(printf '$%.2f' "${COST:-0}")
119
119
 
120
120
  # ─── Workflow gate + budget (F2-5, statusline v3) ─────────────────────────
121
- # Read ~/.arkaos/workflow-state.json directly (plain JSON, one jq call) —
122
- # no Python spawn on this hot path (the statusline re-renders continuously).
121
+ # Read the per-project .arka/workflow-state.json directly (plain JSON,
122
+ # one jq call) — no Python spawn on this hot path (the statusline
123
+ # re-renders continuously). The path mirrors core/workflow/state.py's
124
+ # resolver: git toplevel, cwd fallback (Gate Economy PR-9, QG round 1).
123
125
  # Shows the active workflow, current gate as G<n>/<total>, and violations.
124
126
  WF_SEGMENT=""
125
- WF_STATE="$HOME/.arkaos/workflow-state.json"
127
+ # Payload-cwd convention (QG round 2): the state root follows WORK_DIR —
128
+ # the runtime's own statement of the project — never the process cwd,
129
+ # and the toplevel lookup rides the same 5s cache as the branch query.
130
+ WF_ROOT=""
131
+ if [ -n "$WORK_DIR" ] && [ -d "$WORK_DIR" ]; then
132
+ ROOT_CACHE_FILE="${GIT_CACHE}-root-${CACHE_KEY}"
133
+ if [ -f "$ROOT_CACHE_FILE" ] && [ $(($(date +%s) - $(stat -f%m "$ROOT_CACHE_FILE" 2>/dev/null || stat -c%Y "$ROOT_CACHE_FILE" 2>/dev/null || echo 0))) -lt $CACHE_MAX_AGE ]; then
134
+ WF_ROOT=$(cat "$ROOT_CACHE_FILE" 2>/dev/null)
135
+ else
136
+ WF_ROOT=$(git -C "$WORK_DIR" rev-parse --show-toplevel 2>/dev/null || echo "$WORK_DIR")
137
+ echo "${WF_ROOT}" > "$ROOT_CACHE_FILE" 2>/dev/null
138
+ fi
139
+ fi
140
+ WF_STATE="${WF_ROOT:-$WORK_DIR}/.arka/workflow-state.json"
126
141
  if [ -f "$WF_STATE" ]; then
127
142
  WF_LINE=$(jq -r '
128
143
  (.phases // {}) as $p
@@ -124,7 +124,15 @@ def _drive_redo(verdict: QGVerdict, session_id: str) -> str:
124
124
  from core.governance import redo_counter
125
125
 
126
126
  if verdict.verdict == "REJECTED":
127
- return redo_counter.record_rejected(session_id).to_message()
127
+ state = redo_counter.record_rejected(session_id)
128
+ message = state.to_message()
129
+ if state.escalate:
130
+ # Gate Economy: the cap is actionable, not decorative — the
131
+ # escalation lands on stderr (and as an ESCALATE marker in
132
+ # the session ledger), never only inside a JSON field the
133
+ # orchestrator may not read.
134
+ print(message, file=sys.stderr)
135
+ return message
128
136
  redo_counter.reset(session_id)
129
137
  return ""
130
138
 
@@ -220,6 +228,17 @@ def _ledger_match(verdict: QGVerdict, session_id: str) -> str:
220
228
 
221
229
 
222
230
  def _record_label(verdict: QGVerdict, args: argparse.Namespace) -> None:
231
+ round_label = args.round_label
232
+ if not round_label and args.session_id:
233
+ # Gate Economy: the round signal was dead (112 None / 95 '' of
234
+ # 234 records) because no caller passed it. Derive it from the
235
+ # redo counter: N rejections so far → this verdict belongs to
236
+ # round N+1.
237
+ from core.governance import redo_counter
238
+
239
+ round_label = str(
240
+ redo_counter.current(args.session_id).count + 1
241
+ )
223
242
  record_verdict_label(
224
243
  verdict,
225
244
  deliverable=args.deliverable,
@@ -227,7 +246,7 @@ def _record_label(verdict: QGVerdict, args: argparse.Namespace) -> None:
227
246
  eval_task_id=args.eval_task_id,
228
247
  session_id=args.session_id,
229
248
  kind=args.kind,
230
- round_label=args.round_label,
249
+ round_label=round_label,
231
250
  head=args.head,
232
251
  )
233
252
 
@@ -17,6 +17,7 @@ from __future__ import annotations
17
17
 
18
18
  import json
19
19
  import os
20
+ import re
20
21
  from datetime import UTC, datetime
21
22
  from pathlib import Path
22
23
  from typing import Any
@@ -43,6 +44,14 @@ def _judge_labels_path() -> Path:
43
44
  return Path(override) if override else DEFAULT_JUDGE_LABELS_PATH
44
45
 
45
46
 
47
+ def _normalize_round(value: str) -> str:
48
+ """Round labels arrived ad-hoc ('3', 'r5', 'redo-1', 'aggregate-r26')
49
+ and unparseable values killed the round signal (Gate Economy). Keep
50
+ the first integer; anything without one records as ''."""
51
+ match = re.search(r"(\d+)", str(value or ""))
52
+ return match.group(1) if match else ""
53
+
54
+
46
55
  def record_verdict_label(
47
56
  verdict: QGVerdict,
48
57
  deliverable: str = "",
@@ -69,7 +78,7 @@ def record_verdict_label(
69
78
  "eval_task_id": str(eval_task_id or ""),
70
79
  "session_id": str(session_id or ""),
71
80
  "kind": str(kind or ""),
72
- "round": str(round_label or ""),
81
+ "round": _normalize_round(round_label),
73
82
  "head": str(head or ""),
74
83
  **verdict.model_dump(),
75
84
  }
@@ -277,13 +277,22 @@ def _matches(a: frozenset[str], b: frozenset[str]) -> bool:
277
277
  return bool(a) and bool(b) and (a <= b or b <= a)
278
278
 
279
279
 
280
- def _covered(key: frozenset[str], aggregate: dict, approved: bool) -> str:
280
+ def _covered(
281
+ key: frozenset[str],
282
+ aggregate: dict[str, object],
283
+ approved: bool,
284
+ severity: str = "",
285
+ ) -> str:
281
286
  """Status of one CONFIRMED reviewer blocker against the aggregate.
282
287
 
283
288
  Returns "ok", "absent", "bare-refute" (REFUTED without a
284
- substantive reason) or "carried-approved" (kept un-refuted while
289
+ substantive reason), "carried-approved" (kept un-refuted while
285
290
  the aggregate approves — flipping the decision instead of hiding
286
- the string is laundering too).
291
+ the string is laundering too) or "minor-carried" (Gate Economy:
292
+ the REVIEWER's own severity is "minor", so the finding may ride an
293
+ APPROVED aggregate as a recorded fix-forward; the reviewer's
294
+ severity is authoritative — an aggregate relabeling a major as
295
+ minor still refuses).
287
296
  """
288
297
  for entry in aggregate.get("blockers") or []:
289
298
  if not isinstance(entry, dict):
@@ -294,7 +303,9 @@ def _covered(key: frozenset[str], aggregate: dict, approved: bool) -> str:
294
303
  if len(_norm(entry.get("detail"))) < _MIN_REFUTE_DETAIL:
295
304
  return "bare-refute"
296
305
  return "ok"
297
- return "carried-approved" if approved else "ok"
306
+ if approved:
307
+ return "minor-carried" if severity == "minor" else "carried-approved"
308
+ return "ok"
298
309
  return "absent"
299
310
 
300
311
 
@@ -471,9 +482,16 @@ _COVERAGE_REASONS = {
471
482
 
472
483
  def _blocker_reasons(
473
484
  aggregate: dict, verdicts: list[tuple[str, dict]], approved: bool
474
- ) -> list[str]:
475
- """CONFIRMED reviewer blockers must not vanish (or be out-voted)."""
485
+ ) -> tuple[list[str], list[str]]:
486
+ """CONFIRMED reviewer blockers must not vanish (or be out-voted).
487
+
488
+ Returns ``(reasons, warnings)``. A minor-severity finding carried
489
+ on an APPROVED aggregate is a recorded fix-forward (Gate Economy),
490
+ not a refusal — and the REVIEWER's severity decides, so an
491
+ aggregate can never launder a major by relabeling it minor.
492
+ """
476
493
  reasons: list[str] = []
494
+ warnings: list[str] = []
477
495
  for reviewer_id, verdict in verdicts:
478
496
  for blocker in verdict.get("blockers") or []:
479
497
  if not isinstance(blocker, dict):
@@ -491,13 +509,24 @@ def _blocker_reasons(
491
509
  "check; the redo round supersedes this record"
492
510
  )
493
511
  continue
494
- status = _covered(key, aggregate, approved)
512
+ status = _covered(
513
+ key, aggregate, approved, _norm(blocker.get("severity"))
514
+ )
515
+ if status == "minor-carried":
516
+ warnings.append(
517
+ f"minor finding "
518
+ f"'{_norm(blocker.get('check')) or '(no check field)'}' "
519
+ f"({reviewer_id}) rides the APPROVED aggregate as a "
520
+ "fix-forward — the correction belongs in the "
521
+ "aggregate notes"
522
+ )
523
+ continue
495
524
  if status != "ok":
496
525
  reasons.append(_COVERAGE_REASONS[status].format(
497
526
  check=_norm(blocker.get("check")) or "(no check field)",
498
527
  rid=reviewer_id,
499
528
  ))
500
- return reasons
529
+ return reasons, warnings
501
530
 
502
531
 
503
532
  def _own_finding_warnings(
@@ -621,14 +650,15 @@ def _reasons(
621
650
  approved = _norm(aggregate.get("verdict")) == "approved"
622
651
  issues, notes = _digest_issues(aggregate, verdicts)
623
652
  issues += _ended_issues(session_id, artifact_names)
624
- reasons = (
625
- _verdict_reasons(aggregate, verdicts)
626
- + _blocker_reasons(aggregate, verdicts, approved)
653
+ blocker_reasons, blocker_warnings = _blocker_reasons(
654
+ aggregate, verdicts, approved
627
655
  )
656
+ reasons = _verdict_reasons(aggregate, verdicts) + blocker_reasons
628
657
  warnings = (
629
658
  notes
630
659
  + _check_key_warnings(verdicts)
631
660
  + _own_finding_warnings(aggregate, verdicts)
661
+ + blocker_warnings
632
662
  )
633
663
  if approved:
634
664
  return issues + reasons, warnings