arkaos 2.10.0 → 2.11.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/README.md +318 -107
- package/VERSION +1 -1
- package/config/hooks/cwd-changed.ps1 +144 -0
- package/config/hooks/post-tool-use.ps1 +347 -0
- package/config/hooks/post-tool-use.sh +6 -6
- package/config/hooks/pre-compact.ps1 +238 -0
- package/config/hooks/pre-compact.sh +10 -6
- package/config/hooks/session-start.ps1 +109 -0
- package/config/hooks/session-start.sh +1 -1
- package/config/hooks/user-prompt-submit.ps1 +287 -0
- package/config/hooks/user-prompt-submit.sh +5 -2
- package/config/statusline.ps1 +160 -0
- package/core/cognition/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/cognition/capture/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/cognition/capture/__pycache__/collector.cpython-313.pyc +0 -0
- package/core/cognition/capture/__pycache__/store.cpython-313.pyc +0 -0
- package/core/cognition/insights/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/cognition/insights/__pycache__/store.cpython-313.pyc +0 -0
- package/core/cognition/memory/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/cognition/memory/__pycache__/obsidian.cpython-313.pyc +0 -0
- package/core/cognition/memory/__pycache__/schemas.cpython-313.pyc +0 -0
- package/core/cognition/memory/__pycache__/vector.cpython-313.pyc +0 -0
- package/core/cognition/memory/__pycache__/writer.cpython-313.pyc +0 -0
- package/core/cognition/research/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/cognition/research/__pycache__/profiler.cpython-313.pyc +0 -0
- package/core/cognition/scheduler/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/cognition/scheduler/__pycache__/cli.cpython-313.pyc +0 -0
- package/core/cognition/scheduler/__pycache__/daemon.cpython-313.pyc +0 -0
- package/core/cognition/scheduler/__pycache__/platform.cpython-313.pyc +0 -0
- package/core/cognition/scheduler/daemon.py +77 -21
- package/core/cognition/scheduler/platform.py +43 -12
- package/core/knowledge/__pycache__/vector_store.cpython-313.pyc +0 -0
- package/core/knowledge/vector_store.py +50 -25
- package/core/synapse/__pycache__/layers.cpython-313.pyc +0 -0
- package/core/synapse/layers.py +2 -2
- package/installer/adapters/claude-code.js +72 -45
- package/installer/cli.js +19 -6
- package/installer/doctor.js +130 -18
- package/installer/index.js +592 -149
- package/installer/platform.js +20 -0
- package/installer/prompts.js +109 -5
- package/installer/python-resolver.js +251 -0
- package/installer/update.js +497 -62
- package/package.json +1 -1
- package/pyproject.toml +2 -2
- package/scripts/start-dashboard.ps1 +271 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# ArkaOS — SessionStart Hook (Windows / PowerShell 5.1+)
|
|
3
|
+
#
|
|
4
|
+
# Port of config/hooks/session-start.sh. Must remain behaviourally identical:
|
|
5
|
+
# - Same profile lookup, time-based greeting, version-drift detection.
|
|
6
|
+
# - Same JSON output contract: {"systemMessage": "<...>"} on stdout.
|
|
7
|
+
#
|
|
8
|
+
# Minimum PowerShell: 5.1 (shipped with every Windows 10+). No pwsh 7 prereq.
|
|
9
|
+
# Box-drawing characters are built from [char] codes so this file stays pure
|
|
10
|
+
# ASCII and avoids source-encoding pitfalls with PS 5.1 default ANSI reads.
|
|
11
|
+
# ============================================================================
|
|
12
|
+
|
|
13
|
+
$ErrorActionPreference = 'Stop'
|
|
14
|
+
# Force UTF-8 on stdout so ConvertTo-Json does not emit mojibake box chars.
|
|
15
|
+
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)
|
|
16
|
+
|
|
17
|
+
$arkaosHome = Join-Path $env:USERPROFILE '.arkaos'
|
|
18
|
+
|
|
19
|
+
# ─── Profile ───────────────────────────────────────────────────────────
|
|
20
|
+
$name = 'founder'
|
|
21
|
+
$company = 'WizardingCode'
|
|
22
|
+
$version = '2.x'
|
|
23
|
+
|
|
24
|
+
$profilePath = Join-Path $arkaosHome 'profile.json'
|
|
25
|
+
if (Test-Path -LiteralPath $profilePath) {
|
|
26
|
+
try {
|
|
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
|
+
}
|
|
36
|
+
|
|
37
|
+
$repoPathFile = Join-Path $arkaosHome '.repo-path'
|
|
38
|
+
if (Test-Path -LiteralPath $repoPathFile) {
|
|
39
|
+
try {
|
|
40
|
+
$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
|
+
|
|
50
|
+
# ─── Time greeting ─────────────────────────────────────────────────────
|
|
51
|
+
$hour = [int](Get-Date -Format 'HH')
|
|
52
|
+
if ($hour -ge 5 -and $hour -lt 12) { $greeting = 'Bom dia' }
|
|
53
|
+
elseif ($hour -ge 12 -and $hour -lt 19) { $greeting = 'Boa tarde' }
|
|
54
|
+
else { $greeting = 'Boa noite' }
|
|
55
|
+
|
|
56
|
+
# ─── Version drift ─────────────────────────────────────────────────────
|
|
57
|
+
$drift = ''
|
|
58
|
+
$syncStatePath = Join-Path $arkaosHome 'sync-state.json'
|
|
59
|
+
if (Test-Path -LiteralPath $syncStatePath) {
|
|
60
|
+
try {
|
|
61
|
+
$syncState = Get-Content -Raw -LiteralPath $syncStatePath -Encoding UTF8 | ConvertFrom-Json
|
|
62
|
+
$synced = if ($null -ne $syncState.version) { [string]$syncState.version } else { 'none' }
|
|
63
|
+
if ($synced -ne $version) {
|
|
64
|
+
$drift = "`n[arka:update-available] Core v$version != synced v$synced. Run /arka update."
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
$drift = "`n[arka:update-available] Sync state unreadable. Run /arka update."
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
$drift = "`n[arka:update-available] Never synced. Run /arka update."
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# ─── Build ASCII header from char codes ───────────────────────────────
|
|
74
|
+
$tl = [char]0x2554 # ╔
|
|
75
|
+
$tr = [char]0x2557 # ╗
|
|
76
|
+
$bl = [char]0x255A # ╚
|
|
77
|
+
$br = [char]0x255D # ╝
|
|
78
|
+
$h = [char]0x2550 # ═
|
|
79
|
+
$v = [char]0x2551 # ║
|
|
80
|
+
$bar = [string]($h.ToString() * 46)
|
|
81
|
+
|
|
82
|
+
$topLine = "$tl$bar$tr"
|
|
83
|
+
$bottomLine = "$bl$bar$br"
|
|
84
|
+
$empty = "$v" + (' ' * 46) + "$v"
|
|
85
|
+
function Pad-Line([string]$text) {
|
|
86
|
+
$innerWidth = 46
|
|
87
|
+
$padLeft = [int](($innerWidth - $text.Length) / 2)
|
|
88
|
+
$padRight = $innerWidth - $text.Length - $padLeft
|
|
89
|
+
return "$v" + (' ' * $padLeft) + $text + (' ' * $padRight) + "$v"
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
$lines = @(
|
|
93
|
+
$topLine
|
|
94
|
+
$empty
|
|
95
|
+
(Pad-Line 'A R K A O S')
|
|
96
|
+
$empty
|
|
97
|
+
(Pad-Line 'The Operating System for AI Teams')
|
|
98
|
+
(Pad-Line 'by WizardingCode')
|
|
99
|
+
$empty
|
|
100
|
+
$bottomLine
|
|
101
|
+
''
|
|
102
|
+
"$greeting, $name ($company)"
|
|
103
|
+
"ArkaOS v$version | 65 agents | 17 departments | 244+ skills$drift"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
$msg = "`n" + ($lines -join "`n")
|
|
107
|
+
|
|
108
|
+
# ─── Output as systemMessage JSON (single line, same contract as .sh) ──
|
|
109
|
+
[pscustomobject]@{ systemMessage = $msg } | ConvertTo-Json -Compress
|
|
@@ -39,7 +39,7 @@ else
|
|
|
39
39
|
fi
|
|
40
40
|
|
|
41
41
|
# ─── Build message ─────────────────────────────────────────────────────
|
|
42
|
-
MSG="╔══════════════════════════════════════════════╗\\n"
|
|
42
|
+
MSG="\\n╔══════════════════════════════════════════════╗\\n"
|
|
43
43
|
MSG+="║ ║\\n"
|
|
44
44
|
MSG+="║ A R K A O S ║\\n"
|
|
45
45
|
MSG+="║ ║\\n"
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# ArkaOS v2 - UserPromptSubmit Hook (Synapse Bridge) (Windows / PowerShell 5.1+)
|
|
3
|
+
#
|
|
4
|
+
# Port of config/hooks/user-prompt-submit.sh. Calls the Python Synapse bridge
|
|
5
|
+
# for 8-layer context injection, with a pure-PowerShell fallback for
|
|
6
|
+
# installations where Python is unavailable or the bridge is missing.
|
|
7
|
+
#
|
|
8
|
+
# Contract:
|
|
9
|
+
# - Reads a JSON payload from stdin (userInput / message / raw).
|
|
10
|
+
# - Emits a single-line JSON object on stdout:
|
|
11
|
+
# {"additionalContext": "<sync-notice><synapse-or-fallback>"}
|
|
12
|
+
# - Side effect: writes one line to the cache metrics JSONL.
|
|
13
|
+
#
|
|
14
|
+
# Target latency: <100ms (hook timeout is 10s in settings).
|
|
15
|
+
#
|
|
16
|
+
# File is pure ASCII. Any typographic characters that need to appear in the
|
|
17
|
+
# output are built from [char] codes at runtime so PS 5.1's default ANSI
|
|
18
|
+
# source read cannot mojibake them.
|
|
19
|
+
# ============================================================================
|
|
20
|
+
|
|
21
|
+
$ErrorActionPreference = 'Stop'
|
|
22
|
+
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)
|
|
23
|
+
|
|
24
|
+
# --- Read stdin ------------------------------------------------------------
|
|
25
|
+
$stdinText = [Console]::In.ReadToEnd()
|
|
26
|
+
$payload = $null
|
|
27
|
+
if (-not [string]::IsNullOrWhiteSpace($stdinText)) {
|
|
28
|
+
try { $payload = $stdinText | ConvertFrom-Json } catch { $payload = $null }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# --- V1 migration detection ------------------------------------------------
|
|
32
|
+
# The old logic fired whenever any v1 skill directory existed on disk and
|
|
33
|
+
# the migrated-from-v1 marker was absent. That is too noisy on a Windows
|
|
34
|
+
# install that has v2 running correctly alongside leftover v1 skill dirs
|
|
35
|
+
# (common when a user installed via `npx arkaos install` after having
|
|
36
|
+
# previously used v1): the hook early-returned with the MIGRATION message
|
|
37
|
+
# on every single user prompt, drowning out the Synapse context.
|
|
38
|
+
#
|
|
39
|
+
# The presence of a valid v2 install manifest at `~/.arkaos/install-
|
|
40
|
+
# manifest.json` is the canonical signal that v2 is functional. If we
|
|
41
|
+
# see that file, skip the migration nag — the user has clearly already
|
|
42
|
+
# moved to v2, even if the explicit `migrated-from-v1` flag file was
|
|
43
|
+
# never created.
|
|
44
|
+
$v2Manifest = Join-Path $env:USERPROFILE '.arkaos\install-manifest.json'
|
|
45
|
+
$v2Installed = Test-Path -LiteralPath $v2Manifest
|
|
46
|
+
|
|
47
|
+
if (-not $v2Installed) {
|
|
48
|
+
$v1Paths = @(
|
|
49
|
+
(Join-Path $env:USERPROFILE '.claude\skills\arka-os'),
|
|
50
|
+
(Join-Path $env:USERPROFILE '.claude\skills\arkaos')
|
|
51
|
+
)
|
|
52
|
+
$migrationMarker = Join-Path $env:USERPROFILE '.arkaos\migrated-from-v1'
|
|
53
|
+
|
|
54
|
+
foreach ($v1 in $v1Paths) {
|
|
55
|
+
if ((Test-Path -LiteralPath $v1 -PathType Container) -and -not (Test-Path -LiteralPath $migrationMarker)) {
|
|
56
|
+
$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
|
|
58
|
+
exit 0
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# --- Performance timing ----------------------------------------------------
|
|
64
|
+
$sw = [System.Diagnostics.Stopwatch]::StartNew()
|
|
65
|
+
|
|
66
|
+
# --- Sync version detection ------------------------------------------------
|
|
67
|
+
$syncNotice = ''
|
|
68
|
+
$arkaosHome = Join-Path $env:USERPROFILE '.arkaos'
|
|
69
|
+
$syncStatePath = Join-Path $arkaosHome 'sync-state.json'
|
|
70
|
+
$repoPathFile = Join-Path $arkaosHome '.repo-path'
|
|
71
|
+
|
|
72
|
+
$currentVersion = ''
|
|
73
|
+
if (Test-Path -LiteralPath $repoPathFile) {
|
|
74
|
+
try {
|
|
75
|
+
$repoPath = (Get-Content -Raw -LiteralPath $repoPathFile -Encoding UTF8).Trim()
|
|
76
|
+
if ($repoPath) {
|
|
77
|
+
$vFile = Join-Path $repoPath 'VERSION'
|
|
78
|
+
$pkgFile = Join-Path $repoPath 'package.json'
|
|
79
|
+
if (Test-Path -LiteralPath $vFile) {
|
|
80
|
+
$currentVersion = (Get-Content -Raw -LiteralPath $vFile -Encoding UTF8).Trim()
|
|
81
|
+
} elseif (Test-Path -LiteralPath $pkgFile) {
|
|
82
|
+
try {
|
|
83
|
+
$currentVersion = [string]((Get-Content -Raw -LiteralPath $pkgFile -Encoding UTF8 | ConvertFrom-Json).version)
|
|
84
|
+
} catch { }
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
} catch { }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if ($currentVersion) {
|
|
91
|
+
$syncedVersion = 'none'
|
|
92
|
+
if (Test-Path -LiteralPath $syncStatePath) {
|
|
93
|
+
try {
|
|
94
|
+
$ss = Get-Content -Raw -LiteralPath $syncStatePath -Encoding UTF8 | ConvertFrom-Json
|
|
95
|
+
if ($ss.version) { $syncedVersion = [string]$ss.version }
|
|
96
|
+
} catch { }
|
|
97
|
+
}
|
|
98
|
+
if ($currentVersion -ne $syncedVersion) {
|
|
99
|
+
$syncNotice = "[arka:update-available] ArkaOS v$currentVersion installed (synced: $syncedVersion). Run /arka update to sync all projects. "
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
# --- Resolve ARKAOS_ROOT (env var -> .repo-path -> ~/.arkaos -> fallback) --
|
|
104
|
+
if ($env:ARKAOS_ROOT) {
|
|
105
|
+
$arkaosRoot = $env:ARKAOS_ROOT
|
|
106
|
+
} elseif (Test-Path -LiteralPath $repoPathFile) {
|
|
107
|
+
try {
|
|
108
|
+
$arkaosRoot = (Get-Content -Raw -LiteralPath $repoPathFile -Encoding UTF8).Trim()
|
|
109
|
+
} catch {
|
|
110
|
+
$arkaosRoot = $null
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (-not $arkaosRoot) {
|
|
114
|
+
if (Test-Path -LiteralPath $arkaosHome -PathType Container) {
|
|
115
|
+
$arkaosRoot = $arkaosHome
|
|
116
|
+
} else {
|
|
117
|
+
# Portable fallback - matches the shell hook's final branch.
|
|
118
|
+
$arkaosRoot = if ($env:ARKA_OS) { $env:ARKA_OS } else { Join-Path $env:USERPROFILE '.claude\skills\arkaos' }
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
# --- Cache dir -------------------------------------------------------------
|
|
123
|
+
# Windows equivalent of /tmp: use the per-user temp dir so constitution
|
|
124
|
+
# caches are isolated per user.
|
|
125
|
+
$cacheRoot = [System.IO.Path]::GetTempPath()
|
|
126
|
+
$cacheDir = Join-Path $cacheRoot 'arkaos-context-cache'
|
|
127
|
+
$null = New-Item -ItemType Directory -Force -Path $cacheDir -ErrorAction SilentlyContinue
|
|
128
|
+
$cacheTtlSeconds = 300
|
|
129
|
+
|
|
130
|
+
# --- Extract user input ----------------------------------------------------
|
|
131
|
+
$userInput = ''
|
|
132
|
+
if ($payload) {
|
|
133
|
+
if ($payload.userInput) {
|
|
134
|
+
$userInput = [string]$payload.userInput
|
|
135
|
+
} elseif ($payload.message) {
|
|
136
|
+
$userInput = [string]$payload.message
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (-not $userInput) {
|
|
140
|
+
# Fallback: truncate raw stdin (mirrors `head -c 2000` from bash).
|
|
141
|
+
if ($stdinText.Length -gt 2000) {
|
|
142
|
+
$userInput = $stdinText.Substring(0, 2000)
|
|
143
|
+
} else {
|
|
144
|
+
$userInput = $stdinText
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
# --- Helper: locate a usable Python interpreter ----------------------------
|
|
149
|
+
function Find-Python {
|
|
150
|
+
# Prefer the ArkaOS venv python if the install manifest tells us where it is.
|
|
151
|
+
$manifestPath = Join-Path $env:USERPROFILE '.arkaos\install-manifest.json'
|
|
152
|
+
if (Test-Path -LiteralPath $manifestPath) {
|
|
153
|
+
try {
|
|
154
|
+
$m = Get-Content -Raw -LiteralPath $manifestPath -Encoding UTF8 | ConvertFrom-Json
|
|
155
|
+
if ($m.pythonCmd -and (Test-Path -LiteralPath $m.pythonCmd)) {
|
|
156
|
+
return $m.pythonCmd
|
|
157
|
+
}
|
|
158
|
+
} catch { }
|
|
159
|
+
}
|
|
160
|
+
# Fallback to the typical Windows venv layout.
|
|
161
|
+
$venvPy = Join-Path $env:USERPROFILE '.arkaos\venv\Scripts\python.exe'
|
|
162
|
+
if (Test-Path -LiteralPath $venvPy) { return $venvPy }
|
|
163
|
+
# System python. python3 is rare on Windows; `python` and `py -3` are typical.
|
|
164
|
+
foreach ($cmd in 'python3','python','py') {
|
|
165
|
+
$resolved = Get-Command $cmd -ErrorAction SilentlyContinue
|
|
166
|
+
if ($resolved) { return $resolved.Source }
|
|
167
|
+
}
|
|
168
|
+
return $null
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
# --- Try Python Synapse bridge ---------------------------------------------
|
|
172
|
+
$pythonResult = ''
|
|
173
|
+
$bridgeScript = Join-Path $arkaosRoot 'scripts\synapse-bridge.py'
|
|
174
|
+
$python = Find-Python
|
|
175
|
+
|
|
176
|
+
if ($python -and (Test-Path -LiteralPath $bridgeScript)) {
|
|
177
|
+
try {
|
|
178
|
+
$bridgeInput = [pscustomobject]@{ user_input = $userInput } | ConvertTo-Json -Compress
|
|
179
|
+
|
|
180
|
+
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
|
181
|
+
$psi.FileName = $python
|
|
182
|
+
# PS 5.1 / .NET Framework: ProcessStartInfo only exposes `Arguments`
|
|
183
|
+
# as a single command-line string. Quote both paths so spaces work.
|
|
184
|
+
$psi.Arguments = "`"$bridgeScript`" --root `"$arkaosRoot`""
|
|
185
|
+
$psi.RedirectStandardInput = $true
|
|
186
|
+
$psi.RedirectStandardOutput = $true
|
|
187
|
+
$psi.RedirectStandardError = $true
|
|
188
|
+
$psi.UseShellExecute = $false
|
|
189
|
+
$psi.CreateNoWindow = $true
|
|
190
|
+
$psi.StandardInputEncoding = [System.Text.UTF8Encoding]::new($false)
|
|
191
|
+
$psi.StandardOutputEncoding = [System.Text.UTF8Encoding]::new($false)
|
|
192
|
+
# Set ARKAOS_ROOT in the child environment.
|
|
193
|
+
if ($psi.EnvironmentVariables.ContainsKey('ARKAOS_ROOT')) {
|
|
194
|
+
$psi.EnvironmentVariables['ARKAOS_ROOT'] = $arkaosRoot
|
|
195
|
+
} else {
|
|
196
|
+
[void]$psi.EnvironmentVariables.Add('ARKAOS_ROOT', $arkaosRoot)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
$proc = [System.Diagnostics.Process]::Start($psi)
|
|
200
|
+
$proc.StandardInput.Write($bridgeInput)
|
|
201
|
+
$proc.StandardInput.Close()
|
|
202
|
+
# Cap the bridge call at 3 seconds so a stuck bridge cannot burn the
|
|
203
|
+
# full 10-second hook budget.
|
|
204
|
+
if (-not $proc.WaitForExit(3000)) {
|
|
205
|
+
try { $proc.Kill() } catch { }
|
|
206
|
+
} else {
|
|
207
|
+
$bridgeOutput = $proc.StandardOutput.ReadToEnd()
|
|
208
|
+
if ($bridgeOutput) {
|
|
209
|
+
try {
|
|
210
|
+
$parsed = $bridgeOutput | ConvertFrom-Json
|
|
211
|
+
if ($parsed.context_string) {
|
|
212
|
+
$pythonResult = [string]$parsed.context_string
|
|
213
|
+
}
|
|
214
|
+
} catch { }
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
} catch {
|
|
218
|
+
$pythonResult = ''
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
# --- Fallback: pure-PowerShell context -------------------------------------
|
|
223
|
+
if (-not $pythonResult) {
|
|
224
|
+
# L0 Constitution (5-min cache)
|
|
225
|
+
$l0CacheFile = Join-Path $cacheDir 'l0-constitution'
|
|
226
|
+
$l0 = ''
|
|
227
|
+
$cacheFresh = $false
|
|
228
|
+
if (Test-Path -LiteralPath $l0CacheFile) {
|
|
229
|
+
try {
|
|
230
|
+
$ageSeconds = ((Get-Date) - (Get-Item -LiteralPath $l0CacheFile).LastWriteTime).TotalSeconds
|
|
231
|
+
if ($ageSeconds -lt $cacheTtlSeconds) {
|
|
232
|
+
$l0 = (Get-Content -Raw -LiteralPath $l0CacheFile -Encoding UTF8).Trim()
|
|
233
|
+
if ($l0) { $cacheFresh = $true }
|
|
234
|
+
}
|
|
235
|
+
} catch { }
|
|
236
|
+
}
|
|
237
|
+
if (-not $cacheFresh) {
|
|
238
|
+
$l0 = '[Constitution] NON-NEGOTIABLE: branch-isolation, obsidian-output, authority-boundaries, security-gate, context-first, solid-clean-code, spec-driven, human-writing, squad-routing, full-visibility, sequential-validation, mandatory-qa, arka-supremacy | QUALITY-GATE: marta-cqo, eduardo-copy, francisca-tech-ux | MUST: conventional-commits, test-coverage, pattern-matching, actionable-output, memory-persistence'
|
|
239
|
+
try {
|
|
240
|
+
$utf8NoBom = [System.Text.UTF8Encoding]::new($false)
|
|
241
|
+
[System.IO.File]::WriteAllText($l0CacheFile, $l0, $utf8NoBom)
|
|
242
|
+
} catch { }
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
# L4 Git branch
|
|
246
|
+
$l4 = ''
|
|
247
|
+
try {
|
|
248
|
+
$branchOutput = & git rev-parse --abbrev-ref HEAD 2>$null
|
|
249
|
+
if ($LASTEXITCODE -eq 0 -and $branchOutput) {
|
|
250
|
+
$branch = $branchOutput.Trim()
|
|
251
|
+
if ($branch -and $branch -ne 'main' -and $branch -ne 'master' -and $branch -ne 'dev') {
|
|
252
|
+
$l4 = "[branch:$branch]"
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
} catch { }
|
|
256
|
+
|
|
257
|
+
# L7 Time
|
|
258
|
+
$hour = [int](Get-Date -Format 'HH')
|
|
259
|
+
if ($hour -ge 5 -and $hour -lt 12) { $l7 = '[time:morning]' }
|
|
260
|
+
elseif ($hour -ge 12 -and $hour -lt 18) { $l7 = '[time:afternoon]' }
|
|
261
|
+
else { $l7 = '[time:evening]' }
|
|
262
|
+
|
|
263
|
+
$pythonResult = (@($l0, $l4, $l7) | Where-Object { $_ }) -join ' '
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
# --- Output ----------------------------------------------------------------
|
|
267
|
+
$additionalContext = "$syncNotice$pythonResult"
|
|
268
|
+
[pscustomobject]@{ additionalContext = $additionalContext } | ConvertTo-Json -Compress
|
|
269
|
+
|
|
270
|
+
# --- Metrics (JSONL append) ------------------------------------------------
|
|
271
|
+
$elapsed = [int]$sw.ElapsedMilliseconds
|
|
272
|
+
if ($elapsed -gt 0) {
|
|
273
|
+
try {
|
|
274
|
+
$metricsLine = [pscustomobject]@{ hook = 'user-prompt-submit-v2'; ms = $elapsed } | ConvertTo-Json -Compress
|
|
275
|
+
$metricsFile = Join-Path $cacheDir 'hook-metrics.jsonl'
|
|
276
|
+
$utf8NoBom = [System.Text.UTF8Encoding]::new($false)
|
|
277
|
+
# Append mode: read existing bytes, append new line, write back.
|
|
278
|
+
# Small enough that this is cheaper than opening a FileStream.
|
|
279
|
+
$existing = ''
|
|
280
|
+
if (Test-Path -LiteralPath $metricsFile) {
|
|
281
|
+
$existing = [System.IO.File]::ReadAllText($metricsFile, $utf8NoBom)
|
|
282
|
+
}
|
|
283
|
+
[System.IO.File]::WriteAllText($metricsFile, "$existing$metricsLine`n", $utf8NoBom)
|
|
284
|
+
} catch {
|
|
285
|
+
# Metrics are best-effort.
|
|
286
|
+
}
|
|
287
|
+
}
|
|
@@ -58,7 +58,7 @@ _hook_ms() {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
# ─── Paths ───────────────────────────────────────────────────────────────
|
|
61
|
-
# Resolve ARKAOS_ROOT: env var → .repo-path →
|
|
61
|
+
# Resolve ARKAOS_ROOT: env var → .repo-path → $HOME/.arkaos → portable fallback
|
|
62
62
|
if [ -n "${ARKAOS_ROOT:-}" ]; then
|
|
63
63
|
: # already set
|
|
64
64
|
elif [ -f "$HOME/.arkaos/.repo-path" ]; then
|
|
@@ -66,7 +66,10 @@ elif [ -f "$HOME/.arkaos/.repo-path" ]; then
|
|
|
66
66
|
elif [ -d "$HOME/.arkaos" ]; then
|
|
67
67
|
ARKAOS_ROOT="$HOME/.arkaos"
|
|
68
68
|
else
|
|
69
|
-
|
|
69
|
+
# Portable fallback — matches user-prompt-submit-v2.sh. Power users can
|
|
70
|
+
# override with the ARKA_OS env var. Reached only on corrupt/uninitialised
|
|
71
|
+
# installs; synapse-bridge.py will fail gracefully if the path is wrong.
|
|
72
|
+
ARKAOS_ROOT="${ARKA_OS:-$HOME/.claude/skills/arkaos}"
|
|
70
73
|
fi
|
|
71
74
|
export ARKAOS_ROOT
|
|
72
75
|
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# ============================================================================
|
|
2
|
+
# ARKA OS — Two-Line Color-Coded Status Line for Claude Code (Windows port)
|
|
3
|
+
# Receives JSON via stdin, outputs formatted two-line status bar.
|
|
4
|
+
#
|
|
5
|
+
# Paridade 1:1 com config/statusline.sh. Usa ConvertFrom-Json nativo — no jq
|
|
6
|
+
# or bc dependency, works on stock Windows PowerShell 5.1 and PowerShell 7.
|
|
7
|
+
#
|
|
8
|
+
# Why a separate file instead of invoking the bash script: Windows has no
|
|
9
|
+
# POSIX shell on the default PATH, and wrapping via `wsl bash -c ...` adds
|
|
10
|
+
# ~150 ms per statusline render. This port matches the canonical schema
|
|
11
|
+
# documented by Claude Code's statusline payload (model.display_name,
|
|
12
|
+
# context_window.used_percentage, cost.total_cost_usd, etc.) — NOT the
|
|
13
|
+
# experimental statusline-v2.sh schema, which is broken.
|
|
14
|
+
# ============================================================================
|
|
15
|
+
|
|
16
|
+
$ErrorActionPreference = 'SilentlyContinue'
|
|
17
|
+
|
|
18
|
+
# Force UTF-8 on stdout so the Unicode box-drawing characters (█ ░ ▲)
|
|
19
|
+
# render correctly on Windows PowerShell 5.1, which otherwise emits
|
|
20
|
+
# cp1252 to the host and shows "?" in place of the blocks.
|
|
21
|
+
try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch {}
|
|
22
|
+
|
|
23
|
+
# ─── Read stdin ────────────────────────────────────────────────────────────
|
|
24
|
+
$raw = [Console]::In.ReadToEnd()
|
|
25
|
+
if ([string]::IsNullOrWhiteSpace($raw)) {
|
|
26
|
+
# Claude Code may invoke the statusline with no payload at startup.
|
|
27
|
+
# Emit a minimal banner so the bar is never blank.
|
|
28
|
+
Write-Output ([char]0x25B2 + 'ARKA')
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# ─── Parse JSON ────────────────────────────────────────────────────────────
|
|
33
|
+
try {
|
|
34
|
+
$j = $raw | ConvertFrom-Json
|
|
35
|
+
} catch {
|
|
36
|
+
Write-Output ([char]0x25B2 + 'ARKA | json parse failed')
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# Safe nested property access. PSCustomObject returns $null silently for
|
|
41
|
+
# missing members, which is the behavior we want — mimics jq's `// default`.
|
|
42
|
+
function Get-Field($obj, $path, $default) {
|
|
43
|
+
$cur = $obj
|
|
44
|
+
foreach ($seg in $path -split '\.') {
|
|
45
|
+
if ($null -eq $cur) { return $default }
|
|
46
|
+
$cur = $cur.$seg
|
|
47
|
+
}
|
|
48
|
+
if ($null -eq $cur) { return $default }
|
|
49
|
+
return $cur
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
$model = [string](Get-Field $j 'model.display_name' 'unknown')
|
|
53
|
+
$cwd = [string](Get-Field $j 'cwd' '')
|
|
54
|
+
$projDir = [string](Get-Field $j 'workspace.project_dir' '')
|
|
55
|
+
$pctRaw = Get-Field $j 'context_window.used_percentage' 0
|
|
56
|
+
$inTok = [int64](Get-Field $j 'context_window.total_input_tokens' 0)
|
|
57
|
+
$outTok = [int64](Get-Field $j 'context_window.total_output_tokens' 0)
|
|
58
|
+
$cost = [double](Get-Field $j 'cost.total_cost_usd' 0)
|
|
59
|
+
$durMs = [int64](Get-Field $j 'cost.total_duration_ms' 0)
|
|
60
|
+
$added = [int64](Get-Field $j 'cost.total_lines_added' 0)
|
|
61
|
+
$removed = [int64](Get-Field $j 'cost.total_lines_removed' 0)
|
|
62
|
+
|
|
63
|
+
$pct = [int][math]::Floor([double]$pctRaw)
|
|
64
|
+
if ($pct -lt 0) { $pct = 0 }
|
|
65
|
+
if ($pct -gt 100) { $pct = 100 }
|
|
66
|
+
|
|
67
|
+
# ─── Project name ─────────────────────────────────────────────────────────
|
|
68
|
+
$workDir = if ($cwd) { $cwd } else { $projDir }
|
|
69
|
+
if ([string]::IsNullOrEmpty($workDir)) {
|
|
70
|
+
$dirName = 'arka'
|
|
71
|
+
} else {
|
|
72
|
+
$dirName = Split-Path -Leaf $workDir
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# ─── Git branch ───────────────────────────────────────────────────────────
|
|
76
|
+
# No file cache: PowerShell startup already dominates the render cost, the
|
|
77
|
+
# git call is ~20 ms on NTFS, and caching across renders is unsafe because
|
|
78
|
+
# Claude Code can render the statusline from multiple cwd's concurrently.
|
|
79
|
+
$branch = ''
|
|
80
|
+
if ($workDir -and (Test-Path -LiteralPath $workDir)) {
|
|
81
|
+
$branch = & git -C $workDir rev-parse --abbrev-ref HEAD 2>$null
|
|
82
|
+
if ($null -eq $branch) { $branch = '' }
|
|
83
|
+
$branch = ([string]$branch).Trim()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# ─── ANSI colors ──────────────────────────────────────────────────────────
|
|
87
|
+
$ESC = [char]27
|
|
88
|
+
$C_RESET = "$ESC[0m"
|
|
89
|
+
$C_CYAN = "$ESC[0;36m"
|
|
90
|
+
$C_DIM = "$ESC[2m"
|
|
91
|
+
$C_WHITE = "$ESC[1;37m"
|
|
92
|
+
$C_GREEN = "$ESC[0;32m"
|
|
93
|
+
$C_YELLOW = "$ESC[1;33m"
|
|
94
|
+
$C_RED = "$ESC[0;31m"
|
|
95
|
+
$C_BLINK_RED = "$ESC[5;31m"
|
|
96
|
+
|
|
97
|
+
if ($pct -ge 90) { $C_BAR = $C_BLINK_RED }
|
|
98
|
+
elseif ($pct -ge 80) { $C_BAR = $C_RED }
|
|
99
|
+
elseif ($pct -ge 60) { $C_BAR = $C_YELLOW }
|
|
100
|
+
else { $C_BAR = $C_GREEN }
|
|
101
|
+
|
|
102
|
+
# ─── Format tokens (K/M, invariant culture so pt-PT doesn't emit "12,3K") ──
|
|
103
|
+
function Format-Tokens($n) {
|
|
104
|
+
$inv = [cultureinfo]::InvariantCulture
|
|
105
|
+
if ($n -ge 1000000) {
|
|
106
|
+
return ([double]($n / 1000000.0)).ToString('0.0', $inv) + 'M'
|
|
107
|
+
} elseif ($n -ge 1000) {
|
|
108
|
+
return ([double]($n / 1000.0)).ToString('0.0', $inv) + 'K'
|
|
109
|
+
} else {
|
|
110
|
+
return "$n"
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
$inFmt = Format-Tokens $inTok
|
|
115
|
+
$outFmt = Format-Tokens $outTok
|
|
116
|
+
|
|
117
|
+
# ─── Progress bar (10 chars) ──────────────────────────────────────────────
|
|
118
|
+
# Floor the division: 95 % → 9 blocks, matching the bash integer division.
|
|
119
|
+
# PowerShell's `/` returns double, so [int] alone would round-to-even and
|
|
120
|
+
# drift on odd multiples of 5.
|
|
121
|
+
$filled = [int][math]::Floor($pct / 10.0)
|
|
122
|
+
$empty = 10 - $filled
|
|
123
|
+
$block = [char]0x2588 # █
|
|
124
|
+
$shade = [char]0x2591 # ░
|
|
125
|
+
$bar = ([string]$block * $filled) + ([string]$shade * $empty)
|
|
126
|
+
|
|
127
|
+
# ─── Format duration ──────────────────────────────────────────────────────
|
|
128
|
+
$secs = [int64][math]::Floor($durMs / 1000.0)
|
|
129
|
+
if ($secs -ge 3600) {
|
|
130
|
+
$h = [int][math]::Floor($secs / 3600.0)
|
|
131
|
+
$m = [int][math]::Floor(($secs % 3600) / 60.0)
|
|
132
|
+
$timeFmt = "${h}h${m}m"
|
|
133
|
+
} elseif ($secs -ge 60) {
|
|
134
|
+
$m = [int][math]::Floor($secs / 60.0)
|
|
135
|
+
$r = [int]($secs % 60)
|
|
136
|
+
$timeFmt = "${m}m${r}s"
|
|
137
|
+
} else {
|
|
138
|
+
$timeFmt = "${secs}s"
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
# ─── Format cost (invariant culture: always "$0.00", never "$0,00") ───────
|
|
142
|
+
$costFmt = '$' + ([double]$cost).ToString('0.00', [cultureinfo]::InvariantCulture)
|
|
143
|
+
|
|
144
|
+
# ─── Build Line 1: Context bar ────────────────────────────────────────────
|
|
145
|
+
$triangle = [char]0x25B2 # ▲
|
|
146
|
+
$line1 = "${C_CYAN}${triangle}ARKA${C_RESET} ${C_WHITE}${dirName}${C_RESET}"
|
|
147
|
+
if ($branch -and $branch -ne 'main' -and $branch -ne 'master') {
|
|
148
|
+
$line1 += " ${C_DIM}on${C_RESET} ${C_GREEN}${branch}${C_RESET}"
|
|
149
|
+
}
|
|
150
|
+
$line1 += " ${C_DIM}|${C_RESET} ${model}"
|
|
151
|
+
|
|
152
|
+
# ─── Build Line 2: Metrics bar ────────────────────────────────────────────
|
|
153
|
+
$line2 = "${C_BAR}${bar} ${pct}%${C_RESET}"
|
|
154
|
+
$line2 += " ${C_DIM}|${C_RESET} ${inFmt} in ${outFmt} out"
|
|
155
|
+
$line2 += " ${C_DIM}|${C_RESET} ${C_GREEN}+${added}${C_RESET} ${C_RED}-${removed}${C_RESET}"
|
|
156
|
+
$line2 += " ${C_DIM}|${C_RESET} ${timeFmt}"
|
|
157
|
+
$line2 += " ${C_DIM}|${C_RESET} ${costFmt}"
|
|
158
|
+
|
|
159
|
+
Write-Output $line1
|
|
160
|
+
Write-Output $line2
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|