diff --git a/packaging/chocolatey/README.md b/packaging/chocolatey/README.md
new file mode 100644
index 0000000..10b4acc
--- /dev/null
+++ b/packaging/chocolatey/README.md
@@ -0,0 +1,59 @@
+# DevNav Chocolatey packaging source
+
+This directory contains the source for a future Chocolatey package. It is not
+published and is deliberately not tied to v0.14.0: the package must be
+materialized from the release manifest of a future release that includes this
+integration.
+
+The package is machine-owned and installs the official, versioned release
+assets into:
+
+```text
+%ChocolateyInstall%\lib\devnav\tools\DevNav\
+ dev.exe
+ DevNav.psm1
+ DevNav.psd1
+ .devnav-managed-by-chocolatey
+```
+
+It registers `%ChocolateyInstall%\lib\devnav\tools` in the machine
+`PSModulePath`, without changing any PowerShell profile. The module keeps each
+user's configuration in that user's `%LOCALAPPDATA%\DevNav` directory.
+
+## Materialize and pack
+
+The release workflow's `release-manifest.json` is the input source of truth.
+The materializer constructs the immutable GitHub Release URLs and injects the
+published SHA-256 values; it rejects other URLs, missing architectures and
+invalid hashes.
+
+```powershell
+./scripts/New-DevNavChocolateyPackage.ps1 `
+ -Version 0.15.0 `
+ -ReleaseManifest .\release-manifest.json `
+ -OutputDirectory $env:TEMP\devnav-chocolatey
+choco pack $env:TEMP\devnav-chocolatey\devnav.nuspec
+```
+
+Do not add compiled binaries to this repository and do not run `choco push`
+from this project without a separate release decision.
+
+## Architecture and ownership
+
+Native architecture is detected from `PROCESSOR_ARCHITECTURE` and
+`PROCESSOR_ARCHITEW6432`, not Chocolatey's processor-width helpers. Native
+x64 and ARM64 select their matching official binary. Real x86 is rejected,
+and `--forcex86` cannot select an incorrect asset.
+
+Installation, upgrade and uninstall are expected to be idempotent. The
+Chocolatey marker prevents DevNav self-updates; `dev update` only tells the
+user to run `choco upgrade devnav`. The package never runs DevNav as
+administrator to initialize user configuration.
+
+## Community verifier limitation
+
+DevNav supports Windows 10/11 on x64 and ARM64. It does not support x86 and
+does not declare Windows Server 2019. The Chocolatey Community verifier runs
+on Windows Server 2019 and also exercises `forcex86`. Publishing will
+therefore require a verifier exemption request; this source does not broaden
+DevNav's supported platforms and does not request the exemption yet.
diff --git a/packaging/chocolatey/devnav.nuspec b/packaging/chocolatey/devnav.nuspec
new file mode 100644
index 0000000..9534cbc
--- /dev/null
+++ b/packaging/chocolatey/devnav.nuspec
@@ -0,0 +1,16 @@
+
+
+
+ devnav
+ __VERSION__
+ DevNav
+ JacobOptimiza
+ JacobOptimiza
+ https://github.com/JacobOptimiza/dev-nav/blob/main/LICENSE
+ https://github.com/JacobOptimiza/dev-nav
+ false
+ Native Rust workspace navigator and coding-agent launcher for PowerShell 7 on Windows.
+ Keyboard-first workspace navigation for Windows.
+ devnav developer powershell tui workspace navigator portable
+
+
diff --git a/packaging/chocolatey/tools/DevNavChocolatey.ps1 b/packaging/chocolatey/tools/DevNavChocolatey.ps1
new file mode 100644
index 0000000..eed6b13
--- /dev/null
+++ b/packaging/chocolatey/tools/DevNavChocolatey.ps1
@@ -0,0 +1,80 @@
+Set-StrictMode -Version Latest
+
+function Get-DevNavNativeArchitecture {
+ [CmdletBinding()]
+ param(
+ [string] $ProcessorArchitecture = $env:PROCESSOR_ARCHITECTURE,
+ [string] $ProcessorArchW6432 = $env:PROCESSOR_ARCHITEW6432,
+ [bool] $ForceX86 = ($env:ChocolateyForceX86 -eq 'true')
+ )
+
+ $process = ([string]$ProcessorArchitecture).Trim().ToUpperInvariant()
+ $wow = ([string]$ProcessorArchW6432).Trim().ToUpperInvariant()
+ $native = if ($process -eq 'ARM64' -or $wow -eq 'ARM64') {
+ 'arm64'
+ }
+ elseif ($process -eq 'AMD64' -or $wow -eq 'AMD64') {
+ 'x64'
+ }
+ elseif ($process -eq 'X86' -and [string]::IsNullOrWhiteSpace($wow)) {
+ 'x86'
+ }
+ else {
+ throw "Unsupported Windows architecture environment: PROCESSOR_ARCHITECTURE='$process', PROCESSOR_ARCHITEW6432='$wow'."
+ }
+
+ if ($ForceX86 -and $native -ne 'x86') {
+ throw "Chocolatey --forcex86 is unsafe for DevNav: the native OS architecture is $native. DevNav does not provide x86 assets."
+ }
+ if ($native -eq 'x86') {
+ throw 'DevNav does not support x86 Windows. Use a native x64 or ARM64 system.'
+ }
+ return $native
+}
+
+function Get-DevNavAssetSet {
+ param(
+ [Parameter(Mandatory)][ValidateSet('x64', 'arm64')][string] $Architecture,
+ [Parameter(Mandatory)][hashtable] $Assets
+ )
+
+ $selected = $Assets[$Architecture]
+ if ($null -eq $selected) { throw "No DevNav release assets were supplied for $Architecture." }
+ foreach ($name in @('dev', 'module', 'manifest')) {
+ if ([string]::IsNullOrWhiteSpace([string]$selected[$name].url) -or
+ [string]::IsNullOrWhiteSpace([string]$selected[$name].sha256)) {
+ throw "The $Architecture DevNav asset '$name' is missing its URL or SHA-256."
+ }
+ }
+ return $selected
+}
+
+function Assert-DevNavSha256 {
+ param(
+ [Parameter(Mandatory)][string] $Path,
+ [Parameter(Mandatory)][ValidatePattern('^[A-Fa-f0-9]{64}$')][string] $Expected
+ )
+ $actual = (Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash
+ if ($actual -ne $Expected) { throw "SHA-256 mismatch for '$Path'." }
+}
+
+function Add-DevNavMachineModulePath {
+ param([Parameter(Mandatory)][string] $PackageTools)
+
+ $machine = [Environment]::GetEnvironmentVariable('PSModulePath', 'Machine')
+ $entries = @($machine -split ';' | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
+ if ($entries -notcontains $PackageTools) {
+ [Environment]::SetEnvironmentVariable('PSModulePath', (($entries + $PackageTools) -join ';'), 'Machine')
+ }
+}
+
+function Remove-DevNavMachineModulePath {
+ [CmdletBinding(SupportsShouldProcess)]
+ param([Parameter(Mandatory)][string] $PackageTools)
+
+ $machine = [Environment]::GetEnvironmentVariable('PSModulePath', 'Machine')
+ $entries = @($machine -split ';' | Where-Object { -not [string]::IsNullOrWhiteSpace($_) -and $_ -ne $PackageTools })
+ if ($PSCmdlet.ShouldProcess('Machine PSModulePath', "Remove '$PackageTools'")) {
+ [Environment]::SetEnvironmentVariable('PSModulePath', ($entries -join ';'), 'Machine')
+ }
+}
diff --git a/packaging/chocolatey/tools/chocolateyInstall.ps1 b/packaging/chocolatey/tools/chocolateyInstall.ps1
new file mode 100644
index 0000000..c67bd5a
--- /dev/null
+++ b/packaging/chocolatey/tools/chocolateyInstall.ps1
@@ -0,0 +1,29 @@
+$ErrorActionPreference = 'Stop'
+. (Join-Path $PSScriptRoot 'DevNavChocolatey.ps1')
+
+$architecture = Get-DevNavNativeArchitecture
+$assets = @{
+ x64 = @{
+ dev = @{ url = '__X64_DEV_URL__'; sha256 = '__X64_DEV_SHA256__' }
+ module = @{ url = '__MODULE_URL__'; sha256 = '__MODULE_SHA256__' }
+ manifest = @{ url = '__MANIFEST_URL__'; sha256 = '__MANIFEST_SHA256__' }
+ }
+ arm64 = @{
+ dev = @{ url = '__ARM64_DEV_URL__'; sha256 = '__ARM64_DEV_SHA256__' }
+ module = @{ url = '__MODULE_URL__'; sha256 = '__MODULE_SHA256__' }
+ manifest = @{ url = '__MANIFEST_URL__'; sha256 = '__MANIFEST_SHA256__' }
+ }
+}
+$selected = Get-DevNavAssetSet -Architecture $architecture -Assets $assets
+$packageTools = Join-Path $env:ChocolateyPackageFolder 'tools'
+$moduleRoot = Join-Path $packageTools 'DevNav'
+New-Item -ItemType Directory -Path $moduleRoot -Force | Out-Null
+
+Get-ChocolateyWebFile -PackageName 'devnav' -FileFullPath (Join-Path $moduleRoot 'dev.exe') -Url $selected.dev.url -Checksum $selected.dev.sha256 -ChecksumType 'sha256'
+Get-ChocolateyWebFile -PackageName 'devnav' -FileFullPath (Join-Path $moduleRoot 'DevNav.psm1') -Url $selected.module.url -Checksum $selected.module.sha256 -ChecksumType 'sha256'
+Get-ChocolateyWebFile -PackageName 'devnav' -FileFullPath (Join-Path $moduleRoot 'DevNav.psd1') -Url $selected.manifest.url -Checksum $selected.manifest.sha256 -ChecksumType 'sha256'
+Assert-DevNavSha256 -Path (Join-Path $moduleRoot 'dev.exe') -Expected $selected.dev.sha256
+Assert-DevNavSha256 -Path (Join-Path $moduleRoot 'DevNav.psm1') -Expected $selected.module.sha256
+Assert-DevNavSha256 -Path (Join-Path $moduleRoot 'DevNav.psd1') -Expected $selected.manifest.sha256
+New-Item -ItemType File -Path (Join-Path $moduleRoot '.devnav-managed-by-chocolatey') -Force | Out-Null
+Add-DevNavMachineModulePath -PackageTools $packageTools
diff --git a/packaging/chocolatey/tools/chocolateyUninstall.ps1 b/packaging/chocolatey/tools/chocolateyUninstall.ps1
new file mode 100644
index 0000000..62cbe67
--- /dev/null
+++ b/packaging/chocolatey/tools/chocolateyUninstall.ps1
@@ -0,0 +1,3 @@
+$ErrorActionPreference = 'Stop'
+. (Join-Path $PSScriptRoot 'DevNavChocolatey.ps1')
+Remove-DevNavMachineModulePath -PackageTools (Join-Path $env:ChocolateyPackageFolder 'tools')
diff --git a/scripts/New-DevNavChocolateyPackage.ps1 b/scripts/New-DevNavChocolateyPackage.ps1
new file mode 100644
index 0000000..022fe26
--- /dev/null
+++ b/scripts/New-DevNavChocolateyPackage.ps1
@@ -0,0 +1,55 @@
+[CmdletBinding()]
+param(
+ [Parameter(Mandatory)][ValidatePattern('^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$')][string] $Version,
+ [Parameter(Mandatory)][ValidateScript({ Test-Path -LiteralPath $_ -PathType Leaf })][string] $ReleaseManifest,
+ [Parameter(Mandatory)][string] $OutputDirectory
+)
+
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+$templateRoot = Join-Path $PSScriptRoot '..\packaging\chocolatey'
+$manifest = Get-Content -LiteralPath $ReleaseManifest -Raw | ConvertFrom-Json
+$baseUrl = "https://github.com/JacobOptimiza/dev-nav/releases/download/v$Version"
+$artifacts = $manifest.artifacts
+$assets = @{}
+foreach ($architecture in @('x64', 'arm64')) {
+ $suffix = if ($architecture -eq 'x64') { 'x86_64' } else { 'aarch64' }
+ $binary = $artifacts."binary-$architecture"
+ if ($null -eq $binary) { throw "Release manifest has no binary-$architecture artifact." }
+ $assets[$architecture] = @{
+ dev = @{ url = "$baseUrl/dev-windows-$suffix.exe"; sha256 = [string]$binary.sha256 }
+ module = @{ url = "$baseUrl/DevNav.psm1"; sha256 = [string]$artifacts.module.sha256 }
+ manifest = @{ url = "$baseUrl/DevNav.psd1"; sha256 = [string]$artifacts.'module-manifest'.sha256 }
+ }
+}
+
+function Assert-ReleaseUrl([string]$url, [string]$expectedAsset) {
+ $prefix = "https://github.com/JacobOptimiza/dev-nav/releases/download/v$Version/"
+ if ($url -ne "$prefix$expectedAsset") { throw "Unexpected release URL for ${expectedAsset}: $url" }
+}
+Assert-ReleaseUrl $assets.x64.dev.url 'dev-windows-x86_64.exe'
+Assert-ReleaseUrl $assets.arm64.dev.url 'dev-windows-aarch64.exe'
+Assert-ReleaseUrl $assets.x64.module.url 'DevNav.psm1'
+Assert-ReleaseUrl $assets.x64.manifest.url 'DevNav.psd1'
+foreach ($architecture in @('x64', 'arm64')) {
+ foreach ($name in @('dev', 'module', 'manifest')) {
+ if ($assets[$architecture][$name].sha256 -notmatch '^[A-Fa-f0-9]{64}$') { throw "Invalid SHA-256 for $architecture/$name." }
+ }
+}
+
+New-Item -ItemType Directory -Path $OutputDirectory -Force | Out-Null
+Copy-Item -LiteralPath (Join-Path $templateRoot 'devnav.nuspec') -Destination $OutputDirectory -Force
+Copy-Item -LiteralPath (Join-Path $templateRoot 'tools') -Destination $OutputDirectory -Recurse -Force
+$nuspec = Join-Path $OutputDirectory 'devnav.nuspec'
+(Get-Content -LiteralPath $nuspec -Raw).Replace('__VERSION__', $Version) | Set-Content -LiteralPath $nuspec -Encoding utf8NoBOM
+$install = Join-Path $OutputDirectory 'tools\chocolateyInstall.ps1'
+$text = Get-Content -LiteralPath $install -Raw
+$replacements = @{
+ '__X64_DEV_URL__' = $assets.x64.dev.url; '__X64_DEV_SHA256__' = $assets.x64.dev.sha256
+ '__ARM64_DEV_URL__' = $assets.arm64.dev.url; '__ARM64_DEV_SHA256__' = $assets.arm64.dev.sha256
+ '__MODULE_URL__' = $assets.x64.module.url; '__MODULE_SHA256__' = $assets.x64.module.sha256
+ '__MANIFEST_URL__' = $assets.x64.manifest.url; '__MANIFEST_SHA256__' = $assets.x64.manifest.sha256
+}
+foreach ($replacement in $replacements.GetEnumerator()) { $text = $text.Replace($replacement.Key, $replacement.Value) }
+$text | Set-Content -LiteralPath $install -Encoding utf8NoBOM
+Write-Output $OutputDirectory
diff --git a/tests/powershell/ChocolateyPackaging.Tests.ps1 b/tests/powershell/ChocolateyPackaging.Tests.ps1
new file mode 100644
index 0000000..ab3d5b4
--- /dev/null
+++ b/tests/powershell/ChocolateyPackaging.Tests.ps1
@@ -0,0 +1,75 @@
+BeforeAll {
+ $script:repositoryRoot = Split-Path -Parent (Split-Path -Parent $PSScriptRoot)
+ $script:helperPath = Join-Path $repositoryRoot 'packaging\chocolatey\tools\DevNavChocolatey.ps1'
+ $script:installPath = Join-Path $repositoryRoot 'packaging\chocolatey\tools\chocolateyInstall.ps1'
+ $script:materializerPath = Join-Path $repositoryRoot 'scripts\New-DevNavChocolateyPackage.ps1'
+ . $script:helperPath
+}
+
+Describe 'Chocolatey native architecture routing' {
+ It 'routes native x64 to x64' {
+ Get-DevNavNativeArchitecture -ProcessorArchitecture AMD64 -ProcessorArchW6432 '' | Should -Be 'x64'
+ }
+ It 'routes an x86 process on x64 to x64' {
+ Get-DevNavNativeArchitecture -ProcessorArchitecture x86 -ProcessorArchW6432 AMD64 | Should -Be 'x64'
+ }
+ It 'routes native ARM64 to arm64' {
+ Get-DevNavNativeArchitecture -ProcessorArchitecture ARM64 -ProcessorArchW6432 '' | Should -Be 'arm64'
+ }
+ It 'routes an x86 process on ARM64 to arm64' {
+ Get-DevNavNativeArchitecture -ProcessorArchitecture x86 -ProcessorArchW6432 ARM64 | Should -Be 'arm64'
+ }
+ It 'rejects real x86' {
+ { Get-DevNavNativeArchitecture -ProcessorArchitecture x86 -ProcessorArchW6432 '' } | Should -Throw '*does not support x86*'
+ }
+ It 'rejects forcex86 on a native supported OS' {
+ { Get-DevNavNativeArchitecture -ProcessorArchitecture AMD64 -ProcessorArchW6432 '' -ForceX86 $true } | Should -Throw '*forcex86*'
+ }
+}
+
+Describe 'Chocolatey package source contract' {
+ It 'uses official direct assets and the machine-owned marker' {
+ $source = (Get-Content -LiteralPath $script:installPath -Raw) + (Get-Content -LiteralPath $script:helperPath -Raw)
+ $source | Should -Match 'Get-ChocolateyWebFile'
+ $source | Should -Match '\.devnav-managed-by-chocolatey'
+ $source | Should -Not -Match 'DevNavSetup|Get-OSArchitectureWidth|Get-ProcessorBits'
+ $source | Should -Match 'PSModulePath'
+ }
+
+ It 'materializes versioned URLs and hashes from a release manifest' {
+ $root = Join-Path ([IO.Path]::GetTempPath()) ('devnav-choco-materialize-' + [guid]::NewGuid().ToString('N'))
+ New-Item -ItemType Directory -Path $root | Out-Null
+ try {
+ $manifest = [ordered]@{
+ schemaVersion = 1
+ version = '9.8.7'
+ artifacts = [ordered]@{
+ 'binary-x64' = @{ file = 'dev-windows-x86_64.exe'; sha256 = ('a' * 64) }
+ 'binary-arm64' = @{ file = 'dev-windows-aarch64.exe'; sha256 = ('b' * 64) }
+ module = @{ file = 'DevNav.psm1'; sha256 = ('c' * 64) }
+ 'module-manifest' = @{ file = 'DevNav.psd1'; sha256 = ('d' * 64) }
+ }
+ }
+ $manifestPath = Join-Path $root 'release-manifest.json'
+ $manifest | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath $manifestPath -Encoding utf8NoBOM
+ $output = Join-Path $root 'package'
+ & $script:materializerPath -Version '9.8.7' -ReleaseManifest $manifestPath -OutputDirectory $output | Out-Null
+ $nuspec = Get-Content (Join-Path $output 'devnav.nuspec') -Raw
+ $install = Get-Content (Join-Path $output 'tools\chocolateyInstall.ps1') -Raw
+ $nuspec | Should -Match '9\.8\.7'
+ $install | Should -Match 'releases/download/v9\.8\.7/dev-windows-x86_64\.exe'
+ $install | Should -Match ('a' * 64)
+ $install | Should -Not -Match '__[A-Z0-9_]+__'
+ }
+ finally { Remove-Item -LiteralPath $root -Recurse -Force -ErrorAction SilentlyContinue }
+ }
+
+ It 'rejects a tampered file checksum' {
+ $path = Join-Path ([IO.Path]::GetTempPath()) ('devnav-choco-tamper-' + [guid]::NewGuid().ToString('N'))
+ 'original' | Set-Content -LiteralPath $path -NoNewline
+ try {
+ { Assert-DevNavSha256 -Path $path -Expected ('0' * 64) } | Should -Throw '*SHA-256 mismatch*'
+ }
+ finally { Remove-Item -LiteralPath $path -Force -ErrorAction SilentlyContinue }
+ }
+}