@fusengine/harness 0.1.72 → 0.1.74
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-C6tIKeGU.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
|
}
|
|
@@ -5266,10 +5266,10 @@ function transcriptOf(payload) {
|
|
|
5266
5266
|
return typeof t === "string" ? t : void 0;
|
|
5267
5267
|
}
|
|
5268
5268
|
/** Type-specific SubagentStart cache for an agent, or "" when none applies. */
|
|
5269
|
-
async function typeSpecificCache(agent, cwd, now) {
|
|
5270
|
-
if (agent.includes("explore-codebase")) return injectExploreCache(cwd,
|
|
5271
|
-
if (agent.includes("research-expert")) return injectDocCache(cwd,
|
|
5272
|
-
if (agent.includes("sniper")) return injectTestCache(cwd,
|
|
5269
|
+
async function typeSpecificCache(agent, cwd, now, home) {
|
|
5270
|
+
if (agent.includes("explore-codebase")) return injectExploreCache(cwd, home, now);
|
|
5271
|
+
if (agent.includes("research-expert")) return injectDocCache(cwd, home, now);
|
|
5272
|
+
if (agent.includes("sniper")) return injectTestCache(cwd, home, now);
|
|
5273
5273
|
return "";
|
|
5274
5274
|
}
|
|
5275
5275
|
/**
|
|
@@ -5277,33 +5277,34 @@ async function typeSpecificCache(agent, cwd, now) {
|
|
|
5277
5277
|
* entries (APEX context + lessons) fire for EVERY sub-agent, then the type-specific
|
|
5278
5278
|
* cache is concatenated on top — sniper too, hence no early return.
|
|
5279
5279
|
*/
|
|
5280
|
-
async function onSubagentStart(payload, cwd, now) {
|
|
5280
|
+
async function onSubagentStart(payload, cwd, now, home) {
|
|
5281
5281
|
const agent = agentTypeOf(payload);
|
|
5282
|
-
return combineContext(await injectApexSubagentContext(cwd), await injectLessonsCache(cwd,
|
|
5282
|
+
return combineContext(await injectApexSubagentContext(cwd, home), await injectLessonsCache(cwd, home, now), await typeSpecificCache(agent, cwd, now, home));
|
|
5283
5283
|
}
|
|
5284
5284
|
/** SubagentStop routing: transcript-driven cache writers, then the universal SOLID check. */
|
|
5285
|
-
async function onSubagentStop(payload, cwd) {
|
|
5285
|
+
async function onSubagentStop(payload, cwd, home) {
|
|
5286
5286
|
const agent = agentTypeOf(payload);
|
|
5287
5287
|
const transcript = transcriptOf(payload);
|
|
5288
|
-
if (agent.includes("research-expert")) await cacheDocFromTranscript(transcript, cwd);
|
|
5288
|
+
if (agent.includes("research-expert")) await cacheDocFromTranscript(transcript, cwd, home);
|
|
5289
5289
|
if (agent.includes("sniper")) {
|
|
5290
|
-
await cacheSniperLessons(transcript, cwd);
|
|
5291
|
-
await cacheTestResults(transcript, cwd);
|
|
5290
|
+
await cacheSniperLessons(transcript, cwd, home);
|
|
5291
|
+
await cacheTestResults(transcript, cwd, home);
|
|
5292
5292
|
}
|
|
5293
5293
|
return checkSolidFromTranscript(transcript);
|
|
5294
5294
|
}
|
|
5295
5295
|
/**
|
|
5296
5296
|
* Dispatch an ai-pilot-scope lifecycle event. Returns the native stdout, or
|
|
5297
5297
|
* `null` when unhandled (caller falls through to the default pipeline).
|
|
5298
|
+
* @param home - Home dir for cache resolution (defaults to `~`; injectable for test isolation).
|
|
5298
5299
|
*/
|
|
5299
|
-
async function dispatchAipilot(event, payload, cwd, now) {
|
|
5300
|
-
if (event === "SubagentStart") return onSubagentStart(payload, cwd, now);
|
|
5301
|
-
if (event === "SubagentStop") return onSubagentStop(payload, cwd);
|
|
5300
|
+
async function dispatchAipilot(event, payload, cwd, now, home = homedir()) {
|
|
5301
|
+
if (event === "SubagentStart") return onSubagentStart(payload, cwd, now, home);
|
|
5302
|
+
if (event === "SubagentStop") return onSubagentStop(payload, cwd, home);
|
|
5302
5303
|
if (event === "SessionEnd" || event === "Stop") {
|
|
5303
|
-
await cacheAnalyticsSave(
|
|
5304
|
+
await cacheAnalyticsSave(home, now);
|
|
5304
5305
|
return "";
|
|
5305
5306
|
}
|
|
5306
|
-
if (event === "PreToolUse") return docCacheGate(payload, cwd, now);
|
|
5307
|
+
if (event === "PreToolUse") return docCacheGate(payload, cwd, now, home);
|
|
5307
5308
|
return null;
|
|
5308
5309
|
}
|
|
5309
5310
|
/** PostToolUse (Write/Edit SOLID check, else TaskCreate/TaskUpdate sync) for the ai-pilot scope. */
|
|
@@ -7345,8 +7346,8 @@ function runDesignChecks(content) {
|
|
|
7345
7346
|
/** Domain skill triggers (ported 1:1 from `design_skill_triggers.py`). */
|
|
7346
7347
|
const SKILL_TRIGGERS = [
|
|
7347
7348
|
{
|
|
7348
|
-
skill: "
|
|
7349
|
-
readFragment: "
|
|
7349
|
+
skill: "design-web",
|
|
7350
|
+
readFragment: "design-web",
|
|
7350
7351
|
patterns: [
|
|
7351
7352
|
/className\s*=\s*["{]/i,
|
|
7352
7353
|
/<(div|section|main|header|footer|nav|aside)\s/i,
|
|
@@ -7359,8 +7360,8 @@ const SKILL_TRIGGERS = [
|
|
|
7359
7360
|
]
|
|
7360
7361
|
},
|
|
7361
7362
|
{
|
|
7362
|
-
skill: "
|
|
7363
|
-
readFragment: "
|
|
7363
|
+
skill: "design-system",
|
|
7364
|
+
readFragment: "design-system",
|
|
7364
7365
|
patterns: [
|
|
7365
7366
|
/--(\w+-)+color:|:root\s*\{|@theme\b/i,
|
|
7366
7367
|
/design.?system|token|palette|typography.?scale/i,
|
|
@@ -7374,8 +7375,8 @@ const SKILL_TRIGGERS = [
|
|
|
7374
7375
|
]
|
|
7375
7376
|
},
|
|
7376
7377
|
{
|
|
7377
|
-
skill: "
|
|
7378
|
-
readFragment: "
|
|
7378
|
+
skill: "design-motion",
|
|
7379
|
+
readFragment: "design-motion",
|
|
7379
7380
|
patterns: [
|
|
7380
7381
|
/motion\.|framer-motion|animate|variants/i,
|
|
7381
7382
|
/whileHover|whileTap|AnimatePresence|transition/i,
|
|
@@ -7390,8 +7391,8 @@ const SKILL_TRIGGERS = [
|
|
|
7390
7391
|
]
|
|
7391
7392
|
},
|
|
7392
7393
|
{
|
|
7393
|
-
skill: "
|
|
7394
|
-
readFragment: "design-
|
|
7394
|
+
skill: "design-review",
|
|
7395
|
+
readFragment: "design-review",
|
|
7395
7396
|
patterns: [/aria-|role=|sr-only|tabIndex|alt=/i, /WCAG|a11y|contrast|screen.?reader/i]
|
|
7396
7397
|
}
|
|
7397
7398
|
];
|
|
@@ -7410,7 +7411,7 @@ const UI_PATH_RE = /(components|ui|styles|page|layout|content|view|feature|secti
|
|
|
7410
7411
|
/** Tailwind utility usage in JSX `className` (ports the Python `has_jsx_tailwind`). */
|
|
7411
7412
|
const TAILWIND_RE = /className\s*=.*(?:flex|grid|p-|m-|bg-|text-|rounded|shadow|border|gap-|w-|h-)/;
|
|
7412
7413
|
/** A read reference path that counts as "a design skill was consulted". */
|
|
7413
|
-
const DESIGN_SKILL_RE = /skills\/(
|
|
7414
|
+
const DESIGN_SKILL_RE = /skills\/(design-method|design-system|design-web|design-webapp|design-motion|design-review|ux-copy|design-ios|design-android)/i;
|
|
7414
7415
|
/** True when this Write/Edit targets a UI file in a UI-ish path or with Tailwind classes. */
|
|
7415
7416
|
function isUiWrite(tool, filePath, content) {
|
|
7416
7417
|
if (tool !== "Write" && tool !== "Edit") return false;
|
|
@@ -7437,7 +7438,7 @@ const block = (reason, action) => ({
|
|
|
7437
7438
|
*/
|
|
7438
7439
|
function uiDesignSkillGate(tool, filePath, content, ev) {
|
|
7439
7440
|
if (!isUiWrite(tool, filePath, content)) return null;
|
|
7440
|
-
if (!designSkillRead(ev.refsRead)) return block("BLOCKED: design skill not consulted before writing UI. Read a design SKILL.md (
|
|
7441
|
+
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");
|
|
7441
7442
|
const missing = missingDomainSkills(content, ev.refsRead);
|
|
7442
7443
|
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`);
|
|
7443
7444
|
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");
|
|
@@ -7615,7 +7616,7 @@ const KNOWN_DOMAINS = [
|
|
|
7615
7616
|
/** Gate writing design-system.md: requires phase >= 2 and the per-mode screenshot quota. */
|
|
7616
7617
|
function designSystemWriteGate(filePath, state) {
|
|
7617
7618
|
if (!filePath.endsWith("design-system.md")) return null;
|
|
7618
|
-
if (state.currentPhase < 2) return deny(`BLOCKED: Cannot write design-system.md at phase ${state.currentPhase}. RECOVERY: 1) Read identity templates from skills/
|
|
7619
|
+
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`);
|
|
7619
7620
|
const needed = MIN_SCREENSHOTS[state.mode];
|
|
7620
7621
|
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`);
|
|
7621
7622
|
return null;
|
|
@@ -7628,9 +7629,9 @@ function geminiCreateGate(state) {
|
|
|
7628
7629
|
}
|
|
7629
7630
|
/** Gate fuse-browser navigate: phase >= 1, inspiration read, URL in the catalog. */
|
|
7630
7631
|
function browserNavigateGate(state, url) {
|
|
7631
|
-
if (state.currentPhase < 1) return deny(`BLOCKED: Phase 0 not done. READ: ${SKILLS}/
|
|
7632
|
-
if (!state.inspirationRead) return deny(`BLOCKED: Read inspiration catalog first. READ: ${SKILLS}/
|
|
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
|
+
if (state.currentPhase < 1) return deny(`BLOCKED: Phase 0 not done. READ: ${SKILLS}/design-system/SKILL.md first.`);
|
|
7633
|
+
if (!state.inspirationRead) return deny(`BLOCKED: Read inspiration catalog first. READ: ${SKILLS}/design-web/references/design-inspiration.md + design-inspiration-urls.md`);
|
|
7634
|
+
if (state.currentPhase === 1 && 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(", ")}`);
|
|
7634
7635
|
return null;
|
|
7635
7636
|
}
|
|
7636
7637
|
//#endregion
|
package/dist/runtime/index.d.mts
CHANGED
|
@@ -517,8 +517,9 @@ declare function postEditTypescript(filePath: string): string;
|
|
|
517
517
|
/**
|
|
518
518
|
* Dispatch an ai-pilot-scope lifecycle event. Returns the native stdout, or
|
|
519
519
|
* `null` when unhandled (caller falls through to the default pipeline).
|
|
520
|
+
* @param home - Home dir for cache resolution (defaults to `~`; injectable for test isolation).
|
|
520
521
|
*/
|
|
521
|
-
declare function dispatchAipilot(event: string, payload: Record<string, unknown>, cwd: string, now: number): Promise<string | null>;
|
|
522
|
+
declare function dispatchAipilot(event: string, payload: Record<string, unknown>, cwd: string, now: number, home?: string): Promise<string | null>;
|
|
522
523
|
/** PostToolUse (Write/Edit SOLID check, else TaskCreate/TaskUpdate sync) for the ai-pilot scope. */
|
|
523
524
|
declare function aipilotPostToolUse(payload: Record<string, unknown>, cwd: string): Promise<string>;
|
|
524
525
|
//#endregion
|
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-C6tIKeGU.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.74",
|
|
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",
|