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.
@@ -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,6 @@
1
+ _____ _ __ _____
2
+ / ___/(_)___ ___ ____ / /__ / ___/____ ___ _____
3
+ \__ \/ / __ `__ \/ __ \/ / _ \\__ \/ __ \/ _ \/ ___/
4
+ ___/ / / / / / / / /_/ / / __/__/ / /_/ / __/ /__
5
+ /____/_/_/ /_/ /_/ .___/_/\___/____/ .___/\___/\___/
6
+ /_/ /_/
@@ -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