sequant 1.11.0 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,12 +9,31 @@ Solve GitHub issues with structured phases and quality gates — from issue to m
9
9
 
10
10
  ## Quick Start
11
11
 
12
+ ### Option A: Install as Claude Code Plugin (Recommended)
13
+
14
+ ```bash
15
+ # Add the Sequant marketplace
16
+ /plugin marketplace add admarble/sequant
17
+
18
+ # Install the plugin
19
+ /plugin install sequant
20
+ ```
21
+
22
+ Then run setup:
23
+ ```
24
+ /sequant:setup # Initialize worktrees directory
25
+ ```
26
+
27
+ ### Option B: Install via npm
28
+
12
29
  ```bash
13
30
  # In your project directory
14
31
  npx sequant init
15
32
  npx sequant doctor # Verify setup
16
33
  ```
17
34
 
35
+ ### Start Using
36
+
18
37
  Then in Claude Code:
19
38
 
20
39
  ```
@@ -31,9 +50,20 @@ Or step-by-step:
31
50
 
32
51
  ### Prerequisites
33
52
 
53
+ **Required:**
34
54
  - [Claude Code](https://claude.ai/code) — AI coding assistant
35
55
  - [GitHub CLI](https://cli.github.com/) — run `gh auth login`
36
- - Node.js 18+ and Git
56
+ - Git (for worktree-based isolation)
57
+
58
+ **For npm installation:**
59
+ - Node.js 18+
60
+
61
+ **Optional MCP servers (enhanced features):**
62
+ - `chrome-devtools` — enables `/test` for browser-based UI testing
63
+ - `sequential-thinking` — enhanced reasoning for complex decisions
64
+ - `context7` — library documentation lookup
65
+
66
+ > **Note:** Sequant is optimized for Node.js/TypeScript projects. The worktree workflow works with any git repository.
37
67
 
38
68
  ---
39
69
 
@@ -52,6 +82,24 @@ Sequant adds slash commands to Claude Code that enforce a structured workflow:
52
82
 
53
83
  > `/test` is optional — used for UI features when Chrome DevTools MCP is available.
54
84
 
85
+ ### Worktree Isolation
86
+
87
+ Sequant uses Git worktrees to isolate each issue's work:
88
+
89
+ ```
90
+ your-project/ # Main repo (stays on main branch)
91
+ ../worktrees/
92
+ feature/
93
+ 123-add-login/ # Issue #123 worktree (feature branch)
94
+ 456-fix-bug/ # Issue #456 worktree (feature branch)
95
+ ```
96
+
97
+ **Why worktrees?**
98
+ - Work on multiple issues simultaneously
99
+ - Never pollute your main branch
100
+ - Each issue has its own dependencies and build
101
+ - Safe to discard failed experiments
102
+
55
103
  ### Quality Gates
56
104
 
57
105
  Every `/qa` runs automated checks:
@@ -59,6 +107,7 @@ Every `/qa` runs automated checks:
59
107
  - **AC Adherence** — Code verified against acceptance criteria
60
108
  - **Type Safety** — Detects `any`, `as any`, missing types
61
109
  - **Security Scans** — OWASP-style vulnerability detection
110
+ - **Semgrep Static Analysis** — Stack-aware rulesets, custom rules via `.sequant/semgrep-rules.yaml`
62
111
  - **Scope Analysis** — Flags changes outside issue scope
63
112
  - **Execution Evidence** — Scripts/CLI must pass smoke tests
64
113
  - **Test Quality** — Validates test coverage and mock hygiene
@@ -115,9 +164,9 @@ npx sequant run 123 --base feature/dashboard # Custom base branch
115
164
 
116
165
  | Command | Purpose |
117
166
  |---------|---------|
118
- | `/merger` | Multi-issue integration and merge |
119
167
  | `/testgen` | Generate test stubs from spec |
120
168
  | `/verify` | CLI/script execution verification |
169
+ | `/setup` | Initialize Sequant in a project |
121
170
 
122
171
  ### Utilities
123
172
 
@@ -126,6 +175,7 @@ npx sequant run 123 --base feature/dashboard # Custom base branch
126
175
  | `/assess` | Issue triage and status assessment |
127
176
  | `/docs` | Generate feature documentation |
128
177
  | `/clean` | Repository cleanup |
178
+ | `/improve` | Codebase analysis and improvement discovery |
129
179
  | `/security-review` | Deep security analysis |
130
180
  | `/reflect` | Workflow improvement analysis |
131
181
 
@@ -141,6 +191,7 @@ npx sequant status # Show version and config
141
191
  npx sequant run <issues...> # Execute workflow
142
192
  npx sequant state <cmd> # Manage workflow state (init/rebuild/clean)
143
193
  npx sequant stats # View local workflow analytics
194
+ npx sequant dashboard # Launch real-time workflow dashboard
144
195
  ```
