@fusengine/harness 0.1.47 → 0.1.48

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-C8Nxxyn_.mjs";
4
4
  import { t as claudeHome } from "../home-state-mKZxP4oZ.mjs";
5
5
  import { n as stagedContent, r as stagedFiles, t as checkStaged } from "../run-CaqeQFYp.mjs";
6
6
  import { n as writeInitFile, t as initFor } from "../run-Do2JltgU.mjs";
7
- import { Mt as todayUtc, t as handleHook } from "../handle-DUlUbhyF.mjs";
7
+ import { Mt as todayUtc, t as handleHook } from "../handle-CCV0ycdr.mjs";
8
8
  import { delimiter, dirname, join } from "node:path";
9
9
  import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
10
10
  import { homedir } from "node:os";
@@ -1,7 +1,7 @@
1
1
  import { a as parseEnvInt, i as splitTarget, r as resolveMaxLines } from "./limits-CHn8AIL1.mjs";
2
2
  import { r as projectLayout } from "./layout-C0jaaCQC.mjs";
3
3
  import { i as walkUpFor, n as projectRoot, r as projectRootOrNull, t as isCodeFile } from "./project-root-3kk7gCOp.mjs";
4
- import { B as detectFramework, C as resolveSkillPath, D as frameworkSolidGate, G as requiredArchSkill, H as detectModularArchitecture, I as docConsultedGate, L as evaluateApex, M as capVerbosity, N as detectCreationIntent, O as isExcludedJsPath, S as usesTailwindUtilities, T as readPluginMeta, U as detectProjectType$1, _ as scanPlugin, c as EXCLUDE_DIRS$1, d as buildApexTaskInjection, h as buildClaudeMdContext, k as isExcludedSwiftPath, l as PROJECT_INDICATORS, n as missingSeoElements, o as parseEnrichment, r as descFromText, s as parseEntry, t as isHtmlLike, w as findMarketplacePlugins, x as skillTriggerGate, y as parseField } from "./validate-DVV4nRfr.mjs";
4
+ import { B as detectFramework, C as resolveSkillPath, D as frameworkSolidGate, G as requiredArchSkill, H as detectModularArchitecture, I as docConsultedGate, L as evaluateApex, M as capVerbosity, N as detectCreationIntent, O as isExcludedJsPath, S as usesTailwindUtilities, T as readPluginMeta, U as detectProjectType$1, _ as scanPlugin, c as EXCLUDE_DIRS$1, d as buildApexTaskInjection, h as buildClaudeMdContext, k as isExcludedSwiftPath, l as PROJECT_INDICATORS, n as missingSeoElements, o as parseEnrichment, r as descFromText, s as parseEntry, t as isHtmlLike, w as findMarketplacePlugins, x as skillTriggerGate, y as parseField } from "./validate-ebDjIyOa.mjs";
5
5
  import { F as PLUGINS_DIR, L as countFrameworkCodeLines, R as countLines, n as FAIL_CLOSED, t as evaluate } from "./evaluate-qYC5xClb.mjs";
6
6
  import { a as writeJsonFile, i as readJsonFile, r as hashText, t as atomicWrite } from "./json-io-DisYd2fb.mjs";
7
7
  import { a as sanitizeSessionId, c as sessionsDir, i as loadSessionState, n as fuseHarnessHome, o as saveSessionState, r as fusengineCache, t as claudeHome } from "./home-state-mKZxP4oZ.mjs";
@@ -10,7 +10,7 @@ import { t as formatPrompt } from "./types-ernB1Dy3.mjs";
10
10
  import { a as nowStamp, l as throttleMs, n as readRoots, o as readState, s as setStateField, t as addRoot } from "./registry-CymilZiZ.mjs";
11
11
  import { d as loadIndex, i as cacheLookupMeta, n as webfetchCacheWrite, o as cacheLookupSubstringMeta, t as mcpCacheWrite, u as extractText } from "./mcp-store-BkBDmuxN.mjs";
12
12
  import { t as loadRefs } from "./loader-AGz4nK7d.mjs";
13
- import { c as recordDoc, d as trivialCount, i as agentsFresh, l as recordRefRead, n as saveTrack, o as recordAgent, r as verifyTrack, s as recordBrainstormRequired, t as loadTrack, u as recordTrivialEdit } from "./store-CNjFenWe.mjs";
13
+ import { c as recordDoc, d as trivialCount, i as agentsFresh, l as recordRefRead, n as saveTrack, o as recordAgent, r as verifyTrack, s as recordBrainstormRequired, t as loadTrack, u as recordTrivialEdit } from "./store-ARx-GQSQ.mjs";
14
14
  import { d as readText, f as sleep, l as collectFiles, m as writeText, n as contextResponse, p as spawnCapture, r as denyResponse, s as systemMessage, t as blockResponse, u as pathExists } from "./claude-U1KTaRG4.mjs";
15
15
  import { basename, dirname, extname, join, relative, resolve, sep } from "node:path";
