@fusengine/harness 0.1.0 → 0.1.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.
@@ -0,0 +1,23 @@
1
+ //#region src/adapters/cline/index.d.ts
2
+ /** `PreToolUse` stdin payload (subset). */
3
+ interface ClineHookInput {
4
+ hookName?: string;
5
+ preToolUse?: {
6
+ toolName?: string;
7
+ parameters?: {
8
+ path?: string;
9
+ content?: string;
10
+ command?: string;
11
+ };
12
+ };
13
+ }
14
+ /** Hook stdout response. */
15
+ interface ClineResponse {
16
+ cancel?: boolean;
17
+ errorMessage?: string;
18
+ contextModification?: string;
19
+ }
20
+ /** Evaluate a tool use; cancel on a hard block, otherwise inject context. */
21
+ declare function preToolUse(input: ClineHookInput): ClineResponse;
22
+ //#endregion
23
+ export { ClineHookInput, ClineResponse, preToolUse };
@@ -0,0 +1,25 @@
1
+ import { t as evaluate } from "../../evaluate-CsYyUucy.mjs";
2
+ import { t as formatPrompt } from "../../types-ernB1Dy3.mjs";
3
+ //#region src/adapters/cline/index.ts
4
+ /**
5
+ * Cline adapter (hook-mode). Schema per docs.cline.bot / .clinerules/hooks (2026):
6
+ * `PreToolUse` blocks via `{ cancel: true }`; it cannot modify tool parameters.
7
+ */
8
+ /** Evaluate a tool use; cancel on a hard block, otherwise inject context. */
9
+ function preToolUse(input) {
10
+ const t = input.preToolUse;
11
+ const r = evaluate({
12
+ tool: t?.toolName ?? "write_to_file",
13
+ filePath: t?.parameters?.path,
14
+ content: t?.parameters?.content,
15
+ command: t?.parameters?.command
16
+ });
17
+ if (r.decision === "allow" || !r.prompt) return {};
18
+ const msg = formatPrompt(r.prompt);
19
+ return r.prompt.kind === "block" ? {
20
+ cancel: true,
21
+ errorMessage: msg
22
+ } : { contextModification: msg };
23
+ }
24
+ //#endregion
25
+ export { preToolUse };
@@ -0,0 +1,31 @@
1
+ //#region src/adapters/cursor/index.d.ts
2
+ /** `beforeShellExecution` stdin payload (subset). */
3
+ interface CursorShellPayload {
4
+ command?: string;
5
+ cwd?: string;
6
+ workspace_roots?: string[];
7
+ hook_event_name?: string;
8
+ }
9
+ /** `afterFileEdit` stdin payload (subset). */
10
+ interface CursorEditPayload {
11
+ file_path?: string;
12
+ edits?: {
13
+ old_string: string;
14
+ new_string: string;
15
+ }[];
16
+ }
17
+ /** `beforeShellExecution` stdout response. */
18
+ interface CursorResponse {
19
+ permission: "allow" | "deny" | "ask";
20
+ continue?: boolean;
21
+ userMessage?: string;
22
+ agentMessage?: string;
23
+ }
24
+ /** Guard a shell command (git/install policies). */
25
+ declare function beforeShellExecution(payload: CursorShellPayload): CursorResponse;
26
+ /** Observe a file edit (Cursor cannot block here). Returns the verdict for logging. */
27
+ declare function afterFileEdit(payload: CursorEditPayload): {
28
+ violation: string | null;
29
+ };
30
+ //#endregion
31
+ export { CursorEditPayload, CursorResponse, CursorShellPayload, afterFileEdit, beforeShellExecution };
@@ -0,0 +1,37 @@
1
+ import { t as evaluate } from "../../evaluate-CsYyUucy.mjs";
2
+ import { t as formatPrompt } from "../../types-ernB1Dy3.mjs";
3
+ //#region src/adapters/cursor/index.ts
4
+ /**
5
+ * Cursor adapter (hook-mode). Schemas per cursor.com/docs/hooks (2026):
6
+ * `beforeShellExecution` can block; `afterFileEdit` is observe-only.
7
+ */
8
+ function toPermission(kind) {
9
+ return kind === "block" ? "deny" : kind === "ask" ? "ask" : "allow";
10
+ }
11
+ /** Guard a shell command (git/install policies). */
12
+ function beforeShellExecution(payload) {
13
+ const r = evaluate({
14
+ tool: "Bash",
15
+ command: payload.command
16
+ });
17
+ if (r.decision === "allow" || !r.prompt) return { permission: "allow" };
18
+ const msg = formatPrompt(r.prompt);
19
+ return {
20
+ permission: toPermission(r.prompt.kind),
21
+ continue: false,
22
+ userMessage: msg,
23
+ agentMessage: msg
24
+ };
25
+ }
26
+ /** Observe a file edit (Cursor cannot block here). Returns the verdict for logging. */
27
+ function afterFileEdit(payload) {
28
+ const content = payload.edits?.map((e) => e.new_string).join("\n") ?? "";
29
+ const r = evaluate({
30
+ tool: "Edit",
31
+ filePath: payload.file_path,
32
+ content
33
+ });
34
+ return { violation: r.decision === "deny" ? r.message : null };
35
+ }
36
+ //#endregion
37
+ export { afterFileEdit, beforeShellExecution };
@@ -0,0 +1,23 @@
1
+ //#region src/adapters/gemini/index.d.ts
2
+ /** `BeforeTool` stdin payload (subset). */
3
+ interface GeminiHookInput {
4
+ tool_name?: string;
5
+ tool_input?: {
6
+ command?: string;
7
+ path?: string;
8
+ content?: string;
9
+ };
10
+ hook_event_name?: string;
11
+ }
12
+ /** Hook stdout response. */
13
+ interface GeminiResponse {
14
+ decision?: "allow" | "deny";
15
+ reason?: string;
16
+ hookSpecificOutput?: {
17
+ additionalContext?: string;
18
+ };
19
+ }
20
+ /** Evaluate a tool use; deny on a hard block, otherwise inject context. */
21
+ declare function beforeTool(input: GeminiHookInput): GeminiResponse;
22
+ //#endregion
23
+ export { GeminiHookInput, GeminiResponse, beforeTool };
@@ -0,0 +1,25 @@
1
+ import { t as evaluate } from "../../evaluate-CsYyUucy.mjs";
2
+ import { t as formatPrompt } from "../../types-ernB1Dy3.mjs";
3
+ //#region src/adapters/gemini/index.ts
4
+ /**
5
+ * Gemini CLI adapter (hook-mode). Schema per google-gemini/gemini-cli docs/hooks (2026):
6
+ * `BeforeTool` blocks via `{ decision: "deny", reason }` (or exit 2).
7
+ */
8
+ /** Evaluate a tool use; deny on a hard block, otherwise inject context. */
9
+ function beforeTool(input) {
10
+ const i = input.tool_input;
11
+ const r = evaluate({
12
+ tool: input.tool_name ?? "write_file",
13
+ filePath: i?.path,
14
+ content: i?.content,
15
+ command: i?.command
16
+ });
17
+ if (r.decision === "allow" || !r.prompt) return {};
18
+ const msg = formatPrompt(r.prompt);
19
+ return r.prompt.kind === "block" ? {
20
+ decision: "deny",
21
+ reason: msg
22
+ } : { hookSpecificOutput: { additionalContext: msg } };
23
+ }
24
+ //#endregion
25
+ export { beforeTool };
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ import { n as stagedContent, r as stagedFiles, t as checkStaged } from "../run-h_2LNEA8.mjs";
3
+ //#region src/cli/bin.ts
4
+ /**
5
+ * harness-check — cli-mode entry for harnesses without hooks (Aider, Windsurf,
6
+ * OpenHands...). Run it as a pre-commit step: it checks staged files against the
7
+ * policy core and exits non-zero on a violation.
8
+ */
9
+ const files = stagedFiles();
10
+ if (files.length === 0) process.exit(0);
11
+ const violations = checkStaged(files, stagedContent);
12
+ if (violations.length > 0) {
13
+ process.stderr.write(`harness-check: policy violations\n\n${violations.join("\n\n")}\n`);
14
+ process.exit(1);
15
+ }
16
+ process.exit(0);
17
+ //#endregion
18
+ export {};
@@ -0,0 +1,12 @@
1
+ //#region src/cli/run.d.ts
2
+ /** Staged files (Added/Copied/Modified/Renamed). Uses `node:child_process` (Bun shell can hang on `git show`). */
3
+ declare function stagedFiles(): string[];
4
+ /** Read a file's staged (index) content — not the working-tree version. */
5
+ declare function stagedContent(path: string): string;
6
+ /**
7
+ * Evaluate staged code files against the policy core; return violation blocks.
8
+ * `read` is injected (the real impl is {@link stagedContent}) so this is pure + testable.
9
+ */
10
+ declare function checkStaged(files: string[], read: (path: string) => string): string[];
11
+ //#endregion
12
+ export { checkStaged, stagedContent, stagedFiles };
@@ -0,0 +1,2 @@
1
+ import { n as stagedContent, r as stagedFiles, t as checkStaged } from "../run-h_2LNEA8.mjs";
2
+ export { checkStaged, stagedContent, stagedFiles };
@@ -0,0 +1,32 @@
1
+ import { t as isCodeFile } from "./project-root-C4ks_q1G.mjs";
2
+ import { t as evaluate } from "./evaluate-CsYyUucy.mjs";
3
+ import { t as formatPrompt } from "./types-ernB1Dy3.mjs";
4
+ import { execSync } from "node:child_process";
5
+ //#region src/cli/run.ts
6
+ /** Staged files (Added/Copied/Modified/Renamed). Uses `node:child_process` (Bun shell can hang on `git show`). */
7
+ function stagedFiles() {
8
+ return execSync("git diff --cached --name-only --diff-filter=ACMR", { encoding: "utf8" }).trim().split("\n").filter(Boolean);
9
+ }
10
+ /** Read a file's staged (index) content — not the working-tree version. */
11
+ function stagedContent(path) {
12
+ return execSync(`git show ":${path}"`, { encoding: "utf8" });
13
+ }
14
+ /**
15
+ * Evaluate staged code files against the policy core; return violation blocks.
16
+ * `read` is injected (the real impl is {@link stagedContent}) so this is pure + testable.
17
+ */
18
+ function checkStaged(files, read) {
19
+ const violations = [];
20
+ for (const file of files) {
21
+ if (!isCodeFile(file)) continue;
22
+ const r = evaluate({
23
+ tool: "Write",
24
+ filePath: file,
25
+ content: read(file)
26
+ });
27
+ if (r.decision === "deny" && r.prompt) violations.push(`${file}\n${formatPrompt(r.prompt)}`);
28
+ }
29
+ return violations;
30
+ }
31
+ //#endregion
32
+ export { stagedContent as n, stagedFiles as r, checkStaged as t };
package/package.json CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "name": "@fusengine/harness",
3
- "version": "0.1.0",
4
- "description": "Harness-agnostic toolkit for AI coding agents: runtime harness detection (Claude Code, Codex, Cursor, Cline, Gemini, Aider...), pure policy core (env config, project/framework detection, SOLID/file-size limits, APEX freshness, guard patterns, portable prompts), cache, project memory, ref routing, state/locks, statusline, and thin per-harness adapters. Bun-native, with a built dist for Node + bundlers.",
3
+ "version": "0.1.1",
4
+ "description": "Harness-agnostic toolkit for AI coding agents: runtime harness detection (Claude Code, Codex, Cursor, Cline, Gemini, Aider...), pure policy core (env config, project/framework detection, SOLID/file-size limits, APEX freshness, guard patterns, portable prompts), cache, project memory, ref routing, state/locks, statusline, per-harness adapters (Claude/Cursor/Cline/Gemini) and a cli-mode harness-check binary. Bun-native, with a built dist for Node + bundlers.",
5
5
  "type": "module",