145
196
 
146
197
  See [Run Command Options](docs/run-command.md), [State Command](docs/state-command.md), and [Analytics](docs/analytics.md) for details.
@@ -178,16 +229,49 @@ See [Customization Guide](docs/customization.md) for all options.
178
229
  ## Documentation
179
230
 
180
231
  - [Getting Started](docs/getting-started/installation.md)
232
+ - [What We've Built](docs/what-weve-built.md) — Comprehensive project overview
181
233
  - [Workflow Concepts](docs/concepts/workflow-phases.md)
182
234
  - [Run Command](docs/run-command.md)
183
235
  - [Feature Branch Workflows](docs/feature-branch-workflow.md)
184
236
  - [Customization](docs/customization.md)
237
+ - [Plugin Updates & Versioning](docs/plugin-updates.md)
185
238
  - [Troubleshooting](docs/troubleshooting.md)
186
239
 
187
240
  Stack guides: [Next.js](docs/stacks/nextjs.md) · [Rust](docs/stacks/rust.md) · [Python](docs/stacks/python.md) · [Go](docs/stacks/go.md)
188
241
 
189
242
  ---
190
243
 
244
+ ## Feedback & Contributing
245
+
246
+ ### Reporting Issues
247
+
248
+ - **Plugin issues:** [Plugin Feedback template](https://github.com/admarble/sequant/issues/new?template=plugin-feedback.yml)
249
+ - **Bug reports:** [Bug template](https://github.com/admarble/sequant/issues/new?template=bug.yml)
250
+ - **Feature requests:** [Feature template](https://github.com/admarble/sequant/issues/new?template=feature.yml)
251
+ - **Questions:** [GitHub Discussions](https://github.com/admarble/sequant/discussions)
252
+
253
+ ### Using `/improve` for Feedback
254
+
255
+ Run `/improve` in Claude Code to analyze your codebase and create structured issues:
256
+
257
+ ```
258
+ /improve # Analyze entire codebase
259
+ /improve security # Focus on security concerns
260
+ /improve tests # Find test coverage gaps
261
+ ```
262
+
263
+ The skill will present findings and offer to create GitHub issues automatically.
264
+
265
+ ### Contributing
266
+
267
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and guidelines.
268
+
269
+ ### Telemetry
270
+
271
+ Sequant does not collect any usage telemetry. See [docs/telemetry.md](docs/telemetry.md) for details.
272
+
273
+ ---
274
+
191
275
  ## License
192
276
 
193
277
  MIT
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Acceptance Criteria Linter
3
+ *
4
+ * Static analysis of acceptance criteria to flag vague, untestable,
5
+ * or incomplete requirements before implementation begins.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { lintAcceptanceCriteria } from './ac-linter';
10
+ * import { parseAcceptanceCriteria } from './ac-parser';
11
+ *
12
+ * const criteria = parseAcceptanceCriteria(issueBody);
13
+ * const lintResults = lintAcceptanceCriteria(criteria);
14
+ *
15
+ * console.log(formatACLintResults(lintResults));
16
+ * ```
17
+ */
18
+ import type { AcceptanceCriterion } from "./workflow/state-schema.js";
19
+ /**
20
+ * Types of issues that can be flagged in AC
21
+ */
22
+ export type ACLintIssueType = "vague" | "unmeasurable" | "incomplete" | "open_ended";
23
+ /**
24
+ * A lint issue found in an acceptance criterion
25
+ */
26
+ export interface ACLintIssue {
27
+ /** Type of issue detected */
28
+ type: ACLintIssueType;
29
+ /** The matched pattern that triggered this issue */
30
+ matchedPattern: string;
31
+ /** Human-readable description of the problem */
32
+ problem: string;
33
+ /** Suggested improvement */
34
+ suggestion: string;
35
+ }
36
+ /**
37
+ * Lint result for a single acceptance criterion
38
+ */
39
+ export interface ACLintResult {
40
+ /** The AC being linted */
41
+ ac: AcceptanceCriterion;
42
+ /** Issues found (empty if AC is clear) */
43
+ issues: ACLintIssue[];
44
+ /** Whether this AC passed linting (no issues) */
45
+ passed: boolean;
46
+ }
47
+ /**
48
+ * Overall lint results for all acceptance criteria
49
+ */
50
+ export interface ACLintResults {
51
+ /** Results for each AC */
52
+ results: ACLintResult[];
53
+ /** Summary counts */
54
+ summary: {
55
+ total: number;
56
+ passed: number;
57
+ flagged: number;
58
+ };
59
+ /** Whether any issues were found */
60
+ hasIssues: boolean;
61
+ }
62
+ /**
63
+ * Pattern definition for detecting issues
64
+ */
65
+ interface LintPattern {
66
+ /** Regular expression to match (case-insensitive) */
67
+ regex: RegExp;
68
+ /** Type of issue this pattern indicates */
69
+ type: ACLintIssueType;
70
+ /** Problem description */
71
+ problem: string;
72
+ /** Suggested fix */
73
+ suggestion: string;
74
+ }
75
+ /**
76
+ * Lint a single acceptance criterion against all patterns
77
+ *
78
+ * @param ac - The acceptance criterion to lint
79
+ * @param patterns - Optional custom patterns (defaults to DEFAULT_LINT_PATTERNS)
80
+ * @returns Lint result with any issues found
81
+ */
82
+ export declare function lintAcceptanceCriterion(ac: AcceptanceCriterion, patterns?: LintPattern[]): ACLintResult;
83
+ /**
84
+ * Lint all acceptance criteria
85
+ *
86
+ * @param criteria - Array of acceptance criteria to lint
87
+ * @param patterns - Optional custom patterns
88
+ * @returns Complete lint results with summary
89
+ */
90
+ export declare function lintAcceptanceCriteria(criteria: AcceptanceCriterion[], patterns?: LintPattern[]): ACLintResults;
91
+ /**
92
+ * Format lint results as markdown for the spec output
93
+ *
94
+ * @param results - Lint results to format
95
+ * @returns Markdown-formatted output string
96
+ */
97
+ export declare function formatACLintResults(results: ACLintResults): string;
98
+ /**
99
+ * Get the default lint patterns
100
+ *
101
+ * @returns Copy of the default lint patterns
102
+ */
103
+ export declare function getDefaultLintPatterns(): LintPattern[];
104
+ /**
105
+ * Create custom lint patterns from a simplified configuration
106
+ *
107
+ * @param config - Array of pattern configurations
108
+ * @returns Array of LintPattern objects
109
+ */
110
+ export declare function createLintPatterns(config: Array<{
111
+ pattern: string;
112
+ type: ACLintIssueType;
113
+ problem: string;
114
+ suggestion: string;
115
+ }>): LintPattern[];
116
+ export {};
@@ -0,0 +1,304 @@
1
+ /**
2
+ * Acceptance Criteria Linter
3
+ *
4
+ * Static analysis of acceptance criteria to flag vague, untestable,
5
+ * or incomplete requirements before implementation begins.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { lintAcceptanceCriteria } from './ac-linter';
10
+ * import { parseAcceptanceCriteria } from './ac-parser';
11
+ *
12
+ * const criteria = parseAcceptanceCriteria(issueBody);
13
+ * const lintResults = lintAcceptanceCriteria(criteria);
14
+ *
15
+ * console.log(formatACLintResults(lintResults));
16
+ * ```
17
+ */
18
+ /**
19
+ * Default lint patterns organized by issue type
20
+ *
21
+ * Patterns are checked in order, with longer/more specific patterns first
22
+ */
23
+ const DEFAULT_LINT_PATTERNS = [
24
+ // Vague patterns
25
+ {
26
+ regex: /\bshould work\b/i,
27
+ type: "vague",
28
+ problem: 'Vague: "should work" is not specific',
29
+ suggestion: "Specify the expected behavior and success criteria",
30
+ },
31
+ {
32
+ regex: /\bwork(?:s|ing)? (?:properly|correctly|well|nicely)\b/i,
33
+ type: "vague",
34
+ problem: "Vague: adverb does not define expected behavior",
35
+ suggestion: "Define specific, measurable outcomes",
36
+ },
37
+ {
38
+ regex: /\bproperly\b/i,
39
+ type: "vague",
40
+ problem: 'Vague: "properly" is subjective',
41
+ suggestion: "Specify what correct behavior looks like",
42
+ },
43
+ {
44
+ regex: /\bcorrectly\b/i,
45
+ type: "vague",
46
+ problem: 'Vague: "correctly" is subjective',
47
+ suggestion: "Define the expected output or behavior",
48
+ },
49
+ {
50
+ regex: /\bnicely\b/i,
51
+ type: "vague",
52
+ problem: 'Vague: "nicely" is subjective',
53
+ suggestion: "Specify concrete UX requirements",
54
+ },
55
+ {
56
+ regex: /\bgood\b(?!\s+(?:practice|pattern|reason))/i,
57
+ type: "vague",
58
+ problem: 'Vague: "good" is subjective without context',
59
+ suggestion: "Define measurable quality criteria",
60
+ },
61
+ {
62
+ regex: /\bas expected\b/i,
63
+ type: "vague",
64
+ problem: 'Vague: "as expected" requires explicit expectations',
65
+ suggestion: "Define what the expected behavior is",
66
+ },
67
+ {
68
+ regex: /\bshould be fine\b/i,
69
+ type: "vague",
70
+ problem: 'Vague: "should be fine" is not a testable criterion',
71
+ suggestion: "Specify the acceptance threshold",
72
+ },
73
+ // Unmeasurable patterns (performance-related)
74
+ {
75
+ regex: /\bfast(?:er)?\b(?!\s+forward)/i,
76
+ type: "unmeasurable",
77
+ problem: 'Unmeasurable: "fast" has no threshold',
78
+ suggestion: "Add latency threshold (e.g., <2 seconds, <100ms)",
79
+ },
80
+ {
81
+ regex: /\bslow(?:er)?\b/i,
82
+ type: "unmeasurable",
83
+ problem: 'Unmeasurable: "slow" has no threshold',
84
+ suggestion: "Define specific timing or threshold",
85
+ },
86
+ {
87
+ regex: /\bperformant\b/i,
88
+ type: "unmeasurable",
89
+ problem: 'Unmeasurable: "performant" has no threshold',
90
+ suggestion: "Add specific performance metrics (latency, throughput)",
91
+ },
92
+ {
93
+ regex: /\befficient(?:ly)?\b/i,
94
+ type: "unmeasurable",
95
+ problem: 'Unmeasurable: "efficient" has no threshold',
96
+ suggestion: "Define resource usage limits or benchmarks",
97
+ },
98
+ {
99
+ regex: /\bresponsive\b/i,
100
+ type: "unmeasurable",
101
+ problem: 'Unmeasurable: "responsive" has no threshold',
102
+ suggestion: "Add response time target (e.g., <100ms interaction, <3s load)",
103
+ },
104
+ {
105
+ regex: /\bquick(?:ly)?\b/i,
106
+ type: "unmeasurable",
107
+ problem: 'Unmeasurable: "quickly" has no threshold',
108
+ suggestion: "Specify time limit (e.g., completes in <5 seconds)",
109
+ },
110
+ {
111
+ regex: /\bscalable\b/i,
112
+ type: "unmeasurable",
113
+ problem: 'Unmeasurable: "scalable" needs concrete bounds',
114
+ suggestion: "Define load targets (e.g., handles 1000 concurrent users)",
115
+ },
116
+ // Incomplete patterns (error handling, edge cases)
117
+ {
118
+ regex: /\bhandle(?:s)? errors?\b/i,
119
+ type: "incomplete",
120
+ problem: "Incomplete: error types not specified",
121
+ suggestion: "List specific error types and expected responses (e.g., 400 for invalid input, 503 for service unavailable)",
122
+ },
123
+ {
124
+ regex: /\berror handling\b/i,
125
+ type: "incomplete",
126
+ problem: "Incomplete: error scenarios not specified",
127
+ suggestion: "Enumerate error conditions and recovery behaviors",
128
+ },
129
+ {
130
+ regex: /\bedge cases?\b/i,
131
+ type: "incomplete",
132
+ problem: "Incomplete: edge cases not enumerated",
133
+ suggestion: "List the specific edge cases to handle",
134
+ },
135
+ {
136
+ regex: /\bcorner cases?\b/i,
137
+ type: "incomplete",
138
+ problem: "Incomplete: corner cases not enumerated",
139
+ suggestion: "List the specific corner cases to handle",
140
+ },
141
+ {
142
+ regex: /\ball (?:cases|scenarios|situations)\b/i,
143
+ type: "incomplete",
144
+ problem: 'Incomplete: "all" cases cannot be verified',
145
+ suggestion: "Enumerate the specific cases to test",
146
+ },
147
+ {
148
+ regex: /\bappropriate(?:ly)?\b/i,
149
+ type: "incomplete",
150
+ problem: 'Incomplete: "appropriate" behavior not defined',
151
+ suggestion: "Specify what the appropriate response is",
152
+ },
153
+ // Open-ended patterns
154
+ {
155
+ regex: /\betc\.?\b/i,
156
+ type: "open_ended",
157
+ problem: 'Open-ended: "etc." leaves scope undefined',
158
+ suggestion: "Enumerate all items explicitly",
159
+ },
160
+ {
161
+ regex: /\band more\b/i,
162
+ type: "open_ended",
163
+ problem: 'Open-ended: "and more" leaves scope undefined',
164
+ suggestion: "List all items explicitly",
165
+ },
166
+ {
167
+ regex: /\bsuch as\b/i,
168
+ type: "open_ended",
169
+ problem: 'Open-ended: "such as" implies incomplete list',
170
+ suggestion: "Provide exhaustive list or define boundaries",
171
+ },
172
+ {
173
+ regex: /\bincluding but not limited to\b/i,
174
+ type: "open_ended",
175
+ problem: "Open-ended: scope is unbounded",
176
+ suggestion: "Define explicit boundaries or enumerate all items",
177
+ },
178
+ {
179
+ regex: /\bfor example\b/i,
180
+ type: "open_ended",
181
+ problem: 'Open-ended: "for example" implies other cases exist',
182
+ suggestion: "List all cases or define scope boundaries",
183
+ },
184
+ {
185
+ regex: /\bvarious\b/i,
186
+ type: "open_ended",
187
+ problem: 'Open-ended: "various" items not specified',
188
+ suggestion: "Enumerate the specific items",
189
+ },
190
+ {
191
+ regex: /\bother(?:s)?\b(?!\s+(?:than|hand|words|side))/i,
192
+ type: "open_ended",
193
+ problem: 'Open-ended: "other" items not specified',
194
+ suggestion: "List all items explicitly or define boundaries",
195
+ },
196
+ ];
197
+ /**
198
+ * Lint a single acceptance criterion against all patterns
199
+ *
200
+ * @param ac - The acceptance criterion to lint
201
+ * @param patterns - Optional custom patterns (defaults to DEFAULT_LINT_PATTERNS)
202
+ * @returns Lint result with any issues found
203
+ */
204
+ export function lintAcceptanceCriterion(ac, patterns = DEFAULT_LINT_PATTERNS) {
205
+ const issues = [];
206
+ const description = ac.description;
207
+ for (const pattern of patterns) {
208
+ const match = description.match(pattern.regex);
209
+ if (match) {
210
+ issues.push({
211
+ type: pattern.type,
212
+ matchedPattern: match[0],
213
+ problem: pattern.problem,
214
+ suggestion: pattern.suggestion,
215
+ });
216
+ }
217
+ }
218
+ return {
219
+ ac,
220
+ issues,
221
+ passed: issues.length === 0,
222
+ };
223
+ }
224
+ /**
225
+ * Lint all acceptance criteria
226
+ *
227
+ * @param criteria - Array of acceptance criteria to lint
228
+ * @param patterns - Optional custom patterns
229
+ * @returns Complete lint results with summary
230
+ */
231
+ export function lintAcceptanceCriteria(criteria, patterns) {
232
+ const results = criteria.map((ac) => lintAcceptanceCriterion(ac, patterns));
233
+ const passed = results.filter((r) => r.passed).length;
234
+ const flagged = results.filter((r) => !r.passed).length;
235
+ return {
236
+ results,
237
+ summary: {
238
+ total: criteria.length,
239
+ passed,
240
+ flagged,
241
+ },
242
+ hasIssues: flagged > 0,
243
+ };
244
+ }
245
+ /**
246
+ * Format lint results as markdown for the spec output
247
+ *
248
+ * @param results - Lint results to format
249
+ * @returns Markdown-formatted output string
250
+ */
251
+ export function formatACLintResults(results) {
252
+ if (results.summary.total === 0) {
253
+ return "## AC Quality Check\n\nNo acceptance criteria found to lint.";
254
+ }
255
+ const lines = ["## AC Quality Check", ""];
256
+ // Add summary line
257
+ if (!results.hasIssues) {
258
+ lines.push(`✅ All ${results.summary.total} acceptance criteria are clear and testable.`);
259
+ lines.push("");
260
+ return lines.join("\n");
261
+ }
262
+ // Add issues
263
+ for (const result of results.results) {
264
+ if (!result.passed) {
265
+ lines.push(`⚠️ **${result.ac.id}:** "${result.ac.description}"`);
266
+ for (const issue of result.issues) {
267
+ lines.push(` → ${issue.problem}`);
268
+ lines.push(` → Suggest: ${issue.suggestion}`);
269
+ }
270
+ lines.push("");
271
+ }
272
+ }
273
+ // Add passed ACs summary
274
+ const passedIds = results.results.filter((r) => r.passed).map((r) => r.ac.id);
275
+ if (passedIds.length > 0) {
276
+ lines.push(`✅ ${passedIds.join(", ")}: Clear and testable`);
277
+ lines.push("");
278
+ }
279
+ // Add summary
280
+ lines.push(`**Summary:** ${results.summary.flagged}/${results.summary.total} AC items flagged for review`);
281
+ return lines.join("\n");
282
+ }
283
+ /**
284
+ * Get the default lint patterns
285
+ *
286
+ * @returns Copy of the default lint patterns
287
+ */
288
+ export function getDefaultLintPatterns() {
289
+ return [...DEFAULT_LINT_PATTERNS];
290
+ }
291
+ /**
292
+ * Create custom lint patterns from a simplified configuration
293
+ *
294
+ * @param config - Array of pattern configurations
295
+ * @returns Array of LintPattern objects
296
+ */
297
+ export function createLintPatterns(config) {
298
+ return config.map((c) => ({
299
+ regex: new RegExp(c.pattern, "i"),
300
+ type: c.type,
301
+ problem: c.problem,
302
+ suggestion: c.suggestion,
303
+ }));
304
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Plugin version sync utilities
3
+ *
4
+ * Ensures plugin.json version stays in sync with package.json version.
5
+ * Used by CI validation and /release skill.
6
+ */
7
+ export interface VersionSyncResult {
8
+ inSync: boolean;
9
+ packageVersion: string | null;
10
+ pluginVersion: string | null;
11
+ error?: string;
12
+ }
13
+ /**
14
+ * Check if package.json and plugin.json versions are in sync
15
+ *
16
+ * @param projectRoot - Root directory of the project
17
+ * @returns Sync status with version details
18
+ */
19
+ export declare function checkVersionSync(projectRoot?: string): VersionSyncResult;
20
+ /**
21
+ * Get a helpful error message for version mismatch
22
+ *
23
+ * @param result - Version sync result
24
+ * @returns Human-readable error message with fix command
25
+ */
26
+ export declare function getVersionMismatchMessage(result: VersionSyncResult): string;
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Plugin version sync utilities
3
+ *
4
+ * Ensures plugin.json version stays in sync with package.json version.
5
+ * Used by CI validation and /release skill.
6
+ */
7
+ import { existsSync, readFileSync } from "fs";
8
+ import { join } from "path";
9
+ /**
10
+ * Check if package.json and plugin.json versions are in sync
11
+ *
12
+ * @param projectRoot - Root directory of the project
13
+ * @returns Sync status with version details
14
+ */
15
+ export function checkVersionSync(projectRoot = process.cwd()) {
16
+ const packageJsonPath = join(projectRoot, "package.json");
17
+ const pluginJsonPath = join(projectRoot, ".claude-plugin", "plugin.json");
18
+ // Check package.json exists
19
+ if (!existsSync(packageJsonPath)) {
20
+ return {
21
+ inSync: false,
22
+ packageVersion: null,
23
+ pluginVersion: null,
24
+ error: "package.json not found",
25
+ };
26
+ }
27
+ // Check plugin.json exists
28
+ if (!existsSync(pluginJsonPath)) {
29
+ return {
30
+ inSync: false,
31
+ packageVersion: null,
32
+ pluginVersion: null,
33
+ error: "plugin.json not found",
34
+ };
35
+ }
36
+ try {
37
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
38
+ const pluginJson = JSON.parse(readFileSync(pluginJsonPath, "utf8"));
39
+ const packageVersion = packageJson.version || null;
40
+ const pluginVersion = pluginJson.version || null;
41
+ if (!packageVersion) {
42
+ return {
43
+ inSync: false,
44
+ packageVersion: null,
45
+ pluginVersion,
46
+ error: "package.json missing version field",
47
+ };
48
+ }
49
+ if (!pluginVersion) {
50
+ return {
51
+ inSync: false,
52
+ packageVersion,
53
+ pluginVersion: null,
54
+ error: "plugin.json missing version field",
55
+ };
56
+ }
57
+ return {
58
+ inSync: packageVersion === pluginVersion,
59
+ packageVersion,
60
+ pluginVersion,
61
+ };
62
+ }
63
+ catch (e) {
64
+ return {
65
+ inSync: false,
66
+ packageVersion: null,
67
+ pluginVersion: null,
68
+ error: `Failed to parse JSON: ${e instanceof Error ? e.message : String(e)}`,
69
+ };
70
+ }
71
+ }
72
+ /**
73
+ * Get a helpful error message for version mismatch
74
+ *
75
+ * @param result - Version sync result
76
+ * @returns Human-readable error message with fix command
77
+ */
78
+ export function getVersionMismatchMessage(result) {
79
+ if (result.inSync) {
80
+ return `✓ Versions are in sync: ${result.packageVersion}`;
81
+ }
82
+ if (result.error) {
83
+ return `✗ Version sync check failed: ${result.error}`;
84
+ }
85
+ return `✗ Version mismatch!
86
+ package.json: ${result.packageVersion}
87
+ plugin.json: ${result.pluginVersion}
88
+
89
+ Run the following to sync:
90
+ node -e "const p=require('./.claude-plugin/plugin.json');p.version='${result.packageVersion}';require('fs').writeFileSync('./.claude-plugin/plugin.json',JSON.stringify(p,null,2)+'\\n')"`;
91
+ }