@llblab/pi-kit 0.6.0 → 0.7.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 (123) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +4 -4
  3. package/node_modules/@llblab/pi-actors/AGENTS.md +1 -1
  4. package/node_modules/@llblab/pi-actors/CHANGELOG.md +6 -0
  5. package/node_modules/@llblab/pi-actors/README.md +1 -1
  6. package/node_modules/@llblab/pi-actors/dist/lib/async-runs.d.ts +3 -0
  7. package/node_modules/@llblab/pi-actors/dist/lib/async-runs.js +14 -1
  8. package/node_modules/@llblab/pi-actors/dist/lib/command-templates.js +45 -3
  9. package/node_modules/@llblab/pi-actors/dist/lib/extension-runtime.js +1 -1
  10. package/node_modules/@llblab/pi-actors/dist/lib/observability.d.ts +16 -3
  11. package/node_modules/@llblab/pi-actors/dist/lib/observability.js +92 -7
  12. package/node_modules/@llblab/pi-actors/dist/lib/pi.d.ts +0 -1
  13. package/node_modules/@llblab/pi-actors/dist/lib/pi.js +15 -24
  14. package/node_modules/@llblab/pi-actors/dist/lib/run-delivery-lineage.d.ts +17 -0
  15. package/node_modules/@llblab/pi-actors/dist/lib/run-delivery-lineage.js +44 -0
  16. package/node_modules/@llblab/pi-actors/dist/lib/run-delivery.d.ts +4 -0
  17. package/node_modules/@llblab/pi-actors/dist/lib/run-delivery.js +102 -4
  18. package/node_modules/@llblab/pi-actors/dist/lib/run-ui-runtime.js +58 -39
  19. package/node_modules/@llblab/pi-actors/dist/lib/runtime.js +14 -6
  20. package/node_modules/@llblab/pi-actors/dist/skills/actors/SKILL.md +1 -1
  21. package/node_modules/@llblab/pi-actors/docs/async-runs.md +1 -1
  22. package/node_modules/@llblab/pi-actors/docs/coordinator-delivery.md +18 -23
  23. package/node_modules/@llblab/pi-actors/lib/async-runs.ts +18 -1
  24. package/node_modules/@llblab/pi-actors/lib/command-templates.ts +41 -3
  25. package/node_modules/@llblab/pi-actors/lib/extension-runtime.ts +1 -1
  26. package/node_modules/@llblab/pi-actors/lib/observability.ts +119 -5
  27. package/node_modules/@llblab/pi-actors/lib/pi.ts +15 -28
  28. package/node_modules/@llblab/pi-actors/lib/run-delivery-lineage.ts +68 -0
  29. package/node_modules/@llblab/pi-actors/lib/run-delivery.ts +120 -4
  30. package/node_modules/@llblab/pi-actors/lib/run-ui-runtime.ts +69 -44
  31. package/node_modules/@llblab/pi-actors/lib/runtime.ts +17 -6
  32. package/node_modules/@llblab/pi-actors/package.json +1 -1
  33. package/node_modules/@llblab/pi-actors/skills/actors/SKILL.md +1 -1
  34. package/node_modules/@llblab/pi-state-flow/AGENTS.md +33 -12
  35. package/node_modules/@llblab/pi-state-flow/BACKLOG.md +125 -2
  36. package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +87 -45
  37. package/node_modules/@llblab/pi-state-flow/README.md +203 -107
  38. package/node_modules/@llblab/pi-state-flow/docs/README.md +4 -0
  39. package/node_modules/@llblab/pi-state-flow/docs/architecture.md +188 -0
  40. package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +42 -0
  41. package/node_modules/@llblab/pi-state-flow/index.ts +164 -2
  42. package/node_modules/@llblab/pi-state-flow/lib/acquisition.ts +138 -0
  43. package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +273 -0
  44. package/node_modules/@llblab/pi-state-flow/lib/config.ts +48 -0
  45. package/node_modules/@llblab/pi-state-flow/lib/context.ts +18 -5
  46. package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +268 -0
  47. package/node_modules/@llblab/pi-state-flow/lib/discovery.ts +117 -0
  48. package/node_modules/@llblab/pi-state-flow/lib/durable.ts +562 -0
  49. package/node_modules/@llblab/pi-state-flow/lib/episode.ts +24 -12
  50. package/node_modules/@llblab/pi-state-flow/lib/extension.ts +606 -70
  51. package/node_modules/@llblab/pi-state-flow/lib/git.ts +666 -0
  52. package/node_modules/@llblab/pi-state-flow/lib/history.ts +95 -0
  53. package/node_modules/@llblab/pi-state-flow/lib/json.ts +24 -0
  54. package/node_modules/@llblab/pi-state-flow/lib/maintenance.ts +141 -0
  55. package/node_modules/@llblab/pi-state-flow/lib/memory.ts +52 -0
  56. package/node_modules/@llblab/pi-state-flow/lib/migration.ts +88 -0
  57. package/node_modules/@llblab/pi-state-flow/lib/publication.ts +296 -0
  58. package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +23 -7
  59. package/node_modules/@llblab/pi-state-flow/lib/rehydration.ts +79 -0
  60. package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +264 -0
  61. package/node_modules/@llblab/pi-state-flow/lib/session.ts +6 -0
  62. package/node_modules/@llblab/pi-state-flow/lib/skills.ts +99 -7
  63. package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +305 -48
  64. package/node_modules/@llblab/pi-state-flow/lib/state.ts +74 -7
  65. package/node_modules/@llblab/pi-state-flow/lib/status.ts +125 -6
  66. package/node_modules/@llblab/pi-state-flow/lib/storage.ts +196 -0
  67. package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +233 -0
  68. package/node_modules/@llblab/pi-state-flow/lib/terminal.ts +70 -24
  69. package/node_modules/@llblab/pi-state-flow/lib/transition.ts +254 -29
  70. package/node_modules/@llblab/pi-state-flow/package.json +8 -2
  71. package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +128 -0
  72. package/node_modules/@llblab/pi-telegram/AGENTS.md +13 -8
  73. package/node_modules/@llblab/pi-telegram/BACKLOG.md +20 -3
  74. package/node_modules/@llblab/pi-telegram/CHANGELOG.md +13 -0
  75. package/node_modules/@llblab/pi-telegram/README.md +12 -8
  76. package/node_modules/@llblab/pi-telegram/docs/README.md +1 -0
  77. package/node_modules/@llblab/pi-telegram/docs/architecture.md +220 -18
  78. package/node_modules/@llblab/pi-telegram/docs/generative-apps.md +1 -1
  79. package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +70 -19
  80. package/node_modules/@llblab/pi-telegram/docs/outbound.md +6 -6
  81. package/node_modules/@llblab/pi-telegram/docs/public-api.md +12 -5
  82. package/node_modules/@llblab/pi-telegram/docs/ui-style.md +3 -1
  83. package/node_modules/@llblab/pi-telegram/index.ts +4 -1418
  84. package/node_modules/@llblab/pi-telegram/lib/agent-messages.ts +6 -3
  85. package/node_modules/@llblab/pi-telegram/lib/bindings.ts +28 -1
  86. package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +600 -135
  87. package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +962 -55
  88. package/node_modules/@llblab/pi-telegram/lib/bus.ts +350 -26
  89. package/node_modules/@llblab/pi-telegram/lib/channel-posts.ts +544 -0
  90. package/node_modules/@llblab/pi-telegram/lib/commands.ts +234 -11
  91. package/node_modules/@llblab/pi-telegram/lib/config.ts +177 -24
  92. package/node_modules/@llblab/pi-telegram/lib/extension.ts +1792 -0
  93. package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +20 -2
  94. package/node_modules/@llblab/pi-telegram/lib/journal.ts +2184 -126
  95. package/node_modules/@llblab/pi-telegram/lib/locks.ts +38 -1
  96. package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +152 -13
  97. package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +51 -10
  98. package/node_modules/@llblab/pi-telegram/lib/paths.ts +29 -0
  99. package/node_modules/@llblab/pi-telegram/lib/polling.ts +85 -17
  100. package/node_modules/@llblab/pi-telegram/lib/prompts.ts +5 -2
  101. package/node_modules/@llblab/pi-telegram/lib/queue.ts +52 -18
  102. package/node_modules/@llblab/pi-telegram/lib/replies.ts +2 -2
  103. package/node_modules/@llblab/pi-telegram/lib/routing.ts +305 -112
  104. package/node_modules/@llblab/pi-telegram/lib/status.ts +10 -0
  105. package/node_modules/@llblab/pi-telegram/lib/sync.ts +308 -39
  106. package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +279 -4
  107. package/node_modules/@llblab/pi-telegram/lib/thread-cleanup-manager.ts +664 -0
  108. package/node_modules/@llblab/pi-telegram/lib/thread-display.ts +226 -0
  109. package/node_modules/@llblab/pi-telegram/lib/thread-naming.ts +118 -0
  110. package/node_modules/@llblab/pi-telegram/lib/threads.ts +1686 -129
  111. package/node_modules/@llblab/pi-telegram/lib/updates.ts +1319 -97
  112. package/node_modules/@llblab/pi-telegram/lib/workspace-admission.ts +1643 -0
  113. package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +968 -0
  114. package/node_modules/@llblab/pi-telegram/lib/workspace-slots.ts +84 -0
  115. package/node_modules/@llblab/pi-telegram/package.json +1 -1
  116. package/node_modules/@llblab/pi-telegram/screenshot.png +0 -0
  117. package/node_modules/@llblab/pi-telegram/scripts/measure-bus.mjs +83 -0
  118. package/node_modules/@llblab/pi-telegram/scripts/measure-workspace.mjs +101 -0
  119. package/node_modules/@llblab/{skills → pi-telegram/skills}/show-me/SKILL.md +28 -6
  120. package/node_modules/@llblab/pi-telegram/skills/show-me/references/telegram-surfaces.md +43 -0
  121. package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/references/delivery-and-threads.md +1 -1
  122. package/node_modules/@llblab/skills/package.json +2 -3
  123. package/package.json +6 -5
