tissues 0.5.0 → 0.5.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 +21 -15
- package/package.json +1 -1
- package/src/cli.js +3 -3
- package/src/commands/create.js +25 -8
- package/src/commands/status.js +1 -1
- package/src/commands/{open.js → use.js} +1 -1
- package/src/lib/attribution.js +7 -7
- package/src/lib/db.js +19 -0
- package/src/lib/safety.js +3 -4
package/README.md
CHANGED
|
@@ -32,11 +32,11 @@ Requires Node.js >= 18.
|
|
|
32
32
|
# Authenticate (auto-detects gh CLI token)
|
|
33
33
|
tissues auth
|
|
34
34
|
|
|
35
|
-
# Set your active repo
|
|
36
|
-
tissues
|
|
35
|
+
# Set your active repo (optional — saves typing --repo every time)
|
|
36
|
+
tissues use calebogden/tissues
|
|
37
37
|
|
|
38
|
-
# Create an issue interactively
|
|
39
|
-
tissues create
|
|
38
|
+
# Create an issue interactively (title as positional argument — no quotes needed)
|
|
39
|
+
tissues create fix the login bug
|
|
40
40
|
|
|
41
41
|
# Check safety status before running in CI
|
|
42
42
|
tissues status
|
|
@@ -54,13 +54,14 @@ Authenticate with GitHub. Auto-detects your `gh` CLI token if you're already log
|
|
|
54
54
|
tissues auth
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
### `tissues
|
|
57
|
+
### `tissues use`
|
|
58
58
|
|
|
59
|
-
Set the active repository context.
|
|
59
|
+
Set the active repository context. Optional — all commands also accept `--repo` directly. Once set, subsequent commands use this repo automatically unless overridden.
|
|
60
60
|
|
|
61
61
|
```bash
|
|
62
|
-
tissues
|
|
63
|
-
# prompts: pick a repo from your GitHub account
|
|
62
|
+
tissues use calebogden/tissues # set by name
|
|
63
|
+
tissues use # prompts: pick a repo from your GitHub account
|
|
64
|
+
tissues use --repo calebogden/tissues # same, via flag
|
|
64
65
|
```
|
|
65
66
|
|
|
66
67
|
### `tissues create`
|
|
@@ -68,7 +69,7 @@ tissues open
|
|
|
68
69
|
Create a new GitHub issue. Runs dedup checks and safety gates before creating anything.
|
|
69
70
|
|
|
70
71
|
```bash
|
|
71
|
-
tissues create [options]
|
|
72
|
+
tissues create [title...] [options]
|
|
72
73
|
```
|
|
73
74
|
|
|
74
75
|
**Options:**
|
|
@@ -78,7 +79,10 @@ tissues create [options]
|
|
|
78
79
|
| `--repo <owner/name>` | Override active repo |
|
|
79
80
|
| `--template <name>` | Template to use: `bug`, `feature`, `security`, `performance`, `refactor` |
|
|
80
81
|
| `--title <title>` | Issue title (skips interactive prompt) |
|
|
81
|
-
| `--body <text>` | Issue body / description (skips interactive prompt) |
|
|
82
|
+
| `--body <text>` | Issue body / description — the actual content describing the issue (skips interactive prompt) |
|
|
83
|
+
| `--instructions <text>` | AI enhancement instructions: guides how the AI writes the issue body (e.g. "keep it under 200 words", "use formal tone"). Does not appear in the created issue. Optional — skips interactive prompt when provided. |
|
|
84
|
+
| `--no-enhance` | Skip AI enhancement, use rendered template as-is |
|
|
85
|
+
| `--batch <file>` | Create multiple issues from a JSON file. Each item supports: `title`, `body`, `template`, `labels`, `agent`, `session` |
|
|
82
86
|
| `--labels <labels>` | Comma-separated labels to apply |
|
|
83
87
|
| `--agent <name>` | Agent identifier for attribution (default: `human`) |
|
|
84
88
|
| `--session <id>` | Session ID for attribution and idempotency |
|
|
@@ -88,8 +92,8 @@ tissues create [options]
|
|
|
88
92
|
**Examples:**
|
|
89
93
|
|
|
90
94
|
```bash
|
|
91
|
-
# Interactive
|
|
92
|
-
tissues create
|
|
95
|
+
# Interactive (title pre-filled from positional argument)
|
|
96
|
+
tissues create fix the login bug
|
|
93
97
|
|
|
94
98
|
# Fully scripted (no prompts)
|
|
95
99
|
tissues create \
|
|
@@ -97,6 +101,7 @@ tissues create \
|
|
|
97
101
|
--template bug \
|
|
98
102
|
--title "Login fails on Safari 17" \
|
|
99
103
|
--body "Reproducible on fresh profile. Console shows CORS error." \
|
|
104
|
+
--instructions "Keep it under 150 words and include a clear reproduction checklist." \
|
|
100
105
|
--labels "bug,P1"
|
|
101
106
|
|
|
102
107
|
# From an AI agent
|
|
@@ -279,7 +284,7 @@ Template files support `{{variable}}` substitution:
|
|
|
279
284
|
| Variable | Value |
|
|
280
285
|
|---|---|
|
|
281
286
|
| `{{title}}` | Issue title |
|
|
282
|
-
| `{{description}}` |
|
|
287
|
+
| `{{description}}` | Issue description — the actual content describing the issue |
|
|
283
288
|
| `{{agent}}` | Agent identifier |
|
|
284
289
|
| `{{session}}` | Session ID |
|
|
285
290
|
| `{{date}}` | ISO date (YYYY-MM-DD) |
|
|
@@ -320,11 +325,10 @@ Every issue created by tissues includes a machine-readable HTML comment at the b
|
|
|
320
325
|
<!-- tissues-meta
|
|
321
326
|
agent: claude-opus-4-6
|
|
322
327
|
session: abc123
|
|
323
|
-
pid: 84921
|
|
324
328
|
trigger: cli-create
|
|
325
329
|
fingerprint: sha256:3f2a1b...
|
|
326
330
|
created_at: 2026-02-19T15:30:00.000Z
|
|
327
|
-
created_via: tissues-cli/0.
|
|
331
|
+
created_via: tissues-cli/0.5
|
|
328
332
|
-->
|
|
329
333
|
```
|
|
330
334
|
|
|
@@ -336,6 +340,8 @@ The block is used by tissues to:
|
|
|
336
340
|
|
|
337
341
|
You can pass additional fields via `--agent`, `--session`, and the programmatic API.
|
|
338
342
|
|
|
343
|
+
`pid` is available but opt-in — pass it explicitly via the programmatic API (`pid: process.pid`). It is omitted by default to avoid leaking process info into public issues.
|
|
344
|
+
|
|
339
345
|
---
|
|
340
346
|
|
|
341
347
|
## State Database
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createRequire } from 'module'
|
|
|
3
3
|
const { version } = createRequire(import.meta.url)('../package.json')
|
|
4
4
|
import { authCommand } from './commands/auth.js'
|
|
5
5
|
import { configCommand } from './commands/config.js'
|
|
6
|
-
import {
|
|
6
|
+
import { useCommand } from './commands/use.js'
|
|
7
7
|
import { createCommand } from './commands/create.js'
|
|
8
8
|
import { listCommand } from './commands/list.js'
|
|
9
9
|
import { statusCommand } from './commands/status.js'
|
|
@@ -40,7 +40,7 @@ program
|
|
|
40
40
|
requireGh()
|
|
41
41
|
|
|
42
42
|
// Show active repo context on every command (except auth/open)
|
|
43
|
-
if (name !== 'auth' && name !== '
|
|
43
|
+
if (name !== 'auth' && name !== 'use' && name !== 'login' && name !== 'status' && name !== 'switch' && name !== 'logout') {
|
|
44
44
|
const activeRepo = store.get('activeRepo')
|
|
45
45
|
if (activeRepo) {
|
|
46
46
|
console.log(chalk.dim(`Working in: ${activeRepo}\n`))
|
|
@@ -63,7 +63,7 @@ program.hook('preAction', () => {
|
|
|
63
63
|
})
|
|
64
64
|
program.addCommand(authCommand)
|
|
65
65
|
program.addCommand(configCommand)
|
|
66
|
-
program.addCommand(
|
|
66
|
+
program.addCommand(useCommand)
|
|
67
67
|
program.addCommand(createCommand)
|
|
68
68
|
program.addCommand(listCommand)
|
|
69
69
|
program.addCommand(statusCommand)
|
package/src/commands/create.js
CHANGED
|
@@ -88,14 +88,19 @@ function runPostCreateHook(hookCmd, ctx) {
|
|
|
88
88
|
* Enhance issue body with AI. Currently a structured placeholder.
|
|
89
89
|
* Wire up real AI (OpenAI / Anthropic) here.
|
|
90
90
|
*
|
|
91
|
-
* @param {string} title
|
|
92
|
-
* @param {string} description
|
|
91
|
+
* @param {string} title - issue title
|
|
92
|
+
* @param {string} description - the actual issue content describing what the issue is about;
|
|
93
|
+
* this is rendered into the template body via {{description}} and forms the core of the issue
|
|
93
94
|
* @param {string} templateBody - already-rendered template body to use as context
|
|
95
|
+
* @param {string} [instructions] - optional AI prompt instructions that guide *how* the AI
|
|
96
|
+
* writes or post-processes the issue body (e.g. "keep it under 200 words",
|
|
97
|
+
* "after creating, send to this webhook: https://..."). Does NOT appear in the issue itself.
|
|
94
98
|
* @returns {Promise<string>}
|
|
95
99
|
*/
|
|
96
|
-
async function enhanceWithAI(title, description, templateBody) {
|
|
100
|
+
async function enhanceWithAI(title, description, templateBody, instructions) {
|
|
97
101
|
// TODO: wire up real AI (OpenAI / Anthropic)
|
|
98
|
-
//
|
|
102
|
+
// - description goes INTO the issue body (rendered via {{description}} in the template)
|
|
103
|
+
// - instructions guide the AI behaviour but are NOT included in the output
|
|
99
104
|
return templateBody
|
|
100
105
|
}
|
|
101
106
|
|
|
@@ -164,7 +169,8 @@ async function runCreate(opts) {
|
|
|
164
169
|
// -----------------------------------------------------------------------
|
|
165
170
|
// 4. Get inputs
|
|
166
171
|
// -----------------------------------------------------------------------
|
|
167
|
-
const title =
|
|
172
|
+
const title =
|
|
173
|
+
opts.title ?? (await input({ message: 'Issue title', default: opts._titleDefault }))
|
|
168
174
|
if (!title || !title.trim()) {
|
|
169
175
|
console.error(chalk.red('Title is required.'))
|
|
170
176
|
throw new Error('Title is required')
|
|
@@ -173,7 +179,13 @@ async function runCreate(opts) {
|
|
|
173
179
|
const description =
|
|
174
180
|
opts.body ??
|
|
175
181
|
(await input({
|
|
176
|
-
message: '
|
|
182
|
+
message: 'Issue description (optional)',
|
|
183
|
+
}))
|
|
184
|
+
|
|
185
|
+
const instructions =
|
|
186
|
+
opts.instructions ??
|
|
187
|
+
(await input({
|
|
188
|
+
message: 'Issue instruction (optional, guides AI enhancement)',
|
|
177
189
|
}))
|
|
178
190
|
|
|
179
191
|
// -----------------------------------------------------------------------
|
|
@@ -286,7 +298,7 @@ async function runCreate(opts) {
|
|
|
286
298
|
if (opts.enhance !== false && !opts.dryRun) {
|
|
287
299
|
const aiSpinner = ora('Enhancing with AI...').start()
|
|
288
300
|
try {
|
|
289
|
-
body = await enhanceWithAI(title, description, renderedTemplate)
|
|
301
|
+
body = await enhanceWithAI(title, description, renderedTemplate, instructions)
|
|
290
302
|
aiSpinner.succeed('Enhanced')
|
|
291
303
|
} catch {
|
|
292
304
|
aiSpinner.warn('AI enhancement unavailable — using template as-is')
|
|
@@ -427,12 +439,14 @@ async function runCreate(opts) {
|
|
|
427
439
|
|
|
428
440
|
export const createCommand = new Command('create')
|
|
429
441
|
.description('Create a new GitHub issue')
|
|
442
|
+
.argument('[title...]', 'Issue title (positional shorthand for --title)')
|
|
430
443
|
.option('--repo <repo>', 'Repository override (owner/name)')
|
|
431
444
|
.option('--template <name>', 'Template to use (bug, feature, security, performance, refactor)')
|
|
432
445
|
.option('--agent <name>', 'Agent identifier for attribution (default: human)')
|
|
433
446
|
.option('--session <id>', 'Session ID for attribution')
|
|
434
447
|
.option('--title <title>', 'Issue title (skips interactive prompt)')
|
|
435
448
|
.option('--body <body>', 'Issue body / description (skips interactive prompt)')
|
|
449
|
+
.option('--instructions <text>', 'AI enhancement instruction — a prompt that guides how the AI writes the issue body (skips interactive prompt)')
|
|
436
450
|
.option('--labels <labels>', 'Comma-separated labels to apply')
|
|
437
451
|
.option('--force', 'Skip dedup warnings (still blocks on exact matches)')
|
|
438
452
|
.option('--dry-run', 'Check dedup and safety without creating the issue')
|
|
@@ -441,7 +455,10 @@ export const createCommand = new Command('create')
|
|
|
441
455
|
'--batch <file>',
|
|
442
456
|
'Create multiple issues from a JSON file (title, body, template, labels, agent, session per item)',
|
|
443
457
|
)
|
|
444
|
-
.action(async (opts) => {
|
|
458
|
+
.action(async (titleArg, opts) => {
|
|
459
|
+
// Positional words → prefill the prompt (user can still edit)
|
|
460
|
+
// --title flag → skip the prompt entirely (scripted/batch use)
|
|
461
|
+
if (titleArg?.length && !opts.title) opts._titleDefault = titleArg.join(' ')
|
|
445
462
|
// -----------------------------------------------------------------------
|
|
446
463
|
// Batch mode
|
|
447
464
|
// -----------------------------------------------------------------------
|
package/src/commands/status.js
CHANGED
|
@@ -83,7 +83,7 @@ export const statusCommand = new Command('status')
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
if (!repo) {
|
|
86
|
-
console.error(chalk.red('No active repo. Set one with: tissues
|
|
86
|
+
console.error(chalk.red('No active repo. Set one with: tissues use <owner/repo>'))
|
|
87
87
|
process.exit(1)
|
|
88
88
|
}
|
|
89
89
|
|
|
@@ -3,7 +3,7 @@ import { pickRepo } from '../lib/repo-picker.js'
|
|
|
3
3
|
import { setConfig } from '../lib/config.js'
|
|
4
4
|
import chalk from 'chalk'
|
|
5
5
|
|
|
6
|
-
export const
|
|
6
|
+
export const useCommand = new Command('use')
|
|
7
7
|
.description('Set the active repository context')
|
|
8
8
|
.argument('[repo]', 'Repository in owner/name format (e.g. owner/repo)')
|
|
9
9
|
.option('--repo <repo>', 'Repository in owner/name format (alias for positional argument)')
|
package/src/lib/attribution.js
CHANGED
|
@@ -56,7 +56,7 @@ function nowISO() {
|
|
|
56
56
|
* @typedef {object} AttributionOpts
|
|
57
57
|
* @property {string} [agent] - agent identifier (e.g. 'claude-opus-4-6')
|
|
58
58
|
* @property {string} [session] - session or conversation ID
|
|
59
|
-
* @property {number} [pid] - process ID
|
|
59
|
+
* @property {number} [pid] - process ID (opt-in; omitted by default)
|
|
60
60
|
* @property {string} [model] - AI model used (if any)
|
|
61
61
|
* @property {string} [trigger] - how the issue was created (e.g. 'cli-create')
|
|
62
62
|
* @property {string} [fingerprint] - content fingerprint (sha256:...)
|
|
@@ -72,7 +72,8 @@ function nowISO() {
|
|
|
72
72
|
* Build a normalized attribution metadata object from raw options.
|
|
73
73
|
*
|
|
74
74
|
* The returned object includes all provided fields plus automatic defaults
|
|
75
|
-
* (`created_at`, `created_via
|
|
75
|
+
* (`created_at`, `created_via`). Undefined/null fields are omitted.
|
|
76
|
+
* `pid` is opt-in — it is only included if explicitly passed.
|
|
76
77
|
*
|
|
77
78
|
* @param {AttributionOpts} opts
|
|
78
79
|
* @returns {object} metadata record
|
|
@@ -100,7 +101,7 @@ export function buildAttribution(opts = {}) {
|
|
|
100
101
|
if (session != null) meta.session = String(session)
|
|
101
102
|
if (model != null) meta.model = String(model)
|
|
102
103
|
|
|
103
|
-
// Process info
|
|
104
|
+
// Process info (pid is opt-in — pass it explicitly to include)
|
|
104
105
|
if (pid != null) meta.pid = Number(pid)
|
|
105
106
|
meta.trigger = trigger != null ? String(trigger) : 'cli-create'
|
|
106
107
|
|
|
@@ -120,7 +121,7 @@ export function buildAttribution(opts = {}) {
|
|
|
120
121
|
|
|
121
122
|
// Timestamps / versioning
|
|
122
123
|
meta.created_at = createdAt ?? nowISO()
|
|
123
|
-
meta.created_via = `tissues-cli/${PKG_VERSION}`
|
|
124
|
+
meta.created_via = `tissues-cli/${PKG_VERSION.split('.').slice(0, 2).join('.')}`
|
|
124
125
|
|
|
125
126
|
return meta
|
|
126
127
|
}
|
|
@@ -137,11 +138,10 @@ export function buildAttribution(opts = {}) {
|
|
|
137
138
|
* <!-- tissues-meta
|
|
138
139
|
* agent: claude-opus-4-6
|
|
139
140
|
* session: abc123
|
|
140
|
-
* pid: 12345
|
|
141
141
|
* trigger: cli-create
|
|
142
142
|
* fingerprint: sha256:deadbeef
|
|
143
143
|
* created_at: 2026-02-19T15:30:00Z
|
|
144
|
-
* created_via: tissues-cli/0.1
|
|
144
|
+
* created_via: tissues-cli/0.1
|
|
145
145
|
* -->
|
|
146
146
|
*
|
|
147
147
|
* @param {AttributionOpts} opts
|
|
@@ -170,7 +170,7 @@ export function renderAttribution(opts = {}) {
|
|
|
170
170
|
* Returns the parsed key/value pairs as a plain object, or `null` if no
|
|
171
171
|
* attribution block is present in `issueBody`.
|
|
172
172
|
*
|
|
173
|
-
* Numeric fields (`
|
|
173
|
+
* Numeric fields (`risk`, `complexity`, `confidence`) are coerced to
|
|
174
174
|
* numbers. The `context_tags` field is split back into an array.
|
|
175
175
|
*
|
|
176
176
|
* @param {string} issueBody - raw GitHub issue body markdown
|
package/src/lib/db.js
CHANGED
|
@@ -277,6 +277,25 @@ export function getCircuitState(repo, agent = 'human') {
|
|
|
277
277
|
}
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
+
/**
|
|
281
|
+
* Increment the failure count for a circuit without opening it.
|
|
282
|
+
* Used to persist intermediate failures before the trip threshold is reached.
|
|
283
|
+
*
|
|
284
|
+
* @param {string} repo
|
|
285
|
+
* @param {string} [agent='human']
|
|
286
|
+
*/
|
|
287
|
+
export function incrementFailureCount(repo, agent = 'human') {
|
|
288
|
+
const db = getDb()
|
|
289
|
+
const id = `${repo}:${agent}`
|
|
290
|
+
db.prepare(
|
|
291
|
+
`INSERT INTO circuit_breaker (id, repo, agent, status, failure_count, updated_at)
|
|
292
|
+
VALUES (?, ?, ?, 'closed', 1, datetime('now'))
|
|
293
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
294
|
+
failure_count = failure_count + 1,
|
|
295
|
+
updated_at = datetime('now')`,
|
|
296
|
+
).run(id, repo, agent)
|
|
297
|
+
}
|
|
298
|
+
|
|
280
299
|
/**
|
|
281
300
|
* Trip (open) the circuit breaker and set a cooldown window.
|
|
282
301
|
*
|
package/src/lib/safety.js
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
probeCircuit,
|
|
17
17
|
recordRateEvent,
|
|
18
18
|
countRecentEvents,
|
|
19
|
+
incrementFailureCount,
|
|
19
20
|
getDb,
|
|
20
21
|
} from './db.js'
|
|
21
22
|
|
|
@@ -206,10 +207,8 @@ export function recordFailure(repo, agent, config = {}) {
|
|
|
206
207
|
`Cooldown: ${cfg.cooldownMinutes} minutes.`
|
|
207
208
|
)
|
|
208
209
|
} else {
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
// is persisted across calls.
|
|
212
|
-
recordRateEvent(repo, agent, 'failure')
|
|
210
|
+
// Persist intermediate failure count without opening the circuit yet
|
|
211
|
+
incrementFailureCount(repo, agent)
|
|
213
212
|
}
|
|
214
213
|
}
|
|
215
214
|
|