prjct-cli 0.13.3 → 0.15.1
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/CHANGELOG.md +122 -0
- package/bin/prjct +10 -13
- package/core/agentic/memory-system/semantic-memories.ts +2 -1
- package/core/agentic/plan-mode/plan-mode.ts +2 -1
- package/core/agentic/prompt-builder.ts +22 -43
- package/core/agentic/services.ts +5 -5
- package/core/agentic/smart-context.ts +7 -2
- package/core/command-registry/core-commands.ts +54 -29
- package/core/command-registry/optional-commands.ts +64 -0
- package/core/command-registry/setup-commands.ts +18 -3
- package/core/commands/analysis.ts +21 -68
- package/core/commands/analytics.ts +247 -213
- package/core/commands/base.ts +1 -1
- package/core/commands/index.ts +41 -36
- package/core/commands/maintenance.ts +300 -31
- package/core/commands/planning.ts +233 -22
- package/core/commands/setup.ts +3 -8
- package/core/commands/shipping.ts +14 -18
- package/core/commands/types.ts +8 -6
- package/core/commands/workflow.ts +105 -100
- package/core/context/generator.ts +317 -0
- package/core/context-sync.ts +7 -350
- package/core/data/index.ts +13 -32
- package/core/data/md-ideas-manager.ts +155 -0
- package/core/data/md-queue-manager.ts +4 -3
- package/core/data/md-shipped-manager.ts +90 -0
- package/core/data/md-state-manager.ts +11 -7
- package/core/domain/agent-generator.ts +23 -63
- package/core/events/index.ts +143 -0
- package/core/index.ts +17 -14
- package/core/infrastructure/capability-installer.ts +13 -149
- package/core/infrastructure/migrator/project-scanner.ts +2 -1
- package/core/infrastructure/path-manager.ts +4 -6
- package/core/infrastructure/setup.ts +3 -0
- package/core/infrastructure/uuid-migration.ts +750 -0
- package/core/outcomes/recorder.ts +2 -1
- package/core/plugin/loader.ts +4 -7
- package/core/plugin/registry.ts +3 -3
- package/core/schemas/index.ts +23 -25
- package/core/schemas/state.ts +1 -0
- package/core/serializers/ideas-serializer.ts +187 -0
- package/core/serializers/index.ts +16 -0
- package/core/serializers/shipped-serializer.ts +108 -0
- package/core/session/utils.ts +3 -9
- package/core/storage/ideas-storage.ts +273 -0
- package/core/storage/index.ts +204 -0
- package/core/storage/queue-storage.ts +297 -0
- package/core/storage/shipped-storage.ts +223 -0
- package/core/storage/state-storage.ts +235 -0
- package/core/storage/storage-manager.ts +175 -0
- package/package.json +1 -1
- package/packages/web/app/api/projects/[id]/momentum/route.ts +257 -0
- package/packages/web/app/api/sessions/current/route.ts +132 -0
- package/packages/web/app/api/sessions/history/route.ts +96 -14
- package/packages/web/app/globals.css +5 -0
- package/packages/web/app/layout.tsx +2 -0
- package/packages/web/app/project/[id]/code/layout.tsx +18 -0
- package/packages/web/app/project/[id]/code/page.tsx +408 -0
- package/packages/web/app/project/[id]/page.tsx +359 -389
- package/packages/web/app/project/[id]/reports/page.tsx +59 -0
- package/packages/web/app/project/[id]/reports/print/page.tsx +58 -0
- package/packages/web/components/ActivityTimeline/ActivityTimeline.tsx +0 -1
- package/packages/web/components/AgentsCard/AgentsCard.tsx +64 -34
- package/packages/web/components/AgentsCard/AgentsCard.types.ts +1 -0
- package/packages/web/components/AppSidebar/AppSidebar.tsx +135 -11
- package/packages/web/components/BentoCard/BentoCard.constants.ts +3 -3
- package/packages/web/components/BentoCard/BentoCard.tsx +2 -1
- package/packages/web/components/BentoGrid/BentoGrid.tsx +2 -2
- package/packages/web/components/BlockersCard/BlockersCard.tsx +65 -57
- package/packages/web/components/BlockersCard/BlockersCard.types.ts +1 -0
- package/packages/web/components/CommandBar/CommandBar.tsx +67 -0
- package/packages/web/components/CommandBar/index.ts +1 -0
- package/packages/web/components/DashboardContent/DashboardContent.tsx +35 -5
- package/packages/web/components/DateGroup/DateGroup.tsx +1 -1
- package/packages/web/components/EmptyState/EmptyState.tsx +39 -21
- package/packages/web/components/EmptyState/EmptyState.types.ts +1 -0
- package/packages/web/components/EventRow/EventRow.tsx +4 -4
- package/packages/web/components/EventRow/EventRow.utils.ts +3 -3
- package/packages/web/components/HeroSection/HeroSection.tsx +52 -15
- package/packages/web/components/HeroSection/HeroSection.types.ts +4 -4
- package/packages/web/components/HeroSection/HeroSection.utils.ts +7 -3
- package/packages/web/components/IdeasCard/IdeasCard.tsx +94 -27
- package/packages/web/components/IdeasCard/IdeasCard.types.ts +1 -0
- package/packages/web/components/MasonryGrid/MasonryGrid.tsx +18 -0
- package/packages/web/components/MasonryGrid/index.ts +1 -0
- package/packages/web/components/MomentumWidget/MomentumWidget.tsx +119 -0
- package/packages/web/components/MomentumWidget/MomentumWidget.types.ts +16 -0
- package/packages/web/components/MomentumWidget/index.ts +2 -0
- package/packages/web/components/NowCard/NowCard.tsx +81 -56
- package/packages/web/components/NowCard/NowCard.types.ts +1 -0
- package/packages/web/components/PageHeader/PageHeader.tsx +24 -0
- package/packages/web/components/PageHeader/index.ts +1 -0
- package/packages/web/components/ProgressRing/ProgressRing.constants.ts +2 -2
- package/packages/web/components/ProjectAvatar/ProjectAvatar.tsx +2 -2
- package/packages/web/components/ProjectColorDot/ProjectColorDot.tsx +37 -0
- package/packages/web/components/ProjectColorDot/index.ts +1 -0
- package/packages/web/components/ProjectSelectorModal/ProjectSelectorModal.tsx +104 -0
- package/packages/web/components/ProjectSelectorModal/index.ts +1 -0
- package/packages/web/components/Providers/Providers.tsx +4 -1
- package/packages/web/components/QueueCard/QueueCard.tsx +78 -25
- package/packages/web/components/QueueCard/QueueCard.types.ts +1 -0
- package/packages/web/components/QueueCard/QueueCard.utils.ts +3 -3
- package/packages/web/components/RecoverCard/RecoverCard.tsx +72 -0
- package/packages/web/components/RecoverCard/RecoverCard.types.ts +16 -0
- package/packages/web/components/RecoverCard/index.ts +2 -0
- package/packages/web/components/RoadmapCard/RoadmapCard.tsx +101 -33
- package/packages/web/components/RoadmapCard/RoadmapCard.types.ts +1 -0
- package/packages/web/components/ShipsCard/ShipsCard.tsx +71 -28
- package/packages/web/components/ShipsCard/ShipsCard.types.ts +2 -0
- package/packages/web/components/SparklineChart/SparklineChart.tsx +20 -18
- package/packages/web/components/StatsMasonry/StatsMasonry.tsx +95 -0
- package/packages/web/components/StatsMasonry/index.ts +1 -0
- package/packages/web/components/StreakCard/StreakCard.tsx +37 -35
- package/packages/web/components/TasksCounter/TasksCounter.tsx +1 -1
- package/packages/web/components/TechStackBadges/TechStackBadges.tsx +12 -4
- package/packages/web/components/TerminalDock/DockToggleTab.tsx +29 -0
- package/packages/web/components/TerminalDock/TerminalDock.tsx +386 -0
- package/packages/web/components/TerminalDock/TerminalDockTab.tsx +130 -0
- package/packages/web/components/TerminalDock/TerminalTabBar.tsx +142 -0
- package/packages/web/components/TerminalDock/index.ts +2 -0
- package/packages/web/components/VelocityBadge/VelocityBadge.tsx +8 -3
- package/packages/web/components/VelocityCard/VelocityCard.tsx +49 -47
- package/packages/web/components/WeeklyReports/PrintableReport.tsx +259 -0
- package/packages/web/components/WeeklyReports/ReportPreviewCard.tsx +187 -0
- package/packages/web/components/WeeklyReports/WeekCalendar.tsx +288 -0
- package/packages/web/components/WeeklyReports/WeeklyReports.tsx +149 -0
- package/packages/web/components/WeeklyReports/index.ts +4 -0
- package/packages/web/components/WeeklySparkline/WeeklySparkline.tsx +16 -4
- package/packages/web/components/WeeklySparkline/WeeklySparkline.types.ts +1 -0
- package/packages/web/components/charts/SessionsChart.tsx +6 -3
- package/packages/web/components/ui/dialog.tsx +143 -0
- package/packages/web/components/ui/drawer.tsx +135 -0
- package/packages/web/components/ui/select.tsx +187 -0
- package/packages/web/context/GlobalTerminalContext.tsx +538 -0
- package/packages/web/lib/commands.ts +81 -0
- package/packages/web/lib/generate-week-report.ts +285 -0
- package/packages/web/lib/parse-prjct-files.ts +56 -55
- package/packages/web/lib/project-colors.ts +58 -0
- package/packages/web/lib/projects.ts +58 -5
- package/packages/web/lib/services/projects.server.ts +11 -1
- package/packages/web/next-env.d.ts +1 -1
- package/packages/web/package.json +5 -1
- package/templates/commands/analyze.md +39 -3
- package/templates/commands/ask.md +58 -3
- package/templates/commands/bug.md +117 -26
- package/templates/commands/dash.md +95 -158
- package/templates/commands/done.md +130 -148
- package/templates/commands/feature.md +125 -103
- package/templates/commands/git.md +18 -3
- package/templates/commands/idea.md +121 -38
- package/templates/commands/init.md +124 -20
- package/templates/commands/migrate-all.md +63 -28
- package/templates/commands/migrate.md +140 -0
- package/templates/commands/next.md +115 -5
- package/templates/commands/now.md +146 -82
- package/templates/commands/pause.md +89 -74
- package/templates/commands/redo.md +6 -4
- package/templates/commands/resume.md +141 -59
- package/templates/commands/setup.md +18 -3
- package/templates/commands/ship.md +103 -231
- package/templates/commands/spec.md +98 -8
- package/templates/commands/suggest.md +22 -2
- package/templates/commands/sync.md +192 -203
- package/templates/commands/undo.md +6 -4
- package/templates/mcp-config.json +20 -1
- package/core/data/agents-manager.ts +0 -76
- package/core/data/analysis-manager.ts +0 -83
- package/core/data/base-manager.ts +0 -156
- package/core/data/ideas-manager.ts +0 -81
- package/core/data/outcomes-manager.ts +0 -96
- package/core/data/project-manager.ts +0 -75
- package/core/data/roadmap-manager.ts +0 -118
- package/core/data/shipped-manager.ts +0 -65
- package/core/data/state-manager.ts +0 -214
- package/core/state/index.ts +0 -25
- package/core/state/manager.ts +0 -376
- package/core/state/types.ts +0 -185
- package/core/utils/project-capabilities.ts +0 -156
- package/core/view-generator.ts +0 -536
- package/packages/web/app/project/[id]/stats/loading.tsx +0 -43
- package/packages/web/app/project/[id]/stats/page.tsx +0 -253
- package/templates/agent-assignment.md +0 -72
- package/templates/analysis/project-analysis.md +0 -78
- package/templates/checklists/accessibility.md +0 -33
- package/templates/commands/build.md +0 -17
- package/templates/commands/decision.md +0 -226
- package/templates/commands/fix.md +0 -79
- package/templates/commands/help.md +0 -61
- package/templates/commands/progress.md +0 -14
- package/templates/commands/recap.md +0 -14
- package/templates/commands/roadmap.md +0 -52
- package/templates/commands/status.md +0 -17
- package/templates/commands/task.md +0 -63
- package/templates/commands/work.md +0 -44
- package/templates/commands/workflow.md +0 -12
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import path from 'path'
|
|
9
9
|
import * as fileHelper from '../utils/file-helper'
|
|
10
10
|
import pathManager from '../infrastructure/path-manager'
|
|
11
|
+
import { generateUUID } from '../schemas'
|
|
11
12
|
import type { Outcome, OutcomeInput, OutcomeFilter } from './types'
|
|
12
13
|
|
|
13
14
|
const OUTCOMES_DIR = 'outcomes'
|
|
@@ -38,7 +39,7 @@ export class OutcomeRecorder {
|
|
|
38
39
|
async record(projectId: string, input: OutcomeInput): Promise<Outcome> {
|
|
39
40
|
const outcome: Outcome = {
|
|
40
41
|
...input,
|
|
41
|
-
id:
|
|
42
|
+
id: generateUUID(),
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
const outcomesPath = this.getOutcomesPath(projectId)
|
package/core/plugin/loader.ts
CHANGED
|
@@ -167,8 +167,9 @@ class PluginLoader {
|
|
|
167
167
|
// Check if file exists
|
|
168
168
|
await fs.access(pluginPath)
|
|
169
169
|
|
|
170
|
-
//
|
|
171
|
-
const
|
|
170
|
+
// Import the plugin dynamically
|
|
171
|
+
const pluginModule = await import(pluginPath)
|
|
172
|
+
const plugin: Plugin = pluginModule.default || pluginModule
|
|
172
173
|
|
|
173
174
|
// Validate plugin structure
|
|
174
175
|
if (!plugin.name) {
|
|
@@ -259,11 +260,7 @@ class PluginLoader {
|
|
|
259
260
|
// Remove from loaded plugins
|
|
260
261
|
this.plugins.delete(name)
|
|
261
262
|
|
|
262
|
-
// Clear
|
|
263
|
-
const pluginPath = this.pluginPaths.get(name)
|
|
264
|
-
if (pluginPath) {
|
|
265
|
-
delete require.cache[require.resolve(pluginPath)]
|
|
266
|
-
}
|
|
263
|
+
// Clear plugin path reference
|
|
267
264
|
this.pluginPaths.delete(name)
|
|
268
265
|
}
|
|
269
266
|
|
package/core/plugin/registry.ts
CHANGED
|
@@ -122,9 +122,9 @@ class PluginRegistry {
|
|
|
122
122
|
}
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
// Fallback: try to
|
|
126
|
-
const
|
|
127
|
-
|
|
125
|
+
// Fallback: try to import (might have side effects)
|
|
126
|
+
const pluginModule = await import(pluginPath)
|
|
127
|
+
const plugin = pluginModule.default || pluginModule
|
|
128
128
|
|
|
129
129
|
if (plugin.name) {
|
|
130
130
|
return {
|
package/core/schemas/index.ts
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Schemas Module
|
|
3
3
|
*
|
|
4
|
-
* TypeScript types and defaults for all
|
|
5
|
-
*
|
|
4
|
+
* TypeScript types and defaults for all project data.
|
|
5
|
+
* MD-First Architecture: Markdown files are the source of truth.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* Structure: ~/.prjct-cli/projects/{projectId}/
|
|
8
|
+
* - core/ (now.md, next.md, context.md)
|
|
9
|
+
* - progress/ (shipped.md, metrics.md)
|
|
10
|
+
* - planning/ (ideas.md, roadmap.md, tasks/)
|
|
11
|
+
* - analysis/ (repo-summary.md)
|
|
12
|
+
* - memory/ (context.jsonl, patterns.json)
|
|
9
13
|
*/
|
|
10
14
|
|
|
11
15
|
// State (current task + queue)
|
|
@@ -33,23 +37,25 @@ export * from './analysis'
|
|
|
33
37
|
export * from './outcomes'
|
|
34
38
|
|
|
35
39
|
// ============================================
|
|
36
|
-
// ID GENERATORS
|
|
40
|
+
// ID GENERATORS - UUID ONLY
|
|
37
41
|
// ============================================
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
import crypto from 'crypto'
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Generate a standard UUID.
|
|
47
|
+
* All IDs in the system use this format for PostgreSQL consistency.
|
|
48
|
+
*/
|
|
49
|
+
export function generateUUID(): string {
|
|
50
|
+
return crypto.randomUUID()
|
|
46
51
|
}
|
|
47
52
|
|
|
48
|
-
|
|
49
|
-
export const
|
|
50
|
-
export const
|
|
51
|
-
export const
|
|
52
|
-
export const
|
|
53
|
+
// All use the same UUID generator
|
|
54
|
+
export const generateTaskId = generateUUID
|
|
55
|
+
export const generateFeatureId = generateUUID
|
|
56
|
+
export const generateIdeaId = generateUUID
|
|
57
|
+
export const generateShipId = generateUUID
|
|
58
|
+
export const generateSessionId = generateUUID
|
|
53
59
|
|
|
54
60
|
// ============================================
|
|
55
61
|
// PATH HELPERS
|
|
@@ -63,11 +69,3 @@ export const GLOBAL_STORAGE = join(homedir(), '.prjct-cli', 'projects')
|
|
|
63
69
|
export function getProjectPath(projectId: string): string {
|
|
64
70
|
return join(GLOBAL_STORAGE, projectId)
|
|
65
71
|
}
|
|
66
|
-
|
|
67
|
-
export function getDataPath(projectId: string): string {
|
|
68
|
-
return join(GLOBAL_STORAGE, projectId, 'data')
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export function getViewsPath(projectId: string): string {
|
|
72
|
-
return join(GLOBAL_STORAGE, projectId, 'views')
|
|
73
|
-
}
|
package/core/schemas/state.ts
CHANGED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ideas Serializer
|
|
3
|
+
*
|
|
4
|
+
* Parses and serializes ideas.md for idea backlog.
|
|
5
|
+
*
|
|
6
|
+
* MD Format (ideas.md):
|
|
7
|
+
* ```
|
|
8
|
+
* # IDEAS 💡
|
|
9
|
+
*
|
|
10
|
+
* ## Brain Dump
|
|
11
|
+
*
|
|
12
|
+
* - Add dark mode _(2025-12-10)_ #ui #enhancement
|
|
13
|
+
* - Improve caching _(2025-12-09)_ #performance
|
|
14
|
+
*
|
|
15
|
+
* ## Converted
|
|
16
|
+
*
|
|
17
|
+
* - ✓ Add user auth → feat_123 _(2025-12-08)_
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export type IdeaStatus = 'pending' | 'converted' | 'archived'
|
|
22
|
+
export type IdeaPriority = 'low' | 'medium' | 'high'
|
|
23
|
+
|
|
24
|
+
export interface Idea {
|
|
25
|
+
id: string
|
|
26
|
+
text: string
|
|
27
|
+
status: IdeaStatus
|
|
28
|
+
priority: IdeaPriority
|
|
29
|
+
tags: string[]
|
|
30
|
+
addedAt: string // ISO8601
|
|
31
|
+
convertedTo?: string // featureId if converted
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Parse ideas.md content to Idea[]
|
|
36
|
+
*/
|
|
37
|
+
export function parseIdeas(content: string): Idea[] {
|
|
38
|
+
if (!content || !content.trim()) {
|
|
39
|
+
return []
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const ideas: Idea[] = []
|
|
43
|
+
|
|
44
|
+
// Split by sections
|
|
45
|
+
const sections = content.split(/\n## /).slice(1)
|
|
46
|
+
|
|
47
|
+
for (const section of sections) {
|
|
48
|
+
const lines = section.trim().split('\n')
|
|
49
|
+
const sectionName = lines[0]?.trim().toLowerCase() || ''
|
|
50
|
+
|
|
51
|
+
// Determine status based on section
|
|
52
|
+
let status: IdeaStatus = 'pending'
|
|
53
|
+
if (sectionName.includes('converted')) {
|
|
54
|
+
status = 'converted'
|
|
55
|
+
} else if (sectionName.includes('archived')) {
|
|
56
|
+
status = 'archived'
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Parse list items
|
|
60
|
+
for (let i = 1; i < lines.length; i++) {
|
|
61
|
+
const line = lines[i].trim()
|
|
62
|
+
if (!line.startsWith('-')) continue
|
|
63
|
+
|
|
64
|
+
const itemText = line.slice(1).trim()
|
|
65
|
+
if (!itemText) continue
|
|
66
|
+
|
|
67
|
+
// Extract date: _(date)_
|
|
68
|
+
const dateMatch = itemText.match(/_\((.+?)\)_/)
|
|
69
|
+
const addedAt = dateMatch ? dateMatch[1].trim() : new Date().toISOString()
|
|
70
|
+
|
|
71
|
+
// Extract tags: #tag1 #tag2
|
|
72
|
+
const tags = (itemText.match(/#(\w+)/g) || []).map(t => t.slice(1))
|
|
73
|
+
|
|
74
|
+
// Extract converted feature id: → feat_123
|
|
75
|
+
const convertedMatch = itemText.match(/→\s*(\w+)/)
|
|
76
|
+
const convertedTo = convertedMatch ? convertedMatch[1] : undefined
|
|
77
|
+
|
|
78
|
+
// Clean text: remove date, tags, and conversion marker
|
|
79
|
+
let text = itemText
|
|
80
|
+
.replace(/_\(.+?\)_/g, '')
|
|
81
|
+
.replace(/#\w+/g, '')
|
|
82
|
+
.replace(/→\s*\w+/g, '')
|
|
83
|
+
.replace(/^✓\s*/, '')
|
|
84
|
+
.trim()
|
|
85
|
+
|
|
86
|
+
// Detect priority from keywords
|
|
87
|
+
let priority: IdeaPriority = 'medium'
|
|
88
|
+
if (text.toLowerCase().includes('urgent') || text.toLowerCase().includes('critical')) {
|
|
89
|
+
priority = 'high'
|
|
90
|
+
} else if (text.toLowerCase().includes('nice to have') || text.toLowerCase().includes('maybe')) {
|
|
91
|
+
priority = 'low'
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
ideas.push({
|
|
95
|
+
id: `idea_${Date.parse(addedAt) || Date.now()}_${i}`,
|
|
96
|
+
text,
|
|
97
|
+
status,
|
|
98
|
+
priority,
|
|
99
|
+
tags,
|
|
100
|
+
addedAt,
|
|
101
|
+
convertedTo
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return ideas
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Serialize Idea[] to ideas.md format
|
|
111
|
+
*/
|
|
112
|
+
export function serializeIdeas(ideas: Idea[]): string {
|
|
113
|
+
const lines: string[] = ['# IDEAS 💡', '']
|
|
114
|
+
|
|
115
|
+
const pending = ideas.filter(i => i.status === 'pending')
|
|
116
|
+
const converted = ideas.filter(i => i.status === 'converted')
|
|
117
|
+
const archived = ideas.filter(i => i.status === 'archived')
|
|
118
|
+
|
|
119
|
+
// Brain Dump section (pending ideas)
|
|
120
|
+
lines.push('## Brain Dump', '')
|
|
121
|
+
|
|
122
|
+
if (pending.length === 0) {
|
|
123
|
+
lines.push('_No pending ideas. Use `/p:idea` to capture one._', '')
|
|
124
|
+
} else {
|
|
125
|
+
// Sort by addedAt descending (newest first)
|
|
126
|
+
const sorted = [...pending].sort(
|
|
127
|
+
(a, b) => new Date(b.addedAt).getTime() - new Date(a.addedAt).getTime()
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
for (const idea of sorted) {
|
|
131
|
+
const tagsStr = idea.tags.length > 0 ? ' ' + idea.tags.map(t => `#${t}`).join(' ') : ''
|
|
132
|
+
const dateStr = formatDate(idea.addedAt)
|
|
133
|
+
lines.push(`- ${idea.text} _(${dateStr})_${tagsStr}`)
|
|
134
|
+
}
|
|
135
|
+
lines.push('')
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Converted section
|
|
139
|
+
if (converted.length > 0) {
|
|
140
|
+
lines.push('## Converted', '')
|
|
141
|
+
const sorted = [...converted].sort(
|
|
142
|
+
(a, b) => new Date(b.addedAt).getTime() - new Date(a.addedAt).getTime()
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
for (const idea of sorted) {
|
|
146
|
+
const convStr = idea.convertedTo ? ` → ${idea.convertedTo}` : ''
|
|
147
|
+
const dateStr = formatDate(idea.addedAt)
|
|
148
|
+
lines.push(`- ✓ ${idea.text}${convStr} _(${dateStr})_`)
|
|
149
|
+
}
|
|
150
|
+
lines.push('')
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Archived section
|
|
154
|
+
if (archived.length > 0) {
|
|
155
|
+
lines.push('## Archived', '')
|
|
156
|
+
const sorted = [...archived].sort(
|
|
157
|
+
(a, b) => new Date(b.addedAt).getTime() - new Date(a.addedAt).getTime()
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
for (const idea of sorted) {
|
|
161
|
+
const dateStr = formatDate(idea.addedAt)
|
|
162
|
+
lines.push(`- ${idea.text} _(${dateStr})_`)
|
|
163
|
+
}
|
|
164
|
+
lines.push('')
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return lines.join('\n')
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Format date for display
|
|
172
|
+
*/
|
|
173
|
+
function formatDate(isoDate: string): string {
|
|
174
|
+
try {
|
|
175
|
+
const date = new Date(isoDate)
|
|
176
|
+
return date.toISOString().split('T')[0] // YYYY-MM-DD
|
|
177
|
+
} catch {
|
|
178
|
+
return isoDate
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Create empty ideas.md
|
|
184
|
+
*/
|
|
185
|
+
export function createEmptyIdeasMd(): string {
|
|
186
|
+
return serializeIdeas([])
|
|
187
|
+
}
|
|
@@ -18,3 +18,19 @@ export {
|
|
|
18
18
|
serializeQueue,
|
|
19
19
|
createEmptyQueueMd
|
|
20
20
|
} from './queue-serializer'
|
|
21
|
+
|
|
22
|
+
// Shipped (shipped.md)
|
|
23
|
+
export {
|
|
24
|
+
parseShipped,
|
|
25
|
+
serializeShipped,
|
|
26
|
+
createEmptyShippedMd
|
|
27
|
+
} from './shipped-serializer'
|
|
28
|
+
export type { ShippedFeature } from './shipped-serializer'
|
|
29
|
+
|
|
30
|
+
// Ideas (ideas.md)
|
|
31
|
+
export {
|
|
32
|
+
parseIdeas,
|
|
33
|
+
serializeIdeas,
|
|
34
|
+
createEmptyIdeasMd
|
|
35
|
+
} from './ideas-serializer'
|
|
36
|
+
export type { Idea, IdeaStatus, IdeaPriority } from './ideas-serializer'
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shipped Serializer
|
|
3
|
+
*
|
|
4
|
+
* Parses and serializes shipped.md for shipped features.
|
|
5
|
+
*
|
|
6
|
+
* MD Format (shipped.md):
|
|
7
|
+
* ```
|
|
8
|
+
* # SHIPPED 🚀
|
|
9
|
+
*
|
|
10
|
+
* ## Feature Name
|
|
11
|
+
*
|
|
12
|
+
* Shipped: 2025-12-10T10:00:00.000Z
|
|
13
|
+
* Version: 0.1.5
|
|
14
|
+
*
|
|
15
|
+
* ## Another Feature
|
|
16
|
+
*
|
|
17
|
+
* Shipped: 2025-12-09T14:30:00.000Z
|
|
18
|
+
* Version: 0.1.4
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export interface ShippedFeature {
|
|
23
|
+
id: string
|
|
24
|
+
name: string
|
|
25
|
+
shippedAt: string // ISO8601
|
|
26
|
+
version: string
|
|
27
|
+
description?: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Parse shipped.md content to ShippedFeature[]
|
|
32
|
+
*/
|
|
33
|
+
export function parseShipped(content: string): ShippedFeature[] {
|
|
34
|
+
if (!content || !content.trim()) {
|
|
35
|
+
return []
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const features: ShippedFeature[] = []
|
|
39
|
+
const sections = content.split(/\n## /).slice(1) // Skip header
|
|
40
|
+
|
|
41
|
+
for (const section of sections) {
|
|
42
|
+
const lines = section.trim().split('\n')
|
|
43
|
+
const name = lines[0]?.trim()
|
|
44
|
+
|
|
45
|
+
if (!name) continue
|
|
46
|
+
|
|
47
|
+
const shippedMatch = section.match(/Shipped:\s*(.+)/i)
|
|
48
|
+
const versionMatch = section.match(/Version:\s*(.+)/i)
|
|
49
|
+
|
|
50
|
+
const shippedAt = shippedMatch ? shippedMatch[1].trim() : new Date().toISOString()
|
|
51
|
+
const version = versionMatch ? versionMatch[1].trim() : '0.0.0'
|
|
52
|
+
|
|
53
|
+
// Extract description (lines between name and metadata)
|
|
54
|
+
const descLines: string[] = []
|
|
55
|
+
for (let i = 1; i < lines.length; i++) {
|
|
56
|
+
const line = lines[i].trim()
|
|
57
|
+
if (line.startsWith('Shipped:') || line.startsWith('Version:') || !line) break
|
|
58
|
+
descLines.push(line)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
features.push({
|
|
62
|
+
id: `ship_${Date.parse(shippedAt) || Date.now()}`,
|
|
63
|
+
name,
|
|
64
|
+
shippedAt,
|
|
65
|
+
version,
|
|
66
|
+
description: descLines.join(' ') || undefined
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return features
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Serialize ShippedFeature[] to shipped.md format
|
|
75
|
+
*/
|
|
76
|
+
export function serializeShipped(features: ShippedFeature[]): string {
|
|
77
|
+
const lines: string[] = ['# SHIPPED 🚀', '']
|
|
78
|
+
|
|
79
|
+
if (features.length === 0) {
|
|
80
|
+
lines.push('_No features shipped yet._', '')
|
|
81
|
+
lines.push('Use `/p:ship` to celebrate your first ship!')
|
|
82
|
+
return lines.join('\n')
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Sort by shippedAt descending (newest first)
|
|
86
|
+
const sorted = [...features].sort(
|
|
87
|
+
(a, b) => new Date(b.shippedAt).getTime() - new Date(a.shippedAt).getTime()
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
for (const feature of sorted) {
|
|
91
|
+
lines.push(`## ${feature.name}`, '')
|
|
92
|
+
if (feature.description) {
|
|
93
|
+
lines.push(feature.description, '')
|
|
94
|
+
}
|
|
95
|
+
lines.push(`Shipped: ${feature.shippedAt}`)
|
|
96
|
+
lines.push(`Version: ${feature.version}`)
|
|
97
|
+
lines.push('')
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return lines.join('\n')
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Create empty shipped.md
|
|
105
|
+
*/
|
|
106
|
+
export function createEmptyShippedMd(): string {
|
|
107
|
+
return serializeShipped([])
|
|
108
|
+
}
|
package/core/session/utils.ts
CHANGED
|
@@ -3,18 +3,12 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { Session, TimelineEvent } from './types'
|
|
6
|
+
import { generateUUID } from '../schemas'
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
|
-
* Generate unique session ID
|
|
9
|
+
* Generate unique session ID (re-export from schemas)
|
|
9
10
|
*/
|
|
10
|
-
export
|
|
11
|
-
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
|
|
12
|
-
let id = 'sess_'
|
|
13
|
-
for (let i = 0; i < 8; i++) {
|
|
14
|
-
id += chars.charAt(Math.floor(Math.random() * chars.length))
|
|
15
|
-
}
|
|
16
|
-
return id
|
|
17
|
-
}
|
|
11
|
+
export const generateId = generateUUID
|
|
18
12
|
|
|
19
13
|
/**
|
|
20
14
|
* Calculate total duration in seconds
|