@vpxa/aikit 0.1.274 → 0.1.275

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/package.json +7 -1
  2. package/packages/cli/dist/index.js +14 -14
  3. package/packages/cli/dist/{init-BgelSos0.js → init-B3a2fygD.js} +1 -1
  4. package/packages/cli/dist/{scaffold-Cxjwg531.js → scaffold-BNPHP-QC.js} +1 -1
  5. package/packages/cli/dist/{templates-C92mODRl.js → templates-CDa0UuoE.js} +19 -19
  6. package/packages/core/dist/index.d.ts +45 -17
  7. package/packages/core/dist/index.js +1 -1
  8. package/packages/flows/dist/index.d.ts +23 -2
  9. package/packages/flows/dist/index.js +1 -1
  10. package/packages/server/dist/bin.js +6 -6
  11. package/packages/server/dist/config-DZ-6Zy94.js +2 -0
  12. package/packages/server/dist/config-DxWyWSb9.js +1 -0
  13. package/packages/server/dist/curated-manager-C5uOPept.js +7 -0
  14. package/packages/server/dist/index.js +1 -1
  15. package/packages/server/dist/{promotion-BNEScZVD.js → promotion-D9anNXv8.js} +1 -1
  16. package/packages/server/dist/{routes-CR3fI-HJ.js → routes-1wkXLxXe.js} +1 -1
  17. package/packages/server/dist/{routes-Afg7J7xK.js → routes-KC-D2U8n.js} +1 -1
  18. package/packages/server/dist/{server-4h0Cclv3.js → server-CkCRBlz4.js} +93 -93
  19. package/packages/server/dist/{server-DIz2FGOX.js → server-DlE6A6sd.js} +93 -93
  20. package/packages/server/dist/{version-check-gazMo-D4.js → version-check-CgfflkJX.js} +1 -1
  21. package/packages/server/dist/{version-check-BgHzxxCW.js → version-check-ruLtfyDd.js} +1 -1
  22. package/packages/server/viewers/canvas.html +2 -1
  23. package/packages/server/viewers/task-plan-static.html +2 -1
  24. package/packages/tools/dist/index.d.ts +5 -5
  25. package/packages/tools/dist/index.js +72 -72
  26. package/scaffold/dist/definitions/hooks.mjs +1 -1
  27. package/scaffold/dist/definitions/skills/c4-architecture.mjs +1 -1
  28. package/scaffold/dist/definitions/skills/session-handoff.mjs +2 -732
  29. package/packages/server/dist/config-CZuVxRpX.js +0 -1
  30. package/packages/server/dist/config-WpN5CWM7.js +0 -2
  31. package/packages/server/dist/curated-manager-CfwN96rp.js +0 -7
