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
18 changes: 2 additions & 16 deletions .rabbit/context.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ version: udx.dev/dev.kit/v1
generator:
tool: dev.kit
repo: https://github.com/udx/dev.kit
version: 0.12.0
generated_at: 2026-05-27T09:28:30Z
version: 0.13.0
generated_at: 2026-05-27T18:39:54Z
sources:
homepage: https://udx.dev/kit
repository: https://github.com/udx/dev.kit
Expand Down Expand Up @@ -56,20 +56,6 @@ commands:
run: make run
source: docs/references/command-surfaces.md

# Gaps — Factors that are missing or only partially supported by current repo signals.
# Note: Base the result on explicit factor rules, not free-form judgment.
# Note: Include message and evidence so the status can be reviewed.
# Note: Prefer traceable refs and missing signals over vague advice.

gaps:
- factor: config
status: partial
message: Found config-bearing repo assets in deploy.yml, but no canonical checked-in config contract is declared yet.
repair_target: deploy.yml or .env.example
reference: docs/references/config-contract-surfaces.md
evidence:
- runtime config: deploy.yml

# Dependencies — Meaningful dependency-repo contracts such as reusable workflows, images, or versioned manifests this repo relies on.
# Note: Capture execution-shaping behavior defined outside the current checkout.
# Note: Avoid promoting standard package inventory or ordinary GitHub action refs into top-level context.
Expand Down
4 changes: 4 additions & 0 deletions changes.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changes

### 0.13.0

- Allow repo-owned manifests to satisfy config contract coverage when they declare explicit config contract metadata or runtime config sections, avoiding forced `.env.example` files for repos with custom manifest contracts.

### 0.12.0

- Add generator source refs to `.rabbit/context.yaml` so generated context points to the dev.kit homepage, source repo, npm package, and installation guide.
Expand Down
1 change: 0 additions & 1 deletion deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,3 @@ config:
command: "/bin/bash"
args:
- "/workspace/tests/suite.sh"

