@snapback/cli 1.1.15 → 3.0.0

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/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node --no-warnings=ExperimentalWarning
2
2
  import { isSnapbackInitialized, getWorkspaceDir, getViolations, getProtectedFiles, appendSnapbackJsonl, clearCredentials, isLoggedIn, getCredentials, createGlobalDirectory, saveCredentials, getWorkspaceVitals, saveProtectedFiles, getCurrentSession, endCurrentSession, getWorkspaceConfig, recordLearning, getLearnings, readSnapbackJson, recordViolation, writeSnapbackJson, saveCurrentSession, loadSnapbackJsonl, getGlobalConfig, getGlobalDir, saveGlobalConfig, saveWorkspaceConfig } from './chunk-DPWFZNMY.js';
3
- import { createServiceClient, connectServiceClient, createSessionViaDaemon, endSessionViaDaemon, SnapBackLocalClient, isServiceRunning, getServiceSocketPath, readServicePid, formatDuration, formatBytes, getLogPath, getServicePidPath } from './chunk-DNEADD2G.js';
3
+ import { createServiceClient, connectServiceClient, createSessionViaDaemon, endSessionViaDaemon, SnapBackLocalClient, isServiceRunning, getServiceSocketPath, readServicePid, formatDuration, formatBytes, getLogPath, getServicePidPath } from './chunk-O7HMAZ7L.js';
4
4
  import { detectOrphans } from './chunk-5SQA44V7.js';
5
5
  import './chunk-4YTE4JEW.js';
6
6
  import { isRedisAvailable, getCache, setCache } from './chunk-GQ73B37K.js';
@@ -8,7 +8,7 @@ import { withBreaker, getCircuitBreakerState } from './chunk-PL4HF4M2.js';
8
8
  import { createLogger, LogLevel, extractErrorCode } from './chunk-WS36HDEU.js';
9
9
  import { generateId } from './chunk-5EOPYJ4Y.js';
10
10
  import './chunk-CBGOC6RV.js';
11
- import { detectAIClients, getClient, getSnapbackMCPConfig, writeClientConfig, getServerKey, validateConfig, removeSnapbackConfig, patchApiKeyInClientConfig, detectWorkspaceConfig, validateClientConfig, repairClientConfig, readClientConfig } from './chunk-OI2HNNT6.js';
11
+ import { detectAIClients, getClient, getSnapbackMCPConfig, writeClientConfig, getServerKey, validateConfig, removeSnapbackConfig, patchApiKeyInClientConfig, detectWorkspaceConfig, validateClientConfig, repairClientConfig, readClientConfig } from './chunk-E6V6QKS7.js';
12
12
  import { __commonJS, __name, __require } from './chunk-7ADPL4Q3.js';
13
13
  import * as fs32 from 'fs';
14
14
  import { existsSync, promises, readFileSync, watch, writeFileSync, statSync, mkdirSync, lstatSync, appendFileSync, renameSync, unlinkSync, openSync, readdirSync, watchFile } from 'fs';