@@ -1,732 +1,2 @@
1
- var e=[{file:`references/handoff-template.md`,content:`# Handoff Template
2
- Use this template.
3
- ## Table of Contents
4
- Use headings
5
- ---
6
- # Handoff: [TASK_TITLE]
7
- ## Session Metadata
8
- - Created: [TIMESTAMP]
9
- - Project: [PROJECT_PATH]
10
- - Branch: [GIT_BRANCH]
11
- - Session duration: [APPROX_DURATION]
12
- ## Current State Summary
13
- [One paragraph: work, status, handoff point]
14
- ## Codebase Understanding
15
- ### Architecture Overview
16
- [Key architecture insights]
17
- ### Critical Files
18
- | File | Purpose | Relevance |
19
- |------|---------|-----------|
20
- | path/to/file | Purpose | Why it matters now |
21
- ### Key Patterns Discovered
22
- [Patterns next agent should follow]
23
- ## Work Completed
24
- ### Tasks Finished
25
- - [x] Task 1 - what was done
26
- - [x] Task 2 - brief description
27
- ### Files Modified
28
- | File | Changes | Rationale |
29
- |------|---------|-----------|
30
- | path/to/file | Changes | Why |
31
- ### Decisions Made
32
- | Decision | Options Considered | Rationale |
33
- |----------|-------------------|-----------|
34
- | Chose X over Y | X, Y, Z | Why |
35
- ## Pending Work
36
- ### Immediate Next Steps
37
- 1. [First action]
38
- 2. [Second action]
39
- 3. [Third action]
40
- ### Blockers/Open Questions
41
- - [ ] Blocker: [description] - Needs: [unblocker]
42
- - [ ] Question: [unclear aspect] - Suggested: [resolution]
43
- ### Deferred Items
44
- - Item 1 (deferred: [reason])
45
- ## Context for Resuming Agent
46
- ### Important Context
47
- [Critical information next agent MUST know]
48
- ### Assumptions Made
49
- - Assumption 1: [assumed true]
50
- - Assumption 2: [another assumption]
51
- ### Potential Gotchas
52
- - [Things that might trip up next agent]
53
- ## Environment State
54
- ### Tools/Services Used
55
- - [Tool/Service]: [relevant state]
56
- ### Active Processes
57
- - [Background processes, dev servers, watchers]
58
- ### Environment Variables
59
- - [Env vars that matter - names only, no values]
60
- ## Related Resources
61
- - [Relevant docs]
62
- - [Related file paths]
63
- - [External resources consulted]
64
- ---
65
- ## Template Usage Notes
66
- Be specific. Use file refs when useful. Exclude secrets. Capture WHAT and WHY.
67
- `},{file:`references/resume-checklist.md`,content:`# Resume Checklist
68
- On resume.
69
- ## Pre-Resume Verification
70
- - [ ] Read handoff before acting
71
- - [ ] Verify project directory
72
- - [ ] Confirm branch matches or note why it differs
73
- - [ ] Check handoff timestamp
74
- ## Context Validation
75
- - [ ] Review important context
76
- - [ ] Check assumptions
77
- - [ ] Check resolved blockers
78
- - [ ] Review gotchas
79
- ## State Verification
80
- - [ ] Run \`git status\`
81
- - [ ] Compare modified files vs current state
82
- - [ ] Check required env vars
83
- - [ ] Verify required services/processes
84
- ## Resume Execution
85
- - [ ] Start with next step #1
86
- - [ ] Apply documented patterns
87
- ## During Work
88
- - [ ] Update handoff if major context changes
89
- - [ ] Add new blockers/questions
90
- ## Red Flags - Stop and Verify
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
96
- ## Quick Start Commands
97
- \`\`\`bash
98
- git branch --show-current
99
- git status
100
- git log --oneline -10
101
- ps aux | grep [process-name]
102
- env | grep [relevant-var]
103
- \`\`\`
104
- ## Handoff Quality Assessment
105
- Re-explore if weak.
106
- `},{file:`scripts/check_staleness.js`,content:`#!/usr/bin/env node
107
- const fs = require('node:fs');
108
- const path = require('node:path');
109
- const { execSync } = require('node:child_process');
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
- };
114
- function parseHandoffMetadata(filepath) {
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))] : [];
133
- }
134
- function checkFilesExist(files, cwd) {
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 };
138
- }
139
- function calculateStaleness(daysOld, commitsSince, filesChanged, branchMatches, filesMissing) {
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 };
156
- }
157
- function resolveProjectRootFromHandoff(handoffPath) {
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, "..");
162
- }
163
- function checkStaleness(handoffPath) {
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;
182
- }
183
- function printReport(result) {
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(", "));
202
- }
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);
213
- `},{file:`scripts/create_handoff.js`,content:`#!/usr/bin/env node
214
- const fs = require('node:fs');
215
- const path = require('node:path');
216
- const { execSync } = require('node:child_process');
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 };
416
- }
417
- const args = process.argv.slice(2);
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);
423
- }
424
- let slug = null; let from = null; let flow = null;
425
- for (let i = 0; i < args.length; i++) {
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);
438
- `},{file:`scripts/list_handoffs.js`,content:`#!/usr/bin/env node
439
- const fs = require('node:fs');
440
- const path = require('node:path');
441
- function extractTitle(filepath) {
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]";
448
- }
449
- function checkCompletion(filepath) {
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"; }
456
- }
457
- function parseDateFromFilename(filename) {
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";
481
- const args = process.argv.slice(2);
482
- if (args.includes("--help") || args.includes("-h")) { console.log("Usage: node list_handoffs.js [project-path]"); process.exit(0); }
483
- const projectPath = args[0] || process.cwd();
484
- const handoffs = listHandoffs(projectPath);
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));
497
- `},{file:`scripts/validate_handoff.js`,content:`#!/usr/bin/env node
498
- const fs = require('node:fs');
499
- const path = require('node:path');
500
- const SECRET_PATTERNS = [
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'],
511
- ];
512
- const REQUIRED_SECTIONS = ['Current State Summary', 'Important Context', 'Immediate Next Steps'];
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 }; }
516
- function checkRequiredSections(content) {
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 };
529
- }
530
- function checkRecommendedSections(content) {
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;
537
- }
538
- function scanForSecrets(content) {
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;
542
- }
543
- function checkFileReferences(content, basePath) {
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' };
565
- }
566
- function resolveProjectRootFromHandoff(filepath) {
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, '..');
571
- }
572
- function validateHandoff(filepath) {
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) };
583
- }
584
- function printReport(result) {
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); }
601
- }
602
- const args = process.argv.slice(2);
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);
611
- `},{file:`SKILL.md`,content:`---
612
- name: session-handoff
613
- description: "Creates handoff docs for session transfer. Trigger on save-context, high context pressure, milestones, session end, or resume requests."
614
- metadata:
615
- category: cross-cutting
616
- domain: general
617
- applicability: always
618
- inputs: [session-state, decisions]
619
- outputs: [handoff-document, flow-knowledge-entry]
620
- requires: [aikit]
621
- relatedSkills: [lesson-learned]
622
- ---
623
- # Session Handoff
624
- Handoffs.
625
- ## Mindset
626
- Keep only state, decisions + why, blockers, next step, gotchas.
627
- ## Quick Reference
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 })\`
633
- ## NEVER
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**
641
- ## Handoff Quality Gate
642
- Cold-start test: can new agent resume correctly on first attempt?
643
- - [ ] **Actionable**
644
- - [ ] **Self-contained**
645
- - [ ] **Minimal**
646
- - [ ] **Verified**
647
- ## CREATE Workflow
648
- ### 1. Choose storage target
649
- - Flow-scoped: \`.aikit-state/handoffs/{flow-slug}/YYYY-MM-DD-HHMMSS-[slug].md\`
650
- - Standalone: \`.aikit-state/handoffs/_standalone/YYYY-MM-DD-HHMMSS-[slug].md\`
651
- ### 2. Gather only critical-path facts
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 })\`
660
- ### 3. Write the full handoff
661
- \`\`\`md
662
- # Handoff: <task>
663
- ## Current State
664
- - Status:
665
- - Last completed step:
666
- - Current failure or pending task:
667
- ## Critical Files
668
- - path/to/file - purpose + why it matters now
669
- ## Decisions
670
- - Decision: why
671
- - Evidence: file:line | test output | tool result
672
- ## Next Steps
673
- 1. First action
674
- 2. Second action
675
- 3. Third action
676
- ## Blockers
677
- - Blocker:
678
- - Tried:
679
- - Not tried yet:
680
- ## Gotchas
681
- - Non-obvious constraint or pattern
682
- \`\`\`
683
- Use [references/handoff-template.md](references/handoff-template.md) if needed.
684
- ### 4. Save compact flow knowledge
685
- \`\`\`
686
- knowledge({
687
- action: "remember",
688
- scope: "flow",
689
- category: "session",
690
- title: "Session Handoff: <slug>",
691
- content: "State: ...
692
- Decisions: ...
693
- Next: ...
694
- Blockers: ...
695
- Evidence: ..."
696
- })
697
- \`\`\`
698
- Target 1-2K chars.
699
- ### 5. Self-review before finalizing
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
705
- ## RESUME Workflow
706
- ### 1. Pull compact context first
707
- \`\`\`
708
- knowledge({ action: "withdraw", scope: "flow", profile: "implementer", budget: 6000 })
709
- \`\`\`
710
- If entry is truncated or multiple handoffs exist, use:
711
- \`\`\`
712
- knowledge({ action: "list", category: "session", scope: "flow" })
713
- knowledge({ action: "read", path: "<handoff-entry>" })
714
- \`\`\`
715
- ### 2. Load the full handoff only when needed
716
- Open markdown file in \`.aikit-state/handoffs/\` only if compact entry is not enough.
717
- ### 3. Verify live state before coding
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).
723
- ### 4. Resume from the first actionable step
724
- Begin with Next Step #1. If reality differs, update handoff or create successor handoff.
725
- ## Chaining Rule
726
- Create successor handoff instead of overwriting history.
727
- ## Output Expectations
728
- Return status, file path, decisions, blocker/next step.
729
- ## Resources
730
- - [references/handoff-template.md](references/handoff-template.md)
731
- - [references/resume-checklist.md](references/resume-checklist.md)
732
- `}];export{e as default};
1
+ const e=e=>e.join(`
2
+ `),t=[`const path = require('node:path');`,`const os = require('node:os');`,`const { createHash } = require('node:crypto');`,``,`function computePartitionKey(cwd) {`,` const absolutePath = path.resolve(cwd);`,` const baseName = path.basename(absolutePath).replace(/[^a-zA-Z0-9-]/g, '-') || 'workspace';`,` const hash = createHash('sha256').update(absolutePath).digest('hex').slice(0, 8);`,` return baseName + '-' + hash;`,`}`,``,`function getHandoffDir(cwd) {`,` return path.join(os.homedir(), '.aikit', 'workspaces', computePartitionKey(cwd), 'handoffs');`,`}`,``,`function isContainedPath(rootPath, targetPath) {`,` const relativePath = path.relative(rootPath, targetPath);`,` return !relativePath.startsWith('..') && !path.isAbsolute(relativePath);`,`}`];var n=[{file:`references/handoff-template.md`,content:e(`# Handoff Template((Runtime path: ~/.aikit/workspaces/<workspace-hash>/handoffs/YYYY-MM-DD-HHMMSS-[slug].md(.flows/ stays workspace-local for flow state only.((## Current State(- Status:(- Last completed step:(- Current blocker:((## Decisions(- Decision:(- Why:(- Evidence:((## Next Steps(1.(2.(3.((## Blockers(- Blocker:(- Tried:(- Not tried:((## Gotchas(- Constraints, caveats, or follow-up notes.((Keep it compact, self-contained, and free of secrets.`.split(`(`))},{file:`references/resume-checklist.md`,content:e([`# Resume Checklist`,``,`- Open the latest handoff from ~/.aikit/workspaces/<workspace-hash>/handoffs/.`,`- Confirm branch and working tree.`,`- Review decisions, blockers, and gotchas.`,`- Resume from the first unfinished next step.`])},{file:`scripts/create_handoff.js`,content:e([`#!/usr/bin/env node`,`const fs = require('node:fs');`,...t,``,`function formatTimestamp(date) {`,` const iso = date.toISOString();`,` return iso.slice(0, 10) + '-' + iso.slice(11, 13) + iso.slice(14, 16) + iso.slice(17, 19);`,`}`,``,`function slugify(value) {`,` return String(value || 'handoff').toLowerCase().replace(/[^a-z0-9-]+/g, '-').replace(/^-+|-+$/g, '') || 'handoff';`,`}`,``,`const handoffDir = getHandoffDir(process.cwd());`,`fs.mkdirSync(handoffDir, { recursive: true });`,``,`const filePath = path.join(handoffDir, formatTimestamp(new Date()) + '-' + slugify(process.argv[2]) + '.md');`,`const content = [`,` '# Handoff',`,` '',`,` '## Current State',`,` '- Status: draft',`,` '- Last completed step:',`,` '- Current blocker:',`,` '',`,` '## Decisions',`,` '- Decision:',`,` '- Why:',`,` '- Evidence:',`,` '',`,` '## Next Steps',`,` '1.',`,` '2.',`,` '3.',`,` '',`,` '## Blockers',`,` '- Blocker:',`,` '- Tried:',`,` '- Not tried:',`,` '',`,` '## Gotchas',`,` '- Constraints, caveats, or follow-up notes.',`,`]).join('\\n');`,``,`fs.writeFileSync(filePath, content, 'utf8');`,`process.stdout.write(filePath + '\\n');`])},{file:`scripts/list_handoffs.js`,content:e([`#!/usr/bin/env node`,`const fs = require('node:fs');`,...t,``,`const handoffDir = getHandoffDir(process.cwd());`,``,`if (!fs.existsSync(handoffDir)) {`,` process.exit(0);`,`}`,``,`for (const entry of fs.readdirSync(handoffDir).filter((name) => name.endsWith('.md')).sort()) {`,` const filePath = path.join(handoffDir, entry);`,` const stat = fs.statSync(filePath);`,` process.stdout.write(entry + '\\t' + stat.size + '\\t' + stat.mtime.toISOString() + '\\n');`,`}`])},{file:`scripts/check_staleness.js`,content:e([`#!/usr/bin/env node`,`const fs = require('node:fs');`,...t,``,`const staleAfterDays = Number(process.argv[2] || 30);`,`const handoffDir = getHandoffDir(process.cwd());`,``,`if (!fs.existsSync(handoffDir)) {`,` process.stderr.write('Missing handoff dir: ' + handoffDir + '\\n');`,` process.exit(1);`,`}`,``,`const now = Date.now();`,``,`for (const entry of fs.readdirSync(handoffDir).filter((name) => name.endsWith('.md')).sort()) {`,` const filePath = path.join(handoffDir, entry);`,` const ageDays = (now - fs.statSync(filePath).mtimeMs) / 86400000;`,` if (ageDays > staleAfterDays) {`,` process.stdout.write(entry + '\\t' + ageDays.toFixed(1) + 'd\\n');`,` }`,`}`])},{file:`scripts/validate_handoff.js`,content:e([`#!/usr/bin/env node`,`const fs = require('node:fs');`,...t,``,`const REQUIRED_SECTIONS = [`,` '## Current State',`,` '## Decisions',`,` '## Next Steps',`,` '## Blockers',`,` '## Gotchas',`,`];`,``,`function isRuntimeHandoff(filePath) {`,` const absolutePath = path.resolve(filePath);`,` const runtimeRoot = path.join(os.homedir(), '.aikit', 'workspaces');`,``,` if (!isContainedPath(runtimeRoot, absolutePath)) {`,` return false;`,` }`,``,` const relativePath = path.relative(runtimeRoot, absolutePath);`,` const parts = relativePath.split(path.sep);`,` return parts.length >= 3 && parts[1] === 'handoffs';`,`}`,``,`function validate(filePath) {`,` const absolutePath = path.resolve(filePath);`,``,` if (!isRuntimeHandoff(absolutePath)) {`,` throw new Error('Handoff must live under ~/.aikit/workspaces/<workspace-hash>/handoffs/');`,` }`,``,` const content = fs.readFileSync(absolutePath, 'utf8');`,``,` for (const section of REQUIRED_SECTIONS) {`,` if (!content.includes(section)) {`,` throw new Error('Missing section: ' + section);`,` }`,` }`,`}`,``,`const filePath = process.argv[2];`,``,`if (!filePath) {`,` process.stderr.write('Usage: validate_handoff.js <handoff-file>\\n');`,` process.exit(1);`,`}`,``,`validate(filePath);`,`process.stdout.write('OK\\n');`])},{file:`SKILL.md`,content:e(`---(name: session-handoff(description: "Create and resume session handoff docs."(metadata:( category: cross-cutting( domain: general( applicability: always( inputs: [session-state, decisions]( outputs: [handoff-document, resume-checklist]( requires: [aikit]( relatedSkills: [lesson-learned](---(# Session Handoff((Use when work needs a durable pause point.((## Runtime Location(- Full handoff docs live at ~/.aikit/workspaces/<workspace-hash>/handoffs/YYYY-MM-DD-HHMMSS-[slug].md(- .flows/ stays workspace-local for flow state only.((## Create(- Capture state, decisions, next steps, blockers, and gotchas.(- Keep it compact, self-contained, and secret-free.(- Write the file to the runtime handoff directory.((## Resume(- Open the latest handoff for the current workspace.(- Verify branch and worktree.(- Continue from the first unfinished next step.`.split(`(`))}];export{n as default};