issy 0.9.0 → 0.10.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.
Files changed (4) hide show
  1. package/README.md +20 -19
  2. package/dist/cli.js +17 -119
  3. package/dist/main.js +329 -59
  4. package/package.json +3 -3
package/README.md CHANGED
@@ -18,7 +18,7 @@
18
18
 
19
19
  ## How It Works
20
20
 
21
- issy gives AI coding assistants a skill for managing issues. Just talk naturally:
21
+ issy gives AI coding assistants versioned instructions for managing issues. Add a small AGENTS.md rule or install the bootstrap skill, then just talk naturally:
22
22
 
23
23
  > "Create a bug for the login redirect issue, high priority"
24
24
 
@@ -28,19 +28,31 @@ issy gives AI coding assistants a skill for managing issues. Just talk naturally
28
28
 
29
29
  The assistant creates, searches, updates, and closes issues for you. Issues are stored as markdown files in `.issy/issues/` — readable, diffable, and committed with your code.
30
30
 
31
- ## Install the Skill
31
+ ## Teach Your AI Assistant
32
+
33
+ ```bash
34
+ issy learn
35
+ ```
36
+
37
+ `issy learn` prints the canonical AI-agent reference for your installed issy version. To make agents run it automatically, add this to AGENTS.md:
38
+
39
+ ```md
40
+ When the task involves creating, reading, updating, listing, searching, prioritizing, closing, reopening, or choosing project issues, tasks, bugs, improvements, or roadmap work, run `issy learn` first and follow its guidance.
41
+ ```
42
+
43
+ You can also install the lightweight bootstrap skill:
32
44
 
33
45
  ```bash
34
46
  issy skill install
35
47
  ```
36
48
 
37
- Or, if you haven't installed `issy` yet:
49
+ Or, without running the issy wrapper:
38
50
 
39
51
  ```bash
40
52
  npx skills add miketromba/issy
41
53
  ```
42
54
 
43
- That's it. Your AI assistant can now manage issues in any repo.
55
+ The skill contains only the relevance rule and tells compatible assistants to run `issy learn`, keeping the real agent guidance in one versioned place. The `issy` CLI still needs to be available when the assistant runs `issy learn`.
44
56
 
45
57
  ## Why Markdown?
46
58
 
@@ -78,9 +90,9 @@ Once installed globally, you can run commands from your terminal:
78
90
  ```bash
79
91
  issy # Start the web UI
80
92
  issy list # List open issues (roadmap order)
81
- issy list --unblocked # List open issues with no open blockers
82
93
  issy next # Show next issue to work on
83
94
  issy create --title "Bug" # Create an issue
95
+ issy learn # Print AI-agent instructions
84
96
  ```
85
97
 
86
98
  ### Repository installation
@@ -125,19 +137,18 @@ Opens a local read-only UI at `http://localhost:1554` for browsing issues.
125
137
  issy init # Create .issy/issues/ directory
126
138
  issy init --seed # Create with a welcome issue
127
139
  issy list # List open issues (roadmap order)
128
- issy list --unblocked # List open issues with no open blockers
129
140
  issy next # Show next issue to work on
130
141
  issy search "auth" # Fuzzy search
131
142
  issy read 0001 # View issue
132
143
  issy create --title "Bug" --after 0002 # Create issue after #0002
133
- issy create --title "Feature" --depends-on 0012,0035 --last # Blocked by issues
134
144
  issy create --title "Bug" --body "Details here" --last # Create with body content
135
145
  issy update 0001 --before 0003 # Reposition in roadmap
136
- issy update 0001 --depends-on 0012,0035 # Replace blockers
137
146
  issy update 0001 --body "New details" # Replace body content
138
147
  issy close 0001 # Close issue
139
148
  issy reopen 0001 --after 0004 # Reopen and place in roadmap
140
- issy skill install # Install the AI skill
149
+ issy learn # Print compact AI-agent instructions
150
+ issy learn roadmap # Print focused topic instructions
151
+ issy skill install # Install the AI bootstrap skill
141
152
  issy migrate # Migrate from .issues/ to .issy/
142
153
  issy --version # Check version
143
154
  ```
@@ -164,14 +175,6 @@ issy list --sort priority # Sort by priority instead
164
175
  issy list --sort created # Sort by creation date
165
176
  ```
166
177
 
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
-
175
178
  ### Hooks
176
179
 
177
180
  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.
@@ -223,7 +226,6 @@ scope: medium
223
226
  type: bug
224
227
  status: open
225
228
  order: a0
226
- depends_on: 0012, 0035
227
229
  created: 2025-01-15T10:30:00
228
230
  ---
229
231
 
@@ -241,7 +243,6 @@ session isn't established, causing a redirect loop.
241
243
  | `status` | `open`, `closed` |
242
244
  | `labels` | comma-separated (optional) |
243
245
  | `order` | fractional index key (managed by issy) |
244
- | `depends_on` | comma-separated blocking issue IDs (optional) |
245
246
 
246
247
  ## Configuration
247
248
 
