frizz 0.2.0 → 0.3.0

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.
Files changed (115) hide show
  1. package/README.md +17 -17
  2. package/dist/claude-agent-broker.js +63 -23
  3. package/dist/dev-child.js +6877 -4459
  4. package/dist/frizz.js +654 -529
  5. package/package.json +6 -2
  6. package/runtime/cc-worker/.claude-plugin/plugin.json +1 -1
  7. package/runtime/cc-worker/DECISIONS.md +64 -4
  8. package/runtime/cc-worker/bin/frizz-mcp.mjs +494 -57
  9. package/runtime/cc-worker/hooks/agent-dispatch.mjs +1 -1
  10. package/runtime/cc-worker/hooks/deny-plan.mjs +1 -1
  11. package/runtime/cc-worker/hooks/hooks.json +0 -7
  12. package/runtime/cc-worker/hooks/precompact-instructions.mjs +39 -28
  13. package/runtime/cc-worker/hooks/scratchpad.mjs +142 -169
  14. package/runtime/cc-worker/hooks/session-seed.mjs +16 -15
  15. package/runtime/cc-worker/skills/waits/SKILL.md +2 -2
  16. package/web-dist/apple-touch-icon.png +0 -0
  17. package/web-dist/assets/{TerminalPane-ROKHp1ib.js → TerminalPane-CTdetDJJ.js} +2 -2
  18. package/web-dist/assets/{abnfDiagram-VRR7QNED-DcpdhBs3.js → abnfDiagram-VRR7QNED-BDKrjCMs.js} +1 -1
  19. package/web-dist/assets/architecture-TIHT7OUA-ChUMo004.js +1 -0
  20. package/web-dist/assets/{architectureDiagram-ZJ3FMSHR-CUAKf0mn.js → architectureDiagram-ZJ3FMSHR-BJebpUUM.js} +1 -1
  21. package/web-dist/assets/{blockDiagram-677ZJIJ3-BPwpJIzx.js → blockDiagram-677ZJIJ3-BNbuk25k.js} +1 -1
  22. package/web-dist/assets/{c4Diagram-LMCZKHZV-1lptuHzZ.js → c4Diagram-LMCZKHZV-lCfyotdU.js} +1 -1
  23. package/web-dist/assets/channel-pr7r6raB.js +1 -0
  24. package/web-dist/assets/{chunk-32BRIVSS-CFR9AKjY.js → chunk-32BRIVSS-DP72SEkr.js} +1 -1
  25. package/web-dist/assets/{chunk-52WLFC77-CM9uct7m.js → chunk-52WLFC77-DxO3gN-p.js} +1 -1
  26. package/web-dist/assets/{chunk-C7G6YPKG-DiveJARw.js → chunk-C7G6YPKG-BXFx9Vlr.js} +1 -1
  27. package/web-dist/assets/{chunk-EX3LRPZG-BE1CBw8F.js → chunk-EX3LRPZG-CR3sHpPf.js} +1 -1
  28. package/web-dist/assets/{chunk-FWX5IMBZ-DL42uXiO.js → chunk-FWX5IMBZ-CJ8L__oG.js} +2 -2
  29. package/web-dist/assets/{chunk-HOUHSVGY-DPhJWgDw.js → chunk-HOUHSVGY-BCl3JSWr.js} +1 -1
  30. package/web-dist/assets/{chunk-ICXQ74PX-CwYy-6AP.js → chunk-ICXQ74PX-CZxFMci1.js} +1 -1
  31. package/web-dist/assets/{chunk-MOJQB5TN-Ds5I9wxq.js → chunk-MOJQB5TN-Ck3_47dB.js} +1 -1
  32. package/web-dist/assets/{chunk-OGEWGWER-DHiZJwQD.js → chunk-OGEWGWER-CVXjES-F.js} +1 -1
  33. package/web-dist/assets/{chunk-PUDLZKDR-B-eyQTsF.js → chunk-PUDLZKDR-JjrVgaR7.js} +1 -1
  34. package/web-dist/assets/{chunk-Q4XR5HBZ-DK7dB3Ti.js → chunk-Q4XR5HBZ-C5YMRAQ0.js} +1 -1
  35. package/web-dist/assets/{chunk-V7JOEXUC-Dn59m74L.js → chunk-V7JOEXUC-DK0mfUHU.js} +1 -1
  36. package/web-dist/assets/{chunk-VAUOI2AC-DK7x36hd.js → chunk-VAUOI2AC-BKBntpid.js} +1 -1
  37. package/web-dist/assets/{chunk-VR4S4FIN-D7-CI3Yl.js → chunk-VR4S4FIN-zgE1dG6U.js} +1 -1
  38. package/web-dist/assets/{chunk-WYO6CB5R-B3l-mLCs.js → chunk-WYO6CB5R-CZhh1IBq.js} +1 -1
  39. package/web-dist/assets/{chunk-ZGVPDNZ5-Dobxlxie.js → chunk-ZGVPDNZ5-CrNjIEem.js} +1 -1
  40. package/web-dist/assets/classDiagram-OUVF2IWQ-CvGbPMn_.js +1 -0
  41. package/web-dist/assets/classDiagram-v2-EOCWNBFH-CvGbPMn_.js +1 -0
  42. package/web-dist/assets/{cynefin-VYW2F7L2-Dh7RuEUJ.js → cynefin-VYW2F7L2-Ca_BPfTG.js} +1 -1
  43. package/web-dist/assets/{cynefinDiagram-TSTJHNR4-ClPi2mZZ.js → cynefinDiagram-TSTJHNR4-CsINQf6g.js} +1 -1
  44. package/web-dist/assets/{dagre-VKFMJZFB-52_WP1QV.js → dagre-VKFMJZFB-CE0DYMFy.js} +1 -1
  45. package/web-dist/assets/{diagram-FQU43EPY-D_1zVsTL.js → diagram-FQU43EPY-BqTduEYE.js} +1 -1
  46. package/web-dist/assets/{diagram-G47NLZAW-CdZxuGUy.js → diagram-G47NLZAW-Cho718s6.js} +1 -1
  47. package/web-dist/assets/{diagram-NH7WQ7WH-C8pSFu0P.js → diagram-NH7WQ7WH-D0Z8t9UC.js} +1 -1
  48. package/web-dist/assets/{diagram-OA4YK3LP-C5bjZLre.js → diagram-OA4YK3LP-CBacyeDx.js} +1 -1
  49. package/web-dist/assets/{diagram-WEI45ONY-Bxzhiuzn.js → diagram-WEI45ONY-CozpXa4j.js} +1 -1
  50. package/web-dist/assets/{ebnfDiagram-CCIWWBDH-g-Z0J2wP.js → ebnfDiagram-CCIWWBDH-ba4NbQQo.js} +1 -1
  51. package/web-dist/assets/{erDiagram-Q63AITRT-DVNkgIHp.js → erDiagram-Q63AITRT-B3BciOYa.js} +1 -1
  52. package/web-dist/assets/eventmodeling-45OFAUF4-DKmyo-jd.js +1 -0
  53. package/web-dist/assets/flowDiagram-23GEKE2U-DluCBvT4.js +1 -0
  54. package/web-dist/assets/{ganttDiagram-NO4QXBWP-_71pQYEK.js → ganttDiagram-NO4QXBWP-lPmrRit4.js} +1 -1
  55. package/web-dist/assets/{gitGraph-TEB2WS4Q-ChIZiGZS.js → gitGraph-TEB2WS4Q-BjyclOq0.js} +1 -1
  56. package/web-dist/assets/{gitGraphDiagram-IHSO6WYX-DCHAFI0l.js → gitGraphDiagram-IHSO6WYX-pzeb4Yrw.js} +1 -1
  57. package/web-dist/assets/index-BQtjYMpV.css +1 -0
  58. package/web-dist/assets/index-CLW1Q49U.js +360 -0
  59. package/web-dist/assets/{info-DKCQHKI2-BW-n_T1j.js → info-DKCQHKI2-Bwycegvf.js} +1 -1
  60. package/web-dist/assets/{infoDiagram-FWYZ7A6U-CgDYsKi9.js → infoDiagram-FWYZ7A6U-BCvRGj_5.js} +1 -1
  61. package/web-dist/assets/{ishikawaDiagram-FXEZZL3T-ClzGNt9N.js → ishikawaDiagram-FXEZZL3T-Ofw1RMj3.js} +1 -1
  62. package/web-dist/assets/{journeyDiagram-5HDEW3XC-DSCQxkHC.js → journeyDiagram-5HDEW3XC-C5ROwFio.js} +1 -1
  63. package/web-dist/assets/{kanban-definition-HUTT4EX6-CdrdX9N8.js → kanban-definition-HUTT4EX6-YLPLkpeT.js} +1 -1
  64. package/web-dist/assets/{line-ha38Dc-1.js → line-KtkNqRgI.js} +1 -1
  65. package/web-dist/assets/{mermaid-parser.core-Z4uMcpip.js → mermaid-parser.core-D_FfqBe7.js} +3 -3
  66. package/web-dist/assets/{mermaid.core-iZRq3hbu.js → mermaid.core-Ffv8anVf.js} +3 -3
  67. package/web-dist/assets/{mindmap-definition-LN4V7U3C-DmhInJO_.js → mindmap-definition-LN4V7U3C-B2jj4vfm.js} +1 -1
  68. package/web-dist/assets/{packet-7NZHBO7P-DBPB36Kl.js → packet-7NZHBO7P-BjmWHwra.js} +1 -1
  69. package/web-dist/assets/{pegDiagram-2B236MQR-CAH3ljfj.js → pegDiagram-2B236MQR-Dq3iJDyq.js} +1 -1
  70. package/web-dist/assets/{pie-RZYD4A2V-_h_eX4Ca.js → pie-RZYD4A2V-jBbH1lv9.js} +1 -1
  71. package/web-dist/assets/{pieDiagram-ENE6RG2P-DFBPus8j.js → pieDiagram-ENE6RG2P-C3ETW9lq.js} +1 -1
  72. package/web-dist/assets/{quadrantDiagram-ABIIQ3AL-DMvOCjt8.js → quadrantDiagram-ABIIQ3AL-DItSmme7.js} +1 -1
  73. package/web-dist/assets/{radar-I7S5WNFK-2EzoPHEZ.js → radar-I7S5WNFK-6ey6crgP.js} +1 -1
  74. package/web-dist/assets/{railroad-3IZDKUUU-BPJnn-hm.js → railroad-3IZDKUUU-Cii-Mn0E.js} +1 -1
  75. package/web-dist/assets/railroad-abnf-AHOZXSZD-U_vb4BrX.js +1 -0
  76. package/web-dist/assets/railroad-ebnf-EBAXGLYW-BIHG7gNU.js +1 -0
  77. package/web-dist/assets/railroad-peg-LSFZ7HO6-Cpd9r-tB.js +1 -0
  78. package/web-dist/assets/{railroadDiagram-RFXS5EU6-DKq5FagA.js → railroadDiagram-RFXS5EU6-M363ils_.js} +1 -1
  79. package/web-dist/assets/{requirementDiagram-TGXJPOKE-BJ5tGazp.js → requirementDiagram-TGXJPOKE-CTs2_V6T.js} +1 -1
  80. package/web-dist/assets/{sankeyDiagram-HTMAVEWB-XSJjcBhX.js → sankeyDiagram-HTMAVEWB-QTLLcDPD.js} +1 -1
  81. package/web-dist/assets/{sequenceDiagram-DBY2YBRQ-CBb8emSe.js → sequenceDiagram-DBY2YBRQ-Bxw9Tr6e.js} +1 -1
  82. package/web-dist/assets/{stateDiagram-2N3HPSRC-DDfRW94V.js → stateDiagram-2N3HPSRC-BecB6roG.js} +1 -1
  83. package/web-dist/assets/stateDiagram-v2-6OUMAXLB-JXw9T96l.js +1 -0
  84. package/web-dist/assets/{swimlanes-5IMT3BWC-DvRYbkZi.js → swimlanes-5IMT3BWC-COiYgS0w.js} +1 -1
  85. package/web-dist/assets/swimlanesDiagram-G3AALYLV--1Wv7FQQ.js +8 -0
  86. package/web-dist/assets/{timeline-definition-FHXFAJF6-BNUa_DwI.js → timeline-definition-FHXFAJF6-Ci22coeH.js} +1 -1
  87. package/web-dist/assets/{treeView-QDETBFTQ-I6-IW6nJ.js → treeView-QDETBFTQ-4DW35czh.js} +1 -1
  88. package/web-dist/assets/{treemap-6X3UGDF4-CWWmEUYJ.js → treemap-6X3UGDF4-Dq_-Z6-Y.js} +1 -1
  89. package/web-dist/assets/{vennDiagram-L72KCM5P-DTDrPGLk.js → vennDiagram-L72KCM5P-CHQSIEYq.js} +1 -1
  90. package/web-dist/assets/{wardley-OPB4EBWU-CNsdgXXA.js → wardley-OPB4EBWU-Bwlh7HCY.js} +1 -1
  91. package/web-dist/assets/{wardleyDiagram-EHGQE667-YE0tq3Kh.js → wardleyDiagram-EHGQE667-DctjuPYn.js} +1 -1
  92. package/web-dist/assets/{xychartDiagram-FW5EYKEG-D0ofMX8C.js → xychartDiagram-FW5EYKEG-ZoIGosv8.js} +1 -1
  93. package/web-dist/favicon-16.png +0 -0
  94. package/web-dist/favicon-32.png +0 -0
  95. package/web-dist/favicon.svg +17 -61
  96. package/web-dist/icon-192.png +0 -0
  97. package/web-dist/icon-512.png +0 -0
  98. package/web-dist/icon-maskable-512.png +0 -0
  99. package/web-dist/index.html +19 -8
  100. package/web-dist/manifest.webmanifest +3 -3
  101. package/runtime/cc-worker/hooks/scratchpad-stop.mjs +0 -125
  102. package/runtime/cc-worker/skills/handoff/SKILL.md +0 -209
  103. package/web-dist/assets/architecture-TIHT7OUA-CAviNivx.js +0 -1
  104. package/web-dist/assets/channel-CqKDIFQF.js +0 -1
  105. package/web-dist/assets/classDiagram-OUVF2IWQ-B_-6iXYY.js +0 -1
  106. package/web-dist/assets/classDiagram-v2-EOCWNBFH-B_-6iXYY.js +0 -1
  107. package/web-dist/assets/eventmodeling-45OFAUF4-MpmeH5YZ.js +0 -1
  108. package/web-dist/assets/flowDiagram-23GEKE2U-D-QgjjhF.js +0 -1
  109. package/web-dist/assets/index-w4v-GZEc.js +0 -358
  110. package/web-dist/assets/index-zyi22LPz.css +0 -1
  111. package/web-dist/assets/railroad-abnf-AHOZXSZD-YeUoiySk.js +0 -1
  112. package/web-dist/assets/railroad-ebnf-EBAXGLYW-Ddw1SuGG.js +0 -1
  113. package/web-dist/assets/railroad-peg-LSFZ7HO6-Dd8BOGeW.js +0 -1
  114. package/web-dist/assets/stateDiagram-v2-6OUMAXLB-hc41W5Lx.js +0 -1
  115. package/web-dist/assets/swimlanesDiagram-G3AALYLV-BZyGdgSG.js +0 -8
