create-snipara 1.3.37 → 1.3.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +185 -104
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -208,7 +208,7 @@ Writes `.snipara/companion/doctor.json` and validates:
208
208
  For Claude-specific support, run:
209
209
 
210
210
  ```bash
211
- npx create-snipara doctor --client claude
211
+ npx create-snipara doctor --client claude-code
212
212
  ```
213
213
 
214
214
  This separates "Snipara config was written" from "Claude actually loaded the MCP
package/dist/index.js CHANGED
@@ -13,7 +13,7 @@ import { homedir } from "os";
13
13
  // package.json
14
14
  var package_default = {
15
15
  name: "create-snipara",
16
- version: "1.3.37",
16
+ version: "1.3.39",
17
17
  description: "Onboard Snipara Hosted MCP Context + Memory with auth, companion workflows, and optional Snipara Sandbox",
18
18
  main: "dist/index.js",
19
19
  type: "module",
@@ -88,6 +88,8 @@ var BASE_URL = "https://www.snipara.com";
88
88
  var API_URL = "https://api.snipara.com";
89
89
  var COMPANION_PACKAGE_NAME = "snipara-companion";
90
90
  var COMPANION_COMMAND_NAME = "snipara-companion";
91
+ var COMPANION_NPX_COMMAND = "npx -y snipara-companion@latest";
92
+ var MIN_COMPANION_VERSION = "1.1.37";
91
93
  var GITHUB_ANSWER_PACKS_LABEL = "GitHub PR Answer Packs";
92
94
  var MCP_PACKAGE_NAME = "snipara-mcp";
93
95
  var SANDBOX_PACKAGE_NAME = "snipara-sandbox[all]";
@@ -288,7 +290,8 @@ function parseInitOptionsFromArgv(args) {
288
290
  return parsed;
289
291
  }
290
292
  async function runInit(options) {
291
- const answers = await gatherConfiguration(normalizeInitOptions(options));
293
+ const normalizedOptions = normalizeInitOptions(options);
294
+ const answers = await gatherConfiguration(normalizedOptions);
292
295
  if (answers.installMcp || answers.installRuntime || answers.installOrchestrator) {
293
296
  await installPythonPackages(answers, false);
294
297
  }
@@ -308,9 +311,9 @@ async function runInit(options) {
308
311
  }
309
312
  await updateEnvFiles(answers);
310
313
  await createCompanionConfig(answers);
311
- await syncRemoteAutomationClient(answers, { enabled: !options.skipTest });
314
+ await syncRemoteAutomationClient(answers, { enabled: !normalizedOptions.skipTest });
312
315
  await createCompanionPack(answers);
313
- const validationReport = await runValidation(answers, !options.skipTest);
316
+ const validationReport = await runValidation(answers, !normalizedOptions.skipTest);
314
317
  writeDoctorReport(validationReport);
315
318
  printSuccess(answers, validationReport);
316
319
  if (answers.configureGitHub && answers.githubRepo) {
@@ -937,8 +940,40 @@ function isProvisionedApiKey(value) {
937
940
  function provisionedApiKeyValidationMessage() {
938
941
  return "New Snipara API keys should start with snp-";
939
942
  }
943
+ function normalizeSemver(value) {
944
+ const match = value?.match(/(\d+)\.(\d+)\.(\d+)/);
945
+ if (!match) {
946
+ return null;
947
+ }
948
+ return `${match[1]}.${match[2]}.${match[3]}`;
949
+ }
950
+ function getCompanionCommandVersion() {
951
+ if (!commandExists(COMPANION_COMMAND_NAME)) {
952
+ return null;
953
+ }
954
+ try {
955
+ return execSync(`${COMPANION_COMMAND_NAME} --version`, {
956
+ encoding: "utf-8",
957
+ stdio: ["ignore", "pipe", "ignore"],
958
+ timeout: 1e3
959
+ }).trim();
960
+ } catch {
961
+ return null;
962
+ }
963
+ }
964
+ function isSupportedCompanionVersion(version) {
965
+ const current = normalizeSemver(version);
966
+ return Boolean(current && compareSemver(current, MIN_COMPANION_VERSION) >= 0);
967
+ }
968
+ function getCompanionRequirementMessage() {
969
+ const version = getCompanionCommandVersion();
970
+ if (!version) {
971
+ return `${COMPANION_PACKAGE_NAME} not found on PATH`;
972
+ }
973
+ return `${COMPANION_PACKAGE_NAME} ${version} is older than required ${MIN_COMPANION_VERSION}`;
974
+ }
940
975
  function detectCompanionCommand() {
941
- if (commandExists(COMPANION_COMMAND_NAME)) {
976
+ if (isSupportedCompanionVersion(getCompanionCommandVersion())) {
942
977
  return COMPANION_COMMAND_NAME;
943
978
  }
944
979
  return null;
@@ -1032,11 +1067,11 @@ function detectNodePackageManagers() {
1032
1067
  }
1033
1068
  return managers;
1034
1069
  }
1035
- function getCompanionInstallCommand(packageManager, upgrade) {
1070
+ function getCompanionInstallCommand(packageManager, _upgrade) {
1036
1071
  if (packageManager === "pnpm") {
1037
- return `pnpm add -g ${upgrade ? "--latest " : ""}${COMPANION_PACKAGE_NAME}`;
1072
+ return `pnpm add -g ${COMPANION_PACKAGE_NAME}@latest`;
1038
1073
  }
1039
- return `npm install -g ${upgrade ? COMPANION_PACKAGE_NAME + "@latest" : COMPANION_PACKAGE_NAME}`;
1074
+ return `npm install -g ${COMPANION_PACKAGE_NAME}@latest`;
1040
1075
  }
1041
1076
  async function installNodeCompanion(upgrade) {
1042
1077
  console.log(chalk.bold(`
@@ -1045,7 +1080,7 @@ async function installNodeCompanion(upgrade) {
1045
1080
  const packageManagers = detectNodePackageManagers();
1046
1081
  if (packageManagers.length === 0) {
1047
1082
  console.log(chalk.yellow(" \u26A0\uFE0F npm/pnpm not found. Please install manually:\n"));
1048
- console.log(chalk.gray(` npm install -g ${COMPANION_PACKAGE_NAME}
1083
+ console.log(chalk.gray(` npm install -g ${COMPANION_PACKAGE_NAME}@latest
1049
1084
  `));
1050
1085
  return;
1051
1086
  }
@@ -1081,12 +1116,8 @@ async function installNodeCompanion(upgrade) {
1081
1116
  console.log(chalk.gray(` - ${failure}`));
1082
1117
  }
1083
1118
  console.log(chalk.yellow("\n Install manually with:"));
1084
- console.log(
1085
- chalk.gray(
1086
- ` npm install -g ${upgrade ? COMPANION_PACKAGE_NAME + "@latest" : COMPANION_PACKAGE_NAME}
1087
- `
1088
- )
1089
- );
1119
+ console.log(chalk.gray(` npm install -g ${COMPANION_PACKAGE_NAME}@latest
1120
+ `));
1090
1121
  }
1091
1122
  async function createMcpConfig(answers) {
1092
1123
  console.log(chalk.bold("\n\u2699\uFE0F Creating Configuration\n"));
@@ -1137,31 +1168,10 @@ async function createClaudeCodeConfig(answers) {
1137
1168
  if (answers.profile === "runtime-only") {
1138
1169
  return;
1139
1170
  }
1140
- const claudeDir = path.join(homedir(), ".claude");
1141
- const settingsPath = path.join(claudeDir, "settings.json");
1142
- console.log(chalk.bold("\n\u{1F916} Configuring Claude Code MCP\n"));
1143
- try {
1144
- fs.mkdirSync(claudeDir, { recursive: true });
1145
- let settings = {};
1146
- if (fs.existsSync(settingsPath)) {
1147
- settings = asObjectRecord(JSON.parse(fs.readFileSync(settingsPath, "utf-8")));
1148
- }
1149
- const mcpServers = asObjectRecord(settings.mcpServers);
1150
- settings.mcpServers = {
1151
- ...mcpServers,
1152
- snipara: buildHostedMcpServer(answers)
1153
- };
1154
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", { mode: 384 });
1155
- try {
1156
- fs.chmodSync(settingsPath, 384);
1157
- } catch {
1158
- }
1159
- console.log(chalk.green(" \u2713 Updated ~/.claude/settings.json with Snipara MCP"));
1160
- } catch (error) {
1161
- const detail = error instanceof Error ? error.message : String(error);
1162
- console.log(chalk.yellow(` \u26A0\uFE0F Could not update Claude Code MCP config: ${detail}`));
1163
- console.log(chalk.dim(" Run create-snipara doctor --client claude after manual setup."));
1164
- }
1171
+ console.log(chalk.bold("\n\u{1F916} Claude Code MCP\n"));
1172
+ console.log(chalk.green(" \u2713 Project .mcp.json contains the Snipara Hosted MCP server"));
1173
+ console.log(chalk.dim(" Claude Code loads project-scoped MCP servers from .mcp.json."));
1174
+ console.log(chalk.dim(` Verify with: claude mcp list && claude mcp test snipara`));
1165
1175
  }
1166
1176
  async function createProjectBinding(answers) {
1167
1177
  if (answers.profile === "runtime-only") {
@@ -1469,14 +1479,8 @@ async function createClaudeDesktopConfig(answers) {
1469
1479
  }
1470
1480
  async function installClaudeCodeHooks(answers) {
1471
1481
  console.log(chalk.bold("\n\u{1FA9D} Installing Claude Code Hooks\n"));
1472
- const claudeDir = path.join(homedir(), ".claude");
1482
+ const claudeDir = path.join(process.cwd(), ".claude");
1473
1483
  const hooksDir = path.join(claudeDir, "hooks");
1474
- if (!fs.existsSync(claudeDir)) {
1475
- console.log(
1476
- chalk.yellow(" \u26A0\uFE0F ~/.claude not found. Install Claude Code first, then rerun repair.\n")
1477
- );
1478
- return;
1479
- }
1480
1484
  if (!fs.existsSync(hooksDir)) {
1481
1485
  fs.mkdirSync(hooksDir, { recursive: true });
1482
1486
  }
@@ -1513,41 +1517,88 @@ async function installClaudeCodeHooks(answers) {
1513
1517
  settings = {};
1514
1518
  }
1515
1519
  }
1516
- settings.hooks = settings.hooks || {};
1520
+ settings.hooks = asObjectRecord(settings.hooks);
1517
1521
  const hooksConfig = settings.hooks;
1518
1522
  const hookEntries = [
1519
1523
  {
1520
1524
  event: "PreToolUse",
1521
- script: path.join(hooksDir, "snipara-stuck-guard.sh"),
1522
- matcher: { tool_name: "Bash|Read|Grep|Glob|Edit|MultiEdit|Write" }
1525
+ entry: {
1526
+ matcher: "Bash|Read|Grep|Glob|Edit|MultiEdit|Write",
1527
+ hooks: [
1528
+ {
1529
+ type: "command",
1530
+ command: createClaudeProjectHookCommand("snipara-stuck-guard.sh"),
1531
+ timeout: 10
1532
+ }
1533
+ ]
1534
+ }
1523
1535
  },
1524
1536
  {
1525
- event: "PreToolUse",
1526
- script: path.join(hooksDir, "snipara-startup.sh"),
1527
- matcher: { tool_name: "TodoWrite" }
1537
+ event: "SessionStart",
1538
+ entry: {
1539
+ matcher: "startup|resume|compact|clear",
1540
+ hooks: [
1541
+ {
1542
+ type: "command",
1543
+ command: createClaudeProjectHookCommand("snipara-startup.sh"),
1544
+ timeout: 30
1545
+ }
1546
+ ]
1547
+ }
1528
1548
  },
1529
1549
  {
1530
1550
  event: "PostToolUse",
1531
- script: path.join(hooksDir, "snipara-session.sh"),
1532
- matcher: { tool_name: "Bash|Read|Grep|Glob|Edit|MultiEdit|Write" }
1551
+ entry: {
1552
+ matcher: "Bash|Read|Grep|Glob|Edit|MultiEdit|Write",
1553
+ hooks: [
1554
+ {
1555
+ type: "command",
1556
+ command: createClaudeProjectHookCommand("snipara-session.sh"),
1557
+ timeout: 10
1558
+ }
1559
+ ]
1560
+ }
1533
1561
  },
1534
1562
  {
1535
1563
  event: "PreCompact",
1536
- script: path.join(hooksDir, "snipara-compact.sh")
1564
+ entry: {
1565
+ matcher: "manual|auto",
1566
+ hooks: [
1567
+ {
1568
+ type: "command",
1569
+ command: createClaudeProjectHookCommand("snipara-compact.sh"),
1570
+ timeout: 30
1571
+ }
1572
+ ]
1573
+ }
1537
1574
  }
1538
1575
  ];
1539
- for (const entry of hookEntries) {
1540
- const eventHooks = hooksConfig[entry.event] || [];
1541
- const exists = eventHooks.some(
1542
- (hook) => typeof hook === "object" && hook !== null && "script" in hook && hook.script === entry.script
1543
- );
1576
+ for (const { event, entry } of hookEntries) {
1577
+ const eventHooks = Array.isArray(hooksConfig[event]) ? hooksConfig[event] : [];
1578
+ const expectedCommand = entry.hooks[0].command;
1579
+ const exists = eventHooks.some((hook) => hookConfigEntryHasCommand(hook, expectedCommand));
1544
1580
  if (!exists) {
1545
1581
  eventHooks.push(entry);
1546
- hooksConfig[entry.event] = eventHooks;
1582
+ hooksConfig[event] = eventHooks;
1547
1583
  }
1548
1584
  }
1549
1585
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
1550
- console.log(chalk.green(" \u2713 Updated ~/.claude/settings.json"));
1586
+ console.log(chalk.green(" \u2713 Updated .claude/settings.json hooks"));
1587
+ }
1588
+ function createClaudeProjectHookCommand(scriptName) {
1589
+ return `bash -lc 'cd "\${CLAUDE_PROJECT_DIR:-$PWD}" && ./.claude/hooks/${scriptName}'`;
1590
+ }
1591
+ function hookConfigEntryHasCommand(entry, expectedCommand) {
1592
+ if (!entry || typeof entry !== "object") {
1593
+ return false;
1594
+ }
1595
+ const hooks = entry.hooks;
1596
+ if (!Array.isArray(hooks)) {
1597
+ return false;
1598
+ }
1599
+ return hooks.some(
1600
+ (hook) => hook && typeof hook === "object" && hook.command === expectedCommand
1601
+ );
1551
1602
  }
1552
1603
  function createStuckGuardHook(answers) {
1553
1604
  return `#!/usr/bin/env bash
@@ -1772,7 +1823,7 @@ function getClientGuidance(client) {
1772
1823
  if (client === "windsurf") {
1773
1824
  return [
1774
1825
  "Windsurf should use the hosted MCP surface plus generated Windsurf automation bundles when available.",
1775
- "Use snipara-companion automations install --client windsurf when you want dashboard-managed hook files in the local project."
1826
+ "Use npx -y snipara-companion@latest automations install --client windsurf when you want dashboard-managed hook files in the local project."
1776
1827
  ];
1777
1828
  }
1778
1829
  if (client === "codex") {
@@ -1822,19 +1873,19 @@ function getCompanionCommands(answers) {
1822
1873
  const businessOnboardingCommands = answers.installCompanion ? [
1823
1874
  {
1824
1875
  title: "Preview a business folder import",
1825
- command: `${COMPANION_COMMAND_NAME} onboard-folder ./client-export --source-provider local_folder --write-manifest ./snipara-onboard.json`,
1876
+ command: `${COMPANION_NPX_COMMAND} onboard-folder ./client-export --source-provider local_folder --write-manifest ./snipara-onboard.json`,
1826
1877
  purpose: "Classify a local or LLM-materialized business folder and review the Snipara sync manifest before upload."
1827
1878
  },
1828
1879
  {
1829
1880
  title: "Apply a business folder import",
1830
- command: `${COMPANION_COMMAND_NAME} onboard-folder ./client-export --source-provider local_folder --apply`,
1881
+ command: `${COMPANION_NPX_COMMAND} onboard-folder ./client-export --source-provider local_folder --apply`,
1831
1882
  purpose: "Upload approved business-context documents to hosted Snipara and trigger document reindexing."
1832
1883
  }
1833
1884
  ] : [
1834
1885
  {
1835
1886
  title: "Enable business folder onboarding",
1836
1887
  command: "npx create-snipara repair --with-companion",
1837
- purpose: `Install ${COMPANION_PACKAGE_NAME} so this project can run ${COMPANION_COMMAND_NAME} onboard-folder without using the dashboard.`
1888
+ purpose: `Install ${COMPANION_PACKAGE_NAME} so this project can run ${COMPANION_NPX_COMMAND} onboard-folder without using the dashboard.`
1838
1889
  }
1839
1890
  ];
1840
1891
  const githubAnswerPacksCommand = answers.githubRepo ? {
@@ -1849,7 +1900,7 @@ function getCompanionCommands(answers) {
1849
1900
  return [
1850
1901
  {
1851
1902
  title: "Bootstrap local companion",
1852
- command: `${COMPANION_COMMAND_NAME} init${clientFlag}${projectFlag}`,
1903
+ command: `${COMPANION_NPX_COMMAND} init${clientFlag}${projectFlag}`,
1853
1904
  purpose: "Re-run local companion setup if this workstation needs to refresh browser login or project selection."
1854
1905
  },
1855
1906
  {
@@ -1864,32 +1915,32 @@ function getCompanionCommands(answers) {
1864
1915
  },
1865
1916
  {
1866
1917
  title: "Run a focused hosted query",
1867
- command: `${COMPANION_COMMAND_NAME} query --query "how does auth middleware work?" --max-tokens 4000`,
1918
+ command: `${COMPANION_NPX_COMMAND} query --query "how does auth middleware work?" --max-tokens 4000`,
1868
1919
  purpose: "Pull ranked source context before editing, reviewing, or drafting."
1869
1920
  },
1870
1921
  {
1871
1922
  title: "Load linked standards only when needed",
1872
- command: `${COMPANION_COMMAND_NAME} shared-context --categories MANDATORY BEST_PRACTICES`,
1923
+ command: `${COMPANION_NPX_COMMAND} shared-context --categories MANDATORY BEST_PRACTICES`,
1873
1924
  purpose: "Fetch project-linked team or workspace guidance without treating it as memory."
1874
1925
  },
1875
1926
  {
1876
1927
  title: "Generate a hosted plan",
1877
- command: `${COMPANION_COMMAND_NAME} plan --query "Implement webhook retry hardening" --max-tokens 3000`,
1928
+ command: `${COMPANION_NPX_COMMAND} plan --query "Implement webhook retry hardening" --max-tokens 3000`,
1878
1929
  purpose: "Produce a structured execution plan against indexed project knowledge."
1879
1930
  },
1880
1931
  {
1881
1932
  title: "Start a managed workflow from the visible plan",
1882
- command: `${COMPANION_COMMAND_NAME} workflow start --goal "Implement webhook retry hardening" --plan-file ./plan.md`,
1933
+ command: `${COMPANION_NPX_COMMAND} workflow start --goal "Implement webhook retry hardening" --plan-file ./plan.md`,
1883
1934
  purpose: "Record the LLM-visible plan in .snipara/workflow/current.json so phase state survives compaction."
1884
1935
  },
1885
1936
  {
1886
1937
  title: "Start the current workflow phase",
1887
- command: `${COMPANION_COMMAND_NAME} workflow phase-start context`,
1938
+ command: `${COMPANION_NPX_COMMAND} workflow phase-start context`,
1888
1939
  purpose: "Mark a phase active and print the required Snipara context gate for the agent."
1889
1940
  },
1890
1941
  {
1891
1942
  title: "Analyze code impact before a risky change",
1892
- command: `${COMPANION_COMMAND_NAME} code impact --changed-files src/api/webhooks.ts tests/webhooks.test.ts --diff-summary "webhook retry hardening"`,
1943
+ command: `${COMPANION_NPX_COMMAND} code impact --changed-files src/api/webhooks.ts tests/webhooks.test.ts --diff-summary "webhook retry hardening"`,
1893
1944
  purpose: "Use paid Context code impact analysis to surface likely tests, docs, routes, config, and risks."
1894
1945
  },
1895
1946
  {
@@ -1900,48 +1951,48 @@ function getCompanionCommands(answers) {
1900
1951
  githubAnswerPacksCommand,
1901
1952
  {
1902
1953
  title: "Upload or refresh a doc",
1903
- command: `${COMPANION_COMMAND_NAME} upload --path docs/local-playbook.md --file ./docs/local-playbook.md`,
1954
+ command: `${COMPANION_NPX_COMMAND} upload --path docs/local-playbook.md --file ./docs/local-playbook.md`,
1904
1955
  purpose: "Push local notes or docs into hosted Snipara context."
1905
1956
  },
1906
1957
  ...businessOnboardingCommands,
1907
1958
  {
1908
1959
  title: "Inspect a specific chunk",
1909
- command: `${COMPANION_COMMAND_NAME} chunk get --chunk-id <chunk_id>`,
1960
+ command: `${COMPANION_NPX_COMMAND} chunk get --chunk-id <chunk_id>`,
1910
1961
  purpose: "Open an exact chunk returned by references-based search."
1911
1962
  },
1912
1963
  {
1913
1964
  title: "Bootstrap the session memory view",
1914
- command: `${COMPANION_COMMAND_NAME} session-bootstrap`,
1965
+ command: `${COMPANION_NPX_COMMAND} session-bootstrap`,
1915
1966
  purpose: "Load durable decisions, learnings, and optional short-lived carryover at session start."
1916
1967
  },
1917
1968
  {
1918
1969
  title: "Check Stuck Guard status",
1919
- command: `${COMPANION_COMMAND_NAME} stuck-guard status`,
1970
+ command: `${COMPANION_NPX_COMMAND} stuck-guard status`,
1920
1971
  purpose: "Inspect whether recent tool failures, empty searches, or risky workflow signals should trigger a Rescue Pack."
1921
1972
  },
1922
1973
  {
1923
1974
  title: "Simulate a Stuck Guard fixture",
1924
- command: `${COMPANION_COMMAND_NAME} stuck-guard simulate --fixture ./stuck-guard-fixture.json`,
1975
+ command: `${COMPANION_NPX_COMMAND} stuck-guard simulate --fixture ./stuck-guard-fixture.json`,
1925
1976
  purpose: "Replay canonical events against the hosted Stuck Guard decision API before enabling stricter modes."
1926
1977
  },
1927
1978
  {
1928
1979
  title: "Ask memory about past decisions",
1929
- command: `${COMPANION_COMMAND_NAME} recall --query "What did we decide about auth retries?" --type decision`,
1980
+ command: `${COMPANION_NPX_COMMAND} recall --query "What did we decide about auth retries?" --type decision`,
1930
1981
  purpose: "Query durable memory for past decisions, preferences, or validated learnings."
1931
1982
  },
1932
1983
  {
1933
1984
  title: "Commit durable task memory",
1934
- command: `${COMPANION_COMMAND_NAME} task-commit --summary "Hardened webhook retries and updated tests" --category engineering --outcome completed --files src/api/webhooks.ts tests/webhooks.test.ts`,
1985
+ command: `${COMPANION_NPX_COMMAND} task-commit --summary "Hardened webhook retries and updated tests" --category engineering --outcome completed --files src/api/webhooks.ts tests/webhooks.test.ts`,
1935
1986
  purpose: "Persist reusable outcomes back into hosted memory after the work is done."
1936
1987
  },
1937
1988
  {
1938
1989
  title: "Commit a managed workflow phase",
1939
- command: `${COMPANION_COMMAND_NAME} workflow phase-commit context --summary "Loaded context and mapped impacted files" --files src/api/webhooks.ts tests/webhooks.test.ts`,
1990
+ command: `${COMPANION_NPX_COMMAND} workflow phase-commit context --summary "Loaded context and mapped impacted files" --files src/api/webhooks.ts tests/webhooks.test.ts`,
1940
1991
  purpose: "Persist this phase through hosted memory and advance the local managed workflow state."
1941
1992
  },
1942
1993
  {
1943
1994
  title: "Commit the final managed workflow result",
1944
- command: `${COMPANION_COMMAND_NAME} final-commit --summary "Shipped webhook retry hardening and tests" --files src/api/webhooks.ts tests/webhooks.test.ts`,
1995
+ command: `${COMPANION_NPX_COMMAND} final-commit --summary "Shipped webhook retry hardening and tests" --files src/api/webhooks.ts tests/webhooks.test.ts`,
1945
1996
  purpose: "Persist the final workflow outcome and close the managed workflow after verification."
1946
1997
  }
1947
1998
  ];
@@ -1978,7 +2029,7 @@ function getClientPackFilename(client) {
1978
2029
  }
1979
2030
  function getClientConfigArtifacts(client) {
1980
2031
  if (client === "claude-code") {
1981
- return ["AGENTS.md", "CLAUDE.md", "~/.claude/settings.json MCP server"];
2032
+ return ["AGENTS.md", "CLAUDE.md", ".mcp.json MCP server", ".claude/settings.json hooks"];
1982
2033
  }
1983
2034
  if (client === "cursor") {
1984
2035
  return [".mcp.json", ".cursor/rules/snipara.mdc", "dashboard automation bundle when installed"];
@@ -2008,7 +2059,7 @@ function getClientConfigArtifacts(client) {
2008
2059
  }
2009
2060
  function getClientAutomationCommand(client) {
2010
2061
  const companionClient = getCompanionInitClientFlag(client).match(/--client\s+(\S+)/)?.[1];
2011
- return companionClient ? `${COMPANION_COMMAND_NAME} automations install --client ${companionClient}` : null;
2062
+ return companionClient ? `${COMPANION_NPX_COMMAND} automations install --client ${companionClient}` : null;
2012
2063
  }
2013
2064
  function buildClientPackMarkdown(answers, commands, guidance) {
2014
2065
  const clientName = getClientDisplayName(answers.aiClient);
@@ -2033,7 +2084,7 @@ ${getClientConfigArtifacts(answers.aiClient).map((item) => `- \`${item}\``).join
2033
2084
 
2034
2085
  ## Recommended Setup
2035
2086
 
2036
- ${bootstrapCommand ? `1. Bootstrap companion: \`${bootstrapCommand.command}\`` : "1. Bootstrap companion with `snipara-companion init`."}
2087
+ ${bootstrapCommand ? `1. Bootstrap companion: \`${bootstrapCommand.command}\`` : `1. Bootstrap companion with \`${COMPANION_NPX_COMMAND} init\`.`}
2037
2088
  2. Confirm the client can list or call the \`snipara_*\` MCP tools.
2038
2089
  3. Keep exact edits, tests, and dirty working-tree checks local; use Snipara for memory, source truth, and indexed code graph context.
2039
2090
  ${automationCommand ? `4. For generated local automations, run \`${automationCommand}\` when that client exposes usable automation files.` : "4. This client is treated as MCP-first; no local hook surface is assumed."}
@@ -2047,6 +2098,7 @@ ${guidance.map((line) => `- ${line}`).join("\n")}
2047
2098
  - Start substantial work with \`snipara_recall\` and \`snipara_context_query\`.
2048
2099
  - Use \`answer_pack\` facts, caveats, source maps, and verification checks before making claims.
2049
2100
  - Use \`snipara_code_symbol_card\` and \`snipara_code_impact\` for important symbols or risky multi-file changes when available.
2101
+ - Use the installed \`snipara-companion\` binary for low-latency hooks; use \`${COMPANION_NPX_COMMAND}\` for one-off terminal commands when freshness matters.
2050
2102
  - Use \`snipara-companion workflow start\`, \`phase-start\`, \`phase-commit\`, \`workflow resume\`, and \`final-commit\` for multi-phase work.
2051
2103
  - Persist only durable decisions, learnings, preferences, workflows, and troubleshooting outcomes. Never persist secrets.
2052
2104
  `;
@@ -2097,11 +2149,11 @@ Use this when Claude, ChatGPT, Codex, or another LLM client has already material
2097
2149
 
2098
2150
  ${answers.installCompanion ? `Preview first:
2099
2151
 
2100
- \`${COMPANION_COMMAND_NAME} onboard-folder ./client-export --source-provider local_folder --write-manifest ./snipara-onboard.json\`
2152
+ \`${COMPANION_NPX_COMMAND} onboard-folder ./client-export --source-provider local_folder --write-manifest ./snipara-onboard.json\`
2101
2153
 
2102
2154
  Apply only after reviewing the manifest:
2103
2155
 
2104
- \`${COMPANION_COMMAND_NAME} onboard-folder ./client-export --source-provider local_folder --apply\`` : `This setup did not install snipara-companion. Enable it before business folder imports:
2156
+ \`${COMPANION_NPX_COMMAND} onboard-folder ./client-export --source-provider local_folder --apply\`` : `This setup did not install snipara-companion. Enable it before business folder imports:
2105
2157
 
2106
2158
  \`npx create-snipara repair --with-companion\``}
2107
2159
 
@@ -2237,11 +2289,15 @@ function getClaudeClientChecks(answers, detection) {
2237
2289
  const checks = [];
2238
2290
  const claudePaths = getClaudeConfigPaths();
2239
2291
  const projectConfig = readJsonFile(path.join(process.cwd(), ".mcp.json"));
2292
+ const projectSettings = readJsonFile(path.join(process.cwd(), ".claude", "settings.json"));
2240
2293
  const codeSettings = readJsonFile(claudePaths.codeSettingsPath);
2241
2294
  const desktopConfig = readJsonFile(claudePaths.desktopConfigPath);
2242
2295
  const projectHasSnipara = containsSniparaMcpReference(projectConfig);
2296
+ const projectSettingsHasSnipara = containsSniparaMcpReference(projectSettings);
2243
2297
  const codeSettingsHasSnipara = containsSniparaMcpReference(codeSettings);
2298
+ const hasLegacyCodeSettingsMcp = projectSettingsHasSnipara || codeSettingsHasSnipara;
2244
2299
  const desktopHasSnipara = containsSniparaMcpReference(desktopConfig);
2300
+ const projectHooksConfigured = containsClaudeHooksReference(projectSettings);
2245
2301
  checks.push({
2246
2302
  name: "claude_project_config",
2247
2303
  status: projectHasSnipara ? "pass" : "warn",
@@ -2250,8 +2306,13 @@ function getClaudeClientChecks(answers, detection) {
2250
2306
  if (answers.aiClient === "claude-code") {
2251
2307
  checks.push({
2252
2308
  name: "claude_code_settings",
2253
- status: detection.files.claudeSettings ? codeSettingsHasSnipara ? "pass" : "warn" : "warn",
2254
- detail: detection.files.claudeSettings ? codeSettingsHasSnipara ? "~/.claude/settings.json contains a Snipara MCP server" : "~/.claude/settings.json exists but no Snipara MCP server was found" : "~/.claude/settings.json not found; Claude may require Settings -> MCP UI setup"
2309
+ status: hasLegacyCodeSettingsMcp ? "warn" : "pass",
2310
+ detail: hasLegacyCodeSettingsMcp ? "Legacy Claude settings contain an MCP server entry; Claude Code project MCP should live in .mcp.json or be managed by claude mcp add --scope project" : "Claude settings are not being used as the MCP destination; .claude/settings.json is hooks-only"
2311
+ });
2312
+ checks.push({
2313
+ name: "claude_code_hooks",
2314
+ status: !answers.installHooks || projectHooksConfigured ? "pass" : "warn",
2315
+ detail: answers.installHooks ? projectHooksConfigured ? "Project .claude/settings.json contains Snipara hook commands" : "Project .claude/settings.json hooks missing; run create-snipara repair --with-hooks" : "Claude hooks not requested"
2255
2316
  });
2256
2317
  }
2257
2318
  if (answers.aiClient === "claude-desktop") {
@@ -2298,6 +2359,15 @@ function containsSniparaMcpReference(value) {
2298
2359
  }
2299
2360
  return false;
2300
2361
  }
2362
+ function containsClaudeHooksReference(value) {
2363
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
2364
+ return false;
2365
+ }
2366
+ const hooks = value.hooks;
2367
+ return Boolean(
2368
+ hooks && typeof hooks === "object" && JSON.stringify(hooks).includes(COMPANION_COMMAND_NAME)
2369
+ );
2370
+ }
2301
2371
  function getCreateSniparaPackageFreshnessCheck() {
2302
2372
  if (!commandExists("npm")) {
2303
2373
  return {
@@ -2388,7 +2458,7 @@ async function runValidation(answers, includeHostedCheck, options = {}) {
2388
2458
  checks.push({
2389
2459
  name: "companion_cli",
2390
2460
  status: answers.installCompanion ? detection.tooling.companionCli ? "pass" : "fail" : "pass",
2391
- detail: answers.installCompanion && !detection.tooling.companionCli ? `${COMPANION_PACKAGE_NAME} not found on PATH` : "Companion CLI ready or not requested"
2461
+ detail: answers.installCompanion && !detection.tooling.companionCli ? getCompanionRequirementMessage() : "Companion CLI ready or not requested"
2392
2462
  });
2393
2463
  checks.push({
2394
2464
  name: "sandbox_cli",
@@ -2417,8 +2487,8 @@ async function runValidation(answers, includeHostedCheck, options = {}) {
2417
2487
  });
2418
2488
  checks.push({
2419
2489
  name: "hooks",
2420
- status: answers.installHooks && targetClient === "claude-code" ? detection.files.claudeDir ? "pass" : "warn" : "pass",
2421
- detail: answers.installHooks && targetClient === "claude-code" ? detection.files.claudeDir ? "~/.claude detected" : "~/.claude missing" : "Hooks not requested"
2490
+ status: answers.installHooks && targetClient === "claude-code" ? detection.files.claudeProjectSettings ? "pass" : "warn" : "pass",
2491
+ detail: answers.installHooks && targetClient === "claude-code" ? detection.files.claudeProjectSettings ? "Project .claude/settings.json hooks present" : "Project .claude/settings.json hooks missing" : "Hooks not requested"
2422
2492
  });
2423
2493
  if (targetClient === "claude-code" || targetClient === "claude-desktop") {
2424
2494
  checks.push(...getClaudeClientChecks(effectiveAnswers, detection));
@@ -2679,7 +2749,7 @@ function printSuccess(answers, report) {
2679
2749
  }
2680
2750
  if (answers.aiClient === "claude-code" && answers.profile !== "runtime-only") {
2681
2751
  console.log(
2682
- chalk.green(" \u2713 ~/.claude/settings.json") + chalk.dim(" - Claude Code Hosted MCP server")
2752
+ chalk.green(" \u2713 Claude Code") + chalk.dim(" - .mcp.json MCP config, .claude/settings.json hooks when enabled")
2683
2753
  );
2684
2754
  }
2685
2755
  console.log(chalk.green(" \u2713 .snipara/companion") + chalk.dim(" - Local pack + doctor report"));
@@ -2696,7 +2766,9 @@ function printSuccess(answers, report) {
2696
2766
  printNextStep(
2697
2767
  "For Claude Code, start a new session and confirm Snipara appears in Settings -> MCP or ToolSearch."
2698
2768
  );
2699
- printNextStep("If Snipara tools are missing, run: npx create-snipara doctor --client claude");
2769
+ printNextStep(
2770
+ "If Snipara tools are missing, run: npx create-snipara doctor --client claude-code"
2771
+ );
2700
2772
  }
2701
2773
  if (answers.aiClient === "claude-desktop") {
2702
2774
  printNextStep(
@@ -2714,15 +2786,17 @@ function printSuccess(answers, report) {
2714
2786
  printNextStep("Open .snipara/companion/README.md for local commands and repair flow.");
2715
2787
  printNextStep("Run create-snipara doctor anytime you want a fresh local validation report.");
2716
2788
  if (answers.installCompanion) {
2717
- printNextStep(`${COMPANION_COMMAND_NAME} is the companion command for local workflow helpers.`);
2718
2789
  printNextStep(
2719
- `Use ${COMPANION_COMMAND_NAME} query / plan / upload / task-commit for explicit local workflows.`
2790
+ `${COMPANION_COMMAND_NAME} ${MIN_COMPANION_VERSION}+ is installed for low-latency local hooks.`
2791
+ );
2792
+ printNextStep(
2793
+ `Use ${COMPANION_NPX_COMMAND} query / plan / upload / task-commit for explicit local workflows.`
2720
2794
  );
2721
2795
  printNextStep(
2722
- `For business folders, preview with: ${COMPANION_COMMAND_NAME} onboard-folder ./client-export --source-provider local_folder --write-manifest ./snipara-onboard.json`
2796
+ `For business folders, preview with: ${COMPANION_NPX_COMMAND} onboard-folder ./client-export --source-provider local_folder --write-manifest ./snipara-onboard.json`
2723
2797
  );
2724
2798
  printNextStep(
2725
- `Apply after review with: ${COMPANION_COMMAND_NAME} onboard-folder ./client-export --source-provider local_folder --apply`
2799
+ `Apply after review with: ${COMPANION_NPX_COMMAND} onboard-folder ./client-export --source-provider local_folder --apply`
2726
2800
  );
2727
2801
  } else if (answers.profile !== "runtime-only") {
2728
2802
  printNextStep(
@@ -2799,6 +2873,8 @@ function detectEnvironment() {
2799
2873
  sniparaProject: fs.existsSync(path.join(process.cwd(), ".snipara", "project")),
2800
2874
  claudeDir: fs.existsSync(path.join(homedir(), ".claude")),
2801
2875
  claudeSettings: fs.existsSync(claudePaths.codeSettingsPath),
2876
+ claudeProjectDir: fs.existsSync(path.join(process.cwd(), ".claude")),
2877
+ claudeProjectSettings: fs.existsSync(path.join(process.cwd(), ".claude", "settings.json")),
2802
2878
  claudeDesktopConfig: fs.existsSync(claudePaths.desktopConfigPath),
2803
2879
  cursorDir: fs.existsSync(path.join(homedir(), ".cursor")),
2804
2880
  sniparaCompanionPack: fs.existsSync(path.join(process.cwd(), ".snipara", "companion")),
@@ -3024,7 +3100,8 @@ function detectRepoProjectSlug() {
3024
3100
  try {
3025
3101
  const remote = execSync("git remote get-url origin", {
3026
3102
  encoding: "utf-8",
3027
- stdio: ["ignore", "pipe", "ignore"]
3103
+ stdio: ["ignore", "pipe", "ignore"],
3104
+ timeout: 500
3028
3105
  }).trim();
3029
3106
  const match = remote.match(/[/:]([^/]+?)(?:\.git)?$/);
3030
3107
  if (match) {
@@ -3041,7 +3118,8 @@ function detectGitRepository() {
3041
3118
  try {
3042
3119
  const remoteUrl = execSync("git remote get-url origin", {
3043
3120
  encoding: "utf-8",
3044
- stdio: ["ignore", "pipe", "ignore"]
3121
+ stdio: ["ignore", "pipe", "ignore"],
3122
+ timeout: 500
3045
3123
  }).trim();
3046
3124
  const match = remoteUrl.match(/github\.com[:/]([^/]+)\/([^/]+?)(?:\.git)?$/) || remoteUrl.match(/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/);
3047
3125
  if (!match) {
@@ -3065,7 +3143,8 @@ function detectGitBranch() {
3065
3143
  try {
3066
3144
  const branch = execSync("git branch --show-current", {
3067
3145
  encoding: "utf-8",
3068
- stdio: ["ignore", "pipe", "ignore"]
3146
+ stdio: ["ignore", "pipe", "ignore"],
3147
+ timeout: 500
3069
3148
  }).trim();
3070
3149
  if (branch) {
3071
3150
  return branch;
@@ -3075,7 +3154,8 @@ function detectGitBranch() {
3075
3154
  try {
3076
3155
  const branch = execSync("git rev-parse --abbrev-ref HEAD", {
3077
3156
  encoding: "utf-8",
3078
- stdio: ["ignore", "pipe", "ignore"]
3157
+ stdio: ["ignore", "pipe", "ignore"],
3158
+ timeout: 500
3079
3159
  }).trim();
3080
3160
  return branch && branch !== "HEAD" ? branch : "main";
3081
3161
  } catch {
@@ -3085,7 +3165,8 @@ function detectGitBranch() {
3085
3165
  function isGitRepo() {
3086
3166
  try {
3087
3167
  execSync("git rev-parse --is-inside-work-tree", {
3088
- stdio: "ignore"
3168
+ stdio: "ignore",
3169
+ timeout: 500
3089
3170
  });
3090
3171
  return true;
3091
3172
  } catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-snipara",
3
- "version": "1.3.37",
3
+ "version": "1.3.39",
4
4
  "description": "Onboard Snipara Hosted MCP Context + Memory with auth, companion workflows, and optional Snipara Sandbox",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",