issy 0.1.7 → 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 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,113 +1,142 @@
1
1
  #!/usr/bin/env node
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";
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
- "list",
10
- "search",
11
- "read",
12
- "create",
13
- "update",
14
- "close",
15
- "help",
16
- "--help",
17
- "-h",
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
- 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;
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
+ }
37
+
38
+ /**
39
+ * Find the git repository root by walking up from the given path.
40
+ * Returns the directory containing .git, or null if not in a git repo.
41
+ */
42
+ function findGitRoot(fromPath) {
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
36
54
  }
37
55
 
38
56
  /**
39
57
  * Resolve the issues directory using priority:
40
58
  * 1. ISSUES_DIR env var (explicit override)
41
59
  * 2. Walk up from ISSUES_ROOT or cwd to find existing .issues
42
- * 3. Fall back to creating .issues in ISSUES_ROOT or cwd
60
+ * 3. If in a git repo, use .issues at the repo root
61
+ * 4. Fall back to creating .issues in ISSUES_ROOT or cwd
43
62
  */
44
63
  function resolveIssuesDir() {
45
- // 1. Explicit override
46
- if (process.env.ISSUES_DIR) {
47
- return resolve(process.env.ISSUES_DIR);
48
- }
49
-
50
- // 2. Try to find existing .issues by walking up
51
- const startDir = process.env.ISSUES_ROOT || process.cwd();
52
- const found = findIssuesDirUpward(startDir);
53
- if (found) {
54
- return found;
55
- }
56
-
57
- // 3. Fall back to creating in start directory
58
- return join(resolve(startDir), ".issues");
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')
59
84
  }
60
85
 
61
- const issuesDir = resolveIssuesDir();
86
+ const issuesDir = resolveIssuesDir()
62
87
  // Set env vars for downstream consumers
63
- process.env.ISSUES_DIR = issuesDir;
88
+ process.env.ISSUES_DIR = issuesDir
64
89
  // Set ISSUES_ROOT to the parent of .issues for consistency
65
- process.env.ISSUES_ROOT = dirname(issuesDir);
90
+ process.env.ISSUES_ROOT = dirname(issuesDir)
66
91
 
67
- if (cliCommands.has(args[0] || "")) {
68
- const here = resolve(fileURLToPath(import.meta.url), "..");
69
- const entry = resolve(here, "..", "dist", "cli.js");
70
- process.argv = [process.argv[0], process.argv[1], ...args];
71
- const cli = await import(entry);
72
- await cli.ready; // Wait for main() to complete before exiting
73
- process.exit(0);
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)
74
99
  }
75
100
 
76
- const portIdx = args.findIndex((arg) => arg === "--port" || arg === "-p");
101
+ const portIdx = args.findIndex(arg => arg === '--port' || arg === '-p')
77
102
  if (portIdx >= 0 && args[portIdx + 1]) {
78
- process.env.ISSUES_PORT = args[portIdx + 1];
103
+ process.env.ISSUES_PORT = args[portIdx + 1]
79
104
  }
80
105
 
81
- const shouldInitOnly = args.includes("init");
82
- const skipSeed = args.includes("--no-seed");
83
-
84
- if (!existsSync(issuesDir)) {
85
- mkdirSync(issuesDir, { recursive: true });
86
- }
87
-
88
- if (!skipSeed) {
89
- const hasIssues =
90
- existsSync(issuesDir) && readdirSync(issuesDir).some((f) => f.endsWith(".md"));
91
- if (!hasIssues) {
92
- const welcome = `---\n` +
93
- `title: Welcome to issy\n` +
94
- `description: Your first issue in this repo\n` +
95
- `priority: medium\n` +
96
- `type: improvement\n` +
97
- `status: open\n` +
98
- `created: ${new Date().toISOString().slice(0, 19)}\n` +
99
- `---\n\n` +
100
- `## Details\n\n` +
101
- `- This issue was created automatically on first run.\n` +
102
- `- Edit it, close it, or delete it to get started.\n`;
103
- writeFileSync(join(issuesDir, "0001-welcome-to-issy.md"), welcome);
104
- }
105
- }
106
+ const shouldInitOnly = args.includes('init')
107
+ const shouldSeed = args.includes('--seed')
106
108
 
109
+ // Only create .issues directory on explicit 'init' command
107
110
  if (shouldInitOnly) {
108
- console.log(`Initialized ${issuesDir}`);
109
- process.exit(0);
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)
110
139
  }
111
140
 
112
141
  // Start the server
113
- await import("@miketromba/issy-app");
142
+ await import('@miketromba/issy-app')
package/dist/cli.js CHANGED
@@ -34,6 +34,20 @@ function findIssuesDirUpward(fromPath) {
34
34
  }
35
35
  return null;
36
36
  }
37
+ function findGitRoot(fromPath) {
38
+ let current = resolve(fromPath);
39
+ for (let i = 0;i < 20; i++) {
40
+ const gitDir = join(current, ".git");
41
+ if (existsSync(gitDir)) {
42
+ return current;
43
+ }
44
+ const parent = dirname(current);
45
+ if (parent === current)
46
+ break;
47
+ current = parent;
48
+ }
49
+ return null;
50
+ }
37
51
  function resolveIssuesDir() {
38
52
  if (process.env.ISSUES_DIR) {
39
53
  const dir = resolve(process.env.ISSUES_DIR);
@@ -46,6 +60,12 @@ function resolveIssuesDir() {
46
60
  setIssuesDir(found);
47
61
  return found;
48
62
  }
63
+ const gitRoot = findGitRoot(startDir);
64
+ if (gitRoot) {
65
+ const gitIssuesDir = join(gitRoot, ".issues");
66
+ setIssuesDir(gitIssuesDir);
67
+ return gitIssuesDir;
68
+ }
49
69
  const fallback = join(resolve(startDir), ".issues");
50
70
  setIssuesDir(fallback);
51
71
  return fallback;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "issy",
3
- "version": "0.1.7",
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.7",
38
- "@miketromba/issy-core": "^0.1.7"
37
+ "@miketromba/issy-app": "^0.1.9",
38
+ "@miketromba/issy-core": "^0.1.9"
39
39
  }
40
40
  }