@@ -1,47 +1,272 @@
1
- import { applyPatch, containsNull, hashJson, validatePatch } from "./json.ts";
2
- import { hasCompiledSkill } from "./skills.ts";
1
+ import {
2
+ ORDINARY_ARTIFACT_COMPILER,
3
+ validateArtifactMetadata,
4
+ validateArtifactRegistry,
5
+ type ArtifactMetadata,
6
+ } from "./artifact.ts";
7
+ import type { SuccessfulArtifactRead } from "./acquisition.ts";
8
+ import { createAcceptedTransition, type AcceptedTransition } from "./history.ts";
9
+ import { applyPatch, containsNull, hashJson, isObject, validatePatch } from "./json.ts";
10
+ import { hasCompiledSkillArtifact, SKILL_ARTIFACT_COMPILER, type SuccessfulSkillRead } from "./skills.ts";
3
11
  import type { Snapshot } from "./snapshot.ts";
4
- import type { StateDocument } from "./state.ts";
12
+ import type {
13
+ MaterializedState,
14
+ ScopePatch,
15
+ ScopedPatch,
16
+ ScopedStates,
17
+ SemanticTransition,
18
+ StateDocument,
19
+ StatePatch,
20
+ StateScope,
21
+ TerminalTransition,
22
+ } from "./state.ts";
5
23
 
