@enterpriseai/cli 3.15.9 → 3.15.11

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 (37) hide show
  1. package/README.md +45 -11
  2. package/dist/commands/classifier.d.ts.map +1 -1
  3. package/dist/commands/classifier.js +4 -1
  4. package/dist/commands/classifier.js.map +1 -1
  5. package/dist/commands/docs.d.ts.map +1 -1
  6. package/dist/commands/docs.js +88 -31
  7. package/dist/commands/docs.js.map +1 -1
  8. package/dist/commands/init.d.ts +1 -1
  9. package/dist/commands/init.d.ts.map +1 -1
  10. package/dist/commands/init.js +15 -10
  11. package/dist/commands/init.js.map +1 -1
  12. package/dist/commands/start.d.ts +15 -0
  13. package/dist/commands/start.d.ts.map +1 -1
  14. package/dist/commands/start.js +111 -29
  15. package/dist/commands/start.js.map +1 -1
  16. package/dist/lib/agent-guide.js +2 -2
  17. package/dist/lib/agent-guide.js.map +1 -1
  18. package/dist/lib/ai-surface-installer.d.ts +38 -0
  19. package/dist/lib/ai-surface-installer.d.ts.map +1 -0
  20. package/dist/lib/ai-surface-installer.js +160 -0
  21. package/dist/lib/ai-surface-installer.js.map +1 -0
  22. package/dist/lib/ai-surfaces.d.ts +257 -6
  23. package/dist/lib/ai-surfaces.d.ts.map +1 -1
  24. package/dist/lib/ai-surfaces.js +3182 -170
  25. package/dist/lib/ai-surfaces.js.map +1 -1
  26. package/dist/lib/api.d.ts +16 -2
  27. package/dist/lib/api.d.ts.map +1 -1
  28. package/dist/lib/api.js +43 -8
  29. package/dist/lib/api.js.map +1 -1
  30. package/dist/lib/gofer-installer.js +1 -1
  31. package/package.json +2 -2
  32. package/resources/gofer/.gofer-version +17 -1
  33. package/resources/gofer/bash-scripts/install-optional-tools.sh +357 -16
  34. package/resources/gofer/bash-scripts/sync-implementation-status.sh +3 -4
  35. package/resources/gofer/powershell-scripts/install-optional-tools.ps1 +419 -11
  36. package/resources/gofer/references/platform/eai-service-patterns.md +84 -1
  37. package/resources/gofer/templates/visuals/capability-heatmap-template.md +2 -2
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bash
2
2
 
3
- set -u
3
+ set -uo pipefail
4
4
 
5
5
  WORKSPACE_PATH=""
6
6
  TOOLS_CSV=""
