ticket-to-pr 1.2.0 → 1.2.2

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
@@ -289,7 +289,7 @@ Environment:
289
289
  ○ LICENSE_KEY Free tier
290
290
 
291
291
  Models:
292
- ✓ Review model claude-sonnet-4-5-20250929
292
+ ✓ Review model claude-sonnet-4-6
293
293
  ✓ Execute model claude-opus-4-6
294
294
 
295
295
  Notion:
@@ -418,7 +418,8 @@ The review agent explores your codebase without modifying anything:
418
418
  The execute agent implements the code based on the spec:
419
419
 
420
420
  - **Tools**: Read, Glob, Grep, Edit, Write + limited Bash (git, build, test only)
421
- - **Cannot**: push, run destructive commands, modify databases, access the web
421
+ - **Dev access** (opt-in): When `devAccess` is enabled, additionally allows `npx tsx`, `node`, `npm run`, `npx vitest`, `npx jest`, `npx prisma`, `python`, and `curl` to localhost/127.0.0.1 only
422
+ - **Cannot**: push, run destructive commands, modify databases, access the web, curl external hosts
422
423
  - **Context**: Reads your project's `CLAUDE.md` for conventions and rules
423
424
  - **Budget**: $15.00 max, 50 turns max
424
425
  - **Typical cost**: $0.20 - $2.00
@@ -496,6 +497,8 @@ Project configuration in `projects.json`:
496
497
  | `projects.<name>.baseBranch` | Optional base branch (e.g. `develop`). Falls back to auto-detected default (`main`/`master`). |
497
498
  | `projects.<name>.blockedFiles` | Optional array of glob patterns the agent must never touch (e.g. `["**/migrations/**", "**/*.sql"]`) |
498
499
  | `projects.<name>.skipPR` | Optional boolean. Set `true` to push the branch but skip automatic PR creation. |
500
+ | `projects.<name>.devAccess` | Optional boolean. Set `true` to let the execute agent run scripts, query DBs, and hit local endpoints. |
501
+ | `projects.<name>.envFile` | Optional env file path relative to project directory (e.g. `.env.local`). Loaded into the agent's environment when set. |
499
502
 
500
503
  ## Project Structure
501
504
 