@@ -1,49 +1,44 @@
1
1
  #!/usr/bin/env node
2
2
  // @ts-check
3
- // SCRATCHPAD REINFORCEMENT hook (frizz-worker) — keeps the ONE per-thread scratchpad
4
- // (`.frizz/threads/<sid>/scratch.md`) written and re-grounded across compaction. Run directly with
5
- // node (zero deps, max Node compat), mirroring the other hooks in this plugin.
3
+ // SCRATCH-DIRECTORY hook (frizz-worker) — keeps a worker aware of the per-thread scratch directory
4
+ // (`.frizz/threads/<sid>/`) it may use, and re-orients it on that directory when its context is lost.
5
+ // Run directly with node (zero deps, max Node compat), mirroring the other hooks in this plugin.
6
6
  //
7
- // WHY THIS EXISTS: compaction is the largest source of context loss in a long session, and the
8
- // scratchpad is frizz's answer to it but the scratchpad only works if two things happen, and
9
- // nothing was enforcing either. It has to be WRITTEN while the work is happening, and it has to be
10
- // RE-READ after the context is gone. A worker that forgets the first has nothing to recover; one
11
- // that forgets the second has a recovery it never opens.
7
+ // WHAT THIS USED TO BE, AND WHY IT IS NOT ANY MORE. Until 2026-08-06 frizz provisioned ONE canonical
8
+ // `scratch.md` per thread and this hook spliced its HEAD into the context window after every compaction,
9
+ // on the argument that a bare "remember to read your scratchpad" routes recovery through a decision the
10
+ // model can skip. That argument was sound and the mechanism still lost: it made a maintained file the
11
+ // price of admission for every worker, it needed a merge-only contract per backend to keep sub-agents
12
+ // from clobbering it, and the injection was invisible to the operator, who could neither see nor change
13
+ // what their worker would be told.
12
14
  //
