spacehub

surshar / claude-code

Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.

Built by Anthropic · github.com/anthropics

sec-default: prompt.compose has its own row, a second case where a person's plugin asks first, and the declarations as the event shippedBrowse files
poteat authored 24 days ago parent 4b97909 · commit 6f268aef1466
6 changed files+47 -3compared with 4b97909
Mmods/sec-default/README.md
Mmods/sec-default/tests/fixtures/composed.ts
Mmods/sec-default/tests/fixtures/index.ts
Amods/sec-default/tests/fixtures/rewording.ts
Mmods/sec-default/tests/register.test.ts
Mmods/types/claude-code.d.ts
@@ -25,7 +25,8 @@
2525 | event | from the outermost seat |
2626 | --- | --- |
2727 | `classic.*` | Continue past the user tier: the organization's settings hooks see the engine's input and their answer stands. |
28-| `prompt.section`, `prompt.context`, `prompt.compose`, `skill.prompt`, `attribution.text` | Continue past the user tier: managed CLAUDE.md, rules and policy skills reach the model as written, and the system prompt's list of sections is composed by the organization's tiers and the built-ins alone. A person's plugins keep `prompt.submit` and its additive context. |
28+| `prompt.section`, `prompt.context`, `skill.prompt`, `attribution.text` | Continue past the user tier: managed CLAUDE.md, rules and policy skills reach the model as written. A person's plugins keep `prompt.submit` and its additive context. |
29+| `prompt.compose` | Continue past the user tier: the system prompt's list of sections is what the organization's tiers, the built-ins and the engine's own composition make it. A person's plugin neither drops, reorders nor rewrites a section, nor changes the facts the list is composed from, nor answers a list of its own in its place. The engine raises this event only when some loaded plugin hooks it, so where this plugin is seated every render of the system prompt runs the chain. |
2930 | `settings.read` | Continue past the user tier: no user hook rewrites what any caller reads as settings, this plugin's own policy reads included. |
3031 | `tool.describe`, `command.describe`, `agent.offer`, `agent.spawn` | When the subject's pinned `e.provider.tier` is `prepend` or `append` (a policy-installed plugin, the managed folder, a policy MCP server), continue past the user tier; a subject provided by `user`, `builtin` or `core` passes. |
3132 | `tool.register` | A caller in `prepend` or `append` continues past the user tier. A `user`-tier caller is refused by name while managed settings hold `allowedMcpServers` (set at all, empty included); otherwise it passes. |
@@ -4,8 +4,8 @@
44 * The facts of one render of the system prompt, as the engine raises them.
55 */
66 export const COMPOSED: PromptComposeInput = {
7- model: 'claude-sonnet-5',
8- promptModel: 'claude-sonnet-5',
7+ model: 'example-model-1',
8+ promptModel: 'example-model-1',
99 surfaces: ['terminal'],
1010 tools: ['Bash'],
1111 outputStyle: null,
@@ -26,6 +26,7 @@
2626 export * from './registered-tool-of.js'
2727 export * from './registering.js'
2828 export * from './relabeling.js'
29+export * from './rewording.js'
2930 export * from './server-policy.js'
3031 export * from './session.js'
3132 export * from './signing.js'
@@ -1,0 +1,23 @@
1+import type { Plugin } from 'claude-code/testing'
2+
3+/**
4+ * A plugin the person installed that asks for what is beneath it, then
5+ * drops the body and rewrites the text of every section it keeps.
6+ */
7+export const rewording: Plugin = {
8+ name: 'rewording',
9+ register(on) {
10+ on('prompt.compose', async ($, e, next) => {
11+ const { sections } = await next(e)
12+
13+ return {
14+ sections: sections
15+ .filter(section => section.id !== 'body')
16+ .map(section => ({
17+ ...section,
18+ text: `${section.text} (reworded)`,
19+ })),
20+ }
21+ })
22+ },
23+}
@@ -159,6 +159,7 @@
159159 "the system prompt's sections pass over the plugins the person installed",
160160 { plugins: [Fixtures.emptying, Fixtures.heading] },
161161 async ($, on) => {
162+ on('settings.read', () => ({ value: Fixtures.NO_ALLOWLIST }))
162163 on('prompt.compose', () => ({
163164 sections: [{ id: 'body', text: 'the body', scope: 'shared' }],
164165 }))
@@ -173,6 +174,24 @@
173174 )
174175
175176 test(
177+ "nor does a person's plugin drop or reword a section it asked for",
178+ { plugins: [Fixtures.rewording, Fixtures.heading] },
179+ async ($, on) => {
180+ on('settings.read', () => ({ value: Fixtures.NO_ALLOWLIST }))
181+ on('prompt.compose', () => ({
182+ sections: [{ id: 'body', text: 'the body', scope: 'shared' }],
183+ }))
184+
185+ expect(await $.prompt.compose(Fixtures.COMPOSED)).toEqual({
186+ sections: [
187+ { id: 'heading:org', text: 'the org says hi', scope: 'shared' },
188+ { id: 'body', text: 'the body', scope: 'shared' },
189+ ],
190+ })
191+ },
192+ )
193+
194+ test(
176195 "a user plugin's rewrite of policy is skipped for every other reader",
177196 {
178197 plugins: [
Large file, diff not shown.