Skip to content

Add custom schema support to openspec init#1115

Open
tsangint wants to merge 1 commit into
Fission-AI:mainfrom
tsangint:feat/init-custom-schema
Open

Add custom schema support to openspec init#1115
tsangint wants to merge 1 commit into
Fission-AI:mainfrom
tsangint:feat/init-custom-schema

Conversation

@tsangint
Copy link
Copy Markdown

@tsangint tsangint commented May 22, 2026

Summary

Adds custom schema support to openspec init.

Users can now initialize a project with an existing workflow schema via
--schema <name>, or import an external schema bundle directory via
--schema-source <dir>.

Usage

Use an already resolvable schema:

openspec init --schema my-workflow

Import an external schema bundle directory:

openspec init --schema-source ../schemas/my-workflow

The imported schema bundle must be a directory containing schema.yaml and its
templates. OpenSpec reads the schema name from schema.yaml, copies the bundle
into:

openspec/schemas/<name>/

and writes:

schema: <name>

to openspec/config.yaml.

If both --schema and --schema-source are provided, their schema names must
match.

Use Case

Teams may manage shared OpenSpec schemas outside individual repositories, such
as in an internal platform or tooling project that standardizes proposal,
design, spec, and task templates across many codebases.

Previously, those teams had to initialize with the default schema, manually copy
schema files into the target project, and edit openspec/config.yaml. This
change makes that workflow a single init command while keeping the target
project self-contained and reproducible.

Changes

Adds openspec init --schema
Adds openspec init --schema-source


Imports schema bundle directories into openspec/schemas//
Validates imported schema structure and referenced templates before setup
Preserves existing openspec/config.yaml / config.yml
Updates shell completion metadata
Adds focused unit and CLI e2e coverage
Updates CLI and customization docs

Summary by CodeRabbit

  • New Features

    • Non-interactive schema options for init: --schema and --schema-source ; CLI help and shell completion updated.
  • Documentation

    • Docs updated with usage, examples, schema naming/consistency rules, and init behavior (including import and config generation notes).
  • Tests

    • Added end-to-end and unit tests covering the new flags, schema import, config creation, and validation scenarios.

Review Change Stack

@tsangint tsangint requested a review from TabishB as a code owner May 22, 2026 03:25
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 22, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 68bb9283-06d7-44d6-9d84-87f0d2a85f64

📥 Commits

Reviewing files that changed from the base of the PR and between 96c1dfb and db32a2f.

📒 Files selected for processing (7)
  • docs/cli.md
  • docs/customization.md
  • src/cli/index.ts
  • src/core/completions/command-registry.ts
  • src/core/init.ts
  • test/cli-e2e/basic.test.ts
  • test/core/init.test.ts
✅ Files skipped from review due to trivial changes (2)
  • docs/cli.md
  • docs/customization.md

📝 Walkthrough

Walkthrough

Adds non-interactive schema selection to openspec init via --schema and --schema-source. Flags are wired through CLI, completion metadata, and InitCommand; InitCommand validates/imports schema bundles, resolves the schema name, updates config generation to persist the schema, and adds unit and e2e tests.

Changes

Non-interactive schema selection

Layer / File(s) Summary
CLI surface: documentation and wiring
docs/cli.md, docs/customization.md, src/cli/index.ts, src/core/completions/command-registry.ts
Documentation adds --schema <name> and --schema-source <dir> usage and matching rules. CLI parsing accepts and forwards schema and schemaSource into InitCommand. Completion metadata registers the new flags for shell completion.
InitCommand options and execution flow
src/core/init.ts
InitCommand gains schema and schemaSource fields; execute() resolves the schema setup plan and validates profile overrides early in the flow.
Schema planning, import, and config output
src/core/init.ts
Implements schema setup planning and validation, copies/imports schema bundles into openspec/schemas/<name> with safety checks, updates createConfig to accept the resolved schemaName and adjust non-interactive creation, and changes success output to report the configured schema.
Tests: e2e and unit
test/cli-e2e/basic.test.ts, test/core/init.test.ts
E2E tests verify help flags and non-interactive --schema/--schema-source behavior; unit tests exercise successful imports and rejection cases (invalid source, name mismatch, missing templates, unknown schemas). Adds createSchemaBundle test helper.

🎯 3 (Moderate) | ⏱️ ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI as openspec CLI
  participant Init as InitCommand
  participant Schema as SchemaResolver
  participant FS as Filesystem
  participant Config as ConfigWriter

  CLI->>Init: invoke init with { --schema, --schema-source, --force, --profile, --tools }
  Init->>Schema: parseSchema / resolveSchema (load schema.yaml from source when provided)
  Schema-->>Init: schemaName (from bundle or built-in)
  Init->>FS: copy schema bundle -> openspec/schemas/<schemaName> (rm+cp) [when importing]
  Init->>Config: createConfig(schemaName, ...)
  Config-->>Init: config written
  Init-->>CLI: displaySuccessMessage(configStatus, schemaName)
