@fusengine/harness 0.1.39 → 0.1.41
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 +7 -4
- package/dist/adapters/claude/index.d.mts +2 -2
- package/dist/adapters/claude/index.mjs +2 -2
- package/dist/adapters/cline/index.mjs +1 -1
- package/dist/adapters/codex/index.d.mts +1 -1
- package/dist/adapters/codex/index.mjs +1 -1
- package/dist/adapters/cursor/index.mjs +1 -1
- package/dist/adapters/gemini/index.mjs +1 -1
- package/dist/cache/index.d.mts +2 -2
- package/dist/cache/index.mjs +3 -3
- package/dist/cache-la_KkjCS.mjs +1 -0
- package/dist/{claude-ZgTC5yDS.mjs → claude-BnFrSfMo.mjs} +17 -3
- package/dist/cli/bin.mjs +70 -6
- package/dist/cli/index.mjs +1 -1
- package/dist/config/index.d.mts +2 -2
- package/dist/config/index.mjs +3 -2
- package/dist/{doc-helpers-Dd_x1-tZ.mjs → doc-helpers-BhzDmJ18.mjs} +8 -6
- package/dist/{doc-helpers-CG1nuf-c.d.mts → doc-helpers-D14nkD5D.d.mts} +5 -3
- package/dist/{dotenv-TLNMiSjP.mjs → dotenv-DGyLln7U.mjs} +1 -20
- package/dist/{evaluate-za4vT-p7.mjs → evaluate-B1n1ti0N.mjs} +89 -49
- package/dist/freshness/index.d.mts +1 -1
- package/dist/freshness/index.mjs +1 -1
- package/dist/{handle-t-lQuQc7.mjs → handle-VhWQxvyN.mjs} +612 -163
- package/dist/index-BX-xcvhY.d.mts +75 -0
- package/dist/{index-DLUfyYmU.d.mts → index-COBvvc3L.d.mts} +3 -1
- package/dist/{index-FrWgmkbP.d.mts → index-CwOdFBOr.d.mts} +11 -2
- package/dist/{index-CPoF_hLP.d.mts → index-D7GpOmkl.d.mts} +1 -1
- package/dist/{index-QzK2dv0V.d.mts → index-DTrjSNmI.d.mts} +38 -13
- package/dist/{index-DL8MxjuP.d.mts → index-mmRF3KNp.d.mts} +4 -4
- package/dist/index.d.mts +7 -7
- package/dist/index.mjs +9 -9
- package/dist/{loader-CyAoJv2W.mjs → loader-Bn-DbZmt.mjs} +1 -1
- package/dist/mcp-store-CDUVqtJ0.mjs +238 -0
- package/dist/policy/index.d.mts +2 -2
- package/dist/policy/index.mjs +3 -3
- package/dist/refs/index.d.mts +1 -1
- package/dist/refs/index.mjs +2 -2
- package/dist/{router-D8cVrI-s.mjs → router-BfX0hJg8.mjs} +13 -20
- package/dist/{run-DOARDruz.mjs → run-D-Ydrw3D.mjs} +1 -1
- package/dist/runtime/index.d.mts +33 -13
- package/dist/runtime/index.mjs +2 -2
- package/dist/{session-state-DxNkIBZ2.d.mts → session-state-Dzq6yrw7.d.mts} +1 -1
- package/dist/state/index.d.mts +1 -1
- package/dist/{store-7j02oGjt.mjs → store-3WBr37Xz.mjs} +1 -1
- package/dist/tracking/index.d.mts +1 -1
- package/dist/tracking/index.mjs +1 -1
- package/dist/ttl-BG55s6HZ.mjs +20 -0
- package/dist/{validate-osn-huLZ.mjs → validate-B3wkqEoN.mjs} +15 -9
- package/package.json +1 -1
- package/dist/cache-C9z9LclL.mjs +0 -56
- package/dist/index-DPkCX_AR.d.mts +0 -40
- package/dist/store-PrNPm6So.mjs +0 -89
package/README.md
CHANGED
|
@@ -26,7 +26,9 @@ bun add @fusengine/harness # Bun reads the TS source directly — no build
|
|
|
26
26
|
```sh
|
|
27
27
|
cd your-project
|
|
28
28
|
harness init # detects the harness, writes its pre+post hooks
|
|
29
|
-
|
|
29
|
+
# The SOLID-read gate auto-activates from discovered skills (default marketplace: fusengine-plugins).
|
|
30
|
+
export FUSE_HARNESS_MARKETPLACES=fusengine-plugins # (optional) which marketplaces to auto-scan
|
|
31
|
+
export FUSE_HARNESS_REFS=.claude/skills # (optional) explicit refs dir, overrides auto-discovery
|
|
30
32
|
```
|
|
31
33
|
|
|
32
34
|
That's it. `init` writes the wiring file for the detected harness
|
|
@@ -65,8 +67,8 @@ Ten portable guards + the APEX gate chain, all evaluated before a tool runs:
|
|
|
65
67
|
| interface-separation | top-level interface/type/protocol in a component/controller |
|
|
66
68
|
| protected-path | edits to `.claude/plugins\|logs\|cache`, `.git/` |
|
|
67
69
|
| APEX freshness | `explore-codebase` + `research-expert` not run within the window |
|
|
68
|
-
| APEX doc-consulted | Context7
|
|
69
|
-
| APEX solid-read | required SOLID refs (
|
|
70
|
+
| APEX doc-consulted | no doc source (Context7 / Exa / fuse-browser / WebSearch / WebFetch) consulted this session |
|
|
71
|
+
| APEX solid-read | required SOLID refs (auto-discovered, or `FUSE_HARNESS_REFS`) not read |
|
|
70
72
|
| brainstorm | creating a new file without brainstorming (when flagged) |
|
|
71
73
|
| MCP verbosity / cache | caps exa `numResults`; serves a fresh cached MCP/WebFetch result |
|
|
72
74
|
|
|
@@ -78,7 +80,8 @@ through per window without the full APEX gates.
|
|
|
78
80
|
| Var | Effect |
|
|
79
81
|
|---|---|
|
|
80
82
|
| `FUSE_SOLID_MAX_LINES` | SOLID file-size limit (default `100`). |
|
|
81
|
-
| `FUSE_HARNESS_REFS` |
|
|
83
|
+
| `FUSE_HARNESS_REFS` | Explicit `path.delimiter`-list of `.md` SOLID-reference dirs → activates `solidReadGate`. Overrides auto-discovery. |
|
|
84
|
+
| `FUSE_HARNESS_MARKETPLACES` | Comma-list of marketplace names whose `solid-*` skill refs are auto-discovered when `FUSE_HARNESS_REFS` is unset (default `fusengine-plugins`; an absent marketplace contributes nothing). Standalone `.claude`/`.codex`/`.cursor`/`.agents` skills are always scanned. |
|
|
82
85
|
| `FUSE_ENFORCE_TTL_SEC` | APEX freshness window in seconds. |
|
|
83
86
|
| `FUSE_LESSONS_THROTTLE_MIN` | Lessons-injection throttle (memory module). |
|
|
84
87
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
export { ClaudeHookInput, contextResponse, denyResponse, fileSizeGuard, guard, readClaudeInput, toClaudeResponse };
|
|
1
|
+
import { a as fileSizeGuard, c as toClaudeResponse, i as denyResponse, n as blockResponse, o as guard, r as contextResponse, s as readClaudeInput, t as ClaudeHookInput } from "../../index-CwOdFBOr.mjs";
|
|
2
|
+
export { ClaudeHookInput, blockResponse, contextResponse, denyResponse, fileSizeGuard, guard, readClaudeInput, toClaudeResponse };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
export { contextResponse, denyResponse, fileSizeGuard, guard, readClaudeInput, toClaudeResponse };
|
|
1
|
+
import { a as guard, i as fileSizeGuard, n as contextResponse, o as readClaudeInput, r as denyResponse, s as toClaudeResponse, t as blockResponse } from "../../claude-BnFrSfMo.mjs";
|
|
2
|
+
export { blockResponse, contextResponse, denyResponse, fileSizeGuard, guard, readClaudeInput, toClaudeResponse };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as denyResponse, o as guard, r as contextResponse, s as readClaudeInput, t as ClaudeHookInput } from "../../index-CwOdFBOr.mjs";
|
|
2
2
|
export { type ClaudeHookInput as CodexHookInput, contextResponse, denyResponse, guard, readClaudeInput as readCodexInput };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as guard, n as contextResponse, o as readClaudeInput, r as denyResponse } from "../../claude-BnFrSfMo.mjs";
|
|
2
2
|
export { contextResponse, denyResponse, guard, readClaudeInput as readCodexInput };
|
package/dist/cache/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
export { IndexSummary, cacheLookup, cachePath, cacheStore, compactMarkdown, extractText, jaccardSimilar, loadIndex, mcpCacheKey, queryHash, summarizeIndex };
|
|
1
|
+
import { a as mcpCacheKey, c as extractText, d as summarizeIndex, f as compactMarkdown, i as cacheStore, l as IndexSummary, m as queryHash, n as cacheLookupSubstring, o as mcpCacheWrite, p as jaccardSimilar, r as cachePath, s as webfetchCacheWrite, t as cacheLookup, u as loadIndex } from "../index-BX-xcvhY.mjs";
|
|
2
|
+
export { IndexSummary, cacheLookup, cacheLookupSubstring, cachePath, cacheStore, compactMarkdown, extractText, jaccardSimilar, loadIndex, mcpCacheKey, mcpCacheWrite, queryHash, summarizeIndex, webfetchCacheWrite };
|
package/dist/cache/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
export { cacheLookup, cachePath, cacheStore, compactMarkdown, extractText, jaccardSimilar, loadIndex, mcpCacheKey, queryHash, summarizeIndex };
|
|
1
|
+
import { a as cachePath, c as extractText, d as compactMarkdown, f as jaccardSimilar, i as cacheLookupSubstring, l as loadIndex, n as webfetchCacheWrite, o as cacheStore, p as queryHash, r as cacheLookup, s as mcpCacheKey, t as mcpCacheWrite, u as summarizeIndex } from "../mcp-store-CDUVqtJ0.mjs";
|
|
2
|
+
import "../cache-la_KkjCS.mjs";
|
|
3
|
+
export { cacheLookup, cacheLookupSubstring, cachePath, cacheStore, compactMarkdown, extractText, jaccardSimilar, loadIndex, mcpCacheKey, mcpCacheWrite, queryHash, summarizeIndex, webfetchCacheWrite };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as evaluate } from "./evaluate-
|
|
1
|
+
import { t as evaluate } from "./evaluate-B1n1ti0N.mjs";
|
|
2
2
|
import { t as formatPrompt } from "./types-ernB1Dy3.mjs";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
@@ -87,7 +87,7 @@ async function readClaudeInput() {
|
|
|
87
87
|
return {};
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
|
-
/** A `deny` hook response for a given event. */
|
|
90
|
+
/** A `deny` hook response for a given event. PreToolUse-only field. */
|
|
91
91
|
function denyResponse(event, reason) {
|
|
92
92
|
return JSON.stringify({ hookSpecificOutput: {
|
|
93
93
|
hookEventName: event,
|
|
@@ -95,6 +95,20 @@ function denyResponse(event, reason) {
|
|
|
95
95
|
permissionDecisionReason: reason
|
|
96
96
|
} });
|
|
97
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* A PostToolUse (and Stop/UserPromptSubmit) `block` response. These events
|
|
100
|
+
* ignore `hookSpecificOutput.permissionDecision` (a PreToolUse-only field) and
|
|
101
|
+
* only honor the top-level `decision`/`reason` keys, which feed the reason back
|
|
102
|
+
* to Claude as automated feedback.
|
|
103
|
+
* @param reason - The block feedback shown to Claude.
|
|
104
|
+
* @returns The native `{decision:"block",reason}` response string.
|
|
105
|
+
*/
|
|
106
|
+
function blockResponse(reason) {
|
|
107
|
+
return JSON.stringify({
|
|
108
|
+
decision: "block",
|
|
109
|
+
reason
|
|
110
|
+
});
|
|
111
|
+
}
|
|
98
112
|
/** An `additionalContext` injection response. */
|
|
99
113
|
function contextResponse(event, text) {
|
|
100
114
|
return JSON.stringify({ hookSpecificOutput: {
|
|
@@ -134,4 +148,4 @@ function guard(input) {
|
|
|
134
148
|
/** @deprecated use {@link guard}. Kept for back-compat. */
|
|
135
149
|
const fileSizeGuard = guard;
|
|
136
150
|
//#endregion
|
|
137
|
-
export {
|
|
151
|
+
export { guard as a, collectFiles as c, sleep as d, spawnCapture as f, fileSizeGuard as i, pathExists as l, contextResponse as n, readClaudeInput as o, writeText as p, denyResponse as r, toClaudeResponse as s, blockResponse as t, readText as u };
|
package/dist/cli/bin.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { r as resolveTtlSec } from "../ttl-BG55s6HZ.mjs";
|
|
3
|
+
import { r as loadDotenv } from "../dotenv-DGyLln7U.mjs";
|
|
3
4
|
import { t as detectHarness } from "../harness-C8Nxxyn_.mjs";
|
|
4
|
-
import { n as stagedContent, r as stagedFiles, t as checkStaged } from "../run-
|
|
5
|
+
import { n as stagedContent, r as stagedFiles, t as checkStaged } from "../run-D-Ydrw3D.mjs";
|
|
5
6
|
import { n as writeInitFile, t as initFor } from "../run-Do2JltgU.mjs";
|
|
6
|
-
import {
|
|
7
|
-
import { join } from "node:path";
|
|
8
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { Ot as todayUtc, kt as claudeHome, t as handleHook } from "../handle-VhWQxvyN.mjs";
|
|
8
|
+
import { delimiter, join } from "node:path";
|
|
9
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
9
10
|
import { homedir } from "node:os";
|
|
10
11
|
//#region src/changelog/fetch.ts
|
|
11
12
|
/**
|
|
@@ -75,6 +76,67 @@ async function scanChangelog(now = Date.now(), home = homedir()) {
|
|
|
75
76
|
};
|
|
76
77
|
}
|
|
77
78
|
//#endregion
|
|
79
|
+
//#region src/refs/discover.ts
|
|
80
|
+
/** Immediate subdirectory names of `p`, or `[]` when it is missing/inaccessible. */
|
|
81
|
+
function subdirs(p) {
|
|
82
|
+
try {
|
|
83
|
+
return readdirSync(p, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
84
|
+
} catch {
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Every `skills/` parent dir across the Open-Agent-Skills layouts
|
|
90
|
+
* (agentskills.io) of Claude Code, Codex and Cursor: the standalone skill roots
|
|
91
|
+
* (`<base>/{.claude,.codex,.cursor,.agents}/skills` + `/etc/codex/skills`) plus
|
|
92
|
+
* installed-plugin roots (`<plugin>/skills` under the Claude marketplace and the
|
|
93
|
+
* Claude/Codex version caches). Shallow `readdir` only — no deep walk — so it is
|
|
94
|
+
* cheap enough to run on every hook.
|
|
95
|
+
*/
|
|
96
|
+
function skillParents(home, cwd, marketplaces) {
|
|
97
|
+
const out = [];
|
|
98
|
+
for (const base of [cwd, home]) for (const tool of [
|
|
99
|
+
".claude",
|
|
100
|
+
".codex",
|
|
101
|
+
".cursor",
|
|
102
|
+
".agents"
|
|
103
|
+
]) out.push(join(base, tool, "skills"));
|
|
104
|
+
out.push(join("/etc", "codex", "skills"));
|
|
105
|
+
const mktRoot = join(home, ".claude", "plugins", "marketplaces");
|
|
106
|
+
for (const mkt of subdirs(mktRoot)) {
|
|
107
|
+
if (!marketplaces.includes(mkt)) continue;
|
|
108
|
+
const pluginsDir = join(mktRoot, mkt, "plugins");
|
|
109
|
+
for (const plugin of subdirs(pluginsDir)) out.push(join(pluginsDir, plugin, "skills"));
|
|
110
|
+
}
|
|
111
|
+
for (const cacheRoot of [join(home, ".claude", "plugins", "cache"), join(home, ".codex", "plugins", "cache")]) for (const mkt of subdirs(cacheRoot)) {
|
|
112
|
+
if (!marketplaces.includes(mkt)) continue;
|
|
113
|
+
for (const plugin of subdirs(join(cacheRoot, mkt))) for (const ver of subdirs(join(cacheRoot, mkt, plugin))) out.push(join(cacheRoot, mkt, plugin, ver, "skills"));
|
|
114
|
+
}
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Auto-discover SOLID reference dirs (`<skill>/references` for every `solid-*`
|
|
119
|
+
* skill) across the Claude/Codex/Cursor skill layouts — the fallback used when
|
|
120
|
+
* `FUSE_HARNESS_REFS` is unset. Deduped by skill name (first source wins, so a
|
|
121
|
+
* marketplace skill shadows its version-cache copy). Returns a path-delimiter
|
|
122
|
+
* list, or `""` when none is found — with no refs the SOLID-read gate stays off.
|
|
123
|
+
* @param home - User home dir (`os.homedir()`).
|
|
124
|
+
* @param cwd - Current project dir (`process.cwd()`).
|
|
125
|
+
* @param marketplaces - Allowlist of marketplace names to scan (env
|
|
126
|
+
* `FUSE_HARNESS_MARKETPLACES`, default `["fusengine-plugins"]`); a marketplace
|
|
127
|
+
* absent from disk simply contributes nothing. Standalone
|
|
128
|
+
* `.claude`/`.codex`/`.cursor`/`.agents` skill roots are always scanned.
|
|
129
|
+
* @returns A `path.delimiter`-joined list of `references` dirs, or `""`.
|
|
130
|
+
*/
|
|
131
|
+
function discoverRefs(home, cwd, marketplaces) {
|
|
132
|
+
const bySkill = /* @__PURE__ */ new Map();
|
|
133
|
+
for (const parent of skillParents(home, cwd, marketplaces)) for (const skill of subdirs(parent)) {
|
|
134
|
+
if (!skill.startsWith("solid-") || bySkill.has(skill)) continue;
|
|
135
|
+
if (subdirs(join(parent, skill)).includes("references")) bySkill.set(skill, join(parent, skill, "references"));
|
|
136
|
+
}
|
|
137
|
+
return [...bySkill.values()].join(delimiter);
|
|
138
|
+
}
|
|
139
|
+
//#endregion
|
|
78
140
|
//#region src/cli/bin.ts
|
|
79
141
|
/**
|
|
80
142
|
* harness — CLI for @fusengine/harness.
|
|
@@ -111,10 +173,12 @@ if (cmd === "hook") {
|
|
|
111
173
|
"seo",
|
|
112
174
|
"memory"
|
|
113
175
|
])).has(scopeArg) ? scopeArg : "core";
|
|
176
|
+
const marketplaces = (process.env.FUSE_HARNESS_MARKETPLACES ?? "fusengine-plugins").split(",").map((s) => s.trim()).filter(Boolean);
|
|
177
|
+
const refsDir = process.env.FUSE_HARNESS_REFS || discoverRefs(homedir(), process.cwd(), marketplaces) || void 0;
|
|
114
178
|
const outcome = await handleHook(id, await readStdin(), {
|
|
115
179
|
now: Date.now(),
|
|
116
180
|
cwd: process.cwd(),
|
|
117
|
-
refsDir
|
|
181
|
+
refsDir,
|
|
118
182
|
windowMs: resolveTtlSec(process.env) * 1e3,
|
|
119
183
|
scope
|
|
120
184
|
});
|
package/dist/cli/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as stagedContent, r as stagedFiles, t as checkStaged } from "../run-
|
|
1
|
+
import { n as stagedContent, r as stagedFiles, t as checkStaged } from "../run-D-Ydrw3D.mjs";
|
|
2
2
|
export { checkStaged, stagedContent, stagedFiles };
|
package/dist/config/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
export { DEFAULT_MAX_LINES, DEFAULT_TTL_SEC, MAX_LINES_ENV_KEY, ProjectLayout, STATE_GITIGNORE, STATE_ROOT, TTL_ENV_KEY, envCandidates, loadDotenv, parseEnvFile, parseEnvInt, projectLayout, resolveMaxLines, resolveTtlSec, splitTarget, ttlLabel };
|
|
1
|
+
import { _ as parseEnvInt, a as ProjectLayout, c as projectLayout, d as resolveMaxLines, f as splitTarget, g as ttlLabel, h as resolveTtlSec, i as parseEnvFile, l as DEFAULT_MAX_LINES, m as TTL_ENV_KEY, n as envCandidates, o as STATE_GITIGNORE, p as DEFAULT_TTL_SEC, r as loadDotenv, s as STATE_ROOT, t as HOME_DIR, u as MAX_LINES_ENV_KEY } from "../index-COBvvc3L.mjs";
|
|
2
|
+
export { DEFAULT_MAX_LINES, DEFAULT_TTL_SEC, HOME_DIR, MAX_LINES_ENV_KEY, ProjectLayout, STATE_GITIGNORE, STATE_ROOT, TTL_ENV_KEY, envCandidates, loadDotenv, parseEnvFile, parseEnvInt, projectLayout, resolveMaxLines, resolveTtlSec, splitTarget, ttlLabel };
|
package/dist/config/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { a as parseEnvInt, i as splitTarget, n as MAX_LINES_ENV_KEY, r as resolveMaxLines, t as DEFAULT_MAX_LINES } from "../limits-CHn8AIL1.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { i as ttlLabel, n as TTL_ENV_KEY, r as resolveTtlSec, t as DEFAULT_TTL_SEC } from "../ttl-BG55s6HZ.mjs";
|
|
3
3
|
import { n as STATE_ROOT, r as projectLayout, t as STATE_GITIGNORE } from "../layout-C0jaaCQC.mjs";
|
|
4
|
+
import { i as parseEnvFile, n as envCandidates, r as loadDotenv, t as HOME_DIR } from "../dotenv-DGyLln7U.mjs";
|
|
4
5
|
import "../config-la_KkjCS.mjs";
|
|
5
|
-
export { DEFAULT_MAX_LINES, DEFAULT_TTL_SEC, MAX_LINES_ENV_KEY, STATE_GITIGNORE, STATE_ROOT, TTL_ENV_KEY, envCandidates, loadDotenv, parseEnvFile, parseEnvInt, projectLayout, resolveMaxLines, resolveTtlSec, splitTarget, ttlLabel };
|
|
6
|
+
export { DEFAULT_MAX_LINES, DEFAULT_TTL_SEC, HOME_DIR, MAX_LINES_ENV_KEY, STATE_GITIGNORE, STATE_ROOT, TTL_ENV_KEY, envCandidates, loadDotenv, parseEnvFile, parseEnvInt, projectLayout, resolveMaxLines, resolveTtlSec, splitTarget, ttlLabel };
|
|
@@ -11,20 +11,22 @@ function sessionAuthsFor(authorizations, sessionId) {
|
|
|
11
11
|
function evaluateDoc(auths) {
|
|
12
12
|
const sources = auths.flatMap((a) => a.sources ?? [a.source ?? ""]);
|
|
13
13
|
const readPaths = auths.flatMap((a) => a.read_paths ?? []);
|
|
14
|
-
const liveC7 = sources.some((s) => /context7
|
|
15
|
-
const liveExa = sources.some((s) => /exa
|
|
14
|
+
const liveC7 = sources.some((s) => /context7/i.test(s));
|
|
15
|
+
const liveExa = sources.some((s) => /exa/i.test(s));
|
|
16
|
+
const liveWeb = sources.some((s) => /web(search|fetch)|fuse-browser/i.test(s));
|
|
16
17
|
const cacheC7 = readPaths.some((p) => /\/context\/mcp\/context7-/.test(p));
|
|
17
18
|
const cacheExa = readPaths.some((p) => /\/context\/mcp\/(exa-search|exa-code-context)-/.test(p));
|
|
18
19
|
return {
|
|
19
20
|
context7: liveC7 || cacheC7,
|
|
20
21
|
exa: liveExa || cacheExa,
|
|
22
|
+
web: liveWeb,
|
|
21
23
|
viaCache: !liveC7 && cacheC7 || !liveExa && cacheExa
|
|
22
24
|
};
|
|
23
25
|
}
|
|
24
|
-
/** True when
|
|
26
|
+
/** True when ANY documentation source (Context7, Exa, or web) was satisfied for the session. */
|
|
25
27
|
function isDocConsulted(authorizations, sessionId) {
|
|
26
28
|
const s = evaluateDoc(sessionAuthsFor(authorizations, sessionId));
|
|
27
|
-
return s.context7
|
|
29
|
+
return s.context7 || s.exa || s.web;
|
|
28
30
|
}
|
|
29
31
|
/** Report how each doc source was satisfied for a session. */
|
|
30
32
|
function formatDocSatisfactionStatus(authorizations, sessionId) {
|
|
@@ -34,8 +36,8 @@ function formatDocSatisfactionStatus(authorizations, sessionId) {
|
|
|
34
36
|
function formatDocDeny(framework) {
|
|
35
37
|
return [
|
|
36
38
|
`APEX: Online documentation not consulted for ${framework}!`,
|
|
37
|
-
"Use
|
|
38
|
-
"This check is once per session — after consulting
|
|
39
|
+
"Use ANY ONE of: mcp__context7__query-docs, mcp__exa__web_search_exa (or — if Exa is down — mcp__fuse-browser__browser_fetch / browser_crawl / browser_serp_batch), WebSearch, or WebFetch.",
|
|
40
|
+
"This check is once per session — after consulting one source, Write/Edit will be allowed."
|
|
39
41
|
].join("\n");
|
|
40
42
|
}
|
|
41
43
|
//#endregion
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
//#region src/freshness/doc-helpers.d.ts
|
|
2
2
|
/**
|
|
3
|
-
* Documentation-consultation freshness: was
|
|
4
|
-
* session
|
|
3
|
+
* Documentation-consultation freshness: was ANY documentation source consulted
|
|
4
|
+
* this session — Context7, Exa, or WebSearch/WebFetch — via a live call or a
|
|
5
|
+
* Read on a cached MCP result file?
|
|
5
6
|
*/
|
|
6
7
|
/** Authorization entry from APEX state (legacy session + new sessions[]). */
|
|
7
8
|
interface AuthEntry {
|
|
@@ -18,9 +19,10 @@ declare function resolveSessions(auth: AuthEntry | undefined): string[];
|
|
|
18
19
|
interface DocSatisfactionStatus {
|
|
19
20
|
context7: boolean;
|
|
20
21
|
exa: boolean;
|
|
22
|
+
web: boolean;
|
|
21
23
|
viaCache: boolean;
|
|
22
24
|
}
|
|
23
|
-
/** True when
|
|
25
|
+
/** True when ANY documentation source (Context7, Exa, or web) was satisfied for the session. */
|
|
24
26
|
declare function isDocConsulted(authorizations: Record<string, AuthEntry> | undefined, sessionId: string): boolean;
|
|
25
27
|
/** Report how each doc source was satisfied for a session. */
|
|
26
28
|
declare function formatDocSatisfactionStatus(authorizations: Record<string, AuthEntry> | undefined, sessionId: string): DocSatisfactionStatus;
|
|
@@ -1,25 +1,6 @@
|
|
|
1
|
-
import { a as parseEnvInt } from "./limits-CHn8AIL1.mjs";
|
|
2
1
|
import { join } from "node:path";
|
|
3
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
4
3
|
import { homedir } from "node:os";
|
|
5
|
-
//#region src/config/ttl.ts
|
|
6
|
-
/** Default enforcement-freshness window, in seconds (2 minutes). */
|
|
7
|
-
const DEFAULT_TTL_SEC = 120;
|
|
8
|
-
/** Default env var name carrying the TTL override. */
|
|
9
|
-
const TTL_ENV_KEY = "FUSE_ENFORCE_TTL_SEC";
|
|
10
|
-
/**
|
|
11
|
-
* Resolve the enforcement TTL (seconds) from an env map.
|
|
12
|
-
* @param env - environment map (defaults to `process.env`)
|
|
13
|
-
* @param key - env var name (defaults to `FUSE_ENFORCE_TTL_SEC`)
|
|
14
|
-
*/
|
|
15
|
-
function resolveTtlSec(env = process.env, key = TTL_ENV_KEY) {
|
|
16
|
-
return parseEnvInt(env[key], 120);
|
|
17
|
-
}
|
|
18
|
-
/** Human label for a TTL: 120 -> "2min", 240 -> "4min", 90 -> "90s". */
|
|
19
|
-
function ttlLabel(sec) {
|
|
20
|
-
return sec % 60 === 0 ? `${sec / 60}min` : `${sec}s`;
|
|
21
|
-
}
|
|
22
|
-
//#endregion
|
|
23
4
|
//#region src/config/dotenv.ts
|
|
24
5
|
/**
|
|
25
6
|
* Native .env loader. Ports the claude-plugins `services/env-file.ts`
|
|
@@ -71,4 +52,4 @@ function loadDotenv(id, env = process.env, home = homedir(), cwd = process.cwd()
|
|
|
71
52
|
for (const path of envCandidates(id, home, cwd)) for (const [k, v] of Object.entries(parseEnvFile(path))) if (env[k] === void 0) env[k] = v;
|
|
72
53
|
}
|
|
73
54
|
//#endregion
|
|
74
|
-
export {
|
|
55
|
+
export { parseEnvFile as i, envCandidates as n, loadDotenv as r, HOME_DIR as t };
|
|
@@ -23,20 +23,14 @@ function detectFramework(filePath, content) {
|
|
|
23
23
|
//#endregion
|
|
24
24
|
//#region src/policy/file-size.ts
|
|
25
25
|
/**
|
|
26
|
-
* Count
|
|
27
|
-
* (
|
|
28
|
-
*
|
|
29
|
-
*
|
|
26
|
+
* Count physical lines — parity with the Python `enforce-file-size.py`
|
|
27
|
+
* (`sum(1 for _ in f)`): every line counts (blanks and comments included), and a
|
|
28
|
+
* single trailing newline does not add a phantom line. The SOLID ceiling is
|
|
29
|
+
* measured on raw file length, not substantive code, to match the upstream plugin.
|
|
30
30
|
*/
|
|
31
31
|
function countLines(content) {
|
|
32
32
|
if (content === "") return 0;
|
|
33
|
-
|
|
34
|
-
for (const line of content.split("\n")) {
|
|
35
|
-
const s = line.trim();
|
|
36
|
-
if (!s || s.startsWith("//") || s.startsWith("*") || s.startsWith("/*")) continue;
|
|
37
|
-
n++;
|
|
38
|
-
}
|
|
39
|
-
return n;
|
|
33
|
+
return content.split("\n").length - (content.endsWith("\n") ? 1 : 0);
|
|
40
34
|
}
|
|
41
35
|
/**
|
|
42
36
|
* Evaluate a file's line count against the SOLID limit.
|
|
@@ -135,21 +129,21 @@ const CRITICAL_PATTERNS = [
|
|
|
135
129
|
/\bfdisk\b/,
|
|
136
130
|
/\bdiskutil\s+(?:erase|partitionDisk)/i,
|
|
137
131
|
/(?:>|>>)\s*\/dev\/(?:sda|hda|nvme)/,
|
|
138
|
-
/\brm\s+(?:-[a-z]*\s+)*-[a-z]*[rf][a-z]*\s+(?:-[a-z]+\s+)*\/(?:etc|usr|var|bin|sbin|boot|lib)\b
|
|
132
|
+
/\brm\s+(?:-[a-z]*\s+)*-[a-z]*[rf][a-z]*\s+(?:-[a-z]+\s+)*\/(?:etc|usr|var|bin|sbin|boot|lib)\b/,
|
|
133
|
+
/(?:^|[\s;|&])sudo(?:[\s;|&]|$)/,
|
|
134
|
+
/(?:^|[\s;|&])su(?:[\s;|&]|$)/,
|
|
135
|
+
/(?:^|[\s;|&])doas(?:[\s;|&]|$)/,
|
|
136
|
+
/(?:^|[\s;|&])passwd(?:[\s;|&]|$)/,
|
|
137
|
+
/(?:^|[\s;|&])del(?:[\s;|&]|$)/
|
|
139
138
|
];
|
|
140
139
|
/** Patterns that warrant explicit confirmation before running. */
|
|
141
140
|
const ASK_PATTERNS = [
|
|
142
|
-
/\bsudo\s/,
|
|
143
141
|
/\bchmod\s+(?:-[a-zA-Z]+\s+)*777\b/,
|
|
144
142
|
/\bchown\s+-R\b/,
|
|
145
143
|
/\beval\s/,
|
|
146
144
|
/(?:>|>>|\btee\b)\s*\/etc\//,
|
|
147
|
-
/\bsu\s/,
|
|
148
|
-
/\bdoas\s/,
|
|
149
|
-
/\bpasswd\b/,
|
|
150
145
|
/\brm\s/,
|
|
151
|
-
/\bunlink\s
|
|
152
|
-
/\bdel\s/
|
|
146
|
+
/\bunlink\s/
|
|
153
147
|
];
|
|
154
148
|
/**
|
|
155
149
|
* Strip heredoc bodies so their content isn't matched as a command (false positives).
|
|
@@ -179,7 +173,7 @@ function securityGuard(ctx) {
|
|
|
179
173
|
for (const re of CRITICAL_PATTERNS) if (re.test(cmd)) return {
|
|
180
174
|
kind: "block",
|
|
181
175
|
title: "Dangerous command",
|
|
182
|
-
reason: "Command matches a destructive pattern (recursive root delete, fork bomb, remote-script piped to a shell, world-writable root, filesystem format, or
|
|
176
|
+
reason: "Command matches a destructive pattern (recursive root delete, fork bomb, remote-script piped to a shell, world-writable root, filesystem format, disk overwrite, or privilege escalation: sudo/su/doas/passwd).",
|
|
183
177
|
actions: ["Remove the destructive command", "Scope the operation to a specific safe path"]
|
|
184
178
|
};
|
|
185
179
|
for (const re of ASK_PATTERNS) if (re.test(cmd)) return {
|
|
@@ -192,17 +186,28 @@ function securityGuard(ctx) {
|
|
|
192
186
|
}
|
|
193
187
|
//#endregion
|
|
194
188
|
//#region src/policy/guards/protected-path.ts
|
|
195
|
-
/**
|
|
189
|
+
/**
|
|
190
|
+
* Path fragments that mark a location as internal/generated state.
|
|
191
|
+
*
|
|
192
|
+
* Parity with safe_paths.py: `~/.claude/fusengine-cache` is a *writable* cache
|
|
193
|
+
* the harness owns (lessons, MCP cache, per-type state) — only the
|
|
194
|
+
* `fusengine-cache/sessions` subtree is protected, not the whole cache.
|
|
195
|
+
*/
|
|
196
196
|
const PROTECTED_FRAGMENTS = [
|
|
197
197
|
".claude/plugins/marketplaces",
|
|
198
198
|
".claude/plugins/cache",
|
|
199
199
|
".claude/logs/00-apex",
|
|
200
|
-
".claude/fusengine-cache",
|
|
201
|
-
".git/",
|
|
200
|
+
".claude/fusengine-cache/sessions",
|
|
202
201
|
".claude/apex/",
|
|
203
202
|
".harness/track",
|
|
204
203
|
".harness/memory/state"
|
|
205
204
|
];
|
|
205
|
+
/**
|
|
206
|
+
* Matches a real `.git` directory segment (`/.git/`, `~/.git`, leading or
|
|
207
|
+
* trailing `.git`) without matching unrelated names like `foo.git/` or
|
|
208
|
+
* `.github/`. Kept separate from the substring fragments for precise scoping.
|
|
209
|
+
*/
|
|
210
|
+
const PROTECTED_GIT_RE = /(?:^|[/~\s])\.git(?:\/|$)/;
|
|
206
211
|
/** Standard block response for any protected-path violation. */
|
|
207
212
|
const BLOCK = {
|
|
208
213
|
kind: "block",
|
|
@@ -210,29 +215,48 @@ const BLOCK = {
|
|
|
210
215
|
reason: "This is internal/generated enforcement state — do not edit it directly.",
|
|
211
216
|
actions: ["Edit the source, not the generated/cache/state copy"]
|
|
212
217
|
};
|
|
213
|
-
/** Returns true if `str`
|
|
218
|
+
/** Returns true if `str` references a protected fragment or a real `.git` segment. */
|
|
214
219
|
function containsProtected(str) {
|
|
215
|
-
return PROTECTED_FRAGMENTS.some((f) => str.includes(f));
|
|
220
|
+
return PROTECTED_FRAGMENTS.some((f) => str.includes(f)) || PROTECTED_GIT_RE.test(str);
|
|
221
|
+
}
|
|
222
|
+
/** Strips surrounding quotes from a captured shell token. */
|
|
223
|
+
function unquote(t) {
|
|
224
|
+
return t.replace(/^['"]|['"]$/g, "");
|
|
216
225
|
}
|
|
217
226
|
/**
|
|
218
|
-
*
|
|
227
|
+
* Extracts the genuine write *targets* of a shell command, so a protected path
|
|
228
|
+
* appearing only as a *read source* (e.g. `grep x .claude/apex/ > out.txt`,
|
|
229
|
+
* `cat .git/config > /dev/null`) is not mistaken for a write.
|
|
219
230
|
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
231
|
+
* Covers `>` / `>>` redirects (skipping `2>`, `&>`, `1>` fd-redirects and
|
|
232
|
+
* `/dev/null`), `tee`, `dd of=`, and the destination / in-place file of
|
|
233
|
+
* `cp` / `mv` / `sed -i` / `perl -i` (last path token of the segment).
|
|
234
|
+
* Best-effort: obfuscated shell (base64, indirection, process substitution)
|
|
235
|
+
* can still evade this — residual risk, mitigated by the freshness gate.
|
|
225
236
|
*/
|
|
226
|
-
function
|
|
227
|
-
|
|
237
|
+
function extractWriteTargets(cmd) {
|
|
238
|
+
const out = [];
|
|
239
|
+
const push = (t) => {
|
|
240
|
+
const v = unquote((t ?? "").trim());
|
|
241
|
+
if (v && v !== "/dev/null") out.push(v);
|
|
242
|
+
};
|
|
243
|
+
for (const m of cmd.matchAll(/(?<![2&\d])>{1,2}\s*('[^']+'|"[^"]+"|\S+)/g)) push(m[1]);
|
|
244
|
+
for (const m of cmd.matchAll(/\btee\b(?:\s+-\S+)*\s+('[^']+'|"[^"]+"|\S+)/g)) push(m[1]);
|
|
245
|
+
for (const m of cmd.matchAll(/\bdd\b[^|;&]*\bof=('[^']+'|"[^"]+"|\S+)/g)) push(m[1]);
|
|
246
|
+
for (const seg of cmd.split(/[;&|]+/)) {
|
|
247
|
+
if (!/\b(?:cp|mv)\b|\b(?:sed|perl)\b[^|;&]*\s-i/.test(seg)) continue;
|
|
248
|
+
const toks = (seg.split(/[<>]/)[0] ?? "").trim().split(/\s+/).filter((t) => Boolean(t) && !t.startsWith("-"));
|
|
249
|
+
push(toks[toks.length - 1]);
|
|
250
|
+
}
|
|
251
|
+
return out;
|
|
228
252
|
}
|
|
229
253
|
/**
|
|
230
254
|
* Blocks direct edits to internal/generated state directories.
|
|
231
255
|
*
|
|
232
256
|
* Covers:
|
|
233
257
|
* - Write / Edit tool calls whose `filePath` targets a protected fragment.
|
|
234
|
-
* - Bash commands
|
|
235
|
-
*
|
|
258
|
+
* - Bash commands whose actual write *target* is a protected fragment
|
|
259
|
+
* (read sources are ignored; see `extractWriteTargets`).
|
|
236
260
|
*
|
|
237
261
|
* @param ctx - The guard context (tool, filePath, command).
|
|
238
262
|
* @returns A blocking {@link Prompt}, or null to allow.
|
|
@@ -242,18 +266,25 @@ function protectedPathGuard(ctx) {
|
|
|
242
266
|
if (containsProtected(ctx.filePath)) return BLOCK;
|
|
243
267
|
}
|
|
244
268
|
if (ctx.tool === "Bash" && ctx.command) {
|
|
245
|
-
if (
|
|
269
|
+
if (extractWriteTargets(ctx.command).some(containsProtected)) return BLOCK;
|
|
246
270
|
}
|
|
247
271
|
return null;
|
|
248
272
|
}
|
|
249
273
|
//#endregion
|
|
250
274
|
//#region src/policy/guards/bash-write.ts
|
|
251
275
|
/** Redirect (`>`/`>>`) targeting a code-file extension. */
|
|
252
|
-
const CODE_REDIRECT = /(?:>>?)\s*[^\s|;&]*\.(?:ts|tsx|js|jsx|py|go|rb|rs|java|kt|php|swift|vue|svelte|c|cpp|h)\b/;
|
|
253
|
-
/** Interpreters / tools that mutate source in place, plus heredoc-into-file.
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
276
|
+
const CODE_REDIRECT = /(?:>>?)\s*[^\s|;&]*\.(?:ts|tsx|js|jsx|py|go|rb|rs|java|kt|php|swift|vue|svelte|astro|css|c|cpp|h)\b/;
|
|
277
|
+
/** Interpreters / tools that mutate source in place, plus heredoc-into-file.
|
|
278
|
+
* `sed`/`perl`/`awk` allow intervening flags before `-i` (parity bash-write-guard.py). */
|
|
279
|
+
const CODE_MUTATORS = /\bpython3?\s+-c\b|\bpython3?\s+-\s*<<|\bsed\b[^|]*\s-i|\bperl\b[^|]*\s-[pi]i?\b|\bawk\b[^|]*-i\s*inplace|\bpatch\b|<<[-~]?\s*['"]?\w+['"]?[\s\S]*?>/;
|
|
280
|
+
/** File-mutating one-liners via `node -e` / `ruby -e` (parity NODE_WRITES/RUBY_WRITES). */
|
|
281
|
+
const NODE_WRITES = /writeFile|appendFile|createWriteStream|fs\.(?:write|rename|unlink|mkdir|rmdir|copyFile)|execSync|spawnSync|child_process/;
|
|
282
|
+
const RUBY_WRITES = /File\.(?:write|open|delete|rename)|IO\.write|FileUtils|\bsystem\b|\bexec\b|`[^`]/;
|
|
283
|
+
/** Redirect to a non-code file. Excludes `/dev/null`, `2>`/`N>` and `>&N` fd
|
|
284
|
+
* redirects via the `(?<![0-9&])` lookbehind + `(?!…|&)` (parity has_file_redirect). */
|
|
285
|
+
const FILE_REDIRECT = /(?<![0-9&])\s*>>?\s*(?!\/dev\/null|&)[a-zA-Z./~$]/;
|
|
286
|
+
/** Other ambiguous file writers (ASK): `tee <file>` (not `tee -a`/path) and `dd … of=`. */
|
|
287
|
+
const ASK_WRITERS = /\btee\s+[^-/\s]|\bdd\b[^|]*\bof=/;
|
|
257
288
|
/**
|
|
258
289
|
* Blocks shell commands that mutate code files in place (and heredocs/redirects
|
|
259
290
|
* to source files); asks before other file-writing shell commands. Forces use
|
|
@@ -268,7 +299,7 @@ function bashWriteGuard(ctx) {
|
|
|
268
299
|
reason: "Shell in-place edits / redirects to source files bypass APEX/SOLID checks.",
|
|
269
300
|
actions: ["Use the Write/Edit tool instead"]
|
|
270
301
|
};
|
|
271
|
-
if (ASK_WRITERS.test(cmd)) return {
|
|
302
|
+
if (FILE_REDIRECT.test(cmd) || ASK_WRITERS.test(cmd) || /\bnode\s+-e\b/.test(cmd) && NODE_WRITES.test(cmd) || /\bruby\s+-e\b/.test(cmd) && RUBY_WRITES.test(cmd)) return {
|
|
272
303
|
kind: "ask",
|
|
273
304
|
title: "Bash file write",
|
|
274
305
|
reason: "This command writes a file from the shell; confirm it is intended.",
|
|
@@ -282,8 +313,12 @@ function bashWriteGuard(ctx) {
|
|
|
282
313
|
const TS_DECL_RE = /^\s*(export\s+)?(interface|type)\s+[A-Z]/m;
|
|
283
314
|
/** Python view models: class subclassing a schema/protocol base. */
|
|
284
315
|
const PY_MODEL_RE = /^\s*class\s+\w+\((BaseModel|TypedDict|Protocol)\)/m;
|
|
285
|
-
/**
|
|
286
|
-
|
|
316
|
+
/**
|
|
317
|
+
* PHP controllers: top-level `interface`, `abstract class`, or a concrete
|
|
318
|
+
* `class …Interface/DTO/Request`. Union of the TS-only `abstract class` rule
|
|
319
|
+
* and the Python rule (`class [A-Z].*(Interface|DTO|Request)`, enforce-interfaces.py:16).
|
|
320
|
+
*/
|
|
321
|
+
const PHP_DECL_RE = /^\s*(?:abstract\s+class\b|interface\b|class\s+[A-Z].*(?:Interface|DTO|Request))/m;
|
|
287
322
|
/** Swift views: top-level `protocol Foo`. */
|
|
288
323
|
const SWIFT_PROTO_RE = /^\s*protocol\s+[A-Z]/m;
|
|
289
324
|
/** Go handlers/controllers: top-level `type Foo interface`. */
|
|
@@ -294,6 +329,11 @@ const JAVA_DECL_RE = /^\s*(?:public\s+|private\s+|protected\s+|internal\s+)?(?:i
|
|
|
294
329
|
* Blocks top-level interface/type/protocol declarations in component, view or
|
|
295
330
|
* controller files (Interface Segregation). Fires only when BOTH the path
|
|
296
331
|
* category AND the content pattern match.
|
|
332
|
+
*
|
|
333
|
+
* Parity note: enforce-interfaces.py only inspects `Write` (tool_input.content).
|
|
334
|
+
* We deliberately also fire on `Edit` — an in-place edit can introduce the same
|
|
335
|
+
* violation — and the path fragments accept singular *and* plural directory
|
|
336
|
+
* names (`view/` + `views/`), mirroring the Python `s?` regexes.
|
|
297
337
|
*/
|
|
298
338
|
function interfaceSeparationGuard(ctx) {
|
|
299
339
|
if (ctx.tool !== "Write" && ctx.tool !== "Edit") return null;
|
|
@@ -308,11 +348,11 @@ function interfaceSeparationGuard(ctx) {
|
|
|
308
348
|
};
|
|
309
349
|
const inAny = (...frags) => frags.some((f) => path.includes(f));
|
|
310
350
|
if (/\.(tsx|jsx|vue|svelte)$/.test(path) && TS_DECL_RE.test(content)) return block;
|
|
311
|
-
if (/\.py$/.test(path) && inAny("views/", "controllers/", "routes/") && PY_MODEL_RE.test(content)) return block;
|
|
312
|
-
if (/\.go$/.test(path) && inAny("handlers/", "controllers/") && GO_DECL_RE.test(content)) return block;
|
|
313
|
-
if (/\.(java|kt)$/.test(path) && inAny("controllers/", "handlers/") && JAVA_DECL_RE.test(content)) return block;
|
|
314
|
-
if (/\.php$/.test(path) && inAny("Controllers/", "Handlers/") && PHP_DECL_RE.test(content)) return block;
|
|
315
|
-
if (/\.swift$/.test(path) && inAny("Views/", "Components/") && SWIFT_PROTO_RE.test(content)) return block;
|
|
351
|
+
if (/\.py$/.test(path) && inAny("view/", "views/", "controller/", "controllers/", "route/", "routes/") && PY_MODEL_RE.test(content)) return block;
|
|
352
|
+
if (/\.go$/.test(path) && inAny("handler/", "handlers/", "controller/", "controllers/") && GO_DECL_RE.test(content)) return block;
|
|
353
|
+
if (/\.(java|kt)$/.test(path) && inAny("controller/", "controllers/", "handler/", "handlers/") && JAVA_DECL_RE.test(content)) return block;
|
|
354
|
+
if (/\.php$/.test(path) && inAny("Controller/", "Controllers/", "Handler/", "Handlers/") && PHP_DECL_RE.test(content)) return block;
|
|
355
|
+
if (/\.swift$/.test(path) && inAny("View/", "Views/", "Component/", "Components/") && SWIFT_PROTO_RE.test(content)) return block;
|
|
316
356
|
return null;
|
|
317
357
|
}
|
|
318
358
|
//#endregion
|
|
@@ -434,4 +474,4 @@ function evaluate(ctx) {
|
|
|
434
474
|
};
|
|
435
475
|
}
|
|
436
476
|
//#endregion
|
|
437
|
-
export {
|
|
477
|
+
export { matchPatterns as A, ASK_PATTERNS as C, GIT_BLOCKED as D, GIT_ASK as E, evaluateFileSize as M, detectFramework as N, PROJECT_INSTALL as O, protectedPathGuard as S, securityGuard as T, CODE_REDIRECT as _, registerGuard as a, PROTECTED_FRAGMENTS as b, GO_DECL_RE as c, PY_MODEL_RE as d, SWIFT_PROTO_RE as f, CODE_MUTATORS as g, ASK_WRITERS as h, clearUserGuards as i, countLines as j, SYSTEM_INSTALL as k, JAVA_DECL_RE as l, interfaceSeparationGuard as m, FAIL_CLOSED as n, runGuards as o, TS_DECL_RE as p, GUARDS as r, installGuard as s, evaluate as t, PHP_DECL_RE as u, FILE_REDIRECT as v, CRITICAL_PATTERNS as w, PROTECTED_GIT_RE as x, bashWriteGuard as y };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as isDocConsulted, i as formatDocSatisfactionStatus, n as DocSatisfactionStatus, o as resolveSessions, r as formatDocDeny, t as AuthEntry } from "../doc-helpers-
|
|
1
|
+
import { a as isDocConsulted, i as formatDocSatisfactionStatus, n as DocSatisfactionStatus, o as resolveSessions, r as formatDocDeny, t as AuthEntry } from "../doc-helpers-D14nkD5D.mjs";
|
|
2
2
|
import { t as incrementTrivialEditCounter } from "../index-BOBXQ91y.mjs";
|
|
3
3
|
export { AuthEntry, DocSatisfactionStatus, formatDocDeny, formatDocSatisfactionStatus, incrementTrivialEditCounter, isDocConsulted, resolveSessions };
|