@tinoy/pi-canon 0.3.0 → 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.
Files changed (3) hide show
  1. package/README.md +2 -1
  2. package/index.ts +22 -21
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -16,7 +16,8 @@ pi install npm:@tinoy/pi-canon
16
16
  - **Tools** — `canon_add`, `canon_remove`, `canon_edit`, `canon_category`.
17
17
  - **Commands** — `/canon` (list, add, remove, edit, category management) and `/canon-dump`.
18
18
  - **Injection** — the block is appended at `before_agent_start` and re-normalized on every provider request, so a run started by an injected message carries the same bytes as an interactive prompt.
19
- - **Peer notices** — entry changes are broadcast over the pi-intercom bus (namespace `canon`); receivers match the entry scope against their own model and audience.
19
+ - **Category headings** — a category sub-heading inside a scope group prints the store id after the word `category` (`#### Behavioural Preferences [category 1cg5lr]`), which is the same id a `canon_add` refusal lists; the word keeps it from reading as an entry handle, which the block renders as `[1i15c2]`. Uncategorized entries carry no id, and a scope group holding a single category stays flat with no sub-heading.
20
+ - **Peer notices** — entry changes are published over the `ipc` transport's bus (namespace `canon`); receivers match the entry scope against their own model and audience.
20
21
  - **Tail sections** — another extension contributes prompt text through the `canon:section` event; its ids are published on `canon:sections`.
21
22
 
22
23
  ## Exports
