javi-forge 1.31.0 → 1.32.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.
@@ -4,8 +4,11 @@
4
4
  * (`./commands/hooks.js`) is lazy-imported inside the handler to keep cold-start
5
5
  * minimal, because hooks are on the commit/push hot path.
6
6
  *
7
- * Only subcommand: `hooks run <pre-commit|pre-push>` → dispatches to runHook and
8
- * exits with its code. Any other subcommand or a missing name → usage + exit 1.
7
+ * Subcommands:
8
+ * - `hooks run <pre-commit|pre-push>` runHook, exits with its code.
9
+ * - `hooks <install|doctor|repair> claude [--force]` → runClaudeHookCommand,
10
+ * exits with its code (wrong/missing target → usage + exit 1).
11
+ * Any other subcommand or a missing name → usage + exit 1.
9
12
  */
10
13
  import type { CLI } from "./types.js";
11
14
  export declare function handleHooks(cli: CLI): Promise<void>;
@@ -4,8 +4,11 @@
4
4
  * (`./commands/hooks.js`) is lazy-imported inside the handler to keep cold-start
5
5
  * minimal, because hooks are on the commit/push hot path.
6
6
  *
7
- * Only subcommand: `hooks run <pre-commit|pre-push>` → dispatches to runHook and
8
- * exits with its code. Any other subcommand or a missing name → usage + exit 1.
7
+ * Subcommands:
8
+ * - `hooks run <pre-commit|pre-push>` runHook, exits with its code.
9
+ * - `hooks <install|doctor|repair> claude [--force]` → runClaudeHookCommand,
10
+ * exits with its code (wrong/missing target → usage + exit 1).
11
+ * Any other subcommand or a missing name → usage + exit 1.
9
12
  */
10
13
  import { HOOKS_HELP_TEXT } from "../help.js";
