issy 0.8.0 → 0.9.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
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="https://raw.githubusercontent.com/miketromba/issy/main/assets/issy-logo.png" alt="issy" width="600" />
2
+ <img src="https://raw.githubusercontent.com/miketromba/issy/main/assets/issy-banner.png" alt="issy" width="600" />
3
3
  </p>
4
4
 
5
5
  <p align="center">
@@ -10,7 +10,8 @@
10
10
 
11
11
  <p align="center">
12
12
  <strong>AI-native issue tracking.</strong><br>
13
- Tell your coding assistant what to track. It handles the rest.
13
+ Tell your coding assistant what to track. It handles the rest.<br>
14
+ <a href="https://issy.sh">issy.sh</a>
14
15
  </p>
15
16
 
16
17
  ---
@@ -77,6 +78,7 @@ Once installed globally, you can run commands from your terminal:
77
78
  ```bash
78
79
  issy # Start the web UI
79
80
  issy list # List open issues (roadmap order)
81
+ issy list --unblocked # List open issues with no open blockers
80
82
  issy next # Show next issue to work on
81
83
  issy create --title "Bug" # Create an issue
82
84
  ```
@@ -123,12 +125,15 @@ Opens a local read-only UI at `http://localhost:1554` for browsing issues.
123
125
  issy init # Create .issy/issues/ directory
124
126
  issy init --seed # Create with a welcome issue
125
127
  issy list # List open issues (roadmap order)
128
+ issy list --unblocked # List open issues with no open blockers
126
129
  issy next # Show next issue to work on
127
130
  issy search "auth" # Fuzzy search
128
131
  issy read 0001 # View issue
129
132
  issy create --title "Bug" --after 0002 # Create issue after #0002
133
+ issy create --title "Feature" --depends-on 0012,0035 --last # Blocked by issues
130
134
  issy create --title "Bug" --body "Details here" --last # Create with body content
131
135
  issy update 0001 --before 0003 # Reposition in roadmap
136
+ issy update 0001 --depends-on 0012,0035 # Replace blockers
132
137
  issy update 0001 --body "New details" # Replace body content
133
138
  issy close 0001 # Close issue
134
139
  issy reopen 0001 --after 0004 # Reopen and place in roadmap
@@ -159,6 +164,14 @@ issy list --sort priority # Sort by priority instead
159
164
  issy list --sort created # Sort by creation date
160
165
  ```
161
166
 
167
+ The `Blk` column in `issy list` shows the count of currently open blockers. `-` means the issue is unblocked. Use `issy list --unblocked` to show only open issues that have no open blockers. Dependency IDs that do not match an existing issue are ignored.
168
+
169
+ Issues can declare blockers with `depends_on`:
170
+
171
+ ```yaml
172
+ depends_on: 0012, 0035
173
+ ```
174
+
162
175
  ### Hooks
163
176
 
164
177
  issy supports optional hook files in `.issy/` that inject context into stdout after successful operations. The file contents are printed directly, making them visible to AI agents in their command output.
@@ -210,6 +223,7 @@ scope: medium
210
223
  type: bug
211
224
  status: open
212
225
  order: a0
226
+ depends_on: 0012, 0035
213
227
  created: 2025-01-15T10:30:00
214
228
  ---
215
229
 
@@ -227,6 +241,7 @@ session isn't established, causing a redirect loop.
227
241
  | `status` | `open`, `closed` |
228
242
  | `labels` | comma-separated (optional) |
229
243
  | `order` | fractional index key (managed by issy) |
244
+ | `depends_on` | comma-separated blocking issue IDs (optional) |
230
245
 
231
246
  ## Configuration
232
247
 
package/dist/cli.js CHANGED
@@ -355,6 +355,9 @@ function generateFrontmatter(data) {
355
355
  if (data.order) {
356
356
  lines.push(`order: ${data.order}`);
357
357
  }
358
+ if (data.depends_on) {
359
+ lines.push(`depends_on: ${data.depends_on}`);
360
+ }
358
361
  lines.push(`created: ${data.created}`);
359
362
  if (data.updated) {
360
363
  lines.push(`updated: ${data.updated}`);
@@ -363,6 +366,44 @@ function generateFrontmatter(data) {
363
366
  return lines.join(`
