@tarcisiopgs/lisa 0.9.5 → 1.0.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.
Files changed (3) hide show
  1. package/README.md +106 -66
  2. package/dist/index.js +178 -27
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  While the Ralphs of the world flooded GitHub with mindless agent loops — brute-forcing their way through issues with no context, no workflow awareness, and no regard for the mess they leave behind — Lisa takes a different approach. She reads the issue, understands the workspace, picks the right repo, creates the branch, validates her work, and opens the PR. Then she moves on to the next one. When there's nothing left to do, she stops.
8
8
 
9
- Named after the smartest Simpson, Lisa is an autonomous issue resolver that connects your project tracker (Linear or Trello) to an AI coding agent (Claude Code, Gemini CLI, or OpenCode) and delivers pull requests via the GitHub API. No MCP servers. No prompt chains. No blind retries. Just structured, end-to-end execution.
9
+ Named after the smartest Simpson, Lisa is an autonomous issue resolver that connects your project tracker (Linear or Trello) to an AI coding agent (Claude Code, Gemini CLI, or OpenCode) and delivers pull requests via GitHub. No MCP servers. No prompt chains. No blind retries. Just structured, end-to-end execution.
10
10
 
11
11
  ## Why Lisa?
12
12
 
@@ -14,10 +14,12 @@ Most AI agent loops work like Ralph — they grab an issue, throw it at a model,
14
14
 
15
15
  Lisa is deterministic. She follows a structured pipeline with clear stages (fetch, activate, implement, validate, PR, update) and stops when the work is done. This means:
16
16
 
17
- - **Token efficiency** — Each issue gets one focused prompt with full context (description, acceptance criteria, repo conventions). No wasted retries, no speculative exploration, no idle polling burning API calls.
18
- - **Multi-repo awareness** — Lisa detects which repos the agent actually touched and creates a PR for each. No guessing, no hardcoded paths.
19
- - **Workflow integration** — Issues move through your board in real time (Todo, In Progress, In Review). Your team always knows what's being worked on.
20
- - **Predictable cost** — One issue = one agent session = one set of PRs. You can estimate cost per issue instead of hoping the loop eventually converges.
17
+ - **Token efficiency** — Each issue gets one focused prompt with full context. No wasted retries, no speculative exploration, no idle polling.
18
+ - **Multi-repo awareness** — Lisa detects which repos the agent touched and creates one PR per repo. Monorepos with multiple packages just work.
19
+ - **Provider fallback** — Configure a chain of models (`claude gemini → opencode`). Transient errors (429, quota, timeout) trigger the next provider; non-transient errors stop the chain.
20
+ - **Workflow integration** — Issues move through your board in real time (Backlog In Progress In Review). Your team always knows what's being worked on.
21
+ - **Self-healing** — Orphan issues (stuck in "In Progress" from interrupted runs) are automatically recovered on startup. Pre-push hook failures trigger the agent to fix and retry.
22
+ - **Guardrails** — Past failures are logged and injected into future prompts so the agent avoids repeating mistakes.
21
23
 
22
24
  ## Install
23
25
 
@@ -27,11 +29,9 @@ npm install -g @tarcisiopgs/lisa
27
29
 
28
30
  ## Environment Variables
29
31
 
30
- Lisa calls external APIs directly. Set these in your shell profile (`~/.zshrc` or `~/.bashrc`):
31
-
32
32
  ```bash
33
- # Required (always)
34
- export GITHUB_TOKEN=""
33
+ # Required (at least one)
34
+ export GITHUB_TOKEN="" # or have `gh` CLI authenticated
35
35
 
36
36
  # Required when source = linear
37
37
  export LINEAR_API_KEY=""
@@ -41,24 +41,28 @@ export TRELLO_API_KEY=""
41
41
  export TRELLO_TOKEN=""
42
42
  ```
43
43
 
44
- The CLI will warn you if any required variable is missing.
45
-
46
44
  ## Quick Start