6
6
  "module": "src/index.ts",
7
7
  "types": "dist/index.d.mts",
8
+ "bin": {
9
+ "harness-check": "./dist/cli/bin.mjs"
10
+ },
8
11
  "exports": {
9
12
  ".": { "bun": "./src/index.ts", "types": "./dist/index.d.mts", "import": "./dist/index.mjs" },
10
13
  "./config": { "bun": "./src/config/index.ts", "types": "./dist/config/index.d.mts", "import": "./dist/config/index.mjs" },
@@ -18,7 +21,11 @@
18
21
  "./refs": { "bun": "./src/refs/index.ts", "types": "./dist/refs/index.d.mts", "import": "./dist/refs/index.mjs" },
19
22
  "./state": { "bun": "./src/state/index.ts", "types": "./dist/state/index.d.mts", "import": "./dist/state/index.mjs" },
20
23
  "./statusline": { "bun": "./src/statusline/index.ts", "types": "./dist/statusline/index.d.mts", "import": "./dist/statusline/index.mjs" },
21
- "./adapters/claude": { "bun": "./src/adapters/claude/index.ts", "types": "./dist/adapters/claude/index.d.mts", "import": "./dist/adapters/claude/index.mjs" }
24
+ "./cli": { "bun": "./src/cli/index.ts", "types": "./dist/cli/index.d.mts", "import": "./dist/cli/index.mjs" },
25
+ "./adapters/claude": { "bun": "./src/adapters/claude/index.ts", "types": "./dist/adapters/claude/index.d.mts", "import": "./dist/adapters/claude/index.mjs" },
26
+ "./adapters/cursor": { "bun": "./src/adapters/cursor/index.ts", "types": "./dist/adapters/cursor/index.d.mts", "import": "./dist/adapters/cursor/index.mjs" },
27
+ "./adapters/cline": { "bun": "./src/adapters/cline/index.ts", "types": "./dist/adapters/cline/index.d.mts", "import": "./dist/adapters/cline/index.mjs" },
28
+ "./adapters/gemini": { "bun": "./src/adapters/gemini/index.ts", "types": "./dist/adapters/gemini/index.d.mts", "import": "./dist/adapters/gemini/index.mjs" }
22
29
  },
