issy 0.1.8 → 0.1.9
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 +6 -0
- package/bin/issy +101 -97
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -25,6 +25,8 @@ Opens a local UI at `http://localhost:1554`.
|
|
|
25
25
|
### CLI
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
+
issy init # Create .issues/ directory
|
|
29
|
+
issy init --seed # Create with a welcome issue
|
|
28
30
|
issy list # List open issues
|
|
29
31
|
issy search "auth" # Fuzzy search
|
|
30
32
|
issy read 0001 # View issue
|
|
@@ -34,6 +36,10 @@ issy close 0001 # Close issue
|
|
|
34
36
|
|
|
35
37
|
Run `issy help` for full options.
|
|
36
38
|
|
|
39
|
+
### Monorepo Support
|
|
40
|
+
|
|
41
|
+
issy walks up from the current directory to find an existing `.issues/` folder, so you can run it from any subdirectory.
|
|
42
|
+
|
|
37
43
|
## Configuration
|
|
38
44
|
|
|
39
45
|
| Variable | Description | Default |
|
package/bin/issy
CHANGED
|
@@ -1,38 +1,38 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { dirname } from
|
|
3
|
-
import { existsSync, mkdirSync, readdirSync, writeFileSync } from
|
|
4
|
-
import { join, resolve } from
|
|
5
|
-
import { fileURLToPath } from
|
|
2
|
+
import { dirname } from 'node:path'
|
|
3
|
+
import { existsSync, mkdirSync, readdirSync, writeFileSync } from 'node:fs'
|
|
4
|
+
import { join, resolve } from 'node:path'
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
6
6
|
|
|
7
|
-
const args = process.argv.slice(2)
|
|
7
|
+
const args = process.argv.slice(2)
|
|
8
8
|
const cliCommands = new Set([
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
])
|
|
9
|
+
'list',
|
|
10
|
+
'search',
|
|
11
|
+
'read',
|
|
12
|
+
'create',
|
|
13
|
+
'update',
|
|
14
|
+
'close',
|
|
15
|
+
'help',
|
|
16
|
+
'--help',
|
|
17
|
+
'-h'
|
|
18
|
+
])
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Find .issues directory by walking up from the given path.
|
|
22
22
|
* Returns the path if found, or null if not found.
|
|
23
23
|
*/
|
|
24
24
|
function findIssuesDirUpward(fromPath) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
25
|
+
let current = resolve(fromPath)
|
|
26
|
+
for (let i = 0; i < 20; i++) {
|
|
27
|
+
const candidate = join(current, '.issues')
|
|
28
|
+
if (existsSync(candidate)) {
|
|
29
|
+
return candidate
|
|
30
|
+
}
|
|
31
|
+
const parent = dirname(current)
|
|
32
|
+
if (parent === current) break
|
|
33
|
+
current = parent
|
|
34
|
+
}
|
|
35
|
+
return null
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/**
|
|
@@ -40,17 +40,17 @@ function findIssuesDirUpward(fromPath) {
|
|
|
40
40
|
* Returns the directory containing .git, or null if not in a git repo.
|
|
41
41
|
*/
|
|
42
42
|
function findGitRoot(fromPath) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
43
|
+
let current = resolve(fromPath)
|
|
44
|
+
for (let i = 0; i < 20; i++) {
|
|
45
|
+
const gitDir = join(current, '.git')
|
|
46
|
+
if (existsSync(gitDir)) {
|
|
47
|
+
return current
|
|
48
|
+
}
|
|
49
|
+
const parent = dirname(current)
|
|
50
|
+
if (parent === current) break
|
|
51
|
+
current = parent
|
|
52
|
+
}
|
|
53
|
+
return null
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
/**
|
|
@@ -61,78 +61,82 @@ function findGitRoot(fromPath) {
|
|
|
61
61
|
* 4. Fall back to creating .issues in ISSUES_ROOT or cwd
|
|
62
62
|
*/
|
|
63
63
|
function resolveIssuesDir() {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
64
|
+
// 1. Explicit override
|
|
65
|
+
if (process.env.ISSUES_DIR) {
|
|
66
|
+
return resolve(process.env.ISSUES_DIR)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 2. Try to find existing .issues by walking up
|
|
70
|
+
const startDir = process.env.ISSUES_ROOT || process.cwd()
|
|
71
|
+
const found = findIssuesDirUpward(startDir)
|
|
72
|
+
if (found) {
|
|
73
|
+
return found
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 3. If in a git repo, use .issues at the repo root
|
|
77
|
+
const gitRoot = findGitRoot(startDir)
|
|
78
|
+
if (gitRoot) {
|
|
79
|
+
return join(gitRoot, '.issues')
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 4. Fall back to creating in start directory
|
|
83
|
+
return join(resolve(startDir), '.issues')
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
const issuesDir = resolveIssuesDir()
|
|
86
|
+
const issuesDir = resolveIssuesDir()
|
|
87
87
|
// Set env vars for downstream consumers
|
|
88
|
-
process.env.ISSUES_DIR = issuesDir
|
|
88
|
+
process.env.ISSUES_DIR = issuesDir
|
|
89
89
|
// Set ISSUES_ROOT to the parent of .issues for consistency
|
|
90
|
-
process.env.ISSUES_ROOT = dirname(issuesDir)
|
|
90
|
+
process.env.ISSUES_ROOT = dirname(issuesDir)
|
|
91
91
|
|
|
92
|
-
if (cliCommands.has(args[0] ||
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
92
|
+
if (cliCommands.has(args[0] || '')) {
|
|
93
|
+
const here = resolve(fileURLToPath(import.meta.url), '..')
|
|
94
|
+
const entry = resolve(here, '..', 'dist', 'cli.js')
|
|
95
|
+
process.argv = [process.argv[0], process.argv[1], ...args]
|
|
96
|
+
const cli = await import(entry)
|
|
97
|
+
await cli.ready // Wait for main() to complete before exiting
|
|
98
|
+
process.exit(0)
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
const portIdx = args.findIndex(
|
|
101
|
+
const portIdx = args.findIndex(arg => arg === '--port' || arg === '-p')
|
|
102
102
|
if (portIdx >= 0 && args[portIdx + 1]) {
|
|
103
|
-
|
|
103
|
+
process.env.ISSUES_PORT = args[portIdx + 1]
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
const shouldInitOnly = args.includes(
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
if (!existsSync(issuesDir)) {
|
|
110
|
-
mkdirSync(issuesDir, { recursive: true });
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (!skipSeed) {
|
|
114
|
-
const hasIssues =
|
|
115
|
-
existsSync(issuesDir) && readdirSync(issuesDir).some((f) => f.endsWith(".md"));
|
|
116
|
-
if (!hasIssues) {
|
|
117
|
-
const welcome = `---\n` +
|
|
118
|
-
`title: Welcome to issy\n` +
|
|
119
|
-
`description: Your first issue in this repo\n` +
|
|
120
|
-
`priority: medium\n` +
|
|
121
|
-
`type: improvement\n` +
|
|
122
|
-
`status: open\n` +
|
|
123
|
-
`created: ${new Date().toISOString().slice(0, 19)}\n` +
|
|
124
|
-
`---\n\n` +
|
|
125
|
-
`## Details\n\n` +
|
|
126
|
-
`- This issue was created automatically on first run.\n` +
|
|
127
|
-
`- Edit it, close it, or delete it to get started.\n`;
|
|
128
|
-
writeFileSync(join(issuesDir, "0001-welcome-to-issy.md"), welcome);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
106
|
+
const shouldInitOnly = args.includes('init')
|
|
107
|
+
const shouldSeed = args.includes('--seed')
|
|
131
108
|
|
|
109
|
+
// Only create .issues directory on explicit 'init' command
|
|
132
110
|
if (shouldInitOnly) {
|
|
133
|
-
|
|
134
|
-
|
|
111
|
+
if (!existsSync(issuesDir)) {
|
|
112
|
+
mkdirSync(issuesDir, { recursive: true })
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Only seed with welcome issue if --seed flag is passed
|
|
116
|
+
if (shouldSeed) {
|
|
117
|
+
const hasIssues =
|
|
118
|
+
existsSync(issuesDir) &&
|
|
119
|
+
readdirSync(issuesDir).some(f => f.endsWith('.md'))
|
|
120
|
+
if (!hasIssues) {
|
|
121
|
+
const welcome =
|
|
122
|
+
`---\n` +
|
|
123
|
+
`title: Welcome to issy\n` +
|
|
124
|
+
`description: Your first issue in this repo\n` +
|
|
125
|
+
`priority: medium\n` +
|
|
126
|
+
`type: improvement\n` +
|
|
127
|
+
`status: open\n` +
|
|
128
|
+
`created: ${new Date().toISOString().slice(0, 19)}\n` +
|
|
129
|
+
`---\n\n` +
|
|
130
|
+
`## Details\n\n` +
|
|
131
|
+
`- This issue was created automatically on first run.\n` +
|
|
132
|
+
`- Edit it, close it, or delete it to get started.\n`
|
|
133
|
+
writeFileSync(join(issuesDir, '0001-welcome-to-issy.md'), welcome)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
console.log(`Initialized ${issuesDir}`)
|
|
138
|
+
process.exit(0)
|
|
135
139
|
}
|
|
136
140
|
|
|
137
141
|
// Start the server
|
|
138
|
-
await import(
|
|
142
|
+
await import('@miketromba/issy-app')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "issy",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "AI-native issue tracking. Markdown files in .issues/, managed by your coding assistant.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"lint": "biome check src bin"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@miketromba/issy-app": "^0.1.
|
|
38
|
-
"@miketromba/issy-core": "^0.1.
|
|
37
|
+
"@miketromba/issy-app": "^0.1.9",
|
|
38
|
+
"@miketromba/issy-core": "^0.1.9"
|
|
39
39
|
}
|
|
40
40
|
}
|