47
45
 
48
46
  ```bash
49
47
  # Interactive setup
50
48
  lisa init
51
49
 
52
- # Run continuously
50
+ # Run continuously until all labeled issues are done
53
51
  lisa run
54
52
 
55
53
  # Single issue
56
54
  lisa run --once
57
55
 
56
+ # Specific issue by identifier or URL
57
+ lisa run --issue INT-150
58
+
59
+ # Process up to N issues
60
+ lisa run --limit 5
61
+
58
62
  # Preview without executing
59
63
  lisa run --dry-run
60
64
 
61
- # Override provider
65
+ # Override provider for a single run
62
66
  lisa run --provider gemini --once
63
67
  ```
64
68
 
@@ -68,8 +72,13 @@ lisa run --provider gemini --once
68
72
  |---------|-------------|
69
73
  | `lisa run` | Run the agent loop |
70
74
  | `lisa run --once` | Process a single issue |
75
+ | `lisa run --issue ID` | Process a specific issue by identifier or URL |
71
76
  | `lisa run --limit N` | Process up to N issues |
72
77
  | `lisa run --dry-run` | Preview without executing |
78
+ | `lisa run --provider NAME` | Override AI provider |
79
+ | `lisa run --label NAME` | Override label filter |
80
+ | `lisa run --json` | Output as JSON lines |
81
+ | `lisa run --quiet` | Suppress non-essential output |
73
82
  | `lisa config` | Interactive config wizard |
74
83
  | `lisa config --show` | Show current config |
75
84
  | `lisa config --set key=value` | Set a config value |
@@ -86,106 +95,137 @@ lisa run --provider gemini --once
86
95
 
87
96
  At least one provider must be installed and available in your PATH.
88
97
 
89
- All providers stream output to stdout and to the session log file in real time. Prompts are written to a temp file and passed via shell expansion (`$(cat file)`) to avoid argument length limits.
98
+ All providers use `child_process.spawn` with `sh -c`. Prompts are written to a temp file and passed via `$(cat file)` to avoid argument length limits. Output streams to both stdout and the session log file in real time.
90
99
 
91
- ## Workflow Modes
100
+ ### Fallback Chain
101
+
102
+ Configure multiple providers in the `models` array. Lisa tries them in order — transient errors (429, quota, timeout, network) trigger the next provider. Non-transient errors stop the chain immediately.
103
+
104
+ ```yaml
105
+ models:
106
+ - claude
107
+ - gemini
108
+ ```
109
+
110
+ If `models` is not set, Lisa uses the single `provider` field.
92
111
 
93
- Lisa supports two workflow modes, configured during `lisa init`:
112
+ ## Workflow Modes
94
113
 
95
- ### Branch (default)
114
+ ### Branch
96
115
 
97
116
  The AI agent creates a branch directly in your current checkout, implements the changes, and pushes. Simple setup, works everywhere.
98
117
 
99
118
  ### Worktree
100
119
 
