-
Notifications
You must be signed in to change notification settings - Fork 961
feat(cli): add external issue tracking support #383
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
- Add `plx` as an alias command alongside `openspec` - Create OpenSplx pixel art logo assets (light/dark themes) - Update README with fork notice and Quick Start section - Make CLI command name dynamic based on invocation - Update completion system to support both command names - Add command-name utility for detecting invoked command
- Add YAML frontmatter parsing for tracked issues in proposal.md - Display issue identifiers in `openspec list` output - Include trackedIssues in `openspec show --json` output - Report tracked issues when archiving changes - Add External Issue Tracking section to AGENTS.md template - Add TrackedIssue schema and type exports
|
Caution Review failedThe pull request is closed. WalkthroughIntroduces a fork-branded "plx" CLI command alongside the original "openspec," adds external issue tracking to proposals via YAML frontmatter, and updates CLI components to dynamically detect and support multiple command names throughout completion generators and messaging. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related PRs
Suggested reviewers
Poem
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro ⛔ Files ignored due to path filters (2)
📒 Files selected for processing (25)
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This pull request adds external issue tracking support to enable integration with tools like Linear, GitHub Issues, and Jira. Additionally, it introduces a plx command alias and rebrands the project as OpenSplx (a fork of OpenSpec).
Key changes:
- YAML frontmatter parsing in proposal.md files to store tracked issue references (tracker, id, url)
- Display of tracked issue identifiers in CLI commands (
list,show,archive) - Agent guidance documentation for detecting, storing, and updating external issues
Reviewed changes
Copilot reviewed 24 out of 27 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/core/parsers/markdown-parser.ts | Adds YAML frontmatter extraction and tracked-issues parsing |
| src/core/parsers/change-parser.ts | Integrates frontmatter parsing into change parsing flow |
| src/core/schemas/change.schema.ts | Adds TrackedIssue schema and optional trackedIssues field to Change schema |
| src/core/schemas/index.ts | Exports TrackedIssue schema and type |
| src/core/list.ts | Displays first tracked issue ID in parentheses after change name |
| src/commands/change.ts | Includes trackedIssues in JSON output and displays issue ID in long format list |
| src/core/archive.ts | Reports tracked issue IDs when archiving changes |
| src/core/templates/agents-template.ts | Adds "External Issue Tracking" section with agent workflow guidance |
| openspec/AGENTS.md | Adds same "External Issue Tracking" section to project template |
| src/utils/command-name.ts | New utility to detect CLI command name from invocation (plx vs openspec) |
| src/cli/index.ts | Uses detected command name for CLI program name |
| src/core/completions/types.ts | Adds optional commandName parameter to CompletionGenerator.generate() |
| src/core/completions/generators/zsh-generator.ts | Parameterizes command name throughout completion script generation |
| src/commands/completion.ts | Passes detected command name to completion generator |
| scripts/postinstall.js | Updates completion install messages to mention both openspec and plx |
| package.json | Adds plx binary entry |
| bin/plx.js | New entry point that imports CLI with plx invocation name |
| README.md | Rebrands to OpenSplx with fork notice and OpenSpec comparison |
| assets/opensplx_pixel_*.svg | New logo assets for OpenSplx branding |
| test/core/completions/generators/zsh-generator.test.ts | Updates test expectation to use lowercase "openspec" in comment |
| openspec/changes/add-external-issue-tracking/* | Complete change proposal with specs and tasks |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /** | ||
| * Generates Zsh completion scripts for the OpenSpec CLI. | ||
| * Follows Zsh completion system conventions using the _openspec function. | ||
| * Generates Zsh completion scripts for the OpenSpec/OpenSplx CLI. |
Copilot
AI
Dec 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment references "OpenSpec/OpenSplx CLI" which is inconsistent with the branding used elsewhere in the codebase. Since this is a fork that adds the 'plx' alias but maintains compatibility, the comment should either reference the generic nature of the tool or just say it generates completion scripts without specific branding references.
| * Generates Zsh completion scripts for the OpenSpec/OpenSplx CLI. | |
| * Generates Zsh completion scripts for a CLI application. |
| const key = inlineMatch[1] as keyof TrackedIssue; | ||
| currentIssue[key] = inlineMatch[2].trim().replace(/^["']|["']$/g, ''); | ||
| } | ||
| } else if (currentIssue) { | ||
| const propMatch = line.match(/^\s+(\w+)\s*:\s*(.+)$/); | ||
| if (propMatch) { | ||
| const key = propMatch[1] as keyof TrackedIssue; | ||
| currentIssue[key] = propMatch[2].trim().replace(/^["']|["']$/g, ''); |
Copilot
AI
Dec 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The YAML parser does not validate that URLs are well-formed before storing them in the parsed result. While the TrackedIssueSchema includes URL validation via zod's .url() method, the parser stores potentially invalid URLs that will only fail validation later when the Change schema is applied. Consider validating URLs during parsing or adding a try-catch to handle malformed URLs gracefully, providing better error messages to users about which line in the frontmatter is problematic.
| static extractFrontmatter(content: string): FrontmatterResult { | ||
| const lines = content.split('\n'); | ||
|
|
||
| if (lines.length === 0 || lines[0].trim() !== '---') { | ||
| return { frontmatter: null, content }; | ||
| } | ||
|
|
||
| let endIndex = -1; | ||
| for (let i = 1; i < lines.length; i++) { | ||
| if (lines[i].trim() === '---') { | ||
| endIndex = i; | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| if (endIndex === -1) { | ||
| return { frontmatter: null, content }; | ||
| } | ||
|
|
||
| const yamlLines = lines.slice(1, endIndex); | ||
| const remainingContent = lines.slice(endIndex + 1).join('\n'); | ||
|
|
||
| const frontmatter = MarkdownParser.parseSimpleYaml(yamlLines); | ||
|
|
||
| return { frontmatter, content: remainingContent }; | ||
| } | ||
|
|
||
| private static parseSimpleYaml(lines: string[]): Frontmatter { | ||
| const result: Frontmatter = { raw: {} }; | ||
| const trackedIssues: TrackedIssue[] = []; | ||
|
|
||
| let inTrackedIssues = false; | ||
| let currentIssue: Partial<TrackedIssue> | null = null; | ||
|
|
||
| for (const line of lines) { | ||
| if (line.trim() === '' || line.trim().startsWith('#')) continue; | ||
|
|
||
| const topLevelMatch = line.match(/^(\w[\w-]*)\s*:/); | ||
| if (topLevelMatch && !line.startsWith(' ') && !line.startsWith('\t')) { | ||
| if (topLevelMatch[1] === 'tracked-issues') { | ||
| inTrackedIssues = true; | ||
| continue; | ||
| } else { | ||
| inTrackedIssues = false; | ||
| currentIssue = null; | ||
| } | ||
| } | ||
|
|
||
| if (inTrackedIssues) { | ||
| if (line.match(/^\s+-\s+\w/)) { | ||
| if (currentIssue && currentIssue.tracker && currentIssue.id && currentIssue.url) { | ||
| trackedIssues.push(currentIssue as TrackedIssue); | ||
| } | ||
| currentIssue = {}; | ||
| const inlineMatch = line.match(/^\s+-\s+(\w+)\s*:\s*(.+)$/); | ||
| if (inlineMatch) { | ||
| const key = inlineMatch[1] as keyof TrackedIssue; | ||
| currentIssue[key] = inlineMatch[2].trim().replace(/^["']|["']$/g, ''); | ||
| } | ||
| } else if (currentIssue) { | ||
| const propMatch = line.match(/^\s+(\w+)\s*:\s*(.+)$/); | ||
| if (propMatch) { | ||
| const key = propMatch[1] as keyof TrackedIssue; | ||
| currentIssue[key] = propMatch[2].trim().replace(/^["']|["']$/g, ''); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (currentIssue && currentIssue.tracker && currentIssue.id && currentIssue.url) { | ||
| trackedIssues.push(currentIssue as TrackedIssue); | ||
| } | ||
|
|
||
| if (trackedIssues.length > 0) { | ||
| result.trackedIssues = trackedIssues; | ||
| } | ||
|
|
||
| return result; | ||
| } |
Copilot
AI
Dec 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The frontmatter extraction and tracked issues parsing logic lacks test coverage. While there are existing tests for MarkdownParser in test/core/parsers/markdown-parser.test.ts, no tests verify the new frontmatter functionality. Consider adding tests for: parsing valid tracked-issues frontmatter, handling missing or malformed frontmatter, handling multiple tracked issues, and validating that invalid URLs are caught appropriately.
| // Try to get tracked issue from proposal.md frontmatter | ||
| let trackedIssue: string | undefined; | ||
| try { | ||
| const proposalPath = path.join(changesDir, changeDir, 'proposal.md'); | ||
| const proposalContent = await fs.readFile(proposalPath, 'utf-8'); | ||
| const parser = new MarkdownParser(proposalContent); | ||
| const frontmatter = parser.getFrontmatter(); | ||
| if (frontmatter?.trackedIssues && frontmatter.trackedIssues.length > 0) { | ||
| trackedIssue = frontmatter.trackedIssues[0].id; | ||
| } | ||
| } catch { | ||
| // proposal.md might not exist or be unreadable | ||
| } | ||
|
|
||
| changes.push({ | ||
| name: changeDir, | ||
| completedTasks: progress.completed, | ||
| totalTasks: progress.total | ||
| totalTasks: progress.total, | ||
| trackedIssue, | ||
| }); |
Copilot
AI
Dec 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The tracked issues feature in the list command lacks test coverage. The existing tests in test/core/list.test.ts don't verify that tracked issues are displayed correctly when present in proposal.md frontmatter. Consider adding a test case that creates a proposal.md with tracked-issues frontmatter and verifies the issue ID appears in the list output.
| // Get tracked issues before moving the change | ||
| let trackedIssues: TrackedIssue[] = []; | ||
| try { | ||
| const proposalPath = path.join(changeDir, 'proposal.md'); | ||
| const proposalContent = await fs.readFile(proposalPath, 'utf-8'); | ||
| const parser = new MarkdownParser(proposalContent); | ||
| const frontmatter = parser.getFrontmatter(); | ||
| if (frontmatter?.trackedIssues) { | ||
| trackedIssues = frontmatter.trackedIssues; | ||
| } | ||
| } catch { | ||
| // proposal.md might not exist or be unreadable | ||
| } | ||
|
|
||
| // Move change to archive | ||
| await fs.rename(changeDir, archivePath); | ||
|
|
||
| console.log(`Change '${changeName}' archived as '${archiveName}'.`); | ||
|
|
||
| // Display archive success with tracked issues if present | ||
| const issueDisplay = trackedIssues.length > 0 | ||
| ? ` (${trackedIssues.map(i => i.id).join(', ')})` | ||
| : ''; | ||
| console.log(`Change '${changeName}'${issueDisplay} archived as '${archiveName}'.`); |
Copilot
AI
Dec 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The tracked issues display in archive command lacks test coverage. Consider adding a test case that creates a change with tracked issues and verifies the issue IDs are displayed in the archive success message.
Summary
openspec listoutput (e.g.,change-name (SM-123) [tasks])trackedIssuesinopenspec show --jsonoutputFrontmatter Format
Test plan
openspec listwith a proposal containing tracked-issues frontmatter - should show issue ID in parenthesesopenspec show <change> --json- should includetrackedIssuesarrayopenspec archive <change>- should report issue IDs in success messageopenspec updatein a project - should include External Issue Tracking section in AGENTS.mdSummary by CodeRabbit
New Features
plxcommand available as an alternative CLI entry point.Documentation
✏️ Tip: You can customize this high-level summary in your review settings.