13
- // This hook closes both, and deliberately targets scratch.md rather than a second file of its own.
14
- // An earlier revision shipped a separate `carryover.md` brief; it was redundant with the scratchpad
15
- // and made the worker maintain two overlapping documents. ONE doc per thread is the rule — the
16
- // scratchpad the dispatcher already provisions, already names in the system prompt, and shares with
17
- // sub-agents under the merge-only contract (see agent-dispatch.mjs).
15
+ // The maintainer's replacement (chosen deliberately over keeping a canonical doc): the thread gets a
16
+ // free-form scratch DIRECTORY, and compaction recovery moves to `mcp__frizz__recurring_prompt`'s
17
+ // post_compaction trigger the worker writes whatever doc it likes and LINKS it in a prompt frizz
18
+ // re-sends when the context is summarized away. Durable in SQLite, visible in the thread footer,
19
+ // editable by the human. This hook's job is therefore reduced to two honest things:
18
20
  //
19
- // CODEX CHILD EPILOGUE native Codex sub-agents inherit the root conversation's system/user
20
- // scratchpad instructions even with `fork_turns:"none"`. Sharing the thread pad is useful: a child
21
- // can persist progress even if the root later compacts. But an undifferentiated "keep it current"
22
- // mandate once made a child replace the whole document with its task notes, then DELETE that
23
- // replacement as a misguided rollback after realizing it had clobbered the root's state. The
24
- // `subagent-start` mode preserves collaborative writes while requiring merge-only, scoped edits. It
25
- // also carries the codex half of the default-off nesting rule (2026-08-04): a native child does the
26
- // work itself and does not `spawn_agent` a layer of its own unless its task said to. SubagentStart is
27
- // the only structural seam that reaches a native child, the way agent-dispatch.mjs's epilogue is for
28
- // Claude — the same rule, stated once per backend.
21
+ // 1. TELL the worker the directory exists, and that the arming is what makes anything in it come back.
22
+ // 2. On compact/resume, say what is IN the directory a listing, not the content. That is the
23
+ // degradation the maintainer accepted when choosing this over a canonical doc, and it is stated
24
+ // here rather than quietly re-implemented as an injection: a worker that never armed the trigger
25
+ // gets a pointer it may skip. Naming the files it already wrote is the most a pointer can do.
29
26
  //
30
- // THE RE-READ SIDEinjection, not a reminder. On compaction/resume the head of scratch.md is
31
- // spliced into the context window by the harness, before the model's first token, alongside a
32
- // pointer to read the rest. A bare "remember to read your scratchpad" routes recovery through a
33
- // decision the model can skip, which is exactly the failure being fixed; injecting the head
34
- // guarantees a floor of orientation even if the pointer is ignored. The cap keeps the guarantee
35
- // affordable a scratchpad is unbounded working memory and the whole file is not owed to every
36
- // session start.
27
+ // CODEX CHILD EPILOGUEnative Codex sub-agents inherit the root conversation's system/user
28
+ // instructions even with `fork_turns:"none"`. The `subagent-start` mode is what tells such a child to
29
+ // write its OWN file rather than treating a document it did not create as its own. It also carries the
30
+ // codex half of the default-off nesting rule (2026-08-04): a native child does the work itself and does
31
+ // not `spawn_agent` a layer of its own unless its task said to. SubagentStart is the only structural
32
+ // seam that reaches a native child, the way agent-dispatch.mjs's epilogue is for Claude.
37
33
  //
38
- // THE WRITE SIDE — a staleness nudge on two channels:
34
+ // THE WRITE-SIDE NUDGE — two channels:
39
35
  // UserPromptSubmit — the turn boundary.
40
36
  // PostToolUse — MID-TURN, and this is the one that matters. A frizz worker runs enormous
41
37
  // autonomous turns (dozens of tool calls between human prompts), so a