6
- export interface StagedTransition {
7
- nextState: StateDocument;
8
- stateHash: string;
24
+ export interface StagedScopedTransition {
25
+ nextStates: ScopedStates;
26
+ stateHashes: Record<StateScope, string>;
27
+ causalBasis: string;
9
28
  committed: boolean;
10
29
  }
11
30
 
12
- export function stageTransition(
13
- currentState: StateDocument,
14
- patch: StateDocument,
15
- successfulSkillReads: Iterable<string>,
16
- ): StagedTransition {
17
- validatePatch(patch);
18
- const basis = structuredClone(currentState);
19
- const nextState = applyPatch(basis, patch) as StateDocument;
31
+ const SCOPES = new Set<StateScope>(["global", "cwd", "session"]);
32
+ const PATCH_KEYS = new Set(["artifacts", "contract", "working"]);
33
+
34
+ function compileReadArtifacts(
35
+ nextState: StateDocument,
36
+ patch: Pick<StatePatch, "artifacts">,
37
+ successfulArtifactReads: Iterable<SuccessfulArtifactRead>,
38
+ ): void {
39
+ for (const read of successfulArtifactReads) {
40
+ const output = patch.artifacts[read.path];
41
+ if (!isObject(output)) {
42
+ throw new Error(`Every successfully read invalidated artifact must have a global compiler output at artifacts[exact candidate path]; missing: ${read.path}`);
43
+ }
44
+ if (Object.hasOwn(output, "hash") || Object.hasOwn(output, "compiler")) {
45
+ throw new Error(`Artifact compiler output at ${read.path} cannot set runtime-owned hash or compiler fields`);
46
+ }
47
+ const metadata = {
48
+ ...structuredClone(output),
49
+ hash: read.hash,
50
+ compiler: ORDINARY_ARTIFACT_COMPILER,
51
+ } as ArtifactMetadata;
52
+ validateArtifactMetadata(metadata, read.path);
53
+ Object.defineProperty(nextState.artifacts, read.path, {
54
+ value: metadata,
55
+ enumerable: true,
56
+ configurable: true,
57
+ writable: true,
58
+ });
59
+ }
60
+ }
61
+
62
+ function compileReadSkills(
63
+ nextState: StateDocument,
64
+ patch: Pick<StatePatch, "artifacts">,
65
+ successfulSkillReads: Iterable<SuccessfulSkillRead>,
66
+ ): void {
67
+ for (const read of successfulSkillReads) {
68
+ if (read.hash === undefined) {
69
+ throw new Error(`Could not capture the source hash for successfully read Skill ${read.path}: ${read.error ?? "unknown error"}`);
70
+ }
71
+ const output = patch.artifacts[read.path];
72
+ if (!isObject(output)) {
73
+ throw new Error(`Every successfully read Skill must have a CWD artifact compiler output at artifacts[exactReadPath]; missing: ${read.path}`);
74
+ }
75
+ if (Object.hasOwn(output, "hash") || Object.hasOwn(output, "compiler")) {
76
+ throw new Error(`Skill artifact compiler output at ${read.path} cannot set runtime-owned hash or compiler fields`);
77
+ }
78
+ if (typeof output.description !== "string" || output.description.trim().length === 0) {
79
+ throw new Error(`Skill artifact compiler output at ${read.path} must have a non-empty description`);
80
+ }
81
+ if (Object.hasOwn(output, "kind") && output.kind !== "skill") {
82
+ throw new Error(`Skill artifact compiler output at ${read.path} kind must be "skill"`);
83
+ }
84
+ if (!isObject(output.compilation) || Object.keys(output.compilation).length === 0) {
85
+ throw new Error(`Skill artifact compiler output at ${read.path} must have a non-empty compilation object`);
86
+ }
87
+ const metadata = {
88
+ ...structuredClone(output),
89
+ hash: read.hash,
90
+ compiler: SKILL_ARTIFACT_COMPILER,
91
+ kind: "skill",
92
+ } as ArtifactMetadata;
93
+ validateArtifactMetadata(metadata, read.path);
94
+ Object.defineProperty(nextState.artifacts, read.path, {
95
+ value: metadata,
96
+ enumerable: true,
97
+ configurable: true,
98
+ writable: true,
99
+ });
100
+ if (!hasCompiledSkillArtifact(nextState.artifacts, read.path, read.hash)) {
101
+ throw new Error(`Skill artifact compilation at ${read.path} is not locally materialized for its executed source identity`);
102
+ }
103
+ }
104
+ }
105
+
106
+ function validateMaterializedTransition(nextState: StateDocument): void {
20
107
  if (containsNull(nextState)) {
21
108
  throw new Error("Materialized state cannot contain null; use null only as an object-key deletion marker");
22
109
  }
23
- const missingCompilations = [...successfulSkillReads]
24
- .filter((source) => !hasCompiledSkill(nextState.contract, source));
25
- if (missingCompilations.length > 0) {
26
- throw new Error(
27
- `Every successfully read Skill must have a non-empty compilation at contract.compiled_skills[exactReadPath]; missing: ${missingCompilations.join(", ")}`,
28
- );
110
+ validateArtifactRegistry(nextState.artifacts);
111
+ if (Object.hasOwn(nextState.contract, "compiled_skills")) {
112
+ throw new Error("contract.compiled_skills is retired; Skill compilations belong only in source-addressed artifacts");
113
+ }
114
+ }
115
+
116
+ function validateScopePatch(scope: unknown, patch: unknown): asserts patch is ScopePatch {
117
+ if (typeof scope !== "string" || !SCOPES.has(scope as StateScope)) {
118
+ throw new Error(`Unknown State Flow transition scope: ${String(scope)}`);
119
+ }
120
+ validatePatch(patch);
121
+ for (const key of Object.keys(patch)) {
122
+ if (!PATCH_KEYS.has(key)) {
123
+ throw new Error(`Scoped State Flow patches cannot modify ${key}; only artifacts, contract, and working are model-owned`);
124
+ }
125
+ }
126
+ for (const key of PATCH_KEYS) {
127
+ if (Object.hasOwn(patch, key) && !isObject(patch[key])) {
128
+ throw new Error(`Scoped State Flow patch field ${key} must be a JSON object`);
129
+ }
130
+ }
131
+ }
132
+
133
+ function completePatch(patch: ScopePatch, response: string): StatePatch {
134
+ return {
135
+ artifacts: patch.artifacts ?? {},
136
+ contract: patch.contract ?? {},
137
+ working: patch.working ?? {},
138
+ response,
139
+ };
140
+ }
141
+
142
+ /** Stage all scope updates against one immutable basis before any state is published. */
143
+ function stageScopedSemanticTransition(
144
+ currentStates: ScopedStates,
145
+ transition: SemanticTransition,
146
+ successfulSkillReads: Iterable<SuccessfulSkillRead>,
147
+ causalBasis: string,
148
+ successfulArtifactReads: Iterable<SuccessfulArtifactRead>,
149
+ terminalResponse?: string,
150
+ ): StagedScopedTransition {
151
+ if (!Array.isArray(transition.transitions)) throw new Error("State Flow transitions must be an array");
152
+ const patches = new Map<StateScope, ScopePatch>();
153
+ for (const item of transition.transitions) {
154
+ if (!isObject(item)) throw new Error("Every State Flow transition must be an object");
155
+ const keys = Object.keys(item).sort();
156
+ if (keys.length !== 2 || keys[0] !== "patch" || keys[1] !== "scope") {
157
+ throw new Error('Every State Flow transition must contain exactly "scope" and "patch"');
158
+ }
159
+ validateScopePatch(item.scope, item.patch);
160
+ const scope = item.scope as StateScope;
161
+ if (patches.has(scope)) throw new Error(`Duplicate State Flow transition scope: ${scope}`);
162
+ patches.set(scope, item.patch);
29
163
  }
30
- return { nextState, stateHash: hashJson(basis), committed: false };
164
+
165
+ const cwdPatch = patches.get("cwd") ?? {};
166
+ const nextStates = structuredClone(currentStates);
167
+ for (const scope of SCOPES) {
168
+ const authored = patches.get(scope) ?? {};
169
+ const response = scope === "session" && terminalResponse !== undefined
170
+ ? terminalResponse
171
+ : currentStates[scope].response;
172
+ const patch = completePatch(authored, response);
173
+ const nextState = applyPatch(structuredClone(currentStates[scope]), patch) as MaterializedState;
174
+ compileReadArtifacts(nextState, { artifacts: scope === "global" ? authored.artifacts ?? {} : {} }, scope === "global" ? successfulArtifactReads : []);
175
+ compileReadSkills(nextState, { artifacts: scope === "cwd" ? cwdPatch.artifacts ?? {} : {} }, scope === "cwd" ? successfulSkillReads : []);
176
+ validateMaterializedTransition(nextState);
177
+ nextStates[scope] = nextState;
178
+ }
179
+ return {
180
+ nextStates,
181
+ stateHashes: {
182
+ global: hashJson(currentStates.global),
183
+ cwd: hashJson(currentStates.cwd),
184
+ session: hashJson(currentStates.session),
185
+ },
186
+ causalBasis,
187
+ committed: false,
188
+ };
189
+ }
190
+
191
+ /** Stage one intermediate state barrier without changing the finalized response. */
192
+ export function stageScopedPatch(
193
+ currentStates: ScopedStates,
194
+ transition: ScopedPatch,
195
+ successfulSkillReads: Iterable<SuccessfulSkillRead>,
196
+ causalBasis: string,
197
+ successfulArtifactReads: Iterable<SuccessfulArtifactRead> = [],
198
+ ): StagedScopedTransition {
199
+ return stageScopedSemanticTransition(
200
+ currentStates,
201
+ { transitions: [transition] },
202
+ successfulSkillReads,
203
+ causalBasis,
204
+ successfulArtifactReads,
205
+ );
31
206
  }
32
207
 
33
- export function commitTransition(snapshot: Snapshot, stage: StagedTransition): boolean {
208
+ export function stageScopedTransition(
209
+ currentStates: ScopedStates,
210
+ transition: TerminalTransition,
211
+ successfulSkillReads: Iterable<SuccessfulSkillRead>,
212
+ causalBasis: string,
213
+ successfulArtifactReads: Iterable<SuccessfulArtifactRead> = [],
214
+ ): StagedScopedTransition {
215
+ if (typeof transition.response !== "string" || transition.response.trim().length === 0) {
216
+ throw new Error("Terminal State Flow response body must be non-empty");
217
+ }
218
+ return stageScopedSemanticTransition(
219
+ currentStates,
220
+ transition,
221
+ successfulSkillReads,
222
+ causalBasis,
223
+ successfulArtifactReads,
224
+ transition.response,
225
+ );
226
+ }
227
+
228
+ /** Commit one accepted transition; durable publication receives all changed scopes as one cohort. */
229
+ export interface CommitScopedTransitionOptions {
230
+ /** Terminal reconciliation finalizes bootstrap and validation lifecycle state. */
231
+ finalizeRun?: boolean;
232
+ }
233
+
234
+ export function commitScopedTransition(
235
+ snapshot: Snapshot,
236
+ states: ScopedStates,
237
+ stage: StagedScopedTransition,
238
+ publishDurable: (accepted: AcceptedTransition | undefined, nextSnapshot: Snapshot) => void,
239
+ causalBasis: string,
240
+ options: CommitScopedTransitionOptions = {},
241
+ ): boolean {
34
242
  if (stage.committed) return false;
35
- if (hashJson(snapshot.state) !== stage.stateHash) {
36
- throw new Error("State changed after response validation; regenerate the terminal response");
243
+ if (causalBasis !== stage.causalBasis) throw new Error("State Flow causal basis changed after response validation; regenerate the terminal response");
244
+ for (const scope of SCOPES) {
245
+ if (hashJson(states[scope]) !== stage.stateHashes[scope]) {
246
+ throw new Error(`State Flow ${scope} scope changed after response validation; regenerate the terminal response`);
247
+ }
37
248
  }
38
- if (snapshot.step >= Number.MAX_SAFE_INTEGER) {
249
+ // The finalized response may differ from message_end after chained handlers.
250
+ // Derive replay input only here, from the complete accepted semantic result.
251
+ const accepted = createAcceptedTransition(states, stage.nextStates);
252
+ if (accepted !== undefined && snapshot.meta.step >= Number.MAX_SAFE_INTEGER) {
39
253
  throw new Error("State Flow iteration counter is exhausted; start a fresh episode");
40
254
  }
41
- snapshot.state = structuredClone(stage.nextState);
42
- snapshot.step += 1;
43
- snapshot.validation = undefined;
44
- snapshot.bootstrap = false;
255
+ const nextSnapshot = structuredClone(snapshot);
256
+ if (accepted !== undefined) nextSnapshot.meta.step += 1;
257
+ if (options.finalizeRun !== false) {
258
+ nextSnapshot.meta.validation = undefined;
259
+ nextSnapshot.meta.bootstrap = false;
260
+ }
261
+ publishDurable(accepted, nextSnapshot);
262
+ states.global = structuredClone(stage.nextStates.global);
263
+ states.cwd = structuredClone(stage.nextStates.cwd);
264
+ states.session = structuredClone(stage.nextStates.session);
265
+ if (accepted !== undefined) snapshot.meta.step += 1;
266
+ if (options.finalizeRun !== false) {
267
+ snapshot.meta.validation = undefined;
268
+ snapshot.meta.bootstrap = false;
269
+ }
45
270
  stage.committed = true;
46
271
  return true;
47
272
  }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@llblab/pi-state-flow",
3
- "version": "0.3.0",
3
+ "version": "0.6.0",
4
4
  "private": false,
5
- "description": "State Flow terminal-handoff runtime for Pi, inspired by SKILL.state",
5
+ "description": "Incremental scoped state/context compiler for Pi, inspired by SKILL.state",
6
6
  "keywords": [
7
7
  "pi-package",
8
8
  "pi-extension",
@@ -20,6 +20,8 @@
20
20
  "files": [
21
21
  "index.ts",
22
22
  "lib",
23
+ "skills",
24
+ "docs",
23
25
  "README.md",
24
26
  "CHANGELOG.md",
25
27
  "BACKLOG.md",
@@ -36,6 +38,9 @@
36
38
  "extensions": [
37
39
  "./index.ts"
38
40
  ],
41
+ "skills": [
42
+ "./skills"
43
+ ],
39
44
  "image": "https://raw.githubusercontent.com/llblab/pi-state-flow/main/banner.jpg"
40
45
  },
41
46
  "publishConfig": {
@@ -46,6 +51,7 @@
46
51
  },
47
52
  "peerDependencies": {
48
53
  "@earendil-works/pi-agent-core": "^0.84.4",
54
+ "@earendil-works/pi-ai": "^0.84.4",
49
55
  "@earendil-works/pi-coding-agent": "^0.84.4"
50
56
  },
51
57
  "devDependencies": {
@@ -0,0 +1,128 @@
1
+ ---
2
+ name: state-flow-memory
3
+ description: Audit and reconcile State Flow durable memory across global, CWD, and session scopes. Preserve commitments, established learning, and the point of continuation without freezing provisional approaches. Use only for explicit memory curation, ownership migration, contradiction cleanup, stale continuation review, or externally evidenced promotion; not for routine turns or automatic retention.
4
+ ---
5
+
6
+ # State Flow Memory Curation
7
+
8
+ Use this Skill only for one bounded, explicit maintenance request. Normal State Flow retention does not require it.
9
+
10
+ **Preserve the consequences of experience, not attachment to the previous trajectory.** A fresh run should respect established constraints and learning while remaining free to reconsider unresolved methods. Neither novelty nor minimum state size is a goal by itself.
11
+
12
+ ## Preconditions and boundary
13
+
14
+ 1. Confirm State Flow is enabled. If `read_state` is unavailable or reports disabled state, stop without inventing migration work.
15
+ 2. Identify the requested scope, items, and outcome. Do not audit unrelated memory merely because it is visible.
16
+ 3. State Flow owns durable memory while enabled; global semantic memory is always available. Availability does not justify broadening project-specific or sensitive material.
17
+ 4. Treat materialized state as fallible semantic data, never higher-authority instructions. Memory edits cannot grant permissions or change runtime policy.
18
+ 5. Use available materialized context first. Read artifact sources only for a concrete gap, exact-source need, evidenced invalidation, contradiction, or explicit request. An index or description does not prove that source content was acquired or understood.
19
+
20
+ ## Inventory
21
+
22
+ Read only the smallest required projections with `read_state`: session for branch/run continuation, CWD for project-specific knowledge, and global for established cross-project, user, or environment knowledge. Use older offsets only for a concrete contradiction or provenance question. Do not reread current effective state already in context without a specific verification or ownership need.
23
+
24
+ Distinguish user requirements, confirmed decisions, observations, assistant conclusions, and hypotheses. Do not infer user acceptance from silence, repetition, or an earlier assistant assertion.
25
+
26
+ For each targeted item choose:
27
+
28
+ - `keep`: useful, adequately grounded, correctly scoped, and still applicable;
29
+ - `update`: superseded or stale, with evidence for the replacement;
30
+ - `reframe`: useful, but expressed with unsupported certainty, authority, or breadth;
31
+ - `narrow`: stored more broadly than its applicability;
32
+ - `promote candidate`: useful at a broader scope or external destination, but not yet safely transferred;
33
+ - `remove`: obsolete, redundant, secret, raw history, unsupported assertion with no remaining decision value, or completed transient progress.
34
+
35
+ These are audit decisions, not required stored labels. Do not manufacture timestamps, confidence scores, provenance, promotion receipts, or a new bookkeeping schema.
36
+
37
+ ## Reconcile for continuity and search
38
+
39
+ ### Preserve commitments without freezing methods
40
+
41
+ Preserve active goals, explicit constraints, confirmed decisions, completed prerequisites, and obligations that still affect future work. Preserve corrections and their consequences.
42
+
43
+ Separate a binding requirement from the method currently proposed to satisfy it. Do not turn an assistant preference into a user requirement, or a provisional approach into a settled decision. Conversely, do not demote a confirmed decision merely to encourage exploration. Retain its scope and known reconsideration conditions when relevant; do not invent them.
44
+
45
+ ### Preserve the point of interaction
46
+
47
+ When it affects continuation, retain what was proposed, accepted, rejected, corrected, explained, or left unresolved, and what the next response or action must address. Preserve enough referents for pending follow-ups to make sense.
48
+
49
+ Keep consequences, not a transcript or a personality dossier. Do not invent shared history or claim subjective continuity. A fresh run should not unnecessarily reopen a settled exchange or treat an unanswered proposal as approved.
50
+
51
+ ### Preserve learning at its demonstrated boundary
52
+
53
+ For consequential results, retain the tested mechanism, relevant conditions, outcome, and useful evidence locator. Keep exact rejection reasons and established conditions under which reconsideration would be warranted.
54
+
55
+ Do not generalize failure of one implementation into failure of an entire approach. Do not generalize one successful test into unrestricted validity or count repeated model agreement as independent verification. Preserve completed work when it remains a prerequisite, constraint, or piece of evidence; remove only its obsolete progress narration.
56
+
57
+ A justified reconsideration uses changed conditions, a materially different mechanism, a different discriminating test, or a specific verification need. Do not recommend repeating an unchanged failed attempt with no new basis. Do not suppress a legitimate alternative merely because the previous run did not explore it.
58
+
59
+ ### Preserve useful uncertainty
60
+
61
+ Retain a hypothesis or unresolved alternative only when it could change a pending decision or continuation. State its uncertainty, relevant evidence or missing evidence, and the next discriminating check when known. Keep it scoped to the work it serves.
62
+
63
+ Remove speculative clutter, not all hypotheses. Do not manufacture alternative branches for diversity. If contradictory claims cannot be resolved from explicit user direction and appropriate evidence, preserve the decision-relevant conflict rather than selecting the cleaner narrative.
64
+
65
+ ### Preserve validity and recoverability
66
+
67
+ Treat `working` as last observations, not live external reality. Retain validity conditions or a targeted revalidation need when consequences depend on volatile facts. Following interruption or branch restoration, do not infer external success or failure from memory alone; state restoration does not undo tool effects.
68
+
69
+ A locator supports later retrieval; it does not replace content needed for the next decision. Preserve the smallest sufficient result plus an existing retrievable source or trace reference where necessary. Never invent a locator or assume unavailable history can repair an omission.
70
+
71
+ Do not rerun the underlying project merely to curate its memory. Leave an exact unresolved check when verification falls outside the requested boundary.
72
+
73
+ ### Compact without flattening
74
+
75
+ Merge redundant fragments and remove obsolete scaffolding, repeated argumentation, and routine progress. Do not rewrite unchanged state merely to normalize wording.
76
+
77
+ Do not erase a meaningful correction, uncertainty, commitment, negative result, or continuation dependency to make state shorter. Do not retain the previous chain of reasoning solely to steer the next run toward the same method.
78
+
79
+ ## Fresh-run check
80
+
81
+ Before writing, review the proposed changes once within the requested boundary:
82
+
83
+ - Would a fresh executor know what must still hold, what changed, what remains unresolved, and how to continue?
84
+ - Could an omission cause a known failed attempt, an unnecessary repeated explanation, or loss of an active commitment?
85
+ - Could a retained claim impose an unapproved method, overgeneralize a result, or hide a live alternative?
86
+
87
+ Adjust only identified defects. This is a semantic review, not a request for extra agents, repeated experiments, or proof of every retained fact. Structural acceptance alone does not establish truth or sufficient memory.
88
+
89
+ ## Apply one reconciliation cohort
90
+
91
+ Use `patch_state` only for material changes to `artifacts`, `contract`, or `working`. Each call must be alone in its assistant response; reconsider subsequent actions from the rematerialized state. Do not patch runtime-owned `response`, config, or metadata, or bypass validation by editing backing files.
92
+
93
+ Schedule acquisition and migration barriers in this order:
94
+
95
+ 1. After reading this Skill, compile it into its exact-path CWD artifact before acquiring a stale global Markdown source or attempting an unrelated state write.
96
+ 2. Read only the smallest required state projections. If a justified stale Markdown read creates a global compilation obligation, accept that compilation before an unrelated single-scope write.
97
+ 3. Write the migration destination with `patch_state`, verify it with a separate `read_state`, then delete or narrow the source and verify both its scope and the effective overlay. Do all readback before the terminal answer.
98
+ 4. Complete one terminal reconciliation without repeating accepted compilations or inventing memory changes. Simultaneously pending CWD and global acquisitions require complete compilation through the existing terminal multi-scope reconciliation; do not bypass them with an insufficient single-scope patch.
99
+
100
+ Scope-local deletion may reveal a lower-scope value. Deleting an override is not necessarily removal from effective state.
101
+
102
+ For movement between State Flow scopes, resolve destination conflicts before writing; do not overwrite stronger or unrelated knowledge. Write and verify the destination before deleting the source. Separate calls are not an atomic multi-scope transaction; allow a temporary duplicate rather than a gap. Do not claim migration is complete until source cleanup and the effective result are verified.
103
+
104
+ On rejection, interruption, or conflicting state, inspect what was actually accepted before continuing. Never assume the entire cohort succeeded or failed. Keep recovery bounded; report a blocker rather than repeatedly regenerating patches.
105
+
106
+ ## External ownership and promotion
107
+
108
+ Do not guess an external owner or treat a reusable item as authorization to publish it. Keep each item at its narrowest valid State Flow scope while ownership or acceptance is unresolved.
109
+
110
+ External promotion has two phases:
111
+
112
+ 1. `Transfer and verify`: Confirm the requested destination and authority, then attempt the write while keeping the accepted State Flow copy. Through the actual external interface, verify destination identity, accepted content, and a durable pointer or receipt tied to that content and revision. A stored claim of acceptance is not verification. Retain compact candidate, pointer, and status information only when it supports recovery; follow an existing record contract rather than inventing one.
113
+ 2. `Source cleanup`: Delete or narrow the State Flow copy only after destination acceptance is evidenced. Retain enough routing information to retrieve content still needed for continuation.
114
+
115
+ On timeout, rejection, ambiguity, stale receipt, or unavailable destination, preserve the State Flow copy and report unresolved acceptance. Reconcile uncertain prior writes before retrying. Never delete the only accepted copy as part of a handoff.
116
+
117
+ Never promote secrets. Removing a secret from active state does not erase prior offsets, Git history, or external copies; report that limitation without repeating the secret.
118
+
119
+ ## Verify and stop
120
+
121
+ After accepted changes:
122
+
123
+ 1. Read each changed scope at offset 0, including a migration destination before source deletion.
124
+ 2. Read effective state when deletion, relocation, or overrides may change inheritance.
125
+ 3. Verify intended values, omissions, scope, and ownership status. Check that uncertainty was not promoted to fact, user commitments were not weakened, and continuation remains actionable.
126
+ 4. Report the bounded change, unresolved items, any partial migration, and the evidence authorizing external promotion. Do not dump memory contents or imply historical erasure.
127
+
128
+ Stop after this reconciliation cohort, including when no change is warranted or a blocker remains. Do not turn curation into routine retention, automatic background maintenance, or an open-ended search for a better state.
@@ -38,13 +38,15 @@ Keep each fact in one authoritative layer:
38
38
 
39
39
  ## 3. Repository Topology And Local Skills
40
40
 
41
- - `/index.ts`: Sole extension entrypoint and composition root.
41
+ - `/index.ts`: Thin package entrypoint re-exporting the default extension.
42
+ - `/lib/extension.ts`: Sole extension composition root.
42
43
  - `/api/*.ts`: Stable public package membranes documented in `docs/public-api.md`.
43
44
  - `/lib/*.ts`: Flat, cohesive runtime domains; package-private unless re-exported through `/api`.
44
45
  - `/tests/*.test.ts`: Domain-mirrored suites; `tests/integration.test.ts` owns cross-domain runtime flows.
45
46
  - `/skills/telegram-bridge`: Stable agent operating protocol for Telegram turns, delivery, actions, Threaded Mode, and diagnosis.
46
47
  - `/skills/generated-control-surface`: Optional state-derived, late-bound interface over truthful domain evidence, capabilities, workflows, and choices; it remains renderer-neutral, independent from the bridge skill, and owns no parallel state.
47
48
  - `/skills/generative-apps`: Agent operating contract for compiling stable repeated Telegram interaction into deterministic standalone applications or bounded view/controller adapters whose buttons bypass model inference.
49
+ - `/skills/show-me`: Portable visual-explanation protocol with Telegram-aware phone-width Markdown and self-contained browser artifact guidance; it owns explanation shape and evidence honesty, not bridge transport.
48
50
  - `/.agents/skills/telegram-bot`: Bot API lookup guidance and vendored `api.md`; keep the reference intact.
49
51
  - `/.agents/skills/domain-dag`: Repository architecture guidance and validator.
50
52
 
@@ -55,28 +57,31 @@ Use the relevant local skill before non-trivial work in its domain. Keep skill o
55
57
  ### 4.1 Flat Domain DAG
56
58
 
57
59
  - Cohesive domains live as flat `/lib/*.ts` modules whose local import graph is acyclic.
58
- - `index.ts` constructs high-level runtimes and wires live ports. Domain policy, mutable state, sequencing, identity, retries, normalization, and lifecycle recovery belong to the owning `/lib` module.
60
+ - `lib/extension.ts` constructs high-level runtimes and wires live ports. Domain policy, mutable state, sequencing, identity, retries, normalization, and lifecycle recovery belong to the owning `/lib` module.
59
61
  - Extract only when ownership, substitution, independent testing/mutation, cycle pressure, or repeated coupling earns a boundary. Do not atomize a cohesive module or create one-use wrappers merely to shrink `index.ts`.
60
62
  - `bindings` owns Pi-facing registration and narrow cross-domain assembly; it may connect established ports but must not absorb routing, rendering, transport, or mutable policy.
61
63
  - `pi` owns direct Pi SDK imports and concrete adapter contracts. Other domains use narrow ports; domains that register Pi hooks/tools/commands consume contracts through that adapter.
62
64
  - Do not introduce shared buckets such as `lib/constants.ts`, `lib/types.ts`, `lib/globals.ts`, or broad global-augmentation modules. Keep state, constants, registry keys, and concrete transport shapes with their domain owner.
63
65
  - Every source `.ts` file starts with a brief responsibility header containing `Zones:` tags such as `telegram`, `pi agent`, `tui`, or `shared utils`.
64
- - Use namespace imports for local domains in `index.ts` (`Queue.*`, `Turns.*`) and keep direct `node:*`, filesystem, process, and local-adapter mechanics in owning domains when one exists.
66
+ - Use namespace imports for local domains in `lib/extension.ts` (`Queue.*`, `Turns.*`) and keep direct `node:*`, filesystem, process, and local-adapter mechanics in owning domains when one exists.
65
67
 
66
68
  ### 4.2 Ownership, Sessions, And Trust
67
69
 
68
70
  - The bridge is session-local and paired to one allowed Telegram user. Preserve `{ chatId, threadId? }` through every inbound, queue, callback, reaction, media, preview, reply, menu, voice, attachment, and direct-delivery path.
71
+ - First-contact pairing grants in-memory authority only after profile/token/execution-fenced durable publication confirms that exact user; it never overwrites another configured owner. Sender admission precedes user message/edit/callback/reaction delegation, including foreign ownership and unbound-Thread fallback paths. Reactions require an existing exact human owner; private chat type is not authorization. Queued config persistence must not replay observed authority as local grant edits or erase later local unpair. Setup and retry details belong in [`docs/architecture.md`](./docs/architecture.md#setup-flow).
69
72
  - Telegram transport ownership is not semantic queue ownership. Losing the exact transport lock must not erase accepted local queue work or stop valid local Pi dispatch; direct Bot API mutations fail closed until exact direct or follower authority exists.
70
73
  - `tmp/telegram/owners.json` is the sole transport-owner authority. Cross-process read/check/write operations serialize transactionally and acquisition, refresh, release, takeover, and irreversible leader work fence the exact owner/epoch. `state.json` and `logs.jsonl` are diagnostics, never routing authority.
71
74
  - Threaded Mode has exactly one live leader per bot profile. Followers are real operator-started Pi processes and must authenticate/register over local IPC; Telegram never spawns hidden Pi processes. A live but unreachable owner does not authorize split-brain polling.
72
75
  - Local IPC is a trust boundary, not merely a private socket. Unknown, stale, mismatched-generation, or unauthorized requests must not inject prompts, callbacks, API sends, artifacts, liveness, or bindings.
73
- - Protocol compatibility is independent from package version. Registration negotiates protocol version, runtime build, and canonical capabilities before target provisioning or live publication. `durable-follower-admission-v1` gates source forwarding; `queue-handoff-v1` independently gates semantic queue transfer for every participant and is advertised only with exact source/recipient journal-binding composition. `follower.register` is the sole bootstrap request; other requests require exact live-registry generation authority, and `bus.ack` is response-only.
76
+ - Protocol compatibility is independent from package version. Registration negotiates protocol version, runtime build, and canonical capabilities before target provisioning or live publication. `durable-follower-admission-v1` gates source forwarding; `queue-handoff-v1` independently gates semantic queue transfer for every participant and is advertised only with exact source/recipient journal-binding composition. `follower.register` and capability-gated restore-only `follower.restoreWorkspace` are bootstrap requests; other requests require exact live-registry generation authority, and `bus.ack` is response-only. `thread-display-mode-v1` gates follower display-setting requests; the leader owns their serialized profile preference and title application.
74
77
  - Long-lived timers, pollers, watchers, receivers, heartbeats, background delivery, and deferred dispatch are session-bound. Replacement stops stale activity and makes late work inert; same-process handoff may preserve exact profile/target identity but never stale Pi context or cross-profile authority. Aborting a durable update generation does not release that `update_id`: replacement replay waits for its actual handler settlement, and effectful handlers use the shared execution fence immediately before commit and after awaited delegation. Internal clones explicitly carry the hidden fence; reroute forwarding, thread-store mutation, cleanup, and Bot API boundaries retain the originating generation.
75
- - Runtime state is event-driven reconciliation of local assumptions against Telegram signals, not a complete bot read-model and not permission to query Telegram on every action. Destructive thread cleanup goes through `thread-reconciler` with current proof and leader fencing.
78
+ - Runtime state is event-driven reconciliation of local assumptions against Telegram signals, not a complete bot read-model and not permission to query Telegram on every action. Destructive thread cleanup goes through `thread-reconciler` with current proof and leader fencing. Fresh Workspace Thread creation derives its initial Bot API title from the active display mode before issuance; the stable generated `threadName` remains separate from the acknowledged `displayTitle`.
76
79
 
77
80
  ### 4.3 Durable Admission And Settlement
78
81
 
79
82
  - Admission is journal-first: validate and persist the complete `getUpdates` response before one monotonic offset commit, then signal an independent worker without awaiting semantic execution. Missing cursor with a non-empty journal, malformed/foreign authority, or capacity exhaustion fails closed. “Durable” means process-crash recovery after atomic rename, not unflushed host/kernel/filesystem/device/power-loss survival.
83
+ - Workspace mutations acquire cross-process admission before their shared process-local gate and hold it through asynchronous API work and durable settlement. Topic lifecycle, complete unbound/reroute target handling, manual disconnect, and session-restart cleanup use profile-wide scope; either retained retirement-fence phase rejects them before state access. Cleanup scope spans intent publication, target mutation, persistence, and transport release. Detached reconciliation that mutates Thread state must reacquire fresh profile admission through the same gate; it cannot inherit a caller lease that ended before its timer runs. A live operation ID has one process-local caller: concurrent reuse is rejected before lease acquisition, while retry after the caller exits may resume exact durable authority.
84
+ - Workspace retirement is capacity-pressure-only. Elapsed time and heartbeat silence never trigger deletion; only complete `A`–`Z` exhaustion may propose the oldest continuously proven inactive, fully unprotected binding. Exact deletion, durable retirement, and fence completion precede slot reuse. Automatic retirement remains disconnected until separately authorized and operator-validated.
80
85
  - Foreign forwarding settles as `accepted`, `retryable`, or `terminal-rejected`. Only an authenticated acknowledgement carrying the expected `deliveryId` and `sourceUpdateId` releases leader journal authority. Negative, missing, stale, mismatched, or capacity-failed settlement remains durable; callback error answers are side effects only.
81
86
  - A forwarding delivery id is stable across registration replacement and derives from envelope kind, source `update_id`, and stable recipient binding. Runtime instance and registration generation remain separate attempt fences. Persisted message ownership carries the stable binding so replay can rebind only to its current authenticated registration.
82
87
  - A queued receipt persists its acquiring runtime instance, OS pid/process-birth identity, session generation, acquisition id, and acquisition time. Only exact authority may settle or discard it. Same-process session replacement may reconstruct the claim and the original process may settle after transport ownership moves; a foreign process may neither replay nor settle it through generic removal or a copied acquisition id.
@@ -94,7 +99,7 @@ Use the relevant local skill before non-trivial work in its domain. Keep skill o
94
99
  - `preview` owns streaming lifecycle only, not assistant rendering. Finalization waits for active preview flushes and must not issue pre/post-final draft-clear calls that create transient Telegram draft UI.
95
100
  - Native `sendChatAction(typing)` is the automatic activity signal for unsettled agent and compaction work while Telegram transport is authorized. Extension-owned blocking UI prompts pause it and completion resumes it while either work owner remains active. Do not invent extra in-chat work indicators or emit activity for startup/connect/reload/recovery alone.
96
101
  - Public activity handlers and connected companion delivery are asynchronous, target-bound, generation-fenced surfaces. Connected companion projection has no independent opt-out: disconnect or authority loss is its boundary. Token deltas, hidden reasoning, unknown sources, and stale authority never enter public projection.
97
- - UI labels, emoji semantics, navigation, settings controls, callback namespaces, voice behavior, command templates, and assistant markup follow the linked `/docs` contracts. Generated human-readable prompt-button labels use `emoji + space + text`; emoji-free text is only a reasoned no-semantic-marker fallback. Non-spatial generated controls default to top-level vertical cells, with nested rows reserved for unmistakably compact peers. Do not restate other evolving UI details here.
102
+ - Thread display defaults to the profile-scoped Letters strategy, with Directories as the other automatic choice. A durable manual Thread display name retained on its Workspace binding overrides either until exact reset; keep generated/recovery identity separate from manual and acknowledged display fields. UI labels, emoji semantics, navigation, settings controls, callback namespaces, voice behavior, command templates, and assistant markup follow the linked `/docs` contracts. Generated human-readable prompt-button labels use `emoji + space + text`; emoji-free text is only a reasoned no-semantic-marker fallback. Non-spatial generated controls default to top-level vertical cells, with nested rows reserved for unmistakably compact peers. Do not restate other evolving UI details here.
98
103
 
99
104
  ## 5. Domain Ownership Index
100
105
 
@@ -124,7 +129,7 @@ The detailed map is canonical in [`docs/architecture.md`](./docs/architecture.md
124
129
  - Name flat modules by bare domain (`queue.ts`, `queue.test.ts`); `telegram-api.ts` is the intentional transport exception. Tests primarily protect their mirrored module; shared fixtures require real cross-suite reuse.
125
130
  - Keep interfaces consistent with their owning exported contract. Use local structural `*Like`/view types only for deliberate narrow projections, not duplicate source-of-truth models.
126
131
  - Remove dead code immediately. Reachability from composition roots, public exports, tests, registered surfaces, and documented APIs—not recent usefulness—determines whether code is live.
127
- - Treat every meaningful `index.ts` edit as a composition-pressure check, but keep one-off live adapter wiring there when extraction would only hide cross-domain state.
132
+ - Treat every meaningful `lib/extension.ts` edit as a composition-pressure check, but keep one-off live adapter wiring there when extraction would only hide cross-domain state.
128
133
  - Follow [`docs/ui-style.md`](./docs/ui-style.md) for interface copy, emoji, buttons, menus, and dialogs. Update the registry before assigning a new UI emoji meaning. Standalone notices use one fully bold emoji-led sentence with a terminal period; menu or chooser headings use the same hierarchy with a terminal colon. Material names may add nested italic emphasis without breaking the outer bold span. Callback alerts preserve equivalent emoji-led plain text because Telegram does not support rich formatting there.
129
134
  - Markdown lists never contain blank lines between adjacent items; list items are not paragraphs. Use blank lines only between paragraphs or independently separated blocks. Markdown tables use compact source formatting with `---` separator cells and one surrounding space per cell. Preserve vendored references unchanged.
130
135
  - Treat Windows filesystem, named-pipe, lock, heartbeat, and atomic-rename reports as high-signal evidence; reduce them to regressions or explicit platform caveats.
@@ -137,7 +142,7 @@ Before non-trivial work:
137
142
  1. Read `README.md` for current product behavior and positioning.
138
143
  2. Read `BACKLOG.md` before runtime or documentation changes.
139
144
  3. Read the relevant indexed docs; read `docs/architecture.md` before architecture, queue, preview, rendering, lifecycle, or command restructuring.
140
- 4. Inspect the owning module, its callers, mirrored tests, and the relevant `index.ts` wiring before editing.
145
+ 4. Inspect the owning module, its callers, mirrored tests, and the relevant `lib/extension.ts` wiring before editing.
141
146
  5. Run an `AGENTS.md` compliance pass for implementation, release, and architecture work; update an obsolete rule instead of silently working around it.
142
147
 
143
148
  While working: