@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-Cw1H21Eg.mjs";
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("identity-system")) {
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, void 0, now);
5271
- if (agent.includes("research-expert")) return injectDocCache(cwd, void 0, now);
5272
- if (agent.includes("sniper")) return injectTestCache(cwd, void 0, now);
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, void 0, now), await typeSpecificCache(agent, cwd, now));
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(void 0, now);
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: "3-generating-components",
7349
- readFragment: "generating-components",
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: "1-designing-systems",
7363
- readFragment: "designing-systems",
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: "4-adding-animations",
7378
- readFragment: "adding-animations",
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: "5-design-audit",
7394
- readFragment: "design-audit",
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\/([0-9]-)?(designing-systems|generating-components|solid-react|solid-nextjs|design|impeccable|frontend-design)/i;
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 (solid-react, generating-components, or designing-systems), then retry.", "Read a design skill reference, then retry");
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/0-identity-system/ 2) Read design-inspiration.md 3) Browse and screenshot sites 4) Then write design-system.md`);
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}/0-identity-system/SKILL.md first.`);
7632
- if (!state.inspirationRead) return deny(`BLOCKED: Read inspiration catalog first. READ: ${SKILLS}/1-designing-systems/references/design-inspiration.md + design-inspiration-urls.md`);
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
@@ -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
@@ -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-Cw1H21Eg.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-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.72",
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",