package/dist/cli.js CHANGED
@@ -624,7 +624,7 @@ export async function runInit() {
624
624
  console.log(` ${DIM}Choose which Claude model each agent uses.${RESET}`);
625
625
  console.log(` ${DIM}Sonnet = fast/cheap, Opus = best quality, Haiku = fastest/cheapest${RESET}\n`);
626
626
  const modelChoices = [
627
- { label: 'sonnet', id: 'claude-sonnet-4-5-20250929' },
627
+ { label: 'sonnet', id: 'claude-sonnet-4-6' },
628
628
  { label: 'opus', id: 'claude-opus-4-6' },
629
629
  { label: 'haiku', id: 'claude-haiku-4-5-20251001' },
630
630
  ];
@@ -695,7 +695,15 @@ export async function runInit() {
695
695
  : undefined;
696
696
  const skipPRInput = await ask(rl, 'Skip automatic PR creation?', { defaultValue: 'N' });
697
697
  const skipPR = skipPRInput.toLowerCase() === 'y' || skipPRInput.toLowerCase() === 'yes' ? true : undefined;
698
- projects.push({ name, dir, buildCmd: buildCmd || undefined, baseBranch, blockedFiles, skipPR });
698
+ const devAccessInput = await ask(rl, 'Enable dev access (run scripts, query DB, hit endpoints)?', { defaultValue: 'N' });
699
+ const devAccessEnabled = devAccessInput.toLowerCase() === 'y' || devAccessInput.toLowerCase() === 'yes';
700
+ let envFile;
701
+ if (devAccessEnabled) {
702
+ const envCandidates = ['.env.local', '.env.development', '.env'];
703
+ const detected = envCandidates.find(f => existsSync(join(dir, f)));
704
+ envFile = await ask(rl, 'Env file to load', { defaultValue: detected }) || undefined;
705
+ }
706
+ projects.push({ name, dir, buildCmd: buildCmd || undefined, baseBranch, blockedFiles, skipPR, devAccess: devAccessEnabled || undefined, envFile });
699
707
  // Offer to generate CLAUDE.md if it doesn't exist
700
708
  const claudeMdPath = join(dir, 'CLAUDE.md');
701
709
  if (!existsSync(claudeMdPath)) {
package/dist/config.js CHANGED
@@ -48,7 +48,7 @@ export const CONFIG = {
48
48
  EXECUTE_BUDGET_USD: 15.00,
49
49
  // Agent models (env override → default)
50
50
  get REVIEW_MODEL() {
51
- return process.env.REVIEW_MODEL || 'claude-sonnet-4-5-20250929';
51
+ return process.env.REVIEW_MODEL || 'claude-sonnet-4-6';
52
52
  },
53
53
  get EXECUTE_MODEL() {
54
54
  return process.env.EXECUTE_MODEL || 'claude-opus-4-6';
package/dist/index.js CHANGED
@@ -3,8 +3,8 @@ import { execSync } from 'node:child_process';
3
3
  import { join } from 'node:path';
4
4
  import { query } from '@anthropic-ai/claude-agent-sdk';
5
5
  import { CONFIG, REVIEW_OUTPUT_SCHEMA, isPro } from './config.js';
6
- import { sleep, clamp, extractJsonFromOutput, shellEscape, extractNumber, loadEnv, createWorktree, removeWorktree, getDefaultBranch, validateNoBlockedFiles } from './lib/utils.js';
7
- import { getProjectDir, getProjectNames, getBuildCommand, getBaseBranch, getBlockedFiles, getSkipPR } from './lib/projects.js';
6
+ import { sleep, clamp, extractJsonFromOutput, shellEscape, extractNumber, loadEnv, parseEnvFile, createWorktree, removeWorktree, getDefaultBranch, validateNoBlockedFiles } from './lib/utils.js';
7
+ import { getProjectDir, getProjectNames, getBuildCommand, getBaseBranch, getBlockedFiles, getSkipPR, getDevAccess, getEnvFile } from './lib/projects.js';
8
8
  import { fetchTicketsByStatus, fetchTicketDetails, writeReviewResults, writeExecutionResults, moveTicketStatus, writeFailure, addComment, } from './lib/notion.js';
9
9
  import { PACKAGE_ROOT, CONFIG_DIR } from './lib/paths.js';
10
10
  // Load .env.local from the user's working directory
@@ -166,6 +166,8 @@ async function runExecuteAgent(ticket) {
166
166
  const baseBranch = getBaseBranch(ticket.project) || getDefaultBranch(projectDir);
167
167
  const blockedFiles = getBlockedFiles(ticket.project);
168
168
  const skipPR = getSkipPR(ticket.project);
169
+ const devAccess = getDevAccess(ticket.project);
170
+ const envFile = getEnvFile(ticket.project);
169
171
  log(MAGENTA, 'EXECUTE', `Starting execution for "${ticket.title}" on branch ${branchName}`);
170
172
  const startTime = Date.now();
171
173
  // Move to In Progress immediately
@@ -196,18 +198,41 @@ async function runExecuteAgent(ticket) {
196
198
  if (blockedFiles.length > 0) {
197
199
  promptParts.push('', '## BLOCKED FILES — DO NOT TOUCH', 'The following file patterns are off-limits. Do NOT create, modify, or delete any files matching these patterns. Violations will cause the entire run to fail.', '', ...blockedFiles.map((p) => `- \`${p}\``));
198
200
  }
201
+ if (devAccess) {
202
+ promptParts.push('', '## DEV ENVIRONMENT ACCESS', 'You have access to run scripts and dev tools in this project. Use this to:', '- Write and run scripts to understand database schema or existing data', '- Hit local API endpoints with curl to understand response shapes', '- Run tests to verify your implementation', '- Use ORM tools (e.g. `npx prisma studio`) to inspect the data model', '', '### Rules', '- Do NOT run database migrations (`prisma migrate`, `db push`, `alembic`, etc.)', '- Do NOT drop, truncate, or bulk-delete data', '- Do NOT make requests to external/production hosts — only localhost and 127.0.0.1', '- Clean up any temporary scripts you create before your final commit', '- If you create test data, document it in a commit message so reviewers know');
203
+ }
199
204
  const prompt = promptParts.join('\n');
205
+ // Build agent environment when envFile is configured
206
+ let agentEnv;
207
+ if (envFile) {
208
+ const projectEnv = parseEnvFile(join(projectDir, envFile));
209
+ agentEnv = { ...process.env, ...projectEnv };
210
+ }
211
+ const baseTools = [
212
+ 'Read', 'Glob', 'Grep', 'Edit', 'Write', 'Task',
213
+ 'Bash(git add:*)', 'Bash(git commit:*)', 'Bash(git status:*)',
214
+ 'Bash(git diff:*)', 'Bash(git log:*)',
215
+ 'Bash(npm run build:*)', 'Bash(npm test:*)', 'Bash(npx tsc:*)',
216
+ ];
217
+ const devTools = [
218
+ 'Bash(npx tsx:*)',
219
+ 'Bash(node:*)',
220
+ 'Bash(npm run:*)',
221
+ 'Bash(npx vitest:*)',
222
+ 'Bash(npx jest:*)',
223
+ 'Bash(npx prisma:*)',
224
+ 'Bash(python:*)',
225
+ 'Bash(curl http://localhost:*)',
226
+ 'Bash(curl http://127.0.0.1:*)',
227
+ ];
228
+ const allowedTools = devAccess ? [...baseTools, ...devTools] : baseTools;
200
229
  const messages = query({
201
230
  prompt,
202
231
  options: {
203
232
  model: CONFIG.EXECUTE_MODEL,
204
233
  cwd: worktreeDir,
205
- allowedTools: [
206
- 'Read', 'Glob', 'Grep', 'Edit', 'Write', 'Task',
207
- 'Bash(git add:*)', 'Bash(git commit:*)', 'Bash(git status:*)',
208
- 'Bash(git diff:*)', 'Bash(git log:*)',
209
- 'Bash(npm run build:*)', 'Bash(npm test:*)', 'Bash(npx tsc:*)',
210
- ],
234
+ allowedTools,
235
+ env: agentEnv,
211
236
  disallowedTools: ['WebFetch', 'WebSearch'],
212
237
  maxTurns: CONFIG.EXECUTE_MAX_TURNS,
213
238
  maxBudgetUsd: CONFIG.EXECUTE_BUDGET_USD,
@@ -4,6 +4,8 @@ export declare function getBuildCommand(name: string): string | undefined;
4
4
  export declare function getBaseBranch(name: string): string | undefined;
5
5
  export declare function getBlockedFiles(name: string): string[];
6
6
  export declare function getSkipPR(name: string): boolean;
7
+ export declare function getDevAccess(name: string): boolean;
8
+ export declare function getEnvFile(name: string): string | undefined;
7
9
  export declare function getAllProjects(): Record<string, string>;
8
10
  /** Reset the in-memory cache (for tests). */
9
11
  export declare function _resetCache(): void;
@@ -33,6 +33,12 @@ export function getBlockedFiles(name) {
33
33
  export function getSkipPR(name) {
34
34
  return load().projects[name]?.skipPR ?? false;
35
35
  }
36
+ export function getDevAccess(name) {
37
+ return load().projects[name]?.devAccess ?? false;
38
+ }
39
+ export function getEnvFile(name) {
40
+ return load().projects[name]?.envFile;
41
+ }
36
42
  export function getAllProjects() {
37
43
  const data = load();
38
44
  const result = {};
@@ -5,6 +5,7 @@ export declare function shellEscape(str: string): string;
5
5
  export declare function extractNumber(ticket: {
6
6
  impact?: string;
7
7
  }, field: string): string;
8
+ export declare function parseEnvFile(filepath: string): Record<string, string>;
8
9
  export declare function loadEnv(filepath: string): void;
9
10
  export declare function mask(str: string): string;
10
11
  export declare function writeEnvFile(filepath: string, updates: Record<string, string>): void;
@@ -15,6 +16,8 @@ export declare function updateProjectsFile(filepath: string, projects: Array<{
15
16
  baseBranch?: string;
16
17
  blockedFiles?: string[];
17
18
  skipPR?: boolean;
19
+ devAccess?: boolean;
20
+ envFile?: string;
18
21
  }>): void;
19
22
  export declare function getDefaultBranch(projectDir: string): string;
20
23
  /** Reset the default branch cache (for tests). */
package/dist/lib/utils.js CHANGED
@@ -61,6 +61,23 @@ export function extractNumber(ticket, field) {
61
61
  }
62
62
  return '?';
63
63
  }
64
+ export function parseEnvFile(filepath) {
65
+ const vars = {};
66
+ try {
67
+ const content = readFileSync(filepath, 'utf-8');
68
+ for (const line of content.split('\n')) {
69
+ const trimmed = line.trim();
70
+ if (!trimmed || trimmed.startsWith('#'))
71
+ continue;
72
+ const eqIndex = trimmed.indexOf('=');
73
+ if (eqIndex === -1)
74
+ continue;
75
+ vars[trimmed.slice(0, eqIndex).trim()] = trimmed.slice(eqIndex + 1).trim();
76
+ }
77
+ }
78
+ catch { /* file doesn't exist */ }
79
+ return vars;
80
+ }
64
81
  export function loadEnv(filepath) {
65
82
  try {
66
83
  const content = readFileSync(filepath, 'utf-8');
@@ -136,6 +153,8 @@ export function updateProjectsFile(filepath, projects) {
136
153
  ...(proj.baseBranch ? { baseBranch: proj.baseBranch } : {}),
137
154
  ...(proj.blockedFiles && proj.blockedFiles.length > 0 ? { blockedFiles: proj.blockedFiles } : {}),
138
155
  ...(proj.skipPR ? { skipPR: proj.skipPR } : {}),
156
+ ...(proj.devAccess ? { devAccess: proj.devAccess } : {}),
157
+ ...(proj.envFile ? { envFile: proj.envFile } : {}),
139
158
  };
140
159
  }
141
160
  writeFileSync(filepath, JSON.stringify(data, null, 2) + '\n');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ticket-to-pr",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "Drag a Notion ticket, get a pull request. AI-powered dev automation.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,6 +13,22 @@
13
13
  "projects.example.json",
14
14
  "README.md"
15
15
  ],
16
+ "homepage": "https://www.tickettopr.com",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/JohnRiceML/ticket-to-pr.git"
20
+ },
21
+ "bugs": {
22
+ "url": "https://github.com/JohnRiceML/ticket-to-pr/issues"
23
+ },
24
+ "keywords": [
25
+ "notion",
26
+ "claude",
27
+ "ai",
28
+ "pull-request",
29
+ "automation",
30
+ "developer-tools"
31
+ ],
16
32
  "engines": {
17
33
  "node": ">=18.0.0"
18
34
  },
@@ -15,6 +15,7 @@ You have been given a ticket with an implementation spec. Follow the spec and im
15
15
  9. Run existing tests if available, but do not add new test files unless the spec explicitly requires it.
16
16
  10. Do not modify files outside the scope of the spec.
17
17
  11. If your prompt includes a "BLOCKED FILES" section, you MUST NOT modify any files matching those patterns. Violations will cause the entire run to fail.
18
+ 12. If your prompt includes a "DEV ENVIRONMENT ACCESS" section, you may run scripts and dev tools as described. Always prefer reading code directly over running scripts when possible.
18
19
 
19
20
  ## When Done
20
21
  Commit all changes with a final commit message summarizing what was done. The commit message should reference the ticket title.