@miketromba/issy-core 0.2.1 → 0.3.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/README.md CHANGED
@@ -18,6 +18,7 @@ await createIssue({
18
18
  title: "Add dark mode",
19
19
  description: "Theme toggle",
20
20
  priority: "medium",
21
+ scope: "medium",
21
22
  type: "improvement",
22
23
  });
23
24
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miketromba/issy-core",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Issue storage, search, and parsing for issy",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -20,7 +20,14 @@ export interface Suggestion {
20
20
  /**
21
21
  * Supported qualifier keys
22
22
  */
23
- const QUALIFIER_KEYS = ['is', 'priority', 'type', 'label', 'sort'] as const
23
+ const QUALIFIER_KEYS = [
24
+ 'is',
25
+ 'priority',
26
+ 'scope',
27
+ 'type',
28
+ 'label',
29
+ 'sort',
30
+ ] as const
24
31
 
25
32
  /**
26
33
  * Valid values for each qualifier
@@ -28,8 +35,9 @@ const QUALIFIER_KEYS = ['is', 'priority', 'type', 'label', 'sort'] as const
28
35
  const QUALIFIER_VALUES: Record<string, readonly string[]> = {
29
36
  is: ['open', 'closed'] as const,
30
37
  priority: ['high', 'medium', 'low'] as const,
38
+ scope: ['small', 'medium', 'large'] as const,
31
39
  type: ['bug', 'improvement'] as const,
32
- sort: ['priority', 'created', 'updated', 'id'] as const,
40
+ sort: ['priority', 'scope', 'created', 'updated', 'id'] as const,
33
41
  // label values are dynamic and provided via existingLabels parameter
34
42
  }
35
43
 
@@ -203,6 +211,7 @@ function getQualifierDescription(key: string): string {
203
211
  const descriptions: Record<string, string> = {
204
212
  is: 'Filter by status',
205
213
  priority: 'Filter by priority',
214
+ scope: 'Filter by scope',
206
215
  type: 'Filter by type',
207
216
  label: 'Filter by label',
208
217
  sort: 'Sort results',
@@ -220,6 +229,9 @@ function getValueDescription(key: string, value: string): string {
220
229
  if (key === 'priority') {
221
230
  return `Priority: ${value}`
222
231
  }
232
+ if (key === 'scope') {
233
+ return `Scope: ${value}`
234
+ }
223
235
  if (key === 'type') {
224
236
  return value === 'bug' ? 'Bug report' : 'Improvement'
225
237
  }
package/src/lib/issues.ts CHANGED
@@ -163,6 +163,9 @@ export function generateFrontmatter(data: IssueFrontmatter): string {
163
163
  lines.push(`title: ${data.title}`)
164
164
  lines.push(`description: ${data.description}`)
165
165
  lines.push(`priority: ${data.priority}`)
166
+ if (data.scope) {
167
+ lines.push(`scope: ${data.scope}`)
168
+ }
166
169
  lines.push(`type: ${data.type}`)
167
170
  if (data.labels) {
168
171
  lines.push(`labels: ${data.labels}`)
@@ -300,12 +303,17 @@ export async function createIssue(input: CreateIssueInput): Promise<Issue> {
300
303
  }
301
304
 
302
305
  const priority = input.priority || 'medium'
306
+ const scope = input.scope
303
307
  const type = input.type || 'improvement'
304
308
 
305
309
  if (!['high', 'medium', 'low'].includes(priority)) {
306
310
  throw new Error('Priority must be: high, medium, or low')
307
311
  }
308
312
 
313
+ if (scope && !['small', 'medium', 'large'].includes(scope)) {
314
+ throw new Error('Scope must be: small, medium, or large')
315
+ }
316
+
309
317
  if (!['bug', 'improvement'].includes(type)) {
310
318
  throw new Error('Type must be: bug or improvement')
311
319
  }
@@ -318,6 +326,7 @@ export async function createIssue(input: CreateIssueInput): Promise<Issue> {
318
326
  title: input.title,
319
327
  description: input.description || input.title,
320
328
  priority,
329
+ scope: scope || undefined,
321
330
  type,
322
331
  labels: input.labels || undefined,
323
332
  status: 'open',
@@ -361,6 +370,7 @@ export async function updateIssue(
361
370
  ...(input.title && { title: input.title }),
362
371
  ...(input.description && { description: input.description }),
363
372
  ...(input.priority && { priority: input.priority }),
373
+ ...(input.scope && { scope: input.scope }),
364
374
  ...(input.type && { type: input.type }),
365
375
  ...(input.labels !== undefined && {
366
376
  labels: input.labels || undefined,
@@ -19,6 +19,7 @@ export interface ParsedQuery {
19
19
  const SUPPORTED_QUALIFIERS = new Set([
20
20
  'is',
21
21
  'priority',
22
+ 'scope',
22
23
  'type',
23
24
  'label',
24
25
  'sort',
package/src/lib/search.ts CHANGED
@@ -50,6 +50,9 @@ export function filterIssues(issues: Issue[], filters: IssueFilters): Issue[] {
50
50
  if (filters.priority && issue.frontmatter.priority !== filters.priority) {
51
51
  return false
52
52
  }
53
+ if (filters.scope && issue.frontmatter.scope !== filters.scope) {
54
+ return false
55
+ }
53
56
  if (filters.type && issue.frontmatter.type !== filters.type) {
54
57
  return false
55
58
  }
@@ -121,7 +124,7 @@ export function filterAndSearchIssues(
121
124
  * Sort issues by the specified sort option
122
125
  *
123
126
  * @param issues - Array of issues to sort (modified in place)
124
- * @param sortBy - Sort option: "priority", "created", "updated", or "id"
127
+ * @param sortBy - Sort option: "priority", "scope", "created", "updated", or "id"
125
128
  */
126
129
  function sortIssues(issues: Issue[], sortBy: string): void {
127
130
  const sortOption = sortBy.toLowerCase()
@@ -139,6 +142,24 @@ function sortIssues(issues: Issue[], sortBy: string): void {
139
142
  if (priorityA !== priorityB) return priorityA - priorityB
140
143
  return b.id.localeCompare(a.id) // newest first within priority
141
144
  })
145
+ } else if (sortOption === 'scope') {
146
+ // Sort by scope (small → medium → large), then by ID (newest first)
147
+ // Issues without scope go to the end
148
+ const scopeOrder: Record<string, number> = {
149
+ small: 0,
150
+ medium: 1,
151
+ large: 2,
152
+ }
153
+ issues.sort((a, b) => {
154
+ const scopeA = a.frontmatter.scope
155
+ ? (scopeOrder[a.frontmatter.scope] ?? 99)
156
+ : 99
157
+ const scopeB = b.frontmatter.scope
158
+ ? (scopeOrder[b.frontmatter.scope] ?? 99)
159
+ : 99
160
+ if (scopeA !== scopeB) return scopeA - scopeB
161
+ return b.id.localeCompare(a.id) // newest first within scope
162
+ })
142
163
  } else if (sortOption === 'created') {
143
164
  // Sort by creation date (newest first)
144
165
  issues.sort((a, b) => {
@@ -262,6 +283,22 @@ export function filterByQuery(issues: Issue[], query: string): Issue[] {
262
283
  // Invalid values are ignored (issue passes filter)
263
284
  }
264
285
 
286
+ // scope: qualifier
287
+ if (parsed.qualifiers.scope) {
288
+ const scopeValue = parsed.qualifiers.scope.toLowerCase()
289
+ // Only filter if value is valid (small, medium, or large)
290
+ if (
291
+ scopeValue === 'small' ||
292
+ scopeValue === 'medium' ||
293
+ scopeValue === 'large'
294
+ ) {
295
+ if (issue.frontmatter.scope !== scopeValue) {
296
+ return false
297
+ }
298
+ }
299
+ // Invalid values are ignored (issue passes filter)
300
+ }
301
+
265
302
  // type: qualifier
266
303
  if (parsed.qualifiers.type) {
267
304
  const typeValue = parsed.qualifiers.type.toLowerCase()
package/src/lib/types.ts CHANGED
@@ -6,6 +6,7 @@ export interface IssueFrontmatter {
6
6
  title: string
7
7
  description: string
8
8
  priority: 'high' | 'medium' | 'low'
9
+ scope?: 'small' | 'medium' | 'large'
9
10
  type: 'bug' | 'improvement'
10
11
  labels?: string
11
12
  status: 'open' | 'closed'
@@ -23,6 +24,7 @@ export interface Issue {
23
24
  export interface IssueFilters {
24
25
  status?: string
25
26
  priority?: string
27
+ scope?: string
26
28
  type?: string
27
29
  search?: string
28
30
  }
@@ -31,6 +33,7 @@ export interface CreateIssueInput {
31
33
  title: string
32
34
  description?: string
33
35
  priority?: 'high' | 'medium' | 'low'
36
+ scope?: 'small' | 'medium' | 'large'
34
37
  type?: 'bug' | 'improvement'
35
38
  labels?: string
36
39
  }
@@ -39,6 +42,7 @@ export interface UpdateIssueInput {
39
42
  title?: string
40
43
  description?: string
41
44
  priority?: 'high' | 'medium' | 'low'
45
+ scope?: 'small' | 'medium' | 'large'
42
46
  type?: 'bug' | 'improvement'
43
47
  labels?: string
44
48
  status?: 'open' | 'closed'