total-recall-brain 3.40.6 → 3.41.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 (170) hide show
  1. package/bin/total-recall-memory.mjs +0 -0
  2. package/bin/total-recall.mjs +9 -40
  3. package/docs/reference/cli-reference.md +65 -1
  4. package/frontend/dist/assets/{index-AGUes-VG.css → index-BG9NkVUy.css} +1 -1
  5. package/frontend/dist/assets/{index-Bt5iVPZj.js → index-BtuiZqwP.js} +20 -7
  6. package/frontend/dist/index.html +2 -2
  7. package/frontend/src/components/plugins/PluginPanel.tsx +31 -5
  8. package/frontend/src/components/plugins/card/CardEngine.tsx +15 -23
  9. package/frontend/src/components/plugins/card/CardScenes.tsx +52 -23
  10. package/frontend/src/components/plugins/card/card.css +2 -5
  11. package/frontend/src/components/plugins/card/contract.ts +13 -19
  12. package/frontend/src/components/plugins/card/format.ts +8 -1
  13. package/frontend/src/components/plugins/card/widgets.tsx +4 -35
  14. package/frontend/src/components/plugins/plugin-frame.ts +30 -3
  15. package/frontend/src/pages/PluginsPage.tsx +3 -2
  16. package/package.json +1 -1
  17. package/scaffold/.agent/skills/plugin/SKILL.md +110 -0
  18. package/scaffold/.agent/skills/plugin/evals/README.md +53 -0
  19. package/scaffold/.agent/skills/plugin/evals/cases/01-fabricated-seed.mjs +42 -0
  20. package/scaffold/.agent/skills/plugin/evals/cases/02-reassuring-default.mjs +39 -0
  21. package/scaffold/.agent/skills/plugin/evals/cases/03-declared-template.mjs +32 -0
  22. package/scaffold/.agent/skills/plugin/evals/cases/04-card-collects-credential.mjs +46 -0
  23. package/scaffold/.agent/skills/plugin/evals/cases/05-host-leak-in-scaffold.mjs +55 -0
  24. package/scaffold/.agent/skills/plugin/evals/cases/06-correct-plugin.mjs +92 -0
  25. package/scaffold/.agent/skills/plugin/evals/run.mjs +118 -0
  26. package/scaffold/.agent/skills/plugin/prompts/01-function.md +29 -0
  27. package/scaffold/.agent/skills/plugin/prompts/02-surfaces.md +43 -0
  28. package/scaffold/.agent/skills/plugin/prompts/03-scaffold.md +31 -0
  29. package/scaffold/.agent/skills/plugin/prompts/04-cli.md +49 -0
  30. package/scaffold/.agent/skills/plugin/prompts/05-credentials.md +41 -0
  31. package/scaffold/.agent/skills/plugin/prompts/06-card.md +46 -0
  32. package/scaffold/.agent/skills/plugin/prompts/07-scaffold.md +52 -0
  33. package/scaffold/.agent/skills/plugin/prompts/08-verify.md +43 -0
  34. package/scaffold/.agent/skills/plugin/references/anti-patterns.md +123 -0
  35. package/scaffold/.agent/skills/plugin/references/card.md +90 -0
  36. package/scaffold/.agent/skills/plugin/references/scaffold.md +120 -0
  37. package/scaffold/.agent/skills/plugin/references/surfaces.md +206 -0
  38. package/scaffold/.agent/skills/plugin/scripts/record.mjs +360 -0
  39. package/scaffold/.agent/skills/plugin/subagents/card-designer.md +60 -0
  40. package/scaffold/.agent/skills/plugin/subagents/plugin-reviewer.md +55 -0
  41. package/scaffold/.agent/skills/plugin/subagents/scaffold-extractor.md +71 -0
  42. package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +71 -1441
  43. package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +7 -5
  44. package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +3 -44
  45. package/scaffold/.agent/skills/total-recall/plugins/auth/scripts/agent-login-pty.py +185 -0
  46. package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +20 -1
  47. package/scaffold/.agent/skills/total-recall/plugins/auth/src/host-secrets-store.mjs +15 -0
  48. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-env-export.mjs +6 -4
  49. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-remote-deploy.mjs +1 -0
  50. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/plugin.json +0 -24
  51. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +4 -4
  52. package/scaffold/.agent/skills/total-recall/plugins/code-quality/plugin.json +0 -21
  53. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +4 -4
  54. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +0 -32
  55. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +4 -4
  56. package/scaffold/.agent/skills/total-recall/plugins/embeddings/plugin.json +11 -0
  57. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/README.md +5 -2
  58. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +4 -4
  59. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/plugin.json +1 -28
  60. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/DESIGN.md +95 -13
  61. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/card.js +553 -340
  62. package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +0 -36
  63. package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/card.js +383 -400
  64. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +4 -4
  65. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +0 -1
  66. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +0 -28
  67. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +4 -4
  68. package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +0 -32
  69. package/scaffold/.agent/skills/total-recall/references/auth.md +3 -1
  70. package/scaffold/.agent/skills/total-recall/references/memory.md +4 -0
  71. package/scaffold/.agent/skills/total-recall/references/openwiki.md +2 -0
  72. package/scaffold/.agent/skills/total-recall/references/operations.md +12 -0
  73. package/src/cli/brain.mjs +4 -2
  74. package/src/cli/context-stats.mjs +104 -0
  75. package/src/cli/context.mjs +8 -0
  76. package/src/cli/daemon.mjs +10 -1
  77. package/src/cli/export.mjs +3 -2
  78. package/src/cli/ingest-okf.mjs +12 -15
  79. package/src/cli/init.mjs +45 -85
  80. package/src/cli/lint.mjs +3 -2
  81. package/src/cli/plugin/copies.mjs +68 -0
  82. package/src/cli/plugin/create.mjs +22 -24
  83. package/src/cli/plugin/index.mjs +49 -3
  84. package/src/cli/plugin/search.mjs +19 -2
  85. package/src/cli/rebuild.mjs +2 -0
  86. package/src/cli/remember.mjs +23 -0
  87. package/src/cli/status.mjs +10 -3
  88. package/src/cli/update.mjs +3 -2
  89. package/src/core/action-predictor.mjs +161 -0
  90. package/src/core/app-deploy/runtime-cli/node.mjs.tmpl +20 -3
  91. package/src/core/app-deploy/source.mjs +16 -9
  92. package/src/core/automatic-brain-updates.mjs +6 -3
  93. package/src/core/brain-registry.mjs +19 -2
  94. package/src/core/brain-state.mjs +23 -0
  95. package/src/core/cli-commands.mjs +1 -0
  96. package/src/core/cli-maintenance.mjs +1 -1
  97. package/src/core/cli-semantic-index.mjs +1 -1
  98. package/src/core/command-surface.mjs +3 -3
  99. package/src/core/composed-command-resolution.mjs +21 -0
  100. package/src/core/config.mjs +84 -0
  101. package/src/core/confined-memory.mjs +93 -0
  102. package/src/core/context-telemetry.mjs +214 -0
  103. package/src/core/daemon-control.mjs +14 -43
  104. package/src/core/fast-recall.mjs +1 -1
  105. package/src/core/memory-brain.mjs +2 -0
  106. package/src/core/plugin-activation.mjs +38 -6
  107. package/src/core/plugin-bundle.mjs +54 -0
  108. package/src/core/plugin-catalog.mjs +1 -1
  109. package/src/core/plugin-context.mjs +1 -1
  110. package/src/core/plugin-contracts/card.mjs +69 -37
  111. package/src/core/plugin-loader.mjs +5 -6
  112. package/src/core/plugin-runner.mjs +13 -6
  113. package/src/core/plugin-runtime.mjs +5 -4
  114. package/src/core/plugin-setup.mjs +5 -3
  115. package/src/core/plugin-state-store.mjs +12 -1
  116. package/src/core/plugin-state.mjs +46 -5
  117. package/src/core/plugin-store.mjs +78 -3
  118. package/src/core/plugin-tasks.mjs +3 -1
  119. package/src/core/plugin-updates.mjs +117 -0
  120. package/src/core/plugin-vendor.mjs +10 -4
  121. package/src/core/project-brain.mjs +14 -41
  122. package/src/core/project-id.mjs +4 -1
  123. package/src/core/required-plugins.mjs +151 -13
  124. package/src/core/skills-registry.mjs +27 -3
  125. package/src/core/ssss-kernel-bridge.mjs +3 -1
  126. package/src/core/startup-health.mjs +66 -16
  127. package/src/core/surface.mjs +51 -44
  128. package/src/core/testing/plugin-fixture.mjs +69 -0
  129. package/src/core/vault-files.mjs +13 -0
  130. package/src/core/vault.mjs +4 -30
  131. package/src/server/index.mjs +13 -1
  132. package/src/server/invoke-decision-child.mjs +49 -0
  133. package/src/server/invoke-execution.mjs +87 -0
  134. package/src/server/invoke-policy.mjs +64 -0
  135. package/src/server/invoke-read-child.mjs +26 -0
  136. package/src/server/invoke-targets.mjs +49 -0
  137. package/src/server/invoke-write-child.mjs +19 -0
  138. package/src/server/routes/invoke.mjs +314 -0
  139. package/src/server/routes/plugins.mjs +1 -1
  140. package/scaffold/.agent/skills/plugins/SKILL.md +0 -57
  141. package/scaffold/.agent/skills/plugins/references/card-design.md +0 -55
  142. package/scaffold/.agent/skills/plugins/references/distribution.md +0 -23
  143. package/scaffold/.agent/skills/plugins/references/manifest-contract.md +0 -90
  144. package/scaffold/.agent/skills/plugins/references/optimized/index.md +0 -9
  145. package/scaffold/.agent/skills/plugins/references/optimized/manifest.json +0 -35
  146. package/scaffold/.agent/skills/plugins/references/optimized/original.md +0 -58
  147. package/scaffold/.agent/skills/plugins/references/optimized/section-01.md +0 -5
  148. package/scaffold/.agent/skills/plugins/references/optimized/section-02.md +0 -25
  149. package/scaffold/.agent/skills/plugins/references/optimized/section-03.md +0 -12
  150. package/scaffold/.agent/skills/plugins/references/optimized/section-04.md +0 -7
  151. package/scaffold/.agent/skills/plugins/references/optimized/section-05.md +0 -5
  152. package/scaffold/.agent/skills/total-recall/openwiki/README.md +0 -10
  153. package/scaffold/.agent/skills/total-recall/openwiki/architecture.md +0 -5
  154. package/scaffold/.agent/skills/total-recall/openwiki/ide.md +0 -10
  155. package/scaffold/.agent/skills/total-recall/openwiki/memory.md +0 -11
  156. package/scaffold/.agent/skills/total-recall/openwiki/secrets.md +0 -7
  157. package/scaffold/.agent/skills/total-recall/openwiki/skills.md +0 -7
  158. package/scaffold/openwiki/README.md +0 -10
  159. package/scaffold/openwiki/architecture.md +0 -5
  160. package/scaffold/openwiki/ide.md +0 -10
  161. package/scaffold/openwiki/memory.md +0 -11
  162. package/scaffold/openwiki/secrets.md +0 -7
  163. package/scaffold/openwiki/skills.md +0 -7
  164. package/src/core/okf-adapter.mjs +0 -559
  165. package/templates/openwiki/README.md +0 -10
  166. package/templates/openwiki/architecture.md +0 -5
  167. package/templates/openwiki/ide.md +0 -10
  168. package/templates/openwiki/memory.md +0 -11
  169. package/templates/openwiki/secrets.md +0 -7
  170. package/templates/openwiki/skills.md +0 -7
