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.
- package/README.md +20 -19
- package/dist/cli.js +17 -119
- package/dist/main.js +329 -59
- 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
|
|
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
|
-
##
|
|
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,
|
|
49
|
+
Or, without running the issy wrapper:
|
|
38
50
|
|
|
39
51
|
```bash
|
|
40
52
|
npx skills add miketromba/issy
|
|
41
53
|
```
|
|
42
54
|
|
|
43
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
39
|
-
"@miketromba/issy-core": "^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
|
}
|