364
367
  `);
365
368
  }
369
+ function parseDependencyIds(dependsOn) {
370
+ if (!dependsOn)
371
+ return [];
372
+ const seen = new Set;
373
+ const ids = [];
374
+ for (const token of dependsOn.split(/[,\s]+/)) {
375
+ const value = token.trim().replace(/^['"]+|['"]+$/g, "").replace(/^#/, "");
376
+ if (!/^\d+$/.test(value))
377
+ continue;
378
+ const id = value.padStart(4, "0");
379
+ if (seen.has(id))
380
+ continue;
381
+ seen.add(id);
382
+ ids.push(id);
383
+ }
384
+ return ids;
385
+ }
386
+ function filterExistingDependencyIds(dependsOn, issues, excludeId) {
387
+ const existingIds = new Set(issues.map((issue) => issue.id));
388
+ const excludedId = excludeId?.padStart(4, "0");
389
+ return parseDependencyIds(dependsOn).filter((id) => existingIds.has(id) && id !== excludedId);
390
+ }
391
+ function formatExistingDependencyIds(dependsOn, issues, excludeId) {
392
+ return filterExistingDependencyIds(dependsOn, issues, excludeId).join(", ");
393
+ }
394
+ function getDependencyIssues(issue, issues) {
395
+ const dependencyIds = filterExistingDependencyIds(issue.frontmatter.depends_on, issues, issue.id);
396
+ if (dependencyIds.length === 0)
397
+ return [];
398
+ const issueById = new Map(issues.map((i) => [i.id, i]));
399
+ return dependencyIds.map((id) => issueById.get(id)).filter((dependency) => Boolean(dependency));
400
+ }
401
+ function getBlockingIssues(issue, issues) {
402
+ return getDependencyIssues(issue, issues).filter((dependency) => dependency.frontmatter.status === "open");
403
+ }
404
+ function isIssueUnblocked(issue, issues) {
405
+ return issue.frontmatter.status === "open" && getBlockingIssues(issue, issues).length === 0;
406
+ }
366
407
  function getIssueIdFromFilename(filename) {
367
408
  const match = filename.match(/^(\d+)-/);
368
409
  return match ? match[1] : filename.replace(".md", "");
@@ -496,6 +537,8 @@ async function createIssue(input) {
496
537
  const issueNumber = await getNextIssueNumber();
497
538
  const slug = createSlug(input.title);
498
539
  const filename = `${issueNumber}-${slug}.md`;
540
+ const existingIssues = await getAllIssues();
541
+ const dependsOn = formatExistingDependencyIds(input.depends_on, existingIssues, issueNumber);
499
542
  const frontmatter = {
500
543
  title: input.title,
501
544
  priority,
@@ -504,6 +547,7 @@ async function createIssue(input) {
504
547
  labels: input.labels || undefined,
505
548
  status: "open",
506
549
  order: input.order || undefined,
550
+ depends_on: dependsOn || undefined,
507
551
  created: formatDate()
508
552
  };
509
553
  const body = input.body ?? `