@@ -0,0 +1,110 @@
1
+ ---
2
+ name: plugin
3
+ description: Author, review or fix a Total Recall plugin (tr-plugin-*). A recipe of exact specifications an agent executes, so the plugin declares the surfaces its function needs, serves agents through its CLI, scaffolds its capability into other applications, and renders a bespoke card face.
4
+ repo_scoped: false
5
+ metadata:
6
+ version: "1.0.0"
7
+ source: total-recall-brain
8
+ ---
9
+
10
+ # The plugin creator
11
+
12
+ A recipe for agents: prompts with exact specifications, executed in order. Each prompt says what to produce and what "correct" means, and each has a runnable check.
13
+
14
+ A plugin adds **one capability** to Total Recall. Core holds portable memory and generic loading; everything else is a plugin.
15
+
16
+ **A plugin is not one shape.** It declares the surfaces its capability needs, and a capability can need almost any combination — memory, context, a CLI, skills, scheduled work, runtime behaviour, a face, deployment, distribution, composition. Some plugins are only a memory category. Some are only a generator. Some are a CLI. Some are several at once. What it is, is decided by what it does.
17
+
18
+ ```
19
+ references/ every field, every rule, and the failure catalogue
20
+ prompts/ the recipe, one prompt per step
21
+ scripts/ the checks, runnable at any point
22
+ subagents/ delegatable reviews and extractions
23
+ evals/ proof that the recipe produces correct plugins
24
+ ```
25
+
26
+ ---
27
+
28
+ ## The invariants
29
+
30
+ True of every plugin, whatever it turns out to be. Check every one before calling it done.
31
+
32
+ 1. **Everything is declared.** If the plugin can do it, the manifest says so. An undeclared operation does not exist to the host or to an agent.
33
+ 2. **Every executable operation declares an effect** — `read`, `write`, `destructive`, `send`. The host uses it to decide what may run read-only; an agent uses it to know what needs confirmation.
34
+ 3. **Machine-facing output is structured.** A command emits JSON under `--json` and exits non-zero on failure, so an agent acts on a result instead of scraping prose.
35
+ 4. **Credentials are declared, never held.** Resolved only through `total-recall auth secret`. Never a second store, never `argv`, never an ambient environment variable, never a log.
36
+ 5. **Real data only.** Every value traces to something that actually returned it. No seeded readings, no placeholders, no example defaults, no reassuring badges. A failure is reported as a failure, with the time of the last attempt.
37
+ 6. **No templates.** Not for the face, and not for the plugin's shape. A face that could be dropped onto a different plugin is a failed face.
38
+ 7. **Every plugin has a card face**, authored by hand from its own function.
39
+ 8. **Fail closed.** A missing credential, a failed provider or an unreachable peer is an explicit error, never a success state.
40
+ 9. **Copy.** No filler words, and no sentence ends with a preposition.
41
+
42
+ ---
43
+
44
+ ## The recipe
45
+
46
+ | # | Prompt | Produce | Check |
47
+ | --- | --- | --- | --- |
48
+ | 1 | [Establish the function](prompts/01-function.md) | the users, the tasks, the dependencies | you can name them |
49
+ | 2 | [Choose the surfaces](prompts/02-surfaces.md) | the manifest's surface set | each declared surface is used |
50
+ | 3 | [Scaffold, then stop generating](prompts/03-scaffold.md) | repository skeleton | it exists, tests run |
51
+ | 4 | [Write the CLI as an agent interface](prompts/04-cli.md) | operations, args, effects, JSON | `record.mjs` passes |
52
+ | 5 | [Declare credentials and settings](prompts/05-credentials.md) | `secrets[]`, `configuration` | one credential path, auth |
53
+ | 6 | [Design the card face by hand](prompts/06-card.md) | `ui/DESIGN.md`, `ui/card.js` | `record.mjs --card` passes |
54
+ | 7 | [Extract the scaffold](prompts/07-scaffold.md) | the capability for another app | runs with Total Recall absent |
55
+ | 8 | [Verify and report](prompts/08-verify.md) | a truthful report | `record.mjs` passes every check |
56
+
57
+ Read [surfaces](references/surfaces.md) before Prompt 2, and [card](references/card.md) before Prompt 6.
58
+
59
+ ---
60
+
61
+ ## Scripts
62
+
63
+ Run from the plugin repository.
64
+
65
+ ```
66
+ node <skill>/scripts/record.mjs . # every check
67
+ node <skill>/scripts/record.mjs . --manifest # manifest only
68
+ node <skill>/scripts/record.mjs . --card # face only
69
+ node <skill>/scripts/record.mjs . --secrets # credential safety only
70
+ node <skill>/scripts/record.mjs . --scaffold # standalone scaffold only
71
+ node <skill>/scripts/record.mjs . --json # machine-readable result
72
+ ```
73
+
74
+ `record.mjs` exits non-zero on any failure and prints the real reason. It is the same battery Prompt 8 asks you to run, so there is nothing to remember.
75
+
76
+ ---
77
+
78
+ ## Subagents
79
+
80
+ Delegate when the work is separable. Each is a prompt file, disposable and repeatable.
81
+
82
+ | Subagent | Use when | Returns |
83
+ | --- | --- | --- |
84
+ | [plugin-reviewer](subagents/plugin-reviewer.md) | a plugin is claimed finished | violations against the invariants, with evidence, or "clean" |
85
+ | [card-designer](subagents/card-designer.md) | the function is known and the face is not written | `ui/DESIGN.md` reasoning and a first `ui/card.js`, or the questions it cannot answer |
86
+ | [scaffold-extractor](subagents/scaffold-extractor.md) | a plugin holds a capability another app could use | capability module design, or "not separable, because…" |
87
+
88
+ The reviewer is adversarial by construction: it looks for fabricated values, host leaks, and a face that would suit any other plugin.
89
+
90
+ ---
91
+
92
+ ## Evals
93
+
94
+ `evals/` proves the recipe works, using plugins whose correct outcome is already known — including the real defects found in this ecosystem.
95
+
96
+ ```
97
+ node evals/run.mjs # run every case
98
+ node evals/run.mjs --case <name>
99
+ ```
100
+
101
+ See [evals](evals/README.md).
102
+
103
+ ---
104
+
105
+ ## References
106
+
107
+ - [Surfaces](references/surfaces.md) — every surface and its exact manifest fields.
108
+ - [Card](references/card.md) — designing a bespoke face, and what it must never do.
109
+ - [Scaffold](references/scaffold.md) — the capability for another application, and distribution.
110
+ - [Anti-patterns](references/anti-patterns.md) — the failure catalogue, every entry observed in a real plugin.
@@ -0,0 +1,53 @@
1
+ # Evals
2
+
3
+ Proof that the recipe and its checks work.
4
+
5
+ Each case is a synthetic plugin whose correct outcome is already known. Five are **real defects observed in this ecosystem**; one is a plugin that follows the recipe and must be **accepted**. A checker that only ever rejects, or only ever accepts, proves nothing.
6
+
7
+ ```
8
+ node evals/run.mjs # every case
9
+ node evals/run.mjs --case <name>
10
+ node evals/run.mjs --json
11
+ node evals/run.mjs --keep # keep the temp fixture for inspection
12
+ ```
13
+
14
+ Exit code is non-zero unless every case behaves as expected.
15
+
16
+ ## How a case is judged
17
+
18
+ A case declares the failures it expects. It passes when `scripts/record.mjs` reports **exactly** those and nothing else:
19
+
20
+ - A defect case passes only if the checker **catches** it. A checker that looks at nothing fails here.
21
+ - The correct-plugin case passes only if there are **no** failures. An over-eager checker fails here.
22
+ - A case whose expectation is empty treats any failure as unexpected.
23
+
24
+ ## The cases
25
+
26
+ | Case | Defect it encodes | Expected |
27
+ | --- | --- | --- |
28
+ | [`fabricated-seed`](cases/01-fabricated-seed.mjs) | a card seeding a telemetry reading — a literal hostname, uptime, load average and `usedPercent: 99.1` before any command ran, commented "Retain fallback telemetry" | `no seeded values` |
29
+ | [`reassuring-default`](cases/02-reassuring-default.mjs) | a card reading three fields its command never returns and defaulting to "Up to date", so a repository with three unpushed commits rendered clean | `no reassuring default` |
30
+ | [`declared-template`](cases/03-declared-template.mjs) | a manifest declaring `card_layout` with a template name and region list — the vocabulary that made 24 of 34 plugins identical | `no template` |
31
+ | [`card-collects-credential`](cases/04-card-collects-credential.mjs) | a card collecting an API key in a password field and dispatching the raw value — generated by the old scaffolder | `never collects a credential`, `never transmits a credential` |
32
+ | [`host-leak-in-scaffold`](cases/05-host-leak-in-scaffold.mjs) | a capability calling the `total-recall` binary for credentials and shipping `https://example.com/checkout/success` | `no Total Recall dependency`, `no placeholder values` |
33
+ | [`correct-plugin`](cases/06-correct-plugin.mjs) | a plugin that follows the recipe: one bespoke face, real data only, credentials resolved through auth in the CLI, and a host-free capability | no failures |
34
+
35
+ ## Adding a case
36
+
37
+ A case is a module exporting `{ name, why, files, expect }`:
38
+
39
+ ```js
40
+ export default {
41
+ name: 'kebab-case-name',
42
+ why: 'the failure mode this encodes, and what it costs',
43
+ files: { 'plugin.json': '…', 'cli.mjs': '…', 'ui/card.js': '…' },
44
+ expect: { fail: ['the check name record.mjs must report'] },
45
+ }
46
+ ```
47
+
48
+ Rules:
49
+
50
+ - `why` states the **cost** of the defect, not just its shape.
51
+ - `expect.fail` lists check names as `record.mjs` prints them. Empty means "must be accepted".
52
+ - Prefer a case grounded in something that actually happened. An invented defect tests the checker against your imagination; a real one tests it against the ecosystem.
53
+ - If a new case cannot be made to fail, the check behind it is decorative — fix the check, not the case.
@@ -0,0 +1,42 @@
1
+ /**
2
+ * A card that seeds a telemetry reading in its constructor.
3
+ *
4
+ * Observed: system-monitor held a literal hostname, uptime, load average and
5
+ * `usedPercent: 99.1` before any command ran, with the comment
6
+ * "// Retain fallback telemetry".
7
+ */
8
+ const CARD = `
9
+ export class SeededCard extends HTMLElement {
10
+ constructor() {
11
+ super();
12
+ this.sample = {
13
+ hostname: 'Gregorys-Mac-mini.local',
14
+ platform: 'darwin',
15
+ uptimeSeconds: 566924,
16
+ loadAvg: [3.34, 3.76, 5.00],
17
+ memory: { total: 17179869184, free: 159334400, usedPercent: 99.1 }
18
+ };
19
+ }
20
+ connectedCallback() { this.attachShadow({ mode: 'open' }) }
21
+ }
22
+ customElements.define('tr-seeded-card', SeededCard)
23
+ `;
24
+
25
+ export default {
26
+ name: 'fabricated-seed',
27
+ why: 'a fabricated reading on the face is the most consequential kind of lie',
28
+ files: {
29
+ 'plugin.json': JSON.stringify({
30
+ id: 'seeded', name: 'Seeded', version: '1.0.0', description: 'A plugin that fabricates a reading.',
31
+ cli: { command: 'seeded', handler: './cli.mjs', subcommands: [{ name: 'sample', effect: 'read' }] },
32
+ status: { command: 'sample', refresh_seconds: 30 },
33
+ branding: { color: '#059669' },
34
+ ui: { elements: [{ id: 'card', kind: 'card', tag: 'tr-seeded-card', module: 'ui/card.js' }] },
35
+ }, null, 2),
36
+ 'cli.mjs': 'export default async function run() {}\n',
37
+ 'ui/card.js': CARD,
38
+ },
39
+ expect: {
40
+ fail: ['no seeded values'],
41
+ },
42
+ };
@@ -0,0 +1,39 @@
1
+ /**
2
+ * A card that reads fields its command does not return, and defaults to a
3
+ * reassurance.
4
+ *
5
+ * Observed: a git card read `data.worktree.unstaged`, `data.synced` and
6
+ * `data.upstream.status`, none of which the command returns, and defaulted to
7
+ * "Up to date" — so a repository with three unpushed commits rendered clean.
8
+ */
9
+ const CARD = `
10
+ export class ReassuringCard extends HTMLElement {
11
+ async read() {
12
+ const data = await this.host.run('audit', ['--json'])
13
+ const isClean = data.worktree?.clean ?? (data.clean ?? false)
14
+ const upstream = data.upstream?.status || (data.synced ? 'Synced' : 'Up to date')
15
+ this.shadowRoot.querySelector('.state').textContent = isClean ? 'Clean' : upstream
16
+ }
17
+ connectedCallback() { this.attachShadow({ mode: 'open' }); this.read() }
18
+ }
19
+ customElements.define('tr-reassuring-card', ReassuringCard)
20
+ `;
21
+
22
+ export default {
23
+ name: 'reassuring-default',
24
+ why: 'an unverified reassurance is worse than no value, because it is believed',
25
+ files: {
26
+ 'plugin.json': JSON.stringify({
27
+ id: 'reassuring', name: 'Reassuring', version: '1.0.0', description: 'A plugin that reassures without checking.',
28
+ cli: { command: 'reassuring', handler: './cli.mjs', subcommands: [{ name: 'audit', effect: 'read' }] },
29
+ status: { command: 'audit', refresh_seconds: 30 },
30
+ branding: { color: '#b45309' },
31
+ ui: { elements: [{ id: 'card', kind: 'card', tag: 'tr-reassuring-card', module: 'ui/card.js' }] },
32
+ }, null, 2),
33
+ 'cli.mjs': 'export default async function run() {}\n',
34
+ 'ui/card.js': CARD,
35
+ },
36
+ expect: {
37
+ fail: ['no reassuring default'],
38
+ },
39
+ };
@@ -0,0 +1,32 @@
1
+ /**
2
+ * A manifest that declares a shared layout template.
3
+ *
4
+ * Observed: core enforced a closed allowlist of seven layouts, and 24 of 34
5
+ * plugins shared a byte-identical card signature.
6
+ */
7
+ const CARD = `
8
+ export class TemplatedCard extends HTMLElement {
9
+ connectedCallback() { this.attachShadow({ mode: 'open' }) }
10
+ }
11
+ customElements.define('tr-templated-card', TemplatedCard)
12
+ `;
13
+
14
+ export default {
15
+ name: 'declared-template',
16
+ why: 'a shared layout vocabulary is the mechanism that made unrelated plugins look alike',
17
+ files: {
18
+ 'plugin.json': JSON.stringify({
19
+ id: 'templated', name: 'Templated', version: '1.0.0', description: 'A plugin that declares a layout template.',
20
+ cli: { command: 'templated', handler: './cli.mjs', subcommands: [{ name: 'status', effect: 'read' }] },
21
+ status: { command: 'status', refresh_seconds: 30 },
22
+ branding: { color: '#475569' },
23
+ card_layout: { template: 'ledger', regions: [{ widget: 'status' }, { widget: 'table', source: { command: 'status' } }] },
24
+ ui: { elements: [{ id: 'card', kind: 'card', tag: 'tr-templated-card', module: 'ui/card.js' }] },
25
+ }, null, 2),
26
+ 'cli.mjs': 'export default async function run() {}\n',
27
+ 'ui/card.js': CARD,
28
+ },
29
+ expect: {
30
+ fail: ['no template', 'card_layout'],
31
+ },
32
+ };
@@ -0,0 +1,46 @@
1
+ /**
2
+ * A card that collects an API key in a password field.
3
+ *
4
+ * Observed: the scaffolder generated exactly this, dispatching the raw value
5
+ * as a `save-token` event — a second credential store and a leak surface.
6
+ */
7
+ const CARD = `
8
+ export class CollectingCard extends HTMLElement {
9
+ connectedCallback() {
10
+ const root = this.attachShadow({ mode: 'open' })
11
+ const input = document.createElement('input')
12
+ input.type = 'password'
13
+ input.setAttribute('aria-label', 'API key')
14
+ const button = document.createElement('button')
15
+ button.textContent = 'Save'
16
+ button.addEventListener('click', () => {
17
+ this.dispatchEvent(new CustomEvent('save-token', { detail: { token: input.value }, bubbles: true }))
18
+ })
19
+ root.append(input, button)
20
+ }
21
+ }
22
+ customElements.define('tr-collecting-card', CollectingCard)
23
+ `;
24
+
25
+ export default {
26
+ name: 'card-collects-credential',
27
+ why: 'one credential path exists in the system, and it is the auth plugin',
28
+ files: {
29
+ 'plugin.json': JSON.stringify({
30
+ id: 'collecting', name: 'Collecting', version: '1.0.0', description: 'A plugin whose card collects a key.',
31
+ cli: { command: 'collecting', handler: './cli.mjs', subcommands: [
32
+ { name: 'status', effect: 'read' },
33
+ { name: 'token', effect: 'write', args: [{ name: 'key', label: 'Key', type: 'string', secret: true }] },
34
+ ] },
35
+ secrets: [{ key: 'COLLECTING_API_KEY', description: 'Service key', required: true }],
36
+ status: { command: 'status', refresh_seconds: 30 },
37
+ branding: { color: '#6366f1' },
38
+ ui: { elements: [{ id: 'card', kind: 'card', tag: 'tr-collecting-card', module: 'ui/card.js' }] },
39
+ }, null, 2),
40
+ 'cli.mjs': "import { spawnSync } from 'node:child_process'\nspawnSync('total-recall', ['auth', 'secret', 'get', 'COLLECTING_API_KEY'])\nexport default async function run() {}\n",
41
+ 'ui/card.js': CARD,
42
+ },
43
+ expect: {
44
+ fail: ['never collects a credential', 'never transmits a credential'],
45
+ },
46
+ };
@@ -0,0 +1,55 @@
1
+ /**
2
+ * A capability that depends on Total Recall and ships a placeholder.
3
+ *
4
+ * Observed: a vendored brick called the `total-recall` binary for every
5
+ * credential read — failing with `Auth credential read failed` once it was
6
+ * absent — and carried `https://example.com/checkout/success`.
7
+ */
8
+ const CARD = `
9
+ export class LeakyCard extends HTMLElement {
10
+ connectedCallback() { this.attachShadow({ mode: 'open' }) }
11
+ }
12
+ customElements.define('tr-leaky-card', LeakyCard)
13
+ `;
14
+
15
+ const CAPABILITY = `
16
+ import { spawnSync } from 'node:child_process'
17
+
18
+ function secret() {
19
+ const binary = process.env.TOTAL_RECALL_BIN || 'total-recall'
20
+ const out = spawnSync(binary, ['auth', 'secret', 'get', 'LEAKY_API_KEY', '--json'])
21
+ return JSON.parse(out.stdout).value
22
+ }
23
+
24
+ export async function createCheckout({ amount }) {
25
+ const successUrl = 'https://example.com/checkout/success'
26
+ const res = await fetch('https://api.example.com/v1/sessions', {
27
+ method: 'POST',
28
+ headers: { Authorization: 'Bearer ' + secret() },
29
+ body: JSON.stringify({ amount, success_url: successUrl }),
30
+ })
31
+ return res.json()
32
+ }
33
+ `;
34
+
35
+ export default {
36
+ name: 'host-leak-in-scaffold',
37
+ why: 'a scaffold is laid into an application that has no Total Recall, and no placeholders',
38
+ files: {
39
+ 'plugin.json': JSON.stringify({
40
+ id: 'leaky', name: 'Leaky', version: '1.0.0', description: 'A plugin whose capability depends on the host.',
41
+ cli: { command: 'leaky', handler: './cli.mjs', subcommands: [{ name: 'status', effect: 'read' }] },
42
+ capability: { entry: 'capability/index.mjs', exports: ['createCheckout'] },
43
+ secrets: [{ key: 'LEAKY_API_KEY', description: 'Service key', required: true }],
44
+ status: { command: 'status', refresh_seconds: 30 },
45
+ branding: { color: '#b45309' },
46
+ ui: { elements: [{ id: 'card', kind: 'card', tag: 'tr-leaky-card', module: 'ui/card.js' }] },
47
+ }, null, 2),
48
+ 'cli.mjs': 'export default async function run() {}\n',
49
+ 'ui/card.js': CARD,
50
+ 'capability/index.mjs': CAPABILITY,
51
+ },
52
+ expect: {
53
+ fail: ['no Total Recall dependency', 'no placeholder values'],
54
+ },
55
+ };
@@ -0,0 +1,92 @@
1
+ /**
2
+ * A plugin that satisfies the recipe: one bespoke face, real data only,
3
+ * credentials through auth, values the caller supplies.
4
+ *
5
+ * This is the positive case. It must produce no failures.
6
+ */
7
+ const CARD = `
8
+ const DASH = '\\u2014'
9
+
10
+ export class ExampleCard extends HTMLElement {
11
+ constructor() {
12
+ super()
13
+ this.state = 'idle'
14
+ this.reading = null
15
+ this.lastError = null
16
+ this.lastAttempt = null
17
+ this.screen = 'status'
18
+ }
19
+ get screens() { return [{ id: 'status', label: 'Status' }] }
20
+ connectedCallback() {
21
+ if (!this.shadowRoot) this.attachShadow({ mode: 'open' })
22
+ this.render()
23
+ void this.read()
24
+ }
25
+ async read() {
26
+ this.lastAttempt = new Date()
27
+ try {
28
+ const payload = await this.host.run('status', ['--json'])
29
+ this.reading = payload && typeof payload === 'object' ? payload : null
30
+ if (!this.reading) throw new Error('The status command returned nothing readable')
31
+ this.lastError = null
32
+ this.state = 'ready'
33
+ } catch (err) {
34
+ this.reading = null
35
+ this.lastError = err && err.message ? err.message : String(err)
36
+ this.state = 'error'
37
+ } finally {
38
+ this.paint()
39
+ }
40
+ }
41
+ paint() {
42
+ const value = this.shadowRoot.querySelector('#value')
43
+ const note = this.shadowRoot.querySelector('#note')
44
+ if (value) value.textContent = this.reading ? String(this.reading.usedPercent ?? DASH) : DASH
45
+ if (note) note.textContent = this.lastError ? this.lastError + ' at ' + this.lastAttempt.toLocaleTimeString() : ''
46
+ }
47
+ render() {
48
+ this.shadowRoot.innerHTML = '<style>:host{display:block;color:var(--text-primary);border:1px solid var(--border);padding:14px}' +
49
+ 'button{min-height:44px}</style>' +
50
+ '<h2>Example</h2><span id="value">' + DASH + '</span><p id="note"></p><button type="button">Read</button>'
51
+ }
52
+ }
53
+ customElements.define('tr-example-card', ExampleCard)
54
+ `;
55
+
56
+ const CAPABILITY = `
57
+ export async function readStatus({ endpoint, apiKey, fetchImpl = fetch }) {
58
+ if (!endpoint) throw new Error('endpoint is required')
59
+ const res = await fetchImpl(endpoint + '/status', { headers: apiKey ? { Authorization: 'Bearer ' + apiKey } : {} })
60
+ if (!res.ok) throw new Error('status request failed: ' + res.status)
61
+ return res.json()
62
+ }
63
+ `;
64
+
65
+ export default {
66
+ name: 'correct-plugin',
67
+ why: 'the recipe must accept a plugin that follows it, or it is not a recipe',
68
+ files: {
69
+ 'plugin.json': JSON.stringify({
70
+ id: 'example', name: 'Example', version: '1.0.0', description: 'A plugin that follows the recipe exactly.',
71
+ cli: { command: 'example', handler: './cli.mjs', subcommands: [
72
+ { name: 'status', description: 'Read current status', effect: 'read', output: 'json' },
73
+ { name: 'set-key', description: 'Store the service key', effect: 'write',
74
+ args: [{ name: 'apiKey', label: 'API key', type: 'string', secret: true }] },
75
+ ] },
76
+ capability: { entry: 'capability/index.mjs', exports: ['readStatus'] },
77
+ secrets: [{ key: 'EXAMPLE_API_KEY', description: 'Reads the service', required: true }],
78
+ status: { command: 'status', refresh_seconds: 30 },
79
+ distribution: { tier: 'official', license: 'open' },
80
+ branding: { name: 'Example', color: '#047857', accent: '#10b981', font: 'inter', density: 'comfortable', radius: 'sm', header: 'plain' },
81
+ ui: { design_tokens: 'ui/DESIGN.md', elements: [{ id: 'card', kind: 'card', tag: 'tr-example-card', module: 'ui/card.js' }] },
82
+ }, null, 2),
83
+ 'cli.mjs': "import { readAuthSecret } from './auth-client.mjs'\nimport { readStatus } from './capability/index.mjs'\nexport default async function run() {\n return readStatus({ endpoint: process.env.EXAMPLE_ENDPOINT, apiKey: readAuthSecret('EXAMPLE_API_KEY') })\n}\n",
84
+ 'auth-client.mjs': "/** Auth-only transport: the plugin holds no credential of its own. */\nimport { spawnSync } from 'node:child_process'\nexport function readAuthSecret(key) {\n const out = spawnSync('total-recall', ['auth', 'secret', 'get', key, '--json'], { encoding: 'utf8' })\n const parsed = JSON.parse(out.stdout)\n return parsed.found ? parsed.value : ''\n}\n",
85
+ 'ui/DESIGN.md': '# DESIGN.md — Example\n\n```yaml\ntokens:\n brand-name: "Example"\n color-primary: "#047857"\n```\n\nOne screen: the status reading.\n',
86
+ 'ui/card.js': CARD,
87
+ 'capability/index.mjs': CAPABILITY,
88
+ },
89
+ expect: {
90
+ fail: [],
91
+ },
92
+ };
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * evals/run.mjs — proof that the recipe and its checks work.
4
+ *
5
+ * Each case is a synthetic plugin whose correct outcome is already known. Most
6
+ * are defects observed in the real ecosystem; one is a plugin that follows the
7
+ * recipe and must be accepted. Every case is materialised into a temp
8
+ * directory, run through scripts/record.mjs, and asserted.
9
+ *
10
+ * node evals/run.mjs [--case <name>] [--json] [--keep]
11
+ *
12
+ * A case passes when record.mjs reports exactly the failures the case expects:
13
+ * an eval that passes because the checker looks at nothing is not evidence.
14
+ */
15
+ import fs from 'node:fs'
16
+ import os from 'node:os'
17
+ import path from 'node:path'
18
+ import { execFileSync } from 'node:child_process'
19
+ import { pathToFileURL } from 'node:url'
20
+
21
+ const HERE = import.meta.dirname
22
+ const RECORD = path.join(HERE, '..', 'scripts', 'record.mjs')
23
+ const args = process.argv.slice(2)
24
+ const asJson = args.includes('--json')
25
+ const keep = args.includes('--keep')
26
+ const onlyCase = args.includes('--case') ? args[args.indexOf('--case') + 1] : null
27
+
28
+ if (!fs.existsSync(RECORD)) {
29
+ console.error(`record.mjs not found at ${RECORD}`)
30
+ process.exit(2)
31
+ }
32
+
33
+ /* Load every case. */
34
+ const caseFiles = fs.readdirSync(path.join(HERE, 'cases'))
35
+ .filter((f) => f.endsWith('.mjs'))
36
+ .sort()
37
+ const cases = []
38
+ for (const file of caseFiles) {
39
+ const mod = await import(pathToFileURL(path.join(HERE, 'cases', file)).href)
40
+ if (mod.default) cases.push({ ...mod.default, file })
41
+ }
42
+
43
+ /* Materialise and check one case. */
44
+ function materialise(spec, dir) {
45
+ for (const [rel, content] of Object.entries(spec.files)) {
46
+ const abs = path.join(dir, rel)
47
+ fs.mkdirSync(path.dirname(abs), { recursive: true })
48
+ fs.writeFileSync(abs, content)
49
+ }
50
+ }
51
+
52
+ function runRecord(dir) {
53
+ try {
54
+ const out = execFileSync(process.execPath, [RECORD, dir, '--json'], { encoding: 'utf8', timeout: 60000 })
55
+ return { code: 0, report: JSON.parse(out) }
56
+ } catch (err) {
57
+ // record.mjs exits non-zero when it finds failures; the JSON is still valid.
58
+ const out = String(err.stdout || '')
59
+ try { return { code: err.status ?? 1, report: JSON.parse(out) } }
60
+ catch { return { code: err.status ?? 1, report: null, raw: out + String(err.stderr || '') } }
61
+ }
62
+ }
63
+
64
+ const results = []
65
+ for (const spec of cases) {
66
+ if (onlyCase && spec.name !== onlyCase) continue
67
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), `plugin-eval-${spec.name}-`))
68
+ try {
69
+ materialise(spec, dir)
70
+ const { report } = runRecord(dir)
71
+
72
+ if (!report) {
73
+ results.push({ name: spec.name, ok: false, reason: 'record.mjs produced no JSON' })
74
+ continue
75
+ }
76
+
77
+ const failures = report.results.filter((r) => r.level === 'FAIL')
78
+ const failureText = failures.map((f) => `${f.check}: ${f.detail}`).join(' | ')
79
+ const expected = spec.expect.fail || []
80
+
81
+ const missing = expected.filter((want) => !failureText.includes(want.split(':')[0].trim()))
82
+ const unexpected = expected.length === 0
83
+ ? failures.map((f) => `${f.check}: ${f.detail}`)
84
+ : []
85
+
86
+ const ok = missing.length === 0 && unexpected.length === 0
87
+ results.push({
88
+ name: spec.name,
89
+ ok,
90
+ why: spec.why,
91
+ expected,
92
+ found: failures.map((f) => `${f.check} — ${f.detail}`),
93
+ reason: ok ? null
94
+ : missing.length ? `checker missed: ${missing.join(', ')}`
95
+ : `unexpected failures: ${unexpected.join(' | ')}`,
96
+ })
97
+ } finally {
98
+ if (!keep) fs.rmSync(dir, { recursive: true, force: true })
99
+ else console.log(`kept: ${dir}`)
100
+ }
101
+ }
102
+
103
+ /* Report. */
104
+ if (asJson) {
105
+ process.stdout.write(JSON.stringify({ cases: results, passed: results.filter((r) => r.ok).length, total: results.length }, null, 2) + '\n')
106
+ } else {
107
+ for (const r of results) {
108
+ console.log(`${r.ok ? ' ok ' : ' FAIL '} ${r.name}`)
109
+ if (r.why) console.log(` why: ${r.why}`)
110
+ if (r.expected.length) console.log(` expects: ${r.expected.join(', ')}`)
111
+ if (r.found.length) console.log(` found: ${r.found.join(' | ')}`)
112
+ if (r.reason) console.log(` ${r.reason}`)
113
+ }
114
+ const passed = results.filter((r) => r.ok).length
115
+ console.log(`\n${passed}/${results.length} eval cases passed`)
116
+ }
117
+
118
+ process.exitCode = results.every((r) => r.ok) ? 0 : 1
@@ -0,0 +1,29 @@
1
+ # Prompt 1 — Establish the function
2
+
3
+ **Do this before any code.** A plugin designed before its function is understood becomes a template.
4
+
5
+ ## The prompt
6
+
7
+ Answer all five, concretely:
8
+
9
+ 1. **What capability does this add?** One sentence. If it needs "and" to hold together, it is two plugins.
10
+ 2. **Who uses it, and what two or three things do they actually do with it?** Name the tasks, not the features.
11
+ 3. **What must be visible without a single click?** This decides the hierarchy later.
12
+ 4. **What does it depend on outside itself?** A provider's API, a local service, other machines on the mesh, another plugin, or nothing.
13
+ 5. **Could another application want this capability?** If yes, part of this plugin is the capability and part is Total Recall presentation, and they must be separable from the first line of code.
14
+
15
+ ## Correct means
16
+
17
+ - You can name the users and their tasks in plain words.
18
+ - You know whether a scaffold is in scope (Prompt 7), because that changes where the domain logic lives.
19
+ - The capability is one thing, not a bundle.
20
+
21
+ If you cannot answer 1 and 2, you are not ready to write code. Stop and find out.
22
+
23
+ ## Record it
24
+
25
+ Write the answers into `ui/DESIGN.md` when you reach Prompt 6 — they are the basis of the face. Keep the scaffold answer for Prompt 7.
26
+
27
+ ## Check
28
+
29
+ None mechanical. The check is that you can state the answers without hedging.
@@ -0,0 +1,43 @@
1
+ # Prompt 2 — Choose the surfaces
2
+
3
+ A plugin declares the surfaces its capability needs. **It is not one shape.**
4
+
5
+ ## The prompt
6
+
7
+ Read [surfaces](../references/surfaces.md) and select only what this function needs.
8
+
9
+ | Surface | Use when the capability… |
10
+ | --- | --- |
11
+ | Memory | adds a new kind of knowledge to the vault |
12
+ | Context | must change what agents read at the start of a task |
13
+ | CLI | offers operations an operator or an agent invokes |
14
+ | Skills | ships instruction an agent loads on demand |
15
+ | Tasks | must run on a schedule without being asked |
16
+ | Runtime | provides live behaviour rather than a command |
17
+ | UI | always — every plugin has a face |
18
+ | Deploy | is deployed into other applications |
19
+ | Distribution | is shared, sold, or operated as a service |
20
+ | Composition | builds on other bricks rather than starting from zero |
21
+
22
+ A plugin might be only a memory category. Only a generator. Only a router. Only a skill pack. A CLI with no UI beyond its card. Several at once.
23
+
24
+ ## Correct means
25
+
26
+ - Every declared surface is **used**. An unused surface is noise and a maintenance cost.
27
+ - Every surface the function needs is **declared**. A missing one makes the capability invisible to the host and to agents.
28
+ - The set is justified by Prompt 1's answers, not by what other plugins do.
29
+
30
+ ## Common wrong answers
31
+
32
+ - Declaring a CLI out of habit for a capability that is purely a memory contribution.
33
+ - Skipping `status` for a plugin whose state a user must be able to check at a glance.
34
+ - Declaring `tasks` when nothing needs to run unasked.
35
+ - Declaring `requires` to pull in a brick that is not actually needed.
36
+
37
+ ## Check
38
+
39
+ ```
40
+ node <skill>/scripts/record.mjs . --manifest
41
+ ```
42
+
43
+ Reports each declared surface and whether a stated function supports it.