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: 26 additions & 1 deletion .github/workflows/nightly-install.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,40 @@ jobs:
# (~/.kiro, ~/.claude, ~/.codex, ~/.config/opencode). With a fresh HOME
# there is no Python 3.12 either, so this also exercises the script's
# `uv python install` fallback every night.
#
# The script refuses, by design, when no supported AI tool is present
# ("No supported AI tools detected"), and the runner has none: no
# harness binary on PATH and nothing in the fresh HOME. So the fixture
# plants the four harness markers `detect_available_agent_tools()`
# reads without a binary -- ~/.kiro, ~/.claude, ~/.pi, ~/.grok -- and
# the install-agents step then exercises all four link sets for real.
# (Without this the job failed every night from 2026-09-15, before its
# verify steps ever ran; pinned by test_ci_workflow_contract.py.)
env:
HOME: ${{ runner.temp }}/home
UV_TOOL_DIR: ${{ runner.temp }}/tools
UV_TOOL_BIN_DIR: ${{ runner.temp }}/bin
run: mkdir -p "$HOME" && ./scripts/install.sh --non-interactive --no-smoke
run: |
mkdir -p "$HOME/.kiro" "$HOME/.claude" "$HOME/.pi" "$HOME/.grok"
./scripts/install.sh --non-interactive --no-smoke

- name: Verify installed CLI entry points
env:
UV_TOOL_BIN_DIR: ${{ runner.temp }}/bin
run: |
"$UV_TOOL_BIN_DIR/studyloop" --version
"$UV_TOOL_BIN_DIR/session-export" --help

- name: Verify installed agent definitions
# One artefact per planted harness, each a path `install agents`
# writes for that tool (installers.py _TOOL_LINKS / _configure_*).
env:
HOME: ${{ runner.temp }}/home
run: |
test -e "$HOME/.kiro/agents/study-mentor.json"
test -e "$HOME/.kiro/agents/study-plan-architect.json"
test -e "$HOME/.claude/agents/socratic-mentor.md"
test -e "$HOME/.claude/agents/study-plan-architect.md"
test -e "$HOME/.pi/agent/AGENTS.md"
test -e "$HOME/.grok/hooks/studyloop.json"
test -e "$HOME/.grok/rules/session-db.md"
8 changes: 6 additions & 2 deletions openspec/changes/plan-integration-followons/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,11 @@ writer, through the existing gate, closes that. Kept out of item 3 so item 3's f
byte-equal afterwards; single `main` on both sides. (3) closeout comments — done: posted on #8–#15 from the
re-verified draft (`receipts/issue-closeout-draft-2026-09-16.md`, status paragraph records the shas rewritten
and the rows overtaken); #8, #9, #11, #12, #13, #14 closed as completed; #10 and #15 open (row 3b); #7
reopened (auto-closed by PR #20's body) with the parent mapping. PR #20's body left as merged. **Open:**
(4) the two item-6 issues (after T6.4) and #21's closing comment or decision; (5) the local tag
reopened (auto-closed by PR #20's body) with the parent mapping. PR #20's body left as merged. (4) the two
item-6 issues — done 2026-09-19: **#25** (D-D derived plan bias) and **#26** (D-E nudge + retire/snooze), both
`ready-for-agent`, bodies drawn from the proposals with every cited test id, symbol and path checked against
`main` `a03fc9bd`; #21 given a status comment from the harness receipt and **left open** (its DoD names OpenCode
in core; OpenCode stays preview on the `opencode.db` exporter and the no-completed-reply gaps). **Open:**
(5) the local tag
`archive/feat-clean-start-2026-09-15` — owner: push or discard; (6) the GitHub Support ticket text — owner;
(7) revoke both tokens and delete `~/tmp/.env` — owner (D-J).
54 changes: 54 additions & 0 deletions packages/studyloop/tests/test_ci_workflow_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,3 +236,57 @@ def test_sast_and_pre_commit_bandit_skip_lists_agree() -> None:
)

assert sast_skipped == precommit_skipped == ci_standards_skipped


NIGHTLY_WORKFLOW = WORKFLOW_DIR / "nightly-install.yml"


def _nightly_workflow() -> dict[str, Any]:
return yaml.safe_load(NIGHTLY_WORKFLOW.read_text(encoding="utf-8"))


def test_nightly_installer_job_plants_a_harness_before_running_install_sh() -> None:
"""The nightly `installer` job (A4, added 2026-09-14) isolates HOME so
`studyloop install agents` writes into scratch -- and had never passed:
an empty HOME has no harness, `detect_available_agent_tools()` finds
nothing, and `install.sh` exits 1 at "Installing agent definitions"
exactly as the README says it should when no supported AI tool exists.
The job died five nights running before its verify step ever ran.

The fixture must supply the precondition the script documents: at least
one harness marker directory that the detector reads without a binary
(`~/.kiro`, `~/.claude`, `~/.pi`, `~/.grok`), created in the isolated
HOME by the same step that runs the script. And the job must then check
what `install agents` wrote, or the isolation buys nothing.
"""
data = _nightly_workflow()
installer = data["jobs"]["installer"]
steps = installer["steps"]
run_step = next(step for step in steps if step.get("name") == "Run scripts/install.sh")

assert run_step["env"]["HOME"] == "${{ runner.temp }}/home", (
"HOME isolation is the point of the job; it must stay"
)
run = run_step["run"]
assert "./scripts/install.sh --non-interactive --no-smoke" in run

planted = [
marker
for marker in ("$HOME/.kiro", "$HOME/.claude", "$HOME/.pi", "$HOME/.grok")
if marker in run
]
assert planted, (
"the installer job runs install.sh in an empty HOME; plant at least one "
"directory-detected harness marker first or `install agents` exits 1"
)
Comment on lines +273 to +281
plant_at = min(run.index(marker) for marker in planted)
assert plant_at < run.index("./scripts/install.sh"), "plant the marker BEFORE the script runs"

verify = next(
(step for step in steps if step.get("name") == "Verify installed agent definitions"),
None,
)
assert verify is not None, "the job must verify what `install agents` wrote into HOME"
assert verify["env"]["HOME"] == "${{ runner.temp }}/home"
for planted_marker in planted:
assert planted_marker in verify["run"], f"verify step does not look inside {planted_marker}"
Loading