42
- // turn-boundary-only nudge can miss an entire session's worth of work and let
43
- // it compact unpersisted. PostToolUse additionalContext was verified live
44
- // against cli 2.1.220: a real session quoted a sentinel injected after a Bash
45
- // call. Both channels share one state file, so the interval is global — moving
46
- // to per-tool-call firing does NOT multiply the number of nudges.
38
+ // turn-boundary-only nudge can miss an entire session's worth of work. PostToolUse
39
+ // additionalContext was verified live against cli 2.1.220: a real session quoted a
40
+ // sentinel injected after a Bash call. Both channels share one state file, so the
41
+ // interval is global firing per tool call does NOT multiply the nudges.
47
42
  //
48
43
  // NO HOOK FIRES ON CONTEXT PRESSURE — measured, not assumed. Claude Code 2.1.220 exposes 31 hook
49
44
  // events and not one of them signals an approaching context limit; no hook input carries a token
@@ -59,22 +54,19 @@
59
54
  // tried and REMOVED on 2026-07-02 (maintainer's call): the block-until-file-edited nag forced even
60
55
  // trivial workers into Read/Edit dances that render as noise in the chat UI. This nudges; it never
61
56
  // blocks.
62
- import { readFileSync, writeFileSync, mkdirSync, statSync, openSync, readSync, closeSync } from 'node:fs';
57
+ import { readFileSync, writeFileSync, mkdirSync, statSync, readdirSync, openSync, readSync, closeSync } from 'node:fs';
63
58
  import { join } from 'node:path';
64
59
  import { currentSessionId } from '../scripts/frizz/config.mjs';
65
60
 
66
- const SCRATCH_FILE = 'scratch.md';
61
+ /** How many filenames a listing names before it summarizes the rest. A worker with 200 scratch files
62
+ * needs to know that, not to be handed 200 lines of them. */
63
+ const MAX_LISTED_FILES = intFromEnv('FRIZZ_SCRATCH_MAX_LISTED', 40);
67
64
 
68
- /** Hard cap on injected characters. The scratchpad is unbounded working memory, so this bounds what
69
- * a session start is charged; past the cap we inject the HEAD (a scratchpad's orientation lives at
70
- * the top) and say plainly that it was clipped, pointing at the file for the rest. */
71
- const MAX_INJECT_CHARS = intFromEnv('FRIZZ_SCRATCHPAD_MAX_CHARS', 12000);
72
-
73
- /** Context-token growth since the last scratchpad write that marks it stale. 60k is ~a third of a
74
- * 200k window and ~6% of a 1M one: frequent enough that the pad is never many turns behind, rare
75
- * enough not to be chatter. Also the first-write trigger — an untouched template counts as
76
- * unwritten, so the baseline is zero and the first nudge lands once a session has accumulated 60k
77
- * tokens actually worth persisting. */
65
+ /** Context-token growth since the last scratch write that marks the directory stale. 60k is ~a third of
66
+ * a 200k window and ~6% of a 1M one: frequent enough that a long effort is reminded while there is
67
+ * still something to record, rare enough not to be chatter. Also the first-write trigger an EMPTY
68
+ * directory has no clock of its own, so the baseline is zero and the first nudge lands once a session
69
+ * has accumulated 60k tokens actually worth persisting. */
78
70
  const STALE_TOKENS = intFromEnv('FRIZZ_SCRATCHPAD_STALE_TOKENS', 60000);
79
71
 
80
72
  /** @param {string} name @param {number} fallback */
