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 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 project ID and name (for upsert)
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
- projectId: string,
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
- and: [
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
- const db = await notionClient.createDatabase(parentPageId, schema)
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 ${schema.title} database`,
111
+ error: `Failed to create ${projectSchema.title} database`,
107
112
  }
108
113
  }
109
114
  }