@vpxa/aikit 0.1.214 → 0.1.216
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/package.json +1 -1
- package/scaffold/dist/adapters/copilot.mjs +4 -4
- package/scaffold/dist/definitions/agents.mjs +2 -2
- package/scaffold/dist/definitions/bodies.mjs +412 -507
- package/scaffold/dist/definitions/flows.mjs +303 -237
- package/scaffold/dist/definitions/models.mjs +1 -1
- package/scaffold/dist/definitions/protocols.mjs +243 -346
- package/scaffold/dist/definitions/skills/adr-skill.mjs +470 -1044
- package/scaffold/dist/definitions/skills/multi-agents-development.mjs +102 -214
- package/scaffold/dist/definitions/skills/session-handoff.mjs +541 -1314
|
@@ -1,1304 +1,616 @@
|
|
|
1
1
|
var e=[{file:`references/handoff-template.md`,content:`# Handoff Template
|
|
2
|
-
|
|
3
|
-
Use this template structure when creating handoff documents. The smart scaffold script will pre-fill metadata sections; complete the remaining sections based on session context.
|
|
4
|
-
|
|
2
|
+
Use this template.
|
|
5
3
|
## Table of Contents
|
|
6
|
-
|
|
7
|
-
- [Session Metadata](#session-metadata)
|
|
8
|
-
- [Current State Summary](#current-state-summary)
|
|
9
|
-
- [Codebase Understanding](#codebase-understanding)
|
|
10
|
-
- [Architecture Overview](#architecture-overview)
|
|
11
|
-
- [Critical Files](#critical-files)
|
|
12
|
-
- [Key Patterns Discovered](#key-patterns-discovered)
|
|
13
|
-
- [Work Completed](#work-completed)
|
|
14
|
-
- [Tasks Finished](#tasks-finished)
|
|
15
|
-
- [Files Modified](#files-modified)
|
|
16
|
-
- [Decisions Made](#decisions-made)
|
|
17
|
-
- [Pending Work](#pending-work)
|
|
18
|
-
- [Immediate Next Steps](#immediate-next-steps)
|
|
19
|
-
- [Blockers/Open Questions](#blockersopen-questions)
|
|
20
|
-
- [Deferred Items](#deferred-items)
|
|
21
|
-
- [Context for Resuming Agent](#context-for-resuming-agent)
|
|
22
|
-
- [Important Context](#important-context)
|
|
23
|
-
- [Assumptions Made](#assumptions-made)
|
|
24
|
-
- [Potential Gotchas](#potential-gotchas)
|
|
25
|
-
- [Environment State](#environment-state)
|
|
26
|
-
- [Related Resources](#related-resources)
|
|
27
|
-
- [Template Usage Notes](#template-usage-notes)
|
|
28
|
-
|
|
4
|
+
Use headings
|
|
29
5
|
---
|
|
30
|
-
|
|
31
6
|
# Handoff: [TASK_TITLE]
|
|
32
|
-
|
|
33
7
|
## Session Metadata
|
|
34
8
|
- Created: [TIMESTAMP]
|
|
35
9
|
- Project: [PROJECT_PATH]
|
|
36
10
|
- Branch: [GIT_BRANCH]
|
|
37
11
|
- Session duration: [APPROX_DURATION]
|
|
38
|
-
|
|
39
12
|
## Current State Summary
|
|
40
|
-
|
|
41
|
-
[One paragraph: What was being worked on, current status, and where things left off]
|
|
42
|
-
|
|
13
|
+
[One paragraph: work, status, handoff point]
|
|
43
14
|
## Codebase Understanding
|
|
44
|
-
|
|
45
15
|
### Architecture Overview
|
|
46
|
-
|
|
47
|
-
[Key architectural insights discovered during this session - how the system is structured, main components, data flow]
|
|
48
|
-
|
|
16
|
+
[Key architecture insights]
|
|
49
17
|
### Critical Files
|
|
50
|
-
|
|
51
18
|
| File | Purpose | Relevance |
|
|
52
19
|
|------|---------|-----------|
|
|
53
|
-
| path/to/file |
|
|
54
|
-
|
|
20
|
+
| path/to/file | Purpose | Why it matters now |
|
|
55
21
|
### Key Patterns Discovered
|
|
56
|
-
|
|
57
|
-
[Important patterns, conventions, or idioms found in this codebase that the next agent should follow]
|
|
58
|
-
|
|
22
|
+
[Patterns next agent should follow]
|
|
59
23
|
## Work Completed
|
|
60
|
-
|
|
61
24
|
### Tasks Finished
|
|
62
|
-
|
|
63
|
-
- [x] Task 1 - brief description of what was done
|
|
25
|
+
- [x] Task 1 - what was done
|
|
64
26
|
- [x] Task 2 - brief description
|
|
65
|
-
|
|
66
27
|
### Files Modified
|
|
67
|
-
|
|
68
28
|
| File | Changes | Rationale |
|
|
69
29
|
|------|---------|-----------|
|
|
70
|
-
| path/to/file |
|
|
71
|
-
|
|
30
|
+
| path/to/file | Changes | Why |
|
|
72
31
|
### Decisions Made
|
|
73
|
-
|
|
74
32
|
| Decision | Options Considered | Rationale |
|
|
75
33
|
|----------|-------------------|-----------|
|
|
76
|
-
| Chose X over Y | X, Y, Z | Why
|
|
77
|
-
|
|
34
|
+
| Chose X over Y | X, Y, Z | Why |
|
|
78
35
|
## Pending Work
|
|
79
|
-
|
|
80
36
|
### Immediate Next Steps
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
3. [Third priority]
|
|
85
|
-
|
|
37
|
+
1. [First action]
|
|
38
|
+
2. [Second action]
|
|
39
|
+
3. [Third action]
|
|
86
40
|
### Blockers/Open Questions
|
|
87
|
-
|
|
88
|
-
- [ ]
|
|
89
|
-
- [ ] Question: [unclear aspect] - Suggested: [potential resolution]
|
|
90
|
-
|
|
41
|
+
- [ ] Blocker: [description] - Needs: [unblocker]
|
|
42
|
+
- [ ] Question: [unclear aspect] - Suggested: [resolution]
|
|
91
43
|
### Deferred Items
|
|
92
|
-
|
|
93
|
-
- Item 1 (deferred because: [reason, e.g., out of scope, needs user input])
|
|
94
|
-
|
|
44
|
+
- Item 1 (deferred: [reason])
|
|
95
45
|
## Context for Resuming Agent
|
|
96
|
-
|
|
97
46
|
### Important Context
|
|
98
|
-
|
|
99
|
-
[Critical information the next agent MUST know to continue effectively - this is the most important section for handoff]
|
|
100
|
-
|
|
47
|
+
[Critical information next agent MUST know]
|
|
101
48
|
### Assumptions Made
|
|
102
|
-
|
|
103
|
-
- Assumption 1: [what was assumed to be true]
|
|
49
|
+
- Assumption 1: [assumed true]
|
|
104
50
|
- Assumption 2: [another assumption]
|
|
105
|
-
|
|
106
51
|
### Potential Gotchas
|
|
107
|
-
|
|
108
|
-
- [Things that might trip up a new agent - edge cases, quirks, non-obvious behavior]
|
|
109
|
-
|
|
52
|
+
- [Things that might trip up next agent]
|
|
110
53
|
## Environment State
|
|
111
|
-
|
|
112
54
|
### Tools/Services Used
|
|
113
|
-
|
|
114
|
-
- [Tool/Service]: [relevant configuration or state]
|
|
115
|
-
|
|
55
|
+
- [Tool/Service]: [relevant state]
|
|
116
56
|
### Active Processes
|
|
117
|
-
|
|
118
|
-
- [Any background processes, dev servers, watchers that may be running]
|
|
119
|
-
|
|
57
|
+
- [Background processes, dev servers, watchers]
|
|
120
58
|
### Environment Variables
|
|
121
|
-
|
|
122
|
-
- [Key env vars that matter for this work - DO NOT include secrets/values, just names]
|
|
123
|
-
|
|
59
|
+
- [Env vars that matter - names only, no values]
|
|
124
60
|
## Related Resources
|
|
125
|
-
|
|
126
|
-
- [Link to relevant documentation]
|
|
61
|
+
- [Relevant docs]
|
|
127
62
|
- [Related file paths]
|
|
128
63
|
- [External resources consulted]
|
|
129
|
-
|
|
130
64
|
---
|
|
131
|
-
|
|
132
65
|
## Template Usage Notes
|
|
133
|
-
|
|
134
|
-
When filling this template:
|
|
135
|
-
1. Be specific and concrete - vague descriptions don't help the next agent
|
|
136
|
-
2. Include file paths with line numbers where relevant (e.g., \`src/auth.ts:142\`)
|
|
137
|
-
3. Prioritize the "Important Context" and "Immediate Next Steps" sections
|
|
138
|
-
4. Don't include sensitive data (API keys, passwords, tokens)
|
|
139
|
-
5. Focus on WHAT and WHY, not just WHAT - rationale is crucial for handoffs
|
|
66
|
+
Be specific. Use file refs when useful. Exclude secrets. Capture WHAT and WHY.
|
|
140
67
|
`},{file:`references/resume-checklist.md`,content:`# Resume Checklist
|
|
141
|
-
|
|
142
|
-
Follow this checklist when resuming work from a handoff document to ensure zero-ambiguity continuation.
|
|
143
|
-
|
|
68
|
+
On resume.
|
|
144
69
|
## Pre-Resume Verification
|
|
145
|
-
|
|
146
|
-
- [ ]
|
|
147
|
-
- [ ]
|
|
148
|
-
- [ ]
|
|
149
|
-
- [ ] Check the handoff timestamp - how stale is this context?
|
|
150
|
-
|
|
70
|
+
- [ ] Read handoff before acting
|
|
71
|
+
- [ ] Verify project directory
|
|
72
|
+
- [ ] Confirm branch matches or note why it differs
|
|
73
|
+
- [ ] Check handoff timestamp
|
|
151
74
|
## Context Validation
|
|
152
|
-
|
|
153
|
-
- [ ]
|
|
154
|
-
- [ ]
|
|
155
|
-
- [ ]
|
|
156
|
-
- [ ] Review "Potential Gotchas" to avoid known pitfalls
|
|
157
|
-
|
|
75
|
+
- [ ] Review important context
|
|
76
|
+
- [ ] Check assumptions
|
|
77
|
+
- [ ] Check resolved blockers
|
|
78
|
+
- [ ] Review gotchas
|
|
158
79
|
## State Verification
|
|
159
|
-
|
|
160
|
-
- [ ]
|
|
161
|
-
- [ ]
|
|
162
|
-
- [ ]
|
|
163
|
-
- [ ] Verify any required services/processes are running
|
|
164
|
-
|
|
80
|
+
- [ ] Run \`git status\`
|
|
81
|
+
- [ ] Compare modified files vs current state
|
|
82
|
+
- [ ] Check required env vars
|
|
83
|
+
- [ ] Verify required services/processes
|
|
165
84
|
## Resume Execution
|
|
166
|
-
|
|
167
|
-
- [ ]
|
|
168
|
-
- [ ] Reference "Files Modified" table for context on recent changes
|
|
169
|
-
- [ ] Apply patterns documented in "Key Patterns Discovered"
|
|
170
|
-
- [ ] Follow architectural insights from "Architecture Overview"
|
|
171
|
-
|
|
85
|
+
- [ ] Start with next step #1
|
|
86
|
+
- [ ] Apply documented patterns
|
|
172
87
|
## During Work
|
|
173
|
-
|
|
174
|
-
- [ ]
|
|
175
|
-
- [ ] Mark completed items in "Pending Work" as you finish them
|
|
176
|
-
- [ ] Add new blockers/questions as they arise
|
|
177
|
-
- [ ] Consider creating a new handoff if session becomes long
|
|
178
|
-
|
|
88
|
+
- [ ] Update handoff if major context changes
|
|
89
|
+
- [ ] Add new blockers/questions
|
|
179
90
|
## Red Flags - Stop and Verify
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
3. **Assumptions are clearly invalid** - reassess the approach
|
|
186
|
-
4. **Blockers marked as unresolved are now blocking you** - escalate to user
|
|
187
|
-
5. **Architecture has changed** - re-explore before continuing
|
|
188
|
-
|
|
91
|
+
1. **Files mentioned in handoff don't exist** - codebase may have changed
|
|
92
|
+
2. **Branch has diverged substantially** - check git log
|
|
93
|
+
3. **Assumptions are clearly invalid** - reassess
|
|
94
|
+
4. **Unresolved blockers are still blocking you** - escalate
|
|
95
|
+
5. **Architecture has changed** - re-explore
|
|
189
96
|
## Quick Start Commands
|
|
190
|
-
|
|
191
|
-
After reading the handoff, these commands help verify state:
|
|
192
|
-
|
|
193
97
|
\`\`\`bash
|
|
194
|
-
# Check current branch and status
|
|
195
98
|
git branch --show-current
|
|
196
99
|
git status
|
|
197
|
-
|
|
198
|
-
# See recent commits (compare with handoff)
|
|
199
100
|
git log --oneline -10
|
|
200
|
-
|
|
201
|
-
# Check for any running processes mentioned
|
|
202
101
|
ps aux | grep [process-name]
|
|
203
|
-
|
|
204
|
-
# Verify environment
|
|
205
102
|
env | grep [relevant-var]
|
|
206
103
|
\`\`\`
|
|
207
|
-
|
|
208
104
|
## Handoff Quality Assessment
|
|
209
|
-
|
|
210
|
-
Rate the handoff quality to identify if more exploration is needed:
|
|
211
|
-
|
|
212
|
-
| Aspect | Good | Needs Exploration |
|
|
213
|
-
|--------|------|-------------------|
|
|
214
|
-
| Next steps | Clear, actionable | Vague or missing |
|
|
215
|
-
| File references | Specific paths/lines | General descriptions |
|
|
216
|
-
| Decisions | Rationale included | Just outcomes |
|
|
217
|
-
| Context | Complete picture | Gaps or assumptions |
|
|
218
|
-
|
|
219
|
-
If multiple aspects "Need Exploration", spend time re-exploring the codebase before continuing implementation.
|
|
105
|
+
Re-explore if weak.
|
|
220
106
|
`},{file:`scripts/check_staleness.js`,content:`#!/usr/bin/env node
|
|
221
|
-
/**
|
|
222
|
-
* Check staleness of a handoff document compared to current project state.
|
|
223
|
-
*
|
|
224
|
-
* Analyzes:
|
|
225
|
-
* - Time since handoff was created
|
|
226
|
-
* - Git commits since handoff
|
|
227
|
-
* - Files that changed since handoff
|
|
228
|
-
* - Branch divergence
|
|
229
|
-
* - Modified files status
|
|
230
|
-
*
|
|
231
|
-
* Usage:
|
|
232
|
-
* node check_staleness.js <handoff-file>
|
|
233
|
-
* node check_staleness.js .aikit-state/handoffs/add-auth/2024-01-15-143022-auth.md
|
|
234
|
-
* node check_staleness.js .aikit-state/handoffs/_standalone/2024-01-15-143022-auth.md
|
|
235
|
-
*/
|
|
236
|
-
|
|
237
107
|
const fs = require('node:fs');
|
|
238
108
|
const path = require('node:path');
|
|
239
109
|
const { execSync } = require('node:child_process');
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function runCmd(cmd, cwd) {
|
|
247
|
-
try {
|
|
248
|
-
return { ok: true, out: execSync(cmd, { cwd, timeout: 10_000, encoding: 'utf-8' }).trim() };
|
|
249
|
-
} catch {
|
|
250
|
-
return { ok: false, out: '' };
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
110
|
+
const runCmd = (cmd, cwd) => {
|
|
111
|
+
try { return { ok: true, out: execSync(cmd, { cwd, timeout: 10000, encoding: "utf-8" }).trim() }; }
|
|
112
|
+
catch { return { ok: false, out: "" }; }
|
|
113
|
+
};
|
|
254
114
|
function parseHandoffMetadata(filepath) {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
function getCommitsSince(timestamp, cwd) {
|
|
276
|
-
if (!timestamp) return [];
|
|
277
|
-
const iso = timestamp.toISOString();
|
|
278
|
-
const { ok, out } = runCmd(\`git log --since="\${iso}" --oneline --no-decorate\`, cwd);
|
|
279
|
-
return ok && out ? out.split('\\n') : [];
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
function getChangedFilesSince(timestamp, cwd) {
|
|
283
|
-
if (!timestamp) return [];
|
|
284
|
-
const iso = timestamp.toISOString();
|
|
285
|
-
const { ok, out } = runCmd(\`git log --since="\${iso}" --name-only --pretty=format:\`, cwd);
|
|
286
|
-
if (ok && out) {
|
|
287
|
-
return [
|
|
288
|
-
...new Set(
|
|
289
|
-
out
|
|
290
|
-
.split('\\n')
|
|
291
|
-
.map((f) => f.trim())
|
|
292
|
-
.filter(Boolean),
|
|
293
|
-
),
|
|
294
|
-
];
|
|
295
|
-
}
|
|
296
|
-
return [];
|
|
115
|
+
const content = fs.readFileSync(filepath, "utf-8");
|
|
116
|
+
const meta = { created: null, branch: null, projectPath: null, modifiedFiles: [] };
|
|
117
|
+
const created = content.match(/Created:\\s*(\\d{4}-\\d{2}-\\d{2}\\s+\\d{2}:\\d{2}:\\d{2})/);
|
|
118
|
+
if (created) meta.created = new Date(created[1].replace(" ", "T"));
|
|
119
|
+
const branch = content.match(/Branch:\\s*(\\S+)/);
|
|
120
|
+
if (branch && !branch[1].startsWith("[")) meta.branch = branch[1];
|
|
121
|
+
const proj = content.match(/Project:\\s*(.+?)(?:\\n|$)/);
|
|
122
|
+
if (proj) meta.projectPath = proj[1].trim();
|
|
123
|
+
for (const match of content.matchAll(/\\|\\s*([a-zA-Z0-9_\\-./]+\\.[a-zA-Z]+)\\s*\\|/g)) {
|
|
124
|
+
if (match[1].includes("/") && !match[1].startsWith("[")) meta.modifiedFiles.push(match[1]);
|
|
125
|
+
}
|
|
126
|
+
return meta;
|
|
127
|
+
}
|
|
128
|
+
const getCommitsSince = (ts, cwd) => !ts ? [] : ((r) => r.ok && r.out ? r.out.split("\\n") : [])(runCmd("git log --since=\\"" + ts.toISOString() + "\\" --oneline --no-decorate", cwd));
|
|
129
|
+
function getChangedFilesSince(ts, cwd) {
|
|
130
|
+
if (!ts) return [];
|
|
131
|
+
const result = runCmd("git log --since=\\"" + ts.toISOString() + "\\" --name-only --pretty=format:", cwd);
|
|
132
|
+
return result.ok && result.out ? [...new Set(result.out.split("\\n").map((f) => f.trim()).filter(Boolean))] : [];
|
|
297
133
|
}
|
|
298
|
-
|
|
299
134
|
function checkFilesExist(files, cwd) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
if (fs.existsSync(path.join(cwd, f))) existing.push(f);
|
|
304
|
-
else missing.push(f);
|
|
305
|
-
}
|
|
306
|
-
return { existing, missing };
|
|
135
|
+
const existing = []; const missing = [];
|
|
136
|
+
for (const file of files) (fs.existsSync(path.join(cwd, file)) ? existing : missing).push(file);
|
|
137
|
+
return { existing, missing };
|
|
307
138
|
}
|
|
308
|
-
|
|
309
139
|
function calculateStaleness(daysOld, commitsSince, filesChanged, branchMatches, filesMissing) {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
} else if (commitsSince > 20) {
|
|
327
|
-
score += 2;
|
|
328
|
-
issues.push(\`\${commitsSince} commits since handoff\`);
|
|
329
|
-
} else if (commitsSince > 5) {
|
|
330
|
-
score += 1;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
if (!branchMatches) {
|
|
334
|
-
score += 2;
|
|
335
|
-
issues.push('Current branch differs from handoff branch');
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
if (filesMissing > 5) {
|
|
339
|
-
score += 2;
|
|
340
|
-
issues.push(\`\${filesMissing} referenced files no longer exist\`);
|
|
341
|
-
} else if (filesMissing > 0) {
|
|
342
|
-
score += 1;
|
|
343
|
-
issues.push(\`\${filesMissing} referenced file(s) missing\`);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
if (filesChanged > 20) {
|
|
347
|
-
score += 2;
|
|
348
|
-
issues.push(\`\${filesChanged} files changed since handoff\`);
|
|
349
|
-
} else if (filesChanged > 5) {
|
|
350
|
-
score += 1;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
let level, recommendation;
|
|
354
|
-
if (score === 0) {
|
|
355
|
-
level = 'FRESH';
|
|
356
|
-
recommendation = 'Safe to resume - minimal changes since handoff';
|
|
357
|
-
} else if (score <= 2) {
|
|
358
|
-
level = 'SLIGHTLY_STALE';
|
|
359
|
-
recommendation = 'Generally safe to resume - review changes before continuing';
|
|
360
|
-
} else if (score <= 4) {
|
|
361
|
-
level = 'STALE';
|
|
362
|
-
recommendation = 'Proceed with caution - significant changes may affect context';
|
|
363
|
-
} else {
|
|
364
|
-
level = 'VERY_STALE';
|
|
365
|
-
recommendation = 'Consider creating new handoff - too many changes since original';
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
return { level, recommendation, issues };
|
|
140
|
+
const issues = []; let score = 0;
|
|
141
|
+
if (daysOld > 30) { score += 3; issues.push("Handoff is " + Math.floor(daysOld) + "d old"); }
|
|
142
|
+
else if (daysOld > 7) { score += 2; issues.push("Handoff is " + Math.floor(daysOld) + "d old"); }
|
|
143
|
+
else if (daysOld > 1) score += 1;
|
|
144
|
+
if (commitsSince > 50) { score += 3; issues.push(commitsSince + " commits since handoff - significant changes"); }
|
|
145
|
+
else if (commitsSince > 20) { score += 2; issues.push(commitsSince + " commits since handoff"); }
|
|
146
|
+
else if (commitsSince > 5) score += 1;
|
|
147
|
+
if (!branchMatches) { score += 2; issues.push("Current branch differs from handoff branch"); }
|
|
148
|
+
if (filesMissing > 5) { score += 2; issues.push(filesMissing + " referenced files no longer exist"); }
|
|
149
|
+
else if (filesMissing > 0) { score += 1; issues.push(filesMissing + " referenced file(s) missing"); }
|
|
150
|
+
if (filesChanged > 20) { score += 2; issues.push(filesChanged + " files changed since handoff"); }
|
|
151
|
+
else if (filesChanged > 5) score += 1;
|
|
152
|
+
if (score === 0) return { level: "FRESH", recommendation: "Safe to resume - minimal change", issues };
|
|
153
|
+
if (score <= 2) return { level: "SLIGHTLY_STALE", recommendation: "Generally safe - review changes", issues };
|
|
154
|
+
if (score <= 4) return { level: "STALE", recommendation: "Proceed with caution - changes may affect context", issues };
|
|
155
|
+
return { level: "VERY_STALE", recommendation: "Consider new handoff - too many changes", issues };
|
|
369
156
|
}
|
|
370
|
-
|
|
371
157
|
function resolveProjectRootFromHandoff(handoffPath) {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
return isFlowScoped ? path.resolve(handoffsDir, '..', '..', '..') : path.resolve(handoffsDir, '..');
|
|
158
|
+
const handoffsDir = path.dirname(handoffPath);
|
|
159
|
+
const handoffMarkerDir = path.basename(path.dirname(handoffsDir));
|
|
160
|
+
const stateMarkerDir = path.basename(path.dirname(path.dirname(handoffsDir)));
|
|
161
|
+
return handoffMarkerDir === "handoffs" && stateMarkerDir === ".aikit-state" ? path.resolve(handoffsDir, "..", "..", "..") : path.resolve(handoffsDir, "..");
|
|
378
162
|
}
|
|
379
|
-
|
|
380
163
|
function checkStaleness(handoffPath) {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
if (meta.created) {
|
|
400
|
-
const age = Date.now() - meta.created.getTime();
|
|
401
|
-
result.daysOld = age / 86_400_000;
|
|
402
|
-
result.hoursOld = age / 3_600_000;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
if (isGitRepo) {
|
|
406
|
-
const { out: currentBranch } = runCmd('git branch --show-current', projectPath);
|
|
407
|
-
result.currentBranch = currentBranch;
|
|
408
|
-
result.branchMatches = meta.branch ? currentBranch === meta.branch : true;
|
|
409
|
-
|
|
410
|
-
const commits = getCommitsSince(meta.created, projectPath);
|
|
411
|
-
result.commitsSince = commits.length;
|
|
412
|
-
result.recentCommits = commits.slice(0, 5);
|
|
413
|
-
|
|
414
|
-
const changedFiles = getChangedFilesSince(meta.created, projectPath);
|
|
415
|
-
result.filesChangedCount = changedFiles.length;
|
|
416
|
-
result.filesChanged = changedFiles.slice(0, 10);
|
|
417
|
-
|
|
418
|
-
const { existing, missing } = checkFilesExist(meta.modifiedFiles, projectPath);
|
|
419
|
-
result.referencedFilesExist = existing.length;
|
|
420
|
-
result.referencedFilesMissing = missing;
|
|
421
|
-
|
|
422
|
-
const { level, recommendation, issues } = calculateStaleness(
|
|
423
|
-
result.daysOld || 0,
|
|
424
|
-
result.commitsSince,
|
|
425
|
-
result.filesChangedCount,
|
|
426
|
-
result.branchMatches,
|
|
427
|
-
missing.length,
|
|
428
|
-
);
|
|
429
|
-
result.stalenessLevel = level;
|
|
430
|
-
result.recommendation = recommendation;
|
|
431
|
-
result.issues = issues;
|
|
432
|
-
} else {
|
|
433
|
-
result.stalenessLevel = 'UNKNOWN';
|
|
434
|
-
result.recommendation = 'Not a git repo - unable to detect changes';
|
|
435
|
-
result.issues = ['Project is not a git repository'];
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
return result;
|
|
164
|
+
if (!fs.existsSync(handoffPath)) return { error: "Handoff file not found: " + handoffPath };
|
|
165
|
+
const meta = parseHandoffMetadata(handoffPath);
|
|
166
|
+
const projectPath = meta.projectPath && fs.existsSync(meta.projectPath) ? meta.projectPath : resolveProjectRootFromHandoff(handoffPath);
|
|
167
|
+
const isGitRepo = runCmd("git rev-parse --git-dir", projectPath).ok;
|
|
168
|
+
const result = { handoffFile: handoffPath, projectPath, isGitRepo, created: meta.created, handoffBranch: meta.branch };
|
|
169
|
+
if (meta.created) { const age = Date.now() - meta.created.getTime(); result.daysOld = age / 86400000; result.hoursOld = age / 3600000; }
|
|
170
|
+
if (!isGitRepo) { result.stalenessLevel = "UNKNOWN"; result.recommendation = "Not a git repo - unable to detect changes"; result.issues = ["Project is not a git repository"]; return result; }
|
|
171
|
+
const currentBranch = runCmd("git branch --show-current", projectPath).out;
|
|
172
|
+
result.currentBranch = currentBranch; result.branchMatches = meta.branch ? currentBranch === meta.branch : true;
|
|
173
|
+
const commits = getCommitsSince(meta.created, projectPath);
|
|
174
|
+
result.commitsSince = commits.length; result.recentCommits = commits.slice(0, 5);
|
|
175
|
+
const changedFiles = getChangedFilesSince(meta.created, projectPath);
|
|
176
|
+
result.filesChangedCount = changedFiles.length; result.filesChanged = changedFiles.slice(0, 10);
|
|
177
|
+
const refs = checkFilesExist(meta.modifiedFiles, projectPath);
|
|
178
|
+
result.referencedFilesExist = refs.existing.length; result.referencedFilesMissing = refs.missing;
|
|
179
|
+
const stale = calculateStaleness(result.daysOld || 0, result.commitsSince, result.filesChangedCount, result.branchMatches, refs.missing.length);
|
|
180
|
+
result.stalenessLevel = stale.level; result.recommendation = stale.recommendation; result.issues = stale.issues;
|
|
181
|
+
return result;
|
|
439
182
|
}
|
|
440
|
-
|
|
441
183
|
function printReport(result) {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
);
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
console.log(\`\\nStaleness: \${result.stalenessLevel}\`);
|
|
465
|
-
console.log(\`Recommendation: \${result.recommendation}\`);
|
|
466
|
-
|
|
467
|
-
if (result.issues && result.issues.length > 0) {
|
|
468
|
-
console.log('\\nIssues:');
|
|
469
|
-
for (const issue of result.issues) console.log(\` - \${issue}\`);
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
if (result.isGitRepo) {
|
|
473
|
-
console.log(
|
|
474
|
-
\`\\nBranch: \${result.currentBranch || 'detached'}\${result.branchMatches ? ' (matches handoff)' : \` (handoff: \${result.handoffBranch})\`}\`,
|
|
475
|
-
);
|
|
476
|
-
console.log(\`Commits since handoff: \${result.commitsSince}\`);
|
|
477
|
-
console.log(\`Files changed since handoff: \${result.filesChangedCount}\`);
|
|
478
|
-
if (result.referencedFilesMissing.length > 0) {
|
|
479
|
-
console.log(\`Missing referenced files: \${result.referencedFilesMissing.join(', ')}\`);
|
|
480
|
-
}
|
|
481
|
-
}
|
|
184
|
+
if (result.error) return void console.log("Error: " + result.error);
|
|
185
|
+
console.log("\\n" + "=".repeat(60));
|
|
186
|
+
console.log("Handoff Staleness");
|
|
187
|
+
console.log("=".repeat(60));
|
|
188
|
+
console.log("File: " + result.handoffFile);
|
|
189
|
+
console.log("Project: " + result.projectPath);
|
|
190
|
+
if (result.created) {
|
|
191
|
+
console.log("Created: " + result.created.toISOString().replace("T", " " ).slice(0, 19));
|
|
192
|
+
if (result.daysOld != null) console.log(result.daysOld < 1 ? ("Age: " + result.hoursOld.toFixed(1) + "h") : ("Age: " + result.daysOld.toFixed(1) + "d"));
|
|
193
|
+
}
|
|
194
|
+
console.log("\\nStaleness: " + result.stalenessLevel);
|
|
195
|
+
console.log("Next: " + result.recommendation);
|
|
196
|
+
if (result.issues && result.issues.length) { console.log("\\nIssues:"); for (const issue of result.issues) console.log(" - " + issue); }
|
|
197
|
+
if (result.isGitRepo) {
|
|
198
|
+
console.log("\\nBranch: " + (result.currentBranch || "detached") + (result.branchMatches ? " (matches handoff)" : (" (handoff: " + result.handoffBranch + ")")));
|
|
199
|
+
console.log("Commits since handoff: " + result.commitsSince);
|
|
200
|
+
console.log("Files changed since handoff: " + result.filesChangedCount);
|
|
201
|
+
if (result.referencedFilesMissing.length) console.log("Missing referenced files: " + result.referencedFilesMissing.join(", "));
|
|
482
202
|
}
|
|
483
|
-
|
|
484
|
-
// --- Main ---
|
|
485
|
-
const args = process.argv.slice(2);
|
|
486
|
-
if (args.includes('--help') || args.includes('-h') || args.length === 0) {
|
|
487
|
-
console.log('Usage: node check_staleness.js <handoff-file>');
|
|
488
|
-
console.log(' node check_staleness.js .aikit-state/handoffs/<flow-slug>/2024-01-15-143022-auth.md');
|
|
489
|
-
console.log(' node check_staleness.js .aikit-state/handoffs/_standalone/2024-01-15-143022-auth.md');
|
|
490
|
-
process.exit(args.length === 0 ? 1 : 0);
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
if (args.includes('--json')) {
|
|
494
|
-
const result = checkStaleness(args.find((a) => !a.startsWith('-')));
|
|
495
|
-
console.log(JSON.stringify(result, null, 2));
|
|
496
|
-
} else {
|
|
497
|
-
const result = checkStaleness(args[0]);
|
|
498
|
-
printReport(result);
|
|
499
203
|
}
|
|
204
|
+
const args = process.argv.slice(2);
|
|
205
|
+
if (args.includes("--help") || args.includes("-h") || !args.length) {
|
|
206
|
+
console.log("Usage: node check_staleness.js <handoff-file>");
|
|
207
|
+
console.log(" node check_staleness.js .aikit-state/handoffs/<flow-slug>/file.md");
|
|
208
|
+
process.exit(!args.length ? 1 : 0);
|
|
209
|
+
}
|
|
210
|
+
const result = checkStaleness(args.find((arg) => !arg.startsWith("-")));
|
|
211
|
+
if (args.includes("--json")) console.log(JSON.stringify(result, null, 2));
|
|
212
|
+
else printReport(result);
|
|
500
213
|
`},{file:`scripts/create_handoff.js`,content:`#!/usr/bin/env node
|
|
501
|
-
/**
|
|
502
|
-
* Smart scaffold generator for handoff documents.
|
|
503
|
-
*
|
|
504
|
-
* Creates a new handoff document with auto-detected metadata:
|
|
505
|
-
* - Current timestamp
|
|
506
|
-
* - Project path
|
|
507
|
-
* - Git branch (if available)
|
|
508
|
-
* - Recent git log
|
|
509
|
-
* - Modified/staged files
|
|
510
|
-
* - Handoff chain linking
|
|
511
|
-
*
|
|
512
|
-
* Usage:
|
|
513
|
-
* node create_handoff.js [task-slug]
|
|
514
|
-
* node create_handoff.js "implementing-auth"
|
|
515
|
-
* node create_handoff.js "auth-part-2" --continues-from 2024-01-15-auth.md
|
|
516
|
-
* node create_handoff.js "auth-part-2" --flow add-auth
|
|
517
|
-
* node create_handoff.js # auto-generates slug from timestamp
|
|
518
|
-
*/
|
|
519
|
-
|
|
520
214
|
const fs = require('node:fs');
|
|
521
215
|
const path = require('node:path');
|
|
522
216
|
const { execSync } = require('node:child_process');
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
function
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
const allModified = [...new Set([...git.modifiedFiles, ...git.stagedFiles])];
|
|
724
|
-
let modifiedSection;
|
|
725
|
-
if (allModified.length > 0) {
|
|
726
|
-
modifiedSection = allModified
|
|
727
|
-
.slice(0, 10)
|
|
728
|
-
.map((f) => \`| \${f} | [describe changes] | [why changed] |\`)
|
|
729
|
-
.join('\\n');
|
|
730
|
-
if (allModified.length > 10)
|
|
731
|
-
modifiedSection += \`\\n| ... and \${allModified.length - 10} more files | | |\`;
|
|
732
|
-
} else {
|
|
733
|
-
modifiedSection = '| [no modified files detected] | | |';
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
let chainSection;
|
|
737
|
-
if (prev.exists) {
|
|
738
|
-
const currentFlowDir = resolvedFlowSlug || '_standalone';
|
|
739
|
-
const prevFlowDir = prev.flow || '_standalone';
|
|
740
|
-
const prevPath =
|
|
741
|
-
prevFlowDir === currentFlowDir ? \`./\${prev.filename}\` : \`../\${prevFlowDir}/\${prev.filename}\`;
|
|
742
|
-
chainSection = \`## Handoff Chain
|
|
743
|
-
|
|
744
|
-
- **Continues from**: [\${prev.filename}](\${prevPath})
|
|
745
|
-
- Previous title: \${prev.title || 'Unknown'}
|
|
746
|
-
- **Supersedes**: [list any older handoffs this replaces, or "None"]
|
|
747
|
-
|
|
748
|
-
> Review the previous handoff for full context before filling this one.\`;
|
|
749
|
-
} else {
|
|
750
|
-
chainSection = \`## Handoff Chain
|
|
751
|
-
|
|
752
|
-
- **Continues from**: None (fresh start)
|
|
753
|
-
- **Supersedes**: None
|
|
754
|
-
|
|
755
|
-
> This is the first handoff for this task.\`;
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
const content = \`# Handoff: [TASK_TITLE - replace this]
|
|
759
|
-
|
|
760
|
-
## Session Metadata
|
|
761
|
-
- Created: \${timestamp}
|
|
762
|
-
- Project: \${projectPath}
|
|
763
|
-
- Branch: \${branchLine}
|
|
764
|
-
- Session duration: [estimate how long you worked]
|
|
765
|
-
|
|
766
|
-
### Recent Commits (for context)
|
|
767
|
-
\${commitsSection}
|
|
768
|
-
|
|
769
|
-
\${chainSection}
|
|
770
|
-
|
|
771
|
-
## Current State Summary
|
|
772
|
-
|
|
773
|
-
[TODO: Write one paragraph describing what was being worked on, current status, and where things left off]
|
|
774
|
-
|
|
775
|
-
## Codebase Understanding
|
|
776
|
-
|
|
777
|
-
### Architecture Overview
|
|
778
|
-
|
|
779
|
-
[TODO: Document key architectural insights discovered during this session]
|
|
780
|
-
|
|
781
|
-
### Critical Files
|
|
782
|
-
|
|
783
|
-
| File | Purpose | Relevance |
|
|
784
|
-
|------|---------|-----------|
|
|
785
|
-
| [TODO: Add critical files] | | |
|
|
786
|
-
|
|
787
|
-
### Key Patterns Discovered
|
|
788
|
-
|
|
789
|
-
[TODO: Document important patterns, conventions, or idioms found in this codebase]
|
|
790
|
-
|
|
791
|
-
## Work Completed
|
|
792
|
-
|
|
793
|
-
### Tasks Finished
|
|
794
|
-
|
|
795
|
-
- [ ] [TODO: List completed tasks]
|
|
796
|
-
|
|
797
|
-
### Files Modified
|
|
798
|
-
|
|
799
|
-
| File | Changes | Rationale |
|
|
800
|
-
|------|---------|-----------|
|
|
801
|
-
\${modifiedSection}
|
|
802
|
-
|
|
803
|
-
### Decisions Made
|
|
804
|
-
|
|
805
|
-
| Decision | Options Considered | Rationale |
|
|
806
|
-
|----------|-------------------|-----------|
|
|
807
|
-
| [TODO: Document key decisions] | | |
|
|
808
|
-
|
|
809
|
-
## Pending Work
|
|
810
|
-
|
|
811
|
-
### Immediate Next Steps
|
|
812
|
-
|
|
813
|
-
1. [TODO: Most critical next action]
|
|
814
|
-
2. [TODO: Second priority]
|
|
815
|
-
3. [TODO: Third priority]
|
|
816
|
-
|
|
817
|
-
### Blockers/Open Questions
|
|
818
|
-
|
|
819
|
-
- [ ] [TODO: List blockers or open questions]
|
|
820
|
-
|
|
821
|
-
### Deferred Items
|
|
822
|
-
|
|
823
|
-
- [TODO: Items deferred and why]
|
|
824
|
-
|
|
825
|
-
## Context for Resuming Agent
|
|
826
|
-
|
|
827
|
-
### Important Context
|
|
828
|
-
|
|
829
|
-
[TODO: Critical information the next agent MUST know]
|
|
830
|
-
|
|
831
|
-
### Assumptions Made
|
|
832
|
-
|
|
833
|
-
[TODO: List assumptions that might not be obvious]
|
|
834
|
-
|
|
835
|
-
### Potential Gotchas
|
|
836
|
-
|
|
837
|
-
[TODO: Things that could trip up the next agent]
|
|
838
|
-
|
|
839
|
-
## Environment State
|
|
840
|
-
|
|
841
|
-
[TODO: Required env vars, running processes, DB state]
|
|
842
|
-
|
|
843
|
-
## Related Resources
|
|
844
|
-
|
|
845
|
-
[TODO: Links to docs, PRs, issues, Slack threads]
|
|
846
|
-
\`;
|
|
847
|
-
|
|
848
|
-
fs.writeFileSync(filepath, content, 'utf-8');
|
|
849
|
-
|
|
850
|
-
const compactContent = \`## Handoff: \${slug}
|
|
851
|
-
Branch: \${branchLine} | Created: \${timestamp}
|
|
852
|
-
|
|
853
|
-
### State
|
|
854
|
-
[TODO: 1-2 sentences — what's happening, where we left off]
|
|
855
|
-
|
|
856
|
-
### Decisions
|
|
857
|
-
- [TODO: key decisions with rationale]
|
|
858
|
-
|
|
859
|
-
### Next Steps
|
|
860
|
-
1. [TODO: most critical action]
|
|
861
|
-
2. [TODO: second priority]
|
|
862
|
-
|
|
863
|
-
### Blockers
|
|
864
|
-
- [TODO: what's blocking progress]
|
|
865
|
-
|
|
866
|
-
### Assumptions
|
|
867
|
-
- [TODO: assumptions that if wrong, change the approach]\`;
|
|
868
|
-
|
|
869
|
-
return { filepath, filename, compactContent };
|
|
217
|
+
const runCmd = (cmd, cwd) => {
|
|
218
|
+
try { return { ok: true, out: execSync(cmd, { cwd, timeout: 10000, encoding: "utf-8" }).trim() }; }
|
|
219
|
+
catch { return { ok: false, out: "" }; }
|
|
220
|
+
};
|
|
221
|
+
function getGitInfo(root) {
|
|
222
|
+
const info = { isGitRepo: false, branch: null, recentCommits: [], modifiedFiles: [], stagedFiles: [] };
|
|
223
|
+
if (!runCmd("git rev-parse --git-dir", root).ok) return info;
|
|
224
|
+
info.isGitRepo = true;
|
|
225
|
+
const branch = runCmd("git branch --show-current", root); if (branch.ok && branch.out) info.branch = branch.out;
|
|
226
|
+
const log = runCmd("git log --oneline -5 --no-decorate", root); if (log.ok && log.out) info.recentCommits = log.out.split("\\n");
|
|
227
|
+
const modified = runCmd("git diff --name-only", root); if (modified.ok && modified.out) info.modifiedFiles = modified.out.split("\\n");
|
|
228
|
+
const staged = runCmd("git diff --name-only --cached", root); if (staged.ok && staged.out) info.stagedFiles = staged.out.split("\\n");
|
|
229
|
+
return info;
|
|
230
|
+
}
|
|
231
|
+
function getActiveFlowSlug(root) {
|
|
232
|
+
const flowsDir = path.join(root, ".flows");
|
|
233
|
+
if (!fs.existsSync(flowsDir)) return null;
|
|
234
|
+
for (const name of fs.readdirSync(flowsDir)) {
|
|
235
|
+
const flowPath = path.join(flowsDir, name);
|
|
236
|
+
const metaPath = path.join(flowPath, "meta.json");
|
|
237
|
+
try {
|
|
238
|
+
if (!name.startsWith(".") && fs.statSync(flowPath).isDirectory() && fs.existsSync(metaPath)) {
|
|
239
|
+
const meta = JSON.parse(fs.readFileSync(metaPath, "utf-8"));
|
|
240
|
+
if (meta.status === "active") return name;
|
|
241
|
+
}
|
|
242
|
+
} catch {}
|
|
243
|
+
}
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
function getHandoffsDir(root, flow) {
|
|
247
|
+
const active = flow || getActiveFlowSlug(root);
|
|
248
|
+
return path.join(root, ".aikit-state", "handoffs", active || "_standalone");
|
|
249
|
+
}
|
|
250
|
+
function listPrevious(root, flow) {
|
|
251
|
+
const baseDir = path.join(root, ".aikit-state", "handoffs");
|
|
252
|
+
const handoffs = [];
|
|
253
|
+
const collect = (dir, flowName) => {
|
|
254
|
+
if (!fs.existsSync(dir)) return;
|
|
255
|
+
for (const name of fs.readdirSync(dir)) {
|
|
256
|
+
if (!name.endsWith(".md")) continue;
|
|
257
|
+
const file = path.join(dir, name);
|
|
258
|
+
let title = name; let date = null;
|
|
259
|
+
try { const content = fs.readFileSync(file, "utf-8"); const match = content.match(/^#\\s+(?:Handoff:\\s*)?(.+)$/m); if (match) title = match[1].trim(); } catch {}
|
|
260
|
+
const dm = name.match(/^(\\d{4}-\\d{2}-\\d{2})-(\\d{6})/);
|
|
261
|
+
if (dm) { try { date = new Date(dm[1] + "T" + dm[2].slice(0,2) + ":" + dm[2].slice(2,4) + ":" + dm[2].slice(4,6)); } catch {} }
|
|
262
|
+
handoffs.push({ filename: name, path: file, title, date, flow: flowName });
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
if (!fs.existsSync(baseDir)) return handoffs;
|
|
266
|
+
const dirs = flow ? [flow] : fs.readdirSync(baseDir).filter((name) => { try { return fs.statSync(path.join(baseDir, name)).isDirectory(); } catch { return false; } });
|
|
267
|
+
for (const dir of dirs) collect(path.join(baseDir, dir), dir === "_standalone" ? null : dir);
|
|
268
|
+
handoffs.sort((a, b) => (b.date || 0) - (a.date || 0));
|
|
269
|
+
return handoffs;
|
|
270
|
+
}
|
|
271
|
+
const sanitizeSlug = (slug) => slug.toLowerCase().replace(/[\\s_]/g, "-").replace(/[^a-z0-9-]/g, "");
|
|
272
|
+
function getPrevious(root, from, flow) {
|
|
273
|
+
const handoffs = listPrevious(root, flow);
|
|
274
|
+
if (from) {
|
|
275
|
+
const found = handoffs.find((item) => item.filename.includes(from));
|
|
276
|
+
return found ? { exists: true, filename: found.filename, title: found.title, flow: found.flow } : { exists: false };
|
|
277
|
+
}
|
|
278
|
+
return handoffs[0] ? { exists: true, filename: handoffs[0].filename, title: handoffs[0].title, flow: handoffs[0].flow } : { exists: false };
|
|
279
|
+
}
|
|
280
|
+
function generateHandoff(root, slug, from, flow) {
|
|
281
|
+
const now = new Date();
|
|
282
|
+
const stamp = now.toISOString().replace("T", " ").slice(0, 19);
|
|
283
|
+
const fileTs = now.toISOString().slice(0,10) + "-" + String(now.getHours()).padStart(2,"0") + String(now.getMinutes()).padStart(2,"0") + String(now.getSeconds()).padStart(2,"0");
|
|
284
|
+
const safeSlug = sanitizeSlug(slug || "handoff");
|
|
285
|
+
const filename = fileTs + "-" + safeSlug + ".md";
|
|
286
|
+
const activeFlow = flow || getActiveFlowSlug(root);
|
|
287
|
+
const dir = getHandoffsDir(root, flow);
|
|
288
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
289
|
+
const filepath = path.join(dir, filename);
|
|
290
|
+
const git = getGitInfo(root);
|
|
291
|
+
const prev = getPrevious(root, from, activeFlow);
|
|
292
|
+
const branch = git.branch || "[not a git repo or detached HEAD]";
|
|
293
|
+
const commits = git.recentCommits.length ? git.recentCommits.map((c) => " - " + c).join("\\n") : " - [none]";
|
|
294
|
+
const files = [...new Set([...git.modifiedFiles, ...git.stagedFiles])];
|
|
295
|
+
let modified = files.length ? files.slice(0, 10).map((f) => "| " + f + " | [changes] | [why] |").join("\\n") : "| [no modified files detected] | | |";
|
|
296
|
+
if (files.length > 10) modified += "\\n| ... and " + (files.length - 10) + " more files | | |";
|
|
297
|
+
let chain;
|
|
298
|
+
if (prev.exists) {
|
|
299
|
+
const currentFlow = activeFlow || "_standalone";
|
|
300
|
+
const prevFlow = prev.flow || "_standalone";
|
|
301
|
+
const prevPath = prevFlow === currentFlow ? "./" + prev.filename : "../" + prevFlow + "/" + prev.filename;
|
|
302
|
+
chain = ["## Handoff Chain", "", "- **Continues from**: [" + prev.filename + "](" + prevPath + ")", " - Previous title: " + (prev.title || "Unknown"), "- **Supersedes**: [list older handoffs replaced, or \\"None\\"]", "", "> Review previous handoff before filling this one."].join("\\n");
|
|
303
|
+
} else chain = ["## Handoff Chain", "", "- **Continues from**: None (fresh start)", "- **Supersedes**: None", "", "> First handoff for this task."].join("\\n");
|
|
304
|
+
const content = [
|
|
305
|
+
"# Handoff: [TASK_TITLE - replace this]",
|
|
306
|
+
"",
|
|
307
|
+
"## Session Metadata",
|
|
308
|
+
"- Created: " + stamp,
|
|
309
|
+
"- Project: " + root,
|
|
310
|
+
"- Branch: " + branch,
|
|
311
|
+
"- Session duration: [estimate]",
|
|
312
|
+
"",
|
|
313
|
+
"### Recent Commits",
|
|
314
|
+
commits,
|
|
315
|
+
"",
|
|
316
|
+
chain,
|
|
317
|
+
"",
|
|
318
|
+
"## Current State Summary",
|
|
319
|
+
"",
|
|
320
|
+
"[TODO: Summarize work, status, handoff point]",
|
|
321
|
+
"",
|
|
322
|
+
"## Codebase Understanding",
|
|
323
|
+
"",
|
|
324
|
+
"### Architecture Overview",
|
|
325
|
+
"",
|
|
326
|
+
"[TODO: Note key architecture insights]",
|
|
327
|
+
"",
|
|
328
|
+
"### Critical Files",
|
|
329
|
+
"",
|
|
330
|
+
"| File | Purpose | Relevance |",
|
|
331
|
+
"|------|---------|-----------|",
|
|
332
|
+
"| [TODO: Add critical files] | | |",
|
|
333
|
+
"",
|
|
334
|
+
"### Key Patterns Discovered",
|
|
335
|
+
"",
|
|
336
|
+
"[TODO: Note key patterns and conventions]",
|
|
337
|
+
"",
|
|
338
|
+
"## Work Completed",
|
|
339
|
+
"",
|
|
340
|
+
"### Tasks Finished",
|
|
341
|
+
"",
|
|
342
|
+
"- [ ] [TODO: List completed tasks]",
|
|
343
|
+
"",
|
|
344
|
+
"### Files Modified",
|
|
345
|
+
"",
|
|
346
|
+
"| File | Changes | Rationale |",
|
|
347
|
+
"|------|---------|-----------|",
|
|
348
|
+
modified,
|
|
349
|
+
"",
|
|
350
|
+
"### Decisions Made",
|
|
351
|
+
"",
|
|
352
|
+
"| Decision | Options Considered | Rationale |",
|
|
353
|
+
"|----------|-------------------|-----------|",
|
|
354
|
+
"| [TODO: Key decisions] | | |",
|
|
355
|
+
"",
|
|
356
|
+
"## Pending Work",
|
|
357
|
+
"",
|
|
358
|
+
"### Immediate Next Steps",
|
|
359
|
+
"",
|
|
360
|
+
"1. [TODO: First action]",
|
|
361
|
+
"2. [TODO: Second action]",
|
|
362
|
+
"3. [TODO: Third action]",
|
|
363
|
+
"",
|
|
364
|
+
"### Blockers/Open Questions",
|
|
365
|
+
"",
|
|
366
|
+
"- [ ] [TODO: Blockers or open questions]",
|
|
367
|
+
"",
|
|
368
|
+
"### Deferred Items",
|
|
369
|
+
"",
|
|
370
|
+
"- [TODO: Deferred items + why]",
|
|
371
|
+
"",
|
|
372
|
+
"## Context for Resuming Agent",
|
|
373
|
+
"",
|
|
374
|
+
"### Important Context",
|
|
375
|
+
"",
|
|
376
|
+
"[TODO: Critical context next agent MUST know]",
|
|
377
|
+
"",
|
|
378
|
+
"### Assumptions Made",
|
|
379
|
+
"",
|
|
380
|
+
"[TODO: Assumptions]",
|
|
381
|
+
"",
|
|
382
|
+
"### Potential Gotchas",
|
|
383
|
+
"",
|
|
384
|
+
"[TODO: Gotchas]",
|
|
385
|
+
"",
|
|
386
|
+
"## Environment State",
|
|
387
|
+
"",
|
|
388
|
+
"[TODO: Env vars, processes, DB state]",
|
|
389
|
+
"",
|
|
390
|
+
"## Related Resources",
|
|
391
|
+
"",
|
|
392
|
+
"[TODO: Docs, PRs, issues, threads]"
|
|
393
|
+
].join("\\n");
|
|
394
|
+
fs.writeFileSync(filepath, content, "utf-8");
|
|
395
|
+
const compact = [
|
|
396
|
+
"## Handoff: " + safeSlug,
|
|
397
|
+
"Branch: " + branch + " | Created: " + stamp,
|
|
398
|
+
"",
|
|
399
|
+
"### State",
|
|
400
|
+
"[TODO: 1-2 sentences — state + handoff point]",
|
|
401
|
+
"",
|
|
402
|
+
"### Decisions",
|
|
403
|
+
"- [TODO: key decisions + rationale]",
|
|
404
|
+
"",
|
|
405
|
+
"### Next Steps",
|
|
406
|
+
"1. [TODO: first action]",
|
|
407
|
+
"2. [TODO: second action]",
|
|
408
|
+
"",
|
|
409
|
+
"### Blockers",
|
|
410
|
+
"- [TODO: blockers]",
|
|
411
|
+
"",
|
|
412
|
+
"### Assumptions",
|
|
413
|
+
"- [TODO: assumptions that could change approach]"
|
|
414
|
+
].join("\\n");
|
|
415
|
+
return { filepath, compact };
|
|
870
416
|
}
|
|
871
|
-
|
|
872
|
-
// --- Main ---
|
|
873
417
|
const args = process.argv.slice(2);
|
|
874
|
-
if (args.includes(
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
process.exit(0);
|
|
418
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
419
|
+
console.log("Usage: node create_handoff.js [task-slug] [--continues-from <previous>] [--flow <flow-slug>]");
|
|
420
|
+
console.log(" node create_handoff.js --continues-from 2024-01-15-auth.md");
|
|
421
|
+
console.log(" node create_handoff.js \\"auth-part-2\\" --flow add-auth");
|
|
422
|
+
process.exit(0);
|
|
880
423
|
}
|
|
881
|
-
|
|
882
|
-
let slug = null;
|
|
883
|
-
let continuesFrom = null;
|
|
884
|
-
let flowSlug = null;
|
|
424
|
+
let slug = null; let from = null; let flow = null;
|
|
885
425
|
for (let i = 0; i < args.length; i++) {
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
console.log(
|
|
898
|
-
console.log('\\nCompact flow knowledge entry:');
|
|
899
|
-
console.log('---');
|
|
900
|
-
console.log(compactContent);
|
|
901
|
-
console.log('---');
|
|
902
|
-
console.log(\`\\nNext: Open the file and fill in all [TODO: ...] sections.\`);
|
|
903
|
-
console.log(\`Then validate: node scripts/validate_handoff.js \${filepath}\`);
|
|
426
|
+
if (args[i] === "--continues-from" && i + 1 < args.length) from = args[++i];
|
|
427
|
+
else if (args[i] === "--flow" && i + 1 < args.length) flow = sanitizeSlug(args[++i]);
|
|
428
|
+
else if (!args[i].startsWith("-")) slug = args[i];
|
|
429
|
+
}
|
|
430
|
+
const result = generateHandoff(process.cwd(), slug, from, flow);
|
|
431
|
+
console.log("Created handoff: " + result.filepath);
|
|
432
|
+
console.log("\\nCompact flow knowledge entry:");
|
|
433
|
+
console.log("---");
|
|
434
|
+
console.log(result.compact);
|
|
435
|
+
console.log("---");
|
|
436
|
+
console.log("\\nNext: Open file and fill all [TODO: ...] sections.");
|
|
437
|
+
console.log("Then validate: node scripts/validate_handoff.js " + result.filepath);
|
|
904
438
|
`},{file:`scripts/list_handoffs.js`,content:`#!/usr/bin/env node
|
|
905
|
-
/**
|
|
906
|
-
* List available handoff documents in the current project.
|
|
907
|
-
*
|
|
908
|
-
* Searches for handoff documents in .aikit-state/handoffs/<flow-slug>/ and .aikit-state/handoffs/_standalone/ and displays:
|
|
909
|
-
* - Filename with date
|
|
910
|
-
* - Flow scope
|
|
911
|
-
* - Title extracted from document
|
|
912
|
-
* - Status (complete / in progress / needs work)
|
|
913
|
-
*
|
|
914
|
-
* Usage:
|
|
915
|
-
* node list_handoffs.js # List handoffs in current project
|
|
916
|
-
* node list_handoffs.js /path # List handoffs in specified path
|
|
917
|
-
*/
|
|
918
|
-
|
|
919
439
|
const fs = require('node:fs');
|
|
920
440
|
const path = require('node:path');
|
|
921
|
-
|
|
922
441
|
function extractTitle(filepath) {
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
return title.length > 50 ? \`\${title.slice(0, 50)}...\` : title;
|
|
930
|
-
}
|
|
931
|
-
} catch {
|
|
932
|
-
/* ignore */
|
|
933
|
-
}
|
|
934
|
-
return '[Unable to read title]';
|
|
442
|
+
try {
|
|
443
|
+
const content = fs.readFileSync(filepath, "utf-8");
|
|
444
|
+
const match = content.match(/^#\\s+(?:Handoff:\\s*)?(.+)$/m);
|
|
445
|
+
if (match) { const title = match[1].trim(); return title.startsWith("[") && title.endsWith("]") ? "[Untitled - needs completion]" : (title.length > 50 ? title.slice(0, 50) + "..." : title); }
|
|
446
|
+
} catch {}
|
|
447
|
+
return "[Unable to read title]";
|
|
935
448
|
}
|
|
936
|
-
|
|
937
449
|
function checkCompletion(filepath) {
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
} catch {
|
|
945
|
-
return 'Unknown';
|
|
946
|
-
}
|
|
450
|
+
try {
|
|
451
|
+
const count = (fs.readFileSync(filepath, "utf-8").match(/\\[TODO:/g) || []).length;
|
|
452
|
+
if (count === 0) return "Complete";
|
|
453
|
+
if (count <= 3) return "In Progress (" + count + " TODOs)";
|
|
454
|
+
return "Needs Work (" + count + " TODOs)";
|
|
455
|
+
} catch { return "Unknown"; }
|
|
947
456
|
}
|
|
948
|
-
|
|
949
457
|
function parseDateFromFilename(filename) {
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
handoffs.push({
|
|
974
|
-
path: fp,
|
|
975
|
-
filename: name,
|
|
976
|
-
flow,
|
|
977
|
-
title: extractTitle(fp),
|
|
978
|
-
status: checkCompletion(fp),
|
|
979
|
-
date: parseDateFromFilename(name),
|
|
980
|
-
size: stat.size,
|
|
981
|
-
});
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
if (fs.existsSync(baseDir)) {
|
|
986
|
-
const flowDirs = fs
|
|
987
|
-
.readdirSync(baseDir)
|
|
988
|
-
.filter((name) => {
|
|
989
|
-
try {
|
|
990
|
-
return fs.statSync(path.join(baseDir, name)).isDirectory();
|
|
991
|
-
} catch {
|
|
992
|
-
return false;
|
|
993
|
-
}
|
|
994
|
-
})
|
|
995
|
-
.sort((a, b) => {
|
|
996
|
-
try {
|
|
997
|
-
return fs.statSync(path.join(baseDir, b)).mtimeMs - fs.statSync(path.join(baseDir, a)).mtimeMs;
|
|
998
|
-
} catch {
|
|
999
|
-
return 0;
|
|
1000
|
-
}
|
|
1001
|
-
});
|
|
1002
|
-
|
|
1003
|
-
for (const flow of flowDirs) {
|
|
1004
|
-
collectFromDir(path.join(baseDir, flow), flow === '_standalone' ? null : flow);
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
handoffs.sort((a, b) => (b.date || 0) - (a.date || 0));
|
|
1009
|
-
return handoffs;
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
function formatDate(dt) {
|
|
1013
|
-
if (!dt) return 'Unknown date';
|
|
1014
|
-
return dt.toISOString().replace('T', ' ').slice(0, 16);
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
// --- Main ---
|
|
458
|
+
const match = filename.match(/^(\\d{4}-\\d{2}-\\d{2})-(\\d{6})/);
|
|
459
|
+
if (!match) return null;
|
|
460
|
+
try { return new Date(match[1] + "T" + match[2].slice(0,2) + ":" + match[2].slice(2,4) + ":" + match[2].slice(4,6)); } catch { return null; }
|
|
461
|
+
}
|
|
462
|
+
function listHandoffs(root) {
|
|
463
|
+
const handoffs = [];
|
|
464
|
+
const baseDir = path.join(root, ".aikit-state", "handoffs");
|
|
465
|
+
const collect = (dir, flow) => {
|
|
466
|
+
if (!fs.existsSync(dir)) return;
|
|
467
|
+
for (const name of fs.readdirSync(dir)) {
|
|
468
|
+
if (!name.endsWith(".md")) continue;
|
|
469
|
+
const fp = path.join(dir, name); const stat = fs.statSync(fp);
|
|
470
|
+
handoffs.push({ path: fp, filename: name, flow, title: extractTitle(fp), status: checkCompletion(fp), date: parseDateFromFilename(name), size: stat.size });
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
if (fs.existsSync(baseDir)) {
|
|
474
|
+
const flowDirs = fs.readdirSync(baseDir).filter((name) => { try { return fs.statSync(path.join(baseDir, name)).isDirectory(); } catch { return false; } }).sort((a, b) => { try { return fs.statSync(path.join(baseDir, b)).mtimeMs - fs.statSync(path.join(baseDir, a)).mtimeMs; } catch { return 0; } });
|
|
475
|
+
for (const flow of flowDirs) collect(path.join(baseDir, flow), flow === "_standalone" ? null : flow);
|
|
476
|
+
}
|
|
477
|
+
handoffs.sort((a, b) => (b.date || 0) - (a.date || 0));
|
|
478
|
+
return handoffs;
|
|
479
|
+
}
|
|
480
|
+
const formatDate = (dt) => dt ? dt.toISOString().replace("T", " ").slice(0, 16) : "Unknown date";
|
|
1018
481
|
const args = process.argv.slice(2);
|
|
1019
|
-
if (args.includes(
|
|
1020
|
-
console.log('Usage: node list_handoffs.js [project-path]');
|
|
1021
|
-
process.exit(0);
|
|
1022
|
-
}
|
|
1023
|
-
|
|
482
|
+
if (args.includes("--help") || args.includes("-h")) { console.log("Usage: node list_handoffs.js [project-path]"); process.exit(0); }
|
|
1024
483
|
const projectPath = args[0] || process.cwd();
|
|
1025
484
|
const handoffs = listHandoffs(projectPath);
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
console.log(
|
|
1036
|
-
console.log(
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
for (const h of handoffs) {
|
|
1040
|
-
const flowLabel = h.flow || '[standalone]';
|
|
1041
|
-
console.log(\`\${formatDate(h.date).padEnd(18)} \${flowLabel.padEnd(18)} \${h.status.padEnd(25)} \${h.title}\`);
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
if (args.includes('--json')) {
|
|
1045
|
-
console.log(\`\\n\${JSON.stringify(handoffs, null, 2)}\`);
|
|
1046
|
-
}
|
|
485
|
+
if (!handoffs.length) {
|
|
486
|
+
console.log("No handoff documents found.");
|
|
487
|
+
console.log("Looked in: " + path.join(projectPath, ".aikit-state", "handoffs", "*"));
|
|
488
|
+
console.log(" " + path.join(projectPath, ".aikit-state", "handoffs", "_standalone"));
|
|
489
|
+
console.log("\\nCreate one with: node scripts/create_handoff.js [task-slug]");
|
|
490
|
+
process.exit(0);
|
|
491
|
+
}
|
|
492
|
+
console.log("\\nFound " + handoffs.length + " handoff(s) in " + projectPath + ":\\n");
|
|
493
|
+
console.log(String("Date").padEnd(18) + " " + String("Flow").padEnd(18) + " " + String("Status").padEnd(25) + " Title");
|
|
494
|
+
console.log("-".repeat(18) + " " + "-".repeat(18) + " " + "-".repeat(25) + " " + "-".repeat(40));
|
|
495
|
+
for (const handoff of handoffs) console.log(formatDate(handoff.date).padEnd(18) + " " + String(handoff.flow || "[standalone]").padEnd(18) + " " + handoff.status.padEnd(25) + " " + handoff.title);
|
|
496
|
+
if (args.includes("--json")) console.log("\\n" + JSON.stringify(handoffs, null, 2));
|
|
1047
497
|
`},{file:`scripts/validate_handoff.js`,content:`#!/usr/bin/env node
|
|
1048
|
-
/**
|
|
1049
|
-
* Validate a handoff document for completeness and quality.
|
|
1050
|
-
*
|
|
1051
|
-
* Checks:
|
|
1052
|
-
* - No TODO placeholders remaining
|
|
1053
|
-
* - Required sections present and populated
|
|
1054
|
-
* - No potential secrets detected
|
|
1055
|
-
* - Referenced files exist
|
|
1056
|
-
* - Quality scoring (0-100)
|
|
1057
|
-
*
|
|
1058
|
-
* Usage:
|
|
1059
|
-
* node validate_handoff.js <handoff-file>
|
|
1060
|
-
* node validate_handoff.js .aikit-state/handoffs/add-auth/2024-01-15-143022-auth.md
|
|
1061
|
-
* node validate_handoff.js .aikit-state/handoffs/_standalone/2024-01-15-143022-auth.md
|
|
1062
|
-
*/
|
|
1063
|
-
|
|
1064
498
|
const fs = require('node:fs');
|
|
1065
499
|
const path = require('node:path');
|
|
1066
|
-
|
|
1067
|
-
// Secret detection patterns
|
|
1068
500
|
const SECRET_PATTERNS = [
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
[/ghp_[a-zA-Z0-9]{36}/g, 'GitHub personal access token'],
|
|
1080
|
-
[/sk-[a-zA-Z0-9]{48}/g, 'OpenAI API key'],
|
|
1081
|
-
[/xox[baprs]-[a-zA-Z0-9-]+/g, 'Slack token'],
|
|
501
|
+
[/[\\"']?[a-zA-Z_]*api[_-]?key[\\"']?\\\\s*[:=]\\\\s*[\\"'][^\\"']{10,}[\\"']/gi, 'API key'],
|
|
502
|
+
[/[\\"']?[a-zA-Z_]*password[\\"']?\\\\s*[:=]\\\\s*[\\"'][^\\"']+[\\"']/gi, 'Password'],
|
|
503
|
+
[/[\\"']?[a-zA-Z_]*secret[\\"']?\\\\s*[:=]\\\\s*[\\"'][^\\"']{10,}[\\"']/gi, 'Secret'],
|
|
504
|
+
[/[\\"']?[a-zA-Z_]*token[\\"']?\\\\s*[:=]\\\\s*[\\"'][^\\"']{20,}[\\"']/gi, 'Token'],
|
|
505
|
+
[/[\\"']?[a-zA-Z_]*private[_-]?key[\\"']?\\\\s*[:=]/gi, 'Private key'],
|
|
506
|
+
[/-----BEGIN [A-Z]+ PRIVATE KEY-----/g, 'PEM key'],
|
|
507
|
+
[/Bearer\\\\s+[A-Za-z0-9_.-]+/g, 'Bearer token'],
|
|
508
|
+
[/ghp_[a-zA-Z0-9]{36}/g, 'GitHub PAT'],
|
|
509
|
+
[/sk-[a-zA-Z0-9]{48}/g, 'OpenAI key'],
|
|
510
|
+
[/xox[baprs]-[a-zA-Z0-9-]+/g, 'Slack token'],
|
|
1082
511
|
];
|
|
1083
|
-
|
|
1084
512
|
const REQUIRED_SECTIONS = ['Current State Summary', 'Important Context', 'Immediate Next Steps'];
|
|
1085
|
-
const RECOMMENDED_SECTIONS = [
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
'Files Modified',
|
|
1089
|
-
'Decisions Made',
|
|
1090
|
-
'Assumptions Made',
|
|
1091
|
-
'Potential Gotchas',
|
|
1092
|
-
];
|
|
1093
|
-
|
|
1094
|
-
function checkTodos(content) {
|
|
1095
|
-
const todos = content.match(/\\[TODO:[^\\]]*\\]/g) || [];
|
|
1096
|
-
return { clear: todos.length === 0, todos };
|
|
1097
|
-
}
|
|
1098
|
-
|
|
513
|
+
const RECOMMENDED_SECTIONS = ['Architecture Overview', 'Critical Files', 'Files Modified', 'Decisions Made', 'Assumptions Made', 'Potential Gotchas'];
|
|
514
|
+
const escapeRe = (value) => value.replace(/[.*+?^\${}()|[\\\\]\\\\]/g, '\\\\$&');
|
|
515
|
+
function checkTodos(content) { const todos = content.match(/\\\\[TODO:[^\\\\]]*\\\\]/g) || []; return { clear: todos.length === 0, todos }; }
|
|
1099
516
|
function checkRequiredSections(content) {
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
const end = nextSection >= 0 ? start + nextSection : content.length;
|
|
1113
|
-
const sectionContent = content.slice(start, end).trim();
|
|
1114
|
-
if (sectionContent.length < 50 || sectionContent.includes('[TODO')) {
|
|
1115
|
-
missing.push(\`\${section} (incomplete)\`);
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
return { complete: missing.length === 0, missing };
|
|
517
|
+
const missing = [];
|
|
518
|
+
for (const section of REQUIRED_SECTIONS) {
|
|
519
|
+
const pattern = new RegExp('(?:^|\\\\n)##?\\\\s*' + escapeRe(section), 'i');
|
|
520
|
+
const match = pattern.exec(content);
|
|
521
|
+
if (!match) { missing.push(section + ' (missing)'); continue; }
|
|
522
|
+
const start = match.index + match[0].length;
|
|
523
|
+
const nextSection = content.slice(start).search(/\\\\n##?\\\\s+/);
|
|
524
|
+
const end = nextSection >= 0 ? start + nextSection : content.length;
|
|
525
|
+
const sectionContent = content.slice(start, end).trim();
|
|
526
|
+
if (sectionContent.length < 50 || sectionContent.includes('[TODO')) missing.push(section + ' (incomplete)');
|
|
527
|
+
}
|
|
528
|
+
return { complete: missing.length === 0, missing };
|
|
1120
529
|
}
|
|
1121
|
-
|
|
1122
530
|
function checkRecommendedSections(content) {
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
if (!pattern.test(content)) missing.push(section);
|
|
1130
|
-
}
|
|
1131
|
-
return missing;
|
|
531
|
+
const missing = [];
|
|
532
|
+
for (const section of RECOMMENDED_SECTIONS) {
|
|
533
|
+
const pattern = new RegExp('(?:^|\\\\n)##?\\\\s*' + escapeRe(section), 'i');
|
|
534
|
+
if (!pattern.test(content)) missing.push(section);
|
|
535
|
+
}
|
|
536
|
+
return missing;
|
|
1132
537
|
}
|
|
1133
|
-
|
|
1134
538
|
function scanForSecrets(content) {
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
if (matches) findings.push({ description, count: matches.length });
|
|
1139
|
-
}
|
|
1140
|
-
return findings;
|
|
539
|
+
const findings = [];
|
|
540
|
+
for (const [pattern, description] of SECRET_PATTERNS) { const matches = content.match(pattern); if (matches) findings.push({ description, count: matches.length }); }
|
|
541
|
+
return findings;
|
|
1141
542
|
}
|
|
1142
|
-
|
|
1143
543
|
function checkFileReferences(content, basePath) {
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
}
|
|
1166
|
-
return { existing, missing };
|
|
544
|
+
const patterns = [/\\\\|\\\\s*([A-Za-z0-9_./-]+\\\\.[a-zA-Z]+)\\\\s*\\\\|/g, /\`([A-Za-z0-9_./-]+\\\\.[a-zA-Z]+(?::\\\\d+)?)\`/g, /(?:^|\\\\s)([A-Za-z0-9_./-]+\\\\.[a-zA-Z]+:\\\\d+)/gm];
|
|
545
|
+
const foundFiles = new Set();
|
|
546
|
+
for (const pattern of patterns) {
|
|
547
|
+
for (const match of content.matchAll(pattern)) {
|
|
548
|
+
const filepath = match[1].split(':')[0];
|
|
549
|
+
if (filepath && !filepath.startsWith('http') && filepath.includes('/')) foundFiles.add(filepath);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
const existing = []; const missing = [];
|
|
553
|
+
for (const file of foundFiles) (fs.existsSync(path.join(basePath, file)) ? existing : missing).push(file);
|
|
554
|
+
return { existing, missing };
|
|
555
|
+
}
|
|
556
|
+
function calculateScore(todosClear, requiredComplete, missingRequired, missingRecommended, secretsFound, filesMissing) {
|
|
557
|
+
let score = 100;
|
|
558
|
+
if (!todosClear) score -= 30;
|
|
559
|
+
if (!requiredComplete) score -= 10 * missingRequired.length;
|
|
560
|
+
if (secretsFound.length) score -= 20;
|
|
561
|
+
if (filesMissing.length) score -= 5 * Math.min(filesMissing.length, 4);
|
|
562
|
+
score -= 2 * missingRecommended.length;
|
|
563
|
+
score = Math.max(0, score);
|
|
564
|
+
return { score, rating: score >= 90 ? 'Excellent' : score >= 70 ? 'Good' : score >= 50 ? 'Fair' : 'Poor' };
|
|
1167
565
|
}
|
|
1168
|
-
|
|
1169
|
-
function calculateScore(todosClear, reqComplete, missingReq, missingRec, secrets, filesMissing) {
|
|
1170
|
-
let score = 100;
|
|
1171
|
-
|
|
1172
|
-
if (!todosClear) score -= 30;
|
|
1173
|
-
if (!reqComplete) score -= 10 * missingReq.length;
|
|
1174
|
-
if (secrets.length > 0) score -= 20;
|
|
1175
|
-
if (filesMissing.length > 0) score -= 5 * Math.min(filesMissing.length, 4);
|
|
1176
|
-
score -= 2 * missingRec.length;
|
|
1177
|
-
|
|
1178
|
-
score = Math.max(0, score);
|
|
1179
|
-
|
|
1180
|
-
let rating;
|
|
1181
|
-
if (score >= 90) rating = 'Excellent - Ready for handoff';
|
|
1182
|
-
else if (score >= 70) rating = 'Good - Minor improvements suggested';
|
|
1183
|
-
else if (score >= 50) rating = 'Fair - Needs attention before handoff';
|
|
1184
|
-
else rating = 'Poor - Significant work needed';
|
|
1185
|
-
|
|
1186
|
-
return { score, rating };
|
|
1187
|
-
}
|
|
1188
|
-
|
|
1189
566
|
function resolveProjectRootFromHandoff(filepath) {
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
return isFlowScoped ? path.resolve(handoffsDir, '..', '..', '..') : path.resolve(handoffsDir, '..');
|
|
567
|
+
const handoffsDir = path.dirname(filepath);
|
|
568
|
+
const handoffMarkerDir = path.basename(path.dirname(handoffsDir));
|
|
569
|
+
const stateMarkerDir = path.basename(path.dirname(path.dirname(handoffsDir)));
|
|
570
|
+
return handoffMarkerDir === 'handoffs' && stateMarkerDir === '.aikit-state' ? path.resolve(handoffsDir, '..', '..', '..') : path.resolve(handoffsDir, '..');
|
|
1196
571
|
}
|
|
1197
|
-
|
|
1198
572
|
function validateHandoff(filepath) {
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
const { score, rating } = calculateScore(
|
|
1211
|
-
todosClear,
|
|
1212
|
-
reqComplete,
|
|
1213
|
-
missingReq,
|
|
1214
|
-
missingRec,
|
|
1215
|
-
secrets,
|
|
1216
|
-
missingFiles,
|
|
1217
|
-
);
|
|
1218
|
-
|
|
1219
|
-
return {
|
|
1220
|
-
filepath,
|
|
1221
|
-
score,
|
|
1222
|
-
rating,
|
|
1223
|
-
todosClear,
|
|
1224
|
-
remainingTodos: todos.slice(0, 5),
|
|
1225
|
-
todoCount: todos.length,
|
|
1226
|
-
requiredComplete: reqComplete,
|
|
1227
|
-
missingRequired: missingReq,
|
|
1228
|
-
missingRecommended: missingRec,
|
|
1229
|
-
secretsFound: secrets,
|
|
1230
|
-
filesVerified: existingFiles.length,
|
|
1231
|
-
filesMissing: missingFiles.slice(0, 5),
|
|
1232
|
-
};
|
|
573
|
+
if (!fs.existsSync(filepath)) return { error: 'File not found: ' + filepath };
|
|
574
|
+
const content = fs.readFileSync(filepath, 'utf-8');
|
|
575
|
+
const basePath = resolveProjectRootFromHandoff(filepath);
|
|
576
|
+
const { clear: todosClear, todos } = checkTodos(content);
|
|
577
|
+
const { complete: requiredComplete, missing: missingRequired } = checkRequiredSections(content);
|
|
578
|
+
const missingRecommended = checkRecommendedSections(content);
|
|
579
|
+
const secretsFound = scanForSecrets(content);
|
|
580
|
+
const refs = checkFileReferences(content, basePath);
|
|
581
|
+
const { score, rating } = calculateScore(todosClear, requiredComplete, missingRequired, missingRecommended, secretsFound, refs.missing);
|
|
582
|
+
return { filepath, score, rating, todosClear, remainingTodos: todos.slice(0, 5), todoCount: todos.length, requiredComplete, missingRequired, missingRecommended, secretsFound, filesVerified: refs.existing.length, filesMissing: refs.missing.slice(0, 5) };
|
|
1233
583
|
}
|
|
1234
|
-
|
|
1235
584
|
function printReport(result) {
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
for (const t of result.remainingTodos) console.log(\` - \${t.slice(0, 50)}...\`);
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
|
-
if (result.requiredComplete) {
|
|
1256
|
-
console.log('\\n[PASS] All required sections complete');
|
|
1257
|
-
} else {
|
|
1258
|
-
console.log('\\n[FAIL] Missing/incomplete required sections:');
|
|
1259
|
-
for (const s of result.missingRequired) console.log(\` - \${s}\`);
|
|
1260
|
-
}
|
|
1261
|
-
|
|
1262
|
-
if (result.secretsFound.length === 0) {
|
|
1263
|
-
console.log('\\n[PASS] No potential secrets detected');
|
|
1264
|
-
} else {
|
|
1265
|
-
console.log('\\n[WARN] Potential secrets detected:');
|
|
1266
|
-
for (const s of result.secretsFound)
|
|
1267
|
-
console.log(\` - \${s.description} (\${s.count} match(es))\`);
|
|
1268
|
-
}
|
|
1269
|
-
|
|
1270
|
-
console.log(
|
|
1271
|
-
\`\\n[INFO] File references: \${result.filesVerified} verified, \${result.filesMissing.length} missing\`,
|
|
1272
|
-
);
|
|
1273
|
-
if (result.filesMissing.length > 0) {
|
|
1274
|
-
for (const f of result.filesMissing) console.log(\` - \${f}\`);
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
if (result.missingRecommended.length > 0) {
|
|
1278
|
-
console.log('\\n[INFO] Missing recommended sections:');
|
|
1279
|
-
for (const s of result.missingRecommended) console.log(\` - \${s}\`);
|
|
1280
|
-
}
|
|
585
|
+
if (result.error) return void console.log('Error: ' + result.error);
|
|
586
|
+
console.log('\\n' + '='.repeat(60));
|
|
587
|
+
console.log('Handoff Validation');
|
|
588
|
+
console.log('='.repeat(60));
|
|
589
|
+
console.log('File: ' + result.filepath);
|
|
590
|
+
console.log('\\nScore: ' + result.score + '/100 - ' + result.rating);
|
|
591
|
+
console.log('='.repeat(60));
|
|
592
|
+
if (result.todosClear) console.log('\\n[PASS] No TODO placeholders remaining');
|
|
593
|
+
else { console.log('\\n[FAIL] ' + result.todoCount + ' TODO placeholders found:'); for (const todo of result.remainingTodos) console.log(' - ' + todo.slice(0, 50) + '...'); }
|
|
594
|
+
if (result.requiredComplete) console.log('\\n[PASS] All required sections complete');
|
|
595
|
+
else { console.log('\\n[FAIL] Missing/incomplete required sections:'); for (const section of result.missingRequired) console.log(' - ' + section); }
|
|
596
|
+
if (!result.secretsFound.length) console.log('\\n[PASS] No potential secrets detected');
|
|
597
|
+
else { console.log('\\n[WARN] Potential secrets detected:'); for (const finding of result.secretsFound) console.log(' - ' + finding.description + ' (' + finding.count + ' match(es))'); }
|
|
598
|
+
console.log('\\n[INFO] File refs: ' + result.filesVerified + ' verified, ' + result.filesMissing.length + ' missing');
|
|
599
|
+
for (const missingFile of result.filesMissing) console.log(' - ' + missingFile);
|
|
600
|
+
if (result.missingRecommended.length) { console.log('\\n[INFO] Missing recommended sections:'); for (const section of result.missingRecommended) console.log(' - ' + section); }
|
|
1281
601
|
}
|
|
1282
|
-
|
|
1283
|
-
// --- Main ---
|
|
1284
602
|
const args = process.argv.slice(2);
|
|
1285
|
-
if (args.includes('--help') || args.includes('-h') || args.length
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
const result = validateHandoff(args.find((a) => !a.startsWith('-')));
|
|
1294
|
-
console.log(JSON.stringify(result, null, 2));
|
|
1295
|
-
} else {
|
|
1296
|
-
const result = validateHandoff(args[0]);
|
|
1297
|
-
printReport(result);
|
|
1298
|
-
}
|
|
603
|
+
if (args.includes('--help') || args.includes('-h') || !args.length) {
|
|
604
|
+
console.log('Usage: node validate_handoff.js <handoff-file>');
|
|
605
|
+
console.log(' node validate_handoff.js .aikit-state/handoffs/<flow-slug>/file.md');
|
|
606
|
+
process.exit(!args.length ? 1 : 0);
|
|
607
|
+
}
|
|
608
|
+
const result = validateHandoff(args.find((arg) => !arg.startsWith('-')) || args[0]);
|
|
609
|
+
if (args.includes('--json')) console.log(JSON.stringify(result, null, 2));
|
|
610
|
+
else printReport(result);
|
|
1299
611
|
`},{file:`SKILL.md`,content:`---
|
|
1300
612
|
name: session-handoff
|
|
1301
|
-
description: "Creates
|
|
613
|
+
description: "Creates handoff docs for session transfer. Trigger on save-context, high context pressure, milestones, session end, or resume requests."
|
|
1302
614
|
metadata:
|
|
1303
615
|
category: cross-cutting
|
|
1304
616
|
domain: general
|
|
@@ -1308,121 +620,68 @@ metadata:
|
|
|
1308
620
|
requires: [aikit]
|
|
1309
621
|
relatedSkills: [lesson-learned]
|
|
1310
622
|
---
|
|
1311
|
-
|
|
1312
623
|
# Session Handoff
|
|
1313
|
-
|
|
1314
|
-
Creates compact, high-signal handoffs so a fresh agent can resume work without rereading the full conversation.
|
|
1315
|
-
|
|
624
|
+
Handoffs.
|
|
1316
625
|
## Mindset
|
|
1317
|
-
|
|
1318
|
-
Session handoffs solve the "context amnesia" problem — you have 200K tokens of accumulated understanding that will vanish. Your job is compression with zero information loss on the critical path.
|
|
1319
|
-
|
|
1320
|
-
Think of it as writing a briefing for a colleague who will take over your shift:
|
|
1321
|
-
- What's the current state? (not how we got here)
|
|
1322
|
-
- What decisions were made and WHY? (not what was considered)
|
|
1323
|
-
- What's blocked and needs action? (not what's going fine)
|
|
1324
|
-
- What will surprise them? (gotchas, non-obvious constraints)
|
|
1325
|
-
|
|
1326
|
-
The 80/20 rule applies brutally: 80% of context is irrelevant to the next step. Identify the 20% that matters and make it crystal clear.
|
|
1327
|
-
|
|
626
|
+
Keep only state, decisions + why, blockers, next step, gotchas.
|
|
1328
627
|
## Quick Reference
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
-
|
|
1332
|
-
-
|
|
1333
|
-
|
|
1334
|
-
Every good handoff stores context in two forms:
|
|
1335
|
-
1. **Full markdown file** at \`.aikit-state/handoffs/{flow-slug}/YYYY-MM-DD-HHMMSS-[slug].md\`
|
|
1336
|
-
2. **Compact flow knowledge entry** via \`knowledge({ action: "remember", scope: "flow", category: "session", title: "Session Handoff: <slug>", content })\`
|
|
1337
|
-
|
|
1338
|
-
On resume, start with \`knowledge({ action: "withdraw", scope: "flow", profile: "implementer", budget: 6000 })\`. Only open the full handoff file if the compact entry is insufficient.
|
|
1339
|
-
|
|
628
|
+
- **CREATE** — save context near milestone, pause, session end, or high context pressure
|
|
629
|
+
- **RESUME** — continue prior work from handoff
|
|
630
|
+
- Full file: \`.aikit-state/handoffs/{flow-slug}/YYYY-MM-DD-HHMMSS-[slug].md\`
|
|
631
|
+
- Compact entry: \`knowledge({ action: "remember", scope: "flow", category: "session", title: "Session Handoff: <slug>", content })\`
|
|
632
|
+
- Fast-path: \`knowledge({ action: "withdraw", scope: "flow", profile: "implementer", budget: 6000 })\`
|
|
1340
633
|
## NEVER
|
|
1341
|
-
|
|
1342
|
-
- **NEVER
|
|
1343
|
-
- **NEVER
|
|
1344
|
-
- **NEVER
|
|
1345
|
-
- **NEVER
|
|
1346
|
-
- **NEVER
|
|
1347
|
-
- **NEVER
|
|
1348
|
-
- **NEVER skip the architecture context** — the next agent doesn't know the codebase. Include: key files, patterns used, critical constraints.
|
|
1349
|
-
|
|
634
|
+
- **NEVER include full file contents in handoffs**
|
|
635
|
+
- **NEVER omit the "why" behind decisions**
|
|
636
|
+
- **NEVER create a handoff without blockers/next-steps**
|
|
637
|
+
- **NEVER hand off mid-failure** without error text and tried/untried paths
|
|
638
|
+
- **NEVER include conversation history**
|
|
639
|
+
- **NEVER create handoffs larger than 500 words**
|
|
640
|
+
- **NEVER skip architecture context**
|
|
1350
641
|
## Handoff Quality Gate
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
- [ ] **
|
|
1356
|
-
- [ ] **Self-contained** — doesn't require reading the conversation to understand
|
|
1357
|
-
- [ ] **Minimal** — no content that doesn't directly serve the next agent's work
|
|
1358
|
-
- [ ] **Verified** — decisions cite evidence (file:line, test output, tool result)
|
|
1359
|
-
|
|
1360
|
-
If any criterion fails, compress further or add the missing context.
|
|
1361
|
-
|
|
642
|
+
Cold-start test: can new agent resume correctly on first attempt?
|
|
643
|
+
- [ ] **Actionable**
|
|
644
|
+
- [ ] **Self-contained**
|
|
645
|
+
- [ ] **Minimal**
|
|
646
|
+
- [ ] **Verified**
|
|
1362
647
|
## CREATE Workflow
|
|
1363
|
-
|
|
1364
648
|
### 1. Choose storage target
|
|
1365
|
-
|
|
1366
|
-
Save the full handoff at one of these locations:
|
|
1367
649
|
- Flow-scoped: \`.aikit-state/handoffs/{flow-slug}/YYYY-MM-DD-HHMMSS-[slug].md\`
|
|
1368
650
|
- Standalone: \`.aikit-state/handoffs/_standalone/YYYY-MM-DD-HHMMSS-[slug].md\`
|
|
1369
|
-
|
|
1370
|
-
Use a slug that describes the active task, not the conversation.
|
|
1371
|
-
|
|
1372
651
|
### 2. Gather only critical-path facts
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
-
|
|
1376
|
-
-
|
|
1377
|
-
-
|
|
1378
|
-
-
|
|
1379
|
-
-
|
|
1380
|
-
-
|
|
1381
|
-
|
|
1382
|
-
Use AI Kit tools inline instead of external scripts:
|
|
1383
|
-
- \`stash({ action: "set", key: "handoff:<slug>:notes", value })\` for temporary compression while drafting
|
|
1384
|
-
- \`checkpoint({ action: "save", label: "handoff-<slug>", notes })\` when session state itself matters
|
|
1385
|
-
- \`knowledge({ action: "list", category: "decisions" })\` and \`search({ query })\` to pull prior decisions into the handoff when relevant
|
|
1386
|
-
|
|
652
|
+
- Current state
|
|
653
|
+
- Key files + why they matter
|
|
654
|
+
- Decisions + why
|
|
655
|
+
- Verification evidence
|
|
656
|
+
- Blockers + tried/untried paths
|
|
657
|
+
- Ordered next steps
|
|
658
|
+
- \`stash({ action: "set", key: "handoff:<slug>:notes", value })\`
|
|
659
|
+
- \`knowledge({ action: "list", category: "decisions" })\` + \`search({ query })\`
|
|
1387
660
|
### 3. Write the full handoff
|
|
1388
|
-
|
|
1389
|
-
Use this structure:
|
|
1390
|
-
|
|
1391
661
|
\`\`\`md
|
|
1392
662
|
# Handoff: <task>
|
|
1393
|
-
|
|
1394
663
|
## Current State
|
|
1395
664
|
- Status:
|
|
1396
665
|
- Last completed step:
|
|
1397
666
|
- Current failure or pending task:
|
|
1398
|
-
|
|
1399
667
|
## Critical Files
|
|
1400
668
|
- path/to/file - purpose + why it matters now
|
|
1401
|
-
|
|
1402
669
|
## Decisions
|
|
1403
670
|
- Decision: why
|
|
1404
671
|
- Evidence: file:line | test output | tool result
|
|
1405
|
-
|
|
1406
672
|
## Next Steps
|
|
1407
673
|
1. First action
|
|
1408
674
|
2. Second action
|
|
1409
675
|
3. Third action
|
|
1410
|
-
|
|
1411
676
|
## Blockers
|
|
1412
677
|
- Blocker:
|
|
1413
678
|
- Tried:
|
|
1414
679
|
- Not tried yet:
|
|
1415
|
-
|
|
1416
680
|
## Gotchas
|
|
1417
681
|
- Non-obvious constraint or pattern
|
|
1418
682
|
\`\`\`
|
|
1419
|
-
|
|
1420
|
-
Use [references/handoff-template.md](references/handoff-template.md) when you need a fuller structure, but delete sections that add no value.
|
|
1421
|
-
|
|
683
|
+
Use [references/handoff-template.md](references/handoff-template.md) if needed.
|
|
1422
684
|
### 4. Save compact flow knowledge
|
|
1423
|
-
|
|
1424
|
-
Store a compressed version for automatic resume:
|
|
1425
|
-
|
|
1426
685
|
\`\`\`
|
|
1427
686
|
knowledge({
|
|
1428
687
|
action: "remember",
|
|
@@ -1436,70 +695,38 @@ Blockers: ...
|
|
|
1436
695
|
Evidence: ..."
|
|
1437
696
|
})
|
|
1438
697
|
\`\`\`
|
|
1439
|
-
|
|
1440
|
-
Target 1-2K characters. This is the resume fast-path.
|
|
1441
|
-
|
|
698
|
+
Target 1-2K chars.
|
|
1442
699
|
### 5. Self-review before finalizing
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
-
|
|
1446
|
-
-
|
|
1447
|
-
-
|
|
1448
|
-
- Every blocker includes attempted and untried paths
|
|
1449
|
-
- Next step #1 is immediately executable
|
|
1450
|
-
- Quality Gate score is 4/4
|
|
1451
|
-
|
|
700
|
+
- No secrets or file dumps
|
|
701
|
+
- Decisions include rationale
|
|
702
|
+
- Blockers include tried + untried paths
|
|
703
|
+
- Next step #1 is executable
|
|
704
|
+
- Quality Gate is 4/4
|
|
1452
705
|
## RESUME Workflow
|
|
1453
|
-
|
|
1454
706
|
### 1. Pull compact context first
|
|
1455
|
-
|
|
1456
|
-
Start with:
|
|
1457
|
-
|
|
1458
707
|
\`\`\`
|
|
1459
708
|
knowledge({ action: "withdraw", scope: "flow", profile: "implementer", budget: 6000 })
|
|
1460
709
|
\`\`\`
|
|
1461
|
-
|
|
1462
|
-
If the entry is truncated or multiple handoffs exist, use:
|
|
1463
|
-
|
|
710
|
+
If entry is truncated or multiple handoffs exist, use:
|
|
1464
711
|
\`\`\`
|
|
1465
712
|
knowledge({ action: "list", category: "session", scope: "flow" })
|
|
1466
713
|
knowledge({ action: "read", path: "<handoff-entry>" })
|
|
1467
714
|
\`\`\`
|
|
1468
|
-
|
|
1469
715
|
### 2. Load the full handoff only when needed
|
|
1470
|
-
|
|
1471
|
-
Open the markdown file in \`.aikit-state/handoffs/\` if the compact entry lacks enough detail to act safely.
|
|
1472
|
-
|
|
716
|
+
Open markdown file in \`.aikit-state/handoffs/\` only if compact entry is not enough.
|
|
1473
717
|
### 3. Verify live state before coding
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
-
|
|
1477
|
-
-
|
|
1478
|
-
-
|
|
1479
|
-
- Do stated next steps still make sense against current diff and tests?
|
|
1480
|
-
|
|
1481
|
-
Use the checklist in [references/resume-checklist.md](references/resume-checklist.md) for the verification pass.
|
|
1482
|
-
|
|
718
|
+
- Branch still matches assumptions?
|
|
719
|
+
- Referenced files still exist?
|
|
720
|
+
- Blockers already resolved?
|
|
721
|
+
- Next steps still fit current diff and tests?
|
|
722
|
+
Use [references/resume-checklist.md](references/resume-checklist.md).
|
|
1483
723
|
### 4. Resume from the first actionable step
|
|
1484
|
-
|
|
1485
|
-
Begin with Next Step #1. If reality differs from the handoff, update the handoff or create a successor handoff instead of silently continuing on stale assumptions.
|
|
1486
|
-
|
|
724
|
+
Begin with Next Step #1. If reality differs, update handoff or create successor handoff.
|
|
1487
725
|
## Chaining Rule
|
|
1488
|
-
|
|
1489
|
-
For long-running work, create a successor handoff instead of overwriting history. Each new handoff should state what it supersedes and what changed since the previous handoff.
|
|
1490
|
-
|
|
726
|
+
Create successor handoff instead of overwriting history.
|
|
1491
727
|
## Output Expectations
|
|
1492
|
-
|
|
1493
|
-
When asked to create a handoff, return:
|
|
1494
|
-
- Handoff status
|
|
1495
|
-
- File path written
|
|
1496
|
-
- Key decisions captured
|
|
1497
|
-
- Top blocker or next step
|
|
1498
|
-
|
|
1499
|
-
Do not return conversation transcripts or visual dashboards.
|
|
1500
|
-
|
|
728
|
+
Return status, file path, decisions, blocker/next step.
|
|
1501
729
|
## Resources
|
|
1502
|
-
|
|
1503
|
-
- [references/
|
|
1504
|
-
- [references/resume-checklist.md](references/resume-checklist.md) - verification checklist before resuming from saved context
|
|
730
|
+
- [references/handoff-template.md](references/handoff-template.md)
|
|
731
|
+
- [references/resume-checklist.md](references/resume-checklist.md)
|
|
1505
732
|
`}];export{e as default};
|