javi-forge 1.36.0 → 1.37.1

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 CHANGED
@@ -372,6 +372,11 @@ npx javi-forge doctor
372
372
  - **Project Manifest** — Reads `.javi-forge/manifest.json`
373
373
  - **Installed Modules** — engram, obsidian-brain, memory-simple, ghagga
374
374
 
375
+
376
+ ## macOS CI-Local support
377
+
378
+ macOS is deprecated and unsupported for new CI-Local install/startup. Pin a supported release or migrate your workflow. Existing installed guards are not removed in 1.x; Darwin code removal is planned separately for 2.0.
379
+
375
380
  ## Requirements
376
381
 
377
382
  - **Node.js** >= 22 (required by ink 7; previous versions ran on >= 18)
@@ -14,8 +14,8 @@
14
14
  ]
15
15
  },
16
16
  "pre-push": {
17
- "version": 3,
18
- "sha256": "c396387a15c3c448d524f68597a993f6b4341ae06143d2d5907ad914823d0ad0",
17
+ "version": 4,
18
+ "sha256": "ffe1b5dd0931617df3c9fea37947dac84dffd11c490c39590459e861b404148d",
19
19
  "historical": [
20
20
  {
21
21
  "sha256": "7de58640aeef33085a49f31f1d9d0c8bacde0069d6d3265ae41aa8d3cd14d7a5",
@@ -28,6 +28,10 @@
28
28
  {
29
29
  "sha256": "c396387a15c3c448d524f68597a993f6b4341ae06143d2d5907ad914823d0ad0",
30
30
  "firstCommit": "0779d746b23ad614a13181d60f1fe3aa034e91dd"
31
+ },
32
+ {
33
+ "sha256": "ffe1b5dd0931617df3c9fea37947dac84dffd11c490c39590459e861b404148d",
34
+ "firstCommit": "6b5799d9d9e8c528fad9dc1f7b641afbd3a60637"
31
35
  }
32
36
  ]
33
37
  },
@@ -4,13 +4,211 @@
4
4
  # Default: quick native CI gate (setup + lint + compile + gates — no tests, no coverage).
5
5
  # Requires javi-forge on PATH (npx fallback; offline npx fails -> hook fails closed).
6
6
  # To skip: git push --no-verify
7
- set -e
8
- if command -v javi-forge >/dev/null 2>&1; then
9
- javi-forge hooks run pre-push
10
- else
11
- npx javi-forge hooks run pre-push
12
- fi || {
7
+ fail_pre_push() {
8
+ message=$1
9
+ status=${2:-1}
13
10
  echo ""
14
- echo "pre-push FAILED - fix the issues above."
15
- exit 1
11
+ echo "pre-push FAILED - $message"
12
+ exit "$status"
13
+ }
14
+
15
+ hash_required_file() {
16
+ target=$1
17
+ if [ -L "$target" ] || [ ! -f "$target" ]; then
18
+ return 1
19
+ fi
20
+ git hash-object --no-filters "$target"
21
+ }
22
+
23
+ hash_optional_file() {
24
+ target=$1
25
+ if [ -L "$target" ]; then
26
+ return 1
27
+ fi
28
+ if [ -f "$target" ]; then
29
+ git hash-object --no-filters "$target"
30
+ return $?
31
+ fi
32
+ if [ -e "$target" ]; then
33
+ return 1
34
+ fi
35
+ printf '%s\n' absent
16
36
  }
37
+
38
+ # Git exports repository-local targeting variables to hooks. Preserve every
39
+ # path needed for this repository before clearing the complete Git-owned set.
40
+ if ! repository_root=$(git rev-parse --show-toplevel) || [ -z "$repository_root" ]; then
41
+ fail_pre_push "Could not resolve the repository root."
42
+ fi
43
+ if ! cd "$repository_root"; then
44
+ fail_pre_push "Could not return to the repository root."
45
+ fi
46
+ if ! git_dir=$(git rev-parse --absolute-git-dir) || [ -z "$git_dir" ]; then
47
+ fail_pre_push "Could not resolve the worktree Git directory."
48
+ fi
49
+ if ! common_dir=$(git rev-parse --path-format=absolute --git-common-dir) || [ -z "$common_dir" ]; then
50
+ fail_pre_push "Could not resolve the common Git directory."
51
+ fi
52
+ if ! index_path=$(git rev-parse --path-format=absolute --git-path index) || [ -z "$index_path" ]; then
53
+ fail_pre_push "Could not resolve the worktree index."
54
+ fi
55
+ if ! worktree_config_path=$(git rev-parse --path-format=absolute --git-path config.worktree) || [ -z "$worktree_config_path" ]; then
56
+ fail_pre_push "Could not resolve the worktree-local Git config."
57
+ fi
58
+
59
+ if ! git_local_env_vars=$(git rev-parse --local-env-vars); then
60
+ fail_pre_push "Could not discover Git-local environment variables."
61
+ fi
62
+
63
+ git_local_env_count=0
64
+ invalid_git_local_env_var=
65
+ saved_ifs=$IFS
66
+ IFS='
67
+ '
68
+ for git_local_env_var in $git_local_env_vars; do
69
+ git_local_env_count=$((git_local_env_count + 1))
70
+ case "$git_local_env_var" in
71
+ GIT_*) ;;
72
+ *) invalid_git_local_env_var=$git_local_env_var ;;
73
+ esac
74
+ case "$git_local_env_var" in
75
+ *[!A-Za-z0-9_]*) invalid_git_local_env_var=$git_local_env_var ;;
76
+ esac
77
+ if [ -n "$invalid_git_local_env_var" ]; then
78
+ break
79
+ fi
80
+ done
81
+ IFS=$saved_ifs
82
+
83
+ if [ "$git_local_env_count" -eq 0 ]; then
84
+ fail_pre_push "Git returned no Git-local environment variables."
85
+ fi
86
+ if [ -n "$invalid_git_local_env_var" ]; then
87
+ fail_pre_push "Invalid Git-local environment variable name: $invalid_git_local_env_var"
88
+ fi
89
+
90
+ # A bounded canary protects the metadata proven vulnerable in the incident.
91
+ common_config_path="$common_dir/config"
92
+ head_path="$git_dir/HEAD"
93
+ packed_refs_path="$common_dir/packed-refs"
94
+
95
+ if ! common_config_before=$(hash_required_file "$common_config_path"); then
96
+ fail_pre_push "Could not snapshot the common Git config."
97
+ fi
98
+ if ! head_before=$(hash_required_file "$head_path"); then
99
+ fail_pre_push "Could not snapshot HEAD."
100
+ fi
101
+ if ! index_before=$(hash_required_file "$index_path"); then
102
+ fail_pre_push "Could not snapshot the worktree index."
103
+ fi
104
+ if ! worktree_config_before=$(hash_optional_file "$worktree_config_path"); then
105
+ fail_pre_push "Could not snapshot the worktree-local Git config."
106
+ fi
107
+ if ! packed_refs_before=$(hash_optional_file "$packed_refs_path"); then
108
+ fail_pre_push "Could not snapshot packed refs."
109
+ fi
110
+
111
+ attached_ref=
112
+ ref_path=
113
+ ref_before=detached
114
+ if attached_ref=$(git symbolic-ref -q HEAD); then
115
+ if ! ref_path=$(git rev-parse --path-format=absolute --git-path "$attached_ref") || [ -z "$ref_path" ]; then
116
+ fail_pre_push "Could not resolve the attached HEAD ref."
117
+ fi
118
+ if ! ref_before=$(hash_optional_file "$ref_path"); then
119
+ fail_pre_push "Could not snapshot the attached HEAD ref."
120
+ fi
121
+ if ! ref_oid_before=$(git rev-parse --verify "$attached_ref"); then
122
+ fail_pre_push "Could not resolve the attached HEAD ref value."
123
+ fi
124
+ else
125
+ symbolic_ref_status=$?
126
+ if [ "$symbolic_ref_status" -ne 1 ]; then
127
+ fail_pre_push "Could not determine whether HEAD is attached."
128
+ fi
129
+ if ! ref_oid_before=$(git rev-parse --verify HEAD); then
130
+ fail_pre_push "Could not resolve detached HEAD."
131
+ fi
132
+ fi
133
+
134
+ # The scrub lives in a subshell: the hook retains its own targeting context for
135
+ # the post-test canary, while the dispatcher and every descendant start clean.
136
+ if (
137
+ scrub_ifs=$IFS
138
+ IFS='
139
+ '
140
+ for git_local_env_var in $git_local_env_vars; do
141
+ if ! unset "$git_local_env_var"; then
142
+ echo "pre-push: could not unset $git_local_env_var"
143
+ exit 1
144
+ fi
145
+ if declare -p "$git_local_env_var" >/dev/null 2>&1; then
146
+ echo "pre-push: $git_local_env_var remained set after unset"
147
+ exit 1
148
+ fi
149
+ done
150
+ IFS=$scrub_ifs
151
+
152
+ if ! cd "$repository_root"; then
153
+ echo "pre-push: could not return to the repository root after scrubbing"
154
+ exit 1
155
+ fi
156
+
157
+ if command -v javi-forge >/dev/null 2>&1; then
158
+ javi-forge hooks run pre-push
159
+ else
160
+ npx javi-forge hooks run pre-push
161
+ fi
162
+ ); then
163
+ test_status=0
164
+ else
165
+ test_status=$?
166
+ fi
167
+
168
+ if ! cd "$repository_root"; then
169
+ fail_pre_push "Could not return to the repository root after tests."
170
+ fi
171
+
172
+ if ! common_config_after=$(hash_required_file "$common_config_path"); then
173
+ fail_pre_push "Could not verify the common Git config after tests."
174
+ fi
175
+ if ! head_after=$(hash_required_file "$head_path"); then
176
+ fail_pre_push "Could not verify HEAD after tests."
177
+ fi
178
+ if ! index_after=$(hash_required_file "$index_path"); then
179
+ fail_pre_push "Could not verify the worktree index after tests."
180
+ fi
181
+ if ! worktree_config_after=$(hash_optional_file "$worktree_config_path"); then
182
+ fail_pre_push "Could not verify the worktree-local Git config after tests."
183
+ fi
184
+ if ! packed_refs_after=$(hash_optional_file "$packed_refs_path"); then
185
+ fail_pre_push "Could not verify packed refs after tests."
186
+ fi
187
+
188
+ ref_after=detached
189
+ if [ -n "$attached_ref" ]; then
190
+ if ! ref_after=$(hash_optional_file "$ref_path"); then
191
+ fail_pre_push "Could not verify the attached HEAD ref after tests."
192
+ fi
193
+ if ! ref_oid_after=$(git rev-parse --verify "$attached_ref"); then
194
+ fail_pre_push "Could not resolve the attached HEAD ref value after tests."
195
+ fi
196
+ else
197
+ if ! ref_oid_after=$(git rev-parse --verify HEAD); then
198
+ fail_pre_push "Could not resolve detached HEAD after tests."
199
+ fi
200
+ fi
201
+
202
+ if [ "$common_config_before" != "$common_config_after" ] ||
203
+ [ "$head_before" != "$head_after" ] ||
204
+ [ "$index_before" != "$index_after" ] ||
205
+ [ "$worktree_config_before" != "$worktree_config_after" ] ||
206
+ [ "$packed_refs_before" != "$packed_refs_after" ] ||
207
+ [ "$ref_before" != "$ref_after" ] ||
208
+ [ "$ref_oid_before" != "$ref_oid_after" ]; then
209
+ fail_pre_push "Git metadata changed during pre-push tests."
210
+ fi
211
+
212
+ if [ "$test_status" -ne 0 ]; then
213
+ fail_pre_push "fix the issues above." "$test_status"
214
+ fi
@@ -34,21 +34,26 @@ cp -r lib /path/to/new-project/
34
34
 
