@fusengine/harness 0.1.7 → 0.1.9

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/dist/cli/bin.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { t as detectHarness } from "../harness-C8Nxxyn_.mjs";
3
3
  import { n as stagedContent, r as stagedFiles, t as checkStaged } from "../run-h_2LNEA8.mjs";
4
- import { n as writeInitFile, t as initFor } from "../run-Cc98348q.mjs";
5
- import { t as handleHook } from "../handle-BGe0QZvQ.mjs";
4
+ import { n as writeInitFile, t as initFor } from "../run-Cdp2Ef9B.mjs";
5
+ import { t as handleHook } from "../handle-Lz_thVKr.mjs";
6
6
  //#region src/cli/bin.ts
7
7
  /**
8
8
  * harness — CLI for @fusengine/harness.
@@ -26,18 +26,20 @@ const cmd = process.argv[2];
26
26
  if (cmd === "hook") {
27
27
  const outcome = await handleHook(process.argv[3] ?? detectHarness().id, await readStdin(), {
28
28
  now: Date.now(),
29
- cwd: process.cwd()
29
+ cwd: process.cwd(),
30
+ refsDir: process.env.FUSE_HARNESS_REFS
30
31
  });
31
32
  if (outcome.stdout) process.stdout.write(outcome.stdout);
32
33
  process.exit(outcome.exit);
33
34
  } else if (cmd === "init") {
34
35
  const id = process.argv[3] ?? detectHarness().id;
35
- const file = initFor(id);
36
- if (!file) {
36
+ const files = initFor(id);
37
+ if (!files) {
37
38
  process.stderr.write(`harness: no hook integration for "${id}" — use \`harness check\` in a pre-commit step\n`);
38
39
  process.exit(1);
39
40
  }
40
- process.stdout.write(`harness: wired ${id} -> ${writeInitFile(process.cwd(), file)}\n`);
41
+ const written = files.map((f) => writeInitFile(process.cwd(), f));
42
+ process.stdout.write(`harness: wired ${id} -> ${written.join(", ")}\n`);
41
43
  process.exit(0);
42
44
  } else {
43
45
  const files = stagedFiles();
@@ -1,6 +1,7 @@
1
1
  import { l as detectFramework, t as evaluate } from "./evaluate-CsYyUucy.mjs";
2
2
  import { r as evaluateApex } from "./apex-gGrHzvM2.mjs";
3
3
  import { t as formatPrompt } from "./types-ernB1Dy3.mjs";
4
+ import { t as loadRefs } from "./loader-BephwI8n.mjs";
4
5
  import { a as recordAgent, n as saveTrack, o as recordDoc, r as agentsFresh, s as recordRefRead, t as loadTrack } from "./store-BWvwnnf6.mjs";
5
6
  import { join } from "node:path";
6
7
  import { tmpdir } from "node:os";
@@ -209,6 +210,7 @@ async function handleHook(id, payload, opts) {
209
210
  filePath: event.filePath,
210
211
  content: event.content,
211
212
  command: event.command,
213
+ refs: opts.refsDir ? await loadRefs(opts.refsDir) : void 0,
212
214
  now: opts.now,
213
215
  trackFile: file
214
216
  });
@@ -18,4 +18,14 @@ declare function scoreReferences(refs: RefMeta[], filePath: string, content: str
18
18
  */
19
19
  declare function routeReferences(refs: RefMeta[], filePath: string, content: string, skillPath?: string): RouteResult | null;
20
20
  //#endregion
