@agentshouse/skills 0.0.0-stage → 0.1.0-c6411c39c6d0f

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 (103) hide show
  1. package/README.md +26 -2
  2. package/agent-instructions.json +10 -0
  3. package/app-guide.json +346 -0
  4. package/dist/index.d.ts +25 -0
  5. package/dist/index.js +46 -0
  6. package/how-we-work-earlier.json +6 -0
  7. package/how-we-work.md +33 -0
  8. package/package.json +27 -4
  9. package/set/ask-matt/LICENSE +21 -0
  10. package/set/ask-matt/PHASE-BOUNDARIES.md +55 -0
  11. package/set/ask-matt/SKILL.md +97 -0
  12. package/set/ask-matt/agents/openai.yaml +5 -0
  13. package/set/code-review/LICENSE +21 -0
  14. package/set/code-review/SKILL.md +89 -0
  15. package/set/code-review/agents/openai.yaml +3 -0
  16. package/set/codebase-design/DEEPENING.md +37 -0
  17. package/set/codebase-design/DESIGN-IT-TWICE.md +44 -0
  18. package/set/codebase-design/LICENSE +21 -0
  19. package/set/codebase-design/SKILL.md +114 -0
  20. package/set/codebase-design/agents/openai.yaml +3 -0
  21. package/set/diagnosing-bugs/LICENSE +21 -0
  22. package/set/diagnosing-bugs/SKILL.md +138 -0
  23. package/set/diagnosing-bugs/agents/openai.yaml +3 -0
  24. package/set/diagnosing-bugs/scripts/hitl-loop.template.sh +44 -0
  25. package/set/domain-modeling/ADR-FORMAT.md +47 -0
  26. package/set/domain-modeling/GLOSSARY-FORMAT.md +60 -0
  27. package/set/domain-modeling/LICENSE +21 -0
  28. package/set/domain-modeling/SKILL.md +74 -0
  29. package/set/domain-modeling/agents/openai.yaml +3 -0
  30. package/set/grill-me/LICENSE +21 -0
  31. package/set/grill-me/SKILL.md +7 -0
  32. package/set/grill-me/agents/openai.yaml +5 -0
  33. package/set/grill-with-docs/LICENSE +21 -0
  34. package/set/grill-with-docs/SKILL.md +7 -0
  35. package/set/grill-with-docs/agents/openai.yaml +5 -0
  36. package/set/grilling/LICENSE +21 -0
  37. package/set/grilling/SKILL.md +30 -0
  38. package/set/grilling/agents/openai.yaml +3 -0
  39. package/set/handoff/LICENSE +21 -0
  40. package/set/handoff/SKILL.md +16 -0
  41. package/set/handoff/agents/openai.yaml +5 -0
  42. package/set/implement/LICENSE +21 -0
  43. package/set/implement/SKILL.md +17 -0
  44. package/set/implement/agents/openai.yaml +5 -0
  45. package/set/implement-spec/LICENSE +21 -0
  46. package/set/implement-spec/SKILL.md +40 -0
  47. package/set/implement-spec/agents/openai.yaml +5 -0
  48. package/set/improve-codebase-architecture/HTML-REPORT.md +123 -0
  49. package/set/improve-codebase-architecture/LICENSE +21 -0
  50. package/set/improve-codebase-architecture/SKILL.md +71 -0
  51. package/set/improve-codebase-architecture/agents/openai.yaml +5 -0
  52. package/set/pr/CREDITS.md +3 -0
  53. package/set/pr/LICENSE +21 -0
  54. package/set/pr/SKILL.md +170 -0
  55. package/set/pr/agents/openai.yaml +3 -0
  56. package/set/prototype/LICENSE +21 -0
  57. package/set/prototype/LOGIC.md +67 -0
  58. package/set/prototype/SKILL.md +26 -0
  59. package/set/prototype/UI.md +112 -0
  60. package/set/prototype/agents/openai.yaml +3 -0
  61. package/set/research/LICENSE +21 -0
  62. package/set/research/SKILL.md +12 -0
  63. package/set/research/agents/openai.yaml +3 -0
  64. package/set/retro/LICENSE +21 -0
  65. package/set/retro/SKILL.md +44 -0
  66. package/set/retro/agents/openai.yaml +5 -0
  67. package/set/tdd/LICENSE +21 -0
  68. package/set/tdd/SKILL.md +38 -0
  69. package/set/tdd/agents/openai.yaml +3 -0
  70. package/set/tdd/mocking.md +59 -0
  71. package/set/tdd/tests.md +77 -0
  72. package/set/teach/GLOSSARY-FORMAT.md +35 -0
  73. package/set/teach/LEARNING-RECORD-FORMAT.md +46 -0
  74. package/set/teach/LICENSE +21 -0
  75. package/set/teach/MISSION-FORMAT.md +31 -0
  76. package/set/teach/RESOURCES-FORMAT.md +32 -0
  77. package/set/teach/SKILL.md +140 -0
  78. package/set/teach/agents/openai.yaml +5 -0
  79. package/set/to-questionnaire/LICENSE +21 -0
  80. package/set/to-questionnaire/SKILL.md +54 -0
  81. package/set/to-questionnaire/agents/openai.yaml +5 -0
  82. package/set/to-spec/LICENSE +21 -0
  83. package/set/to-spec/SKILL.md +75 -0
  84. package/set/to-spec/agents/openai.yaml +5 -0
  85. package/set/to-tickets/LICENSE +21 -0
  86. package/set/to-tickets/SKILL.md +105 -0
  87. package/set/to-tickets/agents/openai.yaml +5 -0
  88. package/set/triage/AGENT-BRIEF.md +207 -0
  89. package/set/triage/LICENSE +21 -0
  90. package/set/triage/OUT-OF-SCOPE.md +105 -0
  91. package/set/triage/SKILL.md +112 -0
  92. package/set/triage/agents/openai.yaml +5 -0
  93. package/set/wait-what/LICENSE +21 -0
  94. package/set/wait-what/SKILL.md +7 -0
  95. package/set/wait-what/agents/openai.yaml +5 -0
  96. package/set/wayfinder/LICENSE +21 -0
  97. package/set/wayfinder/SKILL.md +128 -0
  98. package/set/wayfinder/agents/openai.yaml +5 -0
  99. package/set/writing-for-agents/LICENSE +21 -0
  100. package/set/writing-for-agents/SKILL-MECHANICS.md +22 -0
  101. package/set/writing-for-agents/SKILL.md +81 -0
  102. package/set/writing-for-agents/agents/openai.yaml +3 -0
  103. package/upstream.json +32 -0
