@supersession/sup 0.1.1
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/README.md +61 -0
- package/dist/cli.d.ts +14 -0
- package/dist/cli.js +385 -0
- package/dist/cli.js.map +1 -0
- package/dist/cloud.d.ts +33 -0
- package/dist/cloud.js +72 -0
- package/dist/cloud.js.map +1 -0
- package/dist/config.d.ts +10 -0
- package/dist/config.js +36 -0
- package/dist/config.js.map +1 -0
- package/dist/discover.d.ts +14 -0
- package/dist/discover.js +44 -0
- package/dist/discover.js.map +1 -0
- package/dist/distill/smart.d.ts +30 -0
- package/dist/distill/smart.js +108 -0
- package/dist/distill/smart.js.map +1 -0
- package/dist/distill/template.d.ts +58 -0
- package/dist/distill/template.js +127 -0
- package/dist/distill/template.js.map +1 -0
- package/dist/engine.d.ts +24 -0
- package/dist/engine.js +42 -0
- package/dist/engine.js.map +1 -0
- package/dist/health.d.ts +35 -0
- package/dist/health.js +133 -0
- package/dist/health.js.map +1 -0
- package/dist/import.d.ts +27 -0
- package/dist/import.js +129 -0
- package/dist/import.js.map +1 -0
- package/dist/llm.d.ts +23 -0
- package/dist/llm.js +90 -0
- package/dist/llm.js.map +1 -0
- package/dist/mcp.d.ts +13 -0
- package/dist/mcp.js +115 -0
- package/dist/mcp.js.map +1 -0
- package/dist/nsf.d.ts +75 -0
- package/dist/nsf.js +14 -0
- package/dist/nsf.js.map +1 -0
- package/dist/parsers/chat-export.d.ts +21 -0
- package/dist/parsers/chat-export.js +107 -0
- package/dist/parsers/chat-export.js.map +1 -0
- package/dist/parsers/claude-code.d.ts +24 -0
- package/dist/parsers/claude-code.js +193 -0
- package/dist/parsers/claude-code.js.map +1 -0
- package/dist/redact.d.ts +24 -0
- package/dist/redact.js +65 -0
- package/dist/redact.js.map +1 -0
- package/dist/tools.d.ts +37 -0
- package/dist/tools.js +67 -0
- package/dist/tools.js.map +1 -0
- package/dist/writers/brief-markdown.d.ts +12 -0
- package/dist/writers/brief-markdown.js +83 -0
- package/dist/writers/brief-markdown.js.map +1 -0
- package/package.json +45 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-export.js","sourceRoot":"","sources":["../../src/parsers/chat-export.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code parser: JSONL transcript -> NSF.
|
|
3
|
+
*
|
|
4
|
+
* Grounded in the REAL on-disk schema (verified 2026-07-14 against live
|
|
5
|
+
* sessions), not a guess:
|
|
6
|
+
* - Each line is a JSON record with a top-level `type`.
|
|
7
|
+
* - Message-bearing types: "user", "assistant". Others (system, mode,
|
|
8
|
+
* file-history-snapshot, attachment, last-prompt, ai-title, summary) carry
|
|
9
|
+
* no conversational turn and are counted as gaps, not dropped silently.
|
|
10
|
+
* - `message.content` is EITHER a string OR an array of blocks.
|
|
11
|
+
* - Block types seen: "text", "thinking", "tool_use", "tool_result".
|
|
12
|
+
* - Records carry: cwd, gitBranch, sessionId, timestamp, isSidechain, isMeta.
|
|
13
|
+
*
|
|
14
|
+
* Compaction note (per SPEC §4d): Claude Code writes compaction as an added
|
|
15
|
+
* summary record, not by deleting turns — so reading the file survives it. We
|
|
16
|
+
* treat a summary record as a gap (it's a vendor artifact, not a user turn).
|
|
17
|
+
*/
|
|
18
|
+
import type { NsfSession } from "../nsf.js";
|
|
19
|
+
export declare const CLAUDE_CODE_TOOL_ID = "claude-code";
|
|
20
|
+
/**
|
|
21
|
+
* Parse a Claude Code JSONL session file into NSF.
|
|
22
|
+
* @param filePath absolute path to the `.jsonl` transcript.
|
|
23
|
+
*/
|
|
24
|
+
export declare function parseClaudeCode(filePath: string): NsfSession;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code parser: JSONL transcript -> NSF.
|
|
3
|
+
*
|
|
4
|
+
* Grounded in the REAL on-disk schema (verified 2026-07-14 against live
|
|
5
|
+
* sessions), not a guess:
|
|
6
|
+
* - Each line is a JSON record with a top-level `type`.
|
|
7
|
+
* - Message-bearing types: "user", "assistant". Others (system, mode,
|
|
8
|
+
* file-history-snapshot, attachment, last-prompt, ai-title, summary) carry
|
|
9
|
+
* no conversational turn and are counted as gaps, not dropped silently.
|
|
10
|
+
* - `message.content` is EITHER a string OR an array of blocks.
|
|
11
|
+
* - Block types seen: "text", "thinking", "tool_use", "tool_result".
|
|
12
|
+
* - Records carry: cwd, gitBranch, sessionId, timestamp, isSidechain, isMeta.
|
|
13
|
+
*
|
|
14
|
+
* Compaction note (per SPEC §4d): Claude Code writes compaction as an added
|
|
15
|
+
* summary record, not by deleting turns — so reading the file survives it. We
|
|
16
|
+
* treat a summary record as a gap (it's a vendor artifact, not a user turn).
|
|
17
|
+
*/
|
|
18
|
+
import { readFileSync } from "node:fs";
|
|
19
|
+
import { NSF_VERSION } from "../nsf.js";
|
|
20
|
+
export const CLAUDE_CODE_TOOL_ID = "claude-code";
|
|
21
|
+
/** Map a Claude Code tool name to a normalized NSF action kind. */
|
|
22
|
+
function normalizeActionKind(name) {
|
|
23
|
+
switch (name) {
|
|
24
|
+
case "Edit":
|
|
25
|
+
case "MultiEdit":
|
|
26
|
+
return "edit";
|
|
27
|
+
case "Write":
|
|
28
|
+
case "NotebookEdit":
|
|
29
|
+
return "write";
|
|
30
|
+
case "Read":
|
|
31
|
+
case "NotebookRead":
|
|
32
|
+
return "read";
|
|
33
|
+
case "Bash":
|
|
34
|
+
case "BashOutput":
|
|
35
|
+
return "run";
|
|
36
|
+
case "Grep":
|
|
37
|
+
case "Glob":
|
|
38
|
+
return "search";
|
|
39
|
+
default:
|
|
40
|
+
return "other";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** Extract the file path an action touched, from the tool's input object. */
|
|
44
|
+
function extractPath(input) {
|
|
45
|
+
if (!input || typeof input !== "object")
|
|
46
|
+
return undefined;
|
|
47
|
+
const o = input;
|
|
48
|
+
const p = o.file_path ?? o.notebook_path ?? o.path;
|
|
49
|
+
return typeof p === "string" ? p : undefined;
|
|
50
|
+
}
|
|
51
|
+
function extractCommand(input) {
|
|
52
|
+
if (!input || typeof input !== "object")
|
|
53
|
+
return undefined;
|
|
54
|
+
const c = input.command;
|
|
55
|
+
return typeof c === "string" ? c : undefined;
|
|
56
|
+
}
|
|
57
|
+
/** Pull prose text + actions out of one message's content (string or blocks). */
|
|
58
|
+
function readContent(content) {
|
|
59
|
+
if (typeof content === "string") {
|
|
60
|
+
return { text: content.trim(), actions: [] };
|
|
61
|
+
}
|
|
62
|
+
if (!Array.isArray(content))
|
|
63
|
+
return { text: "", actions: [] };
|
|
64
|
+
const textParts = [];
|
|
65
|
+
const actions = [];
|
|
66
|
+
for (const block of content) {
|
|
67
|
+
if (!block || typeof block !== "object")
|
|
68
|
+
continue;
|
|
69
|
+
switch (block.type) {
|
|
70
|
+
case "text":
|
|
71
|
+
if (typeof block.text === "string")
|
|
72
|
+
textParts.push(block.text.trim());
|
|
73
|
+
break;
|
|
74
|
+
case "thinking":
|
|
75
|
+
// Deliberately dropped: private reasoning, not portable context.
|
|
76
|
+
break;
|
|
77
|
+
case "tool_use": {
|
|
78
|
+
const rawName = typeof block.name === "string" ? block.name : "unknown";
|
|
79
|
+
actions.push({
|
|
80
|
+
kind: normalizeActionKind(rawName),
|
|
81
|
+
rawName,
|
|
82
|
+
path: extractPath(block.input),
|
|
83
|
+
command: extractCommand(block.input),
|
|
84
|
+
});
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
case "tool_result": {
|
|
88
|
+
// Mark the matching action as failed if the tool reported an error.
|
|
89
|
+
// Results are otherwise dropped (noise); the action itself is the signal.
|
|
90
|
+
if (block.is_error && actions.length > 0) {
|
|
91
|
+
actions[actions.length - 1].failed = true;
|
|
92
|
+
}
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return { text: textParts.join("\n\n").trim(), actions };
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Parse a Claude Code JSONL session file into NSF.
|
|
101
|
+
* @param filePath absolute path to the `.jsonl` transcript.
|
|
102
|
+
*/
|
|
103
|
+
export function parseClaudeCode(filePath) {
|
|
104
|
+
const raw = readFileSync(filePath, "utf8");
|
|
105
|
+
const lines = raw.split("\n");
|
|
106
|
+
const turns = [];
|
|
107
|
+
const filesSeen = new Set();
|
|
108
|
+
const filesOrdered = [];
|
|
109
|
+
const gapCounts = new Map();
|
|
110
|
+
const provenance = {
|
|
111
|
+
tool: CLAUDE_CODE_TOOL_ID,
|
|
112
|
+
rawBytes: Buffer.byteLength(raw, "utf8"),
|
|
113
|
+
};
|
|
114
|
+
let compactions = 0;
|
|
115
|
+
const addGap = (reason) => gapCounts.set(reason, (gapCounts.get(reason) ?? 0) + 1);
|
|
116
|
+
for (const line of lines) {
|
|
117
|
+
const trimmed = line.trim();
|
|
118
|
+
if (!trimmed)
|
|
119
|
+
continue;
|
|
120
|
+
let rec;
|
|
121
|
+
try {
|
|
122
|
+
rec = JSON.parse(trimmed);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
addGap("unparseable line");
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
// Capture provenance from the first record that carries it.
|
|
129
|
+
provenance.cwd ??= rec.cwd;
|
|
130
|
+
provenance.gitBranch ??= rec.gitBranch;
|
|
131
|
+
provenance.sessionId ??= rec.sessionId;
|
|
132
|
+
const type = rec.type;
|
|
133
|
+
// Compaction/summary events: a "scar" — a lossy in-place summarization.
|
|
134
|
+
// Detected best-effort by type or an isCompactSummary marker.
|
|
135
|
+
if (type === "summary" ||
|
|
136
|
+
(typeof type === "string" && /compact/i.test(type)) ||
|
|
137
|
+
rec.isCompactSummary === true) {
|
|
138
|
+
compactions++;
|
|
139
|
+
addGap("compaction/summary event");
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (type !== "user" && type !== "assistant") {
|
|
143
|
+
addGap(`non-message record (${type ?? "unknown"})`);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
// Sidechains are subagent conversations — real work, but not the main
|
|
147
|
+
// thread the user is handing off. Fold as a gap so the report is honest.
|
|
148
|
+
if (rec.isSidechain) {
|
|
149
|
+
addGap("subagent sidechain turn");
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const role = rec.message?.role;
|
|
153
|
+
if (role !== "user" && role !== "assistant") {
|
|
154
|
+
addGap("message with no role");
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const { text, actions } = readContent(rec.message?.content);
|
|
158
|
+
for (const a of actions) {
|
|
159
|
+
if (a.path && !filesSeen.has(a.path)) {
|
|
160
|
+
filesSeen.add(a.path);
|
|
161
|
+
filesOrdered.push(a.path);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// A turn with neither prose nor actions is pure noise (e.g. a lone
|
|
165
|
+
// tool_result echo). Skip it rather than emit an empty turn.
|
|
166
|
+
if (!text && actions.length === 0) {
|
|
167
|
+
if (!rec.isMeta)
|
|
168
|
+
addGap("empty turn");
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
// Meta turns (local-command caveats, system-injected notes) aren't the
|
|
172
|
+
// user actually talking — mark as gap but keep the text, it can carry
|
|
173
|
+
// useful signal like slash-command output.
|
|
174
|
+
if (rec.isMeta)
|
|
175
|
+
addGap("meta/system-injected turn");
|
|
176
|
+
turns.push({
|
|
177
|
+
role,
|
|
178
|
+
text,
|
|
179
|
+
actions,
|
|
180
|
+
timestamp: rec.timestamp ? Date.parse(rec.timestamp) : undefined,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
const gaps = [...gapCounts.entries()].map(([reason, count]) => ({ reason, count }));
|
|
184
|
+
provenance.compactions = compactions;
|
|
185
|
+
return {
|
|
186
|
+
nsfVersion: NSF_VERSION,
|
|
187
|
+
provenance,
|
|
188
|
+
turns,
|
|
189
|
+
filesTouched: filesOrdered,
|
|
190
|
+
gaps,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=claude-code.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-code.js","sourceRoot":"","sources":["../../src/parsers/claude-code.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,MAAM,CAAC,MAAM,mBAAmB,GAAG,aAAa,CAAC;AAEjD,mEAAmE;AACnE,SAAS,mBAAmB,CAAC,IAAY;IACvC,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,MAAM,CAAC;QACZ,KAAK,WAAW;YACd,OAAO,MAAM,CAAC;QAChB,KAAK,OAAO,CAAC;QACb,KAAK,cAAc;YACjB,OAAO,OAAO,CAAC;QACjB,KAAK,MAAM,CAAC;QACZ,KAAK,cAAc;YACjB,OAAO,MAAM,CAAC;QAChB,KAAK,MAAM,CAAC;QACZ,KAAK,YAAY;YACf,OAAO,KAAK,CAAC;QACf,KAAK,MAAM,CAAC;QACZ,KAAK,MAAM;YACT,OAAO,QAAQ,CAAC;QAClB;YACE,OAAO,OAAO,CAAC;IACnB,CAAC;AACH,CAAC;AAED,6EAA6E;AAC7E,SAAS,WAAW,CAAC,KAAc;IACjC,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAC1D,MAAM,CAAC,GAAG,KAAgC,CAAC;IAC3C,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,aAAa,IAAI,CAAC,CAAC,IAAI,CAAC;IACnD,OAAO,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAC/C,CAAC;AAED,SAAS,cAAc,CAAC,KAAc;IACpC,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAC1D,MAAM,CAAC,GAAI,KAAiC,CAAC,OAAO,CAAC;IACrD,OAAO,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAC/C,CAAC;AAyBD,iFAAiF;AACjF,SAAS,WAAW,CAAC,OAAgB;IACnC,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAChC,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC/C,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAE9D,MAAM,SAAS,GAAa,EAAE,CAAC;IAC/B,MAAM,OAAO,GAAgB,EAAE,CAAC;IAEhC,KAAK,MAAM,KAAK,IAAI,OAAyB,EAAE,CAAC;QAC9C,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,SAAS;QAClD,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;YACnB,KAAK,MAAM;gBACT,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ;oBAAE,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;gBACtE,MAAM;YACR,KAAK,UAAU;gBACb,iEAAiE;gBACjE,MAAM;YACR,KAAK,UAAU,CAAC,CAAC,CAAC;gBAChB,MAAM,OAAO,GAAG,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;gBACxE,OAAO,CAAC,IAAI,CAAC;oBACX,IAAI,EAAE,mBAAmB,CAAC,OAAO,CAAC;oBAClC,OAAO;oBACP,IAAI,EAAE,WAAW,CAAC,KAAK,CAAC,KAAK,CAAC;oBAC9B,OAAO,EAAE,cAAc,CAAC,KAAK,CAAC,KAAK,CAAC;iBACrC,CAAC,CAAC;gBACH,MAAM;YACR,CAAC;YACD,KAAK,aAAa,CAAC,CAAC,CAAC;gBACnB,oEAAoE;gBACpE,0EAA0E;gBAC1E,IAAI,KAAK,CAAC,QAAQ,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACzC,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC,MAAM,GAAG,IAAI,CAAC;gBAC7C,CAAC;gBACD,MAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC;AAC1D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,QAAgB;IAC9C,MAAM,GAAG,GAAG,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAE9B,MAAM,KAAK,GAAc,EAAE,CAAC;IAC5B,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;IACpC,MAAM,YAAY,GAAa,EAAE,CAAC;IAClC,MAAM,SAAS,GAAG,IAAI,GAAG,EAAkB,CAAC;IAE5C,MAAM,UAAU,GAA6B;QAC3C,IAAI,EAAE,mBAAmB;QACzB,QAAQ,EAAE,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;KACzC,CAAC;IACF,IAAI,WAAW,GAAG,CAAC,CAAC;IAEpB,MAAM,MAAM,GAAG,CAAC,MAAc,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAE3F,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO;YAAE,SAAS;QAEvB,IAAI,GAAc,CAAC;QACnB,IAAI,CAAC;YACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC5B,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,CAAC,kBAAkB,CAAC,CAAC;YAC3B,SAAS;QACX,CAAC;QAED,4DAA4D;QAC5D,UAAU,CAAC,GAAG,KAAK,GAAG,CAAC,GAAG,CAAC;QAC3B,UAAU,CAAC,SAAS,KAAK,GAAG,CAAC,SAAS,CAAC;QACvC,UAAU,CAAC,SAAS,KAAK,GAAG,CAAC,SAAS,CAAC;QAEvC,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;QACtB,wEAAwE;QACxE,8DAA8D;QAC9D,IACE,IAAI,KAAK,SAAS;YAClB,CAAC,OAAO,IAAI,KAAK,QAAQ,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAClD,GAA+B,CAAC,gBAAgB,KAAK,IAAI,EAC1D,CAAC;YACD,WAAW,EAAE,CAAC;YACd,MAAM,CAAC,0BAA0B,CAAC,CAAC;YACnC,SAAS;QACX,CAAC;QACD,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,WAAW,EAAE,CAAC;YAC5C,MAAM,CAAC,uBAAuB,IAAI,IAAI,SAAS,GAAG,CAAC,CAAC;YACpD,SAAS;QACX,CAAC;QAED,sEAAsE;QACtE,yEAAyE;QACzE,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC;YACpB,MAAM,CAAC,yBAAyB,CAAC,CAAC;YAClC,SAAS;QACX,CAAC;QAED,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC;QAC/B,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,WAAW,EAAE,CAAC;YAC5C,MAAM,CAAC,sBAAsB,CAAC,CAAC;YAC/B,SAAS;QACX,CAAC;QAED,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,WAAW,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAE5D,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACxB,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;gBACrC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;gBACtB,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC;QAED,mEAAmE;QACnE,6DAA6D;QAC7D,IAAI,CAAC,IAAI,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAClC,IAAI,CAAC,GAAG,CAAC,MAAM;gBAAE,MAAM,CAAC,YAAY,CAAC,CAAC;YACtC,SAAS;QACX,CAAC;QAED,uEAAuE;QACvE,sEAAsE;QACtE,2CAA2C;QAC3C,IAAI,GAAG,CAAC,MAAM;YAAE,MAAM,CAAC,2BAA2B,CAAC,CAAC;QAEpD,KAAK,CAAC,IAAI,CAAC;YACT,IAAI;YACJ,IAAI;YACJ,OAAO;YACP,SAAS,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS;SACjE,CAAC,CAAC;IACL,CAAC;IAED,MAAM,IAAI,GAAa,CAAC,GAAG,SAAS,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;IAC9F,UAAU,CAAC,WAAW,GAAG,WAAW,CAAC;IAErC,OAAO;QACL,UAAU,EAAE,WAAW;QACvB,UAAU;QACV,KAAK;QACL,YAAY,EAAE,YAAY;QAC1B,IAAI;KACL,CAAC;AACJ,CAAC"}
|
package/dist/redact.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secret redaction. Runs on NSF text + commands before a brief is written or
|
|
3
|
+
* uploaded. Local-first: this is the last line of defense before your keys
|
|
4
|
+
* could leak into another tool's context or our cloud.
|
|
5
|
+
*
|
|
6
|
+
* Philosophy: false positives (over-redacting) are acceptable; false negatives
|
|
7
|
+
* (a leaked key) are not. When unsure, redact. Every hit is counted so the
|
|
8
|
+
* fidelity report can tell the user "3 secrets redacted".
|
|
9
|
+
*/
|
|
10
|
+
import type { NsfSession } from "./nsf.js";
|
|
11
|
+
export interface RedactionResult {
|
|
12
|
+
text: string;
|
|
13
|
+
hits: number;
|
|
14
|
+
}
|
|
15
|
+
/** Redact a single string. Returns the cleaned text and how many hits fired. */
|
|
16
|
+
export declare function redactText(input: string): RedactionResult;
|
|
17
|
+
/**
|
|
18
|
+
* Redact an entire NSF session in place-safe manner (returns a new session).
|
|
19
|
+
* Cleans turn text and shell commands — the two places secrets actually land.
|
|
20
|
+
*/
|
|
21
|
+
export declare function redactSession(session: NsfSession): {
|
|
22
|
+
session: NsfSession;
|
|
23
|
+
hits: number;
|
|
24
|
+
};
|
package/dist/redact.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secret redaction. Runs on NSF text + commands before a brief is written or
|
|
3
|
+
* uploaded. Local-first: this is the last line of defense before your keys
|
|
4
|
+
* could leak into another tool's context or our cloud.
|
|
5
|
+
*
|
|
6
|
+
* Philosophy: false positives (over-redacting) are acceptable; false negatives
|
|
7
|
+
* (a leaked key) are not. When unsure, redact. Every hit is counted so the
|
|
8
|
+
* fidelity report can tell the user "3 secrets redacted".
|
|
9
|
+
*/
|
|
10
|
+
// Ordered most-specific first. All global + multiline where relevant.
|
|
11
|
+
const RULES = [
|
|
12
|
+
{ name: "openai-key", pattern: /\bsk-[A-Za-z0-9_-]{20,}\b/g },
|
|
13
|
+
{ name: "anthropic-key", pattern: /\bsk-ant-[A-Za-z0-9_-]{20,}\b/g },
|
|
14
|
+
{ name: "aws-access-key", pattern: /\bAKIA[0-9A-Z]{16}\b/g },
|
|
15
|
+
{ name: "github-token", pattern: /\b(?:ghp|gho|ghu|ghs|ghr|github_pat)_[A-Za-z0-9_]{20,}\b/g },
|
|
16
|
+
{ name: "google-key", pattern: /\bAIza[0-9A-Za-z_-]{35}\b/g },
|
|
17
|
+
{ name: "slack-token", pattern: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g },
|
|
18
|
+
{ name: "stripe-key", pattern: /\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{20,}\b/g },
|
|
19
|
+
{ name: "bearer-token", pattern: /\bBearer\s+[A-Za-z0-9._-]{20,}\b/g },
|
|
20
|
+
{ name: "private-key-block", pattern: /-----BEGIN[A-Z ]*PRIVATE KEY-----[\s\S]*?-----END[A-Z ]*PRIVATE KEY-----/g },
|
|
21
|
+
{ name: "jwt", pattern: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g },
|
|
22
|
+
// Generic KEY=secret assignments (env-file style). Value must look secret-ish.
|
|
23
|
+
{
|
|
24
|
+
name: "env-assignment",
|
|
25
|
+
pattern: /\b([A-Z][A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL)[A-Z0-9_]*)\s*[=:]\s*["']?([A-Za-z0-9._/+-]{8,})["']?/g,
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
const PLACEHOLDER = (name) => `«redacted:${name}»`;
|
|
29
|
+
/** Redact a single string. Returns the cleaned text and how many hits fired. */
|
|
30
|
+
export function redactText(input) {
|
|
31
|
+
let text = input;
|
|
32
|
+
let hits = 0;
|
|
33
|
+
for (const rule of RULES) {
|
|
34
|
+
text = text.replace(rule.pattern, (match, ...groups) => {
|
|
35
|
+
hits++;
|
|
36
|
+
// For env-assignment keep the key name, mask only the value.
|
|
37
|
+
if (rule.name === "env-assignment" && typeof groups[0] === "string") {
|
|
38
|
+
return `${groups[0]}=${PLACEHOLDER("secret")}`;
|
|
39
|
+
}
|
|
40
|
+
return PLACEHOLDER(rule.name);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return { text, hits };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Redact an entire NSF session in place-safe manner (returns a new session).
|
|
47
|
+
* Cleans turn text and shell commands — the two places secrets actually land.
|
|
48
|
+
*/
|
|
49
|
+
export function redactSession(session) {
|
|
50
|
+
let hits = 0;
|
|
51
|
+
const turns = session.turns.map((turn) => {
|
|
52
|
+
const t = redactText(turn.text);
|
|
53
|
+
hits += t.hits;
|
|
54
|
+
const actions = turn.actions.map((a) => {
|
|
55
|
+
if (!a.command)
|
|
56
|
+
return a;
|
|
57
|
+
const c = redactText(a.command);
|
|
58
|
+
hits += c.hits;
|
|
59
|
+
return { ...a, command: c.text };
|
|
60
|
+
});
|
|
61
|
+
return { ...turn, text: t.text, actions };
|
|
62
|
+
});
|
|
63
|
+
return { session: { ...session, turns }, hits };
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=redact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.js","sourceRoot":"","sources":["../src/redact.ts"],"names":[],"mappings":"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"}
|
package/dist/tools.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool registry: the single place that knows, per supported tool, (a) where it
|
|
3
|
+
* stores sessions on disk, (b) how to parse them, and (c) how to hand a brief
|
|
4
|
+
* TO it. Adding a tool = adding one entry here + a parser. Everything else in
|
|
5
|
+
* the CLI is tool-agnostic.
|
|
6
|
+
*
|
|
7
|
+
* IMPORTANT (SPEC §4d): only Claude Code's on-disk format is verified. Codex,
|
|
8
|
+
* Gemini, Cursor, etc. are stubs until confirmed against a live install — they
|
|
9
|
+
* are declared but marked `verified: false` so the CLI can be honest about it
|
|
10
|
+
* instead of silently producing garbage.
|
|
11
|
+
*/
|
|
12
|
+
import type { NsfSession } from "./nsf.js";
|
|
13
|
+
import type { Brief } from "./distill/template.js";
|
|
14
|
+
export interface ToolDef {
|
|
15
|
+
id: string;
|
|
16
|
+
label: string;
|
|
17
|
+
/** False until the on-disk format is verified against a real install. */
|
|
18
|
+
verified: boolean;
|
|
19
|
+
/** Directory where this tool stores its session files, or undefined if cloud-only. */
|
|
20
|
+
sessionsDir?: () => string;
|
|
21
|
+
/** Parse one native session file into NSF. */
|
|
22
|
+
parse?: (filePath: string) => NsfSession;
|
|
23
|
+
/**
|
|
24
|
+
* Turn a brief into what this tool should receive. Returns the file to write
|
|
25
|
+
* (relative to the target repo) and an optional launch command hint shown to
|
|
26
|
+
* the user. Kept as data — actually launching subprocesses is a later step.
|
|
27
|
+
*/
|
|
28
|
+
writeTarget?: (brief: Brief, sourceLabel: string) => {
|
|
29
|
+
file: string;
|
|
30
|
+
body: string;
|
|
31
|
+
launchHint: string;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** Claude Code project-slug directory: /abs/path -> -abs-path form under ~/.claude/projects. */
|
|
35
|
+
export declare function claudeCodeProjectDir(cwd: string): string;
|
|
36
|
+
export declare const TOOLS: Record<string, ToolDef>;
|
|
37
|
+
export declare function resolveTool(id: string): ToolDef | undefined;
|
package/dist/tools.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool registry: the single place that knows, per supported tool, (a) where it
|
|
3
|
+
* stores sessions on disk, (b) how to parse them, and (c) how to hand a brief
|
|
4
|
+
* TO it. Adding a tool = adding one entry here + a parser. Everything else in
|
|
5
|
+
* the CLI is tool-agnostic.
|
|
6
|
+
*
|
|
7
|
+
* IMPORTANT (SPEC §4d): only Claude Code's on-disk format is verified. Codex,
|
|
8
|
+
* Gemini, Cursor, etc. are stubs until confirmed against a live install — they
|
|
9
|
+
* are declared but marked `verified: false` so the CLI can be honest about it
|
|
10
|
+
* instead of silently producing garbage.
|
|
11
|
+
*/
|
|
12
|
+
import { homedir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { parseClaudeCode, CLAUDE_CODE_TOOL_ID } from "./parsers/claude-code.js";
|
|
15
|
+
import { renderBriefMarkdown } from "./writers/brief-markdown.js";
|
|
16
|
+
/** Claude Code project-slug directory: /abs/path -> -abs-path form under ~/.claude/projects. */
|
|
17
|
+
export function claudeCodeProjectDir(cwd) {
|
|
18
|
+
const slug = cwd.replace(/[/.]/g, "-");
|
|
19
|
+
return join(homedir(), ".claude", "projects", slug);
|
|
20
|
+
}
|
|
21
|
+
export const TOOLS = {
|
|
22
|
+
"claude-code": {
|
|
23
|
+
id: CLAUDE_CODE_TOOL_ID,
|
|
24
|
+
label: "Claude Code",
|
|
25
|
+
verified: true,
|
|
26
|
+
sessionsDir: () => join(homedir(), ".claude", "projects"),
|
|
27
|
+
parse: parseClaudeCode,
|
|
28
|
+
writeTarget: (brief, sourceLabel) => ({
|
|
29
|
+
file: "CLAUDE.local.md",
|
|
30
|
+
body: renderBriefMarkdown(brief, sourceLabel),
|
|
31
|
+
launchHint: "claude # then say: read CLAUDE.local.md and continue",
|
|
32
|
+
}),
|
|
33
|
+
},
|
|
34
|
+
codex: {
|
|
35
|
+
id: "codex",
|
|
36
|
+
label: "Codex CLI",
|
|
37
|
+
verified: false,
|
|
38
|
+
writeTarget: (brief, sourceLabel) => ({
|
|
39
|
+
file: "AGENTS.md",
|
|
40
|
+
body: renderBriefMarkdown(brief, sourceLabel),
|
|
41
|
+
launchHint: "codex # picks up AGENTS.md automatically",
|
|
42
|
+
}),
|
|
43
|
+
},
|
|
44
|
+
gemini: {
|
|
45
|
+
id: "gemini",
|
|
46
|
+
label: "Gemini CLI",
|
|
47
|
+
verified: false,
|
|
48
|
+
writeTarget: (brief, sourceLabel) => ({
|
|
49
|
+
file: "GEMINI.md",
|
|
50
|
+
body: renderBriefMarkdown(brief, sourceLabel),
|
|
51
|
+
launchHint: "gemini # picks up GEMINI.md automatically",
|
|
52
|
+
}),
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
/** Friendly aliases so `sup claude` == `sup claude-code`, `sup cc`, etc. */
|
|
56
|
+
const ALIASES = {
|
|
57
|
+
claude: "claude-code",
|
|
58
|
+
"claude-code": "claude-code",
|
|
59
|
+
cc: "claude-code",
|
|
60
|
+
codex: "codex",
|
|
61
|
+
gemini: "gemini",
|
|
62
|
+
};
|
|
63
|
+
export function resolveTool(id) {
|
|
64
|
+
const key = id.toLowerCase();
|
|
65
|
+
return TOOLS[ALIASES[key] ?? key];
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAEhF,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAmBlE,gGAAgG;AAChG,MAAM,UAAU,oBAAoB,CAAC,GAAW;IAC9C,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACvC,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,CAAC,CAAC;AACtD,CAAC;AAED,MAAM,CAAC,MAAM,KAAK,GAA4B;IAC5C,aAAa,EAAE;QACb,EAAE,EAAE,mBAAmB;QACvB,KAAK,EAAE,aAAa;QACpB,QAAQ,EAAE,IAAI;QACd,WAAW,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,UAAU,CAAC;QACzD,KAAK,EAAE,eAAe;QACtB,WAAW,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,CAAC,CAAC;YACpC,IAAI,EAAE,iBAAiB;YACvB,IAAI,EAAE,mBAAmB,CAAC,KAAK,EAAE,WAAW,CAAC;YAC7C,UAAU,EAAE,wDAAwD;SACrE,CAAC;KACH;IAED,KAAK,EAAE;QACL,EAAE,EAAE,OAAO;QACX,KAAK,EAAE,WAAW;QAClB,QAAQ,EAAE,KAAK;QACf,WAAW,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,CAAC,CAAC;YACpC,IAAI,EAAE,WAAW;YACjB,IAAI,EAAE,mBAAmB,CAAC,KAAK,EAAE,WAAW,CAAC;YAC7C,UAAU,EAAE,4CAA4C;SACzD,CAAC;KACH;IAED,MAAM,EAAE;QACN,EAAE,EAAE,QAAQ;QACZ,KAAK,EAAE,YAAY;QACnB,QAAQ,EAAE,KAAK;QACf,WAAW,EAAE,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,CAAC,CAAC;YACpC,IAAI,EAAE,WAAW;YACjB,IAAI,EAAE,mBAAmB,CAAC,KAAK,EAAE,WAAW,CAAC;YAC7C,UAAU,EAAE,6CAA6C;SAC1D,CAAC;KACH;CACF,CAAC;AAEF,4EAA4E;AAC5E,MAAM,OAAO,GAA2B;IACtC,MAAM,EAAE,aAAa;IACrB,aAAa,EAAE,aAAa;IAC5B,EAAE,EAAE,aAAa;IACjB,KAAK,EAAE,OAAO;IACd,MAAM,EAAE,QAAQ;CACjB,CAAC;AAEF,MAAM,UAAU,WAAW,CAAC,EAAU;IACpC,MAAM,GAAG,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC;IAC7B,OAAO,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC;AACpC,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders a Brief into the canonical markdown that every target tool receives.
|
|
3
|
+
* Writers wrap this with tool-specific framing (Codex kickoff prompt, Claude
|
|
4
|
+
* Code CLAUDE.local.md, etc.), but the body is shared so a handoff reads the
|
|
5
|
+
* same regardless of destination.
|
|
6
|
+
*/
|
|
7
|
+
import type { Brief } from "../distill/template.js";
|
|
8
|
+
/**
|
|
9
|
+
* @param brief the distilled brief
|
|
10
|
+
* @param sourceTool human label of where the session came from (e.g. "Claude Code")
|
|
11
|
+
*/
|
|
12
|
+
export declare function renderBriefMarkdown(brief: Brief, sourceTool: string): string;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders a Brief into the canonical markdown that every target tool receives.
|
|
3
|
+
* Writers wrap this with tool-specific framing (Codex kickoff prompt, Claude
|
|
4
|
+
* Code CLAUDE.local.md, etc.), but the body is shared so a handoff reads the
|
|
5
|
+
* same regardless of destination.
|
|
6
|
+
*/
|
|
7
|
+
function fileLine(f) {
|
|
8
|
+
const marks = [];
|
|
9
|
+
if (!f.exists)
|
|
10
|
+
marks.push("MISSING NOW");
|
|
11
|
+
if (f.git === "M")
|
|
12
|
+
marks.push("modified");
|
|
13
|
+
else if (f.git === "A")
|
|
14
|
+
marks.push("staged");
|
|
15
|
+
else if (f.git === "??")
|
|
16
|
+
marks.push("untracked");
|
|
17
|
+
const suffix = marks.length ? ` — ${marks.join(", ")}` : "";
|
|
18
|
+
return `- \`${f.path}\`${suffix}`;
|
|
19
|
+
}
|
|
20
|
+
function turnBlock(turn) {
|
|
21
|
+
const who = turn.role === "user" ? "User" : "Assistant";
|
|
22
|
+
const actions = turn.actions
|
|
23
|
+
.filter((a) => a.path || a.command)
|
|
24
|
+
.map((a) => {
|
|
25
|
+
if (a.command)
|
|
26
|
+
return ` · ran: \`${a.command.split("\n")[0]}\`${a.failed ? " (failed)" : ""}`;
|
|
27
|
+
return ` · ${a.kind}: \`${a.path}\`${a.failed ? " (failed)" : ""}`;
|
|
28
|
+
});
|
|
29
|
+
const parts = [`**${who}:** ${turn.text || "(no prose — took actions)"}`];
|
|
30
|
+
if (actions.length)
|
|
31
|
+
parts.push(actions.join("\n"));
|
|
32
|
+
return parts.join("\n");
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* @param brief the distilled brief
|
|
36
|
+
* @param sourceTool human label of where the session came from (e.g. "Claude Code")
|
|
37
|
+
*/
|
|
38
|
+
export function renderBriefMarkdown(brief, sourceTool) {
|
|
39
|
+
const lines = [];
|
|
40
|
+
lines.push(`# Session handoff (via Supersession)`);
|
|
41
|
+
lines.push("");
|
|
42
|
+
lines.push(`You are picking up a coding session handed off from **${sourceTool}**. ` +
|
|
43
|
+
`Continue the work below as if it were your own. Do not restart from scratch; ` +
|
|
44
|
+
`the state described here is real and already on disk.`);
|
|
45
|
+
lines.push("");
|
|
46
|
+
lines.push(`## Goal`);
|
|
47
|
+
lines.push(brief.goal);
|
|
48
|
+
lines.push("");
|
|
49
|
+
if (brief.files.length) {
|
|
50
|
+
lines.push(`## Files touched (verified against the current repo)`);
|
|
51
|
+
lines.push(...brief.files.map(fileLine));
|
|
52
|
+
lines.push("");
|
|
53
|
+
}
|
|
54
|
+
if (brief.commands.length) {
|
|
55
|
+
lines.push(`## Key commands run this session`);
|
|
56
|
+
lines.push(...brief.commands.map((c) => `- \`${c}\``));
|
|
57
|
+
lines.push("");
|
|
58
|
+
}
|
|
59
|
+
if (brief.decisions?.length) {
|
|
60
|
+
lines.push(`## Decisions made (and why)`);
|
|
61
|
+
lines.push(...brief.decisions.map((d) => `- ${d}`));
|
|
62
|
+
lines.push("");
|
|
63
|
+
}
|
|
64
|
+
if (brief.deadEnds?.length) {
|
|
65
|
+
lines.push(`## Dead ends — do NOT retry these`);
|
|
66
|
+
lines.push(...brief.deadEnds.map((d) => `- ${d}`));
|
|
67
|
+
lines.push("");
|
|
68
|
+
}
|
|
69
|
+
lines.push(`## Where things stand`);
|
|
70
|
+
lines.push(brief.currentState);
|
|
71
|
+
lines.push("");
|
|
72
|
+
lines.push(`## Your next step`);
|
|
73
|
+
lines.push(brief.nextStep);
|
|
74
|
+
lines.push("");
|
|
75
|
+
if (brief.recentTurns.length) {
|
|
76
|
+
lines.push(`## Recent conversation (verbatim, most recent last)`);
|
|
77
|
+
lines.push("");
|
|
78
|
+
lines.push(brief.recentTurns.map(turnBlock).join("\n\n"));
|
|
79
|
+
lines.push("");
|
|
80
|
+
}
|
|
81
|
+
return lines.join("\n");
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=brief-markdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"brief-markdown.js","sourceRoot":"","sources":["../../src/writers/brief-markdown.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,SAAS,QAAQ,CAAC,CAAY;IAC5B,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,CAAC,CAAC,CAAC,MAAM;QAAE,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IACzC,IAAI,CAAC,CAAC,GAAG,KAAK,GAAG;QAAE,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;SACrC,IAAI,CAAC,CAAC,GAAG,KAAK,GAAG;QAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;SACxC,IAAI,CAAC,CAAC,GAAG,KAAK,IAAI;QAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACjD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7D,OAAO,OAAO,CAAC,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;AACpC,CAAC;AAED,SAAS,SAAS,CAAC,IAAa;IAC9B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC;IACxD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO;SACzB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,OAAO,CAAC;SAClC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,IAAI,CAAC,CAAC,OAAO;YAAE,OAAO,gBAAgB,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;QACjG,OAAO,SAAS,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;IACxE,CAAC,CAAC,CAAC;IACL,MAAM,KAAK,GAAG,CAAC,KAAK,GAAG,OAAO,IAAI,CAAC,IAAI,IAAI,2BAA2B,EAAE,CAAC,CAAC;IAC1E,IAAI,OAAO,CAAC,MAAM;QAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACnD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAY,EAAE,UAAkB;IAClE,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,CAAC,IAAI,CAAC,sCAAsC,CAAC,CAAC;IACnD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CACR,yDAAyD,UAAU,MAAM;QACvE,+EAA+E;QAC/E,uDAAuD,CAC1D,CAAC;IACF,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEf,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACtB,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACvB,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEf,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;QACvB,KAAK,CAAC,IAAI,CAAC,sDAAsD,CAAC,CAAC;QACnE,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;QACzC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAED,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;QAC1B,KAAK,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC;QAC/C,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACvD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAED,IAAI,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC;QAC5B,KAAK,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;QAC1C,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;QACpD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAED,IAAI,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;QAC3B,KAAK,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC;QAChD,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;QACnD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;IACpC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAC/B,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEf,KAAK,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IAChC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC3B,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEf,IAAI,KAAK,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC;QAC7B,KAAK,CAAC,IAAI,CAAC,qDAAqD,CAAC,CAAC;QAClE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;QAC1D,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@supersession/sup",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Your AI coding session, superseded — hand it off to any tool and keep going.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"sup": "./dist/cli.js",
|
|
8
|
+
"supersession-mcp": "./dist/mcp.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=20"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"prepublishOnly": "npm run build",
|
|
19
|
+
"dev": "tsx src/cli.ts",
|
|
20
|
+
"test": "vitest run",
|
|
21
|
+
"test:watch": "vitest",
|
|
22
|
+
"bench": "tsx bench/harness.ts",
|
|
23
|
+
"bench:selftest": "tsx bench/harness.ts --selftest"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
27
|
+
"picocolors": "^1.1.1"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/node": "^22.10.0",
|
|
31
|
+
"tsx": "^4.19.0",
|
|
32
|
+
"typescript": "^5.7.0",
|
|
33
|
+
"vitest": "^2.1.0"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"ai",
|
|
37
|
+
"claude-code",
|
|
38
|
+
"codex",
|
|
39
|
+
"session",
|
|
40
|
+
"handoff",
|
|
41
|
+
"context",
|
|
42
|
+
"cli"
|
|
43
|
+
],
|
|
44
|
+
"license": "MIT"
|
|
45
|
+
}
|