@fusengine/harness 0.1.55 → 0.1.56
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-DZvP_9xB.mjs";
|
|
6
6
|
import { n as writeInitFile, t as initFor } from "../run-Do2JltgU.mjs";
|
|
7
|
-
import { F as runDoctor, I as runningVersion, L as versionBanner, Lt as todayUtc, t as handleHook } from "../handle-
|
|
7
|
+
import { F as runDoctor, I as runningVersion, L as versionBanner, Lt as todayUtc, t as handleHook } from "../handle-CoyvRORV.mjs";
|
|
8
8
|
import { delimiter, join } from "node:path";
|
|
9
9
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
10
10
|
import { homedir } from "node:os";
|
|
@@ -2361,20 +2361,29 @@ function firstSentence(s) {
|
|
|
2361
2361
|
return (s.split(/(?<=\.)\s/)[0] ?? s).trim();
|
|
2362
2362
|
}
|
|
2363
2363
|
/**
|
|
2364
|
-
*
|
|
2365
|
-
*
|
|
2366
|
-
*
|
|
2367
|
-
*
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
*
|
|
2364
|
+
* The bullet's `narrative → rule` delimiter: a SPACED arrow only. A GLUED arrow
|
|
2365
|
+
* between tokens (e.g. `120s→300s`, `s→3`) is prose the author wrote, never a
|
|
2366
|
+
* delimiter — matching on `→` alone chopped rules mid-token (bug: `300s) pensant
|
|
2367
|
+
* corriger…`). Also used to split rule-internal clauses.
|
|
2368
|
+
*/
|
|
2369
|
+
const RULE_ARROW = /\s+→\s+/;
|
|
2370
|
+
/**
|
|
2371
|
+
* Distil the actionable rule from a bullet body. With no spaced arrow the whole
|
|
2372
|
+
* bullet is the rule → its first sentence. Otherwise the rule is everything after
|
|
2373
|
+
* the FIRST spaced arrow; its spaced-arrow-delimited segments are kept whole
|
|
2374
|
+
* except for TRAILING short asides (< {@link MIN_RULE} chars, e.g. `→ (cf.
|
|
2375
|
+
* lecture).`) which are dropped — so an arrow used as PROSE inside a rule (`maps
|
|
2376
|
+
* X → Y doit…`) is preserved intact rather than chopped at the arrow. When the
|
|
2377
|
+
* kept rule is still under {@link MIN_RULE} chars, fall back to the first sentence
|
|
2378
|
+
* of the WHOLE rule part (never the narrative), avoiding an illegible stub.
|
|
2372
2379
|
*/
|
|
2373
2380
|
function distillRule(text) {
|
|
2374
|
-
const
|
|
2375
|
-
if (
|
|
2376
|
-
const rulePart = text.slice(
|
|
2377
|
-
const
|
|
2381
|
+
const sep = text.search(RULE_ARROW);
|
|
2382
|
+
if (sep < 0) return firstSentence(text);
|
|
2383
|
+
const rulePart = text.slice(sep).replace(RULE_ARROW, "").trim();
|
|
2384
|
+
const segments = rulePart.split(RULE_ARROW).map((s) => s.trim()).filter(Boolean);
|
|
2385
|
+
while (segments.length > 1 && (segments[segments.length - 1]?.length ?? 0) < 40) segments.pop();
|
|
2386
|
+
const rule = firstSentence(segments.join(" → "));
|
|
2378
2387
|
return rule.length >= 40 ? rule : firstSentence(rulePart);
|
|
2379
2388
|
}
|
|
2380
2389
|
/** Collapse one older bullet to `- [date] <rule>`: {@link distillRule}, capped. */
|
|
@@ -5587,6 +5596,50 @@ function frameworkSkillGate(input, refsRead, existingCodeLines) {
|
|
|
5587
5596
|
return skillTriggerGate(input.framework, content, refsRead, forced, input.cwd, input.filePath);
|
|
5588
5597
|
}
|
|
5589
5598
|
//#endregion
|
|
5599
|
+
//#region src/freshness/ref-evidence.ts
|
|
5600
|
+
/**
|
|
5601
|
+
* Platform-transcript reconciliation for `.md` reference reads — the durable
|
|
5602
|
+
* counterpart to {@link agentsRanFromTranscript} for agent freshness.
|
|
5603
|
+
*
|
|
5604
|
+
* WHY: the session track is persisted by a non-atomic load→mutate→save. Under
|
|
5605
|
+
* the multi-plugin hook fan-out (one hook process per installed plugin, ×N) plus
|
|
5606
|
+
* back-to-back tool events, concurrent writers clobber each other (lost update).
|
|
5607
|
+
* `agents`/`authorizations` self-heal — every explore/research/doc call rewrites
|
|
5608
|
+
* them, so a lost update lands again on the next of hundreds of writes — but a
|
|
5609
|
+
* `refsRead` entry is written ONCE (when the `.md` is Read), so a single lost
|
|
5610
|
+
* update erases it permanently, and the lead has no SubagentStop
|
|
5611
|
+
* {@link harvestAgentEvidence} pass to reconcile it (sub-agents do, which is why
|
|
5612
|
+
* only the LEAD's solidReadGate never credited). The Claude-authored transcript
|
|
5613
|
+
* is append-only and race-immune, so folding its `.md` Reads back into the track
|
|
5614
|
+
* restores the lost evidence; each gate still applies its own TTL/session policy.
|
|
5615
|
+
*/
|
|
5616
|
+
/**
|
|
5617
|
+
* Fold every `.md` `Read` in the transcript into `track` as a timestamped ref
|
|
5618
|
+
* read (immutably). PURE reconciliation — the caller owns the track; each read
|
|
5619
|
+
* is stamped with its transcript timestamp (unstamped → `now`, lenient, matching
|
|
5620
|
+
* {@link agentsRanFromTranscript}), and an existing MORE-recent stamp is never
|
|
5621
|
+
* rolled back. Fail-open: an absent/unreadable transcript returns `track`
|
|
5622
|
+
* unchanged (same reference).
|
|
5623
|
+
* @param track - The current (possibly race-damaged) session track.
|
|
5624
|
+
* @param transcriptPath - Claude `transcript_path` for this session.
|
|
5625
|
+
* @param now - Fallback epoch-ms for transcript entries the platform left unstamped.
|
|
5626
|
+
* @returns The track with transcript `.md` reads merged into `refsRead`/`refsReadAt`.
|
|
5627
|
+
*/
|
|
5628
|
+
function reconcileRefReadsFromTranscript(track, transcriptPath, now) {
|
|
5629
|
+
const uses = readAgentToolUses(transcriptPath);
|
|
5630
|
+
if (!uses) return track;
|
|
5631
|
+
let next = track;
|
|
5632
|
+
for (const u of uses) {
|
|
5633
|
+
if (u.name !== "Read") continue;
|
|
5634
|
+
const path = String(u.input?.file_path ?? u.input?.path ?? "");
|
|
5635
|
+
if (!path.endsWith(".md")) continue;
|
|
5636
|
+
const ts = u.ts ?? now;
|
|
5637
|
+
const prev = next.refsReadAt?.[path];
|
|
5638
|
+
if (prev === void 0 || prev < ts) next = recordRefRead(next, path, ts);
|
|
5639
|
+
}
|
|
5640
|
+
return next;
|
|
5641
|
+
}
|
|
5642
|
+
//#endregion
|
|
5590
5643
|
//#region src/policy/shadcn-skill-gate.ts
|
|
5591
5644
|
/** File extensions the shadcn gate polices (source: `\.(tsx|jsx|css|scss|json)$`). */
|
|
5592
5645
|
const SHADCN_FILE_RE = /\.(tsx|jsx|css|scss|json)$/;
|
|
@@ -6048,8 +6101,7 @@ async function runGates(input) {
|
|
|
6048
6101
|
if (modular) return modular;
|
|
6049
6102
|
if (!input.filePath) return null;
|
|
6050
6103
|
const filePath = input.filePath;
|
|
6051
|
-
const
|
|
6052
|
-
const track = await loadTrack(input.trackFile);
|
|
6104
|
+
const track = reconcileRefReadsFromTranscript(await loadTrack(input.trackFile), input.transcriptPath, input.now);
|
|
6053
6105
|
const solidOrSkill = frameworkSkillGate(input, track.refsRead, existingCodeLines);
|
|
6054
6106
|
if (solidOrSkill) return solidOrSkill;
|
|
6055
6107
|
if (isShadcnWrite(input.tool, filePath)) {
|
|
@@ -6070,7 +6122,7 @@ async function runGates(input) {
|
|
|
6070
6122
|
});
|
|
6071
6123
|
if (geminiBlock) return geminiBlock;
|
|
6072
6124
|
if (isApexScoped(input.filePath)) {
|
|
6073
|
-
const apex = await apexScopedGate(input, track,
|
|
6125
|
+
const apex = await apexScopedGate(input, track, input.windowMs ?? 12e4);
|
|
6074
6126
|
if (apex) return apex;
|
|
6075
6127
|
}
|
|
6076
6128
|
return dryGate(input.tool, input.filePath, input.content, input.cwd);
|
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 { $ as postEditTypescript, A as trackSkillRead, At as trackFile, B as lessonsFileFor, C as seoPostToolUse, Ct as gitContext, D as postTrackingSideEffects, Dt as taskContext, E as securityAdvisory, Et as promptSubmitContext, Ft as securityStateDir, G as generateProjectMap, H as cartoSessionStart, It as securityStatePath, J as loadEnriched, K as isProject, Lt as todayUtc, M as dispatchLifecycle, Mt as isoUtc, N as aipilotPostToolUse, Nt as loadSecurityState, O as trackWatchResearch, Ot as defaultStateDir, P as dispatchAipilot, Pt as saveSecurityState, Q as listChildren, R as dispatchLessons, S as postEditContext, St as devContext, T as dispatchMemory, Tt as claudeMdKey, U as generateEcosystemMap, V as lessonsStateFileFor, W as writePluginMap, X as countFiles, Y as mergeLines, Z as getFileDesc, _ as preCommitGate, _t as sessionStartCore, a as recordActivity, at as validateTeammateOutput, b as extractSymbols, bt as removeOldFiles, c as MCP_TTL_MS, ct as validateTailwind, d as isMcpTool, dt as countLoc, et as trackSessionChanges, f as queryOf, ft as detectSolidProfile, g as gate, gt as runSessionStartCleanups, h as TRIVIAL_BUDGET, ht as readRules, i as respond, it as logToolFailure, j as trackEnrichment, jt as normalizeEvent, k as trackMcpResearch, kt as projectHash, l as WEBFETCH_TTL_MS, lt as validateSolidGate, m as REQUIRED_AGENTS, mt as injectRules, n as activityFor, nt as cleanupSession, o as mcpPostStore, ot as trackAgentMemory, p as DEFAULT_WINDOW_MS, pt as solidDetectStart, q as writeTree, r as handlePre, rt as saveApexState, s as mcpPreIntercept, st as subagentCacheContext, t as handleHook, tt as validateRulesLoaded, u as cacheQueryOf, ut as checkFileSize, v as detectDuplication, vt as pruneEmptyDirs, w as seoPostToolUseResponse, wt as projectContext, x as lifecycleStdout, xt as trimLogFile, y as dryGate, yt as purgeTtlTree, z as lessonsArchiveFileFor } from "../handle-
|
|
3
|
+
import { $ as postEditTypescript, A as trackSkillRead, At as trackFile, B as lessonsFileFor, C as seoPostToolUse, Ct as gitContext, D as postTrackingSideEffects, Dt as taskContext, E as securityAdvisory, Et as promptSubmitContext, Ft as securityStateDir, G as generateProjectMap, H as cartoSessionStart, It as securityStatePath, J as loadEnriched, K as isProject, Lt as todayUtc, M as dispatchLifecycle, Mt as isoUtc, N as aipilotPostToolUse, Nt as loadSecurityState, O as trackWatchResearch, Ot as defaultStateDir, P as dispatchAipilot, Pt as saveSecurityState, Q as listChildren, R as dispatchLessons, S as postEditContext, St as devContext, T as dispatchMemory, Tt as claudeMdKey, U as generateEcosystemMap, V as lessonsStateFileFor, W as writePluginMap, X as countFiles, Y as mergeLines, Z as getFileDesc, _ as preCommitGate, _t as sessionStartCore, a as recordActivity, at as validateTeammateOutput, b as extractSymbols, bt as removeOldFiles, c as MCP_TTL_MS, ct as validateTailwind, d as isMcpTool, dt as countLoc, et as trackSessionChanges, f as queryOf, ft as detectSolidProfile, g as gate, gt as runSessionStartCleanups, h as TRIVIAL_BUDGET, ht as readRules, i as respond, it as logToolFailure, j as trackEnrichment, jt as normalizeEvent, k as trackMcpResearch, kt as projectHash, l as WEBFETCH_TTL_MS, lt as validateSolidGate, m as REQUIRED_AGENTS, mt as injectRules, n as activityFor, nt as cleanupSession, o as mcpPostStore, ot as trackAgentMemory, p as DEFAULT_WINDOW_MS, pt as solidDetectStart, q as writeTree, r as handlePre, rt as saveApexState, s as mcpPreIntercept, st as subagentCacheContext, t as handleHook, tt as validateRulesLoaded, u as cacheQueryOf, ut as checkFileSize, v as detectDuplication, vt as pruneEmptyDirs, w as seoPostToolUseResponse, wt as projectContext, x as lifecycleStdout, xt as trimLogFile, y as dryGate, yt as purgeTtlTree, z as lessonsArchiveFileFor } from "../handle-CoyvRORV.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.56",
|
|
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",
|