package/index.ts CHANGED
@@ -14,7 +14,7 @@
14
14
  * changes via canon notices (and the user's /canon-dump).
15
15
  * Edited at runtime via canon_add / canon_remove / canon_edit (plus
16
16
  * the /canon and /canon-dump commands). Changes are broadcast to peer sessions
17
- * over the pi-intercom extension bus (namespace "canon"); each receiver matches
17
+ * over the `ipc` transport's extension bus (namespace "canon"); each receiver matches
18
18
  * the entry scope against its own model + audience before showing a notice.
19
19
  *
20
20
  * Replaces APPEND_SYSTEM.md (global/parent) and FLASH.md (global/subagent).
@@ -31,11 +31,13 @@
31
31
  *
32
32
  * Store: ~/.pi/agent/canon/canon.json — { entries: [{id, text, model, audience, reason?, category?}], categories: [{id, title, description?}] }
33
33
  * Categories are un-ordered; entries reference them by id. They render as sub-headings
34
- * inside scope groups (store insertion order), with uncategorized entries last.
34
+ * inside scope groups (store insertion order), with uncategorized entries last, and
35
+ * each heading carries the store's own id after the word `category`
36
+ * (`#### Behavioural Preferences [category 1cg5lr]`) — the word keeps the category id
37
+ * from reading as an entry handle, which the block renders as `[1i15c2]`.
35
38
  * canon_add REQUIRES a category and canon_edit can only CHANGE one: every refusal
36
- * lists the valid ids with their titles, so a retry costs no lookup call. The
37
- * injected block renders titles only, so the refusal is also the only place an id
38
- * reaches the model. The store validator counts entries that are uncategorized or
39
+ * lists the valid ids with their titles, so a retry costs no lookup call. The store
40
+ * validator counts entries that are uncategorized or
39
41
  * carry a category id the store no longer holds (both render as Uncategorized) and
40
42
  * reports the count to the hook log once per distinct state.
41
43
  * Both the injected block and /canon-dump group entries under scope headers (model ×
@@ -339,7 +341,8 @@ function scopeGroups(entries: CanonEntry[]): ScopeGroup[] {
339
341
  );
340
342
  }
341
343
 
342
- /** Render the injected block: header, scope groups, category sub-headings inside. */
344
+ /** Render the injected block: header, scope groups, category sub-headings inside, each
345
+ * naming its store category id. */
343
346
  function render(
344
347
  store: Store,
345
348
  model: string,
@@ -367,7 +370,7 @@ function render(
367
370
  // only show category sub-headings when a scope group actually splits
368
371
  const showCatHeadings = orderedCats.length + (uncat.length ? 1 : 0) >= 2;
369
372
  for (const c of orderedCats) {
370
- if (showCatHeadings) block += `\n\n#### ${c.title}`;
373
+ if (showCatHeadings) block += `\n\n#### ${c.title} [category ${c.id}]`;
371
374
  block += `\n${buckets
372
375
  .get(c.id)!
373
376
  .map((e) => `[${e.id}] ${e.text}`)
@@ -417,11 +420,11 @@ function renderDump(
417
420
  /**
418
421
  * Resolve a category reference to its id.
419
422
  *
420
- * The injected canon block shows category TITLES as sub-headings (`#### System
421
- * Knowledge`) and never shows an id, so a model naturally passes the title back to
422
- * canon_add — refusing that costs a call and teaches nothing. An exact id wins; a
423
- * title matches case-insensitively; anything else fails with a short reason that
424
- * the caller turns into a refusal.
423
+ * The block prints each heading as its title followed by the store id
424
+ * (`#### <title> [category <id>]`), and a model that copies the heading copies the
425
+ * title most of the time, so both forms are accepted: an exact id wins, a title
426
+ * matches case-insensitively, and anything else fails with a short reason that the
427
+ * caller turns into a refusal.
425
428
  */
426
429
  function lookupCategory(
427
430
  store: Store,
@@ -447,10 +450,8 @@ function lookupCategory(
447
450
 
448
451
  /**
449
452
  * The refusal for every rejected category value: what failed, then every valid id
450
- * with its title so a retry needs no lookup call of its own, then the one
451
- * instruction that answers it. Terse by construction — it lands in a model's
452
- * context, and the listing is the only place a category id is ever shown (the
453
- * injected block renders titles).
453
+ * with its title, then the one instruction that answers it. Terse by construction —
454
+ * it lands in a model's context.
454
455
  */
455
456
  function categoryRefusal(
456
457
  store: Store,
@@ -713,8 +714,8 @@ export default function (pi: ExtensionAPI) {
713
714
 
714
715
  // Best-effort peer notice: the store write has already landed when this runs,
715
716
  // so EVERY failure mode of the channel call is swallowed here and logged:
716
- // - pi-intercom 0.12.1's channel.publish is SYNCHRONOUS and throws
717
- // "Intercom is not connected" when the broker client is down;
717
+ // - the channel's publish may throw SYNCHRONOUSLY when the transport is
718
+ // down, and returns void, so there is no promise to catch;
718
719
  // - it returns void, so .catch must only ever be reached through the
719
720
  // optional call (chaining it directly was the TypeError that escaped);
720
721
  // - a promise-returning build rejects instead of throwing — the guarded
@@ -747,7 +748,7 @@ export default function (pi: ExtensionAPI) {
747
748
  }
748
749
  }
749
750
 
750
- // ---------- intercom channel (mirrors the intercom-broadcast package) ----------
751
+ // ---------- the bus channel (mirrors the ipc package's registration) ----------
751
752
 
752
753
  const registration: CanonRegistration = {
753
754
  namespace: NAMESPACE,
@@ -792,7 +793,7 @@ export default function (pi: ExtensionAPI) {
792
793
  pi.events.emit("intercom:extension-register", registration);
793
794
  }
794
795
 
795
- // pi-intercom may load after this extension; re-emit once its registry is
796
+ // The registrar may load after this extension; re-emit once the registry is
796
797
  // reported ready. First successful registration wins (duplicate namespace
797
798
  // is rejected, not thrown).
798
799
  pi.events.on("intercom:extension-registry-ready", () => {
@@ -1243,7 +1244,7 @@ export default function (pi: ExtensionAPI) {
1243
1244
  name: "canon_category",
1244
1245
  label: "Manage canon categories",
1245
1246
  description:
1246
- "Manage canon categories: op add (title, description?) creates one; op edit (id, title?, description?) updates it; op remove (id) deletes it and detaches its entries to Uncategorized; op list shows all. Categories are un-ordered and render as sub-headings inside scope groups in store insertion order.",
1247
+ "Manage canon categories: op add (title, description?) creates one; op edit (id, title?, description?) updates it; op remove (id) deletes it and detaches its entries to Uncategorized; op list shows all. Categories are un-ordered and render as sub-headings inside scope groups in store insertion order, each heading naming the category id (`#### Tools [category u3q9pr]`).",
1247
1248
  promptSnippet: "Manage canon categories (add/edit/remove/list)",
1248
1249
  parameters: Type.Object({
1249
1250
  op: Type.String({ description: "add | edit | remove | list" }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tinoy/pi-canon",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Durable system-prompt rules for pi: an on-disk canon store, its tools, and the /canon commands.",
5
5
  "license": "MIT",
6
6
  "repository": {