Opt-in Claude Code harness desk: verification receipts, Skill flight recorder, bounded edit replay, context readout and user-controlled prompt briefing…

Opt-in Claude Code mods, tested with 2.1.287. Inspect check receipts, Skill calls, successful edit patches and a user-controlled workflow brief in one native pane. Harness lens keeps its context/last-skill readout above the prompt, with a Brief on indicator while prompt enrichment is enabled. Existing shell enforcement and Codex behavior stay unchanged.
Review before installing: mods have Claude Code's machine access. This plugin makes bounded read-only Git/process and file-metadata calls for comparisons; no file writes, model/network calls, permission changes or automatic undo. Live terminal/Desktop painting and before/after captures remain unverified.
After adding malinskibeniamin/skills as a marketplace:
/plugin marketplace update skills
/plugin install frontend-skills-mods@skills
/reload-plugins
Disable with claude plugin disable frontend-skills-mods@skills, or remove with claude plugin uninstall frontend-skills-mods@skills, then reload plugins.
/harness proof Check receipts and repo comparison
/harness skills Skill Flight Recorder
/harness replay Step through returned edit patches
/harness brief Preview or configure prompt enrichment
Commands run without a model turn. Tabs, refresh, previous/next, clear and close use native buttons. If a pane cannot be placed, the command still returns text.
Default off. Configure every field, inspect the exact injected block in the Brief tab, then enable it with the command or native button:
/harness brief objective Add pagination
/harness brief guardrails Keep the public API
/harness brief verification Run the pagination tests
/harness brief stop Commit only
/harness brief on
The mod adds this user-configured guidance to prompt.submit's model-only context, without changing user text, attachments, queue flags or origin. Existing context from other mods is preserved; an identical block is not added twice. Only composer, Remote Control bridge and SDK submissions qualify, not slash commands, plugin messages (even asUser), peers, schedules or notifications. These origins may also configure the brief; other origins can only view it.
Editing a field pauses enrichment until explicitly re-enabled. Pause brief or /harness brief off stops future injection. Clear brief or /harness brief clear also erases the mod's copy. Session end, including clear, resume or switching conversations, resets it; hot reload retains session state. Overlapping field commands preserve both updates. A state-read failure passes the prompt unchanged and logs an unavailable notice. Downstream prompt blocks stay blocked.
Each field is 1 nonempty line, at most 1024 UTF-8 bytes, without control or hidden format characters. Invalid inputs leave the brief unchanged. Nothing is inferred from repository files or complete prompts. The brief is guidance, not skill selection, adherence proof, new permission or a tool rewrite; current prompts and higher-priority instructions take precedence.
Privacy: these fields are retained in session memory and, when enabled, sent to the model with each eligible prompt. Command output and injected context may remain in Claude's conversation/logs. Clearing cannot retract prior context; do not put secrets in the brief. Extra context consumes tokens. The mod itself makes no model/network calls; normal prompt submission still does.
Only exact foreground main-loop Bash commands are recognized: bun run type:check, bun run lint, bun run lint:fix, bun run test, bun run test:mods. Compound commands, aliases, subagents and background checks are not evidence. A host error is failed; denial is denied; interrupted, backgrounded or special-return-code outcomes are incomplete. Concurrent checks keep the latest started receipt, not whichever finishes last.
A passed result is compared with bounded Git fingerprints before/after the check and on /harness proof or Refresh repo state. Changes produce stale; unavailable comparisons produce unverified. Every tool call invalidates the last comparison. External edits require refresh; fingerprints are non-atomic, not continuous monitoring or release certification. Dogfood and visual evidence remain missing: the mod cannot infer them from a successful command.
Comparison scope: session cwd must equal the Git root; HEAD and tracked/unignored working content only. No ignored dependencies, environment or full staging-state proof. Limit: 200 surviving changed/untracked files, 4 MiB each, 16 MiB combined, 1.5 seconds per Git query. Symlinks, submodules, failed/truncated output and unsupported hosts become unverified. Git runs with optional locks, fsmonitor, external diffs, textconv and hash-object filters disabled. The tested SDK supports process calls only on CLI; Desktop comparisons are unavailable, receipts still display.
Latest 24 completed Skill calls: resolved name when available, outcome and main/subagent provenance. Read-only loading differs from execution; a background fork is a launch with unknown outcome, not completed work. No arguments, prompts, error text or fork result bodies retained. Clear discards history, including already-in-flight calls; it does not erase the separate Last skill readout.
Not an instruction-adherence audit. Slash expansion, installed versions and which references Claude actually read are not attributed by this recorder.
Independent implementation inspired by Replay Theater, not copied upstream code. Retains only successful, non-staged Edit/Write returned patches, including owner-modified output. Not attempted inputs or full file contents. Other tools and external edits are not recorded.
Completion order; tool id, agent and observed main-turn id attached at call start. A late subagent completion is not attributed to a guessed agent turn. Empty patches say diff unavailable. Session memory only: latest 20 entries, 8 KiB per serialized entry, 64 KiB combined. Old entries evict; clipped patches are labeled. Clear cancels in-flight retention; no snapshots, persistence or undo.
Sensitive paths and recognizable secret markers are excluded without retaining their names or contents; terminal controls are neutralized. Filters are best effort, not guaranteed redaction. Unrecognized secrets may appear in ordinary-file patches. Disable the plugin when that risk is unacceptable.
Skill result, not an active skill, completed background work or proven adherence.CLAUDE_BIN=/absolute/path/to/claude bun run test:mods
From repo root with dependencies installed: isolated HOME/config/cache install, strict validation, real host-kit tests, generated SDK compilation and rollback. No model calls or changes to your Claude config. Generated .claude-plugin/types/ is untracked; the dedicated gate owns host-specific types, not root typecheck.
Tests validate native element trees and interactions, not terminal/Desktop paint. Separate live visual checks and before/after captures remain a draft gap. Read extend-harness/MODS.md in the source repository (outside the standalone cache). Official authoring and the target build's generated declarations own this early-access contract.
hooks/harness-lens.ts 675 lines1import {
2 type EngineInterface,
3 type On,
4 type Register,
5 type ToolCallResult,
6 update,
7} from "claude-code";
8import type {
9 CheckKind,
10 ContextReading,
11 DeskView,
12 ProofReceipt,
13 ReplayEntry,
14 ReplayState,
15 SkillsState,
16 WorkflowBrief,
17} from "../types";
18
19import {
20 boundedText,
21 byteLength,
22 displayLabel,
23 emptyReplay,
24 returnedPatch,
25 skillObservation,
26} from "./observations";
27import {
28 briefContext,
29 canEnableBrief,
30 changeBrief,
31 emptyBrief,
32 isBriefOrigin,
33} from "./workflow-brief";
34
35const contextState = {
36 plugin: "frontend-skills-mods",
37 key: "context",
38} as const;
39const skillState = {
40 plugin: "frontend-skills-mods",
41 key: "lastSkill",
42} as const;
43
44export const register: Register = (on) => {
45 registerDesk(on);
46 on("session.start", async ($, e, next) => {
47 const result = await next(e);
48 await readContext($);
49 await startDesk($);
50 return result;
51 });
52
53 on("turn.complete", async ($, e, next) => {
54 const result = await next(e);
55 if (!e.agentId) await readContext($);
56 return result;
57 });
58
59 on("tool.call", { tool: "Skill" }, async ($, e, next) => {
60 const { value: initial } = await $.state.get(skillsState);
61 const generation = initial?.generation ?? 0;
62 const result = await next(e);
63 const observation = skillObservation(e.skill, e.agentId, result);
64 await update($, skillsState, (current): SkillsState => {
65 const state = current ?? { entries: [], generation: 0 };
66 return state.generation !== generation
67 ? state
68 : { ...state, entries: [...state.entries, observation].slice(-24) };
69 });
70 if (
71 !e.agentId &&
72 result.deny === undefined &&
73 !result.isError &&
74 result.result?.success
75 ) {
76 await $.state.set(skillState, e.skill);
77 }
78 return result;
79 });
80
81 on("ui.render", { component: "AbovePrompt" }, async ($, e, next) => {
82 if (e.props.hasSurvey || e.props.bodyColumns < 32 || e.props.view.agentId) {
83 return next(e);
84 }
85 const { value: usage } = await $.state.get(contextState);
86 const { value: lastSkill } = await $.state.get(skillState);
87 const { value: brief } = await $.state.get(briefState);
88 const context = usage ? `Context ${usage.percent}%` : "Context unavailable";
89 const briefing = brief?.enabled ? " · Brief on" : "";
90 const skill = lastSkill ? ` · Last skill ${displayLabel(lastSkill)}` : "";
91 const { Box, Text } = $.ui.resolve(e);
92 return Box({
93 flexDirection: "column",
94 children: [
95 await next(e),
96 Text({
97 dimColor: true,
98 children: `${context}${briefing}${skill}`.slice(
99 0,
100 e.props.bodyColumns,
101 ),
102 }),
103 ],
104 });
105 });
106};
107
108async function readContext($: EngineInterface): Promise<void> {
109 let context: ContextReading = null;
110 try {
111 const { context: usage } = await $.session.usage();
112 if (usage.window > 0 && usage.tokens !== undefined) {
113 const percent = usage.percent ?? (usage.tokens / usage.window) * 100;
114 if (Number.isFinite(percent)) {
115 context = {
116 tokens: usage.tokens,
117 window: usage.window,
118 percent: Math.round(percent),
119 };
120 }
121 }
122 } catch {
123 // Visible unavailable state replaces stale numbers; no tool/turn is denied.
124 context = null;
125 }
126 await $.state.set(contextState, context);
127}
128
129const viewState = { plugin: "frontend-skills-mods", key: "deskView" } as const;
130
131function registerDesk(on: On): void {
132 registerReplay(on);
133 registerBrief(on);
134 on("tool.call", async ($, e, next) => {
135 // Any tool may mutate repo state, including MCP tools. Require a fresh comparison.
136 await $.state.set(repositoryState, null);
137 return next(e);
138 });
139 on("tool.call", { tool: "Bash" }, async ($, e, next) => {
140 const checks = new Map<string, CheckKind>([
141 ["bun run type:check", "types"],
142 ["bun run lint", "lint"],
143 ["bun run lint:fix", "lint"],
144 ["bun run test", "tests"],
145 ["bun run test:mods", "mods"],
146 ]);
147 const kind = checks.get(e.command.trim());
148 if (!kind || e.agentId || e.run_in_background) return next(e);
149 const ref = {
150 plugin: "frontend-skills-mods",
151 key: "proof",
152 id: kind,
153 } as const;
154 await $.state.set(ref, {
155 toolId: e.tool_use_id,
156 outcome: "pending",
157 fingerprint: null,
158 changedDuringCheck: false,
159 });
160 const before = await repositoryFingerprint($);
161 let result: ToolCallResult<"Bash">;
162 try {
163 result = await next(e);
164 } catch (error) {
165 await update(
166 $,
167 ref,
168 (current): ProofReceipt =>
169 current?.toolId !== e.tool_use_id
170 ? (current ?? {
171 toolId: e.tool_use_id,
172 outcome: "incomplete",
173 fingerprint: null,
174 changedDuringCheck: false,
175 })
176 : { ...current, outcome: "incomplete" },
177 );
178 throw error;
179 }
180 const after = await repositoryFingerprint($);
181 const outcome = proofOutcome(result);
182 await update(
183 $,
184 ref,
185 (current): ProofReceipt =>
186 current?.toolId !== e.tool_use_id
187 ? (current ?? {
188 toolId: e.tool_use_id,
189 outcome: "incomplete",
190 fingerprint: null,
191 changedDuringCheck: false,
192 })
193 : {
194 toolId: e.tool_use_id,
195 outcome,
196 fingerprint: before === after ? after : null,
197 changedDuringCheck:
198 before !== null && after !== null && before !== after,
199 },
200 );
201 return result;
202 });
203 on("command.run", { command: "harness" }, async ($, e) => {
204 const args = e.args.trim();
205 const briefCommand = /^brief(?:\s+([\s\S]*))?$/.exec(args);
206 const view = briefCommand ? "brief" : args || "proof";
207 if (
208 view !== "proof" &&
209 view !== "skills" &&
210 view !== "replay" &&
211 view !== "brief"
212 ) {
213 return { text: "Usage: /harness [proof|skills|replay|brief]" };
214 }
215 if (briefCommand?.[1]) {
216 if (!isBriefOrigin(e.origin)) {
217 return {
218 text: "Workflow brief not changed: use a composer, bridge or SDK command.",
219 };
220 }
221 const briefArgs = briefCommand[1];
222 let error: string | undefined;
223 await update($, briefState, (current): WorkflowBrief => {
224 const state = current ?? emptyBrief();
225 const changed = changeBrief(state, briefArgs);
226 error = "error" in changed ? changed.error : undefined;
227 return "state" in changed ? changed.state : state;
228 });
229 if (error) return { text: `Workflow brief not changed: ${error}` };
230 }
231 if (view === "proof") await refreshRepository($);
232 await $.state.set(viewState, view);
233 const { isPlaced } = await $.ui.open({
234 id: "harness",
235 title: "Harness",
236 focus: true,
237 closeOnEscape: true,
238 rows: 16,
239 });
240 const text = await deskText($, view);
241 return {
242 text: isPlaced ? text : `${text}\nPane unavailable on this surface.`,
243 };
244 });
245 on("ui.render", { component: "Pane" }, async ($, e, next) => {
246 if (e.requestId !== "harness") return next(e);
247 const { value: view = "proof" } = await $.state.get(viewState);
248 const { value: brief = emptyBrief() } = await $.state.get(briefState);
249 const { Box, Text, Button } = $.ui.resolve(e);
250 return Box({
251 flexDirection: "column",
252 children: [
253 Box({
254 children: (["proof", "skills", "replay", "brief"] as const).map(
255 (tab) =>
256 Button({
257 key: tab,
258 label: tab.charAt(0).toUpperCase() + tab.slice(1),
259 onPress: () => $.state.set(viewState, tab),
260 }),
261 ),
262 }),
263 Text({ children: await deskText($, view) }),
264 ...(view === "brief"
265 ? [
266 ...(brief.enabled || canEnableBrief(brief)
267 ? [
268 Button({
269 key: "toggle-brief",
270 label: brief.enabled ? "Pause brief" : "Enable brief",
271 onPress: () =>
272 update($, briefState, (current): WorkflowBrief => {
273 const state = current ?? emptyBrief();
274 const changed = changeBrief(
275 state,
276 state.enabled ? "off" : "on",
277 );
278 return "state" in changed ? changed.state : state;
279 }),
280 }),
281 ]
282 : []),
283 Button({
284 key: "clear-brief",
285 label: "Clear brief",
286 onPress: () => $.state.set(briefState, emptyBrief()),
287 }),
288 ]
289 : []),
290 ...(view === "replay"
291 ? [
292 Button({
293 key: "previous",
294 label: "Previous edit",
295 onPress: () =>
296 update(
297 $,
298 replayState,
299 (state): ReplayState => ({
300 ...(state ?? emptyReplay()),
301 index: Math.max(0, (state?.index ?? 0) - 1),
302 }),
303 ),
304 }),
305 Button({
306 key: "next",
307 label: "Next edit",
308 onPress: () =>
309 update(
310 $,
311 replayState,
312 (state): ReplayState => ({
313 ...(state ?? emptyReplay()),
314 index: Math.min(
315 Math.max(0, (state?.entries.length ?? 0) - 1),
316 (state?.index ?? 0) + 1,
317 ),
318 }),
319 ),
320 }),
321 Button({
322 key: "clear-replay",
323 label: "Clear replay",
324 onPress: () =>
325 update(
326 $,
327 replayState,
328 (state): ReplayState => ({
329 ...emptyReplay(),
330 generation: (state?.generation ?? 0) + 1,
331 }),
332 ),
333 }),
334 ]
335 : []),
336 ...(view === "skills"
337 ? [
338 Button({
339 key: "clear-skills",
340 label: "Clear skill history",
341 onPress: () =>
342 update(
343 $,
344 skillsState,
345 (state): SkillsState => ({
346 entries: [],
347 generation: (state?.generation ?? 0) + 1,
348 }),
349 ),
350 }),
351 ]
352 : []),
353 ...(view === "proof"
354 ? [
355 Button({
356 key: "refresh",
357 label: "Refresh repo state",
358 onPress: () => refreshRepository($),
359 }),
360 ]
361 : []),
362 Button({
363 key: "close",
364 label: "Close",
365 role: "dismiss",
366 onPress: () => $.ui.close({ id: "harness" }),
367 }),
368 ],
369 });
370 });
371}
372
373function proofOutcome(result: ToolCallResult<"Bash">): ProofReceipt["outcome"] {
374 if (result.deny !== undefined) return "denied";
375 if (result.isError) return "failed";
376 const output = result.result;
377 return output.interrupted ||
378 output.backgroundTaskId !== undefined ||
379 output.backgroundedByUser ||
380 output.backgroundedByTurnAbort ||
381 output.backgroundedToDeliverMessage ||
382 output.timedOutAfterMs !== undefined ||
383 output.returnCodeInterpretation !== undefined
384 ? "incomplete"
385 : "passed";
386}
387
388async function deskText($: EngineInterface, view: DeskView): Promise<string> {
389 if (view === "proof") return proofText($);
390 if (view === "brief") {
391 const { value: brief = emptyBrief() } = await $.state.get(briefState);
392 return [
393 `Workflow brief: ${brief.enabled ? "enabled" : "disabled"}`,
394 "Future composer/bridge/SDK non-command prompts only. All 4 fields required.",
395 "Edit with /harness brief <field> <text>; enable with /harness brief on.",
396 "Disable with off; clear erases this mod's copy, not prior conversation context.",
397 "Injected block preview:",
398 briefContext(brief),
399 ].join("\n");
400 }
401 if (view === "skills") {
402 const { value: history } = await $.state.get(skillsState);
403 return [
404 "Skill Flight Recorder",
405 "Latest 24 completions; tool observations, not adherence.",
406 ...(history?.entries.length
407 ? history.entries.map(
408 (entry) => `${entry.name} · ${entry.outcome} · ${entry.provenance}`,
409 )
410 : ["No Skill calls observed."]),
411 ].join("\n");
412 }
413 const { value: replay } = await $.state.get(replayState);
414 const entry = replay?.entries[replay.index];
415 const body = entry
416 ? `Edit ${replay.index + 1}/${replay.entries.length} · ${entry.path}\n${entry.provenance} · observed main turn ${entry.turn} · tool ${entry.toolId}\n${entry.diff}${entry.truncated ? "\n[Patch truncated at retention limit.]" : ""}`
417 : "Edit replay\nNo successful edits observed (after exclusions or clearing).";
418 return `${body}\nExcluded sensitive edits: ${replay?.excluded ?? 0}\nSession memory only; latest 20 entries / 64 KiB. Secret filtering is best effort.`;
419}
420
421async function startDesk($: EngineInterface): Promise<void> {
422 await $.command.register({
423 name: "harness",
424 description: "Inspect evidence or configure an opt-in workflow brief.",
425 argumentHint: "[proof|skills|replay|brief]",
426 });
427}
428
429const briefState = { plugin: "frontend-skills-mods", key: "brief" } as const;
430
431function registerBrief(on: On): void {
432 on("prompt.submit", async ($, e, next) => {
433 if (!isBriefOrigin(e.origin) || e.text.trimStart().startsWith("/"))
434 return next(e);
435 let brief: WorkflowBrief | undefined;
436 try {
437 brief = (await $.state.get(briefState)).value;
438 } catch {
439 $.ui.log("Workflow brief unavailable; prompt passed unchanged.");
440 return next(e);
441 }
442 if (!brief?.enabled) return next(e);
443 const context = briefContext(brief);
444 if (e.context?.includes(context)) return next(e);
445 return next({ ...e, context: [...(e.context ?? []), context] });
446 });
447 on("session.end", async ($, e, next) => {
448 await $.state.set(briefState, emptyBrief());
449 return next(e);
450 });
451}
452
453const repositoryState = {
454 plugin: "frontend-skills-mods",
455 key: "repository",
456} as const;
457
458async function refreshRepository($: EngineInterface): Promise<void> {
459 await $.state.set(repositoryState, await repositoryFingerprint($));
460}
461
462async function proofText($: EngineInterface): Promise<string> {
463 const { value: repository } = await $.state.get(repositoryState);
464 const lines = [
465 "Proof Desk",
466 "Observed commands, not a release certification.",
467 ];
468 for (const [id, label] of [
469 ["types", "Types"],
470 ["lint", "Lint"],
471 ["tests", "Tests"],
472 ["mods", "Mod tests"],
473 ] as const) {
474 const { value: receipt } = await $.state.get({
475 plugin: "frontend-skills-mods",
476 key: "proof",
477 id,
478 });
479 let status: string = receipt?.outcome ?? "missing";
480 if (receipt?.outcome === "passed") {
481 status = receipt.changedDuringCheck
482 ? "stale (passed; files changed during check)"
483 : !receipt.fingerprint || !repository
484 ? "unverified (passed)"
485 : receipt.fingerprint !== repository
486 ? "stale (passed)"
487 : "passed";
488 }
489 lines.push(`${label}: ${status}`);
490 }
491 lines.push(
492 "Dogfood: missing",
493 "Visual: missing",
494 "Repo comparison: last refresh only; refresh after external edits.",
495 "Scope: tracked/unignored repo content; no environment or dependency proof.",
496 );
497 return lines.join("\n");
498}
499
500async function gitOutput(
501 $: EngineInterface,
502 cwd: string,
503 args: string[],
504): Promise<string> {
505 const result = await $.process.run(
506 ["git", "-c", "core.fsmonitor=false", ...args],
507 {
508 cwd,
509 timeoutMs: 1500,
510 env: { GIT_OPTIONAL_LOCKS: "0" },
511 },
512 );
513 if (
514 result.exitCode !== 0 ||
515 result.isStdoutTruncated ||
516 result.isStderrTruncated
517 ) {
518 throw new Error("Repository comparison unavailable");
519 }
520 return result.stdout;
521}
522
523async function repositoryFingerprint(
524 $: EngineInterface,
525): Promise<string | null> {
526 try {
527 const cwd = await $.session.cwd();
528 const root = (
529 await gitOutput($, cwd, ["rev-parse", "--show-toplevel"])
530 ).trim();
531 if (cwd !== root) return null;
532 const head = await gitOutput($, cwd, ["rev-parse", "--verify", "HEAD"]);
533 const raw = await gitOutput($, cwd, [
534 "diff",
535 "--no-ext-diff",
536 "--no-textconv",
537 "--raw",
538 "HEAD",
539 "--",
540 ]);
541 const changed = await gitOutput($, cwd, [
542 "diff",
543 "--no-ext-diff",
544 "--no-textconv",
545 "--name-only",
546 "-z",
547 "HEAD",
548 "--",
549 ]);
550 const deleted = await gitOutput($, cwd, [
551 "diff",
552 "--diff-filter=D",
553 "--name-only",
554 "-z",
555 "HEAD",
556 "--",
557 ]);
558 const untracked = await gitOutput($, cwd, [
559 "ls-files",
560 "--others",
561 "--exclude-standard",
562 "-z",
563 ]);
564 const removed = new Set(deleted.split("\0"));
565 const files = [...new Set(`${changed}${untracked}`.split("\0"))]
566 .filter((path) => path && !removed.has(path))
567 .sort();
568 if (
569 files.length > 200 ||
570 files.some(
571 (path) => path.startsWith("/") || path.split("/").includes(".."),
572 )
573 )
574 return null;
575 const stats = await Promise.all(
576 files.map((path) => $.fs.stat(`${cwd}/${path}`)),
577 );
578 if (
579 stats.some(
580 (stat) =>
581 stat.isLink || stat.kind !== "file" || stat.size > 4 * 1024 * 1024,
582 ) ||
583 stats.reduce((size, stat) => size + stat.size, 0) > 16 * 1024 * 1024
584 )
585 return null;
586 const hashes = files.length
587 ? await gitOutput($, cwd, ["hash-object", "--no-filters", "--", ...files])
588 : "";
589 if (files.length && hashes.trim().split("\n").length !== files.length)
590 return null;
591 const digest = await crypto.subtle.digest(
592 "SHA-256",
593 new TextEncoder().encode(
594 JSON.stringify([
595 cwd,
596 head,
597 raw,
598 changed,
599 deleted,
600 untracked,
601 files,
602 hashes,
603 ]),
604 ),
605 );
606 return [...new Uint8Array(digest)]
607 .map((byte) => byte.toString(16).padStart(2, "0"))
608 .join("");
609 } catch {
610 // Unsupported host/process/repo state stays visibly unverified, never green.
611 return null;
612 }
613}
614
615const skillsState = { plugin: "frontend-skills-mods", key: "skills" } as const;
616
617const turnState = { plugin: "frontend-skills-mods", key: "turn" } as const;
618const replayState = { plugin: "frontend-skills-mods", key: "replay" } as const;
619
620function registerReplay(on: On): void {
621 on("turn.start", async ($, e, next) => {
622 await $.state.set(turnState, displayLabel(e.turnId));
623 return next(e);
624 });
625 on("tool.call", { tool: ["Edit", "Write"] }, async ($, e, next) => {
626 const { value: turn } = await $.state.get(turnState);
627 const { value: initial } = await $.state.get(replayState);
628 const generation = initial?.generation ?? 0;
629 const result = await next(e);
630 if (result.deny !== undefined || result.isError || result.result.staged)
631 return result;
632 const output = result.result;
633 const sensitivePath =
634 /(?:^|[\\/])(?:\.env(?:[.\\/]|$)|\.(?:ssh|aws|kube|git)(?:[\\/]|$)|\.(?:npmrc|netrc|pypirc)$|(?:credentials?|secrets?|passwords?)(?:[.\\/]|$)|id_(?:rsa|ed25519|dsa|ecdsa)(?:[.]|$))|\.(?:pem|key|p12|pfx|keystore)$/i;
635 const patch = sensitivePath.test(output.filePath)
636 ? null
637 : returnedPatch(output.structuredPatch);
638 const excluded =
639 !patch ||
640 /-----BEGIN [^\n]*PRIVATE KEY-----|\b(?:sk-(?:ant|proj)-|gh[pousr]_|github_pat_|(?:AKIA|ASIA)[A-Z0-9]{16})|(?:api[_-]?key|access[_-]?token|password|secret)\s*["']?\s*[:=]/i.test(
641 patch.diff,
642 );
643 let entry: ReplayEntry | null = null;
644 if (patch && !excluded) {
645 entry = {
646 path: displayLabel(output.filePath),
647 toolId: displayLabel(e.tool_use_id),
648 provenance: e.agentId ? `agent ${displayLabel(e.agentId)}` : "main",
649 turn: turn ?? "unknown",
650 ...patch,
651 };
652 while (byteLength(JSON.stringify(entry)) > 8192) {
653 entry.diff = boundedText(
654 entry.diff,
655 Math.max(
656 0,
657 byteLength(entry.diff) - (byteLength(JSON.stringify(entry)) - 8192),
658 ),
659 );
660 entry.truncated = true;
661 }
662 }
663 const retained = entry;
664 await update($, replayState, (current): ReplayState => {
665 const state = current ?? emptyReplay();
666 if (state.generation !== generation) return state;
667 if (!retained) return { ...state, excluded: state.excluded + 1 };
668 const entries = [...state.entries, retained].slice(-20);
669 while (byteLength(JSON.stringify(entries)) > 64 * 1024) entries.shift();
670 return { ...state, entries, index: entries.length - 1 };
671 });
672 return result;
673 });
674}
675hooks/observations.ts 85 lines1import type { ToolCallResult, ToolResultOf } from "claude-code";
2import type { ReplayState, SkillObservation } from "../types";
3
4export function displayLabel(value: string): string {
5 return value.slice(0, 120).replace(/[\p{Cc}\p{Cf}]/gu, "?");
6}
7
8export function skillObservation(
9 requested: string,
10 agentId: string | undefined,
11 result: ToolCallResult<"Skill">,
12): SkillObservation {
13 const provenance = agentId ? `agent ${displayLabel(agentId)}` : "main";
14 if (result.deny !== undefined)
15 return { name: displayLabel(requested), provenance, outcome: "denied" };
16 if (result.isError)
17 return { name: displayLabel(requested), provenance, outcome: "failed" };
18 const output = result.result;
19 const name = displayLabel(output.commandName);
20 const outcome: SkillObservation["outcome"] = !output.success
21 ? "failed"
22 : output.status === "forked"
23 ? output.background
24 ? "forked (launched; outcome unknown)"
25 : "forked (completed)"
26 : output.readOnly
27 ? "loaded read-only"
28 : "succeeded";
29 return { name, provenance, outcome };
30}
31
32export function emptyReplay(): ReplayState {
33 return { entries: [], index: 0, generation: 0, excluded: 0 };
34}
35
36export function byteLength(value: string): number {
37 return new TextEncoder().encode(value).length;
38}
39
40export function boundedText(value: string, max: number): string {
41 let result = "";
42 let bytes = 0;
43 for (const character of value.slice(0, max)) {
44 bytes += byteLength(character);
45 if (bytes > max) break;
46 result += character;
47 }
48 return result;
49}
50
51export function returnedPatch(patch: ToolResultOf<"Edit">["structuredPatch"]): {
52 diff: string;
53 truncated: boolean;
54} {
55 if (!patch.length)
56 return {
57 diff: "Diff unavailable (host returned no patch).",
58 truncated: false,
59 };
60 let diff = "";
61 let truncated = false;
62 const append = (text: string) => {
63 const remaining = 8192 - byteLength(diff);
64 const part = boundedText(text, remaining);
65 diff += part;
66 truncated = part.length !== text.length;
67 };
68 for (const hunk of patch) {
69 append(
70 `${diff ? "\n" : ""}@@ -${hunk.oldStart},${hunk.oldLines} +${hunk.newStart},${hunk.newLines} @@`,
71 );
72 if (truncated) break;
73 for (const line of hunk.lines) {
74 append(`\n${line}`);
75 if (truncated) break;
76 }
77 if (truncated) break;
78 }
79 // Keep newlines/tabs, neutralize terminal control sequences before retaining them.
80 diff = diff.replace(/[\p{Cc}\p{Cf}]/gu, (character) =>
81 character === "\n" || character === "\t" ? character : "?",
82 );
83 return { diff, truncated };
84}
85hooks/workflow-brief.ts 70 lines1import type { PromptOrigin } from "claude-code";
2import type { WorkflowBrief } from "../types";
3import { byteLength } from "./observations";
4
5const fields = ["objective", "guardrails", "verification", "stop"] as const;
6
7export function emptyBrief(): WorkflowBrief {
8 return {
9 objective: "",
10 guardrails: "",
11 verification: "",
12 stop: "",
13 enabled: false,
14 };
15}
16
17export function isBriefOrigin(origin: PromptOrigin): boolean {
18 return (
19 origin.kind === "composer" ||
20 origin.kind === "bridge" ||
21 origin.kind === "sdk"
22 );
23}
24
25export function canEnableBrief(brief: WorkflowBrief): boolean {
26 return fields.every((field) => Boolean(brief[field]));
27}
28
29export function briefContext(brief: WorkflowBrief): string {
30 return [
31 "Harness workflow brief (user-configured guidance, not enforcement).",
32 "Current prompt and higher-priority instructions take precedence; this grants no tool permissions.",
33 `Objective: ${brief.objective}`,
34 `Guardrails: ${brief.guardrails}`,
35 `Verification: ${brief.verification}`,
36 `Stop: ${brief.stop}`,
37 ].join("\n");
38}
39
40export function changeBrief(
41 brief: WorkflowBrief,
42 args: string,
43): { state: WorkflowBrief } | { error: string } {
44 if (args === "clear") return { state: emptyBrief() };
45 if (args === "off") return { state: { ...brief, enabled: false } };
46 if (args === "on") {
47 const missing = fields.filter((field) => !brief[field]);
48 return missing.length
49 ? { error: `Set every field before enabling: ${missing.join(", ")}.` }
50 : { state: { ...brief, enabled: true } };
51 }
52 const match = /^(\S+)\s+([\s\S]+)$/.exec(args);
53 const field = fields.find((field) => field === match?.[1]);
54 const value = match?.[2]?.trim();
55 if (!field || !value) {
56 return {
57 error:
58 "Usage: /harness brief [objective|guardrails|verification|stop <text>|on|off|clear].",
59 };
60 }
61 if (/[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u.test(value) || byteLength(value) > 1024) {
62 return {
63 error:
64 "Use 1 line per field, at most 1024 UTF-8 bytes, without control characters.",
65 };
66 }
67 // Changing the brief pauses injection until the person enables the new preview.
68 return { state: { ...brief, [field]: value, enabled: false } };
69}
70types/index.d.ts 69 lines1export type ContextReading = {
2 tokens: number;
3 window: number;
4 percent: number;
5} | null;
6
7export type DeskView = "proof" | "skills" | "replay" | "brief";
8
9export type WorkflowBrief = {
10 objective: string;
11 guardrails: string;
12 verification: string;
13 stop: string;
14 enabled: boolean;
15};
16
17export type CheckKind = "types" | "lint" | "tests" | "mods";
18export type ProofReceipt = {
19 toolId: string;
20 outcome: "pending" | "passed" | "failed" | "denied" | "incomplete";
21 fingerprint: string | null;
22 changedDuringCheck: boolean;
23};
24
25export type SkillObservation = {
26 name: string;
27 provenance: string;
28 outcome:
29 | "succeeded"
30 | "failed"
31 | "denied"
32 | "loaded read-only"
33 | "forked (launched; outcome unknown)"
34 | "forked (completed)";
35};
36
37export type SkillsState = { entries: SkillObservation[]; generation: number };
38
39export type ReplayEntry = {
40 path: string;
41 toolId: string;
42 provenance: string;
43 turn: string;
44 diff: string;
45 truncated: boolean;
46};
47export type ReplayState = {
48 entries: ReplayEntry[];
49 index: number;
50 generation: number;
51 excluded: number;
52};
53
54declare module "claude-code" {
55 interface PluginState {
56 "frontend-skills-mods": {
57 context: ContextReading;
58 deskView: DeskView;
59 repository: string | null;
60 proof: StateFamily<ProofReceipt>;
61 lastSkill: string | null;
62 skills: SkillsState;
63 turn: string;
64 replay: ReplayState;
65 brief: WorkflowBrief;
66 };
67 }
68}
69