@llblab/pi-kit 0.3.2 → 0.5.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.
- package/AGENTS.md +3 -3
- package/BACKLOG.md +1 -1
- package/CHANGELOG.md +10 -0
- package/README.md +19 -10
- package/node_modules/@llblab/pi-actors/AGENTS.md +1 -0
- package/node_modules/@llblab/pi-actors/CHANGELOG.md +6 -0
- package/node_modules/@llblab/pi-actors/dist/skills/actors/SKILL.md +16 -6
- package/node_modules/@llblab/pi-actors/dist/skills/music-player/SKILL.md +3 -3
- package/node_modules/@llblab/pi-actors/dist/skills/music-player/genapps/music-player.mjs +6 -4
- package/node_modules/@llblab/pi-actors/dist/skills/music-player/scripts/playback.mjs +85 -18
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/SKILL.md +2 -6
- package/node_modules/@llblab/pi-actors/dist/skills/swarm/references/development-swarm.md +2 -31
- package/node_modules/@llblab/pi-actors/docs/recipe-library.md +1 -1
- package/node_modules/@llblab/pi-actors/package.json +1 -1
- package/node_modules/@llblab/pi-actors/skills/actors/SKILL.md +16 -6
- package/node_modules/@llblab/pi-actors/skills/music-player/SKILL.md +3 -3
- package/node_modules/@llblab/pi-actors/skills/music-player/genapps/music-player.mjs +6 -4
- package/node_modules/@llblab/pi-actors/skills/music-player/scripts/playback.mjs +85 -18
- package/node_modules/@llblab/pi-actors/skills/swarm/SKILL.md +2 -6
- package/node_modules/@llblab/pi-actors/skills/swarm/references/development-swarm.md +2 -31
- package/node_modules/@llblab/pi-clean-room/AGENTS.md +5 -0
- package/node_modules/@llblab/pi-clean-room/BACKLOG.md +3 -0
- package/node_modules/@llblab/pi-clean-room/CHANGELOG.md +15 -0
- package/node_modules/@llblab/pi-clean-room/README.md +61 -0
- package/node_modules/@llblab/pi-clean-room/banner.jpg +0 -0
- package/node_modules/@llblab/pi-clean-room/index.ts +178 -0
- package/node_modules/@llblab/pi-clean-room/package.json +53 -0
- package/node_modules/@llblab/pi-grow-loop/AGENTS.md +1 -1
- package/node_modules/@llblab/pi-grow-loop/CHANGELOG.md +4 -0
- package/node_modules/@llblab/pi-grow-loop/package.json +1 -1
- package/node_modules/@llblab/pi-grow-loop/skills/grow-loop/SKILL.md +3 -17
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +28 -0
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +3 -0
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +79 -0
- package/node_modules/@llblab/pi-state-flow/README.md +197 -0
- package/node_modules/@llblab/pi-state-flow/index.ts +4 -0
- package/node_modules/@llblab/pi-state-flow/lib/context.ts +88 -0
- package/node_modules/@llblab/pi-state-flow/lib/episode.ts +29 -0
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +238 -0
- package/node_modules/@llblab/pi-state-flow/lib/json.ts +78 -0
- package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +38 -0
- package/node_modules/@llblab/pi-state-flow/lib/session.ts +49 -0
- package/node_modules/@llblab/pi-state-flow/lib/skills.ts +58 -0
- package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +103 -0
- package/node_modules/@llblab/pi-state-flow/lib/state.ts +20 -0
- package/node_modules/@llblab/pi-state-flow/lib/status.ts +16 -0
- package/node_modules/@llblab/pi-state-flow/lib/terminal.ts +148 -0
- package/node_modules/@llblab/pi-state-flow/lib/transition.ts +47 -0
- package/node_modules/@llblab/pi-state-flow/lib/validation.ts +27 -0
- package/node_modules/@llblab/pi-state-flow/package.json +55 -0
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +3 -0
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +18 -0
- package/node_modules/@llblab/pi-telegram/README.md +2 -2
- package/node_modules/@llblab/pi-telegram/docs/README.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +5 -3
- package/node_modules/@llblab/pi-telegram/docs/compact-matrix-literal.md +39 -11
- package/node_modules/@llblab/pi-telegram/docs/generative-apps.md +2 -2
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +17 -5
- package/node_modules/@llblab/pi-telegram/docs/outbound.md +6 -4
- package/node_modules/@llblab/pi-telegram/docs/public-api.md +1 -1
- package/node_modules/@llblab/pi-telegram/index.ts +13 -1
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +12 -3
- package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +29 -18
- package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +15 -6
- package/node_modules/@llblab/pi-telegram/lib/bus.ts +11 -4
- package/node_modules/@llblab/pi-telegram/lib/keyboard.ts +5 -3
- package/node_modules/@llblab/pi-telegram/lib/outbound-buttons.ts +72 -15
- package/node_modules/@llblab/pi-telegram/lib/outbound-markup.ts +81 -9
- package/node_modules/@llblab/pi-telegram/lib/outbound.ts +2 -0
- package/node_modules/@llblab/pi-telegram/lib/replies.ts +1 -1
- package/node_modules/@llblab/pi-telegram/lib/routing.ts +89 -32
- package/node_modules/@llblab/pi-telegram/lib/sync.ts +74 -15
- package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +32 -1
- package/node_modules/@llblab/pi-telegram/lib/thread-reconciler.ts +17 -0
- package/node_modules/@llblab/pi-telegram/lib/threads.ts +123 -15
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/skills/generated-control-surface/SKILL.md +4 -2
- package/node_modules/@llblab/pi-telegram/skills/generated-control-surface/references/layout-and-state.md +4 -2
- package/node_modules/@llblab/pi-telegram/skills/generative-apps/SKILL.md +4 -3
- package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/SKILL.md +19 -8
- package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/references/diagnosis.md +4 -2
- package/node_modules/@llblab/skills/abcd-context/AGENTS.md +40 -0
- package/node_modules/@llblab/skills/abcd-context/BACKLOG.md +3 -0
- package/node_modules/@llblab/skills/abcd-context/CHANGELOG.md +19 -0
- package/node_modules/@llblab/skills/abcd-context/README.md +30 -0
- package/node_modules/@llblab/skills/abcd-context/SKILL.md +122 -0
- package/node_modules/@llblab/skills/abcd-context/docs/README.md +9 -0
- package/node_modules/@llblab/skills/abcd-context/docs/protocols.md +199 -0
- package/node_modules/@llblab/skills/abcd-context/docs/templates.md +194 -0
- package/node_modules/@llblab/skills/abcd-context/docs/validation-design.md +117 -0
- package/node_modules/@llblab/skills/abcd-context/fixtures/abcd-project/AGENTS.md +10 -0
- package/node_modules/@llblab/skills/abcd-context/fixtures/abcd-project/BACKLOG.md +5 -0
- package/node_modules/@llblab/skills/abcd-context/fixtures/abcd-project/CHANGELOG.md +5 -0
- package/node_modules/@llblab/skills/abcd-context/fixtures/abcd-project/README.md +10 -0
- package/node_modules/@llblab/skills/abcd-context/fixtures/abcd-project/docs/README.md +7 -0
- package/node_modules/@llblab/skills/abcd-context/fixtures/abcd-project/docs/overview.md +3 -0
- package/node_modules/@llblab/skills/abcd-context/recipes/validate-context.json +7 -0
- package/node_modules/@llblab/skills/abcd-context/scripts/_self-test.mjs +244 -0
- package/node_modules/@llblab/skills/abcd-context/scripts/validate-context.mjs +497 -0
- package/node_modules/@llblab/skills/brain-storm/AGENTS.md +5 -0
- package/node_modules/@llblab/skills/brain-storm/SKILL.md +321 -0
- package/node_modules/@llblab/skills/coding-contract/SKILL.md +228 -0
- package/node_modules/@llblab/skills/domain-dag/AGENTS.md +6 -0
- package/node_modules/@llblab/skills/domain-dag/SKILL.md +257 -0
- package/node_modules/@llblab/skills/domain-dag/domain-dag.json +9 -0
- package/node_modules/@llblab/skills/domain-dag/recipes/validate-domain-dag.json +7 -0
- package/node_modules/@llblab/skills/domain-dag/scripts/validate-domain-dag.mjs +771 -0
- package/node_modules/@llblab/skills/domain-dag/scripts/validate-domain-dag.sh +4 -0
- package/node_modules/@llblab/skills/edge-tts/AGENTS.md +19 -0
- package/node_modules/@llblab/skills/edge-tts/SKILL.md +56 -0
- package/node_modules/@llblab/skills/edge-tts/recipes/say-edge.json +9 -0
- package/node_modules/@llblab/skills/edge-tts/scripts/say.mjs +1548 -0
- package/node_modules/@llblab/skills/edge-tts/scripts/say.sh +4 -0
- package/node_modules/@llblab/skills/extra-self/SKILL.md +229 -0
- package/node_modules/@llblab/skills/frontend-design/SKILL.md +78 -0
- package/node_modules/@llblab/skills/groq-stt/AGENTS.md +15 -0
- package/node_modules/@llblab/skills/groq-stt/SKILL.md +35 -0
- package/node_modules/@llblab/skills/groq-stt/recipes/transcribe-groq.json +10 -0
- package/node_modules/@llblab/skills/groq-stt/scripts/transcribe.mjs +190 -0
- package/node_modules/@llblab/skills/groq-stt/scripts/transcribe.sh +4 -0
- package/node_modules/@llblab/skills/mistral-stt/AGENTS.md +15 -0
- package/node_modules/@llblab/skills/mistral-stt/SKILL.md +35 -0
- package/node_modules/@llblab/skills/mistral-stt/recipes/transcribe-mistral.json +10 -0
- package/node_modules/@llblab/skills/mistral-stt/scripts/transcribe.mjs +191 -0
- package/node_modules/@llblab/skills/mistral-stt/scripts/transcribe.sh +4 -0
- package/node_modules/@llblab/skills/package.json +46 -0
- package/node_modules/@llblab/skills/re-review/SKILL.md +318 -0
- package/node_modules/@llblab/skills/release-flow/SKILL.md +456 -0
- package/node_modules/@llblab/skills/show-me/SKILL.md +144 -0
- package/package.json +20 -8
- package/node_modules/@llblab/pi-actors/dist/skills/music-player/scripts/playback-client.mjs +0 -143
- package/node_modules/@llblab/pi-actors/skills/music-player/scripts/playback-client.mjs +0 -143
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
writeFile,
|
|
22
22
|
} from "node:fs/promises";
|
|
23
23
|
import { dirname } from "node:path";
|
|
24
|
+
import { isDeepStrictEqual } from "node:util";
|
|
24
25
|
|
|
25
26
|
import {
|
|
26
27
|
isTelegramApiCommitUnknownError,
|
|
@@ -229,6 +230,11 @@ export interface TelegramTopicTargetStore {
|
|
|
229
230
|
/** Discard process-local projections and reload owner-published state. */
|
|
230
231
|
refresh?: () => Promise<void>;
|
|
231
232
|
persist: () => Promise<void>;
|
|
233
|
+
invalidateTarget: (
|
|
234
|
+
target: TelegramTarget,
|
|
235
|
+
isCurrent: () => boolean,
|
|
236
|
+
lastSyncError: string,
|
|
237
|
+
) => Promise<boolean>;
|
|
232
238
|
list: () => TelegramTopicTargetRecord[];
|
|
233
239
|
getFollowerRecoveryHintByTarget?: (
|
|
234
240
|
target: TelegramTarget,
|
|
@@ -316,6 +322,53 @@ export function reconcileTelegramFreshAllocationCursor(
|
|
|
316
322
|
return true;
|
|
317
323
|
}
|
|
318
324
|
|
|
325
|
+
export function createTelegramCleanupTargetProtection(
|
|
326
|
+
store: Pick<TelegramTopicTargetStore, "list"> & Partial<Pick<TelegramTopicTargetStore, "listReservations" | "listPendingProvisions" | "listPendingCleanups">>,
|
|
327
|
+
departingRecord?: TelegramTopicTargetRecord,
|
|
328
|
+
): NonNullable<ThreadReconciler.ThreadReconciliationApplyPorts["isCleanupTargetProtected"]> {
|
|
329
|
+
const records = store.list();
|
|
330
|
+
const reservations = store.listReservations?.() ?? [];
|
|
331
|
+
const provisions = store.listPendingProvisions?.() ?? [];
|
|
332
|
+
const intents = store.listPendingCleanups?.() ?? [];
|
|
333
|
+
// Persistence may reconstruct keys in another order and omit undefined
|
|
334
|
+
// optional fields; neither changes the authority represented by a snapshot.
|
|
335
|
+
const sameSnapshot = (left: unknown, right: unknown): boolean =>
|
|
336
|
+
isDeepStrictEqual(JSON.parse(JSON.stringify(left)), JSON.parse(JSON.stringify(right)));
|
|
337
|
+
return (target, action) => {
|
|
338
|
+
for (const record of store.list()) {
|
|
339
|
+
if (!targetMatches(record.target, target)) continue;
|
|
340
|
+
// A persisted shutdown intent may retire only its original pre-intent
|
|
341
|
+
// binding. Registration/rebinding after that intent supersedes it.
|
|
342
|
+
const intent = action.kind === "close-delete-graceful-shutdown-topic"
|
|
343
|
+
? intents.find((candidate) => candidate.id === action.cleanupIntentId && candidate.runtimeGeneration === action.runtimeGeneration)
|
|
344
|
+
: undefined;
|
|
345
|
+
const expectedDeparting = departingRecord ?? (intent && records.find((candidate) =>
|
|
346
|
+
candidate.instanceId === intent.instanceId && targetMatches(candidate.target, intent.target) &&
|
|
347
|
+
candidate.updatedAtMs <= intent.requestedAtMs));
|
|
348
|
+
if (expectedDeparting && "instanceId" in action &&
|
|
349
|
+
(action.kind === "close-delete-previous-leader-topic" || action.instanceId === expectedDeparting.instanceId) &&
|
|
350
|
+
sameSnapshot(record, expectedDeparting)) {
|
|
351
|
+
if (action.kind === "close-delete-previous-leader-topic" || action.kind === "close-stale-replaced-topic") continue;
|
|
352
|
+
if (action.kind === "close-delete-graceful-shutdown-topic" &&
|
|
353
|
+
store.listPendingCleanups?.().some((intent) => intent.id === action.cleanupIntentId &&
|
|
354
|
+
intent.instanceId === action.instanceId && intent.runtimeGeneration === action.runtimeGeneration &&
|
|
355
|
+
targetMatches(intent.target, target))) continue;
|
|
356
|
+
}
|
|
357
|
+
if (record.status === "active" || record.status === "starting" || record.status === "pending" || record.status === "probe-required") return true;
|
|
358
|
+
}
|
|
359
|
+
for (const reservation of store.listReservations?.() ?? []) {
|
|
360
|
+
if (!targetMatches(reservation.target, target)) continue;
|
|
361
|
+
if (action.kind !== "close-delete-reserved-topic" || !reservations.some((initial) => sameSnapshot(initial, reservation))) return true;
|
|
362
|
+
}
|
|
363
|
+
for (const provision of store.listPendingProvisions?.() ?? []) {
|
|
364
|
+
if (!provision.target || !targetMatches(provision.target, target)) continue;
|
|
365
|
+
if (action.kind !== "close-delete-expired-pending-provision-topic" || provision.id !== action.pendingProvisionId ||
|
|
366
|
+
!provisions.some((initial) => sameSnapshot(initial, provision))) return true;
|
|
367
|
+
}
|
|
368
|
+
return false;
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
319
372
|
export interface TelegramTopicTargetStoreOptions {
|
|
320
373
|
path: string | (() => string);
|
|
321
374
|
getNowMs?: () => number;
|
|
@@ -1230,7 +1283,11 @@ export function createTelegramTopicTargetStore(
|
|
|
1230
1283
|
loaded = true;
|
|
1231
1284
|
return;
|
|
1232
1285
|
}
|
|
1286
|
+
const revision = mutationRevision;
|
|
1233
1287
|
const content = await readFile(path, "utf8");
|
|
1288
|
+
// A read begun before a local mutation must not replace the newly admitted
|
|
1289
|
+
// binding/cleanup state with its older disk snapshot.
|
|
1290
|
+
if (mutationRevision !== revision || getPath() !== path) return;
|
|
1234
1291
|
const rawFile: unknown = JSON.parse(content);
|
|
1235
1292
|
const file = parseTopicTargetFile(rawFile);
|
|
1236
1293
|
followerRecoveryHints = parseFollowerRecoveryHints(rawFile);
|
|
@@ -1293,25 +1350,20 @@ export function createTelegramTopicTargetStore(
|
|
|
1293
1350
|
mutationRevision += 1;
|
|
1294
1351
|
};
|
|
1295
1352
|
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
refresh() {
|
|
1302
|
-
const refresh = persistQueue.then(loadFromDisk);
|
|
1303
|
-
persistQueue = refresh.catch(() => undefined);
|
|
1304
|
-
return refresh;
|
|
1305
|
-
},
|
|
1306
|
-
persist() {
|
|
1353
|
+
const persistSnapshot = (invalidation?: {
|
|
1354
|
+
target: TelegramTarget;
|
|
1355
|
+
isCurrent: () => boolean;
|
|
1356
|
+
lastSyncError: string;
|
|
1357
|
+
}): Promise<boolean> => {
|
|
1307
1358
|
const persist = persistQueue.then(async () => {
|
|
1308
1359
|
const path = getPath();
|
|
1309
1360
|
if (loadedPath !== path && !dirty) resetForPath(path);
|
|
1310
1361
|
if (options.canPersist && !options.canPersist()) {
|
|
1311
|
-
await loadFromDisk();
|
|
1312
|
-
return;
|
|
1362
|
+
if (!invalidation) await loadFromDisk();
|
|
1363
|
+
return false;
|
|
1313
1364
|
}
|
|
1314
1365
|
if (!dirty || !loaded) await loadFromDisk();
|
|
1366
|
+
if (invalidation && !invalidation.isCurrent()) return false;
|
|
1315
1367
|
await mkdir(dirname(path), { recursive: true });
|
|
1316
1368
|
const tempPath = `${path}.${process.pid}.${Date.now()}.${randomUUID()}.tmp`;
|
|
1317
1369
|
const nowMs = getNowMs();
|
|
@@ -1359,6 +1411,21 @@ export function createTelegramTopicTargetStore(
|
|
|
1359
1411
|
return serialized;
|
|
1360
1412
|
}),
|
|
1361
1413
|
};
|
|
1414
|
+
if (invalidation) {
|
|
1415
|
+
const record = file.threads.find((record) => targetMatches(record.target, invalidation.target));
|
|
1416
|
+
if (!record) return false;
|
|
1417
|
+
file.threads = file.threads.filter((candidate) => candidate !== record);
|
|
1418
|
+
file.syncObservations = file.syncObservations.filter((observation) => !targetMatches(observation.target, record.target));
|
|
1419
|
+
file.syncObservations.push({
|
|
1420
|
+
target: { ...record.target },
|
|
1421
|
+
syncStatus: "deleted",
|
|
1422
|
+
observedAtMs: nowMs,
|
|
1423
|
+
...(record.instanceId ? { instanceId: record.instanceId } : {}),
|
|
1424
|
+
...(record.slot ? { slot: record.slot } : {}),
|
|
1425
|
+
lastSyncError: invalidation.lastSyncError,
|
|
1426
|
+
lastReconcileAction: "mark-stale",
|
|
1427
|
+
});
|
|
1428
|
+
}
|
|
1362
1429
|
let persistedSemanticSnapshot: string | undefined;
|
|
1363
1430
|
try {
|
|
1364
1431
|
persistedSemanticSnapshot = serializeTelegramStateSemanticSnapshot(
|
|
@@ -1375,7 +1442,7 @@ export function createTelegramTopicTargetStore(
|
|
|
1375
1442
|
) {
|
|
1376
1443
|
loaded = true;
|
|
1377
1444
|
dirty = false;
|
|
1378
|
-
return;
|
|
1445
|
+
return true;
|
|
1379
1446
|
}
|
|
1380
1447
|
await writeFile(tempPath, `${JSON.stringify(file, null, 2)}\n`, {
|
|
1381
1448
|
encoding: "utf8",
|
|
@@ -1383,6 +1450,25 @@ export function createTelegramTopicTargetStore(
|
|
|
1383
1450
|
});
|
|
1384
1451
|
await chmod(tempPath, 0o600);
|
|
1385
1452
|
try {
|
|
1453
|
+
if (invalidation) {
|
|
1454
|
+
let applied = false;
|
|
1455
|
+
const commit = () => {
|
|
1456
|
+
if (getPath() !== path || mutationRevision !== persistedRevision ||
|
|
1457
|
+
statusRevision !== persistedStatusRevision || !invalidation.isCurrent()) return;
|
|
1458
|
+
// Fence and rename share one synchronous commit boundary. No stale
|
|
1459
|
+
// invalidation enters the live projection before durable commit.
|
|
1460
|
+
renameSync(tempPath, path);
|
|
1461
|
+
records = new Map(Array.from(records).filter(([, record]) => !targetMatches(record.target, invalidation.target)));
|
|
1462
|
+
syncObservations = file.syncObservations;
|
|
1463
|
+
mutationRevision += 1;
|
|
1464
|
+
dirty = false;
|
|
1465
|
+
applied = true;
|
|
1466
|
+
};
|
|
1467
|
+
if (options.commitPersist) options.commitPersist(commit);
|
|
1468
|
+
else if (!options.canPersist || options.canPersist()) commit();
|
|
1469
|
+
if (!applied) await unlink(tempPath).catch(() => undefined);
|
|
1470
|
+
return applied;
|
|
1471
|
+
}
|
|
1386
1472
|
if (options.commitPersist) {
|
|
1387
1473
|
const committed = options.commitPersist(() => {
|
|
1388
1474
|
renameSync(tempPath, path);
|
|
@@ -1404,9 +1490,27 @@ export function createTelegramTopicTargetStore(
|
|
|
1404
1490
|
}
|
|
1405
1491
|
loaded = true;
|
|
1406
1492
|
if (mutationRevision === persistedRevision) dirty = false;
|
|
1493
|
+
return true;
|
|
1407
1494
|
});
|
|
1408
|
-
persistQueue = persist.
|
|
1495
|
+
persistQueue = persist.then(() => undefined, () => undefined);
|
|
1409
1496
|
return persist;
|
|
1497
|
+
};
|
|
1498
|
+
|
|
1499
|
+
return {
|
|
1500
|
+
async load() {
|
|
1501
|
+
if (dirty) return;
|
|
1502
|
+
await loadFromDisk();
|
|
1503
|
+
},
|
|
1504
|
+
refresh() {
|
|
1505
|
+
const refresh = persistQueue.then(loadFromDisk);
|
|
1506
|
+
persistQueue = refresh.catch(() => undefined);
|
|
1507
|
+
return refresh;
|
|
1508
|
+
},
|
|
1509
|
+
async persist() {
|
|
1510
|
+
await persistSnapshot();
|
|
1511
|
+
},
|
|
1512
|
+
invalidateTarget(target, isCurrent, lastSyncError) {
|
|
1513
|
+
return persistSnapshot({ target, isCurrent, lastSyncError });
|
|
1410
1514
|
},
|
|
1411
1515
|
list() {
|
|
1412
1516
|
return Array.from(records.values()).map(cloneRecord);
|
|
@@ -2100,6 +2204,7 @@ export async function provisionOwnBusTopic(
|
|
|
2100
2204
|
if (typeof chatId !== "number") return undefined;
|
|
2101
2205
|
await deps.store.load();
|
|
2102
2206
|
const reservationCleanupPorts = {
|
|
2207
|
+
isCleanupTargetProtected: createTelegramCleanupTargetProtection(deps.store),
|
|
2103
2208
|
callApi: deps.callApi,
|
|
2104
2209
|
markStaleByTarget: (
|
|
2105
2210
|
target: TelegramTarget & { threadId: number },
|
|
@@ -2253,9 +2358,11 @@ export async function provisionOwnBusTopic(
|
|
|
2253
2358
|
continue;
|
|
2254
2359
|
}
|
|
2255
2360
|
const previousLeaderCleanupStartedAtMs = Date.now();
|
|
2361
|
+
const isCleanupTargetProtected = createTelegramCleanupTargetProtection(deps.store, record);
|
|
2256
2362
|
const cleanup = await ThreadReconciler.applyThreadReconciliationPlan(
|
|
2257
2363
|
{ actions: [action] },
|
|
2258
2364
|
{
|
|
2365
|
+
isCleanupTargetProtected,
|
|
2259
2366
|
callApi: deps.callApi,
|
|
2260
2367
|
markStaleByTarget: (target, syncStatus, lastSyncError) =>
|
|
2261
2368
|
deps.store.markStaleByTarget(target, syncStatus, lastSyncError),
|
|
@@ -2304,6 +2411,7 @@ export async function provisionOwnBusTopic(
|
|
|
2304
2411
|
"Previous Telegram leader topic deletion was not confirmed.",
|
|
2305
2412
|
);
|
|
2306
2413
|
}
|
|
2414
|
+
if (isCleanupTargetProtected(action.target, action)) continue;
|
|
2307
2415
|
deps.store.markStaleByTarget(record.target);
|
|
2308
2416
|
deps.store.reserveThread({
|
|
2309
2417
|
target: record.target,
|
|
@@ -40,7 +40,9 @@ A surface is an ordered ragged sequence of rows. Each button carries:
|
|
|
40
40
|
|
|
41
41
|
- A short, distinct label.
|
|
42
42
|
- The smallest self-contained next-request prompt.
|
|
43
|
-
- Optional presentation state supported by the transport.
|
|
43
|
+
- Optional presentation state supported by the transport, including disabled controls when their visible unavailability helps explain current state.
|
|
44
|
+
|
|
45
|
+
A disabled control is not an action: it needs no prompt or selected style and must not enqueue a prompt or invoke a bound method. Prefer a meaningful label; omit it only for an intentional blank cell in a spatial layout, never as decorative padding. Preserve its label and position when that makes a changing surface easier to understand; otherwise omit irrelevant controls. Explain non-obvious unavailability without relying on color alone. Retain at least one useful enabled action, such as refresh or navigation. Derive disabled state from the same evidence as the view; an old enabled control still requires current domain validation. Use the transport owner's disabled encoding rather than a dummy prompt or no-op callback.
|
|
44
46
|
|
|
45
47
|
Prompts must name any target, operation, constraint, or freshness identity whose omission could change the action. Reuse visible context only when it remains unambiguous under delayed or reordered clicks. Never encode volatile output that should be freshly inspected.
|
|
46
48
|
|
|
@@ -57,7 +59,7 @@ Every generated human-readable action label must use `emoji + space + text`; emo
|
|
|
57
59
|
|
|
58
60
|
For complex grids, navigation collections, or stateful repeated clicks, read [`references/layout-and-state.md`](./references/layout-and-state.md).
|
|
59
61
|
|
|
60
|
-
Serialize the resulting rows with the active transport contract. This Skill owns admission and composition, not transport syntax.
|
|
62
|
+
Place a control group beside the section it governs when the transport supports in-body blocks; use a footer for whole-answer actions. Placement must not change the matrix or action semantics. Serialize the resulting rows with the active transport contract. This Skill owns admission and composition, not transport syntax.
|
|
61
63
|
|
|
62
64
|
## Safety
|
|
63
65
|
|
|
@@ -10,7 +10,7 @@ Model the surface as ordered ragged rows, not a rectangle to fill. Infer indepen
|
|
|
10
10
|
- A horizontal pair is earned only by genuine peers with unmistakably compact labels and no plausible wrapping or truncation.
|
|
11
11
|
- Three through five columns are for short symbols, coordinates, glyphs, or codes whose position carries meaning.
|
|
12
12
|
- Six through eight columns require single-glyph or similarly minimal position-bearing labels. Never exceed eight columns on a phone surface.
|
|
13
|
-
- Vary row width intentionally; never pad with empty, duplicate, or no-op controls.
|
|
13
|
+
- Vary row width intentionally; never pad for symmetry with empty, duplicate, or no-op controls. A blank disabled cell is appropriate only when it represents a real unavailable position in a spatial grid.
|
|
14
14
|
- Preserve reading order: orientation/navigation, primary content or choices, secondary controls, then separated destructive actions.
|
|
15
15
|
- Rectangular grids require genuine spatial or coordinate correspondence. Vertical continuity may justify many rows; non-spatial button walls should paginate or group.
|
|
16
16
|
|
|
@@ -32,4 +32,6 @@ Keep trivial state in conversation. Persist a small human-auditable artifact whe
|
|
|
32
32
|
|
|
33
33
|
Evaluate repeated clicks against current state, not stale button appearance. Preserve tap-ahead when the transport queues each click independently. In source-then-destination interaction, retain the source selection without duplicating the whole surface; regenerate after a completed transition, invalid input, or evidence that the transport cannot preserve the intermediate view.
|
|
34
34
|
|
|
35
|
-
Omit unavailable controls when layout does not matter. Preserve occupied or selected cells when spatial topology depends on stable coordinates.
|
|
35
|
+
Omit unavailable controls when layout does not matter. Preserve occupied or selected cells when spatial topology depends on stable coordinates, using the transport's disabled state without a fabricated prompt. Keep a useful enabled navigation or inspection action.
|
|
36
|
+
|
|
37
|
+
Place each control group beside the content it governs when in-body blocks are supported; keep global navigation and whole-view actions in the footer. Do not duplicate one action in both positions merely for visibility. Row topology and current-state validation stay the same across compact and named representations and across placements; renderer limits and selection feedback belong to the transport.
|
|
@@ -29,9 +29,9 @@ Generated Control Surface → current context → model → one ephemeral surfac
|
|
|
29
29
|
Generative App → model → reusable program → many evolving surfaces
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
Both Skills use the same logical button matrix and `label + prompt` interaction model. The Telegram runtime owns its full JSON/CML notation and callback routing; this Skill owns reusable program judgment, while `generated-control-surface` owns ephemeral agent-authored composition. Shared rendering needs no third button Skill and does not collapse those responsibilities.
|
|
32
|
+
Both Skills use the same logical button matrix and `label + prompt` interaction model. An app may place groups beside their related content using the transport's in-body button blocks or keep whole-view controls in the footer; placement does not change binding, disabled state, or method authority. The Telegram runtime owns its full JSON/CML notation and callback routing; this Skill owns reusable program judgment, while `generated-control-surface` owns ephemeral agent-authored composition. Shared rendering needs no third button Skill and does not collapse those responsibilities.
|
|
33
33
|
|
|
34
|
-
An app may mix deterministic `app::method` controls and ordinary prompts in one view. Compile only the stable transitions that benefit from inference bypass; keep explanation, interpretation, teaching, and adaptation on the model-mediated plane. When no reusable state or deterministic loop earns a script, load and use `generated-control-surface` instead.
|
|
34
|
+
An app may mix deterministic `app::method` controls and ordinary prompts in one view, including one control group. Compact and named cells may coexist under the shared transport grammar; neither representation nor placement creates another action plane. Compile only the stable transitions that benefit from inference bypass; keep explanation, interpretation, teaching, and adaptation on the model-mediated plane. When no reusable state or deterministic loop earns a script, load and use `generated-control-surface` instead.
|
|
35
35
|
|
|
36
36
|
The `generated` / `generative` distinction is intentional. Do not rename `generated-control-surface` to a competing generative term.
|
|
37
37
|
|
|
@@ -76,7 +76,7 @@ Another capability remains the authoritative real owner. The app stores validate
|
|
|
76
76
|
1. Identify the repeated feedback loop, real state owner, and actions that are truly deterministic.
|
|
77
77
|
2. Choose one stable lowercase app and one self-contained `<app>.mjs` source outside the managed installation directory.
|
|
78
78
|
3. Keep `init` and every exported method small, named, bounded, shell-free, and capability-specific.
|
|
79
|
-
4. Render one complete next view after each action.
|
|
79
|
+
4. Render one complete next view after each action. Derive disabled controls from current state using the shared transport contract; visible disabled controls neither invoke methods nor enqueue prompts. Revalidate domain preconditions when an enabled control is invoked, because previously rendered views may be stale.
|
|
80
80
|
5. Mix action planes intentionally:
|
|
81
81
|
|
|
82
82
|
```text
|
|
@@ -107,6 +107,7 @@ Before presenting an app as working:
|
|
|
107
107
|
- Inspect the installed initial view and persisted bounded state.
|
|
108
108
|
- Exercise at least one real bound action and prove it bypasses Pi queue/model admission.
|
|
109
109
|
- Exercise at least one ordinary prompt when the app intentionally uses the model plane.
|
|
110
|
+
- Verify the placements actually used by the app and prove disabled cells invoke nothing. A successful ordinary prompt-button smoke is not evidence of app-method dispatch or stale-revision rejection.
|
|
110
111
|
- Verify replacement rejects stale buttons and failed initialization preserves the prior app.
|
|
111
112
|
- For adapters, prove fresh external status and terminal mutation evidence.
|
|
112
113
|
- Confirm failures are bounded, redacted, and do not silently render success.
|
|
@@ -15,7 +15,8 @@ Use Telegram as a mobile companion to the current Pi session. Preserve the exact
|
|
|
15
15
|
| Attach a requested file to the current turn | `telegram_attach(path)` without targeting |
|
|
16
16
|
| Explicitly send from local/TUI to Telegram | `telegram_message` or `telegram_attach` |
|
|
17
17
|
| Explicitly send to a different live Thread | `telegram_message(thread=...)` |
|
|
18
|
-
| Add
|
|
18
|
+
| Add buttons | Hidden comment for footer; `telegram_button` fence for in-body rows |
|
|
19
|
+
| Add explicit voice | Top-level hidden action comment |
|
|
19
20
|
| Build a repeated deterministic interaction | Follow `generative-apps` |
|
|
20
21
|
|
|
21
22
|
A connected Telegram session proves capability, not user intent. Use Telegram features on Telegram-originated turns or explicit Telegram delivery requests only. Never call `telegram_message` for the current active target.
|
|
@@ -39,7 +40,7 @@ Reply in concise, phone-width Telegram Rich Markdown. Use `$...$` and `$$...$$`
|
|
|
39
40
|
|
|
40
41
|
## Assistant Actions
|
|
41
42
|
|
|
42
|
-
`telegram_button` and `telegram_voice` are
|
|
43
|
+
`telegram_button` and `telegram_voice` are markup, not tools. Emit action comments at column zero outside lists, quotes, code, and indentation. Comments create footer buttons or voice artifacts. For buttons between paragraphs, use a column-zero triple-backtick `telegram_button` block. Both button wrappers accept the same singleton JSON/CML cell or mixed matrix; the wrapper determines placement. Telegram removes every assistant-authored HTML comment from previews and final replies regardless of owner or Markdown position; only recognized top-level wrappers activate actions; comment-only output sends no text message.
|
|
43
44
|
|
|
44
45
|
### Shared Encoding Rule
|
|
45
46
|
|
|
@@ -48,25 +49,35 @@ Choose the least verbose sufficient representation:
|
|
|
48
49
|
1. Positional CML — default.
|
|
49
50
|
2. JSON — only when multiline content, named fields, or escaping earns it.
|
|
50
51
|
|
|
51
|
-
CML trims atom boundaries and decodes `\|`, `\}`, and `\\`. Keep
|
|
52
|
+
CML trims atom boundaries and decodes `\|`, `\}`, and `\\`. Keep each payload inside one complete wrapper.
|
|
52
53
|
|
|
53
54
|
### Prompt Buttons
|
|
54
55
|
|
|
55
|
-
Every button has a self-contained prompt and an optional selection style. Use a short distinct `emoji + space + text` label when separate human-readable labeling adds meaning; established coordinates or symbolic tokens may use the prompt itself as visible text. A click creates an ordinary user request; it never grants authority or bypasses confirmation.
|
|
56
|
+
Every enabled button has a self-contained prompt and an optional selection style. Use a short distinct `emoji + space + text` label when separate human-readable labeling adds meaning; established coordinates or symbolic tokens may use the prompt itself as visible text. A click creates an ordinary user request; it never grants authority or bypasses confirmation.
|
|
56
57
|
|
|
57
58
|
- `{prompt}` uses the same text for label and prompt.
|
|
58
59
|
- `{|prompt}` omits a separately authored label and uses the prompt as both visible text and queued prompt.
|
|
59
60
|
- `{label|prompt}` separates visible label from queued prompt.
|
|
60
61
|
- `{label|prompt|selected_style}` and `{|prompt|selected_style}` accept `primary`, `success`, or `danger`.
|
|
62
|
+
- Fourth-position `1`/`true` disables, `0`/`false` enables; omission means enabled. JSON uses boolean `disabled`. `{|Next||1}` omits label/style; `{Next|||1}` omits prompt/style; `{|||1}` is blank (Telegram receives a non-breaking space). Prefer meaningful labels and retain a useful enabled action. Enabled CML requires a prompt. Disabled controls stay visible but have no callback, queued prompt, or bound-method invocation.
|
|
61
63
|
- Top-level cells form vertical rows; one nested row groups horizontal peers.
|
|
62
|
-
- Prefer one matrix
|
|
64
|
+
- Prefer one matrix per related group. Fenced blocks stay in place in Rich mode; HTML compatibility moves them to the footer. Native rows allow eight buttons. Malformed/oversized/unclosed blocks activate nothing; drafts hide them. Outer code fences and quoted/indented examples remain literal.
|
|
65
|
+
- Both placements share prompt/app routing. In-body clicks acknowledge without recoloring; selected-style highlighting remains footer-only.
|
|
66
|
+
|
|
67
|
+
A single in-body button (the four-backtick wrapper below makes this a literal example):
|
|
68
|
+
|
|
69
|
+
````markdown
|
|
70
|
+
```telegram_button
|
|
71
|
+
{📖 Details|Explain this section.}
|
|
72
|
+
```
|
|
73
|
+
````
|
|
63
74
|
|
|
64
75
|
```html
|
|
65
76
|
<!-- telegram_button [{▶️ Continue|Continue the current plan.}[{✅ Approve|Approve this.}{❌ Reject|Reject this.}]] -->
|
|
66
77
|
<!-- telegram_button {"label":"💡 Explain","prompt":"Explain this.\nInclude the risks."} -->
|
|
67
78
|
```
|
|
68
79
|
|
|
69
|
-
Proactively use `generated-control-surface` whenever controls can materially shorten likely feedback; once active, it must emit useful buttons rather than prose alone. That Skill owns action composition; this Skill owns Telegram serialization and delivery.
|
|
80
|
+
Proactively use `generated-control-surface` whenever controls can materially shorten likely feedback; once active, it must emit useful buttons rather than prose alone. That Skill owns action composition; this Skill owns Telegram serialization and delivery. Footer-only replies receive the standard choice heading.
|
|
70
81
|
|
|
71
82
|
### Voice
|
|
72
83
|
|
|
@@ -113,6 +124,6 @@ Before replying:
|
|
|
113
124
|
|
|
114
125
|
- Use the ordinary path for the current target and direct tools only for explicit other delivery.
|
|
115
126
|
- Attach requested files rather than only mentioning them.
|
|
116
|
-
- Keep action comments top-level, complete, and canonical: CML first, JSON when necessary.
|
|
117
|
-
- Give every button a self-contained prompt; preserve confirmation for dangerous actions.
|
|
127
|
+
- Keep action comments and button fences top-level, complete, and canonical: CML first, JSON when necessary; either syntax may coexist within one matrix.
|
|
128
|
+
- Give every enabled button a self-contained prompt; preserve confirmation for dangerous actions.
|
|
118
129
|
- Expose no secret or hidden reasoning.
|
|
@@ -4,11 +4,13 @@ Read this reference only when diagnosing Telegram bridge health or delivery fail
|
|
|
4
4
|
|
|
5
5
|
Inspect in this order:
|
|
6
6
|
|
|
7
|
-
1.
|
|
8
|
-
2.
|
|
7
|
+
1. `/telegram-status` for compact health in the Pi TUI.
|
|
8
|
+
2. `/telegram-status --debug` for bounded human-readable diagnostics in the Pi TUI.
|
|
9
9
|
3. `~/.pi/agent/tmp/telegram/state.json` and `logs.jsonl` for default-profile redacted evidence.
|
|
10
10
|
4. `state.<profile>.json` and `logs.<profile>.jsonl` for a named profile.
|
|
11
11
|
|
|
12
|
+
These slash commands are registered Pi commands, not shell executables or agent tools. If the agent cannot invoke them through a supported Pi surface, read the diagnostic files directly; do not run them in Bash or inject terminal input.
|
|
13
|
+
|
|
12
14
|
When `PI_CODING_AGENT_DIR` selects another compatible runtime, resolve its equivalent `tmp/telegram` directory.
|
|
13
15
|
|
|
14
16
|
Do not mutate ownership files, bridge state, journals, bindings, or locks to force recovery. Use supported commands and preserve exact profile, target, transport, and session authority. Never claim successful delivery without transport evidence.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# ABCd Context Project Context
|
|
2
|
+
|
|
3
|
+
## Meta-Protocol Principles
|
|
4
|
+
|
|
5
|
+
- `Self-Reference`: This skill validates its own context graph with its own runtimes and regression fixture.
|
|
6
|
+
- `Root State Separation`: Durable protocol, open work, completed delivery, README entrypoints, and subsystem docs keep distinct ownership.
|
|
7
|
+
- `Constraint-Driven Evolution`: Structure grows from observed coordination pressure, not template ceremony.
|
|
8
|
+
- `Project Neutrality`: Portable rules remain independent of repository stacks and local overlays.
|
|
9
|
+
- `Single Runtime`: The Node validator provides one authoritative behavior and supported execution path.
|
|
10
|
+
- `Cross-Platform`: Validator behavior runs wherever the supported Node runtime runs.
|
|
11
|
+
|
|
12
|
+
## Operating Principles
|
|
13
|
+
|
|
14
|
+
- Keep `SKILL.md` as the operating kernel; place resolution, lifecycle, and consolidation detail in `docs/protocols.md`.
|
|
15
|
+
- Keep `BACKLOG.md` limited to real open, gated, or blocked work and `CHANGELOG.md` limited to completed outcomes.
|
|
16
|
+
- Keep root `README.md` connected to `AGENTS.md`, `BACKLOG.md`, `CHANGELOG.md`, and `docs/README.md` when those surfaces exist.
|
|
17
|
+
- Keep subtree README entrypoints reachable once they become real human starting points.
|
|
18
|
+
- Use `scripts/validate-context.mjs` for audits and `scripts/_self-test.mjs` for regression.
|
|
19
|
+
- Treat warnings as evidence requiring judgment, not as automatic failure or automatic approval.
|
|
20
|
+
|
|
21
|
+
## Protocol Constraints
|
|
22
|
+
|
|
23
|
+
- `Farmville Guard`: Skip context mutation when it would not preserve truth, prevent drift, record meaningful delivery, or repair discoverability.
|
|
24
|
+
- `Activation Symmetry`: Default post-task activation performs post-task reconciliation; it must not imply retroactive pre-task ceremony.
|
|
25
|
+
- `Template Proportionality`: Lean projects receive lean structure; mature hierarchy requires real complexity.
|
|
26
|
+
- `State Ownership`: The same reality must not remain simultaneously durable, open, and completed.
|
|
27
|
+
- `README Continuity`: Setup, usage, topology, ownership, and same-domain entrypoint knowledge belong in the nearest relevant README.
|
|
28
|
+
- `Impact-Oriented History`: Delivery history records outcomes and impact rather than iteration bookkeeping.
|
|
29
|
+
- `Natural-Language Operation`: Do not introduce fictional YAML tracking or formalize obvious senior-engineer behavior without a non-obvious contract.
|
|
30
|
+
- `Local Overlay Boundary`: Project-specific release, architecture, security, and stack gates remain in local overlays.
|
|
31
|
+
|
|
32
|
+
## Validator Constraints
|
|
33
|
+
|
|
34
|
+
- `Single Runtime`: Keep `validate-context.mjs` as the only supported validator implementation.
|
|
35
|
+
- `Anchor Validation`: Preserve underscores while normalizing GitHub-style heading anchors and validate line-reference bounds.
|
|
36
|
+
- `Path Portability`: Resolve paths through Node rather than platform-specific shell utilities.
|
|
37
|
+
- `Human Output`: Emit concise readable logs and a summary; `NO_COLOR=1` suppresses color.
|
|
38
|
+
- `Bounded Scanning`: Skip link scanning above the configured byte threshold while validating the surrounding graph.
|
|
39
|
+
- `Compact Tables`: Fail non-compact delimiter cells; require exactly three hyphens with one space inside each pipe, while table rows over 120 characters only warn.
|
|
40
|
+
- `Core Shape Flexibility`: Accept both numbered mature-project sections and compact skill-style durable sections.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.5.3
|
|
4
|
+
|
|
5
|
+
- `Markdown Freedom`: LaTeX syntax is now allowed, while table checks always enforce compact three-hyphen delimiter cells and warn on rows over 120 characters without imposing a maximum. Indented backtick and tilde code fences remain excluded from table validation. Impact: context authors can use mathematical notation and fenced examples while receiving default, noise-focused table formatting guidance.
|
|
6
|
+
|
|
7
|
+
## 1.5.2
|
|
8
|
+
|
|
9
|
+
- `Human-First Validation`: The validator now has one Node implementation and prints classic readable logs by default; JSON output and the Bash runtime are removed. Impact: direct local use and agent calls receive the same concise report without format switches.
|
|
10
|
+
- `Line-Reference Validation`: GitHub-style line-reference links now fail when their target line is absent. Impact: broken Markdown references cannot silently pass validation.
|
|
11
|
+
|
|
12
|
+
## 1.5.1
|
|
13
|
+
|
|
14
|
+
- `JSON-First Validation`: Bash and Node validators now emit stable JSON by default while preserving explicit `--text` output and the existing `--json` alias. Impact: agent, recipe, and CI callers receive machine-readable results without extra flags.
|
|
15
|
+
|
|
16
|
+
## 1.0.20
|
|
17
|
+
|
|
18
|
+
- `Protocol Cohesion`: Consolidated the operating kernel, deep protocols, templates, project context, and human entrypoint around one context ownership model. Impact: activation, routing, reconciliation, and validation now form one proportional workflow without duplicated ceremony.
|
|
19
|
+
- `Self-Reference`: Added the skill's own completed-history surface and expanded regression coverage to validate both the fixture and the skill root across Bash and Node runtimes. Impact: the skill now exercises the context graph it prescribes.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# ABCd Context
|
|
2
|
+
|
|
3
|
+
Portable context protocol for keeping durable rules, open work, completed delivery, human entrypoints, and project docs truthful without generating ceremonial churn.
|
|
4
|
+
|
|
5
|
+
## Context Graph
|
|
6
|
+
|
|
7
|
+
- `AGENTS.md`: Durable protocol and reusable constraints.
|
|
8
|
+
- `BACKLOG.md`: Canonical remaining work.
|
|
9
|
+
- `CHANGELOG.md`: Completed outcomes and impact.
|
|
10
|
+
- `README.md` tree: Human entrypoints and navigation.
|
|
11
|
+
- `docs/README.md` + `/docs`: Indexed subsystem and contract knowledge.
|
|
12
|
+
|
|
13
|
+
## Validation
|
|
14
|
+
|
|
15
|
+
The Node validator prints human-readable results wherever the supported Node runtime runs.
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
node ./scripts/validate-context.mjs /path/to/project
|
|
19
|
+
node ./scripts/_self-test.mjs
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Warnings require review but do not fail validation. Errors return a nonzero exit.
|
|
23
|
+
|
|
24
|
+
## Start Here
|
|
25
|
+
|
|
26
|
+
- [Skill Definition](./SKILL.md)
|
|
27
|
+
- [Project Context](./AGENTS.md)
|
|
28
|
+
- [Open Backlog](./BACKLOG.md)
|
|
29
|
+
- [Changelog](./CHANGELOG.md)
|
|
30
|
+
- [Documentation](./docs/README.md)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: abcd-context
|
|
3
|
+
description: ABCd context protocol across ABC root files (AGENTS.md, BACKLOG.md, CHANGELOG.md), human entrypoint README.md files tree, and /docs. Use after meaningful project changes, backlog drift, documentation refactors, self-evolving context maintenance, or a forced context reconciliation pass.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ABCd Context
|
|
7
|
+
|
|
8
|
+
Keep project context truthful, separated, connected, and small enough to use. Treat `AGENTS.md`, `BACKLOG.md`, `CHANGELOG.md`, the README tree, and `/docs` as one context graph with distinct ownership.
|
|
9
|
+
|
|
10
|
+
## Core Guarantee
|
|
11
|
+
|
|
12
|
+
Durable rules stay durable. Open work stays open. Completed delivery moves to history. README entrypoints remain trustworthy. Docs remain discoverable. Each fact has one authoritative home.
|
|
13
|
+
|
|
14
|
+
## Activation Modes
|
|
15
|
+
|
|
16
|
+
- `POST_TASK` (default): Reconcile context after meaningful reality changed. Do not add pre-task ceremony retroactively.
|
|
17
|
+
- `ON_REQUEST`: Run a full context audit and reconciliation when the user explicitly asks.
|
|
18
|
+
- `ALWAYS_ON` (opt-in): Monitor context continuously only when explicitly requested; otherwise its attention cost exceeds its value.
|
|
19
|
+
|
|
20
|
+
Skip the skill for conversational work, trivial changes, or tasks that produced no durable context change.
|
|
21
|
+
|
|
22
|
+
## Context Model
|
|
23
|
+
|
|
24
|
+
- `README.md`: Human entrypoint, setup, usage, topology, and navigation.
|
|
25
|
+
- Subtree `README.md`: Local human entrypoint for a real subsystem, workspace, package, or tool.
|
|
26
|
+
- `AGENTS.md`: Durable protocol, constraints, conventions, architecture memory, and recurring operating rules.
|
|
27
|
+
- `BACKLOG.md`: Canonical remaining open, gated, or blocked work. Accepted aliases: `TODO.md`, `PLAN.md`, `ROADMAP.md` when project convention already owns them.
|
|
28
|
+
- `CHANGELOG.md`: Completed delivery and impact, unless the project already has another canonical shipped-history surface.
|
|
29
|
+
- `docs/README.md`: Documentation index.
|
|
30
|
+
- `docs/*`: Product, subsystem, architecture, operations, and contract documentation.
|
|
31
|
+
|
|
32
|
+
Detailed resolution, adaptation, consolidation, and overlay rules live in [`docs/protocols.md`](./docs/protocols.md). Templates live in [`docs/templates.md`](./docs/templates.md).
|
|
33
|
+
|
|
34
|
+
## Hierarchical Backlog Batching
|
|
35
|
+
|
|
36
|
+
For non-trivial work, model the plan as `outcome or epic → acceptance checkpoint → owned work item`, not one long flat sequence.
|
|
37
|
+
|
|
38
|
+
- Create a checkpoint only when several compatible changes share one meaningful integration boundary and can form a compiling, bisectable candidate. Keep one-shot work flat.
|
|
39
|
+
- Group by coupled invariant, ownership boundary, or acceptance evidence rather than file proximity or arbitrary item count. Keep conflicting transitions, unrelated domains, and externally gated work in separate checkpoints.
|
|
40
|
+
- Give every work item one focused falsification route: a named test family, filter, audit, or explicit evidence owner. A shared test names the smallest item set whose interaction it proves.
|
|
41
|
+
- During edits, prefer focused item validation. Run aggregate package/runtime checks once when the checkpoint candidate forms; reserve full release validation for the highest boundary that actually needs it.
|
|
42
|
+
- Keep dependency order inside a checkpoint only where correctness requires it. Independent items may proceed in parallel without turning the backlog into a false linear pipeline.
|
|
43
|
+
- Treat the checkpoint as an acceptance and context boundary, not a second source of task truth. Child items own concrete remaining work; the checkpoint owns only the shared exit condition.
|
|
44
|
+
- Close or narrow children as reality changes. Remove the checkpoint when its aggregate exit condition passes, and move meaningful impact to delivery history.
|
|
45
|
+
|
|
46
|
+
## Invariants
|
|
47
|
+
|
|
48
|
+
1. `Single source of truth`: Do not track the same state across durable protocol, open work, completed history, and docs.
|
|
49
|
+
2. `Reality before prose`: Inspect the implementation, diff, plans, and existing conventions before updating context.
|
|
50
|
+
3. `Constraint-driven structure`: Add hierarchy only when discovered constraints justify it; restructure inherited context when accidental shape hides truth.
|
|
51
|
+
4. `Hierarchical execution`: Group compatible non-trivial work under acceptance checkpoints with attributable item-level evidence; do not force independent work into a false sequence or replay broad gates after every edit.
|
|
52
|
+
5. `Human entrypoint continuity`: Keep root and relevant subtree README files reachable and current when setup, usage, topology, or ownership changes.
|
|
53
|
+
6. `Backlog truth`: A completed or narrowed slice must not remain falsely open.
|
|
54
|
+
7. `Impact-oriented history`: Record meaningful delivered outcomes, not bookkeeping or duplicated commit logs.
|
|
55
|
+
8. `Local convention compatibility`: Cooperate with stricter project overlays; do not copy project-specific gates into this portable protocol.
|
|
56
|
+
9. `Farmville guard`: If an edit would not preserve durable wisdom, correct open-work truth, record meaningful delivery, or repair discoverability, skip it. Silence is valid.
|
|
57
|
+
|
|
58
|
+
## POST_TASK Flow
|
|
59
|
+
|
|
60
|
+
Run one proportional reconciliation pass:
|
|
61
|
+
|
|
62
|
+
1. `Assess`: Did the task materially change behavior, architecture, public contracts, open-work truth, setup, topology, or reusable operating knowledge? If not, stop.
|
|
63
|
+
2. `Inspect`: Read the touched context surfaces, relevant diff, canonical plan, nearest README entrypoints, and existing shipped-history convention.
|
|
64
|
+
3. `Reconcile open work`: Close, narrow, split, retarget, defer, gate, or block affected backlog items so only real remaining work stays open.
|
|
65
|
+
4. `Route knowledge`:
|
|
66
|
+
- Setup, usage, topology, navigation, ownership → nearest `README.md`.
|
|
67
|
+
- Durable reusable constraint → `AGENTS.md`.
|
|
68
|
+
- Remaining executable work → canonical backlog.
|
|
69
|
+
- Meaningful completed outcome → `CHANGELOG.md` or established equivalent.
|
|
70
|
+
- Product/subsystem/architecture contract → indexed `/docs` document.
|
|
71
|
+
5. `Consolidate and connect`: Merge duplicates, remove stale statements, preserve one authoritative home, update navigation, and keep docs index coverage honest.
|
|
72
|
+
6. `Validate`: When context files changed, run the project convention first, otherwise run `validate-context`. Review warnings rather than treating exit code alone as proof.
|
|
73
|
+
7. `Report`: Name changed context files, validation result, and any unresolved drift. If no update passed the Farmville guard, report that no context mutation was needed.
|
|
74
|
+
|
|
75
|
+
Use a light pass for small scoped changes. Use the full protocol in [`docs/protocols.md`](./docs/protocols.md) for broad context refactors, architecture changes, or multi-surface drift.
|
|
76
|
+
|
|
77
|
+
## ON_REQUEST Flow
|
|
78
|
+
|
|
79
|
+
1. Map the current context graph and canonical files.
|
|
80
|
+
2. Run both automated validation and manual truth checks.
|
|
81
|
+
3. Compare claims against repository reality.
|
|
82
|
+
4. Reconcile state ownership, stale content, duplicates, missing entrypoints, links, backlog drift, and docs coverage.
|
|
83
|
+
5. Restructure only where current shape obstructs navigation or truth.
|
|
84
|
+
6. Rerun validation and report remaining uncertainty.
|
|
85
|
+
|
|
86
|
+
## Mutation Rules
|
|
87
|
+
|
|
88
|
+
- Read the existing format before writing and match it when it remains honest.
|
|
89
|
+
- Prefer targeted reconciliation over broad rewrites.
|
|
90
|
+
- Use templates for new/empty files and as restructuring targets for drifted files, not as mandatory ceremony.
|
|
91
|
+
- Keep prose unwrapped unless project tooling requires wrapping.
|
|
92
|
+
- Prefer label bullets over definition tables.
|
|
93
|
+
- Do not create missing root files automatically when project convention is ambiguous; ask or preserve the established equivalent.
|
|
94
|
+
- Do not claim an item completed from documentation alone; verify reality.
|
|
95
|
+
- Do not let generated context exceed the mistakes or navigation cost it prevents.
|
|
96
|
+
|
|
97
|
+
## Validation
|
|
98
|
+
|
|
99
|
+
Run the Node validator from the project root or pass an explicit root:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
node "${SKILL_DIR}/scripts/validate-context.mjs" /path/to/project
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Output is human-readable. Markdown table checks always require compact delimiter cells such as `| --- | ---: | :--- |`; non-compact delimiters fail validation, while rows longer than 120 characters only warn without imposing a maximum length.
|
|
106
|
+
|
|
107
|
+
Useful environment controls:
|
|
108
|
+
|
|
109
|
+
- `VALIDATE_CONTEXT_ROOT=/path` as an environment fallback.
|
|
110
|
+
- `NO_COLOR=1` for logs consumed by CI or agents.
|
|
111
|
+
|
|
112
|
+
Exit `0` means no errors; warnings may still require judgment. Exit `1` means validation errors or invalid invocation. Validator design and check semantics live in [`docs/validation-design.md`](./docs/validation-design.md).
|
|
113
|
+
|
|
114
|
+
## Completion Checklist
|
|
115
|
+
|
|
116
|
+
- [ ] Canonical open work matches reality.
|
|
117
|
+
- [ ] Completed delivery left the backlog and reached the canonical history surface when meaningful.
|
|
118
|
+
- [ ] Durable constraints live in `AGENTS.md`, not delivery history.
|
|
119
|
+
- [ ] Relevant README entrypoints still explain and connect the changed area.
|
|
120
|
+
- [ ] Docs describe current contracts and remain indexed.
|
|
121
|
+
- [ ] Duplicate or stale context was removed rather than layered over.
|
|
122
|
+
- [ ] Automated validation ran after context mutation, and warnings were reviewed.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Documentation Index (abcd-context)
|
|
2
|
+
|
|
3
|
+
Living index of all documentation in the `/docs` directory for the ABCd context protocol (`ABC + README tree + /docs`).
|
|
4
|
+
|
|
5
|
+
## Documents
|
|
6
|
+
|
|
7
|
+
- [`protocols.md`](./protocols.md): Adaptation rules, lifecycle management, and validation checklist.
|
|
8
|
+
- [`templates.md`](./templates.md): Entry templates for index files, docs, and changelog.
|
|
9
|
+
- [`validation-design.md`](./validation-design.md): Design and checks of `validate-context`.
|