@sonnechasser/ntrp 0.3.2 → 0.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai/findings-stream-smoke.js +185 -0
- package/dist/ai/findings-stream-smoke.js.map +1 -0
- package/dist/ai/guardrails-smoke.js +3755 -652
- package/dist/ai/guardrails-smoke.js.map +1 -1
- package/dist/conversation/deepdive-smoke.js +3010 -0
- package/dist/conversation/deepdive-smoke.js.map +1 -0
- package/dist/conversation/loop-guard-smoke.js +23072 -9300
- package/dist/conversation/loop-guard-smoke.js.map +1 -1
- package/dist/demo/whimsy-smoke.js +5 -0
- package/dist/demo/whimsy-smoke.js.map +1 -1
- package/dist/index.js +11470 -7722
- package/dist/index.js.map +1 -1
- package/dist/investigation/quality-eval-cli.js +23025 -0
- package/dist/investigation/quality-eval-cli.js.map +1 -0
- package/dist/investigation/verbosity-cli.js +5090 -2159
- package/dist/investigation/verbosity-cli.js.map +1 -1
- package/dist/mcp/server.js +8275 -5896
- package/dist/mcp/server.js.map +1 -1
- package/dist/services/exports-registry-smoke.js +858 -0
- package/dist/services/exports-registry-smoke.js.map +1 -0
- package/dist/services/transcript-smoke.js +171 -34
- package/dist/services/transcript-smoke.js.map +1 -1
- package/dist/strategist/strategist-smoke.js +1203 -75
- package/dist/strategist/strategist-smoke.js.map +1 -1
- package/dist/whimsy/time-bank-smoke.js +9144 -4944
- package/dist/whimsy/time-bank-smoke.js.map +1 -1
- package/package.json +5 -1
|
@@ -0,0 +1,858 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
process.noDeprecation = true;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
// src/config/store.ts
|
|
9
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
10
|
+
import { homedir } from "os";
|
|
11
|
+
import { join, resolve } from "path";
|
|
12
|
+
function ntrpHome() {
|
|
13
|
+
return NTRP_DIR;
|
|
14
|
+
}
|
|
15
|
+
function ensureDir() {
|
|
16
|
+
if (!existsSync(NTRP_DIR)) {
|
|
17
|
+
mkdirSync(NTRP_DIR, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function loadConfig() {
|
|
21
|
+
if (cachedConfig) return cachedConfig;
|
|
22
|
+
ensureDir();
|
|
23
|
+
if (!existsSync(CONFIG_PATH)) {
|
|
24
|
+
cachedConfig = {};
|
|
25
|
+
return cachedConfig;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
cachedConfig = JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
|
|
29
|
+
} catch {
|
|
30
|
+
cachedConfig = {};
|
|
31
|
+
}
|
|
32
|
+
return cachedConfig;
|
|
33
|
+
}
|
|
34
|
+
function saveConfig(config) {
|
|
35
|
+
ensureDir();
|
|
36
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n");
|
|
37
|
+
cachedConfig = config;
|
|
38
|
+
}
|
|
39
|
+
function resetConfigCache() {
|
|
40
|
+
cachedConfig = null;
|
|
41
|
+
}
|
|
42
|
+
function setConfigValue(key, value) {
|
|
43
|
+
const config = loadConfig();
|
|
44
|
+
config[key] = value;
|
|
45
|
+
saveConfig(config);
|
|
46
|
+
}
|
|
47
|
+
function getExportsDir() {
|
|
48
|
+
const config = loadConfig();
|
|
49
|
+
const dir = resolve(config["export-dir"] ?? join(NTRP_DIR, "exports"));
|
|
50
|
+
if (!existsSync(dir)) {
|
|
51
|
+
mkdirSync(dir, { recursive: true });
|
|
52
|
+
}
|
|
53
|
+
return dir;
|
|
54
|
+
}
|
|
55
|
+
function getConfiguredAiInboxDir() {
|
|
56
|
+
const raw = loadConfig()["ai-inbox-dir"];
|
|
57
|
+
return raw ? resolve(raw) : null;
|
|
58
|
+
}
|
|
59
|
+
var NTRP_DIR, CONFIG_PATH, cachedConfig;
|
|
60
|
+
var init_store = __esm({
|
|
61
|
+
"src/config/store.ts"() {
|
|
62
|
+
"use strict";
|
|
63
|
+
NTRP_DIR = process.env.NTRP_HOME ? resolve(process.env.NTRP_HOME) : join(homedir(), ".ntrp");
|
|
64
|
+
CONFIG_PATH = join(NTRP_DIR, "config.json");
|
|
65
|
+
cachedConfig = null;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// src/output/formatters.ts
|
|
70
|
+
function formatCurrency(value) {
|
|
71
|
+
if (value >= 1e6) return `$${(value / 1e6).toFixed(1)}M`;
|
|
72
|
+
if (value >= 1e3) return `$${(value / 1e3).toFixed(0)}K`;
|
|
73
|
+
return `$${value.toFixed(0)}`;
|
|
74
|
+
}
|
|
75
|
+
var VITAL_SIGN_LABELS;
|
|
76
|
+
var init_formatters = __esm({
|
|
77
|
+
"src/output/formatters.ts"() {
|
|
78
|
+
"use strict";
|
|
79
|
+
VITAL_SIGN_LABELS = {
|
|
80
|
+
freshness: "Freshness",
|
|
81
|
+
flow_rate: "Flow Rate",
|
|
82
|
+
drop_rate: "Drop Rate",
|
|
83
|
+
signal_to_noise: "Signal:Noise",
|
|
84
|
+
thread_depth: "Thread Depth"
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// src/services/exports-registry-smoke.ts
|
|
90
|
+
init_store();
|
|
91
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readdirSync as readdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "fs";
|
|
92
|
+
import { join as join5 } from "path";
|
|
93
|
+
|
|
94
|
+
// src/services/exports-registry.ts
|
|
95
|
+
init_store();
|
|
96
|
+
import {
|
|
97
|
+
appendFileSync,
|
|
98
|
+
copyFileSync,
|
|
99
|
+
cpSync,
|
|
100
|
+
existsSync as existsSync2,
|
|
101
|
+
mkdirSync as mkdirSync2,
|
|
102
|
+
readFileSync as readFileSync2,
|
|
103
|
+
readdirSync,
|
|
104
|
+
renameSync,
|
|
105
|
+
rmSync,
|
|
106
|
+
statSync,
|
|
107
|
+
writeFileSync as writeFileSync2
|
|
108
|
+
} from "fs";
|
|
109
|
+
import { basename, dirname, join as join2, resolve as resolve3, sep as sep2 } from "path";
|
|
110
|
+
import { randomUUID } from "crypto";
|
|
111
|
+
|
|
112
|
+
// src/output/path-safety.ts
|
|
113
|
+
init_store();
|
|
114
|
+
import { homedir as homedir2 } from "os";
|
|
115
|
+
import { resolve as resolve2, sep } from "path";
|
|
116
|
+
var NTRP_HOME = ntrpHome();
|
|
117
|
+
function resolveUserPath(path) {
|
|
118
|
+
if (path === "~" || path.startsWith("~/") || path.startsWith("~\\")) {
|
|
119
|
+
return resolve2(homedir2(), path.slice(2));
|
|
120
|
+
}
|
|
121
|
+
return resolve2(path);
|
|
122
|
+
}
|
|
123
|
+
function isInsideNtrp(path) {
|
|
124
|
+
const home = ntrpHome();
|
|
125
|
+
const resolved = resolve2(path);
|
|
126
|
+
return resolved === home || resolved.startsWith(home + sep);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/services/exports-registry.ts
|
|
130
|
+
var KIND_DIRS = ["handoffs", "reports", "notes", "csv", "publish"];
|
|
131
|
+
var INBOX_ARCHIVE_KEEP = 20;
|
|
132
|
+
function archiveSubdirForKind(kind) {
|
|
133
|
+
if (kind.startsWith("prompt:")) return "handoffs";
|
|
134
|
+
if (kind === "report") return "reports";
|
|
135
|
+
if (kind === "notes") return "notes";
|
|
136
|
+
if (kind === "csv") return "csv";
|
|
137
|
+
if (kind === "publish") return "publish";
|
|
138
|
+
return "handoffs";
|
|
139
|
+
}
|
|
140
|
+
function latestBasenameForKind(kind) {
|
|
141
|
+
if (kind.startsWith("prompt:")) {
|
|
142
|
+
const target = kind.slice("prompt:".length);
|
|
143
|
+
return target ? `handoff-${target}.md` : "handoff.md";
|
|
144
|
+
}
|
|
145
|
+
if (kind === "report") return "report.md";
|
|
146
|
+
if (kind === "notes") return "notes.md";
|
|
147
|
+
if (kind === "csv") return "csv";
|
|
148
|
+
if (kind === "publish") return "publish";
|
|
149
|
+
return "handoff.md";
|
|
150
|
+
}
|
|
151
|
+
function inboxLatestNameForKind(kind) {
|
|
152
|
+
if (kind.startsWith("prompt:")) {
|
|
153
|
+
const target = kind.slice("prompt:".length);
|
|
154
|
+
return target ? `latest-handoff-${target}.md` : "latest-handoff.md";
|
|
155
|
+
}
|
|
156
|
+
if (kind === "report") return "latest-report.md";
|
|
157
|
+
if (kind === "notes") return "latest-notes.md";
|
|
158
|
+
if (kind === "csv") return "latest-csv";
|
|
159
|
+
if (kind === "publish") return "latest-publish";
|
|
160
|
+
return "latest-handoff.md";
|
|
161
|
+
}
|
|
162
|
+
function exportStamp(d = /* @__PURE__ */ new Date()) {
|
|
163
|
+
return d.toISOString().replace(/T/, "-").replace(/:/g, "").slice(0, 15);
|
|
164
|
+
}
|
|
165
|
+
function ensureExportsLayout(root = getExportsDir()) {
|
|
166
|
+
mkdirSync2(root, { recursive: true });
|
|
167
|
+
mkdirSync2(join2(root, "latest"), { recursive: true });
|
|
168
|
+
for (const sub of KIND_DIRS) {
|
|
169
|
+
mkdirSync2(join2(root, sub), { recursive: true });
|
|
170
|
+
}
|
|
171
|
+
const readme = join2(root, "README.md");
|
|
172
|
+
if (!existsSync2(readme)) {
|
|
173
|
+
writeFileSync2(readme, ARCHIVE_README, "utf-8");
|
|
174
|
+
}
|
|
175
|
+
if (!existsSync2(join2(root, "INDEX.md"))) {
|
|
176
|
+
writeFileSync2(join2(root, "INDEX.md"), "# NTRP exports\n\n_No exports yet._\n", "utf-8");
|
|
177
|
+
}
|
|
178
|
+
if (!existsSync2(join2(root, "manifest.jsonl"))) {
|
|
179
|
+
writeFileSync2(join2(root, "manifest.jsonl"), "", "utf-8");
|
|
180
|
+
}
|
|
181
|
+
return root;
|
|
182
|
+
}
|
|
183
|
+
function getArchiveKindDir(kind) {
|
|
184
|
+
const root = ensureExportsLayout();
|
|
185
|
+
const dir = join2(root, archiveSubdirForKind(kind));
|
|
186
|
+
mkdirSync2(dir, { recursive: true });
|
|
187
|
+
return dir;
|
|
188
|
+
}
|
|
189
|
+
function resolveArchivePath(kind, filename) {
|
|
190
|
+
return join2(getArchiveKindDir(kind), filename);
|
|
191
|
+
}
|
|
192
|
+
function getAiInboxDir() {
|
|
193
|
+
return getConfiguredAiInboxDir();
|
|
194
|
+
}
|
|
195
|
+
function setAiInboxDir(path) {
|
|
196
|
+
const resolved = resolveUserPath(path);
|
|
197
|
+
mkdirSync2(resolved, { recursive: true });
|
|
198
|
+
setConfigValue("ai-inbox-dir", resolved);
|
|
199
|
+
ensureInboxLayout(resolved);
|
|
200
|
+
return resolved;
|
|
201
|
+
}
|
|
202
|
+
function ensureInboxLayout(inbox) {
|
|
203
|
+
mkdirSync2(inbox, { recursive: true });
|
|
204
|
+
mkdirSync2(join2(inbox, "archive"), { recursive: true });
|
|
205
|
+
const readme = join2(inbox, "README.md");
|
|
206
|
+
writeFileSync2(readme, buildInboxReadme(), "utf-8");
|
|
207
|
+
if (!existsSync2(join2(inbox, "INDEX.md"))) {
|
|
208
|
+
writeFileSync2(join2(inbox, "INDEX.md"), "# NTRP AI inbox\n\n_No exports synced yet._\n", "utf-8");
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function manifestPath(root = getExportsDir()) {
|
|
212
|
+
return join2(root, "manifest.jsonl");
|
|
213
|
+
}
|
|
214
|
+
function readManifestEvents(root = getExportsDir()) {
|
|
215
|
+
const path = manifestPath(root);
|
|
216
|
+
if (!existsSync2(path)) return [];
|
|
217
|
+
const text = readFileSync2(path, "utf-8");
|
|
218
|
+
const events = [];
|
|
219
|
+
for (const line of text.split("\n")) {
|
|
220
|
+
const trimmed = line.trim();
|
|
221
|
+
if (!trimmed) continue;
|
|
222
|
+
try {
|
|
223
|
+
events.push(JSON.parse(trimmed));
|
|
224
|
+
} catch {
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return events;
|
|
228
|
+
}
|
|
229
|
+
function appendManifestEvent(event, root = getExportsDir()) {
|
|
230
|
+
ensureExportsLayout(root);
|
|
231
|
+
appendFileSync(manifestPath(root), JSON.stringify(event) + "\n", "utf-8");
|
|
232
|
+
}
|
|
233
|
+
function listExports(opts = {}) {
|
|
234
|
+
const limit = opts.limit ?? 20;
|
|
235
|
+
const events = readManifestEvents();
|
|
236
|
+
const byId = /* @__PURE__ */ new Map();
|
|
237
|
+
for (const e of events) {
|
|
238
|
+
if (e.op === "inbox_sync") continue;
|
|
239
|
+
byId.set(e.id, e);
|
|
240
|
+
}
|
|
241
|
+
let items = [...byId.values()].sort((a, b) => a.at < b.at ? 1 : a.at > b.at ? -1 : 0);
|
|
242
|
+
if (opts.kind) {
|
|
243
|
+
const k = opts.kind.toLowerCase();
|
|
244
|
+
items = items.filter((e) => e.kind === opts.kind || e.kind.startsWith(k) || e.kind.includes(k));
|
|
245
|
+
}
|
|
246
|
+
return items.slice(0, limit);
|
|
247
|
+
}
|
|
248
|
+
function findExportByIdOrName(idOrPath) {
|
|
249
|
+
const items = listExports({ limit: 500 });
|
|
250
|
+
const needle = idOrPath.trim();
|
|
251
|
+
const byId = items.find((e) => e.id === needle || e.id.startsWith(needle));
|
|
252
|
+
if (byId) return byId;
|
|
253
|
+
const base = basename(needle);
|
|
254
|
+
const byName = items.find((e) => basename(e.path) === base || e.path.endsWith(needle));
|
|
255
|
+
if (byName) return byName;
|
|
256
|
+
const resolved = resolveUserPath(needle);
|
|
257
|
+
return items.find((e) => e.path === resolved) ?? null;
|
|
258
|
+
}
|
|
259
|
+
function updateArchiveLatest(kind, sourcePath, root) {
|
|
260
|
+
const latestDir = join2(root, "latest");
|
|
261
|
+
mkdirSync2(latestDir, { recursive: true });
|
|
262
|
+
const name = latestBasenameForKind(kind);
|
|
263
|
+
const dest = join2(latestDir, name);
|
|
264
|
+
copyPath(sourcePath, dest);
|
|
265
|
+
if (kind.startsWith("prompt:")) {
|
|
266
|
+
copyPath(sourcePath, join2(latestDir, "handoff.md"));
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function copyPath(src, dest) {
|
|
270
|
+
mkdirSync2(dirname(dest), { recursive: true });
|
|
271
|
+
if (existsSync2(dest)) {
|
|
272
|
+
rmSync(dest, { recursive: true, force: true });
|
|
273
|
+
}
|
|
274
|
+
const st = statSync(src);
|
|
275
|
+
if (st.isDirectory()) {
|
|
276
|
+
cpSync(src, dest, { recursive: true });
|
|
277
|
+
} else {
|
|
278
|
+
copyFileSync(src, dest);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function regenerateIndex(root = getExportsDir()) {
|
|
282
|
+
ensureExportsLayout(root);
|
|
283
|
+
const items = listExports({ limit: 50 });
|
|
284
|
+
const events = readManifestEvents(root);
|
|
285
|
+
const withHistory = items.filter((e) => (e.previous_paths?.length ?? 0) > 0);
|
|
286
|
+
const latestDir = join2(root, "latest");
|
|
287
|
+
const latestLines = [];
|
|
288
|
+
if (existsSync2(latestDir)) {
|
|
289
|
+
for (const name of readdirSync(latestDir).sort()) {
|
|
290
|
+
latestLines.push(`- \`latest/${name}\` \u2192 \`${join2(latestDir, name)}\``);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
const lines = [
|
|
294
|
+
"# NTRP exports",
|
|
295
|
+
"",
|
|
296
|
+
`Archive root: \`${root}\``,
|
|
297
|
+
"",
|
|
298
|
+
"Desktop AI tip: set an inbox with `/inbox set <folder>` and open that folder's `latest-handoff.md` or `INDEX.md`.",
|
|
299
|
+
"",
|
|
300
|
+
"## Latest pointers",
|
|
301
|
+
""
|
|
302
|
+
];
|
|
303
|
+
if (latestLines.length > 0) lines.push(...latestLines);
|
|
304
|
+
else lines.push("_None yet._");
|
|
305
|
+
lines.push("", "## Recent exports", "");
|
|
306
|
+
if (items.length === 0) {
|
|
307
|
+
lines.push("_No exports yet._");
|
|
308
|
+
} else {
|
|
309
|
+
for (const e of items) {
|
|
310
|
+
const title = e.title ? ` \u2014 ${e.title}` : "";
|
|
311
|
+
const session = e.session_id ? ` \xB7 session ${e.session_id.slice(-4)}` : "";
|
|
312
|
+
lines.push(`- **${e.kind}** (${e.at})${title}${session}`);
|
|
313
|
+
lines.push(` - id: \`${e.id}\``);
|
|
314
|
+
lines.push(` - path: \`${e.path}\``);
|
|
315
|
+
if (e.inbox_path) lines.push(` - inbox: \`${e.inbox_path}\``);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
lines.push("", "## Location history", "");
|
|
319
|
+
if (withHistory.length === 0) {
|
|
320
|
+
lines.push("_No moves recorded._");
|
|
321
|
+
} else {
|
|
322
|
+
for (const e of withHistory) {
|
|
323
|
+
lines.push(`- **${e.kind}** \`${e.id}\``);
|
|
324
|
+
for (const prev of e.previous_paths ?? []) {
|
|
325
|
+
lines.push(` - was: \`${prev}\``);
|
|
326
|
+
}
|
|
327
|
+
lines.push(` - now: \`${e.path}\``);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
const moveOps = events.filter((e) => e.op === "move").slice(-20).reverse();
|
|
331
|
+
if (moveOps.length > 0) {
|
|
332
|
+
lines.push("", "## Recent moves", "");
|
|
333
|
+
for (const e of moveOps) {
|
|
334
|
+
const from = e.previous_paths?.[e.previous_paths.length - 1] ?? "?";
|
|
335
|
+
lines.push(`- ${e.at}: \`${from}\` \u2192 \`${e.path}\` (${e.kind}, \`${e.id}\`)`);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
lines.push("");
|
|
339
|
+
writeFileSync2(join2(root, "INDEX.md"), lines.join("\n"), "utf-8");
|
|
340
|
+
}
|
|
341
|
+
function regenerateInboxIndex(inbox) {
|
|
342
|
+
ensureInboxLayout(inbox);
|
|
343
|
+
const items = listExports({ limit: 15 });
|
|
344
|
+
const archiveRoot = getExportsDir();
|
|
345
|
+
const lines = [
|
|
346
|
+
"# NTRP AI inbox",
|
|
347
|
+
"",
|
|
348
|
+
"Start here. Prefer `latest-handoff.md` (or `latest-handoff-<target>.md`) for the newest agent prompt.",
|
|
349
|
+
"",
|
|
350
|
+
`Canonical archive: \`${archiveRoot}\` (see \`${join2(archiveRoot, "INDEX.md")}\`).`,
|
|
351
|
+
"",
|
|
352
|
+
"## Latest pointers",
|
|
353
|
+
""
|
|
354
|
+
];
|
|
355
|
+
const latestNames = readdirSync(inbox).filter((n) => n.startsWith("latest-")).sort();
|
|
356
|
+
if (latestNames.length === 0) lines.push("_None yet \u2014 run a handoff after `/inbox set`._");
|
|
357
|
+
else {
|
|
358
|
+
for (const name of latestNames) {
|
|
359
|
+
lines.push(`- [\`${name}\`](./${name})`);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
lines.push("", "## Recent exports", "");
|
|
363
|
+
if (items.length === 0) lines.push("_No exports yet._");
|
|
364
|
+
else {
|
|
365
|
+
for (const e of items) {
|
|
366
|
+
lines.push(`- **${e.kind}** (${e.at}): \`${e.path}\``);
|
|
367
|
+
if (e.inbox_path) lines.push(` - inbox copy: \`${e.inbox_path}\``);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
lines.push("");
|
|
371
|
+
writeFileSync2(join2(inbox, "INDEX.md"), lines.join("\n"), "utf-8");
|
|
372
|
+
writeFileSync2(join2(inbox, "README.md"), buildInboxReadme(), "utf-8");
|
|
373
|
+
}
|
|
374
|
+
function buildInboxReadme() {
|
|
375
|
+
const archive = getExportsDir();
|
|
376
|
+
return `# NTRP AI inbox
|
|
377
|
+
|
|
378
|
+
This folder is the Claude Desktop / desktop-AI landing zone for NTRP handoffs.
|
|
379
|
+
|
|
380
|
+
## Start here
|
|
381
|
+
|
|
382
|
+
1. Open \`INDEX.md\` for the catalog
|
|
383
|
+
2. Or open \`latest-handoff.md\` (or \`latest-handoff-deck.md\`, etc.) for the newest prompt
|
|
384
|
+
|
|
385
|
+
Stable \`latest-*\` files are overwritten on every export. Dated copies live in \`archive/\`.
|
|
386
|
+
|
|
387
|
+
## Canonical archive
|
|
388
|
+
|
|
389
|
+
The full history (with move trail) lives at:
|
|
390
|
+
|
|
391
|
+
\`${archive}\`
|
|
392
|
+
|
|
393
|
+
See \`${join2(archive, "INDEX.md")}\` and \`${join2(archive, "manifest.jsonl")}\`.
|
|
394
|
+
|
|
395
|
+
Configure with \`/inbox set <path>\` \xB7 clear with \`/inbox clear\` \xB7 list with \`/exports\`.
|
|
396
|
+
`;
|
|
397
|
+
}
|
|
398
|
+
var ARCHIVE_README = `# NTRP exports archive
|
|
399
|
+
|
|
400
|
+
Handoffs, reports, notes, CSV receipts, and publish packages land here by kind:
|
|
401
|
+
|
|
402
|
+
- \`handoffs/\` \u2014 agent prompts (\`handoff-deck-*.md\`, \u2026)
|
|
403
|
+
- \`reports/\` \u2014 markdown reports
|
|
404
|
+
- \`notes/\` \u2014 Obsidian-style notes
|
|
405
|
+
- \`csv/\` \u2014 backmeup receipt folders
|
|
406
|
+
- \`publish/\` \u2014 repository export packages
|
|
407
|
+
- \`latest/\` \u2014 stable copies of the newest file per kind
|
|
408
|
+
|
|
409
|
+
\`INDEX.md\` is regenerated from \`manifest.jsonl\` on every write/move.
|
|
410
|
+
|
|
411
|
+
Point a desktop AI app at a dedicated inbox instead of this folder:
|
|
412
|
+
|
|
413
|
+
\`\`\`
|
|
414
|
+
/inbox set ~/Documents/Claude/ntrp-inbox
|
|
415
|
+
\`\`\`
|
|
416
|
+
`;
|
|
417
|
+
function pruneInboxArchive(archiveDir, keep = INBOX_ARCHIVE_KEEP) {
|
|
418
|
+
if (!existsSync2(archiveDir)) return;
|
|
419
|
+
const entries = readdirSync(archiveDir).map((name) => {
|
|
420
|
+
const p = join2(archiveDir, name);
|
|
421
|
+
try {
|
|
422
|
+
return { name, path: p, mtime: statSync(p).mtimeMs };
|
|
423
|
+
} catch {
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
}).filter((e) => e != null).sort((a, b) => b.mtime - a.mtime);
|
|
427
|
+
for (const old of entries.slice(keep)) {
|
|
428
|
+
rmSync(old.path, { recursive: true, force: true });
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
function syncAiInbox(entry) {
|
|
432
|
+
const inbox = getAiInboxDir();
|
|
433
|
+
if (!inbox) return null;
|
|
434
|
+
if (!existsSync2(entry.path)) return null;
|
|
435
|
+
ensureInboxLayout(inbox);
|
|
436
|
+
const archiveDir = join2(inbox, "archive");
|
|
437
|
+
mkdirSync2(archiveDir, { recursive: true });
|
|
438
|
+
const base = basename(entry.path);
|
|
439
|
+
const archiveDest = join2(archiveDir, base);
|
|
440
|
+
copyPath(entry.path, archiveDest);
|
|
441
|
+
pruneInboxArchive(archiveDir);
|
|
442
|
+
const latestName = inboxLatestNameForKind(entry.kind);
|
|
443
|
+
const latestDest = join2(inbox, latestName);
|
|
444
|
+
copyPath(entry.path, latestDest);
|
|
445
|
+
if (entry.kind.startsWith("prompt:")) {
|
|
446
|
+
copyPath(entry.path, join2(inbox, "latest-handoff.md"));
|
|
447
|
+
}
|
|
448
|
+
regenerateInboxIndex(inbox);
|
|
449
|
+
return latestDest;
|
|
450
|
+
}
|
|
451
|
+
function recordExportWrite(opts) {
|
|
452
|
+
const root = ensureExportsLayout();
|
|
453
|
+
const path = resolve3(opts.path);
|
|
454
|
+
if (!existsSync2(path)) {
|
|
455
|
+
throw new Error(`Export path does not exist: ${path}`);
|
|
456
|
+
}
|
|
457
|
+
updateArchiveLatest(opts.kind, path, root);
|
|
458
|
+
const inboxPath = syncAiInbox({ kind: opts.kind, path });
|
|
459
|
+
const event = {
|
|
460
|
+
id: randomUUID().slice(0, 8),
|
|
461
|
+
op: "write",
|
|
462
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
463
|
+
kind: opts.kind,
|
|
464
|
+
path,
|
|
465
|
+
session_id: opts.sessionId,
|
|
466
|
+
title: opts.title,
|
|
467
|
+
inbox_path: inboxPath ?? void 0
|
|
468
|
+
};
|
|
469
|
+
appendManifestEvent(event, root);
|
|
470
|
+
regenerateIndex(root);
|
|
471
|
+
return event;
|
|
472
|
+
}
|
|
473
|
+
function moveExport(idOrPath, destDir) {
|
|
474
|
+
const item = findExportByIdOrName(idOrPath);
|
|
475
|
+
if (!item) {
|
|
476
|
+
throw new Error(`No export matching "${idOrPath}". Try /exports list.`);
|
|
477
|
+
}
|
|
478
|
+
if (!existsSync2(item.path)) {
|
|
479
|
+
throw new Error(`Export file missing on disk: ${item.path}`);
|
|
480
|
+
}
|
|
481
|
+
const destRoot = resolveUserPath(destDir);
|
|
482
|
+
mkdirSync2(destRoot, { recursive: true });
|
|
483
|
+
const name = basename(item.path);
|
|
484
|
+
let destPath = join2(destRoot, name);
|
|
485
|
+
if (existsSync2(destPath)) {
|
|
486
|
+
destPath = join2(destRoot, `${exportStamp()}-${name}`);
|
|
487
|
+
}
|
|
488
|
+
renameSync(item.path, destPath);
|
|
489
|
+
const previous = [...item.previous_paths ?? [], item.path];
|
|
490
|
+
updateArchiveLatest(item.kind, destPath, ensureExportsLayout());
|
|
491
|
+
const inboxPath = syncAiInbox({ kind: item.kind, path: destPath });
|
|
492
|
+
const event = {
|
|
493
|
+
id: item.id,
|
|
494
|
+
op: "move",
|
|
495
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
496
|
+
kind: item.kind,
|
|
497
|
+
path: destPath,
|
|
498
|
+
previous_paths: previous,
|
|
499
|
+
session_id: item.session_id,
|
|
500
|
+
title: item.title,
|
|
501
|
+
inbox_path: inboxPath ?? void 0
|
|
502
|
+
};
|
|
503
|
+
appendManifestEvent(event);
|
|
504
|
+
regenerateIndex();
|
|
505
|
+
return event;
|
|
506
|
+
}
|
|
507
|
+
function archiveIndexPath() {
|
|
508
|
+
return join2(ensureExportsLayout(), "INDEX.md");
|
|
509
|
+
}
|
|
510
|
+
function inboxLatestHandoffPath() {
|
|
511
|
+
const inbox = getAiInboxDir();
|
|
512
|
+
if (!inbox) return null;
|
|
513
|
+
const p = join2(inbox, "latest-handoff.md");
|
|
514
|
+
return existsSync2(p) ? p : null;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// src/services/context-doc.ts
|
|
518
|
+
import { writeFileSync as writeFileSync5 } from "fs";
|
|
519
|
+
|
|
520
|
+
// src/cli/context.ts
|
|
521
|
+
import { basename as basename2, join as join4, resolve as resolve4, sep as sep3 } from "path";
|
|
522
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync2, rmSync as rmSync3 } from "fs";
|
|
523
|
+
import { homedir as homedir3 } from "os";
|
|
524
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
525
|
+
|
|
526
|
+
// src/services/transcript.ts
|
|
527
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3, rmSync as rmSync2 } from "fs";
|
|
528
|
+
import { join as join3 } from "path";
|
|
529
|
+
|
|
530
|
+
// src/services/terminal-capture.ts
|
|
531
|
+
var SECRET_PATTERNS = [
|
|
532
|
+
/\bsk-ant-[A-Za-z0-9_-]{8,}/g,
|
|
533
|
+
// Anthropic
|
|
534
|
+
/\bsk-or-[A-Za-z0-9_-]{8,}/g,
|
|
535
|
+
// OpenRouter
|
|
536
|
+
/\bsk-proj-[A-Za-z0-9_-]{8,}/g,
|
|
537
|
+
// OpenAI project keys
|
|
538
|
+
/\bsk-[A-Za-z0-9_-]{20,}/g,
|
|
539
|
+
// OpenAI / generic sk-
|
|
540
|
+
/\bgsk_[A-Za-z0-9_-]{8,}/g,
|
|
541
|
+
// Groq
|
|
542
|
+
/\bxai-[A-Za-z0-9_-]{8,}/g,
|
|
543
|
+
// xAI
|
|
544
|
+
/\bfw_[A-Za-z0-9_-]{8,}/g,
|
|
545
|
+
// Fireworks
|
|
546
|
+
/\bAIza[A-Za-z0-9_-]{10,}/g,
|
|
547
|
+
// Google
|
|
548
|
+
/\bNTRP-[A-Z0-9][A-Z0-9-]{8,}/g
|
|
549
|
+
// license keys
|
|
550
|
+
];
|
|
551
|
+
function redactSecrets(line) {
|
|
552
|
+
let out = line;
|
|
553
|
+
for (const pattern of SECRET_PATTERNS) {
|
|
554
|
+
out = out.replace(pattern, (m) => `${m.slice(0, 6)}\u2026[redacted]`);
|
|
555
|
+
}
|
|
556
|
+
return out;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// src/cli/context.ts
|
|
560
|
+
var STALE_SESSION_MS = 14 * 24 * 60 * 60 * 1e3;
|
|
561
|
+
function ntrpHomeDir() {
|
|
562
|
+
return process.env.NTRP_HOME ? resolve4(process.env.NTRP_HOME) : join4(homedir3(), ".ntrp");
|
|
563
|
+
}
|
|
564
|
+
function getSessionsDir() {
|
|
565
|
+
const dir = join4(ntrpHomeDir(), "sessions");
|
|
566
|
+
if (!existsSync4(dir)) {
|
|
567
|
+
mkdirSync3(dir, { recursive: true });
|
|
568
|
+
}
|
|
569
|
+
return dir;
|
|
570
|
+
}
|
|
571
|
+
function getDatasetsDir() {
|
|
572
|
+
const dir = join4(ntrpHomeDir(), "datasets");
|
|
573
|
+
if (!existsSync4(dir)) {
|
|
574
|
+
mkdirSync3(dir, { recursive: true });
|
|
575
|
+
}
|
|
576
|
+
return dir;
|
|
577
|
+
}
|
|
578
|
+
function datasetPathForSession(id) {
|
|
579
|
+
return join4(getDatasetsDir(), `${id}.duckdb`);
|
|
580
|
+
}
|
|
581
|
+
function transcriptPathForSession(id) {
|
|
582
|
+
return join4(getSessionsDir(), `${id}.transcript.md`);
|
|
583
|
+
}
|
|
584
|
+
function defaultSessionAnalysis(primary = "gtm_health") {
|
|
585
|
+
return { primary, completed: [] };
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// src/services/context-doc.ts
|
|
589
|
+
init_formatters();
|
|
590
|
+
init_store();
|
|
591
|
+
var AGENT_EXCERPT_CHARS = 400;
|
|
592
|
+
function buildSessionContextDoc(file, opts = {}) {
|
|
593
|
+
const id = file.id;
|
|
594
|
+
const shortId = id.slice(-4);
|
|
595
|
+
const exchanges = file.exchange_count ?? Math.floor(file.messages.length / 2);
|
|
596
|
+
const lines = [];
|
|
597
|
+
lines.push(`# Session context \u2014 ${id}${file.name ? ` (${file.name})` : ""}`);
|
|
598
|
+
lines.push("");
|
|
599
|
+
lines.push("## Status");
|
|
600
|
+
lines.push("");
|
|
601
|
+
lines.push(`- Stage: ${file.stage ?? "new"}`);
|
|
602
|
+
lines.push(`- Created: ${file.created_at}`);
|
|
603
|
+
if (file.ended_at) lines.push(`- Ended: ${file.ended_at}`);
|
|
604
|
+
lines.push(`- Updated: ${(/* @__PURE__ */ new Date()).toISOString()}`);
|
|
605
|
+
lines.push(`- Exchanges: ${exchanges}`);
|
|
606
|
+
if (file.summary) lines.push(`- Summary: ${file.summary}`);
|
|
607
|
+
if (file.resumed_from) lines.push(`- Resumed from: ${file.resumed_from}`);
|
|
608
|
+
lines.push("");
|
|
609
|
+
lines.push("## Dataset");
|
|
610
|
+
lines.push("");
|
|
611
|
+
if (file.dataset?.label || file.dataset?.source) {
|
|
612
|
+
lines.push(`- Label: ${file.dataset.label ?? "(unlabeled)"}`);
|
|
613
|
+
if (file.dataset.source) lines.push(`- Source: ${file.dataset.source}`);
|
|
614
|
+
if (file.dataset.ingested_at) lines.push(`- Ingested: ${file.dataset.ingested_at}`);
|
|
615
|
+
const counts = Object.entries(file.dataset.counts ?? {}).filter(([, n]) => n > 0);
|
|
616
|
+
if (counts.length > 0) {
|
|
617
|
+
lines.push(`- Counts: ${counts.map(([k, n]) => `${n.toLocaleString()} ${k}`).join(", ")}`);
|
|
618
|
+
}
|
|
619
|
+
} else {
|
|
620
|
+
lines.push("- No data loaded.");
|
|
621
|
+
}
|
|
622
|
+
if (file.attachments && file.attachments.length > 0) {
|
|
623
|
+
for (const a of file.attachments) {
|
|
624
|
+
const detail = [a.entity_type, a.row_count != null ? `${a.row_count} rows` : null].filter(Boolean).join(", ");
|
|
625
|
+
lines.push(`- Attachment: ${a.path}${detail ? ` (${detail})` : ""}`);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
lines.push("");
|
|
629
|
+
if (file.scope) {
|
|
630
|
+
lines.push("## Scope");
|
|
631
|
+
lines.push("");
|
|
632
|
+
lines.push(`- Intent: ${file.scope.intent_summary}`);
|
|
633
|
+
lines.push(`- Lens: ${file.scope.primary_lens}`);
|
|
634
|
+
if (file.scope.audience) lines.push(`- Audience: ${file.scope.audience}`);
|
|
635
|
+
if (file.scope.time_horizon) lines.push(`- Time horizon: ${file.scope.time_horizon}`);
|
|
636
|
+
if (file.scope.segments?.length) lines.push(`- Segments: ${file.scope.segments.join(", ")}`);
|
|
637
|
+
if (file.scope.confirmed_at) lines.push(`- Confirmed: ${file.scope.confirmed_at}`);
|
|
638
|
+
lines.push("");
|
|
639
|
+
}
|
|
640
|
+
lines.push("## Analysis");
|
|
641
|
+
lines.push("");
|
|
642
|
+
if (file.analysis) {
|
|
643
|
+
lines.push(`- Primary lens: ${file.analysis.primary}`);
|
|
644
|
+
lines.push(`- Completed: ${file.analysis.completed.join(", ") || "none"}`);
|
|
645
|
+
if (file.analysis.coverage) {
|
|
646
|
+
lines.push(
|
|
647
|
+
`- Coverage: ${file.analysis.coverage.distinct_months} months \xB7 recommended cadence ${file.analysis.coverage.recommended_cadence}`
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
if (file.analysis.data_source_type) {
|
|
651
|
+
lines.push(`- Data source type: ${file.analysis.data_source_type}`);
|
|
652
|
+
}
|
|
653
|
+
if (file.analysis.headline?.length) {
|
|
654
|
+
lines.push("");
|
|
655
|
+
lines.push("### Headline metrics");
|
|
656
|
+
lines.push("");
|
|
657
|
+
for (const h of file.analysis.headline) {
|
|
658
|
+
lines.push(`- ${h.label}: ${h.formatted}`);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
} else {
|
|
662
|
+
lines.push("- No analysis recorded.");
|
|
663
|
+
}
|
|
664
|
+
const health = opts.snapshot?.aggregate;
|
|
665
|
+
if (health) {
|
|
666
|
+
lines.push("");
|
|
667
|
+
lines.push("### GTM health snapshot");
|
|
668
|
+
lines.push("");
|
|
669
|
+
lines.push(`- Overall: ${Math.round(health.overall_score)} (${health.overall_status})`);
|
|
670
|
+
lines.push(`- Gating vital sign: ${health.gating_vital_sign.replace(/_/g, " ")}`);
|
|
671
|
+
if (health.total_value_at_risk != null && health.total_value_at_risk > 0) {
|
|
672
|
+
lines.push(`- Total value at risk: ${formatCurrency(health.total_value_at_risk)}`);
|
|
673
|
+
}
|
|
674
|
+
for (const vs of health.vital_signs) {
|
|
675
|
+
const label = VITAL_SIGN_LABELS[vs.vital_sign] ?? vs.vital_sign;
|
|
676
|
+
const dollars = vs.dollar_value != null ? ` \u2014 ${formatCurrency(vs.dollar_value)}${vs.dollar_label ? ` ${vs.dollar_label}` : ""}` : "";
|
|
677
|
+
lines.push(`- ${label}: ${Math.round(vs.score)} (${vs.status})${dollars}`);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
lines.push("");
|
|
681
|
+
if (file.strategist) {
|
|
682
|
+
lines.push("## Strategist (in flight)");
|
|
683
|
+
lines.push("");
|
|
684
|
+
lines.push(`- Step: ${file.strategist.step}`);
|
|
685
|
+
if (file.strategist.objective) lines.push(`- Objective: ${file.strategist.objective}`);
|
|
686
|
+
if (file.strategist.constraintsNote) {
|
|
687
|
+
lines.push(`- Constraints: ${file.strategist.constraintsNote}`);
|
|
688
|
+
}
|
|
689
|
+
if (file.strategist.origin) lines.push(`- Origin: ${file.strategist.origin}`);
|
|
690
|
+
lines.push("");
|
|
691
|
+
}
|
|
692
|
+
lines.push("## Deliverables");
|
|
693
|
+
lines.push("");
|
|
694
|
+
if (file.deliverables && file.deliverables.length > 0) {
|
|
695
|
+
for (const d of file.deliverables) {
|
|
696
|
+
const detail = [d.path, d.note].filter(Boolean).join(" \u2014 ");
|
|
697
|
+
lines.push(`- ${d.kind} (${d.at})${detail ? `: ${detail}` : ""}`);
|
|
698
|
+
}
|
|
699
|
+
} else {
|
|
700
|
+
lines.push("- None yet.");
|
|
701
|
+
}
|
|
702
|
+
lines.push("");
|
|
703
|
+
lines.push("## Exports");
|
|
704
|
+
lines.push("");
|
|
705
|
+
try {
|
|
706
|
+
lines.push(`- Archive index: \`${archiveIndexPath()}\``);
|
|
707
|
+
lines.push(`- Archive root: \`${getExportsDir()}\``);
|
|
708
|
+
const inbox = getAiInboxDir();
|
|
709
|
+
if (inbox) {
|
|
710
|
+
lines.push(`- AI inbox: \`${inbox}\` (open \`latest-handoff.md\` or \`INDEX.md\`)`);
|
|
711
|
+
} else {
|
|
712
|
+
lines.push("- AI inbox: unset \u2014 `/inbox set <folder>` for Claude Desktop");
|
|
713
|
+
}
|
|
714
|
+
} catch {
|
|
715
|
+
lines.push("- Export catalog unavailable.");
|
|
716
|
+
}
|
|
717
|
+
lines.push("");
|
|
718
|
+
lines.push(`## Conversation (${exchanges} exchange${exchanges === 1 ? "" : "s"})`);
|
|
719
|
+
lines.push("");
|
|
720
|
+
if (file.messages.length === 0) {
|
|
721
|
+
lines.push("- No exchanges yet.");
|
|
722
|
+
} else {
|
|
723
|
+
let n = 0;
|
|
724
|
+
for (const msg of file.messages) {
|
|
725
|
+
if (msg.role === "user") {
|
|
726
|
+
n++;
|
|
727
|
+
lines.push(`${n}. \u276F ${excerpt(msg.content, AGENT_EXCERPT_CHARS)}`);
|
|
728
|
+
} else {
|
|
729
|
+
lines.push(` \u21B3 ${excerpt(msg.content, AGENT_EXCERPT_CHARS)}`);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
lines.push("");
|
|
734
|
+
lines.push("## Files");
|
|
735
|
+
lines.push("");
|
|
736
|
+
lines.push(`- Transcript (raw terminal): \`${transcriptPathForSession(id)}\``);
|
|
737
|
+
lines.push(`- Session data (JSON): \`${sessionJsonPath(id)}\``);
|
|
738
|
+
lines.push(`- Dataset (DuckDB): \`${datasetPathForSession(id)}\``);
|
|
739
|
+
lines.push("");
|
|
740
|
+
lines.push("## Pick up this session");
|
|
741
|
+
lines.push("");
|
|
742
|
+
lines.push(`Run \`ntrp\`, then \`/session ${shortId}\` \u2014 rebinds the dataset and reloads the`);
|
|
743
|
+
lines.push("conversation thread in place. Read the transcript above for the full terminal");
|
|
744
|
+
lines.push("history before continuing.");
|
|
745
|
+
lines.push("");
|
|
746
|
+
return lines.map(redactSecrets).join("\n");
|
|
747
|
+
}
|
|
748
|
+
function excerpt(content, max) {
|
|
749
|
+
const flat = content.replace(/\s+/g, " ").trim();
|
|
750
|
+
return flat.length > max ? `${flat.slice(0, max - 1)}\u2026` : flat;
|
|
751
|
+
}
|
|
752
|
+
function sessionJsonPath(id) {
|
|
753
|
+
return `${getSessionsDir()}/${id}.json`;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// src/services/exports-registry-smoke.ts
|
|
757
|
+
var failures = [];
|
|
758
|
+
function assert(cond, msg) {
|
|
759
|
+
if (!cond) failures.push(msg);
|
|
760
|
+
}
|
|
761
|
+
function section(name) {
|
|
762
|
+
console.log(` \xB7 ${name}`);
|
|
763
|
+
}
|
|
764
|
+
section("archive layout + write");
|
|
765
|
+
{
|
|
766
|
+
resetConfigCache();
|
|
767
|
+
const root = ensureExportsLayout();
|
|
768
|
+
assert(existsSync5(join5(root, "handoffs")), "handoffs/ created");
|
|
769
|
+
assert(existsSync5(join5(root, "README.md")), "archive README seeded");
|
|
770
|
+
assert(existsSync5(join5(root, "manifest.jsonl")), "manifest.jsonl seeded");
|
|
771
|
+
const out = resolveArchivePath("prompt:deck", `handoff-deck-${exportStamp()}.md`);
|
|
772
|
+
writeFileSync6(out, "# Deck handoff\n\nBuild slides from this.\n", "utf-8");
|
|
773
|
+
const event = recordExportWrite({
|
|
774
|
+
kind: "prompt:deck",
|
|
775
|
+
path: out,
|
|
776
|
+
sessionId: "sess-abcd1234",
|
|
777
|
+
title: "deck handoff prompt"
|
|
778
|
+
});
|
|
779
|
+
assert(event.kind === "prompt:deck", "kind recorded");
|
|
780
|
+
assert(existsSync5(join5(root, "latest", "handoff-deck.md")), "latest/handoff-deck.md");
|
|
781
|
+
assert(existsSync5(join5(root, "latest", "handoff.md")), "latest/handoff.md generic");
|
|
782
|
+
const index = readFileSync5(archiveIndexPath(), "utf-8");
|
|
783
|
+
assert(index.includes("prompt:deck"), "INDEX mentions kind");
|
|
784
|
+
assert(index.includes(out), "INDEX mentions path");
|
|
785
|
+
assert(readManifestEvents().some((e) => e.op === "write" && e.path === out), "manifest write event");
|
|
786
|
+
}
|
|
787
|
+
section("ai inbox sync");
|
|
788
|
+
{
|
|
789
|
+
const inbox = join5(ntrpHome(), "claude-inbox");
|
|
790
|
+
setAiInboxDir(inbox);
|
|
791
|
+
assert(getAiInboxDir() === inbox, "inbox config set");
|
|
792
|
+
assert(existsSync5(join5(inbox, "README.md")), "inbox README");
|
|
793
|
+
const out = resolveArchivePath("prompt:plan", `handoff-plan-${exportStamp()}.md`);
|
|
794
|
+
writeFileSync6(out, "# Plan handoff\n", "utf-8");
|
|
795
|
+
const event = recordExportWrite({ kind: "prompt:plan", path: out, title: "plan" });
|
|
796
|
+
assert(typeof event.inbox_path === "string", "inbox_path on write event");
|
|
797
|
+
assert(existsSync5(join5(inbox, "latest-handoff.md")), "latest-handoff.md");
|
|
798
|
+
assert(existsSync5(join5(inbox, "latest-handoff-plan.md")), "latest-handoff-plan.md");
|
|
799
|
+
assert(existsSync5(join5(inbox, "archive")), "inbox archive dir");
|
|
800
|
+
assert(readdirSync3(join5(inbox, "archive")).length >= 1, "dated copy in inbox archive");
|
|
801
|
+
const inboxIndex = readFileSync5(join5(inbox, "INDEX.md"), "utf-8");
|
|
802
|
+
assert(inboxIndex.includes("latest-handoff"), "inbox INDEX lists latest");
|
|
803
|
+
assert(inboxLatestHandoffPath() === join5(inbox, "latest-handoff.md"), "inboxLatestHandoffPath");
|
|
804
|
+
}
|
|
805
|
+
section("move trail");
|
|
806
|
+
{
|
|
807
|
+
const items = listExports({ limit: 10, kind: "prompt" });
|
|
808
|
+
assert(items.length >= 1, "listExports returns items");
|
|
809
|
+
const target = items.find((e) => e.kind === "prompt:deck") ?? items[0];
|
|
810
|
+
const dest = join5(ntrpHome(), "moved-exports");
|
|
811
|
+
mkdirSync4(dest, { recursive: true });
|
|
812
|
+
const moved = moveExport(target.id, dest);
|
|
813
|
+
assert(moved.op === "move", "move op");
|
|
814
|
+
assert(moved.path.startsWith(dest), `moved under dest (got ${moved.path})`);
|
|
815
|
+
assert((moved.previous_paths?.length ?? 0) >= 1, "previous_paths recorded");
|
|
816
|
+
assert(existsSync5(moved.path), "file exists at new path");
|
|
817
|
+
assert(!existsSync5(target.path), "old path gone");
|
|
818
|
+
const index = readFileSync5(archiveIndexPath(), "utf-8");
|
|
819
|
+
assert(index.includes("Location history") || index.includes("Recent moves"), "INDEX has history section");
|
|
820
|
+
assert(index.includes(moved.previous_paths[0]), "INDEX shows previous path");
|
|
821
|
+
}
|
|
822
|
+
section("kind dirs");
|
|
823
|
+
{
|
|
824
|
+
assert(getArchiveKindDir("notes").endsWith(`${join5("exports", "notes")}`) || getArchiveKindDir("notes").includes("/notes"), "notes kind dir");
|
|
825
|
+
assert(getArchiveKindDir("csv").includes("/csv") || getArchiveKindDir("csv").includes("\\csv"), "csv kind dir");
|
|
826
|
+
assert(getArchiveKindDir("report").includes("reports"), "reports kind dir");
|
|
827
|
+
}
|
|
828
|
+
section("path-safety NTRP_HOME");
|
|
829
|
+
{
|
|
830
|
+
const home = ntrpHome();
|
|
831
|
+
assert(isInsideNtrp(join5(home, "exports")), "exports inside NTRP_HOME");
|
|
832
|
+
assert(!isInsideNtrp("/tmp/not-ntrp-exports"), "foreign path outside");
|
|
833
|
+
const expanded = resolveUserPath("~/Documents/test-inbox");
|
|
834
|
+
assert(expanded.includes("Documents"), "tilde expands");
|
|
835
|
+
}
|
|
836
|
+
section("context brief exports blurb");
|
|
837
|
+
{
|
|
838
|
+
const file = {
|
|
839
|
+
id: "sess-exports-test",
|
|
840
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
841
|
+
messages: [],
|
|
842
|
+
stage: "delivered",
|
|
843
|
+
analysis: defaultSessionAnalysis(),
|
|
844
|
+
deliverables: [{ kind: "prompt:deck", at: (/* @__PURE__ */ new Date()).toISOString(), path: "/tmp/deck.md" }]
|
|
845
|
+
};
|
|
846
|
+
const doc = buildSessionContextDoc(file);
|
|
847
|
+
assert(doc.includes("## Exports"), "context doc has Exports section");
|
|
848
|
+
assert(doc.includes("## Deliverables"), "context doc has Deliverables");
|
|
849
|
+
assert(doc.includes("/tmp/deck.md"), "deliverable path shown");
|
|
850
|
+
assert(doc.includes("AI inbox"), "mentions AI inbox");
|
|
851
|
+
}
|
|
852
|
+
if (failures.length > 0) {
|
|
853
|
+
console.error("\nexports-registry smoke FAILED:");
|
|
854
|
+
for (const f of failures) console.error(` \u2717 ${f}`);
|
|
855
|
+
process.exit(1);
|
|
856
|
+
}
|
|
857
|
+
console.log("\nexports-registry smoke OK");
|
|
858
|
+
//# sourceMappingURL=exports-registry-smoke.js.map
|