11
14
  export async function handleHooks(cli) {
@@ -23,6 +26,17 @@ export async function handleHooks(cli) {
23
26
  const code = await runHook(name, process.cwd());
24
27
  process.exit(code);
25
28
  }
29
+ const sub = cli.input[1];
30
+ if (sub === "install" || sub === "doctor" || sub === "repair") {
31
+ if (cli.input[2] !== "claude") {
32
+ console.error(`Usage: javi-forge hooks ${sub} claude`);
33
+ process.exit(1);
34
+ }
35
+ const { runClaudeHookCommand } = await import("../../commands/claude-hooks.js");
36
+ process.exit(await runClaudeHookCommand(sub, process.cwd(), {
37
+ force: cli.flags.force === true,
38
+ }));
39
+ }
26
40
  // No subcommand → show usage (exit 0). An unknown subcommand is a typo →
27
41
  // show usage but exit 1 rather than run nothing silently.
28
42
  console.log(HOOKS_HELP_TEXT);
@@ -19,7 +19,7 @@ export declare const CI_HELP_TEXT = "\n Usage\n $ javi-forge ci [subcommand]
19
19
  * Per-command help for `hooks`, shown by `javi-forge hooks --help` (or when
20
20
  * `hooks` is given an unknown subcommand). Whitespace is significant.
21
21
  */
22
- export declare const HOOKS_HELP_TEXT = "\n Usage\n $ javi-forge hooks run <pre-commit|pre-push>\n\n Run the sections enabled under hooks: in .javi-forge/ci.yaml, in a fixed\n cheap\u2192expensive order, fail-fast. With no hooks: config the default is the\n quick native CI gate (setup + lint + compile + gates \u2014 no tests, no coverage).\n\n Subcommands\n run pre-commit Run the composed pre-commit sections\n run pre-push Run the composed pre-push sections\n\n Notes\n A blocking section failure exits non-zero and blocks the commit/push.\n A broken .javi-forge/ci.yaml exits 1 (fail-closed \u2014 never skips a gate).\n To skip: git commit --no-verify (pre-push: git push --no-verify)\n\n Examples\n $ javi-forge hooks run pre-commit\n $ javi-forge hooks run pre-push\n";
22
+ export declare const HOOKS_HELP_TEXT = "\n Usage\n $ javi-forge hooks run <pre-commit|pre-push>\n $ javi-forge hooks <install|doctor|repair> claude [--force]\n\n Run the sections enabled under hooks: in .javi-forge/ci.yaml, in a fixed\n cheap\u2192expensive order, fail-fast. With no hooks: config the default is the\n quick native CI gate (setup + lint + compile + gates \u2014 no tests, no coverage).\n\n Subcommands\n run pre-commit Run the composed pre-commit sections\n run pre-push Run the composed pre-push sections\n install claude Install the managed Claude PreToolUse guard (.claude/)\n doctor claude Report Claude PreToolUse guard health (informational)\n repair claude Repair the managed guard; --force overwrites edited assets\n\n Notes\n A blocking section failure exits non-zero and blocks the commit/push.\n A broken .javi-forge/ci.yaml exits 1 (fail-closed \u2014 never skips a gate).\n To skip: git commit --no-verify (pre-push: git push --no-verify)\n doctor claude is informational (always exits 0); install/repair exit 0 on\n success, non-zero on refusal/failure. Use repair claude --force to overwrite\n a locally edited managed asset.\n\n Examples\n $ javi-forge hooks run pre-commit\n $ javi-forge hooks run pre-push\n $ javi-forge hooks install claude\n $ javi-forge hooks doctor claude\n $ javi-forge hooks repair claude --force\n";
23
23
  export declare const FLAGS_SCHEMA: {
24
24
  readonly help: {
25
25
  readonly type: "boolean";
package/dist/cli/help.js CHANGED
@@ -166,23 +166,33 @@ export const CI_HELP_TEXT = `
166
166
  export const HOOKS_HELP_TEXT = `
167
167
  Usage
168
168
  $ javi-forge hooks run <pre-commit|pre-push>
169
+ $ javi-forge hooks <install|doctor|repair> claude [--force]
169
170
 
170
171
  Run the sections enabled under hooks: in .javi-forge/ci.yaml, in a fixed
171
172
  cheap→expensive order, fail-fast. With no hooks: config the default is the
172
173
  quick native CI gate (setup + lint + compile + gates — no tests, no coverage).
173
174
 
174
175
  Subcommands
175
- run pre-commit Run the composed pre-commit sections
176
- run pre-push Run the composed pre-push sections
176
+ run pre-commit Run the composed pre-commit sections
177
+ run pre-push Run the composed pre-push sections
178
+ install claude Install the managed Claude PreToolUse guard (.claude/)
179
+ doctor claude Report Claude PreToolUse guard health (informational)
180
+ repair claude Repair the managed guard; --force overwrites edited assets
177
181
 
178
182
  Notes
179
183
  A blocking section failure exits non-zero and blocks the commit/push.
180
184
  A broken .javi-forge/ci.yaml exits 1 (fail-closed — never skips a gate).
181
185
  To skip: git commit --no-verify (pre-push: git push --no-verify)
186
+ doctor claude is informational (always exits 0); install/repair exit 0 on
187
+ success, non-zero on refusal/failure. Use repair claude --force to overwrite
188
+ a locally edited managed asset.
182
189
 
183
190
  Examples
184
191
  $ javi-forge hooks run pre-commit
185
192
  $ javi-forge hooks run pre-push
193
+ $ javi-forge hooks install claude
194
+ $ javi-forge hooks doctor claude
195
+ $ javi-forge hooks repair claude --force
186
196
  `;
187
197
  export const FLAGS_SCHEMA = {
188
198
  // `--help` is handled manually (autoHelp is disabled at the entrypoint so
@@ -0,0 +1,32 @@
1
+ /**
2
+ * `javi-forge hooks <install|doctor|repair> claude` — console-only renderer for
3
+ * the already-tested Claude PreToolUse guard library (Slice 4a). It wires the
4
+ * three lib fns to human output + exit codes; it adds NO new security logic and
5
+ * never touches `runTransaction`/secure-fs directly.
6
+ *
7
+ * Honest-execution constraint (spec Requirement "…never fabricates execution
8
+ * status"): the doctor renderer reports effective execution as `inconclusive`
9
+ * only. It MUST NOT print, imply, or default to `RUNNABLE` — real host-probing
10
+ * is Slice 4b. Exit 2 (INCONCLUSIVE gate semantics) is likewise reserved for 4b
11
+ * and never emitted here.
12
+ */
13
+ import { doctorClaudePreToolUse, installClaudePreToolUse, repairClaudePreToolUse } from "../lib/claude-hook-manager.js";
14
+ export type ClaudeHookSub = "install" | "doctor" | "repair";
15
+ /** Injectable seams for tests; each defaults to the real implementation. */
16
+ export interface ClaudeHookCmdDeps {
17
+ install?: typeof installClaudePreToolUse;
18
+ doctor?: typeof doctorClaudePreToolUse;
19
+ repair?: typeof repairClaudePreToolUse;
20
+ log?: (msg: string) => void;
21
+ logError?: (msg: string) => void;
22
+ }
23
+ /**
24
+ * Run one Claude-hook subcommand against `projectDir`. Returns the process exit
25
+ * code (the dispatcher calls `process.exit`, not this fn):
26
+ * - install/repair: 0 when `ok`, 1 on refusal/failure.
27
+ * - doctor: always 0 (informational).
28
+ */
29
+ export declare function runClaudeHookCommand(sub: ClaudeHookSub, projectDir: string, opts: {
30
+ force?: boolean;
31
+ }, deps?: ClaudeHookCmdDeps): Promise<number>;
32
+ //# sourceMappingURL=claude-hooks.d.ts.map
@@ -0,0 +1,75 @@
1
+ /**
2
+ * `javi-forge hooks <install|doctor|repair> claude` — console-only renderer for
3
+ * the already-tested Claude PreToolUse guard library (Slice 4a). It wires the
4
+ * three lib fns to human output + exit codes; it adds NO new security logic and
5
+ * never touches `runTransaction`/secure-fs directly.
6
+ *
7
+ * Honest-execution constraint (spec Requirement "…never fabricates execution
8
+ * status"): the doctor renderer reports effective execution as `inconclusive`
9
+ * only. It MUST NOT print, imply, or default to `RUNNABLE` — real host-probing
10
+ * is Slice 4b. Exit 2 (INCONCLUSIVE gate semantics) is likewise reserved for 4b
11
+ * and never emitted here.
12
+ */
13
+ import { doctorClaudePreToolUse, installClaudePreToolUse, repairClaudePreToolUse, } from "../lib/claude-hook-manager.js";
14
+ function renderMutation(verb, result, log, logError) {
15
+ if (result.ok) {
16
+ log(`${verb} claude: ok`);
17
+ if (result.changed.length > 0) {
18
+ log("changed:");
19
+ for (const p of result.changed)
20
+ log(` ${p}`);
21
+ }
22
+ else {
23
+ log("changed: nothing (already up to date)");
24
+ }
25
+ if (result.backups.length > 0) {
26
+ log("backups:");
27
+ for (const p of result.backups)
28
+ log(` ${p}`);
29
+ }
30
+ return 0;
31
+ }
32
+ logError(`${verb} claude: refused`);
33
+ for (const e of result.errors)
34
+ logError(` ${e}`);
35
+ return 1;
36
+ }
37
+ function renderDoctor(report, log) {
38
+ log(`doctor claude: ${report.healthy ? "healthy" : "unhealthy"}`);
39
+ log(` settings: ${report.settings.state} — ${report.settings.detail}`);
40
+ log(` asset: ${report.asset.state} — ${report.asset.detail}`);
41
+ log(` node: ${report.node.version ?? "unavailable"} (min-satisfied: ${report.node.satisfiesMinimum})`);
42
+ // Honest stub: 4a cannot confirm the guard is live. Never RUNNABLE.
43
+ log(" execution: inconclusive (effective-execution probe deferred to 4b)");
44
+ log(` host-residual: ${report.hostResidual}`);
45
+ if (report.remediation.length > 0) {
46
+ log(" remediation:");
47
+ for (const r of report.remediation)
48
+ log(` - ${r}`);
49
+ }
50
+ // Doctor is informational, not a gate — always exit 0 (spec Scenario
51
+ // "Doctor reports component health").
52
+ return 0;
53
+ }
54
+ /**
55
+ * Run one Claude-hook subcommand against `projectDir`. Returns the process exit
56
+ * code (the dispatcher calls `process.exit`, not this fn):
57
+ * - install/repair: 0 when `ok`, 1 on refusal/failure.
58
+ * - doctor: always 0 (informational).
59
+ */
60
+ export async function runClaudeHookCommand(sub, projectDir, opts, deps = {}) {
61
+ const log = deps.log ?? ((m) => console.log(m));
62
+ const logError = deps.logError ?? ((m) => console.error(m));
63
+ const install = deps.install ?? installClaudePreToolUse;
64
+ const doctor = deps.doctor ?? doctorClaudePreToolUse;
65
+ const repair = deps.repair ?? repairClaudePreToolUse;
66
+ if (sub === "install") {
67
+ return renderMutation("install", await install(projectDir), log, logError);
68
+ }
69
+ if (sub === "repair") {
70
+ const result = await repair(projectDir, { force: opts.force === true });
71
+ return renderMutation("repair", result, log, logError);
72
+ }
73
+ return renderDoctor(await doctor(projectDir), log);
74
+ }
75
+ //# sourceMappingURL=claude-hooks.js.map
@@ -1,11 +1,15 @@
1
1
  import type { StepFn } from "../types.js";
2
2
  /**
3
- * Step 14: Scaffold security hooks (hook-consolidation S4 fold).
3
+ * Step 14: Scaffold security hooks (hook-consolidation S4 fold + SkillGuard 4a).
4
4
  *
5
5
  * - When options.securityHooks is false, reports "skipped".
6
6
  * - Otherwise:
7
- * 1. Copies the kiteguard-style runtime settings to `.claude/settings.json`
8
- * when absent (KEPT this is a real feature).
7
+ * 1. When `claudePreToolUseGuard` is set, installs the managed Claude
8
+ * PreToolUse guard via the transactional `installClaudePreToolUse`
9
+ * (SkillGuard Slice 4a). The legacy copy-if-absent
10
+ * `claude-settings-security.json` scaffold is RETIRED — the managed
11
+ * installer owns `.claude/settings.json` + the hook asset with proper
12
+ * ownership markers.
9
13
  * 2. Merges the `hooks:` security sections for the selected reliability
10
14
  * profile into `.javi-forge/ci.yaml` via `setHookFeature` (creating a
11
15
  * minimal `version: 2` config when absent). The dispatcher composes these
@@ -1,8 +1,5 @@
1
- import path from "node:path";
2
- import fs from "fs-extra";
3
- import { SECURITY_HOOKS_DIR } from "../../../constants.js";
4
1
  import { setHookFeature } from "../../../lib/ci-config.js";
5
- import { ensureDirExists } from "../../../lib/common.js";
2
+ import { installClaudePreToolUse } from "../../../lib/claude-hook-manager.js";
6
3
  import { report } from "../report.js";
7
4
  /**
8
5
  * Hook-feature preset per reliability profile (hook-consolidation S4).
@@ -21,12 +18,16 @@ const PROFILE_PRESET = {
21
18
  strict: { preCommit: ["secrets", "permissions"], prePush: ["deps"] },
22
19
  };
23
20
  /**
24
- * Step 14: Scaffold security hooks (hook-consolidation S4 fold).
21
+ * Step 14: Scaffold security hooks (hook-consolidation S4 fold + SkillGuard 4a).
25
22
  *
26
23
  * - When options.securityHooks is false, reports "skipped".
27
24
  * - Otherwise:
28
- * 1. Copies the kiteguard-style runtime settings to `.claude/settings.json`
29
- * when absent (KEPT this is a real feature).
25
+ * 1. When `claudePreToolUseGuard` is set, installs the managed Claude
26
+ * PreToolUse guard via the transactional `installClaudePreToolUse`
27
+ * (SkillGuard Slice 4a). The legacy copy-if-absent
28
+ * `claude-settings-security.json` scaffold is RETIRED — the managed
29
+ * installer owns `.claude/settings.json` + the hook asset with proper
30
+ * ownership markers.
30
31
  * 2. Merges the `hooks:` security sections for the selected reliability
31
32
  * profile into `.javi-forge/ci.yaml` via `setHookFeature` (creating a
32
33
  * minimal `version: 2` config when absent). The dispatcher composes these
@@ -37,7 +38,7 @@ const PROFILE_PRESET = {
37
38
  */
38
39
  export const stepSecurityHooks = async (ctx) => {
39
40
  const { projectDir, dryRun, onStep, options } = ctx;
40
- const { securityHooks, hookProfile } = options;
41
+ const { securityHooks, hookProfile, claudePreToolUseGuard } = options;
41
42
  const stepId = "security-hooks";
42
43
  report(onStep, stepId, "Scaffold security hooks", "running");
43
44
  try {
@@ -48,18 +49,22 @@ export const stepSecurityHooks = async (ctx) => {
48
49
  const profile = hookProfile ?? "standard";
49
50
  const preset = PROFILE_PRESET[profile];
50
51
  if (dryRun) {
51
- report(onStep, stepId, "Scaffold security hooks", "done", `dry-run: would merge ${profile} hooks preset + copy .claude/settings.json`);
52
+ const guardNote = claudePreToolUseGuard
53
+ ? " + install Claude PreToolUse guard"
54
+ : "";
55
+ report(onStep, stepId, "Scaffold security hooks", "done", `dry-run: would merge ${profile} hooks preset${guardNote}`);
52
56
  return;
53
57
  }
54
- // 1. Copy the kiteguard-style runtime security settings to .claude/.
55
- const settingsSrc = path.join(SECURITY_HOOKS_DIR, "claude-settings-security.json");
56
- if (await fs.pathExists(settingsSrc)) {
57
- const claudeDir = path.join(projectDir, ".claude");
58
- await ensureDirExists(claudeDir);
59
- const settingsDest = path.join(claudeDir, "settings.json");
60
- if (!(await fs.pathExists(settingsDest))) {
61
- await fs.copy(settingsSrc, settingsDest);
58
+ // 1. Install the managed Claude PreToolUse guard (transactional; owns
59
+ // .claude/settings.json + the hook asset). Retires the legacy copy.
60
+ let guardNote = "";
61
+ if (claudePreToolUseGuard) {
62
+ const result = await installClaudePreToolUse(projectDir);
63
+ if (!result.ok) {
64
+ report(onStep, stepId, "Scaffold security hooks", "error", `Claude guard install refused: ${result.errors.join("; ")}`);
65
+ return;
62
66
  }
67
+ guardNote = "; Claude guard installed";
63
68
  }
64
69
  // 2. Merge the profile's security sections into .javi-forge/ci.yaml.
65
70
  for (const feature of preset.preCommit) {
@@ -72,9 +77,10 @@ export const stepSecurityHooks = async (ctx) => {
72
77
  ...preset.preCommit.map((f) => `pre-commit.${f}`),
73
78
  ...preset.prePush.map((f) => `pre-push.${f}`),
74
79
  ];
75
- report(onStep, stepId, "Scaffold security hooks", "done", merged.length > 0
80
+ const presetNote = merged.length > 0
76
81
  ? `${profile} preset: ${merged.join(", ")}`
77
- : `${profile} preset: CI gate only (no security sections)`);
82
+ : `${profile} preset: CI gate only (no security sections)`;
83
+ report(onStep, stepId, "Scaffold security hooks", "done", `${presetNote}${guardNote}`);
78
84
  }
79
85
  catch (e) {
80
86
  report(onStep, stepId, "Scaffold security hooks", "error", String(e));
@@ -16,6 +16,11 @@ export interface InitOptions {
16
16
  claudeMd: boolean;
17
17
  securityHooks: boolean;
18
18
  hookProfile: HookProfile;
19
+ /**
20
+ * Install the managed Claude PreToolUse guard during init. Derived from
21
+ * `securityHooks` at the single App.tsx call site (all profiles incl. Minimal).
22
+ */
23
+ claudePreToolUseGuard: boolean;
19
24
  codeGraph: boolean;
20
25
  dockerDeploy: boolean;
21
26
  /** Service name for docker rollout (default: 'app') */
package/dist/ui/App.js CHANGED
@@ -93,6 +93,9 @@ export default function App({ dryRun = false, presetStack, presetCI, presetMemor
93
93
  claudeMd: opts.claudeMd,
94
94
  securityHooks: opts.securityHooks,
95
95
  hookProfile: opts.hookProfile,
96
+ // Derived from securityHooks alone — ALL profiles incl. Minimal
97
+ // install the managed guard when security hooks are enabled.
98
+ claudePreToolUseGuard: opts.securityHooks,
96
99
  codeGraph: opts.codeGraph,
97
100
  localAi: opts.localAi,
98
101
  dockerDeploy: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "javi-forge",
3
- "version": "1.31.0",
3
+ "version": "1.32.0",
4
4
  "description": "Project scaffolding and AI-ready CI bootstrap",
5
5
  "type": "module",
6
6
  "bin": {