@@ -8,16 +8,24 @@ TOOLS_CSV=""
8
8
  while [[ $# -gt 0 ]]; do
9
9
  case "$1" in
10
10
  --workspace-path)
11
- WORKSPACE_PATH="${2:-}"
11
+ if [[ $# -lt 2 || "$2" == --* ]]; then
12
+ echo "Missing value for --workspace-path." >&2
13
+ exit 64
14
+ fi
15
+ WORKSPACE_PATH="$2"
12
16
  shift 2
13
17
  ;;
14
18
  --tools)
15
- TOOLS_CSV="${2:-}"
19
+ if [[ $# -lt 2 || "$2" == --* ]]; then
20
+ echo "Missing value for --tools." >&2
21
+ exit 64
22
+ fi
23
+ TOOLS_CSV="$2"
16
24
  shift 2
17
25
  ;;
18
26
  *)
19
- echo "Unknown argument: $1" >&2
20
- shift
27
+ echo "Unknown argument. Expected --workspace-path or --tools." >&2
28
+ exit 64
21
29
  ;;
22
30
  esac
23
31
  done
@@ -36,6 +44,43 @@ IFS=',' read -r -a TOOLS <<< "$TOOLS_CSV"
36
44
  REPO_ROOT="$WORKSPACE_PATH"
37
45
 
38
46
  HAS_FAILURE=0
47
+ TEMP_INSTALLERS=()
48
+ ACTIVE_INSTALLER_PID=""
49
+ INSTALLER_TIMEOUT_SECONDS=900
50
+
51
+ terminate_process_tree() {
52
+ local target_pid="$1"
53
+ local signal_name="$2"
54
+ local child_pid
55
+
56
+ [[ "$target_pid" =~ ^[1-9][0-9]*$ ]] || return 0
57
+ if [[ -x /usr/bin/pgrep ]]; then
58
+ while IFS= read -r child_pid; do
59
+ if [[ "$child_pid" =~ ^[1-9][0-9]*$ ]]; then
60
+ terminate_process_tree "$child_pid" "$signal_name"
61
+ fi
62
+ done < <(/usr/bin/pgrep -P "$target_pid" 2>/dev/null || true)
63
+ fi
64
+ kill "-$signal_name" "$target_pid" 2>/dev/null || true
65
+ }
66
+
67
+ cleanup_temp_installers() {
68
+ local installer_path
69
+ if [[ -n "$ACTIVE_INSTALLER_PID" ]]; then
70
+ terminate_process_tree "$ACTIVE_INSTALLER_PID" TERM
71
+ terminate_process_tree "$ACTIVE_INSTALLER_PID" KILL
72
+ fi
73
+ for installer_path in "${TEMP_INSTALLERS[@]-}"; do
74
+ if [[ -n "$installer_path" ]]; then
75
+ rm -f -- "$installer_path"
76
+ fi
77
+ done
78
+ }
79
+
80
+ trap cleanup_temp_installers EXIT
81
+ trap 'exit 129' HUP
82
+ trap 'exit 130' INT
83
+ trap 'exit 143' TERM
39
84
 
40
85
  log_info() {
41
86
  printf '[gofer] %s\n' "$1"
@@ -139,6 +184,238 @@ install_with_npm_global() {
139
184
  run_command "Installing $package_name globally with npm" npm install --global "$package_name"
140
185
  }
141
186
 
187
+ validate_installed_cli() {
188
+ local tool_name="$1"
189
+ local command_path="$2"
190
+ if [[ ! -f "$command_path" || ! -x "$command_path" ]]; then
191
+ HAS_FAILURE=1
192
+ log_error "$tool_name installation did not produce its expected executable"
193
+ return 1
194
+ fi
195
+ log_info "$tool_name installer produced its provider-defined executable"
196
+ }
197
+
198
+ install_pinned_copilot_cli() {
199
+ local package_spec='@github/copilot@1.0.83'
200
+ local expected_integrity='sha512-M8uZI0V0dahYV1KZij3nGDxaXEGG7I7YUZzQPI7NEZkL/83Nl/tNTbPdxKtdWZbOmWoXsPKXty/eEYoj6RHDhA=='
201
+ local registry='https://registry.npmjs.org/'
202
+ local npm_path
203
+ npm_path="$(type -P npm 2>/dev/null || true)"
204
+ if [[ -z "$npm_path" || ! -f "$npm_path" || ! -x "$npm_path" ]]; then
205
+ HAS_FAILURE=1
206
+ log_warn "npm is required to install $package_spec globally"
207
+ return 1
208
+ fi
209
+
210
+ log_info "Verifying the pinned GitHub Copilot CLI package"
211
+ local actual_integrity
212
+ if ! actual_integrity="$("$npm_path" view "$package_spec" dist.integrity --json --registry="$registry" 2>/dev/null)"; then
213
+ HAS_FAILURE=1
214
+ log_error "Could not resolve the pinned GitHub Copilot CLI package"
215
+ return 1
216
+ fi
217
+ actual_integrity="${actual_integrity#\"}"
218
+ actual_integrity="${actual_integrity%\"}"
219
+ if [[ "$actual_integrity" != "$expected_integrity" ]]; then
220
+ HAS_FAILURE=1
221
+ log_error "Refusing GitHub Copilot CLI because its npm integrity changed"
222
+ return 1
223
+ fi
224
+
225
+ log_info "Installing the pinned GitHub Copilot CLI"
226
+ if ! "$npm_path" install --global "$package_spec" --registry="$registry" \
227
+ --ignore-scripts --no-audit --no-fund >/dev/null 2>&1; then
228
+ HAS_FAILURE=1
229
+ log_error "Failed: Installing the pinned GitHub Copilot CLI"
230
+ return 1
231
+ fi
232
+ if ! "$npm_path" list --global --depth=0 "$package_spec" >/dev/null 2>&1; then
233
+ HAS_FAILURE=1
234
+ log_error "The installed GitHub Copilot CLI package did not match the pinned version"
235
+ return 1
236
+ fi
237
+
238
+ local npm_prefix
239
+ npm_prefix="$("$npm_path" prefix --global 2>/dev/null || true)"
240
+ case "$npm_prefix" in
241
+ /*) ;;
242
+ *)
243
+ HAS_FAILURE=1
244
+ log_error "Could not resolve an absolute npm prefix for GitHub Copilot CLI"
245
+ return 1
246
+ ;;
247
+ esac
248
+ validate_installed_cli "GitHub Copilot CLI" "$npm_prefix/bin/copilot"
249
+ }
250
+
251
+ compute_sha256() {
252
+ local target_path="$1"
253
+
254
+ if [[ -x /usr/bin/shasum ]]; then
255
+ /usr/bin/shasum -a 256 "$target_path" | /usr/bin/awk '{print $1}'
256
+ return
257
+ fi
258
+
259
+ if [[ -x /usr/bin/sha256sum ]]; then
260
+ /usr/bin/sha256sum "$target_path" | /usr/bin/awk '{print $1}'
261
+ return
262
+ fi
263
+
264
+ if [[ -x /usr/bin/openssl ]]; then
265
+ /usr/bin/openssl dgst -sha256 "$target_path" | /usr/bin/awk '{print $NF}'
266
+ return
267
+ fi
268
+
269
+ return 1
270
+ }
271
+
272
+ run_sanitized_installer() {
273
+ local shell_name="$1"
274
+ local installer_fd_path="$2"
275
+ local shell_path
276
+ case "$shell_name" in
277
+ bash) shell_path=/bin/bash ;;
278
+ sh) shell_path=/bin/sh ;;
279
+ *)
280
+ log_error "Refusing an unsupported installer interpreter"
281
+ return 1
282
+ ;;
283
+ esac
284
+ [[ -x "$shell_path" ]] || {
285
+ log_error "The required installer interpreter is unavailable"
286
+ return 1
287
+ }
288
+
289
+ /usr/bin/env -i \
290
+ HOME="${HOME:-}" \
291
+ USER="${USER:-}" \
292
+ LOGNAME="${LOGNAME:-}" \
293
+ SHELL="${SHELL:-/bin/sh}" \
294
+ PATH="/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/homebrew/bin" \
295
+ TMPDIR="${TMPDIR:-/tmp}" \
296
+ LANG="${LANG:-C}" \
297
+ LC_ALL="${LC_ALL:-}" \
298
+ XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-}" \
299
+ XDG_DATA_HOME="${XDG_DATA_HOME:-}" \
300
+ XDG_CACHE_HOME="${XDG_CACHE_HOME:-}" \
301
+ CODEX_NON_INTERACTIVE=1 \
302
+ "$shell_path" "$installer_fd_path" &
303
+ ACTIVE_INSTALLER_PID=$!
304
+
305
+ local deadline=$((SECONDS + INSTALLER_TIMEOUT_SECONDS))
306
+ while kill -0 "$ACTIVE_INSTALLER_PID" 2>/dev/null; do
307
+ if (( SECONDS >= deadline )); then
308
+ terminate_process_tree "$ACTIVE_INSTALLER_PID" TERM
309
+ /bin/sleep 2
310
+ terminate_process_tree "$ACTIVE_INSTALLER_PID" KILL
311
+ wait "$ACTIVE_INSTALLER_PID" 2>/dev/null || true
312
+ ACTIVE_INSTALLER_PID=""
313
+ log_error "The provider installer exceeded its bounded execution time"
314
+ return 124
315
+ fi
316
+ /bin/sleep 1
317
+ done
318
+
319
+ local installer_status=0
320
+ wait "$ACTIVE_INSTALLER_PID" || installer_status=$?
321
+ ACTIVE_INSTALLER_PID=""
322
+ return "$installer_status"
323
+ }
324
+
325
+ install_from_official_script() {
326
+ local tool_name="$1"
327
+ local script_url="$2"
328
+ local shell_name="$3"
329
+ local expected_sha256="$4"
330
+ local allowed_final_origin="$5"
331
+
332
+ if [[ ! -x /usr/bin/curl ]]; then
333
+ HAS_FAILURE=1
334
+ log_warn "curl is required to install $tool_name from its official provider"
335
+ return 1
336
+ fi
337
+
338
+ local installer_path
339
+ installer_path="$(/usr/bin/mktemp "${TMPDIR:-/tmp}/gofer-installer.XXXXXX" 2>/dev/null)" || {
340
+ HAS_FAILURE=1
341
+ log_error "Could not create a temporary installer for $tool_name"
342
+ return 1
343
+ }
344
+ TEMP_INSTALLERS+=("$installer_path")
345
+
346
+ log_info "Installing or updating $tool_name from $script_url"
347
+ local effective_url
348
+ if ! effective_url="$(/usr/bin/curl --fail --silent --show-error --location \
349
+ --proto '=https' --proto-redir '=https' --tlsv1.2 \
350
+ --max-redirs 5 --connect-timeout 15 --max-time 120 --max-filesize 1048576 \
351
+ --output "$installer_path" --write-out '%{url_effective}' "$script_url" 2>/dev/null)"; then
352
+ HAS_FAILURE=1
353
+ log_error "Failed to download the official $tool_name installer"
354
+ rm -f -- "$installer_path"
355
+ return 1
356
+ fi
357
+
358
+ case "$effective_url" in
359
+ "$allowed_final_origin"|"$allowed_final_origin"/*)
360
+ ;;
361
+ *)
362
+ HAS_FAILURE=1
363
+ log_error "Refusing $tool_name installer from an unexpected redirect origin"
364
+ rm -f -- "$installer_path"
365
+ return 1
366
+ ;;
367
+ esac
368
+
369
+ if [[ ! -s "$installer_path" ]]; then
370
+ HAS_FAILURE=1
371
+ log_error "Refusing an empty $tool_name installer"
372
+ rm -f -- "$installer_path"
373
+ return 1
374
+ fi
375
+
376
+ # Open two independent descriptors, then unlink the pathname. The hash and
377
+ # interpreter now consume the same unreachable inode without a path-based
378
+ # replacement window between verification and execution.
379
+ if ! exec 8<"$installer_path" || ! exec 9<"$installer_path"; then
380
+ HAS_FAILURE=1
381
+ log_error "Could not secure the downloaded $tool_name installer"
382
+ rm -f -- "$installer_path"
383
+ exec 8<&- 2>/dev/null || true
384
+ exec 9<&- 2>/dev/null || true
385
+ return 1
386
+ fi
387
+ rm -f -- "$installer_path"
388
+
389
+ local actual_sha256
390
+ if ! actual_sha256="$(compute_sha256 /dev/fd/8 2>/dev/null)"; then
391
+ HAS_FAILURE=1
392
+ log_error "No supported SHA-256 tool is available to verify $tool_name"
393
+ exec 8<&-
394
+ exec 9<&-
395
+ return 1
396
+ fi
397
+ exec 8<&-
398
+
399
+ if [[ "$actual_sha256" != "$expected_sha256" ]]; then
400
+ HAS_FAILURE=1
401
+ log_error "Refusing $tool_name installer because its SHA-256 digest changed"
402
+ exec 9<&-
403
+ return 1
404
+ fi
405
+
406
+ # Provider output is intentionally not relayed: verified installers can still
407
+ # print local paths or inherited account data. Emit only bounded Gofer-owned
408
+ # status messages around the isolated child process.
409
+ if ! run_sanitized_installer "$shell_name" /dev/fd/9 >/dev/null 2>&1; then
410
+ HAS_FAILURE=1
411
+ log_error "Failed: Installing or updating $tool_name"
412
+ exec 9<&-
413
+ return 1
414
+ fi
415
+ exec 9<&-
416
+ return 0
417
+ }
418
+
142
419
  install_gh_cli() {
143
420
  if has_command gh; then
144
421
  log_info "GitHub CLI is already installed"
@@ -191,20 +468,41 @@ install_azure_cli() {
191
468
  fi
192
469
 
193
470
  if has_command apt-get; then
194
- run_command "Installing Azure CLI using Microsoft Debian installer" bash -c "curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash"
471
+ if ! has_command apt-cache || ! apt-cache show azure-cli >/dev/null 2>&1; then
472
+ HAS_FAILURE=1
473
+ log_warn "Azure CLI is unavailable from the configured apt repositories"
474
+ log_warn "Configure Microsoft's signed Azure CLI apt repository, then rerun Gofer"
475
+ return 1
476
+ fi
477
+ run_command "Updating apt package index for Azure CLI" sudo apt-get update
478
+ run_command "Installing Azure CLI with apt-get" sudo apt-get install --yes azure-cli
195
479
  return
196
480
  fi
197
481
 
198
482
  if has_command dnf; then
483
+ local rhel_release
484
+ rhel_release="$(rpm -E %rhel)"
485
+ if [[ ! "$rhel_release" =~ ^[0-9]+$ ]]; then
486
+ HAS_FAILURE=1
487
+ log_warn "Could not determine a safe RHEL release for the Azure CLI feed"
488
+ return
489
+ fi
199
490
  run_command "Registering Microsoft package feed for Azure CLI" sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc
200
- run_command "Installing Microsoft package feed for Azure CLI" bash -c 'sudo dnf install --assumeyes "https://packages.microsoft.com/config/rhel/$(rpm -E %rhel)/packages-microsoft-prod.rpm"'
491
+ run_command "Installing Microsoft package feed for Azure CLI" sudo dnf install --assumeyes "https://packages.microsoft.com/config/rhel/$rhel_release/packages-microsoft-prod.rpm"
201
492
  run_command "Installing Azure CLI with dnf" sudo dnf install --assumeyes azure-cli
202
493
  return
203
494
  fi
204
495
 
205
496
  if has_command yum; then
497
+ local rhel_release
498
+ rhel_release="$(rpm -E %rhel)"
499
+ if [[ ! "$rhel_release" =~ ^[0-9]+$ ]]; then
500
+ HAS_FAILURE=1
501
+ log_warn "Could not determine a safe RHEL release for the Azure CLI feed"
502
+ return
503
+ fi
206
504
  run_command "Registering Microsoft package feed for Azure CLI" sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc
207
- run_command "Installing Microsoft package feed for Azure CLI" bash -c 'sudo yum install --assumeyes "https://packages.microsoft.com/config/rhel/$(rpm -E %rhel)/packages-microsoft-prod.rpm"'
505
+ run_command "Installing Microsoft package feed for Azure CLI" sudo yum install --assumeyes "https://packages.microsoft.com/config/rhel/$rhel_release/packages-microsoft-prod.rpm"
208
506
  run_command "Installing Azure CLI with yum" sudo yum install --assumeyes azure-cli
209
507
  return
210
508
  fi
@@ -213,7 +511,30 @@ install_azure_cli() {
213
511
  log_warn "No supported package manager found to install Azure CLI"
214
512
  }
215
513
 
216
- log_info "Workspace: $REPO_ROOT"
514
+ NORMALIZED_TOOLS=()
515
+ NORMALIZED_TOOLS_CSV=""
516
+ for raw_tool in "${TOOLS[@]}"; do
517
+ normalized_tool="${raw_tool#"${raw_tool%%[![:space:]]*}"}"
518
+ normalized_tool="${normalized_tool%"${normalized_tool##*[![:space:]]}"}"
519
+ case "$normalized_tool" in
520
+ agy|gemini) normalized_tool="antigravity" ;;
521
+ esac
522
+ case "$normalized_tool" in
523
+ "") continue ;;
524
+ *[!a-z0-9-]*) normalized_tool="invalid-tool-id" ;;
525
+ esac
526
+ case ",$NORMALIZED_TOOLS_CSV," in
527
+ *,"$normalized_tool",*) ;;
528
+ *)
529
+ NORMALIZED_TOOLS+=("$normalized_tool")
530
+ NORMALIZED_TOOLS_CSV="${NORMALIZED_TOOLS_CSV:+$NORMALIZED_TOOLS_CSV,}$normalized_tool"
531
+ ;;
532
+ esac
533
+ done
534
+ TOOLS=("${NORMALIZED_TOOLS[@]}")
535
+ TOOLS_CSV="$NORMALIZED_TOOLS_CSV"
536
+
537
+ log_info "Workspace selected"
217
538
  log_info "Selected tools: $TOOLS_CSV"
218
539
 
219
540
  PACKAGE_MANAGER="$(detect_js_package_manager)"
@@ -228,16 +549,35 @@ for tool in "${TOOLS[@]}"; do
228
549
  install_playwright_browsers
229
550
  ;;
230
551
  claude)
231
- install_with_npm_global "@anthropic-ai/claude-code"
552
+ if install_from_official_script "Claude Code" "https://claude.ai/install.sh" bash \
553
+ "3a68d3406cf674e17bed1733a4dcf37805e2e47d87417700007d7e1aa766a944" \
554
+ "https://downloads.claude.ai"; then
555
+ validate_installed_cli "Claude Code" "${HOME:?HOME is required}/.local/bin/claude"
556
+ fi
232
557
  ;;
233
558
  codex)
234
- install_with_npm_global "@openai/codex-cli"
559
+ if install_from_official_script "Codex CLI" "https://chatgpt.com/codex/install.sh" sh \
560
+ "ba92dd27e5c06f0d3bbc58bfa4b9cfb6599cd2742fbb1f92a2765e6c07dedb5a" \
561
+ "https://releases.openai.com"; then
562
+ validate_installed_cli "Codex CLI" "${HOME:?HOME is required}/.local/bin/codex"
563
+ fi
235
564
  ;;
236
565
  copilot)
237
- install_with_npm_global "@github/copilot"
566
+ install_pinned_copilot_cli
567
+ ;;
568
+ antigravity)
569
+ if install_from_official_script "Antigravity CLI (agy)" "https://antigravity.google/cli/install.sh" bash \
570
+ "ee1ea43ce4e9e56356c4ab6dad907ef357ae4bdfcaadb682735909fb57c9c640" \
571
+ "https://antigravity.google"; then
572
+ validate_installed_cli "Antigravity CLI" "${HOME:?HOME is required}/.local/bin/agy"
573
+ fi
238
574
  ;;
239
- gemini)
240
- install_with_npm_global "@google/gemini-cli"
575
+ grok)
576
+ if install_from_official_script "Grok Build" "https://x.ai/cli/install.sh" bash \
577
+ "7fd6fdc75d9418b2e58356726fcbf1ae849416f773925da07d0ccc7a60d3e791" \
578
+ "https://x.ai"; then
579
+ validate_installed_cli "Grok Build" "${HOME:?HOME is required}/.grok/bin/grok"
580
+ fi
241
581
  ;;
242
582
  gh)
243
583
  install_gh_cli
@@ -255,10 +595,11 @@ for tool in "${TOOLS[@]}"; do
255
595
  done
256
596
 
257
597
  log_info "Suggested next steps:"
258
- log_info " claude login"
598
+ log_info " claude auth login"
259
599
  log_info " codex login"
260
600
  log_info " copilot login"
261
- log_info " gemini auth login"
601
+ log_info " agy"
602
+ log_info " grok"
262
603
  log_info " gh auth login"
263
604
  log_info " az login"
264
605
 
@@ -1,8 +1,7 @@
1
1
  #!/bin/bash
2
- # Sync implementation status across feature branches
2
+ # Report implementation status across feature branches
3
3
  #
4
- # This script checks which tasks were actually implemented in git commits
5
- # and updates tasks.md to reflect the actual implementation state.
4
+ # This script reports related commits. It never changes tasks.md.
6
5
  #
7
6
  # Usage: sync-implementation-status.sh <feature-dir>
8
7
  # Example: sync-implementation-status.sh .specify/specs/025-ai-usage-tracking
@@ -37,7 +36,7 @@ fi
37
36
  # This requires reading tasks.md, extracting file paths from task descriptions,
38
37
  # and checking if those files were modified in the commits
39
38
 
40
- echo "✓ Analysis complete"
39
+ echo "✓ Report complete. No task state was changed."
41
40
  echo ""
42
41
  echo "MANUAL STEP REQUIRED:"
43
42
  echo "1. Review git commits for this feature"