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.
- package/README.md +1 -1
- package/dist/index.js +185 -104
- 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.
|
|
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
|
|
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: !
|
|
314
|
+
await syncRemoteAutomationClient(answers, { enabled: !normalizedOptions.skipTest });
|
|
312
315
|
await createCompanionPack(answers);
|
|
313
|
-
const validationReport = await runValidation(answers, !
|
|
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 (
|
|
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,
|
|
1070
|
+
function getCompanionInstallCommand(packageManager, _upgrade) {
|
|
1036
1071
|
if (packageManager === "pnpm") {
|
|
1037
|
-
return `pnpm add -g ${
|
|
1072
|
+
return `pnpm add -g ${COMPANION_PACKAGE_NAME}@latest`;
|
|
1038
1073
|
}
|
|
1039
|
-
return `npm install -g ${
|
|
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
|
-
|
|
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
|
-
|
|
1141
|
-
|
|
1142
|
-
console.log(chalk.
|
|
1143
|
-
|
|
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(
|
|
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
|
-
|
|
1522
|
-
|
|
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: "
|
|
1526
|
-
|
|
1527
|
-
|
|
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
|
-
|
|
1532
|
-
|
|
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
|
-
|
|
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[
|
|
1541
|
-
const
|
|
1542
|
-
|
|
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[
|
|
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
|
|
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: `${
|
|
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: `${
|
|
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 ${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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", "
|
|
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 ? `${
|
|
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}\`` :
|
|
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
|
-
\`${
|
|
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
|
-
\`${
|
|
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:
|
|
2254
|
-
detail:
|
|
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 ?
|
|
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.
|
|
2421
|
-
detail: answers.installHooks && targetClient === "claude-code" ? detection.files.
|
|
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
|
|
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(
|
|
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
|
-
|
|
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: ${
|
|
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: ${
|
|
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