diff --git a/apps/cli/ai/block-settings.ts b/apps/cli/ai/block-settings.ts
new file mode 100644
index 0000000000..759df20079
--- /dev/null
+++ b/apps/cli/ai/block-settings.ts
@@ -0,0 +1,73 @@
+import { readFileSync } from 'fs';
+import path from 'path';
+
+// Matches the opening comment of a block in serialized markup, e.g.
+// `` or ``. Closing
+// comments (``) do not match.
+const BLOCK_COMMENT = /
+
+
+
+
+
+`;
+
+describe( 'createBlockSettingsNotes', () => {
+ let blocksDir: string;
+
+ function declare( slug: string, attributes: Record< string, unknown > ) {
+ mkdirSync( path.join( blocksDir, slug ), { recursive: true } );
+ writeFileSync(
+ path.join( blocksDir, slug, 'block.json' ),
+ JSON.stringify( { name: `core/${ slug }`, attributes } )
+ );
+ }
+
+ beforeEach( () => {
+ blocksDir = path.join( mkdtempSync( path.join( tmpdir(), 'block-settings-' ) ), 'blocks' );
+ declare( 'navigation', {
+ ref: { type: 'number' },
+ submenuVisibility: { type: 'string', enum: [ 'hover', 'click', 'always' ], default: 'hover' },
+ showSubmenuIcon: { type: 'boolean', default: true },
+ __unstableLocation: { type: 'string' },
+ } );
+ declare( 'site-title', { level: { type: 'number', default: 1 } } );
+ declare( 'group', { tagName: { type: 'string', default: 'div' } } );
+ declare( 'paragraph', { content: { type: 'rich-text', source: 'rich-text', selector: 'p' } } );
+ } );
+
+ afterEach( () => {
+ rmSync( path.dirname( blocksDir ), { recursive: true, force: true } );
+ } );
+
+ it( 'describes each core block type in the markup from its block.json', () => {
+ const notes = createBlockSettingsNotes( blocksDir )( HEADER ).split( '\n' );
+
+ const where = blocksDir.split( path.sep ).join( '/' );
+ expect( notes ).toEqual( [
+ `core/group settings, declared in ${ where }/group/block.json: tagName (string, default "div").`,
+ `core/site-title settings, declared in ${ where }/site-title/block.json: level (number, default 1).`,
+ `core/navigation settings, declared in ${ where }/navigation/block.json: ref (number), submenuVisibility ("hover"|"click"|"always", default "hover"), showSubmenuIcon (boolean, default true).`,
+ ] );
+ } );
+
+ it( 'describes a block type once', () => {
+ const notes = createBlockSettingsNotes( blocksDir );
+
+ expect( notes( HEADER ) ).toContain( 'core/navigation' );
+ expect( notes( '' ) ).toBe( '' );
+ } );
+
+ it( 'leaves out content attributes, closing comments and undeclared blocks', () => {
+ const notes = createBlockSettingsNotes( blocksDir );
+
+ expect( notes( 'Hi
' ) ).toBe( '' );
+ expect( notes( '' ) ).toBe( '' );
+ expect( notes( 'No blocks here.' ) ).toBe( '' );
+ } );
+} );