35
35
  # En el nuevo proyecto
36
36
  cd /path/to/new-project
37
- ./.ci-local/install.sh # Linux/Mac/WSL
37
+ ./.ci-local/install.sh # Linux/WSL
38
38
  # o
39
39
  .\.ci-local\install.ps1 # Windows (PowerShell 7+)
40
40
  ```
41
41
 
42
- > **Importante:** CI-Local depende de `lib/common.sh` (Linux/Mac) y/o `lib/common.psm1` (Windows). Copiá ambos junto con el directorio `ci-local/`.
42
+ > **Importante:** CI-Local depende de `lib/common.sh` (Linux/WSL) y/o `lib/common.psm1` (Windows). Copiá ambos junto con el directorio `ci-local/`. macOS está deprecated y se rechaza para instalaciones o arranques nuevos.
43
43
 
44
44
  El installer (ambas variantes) falla con mensaje claro si `javi-forge` no está en el PATH.
45
45
 
46
+
47
+ ## macOS deprecation
48
+
49
+ macOS is deprecated and unsupported for new CI-Local install/startup. Pin a supported release or migrate your workflow. Existing installed guards are not removed in 1.x; Darwin code removal is planned separately for 2.0.
50
+
46
51
  ### Soporte cross-platform
47
52
 
48
53
  | Plataforma | Installer | Runner | Hooks | Requisitos |
49
54
  |---|---|---|---|---|
50
55
  | Linux | `install.sh` | `ci-local.sh` | bash | bash, perl, docker (opcional) |
51
- | macOS | `install.sh` | `ci-local.sh` | bash | bash, perl, docker (opcional) |
56
+ | macOS | Unsupported: pin a supported release or migrate | Refused before startup | Existing installed guards retained | No new install or startup |
52
57
  | WSL | `install.sh` | `ci-local.sh` | bash | bash, perl, docker (opcional) |
53
58
  | Windows nativo | `install.ps1` | `ci-local.ps1` | bash | PowerShell 7+, Git for Windows (MSYS2 bash), docker (opcional) |
54
59
 
@@ -117,7 +122,7 @@ Si tu modelo de amenaza requiere defensa contra adversarios motivados, usá
117
122
  .\.ci-local\ci-local.ps1 shell # Shell en entorno CI
118
123
  .\.ci-local\ci-local.ps1 detect # Ver stack detectado
119
124
 
120
- # Linux/Mac
125
+ # Linux/WSL
121
126
  ./.ci-local/ci-local.sh quick
122
127
  ./.ci-local/ci-local.sh full
123
128
  ./.ci-local/ci-local.sh shell
@@ -164,9 +169,9 @@ Developer workflow:
164
169
  ```