2 changes: 1 addition & 1 deletion docs/context-coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ gaps:
message: No explicit configuration contract was detected.
```

That should lead to a repo change such as adding config docs, manifest metadata, or a checked-in example file, then regenerating context.
That should lead to a repo change such as adding config docs, adding explicit manifest metadata, declaring runtime config sections in a repo-owned manifest, or adding a checked-in example file, then regenerating context.

## What Does Not Belong There

Expand Down
4 changes: 3 additions & 1 deletion docs/references/config-contract-surfaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Configuration is often declared through:
- `.env.example`, `.env.sample`, or `.env.template`
- focused repo docs such as `README.md` or `docs/config.md`
- deploy manifests such as `deploy.yml`
- versioned YAML/JSON manifests with explicit config metadata
- versioned YAML/JSON manifests with explicit config metadata or runtime config sections
- checked-in example config files when the repo uses a custom format

## Build defaults and runtime overlays
Expand All @@ -26,6 +26,8 @@ Example pattern:

That is still one coherent repo contract as long as the split is explicit and checked in.

Custom manifests should not rely on filename rules. For YAML manifests, `dev.kit` treats explicit contract metadata such as `contract: config` or runtime config sections such as `config.env`, `config.environment`, `config.image`, `config.command`, or top-level `env`/`variables`/`settings` as configuration contract evidence.

## Practical rule

When config gaps are detected:
Expand Down
93 changes: 92 additions & 1 deletion lib/modules/repo_factors.sh
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,89 @@ dev_kit_repo_has_meaningful_dependency_contract() {
return 1
}

dev_kit_manifest_declares_config_contract() {
local manifest_path="$1"

[ -f "$manifest_path" ] || return 1

awk '
/^[[:space:]]*#/ { next }
/^[^[:space:]][^:]*:/ {
in_contracts = 0
in_config = 0
}
/^[[:space:]]*contracts:[[:space:]]*$/ {
in_contracts = 1
next
}
/^[[:space:]]*contracts:[[:space:]]*/ {
value = $0
sub(/^[[:space:]]*contracts:[[:space:]]*/, "", value)
gsub(/["'\''\[\],]/, " ", value)
if (value ~ /(^|[[:space:]])config([[:space:]]|$)/) {
found = 1
}
in_contracts = 0
next
}
/^[[:space:]]*contract:[[:space:]]*/ {
value = $0
sub(/^[[:space:]]*contract:[[:space:]]*/, "", value)
gsub(/["'\''\[\],]/, " ", value)
if (value ~ /(^|[[:space:]])config([[:space:]]|$)/) {
found = 1
}
next
}
in_contracts && /^[[:space:]]*-[[:space:]]*/ {
value = $0
sub(/^[[:space:]]*-[[:space:]]*/, "", value)
gsub(/["'\'',]/, " ", value)
if (value ~ /(^|[[:space:]])config([[:space:]]|$)/) {
found = 1
}
next
}
/^[[:space:]]*config:[[:space:]]*$/ {
in_config = 1
next
}
/^(env|environment|secrets|variables|settings):[[:space:]]*/ {
found = 1
next
}
in_config && /^[[:space:]]+[A-Za-z0-9_.-]+:[[:space:]]*/ {
value = $0
sub(/^[[:space:]]+/, "", value)
sub(/:.*/, "", value)
if (value ~ /^(env|environment|secrets|variables|settings|image|images|container|containers|service|services|command|args|volumes)$/) {
found = 1
}
next
}
END { exit(found ? 0 : 1) }
' "$manifest_path"
}

dev_kit_repo_config_contract_manifest_files() {
local repo_dir="$1"
local path=""

while IFS= read -r path; do
[ -n "$path" ] || continue
if dev_kit_manifest_declares_config_contract "${repo_dir}/${path}"; then
printf '%s\n' "$path"
fi
done <<EOF
$(dev_kit_repo_contract_manifest_files "$repo_dir")
EOF
}

dev_kit_repo_has_config_contract_manifest() {
local repo_dir="$1"
[ -n "$(dev_kit_repo_config_contract_manifest_files "$repo_dir")" ]
}

dev_kit_repo_factor_applicable() {
local repo_dir="$1"
local factor="$2"
Expand Down Expand Up @@ -120,7 +203,8 @@ _dev_kit_repo_factor_status_compute() {
fi
;;
config)
if dev_kit_repo_has_any_file_from_list "$repo_dir" "config_contract_files"; then
if dev_kit_repo_has_any_file_from_list "$repo_dir" "config_contract_files" || \
dev_kit_repo_has_config_contract_manifest "$repo_dir"; then
printf "%s" "present"
elif dev_kit_repo_has_any_file_from_list "$repo_dir" "config_runtime_files" || dev_kit_repo_documented_env_var "$repo_dir"; then
printf "%s" "partial"
Expand Down Expand Up @@ -230,6 +314,13 @@ EOF
fi
done <<EOF
$(dev_kit_detection_list "config_contract_files")
EOF
while IFS= read -r path; do
[ -n "$path" ] || continue
evidence="${evidence}config contract manifest: ${path}
"
done <<EOF
$(dev_kit_repo_config_contract_manifest_files "$repo_dir")
EOF
while IFS= read -r path; do
[ -n "$path" ] || continue
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@udx/dev-kit",
"version": "0.12.0",
"version": "0.13.0",
"description": "Context-driven engineering toolkit for AI agents and developers",
"license": "MIT",
"repository": {
Expand Down
3 changes: 2 additions & 1 deletion tests/fixtures/docker-repo/deploy.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
kind: workerDeployConfig
version: udx.io/worker-v1/deploy

service:
config:
image: "acme/docker-repo:latest"
30 changes: 30 additions & 0 deletions tests/suite.sh
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,27 @@ replace_in_file() {
' "$file_path" >"$tmp_file" && mv "$tmp_file" "$file_path"
}

json_factor_status() {
local json="$1"
local factor="$2"

printf '%s\n' "$json" | awk -v factor="$factor" '
$0 ~ "\"" factor "\":[[:space:]]*\\{" {
in_factor = 1
next
}
in_factor && /"status":[[:space:]]*"/ {
sub(/^.*"status":[[:space:]]*"/, "", $0)
sub(/".*$/, "", $0)
print
exit
}
in_factor && /^ }/ {
exit
}
'
}

while [ "$#" -gt 0 ]; do
case "$1" in
--only)
Expand All @@ -104,6 +125,8 @@ DEV_KIT_BIN_DIR="$TEST_HOME/.local/bin"
mkdir -p "$DEV_KIT_BIN_DIR"
ln -sf "$REPO_DIR/bin/dev-kit" "$DEV_KIT_BIN_DIR/dev.kit"
export PATH="$DEV_KIT_BIN_DIR:$PATH"
assert_contains "$(command -v dev.kit)" "$DEV_KIT_BIN_DIR/dev.kit" "suite: uses local dev.kit shim"
assert_contains "$(dev.kit --version)" "$(awk -F'"' '/"version"/{print $4; exit}' "$REPO_DIR/package.json")" "suite: uses checkout dev.kit version"

# shellcheck disable=SC1090
. "$DEV_KIT_HOME/bin/env/dev-kit.sh"
Expand Down Expand Up @@ -146,6 +169,8 @@ if should_run_explicit "repo-contract"; then

docker_repo_json="$(cd "$DOCKER_ACTION_REPO" && dev.kit repo --json)"
assert_contains "$docker_repo_json" "\"context\":" "repo contract: docker repo reports context path"
assert_contains "$(json_factor_status "$docker_repo_json" config)" "present" "repo contract: reports present config factor"
assert_contains "$docker_repo_json" "config contract manifest: deploy.yml" "repo contract: manifest config shape satisfies config contract"

docker_context_yaml="${DOCKER_ACTION_REPO}/.rabbit/context.yaml"
assert_contains "$(cat "$docker_context_yaml")" "path: deploy.yml" "repo contract: includes deploy manifest"
Expand Down Expand Up @@ -364,6 +389,8 @@ if should_run "core"; then

docker_repo_json="$(cd "$DOCKER_ACTION_REPO" && dev.kit repo --json)"
assert_contains "$docker_repo_json" "\"context\":" "docker repo: reports context path"
assert_contains "$(json_factor_status "$docker_repo_json" config)" "present" "docker repo: reports present config factor"
assert_contains "$docker_repo_json" "config contract manifest: deploy.yml" "docker repo: manifest config shape satisfies config contract"

docker_context_yaml="${DOCKER_ACTION_REPO}/.rabbit/context.yaml"
assert_contains "$(cat "$docker_context_yaml")" "generator:" "docker repo: includes generator metadata"
Expand All @@ -385,13 +412,16 @@ EOF
cat > "$IGNORED_ACTION_REPO/deploy.yml" <<'EOF'
version: udx.io/worker-v1/deploy
kind: workerDeployConfig
metadata:
env: staging
EOF
cat > "$IGNORED_ACTION_REPO/.next/cache/reference.txt" <<'EOF'
deploy.yml
EOF

ignored_repo_json="$(cd "$IGNORED_ACTION_REPO" && dev.kit repo --json)"
assert_contains "$ignored_repo_json" "\"context\":" "ignored repo: reports context path"
assert_contains "$ignored_repo_json" "no canonical checked-in config contract is declared yet" "ignored repo: deploy filename alone does not satisfy config contract"
ignored_context_yaml="${IGNORED_ACTION_REPO}/.rabbit/context.yaml"
assert_not_contains "$(cat "$ignored_context_yaml")" ".next/cache/reference.txt" "ignored repo: excludes gitignored artifact references"

Expand Down