javi-forge 1.37.0 → 1.38.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 +5 -5
- package/assets/claude-hooks/javi-forge-skillguard-pre-tool-use.mjs +13 -11
- package/assets/claude-hooks/manifest.json +35 -1
- package/assets/hooks/manifest.json +6 -2
- package/assets/hooks/pre-push +206 -8
- package/ci-local/README.md +3 -6
- package/ci-local/ci-local.ps1 +36 -10
- package/ci-local/ci-local.sh +26 -9
- package/ci-local/install.ps1 +36 -14
- package/ci-local/install.sh +27 -10
- package/ci-local/lib/common.sh +3 -7
- package/ci-local/uninstall.ps1 +50 -0
- package/ci-local/uninstall.sh +38 -3
- package/dist/cli/bootstrap.d.ts +12 -0
- package/dist/cli/bootstrap.js +12 -0
- package/dist/cli/dispatch/simple-renderers.d.ts +8 -1
- package/dist/cli/dispatch/simple-renderers.js +11 -2
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +85 -0
- package/dist/commands/claude-hooks.d.ts +1 -0
- package/dist/commands/claude-hooks.js +12 -3
- package/dist/commands/codex-hooks.d.ts +1 -0
- package/dist/commands/codex-hooks.js +12 -3
- package/dist/commands/doctor.d.ts +16 -1
- package/dist/commands/doctor.js +54 -35
- package/dist/commands/init.d.ts +4 -1
- package/dist/commands/init.js +2 -2
- package/dist/index.js +4 -81
- package/dist/lib/claude-hook-manager.d.ts +11 -6
- package/dist/lib/claude-hook-manager.js +28 -21
- package/dist/lib/codex-hook-manager.d.ts +10 -4
- package/dist/lib/codex-hook-manager.js +44 -13
- package/dist/lib/platform-support.d.ts +18 -14
- package/dist/lib/platform-support.js +19 -17
- package/dist/lib/secure-fs-posix.d.ts +8 -13
- package/dist/lib/secure-fs-posix.js +11 -52
- package/dist/lib/secure-fs-transaction.d.ts +2 -3
- package/dist/types/index.d.ts +7 -2
- package/dist/ui/Doctor.d.ts +2 -0
- package/dist/ui/Doctor.js +13 -0
- package/lib/common.sh +3 -7
- package/package.json +7 -2
|
@@ -1,22 +1,24 @@
|
|
|
1
|
-
export const
|
|
2
|
-
|
|
1
|
+
export const HOST_SUPPORT_STATE = {
|
|
2
|
+
SUPPORTED: "supported",
|
|
3
|
+
UNSUPPORTED: "unsupported-platform",
|
|
3
4
|
};
|
|
4
|
-
export const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if (platform !== "darwin")
|
|
13
|
-
return undefined;
|
|
5
|
+
export const HOST_SUPPORT_GUIDANCE = "javi-forge supports Linux and Windows only.";
|
|
6
|
+
/**
|
|
7
|
+
* Classifies every host value without aliases or fallthrough.
|
|
8
|
+
*/
|
|
9
|
+
export function classifyHostPlatform(platform) {
|
|
10
|
+
if (platform === "linux" || platform === "win32") {
|
|
11
|
+
return { state: HOST_SUPPORT_STATE.SUPPORTED, platform };
|
|
12
|
+
}
|
|
14
13
|
return {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
refusalCode: PLATFORM_REFUSAL.MACOS_LIFECYCLE_UNSUPPORTED,
|
|
19
|
-
guidance: MACOS_DEPRECATION_GUIDANCE,
|
|
14
|
+
state: HOST_SUPPORT_STATE.UNSUPPORTED,
|
|
15
|
+
refusalCode: HOST_SUPPORT_STATE.UNSUPPORTED,
|
|
16
|
+
guidance: HOST_SUPPORT_GUIDANCE,
|
|
20
17
|
};
|
|
21
18
|
}
|
|
19
|
+
/** Returns an unsupported result for legacy callers, otherwise undefined. */
|
|
20
|
+
export function resolvePlatformSupport(platform) {
|
|
21
|
+
const result = classifyHostPlatform(platform);
|
|
22
|
+
return result.state === HOST_SUPPORT_STATE.UNSUPPORTED ? result : undefined;
|
|
23
|
+
}
|
|
22
24
|
//# sourceMappingURL=platform-support.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* POSIX `PlatformSecureFs` adapters for the SkillGuard transactional installer
|
|
3
|
-
* (Slice 3a). This is the ONLY place a shell tool runs (`getfacl` on Linux
|
|
4
|
-
*
|
|
3
|
+
* (Slice 3a). This is the ONLY place a shell tool runs (`getfacl` on Linux)
|
|
4
|
+
* and the ONLY place `os`/`fs` ownership bits are interpreted.
|
|
5
5
|
* interpreted. Everything fails closed: a missing/erroring/timed-out ACL tool,
|
|
6
6
|
* an extended/named/mask/default/inherited ACL entry, a foreign-owned or
|
|
7
7
|
* group/other-writable directory, or any inconclusive result refuses rather than
|
|
@@ -38,7 +38,7 @@ export interface PosixAclAdapter {
|
|
|
38
38
|
* mask) `w`, OR any named-group with effective `w`. Everything else (base
|
|
39
39
|
* entries, a lone `mask::`, effective-non-write named entries, x-only, trusted
|
|
40
40
|
* named users, `default:*`) proceeds. Same fail-closed spawn edges as
|
|
41
|
-
* `proveClean`.
|
|
41
|
+
* `proveClean`.
|
|
42
42
|
*/
|
|
43
43
|
proveNoEndangeringAcl(target: string): Promise<SecureResult<void>>;
|
|
44
44
|
}
|
|
@@ -51,13 +51,8 @@ export declare const ACL_DETAIL: {
|
|
|
51
51
|
readonly getfaclAbsent: "getfacl absent";
|
|
52
52
|
readonly getfaclTimeout: "getfacl timeout";
|
|
53
53
|
readonly extendedAclEntry: "extended ACL entry";
|
|
54
|
-
readonly macosLsAbsent: "/bin/ls absent";
|
|
55
|
-
readonly macosLsTimeout: "ls timeout";
|
|
56
|
-
readonly macosAclFlag: "ACL present (+ flag)";
|
|
57
|
-
readonly macosAceListed: "ACE listed";
|
|
58
54
|
};
|
|
59
55
|
export declare function createLinuxAclAdapter(spawn?: SpawnFn, stat?: StatFn): PosixAclAdapter;
|
|
60
|
-
export declare function createMacosAclAdapter(spawn?: SpawnFn): PosixAclAdapter;
|
|
61
56
|
/**
|
|
62
57
|
* Whether the host's ACL adapter is RESOLVABLE — an install-time capability
|
|
63
58
|
* question, deliberately separate from the per-target prover above. It never
|
|
@@ -65,10 +60,10 @@ export declare function createMacosAclAdapter(spawn?: SpawnFn): PosixAclAdapter;
|
|
|
65
60
|
*/
|
|
66
61
|
export type AclCapability = {
|
|
67
62
|
status: "available";
|
|
68
|
-
tool: "getfacl"
|
|
63
|
+
tool: "getfacl";
|
|
69
64
|
} | {
|
|
70
65
|
status: "absent";
|
|
71
|
-
tool: "getfacl"
|
|
66
|
+
tool: "getfacl";
|
|
72
67
|
} | {
|
|
73
68
|
status: "unknown";
|
|
74
69
|
tool: string;
|
|
@@ -89,9 +84,9 @@ export type AclCapability = {
|
|
|
89
84
|
export declare function probeAclCapability(spawn?: SpawnFn, platform?: NodeJS.Platform): Promise<AclCapability>;
|
|
90
85
|
export declare function createPosixSecureFs(acl: PosixAclAdapter): PlatformSecureFs;
|
|
91
86
|
/**
|
|
92
|
-
* Select the secure filesystem for
|
|
93
|
-
* adapter
|
|
94
|
-
*
|
|
87
|
+
* Select the secure filesystem for supported hosts. Linux uses the `getfacl`
|
|
88
|
+
* adapter and win32 uses the digest-bound PowerShell helper over a lazily-spawned
|
|
89
|
+
* session (Slice 3b). Every other platform returns
|
|
95
90
|
* `null` so the manager refuses with `windows-secure-object-unavailable` and
|
|
96
91
|
* mutates nothing.
|
|
97
92
|
*
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* POSIX `PlatformSecureFs` adapters for the SkillGuard transactional installer
|
|
3
|
-
* (Slice 3a). This is the ONLY place a shell tool runs (`getfacl` on Linux
|
|
4
|
-
*
|
|
3
|
+
* (Slice 3a). This is the ONLY place a shell tool runs (`getfacl` on Linux)
|
|
4
|
+
* and the ONLY place `os`/`fs` ownership bits are interpreted.
|
|
5
5
|
* interpreted. Everything fails closed: a missing/erroring/timed-out ACL tool,
|
|
6
6
|
* an extended/named/mask/default/inherited ACL entry, a foreign-owned or
|
|
7
7
|
* group/other-writable directory, or any inconclusive result refuses rather than
|
|
@@ -57,10 +57,6 @@ export const ACL_DETAIL = {
|
|
|
57
57
|
getfaclAbsent: "getfacl absent",
|
|
58
58
|
getfaclTimeout: "getfacl timeout",
|
|
59
59
|
extendedAclEntry: "extended ACL entry",
|
|
60
|
-
macosLsAbsent: "/bin/ls absent",
|
|
61
|
-
macosLsTimeout: "ls timeout",
|
|
62
|
-
macosAclFlag: "ACL present (+ flag)",
|
|
63
|
-
macosAceListed: "ACE listed",
|
|
64
60
|
};
|
|
65
61
|
// --- Linux getfacl adapter (Algorithm D) -------------------------------------
|
|
66
62
|
const LINUX_BASE_ENTRY = /^(user|group|other)::/;
|
|
@@ -208,39 +204,6 @@ export function createLinuxAclAdapter(spawn = defaultSpawn, stat = defaultStat)
|
|
|
208
204
|
},
|
|
209
205
|
};
|
|
210
206
|
}
|
|
211
|
-
// --- macOS /bin/ls -lde adapter (Algorithm E) --------------------------------
|
|
212
|
-
const MACOS_ACE_LINE = /^\s*\d+:\s/;
|
|
213
|
-
export function createMacosAclAdapter(spawn = defaultSpawn) {
|
|
214
|
-
const adapter = {
|
|
215
|
-
async proveClean(target) {
|
|
216
|
-
const res = await spawn("/bin/ls", ["-lde", "--", target]);
|
|
217
|
-
if (res.spawnError)
|
|
218
|
-
return refuse("unsupported-posix-acl", ACL_DETAIL.macosLsAbsent);
|
|
219
|
-
if (res.timedOut)
|
|
220
|
-
return refuse("unsupported-posix-acl", ACL_DETAIL.macosLsTimeout);
|
|
221
|
-
if (res.code !== 0) {
|
|
222
|
-
return refuse("unsupported-posix-acl", `ls exit ${res.code}`);
|
|
223
|
-
}
|
|
224
|
-
const lines = res.stdout.split("\n");
|
|
225
|
-
const modeLine = lines[0] ?? "";
|
|
226
|
-
if (modeLine[10] === "+") {
|
|
227
|
-
return refuse("unsupported-posix-acl", ACL_DETAIL.macosAclFlag);
|
|
228
|
-
}
|
|
229
|
-
if (lines.some((line) => MACOS_ACE_LINE.test(line))) {
|
|
230
|
-
return refuse("unsupported-posix-acl", ACL_DETAIL.macosAceListed);
|
|
231
|
-
}
|
|
232
|
-
return ok();
|
|
233
|
-
},
|
|
234
|
-
// DEFERRED (user: Linux only): darwin ancestors stay STRICT = status-quo
|
|
235
|
-
// over-refusal, not the reported Linux bug. `/bin/ls -lde` ACE text carries
|
|
236
|
-
// no numeric mask to compute effective rights from, so the path-endangering
|
|
237
|
-
// narrowing is a documented follow-up. Alias to the strict proof.
|
|
238
|
-
proveNoEndangeringAcl(target) {
|
|
239
|
-
return adapter.proveClean(target);
|
|
240
|
-
},
|
|
241
|
-
};
|
|
242
|
-
return adapter;
|
|
243
|
-
}
|
|
244
207
|
/**
|
|
245
208
|
* Probe the ACL adapter READ-ONLY: it resolves and runs a version/list argv and
|
|
246
209
|
* inspects nothing on disk. It creates, modifies and removes nothing, and its
|
|
@@ -254,29 +217,27 @@ export async function probeAclCapability(spawn = defaultSpawn, platform = proces
|
|
|
254
217
|
if (platform === "win32") {
|
|
255
218
|
return { status: "not-applicable", tool: "windows-secure-object" };
|
|
256
219
|
}
|
|
257
|
-
if (platform !== "linux"
|
|
220
|
+
if (platform !== "linux") {
|
|
258
221
|
return {
|
|
259
222
|
status: "unknown",
|
|
260
223
|
tool: platform,
|
|
261
224
|
detail: `no POSIX ACL adapter for platform ${platform}`,
|
|
262
225
|
};
|
|
263
226
|
}
|
|
264
|
-
const tool =
|
|
265
|
-
const args =
|
|
227
|
+
const tool = "getfacl";
|
|
228
|
+
const args = ["--version"];
|
|
266
229
|
const res = await spawn(tool, args);
|
|
267
230
|
if (res.spawnError)
|
|
268
231
|
return { status: "absent", tool };
|
|
269
232
|
if (res.timedOut) {
|
|
270
|
-
// The argv differs per platform (`getfacl --version` on linux, `/bin/ls -ld /`
|
|
271
|
-
// on darwin), so the detail names the probe, not a hardcoded flag.
|
|
272
233
|
return { status: "unknown", tool, detail: `${tool} probe timeout` };
|
|
273
234
|
}
|
|
274
235
|
if (res.code !== 0) {
|
|
275
236
|
return { status: "unknown", tool, detail: `${tool} exit ${res.code}` };
|
|
276
237
|
}
|
|
277
|
-
//
|
|
278
|
-
//
|
|
279
|
-
if (
|
|
238
|
+
// A zero exit whose banner does not name getfacl is a foreign binary on PATH,
|
|
239
|
+
// not proof of the adapter — report ignorance, not success.
|
|
240
|
+
if (!res.stdout.toLowerCase().includes("getfacl")) {
|
|
280
241
|
return {
|
|
281
242
|
status: "unknown",
|
|
282
243
|
tool,
|
|
@@ -504,9 +465,9 @@ export function createPosixSecureFs(acl) {
|
|
|
504
465
|
return secureFs;
|
|
505
466
|
}
|
|
506
467
|
/**
|
|
507
|
-
* Select the secure filesystem for
|
|
508
|
-
* adapter
|
|
509
|
-
*
|
|
468
|
+
* Select the secure filesystem for supported hosts. Linux uses the `getfacl`
|
|
469
|
+
* adapter and win32 uses the digest-bound PowerShell helper over a lazily-spawned
|
|
470
|
+
* session (Slice 3b). Every other platform returns
|
|
510
471
|
* `null` so the manager refuses with `windows-secure-object-unavailable` and
|
|
511
472
|
* mutates nothing.
|
|
512
473
|
*
|
|
@@ -521,8 +482,6 @@ export function createPosixSecureFs(acl) {
|
|
|
521
482
|
export function selectSecureFs(platform = process.platform) {
|
|
522
483
|
if (platform === "linux")
|
|
523
484
|
return createPosixSecureFs(createLinuxAclAdapter());
|
|
524
|
-
if (platform === "darwin")
|
|
525
|
-
return createPosixSecureFs(createMacosAclAdapter());
|
|
526
485
|
if (platform === "win32")
|
|
527
486
|
return createWindowsSecureFs(createPs1Session());
|
|
528
487
|
return null;
|
|
@@ -74,9 +74,8 @@ export interface PlatformSecureFs {
|
|
|
74
74
|
* ones (a lone mask, an effective-non-write or x-only or trusted named entry, a
|
|
75
75
|
* `default:*`). Called by the engine on ANCESTOR (non-managed) controlling dirs
|
|
76
76
|
* only; managed containers keep the strict proof. Selection is by the
|
|
77
|
-
* managed-containers role, NEVER by `process.platform`.
|
|
78
|
-
* the
|
|
79
|
-
* strict no-op alias (deferred).
|
|
77
|
+
* managed-containers role, NEVER by `process.platform`. Supported adapters
|
|
78
|
+
* implement the same role contract without host branching in this engine.
|
|
80
79
|
*/
|
|
81
80
|
proveNoEndangeringAcl(target: string): Promise<SecureResult<void>>;
|
|
82
81
|
/** Create ONE child directory exclusively at mode, reopen+verify, return its handle. */
|
package/dist/types/index.d.ts
CHANGED
|
@@ -69,9 +69,14 @@ export interface DoctorSection {
|
|
|
69
69
|
title: string;
|
|
70
70
|
checks: DoctorCheck[];
|
|
71
71
|
}
|
|
72
|
-
export
|
|
72
|
+
export type DoctorResult = {
|
|
73
|
+
state: "supported";
|
|
73
74
|
sections: DoctorSection[];
|
|
74
|
-
}
|
|
75
|
+
} | {
|
|
76
|
+
state: "unsupported-platform";
|
|
77
|
+
guidance: string;
|
|
78
|
+
sections: [];
|
|
79
|
+
};
|
|
75
80
|
export interface PluginManifest {
|
|
76
81
|
name: string;
|
|
77
82
|
version: string;
|
package/dist/ui/Doctor.d.ts
CHANGED
package/dist/ui/Doctor.js
CHANGED
|
@@ -15,6 +15,11 @@ const STATUS_COLOR = {
|
|
|
15
15
|
fail: theme.error,
|
|
16
16
|
skip: theme.muted,
|
|
17
17
|
};
|
|
18
|
+
export function unsupportedDoctorMessage(result) {
|
|
19
|
+
return result.state === "unsupported-platform"
|
|
20
|
+
? `unsupported-platform: ${result.guidance}`
|
|
21
|
+
: undefined;
|
|
22
|
+
}
|
|
18
23
|
export default function Doctor() {
|
|
19
24
|
const { exit } = useApp();
|
|
20
25
|
const isCI = useCIMode();
|
|
@@ -52,6 +57,14 @@ export default function Doctor() {
|
|
|
52
57
|
if (input.toLowerCase() === "q" || key.return || key.escape)
|
|
53
58
|
exit();
|
|
54
59
|
}, { isActive: !isCI });
|
|
60
|
+
const unsupportedMessage = result
|
|
61
|
+
? unsupportedDoctorMessage(result)
|
|
62
|
+
: undefined;
|
|
63
|
+
if (unsupportedMessage) {
|
|
64
|
+
return (React.createElement(Box, { flexDirection: "column", padding: 1 },
|
|
65
|
+
React.createElement(Header, { subtitle: "doctor" }),
|
|
66
|
+
React.createElement(Text, { color: theme.error }, unsupportedMessage)));
|
|
67
|
+
}
|
|
55
68
|
// Compute health score
|
|
56
69
|
const allChecks = result?.sections.flatMap((s) => s.checks) ?? [];
|
|
57
70
|
const passed = allChecks.filter((c) => c.status === "ok").length;
|
package/lib/common.sh
CHANGED
|
@@ -39,16 +39,12 @@ log_info() { echo -e " ${CYAN}[INFO]${NC} $1"; }
|
|
|
39
39
|
log_step() { echo -e "${YELLOW}$1${NC}"; }
|
|
40
40
|
|
|
41
41
|
# =============================================================================
|
|
42
|
-
# sed_inplace -
|
|
42
|
+
# sed_inplace - GNU sed -i for supported Linux/WSL hosts
|
|
43
43
|
# =============================================================================
|
|
44
44
|
# Usage: sed_inplace "s/foo/bar/" file.txt
|
|
45
45
|
# =============================================================================
|
|
46
46
|
sed_inplace() {
|
|
47
|
-
|
|
48
|
-
sed -i "$@"
|
|
49
|
-
else
|
|
50
|
-
sed -i '' "$@"
|
|
51
|
-
fi
|
|
47
|
+
sed -i "$@"
|
|
52
48
|
}
|
|
53
49
|
|
|
54
50
|
# =============================================================================
|
|
@@ -130,7 +126,7 @@ detect_stack() {
|
|
|
130
126
|
STACK_TYPE="java-gradle"
|
|
131
127
|
BUILD_TOOL="gradle"
|
|
132
128
|
|
|
133
|
-
# Detect Java version from build files
|
|
129
|
+
# Detect Java version from build files
|
|
134
130
|
if [[ -f "$project_dir/build.gradle.kts" ]]; then
|
|
135
131
|
JAVA_VERSION=$(grep -E 'languageVersion\s*=\s*JavaLanguageVersion\.of\(' "$project_dir/build.gradle.kts" 2>/dev/null | grep -o '[0-9]\+' | head -1 || echo "21")
|
|
136
132
|
elif [[ -f "$project_dir/build.gradle" ]]; then
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "javi-forge",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.38.0",
|
|
4
4
|
"description": "Project scaffolding and AI-ready CI bootstrap",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"lint:fix": "biome check --write src/",
|
|
22
22
|
"format": "biome format --write src/",
|
|
23
23
|
"validate": "pnpm typecheck && pnpm typecheck:test && pnpm lint && pnpm test",
|
|
24
|
-
"package:check": "
|
|
24
|
+
"package:check": "node scripts/verify-package-contents.mjs"
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
27
|
"dist/**/*.js",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"assets/",
|
|
32
32
|
"!assets/**/*.test.sh",
|
|
33
33
|
"ci-local/",
|
|
34
|
+
"!ci-local/tests/**",
|
|
34
35
|
"modules/",
|
|
35
36
|
"templates/",
|
|
36
37
|
"workflows/",
|
|
@@ -45,6 +46,10 @@
|
|
|
45
46
|
"type": "git",
|
|
46
47
|
"url": "git+https://github.com/JNZader/javi-forge.git"
|
|
47
48
|
},
|
|
49
|
+
"os": [
|
|
50
|
+
"linux",
|
|
51
|
+
"win32"
|
|
52
|
+
],
|
|
48
53
|
"engines": {
|
|
49
54
|
"node": ">=22"
|
|
50
55
|
},
|