16
16
  import { appendFileSync, copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
@@ -4136,9 +4136,17 @@ const EXPLORE_BASH_CMDS = /* @__PURE__ */ new Set([
4136
4136
  "head",
4137
4137
  "tail"
4138
4138
  ]);
4139
- /** Cached MCP result reads count as research (parity `CACHE_READ_RE` + doc-helpers). */
4139
+ /** Legacy Python cache names still credited (parity `CACHE_READ_RE` + doc-helpers). */
4140
4140
  const CACHE_READ_RE = /\/context\/mcp\/(exa-search|exa-code-context|context7)-/;
4141
4141
  /**
4142
+ * Real TS cache stores: `<root>/.harness/cache/<fnv16>.md` (core MCP store,
4143
+ * `projectLayout().cacheDir`) and `~/.fuse-harness/cache/**` (ai-pilot doc
4144
+ * caches, `cache-base.cacheBaseDir`). Segment-matched — not resolved against
4145
+ * `homedir()` — so tilde-prefixed and absolute paths both hit; the `.md`
4146
+ * suffix keeps session-state JSON reads from counting as research.
4147
+ */
4148
+ const TS_CACHE_READ_RE = /\.(?:fuse-)?harness[\\/]cache[\\/].*\.md$/;
4149
+ /**
4142
4150
  * First non-assignment shell token's basename, or "" — mirrors Python
4143
4151
  * `_bash_executable` (skips leading `VAR=value` env prefixes, e.g. `FOO=1 grep`).
4144
4152
  * @param cmd - Raw Bash `command` string.
@@ -4170,7 +4178,7 @@ function classifyExplore(tool, input) {
4170
4178
  };
4171
4179
  if (tool === "Read") {
4172
4180
  const path = String(input?.file_path ?? input?.path ?? "");
4173
- if (path && CACHE_READ_RE.test(path)) return {
4181
+ if (path && (CACHE_READ_RE.test(path) || TS_CACHE_READ_RE.test(path))) return {
4174
4182
  phase: "research-expert",
4175
4183
  cacheHit: true
4176
4184
  };
@@ -4288,6 +4296,8 @@ async function apexScopedGate(input, track, window) {
4288
4296
  authorizations: track.authorizations,
4289
4297
  refs: input.refs,
4290
4298
  refsRead: track.refsRead,
4299
+ refsReadAt: track.refsReadAt,
4300
+ now: input.now,
4291
4301
  agentsFresh: freshnessFor([...REQUIRED_AGENTS]),
4292
4302
  missingAgents: REQUIRED_AGENTS.filter((name) => !freshnessFor([name])),
4293
4303
  windowMs: window,
@@ -4483,7 +4493,7 @@ function mcpPostStore(tool, input, response, dir, now = Date.now()) {
4483
4493
  /** Apply an activity to a session's track and persist it (PostToolUse path). */
4484
4494
  async function recordActivity(file, activity) {
4485
4495
  const track = await loadTrack(file);
4486
- await saveTrack(file, activity.kind === "agent" ? recordAgent(track, activity.name, activity.ts, activity.quality) : activity.kind === "doc" ? recordDoc(track, activity.framework, activity.sessionId, activity.source) : recordRefRead(track, activity.path));
4496
+ await saveTrack(file, activity.kind === "agent" ? recordAgent(track, activity.name, activity.ts, activity.quality) : activity.kind === "doc" ? recordDoc(track, activity.framework, activity.sessionId, activity.source) : recordRefRead(track, activity.path, activity.ts));
4487
4497
  }
4488
4498
  //#endregion
4489
4499
  //#region src/runtime/respond.ts
@@ -4947,13 +4957,10 @@ async function handlePre(ctx) {
4947
4957
  stdout: securityAdvisory(event.tool, event.filePath ?? "", opts.now),
4948
4958
  exit: 0
4949
4959
  };
4950
- if (opts.scope === "solid" && event.filePath) {
4951
- const solidDeny = validateSolidGate(event.tool, event.filePath, event.content ?? "");
4952
- if (solidDeny) return {
4953
- stdout: solidDeny,
4954
- exit: 0
4955
- };
4956
- }
4960
+ if (opts.scope === "solid") return {
4961
+ stdout: validateSolidGate(event.tool, event.filePath ?? "", event.content ?? ""),
4962
+ exit: 0
4963
+ };
4957
4964
  if (event.tool === "Task") {
4958
4965
  const taskCtx = taskContext(opts.cwd);
4959
4966
  if (taskCtx) return {
@@ -5062,7 +5069,8 @@ function activityFor(event) {
5062
5069
  const path = String(event.input?.file_path ?? event.input?.path ?? "");
5063
5070
  if (path.endsWith(".md")) out.push({
5064
5071
  kind: "ref",
5065
- path
5072
+ path,
5073
+ ts: event.now
5066
5074
  });
5067
5075
  }
5068
5076
  return out;
@@ -156,7 +156,7 @@ interface PolicyResult {
156
156
  declare function evaluate(ctx: PolicyContext): PolicyResult;
157
157
  //#endregion
158
158
  //#region src/policy/apex-gates.d.ts
159
- /** Gate: the routed SOLID references for this edit must have been read. */
159
+ /** Gate: the routed SOLID references for this edit must have been read within the TTL. */
160
160
  declare const solidReadGate: ApexGate;
161
161
  /** Gate: the required prior agents (explore + research) must have run within the window. */
162
162
  declare const freshnessGate: ApexGate;
@@ -178,11 +178,22 @@ interface ApexContext {
178
178
  refs?: RefMeta[];
179
179
  /** Absolute paths of SOLID refs already read this session. */
180
180
  refsRead?: string[];
181
+ /**
182
+ * Epoch-ms read timestamp per `refsRead` path (parity track-solid-reads.py
183
+ * `solid_reads[].timestamp`). PARITY: the TTL applies ONLY to solidReadGate —
184
+ * Python TTL-izes SOLID reads exclusively (require-solid-read.py); the
185
+ * skill-trigger/design/shadcn gates stay session-scoped (no TTL). A path in
186
+ * `refsRead` with no stamp here (tracks recorded before this field existed)
187
+ * counts as read — backward compat.
188
+ */
189
+ refsReadAt?: Record<string, number>;
190
+ /** Current epoch ms for the SOLID-read TTL check; absent → reads never expire. */
191
+ now?: number;
181
192
  /** Whether the required prior agents (explore + research) ran within the freshness window. */
182
193
  agentsFresh?: boolean;
183
194
  /** Names of REQUIRED_AGENTS that have NOT run fresh (subset), for a precise freshnessGate message. Absent → generic wording. */
184
195
  missingAgents?: string[];
185
- /** Freshness window in ms, used only to label the block message with its TTL (e.g. "2min"). */
196
+ /** Freshness window in ms: labels the block messages' TTL (e.g. "2min") and bounds the SOLID-read TTL in solidReadGate. */
186
197
  windowMs?: number;
187
198
  /** Whether brainstorming is required for this edit (creation intent on a new file). */
188
199
  brainstormRequired?: boolean;
package/dist/index.d.mts CHANGED
@@ -5,7 +5,7 @@ import { a as HarnessInfo, i as HarnessId, n as detectMode, o as HarnessMode, r
5
5
  import { a as isDocConsulted, i as formatDocSatisfactionStatus, n as DocSatisfactionStatus, o as resolveSessions, r as formatDocDeny, t as AuthEntry } from "./doc-helpers-BNfYWvYv.mjs";
6
6
  import { t as incrementTrivialEditCounter } from "./index-BOBXQ91y.mjs";
7
7
  import { a as compactJson, i as walkUpFor, n as projectRoot, r as projectRootOrNull, t as isCodeFile } from "./index-BEMumjOw.mjs";
8
- import { $ as ASK_PATTERNS, A as FAIL_CLOSED, At as DEV_KEYWORDS, B as SWIFT_PROTO_RE, C as usesTailwindUtilities, Ct as FileSizeVerdict, D as MAX_TOKENS, Dt as countLines, E as MAX_EXA_RESULTS, Et as countFrameworkCodeLines, F as installGuard, Ft as isApexCommand, G as CODE_MUTATORS, H as interfaceSeparationGuard, I as GO_DECL_RE, It as requiredArchSkill, J as SAFE_PREFIXES, K as CODE_REDIRECT, L as JAVA_DECL_RE, M as clearUserGuards, Mt as ProjectType, N as registerGuard, Nt as detectModularArchitecture, O as capVerbosity, Ot as evaluateFileSize, P as runGuards, Pt as detectProjectType, Q as protectedPathGuard, R as PHP_DECL_RE, S as skillTriggerGate, St as matchPatterns, T as frameworkSolidGate, Tt as SOLID_REF, U as bashWriteGuard, V as TS_DECL_RE, W as ASK_WRITERS, X as PROTECTED_FRAGMENTS, Y as SESSION_STATE_FRAGMENT, Z as PROTECTED_GIT_RE, _ as DEV_VERBS, _t as GIT_BLOCKED, a as firstHeading, at as APEX_GATES, b as detectClaudeMdProjectType, bt as SYSTEM_INSTALL, c as parseEntry, ct as brainstormGate, d as EXCLUDE_DIRS, dt as freshnessGate, et as CRITICAL_PATTERNS, f as PROJECT_INDICATORS, ft as solidReadGate, g as loadApexTaskState, gt as GIT_ASK, h as buildApexTaskInjection, ht as PolicyResult, i as firstComment, it as GuardContext, j as GUARDS, jt as ModularArchitecture, k as detectCreationIntent, kt as detectFramework, l as parseBodyDesc, lt as docConsultedGate, m as buildApexTaskContext, mt as PolicyContext, n as missingSeoElements, nt as securityGuard, o as TreeEntry, ot as ApexContext, p as ApexTaskState, pt as evaluate, q as FILE_REDIRECT, r as descFromText, rt as Guard, s as parseEnrichment, st as ApexGate, t as isHtmlLike, tt as LabeledPattern, u as parseField, ut as evaluateApex, v as buildApexInstruction, vt as PROJECT_INSTALL, w as SKILL_TRIGGERS, wt as PLUGINS_DIR, x as detectRequiredSkills, xt as isRalphMode, y as buildClaudeMdContext, yt as RALPH_SAFE, z as PY_MODEL_RE } from "./index-l9Xvxrx7.mjs";
8
+ import { $ as ASK_PATTERNS, A as FAIL_CLOSED, At as DEV_KEYWORDS, B as SWIFT_PROTO_RE, C as usesTailwindUtilities, Ct as FileSizeVerdict, D as MAX_TOKENS, Dt as countLines, E as MAX_EXA_RESULTS, Et as countFrameworkCodeLines, F as installGuard, Ft as isApexCommand, G as CODE_MUTATORS, H as interfaceSeparationGuard, I as GO_DECL_RE, It as requiredArchSkill, J as SAFE_PREFIXES, K as CODE_REDIRECT, L as JAVA_DECL_RE, M as clearUserGuards, Mt as ProjectType, N as registerGuard, Nt as detectModularArchitecture, O as capVerbosity, Ot as evaluateFileSize, P as runGuards, Pt as detectProjectType, Q as protectedPathGuard, R as PHP_DECL_RE, S as skillTriggerGate, St as matchPatterns, T as frameworkSolidGate, Tt as SOLID_REF, U as bashWriteGuard, V as TS_DECL_RE, W as ASK_WRITERS, X as PROTECTED_FRAGMENTS, Y as SESSION_STATE_FRAGMENT, Z as PROTECTED_GIT_RE, _ as DEV_VERBS, _t as GIT_BLOCKED, a as firstHeading, at as APEX_GATES, b as detectClaudeMdProjectType, bt as SYSTEM_INSTALL, c as parseEntry, ct as brainstormGate, d as EXCLUDE_DIRS, dt as freshnessGate, et as CRITICAL_PATTERNS, f as PROJECT_INDICATORS, ft as solidReadGate, g as loadApexTaskState, gt as GIT_ASK, h as buildApexTaskInjection, ht as PolicyResult, i as firstComment, it as GuardContext, j as GUARDS, jt as ModularArchitecture, k as detectCreationIntent, kt as detectFramework, l as parseBodyDesc, lt as docConsultedGate, m as buildApexTaskContext, mt as PolicyContext, n as missingSeoElements, nt as securityGuard, o as TreeEntry, ot as ApexContext, p as ApexTaskState, pt as evaluate, q as FILE_REDIRECT, r as descFromText, rt as Guard, s as parseEnrichment, st as ApexGate, t as isHtmlLike, tt as LabeledPattern, u as parseField, ut as evaluateApex, v as buildApexInstruction, vt as PROJECT_INSTALL, w as SKILL_TRIGGERS, wt as PLUGINS_DIR, x as detectRequiredSkills, xt as isRalphMode, y as buildClaudeMdContext, yt as RALPH_SAFE, z as PY_MODEL_RE } from "./index-BqfOFKm7.mjs";
9
9
  import { n as RouteResult, r as ScoredRef, t as RefMeta } from "./types-CY5qT2X1.mjs";
10
10
  import { a as ReminderState, c as readState, d as throttleMs, i as registryFile, l as setStateField, n as addRoot, o as lessonsFileFor, r as readRoots, s as nowStamp, t as ensureMemoryGitignore, u as stateFileFor } from "./index-DLYhervv.mjs";
11
11
  import { a as globToRe, i as scoreReferences, n as toRefMeta, o as parseFrontmatter, r as routeReferences, t as loadRefs } from "./index-mmRF3KNp.mjs";
package/dist/index.mjs CHANGED
@@ -4,7 +4,7 @@ import { n as STATE_ROOT, r as projectLayout, t as STATE_GITIGNORE } from "./lay
4
4
  import { t as compactJson } from "./compact-json-DK2nX-MK.mjs";
5
5
  import { i as walkUpFor, n as projectRoot, r as projectRootOrNull, t as isCodeFile } from "./project-root-3kk7gCOp.mjs";
6
6
  import { n as detectMode, r as modeFor, t as detectHarness } from "./harness-C8Nxxyn_.mjs";
7
- import { A as MAX_EXA_RESULTS, B as detectFramework, D as frameworkSolidGate, E as SKILL_TRIGGERS, F as brainstormGate, G as requiredArchSkill, H as detectModularArchitecture, I as docConsultedGate, L as evaluateApex, M as capVerbosity, N as detectCreationIntent, P as APEX_GATES, R as freshnessGate, S as usesTailwindUtilities, U as detectProjectType, V as DEV_KEYWORDS, W as isApexCommand, a as firstHeading, b as detectRequiredSkills, c as EXCLUDE_DIRS, d as buildApexTaskInjection, f as loadApexTaskState, g as detectClaudeMdProjectType, h as buildClaudeMdContext, i as firstComment, j as MAX_TOKENS, l as PROJECT_INDICATORS, m as buildApexInstruction, n as missingSeoElements, o as parseEnrichment, p as DEV_VERBS, r as descFromText, s as parseEntry, t as isHtmlLike, u as buildApexTaskContext, v as parseBodyDesc, x as skillTriggerGate, y as parseField, z as solidReadGate } from "./validate-DVV4nRfr.mjs";
7
+ import { A as MAX_EXA_RESULTS, B as detectFramework, D as frameworkSolidGate, E as SKILL_TRIGGERS, F as brainstormGate, G as requiredArchSkill, H as detectModularArchitecture, I as docConsultedGate, L as evaluateApex, M as capVerbosity, N as detectCreationIntent, P as APEX_GATES, R as freshnessGate, S as usesTailwindUtilities, U as detectProjectType, V as DEV_KEYWORDS, W as isApexCommand, a as firstHeading, b as detectRequiredSkills, c as EXCLUDE_DIRS, d as buildApexTaskInjection, f as loadApexTaskState, g as detectClaudeMdProjectType, h as buildClaudeMdContext, i as firstComment, j as MAX_TOKENS, l as PROJECT_INDICATORS, m as buildApexInstruction, n as missingSeoElements, o as parseEnrichment, p as DEV_VERBS, r as descFromText, s as parseEntry, t as isHtmlLike, u as buildApexTaskContext, v as parseBodyDesc, x as skillTriggerGate, y as parseField, z as solidReadGate } from "./validate-ebDjIyOa.mjs";
8
8
  import { A as PROJECT_INSTALL, C as PROTECTED_GIT_RE, D as securityGuard, E as CRITICAL_PATTERNS, F as PLUGINS_DIR, I as SOLID_REF, L as countFrameworkCodeLines, M as SYSTEM_INSTALL, N as isRalphMode, O as GIT_ASK, P as matchPatterns, R as countLines, S as PROTECTED_FRAGMENTS, T as ASK_PATTERNS, _ as CODE_MUTATORS, a as registerGuard, b as SAFE_PREFIXES, c as GO_DECL_RE, d as PY_MODEL_RE, f as SWIFT_PROTO_RE, g as ASK_WRITERS, h as bashWriteGuard, i as clearUserGuards, j as RALPH_SAFE, k as GIT_BLOCKED, l as JAVA_DECL_RE, m as interfaceSeparationGuard, n as FAIL_CLOSED, o as runGuards, p as TS_DECL_RE, r as GUARDS, s as installGuard, t as evaluate, u as PHP_DECL_RE, v as CODE_REDIRECT, w as protectedPathGuard, x as SESSION_STATE_FRAGMENT, y as FILE_REDIRECT, z as evaluateFileSize } from "./evaluate-qYC5xClb.mjs";
9
9
  import { i as resolveSessions, n as formatDocSatisfactionStatus, r as isDocConsulted, t as formatDocDeny } from "./doc-helpers-CWZegVdR.mjs";
10
10
  import { i as parseFrontmatter, n as scoreReferences, r as globToRe, t as routeReferences } from "./router-PKVNBHge.mjs";
@@ -1,2 +1,2 @@
1
- import { $ as ASK_PATTERNS, A as FAIL_CLOSED, At as DEV_KEYWORDS, B as SWIFT_PROTO_RE, C as usesTailwindUtilities, Ct as FileSizeVerdict, D as MAX_TOKENS, Dt as countLines, E as MAX_EXA_RESULTS, Et as countFrameworkCodeLines, F as installGuard, Ft as isApexCommand, G as CODE_MUTATORS, H as interfaceSeparationGuard, I as GO_DECL_RE, It as requiredArchSkill, J as SAFE_PREFIXES, K as CODE_REDIRECT, L as JAVA_DECL_RE, M as clearUserGuards, Mt as ProjectType, N as registerGuard, Nt as detectModularArchitecture, O as capVerbosity, Ot as evaluateFileSize, P as runGuards, Pt as detectProjectType, Q as protectedPathGuard, R as PHP_DECL_RE, S as skillTriggerGate, St as matchPatterns, T as frameworkSolidGate, Tt as SOLID_REF, U as bashWriteGuard, V as TS_DECL_RE, W as ASK_WRITERS, X as PROTECTED_FRAGMENTS, Y as SESSION_STATE_FRAGMENT, Z as PROTECTED_GIT_RE, _ as DEV_VERBS, _t as GIT_BLOCKED, a as firstHeading, at as APEX_GATES, b as detectClaudeMdProjectType, bt as SYSTEM_INSTALL, c as parseEntry, ct as brainstormGate, d as EXCLUDE_DIRS, dt as freshnessGate, et as CRITICAL_PATTERNS, f as PROJECT_INDICATORS, ft as solidReadGate, g as loadApexTaskState, gt as GIT_ASK, h as buildApexTaskInjection, ht as PolicyResult, i as firstComment, it as GuardContext, j as GUARDS, jt as ModularArchitecture, k as detectCreationIntent, kt as detectFramework, l as parseBodyDesc, lt as docConsultedGate, m as buildApexTaskContext, mt as PolicyContext, n as missingSeoElements, nt as securityGuard, o as TreeEntry, ot as ApexContext, p as ApexTaskState, pt as evaluate, q as FILE_REDIRECT, r as descFromText, rt as Guard, s as parseEnrichment, st as ApexGate, t as isHtmlLike, tt as LabeledPattern, u as parseField, ut as evaluateApex, v as buildApexInstruction, vt as PROJECT_INSTALL, w as SKILL_TRIGGERS, wt as PLUGINS_DIR, x as detectRequiredSkills, xt as isRalphMode, y as buildClaudeMdContext, yt as RALPH_SAFE, z as PY_MODEL_RE } from "../index-l9Xvxrx7.mjs";
1
+ import { $ as ASK_PATTERNS, A as FAIL_CLOSED, At as DEV_KEYWORDS, B as SWIFT_PROTO_RE, C as usesTailwindUtilities, Ct as FileSizeVerdict, D as MAX_TOKENS, Dt as countLines, E as MAX_EXA_RESULTS, Et as countFrameworkCodeLines, F as installGuard, Ft as isApexCommand, G as CODE_MUTATORS, H as interfaceSeparationGuard, I as GO_DECL_RE, It as requiredArchSkill, J as SAFE_PREFIXES, K as CODE_REDIRECT, L as JAVA_DECL_RE, M as clearUserGuards, Mt as ProjectType, N as registerGuard, Nt as detectModularArchitecture, O as capVerbosity, Ot as evaluateFileSize, P as runGuards, Pt as detectProjectType, Q as protectedPathGuard, R as PHP_DECL_RE, S as skillTriggerGate, St as matchPatterns, T as frameworkSolidGate, Tt as SOLID_REF, U as bashWriteGuard, V as TS_DECL_RE, W as ASK_WRITERS, X as PROTECTED_FRAGMENTS, Y as SESSION_STATE_FRAGMENT, Z as PROTECTED_GIT_RE, _ as DEV_VERBS, _t as GIT_BLOCKED, a as firstHeading, at as APEX_GATES, b as detectClaudeMdProjectType, bt as SYSTEM_INSTALL, c as parseEntry, ct as brainstormGate, d as EXCLUDE_DIRS, dt as freshnessGate, et as CRITICAL_PATTERNS, f as PROJECT_INDICATORS, ft as solidReadGate, g as loadApexTaskState, gt as GIT_ASK, h as buildApexTaskInjection, ht as PolicyResult, i as firstComment, it as GuardContext, j as GUARDS, jt as ModularArchitecture, k as detectCreationIntent, kt as detectFramework, l as parseBodyDesc, lt as docConsultedGate, m as buildApexTaskContext, mt as PolicyContext, n as missingSeoElements, nt as securityGuard, o as TreeEntry, ot as ApexContext, p as ApexTaskState, pt as evaluate, q as FILE_REDIRECT, r as descFromText, rt as Guard, s as parseEnrichment, st as ApexGate, t as isHtmlLike, tt as LabeledPattern, u as parseField, ut as evaluateApex, v as buildApexInstruction, vt as PROJECT_INSTALL, w as SKILL_TRIGGERS, wt as PLUGINS_DIR, x as detectRequiredSkills, xt as isRalphMode, y as buildClaudeMdContext, yt as RALPH_SAFE, z as PY_MODEL_RE } from "../index-BqfOFKm7.mjs";
2
2
  export { APEX_GATES, ASK_PATTERNS, ASK_WRITERS, ApexContext, ApexGate, ApexTaskState, CODE_MUTATORS, CODE_REDIRECT, CRITICAL_PATTERNS, DEV_KEYWORDS, DEV_VERBS, EXCLUDE_DIRS, FAIL_CLOSED, FILE_REDIRECT, FileSizeVerdict, GIT_ASK, GIT_BLOCKED, GO_DECL_RE, GUARDS, Guard, GuardContext, JAVA_DECL_RE, LabeledPattern, MAX_EXA_RESULTS, MAX_TOKENS, ModularArchitecture, PHP_DECL_RE, PLUGINS_DIR, PROJECT_INDICATORS, PROJECT_INSTALL, PROTECTED_FRAGMENTS, PROTECTED_GIT_RE, PY_MODEL_RE, type PolicyContext, type PolicyResult, ProjectType, RALPH_SAFE, SAFE_PREFIXES, SESSION_STATE_FRAGMENT, SKILL_TRIGGERS, SOLID_REF, SWIFT_PROTO_RE, SYSTEM_INSTALL, TS_DECL_RE, TreeEntry, bashWriteGuard, brainstormGate, buildApexInstruction, buildApexTaskContext, buildApexTaskInjection, buildClaudeMdContext, capVerbosity, clearUserGuards, countFrameworkCodeLines, countLines, descFromText, detectClaudeMdProjectType, detectCreationIntent, detectFramework, detectModularArchitecture, detectProjectType, detectRequiredSkills, docConsultedGate, evaluate, evaluateApex, evaluateFileSize, firstComment, firstHeading, frameworkSolidGate, freshnessGate, installGuard, interfaceSeparationGuard, isApexCommand, isHtmlLike, isRalphMode, loadApexTaskState, matchPatterns, missingSeoElements, parseBodyDesc, parseEnrichment, parseEntry, parseField, protectedPathGuard, registerGuard, requiredArchSkill, runGuards, securityGuard, skillTriggerGate, solidReadGate, usesTailwindUtilities };
@@ -1,4 +1,4 @@
1
- import { A as MAX_EXA_RESULTS, B as detectFramework, D as frameworkSolidGate, E as SKILL_TRIGGERS, F as brainstormGate, G as requiredArchSkill, H as detectModularArchitecture, I as docConsultedGate, L as evaluateApex, M as capVerbosity, N as detectCreationIntent, P as APEX_GATES, R as freshnessGate, S as usesTailwindUtilities, U as detectProjectType, V as DEV_KEYWORDS, W as isApexCommand, a as firstHeading, b as detectRequiredSkills, c as EXCLUDE_DIRS, d as buildApexTaskInjection, f as loadApexTaskState, g as detectClaudeMdProjectType, h as buildClaudeMdContext, i as firstComment, j as MAX_TOKENS, l as PROJECT_INDICATORS, m as buildApexInstruction, n as missingSeoElements, o as parseEnrichment, p as DEV_VERBS, r as descFromText, s as parseEntry, t as isHtmlLike, u as buildApexTaskContext, v as parseBodyDesc, x as skillTriggerGate, y as parseField, z as solidReadGate } from "../validate-DVV4nRfr.mjs";
1
+ import { A as MAX_EXA_RESULTS, B as detectFramework, D as frameworkSolidGate, E as SKILL_TRIGGERS, F as brainstormGate, G as requiredArchSkill, H as detectModularArchitecture, I as docConsultedGate, L as evaluateApex, M as capVerbosity, N as detectCreationIntent, P as APEX_GATES, R as freshnessGate, S as usesTailwindUtilities, U as detectProjectType, V as DEV_KEYWORDS, W as isApexCommand, a as firstHeading, b as detectRequiredSkills, c as EXCLUDE_DIRS, d as buildApexTaskInjection, f as loadApexTaskState, g as detectClaudeMdProjectType, h as buildClaudeMdContext, i as firstComment, j as MAX_TOKENS, l as PROJECT_INDICATORS, m as buildApexInstruction, n as missingSeoElements, o as parseEnrichment, p as DEV_VERBS, r as descFromText, s as parseEntry, t as isHtmlLike, u as buildApexTaskContext, v as parseBodyDesc, x as skillTriggerGate, y as parseField, z as solidReadGate } from "../validate-ebDjIyOa.mjs";
2
2
  import { A as PROJECT_INSTALL, C as PROTECTED_GIT_RE, D as securityGuard, E as CRITICAL_PATTERNS, F as PLUGINS_DIR, I as SOLID_REF, L as countFrameworkCodeLines, M as SYSTEM_INSTALL, N as isRalphMode, O as GIT_ASK, P as matchPatterns, R as countLines, S as PROTECTED_FRAGMENTS, T as ASK_PATTERNS, _ as CODE_MUTATORS, a as registerGuard, b as SAFE_PREFIXES, c as GO_DECL_RE, d as PY_MODEL_RE, f as SWIFT_PROTO_RE, g as ASK_WRITERS, h as bashWriteGuard, i as clearUserGuards, j as RALPH_SAFE, k as GIT_BLOCKED, l as JAVA_DECL_RE, m as interfaceSeparationGuard, n as FAIL_CLOSED, o as runGuards, p as TS_DECL_RE, r as GUARDS, s as installGuard, t as evaluate, u as PHP_DECL_RE, v as CODE_REDIRECT, w as protectedPathGuard, x as SESSION_STATE_FRAGMENT, y as FILE_REDIRECT, z as evaluateFileSize } from "../evaluate-qYC5xClb.mjs";
3
3
  import "../policy-la_KkjCS.mjs";
4
4
  export { APEX_GATES, ASK_PATTERNS, ASK_WRITERS, CODE_MUTATORS, CODE_REDIRECT, CRITICAL_PATTERNS, DEV_KEYWORDS, DEV_VERBS, EXCLUDE_DIRS, FAIL_CLOSED, FILE_REDIRECT, GIT_ASK, GIT_BLOCKED, GO_DECL_RE, GUARDS, JAVA_DECL_RE, MAX_EXA_RESULTS, MAX_TOKENS, PHP_DECL_RE, PLUGINS_DIR, PROJECT_INDICATORS, PROJECT_INSTALL, PROTECTED_FRAGMENTS, PROTECTED_GIT_RE, PY_MODEL_RE, RALPH_SAFE, SAFE_PREFIXES, SESSION_STATE_FRAGMENT, SKILL_TRIGGERS, SOLID_REF, SWIFT_PROTO_RE, SYSTEM_INSTALL, TS_DECL_RE, bashWriteGuard, brainstormGate, buildApexInstruction, buildApexTaskContext, buildApexTaskInjection, buildClaudeMdContext, capVerbosity, clearUserGuards, countFrameworkCodeLines, countLines, descFromText, detectClaudeMdProjectType, detectCreationIntent, detectFramework, detectModularArchitecture, detectProjectType, detectRequiredSkills, docConsultedGate, evaluate, evaluateApex, evaluateFileSize, firstComment, firstHeading, frameworkSolidGate, freshnessGate, installGuard, interfaceSeparationGuard, isApexCommand, isHtmlLike, isRalphMode, loadApexTaskState, matchPatterns, missingSeoElements, parseBodyDesc, parseEnrichment, parseEntry, parseField, protectedPathGuard, registerGuard, requiredArchSkill, runGuards, securityGuard, skillTriggerGate, solidReadGate, usesTailwindUtilities };
@@ -1,6 +1,6 @@
1
1
  import { t as Prompt } from "../types-D56jSgD9.mjs";
2
2
  import { t as RefMeta } from "../types-CY5qT2X1.mjs";
3
- import { t as AgentQuality } from "../session-state-D4F_Dub6.mjs";
3
+ import { t as AgentQuality } from "../session-state-V8Jp-KE5.mjs";
4
4
 
5
5
  //#region src/runtime/paths.d.ts
6
6
  /**
@@ -53,6 +53,7 @@ type Activity = {
53
53
  } | {
54
54
  kind: "ref";
55
55
  path: string;
56
+ ts?: number;
56
57
  };
57
58
  /** Apply an activity to a session's track and persist it (PostToolUse path). */
58
59
  declare function recordActivity(file: string, activity: Activity): Promise<void>;
@@ -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-mKZxP4oZ.mjs";
3
- import { $ as saveApexState, A as trackSkillRead, At as securityStateDir, B as writePluginMap, C as seoPostToolUse, Ct as defaultStateDir, D as postTrackingSideEffects, Dt as isoUtc, E as securityAdvisory, Et as normalizeEvent, F as dispatchLessons, G as mergeLines, H as isProject, I as lessonsFileFor, J as listChildren, K as countFiles, L as lessonsStateFileFor, M as dispatchLifecycle, Mt as todayUtc, N as aipilotPostToolUse, O as trackWatchResearch, Ot as loadSecurityState, P as dispatchAipilot, Q as cleanupSession, R as cartoSessionStart, S as postEditContext, St as taskContext, T as dispatchMemory, Tt as trackFile, U as writeTree, V as generateProjectMap, W as loadEnriched, X as trackSessionChanges, Y as postEditTypescript, Z as validateRulesLoaded, _ as preCommitGate, _t as trimLogFile, a as recordActivity, at as validateSolidGate, b as extractSymbols, bt as projectContext, c as MCP_TTL_MS, ct as detectSolidProfile, d as isMcpTool, dt as readRules, et as logToolFailure, f as queryOf, ft as runSessionStartCleanups, g as gate, gt as removeOldFiles, h as TRIVIAL_BUDGET, ht as purgeTtlTree, i as respond, it as validateTailwind, j as trackEnrichment, jt as securityStatePath, k as trackMcpResearch, kt as saveSecurityState, l as WEBFETCH_TTL_MS, lt as solidDetectStart, m as REQUIRED_AGENTS, mt as pruneEmptyDirs, n as activityFor, nt as trackAgentMemory, o as mcpPostStore, ot as checkFileSize, p as DEFAULT_WINDOW_MS, pt as sessionStartCore, q as getFileDesc, r as handlePre, rt as subagentCacheContext, s as mcpPreIntercept, st as countLoc, t as handleHook, tt as validateTeammateOutput, u as cacheQueryOf, ut as injectRules, v as detectDuplication, vt as devContext, w as seoPostToolUseResponse, wt as projectHash, x as lifecycleStdout, xt as promptSubmitContext, y as dryGate, yt as gitContext, z as generateEcosystemMap } from "../handle-DUlUbhyF.mjs";
3
+ import { $ as saveApexState, A as trackSkillRead, At as securityStateDir, B as writePluginMap, C as seoPostToolUse, Ct as defaultStateDir, D as postTrackingSideEffects, Dt as isoUtc, E as securityAdvisory, Et as normalizeEvent, F as dispatchLessons, G as mergeLines, H as isProject, I as lessonsFileFor, J as listChildren, K as countFiles, L as lessonsStateFileFor, M as dispatchLifecycle, Mt as todayUtc, N as aipilotPostToolUse, O as trackWatchResearch, Ot as loadSecurityState, P as dispatchAipilot, Q as cleanupSession, R as cartoSessionStart, S as postEditContext, St as taskContext, T as dispatchMemory, Tt as trackFile, U as writeTree, V as generateProjectMap, W as loadEnriched, X as trackSessionChanges, Y as postEditTypescript, Z as validateRulesLoaded, _ as preCommitGate, _t as trimLogFile, a as recordActivity, at as validateSolidGate, b as extractSymbols, bt as projectContext, c as MCP_TTL_MS, ct as detectSolidProfile, d as isMcpTool, dt as readRules, et as logToolFailure, f as queryOf, ft as runSessionStartCleanups, g as gate, gt as removeOldFiles, h as TRIVIAL_BUDGET, ht as purgeTtlTree, i as respond, it as validateTailwind, j as trackEnrichment, jt as securityStatePath, k as trackMcpResearch, kt as saveSecurityState, l as WEBFETCH_TTL_MS, lt as solidDetectStart, m as REQUIRED_AGENTS, mt as pruneEmptyDirs, n as activityFor, nt as trackAgentMemory, o as mcpPostStore, ot as checkFileSize, p as DEFAULT_WINDOW_MS, pt as sessionStartCore, q as getFileDesc, r as handlePre, rt as subagentCacheContext, s as mcpPreIntercept, st as countLoc, t as handleHook, tt as validateTeammateOutput, u as cacheQueryOf, ut as injectRules, v as detectDuplication, vt as devContext, w as seoPostToolUseResponse, wt as projectHash, x as lifecycleStdout, xt as promptSubmitContext, y as dryGate, yt as gitContext, z as generateEcosystemMap } from "../handle-CCV0ycdr.mjs";
4
4
  //#region src/runtime/storage.ts
5
5
  /**
6
6
  * The project's single state dir (`<root>/.harness`) — neutral + harness-agnostic,
@@ -7,6 +7,15 @@ type AgentQuality = "sufficient" | "insufficient";
7
7
  interface SessionTrack {
8
8
  authorizations: Record<string, AuthEntry>;
9
9
  refsRead: string[];
10
+ /**
11
+ * Epoch-ms timestamp of each reference read, keyed by `refsRead` path (parity
12
+ * track-solid-reads.py `solid_reads[].timestamp`). Optional: tracks persisted
13
+ * before this field existed only carry `refsRead`, and such paths count as
14
+ * read (backward compat). PARITY: only the SOLID-read gate TTL-checks these —
15
+ * Python TTL-izes SOLID reads exclusively (require-solid-read.py); the other
16
+ * refsRead consumers (skill-trigger/shadcn/tailwind/design) stay session-scoped.
17
+ */
18
+ refsReadAt?: Record<string, number>;
10
19
  agents: {
11
20
  name: string;
12
21
  ts: number;
@@ -19,8 +28,15 @@ interface SessionTrack {
19
28
  declare function emptyTrack(): SessionTrack;
20
29
  /** Record a doc consultation (Context7/Exa) for a framework in this session. Immutable. */
21
30
  declare function recordDoc(track: SessionTrack, framework: string, sessionId: string, source: string): SessionTrack;
22
- /** Record that a SOLID reference file was read (deduped). Immutable. */
23
- declare function recordRefRead(track: SessionTrack, path: string): SessionTrack;
31
+ /**
32
+ * Record that a SOLID reference file was read (deduped). Immutable. When `now`
33
+ * (epoch ms — the tool event's own timestamp, never a fresh `Date.now()`) is
34
+ * supplied, the read is stamped in `refsReadAt`, refreshed on re-reads so the
35
+ * LATEST read drives the SOLID TTL (parity track-solid-reads.py, which appends
36
+ * a timestamped entry per read; require-solid-read.py checks the most recent).
37
+ * Callers that omit `now` keep the legacy untimestamped behavior.
38
+ */
39
+ declare function recordRefRead(track: SessionTrack, path: string, now?: number): SessionTrack;
24
40
  /** Record an agent/tool call with a timestamp + optional quality. Immutable. */
25
41
  declare function recordAgent(track: SessionTrack, name: string, ts: number, quality?: AgentQuality): SessionTrack;
26
42
  /** True when ALL of `names` ran within `windowMs` with non-insufficient quality. */
@@ -32,11 +32,27 @@ function recordDoc(track, framework, sessionId, source) {
32
32
  }
33
33
  };
34
34
  }
35
- /** Record that a SOLID reference file was read (deduped). Immutable. */
36
- function recordRefRead(track, path) {
37
- return track.refsRead.includes(path) ? track : {
35
+ /**
36
+ * Record that a SOLID reference file was read (deduped). Immutable. When `now`
37
+ * (epoch ms the tool event's own timestamp, never a fresh `Date.now()`) is
38
+ * supplied, the read is stamped in `refsReadAt`, refreshed on re-reads so the
39
+ * LATEST read drives the SOLID TTL (parity track-solid-reads.py, which appends
40
+ * a timestamped entry per read; require-solid-read.py checks the most recent).
41
+ * Callers that omit `now` keep the legacy untimestamped behavior.
42
+ */
43
+ function recordRefRead(track, path, now) {
44
+ const refsRead = track.refsRead.includes(path) ? track.refsRead : [...track.refsRead, path];
45
+ if (now === void 0) return refsRead === track.refsRead ? track : {
46
+ ...track,
47
+ refsRead
48
+ };
49
+ return {
38
50
  ...track,
39
- refsRead: [...track.refsRead, path]
51
+ refsRead,
52
+ refsReadAt: {
53
+ ...track.refsReadAt,
54
+ [path]: now
55
+ }
40
56
  };
41
57
  }
42
58
  /** Record an agent/tool call with a timestamp + optional quality. Immutable. */
@@ -1,4 +1,4 @@
1
- import { a as recordAgent, c as recordRefRead, i as emptyTrack, l as recordTrivialEdit, n as SessionTrack, o as recordBrainstormRequired, r as agentsFresh, s as recordDoc, t as AgentQuality, u as trivialCount } from "../session-state-D4F_Dub6.mjs";
1
+ import { a as recordAgent, c as recordRefRead, i as emptyTrack, l as recordTrivialEdit, n as SessionTrack, o as recordBrainstormRequired, r as agentsFresh, s as recordDoc, t as AgentQuality, u as trivialCount } from "../session-state-V8Jp-KE5.mjs";
2
2
 
3
3
  //#region src/tracking/store.d.ts
4
4
  /**
@@ -1,2 +1,2 @@
1
- import { a as emptyTrack, c as recordDoc, d as trivialCount, i as agentsFresh, l as recordRefRead, n as saveTrack, o as recordAgent, s as recordBrainstormRequired, t as loadTrack, u as recordTrivialEdit } from "../store-CNjFenWe.mjs";
1
+ import { a as emptyTrack, c as recordDoc, d as trivialCount, i as agentsFresh, l as recordRefRead, n as saveTrack, o as recordAgent, s as recordBrainstormRequired, t as loadTrack, u as recordTrivialEdit } from "../store-ARx-GQSQ.mjs";
2
2
  export { agentsFresh, emptyTrack, loadTrack, recordAgent, recordBrainstormRequired, recordDoc, recordRefRead, recordTrivialEdit, saveTrack, trivialCount };
@@ -109,11 +109,26 @@ function detectFramework(filePath, content) {
109
109
  }
110
110
  //#endregion
111
111
  //#region src/policy/apex-gates.ts
112
- /** Gate: the routed SOLID references for this edit must have been read. */
112
+ /**
113
+ * True when the SOLID ref read at `path` still counts. Parity require-solid-read.py
114
+ * `_check_solid_read` (FUSE_ENFORCE_TTL_SEC, default 120s): a SOLID read older than
115
+ * the window has expired and must be redone. PARITY NOTE: the TTL applies ONLY to
116
+ * this gate — Python TTL-izes SOLID reads exclusively; the other refsRead consumers
117
+ * (skillTriggerGate, shadcn/tailwind/design gates) stay session-scoped, so this
118
+ * predicate must never leak into them. Backward compat: a path with no `refsReadAt`
119
+ * stamp (tracks recorded before the field existed) or a context without a clock
120
+ * (`now` absent) counts as read.
121
+ */
122
+ function refReadFresh(ctx, path, windowMs) {
123
+ const at = ctx.refsReadAt?.[path];
124
+ return at === void 0 || ctx.now === void 0 || ctx.now - at < windowMs;
125
+ }
126
+ /** Gate: the routed SOLID references for this edit must have been read within the TTL. */
113
127
  const solidReadGate = (ctx) => {
114
128
  if (!ctx.refs?.length) return null;
115
129
  const routed = routeReferences(ctx.refs, ctx.filePath, ctx.content);
116
- const ttl = ttlLabel(Math.round((ctx.windowMs ?? 120 * 1e3) / 1e3));
130
+ const windowMs = ctx.windowMs ?? 120 * 1e3;
131
+ const ttl = ttlLabel(Math.round(windowMs / 1e3));
117
132
  if (!routed) {
118
133
  const ref = SOLID_REF[ctx.framework] ?? "generic/";
119
134
  return {
@@ -123,7 +138,7 @@ const solidReadGate = (ctx) => {
123
138
  actions: [`Read ${PLUGINS_DIR}/${ref}SKILL.md`]
124
139
  };
125
140
  }
126
- const read = new Set(ctx.refsRead ?? []);
141
+ const read = new Set((ctx.refsRead ?? []).filter((p) => refReadFresh(ctx, p, windowMs)));
127
142
  const missing = !!routed.skillPath && read.has(routed.skillPath) ? [] : routed.required.map((r) => r.meta.filePath).filter((p) => !read.has(p));
128
143
  if (missing.length === 0) return null;
129
144
  const optional = routed.optional.map((r) => r.meta.filePath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fusengine/harness",
3
- "version": "0.1.47",
3
+ "version": "0.1.48",
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",