@@ -529,6 +573,8 @@ async function updateIssue(id, input) {
529
573
  if (!issue) {
530
574
  throw new Error(`Issue not found: ${id}`);
531
575
  }
576
+ const allIssues = await getAllIssues();
577
+ const dependsOn = input.depends_on !== undefined ? formatExistingDependencyIds(input.depends_on, allIssues, issue.id) : undefined;
532
578
  const updatedFrontmatter = {
533
579
  ...issue.frontmatter,
534
580
  ...input.title && { title: input.title },
@@ -540,6 +586,9 @@ async function updateIssue(id, input) {
540
586
  },
541
587
  ...input.status && { status: input.status },
542
588
  ...input.order && { order: input.order },
589
+ ...input.depends_on !== undefined && {
590
+ depends_on: dependsOn || undefined
591
+ },
543
592
  updated: formatDate()
544
593
  };
545
594
  const updatedContent = input.body !== undefined ? `
@@ -2039,6 +2088,14 @@ function filterByQuery(issues, query) {
2039
2088
  if (issue.frontmatter.status !== statusValue) {
2040
2089
  return false;
2041
2090
  }
2091
+ } else if (statusValue === "unblocked") {
2092
+ if (!isIssueUnblocked(issue, issues)) {
2093
+ return false;
2094
+ }
2095
+ } else if (statusValue === "blocked") {
2096
+ if (issue.frontmatter.status !== "open" || isIssueUnblocked(issue, issues)) {
2097
+ return false;
2098
+ }
2042
2099
  }
2043
2100
  }
2044
2101
  if (parsed.qualifiers.priority) {
@@ -2126,7 +2183,23 @@ function typeSymbol(type) {
2126
2183
  }
2127
2184
  function formatIssueRow(issue) {
2128
2185
  const status = issue.frontmatter.status === "open" ? "OPEN " : "CLOSED";
2129
- return ` ${issue.id} ${prioritySymbol(issue.frontmatter.priority)} ${typeSymbol(issue.frontmatter.type)} ${status} ${issue.frontmatter.title}`;
2186
+ const blocked = issue.blockers > 0 ? String(issue.blockers) : "-";
2187
+ return ` ${issue.id} ${prioritySymbol(issue.frontmatter.priority)} ${typeSymbol(issue.frontmatter.type)} ${status} ${blocked.padEnd(3)} ${issue.frontmatter.title}`;
2188
+ }
2189
+ function printDependsOn(issue, issues) {
2190
+ const dependencyIssues = getDependencyIssues(issue, issues);
2191
+ if (dependencyIssues.length === 0)
2192
+ return;
2193
+ console.log(` Depends on: ${dependencyIssues.map((i) => `#${i.id}`).join(", ")}`);
2194
+ }
2195
+ function getDependsOnArg(values) {
2196
+ const value = values["depends-on"];
2197
+ return typeof value === "string" ? value : undefined;
2198
+ }
2199
+ async function resolveDependsOn(value, excludeId) {
2200
+ if (value === undefined)
2201
+ return;
2202
+ return formatExistingDependencyIds(value, await getAllIssues(), excludeId);
2130
2203
  }
2131
2204
  async function resolvePosition(opts) {
2132
2205
  const openIssues = await getOpenIssuesByOrder();
@@ -2158,7 +2231,7 @@ function hasHelpFlag(commandArgs) {
2158
2231
  async function listIssues(options) {
2159
2232
  const allIssues = await getAllIssues();
2160
2233
  const queryParts = [];
2161
- if (!options.all)
2234
+ if (!options.all && !options.unblocked)
2162
2235
  queryParts.push("is:open");
2163
2236
  if (options.priority)
2164
2237
  queryParts.push(`priority:${options.priority}`);
@@ -2170,17 +2243,23 @@ async function listIssues(options) {
2170
2243
  queryParts.push(`sort:${options.sort}`);
2171
2244
  if (options.search)
2172
2245
  queryParts.push(options.search);
2173
- const query = queryParts.join(" ") || "is:open";
2174
- const issues = filterByQuery(allIssues, query);
2246
+ const query = queryParts.join(" ");
2247
+ let issues = query ? filterByQuery(allIssues, query) : allIssues;
2248
+ if (options.unblocked) {
2249
+ issues = issues.filter((issue) => isIssueUnblocked(issue, allIssues));
2250
+ }
2175
2251
  if (issues.length === 0) {
2176
2252
  console.log("No issues found.");
2177
2253
  return;
2178
2254
  }
2179
2255
  console.log(`
2180
- ID Pri Type Status Title`);
2256
+ ID Pri Type Status Blk Title`);
2181
2257
  console.log(` ${"-".repeat(100)}`);
