arkaos 4.38.0 → 4.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THE-ARKAOS-GUIDE.md +1 -1
- package/VERSION +1 -1
- package/bin/arka-doctor +3 -2
- package/config/hooks/_lib/fastpath/engine.cjs +44 -7
- package/config/hooks/cwd-changed.ps1 +11 -5
- package/config/hooks/cwd-changed.sh +11 -4
- package/config/hooks/gate-manifest.json +2 -6
- package/config/hooks/post-tool-use.ps1 +42 -7
- package/config/hooks/session-start.sh +25 -1
- package/config/hooks/user-prompt-submit.ps1 +9 -7
- package/config/hooks/user-prompt-submit.sh +3 -2
- package/config/settings-template.json +12 -1
- package/core/governance/evidence_checks.py +33 -3
- package/core/governance/reviewer_ledger.py +708 -0
- package/core/hooks/_shared.py +20 -0
- package/core/hooks/gate_manifest.py +14 -6
- package/core/hooks/post_tool_use.py +164 -50
- package/core/hooks/session_end.py +16 -0
- package/core/hooks/session_start.py +62 -13
- package/core/hooks/stop.py +38 -0
- package/core/hooks/subagent_stop.py +158 -17
- package/core/hooks/user_prompt_submit.py +230 -81
- package/core/runtime/opencode.py +2 -2
- package/core/synapse/engine.py +2 -2
- package/dashboard/nuxt.config.ts +6 -0
- package/departments/ops/skills/hookify/SKILL.md +7 -4
- package/harness/codex/AGENTS.md +1 -1
- package/harness/copilot/copilot-instructions.md +1 -1
- package/harness/cursor/rules/arkaos.mdc +2 -2
- package/harness/gemini/GEMINI.md +1 -1
- package/harness/opencode/AGENTS.md +1 -1
- package/harness/opencode/agents/arka-architect-gabriel.md +1 -1
- package/harness/opencode/agents/arka-brand-director-valentina.md +1 -1
- package/harness/opencode/agents/arka-cfo-helena.md +1 -1
- package/harness/opencode/agents/arka-chief-of-staff-afonso.md +1 -1
- package/harness/opencode/agents/arka-community-strategist-beatriz.md +1 -1
- package/harness/opencode/agents/arka-content-strategist-rafael.md +1 -1
- package/harness/opencode/agents/arka-conversion-strategist-ines.md +1 -1
- package/harness/opencode/agents/arka-coo-sofia.md +1 -1
- package/harness/opencode/agents/arka-copy-director-eduardo.md +1 -1
- package/harness/opencode/agents/arka-cqo-marta.md +1 -1
- package/harness/opencode/agents/arka-cto-marco.md +1 -1
- package/harness/opencode/agents/arka-design-ops-lead-iris.md +1 -1
- package/harness/opencode/agents/arka-ecom-director-ricardo.md +1 -1
- package/harness/opencode/agents/arka-knowledge-director-clara.md +1 -1
- package/harness/opencode/agents/arka-leadership-director-rodrigo.md +1 -1
- package/harness/opencode/agents/arka-marketing-director-luna.md +1 -1
- package/harness/opencode/agents/arka-ops-lead-daniel.md +1 -1
- package/harness/opencode/agents/arka-pm-director-carolina.md +1 -1
- package/harness/opencode/agents/arka-revops-lead-vicente.md +1 -1
- package/harness/opencode/agents/arka-saas-strategist-tiago.md +1 -1
- package/harness/opencode/agents/arka-sales-director-miguel.md +1 -1
- package/harness/opencode/agents/arka-strategy-director-tomas.md +1 -1
- package/harness/opencode/agents/arka-tech-director-francisca.md +1 -1
- package/harness/opencode/agents/arka-tech-lead-paulo.md +1 -1
- package/harness/opencode/agents/arka-video-producer-simao.md +1 -1
- package/harness/zed/.rules +1 -1
- package/installer/adapters/claude-code.js +14 -2
- package/installer/doctor.js +21 -0
- package/knowledge/commands-registry.json.bak +705 -98
- package/knowledge/skills-manifest.json +1 -1
- package/mcps/arka-tools/pyproject.toml +1 -1
- package/package.json +1 -1
- package/pyproject.toml +5 -2
- package/scripts/start-dashboard.ps1 +42 -2
- package/scripts/synapse-bridge.py +1 -1
- package/scripts/tools/prompt_surface_benchmark.py +8 -6
- package/config/hooks/post-tool-use-v2.sh +0 -120
- package/config/hooks/pre-compact-v2.sh +0 -43
- package/config/hooks/user-prompt-submit-v2.sh +0 -159
package/THE-ARKAOS-GUIDE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# The ArkaOS Guide
|
|
2
2
|
|
|
3
|
-
> v4.
|
|
3
|
+
> v4.40.0 — 89 agents, 17 departments, 332 skills, 297 commands, 17 ADRs.
|
|
4
4
|
> One file, everything you need to start. Generated by `scripts/guide_gen.py` — never hand-edited.
|
|
5
5
|
|
|
6
6
|
## What it is
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
4.
|
|
1
|
+
4.40.0
|
package/bin/arka-doctor
CHANGED
|
@@ -162,9 +162,10 @@ if [ "$FIX_MODE" = true ] && [ "$HOOKS_OK" = false ]; then
|
|
|
162
162
|
--arg pc "$HOOKS_DIR/pre-compact.sh" \
|
|
163
163
|
--arg ptu "$HOOKS_DIR/post-tool-use.sh" \
|
|
164
164
|
'{
|
|
165
|
-
"UserPromptSubmit": [{"hooks":[{"type":"command","command":$ups,"timeout":
|
|
165
|
+
"UserPromptSubmit": [{"hooks":[{"type":"command","command":$ups,"timeout":20}]}],
|
|
166
166
|
"PreCompact": [{"hooks":[{"type":"command","command":$pc,"timeout":30}]}],
|
|
167
|
-
"PostToolUse": [{"hooks":[{"type":"command","command":$ptu,"timeout":5}]}]
|
|
167
|
+
"PostToolUse": [{"hooks":[{"type":"command","command":$ptu,"timeout":5}]}],
|
|
168
|
+
"PostToolUseFailure": [{"hooks":[{"type":"command","command":$ptu,"timeout":5}]}]
|
|
168
169
|
}')
|
|
169
170
|
jq --argjson hooks "$HOOKS_JSON" '.hooks = $hooks' "$SETTINGS_FILE" > "$SETTINGS_FILE.tmp" && \
|
|
170
171
|
mv "$SETTINGS_FILE.tmp" "$SETTINGS_FILE"
|
|
@@ -17,7 +17,10 @@
|
|
|
17
17
|
* this file — that is exactly the drift the manifest exists to kill.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
// v2: post.success_exit_codes removed with the PostToolUseFailure
|
|
21
|
+
// wiring — an old engine + new manifest mismatch now delegates via
|
|
22
|
+
// validateManifest instead of crashing into the shim's fail-open {}.
|
|
23
|
+
const SCHEMA_VERSION = 2;
|
|
21
24
|
|
|
22
25
|
/** Python truthiness — NOT JS Boolean(): [] and {} are falsy in Python,
|
|
23
26
|
* "false" (non-empty string) is truthy in both. flow_enforcer coerces
|
|
@@ -255,6 +258,34 @@ function flowAuthFresh(sid, manifest, ctx) {
|
|
|
255
258
|
return nowSeconds - ts <= manifest.numbers.auth_ttl_seconds;
|
|
256
259
|
}
|
|
257
260
|
|
|
261
|
+
/** Textual output of a PostToolUse tool_response — parity with the
|
|
262
|
+
* PostToolUse paths of _tool_text in post_tool_use.py (string,
|
|
263
|
+
* stdout/stderr and Task/content shapes, isAsync guard and Python's
|
|
264
|
+
* `or ""` falsy coercion included). Failure-event payloads never
|
|
265
|
+
* reach this — decidePost delegates PostToolUseFailure outright. */
|
|
266
|
+
function toolText(payload) {
|
|
267
|
+
const response = payload.tool_response;
|
|
268
|
+
if (typeof response === "string") return response;
|
|
269
|
+
if (response && typeof response === "object") {
|
|
270
|
+
if ("stdout" in response || "stderr" in response) {
|
|
271
|
+
return [response.stdout, response.stderr]
|
|
272
|
+
.map((part) => String(part || ""))
|
|
273
|
+
.filter(Boolean)
|
|
274
|
+
.join("\n");
|
|
275
|
+
}
|
|
276
|
+
if (response.isAsync) return "";
|
|
277
|
+
const content = response.content;
|
|
278
|
+
if (typeof content === "string") return content;
|
|
279
|
+
if (Array.isArray(content)) {
|
|
280
|
+
return content
|
|
281
|
+
.filter((block) => block && block.type === "text")
|
|
282
|
+
.map((block) => String(block.text || ""))
|
|
283
|
+
.join("");
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return "";
|
|
287
|
+
}
|
|
288
|
+
|
|
258
289
|
/**
|
|
259
290
|
* PostToolUse decision. Returns
|
|
260
291
|
* {action:"fast-exit", stdout:"{}", writes:[...]} — mcp telemetry only
|
|
@@ -267,18 +298,23 @@ function decidePost(payload, manifest, ctx) {
|
|
|
267
298
|
}
|
|
268
299
|
const toolName = String(payload.tool_name || "");
|
|
269
300
|
|
|
301
|
+
// A failing tool throws inside the runtime and fires
|
|
302
|
+
// PostToolUseFailure (2.1.220 contract) — failed-command turns are
|
|
303
|
+
// the gotchas pipeline's primary material, so they always go to
|
|
304
|
+
// Python. Rare by construction: no fast path is worth having here.
|
|
305
|
+
if (payload.hook_event_name === "PostToolUseFailure") {
|
|
306
|
+
return { action: "delegate", reason: "failure-event" };
|
|
307
|
+
}
|
|
308
|
+
|
|
270
309
|
// Q3/Q4 — plan approval (G2) + subagent tracking live in Python.
|
|
271
310
|
if (manifest.tools.post_delegate_always.includes(toolName)) {
|
|
272
311
|
return { action: "delegate", reason: "post-stateful-tool" };
|
|
273
312
|
}
|
|
274
313
|
|
|
275
314
|
// Q5 — error path: gotchas, violations, cognition capture, metrics.
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}
|
|
280
|
-
const toolOutput = String(payload.tool_output ?? "");
|
|
281
|
-
if (compileRegex(manifest.post.error_trigger).test(toolOutput)) {
|
|
315
|
+
// PostToolUse never carries an exit code (failures fire the event
|
|
316
|
+
// above), so the only error signal on this path is trigger text.
|
|
317
|
+
if (compileRegex(manifest.post.error_trigger).test(toolText(payload))) {
|
|
282
318
|
return { action: "delegate", reason: "error-trigger" };
|
|
283
319
|
}
|
|
284
320
|
|
|
@@ -333,5 +369,6 @@ module.exports = {
|
|
|
333
369
|
pythonTruthy,
|
|
334
370
|
readJsonFile,
|
|
335
371
|
safeSessionId,
|
|
372
|
+
toolText,
|
|
336
373
|
validateManifest,
|
|
337
374
|
};
|
|
@@ -2,13 +2,17 @@
|
|
|
2
2
|
# ArkaOS — CwdChanged Hook (Windows / PowerShell 5.1+)
|
|
3
3
|
#
|
|
4
4
|
# Port of config/hooks/cwd-changed.sh. Fires when the working directory
|
|
5
|
-
# changes. Detects ecosystem and
|
|
6
|
-
#
|
|
5
|
+
# changes. Detects the ecosystem/stack and surfaces them to the operator
|
|
6
|
+
# as a system message.
|
|
7
7
|
#
|
|
8
8
|
# Contract:
|
|
9
9
|
# - Reads a JSON object on stdin with a `cwd` field.
|
|
10
|
-
# - Emits `{"
|
|
11
|
-
#
|
|
10
|
+
# - Emits `{"systemMessage": "..."}` on stdout when context is found, or
|
|
11
|
+
# nothing (and exit 0) when there is nothing to say. CwdChanged's
|
|
12
|
+
# hookSpecificOutput accepts watchPaths only (Claude Code 2.1.220) —
|
|
13
|
+
# additionalContext is unreachable on this event. On later prompts the
|
|
14
|
+
# cwd-reading Synapse layers refresh (git branch, graph context,
|
|
15
|
+
# knowledge retrieval, cwd-scoped session memory).
|
|
12
16
|
# ============================================================================
|
|
13
17
|
|
|
14
18
|
$ErrorActionPreference = 'Stop'
|
|
@@ -148,5 +152,7 @@ if ($descriptor) {
|
|
|
148
152
|
}
|
|
149
153
|
|
|
150
154
|
if ($context) {
|
|
151
|
-
|
|
155
|
+
# CwdChanged accepts no additionalContext (watchPaths only) — the
|
|
156
|
+
# top-level systemMessage is the one deliverable surface on this event.
|
|
157
|
+
[pscustomobject]@{ systemMessage = $context } | ConvertTo-Json -Compress
|
|
152
158
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# ============================================================================
|
|
3
3
|
# ArkaOS — CwdChanged Hook
|
|
4
|
-
# Fires when the working directory changes. Detects ecosystem
|
|
5
|
-
#
|
|
4
|
+
# Fires when the working directory changes. Detects the ecosystem/stack
|
|
5
|
+
# and surfaces a systemMessage naming them (the one deliverable surface
|
|
6
|
+
# on this event — CwdChanged's hookSpecificOutput accepts watchPaths
|
|
7
|
+
# only).
|
|
6
8
|
# ============================================================================
|
|
7
9
|
|
|
8
10
|
input=$(cat)
|
|
@@ -116,7 +118,12 @@ if [ -n "$DESCRIPTOR" ]; then
|
|
|
116
118
|
fi
|
|
117
119
|
|
|
118
120
|
if [ -n "$CONTEXT" ]; then
|
|
119
|
-
#
|
|
121
|
+
# CwdChanged's hookSpecificOutput accepts watchPaths ONLY (Claude Code
|
|
122
|
+
# 2.1.220 schema) — additionalContext is unreachable on this event. The
|
|
123
|
+
# top-level systemMessage key IS consumed and surfaced to the operator.
|
|
124
|
+
# On later prompts the cwd-reading Synapse layers refresh (git branch,
|
|
125
|
+
# graph context, knowledge retrieval, cwd-scoped session memory).
|
|
126
|
+
# Built with jq so any quote/backslash in the ecosystem name or
|
|
120
127
|
# descriptor path is escaped, never breaking the envelope (OWASP A03).
|
|
121
|
-
jq -nc --arg
|
|
128
|
+
jq -nc --arg msg "$CONTEXT" '{systemMessage: $msg}'
|
|
122
129
|
fi
|
|
@@ -635,13 +635,9 @@
|
|
|
635
635
|
"flags": "i",
|
|
636
636
|
"js": "(error:|fatal:|exception:|failed|ENOENT|EACCES|EPERM|panic:)",
|
|
637
637
|
"py": "(error:|fatal:|exception:|failed|ENOENT|EACCES|EPERM|panic:)"
|
|
638
|
-
}
|
|
639
|
-
"success_exit_codes": [
|
|
640
|
-
"0",
|
|
641
|
-
""
|
|
642
|
-
]
|
|
638
|
+
}
|
|
643
639
|
},
|
|
644
|
-
"schema_version":
|
|
640
|
+
"schema_version": 2,
|
|
645
641
|
"session_id": {
|
|
646
642
|
"flags": "",
|
|
647
643
|
"js": "^[A-Za-z0-9._-]{1,128}$",
|
|
@@ -6,8 +6,16 @@
|
|
|
6
6
|
# which mistakes repeat across projects.
|
|
7
7
|
#
|
|
8
8
|
# Contract:
|
|
9
|
-
# - Reads a hook payload JSON from stdin
|
|
10
|
-
#
|
|
9
|
+
# - Reads a hook payload JSON from stdin. Registered for two events
|
|
10
|
+
# (2.1.220 contract): PostToolUse delivers a structured tool_response
|
|
11
|
+
# (Bash dicts carry stdout/stderr; this port handles the string and
|
|
12
|
+
# stdout/stderr shapes only — no Task/content path); a failing tool
|
|
13
|
+
# throws inside the runtime and fires PostToolUseFailure instead,
|
|
14
|
+
# which carries the output in `error`, prefixed "Exit code N" for
|
|
15
|
+
# Bash. The payload has no top-level output or exit-code fields.
|
|
16
|
+
# Also read: tool_name, cwd, session_id, and a legacy
|
|
17
|
+
# assistant_message the payload never carries (dead read, see
|
|
18
|
+
# core/hooks/post_tool_use.py `_confirm_flow_authorization`).
|
|
11
19
|
# - Always exits 0 and writes `{}` on stdout — PostToolUse does not inject
|
|
12
20
|
# context.
|
|
13
21
|
# - Side effect: updates %USERPROFILE%\.arkaos\gotchas.json and
|
|
@@ -100,7 +108,7 @@ $stdinText = [Console]::In.ReadToEnd()
|
|
|
100
108
|
$shouldProcessGotchas = $true
|
|
101
109
|
$payload = $null
|
|
102
110
|
$toolName = ''
|
|
103
|
-
$
|
|
111
|
+
$toolText = ''
|
|
104
112
|
$exitCode = '0'
|
|
105
113
|
$cwd = ''
|
|
106
114
|
|
|
@@ -116,10 +124,37 @@ if ([string]::IsNullOrWhiteSpace($stdinText)) {
|
|
|
116
124
|
|
|
117
125
|
if ($shouldProcessGotchas -and $null -ne $payload) {
|
|
118
126
|
$toolName = if ($null -ne $payload.tool_name) { [string]$payload.tool_name } else { '' }
|
|
119
|
-
$toolOutput = if ($null -ne $payload.tool_output) { [string]$payload.tool_output } else { '' }
|
|
120
|
-
$exitCode = if ($null -ne $payload.exit_code) { [string]$payload.exit_code } else { '0' }
|
|
121
127
|
$cwd = if ($null -ne $payload.cwd) { [string]$payload.cwd } else { '' }
|
|
122
128
|
|
|
129
|
+
# Event → (text, exit code). Mirrors the string and stdout/stderr
|
|
130
|
+
# paths of _tool_text / _tool_exit_code in core/hooks/post_tool_use.py
|
|
131
|
+
# (no Task/content path in this port). On PostToolUseFailure the
|
|
132
|
+
# output arrives in `error`; the runtime's "Exit code N" envelope is
|
|
133
|
+
# its wrapper, not command output, so it is stripped from the text
|
|
134
|
+
# and surfaced as the exit code. A user interrupt is not a failure.
|
|
135
|
+
$eventName = if ($null -ne $payload.hook_event_name) { [string]$payload.hook_event_name } else { '' }
|
|
136
|
+
if ($eventName -eq 'PostToolUseFailure') {
|
|
137
|
+
$errText = if ($null -ne $payload.error) { [string]$payload.error } else { '' }
|
|
138
|
+
$envMatch = [regex]::Match($errText, '^Exit code (\d+)\n?')
|
|
139
|
+
if ($envMatch.Success) {
|
|
140
|
+
$exitCode = $envMatch.Groups[1].Value
|
|
141
|
+
$toolText = $errText.Substring($envMatch.Length)
|
|
142
|
+
} else {
|
|
143
|
+
$exitCode = '1'
|
|
144
|
+
$toolText = $errText
|
|
145
|
+
}
|
|
146
|
+
if ($payload.is_interrupt) { $exitCode = '0' }
|
|
147
|
+
} else {
|
|
148
|
+
$resp = $payload.tool_response
|
|
149
|
+
if ($resp -is [string]) {
|
|
150
|
+
$toolText = $resp
|
|
151
|
+
} elseif ($null -ne $resp) {
|
|
152
|
+
$stdoutPart = if ($null -ne $resp.stdout) { [string]$resp.stdout } else { '' }
|
|
153
|
+
$stderrPart = if ($null -ne $resp.stderr) { [string]$resp.stderr } else { '' }
|
|
154
|
+
$toolText = (@($stdoutPart, $stderrPart) | Where-Object { $_ }) -join "`n"
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
123
158
|
# --- Flow marker cache write (v2 ALLOW accelerator) ------------------
|
|
124
159
|
# Mirror of the bash hook: detect [arka:routing] or [arka:trivial] in
|
|
125
160
|
# $payload.assistant_message and persist via core.workflow.marker_cache.
|
|
@@ -211,7 +246,7 @@ except Exception:
|
|
|
211
246
|
# ─── Only process when there is actually an error ─────────────────
|
|
212
247
|
$errorPattern = '(?i)(error:|fatal:|exception:|failed|ENOENT|EACCES|EPERM|panic:)'
|
|
213
248
|
if ($exitCode -eq '0' -or [string]::IsNullOrEmpty($exitCode)) {
|
|
214
|
-
if ($
|
|
249
|
+
if ($toolText -notmatch $errorPattern) {
|
|
215
250
|
$shouldProcessGotchas = $false
|
|
216
251
|
}
|
|
217
252
|
}
|
|
@@ -230,7 +265,7 @@ $null = New-Item -ItemType Directory -Force -Path $arkaosRuntimeDir -ErrorAction
|
|
|
230
265
|
if ($shouldProcessGotchas) { do {
|
|
231
266
|
|
|
232
267
|
# ─── Extract first meaningful error line ─────────────────────────────
|
|
233
|
-
$lines = $
|
|
268
|
+
$lines = $toolText -split "`r?`n"
|
|
234
269
|
$errorLineRegex = '(?i)(error|fatal|exception|failed|ENOENT|EACCES|EPERM|panic|cannot|not found|permission denied)'
|
|
235
270
|
$errorLine = $null
|
|
236
271
|
foreach ($ln in $lines) {
|
|
@@ -18,8 +18,32 @@ export ARKA_HOOK_CWD="$_HOOK_CWD"
|
|
|
18
18
|
_ARKA_LIB="$(dirname "${BASH_SOURCE[0]:-$0}")/_lib/arka_python.sh"
|
|
19
19
|
if [ -f "$_ARKA_LIB" ]; then . "$_ARKA_LIB"; else ARKA_PY="python3"; fi
|
|
20
20
|
|
|
21
|
+
# ─── Resolve the root via the shared resolver (one root per session) ───
|
|
22
|
+
# Reading .repo-path raw diverged from every other hook: the file points
|
|
23
|
+
# at an npx cache that `npm cache clean` can purge, while
|
|
24
|
+
# arka_resolve_root falls through to the ~/.arkaos/lib snapshot. The
|
|
25
|
+
# chosen root AND source both travel to the entrypoint so the session
|
|
26
|
+
# can name the root it actually ran from ([arka:root]).
|
|
21
27
|
REPO=""
|
|
22
|
-
|
|
28
|
+
ARKA_ROOT_SOURCE="legacy"
|
|
29
|
+
if command -v arka_resolve_root >/dev/null 2>&1; then
|
|
30
|
+
[ -n "${ARKAOS_ROOT:-}" ] && ARKA_ROOT_SOURCE="env"
|
|
31
|
+
REPO="$(arka_resolve_root)"
|
|
32
|
+
if [ "$ARKA_ROOT_SOURCE" != "env" ]; then
|
|
33
|
+
if [ -f "$HOME/.arkaos/.repo-path" ] \
|
|
34
|
+
&& [ "$REPO" = "$(cat "$HOME/.arkaos/.repo-path" 2>/dev/null)" ]; then
|
|
35
|
+
ARKA_ROOT_SOURCE="repo-path"
|
|
36
|
+
elif [ "$REPO" = "$HOME/.arkaos/lib" ]; then
|
|
37
|
+
ARKA_ROOT_SOURCE="lib-snapshot"
|
|
38
|
+
else
|
|
39
|
+
ARKA_ROOT_SOURCE="fallback"
|
|
40
|
+
fi
|
|
41
|
+
fi
|
|
42
|
+
elif [ -f "$HOME/.arkaos/.repo-path" ]; then
|
|
43
|
+
REPO=$(cat "$HOME/.arkaos/.repo-path")
|
|
44
|
+
fi
|
|
45
|
+
export ARKA_ROOT_SOURCE
|
|
46
|
+
export ARKA_ROOT="$REPO"
|
|
23
47
|
|
|
24
48
|
# The MODULE file is the guard, not just core/: an older installed
|
|
25
49
|
# snapshot without it would exec into "No module named ..." (exit 1,
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
# ============================================================================
|
|
2
2
|
# ArkaOS v2 - UserPromptSubmit Hook (Synapse Bridge) (Windows / PowerShell 5.1+)
|
|
3
3
|
#
|
|
4
|
-
# Port of config/hooks/user-prompt-submit.sh. Calls the Python Synapse
|
|
5
|
-
# for
|
|
6
|
-
# installations where Python is unavailable or the bridge is missing.
|
|
4
|
+
# Port of config/hooks/user-prompt-submit.sh. Calls the Python Synapse
|
|
5
|
+
# bridge for layered context injection, with a pure-PowerShell fallback
|
|
6
|
+
# for installations where Python is unavailable or the bridge is missing.
|
|
7
7
|
#
|
|
8
8
|
# Contract:
|
|
9
9
|
# - Reads a JSON payload from stdin (userInput / message / raw).
|
|
10
10
|
# - Emits a single-line JSON object on stdout:
|
|
11
|
-
# {"
|
|
11
|
+
# {"hookSpecificOutput": {"hookEventName": "UserPromptSubmit",
|
|
12
|
+
# "additionalContext": "<sync-notice><route-reminder> <synapse-or-fallback>"}}
|
|
13
|
+
# (a top-level additionalContext key is ignored by Claude Code)
|
|
12
14
|
# - Side effect: writes one line to the cache metrics JSONL.
|
|
13
15
|
#
|
|
14
|
-
# Target latency: <100ms (hook timeout is
|
|
16
|
+
# Target latency: <100ms (hook timeout is 20s in settings).
|
|
15
17
|
#
|
|
16
18
|
# File is pure ASCII. Any typographic characters that need to appear in the
|
|
17
19
|
# output are built from [char] codes at runtime so PS 5.1's default ANSI
|
|
@@ -54,7 +56,7 @@ if (-not $v2Installed) {
|
|
|
54
56
|
foreach ($v1 in $v1Paths) {
|
|
55
57
|
if ((Test-Path -LiteralPath $v1 -PathType Container) -and -not (Test-Path -LiteralPath $migrationMarker)) {
|
|
56
58
|
$msg = "[MIGRATION] ArkaOS v1 detected at $v1. Run: npx arkaos migrate - This will backup v1, preserve your data, and install v2. See: https://github.com/andreagroferreira/arka-os#install"
|
|
57
|
-
[pscustomobject]@{ additionalContext = $msg } | ConvertTo-Json -Compress
|
|
59
|
+
[pscustomobject]@{ hookSpecificOutput = [pscustomobject]@{ hookEventName = 'UserPromptSubmit'; additionalContext = $msg } } | ConvertTo-Json -Compress -Depth 4
|
|
58
60
|
exit 0
|
|
59
61
|
}
|
|
60
62
|
}
|
|
@@ -336,7 +338,7 @@ $routeReminder = '[arka:route] Every response MUST route through a department sq
|
|
|
336
338
|
|
|
337
339
|
# --- Output ----------------------------------------------------------------
|
|
338
340
|
$additionalContext = "$syncNotice$routeReminder $pythonResult"
|
|
339
|
-
[pscustomobject]@{ additionalContext = $additionalContext } | ConvertTo-Json -Compress
|
|
341
|
+
[pscustomobject]@{ hookSpecificOutput = [pscustomobject]@{ hookEventName = 'UserPromptSubmit'; additionalContext = $additionalContext } } | ConvertTo-Json -Compress -Depth 4
|
|
340
342
|
|
|
341
343
|
# --- Metrics (JSONL append) ------------------------------------------------
|
|
342
344
|
$elapsed = [int]$sw.ElapsedMilliseconds
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
# - One-shot nudges (KB-cite, meta-tag, closing-marker) gated by
|
|
21
21
|
# effort level; cognitive context injection.
|
|
22
22
|
#
|
|
23
|
-
# Timeout:
|
|
23
|
+
# Timeout: 20 s (in-hook budget ARKA_UPS_BUDGET_MS, default 6000 ms)
|
|
24
|
+
# Output: JSON to stdout | Target: <100ms
|
|
24
25
|
# ============================================================================
|
|
25
26
|
|
|
26
27
|
# ─── Shared Python resolver (exports ARKA_PY) ──────────────────────────
|
|
@@ -44,7 +45,7 @@ elif [ -z "${ARKAOS_ROOT:-}" ]; then
|
|
|
44
45
|
fi
|
|
45
46
|
export ARKAOS_ROOT
|
|
46
47
|
|
|
47
|
-
_ARKA_L0_FALLBACK='{"additionalContext": "[Constitution] NON-NEGOTIABLE: branch-isolation, security-gate, mandatory-qa, evidence-flow, arkaos-not-yes-man, excellence-mandate | QUALITY-GATE: marta-cqo, eduardo-copy, francisca-tech-ux | MUST (28) incl.: squad-routing, spec-driven, conventional-commits, test-coverage, subagent-discipline, persona-vs-artifact"}'
|
|
48
|
+
_ARKA_L0_FALLBACK='{"hookSpecificOutput": {"hookEventName": "UserPromptSubmit", "additionalContext": "[Constitution] NON-NEGOTIABLE: branch-isolation, security-gate, mandatory-qa, evidence-flow, arkaos-not-yes-man, excellence-mandate | QUALITY-GATE: marta-cqo, eduardo-copy, francisca-tech-ux | MUST (28) incl.: squad-routing, spec-driven, conventional-commits, test-coverage, subagent-discipline, persona-vs-artifact"}}'
|
|
48
49
|
|
|
49
50
|
# Test seam: bats asserts the fallback CONTENT deterministically —
|
|
50
51
|
# the shared resolver overwrites ARKA_PY, so an env flag is the only
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
{
|
|
26
26
|
"type": "command",
|
|
27
27
|
"command": "{{HOOKS_DIR}}/user-prompt-submit.sh",
|
|
28
|
-
"timeout":
|
|
28
|
+
"timeout": 20
|
|
29
29
|
}
|
|
30
30
|
]
|
|
31
31
|
}
|
|
@@ -52,6 +52,17 @@
|
|
|
52
52
|
]
|
|
53
53
|
}
|
|
54
54
|
],
|
|
55
|
+
"PostToolUseFailure": [
|
|
56
|
+
{
|
|
57
|
+
"hooks": [
|
|
58
|
+
{
|
|
59
|
+
"type": "command",
|
|
60
|
+
"command": "{{HOOKS_DIR}}/post-tool-use.sh",
|
|
61
|
+
"timeout": 5
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
],
|
|
55
66
|
"CwdChanged": [
|
|
56
67
|
{
|
|
57
68
|
"hooks": [
|
|
@@ -390,24 +390,54 @@ def _foreign_pytest_can_collect(
|
|
|
390
390
|
return probe.returncode in (0, 5)
|
|
391
391
|
|
|
392
392
|
|
|
393
|
+
def _degrade_pytest_no_tests(result: CheckResult) -> CheckResult:
|
|
394
|
+
"""Map pytest exit 5 (no tests collected) to insufficient evidence.
|
|
395
|
+
|
|
396
|
+
A bare ``pytest`` in a Python project that ships no tests exits 5.
|
|
397
|
+
Absence of tests is not evidence of FAILURE, yet ``passed =
|
|
398
|
+
returncode == 0`` read exit 5 as FAIL and ``_derive_overall`` then
|
|
399
|
+
forced REJECTED at the QG (issue #354). ``_foreign_pytest_can_collect``
|
|
400
|
+
already treats exit 5 as a valid import path — this keeps that
|
|
401
|
+
precedent for the verdict: exit 5 becomes ``passed=None``
|
|
402
|
+
(non-conclusive), never ``False``.
|
|
403
|
+
"""
|
|
404
|
+
if result.exit_code != 5:
|
|
405
|
+
return result
|
|
406
|
+
prefix = "no tests collected (pytest exit 5) — insufficient evidence; "
|
|
407
|
+
return replace(result, passed=None, summary=prefix + result.summary)
|
|
408
|
+
|
|
409
|
+
|
|
393
410
|
def _check_tests(
|
|
394
411
|
project_dir: Path, changed: list[str] | None,
|
|
395
412
|
test_command: str | None, timeout: int,
|
|
396
413
|
) -> CheckResult:
|
|
397
414
|
if test_command:
|
|
398
|
-
|
|
415
|
+
argv = shlex.split(test_command)
|
|
416
|
+
result = _run("tests", argv, project_dir, timeout)
|
|
417
|
+
# exit 5 is pytest's "no tests collected"; scan the first 3
|
|
418
|
+
# tokens so `python -m pytest` / `arka-py -m pytest` degrade too,
|
|
419
|
+
# while non-pytest runners (npm test) stay a real FAIL. Bounded to
|
|
420
|
+
# argv[:3] so a later test-path arg named *pytest* never matches
|
|
421
|
+
# (issue #354).
|
|
422
|
+
if any("pytest" in Path(tok).name for tok in argv[:3]):
|
|
423
|
+
return _degrade_pytest_no_tests(result)
|
|
424
|
+
return result
|
|
399
425
|
if _has_python(project_dir, changed):
|
|
400
426
|
local_pytest = _project_pytest(project_dir)
|
|
401
427
|
if local_pytest:
|
|
402
428
|
return _labelled(
|
|
403
|
-
|
|
429
|
+
_degrade_pytest_no_tests(
|
|
430
|
+
_run("tests", [*local_pytest, "-q"], project_dir, timeout),
|
|
431
|
+
),
|
|
404
432
|
"tests(project-venv)",
|
|
405
433
|
)
|
|
406
434
|
pytest_cmd = _tool_cmd("pytest")
|
|
407
435
|
if pytest_cmd and _foreign_pytest_can_collect(
|
|
408
436
|
pytest_cmd, project_dir, timeout
|
|
409
437
|
):
|
|
410
|
-
return
|
|
438
|
+
return _degrade_pytest_no_tests(
|
|
439
|
+
_run("tests", [*pytest_cmd, "-q"], project_dir, timeout),
|
|
440
|
+
)
|
|
411
441
|
if pytest_cmd:
|
|
412
442
|
return _skip(
|
|
413
443
|
"tests",
|