arkaos 5.11.0 → 5.12.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/config/hooks/_lib/arka_python.ps1 +33 -3
- package/config/hooks/_lib/arka_python.sh +33 -3
- package/config/hooks/_lib/workflow-classifier.sh +20 -0
- package/config/hooks/post-tool-use.ps1 +9 -2
- package/config/hooks/session-start.ps1 +60 -101
- package/config/hooks/stop.ps1 +139 -1
- package/config/hooks/user-prompt-submit.ps1 +62 -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/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 +14 -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/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.12.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.12.0
|
|
@@ -8,9 +8,37 @@
|
|
|
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 python3/python/py on PATH that can `import yaml
|
|
11
|
+
# 3. Any python3/python/py on PATH that can `import yaml`, skipping the
|
|
12
|
+
# Microsoft Store App Execution Aliases (see Test-StoreAlias).
|
|
12
13
|
# 4. Bare `python` as a last resort.
|
|
13
14
|
|
|
15
|
+
function Test-StoreAlias {
|
|
16
|
+
<#
|
|
17
|
+
.SYNOPSIS
|
|
18
|
+
True when a resolved command is a Microsoft Store App Execution Alias.
|
|
19
|
+
|
|
20
|
+
.DESCRIPTION
|
|
21
|
+
%LOCALAPPDATA%\Microsoft\WindowsApps holds zero-length reparse points
|
|
22
|
+
that Get-Command resolves like any other executable. On a default
|
|
23
|
+
Windows install `python3.exe` there points at the Python install
|
|
24
|
+
manager, and *running* it — which the probe below does, with
|
|
25
|
+
`-c "import yaml"` — makes the manager download and install a full
|
|
26
|
+
CPython into the current working directory.
|
|
27
|
+
|
|
28
|
+
For a hook that is a catastrophic side effect: the cwd is the user's
|
|
29
|
+
project, so a background hook silently drops a ~3700-file Python/
|
|
30
|
+
tree and a python_install_<timestamp>.log into their repo. Observed
|
|
31
|
+
on Windows 10, Python install manager 26.3.
|
|
32
|
+
|
|
33
|
+
Probing an alias can never help anyway: the stub carries no site
|
|
34
|
+
packages, so the `import yaml` check it exists to answer would fail
|
|
35
|
+
after paying the install.
|
|
36
|
+
#>
|
|
37
|
+
param([string]$Path)
|
|
38
|
+
if ([string]::IsNullOrWhiteSpace($Path)) { return $false }
|
|
39
|
+
return $Path -match '\\Microsoft\\WindowsApps\\'
|
|
40
|
+
}
|
|
41
|
+
|
|
14
42
|
function Resolve-ArkaPython {
|
|
15
43
|
if ($env:ARKAOS_PYTHON -and (Test-Path -LiteralPath $env:ARKAOS_PYTHON)) {
|
|
16
44
|
return $env:ARKAOS_PYTHON
|
|
@@ -29,9 +57,11 @@ function Resolve-ArkaPython {
|
|
|
29
57
|
}
|
|
30
58
|
}
|
|
31
59
|
|
|
32
|
-
|
|
60
|
+
# "python" before "python3": on Windows a real interpreter answers to
|
|
61
|
+
# the former, while the latter is usually only the Store alias.
|
|
62
|
+
foreach ($name in @("python", "py", "python3")) {
|
|
33
63
|
$cmd = Get-Command $name -ErrorAction SilentlyContinue
|
|
34
|
-
if ($cmd) {
|
|
64
|
+
if ($cmd -and -not (Test-StoreAlias $cmd.Source)) {
|
|
35
65
|
& $cmd.Source -c "import yaml" 2>$null
|
|
36
66
|
if ($LASTEXITCODE -eq 0) { return $cmd.Source }
|
|
37
67
|
}
|
|
@@ -28,24 +28,54 @@ arka_resolve_python() {
|
|
|
28
28
|
return 0
|
|
29
29
|
fi
|
|
30
30
|
|
|
31
|
+
# Scripts/python.exe is the Windows venv layout. Without it, a Git-Bash
|
|
32
|
+
# hook on Windows never matches the venv and always falls through to the
|
|
33
|
+
# PATH probe below -- which is where the Store alias lives.
|
|
31
34
|
for cand in \
|
|
32
35
|
"$HOME/.arkaos/venv/bin/python" \
|
|
33
36
|
"$HOME/.arkaos/venv/bin/python3" \
|
|
37
|
+
"$HOME/.arkaos/venv/Scripts/python.exe" \
|
|
34
38
|
"$HOME/.arkaos/.venv/bin/python" \
|
|
35
|
-
"$HOME/.arkaos/.venv/bin/python3"
|
|
39
|
+
"$HOME/.arkaos/.venv/bin/python3" \
|
|
40
|
+
"$HOME/.arkaos/.venv/Scripts/python.exe"; do
|
|
36
41
|
if [ -x "$cand" ]; then
|
|
37
42
|
printf '%s\n' "$cand"
|
|
38
43
|
return 0
|
|
39
44
|
fi
|
|
40
45
|
done
|
|
41
46
|
|
|
42
|
-
|
|
43
|
-
|
|
47
|
+
# Never probe a Microsoft Store App Execution Alias: under Git Bash on
|
|
48
|
+
# Windows `command -v python3` resolves to .../Microsoft/WindowsApps/
|
|
49
|
+
# python3, which is the Python install manager, not an interpreter. The
|
|
50
|
+
# `import yaml` check RUNS each candidate, and running that one installs
|
|
51
|
+
# a CPython into the current working directory -- the user's project, for
|
|
52
|
+
# a hook. It could not have succeeded anyway: an alias stub carries no
|
|
53
|
+
# site packages. On POSIX nothing ever matches that path: pure no-op.
|
|
54
|
+
#
|
|
55
|
+
# `python` is LAST on purpose. On Windows it is the only real interpreter
|
|
56
|
+
# left once the alias is filtered out; on POSIX one of the entries before
|
|
57
|
+
# it always matches first, so an old box where `python` is Python 2 keeps
|
|
58
|
+
# resolving exactly as it does today.
|
|
59
|
+
for cand in python3 /opt/homebrew/bin/python3 /usr/local/bin/python3 /usr/bin/python3 python; do
|
|
60
|
+
case "$(command -v "$cand" 2>/dev/null)" in
|
|
61
|
+
"") continue ;;
|
|
62
|
+
*[Ww]indows[Aa]pps*) continue ;;
|
|
63
|
+
esac
|
|
64
|
+
if "$cand" -c "import yaml" >/dev/null 2>&1; then
|
|
44
65
|
printf '%s\n' "$cand"
|
|
45
66
|
return 0
|
|
46
67
|
fi
|
|
47
68
|
done
|
|
48
69
|
|
|
70
|
+
# Last resort. The documented contract is `python3` + rc 1, kept as-is,
|
|
71
|
+
# except where that name is the Store alias: handing an alias back to a
|
|
72
|
+
# caller that will `exec` it is the very trap this function now avoids.
|
|
73
|
+
case "$(command -v python3 2>/dev/null)" in
|
|
74
|
+
*[Ww]indows[Aa]pps*)
|
|
75
|
+
printf '%s\n' "python"
|
|
76
|
+
return 1
|
|
77
|
+
;;
|
|
78
|
+
esac
|
|
49
79
|
printf '%s\n' "python3"
|
|
50
80
|
return 1
|
|
51
81
|
}
|
|
@@ -40,6 +40,13 @@ arka_wf_safe_session_id() {
|
|
|
40
40
|
# missed prompts were short continuations ("continua", "força") or
|
|
41
41
|
# ship-tier verbs ("ship", "publish", "merge", "release", "deploy")
|
|
42
42
|
# that prolong existing flow-required activity.
|
|
43
|
+
# Keep in sync with _WF_QUESTION_LEAD_PATTERN in core/hooks/
|
|
44
|
+
# user_prompt_submit.py. Interrogative-led prompts ending in "?" are
|
|
45
|
+
# information questions, not creation intent, even when they contain an
|
|
46
|
+
# action verb ("o que e que este projeto faz?"). Polite requests ("podes
|
|
47
|
+
# implementar X?") keep matching: their lead word is not interrogative.
|
|
48
|
+
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'
|
|
49
|
+
|
|
43
50
|
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
51
|
|
|
45
52
|
# Classify: returns "true" if the prompt looks like a creation/
|
|
@@ -56,6 +63,19 @@ arka_wf_classify() {
|
|
|
56
63
|
return 0
|
|
57
64
|
fi
|
|
58
65
|
|
|
66
|
+
# Question guard: interrogative lead + trailing "?" = information
|
|
67
|
+
# question, never creation intent (X5 false positive).
|
|
68
|
+
local trimmed
|
|
69
|
+
trimmed=$(printf '%s' "$text" | sed 's/[[:space:]]*$//')
|
|
70
|
+
case "$trimmed" in
|
|
71
|
+
*\?)
|
|
72
|
+
if echo "$text" | grep -qiE "$ARKA_WF_QUESTION_LEAD"; then
|
|
73
|
+
echo "false"
|
|
74
|
+
return 0
|
|
75
|
+
fi
|
|
76
|
+
;;
|
|
77
|
+
esac
|
|
78
|
+
|
|
59
79
|
if echo "$text" | grep -qiE "\b${ARKA_WF_VERB_PATTERN}\b"; then
|
|
60
80
|
echo "true"
|
|
61
81
|
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,80 @@
|
|
|
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; with no usable interpreter it emits a static
|
|
9
|
+
# banner and exits 0 (fail-open, dependency-free).
|
|
10
|
+
#
|
|
11
|
+
# WHY A WRAPPER AND NOT A PORT
|
|
12
|
+
# The previous version reimplemented the banner natively in PowerShell. It
|
|
13
|
+
# was a faithful port of the *older* .sh, from the era when the .sh built the
|
|
14
|
+
# banner in bash. When the .sh became a thin wrapper delegating to Python,
|
|
15
|
+
# this file did not follow - so on Windows the four contract blocks
|
|
16
|
+
# (EVIDENCE-FLOW, META-TAG, AUTHORITY, MODEL-FABRIC) were computed by nobody
|
|
17
|
+
# and never reached the model, while the Linux side got them every session.
|
|
18
|
+
# Reimplementing the blocks here would duplicate the producer and recreate
|
|
19
|
+
# the same drift one release later. One producer, two thin wrappers.
|
|
7
20
|
#
|
|
8
21
|
# Minimum PowerShell: 5.1 (shipped with every Windows 10+). No pwsh 7 prereq.
|
|
9
|
-
#
|
|
10
|
-
#
|
|
22
|
+
# This file stays pure ASCII; every non-ASCII byte in the output comes from
|
|
23
|
+
# Python's UTF-8 stdout, which is why the encoding setup below is required.
|
|
11
24
|
# ============================================================================
|
|
12
25
|
|
|
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)
|
|
26
|
+
$ErrorActionPreference = 'SilentlyContinue'
|
|
16
27
|
|
|
17
|
-
|
|
28
|
+
# Captured BEFORE any location change - inside a compound command the CWD
|
|
29
|
+
# would resolve after the push (QG blocker F1-A3: cross-project leak).
|
|
30
|
+
$env:ARKA_HOOK_CWD = (Get-Location).Path
|
|
18
31
|
|
|
19
|
-
#
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
32
|
+
# Force UTF-8 on both sides of the pipe. Python emits UTF-8; without this
|
|
33
|
+
# PS 5.1 decodes it as the ANSI codepage (cp1252 on pt-PT/pt-BR machines)
|
|
34
|
+
# and the banner arrives as mojibake - the failure mode tracked in #395/#396.
|
|
35
|
+
# PYTHONIOENCODING covers the producer, OutputEncoding the consumer.
|
|
36
|
+
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)
|
|
37
|
+
$env:PYTHONIOENCODING = 'utf-8'
|
|
23
38
|
|
|
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
|
-
}
|
|
39
|
+
# --- Shared Python resolver (sets $env:ARKA_PY, provides Resolve-ArkaRoot) ---
|
|
40
|
+
$arkaLib = Join-Path $PSScriptRoot '_lib\arka_python.ps1'
|
|
41
|
+
if (Test-Path -LiteralPath $arkaLib) { . $arkaLib }
|
|
36
42
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
43
|
+
# --- Resolve the repo root ---
|
|
44
|
+
# .repo-path can point at a purged npx cache; Resolve-ArkaRoot falls through
|
|
45
|
+
# to the ~/.arkaos/lib snapshot instead of returning a dead root.
|
|
46
|
+
$repo = ''
|
|
47
|
+
if (Get-Command Resolve-ArkaRoot -ErrorAction SilentlyContinue) {
|
|
48
|
+
$repo = Resolve-ArkaRoot
|
|
49
|
+
} else {
|
|
50
|
+
$repoPathFile = Join-Path $HOME '.arkaos/.repo-path'
|
|
51
|
+
if (Test-Path -LiteralPath $repoPathFile) {
|
|
40
52
|
$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
53
|
}
|
|
58
54
|
}
|
|
59
55
|
|
|
60
|
-
#
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
else { $greeting = 'Boa noite' }
|
|
56
|
+
# The MODULE file is the guard, not just core/: an older installed snapshot
|
|
57
|
+
# without it would delegate into "No module named ..." (exit 1, empty output
|
|
58
|
+
# - a broken SessionStart instead of a degraded banner).
|
|
59
|
+
$moduleFile = if ($repo) { Join-Path $repo 'core/hooks/session_start.py' } else { '' }
|
|
65
60
|
|
|
66
|
-
|
|
67
|
-
$
|
|
68
|
-
|
|
69
|
-
if (Test-Path -LiteralPath $syncStatePath) {
|
|
61
|
+
if ($repo -and $moduleFile -and (Test-Path -LiteralPath $moduleFile) -and $env:ARKA_PY) {
|
|
62
|
+
$env:PYTHONPATH = $repo
|
|
63
|
+
Push-Location -LiteralPath $repo
|
|
70
64
|
try {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if ($synced -ne $version) {
|
|
74
|
-
$drift = "`n[arka:update-available] Core v$version != synced v$synced. Run /arka update."
|
|
75
|
-
}
|
|
65
|
+
& $env:ARKA_PY -m core.hooks.session_start
|
|
66
|
+
if ($LASTEXITCODE -eq 0) { Pop-Location; exit 0 }
|
|
76
67
|
} catch {
|
|
77
|
-
|
|
68
|
+
# fall through to the degraded banner
|
|
78
69
|
}
|
|
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"
|
|
70
|
+
Pop-Location
|
|
100
71
|
}
|
|
101
72
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
$bottomLine
|
|
111
|
-
''
|
|
112
|
-
"$greeting, $name ($company)"
|
|
113
|
-
# Counts intentionally omitted (prompt-surface P0 2026-07-08): hand-typed
|
|
114
|
-
# numbers drift from docs_stats.py truth and change no model behavior.
|
|
115
|
-
"ArkaOS v$version$drift"
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
$msg = "`n" + ($lines -join "`n")
|
|
119
|
-
|
|
120
|
-
# ─── Output as systemMessage JSON (single line, same contract as .sh) ──
|
|
121
|
-
[pscustomobject]@{ systemMessage = $msg } | ConvertTo-Json -Compress
|
|
73
|
+
# --- Degraded fallback: static banner, valid JSON, exit 0 ------------------
|
|
74
|
+
# Same contract as the .sh fallback. Built from [char] codes so this source
|
|
75
|
+
# file needs no particular encoding to produce correct output.
|
|
76
|
+
$tri = [char]0x25B2
|
|
77
|
+
$oAcute = [char]0x00F3
|
|
78
|
+
$banner = "`n $tri A R K A O S`n The Operating System for AI Agent Teams`n`n Ol$oAcute, founder`n degraded: no usable interpreter - run npx arkaos doctor"
|
|
79
|
+
[pscustomobject]@{ systemMessage = $banner } | ConvertTo-Json -Compress
|
|
80
|
+
exit 0
|
package/config/hooks/stop.ps1
CHANGED
|
@@ -24,7 +24,17 @@ $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 (_wf_mark_required /
|
|
28
|
+
# core.shared.temp_paths.arkaos_temp_dir): %TEMP%\arkaos-wf-required on
|
|
29
|
+
# Windows, with the same ARKA_WF_REQUIRED_DIR override the writer honors.
|
|
30
|
+
# The previous hardcoded "/tmp/arkaos-wf-required" resolves to a path that
|
|
31
|
+
# exists on no stock Windows machine, so this gate exited 0 on every real
|
|
32
|
+
# session and the whole compliance chain below never ran.
|
|
33
|
+
$wfDir = $env:ARKA_WF_REQUIRED_DIR
|
|
34
|
+
if ([string]::IsNullOrWhiteSpace($wfDir)) {
|
|
35
|
+
$wfDir = Join-Path ([System.IO.Path]::GetTempPath()) 'arkaos-wf-required'
|
|
36
|
+
}
|
|
37
|
+
$wfMarker = Join-Path $wfDir $sessionId
|
|
28
38
|
if ([string]::IsNullOrWhiteSpace($sessionId) -or -not (Test-Path $wfMarker)) {
|
|
29
39
|
exit 0
|
|
30
40
|
}
|
|
@@ -193,6 +203,134 @@ try {
|
|
|
193
203
|
# Swallow — enqueue is fire-and-forget.
|
|
194
204
|
}
|
|
195
205
|
|
|
206
|
+
# ─── Governance parity (skill capture + warn-only detectors) ───────────
|
|
207
|
+
# config/hooks/stop.sh delegates the whole event to `python -m
|
|
208
|
+
# core.hooks.stop`, which runs seventeen core modules. This port
|
|
209
|
+
# reimplements the event inline and reached only two of them, so four
|
|
210
|
+
# detectors that exist, are tested and are wired on POSIX never ran on
|
|
211
|
+
# Windows at all — most visibly skill_proposer, whose constitution rule
|
|
212
|
+
# (mandatory-skill-evaluation) mandates a capability sweep after every
|
|
213
|
+
# completed task.
|
|
214
|
+
#
|
|
215
|
+
# Scope is deliberately the NON-ENFORCEMENT subset: these four write
|
|
216
|
+
# proposals and diagnostic state only. The gating checks
|
|
217
|
+
# (closing_marker_check, meta_tag_check, kb_cite_check, dna_fidelity)
|
|
218
|
+
# are left out of this change — they feed enforcement surfaces and
|
|
219
|
+
# deserve their own baseline on Windows before being switched on.
|
|
220
|
+
$governanceScript = @'
|
|
221
|
+
import os
|
|
222
|
+
import sys
|
|
223
|
+
from pathlib import Path
|
|
224
|
+
|
|
225
|
+
sys.path.insert(0, os.environ.get("ARKAOS_ROOT", ""))
|
|
226
|
+
try:
|
|
227
|
+
from core.workflow.flow_enforcer import _load_last_assistant_messages
|
|
228
|
+
except Exception:
|
|
229
|
+
sys.exit(0)
|
|
230
|
+
|
|
231
|
+
transcript_path = os.environ.get("TRANSCRIPT_PATH_VAL", "")
|
|
232
|
+
session_id = os.environ.get("SESSION_ID_VAL", "")
|
|
233
|
+
if not transcript_path:
|
|
234
|
+
sys.exit(0)
|
|
235
|
+
|
|
236
|
+
try:
|
|
237
|
+
msgs = _load_last_assistant_messages(transcript_path, n=1)
|
|
238
|
+
last = msgs[-1] if msgs else ""
|
|
239
|
+
except Exception:
|
|
240
|
+
last = ""
|
|
241
|
+
|
|
242
|
+
raw = None
|
|
243
|
+
try:
|
|
244
|
+
raw = Path(transcript_path).read_text(encoding="utf-8", errors="replace")
|
|
245
|
+
except Exception:
|
|
246
|
+
raw = None
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _write_tmp_state(prefix, sid, payload):
|
|
250
|
+
"""Mirror of core.hooks.stop._write_tmp_state (best-effort)."""
|
|
251
|
+
if not sid:
|
|
252
|
+
return
|
|
253
|
+
try:
|
|
254
|
+
import json
|
|
255
|
+
|
|
256
|
+
from core.shared.temp_paths import arkaos_temp_dir
|
|
257
|
+
|
|
258
|
+
target = arkaos_temp_dir(prefix)
|
|
259
|
+
target.mkdir(parents=True, exist_ok=True)
|
|
260
|
+
(target / f"{sid}.json").write_text(
|
|
261
|
+
json.dumps(payload), encoding="utf-8"
|
|
262
|
+
)
|
|
263
|
+
except Exception:
|
|
264
|
+
pass
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
safe_sid = None
|
|
268
|
+
try:
|
|
269
|
+
from core.shared.safe_session_id import safe_session_id
|
|
270
|
+
|
|
271
|
+
safe_sid = safe_session_id(session_id)
|
|
272
|
+
except Exception:
|
|
273
|
+
safe_sid = None
|
|
274
|
+
|
|
275
|
+
# 1. Capability capture — the constitution rule this port was missing.
|
|
276
|
+
try:
|
|
277
|
+
from core.governance.skill_proposer import evaluate as _eval_skill
|
|
278
|
+
|
|
279
|
+
_eval_skill(last)
|
|
280
|
+
except Exception:
|
|
281
|
+
pass
|
|
282
|
+
|
|
283
|
+
# 2. Sycophancy (warn-only telemetry).
|
|
284
|
+
try:
|
|
285
|
+
from core.governance.sycophancy_detector import detect_sycophancy
|
|
286
|
+
|
|
287
|
+
sv = detect_sycophancy(last)
|
|
288
|
+
if sv.is_sycophantic and safe_sid:
|
|
289
|
+
_write_tmp_state("arkaos-sycophancy", safe_sid, {
|
|
290
|
+
"is_sycophantic": sv.is_sycophantic,
|
|
291
|
+
"signals": sv.signals,
|
|
292
|
+
"confidence": sv.confidence,
|
|
293
|
+
})
|
|
294
|
+
except Exception:
|
|
295
|
+
pass
|
|
296
|
+
|
|
297
|
+
# 3. Phantom actions — narrated effects with no tool call in the turn.
|
|
298
|
+
try:
|
|
299
|
+
from core.governance.phantom_action_check import check_phantom_actions
|
|
300
|
+
|
|
301
|
+
pr = check_phantom_actions(last, raw)
|
|
302
|
+
if safe_sid:
|
|
303
|
+
_write_tmp_state("arkaos-phantom", safe_sid, {
|
|
304
|
+
"passed": pr.passed,
|
|
305
|
+
"reason": pr.reason,
|
|
306
|
+
"claims": pr.claims,
|
|
307
|
+
"suggestion": pr.suggestion,
|
|
308
|
+
})
|
|
309
|
+
except Exception:
|
|
310
|
+
pass
|
|
311
|
+
|
|
312
|
+
# 4. Tool loops.
|
|
313
|
+
try:
|
|
314
|
+
from core.governance.tool_loop_check import check_tool_loops
|
|
315
|
+
|
|
316
|
+
lv = check_tool_loops(raw)
|
|
317
|
+
if lv.detected and safe_sid:
|
|
318
|
+
_write_tmp_state("arkaos-tool-loop", safe_sid, {
|
|
319
|
+
"tool": lv.tool,
|
|
320
|
+
"repeats": lv.repeats,
|
|
321
|
+
"pattern": lv.pattern,
|
|
322
|
+
"total_tool_uses": lv.total_tool_uses,
|
|
323
|
+
})
|
|
324
|
+
except Exception:
|
|
325
|
+
pass
|
|
326
|
+
'@
|
|
327
|
+
|
|
328
|
+
try {
|
|
329
|
+
$governanceScript | & $pythonExe - | Out-Null
|
|
330
|
+
} catch {
|
|
331
|
+
# Swallow — governance observation must never affect the Stop hook.
|
|
332
|
+
}
|
|
333
|
+
|
|
196
334
|
# Belt-and-braces marker cleanup (safe even if the Python block crashed).
|
|
197
335
|
if ($sessionId -match '^[A-Za-z0-9._-]{1,128}$') {
|
|
198
336
|
Remove-Item -LiteralPath $wfMarker -ErrorAction SilentlyContinue
|