-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathwork_snapshot.py
More file actions
180 lines (146 loc) · 6.56 KB
/
work_snapshot.py
File metadata and controls
180 lines (146 loc) · 6.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#!/usr/bin/env python3
"""ddday work-snapshot — Complete work context snapshot for AI handoff"""
import json, os, subprocess, datetime, glob, re
HOME = os.path.expanduser("~")
DDDAY_HOME = os.environ.get("DDDAY_HOME", os.path.dirname(os.path.abspath(__file__)))
TODAY = datetime.date.today().isoformat()
def run(cmd, cwd=None):
try:
return subprocess.check_output(cmd, shell=True, cwd=cwd,
stderr=subprocess.DEVNULL, timeout=10).decode().strip()
except:
return ""
def read_file(path, max_lines=None):
try:
with open(path, "r", encoding="utf-8") as f:
return "".join(f.readlines()[:max_lines]) if max_lines else f.read()
except:
return ""
def latest_file(pattern):
files = sorted(glob.glob(pattern))
return files[-1] if files else None
# ── Load workspace ──────────────────────────────────────────────
ws_file = os.path.join(DDDAY_HOME, "workspace.json")
with open(ws_file, "r") as f:
workspace = json.load(f)
projects = workspace.get("projects", [])
# ── Scan projects ───────────────────────────────────────────────
project_sections = []
for proj in projects:
path = proj["path"]
name = proj["name"]
emoji = proj.get("emoji", "📁")
ptype = proj.get("type", "unknown")
desc = proj.get("description", "")
exists = os.path.isdir(path)
section = f"### {emoji} {name}\n"
section += f"- **Type**: {ptype}\n"
section += f"- **Path**: `{path}`\n"
section += f"- **Description**: {desc}\n"
for key in ["url", "github"]:
if proj.get(key):
section += f"- **{key.title()}**: {proj[key]}\n"
if not exists:
section += f"- **Status**: path does not exist (needs clone/restore)\n"
project_sections.append(section)
continue
has_git = os.path.isdir(os.path.join(path, ".git"))
if has_git:
branch = run("git rev-parse --abbrev-ref HEAD", cwd=path)
log = run("git log --oneline -10", cwd=path)
diff = run("git diff --stat", cwd=path)
status = run("git status --short", cwd=path)
remote = run("git remote get-url origin", cwd=path)
section += f"- **Branch**: {branch}\n"
if remote:
section += f"- **Remote**: {remote}\n"
section += f"\n**Recent 10 commits**:\n```\n{log}\n```\n"
if diff:
section += f"\n**Uncommitted changes**:\n```\n{diff}\n```\n"
if status:
section += f"\n**Working tree**:\n```\n{status}\n```\n"
else:
section += f"- **Git**: not a git repo\n"
for readme_name in ["CLAUDE.md", "README.md"]:
rpath = os.path.join(path, readme_name)
if os.path.isfile(rpath):
content = read_file(rpath, max_lines=30)
section += f"\n**{readme_name}** (first 30 lines):\n```\n{content}\n```\n"
break
# .env keys only (no values)
env_path = None
for env_name in [".env", ".env.local"]:
ep = os.path.join(path, env_name)
if os.path.isfile(ep):
env_path = ep
break
if not env_path:
for sub in os.listdir(path):
ep = os.path.join(path, sub, ".env")
if os.path.isfile(ep):
env_path = ep
break
if env_path:
env_content = read_file(env_path)
keys = [line.split("=")[0].strip() for line in env_content.split("\n")
if line.strip() and not line.startswith("#") and "=" in line]
if keys:
section += f"\n**Environment variables** (`{os.path.relpath(env_path, path)}`):\n"
for k in keys:
section += f"- `{k}`\n"
project_sections.append(section)
# ── Recent ddday logs ───────────────────────────────────────────
logs_section = "## Recent Work Logs\n\n"
log_files = sorted(glob.glob(os.path.join(DDDAY_HOME, "logs", "*.md")))
for lf in log_files[-7:]:
logs_section += f"### {os.path.basename(lf)}\n```\n{read_file(lf)}\n```\n\n"
if not log_files:
logs_section += "> No logs yet\n\n"
# ── Claude memory ───────────────────────────────────────────────
memory_section = "## AI Memory Archive\n\n"
memory_dirs = glob.glob(os.path.join(HOME, ".claude/projects/*/memory"))
for md in sorted(memory_dirs):
project_name = os.path.basename(os.path.dirname(md))
memory_section += f"### Memory: {project_name}\n\n"
for mf in sorted(glob.glob(os.path.join(md, "*.md"))):
content = read_file(mf)
if content:
memory_section += f"**{os.path.basename(mf)}**:\n```\n{content}\n```\n\n"
# ── Environment ─────────────────────────────────────────────────
env_section = "## Environment\n\n"
crontab = run("crontab -l")
if crontab:
env_section += f"### Cron Jobs\n```\n{crontab}\n```\n\n"
py_ver = run("python3 --version")
env_section += f"### System\n- Python: {py_ver}\n- OS: {run('uname -s')} {run('uname -r')}\n- Arch: {run('uname -m')}\n\n"
# ── Assemble ────────────────────────────────────────────────────
snapshot = f"""# ddday Work Snapshot · {TODAY}
> **Purpose**: This file is the single entry point to understand all work.
> After reading this, an AI should be able to:
> 1. Know all projects, their purpose, and current status
> 2. Know recent work activity and pending items
> 3. Immediately pick up where work left off
---
## Projects
{chr(10).join(project_sections)}
---
{logs_section}
---
{memory_section}
---
{env_section}
---
*Snapshot generated: {datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}*
*By ddday work-snapshot*
"""
snapshot_path = os.path.join(DDDAY_HOME, "context", f"work-snapshot-{TODAY}.md")
latest_path = os.path.join(DDDAY_HOME, "context", "LATEST-SNAPSHOT.md")
os.makedirs(os.path.join(DDDAY_HOME, "context"), exist_ok=True)
with open(snapshot_path, "w", encoding="utf-8") as f:
f.write(snapshot)
with open(latest_path, "w", encoding="utf-8") as f:
f.write(snapshot)
lines = snapshot.count("\n")
print(f"✅ Snapshot: {snapshot_path}")
print(f"✅ Latest copy: {latest_path}")
print(f" {lines} lines | {len(projects)} projects")