prjct-cli 0.21.0 → 0.23.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 +30 -0
- package/core/integrations/notion/client.ts +94 -4
- package/core/integrations/notion/index.ts +4 -1
- package/core/integrations/notion/setup.ts +8 -3
- package/core/integrations/notion/sync.ts +517 -10
- package/core/integrations/notion/templates.ts +36 -24
- package/core/types/storage.ts +8 -0
- package/core/types/task.ts +4 -0
- package/package.json +1 -1
- package/templates/commands/bug.md +79 -3
- package/templates/commands/git.md +82 -6
- package/templates/commands/now.md +83 -7
- package/templates/commands/ship.md +265 -39
- package/templates/skills/notion-push.md +116 -0
- package/templates/{commands → skills}/notion-setup.md +26 -18
- package/templates/skills/notion-sync.md +290 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.23.0] - 2025-12-28
|
|
4
|
+
|
|
5
|
+
### Feature: Branch + PR Workflow
|
|
6
|
+
|
|
7
|
+
Integrated git branch management and PR-based shipping workflow.
|
|
8
|
+
|
|
9
|
+
**Branch Management:**
|
|
10
|
+
- `/p:now` auto-creates feature branches when on main/master
|
|
11
|
+
- `/p:bug` auto-creates bug branches with proper naming
|
|
12
|
+
- Handles uncommitted changes (stash/commit/abort prompts)
|
|
13
|
+
- Tracks branch info in state.json
|
|
14
|
+
|
|
15
|
+
**Protected Branch Validation:**
|
|
16
|
+
- `/p:git` blocks commits/pushes to main/master
|
|
17
|
+
- Validates current branch matches expected task branch
|
|
18
|
+
- Clear error messages with recovery instructions
|
|
19
|
+
|
|
20
|
+
**PR-Based Shipping (`/p:ship`):**
|
|
21
|
+
- Creates PR via `gh` CLI instead of direct push
|
|
22
|
+
- Generates PR description with quality metrics
|
|
23
|
+
- Waits for CI checks (up to 10 min)
|
|
24
|
+
- Records CI status (passed/failed/timeout)
|
|
25
|
+
- Supports `--draft` flag for WIP PRs
|
|
26
|
+
|
|
27
|
+
**Template Updates:**
|
|
28
|
+
- `templates/commands/bug.md` - Branch creation + stashing
|
|
29
|
+
- `templates/commands/git.md` - Protected branch checks
|
|
30
|
+
- `templates/commands/now.md` - Feature branch workflow
|
|
31
|
+
- `templates/commands/ship.md` - Full PR lifecycle
|
|
32
|
+
|
|
3
33
|
## [0.20.1] - 2025-12-26
|
|
4
34
|
|
|
5
35
|
### Refactor: Type Consolidation
|
|
@@ -179,6 +179,95 @@ export class NotionClient {
|
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Update page content (for dashboard)
|
|
184
|
+
*/
|
|
185
|
+
async updatePageContent(pageId: string, content: string): Promise<boolean> {
|
|
186
|
+
try {
|
|
187
|
+
// Convert markdown content to Notion blocks
|
|
188
|
+
const blocks = this.markdownToBlocks(content)
|
|
189
|
+
|
|
190
|
+
// Clear existing content and add new blocks
|
|
191
|
+
// First, get existing blocks to delete them
|
|
192
|
+
const existingBlocks = await this.apiRequest(
|
|
193
|
+
`/blocks/${pageId}/children`,
|
|
194
|
+
'GET'
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
// Delete existing blocks
|
|
198
|
+
for (const block of existingBlocks.results || []) {
|
|
199
|
+
await this.apiRequest(`/blocks/${block.id}`, 'DELETE')
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Add new blocks
|
|
203
|
+
await this.apiRequest(`/blocks/${pageId}/children`, 'PATCH', {
|
|
204
|
+
children: blocks,
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
return true
|
|
208
|
+
} catch (error) {
|
|
209
|
+
console.error('[notion] Failed to update page content:', (error as Error).message)
|
|
210
|
+
return false
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Convert simple markdown to Notion blocks
|
|
216
|
+
*/
|
|
217
|
+
private markdownToBlocks(content: string): unknown[] {
|
|
218
|
+
const blocks: unknown[] = []
|
|
219
|
+
const lines = content.split('\n')
|
|
220
|
+
|
|
221
|
+
for (const line of lines) {
|
|
222
|
+
if (line.startsWith('# ')) {
|
|
223
|
+
blocks.push({
|
|
224
|
+
type: 'heading_1',
|
|
225
|
+
heading_1: {
|
|
226
|
+
rich_text: [{ type: 'text', text: { content: line.slice(2) } }],
|
|
227
|
+
},
|
|
228
|
+
})
|
|
229
|
+
} else if (line.startsWith('## ')) {
|
|
230
|
+
blocks.push({
|
|
231
|
+
type: 'heading_2',
|
|
232
|
+
heading_2: {
|
|
233
|
+
rich_text: [{ type: 'text', text: { content: line.slice(3) } }],
|
|
234
|
+
},
|
|
235
|
+
})
|
|
236
|
+
} else if (line.startsWith('- ')) {
|
|
237
|
+
blocks.push({
|
|
238
|
+
type: 'bulleted_list_item',
|
|
239
|
+
bulleted_list_item: {
|
|
240
|
+
rich_text: [{ type: 'text', text: { content: line.slice(2) } }],
|
|
241
|
+
},
|
|
242
|
+
})
|
|
243
|
+
} else if (line.startsWith('|') && line.includes('|')) {
|
|
244
|
+
// Table row - skip header separator
|
|
245
|
+
if (!line.match(/^\|[-|]+\|$/)) {
|
|
246
|
+
const cells = line.split('|').filter(Boolean).map((c) => c.trim())
|
|
247
|
+
if (cells.length > 0) {
|
|
248
|
+
blocks.push({
|
|
249
|
+
type: 'paragraph',
|
|
250
|
+
paragraph: {
|
|
251
|
+
rich_text: [{ type: 'text', text: { content: cells.join(' | ') } }],
|
|
252
|
+
},
|
|
253
|
+
})
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
} else if (line.trim() === '---') {
|
|
257
|
+
blocks.push({ type: 'divider', divider: {} })
|
|
258
|
+
} else if (line.trim()) {
|
|
259
|
+
blocks.push({
|
|
260
|
+
type: 'paragraph',
|
|
261
|
+
paragraph: {
|
|
262
|
+
rich_text: [{ type: 'text', text: { content: line } }],
|
|
263
|
+
},
|
|
264
|
+
})
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return blocks
|
|
269
|
+
}
|
|
270
|
+
|
|
182
271
|
/**
|
|
183
272
|
* Query a database
|
|
184
273
|
*/
|
|
@@ -211,11 +300,12 @@ export class NotionClient {
|
|
|
211
300
|
}
|
|
212
301
|
|
|
213
302
|
/**
|
|
214
|
-
* Find page by
|
|
303
|
+
* Find page by name (for upsert)
|
|
304
|
+
* Note: Since each project has its own database, we don't need to filter by project
|
|
215
305
|
*/
|
|
216
306
|
async findPageByProjectAndName(
|
|
217
307
|
databaseId: string,
|
|
218
|
-
|
|
308
|
+
_projectId: string,
|
|
219
309
|
name: string
|
|
220
310
|
): Promise<string | null> {
|
|
221
311
|
try {
|
|
@@ -224,9 +314,9 @@ export class NotionClient {
|
|
|
224
314
|
'POST',
|
|
225
315
|
{
|
|
226
316
|
filter: {
|
|
227
|
-
|
|
228
|
-
{ property: 'Project', rich_text: { equals: projectId } },
|
|
317
|
+
or: [
|
|
229
318
|
{ property: 'Name', title: { equals: name } },
|
|
319
|
+
{ property: 'Idea', title: { equals: name } },
|
|
230
320
|
],
|
|
231
321
|
},
|
|
232
322
|
}
|
|
@@ -28,8 +28,11 @@ export {
|
|
|
28
28
|
syncShippedFeature,
|
|
29
29
|
syncIdea,
|
|
30
30
|
fullSync,
|
|
31
|
+
pullShippedFeatures,
|
|
32
|
+
pullIdeas,
|
|
33
|
+
bidirectionalSync,
|
|
31
34
|
} from './sync'
|
|
32
|
-
export type { SyncResult } from './sync'
|
|
35
|
+
export type { SyncResult, PullResult } from './sync'
|
|
33
36
|
|
|
34
37
|
// Setup
|
|
35
38
|
export {
|
|
@@ -94,16 +94,21 @@ export async function createDatabases(
|
|
|
94
94
|
const databases: NotionIntegrationConfig['databases'] = {}
|
|
95
95
|
|
|
96
96
|
try {
|
|
97
|
-
// Create each database
|
|
97
|
+
// Create each database with project-specific names
|
|
98
98
|
for (const [key, schema] of Object.entries(ALL_DATABASE_SCHEMAS)) {
|
|
99
|
-
|
|
99
|
+
// Override title with project name prefix
|
|
100
|
+
const projectSchema = {
|
|
101
|
+
...schema,
|
|
102
|
+
title: schema.title.replace('prjct:', `${projectName}:`),
|
|
103
|
+
}
|
|
104
|
+
const db = await notionClient.createDatabase(parentPageId, projectSchema)
|
|
100
105
|
if (db) {
|
|
101
106
|
databases[key as keyof typeof databases] = db.id
|
|
102
107
|
} else {
|
|
103
108
|
return {
|
|
104
109
|
success: false,
|
|
105
110
|
databases,
|
|
106
|
-
error: `Failed to create ${
|
|
111
|
+
error: `Failed to create ${projectSchema.title} database`,
|
|
107
112
|
}
|
|
108
113
|
}
|
|
109
114
|
}
|