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
27 changes: 25 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,13 +304,36 @@ With the default `github.token`, the repository or organization must allow GitHu
| `model` | both | empty | Default model for both analysis and parsing. |
| `agent_model` | both | empty | Analysis-only override for `model`. |
| `parsing_model` | both | empty | Parsing-only override for `model`. |
| `depth_cap` | both | `2` | Positive integer maximum analysis depth, including full-analysis fallbacks. Changing it rebuilds incompatible state. |
| `github_token` | both | `${{ github.token }}` | Token for comments and sync delivery. |
| `sync_strategy` | sync | `push` | `push` or `pull_request`. |
| `target_branch` | sync | event branch | Branch receiving the baseline or rolling PR. |
| `force_full` | sync | `false` | Ignore the committed baseline for this run. |
| `warmstart_retention_days` | review | `1` | Days to keep the reusable analysis. Only the next run reads it. |

The `/codeboarding` command, comment heading, Mermaid direction (`LR`), hosted webview URL, rolling sync branch, commit message, and CodeBoarding 0.14.0 version are intentionally fixed rather than exposed as configuration.
The `/codeboarding` command, comment heading, Mermaid direction (`LR`), hosted webview URL, rolling sync branch, commit message, and CodeBoarding 0.14.1 version are intentionally fixed rather than exposed as configuration.

Review mode needs no sync workflow or committed `.codeboarding` directory. If no
usable merge-base analysis exists, it runs full analysis there directly, then
seeds an incremental analysis of the PR head and publishes both states. Later
runs prefer compatible prior PR state for incremental updates, while the review
still compares the merge base with the current head.

Set `depth_cap` in the action's `with:` block (for example, `depth_cap: 4`).
This configuration is authoritative: stored `metadata.depth_cap` is checked for
compatibility, not inherited, and legacy `metadata.depth_level` is not used as a
fallback. Missing or incompatible baseline depth triggers a rebuild.

`metadata.depth_cap` records the configured maximum; `metadata.depth_level`
records the depth actually reached, which can be shallower. Comparing the cap
avoids rejecting valid state or reducing future rebuild depth when a run stops
early. The action input matches the metadata name and the engine receives only
`--depth-cap`. There are no old-name input aliases. Historical workflows using
the removed `depth_level` action input must switch to `depth_cap`.

This action pins Core 0.14.1 for the `--depth-cap` CLI contract. Publish that Core
release before releasing the action. Earlier eShop evidence predates this final
breaking CLI migration.

## Outputs

Expand Down Expand Up @@ -347,7 +370,7 @@ Run the local analysis pipeline:

```bash
export OPENROUTER_API_KEY=sk-or-...
python -m pip install codeboarding==0.14.0
python -m pip install codeboarding==0.14.1
tests/run_local.sh --repo /path/to/repo --base main --head feature
```