165
170
  .ci-local/
166
171
  ├── ci-local.ps1 # Script principal (Windows)
167
- ├── ci-local.sh # Script principal (Linux/Mac)
172
+ ├── ci-local.sh # Script principal (Linux/WSL)
168
173
  ├── install.ps1 # Instalador Windows
169
- ├── install.sh # Instalador Linux/Mac
174
+ ├── install.sh # Instalador Linux/WSL
170
175
  ├── semgrep.yml # Reglas de seguridad
171
176
  ├── README.md # Esta guía
172
177
  ├── hooks/
@@ -21,6 +21,30 @@ param(
21
21
  [string]$Mode = 'full'
22
22
  )
23
23
 
24
+ function Invoke-CiLocalMain {
25
+ param(
26
+ [Parameter(Mandatory)][string]$Platform,
27
+ [ref]$ExitCode
28
+ )
29
+
30
+ if ($Platform -eq 'Darwin') {
31
+ Write-Host 'macOS is deprecated and unsupported for new CI-Local install/startup. Pin a supported release or migrate. Existing installed guards are not removed; Darwin code removal is planned separately for 2.0.'
32
+ if ($PSBoundParameters.ContainsKey('ExitCode')) {
33
+ $ExitCode.Value = 1
34
+ return
35
+ }
36
+ return 1
37
+ }
38
+
39
+ Invoke-CiLocalStartupBody -Platform $Platform
40
+ if ($PSBoundParameters.ContainsKey('ExitCode')) {
41
+ $ExitCode.Value = 0
42
+ }
43
+ }
44
+
45
+ function Invoke-CiLocalStartupBody {
46
+ param([string]$Platform)
47
+
24
48
  # See install.ps1 for the rationale behind 7.2 minimum (ResolveLinkTarget).
25
49
  if ($PSVersionTable.PSEdition -ne 'Core' -or $PSVersionTable.PSVersion -lt [Version]'7.2') {
26
50
  Write-Host 'ERROR: ci-local.ps1 requires PowerShell 7.2+ (pwsh).' -ForegroundColor Red
@@ -441,3 +465,13 @@ Write-Host ''
441
465
  Write-Host 'CI Local completed successfully!' -ForegroundColor Green
442
466
  Write-Host ' Safe to push - CI should pass.' -ForegroundColor Green
443
467
  Write-Host ''
468
+ }
469
+
470
+ if ($MyInvocation.InvocationName -ne '.') {
471
+ $platform = if ($IsMacOS) { 'Darwin' } else { 'Windows' }
472
+ $exitCode = 0
473
+ & ${function:Invoke-CiLocalMain} -Platform $platform -ExitCode ([ref]$exitCode)
474
+ if ($exitCode -ne 0) {
475
+ exit $exitCode
476
+ }
477
+ }
@@ -11,7 +11,20 @@
11
11
  # ./ci-local.sh detect # Mostrar stack detectado
12
12
  # =============================================================================
13
13
 
14
- set -e
14
+ function ci_local_main() {
15
+ local platform="${1:?platform required}"
16
+ shift
17
+
18
+ if [ "$platform" = "Darwin" ]; then
19
+ printf '%s\n' 'macOS is deprecated and unsupported for new CI-Local install/startup. Pin a supported release or migrate. Existing installed guards are not removed; Darwin code removal is planned separately for 2.0.'
20
+ return 1
21
+ fi
22
+
23
+ ci_local_startup_body "$@"
24
+ }
25
+
26
+ function ci_local_startup_body() {
27
+ set -e
15
28
 
16
29
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
17
30
  PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
@@ -390,3 +403,8 @@ esac
390
403
 
391
404
  echo -e "\n${GREEN}CI Local completed successfully!${NC}"
392
405
  echo -e "${GREEN} Safe to push - CI should pass.${NC}\n"
406
+ }
407
+
408
+ if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
409
+ ci_local_main "$(/usr/bin/uname -s)" "$@"
410
+ fi
@@ -14,6 +14,20 @@
14
14
  [CmdletBinding()]
15
15
  param()
16
16
 
17
+ function Invoke-CiLocalMain {
18
+ param([Parameter(Mandatory)][string]$Platform)
19
+
20
+ if ($Platform -eq 'Darwin') {
21
+ Write-Host 'macOS is deprecated and unsupported for new CI-Local install/startup. Pin a supported release or migrate. Existing installed guards are not removed; Darwin code removal is planned separately for 2.0.'
22
+ return 1
23
+ }
24
+
25
+ Invoke-CiLocalStartupBody -Platform $Platform
26
+ }
27
+
28
+ function Invoke-CiLocalStartupBody {
29
+ param([string]$Platform)
30
+
17
31
  # Defense-in-depth: #Requires is parsed by some hosts AFTER the script body.
18
32
  # 7.2 is the minimum because FileSystemInfo.ResolveLinkTarget($true) is .NET 6+,
19
33
  # which shipped with pwsh 7.2. Earlier pwsh 7.x runs on .NET 5 and the method
@@ -365,3 +379,12 @@ try {
365
379
  } finally {
366
380
  Pop-Location
367
381
  }
382
+ }
383
+
384
+ if ($MyInvocation.InvocationName -ne '.') {
385
+ $platform = if ($IsMacOS) { 'Darwin' } else { 'Windows' }
386
+ $exitCode = & ${function:Invoke-CiLocalMain} -Platform $platform
387
+ if ($exitCode -is [int] -and $exitCode -ne 0) {
388
+ exit $exitCode
389
+ }
390
+ }
@@ -3,7 +3,19 @@
3
3
  # CI-LOCAL: Installation Script
4
4
  # =============================================================================
5
5
 
6
- set -e
6
+ function ci_local_main() {
7
+ local platform="${1:?platform required}"
8
+
9
+ if [ "$platform" = "Darwin" ]; then
10
+ printf '%s\n' 'macOS is deprecated and unsupported for new CI-Local install/startup. Pin a supported release or migrate. Existing installed guards are not removed; Darwin code removal is planned separately for 2.0.'
11
+ return 1
12
+ fi
13
+
14
+ ci_local_startup_body "$platform"
15
+ }
16
+
17
+ function ci_local_startup_body() {
18
+ set -e
7
19
 
8
20
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
9
21
  PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
@@ -193,3 +205,8 @@ echo -e " - pre-commit: AI check + lint + security"
193
205
  echo -e " - commit-msg: Block AI attribution"
194
206
  echo -e " - pre-push: CI simulation in Docker"
195
207
  echo -e ""
208
+ }
209
+
210
+ if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
211
+ ci_local_main "$(/usr/bin/uname -s)"
212
+ fi
@@ -9,6 +9,7 @@
9
9
  */
10
10
  import { render } from "ink";
11
11
  import React from "react";
12
+ import { resolvePlatformSupport } from "../../lib/platform-support.js";
12
13
  import AnalyzeUI from "../../ui/AnalyzeUI.js";
13
14
  import App from "../../ui/App.js";
14
15
  import { CIProvider as CIContextProvider } from "../../ui/CIContext.js";
@@ -49,6 +50,12 @@ export function handlePlugin(cli, ctx) {
49
50
  React.createElement(Plugin, { action: action, target: target, dryRun: cli.flags.dryRun, codex: cli.flags.codex, force: cli.flags.force })), { stdin: ctx.inkStdin });
