@fusengine/harness 0.1.71 → 0.1.73
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
|
@@ -4,7 +4,7 @@ import { t as detectHarness } from "../harness-Cb9xR8dC.mjs";
|
|
|
4
4
|
import { t as claudeHome } from "../home-state-D0RLWP8J.mjs";
|
|
5
5
|
import { n as stagedContent, r as stagedFiles, t as checkStaged } from "../run-B4BqxlXH.mjs";
|
|
6
6
|
import { n as writeInitFile, t as initFor } from "../run-Do2JltgU.mjs";
|
|
7
|
-
import { $ as runningVersion, Q as runDoctor, Z as notify, et as versionBanner, t as handleHook, zt as todayUtc } from "../handle-
|
|
7
|
+
import { $ as runningVersion, Q as runDoctor, Z as notify, et as versionBanner, t as handleHook, zt as todayUtc } from "../handle-BcUrrK0Q.mjs";
|
|
8
8
|
import { p as readStdin$1 } from "../claude-Z0pN-dqc.mjs";
|
|
9
9
|
import { delimiter, join } from "node:path";
|
|
10
10
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
@@ -227,7 +227,7 @@ function recordValidDesignSystem(state) {
|
|
|
227
227
|
*/
|
|
228
228
|
function recordRead(state, filePath) {
|
|
229
229
|
const next = { ...state };
|
|
230
|
-
if (filePath.includes("
|
|
230
|
+
if (filePath.includes("design-system/SKILL.md")) {
|
|
231
231
|
next.currentPhase = Math.max(state.currentPhase, 1);
|
|
232
232
|
next.phasesCompleted = [.../* @__PURE__ */ new Set([...state.phasesCompleted, "identity"])];
|
|
233
233
|
}
|
|
@@ -790,16 +790,18 @@ function readRules(rulesDir) {
|
|
|
790
790
|
return parts.join("\n\n");
|
|
791
791
|
}
|
|
792
792
|
/**
|
|
793
|
-
* Build the rules injection for claude-rules (SessionStart
|
|
794
|
-
* read `<pluginRoot>/rules/*.md` and emit as `additionalContext`,
|
|
795
|
-
*
|
|
796
|
-
*
|
|
793
|
+
* Build the rules injection for claude-rules (SessionStart, UserPromptSubmit,
|
|
794
|
+
* SubagentStart): read `<pluginRoot>/rules/*.md` and emit as `additionalContext`,
|
|
795
|
+
* or "" when no rules. Tags the output with the *actual* `hookEventName` — the
|
|
796
|
+
* spec requires it to match the firing event (a hardcoded "SessionStart" is
|
|
797
|
+
* non-conforming and may be dropped on UserPromptSubmit/SubagentStart).
|
|
797
798
|
* @param pluginRoot - `CLAUDE_PLUGIN_ROOT` of the claude-rules plugin.
|
|
799
|
+
* @param event - The firing hook event name (e.g. "SessionStart").
|
|
798
800
|
* @returns The native hook stdout (possibly empty).
|
|
799
801
|
*/
|
|
800
|
-
function injectRules(pluginRoot) {
|
|
802
|
+
function injectRules(pluginRoot, event) {
|
|
801
803
|
const content = readRules(join(pluginRoot, "rules"));
|
|
802
|
-
return content ? contextResponse(
|
|
804
|
+
return content ? contextResponse(event, content) : "";
|
|
803
805
|
}
|
|
804
806
|
//#endregion
|
|
805
807
|
//#region src/runtime/lifecycle/solid-detect.ts
|
|
@@ -5313,7 +5315,7 @@ async function aipilotPostToolUse(payload, cwd) {
|
|
|
5313
5315
|
/** SessionStart handler keyed on plugin scope. */
|
|
5314
5316
|
function sessionStart(input) {
|
|
5315
5317
|
if (input.scope === "solid") return solidDetectStart();
|
|
5316
|
-
if (input.scope === "rules") return injectRules(process.env.CLAUDE_PLUGIN_ROOT ?? input.cwd);
|
|
5318
|
+
if (input.scope === "rules") return injectRules(process.env.CLAUDE_PLUGIN_ROOT ?? input.cwd, input.event);
|
|
5317
5319
|
if (input.scope === "carto") return cartoSessionStart(input.cwd, input.now);
|
|
5318
5320
|
if (input.scope === "lessons") return dispatchLessons("SessionStart", input.payload, input.cwd, input.now);
|
|
5319
5321
|
const core = sessionStartCore(input.cwd, void 0, input.now);
|
|
@@ -5329,9 +5331,9 @@ function sessionStart(input) {
|
|
|
5329
5331
|
function dispatchLifecycle(input) {
|
|
5330
5332
|
switch (input.event) {
|
|
5331
5333
|
case "SessionStart": return sessionStart(input);
|
|
5332
|
-
case "UserPromptSubmit": return input.scope === "rules" ? injectRules(process.env.CLAUDE_PLUGIN_ROOT ?? input.cwd) : null;
|
|
5334
|
+
case "UserPromptSubmit": return input.scope === "rules" ? injectRules(process.env.CLAUDE_PLUGIN_ROOT ?? input.cwd, input.event) : null;
|
|
5333
5335
|
case "SubagentStart":
|
|
5334
|
-
if (input.scope === "rules") return injectRules(process.env.CLAUDE_PLUGIN_ROOT ?? input.cwd);
|
|
5336
|
+
if (input.scope === "rules") return injectRules(process.env.CLAUDE_PLUGIN_ROOT ?? input.cwd, input.event);
|
|
5335
5337
|
if (input.scope === "aipilot") return "";
|
|
5336
5338
|
if (input.scope === "lessons") return dispatchLessons("SubagentStart", input.payload, input.cwd, input.now);
|
|
5337
5339
|
return subagentCacheContext(input.payload.session_id);
|
|
@@ -7343,8 +7345,8 @@ function runDesignChecks(content) {
|
|
|
7343
7345
|
/** Domain skill triggers (ported 1:1 from `design_skill_triggers.py`). */
|
|
7344
7346
|
const SKILL_TRIGGERS = [
|
|
7345
7347
|
{
|
|
7346
|
-
skill: "
|
|
7347
|
-
readFragment: "
|
|
7348
|
+
skill: "design-web",
|
|
7349
|
+
readFragment: "design-web",
|
|
7348
7350
|
patterns: [
|
|
7349
7351
|
/className\s*=\s*["{]/i,
|
|
7350
7352
|
/<(div|section|main|header|footer|nav|aside)\s/i,
|
|
@@ -7357,8 +7359,8 @@ const SKILL_TRIGGERS = [
|
|
|
7357
7359
|
]
|
|
7358
7360
|
},
|
|
7359
7361
|
{
|
|
7360
|
-
skill: "
|
|
7361
|
-
readFragment: "
|
|
7362
|
+
skill: "design-system",
|
|
7363
|
+
readFragment: "design-system",
|
|
7362
7364
|
patterns: [
|
|
7363
7365
|
/--(\w+-)+color:|:root\s*\{|@theme\b/i,
|
|
7364
7366
|
/design.?system|token|palette|typography.?scale/i,
|
|
@@ -7372,8 +7374,8 @@ const SKILL_TRIGGERS = [
|
|
|
7372
7374
|
]
|
|
7373
7375
|
},
|
|
7374
7376
|
{
|
|
7375
|
-
skill: "
|
|
7376
|
-
readFragment: "
|
|
7377
|
+
skill: "design-motion",
|
|
7378
|
+
readFragment: "design-motion",
|
|
7377
7379
|
patterns: [
|
|
7378
7380
|
/motion\.|framer-motion|animate|variants/i,
|
|
7379
7381
|
/whileHover|whileTap|AnimatePresence|transition/i,
|
|
@@ -7388,8 +7390,8 @@ const SKILL_TRIGGERS = [
|
|
|
7388
7390
|
]
|
|
7389
7391
|
},
|
|
7390
7392
|
{
|
|
7391
|
-
skill: "
|
|
7392
|
-
readFragment: "design-
|
|
7393
|
+
skill: "design-review",
|
|
7394
|
+
readFragment: "design-review",
|
|
7393
7395
|
patterns: [/aria-|role=|sr-only|tabIndex|alt=/i, /WCAG|a11y|contrast|screen.?reader/i]
|
|
7394
7396
|
}
|
|
7395
7397
|
];
|
|
@@ -7408,7 +7410,7 @@ const UI_PATH_RE = /(components|ui|styles|page|layout|content|view|feature|secti
|
|
|
7408
7410
|
/** Tailwind utility usage in JSX `className` (ports the Python `has_jsx_tailwind`). */
|
|
7409
7411
|
const TAILWIND_RE = /className\s*=.*(?:flex|grid|p-|m-|bg-|text-|rounded|shadow|border|gap-|w-|h-)/;
|
|
7410
7412
|
/** A read reference path that counts as "a design skill was consulted". */
|
|
7411
|
-
const DESIGN_SKILL_RE = /skills\/(
|
|
7413
|
+
const DESIGN_SKILL_RE = /skills\/(design-method|design-system|design-web|design-webapp|design-motion|design-review|ux-copy|design-ios|design-android)/i;
|
|
7412
7414
|
/** True when this Write/Edit targets a UI file in a UI-ish path or with Tailwind classes. */
|
|
7413
7415
|
function isUiWrite(tool, filePath, content) {
|
|
7414
7416
|
if (tool !== "Write" && tool !== "Edit") return false;
|
|
@@ -7435,7 +7437,7 @@ const block = (reason, action) => ({
|
|
|
7435
7437
|
*/
|
|
7436
7438
|
function uiDesignSkillGate(tool, filePath, content, ev) {
|
|
7437
7439
|
if (!isUiWrite(tool, filePath, content)) return null;
|
|
7438
|
-
if (!designSkillRead(ev.refsRead)) return block("BLOCKED: design skill not consulted before writing UI. Read a design SKILL.md (
|
|
7440
|
+
if (!designSkillRead(ev.refsRead)) return block("BLOCKED: design skill not consulted before writing UI. Read a design SKILL.md (design-system, design-web, or design-motion), then retry.", "Read a design skill reference, then retry");
|
|
7439
7441
|
const missing = missingDomainSkills(content, ev.refsRead);
|
|
7440
7442
|
if (missing.length) return block(`BLOCKED: code uses ${missing.join(", ")} but the matching design skill(s) were not consulted. Read the SKILL.md for: ${missing.join(", ")}, then retry.`, `Read design skill(s): ${missing.join(", ")}, then retry`);
|
|
7441
7443
|
if (!ev.docConsulted) return block("BLOCKED: no documentation consulted. Use BOTH mcp__context7__query-docs AND mcp__exa__web_search_exa, or a web fallback alone (WebSearch/WebFetch). Gemini is NOT required.", "Consult Context7+Exa, or web docs alone (Gemini not required), then retry");
|
|
@@ -7613,7 +7615,7 @@ const KNOWN_DOMAINS = [
|
|
|
7613
7615
|
/** Gate writing design-system.md: requires phase >= 2 and the per-mode screenshot quota. */
|
|
7614
7616
|
function designSystemWriteGate(filePath, state) {
|
|
7615
7617
|
if (!filePath.endsWith("design-system.md")) return null;
|
|
7616
|
-
if (state.currentPhase < 2) return deny(`BLOCKED: Cannot write design-system.md at phase ${state.currentPhase}. RECOVERY: 1) Read identity templates from skills/
|
|
7618
|
+
if (state.currentPhase < 2) return deny(`BLOCKED: Cannot write design-system.md at phase ${state.currentPhase}. RECOVERY: 1) Read identity templates from skills/design-system/ 2) Read design-inspiration.md 3) Browse and screenshot sites 4) Then write design-system.md`);
|
|
7617
7619
|
const needed = MIN_SCREENSHOTS[state.mode];
|
|
7618
7620
|
if (state.screenshotsCount < needed) return deny(`BLOCKED: ${state.screenshotsCount}/${needed} screenshots for mode '${state.mode}'. RECOVERY: 1) Take ${needed - state.screenshotsCount} more fuse-browser screenshots 2) Use browser_open + browser_navigate + browser_screenshot fullPage:true 3) Then write design-system.md`);
|
|
7619
7621
|
return null;
|
|
@@ -7626,8 +7628,8 @@ function geminiCreateGate(state) {
|
|
|
7626
7628
|
}
|
|
7627
7629
|
/** Gate fuse-browser navigate: phase >= 1, inspiration read, URL in the catalog. */
|
|
7628
7630
|
function browserNavigateGate(state, url) {
|
|
7629
|
-
if (state.currentPhase < 1) return deny(`BLOCKED: Phase 0 not done. READ: ${SKILLS}/
|
|
7630
|
-
if (!state.inspirationRead) return deny(`BLOCKED: Read inspiration catalog first. READ: ${SKILLS}/
|
|
7631
|
+
if (state.currentPhase < 1) return deny(`BLOCKED: Phase 0 not done. READ: ${SKILLS}/design-system/SKILL.md first.`);
|
|
7632
|
+
if (!state.inspirationRead) return deny(`BLOCKED: Read inspiration catalog first. READ: ${SKILLS}/design-web/references/design-inspiration.md + design-inspiration-urls.md`);
|
|
7631
7633
|
if (url && !KNOWN_DOMAINS.some((d) => url.includes(d))) return deny(`BLOCKED: '${url}' not in catalog. Use URLs from design-inspiration-urls.md. Domains: ${KNOWN_DOMAINS.join(", ")}`);
|
|
7632
7634
|
return null;
|
|
7633
7635
|
}
|
package/dist/runtime/index.d.mts
CHANGED
|
@@ -352,14 +352,16 @@ declare function sessionStartCore(cwd: string, home?: string, now?: number): str
|
|
|
352
352
|
/** Read & concatenate all `*.md` files (sorted) under `rulesDir`. */
|
|
353
353
|
declare function readRules(rulesDir: string): string;
|
|
354
354
|
/**
|
|
355
|
-
* Build the rules injection for claude-rules (SessionStart
|
|
356
|
-
* read `<pluginRoot>/rules/*.md` and emit as `additionalContext`,
|
|
357
|
-
*
|
|
358
|
-
*
|
|
355
|
+
* Build the rules injection for claude-rules (SessionStart, UserPromptSubmit,
|
|
356
|
+
* SubagentStart): read `<pluginRoot>/rules/*.md` and emit as `additionalContext`,
|
|
357
|
+
* or "" when no rules. Tags the output with the *actual* `hookEventName` — the
|
|
358
|
+
* spec requires it to match the firing event (a hardcoded "SessionStart" is
|
|
359
|
+
* non-conforming and may be dropped on UserPromptSubmit/SubagentStart).
|
|
359
360
|
* @param pluginRoot - `CLAUDE_PLUGIN_ROOT` of the claude-rules plugin.
|
|
361
|
+
* @param event - The firing hook event name (e.g. "SessionStart").
|
|
360
362
|
* @returns The native hook stdout (possibly empty).
|
|
361
363
|
*/
|
|
362
|
-
declare function injectRules(pluginRoot: string): string;
|
|
364
|
+
declare function injectRules(pluginRoot: string, event: string): string;
|
|
363
365
|
//#endregion
|
|
364
366
|
//#region src/runtime/lifecycle/solid-detect.d.ts
|
|
365
367
|
/** A SOLID project profile: type label, per-file line limit, interface dir. */
|
package/dist/runtime/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { r as projectLayout } from "../layout-C0jaaCQC.mjs";
|
|
2
2
|
import { a as sanitizeSessionId, c as sessionsDir, i as loadSessionState, n as fuseHarnessHome, o as saveSessionState, r as fusengineCache, s as sessionStatePath, t as claudeHome } from "../home-state-D0RLWP8J.mjs";
|
|
3
|
-
import { A as trackMcpResearch, At as defaultStateDir, B as generateProjectMap, C as seoPostToolUse, Ct as trimLogFile, D as securityAdvisoryForPatch, Dt as claudeMdKey, E as securityAdvisory, Et as projectContext, F as dispatchAipilot, Ft as loadSecurityState, G as countFiles, H as writeTree, I as dispatchLessons, It as saveSecurityState, J as lessonsArchiveFileFor, K as getFileDesc, L as cartoSessionStart, Lt as securityStateDir, M as trackEnrichment, Mt as trackFile, N as dispatchLifecycle, Nt as normalizeEvent, O as postTrackingSideEffects, Ot as promptSubmitContext, P as aipilotPostToolUse, Pt as isoUtc, R as generateEcosystemMap, Rt as securityStatePath, S as postEditContext, St as removeOldFiles, T as dispatchMemory, Tt as gitContext, U as loadEnriched, V as isProject, W as mergeLines, X as lessonsStateFileFor, Y as lessonsFileFor, _ as preCommitGate, _t as readRules, a as recordActivity, at as saveApexState, b as extractSymbols, bt as pruneEmptyDirs, c as MCP_TTL_MS, ct as trackAgentMemory, d as isMcpTool, dt as validateSolidGate, f as queryOf, ft as checkFileSize, g as gate, gt as injectRules, h as TRIVIAL_BUDGET, ht as solidDetectStart, i as respond, it as cleanupSession, j as trackSkillRead, jt as projectHash, k as trackWatchResearch, kt as taskContext, l as WEBFETCH_TTL_MS, lt as subagentCacheContext, m as REQUIRED_AGENTS, mt as detectSolidProfile, n as activityFor, nt as trackSessionChanges, o as mcpPostStore, ot as logToolFailure, p as DEFAULT_WINDOW_MS, pt as countLoc, q as listChildren, r as handlePre, rt as validateRulesLoaded, s as mcpPreIntercept, st as validateTeammateOutput, t as handleHook, tt as postEditTypescript, u as cacheQueryOf, ut as validateTailwind, v as detectDuplication, vt as runSessionStartCleanups, w as seoPostToolUseResponse, wt as devContext, x as lifecycleStdout, xt as purgeTtlTree, y as dryGate, yt as sessionStartCore, z as writePluginMap, zt as todayUtc } from "../handle-
|
|
3
|
+
import { A as trackMcpResearch, At as defaultStateDir, B as generateProjectMap, C as seoPostToolUse, Ct as trimLogFile, D as securityAdvisoryForPatch, Dt as claudeMdKey, E as securityAdvisory, Et as projectContext, F as dispatchAipilot, Ft as loadSecurityState, G as countFiles, H as writeTree, I as dispatchLessons, It as saveSecurityState, J as lessonsArchiveFileFor, K as getFileDesc, L as cartoSessionStart, Lt as securityStateDir, M as trackEnrichment, Mt as trackFile, N as dispatchLifecycle, Nt as normalizeEvent, O as postTrackingSideEffects, Ot as promptSubmitContext, P as aipilotPostToolUse, Pt as isoUtc, R as generateEcosystemMap, Rt as securityStatePath, S as postEditContext, St as removeOldFiles, T as dispatchMemory, Tt as gitContext, U as loadEnriched, V as isProject, W as mergeLines, X as lessonsStateFileFor, Y as lessonsFileFor, _ as preCommitGate, _t as readRules, a as recordActivity, at as saveApexState, b as extractSymbols, bt as pruneEmptyDirs, c as MCP_TTL_MS, ct as trackAgentMemory, d as isMcpTool, dt as validateSolidGate, f as queryOf, ft as checkFileSize, g as gate, gt as injectRules, h as TRIVIAL_BUDGET, ht as solidDetectStart, i as respond, it as cleanupSession, j as trackSkillRead, jt as projectHash, k as trackWatchResearch, kt as taskContext, l as WEBFETCH_TTL_MS, lt as subagentCacheContext, m as REQUIRED_AGENTS, mt as detectSolidProfile, n as activityFor, nt as trackSessionChanges, o as mcpPostStore, ot as logToolFailure, p as DEFAULT_WINDOW_MS, pt as countLoc, q as listChildren, r as handlePre, rt as validateRulesLoaded, s as mcpPreIntercept, st as validateTeammateOutput, t as handleHook, tt as postEditTypescript, u as cacheQueryOf, ut as validateTailwind, v as detectDuplication, vt as runSessionStartCleanups, w as seoPostToolUseResponse, wt as devContext, x as lifecycleStdout, xt as purgeTtlTree, y as dryGate, yt as sessionStartCore, z as writePluginMap, zt as todayUtc } from "../handle-BcUrrK0Q.mjs";
|
|
4
4
|
//#region src/runtime/storage.ts
|
|
5
5
|
/**
|
|
6
6
|
* The project's single state dir (`<root>/.harness`) — neutral + harness-agnostic,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fusengine/harness",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.73",
|
|
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",
|