issy 0.7.3 → 0.8.1
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 +13 -3
- package/dist/cli.js +43 -5
- package/dist/main.js +31 -5
- package/package.json +3 -3
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-
|
|
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">
|
|
@@ -159,9 +159,17 @@ issy list --sort priority # Sort by priority instead
|
|
|
159
159
|
issy list --sort created # Sort by creation date
|
|
160
160
|
```
|
|
161
161
|
|
|
162
|
-
###
|
|
162
|
+
### Hooks
|
|
163
163
|
|
|
164
|
-
|
|
164
|
+
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.
|
|
165
|
+
|
|
166
|
+
| Hook file | Triggered after |
|
|
167
|
+
|-----------|----------------|
|
|
168
|
+
| `on_create.md` | Creating an issue |
|
|
169
|
+
| `on_update.md` | Updating an issue |
|
|
170
|
+
| `on_close.md` | Closing an issue |
|
|
171
|
+
|
|
172
|
+
Use these for post-action reminders like updating documentation, running checks, or prompting the agent with project-specific instructions.
|
|
165
173
|
|
|
166
174
|
### Monorepo Support
|
|
167
175
|
|
|
@@ -172,6 +180,8 @@ issy automatically walks up from the current directory to find an existing `.iss
|
|
|
172
180
|
my-monorepo/
|
|
173
181
|
.issy/ # ← issy finds this automatically
|
|
174
182
|
issues/
|
|
183
|
+
on_create.md
|
|
184
|
+
on_update.md
|
|
175
185
|
on_close.md
|
|
176
186
|
packages/
|
|
177
187
|
frontend/ # ← works from here
|
package/dist/cli.js
CHANGED
|
@@ -316,15 +316,33 @@ function parseFrontmatter(content) {
|
|
|
316
316
|
const colonIdx = line.indexOf(":");
|
|
317
317
|
if (colonIdx > 0) {
|
|
318
318
|
const key = line.slice(0, colonIdx).trim();
|
|
319
|
-
const
|
|
319
|
+
const rawValue = line.slice(colonIdx + 1).trim();
|
|
320
|
+
const value = key === "title" ? yamlUnquote(rawValue) : rawValue;
|
|
320
321
|
frontmatter[key] = value;
|
|
321
322
|
}
|
|
322
323
|
}
|
|
323
324
|
return { frontmatter, body };
|
|
324
325
|
}
|
|
326
|
+
function yamlQuote(value) {
|
|
327
|
+
if (/[:#\[\]{}&*!|>'"%@`,\n]/.test(value) || value !== value.trim()) {
|
|
328
|
+
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
329
|
+
return `"${escaped}"`;
|
|
330
|
+
}
|
|
331
|
+
return value;
|
|
332
|
+
}
|
|
333
|
+
function yamlUnquote(value) {
|
|
334
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
335
|
+
const inner = value.slice(1, -1);
|
|
336
|
+
if (value.startsWith('"')) {
|
|
337
|
+
return inner.replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
338
|
+
}
|
|
339
|
+
return inner;
|
|
340
|
+
}
|
|
341
|
+
return value;
|
|
342
|
+
}
|
|
325
343
|
function generateFrontmatter(data) {
|
|
326
344
|
const lines = ["---"];
|
|
327
|
-
lines.push(`title: ${data.title}`);
|
|
345
|
+
lines.push(`title: ${yamlQuote(data.title)}`);
|
|
328
346
|
lines.push(`priority: ${data.priority}`);
|
|
329
347
|
if (data.scope) {
|
|
330
348
|
lines.push(`scope: ${data.scope}`);
|
|
@@ -542,14 +560,22 @@ async function closeIssue(id) {
|
|
|
542
560
|
async function reopenIssue(id, order) {
|
|
543
561
|
return updateIssue(id, { status: "open", order });
|
|
544
562
|
}
|
|
545
|
-
async function
|
|
563
|
+
async function readHookFile(filename) {
|
|
546
564
|
try {
|
|
547
|
-
|
|
548
|
-
return await readFile(onClosePath, "utf-8");
|
|
565
|
+
return await readFile(join(getIssyDir(), filename), "utf-8");
|
|
549
566
|
} catch {
|
|
550
567
|
return null;
|
|
551
568
|
}
|
|
552
569
|
}
|
|
570
|
+
async function getOnCloseContent() {
|
|
571
|
+
return readHookFile("on_close.md");
|
|
572
|
+
}
|
|
573
|
+
async function getOnCreateContent() {
|
|
574
|
+
return readHookFile("on_create.md");
|
|
575
|
+
}
|
|
576
|
+
async function getOnUpdateContent() {
|
|
577
|
+
return readHookFile("on_update.md");
|
|
578
|
+
}
|
|
553
579
|
async function getNextIssue() {
|
|
554
580
|
const openIssues = await getOpenIssuesByOrder();
|
|
555
581
|
return openIssues.length > 0 ? openIssues[0] : null;
|
|
@@ -2236,6 +2262,12 @@ async function createIssueCommand(options) {
|
|
|
2236
2262
|
const issue = await createIssue(input);
|
|
2237
2263
|
console.log(`
|
|
2238
2264
|
Created issue: ${issue.filename}`);
|
|
2265
|
+
const onCreateContent = await getOnCreateContent();
|
|
2266
|
+
if (onCreateContent) {
|
|
2267
|
+
console.log(`
|
|
2268
|
+
${onCreateContent.trim()}
|
|
2269
|
+
`);
|
|
2270
|
+
}
|
|
2239
2271
|
} catch (e) {
|
|
2240
2272
|
console.error(e instanceof Error ? e.message : "Failed to create issue");
|
|
2241
2273
|
process.exit(1);
|
|
@@ -2264,6 +2296,12 @@ async function updateIssueCommand(id, options) {
|
|
|
2264
2296
|
order
|
|
2265
2297
|
});
|
|
2266
2298
|
console.log(`Updated issue: ${issue.filename}`);
|
|
2299
|
+
const onUpdateContent = await getOnUpdateContent();
|
|
2300
|
+
if (onUpdateContent) {
|
|
2301
|
+
console.log(`
|
|
2302
|
+
${onUpdateContent.trim()}
|
|
2303
|
+
`);
|
|
2304
|
+
}
|
|
2267
2305
|
} catch (e) {
|
|
2268
2306
|
console.error(e instanceof Error ? e.message : "Failed to update issue");
|
|
2269
2307
|
process.exit(1);
|
package/dist/main.js
CHANGED
|
@@ -327,15 +327,33 @@ function parseFrontmatter(content) {
|
|
|
327
327
|
const colonIdx = line.indexOf(":");
|
|
328
328
|
if (colonIdx > 0) {
|
|
329
329
|
const key = line.slice(0, colonIdx).trim();
|
|
330
|
-
const
|
|
330
|
+
const rawValue = line.slice(colonIdx + 1).trim();
|
|
331
|
+
const value = key === "title" ? yamlUnquote(rawValue) : rawValue;
|
|
331
332
|
frontmatter[key] = value;
|
|
332
333
|
}
|
|
333
334
|
}
|
|
334
335
|
return { frontmatter, body };
|
|
335
336
|
}
|
|
337
|
+
function yamlQuote(value) {
|
|
338
|
+
if (/[:#\[\]{}&*!|>'"%@`,\n]/.test(value) || value !== value.trim()) {
|
|
339
|
+
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
|
340
|
+
return `"${escaped}"`;
|
|
341
|
+
}
|
|
342
|
+
return value;
|
|
343
|
+
}
|
|
344
|
+
function yamlUnquote(value) {
|
|
345
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
346
|
+
const inner = value.slice(1, -1);
|
|
347
|
+
if (value.startsWith('"')) {
|
|
348
|
+
return inner.replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
349
|
+
}
|
|
350
|
+
return inner;
|
|
351
|
+
}
|
|
352
|
+
return value;
|
|
353
|
+
}
|
|
336
354
|
function generateFrontmatter(data) {
|
|
337
355
|
const lines = ["---"];
|
|
338
|
-
lines.push(`title: ${data.title}`);
|
|
356
|
+
lines.push(`title: ${yamlQuote(data.title)}`);
|
|
339
357
|
lines.push(`priority: ${data.priority}`);
|
|
340
358
|
if (data.scope) {
|
|
341
359
|
lines.push(`scope: ${data.scope}`);
|
|
@@ -553,14 +571,22 @@ async function closeIssue(id) {
|
|
|
553
571
|
async function reopenIssue(id, order) {
|
|
554
572
|
return updateIssue(id, { status: "open", order });
|
|
555
573
|
}
|
|
556
|
-
async function
|
|
574
|
+
async function readHookFile(filename) {
|
|
557
575
|
try {
|
|
558
|
-
|
|
559
|
-
return await readFile(onClosePath, "utf-8");
|
|
576
|
+
return await readFile(join(getIssyDir(), filename), "utf-8");
|
|
560
577
|
} catch {
|
|
561
578
|
return null;
|
|
562
579
|
}
|
|
563
580
|
}
|
|
581
|
+
async function getOnCloseContent() {
|
|
582
|
+
return readHookFile("on_close.md");
|
|
583
|
+
}
|
|
584
|
+
async function getOnCreateContent() {
|
|
585
|
+
return readHookFile("on_create.md");
|
|
586
|
+
}
|
|
587
|
+
async function getOnUpdateContent() {
|
|
588
|
+
return readHookFile("on_update.md");
|
|
589
|
+
}
|
|
564
590
|
async function getNextIssue() {
|
|
565
591
|
const openIssues = await getOpenIssuesByOrder();
|
|
566
592
|
return openIssues.length > 0 ? openIssues[0] : null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "issy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
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.8.1",
|
|
39
|
+
"@miketromba/issy-core": "^0.8.1",
|
|
40
40
|
"update-notifier": "^7.3.1"
|
|
41
41
|
}
|
|
42
42
|
}
|