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 +86 -2
- package/dist/src/lib/ac-linter.d.ts +116 -0
- package/dist/src/lib/ac-linter.js +304 -0
- package/dist/src/lib/plugin-version-sync.d.ts +26 -0
- package/dist/src/lib/plugin-version-sync.js +91 -0
- package/dist/src/lib/project-name.d.ts +40 -0
- package/dist/src/lib/project-name.js +191 -0
- package/dist/src/lib/semgrep.d.ts +136 -0
- package/dist/src/lib/semgrep.js +406 -0
- package/dist/src/lib/stacks.d.ts +14 -0
- package/dist/src/lib/stacks.js +159 -0
- package/dist/src/lib/templates.js +9 -3
- package/package.json +1 -1
- package/templates/hooks/pre-tool.sh +6 -0
- package/templates/memory/constitution.md +1 -5
- package/templates/skills/_shared/references/prompt-templates.md +350 -0
- package/templates/skills/_shared/references/subagent-types.md +131 -0
- package/templates/skills/exec/SKILL.md +82 -0
- package/templates/skills/fullsolve/SKILL.md +19 -2
- package/templates/skills/loop/SKILL.md +3 -1
- package/templates/skills/qa/SKILL.md +79 -9
- package/templates/skills/qa/references/quality-gates.md +85 -1
- package/templates/skills/qa/references/semgrep-rules.md +207 -0
- package/templates/skills/qa/scripts/quality-checks.sh +54 -0
- package/templates/skills/spec/SKILL.md +215 -4
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
|
-
-
|
|
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
|
+
}
|