Expand Down
9 changes: 8 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ inputs:
description: 'Optional parsing-model override. Takes precedence over model.'
required: false
default: ''
depth_cap:
description: 'Positive integer analysis depth cap, used for both review and sync, including full fallbacks.'
required: false
default: '2'
github_token:
description: 'Token used for comments and sync delivery.'
required: false
Expand Down Expand Up @@ -353,7 +357,7 @@ runs:
if: steps.guard.outputs.skip != 'true'
shell: bash
run: |
python -m pip install --disable-pip-version-check 'codeboarding==0.14.0'
python -m pip install --disable-pip-version-check 'codeboarding==0.14.1'
# Fail here, with the reason, rather than mid-analysis with a traceback:
# pinning a release does not pin what its dependencies resolve to. Run
# the console script, not `python -c`, which would put the analyzed
Expand Down Expand Up @@ -382,6 +386,7 @@ runs:
IS_FORK: ${{ steps.guard.outputs.is_fork }}
LLM_PROVIDER: ${{ steps.llm.outputs.provider }}
BACKEND_ID: ${{ steps.llm.outputs.backend_id }}
DEPTH_CAP: ${{ inputs.depth_cap }}
MODEL: ${{ inputs.model }}
AGENT_MODEL_INPUT: ${{ inputs.agent_model }}
PARSING_MODEL_INPUT: ${{ inputs.parsing_model }}
Expand Down Expand Up @@ -430,6 +435,7 @@ runs:
CHECKOUT_DIR: ${{ github.workspace }}/.codeboarding-target
STAGE_DIR: ${{ runner.temp }}/cb-state/${{ github.action }}/out
FORCE_FULL: ${{ inputs.force_full }}
DEPTH_CAP: ${{ inputs.depth_cap }}
MODEL: ${{ inputs.model }}
AGENT_MODEL_INPUT: ${{ inputs.agent_model }}
PARSING_MODEL_INPUT: ${{ inputs.parsing_model }}
Expand Down Expand Up @@ -521,6 +527,7 @@ runs:
CFG_HASH: ${{ steps.state.outputs.cfg_hash }}
GIT_TOKEN: ${{ inputs.github_token }}
GITHUB_SERVER_URL: ${{ github.server_url }}
DEPTH_CAP: ${{ inputs.depth_cap }}
MODEL: ${{ inputs.model }}
AGENT_MODEL_INPUT: ${{ inputs.agent_model }}
PARSING_MODEL_INPUT: ${{ inputs.parsing_model }}
Expand Down
9 changes: 6 additions & 3 deletions docs/COMMIT_STRATEGY.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,16 @@ them:

| Source | Engine cost |
|---|---|
| the published `codeboarding-base-<cfg>-<merge_base>` artifact | none |
| no artifact — check out the merge base, seed from the baseline committed there, catch up | one incremental |
| no committed baseline either | full analysis |
| the published `codeboarding-base-<cfg>-<merge_base>` artifact with a compatible depth cap | none |
| no usable artifact — check out the merge base, seed from a compatible baseline committed there, catch up | one incremental, full if Core requires it |
| no compatible committed baseline either | full analysis directly, at the configured `depth_cap` |

A trusted run that computed the base publishes it, so the next pull request
forking from that commit gets the first row.

The configuration hash includes `depth_cap`. The workflow input controls depth
for both fresh and fallback analyses; stored legacy depth values never override it.

**Head**, first match wins:

| Source | Covers |
Expand Down
48 changes: 22 additions & 26 deletions scripts/action/analyze.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
#!/usr/bin/env bash
# Runs incremental/full Core analysis and outputs the selected analysis paths and mode.
set -euo pipefail
DEPTH_CAP="${DEPTH_CAP:-2}"
if [[ ! "$DEPTH_CAP" =~ ^[1-9][0-9]*$ ]]; then
echo "::error::depth_cap must be a positive integer."
exit 1
fi
parse_output() {
local output="$1"
ANALYSIS_MODE="$(awk -F= '$1 == "analysis_mode" {print $2; exit}' <<< "$output")"
Expand All @@ -20,23 +25,21 @@ incremental() {
full() {
local checkout="$1" output_dir="$2" depth="$3" output
output="$(python3 "$ACTION_PATH/scripts/analyze_repository.py" full \
--checkout "$checkout" --output-dir "$output_dir" --depth-level "$depth")"
--checkout "$checkout" --output-dir "$output_dir" --depth-cap "$depth")"
parse_output "$output"
if [ "$ANALYSIS_MODE" != full ] || [ ! -f "$ANALYSIS_PATH" ]; then
echo "::error::Invalid full-analysis result."
exit 1
fi
}
# Core resolves depth from depth_cap, falling back to depth_level for baselines
# predating it. Use the same value everywhere: a run that stopped short of its
# cap must not be read as a scope change, and rebuilding at the realized depth
# would ratchet the configured depth down every time a full run happens.
# Metadata is only a compatibility check, never the source of configuration.
# Legacy baselines without a configured cap are rebuilt at the requested depth.
depth_cap_from() {
local analysis="$1"
[ -f "$analysis" ] || return 0
python3 -c 'import json,sys
metadata = json.load(open(sys.argv[1])).get("metadata", {})
print(metadata.get("depth_cap", metadata.get("depth_level", "")))' "$analysis" 2>/dev/null || true
print(metadata.get("depth_cap", ""))' "$analysis" 2>/dev/null || true
}

seed_state() {
Expand Down Expand Up @@ -120,16 +123,13 @@ analyze_sync() {
local work="$RUNNER_TEMP/codeboarding-sync" state="$RUNNER_TEMP/codeboarding-sync/analysis"
rm -rf "$work"
seed_state "$CHECKOUT_DIR" "$state"
local depth
depth="$(depth_cap_from "$state/analysis.json")"
depth="${depth:-2}"

if [ "${FORCE_FULL,,}" = true ]; then
full "$CHECKOUT_DIR" "$state" "$depth"
if [ "${FORCE_FULL,,}" = true ] || [ "$(depth_cap_from "$state/analysis.json")" != "$DEPTH_CAP" ]; then
full "$CHECKOUT_DIR" "$state" "$DEPTH_CAP"
else
incremental "$CHECKOUT_DIR" "$state"
if [ "$REQUIRES_FULL" = true ]; then
full "$CHECKOUT_DIR" "$state" "$depth"
full "$CHECKOUT_DIR" "$state" "$DEPTH_CAP"
fi
fi
# Sync already computes the graph every review of this branch compares against,
Expand All @@ -148,14 +148,12 @@ fetch_commit() {
"${GITHUB_SERVER_URL%/}/${repository}.git" "$sha" --depth=1
}

# The artifact name pins the engine, the analysis scope and the models. Depth and
# lineage are read from the bundle itself, so they are checked here.
# The artifact name pins configuration; verify the stored cap and lineage too.
warmstart_usable() {
local base_analysis="$1" bundle_cap base_cap
local base_analysis="$1" bundle_cap
[ -f "${WARMSTART_DIR:-}/analysis.json" ] || return 1
bundle_cap="$(depth_cap_from "$WARMSTART_DIR/analysis.json")"
base_cap="$(depth_cap_from "$base_analysis")"
if [ -n "$bundle_cap" ] && [ -n "$base_cap" ] && [ "$bundle_cap" != "$base_cap" ]; then
if [ "$bundle_cap" != "$DEPTH_CAP" ]; then
echo "::notice::Analysis depth changed since the last run; re-seeding from the base analysis."
return 1
fi
Expand All @@ -178,28 +176,26 @@ analyze_review() {
# needs no engine run at all. Without one, the merge base is checked out and
# analyzed from whatever baseline the repository committed there.
local base_source=published
if [ -f "${BASE_DIR:-}/analysis.json" ]; then
if [ "$(depth_cap_from "${BASE_DIR:-}/analysis.json")" = "$DEPTH_CAP" ]; then
mkdir -p "$base_state"
cp -a "$BASE_DIR/." "$base_state/"
else
base_source=computed
fetch_commit "$REVIEW_BASE_REPO" "$REVIEW_BASE_SHA"
git -C "$CHECKOUT_DIR" worktree add --detach "$base_checkout" "$REVIEW_BASE_SHA" >/dev/null
seed_state "$base_checkout" "$base_state"
local base_depth
base_depth="$(depth_cap_from "$base_state/analysis.json")"
incremental "$base_checkout" "$base_state"
REQUIRES_FULL=true
if [ "$(depth_cap_from "$base_state/analysis.json")" = "$DEPTH_CAP" ]; then
incremental "$base_checkout" "$base_state"
fi
if [ "$REQUIRES_FULL" = true ]; then
full "$base_checkout" "$base_state" "${base_depth:-2}"
full "$base_checkout" "$base_state" "$DEPTH_CAP"
fi
fi
unset GIT_TOKEN

local base_analysis="$base_state/analysis.json"
[ -f "$base_analysis" ] || { echo "::error::Review baseline analysis is missing."; exit 1; }
local depth
depth="$(depth_cap_from "$base_analysis")"
depth="${depth:-2}"

# Seed the head from this pull request's own last analysis when there is one,
# so the run only covers commits pushed since it.
Expand All @@ -219,7 +215,7 @@ analyze_review() {

incremental "$CHECKOUT_DIR" "$head_state"
if [ "$REQUIRES_FULL" = true ]; then
full "$CHECKOUT_DIR" "$head_state" "$depth"
full "$CHECKOUT_DIR" "$head_state" "$DEPTH_CAP"
fi

write_origin "$head_state" "$seed_source" "$chain_depth" "$(analysis_digest "$base_analysis")"
Expand Down
4 changes: 2 additions & 2 deletions scripts/action/state-names.sh
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ ignore_file="$CHECKOUT_DIR/.codeboarding/.codeboardingignore"
# does. It carries no key: rotating a secret must not throw away reusable analysis.
model_digest="$(printf '%s\n%s\n%s\n%s\n%s\n' \
"${LLM_PROVIDER:-}" "${BACKEND_ID:-}" "${MODEL:-}" "${AGENT_MODEL_INPUT:-}" "${PARSING_MODEL_INPUT:-}" | digest)"
cfg="$(printf '%s\n%s\n%s\n%s\n' \
"$STATE_SCHEMA" "$engine_version" "$ignore_digest" "$model_digest" | digest)"
cfg="$(printf '%s\n%s\n%s\n%s\n%s\n' \
"$STATE_SCHEMA" "$engine_version" "$ignore_digest" "$model_digest" "${DEPTH_CAP:-2}" | digest)"

{
echo "engine_version=$engine_version"
Expand Down
2 changes: 1 addition & 1 deletion scripts/action/supported-providers.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"a selection env (AWS_DEFAULT_REGION, OLLAMA_API_KEY) therefore cannot select a provider",
"on its own, which is why ollama and litellm need their base URL and not just a key."
],
"engine": "0.14.0",
"engine": "0.14.1",
"hosted_provider": "openrouter",
"providers": {
"openrouter": {
Expand Down
10 changes: 5 additions & 5 deletions scripts/analyze_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def main(argv: list[str] | None = None) -> int:
parser.add_argument("mode", choices=["incremental", "full"], help="Which CLI command to invoke")
parser.add_argument("--checkout", required=True, help="Path to repository checkout")
parser.add_argument("--output-dir", required=True, help="Action-owned output directory")
parser.add_argument("--depth-level", help="Depth passed to full analyses")
parser.add_argument("--depth-cap", help="Maximum hierarchy depth allowed for full analyses")

args = parser.parse_args(argv)
checkout = Path(args.checkout)
Expand All @@ -112,8 +112,8 @@ def main(argv: list[str] | None = None) -> int:
print(f"analysis_path={analysis_path or ''}")
return 0

if not args.depth_level:
raise SystemExit("--depth-level is required for mode=full")
if not args.depth_cap:
raise SystemExit("--depth-cap is required for mode=full")
shutil.rmtree(output_dir)
output_dir.mkdir(parents=True)
command = [
Expand All @@ -123,8 +123,8 @@ def main(argv: list[str] | None = None) -> int:
str(checkout),
"--output-dir",
str(output_dir),
"--depth-level",
args.depth_level,
"--depth-cap",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep the adapter compatible with the pinned Core release

Every full-analysis path now invokes Core with --depth-cap, but action.yml:356-360 still installs CodeBoarding 0.14.0, and the added release-blocker note in README.md:334-338 explicitly confirms that this version does not support the flag. Consequently, cold review baselines, missing or incompatible sync baselines, force_full, and incremental fallbacks all exit before producing an analysis. Continue translating the public depth_cap input to 0.14.0's supported --depth-level flag, or bump the engine pin and provider table to a compatible release.

AGENTS.md reference: AGENTS.md:L12-L16

Useful? React with 👍 / 👎.

args.depth_cap,
"--force",
]
_run_command(command, output_dir)
Expand Down
14 changes: 5 additions & 9 deletions tests/run_local.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ set -euo pipefail
# 2) REVIEW LOCAL (full local pipeline):
# tests/run_local.sh --repo /path/to/repo --base <base-ref> --head <head-ref>
# 3) REVIEW LOCAL against committed baseline only (if available):
# tests/run_local.sh --repo /path/to/repo --base <base-ref> --head <head-ref> --depth 2
# tests/run_local.sh --repo /path/to/repo --base <base-ref> --head <head-ref> --depth-cap 2
#
# Output:
# diagram.md Mermaid payload posted by the action
Expand All @@ -29,7 +29,7 @@ while [ $# -gt 0 ]; do
--base-json) BASE_JSON="$2"; shift 2;;
--head-json) HEAD_JSON="$2"; shift 2;;
--out) OUT="$2"; shift 2;;
--depth) DEPTH="$2"; shift 2;;
--depth-cap) DEPTH="$2"; shift 2;;
--direction) DIRECTION="$2"; shift 2;;
--no-open) OPEN="no"; shift;;
-h|--help)
Expand Down Expand Up @@ -67,7 +67,7 @@ run_full() {
python3 "$ACTION_DIR/scripts/analyze_repository.py" full \
--checkout "$checkout" \
--output-dir "$out_dir" \
--depth-level "$DEPTH"
--depth-cap "$DEPTH"
}

if [ -n "$BASE_JSON" ] && [ -n "$HEAD_JSON" ]; then
Expand Down Expand Up @@ -111,14 +111,10 @@ else

BASELINE_DEPTH=""
if [ -f "$BASE_DIR/.codeboarding/analysis.json" ]; then
BASELINE_DEPTH="$(python3 -c 'import json, sys; data = json.load(open(sys.argv[1])); print(data.get("metadata", {}).get("depth_level", ""))' "$BASE_DIR/.codeboarding/analysis.json" 2>/dev/null || true)"
BASELINE_DEPTH="$(python3 -c 'import json, sys; data = json.load(open(sys.argv[1])); print(data.get("metadata", {}).get("depth_cap", ""))' "$BASE_DIR/.codeboarding/analysis.json" 2>/dev/null || true)"
fi

if [ -n "$BASELINE_DEPTH" ] && [[ "$BASELINE_DEPTH" =~ ^[0-9]+$ ]]; then
DEPTH="$BASELINE_DEPTH"
fi

if [ -f "$BASE_DIR/.codeboarding/analysis.json" ]; then
if [ "$BASELINE_DEPTH" = "$DEPTH" ]; then
cp -a "$BASE_DIR/.codeboarding/." "$BASE_STATE/"
BASE_OUTPUT="$(run_inc "$BASE_DIR" "$BASE_STATE")"
BASE_MODE="$(parse_value analysis_mode "$BASE_OUTPUT")"
Expand Down
8 changes: 8 additions & 0 deletions tests/test_action_inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@ def test_llm_is_required_and_has_no_default(self) -> None:
self.assertIn("required: true", block)
self.assertNotIn("default:", block)

def test_depth_is_wired_to_state_identity_and_both_analysis_modes(self) -> None:
self.assertIn("default: '2'", self.inputs["depth_cap"])
self.assertNotIn("depth_level", self.inputs)
for identifier in ("id: state", "id: sync_analyze", "id: review_analyze"):
start = ACTION.index(identifier)
block = ACTION[start : ACTION.index("\n run:", start)]
self.assertIn("DEPTH_CAP: ${{ inputs.depth_cap }}", block)

def test_the_inferred_credential_inputs_are_gone(self) -> None:
"""`llm_api_key`/`llm_provider` are what made a fallback expressible at all."""
for stale in ("llm_api_key", "llm_provider"):
Expand Down
Loading
Loading