@@ -140,8 +132,7 @@ try {
140
132
  if (!sid) process.exit(0);
141
133
 
142
134
  const threadDir = join(projectDir, '.frizz', 'threads', sid);
143
- const relPath = '.frizz/threads/' + sid + '/' + SCRATCH_FILE;
144
- const absPath = join(threadDir, SCRATCH_FILE);
135
+ const relPath = '.frizz/threads/' + sid + '/';
145
136
 
146
137
  // ── mode: subagent-start (Codex child-only merge epilogue) ───────────────────────────────────────
147
138
  if (mode === 'subagent-start') {
@@ -149,18 +140,16 @@ if (mode === 'subagent-start') {
149
140
  hookSpecificOutput: {
150
141
  hookEventName: 'SubagentStart',
151
142
  additionalContext:
152
- '⟦shared scratchpadmerge only⟧ `' + relPath + '` is the canonical document shared with ' +
153
- 'the top-level `/root` worker. Update your own task progress in it as you work rather than ' +
154
- 'leaving the root as its sole writer. Before every edit, re-read the current file, then ' +
155
- 'patch only your scoped task/progress entry while ' +
156
- 'preserving every other agent’s content. This exact file is Frizz coordination state, not a ' +
157
- 'project deliverable or source edit: a scoped merge is an explicit exception to delegated ' +
158
- 'phrases such as “write only <path>” or “do not modify the repo”; never classify that merge ' +
159
- 'as unauthorized or roll it back. Never delete, truncate, reinitialize, move, or replace the ' +
160
- 'whole file not even to “clean up” or undo your own mistaken change. Other project files, ' +
161
- 'including files at the repository root, remain governed by your delegated authority; their ' +
162
- 'location alone neither permits nor forbids editing. If the file is absent or you cannot ' +
163
- 'merge safely, return your state to the parent without writing.\n' +
143
+ '⟦scratch directorywrite your OWN file⟧ `' + relPath + '` is a scratch directory shared with ' +
144
+ 'the top-level `/root` worker and any sibling agents. If you want notes on disk, create your ' +
145
+ 'own file in it never edit, replace or delete a file another agent wrote, and never treat a ' +
146
+ 'document you did not create as yours to rewrite. One file per writer means there is nothing ' +
147
+ 'to merge and nothing to clobber. Writing your own file here is Frizz coordination state, not ' +
148
+ 'a project deliverable or source edit: it remains allowed when a delegated task limits your ' +
149
+ 'deliverable paths with phrases such as “write only <path>” or “do not modify the repo”, and ' +
150
+ 'must never be classified as unauthorized or rolled back. Other project files, including files ' +
151
+ 'at the repository root, remain governed by your delegated authority; their location alone ' +
152
+ 'neither permits nor forbids editing.\n' +
164
153
  '⟦no fan-out of your own⟧ Do the work yourself: do not spawn agents of your own ' +
165
154
  '(`spawn_agent`) unless the task you were given explicitly tells you to. You are already one ' +
166
155
  'prong of the root worker’s fan-out, and another layer below you splits the context you were ' +
@@ -179,54 +168,41 @@ try {
179
168
  /* a read-only or racing FS just means the agent's Write creates it instead */
180
169
  }
181
170
 
182
- /** Raw scratchpad text, or null when absent/empty/unreadable. */
183
- function readPad() {
171
+ /** The worker's own files in the scratch directory, newest first name, size, and how long ago it was
172
+ * touched. Dotfiles are excluded: frizz keeps its own per-thread bookkeeping in here
173
+ * (`.scratchpad-state.json`), and reporting that back to the worker as its own notes would be a lie.
174
+ * @returns {{ name: string, size: number, mtimeMs: number }[]} */
175
+ function listScratch() {
176
+ let names;
184
177
  try {
185
- const raw = readFileSync(absPath, 'utf8');
186
- return raw.trim() ? raw : null;
178
+ names = readdirSync(threadDir);
187
179
  } catch {
188
- return null;
180
+ return [];
189
181
  }
182
+ const out = [];
183
+ for (const name of names) {
184
+ if (name.startsWith('.')) continue;
185
+ try {
186
+ const st = statSync(join(threadDir, name));
187
+ if (!st.isFile()) continue;
188
+ out.push({ name, size: st.size, mtimeMs: st.mtimeMs });
189
+ } catch {
190
+ // vanished between the listing and the stat — simply not listed
191
+ }
192
+ }
193
+ return out.sort((a, b) => b.mtimeMs - a.mtimeMs);
190
194
  }
191
195
 
192
- /** Characters of SUBSTANTIVE content the pad minus its provisioned skeleton.
193
- * frizz writes scratch.md up front with an H1, a one-line orientation, section headings and an empty
194
- * task box, so unlike a file that simply does not exist, "present" no longer means "written". This
195
- * strips exactly those skeleton shapes and measures what is left. A heuristic on purpose: it only
196
- * decides whether to NUDGE, so a wrong call costs one redundant reminder, never correctness.
197
- * @param {string|null} text */
198
- function substanceLength(text) {
199
- if (!text) return 0;
200
- return text
201
- .split('\n')
202
- .filter((line) => {
203
- const t = line.trim();
204
- if (!t) return false;
205
- if (t.startsWith('#')) return false; // headings
206
- if (/^[-*]\s*\[\s*\]\s*$/.test(t)) return false; // an empty task box
207
- // The visible legend/collaboration guide provisioned in every new pad. They teach the shared
208
- // editing contract but are not evidence that the worker has recorded any task state yet.
209
- if (/^>\s*(?:Status legend:|Collaboration:)/.test(t)) return false;
210
- // The provisioned orientation line. Matched on the CONCEPT rather than a leading word, because
211
- // the wording has changed once already and pads written under the old shape are still on disk —
212
- // anchoring on a prefix silently reclassified a template as "written", which made an empty pad
213
- // skip its re-grounding and made the summarizer swallow a skeleton.
214
- if (/compaction-survival mechanism|compaction-proof working memory/.test(t)) return false;
215
- return true;
216
- })
217
- .join('')
218
- .trim().length;
196
+ /** The listing as text: what the worker actually has to go back to. NAMES ONLY, never content — see
197
+ * the header for why this hook points rather than injects.
198
+ * @param {{ name: string, size: number }[]} files */
199
+ function describe(files) {
200
+ const shown = files.slice(0, MAX_LISTED_FILES).map((f) => ' - `' + relPath + f.name + '` (' + f.size + ' bytes)');
201
+ if (files.length > shown.length) shown.push(' - …and ' + (files.length - shown.length) + ' more');
202
+ return shown.join('\n');
219
203
  }
220
204
 
221
- /** @param {string} text */
222
- function capped(text) {
223
- if (text.length <= MAX_INJECT_CHARS) return text;
224
- return (
225
- text.slice(0, MAX_INJECT_CHARS) +
226
- '\n\n[…clipped at ' + MAX_INJECT_CHARS.toLocaleString('en-US') + ' characters — read `' + relPath +
227
- '` for the rest.]'
228
- );
229
- }
205
+
230
206
 
231
207
  /** @param {string} additionalContext @param {'SessionStart'|'UserPromptSubmit'|'PostToolUse'} hookEventName */
232
208
  function emitJson(additionalContext, hookEventName) {
@@ -236,13 +212,10 @@ function emitJson(additionalContext, hookEventName) {
236
212
 
237
213
  // ── mode: session-start ──────────────────────────────────────────────────────────────────────────
238
214
  if (mode === 'session-start') {
239
- const pad = readPad();
240
- const written = substanceLength(pad) > 0;
241
- const parts = [];
215
+ const files = listScratch();
216
+ const written = files.length > 0;
242
217
 
243
- // The sources where the deep model of the work is actually GONE. On these the worker is ALWAYS
244
- // re-grounded on its scratchpad — whether or not there is anything in it yet, because "your pad is
245
- // empty and you just lost your context" is itself the most urgent thing the next turn can be told.
218
+ // The sources where the deep model of the work is actually GONE.
246
219
  const lostContext = input.source === 'compact' || input.source === 'resume' || input.source === 'clear'
247
220
 
248
221
  // Claude Code opens every compaction summary with the fixed preamble "This session is being
@@ -252,8 +225,7 @@ if (mode === 'session-start') {
252
225
  // auto-compaction at line 20239 and then declared "I'm out of context" / "I'm at the end of this
253
226
  // context window" on 13 consecutive turns at fills of 176k-244k, before self-diagnosing at line
254
227
  // 20628 — "I've been treating 'low context' as a stopping condition ... and winding down instead of
255
- // working." This hook is the only frizz text that lands in that exact window, so it is where the
256
- // preamble gets answered. Kept to two sentences: the re-grounding instruction is the payload.
228
+ // working." Kept to two sentences: the re-grounding instruction is the payload.
257
229
  const compactedNote =
258
230
  input.source === 'compact'
259
231
  ? ' The summary opens "a previous conversation that ran out of context" — that describes the ' +
@@ -262,41 +234,46 @@ if (mode === 'session-start') {
262
234
  'a reason to wind down, hand off, or leave the next step to a fresh session.'
263
235
  : ''
264
236
 
237
+ const parts = [];
265
238
  if (lostContext && written) {
266
- // Inject the head AND point at the file: the injection is the floor (it cannot be skipped), the
267
- // pointer is the ceiling (the pad may be longer than the cap, and it is the canonical doc).
268
- const lead =
269
- input.source === 'compact'
270
- ? '⟦scratchpad reground here⟧ Context was just compacted. Your scratchpad `' + relPath +
271
- '` is the CANONICAL record of this thread and the head of it follows. RE-GROUND ON IT BEFORE ' +
272
- 'DOING ANYTHING ELSE: re-read the full file, treat it as authoritative over anything the ' +
273
- 'summary implies, and re-read only the code you are about to describe or change.' + compactedNote
274
- : '⟦scratchpad reground here⟧ This session resumed and lost its working context. Your ' +
275
- 'scratchpad `' + relPath + '` is the CANONICAL record of this thread and the head of it ' +
276
- 'follows. Re-read the full file before acting.';
277
- parts.push(lead + '\n\n' + capped(/** @type {string} */ (pad)) + '\n\n⟦end scratchpad⟧');
239
+ // NAMES, NOT CONTENT. This is the pointer the maintainer accepted in place of an injection when
240
+ // the canonical pad was dropped; the guaranteed channel is now the recurring prompt's
241
+ // post_compaction trigger, which the worker arms for itself. Saying which files exist is the most
242
+ // a pointer can do, and it is worth doing: a worker that wrote three docs and lost its context
243
+ // otherwise has no idea they are there.
244
+ parts.push(
245
+ '⟦scratch directory⟧ Context was just ' + (input.source === 'compact' ? 'compacted' : 'lost') +
246
+ '. You have files in your scratch directory `' + relPath + '`:\n' + describe(files) +
247
+ '\n\nRead whichever of them bears on what you were doing BEFORE acting, and treat what you ' +
248
+ 'wrote there as authoritative over anything the summary implies. If you have not already, arm ' +
249
+ 'mcp__frizz__recurring_prompt with post_compaction: true and a prompt linking the one that ' +
250
+ 'matters, so the next compaction hands it back to you without relying on this note.' +
251
+ compactedNote,
252
+ );
278
253
  } else if (lostContext) {
279
- // Context is gone and there is nothing to restore. Say plainly that the exact pad is empty and
280
- // constrain reconstruction to the compact summary + named handoffs. Searching neighbouring
281
- // thread pads is both expensive and unsafe: they belong to unrelated workers.
254
+ // Context is gone and the worker left itself nothing. Say so plainly and constrain reconstruction:
255
+ // searching neighbouring threads' directories is both expensive and unsafe they belong to
256
+ // unrelated workers.
282
257
  parts.push(
283
- '⟦scratchpad — reground here⟧ Context was just compacted or resumed. Your scratchpad `' +
284
- relPath + '` is the CANONICAL record of this thread, but it is absent or has nothing ' +
285
- 'substantive in it. That exact path is authoritative: do not search other ' +
286
- '`.frizz/threads/*/scratch.md` files for a substitute, and do not broadly reload repo docs or ' +
258
+ '⟦scratch directory⟧ Context was just compacted or resumed, and your scratch directory `' +
259
+ relPath + '` is EMPTY you left yourself nothing to recover from. Do not search other ' +
260
+ '`.frizz/threads/*/` directories for a substitute, and do not broadly reload repo docs or ' +
287
261
  'skills merely to reconstruct context. Recover from the retained compaction summary and any ' +
288
- 'task-specific handoff it directly names, then WRITE this exact pad: the problem, the approach ' +
289
- 'and the approaches you rejected, the decisions the human made, what is verified versus merely ' +
290
- 'believed, and the next action.' + compactedNote,
262
+ 'task-specific handoff it directly names. If this effort is long enough to be compacted again, ' +
263
+ 'write the account down this time — the problem, the approach and what you rejected, the ' +
264
+ "human's decisions, what is verified versus believed, the next action — and arm " +
265
+ 'mcp__frizz__recurring_prompt with post_compaction: true and a prompt linking it.' +
266
+ compactedNote,
291
267
  );
292
268
  } else {
293
- // A fresh start has lost nothing — teach the contract so the pad gets written in the first place.
269
+ // A fresh start has lost nothing — teach the arrangement while there is still time to make it.
294
270
  parts.push(
295
- '⟦scratchpad⟧ `' + relPath + '` is the CANONICAL document for this thread and your ONE durable ' +
296
- 'working doc: its head is injected back into your context automatically whenever the context ' +
297
- 'is lost, so it is the only thing guaranteed to survive a compaction. Keep it current as you ' +
298
- 'work the problem, the approach and the approaches you REJECTED and why, decisions the human ' +
299
- 'made or reversed, what is VERIFIED by running it versus merely believed, and the next action.',
271
+ '⟦scratch directory⟧ `' + relPath + '` is yours: any files you like, no format expected, and ' +
272
+ 'nothing in it is read automatically. On an effort long enough to be compacted, write the doc ' +
273
+ 'you would want if you lost your context the approach, what you rejected, the decisions the ' +
274
+ 'human made, what is VERIFIED versus believed, the next action then arm ' +
275
+ 'mcp__frizz__recurring_prompt with post_compaction: true and a prompt that LINKS it. That ' +
276
+ 'arming, not the file, is what makes it come back.',
300
277
  );
301
278
  }
302
279
  emitJson(parts.join('\n\n'), 'SessionStart');
@@ -307,15 +284,13 @@ if (mode === 'session-start') {
307
284
  // `Additional Instructions:`. Worded as an ordinary editorial note: precompact-instructions.mjs
308
285
  // records that a summarizer REFUSES instructions that read like prompt-hijacking.
309
286
  if (mode === 'precompact') {
310
- const pad = readPad();
311
- if (substanceLength(pad) === 0) process.exit(0);
287
+ const files = listScratch();
288
+ if (files.length === 0) process.exit(0);
312
289
  process.stdout.write(
313
- 'The worker keeps a running scratchpad of this effort at `' + relPath + '`, written by hand as ' +
314
- 'the work progressed. Its current head is reproduced below. Treat it as the authoritative ' +
315
- 'account of the problem, the chosen approach, and the decisions behind them, and make sure the ' +
316
- 'summary preserves its substance where it disagrees with your reading of the transcript, ' +
317
- 'prefer it.\n\n' +
318
- capped(/** @type {string} */ (pad)) + '\n',
290
+ 'The worker kept working notes for this effort in `' + relPath + '`:\n' + describe(files) +
291
+ '\nThose files are the hand-written account of the problem, the chosen approach and the ' +
292
+ 'decisions behind them. Make sure the summary preserves the substance of the work they describe, ' +
293
+ 'and name their paths in it so the continuing session can open them.\n',
319
294
  );
320
295
  process.exit(0);
321
296
  }
@@ -392,18 +367,15 @@ if (mode === 'nudge') {
392
367
  // silence is always safe.
393
368
  if (!tokens) process.exit(0);
394
369
 
395
- const pad = readPad();
396
- const written = substanceLength(pad) > 0;
370
+ const files = listScratch();
397
371
 
398
- let mtimeMs = 0;
399
- try {
400
- mtimeMs = written ? statSync(absPath).mtimeMs : 0;
401
- } catch {
402
- mtimeMs = 0;
403
- }
372
+ // The NEWEST write across the whole directory is the clock. A worker with several docs has "written
373
+ // recently" if it touched ANY of them — the nudge asks whether the effort is being recorded at all,
374
+ // not whether one particular file moved. Zero when the directory is empty.
375
+ const mtimeMs = files.length ? Math.max(...files.map((f) => f.mtimeMs)) : 0;
404
376
 
405
377
  let state = readState();
406
- // A changed mtime means the pad was just written — rebase the baseline to NOW and go quiet. This is
378
+ // A changed mtime means something was just written — rebase the baseline to NOW and go quiet. This is
407
379
  // also the first-ever observation, and it is why a fresh write buys a full interval of silence. It
408
380
  // fires for a human's hand-edit exactly as for the agent's Write: both are just an mtime change.
409
381
  if (state.mtimeMs !== mtimeMs) {
@@ -411,8 +383,8 @@ if (mode === 'nudge') {
411
383
  writeState(state);
412
384
  }
413
385
 
414
- // An unwritten pad (absent, or still the provisioned skeleton) measures growth from ZERO: the whole
415
- // session is unpersisted, so the clock starts at the beginning, not at whenever this first looked.
386
+ // An EMPTY directory measures growth from ZERO: the whole session is unrecorded, so the clock starts
387
+ // at the beginning, not at whenever this first looked.
416
388
  const baseline = mtimeMs ? (state.tokensAtWrite ?? tokens) : 0;
417
389
  const grown = tokens - baseline;
418
390
  if (grown < STALE_TOKENS) process.exit(0);
@@ -428,17 +400,18 @@ if (mode === 'nudge') {
428
400
  );
429
401
  emitJson(
430
402
  mtimeMs
431
- ? '⟦scratchpad stale⟧ Your context has grown ~' + k + 'k tokens since you last wrote `' +
432
- relPath + '`. If this effort is long enough that losing your reasoning would hurt, top it up ' +
433
- 'in passing — the approach, what you rejected, the human\'s decisions, what is verified ' +
434
- 'versus believed. Its head is injected back after a compaction. This is a background note, ' +
435
- 'NOT a task and NOT a reason to pause: do not stop working to service it, and never end a ' +
436
- 'turn on it while the human\'s instruction still has parts left.'
437
- : '⟦scratchpad empty⟧ This session is ~' + k + 'k tokens deep and `' + relPath + '` is empty. ' +
438
- 'That is fine for a single direct task — the pad is optional and writing in it is not doing ' +
439
- 'the work. If this effort is long or branching, a few lines on the approach and the human\'s ' +
440
- 'decisions will survive a compaction. This is a background note, NOT a task and NOT a reason ' +
441
- 'to pause: keep going with what you were asked to do.',
403
+ ? '⟦scratch notes stale⟧ Your context has grown ~' + k + 'k tokens since you last wrote anything ' +
404
+ 'in `' + relPath + '`. If this effort is long enough that losing your reasoning would hurt, ' +
405
+ 'top it up in passing — the approach, what you rejected, the human\'s decisions, what is ' +
406
+ 'verified versus believed and make sure a post_compaction recurring prompt links it. This ' +
407
+ 'is a background note, NOT a task and NOT a reason to pause: do not stop working to service ' +
408
+ 'it, and never end a turn on it while the human\'s instruction still has parts left.'
409
+ : '⟦scratch directory empty⟧ This session is ~' + k + 'k tokens deep and `' + relPath + '` is ' +
410
+ 'empty. That is fine for a single direct task — notes are optional and writing them is not ' +
411
+ 'doing the work. If this effort is long or branching, write down the approach and the ' +
412
+ 'human\'s decisions and arm mcp__frizz__recurring_prompt with post_compaction: true linking ' +
413
+ 'that file, or a compaction takes your reasoning with it. This is a background note, NOT a ' +
414
+ 'task and NOT a reason to pause: keep going with what you were asked to do.',
442
415
  event,
443
416
  );
444
417
  }
@@ -5,14 +5,15 @@
5
5
  //
6
6
  // A frizz worker is a top-level interactive `claude` the UI spawns per effort; the slug arrives in
7
7
  // env FRIZZ_THREAD (and a `THREAD:` line in the first prompt). There are NO thread files, no
8
- // frontmatter, no status field — a worker SIGNALS through its final message (fences) and PERSISTS
9
- // through a scratchpad. This hook injects, on every session start (startup/resume/clear/compact):
8
+ // frontmatter, no status field — a worker SIGNALS through its final message (fences), and anything that
9
+ // must outlive its context window is an arrangement it makes for itself. This hook injects, on every
10
+ // session start (startup/resume/clear/compact):
10
11
  // 1. `core` — a runtime re-grounding + pointer, NOT a second copy of the contract: the full worker
11
12
  // contract lives ONCE in the system prompt (workerPrompt.ts) the server injects at spawn. This
12
- // carries only what a static system prompt can't: the runtime scratchpad PATH + an essential
13
+ // carries only what a static system prompt can't: the runtime scratch-directory PATH + an essential
13
14
  // signal-at-rest anchor + a pointer to the system-prompt contract.
14
- // 2. the SCRATCHPAD PATH — `.frizz/threads/<session_id>/scratch.md`, the worker's compaction-proof memory.
15
- // 3. on `compact` — a short re-grounding (compaction drops the deep model + the scratchpad reminder).
15
+ // 2. the SCRATCH DIRECTORY — `.frizz/threads/<session_id>/`, a folder the worker may use as it likes.
16
+ // 3. on `compact` — a short re-grounding (compaction drops the deep model + this orientation).
16
17
  //
17
18
  // GATE: everything is gated on FRIZZ_THREAD being set, so the plugin is completely inert when
18
19
  // loaded outside a frizz worker (e.g. a plain `claude --plugin-dir cc-worker` smoke run).
@@ -44,7 +45,7 @@ if (!thread) process.exit(0);
44
45
  const dir = process.env.CLAUDE_PROJECT_DIR ?? '.';
45
46
 
46
47
  // Neutralize the orchestrator cc plugin for THIS session (defensive; see header + DECISIONS.md).
47
- // The session id also names the worker's scratchpad (`.frizz/threads/<session_id>/scratch.md`).
48
+ // The session id also names the worker's scratch directory (`.frizz/threads/<session_id>/`).
48
49
  let sid = null;
49
50
  try {
50
51
  sid = currentSessionId(input.session_id);
@@ -53,24 +54,24 @@ try {
53
54
  /* best-effort — a failed sentinel write just leaves cc at its dormant default */
54
55
  }
55
56
  const scratch = sid
56
- ? '.frizz/threads/' + sid + '/scratch.md'
57
- : '.frizz/threads/<session-id>/scratch.md';
57
+ ? '.frizz/threads/' + sid + '/'
58
+ : '.frizz/threads/<session-id>/';
58
59
 
59
60
  // A RUNTIME re-grounding + pointer, NOT a second copy of the contract. The full worker contract
60
- // (signal fences, scratchpad rules, sub-agent rules, the question handback) lives
61
+ // (signal fences, scratch-directory rules, sub-agent rules, the question handback) lives
61
62
  // ONCE in the system prompt frizz injects at spawn (workerPrompt.ts / loadWorkerPrompt) — which is
62
63
  // re-applied on every resume and survives compaction. This hook adds only what a static system prompt
63
- // CANNOT carry: the runtime-derived scratchpad PATH, an essential signal-at-rest anchor, and (below)
64
+ // CANNOT carry: the runtime-derived scratch-directory PATH, an essential signal-at-rest anchor, and (below)
64
65
  // the compaction re-read nudge, gh guidance, and the defensive cc-orchestrator off-sentinel.
65
66
  const core =
66
- '⟦frizz worker contract⟧ You are a frizz WORKER driving EXACTLY ONE effort. Your FULL operating contract — the end-of-turn signal fences, scratchpad rules, sub-agent rules, and the question handback — lives in your SYSTEM PROMPT; follow it there (this is a runtime re-grounding, not a second copy). The human + the frizz app are the ORCHESTRATOR; you drive ONE effort and never scan the board or touch other efforts. There is no orchestrator mode and no fleet to run: doing the work yourself is the default, and you dispatch a sub-agent only when the work genuinely decomposes into independent prongs.\n' +
67
- 'SCRATCHPAD (OPTIONAL): `' + scratch + '` — a scratch file kept FOR YOU, not a deliverable, and never a substitute for doing the work. A single direct task usually needs nothing in it. On a long effort it is crash insurance and your sub-agents\' shared blackboard: write the approach and what you rejected there AS YOU GO, mid-work, then KEEP WORKING; re-read it after any compaction or resume, and pass its PATH into every sub-agent prompt.\n' +
68
- 'DO NOT REST WHILE THE INSTRUCTION HAS PARTS LEFT — finish them in THIS turn; a milestone, a green test run and a long turn are none of them stopping points, and announcing the next step or recording it in the scratchpad is not doing it.\n' +
69
- 'SIGNAL AT REST through your FINAL MESSAGE, per the fence rules in your system prompt: bare rest is the ordinary handoff and queues for the human; ```done only when the effort\'s real work is COMPLETE (code LANDED on the mainline — an open PR is NOT done, park it on ```awaiting until it MERGES) and is a DISMISSAL (its card files the thread away where nobody looks again), so if the thread points at future work AT ALL — a pre-fix investigation, a live code-change discussion — bare rest instead, and uncertain is not done; the ONE exception is a planning session whose plan file is fully written and persisted, because that artifact outlives the thread; ```awaiting parks only a human:/timer:/pr-watch: gate, never CI/releases/merge progression (those stay ACTIVE); ```question is the operator ask. Load `frizz:handoff` for the full fence reference.\n' +
67
+ '⟦frizz worker contract⟧ You are a frizz WORKER driving EXACTLY ONE effort. Your FULL operating contract — the end-of-turn signal fences, scratch-directory rules, sub-agent rules, and the question handback — lives in your SYSTEM PROMPT; follow it there (this is a runtime re-grounding, not a second copy). The human + the frizz app are the ORCHESTRATOR; you drive ONE effort and never scan the board or touch other efforts. There is no orchestrator mode and no fleet to run: doing the work yourself is the default, and you dispatch a sub-agent only when the work genuinely decomposes into independent prongs.\n' +
68
+ 'SCRATCH DIRECTORY (OPTIONAL): `' + scratch + '` — a folder kept FOR YOU: any files you like, no format expected, nothing in it read automatically, and never a substitute for doing the work. A single direct task usually needs nothing. On a long effort write the doc you would want if you lost your context — the approach, what you rejected, the human\'s decisions — AS YOU GO, mid-work, then KEEP WORKING, and arm mcp__frizz__recurring_prompt with post_compaction: true and a prompt LINKING that file, because the arming is what brings it back. Give each sub-agent its OWN file rather than a shared one.\n' +
69
+ 'DO NOT REST WHILE THE INSTRUCTION HAS PARTS LEFT — finish them in THIS turn; a milestone, a green test run and a long turn are none of them stopping points, and announcing the next step or writing it into a scratch file is not doing it.\n' +
70
+ 'ALWAYS SIGN OFF WITH A FENCE, per the fence rules in your system prompt. A rest with NO fence is not a handoff — it is an item nobody can triage, and frizz will tell you so. ```done only when the effort\'s real work is COMPLETE (code LANDED on the mainline — an open PR is NOT done, park it on ```awaiting until it MERGES) and is a DISMISSAL (its card files the thread away where nobody looks again), so if the thread points at future work AT ALL — a pre-fix investigation, a live code-change discussion — ask a ```question instead, and uncertain is not done; the ONE exception is a planning session whose plan file is fully written and persisted, because that artifact outlives the thread; ```awaiting parks only a human:/timer:/pr-watch: gate, never CI/releases/merge progression (those stay ACTIVE); ```question is the operator ask. Blocked on a PR, CI or your own background shell? REGISTER it with mcp__frizz__watch and rest — it is durable, and you can drop it when it stops mattering. Keep the write-up SHORT: 1-3 sentences, then bullets starting with a **bolded verb phrase**.\n' +
70
71
  'DECIDE rather than ask: anything derivable from the code, the conventions, or ordinary engineering judgment is YOURS to settle — asking permission to do the work you were dispatched to do is not a question, it is the job. Reserve the operator for the irreversible and the genuinely human-owned.';
71
72
 
72
73
  const grounding =
73
- '⟦frizz worker re-grounding (post-compaction)⟧ Context was just compacted. You are still the frizz worker for effort `' + thread + '` — re-read your scratchpad `' + scratch + '` NOW to recover your working state and to-do list before asserting anything, and re-read any code before claiming how it is structured. Signal at rest through your FINAL MESSAGE: bare rest queues an ordinary handoff; ```done queues a checked completion until Archive and is a DISMISSAL — completed work only, and never when the thread still points at future work (a pre-fix investigation, a live code-change discussion); use a question or bare rest; ```awaiting parks only a human:/timer: gate (or a pr-watch: PR watcher, which wakes on any new review/comment, bot or human); ```question is the explicit higher-priority operator ask. CI/releases/merge progression stay active through Monitor/background Bash.';
74
+ '⟦frizz worker re-grounding (post-compaction)⟧ Context was just compacted. You are still the frizz worker for effort `' + thread + '` — read whatever you left yourself in `' + scratch + '` NOW to recover your working state and to-do list before asserting anything, and re-read any code before claiming how it is structured. Signal at rest through your FINAL MESSAGE, always with a fence — a fenceless rest is an item nobody can triage: ```done queues a checked completion until Archive and is a DISMISSAL — completed work only, and never when the thread still points at future work (a pre-fix investigation, a live code-change discussion); use a question or bare rest; ```awaiting parks only a human:/timer: gate (or a pr-watch: PR watcher, which wakes on any new review/comment, bot or human); ```question is the explicit higher-priority operator ask. CI/releases/merge progression stay active through Monitor/background Bash.';
74
75
 
75
76
  // AUTH-GATED gh guidance — teach the worker to use `gh` well, but ONLY when signed in.
76
77
  // Shell `gh auth status --active`: exit 0 = an active gh account is authenticated. The whole gate is
@@ -55,7 +55,7 @@ while its own watcher is still live — the wait IS its work.
55
55
  Give the child, literally, in the prompt:
56
56
 
57
57
  - the exact command to run and the terminal condition to stop on;
58
- - the repo path and the scratchpad path;
58
+ - the repo path and the thread's scratch-directory path;
59
59
  - what to return: the verdict plus enough detail to act on a failure (job name, failing step, log tail);
60
60
  - an instruction not to fix anything — it observes and reports; you decide.
61
61
 
@@ -67,7 +67,7 @@ also inspect the workflow runs for the exact PR head, and treat `ACTION_REQUIRED
67
67
  not passing.
68
68
 
69
69
  **PR review activity.** Do not build a watcher — emit `awaiting` with `pr-watch: owner/repo#N` and frizz
70
- polls it for you, waking on any new review, approval, or comment, bot or human. See `frizz:handoff`.
70
+ polls it for you, waking on any new review, approval, or comment, bot or human.
71
71
 
72
72
  **A release or deploy.** Poll the artifact that proves it, not the pipeline that promises it — the
73
73
  published version on the registry, the health endpoint, the deployed asset hash.
Binary file