tissues 0.3.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Caleb Ogden
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,353 @@
1
+ # ghissue
2
+
3
+ AI-enhanced GitHub issue creation with built-in safety guardrails.
4
+
5
+ Create structured, deduplicated GitHub issues from the command line — with circuit breakers and rate limiting to prevent runaway issue creation when used inside AI agent workflows.
6
+
7
+ [![npm version](https://img.shields.io/npm/v/ghissue)](https://www.npmjs.com/package/ghissue)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
9
+
10
+ ---
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm install -g ghissue
16
+ ```
17
+
18
+ Requires Node.js >= 18.
19
+
20
+ ---
21
+
22
+ ## Quick Start
23
+
24
+ ```bash
25
+ # Authenticate (auto-detects gh CLI token)
26
+ ghissue auth
27
+
28
+ # Set your active repo
29
+ ghissue open
30
+
31
+ # Create an issue interactively
32
+ ghissue create
33
+
34
+ # Check safety status before running in CI
35
+ ghissue status
36
+ ```
37
+
38
+ ---
39
+
40
+ ## Commands
41
+
42
+ ### `ghissue auth`
43
+
44
+ Authenticate with GitHub. Auto-detects your `gh` CLI token if you're already logged in via `gh`. Falls back to GitHub OAuth Device Flow if not.
45
+
46
+ ```bash
47
+ ghissue auth
48
+ ```
49
+
50
+ ### `ghissue open`
51
+
52
+ Set the active repository context. All subsequent commands use this repo unless overridden with `--repo`.
53
+
54
+ ```bash
55
+ ghissue open
56
+ # prompts: pick a repo from your GitHub account
57
+ ```
58
+
59
+ ### `ghissue create`
60
+
61
+ Create a new GitHub issue. Runs dedup checks and safety gates before creating anything.
62
+
63
+ ```bash
64
+ ghissue create [options]
65
+ ```
66
+
67
+ **Options:**
68
+
69
+ | Flag | Description |
70
+ |---|---|
71
+ | `--repo <owner/name>` | Override active repo |
72
+ | `--template <name>` | Template to use: `bug`, `feature`, `security`, `performance`, `refactor` |
73
+ | `--title <title>` | Issue title (skips interactive prompt) |
74
+ | `--body <text>` | Issue body / description (skips interactive prompt) |
75
+ | `--labels <labels>` | Comma-separated labels to apply |
76
+ | `--agent <name>` | Agent identifier for attribution (default: `human`) |
77
+ | `--session <id>` | Session ID for attribution and idempotency |
78
+ | `--force` | Skip dedup warnings (still blocks on exact matches) |
79
+ | `--dry-run` | Check dedup and safety without creating the issue |
80
+
81
+ **Examples:**
82
+
83
+ ```bash
84
+ # Interactive
85
+ ghissue create
86
+
87
+ # Fully scripted (no prompts)
88
+ ghissue create \
89
+ --repo owner/my-repo \
90
+ --template bug \
91
+ --title "Login fails on Safari 17" \
92
+ --body "Reproducible on fresh profile. Console shows CORS error." \
93
+ --labels "bug,P1"
94
+
95
+ # From an AI agent
96
+ ghissue create \
97
+ --agent claude-opus-4-6 \
98
+ --session abc123 \
99
+ --template feature \
100
+ --title "Add dark mode toggle" \
101
+ --dry-run
102
+ ```
103
+
104
+ ### `ghissue list`
105
+
106
+ Browse open issues for the active repo.
107
+
108
+ ```bash
109
+ ghissue list
110
+ ghissue list --repo owner/other-repo
111
+ ```
112
+
113
+ ### `ghissue status`
114
+
115
+ Show circuit breaker state and rate limit usage for the active repo.
116
+
117
+ ```bash
118
+ ghissue status
119
+ ghissue status --agent claude-opus-4-6
120
+ ghissue status --reset # force-reset circuit breaker to closed
121
+ ```
122
+
123
+ Output:
124
+
125
+ ```
126
+ Circuit Breaker: closed ✓
127
+ Rate Limit: 3/10 per hour (7 remaining)
128
+ Burst: 1/5 in last 5 min
129
+ Global: 3/30 per hour (27 remaining)
130
+
131
+ Last issue created: 12 minutes ago
132
+ Fingerprints stored: 47
133
+ ```
134
+
135
+ ---
136
+
137
+ ## Safety Features
138
+
139
+ ghissue is designed to be called from AI agent loops. The safety infrastructure prevents one misconfigured agent from flooding a repo with issues.
140
+
141
+ ### Circuit Breaker
142
+
143
+ Three-state machine (closed / half-open / open) per repo+agent pair.
144
+
145
+ - **Closed** — normal operation
146
+ - **Open** — creation blocked; cooldown in effect (default: 30 minutes)
147
+ - **Half-open** — one probe request allowed through to test recovery
148
+
149
+ The circuit trips after 3 blocked attempts (dedup blocks count as failures). If the probe succeeds, the circuit resets to closed automatically.
150
+
151
+ To inspect or reset:
152
+
153
+ ```bash
154
+ ghissue status
155
+ ghissue status --reset
156
+ ```
157
+
158
+ ### Rate Limiting
159
+
160
+ Three independent rate limits, all checked before creating:
161
+
162
+ | Limit | Default | Scope |
163
+ |---|---|---|
164
+ | Per-agent hourly | 10 issues/hour | per repo + agent |
165
+ | Per-agent burst | 5 issues / 5 minutes | per repo + agent |
166
+ | Global hourly | 30 issues/hour | per repo, all agents combined |
167
+
168
+ All limits are configurable in `.gitissues/config.json`.
169
+
170
+ ### Deduplication (3 layers)
171
+
172
+ Dedup runs cheapest-first before every `create`:
173
+
174
+ 1. **Idempotency key** (O(1) DB lookup) — blocks if the same `agent + trigger + issueType + repo` combination has already created an issue this session.
175
+ 2. **Content fingerprint** (O(1) DB lookup) — SHA-256 of normalized title + first 500 chars of body. Blocks exact re-submissions even across sessions.
176
+ 3. **Fuzzy title match** (O(n) GitHub API) — Levenshtein similarity against all open issues. Blocks at >95% similarity; warns at >80%.
177
+
178
+ A `block` always prevents creation. A `warn` prompts interactively (or is skipped with `--force`).
179
+
180
+ ### Attribution Tracking
181
+
182
+ Every issue created by ghissue includes a machine-readable `<!-- gitissues-meta -->` block. See [Attribution](#attribution) below.
183
+
184
+ ---
185
+
186
+ ## Configuration
187
+
188
+ Configuration is loaded and merged from four sources in ascending priority order:
189
+
190
+ 1. Built-in defaults
191
+ 2. User-level config: `~/.config/gitissues/config.json`
192
+ 3. Repo-level config: `.gitissues/config.json`
193
+ 4. Environment variables: `GITISSUES_*`
194
+
195
+ ### Example `.gitissues/config.json`
196
+
197
+ ```json
198
+ {
199
+ "safety": {
200
+ "maxPerHour": 5,
201
+ "burstLimit": 3,
202
+ "burstWindowMinutes": 5,
203
+ "tripThreshold": 3,
204
+ "cooldownMinutes": 30,
205
+ "globalMaxPerHour": 20
206
+ },
207
+ "dedup": {
208
+ "blockAbove": 0.95,
209
+ "warnAbove": 0.80,
210
+ "fingerprintTTLDays": 90
211
+ },
212
+ "attribution": {
213
+ "required": false,
214
+ "defaultAgent": "human"
215
+ },
216
+ "templates": {
217
+ "dir": ".gitissues/templates",
218
+ "default": "bug"
219
+ },
220
+ "hooks": {
221
+ "postCreate": "scripts/notify-slack.sh"
222
+ }
223
+ }
224
+ ```
225
+
226
+ ### Environment Variables
227
+
228
+ Environment variables follow the pattern `GITISSUES_<SECTION>_<KEY>`:
229
+
230
+ ```bash
231
+ GITISSUES_SAFETY_MAX_PER_HOUR=5
232
+ GITISSUES_SAFETY_BURST_LIMIT=3
233
+ GITISSUES_SAFETY_GLOBAL_MAX_PER_HOUR=20
234
+ GITISSUES_ATTRIBUTION_DEFAULT_AGENT=my-agent
235
+ ```
236
+
237
+ ### Hooks
238
+
239
+ `postCreate` runs a shell command after every successful issue creation. Three environment variables are injected:
240
+
241
+ ```bash
242
+ GITISSUES_REPO # owner/repo
243
+ GITISSUES_ISSUE_NUMBER # 142
244
+ GITISSUES_ISSUE_URL # https://github.com/owner/repo/issues/142
245
+ ```
246
+
247
+ ---
248
+
249
+ ## Templates
250
+
251
+ ### Built-in Templates
252
+
253
+ | Key | Description |
254
+ |---|---|
255
+ | `default` | Generic summary + details + additional context |
256
+ | `bug` | Steps to reproduce, expected vs actual behavior, environment |
257
+ | `feature` | Motivation, proposed solution, alternatives |
258
+ | `security` | Severity, affected components, suggested fix |
259
+ | `performance` | Current metric, target metric, affected area |
260
+ | `refactor` | Motivation, scope, risk assessment |
261
+
262
+ ### Custom Templates
263
+
264
+ Place `.md` files in `.gitissues/templates/` (repo-level) or `~/.config/gitissues/templates/` (user-level). Repo templates take priority over user templates, which take priority over built-ins.
265
+
266
+ Template files support `{{variable}}` substitution:
267
+
268
+ | Variable | Value |
269
+ |---|---|
270
+ | `{{title}}` | Issue title |
271
+ | `{{description}}` | User's description |
272
+ | `{{agent}}` | Agent identifier |
273
+ | `{{session}}` | Session ID |
274
+ | `{{date}}` | ISO date (YYYY-MM-DD) |
275
+ | `{{repo}}` | Repository (owner/name) |
276
+
277
+ **Example** `.gitissues/templates/incident.md`:
278
+
279
+ ```markdown
280
+ name: Incident Report
281
+
282
+ ## Incident: {{title}}
283
+
284
+ **Date:** {{date}}
285
+ **Reported by:** {{agent}}
286
+
287
+ ## What happened
288
+
289
+ {{description}}
290
+
291
+ ## Impact
292
+
293
+ ## Timeline
294
+
295
+ ## Resolution
296
+
297
+ ## Prevention
298
+ ```
299
+
300
+ Use it with `--template incident`.
301
+
302
+ ---
303
+
304
+ ## Attribution
305
+
306
+ Every issue created by ghissue includes a machine-readable HTML comment at the bottom of the body. GitHub renders it as invisible text; it does not appear in the rendered issue.
307
+
308
+ ```html
309
+ <!-- gitissues-meta
310
+ agent: claude-opus-4-6
311
+ session: abc123
312
+ pid: 84921
313
+ trigger: cli-create
314
+ fingerprint: sha256:3f2a1b...
315
+ created_at: 2026-02-19T15:30:00.000Z
316
+ created_via: ghissue-cli/0.3.0
317
+ -->
318
+ ```
319
+
320
+ The block is used by ghissue to:
321
+
322
+ - Power content fingerprint dedup (prevents re-creating identical issues)
323
+ - Track which agent created which issue
324
+ - Enable idempotency for agent workflows
325
+
326
+ You can pass additional fields via `--agent`, `--session`, and the programmatic API.
327
+
328
+ ---
329
+
330
+ ## State Database
331
+
332
+ ghissue stores local state in a SQLite database at:
333
+
334
+ ```
335
+ .gitissues/data/gitissues.db
336
+ ```
337
+
338
+ **No separate SQLite install.** The CLI uses [better-sqlite3](https://github.com/WiseLibs/better-sqlite3), which compiles SQLite into the Node addon at `npm install` time. Users do not need to install SQLite on their system. SQLite is a common choice for CLI tool state when you need queryable, structured data (rate limits, dedup history, circuit breaker); simpler config lives in JSON (e.g. `conf` store).
339
+
340
+ **What it stores:**
341
+
342
+ - `fingerprints` — SHA-256 hashes of previously created issues (for dedup layer 2)
343
+ - `rate_events` — timestamped creation events (for rate limiting)
344
+ - `circuit_breaker` — circuit state per repo+agent (status, failure count, cooldown)
345
+ - `idempotency_keys` — deterministic keys for agent-driven idempotency
346
+
347
+ The database file can be committed to share dedup history across a team, or added to `.gitignore` to keep it local. It is created automatically on first use.
348
+
349
+ ---
350
+
351
+ ## License
352
+
353
+ MIT
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ import { program } from '../src/cli.js'
3
+
4
+ try {
5
+ await program.parseAsync()
6
+ } catch (err) {
7
+ if (err.name === 'ExitPromptError') {
8
+ process.exit(0)
9
+ }
10
+ throw err
11
+ }
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "tissues",
3
+ "version": "0.3.0",
4
+ "description": "AI-enhanced GitHub issue creation with built-in safety guardrails. Wraps gh CLI with circuit breakers, rate limiting, dedup, and templates.",
5
+ "type": "module",
6
+ "bin": {
7
+ "tissues": "./bin/gitissues.js"
8
+ },
9
+ "scripts": {
10
+ "dev": "node bin/gitissues.js",
11
+ "watch": "node --watch bin/gitissues.js --help",
12
+ "test": "node --test src/**/*.test.js",
13
+ "audit": "node scripts/audit.js",
14
+ "build": "node scripts/build.js",
15
+ "prepublishOnly": "node scripts/audit.js",
16
+ "dev:install": "node scripts/dev-install.js",
17
+ "dev:uninstall": "node scripts/dev-install.js --uninstall"
18
+ },
19
+ "keywords": [
20
+ "git",
21
+ "github",
22
+ "issues",
23
+ "ai",
24
+ "cli",
25
+ "safety",
26
+ "dedup",
27
+ "circuit-breaker"
28
+ ],
29
+ "author": "Caleb Ogden",
30
+ "license": "MIT",
31
+ "homepage": "https://ghissue.dev",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/calebogden/ghissue.git"
35
+ },
36
+ "bugs": {
37
+ "url": "https://github.com/calebogden/ghissue/issues"
38
+ },
39
+ "files": [
40
+ "bin",
41
+ "src",
42
+ "README.md",
43
+ "LICENSE"
44
+ ],
45
+ "engines": {
46
+ "node": ">=18"
47
+ },
48
+ "dependencies": {
49
+ "@inquirer/prompts": "^7.0.0",
50
+ "better-sqlite3": "^11.0.0",
51
+ "chalk": "^5.3.0",
52
+ "commander": "^12.0.0",
53
+ "conf": "^13.0.0",
54
+ "ora": "^8.0.0"
55
+ }
56
+ }
package/src/cli.js ADDED
@@ -0,0 +1,64 @@
1
+ import { Command } from 'commander'
2
+ import { authCommand } from './commands/auth.js'
3
+ import { openCommand } from './commands/open.js'
4
+ import { createCommand } from './commands/create.js'
5
+ import { listCommand } from './commands/list.js'
6
+ import { statusCommand } from './commands/status.js'
7
+ import { store } from './lib/config.js'
8
+ import { requireGh, getAuthStatus } from './lib/gh.js'
9
+ import chalk from 'chalk'
10
+
11
+ export const program = new Command()
12
+
13
+ function authDescription() {
14
+ const base = 'Authenticate with GitHub (via gh CLI)'
15
+ try {
16
+ const status = getAuthStatus()
17
+ const active = status.accounts?.find(a => a.active)
18
+ if (!active) return base
19
+ const check = chalk.green('✔')
20
+ return `${base} ${check} ${chalk.dim(active.login)}`
21
+ } catch {
22
+ return base
23
+ }
24
+ }
25
+
26
+ program
27
+ .name('tissues')
28
+ .description('AI-enhanced GitHub issue creation from the command line.')
29
+ .version('0.3.0')
30
+ .hook('preAction', (_thisCommand, actionCommand) => {
31
+ const name = actionCommand.name()
32
+
33
+ // completion doesn't need gh installed
34
+ if (name === 'completion') return
35
+
36
+ // Ensure gh is installed before any command
37
+ requireGh()
38
+
39
+ // Show active repo context on every command (except auth/open)
40
+ if (name !== 'auth' && name !== 'open' && name !== 'login' && name !== 'status' && name !== 'switch' && name !== 'logout') {
41
+ const activeRepo = store.get('activeRepo')
42
+ if (activeRepo) {
43
+ console.log(chalk.dim(`Working in: ${activeRepo}\n`))
44
+ }
45
+ }
46
+ })
47
+
48
+ program.addHelpText(
49
+ 'after',
50
+ `
51
+
52
+ Repo: https://github.com/calebogden/ghissue
53
+ `,
54
+ )
55
+
56
+ program.hook('preAction', () => {
57
+ // Lazily resolve auth description only when a command actually runs
58
+ authCommand.description(authDescription())
59
+ })
60
+ program.addCommand(authCommand)
61
+ program.addCommand(openCommand)
62
+ program.addCommand(createCommand)
63
+ program.addCommand(listCommand)
64
+ program.addCommand(statusCommand)
@@ -0,0 +1,62 @@
1
+ import { Command } from 'commander'
2
+ import {
3
+ requireGh,
4
+ checkScopes,
5
+ authLogin,
6
+ authStatus,
7
+ authSwitch,
8
+ authLogout,
9
+ } from '../lib/gh.js'
10
+ import chalk from 'chalk'
11
+
12
+ export const authCommand = new Command('auth')
13
+ .description('Authenticate with GitHub (via gh CLI)')
14
+ .action(function () {
15
+ this.help()
16
+ })
17
+
18
+ // Subcommands
19
+ authCommand.addCommand(
20
+ new Command('login')
21
+ .description('Log in to GitHub (opens browser)')
22
+ .action(() => {
23
+ requireGh()
24
+ authLogin()
25
+ }),
26
+ )
27
+
28
+ authCommand.addCommand(
29
+ new Command('status')
30
+ .description('Show auth status, accounts, and scopes')
31
+ .action(() => {
32
+ requireGh()
33
+ authStatus()
34
+
35
+ const { ok, missing } = checkScopes()
36
+ if (!ok) {
37
+ console.log()
38
+ console.log(chalk.yellow('⚠ Missing required scope: ') + chalk.bold(missing.join(', ')))
39
+ console.log(chalk.dim(' Fix with: ') + chalk.cyan(`gh auth refresh -s ${missing.join(',')}`))
40
+ } else {
41
+ console.log(chalk.green('\n✓ Token has required scopes for issue creation'))
42
+ }
43
+ }),
44
+ )
45
+
46
+ authCommand.addCommand(
47
+ new Command('switch')
48
+ .description('Switch active GitHub account')
49
+ .action(() => {
50
+ requireGh()
51
+ authSwitch()
52
+ }),
53
+ )
54
+
55
+ authCommand.addCommand(
56
+ new Command('logout')
57
+ .description('Log out of GitHub')
58
+ .action(() => {
59
+ requireGh()
60
+ authLogout()
61
+ }),
62
+ )