50
51
  }
51
52
  export function handleInitDefault(cli, ctx) {
53
+ const platformSupport = resolvePlatformSupport(process.platform);
54
+ if (platformSupport) {
55
+ console.error(`${platformSupport.refusalCode}: ${platformSupport.guidance}`);
56
+ process.exitCode = 1;
57
+ return;
58
+ }
52
59
  const presetStack = VALID_STACKS.includes(cli.flags.stack)
53
60
  ? cli.flags.stack
54
61
  : undefined;
@@ -59,6 +59,8 @@ function renderMutation(verb, result, log, logError) {
59
59
  }
60
60
  function renderDoctor(report, log) {
61
61
  log(`doctor claude: ${report.healthy ? "healthy" : "unhealthy"}`);
62
+ if (report.platformSupport)
63
+ log(` platform-support: ${report.platformSupport.state} — ${report.platformSupport.guidance}`);
62
64
  log(` settings: ${report.settings.state} — ${report.settings.detail}`);
63
65
  log(` asset: ${report.asset.state} — ${report.asset.detail}`);
64
66
  log(` node: ${report.node.version ?? "unavailable"} (min-satisfied: ${report.node.satisfiesMinimum})`);
@@ -44,6 +44,8 @@ function renderMutation(verb, result, log, logError) {
44
44
  }
45
45
  function renderDoctor(report, log) {
46
46
  log(`doctor codex: ${report.healthy ? "healthy" : "unhealthy"}`);
47
+ if (report.platformSupport)
48
+ log(` platform-support: ${report.platformSupport.state} — ${report.platformSupport.guidance}`);
47
49
  log(` hooks.json: ${report.hooksJson.state}`);
48
50
  log(` config: [features] hooks=${report.config.featuresHooks} (readable: ${report.config.readable})`);
49
51
  log(` trust: ${report.trust.state}`);
@@ -1,3 +1,4 @@
1
+ import { type PlatformSupport } from "../lib/platform-support.js";
1
2
  import type { InitOptions } from "../types/index.js";
2
3
  import type { StepCallback } from "./init/types.js";
3
4
  /**
@@ -6,5 +7,12 @@ import type { StepCallback } from "./init/types.js";
6
7
  *
7
8
  * Pure dispatch: every step lives in src/commands/init/steps/.
8
9
  */
9
- export declare function initProject(options: InitOptions, onStep: StepCallback): Promise<void>;
10
+ export type InitProjectResult = {
11
+ ok: true;
12
+ } | {
13
+ ok: false;
14
+ refusalCode: string;
15
+ platformSupport: PlatformSupport;
16
+ };
17
+ export declare function initProject(options: InitOptions, onStep: StepCallback): Promise<InitProjectResult>;
10
18
  //# sourceMappingURL=init.d.ts.map
@@ -1,4 +1,5 @@
1
1
  import { ensureDirExists } from "../lib/common.js";
2
+ import { resolvePlatformSupport, } from "../lib/platform-support.js";
2
3
  import { stepAgentSkills } from "./init/steps/agent-skills.js";
3
4
  import { stepAISync } from "./init/steps/ai-sync.js";
4
5
  import { stepCITemplate, stepDependabot } from "./init/steps/ci.js";
@@ -15,13 +16,15 @@ import { stepMemory } from "./init/steps/memory.js";
15
16
  import { stepMock } from "./init/steps/mock.js";
16
17
  import { stepSDD } from "./init/steps/sdd.js";
17
18
  import { stepSecurityHooks } from "./init/steps/security.js";
18
- /**
19
- * Main init orchestrator: bootstraps a project with CI, git hooks,
20
- * memory module, AI config sync, SDD, ghagga, and friends.
21
- *
22
- * Pure dispatch: every step lives in src/commands/init/steps/.
23
- */
24
19
  export async function initProject(options, onStep) {
20
+ const platformSupport = resolvePlatformSupport(process.platform);
21
+ if (platformSupport) {
22
+ return {
23
+ ok: false,
24
+ refusalCode: platformSupport.refusalCode,
25
+ platformSupport,
26
+ };
27
+ }
25
28
  const { projectDir, dryRun } = options;
26
29
  // Ensure project directory exists before any steps
27
30
  if (!dryRun && projectDir) {
@@ -47,5 +50,6 @@ export async function initProject(options, onStep) {
47
50
  await stepLocalAi(ctx);
48
51
  await stepAgentSkills(ctx);
49
52
  await stepManifest(ctx);
53
+ return { ok: true };
50
54
  }
51
55
  //# sourceMappingURL=init.js.map
@@ -8,6 +8,7 @@
8
8
  * Slice-3 seams — Slice 3 GROWS this file, it does not relocate this code.
9
9
  */
10
10
  import { type ClaudeHookComponentState, type SettingsClassification, type SettingsIdentityManifest } from "./claude-hook-settings.js";
11
+ import { type PlatformSupport } from "./platform-support.js";
11
12
  import { type AclCapability, type SpawnFn } from "./secure-fs-posix.js";
12
13
  import { type PlatformSecureFs } from "./secure-fs-transaction.js";
13
14
  declare const COVERAGE: readonly ["Bash", "PowerShell", "Read", "Write", "Edit"];
@@ -29,6 +30,7 @@ export interface ClaudeHookAssetClassification {
29
30
  }
30
31
  export interface ClaudeHookDoctorReport {
31
32
  healthy: boolean;
33
+ platformSupport?: PlatformSupport;
32
34
  settings: {
33
35
  state: ClaudeHookComponentState;
34
36
  version?: number;
@@ -207,8 +209,9 @@ export declare function doctorClaudePreToolUse(projectDir: string, options?: {
207
209
  execution?: ExecutionProbeEnv;
208
210
  /** Injectable read-only ACL capability probe (defaults to the real one). */
209
211
  aclProbe?: () => Promise<AclCapability>;
212
+ platform?: NodeJS.Platform;
210
213
  }): Promise<ClaudeHookDoctorReport>;
211
- export interface ClaudeHookMutationResult {
214
+ interface ClaudeHookMutationResultWithReport {
212
215
  ok: boolean;
213
216
  changed: string[];
214
217
  backups: string[];
@@ -220,7 +223,19 @@ export interface ClaudeHookMutationResult {
220
223
  * which is strictly worse than an exec-form guard that may not resolve.
221
224
  */
222
225
  warnings: string[];
226
+ lifecycleRefusal?: never;
227
+ }
228
+ interface ClaudeHookLifecycleRefusal {
229
+ ok: false;
230
+ changed: string[];
231
+ backups: string[];
232
+ errors: string[];
233
+ warnings: string[];
234
+ /** The lifecycle gate refused before any doctor or installed-state probe. */
235
+ lifecycleRefusal: PlatformSupport;
236
+ report?: never;
223
237
  }
238
+ export type ClaudeHookMutationResult = ClaudeHookMutationResultWithReport | ClaudeHookLifecycleRefusal;
224
239
  /** Injectable deps so tests drive `_run` with a fake `PlatformSecureFs`. */
225
240
  export interface ClaudeHookRunDeps {
226
241
  secureFs?: PlatformSecureFs | null;
@@ -236,6 +251,7 @@ export interface ClaudeHookRunDeps {
236
251
  aclProbe?: () => Promise<AclCapability>;
237
252
  /** Injectable node-on-PATH heuristic, shared by the warning and the report. */
238
253
  nodeProbe?: () => Promise<NodeOnPathProbe>;
254
+ doctor?: typeof doctorClaudePreToolUse;
239
255
  }
240
256
  /** Internal deps-taking entry; tests drive it with a fake `PlatformSecureFs`. */
241
257
  export declare function _run(projectDir: string, mode: "install" | "repair", options: {
@@ -15,6 +15,7 @@ import path from "node:path";
15
15
  import { CLAUDE_HOOK_ASSETS_DIR } from "../constants.js";
16
16
  import { ASSET_MANAGED_MARKER, ASSET_NAME, } from "./__fixtures__/claude-hook-ownership.js";
17
17
  import { buildManagedContainer, classifySettingsEntry, isPlainObject, LEGACY_FILE_SHA256, MANAGED_AGENT_ARG, MANAGED_ASSET_ARG, MANAGED_MATCHER, MANAGED_STATUS_PREFIX, planForceReplace, planLegacyCohortExcision, planManagedClaudeHookMerge, scanExecutionFlags, } from "./claude-hook-settings.js";
18
+ import { resolvePlatformSupport, } from "./platform-support.js";
18
19
  import { safeReadFile } from "./safe-read.js";
19
20
  import { ACL_DETAIL, probeAclCapability, selectSecureFs, } from "./secure-fs-posix.js";
20
21
  import { runTransaction, } from "./secure-fs-transaction.js";
@@ -537,7 +538,9 @@ export async function doctorClaudePreToolUse(projectDir, options) {
537
538
  execution.blockers.some((blocker) => blocker.startsWith("guard:"))) {
538
539
  remediation.add(aclRemediation);
539
540
  }
541
+ const platformSupport = resolvePlatformSupport(options?.platform ?? process.platform);
540
542
  return {
543
+ ...(platformSupport ? { platformSupport } : {}),
541
544
  healthy,
542
545
  settings: {
543
546
  state: settings.state,
@@ -712,8 +715,20 @@ function serializeSettings(container) {
712
715
  }
713
716
  /** Internal deps-taking entry; tests drive it with a fake `PlatformSecureFs`. */
714
717
  export async function _run(projectDir, mode, options, deps) {
715
- const manifest = deps.manifest ?? (await readManifest());
716
718
  const platform = deps.platform ?? process.platform;
719
+ const platformSupport = resolvePlatformSupport(platform);
720
+ if (platformSupport) {
721
+ return {
722
+ ok: false,
723
+ changed: [],
724
+ backups: [],
725
+ errors: [platformSupport.refusalCode],
726
+ warnings: [platformSupport.guidance],
727
+ lifecycleRefusal: platformSupport,
728
+ };
729
+ }
730
+ const doctorFn = deps.doctor ?? doctorClaudePreToolUse;
731
+ const manifest = deps.manifest ?? (await readManifest());
717
732
  const secureFs = deps.secureFs !== undefined ? deps.secureFs : selectSecureFs(platform);
718
733
  const clock = deps.clock ?? (() => new Date());
719
734
  const nonce = deps.nonce ?? (() => randomBytes(4).toString("hex"));
@@ -725,7 +740,7 @@ export async function _run(projectDir, mode, options, deps) {
725
740
  // report, so the run never spawns `node --version` twice and the warning, the
726
741
  // report row and the verdict can never disagree about the same PATH.
727
742
  const nodeOnPath = await (deps.nodeProbe ?? probeNodeOnPath)();
728
- const doctor = () => doctorClaudePreToolUse(projectDir, {
743
+ const doctor = () => doctorFn(projectDir, {
729
744
  manifest,
730
745
  aclProbe: deps.aclProbe,
731
746
  execution: { nodeProbe: async () => nodeOnPath },
@@ -30,6 +30,7 @@
30
30
  */
31
31
  import { type AssetManifestEntry, type ExecutionReport, type NodeOnPathProbe } from "./claude-hook-manager.js";
32
32
  import { type ClaudeHookComponentState } from "./claude-hook-settings.js";
33
+ import { type PlatformSupport } from "./platform-support.js";
33
34
  import { type SpawnFn } from "./secure-fs-posix.js";
34
35
  import { type PlatformSecureFs } from "./secure-fs-transaction.js";
35
36
  export type { NodeOnPathProbe };
@@ -101,6 +102,7 @@ export declare function classifyCodexHooksJson(value: unknown, expectedCommand:
101
102
  export type CodexTrustState = "trusted" | "untrusted";
102
103
  export interface CodexHookDoctorReport {
103
104
  healthy: boolean;
105
+ platformSupport?: PlatformSupport;
104
106
  hooksJson: {
105
107
  state: ClaudeHookComponentState;
106
108
  detail?: string;
@@ -127,20 +129,34 @@ export interface CodexHookDoctorReport {
127
129
  remediation: string[];
128
130
  }
129
131
  export interface CodexDoctorOptions {
132
+ platform?: NodeJS.Platform;
130
133
  manifest?: Manifest;
131
134
  assetPath?: string;
132
135
  nodeVersion?: string;
133
136
  nodeProbe?: () => Promise<NodeOnPathProbe>;
134
137
  }
135
138
  export declare function doctorCodexPreToolUse(homeDir?: string, options?: CodexDoctorOptions): Promise<CodexHookDoctorReport>;
136
- export interface CodexHookMutationResult {
139
+ interface CodexHookMutationResultWithReport {
137
140
  ok: boolean;
138
141
  changed: string[];
139
142
  backups: string[];
143
+ /** Real post-mutation doctor snapshot. */
140
144
  report: CodexHookDoctorReport;
141
145
  errors: string[];
142
146
  warnings: string[];
147
+ lifecycleRefusal?: never;
148
+ }
149
+ interface CodexHookLifecycleRefusal {
150
+ ok: false;
151
+ changed: string[];
152
+ backups: string[];
153
+ errors: string[];
154
+ warnings: string[];
155
+ /** The lifecycle gate refused before any doctor or installed-state probe. */
156
+ lifecycleRefusal: PlatformSupport;
157
+ report?: never;
143
158
  }
159
+ export type CodexHookMutationResult = CodexHookMutationResultWithReport | CodexHookLifecycleRefusal;
144
160
  export interface CodexHookRunDeps {
145
161
  secureFs?: PlatformSecureFs | null;
146
162
  clock?: () => Date;
@@ -150,6 +166,7 @@ export interface CodexHookRunDeps {
150
166
  assetPath?: string;
151
167
  nodeProbe?: () => Promise<NodeOnPathProbe>;
152
168
  nodeSpawn?: SpawnFn;
169
+ doctor?: typeof doctorCodexPreToolUse;
153
170
  }
154
171
  export declare function _runCodex(homeDir: string, _mode: "install" | "repair", _options: {
155
172
  force?: boolean;
@@ -34,6 +34,7 @@ import { CLAUDE_HOOK_ASSETS_DIR } from "../constants.js";
34
34
  import { ASSET_NAME } from "./__fixtures__/claude-hook-ownership.js";
35
35
  import { classifyAssetState, detectNode, probeNodeOnPath, } from "./claude-hook-manager.js";
36
36
  import { isPlainObject, validateSettingsShape, } from "./claude-hook-settings.js";
37
+ import { resolvePlatformSupport, } from "./platform-support.js";
37
38
  import { safeReadFile } from "./safe-read.js";
38
39
  import { selectSecureFs } from "./secure-fs-posix.js";
39
40
  import { runTransaction, } from "./secure-fs-transaction.js";
@@ -372,7 +373,9 @@ export async function doctorCodexPreToolUse(homeDir = os.homedir(), options = {}
372
373
  }
373
374
  if (!node.satisfiesMinimum)
374
375
  remediation.push("install Node 22 or newer");
376
+ const platformSupport = resolvePlatformSupport(options.platform ?? process.platform);
375
377
  return {
378
+ ...(platformSupport ? { platformSupport } : {}),
376
379
  healthy: status === "runnable",
377
380
  hooksJson,
378
381
  config: { featuresHooks, readable: configReadable },
@@ -396,8 +399,20 @@ function serialize(container) {
396
399
  return Buffer.from(`${JSON.stringify(container, null, 2)}\n`, "utf8");
397
400
  }
398
401
  export async function _runCodex(homeDir, _mode, _options, deps) {
399
- const manifest = deps.manifest ?? (await readManifest());
400
402
  const platform = deps.platform ?? process.platform;
403
+ const platformSupport = resolvePlatformSupport(platform);
404
+ if (platformSupport) {
405
+ return {
406
+ ok: false,
407
+ changed: [],
408
+ backups: [],
409
+ errors: [platformSupport.refusalCode],
410
+ warnings: [platformSupport.guidance],
411
+ lifecycleRefusal: platformSupport,
412
+ };
413
+ }
414
+ const doctorFn = deps.doctor ?? doctorCodexPreToolUse;
415
+ const manifest = deps.manifest ?? (await readManifest());
401
416
  const secureFs = deps.secureFs !== undefined ? deps.secureFs : selectSecureFs(platform);
402
417
  const clock = deps.clock ?? (() => new Date());
403
418
  const nonce = deps.nonce ??
@@ -406,7 +421,7 @@ export async function _runCodex(homeDir, _mode, _options, deps) {
406
421
  const { codexDir, hooksFile, configFile } = codexConfigPaths(homeDir);
407
422
  const expectedCommand = expectedCodexCommand(assetPath);
408
423
  const nodeOnPath = await (deps.nodeProbe ?? probeNodeOnPath)();
409
- const doctor = () => doctorCodexPreToolUse(homeDir, {
424
+ const doctor = () => doctorFn(homeDir, {
410
425
  manifest,
411
426
  assetPath,
412
427
  nodeProbe: async () => nodeOnPath,
@@ -0,0 +1,19 @@
1
+ export declare const PLATFORM_SUPPORT_STATE: {
2
+ readonly MACOS_DEPRECATED: "macos-deprecated";
3
+ };
4
+ export declare const LIFECYCLE_SUPPORT: {
5
+ readonly UNSUPPORTED: "unsupported";
6
+ };
7
+ export declare const PLATFORM_REFUSAL: {
8
+ readonly MACOS_LIFECYCLE_UNSUPPORTED: "macos-lifecycle-unsupported";
9
+ };
10
+ export interface PlatformSupport {
11
+ platform: "darwin";
12
+ state: typeof PLATFORM_SUPPORT_STATE.MACOS_DEPRECATED;
13
+ lifecycle: typeof LIFECYCLE_SUPPORT.UNSUPPORTED;
14
+ refusalCode: typeof PLATFORM_REFUSAL.MACOS_LIFECYCLE_UNSUPPORTED;
15
+ guidance: string;
16
+ }
17
+ export declare const MACOS_DEPRECATION_GUIDANCE = "macOS is deprecated and unsupported for install, repair, and init; pin a supported release or migrate. Existing installed guards are not removed; Darwin removal is planned for 2.0.";
18
+ export declare function resolvePlatformSupport(platform: string): PlatformSupport | undefined;
19
+ //# sourceMappingURL=platform-support.d.ts.map
@@ -0,0 +1,22 @@
1
+ export const PLATFORM_SUPPORT_STATE = {
2
+ MACOS_DEPRECATED: "macos-deprecated",
3
+ };
4
+ export const LIFECYCLE_SUPPORT = {
5
+ UNSUPPORTED: "unsupported",
6
+ };
7
+ export const PLATFORM_REFUSAL = {
8
+ MACOS_LIFECYCLE_UNSUPPORTED: "macos-lifecycle-unsupported",
9
+ };
10
+ export const MACOS_DEPRECATION_GUIDANCE = "macOS is deprecated and unsupported for install, repair, and init; pin a supported release or migrate. Existing installed guards are not removed; Darwin removal is planned for 2.0.";
11
+ export function resolvePlatformSupport(platform) {
12
+ if (platform !== "darwin")
13
+ return undefined;
14
+ return {
15
+ platform: "darwin",
16
+ state: PLATFORM_SUPPORT_STATE.MACOS_DEPRECATED,
17
+ lifecycle: LIFECYCLE_SUPPORT.UNSUPPORTED,
18
+ refusalCode: PLATFORM_REFUSAL.MACOS_LIFECYCLE_UNSUPPORTED,
19
+ guidance: MACOS_DEPRECATION_GUIDANCE,
20
+ };
21
+ }
22
+ //# sourceMappingURL=platform-support.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "javi-forge",
3
- "version": "1.36.0",
3
+ "version": "1.37.1",
4
4
  "description": "Project scaffolding and AI-ready CI bootstrap",
5
5
  "type": "module",
6
6
  "bin": {