session-continuity 0.1.3

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/LICENSE ADDED
@@ -0,0 +1,116 @@
1
+ PolyForm Noncommercial License 1.0.0
2
+
3
+ Copyright (c) 2026 Waycraft (https://github.com/100615056)
4
+
5
+ This software is provided under the PolyForm Noncommercial License 1.0.0.
6
+ Full license text: https://polyformproject.org/licenses/noncommercial/1.0.0
7
+
8
+ ---
9
+
10
+ Acceptance
11
+
12
+ In order to get any license under these terms, you must agree to them as both
13
+ strict obligations and conditions to all your licenses.
14
+
15
+ Copyright License
16
+
17
+ The licensor grants you a copyright license for the software to do everything
18
+ you might do with the software that would otherwise infringe the licensor's
19
+ copyright in it for any permitted purpose. However, you may only distribute
20
+ the software according to Distribution License and make changes or new works
21
+ based on the software according to Changes and New Works License.
22
+
23
+ Distribution License
24
+
25
+ The licensor grants you an additional copyright license to distribute copies
26
+ of the software. Your license to distribute covers distributing the software
27
+ with changes and new works permitted by Changes and New Works License.
28
+
29
+ Notices
30
+
31
+ You must ensure that anyone who gets a copy of any part of the software from
32
+ you also gets a copy of these terms or the URL for them above, as well as
33
+ copies of any plain-text lines beginning with Required Notice: that the
34
+ licensor provided with the software. For example:
35
+
36
+ Required Notice: Copyright Waycraft (https://github.com/100615056)
37
+
38
+ Changes and New Works License
39
+
40
+ The licensor grants you an additional copyright license to make changes and
41
+ new works based on the software for any permitted purpose.
42
+
43
+ Patent License
44
+
45
+ The licensor grants you a patent license for the software that covers patent
46
+ claims the licensor can license, or becomes able to license, that you would
47
+ infringe by using the software.
48
+
49
+ Noncommercial Purposes
50
+
51
+ Any noncommercial purpose is a permitted purpose.
52
+
53
+ Personal Uses
54
+
55
+ Personal use for research, experiment, and testing for the benefit of public
56
+ knowledge, personal study, private entertainment, hobby projects, amateur
57
+ pursuits, or religious observance, without any anticipated commercial
58
+ application, is use for a noncommercial purpose.
59
+
60
+ Noncommercial Organizations
61
+
62
+ Use by any charitable organization, educational institution, public research
63
+ organization, public safety or health organization, environmental protection
64
+ organization, or government institution is use for a noncommercial purpose
65
+ regardless of the source of funding or obligations resulting from the funding.
66
+
67
+ Fair Use
68
+
69
+ You may have "fair use" rights for the software under the law. These terms do
70
+ not limit them.
71
+
72
+ No Other Rights
73
+
74
+ These terms do not allow you to sublicense or transfer any of your licenses
75
+ to anyone else, or prevent the licensor from granting licenses to anyone else.
76
+ These terms do not imply any other licenses.
77
+
78
+ Patent Defense
79
+
80
+ If you make any written claim that the software infringes or contributes to
81
+ infringement of any patent, your patent license for the software granted under
82
+ these terms ends immediately. If your employer makes such a claim, your patent
83
+ license ends immediately for work on behalf of your employer.
84
+
85
+ Violations
86
+
87
+ The first time you are notified in writing that you have violated any of these
88
+ terms, or done anything with the software not covered by your licenses, your
89
+ licenses can nonetheless continue if you come into full compliance with these
90
+ terms, and take practical steps to correct past violations, within 32 days of
91
+ receiving notice. Otherwise, all your licenses end immediately.
92
+
93
+ No Liability
94
+
95
+ As far as the law allows, the software comes as is, without any warranty or
96
+ condition, and the licensor will not be liable to you for any damages arising
97
+ out of these terms or the use or nature of the software, under any kind of
98
+ legal claim.
99
+
100
+ Definitions
101
+
102
+ The licensor is the entity offering these terms, and the software is the
103
+ software the licensor makes available under these terms.
104
+
105
+ You refers to the individual or entity agreeing to these terms.
106
+
107
+ Your company is any legal entity, sole proprietorship, or other kind of
108
+ organization that you work for, plus all organizations that have control over,
109
+ are under the control of, or are under common control with that organization.
110
+
111
+ Your licenses are all the licenses granted to you for the software under
112
+ these terms.
113
+
114
+ Use means anything you do with the software requiring one of your licenses.
115
+
116
+ Trademark means trademarks, service marks, and similar rights.
package/README.md ADDED
@@ -0,0 +1,175 @@
1
+ # sc — session continuity for Claude Code
2
+
3
+ Pick up exactly where you left off across Claude sessions. No re-explaining context, no copy-pasting summaries — just open a new session and keep working.
4
+
5
+ ---
6
+
7
+ ## How it works
8
+
9
+ When a Claude session ends, `sc` automatically writes a snapshot to `.claude/session.md`:
10
+ - **Git state** — branch, status, recent commits, diff stat
11
+ - **Narrative** — what was being worked on, decisions made, next steps, blockers (generated by `claude -p`)
12
+
13
+ When a new session starts, Claude reads `session.md` automatically via a `CLAUDE.md` import — no action needed.
14
+
15
+ ```
16
+ Session ends → Stop hook → sc snapshot → .claude/session.md
17
+ New session starts → CLAUDE.md @import → full context loaded ✓
18
+ ```
19
+
20
+ ---
21
+
22
+ ## Install
23
+
24
+ ```bash
25
+ npm install -g session-continuity
26
+ ```
27
+
28
+ > Requires Node.js ≥ 18 and the [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code).
29
+
30
+ ---
31
+
32
+ ## Quickstart
33
+
34
+ **One-time setup per project:**
35
+
36
+ ```bash
37
+ cd your-project
38
+ sc init
39
+ ```
40
+
41
+ That's it. `sc init` does three things:
42
+ 1. Creates `.claude/session.md` (the snapshot file)
43
+ 2. Adds `@.claude/session.md` to `CLAUDE.md` so Claude reads it on every session start
44
+ 3. Registers a `Stop` hook in `.claude/settings.json` so `sc snapshot` runs automatically when a session ends
45
+
46
+ **From then on, every session:**
47
+ - Ends by writing a snapshot automatically
48
+ - Starts with full context from the last snapshot
49
+
50
+ ---
51
+
52
+ ## Commands
53
+
54
+ | Command | Description |
55
+ |---|---|
56
+ | `sc init` | Wire up a project (run once) |
57
+ | `sc snapshot` | Write a snapshot now (called automatically by the Stop hook) |
58
+ | `sc status` | Show the current session state |
59
+ | `sc rotate` | Manually trigger a snapshot — use this before force-quitting Claude |
60
+ | `sc clear` | Reset session history |
61
+ | `sc decide "<why>"` | Pin a permanent decision that survives the rolling session window |
62
+
63
+ ---
64
+
65
+ ## Snapshot format
66
+
67
+ Each snapshot is a dated markdown block. Up to 3 sessions are kept (newest first); older sessions roll off automatically.
68
+
69
+ ```markdown
70
+ ---
71
+ ## Session — 2026-05-24T21:30:00Z | branch: feat/auth
72
+
73
+ **Git status:**
74
+ ...
75
+
76
+ **Recent commits:**
77
+ ...
78
+
79
+ **Narrative:**
80
+ **Status:** Implementing JWT refresh token flow — middleware done, route handler in progress.
81
+
82
+ **Decisions made:**
83
+ - Storing refresh tokens in httpOnly cookies (not localStorage) to prevent XSS
84
+ - Using a 15-minute access token TTL based on security requirements
85
+
86
+ **Next steps:**
87
+ - Wire up the /auth/refresh route
88
+ - Add token rotation on each refresh
89
+
90
+ **Blockers:** None
91
+ ---
92
+ ```
93
+
94
+ ---
95
+
96
+ ## Pinning decisions
97
+
98
+ Use `sc decide` to record architectural choices, tradeoffs, or rejected ideas that should survive past the 3-session rolling window:
99
+
100
+ ```bash
101
+ sc decide "Chose Postgres over SQLite — need concurrent writes in production"
102
+ sc decide "Rejected Redis for session storage — adds ops complexity we don't need yet"
103
+ ```
104
+
105
+ Pinned decisions appear at the top of `session.md` under a `## Pinned decisions` block and are never trimmed.
106
+
107
+ ---
108
+
109
+ ## .gitignore recommendation
110
+
111
+ Session state is personal — add this to your project's `.gitignore`:
112
+
113
+ ```
114
+ .claude/session.md
115
+ .claude/session.md.tmp
116
+ ```
117
+
118
+ Do commit `.claude/settings.json` (it contains the Stop hook config other contributors need).
119
+
120
+ ---
121
+
122
+ ## Troubleshooting
123
+
124
+ **Narrative says `[unavailable]`**
125
+
126
+ The `claude` CLI is not in your PATH. Check with `claude --version`. If not installed, follow the [Claude Code setup guide](https://docs.anthropic.com/en/docs/claude-code). Without it, `sc snapshot` still writes objective git state — just no AI-generated narrative.
127
+
128
+ **Context not loading in new sessions**
129
+
130
+ Check that `CLAUDE.md` contains `@.claude/session.md`. If the line is missing, re-run `sc init` — it's safe to run multiple times.
131
+
132
+ **Hook not firing**
133
+
134
+ Verify `.claude/settings.json` has a `Stop` hook pointing to `sc snapshot`:
135
+
136
+ ```json
137
+ {
138
+ "hooks": {
139
+ "Stop": [{ "hooks": [{ "type": "command", "command": "sc snapshot" }] }]
140
+ }
141
+ }
142
+ ```
143
+
144
+ If it's missing, run `sc init` again. Also confirm `sc` is in your PATH (`which sc`).
145
+
146
+ **Session ended without a snapshot (crash / force-quit)**
147
+
148
+ Run `sc rotate` manually to write a snapshot from the current git state. To avoid this in future, run `sc rotate` before closing Claude if you know you'll be force-quitting.
149
+
150
+ **`sc init` breaks my existing hooks**
151
+
152
+ It won't — `sc init` merges into `.claude/settings.json` rather than overwriting it. Your existing hooks are preserved.
153
+
154
+ ---
155
+
156
+ ## How sessions are bounded
157
+
158
+ - **Rolling window:** 3 sessions kept, newest first. Older sessions roll off.
159
+ - **Per-session token cap:** ~200 tokens per session entry (~600 tokens total). Well within Claude's context budget.
160
+ - **Pinned decisions:** Never trimmed — use `sc decide` for anything you want to keep long-term.
161
+
162
+ ---
163
+
164
+ ## Roadmap
165
+
166
+ - `PostToolUse` incremental checkpointing (crash safety without `sc rotate`)
167
+ - `sc history` — list past session headers
168
+ - `sc diff` — show git diff since last snapshot
169
+ - Configurable rolling window size
170
+
171
+ ---
172
+
173
+ ## License
174
+
175
+ MIT
package/dist/cli.js ADDED
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+ #!/usr/bin/env node
3
+ import{existsSync as R,readFileSync as z,writeFileSync as k}from"fs";import{readFileSync as B,writeFileSync as I,existsSync as D,mkdirSync as H,renameSync as G}from"fs";import{execSync as J}from"child_process";import{join as v}from"path";var p=".claude",n=v(p,"session.md"),x=v(p,"settings.json"),h="CLAUDE.md",w="@.claude/session.md",N=3,_=150,u=`<!-- sc: no previous session recorded -->
4
+ `;function S(e){D(e)||H(e,{recursive:!0})}function L(e,o={}){if(!D(e))return o;try{return JSON.parse(B(e,"utf8"))}catch{return o}}function O(e,o){I(e,JSON.stringify(o,null,2)+`
5
+ `,"utf8")}function m(e,o){let t=e+".tmp";I(t,o,"utf8"),G(t,e)}function f(e,o=""){try{return J(`git ${e}`,{encoding:"utf8",stdio:["pipe","pipe","pipe"]}).trim()}catch{return o}}function C(e,o){let t=e.split(/\s+/).filter(Boolean);return t.length<=o?e:t.slice(0,o).join(" ")+" \u2026[trimmed]"}var q="## Pinned decisions";function $(e){if(!e.includes(q))return{pinned:"",rest:e};let o=e.indexOf(`
6
+ ---
7
+ `);return o===-1?{pinned:e.trim(),rest:""}:{pinned:e.slice(0,o).trim(),rest:e.slice(o)}}function b(e){return e.split(/^---$/m).map(o=>o.trim()).filter(o=>o&&!o.startsWith("<!--"))}function A(e){return e.map(o=>`---
8
+ ${o}
9
+ `).join(`
10
+ `)+`
11
+ `}async function P(){let e=[];S(p),e.push("\u2713 .claude/ directory ready"),R(n)?e.push("\xB7 .claude/session.md already exists"):(k(n,u,"utf8"),e.push("\u2713 .claude/session.md created"));let o=R(h),t=o?z(h,"utf8"):"";t.includes(w)?e.push("\xB7 CLAUDE.md already has @import"):(t=`
12
+ <!-- sc: session continuity -->
13
+ ${w}
14
+ `+t,k(h,t,"utf8"),e.push(`\u2713 CLAUDE.md ${o?"updated":"created"} with @import`));let s=L(x,{}),d={type:"command",command:"sc snapshot"};s.hooks||(s.hooks={}),s.hooks.Stop||(s.hooks.Stop=[]),s.hooks.Stop.some(r=>Array.isArray(r.hooks)&&r.hooks.some(a=>a.command&&a.command.includes("sc snapshot")))?e.push("\xB7 Stop hook already registered"):(s.hooks.Stop.push({hooks:[d]}),O(x,s),e.push("\u2713 Stop hook registered in .claude/settings.json"));let c=!1;try{let{execSync:r}=await import("child_process");r("claude --version",{stdio:"pipe"}),c=!0,e.push("\u2713 claude CLI found \u2014 narrative snapshots enabled")}catch{e.push("\u26A0 claude CLI not found \u2014 snapshots will be objective-only (git state, no narrative)")}console.log(`
15
+ sc init complete
16
+ `),e.forEach(r=>console.log(" ",r)),console.log(`
17
+ How it works:`),console.log(" \u2022 When a Claude session ends, sc snapshot runs automatically"),console.log(" \u2022 The snapshot is saved to .claude/session.md"),console.log(" \u2022 CLAUDE.md imports it, so every new session starts with full context"),c||(console.log(`
18
+ To enable narrative summaries, install the Claude CLI:`),console.log(" https://docs.anthropic.com/en/docs/claude-code")),console.log()}import{spawnSync as X}from"child_process";import{existsSync as Y,readFileSync as V}from"fs";var K=`You are summarizing a Claude Code development session for the developer who just finished it.
19
+ Given the git state below, write a concise session snapshot in this exact format (no other text):
20
+
21
+ **Status:** <one sentence \u2014 what was being worked on and where things stand>
22
+
23
+ **Decisions made:**
24
+ - <decision 1 \u2014 include the "why" if inferable from context>
25
+ - <decision 2>
26
+
27
+ **Next steps:**
28
+ - <most important thing to do next>
29
+ - <second thing if applicable>
30
+
31
+ **Blockers:** <"None" or a brief description>
32
+
33
+ Rules:
34
+ - Total response must be under 120 words
35
+ - Be specific \u2014 name files, functions, or commands if relevant
36
+ - If git state shows no meaningful changes, say "No significant changes this session"
37
+ - Do not add headings, preamble, or closing remarks \u2014 just the five fields above
38
+
39
+ Git state:
40
+ `;function Q(){let e=f("branch --show-current","unknown"),o=f("status --short","(no changes)"),t=f("log --oneline -5","(no commits)"),s=f("diff --stat HEAD","").split(`
41
+ `).slice(0,10).join(`
42
+ `);return{branch:e,status:o,log:t,diffStat:s}}function Z(e,o){let{branch:t,status:s,log:d,diffStat:i}=o;return[`## Session \u2014 ${e} | branch: ${t}`,"","**Git status:**","```",s||"(clean)","```","","**Recent commits:**","```",d||"(none)","```",i?`
43
+ **Diff stat:**
44
+ \`\`\`
45
+ ${i}
46
+ \`\`\``:""].filter(c=>c!==void 0).join(`
47
+ `).trim()}function ee(e){let o=[`Branch: ${e.branch}`,`Status:
48
+ ${e.status}`,`Recent commits:
49
+ ${e.log}`,e.diffStat?`Diff stat:
50
+ ${e.diffStat}`:""].filter(Boolean).join(`
51
+
52
+ `),t=K+o,s=X("claude",["-p",t],{encoding:"utf8",timeout:15e3,stdio:["pipe","pipe","pipe"]});return s.status!==0||s.error||!s.stdout.trim()?null:s.stdout.trim()}async function g(){S(p);let e=new Date().toISOString(),o=Q(),t=Z(e,o),s=ee(o);if(s){let E=C(s,_);t+=`
53
+
54
+ **Narrative:**
55
+ `+E}else t+="\n\n**Narrative:** [unavailable \u2014 run `claude --version` to enable]";let d=Y(n)?V(n,"utf8"):"",{pinned:i,rest:c}=$(d),r=b(c),a=[t,...r].slice(0,N),y=(i?i+`
56
+ `:"")+A(a);m(n,y),process.stderr.write(`[sc] snapshot written \u2014 ${e}
57
+ `)}import{existsSync as oe,readFileSync as te}from"fs";async function W(){if(!oe(n)){console.log("No session file found. Run `sc init` first.");return}let e=te(n,"utf8").trim();if(!e||e.startsWith("<!--")){console.log("No session recorded yet. End a Claude session to generate the first snapshot.");return}console.log(`
58
+ \u2500\u2500 Current session state (`+n+`) \u2500\u2500
59
+ `),console.log(e),console.log(`
60
+ \u2500\u2500 end \u2500\u2500
61
+ `)}import{existsSync as se}from"fs";import{createInterface as ne}from"readline";async function j(){if(!se(n)){console.log("Nothing to clear \u2014 .claude/session.md does not exist.");return}if(!await ie("Clear all session history? This cannot be undone. (y/N) ")){console.log("Aborted.");return}m(n,u),console.log("Session history cleared.")}function ie(e){return new Promise(o=>{let t=ne({input:process.stdin,output:process.stdout});t.question(e,s=>{t.close(),o(s.trim().toLowerCase()==="y")})})}async function F(){console.log("Writing snapshot\u2026"),await g(),console.log("Done. Run `sc status` to review.")}import{existsSync as re,readFileSync as ce}from"fs";var T="## Pinned decisions",ae="<!-- sc: pinned decisions survive the rolling session window -->";async function M([e]){(!e||!e.trim())&&(console.error('Usage: sc decide "Your decision or rationale here"'),process.exit(1));let o=e.trim(),t=`- ${o}`,s=new Date().toISOString(),d=`${t} _(${s})_`,i=re(n)?ce(n,"utf8"):u,c;if(i.includes(T))c=i.replace(/(## Pinned decisions\n(?:<!--[^>]*-->\n)?)([\s\S]*?)(\n---|\n<!-- sc:|$)/,(r,a,y,E)=>`${a}${y}
62
+ ${d}${E}`);else{let r=`${T}
63
+ ${ae}
64
+ ${d}
65
+
66
+ `,a=i.indexOf(`---
67
+ `);a===-1||i.trim()===u.trim()?c=r+(i.startsWith("<!--")?"":i):c=i.slice(0,a)+r+i.slice(a)}m(n,c),console.log(`Decision pinned: "${o}"`)}var[,,l,...le]=process.argv,U={init:P,snapshot:g,status:W,clear:j,rotate:F,decide:M};(!l||l==="--help"||l==="-h")&&(console.log(`
68
+ sc \u2014 session continuity for Claude Code
69
+
70
+ Commands:
71
+ sc init Wire up this project (run once per project)
72
+ sc snapshot Write a session snapshot (called automatically by Stop hook)
73
+ sc status Show the current session state
74
+ sc clear Reset session history
75
+ sc rotate Manually trigger a snapshot write (use before force-quit)
76
+ sc decide "<why>" Pin a permanent decision that survives the rolling window
77
+
78
+ Options:
79
+ --version, -v Print version
80
+ --help, -h Show this help
81
+ `),process.exit(0));if(l==="--version"||l==="-v"){let{createRequire:e}=await import("module"),t=e(import.meta.url)("../package.json");console.log(t.version),process.exit(0)}U[l]||(console.error(`Unknown command: ${l}. Run 'sc --help' for usage.`),process.exit(1));try{await U[l](le)}catch(e){console.error(`sc ${l} failed:`,e.message),process.exit(1)}
package/dist/server.js ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ #!/usr/bin/env node
3
+ import{McpServer as C}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as L}from"@modelcontextprotocol/sdk/server/stdio.js";import{z as r}from"zod";import{existsSync as x,readFileSync as y,readdirSync as N,mkdirSync as O}from"fs";import{join as d,basename as S}from"path";import{homedir as k}from"os";import{createHash as I}from"crypto";import{readFileSync as M,writeFileSync as j,existsSync as W,mkdirSync as F,renameSync as v}from"fs";import{join as h}from"path";var l=".claude",U=h(l,"session.md"),X=h(l,"settings.json");var m=3;function f(e,s){let t=e+".tmp";j(t,s,"utf8"),v(t,e)}var c=d(k(),".sc","sessions");function g(){x(c)||O(c,{recursive:!0})}function A(e){return I("sha1").update(e).digest("hex").slice(0,12)}function w(e){return d(c,`${A(e)}.json`)}function u(e){let s=w(e);if(!x(s))return{path:e,name:S(e),pinned:[],sessions:[]};try{return JSON.parse(y(s,"utf8"))}catch{return{path:e,name:S(e),pinned:[],sessions:[]}}}function b(e,s){g(),f(w(e),JSON.stringify(s,null,2)+`
4
+ `)}function $(e,s){let t=u(e);return t.sessions=[s,...t.sessions].slice(0,m),b(e,t),t}function _(e,s){let t=u(e);return t.pinned=t.pinned||[],t.pinned.push({text:s,timestamp:new Date().toISOString()}),b(e,t),t}function D(){return g(),N(c).filter(e=>e.endsWith(".json")).map(e=>{try{return JSON.parse(y(d(c,e),"utf8"))}catch{return null}}).filter(Boolean).sort((e,s)=>{let t=e.sessions[0]?.timestamp??"";return(s.sessions[0]?.timestamp??"").localeCompare(t)})}import{createRequire as P}from"module";var{version:R}=P(import.meta.url)("../package.json"),a=new C({name:"session-continuity",version:R});a.tool("load_session","Load the previous session context for a project. Call this at the start of every session to restore context without re-explanation.",{project_path:r.string().describe("Absolute path to the project root. Defaults to current working directory.").optional()},async({project_path:e})=>{let s=e||process.cwd(),t=u(s);if(t.sessions.length===0&&t.pinned.length===0)return{content:[{type:"text",text:`No previous session found for ${t.name}. This appears to be a fresh start.`}]};let n=[`# Session context \u2014 ${t.name}`,`**Project:** ${t.path}`,""];return t.pinned.length>0&&(n.push("## Pinned decisions"),t.pinned.forEach(o=>n.push(`- ${o.text} *(${o.timestamp})*`)),n.push("")),t.sessions.forEach((o,p)=>{n.push(`## ${p===0?"Last session":`Session ${p+1} ago`} \u2014 ${o.timestamp}`),n.push(`**Branch:** ${o.branch}`),n.push(`**Status:** ${o.status}`),o.decisions?.length&&(n.push("**Decisions made:**"),o.decisions.forEach(i=>n.push(`- ${i}`))),o.next_steps?.length&&(n.push("**Next steps:**"),o.next_steps.forEach(i=>n.push(`- ${i}`))),o.blockers&&n.push(`**Blockers:** ${o.blockers}`),n.push("")}),{content:[{type:"text",text:n.join(`
5
+ `)}]}});a.tool("save_session","Save the current session context. Call this at the end of a session or at any checkpoint. Claude should summarize what happened, decisions made, and what comes next.",{project_path:r.string().describe("Absolute path to the project root. Defaults to current working directory.").optional(),status:r.string().describe("One sentence: what was being worked on and where things stand."),decisions:r.array(r.string()).describe("Key decisions made this session, each with the reason why.").optional(),next_steps:r.array(r.string()).describe("Ordered list of what to do next session, most important first.").optional(),blockers:r.string().describe('Current blockers, or "None".').optional(),branch:r.string().describe("Current git branch.").optional()},async({project_path:e,status:s,decisions:t,next_steps:n,blockers:o,branch:p})=>{let i=e||process.cwd(),E={timestamp:new Date().toISOString(),branch:p||"unknown",status:s,decisions:t||[],next_steps:n||[],blockers:o||"None"};return $(i,E),{content:[{type:"text",text:`Session saved for ${i}.
6
+ Next session will start with: "${s}"`}]}});a.tool("pin_decision","Pin a permanent decision or architectural choice that should survive the rolling session window. Use for tradeoffs, rejected alternatives, and why-choices.",{project_path:r.string().describe("Absolute path to the project root. Defaults to current working directory.").optional(),decision:r.string().describe('The decision to record. Include the "why" \u2014 e.g. "Chose X over Y because Z."')},async({project_path:e,decision:s})=>{let t=e||process.cwd();return _(t,s),{content:[{type:"text",text:`Decision pinned: "${s}"`}]}});a.tool("list_projects","List all projects with tracked session history, sorted by most recently active.",{},async()=>{let e=D();if(e.length===0)return{content:[{type:"text",text:"No projects tracked yet. Save a session first."}]};let s=[`# Tracked projects
7
+ `];return e.forEach(t=>{let n=t.sessions[0];s.push(`**${t.name}**`),s.push(` Path: ${t.path}`),n&&(s.push(` Last session: ${n.timestamp}`),s.push(` Status: ${n.status}`)),t.pinned?.length&&s.push(` Pinned decisions: ${t.pinned.length}`),s.push("")}),{content:[{type:"text",text:s.join(`
8
+ `)}]}});var T=new L;await a.connect(T);
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "session-continuity",
3
+ "version": "0.1.3",
4
+ "description": "Pick up where you left off across Claude sessions — zero re-explanation.",
5
+ "bin": {
6
+ "sc": "./dist/cli.js",
7
+ "sc-mcp": "./dist/server.js"
8
+ },
9
+ "type": "module",
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "build": "node build.js",
15
+ "prepare": "node build.js",
16
+ "test": "node --test src/*.test.js"
17
+ },
18
+ "engines": {
19
+ "node": ">=18"
20
+ },
21
+ "keywords": [
22
+ "mcp",
23
+ "claude",
24
+ "claude-code",
25
+ "session",
26
+ "context",
27
+ "memory",
28
+ "continuity",
29
+ "ai-native",
30
+ "workflow",
31
+ "developer-tools"
32
+ ],
33
+ "license": "PolyForm-Noncommercial-1.0.0",
34
+ "dependencies": {
35
+ "@modelcontextprotocol/sdk": "^1.29.0",
36
+ "zod": "^4.4.3"
37
+ },
38
+ "devDependencies": {
39
+ "esbuild": "^0.28.0"
40
+ }
41
+ }