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.
- package/THE-ARKAOS-GUIDE.md +1 -1
- package/VERSION +1 -1
- package/arka/skills/flow/SKILL.md +15 -2
- package/config/claude-agents/eduardo-copy.md +21 -7
- package/config/claude-agents/francisca-tech.md +38 -12
- package/config/claude-agents/marta-cqo.md +116 -19
- package/config/constitution.yaml +7 -3
- package/config/hooks/_lib/arka_python.sh +104 -0
- package/config/hooks/pre-tool-use.cjs +79 -1
- package/config/hooks/pre-tool-use.sh +19 -2
- package/config/statusline.sh +18 -3
- package/core/cognition/dreaming.py +323 -26
- package/core/cognition/reorganizer.py +18 -6
- package/core/cognition/reorganizer_cli.py +61 -14
- package/core/evals/record_cli.py +21 -2
- package/core/evals/verdict_labels.py +10 -1
- package/core/governance/aggregate_guard.py +41 -11
- package/core/governance/carry_advisor.py +166 -0
- package/core/governance/evidence_checks.py +945 -81
- package/core/governance/qg_tier.py +224 -0
- package/core/governance/qg_verdict.py +53 -0
- package/core/governance/redo_counter.py +56 -4
- package/core/hooks/_shared.py +132 -0
- package/core/hooks/post_tool_use.py +99 -20
- package/core/hooks/pre_tool_use.py +13 -0
- package/core/hooks/subagent_stop.py +25 -0
- package/core/hooks/user_prompt_submit.py +35 -62
- package/core/runtime/cost_governor.py +88 -4
- package/core/runtime/native_usage.py +120 -29
- package/core/runtime/pricing.py +25 -1
- package/core/runtime/user_paths.py +40 -8
- package/core/shared/telemetry_rotate.py +86 -0
- package/core/sync/engine.py +2 -0
- package/core/sync/feature_injector.py +182 -0
- package/core/sync/features/workflow-tiers.yaml +1 -1
- package/core/sync/marker_audit.py +253 -0
- package/core/sync/reporter.py +74 -2
- package/core/sync/schema.py +103 -1
- package/core/terminal/session.py +64 -32
- package/core/workflow/dashboard.py +7 -5
- package/core/workflow/flow_enforcer.py +2 -0
- package/core/workflow/gate_checkpoint.py +5 -6
- package/core/workflow/research_gate.py +2 -0
- package/core/workflow/specialist_enforcer.py +2 -0
- package/core/workflow/state.py +137 -23
- package/core/workflow/state_reader.sh +6 -2
- package/departments/ops/skills/update/references/sync-engine.md +1 -1
- package/departments/ops/skills/update/references/workflows.md +2 -1
- package/departments/quality/SKILL.md +74 -12
- package/harness/codex/AGENTS.md +1 -1
- package/harness/copilot/copilot-instructions.md +1 -1
- package/harness/cursor/rules/arkaos.mdc +2 -2
- package/harness/gemini/GEMINI.md +1 -1
- package/harness/opencode/AGENTS.md +1 -1
- package/harness/opencode/agents/arka-architect-gabriel.md +1 -1
- package/harness/opencode/agents/arka-brand-director-valentina.md +1 -1
- package/harness/opencode/agents/arka-cfo-helena.md +1 -1
- package/harness/opencode/agents/arka-chief-of-staff-afonso.md +1 -1
- package/harness/opencode/agents/arka-community-strategist-beatriz.md +1 -1
- package/harness/opencode/agents/arka-content-strategist-rafael.md +1 -1
- package/harness/opencode/agents/arka-conversion-strategist-ines.md +1 -1
- package/harness/opencode/agents/arka-coo-sofia.md +1 -1
- package/harness/opencode/agents/arka-copy-director-eduardo.md +1 -1
- package/harness/opencode/agents/arka-cqo-marta.md +1 -1
- package/harness/opencode/agents/arka-cto-marco.md +1 -1
- package/harness/opencode/agents/arka-design-ops-lead-iris.md +1 -1
- package/harness/opencode/agents/arka-ecom-director-ricardo.md +1 -1
- package/harness/opencode/agents/arka-knowledge-director-clara.md +1 -1
- package/harness/opencode/agents/arka-leadership-director-rodrigo.md +1 -1
- package/harness/opencode/agents/arka-marketing-director-luna.md +1 -1
- package/harness/opencode/agents/arka-ops-lead-daniel.md +1 -1
- package/harness/opencode/agents/arka-pm-director-carolina.md +1 -1
- package/harness/opencode/agents/arka-revops-lead-vicente.md +1 -1
- package/harness/opencode/agents/arka-saas-strategist-tiago.md +1 -1
- package/harness/opencode/agents/arka-sales-director-miguel.md +1 -1
- package/harness/opencode/agents/arka-strategy-director-tomas.md +1 -1
- package/harness/opencode/agents/arka-tech-director-francisca.md +1 -1
- package/harness/opencode/agents/arka-tech-lead-paulo.md +1 -1
- package/harness/opencode/agents/arka-video-producer-simao.md +1 -1
- package/harness/zed/.rules +1 -1
- package/installer/claude-md.js +42 -4
- package/knowledge/skills-manifest.json +1 -1
- package/package.json +1 -1
- package/pyproject.toml +6 -1
- package/scripts/dashboard-api.py +16 -1
- package/scripts/start-dashboard.ps1 +14 -0
package/THE-ARKAOS-GUIDE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# The ArkaOS Guide
|
|
2
2
|
|
|
3
|
-
> v5.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
26
|
-
|
|
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
|
|
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
|
|
58
|
-
correction — `check` names the evidence check or rubric
|
|
59
|
-
aggregate's coverage matching keys on it; never leave it
|
|
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
|
|
25
|
-
not your impression of it.
|
|
26
|
-
2. `tests` — exit code decides
|
|
27
|
-
insufficient: say so explicitly, never
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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.
|
|
57
|
-
references and fix suggestions — `check` names the evidence
|
|
58
|
-
rubric area (the aggregate's coverage matching keys on it;
|
|
59
|
-
leave it empty)
|
|
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
|
|
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
|
-
|
|
28
|
-
|
|
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`, `
|
|
37
|
-
|
|
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
|
|
47
|
-
|
|
48
|
-
|
|
86
|
+
telemetry and discarded. Independently reproduce the CONFIRMED ones
|
|
87
|
+
that would flip the verdict — the 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
|
|
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}],
|
|
80
|
-
`model_used`, `notes`, `evidence_digest`
|
|
81
|
-
report you aggregated — mandatory since
|
|
82
|
-
earlier review over a report change,
|
|
83
|
-
[{reviewer, evidence_digest, reason}] naming the
|
|
84
|
-
actually reviewed and why the review still
|
|
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", "
|
|
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
|
|
205
|
+
"notes": "Engine pass but 1 CONFIRMED major finding, reproduced by my own hand."}
|
|
109
206
|
```
|
|
110
207
|
|
|
111
208
|
## Reporting (verbatim, never relay)
|
package/config/constitution.yaml
CHANGED
|
@@ -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
|
|
19
|
-
enforcement: "
|
|
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
|
}
|