@@ -194,6 +194,7 @@ function main() {
194194 fi
195195
196196 # Auto-enable rust, typescript, and reactjs if tauri is specified
197+ # shellcheck disable=SC2034
197198 if is-arg-true " ${tauri:- false} " ; then
198199 rust=true
199200 typescript=true
@@ -255,6 +256,7 @@ function main() {
255256 copy-docs-prompts " ${destination} "
256257 copy-workspace-file " ${destination} "
257258 update-gitignore " ${destination} "
259+ update-vscode-settings " ${destination} "
258260
259261 echo
260262 echo " Done. Assets copied to ${destination} "
@@ -318,7 +320,7 @@ function wipe-directories() {
318320 for dir in " ${dirs[@]} " ; do
319321 if [[ -d " ${dest} /${dir} " ]]; then
320322 print-info " Removing ${dest} /${dir} "
321- rm -rf " ${dest} /${dir} "
323+ rm -rf " ${dest:? } /${dir} "
322324 fi
323325 done
324326}
@@ -585,8 +587,6 @@ function copy-docs-prompts() {
585587
586588 print-info " Copying docs/prompts to ${dest} /prompts"
587589 cp -R " ${DOCS_PROMPTS} /." " ${dest} /prompts/"
588- print-info " Creating docs/.gitignore"
589- echo " prompts" > " ${dest} /.gitignore"
590590}
591591
592592# Copy project.code-workspace to the destination if it does not already exist.
@@ -660,6 +660,178 @@ function update-gitignore() {
660660 return 0
661661}
662662
663+ # Update .vscode/settings.json with promptfiles settings.
664+ # Arguments (provided as function parameters):
665+ # $1=[destination directory path]
666+ function update-vscode-settings() {
667+
668+ local dest=" $1 "
669+ local settings_dir=" ${dest} /.vscode"
670+ local settings_file=" ${settings_dir} /settings.json"
671+
672+ mkdir -p " ${settings_dir} "
673+
674+ if [[ ! -f " ${settings_file} " || ! -s " ${settings_file} " ]]; then
675+ print-info " Creating VS Code settings: ${settings_file} "
676+ cat << 'EOF ' > "${settings_file}"
677+ {
678+ "chat.promptFilesRecommendations": {
679+ "speckit.constitution": true,
680+ "speckit.specify": true,
681+ "speckit.plan": true,
682+ "speckit.tasks": true,
683+ "speckit.implement": true
684+ },
685+ "chat.tools.terminal.autoApprove": {
686+ ".specify/scripts/bash/": true
687+ }
688+ }
689+ EOF
690+ return 0
691+ fi
692+
693+ # Always remove existing sections before adding them back
694+ remove-vscode-json-property " ${settings_file} " " chat.promptFilesRecommendations"
695+ remove-vscode-json-property " ${settings_file} " " chat.tools.terminal.autoApprove"
696+
697+ # Prepare content to insert (always both sections)
698+ local insert_file
699+ insert_file=$( mktemp)
700+
701+ cat << 'EOF ' >> "${insert_file}"
702+ "chat.promptFilesRecommendations": {
703+ "speckit.constitution": true,
704+ "speckit.specify": true,
705+ "speckit.plan": true,
706+ "speckit.tasks": true,
707+ "speckit.implement": true
708+ },
709+ "chat.tools.terminal.autoApprove": {
710+ ".specify/scripts/bash/": true
711+ }
712+ EOF
713+
714+ local last_brace_line
715+ last_brace_line=$( awk ' /}/ { line=NR } END { print line }' " ${settings_file} " )
716+
717+ if [[ -z " ${last_brace_line} " ]]; then
718+ rm -f " ${insert_file} "
719+ print-error " Invalid ${settings_file} : missing closing brace"
720+ fi
721+
722+ local prev_line_num
723+ prev_line_num=$( awk -v last=" ${last_brace_line} " ' NR < last { if ($0 ~ /[^[:space:]]/) { line=NR } } END { print line }' " ${settings_file} " )
724+
725+ local needs_comma=0
726+ if [[ -n " ${prev_line_num} " ]]; then
727+ local prev_line
728+ prev_line=$( sed -n " ${prev_line_num} p" " ${settings_file} " )
729+ if [[ " ${prev_line} " =~ ^[[:space:]]* \{ [[:space:]]* $ ]]; then
730+ needs_comma=0
731+ elif [[ " ${prev_line} " =~ ,[[:space:]]* $ ]]; then
732+ needs_comma=0
733+ else
734+ needs_comma=1
735+ fi
736+ fi
737+
738+ local temp_file
739+ temp_file=$( mktemp)
740+
741+ awk -v insert_file=" ${insert_file} " -v insert_line=" ${last_brace_line} " -v prev_line=" ${prev_line_num} " -v needs_comma=" ${needs_comma} " '
742+ NR == prev_line && needs_comma == 1 {
743+ sub(/[[:space:]]*$/, "", $0)
744+ print $0 ","
745+ next
746+ }
747+ NR == insert_line {
748+ while ((getline line < insert_file) > 0) { print line }
749+ close(insert_file)
750+ print $0
751+ next
752+ }
753+ { print }
754+ ' " ${settings_file} " > " ${temp_file} "
755+
756+ mv " ${temp_file} " " ${settings_file} "
757+ rm -f " ${insert_file} "
758+
759+ print-info " Updated VS Code settings: ${settings_file} "
760+
761+ return 0
762+ }
763+
764+ # Remove a JSON property from a VS Code settings file.
765+ # Arguments (provided as function parameters):
766+ # $1=[settings file path]
767+ # $2=[property name without quotes]
768+ function remove-vscode-json-property() {
769+
770+ local file=" $1 "
771+ local property=" $2 "
772+
773+ # If property doesn't exist, nothing to do
774+ if ! grep -q " \" ${property} \" " " $file " 2> /dev/null; then
775+ return 0
776+ fi
777+
778+ local temp_file
779+ temp_file=$( mktemp)
780+
781+ awk -v prop=" \" ${property} \" " '
782+ BEGIN { skip = 0; depth = 0; skip_comma = 0 }
783+ {
784+ # Check if this line starts the property we want to remove
785+ if (!skip && match($0, prop "[[:space:]]*:[[:space:]]*\\{")) {
786+ skip = 1
787+ depth = 0
788+ # Count braces on the same line
789+ rest_of_line = substr($0, RSTART + RLENGTH)
790+ for (i = 1; i <= length(rest_of_line); i++) {
791+ c = substr(rest_of_line, i, 1)
792+ if (c == "{") depth++
793+ else if (c == "}") depth--
794+ }
795+ # If property closes on same line, stop skipping
796+ if (depth < 0) {
797+ skip = 0
798+ skip_comma = 1
799+ }
800+ next
801+ }
802+
803+ # While inside the property, track brace depth
804+ if (skip) {
805+ for (i = 1; i <= length($0); i++) {
806+ c = substr($0, i, 1)
807+ if (c == "{") depth++
808+ else if (c == "}") depth--
809+ }
810+ # When depth goes negative, we have closed the property
811+ if (depth < 0) {
812+ skip = 0
813+ skip_comma = 1
814+ }
815+ next
816+ }
817+
818+ # Skip a trailing comma line if needed
819+ if (skip_comma) {
820+ skip_comma = 0
821+ if ($0 ~ /^[[:space:]]*,[[:space:]]*$/) {
822+ next
823+ }
824+ }
825+
826+ print
827+ }
828+ ' " $file " > " $temp_file "
829+
830+ mv " $temp_file " " $file "
831+
832+ return 0
833+ }
834+
663835# Copy a directory without bringing across any nested .git metadata.
664836# Arguments (provided as function parameters):
665837# $1=[source directory path]
0 commit comments