@@ -66,7 +66,7 @@ var require_package = __commonJS({
66
66
  "package.json"(exports$1, module) {
67
67
  module.exports = {
68
68
  name: "@snapback/cli",
69
- version: "1.1.15",
69
+ version: "3.0.0",
70
70
  description: "CLI tool for managing SnapBack snapshots and file protection",
71
71
  homepage: "https://snapback.dev",
72
72
  repository: {
@@ -1294,7 +1294,7 @@ var _authPromise = null;
1294
1294
  async function getAuth() {
1295
1295
  if (_auth) return _auth;
1296
1296
  if (_authPromise) return _authPromise;
1297
- _authPromise = import('./auth-HFJRXXG2.js').then((m) => {
1297
+ _authPromise = import('./auth-TDIHGKKL.js').then((m) => {
1298
1298
  _auth = m.auth;
1299
1299
  return _auth;
1300
1300
  });
@@ -2900,7 +2900,7 @@ function createWorkspacesCommand() {
2900
2900
  __name(createWorkspacesCommand, "createWorkspacesCommand");
2901
2901
  var version = "0.0.0";
2902
2902
  {
2903
- version = "1.1.15";
2903
+ version = "3.0.0";
2904
2904
  }
2905
2905
  var client = null;
2906
2906
  var connectionPromise = null;
@@ -3144,7 +3144,7 @@ function displayBox(contentOrOptions, options = {}) {
3144
3144
  }
3145
3145
  __name(displayBox, "displayBox");
3146
3146
  function displaySaveStory(riskScore, affectedFiles, snapshotId) {
3147
- return displayBox(`${chalk37.bold("\u{1F6E1}\uFE0F SnapBack just protected you!")}
3147
+ return displayBox(`${chalk37.bold("\u{1F9E2} SnapBack just protected you!")}
3148
3148
 
3149
3149
  ${chalk37.cyan("Risk Score:")} ${chalk37.red(`${riskScore.toFixed(1)}/10`)}
3150
3150
  ${chalk37.cyan("Files Protected:")} ${chalk37.green(affectedFiles.length.toString())}
@@ -3160,7 +3160,7 @@ function displaySnapshotSuccess(snapshotId, message, fileCount) {
3160
3160
  ${chalk37.cyan("ID:")} ${snapshotId.substring(0, 8)}
3161
3161
  ${chalk37.cyan("Message:")} ${message || "(none)"}
3162
3162
  ${chalk37.cyan("Files:")} ${fileCount} protected`, {
3163
- title: "\u{1F6E1}\uFE0F SnapBack Protection Active",
3163
+ title: "\u{1F9E2} SnapBack Protection Active",
3164
3164
  type: "success"
3165
3165
  });
3166
3166
  }
@@ -3656,7 +3656,7 @@ function findGitRoot(cwd) {
3656
3656
  __name(findGitRoot, "findGitRoot");
3657
3657
 
3658
3658
  // src/commands/init.ts
3659
- var cliVersion = "1.1.15" ;
3659
+ var cliVersion = "3.0.0" ;
3660
3660
  function createInitCommand() {
3661
3661
  return new Command("init").description("Bootstrap SnapBack for a repository").argument("[path]", "Workspace path (default: current directory)").option("-y, --yes", "Skip confirmation prompts").option("--non-interactive", "Run without prompts (extension/automation)").option("--json", "Output structured JSON result").option("--dry-run", "Show what would be configured").option("--force", "Re-initialize even if already set up").option("--skip-mcp", "Skip MCP configuration").option("--skip-daemon", "Skip daemon registration").option("--api-key <key>", "API key for Pro features").option("--dev", "Use local dev mode for MCP").option("--npm", "Use npm/npx mode for MCP").option("-q, --quiet", "Suppress informational output").option("-v, --verbose", "Show detailed detection reasoning").action(async (path7, options) => {
3662
3662
  const result7 = await runInit(path7, options);
@@ -4297,7 +4297,7 @@ function createStatusCommand() {
4297
4297
  const cwd = process.cwd();
4298
4298
  try {
4299
4299
  if (!await isSnapbackInitialized(cwd)) {
4300
- console.log(chalk37.yellow("SnapBack not initialized in this workspace"));
4300
+ console.log(chalk37.yellow("\u{1F9E2} SnapBack not initialized in this workspace"));
4301
4301
  console.log(chalk37.gray("Run: snap init"));
4302
4302
  process.exit(1);
4303
4303
  }
@@ -21698,11 +21698,8 @@ z.object({
21698
21698
  l: z.array(z.string()).optional().describe("Legacy: use 'learnings' instead"),
21699
21699
  // Other parameters
21700
21700
  notes: z.string().optional().describe("Completion notes"),
21701
- outcome: z.enum([
21702
- "completed",
21703
- "abandoned",
21704
- "blocked"
21705
- ]).optional().describe("Task outcome"),
21701
+ /** Free-text outcome: what was accomplished, what remains, decisions made. ok:0|1 is the primary signal. */
21702
+ outcome: z.string().min(1).max(2e3).optional().describe("Free-text task outcome (what was accomplished, what remains, decisions made)"),
21706
21703
  // Efficiency tracking (agent-reported metrics)
21707
21704
  efficiency: z.object({
21708
21705
  saved: z.string().optional().describe("Tokens saved (e.g., '~12K')"),
@@ -29296,7 +29293,7 @@ function createLearnCommand() {
29296
29293
  const cwd = process.cwd();
29297
29294
  try {
29298
29295
  if (!await isSnapbackInitialized(cwd)) {
29299
- console.log(chalk37.yellow("SnapBack not initialized in this workspace"));
29296
+ console.log(chalk37.yellow("\u{1F9E2} SnapBack not initialized in this workspace"));
29300
29297
  console.log(chalk37.gray("Run: snap init"));
29301
29298
  process.exit(1);
29302
29299
  }
@@ -29362,7 +29359,7 @@ function createLearnCommand() {
29362
29359
  const cwd = process.cwd();
29363
29360
  try {
29364
29361
  if (!await isSnapbackInitialized(cwd)) {
29365
- console.log(chalk37.yellow("SnapBack not initialized"));
29362
+ console.log(chalk37.yellow("\u{1F9E2} SnapBack not initialized"));
29366
29363
  console.log(chalk37.gray("Run: snap init"));
29367
29364
  process.exit(1);
29368
29365
  }
@@ -29871,7 +29868,7 @@ function createSessionCommand() {
29871
29868
  const cwd = process.cwd();
29872
29869
  try {
29873
29870
  if (!await isSnapbackInitialized(cwd)) {
29874
- console.log(chalk37.yellow("SnapBack not initialized in this workspace"));
29871
+ console.log(chalk37.yellow("\u{1F9E2} SnapBack not initialized in this workspace"));
29875
29872
  console.log(chalk37.gray("Run: snap init"));
29876
29873
  process.exit(1);
29877
29874
  }
@@ -29943,7 +29940,7 @@ function createSessionCommand() {
29943
29940
  const cwd = process.cwd();
29944
29941
  try {
29945
29942
  if (!await isSnapbackInitialized(cwd)) {
29946
- console.log(chalk37.yellow("SnapBack not initialized"));
29943
+ console.log(chalk37.yellow("\u{1F9E2} SnapBack not initialized"));
29947
29944
  console.log(chalk37.gray("Run: snap init"));
29948
29945
  process.exit(1);
29949
29946
  }
@@ -30007,7 +30004,7 @@ function createSessionCommand() {
30007
30004
  const cwd = process.cwd();
30008
30005
  try {
30009
30006
  if (!await isSnapbackInitialized(cwd)) {
30010
- console.log(chalk37.yellow("SnapBack not initialized"));
30007
+ console.log(chalk37.yellow("\u{1F9E2} SnapBack not initialized"));
30011
30008
  console.log(chalk37.gray("Run: snap init"));
30012
30009
  process.exit(1);
30013
30010
  }
@@ -30021,7 +30018,7 @@ function createSessionCommand() {
30021
30018
  ] : [];
30022
30019
  if (learnings.length > 0 || options.ceremony) {
30023
30020
  try {
30024
- const { createServiceClient: createServiceClient4, connectServiceClient: connectServiceClient2, isServiceRunning: isServiceRunning2 } = await import('./local-service-adapter-3JHN6G4O.js');
30021
+ const { createServiceClient: createServiceClient4, connectServiceClient: connectServiceClient2, isServiceRunning: isServiceRunning2 } = await import('./local-service-adapter-AB3UYRUK.js');
30025
30022
  if (isServiceRunning2()) {
30026
30023
  const client2 = createServiceClient4();
30027
30024
  await connectServiceClient2(client2);
@@ -30068,7 +30065,7 @@ function createSessionCommand() {
30068
30065
  const cwd = process.cwd();
30069
30066
  try {
30070
30067
  if (!await isSnapbackInitialized(cwd)) {
30071
- console.log(chalk37.yellow("SnapBack not initialized"));
30068
+ console.log(chalk37.yellow("\u{1F9E2} SnapBack not initialized"));
30072
30069
  console.log(chalk37.gray("Run: snap init"));
30073
30070
  process.exit(1);
30074
30071
  }
@@ -34670,10 +34667,10 @@ async function autoStartDaemon2() {
34670
34667
  try {
34671
34668
  if (existsSync(SERVICE_SOCKET_PATH2)) {
34672
34669
  try {
34673
- await new Promise((resolve52, reject) => {
34670
+ await new Promise((resolve62, reject) => {
34674
34671
  const sock = createConnection(SERVICE_SOCKET_PATH2, () => {
34675
34672
  sock.end();
34676
- resolve52();
34673
+ resolve62();
34677
34674
  });
34678
34675
  sock.on("error", reject);
34679
34676
  setTimeout(() => {
@@ -34745,10 +34742,10 @@ async function autoStartDaemon2() {
34745
34742
  while (Date.now() < deadline) {
34746
34743
  if (existsSync(SERVICE_SOCKET_PATH2)) {
34747
34744
  try {
34748
- await new Promise((resolve52, reject) => {
34745
+ await new Promise((resolve62, reject) => {
34749
34746
  const sock = createConnection(SERVICE_SOCKET_PATH2, () => {
34750
34747
  sock.end();
34751
- resolve52();
34748
+ resolve62();
34752
34749
  });
34753
34750
  sock.on("error", reject);
34754
34751
  setTimeout(() => {
@@ -36675,11 +36672,8 @@ var init_validation = __esm2({
36675
36672
  l: z.array(z.string()).optional().describe("Legacy: use 'learnings' instead"),
36676
36673
  // Other parameters
36677
36674
  notes: z.string().optional().describe("Completion notes"),
36678
- outcome: z.enum([
36679
- "completed",
36680
- "abandoned",
36681
- "blocked"
36682
- ]).optional().describe("Task outcome"),
36675
+ /** Free-text outcome: what was accomplished, what remains, decisions made. ok:0|1 is the primary signal. */
36676
+ outcome: z.string().min(1).max(2e3).optional().describe("Free-text task outcome (what was accomplished, what remains, decisions made)"),
36683
36677
  // Efficiency tracking (agent-reported metrics)
36684
36678
  efficiency: z.object({
36685
36679
  saved: z.string().optional().describe("Tokens saved (e.g., '~12K')"),
@@ -41880,12 +41874,6 @@ var init_begin_task = __esm2({
41880
41874
  const keywords = providedKeywords ?? extractKeywords2(task);
41881
41875
  const daemonResult = await beginTaskViaService(workspaceRoot, task, files, keywords);
41882
41876
  if (daemonResult) {
41883
- await startTaskAsync(workspaceRoot, {
41884
- description: task,
41885
- plannedFiles: files || [],
41886
- snapshotId: daemonResult.snapshot.id,
41887
- keywords
41888
- });
41889
41877
  const observations2 = (await drainPendingObservationsAsync(workspaceRoot)).map((o) => ({
41890
41878
  type: o.type,
41891
41879
  message: o.message
@@ -41970,7 +41958,9 @@ var init_begin_task = __esm2({
41970
41958
  return result2(formatCompactText(formatCompactResult(output2, gitContext2)));
41971
41959
  }
41972
41960
  const learningsCount = daemonResult.learnings?.length || 0;
41973
- const hint2 = daemonResult.snapshot.created ? `Safety snapshot created via daemon: ${daemonResult.snapshot.id}` : learningsCount > 0 ? `${learningsCount} relevant learning(s) found` : "Ready to start coding! (daemon-coordinated)";
41961
+ const daemonSnapshotCreated = daemonResult.snapshot?.created ?? (daemonResult.snapshotIds?.length ?? 0) > 0;
41962
+ const daemonSnapshotId = daemonResult.snapshot?.id ?? daemonResult.snapshotIds?.[0];
41963
+ const hint2 = daemonSnapshotCreated ? `Safety snapshot created via daemon: ${daemonSnapshotId}` : learningsCount > 0 ? `${learningsCount} relevant learning(s) found` : "Ready to start coding! (daemon-coordinated)";
41974
41964
  return result2(JSON.stringify({
41975
41965
  ...output2,
41976
41966
  message: `Task started: ${task}`,
@@ -42190,12 +42180,7 @@ var init_begin_task = __esm2({
42190
42180
  snapshotId: snapshot.id,
42191
42181
  keywords
42192
42182
  });
42193
- if (!currentTask) {
42194
- return result2(JSON.stringify({
42195
- error: "E002_TASK_START_FAILED",
42196
- message: "Failed to start task. Please try again."
42197
- }), true);
42198
- }
42183
+ const taskId = currentTask?.id ?? `local_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
42199
42184
  const state = await getSessionStateAsync(workspaceRoot);
42200
42185
  if (state) {
42201
42186
  state.riskAreasTouched = riskAssessment.riskAreas;
@@ -42208,7 +42193,7 @@ var init_begin_task = __esm2({
42208
42193
  const nextActions = generateNextActions(riskAssessment, learnings, staticAnalysis);
42209
42194
  const proactive_guidance = await generateProactiveGuidance(files || []);
42210
42195
  const output = {
42211
- taskId: currentTask.id,
42196
+ taskId,
42212
42197
  intent,
42213
42198
  intentHints,
42214
42199
  snapshot,
@@ -43815,7 +43800,7 @@ var init_error_pattern_registry = __esm2({
43815
43800
  });
43816
43801
  async function runCommand2(cmd, args, cwd, timeoutMs = 3e4) {
43817
43802
  const start = Date.now();
43818
- return new Promise((resolve52) => {
43803
+ return new Promise((resolve62) => {
43819
43804
  let stdout = "";
43820
43805
  let stderr = "";
43821
43806
  let killed = false;
@@ -43840,7 +43825,7 @@ async function runCommand2(cmd, args, cwd, timeoutMs = 3e4) {
43840
43825
  });
43841
43826
  proc.on("close", (code) => {
43842
43827
  clearTimeout(timeout);
43843
- resolve52({
43828
+ resolve62({
43844
43829
  exitCode: killed ? -1 : code ?? 0,
43845
43830
  stdout,
43846
43831
  stderr,
@@ -43849,7 +43834,7 @@ async function runCommand2(cmd, args, cwd, timeoutMs = 3e4) {
43849
43834
  });
43850
43835
  proc.on("error", (err22) => {
43851
43836
  clearTimeout(timeout);
43852
- resolve52({
43837
+ resolve62({
43853
43838
  exitCode: -1,
43854
43839
  stdout,
43855
43840
  stderr: err22.message,
@@ -44524,7 +44509,7 @@ var init_review_work = __esm2({
44524
44509
  }
44525
44510
  });
44526
44511
  function getFileDiff(file, workspaceRoot) {
44527
- return new Promise((resolve52) => {
44512
+ return new Promise((resolve62) => {
44528
44513
  try {
44529
44514
  const result7 = execSync(`git diff --no-color -- "${file}"`, {
44530
44515
  cwd: workspaceRoot,
@@ -44532,15 +44517,15 @@ function getFileDiff(file, workspaceRoot) {
44532
44517
  maxBuffer: 1024 * 1024,
44533
44518
  timeout: 5e3
44534
44519
  });
44535
- resolve52(result7.trim() || null);
44520
+ resolve62(result7.trim() || null);
44536
44521
  } catch {
44537
- resolve52(null);
44522
+ resolve62(null);
44538
44523
  }
44539
44524
  });
44540
44525
  }
44541
44526
  __name(getFileDiff, "getFileDiff");
44542
44527
  function getGitChanges(workspaceRoot) {
44543
- return new Promise((resolve52) => {
44528
+ return new Promise((resolve62) => {
44544
44529
  const changes = /* @__PURE__ */ new Map();
44545
44530
  try {
44546
44531
  const result7 = execSync("git status --porcelain", {
@@ -44564,7 +44549,7 @@ function getGitChanges(workspaceRoot) {
44564
44549
  }
44565
44550
  } catch {
44566
44551
  }
44567
- resolve52(changes);
44552
+ resolve62(changes);
44568
44553
  });
44569
44554
  }
44570
44555
  __name(getGitChanges, "getGitChanges");
@@ -46683,16 +46668,7 @@ var init_input_sanitization = __esm2({
46683
46668
  // Intent - reuse guide intent schema
46684
46669
  intent: GuideIntentSchema.optional()
46685
46670
  });
46686
- SnapEndOutcomeSchema = z.enum([
46687
- "completed",
46688
- "abandoned",
46689
- "blocked",
46690
- "partial",
46691
- "success",
46692
- "failure",
46693
- "error",
46694
- "timeout"
46695
- ]);
46671
+ SnapEndOutcomeSchema = sanitizedString(2e3, "outcome");
46696
46672
  SnapEndParamsSchema = z.object({
46697
46673
  // Success flag - boolean only
46698
46674
  ok: z.union([
@@ -46977,8 +46953,7 @@ var init_errors3 = __esm2({
46977
46953
  });
46978
46954
  function isActiveSession(session) {
46979
46955
  if (!session) return false;
46980
- return session.active === true || session.state === "active" || session.state === "started" || // Fallback: Has ID and not explicitly ended/idle
46981
- !!session.id && session.state !== "ended" && session.state !== "idle";
46956
+ return session.active === true || session.state === "active" || session.state === "started" || session.state === "running";
46982
46957
  }
46983
46958
  __name(isActiveSession, "isActiveSession");
46984
46959
  var init_session = __esm2({
@@ -47017,6 +46992,11 @@ function formatSnapbackResponse(data) {
47017
46992
  if (data.isResumed) {
47018
46993
  sections.push(`Session resumed for: ${data.task}`);
47019
46994
  sections.push("Continuing from previous work in this workspace.");
46995
+ if (data.sessionVitals) {
46996
+ const { filesModified, snapshotsCreated, learningsCaptured } = data.sessionVitals;
46997
+ sections.push(`
46998
+ \u{1F4CA} **Session state**: ${filesModified} files modified | ${snapshotsCreated} snapshots | ${learningsCaptured} learnings captured`);
46999
+ }
47020
47000
  } else {
47021
47001
  sections.push(`Session started for: ${data.task}`);
47022
47002
  }
@@ -47081,12 +47061,8 @@ function formatSnapbackResponse(data) {
47081
47061
  if (data.beginData?.context?.coChangePatternsInScope && data.beginData.context.coChangePatternsInScope.length > 0) {
47082
47062
  hasTier2 = true;
47083
47063
  tier2Sections.push("\n### Files That Change Together\n");
47084
- for (const cluster of data.beginData.context.coChangePatternsInScope) {
47085
- for (let i = 0; i < cluster.files.length; i++) {
47086
- for (let j = i + 1; j < cluster.files.length; j++) {
47087
- tier2Sections.push(`- When you modify **${cluster.files[i]}**, you usually need to update **${cluster.files[j]}** too (${Math.round(cluster.coOccurrenceRate * 100)}% confidence)`);
47088
- }
47089
- }
47064
+ for (const pair of data.beginData.context.coChangePatternsInScope) {
47065
+ tier2Sections.push(`- When you modify **${pair.source}**, you usually need to update **${pair.target}** too (${Math.round(pair.confidence * 100)}% confidence)`);
47090
47066
  }
47091
47067
  }
47092
47068
  if (data.contextData?.learnings && data.contextData.learnings.length > 0) {
@@ -47135,13 +47111,9 @@ function formatSnapbackResponse(data) {
47135
47111
  }
47136
47112
  const riskCoChanges = extractCoChangeAlerts(data.riskAssessment);
47137
47113
  const beginCoChangePairs = /* @__PURE__ */ new Set();
47138
- for (const cluster of data.beginData?.context?.coChangePatternsInScope ?? []) {
47139
- for (let i = 0; i < cluster.files.length; i++) {
47140
- for (let j = i + 1; j < cluster.files.length; j++) {
47141
- beginCoChangePairs.add(`${cluster.files[i]}:${cluster.files[j]}`);
47142
- beginCoChangePairs.add(`${cluster.files[j]}:${cluster.files[i]}`);
47143
- }
47144
- }
47114
+ for (const pair of data.beginData?.context?.coChangePatternsInScope ?? []) {
47115
+ beginCoChangePairs.add(`${pair.source}:${pair.target}`);
47116
+ beginCoChangePairs.add(`${pair.target}:${pair.source}`);
47145
47117
  }
47146
47118
  const newCoChanges = riskCoChanges.filter((c) => !beginCoChangePairs.has(`${c.file}:${c.partner}`));
47147
47119
  if (newCoChanges.length > 0) {
@@ -47166,6 +47138,7 @@ function formatSnapbackResponse(data) {
47166
47138
  }
47167
47139
  }
47168
47140
  sections.push("\nSnapBack is now monitoring this session. Files are automatically snapshotted when risk pressure exceeds 70% \u2014 no manual action needed.");
47141
+ sections.push('\n\u26A0\uFE0F **REQUIRED**: Call `snap_end({ outcome: "completed" })` when your task is complete to close the session and capture learnings.');
47169
47142
  const kbNote = learningCount > 0 ? ` [${learningCount} learnings in knowledge base]` : " [session 1 \u2014 building baseline]";
47170
47143
  sections.push(`
47171
47144
  [session: ${data.session.id ?? data.session.taskId}]${kbNote}`);
@@ -47265,12 +47238,23 @@ ${validation.errors?.join("\n") ?? "Unknown validation error"}`
47265
47238
  task: validatedData?.task ?? validatedData?.t ?? args.task ?? "",
47266
47239
  files: validatedData?.files ?? validatedData?.f
47267
47240
  };
47241
+ if (!params.task?.trim()) {
47242
+ return {
47243
+ content: [
47244
+ {
47245
+ type: "text",
47246
+ text: "Error: task parameter is required and cannot be empty."
47247
+ }
47248
+ ],
47249
+ isError: true
47250
+ };
47251
+ }
47268
47252
  const workspace = resolveWorkspace(context);
47269
47253
  const daemon = getDaemonClient2(workspace);
47270
- const health = await checkDaemonHealth(daemon, workspace);
47271
47254
  let session = null;
47272
47255
  let isResumed = false;
47273
47256
  let beginData = null;
47257
+ let sessionVitals = null;
47274
47258
  try {
47275
47259
  const currentSession = await daemon.request("session/current", {
47276
47260
  workspace
@@ -47284,13 +47268,16 @@ ${validation.errors?.join("\n") ?? "Unknown validation error"}`
47284
47268
  };
47285
47269
  isResumed = true;
47286
47270
  if (params.task && params.task !== session.task) {
47271
+ const previousTask = session.task;
47272
+ session.task = params.task;
47287
47273
  try {
47288
47274
  await daemon.request("session/update-task", {
47289
47275
  workspace,
47290
47276
  task: params.task
47291
47277
  });
47292
- session.task = params.task;
47293
- } catch {
47278
+ } catch (updateError) {
47279
+ session.task = previousTask;
47280
+ console.error("[snapback] session/update-task failed, task name reverted:", updateError);
47294
47281
  }
47295
47282
  }
47296
47283
  try {
@@ -47303,24 +47290,31 @@ ${validation.errors?.join("\n") ?? "Unknown validation error"}`
47303
47290
  const warningsResult = await daemon.request("intelligence/warnings", {
47304
47291
  workspace
47305
47292
  });
47306
- const coChangeClusters = (coChangeResult?.pairs ?? []).map((pair) => ({
47307
- files: [
47308
- pair.source,
47309
- pair.target
47310
- ],
47311
- coOccurrenceRate: pair.confidence,
47312
- sampleSize: 1
47313
- }));
47314
47293
  beginData = {
47315
47294
  id: currentSession.id,
47316
47295
  context: {
47317
47296
  fragileFilesInScope: fragileResult?.files ?? [],
47318
- coChangePatternsInScope: coChangeClusters,
47297
+ coChangePatternsInScope: coChangeResult?.pairs ?? [],
47319
47298
  concurrentSessionWarning: null
47320
47299
  },
47321
47300
  learnings: []
47322
47301
  };
47323
47302
  console.error(`[snapback] Resume intelligence: ${fragileResult?.files?.length ?? 0} fragile, ${coChangeResult?.pairs?.length ?? 0} co-changes, ${warningsResult?.warnings?.length ?? 0} warnings`);
47303
+ try {
47304
+ const vitalsResult = await daemon.request("session/vitals", {
47305
+ workspace
47306
+ });
47307
+ if (vitalsResult) {
47308
+ sessionVitals = {
47309
+ filesModified: vitalsResult.filesModified ?? 0,
47310
+ snapshotsCreated: vitalsResult.snapshotsCreated ?? 0,
47311
+ learningsCaptured: vitalsResult.learningsCaptured ?? 0
47312
+ };
47313
+ console.error(`[snapback] Resume vitals: ${sessionVitals.filesModified} files, ${sessionVitals.snapshotsCreated} snapshots, ${sessionVitals.learningsCaptured} learnings`);
47314
+ }
47315
+ } catch (vitalsError) {
47316
+ console.error("[snapback] Resume vitals fetch failed:", vitalsError);
47317
+ }
47324
47318
  } catch (resumeError) {
47325
47319
  console.error("[snapback] Resume intelligence fetch failed:", resumeError);
47326
47320
  }
@@ -47385,6 +47379,7 @@ ${validation.errors?.join("\n") ?? "Unknown validation error"}`
47385
47379
  }
47386
47380
  }
47387
47381
  if (!session) {
47382
+ const health = await checkDaemonHealth(daemon, workspace);
47388
47383
  if (!health.healthy) {
47389
47384
  return formatDaemonError(classifyDaemonError(null));
47390
47385
  }
@@ -47425,7 +47420,8 @@ ${validation.errors?.join("\n") ?? "Unknown validation error"}`
47425
47420
  riskAssessment,
47426
47421
  contextData,
47427
47422
  beginData,
47428
- task: params.task
47423
+ task: params.task,
47424
+ sessionVitals
47429
47425
  });
47430
47426
  }, "handleSnapback");
47431
47427
  __name12(formatSnapbackResponse, "formatSnapbackResponse");
@@ -47433,42 +47429,34 @@ ${validation.errors?.join("\n") ?? "Unknown validation error"}`
47433
47429
  __name12(extractFragileFiles, "extractFragileFiles");
47434
47430
  __name12(extractCoChangeAlerts, "extractCoChangeAlerts");
47435
47431
  snapbackTool = {
47436
- name: "snapback",
47437
- description: `Call this FIRST at the start of every coding task. Returns codebase-specific intelligence about the files you're about to touch \u2014 fragile files to handle carefully, co-change patterns you might miss, and learnings from previous sessions in this area. Without this context, you're coding blind.
47438
-
47439
- WHAT IT DOES:
47440
- - Opens a monitored session that tracks every file you modify
47441
- - Returns relevant learnings from previous sessions, fragile file warnings, and co-change patterns
47442
- - When risk pressure exceeds 70%, snapshots are created automatically (checked every 30s)
47443
- - If a session already exists for this workspace, resumes it with fresh intelligence
47444
- - On first session (cold start): briefing will be sparse \u2014 intelligence accumulates with each session
47432
+ name: "snap_begin",
47433
+ description: `Call this before any other tool when starting a coding task. Opens a session, returns codebase intelligence your context window does not have \u2014 fragile files, co-change patterns, past learnings \u2014 and enables automatic snapshot protection. Without this call: no session tracking, no automatic snapshots, snap_pulse and snap_end return empty results.
47445
47434
 
47446
47435
  WHEN TO CALL:
47447
- - At the start of EVERY task, before making any changes \u2014 even if you don't know which files yet
47448
- - Safe to call multiple times \u2014 resumes the existing session, does not create duplicates
47436
+ - At the start of every task, before any file reads or changes \u2014 even if you don't know which files yet
47437
+ - Safe to call multiple times \u2014 resumes the existing session without creating duplicates
47449
47438
 
47450
47439
  WHEN NOT TO CALL:
47451
- - Mid-task to check status (use snap_pulse instead)
47452
- - To end a session (use snapback_end instead)
47453
- - If you only need to record a discovery (use snapback_learn instead)
47440
+ - Mid-task health check \u2192 snap_pulse
47441
+ - Record a discovery \u2192 snap_learn
47442
+ - Close the session \u2192 snap_end
47454
47443
 
47455
- WORKFLOW POSITION: 1 of 4
47456
- snapback \u2192 [your work] \u2192 snap_pulse (optional, mid-task) \u2192 snapback_end
47457
- snapback_learn can be called anytime during the session.
47444
+ WORKFLOW: snap_begin \u2192 [work] \u2192 snap_learn (anytime) \u2192 snap_pulse (optional) \u2192 snap_end
47458
47445
 
47459
47446
  WHAT YOU GET BACK (natural language text, not structured JSON):
47460
47447
  - Session ID and status (new or resumed)
47461
47448
  - Relevant learnings from previous sessions in this codebase
47462
47449
  - Fragile files in scope (files with high rollback or violation history)
47463
47450
  - Co-change patterns (files that historically change together)
47464
- - Compressed codebase context in wire format (\u{1F9E2}|CTX|...)
47451
+ - Risk assessment for the files you intend to modify
47465
47452
  - Session health vitals (pulse, pressure, trajectory)
47466
47453
 
47467
- Call snapback_end when you finish the task to close the session
47468
- and receive a summary of what was accomplished.
47454
+ AUTOMATIC PROTECTION: Snapshots fire when risk pressure exceeds 70%. No manual action needed.
47455
+
47456
+ COLD START: First session returns sparse output. Intelligence accumulates with each session \u2014 call it anyway.
47469
47457
 
47470
47458
  EXAMPLE:
47471
- snapback({ task: "refactor auth module to use OAuth", files: ["src/auth.ts"] })`,
47459
+ snapback({ task: "Refactor auth middleware to support OAuth2 PKCE", files: ["src/auth/middleware.ts"] })`,
47472
47460
  inputSchema: {
47473
47461
  type: "object",
47474
47462
  properties: {
@@ -47500,7 +47488,7 @@ Example: ["src/auth/middleware.ts", "src/auth/oauth.ts"]`
47500
47488
  t: {
47501
47489
  type: "string",
47502
47490
  title: "Task Description (shorthand)",
47503
- description: "Alias for 'task' parameter. Use full 'task' for clarity."
47491
+ description: "Shorthand for 'task'."
47504
47492
  },
47505
47493
  f: {
47506
47494
  type: "array",
@@ -47508,7 +47496,7 @@ Example: ["src/auth/middleware.ts", "src/auth/oauth.ts"]`
47508
47496
  type: "string"
47509
47497
  },
47510
47498
  title: "Target Files (shorthand)",
47511
- description: "Alias for 'files' parameter. Use full 'files' for clarity."
47499
+ description: "Shorthand for 'files'."
47512
47500
  }
47513
47501
  },
47514
47502
  required: [],
@@ -47527,7 +47515,7 @@ Example: ["src/auth/middleware.ts", "src/auth/oauth.ts"]`
47527
47515
  },
47528
47516
  annotations: {
47529
47517
  title: "\u{1F6E1}\uFE0F SnapBack",
47530
- readOnlyHint: true,
47518
+ readOnlyHint: false,
47531
47519
  destructiveHint: false,
47532
47520
  idempotentHint: true,
47533
47521
  openWorldHint: true
@@ -47536,6 +47524,25 @@ Example: ["src/auth/middleware.ts", "src/auth/oauth.ts"]`
47536
47524
  };
47537
47525
  }
47538
47526
  });
47527
+ function formatDuration22(ms) {
47528
+ const seconds = Math.floor(ms / 1e3);
47529
+ const minutes = Math.floor(seconds / 60);
47530
+ const hours = Math.floor(minutes / 60);
47531
+ if (hours > 0) {
47532
+ const remainingMinutes = minutes % 60;
47533
+ return `${hours}h ${remainingMinutes}m`;
47534
+ }
47535
+ if (minutes > 0) {
47536
+ return `${minutes}m`;
47537
+ }
47538
+ return `${seconds}s`;
47539
+ }
47540
+ __name(formatDuration22, "formatDuration2");
47541
+ var init_format_duration = __esm2({
47542
+ "src/utils/format-duration.ts"() {
47543
+ __name12(formatDuration22, "formatDuration");
47544
+ }
47545
+ });
47539
47546
  function formatEndResponse(data) {
47540
47547
  const sections = [];
47541
47548
  const task = data.session.task ?? data.session.metadata?.task ?? "unnamed task";
@@ -47626,10 +47633,6 @@ _Estimated time saved: ~${timeSaved} minutes_`);
47626
47633
  tier2Sections.push(`- ${typeEmoji} **${insight.title}**: ${insight.body}`);
47627
47634
  }
47628
47635
  }
47629
- if (data.aiSynthesis.nextAction) {
47630
- tier2Sections.push(`
47631
- **Recommended next action**: ${data.aiSynthesis.nextAction}`);
47632
- }
47633
47636
  if (data.aiSynthesis.model) {
47634
47637
  tier2Sections.push(`
47635
47638
  _Analysis powered by ${data.aiSynthesis.model}${data.aiSynthesis.cached ? " (cached)" : ""}_`);
@@ -47689,20 +47692,6 @@ ${metrics.snapshotsCreated} snapshots were created during this session. If any c
47689
47692
  };
47690
47693
  }
47691
47694
  __name(formatEndResponse, "formatEndResponse");
47692
- function formatDuration22(ms) {
47693
- const seconds = Math.floor(ms / 1e3);
47694
- const minutes = Math.floor(seconds / 60);
47695
- const hours = Math.floor(minutes / 60);
47696
- if (hours > 0) {
47697
- const remainingMinutes = minutes % 60;
47698
- return `${hours}h ${remainingMinutes}m`;
47699
- }
47700
- if (minutes > 0) {
47701
- return `${minutes}m`;
47702
- }
47703
- return `${seconds}s`;
47704
- }
47705
- __name(formatDuration22, "formatDuration2");
47706
47695
  function buildSessionDiagnostics(session, workspace) {
47707
47696
  const lines = [
47708
47697
  "**Diagnostic Info:**"
@@ -47728,10 +47717,10 @@ function buildSessionDiagnostics(session, workspace) {
47728
47717
  return lines.join("\n");
47729
47718
  }
47730
47719
  __name(buildSessionDiagnostics, "buildSessionDiagnostics");
47731
- function gatherMetrics(endResult, ceremonyData, session) {
47732
- const filesModified = ceremonyData?.filesModified ?? session?.touchedFiles?.length ?? endResult?.filesModified ?? 0;
47733
- const snapshotsCreated = ceremonyData?.snapshotsCreated ?? ceremonyData?.checkpointsCreated ?? session?.snapshotIds?.length ?? endResult?.snapshotsCreated ?? 0;
47734
- const learningsCaptured = ceremonyData?.learningsCaptured ?? session?.learningIds?.length ?? endResult?.learningsCaptured ?? 0;
47720
+ function gatherMetrics(endResult, ceremonyData, _session) {
47721
+ const filesModified = ceremonyData?.filesModified ?? endResult?.filesModified ?? 0;
47722
+ const snapshotsCreated = ceremonyData?.checkpointsCreated ?? ceremonyData?.snapshotsCreated ?? endResult?.snapshotsCreated ?? 0;
47723
+ const learningsCaptured = ceremonyData?.learningsCaptured ?? endResult?.learningsCaptured ?? 0;
47735
47724
  const duration = ceremonyData?.duration ?? ceremonyData?.ceremony?.summary?.duration ?? endResult?.duration ?? null;
47736
47725
  return {
47737
47726
  filesModified,
@@ -47754,10 +47743,12 @@ function describeCoherence(score) {
47754
47743
  };
47755
47744
  return descriptions[score] ?? `Unknown coherence level: ${score}`;
47756
47745
  }
47757
- if (score >= 0.8) return "Highly focused session \u2014 changes were well-scoped and consistent";
47758
- if (score >= 0.6) return "Good coherence \u2014 mostly focused with minor tangents";
47759
- if (score >= 0.4) return "Mixed session \u2014 several distinct concerns addressed";
47760
- if (score >= 0.2) return "Scattered session \u2014 many unrelated changes";
47746
+ let normalizedScore = score;
47747
+ if (typeof normalizedScore === "number" && normalizedScore > 1) normalizedScore = normalizedScore / 100;
47748
+ if (normalizedScore >= 0.8) return "Highly focused session \u2014 changes were well-scoped and consistent";
47749
+ if (normalizedScore >= 0.6) return "Good coherence \u2014 mostly focused with minor tangents";
47750
+ if (normalizedScore >= 0.4) return "Mixed session \u2014 several distinct concerns addressed";
47751
+ if (normalizedScore >= 0.2) return "Scattered session \u2014 many unrelated changes";
47761
47752
  return "Low coherence \u2014 this session touched many unrelated areas";
47762
47753
  }
47763
47754
  __name(describeCoherence, "describeCoherence");
@@ -47914,9 +47905,16 @@ var AI_SYNTHESIS_CONFIG;
47914
47905
  var snapbackEndTool;
47915
47906
  var init_snapback_end = __esm2({
47916
47907
  "src/tools/v2/snapback-end.ts"() {
47908
+ init_input_sanitization();
47909
+ init_format_duration();
47917
47910
  init_utils3();
47918
47911
  handleSnapbackEnd = /* @__PURE__ */ __name12(async (args, context) => {
47919
- const params = args;
47912
+ const rawArgs = args;
47913
+ const rawOutcome = typeof rawArgs.outcome === "string" ? rawArgs.outcome : "";
47914
+ const sanitizedOutcome = normalizeContent(rawOutcome).slice(0, MAX_LENGTHS.description);
47915
+ const params = {
47916
+ outcome: sanitizedOutcome
47917
+ };
47920
47918
  const workspace = resolveWorkspace(context);
47921
47919
  const daemon = getDaemonClient2(workspace);
47922
47920
  let currentSession = null;
@@ -47967,7 +47965,14 @@ var init_snapback_end = __esm2({
47967
47965
  const payload = buildAIMetadataPayload(ceremonyData, currentSession);
47968
47966
  const daemonBaseUrl = process.env.SNAPBACK_API_URL ?? "http://127.0.0.1:4200";
47969
47967
  const apiKey = process.env.SNAPBACK_API_KEY;
47970
- aiSynthesis = await fetchAISynthesis(payload, currentSession.id ?? currentSession.taskId ?? "unknown", daemonBaseUrl, apiKey);
47968
+ const urlHost = new URL(daemonBaseUrl.startsWith("http") ? daemonBaseUrl : `http://${daemonBaseUrl}`).hostname;
47969
+ const isLoopback = urlHost === "127.0.0.1" || urlHost === "localhost" || urlHost === "::1";
47970
+ if (!daemonBaseUrl.startsWith("https://") && !isLoopback) {
47971
+ console.error("[snapback-end] Skipping AI synthesis: Bearer token must not be sent over plain HTTP to remote host");
47972
+ aiSynthesis = null;
47973
+ } else {
47974
+ aiSynthesis = await fetchAISynthesis(payload, currentSession.id ?? currentSession.taskId ?? "unknown", daemonBaseUrl, apiKey);
47975
+ }
47971
47976
  } catch {
47972
47977
  aiSynthesis = null;
47973
47978
  }
@@ -47980,7 +47985,6 @@ var init_snapback_end = __esm2({
47980
47985
  });
47981
47986
  }, "handleSnapbackEnd");
47982
47987
  __name12(formatEndResponse, "formatEndResponse");
47983
- __name12(formatDuration22, "formatDuration");
47984
47988
  __name12(buildSessionDiagnostics, "buildSessionDiagnostics");
47985
47989
  __name12(gatherMetrics, "gatherMetrics");
47986
47990
  __name12(describeCoherence, "describeCoherence");
@@ -47998,8 +48002,8 @@ var init_snapback_end = __esm2({
47998
48002
  __name12(inferScopeType, "inferScopeType");
47999
48003
  __name12(fetchAISynthesis, "fetchAISynthesis");
48000
48004
  snapbackEndTool = {
48001
- name: "snapback_end",
48002
- description: `Close the SnapBack session opened by snapback and receive a summary
48005
+ name: "snap_end",
48006
+ description: `Close the SnapBack session opened by snap_begin and receive a summary
48003
48007
  of what was accomplished.
48004
48008
 
48005
48009
  WHAT IT DOES:
@@ -48012,20 +48016,22 @@ WHAT IT DOES:
48012
48016
  - Persisted session data is cleaned up
48013
48017
 
48014
48018
  WHEN TO CALL:
48015
- - At the end of every task, after your work is complete
48019
+ - At the end of every task, after your work is complete and deliverable is ready
48020
+ - Right after generating a final artifact/code \u2014 before your closing message to the user
48016
48021
  - When abandoning a task partway through (use outcome to explain why)
48017
48022
  - Before starting a completely different task in the same workspace
48018
48023
 
48019
48024
  WHEN NOT TO CALL:
48020
48025
  - Mid-task to check progress (use snap_pulse instead)
48026
+ - Still iterating or waiting for user input
48021
48027
  - If no session was started (the tool handles this gracefully,
48022
- but calling snapback first gives you a meaningful ceremony)
48028
+ but calling snap_begin first gives you a meaningful ceremony)
48023
48029
 
48024
48030
  WORKFLOW POSITION: 4 of 4 (final)
48025
- snapback \u2192 [your work] \u2192 snap_pulse (optional) \u2192 snapback_end
48031
+ snap_begin \u2192 [your work] \u2192 snap_pulse (optional) \u2192 snap_end
48026
48032
 
48027
- This closes the session opened by snapback. Every snapback call
48028
- should eventually have a matching snapback_end.
48033
+ This closes the session opened by snap_begin. Every snap_begin call
48034
+ should eventually have a matching snap_end.
48029
48035
 
48030
48036
  WHAT YOU GET BACK (natural language text):
48031
48037
  - Task name and outcome
@@ -48041,24 +48047,23 @@ Always safe to call \u2014 gracefully handles missing sessions
48041
48047
  with a clear message instead of an error.
48042
48048
 
48043
48049
  EXAMPLE:
48044
- snapback_end({ outcome: "completed OAuth integration, all tests passing" })`,
48050
+ snapback_end({ outcome: "Refactored auth middleware to support OAuth2 PKCE. Token refresh is now in src/auth/refresh.ts. Left incomplete: revoke-token endpoint still uses the old pattern. Decided to keep session cookies HTTP-only after security review." })`,
48045
48051
  inputSchema: {
48046
48052
  type: "object",
48047
48053
  properties: {
48048
48054
  outcome: {
48049
48055
  type: "string",
48050
48056
  title: "Task Outcome",
48051
- description: `What happened during this session.
48057
+ description: `What you accomplished, what remains incomplete, and any decisions made. This becomes the opening context for your next session \u2014 a vague outcome produces a sparse briefing next time. One paragraph minimum.
48052
48058
 
48053
- GOOD: "Completed OAuth2 PKCE implementation, all tests passing"
48059
+ GOOD: "Refactored auth middleware to support OAuth2 PKCE. Token refresh is now in src/auth/refresh.ts. Left incomplete: revoke-token endpoint still uses the old pattern. Decided to keep session cookies HTTP-only after security review."
48054
48060
  GOOD: "Partially done \u2014 auth flow works but token refresh not implemented yet"
48055
48061
  GOOD: "Abandoned \u2014 realized this approach won't work, need different strategy"
48056
48062
 
48057
48063
  BAD: "done"
48064
+ BAD: "completed the task"
48058
48065
 
48059
- Stored with the session and visible in the closing ceremony.
48060
- Be honest \u2014 "partially done, tests still failing" is more useful
48061
- than "done" for future session context.`
48066
+ Be honest \u2014 future sessions start from this context.`
48062
48067
  }
48063
48068
  },
48064
48069
  required: [
@@ -48078,15 +48083,16 @@ than "done" for future session context.`
48078
48083
  });
48079
48084
  function formatLearnResponse(data) {
48080
48085
  const sections = [];
48081
- sections.push(`Learning captured (${data.severity}): "${data.insight}"`);
48086
+ const typeLabel = data.type !== "pattern" ? ` [${data.type}]` : "";
48087
+ sections.push(`Rule encoded${typeLabel} (${data.severity}): when ${data.trigger} \u2192 ${data.action}`);
48082
48088
  if (data.implicitSession) {
48083
- sections.push("\nNote: No active session was found, so one was created automatically. For full codebase intelligence, call `snapback` before starting work.");
48089
+ sections.push("\nNote: No active session was found, so one was created automatically. For full codebase intelligence, call `snap_begin` before starting work.");
48084
48090
  }
48085
48091
  if (data.relatedLearnings.length > 0) {
48086
48092
  sections.push(`
48087
48093
  ## Related Knowledge (${data.relatedLearnings.length} existing)
48088
48094
  `);
48089
- sections.push("Previous discoveries about this area:");
48095
+ sections.push("Previous rules about this area:");
48090
48096
  for (const learning of data.relatedLearnings) {
48091
48097
  sections.push(`- ${learning}`);
48092
48098
  }
@@ -48187,12 +48193,29 @@ var handleSnapbackLearn;
48187
48193
  var snapbackLearnTool;
48188
48194
  var init_snapback_learn = __esm2({
48189
48195
  "src/tools/v2/snapback-learn.ts"() {
48196
+ init_input_sanitization();
48190
48197
  init_utils3();
48191
48198
  handleSnapbackLearn = /* @__PURE__ */ __name12(async (args, context) => {
48192
- const params = args;
48199
+ const rawArgs = args;
48200
+ const sanitizedArgs = {
48201
+ ...rawArgs,
48202
+ ...typeof rawArgs.trigger === "string" && {
48203
+ trigger: normalizeContent(rawArgs.trigger).slice(0, MAX_LENGTHS.trigger)
48204
+ },
48205
+ ...typeof rawArgs.action === "string" && {
48206
+ action: normalizeContent(rawArgs.action).slice(0, MAX_LENGTHS.action)
48207
+ },
48208
+ ...typeof rawArgs.insight === "string" && {
48209
+ insight: normalizeContent(rawArgs.insight).slice(0, MAX_LENGTHS.trigger)
48210
+ }
48211
+ };
48212
+ const params = sanitizedArgs;
48193
48213
  const workspace = resolveWorkspace(context);
48194
48214
  const daemon = getDaemonClient2(workspace);
48195
48215
  const severity = params.severity ?? "info";
48216
+ const trigger = params.trigger ?? params.insight ?? "";
48217
+ const action = params.action ?? deriveAction(params.insight ?? "", severity);
48218
+ const type = params.type ?? mapSeverityToType(severity);
48196
48219
  let implicitSession = false;
48197
48220
  let currentSessionId;
48198
48221
  try {
@@ -48200,11 +48223,12 @@ var init_snapback_learn = __esm2({
48200
48223
  workspace
48201
48224
  });
48202
48225
  if (!isActiveSession(currentSession)) {
48203
- await daemon.request("session/begin", {
48226
+ const beginResult = await daemon.request("session/begin", {
48204
48227
  workspace,
48205
48228
  task: "Implicit session (learning captured before explicit session start)",
48206
48229
  files: params.files ?? []
48207
48230
  });
48231
+ currentSessionId = beginResult?.sessionId ?? beginResult?.taskId;
48208
48232
  implicitSession = true;
48209
48233
  } else {
48210
48234
  currentSessionId = currentSession.id ?? currentSession.taskId;
@@ -48215,13 +48239,13 @@ var init_snapback_learn = __esm2({
48215
48239
  }
48216
48240
  }
48217
48241
  const learningPayload = {
48218
- type: mapSeverityToType(severity),
48219
- trigger: params.files ?? [],
48220
- solution: params.insight,
48221
- action: deriveAction(params.insight, severity),
48242
+ type,
48243
+ trigger,
48244
+ solution: trigger,
48245
+ action,
48222
48246
  source: "llm-observation",
48223
48247
  priority: mapSeverityToPriority(severity),
48224
- keywords: extractKeywords22(params.insight),
48248
+ keywords: extractKeywords22(`${trigger} ${action}`),
48225
48249
  related: params.files
48226
48250
  };
48227
48251
  let addResult = null;
@@ -48243,7 +48267,7 @@ var init_snapback_learn = __esm2({
48243
48267
  try {
48244
48268
  const searchResult = await daemon.request("learning/search", {
48245
48269
  workspace,
48246
- query: params.insight,
48270
+ query: trigger,
48247
48271
  files: params.files,
48248
48272
  limit: 5
48249
48273
  });
@@ -48257,7 +48281,9 @@ var init_snapback_learn = __esm2({
48257
48281
  implicitSession,
48258
48282
  relatedLearnings,
48259
48283
  severity,
48260
- insight: params.insight
48284
+ type,
48285
+ trigger,
48286
+ action
48261
48287
  });
48262
48288
  }, "handleSnapbackLearn");
48263
48289
  __name12(formatLearnResponse, "formatLearnResponse");
@@ -48266,60 +48292,62 @@ var init_snapback_learn = __esm2({
48266
48292
  __name12(deriveAction, "deriveAction");
48267
48293
  __name12(extractKeywords22, "extractKeywords");
48268
48294
  snapbackLearnTool = {
48269
- name: "snapback_learn",
48270
- description: `Record a codebase discovery that should be remembered across sessions.
48295
+ name: "snap_learn",
48296
+ description: `Encode a reusable IF/THEN rule into the workspace knowledge base. trigger: the condition under which this rule applies ('when editing auth.ts without running tests'). action: what to do when that condition is true ('always snapshot before touching token refresh logic'). Not for observations or file annotations \u2014 call snap_pulse with a query param for retrieval.
48271
48297
 
48272
- WHAT IT DOES:
48273
- - Stores a typed learning (pattern, pitfall, preference, or constraint)
48274
- in the workspace knowledge base
48275
- - Associates the learning with the current session for ceremony attribution
48276
- - Returns similar learnings already captured, so you can see what's
48277
- already known and avoid duplicates
48278
-
48279
- WHEN TO CALL:
48280
- - When you discover a pattern: "These two files must always change together"
48281
- - When you hit a pitfall: "Editing this config without rebuilding causes silent failures"
48282
- - When you learn a constraint: "This module cannot import from the platform package"
48283
- - When you identify a preference: "Team prefers explicit error handling over try-catch"
48298
+ CALL IMMEDIATELY WHEN:
48299
+ - User corrects an assumption you made ("Actually, we use X not Y")
48300
+ - You discover a codebase pattern that contradicts your expectations
48301
+ - An error reveals an unknown constraint or fragile dependency
48302
+ - You find undocumented behavior that future sessions must know about
48303
+ - You identify a team preference or hard rule
48284
48304
 
48285
48305
  WHEN NOT TO CALL:
48286
- - To start a session (use snapback instead)
48287
- - To check session health (use snap_pulse instead)
48288
- - To end a session (use snapback_end instead)
48289
- - For trivial observations that won't help future sessions
48306
+ - To retrieve learnings \u2192 snap_pulse with query param (read-only)
48307
+ - For trivial observations that won't matter in future sessions
48308
+ - To confirm expected/standard behavior
48290
48309
 
48291
- WORKFLOW POSITION: Can be called anytime during a session.
48292
- snapback \u2192 [work] \u2192 snapback_learn \u2192 [work] \u2192 snapback_learn \u2192 snapback_end
48310
+ WORKFLOW POSITION: Anytime during a session.
48311
+ snap_begin \u2192 [work] \u2192 snap_learn \u2192 [work] \u2192 snap_learn \u2192 snap_end
48293
48312
 
48294
48313
  Works without an active session, but for full intelligence context,
48295
- call snapback first. Without a session, learnings are stored but
48314
+ call snap_begin first. Without a session, learnings are stored but
48296
48315
  not attributed to any task in the closing ceremony.
48297
48316
 
48298
48317
  WHAT YOU GET BACK (natural language text):
48299
- - Confirmation that the learning was stored
48300
- - The severity level assigned
48301
- - Similar learnings already in the knowledge base (to avoid duplicates
48302
- and reinforce existing knowledge)
48318
+ - Confirmation the rule was stored
48319
+ - Related learnings already in the knowledge base (deduplication loop)
48303
48320
 
48304
48321
  EXAMPLE:
48305
- snapback_learn({ insight: "auth.ts silently swallows token refresh errors", files: ["src/auth.ts"], severity: "warning" })`,
48322
+ snap_learn({
48323
+ trigger: "when modifying token refresh logic in auth.ts",
48324
+ action: "snapshot first \u2014 this code has 3 rollbacks across 2 sessions",
48325
+ type: "pitfall",
48326
+ severity: "warning",
48327
+ files: ["src/auth/auth.ts"]
48328
+ })`,
48306
48329
  inputSchema: {
48307
48330
  type: "object",
48308
48331
  properties: {
48309
- insight: {
48332
+ trigger: {
48310
48333
  type: "string",
48311
- title: "Discovery",
48312
- description: `The discovery to record. Be specific and actionable.
48313
-
48314
- GOOD: "The session-registry.ts appendTouchedFile method silently
48315
- deduplicates \u2014 calling it twice with the same path is safe
48316
- and expected during burst file changes."
48334
+ title: "Condition",
48335
+ description: `The condition. Starts with 'when' \u2014 when X is true, when Y happens, when you're in Z state.
48317
48336
 
48318
- BAD: "session registry is interesting"
48319
- BAD: "found a bug"
48337
+ GOOD: "when editing auth.ts without running tests first"
48338
+ GOOD: "when the daemon restarts during an active session"
48339
+ BAD: "auth.ts is fragile"
48340
+ BAD: "I noticed something"`
48341
+ },
48342
+ action: {
48343
+ type: "string",
48344
+ title: "Action",
48345
+ description: `What to do when the trigger fires. Starts with a verb.
48320
48346
 
48321
- Include the WHY, not just the WHAT. Future sessions surface these
48322
- learnings by keyword relevance \u2014 specific terms help retrieval.`
48347
+ GOOD: "always snapshot before touching token refresh logic"
48348
+ GOOD: "check that session.json was cleaned up before starting a new session"
48349
+ BAD: "be careful"
48350
+ BAD: "token refresh is tricky"`
48323
48351
  },
48324
48352
  files: {
48325
48353
  type: "array",
@@ -48327,10 +48355,25 @@ learnings by keyword relevance \u2014 specific terms help retrieval.`
48327
48355
  type: "string"
48328
48356
  },
48329
48357
  title: "Related Files",
48330
- description: `Files this learning applies to. Improves future retrieval when
48331
- those files are being modified.
48358
+ description: `Files this rule is scoped to. Narrows retrieval relevance.
48332
48359
 
48333
48360
  Example: ["src/services/session-registry.ts"]`
48361
+ },
48362
+ type: {
48363
+ type: "string",
48364
+ enum: [
48365
+ "pattern",
48366
+ "pitfall",
48367
+ "constraint",
48368
+ "preference"
48369
+ ],
48370
+ title: "Rule Type",
48371
+ description: `Controls how this learning surfaces in future briefings.
48372
+
48373
+ "pattern" \u2014 A repeating structure worth following
48374
+ "pitfall" \u2014 A known failure mode to avoid
48375
+ "constraint" \u2014 A hard rule that must not be violated
48376
+ "preference" \u2014 A soft team preference`
48334
48377
  },
48335
48378
  severity: {
48336
48379
  type: "string",
@@ -48340,15 +48383,21 @@ Example: ["src/services/session-registry.ts"]`
48340
48383
  "critical"
48341
48384
  ],
48342
48385
  title: "Severity Level",
48343
- description: `How important is this learning?
48386
+ description: `How urgently this rule surfaces.
48344
48387
 
48345
- "info" \u2014 Useful context, no action required (default)
48346
- "warning" \u2014 Should be surfaced when these files are modified
48347
- "critical" \u2014 Must be surfaced every time \u2014 ignoring this causes failures`
48388
+ "info" \u2014 Shown when relevant files are in scope (default)
48389
+ "warning" \u2014 Shown whenever related files are modified
48390
+ "critical" \u2014 Shown in snap_begin even on cold start, regardless of file scope`
48391
+ },
48392
+ insight: {
48393
+ type: "string",
48394
+ title: "Insight (deprecated)",
48395
+ description: "Deprecated. Use trigger + action instead."
48348
48396
  }
48349
48397
  },
48350
48398
  required: [
48351
- "insight"
48399
+ "trigger",
48400
+ "action"
48352
48401
  ]
48353
48402
  },
48354
48403
  annotations: {
@@ -48516,10 +48565,9 @@ function formatHealthyResponse(session, vitals) {
48516
48565
  } else if (session.filesTouched) {
48517
48566
  parts.push(`${session.filesTouched} files touched`);
48518
48567
  }
48519
- if (vitals?.snapshotsCreated) {
48520
- parts.push(`${vitals.snapshotsCreated} snapshots`);
48521
- } else if (session.snapshotsCreated) {
48522
- parts.push(`${session.snapshotsCreated} snapshots`);
48568
+ const healthySnapshots = vitals?.snapshotsCreated ?? session.snapshotsCreated;
48569
+ if (healthySnapshots) {
48570
+ parts.push(`${healthySnapshots} snapshots`);
48523
48571
  }
48524
48572
  if (vitals) {
48525
48573
  parts.push(`Pulse: ${vitals.pulse}`);
@@ -48623,12 +48671,12 @@ function formatGuardHealth(health, touchedFiles) {
48623
48671
  lines.push(` Issues: ${failCount} failing, ${warnCount} warning${warnCount !== 1 ? "s" : ""}`);
48624
48672
  }
48625
48673
  const relevantIssues = [];
48626
- const touchedSet = touchedFiles ? new Set(touchedFiles.map((f) => f.split("/").pop() ?? f)) : /* @__PURE__ */ new Set();
48674
+ const touchedSet = touchedFiles ? new Set(touchedFiles.map((f) => f.replace(/\\/g, "/").replace(/\/+$/, ""))) : /* @__PURE__ */ new Set();
48627
48675
  for (const guard2 of health.guards) {
48628
48676
  if (guard2.status === "pass") continue;
48629
48677
  for (const file of guard2.files) {
48630
- const fileName = file.path.split("/").pop() ?? file.path;
48631
- if (touchedSet.size === 0 || touchedSet.has(fileName) || relevantIssues.length < 3) {
48678
+ const normalizedFilePath = file.path.replace(/\\/g, "/").replace(/\/+$/, "");
48679
+ if (touchedSet.size === 0 || touchedSet.has(normalizedFilePath) || relevantIssues.length < 3) {
48632
48680
  relevantIssues.push({
48633
48681
  guard: guard2.guard,
48634
48682
  status: guard2.status,
@@ -48761,6 +48809,7 @@ var handleSnapbackPulse;
48761
48809
  var snapbackPulseTool;
48762
48810
  var init_snapback_pulse = __esm2({
48763
48811
  "src/tools/v2/snapback-pulse.ts"() {
48812
+ init_input_sanitization();
48764
48813
  init_utils3();
48765
48814
  RISK_THRESHOLD = 0.3;
48766
48815
  CO_CHANGE_CONFIDENCE_THRESHOLD = 0.5;
@@ -48773,7 +48822,9 @@ var init_snapback_pulse = __esm2({
48773
48822
  const params = args ?? {};
48774
48823
  const workspace = resolveWorkspace(context);
48775
48824
  const daemon = getDaemonClient2(workspace);
48776
- const effectiveQuery = params.task ?? params.query;
48825
+ const rawTask = params.task ? normalizeContent(params.task).slice(0, MAX_LENGTHS.task) : void 0;
48826
+ const rawQuery = params.query ? normalizeContent(params.query).slice(0, MAX_LENGTHS.task) : void 0;
48827
+ const effectiveQuery = rawTask ?? rawQuery;
48777
48828
  const [sessionResult, vitalsResult, warningsResult, fragileResult, coChangeResult, guardHealthResult, topologyStatusResult, topologyQueryResult, learningSearchResult] = await Promise.allSettled([
48778
48829
  daemon.request("session/current", {
48779
48830
  workspace
@@ -48876,8 +48927,7 @@ var init_snapback_pulse = __esm2({
48876
48927
  __name12(countMissingCoChanges, "countMissingCoChanges");
48877
48928
  snapbackPulseTool = {
48878
48929
  name: "snap_pulse",
48879
- description: `Check the health and risk state of your current SnapBack session.
48880
- Read-only \u2014 does not modify any state or create snapshots.
48930
+ description: `Read-only session health check. Does not write, learn, or create snapshots.
48881
48931
 
48882
48932
  WHAT IT DOES:
48883
48933
  - Returns current session vitals: pulse (activity level), pressure
@@ -48891,18 +48941,21 @@ WHAT IT DOES:
48891
48941
 
48892
48942
  WHEN TO CALL:
48893
48943
  - After modifying 3+ files, to check if pressure is building
48944
+ - After 3+ tool calls on the same task without a checkpoint
48894
48945
  - Before a destructive change (delete, revert, major refactor)
48946
+ - When multiple valid implementation paths exist \u2014 before committing to one
48947
+ - Before asking the user a clarifying question (capture state first)
48895
48948
  - When switching context between different areas of the codebase
48896
48949
  - To verify the session is still active after an interruption
48897
48950
  - When starting work on a task \u2014 provide task description for oriented briefing
48898
48951
 
48899
48952
  WHEN NOT TO CALL:
48900
- - To start a session (use snapback instead)
48901
- - To end a session (use snapback_end instead)
48902
- - Immediately after snapback \u2014 the session just started, there's nothing to check yet
48953
+ - To start a session (use snap_begin instead)
48954
+ - To end a session (use snap_end instead)
48955
+ - Immediately after snap_begin \u2014 the session just started, there's nothing to check yet
48903
48956
 
48904
48957
  WORKFLOW POSITION: 3 of 4 (optional, repeatable)
48905
- snapback \u2192 [your work] \u2192 snap_pulse \u2192 [more work] \u2192 snap_pulse \u2192 snapback_end
48958
+ snap_begin \u2192 [your work] \u2192 snap_pulse \u2192 [more work] \u2192 snap_pulse \u2192 snap_end
48906
48959
 
48907
48960
  WHAT YOU GET BACK (natural language text):
48908
48961
  - Session duration and file count
@@ -48956,8 +49009,8 @@ Example: "add rate limiting to API endpoints"`
48956
49009
  query: {
48957
49010
  type: "string",
48958
49011
  title: "Query",
48959
- description: `Query the workspace knowledge base for relevant learnings.
48960
- Use this to ask what SnapBack knows about a topic before implementing it.
49012
+ description: `Retrieve relevant learnings from the knowledge base. Read-only \u2014 does not store anything. To write a new learning, use snap_learn instead.
49013
+
48961
49014
  Results are returned from BM25 search over captured learnings.
48962
49015
 
48963
49016
  Example: "What should I know before changing the auth middleware?"
@@ -48992,10 +49045,15 @@ var init_v2 = __esm2({
48992
49045
  snapbackPulseTool
48993
49046
  ];
48994
49047
  V2_HANDLERS = {
49048
+ // Legacy keys (internal routing, backward compat)
48995
49049
  snapback: handleSnapback,
48996
49050
  snapback_learn: handleSnapbackLearn,
48997
49051
  snapback_end: handleSnapbackEnd,
48998
- snap_pulse: handleSnapbackPulse
49052
+ snap_pulse: handleSnapbackPulse,
49053
+ // Canonical keys — must match SnapBackTool.name for ToolRegistry dispatch
49054
+ snap_begin: handleSnapback,
49055
+ snap_learn: handleSnapbackLearn,
49056
+ snap_end: handleSnapbackEnd
48999
49057
  };
49000
49058
  }
49001
49059
  });
@@ -50932,7 +50990,7 @@ var init_evolution_detector_service = __esm2({
50932
50990
  }
50933
50991
  });
50934
50992
  async function getGitDiffFiles2(workspacePath, scope = "staged") {
50935
- return new Promise((resolve52) => {
50993
+ return new Promise((resolve62) => {
50936
50994
  let args;
50937
50995
  switch (scope) {
50938
50996
  case "staged":
@@ -50978,14 +51036,14 @@ async function getGitDiffFiles2(workspacePath, scope = "staged") {
50978
51036
  });
50979
51037
  proc.on("close", (code) => {
50980
51038
  if (code !== 0) {
50981
- resolve52([]);
51039
+ resolve62([]);
50982
51040
  return;
50983
51041
  }
50984
51042
  const files = stdout.split("\n").map((f) => f.trim()).filter((f) => f.length > 0).filter((f) => /\.(ts|tsx|js|jsx)$/.test(f));
50985
- resolve52(files);
51043
+ resolve62(files);
50986
51044
  });
50987
51045
  proc.on("error", () => {
50988
- resolve52([]);
51046
+ resolve62([]);
50989
51047
  });
50990
51048
  });
50991
51049
  }
@@ -51019,7 +51077,7 @@ var init_file_selector = __esm2({
51019
51077
  });
51020
51078
  function runCommand22(cmd, args, cwd, timeoutMs = 3e4) {
51021
51079
  const start = Date.now();
51022
- return new Promise((resolve52) => {
51080
+ return new Promise((resolve62) => {
51023
51081
  let stdout = "";
51024
51082
  let stderr = "";
51025
51083
  let killed = false;
@@ -51044,7 +51102,7 @@ function runCommand22(cmd, args, cwd, timeoutMs = 3e4) {
51044
51102
  });
51045
51103
  proc.on("close", (code) => {
51046
51104
  clearTimeout(timeout);
51047
- resolve52({
51105
+ resolve62({
51048
51106
  exitCode: killed ? -1 : code ?? 0,
51049
51107
  stdout,
51050
51108
  stderr,
@@ -51053,7 +51111,7 @@ function runCommand22(cmd, args, cwd, timeoutMs = 3e4) {
51053
51111
  });
51054
51112
  proc.on("error", (err22) => {
51055
51113
  clearTimeout(timeout);
51056
- resolve52({
51114
+ resolve62({
51057
51115
  exitCode: -1,
51058
51116
  stdout,
51059
51117
  stderr: err22.message,
@@ -51835,7 +51893,7 @@ var init_validation2 = __esm2({
51835
51893
  }
51836
51894
  }
51837
51895
  async runMadgeCircular(workspacePath) {
51838
- return new Promise((resolve52) => {
51896
+ return new Promise((resolve62) => {
51839
51897
  const madgePath = join(workspacePath, "node_modules", ".bin", "madge");
51840
51898
  const hasLocalMadge = existsSync(madgePath);
51841
51899
  const args = [
@@ -51864,16 +51922,16 @@ var init_validation2 = __esm2({
51864
51922
  try {
51865
51923
  const result7 = JSON.parse(stdout);
51866
51924
  if (Array.isArray(result7)) {
51867
- resolve52(result7);
51925
+ resolve62(result7);
51868
51926
  } else {
51869
- resolve52([]);
51927
+ resolve62([]);
51870
51928
  }
51871
51929
  } catch {
51872
- resolve52([]);
51930
+ resolve62([]);
51873
51931
  }
51874
51932
  });
51875
51933
  proc.on("error", () => {
51876
- resolve52([]);
51934
+ resolve62([]);
51877
51935
  });
51878
51936
  });
51879
51937
  }
@@ -51940,7 +51998,7 @@ var init_validation2 = __esm2({
51940
51998
  };
51941
51999
  }
51942
52000
  async checkToolAvailability(tool, args) {
51943
- return new Promise((resolve52) => {
52001
+ return new Promise((resolve62) => {
51944
52002
  const startTime = Date.now();
51945
52003
  const proc = spawn("npx", [
51946
52004
  tool,
@@ -51954,13 +52012,13 @@ var init_validation2 = __esm2({
51954
52012
  timeout: 5e3
51955
52013
  });
51956
52014
  proc.on("close", (code) => {
51957
- resolve52({
52015
+ resolve62({
51958
52016
  healthy: code === 0,
51959
52017
  latency: Date.now() - startTime
51960
52018
  });
51961
52019
  });
51962
52020
  proc.on("error", () => {
51963
- resolve52({
52021
+ resolve62({
51964
52022
  healthy: false,
51965
52023
  latency: Date.now() - startTime
51966
52024
  });
@@ -51997,7 +52055,7 @@ var init_validation2 = __esm2({
51997
52055
  }
51998
52056
  }
51999
52057
  async runMadgeOrphans(workspacePath) {
52000
- return new Promise((resolve52) => {
52058
+ return new Promise((resolve62) => {
52001
52059
  const madgePath = join(workspacePath, "node_modules", ".bin", "madge");
52002
52060
  const hasLocalMadge = existsSync(madgePath);
52003
52061
  const args = [
@@ -52026,16 +52084,16 @@ var init_validation2 = __esm2({
52026
52084
  try {
52027
52085
  const result7 = JSON.parse(stdout);
52028
52086
  if (Array.isArray(result7)) {
52029
- resolve52(result7);
52087
+ resolve62(result7);
52030
52088
  } else {
52031
- resolve52([]);
52089
+ resolve62([]);
52032
52090
  }
52033
52091
  } catch {
52034
- resolve52([]);
52092
+ resolve62([]);
52035
52093
  }
52036
52094
  });
52037
52095
  proc.on("error", () => {
52038
- resolve52([]);
52096
+ resolve62([]);
52039
52097
  });
52040
52098
  });
52041
52099
  }
@@ -52095,7 +52153,7 @@ var init_validation2 = __esm2({
52095
52153
  return diagnostics;
52096
52154
  }
52097
52155
  async runTypeScriptCheck(workspacePath, _files) {
52098
- return new Promise((resolve52) => {
52156
+ return new Promise((resolve62) => {
52099
52157
  const diagnostics = [];
52100
52158
  const tscPath = join(workspacePath, "node_modules", ".bin", "tsc");
52101
52159
  const hasLocalTsc = existsSync(tscPath);
@@ -52138,19 +52196,19 @@ var init_validation2 = __esm2({
52138
52196
  });
52139
52197
  }
52140
52198
  }
52141
- resolve52({
52199
+ resolve62({
52142
52200
  diagnostics
52143
52201
  });
52144
52202
  });
52145
52203
  proc.on("error", () => {
52146
- resolve52({
52204
+ resolve62({
52147
52205
  diagnostics: []
52148
52206
  });
52149
52207
  });
52150
52208
  });
52151
52209
  }
52152
52210
  async runLintCheck(workspacePath, files, fix = false) {
52153
- return new Promise((resolve52) => {
52211
+ return new Promise((resolve62) => {
52154
52212
  const diagnostics = [];
52155
52213
  const biomePath = join(workspacePath, "node_modules", ".bin", "biome");
52156
52214
  const hasLocalBiome = existsSync(biomePath);
@@ -52192,19 +52250,19 @@ var init_validation2 = __esm2({
52192
52250
  });
52193
52251
  }
52194
52252
  }
52195
- resolve52({
52253
+ resolve62({
52196
52254
  diagnostics
52197
52255
  });
52198
52256
  });
52199
52257
  proc.on("error", () => {
52200
- resolve52({
52258
+ resolve62({
52201
52259
  diagnostics: []
52202
52260
  });
52203
52261
  });
52204
52262
  });
52205
52263
  }
52206
52264
  async runBuildCommand(workspacePath) {
52207
- return new Promise((resolve52) => {
52265
+ return new Promise((resolve62) => {
52208
52266
  const proc = spawn("npm", [
52209
52267
  "run",
52210
52268
  "build"
@@ -52234,13 +52292,13 @@ var init_validation2 = __esm2({
52234
52292
  }
52235
52293
  }
52236
52294
  }
52237
- resolve52({
52295
+ resolve62({
52238
52296
  success: code === 0,
52239
52297
  diagnostics
52240
52298
  });
52241
52299
  });
52242
52300
  proc.on("error", () => {
52243
- resolve52({
52301
+ resolve62({
52244
52302
  success: false,
52245
52303
  diagnostics: []
52246
52304
  });
@@ -52550,7 +52608,7 @@ __export2(check_exports, {
52550
52608
  async function detectGitConflicts(workspaceRoot) {
52551
52609
  const { spawn: spawn62 } = await import('child_process');
52552
52610
  const { access: access22 } = await import('fs/promises');
52553
- const conflictedFiles = await new Promise((resolve52) => {
52611
+ const conflictedFiles = await new Promise((resolve62) => {
52554
52612
  const proc = spawn62("git", [
52555
52613
  "diff",
52556
52614
  "--name-only",
@@ -52569,9 +52627,9 @@ async function detectGitConflicts(workspaceRoot) {
52569
52627
  });
52570
52628
  proc.on("close", () => {
52571
52629
  const files = stdout.split("\n").map((f) => f.trim()).filter((f) => f.length > 0);
52572
- resolve52(files);
52630
+ resolve62(files);
52573
52631
  });
52574
- proc.on("error", () => resolve52([]));
52632
+ proc.on("error", () => resolve62([]));
52575
52633
  });
52576
52634
  let source = "unknown";
52577
52635
  try {
@@ -52601,7 +52659,7 @@ async function detectGitConflicts(workspaceRoot) {
52601
52659
  __name(detectGitConflicts, "detectGitConflicts");
52602
52660
  async function getGitDiffFiles22(workspaceRoot, scope = "staged") {
52603
52661
  const { spawn: spawn62 } = await import('child_process');
52604
- return new Promise((resolve52) => {
52662
+ return new Promise((resolve62) => {
52605
52663
  let args;
52606
52664
  switch (scope) {
52607
52665
  case "staged":
@@ -52647,13 +52705,13 @@ async function getGitDiffFiles22(workspaceRoot, scope = "staged") {
52647
52705
  });
52648
52706
  proc.on("close", (code) => {
52649
52707
  if (code !== 0) {
52650
- resolve52([]);
52708
+ resolve62([]);
52651
52709
  return;
52652
52710
  }
52653
52711
  const files = stdout.split("\n").map((f) => f.trim()).filter((f) => f.length > 0).filter((f) => /\.(ts|tsx|js|jsx)$/.test(f));
52654
- resolve52(files);
52712
+ resolve62(files);
52655
52713
  });
52656
- proc.on("error", () => resolve52([]));
52714
+ proc.on("error", () => resolve62([]));
52657
52715
  });
52658
52716
  }
52659
52717
  __name(getGitDiffFiles22, "getGitDiffFiles2");
@@ -56627,6 +56685,9 @@ function buildWireFields(response) {
56627
56685
  fields.next = actionsStr;
56628
56686
  }
56629
56687
  }
56688
+ if (response.open_session_reminder) {
56689
+ fields.reminder = response.open_session_reminder;
56690
+ }
56630
56691
  return fields;
56631
56692
  }
56632
56693
  __name(buildWireFields, "buildWireFields");
@@ -56671,6 +56732,9 @@ function formatWireCompact(response) {
56671
56732
  break;
56672
56733
  }
56673
56734
  }
56735
+ if (response.open_session_reminder) {
56736
+ parts.push(`reminder:${compress2(response.open_session_reminder, 40)}`);
56737
+ }
56674
56738
  return parts.join("|");
56675
56739
  }
56676
56740
  __name(formatWireCompact, "formatWireCompact");
@@ -56975,9 +57039,10 @@ async function handleStart(normalized, context) {
56975
57039
  } else {
56976
57040
  return result7;
56977
57041
  }
56978
- const snapshotStatus = data.snapshot?.created ? "created" : data.snapshot?.id ? "reused" : "skipped";
57042
+ const snapshotStatus = data.snapshot?.created ? "created" : data.snapshot?.id || data.snapshotIds && data.snapshotIds.length > 0 ? "reused" : "skipped";
56979
57043
  const integrationEnrichment = await getIntegrationEnrichment(normalized.files || [], context);
56980
- const riskLevel = (data.risk?.[0] || "l").toUpperCase();
57044
+ const rawRisk = data.risk?.[0] || data.riskAssessment?.overallRisk || "l";
57045
+ const riskLevel = rawRisk[0].toUpperCase();
56981
57046
  const warningCount = data.hotspots?.reduce((sum, h) => sum + h.violations, 0) || 0;
56982
57047
  const nextActions = generateNextActions2({
56983
57048
  risk: riskLevel,
@@ -57627,7 +57692,7 @@ function identifyImprovements(data, params) {
57627
57692
  priority: "medium"
57628
57693
  });
57629
57694
  }
57630
- if (params.ok === 0 || params.outcome === "abandoned" || params.outcome === "blocked") {
57695
+ if (params.ok === 0) {
57631
57696
  improvements.push({
57632
57697
  suggestion: "Capture specific blocker as pitfall learning",
57633
57698
  trigger: "Task did not complete successfully",
@@ -57802,6 +57867,18 @@ var init_session_reporter = __esm2({
57802
57867
  __name12(suggestNextSteps, "suggestNextSteps");
57803
57868
  }
57804
57869
  });
57870
+ function normalizeOutcome(outcome, ok22) {
57871
+ if (!outcome) return ok22 === 0 || ok22 === false ? "abandoned" : "completed";
57872
+ const lower = outcome.toLowerCase().trim();
57873
+ if (lower === "completed" || lower === "success") return "completed";
57874
+ if (lower === "abandoned" || lower === "cancelled" || lower === "canceled") return "abandoned";
57875
+ if (lower === "blocked" || lower === "failed" || lower === "failure" || lower === "error") return "blocked";
57876
+ if (/\bcomplet|\bfinish|\bdone|\bsuccess/.test(lower)) return "completed";
57877
+ if (/\babandon|\bcancel|\bgave up|\bgive up|\bscrapped/.test(lower)) return "abandoned";
57878
+ if (/\bblock|\bfail|\bstuck|\bbroken|\berror/.test(lower)) return "blocked";
57879
+ return ok22 === 0 || ok22 === false ? "abandoned" : "completed";
57880
+ }
57881
+ __name(normalizeOutcome, "normalizeOutcome");
57805
57882
  function formatUserStats(metrics) {
57806
57883
  const parts = [];
57807
57884
  if (metrics.saved !== "not reported") parts.push(`\u{1F4B0} ${metrics.saved} tokens saved`);
@@ -57832,6 +57909,7 @@ var init_snap_end = __esm2({
57832
57909
  const clean = s.replace(/\s+/g, "\u2192");
57833
57910
  return clean.length > max ? `${clean.slice(0, max - 1)}\u2026` : clean;
57834
57911
  }, "compressStr");
57912
+ __name12(normalizeOutcome, "normalizeOutcome");
57835
57913
  handleSnapEnd = /* @__PURE__ */ __name12(async (args, context) => {
57836
57914
  const validation = validateSnapEndParams(args);
57837
57915
  if (!validation.success) {
@@ -57857,7 +57935,7 @@ ${validation.errors?.join("\n") ?? "Unknown validation error"}`
57857
57935
  let suggestedCeremony = null;
57858
57936
  let ceremonySuggestionReason = "";
57859
57937
  let ceremonySuggestionConfidence = 0;
57860
- const isCleanCompletion = params.outcome === "completed" || params.ok === 1;
57938
+ const isCleanCompletion = normalizeOutcome(params.outcome, params.ok) === "completed" || params.ok === 1;
57861
57939
  if (!params.ceremony && context.workspaceRoot && !isCleanCompletion) {
57862
57940
  const sessionState = await getSessionStateAsync(context.workspaceRoot);
57863
57941
  if (sessionState) {
@@ -58009,9 +58087,9 @@ ${ceremonyOutput}
58009
58087
  }
58010
58088
  }
58011
58089
  const completeArgs = {
58012
- outcome: params.outcome || (params.ok === 0 ? "abandoned" : "completed"),
58090
+ outcome: normalizeOutcome(params.outcome, params.ok),
58013
58091
  createSnapshot: true,
58014
- notes: params.notes
58092
+ notes: params.notes ?? params.outcome
58015
58093
  };
58016
58094
  completeArgs.learningsCaptured = directLearningsPersisted;
58017
58095
  const result7 = await handleCompleteTask(completeArgs, context);
@@ -59912,7 +59990,7 @@ var MCP_ROUTES = {
59912
59990
  }
59913
59991
  }
59914
59992
  delay(ms) {
59915
- return new Promise((resolve52) => setTimeout(resolve52, ms));
59993
+ return new Promise((resolve62) => setTimeout(resolve62, ms));
59916
59994
  }
59917
59995
  /**
59918
59996
  * Get circuit breaker state for monitoring
@@ -59961,7 +60039,7 @@ var BridgeReceiver = class {
59961
60039
  console.error("[BridgeReceiver] Already running");
59962
60040
  return;
59963
60041
  }
59964
- return new Promise((resolve52, reject) => {
60042
+ return new Promise((resolve62, reject) => {
59965
60043
  this.server = createServer((req, res) => this.handleRequest(req, res));
59966
60044
  this.server.on("error", (err22) => {
59967
60045
  console.error("[BridgeReceiver] Server error:", err22);
@@ -59969,7 +60047,7 @@ var BridgeReceiver = class {
59969
60047
  });
59970
60048
  this.server.listen(this.port, this.host, () => {
59971
60049
  console.error(`[BridgeReceiver] Listening on http://${this.host}:${this.port}`);
59972
- resolve52();
60050
+ resolve62();
59973
60051
  });
59974
60052
  });
59975
60053
  }
@@ -59980,11 +60058,11 @@ var BridgeReceiver = class {
59980
60058
  if (!this.server) {
59981
60059
  return;
59982
60060
  }
59983
- return new Promise((resolve52) => {
60061
+ return new Promise((resolve62) => {
59984
60062
  this.server?.close(() => {
59985
60063
  console.error("[BridgeReceiver] Stopped");
59986
60064
  this.server = null;
59987
- resolve52();
60065
+ resolve62();
59988
60066
  });
59989
60067
  });
59990
60068
  }
@@ -60558,11 +60636,11 @@ var SSELocalTransport = class extends EventEmitter {
60558
60636
  res.end("Not Found");
60559
60637
  }
60560
60638
  });
60561
- return new Promise((resolve52, reject) => {
60639
+ return new Promise((resolve62, reject) => {
60562
60640
  this.server.listen(this.config.port, () => {
60563
60641
  this.logger.info(`Listening on localhost:${this.config.port}`);
60564
60642
  this.startPingLoop();
60565
- resolve52();
60643
+ resolve62();
60566
60644
  });
60567
60645
  this.server.on("error", reject);
60568
60646
  });
@@ -60583,13 +60661,13 @@ var SSELocalTransport = class extends EventEmitter {
60583
60661
  this.clients.delete(clientId);
60584
60662
  }
60585
60663
  if (this.server) {
60586
- return new Promise((resolve52, reject) => {
60664
+ return new Promise((resolve62, reject) => {
60587
60665
  this.server.close((err22) => {
60588
60666
  if (err22) reject(err22);
60589
60667
  else {
60590
60668
  this.server = null;
60591
60669
  this.logger.info("Transport stopped");
60592
- resolve52();
60670
+ resolve62();
60593
60671
  }
60594
60672
  });
60595
60673
  });
@@ -62379,18 +62457,18 @@ async function createUnifiedServer(options, injectedRegistry) {
62379
62457
  }
62380
62458
  },
62381
62459
  instructions: [
62382
- "SnapBack MCP \u2014 V2 3-Tool Surface (authoritative, overrides individual descriptions):",
62460
+ "SnapBack MCP \u2014 V2 4-Tool Surface (authoritative, overrides individual descriptions):",
62383
62461
  "",
62384
- "Workflow: snapback \u2192 [work] \u2192 snapback_learn (optional) \u2192 snapback_end.",
62462
+ "Workflow: snap_begin \u2192 [work] \u2192 snap_learn (optional) \u2192 snap_end.",
62385
62463
  "",
62386
62464
  "Tool Usage:",
62387
- " 1. Call snapback FIRST before any code changes \u2014 returns intelligence briefing",
62388
- " 2. Call snapback_learn when you discover something worth remembering",
62389
- " 3. Call snapback_end when task is complete \u2014 returns session summary",
62465
+ " 1. Call snap_begin FIRST before any code changes \u2014 returns intelligence briefing",
62466
+ " 2. Call snap_learn when you discover something worth remembering",
62467
+ " 3. Call snap_end when task is complete \u2014 returns session summary",
62390
62468
  "",
62391
62469
  "Key Points:",
62392
- " - snapback is idempotent \u2014 calling twice resumes the same session",
62393
- " - snapback_learn creates implicit session if needed",
62470
+ " - snap_begin is idempotent \u2014 calling twice resumes the same session",
62471
+ " - snap_learn creates implicit session if needed",
62394
62472
  " - All files are automatically protected \u2014 no manual snapshot needed",
62395
62473
  " - Workspace isolation: learnings and context are scoped to this workspace"
62396
62474
  ].join("\n")
@@ -62703,7 +62781,7 @@ ${learning.action}`,
62703
62781
  logSuccess(reqCtx, "Tool call completed", {
62704
62782
  isError: toolResult.isError
62705
62783
  });
62706
- const shouldEnrich = name !== "meta" && name !== "snapback_end";
62784
+ const shouldEnrich = name !== "meta" && name !== "snap_end";
62707
62785
  const enhanced = shouldEnrich ? await vitalsEnricher.enhance(toolResult, workspaceRoot, eventTracker) : toolResult;
62708
62786
  const enrichedContent = (enhanced.content || []).map((c) => ({
62709
62787
  type: "text",
@@ -62732,6 +62810,19 @@ ${warningsText}${humanPart}`;
62732
62810
  }
62733
62811
  }
62734
62812
  }
62813
+ if (enhanced.isError && name !== "snap_learn" && name !== "snap_end") {
62814
+ const firstContent = enrichedContent[0];
62815
+ if (firstContent && typeof firstContent.text === "string") {
62816
+ const learningHint = [
62817
+ "",
62818
+ "\u{1F4A1} SUGGESTED_LEARNING: This tool call failed. Consider capturing the failure pattern:",
62819
+ ` snap_learn({ type: "pitfall", trigger: "${name} error", action: "..." })`,
62820
+ " (Replace '...' with the specific insight from this failure)",
62821
+ ""
62822
+ ].join("\n");
62823
+ firstContent.text = `${firstContent.text}${learningHint}`;
62824
+ }
62825
+ }
62735
62826
  return {
62736
62827
  content: enrichedContent,
62737
62828
  isError: enhanced.isError
@@ -64235,7 +64326,7 @@ async function autoConfigureTools(options) {
64235
64326
  if (detection.detected.length === 0) {
64236
64327
  if (!jsonOutput) {
64237
64328
  console.log(chalk37.yellow("\nNo AI tools detected"));
64238
- console.log(chalk37.gray("Install one of these to use SnapBack MCP:"));
64329
+ console.log(chalk37.gray("Install one of these to use \u{1F9E2} SnapBack MCP:"));
64239
64330
  console.log(chalk37.gray(" \u2022 Claude Desktop - https://claude.ai/download"));
64240
64331
  console.log(chalk37.gray(" \u2022 Cursor - https://cursor.sh"));
64241
64332
  console.log(chalk37.gray(" \u2022 Windsurf - https://codeium.com/windsurf"));
@@ -64252,7 +64343,7 @@ async function autoConfigureTools(options) {
64252
64343
  }
64253
64344
  if (needsSetup.length === 0) {
64254
64345
  if (!jsonOutput) {
64255
- console.log(chalk37.green("\n\u2713 All detected AI tools already have SnapBack configured!"));
64346
+ console.log(chalk37.green("\n\u2713 All detected AI tools already have \u{1F9E2} SnapBack configured!"));
64256
64347
  console.log(chalk37.gray("Use --force to reconfigure."));
64257
64348
  showNextSteps();
64258
64349
  }
@@ -64270,7 +64361,7 @@ Detected ${detection.detected.length} AI tool(s):`));
64270
64361
  if (!skipPrompts && !jsonOutput) {
64271
64362
  const clientNames = needsSetup.map((c) => c.displayName).join(", ");
64272
64363
  const proceed = await confirm({
64273
- message: `Configure SnapBack for ${clientNames}?`,
64364
+ message: `Configure \u{1F9E2} SnapBack for ${clientNames}?`,
64274
64365
  default: true
64275
64366
  });
64276
64367
  if (!proceed) {
@@ -64326,17 +64417,29 @@ async function configureClient(client2, dryRun, apiKey, devMode = false, npmMode
64326
64417
  try {
64327
64418
  const workspaceRoot = workspaceOverride || findWorkspaceRoot3(process.cwd());
64328
64419
  const workspaceConfig = detectWorkspaceConfig(workspaceRoot);
64420
+ let localCliPath;
64329
64421
  if (workspaceConfig && isGlobalConfig(client2.configPath)) {
64330
- spinner2.info(`${client2.displayName} workspace config detected`);
64331
- console.log(chalk37.gray(` Workspace: ${workspaceConfig.path}`));
64332
- console.log(chalk37.gray(` Type: ${workspaceConfig.type}`));
64333
- console.log();
64334
- console.log(chalk37.cyan(" Skipping global config to prevent conflicts"));
64335
- console.log(chalk37.gray(" Workspace configurations take precedence over global"));
64336
- console.log();
64337
- return;
64422
+ if (client2.format === "qoder" && client2.hasSnapback) {
64423
+ const existingValidation = validateClientConfig(client2);
64424
+ if (existingValidation.valid) {
64425
+ spinner2.info(`${client2.displayName} workspace config detected (global config valid, skipping)`);
64426
+ return;
64427
+ }
64428
+ spinner2.text = `Repairing broken global ${client2.displayName} config...`;
64429
+ devMode = false;
64430
+ npmMode = false;
64431
+ localCliPath = void 0;
64432
+ } else {
64433
+ spinner2.info(`${client2.displayName} workspace config detected`);
64434
+ console.log(chalk37.gray(` Workspace: ${workspaceConfig.path}`));
64435
+ console.log(chalk37.gray(` Type: ${workspaceConfig.type}`));
64436
+ console.log();
64437
+ console.log(chalk37.cyan(" Skipping global config to prevent conflicts"));
64438
+ console.log(chalk37.gray(" Workspace configurations take precedence over global"));
64439
+ console.log();
64440
+ return;
64441
+ }
64338
64442
  }
64339
- let localCliPath;
64340
64443
  if (devMode) {
64341
64444
  localCliPath = findCliDistPath(workspaceRoot);
64342
64445
  if (!localCliPath) {
@@ -64426,12 +64529,24 @@ async function configureClientWithResult(client2, dryRun, apiKey, devMode = fals
64426
64529
  try {
64427
64530
  const workspaceRoot = workspaceOverride || findWorkspaceRoot3(process.cwd());
64428
64531
  const workspaceConfig = detectWorkspaceConfig(workspaceRoot);
64532
+ let localCliPath;
64429
64533
  if (workspaceConfig && isGlobalConfig(client2.configPath)) {
64430
- return {
64431
- success: true
64432
- };
64534
+ if (client2.format === "qoder" && client2.hasSnapback) {
64535
+ const existingValidation = validateClientConfig(client2);
64536
+ if (existingValidation.valid) {
64537
+ return {
64538
+ success: true
64539
+ };
64540
+ }
64541
+ devMode = false;
64542
+ npmMode = false;
64543
+ localCliPath = void 0;
64544
+ } else {
64545
+ return {
64546
+ success: true
64547
+ };
64548
+ }
64433
64549
  }
64434
- let localCliPath;
64435
64550
  if (devMode) {
64436
64551
  localCliPath = findCliDistPath(workspaceRoot);
64437
64552
  if (!localCliPath) {
@@ -64526,7 +64641,7 @@ async function resolveApiKey(providedApiKey, skipPrompts = false) {
64526
64641
  }
64527
64642
  if (!skipPrompts) {
64528
64643
  const wantApiKey = await confirm({
64529
- message: "Do you have a SnapBack API key for Pro features?",
64644
+ message: "Do you have a \u{1F9E2} SnapBack API key for Pro features?",
64530
64645
  default: false
64531
64646
  });
64532
64647
  if (wantApiKey) {
@@ -64606,7 +64721,7 @@ async function validateTools(verbose = false) {
64606
64721
  const detection = detectAIClients();
64607
64722
  const configured = detection.detected.filter((c) => c.hasSnapback);
64608
64723
  if (configured.length === 0) {
64609
- console.log(chalk37.yellow("\nNo AI tools with SnapBack configured."));
64724
+ console.log(chalk37.yellow("\nNo AI tools with \u{1F9E2} SnapBack configured."));
64610
64725
  console.log(chalk37.gray("Run: snap tools configure"));
64611
64726
  return;
64612
64727
  }
@@ -64658,7 +64773,7 @@ async function repairTools(skipPrompts = false, workspaceOverride, providedApiKe
64658
64773
  const detection = detectAIClients();
64659
64774
  const configured = detection.detected.filter((c) => c.hasSnapback);
64660
64775
  if (configured.length === 0) {
64661
- console.log(chalk37.yellow("\nNo AI tools with SnapBack configured."));
64776
+ console.log(chalk37.yellow("\nNo AI tools with \u{1F9E2} SnapBack configured."));
64662
64777
  console.log(chalk37.gray("Run: snap tools configure"));
64663
64778
  return;
64664
64779
  }
@@ -64739,8 +64854,8 @@ function showNextSteps() {
64739
64854
  console.log(chalk37.bold("Next Steps:"));
64740
64855
  console.log();
64741
64856
  console.log(" 1. Restart your AI assistant (Claude Desktop, Cursor, etc.)");
64742
- console.log(' 2. Ask your AI: "What does SnapBack know about this project?"');
64743
- console.log(' 3. Before risky changes, ask: "Create a SnapBack checkpoint"');
64857
+ console.log(' 2. Ask your AI: "What does \u{1F9E2} SnapBack know about this project?"');
64858
+ console.log(' 3. Before risky changes, ask: "Create a \u{1F9E2} SnapBack checkpoint"');
64744
64859
  console.log();
64745
64860
  console.log(chalk37.dim("Available tools your AI can now use:"));
64746
64861
  console.log(chalk37.dim(" \u2022 snapback.get_context - Understand your codebase"));
@@ -66194,13 +66309,13 @@ async function addAgentToConfig(configPath, agentName, agentConfig) {
66194
66309
  __name(addAgentToConfig, "addAgentToConfig");
66195
66310
  var acpCommand = createAcpCommand();
66196
66311
  function registerDaemonCommands(program) {
66197
- const daemon = program.command("daemon").description("Manage SnapBack daemon");
66198
- daemon.command("start").description("Start the SnapBack daemon").option("--no-detach", "Run daemon in foreground (attached to terminal)").option("-f, --foreground", "Same as --no-detach").option("-t, --idle-timeout <minutes>", "Shutdown after idle (default: 240)", "240").action(async (options) => {
66312
+ const daemon = program.command("daemon").description("Manage \u{1F9E2} SnapBack daemon");
66313
+ daemon.command("start").description("Start the \u{1F9E2} SnapBack daemon").option("--no-detach", "Run daemon in foreground (attached to terminal)").option("-f, --foreground", "Same as --no-detach").option("-t, --idle-timeout <minutes>", "Shutdown after idle (default: 240)", "240").action(async (options) => {
66199
66314
  if (isServiceRunning()) {
66200
66315
  console.log("\u2713 Daemon is already running");
66201
66316
  return;
66202
66317
  }
66203
- console.log("Starting SnapBack daemon...");
66318
+ console.log("Starting \u{1F9E2} SnapBack daemon...");
66204
66319
  const snapbackdArgs = [
66205
66320
  "--idle-timeout",
66206
66321
  options.idleTimeout
@@ -66307,7 +66422,7 @@ function registerDaemonCommands(program) {
66307
66422
  child.on("error", reject);
66308
66423
  });
66309
66424
  });
66310
- daemon.command("stop").description("Stop the SnapBack daemon").action(async () => {
66425
+ daemon.command("stop").description("Stop the \u{1F9E2} SnapBack daemon").action(async () => {
66311
66426
  if (!isServiceRunning()) {
66312
66427
  console.log("Daemon is not running");
66313
66428
  return;
@@ -67702,7 +67817,7 @@ function formatValue(value) {
67702
67817
  return String(value);
67703
67818
  }
67704
67819
  __name(formatValue, "formatValue");
67705
- var cliVersion2 = "1.1.15" ;
67820
+ var cliVersion2 = "3.0.0" ;
67706
67821
  function createDoctorCommand() {
67707
67822
  return new Command("doctor").description("Diagnose SnapBack installation and ecosystem health").option("--json", "Output structured JSON result").option("--local", "Skip network checks").option("--check <system>", "Check specific subsystem (cli|daemon|workspace|knowledge|mcp|network|extension)").option("--fix", "Attempt automatic repair for known issues").option("-q, --quiet", "Only show failures").option("-v, --verbose", "Show all check details").action(async (options) => {
67708
67823
  const result7 = await runDoctor(options);
@@ -67721,7 +67836,7 @@ async function runDoctor(options) {
67721
67836
  const workspacePath = findWorkspaceRoot2(process.cwd());
67722
67837
  if (!jsonMode) {
67723
67838
  console.log();
67724
- console.log(chalk37.cyan(` SnapBack Doctor v${cliVersion2}`));
67839
+ console.log(chalk37.cyan(` \u{1F9E2} SnapBack Doctor v${cliVersion2}`));
67725
67840
  console.log(chalk37.gray(` Platform: ${platformLabel()}, Node ${process.version}`));
67726
67841
  console.log();
67727
67842
  }
@@ -67794,9 +67909,9 @@ async function runDoctor(options) {
67794
67909
  if (!jsonMode) {
67795
67910
  console.log(chalk37.gray(` Summary: ${summary.pass} passed, ${summary.warn} warning${summary.warn !== 1 ? "s" : ""}, ${summary.fail} failure${summary.fail !== 1 ? "s" : ""}`));
67796
67911
  if (success) {
67797
- console.log(chalk37.green(" \u2714 SnapBack is healthy"));
67912
+ console.log(chalk37.green(" \u2714 \u{1F9E2} SnapBack is healthy"));
67798
67913
  } else {
67799
- console.log(chalk37.red(" \u2716 SnapBack has issues \u2014 run the suggested fixes above"));
67914
+ console.log(chalk37.red(" \u2716 \u{1F9E2} SnapBack has issues \u2014 run the suggested fixes above"));
67800
67915
  }
67801
67916
  console.log();
67802
67917
  }
@@ -68730,7 +68845,7 @@ Latest: v${versionInfo.latest}`,
68730
68845
  }
68731
68846
  __name(createUpgradeCommand, "createUpgradeCommand");
68732
68847
  async function getVersionInfo(canary = false) {
68733
- const current = "1.1.15";
68848
+ const current = "3.0.0";
68734
68849
  let latest = current;
68735
68850
  try {
68736
68851
  const tag = canary ? "canary" : "latest";
@@ -68990,6 +69105,59 @@ ${chalk37.dim("Learn more: https://docs.snapback.dev")}
68990
69105
  __name(displayWelcomeMessage, "displayWelcomeMessage");
68991
69106
 
68992
69107
  // src/commands/wizard.ts
69108
+ var SNAPBACK_AI_INSTRUCTIONS = `## SnapBack Intelligence
69109
+
69110
+ Always call \`snap_begin\` before any coding task. It opens a session and returns
69111
+ codebase intelligence (fragile files, co-change patterns, past learnings) that
69112
+ your context window does not have. Without it: no session tracking, no automatic
69113
+ snapshots, snap_pulse and snap_end return empty results.
69114
+
69115
+ ### Tool Sequence
69116
+ 1. **snap_begin** \u2190 Always first, before file reads or any other tool
69117
+ 2. [your work tools]
69118
+ 3. **snap_pulse** \u2190 After 3+ tool calls, when uncertain, before clarifying questions
69119
+ 4. **snap_learn** \u2190 When user corrects you, or you discover unexpected codebase patterns
69120
+ 5. **snap_end** \u2190 After task completion, before your final message
69121
+
69122
+ ### Without snap_begin
69123
+ - No session = no automatic snapshots
69124
+ - No intelligence context = blind to codebase risks
69125
+ - snap_pulse and snap_end return empty results
69126
+ `;
69127
+ function writeWorkspaceAIInstructions(workspaceRoot) {
69128
+ const targets = [
69129
+ {
69130
+ file: "CLAUDE.md",
69131
+ header: "## SnapBack Intelligence"
69132
+ },
69133
+ {
69134
+ file: ".cursorrules",
69135
+ header: "## SnapBack Intelligence"
69136
+ },
69137
+ {
69138
+ file: ".windsurfrules",
69139
+ header: "## SnapBack Intelligence"
69140
+ }
69141
+ ];
69142
+ for (const { file, header } of targets) {
69143
+ const filePath = path10.join(workspaceRoot, file);
69144
+ try {
69145
+ if (fs32.existsSync(filePath)) {
69146
+ const existing = fs32.readFileSync(filePath, "utf-8");
69147
+ if (existing.includes(header)) continue;
69148
+ fs32.appendFileSync(filePath, `
69149
+
69150
+ ${SNAPBACK_AI_INSTRUCTIONS}`, "utf-8");
69151
+ } else {
69152
+ fs32.writeFileSync(filePath, SNAPBACK_AI_INSTRUCTIONS, "utf-8");
69153
+ }
69154
+ process.stdout.write(chalk37.gray(` ${file} written \u2713
69155
+ `));
69156
+ } catch {
69157
+ }
69158
+ }
69159
+ }
69160
+ __name(writeWorkspaceAIInstructions, "writeWorkspaceAIInstructions");
68993
69161
  function detectProjectType(cwd) {
68994
69162
  const indicators = [];
68995
69163
  let type = "unknown";
@@ -69281,7 +69449,7 @@ ${chalk37.cyan.bold("Step 4: AI Tool Integration\n")}`);
69281
69449
  let getSnapbackMCPConfig2;
69282
69450
  let writeClientConfig2;
69283
69451
  try {
69284
- const mcpConfig = await import('./dist-TEWNOZYS.js');
69452
+ const mcpConfig = await import('./dist-5LR7APG5.js');
69285
69453
  detectAIClients2 = mcpConfig.detectAIClients;
69286
69454
  getSnapbackMCPConfig2 = mcpConfig.getSnapbackMCPConfig;
69287
69455
  writeClientConfig2 = mcpConfig.writeClientConfig;
@@ -69354,6 +69522,9 @@ ${chalk37.cyan.bold("Step 4: AI Tool Integration\n")}`);
69354
69522
  console.log(chalk37.red(` \u2717 ${result7.error}`));
69355
69523
  }
69356
69524
  }
69525
+ if (state.workspaceRoot) {
69526
+ writeWorkspaceAIInstructions(state.workspaceRoot);
69527
+ }
69357
69528
  console.log();
69358
69529
  status.success("MCP integration configured!");
69359
69530
  console.log(chalk37.gray(" Restart your AI tools to activate SnapBack."));
@@ -69644,7 +69815,7 @@ ${chalk37.green.bold("Setup Complete! \u{1F389}\n")}`);
69644
69815
  console.log(`
69645
69816
  ${chalk37.cyan.bold("Quick Health Check:\n")}`);
69646
69817
  try {
69647
- const { detectAIClients: detectAIClients2 } = await import('./dist-TEWNOZYS.js');
69818
+ const { detectAIClients: detectAIClients2 } = await import('./dist-5LR7APG5.js');
69648
69819
  const detection = detectAIClients2({
69649
69820
  cwd: state.workspaceRoot || process.cwd()
69650
69821
  });
@@ -69659,7 +69830,7 @@ ${chalk37.cyan.bold("Quick Health Check:\n")}`);
69659
69830
  } catch {
69660
69831
  }
69661
69832
  try {
69662
- const { detectAIClients: detectAIClients2 } = await import('./dist-TEWNOZYS.js');
69833
+ const { detectAIClients: detectAIClients2 } = await import('./dist-5LR7APG5.js');
69663
69834
  const detection = detectAIClients2({
69664
69835
  cwd: state.workspaceRoot || process.cwd()
69665
69836
  });