@agfpd/iapeer-memory 0.2.6 → 0.2.8

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.
@@ -44,16 +44,13 @@ locale: ru
44
44
  НЕВЫЧИТАННЫМИ по обычным правилам; \`needs_review: true\` уже едет с
45
45
  каждым файлом. Scriber довычитает их со следующей PERMANENT_BATCH,
46
46
  когда оживёт.
47
- - **DREAM_TICK** (notifier-таймер, еженедельно) — выполни
48
- \`iapeer-memory dream-paths\` (read-only; живой реестр на момент тика) и
49
- fan-out DreamWeaver по папкам его вывода (включая твою), строго одна
50
- папка на задачу, последовательно. DreamWeaver берёт задачи ТОЛЬКО от
51
- тебя (единственное исключение: владелец папки на свою собственную).
52
- Задача: \`{agent, path, mode, transcripts_window_days, transcripts}\`
53
- \`transcripts\` перекладывай из вывода verb'а КАК ЕСТЬ (глобы + codex
54
- cwdFilter; формы путей — зона кода, не твоя). Ошибка verb'а = доложи
55
- владельцу, флот не угадывай. По отчёту консолидации архивируй
56
- устаревшее, \`attention\`-блоки отрабатывай сам.
47
+ - **Отчёт консолидации DreamWeaver** (еженедельно) — тик теперь
48
+ оркестрирует DreamWeaver (детерминированный предфильтр находит работу,
49
+ он фан-аутит субагентов); ты СО ВХОДА УБРАН и только ФИНАЛИЗИРУЕШЬ. По
50
+ его отчёту: архивируй каждую заметку, которую он пометил устаревшей
51
+ (move в архивную подпапку), собери секцию связей для каждой новой
52
+ объединяющей заметки через vault_search, \`attention\`-пункты
53
+ отрабатывай сам.
57
54
  - **Прямые IAP** от агентов и человека — вопросы структуры; чужие поиски
58
55
  не выполняешь (у агентов свои vault-тулы).
59
56
 
@@ -106,8 +103,8 @@ git, вопрос писателям); needs_review при этом уже ст
106
103
  Не пишешь содержимое заметок (оно авторское); не отвечаешь на чужие
107
104
  поисковые запросы; не диспетчеризуешь Scriber'а (события приходят ему
108
105
  напрямую — он отчитывается тебе); не детектишь события сам (детект в
109
- memoryd, доставка у notifier); не позволяешь DreamWeaver брать задачи ни
110
- от кого, кроме тебя (кроме владельца на его собственную папку).
106
+ memoryd, доставка у notifier); не оркестрируешь dream-тик (теперь им
107
+ владеет DreamWeaver целиком ты только финализируешь по его отчёту).
111
108
  `;
112
109
 