2182
2258
  for (const issue of issues) {
2183
- console.log(formatIssueRow(issue));
2259
+ console.log(formatIssueRow({
2260
+ ...issue,
2261
+ blockers: getBlockingIssues(issue, allIssues).length
2262
+ }));
2184
2263
  }
2185
2264
  console.log(`
2186
2265
  Total: ${issues.length} issue(s)
@@ -2188,6 +2267,7 @@ async function listIssues(options) {
2188
2267
  }
2189
2268
  async function readIssue(id) {
2190
2269
  const issue = await getIssue(id);
2270
+ const allIssues = await getAllIssues();
2191
2271
  if (!issue) {
2192
2272
  console.error(`Issue not found: ${id}`);
2193
2273
  process.exit(1);
@@ -2206,6 +2286,7 @@ ${"=".repeat(70)}`);
2206
2286
  if (issue.frontmatter.labels) {
2207
2287
  console.log(` Labels: ${issue.frontmatter.labels}`);
2208
2288
  }
2289
+ printDependsOn(issue, allIssues);
2209
2290
  if (issue.frontmatter.order) {
2210
2291
  console.log(` Order: ${issue.frontmatter.order}`);
2211
2292
  }
@@ -2228,10 +2309,13 @@ async function searchIssuesCommand(query, options) {
2228
2309
  console.log(`
2229
2310
  Search results for "${query}":`);
2230
2311
  console.log(`
2231
- ID Pri Type Status Title`);
2312
+ ID Pri Type Status Blk Title`);
2232
2313
  console.log(` ${"-".repeat(100)}`);
2233
2314
  for (const issue of issues) {
2234
- console.log(formatIssueRow(issue));
2315
+ console.log(formatIssueRow({
2316
+ ...issue,
2317
+ blockers: getBlockingIssues(issue, allIssues).length
2318
+ }));
2235
2319
  }
2236
2320
  console.log(`
2237
2321
  Found: ${issues.length} issue(s)
@@ -2257,6 +2341,7 @@ async function createIssueCommand(options) {
2257
2341
  scope: options.scope,
2258
2342
  type: options.type,
2259
2343
  labels: options.labels,
2344
+ depends_on: await resolveDependsOn(options.dependsOn) || undefined,
2260
2345
  order
2261
2346
  };
2262
2347
  const issue = await createIssue(input);
@@ -2293,6 +2378,9 @@ async function updateIssueCommand(id, options) {
2293
2378
  scope: options.scope,
2294
2379
  type: options.type,
2295
2380
  labels: options.labels,
2381
+ ...options.dependsOn !== undefined && {
2382
+ depends_on: await resolveDependsOn(options.dependsOn, id) || ""
2383
+ },
2296
2384
  order
2297
2385
  });
2298
2386
  console.log(`Updated issue: ${issue.filename}`);
@@ -2341,6 +2429,7 @@ async function reopenIssueCommand(id, options) {
2341
2429
  }
