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: the system prompt's sections continue past the user tier; the declarations carry prompt.composeBrowse files
poteat authored 28 days ago parent e1bb7b0 · commit 690f3d7fe1e7
8 changed files+75 -4compared with e1bb7b0
Mmods/sec-default/README.md
Mmods/sec-default/hooks/register.ts
Amods/sec-default/tests/fixtures/composed.ts
Amods/sec-default/tests/fixtures/emptying.ts
Amods/sec-default/tests/fixtures/heading.ts
Mmods/sec-default/tests/fixtures/index.ts
Mmods/sec-default/tests/register.test.ts
Mmods/types/claude-code.d.ts
@@ -24,7 +24,7 @@
2424 | event | from the outermost seat |
2525 | --- | --- |
2626 | `classic.*` | Continue past the user tier: the organization's settings hooks see the engine's input and their answer stands. |
27-| `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. |
27+| `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. |
2828 | `settings.read` | Continue past the user tier: no user hook rewrites what any caller reads as settings, this plugin's own policy reads included. |
2929 | `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. |
3030 | `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. |
@@ -33,9 +33,10 @@
3333
3434 ## What it hooks
3535
36-`classic.*`, `prompt.section`, `prompt.context`, `skill.prompt`,
37-`attribution.text`, `settings.read`, `tool.describe`, `command.describe`,
38-`agent.offer`, `agent.spawn`, `tool.register`, `tool.list`.
36+`classic.*`, `prompt.section`, `prompt.context`, `prompt.compose`,
37+`skill.prompt`, `attribution.text`, `settings.read`, `tool.describe`,
38+`command.describe`, `agent.offer`, `agent.spawn`, `tool.register`,
39+`tool.list`.
3940
4041 ## What it calls on `$`
4142
@@ -21,6 +21,7 @@
2121
2222 on('prompt.section', ($, e, next) => next.to(e, 'append'))
2323 on('prompt.context', ($, e, next) => next.to(e, 'append'))
24+ on('prompt.compose', ($, e, next) => next.to(e, 'append'))
2425 on('skill.prompt', ($, e, next) => next.to(e, 'append'))
2526 on('attribution.text', ($, e, next) => next.to(e, 'append'))
2627
@@ -1,0 +1,13 @@
1+import type { PromptComposeInput } from 'claude-code'
2+
3+/**
4+ * The facts of one render of the system prompt, as the engine raises them.
5+ */
6+export const COMPOSED: PromptComposeInput = {
7+ model: 'claude-sonnet-5',
8+ promptModel: 'claude-sonnet-5',
9+ surfaces: ['terminal'],
10+ tools: ['Bash'],
11+ outputStyle: null,
12+ traits: [],
13+}
@@ -1,0 +1,14 @@
1+import type { Plugin } from 'claude-code/testing'
2+
3+/**
4+ * A plugin the person installed that answers a system prompt of its own
5+ * and asks nothing of what is beneath it.
6+ */
7+export const emptying: Plugin = {
8+ name: 'emptying',
9+ register(on) {
10+ on('prompt.compose', () => ({
11+ sections: [{ id: 'emptying:all', text: 'mine alone', scope: 'session' }],
12+ }))
13+ },
14+}
@@ -1,0 +1,22 @@
1+import type { Plugin } from 'claude-code/testing'
2+
3+/**
4+ * The organization's own plugin, in its last tier, which puts its section
5+ * at the head of the list beneath it.
6+ */
7+export const heading: Plugin = {
8+ name: 'heading',
9+ tier: 'append',
10+ register(on) {
11+ on('prompt.compose', async ($, e, next) => {
12+ const { sections } = await next(e)
13+
14+ return {
15+ sections: [
16+ { id: 'heading:org', text: 'the org says hi', scope: 'shared' },
17+ ...sections,
18+ ],
19+ }
20+ })
21+ },
22+}
@@ -2,9 +2,12 @@
22 export * from './agent-spawned.js'
33 export * from './allowlist.js'
44 export * from './command-described.js'
5+export * from './composed.js'
56 export * from './denying.js'
67 export * from './dropping.js'
8+export * from './emptying.js'
79 export * from './fullscreen.js'
10+export * from './heading.js'
811 export * from './listing.js'
912 export * from './managed-policy.js'
1013 export * from './marking.js'
@@ -138,6 +138,23 @@
138138 )
139139
140140 test(
141+ "the system prompt's sections pass over the plugins the person installed",
142+ { plugins: [Fixtures.emptying, Fixtures.heading] },
143+ async ($, on) => {
144+ on('prompt.compose', () => ({
145+ sections: [{ id: 'body', text: 'the body', scope: 'shared' }],
146+ }))
147+
148+ expect(await $.prompt.compose(Fixtures.COMPOSED)).toEqual({
149+ sections: [
150+ { id: 'heading:org', text: 'the org says hi', scope: 'shared' },
151+ { id: 'body', text: 'the body', scope: 'shared' },
152+ ],
153+ })
154+ },
155+ )
156+
157+ test(
141158 "a user plugin's rewrite of policy is skipped for every other reader",
142159 {
143160 plugins: [
Large file, diff not shown.