101
- Lisa creates an isolated [git worktree](https://git-scm.com/docs/git-worktree) for each issue under `.worktrees/`. The AI agent works inside the worktree without touching your main checkout. After the PR is created, the worktree is cleaned up automatically.
120
+ Lisa creates an isolated [git worktree](https://git-scm.com/docs/git-worktree) for each issue under `.worktrees/`. The agent works inside the worktree without touching your main checkout. After the PR is created, the worktree is cleaned up automatically.
121
+
122
+ In multi-repo workspaces, the agent selects the correct repository, creates the worktree, implements, and writes a `.lisa-manifest.json` with the repo path, branch name, and PR title. Lisa reads the manifest to push and create the PR.
102
123
 
103
124
  Worktree mode is ideal when you want to keep working in the repo while Lisa resolves issues in the background.
104
125
 
105
126
  ## Configuration
106
127
 
107
- Config lives in `.lisa/config.yaml`:
128
+ Config lives in `.lisa/config.yaml`. Run `lisa init` to create it interactively.
108
129
 
109
- **Linear:**
110
130
  ```yaml
111
131
  provider: claude
132
+ models:
133
+ - claude
134
+ - gemini
112
135
  source: linear
113
- workflow: branch
136
+ workflow: worktree
114
137
 
115
138
  source_config:
116
139
  team: Engineering
117
140
  project: Web App
118
141
  label: ready
119
- pick_from: Todo
142
+ pick_from: Backlog
120
143
  in_progress: In Progress
121
144
  done: In Review
122
145
 
123
- github: cli
146
+ github: cli # "cli" (gh) or "token" (GITHUB_TOKEN)
124
147
  workspace: .
148
+ base_branch: main
149
+
125
150
  repos:
126
- - name: app
151
+ - name: my-api
152
+ path: ./api
153
+ base_branch: main
154
+ - name: my-app
127
155
  path: ./app
128
- match: "App:"
156
+ base_branch: main
129
157
 
130
158
  loop:
131
- cooldown: 10
132
- max_sessions: 0
159
+ cooldown: 10 # seconds between issues
160
+ max_sessions: 0 # 0 = unlimited
133
161
 
134
162
  logs:
135
163
  dir: .lisa/logs
136
- format: text
137
- ```
164
+ format: text # "text" or "json"
138
165
 
139
- **Trello:**
140
- ```yaml
141
- provider: claude
142
- source: trello
143
- workflow: branch
144
-
145
- source_config:
146
- board: Product
147
- pick_from: Backlog
148
- label: ready
149
- in_progress: In Progress
150
- done: Code Review
151
-
152
- github: cli
153
- workspace: .
154
-
155
- loop:
156
- cooldown: 10
157
- max_sessions: 0
158
-
159
- logs:
160
- dir: .lisa/logs
161
- format: text
166
+ # Optional — kill stuck providers
167
+ overseer:
168
+ enabled: true
169
+ check_interval: 30 # seconds between git status checks
170
+ stuck_threshold: 300 # seconds without git changes before killing
162
171
  ```
163
172
 
164
- ### Source-specific fields
173
+ ### Source-Specific Fields
165
174
 
166
175
  | Field | Linear | Trello |
167
176
  |-------|--------|--------|
168
- | `team` / `board` | Team name | Board name |
177
+ | `team` | Team name | Board name |
169
178
  | `project` | Project name | — |
170
- | `pick_from` | Status to pick issues from (e.g. Todo) | List to pick cards from (e.g. Backlog) |
179
+ | `pick_from` | Status to pick issues from | List to pick cards from |
171
180
  | `label` | Label to filter issues | Label to filter cards |
172
- | `in_progress` | In-progress status (e.g. In Progress) | In-progress column |
173
- | `done` | Destination status (e.g. In Review) | Destination column (e.g. Code Review) |
181
+ | `in_progress` | In-progress status | In-progress column |
182
+ | `done` | Destination status after PR | Destination column after PR |
174
183
 
175
- CLI flags override config values:
184
+ ### Lifecycle Resources
176
185
 
177
- ```bash
178
- lisa run --provider gemini --label "urgent"
186
+ For repos that need services running during implementation (databases, dev servers):
187
+
188
+ ```yaml
189
+ repos:
190
+ - name: my-api
191
+ path: ./api
192
+ base_branch: main
193
+ lifecycle:
194
+ resources:
195
+ - name: postgres
196
+ check_port: 5432
197
+ up: "docker compose up -d postgres"
198
+ down: "docker compose down"
199
+ startup_timeout: 30
200
+ setup:
201
+ - "npx prisma generate"
202
+ - "npx prisma db push"
179
203
  ```
180
204
 
205
+ Lisa starts resources before the agent runs, waits for the port to be ready, runs setup commands, then stops everything after the session.
206
+
181
207
  ## How It Works
182
208
 
183
- 1. **Fetch** — Pulls the next issue from Linear or Trello matching the configured label, team, and project. Issues are sorted by priority.
184
- 2. **Activate** — Moves the issue to the `in_progress` status so your team knows it's being worked on.
185
- 3. **Implement** Builds a structured prompt with full issue context and sends it to the AI agent. The agent creates a branch, implements, validates (lint, typecheck, tests), commits, and pushes.
186
- 4. **PR** — Detects every repo the agent touched and creates a pull request for each, referencing the original issue. Multi-repo workspaces are handled automatically.
187
- 5. **Update** — Moves the issue to the `done` status and removes the pickup label.
188
- 6. **Next** — Picks the next issue. When there are no more issues, Lisa stops. No idle polling, no wasted cycles.
209
+ ```
210
+ ┌─────────┐ ┌──────────┐ ┌───────────┐ ┌──────────┐ ┌────┐ ┌────────┐
211
+ │ Fetch │───▶│ Activate │───▶│ Implement │───▶│ Validate │───▶│ PR │───▶│ Update
212
+ └─────────┘ └──────────┘ └───────────┘ └──────────┘ └────┘ └────────┘
213
+ ```
214
+
215
+ 1. **Fetch** — Pulls the next issue from Linear or Trello matching the configured label, team, and project. Issues are sorted by priority. Blocked issues (with unresolved dependencies) are skipped.
216
+ 2. **Activate** — Moves the issue to `in_progress` so your team knows it's being worked on.
217
+ 3. **Implement** — Builds a structured prompt with full issue context and sends it to the AI agent. The agent works in a worktree or branch, implements the change, runs validation, and commits.
218
+ 4. **Validate** — Runs the project's test suite. If tests fail, the session is aborted and the issue reverts.
219
+ 5. **PR** — Pushes the branch and creates a pull request referencing the original issue. If pre-push hooks fail, Lisa re-invokes the agent to fix the errors and retries (up to 2 recovery attempts).
220
+ 6. **Update** — Moves the issue to the `done` status and removes the pickup label in a single atomic operation.
221
+ 7. **Next** — Picks the next issue. When there are no more matching issues, Lisa stops.
222
+
223
+ ### Recovery Mechanisms
224
+
225
+ - **Orphan recovery** — On startup, Lisa scans for issues stuck in `in_progress` from previous interrupted runs and reverts them to `pick_from`.
226
+ - **Push recovery** — If `git push` fails due to pre-push hooks (linter, typecheck, tests), Lisa re-invokes the agent with the error output and retries the push.
227
+ - **Signal handling** — SIGINT/SIGTERM gracefully revert the active issue to its previous status before exiting.
228
+ - **Guardrails** — Failed sessions are logged to `.lisa/guardrails.md` and injected into future prompts so the agent avoids repeating the same mistakes.
189
229
 
190
230
  ## License
191
231
 
package/dist/index.js CHANGED
@@ -531,6 +531,16 @@ This project uses **${testRunner}** as its test runner.
531
531
  - Do NOT skip writing tests \u2014 the PR will be blocked if tests are missing or failing.
532
532
  `;
533
533
  }
534
+ function buildPreCommitHookInstructions() {
535
+ return `
536
+ **Pre-commit hooks:**
537
+ If \`git commit\` fails due to a pre-commit hook (e.g. husky), read the error output carefully and fix the underlying issue:
538
+ - Linter/formatter failures \u2192 run the project's lint/format commands, then re-stage and retry the commit.
539
+ - Code generation errors (e.g. stale Prisma client) \u2192 run the required generation command (e.g. \`npx prisma generate\`), then re-stage and retry.
540
+ - Type errors \u2192 fix the type issues in the source files, then re-stage and retry.
541
+ Do NOT skip or bypass hooks (no \`--no-verify\`). Fix the root cause and retry.
542
+ `;
543
+ }
534
544
  function buildReadmeInstructions() {
535
545
  return `
536
546
  **README.md Evaluation:**
@@ -565,6 +575,7 @@ function buildWorktreeMultiRepoPrompt(issue, config2) {
565
575
  ].join("\n");
566
576
  }).join("\n\n");
567
577
  const readmeBlock = buildReadmeInstructions();
578
+ const hookBlock = buildPreCommitHookInstructions();
568
579
  const manifestPath = join(workspace, ".lisa-manifest.json");
569
580
  return `You are an autonomous implementation agent working in a multi-repository workspace.
570
581
  Your job is to determine the correct repository, create an English-named branch, implement the issue, commit, and write a manifest file.
@@ -610,7 +621,7 @@ ${repoBlock}
610
621
  - Follow the implementation instructions exactly
611
622
  - Verify each acceptance criteria (if present)
612
623
  - Respect any stack or technical constraints (if present)
613
- ${readmeBlock}
624
+ ${readmeBlock}${hookBlock}
614
625
  5. **Validate**: Run the project's linter/typecheck/tests if available:
615
626
  - Check \`package.json\` for lint, typecheck, check, or test scripts.
616
627
  - Run whichever validation scripts exist (e.g., \`npm run lint\`, \`npm run typecheck\`, \`npm run test\`).
@@ -647,6 +658,7 @@ ${readmeBlock}
647
658
  function buildWorktreePrompt(issue, testRunner) {
648
659
  const testBlock = buildTestInstructions(testRunner ?? null);
649
660
  const readmeBlock = buildReadmeInstructions();
661
+ const hookBlock = buildPreCommitHookInstructions();
650
662
  return `You are an autonomous implementation agent. Your job is to implement a single
651
663
  issue, validate it, commit, and push the branch.
652
664
 
@@ -670,7 +682,7 @@ ${issue.description}
670
682
  - Follow the implementation instructions exactly
671
683
  - Verify each acceptance criteria (if present)
672
684
  - Respect any stack or technical constraints (if present)
673
- ${testBlock}${readmeBlock}
685
+ ${testBlock}${readmeBlock}${hookBlock}
674
686
  2. **Validate**: Run the project's linter/typecheck/tests if available:
675
687
  - Check \`package.json\` (or equivalent) for lint, typecheck, check, or test scripts.
676
688
  - Run whichever validation scripts exist (e.g., \`npm run lint\`, \`npm run typecheck\`).
@@ -711,6 +723,7 @@ function buildBranchPrompt(issue, config2, testRunner) {
711
723
  const baseBranchInstruction = config2.repos.length > 0 ? "From the repo's base branch (listed above)" : `From \`${config2.base_branch}\``;
712
724
  const testBlock = buildTestInstructions(testRunner ?? null);
713
725
  const readmeBlock = buildReadmeInstructions();
726
+ const hookBlock = buildPreCommitHookInstructions();
714
727
  const manifestPath = join(workspace, ".lisa-manifest.json");
715
728
  return `You are an autonomous implementation agent. Your job is to implement a single
716
729
  issue, validate it, commit, and push the branch.
@@ -741,7 +754,7 @@ ${repoEntries}
741
754
  - Follow the implementation instructions exactly
742
755
  - Verify each acceptance criteria (if present)
743
756
  - Respect any stack or technical constraints (if present)
744
- ${testBlock}${readmeBlock}
757
+ ${testBlock}${readmeBlock}${hookBlock}
745
758
  4. **Validate**: Run the project's linter/typecheck/tests if available:
746
759
  - Check \`package.json\` (or equivalent) for lint, typecheck, check, or test scripts.
747
760
  - Run whichever validation scripts exist (e.g., \`npm run lint\`, \`npm run typecheck\`).
@@ -771,6 +784,32 @@ ${testBlock}${readmeBlock}
771
784
  - Do NOT create pull requests \u2014 the caller handles that.
772
785
  - Do NOT update the issue tracker \u2014 the caller handles that.`;
773
786
  }
787
+ function buildPushRecoveryPrompt(hookErrors) {
788
+ return `The previous \`git push\` failed because a pre-push hook rejected the push.
789
+ Here is the full error output:
790
+
791
+ \`\`\`
792
+ ${hookErrors}
793
+ \`\`\`
794
+
795
+ ## Instructions
796
+
797
+ 1. **Read the errors** above carefully and identify the root cause.
798
+ 2. **Fix the issue** \u2014 common fixes include:
799
+ - Run linters/formatters (e.g. \`npm run lint -- --fix\`, \`npm run format\`)
800
+ - Run code generation (e.g. \`npx prisma generate\`, \`npm run codegen\`)
801
+ - Fix type errors in the source files
802
+ - Fix failing tests
803
+ 3. **Amend the commit** so the fix is included:
804
+ \`\`\`
805
+ git add -A && git commit --amend --no-edit
806
+ \`\`\`
807
+ 4. **Do NOT push** \u2014 the caller handles pushing after you finish.
808
+ 5. **Do NOT create pull requests** \u2014 the caller handles that.
809
+ 6. **Do NOT update the issue tracker** \u2014 the caller handles that.
810
+
811
+ Focus only on fixing the hook errors. Do not make unrelated changes.`;
812
+ }
774
813
 
775
814
  // src/guardrails.ts
776
815
  import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
@@ -1449,6 +1488,56 @@ var LinearSource = class {
1449
1488
  }
1450
1489
  async attachPullRequest(_issueId, _prUrl) {
1451
1490
  }
1491
+ async completeIssue(issueId, statusName, labelToRemove) {
1492
+ const issueData = await gql(
1493
+ `query($identifier: String!) {
1494
+ issue(id: $identifier) {
1495
+ id
1496
+ team { id }
1497
+ labels { nodes { id name } }
1498
+ }
1499
+ }`,
1500
+ { identifier: issueId }
1501
+ );
1502
+ const statesData = await gql(
1503
+ `query($teamId: ID!) {
1504
+ workflowStates(filter: { team: { id: { eq: $teamId } } }) {
1505
+ nodes { id name }
1506
+ }
1507
+ }`,
1508
+ { teamId: issueData.issue.team.id }
1509
+ );
1510
+ const state = statesData.workflowStates.nodes.find(
1511
+ (s) => s.name.toLowerCase() === statusName.toLowerCase()
1512
+ );
1513
+ if (!state) {
1514
+ const available = statesData.workflowStates.nodes.map((s) => s.name).join(", ");
1515
+ throw new Error(`Status "${statusName}" not found. Available: ${available}`);
1516
+ }
1517
+ const input = { stateId: state.id };
1518
+ if (labelToRemove) {
1519
+ const currentLabels = issueData.issue.labels.nodes;
1520
+ const filtered = currentLabels.filter(
1521
+ (l) => l.name.toLowerCase() !== labelToRemove.toLowerCase()
1522
+ );
1523
+ if (filtered.length !== currentLabels.length) {
1524
+ input.labelIds = filtered.map((l) => l.id);
1525
+ }
1526
+ }
1527
+ const mutationResult = await gql(
1528
+ `mutation($issueId: String!, $input: IssueUpdateInput!) {
1529
+ issueUpdate(id: $issueId, input: $input) {
1530
+ success
1531
+ }
1532
+ }`,
1533
+ { issueId: issueData.issue.id, input }
1534
+ );
1535
+ if (!mutationResult.issueUpdate.success) {
1536
+ throw new Error(
1537
+ `issueUpdate returned success=false for ${issueId} (stateId: ${state.id}, stateName: ${state.name})`
1538
+ );
1539
+ }
1540
+ }
1452
1541
  async removeLabel(issueId, labelName) {
1453
1542
  const issueData = await gql(
1454
1543
  `query($identifier: String!) {
@@ -1591,6 +1680,12 @@ var TrelloSource = class {
1591
1680
  async attachPullRequest(cardId, prUrl) {
1592
1681
  await trelloPost(`/cards/${cardId}/attachments`, `url=${encodeURIComponent(prUrl)}`);
1593
1682
  }
1683
+ async completeIssue(cardId, listName, labelToRemove) {
1684
+ await this.updateStatus(cardId, listName);
1685
+ if (labelToRemove) {
1686
+ await this.removeLabel(cardId, labelToRemove);
1687
+ }
1688
+ }
1594
1689
  async removeLabel(cardId, labelName) {
1595
1690
  const card = await trelloGet(
1596
1691
  `/cards/${cardId}`,
@@ -1792,6 +1887,58 @@ function cleanupManifest(dir) {
1792
1887
  } catch {
1793
1888
  }
1794
1889
  }
1890
+ var MAX_PUSH_RETRIES = 2;
1891
+ var HOOK_ERROR_PATTERNS = [
1892
+ /husky - pre-push/i,
1893
+ /husky - pre-commit/i,
1894
+ /pre-push hook/i,
1895
+ /pre-commit hook/i,
1896
+ /hook declined/i,
1897
+ /hook.*failed/i,
1898
+ /hook.*exited with/i,
1899
+ /hook.*returned.*exit code/i
1900
+ ];
1901
+ function isHookError(errorMessage) {
1902
+ return HOOK_ERROR_PATTERNS.some((pattern) => pattern.test(errorMessage));
1903
+ }
1904
+ async function pushWithRecovery(opts) {
1905
+ for (let attempt = 0; attempt <= MAX_PUSH_RETRIES; attempt++) {
1906
+ try {
1907
+ await execa3("git", ["push", "-u", "origin", opts.branch], { cwd: opts.cwd });
1908
+ return { success: true };
1909
+ } catch (err) {
1910
+ const errorMessage = err instanceof Error ? err.message : String(err);
1911
+ if (!isHookError(errorMessage)) {
1912
+ return { success: false, error: errorMessage };
1913
+ }
1914
+ if (attempt >= MAX_PUSH_RETRIES) {
1915
+ return {
1916
+ success: false,
1917
+ error: `Push hook failed after ${MAX_PUSH_RETRIES} recovery attempts: ${errorMessage}`
1918
+ };
1919
+ }
1920
+ warn(
1921
+ `Push hook failed (attempt ${attempt + 1}/${MAX_PUSH_RETRIES}). Re-invoking provider to fix...`
1922
+ );
1923
+ const recoveryPrompt = buildPushRecoveryPrompt(errorMessage);
1924
+ const result = await runWithFallback(opts.models, recoveryPrompt, {
1925
+ logFile: opts.logFile,
1926
+ cwd: opts.cwd,
1927
+ guardrailsDir: opts.guardrailsDir,
1928
+ issueId: opts.issueId,
1929
+ overseer: opts.overseer
1930
+ });
1931
+ if (!result.success) {
1932
+ return {
1933
+ success: false,
1934
+ error: `Provider failed to fix push hook errors: ${result.output}`
1935
+ };
1936
+ }
1937
+ ok("Provider finished recovery. Retrying push...");
1938
+ }
1939
+ }
1940
+ return { success: false, error: "Push recovery exhausted retries" };
1941
+ }
1795
1942
  function installSignalHandlers() {
1796
1943
  const cleanup = async (signal) => {
1797
1944
  if (shuttingDown) {
@@ -1991,22 +2138,16 @@ async function runLoop(config2, opts) {
1991
2138
  warn(`Failed to attach PR: ${err instanceof Error ? err.message : String(err)}`);
1992
2139
  }
1993
2140
  }
1994
- let statusUpdated = false;
1995
2141
  try {
1996
2142
  const doneStatus = config2.source_config.done;
1997
- await source.updateStatus(issue.id, doneStatus);
2143
+ const labelToRemove = opts.issueId ? void 0 : config2.source_config.label;
2144
+ await source.completeIssue(issue.id, doneStatus, labelToRemove);
1998
2145
  ok(`Updated ${issue.id} status to "${doneStatus}"`);
1999
- statusUpdated = true;
2000
- } catch (err) {
2001
- error(`Failed to update status: ${err instanceof Error ? err.message : String(err)}`);
2002
- }
2003
- if (statusUpdated && !opts.issueId) {
2004
- try {
2005
- await source.removeLabel(issue.id, config2.source_config.label);
2006
- ok(`Removed label "${config2.source_config.label}" from ${issue.id}`);
2007
- } catch (err) {
2008
- error(`Failed to remove label: ${err instanceof Error ? err.message : String(err)}`);
2146
+ if (labelToRemove) {
2147
+ ok(`Removed label "${labelToRemove}" from ${issue.id}`);
2009
2148
  }
2149
+ } catch (err) {
2150
+ error(`Failed to complete issue: ${err instanceof Error ? err.message : String(err)}`);
2010
2151
  }
2011
2152
  activeCleanup = null;
2012
2153
  if (opts.once) {
@@ -2158,12 +2299,17 @@ ${result.output}
2158
2299
  );
2159
2300
  }
2160
2301
  }
2161
- try {
2162
- await execa3("git", ["push", "-u", "origin", effectiveBranch], { cwd: worktreePath });
2163
- } catch (err) {
2164
- error(
2165
- `Failed to push branch to remote: ${err instanceof Error ? err.message : String(err)}`
2166
- );
2302
+ const pushResult = await pushWithRecovery({
2303
+ branch: effectiveBranch,
2304
+ cwd: worktreePath,
2305
+ models,
2306
+ logFile,
2307
+ guardrailsDir: repoPath,
2308
+ issueId: issue.id,
2309
+ overseer: config2.overseer
2310
+ });
2311
+ if (!pushResult.success) {
2312
+ error(`Failed to push branch to remote: ${pushResult.error}`);
2167
2313
  cleanupManifest(worktreePath);
2168
2314
  await cleanupWorktree(repoPath, worktreePath);
2169
2315
  return { success: false, providerUsed: result.providerUsed, prUrls: [], fallback: result };
@@ -2248,12 +2394,17 @@ ${result.output}
2248
2394
  cleanupManifest(workspace);
2249
2395
  return { success: false, providerUsed: result.providerUsed, prUrls: [], fallback: result };
2250
2396
  }
2251
- try {
2252
- await execa3("git", ["push", "-u", "origin", manifest.branch], { cwd: effectiveCwd });
2253
- } catch (err) {
2254
- error(
2255
- `Failed to push branch to remote: ${err instanceof Error ? err.message : String(err)}`
2256
- );
2397
+ const pushResult = await pushWithRecovery({
2398
+ branch: manifest.branch,
2399
+ cwd: effectiveCwd,
2400
+ models,
2401
+ logFile,
2402
+ guardrailsDir: manifest.repoPath,
2403
+ issueId: issue.id,
2404
+ overseer: config2.overseer
2405
+ });
2406
+ if (!pushResult.success) {
2407
+ error(`Failed to push branch to remote: ${pushResult.error}`);
2257
2408
  if (hasWorktree) await cleanupWorktree(manifest.repoPath, worktreePath);
2258
2409
  cleanupManifest(workspace);
2259
2410
  return { success: false, providerUsed: result.providerUsed, prUrls: [], fallback: result };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tarcisiopgs/lisa",
3
- "version": "0.9.5",
3
+ "version": "1.0.0",
4
4
  "description": "Deterministic autonomous issue resolver — structured AI agent loop for Linear/Trello",
5
5
  "license": "MIT",
6
6
  "type": "module",