@tekmidian/pai 0.7.0 → 0.7.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/dist/cli/index.mjs +1 -1
- package/dist/daemon/index.mjs +1 -1
- package/dist/{daemon-D3hYb5_C.mjs → daemon-DuGlDnV7.mjs} +861 -4
- package/dist/daemon-DuGlDnV7.mjs.map +1 -0
- package/dist/hooks/context-compression-hook.mjs +58 -22
- package/dist/hooks/context-compression-hook.mjs.map +2 -2
- package/dist/hooks/load-project-context.mjs +78 -27
- package/dist/hooks/load-project-context.mjs.map +3 -3
- package/dist/hooks/stop-hook.mjs +220 -125
- package/dist/hooks/stop-hook.mjs.map +3 -3
- package/dist/hooks/sync-todo-to-md.mjs.map +1 -1
- package/dist/skills/Reconstruct/SKILL.md +232 -0
- package/package.json +1 -1
- package/plugins/productivity/plugin.json +1 -1
- package/plugins/productivity/skills/Reconstruct/SKILL.md +232 -0
- package/src/hooks/ts/lib/project-utils/index.ts +1 -0
- package/src/hooks/ts/lib/project-utils/session-notes.ts +46 -5
- package/src/hooks/ts/lib/project-utils.ts +1 -0
- package/src/hooks/ts/pre-compact/context-compression-hook.ts +60 -37
- package/src/hooks/ts/session-start/load-project-context.ts +110 -28
- package/src/hooks/ts/stop/stop-hook.ts +259 -199
- package/dist/daemon-D3hYb5_C.mjs.map +0 -1
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Reconstruct
|
|
3
|
+
description: "Retroactively create session notes from JSONL transcripts and git history when automatic capture failed or was incomplete. USE WHEN user says 'reconstruct sessions', 'rebuild session notes', 'recover session notes', 'retroactively create notes', 'create notes from git history', 'notes are missing', 'backfill session notes', 'reconstruct what we did', OR /reconstruct."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Reconstruct Skill
|
|
7
|
+
|
|
8
|
+
USE WHEN user says 'reconstruct sessions', 'rebuild session notes', 'recover session notes', 'retroactively create notes', 'create notes from git history', 'notes are missing', 'backfill session notes', 'reconstruct what we did', OR /reconstruct.
|
|
9
|
+
|
|
10
|
+
### What This Skill Does
|
|
11
|
+
|
|
12
|
+
Reconstructs session notes for a PAI-registered project by reading:
|
|
13
|
+
1. Git commit history (authoritative record of what was built)
|
|
14
|
+
2. JSONL transcripts (user messages reveal intent, decisions, and context)
|
|
15
|
+
|
|
16
|
+
Generates one session note per day (or per logical session if multiple sessions existed in a day), numbered sequentially from the highest existing note number.
|
|
17
|
+
|
|
18
|
+
**ONLY include what can be verified from git log and JSONL. Never invent or embellish content.**
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
### Arguments
|
|
23
|
+
|
|
24
|
+
| Argument | Default | Description |
|
|
25
|
+
|----------|---------|-------------|
|
|
26
|
+
| `--days N` | 7 | Reconstruct notes for the last N days |
|
|
27
|
+
| `--since YYYY-MM-DD` | — | Reconstruct from this date forward |
|
|
28
|
+
| `--until YYYY-MM-DD` | today | Reconstruct up to this date |
|
|
29
|
+
| `--dry-run` | false | Show what would be created without writing files |
|
|
30
|
+
| `--commit` | false | Git commit the created notes after writing |
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
### Pre-Action Check
|
|
35
|
+
|
|
36
|
+
Before starting, verify:
|
|
37
|
+
|
|
38
|
+
1. **Project is registered**: Run `pai project detect` (or `pai project list`) to confirm the current project is known to PAI and get its Notes directory path.
|
|
39
|
+
2. **Notes directory exists**: The target directory should be `Notes/YYYY/MM/`. Create it if absent.
|
|
40
|
+
3. **Find highest existing note number**: Scan all `Notes/**/*.md` files for the pattern `^NNNN` to find max note number. New notes continue from there.
|
|
41
|
+
4. **NEVER overwrite existing notes**: If a note file already exists for that date/session, skip it and warn the user.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
### Step 1 — Find JSONL Files
|
|
46
|
+
|
|
47
|
+
Claude Code encodes project paths lossily: `/`, spaces, dots, hyphens all become `-`. A project may have been accessed from multiple base directories (e.g., `~/Daten/Cloud/Development/ai/PAI` and `~/dev/ai/PAI` both encode to different paths).
|
|
48
|
+
|
|
49
|
+
**Search strategy — use glob patterns, not exact paths:**
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Find all encoded project directories that might match this project
|
|
53
|
+
ls ~/.claude/projects/ | grep -i "<project-slug-fragment>"
|
|
54
|
+
|
|
55
|
+
# For each candidate, look for JSONL files
|
|
56
|
+
ls ~/.claude/projects/<encoded-path>/*.jsonl 2>/dev/null
|
|
57
|
+
|
|
58
|
+
# Also check the dev copy if the project has one
|
|
59
|
+
# Example: if cwd is ~/dev/ai/PAI, also check ~/Daten/Cloud/Development/ai/PAI encoding
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Collect ALL JSONL files from ALL matching encoded paths. A session may have been started from either location.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
### Step 2 — Determine Time Range
|
|
67
|
+
|
|
68
|
+
Calculate the date range from arguments:
|
|
69
|
+
- `--days 7` → from (today - 7 days) to today
|
|
70
|
+
- `--since 2026-03-01` → from that date to today (or --until date)
|
|
71
|
+
- Default: last 7 days
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
### Step 3 — Extract Git History
|
|
76
|
+
|
|
77
|
+
For each day in the range:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
git -C <project-dir> log \\
|
|
81
|
+
--after="YYYY-MM-DD 00:00:00" \\
|
|
82
|
+
--before="YYYY-MM-DD 23:59:59" \\
|
|
83
|
+
--format="%H|%ad|%s" \\
|
|
84
|
+
--date=short \\
|
|
85
|
+
--stat
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Group commits by day. If no commits on a day, skip that day (no note to reconstruct).
|
|
89
|
+
|
|
90
|
+
Also capture:
|
|
91
|
+
- Files changed per commit (`--stat`)
|
|
92
|
+
- Author date (not committer date) for accurate day grouping
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
### Step 4 — Extract User Messages from JSONL
|
|
97
|
+
|
|
98
|
+
For each JSONL file, parse messages where `type == "user"` AND `message.role == "user"`:
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
Each line is a JSON object. Look for:
|
|
102
|
+
{
|
|
103
|
+
"type": "user",
|
|
104
|
+
"message": {
|
|
105
|
+
"role": "user",
|
|
106
|
+
"content": "..." | [{"type": "text", "text": "..."}]
|
|
107
|
+
},
|
|
108
|
+
"timestamp": "2026-03-15T10:23:45.000Z"
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Filter messages by timestamp to match the day being reconstructed.
|
|
113
|
+
|
|
114
|
+
Extract:
|
|
115
|
+
- The text content of user messages (ignore tool_result entries)
|
|
116
|
+
- Timestamps for ordering
|
|
117
|
+
- Session ID (`sessionId` field) to group messages per session
|
|
118
|
+
|
|
119
|
+
**SKIP** messages that are:
|
|
120
|
+
- Tool results (`content` is array with `type: "tool_result"`)
|
|
121
|
+
- System-generated (very short single-word messages like "go", "continue")
|
|
122
|
+
- Pure confirmations ("yes", "ok", "sounds good")
|
|
123
|
+
|
|
124
|
+
**KEEP** messages that reveal:
|
|
125
|
+
- Intent ("build a feature that...", "I need X to do Y")
|
|
126
|
+
- Decisions ("let's use approach A instead of B")
|
|
127
|
+
- Architectural choices
|
|
128
|
+
- Problem descriptions
|
|
129
|
+
- Requirements changes
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
### Step 5 — Identify Logical Sessions
|
|
134
|
+
|
|
135
|
+
A "logical session" is a continuous work block. Use these signals to split a day into multiple sessions:
|
|
136
|
+
- Gap of more than 3 hours between messages
|
|
137
|
+
- Different JSONL session IDs with significant time gaps
|
|
138
|
+
- Commits with clearly different themes separated by time
|
|
139
|
+
|
|
140
|
+
If only one logical session exists for a day, create one note. If multiple exist, create one note per session with a suffix: `NNNN - YYYY-MM-DD - Title.md`, `NNNN+1 - YYYY-MM-DD - Title (2).md`.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
### Step 6 — Generate Note Title
|
|
145
|
+
|
|
146
|
+
The note title comes from synthesizing git commits for that session:
|
|
147
|
+
- If commits share a theme: use that theme ("Implement vault indexer")
|
|
148
|
+
- If commits are varied: use the primary/largest change ("Refactor session notes + misc fixes")
|
|
149
|
+
- Use the conventional commit prefix if present: "feat: Add dark mode" → "Add Dark Mode"
|
|
150
|
+
- Title should be 3-7 words, title-case
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
### Step 7 — Write the Note
|
|
155
|
+
|
|
156
|
+
**Note filename format:** `NNNN - YYYY-MM-DD - Title.md`
|
|
157
|
+
|
|
158
|
+
**Path:** `Notes/YYYY/MM/NNNN - YYYY-MM-DD - Title.md`
|
|
159
|
+
|
|
160
|
+
**Content template:**
|
|
161
|
+
|
|
162
|
+
```markdown
|
|
163
|
+
# Session: [Title]
|
|
164
|
+
|
|
165
|
+
**Date:** YYYY-MM-DD
|
|
166
|
+
**Status:** Completed
|
|
167
|
+
**Reconstructed:** true (from JSONL + git history)
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Work Done
|
|
172
|
+
|
|
173
|
+
[Group related commits into logical sections by theme/component, not by commit order.
|
|
174
|
+
Each section describes WHAT was built and WHY, derived from commit messages and user messages.
|
|
175
|
+
Use present-tense descriptions: "Add X", "Fix Y", "Refactor Z".]
|
|
176
|
+
|
|
177
|
+
## Key Decisions
|
|
178
|
+
|
|
179
|
+
[Architectural choices, technology selections, approach changes.
|
|
180
|
+
ONLY include decisions that are explicitly stated in user messages or strongly implied by commit message changes (e.g., a series of "revert" commits followed by a new approach).
|
|
181
|
+
Format as bullet points.]
|
|
182
|
+
|
|
183
|
+
## Known Issues at End of Session
|
|
184
|
+
|
|
185
|
+
[Bugs discovered, things left unfinished.
|
|
186
|
+
Derive from: last commits in session (if they add TODOs or fix-me comments), user messages mentioning problems, or explicit "not done yet" statements.
|
|
187
|
+
Omit this section if nothing can be verified.]
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
**Tags:** #[project-slug] #reconstructed
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**Filling rules:**
|
|
195
|
+
- Work Done: Always present (use commits as primary source)
|
|
196
|
+
- Key Decisions: Only if verifiable from user messages or commit patterns
|
|
197
|
+
- Known Issues: Only if verifiable; omit section entirely if nothing found
|
|
198
|
+
- Tags: Use the PAI project slug from `pai project detect`
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
### Step 8 — Output Summary
|
|
203
|
+
|
|
204
|
+
After writing (or dry-run preview):
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
Reconstructed N session notes:
|
|
208
|
+
0042 - 2026-03-13 - Implement Reranker → Notes/2026/03/
|
|
209
|
+
0043 - 2026-03-14 - Add Recency Boost → Notes/2026/03/
|
|
210
|
+
0044 - 2026-03-15 - Zettelkasten Schema Update → Notes/2026/03/
|
|
211
|
+
|
|
212
|
+
Skipped: 2026-03-12 (no commits)
|
|
213
|
+
Skipped: 2026-03-16 (note already exists: 0041 - 2026-03-16 - ...)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
If `--commit` was passed:
|
|
217
|
+
```bash
|
|
218
|
+
git add Notes/
|
|
219
|
+
git commit -m "docs: reconstruct session notes for YYYY-MM-DD to YYYY-MM-DD"
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
### Anti-Defaults
|
|
225
|
+
|
|
226
|
+
- **NEVER overwrite existing notes.** Skip silently, report in summary.
|
|
227
|
+
- **NEVER invent content.** If a decision isn't in the JSONL or deducible from commits, leave it out.
|
|
228
|
+
- **NEVER fabricate commit messages.** Copy them verbatim from git log.
|
|
229
|
+
- **NEVER assume intent from assistant messages.** Only user messages reveal intent.
|
|
230
|
+
- **NEVER add speculation.** "It appears that..." or "likely..." should not appear in notes.
|
|
231
|
+
- **DO trim noise.** Skip trivial messages ("ok", "yes", "go") that add no signal.
|
|
232
|
+
- **DO handle missing JSONL gracefully.** If no JSONL files found, generate notes from git only — mark the "Key Decisions" section as "Not available (no JSONL transcript found)".
|
package/package.json
CHANGED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Reconstruct
|
|
3
|
+
description: "Retroactively create session notes from JSONL transcripts and git history when automatic capture failed or was incomplete. USE WHEN user says 'reconstruct sessions', 'rebuild session notes', 'recover session notes', 'retroactively create notes', 'create notes from git history', 'notes are missing', 'backfill session notes', 'reconstruct what we did', OR /reconstruct."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Reconstruct Skill
|
|
7
|
+
|
|
8
|
+
USE WHEN user says 'reconstruct sessions', 'rebuild session notes', 'recover session notes', 'retroactively create notes', 'create notes from git history', 'notes are missing', 'backfill session notes', 'reconstruct what we did', OR /reconstruct.
|
|
9
|
+
|
|
10
|
+
### What This Skill Does
|
|
11
|
+
|
|
12
|
+
Reconstructs session notes for a PAI-registered project by reading:
|
|
13
|
+
1. Git commit history (authoritative record of what was built)
|
|
14
|
+
2. JSONL transcripts (user messages reveal intent, decisions, and context)
|
|
15
|
+
|
|
16
|
+
Generates one session note per day (or per logical session if multiple sessions existed in a day), numbered sequentially from the highest existing note number.
|
|
17
|
+
|
|
18
|
+
**ONLY include what can be verified from git log and JSONL. Never invent or embellish content.**
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
### Arguments
|
|
23
|
+
|
|
24
|
+
| Argument | Default | Description |
|
|
25
|
+
|----------|---------|-------------|
|
|
26
|
+
| `--days N` | 7 | Reconstruct notes for the last N days |
|
|
27
|
+
| `--since YYYY-MM-DD` | — | Reconstruct from this date forward |
|
|
28
|
+
| `--until YYYY-MM-DD` | today | Reconstruct up to this date |
|
|
29
|
+
| `--dry-run` | false | Show what would be created without writing files |
|
|
30
|
+
| `--commit` | false | Git commit the created notes after writing |
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
### Pre-Action Check
|
|
35
|
+
|
|
36
|
+
Before starting, verify:
|
|
37
|
+
|
|
38
|
+
1. **Project is registered**: Run `pai project detect` (or `pai project list`) to confirm the current project is known to PAI and get its Notes directory path.
|
|
39
|
+
2. **Notes directory exists**: The target directory should be `Notes/YYYY/MM/`. Create it if absent.
|
|
40
|
+
3. **Find highest existing note number**: Scan all `Notes/**/*.md` files for the pattern `^NNNN` to find max note number. New notes continue from there.
|
|
41
|
+
4. **NEVER overwrite existing notes**: If a note file already exists for that date/session, skip it and warn the user.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
### Step 1 — Find JSONL Files
|
|
46
|
+
|
|
47
|
+
Claude Code encodes project paths lossily: `/`, spaces, dots, hyphens all become `-`. A project may have been accessed from multiple base directories (e.g., `~/Daten/Cloud/Development/ai/PAI` and `~/dev/ai/PAI` both encode to different paths).
|
|
48
|
+
|
|
49
|
+
**Search strategy — use glob patterns, not exact paths:**
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Find all encoded project directories that might match this project
|
|
53
|
+
ls ~/.claude/projects/ | grep -i "<project-slug-fragment>"
|
|
54
|
+
|
|
55
|
+
# For each candidate, look for JSONL files
|
|
56
|
+
ls ~/.claude/projects/<encoded-path>/*.jsonl 2>/dev/null
|
|
57
|
+
|
|
58
|
+
# Also check the dev copy if the project has one
|
|
59
|
+
# Example: if cwd is ~/dev/ai/PAI, also check ~/Daten/Cloud/Development/ai/PAI encoding
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Collect ALL JSONL files from ALL matching encoded paths. A session may have been started from either location.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
### Step 2 — Determine Time Range
|
|
67
|
+
|
|
68
|
+
Calculate the date range from arguments:
|
|
69
|
+
- `--days 7` → from (today - 7 days) to today
|
|
70
|
+
- `--since 2026-03-01` → from that date to today (or --until date)
|
|
71
|
+
- Default: last 7 days
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
### Step 3 — Extract Git History
|
|
76
|
+
|
|
77
|
+
For each day in the range:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
git -C <project-dir> log \
|
|
81
|
+
--after="YYYY-MM-DD 00:00:00" \
|
|
82
|
+
--before="YYYY-MM-DD 23:59:59" \
|
|
83
|
+
--format="%H|%ad|%s" \
|
|
84
|
+
--date=short \
|
|
85
|
+
--stat
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Group commits by day. If no commits on a day, skip that day (no note to reconstruct).
|
|
89
|
+
|
|
90
|
+
Also capture:
|
|
91
|
+
- Files changed per commit (`--stat`)
|
|
92
|
+
- Author date (not committer date) for accurate day grouping
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
### Step 4 — Extract User Messages from JSONL
|
|
97
|
+
|
|
98
|
+
For each JSONL file, parse messages where `type == "user"` AND `message.role == "user"`:
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
Each line is a JSON object. Look for:
|
|
102
|
+
{
|
|
103
|
+
"type": "user",
|
|
104
|
+
"message": {
|
|
105
|
+
"role": "user",
|
|
106
|
+
"content": "..." | [{"type": "text", "text": "..."}]
|
|
107
|
+
},
|
|
108
|
+
"timestamp": "2026-03-15T10:23:45.000Z"
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Filter messages by timestamp to match the day being reconstructed.
|
|
113
|
+
|
|
114
|
+
Extract:
|
|
115
|
+
- The text content of user messages (ignore tool_result entries)
|
|
116
|
+
- Timestamps for ordering
|
|
117
|
+
- Session ID (`sessionId` field) to group messages per session
|
|
118
|
+
|
|
119
|
+
**SKIP** messages that are:
|
|
120
|
+
- Tool results (`content` is array with `type: "tool_result"`)
|
|
121
|
+
- System-generated (very short single-word messages like "go", "continue")
|
|
122
|
+
- Pure confirmations ("yes", "ok", "sounds good")
|
|
123
|
+
|
|
124
|
+
**KEEP** messages that reveal:
|
|
125
|
+
- Intent ("build a feature that...", "I need X to do Y")
|
|
126
|
+
- Decisions ("let's use approach A instead of B")
|
|
127
|
+
- Architectural choices
|
|
128
|
+
- Problem descriptions
|
|
129
|
+
- Requirements changes
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
### Step 5 — Identify Logical Sessions
|
|
134
|
+
|
|
135
|
+
A "logical session" is a continuous work block. Use these signals to split a day into multiple sessions:
|
|
136
|
+
- Gap of more than 3 hours between messages
|
|
137
|
+
- Different JSONL session IDs with significant time gaps
|
|
138
|
+
- Commits with clearly different themes separated by time
|
|
139
|
+
|
|
140
|
+
If only one logical session exists for a day, create one note. If multiple exist, create one note per session with a suffix: `NNNN - YYYY-MM-DD - Title.md`, `NNNN+1 - YYYY-MM-DD - Title (2).md`.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
### Step 6 — Generate Note Title
|
|
145
|
+
|
|
146
|
+
The note title comes from synthesizing git commits for that session:
|
|
147
|
+
- If commits share a theme: use that theme ("Implement vault indexer")
|
|
148
|
+
- If commits are varied: use the primary/largest change ("Refactor session notes + misc fixes")
|
|
149
|
+
- Use the conventional commit prefix if present: "feat: Add dark mode" → "Add Dark Mode"
|
|
150
|
+
- Title should be 3-7 words, title-case
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
### Step 7 — Write the Note
|
|
155
|
+
|
|
156
|
+
**Note filename format:** `NNNN - YYYY-MM-DD - Title.md`
|
|
157
|
+
|
|
158
|
+
**Path:** `Notes/YYYY/MM/NNNN - YYYY-MM-DD - Title.md`
|
|
159
|
+
|
|
160
|
+
**Content template:**
|
|
161
|
+
|
|
162
|
+
```markdown
|
|
163
|
+
# Session: [Title]
|
|
164
|
+
|
|
165
|
+
**Date:** YYYY-MM-DD
|
|
166
|
+
**Status:** Completed
|
|
167
|
+
**Reconstructed:** true (from JSONL + git history)
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Work Done
|
|
172
|
+
|
|
173
|
+
[Group related commits into logical sections by theme/component, not by commit order.
|
|
174
|
+
Each section describes WHAT was built and WHY, derived from commit messages and user messages.
|
|
175
|
+
Use present-tense descriptions: "Add X", "Fix Y", "Refactor Z".]
|
|
176
|
+
|
|
177
|
+
## Key Decisions
|
|
178
|
+
|
|
179
|
+
[Architectural choices, technology selections, approach changes.
|
|
180
|
+
ONLY include decisions that are explicitly stated in user messages or strongly implied by commit message changes (e.g., a series of "revert" commits followed by a new approach).
|
|
181
|
+
Format as bullet points.]
|
|
182
|
+
|
|
183
|
+
## Known Issues at End of Session
|
|
184
|
+
|
|
185
|
+
[Bugs discovered, things left unfinished.
|
|
186
|
+
Derive from: last commits in session (if they add TODOs or fix-me comments), user messages mentioning problems, or explicit "not done yet" statements.
|
|
187
|
+
Omit this section if nothing can be verified.]
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
**Tags:** #[project-slug] #reconstructed
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**Filling rules:**
|
|
195
|
+
- Work Done: Always present (use commits as primary source)
|
|
196
|
+
- Key Decisions: Only if verifiable from user messages or commit patterns
|
|
197
|
+
- Known Issues: Only if verifiable; omit section entirely if nothing found
|
|
198
|
+
- Tags: Use the PAI project slug from `pai project detect`
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
### Step 8 — Output Summary
|
|
203
|
+
|
|
204
|
+
After writing (or dry-run preview):
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
Reconstructed N session notes:
|
|
208
|
+
0042 - 2026-03-13 - Implement Reranker → Notes/2026/03/
|
|
209
|
+
0043 - 2026-03-14 - Add Recency Boost → Notes/2026/03/
|
|
210
|
+
0044 - 2026-03-15 - Zettelkasten Schema Update → Notes/2026/03/
|
|
211
|
+
|
|
212
|
+
Skipped: 2026-03-12 (no commits)
|
|
213
|
+
Skipped: 2026-03-16 (note already exists: 0041 - 2026-03-16 - ...)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
If `--commit` was passed:
|
|
217
|
+
```bash
|
|
218
|
+
git add Notes/
|
|
219
|
+
git commit -m "docs: reconstruct session notes for YYYY-MM-DD to YYYY-MM-DD"
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
### Anti-Defaults
|
|
225
|
+
|
|
226
|
+
- **NEVER overwrite existing notes.** Skip silently, report in summary.
|
|
227
|
+
- **NEVER invent content.** If a decision isn't in the JSONL or deducible from commits, leave it out.
|
|
228
|
+
- **NEVER fabricate commit messages.** Copy them verbatim from git log.
|
|
229
|
+
- **NEVER assume intent from assistant messages.** Only user messages reveal intent.
|
|
230
|
+
- **NEVER add speculation.** "It appears that..." or "likely..." should not appear in notes.
|
|
231
|
+
- **DO trim noise.** Skip trivial messages ("ok", "yes", "go") that add no signal.
|
|
232
|
+
- **DO handle missing JSONL gracefully.** If no JSONL files found, generate notes from git only — mark the "Key Decisions" section as "Not available (no JSONL transcript found)".
|
|
@@ -204,6 +204,14 @@ export function addWorkToSessionNote(notePath: string, workItems: WorkItem[], se
|
|
|
204
204
|
console.error(`Added ${workItems.length} work item(s) to: ${basename(notePath)}`);
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
/**
|
|
208
|
+
* Check if a candidate title is meaningless / garbage.
|
|
209
|
+
* Public wrapper around the internal filter for use by other hooks.
|
|
210
|
+
*/
|
|
211
|
+
export function isMeaningfulTitle(text: string): boolean {
|
|
212
|
+
return !isMeaninglessCandidate(text);
|
|
213
|
+
}
|
|
214
|
+
|
|
207
215
|
/** Sanitize a string for use in a filename. */
|
|
208
216
|
export function sanitizeForFilename(str: string): string {
|
|
209
217
|
return str
|
|
@@ -215,6 +223,35 @@ export function sanitizeForFilename(str: string): string {
|
|
|
215
223
|
.substring(0, 50);
|
|
216
224
|
}
|
|
217
225
|
|
|
226
|
+
/**
|
|
227
|
+
* Return true if the candidate string should be rejected as a meaningful name.
|
|
228
|
+
* Rejects file paths, shebangs, timestamps, system noise, XML tags, hashes, etc.
|
|
229
|
+
*/
|
|
230
|
+
function isMeaninglessCandidate(text: string): boolean {
|
|
231
|
+
const t = text.trim();
|
|
232
|
+
if (!t) return true;
|
|
233
|
+
if (t.length < 5) return true; // too short to be meaningful
|
|
234
|
+
if (t.startsWith('/') || t.startsWith('~')) return true; // file path
|
|
235
|
+
if (t.startsWith('#!')) return true; // shebang
|
|
236
|
+
if (t.includes('[object Object]')) return true; // serialization artifact
|
|
237
|
+
if (/^\d{4}-\d{2}-\d{2}(T[\d:.Z+-]+)?$/.test(t)) return true; // ISO timestamp
|
|
238
|
+
if (/^\d{1,2}:\d{2}(:\d{2})?(\s*(AM|PM))?$/i.test(t)) return true; // time-only
|
|
239
|
+
if (/^<[a-z-]+[\s/>]/i.test(t)) return true; // XML/HTML tags (<task-notification>, etc.)
|
|
240
|
+
if (/^[0-9a-f]{10,}$/i.test(t)) return true; // hex hash strings
|
|
241
|
+
if (/^Exit code \d+/i.test(t)) return true; // exit code messages
|
|
242
|
+
if (/^Error:/i.test(t)) return true; // error messages
|
|
243
|
+
if (/^This session is being continued/i.test(t)) return true; // continuation boilerplate
|
|
244
|
+
if (/^\(Bash completed/i.test(t)) return true; // bash output noise
|
|
245
|
+
if (/^Task Notification$/i.test(t)) return true; // literal "Task Notification"
|
|
246
|
+
if (/^New Session$/i.test(t)) return true; // placeholder title
|
|
247
|
+
if (/^Recovered Session$/i.test(t)) return true; // placeholder title
|
|
248
|
+
if (/^Continued Session$/i.test(t)) return true; // placeholder title
|
|
249
|
+
if (/^Untitled Session$/i.test(t)) return true; // placeholder title
|
|
250
|
+
if (/^Context Compression$/i.test(t)) return true; // compression artifact
|
|
251
|
+
if (/^[A-Fa-f0-9]{8,}\s+Output$/i.test(t)) return true; // hash + "Output" pattern
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
|
|
218
255
|
/**
|
|
219
256
|
* Extract a meaningful name from session note content and summary.
|
|
220
257
|
* Looks at Work Done section headers, bold text, and summary.
|
|
@@ -228,7 +265,7 @@ export function extractMeaningfulName(noteContent: string, summary: string): str
|
|
|
228
265
|
const subheadings = workDoneSection.match(/### ([^\n]+)/g);
|
|
229
266
|
if (subheadings && subheadings.length > 0) {
|
|
230
267
|
const firstHeading = subheadings[0].replace('### ', '').trim();
|
|
231
|
-
if (firstHeading.length > 5 && firstHeading.length < 60) {
|
|
268
|
+
if (!isMeaninglessCandidate(firstHeading) && firstHeading.length > 5 && firstHeading.length < 60) {
|
|
232
269
|
return sanitizeForFilename(firstHeading);
|
|
233
270
|
}
|
|
234
271
|
}
|
|
@@ -236,23 +273,27 @@ export function extractMeaningfulName(noteContent: string, summary: string): str
|
|
|
236
273
|
const boldMatches = workDoneSection.match(/\*\*([^*]+)\*\*/g);
|
|
237
274
|
if (boldMatches && boldMatches.length > 0) {
|
|
238
275
|
const firstBold = boldMatches[0].replace(/\*\*/g, '').trim();
|
|
239
|
-
if (firstBold.length > 3 && firstBold.length < 50) {
|
|
276
|
+
if (!isMeaninglessCandidate(firstBold) && firstBold.length > 3 && firstBold.length < 50) {
|
|
240
277
|
return sanitizeForFilename(firstBold);
|
|
241
278
|
}
|
|
242
279
|
}
|
|
243
280
|
|
|
244
281
|
const numberedItems = workDoneSection.match(/^\d+\.\s+\*\*([^*]+)\*\*/m);
|
|
245
|
-
if (numberedItems
|
|
282
|
+
if (numberedItems && !isMeaninglessCandidate(numberedItems[1])) {
|
|
283
|
+
return sanitizeForFilename(numberedItems[1]);
|
|
284
|
+
}
|
|
246
285
|
}
|
|
247
286
|
|
|
248
|
-
if (summary && summary.length > 5 && summary !== 'Session completed.') {
|
|
287
|
+
if (summary && summary.length > 5 && summary !== 'Session completed.' && !isMeaninglessCandidate(summary)) {
|
|
249
288
|
const cleanSummary = summary
|
|
250
289
|
.replace(/[^\w\s-]/g, ' ')
|
|
251
290
|
.trim()
|
|
252
291
|
.split(/\s+/)
|
|
253
292
|
.slice(0, 5)
|
|
254
293
|
.join(' ');
|
|
255
|
-
if (cleanSummary.length > 3
|
|
294
|
+
if (cleanSummary.length > 3 && !isMeaninglessCandidate(cleanSummary)) {
|
|
295
|
+
return sanitizeForFilename(cleanSummary);
|
|
296
|
+
}
|
|
256
297
|
}
|
|
257
298
|
|
|
258
299
|
return '';
|