package/README.md CHANGED
@@ -1,3 +1,27 @@
1
- # Temporary Holding Version
1
+ # `@agentshouse/skills`
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The Skills agents.house serves its agents, and the texts its agents read.
4
+
5
+ - `set/` holds one directory per Skill, each with its `SKILL.md`: every engineering and
6
+ productivity skill of [mattpocock/skills](https://github.com/mattpocock/skills) but its setup
7
+ skill and `wizard`, file for file beside upstream's `LICENSE`. `upstream.json` names the
8
+ upstream commit, its date and the skills taken from it.
9
+ - `how-we-work.md` is the How-we-work document's text, and `how-we-work-earlier.json` the
10
+ SHA-256 of every earlier text.
11
+ - `app-guide.json` is the guide to building an App or a Site, and `agent-instructions.json` the
12
+ lines that open an agent's first prompt.
13
+
14
+ ```ts
15
+ import { HOW_WE_WORK, listSkills, readSkill } from '@agentshouse/skills';
16
+
17
+ for (const { name } of listSkills()) console.log(name, readSkill(name).length);
18
+ console.log(HOW_WE_WORK);
19
+ ```
20
+
21
+ `agentInstructions(houseRef, copies)` returns the first prompt's lines, `APP_GUIDE` the guide's
22
+ lines and `EARLIER_HOW_WE_WORK_SHA256` the earlier texts' digests.
23
+
24
+ The skills come from [mattpocock/skills](https://github.com/mattpocock/skills); thank you, Matt
25
+ Pocock.
26
+
27
+ MIT.
@@ -0,0 +1,10 @@
1
+ {
2
+ "lines": [
3
+ "From House:",
4
+ "Work with House through the `house` CLI: run `house --help`, and `house find_command` for House's guides and formats.",
5
+ "To give the User a file, upload it with `house upload` and link it; for an App, a Site or a website, follow `house find_command '{\"query\":\"app\"}'`. Never start a server.",
6
+ "Optional per-Room instructions: `/rooms/private/rooms/<handle>/AGENTS.md`.",
7
+ "For another Agent's help, `send_routine_request`; its mail comes back into this conversation. If an Agent of your User has no Routine to call, write one in the Private Room and `create_routine_access` with your User as its only caller."
8
+ ],
9
+ "copies": "House Local copies; before work run `house fetch` and rebase onto `<house-ref>`; after it commit, then `house push`:"
10
+ }
package/app-guide.json ADDED
@@ -0,0 +1,346 @@
1
+ {
2
+ "name": "App",
3
+ "aliases": [
4
+ {
5
+ "act": "sites/create",
6
+ "text": "site"
7
+ }
8
+ ],
9
+ "words": [
10
+ "app",
11
+ "apps",
12
+ {
13
+ "act": "sites/create",
14
+ "text": "site"
15
+ },
16
+ {
17
+ "act": "sites/create",
18
+ "text": "website"
19
+ },
20
+ "web",
21
+ "ui",
22
+ "frontend",
23
+ "html",
24
+ "build",
25
+ "create",
26
+ "new",
27
+ "make",
28
+ "deploy",
29
+ "publish"
30
+ ],
31
+ "text": [
32
+ {
33
+ "act": "sites/create",
34
+ "text": "# Build an App or a Site",
35
+ "without": "# Build an App"
36
+ },
37
+ "",
38
+ {
39
+ "act": "sites/create",
40
+ "text": "An App is a web page over your User's Rooms, for your User and whom they share it with (`create_share`); a Site is one at a public address anyone opens without signing in. House serves the built files and gives the page the App runtime, which holds every bound Room in the browser and syncs it with House. There is no server, backend or House API of yours to call.",
41
+ "without": "An App is a web page over your User's Rooms, for your User and whom they share it with (`create_share`). House serves the built files and gives the page the App runtime, which holds every bound Room in the browser and syncs it with House. There is no server, backend or House API of yours to call."
42
+ },
43
+ "",
44
+ "## Start",
45
+ "",
46
+ "- An App with an `a_` reference already exists, even with no release yet, as every App your User makes in the panel does. Clone its `.git` address, `<origin>/app/<app_ref>.git` or its `source_url` in `list_apps`, with `git clone -c 'credential.helper=!kit git-credential' -c user.name=<agent-git-name> -c user.email=<agent-git-email> <address>`, so Git signs in as you and can commit, in every later step too. Run `npm ci`, then publish with `npm run publish`, never with `--name`.",
47
+ "- Only when no App exists yet, clone `<origin>/app/new.git` the same way: it gives mdui, a minimal App in plain TypeScript with its test and `npm run publish`. Run `npm ci`, then `npm run publish -- --name \"<display name>\"`, which creates the App and prints its address and its `.git` address. Then `git remote set-url origin <that .git address>`; every later `npm run publish` goes there, without `--name`.",
48
+ {
49
+ "act": "sites/create",
50
+ "text": "- A Site starts with `create_site`: `name` is its address name, and the answer's `source_url` is its App's `.git` address to clone the same way. Anything private is an App."
51
+ },
52
+ {
53
+ "act": "sites/create",
54
+ "text": "- A Site shows the Rooms it published, as `published` names them: creating it publishes its bound Rooms, a release keeps the published data, and later Room edits stay private until `publish_site_data`. A publication whose `state` is `preparing` replaces the shown one once its screenshot is taken; `list_sites` names each Site's newest publication and its `state`, and `publish_site_data` with nothing changed since answers that publication. `unpublish_site` stops serving a Site and keeps it; `republish_site` or `publish_site_data` serves it again."
55
+ },
56
+ "- Use any framework and build tool. `npm run build` leaves the whole release in `dist/`: the entry HTML, every file it loads, `app.json` and `schema.yaml`. The entry names files by relative paths (Vite: `base: './'`), carries no inline script and loads nothing from outside the release, so bundle fonts and libraries. A release holds at most <release-files> files and <release-mib> MiB.",
57
+ "- Your User sees a change only once it is published, so an ask to change an App is an ask to publish it: end every change with a commit and `npm run publish`, which builds, pushes `main` and the build, and prints `published` or the reason House refused. A `git push` of `main` alone publishes nothing and says so. A refused push changes nothing and the previous release keeps running.",
58
+ "- Without Git, zip the project folder after the build, without `node_modules` and `.env`, `upload` the ZIP to a Room and publish it with `publish_app_zip`.",
59
+ "- Every page registers `bridge.cooperate` in its entry script before its first draw, as mdui's `main.ts` does, even a page that reads no Room: only then does a page your User has open take each release by itself, with no reload. A page that registers none keeps the release it opened on until it is opened again. A page that draws everything at once and has nothing to type or select registers `bridge.cooperate({ checkpoint() {}, persist() {}, capture: () => scrollY, restore(top) { scrollTo(0, Number(top)); }, ready: () => true, settle() {} })` after `const bridge = houseBridge()`, which keeps its scroll position. Every other page keeps its User's task through the release as the example below does: `checkpoint` waits until the page has drawn, `persist` until its Room Draft writes are done, failing while a field it shows was refused its last write so the release waits, `capture` answers the focused field with its cursor and selection, the scroll position and whatever else it holds outside the address, and `restore` puts them back once the page has drawn again; `node_modules/@agentshouse/app-bridge/docs/families.md` describes each member.",
60
+ "",
61
+ "## Data",
62
+ "",
63
+ "- Design the Room schema first. Each thing the App shows is one Markdown document in a Room: its frontmatter `type` names its MD type, the rest of its frontmatter holds the fields, and long text goes in `##` sections. `schema.yaml` declares the types (`find_command` \"Schema\").",
64
+ "- Keep each type in its own folder, such as `tasks/`, and read it by `pathPrefix`: a query naming `types` fails with `unknown-type` while no bound Room declares them.",
65
+ "- Link types with `reference` fields instead of copying values; a query follows them with `hop` and `backlinks`.",
66
+ {
67
+ "act": "sites/create",
68
+ "text": "- `app.json` `room_slots` names the Rooms the App needs, each with `access` `read` or `write` and the `types` it uses (`find_command` \"App manifest\"). Bind Rooms with `set_app_bindings`, a Site's with `set_site_bindings`; App code cannot bind and reaches only bound Rooms, so it opens `bridge.ceremony('/bind/<app_ref>?slot=<name>')`, the House page where your User binds Rooms and connects account slots, which returns to the App.",
69
+ "without": "- `app.json` `room_slots` names the Rooms the App needs, each with `access` `read` or `write` and the `types` it uses (`find_command` \"App manifest\"). Bind Rooms with `set_app_bindings`; App code cannot bind and reaches only bound Rooms, so it opens `bridge.ceremony('/bind/<app_ref>?slot=<name>')`, the House page where your User binds Rooms and connects account slots, which returns to the App."
70
+ },
71
+ "- A bound Room must hold each slot type identically in its own `settings/schema.yaml`, beside any other types, or your User is warned. Write it with `edit` before binding; `create_room` makes a new Room.",
72
+ "- Read an existing Room's `settings/schema.yaml` before inventing types. Two Apps over one schema are two interfaces over the same data, and your User's Agents read and write those documents too.",
73
+ "- What no browser can do, such as acting when documents change or at a set time, is a Trigger (`create_trigger`) that notifies or starts a Routine on an Agent; what it writes reaches the App at once.",
74
+ "",
75
+ "## Local first",
76
+ "",
77
+ "- The runtime holds every bound Room on the device and streams in every change, from any device, App or Agent. `query`, `search` and `observe` answer from the device and never wait on the network.",
78
+ "- Draw from `observe` at once, with no loading spinner for Room data and no request of your own; an observation hears each later change by itself, so never poll, refetch or subscribe to House. An answer is `{ pending }` while a Room it reads has not reached this device or been prepared on it, or the search index has not: draw the page without those documents, and keep the empty state for a complete answer that has none.",
79
+ "- Write optimistically: write a Room Draft, then `save`. The change shows in every query at once as a Pending Change and reaches House when it can. `save` refuses at once, with its `code`, a change that is `stale`, `changed-underneath` or against the schema: say so and keep the form.",
80
+ "- Show what the runtime reports: `observe('queue', room)` lists changes House has not confirmed yet, and `observe('sync-status')`'s `attentionRequired` lists each change House refused, returned as a Room Draft with why, to save again or discard.",
81
+ "- Keep what the User types in the Room Draft (`roomDraft.write`) as they type; it survives a new release replacing the page, a reload and another tab. Draw a form again only when its page changes, never on a streamed change.",
82
+ "- A document has one Room Draft, shared by every tab and App, and `save` takes all of it. A write replaces every part and names the revision it read, so write onto the parts it holds; one from an older revision answers `stale` with the current Room Draft: show that one. A field part names as its `base` the value the page showed, `{ value }`, or `null` when the document has no such field.",
83
+ "- Save a change the User does not type, such as a checkbox, with `saveParts(room, [{ path, parts }])`: it writes no Room Draft, so the one another tab holds stays.",
84
+ "- Offline, all of this works and saves wait. `bridge.onStatus` reports `offline`; say so quietly.",
85
+ "- A Room your User reads by Online Read locks once the browser has been offline 2 minutes, and from the moment it opens until House confirms the access. While it is locked, `rooms` lists it `locked: true`, a query or search that does not name it leaves it out, one naming it answers `{ pending }` with `awaits: 'unlock'`, and every other read and write naming it, `save` included, answers the code `room-locked` and keeps nothing. Draw it as locked until it unlocks and keep what the User types in the page; what was saved before stays and is sent then.",
86
+ "- An outside service is reached only at the origins `app.json` lists in `external_origins`; a keyed one through an `account_slots` entry and `bridge.callExternal` naming the slot and a bound Room that connects it, where House adds the key the page never sees.",
87
+ "- The page's own storage is a disposable cache, the Room the only durable home of the App's data; handle any code or value you do not know through `code` and `retryable`, which every refusal and failure carries.",
88
+ "- React: `useObserve` and `useSave` from `@agentshouse/mdruntime/react`. react-admin and Refine: the data providers of `@agentshouse/mdruntime-families`. The whole client is described in `node_modules/@agentshouse/mdruntime/AGENTS.md`.",
89
+ "",
90
+ "## Example: tasks",
91
+ "",
92
+ "`schema.yaml`, also written into the Room's `settings/schema.yaml`:",
93
+ "",
94
+ "```yaml",
95
+ "task:",
96
+ " properties:",
97
+ " title: { type: string }",
98
+ " done: { type: boolean }",
99
+ " required: [title, done]",
100
+ "```",
101
+ "",
102
+ "`app.json`, then `set_app_bindings` with slot `tasks`:",
103
+ "",
104
+ "```json",
105
+ "{ \"entry\": \"index.html\", \"room_slots\": [{ \"name\": \"tasks\", \"access\": \"write\", \"types\": [\"task\"] }] }",
106
+ "```",
107
+ "",
108
+ "`main.ts`: a list at `/` and a form at each task's own address, keeping every rule above.",
109
+ "",
110
+ "```ts",
111
+ "import { houseBridge } from '@agentshouse/app-bridge';",
112
+ "",
113
+ "type Task = { top: number; field: string | null; start: number | null; end: number | null };",
114
+ "",
115
+ "const bridge = houseBridge();",
116
+ "let shown!: () => void;",
117
+ "const showing = new Promise<void>((resolve) => (shown = resolve));",
118
+ "let writing: Promise<unknown> = Promise.resolve();",
119
+ "const unsaved = new Set<HTMLInputElement>();",
120
+ "bridge.cooperate({",
121
+ " checkpoint: () => showing,",
122
+ " async persist() {",
123
+ " await writing;",
124
+ " if ([...unsaved].some((title) => title.isConnected)) throw new Error('unsaved');",
125
+ " },",
126
+ " capture(): Task {",
127
+ " const at = document.activeElement;",
128
+ " const field = at instanceof HTMLInputElement && at.selectionStart !== null ? at : null;",
129
+ " return { top: scrollY, field: field?.ariaLabel ?? null, start: field?.selectionStart ?? null, end: field?.selectionEnd ?? null };",
130
+ " },",
131
+ " async restore(task) {",
132
+ " await showing;",
133
+ " const held = task as Task | null;",
134
+ " if (held === null) return;",
135
+ " const field = Array.from(document.querySelectorAll('input')).find((input) => held.field !== null && input.ariaLabel === held.field);",
136
+ " field?.focus({ preventScroll: true });",
137
+ " field?.setSelectionRange(held.start, held.end);",
138
+ " scrollTo(0, held.top);",
139
+ " },",
140
+ " ready: () => true,",
141
+ " settle() {},",
142
+ "});",
143
+ "const runtime = await bridge.runtime;",
144
+ "const rooms = await runtime.observe('rooms');",
145
+ "const sync = await runtime.observe('sync-status');",
146
+ "const drafts = await runtime.observe('room-drafts');",
147
+ "const tasks = await runtime.observe('query', {",
148
+ " pathPrefix: 'tasks',",
149
+ " select: { fields: ['/title', '/done'] },",
150
+ " orderBy: [{ by: { field: '/title' } }],",
151
+ "});",
152
+ "const main = document.querySelector('main')!;",
153
+ "const list = document.createElement('div');",
154
+ "const note = document.createElement('p');",
155
+ "type Parts = Parameters<typeof runtime.roomDraft.write>[3];",
156
+ "type Draft = Awaited<ReturnType<typeof runtime.roomDraft.read>>;",
157
+ "let drawn: string | undefined;",
158
+ "",
159
+ "async function write(room: string, path: string, held: Draft, parts: Parts) {",
160
+ " const { file: _file, ...kept } = { ...held?.parts };",
161
+ " const onto = { ...kept, ...parts, fields: { ...kept.fields, ...parts.fields } };",
162
+ " const written = await runtime.roomDraft.write(room, path, held?.revision ?? null, onto);",
163
+ " return written.outcome === 'written' ? { draft: written.roomDraft, stale: false } : { draft: written.current, stale: true };",
164
+ "}",
165
+ "",
166
+ "async function save(room: string, draft: Draft): Promise<boolean> {",
167
+ " const saved = draft === null ? null : await runtime.save(room, [{ path: draft.path, revision: draft.revision }]);",
168
+ " note.textContent = saved?.outcome === 'saved' ? '' : `Not saved: ${saved?.code ?? 'stale'}.`;",
169
+ " return saved?.outcome === 'saved';",
170
+ "}",
171
+ "",
172
+ "function queued<T>(work: () => Promise<T>): Promise<T | null> {",
173
+ " const done = writing.then(work).catch((error: { code?: unknown }) => {",
174
+ " note.textContent = `Not saved: ${String(error.code)}.`;",
175
+ " return null;",
176
+ " });",
177
+ " writing = done;",
178
+ " return done;",
179
+ "}",
180
+ "",
181
+ "function taskCreation(draft: Draft) {",
182
+ " const creation = draft?.parts.creation;",
183
+ " return creation !== undefined && 'type' in creation && creation.type === 'task' ? creation : undefined;",
184
+ "}",
185
+ "",
186
+ "function titleOf(draft: Draft): unknown {",
187
+ " return draft?.parts.fields?.title?.value ?? taskCreation(draft)?.fields.title;",
188
+ "}",
189
+ "",
190
+ "function el<K extends keyof HTMLElementTagNameMap>(tag: K, props: object, ...children: (Node | string)[]) {",
191
+ " const node = Object.assign(document.createElement(tag), props);",
192
+ " node.append(...children);",
193
+ " return node;",
194
+ "}",
195
+ "",
196
+ "function titleForm(action: string, room: string, path: () => string, shown: unknown, parts: (title: string, held: Draft) => Parts, done: (title: HTMLInputElement) => void) {",
197
+ " let held = drafts.current().find((draft) => draft.room === room && draft.path === path()) ?? null;",
198
+ " const title = el('input', { value: String(titleOf(held) ?? shown), required: true, ariaLabel: 'Title' });",
199
+ " const keep = () =>",
200
+ " queued(async () => {",
201
+ " unsaved.add(title);",
202
+ " const written = await write(room, path(), held, parts(title.value, held));",
203
+ " unsaved.delete(title);",
204
+ " held = written.draft;",
205
+ " if (written.stale) title.value = String(titleOf(held) ?? shown);",
206
+ " return written.stale;",
207
+ " });",
208
+ " title.oninput = () => void keep();",
209
+ " const form = el('form', {}, title, el('button', {}, action));",
210
+ " form.onsubmit = async (event) => {",
211
+ " event.preventDefault();",
212
+ " const stale = await keep();",
213
+ " if (stale === null) return;",
214
+ " if (stale) return void (note.textContent = 'Not saved: stale.');",
215
+ " if (!(await save(room, held))) return;",
216
+ " held = null;",
217
+ " done(title);",
218
+ " };",
219
+ " return form;",
220
+ "}",
221
+ "",
222
+ "function editForm(room: string, path: string, shown: unknown) {",
223
+ " const parts = (title: string): Parts => ({ fields: { title: { value: title, base: { value: shown } } } });",
224
+ " return titleForm('Save', room, () => path, shown, parts, () => void bridge.push('/'));",
225
+ "}",
226
+ "",
227
+ "function addForm(room: string) {",
228
+ " const parts = (title: string, held: Draft): Parts => {",
229
+ " const kept = taskCreation(held) ?? { type: 'task', fields: { done: false }, sections: {} };",
230
+ " return { creation: { ...kept, fields: { ...kept.fields, title } } };",
231
+ " };",
232
+ " const adding = drafts.current().find((draft) => draft.room === room && draft.path.startsWith('tasks/') && taskCreation(draft));",
233
+ " let path = adding?.path ?? `tasks/${crypto.randomUUID()}.md`;",
234
+ " return titleForm('Add', room, () => path, '', parts, (title) => {",
235
+ " title.value = '';",
236
+ " path = `tasks/${crypto.randomUUID()}.md`;",
237
+ " });",
238
+ "}",
239
+ "",
240
+ "async function toggle(room: string, path: string, done: boolean): Promise<void> {",
241
+ " const parts = { fields: { done: { value: !done, base: { value: done } } } };",
242
+ " const saved = await runtime.saveParts(room, [{ path, parts }]);",
243
+ " note.textContent = saved.outcome === 'saved' ? '' : `Not saved: ${saved.code}.`;",
244
+ " draw();",
245
+ "}",
246
+ "",
247
+ "function draw(): void {",
248
+ " const room = rooms.current()[0];",
249
+ " const answer = tasks.current();",
250
+ " const held = 'pending' in answer ? [] : answer.documents;",
251
+ " const open = held.find((task) => `/${task.path}` === bridge.address());",
252
+ " const page = room === undefined ? '' : `${room.locator}/${open?.path ?? ''}`;",
253
+ " if (page !== drawn) {",
254
+ " drawn = page;",
255
+ " note.textContent = '';",
256
+ " if (room === undefined) main.replaceChildren('This App has no Room yet.');",
257
+ " else if (open !== undefined) main.replaceChildren(editForm(open.room, open.path, open.fields!.title), note);",
258
+ " else main.replaceChildren(list, addForm(room.locator), note);",
259
+ " }",
260
+ " if (!('pending' in answer)) shown();",
261
+ " if (room === undefined) return;",
262
+ " const refused = new Set(sync.current().attentionRequired.flatMap((entry) => ('path' in entry ? [entry.path] : [])));",
263
+ " const rows = held.map((task) => {",
264
+ " const done = task.fields!.done === true;",
265
+ " const box = el('input', { type: 'checkbox', checked: done, ariaLabel: 'Done' });",
266
+ " box.onchange = () => void queued(() => toggle(task.room, task.path, done));",
267
+ " const link = el('a', { href: task.path }, String(task.fields!.title));",
268
+ " link.onclick = (event) => {",
269
+ " event.preventDefault();",
270
+ " void bridge.push(`/${task.path}`);",
271
+ " };",
272
+ " return el('li', {}, box, link, refused.has(task.path) ? ' (refused)' : '');",
273
+ " });",
274
+ " const empty = 'pending' in answer ? '' : 'No tasks yet. Add the first one.';",
275
+ " list.replaceChildren(rows.length > 0 ? el('ul', {}, ...rows) : empty);",
276
+ "}",
277
+ "",
278
+ "for (const observed of [rooms, sync, tasks]) observed.subscribe(draw);",
279
+ "bridge.onAddress(draw);",
280
+ "draw();",
281
+ "```",
282
+ "",
283
+ "## The page",
284
+ "",
285
+ "- The App draws everything; House draws nothing around it. Set the tab's title with `bridge.setTab({ title })`.",
286
+ "- The address bar is the shell's, not your page's: read your place from `bridge.address()` and `bridge.onAddress`, and move with `bridge.push` or `bridge.replace`, so Back, Forward, reload and a shared link keep it. Keep the open item, tab, filter and search in the address. Addresses under `/render`, `/info` and `/tab-icon` are House's.",
287
+ "- Mobile first: lay out from 360 pixels wide up, with no action behind hover alone.",
288
+ "- Every control works from the keyboard, with visible focus and a label.",
289
+ "- An empty list says what it will hold and invites the first document.",
290
+ "- Long names truncate with an ellipsis and show in full on hover.",
291
+ "- The element is the link: a row, card or name opens its item; no \"Open\" buttons.",
292
+ "- Offer installation yourself: `bridge.install()` from a click shows the browser's prompt, or answers `manual` where you show how.",
293
+ "",
294
+ {
295
+ "act": "sites/create",
296
+ "text": "## Sites"
297
+ },
298
+ {
299
+ "act": "sites/create",
300
+ "text": ""
301
+ },
302
+ {
303
+ "act": "sites/create",
304
+ "text": "- A Site is public: anyone opens it, nobody signs in, and every visitor reads its bound Rooms, which must be your User's own and never the Private Room."
305
+ },
306
+ {
307
+ "act": "sites/create",
308
+ "text": "- Everything it shows lives as documents in its Room; put its demo data there, never in code."
309
+ },
310
+ {
311
+ "act": "sites/create",
312
+ "text": "- A visitor's edits stay in their browser and never reach House: write them with `save` as an App does."
313
+ },
314
+ {
315
+ "act": "sites/create",
316
+ "text": "- To receive messages from visitors, name an Intake Room with `set_site_intake_room` and send each with `runtime.call('submit', { content })`, online only."
317
+ },
318
+ {
319
+ "act": "sites/create",
320
+ "text": "- `describe_site` sets the title and description search engines show."
321
+ },
322
+ {
323
+ "act": "sites/create",
324
+ "text": ""
325
+ },
326
+ "## Verify",
327
+ "",
328
+ "Before `npm run publish`:",
329
+ "",
330
+ "- `npm test` passes against the newest `@agentshouse/app-bridge` and `@agentshouse/mdruntime` (`npm install <name>@latest`), since every page gets House's newest runtime. Keep mdui's test: it runs the built page on `createMemoryAdapter` from `@agentshouse/mdruntime/memory` and `createHouseBridgeTestHost` from `@agentshouse/app-bridge/test-host`, with no House.",
331
+ "- Every slot type is in `schema.yaml` exactly as in the bound Room's `settings/schema.yaml`.",
332
+ "- The entry script registers `bridge.cooperate` before its first draw.",
333
+ "- The page works 360 pixels wide, offline, and reloaded at a deep address.",
334
+ "",
335
+ "After it:",
336
+ "",
337
+ "- `list_apps` names the App's `published_release`, whose `revision` is the `main` commit it was built from; a clone, a fetch and `git ls-remote` see `main` alone.",
338
+ "- Bind the Room once with `set_app_bindings`.",
339
+ {
340
+ "act": "sites/create",
341
+ "text": "- See it yourself: sign your own browser in with `create_sign_in_code`, then open the App's url. A Site needs none.",
342
+ "without": "- See it yourself: sign your own browser in with `create_sign_in_code`, then open the App's url."
343
+ },
344
+ "- Give your User its address. An open page that registers `bridge.cooperate` takes each release by itself; nothing asks them to update."
345
+ ]
346
+ }
@@ -0,0 +1,25 @@
1
+ export type GuideLine = string | {
2
+ readonly act: string;
3
+ readonly text: string;
4
+ readonly without?: string;
5
+ };
6
+ export interface Guide {
7
+ readonly name: string;
8
+ readonly aliases: readonly GuideLine[];
9
+ readonly words: readonly GuideLine[];
10
+ readonly text: readonly GuideLine[];
11
+ }
12
+ export interface Skill {
13
+ readonly name: string;
14
+ readonly directory: string;
15
+ }
16
+ export interface SkillFile {
17
+ readonly path: string;
18
+ readonly content: string;
19
+ }
20
+ export declare const HOW_WE_WORK: string;
21
+ export declare const EARLIER_HOW_WE_WORK_SHA256: readonly string[];
22
+ export declare const APP_GUIDE: Guide;
23
+ export declare function agentInstructions(houseRef: string, copies: readonly string[]): string[];
24
+ export declare function listSkills(): readonly Skill[];
25
+ export declare function readSkill(name: string): readonly SkillFile[];
package/dist/index.js ADDED
@@ -0,0 +1,46 @@
1
+ import { readdirSync, readFileSync } from 'node:fs';
2
+ import { join, relative, sep } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ const PACKAGE = fileURLToPath(new URL('..', import.meta.url));
5
+ const SET = join(PACKAGE, 'set');
6
+ const ENTRY = 'SKILL.md';
7
+ const REF_PLACEHOLDER = '<house-ref>';
8
+ function packaged(name) {
9
+ return readFileSync(join(PACKAGE, name), 'utf8');
10
+ }
11
+ export const HOW_WE_WORK = packaged('how-we-work.md');
12
+ export const EARLIER_HOW_WE_WORK_SHA256 = JSON.parse(packaged('how-we-work-earlier.json'));
13
+ export const APP_GUIDE = JSON.parse(packaged('app-guide.json'));
14
+ const INSTRUCTIONS = JSON.parse(packaged('agent-instructions.json'));
15
+ export function agentInstructions(houseRef, copies) {
16
+ if (copies.length === 0)
17
+ return [...INSTRUCTIONS.lines];
18
+ return [
19
+ ...INSTRUCTIONS.lines,
20
+ INSTRUCTIONS.copies.replaceAll(REF_PLACEHOLDER, houseRef),
21
+ ...copies,
22
+ ];
23
+ }
24
+ export function listSkills() {
25
+ return readdirSync(SET, { withFileTypes: true })
26
+ .filter((entry) => entry.isDirectory())
27
+ .map((entry) => entry.name)
28
+ .sort()
29
+ .map((name) => ({ name, directory: join(SET, name) }));
30
+ }
31
+ function entryFirst(left, right) {
32
+ if (left === ENTRY || right === ENTRY)
33
+ return left === ENTRY ? -1 : 1;
34
+ return left < right ? -1 : 1;
35
+ }
36
+ export function readSkill(name) {
37
+ const skill = listSkills().find((listed) => listed.name === name);
38
+ if (skill === undefined)
39
+ throw new Error(`no Skill is named ${name}`);
40
+ return readdirSync(skill.directory, { recursive: true, withFileTypes: true })
41
+ .filter((entry) => entry.isFile())
42
+ .map((entry) => relative(skill.directory, join(entry.parentPath, entry.name)))
43
+ .map((path) => path.split(sep).join('/'))
44
+ .sort(entryFirst)
45
+ .map((path) => ({ path, content: readFileSync(join(skill.directory, path), 'utf8') }));
46
+ }
@@ -0,0 +1,6 @@
1
+ [
2
+ "ae4dda60da7b174f22dc5fa9bf07fc27c25864932a74230de4f9f509ddd91f41",
3
+ "0bdcd43e1603ba6673a3cae56d70bbf90657cbeefd124e91a47be2ca1cbd6c93",
4
+ "4106a87ec66da6f7b2a10e74d18e6559d68d5f4fe7415dae68f5c7bdb9081968",
5
+ "ac814a2197038f037b657bb719340395f89f319ac9fcdd386629c3fe2cec68f1"
6
+ ]
package/how-we-work.md ADDED
@@ -0,0 +1,33 @@
1
+ # How we work
2
+
3
+ A Room is where information people share is kept; it is not the place for
4
+ private information. Work goes into the Room it belongs to, or the First Room
5
+ when none is named.
6
+
7
+ Every House answer carries the `url` of what it names; send the User that link.
8
+
9
+ The skills are already set up; never run `setup-matt-pocock-skills`.
10
+
11
+ ## Where skill output goes
12
+
13
+ - Glossary (`GLOSSARY.md`): `glossary.md`
14
+ - Decisions (ADRs): `decisions/NNNN-<slug>.md`
15
+ - Plan (spec): `projects/<name>/plan.md`
16
+ - Tasks (tickets): `projects/<name>/tasks/NN-<slug>.md`, with a
17
+ `Status:` line (`needs-triage`, `needs-info`, `ready-for-agent`,
18
+ `ready-for-human`, `wontfix`, or `done` once implemented), a
19
+ `Category:` line (`bug`, `enhancement`), a `Blocked by:` line and comments
20
+ under `## Comments`; triage takes no pull requests
21
+ - Rejected requests (triage): `out-of-scope/<concept>.md`
22
+ - Implementation notes (implement-spec): `projects/<name>/notes/`
23
+ - PR bodies (pr): `projects/<name>/prs/<branch>.md`
24
+ - Map (Wayfinding): `projects/<name>/map.md`, its tasks under
25
+ `tasks/` with `Type:`, `Status: claimed|resolved` and `Blocked by:` lines
26
+ - Research: `research/<slug>.md`
27
+ - Architecture reports (improve-codebase-architecture): `architecture/<slug>.html`
28
+ - Handoffs: `handoffs/<slug>.md`
29
+ - Questionnaires: `questionnaires/<slug>.md`
30
+ - Retrospectives (retro): `retros/<date>-<slug>.md`
31
+ - Wizards: `wizards/<slug>.sh`; the values they capture stay out of the Room
32
+ - Learning: `learning/<topic>/`
33
+ - Prototype: one HTML file with every variant on one page.
package/package.json CHANGED
@@ -1,6 +1,29 @@
1
1
  {
2
2
  "name": "@agentshouse/skills",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0-c6411c39c6d0f",
4
+ "description": "The Skills agents.house serves its agents, upstream's verbatim beside its own, and the texts its agents read: the How-we-work document, the App guide and the Agent instructions",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "engines": {
11
+ "node": ">=24.21.0"
12
+ },
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "default": "./dist/index.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "set",
22
+ "upstream.json",
23
+ "how-we-work.md",
24
+ "how-we-work-earlier.json",
25
+ "app-guide.json",
26
+ "agent-instructions.json",
27
+ "README.md"
28
+ ]
29
+ }
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Matt Pocock
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,55 @@
1
+ # Phase boundaries
2
+
3
+ A **phase** is a chunk of work inside a session: the grilling, the implementation, the QA. The definition is fuzzy on purpose: a phase ends when you think *"ok, we're done with that"*.
4
+
5
+ The **phase boundary** is the gap between two phases, and it is the only place this decision belongs. Mid-phase there is no decision to make: continue, or split the work that's left into subagents. Compacting mid-phase makes the agent lose the thread.
6
+
7
+ ## The five options
8
+
9
+ | Option | What it does |
10
+ | ------------ | --------------------------------------------------------------- |
11
+ | **Continue** | Stay in the session. No context switch at all. |
12
+ | **`/clear`** | Empty the context window and start from nothing. |
13
+ | **`/handoff`** | Write a portable markdown file and seed a session anywhere with it. |
14
+ | **Subagent** | Send the task to its own context window and get a report back. |
15
+ | **`/compact`** | Compress this context and seed a fresh session with the summary. |
16
+
17
+ ## The tree
18
+
19
+ Work top to bottom at the boundary. The first **yes** wins.
20
+
21
+ **1. Can you continue in this session?** Two things make the answer yes: the next phase needs this phase as a **primary source**, or you have enough [smart zone](https://www.aihero.dev/ai-coding-dictionary/smart-zone) left (~150k tokens) for the next phase to fit. Grilling → implementation is the standard yes: the implementation wants the reasoning verbatim, not a summary of it. Continue costs nothing and loses nothing, so rule it out before anything else.
22
+
23
+ **2. Is the context irrelevant to what comes next?** Is everything in this session (the exploration, the decisions, the dead ends) disposable? If so, **`/clear`**. It is the cheapest move on the board: it takes no time and hands back the whole window. `/clear` also isn't terminal: the old session stays resumable.
24
+
25
+ The cost of getting this wrong is one-way. Clear a *relevant* context and you lose the **why** behind what you built, and no amount of reading the diff back gets it returned.
26
+
27
+ **3. Do you need to hand off?** `/handoff` is narrow. You need it only when you are:
28
+
29
+ - swapping to a **new harness** (Claude → Codex),
30
+ - moving to a **new directory** or repo,
31
+ - sending the work to a **colleague**,
32
+ - or forking a side task you found **mid-phase** without derailing what you're doing.
33
+
34
+ That list is the whole clause. What `/handoff` buys is **portability**: a file that travels. If nothing is travelling, you don't need it.
35
+
36
+ **4. Can the task be done AFK?** Is it scoped tightly enough to run with you away from the keyboard, no steering? Then send it to a **subagent** and leave this session untouched. Automated review is the standard case: the agent reads the diff and reports, and you aren't needed while it does.
37
+
38
+ **5. Otherwise, `/compact`.** Relevant context, same harness, same directory, and you need to stay in the loop: this is where the tree lands, and it lands here often. Pass it an instruction (`/compact we're going to QA this area`) so the summary keeps what the next phase needs.
39
+
40
+ `/compact` is the **default, not the first reach**. It sits at the bottom because the four questions above it are all cheaper or more precise. The failure mode when people start here is a fresh session that is confidently wrong about a decision the summary flattened.
41
+
42
+ ## Primary and secondary sources
43
+
44
+ Every move except **Continue** turns a **primary source** into a **secondary source**: the session as it happened, replaced by a summary of it. The trade is always the same shape:
45
+
46
+ | Source | Information | Noise | Room to move |
47
+ | --------------------------------- | ----------- | ----- | ------------ |
48
+ | Primary (Continue) | Full | Lots | Little |
49
+ | Secondary (`/compact`, `/handoff`) | Lossy | Less | Lots |
50
+
51
+ This is why question 1 comes first. You only pay the lossiness when staying costs more than it saves.
52
+
53
+ ## These are judgement calls
54
+
55
+ The questions are not objective: each has taste in it, and the same boundary can go two ways on two days. The value is in asking them **in order**, at the boundary rather than in the middle of the work.