simplespec 0.1.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/.simplespec/examples/example-spec.md +241 -0
- package/.simplespec/templates/spec-template.md +128 -0
- package/LICENSE +21 -0
- package/README.md +71 -0
- package/assets/simplespec-logo.txt +6 -0
- package/dist/bin/install.js +77 -0
- package/dist/bin/runtimes/Codex.js +15 -0
- package/dist/bin/runtimes/Kilocode.js +15 -0
- package/dist/bin/runtimes/Runtime.js +135 -0
- package/dist/bin/runtimes/index.js +33 -0
- package/dist/bin/runtimes/symlinkDirectories.js +44 -0
- package/package.json +53 -0
- package/skills/spec-apply/SKILL.md +89 -0
- package/skills/spec-new/SKILL.md +101 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: User Authentication via OAuth2
|
|
3
|
+
description: Enable users to authenticate using Google and GitHub OAuth2 providers
|
|
4
|
+
metadata:
|
|
5
|
+
created_date: 2026-02-28
|
|
6
|
+
last_updated: 2026-02-28
|
|
7
|
+
author: Engineering Team
|
|
8
|
+
status: draft
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# User Authentication via OAuth2
|
|
12
|
+
|
|
13
|
+
## Summary
|
|
14
|
+
This spec defines the implementation of OAuth2-based authentication for our application, allowing users to sign in using their Google or GitHub accounts. This eliminates the need for password management and reduces friction in the signup process, while improving security through trusted third-party authentication providers.
|
|
15
|
+
|
|
16
|
+
## Goals
|
|
17
|
+
- Enable users to sign up and log in using Google OAuth2
|
|
18
|
+
- Enable users to sign up and log in using GitHub OAuth2
|
|
19
|
+
- Store essential user profile information (email, name, avatar URL) from OAuth providers
|
|
20
|
+
- Maintain secure session management with proper token handling
|
|
21
|
+
- Provide a seamless authentication experience with < 3 second flow completion
|
|
22
|
+
|
|
23
|
+
## Non-goals
|
|
24
|
+
- Single Sign-On (SSO) for enterprise accounts - will be addressed in future spec
|
|
25
|
+
- Multi-factor authentication (MFA) - deferred to Q2 2026
|
|
26
|
+
- Account linking (connecting multiple OAuth providers to one account) - future enhancement
|
|
27
|
+
- Custom username/password authentication - we're moving away from this model
|
|
28
|
+
- Social features using OAuth provider data (friends, followers, etc.)
|
|
29
|
+
|
|
30
|
+
## Requirements
|
|
31
|
+
|
|
32
|
+
### Functional Requirements
|
|
33
|
+
- FR1: System must support login via Google OAuth2 with authorization code flow
|
|
34
|
+
- FR2: System must support login via GitHub OAuth2 with authorization code flow
|
|
35
|
+
- FR3: System must capture and store user email, display name, and avatar URL from OAuth provider
|
|
36
|
+
- FR4: System must create a new user account on first OAuth login
|
|
37
|
+
- FR5: System must maintain user sessions with secure, HTTP-only cookies
|
|
38
|
+
- FR6: System must provide logout functionality that clears sessions and revokes tokens
|
|
39
|
+
- FR7: System must display clear error messages for failed authentication attempts
|
|
40
|
+
- FR8: System must redirect users to their intended destination after successful login
|
|
41
|
+
|
|
42
|
+
### Non-functional Requirements
|
|
43
|
+
- NFR1: OAuth authentication flow must complete within 3 seconds (95th percentile)
|
|
44
|
+
- NFR2: Access tokens must be encrypted at rest using AES-256
|
|
45
|
+
- NFR3: Session tokens must expire after 30 days of inactivity
|
|
46
|
+
- NFR4: System must support 1000 concurrent authentication requests
|
|
47
|
+
- NFR5: All authentication events must be logged for security audit
|
|
48
|
+
- NFR6: OAuth redirect URIs must use HTTPS in production
|
|
49
|
+
- NFR7: Implementation must comply with OAuth2 RFC 6749 standard
|
|
50
|
+
|
|
51
|
+
## Dependencies
|
|
52
|
+
- Google OAuth2 API (https://developers.google.com/identity/protocols/oauth2)
|
|
53
|
+
- GitHub OAuth2 API (https://docs.github.com/en/apps/oauth-apps)
|
|
54
|
+
- PostgreSQL database for user account storage
|
|
55
|
+
- Redis for session storage and caching
|
|
56
|
+
- Express.js middleware for request handling
|
|
57
|
+
- Environment variables for OAuth client credentials (GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET)
|
|
58
|
+
- Related spec reference: `spec:12-user-profile-schema.md`
|
|
59
|
+
|
|
60
|
+
## Proposed Approach
|
|
61
|
+
|
|
62
|
+
### Options
|
|
63
|
+
|
|
64
|
+
**Option 1: Use Passport.js with OAuth strategies**
|
|
65
|
+
- Pros:
|
|
66
|
+
- Well-established library with 500+ authentication strategies
|
|
67
|
+
- Extensive community support and documentation
|
|
68
|
+
- Handles edge cases and security best practices
|
|
69
|
+
- Cons:
|
|
70
|
+
- Heavyweight (~200KB minified)
|
|
71
|
+
- Includes many features we won't use
|
|
72
|
+
- Adds another dependency to maintain
|
|
73
|
+
- Effort: 2 weeks
|
|
74
|
+
|
|
75
|
+
**Option 2: Implement OAuth2 flow manually with axios**
|
|
76
|
+
- Pros:
|
|
77
|
+
- Full control over authentication flow
|
|
78
|
+
- Minimal dependencies
|
|
79
|
+
- Can optimize for our specific use case
|
|
80
|
+
- Cons:
|
|
81
|
+
- More code to write and maintain
|
|
82
|
+
- Higher security risk if implementation has bugs
|
|
83
|
+
- Need to handle all OAuth edge cases ourselves
|
|
84
|
+
- Effort: 3-4 weeks
|
|
85
|
+
|
|
86
|
+
**Option 3: Use NextAuth.js (or similar modern library)**
|
|
87
|
+
- Pros:
|
|
88
|
+
- Modern, lightweight, designed for React/Next.js apps
|
|
89
|
+
- Built-in session management
|
|
90
|
+
- Good TypeScript support
|
|
91
|
+
- Cons:
|
|
92
|
+
- Newer library, smaller community
|
|
93
|
+
- Tightly coupled to Next.js ecosystem (we use Express)
|
|
94
|
+
- Effort: 2-3 weeks
|
|
95
|
+
|
|
96
|
+
### Chosen Approach
|
|
97
|
+
**Selected: Option 1 (Passport.js)**
|
|
98
|
+
|
|
99
|
+
We'll use Passport.js with `passport-google-oauth20` and `passport-github2` strategies because:
|
|
100
|
+
1. Battle-tested security implementation reduces risk
|
|
101
|
+
2. Development time is critical for our Q1 deadline
|
|
102
|
+
3. The additional bundle size (~200KB) is acceptable for our use case
|
|
103
|
+
4. Future authentication methods (SAML, LDAP) will be easier to add
|
|
104
|
+
|
|
105
|
+
**Architecture:**
|
|
106
|
+
- Express.js middleware chain: Request → Passport.authenticate() → Route handler
|
|
107
|
+
- User data flow: OAuth Provider → Passport Strategy → User Service → Database
|
|
108
|
+
- Session management: Express-session with Redis store
|
|
109
|
+
- Token storage: Encrypted tokens in PostgreSQL, session IDs in Redis
|
|
110
|
+
|
|
111
|
+
**Key Components:**
|
|
112
|
+
1. `AuthController`: Handles OAuth callback routes and session management
|
|
113
|
+
2. `UserService`: Creates/updates user accounts from OAuth profile data
|
|
114
|
+
3. `SessionStore`: Redis-backed session storage with 30-day TTL
|
|
115
|
+
4. `AuthMiddleware`: Protects routes requiring authentication
|
|
116
|
+
|
|
117
|
+
## Risks & Mitigations
|
|
118
|
+
|
|
119
|
+
| Risk | Impact | Likelihood | Mitigation |
|
|
120
|
+
|------|--------|------------|------------|
|
|
121
|
+
| OAuth provider API changes break auth flow | High | Low | Pin to specific API versions, monitor provider changelogs, add integration tests |
|
|
122
|
+
| Session hijacking via XSS/CSRF | High | Medium | Implement CSRF tokens, use HTTP-only secure cookies, add Content Security Policy headers |
|
|
123
|
+
| User profile data becomes stale | Medium | High | Implement periodic profile refresh (every 7 days), allow manual refresh |
|
|
124
|
+
| Redis outage breaks all sessions | High | Low | Implement Redis clustering for HA, add fallback to database sessions |
|
|
125
|
+
| OAuth client secrets leak | Critical | Low | Use environment variables, never commit to git, rotate secrets quarterly |
|
|
126
|
+
| Passport.js vulnerabilities | Medium | Low | Enable Dependabot alerts, update regularly, have fallback plan to option 2 |
|
|
127
|
+
|
|
128
|
+
## Acceptance Criteria
|
|
129
|
+
### Requirement: Users can authenticate with Google and GitHub OAuth2
|
|
130
|
+
The system SHALL allow users to sign up and log in using Google and GitHub OAuth2 authorization code flows.
|
|
131
|
+
|
|
132
|
+
#### Scenario: New user signs up via Google
|
|
133
|
+
- **WHEN** a user completes Google OAuth2 and no local account exists for that identity
|
|
134
|
+
- **THEN** a new account is created and the user is signed in
|
|
135
|
+
|
|
136
|
+
#### Scenario: New user signs up via GitHub
|
|
137
|
+
- **WHEN** a user completes GitHub OAuth2 and no local account exists for that identity
|
|
138
|
+
- **THEN** a new account is created and the user is signed in
|
|
139
|
+
|
|
140
|
+
#### Scenario: Existing user logs in via Google
|
|
141
|
+
- **WHEN** a user completes Google OAuth2 and a matching account exists
|
|
142
|
+
- **THEN** the user is signed in without creating a duplicate account
|
|
143
|
+
|
|
144
|
+
#### Scenario: Existing user logs in via GitHub
|
|
145
|
+
- **WHEN** a user completes GitHub OAuth2 and a matching account exists
|
|
146
|
+
- **THEN** the user is signed in without creating a duplicate account
|
|
147
|
+
|
|
148
|
+
### Requirement: OAuth profile data is stored on authentication
|
|
149
|
+
The system SHALL persist required user profile attributes from OAuth providers for authenticated users.
|
|
150
|
+
|
|
151
|
+
#### Scenario: Profile fields are captured from provider response
|
|
152
|
+
- **WHEN** authentication succeeds
|
|
153
|
+
- **THEN** the user's name, email, and avatar are stored/updated from provider data
|
|
154
|
+
|
|
155
|
+
### Requirement: Session lifecycle is secure and user-controllable
|
|
156
|
+
The system MUST maintain secure sessions, support logout, and enforce configured session expiration behavior.
|
|
157
|
+
|
|
158
|
+
#### Scenario: Session persists across browser restarts
|
|
159
|
+
- **WHEN** a user closes and reopens the browser within valid session lifetime
|
|
160
|
+
- **THEN** the user remains authenticated
|
|
161
|
+
|
|
162
|
+
#### Scenario: User logs out successfully
|
|
163
|
+
- **WHEN** an authenticated user invokes logout
|
|
164
|
+
- **THEN** the session is terminated and authentication cookies are cleared
|
|
165
|
+
|
|
166
|
+
#### Scenario: Inactive session expires
|
|
167
|
+
- **WHEN** no activity occurs for 30 days
|
|
168
|
+
- **THEN** the session is no longer valid and re-authentication is required
|
|
169
|
+
|
|
170
|
+
### Requirement: Authentication failures and unauthorized access are handled clearly
|
|
171
|
+
The system SHALL provide explicit user feedback for authentication failures and protect restricted routes.
|
|
172
|
+
|
|
173
|
+
#### Scenario: Failed authentication shows user-friendly message
|
|
174
|
+
- **WHEN** OAuth authentication fails
|
|
175
|
+
- **THEN** the user is shown a clear, actionable error message
|
|
176
|
+
|
|
177
|
+
#### Scenario: Unauthorized user hits protected route
|
|
178
|
+
- **WHEN** an unauthenticated request targets a protected endpoint
|
|
179
|
+
- **THEN** the request is redirected to login
|
|
180
|
+
|
|
181
|
+
### Requirement: Security and operational controls are enforced
|
|
182
|
+
The system MUST log authentication events, enable CSRF protection on auth endpoints, and satisfy key performance targets.
|
|
183
|
+
|
|
184
|
+
#### Scenario: Authentication events are auditable
|
|
185
|
+
- **WHEN** authentication succeeds or fails
|
|
186
|
+
- **THEN** an event is logged with timestamp, user identifier (when available), and provider
|
|
187
|
+
|
|
188
|
+
#### Scenario: CSRF protection is active
|
|
189
|
+
- **WHEN** authentication endpoints are invoked
|
|
190
|
+
- **THEN** CSRF safeguards are enforced according to framework policy
|
|
191
|
+
|
|
192
|
+
#### Scenario: OAuth flow performance target is met
|
|
193
|
+
- **WHEN** OAuth authentication is measured in production-like conditions
|
|
194
|
+
- **THEN** 95th percentile completion time is less than 3 seconds
|
|
195
|
+
|
|
196
|
+
### Requirement: Test coverage validates critical auth flows
|
|
197
|
+
The implementation MUST include integration tests for happy-path and error-path behavior across both OAuth providers.
|
|
198
|
+
|
|
199
|
+
#### Scenario: Integration tests cover provider success and failure paths
|
|
200
|
+
- **WHEN** the authentication test suite is executed
|
|
201
|
+
- **THEN** both Google and GitHub flows are validated for successful and failure outcomes
|
|
202
|
+
|
|
203
|
+
## Implementation Tasks
|
|
204
|
+
- [ ] Set up OAuth2 applications in Google Cloud Console and GitHub Developer Settings
|
|
205
|
+
- [ ] Configure OAuth callback URLs for dev/staging/production environments
|
|
206
|
+
- [ ] Install and configure Passport.js with Google and GitHub strategies
|
|
207
|
+
- [ ] Implement OAuth callback routes (`/auth/google/callback`, `/auth/github/callback`)
|
|
208
|
+
- [ ] Create User model with OAuth provider fields (provider, providerId, email, name, avatarUrl)
|
|
209
|
+
- [ ] Implement UserService with `findOrCreateFromOAuthProfile()` method
|
|
210
|
+
- [ ] Set up Redis session store with express-session
|
|
211
|
+
- [ ] Implement authentication middleware (`isAuthenticated`, `requireAuth`)
|
|
212
|
+
- [ ] Add logout route that destroys session and clears cookies
|
|
213
|
+
- [ ] Create login page UI with "Sign in with Google" and "Sign in with GitHub" buttons
|
|
214
|
+
- [ ] Implement CSRF protection using `csurf` middleware
|
|
215
|
+
- [ ] Add authentication event logging to audit service
|
|
216
|
+
- [ ] Write unit tests for UserService OAuth profile handling
|
|
217
|
+
- [ ] Write integration tests for complete OAuth flow (both providers)
|
|
218
|
+
- [ ] Set up monitoring alerts for authentication failures > 5% threshold
|
|
219
|
+
- [ ] Update API documentation with authentication requirements
|
|
220
|
+
- [ ] Perform security review of OAuth implementation
|
|
221
|
+
- [ ] Load test authentication endpoints to validate NFR4 (1000 concurrent requests)
|
|
222
|
+
|
|
223
|
+
## Open Questions
|
|
224
|
+
- Q: Should we implement "remember me" functionality for longer session duration?
|
|
225
|
+
- Decision needed by: Product Manager by 2026-03-05
|
|
226
|
+
- Q: What happens if user's email from OAuth provider changes?
|
|
227
|
+
- Impact: May create duplicate accounts or lose existing account access
|
|
228
|
+
- Q: Do we support account deletion? If so, what happens to OAuth tokens?
|
|
229
|
+
- Compliance consideration for GDPR
|
|
230
|
+
- Q: Should we allow multiple OAuth providers per user account in the future?
|
|
231
|
+
- Would require account linking flow not in current scope
|
|
232
|
+
- Q: What's the UX for users who log in with different providers using same email?
|
|
233
|
+
- Current approach: treat as separate accounts; future enhancement for linking
|
|
234
|
+
|
|
235
|
+
## Notes
|
|
236
|
+
- Design mockups for login page: https://figma.com/file/abc123 (internal link)
|
|
237
|
+
- Related GitHub discussion on OAuth security: https://github.com/simplespec/simplespec/discussions/42
|
|
238
|
+
- Competitive analysis: Analyzed auth flows of 5 competitors (see attached document)
|
|
239
|
+
- OAuth2 RFC 6749: https://datatracker.ietf.org/doc/html/rfc6749
|
|
240
|
+
- Security best practices: https://oauth.net/2/oauth-best-practice/
|
|
241
|
+
- Consider migration path from old password-based accounts (separate spec needed)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: <spec name>
|
|
3
|
+
description: <short description of the spec - one sentence summary>
|
|
4
|
+
metadata:
|
|
5
|
+
created_date: <yyyy-mm-dd>
|
|
6
|
+
last_updated: <yyyy-mm-dd>
|
|
7
|
+
author: <author name or role>
|
|
8
|
+
status: <draft | in-review | approved | implemented | deprecated>
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# <spec name>
|
|
12
|
+
|
|
13
|
+
## Summary
|
|
14
|
+
<!-- 2-3 sentence overview: What are we building and why? Should answer: What problem does this solve? -->
|
|
15
|
+
|
|
16
|
+
## Goals
|
|
17
|
+
<!-- Specific, measurable objectives this spec aims to achieve. Use bullet points. -->
|
|
18
|
+
<!-- Example: Enable users to authenticate via OAuth2 providers (Google, GitHub) -->
|
|
19
|
+
|
|
20
|
+
## Non-goals
|
|
21
|
+
<!-- Explicitly state what this spec will NOT address to prevent scope creep -->
|
|
22
|
+
<!-- Example: Single Sign-On (SSO) integration - this will be addressed in a separate spec -->
|
|
23
|
+
|
|
24
|
+
## Requirements
|
|
25
|
+
|
|
26
|
+
### Functional Requirements
|
|
27
|
+
<!-- What the system must do. Be specific and testable. -->
|
|
28
|
+
<!-- Example:
|
|
29
|
+
- FR1: System must support login via Google OAuth2
|
|
30
|
+
- FR2: System must store user profile data (email, name, avatar) from OAuth provider
|
|
31
|
+
-->
|
|
32
|
+
|
|
33
|
+
### Non-functional Requirements
|
|
34
|
+
<!-- Performance, security, scalability, compliance requirements -->
|
|
35
|
+
<!-- Example:
|
|
36
|
+
- NFR1: Authentication flow must complete within 3 seconds
|
|
37
|
+
- NFR2: User credentials must be encrypted at rest using AES-256
|
|
38
|
+
- NFR3: System must support 1000 concurrent authentication requests
|
|
39
|
+
-->
|
|
40
|
+
|
|
41
|
+
## Dependencies
|
|
42
|
+
<!-- External systems, APIs, libraries, or other specs this depends on -->
|
|
43
|
+
<!-- Example:
|
|
44
|
+
- OAuth2 provider APIs (Google, GitHub)
|
|
45
|
+
- User database schema (see spec: spec:12-user-profile-schema.md)
|
|
46
|
+
- Redis for session storage
|
|
47
|
+
-->
|
|
48
|
+
|
|
49
|
+
## Proposed Approach
|
|
50
|
+
|
|
51
|
+
### Options
|
|
52
|
+
<!-- List alternative approaches considered. For each option, include:
|
|
53
|
+
- Brief description
|
|
54
|
+
- Pros
|
|
55
|
+
- Cons
|
|
56
|
+
- Estimated effort
|
|
57
|
+
-->
|
|
58
|
+
|
|
59
|
+
<!-- Example:
|
|
60
|
+
**Option 1: Use Passport.js**
|
|
61
|
+
- Pros: Well-established, many OAuth strategies available
|
|
62
|
+
- Cons: Heavyweight, may include features we don't need
|
|
63
|
+
- Effort: 2 weeks
|
|
64
|
+
|
|
65
|
+
**Option 2: Implement OAuth2 flow manually**
|
|
66
|
+
- Pros: Full control, minimal dependencies
|
|
67
|
+
- Cons: More code to maintain, security risks if done incorrectly
|
|
68
|
+
- Effort: 3-4 weeks
|
|
69
|
+
-->
|
|
70
|
+
|
|
71
|
+
### Chosen Approach
|
|
72
|
+
<!-- Describe the selected approach and justify why it was chosen over alternatives -->
|
|
73
|
+
<!-- Include high-level architecture, key components, and how they interact -->
|
|
74
|
+
|
|
75
|
+
## Risks & Mitigations
|
|
76
|
+
<!-- Identify potential problems and how to address them -->
|
|
77
|
+
<!-- Example:
|
|
78
|
+
| Risk | Impact | Likelihood | Mitigation |
|
|
79
|
+
|------|--------|------------|------------|
|
|
80
|
+
| OAuth provider API changes | High | Low | Version lock APIs, monitor provider changelogs |
|
|
81
|
+
| Session hijacking | High | Medium | Implement CSRF tokens, secure cookie flags |
|
|
82
|
+
-->
|
|
83
|
+
|
|
84
|
+
## Acceptance Criteria
|
|
85
|
+
<!-- Write acceptance criteria in requirement/scenario format (OpenSpec style), not checkboxes. -->
|
|
86
|
+
<!-- Use this structure:
|
|
87
|
+
### Requirement: <capability statement>
|
|
88
|
+
The system SHALL/MUST <clear, testable requirement sentence>.
|
|
89
|
+
|
|
90
|
+
#### Scenario: <specific behavior>
|
|
91
|
+
- **WHEN** <condition/event>
|
|
92
|
+
- **THEN** <expected outcome>
|
|
93
|
+
--->
|
|
94
|
+
<!-- Guidance:
|
|
95
|
+
- Each Requirement should represent one capability area.
|
|
96
|
+
- Add multiple Scenarios per Requirement to cover happy path, error path, and boundary behavior.
|
|
97
|
+
- Scenarios must be measurable and directly testable.
|
|
98
|
+
- Prefer explicit terms such as SHALL/MUST and avoid vague wording.
|
|
99
|
+
-->
|
|
100
|
+
|
|
101
|
+
## Implementation Tasks
|
|
102
|
+
<!-- Break down into concrete, independently deliverable tasks. Use checkboxes and keep tasks 1-3 days in size. Order by dependencies. -->
|
|
103
|
+
<!-- Example:
|
|
104
|
+
- [ ] Set up OAuth2 client credentials in Google/GitHub developer consoles
|
|
105
|
+
- [ ] Implement OAuth2 callback endpoint
|
|
106
|
+
- [ ] Create user session management service
|
|
107
|
+
- [ ] Add authentication middleware to protected routes
|
|
108
|
+
- [ ] Implement logout functionality
|
|
109
|
+
- [ ] Add authentication UI components (login button, user menu)
|
|
110
|
+
- [ ] Write integration tests for OAuth flow
|
|
111
|
+
- [ ] Update API documentation
|
|
112
|
+
-->
|
|
113
|
+
|
|
114
|
+
## Open Questions
|
|
115
|
+
<!-- Unresolved questions that need stakeholder input or further research -->
|
|
116
|
+
<!-- Example:
|
|
117
|
+
- Q: Should we support multi-factor authentication (MFA) in this phase?
|
|
118
|
+
- Q: What should happen to user data if they revoke OAuth access?
|
|
119
|
+
- Q: Do we need to support account linking (same user, multiple OAuth providers)?
|
|
120
|
+
-->
|
|
121
|
+
|
|
122
|
+
## Notes
|
|
123
|
+
<!-- Any additional context, links to related discussions, design mockups, etc. -->
|
|
124
|
+
<!-- Example:
|
|
125
|
+
- Design mockups: https://figma.com/...
|
|
126
|
+
- Related discussion: https://github.com/org/repo/issues/123
|
|
127
|
+
- Competitive analysis: see attached document
|
|
128
|
+
-->
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kristoffer Svanmark
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/simplespec-logo-light.png" alt="SimpleSpec logo" width="220" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<strong>Spec-driven development for teams building with AI.</strong><br />
|
|
7
|
+
Turn rough ideas into clear specs your coding agent can actually execute.
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Why SimpleSpec?
|
|
13
|
+
|
|
14
|
+
Most AI coding workflows break down in the same place: **unclear intent**.
|
|
15
|
+
|
|
16
|
+
SimpleSpec gives you a lightweight, repeatable structure for defining what to build before generation starts. It helps your agent ship work that is:
|
|
17
|
+
|
|
18
|
+
- ✅ Aligned with your actual requirements
|
|
19
|
+
- ✅ Consistent across features and contributors
|
|
20
|
+
- ✅ Easier to review, test, and iterate
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## What it does
|
|
25
|
+
|
|
26
|
+
SimpleSpec helps you:
|
|
27
|
+
|
|
28
|
+
- Create structured feature specifications
|
|
29
|
+
- Apply specs in a consistent implementation workflow
|
|
30
|
+
- Reduce ambiguity between product ideas and generated code
|
|
31
|
+
- Improve output quality from your AI runtime of choice
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Quick start
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx simplespec@latest
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
That’s it. Follow the guided flow and start building with clearer direction.
|
|
42
|
+
|
|
43
|
+
### Create a spec
|
|
44
|
+
|
|
45
|
+
Use this command as your core workflow:
|
|
46
|
+
|
|
47
|
+
**Create a new spec from an idea**
|
|
48
|
+
```text
|
|
49
|
+
/spec-new Build a task dashboard with filters, sorting, and bulk actions
|
|
50
|
+
```
|
|
51
|
+
This generates a structured spec under `.simplespec/specs/` with clear requirements, acceptance criteria, and implementation tasks.
|
|
52
|
+
|
|
53
|
+
> `/spec-new` will ask if you want to continue directly into implementation.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Built for the AI era
|
|
58
|
+
|
|
59
|
+
SimpleSpec is designed for modern, agent-assisted development:
|
|
60
|
+
|
|
61
|
+
- Human-readable specs
|
|
62
|
+
- Repeatable execution patterns
|
|
63
|
+
- Better prompts through better structure
|
|
64
|
+
|
|
65
|
+
When your specs are clear, your code gets better.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## License
|
|
70
|
+
|
|
71
|
+
MIT — see [`LICENSE`](LICENSE).
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { loadRuntimes } from "./runtimes/index.js";
|
|
3
|
+
import prompts from 'prompts';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import Runtime from "./runtimes/Runtime.js";
|
|
6
|
+
import { readFile } from 'node:fs/promises';
|
|
7
|
+
await loadRuntimes();
|
|
8
|
+
const context = {
|
|
9
|
+
runtimes: []
|
|
10
|
+
};
|
|
11
|
+
async function printAsciiLogo() {
|
|
12
|
+
const logoPathCandidates = [
|
|
13
|
+
new URL('../assets/simplespec-logo.txt', import.meta.url),
|
|
14
|
+
new URL('../../assets/simplespec-logo.txt', import.meta.url),
|
|
15
|
+
`${process.cwd()}/assets/simplespec-logo.txt`
|
|
16
|
+
];
|
|
17
|
+
for (const pathCandidate of logoPathCandidates) {
|
|
18
|
+
try {
|
|
19
|
+
const logo = await readFile(pathCandidate, 'utf8');
|
|
20
|
+
console.log(logo);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// Try the next candidate path.
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
throw new Error('Unable to load ASCII logo from assets/simplespec-logo.txt');
|
|
28
|
+
}
|
|
29
|
+
async function printIntro() {
|
|
30
|
+
const packageJsonPathCandidates = [
|
|
31
|
+
new URL('../package.json', import.meta.url),
|
|
32
|
+
new URL('../../package.json', import.meta.url),
|
|
33
|
+
`${process.cwd()}/package.json`
|
|
34
|
+
];
|
|
35
|
+
let version = 'unknown';
|
|
36
|
+
for (const pathCandidate of packageJsonPathCandidates) {
|
|
37
|
+
try {
|
|
38
|
+
const packageJsonRaw = await readFile(pathCandidate, 'utf8');
|
|
39
|
+
({ version } = JSON.parse(packageJsonRaw));
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Try the next candidate path.
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
console.log(`SimpleSpec ${chalk.gray(`v${version}`)}`);
|
|
47
|
+
console.log(`A simple and lightwiehgt specification framework for AI agents.`);
|
|
48
|
+
console.log(`Installs ${chalk.inverse('./.agents')} directory by default - symlinks to agent specific directories as selected.`);
|
|
49
|
+
console.log('');
|
|
50
|
+
}
|
|
51
|
+
async function askRuntime() {
|
|
52
|
+
const response = await prompts({
|
|
53
|
+
type: 'multiselect',
|
|
54
|
+
name: 'runtimes',
|
|
55
|
+
instructions: 'Use space to select, enter to confirm',
|
|
56
|
+
message: 'Select runtimes to support',
|
|
57
|
+
choices: Runtime.listAvailableRuntimes().map(({ runtime, name }) => ({
|
|
58
|
+
title: name,
|
|
59
|
+
value: runtime
|
|
60
|
+
})),
|
|
61
|
+
});
|
|
62
|
+
context.runtimes = response.runtimes;
|
|
63
|
+
return response;
|
|
64
|
+
}
|
|
65
|
+
async function installRuntimes() {
|
|
66
|
+
for (const runtime of context.runtimes) {
|
|
67
|
+
const runtimeInstance = Runtime.getRuntime(runtime);
|
|
68
|
+
await runtimeInstance.install();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async function run() {
|
|
72
|
+
await printAsciiLogo();
|
|
73
|
+
await printIntro();
|
|
74
|
+
await askRuntime();
|
|
75
|
+
await installRuntimes();
|
|
76
|
+
}
|
|
77
|
+
run();
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import Runtime from "./Runtime.js";
|
|
2
|
+
class Codex extends Runtime {
|
|
3
|
+
async install() {
|
|
4
|
+
await super.install();
|
|
5
|
+
console.log('Installing Codex runtime...');
|
|
6
|
+
await this.symlinkAgentDirectoriesToRuntime('.codex', [{ source: 'skills' }]);
|
|
7
|
+
// Codex runtime-specific install behavior goes here.
|
|
8
|
+
}
|
|
9
|
+
uninstall() {
|
|
10
|
+
super.uninstall();
|
|
11
|
+
// Codex runtime-specific uninstall behavior goes here.
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
Runtime.registerRuntime('codex', 'Codex', '.codex', Codex);
|
|
15
|
+
export default Codex;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import Runtime from "./Runtime.js";
|
|
2
|
+
class Kilocode extends Runtime {
|
|
3
|
+
async install() {
|
|
4
|
+
await super.install();
|
|
5
|
+
console.log('Installing Kilo Code runtime...');
|
|
6
|
+
await this.symlinkAgentDirectoriesToRuntime('.kilocode', [{ source: 'skills' }]);
|
|
7
|
+
// Kilocode runtime-specific install behavior goes here.
|
|
8
|
+
}
|
|
9
|
+
uninstall() {
|
|
10
|
+
super.uninstall();
|
|
11
|
+
// Kilocode runtime-specific uninstall behavior goes here.
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
Runtime.registerRuntime('kilocode', 'Kilo Code', '.kilocode', Kilocode);
|
|
15
|
+
export default Kilocode;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { access, cp, mkdir } from 'node:fs/promises';
|
|
2
|
+
import { dirname, join, relative, resolve, sep } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { symlinkDirectoriesFromAgentsToRuntime, } from "./symlinkDirectories.js";
|
|
5
|
+
const runtimes = {};
|
|
6
|
+
const registeredRuntimes = {};
|
|
7
|
+
const FRAMEWORK_BASE_DIRECTORY_MAPPINGS = [
|
|
8
|
+
{
|
|
9
|
+
source: 'skills',
|
|
10
|
+
target: '.agents/skills',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
source: '.simplespec',
|
|
14
|
+
target: '.simplespec',
|
|
15
|
+
},
|
|
16
|
+
];
|
|
17
|
+
function getInstallationDirectory() {
|
|
18
|
+
const currentWorkingDirectory = process.cwd();
|
|
19
|
+
const initialWorkingDirectory = process.env.INIT_CWD?.trim();
|
|
20
|
+
const npmPackageJsonPath = process.env.npm_package_json;
|
|
21
|
+
if (!initialWorkingDirectory || !npmPackageJsonPath) {
|
|
22
|
+
return currentWorkingDirectory;
|
|
23
|
+
}
|
|
24
|
+
const npmScriptDirectory = dirname(npmPackageJsonPath);
|
|
25
|
+
if (resolve(currentWorkingDirectory) === resolve(npmScriptDirectory)) {
|
|
26
|
+
return initialWorkingDirectory;
|
|
27
|
+
}
|
|
28
|
+
return currentWorkingDirectory;
|
|
29
|
+
}
|
|
30
|
+
async function pathExists(path) {
|
|
31
|
+
try {
|
|
32
|
+
await access(path);
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async function resolveFrameworkBaseSourceDirectory(sourceDirectory) {
|
|
40
|
+
const installationDirectory = getInstallationDirectory();
|
|
41
|
+
const runtimeFileDirectory = dirname(fileURLToPath(import.meta.url));
|
|
42
|
+
const sourceDirectoryCandidates = [
|
|
43
|
+
join(runtimeFileDirectory, '..', '..', sourceDirectory),
|
|
44
|
+
join(runtimeFileDirectory, '..', '..', '..', sourceDirectory),
|
|
45
|
+
join(installationDirectory, sourceDirectory),
|
|
46
|
+
];
|
|
47
|
+
for (const sourceDirectoryCandidate of sourceDirectoryCandidates) {
|
|
48
|
+
if (await pathExists(sourceDirectoryCandidate)) {
|
|
49
|
+
return sourceDirectoryCandidate;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
throw new Error(`Unable to resolve framework base source directory: ${sourceDirectory}`);
|
|
53
|
+
}
|
|
54
|
+
async function installFrameworkBaseDirectories() {
|
|
55
|
+
const installationDirectory = getInstallationDirectory();
|
|
56
|
+
for (const { source, target } of FRAMEWORK_BASE_DIRECTORY_MAPPINGS) {
|
|
57
|
+
const sourceDirectory = await resolveFrameworkBaseSourceDirectory(source);
|
|
58
|
+
const targetDirectory = join(installationDirectory, target);
|
|
59
|
+
if (resolve(sourceDirectory) === resolve(targetDirectory)) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
await mkdir(dirname(targetDirectory), { recursive: true });
|
|
63
|
+
if (source === '.simplespec') {
|
|
64
|
+
await cp(sourceDirectory, targetDirectory, {
|
|
65
|
+
recursive: true,
|
|
66
|
+
force: true,
|
|
67
|
+
filter: (sourcePath) => {
|
|
68
|
+
const sourceRelativePath = relative(sourceDirectory, sourcePath);
|
|
69
|
+
if (!sourceRelativePath) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
return sourceRelativePath !== 'specs' && !sourceRelativePath.startsWith(`specs${sep}`);
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
await cp(sourceDirectory, targetDirectory, {
|
|
78
|
+
recursive: true,
|
|
79
|
+
force: true,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
class Runtime {
|
|
84
|
+
runtime;
|
|
85
|
+
static globalInstallCompleted = false;
|
|
86
|
+
constructor(runtime) {
|
|
87
|
+
if (new.target === Runtime) {
|
|
88
|
+
throw new Error('Use Runtime.getRuntime() instead of new.');
|
|
89
|
+
}
|
|
90
|
+
this.runtime = runtime;
|
|
91
|
+
}
|
|
92
|
+
async install() {
|
|
93
|
+
if (Runtime.globalInstallCompleted) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
console.log('Installing global runtime...');
|
|
97
|
+
await installFrameworkBaseDirectories();
|
|
98
|
+
Runtime.globalInstallCompleted = true;
|
|
99
|
+
}
|
|
100
|
+
uninstall() {
|
|
101
|
+
// Shared uninstall behavior for all runtimes goes here.
|
|
102
|
+
}
|
|
103
|
+
async symlinkAgentSkillsToRuntime(runtimeDirectory) {
|
|
104
|
+
await this.symlinkAgentDirectoriesToRuntime(runtimeDirectory, [{ source: 'skills' }]);
|
|
105
|
+
}
|
|
106
|
+
async symlinkAgentDirectoriesToRuntime(runtimeDirectory, mappings) {
|
|
107
|
+
await symlinkDirectoriesFromAgentsToRuntime(runtimeDirectory, mappings);
|
|
108
|
+
}
|
|
109
|
+
static registerRuntime(runtime, name, runtimePath, RuntimeClass) {
|
|
110
|
+
registeredRuntimes[runtime] = {
|
|
111
|
+
name,
|
|
112
|
+
runtimePath,
|
|
113
|
+
RuntimeClass,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
static listAvailableRuntimes() {
|
|
117
|
+
return Object.entries(registeredRuntimes).map(([runtime, { name, runtimePath }]) => ({
|
|
118
|
+
runtime,
|
|
119
|
+
name,
|
|
120
|
+
runtimePath,
|
|
121
|
+
}));
|
|
122
|
+
}
|
|
123
|
+
static getRuntime(runtime) {
|
|
124
|
+
if (!runtimes[runtime]) {
|
|
125
|
+
const registeredRuntime = registeredRuntimes[runtime];
|
|
126
|
+
if (!registeredRuntime) {
|
|
127
|
+
throw new Error(`Runtime "${runtime}" is not registered.`);
|
|
128
|
+
}
|
|
129
|
+
runtimes[runtime] = new registeredRuntime.RuntimeClass(runtime);
|
|
130
|
+
}
|
|
131
|
+
return runtimes[runtime];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
export { runtimes };
|
|
135
|
+
export default Runtime;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
import { readdir } from 'node:fs/promises';
|
|
10
|
+
import { dirname, extname, basename } from 'node:path';
|
|
11
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
12
|
+
const RUNTIME_FILE_EXTENSIONS = new Set(['.js', '.ts']);
|
|
13
|
+
const EXCLUDED_RUNTIME_FILES = new Set(['Runtime', 'index']);
|
|
14
|
+
async function loadRuntimes() {
|
|
15
|
+
const currentFilePath = fileURLToPath(import.meta.url);
|
|
16
|
+
const currentDir = dirname(currentFilePath);
|
|
17
|
+
const entries = await readdir(currentDir, { withFileTypes: true });
|
|
18
|
+
for (const entry of entries) {
|
|
19
|
+
if (!entry.isFile()) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const extension = extname(entry.name);
|
|
23
|
+
const fileNameWithoutExtension = basename(entry.name, extension);
|
|
24
|
+
if (!RUNTIME_FILE_EXTENSIONS.has(extension)) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (EXCLUDED_RUNTIME_FILES.has(fileNameWithoutExtension)) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
await import(__rewriteRelativeImportExtension(pathToFileURL(`${currentDir}/${entry.name}`).href));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export { loadRuntimes };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { lstat, mkdir, readdir, rm, symlink } from 'node:fs/promises';
|
|
2
|
+
import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
3
|
+
function getInstallationDirectory() {
|
|
4
|
+
const currentWorkingDirectory = process.cwd();
|
|
5
|
+
const initialWorkingDirectory = process.env.INIT_CWD?.trim();
|
|
6
|
+
const npmPackageJsonPath = process.env.npm_package_json;
|
|
7
|
+
if (!initialWorkingDirectory || !npmPackageJsonPath) {
|
|
8
|
+
return currentWorkingDirectory;
|
|
9
|
+
}
|
|
10
|
+
const npmScriptDirectory = dirname(npmPackageJsonPath);
|
|
11
|
+
if (resolve(currentWorkingDirectory) === resolve(npmScriptDirectory)) {
|
|
12
|
+
return initialWorkingDirectory;
|
|
13
|
+
}
|
|
14
|
+
return currentWorkingDirectory;
|
|
15
|
+
}
|
|
16
|
+
async function symlinkDirectoriesFromAgentsToRuntime(runtimeDirectory, mappings) {
|
|
17
|
+
for (const mapping of mappings) {
|
|
18
|
+
const installationDirectory = getInstallationDirectory();
|
|
19
|
+
const sourceDirectoryName = mapping.source;
|
|
20
|
+
const targetDirectoryName = mapping.target ?? mapping.source;
|
|
21
|
+
const sourceDirectory = join(installationDirectory, '.agents', sourceDirectoryName);
|
|
22
|
+
const targetDirectory = join(installationDirectory, runtimeDirectory, targetDirectoryName);
|
|
23
|
+
await mkdir(targetDirectory, { recursive: true });
|
|
24
|
+
const entries = await readdir(sourceDirectory, { withFileTypes: true });
|
|
25
|
+
for (const entry of entries) {
|
|
26
|
+
const sourceEntryPath = join(sourceDirectory, entry.name);
|
|
27
|
+
const targetEntryPath = join(targetDirectory, entry.name);
|
|
28
|
+
try {
|
|
29
|
+
await lstat(targetEntryPath);
|
|
30
|
+
await rm(targetEntryPath, { recursive: true, force: true });
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// Target does not exist yet.
|
|
34
|
+
}
|
|
35
|
+
const relativeSourceEntryPath = relative(targetDirectory, sourceEntryPath);
|
|
36
|
+
if (isAbsolute(relativeSourceEntryPath)) {
|
|
37
|
+
throw new Error(`Expected relative symlink target, but got: ${relativeSourceEntryPath}`);
|
|
38
|
+
}
|
|
39
|
+
const symlinkType = entry.isDirectory() ? 'dir' : 'file';
|
|
40
|
+
await symlink(relativeSourceEntryPath, targetEntryPath, symlinkType);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export { symlinkDirectoriesFromAgentsToRuntime };
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "simplespec",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A simple spec-driven framework",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/bin/install.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"simplespec": "dist/bin/install.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist/bin/**",
|
|
12
|
+
"skills/**",
|
|
13
|
+
".simplespec/**",
|
|
14
|
+
"assets/simplespec-logo.txt",
|
|
15
|
+
"README.md",
|
|
16
|
+
"LICENSE"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc -p tsconfig.json",
|
|
20
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
21
|
+
"clean": "rm -rf dist",
|
|
22
|
+
"dev": "tsx bin/install.ts",
|
|
23
|
+
"install:run": "npm run build && node dist/bin/install.js",
|
|
24
|
+
"lint": "eslint .",
|
|
25
|
+
"lint:fix": "eslint . --fix",
|
|
26
|
+
"test": "node --import tsx --test \"tests/**/*.test.ts\"",
|
|
27
|
+
"prepack": "npm run clean && npm run build",
|
|
28
|
+
"prepublishOnly": "npm run typecheck && npm run lint && npm test"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@eslint/js": "^9.39.3",
|
|
32
|
+
"@types/node": "^22.13.10",
|
|
33
|
+
"@types/prompts": "^2.4.9",
|
|
34
|
+
"eslint": "^9.39.3",
|
|
35
|
+
"tsx": "^4.19.2",
|
|
36
|
+
"typescript": "^5.7.3",
|
|
37
|
+
"typescript-eslint": "^8.56.1"
|
|
38
|
+
},
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "git+https://github.com/Svanmark/simplespec.git"
|
|
42
|
+
},
|
|
43
|
+
"author": "Kristoffer Svanmark",
|
|
44
|
+
"license": "MIT",
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/Svanmark/simplespec/issues"
|
|
47
|
+
},
|
|
48
|
+
"homepage": "https://github.com/Svanmark/simplespec#readme",
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"chalk": "^5.6.2",
|
|
51
|
+
"prompts": "^2.4.2"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: spec-apply
|
|
3
|
+
description: Implement an existing product/engineering specification using the spec-driven development workflow. Use when asked to apply a spec and deliver implementation-ready code changes.
|
|
4
|
+
metadata:
|
|
5
|
+
version: "0.1"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# /spec-apply - Implement spec
|
|
9
|
+
|
|
10
|
+
## Inputs
|
|
11
|
+
|
|
12
|
+
Input can be either of:
|
|
13
|
+
|
|
14
|
+
- A spec id (spec:<number>).
|
|
15
|
+
- Browse `.simplespec/specs/` to find the spec file.
|
|
16
|
+
- File path to a spec file.
|
|
17
|
+
- Empty.
|
|
18
|
+
- Detect spec reference from context (e.g., from a previous message or conversation).
|
|
19
|
+
|
|
20
|
+
## Outputs
|
|
21
|
+
|
|
22
|
+
- Implemented code changes that satisfy the selected spec.
|
|
23
|
+
- Tests added/updated when the repository has tests or when explicitly requested in the spec/task.
|
|
24
|
+
- Validation artifacts from local checks (for example lint/typecheck/tests/build where applicable).
|
|
25
|
+
- A concise implementation summary including what was completed, what was verified, and any unresolved blockers.
|
|
26
|
+
|
|
27
|
+
## Process
|
|
28
|
+
|
|
29
|
+
1. **Resolve the target spec**
|
|
30
|
+
- If input is a spec id, locate matching file under `.simplespec/specs/`.
|
|
31
|
+
- If input is empty, infer the most recent/active spec reference from context.
|
|
32
|
+
- If no clear target can be resolved, ask a focused follow-up question.
|
|
33
|
+
|
|
34
|
+
2. **Read and extract implementation scope**
|
|
35
|
+
- Parse: Summary, Goals, Non-goals, Requirements, Acceptance Criteria, and Implementation Tasks.
|
|
36
|
+
- Convert requirements into an execution checklist.
|
|
37
|
+
- Treat ambiguous or conflicting requirements as blockers and ask targeted clarification only when necessary.
|
|
38
|
+
|
|
39
|
+
3. **Align with repository conventions before coding**
|
|
40
|
+
- Detect language, framework, architecture, and naming/style patterns from existing files.
|
|
41
|
+
- Reuse existing abstractions and patterns before introducing new ones.
|
|
42
|
+
- Prefer minimal, focused diffs that preserve established project structure.
|
|
43
|
+
|
|
44
|
+
4. **Implement iteratively**
|
|
45
|
+
- Complete work in dependency order.
|
|
46
|
+
- Keep commits/changes scoped to spec requirements and explicit acceptance criteria.
|
|
47
|
+
- Avoid out-of-scope enhancements unless they are required to make the spec implementation correct.
|
|
48
|
+
|
|
49
|
+
5. **Parallelize with orchestration when beneficial**
|
|
50
|
+
- The skill may orchestrate/spawn multiple agents to implement independent workstreams in parallel.
|
|
51
|
+
- Split by clear boundaries (for example backend, frontend, tests, docs) to minimize merge conflicts.
|
|
52
|
+
- Define contracts up front (shared types/interfaces, acceptance criteria ownership, file boundaries).
|
|
53
|
+
- Reconcile outputs into a single consistent implementation before validation.
|
|
54
|
+
|
|
55
|
+
6. **Apply testing strategy**
|
|
56
|
+
- If tests exist in the repository, add/update tests to cover all new behavior and relevant edge cases.
|
|
57
|
+
- If the spec or user instruction requires tests, treat tests as mandatory deliverables.
|
|
58
|
+
- Map tests directly to acceptance criteria so each requirement is explicitly verified.
|
|
59
|
+
- Prefer small, focused tests with a single assertion intent (for example filtering, boundary behavior, sorting, validation) over one large mixed scenario.
|
|
60
|
+
- Include both happy path and failure-path coverage (invalid input, missing entities, boundary conditions).
|
|
61
|
+
- Keep tests deterministic (stable fixtures, explicit ordering assertions where ordering matters, no time/network randomness).
|
|
62
|
+
- When one broad test exists, split it into requirement-aligned tests to improve failure diagnosis and maintainability.
|
|
63
|
+
- Run project-appropriate checks (for example test, typecheck, lint, build) and fix issues caused by the implementation.
|
|
64
|
+
|
|
65
|
+
7. **Frontend verification (when applicable)**
|
|
66
|
+
- For UI/frontend implementations, use the agent browser to validate rendered UI and key user flows.
|
|
67
|
+
- Verify that behavior matches acceptance criteria (including visible states and interaction outcomes).
|
|
68
|
+
- Address UI/functional regressions found during browser validation.
|
|
69
|
+
|
|
70
|
+
8. **Review and iterate**
|
|
71
|
+
- Perform a short self-review against:
|
|
72
|
+
- Spec requirements and acceptance criteria coverage.
|
|
73
|
+
- Test quality and reliability.
|
|
74
|
+
- Code quality and robustness (error handling/edge cases).
|
|
75
|
+
- Iterate on implementation until no known gaps remain or a clear blocker is documented.
|
|
76
|
+
|
|
77
|
+
9. **Report completion**
|
|
78
|
+
- Summarize implemented items mapped to acceptance criteria.
|
|
79
|
+
- List tests/checks executed and outcomes.
|
|
80
|
+
- Document any follow-up items explicitly marked out-of-scope or blocked.
|
|
81
|
+
|
|
82
|
+
## Guardrails
|
|
83
|
+
|
|
84
|
+
- Do not implement features outside spec scope unless required for correctness.
|
|
85
|
+
- Do not skip tests when test infrastructure exists or when testing is explicitly required.
|
|
86
|
+
- Do not claim frontend verification without using the browser for frontend work.
|
|
87
|
+
- Prefer repository-standard patterns over introducing new architecture.
|
|
88
|
+
- Escalate blockers early with precise questions rather than making risky assumptions.
|
|
89
|
+
- When parallelizing with multiple agents, always perform a final integration pass to resolve conflicts and ensure end-to-end correctness.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: spec-new
|
|
3
|
+
description: Create a new product/engineering specification using our spec-driven development workflow. Use when asked to draft a new spec, define requirements, acceptance criteria, or produce a spec for a feature.
|
|
4
|
+
metadata:
|
|
5
|
+
version: "0.1"
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# /spec-new - Create a new simple spec
|
|
9
|
+
|
|
10
|
+
## Inputs
|
|
11
|
+
|
|
12
|
+
A text describing the idea for the feature to implement. It can be:
|
|
13
|
+
- A short description of the feature.
|
|
14
|
+
- A longer, more detailed description capturing requirements and approaches.
|
|
15
|
+
- Link(s) to relevant documents or resources. Link(s) to external project management tools like Jira or Confluence.
|
|
16
|
+
|
|
17
|
+
## Outputs
|
|
18
|
+
A spec file created at `.simplespec/specs/spec:<number>-<slug>.md` based on `.simplespec/templates/spec-template.md`. Theres an example spec available under `.simplespec/examples/example-spec.md`
|
|
19
|
+
|
|
20
|
+
### Slug Generation Rules
|
|
21
|
+
Generate the `<slug>` from the spec name using these rules:
|
|
22
|
+
- Convert to lowercase
|
|
23
|
+
- Replace spaces with hyphens
|
|
24
|
+
- Remove special characters (keep only alphanumeric and hyphens)
|
|
25
|
+
- Remove consecutive hyphens
|
|
26
|
+
- Trim hyphens from start/end
|
|
27
|
+
- Maximum 50 characters
|
|
28
|
+
- Examples:
|
|
29
|
+
- "User Authentication" → `user-authentication`
|
|
30
|
+
- "API Rate Limiting (v2)" → `api-rate-limiting-v2`
|
|
31
|
+
- "Real-time Chat Feature!!!" → `real-time-chat-feature`
|
|
32
|
+
|
|
33
|
+
## Process
|
|
34
|
+
1. **Parse Input**: Extract key information from the input text or linked resources. If links are inaccessible, notify and ask for alternative sources or proceed with available information.
|
|
35
|
+
|
|
36
|
+
2. **Analyze Requirements**: Identify the main components, dependencies, and requirements of the feature. Consider:
|
|
37
|
+
- Core functionality needed
|
|
38
|
+
- System components affected
|
|
39
|
+
- Integration points
|
|
40
|
+
- Potential technical challenges
|
|
41
|
+
|
|
42
|
+
3. **Gather Missing Details**: Ask targeted questions only for crucial information gaps that significantly impact the spec. Examples of "major/crucial" details:
|
|
43
|
+
- Target users or use cases that fundamentally change the approach
|
|
44
|
+
- Performance requirements (scale, latency expectations)
|
|
45
|
+
- Security or compliance requirements
|
|
46
|
+
- Integration dependencies or constraints
|
|
47
|
+
- MVP scope vs. future enhancements
|
|
48
|
+
|
|
49
|
+
4. **Complete Spec Sections**: Fill in all template sections with extracted and analyzed information. Ensure:
|
|
50
|
+
- Each section captures all relevant details and is well-structured
|
|
51
|
+
- Use "N/A" for sections that don't apply (with brief justification)
|
|
52
|
+
- Leave "Open Questions" for items needing stakeholder input
|
|
53
|
+
- Be specific and avoid vague language
|
|
54
|
+
|
|
55
|
+
5. **Break Down Tasks**: Create implementation tasks that are:
|
|
56
|
+
- Independently testable and deployable
|
|
57
|
+
- Sized appropriately (1-3 days of work ideally)
|
|
58
|
+
- Ordered by logical dependencies
|
|
59
|
+
- Formatted as markdown checkboxes
|
|
60
|
+
- Include brief acceptance criteria per task
|
|
61
|
+
|
|
62
|
+
6. **Validate Completeness**: Review the spec against success criteria (see below). Iterate if:
|
|
63
|
+
- Any required section is missing or too vague
|
|
64
|
+
- Acceptance criteria are not measurable
|
|
65
|
+
- Tasks are not granular enough or have unclear scope
|
|
66
|
+
- Requirements contain ambiguities
|
|
67
|
+
|
|
68
|
+
7. **Handle Duplicates**: Before saving, check if a spec with similar name/slug exists in `.simplespec/specs/`. If found, either:
|
|
69
|
+
- Append a numeric suffix to the slug (e.g., `-2`, `-3`)
|
|
70
|
+
- Ask if this should update an existing spec
|
|
71
|
+
|
|
72
|
+
8. **Save Spec File**: Create the spec file with proper frontmatter metadata and naming:
|
|
73
|
+
- Set `created_date` to current date in YYYY-MM-DD format
|
|
74
|
+
- Set `last_updated` to creation date
|
|
75
|
+
- Generate slug per rules above
|
|
76
|
+
- Determine `<number>` by listing files in `.simplespec/specs/`, extracting existing `spec:<number>-*.md` prefixes, and using the next incremental number (start at `1` when none exist)
|
|
77
|
+
|
|
78
|
+
9. **Ask if user want to proceed to implementing the spec.**
|
|
79
|
+
- Ask the user if they want to proceed with implementing the spec, providing options to:
|
|
80
|
+
- Start implementation immediately, refer to the `spec-apply` skill.
|
|
81
|
+
|
|
82
|
+
## Error Handling
|
|
83
|
+
|
|
84
|
+
- **Inaccessible Links**: If external links can't be accessed, inform the user and ask if you shall proceed with available information or request alternatives
|
|
85
|
+
- **Incomplete Input**: If critical information is missing and can't be inferred, ask specific questions rather than making assumptions
|
|
86
|
+
- **Duplicate Detection**: Check for existing specs with same/similar slugs before saving
|
|
87
|
+
- **Invalid Spec Name**: If the name can't generate a valid slug, ask for clarification
|
|
88
|
+
|
|
89
|
+
## Success Criteria
|
|
90
|
+
|
|
91
|
+
A high-quality spec should meet these criteria:
|
|
92
|
+
- ✅ All required template sections are completed (or marked N/A with justification)
|
|
93
|
+
- ✅ Summary clearly explains what and why in 2-3 sentences
|
|
94
|
+
- ✅ Goals and Non-goals are specific and mutually exclusive
|
|
95
|
+
- ✅ Requirements are unambiguous and verifiable
|
|
96
|
+
- ✅ Acceptance criteria are measurable and testable
|
|
97
|
+
- ✅ Implementation tasks are granular, ordered, and independently deliverable
|
|
98
|
+
- ✅ Risks identify potential issues with mitigation strategies
|
|
99
|
+
- ✅ No ambiguous or vague language (avoid "should", "might", "probably")
|
|
100
|
+
- ✅ Dependencies on other systems/specs are explicitly documented
|
|
101
|
+
- ✅ The spec can be handed off to an engineer without additional clarification
|