@link-assistant/hive-mind 1.54.4 → 1.54.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 1.54.5
4
+
5
+ ### Patch Changes
6
+
7
+ - ea79845: Disable noisy Claude Code features for solve runs via merged user settings, subprocess environment variables, and Docker image defaults. Expands the quiet config to also disable fast mode, feedback surveys, mouse tracking, away summaries, Claude attribution (commit/pr), co-authored-by trailer, thinking summaries, and UI animations, sets viewMode to verbose, and caps tool-use concurrency at 4 for deterministic autonomous runs. Keeps Claude's built-in git/PR instructions on (`includeGitInstructions: true`), enables task tracking (`CLAUDE_CODE_ENABLE_TASKS=1`) and turn resume (`CLAUDE_CODE_RESUME_INTERRUPTED_TURN=1`), and makes the bypass-permissions mode audible via `permissions.defaultMode: "bypassPermissions"` + `skipDangerousModePermissionPrompt: true` (complementing the existing `--dangerously-skip-permissions` CLI flag). Adds a reusable `configure-claude` bin with an apply default and a `--verify` check-only mode so users and system administrators can reset or audit Claude Code configuration manually after installing `@link-assistant/hive-mind`. Docker release builds now wait for the npm package version to become available, pass that exact version into Docker as `HIVE_MIND_VERSION`, install `@link-assistant/hive-mind@${HIVE_MIND_VERSION}`, and invoke the published `configure-claude` bin directly instead of copying repo source files into the Docker build.
8
+
3
9
  ## 1.54.4
4
10
 
5
11
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.54.4",
3
+ "version": "1.54.5",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -9,11 +9,12 @@
9
9
  "solve": "./src/solve.mjs",
10
10
  "task": "./src/task.mjs",
11
11
  "review": "./src/review.mjs",
12
+ "configure-claude": "./src/configure-claude.mjs",
12
13
  "start-screen": "./src/start-screen.mjs",
13
14
  "hive-telegram-bot": "./src/telegram-bot.mjs"
14
15
  },
15
16
  "scripts": {
16
- "test": "node tests/solve-queue.test.mjs && node tests/limits-display.test.mjs && node tests/test-usage-limit.mjs && node tests/test-codex-support.mjs && node tests/test-build-cost-info-string.mjs && node tests/test-claude-code-install-method.mjs && node tests/test-issue-1616-pr-issue-link-preservation.mjs && node tests/test-pre-pr-failure-notifier-1640.mjs && node tests/test-telegram-message-filters.mjs && node tests/test-telegram-bot-command-aliases.mjs && node tests/test-solve-queue-command.mjs && node tests/test-queue-display-1267.mjs && node tests/test-telegram-bot-launcher.mjs",
17
+ "test": "node tests/solve-queue.test.mjs && node tests/limits-display.test.mjs && node tests/test-usage-limit.mjs && node tests/test-codex-support.mjs && node tests/test-build-cost-info-string.mjs && node tests/test-claude-code-install-method.mjs && node tests/test-claude-quiet-config.mjs && node tests/test-configure-claude-bin.mjs && node tests/test-docker-release-order.mjs && node tests/test-issue-1616-pr-issue-link-preservation.mjs && node tests/test-pre-pr-failure-notifier-1640.mjs && node tests/test-telegram-message-filters.mjs && node tests/test-telegram-bot-command-aliases.mjs && node tests/test-solve-queue-command.mjs && node tests/test-queue-display-1267.mjs && node tests/test-telegram-bot-launcher.mjs",
17
18
  "test:queue": "node tests/solve-queue.test.mjs",
18
19
  "test:limits-display": "node tests/limits-display.test.mjs",
19
20
  "test:usage-limit": "node tests/test-usage-limit.mjs",
@@ -25,7 +26,7 @@
25
26
  "changeset": "changeset",
26
27
  "changeset:version": "changeset version",
27
28
  "changeset:publish": "npm run build:pre && changeset publish",
28
- "build:pre": "chmod +x src/hive.mjs && chmod +x src/solve.mjs",
29
+ "build:pre": "chmod +x src/hive.mjs && chmod +x src/solve.mjs && chmod +x src/configure-claude.mjs",
29
30
  "prepare": "husky"
30
31
  },
