autocheckpoint 0.1.0__tar.gz
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.
- autocheckpoint-0.1.0/PKG-INFO +155 -0
- autocheckpoint-0.1.0/README.md +128 -0
- autocheckpoint-0.1.0/autocheckpoint/__init__.py +3 -0
- autocheckpoint-0.1.0/autocheckpoint/ai_context.py +142 -0
- autocheckpoint-0.1.0/autocheckpoint/cli.py +834 -0
- autocheckpoint-0.1.0/autocheckpoint/config.py +33 -0
- autocheckpoint-0.1.0/autocheckpoint/context.py +160 -0
- autocheckpoint-0.1.0/autocheckpoint/ignore.py +55 -0
- autocheckpoint-0.1.0/autocheckpoint/restore.py +37 -0
- autocheckpoint-0.1.0/autocheckpoint/scanner.py +301 -0
- autocheckpoint-0.1.0/autocheckpoint/snapshot.py +56 -0
- autocheckpoint-0.1.0/autocheckpoint/storage.py +55 -0
- autocheckpoint-0.1.0/autocheckpoint/utils.py +49 -0
- autocheckpoint-0.1.0/autocheckpoint/watcher.py +92 -0
- autocheckpoint-0.1.0/autocheckpoint.egg-info/PKG-INFO +155 -0
- autocheckpoint-0.1.0/autocheckpoint.egg-info/SOURCES.txt +20 -0
- autocheckpoint-0.1.0/autocheckpoint.egg-info/dependency_links.txt +1 -0
- autocheckpoint-0.1.0/autocheckpoint.egg-info/entry_points.txt +2 -0
- autocheckpoint-0.1.0/autocheckpoint.egg-info/requires.txt +5 -0
- autocheckpoint-0.1.0/autocheckpoint.egg-info/top_level.txt +1 -0
- autocheckpoint-0.1.0/setup.cfg +4 -0
- autocheckpoint-0.1.0/setup.py +35 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: autocheckpoint
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Never lose your code or your project context again. AI Development Continuity tool.
|
|
5
|
+
Home-page: https://github.com/epicadidash/talvo-demo
|
|
6
|
+
Author: AutoCheckpoint Maintainers
|
|
7
|
+
Author-email: maintainers@autocheckpoint.dev
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: watchfiles>=0.21.0
|
|
14
|
+
Requires-Dist: typer>=0.9.0
|
|
15
|
+
Requires-Dist: rich>=13.7.0
|
|
16
|
+
Requires-Dist: pyyaml>=6.0.1
|
|
17
|
+
Requires-Dist: pathspec>=0.12.1
|
|
18
|
+
Dynamic: author
|
|
19
|
+
Dynamic: author-email
|
|
20
|
+
Dynamic: classifier
|
|
21
|
+
Dynamic: description
|
|
22
|
+
Dynamic: description-content-type
|
|
23
|
+
Dynamic: home-page
|
|
24
|
+
Dynamic: requires-dist
|
|
25
|
+
Dynamic: requires-python
|
|
26
|
+
Dynamic: summary
|
|
27
|
+
|
|
28
|
+
# AutoCheckpoint
|
|
29
|
+
|
|
30
|
+
> Never lose your code or your project context again.
|
|
31
|
+
|
|
32
|
+
AutoCheckpoint delivers **AI Development Continuity** for developers. It bridges the gap between VM losses and AI agent memory resets.
|
|
33
|
+
|
|
34
|
+
```text
|
|
35
|
+
Claude Session #1
|
|
36
|
+
↓
|
|
37
|
+
AutoCheckpoint
|
|
38
|
+
↓
|
|
39
|
+
Claude Session #2
|
|
40
|
+
↓
|
|
41
|
+
Cursor
|
|
42
|
+
↓
|
|
43
|
+
VS Code
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## The Continuity Formula
|
|
47
|
+
```
|
|
48
|
+
Code Recovery + Context Recovery + AI Session Handoff
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Unlike basic backup tools, AutoCheckpoint preserves both your code **and your project's portable brain** (decisions, intent, constraints, and current tasks). When your cloud VM dies, you restore your workspace and instantly hand off the context to the next AI session.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 1. Hero Feature: AI Session Handoff
|
|
56
|
+
|
|
57
|
+
Every AI developer knows this pain:
|
|
58
|
+
```
|
|
59
|
+
New Claude/Gemini session...
|
|
60
|
+
"Okay... what were we doing again?"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
With AutoCheckpoint, you never start from zero.
|
|
64
|
+
|
|
65
|
+
### `handoff`
|
|
66
|
+
```bash
|
|
67
|
+
autocheckpoint handoff
|
|
68
|
+
```
|
|
69
|
+
Generates a clean project state summary containing your Goal, Current Focus, Decisions, Constraints, and Tasks. It saves this as a `.autocheckpoint/handoff.md` file (which is bundled automatically into your snapshots).
|
|
70
|
+
|
|
71
|
+
### `handoff --markdown`
|
|
72
|
+
```bash
|
|
73
|
+
autocheckpoint handoff --markdown
|
|
74
|
+
```
|
|
75
|
+
Or use the shortcut `-m` to output the raw markdown directly to your terminal. Copy and paste it instantly into Claude Code, Cursor, Gemini, ChatGPT, or VS Code agents so your AI is instantly up to speed.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## 2. Recovery After a VM Loss
|
|
80
|
+
|
|
81
|
+
When a cloud VM disappears, you don't just lose files; you lose momentum.
|
|
82
|
+
|
|
83
|
+
AutoCheckpoint lets you:
|
|
84
|
+
> **Restore your code, project context, decisions, tasks, and session handoff after a VM loss.**
|
|
85
|
+
|
|
86
|
+
*(Note: AutoCheckpoint restores your codebase, decisions, and handoff files, but not running processes, docker containers, or browser tabs.)*
|
|
87
|
+
|
|
88
|
+
### `restore`
|
|
89
|
+
Run this command on your fresh VM:
|
|
90
|
+
```bash
|
|
91
|
+
autocheckpoint restore
|
|
92
|
+
```
|
|
93
|
+
It will:
|
|
94
|
+
1. Prompt for your backup location (e.g. Google Drive, OneDrive, network mount).
|
|
95
|
+
2. Scan your backup directory and list your projects.
|
|
96
|
+
3. Restore files and the complete `context.yaml` state.
|
|
97
|
+
4. Auto-detect the saved handoff so you can immediately resume coding.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 3. Installation
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
pip install -e .
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## 4. Quick Start
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
autocheckpoint init
|
|
113
|
+
```
|
|
114
|
+
1. Asks where to store backups (outside the project directory).
|
|
115
|
+
2. Automatically starts the background watcher.
|
|
116
|
+
3. Scans your project to automatically detect context (README, git history, codebase) and initializes your project's context.
|
|
117
|
+
|
|
118
|
+
From then on, a snapshot is created automatically whenever you save file changes.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## 5. CLI Command Reference
|
|
123
|
+
|
|
124
|
+
### Watcher Controls
|
|
125
|
+
* `autocheckpoint start` — Start the watcher in the foreground.
|
|
126
|
+
* `autocheckpoint start --background` — Run the watcher as a background daemon.
|
|
127
|
+
* `autocheckpoint stop` — Stop the background watcher.
|
|
128
|
+
* `autocheckpoint status` — Check the watcher's current state, snapshot count, and active context.
|
|
129
|
+
|
|
130
|
+
### Context Management
|
|
131
|
+
AutoCheckpoint maintains `.autocheckpoint/context.yaml` as the portable project brain.
|
|
132
|
+
|
|
133
|
+
* `autocheckpoint context show` — View the current context summary.
|
|
134
|
+
* `autocheckpoint context refresh` — Scan the codebase and git history with AI to auto-update context.
|
|
135
|
+
* `autocheckpoint context set-intent "<goal>"` — Define the main goal of the project.
|
|
136
|
+
* `autocheckpoint context set-focus "<focus>"` — Update your current active task.
|
|
137
|
+
* `autocheckpoint context add-decision "<decision>"` — Record an architectural/design decision.
|
|
138
|
+
* `autocheckpoint context add-constraint "<constraint>"` — Record environment or workspace limitations.
|
|
139
|
+
* `autocheckpoint context add-task "<task>"` — Add a task to the todo list.
|
|
140
|
+
* `autocheckpoint context done-task` — Interactively select and mark a task as completed.
|
|
141
|
+
* `autocheckpoint context add-session "<summary>"` — Summarize the current session.
|
|
142
|
+
* `autocheckpoint context summarize` — Run the interactive wizard to update all context fields at once.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Ignore Patterns
|
|
147
|
+
|
|
148
|
+
Create a `.autocheckpointignore` file in your project root to exclude directories:
|
|
149
|
+
```ignore
|
|
150
|
+
.git/
|
|
151
|
+
node_modules/
|
|
152
|
+
venv/
|
|
153
|
+
__pycache__/
|
|
154
|
+
```
|
|
155
|
+
*(Note: `.autocheckpoint/context.yaml` is always captured in snapshots even if `.autocheckpoint/` is ignored.)*
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# AutoCheckpoint
|
|
2
|
+
|
|
3
|
+
> Never lose your code or your project context again.
|
|
4
|
+
|
|
5
|
+
AutoCheckpoint delivers **AI Development Continuity** for developers. It bridges the gap between VM losses and AI agent memory resets.
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
Claude Session #1
|
|
9
|
+
↓
|
|
10
|
+
AutoCheckpoint
|
|
11
|
+
↓
|
|
12
|
+
Claude Session #2
|
|
13
|
+
↓
|
|
14
|
+
Cursor
|
|
15
|
+
↓
|
|
16
|
+
VS Code
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## The Continuity Formula
|
|
20
|
+
```
|
|
21
|
+
Code Recovery + Context Recovery + AI Session Handoff
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Unlike basic backup tools, AutoCheckpoint preserves both your code **and your project's portable brain** (decisions, intent, constraints, and current tasks). When your cloud VM dies, you restore your workspace and instantly hand off the context to the next AI session.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 1. Hero Feature: AI Session Handoff
|
|
29
|
+
|
|
30
|
+
Every AI developer knows this pain:
|
|
31
|
+
```
|
|
32
|
+
New Claude/Gemini session...
|
|
33
|
+
"Okay... what were we doing again?"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
With AutoCheckpoint, you never start from zero.
|
|
37
|
+
|
|
38
|
+
### `handoff`
|
|
39
|
+
```bash
|
|
40
|
+
autocheckpoint handoff
|
|
41
|
+
```
|
|
42
|
+
Generates a clean project state summary containing your Goal, Current Focus, Decisions, Constraints, and Tasks. It saves this as a `.autocheckpoint/handoff.md` file (which is bundled automatically into your snapshots).
|
|
43
|
+
|
|
44
|
+
### `handoff --markdown`
|
|
45
|
+
```bash
|
|
46
|
+
autocheckpoint handoff --markdown
|
|
47
|
+
```
|
|
48
|
+
Or use the shortcut `-m` to output the raw markdown directly to your terminal. Copy and paste it instantly into Claude Code, Cursor, Gemini, ChatGPT, or VS Code agents so your AI is instantly up to speed.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 2. Recovery After a VM Loss
|
|
53
|
+
|
|
54
|
+
When a cloud VM disappears, you don't just lose files; you lose momentum.
|
|
55
|
+
|
|
56
|
+
AutoCheckpoint lets you:
|
|
57
|
+
> **Restore your code, project context, decisions, tasks, and session handoff after a VM loss.**
|
|
58
|
+
|
|
59
|
+
*(Note: AutoCheckpoint restores your codebase, decisions, and handoff files, but not running processes, docker containers, or browser tabs.)*
|
|
60
|
+
|
|
61
|
+
### `restore`
|
|
62
|
+
Run this command on your fresh VM:
|
|
63
|
+
```bash
|
|
64
|
+
autocheckpoint restore
|
|
65
|
+
```
|
|
66
|
+
It will:
|
|
67
|
+
1. Prompt for your backup location (e.g. Google Drive, OneDrive, network mount).
|
|
68
|
+
2. Scan your backup directory and list your projects.
|
|
69
|
+
3. Restore files and the complete `context.yaml` state.
|
|
70
|
+
4. Auto-detect the saved handoff so you can immediately resume coding.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## 3. Installation
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pip install -e .
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## 4. Quick Start
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
autocheckpoint init
|
|
86
|
+
```
|
|
87
|
+
1. Asks where to store backups (outside the project directory).
|
|
88
|
+
2. Automatically starts the background watcher.
|
|
89
|
+
3. Scans your project to automatically detect context (README, git history, codebase) and initializes your project's context.
|
|
90
|
+
|
|
91
|
+
From then on, a snapshot is created automatically whenever you save file changes.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 5. CLI Command Reference
|
|
96
|
+
|
|
97
|
+
### Watcher Controls
|
|
98
|
+
* `autocheckpoint start` — Start the watcher in the foreground.
|
|
99
|
+
* `autocheckpoint start --background` — Run the watcher as a background daemon.
|
|
100
|
+
* `autocheckpoint stop` — Stop the background watcher.
|
|
101
|
+
* `autocheckpoint status` — Check the watcher's current state, snapshot count, and active context.
|
|
102
|
+
|
|
103
|
+
### Context Management
|
|
104
|
+
AutoCheckpoint maintains `.autocheckpoint/context.yaml` as the portable project brain.
|
|
105
|
+
|
|
106
|
+
* `autocheckpoint context show` — View the current context summary.
|
|
107
|
+
* `autocheckpoint context refresh` — Scan the codebase and git history with AI to auto-update context.
|
|
108
|
+
* `autocheckpoint context set-intent "<goal>"` — Define the main goal of the project.
|
|
109
|
+
* `autocheckpoint context set-focus "<focus>"` — Update your current active task.
|
|
110
|
+
* `autocheckpoint context add-decision "<decision>"` — Record an architectural/design decision.
|
|
111
|
+
* `autocheckpoint context add-constraint "<constraint>"` — Record environment or workspace limitations.
|
|
112
|
+
* `autocheckpoint context add-task "<task>"` — Add a task to the todo list.
|
|
113
|
+
* `autocheckpoint context done-task` — Interactively select and mark a task as completed.
|
|
114
|
+
* `autocheckpoint context add-session "<summary>"` — Summarize the current session.
|
|
115
|
+
* `autocheckpoint context summarize` — Run the interactive wizard to update all context fields at once.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Ignore Patterns
|
|
120
|
+
|
|
121
|
+
Create a `.autocheckpointignore` file in your project root to exclude directories:
|
|
122
|
+
```ignore
|
|
123
|
+
.git/
|
|
124
|
+
node_modules/
|
|
125
|
+
venv/
|
|
126
|
+
__pycache__/
|
|
127
|
+
```
|
|
128
|
+
*(Note: `.autocheckpoint/context.yaml` is always captured in snapshots even if `.autocheckpoint/` is ignored.)*
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ai_context.py — Uses Gemini API to synthesize project context from raw signals.
|
|
3
|
+
|
|
4
|
+
Requires GEMINI_API_KEY environment variable.
|
|
5
|
+
Falls back to heuristic extraction if no API key is set.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import re
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Optional
|
|
15
|
+
|
|
16
|
+
from autocheckpoint.scanner import gather_all_signals, build_prompt
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
GEMINI_API_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _get_api_key() -> Optional[str]:
|
|
23
|
+
return os.environ.get("GEMINI_API_KEY") or os.environ.get("GOOGLE_API_KEY")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _call_gemini(prompt: str, api_key: str) -> Optional[str]:
|
|
27
|
+
"""Call Gemini REST API and return the text response."""
|
|
28
|
+
try:
|
|
29
|
+
import urllib.request
|
|
30
|
+
import urllib.error
|
|
31
|
+
|
|
32
|
+
payload = json.dumps({
|
|
33
|
+
"contents": [{"parts": [{"text": prompt}]}],
|
|
34
|
+
"generationConfig": {
|
|
35
|
+
"temperature": 0.2,
|
|
36
|
+
"maxOutputTokens": 1024,
|
|
37
|
+
}
|
|
38
|
+
}).encode("utf-8")
|
|
39
|
+
|
|
40
|
+
url = f"{GEMINI_API_URL}?key={api_key}"
|
|
41
|
+
req = urllib.request.Request(
|
|
42
|
+
url,
|
|
43
|
+
data=payload,
|
|
44
|
+
headers={"Content-Type": "application/json"},
|
|
45
|
+
method="POST"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
with urllib.request.urlopen(req, timeout=30) as resp:
|
|
49
|
+
data = json.loads(resp.read().decode("utf-8"))
|
|
50
|
+
candidates = data.get("candidates", [])
|
|
51
|
+
if candidates:
|
|
52
|
+
parts = candidates[0].get("content", {}).get("parts", [])
|
|
53
|
+
if parts:
|
|
54
|
+
return parts[0].get("text", "")
|
|
55
|
+
except Exception as e:
|
|
56
|
+
return None
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _parse_json_response(text: str) -> Optional[dict]:
|
|
61
|
+
"""Extract JSON from the model response (strip markdown fences if present)."""
|
|
62
|
+
if not text:
|
|
63
|
+
return None
|
|
64
|
+
# Strip ```json ... ``` or ``` ... ```
|
|
65
|
+
text = re.sub(r"```(?:json)?", "", text).strip().rstrip("`").strip()
|
|
66
|
+
try:
|
|
67
|
+
return json.loads(text)
|
|
68
|
+
except json.JSONDecodeError:
|
|
69
|
+
# Try to find a JSON object inside the text
|
|
70
|
+
match = re.search(r"\{.*\}", text, re.DOTALL)
|
|
71
|
+
if match:
|
|
72
|
+
try:
|
|
73
|
+
return json.loads(match.group())
|
|
74
|
+
except Exception:
|
|
75
|
+
pass
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _heuristic_fallback(signals: dict, project_name: str) -> dict:
|
|
80
|
+
"""
|
|
81
|
+
If no API key is available, do simple heuristic extraction.
|
|
82
|
+
Tries README first, then git log.
|
|
83
|
+
"""
|
|
84
|
+
intent = ""
|
|
85
|
+
readme = signals.get("readme", "")
|
|
86
|
+
if readme:
|
|
87
|
+
# Take first non-empty, non-heading line as intent
|
|
88
|
+
for line in readme.splitlines():
|
|
89
|
+
clean = line.strip().lstrip("#").strip()
|
|
90
|
+
if clean and len(clean) > 10:
|
|
91
|
+
intent = clean[:120]
|
|
92
|
+
break
|
|
93
|
+
|
|
94
|
+
if not intent:
|
|
95
|
+
manifest = signals.get("package_manifest", "")
|
|
96
|
+
desc_match = re.search(r"description[=:]\s*['\"]?([^'\"\n]+)", manifest, re.IGNORECASE)
|
|
97
|
+
if desc_match:
|
|
98
|
+
intent = desc_match.group(1).strip()[:120]
|
|
99
|
+
|
|
100
|
+
# Tasks from TODO comments
|
|
101
|
+
tasks = []
|
|
102
|
+
for line in signals.get("todo_comments", "").splitlines():
|
|
103
|
+
match = re.search(r"(?:TODO|FIXME)[:\s]+(.+)", line, re.IGNORECASE)
|
|
104
|
+
if match:
|
|
105
|
+
tasks.append(match.group(1).strip()[:80])
|
|
106
|
+
if len(tasks) >= 5:
|
|
107
|
+
break
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
"intent": intent or f"{project_name} project",
|
|
111
|
+
"current_focus": "",
|
|
112
|
+
"decisions": [],
|
|
113
|
+
"known_constraints": [],
|
|
114
|
+
"tasks": tasks,
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def auto_detect_context(project_path: Path) -> dict:
|
|
119
|
+
"""
|
|
120
|
+
Main entry point: gather signals, call Gemini, return structured context dict.
|
|
121
|
+
|
|
122
|
+
Returns a dict with keys: intent, current_focus, decisions, tasks
|
|
123
|
+
"""
|
|
124
|
+
signals = gather_all_signals(project_path)
|
|
125
|
+
api_key = _get_api_key()
|
|
126
|
+
|
|
127
|
+
if api_key:
|
|
128
|
+
prompt = build_prompt(project_path, signals)
|
|
129
|
+
raw_response = _call_gemini(prompt, api_key)
|
|
130
|
+
parsed = _parse_json_response(raw_response)
|
|
131
|
+
if parsed:
|
|
132
|
+
# Normalize: ensure expected keys exist
|
|
133
|
+
return {
|
|
134
|
+
"intent": str(parsed.get("intent", "")).strip(),
|
|
135
|
+
"current_focus": str(parsed.get("current_focus", "")).strip(),
|
|
136
|
+
"decisions": [str(d).strip() for d in parsed.get("decisions", []) if str(d).strip()],
|
|
137
|
+
"known_constraints": [str(c).strip() for c in parsed.get("known_constraints", []) if str(c).strip()],
|
|
138
|
+
"tasks": [str(t).strip() for t in parsed.get("tasks", []) if str(t).strip()],
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
# Fallback: heuristic extraction (no API key or API call failed)
|
|
142
|
+
return _heuristic_fallback(signals, project_path.name)
|