brand-manager-worker 0.2.0 → 0.2.1
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/package.json +1 -1
- package/worker/compact.js +71 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brand-manager-worker",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "The Goose Tools brand-deal worker \u2014 your computer reads your brand email and drafts replies in your voice for goosetools.com, using your own Claude account. Drafts only; it never sends.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
package/worker/compact.js
CHANGED
|
@@ -37,6 +37,48 @@ export const COMPACTABLE = [
|
|
|
37
37
|
|
|
38
38
|
const ARCHIVE_DIR = join(BRAND_DIR, "archive");
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* A free filename. The stamp used to be the date alone, so a second
|
|
42
|
+
* compaction on the same day overwrote the first archive — replacing the true
|
|
43
|
+
* original with an already-compacted copy and quietly destroying the only
|
|
44
|
+
* record of what was dropped.
|
|
45
|
+
*/
|
|
46
|
+
function archivePathFor(target) {
|
|
47
|
+
const stamp = new Date().toISOString().slice(0, 10);
|
|
48
|
+
let candidate = join(ARCHIVE_DIR, `${target}-${stamp}.md`);
|
|
49
|
+
for (let n = 2; existsSync(candidate); n += 1) {
|
|
50
|
+
candidate = join(ARCHIVE_DIR, `${target}-${stamp}-${n}.md`);
|
|
51
|
+
}
|
|
52
|
+
return candidate;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Compaction can land a file just above the threshold — negotiation.md came
|
|
56
|
+
// out at 48KB against a 40KB line. Without a memory of that, every scan
|
|
57
|
+
// forever would re-compact it: an agent call each time, for a file that is
|
|
58
|
+
// already as small as it gets. So a file is left alone after a compaction
|
|
59
|
+
// until it has actually grown again.
|
|
60
|
+
const STATE_PATH = () => join(ARCHIVE_DIR, ".compact-state.json");
|
|
61
|
+
const REGROWTH = 1.15;
|
|
62
|
+
|
|
63
|
+
function readState() {
|
|
64
|
+
try {
|
|
65
|
+
return JSON.parse(readFileSync(STATE_PATH(), "utf8"));
|
|
66
|
+
} catch {
|
|
67
|
+
return {};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function noteCompaction(target, size) {
|
|
72
|
+
try {
|
|
73
|
+
const state = readState();
|
|
74
|
+
state[target] = { size, at: new Date().toISOString() };
|
|
75
|
+
mkdirSync(ARCHIVE_DIR, { recursive: true });
|
|
76
|
+
writeFileSync(STATE_PATH(), JSON.stringify(state, null, 2));
|
|
77
|
+
} catch {
|
|
78
|
+
// A missing note only costs one redundant compaction; never fail over it.
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
40
82
|
export function sizeOf(path) {
|
|
41
83
|
try {
|
|
42
84
|
return statSync(path).size;
|
|
@@ -46,7 +88,14 @@ export function sizeOf(path) {
|
|
|
46
88
|
}
|
|
47
89
|
|
|
48
90
|
export function oversized() {
|
|
49
|
-
|
|
91
|
+
const state = readState();
|
|
92
|
+
return COMPACTABLE.filter(({ target, path }) => {
|
|
93
|
+
const size = sizeOf(path);
|
|
94
|
+
if (size <= COMPACT_THRESHOLD) return false;
|
|
95
|
+
const last = state[target];
|
|
96
|
+
// Already squeezed and hasn't meaningfully regrown — nothing left to win.
|
|
97
|
+
return !last?.size || size > last.size * REGROWTH;
|
|
98
|
+
});
|
|
50
99
|
}
|
|
51
100
|
|
|
52
101
|
const prompt = (label, body) =>
|
|
@@ -100,33 +149,36 @@ export async function compactFile({ target, path }) {
|
|
|
100
149
|
);
|
|
101
150
|
const after = typeof out?.markdown === "string" ? out.markdown.trim() : "";
|
|
102
151
|
|
|
152
|
+
// A transient failure (bad output, timeout) must stay retryable — the file
|
|
153
|
+
// is over budget and we want another go. Only the deterministic refusals
|
|
154
|
+
// below get remembered, because re-asking would refuse identically.
|
|
103
155
|
if (!after) return { target, ok: false, error: "no parseable rewrite" };
|
|
104
156
|
|
|
105
157
|
// Guards. An agent asked to shrink a file can decide to summarize it, and a
|
|
106
158
|
// 3KB precis of 66KB of learned voice is a catastrophic, silent loss — the
|
|
107
159
|
// archive would be the only copy and nobody would notice for weeks.
|
|
108
160
|
if (after.length < before.length * 0.25) {
|
|
109
|
-
return
|
|
161
|
+
return refuse(
|
|
110
162
|
target,
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
163
|
+
before.length,
|
|
164
|
+
`${before.length} -> ${after.length} bytes looks like a summary, not a rewrite`,
|
|
165
|
+
);
|
|
114
166
|
}
|
|
115
167
|
if (after.length >= before.length) {
|
|
116
|
-
return
|
|
168
|
+
return refuse(target, before.length, "no smaller than the original");
|
|
117
169
|
}
|
|
118
170
|
// The headings are the file's skeleton; losing most of them means it was
|
|
119
171
|
// restructured rather than compacted.
|
|
120
172
|
const headingsBefore = (before.match(/^##\s/gm) ?? []).length;
|
|
121
173
|
const headingsAfter = (after.match(/^##\s/gm) ?? []).length;
|
|
122
174
|
if (headingsBefore >= 4 && headingsAfter < 2) {
|
|
123
|
-
return
|
|
175
|
+
return refuse(target, before.length, "lost the section structure");
|
|
124
176
|
}
|
|
125
177
|
|
|
126
178
|
mkdirSync(ARCHIVE_DIR, { recursive: true });
|
|
127
|
-
|
|
128
|
-
writeFileSync(join(ARCHIVE_DIR, `${target}-${stamp}.md`), before);
|
|
179
|
+
writeFileSync(archivePathFor(target), before);
|
|
129
180
|
writeFileSync(path, `${after}\n`);
|
|
181
|
+
noteCompaction(target, after.length);
|
|
130
182
|
|
|
131
183
|
return {
|
|
132
184
|
target,
|
|
@@ -139,6 +191,16 @@ export async function compactFile({ target, path }) {
|
|
|
139
191
|
};
|
|
140
192
|
}
|
|
141
193
|
|
|
194
|
+
/**
|
|
195
|
+
* A refusal the agent would repeat verbatim if asked again. Remember the size
|
|
196
|
+
* so the file isn't re-attempted every scan — it becomes eligible again only
|
|
197
|
+
* once it has genuinely regrown.
|
|
198
|
+
*/
|
|
199
|
+
function refuse(target, size, why) {
|
|
200
|
+
noteCompaction(target, size);
|
|
201
|
+
return { target, ok: false, error: `refused: ${why}` };
|
|
202
|
+
}
|
|
203
|
+
|
|
142
204
|
/**
|
|
143
205
|
* Compact anything over the threshold. Called at the END of a scan, once —
|
|
144
206
|
* not per thread, so a scan pays for at most one of these and only on the day
|