Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 6 additions & 9 deletions .rabbit/context.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ generator:
tool: dev.kit
repo: https://github.com/udx/dev.kit
version: 0.10.0
generated_at: 2026-05-13T00:02:14Z
generated_at: 2026-05-13T00:55:54Z

repo:
name: dev.kit
Expand All @@ -22,6 +22,7 @@ refs:
- ./changes.md
- ./Makefile
- ./docs/references/command-surfaces.md
- ./docs/references/repo-design.md
- ./src/configs/archetypes.yaml
- ./src/configs/audit-rules.yaml
- ./src/configs/context-config.yaml
Expand Down Expand Up @@ -69,16 +70,14 @@ gaps:
dependencies:
- repo: udx/reusable-workflows
kind: reusable workflow
resolved: true
archetype: workflow-repo
resolved: false
used_by:
- .github/workflows/context7-ops.yml
- .github/workflows/npm-release-ops.yml
- repo: udx/worker
kind: manifest contract (deploy)
resolved: true
resolved: false
declared_as: udx.io/worker-v1/deploy
archetype: manifest-repo
used_by:
- deploy.yml

Expand Down Expand Up @@ -146,22 +145,20 @@ manifests:
declared_as: udx.io/worker-v1/deploy
source_repo: udx/worker
used_by:
- .rabbit/dev.kit/lessons-dev.kit-2026-04-14.md
- .rabbit/dev.kit/lessons-dev.kit-2026-04-15.md
- Makefile
- docs/references/command-surfaces.md
- docs/references/config-contract-surfaces.md
- docs/repo-contract-boundary.md
- lib/modules/repo_factors.sh
- src/configs/context-config.yaml
- src/configs/detection-signals.yaml
- tests/suite.sh
evidence:
- version: udx.io/worker-v1/deploy
- path reference: .rabbit/dev.kit/lessons-dev.kit-2026-04-14.md
- path reference: .rabbit/dev.kit/lessons-dev.kit-2026-04-15.md
- path reference: Makefile
- path reference: docs/references/command-surfaces.md
- path reference: docs/references/config-contract-surfaces.md
- path reference: docs/repo-contract-boundary.md
- path reference: lib/modules/repo_factors.sh
- path reference: src/configs/context-config.yaml
- path reference: src/configs/detection-signals.yaml
Expand Down
6 changes: 5 additions & 1 deletion lib/commands/repo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,11 @@ EOF
write_status=$?
if [ "$write_status" -ne 0 ]; then
dev_kit_output_section "error"
dev_kit_output_list_item "Context write did not finish within the allowed time"
if [ "$write_status" -eq 124 ]; then
dev_kit_output_list_item "Context write did not finish within the allowed time"
else
dev_kit_output_list_item "Context write failed with exit status $write_status"
fi
return "$write_status"
fi
fi
Expand Down
37 changes: 36 additions & 1 deletion lib/commands/uninstall.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ dev_kit_cmd_uninstall() {
local home="${DEV_KIT_HOME:-$HOME/.udx/dev.kit}"
local yes_mode="false"
local arg=""
local stdout_file=""
local stderr_file=""
local uninstall_status=0
local uninstall_error=""
shift || true

if [ "$format" = "json" ]; then
Expand All @@ -23,7 +27,38 @@ dev_kit_cmd_uninstall() {
return 1
fi

"$REPO_DIR/bin/scripts/uninstall.sh" "$@" >/dev/null
stdout_file="$(mktemp "${TMPDIR:-/tmp}/dev-kit-uninstall-out.XXXXXX")" || {
printf '{ "command": "uninstall", "ok": false, "error": "failed to create temp output file" }\n'
return 1
}
stderr_file="$(mktemp "${TMPDIR:-/tmp}/dev-kit-uninstall-err.XXXXXX")" || {
rm -f "$stdout_file"
printf '{ "command": "uninstall", "ok": false, "error": "failed to create temp error file" }\n'
return 1
}

set +e
"$REPO_DIR/bin/scripts/uninstall.sh" "$@" >"$stdout_file" 2>"$stderr_file"
uninstall_status=$?
set -e

[ -s "$stderr_file" ] && cat "$stderr_file" >&2

if [ "$uninstall_status" -ne 0 ]; then
uninstall_error="$(awk 'NF { print; exit }' "$stderr_file")"
[ -n "$uninstall_error" ] || uninstall_error="$(awk 'NF { print; exit }' "$stdout_file")"
[ -n "$uninstall_error" ] || uninstall_error="uninstall failed"
rm -f "$stdout_file" "$stderr_file"
printf '{ "command": "uninstall", "ok": false, "error": "%s", "binary": "%s", "binary_removed": %s, "home": "%s", "home_removed": %s }\n' \
"$(dev_kit_json_escape "$uninstall_error")" \
"$(dev_kit_json_escape "$target")" \
"$([ -e "$target" ] && printf 'false' || printf 'true')" \
"$(dev_kit_json_escape "$home")" \
"$([ -e "$home" ] && printf 'false' || printf 'true')"
return "$uninstall_status"
fi

rm -f "$stdout_file" "$stderr_file"
printf '{ "command": "uninstall", "ok": true, "binary": "%s", "binary_removed": %s, "home": "%s", "home_removed": %s }\n' \
"$(dev_kit_json_escape "$target")" \
"$([ -e "$target" ] && printf 'false' || printf 'true')" \
Expand Down
48 changes: 47 additions & 1 deletion lib/modules/output.sh
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,48 @@ dev_kit_spinner_notice() {
printf ' - %s\n' "$message" >&2
}

dev_kit_process_descendants() {
local root_pid="$1"

ps -eo pid=,ppid= | awk -v root="$root_pid" '
{
pid = $1
ppid = $2
children[ppid] = children[ppid] " " pid
}

function walk(node, list, count, idx) {
count = split(children[node], list, " ")
for (idx = 1; idx <= count; idx++) {
if (list[idx] == "") {
continue
}
walk(list[idx])
print list[idx]
}
}

END {
walk(root)
}
'
}

dev_kit_process_signal_tree() {
local signal="$1"
local root_pid="$2"
local child_pid=""

while IFS= read -r child_pid; do
[ -n "$child_pid" ] || continue
kill "-${signal}" "$child_pid" 2>/dev/null || true
done <<EOF
$(dev_kit_process_descendants "$root_pid")
EOF

kill "-${signal}" "$root_pid" 2>/dev/null || true
}

dev_kit_run_guarded() {
local label="$1"
local soft_timeout="${2:-$DEV_KIT_PROGRESS_SOFT_TIMEOUT}"
Expand Down Expand Up @@ -166,7 +208,11 @@ dev_kit_run_guarded() {
fi

if [ "$hard_timeout" -gt 0 ] && [ "$elapsed" -ge "$hard_timeout" ]; then
kill "$pid" 2>/dev/null || true
dev_kit_process_signal_tree TERM "$pid"
sleep 2
if kill -0 "$pid" 2>/dev/null; then
dev_kit_process_signal_tree KILL "$pid"
fi
wait "$pid" 2>/dev/null || true
dev_kit_spinner_stop ""
[ -s "$stdout_file" ] && cat "$stdout_file"
Expand Down
2 changes: 1 addition & 1 deletion lib/modules/repo_scaffold.sh
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ dev_kit_repo_is_contract_evidence_path() {
local path="$1"

case "$path" in
""|.git/*|.rabbit/context.yaml|AGENTS.md|.udx/*|.claude/*|.copilot/*|.cursor/*)
""|.git/*|.rabbit/context.yaml|.rabbit/dev.kit/*|AGENTS.md|.udx/*|.claude/*|.copilot/*|.cursor/*)
return 1
;;
esac
Expand Down
85 changes: 83 additions & 2 deletions tests/suite.sh
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,28 @@ should_run() {
return 1
}

replace_in_file() {
local file_path="$1"
local before="$2"
local after="$3"
local tmp_file=""

tmp_file="$(mktemp "${TMPDIR:-/tmp}/dev-kit-replace.XXXXXX")" || return 1
awk -v before="$before" -v after="$after" '
BEGIN { replaced = 0 }
{
line = $0
pos = index(line, before)
if (pos > 0 && replaced == 0) {
line = substr(line, 1, pos - 1) after substr(line, pos + length(before))
replaced = 1
}
print line
}
END { exit(replaced == 0) }
' "$file_path" >"$tmp_file" && mv "$tmp_file" "$file_path"
}

while [ "$#" -gt 0 ]; do
case "$1" in
--only)
Expand Down Expand Up @@ -88,6 +110,14 @@ done <<EOF
$(dev_kit_module_paths)
EOF

while IFS= read -r command_file; do
[ -n "$command_file" ] || continue
# shellcheck disable=SC1090
. "$command_file"
done <<EOF
$(find "$REPO_DIR/lib/commands" -maxdepth 1 -type f -name '*.sh' | sort)
EOF

if should_run "core"; then
guard_soft_output="$(
DEV_KIT_SPINNER_DISABLE=1 \
Expand All @@ -109,6 +139,24 @@ if should_run "core"; then
pass "guard: stops hard timeout"
assert_contains "$guard_hard_output" "dev.kit timeout: guard test exceeded 2s" "guard: reports hard timeout"

guard_child_pid_file="$TEST_HOME/guard-child.pid"
set +e
guard_group_output="$(
DEV_KIT_SPINNER_DISABLE=1 \
dev_kit_run_guarded "guard child test" 1 2 "repo resolving is taking longer than usual" \
bash -lc 'sleep 30 & child=$!; printf "%s\n" "$child" > "$1"; wait "$child"' _ "$guard_child_pid_file" 2>&1
)"
guard_group_status=$?
set -e
[ "$guard_group_status" -eq 124 ] || fail "guard: stops child process groups"
guard_child_pid="$(cat "$guard_child_pid_file")"
if kill -0 "$guard_child_pid" 2>/dev/null; then
kill "$guard_child_pid" 2>/dev/null || true
fail "guard: terminates child processes on timeout"
fi
pass "guard: terminates child processes on timeout"
assert_contains "$guard_group_output" "dev.kit timeout: guard child test exceeded 2s" "guard: reports child timeout"

cp -R "$DOCUMENTED_SHELL_REPO" "$HOME_ACTION_REPO"
rm -rf "$HOME_ACTION_REPO/.dev-kit"
rm -f "$HOME_ACTION_REPO/.rabbit/context.yaml"
Expand Down Expand Up @@ -153,6 +201,20 @@ if should_run "core"; then
assert_file_missing "$uninstall_bin_dir/dev.kit" "uninstall json: removes binary target"
assert_file_missing "$uninstall_home_dir" "uninstall json: removes home target"

set +e
uninstall_failure_json="$(
REPO_DIR="$TEST_HOME/missing-repo" \
DEV_KIT_BIN_DIR="$uninstall_bin_dir" \
DEV_KIT_HOME="$uninstall_home_dir" \
dev_kit_cmd_uninstall json --yes 2>/dev/null
)"
uninstall_failure_status=$?
set -e
[ "$uninstall_failure_status" -ne 0 ] || fail "uninstall json: fails when uninstall script fails"
pass "uninstall json: fails when uninstall script fails"
assert_contains "$uninstall_failure_json" "\"ok\": false" "uninstall json: reports failure"
assert_contains "$uninstall_failure_json" "\"error\":" "uninstall json: includes error message"

home_repeat_json="$(cd "$HOME_ACTION_REPO" && DEV_KIT_REPO_HARD_TIMEOUT=1 dev.kit --json)"
assert_contains "$home_repeat_json" "\"context_status\": \"existing\"" "home: reuses existing context"
assert_contains "$home_repeat_json" "\"context_reason\": null" "home: fresh context has no stale reason"
Expand All @@ -166,8 +228,10 @@ if should_run "core"; then
assert_contains "$home_repeat_text" "reference: docs/references/config-contract-surfaces.md" "home text: shows gap reference"
assert_contains "$home_repeat_text" "repair: fix repo-owned gaps, then rerun dev.kit repo" "home text: prints repair loop next step"

perl -0pi -e 's/No repo-owned configuration contract was found in docs, manifests, or checked-in example files\./Add .env.example, .env.sample, or .env.template when repo configuration is required./' \
"$HOME_ACTION_REPO/.rabbit/context.yaml"
replace_in_file \
"$HOME_ACTION_REPO/.rabbit/context.yaml" \
"No repo-owned configuration contract was found in docs, manifests, or checked-in example files." \
"Add .env.example, .env.sample, or .env.template when repo configuration is required."

stale_home_json="$(cd "$HOME_ACTION_REPO" && dev.kit --json)"
assert_contains "$stale_home_json" "\"context_status\": \"stale\"" "home: marks outdated context as stale"
Expand Down Expand Up @@ -196,9 +260,26 @@ if should_run "core"; then
assert_contains "$repo_text" "[next]" "repo text: renders next section"
assert_contains "$repo_text" "confirm whether to start the research-and-fix loop now" "repo text: confirms before repair loop"

set +e
repo_write_failure_output="$(
DEV_KIT_SPINNER_DISABLE=1
dev_kit_context_yaml_write() {
printf 'boom\n' >&2
return 42
}
dev_kit_cmd_repo text "$DOCUMENTED_SHELL_REPO" 2>&1
)"
repo_write_failure_status=$?
set -e
[ "$repo_write_failure_status" -eq 42 ] || fail "repo text: preserves non-timeout write failures"
pass "repo text: preserves non-timeout write failures"
assert_contains "$repo_write_failure_output" "Context write failed with exit status 42" "repo text: distinguishes non-timeout write failures"
assert_contains "$repo_write_failure_output" "boom" "repo text: preserves underlying write error"

self_repo_json="$(cd "$REPO_DIR" && dev.kit repo --json)"
assert_not_contains "$self_repo_json" "\"repo\": \"udx/dev.kit\"" "repo: omits self dependency contracts"
assert_not_contains "$(cat "$REPO_DIR/.rabbit/context.yaml")" "source_repo: udx/dev.kit" "repo: omits self source repo provenance"
assert_not_contains "$(cat "$REPO_DIR/.rabbit/context.yaml")" ".rabbit/dev.kit/" "repo: excludes generated rabbit evidence"

cp -R "$SIMPLE_REPO" "$SIMPLE_ACTION_REPO"
rm -rf "$SIMPLE_ACTION_REPO/.dev-kit"
Expand Down