2342
2430
  async function nextIssueCommand() {
2343
2431
  const issue = await getNextIssue();
2432
+ const allIssues = await getAllIssues();
2344
2433
  if (!issue) {
2345
2434
  console.log("No open issues.");
2346
2435
  return;
@@ -2359,6 +2448,7 @@ ${"=".repeat(70)}`);
2359
2448
  if (issue.frontmatter.labels) {
2360
2449
  console.log(` Labels: ${issue.frontmatter.labels}`);
2361
2450
  }
2451
+ printDependsOn(issue, allIssues);
2362
2452
  if (issue.frontmatter.order) {
2363
2453
  console.log(` Order: ${issue.frontmatter.order}`);
2364
2454
  }
@@ -2386,6 +2476,7 @@ Options:
2386
2476
  Commands:
2387
2477
  list List all open issues (roadmap order)
2388
2478
  --all, -a Include closed issues
2479
+ --unblocked Only show open issues with no open blockers
2389
2480
  --priority, -p <p> Filter by priority (high, medium, low)
2390
2481
  --scope <s> Filter by scope (small, medium, large)
2391
2482
  --type, -t <t> Filter by type (bug, improvement)
@@ -2406,6 +2497,7 @@ Commands:
2406
2497
  --scope <s> Scope (small, medium, large)
2407
2498
  --type <t> Type (bug, improvement)
2408
2499
  --labels, -l <l> Comma-separated labels
2500
+ --depends-on <ids> Comma-separated blocking issue IDs
2409
2501
  --before <id> Insert before this issue in roadmap
2410
2502
  --after <id> Insert after this issue in roadmap
2411
2503
  --first Insert at the beginning of the roadmap
@@ -2418,6 +2510,7 @@ Commands:
2418
2510
  --scope <s> New scope
2419
2511
  --type <t> New type
2420
2512
  --labels, -l <l> New labels
2513
+ --depends-on <ids> Replace blocking issue IDs (empty clears)
2421
2514
  --before <id> Move before this issue in roadmap
2422
2515
  --after <id> Move after this issue in roadmap
2423
2516
  --first Move to the beginning of the roadmap
@@ -2435,6 +2528,7 @@ Commands:
2435
2528
 
2436
2529
  Examples:
2437
2530
  issy list
2531
+ issy list --unblocked
2438
2532
  issy list --priority high --type bug
2439
2533
  issy next
2440
2534
  issy read 0001
@@ -2456,6 +2550,7 @@ List all open issues in roadmap order.
2456
2550
 
2457
2551
  Options:
2458
2552
  --all, -a Include closed issues
2553
+ --unblocked Only show open issues with no open blockers
2459
2554
  --priority, -p <p> Filter by priority (high, medium, low)
2460
2555
  --scope <s> Filter by scope (small, medium, large)
2461
2556
  --type, -t <t> Filter by type (bug, improvement)
@@ -2468,6 +2563,7 @@ Options:
2468
2563
  args: args.slice(1),
2469
2564
  options: {
2470
2565
  all: { type: "boolean", short: "a" },
2566
+ unblocked: { type: "boolean" },
2471
2567
  priority: { type: "string", short: "p" },
2472
2568
  scope: { type: "string" },
2473
2569
  type: { type: "string", short: "t" },
@@ -2545,6 +2641,7 @@ Options:
2545
2641
  --scope <s> Scope: small, medium, large
2546
2642
  --type <t> Type: bug, improvement (default: improvement)
2547
2643
  --labels, -l <l> Comma-separated labels
2644
+ --depends-on <ids> Comma-separated blocking issue IDs
2548
2645
  --before <id> Insert before this issue in roadmap
2549
2646
  --after <id> Insert after this issue in roadmap
2550
2647
  --first Insert at the beginning of the roadmap
@@ -2552,6 +2649,7 @@ Options:
2552
2649
 
2553
2650
  Examples:
2554
2651
  issy create --title "Fix login bug" --type bug --priority high --after 0002
2652
+ issy create --title "Add export" --depends-on 0001,0002 --last
2555
2653
  issy create --title "Add dark mode" --last
2556
2654
  `);
2557
2655
  return;
@@ -2565,6 +2663,7 @@ Examples:
2565
2663
  scope: { type: "string" },
2566
2664
  type: { type: "string" },
2567
2665
  labels: { type: "string", short: "l" },
2666
+ "depends-on": { type: "string" },
2568
2667
  before: { type: "string" },
2569
2668
  after: { type: "string" },
2570
2669
  first: { type: "boolean" },
@@ -2572,7 +2671,10 @@ Examples:
2572
2671
  },
