prjct-cli 0.20.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -6
- package/CLAUDE.md +56 -15
- package/README.md +5 -6
- package/bin/prjct +59 -42
- package/bin/prjct.ts +60 -0
- package/core/__tests__/agentic/memory-system.test.ts +18 -3
- package/core/__tests__/agentic/plan-mode.test.ts +55 -26
- package/core/__tests__/agentic/prompt-builder.test.ts +6 -6
- package/core/__tests__/utils/project-commands.test.ts +72 -0
- package/core/agentic/agent-router.ts +3 -12
- package/core/agentic/command-executor.ts +372 -3
- package/core/agentic/context-builder.ts +7 -27
- package/core/agentic/ground-truth.ts +604 -5
- package/core/agentic/index.ts +180 -0
- package/core/agentic/loop-detector.ts +418 -4
- package/core/agentic/memory-system.ts +857 -3
- package/core/agentic/plan-mode.ts +491 -4
- package/core/agentic/prompt-builder.ts +44 -65
- package/core/agentic/services.ts +13 -5
- package/core/agentic/skill-loader.ts +112 -0
- package/core/agentic/smart-context.ts +37 -122
- package/core/agentic/template-loader.ts +79 -122
- package/core/agentic/tool-registry.ts +5 -11
- package/core/agents/index.ts +1 -1
- package/core/agents/performance.ts +4 -2
- package/core/bus/bus.ts +262 -0
- package/core/bus/index.ts +3 -313
- package/core/commands/analysis.ts +5 -5
- package/core/commands/analytics.ts +11 -11
- package/core/commands/base.ts +33 -209
- package/core/commands/cleanup.ts +148 -0
- package/core/commands/command-data.ts +346 -0
- package/core/commands/commands.ts +216 -0
- package/core/commands/design.ts +83 -0
- package/core/commands/index.ts +13 -207
- package/core/commands/maintenance.ts +52 -473
- package/core/commands/planning.ts +3 -3
- package/core/commands/register.ts +104 -0
- package/core/commands/registry.ts +441 -0
- package/core/commands/setup.ts +25 -9
- package/core/commands/shipping.ts +48 -11
- package/core/commands/snapshots.ts +299 -0
- package/core/commands/workflow.ts +2 -2
- package/core/constants/index.ts +254 -4
- package/core/domain/agent-loader.ts +5 -6
- package/core/domain/task-stack.ts +555 -4
- package/core/errors.ts +127 -1
- package/core/events/events.ts +87 -0
- package/core/events/index.ts +4 -138
- package/core/index.ts +15 -23
- package/core/infrastructure/agent-detector.ts +126 -201
- package/core/infrastructure/author-detector.ts +99 -171
- package/core/infrastructure/command-installer.ts +476 -4
- package/core/infrastructure/config-manager.ts +41 -37
- package/core/infrastructure/path-manager.ts +59 -9
- package/core/infrastructure/permission-manager.ts +286 -0
- package/core/integrations/notion/client.ts +323 -0
- package/core/integrations/notion/index.ts +43 -0
- package/core/integrations/notion/setup.ts +230 -0
- package/core/integrations/notion/sync.ts +311 -0
- package/core/integrations/notion/templates.ts +234 -0
- package/core/outcomes/analyzer.ts +7 -41
- package/core/outcomes/index.ts +1 -1
- package/core/outcomes/recorder.ts +1 -1
- package/core/plugin/builtin/notion.ts +178 -0
- package/core/{plugins → plugin/builtin}/webhook.ts +6 -22
- package/core/plugin/loader.ts +5 -5
- package/core/plugin/registry.ts +2 -2
- package/core/schemas/ideas.ts +85 -54
- package/core/schemas/index.ts +14 -33
- package/core/schemas/permissions.ts +177 -0
- package/core/schemas/project.ts +39 -12
- package/core/schemas/roadmap.ts +94 -59
- package/core/schemas/schemas.ts +39 -0
- package/core/schemas/shipped.ts +87 -60
- package/core/schemas/state.ts +110 -70
- package/core/server/index.ts +21 -0
- package/core/server/routes.ts +165 -0
- package/core/server/server.ts +136 -0
- package/core/server/sse.ts +135 -0
- package/core/services/agent-service.ts +170 -0
- package/core/services/breakdown-service.ts +126 -0
- package/core/services/index.ts +21 -0
- package/core/services/memory-service.ts +108 -0
- package/core/services/project-service.ts +146 -0
- package/core/services/skill-service.ts +253 -0
- package/core/session/compaction.ts +257 -0
- package/core/session/index.ts +20 -8
- package/core/{infrastructure/session-manager/migration.ts → session/log-migration.ts} +9 -9
- package/core/{infrastructure/session-manager/session-manager.ts → session/session-log-manager.ts} +27 -26
- package/core/session/{session-manager.ts → task-session-manager.ts} +7 -4
- package/core/session/utils.ts +1 -1
- package/core/storage/ideas-storage.ts +10 -26
- package/core/storage/index.ts +14 -162
- package/core/storage/queue-storage.ts +13 -11
- package/core/storage/shipped-storage.ts +4 -17
- package/core/storage/state-storage.ts +35 -43
- package/core/storage/storage-manager.ts +42 -52
- package/core/storage/storage.ts +160 -0
- package/core/sync/auth-config.ts +1 -8
- package/core/sync/index.ts +17 -10
- package/core/sync/oauth-handler.ts +1 -6
- package/core/sync/sync-client.ts +6 -34
- package/core/sync/sync-manager.ts +11 -40
- package/core/types/agentic.ts +577 -0
- package/core/types/agents.ts +145 -0
- package/core/types/bus.ts +82 -0
- package/core/types/commands.ts +366 -0
- package/core/types/config.ts +70 -0
- package/core/types/core.ts +96 -0
- package/core/types/domain.ts +71 -0
- package/core/types/events.ts +42 -0
- package/core/types/fs.ts +56 -0
- package/core/types/index.ts +396 -500
- package/core/types/infrastructure.ts +196 -0
- package/core/types/integrations.ts +57 -0
- package/core/{agentic/memory-system/types.ts → types/memory.ts} +33 -8
- package/core/{outcomes/types.ts → types/outcomes.ts} +53 -8
- package/core/types/plugin.ts +25 -0
- package/core/types/server.ts +54 -0
- package/core/types/services.ts +65 -0
- package/core/types/session.ts +135 -0
- package/core/types/storage.ts +148 -0
- package/core/types/sync.ts +121 -0
- package/core/types/task.ts +72 -0
- package/core/types/template.ts +24 -0
- package/core/types/utils.ts +90 -0
- package/core/utils/cache.ts +195 -0
- package/core/utils/collection-filters.ts +245 -0
- package/core/utils/date-helper.ts +1 -5
- package/core/utils/file-helper.ts +20 -10
- package/core/utils/jsonl-helper.ts +5 -8
- package/core/utils/markdown-builder.ts +277 -0
- package/core/utils/project-commands.ts +132 -0
- package/core/utils/runtime.ts +119 -0
- package/dist/bin/prjct.mjs +12568 -0
- package/package.json +13 -8
- package/scripts/build.js +106 -0
- package/scripts/postinstall.js +50 -8
- package/templates/agentic/subagent-generation.md +1 -1
- package/templates/commands/init.md +43 -0
- package/templates/commands/notion-setup.md +191 -0
- package/templates/commands/serve.md +118 -0
- package/templates/commands/ship.md +13 -2
- package/templates/commands/skill.md +110 -0
- package/templates/commands/sync.md +1 -1
- package/templates/commands/test.md +23 -4
- package/templates/mcp-config.json +28 -0
- package/templates/permissions/default.jsonc +60 -0
- package/templates/permissions/permissive.jsonc +49 -0
- package/templates/permissions/strict.jsonc +62 -0
- package/templates/skills/code-review.md +47 -0
- package/templates/skills/debug.md +61 -0
- package/templates/skills/refactor.md +47 -0
- package/templates/subagents/domain/devops.md +1 -1
- package/templates/subagents/domain/testing.md +6 -10
- package/templates/subagents/workflow/prjct-shipper.md +16 -7
- package/templates/tools/bash.txt +22 -0
- package/templates/tools/edit.txt +18 -0
- package/templates/tools/glob.txt +19 -0
- package/templates/tools/grep.txt +21 -0
- package/templates/tools/read.txt +14 -0
- package/templates/tools/task.txt +20 -0
- package/templates/tools/webfetch.txt +16 -0
- package/templates/tools/websearch.txt +18 -0
- package/templates/tools/write.txt +17 -0
- package/core/agentic/command-executor/command-executor.ts +0 -312
- package/core/agentic/command-executor/index.ts +0 -16
- package/core/agentic/command-executor/status-signal.ts +0 -38
- package/core/agentic/command-executor/types.ts +0 -79
- package/core/agentic/ground-truth/index.ts +0 -76
- package/core/agentic/ground-truth/types.ts +0 -33
- package/core/agentic/ground-truth/utils.ts +0 -48
- package/core/agentic/ground-truth/verifiers/analyze.ts +0 -54
- package/core/agentic/ground-truth/verifiers/done.ts +0 -75
- package/core/agentic/ground-truth/verifiers/feature.ts +0 -70
- package/core/agentic/ground-truth/verifiers/index.ts +0 -37
- package/core/agentic/ground-truth/verifiers/init.ts +0 -52
- package/core/agentic/ground-truth/verifiers/now.ts +0 -57
- package/core/agentic/ground-truth/verifiers/ship.ts +0 -85
- package/core/agentic/ground-truth/verifiers/spec.ts +0 -45
- package/core/agentic/ground-truth/verifiers/sync.ts +0 -47
- package/core/agentic/ground-truth/verifiers.ts +0 -6
- package/core/agentic/loop-detector/error-analysis.ts +0 -97
- package/core/agentic/loop-detector/hallucination.ts +0 -71
- package/core/agentic/loop-detector/index.ts +0 -41
- package/core/agentic/loop-detector/loop-detector.ts +0 -222
- package/core/agentic/loop-detector/types.ts +0 -66
- package/core/agentic/memory-system/history.ts +0 -53
- package/core/agentic/memory-system/index.ts +0 -192
- package/core/agentic/memory-system/patterns.ts +0 -156
- package/core/agentic/memory-system/semantic-memories.ts +0 -278
- package/core/agentic/memory-system/session.ts +0 -21
- package/core/agentic/plan-mode/approval.ts +0 -57
- package/core/agentic/plan-mode/constants.ts +0 -44
- package/core/agentic/plan-mode/index.ts +0 -28
- package/core/agentic/plan-mode/plan-mode.ts +0 -407
- package/core/agentic/plan-mode/types.ts +0 -193
- package/core/agents/types.ts +0 -126
- package/core/command-registry/categories.ts +0 -23
- package/core/command-registry/commands.ts +0 -15
- package/core/command-registry/core-commands.ts +0 -344
- package/core/command-registry/index.ts +0 -158
- package/core/command-registry/optional-commands.ts +0 -163
- package/core/command-registry/setup-commands.ts +0 -83
- package/core/command-registry/types.ts +0 -59
- package/core/command-registry.ts +0 -9
- package/core/commands/types.ts +0 -185
- package/core/commands.ts +0 -11
- package/core/constants/formats.ts +0 -187
- package/core/context-sync.ts +0 -18
- package/core/data/index.ts +0 -27
- package/core/data/md-base-manager.ts +0 -203
- package/core/data/md-ideas-manager.ts +0 -155
- package/core/data/md-queue-manager.ts +0 -180
- package/core/data/md-shipped-manager.ts +0 -90
- package/core/data/md-state-manager.ts +0 -137
- package/core/domain/task-stack/index.ts +0 -19
- package/core/domain/task-stack/parser.ts +0 -86
- package/core/domain/task-stack/storage.ts +0 -123
- package/core/domain/task-stack/task-stack.ts +0 -340
- package/core/domain/task-stack/types.ts +0 -51
- package/core/infrastructure/command-installer/command-installer.ts +0 -327
- package/core/infrastructure/command-installer/global-config.ts +0 -136
- package/core/infrastructure/command-installer/index.ts +0 -25
- package/core/infrastructure/command-installer/types.ts +0 -41
- package/core/infrastructure/session-manager/index.ts +0 -23
- package/core/infrastructure/session-manager/types.ts +0 -45
- package/core/infrastructure/session-manager.ts +0 -8
- package/core/serializers/ideas-serializer.ts +0 -187
- package/core/serializers/index.ts +0 -36
- package/core/serializers/queue-serializer.ts +0 -210
- package/core/serializers/shipped-serializer.ts +0 -108
- package/core/serializers/state-serializer.ts +0 -136
- package/core/session/types.ts +0 -29
- /package/core/infrastructure/{agents/claude-agent.ts → claude-agent.ts} +0 -0
|
@@ -1,187 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Serializers Index
|
|
3
|
-
*
|
|
4
|
-
* MD-First Architecture: These serializers convert between TypeScript schemas and MD format.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
// State (now.md)
|
|
8
|
-
export {
|
|
9
|
-
parseState,
|
|
10
|
-
serializeState,
|
|
11
|
-
createCurrentTaskMd,
|
|
12
|
-
createEmptyStateMd
|
|
13
|
-
} from './state-serializer'
|
|
14
|
-
|
|
15
|
-
// Queue (next.md)
|
|
16
|
-
export {
|
|
17
|
-
parseQueue,
|
|
18
|
-
serializeQueue,
|
|
19
|
-
createEmptyQueueMd
|
|
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'
|
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Queue Serializer
|
|
3
|
-
*
|
|
4
|
-
* Parses and serializes next.md for task queue.
|
|
5
|
-
*
|
|
6
|
-
* MD Format (next.md):
|
|
7
|
-
* ```
|
|
8
|
-
* # Priority Queue
|
|
9
|
-
*
|
|
10
|
-
* > Tasks ready to start
|
|
11
|
-
*
|
|
12
|
-
* ## Active Tasks
|
|
13
|
-
* 1. [ ] Task description @agent (from: Feature Name)
|
|
14
|
-
* 2. [x] Completed task ✅
|
|
15
|
-
*
|
|
16
|
-
* ## Previously Active
|
|
17
|
-
* - [ ] Paused task
|
|
18
|
-
*
|
|
19
|
-
* ## Backlog
|
|
20
|
-
* - [ ] 🐛 [HIGH] Bug description
|
|
21
|
-
* - [ ] Feature task
|
|
22
|
-
* ```
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
import type { QueueJson, QueueTask, Priority, TaskType, TaskSection } from '../schemas/state'
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Parse next.md content to QueueJson
|
|
29
|
-
*/
|
|
30
|
-
export function parseQueue(content: string): QueueJson {
|
|
31
|
-
if (!content || !content.trim()) {
|
|
32
|
-
return { tasks: [], lastUpdated: '' }
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const lines = content.split('\n')
|
|
36
|
-
const tasks: QueueTask[] = []
|
|
37
|
-
let currentSection: TaskSection = 'active'
|
|
38
|
-
let taskIndex = 0
|
|
39
|
-
|
|
40
|
-
for (const line of lines) {
|
|
41
|
-
// Detect section headers
|
|
42
|
-
if (line.match(/^##\s*Active/i)) {
|
|
43
|
-
currentSection = 'active'
|
|
44
|
-
continue
|
|
45
|
-
}
|
|
46
|
-
if (line.match(/^##\s*Previously/i)) {
|
|
47
|
-
currentSection = 'previously_active'
|
|
48
|
-
continue
|
|
49
|
-
}
|
|
50
|
-
if (line.match(/^##\s*Backlog/i)) {
|
|
51
|
-
currentSection = 'backlog'
|
|
52
|
-
continue
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Parse task lines: "1. [ ] Task" or "- [ ] Task" or "- [x] Task"
|
|
56
|
-
const taskMatch = line.match(/^(?:\d+\.|[-*])\s*\[([\sx])\]\s*(.+)$/i)
|
|
57
|
-
if (!taskMatch) continue
|
|
58
|
-
|
|
59
|
-
const isCompleted = taskMatch[1].toLowerCase() === 'x'
|
|
60
|
-
let taskText = taskMatch[2].trim()
|
|
61
|
-
|
|
62
|
-
// Extract agent: @fe, @be, @fe+be
|
|
63
|
-
const agentMatch = taskText.match(/@(\w+(?:\+\w+)?)/)
|
|
64
|
-
const agent = agentMatch ? agentMatch[1] : undefined
|
|
65
|
-
taskText = taskText.replace(/@\w+(?:\+\w+)?/, '').trim()
|
|
66
|
-
|
|
67
|
-
// Extract origin feature: (from: Feature Name)
|
|
68
|
-
const fromMatch = taskText.match(/\(from:\s*([^)]+)\)/)
|
|
69
|
-
const originFeature = fromMatch ? fromMatch[1].trim() : undefined
|
|
70
|
-
taskText = taskText.replace(/\(from:\s*[^)]+\)/, '').trim()
|
|
71
|
-
|
|
72
|
-
// Detect task type from emoji/prefix
|
|
73
|
-
let type: TaskType = 'feature'
|
|
74
|
-
let priority: Priority = 'medium'
|
|
75
|
-
|
|
76
|
-
if (taskText.includes('🐛') || taskText.toLowerCase().includes('bug')) {
|
|
77
|
-
type = 'bug'
|
|
78
|
-
taskText = taskText.replace('🐛', '').trim()
|
|
79
|
-
}
|
|
80
|
-
if (taskText.includes('🔧') || taskText.toLowerCase().includes('fix')) {
|
|
81
|
-
type = 'improvement'
|
|
82
|
-
}
|
|
83
|
-
if (taskText.includes('♻️') || taskText.toLowerCase().includes('refactor')) {
|
|
84
|
-
type = 'chore'
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Extract priority: [HIGH], [MEDIUM], [LOW], [CRITICAL]
|
|
88
|
-
const priorityMatch = taskText.match(/\[(HIGH|MEDIUM|LOW|CRITICAL)\]/i)
|
|
89
|
-
if (priorityMatch) {
|
|
90
|
-
priority = priorityMatch[1].toLowerCase() as Priority
|
|
91
|
-
taskText = taskText.replace(/\[(HIGH|MEDIUM|LOW|CRITICAL)\]/i, '').trim()
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Remove checkmarks and clean up
|
|
95
|
-
taskText = taskText.replace(/✅/g, '').trim()
|
|
96
|
-
|
|
97
|
-
// Skip empty descriptions
|
|
98
|
-
if (!taskText) continue
|
|
99
|
-
|
|
100
|
-
const task: QueueTask = {
|
|
101
|
-
id: `task_${Date.now()}_${taskIndex++}`,
|
|
102
|
-
description: taskText,
|
|
103
|
-
priority,
|
|
104
|
-
type,
|
|
105
|
-
completed: isCompleted,
|
|
106
|
-
createdAt: new Date().toISOString(),
|
|
107
|
-
section: currentSection
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (agent) task.agent = agent
|
|
111
|
-
if (originFeature) task.originFeature = originFeature
|
|
112
|
-
if (isCompleted) task.completedAt = new Date().toISOString()
|
|
113
|
-
|
|
114
|
-
tasks.push(task)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Extract updated date
|
|
118
|
-
const updatedMatch = content.match(/_Updated:\s*(\d{4}-\d{2}-\d{2})/)
|
|
119
|
-
const lastUpdated = updatedMatch ? updatedMatch[1] : new Date().toISOString().split('T')[0]
|
|
120
|
-
|
|
121
|
-
return { tasks, lastUpdated }
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Serialize QueueJson to next.md format
|
|
126
|
-
*/
|
|
127
|
-
export function serializeQueue(data: QueueJson): string {
|
|
128
|
-
const lines: string[] = [
|
|
129
|
-
'# Priority Queue',
|
|
130
|
-
'',
|
|
131
|
-
'> Tasks ready to start (max 100)',
|
|
132
|
-
'> Auto-updated by prjct',
|
|
133
|
-
''
|
|
134
|
-
]
|
|
135
|
-
|
|
136
|
-
// Group tasks by section
|
|
137
|
-
const active = data.tasks.filter(t => t.section === 'active')
|
|
138
|
-
const previouslyActive = data.tasks.filter(t => t.section === 'previously_active')
|
|
139
|
-
const backlog = data.tasks.filter(t => t.section === 'backlog')
|
|
140
|
-
|
|
141
|
-
// Active Tasks
|
|
142
|
-
if (active.length > 0) {
|
|
143
|
-
lines.push('## Active Tasks', '')
|
|
144
|
-
active.forEach((task, i) => {
|
|
145
|
-
lines.push(formatTask(task, i + 1, true))
|
|
146
|
-
})
|
|
147
|
-
lines.push('')
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Previously Active
|
|
151
|
-
if (previouslyActive.length > 0) {
|
|
152
|
-
lines.push('## Previously Active', '')
|
|
153
|
-
previouslyActive.forEach(task => {
|
|
154
|
-
lines.push(formatTask(task, 0, false))
|
|
155
|
-
})
|
|
156
|
-
lines.push('')
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Backlog
|
|
160
|
-
lines.push('---', '', '## Backlog', '')
|
|
161
|
-
if (backlog.length > 0) {
|
|
162
|
-
backlog.forEach(task => {
|
|
163
|
-
lines.push(formatTask(task, 0, false))
|
|
164
|
-
})
|
|
165
|
-
} else {
|
|
166
|
-
lines.push('_No backlog items_')
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
lines.push('')
|
|
170
|
-
lines.push('---', '')
|
|
171
|
-
lines.push(`_Updated: ${data.lastUpdated || new Date().toISOString().split('T')[0]}_`)
|
|
172
|
-
|
|
173
|
-
return lines.join('\n')
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Format a single task as markdown
|
|
178
|
-
*/
|
|
179
|
-
function formatTask(task: QueueTask, num: number, numbered: boolean): string {
|
|
180
|
-
const checkbox = task.completed ? '[x]' : '[ ]'
|
|
181
|
-
const prefix = numbered && num > 0 ? `${num}.` : '-'
|
|
182
|
-
|
|
183
|
-
let text = task.description
|
|
184
|
-
|
|
185
|
-
// Add emoji for type
|
|
186
|
-
if (task.type === 'bug') text = `🐛 ${text}`
|
|
187
|
-
|
|
188
|
-
// Add priority tag for high/critical
|
|
189
|
-
if (task.priority === 'high' || task.priority === 'critical') {
|
|
190
|
-
text = `[${task.priority.toUpperCase()}] ${text}`
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Add agent
|
|
194
|
-
if (task.agent) text = `${text} @${task.agent}`
|
|
195
|
-
|
|
196
|
-
// Add origin feature
|
|
197
|
-
if (task.originFeature) text = `${text} (from: ${task.originFeature})`
|
|
198
|
-
|
|
199
|
-
// Add checkmark for completed
|
|
200
|
-
if (task.completed) text = `${text} ✅`
|
|
201
|
-
|
|
202
|
-
return `${prefix} ${checkbox} ${text}`
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Quick helpers
|
|
207
|
-
*/
|
|
208
|
-
export function createEmptyQueueMd(): string {
|
|
209
|
-
return serializeQueue({ tasks: [], lastUpdated: new Date().toISOString().split('T')[0] })
|
|
210
|
-
}
|
|
@@ -1,108 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* State Serializer
|
|
3
|
-
*
|
|
4
|
-
* Parses and serializes now.md for current task state.
|
|
5
|
-
*
|
|
6
|
-
* MD Format (now.md):
|
|
7
|
-
* ```
|
|
8
|
-
* # NOW
|
|
9
|
-
*
|
|
10
|
-
* **Task description here**
|
|
11
|
-
*
|
|
12
|
-
* Started: 2025-12-10T10:00:00.000Z
|
|
13
|
-
* Session: sess_abc123
|
|
14
|
-
* Feature: feat_xyz789
|
|
15
|
-
* Agent: fe
|
|
16
|
-
* ```
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import type { StateJson, CurrentTask, PreviousTask } from '../schemas/state'
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Parse now.md content to StateJson
|
|
23
|
-
*/
|
|
24
|
-
export function parseState(content: string): StateJson {
|
|
25
|
-
if (!content || !content.trim()) {
|
|
26
|
-
return { currentTask: null, lastUpdated: '' }
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const lines = content.split('\n')
|
|
30
|
-
let currentTask: CurrentTask | null = null
|
|
31
|
-
let previousTask: PreviousTask | null = null
|
|
32
|
-
|
|
33
|
-
// Find task description (bold line after # NOW)
|
|
34
|
-
let description = ''
|
|
35
|
-
for (const line of lines) {
|
|
36
|
-
const boldMatch = line.match(/^\*\*(.+)\*\*$/)
|
|
37
|
-
if (boldMatch) {
|
|
38
|
-
description = boldMatch[1].trim()
|
|
39
|
-
break
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (!description || description.toLowerCase().includes('no active task')) {
|
|
44
|
-
return { currentTask: null, lastUpdated: '' }
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Extract metadata
|
|
48
|
-
const startedMatch = content.match(/Started:\s*(.+)/i)
|
|
49
|
-
const sessionMatch = content.match(/Session:\s*(.+)/i)
|
|
50
|
-
const featureMatch = content.match(/Feature:\s*(.+)/i)
|
|
51
|
-
const idMatch = content.match(/ID:\s*(.+)/i)
|
|
52
|
-
const agentMatch = content.match(/Agent:\s*(.+)/i)
|
|
53
|
-
const pausedMatch = content.match(/Paused:\s*(.+)/i)
|
|
54
|
-
|
|
55
|
-
const id = idMatch ? idMatch[1].trim() : `task_${Date.now()}`
|
|
56
|
-
const startedAt = startedMatch ? startedMatch[1].trim() : new Date().toISOString()
|
|
57
|
-
const sessionId = sessionMatch ? sessionMatch[1].trim() : `sess_${Date.now()}`
|
|
58
|
-
|
|
59
|
-
if (pausedMatch) {
|
|
60
|
-
// This is a paused task
|
|
61
|
-
previousTask = {
|
|
62
|
-
id,
|
|
63
|
-
description,
|
|
64
|
-
status: 'paused',
|
|
65
|
-
startedAt,
|
|
66
|
-
pausedAt: pausedMatch[1].trim()
|
|
67
|
-
}
|
|
68
|
-
} else {
|
|
69
|
-
// Active task
|
|
70
|
-
currentTask = {
|
|
71
|
-
id,
|
|
72
|
-
description,
|
|
73
|
-
startedAt,
|
|
74
|
-
sessionId
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (featureMatch) {
|
|
78
|
-
currentTask.featureId = featureMatch[1].trim()
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return {
|
|
83
|
-
currentTask,
|
|
84
|
-
previousTask,
|
|
85
|
-
lastUpdated: startedAt
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Serialize StateJson to now.md format
|
|
91
|
-
*/
|
|
92
|
-
export function serializeState(data: StateJson): string {
|
|
93
|
-
const lines: string[] = ['# NOW', '']
|
|
94
|
-
|
|
95
|
-
if (!data.currentTask && !data.previousTask) {
|
|
96
|
-
lines.push('_No active task_', '')
|
|
97
|
-
lines.push('Use `/p:now` to start working on something.')
|
|
98
|
-
return lines.join('\n')
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const task = data.currentTask || data.previousTask
|
|
102
|
-
|
|
103
|
-
if (task) {
|
|
104
|
-
lines.push(`**${task.description}**`, '')
|
|
105
|
-
|
|
106
|
-
if ('pausedAt' in task && task.pausedAt) {
|
|
107
|
-
lines.push(`Started: ${task.startedAt}`)
|
|
108
|
-
lines.push(`Paused: ${task.pausedAt}`)
|
|
109
|
-
} else if (data.currentTask) {
|
|
110
|
-
lines.push(`Started: ${data.currentTask.startedAt}`)
|
|
111
|
-
lines.push(`Session: ${data.currentTask.sessionId}`)
|
|
112
|
-
if (data.currentTask.featureId) {
|
|
113
|
-
lines.push(`Feature: ${data.currentTask.featureId}`)
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return lines.join('\n')
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Quick helpers for common operations
|
|
123
|
-
*/
|
|
124
|
-
export function createCurrentTaskMd(task: CurrentTask): string {
|
|
125
|
-
return serializeState({
|
|
126
|
-
currentTask: task,
|
|
127
|
-
lastUpdated: task.startedAt
|
|
128
|
-
})
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export function createEmptyStateMd(): string {
|
|
132
|
-
return serializeState({
|
|
133
|
-
currentTask: null,
|
|
134
|
-
lastUpdated: ''
|
|
135
|
-
})
|
|
136
|
-
}
|
package/core/session/types.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Session Types
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export interface SessionMetrics {
|
|
6
|
-
filesChanged: number
|
|
7
|
-
linesAdded: number
|
|
8
|
-
linesRemoved: number
|
|
9
|
-
commits: number
|
|
10
|
-
snapshots: string[]
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface TimelineEvent {
|
|
14
|
-
type: 'start' | 'pause' | 'resume' | 'complete'
|
|
15
|
-
at: string
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface Session {
|
|
19
|
-
id: string
|
|
20
|
-
projectId: string
|
|
21
|
-
task: string
|
|
22
|
-
status: 'active' | 'paused' | 'completed'
|
|
23
|
-
startedAt: string
|
|
24
|
-
pausedAt: string | null
|
|
25
|
-
completedAt: string | null
|
|
26
|
-
duration: number
|
|
27
|
-
metrics: SessionMetrics
|
|
28
|
-
timeline: TimelineEvent[]
|
|
29
|
-
}
|
|
File without changes
|