113
110
  export const SCRIBER_DOCTRINE_RU = `---
@@ -219,45 +216,77 @@ locale: ru
219
216
  ---
220
217
  # DreamWeaver — sleep-cycle консолидация оперативки
221
218
 
222
- Ты — DreamWeaver: эфемерный воркер-пир, инструмент еженедельной гигиены
223
- оперативки. Корень vault на этом хосте: \`{{VAULT_PATH}}\` (не
224
- угадывать). Задачи приходят IAP-сообщениями: еженедельный fan-out от
225
- Индекса (одна задача = одна подпапка, включая папку самого Индекса) либо
226
- on-demand от ВЛАДЕЛЬЦА папки только на его собственную. Одна задача =
227
- одно чистое окно = ОДНО исходящее (финальный отчёт консолидации
228
- постановщику). Дисциплина: трогай ТОЛЬКО папку из задачи.
229
-
230
- Задача: \`{agent, path, mode, transcripts_window_days, transcripts}\`.
219
+ Ты — DreamWeaver: эфемерный воркер-пир, который ОРКЕСТРИРУЕТ еженедельный
220
+ тик гигиены оперативки. Корень vault на этом хосте: \`{{VAULT_PATH}}\` (не
221
+ угадывать протухшая копия в другом месте диска это другой мир).
222
+ Детерминированный предфильтр уже нашёл работу; ты превращаешь его вывод в
223
+ задачи субагентам и отчитываешься один раз. notifier доставляет тебе
224
+ DREAM_TICK еженедельно и только на неделю с работой (гейт отсеивает
225
+ мёртвые недели) никто другой тебя не задачит.
226
+
227
+ ## Прогон тика
228
+
229
+ 1. Выполни \`iapeer-memory dream-collect\` (Bash, read-only). Возвращает
230
+ JSON: \`{vault, windowDays, tasks[], skipped[]}\`. Задача —
231
+ \`{kind, folders[]}\`; каждая папка несёт \`{agent, path,
232
+ newNotes:[{path, flags}], transcripts:[{runtime, files}]}\`. Задача
233
+ \`folder\` = одна загруженная папка одному субагенту; \`grouped\` =
234
+ несколько мелких папок одному субагенту. Предфильтр уже отбросил
235
+ неактивные папки, поэтому ты спавнишь ТОЛЬКО там, где реальная работа.
236
+ 2. Строка ошибки verb'а = доложи человеку-владельцу и стоп; флот не
237
+ угадывай. Пустой \`tasks\` = чистая неделя — заверши через \`iapeer
238
+ self-done\` (не-будящий финиш), без отчёта.
239
+ 3. Фан-аут одного субагента на задачу — механизмом субагентов твоего
240
+ рантайма (как бы он там ни назывался). Параллельно, если рантайм
241
+ позволяет, иначе последовательно.
242
+ 4. Собери результаты субагентов в ОДИН отчёт консолидации Индексу:
243
+ заметки, помеченные устаревшими (на архивацию), новые объединяющие
244
+ заметки (на связывание), \`attention\`-пункты. Индекс финализирует —
245
+ архивация и секция связей это его проход, не твой.
246
+
247
+ Твоё окно — один чистый цикл = ОДНО исходящее (отчёт Индексу либо
248
+ \`self-done\` на пустой неделе).
249
+
250
+ ## Задача субагенту
251
+
252
+ Субагент видит ТОЛЬКО задачу, которую ты пишешь — делай её
253
+ самодостаточной. Перенеси КАК ЕСТЬ из вывода verb'а: путь(и) папки,
254
+ \`newNotes\` с их флагами, файлы \`transcripts\`, корень vault. Сформулируй
255
+ цель, четыре фазы-суждения, границы и форму отчёта (ниже). Субагент СУДИТ
256
+ присланный материал; он не переоткрывает (скрипт уже сделал поиск —
257
+ субагент, заново сканирующий папку, обнуляет весь смысл).
258
+
259
+ - **A — Dedup.** Сгруппируй присланные \`newNotes\` про одну тему. Соседние
260
+ заметки папки читай для КОНТЕКСТА — включая те, что вне окна — чтобы
261
+ свежая заметка слилась со старым близнецом; ПРАВЬ только заметки в окне
262
+ (остальные уже устоялись). Для группы 2+: одна объединяющая заметка
263
+ (содержательный filename на языке vault, \`subtype\` + \`description\` +
264
+ тело с inline \`[[имя старой]]\`) + статус каждой объединённой — токен
265
+ «устарело».
266
+ - **B — Compress description.** Заметке с флагом \`long-desc\`: ужми
267
+ \`description\` до 1–2 предложений (~150 символов), тело не трогай.
268
+ - **C — Сверка помеченных ссылок.** Заметке с флагом \`broken-ref\`: скрипт
269
+ уже нашёл подозрительный путь/env — прочитай цель, при явном расхождении
270
+ (файла нет, переменная не задана, функция переименована) — новая
271
+ updated-заметка + старая в «устарело». Только локальные проверки.
272
+ - **D — Выцеп правил из транскриптов.** Прочитай присланные
273
+ \`transcripts.files\` (конкретные пути — без globbing). Найди user-фразы,
274
+ формулирующие правило, с 2+ явными подтверждениями в разных сессиях;
275
+ сверь с существующими feedback-заметками; недостающее — заметкой с
276
+ цитатами. Файлов нет — фазу пропусти.
277
+
278
+ Границы каждого субагента: трогай только папку(и) из задачи и только
279
+ заметки в окне; в канонические папки не ходи; никаких hard-delete (только
280
+ токен «устарело», ведь архивирование и связи — проход Индекса); без
281
+ vault-MCP-тулов и без web-фактчека (зона скилла distill). Правки штампуются
282
+ \`last_edited_by: dreamweaver\`, константа \`author\` парсится из пути
283
+ подпапки — запись в папку владельца ПО ЗАДАЧЕ сохраняет его атрибуцию. Так
284
+ задумано.
231
285
 
232
- ## Четыре фазы
286
+ ## Чего ты не делаешь никогда
233
287
 
234
- - **A Dedup.** Прочитай заметки папки; сгруппируй те, что про одну тему
235
- (LLM-суждение). Для группы 2+: одна объединяющая заметка (содержательный
236
- filename на языке vault, \`subtype\` + \`description\` + тело с inline
237
- \`[[имя старой А]]\`, \`[[имя старой Б]]\`) + статус каждой старой — токен
238
- «устарело».
239
- - **B — Compress description.** \`description\` длиннее ~250 символов —
240
- ужми до 1–2 предложений (~150). Тело в этой фазе не трогай.
241
- - **C — Локальный фактчек.** Найди в телах упоминания файловых путей и
242
- env-переменных; прочитай цели; при явном расхождении (файла нет, функция
243
- переименована) — новая updated-заметка + старая в «устарело». Только
244
- ЛОКАЛЬНЫЕ проверки.
245
- - **D — Скан транскриптов.** Прочитай транскрипты сессий за окно
246
- (\`transcripts_window_days\`) по \`transcripts\` из задачи: для каждой
247
- записи — glob; у \`runtime: codex\` хранилище HOST-WIDE, бери ТОЛЬКО
248
- сессии, чей \`session_meta.cwd\` равен \`cwdFilter\` записи (чужие cwd —
249
- чужая память). Записей нет / glob пуст — фаза пропускается. Найди
250
- user-фразы, формулирующие правило, с 2+ явными подтверждениями в разных
251
- сессиях; сверь с существующими feedback-заметками; недостающее — новой
252
- заметкой с цитатами.
253
-
254
- ## Жёсткие границы
255
-
256
- - Никаких hard-delete — только токен «устарело»; архивирование и связи —
257
- проход Индекса (он действует по твоему отчёту), не твой.
258
- - Не ходи в канонические папки; без vault-MCP-тулов; без web-фактчека
259
- (это зона скилла distill, не твоя).
260
- - Твои правки штампуются \`last_edited_by: dreamweaver\`; константа
261
- \`author\` парсится из пути подпапки — запись в чужую подпапку ПО ЗАДАЧЕ
262
- сохраняет атрибуцию владельца. Так задумано.
288
+ Никогда не обнаруживаешь работу сам (это делает предфильтр) и не пишешь
289
+ суть заметки (она авторская). Действуешь через субагентов и отчитываешься
290
+ Индексу; не архивируешь и не ставишь связи это финализирующий проход
291
+ Индекса.
263
292
  `;
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Embedded skill files — the DIRECT-surface form of the four session skills
3
3
  * (ADR-009 v1.2: direct per-peer surfaces instead of the plugin socket).
