@fusengine/harness 0.1.28 → 0.1.29
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/adapters/claude/index.mjs +1 -1
- package/dist/adapters/cline/index.mjs +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/{claude-phC5Uh_W.mjs → claude-B9FYp0Yw.mjs} +1 -1
- package/dist/cli/bin.mjs +2 -2
- package/dist/cli/index.mjs +1 -1
- package/dist/{evaluate-CFYPF3re.mjs → evaluate-j3gRJ_ng.mjs} +14 -2
- package/dist/handle-nu3GYVek.mjs +1090 -0
- package/dist/{index-DNAzITvw.d.mts → index-BqdCjaT9.d.mts} +71 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +3 -4
- package/dist/policy/index.d.mts +2 -2
- package/dist/policy/index.mjs +4 -4
- package/dist/policy-la_KkjCS.mjs +1 -0
- package/dist/{run-B8n-H5hA.mjs → run-CXsV-wIJ.mjs} +1 -1
- package/dist/runtime/index.d.mts +33 -8
- package/dist/runtime/index.mjs +2 -2
- package/dist/skill-triggers-BZxov1es.mjs +676 -0
- package/package.json +1 -1
- package/dist/handle-DnOw05K8.mjs +0 -347
- package/dist/policy-EuVJ_5hS.mjs +0 -33
- package/dist/verbosity-CXpf3aQQ.mjs +0 -98
|
@@ -0,0 +1,676 @@
|
|
|
1
|
+
import { r as resolveMaxLines } from "./limits-CHn8AIL1.mjs";
|
|
2
|
+
import { k as countLines } from "./evaluate-j3gRJ_ng.mjs";
|
|
3
|
+
import { r as isDocConsulted, t as formatDocDeny } from "./doc-helpers-Dd_x1-tZ.mjs";
|
|
4
|
+
import { t as routeReferences } from "./router-D8cVrI-s.mjs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { existsSync } from "node:fs";
|
|
7
|
+
//#region src/policy/detect-project.ts
|
|
8
|
+
/** Keywords that signal a development task (APEX trigger). */
|
|
9
|
+
const DEV_KEYWORDS = /\b(implement|create|build|fix|add|refactor|develop|feature|bug|update|modify|change|write|code)\b/i;
|
|
10
|
+
/** True when the prompt invokes the /apex command. */
|
|
11
|
+
function isApexCommand(prompt) {
|
|
12
|
+
return /(?:^|\s)\/apex|\/fuse-ai-pilot:apex/i.test(prompt);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Detect a project-internal modular architecture (a sub-architecture the
|
|
16
|
+
* framework-level {@link detectProjectType} doesn't capture): Fusengine's
|
|
17
|
+
* FuseCore (Laravel) or a `modules/`-based Next.js layout.
|
|
18
|
+
*/
|
|
19
|
+
function detectModularArchitecture(dir) {
|
|
20
|
+
const has = (f) => existsSync(join(dir, f));
|
|
21
|
+
if (has("FuseCore") && has("artisan")) return "fusecore";
|
|
22
|
+
if (has("modules") && (has("next.config.js") || has("next.config.ts") || has("next.config.mjs"))) return "nextjs-modular";
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Resolve the skill a detected modular architecture forces.
|
|
27
|
+
*
|
|
28
|
+
* Ports the Python `check-nextjs-skill.py` / `check-laravel-skill.py` gates:
|
|
29
|
+
* when the project is detected on disk as a modular architecture, a specific
|
|
30
|
+
* skill is required ('solid-nextjs' for nextjs-modular, 'fusecore' for
|
|
31
|
+
* fusecore). Returns `null` when no modular architecture is detected.
|
|
32
|
+
*
|
|
33
|
+
* @param cwd - Project root directory to scan.
|
|
34
|
+
* @returns The forced skill name, or `null` when none applies.
|
|
35
|
+
*/
|
|
36
|
+
function requiredArchSkill(cwd) {
|
|
37
|
+
switch (detectModularArchitecture(cwd)) {
|
|
38
|
+
case "nextjs-modular": return "solid-nextjs";
|
|
39
|
+
case "fusecore": return "fusecore";
|
|
40
|
+
default: return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** Detect the project type by scanning config files in `dir`. */
|
|
44
|
+
function detectProjectType(dir) {
|
|
45
|
+
const has = (f) => existsSync(join(dir, f));
|
|
46
|
+
if (has("next.config.js") || has("next.config.ts") || has("next.config.mjs")) return "nextjs";
|
|
47
|
+
if (has("nuxt.config.ts") || has("nuxt.config.js")) return "nuxt";
|
|
48
|
+
if (has("angular.json")) return "angular";
|
|
49
|
+
if (has("svelte.config.js") || has("svelte.config.ts")) return "svelte";
|
|
50
|
+
if (has("vite.config.ts") && has("src/App.vue")) return "vue";
|
|
51
|
+
if (has("vite.config.ts") || has("vite.config.js")) return "react";
|
|
52
|
+
if (has("tailwind.config.js") || has("tailwind.config.ts")) return "tailwind";
|
|
53
|
+
if (has("composer.json") && has("artisan")) return "laravel";
|
|
54
|
+
if (has("Gemfile") && has("config/routes.rb")) return "rails";
|
|
55
|
+
if (has("requirements.txt") || has("pyproject.toml") || has("setup.py")) return has("manage.py") ? "django" : "python";
|
|
56
|
+
if (has("go.mod")) return "go";
|
|
57
|
+
if (has("Cargo.toml")) return "rust";
|
|
58
|
+
if (has("Package.swift")) return "swift";
|
|
59
|
+
if (has("pom.xml") || has("build.gradle") || has("build.gradle.kts")) return "java";
|
|
60
|
+
if (has("build.sbt")) return "scala";
|
|
61
|
+
if (has("mix.exs")) return "elixir";
|
|
62
|
+
if (has("Gemfile")) return "ruby";
|
|
63
|
+
return "generic";
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
//#region src/policy/apex.ts
|
|
67
|
+
/** Gate: Context7 + Exa must have been consulted this session. */
|
|
68
|
+
const docConsultedGate = (ctx) => isDocConsulted(ctx.authorizations, ctx.sessionId) ? null : {
|
|
69
|
+
kind: "block",
|
|
70
|
+
title: "APEX: documentation not consulted",
|
|
71
|
+
reason: formatDocDeny(ctx.framework),
|
|
72
|
+
actions: ["Call mcp__context7__query-docs", "Call mcp__exa__web_search_exa"]
|
|
73
|
+
};
|
|
74
|
+
/** Gate: the routed SOLID references for this edit must have been read. */
|
|
75
|
+
const solidReadGate = (ctx) => {
|
|
76
|
+
if (!ctx.refs?.length) return null;
|
|
77
|
+
const routed = routeReferences(ctx.refs, ctx.filePath, ctx.content);
|
|
78
|
+
if (!routed) return null;
|
|
79
|
+
const read = new Set(ctx.refsRead ?? []);
|
|
80
|
+
const missing = routed.required.map((r) => r.meta.filePath).filter((p) => !read.has(p));
|
|
81
|
+
if (missing.length === 0) return null;
|
|
82
|
+
return {
|
|
83
|
+
kind: "block",
|
|
84
|
+
title: `APEX: read SOLID references for ${ctx.framework}`,
|
|
85
|
+
reason: `Read these before editing ${ctx.filePath}:`,
|
|
86
|
+
actions: missing
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
/** Gate: the required prior agents (explore + research) must have run within the window. */
|
|
90
|
+
const freshnessGate = (ctx) => ctx.agentsFresh === false ? {
|
|
91
|
+
kind: "block",
|
|
92
|
+
title: "APEX: explore + research required",
|
|
93
|
+
reason: `Run explore-codebase and research-expert (within the freshness window) before editing ${ctx.framework}.`,
|
|
94
|
+
actions: ["Launch the explore-codebase agent", "Launch the research-expert agent"]
|
|
95
|
+
} : null;
|
|
96
|
+
/** Gate: brainstorming must precede creating new files when flagged. */
|
|
97
|
+
const brainstormGate = (ctx) => ctx.brainstormRequired && ctx.brainstormFresh === false ? {
|
|
98
|
+
kind: "block",
|
|
99
|
+
title: "APEX: brainstorm first",
|
|
100
|
+
reason: `Creation intent detected — brainstorm before creating new ${ctx.framework} files.`,
|
|
101
|
+
actions: ["Launch the brainstorming agent"]
|
|
102
|
+
} : null;
|
|
103
|
+
/** Default APEX gate chain (brainstorm, freshness, docs, SOLID refs). */
|
|
104
|
+
const APEX_GATES = [
|
|
105
|
+
brainstormGate,
|
|
106
|
+
freshnessGate,
|
|
107
|
+
docConsultedGate,
|
|
108
|
+
solidReadGate
|
|
109
|
+
];
|
|
110
|
+
/**
|
|
111
|
+
* Run the APEX gates (chain-of-responsibility): the first failing gate's prompt
|
|
112
|
+
* wins; null means every gate passed (allow).
|
|
113
|
+
*/
|
|
114
|
+
function evaluateApex(ctx, gates = APEX_GATES) {
|
|
115
|
+
return gates.reduce((hit, gate) => hit ?? gate(ctx), null);
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/policy/creation-intent.ts
|
|
119
|
+
const CREATE_RE = /\b(?:create|implement|add|build|new|feature|component|generate|make|develop|scaffold)\b/i;
|
|
120
|
+
const SKIP_RE = /\b(?:fix|bug|debug|update|refactor|rename|move|delete|remove|commit|push|edit|modify|change)\b/i;
|
|
121
|
+
/**
|
|
122
|
+
* True when a prompt expresses creation intent (a new feature/component) and is
|
|
123
|
+
* not a fix/refactor — the signal that brainstorming should precede creation.
|
|
124
|
+
* The harness calls this on UserPromptSubmit, then `recordBrainstormRequired`.
|
|
125
|
+
*/
|
|
126
|
+
function detectCreationIntent(prompt) {
|
|
127
|
+
return CREATE_RE.test(prompt) && !SKIP_RE.test(prompt);
|
|
128
|
+
}
|
|
129
|
+
//#endregion
|
|
130
|
+
//#region src/policy/verbosity.ts
|
|
131
|
+
/** Exa MCP tools whose result count + token budget are capped. */
|
|
132
|
+
const EXA_TOOLS = /exa__web_search|exa__get_code_context|exa_web_search|exa_get_code_context/i;
|
|
133
|
+
/** Context7 doc tool whose token budget is capped. */
|
|
134
|
+
const CONTEXT7_TOOLS = /context7__query-docs|context7_query-docs|query-docs/i;
|
|
135
|
+
/** Max results an exa MCP call may request. */
|
|
136
|
+
const MAX_EXA_RESULTS = 3;
|
|
137
|
+
/** Max token budget for exa `tokensNum` / context7 `tokens`. */
|
|
138
|
+
const MAX_TOKENS = 2e3;
|
|
139
|
+
/**
|
|
140
|
+
* Cap an MCP call's verbosity — exa `numResults` ≤ 3 (+ `tokensNum` ≤ 2000),
|
|
141
|
+
* Context7 `tokens` ≤ 2000. Returns the capped input (a mutation for the harness
|
|
142
|
+
* to apply) when a change is needed, else null.
|
|
143
|
+
*/
|
|
144
|
+
function capVerbosity(tool, input) {
|
|
145
|
+
const out = { ...input };
|
|
146
|
+
let changed = false;
|
|
147
|
+
const cap = (key, max, force) => {
|
|
148
|
+
const v = out[key];
|
|
149
|
+
if (typeof v === "number" && v > max || force && typeof v !== "number") {
|
|
150
|
+
out[key] = max;
|
|
151
|
+
changed = true;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
if (EXA_TOOLS.test(tool)) {
|
|
155
|
+
cap("numResults", 3, true);
|
|
156
|
+
cap("tokensNum", MAX_TOKENS, false);
|
|
157
|
+
} else if (CONTEXT7_TOOLS.test(tool)) cap("tokens", MAX_TOKENS, false);
|
|
158
|
+
return changed ? out : null;
|
|
159
|
+
}
|
|
160
|
+
//#endregion
|
|
161
|
+
//#region src/policy/framework-solid-exclude.ts
|
|
162
|
+
/** Build-output / dependency paths excluded from React/Next.js SOLID gating. */
|
|
163
|
+
const JS_EXCLUDE_RE = /(node_modules|dist|build|\.next)/;
|
|
164
|
+
/** Vendored dependency paths excluded from Laravel/PHP SOLID gating. */
|
|
165
|
+
const PHP_EXCLUDE_RE = /\/vendor\//;
|
|
166
|
+
/** Derived/build artifact paths excluded from Swift SOLID gating. */
|
|
167
|
+
const SWIFT_EXCLUDE_RE = /(\.build|DerivedData|Pods)/;
|
|
168
|
+
/**
|
|
169
|
+
* Whether a JS/TS (React/Next.js) file path is an excluded build artifact.
|
|
170
|
+
* Matches the Python validators' early-return guard to avoid false positives.
|
|
171
|
+
* @param filePath - absolute path of the file under validation
|
|
172
|
+
*/
|
|
173
|
+
function isExcludedJsPath(filePath) {
|
|
174
|
+
return JS_EXCLUDE_RE.test(filePath);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Whether a PHP (Laravel) file path is a vendored dependency to skip.
|
|
178
|
+
* @param filePath - absolute path of the file under validation
|
|
179
|
+
*/
|
|
180
|
+
function isExcludedPhpPath(filePath) {
|
|
181
|
+
return PHP_EXCLUDE_RE.test(filePath);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Whether a Swift file path is a derived/build artifact to skip.
|
|
185
|
+
* @param filePath - absolute path of the file under validation
|
|
186
|
+
*/
|
|
187
|
+
function isExcludedSwiftPath(filePath) {
|
|
188
|
+
return SWIFT_EXCLUDE_RE.test(filePath);
|
|
189
|
+
}
|
|
190
|
+
//#endregion
|
|
191
|
+
//#region src/policy/framework-solid-gates.ts
|
|
192
|
+
/** Custom hook export (React): `export function/const use[A-Z]`. */
|
|
193
|
+
const HOOK_RE = /^export (function|const) use[A-Z]/m;
|
|
194
|
+
/** Top-level TS interface/type declaration. */
|
|
195
|
+
const TS_DECL_RE = /^(export )?(interface|type) [A-Z]/m;
|
|
196
|
+
/** Client-only React hooks that require the `'use client'` directive. */
|
|
197
|
+
const CLIENT_HOOK_RE = /(useState|useEffect|useRef|onClick|onChange)/;
|
|
198
|
+
/** PHP top-level `interface` declaration. */
|
|
199
|
+
const PHP_INTERFACE_RE = /^interface /m;
|
|
200
|
+
/** Swift top-level `protocol` declaration. */
|
|
201
|
+
const SWIFT_PROTOCOL_RE = /^protocol /m;
|
|
202
|
+
/** Swift type declaration (`class`/`struct`) opening a body. */
|
|
203
|
+
const SWIFT_TYPE_RE = /^(class|struct) [^\n{]* \{/m;
|
|
204
|
+
/** React: line limit, interface separation, custom hooks under `/hooks/`. */
|
|
205
|
+
function reactGate(filePath, content, fileLines) {
|
|
206
|
+
const v = [];
|
|
207
|
+
const max = resolveMaxLines();
|
|
208
|
+
const lines = fileLines ?? countLines(content);
|
|
209
|
+
if (lines > max) v.push(`File has ${lines} lines (limit: ${max}). Split to hooks/, components/, or utils/.`);
|
|
210
|
+
if (filePath.includes("/components/") && TS_DECL_RE.test(content)) v.push("Interface/type in component. Move to src/interfaces/ or src/types/.");
|
|
211
|
+
if (HOOK_RE.test(content) && !filePath.includes("/hooks/")) v.push("Custom hook defined outside hooks/ directory. Move to hooks/.");
|
|
212
|
+
return v;
|
|
213
|
+
}
|
|
214
|
+
/** Next.js: adaptive line limit, interface separation, `'use client'`. */
|
|
215
|
+
function nextGate(filePath, content, fileLines) {
|
|
216
|
+
const v = [];
|
|
217
|
+
const max = /(page|layout|loading|error|not-found)\.(tsx|ts)$/.test(filePath) ? 150 : 100;
|
|
218
|
+
const lines = fileLines ?? countLines(content);
|
|
219
|
+
if (lines > max) v.push(`File has ${lines} lines (limit: ${max}). Split to lib/, hooks/, or components/.`);
|
|
220
|
+
if (/\/(app|components|modules)\//.test(filePath) && !filePath.includes("/interfaces/") && TS_DECL_RE.test(content)) v.push("Interface/type in component. Move to modules/[feature]/src/interfaces/.");
|
|
221
|
+
if (CLIENT_HOOK_RE.test(content)) {
|
|
222
|
+
const head = content.split("\n").slice(0, 5).join("\n");
|
|
223
|
+
if (!head.includes("'use client'") && !head.includes("\"use client\"")) v.push("Client hooks detected but 'use client' directive missing at top.");
|
|
224
|
+
}
|
|
225
|
+
return v;
|
|
226
|
+
}
|
|
227
|
+
/** Laravel/PHP: line limit, interface outside `/Contracts/`, fat controller (>80). */
|
|
228
|
+
function laravelGate(filePath, content, fileLines) {
|
|
229
|
+
const v = [];
|
|
230
|
+
const lines = fileLines ?? countLines(content);
|
|
231
|
+
const max = resolveMaxLines();
|
|
232
|
+
if (lines > max) v.push(`File has ${lines} lines (limit: ${max}). Split using Services, Actions, or Traits.`);
|
|
233
|
+
if (PHP_INTERFACE_RE.test(content) && !filePath.includes("/Contracts/")) v.push("Interface defined outside Contracts/. Move to app/Contracts/ or FuseCore/{Module}/App/Contracts/.");
|
|
234
|
+
if (filePath.includes("/Controllers/") && lines > 80) v.push(`Fat controller (${lines} lines). Extract logic to Services or Actions.`);
|
|
235
|
+
return v;
|
|
236
|
+
}
|
|
237
|
+
/** Swift: adaptive limit, protocol separation, @MainActor, Sendable. */
|
|
238
|
+
function swiftGate(filePath, content, fileLines) {
|
|
239
|
+
const v = [];
|
|
240
|
+
const max = /(View|Screen)\.swift$/.test(filePath) ? 150 : 100;
|
|
241
|
+
const lines = fileLines ?? countLines(content);
|
|
242
|
+
if (lines > max) v.push(`File has ${lines} lines (limit: ${max}). Extract to ViewModels, Services, or subviews.`);
|
|
243
|
+
if (SWIFT_PROTOCOL_RE.test(content) && !filePath.includes("/Protocols/")) v.push("Protocol defined outside Protocols/ directory.");
|
|
244
|
+
if (filePath.endsWith("ViewModel.swift") && !content.includes("@MainActor")) v.push("ViewModel missing @MainActor annotation.");
|
|
245
|
+
if (SWIFT_TYPE_RE.test(content) && content.includes("async ") && !content.includes("Sendable")) v.push("Type uses async but doesn't conform to Sendable.");
|
|
246
|
+
return v;
|
|
247
|
+
}
|
|
248
|
+
//#endregion
|
|
249
|
+
//#region src/policy/framework-solid.ts
|
|
250
|
+
/** Next.js detection: directive, runtime types, or a `next` import. */
|
|
251
|
+
const NEXT_RE = /(use client|use server|NextRequest|NextResponse|from ['"]next)/;
|
|
252
|
+
/** Build a SOLID `block` prompt from one or more violation messages. */
|
|
253
|
+
function block(filePath, violations) {
|
|
254
|
+
return {
|
|
255
|
+
kind: "block",
|
|
256
|
+
title: "SOLID violation",
|
|
257
|
+
reason: `SOLID VIOLATION in ${filePath}: ${violations.join(" ")}`,
|
|
258
|
+
actions: violations
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
/** Run the JS/TS gate (React or Next.js), honoring build-artifact exclusions. */
|
|
262
|
+
function jsViolations(filePath, content, fileLines) {
|
|
263
|
+
if (isExcludedJsPath(filePath)) return [];
|
|
264
|
+
return NEXT_RE.test(content) ? nextGate(filePath, content, fileLines) : reactGate(filePath, content, fileLines);
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Framework-specific SOLID gate. Dispatches by extension/path to the matching
|
|
268
|
+
* validator (React, Next.js, Laravel, Swift) and returns a blocking
|
|
269
|
+
* {@link Prompt} when any BLOCKING rule fires, or `null` when clean. Excluded
|
|
270
|
+
* build/dependency paths (node_modules, dist, build, .next, vendor, .build,
|
|
271
|
+
* DerivedData, Pods) early-return `null` to avoid false positives.
|
|
272
|
+
* @param filePath - absolute path of the file being written/edited
|
|
273
|
+
* @param content - the file (or new) content under validation
|
|
274
|
+
* @param fileLines - full on-disk line count (set on Edit so a partial
|
|
275
|
+
* `new_string` snippet still judges the whole file, mirroring the base
|
|
276
|
+
* file-size guard / Python `get_full_file_content`). Omit on Write.
|
|
277
|
+
*/
|
|
278
|
+
function frameworkSolidGate(filePath, content, fileLines) {
|
|
279
|
+
if (!filePath || !content) return null;
|
|
280
|
+
let violations = [];
|
|
281
|
+
if (filePath.endsWith(".php")) {
|
|
282
|
+
if (isExcludedPhpPath(filePath)) return null;
|
|
283
|
+
violations = laravelGate(filePath, content, fileLines);
|
|
284
|
+
} else if (filePath.endsWith(".swift")) {
|
|
285
|
+
if (isExcludedSwiftPath(filePath)) return null;
|
|
286
|
+
violations = swiftGate(filePath, content, fileLines);
|
|
287
|
+
} else if (/\.(tsx|ts|jsx|js)$/.test(filePath)) violations = jsViolations(filePath, content, fileLines);
|
|
288
|
+
return violations.length ? block(filePath, violations) : null;
|
|
289
|
+
}
|
|
290
|
+
//#endregion
|
|
291
|
+
//#region src/policy/skill-patterns/shadcn.ts
|
|
292
|
+
/**
|
|
293
|
+
* shadcn/ui HTML-to-component detection patterns, ported verbatim from the
|
|
294
|
+
* shared `shadcn_patterns.py` (FORM/OVERLAY/DATA/NAV/LAYOUT/FEEDBACK groups).
|
|
295
|
+
* 32 patterns total (3+6+8+5+5+5). Matched case-insensitively (source `re.IGNORECASE`).
|
|
296
|
+
*/
|
|
297
|
+
/** Forms: Button, Input, Textarea, Select, Checkbox, Radio, Switch, etc. */
|
|
298
|
+
const FORM = [
|
|
299
|
+
"<(button|input|select|textarea|label|option|optgroup)\\b",
|
|
300
|
+
"type=\"(checkbox|radio|range|file)\"",
|
|
301
|
+
"<input[^>]*maxLength=\"[1-2]\""
|
|
302
|
+
];
|
|
303
|
+
/** Overlay: Dialog, AlertDialog, Sheet, Popover, Tooltip, ContextMenu, etc. */
|
|
304
|
+
const OVERLAY = [
|
|
305
|
+
"<dialog\\b",
|
|
306
|
+
"role=\"(dialog|alertdialog)\"",
|
|
307
|
+
"(aria-haspopup|aria-expanded|aria-pressed)=\"",
|
|
308
|
+
"(onContextMenu|onMouseEnter.*onMouseLeave)\\b",
|
|
309
|
+
"\\b(confirm|window\\.confirm)\\(",
|
|
310
|
+
"title=\"[^\"]{2,}\""
|
|
311
|
+
];
|
|
312
|
+
/** Data: Table, Card, Badge, Avatar, Calendar, Chart, Carousel, Pagination. */
|
|
313
|
+
const DATA = [
|
|
314
|
+
"<(table|thead|tbody|tfoot|th|td|tr|caption|colgroup)\\b",
|
|
315
|
+
"<(article|section)\\b[^>]*className",
|
|
316
|
+
"rounded-full[^>]*className|className[^>]*rounded-full",
|
|
317
|
+
"<img\\b[^>]*rounded",
|
|
318
|
+
"(new Date|\\.toLocaleDateString|date-fns|dayjs)\\b",
|
|
319
|
+
"(recharts|chart\\.js|<svg[^>]*viewBox)",
|
|
320
|
+
"(scroll-snap|embla-carousel|useEmbla)\\b",
|
|
321
|
+
"(page=|currentPage|totalPages|pageSize)\\b"
|
|
322
|
+
];
|
|
323
|
+
/** Navigation: Breadcrumb, NavigationMenu, Menubar, Sidebar, Tabs. */
|
|
324
|
+
const NAV = [
|
|
325
|
+
"<(nav|aside)\\b",
|
|
326
|
+
"<(menu|menuitem)\\b",
|
|
327
|
+
"role=\"(menubar|menu|menuitem|tablist|tab|tabpanel)\"",
|
|
328
|
+
"aria-label=\"(breadcrumb|navigation|sidebar)\"",
|
|
329
|
+
"aria-current=\"(page|step)\""
|
|
330
|
+
];
|
|
331
|
+
/** Layout: Accordion, Collapsible, Separator, ScrollArea, AspectRatio. */
|
|
332
|
+
const LAYOUT = [
|
|
333
|
+
"<(hr|details|summary)\\b",
|
|
334
|
+
"role=\"separator\"",
|
|
335
|
+
"overflow-(auto|scroll|y-auto|x-auto)",
|
|
336
|
+
"(aspect-ratio|aspect-video|aspect-square)\\b",
|
|
337
|
+
"(resize|cursor-(col|row)-resize)\\b"
|
|
338
|
+
];
|
|
339
|
+
/** Feedback: Alert, Toast, Progress, Skeleton, Spinner. */
|
|
340
|
+
const FEEDBACK = [
|
|
341
|
+
"role=\"(alert|status|progressbar)\"",
|
|
342
|
+
"aria-live=\"(polite|assertive)\"",
|
|
343
|
+
"<progress\\b",
|
|
344
|
+
"(animate-pulse|animate-spin)\\b",
|
|
345
|
+
"(sonner|react-hot-toast|\\.toast\\()\\b"
|
|
346
|
+
];
|
|
347
|
+
/** All 32 shadcn detection patterns combined. */
|
|
348
|
+
const SHADCN = [
|
|
349
|
+
...FORM,
|
|
350
|
+
...OVERLAY,
|
|
351
|
+
...DATA,
|
|
352
|
+
...NAV,
|
|
353
|
+
...LAYOUT,
|
|
354
|
+
...FEEDBACK
|
|
355
|
+
];
|
|
356
|
+
//#endregion
|
|
357
|
+
//#region src/policy/skill-patterns/react.ts
|
|
358
|
+
/**
|
|
359
|
+
* React skill-trigger patterns, ported verbatim from `react_skill_triggers.py`.
|
|
360
|
+
* Matched case-insensitively (source `re.IGNORECASE`).
|
|
361
|
+
*/
|
|
362
|
+
/** Map of React sub-skill name → triggering code patterns. */
|
|
363
|
+
const REACT_TRIGGERS = {
|
|
364
|
+
"react-19": [
|
|
365
|
+
"\\buse\\b\\s*\\(",
|
|
366
|
+
"useOptimistic\\b",
|
|
367
|
+
"useActionState\\b",
|
|
368
|
+
"useEffectEvent\\b",
|
|
369
|
+
"<Activity\\b",
|
|
370
|
+
"from\\s+['\"]react['\"]"
|
|
371
|
+
],
|
|
372
|
+
"react-tanstack-router": [
|
|
373
|
+
"(createRouter|createRoute|createRootRoute)\\b",
|
|
374
|
+
"(useNavigate|useParams|useSearch|useLoaderData)\\b",
|
|
375
|
+
"from\\s+['\"]@tanstack/(react-router|router)",
|
|
376
|
+
"(routeTree|createFileRoute|createLazyFileRoute)\\b"
|
|
377
|
+
],
|
|
378
|
+
"react-forms": [
|
|
379
|
+
"(useForm|useAppForm|createFormHook|formOptions)\\b",
|
|
380
|
+
"(mergeForm|formApi|FieldApi|FormApi)\\b",
|
|
381
|
+
"form\\.(Field|Subscribe|handleSubmit)\\b",
|
|
382
|
+
"from\\s+['\"]@tanstack/(react-form|zod-form-adapter)"
|
|
383
|
+
],
|
|
384
|
+
"react-state": [
|
|
385
|
+
"(create|createStore)\\(\\s*\\(\\s*set",
|
|
386
|
+
"from\\s+['\"]zustand(/\\w+)?\"",
|
|
387
|
+
"(useShallow|useStore|skipHydration)\\b",
|
|
388
|
+
"(persist|devtools|immer)\\("
|
|
389
|
+
],
|
|
390
|
+
"react-testing": [
|
|
391
|
+
"(render|screen|fireEvent|waitFor)\\b",
|
|
392
|
+
"from\\s+['\"]@testing-library/react",
|
|
393
|
+
"(describe|it|expect|vi\\.|jest\\.)\\b",
|
|
394
|
+
"from\\s+['\"]vitest"
|
|
395
|
+
],
|
|
396
|
+
"react-shadcn": SHADCN,
|
|
397
|
+
"react-i18n": [
|
|
398
|
+
"(useTranslation|Trans)\\b",
|
|
399
|
+
"from\\s+['\"]react-i18next",
|
|
400
|
+
"\\bt\\(\\s*['\"]",
|
|
401
|
+
"i18n\\.(language|changeLanguage)"
|
|
402
|
+
]
|
|
403
|
+
};
|
|
404
|
+
//#endregion
|
|
405
|
+
//#region src/policy/skill-patterns/nextjs.ts
|
|
406
|
+
/**
|
|
407
|
+
* Next.js skill-trigger patterns, ported verbatim from `nextjs_skill_triggers.py`.
|
|
408
|
+
* Matched case-insensitively (source `re.IGNORECASE`).
|
|
409
|
+
*/
|
|
410
|
+
/** Map of Next.js sub-skill name → triggering code patterns. */
|
|
411
|
+
const NEXTJS_TRIGGERS = {
|
|
412
|
+
"better-auth": [
|
|
413
|
+
"(authClient|betterAuth|createAuthClient)\\b",
|
|
414
|
+
"(signIn|signUp|signOut|useSession|getSession)\\b",
|
|
415
|
+
"auth\\.(api|handler)\\b",
|
|
416
|
+
"(prismaAdapter|drizzleAdapter|mongodbAdapter)\\b",
|
|
417
|
+
"(twoFactor|passkey|magicLink|emailOtp|organization)\\b",
|
|
418
|
+
"(apiKey|bearer|jwt|sso|scim|captcha|anonymous)\\b",
|
|
419
|
+
"from\\s+['\"].*better-auth"
|
|
420
|
+
],
|
|
421
|
+
"nextjs-tanstack-form": [
|
|
422
|
+
"(useForm|useAppForm|createFormHook|formOptions)\\b",
|
|
423
|
+
"(mergeForm|formApi|FieldApi|FormApi)\\b",
|
|
424
|
+
"form\\.(Field|Subscribe|handleSubmit)\\b",
|
|
425
|
+
"(zodValidator|onServerValidate)\\b",
|
|
426
|
+
"from\\s+['\"]@tanstack/(react-form|zod-form-adapter)"
|
|
427
|
+
],
|
|
428
|
+
"prisma-7": [
|
|
429
|
+
"(PrismaClient|prismaAdapter)\\b",
|
|
430
|
+
"prisma\\.(\\w+\\.\\w+|\\$\\w+)",
|
|
431
|
+
"(globalForPrisma|\\$transaction|\\$queryRaw|\\$executeRaw)\\b",
|
|
432
|
+
"from\\s+['\"](@prisma|\\..*generated.*prisma)"
|
|
433
|
+
],
|
|
434
|
+
"nextjs-shadcn": SHADCN,
|
|
435
|
+
"nextjs-zustand": [
|
|
436
|
+
"(create|createStore)\\(\\s*\\(\\s*set",
|
|
437
|
+
"from\\s+['\"]zustand(/\\w+)?\"",
|
|
438
|
+
"(useShallow|useStore|skipHydration)\\b",
|
|
439
|
+
"\\.(getState|setState|subscribe)\\(\\)",
|
|
440
|
+
"(persist|devtools|immer)\\("
|
|
441
|
+
],
|
|
442
|
+
"nextjs-i18n": [
|
|
443
|
+
"(useTranslations|useLocale|useMessages|useFormatter)\\b",
|
|
444
|
+
"(getTranslations|getLocale|getMessages|getFormatter)\\b",
|
|
445
|
+
"(NextIntlClientProvider|defineRouting)\\b",
|
|
446
|
+
"from\\s+['\"]next-intl(/\\w+)?\"",
|
|
447
|
+
"\\bt\\(\\s*['\"]"
|
|
448
|
+
]
|
|
449
|
+
};
|
|
450
|
+
//#endregion
|
|
451
|
+
//#region src/policy/skill-patterns/laravel.ts
|
|
452
|
+
/**
|
|
453
|
+
* Laravel skill-trigger patterns, ported verbatim from `laravel_patterns.py`
|
|
454
|
+
* (consumed by `laravel_skill_triggers.py`). Matched case-insensitively.
|
|
455
|
+
*/
|
|
456
|
+
/** Map of Laravel sub-skill name → triggering PHP code patterns. */
|
|
457
|
+
const LARAVEL_TRIGGERS = {
|
|
458
|
+
"fusecore": [
|
|
459
|
+
"FuseCore\\\\[A-Za-z]+\\\\App\\\\",
|
|
460
|
+
"use HasModule\\b",
|
|
461
|
+
"ModuleServiceProvider\\b",
|
|
462
|
+
"ModuleInterface\\b"
|
|
463
|
+
],
|
|
464
|
+
"laravel-eloquent": [
|
|
465
|
+
"(extends Model|HasFactory|belongsTo|hasMany|hasOne|morphTo)\\b",
|
|
466
|
+
"\\$this->belongsToMany|->with\\(|->whereHas\\(",
|
|
467
|
+
"(Eloquent|Model)::(find|where|create|update|all)\\b"
|
|
468
|
+
],
|
|
469
|
+
"laravel-api": [
|
|
470
|
+
"(JsonResource|ResourceCollection|apiResource)\\b",
|
|
471
|
+
"Route::(get|post|put|delete|apiResource)\\(",
|
|
472
|
+
"(response\\(\\)->json|Request \\$request)\\b"
|
|
473
|
+
],
|
|
474
|
+
"laravel-auth": [
|
|
475
|
+
"(Auth::|auth\\(\\)|Sanctum|Passport|Socialite)\\b",
|
|
476
|
+
"(Gate::|Policy|can\\(|authorize)\\b",
|
|
477
|
+
"(middleware\\(['\"]auth|LoginController|RegisterController)\\b"
|
|
478
|
+
],
|
|
479
|
+
"laravel-livewire": [
|
|
480
|
+
"(extends Component|Livewire|wire:|#\\[On)\\b",
|
|
481
|
+
"(mount|render|emit|dispatch)\\(\\)",
|
|
482
|
+
"@livewire|<livewire:"
|
|
483
|
+
],
|
|
484
|
+
"laravel-queues": [
|
|
485
|
+
"(implements ShouldQueue|dispatch\\(|Bus::)\\b",
|
|
486
|
+
"(Queue::|Job|Batch|Chain)\\b",
|
|
487
|
+
"(onQueue|onConnection|tries|backoff)\\b"
|
|
488
|
+
],
|
|
489
|
+
"laravel-billing": ["(Billable|subscription|Cashier)\\b", "(createSubscription|newSubscription|charge)\\("],
|
|
490
|
+
"laravel-stripe-connect": [
|
|
491
|
+
"(StripeConnect|connectAccount|onboardingUrl)\\b",
|
|
492
|
+
"(paymentIntent|transfer|payout|splitPayment)\\b",
|
|
493
|
+
"Stripe\\\\\\\\(Account|Transfer|PaymentIntent)\\b"
|
|
494
|
+
],
|
|
495
|
+
"laravel-testing": [
|
|
496
|
+
"(extends TestCase|RefreshDatabase|WithFaker)\\b",
|
|
497
|
+
"(assertStatus|assertJson|assertSee|assertRedirect)\\(",
|
|
498
|
+
"(factory\\(|Pest|it\\(|test\\(|expect\\()\\b"
|
|
499
|
+
],
|
|
500
|
+
"laravel-migrations": [
|
|
501
|
+
"(Schema::|Blueprint|->table|->create)\\b",
|
|
502
|
+
"(->string|->integer|->boolean|->foreignId|->index)\\(",
|
|
503
|
+
"extends Migration\\b"
|
|
504
|
+
],
|
|
505
|
+
"laravel-blade": [
|
|
506
|
+
"(@extends|@section|@yield|@component|@slot)\\b",
|
|
507
|
+
"(@if|@foreach|@include|@push|@stack)\\b",
|
|
508
|
+
"(Blade::|x-[a-z])\\b"
|
|
509
|
+
],
|
|
510
|
+
"laravel-permission": [
|
|
511
|
+
"(hasRole|givePermissionTo|assignRole|spatie)\\b",
|
|
512
|
+
"(Permission|Role)::(create|findByName)\\b",
|
|
513
|
+
"@can\\b|@role\\b|middleware.*role:"
|
|
514
|
+
],
|
|
515
|
+
"laravel-i18n": ["(__\\(|trans\\(|trans_choice\\(|@lang)\\b", "Lang::|->locale\\(|setLocale\\b"],
|
|
516
|
+
"laravel-vite": ["(@vite|@viteReactRefresh|Vite::)\\b"]
|
|
517
|
+
};
|
|
518
|
+
//#endregion
|
|
519
|
+
//#region src/policy/skill-patterns/swift.ts
|
|
520
|
+
/**
|
|
521
|
+
* Swift / Apple skill-trigger patterns, ported verbatim from
|
|
522
|
+
* `swift_skill_triggers.py`. 9 skills.
|
|
523
|
+
*
|
|
524
|
+
* NOTE: Swift uses case-SENSITIVE matching (source `re.search` WITHOUT
|
|
525
|
+
* `re.IGNORECASE`). The framework name is registered in
|
|
526
|
+
* `CASE_SENSITIVE_FRAMEWORKS` so the gate compiles these without the `i` flag.
|
|
527
|
+
*/
|
|
528
|
+
/** Map of Swift sub-skill name → triggering code patterns (case-sensitive). */
|
|
529
|
+
const SWIFT_TRIGGERS = {
|
|
530
|
+
"swiftui-core": [
|
|
531
|
+
"\\bstruct\\s+\\w+\\s*:\\s*View\\b",
|
|
532
|
+
"@State\\b",
|
|
533
|
+
"@Binding\\b",
|
|
534
|
+
"@Observable\\b",
|
|
535
|
+
"@Environment\\b",
|
|
536
|
+
"NavigationStack\\b",
|
|
537
|
+
"\\.sheet\\b",
|
|
538
|
+
"\\.toolbar\\b",
|
|
539
|
+
"\\.task\\b"
|
|
540
|
+
],
|
|
541
|
+
"swift-core": [
|
|
542
|
+
"\\bactor\\b",
|
|
543
|
+
"\\basync\\s+(let|func|throws)\\b",
|
|
544
|
+
"\\bawait\\b",
|
|
545
|
+
"Task\\s*\\{",
|
|
546
|
+
"TaskGroup\\b",
|
|
547
|
+
"Sendable\\b",
|
|
548
|
+
"@MainActor\\b"
|
|
549
|
+
],
|
|
550
|
+
"ios": [
|
|
551
|
+
"UIKit|UIViewController|UIView\\b",
|
|
552
|
+
"UIApplication\\b",
|
|
553
|
+
"\\.simulatorId\\b",
|
|
554
|
+
"import\\s+UIKit\\b"
|
|
555
|
+
],
|
|
556
|
+
"macos": [
|
|
557
|
+
"AppKit|NSViewController|NSWindow\\b",
|
|
558
|
+
"NSApplication\\b",
|
|
559
|
+
"\\.menuBar\\b",
|
|
560
|
+
"import\\s+AppKit\\b"
|
|
561
|
+
],
|
|
562
|
+
"watchos": [
|
|
563
|
+
"WatchKit|WKInterface|WKExtension\\b",
|
|
564
|
+
"HealthKit|HKWorkout\\b",
|
|
565
|
+
"WatchConnectivity\\b"
|
|
566
|
+
],
|
|
567
|
+
"visionos": [
|
|
568
|
+
"RealityKit|RealityView|ImmersiveSpace\\b",
|
|
569
|
+
"\\.volumeBaseplateVisibility\\b",
|
|
570
|
+
"SpatialTapGesture\\b"
|
|
571
|
+
],
|
|
572
|
+
"ipados": [
|
|
573
|
+
"UISplitViewController|UIKeyCommand\\b",
|
|
574
|
+
"\\.horizontalSizeClass\\b",
|
|
575
|
+
"pencilInteraction\\b"
|
|
576
|
+
],
|
|
577
|
+
"tvos": [
|
|
578
|
+
"TVUIKit|focusable\\b",
|
|
579
|
+
"\\.focusSection\\b",
|
|
580
|
+
"TVMonogram\\b"
|
|
581
|
+
],
|
|
582
|
+
"build-distribution": [
|
|
583
|
+
"TestFlight|AppStore\\b",
|
|
584
|
+
"\\.entitlements\\b",
|
|
585
|
+
"codesign|notarize|archive\\b"
|
|
586
|
+
]
|
|
587
|
+
};
|
|
588
|
+
//#endregion
|
|
589
|
+
//#region src/policy/skill-trigger-patterns.ts
|
|
590
|
+
/**
|
|
591
|
+
* Per-framework code-pattern → required sub-skill data, ported verbatim from the
|
|
592
|
+
* fusengine `*_skill_triggers.py` + `*_patterns.py`
|
|
593
|
+
* (react/nextjs/laravel/swift) and the shared `shadcn_patterns.py`.
|
|
594
|
+
*
|
|
595
|
+
* Pattern groups live in `./skill-patterns/*`. Most frameworks match
|
|
596
|
+
* case-insensitively (source `re.IGNORECASE`); `swift` matches case-SENSITIVELY
|
|
597
|
+
* (source `re.search` without the flag) — see {@link CASE_SENSITIVE_FRAMEWORKS}.
|
|
598
|
+
*/
|
|
599
|
+
/**
|
|
600
|
+
* Frameworks whose Python source omits `re.IGNORECASE`, so their regexes must
|
|
601
|
+
* be compiled WITHOUT the `i` flag to stay faithful.
|
|
602
|
+
*/
|
|
603
|
+
const CASE_SENSITIVE_FRAMEWORKS = /* @__PURE__ */ new Set(["swift"]);
|
|
604
|
+
/** Map of required sub-skill name → triggering code patterns, keyed by framework. */
|
|
605
|
+
const SKILL_TRIGGERS = {
|
|
606
|
+
react: REACT_TRIGGERS,
|
|
607
|
+
nextjs: NEXTJS_TRIGGERS,
|
|
608
|
+
laravel: LARAVEL_TRIGGERS,
|
|
609
|
+
swift: SWIFT_TRIGGERS
|
|
610
|
+
};
|
|
611
|
+
//#endregion
|
|
612
|
+
//#region src/policy/shadcn-project.ts
|
|
613
|
+
/** Directories whose presence indicates shadcn/ui is installed. */
|
|
614
|
+
const UI_DIRS = [
|
|
615
|
+
"src/components/ui",
|
|
616
|
+
"components/ui",
|
|
617
|
+
"src/modules/cores/shadcn/components/ui"
|
|
618
|
+
];
|
|
619
|
+
/**
|
|
620
|
+
* Whether `cwd` is a shadcn/ui project, ported from the shared Python
|
|
621
|
+
* `is_shadcn_project` (`shadcn_patterns.py`): true when a `components.json`
|
|
622
|
+
* file exists, or any known `components/ui` directory exists under the root.
|
|
623
|
+
* Used to skip `*-shadcn` sub-skill requirements when shadcn is not installed.
|
|
624
|
+
* @param cwd - project root directory to scan.
|
|
625
|
+
* @returns `true` when shadcn/ui is detected on disk.
|
|
626
|
+
*/
|
|
627
|
+
function isShadcnProject(cwd) {
|
|
628
|
+
if (existsSync(join(cwd, "components.json"))) return true;
|
|
629
|
+
return UI_DIRS.some((dir) => existsSync(join(cwd, dir)));
|
|
630
|
+
}
|
|
631
|
+
//#endregion
|
|
632
|
+
//#region src/policy/skill-triggers.ts
|
|
633
|
+
/**
|
|
634
|
+
* Detect which sub-skills the written `content` requires for a `framework`.
|
|
635
|
+
* Faithful to the Python `detect_required_skills`: first matching pattern per
|
|
636
|
+
* skill wins. Most frameworks match case-insensitively (source `re.IGNORECASE`);
|
|
637
|
+
* `swift` matches case-sensitively (see {@link CASE_SENSITIVE_FRAMEWORKS}).
|
|
638
|
+
* @param framework - "react" | "nextjs" | "laravel" | "swift".
|
|
639
|
+
* @param content - the code being written.
|
|
640
|
+
* @returns required sub-skill names (empty when framework unknown / no match).
|
|
641
|
+
*/
|
|
642
|
+
function detectRequiredSkills(framework, content) {
|
|
643
|
+
const groups = SKILL_TRIGGERS[framework];
|
|
644
|
+
if (!groups) return [];
|
|
645
|
+
const flags = CASE_SENSITIVE_FRAMEWORKS.has(framework) ? "" : "i";
|
|
646
|
+
const required = [];
|
|
647
|
+
for (const [skill, patterns] of Object.entries(groups)) if (patterns.some((p) => new RegExp(p, flags).test(content))) required.push(skill);
|
|
648
|
+
return required;
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Block when a required sub-skill's `skills/<name>/` path is absent from
|
|
652
|
+
* `refsRead`. Mirrors `specific_skill_consulted`, which confirms a skill was
|
|
653
|
+
* read by checking the tracking file contains `skills/<name>/`.
|
|
654
|
+
* @param framework - "react" | "nextjs" | "laravel".
|
|
655
|
+
* @param content - the code being written.
|
|
656
|
+
* @param refsRead - in-session read reference paths.
|
|
657
|
+
* @param forcedSkill - a skill the detected modular architecture forces (optional).
|
|
658
|
+
* @param cwd - project root; when set and not a shadcn project, `*-shadcn`
|
|
659
|
+
* requirements are skipped (ports the Python `is_shadcn_project` filter).
|
|
660
|
+
* @returns a `block` Prompt naming the missing sub-skills, or `null` when satisfied.
|
|
661
|
+
*/
|
|
662
|
+
function skillTriggerGate(framework, content, refsRead, forcedSkill, cwd) {
|
|
663
|
+
let required = detectRequiredSkills(framework, content);
|
|
664
|
+
if (forcedSkill && !required.includes(forcedSkill)) required.push(forcedSkill);
|
|
665
|
+
if (cwd && !isShadcnProject(cwd)) required = required.filter((s) => !s.endsWith("-shadcn"));
|
|
666
|
+
const missing = required.filter((s) => !refsRead.some((r) => r.includes(`skills/${s}/`)));
|
|
667
|
+
if (missing.length === 0) return null;
|
|
668
|
+
return {
|
|
669
|
+
kind: "block",
|
|
670
|
+
title: "Required sub-skill not consulted",
|
|
671
|
+
reason: `${framework}: code uses APIs covered by ${missing.join(", ")} but its skill reference was not read this session.`,
|
|
672
|
+
actions: missing.map((s) => `Read skills/${s}/ before writing this code`)
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
//#endregion
|
|
676
|
+
export { detectProjectType as _, MAX_EXA_RESULTS as a, detectCreationIntent as c, docConsultedGate as d, evaluateApex as f, detectModularArchitecture as g, DEV_KEYWORDS as h, frameworkSolidGate as i, APEX_GATES as l, solidReadGate as m, skillTriggerGate as n, MAX_TOKENS as o, freshnessGate as p, SKILL_TRIGGERS as r, capVerbosity as s, detectRequiredSkills as t, brainstormGate as u, isApexCommand as v, requiredArchSkill as y };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fusengine/harness",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.29",
|
|
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",
|