21
- export { parseFrontmatter as i, scoreReferences as n, globToRe as r, routeReferences as t };
21
+ //#region src/refs/loader.d.ts
22
+ /** Build a {@link RefMeta} from parsed frontmatter, tolerant of kebab/camel keys. */
23
+ declare function toRefMeta(fm: Record<string, string>, filePath: string): RefMeta;
24
+ /**
25
+ * Scan a directory recursively for `.md` reference files and parse each into a
26
+ * {@link RefMeta}. The content is entirely the consumer's — point this at any
27
+ * refs dir (`FUSE_HARNESS_REFS`). Returns an empty list when the dir is absent.
28
+ */
29
+ declare function loadRefs(dir: string): Promise<RefMeta[]>;
30
+ //#endregion
31
+ export { globToRe as a, scoreReferences as i, toRefMeta as n, parseFrontmatter as o, routeReferences as r, loadRefs as t };
package/dist/index.d.mts CHANGED
@@ -8,7 +8,7 @@ import { i as compactJson, n as projectRoot, r as projectRootOrNull, t as isCode
8
8
  import { C as isApexCommand, S as detectProjectType, _ as countLines, a as evaluateApex, b as DEV_KEYWORDS, c as PolicyContext, d as GIT_ASK, f as GIT_BLOCKED, g as FileSizeVerdict, h as matchPatterns, i as docConsultedGate, l as PolicyResult, m as SYSTEM_INSTALL, n as ApexContext, o as freshnessGate, p as PROJECT_INSTALL, r as ApexGate, s as solidReadGate, t as APEX_GATES, u as evaluate, v as evaluateFileSize, x as ProjectType, y as detectFramework } from "./index-BbJucvaG.mjs";
9
9
  import { n as RouteResult, r as ScoredRef, t as RefMeta } from "./types-CY5qT2X1.mjs";
10
10
  import { a as ReminderState, c as setStateField, i as registryFile, l as stateFileFor, n as addRoot, o as nowStamp, r as readRoots, s as readState, t as ensureMemoryGitignore, u as throttleMs } from "./index-BEM-mQMC.mjs";
11
- import { i as parseFrontmatter, n as scoreReferences, r as globToRe, t as routeReferences } from "./index-C2Lz-cwJ.mjs";
11
+ import { a as globToRe, i as scoreReferences, n as toRefMeta, o as parseFrontmatter, r as routeReferences, t as loadRefs } from "./index-hL_r6tlc.mjs";
12
12
  import { a as taskStart, c as ensureStateDir, d as stateFilePath, f as acquireLock, i as taskCreate, l as loadState, n as ApexTaskFile, o as ApexState, r as taskComplete, s as apexStateDir, t as ApexTask, u as saveState } from "./index-CPoF_hLP.mjs";
13
13
  import { _ as TIME_INTERVALS, a as formatCost, c as formatTokens, d as colors, f as progressiveColor, g as PROGRESS_CHARS, h as PROGRESS_BAR_DEFAULTS, i as formatBasename, l as ColorFn, m as GRADIENT_BLOCKS, n as generateGradientBar, o as formatPath, p as COLOR_THRESHOLDS, r as generateProgressBar, s as formatTimeLeft, t as ProgressBarOptions, u as Palette } from "./index-BWK8slRi.mjs";
14
- export { APEX_GATES, ApexContext, ApexGate, ApexState, ApexTask, ApexTaskFile, AuthEntry, COLOR_THRESHOLDS, ColorFn, DEFAULT_MAX_LINES, DEFAULT_TTL_SEC, DEV_KEYWORDS, DocSatisfactionStatus, FileSizeVerdict, GIT_ASK, GIT_BLOCKED, GRADIENT_BLOCKS, HarnessId, HarnessInfo, HarnessMode, HarnessVia, IndexSummary, MAX_LINES_ENV_KEY, PROGRESS_BAR_DEFAULTS, PROGRESS_CHARS, PROJECT_INSTALL, Palette, PolicyContext, PolicyResult, ProgressBarOptions, ProjectType, Prompt, PromptKind, RefMeta, ReminderState, RouteResult, SYSTEM_INSTALL, ScoredRef, TIME_INTERVALS, TTL_ENV_KEY, acquireLock, addRoot, apexStateDir, colors, compactJson, compactMarkdown, countLines, detectFramework, detectHarness, detectMode, detectProjectType, docConsultedGate, ensureMemoryGitignore, ensureStateDir, evaluate, evaluateApex, evaluateFileSize, extractText, formatBasename, formatCost, formatDocDeny, formatDocSatisfactionStatus, formatPath, formatPrompt, formatTimeLeft, formatTokens, freshnessGate, generateGradientBar, generateProgressBar, globToRe, incrementTrivialEditCounter, isApexCommand, isCodeFile, isDocConsulted, jaccardSimilar, loadIndex, loadState, matchPatterns, modeFor, nowStamp, parseEnvInt, parseFrontmatter, progressiveColor, projectRoot, projectRootOrNull, queryHash, readRoots, readState, registryFile, resolveMaxLines, resolveSessions, resolveTtlSec, routeReferences, saveState, scoreReferences, setStateField, solidReadGate, splitTarget, stateFileFor, stateFilePath, summarizeIndex, taskComplete, taskCreate, taskStart, throttleMs, ttlLabel };
14
+ export { APEX_GATES, ApexContext, ApexGate, ApexState, ApexTask, ApexTaskFile, AuthEntry, COLOR_THRESHOLDS, ColorFn, DEFAULT_MAX_LINES, DEFAULT_TTL_SEC, DEV_KEYWORDS, DocSatisfactionStatus, FileSizeVerdict, GIT_ASK, GIT_BLOCKED, GRADIENT_BLOCKS, HarnessId, HarnessInfo, HarnessMode, HarnessVia, IndexSummary, MAX_LINES_ENV_KEY, PROGRESS_BAR_DEFAULTS, PROGRESS_CHARS, PROJECT_INSTALL, Palette, PolicyContext, PolicyResult, ProgressBarOptions, ProjectType, Prompt, PromptKind, RefMeta, ReminderState, RouteResult, SYSTEM_INSTALL, ScoredRef, TIME_INTERVALS, TTL_ENV_KEY, acquireLock, addRoot, apexStateDir, colors, compactJson, compactMarkdown, countLines, detectFramework, detectHarness, detectMode, detectProjectType, docConsultedGate, ensureMemoryGitignore, ensureStateDir, evaluate, evaluateApex, evaluateFileSize, extractText, formatBasename, formatCost, formatDocDeny, formatDocSatisfactionStatus, formatPath, formatPrompt, formatTimeLeft, formatTokens, freshnessGate, generateGradientBar, generateProgressBar, globToRe, incrementTrivialEditCounter, isApexCommand, isCodeFile, isDocConsulted, jaccardSimilar, loadIndex, loadRefs, loadState, matchPatterns, modeFor, nowStamp, parseEnvInt, parseFrontmatter, progressiveColor, projectRoot, projectRootOrNull, queryHash, readRoots, readState, registryFile, resolveMaxLines, resolveSessions, resolveTtlSec, routeReferences, saveState, scoreReferences, setStateField, solidReadGate, splitTarget, stateFileFor, stateFilePath, summarizeIndex, taskComplete, taskCreate, taskStart, throttleMs, toRefMeta, ttlLabel };
package/dist/index.mjs CHANGED
@@ -12,6 +12,7 @@ import { t as formatPrompt } from "./types-ernB1Dy3.mjs";
12
12
  import { a as readState, c as throttleMs, i as nowStamp, l as ensureMemoryGitignore, n as readRoots, o as setStateField, r as registryFile, s as stateFileFor, t as addRoot } from "./memory-BVNt4Ary.mjs";
13
13
  import { a as jaccardSimilar, i as compactMarkdown, n as loadIndex, o as queryHash, r as summarizeIndex, t as extractText } from "./cache-DbPSJ9bC.mjs";
14
14
  import { t as incrementTrivialEditCounter } from "./freshness-BK9Xg6oG.mjs";
15
+ import { n as toRefMeta, t as loadRefs } from "./loader-BephwI8n.mjs";
15
16
  import { a as ensureStateDir, c as stateFilePath, i as apexStateDir, l as acquireLock, n as taskCreate, o as loadState, r as taskStart, s as saveState, t as taskComplete } from "./state-Bc4wdnCG.mjs";
16
17
  import { a as formatPath, c as colors, d as GRADIENT_BLOCKS, f as PROGRESS_BAR_DEFAULTS, i as formatCost, l as progressiveColor, m as TIME_INTERVALS, n as generateProgressBar, o as formatTimeLeft, p as PROGRESS_CHARS, r as formatBasename, s as formatTokens, t as generateGradientBar, u as COLOR_THRESHOLDS } from "./statusline-D87eUNXl.mjs";
17
- export { APEX_GATES, COLOR_THRESHOLDS, DEFAULT_MAX_LINES, DEFAULT_TTL_SEC, DEV_KEYWORDS, GIT_ASK, GIT_BLOCKED, GRADIENT_BLOCKS, MAX_LINES_ENV_KEY, PROGRESS_BAR_DEFAULTS, PROGRESS_CHARS, PROJECT_INSTALL, SYSTEM_INSTALL, TIME_INTERVALS, TTL_ENV_KEY, acquireLock, addRoot, apexStateDir, colors, compactJson, compactMarkdown, countLines, detectFramework, detectHarness, detectMode, detectProjectType, docConsultedGate, ensureMemoryGitignore, ensureStateDir, evaluate, evaluateApex, evaluateFileSize, extractText, formatBasename, formatCost, formatDocDeny, formatDocSatisfactionStatus, formatPath, formatPrompt, formatTimeLeft, formatTokens, freshnessGate, generateGradientBar, generateProgressBar, globToRe, incrementTrivialEditCounter, isApexCommand, isCodeFile, isDocConsulted, jaccardSimilar, loadIndex, loadState, matchPatterns, modeFor, nowStamp, parseEnvInt, parseFrontmatter, progressiveColor, projectRoot, projectRootOrNull, queryHash, readRoots, readState, registryFile, resolveMaxLines, resolveSessions, resolveTtlSec, routeReferences, saveState, scoreReferences, setStateField, solidReadGate, splitTarget, stateFileFor, stateFilePath, summarizeIndex, taskComplete, taskCreate, taskStart, throttleMs, ttlLabel };
18
+ export { APEX_GATES, COLOR_THRESHOLDS, DEFAULT_MAX_LINES, DEFAULT_TTL_SEC, DEV_KEYWORDS, GIT_ASK, GIT_BLOCKED, GRADIENT_BLOCKS, MAX_LINES_ENV_KEY, PROGRESS_BAR_DEFAULTS, PROGRESS_CHARS, PROJECT_INSTALL, SYSTEM_INSTALL, TIME_INTERVALS, TTL_ENV_KEY, acquireLock, addRoot, apexStateDir, colors, compactJson, compactMarkdown, countLines, detectFramework, detectHarness, detectMode, detectProjectType, docConsultedGate, ensureMemoryGitignore, ensureStateDir, evaluate, evaluateApex, evaluateFileSize, extractText, formatBasename, formatCost, formatDocDeny, formatDocSatisfactionStatus, formatPath, formatPrompt, formatTimeLeft, formatTokens, freshnessGate, generateGradientBar, generateProgressBar, globToRe, incrementTrivialEditCounter, isApexCommand, isCodeFile, isDocConsulted, jaccardSimilar, loadIndex, loadRefs, loadState, matchPatterns, modeFor, nowStamp, parseEnvInt, parseFrontmatter, progressiveColor, projectRoot, projectRootOrNull, queryHash, readRoots, readState, registryFile, resolveMaxLines, resolveSessions, resolveTtlSec, routeReferences, saveState, scoreReferences, setStateField, solidReadGate, splitTarget, stateFileFor, stateFilePath, summarizeIndex, taskComplete, taskCreate, taskStart, throttleMs, toRefMeta, ttlLabel };
@@ -2,8 +2,9 @@ import { t as HarnessId } from "../harness-DwJskkz_.mjs";
2
2
 
3
3
  //#region src/init/templates.d.ts
4
4
  /**
5
- * Wiring file templates per harness, to register `harness hook <id>` as a
6
- * tool-use hook. Formats verified against each harness's 2026 hook docs.
5
+ * Wiring file templates per harness, registering `harness hook <id>` for BOTH
6
+ * phases: PRE (gate the edit) and POST (catch-all `activityFor` filters what
7
+ * to record). Formats verified against each harness's 2026 hook docs.
7
8
  */
8
9
  /** A file to write during `harness init`. */
9
10
  interface InitFile {
@@ -11,23 +12,23 @@ interface InitFile {
11
12
  content: string;
12
13
  executable?: boolean;
13
14
  }
14
- /** Claude Code: PreToolUse(Write|Edit) -> command. Target: `.claude/settings.json`. */
15
- declare function claudeInit(command: string): InitFile;
16
- /** Codex CLI: `.codex/hooks.json` — PreToolUse (Claude-compatible shape). */
17
- declare function codexInit(command: string): InitFile;
18
- /** Cursor: `.cursor/hooks.json` (version 1) — shell + file-edit hooks. */
19
- declare function cursorInit(command: string): InitFile;
20
- /** Gemini CLI: `.gemini/settings.json` — BeforeTool, timeout in ms. */
21
- declare function geminiInit(command: string): InitFile;
22
- /** Cline: an executable `.clinerules/hooks/PreToolUse` that pipes stdin to the command. */
23
- declare function clineInit(command: string): InitFile;
15
+ /** Claude Code: `.claude/settings.json` — PreToolUse(Write|Edit) + PostToolUse(all). */
16
+ declare function claudeInit(command: string): InitFile[];
17
+ /** Codex CLI: `.codex/hooks.json` (Claude-compatible shape). */
18
+ declare function codexInit(command: string): InitFile[];
19
+ /** Cursor: `.cursor/hooks.json` (version 1) — shell + tool gate + file-edit observe. */
20
+ declare function cursorInit(command: string): InitFile[];
21
+ /** Gemini CLI: `.gemini/settings.json` — BeforeTool(edits) + AfterTool(all, regex). */
22
+ declare function geminiInit(command: string): InitFile[];
23
+ /** Cline: executable `.clinerules/hooks/PreToolUse` + `PostToolUse` piping stdin. */
24
+ declare function clineInit(command: string): InitFile[];
24
25
  //#endregion
25
26
  //#region src/init/run.d.ts
26
27
  /**
27
- * Build the wiring file for a harness, or null when it has no hook integration
28
- * (cli-mode harnesses use `harness check` in a pre-commit step instead).
28
+ * Build the wiring file(s) for a harness, or null when it has no hook
29
+ * integration (cli-mode harnesses use `harness check` in a pre-commit step).
29
30
  */
30
- declare function initFor(id: HarnessId, command?: string): InitFile | null;
31
+ declare function initFor(id: HarnessId, command?: string): InitFile[] | null;
31
32
  /** Write an {@link InitFile} under `root` (creates dirs; `chmod +x` when executable). */
32
33
  declare function writeInitFile(root: string, file: InitFile): string;
33
34
  //#endregion
@@ -1,2 +1,2 @@
1
- import { a as codexInit, i as clineInit, n as writeInitFile, o as cursorInit, r as claudeInit, s as geminiInit, t as initFor } from "../run-Cc98348q.mjs";
1
+ import { a as codexInit, i as clineInit, n as writeInitFile, o as cursorInit, r as claudeInit, s as geminiInit, t as initFor } from "../run-Cdp2Ef9B.mjs";
2
2
  export { claudeInit, clineInit, codexInit, cursorInit, geminiInit, initFor, writeInitFile };
@@ -0,0 +1,47 @@
1
+ import { i as parseFrontmatter } from "./router-Dj3AfgBE.mjs";
2
+ import { join } from "node:path";
3
+ import { readFile, readdir } from "node:fs/promises";
4
+ //#region src/refs/loader.ts
5
+ function pick(fm, ...keys) {
6
+ for (const k of keys) {
7
+ const v = fm[k];
8
+ if (v) return v;
9
+ }
10
+ return "";
11
+ }
12
+ /** Build a {@link RefMeta} from parsed frontmatter, tolerant of kebab/camel keys. */
13
+ function toRefMeta(fm, filePath) {
14
+ return {
15
+ name: pick(fm, "name") || filePath,
16
+ description: pick(fm, "description"),
17
+ keywords: pick(fm, "keywords"),
18
+ priority: pick(fm, "priority"),
19
+ related: pick(fm, "related"),
20
+ appliesTo: pick(fm, "appliesTo", "applies-to", "applies_to"),
21
+ triggerOnEdit: pick(fm, "triggerOnEdit", "trigger-on-edit", "trigger_on_edit"),
22
+ level: pick(fm, "level"),
23
+ filePath
24
+ };
25
+ }
26
+ /**
27
+ * Scan a directory recursively for `.md` reference files and parse each into a
28
+ * {@link RefMeta}. The content is entirely the consumer's — point this at any
29
+ * refs dir (`FUSE_HARNESS_REFS`). Returns an empty list when the dir is absent.
30
+ */
31
+ async function loadRefs(dir) {
32
+ let entries;
33
+ try {
34
+ entries = await readdir(dir, { recursive: true });
35
+ } catch {
36
+ return [];
37
+ }
38
+ const refs = [];
39
+ for (const rel of entries) {
40
+ if (!rel.endsWith(".md")) continue;
41
+ const filePath = join(dir, rel);
42
+ refs.push(toRefMeta(parseFrontmatter(await readFile(filePath, "utf8")), filePath));
43
+ }
44
+ return refs;
45
+ }
46
+ //#endregion
47
+ export { toRefMeta as n, loadRefs as t };
@@ -1,3 +1,3 @@
1
1
  import { n as RouteResult, r as ScoredRef, t as RefMeta } from "../types-CY5qT2X1.mjs";
2
- import { i as parseFrontmatter, n as scoreReferences, r as globToRe, t as routeReferences } from "../index-C2Lz-cwJ.mjs";
3
- export { RefMeta, RouteResult, ScoredRef, globToRe, parseFrontmatter, routeReferences, scoreReferences };
2
+ import { a as globToRe, i as scoreReferences, n as toRefMeta, o as parseFrontmatter, r as routeReferences, t as loadRefs } from "../index-hL_r6tlc.mjs";
3
+ export { RefMeta, RouteResult, ScoredRef, globToRe, loadRefs, parseFrontmatter, routeReferences, scoreReferences, toRefMeta };
@@ -1,3 +1,4 @@
1
1
  import { i as parseFrontmatter, n as scoreReferences, r as globToRe, t as routeReferences } from "../router-Dj3AfgBE.mjs";
2
+ import { n as toRefMeta, t as loadRefs } from "../loader-BephwI8n.mjs";
2
3
  import "../refs-la_KkjCS.mjs";
3
- export { globToRe, parseFrontmatter, routeReferences, scoreReferences };
4
+ export { globToRe, loadRefs, parseFrontmatter, routeReferences, scoreReferences, toRefMeta };
@@ -0,0 +1,128 @@
1
+ import { chmodSync, mkdirSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ //#region src/init/templates.ts
4
+ function json(obj) {
5
+ return `${JSON.stringify(obj, null, 2)}\n`;
6
+ }
7
+ /** Claude Code: `.claude/settings.json` — PreToolUse(Write|Edit) + PostToolUse(all). */
8
+ function claudeInit(command) {
9
+ return [{
10
+ path: ".claude/settings.json",
11
+ content: json({ hooks: {
12
+ PreToolUse: [{
13
+ matcher: "Write|Edit",
14
+ hooks: [{
15
+ type: "command",
16
+ command
17
+ }]
18
+ }],
19
+ PostToolUse: [{
20
+ matcher: "",
21
+ hooks: [{
22
+ type: "command",
23
+ command
24
+ }]
25
+ }]
26
+ } })
27
+ }];
28
+ }
29
+ /** Codex CLI: `.codex/hooks.json` (Claude-compatible shape). */
30
+ function codexInit(command) {
31
+ return [{
32
+ path: ".codex/hooks.json",
33
+ content: json({ hooks: {
34
+ PreToolUse: [{
35
+ matcher: "Bash|apply_patch",
36
+ hooks: [{
37
+ type: "command",
38
+ command
39
+ }]
40
+ }],
41
+ PostToolUse: [{
42
+ matcher: "",
43
+ hooks: [{
44
+ type: "command",
45
+ command
46
+ }]
47
+ }]
48
+ } })
49
+ }];
50
+ }
51
+ /** Cursor: `.cursor/hooks.json` (version 1) — shell + tool gate + file-edit observe. */
52
+ function cursorInit(command) {
53
+ return [{
54
+ path: ".cursor/hooks.json",
55
+ content: json({
56
+ version: 1,
57
+ hooks: {
58
+ beforeShellExecution: [{ command }],
59
+ preToolUse: [{ command }],
60
+ afterFileEdit: [{ command }]
61
+ }
62
+ })
63
+ }];
64
+ }
65
+ /** Gemini CLI: `.gemini/settings.json` — BeforeTool(edits) + AfterTool(all, regex). */
66
+ function geminiInit(command) {
67
+ return [{
68
+ path: ".gemini/settings.json",
69
+ content: json({ hooks: {
70
+ BeforeTool: [{
71
+ matcher: "write_file|edit_file|replace",
72
+ hooks: [{
73
+ type: "command",
74
+ command,
75
+ timeout: 3e4
76
+ }]
77
+ }],
78
+ AfterTool: [{
79
+ matcher: ".*",
80
+ hooks: [{
81
+ type: "command",
82
+ command,
83
+ timeout: 3e4
84
+ }]
85
+ }]
86
+ } })
87
+ }];
88
+ }
89
+ /** Cline: executable `.clinerules/hooks/PreToolUse` + `PostToolUse` piping stdin. */
90
+ function clineInit(command) {
91
+ const script = (phase) => `#!/usr/bin/env bash\n# fuse-harness Cline ${phase} hook\ncat | ${command}\n`;
92
+ return [{
93
+ path: ".clinerules/hooks/PreToolUse",
94
+ content: script("pre"),
95
+ executable: true
96
+ }, {
97
+ path: ".clinerules/hooks/PostToolUse",
98
+ content: script("post"),
99
+ executable: true
100
+ }];
101
+ }
102
+ //#endregion
103
+ //#region src/init/run.ts
104
+ const RUNNERS = {
105
+ "claude-code": claudeInit,
106
+ codex: codexInit,
107
+ cursor: cursorInit,
108
+ "gemini-cli": geminiInit,
109
+ cline: clineInit
110
+ };
111
+ /**
112
+ * Build the wiring file(s) for a harness, or null when it has no hook
113
+ * integration (cli-mode harnesses use `harness check` in a pre-commit step).
114
+ */
115
+ function initFor(id, command = `npx harness hook ${id}`) {
116
+ const make = RUNNERS[id];
117
+ return make ? make(command) : null;
118
+ }
119
+ /** Write an {@link InitFile} under `root` (creates dirs; `chmod +x` when executable). */
120
+ function writeInitFile(root, file) {
121
+ const full = join(root, file.path);
122
+ mkdirSync(dirname(full), { recursive: true });
123
+ writeFileSync(full, file.content);
124
+ if (file.executable) chmodSync(full, 493);
125
+ return full;
126
+ }
127
+ //#endregion
128
+ export { codexInit as a, clineInit as i, writeInitFile as n, cursorInit as o, claudeInit as r, geminiInit as s, initFor as t };
@@ -107,6 +107,8 @@ declare function respond(id: string, prompt: Prompt): string;
107
107
  interface HandleOptions {
108
108
  now: number;
109
109
  cwd: string;
110
+ /** Directory of SOLID reference `.md` files for `solidReadGate` (else inert). */
111
+ refsDir?: string;
110
112
  }
111
113
  /** What the hook bin should print + exit with. */
112
114
  interface HandleOutcome {
@@ -1,2 +1,2 @@
1
- import { a as trackFile, c as REQUIRED_AGENTS, i as recordActivity, l as gate, n as harnessTrackDir, o as normalizeEvent, r as respond, s as DEFAULT_WINDOW_MS, t as handleHook, u as activityFor } from "../handle-BGe0QZvQ.mjs";
1
+ import { a as trackFile, c as REQUIRED_AGENTS, i as recordActivity, l as gate, n as harnessTrackDir, o as normalizeEvent, r as respond, s as DEFAULT_WINDOW_MS, t as handleHook, u as activityFor } from "../handle-Lz_thVKr.mjs";
2
2
  export { DEFAULT_WINDOW_MS, REQUIRED_AGENTS, activityFor, gate, handleHook, harnessTrackDir, normalizeEvent, recordActivity, respond, trackFile };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fusengine/harness",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
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",
@@ -1,94 +0,0 @@
1
- import { chmodSync, mkdirSync, writeFileSync } from "node:fs";
2
- import { dirname, join } from "node:path";
3
- //#region src/init/templates.ts
4
- function json(obj) {
5
- return `${JSON.stringify(obj, null, 2)}\n`;
6
- }
7
- /** Claude Code: PreToolUse(Write|Edit) -> command. Target: `.claude/settings.json`. */
8
- function claudeInit(command) {
9
- return {
10
- path: ".claude/settings.json",
11
- content: json({ hooks: { PreToolUse: [{
12
- matcher: "Write|Edit",
13
- hooks: [{
14
- type: "command",
15
- command
16
- }]
17
- }] } })
18
- };
19
- }
20
- /** Codex CLI: `.codex/hooks.json` — PreToolUse (Claude-compatible shape). */
21
- function codexInit(command) {
22
- return {
23
- path: ".codex/hooks.json",
24
- content: json({ hooks: { PreToolUse: [{
25
- matcher: "Bash|apply_patch",
26
- hooks: [{
27
- type: "command",
28
- command
29
- }]
30
- }] } })
31
- };
32
- }
33
- /** Cursor: `.cursor/hooks.json` (version 1) — shell + file-edit hooks. */
34
- function cursorInit(command) {
35
- return {
36
- path: ".cursor/hooks.json",
37
- content: json({
38
- version: 1,
39
- hooks: {
40
- beforeShellExecution: [{ command }],
41
- afterFileEdit: [{ command }]
42
- }
43
- })
44
- };
45
- }
46
- /** Gemini CLI: `.gemini/settings.json` — BeforeTool, timeout in ms. */
47
- function geminiInit(command) {
48
- return {
49
- path: ".gemini/settings.json",
50
- content: json({ hooks: { BeforeTool: [{
51
- matcher: "write_file|edit_file|replace",
52
- hooks: [{
53
- type: "command",
54
- command,
55
- timeout: 3e4
56
- }]
57
- }] } })
58
- };
59
- }
60
- /** Cline: an executable `.clinerules/hooks/PreToolUse` that pipes stdin to the command. */
61
- function clineInit(command) {
62
- return {
63
- path: ".clinerules/hooks/PreToolUse",
64
- content: `#!/usr/bin/env bash\n# fuse-harness Cline hook — evaluate each tool use\ncat | ${command}\n`,
65
- executable: true
66
- };
67
- }
68
- //#endregion
69
- //#region src/init/run.ts
70
- const RUNNERS = {
71
- "claude-code": claudeInit,
72
- codex: codexInit,
73
- cursor: cursorInit,
74
- "gemini-cli": geminiInit,
75
- cline: clineInit
76
- };
77
- /**
78
- * Build the wiring file for a harness, or null when it has no hook integration
79
- * (cli-mode harnesses use `harness check` in a pre-commit step instead).
80
- */
81
- function initFor(id, command = `npx harness hook ${id}`) {
82
- const make = RUNNERS[id];
83
- return make ? make(command) : null;
84
- }
85
- /** Write an {@link InitFile} under `root` (creates dirs; `chmod +x` when executable). */
86
- function writeInitFile(root, file) {
87
- const full = join(root, file.path);
88
- mkdirSync(dirname(full), { recursive: true });
89
- writeFileSync(full, file.content);
90
- if (file.executable) chmodSync(full, 493);
91
- return full;
92
- }
93
- //#endregion
94
- export { codexInit as a, clineInit as i, writeInitFile as n, cursorInit as o, claudeInit as r, geminiInit as s, initFor as t };