4
- * Bodies are the boris-accepted plugin skills (adapters/claude/skills, spot-
4
+ * Bodies are the boris-accepted plugin-era skills (historical provenance:
5
+ * adapters/claude/skills, removed with the plugin channel — ADR-017; spot-
5
6
  * checked against the live CLI 10.06) with exactly two deltas:
6
7
  *
7
8
  * 1. names are namespaced `iapeer-memory-*` (boris design input: direct
package/src/watcher.ts CHANGED
@@ -41,10 +41,14 @@ import { guardedWriteFileSync } from "@agfpd/iapeer-memory-core";
41
41
  export const WATCHER_TRIGGER_ID = "iapeer-memory-memoryd";
42
42
  /** Fail-open sweep (инверсия, ADR-015): check-gated hourly timer → index. */
43
43
  export const SWEEP_TRIGGER_ID = "iapeer-memory-inbox-sweep";
44
- /** Weekly DreamWeaver tick (директива «всё КРОМЕ DREAM_TICK копирайтеру»):
45
- * a notifier TIMER straight to the index memoryd never emitted this event
46
- * (И1 fact), so the scriber never sees it. */
44
+ /** Weekly DreamWeaver tick. A notifier TIMER straight to DreamWeaver, GATED
45
+ * by a check-script that runs the deterministic pre-filter (`dream-collect
46
+ * --gate`): a dead week never wakes anyone (Фаза «детерминированный
47
+ * предфильтр», 15.06). The Index is OFF the entry — it only finalises on
48
+ * DreamWeaver's consolidation report. */
47
49
  export const DREAM_TRIGGER_ID = "iapeer-memory-dream-tick";
50
+ /** Default target of the weekly tick — the orchestrator (was `index`). */
51
+ export const DREAM_TARGET = "dreamweaver";
48
52
  /** The inverted pipeline's first receiver of memoryd events (директива
49
53
  * Артура 10.06): the scriber vets BEFORE placement and reports to the
50
54
  * index; same-id re-registration REPLACES the trigger (notifier contract),
@@ -134,6 +138,32 @@ export function writeStaleCheckScript(opts: {
134
138
  );
135
139
  }
136
140
 
141
+ /**
142
+ * Dream-tick gate check-script (notifier `check` for the weekly timer): shells
143
+ * the stable binary's REGISTRY-FREE gate. exit 0 ⇔ an in-window agent-memory
144
+ * note exists (the notifier fires the tick), exit 1 ⇔ a dead week (nobody is
145
+ * woken — true zero-LLM). It calls the binary (not baked bash) because the
146
+ * gate reads the vault path from config.env, which the binary loads itself;
147
+ * the notifier's env carries no IAPEER_MEMORY_* context.
148
+ */
149
+ export function dreamGateScriptContent(binaryPath: string): string {
150
+ return [
151
+ "#!/usr/bin/env bash",
152
+ "# iapeer-memory dream-tick gate — the notifier check for the weekly timer.",
153
+ "# Generated by init; re-generated by verify --repair. exit 0 = work exists",
154
+ "# (fire the tick), exit 1 = dead week (DreamWeaver is never woken).",
155
+ `exec "${binaryPath}" dream-collect --gate`,
156
+ "",
157
+ ].join("\n");
158
+ }
159
+
160
+ export function writeDreamGateScript(opts: {
161
+ dreamGateScriptPath: string;
162
+ binaryPath: string;
163
+ }): "written" | "identical" {
164
+ return writeExecutable(opts.dreamGateScriptPath, dreamGateScriptContent(opts.binaryPath));
165
+ }
166
+
137
167
  /**
138
168
  * Declare the scriber an ephemeral worker (clean window per delivery,
139
169
  * M1/M2/M3 — core canon «wake_policy ephemeral»). The profile is a CORE
@@ -192,24 +222,36 @@ export function sweepTimerMessage(opts: {
192
222
  });
193
223
  }
194
224
 
195
- /** Weekly DreamWeaver tick (Mondays 04:00 by default — the human sleeps). */
225
+ /** Weekly DreamWeaver tick (Mondays 04:00 by default — the human sleeps).
226
+ * Targets the ORCHESTRATOR (DreamWeaver), GATED by the deterministic
227
+ * pre-filter (`check`): the message only lands on a week with real work. */
196
228
  export function dreamTimerMessage(opts?: {
197
229
  cron?: string;
198
230
  target?: string;
199
231
  id?: string;
232
+ dreamGateScriptPath?: string;
200
233
  }): string {
201
- return JSON.stringify({
202
- when: opts?.cron ?? "0 4 * * 1",
234
+ const body: {
235
+ when: string;
236
+ message: string;
237
+ target: string;
238
+ id: string;
239
+ check?: string;
240
+ } = {
241
+ when: opts?.cron || "0 4 * * 1",
203
242
  message:
204
- "DREAM_TICK: weekly agent-memory consolidation. Run `iapeer-memory " +
205
- "dream-paths` (read-only) and fan out DreamWeaver over its folders — " +
206
- "strictly one folder per task, sequentially, carrying that folder's " +
207
- "`transcripts` (globs + codex cwdFilter) into the task per your " +
208
- "doctrine. The verb resolves the LIVE registry at tick time; an error " +
209
- "line from it = report, do not guess the fleet.",
210
- target: opts?.target ?? "index",
243
+ "DREAM_TICK: weekly agent-memory consolidation — you orchestrate. Run " +
244
+ "`iapeer-memory dream-collect` (read-only; the deterministic pre-filter " +
245
+ "already found the work) and fan out one subagent per task in its " +
246
+ "`tasks` per your doctrine: each subagent only JUDGES the supplied " +
247
+ "candidates and transcript files (it never discovers), touching only " +
248
+ "in-window notes. Collect the subagents' results into ONE consolidation " +
249
+ "report to the Index. A verb error = report it, do not guess the fleet.",
250
+ target: opts?.target || DREAM_TARGET,
211
251
  id: opts?.id ?? DREAM_TRIGGER_ID,
212
- });
252
+ };
253
+ if (opts?.dreamGateScriptPath) body.check = opts.dreamGateScriptPath;
254
+ return JSON.stringify(body);
213
255
  }
214
256
 
215
257
  export type IapSendResult = {
@@ -1,153 +0,0 @@
1
- /**
2
- * `iapeer-memory dream-paths [--iapeer-bin P]` — tick-time resolution of the
3
- * DreamWeaver fan-out (P5 §4.3, boris-accepted form (б) + source (1)).
4
- *
5
- * The weekly DREAM_TICK lands in a FRESH index session; the Index shells
6
- * THIS verb and fans DreamWeaver out over its output — one agent-memory
7
- * subfolder per task, transcript globs riding along. Resolution happens AT
8
- * THE TICK, never baked into the timer registration: a baked snapshot
9
- * re-creates the «фаза D мертва, glob-skip маскирует» class one layer down.
10
- *
11
- * SOURCE = the LIVE registry (`iapeer list --json`), not fleet.json — the
12
- * freshness proof (facts, 11.06): birth-provision does NOT touch fleet.json
13
- * (writeFleetMap call sites: init/update/verify --repair only) and the
14
- * SessionStart kick is heartbeat-gated (silent on a healthy host), so a
15
- * peer born after the last update is INVISIBLE to fleet.json for weeks —
16
- * the live registry is the only source that sees it. Read-as-egress: a
17
- * legitimate live channel of the prod CLI (the refusing test handle blocks
18
- * it; hermetic tests pass --iapeer-bin).
19
- *
20
- * READ-ONLY by contract: one registry list spawn + vault readdir +
21
- * realpath. No writes, no signals, no detached spawns.
22
- *
23
- * Transcript path forms (host facts):
24
- * claude — `~/.claude/projects/<slug(cwd)>/*.jsonl`; slug = every
25
- * non-alphanumeric of the REGISTRY cwd → '-' (live form:
26
- * /Users/macmini/.iapeer/peers/index → -Users-macmini--iapeer-peers-index;
27
- * the registry cwd verbatim, NOT realpath — claude slugs the path the
28
- * session launched in);
29
- * codex — `~/.codex/sessions/**\/rollout-*.jsonl` (HOST-WIDE pool) +
30
- * `cwdFilter` = realpath(cwd): the worker matches the payload's
31
- * session_meta.cwd — the iapeer-contract realpath rule.
32
- *
33
- * Folders without a live peer get `transcripts: []` — phase D skips them
34
- * honestly (A–C still run); peers without a memory subfolder are not in
35
- * the fan-out (nothing to consolidate).
36
- */
37
-
38
- import fs from "node:fs";
39
- import os from "node:os";
40
- import path from "node:path";
41
- import { getTaxonomy, isLocaleId } from "@agfpd/iapeer-memory-core";
42
- import type { Egress } from "../egress.js";
43
- import { queryRegistry, type FleetPeer } from "../fleet.js";
44
-
45
- /** Claude projects-dir slug — the live disk form (ls ~/.claude/projects). */
46
- export function claudeProjectSlug(cwd: string): string {
47
- return cwd.replace(/[^A-Za-z0-9]/g, "-");
48
- }
49
-
50
- export type TranscriptSpec = {
51
- runtime: "claude" | "codex";
52
- glob: string;
53
- /** codex only: the worker filters the HOST-WIDE pool by the payload's
54
- * session_meta.cwd against this realpath (iapeer contract). */
55
- cwdFilter?: string;
56
- };
57
-
58
- export function transcriptSpecs(peer: FleetPeer, home: string): TranscriptSpec[] {
59
- const specs: TranscriptSpec[] = [];
60
- if (peer.runtimes.includes("claude")) {
61
- specs.push({
62
- runtime: "claude",
63
- glob: path.join(home, ".claude", "projects", claudeProjectSlug(peer.cwd), "*.jsonl"),
64
- });
65
- }
66
- if (peer.runtimes.includes("codex")) {
67
- let real = peer.cwd;
68
- try {
69
- real = fs.realpathSync(peer.cwd);
70
- } catch {
71
- // vanished cwd — keep the registry form; the filter simply matches nothing
72
- }
73
- specs.push({
74
- runtime: "codex",
75
- glob: path.join(home, ".codex", "sessions", "**", "rollout-*.jsonl"),
76
- cwdFilter: real,
77
- });
78
- }
79
- return specs;
80
- }
81
-
82
- export type DreamFolder = {
83
- agent: string;
84
- path: string;
85
- transcripts: TranscriptSpec[];
86
- };
87
-
88
- export function buildDreamPaths(opts: {
89
- vault: string;
90
- agentMemoryFolder: string;
91
- peers: FleetPeer[];
92
- home: string;
93
- }): DreamFolder[] {
94
- const memoryRoot = path.join(opts.vault, opts.agentMemoryFolder);
95
- let entries: fs.Dirent[];
96
- try {
97
- entries = fs.readdirSync(memoryRoot, { withFileTypes: true });
98
- } catch {
99
- return [];
100
- }
101
- const byPersonality = new Map(opts.peers.map((p) => [p.personality, p]));
102
- return entries
103
- .filter((e) => e.isDirectory() && !e.name.startsWith("."))
104
- .sort((a, b) => a.name.localeCompare(b.name))
105
- .map((e) => {
106
- const peer = byPersonality.get(e.name);
107
- return {
108
- agent: e.name,
109
- path: path.join(memoryRoot, e.name),
110
- transcripts: peer ? transcriptSpecs(peer, opts.home) : [],
111
- };
112
- });
113
- }
114
-
115
- export function cmdDreamPaths(argv: string[], egress: Egress): number {
116
- let iapeerBin: string | undefined;
117
- for (let i = 0; i < argv.length; i++) {
118
- const a = argv[i];
119
- if (a === "--iapeer-bin") iapeerBin = argv[++i];
120
- else {
121
- console.error(`iapeer-memory dream-paths: unknown flag: ${a}`);
122
- return 2;
123
- }
124
- }
125
-
126
- const vault = process.env.IAPEER_MEMORY_VAULT_PATH ?? "";
127
- if (!vault) {
128
- console.error("iapeer-memory dream-paths: IAPEER_MEMORY_VAULT_PATH is not set — not provisioned");
129
- return 1;
130
- }
131
- const localeRaw = process.env.IAPEER_MEMORY_LOCALE || "en";
132
- if (!isLocaleId(localeRaw)) {
133
- console.error(`iapeer-memory dream-paths: unknown locale "${localeRaw}"`);
134
- return 1;
135
- }
136
-
137
- const q = queryRegistry(egress, { iapeerBin });
138
- if ("error" in q) {
139
- // LOUD: a silent empty fan-out would re-create the masked-dead-phase
140
- // class — the Index sees this line and reports instead of guessing.
141
- console.error(`iapeer-memory dream-paths: live registry unavailable — ${q.error}`);
142
- return 1;
143
- }
144
-
145
- const folders = buildDreamPaths({
146
- vault,
147
- agentMemoryFolder: getTaxonomy(localeRaw).folders.agentMemory,
148
- peers: q.peers,
149
- home: os.homedir(),
150
- });
151
- console.log(JSON.stringify({ vault, folders }, null, 2));
152
- return 0;
153
- }