31
32
  "repository": {
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'node:fs/promises';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+
7
+ export const REQUIRED_CLAUDE_QUIET_ENV = Object.freeze({
8
+ CLAUDE_CODE_DISABLE_AUTO_MEMORY: '1',
9
+ CLAUDE_CODE_DISABLE_CRON: '1',
10
+ CLAUDE_CODE_DISABLE_TERMINAL_TITLE: '1',
11
+ CLAUDE_CODE_DISABLE_CLAUDE_MDS: '1',
12
+ CLAUDE_CODE_DISABLE_FAST_MODE: '1',
13
+ CLAUDE_CODE_DISABLE_FEEDBACK_SURVEY: '1',
14
+ CLAUDE_CODE_DISABLE_MOUSE: '1',
15
+ CLAUDE_CODE_ENABLE_AWAY_SUMMARY: '0',
16
+ CLAUDE_CODE_ENABLE_TASKS: '1',
17
+ CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY: '4',
18
+ CLAUDE_CODE_RESUME_INTERRUPTED_TURN: '1',
19
+ DISABLE_FEEDBACK_COMMAND: '1',
20
+ });
21
+
22
+ export const REQUIRED_CLAUDE_QUIET_SETTINGS = Object.freeze({
23
+ autoMemoryEnabled: false,
24
+ spinnerTipsEnabled: false,
25
+ awaySummaryEnabled: false,
26
+ feedbackSurveyRate: 0,
27
+ includeCoAuthoredBy: false,
28
+ includeGitInstructions: true,
29
+ prefersReducedMotion: true,
30
+ showThinkingSummaries: false,
31
+ skipDangerousModePermissionPrompt: true,
32
+ viewMode: 'verbose',
33
+ });
34
+
35
+ export const REQUIRED_CLAUDE_QUIET_ATTRIBUTION = Object.freeze({
36
+ commit: '',
37
+ pr: '',
38
+ });
39
+
40
+ export const REQUIRED_CLAUDE_QUIET_PERMISSIONS = Object.freeze({
41
+ defaultMode: 'bypassPermissions',
42
+ });
43
+
44
+ export const buildClaudeQuietEnv = (baseEnv = process.env) => ({
45
+ ...baseEnv,
46
+ ...REQUIRED_CLAUDE_QUIET_ENV,
47
+ });
48
+
49
+ export const formatClaudeQuietConfigSummary = () => {
50
+ const settings = Object.entries(REQUIRED_CLAUDE_QUIET_SETTINGS)
51
+ .map(([key, value]) => `${key}=${JSON.stringify(value)}`)
52
+ .join(', ');
53
+ const attribution = `attribution=${JSON.stringify(REQUIRED_CLAUDE_QUIET_ATTRIBUTION)}`;
54
+ const permissions = `permissions=${JSON.stringify(REQUIRED_CLAUDE_QUIET_PERMISSIONS)}`;
55
+ const env = Object.entries(REQUIRED_CLAUDE_QUIET_ENV)
56
+ .map(([key, value]) => `${key}=${value}`)
57
+ .join(', ');
58
+ return `settings[${settings}, ${attribution}, ${permissions}], env[${env}]`;
59
+ };
60
+
61
+ const isPlainObject = value => value && typeof value === 'object' && !Array.isArray(value);
62
+
63
+ export const ensureClaudeQuietConfig = async ({ settingsPath, log } = {}) => {
64
+ const resolvedPath = settingsPath || path.join(os.homedir(), '.claude', 'settings.json');
65
+ let settings = {};
66
+ try {
67
+ const content = await fs.readFile(resolvedPath, 'utf-8');
68
+ const parsed = JSON.parse(content);
69
+ settings = isPlainObject(parsed) ? parsed : {};
70
+ } catch (err) {
71
+ if (err.code !== 'ENOENT' && log) {
72
+ await log(`⚠️ Could not read ${resolvedPath}: ${err.message}`, { verbose: true });
73
+ }
74
+ settings = {};
75
+ }
76
+
77
+ const updatedSettingsKeys = [];
78
+ for (const [key, value] of Object.entries(REQUIRED_CLAUDE_QUIET_SETTINGS)) {
79
+ if (settings[key] !== value) {
80
+ settings[key] = value;
81
+ updatedSettingsKeys.push(key);
82
+ }
83
+ }
84
+
85
+ const existingAttribution = isPlainObject(settings.attribution) ? settings.attribution : {};
86
+ const updatedAttributionKeys = [];
87
+ for (const [key, value] of Object.entries(REQUIRED_CLAUDE_QUIET_ATTRIBUTION)) {
88
+ if (existingAttribution[key] !== value) {
89
+ existingAttribution[key] = value;
90
+ updatedAttributionKeys.push(key);
91
+ }
92
+ }
93
+ settings.attribution = existingAttribution;
94
+ if (updatedAttributionKeys.length > 0) {
95
+ updatedSettingsKeys.push('attribution');
96
+ }
97
+
98
+ const existingPermissions = isPlainObject(settings.permissions) ? settings.permissions : {};
99
+ const updatedPermissionsKeys = [];
100
+ for (const [key, value] of Object.entries(REQUIRED_CLAUDE_QUIET_PERMISSIONS)) {
101
+ if (existingPermissions[key] !== value) {
102
+ existingPermissions[key] = value;
103
+ updatedPermissionsKeys.push(key);
104
+ }
105
+ }
106
+ settings.permissions = existingPermissions;
107
+ if (updatedPermissionsKeys.length > 0) {
108
+ updatedSettingsKeys.push('permissions');
109
+ }
110
+
111
+ const existingEnv = isPlainObject(settings.env) ? settings.env : {};
112
+ const updatedEnvKeys = [];
113
+ for (const [key, value] of Object.entries(REQUIRED_CLAUDE_QUIET_ENV)) {
114
+ if (existingEnv[key] !== value) {
115
+ existingEnv[key] = value;
116
+ updatedEnvKeys.push(key);
117
+ }
118
+ }
119
+ settings.env = existingEnv;
120
+
121
+ const changed = updatedSettingsKeys.length > 0 || updatedEnvKeys.length > 0;
122
+ try {
123
+ if (changed) {
124
+ await fs.mkdir(path.dirname(resolvedPath), { recursive: true });
125
+ await fs.writeFile(resolvedPath, JSON.stringify(settings, null, 2));
126
+ }
127
+ if (log) {
128
+ await log(`🧭 Claude Code quiet config ${changed ? 'updated' : 'verified'} at ${resolvedPath}: ${formatClaudeQuietConfigSummary()}`);
129
+ }
130
+ } catch (err) {
131
+ if (log) await log(`⚠️ Could not write ${resolvedPath}: ${err.message}`, { verbose: true });
132
+ }
133
+
134
+ return {
135
+ path: resolvedPath,
136
+ changed,
137
+ updatedSettingsKeys,
138
+ updatedEnvKeys,
139
+ updatedAttributionKeys,
140
+ updatedPermissionsKeys,
141
+ settings: { ...REQUIRED_CLAUDE_QUIET_SETTINGS },
142
+ attribution: { ...REQUIRED_CLAUDE_QUIET_ATTRIBUTION },
143
+ permissions: { ...REQUIRED_CLAUDE_QUIET_PERMISSIONS },
144
+ env: { ...REQUIRED_CLAUDE_QUIET_ENV },
145
+ };
146
+ };
@@ -21,6 +21,7 @@ import { handleClaudeRuntimeSwitch } from './claude.runtime-switch.lib.mjs'; //
21
21
  import { CLAUDE_MODELS as availableModels } from './models/index.mjs'; // Issue #1221
22
22
  import { buildMcpConfigWithoutPlaywright } from './playwright-mcp.lib.mjs';
23
23
  import { resolveClaudeSessionToolFlags } from './useless-tools.lib.mjs';
24
+ import { ensureClaudeQuietConfig } from './claude-quiet-config.lib.mjs';
24
25
  import { fetchModelInfo } from './model-info.lib.mjs';
25
26
  export { availableModels }; // Re-export for backward compatibility
26
27
  export { fetchModelInfo };
@@ -725,6 +726,7 @@ export const executeClaudeCommand = async params => {
725
726
  await log(`🔄 Resuming from session: ${argv.resume}`);
726
727
  claudeArgs = `--resume ${argv.resume} ${claudeArgs}`;
727
728
  }
729
+ await ensureClaudeQuietConfig({ log });
728
730
  const { mcpConfigPath, disallowedToolsList } = await resolveClaudeSessionToolFlags({ argv, log, fallbackBuildMcpConfigWithoutPlaywright: buildMcpConfigWithoutPlaywright });
729
731
  if (mcpConfigPath) claudeArgs += ` --strict-mcp-config --mcp-config "${mcpConfigPath}"`;
730
732
  if (disallowedToolsList.length) claudeArgs += ` --disallowedTools ${disallowedToolsList.join(' ')}`;
@@ -24,6 +24,7 @@ const getenv = typeof getenvModule === 'function' ? getenvModule : getenvModule.
24
24
 
25
25
  // Use semver package for version comparison (see issue #1146)
26
26
  import semver from 'semver';
27
+ import { buildClaudeQuietEnv } from './claude-quiet-config.lib.mjs';
27
28
 
28
29
  // Import lino for parsing Links Notation format
29
30
  const { lino } = await import('./lino.lib.mjs');
@@ -419,14 +420,14 @@ export const getClaudeEnv = (options = {}) => {
419
420
  // Get max output tokens based on model (Issue #1221)
420
421
  const maxOutputTokens = options.model ? getMaxOutputTokensForModel(options.model) : claudeCode.maxOutputTokens;
421
422
 
422
- const env = {
423
+ const env = buildClaudeQuietEnv({
423
424
  ...process.env,
424
425
  CLAUDE_CODE_MAX_OUTPUT_TOKENS: String(maxOutputTokens),
425
426
  // MCP timeout configurations to prevent tool calls from hanging indefinitely
426
427
  // See: https://github.com/link-assistant/hive-mind/issues/1066
427
428
  MCP_TIMEOUT: String(claudeCode.mcpTimeout),
428
429
  MCP_TOOL_TIMEOUT: String(claudeCode.mcpToolTimeout),
429
- };
430
+ });
430
431
 
431
432
  // Opus 4.7+ always uses adaptive thinking — MAX_THINKING_TOKENS has no effect (Issue #1620)
432
433
  // For Opus 4.6 and earlier, MAX_THINKING_TOKENS controls extended thinking (Claude Code >= 2.1.12)
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Shared runner for the `configure-claude` bin command. Applies or verifies
5
+ * the quiet Claude Code defaults in a target `settings.json` by reusing the
6
+ * canonical maps and idempotent merge helpers from:
7
+ * - src/claude-quiet-config.lib.mjs (quiet env + settings + attribution
8
+ * + permissions)
9
+ * - src/useless-tools.lib.mjs (disallowedTools block-list)
10
+ *
11
+ * See issues #1627 and #1642.
12
+ */
13
+
14
+ import fs from 'node:fs/promises';
15
+ import os from 'node:os';
16
+ import path from 'node:path';
17
+
18
+ import { REQUIRED_CLAUDE_QUIET_ENV, REQUIRED_CLAUDE_QUIET_SETTINGS, REQUIRED_CLAUDE_QUIET_ATTRIBUTION, REQUIRED_CLAUDE_QUIET_PERMISSIONS, ensureClaudeQuietConfig } from './claude-quiet-config.lib.mjs';
19
+ import { buildDisallowedToolsList, ensureDisallowedToolsInSettings } from './useless-tools.lib.mjs';
20
+
21
+ export const resolveSettingsPath = settingsPath => settingsPath || path.join(os.homedir(), '.claude', 'settings.json');
22
+
23
+ export const parseConfigureClaudeArgs = argv => {
24
+ const args = { settingsPath: null, verify: false, help: false };
25
+ for (let i = 0; i < argv.length; i++) {
26
+ const arg = argv[i];
27
+ if (arg === '--settings-path' || arg === '-s') {
28
+ args.settingsPath = argv[++i];
29
+ } else if (arg.startsWith('--settings-path=')) {
30
+ args.settingsPath = arg.slice('--settings-path='.length);
31
+ } else if (arg === '--verify') {
32
+ args.verify = true;
33
+ } else if (arg === '--help' || arg === '-h') {
34
+ args.help = true;
35
+ }
36
+ }
37
+ return args;
38
+ };
39
+
40
+ export const CONFIGURE_CLAUDE_HELP = `Usage: configure-claude [options]
41
+
42
+ Apply or verify Hive-Mind's quiet, deterministic Claude Code defaults in
43
+ a target ~/.claude/settings.json (env vars, settings, attribution,
44
+ permissions.defaultMode, and the disallowedTools block-list).
45
+
46
+ Options:
47
+ -s, --settings-path <path> Path to settings.json (default: ~/.claude/settings.json)
48
+ --verify Report configuration status without writing; exit 1 if incorrect
49
+ -h, --help Show this help and exit
50
+
51
+ Examples:
52
+ configure-claude # apply defaults to ~/.claude/settings.json
53
+ configure-claude --verify # check only, non-zero exit if drift detected
54
+ configure-claude -s /workspace/.claude/settings.json
55
+
56
+ Reference: https://github.com/link-assistant/hive-mind/issues/1642
57
+ `;
58
+
59
+ const isPlainObject = value => value && typeof value === 'object' && !Array.isArray(value);
60
+
61
+ const readSettings = async settingsPath => {
62
+ try {
63
+ const content = await fs.readFile(settingsPath, 'utf-8');
64
+ const parsed = JSON.parse(content);
65
+ return isPlainObject(parsed) ? parsed : {};
66
+ } catch (err) {
67
+ if (err.code === 'ENOENT') return null;
68
+ throw err;
69
+ }
70
+ };
71
+
72
+ export const verifyConfigureClaude = async ({ settingsPath } = {}) => {
73
+ const resolvedPath = resolveSettingsPath(settingsPath);
74
+ const settings = await readSettings(resolvedPath);
75
+ const missing = {
76
+ file: settings === null,
77
+ settings: [],
78
+ env: [],
79
+ attribution: [],
80
+ permissions: [],
81
+ disallowedTools: [],
82
+ };
83
+ const current = settings || {};
84
+ for (const [key, value] of Object.entries(REQUIRED_CLAUDE_QUIET_SETTINGS)) {
85
+ if (current[key] !== value) missing.settings.push(key);
86
+ }
87
+ const envSection = isPlainObject(current.env) ? current.env : {};
88
+ for (const [key, value] of Object.entries(REQUIRED_CLAUDE_QUIET_ENV)) {
89
+ if (envSection[key] !== value) missing.env.push(key);
90
+ }
91
+ const attributionSection = isPlainObject(current.attribution) ? current.attribution : {};
92
+ for (const [key, value] of Object.entries(REQUIRED_CLAUDE_QUIET_ATTRIBUTION)) {
93
+ if (attributionSection[key] !== value) missing.attribution.push(key);
94
+ }
95
+ const permissionsSection = isPlainObject(current.permissions) ? current.permissions : {};
96
+ for (const [key, value] of Object.entries(REQUIRED_CLAUDE_QUIET_PERMISSIONS)) {
97
+ if (permissionsSection[key] !== value) missing.permissions.push(key);
98
+ }
99
+ const existingDisallowed = Array.isArray(current.disallowedTools) ? current.disallowedTools : [];
100
+ for (const required of buildDisallowedToolsList()) {
101
+ if (!existingDisallowed.includes(required)) missing.disallowedTools.push(required);
102
+ }
103
+ const ok = !missing.file && missing.settings.length === 0 && missing.env.length === 0 && missing.attribution.length === 0 && missing.permissions.length === 0 && missing.disallowedTools.length === 0;
104
+ return { ok, path: resolvedPath, missing };
105
+ };
106
+
107
+ export const runConfigureClaude = async ({ settingsPath, log } = {}) => {
108
+ const resolvedPath = resolveSettingsPath(settingsPath);
109
+ const logger = log || (async line => console.log(line));
110
+ const quietResult = await ensureClaudeQuietConfig({ settingsPath: resolvedPath, log: logger });
111
+ const disallowedResult = await ensureDisallowedToolsInSettings({ settingsPath: resolvedPath, log: logger });
112
+ return { quietResult, disallowedResult, path: resolvedPath };
113
+ };
114
+
115
+ export const formatVerifyReport = ({ ok, path: resolvedPath, missing }) => {
116
+ if (ok) {
117
+ return `✅ Quiet Claude Code configuration is up to date in ${resolvedPath}`;
118
+ }
119
+ const sections = [];
120
+ if (missing.file) sections.push(' - settings.json missing');
121
+ if (missing.settings.length) sections.push(` - settings: ${missing.settings.join(', ')}`);
122
+ if (missing.env.length) sections.push(` - env: ${missing.env.join(', ')}`);
123
+ if (missing.attribution.length) sections.push(` - attribution: ${missing.attribution.join(', ')}`);
124
+ if (missing.permissions.length) sections.push(` - permissions: ${missing.permissions.join(', ')}`);
125
+ if (missing.disallowedTools.length) sections.push(` - disallowedTools: ${missing.disallowedTools.join(', ')}`);
126
+ return `❌ Quiet Claude Code configuration drift detected in ${resolvedPath}\n${sections.join('\n')}`;
127
+ };
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * `configure-claude` — reusable bin that resets Hive-Mind's quiet,
5
+ * deterministic Claude Code defaults (env, settings, attribution,
6
+ * permissions.defaultMode, and the disallowedTools block-list) in a
7
+ * target `settings.json`, or verifies that they are already in place.
8
+ *
9
+ * Users and system administrators can run this manually after installing
10
+ * `@link-assistant/hive-mind` to reset Claude Code configuration. Docker
11
+ * images invoke this published bin after npm release, so the image baseline
12
+ * stays in lock-step with the package users install.
13
+ *
14
+ * See issues #1627 and #1642.
15
+ */
16
+
17
+ import { CONFIGURE_CLAUDE_HELP, formatVerifyReport, parseConfigureClaudeArgs, resolveSettingsPath, runConfigureClaude, verifyConfigureClaude } from './configure-claude.lib.mjs';
18
+
19
+ const args = parseConfigureClaudeArgs(process.argv.slice(2));
20
+
21
+ if (args.help) {
22
+ console.log(CONFIGURE_CLAUDE_HELP);
23
+ process.exit(0);
24
+ }
25
+
26
+ const settingsPath = resolveSettingsPath(args.settingsPath);
27
+
28
+ if (args.verify) {
29
+ const report = await verifyConfigureClaude({ settingsPath });
30
+ console.log(formatVerifyReport(report));
31
+ process.exit(report.ok ? 0 : 1);
32
+ }
33
+
34
+ const { quietResult, disallowedResult } = await runConfigureClaude({ settingsPath });
35
+ console.log(`Configured quiet Claude Code defaults and ${disallowedResult.total} disallowedTools in ${quietResult.path}`);