23
30
  "files": ["dist", "README.md", "LICENSE"],
24
31
  "license": "MIT",
@@ -26,7 +33,7 @@
26
33
  "scripts": {
27
34
  "test": "bun test",
28
35
  "typecheck": "tsc --noEmit",
29
- "build": "tsdown src/index.ts src/config/index.ts src/util/index.ts src/detect/index.ts src/policy/index.ts src/prompt/index.ts src/memory/index.ts src/cache/index.ts src/freshness/index.ts src/refs/index.ts src/state/index.ts src/statusline/index.ts src/adapters/claude/index.ts --dts --format esm --clean --out-dir dist",
36
+ "build": "tsdown src/index.ts src/config/index.ts src/util/index.ts src/detect/index.ts src/policy/index.ts src/prompt/index.ts src/memory/index.ts src/cache/index.ts src/freshness/index.ts src/refs/index.ts src/state/index.ts src/statusline/index.ts src/cli/index.ts src/cli/bin.ts src/adapters/claude/index.ts src/adapters/cursor/index.ts src/adapters/cline/index.ts src/adapters/gemini/index.ts --dts --format esm --clean --out-dir dist",
30
37
  "prepublishOnly": "bun test && tsc --noEmit && bun run build"
31
38
  },
32
39
  "publishConfig": {