2573
2672
  allowPositionals: true
2574
2673
  });
2575
- await createIssueCommand(values);
2674
+ await createIssueCommand({
2675
+ ...values,
2676
+ dependsOn: getDependsOnArg(values)
2677
+ });
2576
2678
  break;
2577
2679
  }
2578
2680
  case "update": {
@@ -2588,6 +2690,7 @@ Options:
2588
2690
  --scope <s> New scope: small, medium, large
2589
2691
  --type <t> New type: bug, improvement
2590
2692
  --labels, -l <l> New labels (comma-separated)
2693
+ --depends-on <ids> Replace blocking issue IDs (empty clears)
2591
2694
  --before <id> Move before this issue in roadmap
2592
2695
  --after <id> Move after this issue in roadmap
2593
2696
  --first Move to the beginning of the roadmap
@@ -2595,6 +2698,7 @@ Options:
2595
2698
 
2596
2699
  Examples:
2597
2700
  issy update 0001 --priority low --after 0003
2701
+ issy update 0002 --depends-on 0001,0003
2598
2702
  issy update 0002 --title "Renamed issue" --first
2599
2703
  `);
2600
2704
  return;
@@ -2613,6 +2717,7 @@ Examples:
2613
2717
  scope: { type: "string" },
2614
2718
  type: { type: "string" },
2615
2719
  labels: { type: "string", short: "l" },
2720
+ "depends-on": { type: "string" },
2616
2721
  before: { type: "string" },
2617
2722
  after: { type: "string" },
2618
2723
  first: { type: "boolean" },
@@ -2620,7 +2725,10 @@ Examples:
2620
2725
  },
2621
2726
  allowPositionals: true
2622
2727
  });
2623
- await updateIssueCommand(id, values);
2728
+ await updateIssueCommand(id, {
2729
+ ...values,
2730
+ dependsOn: getDependsOnArg(values)
2731
+ });
2624
2732
  break;
2625
2733
  }
2626
2734
  case "close": {
package/dist/main.js CHANGED
@@ -366,6 +366,9 @@ function generateFrontmatter(data) {
366
366
  if (data.order) {
367
367
  lines.push(`order: ${data.order}`);
368
368
  }
369
+ if (data.depends_on) {
370
+ lines.push(`depends_on: ${data.depends_on}`);
371
+ }
369
372
  lines.push(`created: ${data.created}`);
370
373
  if (data.updated) {
371
374
  lines.push(`updated: ${data.updated}`);
@@ -374,6 +377,44 @@ function generateFrontmatter(data) {
374
377
  return lines.join(`
375
378
  `);
376
379
  }
380
+ function parseDependencyIds(dependsOn) {
381
+ if (!dependsOn)
382
+ return [];
383
+ const seen = new Set;
384
+ const ids = [];
385
+ for (const token of dependsOn.split(/[,\s]+/)) {
386
+ const value = token.trim().replace(/^['"]+|['"]+$/g, "").replace(/^#/, "");
387
+ if (!/^\d+$/.test(value))
388
+ continue;
389
+ const id = value.padStart(4, "0");
390
+ if (seen.has(id))
391
+ continue;
392
+ seen.add(id);
393
+ ids.push(id);
394
+ }
395
+ return ids;
396
+ }
397
+ function filterExistingDependencyIds(dependsOn, issues, excludeId) {
398
+ const existingIds = new Set(issues.map((issue) => issue.id));
399
+ const excludedId = excludeId?.padStart(4, "0");
400
+ return parseDependencyIds(dependsOn).filter((id) => existingIds.has(id) && id !== excludedId);
401
+ }
402
+ function formatExistingDependencyIds(dependsOn, issues, excludeId) {
403
+ return filterExistingDependencyIds(dependsOn, issues, excludeId).join(", ");
404
+ }
405
+ function getDependencyIssues(issue, issues) {
406
+ const dependencyIds = filterExistingDependencyIds(issue.frontmatter.depends_on, issues, issue.id);
407
+ if (dependencyIds.length === 0)
408
+ return [];
409
+ const issueById = new Map(issues.map((i) => [i.id, i]));
410
+ return dependencyIds.map((id) => issueById.get(id)).filter((dependency) => Boolean(dependency));
411
+ }
412
+ function getBlockingIssues(issue, issues) {
413
+ return getDependencyIssues(issue, issues).filter((dependency) => dependency.frontmatter.status === "open");
414
+ }
415
+ function isIssueUnblocked(issue, issues) {
416
+ return issue.frontmatter.status === "open" && getBlockingIssues(issue, issues).length === 0;
417
+ }
377
418
  function getIssueIdFromFilename(filename) {
378
419
  const match = filename.match(/^(\d+)-/);
379
420
  return match ? match[1] : filename.replace(".md", "");
@@ -507,6 +548,8 @@ async function createIssue(input) {
507
548
  const issueNumber = await getNextIssueNumber();
508
549
  const slug = createSlug(input.title);
509
550
  const filename = `${issueNumber}-${slug}.md`;
551
+ const existingIssues = await getAllIssues();
552
+ const dependsOn = formatExistingDependencyIds(input.depends_on, existingIssues, issueNumber);
510
553
  const frontmatter = {
511
554
  title: input.title,
512
555
  priority,
@@ -515,6 +558,7 @@ async function createIssue(input) {
515
558
  labels: input.labels || undefined,
516
559
  status: "open",
517
560
  order: input.order || undefined,
561
+ depends_on: dependsOn || undefined,
518
562
  created: formatDate()
519
563
  };
520
564
  const body = input.body ?? `
@@ -540,6 +584,8 @@ async function updateIssue(id, input) {
540
584
  if (!issue) {
541
585
  throw new Error(`Issue not found: ${id}`);
542
586
  }
587
+ const allIssues = await getAllIssues();
588
+ const dependsOn = input.depends_on !== undefined ? formatExistingDependencyIds(input.depends_on, allIssues, issue.id) : undefined;
543
589
  const updatedFrontmatter = {
544
590
  ...issue.frontmatter,
545
591
  ...input.title && { title: input.title },
@@ -551,6 +597,9 @@ async function updateIssue(id, input) {
551
597
  },
552
598
  ...input.status && { status: input.status },
553
599
  ...input.order && { order: input.order },
600
+ ...input.depends_on !== undefined && {
601
+ depends_on: dependsOn || undefined
602
+ },
554
603
  updated: formatDate()
555
604
  };
556
605
  const updatedContent = input.body !== undefined ? `
@@ -2050,6 +2099,14 @@ function filterByQuery(issues, query) {
2050
2099
  if (issue.frontmatter.status !== statusValue) {
2051
2100
  return false;
2052
2101
  }
2102
+ } else if (statusValue === "unblocked") {
2103
+ if (!isIssueUnblocked(issue, issues)) {
2104
+ return false;
2105
+ }
2106
+ } else if (statusValue === "blocked") {
2107
+ if (issue.frontmatter.status !== "open" || isIssueUnblocked(issue, issues)) {
2108
+ return false;
2109
+ }
2053
2110
  }
2054
2111
  }
2055
2112
  if (parsed.qualifiers.priority) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "issy",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "AI-native issue tracking. Markdown files in .issues/, managed by your coding assistant.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -35,8 +35,8 @@
35
35
  "lint": "biome check src bin"
36
36
  },
37
37
  "dependencies": {
38
- "@miketromba/issy-app": "^0.8.0",
39
- "@miketromba/issy-core": "^0.8.0",
38
+ "@miketromba/issy-app": "^0.9.0",
39
+ "@miketromba/issy-core": "^0.9.0",
40
40
  "update-notifier": "^7.3.1"
41
41
  }
42
42
  }