javi-forge 1.36.0 → 1.37.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 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)
@@ -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.0",
4
4
  "description": "Project scaffolding and AI-ready CI bootstrap",
5
5
  "type": "module",
6
6
  "bin": {