@@ -29,6 +29,13 @@ export function getPackageSchemasDir(): string {
2929 return path . join ( path . dirname ( currentFile ) , '..' , '..' , '..' , 'schemas' ) ;
3030}
3131
32+ /**
33+ * Gets the project's local schemas directory path.
34+ */
35+ export function getProjectSchemasDir ( ) : string {
36+ return path . join ( process . cwd ( ) , 'openspec' , 'schemas' ) ;
37+ }
38+
3239/**
3340 * Gets the user's schema override directory path.
3441 */
@@ -40,21 +47,29 @@ export function getUserSchemasDir(): string {
4047 * Resolves a schema name to its directory path.
4148 *
4249 * Resolution order:
43- * 1. User override: ${XDG_DATA_HOME}/openspec/schemas/<name>/schema.yaml
44- * 2. Package built-in: <package>/schemas/<name>/schema.yaml
50+ * 1. Project-local: <cwd>/openspec/schemas/<name>/schema.yaml
51+ * 2. User override: ${XDG_DATA_HOME}/openspec/schemas/<name>/schema.yaml
52+ * 3. Package built-in: <package>/schemas/<name>/schema.yaml
4553 *
4654 * @param name - Schema name (e.g., "spec-driven")
4755 * @returns The path to the schema directory, or null if not found
4856 */
4957export function getSchemaDir ( name : string ) : string | null {
50- // 1. Check user override directory
58+ // 1. Check project-local directory
59+ const projectDir = path . join ( getProjectSchemasDir ( ) , name ) ;
60+ const projectSchemaPath = path . join ( projectDir , 'schema.yaml' ) ;
61+ if ( fs . existsSync ( projectSchemaPath ) ) {
62+ return projectDir ;
63+ }
64+
65+ // 2. Check user override directory
5166 const userDir = path . join ( getUserSchemasDir ( ) , name ) ;
5267 const userSchemaPath = path . join ( userDir , 'schema.yaml' ) ;
5368 if ( fs . existsSync ( userSchemaPath ) ) {
5469 return userDir ;
5570 }
5671
57- // 2 . Check package built-in directory
72+ // 3 . Check package built-in directory
5873 const packageDir = path . join ( getPackageSchemasDir ( ) , name ) ;
5974 const packageSchemaPath = path . join ( packageDir , 'schema.yaml' ) ;
6075 if ( fs . existsSync ( packageSchemaPath ) ) {
@@ -123,7 +138,7 @@ export function resolveSchema(name: string): SchemaYaml {
123138
124139/**
125140 * Lists all available schema names.
126- * Combines user override and package built-in schemas.
141+ * Combines project-local, user override and package built-in schemas.
127142 */
128143export function listSchemas ( ) : string [ ] {
129144 const schemas = new Set < string > ( ) ;
@@ -154,6 +169,19 @@ export function listSchemas(): string[] {
154169 }
155170 }
156171
172+ // Add project-local schemas (may override both user and package schemas)
173+ const projectDir = getProjectSchemasDir ( ) ;
174+ if ( fs . existsSync ( projectDir ) ) {
175+ for ( const entry of fs . readdirSync ( projectDir , { withFileTypes : true } ) ) {
176+ if ( entry . isDirectory ( ) ) {
177+ const schemaPath = path . join ( projectDir , entry . name , 'schema.yaml' ) ;
178+ if ( fs . existsSync ( schemaPath ) ) {
179+ schemas . add ( entry . name ) ;
180+ }
181+ }
182+ }
183+ }
184+
157185 return Array . from ( schemas ) . sort ( ) ;
158186}
159187
@@ -164,7 +192,7 @@ export interface SchemaInfo {
164192 name : string ;
165193 description : string ;
166194 artifacts : string [ ] ;
167- source : 'package' | 'user' ;
195+ source : 'package' | 'user' | 'project' ;
168196}
169197
170198/**
@@ -175,11 +203,35 @@ export function listSchemasWithInfo(): SchemaInfo[] {
175203 const schemas : SchemaInfo [ ] = [ ] ;
176204 const seenNames = new Set < string > ( ) ;
177205
178- // Add user override schemas first (they take precedence)
206+ // Add project-local schemas first (highest precedence)
207+ const projectDir = getProjectSchemasDir ( ) ;
208+ if ( fs . existsSync ( projectDir ) ) {
209+ for ( const entry of fs . readdirSync ( projectDir , { withFileTypes : true } ) ) {
210+ if ( entry . isDirectory ( ) ) {
211+ const schemaPath = path . join ( projectDir , entry . name , 'schema.yaml' ) ;
212+ if ( fs . existsSync ( schemaPath ) ) {
213+ try {
214+ const schema = parseSchema ( fs . readFileSync ( schemaPath , 'utf-8' ) ) ;
215+ schemas . push ( {
216+ name : entry . name ,
217+ description : schema . description || '' ,
218+ artifacts : schema . artifacts . map ( ( a ) => a . id ) ,
219+ source : 'project' ,
220+ } ) ;
221+ seenNames . add ( entry . name ) ;
222+ } catch {
223+ // Skip invalid schemas
224+ }
225+ }
226+ }
227+ }
228+ }
229+
230+ // Add user override schemas next
179231 const userDir = getUserSchemasDir ( ) ;
180232 if ( fs . existsSync ( userDir ) ) {
181233 for ( const entry of fs . readdirSync ( userDir , { withFileTypes : true } ) ) {
182- if ( entry . isDirectory ( ) ) {
234+ if ( entry . isDirectory ( ) && ! seenNames . has ( entry . name ) ) {
183235 const schemaPath = path . join ( userDir , entry . name , 'schema.yaml' ) ;
184236 if ( fs . existsSync ( schemaPath ) ) {
185237 try {
0 commit comments