arkaos 5.11.0 → 5.13.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-py.ps1 +16 -1
- package/config/hooks/_lib/arka_python.ps1 +68 -5
- package/config/hooks/_lib/arka_python.sh +61 -6
- package/config/hooks/_lib/workflow-classifier.sh +43 -4
- package/config/hooks/post-tool-use.ps1 +9 -2
- package/config/hooks/session-start.ps1 +76 -101
- package/config/hooks/session-start.sh +7 -3
- package/config/hooks/stop.ps1 +57 -1
- package/config/hooks/user-prompt-submit.ps1 +64 -340
- package/core/cognition/scheduler/daemon.py +8 -0
- package/core/cognition/scheduler/platform.py +26 -2
- package/core/hooks/post_tool_use.py +18 -0
- package/core/hooks/stop.py +2 -2
- package/core/hooks/stop_governance.py +166 -0
- package/core/hooks/user_prompt_submit.py +40 -8
- package/core/knowledge/chunker.py +108 -8
- package/core/knowledge/doctrine.py +435 -0
- package/core/knowledge/indexer.py +83 -5
- package/core/knowledge/lexical.py +225 -0
- package/core/knowledge/lexical_fusion.py +130 -0
- package/core/knowledge/recall_cli.py +162 -0
- package/core/knowledge/retrieval_eval.py +290 -0
- package/core/knowledge/retrieval_eval_cli.py +149 -0
- package/core/knowledge/vault.py +67 -0
- package/core/knowledge/vector_store.py +61 -23
- package/core/runtime/claude_code.py +8 -1
- package/core/shared/temp_paths.py +25 -0
- package/core/synapse/kb_cache.py +37 -6
- package/core/synapse/layers_kb.py +84 -6
- package/core/workflow/flow_enforcer.py +2 -2
- package/core/workflow/research_gate.py +120 -39
- package/core/workflow/state_reader.sh +7 -1
- 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/knowledge/skills-manifest.json +1 -1
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/scripts/knowledge-index.py +157 -62
package/THE-ARKAOS-GUIDE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# The ArkaOS Guide
|
|
2
2
|
|
|
3
|
-
> v5.
|
|
3
|
+
> v5.13.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.13.0
|
package/bin/arka-py.ps1
CHANGED
|
@@ -25,7 +25,22 @@ $libs = @(
|
|
|
25
25
|
foreach ($lib in $libs) {
|
|
26
26
|
if (Test-Path -LiteralPath $lib) { . $lib; break }
|
|
27
27
|
}
|
|
28
|
-
|
|
28
|
+
# Empty/unset means one of two things: the shared resolver was not found at
|
|
29
|
+
# all, or it WAS found and deliberately handed back nothing because every
|
|
30
|
+
# candidate is a Microsoft Store App Execution Alias. A blind `= "python"`
|
|
31
|
+
# here cannot tell those apart, and on a stock Windows box it picks the
|
|
32
|
+
# alias — running the Python install manager, which downloads a full CPython
|
|
33
|
+
# into the caller's cwd. Probe once more, alias-aware, then fail loudly.
|
|
34
|
+
if (-not $env:ARKA_PY) {
|
|
35
|
+
$fallback = Get-Command python -ErrorAction SilentlyContinue
|
|
36
|
+
if ($fallback -and $fallback.Source -notmatch '\\Microsoft\\WindowsApps\\') {
|
|
37
|
+
$env:ARKA_PY = $fallback.Source
|
|
38
|
+
} else {
|
|
39
|
+
[Console]::Error.WriteLine(
|
|
40
|
+
"arka-py: no usable Python interpreter on PATH (none found, or only Store aliases) - run npx arkaos doctor")
|
|
41
|
+
exit 1
|
|
42
|
+
}
|
|
43
|
+
}
|
|
29
44
|
|
|
30
45
|
# ─── Make `-m core.*` resolvable regardless of cwd ────────────────────────
|
|
31
46
|
$root = $env:ARKAOS_ROOT
|
|
@@ -8,8 +8,69 @@
|
|
|
8
8
|
# Resolution order:
|
|
9
9
|
# 1. $env:ARKAOS_PYTHON (explicit override), if it exists.
|
|
10
10
|
# 2. The ArkaOS venv — Scripts\python.exe (Windows) then bin/python.
|
|
11
|
-
# 3. Any
|
|
12
|
-
#
|
|
11
|
+
# 3. Any python/py/python3 on PATH that can `import yaml`, in THAT order
|
|
12
|
+
# (see the comment on the probe loop), skipping the Microsoft Store
|
|
13
|
+
# App Execution Aliases (see Test-StoreAlias).
|
|
14
|
+
# 4. `python` as a last resort — but '' when that name is a Store alias
|
|
15
|
+
# too, because a caller cannot tell an alias from an interpreter and
|
|
16
|
+
# would run it (see Get-ArkaPythonLastResort).
|
|
17
|
+
|
|
18
|
+
function Test-StoreAlias {
|
|
19
|
+
<#
|
|
20
|
+
.SYNOPSIS
|
|
21
|
+
True when a resolved command is a Microsoft Store App Execution Alias.
|
|
22
|
+
|
|
23
|
+
.DESCRIPTION
|
|
24
|
+
%LOCALAPPDATA%\Microsoft\WindowsApps holds zero-length reparse points
|
|
25
|
+
that Get-Command resolves like any other executable. On a default
|
|
26
|
+
Windows install `python3.exe` there points at the Python install
|
|
27
|
+
manager, and *running* it — which the probe below does, with
|
|
28
|
+
`-c "import yaml"` — makes the manager download and install a full
|
|
29
|
+
CPython into the current working directory.
|
|
30
|
+
|
|
31
|
+
For a hook that is a catastrophic side effect: the cwd is the user's
|
|
32
|
+
project, so a background hook silently drops a ~3700-file Python/
|
|
33
|
+
tree and a python_install_<timestamp>.log into their repo. Observed
|
|
34
|
+
on Windows 10, Python install manager 26.3.
|
|
35
|
+
|
|
36
|
+
Probing an alias can never help anyway. Either the manager installs
|
|
37
|
+
a CPython and the probe measures THAT interpreter — a fresh install
|
|
38
|
+
with no pyyaml, so `import yaml` fails and the candidate is rejected
|
|
39
|
+
after paying the full cost — or the install is declined and the stub
|
|
40
|
+
exits without ever running the check. There is no path where probing
|
|
41
|
+
the alias answers the question it was asked.
|
|
42
|
+
#>
|
|
43
|
+
param([string]$Path)
|
|
44
|
+
if ([string]::IsNullOrWhiteSpace($Path)) { return $false }
|
|
45
|
+
return $Path -match '\\Microsoft\\WindowsApps\\'
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function Get-ArkaPythonLastResort {
|
|
49
|
+
<#
|
|
50
|
+
.SYNOPSIS
|
|
51
|
+
The interpreter to hand back when no probed candidate answered, or
|
|
52
|
+
'' when the only name left is a Store alias.
|
|
53
|
+
|
|
54
|
+
.DESCRIPTION
|
|
55
|
+
Extracted from Resolve-ArkaPython so the branch is reachable from a
|
|
56
|
+
test: in place it sits behind a probe that matches on any machine
|
|
57
|
+
with a working interpreter.
|
|
58
|
+
|
|
59
|
+
Returning the bare string "python" from here was the hole in this
|
|
60
|
+
file's own guard. Every caller gates on `-and $env:ARKA_PY`, and any
|
|
61
|
+
non-empty string is truthy in PowerShell, so an aliased "python"
|
|
62
|
+
passed the gate and was then run — the exact install-manager side
|
|
63
|
+
effect Test-StoreAlias exists to prevent, arriving through the
|
|
64
|
+
fallback instead of the probe. '' is the only value that makes the
|
|
65
|
+
callers degrade, so '' is what a box with nothing but aliases gets.
|
|
66
|
+
|
|
67
|
+
Mirror of arka_python_last_resort() in arka_python.sh.
|
|
68
|
+
#>
|
|
69
|
+
param([string]$Name = "python")
|
|
70
|
+
$cmd = Get-Command $Name -ErrorAction SilentlyContinue
|
|
71
|
+
if ($cmd -and -not (Test-StoreAlias $cmd.Source)) { return $cmd.Source }
|
|
72
|
+
return ""
|
|
73
|
+
}
|
|
13
74
|
|
|
14
75
|
function Resolve-ArkaPython {
|
|
15
76
|
if ($env:ARKAOS_PYTHON -and (Test-Path -LiteralPath $env:ARKAOS_PYTHON)) {
|
|
@@ -29,15 +90,17 @@ function Resolve-ArkaPython {
|
|
|
29
90
|
}
|
|
30
91
|
}
|
|
31
92
|
|
|
32
|
-
|
|
93
|
+
# "python" before "python3": on Windows a real interpreter answers to
|
|
94
|
+
# the former, while the latter is usually only the Store alias.
|
|
95
|
+
foreach ($name in @("python", "py", "python3")) {
|
|
33
96
|
$cmd = Get-Command $name -ErrorAction SilentlyContinue
|
|
34
|
-
if ($cmd) {
|
|
97
|
+
if ($cmd -and -not (Test-StoreAlias $cmd.Source)) {
|
|
35
98
|
& $cmd.Source -c "import yaml" 2>$null
|
|
36
99
|
if ($LASTEXITCODE -eq 0) { return $cmd.Source }
|
|
37
100
|
}
|
|
38
101
|
}
|
|
39
102
|
|
|
40
|
-
return
|
|
103
|
+
return (Get-ArkaPythonLastResort)
|
|
41
104
|
}
|
|
42
105
|
|
|
43
106
|
# Windows mirror of arka_resolve_root() in arka_python.sh: env override wins
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
# Contract of arka_resolve_python():
|
|
11
11
|
# Echoes the interpreter path and returns 0 when a provisioned ArkaOS
|
|
12
12
|
# interpreter is found; echoes a best-effort fallback and returns 1 when
|
|
13
|
-
# only a bare `python3` remains (caller degrades gracefully).
|
|
13
|
+
# only a bare `python3` remains (caller degrades gracefully). Echoes
|
|
14
|
+
# NOTHING and returns 1 when every remaining candidate is a Microsoft
|
|
15
|
+
# Store App Execution Alias — see arka_python_last_resort().
|
|
14
16
|
#
|
|
15
17
|
# Resolution order:
|
|
16
18
|
# 1. $ARKAOS_PYTHON (explicit operator override), if executable.
|
|
@@ -18,7 +20,39 @@
|
|
|
18
20
|
# python3. `[ -x ]` follows symlinks, so a Homebrew-rotated broken
|
|
19
21
|
# symlink is skipped automatically.
|
|
20
22
|
# 3. Any python3 on PATH / known locations that can `import yaml`.
|
|
21
|
-
# 4. Bare `python3` as a last resort (return 1)
|
|
23
|
+
# 4. Bare `python3` as a last resort (return 1), or nothing at all when
|
|
24
|
+
# that name — and `python` behind it — is only a Store alias.
|
|
25
|
+
|
|
26
|
+
# Last-resort name selection, extracted from arka_resolve_python() so the
|
|
27
|
+
# branch is reachable from a test: in place it sits behind a PATH probe
|
|
28
|
+
# that matches on every POSIX box with a yaml-capable python3, so it can
|
|
29
|
+
# only be exercised on the machine it was written for.
|
|
30
|
+
#
|
|
31
|
+
# Contract: echoes the name to hand back, or NOTHING when every remaining
|
|
32
|
+
# candidate is a Store alias. Empty is the point, not an oversight. The
|
|
33
|
+
# callers gate on `command -v "$ARKA_PY"` and an alias stub PASSES that
|
|
34
|
+
# gate — the file is real, it is just the Python install manager rather
|
|
35
|
+
# than an interpreter — so the only value that degrades a hook instead of
|
|
36
|
+
# downloading a CPython into the user's project is no value at all.
|
|
37
|
+
arka_python_last_resort() {
|
|
38
|
+
local py3 py
|
|
39
|
+
py3="$(command -v python3 2>/dev/null)"
|
|
40
|
+
case "$py3" in
|
|
41
|
+
*[Ww]indows[Aa]pps*) ;;
|
|
42
|
+
# Absent or real: the documented `python3` contract, unchanged. POSIX
|
|
43
|
+
# never reaches the alias branch below.
|
|
44
|
+
*) printf '%s\n' "python3"; return 0 ;;
|
|
45
|
+
esac
|
|
46
|
+
|
|
47
|
+
# `python3` is the alias. On a Windows box that HAS a real interpreter,
|
|
48
|
+
# `python` is the name it answers to; on a stock box with none, that is
|
|
49
|
+
# an alias too and we must hand back nothing.
|
|
50
|
+
py="$(command -v python 2>/dev/null)"
|
|
51
|
+
case "$py" in
|
|
52
|
+
"" | *[Ww]indows[Aa]pps*) return 0 ;;
|
|
53
|
+
esac
|
|
54
|
+
printf '%s\n' "python"
|
|
55
|
+
}
|
|
22
56
|
|
|
23
57
|
arka_resolve_python() {
|
|
24
58
|
local cand
|
|
@@ -28,25 +62,46 @@ arka_resolve_python() {
|
|
|
28
62
|
return 0
|
|
29
63
|
fi
|
|
30
64
|
|
|
65
|
+
# Scripts/python.exe is the Windows venv layout. Without it, a Git-Bash
|
|
66
|
+
# hook on Windows never matches the venv and always falls through to the
|
|
67
|
+
# PATH probe below -- which is where the Store alias lives.
|
|
31
68
|
for cand in \
|
|
32
69
|
"$HOME/.arkaos/venv/bin/python" \
|
|
33
70
|
"$HOME/.arkaos/venv/bin/python3" \
|
|
71
|
+
"$HOME/.arkaos/venv/Scripts/python.exe" \
|
|
34
72
|
"$HOME/.arkaos/.venv/bin/python" \
|
|
35
|
-
"$HOME/.arkaos/.venv/bin/python3"
|
|
73
|
+
"$HOME/.arkaos/.venv/bin/python3" \
|
|
74
|
+
"$HOME/.arkaos/.venv/Scripts/python.exe"; do
|
|
36
75
|
if [ -x "$cand" ]; then
|
|
37
76
|
printf '%s\n' "$cand"
|
|
38
77
|
return 0
|
|
39
78
|
fi
|
|
40
79
|
done
|
|
41
80
|
|
|
42
|
-
|
|
43
|
-
|
|
81
|
+
# Never probe a Microsoft Store App Execution Alias: under Git Bash on
|
|
82
|
+
# Windows `command -v python3` resolves to .../Microsoft/WindowsApps/
|
|
83
|
+
# python3, which is the Python install manager, not an interpreter. The
|
|
84
|
+
# `import yaml` check RUNS each candidate, and running that one installs
|
|
85
|
+
# a CPython into the current working directory -- the user's project, for
|
|
86
|
+
# a hook. It could not have succeeded anyway: an alias stub carries no
|
|
87
|
+
# site packages. On POSIX nothing ever matches that path: pure no-op.
|
|
88
|
+
#
|
|
89
|
+
# `python` is LAST on purpose. On Windows it is the only real interpreter
|
|
90
|
+
# left once the alias is filtered out; on POSIX one of the entries before
|
|
91
|
+
# it always matches first, so an old box where `python` is Python 2 keeps
|
|
92
|
+
# resolving exactly as it does today.
|
|
93
|
+
for cand in python3 /opt/homebrew/bin/python3 /usr/local/bin/python3 /usr/bin/python3 python; do
|
|
94
|
+
case "$(command -v "$cand" 2>/dev/null)" in
|
|
95
|
+
"") continue ;;
|
|
96
|
+
*[Ww]indows[Aa]pps*) continue ;;
|
|
97
|
+
esac
|
|
98
|
+
if "$cand" -c "import yaml" >/dev/null 2>&1; then
|
|
44
99
|
printf '%s\n' "$cand"
|
|
45
100
|
return 0
|
|
46
101
|
fi
|
|
47
102
|
done
|
|
48
103
|
|
|
49
|
-
|
|
104
|
+
arka_python_last_resort
|
|
50
105
|
return 1
|
|
51
106
|
}
|
|
52
107
|
|
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
# ArkaOS v2 — Shared Workflow Classifier
|
|
4
4
|
#
|
|
5
5
|
# Decides whether a user prompt triggers the 4-gate evidence flow.
|
|
6
|
-
#
|
|
6
|
+
#
|
|
7
|
+
# CLI AND TEST SURFACE ONLY — no hook sources this file. The runtime path
|
|
8
|
+
# is core/hooks/user_prompt_submit.py, which user-prompt-submit.sh execs;
|
|
9
|
+
# these functions survive as the documented CLI (see the dispatcher at the
|
|
10
|
+
# bottom) and as the bash half of the parity contract. That is precisely
|
|
11
|
+
# why the two implementations must be diffed by hand: nothing at runtime
|
|
12
|
+
# will ever notice them disagreeing.
|
|
7
13
|
#
|
|
8
14
|
# Contract:
|
|
9
15
|
# arka_wf_classify "<prompt text>" → echoes "true" or "false", exits 0.
|
|
@@ -11,9 +17,11 @@
|
|
|
11
17
|
# arka_wf_is_required "<session_id>" → exits 0 if required, 1 otherwise.
|
|
12
18
|
# arka_wf_clear_required "<session_id>" → removes marker file.
|
|
13
19
|
#
|
|
14
|
-
# Markers live under
|
|
15
|
-
#
|
|
16
|
-
#
|
|
20
|
+
# Markers live under $ARKA_WF_REQUIRED_DIR/<session_id>, defaulting to
|
|
21
|
+
# /tmp/arkaos-wf-required (see below). The override is the seam that keeps
|
|
22
|
+
# writer and reader on the same directory — core/shared/temp_paths.py
|
|
23
|
+
# ::wf_required_dir is the Python side of it. mark/clear here write the
|
|
24
|
+
# marker file directly; they do NOT call flow_enforcer.py.
|
|
17
25
|
# ============================================================================
|
|
18
26
|
|
|
19
27
|
ARKA_WF_REQUIRED_DIR="${ARKA_WF_REQUIRED_DIR:-/tmp/arkaos-wf-required}"
|
|
@@ -40,6 +48,13 @@ arka_wf_safe_session_id() {
|
|
|
40
48
|
# missed prompts were short continuations ("continua", "força") or
|
|
41
49
|
# ship-tier verbs ("ship", "publish", "merge", "release", "deploy")
|
|
42
50
|
# that prolong existing flow-required activity.
|
|
51
|
+
# Keep in sync with _WF_QUESTION_LEAD_PATTERN in core/hooks/
|
|
52
|
+
# user_prompt_submit.py. Interrogative-led prompts ending in "?" are
|
|
53
|
+
# information questions, not creation intent, even when they contain an
|
|
54
|
+
# action verb ("o que e que este projeto faz?"). Polite requests ("podes
|
|
55
|
+
# implementar X?") keep matching: their lead word is not interrogative.
|
|
56
|
+
ARKA_WF_QUESTION_LEAD='^[[:space:]]*(o[[:space:]]+que|porqu[eê]|por[[:space:]]+que|para[[:space:]]+que|qual|quais|quando|onde|quem|como|what|why|which|who|whose|when|where|how|does)\b'
|
|
57
|
+
|
|
43
58
|
ARKA_WF_VERB_PATTERN='(criar?|crie[ms]?|cria[mr]?|adicionar?|adiciona[mr]?|implementar?|implementa[mr]?|desenvolver?|desenvolve[mr]?|construir?|constru[ií]a?[mr]?|fazer?|faz[ae]?[mr]?|refactor(izar?)?|corrigir?|corrige[mr]?|consertar?|conserta[mr]?|continuar?|continua[mr]?|forçar?|força[mr]?|colocar?|coloca[mr]?|p[oô]r|melhorar?|melhora[mr]?|terminar?|termina[mr]?|acabar?|acaba[mr]?|publicar?|publica[mr]?|lançar?|lança[mr]?|create[sd]?|creating|build(s|ing)?|add(s|ed|ing)?|implement(s|ed|ing)?|develop(s|ed|ing)?|fix(es|ed|ing)?|refactor(s|ed|ing)?|make[sd]?|making|continue[sd]?|continuing|ship(s|ped|ping)?|merge[sd]?|merging|publish(es|ed|ing)?|release[sd]?|releasing|deploy(s|ed|ing)?|finish(es|ed|ing)?|improve[sd]?|improving)'
|
|
44
59
|
|
|
45
60
|
# Classify: returns "true" if the prompt looks like a creation/
|
|
@@ -56,6 +71,30 @@ arka_wf_classify() {
|
|
|
56
71
|
return 0
|
|
57
72
|
fi
|
|
58
73
|
|
|
74
|
+
# Question guard: interrogative lead + trailing "?" = information
|
|
75
|
+
# question, never creation intent (X5 false positive).
|
|
76
|
+
#
|
|
77
|
+
# The lead is taken from the FIRST NON-BLANK LINE, never from the whole
|
|
78
|
+
# text. The Python twin uses re.match on text.strip(), which can only
|
|
79
|
+
# match at offset 0; grep anchors ^ PER LINE, so feeding it everything
|
|
80
|
+
# made any later line able to trigger the guard. The two then disagreed
|
|
81
|
+
# on every multi-line prompt ending in a question — "implementa X\no que
|
|
82
|
+
# e que isto faz?" is a directive followed by a question, and a directive
|
|
83
|
+
# still requires the flow. awk reproduces .strip() closely enough for an
|
|
84
|
+
# anchor that already tolerates leading whitespace: skip blank lines,
|
|
85
|
+
# print the first line with content, stop.
|
|
86
|
+
local trimmed lead
|
|
87
|
+
trimmed=$(printf '%s' "$text" | sed 's/[[:space:]]*$//')
|
|
88
|
+
case "$trimmed" in
|
|
89
|
+
*\?)
|
|
90
|
+
lead=$(printf '%s' "$text" | awk 'NF { print; exit }')
|
|
91
|
+
if printf '%s' "$lead" | grep -qiE "$ARKA_WF_QUESTION_LEAD"; then
|
|
92
|
+
echo "false"
|
|
93
|
+
return 0
|
|
94
|
+
fi
|
|
95
|
+
;;
|
|
96
|
+
esac
|
|
97
|
+
|
|
59
98
|
if echo "$text" | grep -qiE "\b${ARKA_WF_VERB_PATTERN}\b"; then
|
|
60
99
|
echo "true"
|
|
61
100
|
else
|
|
@@ -203,9 +203,16 @@ if ($shouldProcessGotchas -and $null -ne $payload) {
|
|
|
203
203
|
if (Test-Path -LiteralPath $venvPy) {
|
|
204
204
|
$pythonForMarker = $venvPy
|
|
205
205
|
} else {
|
|
206
|
-
|
|
206
|
+
# Never accept %LOCALAPPDATA%\Microsoft\WindowsApps: the
|
|
207
|
+
# python3.exe there is the Store install-manager alias,
|
|
208
|
+
# and running it downloads a full CPython into the cwd —
|
|
209
|
+
# which for a hook is the user's own project directory.
|
|
210
|
+
# Same reason "python" is tried before "python3" here.
|
|
211
|
+
foreach ($cmd in 'python','py','python3') {
|
|
207
212
|
$resolved = Get-Command $cmd -ErrorAction SilentlyContinue
|
|
208
|
-
if ($resolved
|
|
213
|
+
if ($resolved -and $resolved.Source -notmatch '\\Microsoft\\WindowsApps\\') {
|
|
214
|
+
$pythonForMarker = $resolved.Source; break
|
|
215
|
+
}
|
|
209
216
|
}
|
|
210
217
|
}
|
|
211
218
|
if ($pythonForMarker) {
|
|
@@ -1,121 +1,96 @@
|
|
|
1
1
|
# ============================================================================
|
|
2
|
-
# ArkaOS
|
|
2
|
+
# ArkaOS - SessionStart Hook (Windows / PowerShell 5.1+)
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
4
|
+
# Thin wrapper - mirror of config/hooks/session-start.sh. ONE python process
|
|
5
|
+
# (core.hooks.session_start) builds the whole payload: the visible banner in
|
|
6
|
+
# "systemMessage" AND the operating contracts in
|
|
7
|
+
# hookSpecificOutput.additionalContext. This file only resolves the
|
|
8
|
+
# interpreter and delegates; whenever it cannot reach that producer — no
|
|
9
|
+
# repo root, no module file, no interpreter, or the module itself exiting
|
|
10
|
+
# non-zero — it emits a static banner and exits 0 (fail-open,
|
|
11
|
+
# dependency-free).
|
|
12
|
+
#
|
|
13
|
+
# WHY A WRAPPER AND NOT A PORT
|
|
14
|
+
# The previous version reimplemented the banner natively in PowerShell. It
|
|
15
|
+
# was a faithful port of the *older* .sh, from the era when the .sh built the
|
|
16
|
+
# banner in bash. When the .sh became a thin wrapper delegating to Python,
|
|
17
|
+
# this file did not follow - so on Windows the four contract blocks
|
|
18
|
+
# (EVIDENCE-FLOW, META-TAG, AUTHORITY, MODEL-FABRIC) were computed by nobody
|
|
19
|
+
# and never reached the model, while the Linux side got them every session.
|
|
20
|
+
# Reimplementing the blocks here would duplicate the producer and recreate
|
|
21
|
+
# the same drift one release later, so this file delegates instead. The
|
|
22
|
+
# twin-parity test in tests/python/test_hook_output_contract.py is what
|
|
23
|
+
# holds that open: it asserts both wrappers run `-m core.hooks
|
|
24
|
+
# .session_start`, and fails if either stops.
|
|
7
25
|
#
|
|
8
26
|
# Minimum PowerShell: 5.1 (shipped with every Windows 10+). No pwsh 7 prereq.
|
|
9
|
-
#
|
|
10
|
-
#
|
|
27
|
+
# This file stays pure ASCII; every non-ASCII byte in the output comes from
|
|
28
|
+
# Python's UTF-8 stdout, which is why the encoding setup below is required.
|
|
11
29
|
# ============================================================================
|
|
12
30
|
|
|
13
|
-
$ErrorActionPreference = '
|
|
14
|
-
# Force UTF-8 on stdout so ConvertTo-Json does not emit mojibake box chars.
|
|
15
|
-
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)
|
|
31
|
+
$ErrorActionPreference = 'SilentlyContinue'
|
|
16
32
|
|
|
17
|
-
|
|
33
|
+
# Captured BEFORE any location change - inside a compound command the CWD
|
|
34
|
+
# would resolve after the push (QG blocker F1-A3: cross-project leak).
|
|
35
|
+
$env:ARKA_HOOK_CWD = (Get-Location).Path
|
|
18
36
|
|
|
19
|
-
#
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
37
|
+
# Force UTF-8 on both sides of the pipe. Python emits UTF-8; without this
|
|
38
|
+
# PS 5.1 decodes it as the ANSI codepage (cp1252 on pt-PT/pt-BR machines)
|
|
39
|
+
# and the banner arrives as mojibake - the failure mode tracked in #395/#396.
|
|
40
|
+
# PYTHONIOENCODING covers the producer, OutputEncoding the consumer.
|
|
41
|
+
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)
|
|
42
|
+
$env:PYTHONIOENCODING = 'utf-8'
|
|
23
43
|
|
|
24
|
-
$
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
# $profile is an automatic variable in PowerShell — use $arkaosProfile.
|
|
28
|
-
$arkaosProfile = Get-Content -Raw -LiteralPath $profilePath -Encoding UTF8 | ConvertFrom-Json
|
|
29
|
-
if ($arkaosProfile.name) { $name = [string]$arkaosProfile.name }
|
|
30
|
-
elseif ($arkaosProfile.role) { $name = [string]$arkaosProfile.role }
|
|
31
|
-
if ($arkaosProfile.company) { $company = [string]$arkaosProfile.company }
|
|
32
|
-
} catch {
|
|
33
|
-
# Corrupt profile — keep defaults and continue.
|
|
34
|
-
}
|
|
35
|
-
}
|
|
44
|
+
# --- Shared Python resolver (sets $env:ARKA_PY, provides Resolve-ArkaRoot) ---
|
|
45
|
+
$arkaLib = Join-Path $PSScriptRoot '_lib\arka_python.ps1'
|
|
46
|
+
if (Test-Path -LiteralPath $arkaLib) { . $arkaLib }
|
|
36
47
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
48
|
+
# --- Resolve the repo root ---
|
|
49
|
+
# .repo-path can point at a purged npx cache; Resolve-ArkaRoot falls through
|
|
50
|
+
# to the ~/.arkaos/lib snapshot instead of returning a dead root.
|
|
51
|
+
$repo = ''
|
|
52
|
+
if (Get-Command Resolve-ArkaRoot -ErrorAction SilentlyContinue) {
|
|
53
|
+
$repo = Resolve-ArkaRoot
|
|
54
|
+
} else {
|
|
55
|
+
$repoPathFile = Join-Path $HOME '.arkaos/.repo-path'
|
|
56
|
+
if (Test-Path -LiteralPath $repoPathFile) {
|
|
40
57
|
$repo = (Get-Content -Raw -LiteralPath $repoPathFile -Encoding UTF8).Trim()
|
|
41
|
-
if ($repo) {
|
|
42
|
-
$versionFile = Join-Path $repo 'VERSION'
|
|
43
|
-
if (Test-Path -LiteralPath $versionFile) {
|
|
44
|
-
$version = (Get-Content -Raw -LiteralPath $versionFile -Encoding UTF8).Trim()
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
} catch { }
|
|
48
|
-
}
|
|
49
|
-
# .repo-path may point at a purged npx cache — the installer's snapshot
|
|
50
|
-
# keeps the drift banner alive ('2.x' is the untouched default above).
|
|
51
|
-
if ($version -eq '2.x') {
|
|
52
|
-
$libVersionFile = Join-Path $arkaosHome 'lib\VERSION'
|
|
53
|
-
if (Test-Path -LiteralPath $libVersionFile) {
|
|
54
|
-
try {
|
|
55
|
-
$version = (Get-Content -Raw -LiteralPath $libVersionFile -Encoding UTF8).Trim()
|
|
56
|
-
} catch { }
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
#
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
else { $greeting = 'Boa noite' }
|
|
61
|
+
# The MODULE file is the guard, not just core/: an older installed snapshot
|
|
62
|
+
# without it would delegate into "No module named ..." (exit 1, empty output
|
|
63
|
+
# - a broken SessionStart instead of a degraded banner).
|
|
64
|
+
$moduleFile = if ($repo) { Join-Path $repo 'core/hooks/session_start.py' } else { '' }
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
$
|
|
68
|
-
|
|
69
|
-
if (Test-Path -LiteralPath $syncStatePath) {
|
|
66
|
+
if ($repo -and $moduleFile -and (Test-Path -LiteralPath $moduleFile) -and $env:ARKA_PY) {
|
|
67
|
+
$env:PYTHONPATH = $repo
|
|
68
|
+
Push-Location -LiteralPath $repo
|
|
70
69
|
try {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if ($synced -ne $version) {
|
|
74
|
-
$drift = "`n[arka:update-available] Core v$version != synced v$synced. Run /arka update."
|
|
75
|
-
}
|
|
70
|
+
& $env:ARKA_PY -m core.hooks.session_start
|
|
71
|
+
if ($LASTEXITCODE -eq 0) { Pop-Location; exit 0 }
|
|
76
72
|
} catch {
|
|
77
|
-
|
|
73
|
+
# fall through to the degraded banner
|
|
78
74
|
}
|
|
79
|
-
|
|
80
|
-
$drift = "`n[arka:update-available] Never synced. Run /arka update."
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
# ─── Build ASCII header from char codes ───────────────────────────────
|
|
84
|
-
$tl = [char]0x2554 # ╔
|
|
85
|
-
$tr = [char]0x2557 # ╗
|
|
86
|
-
$bl = [char]0x255A # ╚
|
|
87
|
-
$br = [char]0x255D # ╝
|
|
88
|
-
$h = [char]0x2550 # ═
|
|
89
|
-
$v = [char]0x2551 # ║
|
|
90
|
-
$bar = [string]($h.ToString() * 46)
|
|
91
|
-
|
|
92
|
-
$topLine = "$tl$bar$tr"
|
|
93
|
-
$bottomLine = "$bl$bar$br"
|
|
94
|
-
$empty = "$v" + (' ' * 46) + "$v"
|
|
95
|
-
function Pad-Line([string]$text) {
|
|
96
|
-
$innerWidth = 46
|
|
97
|
-
$padLeft = [int](($innerWidth - $text.Length) / 2)
|
|
98
|
-
$padRight = $innerWidth - $text.Length - $padLeft
|
|
99
|
-
return "$v" + (' ' * $padLeft) + $text + (' ' * $padRight) + "$v"
|
|
75
|
+
Pop-Location
|
|
100
76
|
}
|
|
101
77
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
$
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
[pscustomobject]@{ systemMessage = $msg } | ConvertTo-Json -Compress
|
|
78
|
+
# --- Degraded fallback: static banner, valid JSON, exit 0 ------------------
|
|
79
|
+
# Same contract as the .sh fallback. Built from [char] codes so this source
|
|
80
|
+
# file needs no particular encoding to produce correct output.
|
|
81
|
+
#
|
|
82
|
+
# The message names the SYMPTOM, not a cause. Four conditions reach this
|
|
83
|
+
# line (see the guard above) and only one of them is a missing interpreter;
|
|
84
|
+
# the fourth — the module running and exiting non-zero — asserts the
|
|
85
|
+
# opposite. The .sh twin execs, so its fallback is unreachable once an
|
|
86
|
+
# interpreter is launched and the narrower wording survived there; this
|
|
87
|
+
# port falls through on $LASTEXITCODE, which is what made it false. Sending
|
|
88
|
+
# a Windows operator with a healthy Python to debug their Python, when the
|
|
89
|
+
# real fault is a stale ~/.arkaos/.repo-path, costs more than the wording
|
|
90
|
+
# saves. `npx arkaos doctor` diagnoses all four.
|
|
91
|
+
$tri = [char]0x25B2
|
|
92
|
+
$oAcute = [char]0x00F3
|
|
93
|
+
$emDash = [char]0x2014
|
|
94
|
+
$banner = "`n $tri A R K A O S`n The Operating System for AI Agent Teams`n`n Ol$oAcute, founder`n degraded: ArkaOS core not reachable $emDash run npx arkaos doctor"
|
|
95
|
+
[pscustomobject]@{ systemMessage = $banner } | ConvertTo-Json -Compress
|
|
96
|
+
exit 0
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
# ONE python process (core.hooks.session_start) builds the entire
|
|
6
6
|
# systemMessage — the 13 inline python spawns of the previous version
|
|
7
7
|
# live there now (baseline 251ms p50 before consolidation). This file
|
|
8
|
-
# only resolves the interpreter and execs;
|
|
9
|
-
#
|
|
8
|
+
# only resolves the interpreter and execs; whenever it cannot reach that
|
|
9
|
+
# producer — no repo root, no module file, no interpreter — it emits a
|
|
10
|
+
# static banner and exits 0 (fail-open, stdlib-free).
|
|
10
11
|
# ============================================================================
|
|
11
12
|
|
|
12
13
|
# _HOOK_CWD captured BEFORE any cd — inside a compound command $PWD
|
|
@@ -55,7 +56,10 @@ if [ -n "$REPO" ] && [ -f "$REPO/core/hooks/session_start.py" ] \
|
|
|
55
56
|
fi
|
|
56
57
|
|
|
57
58
|
# ─── Degraded fallback: static banner, valid JSON, exit 0 ──────────────
|
|
59
|
+
# Names the symptom, not a cause: three conditions reach here and only one
|
|
60
|
+
# of them is a missing interpreter. Kept byte-identical in wording to the
|
|
61
|
+
# .ps1 twin so an operator on either platform reads the same diagnosis.
|
|
58
62
|
cat <<'EOF'
|
|
59
|
-
{"systemMessage": "\n ▲ A R K A O S\n The Operating System for AI Agent Teams\n\n Olá, founder\n degraded:
|
|
63
|
+
{"systemMessage": "\n ▲ A R K A O S\n The Operating System for AI Agent Teams\n\n Olá, founder\n degraded: ArkaOS core not reachable — run npx arkaos doctor"}
|
|
60
64
|
EOF
|
|
61
65
|
exit 0
|
package/config/hooks/stop.ps1
CHANGED
|
@@ -24,7 +24,23 @@ $cwd = [string]$inp.cwd
|
|
|
24
24
|
|
|
25
25
|
if ($stopHookActive -eq "true") { exit 0 }
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
# Marker dir must match the Python writer: core.hooks.user_prompt_submit
|
|
28
|
+
# ::_wf_mark_required resolves it through core.shared.temp_paths
|
|
29
|
+
# ::wf_required_dir — which is arkaos_temp_dir('arkaos-wf-required'),
|
|
30
|
+
# i.e. %TEMP%\arkaos-wf-required on Windows, EXCEPT that it honors
|
|
31
|
+
# ARKA_WF_REQUIRED_DIR first. Read arkaos_temp_dir directly here and the
|
|
32
|
+
# override splits writer from reader: the writer moves, this gate finds no
|
|
33
|
+
# marker and allows everything, silently. That is the fail-open
|
|
34
|
+
# wf_required_dir exists to close, so mirror wf_required_dir, not
|
|
35
|
+
# arkaos_temp_dir.
|
|
36
|
+
# The previous hardcoded "/tmp/arkaos-wf-required" resolves to a path that
|
|
37
|
+
# exists on no stock Windows machine, so this gate exited 0 on every real
|
|
38
|
+
# session and the whole compliance chain below never ran.
|
|
39
|
+
$wfDir = $env:ARKA_WF_REQUIRED_DIR
|
|
40
|
+
if ([string]::IsNullOrWhiteSpace($wfDir)) {
|
|
41
|
+
$wfDir = Join-Path ([System.IO.Path]::GetTempPath()) 'arkaos-wf-required'
|
|
42
|
+
}
|
|
43
|
+
$wfMarker = Join-Path $wfDir $sessionId
|
|
28
44
|
if ([string]::IsNullOrWhiteSpace($sessionId) -or -not (Test-Path $wfMarker)) {
|
|
29
45
|
exit 0
|
|
30
46
|
}
|
|
@@ -193,6 +209,46 @@ try {
|
|
|
193
209
|
# Swallow — enqueue is fire-and-forget.
|
|
194
210
|
}
|
|
195
211
|
|
|
212
|
+
# ─── Governance parity (skill capture + warn-only detectors) ───────────
|
|
213
|
+
# config/hooks/stop.sh delegates the whole event to `python -m` the Stop
|
|
214
|
+
# entrypoint, which runs the full detector chain (the authoritative list
|
|
215
|
+
# is that module's own imports — no count is stated here because several
|
|
216
|
+
# are conditional, so no fixed number is true of a given event). This
|
|
217
|
+
# port reimplements the event and reached only two of them, so four
|
|
218
|
+
# detectors that exist, are tested and are wired on POSIX never ran on
|
|
219
|
+
# Windows at all — most visibly skill_proposer, whose constitution rule
|
|
220
|
+
# (mandatory-skill-evaluation) mandates a capability sweep after every
|
|
221
|
+
# completed task.
|
|
222
|
+
#
|
|
223
|
+
# The sweep is a real module, not an inline script: core/hooks/
|
|
224
|
+
# stop_governance.py. Delegating rather than mirroring is what keeps the
|
|
225
|
+
# owner-only umask on the state files it writes — the previous inline
|
|
226
|
+
# copy of _write_tmp_state had already lost it — and it is what lets the
|
|
227
|
+
# parity test IMPORT AND RUN the sweep instead of grepping module names
|
|
228
|
+
# out of this file, which a comment could satisfy.
|
|
229
|
+
#
|
|
230
|
+
# Scope is deliberately the NON-ENFORCEMENT subset: these four write
|
|
231
|
+
# proposals and diagnostic state only. The gating checks
|
|
232
|
+
# (closing_marker_check, meta_tag_check, kb_cite_check, dna_fidelity)
|
|
233
|
+
# are left out of this change — they feed enforcement surfaces and
|
|
234
|
+
# deserve their own baseline on Windows before being switched on.
|
|
235
|
+
$governanceModule = Join-Path $env:ARKAOS_ROOT "core/hooks/stop_governance.py"
|
|
236
|
+
if (Test-Path -LiteralPath $governanceModule) {
|
|
237
|
+
# PREPEND, never clobber: an operator's own PYTHONPATH must survive
|
|
238
|
+
# the hook (the .sh twin prepends for the same reason).
|
|
239
|
+
$sep = [IO.Path]::PathSeparator
|
|
240
|
+
$env:PYTHONPATH = if ($env:PYTHONPATH) {
|
|
241
|
+
"$($env:ARKAOS_ROOT)$sep$($env:PYTHONPATH)"
|
|
242
|
+
} else {
|
|
243
|
+
$env:ARKAOS_ROOT
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
& $pythonExe -m core.hooks.stop_governance | Out-Null
|
|
247
|
+
} catch {
|
|
248
|
+
# Swallow — governance observation must never affect the Stop hook.
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
196
252
|
# Belt-and-braces marker cleanup (safe even if the Python block crashed).
|
|
197
253
|
if ($sessionId -match '^[A-Za-z0-9._-]{1,128}$') {
|
|
198
254
|
Remove-Item -LiteralPath $wfMarker -ErrorAction SilentlyContinue
|