package/dist/cli.js CHANGED
@@ -355,9 +355,6 @@ 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
- }
361
358
  lines.push(`created: ${data.created}`);
362
359
  if (data.updated) {
363
360
  lines.push(`updated: ${data.updated}`);
@@ -366,44 +363,6 @@ function generateFrontmatter(data) {
366
363
  return lines.join(`
367
364
  `);
368
365
  }
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
- }
407
366
  function getIssueIdFromFilename(filename) {
408
367
  const match = filename.match(/^(\d+)-/);
409
368
  return match ? match[1] : filename.replace(".md", "");
@@ -537,8 +496,6 @@ async function createIssue(input) {
537
496
  const issueNumber = await getNextIssueNumber();
538
497
  const slug = createSlug(input.title);
539
498
  const filename = `${issueNumber}-${slug}.md`;
540
- const existingIssues = await getAllIssues();
541
- const dependsOn = formatExistingDependencyIds(input.depends_on, existingIssues, issueNumber);
542
499
  const frontmatter = {
543
500
  title: input.title,
544
501
  priority,
@@ -547,7 +504,6 @@ async function createIssue(input) {
547
504
  labels: input.labels || undefined,
548
505
  status: "open",
549
506
  order: input.order || undefined,
550
- depends_on: dependsOn || undefined,
551
507
  created: formatDate()
552
508
  };
553
509
  const body = input.body ?? `
@@ -573,8 +529,6 @@ async function updateIssue(id, input) {
573
529
  if (!issue) {
574
530
  throw new Error(`Issue not found: ${id}`);
575
531
  }
576
- const allIssues = await getAllIssues();
577
- const dependsOn = input.depends_on !== undefined ? formatExistingDependencyIds(input.depends_on, allIssues, issue.id) : undefined;
578
532
  const updatedFrontmatter = {
579
533
  ...issue.frontmatter,
580
534
  ...input.title && { title: input.title },
@@ -586,9 +540,6 @@ async function updateIssue(id, input) {
586
540
  },
587
541
  ...input.status && { status: input.status },
588
542
  ...input.order && { order: input.order },
589
- ...input.depends_on !== undefined && {
590
- depends_on: dependsOn || undefined
591
- },
592
543
  updated: formatDate()
593
544
  };
594
545
  const updatedContent = input.body !== undefined ? `
@@ -2088,14 +2039,6 @@ function filterByQuery(issues, query) {
2088
2039
  if (issue.frontmatter.status !== statusValue) {
2089
2040
  return false;
2090
2041
  }
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
- }
2099
2042
  }
2100
2043
  }
2101
2044
  if (parsed.qualifiers.priority) {
@@ -2183,23 +2126,7 @@ function typeSymbol(type) {
2183
2126
  }
2184
2127
  function formatIssueRow(issue) {
2185
2128
  const status = issue.frontmatter.status === "open" ? "OPEN " : "CLOSED";
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);
2129
+ return ` ${issue.id} ${prioritySymbol(issue.frontmatter.priority)} ${typeSymbol(issue.frontmatter.type)} ${status} ${issue.frontmatter.title}`;
2203
2130
  }
2204
2131
  async function resolvePosition(opts) {
2205
2132
  const openIssues = await getOpenIssuesByOrder();
@@ -2231,7 +2158,7 @@ function hasHelpFlag(commandArgs) {
2231
2158
  async function listIssues(options) {
2232
2159
  const allIssues = await getAllIssues();
2233
2160
  const queryParts = [];
2234
- if (!options.all && !options.unblocked)
2161
+ if (!options.all)
2235
2162
  queryParts.push("is:open");
2236
2163
  if (options.priority)
2237
2164
  queryParts.push(`priority:${options.priority}`);
@@ -2243,23 +2170,17 @@ async function listIssues(options) {
2243
2170
  queryParts.push(`sort:${options.sort}`);
2244
2171
  if (options.search)
2245
2172
  queryParts.push(options.search);
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
- }
2173
+ const query = queryParts.join(" ") || "is:open";
2174
+ const issues = filterByQuery(allIssues, query);
2251
2175
  if (issues.length === 0) {
2252
2176
  console.log("No issues found.");
2253
2177
  return;
2254
2178
  }
2255
2179
  console.log(`
2256
- ID Pri Type Status Blk Title`);
2180
+ ID Pri Type Status Title`);
2257
2181
  console.log(` ${"-".repeat(100)}`);
2258
2182
  for (const issue of issues) {
2259
- console.log(formatIssueRow({
2260
- ...issue,
2261
- blockers: getBlockingIssues(issue, allIssues).length
2262
- }));
2183
+ console.log(formatIssueRow(issue));
2263
2184
  }
2264
2185
  console.log(`
2265
2186
  Total: ${issues.length} issue(s)
@@ -2267,7 +2188,6 @@ async function listIssues(options) {
2267
2188
  }
2268
2189
  async function readIssue(id) {
2269
2190
  const issue = await getIssue(id);
2270
- const allIssues = await getAllIssues();
2271
2191
  if (!issue) {
2272
2192
  console.error(`Issue not found: ${id}`);
2273
2193
  process.exit(1);
@@ -2286,7 +2206,6 @@ ${"=".repeat(70)}`);
2286
2206
  if (issue.frontmatter.labels) {
2287
2207
  console.log(` Labels: ${issue.frontmatter.labels}`);
2288
2208
  }
2289
- printDependsOn(issue, allIssues);
2290
2209
  if (issue.frontmatter.order) {
2291
2210
  console.log(` Order: ${issue.frontmatter.order}`);
2292
2211
  }
@@ -2309,13 +2228,10 @@ async function searchIssuesCommand(query, options) {
2309
2228
  console.log(`
2310
2229
  Search results for "${query}":`);
2311
2230
  console.log(`
2312
- ID Pri Type Status Blk Title`);
2231
+ ID Pri Type Status Title`);
2313
2232
  console.log(` ${"-".repeat(100)}`);
2314
2233
  for (const issue of issues) {
2315
- console.log(formatIssueRow({
2316
- ...issue,
2317
- blockers: getBlockingIssues(issue, allIssues).length
2318
- }));
2234
+ console.log(formatIssueRow(issue));
2319
2235
  }
2320
2236
  console.log(`
2321
2237
  Found: ${issues.length} issue(s)
@@ -2341,7 +2257,6 @@ async function createIssueCommand(options) {
2341
2257
  scope: options.scope,
2342
2258
  type: options.type,
2343
2259
  labels: options.labels,
2344
- depends_on: await resolveDependsOn(options.dependsOn) || undefined,
2345
2260
  order
2346
2261
  };
2347
2262
  const issue = await createIssue(input);
@@ -2378,9 +2293,6 @@ async function updateIssueCommand(id, options) {
2378
2293
  scope: options.scope,
2379
2294
  type: options.type,
2380
2295
  labels: options.labels,
2381
- ...options.dependsOn !== undefined && {
2382
- depends_on: await resolveDependsOn(options.dependsOn, id) || ""
2383
- },
2384
2296
  order
2385
2297
  });
2386
2298
  console.log(`Updated issue: ${issue.filename}`);
@@ -2429,7 +2341,6 @@ async function reopenIssueCommand(id, options) {
2429
2341
  }
2430
2342
  async function nextIssueCommand() {
2431
2343
  const issue = await getNextIssue();
2432
- const allIssues = await getAllIssues();
2433
2344
  if (!issue) {
2434
2345
  console.log("No open issues.");
2435
2346
  return;
@@ -2448,7 +2359,6 @@ ${"=".repeat(70)}`);
2448
2359
  if (issue.frontmatter.labels) {
2449
2360
  console.log(` Labels: ${issue.frontmatter.labels}`);
2450
2361
  }
2451
- printDependsOn(issue, allIssues);
2452
2362
  if (issue.frontmatter.order) {
2453
2363
  console.log(` Order: ${issue.frontmatter.order}`);
2454
2364
  }
@@ -2476,7 +2386,6 @@ Options:
2476
2386
  Commands:
2477
2387
  list List all open issues (roadmap order)
2478
2388
  --all, -a Include closed issues
2479
- --unblocked Only show open issues with no open blockers
2480
2389
  --priority, -p <p> Filter by priority (high, medium, low)
2481
2390
  --scope <s> Filter by scope (small, medium, large)
2482
2391
  --type, -t <t> Filter by type (bug, improvement)
@@ -2490,6 +2399,10 @@ Commands:
2490
2399
 
2491
2400
  next Show the next issue to work on
2492
2401
 
2402
+ learn [topic] Print AI-agent instructions for this issy version
2403
+ --all Print the full reference
2404
+ --list List focused topics
2405
+
2493
2406
  create Create a new issue
2494
2407
  --title, -t <t> Issue title
2495
2408
  --body, -b <b> Markdown body content
@@ -2497,7 +2410,6 @@ Commands:
2497
2410
  --scope <s> Scope (small, medium, large)
2498
2411
  --type <t> Type (bug, improvement)
2499
2412
  --labels, -l <l> Comma-separated labels
2500
- --depends-on <ids> Comma-separated blocking issue IDs
2501
2413
  --before <id> Insert before this issue in roadmap
2502
2414
  --after <id> Insert after this issue in roadmap
2503
2415
  --first Insert at the beginning of the roadmap
@@ -2510,7 +2422,6 @@ Commands:
2510
2422
  --scope <s> New scope
2511
2423
  --type <t> New type
2512
2424
  --labels, -l <l> New labels
2513
- --depends-on <ids> Replace blocking issue IDs (empty clears)
2514
2425
  --before <id> Move before this issue in roadmap
2515
2426
  --after <id> Move after this issue in roadmap
2516
2427
  --first Move to the beginning of the roadmap
@@ -2524,13 +2435,14 @@ Commands:
2524
2435
  --first Insert at the beginning of the roadmap
2525
2436
  --last Insert at the end of the roadmap
2526
2437
 
2527
- skill install Install the issy skill for your AI coding assistant
2438
+ skill install Install the issy bootstrap skill
2528
2439
 
2529
2440
  Examples:
2530
2441
  issy list
2531
- issy list --unblocked
2532
2442
  issy list --priority high --type bug
2533
2443
  issy next
2444
+ issy learn
2445
+ issy learn roadmap
2534
2446
  issy read 0001
2535
2447
  issy create --title "Fix login bug" --type bug --priority high --after 0002
2536
2448
  issy create --title "Add dark mode" --last
@@ -2550,7 +2462,6 @@ List all open issues in roadmap order.
2550
2462
 
2551
2463
  Options:
2552
2464
  --all, -a Include closed issues
2553
- --unblocked Only show open issues with no open blockers
2554
2465
  --priority, -p <p> Filter by priority (high, medium, low)
2555
2466
  --scope <s> Filter by scope (small, medium, large)
2556
2467
  --type, -t <t> Filter by type (bug, improvement)
@@ -2563,7 +2474,6 @@ Options:
2563
2474
  args: args.slice(1),
2564
2475
  options: {
2565
2476
  all: { type: "boolean", short: "a" },
2566
- unblocked: { type: "boolean" },
2567
2477
  priority: { type: "string", short: "p" },
2568
2478
  scope: { type: "string" },
2569
2479
  type: { type: "string", short: "t" },
@@ -2641,7 +2551,6 @@ Options:
2641
2551
  --scope <s> Scope: small, medium, large
2642
2552
  --type <t> Type: bug, improvement (default: improvement)
2643
2553
  --labels, -l <l> Comma-separated labels
2644
- --depends-on <ids> Comma-separated blocking issue IDs
2645
2554
  --before <id> Insert before this issue in roadmap
2646
2555
  --after <id> Insert after this issue in roadmap
2647
2556
  --first Insert at the beginning of the roadmap
@@ -2649,7 +2558,6 @@ Options:
2649
2558
 
2650
2559
  Examples:
2651
2560
  issy create --title "Fix login bug" --type bug --priority high --after 0002
2652
- issy create --title "Add export" --depends-on 0001,0002 --last
2653
2561
  issy create --title "Add dark mode" --last
2654
2562
  `);
2655
2563
  return;
@@ -2663,7 +2571,6 @@ Examples:
2663
2571
  scope: { type: "string" },
2664
2572
  type: { type: "string" },
2665
2573
  labels: { type: "string", short: "l" },
2666
- "depends-on": { type: "string" },
2667
2574
  before: { type: "string" },
2668
2575
  after: { type: "string" },
2669
2576
  first: { type: "boolean" },
@@ -2671,10 +2578,7 @@ Examples:
2671
2578
  },
2672
2579
  allowPositionals: true
2673
2580
  });
2674
- await createIssueCommand({
2675
- ...values,
2676
- dependsOn: getDependsOnArg(values)
2677
- });
2581
+ await createIssueCommand(values);
2678
2582
  break;
2679
2583
  }
2680
2584
  case "update": {
@@ -2690,7 +2594,6 @@ Options:
2690
2594
  --scope <s> New scope: small, medium, large
2691
2595
  --type <t> New type: bug, improvement
2692
2596
  --labels, -l <l> New labels (comma-separated)
2693
- --depends-on <ids> Replace blocking issue IDs (empty clears)
2694
2597
  --before <id> Move before this issue in roadmap
2695
2598
  --after <id> Move after this issue in roadmap
2696
2599
  --first Move to the beginning of the roadmap
@@ -2698,7 +2601,6 @@ Options:
2698
2601
 
2699
2602
  Examples:
2700
2603
  issy update 0001 --priority low --after 0003
2701
- issy update 0002 --depends-on 0001,0003
2702
2604
  issy update 0002 --title "Renamed issue" --first
2703
2605
  `);
2704
2606
  return;
@@ -2717,7 +2619,6 @@ Examples:
2717
2619
  scope: { type: "string" },
2718
2620
  type: { type: "string" },
2719
2621
  labels: { type: "string", short: "l" },
2720
- "depends-on": { type: "string" },
2721
2622
  before: { type: "string" },
2722
2623
  after: { type: "string" },
2723
2624
  first: { type: "boolean" },
@@ -2725,10 +2626,7 @@ Examples:
2725
2626
  },
2726
2627
  allowPositionals: true
2727
2628
  });
2728
- await updateIssueCommand(id, {
2729
- ...values,
2730
- dependsOn: getDependsOnArg(values)
2731
- });
2629
+ await updateIssueCommand(id, values);
2732
2630
  break;
2733
2631
  }
2734
2632
  case "close": {
package/dist/main.js CHANGED
@@ -366,9 +366,6 @@ 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
- }
372
369
  lines.push(`created: ${data.created}`);
373
370
  if (data.updated) {
374
371
  lines.push(`updated: ${data.updated}`);
@@ -377,44 +374,6 @@ function generateFrontmatter(data) {
377
374
  return lines.join(`
378
375
  `);
379
376
  }
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
- }
418
377
  function getIssueIdFromFilename(filename) {
419
378
  const match = filename.match(/^(\d+)-/);
420
379
  return match ? match[1] : filename.replace(".md", "");
@@ -548,8 +507,6 @@ async function createIssue(input) {
548
507
  const issueNumber = await getNextIssueNumber();
549
508
  const slug = createSlug(input.title);
550
509
  const filename = `${issueNumber}-${slug}.md`;
551
- const existingIssues = await getAllIssues();
552
- const dependsOn = formatExistingDependencyIds(input.depends_on, existingIssues, issueNumber);
553
510
  const frontmatter = {
554
511
  title: input.title,
555
512
  priority,
@@ -558,7 +515,6 @@ async function createIssue(input) {
558
515
  labels: input.labels || undefined,
559
516
  status: "open",
560
517
  order: input.order || undefined,
561
- depends_on: dependsOn || undefined,
562
518
  created: formatDate()
563
519
  };
564
520
  const body = input.body ?? `
@@ -584,8 +540,6 @@ async function updateIssue(id, input) {
584
540
  if (!issue) {
585
541
  throw new Error(`Issue not found: ${id}`);
586
542
  }
587
- const allIssues = await getAllIssues();
588
- const dependsOn = input.depends_on !== undefined ? formatExistingDependencyIds(input.depends_on, allIssues, issue.id) : undefined;
589
543
  const updatedFrontmatter = {
590
544
  ...issue.frontmatter,
591
545
  ...input.title && { title: input.title },
@@ -597,9 +551,6 @@ async function updateIssue(id, input) {
597
551
  },
598
552
  ...input.status && { status: input.status },
599
553
  ...input.order && { order: input.order },
600
- ...input.depends_on !== undefined && {
601
- depends_on: dependsOn || undefined
602
- },
603
554
  updated: formatDate()
604
555
  };
605
556
  const updatedContent = input.body !== undefined ? `
@@ -2099,14 +2050,6 @@ function filterByQuery(issues, query) {
2099
2050
  if (issue.frontmatter.status !== statusValue) {
2100
2051
  return false;
2101
2052
  }
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
- }
2110
2053
  }
2111
2054
  }
2112
2055
  if (parsed.qualifiers.priority) {
@@ -2175,12 +2118,339 @@ function filterByQuery(issues, query) {
2175
2118
  }
2176
2119
  return result;
2177
2120
  }
2121
+ // src/learn.ts
2122
+ function markdown(lines) {
2123
+ return `${lines.join(`
2124
+ `).trim()}
2125
+ `;
2126
+ }
2127
+ var compact = markdown([
2128
+ "# issy agent instructions",
2129
+ "",
2130
+ "Use issy for project issue tracking when the user asks to create, read, update, list, search, prioritize, close, reopen, or choose issue/task/bug work.",
2131
+ "",
2132
+ "## First action",
2133
+ "",
2134
+ "- If this request is about issue tracking, run `issy learn` before acting unless you already ran it in this session.",
2135
+ "- Use the returned guidance as the source of truth for this installed issy version.",
2136
+ "- For focused details, run `issy learn <topic>` instead of loading everything.",
2137
+ "",
2138
+ "## Essential workflow",
2139
+ "",
2140
+ "- List work: `issy list` or `issy list --all`.",
2141
+ '- Search work: `issy search "query"` or `issy search "query" --all`.',
2142
+ "- Read before changing: `issy read <id>`.",
2143
+ "- Pick next work: `issy next`.",
2144
+ '- Create work: `issy create --title "..." --type bug|improvement --priority high|medium|low <position>`.',
2145
+ "- Update work: `issy update <id> [options]`.",
2146
+ "- Close work: `issy close <id>` after the work is complete and verified.",
2147
+ "- Reopen work: `issy reopen <id> <position>`.",
2148
+ "",
2149
+ "## Issue authoring",
2150
+ "",
2151
+ "- Capture what needs to be done and why. Keep implementation details out unless the user provides them.",
2152
+ "- Good bodies usually include Problem/Overview, Proposed Solution, optional Acceptance Criteria, optional Verification, optional Future Considerations, and optional References.",
2153
+ "- Include verification guidance when possible so the implementing agent can prove completion with commands, UI flows, queries, or expected behavior.",
2154
+ "- Issues should be completable and verifiable in one focused session. Split only when a child issue is independently closeable.",
2155
+ "",
2156
+ "## Roadmap ordering",
2157
+ "",
2158
+ "- Open issues form a strict roadmap order.",
2159
+ "- When creating an issue and open issues already exist, include exactly one position flag: `--before <id>`, `--after <id>`, `--first`, or `--last`.",
2160
+ "- When reopening an issue and other open issues exist, include exactly one position flag.",
2161
+ "- Use dependency order: prerequisites first, dependent/user-facing work later. Use `--last` when placement is unclear.",
2162
+ "",
2163
+ "## Closing",
2164
+ "",
2165
+ "- Before closing, verify the issue is actually resolved.",
2166
+ "- If useful context was discovered, append a brief `## Resolution Notes` section before closing.",
2167
+ "- If the repo tracks issues in git, consider committing `.issy/` changes after mutations.",
2168
+ "",
2169
+ "## More focused context",
2170
+ "",
2171
+ "- `issy learn topics` lists available topics.",
2172
+ "- `issy learn authoring` covers issue writing rules.",
2173
+ "- `issy learn roadmap` covers placement rules.",
2174
+ "- `issy learn commands` covers CLI command syntax.",
2175
+ "- `issy learn hooks` covers hook files.",
2176
+ "- `issy learn agents` prints AGENTS.md and skill bootstrap guidance.",
2177
+ "- `issy learn --all` prints the full agent reference."
2178
+ ]);
2179
+ var topics = [
2180
+ {
2181
+ name: "authoring",
2182
+ description: "Issue writing, sizing, verification, and resolution notes.",
2183
+ content: markdown([
2184
+ "# issy issue authoring",
2185
+ "",
2186
+ "Issues describe what needs to be done and why. Keep them high-level unless the user provides specific implementation details.",
2187
+ "",
2188
+ "## What to include",
2189
+ "",
2190
+ "- Problem/Overview: what is wrong or needed, usually one or two paragraphs.",
2191
+ "- Proposed Solution: high-level approach.",
2192
+ "- Acceptance Criteria: optional, from the user perspective.",
2193
+ "- Verification: optional but encouraged; explain how to prove the issue is resolved.",
2194
+ "- Future Considerations: optional related ideas for later.",
2195
+ "- References: optional links to related docs, issues, PRs, or resources.",
2196
+ "",
2197
+ "## Verification guidance",
2198
+ "",
2199
+ "When possible, include verification steps or hints. Agents should not claim work is done without evidence.",
2200
+ "",
2201
+ "Useful verification examples:",
2202
+ "",
2203
+ "- Commands to run and expected output, such as `bun test`, `npm test`, `curl`, or a project CLI.",
2204
+ "- UI flows to test, including browser automation when available.",
2205
+ "- Database queries or API calls that confirm state changes.",
2206
+ "- Specific behavior to observe.",
2207
+ "- Edge cases to check.",
2208
+ "",
2209
+ "## Implementation details rule",
2210
+ "",
2211
+ "Only include implementation details if the user explicitly provides them. Do not invent task lists, phases, file paths, code changes, or step-by-step technical breakdowns.",
2212
+ "",
2213
+ "The issue should capture the user intent. The engineer or agent implementing it can plan the implementation later.",
2214
+ "",
2215
+ "## Sizing",
2216
+ "",
2217
+ "An issue should be completable and verifiable in a single focused session. Split large work along verification boundaries so each child issue can be independently completed and closed. Do not split when it adds overhead without clarity.",
2218
+ "",
2219
+ "## Closing with learnings",
2220
+ "",
2221
+ "When closing an issue, append a brief `## Resolution Notes` section if useful context was discovered during implementation:",
2222
+ "",
2223
+ "- Alternative approaches considered or rejected.",
2224
+ "- Unexpected gotchas or edge cases.",
2225
+ "- Decisions that differ from the original plan.",
2226
+ "- Useful context for future maintainers."
2227
+ ])
2228
+ },
2229
+ {
2230
+ name: "roadmap",
2231
+ description: "Strict roadmap ordering and position flag rules.",
2232
+ aliases: ["ordering"],
2233
+ content: markdown([
2234
+ "# issy roadmap ordering",
2235
+ "",
2236
+ "issy maintains a strict roadmap order for all open issues. `issy next` returns the first open issue in that order. `issy list` sorts by roadmap order by default.",
2237
+ "",
2238
+ "## Required position flags",
2239
+ "",
2240
+ "- Creating an issue: if open issues already exist, provide exactly one of `--before <id>`, `--after <id>`, `--first`, or `--last`.",
2241
+ "- Reopening an issue: if other open issues exist, provide exactly one position flag.",
2242
+ "- Updating an issue: position flags are optional and reposition the issue when provided.",
2243
+ "- Never provide more than one position flag.",
2244
+ "",
2245
+ "## Choosing placement",
2246
+ "",
2247
+ "- Place prerequisites before dependent issues.",
2248
+ "- Place foundational or infrastructure work before user-facing work that depends on it.",
2249
+ "- Use `--first` for urgent work that should be tackled immediately.",
2250
+ "- Use `--last` when placement is unclear.",
2251
+ "- Use `--before <id>` or `--after <id>` for precise placement.",
2252
+ "",
2253
+ "## Useful commands",
2254
+ "",
2255
+ "- `issy list` shows open issues in roadmap order.",
2256
+ "- `issy next` shows the first open issue in roadmap order.",
2257
+ '- `issy create --title "..." --last` appends a new issue.',
2258
+ "- `issy update <id> --before <other-id>` repositions an issue.",
2259
+ "- `issy reopen <id> --after <other-id>` reopens and places a closed issue."
2260
+ ])
2261
+ },
2262
+ {
2263
+ name: "commands",
2264
+ description: "CLI command syntax for issue operations.",
2265
+ aliases: ["cli", "reference"],
2266
+ content: markdown([
2267
+ "# issy CLI command reference",
2268
+ "",
2269
+ "Use the `issy` CLI. If it is not installed, install it with the project package manager, for example `npm install issy --global`, `pnpm add issy --global`, or `bun install issy --global`.",
2270
+ "",
2271
+ "## List and search",
2272
+ "",
2273
+ "- `issy list`: list open issues in roadmap order.",
2274
+ "- `issy list --all`: include closed issues.",
2275
+ "- `issy list --priority high|medium|low`: filter by priority.",
2276
+ "- `issy list --scope small|medium|large`: filter by scope.",
2277
+ "- `issy list --type bug|improvement`: filter by type.",
2278
+ '- `issy list --search "keyword"`: fuzzy search while listing.',
2279
+ "- `issy list --sort roadmap|priority|created|updated|id`: choose sort order.",
2280
+ '- `issy search "query"`: fuzzy search open issues.',
2281
+ '- `issy search "query" --all`: include closed issues.',
2282
+ "",
2283
+ "## Read and choose work",
2284
+ "",
2285
+ "- `issy read <id>`: read a full issue.",
2286
+ "- `issy next`: show the next open issue in roadmap order.",
2287
+ "",
2288
+ "## Create",
2289
+ "",
2290
+ '- `issy create --title "Fix login bug" --type bug --priority high --after 0002`.',
2291
+ '- `issy create --title "Add dark mode" --type improvement --last --labels "ui, frontend"`.',
2292
+ '- `issy create --title "Urgent fix" --first`.',
2293
+ '- `issy create --title "Fix crash" --body "## Problem\\n\\nApp crashes on startup." --last`.',
2294
+ "",
2295
+ "Create options: `--title`, `--body`, `--priority`, `--scope`, `--type`, `--labels`, `--before`, `--after`, `--first`, `--last`.",
2296
+ "",
2297
+ "## Update",
2298
+ "",
2299
+ "- `issy update <id> --priority low`.",
2300
+ "- `issy update <id> --after 0003`.",
2301
+ "- `issy update <id> --first`.",
2302
+ '- `issy update <id> --labels "api, backend"`.',
2303
+ '- `issy update <id> --body "## Problem\\n\\nUpdated description."`.',
2304
+ "",
2305
+ "Update options: `--title`, `--body`, `--priority`, `--scope`, `--type`, `--labels`, `--before`, `--after`, `--first`, `--last`.",
2306
+ "",
2307
+ "## Close and reopen",
2308
+ "",
2309
+ "- `issy close <id>`.",
2310
+ "- `issy reopen <id> --last`.",
2311
+ "- `issy reopen <id> --after 0004`.",
2312
+ "",
2313
+ "When reopening and other open issues exist, include exactly one position flag."
2314
+ ])
2315
+ },
2316
+ {
2317
+ name: "hooks",
2318
+ description: "Optional `.issy/` hook files that print agent context after mutations.",
2319
+ content: markdown([
2320
+ "# issy hooks",
2321
+ "",
2322
+ "issy supports optional hook files in `.issy/`. After a successful mutation, issy prints the matching hook file contents to stdout so agents can see project-specific reminders.",
2323
+ "",
2324
+ "## Hook files",
2325
+ "",
2326
+ "- `.issy/on_create.md`: printed after `issy create`.",
2327
+ "- `.issy/on_update.md`: printed after `issy update`.",
2328
+ "- `.issy/on_close.md`: printed after `issy close`.",
2329
+ "",
2330
+ "## Good hook uses",
2331
+ "",
2332
+ "- Remind agents to update docs for user-facing behavior changes.",
2333
+ "- Remind agents to run project-specific checks.",
2334
+ "- Add team conventions for issue mutations.",
2335
+ "- Surface release or changelog requirements.",
2336
+ "",
2337
+ "Hook content should be concise because it is injected directly into command output."
2338
+ ])
2339
+ },
2340
+ {
2341
+ name: "agents",
2342
+ description: "Bootstrap instructions for AGENTS.md and shell skills.",
2343
+ aliases: ["skill", "bootstrap"],
2344
+ content: markdown([
2345
+ "# issy agent bootstrap",
2346
+ "",
2347
+ "`issy learn` is the canonical AI-agent reference for the installed issy version. Skills and AGENTS.md files should bootstrap agents into this command instead of duplicating the full reference.",
2348
+ "",
2349
+ "## Relevance rule",
2350
+ "",
2351
+ "Use issy when creating, reading, updating, listing, searching, prioritizing, closing, reopening, or choosing project issues, tasks, bugs, improvements, or roadmap work.",
2352
+ "",
2353
+ "## AGENTS.md snippet",
2354
+ "",
2355
+ "Add this to AGENTS.md:",
2356
+ "",
2357
+ "```md",
2358
+ "When the task involves creating, reading, updating, listing, searching, prioritizing, closing, reopening, or choosing project issues, tasks, bugs, improvements, or roadmap work, run `issy learn` first and follow its guidance.",
2359
+ "```",
2360
+ "",
2361
+ "## Skill bootstrap behavior",
2362
+ "",
2363
+ "A compatible skill should only define the relevance rule and instruct the agent to run `issy learn` when relevant. The operational reference belongs in the CLI output."
2364
+ ])
2365
+ }
2366
+ ];
2367
+ var aliases = new Map;
2368
+ for (const topic of topics) {
2369
+ aliases.set(topic.name, topic);
2370
+ for (const alias of topic.aliases ?? []) {
2371
+ aliases.set(alias, topic);
2372
+ }
2373
+ }
2374
+ function topicList() {
2375
+ return markdown([
2376
+ "# issy learn topics",
2377
+ "",
2378
+ "Run `issy learn <topic>` for focused agent context.",
2379
+ "",
2380
+ ...topics.map((topic) => `- \`${topic.name}\`: ${topic.description}`),
2381
+ "",
2382
+ "Other options:",
2383
+ "",
2384
+ "- `issy learn`: compact default instructions.",
2385
+ "- `issy learn --all`: full reference.",
2386
+ "- `issy learn --help`: command usage."
2387
+ ]);
2388
+ }
2389
+ function usage() {
2390
+ return markdown([
2391
+ "Usage: issy learn [topic] [options]",
2392
+ "",
2393
+ "Print AI-agent instructions for using issy.",
2394
+ "",
2395
+ "Topics:",
2396
+ ...topics.map((topic) => ` ${topic.name.padEnd(10)} ${topic.description}`),
2397
+ "",
2398
+ "Options:",
2399
+ " --all Print the compact guidance and all topics",
2400
+ " --list List available topics",
2401
+ " --help, -h Show this help",
2402
+ "",
2403
+ "Examples:",
2404
+ " issy learn",
2405
+ " issy learn roadmap",
2406
+ " issy learn commands",
2407
+ " issy learn --all"
2408
+ ]);
2409
+ }
2410
+ function getLearnOutput(args = []) {
2411
+ if (args.includes("--help") || args.includes("-h")) {
2412
+ return usage();
2413
+ }
2414
+ if (args.includes("--list")) {
2415
+ return topicList();
2416
+ }
2417
+ if (args.includes("--all")) {
2418
+ return [compact, ...topics.map((topic2) => topic2.content)].join(`
2419
+ ---
2420
+
2421
+ `);
2422
+ }
2423
+ const topicArg = args.find((arg) => !arg.startsWith("-"));
2424
+ if (!topicArg) {
2425
+ return compact;
2426
+ }
2427
+ if (topicArg === "topics" || topicArg === "list") {
2428
+ return topicList();
2429
+ }
2430
+ const topic = aliases.get(topicArg);
2431
+ if (!topic) {
2432
+ throw new Error(`Unknown learn topic: ${topicArg}
2433
+
2434
+ ${topicList()}`);
2435
+ }
2436
+ return topic.content;
2437
+ }
2438
+
2178
2439
  // src/main.ts
2179
2440
  var args = process.argv.slice(2);
2180
2441
  if (args[0] === "--version" || args[0] === "-v") {
2181
2442
  console.log(`issy v${process.env.ISSY_PKG_VERSION || "unknown"}`);
2182
2443
  process.exit(0);
2183
2444
  }
2445
+ if (args[0] === "learn") {
2446
+ try {
2447
+ process.stdout.write(getLearnOutput(args.slice(1)));
2448
+ } catch (error) {
2449
+ console.error(error instanceof Error ? error.message : "Failed to learn");
2450
+ process.exit(1);
2451
+ }
2452
+ process.exit(0);
2453
+ }
2184
2454
  var issyDir2 = resolveIssyDir();
2185
2455
  var issuesDir2 = join2(issyDir2, "issues");
2186
2456
  process.env.ISSY_DIR = issyDir2;
@@ -2279,11 +2549,11 @@ function skill() {
2279
2549
  Usage: issy skill <command>
2280
2550
 
2281
2551
  Commands:
2282
- install Install the issy skill for your AI coding assistant
2552
+ install Install the issy bootstrap skill for your AI coding assistant
2283
2553
  `);
2284
2554
  process.exit(subcommand ? 1 : 0);
2285
2555
  }
2286
- console.log(`Installing issy skill via skills CLI...
2556
+ console.log(`Installing issy bootstrap skill via skills CLI...
2287
2557
  `);
2288
2558
  try {
2289
2559
  execSync("npx skills add miketromba/issy", { stdio: "inherit" });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "issy",
3
- "version": "0.9.0",
3
+ "version": "0.10.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.9.0",
39
- "@miketromba/issy-core": "^0.9.0",
38
+ "@miketromba/issy-app": "^0.10.0",
39
+ "@miketromba/issy-core": "^0.10.0",
40
40
  "update-notifier": "^7.3.1"
41
41
  }
42
42
  }