jettypod 4.4.115 → 4.4.118
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/.env +7 -0
- package/apps/dashboard/app/api/claude/[workItemId]/message/route.ts +25 -9
- package/apps/dashboard/app/api/claude/sessions/[sessionId]/message/route.ts +7 -3
- package/apps/dashboard/app/api/tests/run/stream/route.ts +13 -1
- package/apps/dashboard/app/api/usage/route.ts +17 -0
- package/apps/dashboard/app/connect-claude/page.tsx +24 -0
- package/apps/dashboard/app/install-claude/page.tsx +8 -6
- package/apps/dashboard/app/login/page.tsx +229 -0
- package/apps/dashboard/app/page.tsx +5 -3
- package/apps/dashboard/app/settings/page.tsx +2 -0
- package/apps/dashboard/app/subscribe/page.tsx +11 -0
- package/apps/dashboard/app/welcome/page.tsx +23 -0
- package/apps/dashboard/components/AppShell.tsx +51 -9
- package/apps/dashboard/components/CardMenu.tsx +14 -5
- package/apps/dashboard/components/ClaudePanel.tsx +65 -9
- package/apps/dashboard/components/ConnectClaudeScreen.tsx +223 -0
- package/apps/dashboard/components/DragContext.tsx +73 -64
- package/apps/dashboard/components/DraggableCard.tsx +6 -46
- package/apps/dashboard/components/GateCard.tsx +21 -0
- package/apps/dashboard/components/InstallClaudeScreen.tsx +132 -30
- package/apps/dashboard/components/KanbanBoard.tsx +173 -56
- package/apps/dashboard/components/PlaceholderCard.tsx +9 -19
- package/apps/dashboard/components/ProjectSwitcher.tsx +28 -0
- package/apps/dashboard/components/RealTimeKanbanWrapper.tsx +34 -3
- package/apps/dashboard/components/RealTimeTestsWrapper.tsx +30 -2
- package/apps/dashboard/components/SubscribeContent.tsx +191 -0
- package/apps/dashboard/components/TipCard.tsx +176 -0
- package/apps/dashboard/components/UpgradeBanner.tsx +29 -0
- package/apps/dashboard/components/WelcomeScreen.tsx +14 -4
- package/apps/dashboard/components/settings/AccountSection.tsx +163 -0
- package/apps/dashboard/contexts/ClaudeSessionContext.tsx +292 -29
- package/apps/dashboard/contexts/UsageContext.tsx +131 -0
- package/apps/dashboard/contexts/usageHelpers.js +9 -0
- package/apps/dashboard/electron/ipc-handlers.js +220 -114
- package/apps/dashboard/electron/main.js +415 -37
- package/apps/dashboard/electron/preload.js +23 -4
- package/apps/dashboard/electron/session-manager.js +141 -0
- package/apps/dashboard/electron-builder.config.js +3 -5
- package/apps/dashboard/lib/claude-process-manager.ts +6 -4
- package/apps/dashboard/lib/db-bridge.ts +32 -0
- package/apps/dashboard/lib/db.ts +159 -13
- package/apps/dashboard/lib/session-state-machine.ts +3 -0
- package/apps/dashboard/lib/session-stream-manager.ts +76 -13
- package/apps/dashboard/lib/tests.ts +3 -1
- package/apps/dashboard/next.config.js +19 -14
- package/apps/dashboard/package.json +3 -1
- package/apps/dashboard/scripts/upload-to-r2.js +89 -0
- package/apps/dashboard/tsconfig.tsbuildinfo +1 -0
- package/apps/update-server/package.json +16 -0
- package/apps/update-server/schema.sql +31 -0
- package/apps/update-server/src/index.ts +1074 -0
- package/apps/update-server/tsconfig.json +16 -0
- package/apps/update-server/wrangler.toml +35 -0
- package/docs/bdd-guidance.md +390 -0
- package/jettypod.js +5 -4
- package/lib/migrations/027-plan-at-creation-column.js +31 -0
- package/lib/migrations/028-ready-for-review-column.js +27 -0
- package/lib/schema.js +3 -1
- package/lib/seed-onboarding.js +100 -68
- package/lib/work-commands/index.js +43 -13
- package/lib/work-tracking/index.js +46 -27
- package/package.json +1 -1
- package/skills-templates/bug-mode/SKILL.md +5 -11
- package/skills-templates/request-routing/SKILL.md +24 -11
- package/skills-templates/simple-improvement/SKILL.md +35 -19
- package/skills-templates/stable-mode/SKILL.md +5 -6
- package/templates/bdd-guidance.md +139 -0
- package/templates/bdd-scaffolding/wait.js +18 -0
- package/templates/bdd-scaffolding/world.js +19 -0
- package/.jettypod-backup/work.db +0 -0
- package/apps/dashboard/app/access-code/page.tsx +0 -110
- package/lib/discovery-checkpoint.js +0 -123
- package/skills-templates/project-discovery/SKILL.md +0 -372
|
@@ -741,9 +741,8 @@ sqlite3 .jettypod/work.db "SELECT project_state FROM project_config WHERE id = 1
|
|
|
741
741
|
|
|
742
742
|
**If project_state = 'internal':**
|
|
743
743
|
|
|
744
|
-
|
|
745
|
-
jettypod work status <feature-id> done
|
|
746
|
-
```
|
|
744
|
+
**The merge command automatically sets the feature as ready for review.**
|
|
745
|
+
It will appear with accept/reject buttons on the kanban board. Do NOT call `jettypod work status <feature-id> done` — that bypasses the review gate.
|
|
747
746
|
|
|
748
747
|
**Display:**
|
|
749
748
|
|
|
@@ -854,7 +853,7 @@ Before ending stable-mode skill, ensure:
|
|
|
854
853
|
- [ ] Integration scenario still passes (feature remains reachable after error handling added)
|
|
855
854
|
- [ ] Final chore merged to main
|
|
856
855
|
- [ ] Project state checked (internal vs external)
|
|
857
|
-
- [ ] **INTERNAL:** Feature
|
|
856
|
+
- [ ] **INTERNAL:** Feature merged (review gate set automatically by merge command)
|
|
858
857
|
- [ ] **EXTERNAL:** Feature mode set to production AND production-mode skill invoked
|
|
859
858
|
|
|
860
859
|
---
|
|
@@ -878,8 +877,8 @@ jettypod work start <chore-id> # Create worktree and start chore
|
|
|
878
877
|
|
|
879
878
|
**Set feature status/mode:**
|
|
880
879
|
```bash
|
|
881
|
-
|
|
882
|
-
jettypod work set-mode <feature-id> production # Set feature to production mode
|
|
880
|
+
# ⚠️ Do NOT call `work status done` — merge sets the review gate automatically
|
|
881
|
+
jettypod work set-mode <feature-id> production # Set feature to production mode (external only)
|
|
883
882
|
```
|
|
884
883
|
|
|
885
884
|
**❌ DO NOT use these to complete chores:**
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# BDD Reference for Skills
|
|
2
|
+
|
|
3
|
+
Read this before writing scenarios or step definitions. Every rule here is enforced — not advisory.
|
|
4
|
+
|
|
5
|
+
## Scenario Formulation
|
|
6
|
+
|
|
7
|
+
**Feature files describe "what" and "why." Step definitions implement "how."**
|
|
8
|
+
|
|
9
|
+
Each scenario: one behavior, one reason to fail.
|
|
10
|
+
|
|
11
|
+
### Rules
|
|
12
|
+
|
|
13
|
+
- **Declarative** — describe intent, not UI clicks. "When the user retries payment" not "When I click the third button."
|
|
14
|
+
- **Stable** — no brittle details (pixel positions, timing, CSS selectors).
|
|
15
|
+
- **Deterministic** — use factories with known data. Never depend on ambient/existing data.
|
|
16
|
+
- **No implementation leakage** — no "Given the database has..." in feature files. Use intentful language: "Given a user with an unpaid invoice."
|
|
17
|
+
|
|
18
|
+
### Good
|
|
19
|
+
|
|
20
|
+
```gherkin
|
|
21
|
+
Scenario: User can retry a failed payment
|
|
22
|
+
Given a user with an unpaid invoice
|
|
23
|
+
And the payment processor returns "insufficient_funds"
|
|
24
|
+
When the user retries payment with a different card
|
|
25
|
+
Then the invoice is marked as paid
|
|
26
|
+
And the user sees a receipt
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Bad
|
|
30
|
+
|
|
31
|
+
```gherkin
|
|
32
|
+
Scenario: Pay invoice
|
|
33
|
+
Given I click the "Billing" tab
|
|
34
|
+
And I wait 2 seconds
|
|
35
|
+
And I click the third button on the page
|
|
36
|
+
When I type "4111111111111111" into the card field
|
|
37
|
+
Then I should see "Success"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Step Definition Banlist
|
|
41
|
+
|
|
42
|
+
These are BANNED in step definitions. Not "avoid" — banned.
|
|
43
|
+
|
|
44
|
+
| Banned | Use instead |
|
|
45
|
+
|--------|-------------|
|
|
46
|
+
| `setTimeout`, `sleep`, fixed waits | `waitFor(() => condition, { timeout })` polling utility |
|
|
47
|
+
| Module-level `let`/`var` state | Cucumber World (`this`) for per-scenario state |
|
|
48
|
+
| Raw SQL (`db.run`, `db.get`) | Helper methods in `features/support/helpers/` |
|
|
49
|
+
| Loops and branching logic | One intentful helper call per step |
|
|
50
|
+
| Assertions in Given/When steps | Assertions belong in Then steps only |
|
|
51
|
+
| Inline selectors/click chains | Page objects or screen models |
|
|
52
|
+
| Setting mock state then asserting it | Tests must exercise real production code |
|
|
53
|
+
|
|
54
|
+
### Thin Step Pattern
|
|
55
|
+
|
|
56
|
+
Steps do three things: parse parameters, call one helper, assert outcome.
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
// GOOD — thin step, calls helper
|
|
60
|
+
When('the user retries payment with a different card', async function () {
|
|
61
|
+
await this.billing.retryPaymentWith(this.validCard);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// BAD — inline implementation
|
|
65
|
+
When('the user retries payment with a different card', async function () {
|
|
66
|
+
await page.click('#billing-tab');
|
|
67
|
+
await page.waitForSelector('.invoice-list');
|
|
68
|
+
await page.click('.invoice:first-child .retry-btn');
|
|
69
|
+
await page.fill('#card-number', '4242424242424242');
|
|
70
|
+
await page.click('#submit');
|
|
71
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Helper Layer Architecture
|
|
76
|
+
|
|
77
|
+
Step definitions must not contain domain logic, SQL, or UI interaction directly. Use this layering:
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
Feature files (behavior — Gherkin)
|
|
81
|
+
└─ Step definitions (glue — thin, 1-5 lines)
|
|
82
|
+
└─ Helpers (features/support/helpers/)
|
|
83
|
+
├─ Domain helpers (intentful operations)
|
|
84
|
+
├─ API client
|
|
85
|
+
├─ DB helpers (encapsulated queries)
|
|
86
|
+
└─ Wait utilities (polling, not sleeping)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Dependency rules:**
|
|
90
|
+
- Step defs call helpers. Never call drivers directly.
|
|
91
|
+
- Helpers call drivers (DB, API, UI).
|
|
92
|
+
- Feature files know nothing about implementation.
|
|
93
|
+
|
|
94
|
+
**Before writing step definitions:** Read `features/support/helpers/` for existing helpers. Reuse before creating new ones.
|
|
95
|
+
|
|
96
|
+
## Async & External Dependencies
|
|
97
|
+
|
|
98
|
+
**Async:** Poll with timeout, never sleep.
|
|
99
|
+
```js
|
|
100
|
+
await waitFor(() => order.status === 'COMPLETED', { timeout: 10_000 });
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**External services:** Stub at the boundary for most tests. Use fakes (in-memory implementations) over mocks. Mocks test call patterns ("did we call X?"); fakes test outcomes ("did the user get the result?").
|
|
104
|
+
|
|
105
|
+
**Auth flows:** Use test shortcuts (token minting, session injection). Don't make every scenario pay the login tax.
|
|
106
|
+
|
|
107
|
+
## Test Doubles
|
|
108
|
+
|
|
109
|
+
Prefer stubs/fakes for BDD. They support behavior assertions without coupling to implementation.
|
|
110
|
+
|
|
111
|
+
Use mocks only when verifying a side effect IS the scenario's purpose (e.g., "audit event was emitted").
|
|
112
|
+
|
|
113
|
+
**BDD asserts outcomes, not internal choreography.**
|
|
114
|
+
|
|
115
|
+
## Data Setup
|
|
116
|
+
|
|
117
|
+
Use intentful factories:
|
|
118
|
+
```js
|
|
119
|
+
// GOOD
|
|
120
|
+
await this.fixtures.createUserWithUnpaidInvoice();
|
|
121
|
+
|
|
122
|
+
// BAD — implementation leakage
|
|
123
|
+
await db.run('INSERT INTO users...');
|
|
124
|
+
await db.run('INSERT INTO invoices...');
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Freeze time and seed randomness. Assert on meaning ("receipt exists") not raw IDs.
|
|
128
|
+
|
|
129
|
+
## Quality Checklist
|
|
130
|
+
|
|
131
|
+
After writing tests, verify:
|
|
132
|
+
|
|
133
|
+
- [ ] Steps are 1-5 lines, each calling one helper
|
|
134
|
+
- [ ] No `setTimeout` or `sleep` anywhere in step definitions
|
|
135
|
+
- [ ] State stored on `this` (Cucumber World), not module-level variables
|
|
136
|
+
- [ ] No raw SQL in step definitions
|
|
137
|
+
- [ ] Assertions only in Then steps
|
|
138
|
+
- [ ] Tests exercise real production code (not asserting mock state you just set)
|
|
139
|
+
- [ ] Existing helpers reused, not duplicated
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Poll a condition function until it returns true, with timeout.
|
|
3
|
+
* Use this instead of setTimeout/sleep in step definitions.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* const { waitFor } = require('../helpers/wait');
|
|
7
|
+
* await waitFor(() => order.status === 'COMPLETED', { timeout: 10000 });
|
|
8
|
+
*/
|
|
9
|
+
async function waitFor(conditionFn, { timeout = 5000, interval = 100 } = {}) {
|
|
10
|
+
const start = Date.now();
|
|
11
|
+
while (Date.now() - start < timeout) {
|
|
12
|
+
if (await conditionFn()) return;
|
|
13
|
+
await new Promise(r => setTimeout(r, interval));
|
|
14
|
+
}
|
|
15
|
+
throw new Error(`waitFor timed out after ${timeout}ms`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = { waitFor };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cucumber World configuration.
|
|
3
|
+
* Provides per-scenario state via `this` in step definitions.
|
|
4
|
+
* Use this instead of module-level let/var for test state.
|
|
5
|
+
*
|
|
6
|
+
* Place at: features/support/world.js
|
|
7
|
+
*/
|
|
8
|
+
const { setWorldConstructor } = require('@cucumber/cucumber');
|
|
9
|
+
|
|
10
|
+
class TestWorld {
|
|
11
|
+
constructor() {
|
|
12
|
+
// Per-scenario state — automatically reset between scenarios.
|
|
13
|
+
// Add properties here or in step definitions via this.propName = value
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
setWorldConstructor(TestWorld);
|
|
18
|
+
|
|
19
|
+
module.exports = { TestWorld };
|
package/.jettypod-backup/work.db
DELETED
|
Binary file
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useState } from 'react';
|
|
4
|
-
import Image from 'next/image';
|
|
5
|
-
|
|
6
|
-
export default function AccessCodePage() {
|
|
7
|
-
const [code, setCode] = useState('');
|
|
8
|
-
const [error, setError] = useState<string | null>(null);
|
|
9
|
-
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
10
|
-
|
|
11
|
-
const handleSubmit = async (e: React.FormEvent) => {
|
|
12
|
-
e.preventDefault();
|
|
13
|
-
setError(null);
|
|
14
|
-
|
|
15
|
-
if (!code.trim()) {
|
|
16
|
-
setError('Please enter an access code.');
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
if (!window.electronAPI?.isElectron) {
|
|
21
|
-
setError('Access code validation is only available in the desktop app.');
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
setIsSubmitting(true);
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
const result = await window.electronAPI.access.validate(code.trim());
|
|
29
|
-
|
|
30
|
-
if (!result.success) {
|
|
31
|
-
setError(result.error || 'Invalid access code.');
|
|
32
|
-
setIsSubmitting(false);
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Access granted - navigate to main app
|
|
37
|
-
window.location.href = '/';
|
|
38
|
-
} catch (err) {
|
|
39
|
-
setError(err instanceof Error ? err.message : 'Failed to validate access code.');
|
|
40
|
-
setIsSubmitting(false);
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
return (
|
|
45
|
-
<div className="flex flex-col items-center justify-center min-h-screen bg-white dark:bg-zinc-900 p-8">
|
|
46
|
-
<div className="max-w-md w-full space-y-8">
|
|
47
|
-
{/* Logo */}
|
|
48
|
-
<div className="flex flex-col items-center space-y-4">
|
|
49
|
-
<Image
|
|
50
|
-
src="/jettypod_wordmark.png"
|
|
51
|
-
alt="JettyPod"
|
|
52
|
-
width={160}
|
|
53
|
-
height={40}
|
|
54
|
-
priority
|
|
55
|
-
/>
|
|
56
|
-
<h1 className="text-2xl font-semibold text-zinc-900 dark:text-zinc-100 text-center">
|
|
57
|
-
Enter Access Code
|
|
58
|
-
</h1>
|
|
59
|
-
<p className="text-zinc-500 dark:text-zinc-400 text-center">
|
|
60
|
-
Enter your access code to get started with JettyPod.
|
|
61
|
-
</p>
|
|
62
|
-
</div>
|
|
63
|
-
|
|
64
|
-
{/* Error */}
|
|
65
|
-
{error && (
|
|
66
|
-
<div className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 text-red-700 dark:text-red-400 px-4 py-3 rounded-lg text-sm">
|
|
67
|
-
{error}
|
|
68
|
-
</div>
|
|
69
|
-
)}
|
|
70
|
-
|
|
71
|
-
{/* Form */}
|
|
72
|
-
<form onSubmit={handleSubmit} className="pt-4 space-y-4">
|
|
73
|
-
<input
|
|
74
|
-
type="text"
|
|
75
|
-
value={code}
|
|
76
|
-
onChange={(e) => setCode(e.target.value)}
|
|
77
|
-
placeholder="Access code"
|
|
78
|
-
autoFocus
|
|
79
|
-
disabled={isSubmitting}
|
|
80
|
-
className="w-full px-4 py-3 rounded-xl border border-zinc-300 dark:border-zinc-600 bg-white dark:bg-zinc-800 text-zinc-900 dark:text-zinc-100 placeholder-zinc-400 dark:placeholder-zinc-500 focus:outline-none focus:ring-2 focus:ring-zinc-400 dark:focus:ring-zinc-500 disabled:opacity-50"
|
|
81
|
-
data-testid="access-code-input"
|
|
82
|
-
/>
|
|
83
|
-
<button
|
|
84
|
-
type="submit"
|
|
85
|
-
disabled={isSubmitting}
|
|
86
|
-
className="w-full py-3 px-6 rounded-xl font-medium transition-all duration-200 hover:-translate-y-1 hover:scale-[1.01] active:translate-y-0 active:scale-100 disabled:opacity-50 disabled:pointer-events-none"
|
|
87
|
-
style={{
|
|
88
|
-
cursor: isSubmitting ? 'default' : 'pointer',
|
|
89
|
-
background: 'linear-gradient(145deg, #ffffff 0%, #faf9f7 10%, #f0f4f4 35%, #c8d9da 55%, #819D9F 90%)',
|
|
90
|
-
color: '#3d4d4e',
|
|
91
|
-
boxShadow: `
|
|
92
|
-
0 1px 1px rgba(0, 0, 0, 0.02),
|
|
93
|
-
0 2px 4px rgba(0, 0, 0, 0.03),
|
|
94
|
-
0 6px 12px rgba(0, 0, 0, 0.05),
|
|
95
|
-
0 12px 24px rgba(0, 0, 0, 0.06),
|
|
96
|
-
0 20px 40px rgba(129, 157, 159, 0.2),
|
|
97
|
-
0 32px 64px rgba(129, 157, 159, 0.18),
|
|
98
|
-
inset 0 2px 4px rgba(255, 255, 255, 1),
|
|
99
|
-
inset 0 -2px 4px rgba(129, 157, 159, 0.05)
|
|
100
|
-
`,
|
|
101
|
-
}}
|
|
102
|
-
data-testid="access-code-submit"
|
|
103
|
-
>
|
|
104
|
-
{isSubmitting ? 'Validating...' : 'Continue'}
|
|
105
|
-
</button>
|
|
106
|
-
</form>
|
|
107
|
-
</div>
|
|
108
|
-
</div>
|
|
109
|
-
);
|
|
110
|
-
}
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
const config = require('./config');
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Update discovery checkpoint with current progress
|
|
5
|
-
* @param {number} step - Current discovery step (1-7)
|
|
6
|
-
* @param {Object} data - Checkpoint data (user_journey, ux_approach, epics_created)
|
|
7
|
-
*/
|
|
8
|
-
function updateCheckpoint(step, data = {}) {
|
|
9
|
-
const currentConfig = config.read();
|
|
10
|
-
|
|
11
|
-
// Ensure project_discovery exists
|
|
12
|
-
if (!currentConfig.project_discovery) {
|
|
13
|
-
currentConfig.project_discovery = config.getDefaultProjectDiscovery();
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// Update checkpoint
|
|
17
|
-
currentConfig.project_discovery.checkpoint = {
|
|
18
|
-
step,
|
|
19
|
-
user_journey: data.user_journey || currentConfig.project_discovery.checkpoint?.user_journey || null,
|
|
20
|
-
ux_approach: data.ux_approach || currentConfig.project_discovery.checkpoint?.ux_approach || null,
|
|
21
|
-
epics_created: data.epics_created !== undefined ? data.epics_created : (currentConfig.project_discovery.checkpoint?.epics_created || false)
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
// Update status to in_progress if not already completed
|
|
25
|
-
if (currentConfig.project_discovery.status === 'not_started') {
|
|
26
|
-
currentConfig.project_discovery.status = 'in_progress';
|
|
27
|
-
currentConfig.project_discovery.started_date = new Date().toISOString();
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
config.write(currentConfig);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Get current discovery checkpoint
|
|
35
|
-
* @returns {Object|null} Checkpoint data or null if not started
|
|
36
|
-
*/
|
|
37
|
-
function getCheckpoint() {
|
|
38
|
-
const currentConfig = config.read();
|
|
39
|
-
|
|
40
|
-
if (!currentConfig.project_discovery || currentConfig.project_discovery.status === 'not_started') {
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return currentConfig.project_discovery.checkpoint || null;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Clear discovery checkpoint (when discovery is completed)
|
|
49
|
-
*/
|
|
50
|
-
function clearCheckpoint() {
|
|
51
|
-
const currentConfig = config.read();
|
|
52
|
-
|
|
53
|
-
if (currentConfig.project_discovery) {
|
|
54
|
-
currentConfig.project_discovery.checkpoint = config.getDefaultProjectDiscovery().checkpoint;
|
|
55
|
-
config.write(currentConfig);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Get human-readable description of current checkpoint step
|
|
61
|
-
* @param {number} step - Step number
|
|
62
|
-
* @returns {string} Step description
|
|
63
|
-
*/
|
|
64
|
-
function getStepDescription(step) {
|
|
65
|
-
const steps = {
|
|
66
|
-
1: 'Starting discovery - define user journey',
|
|
67
|
-
2: 'User journey defined - present UX approaches',
|
|
68
|
-
3: 'UX approach selected - build prototypes',
|
|
69
|
-
4: 'Prototypes tested - break into epics',
|
|
70
|
-
5: 'Epics created - choose tech stack',
|
|
71
|
-
6: 'Tech stack chosen - record decision',
|
|
72
|
-
7: 'Discovery complete'
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
return steps[step] || 'Unknown step';
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Generate a formatted resume message for displaying when resuming discovery
|
|
80
|
-
* @param {Object} checkpoint - Checkpoint object with step, user_journey, ux_approach, epics_created
|
|
81
|
-
* @returns {string|null} Formatted resume message or null if no checkpoint
|
|
82
|
-
*/
|
|
83
|
-
function generateResumeMessage(checkpoint) {
|
|
84
|
-
if (!checkpoint) {
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const stepDesc = getStepDescription(checkpoint.step);
|
|
89
|
-
|
|
90
|
-
let lines = [
|
|
91
|
-
'━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━',
|
|
92
|
-
'🔄 RESUMING PROJECT DISCOVERY',
|
|
93
|
-
'━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━',
|
|
94
|
-
'',
|
|
95
|
-
`📍 Resuming from Step ${checkpoint.step}: ${stepDesc}`,
|
|
96
|
-
''
|
|
97
|
-
];
|
|
98
|
-
|
|
99
|
-
if (checkpoint.user_journey) {
|
|
100
|
-
lines.push(`📝 User Journey: "${checkpoint.user_journey}"`);
|
|
101
|
-
}
|
|
102
|
-
if (checkpoint.ux_approach) {
|
|
103
|
-
lines.push(`🎨 UX Approach: "${checkpoint.ux_approach}"`);
|
|
104
|
-
}
|
|
105
|
-
if (checkpoint.epics_created) {
|
|
106
|
-
lines.push(`✅ Epics: Already created`);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
lines.push('');
|
|
110
|
-
lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
111
|
-
lines.push('');
|
|
112
|
-
lines.push('Continuing where you left off...');
|
|
113
|
-
|
|
114
|
-
return lines.join('\n');
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
module.exports = {
|
|
118
|
-
updateCheckpoint,
|
|
119
|
-
getCheckpoint,
|
|
120
|
-
clearCheckpoint,
|
|
121
|
-
getStepDescription,
|
|
122
|
-
generateResumeMessage
|
|
123
|
-
};
|