Loading

Possibly related PRs

  • Fission-AI/OpenSpec#537: Both PRs affect how openspec/config.yaml is produced and interact with schema/config creation logic.

Suggested labels

codex

Suggested reviewers

  • TabishB

Poem

🐰 A schema, once hidden in prompts so deep,
Now flows through flags for init to keep,
Bundles copied, names checked with care,
Config remembers what we declare —
Hooray for painless setup everywhere!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add custom schema support to openspec init' clearly and accurately summarizes the main change: introducing new CLI options (--schema and --schema-source) to enable schema-aware initialization in the openspec init command.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@tsangint tsangint force-pushed the feat/init-custom-schema branch from 1aee590 to 4a892cb Compare May 22, 2026 03:41
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/core/init.ts`:
- Around line 332-345: The validation currently allows artifact.template values
that resolve outside the schema bundle root; update
validateSchemaSourceTemplates to resolve each candidate path (for the
'templates' subfolder and root) using path.resolve and ensure the resolved path
is inside sourceDir (e.g., via path.relative or startsWith check) before
accepting it; if neither resolved candidate exists or both resolve outside
sourceDir, throw the same error referencing artifact.template and artifact.id.
Use the existing validateSchemaSourceTemplates function, the artifact.template
value, and sourceDir as the identifiers to locate and fix the logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 86fa0d66-0385-4a12-bdb8-8b88d3c05e23

📥 Commits

Reviewing files that changed from the base of the PR and between 1aee590 and 4a892cb.

📒 Files selected for processing (7)
  • docs/cli.md
  • docs/customization.md
  • src/cli/index.ts
  • src/core/completions/command-registry.ts
  • src/core/init.ts
  • test/cli-e2e/basic.test.ts
  • test/core/init.test.ts
✅ Files skipped from review due to trivial changes (2)
  • docs/customization.md
  • docs/cli.md

Comment thread src/core/init.ts
@tsangint tsangint force-pushed the feat/init-custom-schema branch from 4a892cb to 96c1dfb Compare May 22, 2026 03:54
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/core/init.ts`:
- Around line 332-348: In validateSchemaSourceTemplates, the current check uses
lexical containment and fs.existsSync which allows symlink escape; change it to
canonicalize the sourceDir once (use FileSystemUtils.canonicalizeExistingPath or
realpathSync) and for each candidate path (templatePathInTemplates and
templatePathInRoot) canonicalize those existing targets as well and then use
isSameOrDescendant against the canonical sourceDir to reject any template whose
realpath lies outside the sourceDir; alternatively, explicitly detect symlinks
with fs.lstatSync(...).isSymbolicLink() and reject them before accepting the
template so imports (cp with preserved symlinks) cannot escape the bundle.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d4b7920e-de44-42fa-9427-e695223b62a1

📥 Commits

Reviewing files that changed from the base of the PR and between 4a892cb and 96c1dfb.

📒 Files selected for processing (7)
  • docs/cli.md
  • docs/customization.md
  • src/cli/index.ts
  • src/core/completions/command-registry.ts
  • src/core/init.ts
  • test/cli-e2e/basic.test.ts
  • test/core/init.test.ts
✅ Files skipped from review due to trivial changes (1)
  • docs/cli.md

Comment thread src/core/init.ts
Allow `openspec init` to initialize projects with a requested workflow schema and import a schema bundle directory into `openspec/schemas/<name>/`.

The imported schema is validated before setup continues, existing configs are preserved, and the new options are documented with focused CLI and init tests.
@tsangint tsangint force-pushed the feat/init-custom-schema branch from 96c1dfb to db32a2f Compare May 22, 2026 04:05
Copy link
Copy Markdown
Collaborator

@alfred-openspec alfred-openspec left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is directionally good. Init-time schema import is a real workflow need, and the referenced-template path escape issue looks fixed.

One remaining safety issue before approval: --schema-source still copies the whole source directory recursively, so unreferenced symlinks inside the bundle can be preserved into openspec/schemas/<name>/ even when schema.yaml does not reference them. Please reject symlinks anywhere in the source bundle, or use an explicit safe-copy path that refuses them, and add a regression test for an unreferenced symlink.

Also note GitHub currently reports this PR as conflicting with main. I checked the diff against the init/schema path and ran pnpm exec vitest run test/core/init.test.ts test/cli-e2e/basic.test.ts locally on the PR head, which passed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants